Skip to content

test: consolidate 192 test files into 15 - #49

Merged
twmb merged 15 commits into
mainfrom
test-consolidation
Aug 5, 2026
Merged

test: consolidate 192 test files into 15#49
twmb merged 15 commits into
mainfrom
test-consolidation

Conversation

@twmb

@twmb twmb commented Aug 5, 2026

Copy link
Copy Markdown
Owner

Moves test code between files. Deletes no test. Stacked on #48.

192 test files -> 15. The long tail was the problem, not the big files: 132 of
192 were under 500 lines, 60 under 200.

The floor is 7, and it is forced

Seven buckets can never merge — incompatible build tags and the internal /
external package split:

dir package build tag before after
. avro 70 6
. avro race 1 1
. avro !race 1 1
. avro_test 95 4
. avro_test cisuite 4 1
ocf ocf 16 1
ocf ocf_test 5 1

Gate

The set of test function names before and after is byte-identical in all
three build configurations
:

default  2447 -> 2447   diff 0
cisuite  2456 -> 2456   diff 0
race     2447 -> 2447   diff 0

Merging was done by an AST tool that asserts each source's post-import body
appears byte-for-byte in the result before deleting the source — passed for all
190 merged files. Sections carry // ---------- orig_test.go ----------
banners, the convention conformance_test.go already used.

Shared matrix tables (matFrags, matCtxs, matEqual, runCore,
customFrags, resurrectionCells) were reused, never replaced. Nothing moved
between packages, so no internal test lost unexported access.

gofmt clean, go vet clean, suite green, -race green.

Two pre-existing defects it surfaced

The suite reads its own source in a few guards, so consolidation broke them.
Repairing them turned up two bugs that predate this work:

  1. blankCode never blanked rune literals. s[0] != '{' contributes an
    unmatched brace and s[1] != '"' opens a phantom string. One test's
    measured extent came out 7,249 lines instead of 97, swallowing a cost
    generator declared far below it. Small files hid it — the over-run hit EOF
    within a few hundred lines. It fails silently in the other direction too,
    since a phantom string can blank a genuine call.

  2. TestInvariant_DagCostMatrixDrivesEveryEntryPoint could not fail. Its
    want-list literals sit inside the very file it reads, so every entry matched
    its own list line. Renaming all six real ocf-header cells left it green.
    One entry, "Parse(dag)", occurred exactly once in the file — its own list
    line — so it asserted nothing from the day it was written. Now excises its
    own function from the searched body, reads the section rather than the file,
    and is neuter-verified red.

To be clear about severity on (2): the entry points themselves were always
covered by TestInvariant_EveryMinBytesEntryPointIsBounded. What had rotted
was the tripwire guarding the matrix against losing a cell, not the DoS bounds.

twmb added 15 commits August 5, 2026 15:44
Merges block_alloc_regression, codec_cap_regression, differential_ocf,
example and large_datum into one file. package ocf_test cannot merge with
package ocf, so this bucket has its own file by construction.

Move only: every declaration is byte-identical, imports are the deduped
union, and each section keeps a `// ---------- orig_test.go ----------`
banner (the convention conformance_test.go already uses).
Framing, codec ownership and nil-spelling, decompression limits, the DoS
battery, foreign-writer differentials, fuzz targets, the OCF matrices,
metadata compat and the truncation sweeps now sit beside ocf_test.go.

Move only: every declaration body is byte-identical to its source, the
import block is the deduped union, and each section keeps a
`// ---------- orig_test.go ----------` banner.
…_test.go

The JVM differentials (schema, matrix, value, attribute-placement census)
share one build constraint and one package; //go:build cisuite means they
can never merge with anything outside it, so this bucket is one file by
construction.

Move only. Verified against the -tags=cisuite name list as well as the
default one, since a plain `go test` never compiles these.
json_decode_test.go joins json_codec_test.go: both drive the Avro-JSON
wire (json_codec.go / json_decode.go / json_scan.go) from inside package
avro, and the encode and decode halves are read together.

Move only.
…t.go

