test: mutation-harden LibOpConditions coverage - #536
Conversation
Adversarial mutation testing of LibOpConditions surfaced three surviving mutants, all in the odd-input (reason-carrying) code paths: - end calc that ignores oddInputs (reason slot included in the loop range) - output placement for odd inputs (sub(end, 0x20) instead of end) - removal of the no-condition-met revert guard The existing reference-check fuzz test truncates odd inputs to even, so the odd-input success path was never asserted, and the no-condition-met revert was masked by the reference implementation's own revert. Add discriminating tests: - eval of conditions with an odd number of inputs where a condition is met, asserting the matched value (3-input and 5-input cases) - a direct run() call asserting the output stack-top placement for an odd-input match (pointer, not just value) - direct run() revert tests (isolated from the reference implementation) for the no-condition-met case, both even (empty reason) and odd (trailing reason) Tests-only; no source changes. All three mutants are now killed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Important Review skippedToo many files! This PR contains 394 files, which is 294 over the limit of 100. To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch. Upgrade to a paid plan to raise the limit. Usage-priced reviews support at most 300 files. ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Team Run ID: ⛔ Files ignored due to path filters (13)
📒 Files selected for processing (394)
You can disable this status message by setting the ✨ Finishing Touches🧪 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 |
|
🤖 ai:producer |
|
🤖 ai:vetter |
|
🤖 ai:producer |
|
👤 human |
|
Rework note @655a02b46c65ba0d61b03119dc7ac57236437b5c: rework the PR to fit the split release lifecycle — deploys never gate merges (the deploy-before-merge choreography is superseded); remove or restructure anything in the PR that waits on a deploy; where deploy constants/pins are involved, follow the *.deploy repo convention (audited code only; version ↔ snapshot ↔ pins internally consistent; tag-release lifecycle). Whatever states follow the rework (including a typed blocked-on the repo's migration if one is genuinely needed) are the producer's ordinary transitions. Executes the 2026-08-06 ruling: rainlanguage/issue-pr-cron#221 |
`.soldeerignore` is a publish filter, not a description of the tree, so an entry naming a file that is absent today is normally a forward-looking rule worth keeping. These three are not: they are tied to a dependency mechanism this repo no longer uses and cannot go back to. - `.gitmodules` and `/lib` are the git-submodule vendoring layout. Rainix CI runs a `no-submodules` check that fails on a root `.gitmodules` or any committed gitlink, so no future commit can reintroduce either path for these entries to filter. - `/foundry.lock` is Foundry's git-submodule lockfile — it pins the commit of each dependency vendored under `lib/` so `forge install` / `forge update` can restore identical revisions. It is only meaningful alongside that same banned layout. `soldeer.lock` is the live lockfile here and everything lands under `dependencies/`, because `foundry.toml` sets `libs = ['dependencies']`. Every other entry is left alone, including the ones absent from a clean checkout by design (`.DS_Store`, `/deployments`) — those are OS junk and build output that IS present when `soldeer push` runs, so they still filter. `forge soldeer push --dry-run` packs a byte-identical 525-entry set before and after this change. Closes #553 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
main landed the same test refactor this branch carried in 44fab0c ("[3b-attempt] test: one contract per file") via #549 (b051c94, 6bc2663). Every extracted helper this branch added now exists on main, so the branch's copy is superseded: the four conflicting files (BaseRainlangSubParser.subParseLiteral2.t.sol, MismatchedLiteralSubParser.sol, NoMatchLiteralSubParser.sol, ZeroFPRainlangInterpreter.sol) resolve to main's version. No test is dropped: after this merge the branch differs from main by exactly test/src/lib/op/logic/LibOpConditions.t.sol (+96), which is the mutation-hardening this PR is about.
recursive_deps=false means vendored dep src resolves through this repo's remappings, and three reachable deps hard-pin rain-solmem-0.1.3/ in src, so the bump carries its minimum chain to keep one solmem tree: - rain-solmem 0.1.3 -> 0.1.26 - rain-interpreter-interface 0.1.0 (orphaned name) -> rainlang-interface 0.2.5 (successor package; src delta vs 0.1.0 is only the solmem and rain-math-binary import pins) - rain-datacontract 0.1.0 -> 0.1.3 - rain-extrospection 0.1.1 -> 0.1.13 - rain-math-binary 0.1.1 -> 0.1.4 (required by rainlang-interface 0.2.x) solmem 0.1.26 deleted LibStackPointer; its only two rainlang imports were dead (using-directive never invoked) and are removed, not substituted. Regenerated src/generated pointers to fixpoint via script/Build.sol: RainlangParser, RainlangExpressionDeployer and Rainlang bytecode moved (solmem LibBytes32Array codegen changes plus embedded address cascade); RainlangInterpreter, RainlangStore, RainlangReferenceExtern unchanged. Pruned stale remappings by hand (soldeer appends, never prunes), including two pre-existing dangling lines (rain-deploy-0.1.2, rain-extrospection-0.1.0). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
copy-artifacts flagged the four committed ABI JSONs that embed the moved
bytecode (crates/bindings/abi/Rainlang.json and crates/test_fixtures/abi/
{Rainlang,RainlangExpressionDeployer,RainlangParser}.json); regenerated
via script/CopyArtifacts.sol.
rainix@main now enforces the 4096-byte agent-context cap in static and
rs-static; CLAUDE.md was 8587 bytes and failed both on every push. Cut
under the cap per the check's heuristic: kept the non-recoverable
rulings (jidoka process, NatSpec tagging, generated-pointers regen
cascade), cut what is discoverable from the repo (architecture tour,
dependency lists, command inventory).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bump rain-solmem 0.1.3 -> 0.1.26 (and the dep chain pinning it)
Adopts the org's current deploy/release machinery (rain-deploy 0.1.7) without splitting the repo — the shape #545 describes, leaving #552 open. - src/abstract/RainlangDeploySuites.sol is now the one declaration: five candidates (parser, store, interpreter, expression-deployer, rainlang) with their dependency lists. src/abstract/RainDeploySuitesBase.sol re-exports the package path the generated libs reach by relative import. - src/generated/candidate/<Name>.sol replaces <Name>.pointers.sol for the five deploy candidates; the non-deploy codegen (parse meta, pointer tables, described-by hashes) moves to <Name>Pointers.sol. RainlangReferenceExtern is not a deploy candidate — the deploy script, dispatch choices and deploy lib all say five — so it keeps pointers only. - script/Build.sol is a BuildScript; script/Deploy.sol is the one-line RainDeployBroadcast binding with no deployNetworks() override, so this repo targets all seven supported networks. - package-release.yaml releases on sol-v* via rainix-tag-release; the five Rust crates move to their own crates-release.yaml so the two lifecycles stop sharing one next-version invariant. - Chain and snapshot verification replace the hand-rolled pin tests, including the five LibInterpreterDeployProdTest fork tests red on main (#561): the standard scopes chain checks to RELEASED suites, and this repo has released none under the frozen-record model, so 0.1.8 stays and the record starts empty. No pin moves: DEPLOYED_ADDRESS, BYTECODE_HASH, CREATION_CODE and RUNTIME_CODE are byte-identical to their pre-migration values for all five candidates. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The five `deps` arrays came across from `script/Deploy.sol` unchanged and
four of the five over-declared. `DeploySuite.dependencies` is what must
already have code on a network or the deployment is broken on arrival,
and `LibRainDeploy.deployToNetworks` refuses to broadcast without it, so
an address the bytecode never carries blocks the suite on every chain
that lacks it for a call that cannot happen. This PR is also what starts
writing those lists into the append-only frozen record.
Measured by scanning each candidate's creation code for each address at
the width solc emits it:
parser log tables -> none
store log tables -> none
interpreter log tables, TOFU -> unchanged
expression-deployer log tables, TOFU, parser,
store, interpreter -> parser
rainlang TOFU, parser, store,
interpreter, deployer -> the four siblings
No bytecode moves: only `DEPENDENCIES` in `src/generated/candidate/`.
The natspec on `expressionDeployerCandidate` claimed a reach into the
store and the interpreter that the source contradicts; corrected, along
with `rainlangCandidate`'s "facade" framing — it calls none of the four,
it publishes their addresses, which is the other half of what the struct
means.
Both directions are now asserted so neither over- nor under-declaration
can return quietly. `testCandidatesReachEveryDependencyTheyDeclare` is
the converse of `testCandidatesDependOnTheSiblingsTheyReach`;
`testCandidatesDependOnTheExternalsTheyReach` covers the log tables and
TOFU, which are not siblings and so were in neither. Both were mutation
checked: dropping the log tables from `interpreterCandidate()` fails the
second, and the pre-trim lists fail the first.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ation Migrate to the tag-driven release standard, in-repo
HyperEVM entered the network set in #563 and its RPC needs type-0 transactions; the verification retry loop can outlast the broadcast. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
It caps the window at 5 blocks rather than rejecting the call. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…inputs Expose the deploy reusable's legacy and verify inputs
The repo's first release under the tag-driven machinery #563 migrated to. One commit, exactly the runbook's release shape: - foundry.toml [external.package].version 0.1.8 -> 0.1.9, in lockstep with the frozen record it names. - forge script ./script/Build.sol --sig 'cutRelease()' output committed verbatim: src/generated/0_1_9/ (byte-identical copy of candidate/, five of five, verified) and the five released-suites libs regenerated from the record that now holds 0.1.9. Nothing else moves. No pinned value changes: the frozen constants are the candidate/ constants at the parent commit, and `run()` + `forge fmt` on the parent tree was already a fixed point before the freeze. 0.1.8 is not re-cuttable and never was — it was published by the old merge-driven autopublish and froze no record, so release-guard would reject sol-v0.1.8 on this tree. #563 stated that; 0.1.9 is the first release the lifecycle can carry. Freezing arms RainlangDeployChainTest over the released record. Before this commit releasedSuites() was empty and testSuitesLiveOnEverySupportedNetwork early-returned on a length-0 derivation, touching no RPC; after it, the suite forks all seven supported networks and asserts all five suites are live there with the code they froze. Green: 1579 passed, 0 failed, 0 skipped. Merging publishes nothing. Publication happens only when sol-v0.1.9 is pushed on the merged commit, which re-runs the fork suite and release-guard first. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The commands it named were removed from rainix org-wide; sol-tasks at the pinned rev is rainix-sol-artifacts only. Build and test invocation is an org standard carried by rainix, not something this repo restates. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Cut release 0.1.9: freeze candidate pins as src/generated/0_1_9/
…-env Drop the Build Environment section from CLAUDE.md
Closes #571 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rename the Git is clean job id to git-clean
recursive_deps=false means vendored dep src resolves through this repo's remappings, and three reachable deps import a versioned rain-solmem prefix from src, so the bump carries its minimum chain to keep one solmem tree: - rain-solmem 0.1.26 -> 0.1.28 - rainlang-interface 0.2.5 -> 0.2.8 - rain-extrospection 0.1.13 -> 0.1.14 - rain-datacontract 0.1.3 -> 0.1.9 (first release whose src drops the solmem import; its src delta is NatSpec and local renames) None of the four changed behaviour under src between the pinned releases, and src/generated pointers are unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QyCCzi9WZPhuXcU1hwr2bq
Bump rain-solmem 0.1.26 -> 0.1.28 and the dep chain pinning it
First step of the library / deploy split (#552). The five deployed contracts' logic moves into src/abstract/BaseRainlang{Parser,Store, Interpreter,ExpressionDeployer,}.sol and src/concrete/* become thin bindings of the generated tables and Zoltu addresses over them, which is the shape the .deploy repo consumes from the package. PARSE_META_BUILD_DEPTH is a source constant on BaseRainlangParser rather than a value re-read from the generated file it parameterises. test/concrete/Test*.sol are the bases built from current source with no generated tables, placed at fixed test addresses by test/lib/deploy/LibTestInterpreterDeploy.sol. RainlangExpressionDeployerDeploymentTest types I_* as the bases and gets one hook, deployRainlang(), defaulting to the test concretes, so the op, integrity, eval and parse tests run against current source; the concrete-specific tests keep exercising the real concretes and pins. Parser, store and interpreter bytecode are unchanged. RainlangExpressionDeployer is 15 bytes longer (the parser() and integrityFunctionPointers() hooks are internal calls the legacy optimizer does not inline), so its candidate address moves and Rainlang, which embeds it, moves with it. The live 0.1.9 deployments are untouched. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QyCCzi9WZPhuXcU1hwr2bq
script/CopyArtifacts.sol output: the devdoc in the Rainlang, expression deployer and interpreter artifacts follows the moved NatSpec. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QyCCzi9WZPhuXcU1hwr2bq
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QyCCzi9WZPhuXcU1hwr2bq
Second step of the library / deploy split (#552, rainix#296), on top of the BaseRainlang* abstracts. The concretes, their generated tables and deploy records, LibInterpreterDeploy, the released-suites libs, the deploy scripts and tests, the Rust crates and the deploy workflows leave this repo; rainlang.deploy carries them over this package. The 0_1_9 frozen record is a library-tagged pin and is deleted, not relocated: the deploy repo freezes its own first release. script/Build.sol now generates only the reference extern's pointers; BuildAuthoringMeta and the prelude build only its meta. The concretes' logic tests bind test/concrete/Test*.sol, the BaseRainlang* built from current source. package-release.yaml is rainix-autopublish on push to main; this removal is breaking, so next-v0.2.0 ships it as rainlang 0.2.0. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QyCCzi9WZPhuXcU1hwr2bq
…stracts Move the deployed contracts' logic into BaseRainlang* abstracts (#552 step 1)
…brary # Conflicts: # crates/bindings/abi/Rainlang.json # crates/test_fixtures/abi/Rainlang.json # crates/test_fixtures/abi/RainlangExpressionDeployer.json # crates/test_fixtures/abi/RainlangInterpreter.json
rainix's frozen-snapshots-append-only gate rejects deleting a tag dir. Nothing in the library reads src/generated/0_1_9/ any more; it stays as the record of the last release cut while this repo carried the deploy half. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QyCCzi9WZPhuXcU1hwr2bq
The BaseRainlang* contracts are the library's product: their binding hooks are implemented by rainlang.deploy's concretes and by the test concretes, neither of which slither's src-only build sees, so every base is a leaf with unimplemented functions by design. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QyCCzi9WZPhuXcU1hwr2bq
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QyCCzi9WZPhuXcU1hwr2bq
Slim to the library half: the deploy half moves to rainlang.deploy (#552 step 2)
…-entries Remove submodule-era residue from .soldeerignore
forge-std-1.16.1/ -> forge-std-1.16.2/ and rain-sol-codegen-0.1.0/ -> rain-sol-codegen-0.1.36/ across src, test and script, and the remappings block that aliased the older prefixes onto the installed packages is gone. An alias compiles a file against a version it does not name, so the versioned prefix stops being the pin, and every consumer of this package had to carry the same aliases to compile the shipped harness. Import paths only; the 0.1.36 tooling interfaces widen three functions from pure to view and the implementations here keep pure. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QyCCzi9WZPhuXcU1hwr2bq
…sions Spell the pinned forge-std and rain-sol-codegen versions in every import
The branch predates the solmem bump; main pins 0.1.28 and no other version is installed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QyCCzi9WZPhuXcU1hwr2bq
|
@coderabbitai assess this PR size classification for the totality of the PR with the following criterias and report it in your comment: S/M/L PR Classification Guidelines:This guide helps classify merged pull requests by effort and complexity rather than just line count. The goal is to assess the difficulty and scope of changes after they have been completed. Small (S)Characteristics:
Review Effort: Would have taken 5-10 minutes Examples:
Medium (M)Characteristics:
Review Effort: Would have taken 15-30 minutes Examples:
Large (L)Characteristics:
Review Effort: Would have taken 45+ minutes Examples:
Additional Factors to ConsiderWhen deciding between sizes, also consider:
Notes:
|
Summary
Scoped adversarial mutation-testing pass on
LibOpConditions(src/lib/op/logic/LibOpConditions.sol). Tests-only; no source changes.git diff src/is empty.LibOpConditionsreturns the value of the first nonzero condition in a pairwise condition/value list; if the input count is odd the trailing input is an error-reason string used when no condition is met. Itsrunhas subtle odd/even handling (loop range, output placement, reason read, and a no-match revert).Mutation matrix
Mutated each behavior in
LibOpConditions.sol, ranLibOpConditionsTest, recorded SURVIVED/KILLED, restored after each.> 2boundaryoddInputs := mod(inputs, 2)→mod(inputs, 3)endcalc dropsoddInputs(includes reason slot in loop)sub(end, iszero(odd)*0x20)→sub(end, 0x20)→ endmload(end)→mload(cursor)+0x40→+0x20!isZero→isZeroif (conditionIsZero)→if (false))8/11 killed by the existing suite. The three survivors all hid in the odd-input (reason-carrying) paths:
testOpConditionsRun) truncates odd inputs to even, so the odd-input success path was never asserted — R2 and R3 hid there.opReferenceCheck, whose reference implementation also reverts, masking a missing revert inrunitself — R9 hid there.New tests (all pass clean on baseline; each fails under its target mutant)
testOpConditionsEval3InputsTrueOddReason/testOpConditionsEval5InputsSecondTrueOddReason— eval ofconditionswith an odd input count where a condition is met; assert the matched value, trailing reason ignored. (kills R2)testOpConditionsRunOddMatchStackTopPlacement— directrun()call asserting the output stack-top pointer (words consumed), not just the value, for an odd-input match. (kills R3)testOpConditionsRunRevertsNoMatchEven/testOpConditionsRunRevertsNoMatchOddReason— callrun()in isolation (newrunExternalwrapper, no reference fn) and assert it reverts with the correct reason (empty for even, trailing reason for odd). (kills R9)Remaining-gaps checklist
Verification
forge build: Compiler run successful (theboolean-cstlint warnings are pre-existing, unrelated to this diff).forge fmt --checkon the test file: clean.LibOpConditionsTest: 20 passed, 0 failed.test/src/lib/op/logic/*): 181 passed, 0 failed..gas-snapshotchange (no new opcode / bytecode change), so no regen needed.Conflict-avoidance with #535
Touches only
test/src/lib/op/logic/LibOpConditions.t.sol. No overlap with #535's files (LibAllStandardOps.sol,LibOpPi.sol,src/generated/*.pointers.sol, parse-meta,LibOpE/LibOpPi). No.gas-snapshotedit.🤖 Generated with Claude Code