internal/conformance design
Design goal
Four corpus frontends report results for very different data (decimal rows,
interval statements, MPFR rows, TestFloat vectors), and the Python tooling
aggregates them across processes. They must agree on what a “case”, a
“pass”, a “skip” and a “shard” are, or published totals become incomparable.
internal/conformance fixes that vocabulary once: a disposition per case,
counters with fixed identities, and one shard rule.
Mathematical background
Results and counters
A result is a tuple with disposition (executable, diagnostic, legacy, unsupported), pass bit and message . For a list of results define the counter vector
where the are unit vectors in . RunSummary::from_results
computes exactly and and stores the caller’s total .
Shards
For and , shard selects the ordinals .
Design decisions
Four dispositions, one failure notion
Only executable results can fail. Distinguishing diagnostic, legacy and
unsupported skips lets a report say why rows were not run: a diagnostic row
is not a test (no claim is lost), an unsupported row is a missing feature (a
claim is lost), a legacy row follows retired conventions. success() means
“no executable case failed”; stricter verdicts are layered on top by the
caller (the ITL frontend also fails on diagnostics, the CLIs optionally on
unsupported rows).
Round-robin shards
Assigning ordinal to shard needs no knowledge of the total,
can be decided while streaming, and spreads neighbouring (often similar-cost)
cases across shards. Validation is separated (try_new) from use, so
selects is a single comparison.
total is supplied, merge takes the maximum
The number of cases before sharding is known to the caller, not to a shard’s
result list. Every shard of a run reports the same total , so the maximum
in merge returns whatever the number of parts, while the sum would
count it times.
One-based locations
SourceLocation clamps line and column to at least 1, so formatted
diagnostics (file:line:column: message) are always valid editor positions,
even for callers that pass 0 for “unknown”.
Correctness / invariants
Counter identities. For every summary,
,
and
.
Each summand of adds 1 to exactly one side of each identity, so they
hold for from_results; merge adds counters componentwise, which preserves
linear identities.
Shards partition the ordinals. Every has exactly one residue modulo , so the are disjoint and cover . Among the first ordinals, shard receives
so shard sizes differ by at most one.
Merging shards gives the serial counters. is a monoid homomorphism from lists under concatenation to : , and likewise . If the results of the shards are the results of the serial run restricted to (true whenever a case’s result does not depend on the other cases), then
have equal counters and equal totals; only the order of the result list differs (grouped by shard).
Immutability. Summaries copy result arrays on construction and on
results(), so callers cannot change a summary after the fact.
Alternatives rejected
- A single “skipped” counter. It would hide the difference between non-tests and missing features, which is exactly what conformance claims must state.
- Contiguous shards. They need the total in advance and concentrate expensive files in one shard.
- Public frontend use of these types. Frontends wrap them instead, so this package can evolve without breaking published APIs.
Boundaries
- No parsing of corpora, no execution, no IO, no JSON: those belong to the frontends and internal/runner_cli.
- No timing or performance data.
- No check that a result’s
passedbit agrees with its disposition. - Internal: not importable outside
Luna-Flow/floating, no stability promise.