The package-avro tests that are not about one subsystem but about driving
the internals hard and measuring the result: the predicate-agreement and
hidden-state censuses, the DoS entry-point battery, the race wall-clock
authority, every fuzz target, the benchmarks, and the coverage-gap pins.

export_test.go moves here too, as the `// ---------- export_test.go ----------`
section. It is the bridge package avro_test reads (RaceRelaxedForTest /
RaceEnabledForTest / the slab-free probes), and race_bounds_test.go, the
authority it bridges, is now three sections above it. Worth knowing that
the stdlib name is gone: grep the banner, not the filename.

Move only.
The encode side of the binary wire: the array/map element fast paths, the
zero-byte-item producer and unsafe compliance pins, empty-bytes identity,
the per-element minimum stand-in, magnitude arithmetic, the union tag
tiers, the TextAppender/TextMarshaler precedence contracts, and the
decode-then-reencode symmetry audit.

Move only.
The decode side and the machinery it reaches: schema resolution, map
set-iteration targets, union branch matching, the integer-overflow and
enum-ordinal-overflow audits, the unsafe depth bound, the logical gate,
the json.Unmarshal-input integration net, and the CustomType, callback
contract and hidden-state suites (custom wiring is decoder-side state).

Move only.
Go type -> schema inference in one place: namespace-scope composition of
a CustomType.Schema, stray structural keys, the generative round-trip and
struct-shape nets, the tag-edge net, reserved-key exact case, the null
spelling, and the whole embedding family (placement, selection, diamond
cost, repeated embeds).

These stay in package avro because the shape nets reach collectFields and
typeFieldMapping; none of them can move to package avro_test.

