Skip to content

test: drop the filePrefix assertion its exact pin already subsumes - #99

Merged
thedavidmeister merged 4 commits into
mainfrom
2026-08-16-issue-72
Aug 17, 2026
Merged

test: drop the filePrefix assertion its exact pin already subsumes#99
thedavidmeister merged 4 commits into
mainfrom
2026-08-16-issue-72

Conversation

@thedavidmeister

@thedavidmeister thedavidmeister commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Closes #72

Deletes testFilePrefixNamesNoScript, and folds its intent into the docstring of
testFilePrefixExact, the sibling that already subsumes it.

This PR previously deleted a second test, testBytesToHexHasNoPrefix. That
deletion is reverted — the test is restored, and
test/lib/LibHexString.bytesToHex.t.sol is now byte-identical to main.
Issue
#72 bundled both deletions under one rationale; the rationale holds for one of
them. Correction posted on the issue:
#72

Why the correction

The brief this PR was written under said every fix ships a test and that
verify-by-hand is never acceptable, with no exemption for changes that have no
behaviour. That was wrong and is corrected. Here it pushed a deletion argument
past what the evidence supported: a per-input implication between two fuzz tests
was read as one test subsuming the other.

filePrefix() takes no arguments and is pure, so its domain is a single point.
testFilePrefixExact assertEqs the whole return value, so exactly one string
passes it — every string the deleted test rejected is rejected there too, and so
is every other string. One input, one implication, total subsumption.

bytesToHex is not that. On any given output, "every byte is in [0-9a-f]"
does imply "no byte is x" — x is 0x78, outside the alphabet. But
testBytesToHexHasNoPrefix and testBytesToHexCharset are two fuzz tests over
bytes memory, not two assertions on one input. foundry.toml sets
[fuzz] runs = 2048 and pins no seed, so each runs its own independent
2048-draw campaign and each test's coverage of the domain is its own draws.
Deleting the weaker sibling cost 2048 draws of the property over the same domain.
That is coverage, not redundancy.

A mutation matrix cannot rescue that half either. Both tests dying on the same
mutant is exactly what a per-input implication predicts; it cannot show the
survivor would catch a defect only some of the deleted test's own draws reach.

Mutation matrix — the deletion that stands

Measured on the pre-change tree — origin/main at b422d97, where both
tests are still present, so they can be compared on the same mutant in the same
run. Whole --match-path file every time, never a name filter that could match
nothing. src/lib/LibCodeGen.sol restored with git checkout after each mutant
and confirmed clean with git status --short.

Unmutated first, so a red below is the mutant and not a broken harness:

Ran 2 tests for test/lib/LibCodeGen.filePrefix.t.sol:LibCodeGenFilePrefixTest
[PASS] testFilePrefixExact() (gas: 3885)
[PASS] testFilePrefixNamesNoScript() (gas: 3707)
Suite result: ok. 2 passed; 0 failed; 0 skipped
# mutant in src/lib/LibCodeGen.sol deleted test surviving sibling
M1 note text → AUTOGENERATED BY script/Build.sol testFilePrefixNamesNoScript FAILprefix names a script testFilePrefixExact FAIL
M1b emitted pragma ^0.8.25^0.8.26 PASS testFilePrefixExact FAIL

M1 is the subsumption proof — the mutant the deleted test caught, the survivor
caught too. M1b is the converse, and it is why this is a deletion rather than a
swap: the survivor is strictly stronger, killing a mutant the deleted test
sails through.

M1, verbatim

Ran 2 tests for test/lib/LibCodeGen.filePrefix.t.sol:LibCodeGenFilePrefixTest
[FAIL: assertion failed: // SPDX-License-Identifier: LicenseRef-DCL-1.0
// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd
pragma solidity ^0.8.25;

// THIS FILE IS AUTOGENERATED BY script/Build.sol. DO NOT EDIT BY HAND.
 != // SPDX-License-Identifier: LicenseRef-DCL-1.0
// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd
pragma solidity ^0.8.25;

// THIS FILE IS AUTOGENERATED BY THE BUILD SCRIPT. DO NOT EDIT BY HAND.
] testFilePrefixExact() (gas: 3921)
[FAIL: prefix names a script] testFilePrefixNamesNoScript() (gas: 4247)
Suite result: FAILED. 0 passed; 2 failed; 0 skipped

M1b, verbatim — the strictly-stronger direction

Ran 2 tests for test/lib/LibCodeGen.filePrefix.t.sol:LibCodeGenFilePrefixTest
[FAIL: assertion failed: ... pragma solidity ^0.8.26; ...] testFilePrefixExact() (gas: 3921)
[PASS] testFilePrefixNamesNoScript() (gas: 3707)
Suite result: FAILED. 1 passed; 1 failed; 0 skipped

Post-deletion re-check

M1 re-applied to this branch's tree (36a640b), where the deleted test is gone,
to confirm the behaviour is still caught without it:

