test(format): pin the canonical rendering of the flags operand

Assisted-by: GLM 5.3 Flash
This commit is contained in:
petrbalvin committed 2026-10-07 21:50:20 +02:00
1 parent 431d0d9b0b
commit 9179d5cc7a
1 file changed
+108
+108
View File
@@ -0,0 +1,108 @@
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
// SPDX-License-Identifier: BSD-3-Clause
package format
import (
"testing"
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
)
// TestFlagListCanonical pins the canonical rendering of the TEXT and GLOBL
// flags operand: the names stay glued to their '|' separators, the operands
// take one space after each comma, and the spelling survives whatever
// layout it arrives in. Every shape mixes its flags with a frame size,
// which is where the operand boundaries live.
func TestFlagListCanonical(t *testing.T) {
for _, c := range []struct{ in, want string }{
{"TEXT ·f(SB),NOSPLIT|NOFRAME,$0-16\n\tRET\n", "TEXT ·f(SB), NOSPLIT|NOFRAME, $0-16\n\tRET\n"},
{"TEXT ·f(SB) , NOSPLIT|NOFRAME , $0-16\n\tRET\n", "TEXT ·f(SB), NOSPLIT|NOFRAME, $0-16\n\tRET\n"},
{"TEXT ·f(SB), NOSPLIT|NOFRAME|TOPFRAME, $0\n\tRET\n", "TEXT ·f(SB), NOSPLIT|NOFRAME|TOPFRAME, $0\n\tRET\n"},
{"TEXT ·f(SB), 2|4, $4096-0\n\tRET\n", "TEXT ·f(SB), 2|4, $4096-0\n\tRET\n"},
{"TEXT ·f(SB), (NOSPLIT|NOFRAME), $0-8\n\tRET\n", "TEXT ·f(SB), (NOSPLIT|NOFRAME), $0-8\n\tRET\n"},
{"TEXT ·foo<ABIInternal>(SB), NOSPLIT, $-0-24\n\tRET\n", "TEXT ·foo<ABIInternal>(SB), NOSPLIT, $-0-24\n\tRET\n"},
{"GLOBL ·mask(SB),RODATA|NOPTR,$8\n", "GLOBL ·mask(SB), RODATA|NOPTR, $8\n"},
{"GLOBL ·mask(SB), 10, $8\n", "GLOBL ·mask(SB), 10, $8\n"},
} {
got := Source(c.in)
if got != c.want {
t.Errorf("formatting %q:\n got %q\n want %q", c.in, got, c.want)
continue
}
// The standing invariants over the rendered line: the output is its
// own canonical form, and formatting changed layout, never tokens.
if again := Source(got); again != got {
t.Errorf("not idempotent for %q:\n%q", c.in, again)
}
if in, out := tokenView(c.in), tokenView(got); !sameView(in, out) {
t.Errorf("formatting %q changed the token stream:\ninput: %v\noutput: %v",
c.in, viewString(in), viewString(out))
}
}
}
// TestFlagListFormatRoundTrip covers the parser's side of the same shapes:
// a header that parses cleanly still parses cleanly after formatting, and
// the flags value and canonical names survive the pass unchanged.
func TestFlagListFormatRoundTrip(t *testing.T) {
for _, src := range []string{
"TEXT ·f(SB),NOSPLIT|NOFRAME,$0-16\n\tRET\n",
"TEXT ·f(SB), 2|4, $4096-0\n\tRET\n",
"TEXT ·f(SB), (NOSPLIT|NOFRAME), $0-8\n\tRET\n",
"GLOBL ·mask(SB),RODATA|NOPTR,$8\n",
} {
before, errs := parser.Parse("in.s", src)
if len(errs) > 0 {
t.Fatalf("parse %q: %v", src, errs)
}
after, errs := parser.Parse("in.s", Source(src))
if len(errs) > 0 {
t.Fatalf("formatted %q no longer parses: %v", src, errs)
}
beforeD, afterD := firstFlaggedDecl(before), firstFlaggedDecl(after)
if beforeD == nil || afterD == nil {
t.Fatalf("%q: a TEXT or GLOBL declaration was lost by formatting", src)
}
beforeFlags, beforeVal := declFlags(beforeD)
afterFlags, afterVal := declFlags(afterD)
if beforeVal != afterVal {
t.Errorf("%q: FlagVal %d became %d", src, beforeVal, afterVal)
}
if len(beforeFlags) != len(afterFlags) {
t.Errorf("%q: flags %v became %v", src, beforeFlags, afterFlags)
continue
}
for i := range beforeFlags {
if beforeFlags[i] != afterFlags[i] {
t.Errorf("%q: flags %v became %v", src, beforeFlags, afterFlags)
break
}
}
}
}
// firstFlaggedDecl returns the file's first TEXT or GLOBL declaration, the
// two directives whose operand carries flags.
func firstFlaggedDecl(f *ast.File) ast.Decl {
for _, d := range f.Decls {
switch d.(type) {
case *ast.Text, *ast.Globl:
return d
}
}
return nil
}
// declFlags returns a declaration's flags operand: the canonical names and
// the folded value.
func declFlags(d ast.Decl) ([]string, int64) {
switch dd := d.(type) {
case *ast.Text:
return dd.Flags, dd.FlagVal
case *ast.Globl:
return dd.Flags, dd.FlagVal
}
return nil, 0
}