From cd908f1108c2dbb1b260e9f8953dbb3530a2c9ed Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Tue, 8 Oct 2013 14:35:39 -0400 Subject: [PATCH] go.tools/ssa/interp: capture stdout/err of target programs and check for "BUG". The $GOROOT/tests may print "BUG" on failure but do not necessarily exit zero, so we must capture their output too. Details: - make plan9 use unix's valueToBytes function (now in externals.go) - direct the target's syscall.Write and print/println built-ins to a new utility, write(). This may capture the output into a global variable. R=gri, r CC=golang-dev https://golang.org/cl/14550044 --- ssa/interp/external.go | 9 +++++++++ ssa/interp/external_plan9.go | 9 ++------- ssa/interp/external_unix.go | 11 +---------- ssa/interp/interp_test.go | 20 ++++++++++++++++---- ssa/interp/ops.go | 36 ++++++++++++++++++++++++++++++++---- 5 files changed, 60 insertions(+), 25 deletions(-) diff --git a/ssa/interp/external.go b/ssa/interp/external.go index 38af5355..2fe79a94 100644 --- a/ssa/interp/external.go +++ b/ssa/interp/external.go @@ -330,6 +330,15 @@ func ext۰syscall۰RawSyscall(fn *ssa.Function, args []value) value { return tuple{uintptr(0), uintptr(0), uintptr(syscall.ENOSYS)} } +func valueToBytes(v value) []byte { + in := v.([]value) + b := make([]byte, len(in)) + for i := range in { + b[i] = in[i].(byte) + } + return b +} + // The set of remaining native functions we need to implement (as needed): // crypto/aes/cipher_asm.go:10:func hasAsm() bool diff --git a/ssa/interp/external_plan9.go b/ssa/interp/external_plan9.go index 49940cd9..7c02360d 100644 --- a/ssa/interp/external_plan9.go +++ b/ssa/interp/external_plan9.go @@ -37,13 +37,8 @@ func ext۰syscall۰ReadDirent(fn *ssa.Function, args []value) value { func ext۰syscall۰Stat(fn *ssa.Function, args []value) value { panic("syscall.Stat not yet implemented") } - func ext۰syscall۰Write(fn *ssa.Function, args []value) value { - p := args[1].([]value) - b := make([]byte, 0, len(p)) - for i := range p { - b = append(b, p[i].(byte)) - } - n, err := syscall.Write(args[0].(int), b) + // func Write(fd int, p []byte) (n int, err error) + n, err := write(args[0].(int), valueToBytes(args[1])) return tuple{n, wrapError(err)} } diff --git a/ssa/interp/external_unix.go b/ssa/interp/external_unix.go index a227e5cb..fd5e3fb9 100644 --- a/ssa/interp/external_unix.go +++ b/ssa/interp/external_unix.go @@ -12,15 +12,6 @@ import ( "code.google.com/p/go.tools/ssa" ) -func valueToBytes(v value) []byte { - in := v.([]value) - b := make([]byte, len(in)) - for i := range in { - b[i] = in[i].(byte) - } - return b -} - func fillStat(st *syscall.Stat_t, stat structure) { stat[0] = st.Dev stat[1] = st.Ino @@ -132,6 +123,6 @@ func ext۰syscall۰Stat(fn *ssa.Function, args []value) value { func ext۰syscall۰Write(fn *ssa.Function, args []value) value { // func Write(fd int, p []byte) (n int, err error) - n, err := syscall.Write(args[0].(int), valueToBytes(args[1])) + n, err := write(args[0].(int), valueToBytes(args[1])) return tuple{n, wrapError(err)} } diff --git a/ssa/interp/interp_test.go b/ssa/interp/interp_test.go index e92d26ab..89411cb4 100644 --- a/ssa/interp/interp_test.go +++ b/ssa/interp/interp_test.go @@ -7,6 +7,7 @@ package interp_test import ( + "bytes" "fmt" "go/build" "os" @@ -64,7 +65,7 @@ var gorootTestTests = []string{ "simassign.go", "iota.go", "nilptr2.go", - "goprint.go", // doesn't actually assert anything + "goprint.go", // doesn't actually assert anything (cmpout) "utf.go", "method.go", "char_lit.go", @@ -77,13 +78,13 @@ var gorootTestTests = []string{ "reorder.go", "method3.go", "literal.go", - "nul1.go", + "nul1.go", // doesn't actually assert anything (errorcheckoutput) "zerodivide.go", "convert.go", "convT2X.go", "initialize.go", "ddd.go", - "blank.go", // partly disabled; TODO(adonovan): skip blank fields in struct{_} equivalence. + "blank.go", // partly disabled "map.go", "closedchan.go", "divide.go", @@ -93,7 +94,7 @@ var gorootTestTests = []string{ "recover.go", // partly disabled; TODO(adonovan): fix. "typeswitch1.go", "floatcmp.go", - "crlf.go", // doesn't actually assert anything + "crlf.go", // doesn't actually assert anything (runoutput) // Slow tests follow. "bom.go", // ~1.7s "gc1.go", // ~1.7s @@ -182,6 +183,8 @@ func run(t *testing.T, dir, input string) bool { } else { fmt.Println("PASS") } + + interp.CapturedOutput = nil }() hint = fmt.Sprintf("To dump SSA representation, run:\n%% go build code.google.com/p/go.tools/cmd/ssadump && ./ssadump -build=CFP %s\n", input) @@ -197,12 +200,21 @@ func run(t *testing.T, dir, input string) bool { mainPkg := prog.Package(mainInfo.Pkg) mainPkg.CreateTestMainFunction() // (no-op if main already exists) + var out bytes.Buffer + interp.CapturedOutput = &out + hint = fmt.Sprintf("To trace execution, run:\n%% go build code.google.com/p/go.tools/cmd/ssadump && ./ssadump -build=C -run --interp=T %s\n", input) if exitCode := interp.Interpret(mainPkg, 0, inputs[0], []string{}); exitCode != 0 { t.Errorf("interp.Interpret(%s) exited with code %d, want zero", inputs, exitCode) return false } + // $GOROOT/tests are considered a failure if they print "BUG". + if strings.Contains(out.String(), "BUG") { + t.Errorf("interp.Interpret(%s) exited zero but output contained 'BUG'", inputs) + return false + } + hint = "" // call off the hounds if false { diff --git a/ssa/interp/ops.go b/ssa/interp/ops.go index 055e64df..611990a3 100644 --- a/ssa/interp/ops.go +++ b/ssa/interp/ops.go @@ -5,10 +5,13 @@ package interp import ( + "bytes" "fmt" "go/token" "runtime" "strings" + "sync" + "syscall" "unsafe" "code.google.com/p/go.tools/go/exact" @@ -906,6 +909,29 @@ func typeAssert(i *interpreter, instr *ssa.TypeAssert, itf iface) value { return v } +// If CapturedOutput is non-nil, all writes by the interpreted program +// to file descriptors 1 and 2 will also be written to CapturedOutput. +// +// (The $GOROOT/test system requires that the test be considered a +// failure if "BUG" appears in the combined stdout/stderr output, even +// if it exits zero. This is a global variable shared by all +// interpreters in the same process.) +// +var CapturedOutput *bytes.Buffer +var capturedOutputMu sync.Mutex + +// write writes bytes b to the target program's file descriptor fd. +// The print/println built-ins and the write() system call funnel +// through here so they can be captured by the test driver. +func write(fd int, b []byte) (int, error) { + if CapturedOutput != nil && (fd == 1 || fd == 2) { + capturedOutputMu.Lock() + CapturedOutput.Write(b) // ignore errors + capturedOutputMu.Unlock() + } + return syscall.Write(fd, b) +} + // callBuiltin interprets a call to builtin fn with arguments args, // returning its result. func callBuiltin(caller *frame, callpos token.Pos, fn *ssa.Builtin, args []value) value { @@ -948,18 +974,20 @@ func callBuiltin(caller *frame, callpos token.Pos, fn *ssa.Builtin, args []value case "print", "println": // print(anytype, ...interface{}) ln := fn.Name() == "println" - fmt.Print(toString(args[0])) + var buf bytes.Buffer + buf.WriteString(toString(args[0])) if len(args) == 2 { for _, arg := range args[1].([]value) { if ln { - fmt.Print(" ") + buf.WriteRune(' ') } - fmt.Print(toString(arg)) + buf.WriteString(toString(arg)) } } if ln { - fmt.Println() + buf.WriteRune('\n') } + write(1, buf.Bytes()) return nil case "len":