| // 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) |
| } |
| } |
| } |