| // Copyright 2021 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 noder_test | |
| import ( | |
| "encoding/json" | |
| "flag" | |
| exec "internal/execabs" | |
| "os" | |
| "reflect" | |
| "runtime" | |
| "strings" | |
| "testing" | |
| ) | |
| var ( | |
| flagPkgs = flag.String("pkgs", "std", "list of packages to compare (ignored in -short mode)") | |
| flagAll = flag.Bool("all", false, "enable testing of all GOOS/GOARCH targets") | |
| flagParallel = flag.Bool("parallel", false, "test GOOS/GOARCH targets in parallel") | |
| ) | |
| // TestUnifiedCompare implements a test similar to running: | |
| // | |
| // $ go build -toolexec="toolstash -cmp" std | |
| // | |
| // The -pkgs flag controls the list of packages tested. | |
| // | |
| // By default, only the native GOOS/GOARCH target is enabled. The -all | |
| // flag enables testing of non-native targets. The -parallel flag | |
| // additionally enables testing of targets in parallel. | |
| // | |
| // Caution: Testing all targets is very resource intensive! On an IBM | |
| // P920 (dual Intel Xeon Gold 6154 CPUs; 36 cores, 192GB RAM), testing | |
| // all targets in parallel takes about 5 minutes. Using the 'go test' | |
| // command's -run flag for subtest matching is recommended for less | |
| // powerful machines. | |
| func TestUnifiedCompare(t *testing.T) { | |
| targets, err := exec.Command("go", "tool", "dist", "list").Output() | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| for _, target := range strings.Fields(string(targets)) { | |
| t.Run(target, func(t *testing.T) { | |
| parts := strings.Split(target, "/") | |
| goos, goarch := parts[0], parts[1] | |
| if !(*flagAll || goos == runtime.GOOS && goarch == runtime.GOARCH) { | |
| t.Skip("skipping non-native target (use -all to enable)") | |
| } | |
| if *flagParallel { | |
| t.Parallel() | |
| } | |
| pkgs1 := loadPackages(t, goos, goarch, "-d=unified=0 -d=inlfuncswithclosures=0 -d=unifiedquirks=1 -G=0") | |
| pkgs2 := loadPackages(t, goos, goarch, "-d=unified=1 -d=inlfuncswithclosures=0 -d=unifiedquirks=1 -G=0") | |
| if len(pkgs1) != len(pkgs2) { | |
| t.Fatalf("length mismatch: %v != %v", len(pkgs1), len(pkgs2)) | |
| } | |
| for i := range pkgs1 { | |
| pkg1 := pkgs1[i] | |
| pkg2 := pkgs2[i] | |
| path := pkg1.ImportPath | |
| if path != pkg2.ImportPath { | |
| t.Fatalf("mismatched paths: %q != %q", path, pkg2.ImportPath) | |
| } | |
| // Packages that don't have any source files (e.g., packages | |
| // unsafe, embed/internal/embedtest, and cmd/internal/moddeps). | |
| if pkg1.Export == "" && pkg2.Export == "" { | |
| continue | |
| } | |
| if pkg1.BuildID == pkg2.BuildID { | |
| t.Errorf("package %q: build IDs unexpectedly matched", path) | |
| } | |
| // Unlike toolstash -cmp, we're comparing the same compiler | |
| // binary against itself, just with different flags. So we | |
| // don't need to worry about skipping over mismatched version | |
| // strings, but we do need to account for differing build IDs. | |
| // | |
| // Fortunately, build IDs are cryptographic 256-bit hashes, | |
| // and cmd/go provides us with them up front. So we can just | |
| // use them as delimeters to split the files, and then check | |
| // that the substrings are all equal. | |
| file1 := strings.Split(readFile(t, pkg1.Export), pkg1.BuildID) | |
| file2 := strings.Split(readFile(t, pkg2.Export), pkg2.BuildID) | |
| if !reflect.DeepEqual(file1, file2) { | |
| t.Errorf("package %q: compile output differs", path) | |
| } | |
| } | |
| }) | |
| } | |
| } | |
| type pkg struct { | |
| ImportPath string | |
| Export string | |
| BuildID string | |
| Incomplete bool | |
| } | |
| func loadPackages(t *testing.T, goos, goarch, gcflags string) []pkg { | |
| args := []string{"list", "-e", "-export", "-json", "-gcflags=all=" + gcflags, "--"} | |
| if testing.Short() { | |
| t.Log("short testing mode; only testing package runtime") | |
| args = append(args, "runtime") | |
| } else { | |
| args = append(args, strings.Fields(*flagPkgs)...) | |
| } | |
| cmd := exec.Command("go", args...) | |
| cmd.Env = append(os.Environ(), "GOOS="+goos, "GOARCH="+goarch) | |
| cmd.Stderr = os.Stderr | |
| t.Logf("running %v", cmd) | |
| stdout, err := cmd.StdoutPipe() | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| if err := cmd.Start(); err != nil { | |
| t.Fatal(err) | |
| } | |
| var res []pkg | |
| for dec := json.NewDecoder(stdout); dec.More(); { | |
| var pkg pkg | |
| if err := dec.Decode(&pkg); err != nil { | |
| t.Fatal(err) | |
| } | |
| if pkg.Incomplete { | |
| t.Fatalf("incomplete package: %q", pkg.ImportPath) | |
| } | |
| res = append(res, pkg) | |
| } | |
| if err := cmd.Wait(); err != nil { | |
| t.Fatal(err) | |
| } | |
| return res | |
| } | |
| func readFile(t *testing.T, name string) string { | |
| buf, err := os.ReadFile(name) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| return string(buf) | |
| } |