Skip to content

refactor(compilers/openapi): define the yaml-node builders once - #373

Open
OmarAlJarrah wants to merge 1 commit into
mainfrom
refactor/openapi-shared-yaml-builders
Open

refactor(compilers/openapi): define the yaml-node builders once#373
OmarAlJarrah wants to merge 1 commit into
mainfrom
refactor/openapi-shared-yaml-builders

Conversation

@OmarAlJarrah

Copy link
Copy Markdown
Member

Summary

internal/nodeview and internal/scan each carried their own copy of the same six
yaml-node builders — yscalar, ymap, yseq, yalias, ymerge, mergeChain — byte for
byte in four cases. mergeChainSpec was a seventh copy, shared between internal/scan and
the compiler package's own tests.

Direction taken: one definition, not a comment explaining why there are two.

The issue offered both and asked for a decision. What kept the family apart was that
ymerge needs the merge tag and the tag lived in nodeview, so any shared home would have
had to import nodeview — and nodeview's own tests are one of the two callers. That
constraint is real, and I checked it by construction rather than by reading: a probe package
importing nodeview, imported from nodeview's internal test file, fails to build with

imports .../internal/cycleprobe from probe_cycle_internal_test.go
imports .../internal/nodeview from probe.go: import cycle not allowed in test

But the constraint only blocks a home above nodeview. Moving the tag below it removes
the problem entirely, and the same probe inverted — a package nodeview imports, imported
from nodeview's internal test — builds and passes. So the family gets a home:
compilers/openapi/internal/ynode holds MergeTag and the constructors that spell the node
shapes it names, nodeview.IsMergeKey reads the tag back, and both packages' tests build
nodes from the one definition.

MergeTag is a fact about yaml.v3 and speakeasy's yml.IsMergeKey, not about the view, so
it reads no worse one level down. IsMergeKey itself stays in nodeview — it is the view's
predicate, used by the view's expansion — and keeps the comment explaining what the tag
means and why the check is spelled the way it is.

mergeChainSpec moves too, as ynode.MergeChainSpec: it is the same merge-chain fixture in
source form, spelling the same << chain whose tag the package defines.

ynode gets its own entry in internal/archtest's rules map (gopkg.in/yaml.v3 and
nothing else) and nodeview's entry gains it, with the import constraint written down as
the reason the package sits where it does. It also gets its own tests: without -coverpkg a
package is instrumented only by its own test binary, so one with statements and no test
files contributes zero-count blocks to the profile and fails the coverage gate — confirmed
against the probe package, which reported coverage: 0.0% and put a zero-count block in the
profile while its only callers were elsewhere.

Test plan

Every builder is defined exactly once. The issue's own grep now matches nothing:

$ grep -rn '^func \(yscalar\|ymap\|yseq\|yalias\|ymerge\|mergeChain\)(' --include='*_test.go' compilers/openapi
$ grep -rn 'func mergeChainSpec' --include='*.go' .
$ grep -rn '^func \(Scalar\|Map\|Seq\|Alias\|Merge\|MergeChain\|MergeChainSpec\)(' --include='*.go' compilers/openapi
compilers/openapi/internal/ynode/ynode.go:25:func Scalar(v string) *yaml.Node {
compilers/openapi/internal/ynode/ynode.go:31:func Map(pairs ...*yaml.Node) *yaml.Node {
compilers/openapi/internal/ynode/ynode.go:36:func Seq(items ...*yaml.Node) *yaml.Node {
compilers/openapi/internal/ynode/ynode.go:42:func Alias(target *yaml.Node) *yaml.Node {
compilers/openapi/internal/ynode/ynode.go:48:func Merge() *yaml.Node {
compilers/openapi/internal/ynode/ynode.go:56:func MergeChain(levels int) *yaml.Node {
compilers/openapi/internal/ynode/ynode.go:72:func MergeChainSpec(levels int) string {

This is a test-infrastructure change, so the risk is a silently weakened test rather than a
broken one. Three things were checked:

The moved builders produce identical nodes. A throwaway test compiled the pre-move
definitions verbatim from git HEAD alongside the moved ones and compared their output —
cmp.Diff over every scalar value the callers use, a nested tree built by both sets, and
MergeChain/MergeChainSpec at levels 0, 1, 2, 3, 7, 64, 66, 200 and 1600. Green.
MergeChainSpec's body also diffs clean against the original line for line.

No test changed meaning. Comparing go test ./... -list '.*' before and after, the only
difference is additions — 7 new tests, all in the new package, zero removals or renames
(1270 names before, 1277 after).

The new tests were checked against planted defects rather than read:

planted defect result
MergeTag becomes "!!MERGE" RED in three packages — ynode's TestMerge_MatchesTheParsedMergeKey, nodeview's TestIsMergeKey_AgreesWithParsedTags and TestMappingPairs_MergeKeySources, scan's TestDetectCycles_Reproducers
MergeChain's leaf pair becomes {leaf: w} RED — ynode's TestMergeChain_NestsOneMergePerLevelOverALeaf and nodeview's TestNodeView_TruncationIsPerNode
MergeChainSpec stops writing paths: {} initially GREEN everywhere but the throwaway — see below

That first row is the load-bearing one: the mutation reddens production behaviour in
nodeview and scan, not just the new package's own tests, which is what shows the moved
constant is still the one IsMergeKey reads.

The third row is why an assertion was added. Dropping paths: {} from the fixture reddened
nothing in the repository — 3.1 makes paths optional, and the fixture still parsed,
compiled and satisfied both callers. TestMergeChainSpec_AnchorsEveryLevelAndNamesItFromASchema
now asserts the preamble key by key, and re-planting the defect reddens it.

Full gate green, coverage at exactly 100% including the new package:

--- [1/5] gofmt --- OK
--- [2/5] go vet --- OK
--- [3/5] golangci-lint --- 0 issues.
--- [4/5] go build --- OK
--- [5/5] coverage gate ---
ok  github.com/dexpace/morphic/compilers/openapi/internal/ynode  coverage: 100.0% of statements
Coverage gate passed: all 4963 statements covered.

Out of scope

pairMap is still a per-package copy in nodeview and scan. It takes []nodeview.Pair,
so a shared home for it would have to import nodeview — the cycle above, and not one
moving a constant can fix. cycleReproducers and readReproducer are also duplicated
between internal/scan and the compiler package's tests; both are unrelated to the merge
tag.

Closes #325

internal/nodeview and internal/scan each carried their own copy of the same
six yaml-node builders, byte for byte in four cases, and mergeChainSpec was a
seventh copy shared between internal/scan and the compiler's own tests.

The copies survived because ymerge needs the merge tag and the tag lived in
nodeview: a home above nodeview would be one nodeview's own internal tests
could not import, since an internal test file cannot import a package that
imports its own package. Rather than split the family across two homes, the
tag moves down instead. compilers/openapi/internal/ynode holds MergeTag and
the constructors that spell the node shapes it names, nodeview imports it for
IsMergeKey, and both packages' tests build nodes from the one definition.

MergeTag is a fact about yaml.v3 and speakeasy rather than about the view, so
it reads no worse one level down, and the predicate that tests for it keeps
the comment explaining what the tag means.

ynode carries its own tests: without -coverpkg a package is instrumented only
by its own test binary, so one with statements and no test files contributes
zero-count blocks to the profile and fails the coverage gate.
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.

openapi: the nodeview/scan yaml-node builders are still a per-package copy

1 participant