### unmutated
[PASS] testFilePrefixExact() (gas: 3884)
Suite result: ok. 1 passed; 0 failed; 0 skipped
### M1
[FAIL: assertion failed: // SPDX-License-Identifier: LicenseRef-DCL-1.0 ...
Suite result: FAILED. 0 passed; 1 failed; 0 skipped

The prefix literal is in fact pinned in a third place —
test/lib/LibFs.buildFileForContract.t.sol:51 rebuilds it from the literal text —
so the .sol-free property has more behind it than the pair the issue named.

Nothing here is left unguarded, and nothing moves to rainix#317

Stated explicitly because the sweep that produced this correction asks every PR in
it to say plainly what it stops guarding.

This PR stops guarding nothing. The one deletion it keeps is over a single-point
domain where the surviving pin is provably stronger, so there is no input on which
testFilePrefixExact passes and the deleted test would have failed — M1b is that
asymmetry measured. No check is being rehomed, so rainlanguage/rainix#317 is not
this PR's business.

QA

  • Merged origin/main (b422d97) into the branch — no rebase, no force-push. The
    branch predated merged PRs Unlink a symlink with no target before writing the generated file #110 and Declare every tooling builder view so an implementation can read state #124; the merge was clean.
  • The whole diff against origin/main is one file,
    test/lib/LibCodeGen.filePrefix.t.sol, −9 / +6.
  • nix develop -c forge test on origin/main (b422d97):
    Ran 18 test suites: 142 tests passed, 0 failed, 0 skipped (142 total tests).
  • nix develop -c forge test on this branch:
    Ran 18 test suites: 141 tests passed, 0 failed, 0 skipped (141 total tests),
    exit 0. The count drops by exactly 1 — the one deleted test.
  • Both fuzz tests report full campaigns in that run:
    [PASS] testBytesToHexHasNoPrefix(bytes) (runs: 2048, μ: 24199, ~: 19406) and
    [PASS] testBytesToHexCharset(bytes) (runs: 2048, μ: 29504, ~: 22485) — the
    restored test is running, not merely compiling.
  • nix develop -c forge fmt --check → exit 0.
  • Oracle for the deletion: the literal expected text, not the library.
    testFilePrefixExact spells the prefix out as a string literal rather than
    calling filePrefix(). Consumers hold that literal committed in their own repos,
    which is what makes the literal the oracle.
  • No src/ behaviour changed: the diff is test-only.

Note on the TDD order

The remaining finding is redundant coverage, so there is no failing-test-first
step to run: nothing was broken to begin with, and the change removes an assertion
rather than adding behaviour. The mutation matrix is the evidence in its place —
the deleted test was shown to catch a real mutant, and its survivor to catch that
one and one more.

🤖 Generated with Claude Code


Post-Build.sol-removal sweep (2026-08-17)

main (959d527) merged in, no conflict. Unaffected by #138's removal —
nothing cut. The removal does not weaken the deletion argument; it strengthens
the docstring the intent was folded into, since after #138 this repo names no
build script at all and "each consumer names its own" is the whole rule.

Re-verified against main rather than restated:

Suite on the merge commit: Ran 19 test suites: 144 tests passed, 0 failed, 0 skippedmain's 145 minus the one deleted test, nothing else moved.
forge fmt --check clean, git status clean after the run. forge coverage --no-match-coverage "test|script" still 100% lines / statements / branches /
funcs on all three src/lib files (79/79, 92/92, 10/10, 18/18), so the deletion
costs no coverage.

`testFilePrefixNamesNoScript` asserted the prefix holds no `.sol`.
`testFilePrefixExact` pins the whole prefix, so exactly one string passes
it and every string naming a `.sol` file fails it.

`testBytesToHexHasNoPrefix` asserted no output byte is `x`.
`testBytesToHexCharset` asserts every output byte is in `[0-9a-f]`, an
alphabet that excludes `x`, and both fuzz the same `bytes` domain for
2048 runs.

Each survivor carries the removed intent in its docstring.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@thedavidmeister thedavidmeister self-assigned this Aug 16, 2026
@coderabbitai

coderabbitai Bot commented Aug 16, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@thedavidmeister, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 57 minutes

Limit details: You’ve used all 1 included review currently available under your plan.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4ef0ac20-ea51-4e86-a8bb-a0e8562444e0

📥 Commits

Reviewing files that changed from the base of the PR and between 959d527 and b6fd17a.

📒 Files selected for processing (1)
  • test/lib/LibCodeGen.filePrefix.t.sol

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@thedavidmeister

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 16, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

thedavidmeister and others added 2 commits August 16, 2026 19:23
Two fuzz tests over one domain are not one test. `x` is outside
`[0-9a-f]`, so on any single output `testBytesToHexCharset` implies
`testBytesToHexHasNoPrefix` — but `[fuzz] runs = 2048` with no pinned
seed means each of them draws its own 2048 inputs, and the survivor's
coverage of `bytes memory` is its own draws. Deleting the weaker
sibling cost 2048 draws of the property, which is coverage rather than
redundancy.

The `testFilePrefixNamesNoScript` deletion stands. `filePrefix()` takes
no arguments and is `pure`, so `testFilePrefixExact` pins the only
input there is and subsumption is total.

Drops the docstring paragraph added to `testBytesToHexCharset` to stand
in for the deleted test, since the test is back.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@thedavidmeister thedavidmeister changed the title test: drop two assertions a sibling in the same file already implies test: drop the filePrefix assertion its exact pin already subsumes Aug 16, 2026
@thedavidmeister
thedavidmeister merged commit ed5ecdd into main Aug 17, 2026
4 checks passed
thedavidmeister added a commit that referenced this pull request Aug 17, 2026
The automated merge at 5c68149 adds no commit this branch lacks - it is the
same origin/main@c8440fb merged with a different resolution. Two reasons this
tree wins:

- 5c68149 restores testFilePrefixNamesNoScript, which #99 deliberately removed
  as subsumed by testFilePrefixExact. filePrefix() is nullary and pure, so the
  exact pin is a real subsumption and the name assertion is dead weight.
- 5c68149 predates the reorder of testBuildFileForContractCarriesTheCallers-
  LicenceAndCopyright to read then clean up then assert, and the constant
  docstrings that went with it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

testFilePrefixNamesNoScript and testBytesToHexHasNoPrefix assert only what a sibling in the same file already implies

2 participants