From 9c3d9dadf6112a1a4746884e98c0f75cd7445d57 Mon Sep 17 00:00:00 2001 From: Jordan Liggitt Date: Fri, 21 Dec 2018 13:28:44 -0500 Subject: [PATCH] Include test output and stdout in test failures --- go-junit-report_test.go | 213 ++++++++++++++++++++++++++++++++++++++++ parser/parser.go | 16 +++ testdata/30-report.xml | 43 ++++++++ testdata/30-stdout.txt | 100 +++++++++++++++++++ 4 files changed, 372 insertions(+) create mode 100644 testdata/30-report.xml create mode 100644 testdata/30-stdout.txt diff --git a/go-junit-report_test.go b/go-junit-report_test.go index cdc0281..7266286 100644 --- a/go-junit-report_test.go +++ b/go-junit-report_test.go @@ -1212,6 +1212,219 @@ var testCases = []TestCase{ }, }, }, + { + // generated by running go test on https://gist.github.com/liggitt/09a021ccec988b19917e0c2d60a18ee9 + name: "30-stdout.txt", + reportName: "30-report.xml", + report: &parser.Report{ + Packages: []parser.Package{ + { + Name: "package/name1", + Duration: 4567 * time.Millisecond, + Time: 4567, + Tests: []*parser.Test{ + { + Name: "TestFailWithStdoutAndTestOutput", + Duration: 100 * time.Millisecond, + Time: 100, + Result: parser.FAIL, + Output: []string{ + `multi`, + `line`, + `stdout`, + `single-line stdout`, + `example_test.go:13: single-line error`, + `example_test.go:14: multi`, + ` line`, + ` error`, + }, + }, + { + Name: "TestFailWithStdoutAndNoTestOutput", + Duration: 150 * time.Millisecond, + Time: 150, + Result: parser.FAIL, + Output: []string{ + `multi`, + `line`, + `stdout`, + `single-line stdout`, + }, + }, + { + Name: "TestFailWithTestOutput", + Duration: 200 * time.Millisecond, + Time: 200, + Result: parser.FAIL, + Output: []string{ + `example_test.go:26: single-line error`, + `example_test.go:27: multi`, + ` line`, + ` error`, + }, + }, + { + Name: "TestFailWithNoTestOutput", + Duration: 250 * time.Millisecond, + Time: 250, + Result: parser.FAIL, + Output: []string{}, + }, + + { + Name: "TestPassWithStdoutAndTestOutput", + Duration: 300 * time.Millisecond, + Time: 300, + Result: parser.PASS, + Output: []string{ + `multi`, + `line`, + `stdout`, + `single-line stdout`, + `example_test.go:39: single-line info`, + `example_test.go:40: multi`, + ` line`, + ` info`, + }, + }, + { + Name: "TestPassWithStdoutAndNoTestOutput", + Duration: 350 * time.Millisecond, + Time: 350, + Result: parser.PASS, + Output: []string{ + `multi`, + `line`, + `stdout`, + `single-line stdout`, + }, + }, + { + Name: "TestPassWithTestOutput", + Duration: 400 * time.Millisecond, + Time: 400, + Result: parser.PASS, + Output: []string{ + `example_test.go:51: single-line info`, + `example_test.go:52: multi`, + ` line`, + ` info`, + }, + }, + { + Name: "TestPassWithNoTestOutput", + Duration: 500 * time.Millisecond, + Time: 500, + Result: parser.PASS, + Output: []string{}, + }, + + { + Name: "TestSubtests", + Duration: 2270 * time.Millisecond, + Time: 2270, + Result: parser.FAIL, + }, + { + Name: "TestSubtests/TestFailWithStdoutAndTestOutput", + Duration: 100 * time.Millisecond, + Time: 100, + Result: parser.FAIL, + Output: []string{ + `1 multi`, + `line`, + `stdout`, + `1 single-line stdout`, + `example_test.go:65: 1 single-line error`, + `example_test.go:66: 1 multi`, + ` line`, + ` error`, + }, + }, + { + Name: "TestSubtests/TestFailWithStdoutAndNoTestOutput", + Duration: 150 * time.Millisecond, + Time: 150, + Result: parser.FAIL, + Output: []string{ + `2 multi`, + `line`, + `stdout`, + `2 single-line stdout`, + }, + }, + { + Name: "TestSubtests/TestFailWithTestOutput", + Duration: 200 * time.Millisecond, + Time: 200, + Result: parser.FAIL, + Output: []string{ + `example_test.go:78: 3 single-line error`, + `example_test.go:79: 3 multi`, + ` line`, + ` error`, + }, + }, + { + Name: "TestSubtests/TestFailWithNoTestOutput", + Duration: 250 * time.Millisecond, + Time: 250, + Result: parser.FAIL, + Output: []string{}, + }, + + { + Name: "TestSubtests/TestPassWithStdoutAndTestOutput", + Duration: 300 * time.Millisecond, + Time: 300, + Result: parser.PASS, + Output: []string{ + `4 multi`, + `line`, + `stdout`, + `4 single-line stdout`, + `example_test.go:91: 4 single-line info`, + `example_test.go:92: 4 multi`, + ` line`, + ` info`, + }, + }, + { + Name: "TestSubtests/TestPassWithStdoutAndNoTestOutput", + Duration: 350 * time.Millisecond, + Time: 350, + Result: parser.PASS, + Output: []string{ + `5 multi`, + `line`, + `stdout`, + `5 single-line stdout`, + }, + }, + { + Name: "TestSubtests/TestPassWithTestOutput", + Duration: 400 * time.Millisecond, + Time: 400, + Result: parser.PASS, + Output: []string{ + `example_test.go:103: 6 single-line info`, + `example_test.go:104: 6 multi`, + ` line`, + ` info`, + }, + }, + { + Name: "TestSubtests/TestPassWithNoTestOutput", + Duration: 500 * time.Millisecond, + Time: 500, + Result: parser.PASS, + Output: []string{}, + }, + }, + }, + }, + }, + }, } func TestParser(t *testing.T) { diff --git a/parser/parser.go b/parser/parser.go index b095b7c..70ff205 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -43,6 +43,8 @@ type Test struct { Result Result Output []string + SubtestIndent string + // Time is deprecated, use Duration instead. Time int // in milliseconds } @@ -59,6 +61,7 @@ type Benchmark struct { var ( regexStatus = regexp.MustCompile(`--- (PASS|FAIL|SKIP): (.+) \((\d+\.\d+)(?: seconds|s)\)`) + regexIndent = regexp.MustCompile(`^([ \t]+)---`) regexCoverage = regexp.MustCompile(`^coverage:\s+(\d+\.\d+)%\s+of\s+statements(?:\sin\s.+)?$`) regexResult = regexp.MustCompile(`^(ok|FAIL)\s+([^ ]+)\s+(?:(\d+\.\d+)s|\(cached\)|(\[\w+ failed]))(?:\s+coverage:\s+(\d+\.\d+)%\sof\sstatements(?:\sin\s.+)?)?$`) // regexBenchmark captures 3-5 groups: benchmark name, number of times ran, ns/op (with or without decimal), B/op (optional), and allocs/op (optional). @@ -195,6 +198,11 @@ func Parse(r io.Reader, pkgName string) (*Report, error) { } else { test.Result = FAIL } + + if matches := regexIndent.FindStringSubmatch(line); len(matches) == 2 { + test.SubtestIndent = matches[1] + } + test.Output = buffers[cur] test.Name = matches[2] @@ -225,6 +233,14 @@ func Parse(r io.Reader, pkgName string) (*Report, error) { } else if !seenSummary { // buffer anything else that we didn't recognize buffers[cur] = append(buffers[cur], line) + + // if we have a current test, also append to its output + test := findTest(tests, cur) + if test != nil { + if strings.HasPrefix(line, test.SubtestIndent+" ") { + test.Output = append(test.Output, strings.TrimPrefix(line, test.SubtestIndent+" ")) + } + } } } diff --git a/testdata/30-report.xml b/testdata/30-report.xml new file mode 100644 index 0000000..a7964bd --- /dev/null +++ b/testdata/30-report.xml @@ -0,0 +1,43 @@ + + + + + + + + multi line stdout single-line stdout example_test.go:13: single-line error example_test.go:14: multi line error + + + multi line stdout single-line stdout + + + example_test.go:26: single-line error example_test.go:27: multi line error + + + + + + + + + + + + + 1 multi line stdout 1 single-line stdout example_test.go:65: 1 single-line error example_test.go:66: 1 multi line error + + + 2 multi line stdout 2 single-line stdout + + + example_test.go:78: 3 single-line error example_test.go:79: 3 multi line error + + + + + + + + + + diff --git a/testdata/30-stdout.txt b/testdata/30-stdout.txt new file mode 100644 index 0000000..8b494b5 --- /dev/null +++ b/testdata/30-stdout.txt @@ -0,0 +1,100 @@ +=== RUN TestFailWithStdoutAndTestOutput +multi +line +stdout +single-line stdout +--- FAIL: TestFailWithStdoutAndTestOutput (0.10s) + example_test.go:13: single-line error + example_test.go:14: multi + line + error +=== RUN TestFailWithStdoutAndNoTestOutput +multi +line +stdout +single-line stdout +--- FAIL: TestFailWithStdoutAndNoTestOutput (0.15s) +=== RUN TestFailWithTestOutput +--- FAIL: TestFailWithTestOutput (0.20s) + example_test.go:26: single-line error + example_test.go:27: multi + line + error +=== RUN TestFailWithNoTestOutput +--- FAIL: TestFailWithNoTestOutput (0.25s) +=== RUN TestPassWithStdoutAndTestOutput +multi +line +stdout +single-line stdout +--- PASS: TestPassWithStdoutAndTestOutput (0.30s) + example_test.go:39: single-line info + example_test.go:40: multi + line + info +=== RUN TestPassWithStdoutAndNoTestOutput +multi +line +stdout +single-line stdout +--- PASS: TestPassWithStdoutAndNoTestOutput (0.35s) +=== RUN TestPassWithTestOutput +--- PASS: TestPassWithTestOutput (0.40s) + example_test.go:51: single-line info + example_test.go:52: multi + line + info +=== RUN TestPassWithNoTestOutput +--- PASS: TestPassWithNoTestOutput (0.50s) +=== RUN TestSubtests +=== RUN TestSubtests/TestFailWithStdoutAndTestOutput +1 multi +line +stdout +1 single-line stdout +=== RUN TestSubtests/TestFailWithStdoutAndNoTestOutput +2 multi +line +stdout +2 single-line stdout +=== RUN TestSubtests/TestFailWithTestOutput +=== RUN TestSubtests/TestFailWithNoTestOutput +=== RUN TestSubtests/TestPassWithStdoutAndTestOutput +4 multi +line +stdout +4 single-line stdout +=== RUN TestSubtests/TestPassWithStdoutAndNoTestOutput +5 multi +line +stdout +5 single-line stdout +=== RUN TestSubtests/TestPassWithTestOutput +=== RUN TestSubtests/TestPassWithNoTestOutput +--- FAIL: TestSubtests (2.27s) + --- FAIL: TestSubtests/TestFailWithStdoutAndTestOutput (0.10s) + example_test.go:65: 1 single-line error + example_test.go:66: 1 multi + line + error + --- FAIL: TestSubtests/TestFailWithStdoutAndNoTestOutput (0.15s) + --- FAIL: TestSubtests/TestFailWithTestOutput (0.20s) + example_test.go:78: 3 single-line error + example_test.go:79: 3 multi + line + error + --- FAIL: TestSubtests/TestFailWithNoTestOutput (0.25s) + --- PASS: TestSubtests/TestPassWithStdoutAndTestOutput (0.30s) + example_test.go:91: 4 single-line info + example_test.go:92: 4 multi + line + info + --- PASS: TestSubtests/TestPassWithStdoutAndNoTestOutput (0.35s) + --- PASS: TestSubtests/TestPassWithTestOutput (0.40s) + example_test.go:103: 6 single-line info + example_test.go:104: 6 multi + line + info + --- PASS: TestSubtests/TestPassWithNoTestOutput (0.50s) +FAIL +FAIL package/name1 4.567s