2026-07-06 09:49:50 +02:00
|
|
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
|
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
|
|
|
|
|
|
package lint
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"os"
|
2026-08-29 15:25:15 +02:00
|
|
|
"os/exec"
|
2026-07-06 09:49:50 +02:00
|
|
|
"path/filepath"
|
2026-08-29 15:25:15 +02:00
|
|
|
"strings"
|
2026-07-06 09:49:50 +02:00
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
"sourcedock.dev/petrbalvin/gasm-devkit/arch"
|
|
|
|
|
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// TestGoRuntimeCorpus parses and lints every runtime .s file the local Go
|
|
|
|
|
// toolchain ships for all four supported architectures. This is the
|
|
|
|
|
// real-world regression net: it exercises the full breadth of each
|
|
|
|
|
// architecture's syntax (macros, addressing modes, branch aliases) against
|
|
|
|
|
// production assembly. It is skipped when the toolchain source is absent.
|
|
|
|
|
//
|
|
|
|
|
// The bar is zero parse errors and zero error-severity diagnostics — i.e. no
|
|
|
|
|
// false "unknown instruction" / "undefined label" findings on code the real
|
|
|
|
|
// assembler accepts. Advisory warnings are reported but not fatal, since they
|
|
|
|
|
// are heuristics that may legitimately differ across Go versions.
|
|
|
|
|
func TestGoRuntimeCorpus(t *testing.T) {
|
2026-08-29 15:25:15 +02:00
|
|
|
out, err := exec.Command("go", "env", "GOROOT").Output()
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Skipf("cannot locate GOROOT: %v", err)
|
|
|
|
|
}
|
|
|
|
|
dir := filepath.Join(strings.TrimSpace(string(out)), "src", "runtime")
|
2026-07-06 09:49:50 +02:00
|
|
|
var files []string
|
|
|
|
|
for _, suffix := range []string{"_amd64.s", "_arm64.s", "_riscv64.s", "_loong64.s"} {
|
|
|
|
|
matches, _ := filepath.Glob(filepath.Join(dir, "*"+suffix))
|
|
|
|
|
files = append(files, matches...)
|
|
|
|
|
}
|
|
|
|
|
if len(files) == 0 {
|
|
|
|
|
t.Skip("Go toolchain source (src/runtime/*.s) not present")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
warnings := 0
|
|
|
|
|
for _, path := range files {
|
|
|
|
|
src, err := os.ReadFile(path)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("read %s: %v", path, err)
|
|
|
|
|
}
|
|
|
|
|
f, errs := parser.Parse(path, string(src))
|
|
|
|
|
if len(errs) > 0 {
|
|
|
|
|
t.Errorf("parse %s: %v", filepath.Base(path), errs)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
diags := File(f, Config{Arch: arch.FromFilename(path)})
|
|
|
|
|
for _, d := range diags {
|
|
|
|
|
if d.Severity == Error {
|
|
|
|
|
t.Errorf("%s:%d: error %s: %s", filepath.Base(path), d.Pos.Line, d.Code, d.Message)
|
|
|
|
|
} else {
|
|
|
|
|
warnings++
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if warnings > 0 {
|
|
|
|
|
t.Logf("%d advisory warnings across %d files (non-fatal)", warnings, len(files))
|
|
|
|
|
}
|
|
|
|
|
}
|