aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/compile/internal/noder/unified_test.go
blob: 7f0bca23324efe3d0ac0e9bbd0e5c79356965a20 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
// 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) {
	t.Skip("TODO(#48265): this fails on testing/internal/testdeps, possibly due to type aliases. Fix before merging to master.")
	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)
}