| 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