Move only.
Everything from schema text to the parsed tree and back: the SchemaNode
metadata API and its namespace handling, the parse front-end diff, node
kind compat, the name-lookup tables and reference-schema pins (#42), the
caller-composed and caller-edited node matrices, the tree value-type
matrix, alias resolution, fingerprint purity, single-object encoding, the
SchemaCache suites, and the structural cost work (breadth, DAG cost, the
budgeted walk census, walk-budget marshalling, depth uniformity,
degenerate cardinality, union forward references).

Move only.
…e_test.go

conformance_test.go was already a merge of sixteen files under the
`// ---------- orig_test.go ----------` convention; this extends it with
the rest of the black-box, oracle-driven work: every fastavro differential
(binary, JSON, logical, alias resolution, null spelling, matrix,
SchemaFor scope), the Apache schema-tests.txt vectors, the property and
invariant nets, the tier-2 boundary / cross-path / resolution / metadata /
SchemaFor-round-trip matrices, the decimal round-trip matrix, and the
nesting suite.

example_test.go merges in as well. Its Example funcs are godoc examples
and go/doc finds them by function name, not by filename, so pkg.go.dev is
unaffected -- but the conventional filename is gone, so: they are the
`// ---------- example_test.go ----------` section, or
`grep -n "^func Example" conformance_test.go`.

Move only.
The 30 matrix_* cell files plus the magnitude-arithmetic matrix and the
CustomType resurrection parity battery now sit in the file that already
owns the shared tables. Nothing is replaced: matFrags, matCtxs, matEqual
and runCore stay exactly as they were, and the cells that read them
(matEqual alone had 16 readers) are now in the same file as the
definition. customFrags and resurrectionCells likewise -- resurrectionCells
is read by matrix_generative_test.go, which is why the parity battery
belongs in this group rather than with the regressions.

Move only.
New file. The external suites that answer "what does this piece of schema
text MEAN" as a class rather than as remembered cases: the attribute x
placement acceptance census, the reserved-attribute enumeration and its
duplicate / exact-case rules, stray precision-scale and stray-key routing,
attributes written as their destination zero, type-level default and
order binding, non-string logicalType values, the null spellings (quoted
name, body presence, branch spelling), namespace and empty-name-component
grammar, the enum default token, the union default-order / fixed-size /
tag-table rules, forward-referenced and negative-zero defaults, the flat
goavro-style field format, the struct-tag doc contract and runtime tag
grammar, one-way text inference, the JSON escape-position and skip
strictness pins, the reader-grammar census, encode error identity, the
DoS-cap producer-compliance table, and the error-message bound.

Move only.
…t.go

The remaining black-box regressions, grouped with the audit battery they
belong to: the CustomType family (named Avro types, skip-decode parity,
record-level Decode, pointer-returning customs at union branches), the
internal-reparse lax-name surfaces and their class matrix, the SchemaCache
self-containment pins (canonical, overlapping splice defs, wildcard
registration), the slab-free no-alloc oracle (#41), cyclic field types,
named byte elements, the unsafe null-union nil peel, and the b-soft
omitzero contract.

Move only. This is the last of the 13 consolidation groups: 192 test
files -> 15.
The suite reads its own source in several places, and consolidation moved
what those reads name. Repaired, not relaxed:

  * TestInvariant_EveryBatteryEntryPointHasABreadthCell and
    TestInvariant_DagCostMatrixDrivesEveryEntryPoint each read ONE file.
    They now read that file's SECTION of the merged file, via a new
    testFileSection helper keyed on the `// ---------- x ----------` banner.
    Reading the whole enclosing file would be a false pass: the DAG-cost
    guard looks for "cache.Parse(", which its own section uses twice and
    the rest of schema_test.go uses fifty-six more times.

    testFileSection terminates only on a banner naming a *_test.go file.
    The same shape is also used for ordinary subsection headings ("the
    shapes", "triggers", "the WIDTH axis"), and stopping at one of those
    truncates a section to a few lines.

  * moduleTestFiles required 25 test files; there are 15 now, so the floor
    is 10. The directory half is unchanged and is the half that catches
    the failure it was written for.

  * raceRelaxations rows by file, and two rows are now one file
    (race_bounds + export -> internal_nets_test.go, 3+1 sites) with a
    third renamed (error_bound -> schema_semantics_test.go). Counts and
    reasons carry over intact; the row covering two sections says so.

  * blankCode now blanks RUNE literals. This is a latent bug the
    consolidation exposed rather than caused: `s[0] != {rune-brace}`
    contributes an unmatched brace, so the enclosing function extent runs
    to EOF, and a rune-quoted double quote opens a phantom string that
    blanks the code after it. One test measured 7249 lines instead of 97
    and swallowed a cost generator declared far below it, producing a
    false edge in TestInvariant_EveryCostCellDrivesItsFactor. Small files
    hid it because the over-run hit EOF within a few hundred lines. It
    fails silently in the other direction too, since a phantom string can
    blank a genuine call.

Neuter-verified after the change: remove the only Fingerprint breadth
cell, add an unrowed race-predicate consult, add an unrowed test driving
a cost generator. All go red (exit!=0, RUN>0, no panic).
Pre-existing, found while repointing the guard at its section and proven
by neuter, not by reading: renaming all six real "ocf-header" cells in the
DAG-cost matrix left the guard GREEN. The want list lives inside the very
file the guard reads, so each entry matched its own list line. The check
could not go red for any edit to the matrix.

The same is true at the original commit — the guard read
schema_dag_cost_test.go, which is where its want list was — so this is not
fallout from the consolidation. One entry, "Parse(dag)", occurs exactly
once in that file, meaning the ONLY occurrence was its own list line: it
asserted nothing from the day it was written. The matrix spells that cell
wantCostDoesNotScale(t, cell, "Parse", ...).

Two changes: the guard cuts its own function body out of the searched text
(via t.Name(), so it cannot rot on a rename), and the wants become the
cells LABELS, which is the naming the matrix actually uses and applies
uniformly across all nineteen of its cells.

Neuter-verified: renaming the six real ocf-header cells while leaving the
want list intact — the exact edit that used to pass — now goes red
(exit!=0, RUN>0, no panic).
Base automatically changed from doc-comments to main August 5, 2026 22:33
@twmb twmb closed this Aug 5, 2026
@twmb twmb reopened this Aug 5, 2026
@twmb
twmb merged commit d9985ad into main Aug 5, 2026
3 checks passed
@twmb
twmb deleted the test-consolidation branch August 5, 2026 23:03
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