230 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			230 lines
		
	
	
		
			5.1 KiB
		
	
	
	
		
			Go
		
	
	
	
// Copyright 2013 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 interp_test
 | 
						|
 | 
						|
// This test runs the SSA interpreter over sample Go programs.
 | 
						|
// Because the interpreter requires intrinsics for assembly
 | 
						|
// functions and many low-level runtime routines, it is inherently
 | 
						|
// not robust to evolutionary change in the standard library.
 | 
						|
// Therefore the test cases are restricted to programs that
 | 
						|
// use a fake standard library in testdata/src containing a tiny
 | 
						|
// subset of simple functions useful for writing assertions.
 | 
						|
//
 | 
						|
// We no longer attempt to interpret any real standard packages such as
 | 
						|
// fmt or testing, as it proved too fragile.
 | 
						|
 | 
						|
import (
 | 
						|
	"bytes"
 | 
						|
	"fmt"
 | 
						|
	"go/build"
 | 
						|
	"go/types"
 | 
						|
	"log"
 | 
						|
	"os"
 | 
						|
	"path/filepath"
 | 
						|
	"strings"
 | 
						|
	"testing"
 | 
						|
	"time"
 | 
						|
 | 
						|
	"golang.org/x/tools/go/loader"
 | 
						|
	"golang.org/x/tools/go/ssa"
 | 
						|
	"golang.org/x/tools/go/ssa/interp"
 | 
						|
	"golang.org/x/tools/go/ssa/ssautil"
 | 
						|
)
 | 
						|
 | 
						|
// Each line contains a space-separated list of $GOROOT/test/
 | 
						|
// filenames comprising the main package of a program.
 | 
						|
// They are ordered quickest-first, roughly.
 | 
						|
//
 | 
						|
// If a test in this list fails spuriously, remove it.
 | 
						|
var gorootTestTests = []string{
 | 
						|
	"235.go",
 | 
						|
	"alias1.go",
 | 
						|
	"func5.go",
 | 
						|
	"func6.go",
 | 
						|
	"func7.go",
 | 
						|
	"func8.go",
 | 
						|
	"helloworld.go",
 | 
						|
	"varinit.go",
 | 
						|
	"escape3.go",
 | 
						|
	"initcomma.go",
 | 
						|
	"cmp.go",
 | 
						|
	"compos.go",
 | 
						|
	"turing.go",
 | 
						|
	"indirect.go",
 | 
						|
	"complit.go",
 | 
						|
	"for.go",
 | 
						|
	"struct0.go",
 | 
						|
	"intcvt.go",
 | 
						|
	"printbig.go",
 | 
						|
	"deferprint.go",
 | 
						|
	"escape.go",
 | 
						|
	"range.go",
 | 
						|
	"const4.go",
 | 
						|
	"float_lit.go",
 | 
						|
	"bigalg.go",
 | 
						|
	"decl.go",
 | 
						|
	"if.go",
 | 
						|
	"named.go",
 | 
						|
	"bigmap.go",
 | 
						|
	"func.go",
 | 
						|
	"reorder2.go",
 | 
						|
	"gc.go",
 | 
						|
	"simassign.go",
 | 
						|
	"iota.go",
 | 
						|
	"nilptr2.go",
 | 
						|
	"utf.go",
 | 
						|
	"method.go",
 | 
						|
	"char_lit.go",
 | 
						|
	"env.go",
 | 
						|
	"int_lit.go",
 | 
						|
	"string_lit.go",
 | 
						|
	"defer.go",
 | 
						|
	"typeswitch.go",
 | 
						|
	"stringrange.go",
 | 
						|
	"reorder.go",
 | 
						|
	"method3.go",
 | 
						|
	"literal.go",
 | 
						|
	"nul1.go", // doesn't actually assert anything (errorcheckoutput)
 | 
						|
	"zerodivide.go",
 | 
						|
	"convert.go",
 | 
						|
	"convT2X.go",
 | 
						|
	"switch.go",
 | 
						|
	"ddd.go",
 | 
						|
	"blank.go", // partly disabled
 | 
						|
	"closedchan.go",
 | 
						|
	"divide.go",
 | 
						|
	"rename.go",
 | 
						|
	"nil.go",
 | 
						|
	"recover1.go",
 | 
						|
	"recover2.go",
 | 
						|
	"recover3.go",
 | 
						|
	"typeswitch1.go",
 | 
						|
	"floatcmp.go",
 | 
						|
	"crlf.go", // doesn't actually assert anything (runoutput)
 | 
						|
}
 | 
						|
 | 
						|
// These are files in go.tools/go/ssa/interp/testdata/.
 | 
						|
