go/src/cmd/compile/internal/ssa/generate_test.go
David Chase d4c6effaa7 cmd/compile: add up-to-date test for generated files
This runs the ssa/_gen generator writing files into
a temporary directory, and then checks that there are
no differences with what is currently in the ssa directory,
and also checks that any file with the "generated from
_gen/..." header was actually generated, and checks that
the headers on the generated file match the expected
header prefix.

Change-Id: Ic8eeb0b06cf6f2e576a013e865b331a12d3a77aa
Reviewed-on: https://go-review.googlesource.com/c/go/+/680615
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Keith Randall <khr@golang.org>
Reviewed-by: Keith Randall <khr@google.com>
2025-06-11 10:11:53 -07:00

135 lines
4.1 KiB
Go

// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ssa
import (
"bytes"
"fmt"
"internal/testenv"
"os"
"path/filepath"
"testing"
)
const expectedHeader = "// Code generated from _gen/" // this is the common part
// TestGeneratedFilesUpToDate regenerates all the rewrite and rewrite-related
// files defined in _gen into a temporary directory,
// checks that they match what appears in the source tree,
// verifies that they start with the prefix of a generated header,
// and checks that the only source files with that header were actually generated.
func TestGeneratedFilesUpToDate(t *testing.T) {
testenv.MustHaveGoRun(t)
wd, err := os.Getwd()
if err != nil {
t.Fatalf("Failed to get current working directory: %v", err)
}
genDir := filepath.Join(wd, "_gen")
if _, err := os.Stat(genDir); os.IsNotExist(err) {
t.Fatalf("_gen directory not found")
}
tmpdir := t.TempDir()
// Accumulate a list of all existing files that look generated.
// It's an error if this set does not match the set that are
// generated into tmpdir.
genFiles := make(map[string]bool)
genPrefix := []byte(expectedHeader)
ssaFiles, err := filepath.Glob(filepath.Join(wd, "*.go"))
if err != nil {
t.Fatalf("could not glob for .go files in ssa directory: %v", err)
}
for _, f := range ssaFiles {
contents, err := os.ReadFile(f)
if err != nil {
t.Fatalf("could not read source file from ssa directory: %v", err)
}
// verify that the generated file has the expected header
// (this should cause other failures later, but if this is
// the problem, diagnose it here to shorten the treasure hunt.)
if bytes.HasPrefix(contents, genPrefix) {
genFiles[filepath.Base(f)] = true
}
}
goFiles, err := filepath.Glob(filepath.Join(genDir, "*.go"))
if err != nil {
t.Fatalf("could not glob for .go files in _gen: %v", err)
}
if len(goFiles) == 0 {
t.Fatal("no .go files found in _gen")
}
// Construct the command line for "go run".
// Explicitly list the files, just to make it
// clear what is included (if the test is logging).
args := []string{"run", "-C", genDir}
for _, f := range goFiles {
args = append(args, filepath.Base(f))
}
args = append(args, "-outdir", tmpdir)
logArgs := fmt.Sprintf("%v", args)
logArgs = logArgs[1 : len(logArgs)-2] // strip '[' and ']'
t.Logf("%s %v", testenv.GoToolPath(t), logArgs)
output, err := testenv.Command(t, testenv.GoToolPath(t), args...).CombinedOutput()
if err != nil {
t.Fatalf("go run in _gen failed: %v\n%s", err, output)
}
// Compare generated files with existing files in the parent directory.
files, err := os.ReadDir(tmpdir)
if err != nil {
t.Fatalf("could not read tmpdir %s: %v", tmpdir, err)
}
for _, file := range files {
if file.IsDir() {
continue
}
filename := file.Name()
// filename must be in the generated set,
if !genFiles[filename] {
t.Errorf("%s does not start with the expected header '%s' (if the header was changed the test needs to be updated)",
filename, expectedHeader)
}
genFiles[filename] = false // remove from set
generatedPath := filepath.Join(tmpdir, filename)
originalPath := filepath.Join(wd, filename)
generatedData, err := os.ReadFile(generatedPath)
if err != nil {
t.Errorf("could not read generated file %s: %v", generatedPath, err)
continue
}
// there should be a corresponding file in the ssa directory,
originalData, err := os.ReadFile(originalPath)
if err != nil {
if os.IsNotExist(err) {
t.Errorf("generated file %s was created, but does not exist in the ssa directory. It may need to be added to the repository.", filename)
} else {
t.Errorf("could not read original file %s: %v", originalPath, err)
}
continue
}
// and the contents of that file should match.
if !bytes.Equal(originalData, generatedData) {
t.Errorf("%s is out of date. Please run 'go generate'.", filename)
}
}
// the generated set should be empty now.
for file, notGenerated := range genFiles {
if notGenerated {
t.Errorf("%s has the header of a generated file but was not generated", file)
}
}
}