1 | // Copyright 2013 The Go Authors. All rights reserved. |
---|---|
2 | // Use of this source code is governed by a BSD-style |
3 | // license that can be found in the LICENSE file. |
4 | |
5 | package interp_test |
6 | |
7 | // This test runs the SSA interpreter over sample Go programs. |
8 | // Because the interpreter requires intrinsics for assembly |
9 | // functions and many low-level runtime routines, it is inherently |
10 | // not robust to evolutionary change in the standard library. |
11 | // Therefore the test cases are restricted to programs that |
12 | // use a fake standard library in testdata/src containing a tiny |
13 | // subset of simple functions useful for writing assertions. |
14 | // |
15 | // We no longer attempt to interpret any real standard packages such as |
16 | // fmt or testing, as it proved too fragile. |
17 | |
18 | import ( |
19 | "bytes" |
20 | "fmt" |
21 | "go/build" |
22 | "go/types" |
23 | "log" |
24 | "os" |
25 | "path/filepath" |
26 | "strings" |
27 | "testing" |
28 | "time" |
29 | |
30 | "golang.org/x/tools/go/loader" |
31 | "golang.org/x/tools/go/ssa" |
32 | "golang.org/x/tools/go/ssa/interp" |
33 | "golang.org/x/tools/go/ssa/ssautil" |
34 | "golang.org/x/tools/internal/typeparams" |
35 | ) |
36 | |
37 | // Each line contains a space-separated list of $GOROOT/test/ |
38 | // filenames comprising the main package of a program. |
39 | // They are ordered quickest-first, roughly. |
40 | // |
41 | // If a test in this list fails spuriously, remove it. |
42 | var gorootTestTests = []string{ |
43 | "235.go", |
44 | "alias1.go", |
45 | "func5.go", |
46 | "func6.go", |
47 | "func7.go", |
48 | "func8.go", |
49 | "helloworld.go", |
50 | "varinit.go", |
51 | "escape3.go", |
52 | "initcomma.go", |
53 | "cmp.go", |
54 | "compos.go", |
55 | "turing.go", |
56 | "indirect.go", |
57 | "complit.go", |
58 | "for.go", |
59 | "struct0.go", |
60 | "intcvt.go", |
61 | "printbig.go", |
62 | "deferprint.go", |
63 | "escape.go", |
64 | "range.go", |
65 | "const4.go", |
66 | "float_lit.go", |
67 | "bigalg.go", |
68 | "decl.go", |
69 | "if.go", |
70 | "named.go", |
71 | "bigmap.go", |
72 | "func.go", |
73 | "reorder2.go", |
74 | "gc.go", |
75 | "simassign.go", |
76 | "iota.go", |
77 | "nilptr2.go", |
78 | "utf.go", |
79 | "method.go", |
80 | "char_lit.go", |
81 | "env.go", |
82 | "int_lit.go", |
83 | "string_lit.go", |
84 | "defer.go", |
85 | "typeswitch.go", |
86 | "stringrange.go", |
87 | "reorder.go", |
88 | "method3.go", |
89 | "literal.go", |
90 | "nul1.go", // doesn't actually assert anything (errorcheckoutput) |
91 | "zerodivide.go", |
92 | "convert.go", |
93 | "convT2X.go", |
94 | "switch.go", |
95 | "ddd.go", |
96 | "blank.go", // partly disabled |
97 | "closedchan.go", |
98 | "divide.go", |
99 | "rename.go", |
100 | "nil.go", |
101 | "recover1.go", |
102 | "recover2.go", |
103 | "recover3.go", |
104 | "typeswitch1.go", |
105 | "floatcmp.go", |
106 | "crlf.go", // doesn't actually assert anything (runoutput) |
107 | } |
108 | |
109 | // These are files in go.tools/go/ssa/interp/testdata/. |
110 | var testdataTests = []string{ |
111 | "boundmeth.go", |
112 | "complit.go", |
113 | "convert.go", |
114 | "coverage.go", |
115 | "deepequal.go", |
116 | "defer.go", |
117 | "fieldprom.go", |
118 | "ifaceconv.go", |
119 | "ifaceprom.go", |
120 | "initorder.go", |
121 | "methprom.go", |
122 | "mrvchain.go", |
123 | "range.go", |
124 | "recover.go", |
125 | "reflect.go", |
126 | "static.go", |
127 | "width32.go", |
128 | |
129 | "fixedbugs/issue52342.go", |
130 | } |
131 | |
132 | func init() { |
133 | if typeparams.Enabled { |
134 | testdataTests = append(testdataTests, "fixedbugs/issue52835.go") |
135 | testdataTests = append(testdataTests, "fixedbugs/issue55086.go") |
136 | testdataTests = append(testdataTests, "typeassert.go") |
137 | testdataTests = append(testdataTests, "zeros.go") |
138 | } |
139 | } |
140 | |
141 | // Specific GOARCH to use for a test case in go.tools/go/ssa/interp/testdata/. |
142 | // Defaults to amd64 otherwise. |
143 | var testdataArchs = map[string]string{ |
144 | "width32.go": "386", |
145 | } |
146 | |
147 | func run(t *testing.T, input string) bool { |
148 | // The recover2 test case is broken on Go 1.14+. See golang/go#34089. |
149 | // TODO(matloob): Fix this. |
150 | if filepath.Base(input) == "recover2.go" { |
151 | t.Skip("The recover2.go test is broken in go1.14+. See golang.org/issue/34089.") |
152 | } |
153 | |
154 | t.Logf("Input: %s\n", input) |
155 | |
156 | start := time.Now() |
157 | |
158 | ctx := build.Default // copy |
159 | ctx.GOROOT = "testdata" // fake goroot |
160 | ctx.GOOS = "linux" |
161 | ctx.GOARCH = "amd64" |
162 | if arch, ok := testdataArchs[filepath.Base(input)]; ok { |
163 | ctx.GOARCH = arch |
164 | } |
165 | |
166 | conf := loader.Config{Build: &ctx} |
167 | if _, err := conf.FromArgs([]string{input}, true); err != nil { |
168 | t.Errorf("FromArgs(%s) failed: %s", input, err) |
169 | return false |
170 | } |
171 | |
172 | conf.Import("runtime") |
173 | |
174 | // Print a helpful hint if we don't make it to the end. |
175 | var hint string |
176 | defer func() { |
177 | if hint != "" { |
178 | fmt.Println("FAIL") |
179 | fmt.Println(hint) |
180 | } else { |
181 | fmt.Println("PASS") |
182 | } |
183 | |
184 | interp.CapturedOutput = nil |
185 | }() |
186 | |
187 | hint = fmt.Sprintf("To dump SSA representation, run:\n%% go build golang.org/x/tools/cmd/ssadump && ./ssadump -test -build=CFP %s\n", input) |
188 | |
189 | iprog, err := conf.Load() |
190 | if err != nil { |
191 | t.Errorf("conf.Load(%s) failed: %s", input, err) |
192 | return false |
193 | } |
194 | |
195 | bmode := ssa.InstantiateGenerics | ssa.SanityCheckFunctions |
196 | // bmode |= ssa.PrintFunctions // enable for debugging |
197 | prog := ssautil.CreateProgram(iprog, bmode) |
198 | prog.Build() |
199 | |
200 | mainPkg := prog.Package(iprog.Created[0].Pkg) |
201 | if mainPkg == nil { |
202 | t.Fatalf("not a main package: %s", input) |
203 | } |
204 | |
205 | interp.CapturedOutput = new(bytes.Buffer) |
206 | |
207 | sizes := types.SizesFor("gc", ctx.GOARCH) |
208 | hint = fmt.Sprintf("To trace execution, run:\n%% go build golang.org/x/tools/cmd/ssadump && ./ssadump -build=C -test -run --interp=T %s\n", input) |
209 | var imode interp.Mode // default mode |
210 | // imode |= interp.DisableRecover // enable for debugging |
211 | // imode |= interp.EnableTracing // enable for debugging |
212 | exitCode := interp.Interpret(mainPkg, imode, sizes, input, []string{}) |
213 | if exitCode != 0 { |
214 | t.Fatalf("interpreting %s: exit code was %d", input, exitCode) |
215 | } |
216 | // $GOROOT/test tests use this convention: |
217 | if strings.Contains(interp.CapturedOutput.String(), "BUG") { |
218 | t.Fatalf("interpreting %s: exited zero but output contained 'BUG'", input) |
219 | } |
220 | |
221 | hint = "" // call off the hounds |
222 | |
223 | if false { |
224 | t.Log(input, time.Since(start)) // test profiling |
225 | } |
226 | |
227 | return true |
228 | } |
229 | |
230 | func printFailures(failures []string) { |
231 | if failures != nil { |
232 | fmt.Println("The following tests failed:") |
233 | for _, f := range failures { |
234 | fmt.Printf("\t%s\n", f) |
235 | } |
236 | } |
237 | } |
238 | |
239 | // TestTestdataFiles runs the interpreter on testdata/*.go. |
240 | func TestTestdataFiles(t *testing.T) { |
241 | cwd, err := os.Getwd() |
242 | if err != nil { |
243 | log.Fatal(err) |
244 | } |
245 | var failures []string |
246 | for _, input := range testdataTests { |
247 | if !run(t, filepath.Join(cwd, "testdata", input)) { |
248 | failures = append(failures, input) |
249 | } |
250 | } |
251 | printFailures(failures) |
252 | } |
253 | |
254 | // TestGorootTest runs the interpreter on $GOROOT/test/*.go. |
255 | func TestGorootTest(t *testing.T) { |
256 | var failures []string |
257 | |
258 | for _, input := range gorootTestTests { |
259 | if !run(t, filepath.Join(build.Default.GOROOT, "test", input)) { |
260 | failures = append(failures, input) |
261 | } |
262 | } |
263 | printFailures(failures) |
264 | } |
265 | |
266 | // TestTypeparamTest runs the interpreter on runnable examples |
267 | // in $GOROOT/test/typeparam/*.go. |
268 | |
269 | func TestTypeparamTest(t *testing.T) { |
270 | if !typeparams.Enabled { |
271 | return |
272 | } |
273 | |
274 | // Skip known failures for the given reason. |
275 | // TODO(taking): Address these. |
276 | skip := map[string]string{ |
277 | "chans.go": "interp tests do not support runtime.SetFinalizer", |
278 | "issue23536.go": "unknown reason", |
279 | "issue376214.go": "unknown issue with variadic cast on bytes", |
280 | "issue48042.go": "interp tests do not handle reflect.Value.SetInt", |
281 | "issue47716.go": "interp tests do not handle unsafe.Sizeof", |
282 | "issue50419.go": "interp tests do not handle dispatch to String() correctly", |
283 | "issue51733.go": "interp does not handle unsafe casts", |
284 | "ordered.go": "math.NaN() comparisons not being handled correctly", |
285 | "orderedmap.go": "interp tests do not support runtime.SetFinalizer", |
286 | "stringer.go": "unknown reason", |
287 | "issue48317.go": "interp tests do not support encoding/json", |
288 | "issue48318.go": "interp tests do not support encoding/json", |
289 | } |
290 | // Collect all of the .go files in dir that are runnable. |
291 | dir := filepath.Join(build.Default.GOROOT, "test", "typeparam") |
292 | list, err := os.ReadDir(dir) |
293 | if err != nil { |
294 | t.Fatal(err) |
295 | } |
296 | var inputs []string |
297 | for _, entry := range list { |
298 | if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".go") { |
299 | continue // Consider standalone go files. |
300 | } |
301 | if reason := skip[entry.Name()]; reason != "" { |
302 | t.Logf("skipping %q due to %s.", entry.Name(), reason) |
303 | continue |
304 | } |
305 | input := filepath.Join(dir, entry.Name()) |
306 | src, err := os.ReadFile(input) |
307 | if err != nil { |
308 | t.Fatal(err) |
309 | } |
310 | // Only build test files that can be compiled, or compiled and run. |
311 | if bytes.HasPrefix(src, []byte("// run")) && !bytes.HasPrefix(src, []byte("// rundir")) { |
312 | inputs = append(inputs, input) |
313 | } else { |
314 | t.Logf("Not a `// run` file: %s", entry.Name()) |
315 | } |
316 | } |
317 | |
318 | var failures []string |
319 | for _, input := range inputs { |
320 | t.Log("running", input) |
321 | if !run(t, input) { |
322 | failures = append(failures, input) |
323 | } |
324 | } |
325 | printFailures(failures) |
326 | } |
327 |
Members