var testdataTests = []string{
 | 
						|
	"boundmeth.go",
 | 
						|
	"complit.go",
 | 
						|
	"coverage.go",
 | 
						|
	"defer.go",
 | 
						|
	"fieldprom.go",
 | 
						|
	"ifaceconv.go",
 | 
						|
	"ifaceprom.go",
 | 
						|
	"initorder.go",
 | 
						|
	"methprom.go",
 | 
						|
	"mrvchain.go",
 | 
						|
	"range.go",
 | 
						|
	"recover.go",
 | 
						|
	"reflect.go",
 | 
						|
	"static.go",
 | 
						|
}
 | 
						|
 | 
						|
func run(t *testing.T, input string) bool {
 | 
						|
	t.Logf("Input: %s\n", input)
 | 
						|
 | 
						|
	start := time.Now()
 | 
						|
 | 
						|
	ctx := build.Default    // copy
 | 
						|
	ctx.GOROOT = "testdata" // fake goroot
 | 
						|
	ctx.GOOS = "linux"
 | 
						|
	ctx.GOARCH = "amd64"
 | 
						|
 | 
						|
	conf := loader.Config{Build: &ctx}
 | 
						|
	if _, err := conf.FromArgs([]string{input}, true); err != nil {
 | 
						|
		t.Errorf("FromArgs(%s) failed: %s", input, err)
 | 
						|
		return false
 | 
						|
	}
 | 
						|
 | 
						|
	conf.Import("runtime")
 | 
						|
 | 
						|
	// Print a helpful hint if we don't make it to the end.
 | 
						|
	var hint string
 | 
						|
	defer func() {
 | 
						|
		if hint != "" {
 | 
						|
			fmt.Println("FAIL")
 | 
						|
			fmt.Println(hint)
 | 
						|
		} else {
 | 
						|
			fmt.Println("PASS")
 | 
						|
		}
 | 
						|
 | 
						|
		interp.CapturedOutput = nil
 | 
						|
	}()
 | 
						|
 | 
						|
	hint = fmt.Sprintf("To dump SSA representation, run:\n%% go build golang.org/x/tools/cmd/ssadump && ./ssadump -test -build=CFP %s\n", input)
 | 
						|
 | 
						|
	iprog, err := conf.Load()
 | 
						|
	if err != nil {
 | 
						|
		t.Errorf("conf.Load(%s) failed: %s", input, err)
 | 
						|
		return false
 | 
						|
	}
 | 
						|
 | 
						|
	prog := ssautil.CreateProgram(iprog, ssa.SanityCheckFunctions)
 | 
						|
	prog.Build()
 | 
						|
 | 
						|
	mainPkg := prog.Package(iprog.Created[0].Pkg)
 | 
						|
	if mainPkg == nil {
 | 
						|
		t.Fatalf("not a main package: %s", input)
 | 
						|
	}
 | 
						|
 | 
						|
	interp.CapturedOutput = new(bytes.Buffer)
 | 
						|
 | 
						|
	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)
 | 
						|
	exitCode := interp.Interpret(mainPkg, 0, &types.StdSizes{WordSize: 8, MaxAlign: 8}, input, []string{})
 | 
						|
	if exitCode != 0 {
 | 
						|
		t.Fatalf("interpreting %s: exit code was %d", input, exitCode)
 | 
						|
	}
 | 
						|
	// $GOROOT/test tests use this convention:
 | 
						|
	if strings.Contains(interp.CapturedOutput.String(), "BUG") {
 | 
						|
		t.Fatalf("interpreting %s: exited zero but output contained 'BUG'", input)
 | 
						|
	}
 | 
						|
 | 
						|
	hint = "" // call off the hounds
 | 
						|
 | 
						|
	if false {
 | 
						|
		t.Log(input, time.Since(start)) // test profiling
 | 
						|
	}
 | 
						|
 | 
						|
	return true
 | 
						|
}
 | 
						|
 | 
						|
func printFailures(failures []string) {
 | 
						|
	if failures != nil {
 | 
						|
		fmt.Println("The following tests failed:")
 | 
						|
		for _, f := range failures {
 | 
						|
			fmt.Printf("\t%s\n", f)
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// TestTestdataFiles runs the interpreter on testdata/*.go.
 | 
						|
func TestTestdataFiles(t *testing.T) {
 | 
						|
	cwd, err := os.Getwd()
 | 
						|
	if err != nil {
 | 
						|
		log.Fatal(err)
 | 
						|
	}
 | 
						|
 | 
						|
	var failures []string
 | 
						|
	for _, input := range testdataTests {
 | 
						|
		if !run(t, filepath.Join(cwd, "testdata", input)) {
 | 
						|
			failures = append(failures, input)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	printFailures(failures)
 | 
						|
}
 | 
						|
 | 
						|
// TestGorootTest runs the interpreter on $GOROOT/test/*.go.
 | 
						|
func TestGorootTest(t *testing.T) {
 | 
						|
	var failures []string
 | 
						|
 | 
						|
	for _, input := range gorootTestTests {
 | 
						|
		if !run(t, filepath.Join(build.Default.GOROOT, "test", input)) {
 | 
						|
			failures = append(failures, input)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	printFailures(failures)
 | 
						|
}
 |