Skip to content

🧪 Sync coveragepy setup w/ other projects - #13422

Merged
webknjaz merged 12 commits into
aio-libs:masterfrom
webknjaz:testing/coverage-sync-modern-sdist-install
Aug 14, 2026
Merged

🧪 Sync coveragepy setup w/ other projects#13422
webknjaz merged 12 commits into
aio-libs:masterfrom
webknjaz:testing/coverage-sync-modern-sdist-install

Conversation

@webknjaz

Copy link
Copy Markdown
Member

What do these changes do?

Specifically, this change configures the top-level project dir as the global source, put the Python package into source_pkgs by name, and set up the path mapping across the Git checkout and installed site-packages location. This is a follow-up to #13388 that revealed a slight misconfiguration in coveragepy's ability to properly collect coverage from the installed project copy.

Are there changes in behavior for the user?

Nope.

Is it a substantial burden for the maintainers to support this?

No.

Related issue number

#13388. Sorta.

Checklist

  • I think the code is well written
  • Unit tests for the changes exist
  • Documentation reflects the changes
  • If you provide code modification, please add yourself to CONTRIBUTORS.txt
    • The format is <Name> <Surname>.
    • Please keep alphabetical order, the file is sorted by names.
  • Add a new news fragment into the CHANGES/ folder
    • name it <issue_or_pr_num>.<type>.rst (e.g. 588.bugfix.rst)

    • if you don't have an issue number, change it to the pull request
      number after creating the PR

      • .bugfix: A bug fix for something the maintainers deemed an
        improper undesired behavior that got corrected to match
        pre-agreed expectations.
      • .feature: A new behavior, public APIs. That sort of stuff.
      • .deprecation: A declaration of future API removals and breaking
        changes in behavior.
      • .breaking: When something public is removed in a breaking way.
        Could be deprecated in an earlier release.
      • .doc: Notable updates to the documentation structure or build
        process.
      • .packaging: Notes for downstreams about unobvious side effects
        and tooling. Changes in the test invocation considerations and
        runtime assumptions.
      • .contrib: Stuff that affects the contributor experience. e.g.
        Running tests, building the docs, setting up the development
        environment.
      • .misc: Changes that are hard to assign to any of the above
        categories.
    • Make sure to use full sentences with correct case and punctuation,
      for example:

      Fixed issue with non-ascii contents in doctest text files
      -- by :user:`contributor-gh-handle`.

      Use the past tense or the present tense a non-imperative mood,
      referring to what's changed compared to the last released version
      of this project.

@webknjaz webknjaz added infra backport-3.14 Trigger automatic backporting to the 3.14 release branch by Patchback robot backport-3.15 Trigger automatic backporting to the 3.15 release branch by Patchback robot labels Aug 14, 2026
@webknjaz

Copy link
Copy Markdown
Member Author

@aiolibsbot review, this is supposed to be a very small scoped patch recovering the coverage metrics of all the lines aiohttp/_websocket/reader_c.py and one partial line in the circular imports test.

@codecov

codecov Bot commented Aug 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 66.66667% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 98.31%. Comparing base (203c1c5) to head (c1b13b4).
⚠️ Report is 15 commits behind head on master.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
tests/autobahn/test_autobahn.py 66.66% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #13422      +/-   ##
==========================================
- Coverage   98.41%   98.31%   -0.10%     
==========================================
  Files         133      131       -2     
  Lines       49746    49704      -42     
  Branches     2629     2619      -10     
