Skip to content

A unit that cannot record its own error must not take the shard with it - #91

Merged
sotashimozono merged 2 commits into
mainfrom
fix/provider-record-and-results-read
Sep 11, 2026
Merged

sotashimozono merged 2 commits into
mainfrom
fix/provider-record-and-results-read

Conversation

@sotashimozono

Copy link
Copy Markdown
Member

A unit that cannot record its own error takes the shard with it

_run catches an escaping error so the shard survives a unit that throws — that is what
the catch is for. Recording that error undid it.

A provider testset type with no Test.record for Test.Error makes the record call
itself throw. This is the natural gap, not an exotic one: the only way to reach that
branch is a unit whose body throws (a load error), never an ordinary @test failure, so
a provider author testing assertions has no reason to cover it. The repo's own StubSet
happens to use a catch-all record, which is why nothing here caught it.

Reproduced before fixing — three units, the middle one throwing:

before   [ok] u1.jl
         ESCAPED after [u1, u2] → MethodError: no method matching
                                  record(::PartialSet, ::Test.Error)
         units recorded = ["u1.jl"]

after    [ok]   u1.jl
         Error: u2.jl threw, and its testset could not record the error
         [FAIL] u2.jl  (0 pass, 0 fail, 1 error)
         [ok]   u3.jl
         units recorded = ["u1.jl", "u2.jl", "u3.jl"]

u3 never ran, and what surfaced was a TestShards internal rather than the unit that
could not load.

Counting it as errored is the other half — and the more important one

Swallow the record failure alone and the unit that threw reports 0 error and reads
green. That is worse than the truncation: a truncated shard is at least loud. So
_run tracks whether recording worked and adds the error itself when it did not.

Why not hasmethod

A registration-time hasmethod(Test.record, (T, Test.Error)) gate looks like the obvious
fix and is the wrong instrument: a catch-all record(ts, ::Any) that throws for
Test.Error satisfies hasmethod and still fails here. The proxy would license exactly
the case it claims to exclude. This tests the real thing — try to record, and report
whether it worked.

Second, unrelated: the read side of results was unlocked

_section walked ts.results directly. Julia 1.13 gave DefaultTestSet a results_lock
and takes it on every record; the read now takes it too, and copies rather than
iterating under it, since _section recurses.

Only reachable from a unit that left a task running, which @shard's docstring forbids —
but the forbidden case should cost a wrong count, not a walk over a vector being pushed to.
Gated on hasfield, so 1.10–1.12 are untouched.

Also

The hand-popped left in test_provider.jl's header, missed when #90 renamed that phrase
everywhere else.

Version

0.3.41 → 0.3.42, patch.

🤖 Generated with Claude Code

`_run` catches an escaping error so the shard survives a unit that throws.
Recording that error undid it. A provider testset type with no
`Test.record` for `Test.Error` — the natural gap, since only a THROWING
unit reaches that branch, not an ordinary `@test` failure — makes the
record call itself throw, and the `MethodError` escaped `_run`.

Reproduced before fixing, three units, the middle one throwing:

    before   [ok] u1.jl
             ESCAPED after [u1, u2] → MethodError: no method matching
                                      record(::PartialSet, ::Test.Error)
             units recorded = ["u1.jl"]

    after    [ok]   u1.jl
             Error: u2.jl threw, and its testset could not record the error
             [FAIL] u2.jl  (0 pass, 0 fail, 1 error)
             [ok]   u3.jl
             units recorded = ["u1.jl", "u2.jl", "u3.jl"]

u3 never ran, and the failure surfaced as a TestShards internal rather
than as the unit that could not load.

Counting the unit as errored when recording fails is the other half, and
the more important one: swallow the record failure alone and the unit
that THREW reports `0 error` and reads green — worse than the truncation.

Tested by recording, not by `hasmethod`. A catch-all
`record(ts, ::Any)` that throws for `Test.Error` satisfies `hasmethod`
and still fails here, so the proxy would license exactly the case it
claims to exclude.

Second, unrelated to that: `_section` walked `ts.results` unlocked.
Julia 1.13 gave `DefaultTestSet` a `results_lock` and takes it on every
`record`; the read side now takes it too, and copies rather than
iterating under it, since `_section` recurses. Only reachable from a unit
that left a task running, which `@shard`'s docstring forbids — but the
forbidden case should be a wrong count, not a walk over a vector being
pushed to.

Also the "hand-popped" left in test_provider.jl's header, missed when
#90 renamed the same phrase everywhere else.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

📚 Docs preview: https://qatlashub.github.io/TestShards.jl/previews/PR91/

(updates on each push to this PR)

@codecov

codecov Bot commented Sep 11, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.85714% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/provider.jl 75.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

The suite runs inside a `@shard` block, and `_begin` installs into
`TestShards.CURRENT` — so calling it from a test replaces the running
block's own context. Every Julia version went red on the same line.

`bare_context` exists for exactly this and says so in its docstring; the
two testsets directly above use it. `test_claim.jl` already pins that
`_begin` from inside is an error. I had all three and still reached for
the driver under test.

`shard=""` makes `_owns` total, so all three units run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sotashimozono
sotashimozono merged commit 12eac5c into main Sep 11, 2026
35 checks passed
@sotashimozono
sotashimozono deleted the fix/provider-record-and-results-read branch September 11, 2026 04:48
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.

1 participant