Skip to content

Commit 1b86bdb

Browse files
author
Bryan C. Mills
committed
cmd/test2json: do not emit a final Action if the result is not known
If we are parsing a test output, and the test does not end in the usual PASS or FAIL line (say, because it panicked), then we need the exit status of the test binary in order to determine whether the test passed or failed. If we don't have that status available, we shouldn't guess arbitrarily — instead, we should omit the final "pass" or "fail" action entirely. (In practice, we nearly always DO have the final status, such as when running 'go test' or 'go tool test2json some.exe'.) Fixes #40132 Change-Id: Iae482577361a6033395fe4a05d746b980e18c3de Reviewed-on: https://go-review.googlesource.com/c/go/+/248624 Run-TryBot: Bryan C. Mills <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Jay Conrod <[email protected]>
1 parent f30044a commit 1b86bdb

File tree

6 files changed

+139
-23
lines changed

6 files changed

+139
-23
lines changed

src/cmd/go/internal/test/test.go

+6-2
Original file line numberDiff line numberDiff line change
@@ -1098,9 +1098,13 @@ func (c *runCache) builderRunTest(b *work.Builder, ctx context.Context, a *work.
10981098
}
10991099

11001100
var stdout io.Writer = os.Stdout
1101+
var err error
11011102
if testJSON {
11021103
json := test2json.NewConverter(lockedStdout{}, a.Package.ImportPath, test2json.Timestamp)
1103-
defer json.Close()
1104+
defer func() {
1105+
json.Exited(err)
1106+
json.Close()
1107+
}()
11041108
stdout = json
11051109
}
11061110

@@ -1204,7 +1208,7 @@ func (c *runCache) builderRunTest(b *work.Builder, ctx context.Context, a *work.
12041208
}
12051209

12061210
t0 := time.Now()
1207-
err := cmd.Start()
1211+
err = cmd.Start()
12081212

12091213
// This is a last-ditch deadline to detect and
12101214
// stop wedged test binaries, to keep the builders
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
[short] skip
2+
3+
go test -c -o mainpanic.exe ./mainpanic &
4+
go test -c -o mainexit0.exe ./mainexit0 &
5+
go test -c -o testpanic.exe ./testpanic &
6+
go test -c -o testbgpanic.exe ./testbgpanic &
7+
wait
8+
9+
# Test binaries that panic in TestMain should be marked as failing.
10+
11+
! go test -json ./mainpanic
12+
stdout '"Action":"fail"'
13+
! stdout '"Action":"pass"'
14+
15+
! go tool test2json ./mainpanic.exe
16+
stdout '"Action":"fail"'
17+
! stdout '"Action":"pass"'
18+
19+
# Test binaries that exit with status 0 should be marked as passing.
20+
21+
go test -json ./mainexit0
22+
stdout '"Action":"pass"'
23+
! stdout '"Action":"fail"'
24+
25+
go tool test2json ./mainexit0.exe
26+
stdout '"Action":"pass"'
27+
! stdout '"Action":"fail"'
28+
29+
# Test functions that panic should never be marked as passing
30+
# (https://golang.org/issue/40132).
31+
32+
! go test -json ./testpanic
33+
stdout '"Action":"fail"'
34+
! stdout '"Action":"pass"'
35+
36+
! go tool test2json ./testpanic.exe -test.v
37+
stdout '"Action":"fail"'
38+
! stdout '"Action":"pass"'
39+
40+
! go tool test2json ./testpanic.exe
41+
stdout '"Action":"fail"'
42+
! stdout '"Action":"pass"'
43+
44+
# Tests that panic in a background goroutine should be marked as failing.
45+
46+
! go test -json ./testbgpanic
47+
stdout '"Action":"fail"'
48+
! stdout '"Action":"pass"'
49+
50+
! go tool test2json ./testbgpanic.exe -test.v
51+
stdout '"Action":"fail"'
52+
! stdout '"Action":"pass"'
53+
54+
! go tool test2json ./testbgpanic.exe
55+
stdout '"Action":"fail"'
56+
! stdout '"Action":"pass"'
57+
58+
-- go.mod --
59+
module m
60+
go 1.14
61+
-- mainpanic/mainpanic_test.go --
62+
package mainpanic_test
63+
64+
import "testing"
65+
66+
func TestMain(m *testing.M) {
67+
panic("haha no")
68+
}
69+
-- mainexit0/mainexit0_test.go --
70+
package mainexit0_test
71+
72+
import (
73+
"fmt"
74+
"os"
75+
"testing"
76+
)
77+
78+
func TestMain(m *testing.M) {
79+
fmt.Println("nothing to do")
80+
os.Exit(0)
81+
}
82+
-- testpanic/testpanic_test.go --
83+
package testpanic_test
84+
85+
import "testing"
86+
87+
func TestPanic(*testing.T) {
88+
panic("haha no")
89+
}
90+
-- testbgpanic/testbgpanic_test.go --
91+
package testbgpanic_test
92+
93+
import "testing"
94+
95+
func TestPanicInBackground(*testing.T) {
96+
c := make(chan struct{})
97+
go func() {
98+
panic("haha no")
99+
close(c)
100+
}()
101+
<-c
102+
}

src/cmd/internal/test2json/test2json.go

+26-18
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,10 @@ type textBytes []byte
4545

4646
func (b textBytes) MarshalText() ([]byte, error) { return b, nil }
4747