==========================================
- Hits        48958    48867      -91     
- Misses        663      711      +48     
- Partials      125      126       +1     
Flag Coverage Δ
Autobahn ?
CI-GHA 98.32% <66.66%> (+<0.01%) ⬆️
OS-Linux 98.09% <66.66%> (+<0.01%) ⬆️
OS-Windows 96.45% <66.66%> (-0.01%) ⬇️
OS-macOS 97.33% <66.66%> (-0.02%) ⬇️
Py-3.10 97.54% <66.66%> (-0.02%) ⬇️
Py-3.11 97.79% <66.66%> (-0.01%) ⬇️
Py-3.12 97.88% <66.66%> (-0.02%) ⬇️
Py-3.13 97.87% <66.66%> (+<0.01%) ⬆️
Py-3.14 97.88% <66.66%> (-0.01%) ⬇️
Py-3.14t 96.97% <66.66%> (+<0.01%) ⬆️
Py-pypy-3.11 96.85% <66.66%> (+0.01%) ⬆️
VM-macos 97.33% <66.66%> (-0.02%) ⬇️
VM-ubuntu 98.09% <66.66%> (+<0.01%) ⬆️
VM-windows 96.45% <66.66%> (-0.01%) ⬇️
cython-coverage 82.19% <ø> (+44.04%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

@codspeed-hq

codspeed-hq Bot commented Aug 14, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 84 untouched benchmarks
⏩ 83 skipped benchmarks1


Comparing webknjaz:testing/coverage-sync-modern-sdist-install (c1b13b4) with master (203c1c5)2

Open in CodSpeed

Footnotes

  1. 83 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

  2. No successful run was found on master (c1b13b4) during the generation of this report, so 203c1c5 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@aiolibsbot

Copy link
Copy Markdown
Contributor

@webknjaz: @aiolibsbot review, this is supposed to be a very small scoped patch recovering the coverage metrics...

Reviewed. Scope stays small. Confirmed .coveragerc.toml auto-discovery — coveragepy tries it right after .coveragerc in config_files_to_try(), so no --cov-config needed.

One caveat worth knowing: ci-cd.yml passes --cov=aiohttp/ --cov=tests/ on every run. That overrides [run] source. So source = ['.'] stays inert in CI. source_pkgs plus [paths] carry reader_c.py recovery — that part works.

Second caveat: bare local pytest --cov under source = ['.'] traces in-tree .venv. Verified empirically. cover_pylib = false won't stop it.

Rest: nits only. No blockers.

@aiolibsbot

Copy link
Copy Markdown
Contributor

PR Review — 🧪 Sync coveragepy setup w/ other projects

Merge-ready. The substantive part of the change is correct and well-reasoned; everything I found is a nit.

Specific things done well: source_pkgs = ['aiohttp'] is the right lever for the installed-copy problem — I confirmed it survives CI's --cov=aiohttp/ --cov=tests/ override (pytest-cov only overrides source), so the [paths] remapping plus relative_files = true genuinely does what the PR body claims. Adding core = 'ctrace' to .coveragerc-cython.toml is a real future-proofing win, since Cython.Coverage cannot run under the sysmon core that becomes default on 3.14. Keeping [html] skip_covered = false as a counterweight to [report] skip_covered = true shows the interaction was thought through. I also verified .coveragerc.toml is picked up despite never appearing in a --cov-config flag — coveragepy explicitly tries it in config_files_to_try() — and that parallel = true does not strand data, because both pytest-cov and the standalone coverage xml step combine automatically (checked empirically).

  • source = ['.'] is overridden by CI's --cov= arguments, so that half of the change is a no-op in CI; locally it makes a bare pytest --cov trace an in-tree .venv (verified empirically) since cover_pylib = false does not cover site-packages
  • [html] show_contexts = true is inert: dynamic_context is commented out and no --cov-context=test is passed anywhere
  • Commented-out fail_under = 100 and plugins = ['covdefaults'] look carried over from sibling projects; covdefaults is not in requirements/test.in
  • The disable_warnings NOTE says "false negative" where it means "false positive"
  • No CHANGES/ fragment yet — .contrib or .packaging fits, otherwise chronographer will need the skip label

🟢 Suggestions

1. `source = ['.']` is inert in CI and traces an in-tree `.venv` locally
.coveragerc.toml:63-65

Two observations about switching source from ['aiohttp', 'tests'] to ['.'].

1. It never takes effect in CI. .github/workflows/ci-cd.yml passes --cov=aiohttp/ --cov=tests/ on every coverage run (lines 320, 336, 477, 599). pytest-cov forwards those as Coverage(source=...), which goes through config.from_args() and overrides [run] source from the config file. So the "top-level project dir as the global source" part of this change is a no-op for CI.

The good news: source_pkgs is not overridden by --cov, so source_pkgs = ['aiohttp'] plus the new [paths] mapping is what actually delivers the installed-copy fix described in the PR body. If you want the config to be authoritative, the follow-up is dropping the --cov=aiohttp/ --cov=tests/ arguments (keeping a bare --cov so pytest-cov still activates — it only enables itself when cov_source is truthy, see pytest_cov/plugin.py:202).

2. For a bare local pytest --cov, source = ['.'] measures anything under the repo root, including an in-tree .venv. Verified empirically with this exact config:

Name                                                        Stmts   Miss  Cover
.venv/lib/python3.12/site-packages/thirdparty/__init__.py       2      0   100%
pkg/__init__.py                                                 4      1    75%

cover_pylib = false only excludes the stdlib, not site-packages. And because the [paths] mapping rewrites */lib/python*/site-packages to ., those files map to paths that do not exist in the checkout, so the alias is skipped and they stay in the report as raw .venv/... entries. Both CI (astral-sh/setup-uv with activate-environment: true) and typical local setups put .venv in the working tree.

An omit = ['.venv/*', '*/site-packages/*'] (or keeping explicit source dirs) would close that off.

source = [
  '.',
]
source_pkgs = [
  'aiohttp',
]
2. `[html] show_contexts` has no contexts to show
.coveragerc.toml:2

show_contexts = true renders the per-line context UI in the HTML report, but nothing in this repo records contexts:

  • dynamic_context is deliberately commented out on line 57 (with a correct rationale about the pytest-cov conflict).
  • No --cov-context=test is passed in .github/workflows/ci-cd.yml or in the Makefile's cov-dev target.

So the setting produces an empty contexts column rather than useful data. Either pair it with --cov-context=test in make cov-dev, or drop it until contexts are actually collected — otherwise the next person reading this config will assume context data exists and go looking for it.

[html]
show_contexts = true
3. No CHANGES fragment

AGENTS.md requires a fragment in CHANGES/{pr_or_issue}.{type}.rst, and the PR checklist leaves that box unticked. This change alters test-invocation/coverage-collection assumptions, which is exactly what the .contrib (contributor experience, running tests) or .packaging (test invocation considerations) types cover.

Without it the chronographer check will fail. If a note genuinely is not wanted for a CI-only tweak, the alternative is requesting the bot:chronographer:skip label — but one or the other is needed before this can merge green.


Checklist

  • No hardcoded secrets or credentials
  • Config is actually wired up and takes effect — suggestion #1, suggestion #2
  • Coverage data is combined before reporting (parallel mode)
  • No unintended measurement scope expansion — suggestion #1
  • Diff matches PR description, no scope creep
  • Changelog fragment per AGENTS.md — suggestion #3
  • No backward-incompatible change to public API or user-facing behavior

Automated review by Kōan (Claude) HEAD=e25d6d1 7 min 11s

Comment thread .coveragerc-cython.toml Outdated
'^ +\.\.\.$',
'pytest.fail\('
'pytest.fail\(',
'^\s*@pytest\.mark\.xfail',

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't ignore xfail tests, these catch real regressions in our code.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, you implied this in the past, but I still disagree and this is and accepted practice across many projects. The thing with the xfail tests is that these tend to execute an arbitrary number of instructions which is rather pointless for the measurements and don't contribute any meaningful value. Instead, they are quite harmful in coverage reports, in practice.

Comment thread .coveragerc.toml Outdated
]
# https://coverage.rtfd.io/en/latest/contexts.html#dynamic-contexts
# dynamic_context = 'test_function' # conflicts with `pytest-cov` if set here
parallel = true

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I have my autobahn job breakage suspect here ^

