test(format): corpus invariants over GOROOT assembly

Three property tests walk every .s file under the installed GOROOT plus
the repository's kernels: formatting is idempotent and preserves the
token stream, a clean parse keeps its tree through a format pass, and a
cleanly expanding file expands to the same statements afterwards.  The
walks skip under -short so the push suite keeps its budget.

Assisted-by: GLM 5.3
This commit is contained in:
petrbalvin committed 2026-10-07 13:54:42 +02:00
1 parent 59637d35b1
commit 32c453d66e
1 file changed
+142
+142
View File
@@ -0,0 +1,142 @@
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
// SPDX-License-Identifier: BSD-3-Clause
package format
import (
"io/fs"
"os"
"path/filepath"
"reflect"
"runtime"
"strings"
"testing"
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
)
// corpusSources collects every .s file the formatter is held against: the
// repository's own kernels first, then the whole of the installed GOROOT, the
// richest body of real Plan 9 assembly on the machine.
func corpusSources(t *testing.T) []string {
t.Helper()
var paths []string
for _, pattern := range []string{"../testdata/*.s", "../testdata/verify/*.s"} {
files, _ := filepath.Glob(pattern)
paths = append(paths, files...)
}
root := runtime.GOROOT()
filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
if err == nil && !d.IsDir() && strings.HasSuffix(path, ".s") {
paths = append(paths, path)
}
return nil
})
if len(paths) == 0 {
t.Fatal("no corpus sources found")
}
return paths
}
// corpusSkip keeps the corpus walks out of the -short push suite: they need
// the machine to themselves the way the suite workflow's long tests do.
func corpusSkip(t *testing.T) {
t.Helper()
if testing.Short() {
t.Skip("the corpus belongs to the full suite; -short replays the seed corpora only")
}
}
// TestCorpusIdempotenceAndTokens runs the formatter's two standing
// invariants over the whole corpus: formatting is idempotent
// (Source(Source(x)) == Source(x)), and the output re-lexes to the same
// tokens as the input, so formatting changes layout, never meaning.
func TestCorpusIdempotenceAndTokens(t *testing.T) {
corpusSkip(t)
var files, failures int
for _, path := range corpusSources(t) {
b, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read %s: %v", path, err)
}
files++
src := string(b)
once := Source(src)
if twice := Source(once); twice != once {
failures++
t.Errorf("formatting %s is not idempotent", path)
}
if in, out := tokenView(src), tokenView(once); !sameView(in, out) {
failures++
t.Errorf("formatting %s changed the token stream:\ninput: %v\noutput: %v", path, viewString(in), viewString(out))
}
}
t.Logf("idempotence and token view checked over %d files, %d failures", files, failures)
}
// TestCorpusParseRoundTrip runs the parser's side of the contract over the
// corpus: a file that parses cleanly still parses cleanly after formatting,
// and its tree, positions aside, is unchanged. parse(format(parse(x))
// therefore sees the same file as parse(x).
func TestCorpusParseRoundTrip(t *testing.T) {
corpusSkip(t)
var files, clean, failures int
for _, path := range corpusSources(t) {
b, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read %s: %v", path, err)
}
files++
src := string(b)
file, errs := parser.Parse(path, src)
if len(errs) > 0 || hasStrayIllegal(src) {
continue // no clean tree to preserve; the token test covers it
}
clean++
reFile, errs := parser.Parse(path, Source(src))
if len(errs) > 0 {
failures++
t.Errorf("formatted %s no longer parses: %v", path, errs[0])
continue
}
if got := scrub(reFile); !reflect.DeepEqual(got, scrub(file)) {
failures++
t.Errorf("formatting %s changed the tree", path)
}
}
t.Logf("tree round-trip checked over %d files, %d clean parses, %d failures", files, clean, failures)
}
// TestCorpusExpansionRoundTrip runs the assembly path's contract over the
// corpus: a file the expander reads cleanly expands to exactly the same
// statement sequence after formatting. Files whose includes cannot be
// resolved without the go command's generated headers are skipped on both
// sides of the comparison alike.
func TestCorpusExpansionRoundTrip(t *testing.T) {
corpusSkip(t)
var files, expandable, failures int
for _, path := range corpusSources(t) {
b, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read %s: %v", path, err)
}
files++
src := string(b)
before, errs := parser.ParseWithOptions(path, src, parser.Options{Expand: true})
if len(errs) > 0 || hasStrayIllegal(src) {
continue
}
expandable++
after, errs := parser.ParseWithOptions(path, Source(src), parser.Options{Expand: true})
if len(errs) > 0 {
failures++
t.Errorf("formatted %s no longer expands: %v", path, errs[0])
continue
}
if got, want := stmtSignature(after), stmtSignature(before); !reflect.DeepEqual(got, want) {
failures++
t.Errorf("formatting %s changed the expansion", path)
}
}
t.Logf("expansion round-trip checked over %d files, %d expandable, %d failures", files, expandable, failures)
}