Skip to content

Move every example onto Anchor v2's LiteSVM test harness - #133

Merged
mikemaccana merged 4 commits into
mainfrom
claude/anchor-v2-testing-harness
Aug 22, 2026
Merged

Move every example onto Anchor v2's LiteSVM test harness#133
mikemaccana merged 4 commits into
mainfrom
claude/anchor-v2-testing-harness

Conversation

@mikemaccana

Copy link
Copy Markdown
Collaborator

Anchor v2 ships its own LiteSVM wrapper, anchor-v2-testing. Tests that call LiteSVM::new() directly still run and still pass, but they record nothing, so anchor test --profile, anchor debugger and anchor coverage all come back empty. Going through anchor_v2_testing::svm() is what connects a test to those three tools, and it is LiteSVM::new() until the profile feature turns tracing on, so the default path costs nothing.

What changes

Per program crate, the dev-dependencies drop litesvm, solana-signer and solana-keypair, which the harness re-exports, and gain the harness plus a profile feature forwarding to it:

[dev-dependencies]
anchor-v2-testing = { git = "https://github.com/otter-sec/anchor.git", rev = "3e38a121b969d1a77230baeb0056baf5cc9b3c1a" }

[features]
profile = ["anchor-v2-testing/profile"]

Tests swap LiteSVM::new() for anchor_v2_testing::svm() and their imports follow. The execution model does not change: same in-process LiteSVM, same solana-kite helpers, which take the same LiteSVM value.

Why a git dependency, and why a pinned rev

crates.io has no anchor-v2-testing; a git dependency is what the v2 testing documentation prescribes. That documentation says branch = "anchor-next". This pins a revision instead, because Cargo.lock is not tracked here, so CI resolves fresh on every run and a branch would let an upstream push change a build with no commit in this repository to explain it. Bumping the rev then becomes a deliberate, reviewable change. Happy to switch to the branch if you would rather track it.

What this unlocks

None of these work today, because nothing records traces:

  • anchor test --profile writes per-test SBF traces and renders flamegraphs
  • anchor debugger opens an instruction stepper with DWARF source mapping and CPI frames
  • anchor coverage emits LCOV at target/coverage/sbf.lcov

Verified on basics/counter: anchor coverage resolved 294 of 312 program counters to source across 19 files, 91 lines covered, with per-line hit counts in the LCOV.

Verification

A full local build-and-test sweep over all 55 Anchor projects: 54 pass, 286 tests.

The one failure, finance/vault-strategy's test_add_asset_enforces_max_assets, is not caused by this change. It fails identically on unmigrated origin/main with ProgramLoad("Entrypoint out of bounds") while loading mock_swap_router.so, and it passes in CI, so it is an artefact of the locally-built cargo-build-sbf 4.2.0 rather than the Solana 3.1.14 toolchain CI installs. Flagging it rather than folding a fix into this PR.

The sweep earned its keep. The first pass of this migration failed on 44 of 51 projects with unresolved import anchor_lang::anchor_v2_testing: the import was being inserted at the first alphabetically-later entry without regard to nesting depth, so in a block like

use {
    anchor_lang::{
        solana_program::instruction::Instruction, ...
    },

it landed inside anchor_lang. Five further files named the replaced crates by full path rather than importing them, mostly in helper signatures like fn setup() -> (LiteSVM, solana_keypair::Keypair), and dropping the dev-dependencies left those dangling. Both are fixed in the second commit.

Not included

The one test importing solana_transaction::Transaction keeps that dependency, because the harness re-exports VersionedTransaction and not Transaction. mock-swap-router has no tests, so it gains nothing.


Generated by Claude Code

claude added 4 commits August 21, 2026 15:35
Anchor v2 ships its own LiteSVM wrapper, `anchor-v2-testing`. Tests that call
`LiteSVM::new()` directly still run and still pass, but they record nothing,
so `anchor test --profile`, `anchor debugger` and `anchor coverage` all come
back empty. Going through `anchor_v2_testing::svm()` is what connects a test
to those three tools; it is `LiteSVM::new()` until the `profile` feature turns
tracing on, so the default path costs nothing.

Per program crate, the dev-dependencies drop `litesvm`, `solana-signer` and
`solana-keypair`, which the harness re-exports, and gain the harness plus a
`profile` feature forwarding to it. Tests swap `LiteSVM::new()` for
`anchor_v2_testing::svm()` and their imports follow. The execution model does
not change: same in-process LiteSVM, same solana-kite helpers, which take the
same `LiteSVM` value.

The harness is published only from git; crates.io has no `anchor-v2-testing`,
and a git dependency is what the v2 testing documentation prescribes. It is
pinned to a revision rather than tracking `anchor-next`: Cargo.lock is not
tracked in this repository, so CI resolves fresh on every run, and a branch
would let an upstream push change a build with no commit here to explain it.
Bumping the rev is then a deliberate, reviewable change.

Two things stay as they were. The one test using `solana_transaction::Transaction`
keeps that dependency, because the harness re-exports `VersionedTransaction`
and not `Transaction`. `mock-swap-router` has no tests, so it gains nothing.

Verified locally before the full sweep: `basics/counter` passes its 3 tests and
`anchor coverage` resolves 294 of 312 program counters to source across 19
files; `finance/lending`, eight test binaries over a shared harness, passes all
26.
The first pass inserted `anchor_v2_testing::{...}` wherever the first
alphabetically-later entry appeared, without checking how deep in the `use`
tree that entry was. In a block like

    use {
        anchor_lang::{
            solana_program::instruction::Instruction, system_program, ...
        },
        ...
    };

the first later entry is `solana_program`, nested one level inside
`anchor_lang`, so the import landed there and every affected crate failed to
compile with `unresolved import anchor_lang::anchor_v2_testing`. The insertion
point is now pinned to depth exactly 1, the top level of the outer block.

Five test files also named the replaced crates by full path rather than
importing them, mostly in helper signatures like
`fn setup() -> (LiteSVM, solana_keypair::Keypair)`. Dropping the
dev-dependencies left those paths dangling, so they now resolve through the
harness too.

A local build-and-test sweep caught both: 44 of the 51 projects it reached
failed on the first, two on the second. The three shapes are re-verified:
account-data (nested block import), processing-instructions and
repository-layout (full-path references) all build and pass.
rustfmt writes `use anchor_v2_testing::Signer;`, not
`use anchor_v2_testing::{Signer};`. Three lending test files import only
`Signer` from the harness and got the braced form from the migration.
`cargo fmt --all --check` is clean across the workspace again.
`anchor test` generates the IDL as well, so the `--no-idl` exemption added in
#132 only covered half the problem: the eight projects it exempts got past
`anchor build` and then failed at `anchor test` with the same upstream error,

    Error: missing field `variants` at line 1 column 6579

which is anchor#4947, `#[derive(IdlType)]` emitting `fields` where the IDL
spec requires `variants` for enums.

`betting-market` hit this on the harness-migration PR. I have not established
why the same call sequence passed on the `main` run that introduced the skip;
the build cache is the likeliest difference, since this branch touches every
program's manifest and so cannot reuse it. Either way the exemption belongs on
both commands, and adding it cannot break a project that was already passing.

Removed together with the rest of the workaround when an Anchor release
carrying the fix ships.
@mikemaccana
mikemaccana merged commit 53e30d4 into main Aug 22, 2026
42 checks passed
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.

2 participants