The tests invoke coverage run in subprocess directly in a non-parallel but append mode which isn't exactly well-integrated.

The CI has a few subtle non-highlighted hints in the middle of the output:

----------------------------- Captured stdout call -----------------------------
Full documentation is at https://coverage.readthedocs.io/en/7.15.4
----------------------------- Captured stderr call -----------------------------
Can't append to data files in parallel mode.
Use 'coverage help' for help.

(https://github.com/aio-libs/aiohttp/actions/runs/31819519795/job/94849977268?pr=13422#step:6:199)

But I'm 99% sure this is it.

There's an easy way to check this. I'll try to deal with a better integration later. But for now, I could just unset the option.

Suggested change
parallel = true
# NOTE: tests/autobahn/test_autobahn.py::test_{client,server}
# NOTE: pass `-a|--append` to `coverage run` wich conflicts
# NOTE: with `-p|--parallel-mode`. We cannot override it on
# NOTE: the CLI level, which is why it's unset here.
# Ref: https://discord.com/channels/267624335836053506/1253355750684753950/1537906072474488973
# parallel = true

Comment thread .coveragerc-cython.toml Outdated
]
# https://coverage.rtfd.io/en/latest/contexts.html#dynamic-contexts
# dynamic_context = 'test_function' # conflicts with `pytest-cov` if set here
parallel = true

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
parallel = true
# parallel = true