48-
// A converter holds the state of a test-to-JSON conversion.
48+
// A Converter holds the state of a test-to-JSON conversion.
4949
// It implements io.WriteCloser; the caller writes test output in,
5050
// and the converter writes JSON output to w.
51-
type converter struct {
51+
type Converter struct {
5252
w io.Writer // JSON output stream
5353
pkg string // package to name in events
5454
mode Mode // mode bits
@@ -100,9 +100,9 @@ var (
100100
//
101101
// The pkg string, if present, specifies the import path to
102102
// report in the JSON stream.
103-
func NewConverter(w io.Writer, pkg string, mode Mode) io.WriteCloser {
104-
c := new(converter)
105-
*c = converter{
103+
func NewConverter(w io.Writer, pkg string, mode Mode) *Converter {
104+
c := new(Converter)
105+
*c = Converter{
106106
w: w,
107107
pkg: pkg,
108108
mode: mode,
@@ -122,11 +122,20 @@ func NewConverter(w io.Writer, pkg string, mode Mode) io.WriteCloser {
122122
}
123123

124124
// Write writes the test input to the converter.
125-
func (c *converter) Write(b []byte) (int, error) {
125+
func (c *Converter) Write(b []byte) (int, error) {
126126
c.input.write(b)
127127
return len(b), nil
128128
}
129129

130+
// Exited marks the test process as having exited with the given error.
131+
func (c *Converter) Exited(err error) {
132+
if err == nil {
133+
c.result = "pass"
134+
} else {
135+
c.result = "fail"
136+
}
137+
}
138+
130139
var (
131140
// printed by test on successful run.
132141
bigPass = []byte("PASS\n")
@@ -160,7 +169,7 @@ var (
160169
// handleInputLine handles a single whole test output line.
161170
// It must write the line to c.output but may choose to do so
162171
// before or after emitting other events.
163-
func (c *converter) handleInputLine(line []byte) {
172+
func (c *Converter) handleInputLine(line []byte) {
164173
// Final PASS or FAIL.
165174
if bytes.Equal(line, bigPass) || bytes.Equal(line, bigFail) || bytes.HasPrefix(line, bigFailErrorPrefix) {
166175
c.flushReport(0)
@@ -286,7 +295,7 @@ func (c *converter) handleInputLine(line []byte) {
286295
}
287296

288297
// flushReport flushes all pending PASS/FAIL reports at levels >= depth.
289-
func (c *converter) flushReport(depth int) {
298+
func (c *Converter) flushReport(depth int) {
290299
c.testName = ""
291300
for len(c.report) > depth {
292301
e := c.report[len(c.report)-1]
@@ -298,23 +307,22 @@ func (c *converter) flushReport(depth int) {
298307
// Close marks the end of the go test output.
299308
// It flushes any pending input and then output (only partial lines at this point)
300309
// and then emits the final overall package-level pass/fail event.
301-
func (c *converter) Close() error {
310+
func (c *Converter) Close() error {
302311
c.input.flush()
303312
c.output.flush()
304-
e := &event{Action: "pass"}
305313
if c.result != "" {
306-
e.Action = c.result
307-
}
308-
if c.mode&Timestamp != 0 {
309-
dt := time.Since(c.start).Round(1 * time.Millisecond).Seconds()
310-
e.Elapsed = &dt
314+
e := &event{Action: c.result}
315+
if c.mode&Timestamp != 0 {
316+
dt := time.Since(c.start).Round(1 * time.Millisecond).Seconds()
317+
e.Elapsed = &dt
318+
}
319+
c.writeEvent(e)
311320
}
312-
c.writeEvent(e)
313321
return nil
314322
}
315323

316324
// writeOutputEvent writes a single output event with the given bytes.
317-
func (c *converter) writeOutputEvent(out []byte) {
325+
func (c *Converter) writeOutputEvent(out []byte) {
318326
c.writeEvent(&event{
319327
Action: "output",
320328
Output: (*textBytes)(&out),
@@ -323,7 +331,7 @@ func (c *converter) writeOutputEvent(out []byte) {
323331

324332
// writeEvent writes a single event.
325333
// It adds the package, time (if requested), and test name (if needed).
326-
func (c *converter) writeEvent(e *event) {
334+
func (c *Converter) writeEvent(e *event) {
327335
e.Package = c.pkg
328336
if c.mode&Timestamp != 0 {
329337
t := time.Now()

src/cmd/internal/test2json/testdata/benchshort.json

-1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,3 @@
44
{"Action":"output","Output":"# but to avoid questions of timing, we just use a file with no \\n at all.\n"}
55
{"Action":"output","Output":"BenchmarkFoo \t"}
66
{"Action":"output","Output":"10000 early EOF"}
7-
{"Action":"pass"}
Original file line numberDiff line numberDiff line change
@@ -1 +0,0 @@
1-
{"Action":"pass"}

src/cmd/test2json/main.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -118,12 +118,16 @@ func main() {
118118
w := &countWriter{0, c}
119119
cmd.Stdout = w
120120
cmd.Stderr = w
121-
if err := cmd.Run(); err != nil {
121+
err := cmd.Run()
122+
if err != nil {
122123
if w.n > 0 {
123124
// Assume command printed why it failed.
124125
} else {
125126
fmt.Fprintf(c, "test2json: %v\n", err)
126127
}
128+
}
129+
c.Exited(err)
130+
if err != nil {
127131
c.Close()
128132
os.Exit(1)
129133
}

0 commit comments

Comments
 (0)