test: raise coverage and stop persisting checkout credentials - #595
Conversation
BodyValidator checked for text after the subject twice. The message is stripped before it is split, so its first and last lines both carry text: whenever there is more than one line there are at least two non-empty ones, and the first check has already passed. The second check could never run. BaseValidator.validate is abstract; its docstring is the whole body, so the trailing `pass` is dropped as well.
The suite stood at 99% of lines (22 missed) and 98% with branches. These tests reach every remaining line and all but one branch: - engine: the base subject rule, a subject with no leading word, an author with no identity anywhere, an empty message under the sign-off rule, co-authors read from a --rev commit, a merge-base target that resolves to no ref, push lines that name no ref pair, an unresolvable tag push, ignore_authors for an author not on the list, a single accepted disclosure trailer, a co-author fix withheld by the pattern, and an unknown check in the detailed run - api: validate_author with a name only and with no arguments, validate_push without a [push] table in the defaults - main: pre-commit push data with no remote tip, stdin left unread without a check that takes it, a --fix that never satisfies its rule - config, rule builder and util helpers: a scalar section in the unknown-setting scan, a found config that vanishes before it is read, files = false, a blank GITHUB_REF_NAME, submodule entries in ls-tree output, a git diagnostic in the identity lookup The one branch left is the tag rule builder's guard for a catalog entry other than "tag", which the catalog does not contain.
Three patterns shipped with commit-check backtrack super-linearly on a long line, so a long enough commit message holds up the hook or the CI job that checks it: - CC012's sign-off pattern `Signed-off-by: .+ <.+@.+>` is cubic in a line that never closes the address: 2.5 KB took 2.6 s, and 15 KB was still running after 20 s. Each part now stops at the first delimiter that can end it, and only the first "Signed-off-by: " on a line is read. - find_trailers (the disclose policy) read a value with a lazy `[^\n]*?` before trailing blanks: quadratic in a run of blanks. - The header pattern behind every CC001 fix suggestion had the same lazy shape, so any subject CC001 rejects went through it. The replacements match exactly what the old patterns matched. Tests compare each one with its predecessor over 20,000 generated inputs, and run inputs that took the old patterns over 15 s, which now finish in well under 0.1 s.
A setting in [commit], [branch] or [push] whose value has the wrong type either crashed or was quietly read as something else: - `commit = 5`, or `[[commit]]` (a list of tables), and a non-string message or author pattern stopped the run with a bare AttributeError and exit code 1, the code a rejected commit gets. - `ignore_authors = 5` raised a TypeError from inside the engine, which reads that list itself. - A string where a list belongs was read a character at a time: `allow_commit_types = "feat,fix"` allowed the types f, e, a, t, i, x, and `ignore_authors = "bot"` skipped any author whose name is part of "bot". - `allow_force_push = "false"` counted as true and let force pushes through; `subject_max_length = "72"` switched the rule off. The rule builder now checks each value against the type of its default before it builds anything, and refuses a mismatch as a config error (exit code 2) that names the setting, as it already does for a regex that does not compile. [files] and [tag] keep their lenient handling, and so do the AI settings, whose readers already check their values. The README's list of exit-code-2 causes says so.
The build, install, benchmark and publish jobs never push or fetch with the token after checkout, so actions/checkout no longer leaves it in .git/config (zizmor: artipacked). commit-check.yml and scorecard.yml already did this. Dependabot now waits seven days after a release before proposing it, for both GitHub Actions and pip (zizmor: dependabot-cooldown).
GitHub notes on these runs that ubuntu-latest moves to Ubuntu 26 from October 19, 2026. The CodSpeed and Commit Check jobs were the only ones still on ubuntu-latest; they now name ubuntu-24.04 like every other job, so the image (and the Python builds setup-python finds for it) changes when the repository decides, not on GitHub's schedule. actionlint: - publish-package.yml: a release trigger takes no branches filter; GitHub ignored it, so dropping it changes nothing. - codspeed.yml: quote ".[test]" so the shell cannot read it as a glob (shellcheck SC2102).
pre-commit hooks: - ruff-pre-commit v0.15.20 -> v0.15.22, the last 0.15 release. 0.16 turns on 413 lint rules by default instead of 59 (123 findings here) and formats Markdown code blocks, so adopting it is left as a decision rather than folded into this update. - mirrors-mypy v2.1.0 -> v2.3.1. mypy now reads through a getattr with a literal name, so _LazyOpener.handlers says plainly that typeshed does not declare OpenerDirector.handlers. - codespell v2.4.2 -> v2.4.3 - commit-check v2.17.0 -> v2.18.2, the latest published release GitHub Actions: codecov/codecov-action v7.0.0 -> v7.1.1, pinned to its commit as before; it only adds an optional `cleanup` input. uv.lock: `uv lock --upgrade`, including pytest 9.1.1, pytest-codspeed 5.0.3, pytest-mock 3.16.0 and coverage 7.16.2, the versions CI already installs. The suite passes against the lock on Python 3.10 and 3.14.
On Python 3.10 four config tests failed, and reached the network on the way: the inherit_from and URL loading tests patch "commit_check.config._opener.open", and none of those patches took. Two tomli fallback tests import commit_check.config afresh and then put the original back in sys.modules, but not on the commit_check package, whose `config` attribute the fresh import had rebound. Python 3.10's patch() resolves a dotted target through package attributes, so every later patch landed on the copy while the code under test used the original. Python 3.11 resolves it through sys.modules, which is why the suite only fails on 3.10, which CI does not run the tests on. Both tests now restore the attribute too. The suite passes on 3.10 and 3.14 with DNS lookups blocked.
v2.18.2 is the latest release on PyPI and its tag exists, so the snippet installs it instead of v2.18.1 (AGENTS.md).
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. Note Currently processing new changes in this PR. This may take a few minutes, please wait... ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (12)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #595 +/- ##
===========================================
+ Coverage 99.02% 100.00% +0.97%
===========================================
Files 14 14
Lines 2257 2252 -5
===========================================
+ Hits 2235 2252 +17
+ Misses 22 0 -22 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Merging this PR will not alter performance
Comparing Footnotes
|
The two type-check tests built the RuleBuilder inside the block, so a ConfigError from the constructor would have passed for one from build_all_rules (SonarCloud python:S5778). The builder is now made first, and only the call under test sits in the block.
The type check runs every time rules are built, which the Python API does on every call. It called get_default_config() each time and checked each value through a generator, adding about 6.4 us to a build that takes about 15 us; CodSpeed reported the API benchmarks 15-18% slower. The default types are now worked out once at import, and a value of exactly its default's type passes with one comparison (a list, with one pass over its items). Anything else still goes through the full check, so what is accepted and refused does not change. The check now costs about 2 us on a full config and 0.5 us on a partial one.
The linear CC012 pattern scanned every line a character at a time through an alternation, and backtracked over the scan when a line had no sign-off: about 5 us per message, against 0.25 us for the cubic pattern it replaced. It now skips whole lines, and takes each part up to its first delimiter with a lookahead and a backreference: Python's lookaheads are atomic, so nothing is scanned twice. About 1.3 us per message, still linear (2.6 ms for 300 KB of adversarial input, 61 ms for a million lines), and still accepting exactly what `Signed-off-by: .+ <.+@.+>` accepts: checked against it on 1.2 million generated messages, and by the existing tests.
Quoting `.[test]` changed the line's text, so SonarCloud no longer matched it to the two findings on it (githubactions:S8541 and S8544) that are already resolved as won't-fix on main. It raised them again as new code, and they failed the PR's quality gate on security rating. The line is back as it is on main. actionlint's shellcheck note on it (SC2102, info level) returns, and is harmless: no file in the checkout matches `.[test]` as a glob.
A value of exactly its default's type now takes a shortcut, so a subclass, such as an IntEnum from a Python caller, goes the long way round. This test shows the long way still accepts it, and covers the line that does.
Co-authored-by: Xianpeng Shen <xianpeng.shen@gmail.com>
Dependabot already applies a 3-day cooldown to version updates by default, which is enough here.
The maintainer asked for this pull request to carry only SHA pins, persist-credentials: false and tests that raise coverage. The source changes go back to main, to be proposed separately: - "fix: keep three built-in patterns linear on long input" and its follow-up "refactor: speed up the linear sign-off pattern ..." - "fix: refuse config values of the wrong type", its follow-up "refactor: work out the config setting types once ...", and the README sentence it added - "refactor: drop unreachable code from the engine" The tests that only pass with those changes go with them: the pattern equivalence and linear-time tests, and the setting-type tests in rule_builder_test.py, api_test.py and main_test.py. The coverage tests, which pass against the source on main, stay.
Dependency and documentation updates are out of scope for this pull request, so these go back to main: - the pre-commit hook revisions (ruff, mypy, codespell, commit-check), with the typing change in config.py that the newer mypy needed - codecov/codecov-action v7.1.1, back to v7.0.0 - the uv.lock refresh - the README's pre-commit pin, back to v2.18.1
Dropping it was an actionlint clean-up with no effect on when the workflow runs, and other workflow edits are out of scope for this pull request. persist-credentials: false on the checkout stays.
With the dead code back in place, two spots in engine.py cannot run: - BaseValidator.validate is abstract, and no subclass calls it through super(), so its `pass` never runs. - BodyValidator's second body check: the message is stripped before it is split, so two or more lines always hold two non-empty ones, and the first check has already passed. Both are marked `# pragma: no cover`, which changes nothing at run time. Every other line is covered by tests.
|



Summary
This pull request makes three kinds of change:
mainis already pinned to a full commit SHA, and each SHA matches the tag in its comment. The two org reusable workflows reference@main, a branch, and are left as they are.persist-credentials: falseon 4actions/checkoutsteps that never use the token after checkout: thebuildandinstalljobs inmain.yml, CodSpeed, and the publish job.commit-check.ymlandscorecard.ymlalready set it.commit_checkpackage, asnox -s coveragemeasures it):main.# pragma: no coveron two lines inengine.pythat cannot run: the abstractBaseValidator.validatebody, and a second body check inBodyValidatorthat the first check always pre-empts.config_test.pynow restorescommit_check.configafter the tomli fallback tests. Before this, four config tests failed on Python 3.10 and made real HTTP requests.The earlier commits on this branch are undone by commits on top rather than rewritten, because AGENTS.md asks for additive commits and no force pushes. The diff contains only the changes above.
How it was verified
pre-commit run --all-filespasses.mainalready has.