diff --git a/README.md b/README.md index 0ad120a..95d3614 100644 --- a/README.md +++ b/README.md @@ -37,9 +37,13 @@ go test -v 2>&1 | go-junit-report > report.xml Note that it also can parse benchmark output with `-bench` flag: ```bash - go test -bench . -benchmem -count 100 +go test -v -bench . 2>&1 | ./go-junit-report > report.xml ``` -will return the average mean benchmark time as the test case time. + +or using the optional -count parameter: +```bash +go test -v -bench . -count 5 2>&1 | ./go-junit-report > report.xml +``` [travis-badge]: https://travis-ci.org/jstemmer/go-junit-report.svg [travis-link]: https://travis-ci.org/jstemmer/go-junit-report diff --git a/formatter/formatter.go b/formatter/formatter.go index 51b4593..c55c67d 100644 --- a/formatter/formatter.go +++ b/formatter/formatter.go @@ -6,7 +6,6 @@ import ( "fmt" "io" "runtime" - "strconv" "strings" "time" @@ -39,9 +38,6 @@ type JUnitTestCase struct { Time string `xml:"time,attr"` SkipMessage *JUnitSkipMessage `xml:"skipped,omitempty"` Failure *JUnitFailure `xml:"failure,omitempty"` - // for benchmarks - Bytes string `xml:"bytes,attr,omitempty"` - Allocs string `xml:"allocs,attr,omitempty"` } // JUnitSkipMessage contains the reason why a testcase was skipped. @@ -69,17 +65,8 @@ func JUnitReportXML(report *parser.Report, noXMLHeader bool, goVersion string, w // convert Report to JUnit test suites for _, pkg := range report.Packages { - var tests int - if len(pkg.Tests) >= 1 && len(pkg.Benchmarks) >= 1 { - tests = len(pkg.Tests) + len(pkg.Benchmarks) - } else if len(pkg.Benchmarks) >= 1 { - tests = len(pkg.Benchmarks) - } else { - tests = len(pkg.Tests) - } - ts := JUnitTestSuite{ - Tests: tests, + Tests: len(pkg.Tests) + len(pkg.Benchmarks), Failures: 0, Time: formatTime(pkg.Duration), Name: pkg.Name, @@ -128,7 +115,7 @@ func JUnitReportXML(report *parser.Report, noXMLHeader bool, goVersion string, w } // individual benchmarks - for _, benchmark := range pkg.Benchmarks { + for _, benchmark := range mergeBenchmarks(pkg.Benchmarks) { benchmarkCase := JUnitTestCase{ Classname: classname, Name: benchmark.Name, @@ -136,13 +123,6 @@ func JUnitReportXML(report *parser.Report, noXMLHeader bool, goVersion string, w Failure: nil, } - if benchmark.Bytes != 0 { - benchmarkCase.Bytes = strconv.Itoa(benchmark.Bytes) - } - if benchmark.Allocs != 0 { - benchmarkCase.Allocs = strconv.Itoa(benchmark.Allocs) - } - ts.TestCases = append(ts.TestCases, benchmarkCase) } @@ -168,6 +148,44 @@ func JUnitReportXML(report *parser.Report, noXMLHeader bool, goVersion string, w return nil } +func mergeBenchmarks(benchmarks []*parser.Benchmark) []*parser.Benchmark { + // calculate the cumulative moving average CMA for each benchmark. + // CMA(n + 1) = val(n+1) + n*CMA/(n+1) + alloc := "Allocs" + bytes := "Bytes" + dur := "Duration" + n := 1 + var merged []*parser.Benchmark + averages := make(map[string] /*bench name*/ map[string] /* type */ int) + for _, b := range benchmarks { + if avg, found := averages[b.Name]; found { + // calculate CMAs + averages[b.Name][alloc] = (b.Allocs + n*averages[b.Name][alloc]) / (n + 1) + averages[b.Name][bytes] = (b.Bytes + n*avg[bytes]) / (n + 1) + averages[b.Name][dur] = (int(b.Duration.Nanoseconds()) + n*avg[dur]) / (n + 1) + + n++ + continue + } + n = 1 // reset duplicate counter + merged = append(merged, &parser.Benchmark{Name: b.Name}) + averages[b.Name] = make(map[string]int) + averages[b.Name][alloc] = b.Allocs + averages[b.Name][bytes] = b.Bytes + averages[b.Name][dur] = int(b.Duration.Nanoseconds()) + } + + // fill out benchmarks + for i := range merged { + avgVals := averages[merged[i].Name] + merged[i].Allocs = avgVals[alloc] + merged[i].Bytes = avgVals[bytes] + merged[i].Duration = time.Duration(avgVals[dur]) + } + + return merged +} + func formatTime(d time.Duration) string { return fmt.Sprintf("%.3f", d.Seconds()) } diff --git a/go-junit-report_test.go b/go-junit-report_test.go index 5b32ca9..e44a0ba 100644 --- a/go-junit-report_test.go +++ b/go-junit-report_test.go @@ -957,14 +957,12 @@ var testCases = []TestCase{ Duration: 52568 * time.Nanosecond, Bytes: 24879, Allocs: 494, - Output: []string{}, }, { Name: "BenchmarkIpsHistoryLookup", Duration: 15208 * time.Nanosecond, Bytes: 7369, Allocs: 143, - Output: []string{}, }, }, }, @@ -1016,14 +1014,12 @@ var testCases = []TestCase{ Duration: 2611 * time.Nanosecond, Bytes: 1110, Allocs: 16, - Output: []string{}, }, { Name: "BenchmarkNext", Duration: 100 * time.Nanosecond, Bytes: 100, Allocs: 1, - Output: []string{}, }, }, }, @@ -1042,19 +1038,63 @@ var testCases = []TestCase{ Benchmarks: []*parser.Benchmark{ { Name: "BenchmarkNew", - Duration: 352 * time.Nanosecond, + Duration: 350 * time.Nanosecond, Bytes: 80, Allocs: 3, - Count: 5, - Output: []string{}, + }, + { + Name: "BenchmarkNew", + Duration: 357 * time.Nanosecond, + Bytes: 80, + Allocs: 3, + }, + { + Name: "BenchmarkNew", + Duration: 354 * time.Nanosecond, + Bytes: 80, + Allocs: 3, + }, + { + Name: "BenchmarkNew", + Duration: 358 * time.Nanosecond, + Bytes: 80, + Allocs: 3, + }, + { + Name: "BenchmarkNew", + Duration: 345 * time.Nanosecond, + Bytes: 80, + Allocs: 3, + }, + { + Name: "BenchmarkFew", + Duration: 100 * time.Nanosecond, + Bytes: 20, + Allocs: 1, + }, + { + Name: "BenchmarkFew", + Duration: 105 * time.Nanosecond, + Bytes: 20, + Allocs: 1, + }, + { + Name: "BenchmarkFew", + Duration: 102 * time.Nanosecond, + Bytes: 20, + Allocs: 1, + }, + { + Name: "BenchmarkFew", + Duration: 102 * time.Nanosecond, + Bytes: 20, + Allocs: 1, }, { Name: "BenchmarkFew", Duration: 102 * time.Nanosecond, Bytes: 20, Allocs: 1, - Count: 5, - Output: []string{}, }, }, }, @@ -1076,53 +1116,92 @@ var testCases = []TestCase{ Duration: 0, Time: 0, Result: parser.PASS, - Output: []string{}, }, { Name: "TestRepeat", Duration: 0, Time: 0, Result: parser.PASS, - Output: []string{}, }, { Name: "TestRepeat", Duration: 0, Time: 0, Result: parser.PASS, - Output: []string{}, }, { Name: "TestRepeat", Duration: 0, Time: 0, Result: parser.PASS, - Output: []string{}, }, { Name: "TestRepeat", Duration: 0, Time: 0, Result: parser.PASS, - Output: []string{}, }, }, Benchmarks: []*parser.Benchmark{ { Name: "BenchmarkNew", - Duration: 352 * time.Nanosecond, + Duration: 350 * time.Nanosecond, Bytes: 80, Allocs: 3, - Count: 5, - Output: []string{}, + }, + { + Name: "BenchmarkNew", + Duration: 357 * time.Nanosecond, + Bytes: 80, + Allocs: 3, + }, + { + Name: "BenchmarkNew", + Duration: 354 * time.Nanosecond, + Bytes: 80, + Allocs: 3, + }, + { + Name: "BenchmarkNew", + Duration: 358 * time.Nanosecond, + Bytes: 80, + Allocs: 3, + }, + { + Name: "BenchmarkNew", + Duration: 345 * time.Nanosecond, + Bytes: 80, + Allocs: 3, + }, + { + Name: "BenchmarkFew", + Duration: 100 * time.Nanosecond, + Bytes: 20, + Allocs: 1, + }, + { + Name: "BenchmarkFew", + Duration: 105 * time.Nanosecond, + Bytes: 20, + Allocs: 1, + }, + { + Name: "BenchmarkFew", + Duration: 102 * time.Nanosecond, + Bytes: 20, + Allocs: 1, + }, + { + Name: "BenchmarkFew", + Duration: 102 * time.Nanosecond, + Bytes: 20, + Allocs: 1, }, { Name: "BenchmarkFew", Duration: 102 * time.Nanosecond, Bytes: 20, Allocs: 1, - Count: 5, - Output: []string{}, }, }, }, @@ -1142,17 +1221,14 @@ var testCases = []TestCase{ { Name: "BenchmarkItsy", Duration: 45 * time.Nanosecond, - Output: []string{}, }, { Name: "BenchmarkTeeny", Duration: 2 * time.Nanosecond, - Output: []string{}, }, { Name: "BenchmarkWeeny", Duration: 0 * time.Second, - Output: []string{}, }, }, }, @@ -1257,12 +1333,6 @@ func TestParser(t *testing.T) { if benchmark.Allocs != expBenchmark.Allocs { t.Errorf("benchmark.Allocs == %d, want %d", benchmark.Allocs, expBenchmark.Allocs) } - - benchmarkOutput := strings.Join(benchmark.Output, "\n") - expBenchmarkOutput := strings.Join(expBenchmark.Output, "\n") - if benchmarkOutput != expBenchmarkOutput { - t.Errorf("Benchmark.Output (%s) ==\n%s\n, want\n%s", benchmark.Name, benchmarkOutput, expBenchmarkOutput) - } } if pkg.CoveragePct != expPkg.CoveragePct { diff --git a/parser/parser.go b/parser/parser.go index 45ceb72..8b79f9a 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -55,9 +55,6 @@ type Benchmark struct { Bytes int // number of allocs/op Allocs int - // number of times this benchmark has been seen (for averaging). - Count int - Output []string } var ( @@ -65,7 +62,7 @@ var ( 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). - regexBenchmark = regexp.MustCompile(`^(Benchmark\w+)-\d\s+(\d+)\s+((\d+|\d+\.\d+)\sns/op)(\s+\d+\sB/op)?(\s+\d+\sallocs/op)?`) + regexBenchmark = regexp.MustCompile(`^(Benchmark[^ ]+)-\d\s+(\d+)\s+(\d+|\d+\.\d+)\sns/op(?:\s+(\d+)\sB/op)?(?:\s+(\d+)\sallocs/op)?`) regexOutput = regexp.MustCompile(`( )*\t(.*)`) regexSummary = regexp.MustCompile(`^(PASS|FAIL|SKIP)$`) ) @@ -128,60 +125,16 @@ func Parse(r io.Reader, pkgName string) (*Report, error) { // clear the current build package, so output lines won't be added to that build capturedPackage = "" seenSummary = false - } else if strings.HasPrefix(line, "Benchmark") { - // parse benchmarking info - matches := regexBenchmark.FindStringSubmatch(line) - if len(matches) < 1 { - continue - } - var name string - var duration time.Duration - var bytes int - var allocs int - - for _, field := range matches[1:] { - field = strings.TrimSpace(field) - if strings.HasPrefix(field, "Benchmark") { - name = field - } - if strings.HasSuffix(field, " ns/op") { - durString := strings.TrimSuffix(field, " ns/op") - duration = parseNanoseconds(durString) - } - if strings.HasSuffix(field, " B/op") { - b, _ := strconv.Atoi(strings.TrimSuffix(field, " B/op")) - bytes = b - } - if strings.HasSuffix(field, " allocs/op") { - a, _ := strconv.Atoi(strings.TrimSuffix(field, " allocs/op")) - allocs = a - } - } - - var duplicate bool - // check if duplicate benchmark - for _, bench := range benchmarks { - if bench.Name == name { - duplicate = true - bench.Count++ - bench.Duration += duration - bench.Bytes += bytes - bench.Allocs += allocs - } - } - - if len(benchmarks) < 1 || duplicate == false { - // the first time this benchmark has been seen - benchmarks = append(benchmarks, &Benchmark{ - Name: name, - Duration: duration, - Bytes: bytes, - Allocs: allocs, - Count: 1, - }, - ) - } + } else if matches := regexBenchmark.FindStringSubmatch(line); len(matches) == 6 { + bytes, _ := strconv.Atoi(matches[4]) + allocs, _ := strconv.Atoi(matches[5]) + benchmarks = append(benchmarks, &Benchmark{ + Name: matches[1], + Duration: parseNanoseconds(matches[3]), + Bytes: bytes, + Allocs: allocs, + }) } else if strings.HasPrefix(line, "=== PAUSE ") { continue } else if strings.HasPrefix(line, "=== CONT ") { @@ -212,15 +165,6 @@ func Parse(r io.Reader, pkgName string) (*Report, error) { // all tests in this package are finished - for _, bench := range benchmarks { - if bench.Count > 1 { - bench.Allocs = bench.Allocs / bench.Count - bench.Bytes = bench.Bytes / bench.Count - newDuration := bench.Duration / time.Duration(bench.Count) - bench.Duration = newDuration - } - } - report.Packages = append(report.Packages, Package{ Name: matches[2], Duration: parseSeconds(matches[3]), diff --git a/parser/parser_test.go b/parser/parser_test.go index 63f3ac4..0daa747 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -24,3 +24,24 @@ func TestParseSeconds(t *testing.T) { } } } + +func TestParseNanoseconds(t *testing.T) { + tests := []struct { + in string + d time.Duration + }{ + {"", 0}, + {"0.1", 0 * time.Nanosecond}, + {"0.9", 0 * time.Nanosecond}, + {"4", 4 * time.Nanosecond}, + {"5000", 5 * time.Microsecond}, + {"2000003", 2*time.Millisecond + 3*time.Nanosecond}, + } + + for _, test := range tests { + d := parseNanoseconds(test.in) + if d != test.d { + t.Errorf("parseSeconds(%q) == %v, want %v\n", test.in, d, test.d) + } + } +} diff --git a/testdata/23-report.xml b/testdata/23-report.xml index 31a04b3..3d6e11a 100644 --- a/testdata/23-report.xml +++ b/testdata/23-report.xml @@ -4,7 +4,7 @@ - - + + diff --git a/testdata/24-report.xml b/testdata/24-report.xml index 199fa1e..b1b27a6 100644 --- a/testdata/24-report.xml +++ b/testdata/24-report.xml @@ -8,7 +8,7 @@ - - + + diff --git a/testdata/25-report.xml b/testdata/25-report.xml index a8399ae..d5277f1 100644 --- a/testdata/25-report.xml +++ b/testdata/25-report.xml @@ -1,10 +1,10 @@ - + - - + + diff --git a/testdata/26-report.xml b/testdata/26-report.xml index 293d2ab..24330d9 100644 --- a/testdata/26-report.xml +++ b/testdata/26-report.xml @@ -1,6 +1,6 @@ - + @@ -9,7 +9,7 @@ - - + +