golang.org/x/tools@v0.21.1-0.20240520172518-788d39e776b1/go/ssa/interp/interp_test.go (about) 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 "runtime" 27 "strings" 28 "testing" 29 "time" 30 "unsafe" 31 32 "golang.org/x/tools/go/loader" 33 "golang.org/x/tools/go/ssa" 34 "golang.org/x/tools/go/ssa/interp" 35 "golang.org/x/tools/go/ssa/ssautil" 36 "golang.org/x/tools/internal/testenv" 37 ) 38 39 // Each line contains a space-separated list of $GOROOT/test/ 40 // filenames comprising the main package of a program. 41 // They are ordered quickest-first, roughly. 42 // 43 // If a test in this list fails spuriously, remove it. 44 var gorootTestTests = []string{ 45 "235.go", 46 "alias1.go", 47 "func5.go", 48 "func6.go", 49 "func7.go", 50 "func8.go", 51 "helloworld.go", 52 "varinit.go", 53 "escape3.go", 54 "initcomma.go", 55 "cmp.go", 56 "compos.go", 57 "turing.go", 58 "indirect.go", 59 "complit.go", 60 "for.go", 61 "struct0.go", 62 "intcvt.go", 63 "printbig.go", 64 "deferprint.go", 65 "escape.go", 66 "range.go", 67 "const4.go", 68 "float_lit.go", 69 "bigalg.go", 70 "decl.go", 71 "if.go", 72 "named.go", 73 "bigmap.go", 74 "func.go", 75 "reorder2.go", 76 "gc.go", 77 "simassign.go", 78 "iota.go", 79 "nilptr2.go", 80 "utf.go", 81 "method.go", 82 "char_lit.go", 83 "env.go", 84 "int_lit.go", 85 "string_lit.go", 86 "defer.go", 87 "typeswitch.go", 88 "stringrange.go", 89 "reorder.go", 90 "method3.go", 91 "literal.go", 92 "nul1.go", // doesn't actually assert anything (errorcheckoutput) 93 "zerodivide.go", 94 "convert.go", 95 "convT2X.go", 96 "switch.go", 97 "ddd.go", 98 "blank.go", // partly disabled 99 "closedchan.go", 100 "divide.go", 101 "rename.go", 102 "nil.go", 103 "recover1.go", 104 "recover2.go", 105 "recover3.go", 106 "typeswitch1.go", 107 "floatcmp.go", 108 "crlf.go", // doesn't actually assert anything (runoutput) 109 } 110 111 // These are files in go.tools/go/ssa/interp/testdata/. 112 var testdataTests = []string{ 113 "boundmeth.go", 114 "complit.go", 115 "convert.go", 116 "coverage.go", 117 "deepequal.go", 118 "defer.go", 119 "fieldprom.go", 120 "forvarlifetime_old.go", 121 "ifaceconv.go", 122 "ifaceprom.go", 123 "initorder.go", 124 "methprom.go", 125 "mrvchain.go", 126 "range.go", 127 "recover.go", 128 "reflect.go", 129 "slice2arrayptr.go", 130 "static.go", 131 "width32.go", 132 "rangevarlifetime_old.go", 133 "fixedbugs/issue52342.go", 134 "fixedbugs/issue55115.go", 135 "fixedbugs/issue52835.go", 136 "fixedbugs/issue55086.go", 137 "typeassert.go", 138 "zeros.go", 139 } 140 141 func init() { 142 // GOROOT/test used to assume that GOOS and GOARCH were explicitly set in the 143 // environment, so do that here for TestGorootTest. 144 os.Setenv("GOOS", runtime.GOOS) 145 os.Setenv("GOARCH", runtime.GOARCH) 146 } 147 148 func run(t *testing.T, input string, goroot string) { 149 // The recover2 test case is broken on Go 1.14+. See golang/go#34089. 150 // TODO(matloob): Fix this. 151 if filepath.Base(input) == "recover2.go" { 152 t.Skip("The recover2.go test is broken in go1.14+. See golang.org/issue/34089.") 153 } 154 155 t.Logf("Input: %s\n", input) 156 157 start := time.Now() 158 159 ctx := build.Default // copy 160 ctx.GOROOT = goroot 161 ctx.GOOS = runtime.GOOS 162 ctx.GOARCH = runtime.GOARCH 163 if filepath.Base(input) == "width32.go" && unsafe.Sizeof(int(0)) > 4 { 164 t.Skipf("skipping: width32.go checks behavior for a 32-bit int") 165 } 166 167 gover := "" 168 if p := testenv.Go1Point(); p > 0 { 169 gover = fmt.Sprintf("go1.%d", p) 170 } 171 172 conf := loader.Config{Build: &ctx, TypeChecker: types.Config{GoVersion: gover}} 173 if _, err := conf.FromArgs([]string{input}, true); err != nil { 174 t.Fatalf("FromArgs(%s) failed: %s", input, err) 175 } 176 177 conf.Import("runtime") 178 179 // Print a helpful hint if we don't make it to the end. 180 var hint string 181 defer func() { 182 if hint != "" { 183 fmt.Println("FAIL") 184 fmt.Println(hint) 185 } else { 186 fmt.Println("PASS") 187 } 188 189 interp.CapturedOutput = nil 190 }() 191 192 hint = fmt.Sprintf("To dump SSA representation, run:\n%% go build golang.org/x/tools/cmd/ssadump && ./ssadump -test -build=CFP %s\n", input) 193 194 iprog, err := conf.Load() 195 if err != nil { 196 t.Fatalf("conf.Load(%s) failed: %s", input, err) 197 } 198 199 bmode := ssa.InstantiateGenerics | ssa.SanityCheckFunctions 200 // bmode |= ssa.PrintFunctions // enable for debugging 201 prog := ssautil.CreateProgram(iprog, bmode) 202 prog.Build() 203 204 mainPkg := prog.Package(iprog.Created[0].Pkg) 205 if mainPkg == nil { 206 t.Fatalf("not a main package: %s", input) 207 } 208 209 interp.CapturedOutput = new(bytes.Buffer) 210 211 sizes := types.SizesFor("gc", ctx.GOARCH) 212 if sizes.Sizeof(types.Typ[types.Int]) < 4 { 213 panic("bogus SizesFor") 214 } 215 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) 216 var imode interp.Mode // default mode 217 // imode |= interp.DisableRecover // enable for debugging 218 // imode |= interp.EnableTracing // enable for debugging 219 exitCode := interp.Interpret(mainPkg, imode, sizes, input, []string{}) 220 if exitCode != 0 { 221 t.Fatalf("interpreting %s: exit code was %d", input, exitCode) 222 } 223 // $GOROOT/test tests use this convention: 224 if strings.Contains(interp.CapturedOutput.String(), "BUG") { 225 t.Fatalf("interpreting %s: exited zero but output contained 'BUG'", input) 226 } 227 228 hint = "" // call off the hounds 229 230 if false { 231 t.Log(input, time.Since(start)) // test profiling 232 } 233 } 234 235 // makeGoroot copies testdata/src into the "src" directory of a temporary 236 // location to mimic GOROOT/src, and adds a file "runtime/consts.go" containing 237 // declarations for GOOS and GOARCH that match the GOOS and GOARCH of this test. 238 // 239 // It returns the directory that should be used for GOROOT. 240 func makeGoroot(t *testing.T) string { 241 goroot := t.TempDir() 242 src := filepath.Join(goroot, "src") 243 244 err := filepath.Walk("testdata/src", func(path string, info os.FileInfo, err error) error { 245 if err != nil { 246 return err 247 } 248 249 rel, err := filepath.Rel("testdata/src", path) 250 if err != nil { 251 return err 252 } 253 targ := filepath.Join(src, rel) 254 255 if info.IsDir() { 256 return os.Mkdir(targ, info.Mode().Perm()|0700) 257 } 258 259 b, err := os.ReadFile(path) 260 if err != nil { 261 return err 262 } 263 return os.WriteFile(targ, b, info.Mode().Perm()) 264 }) 265 if err != nil { 266 t.Fatal(err) 267 } 268 269 constsGo := fmt.Sprintf(`package runtime 270 const GOOS = %q 271 const GOARCH = %q 272 `, runtime.GOOS, runtime.GOARCH) 273 err = os.WriteFile(filepath.Join(src, "runtime/consts.go"), []byte(constsGo), 0644) 274 if err != nil { 275 t.Fatal(err) 276 } 277 278 return goroot 279 } 280 281 // TestTestdataFiles runs the interpreter on testdata/*.go. 282 func TestTestdataFiles(t *testing.T) { 283 goroot := makeGoroot(t) 284 cwd, err := os.Getwd() 285 if err != nil { 286 log.Fatal(err) 287 } 288 for _, input := range testdataTests { 289 t.Run(input, func(t *testing.T) { 290 run(t, filepath.Join(cwd, "testdata", input), goroot) 291 }) 292 } 293 } 294 295 // TestGorootTest runs the interpreter on $GOROOT/test/*.go. 296 func TestGorootTest(t *testing.T) { 297 goroot := makeGoroot(t) 298 for _, input := range gorootTestTests { 299 t.Run(input, func(t *testing.T) { 300 run(t, filepath.Join(build.Default.GOROOT, "test", input), goroot) 301 }) 302 } 303 } 304 305 // TestTypeparamTest runs the interpreter on runnable examples 306 // in $GOROOT/test/typeparam/*.go. 307 308 func TestTypeparamTest(t *testing.T) { 309 goroot := makeGoroot(t) 310 311 // Skip known failures for the given reason. 312 // TODO(taking): Address these. 313 skip := map[string]string{ 314 "chans.go": "interp tests do not support runtime.SetFinalizer", 315 "issue23536.go": "unknown reason", 316 "issue48042.go": "interp tests do not handle reflect.Value.SetInt", 317 "issue47716.go": "interp tests do not handle unsafe.Sizeof", 318 "issue50419.go": "interp tests do not handle dispatch to String() correctly", 319 "issue51733.go": "interp does not handle unsafe casts", 320 "ordered.go": "math.NaN() comparisons not being handled correctly", 321 "orderedmap.go": "interp tests do not support runtime.SetFinalizer", 322 "stringer.go": "unknown reason", 323 "issue48317.go": "interp tests do not support encoding/json", 324 "issue48318.go": "interp tests do not support encoding/json", 325 "issue58513.go": "interp tests do not support runtime.Caller", 326 } 327 // Collect all of the .go files in dir that are runnable. 328 dir := filepath.Join(build.Default.GOROOT, "test", "typeparam") 329 list, err := os.ReadDir(dir) 330 if err != nil { 331 t.Fatal(err) 332 } 333 for _, entry := range list { 334 if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".go") { 335 continue // Consider standalone go files. 336 } 337 t.Run(entry.Name(), func(t *testing.T) { 338 input := filepath.Join(dir, entry.Name()) 339 src, err := os.ReadFile(input) 340 if err != nil { 341 t.Fatal(err) 342 } 343 344 // Only build test files that can be compiled, or compiled and run. 345 if !bytes.HasPrefix(src, []byte("// run")) || bytes.HasPrefix(src, []byte("// rundir")) { 346 t.Logf("Not a `// run` file: %s", entry.Name()) 347 return 348 } 349 350 if reason := skip[entry.Name()]; reason != "" { 351 t.Skipf("skipping: %s", reason) 352 } 353 354 run(t, input, goroot) 355 }) 356 } 357 }