@webknjaz
webknjaz force-pushed the testing/coverage-sync-modern-sdist-install branch from 66daf7f to cbc8a04 Compare August 14, 2026 21:25
webknjaz and others added 9 commits August 14, 2026 23:34
Specifically, configure the top-level project dir as the global source,
put the Python package into `source_pkgs` by name, and set up the path
mapping across the Git checkout and installed site-packages location.
It's impossible to override it from the `coverage run` CLI invocation in autobahn tests: https://discord.com/channels/267624335836053506/1253355750684753950/1537906072474488973
Sam's convinced this helps catch bugs. We've agreed to table this for
now and try to figure out a way to split the test runs on the infra
level to get the best of the both worlds later.
@webknjaz
webknjaz force-pushed the testing/coverage-sync-modern-sdist-install branch from cbc8a04 to d83589d Compare August 14, 2026 21:56
@webknjaz
webknjaz force-pushed the testing/coverage-sync-modern-sdist-install branch from b346d9b to d9068eb Compare August 14, 2026 23:03
pytest tests/test_client_functional.py tests/test_http_parser.py tests/test_http_writer.py tests/test_web_functional.py tests/test_web_response.py tests/test_websocket_parser.py
--cov-config=.coveragerc-cython.toml --cov=aiohttp/ --cov=tests/ --numprocesses=auto
--cov-config=.coveragerc-cython.toml --cov
--cov-report=xml:cython-coverage.xml

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually wanted to try w/o the :cython-coverage.xml part so that it'd just output the standard coverage.xml and it did, but it was making codecov/codecov-action crazy for reasons beyond my imagination.

Comment thread setup.cfg
known_third_party=jinja2,pytest,multidict,yarl,gunicorn,freezegun
known_first_party=aiohttp,aiohttp_jinja2,aiopg

[report]

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is sorta related to the cleanup in terms of “one place for the coverage config”. I was trying to see if we could have just one .coveragerc.toml. But that didn't work out. The env var trick that @nedbat hinted over @ discord worked for the parallel mode setting which was a string/bool but not for the plugins (list of strings).

Comment on lines +110 to +111
sys.executable,
"-m",

@webknjaz webknjaz Aug 14, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This started being necessary as soon as I started passing env= (for setting COVERAGE_PARALLEL_MODE=false to disable parallelism in coveragepy) — looks like it stopped being able to find coverage on $PATH.

),
env={
"COVERAGE_PARALLEL_MODE": "false",
**os.environ.copy(),

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After giving it a thought, I figured, I'd still pass all those env vars from the parent process just in case.

Comment thread .coveragerc-cython.toml
# * https://discord.com/channels/267624335836053506/1253355750684753950/1537914331629486130
parallel = '${COVERAGE_PARALLEL_MODE-true}'
plugins = [
'Cython.Coverage',

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we could make loading this conditional via something like an in-tree plugin that injects things into the config or something...

@psf-chronographer psf-chronographer Bot added the bot:chronographer:provided There is a change note present in this PR label Aug 14, 2026
@webknjaz
webknjaz merged commit 356d355 into aio-libs:master Aug 14, 2026
44 of 47 checks passed
@patchback

patchback Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Backport to 3.15: #13423

@patchback

patchback Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Backport to 3.14: #13424

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport-3.14 Trigger automatic backporting to the 3.14 release branch by Patchback robot backport-3.15 Trigger automatic backporting to the 3.15 release branch by Patchback robot bot:chronographer:provided There is a change note present in this PR infra

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants