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 @@
-
-
+
+