test(format): fuzz the expansion round-trip

A second formatter target holds the assembly path's contract: beyond the
token view, a file the expander reads cleanly must expand to the same
statement sequence after formatting, because the macro language draws
distinctions from layout (the adjacency of a #define name and its '(',
continuation bodies) that a token count cannot see.

Assisted-by: GLM 5.3
This commit is contained in:
petrbalvin committed 2026-10-07 13:54:42 +02:00
1 parent e600fc00e7
commit 59637d35b1
1 file changed
+62
+62
View File
@@ -204,6 +204,68 @@ func dump(v any) string {
return b.String()
}
// FuzzFormatExpansionRoundTrip hammers the formatter against the assembly
// path's reading of the file: macro expansion and include splicing. The
// token view pins every token the formatter carries over, and beyond that a
// file the expander reads cleanly must expand to exactly the same statement
// sequence after formatting, because distinctions the macro language draws
// from layout (the adjacency of a #define name and its '(', the backslash
// continuations that split a body into statements) live in spacing a pure
// token count cannot see. The seed corpus carries the repository's kernels,
// so a plain `go test` run replays every seed as a regression case.
func FuzzFormatExpansionRoundTrip(f *testing.F) {
for _, pattern := range []string{
"../testdata/*.s",
"../testdata/verify/*.s",
} {
files, _ := filepath.Glob(pattern)
for _, path := range files {
if b, err := os.ReadFile(path); err == nil {
f.Add(string(b))
}
}
}
f.Add("#define A(x) x+1\nTEXT ·f(SB), $0\n\tA(2)\n\tRET\n")
f.Add("#define A (x) x+1\nA\n")
f.Add("#define M (BX)\nTEXT ·f(SB), $0\n\tMOVL M, AX\n\tRET\n")
f.Add("#define M() \\\n\tADD $1, R0 \\\n\tSUB $2, R1\nTEXT ·f(SB), $0\n\tM()\n\tRET\n")
f.Add("#define PAIR(v) \\\n\tADD $v, R0; \\\n\tSUB $v, R1\nTEXT ·f(SB), $0\n\tPAIR(3)\n\tRET\n")
f.Add("#define A A\nA\n") // self-reference
f.Add("#define A(x) ((x))\nA(A(A(1)))\n") // nested invocation
f.Add("#define A 1\n#define B A\nB\n") // chained object macros
f.Add("#ifdef X\n#else\n#endif\n") // conditionals
f.Add("#define A(x) x x\nA(A(A(1)))\n") // amplifying body
f.Add("#include \"textflag.h\"\n") // the never-spliced header
f.Add("#include \"no/such/header.h\"\n") // unresolvable include
f.Add("#define A(x)\nA()\n") // empty body
f.Add("TEXT ·f(SB), $0\n\tM\n\tRET\n") // undefined name, no table entry
f.Fuzz(func(t *testing.T, src string) {
out := Source(src)
in, o := tokenView(src), tokenView(out)
if !sameView(in, o) {
t.Fatalf("formatting changed the token stream:\ninput: %v\noutput: %v\n%s", viewString(in), viewString(o), out)
}
if hasStrayIllegal(src) {
// The formatter documents dropping stray runes; their files have
// no expansion to preserve, and the token view above is a weaker
// contract that already held.
return
}
before, errs := parser.ParseWithOptions("in.s", src, parser.Options{Expand: true})
if len(errs) > 0 {
return
}
after, errs := parser.ParseWithOptions("out.s", out, parser.Options{Expand: true})
if len(errs) > 0 {
t.Fatalf("formatted output of expandable input no longer expands: %v\n%s", errs[0], out)
}
if got, want := stmtSignature(after), stmtSignature(before); !reflect.DeepEqual(got, want) {
t.Fatalf("formatting changed the expansion:\nbefore: %v\nafter: %v\n%s", want, got, out)
}
})
}
func dumpValue(v reflect.Value, b *strings.Builder) {
switch v.Kind() {
case reflect.Pointer, reflect.Interface: