diff --git a/pkg/gtr/builder.go b/pkg/gtr/builder.go index 49e8cec..5fbdffe 100644 --- a/pkg/gtr/builder.go +++ b/pkg/gtr/builder.go @@ -7,8 +7,9 @@ import ( // ReportBuilder builds Reports. type ReportBuilder struct { - packages []Package - tests map[int]Test + packages []Package + tests map[int]Test + benchmarks map[int]Benchmark // state nextId int // next free id @@ -18,8 +19,9 @@ type ReportBuilder struct { func NewReportBuilder() *ReportBuilder { return &ReportBuilder{ - tests: make(map[int]Test), - nextId: 1, + tests: make(map[int]Test), + benchmarks: make(map[int]Benchmark), + nextId: 1, } } @@ -52,19 +54,46 @@ func (b *ReportBuilder) EndTest(name, result string, duration time.Duration) { b.tests[id] = t } +func (b *ReportBuilder) Benchmark(name string, iterations int64, nsPerOp, mbPerSec float64, bytesPerOp, allocsPerOp int64) { + id := b.nextId + b.lastId = id + + b.nextId += 1 + b.benchmarks[id] = Benchmark{ + Name: name, + Result: Pass, + Iterations: iterations, + NsPerOp: nsPerOp, + MBPerSec: mbPerSec, + BytesPerOp: bytesPerOp, + AllocsPerOp: allocsPerOp, + } +} + func (b *ReportBuilder) CreatePackage(name string, duration time.Duration) { var tests []Test + var benchmarks []Benchmark + + // Iterate by id to maintain original test order for id := 1; id < b.nextId; id++ { - tests = append(tests, b.tests[id]) + if t, ok := b.tests[id]; ok { + tests = append(tests, t) + } + if bm, ok := b.benchmarks[id]; ok { + benchmarks = append(benchmarks, bm) + } } + b.packages = append(b.packages, Package{ - Name: name, - Duration: duration, - Tests: tests, - Output: b.output, + Name: name, + Duration: duration, + Tests: tests, + Benchmarks: benchmarks, + Output: b.output, }) b.tests = make(map[int]Test) + b.benchmarks = make(map[int]Benchmark) b.output = nil b.nextId = 1 b.lastId = 0 @@ -93,6 +122,19 @@ func (b *ReportBuilder) findTest(name string) int { return -1 } +func (b *ReportBuilder) findBenchmark(name string) int { + // check if this benchmark was lastId + if bm, ok := b.benchmarks[b.lastId]; ok && bm.Name == name { + return b.lastId + } + for id := len(b.benchmarks); id >= 0; id-- { + if b.benchmarks[id].Name == name { + return id + } + } + return -1 +} + func parseResult(r string) Result { switch r { case "PASS": diff --git a/pkg/gtr/gtr.go b/pkg/gtr/gtr.go index ade5645..3262afa 100644 --- a/pkg/gtr/gtr.go +++ b/pkg/gtr/gtr.go @@ -10,6 +10,11 @@ import ( "github.com/jstemmer/go-junit-report/v2/pkg/junit" ) +var ( + propPrefixes = map[string]bool{"goos": true, "goarch": true, "pkg": true} + propFieldsFunc = func(r rune) bool { return r == ':' || r == ' ' } +) + type Report struct { Packages []Package } @@ -31,7 +36,8 @@ type Package struct { Coverage float64 Output []string - Tests []Test + Tests []Test + Benchmarks []Benchmark } type Test struct { @@ -41,6 +47,17 @@ type Test struct { Output []string } +type Benchmark struct { + Name string + Result Result + Output []string + Iterations int64 + NsPerOp float64 + MBPerSec float64 + BytesPerOp int64 + AllocsPerOp int64 +} + // FromEvents creates a Report from the given list of events. func FromEvents(events []Event) Report { report := NewReportBuilder() @@ -50,6 +67,8 @@ func FromEvents(events []Event) Report { report.CreateTest(ev.Name) case "end_test": report.EndTest(ev.Name, ev.Result, ev.Duration) + case "benchmark": + report.Benchmark(ev.Name, ev.Iterations, ev.NsPerOp, ev.MBPerSec, ev.BytesPerOp, ev.AllocsPerOp) case "status": // ignore for now case "summary": report.CreatePackage(ev.Name, ev.Duration) @@ -72,6 +91,12 @@ func JUnit(report Report) junit.Testsuites { Time: junit.FormatDuration(pkg.Duration), } + for _, line := range pkg.Output { + if fields := strings.FieldsFunc(line, propFieldsFunc); len(fields) == 2 && propPrefixes[fields[0]] { + suite.AddProperty(fields[0], fields[1]) + } + } + for _, test := range pkg.Tests { tc := junit.Testcase{ Classname: pkg.Name, @@ -90,7 +115,50 @@ func JUnit(report Report) junit.Testsuites { } suite.AddTestcase(tc) } + + for _, bm := range mergeBenchmarks(pkg.Benchmarks) { + tc := junit.Testcase{ + Classname: pkg.Name, + Name: bm.Name, + Time: junit.FormatBenchmarkTime(time.Duration(bm.NsPerOp)), + } + if bm.Result == Fail { + tc.Failure = &junit.Result{ + Message: "Failed", + } + } + suite.AddTestcase(tc) + } + suites.AddSuite(suite) } return suites } + +func mergeBenchmarks(benchmarks []Benchmark) []Benchmark { + var merged []Benchmark + + benchmap := make(map[string][]Benchmark) + for _, bm := range benchmarks { + if _, ok := benchmap[bm.Name]; !ok { + merged = append(merged, Benchmark{Name: bm.Name}) + } + benchmap[bm.Name] = append(benchmap[bm.Name], bm) + } + + for i, bm := range merged { + for _, b := range benchmap[bm.Name] { + bm.NsPerOp += b.NsPerOp + bm.MBPerSec += b.MBPerSec + bm.BytesPerOp += b.BytesPerOp + bm.AllocsPerOp += b.AllocsPerOp + } + n := len(benchmap[bm.Name]) + merged[i].NsPerOp = bm.NsPerOp / float64(n) + merged[i].MBPerSec = bm.MBPerSec / float64(n) + merged[i].BytesPerOp = bm.BytesPerOp / int64(n) + merged[i].AllocsPerOp = bm.AllocsPerOp / int64(n) + } + + return merged +} diff --git a/pkg/gtr/gtr_test.go b/pkg/gtr/gtr_test.go index f30205a..61760f0 100644 --- a/pkg/gtr/gtr_test.go +++ b/pkg/gtr/gtr_test.go @@ -21,6 +21,11 @@ func TestFromEvents(t *testing.T) { {Type: "end_test", Name: "TestOne", Result: "FAIL", Duration: 1 * time.Millisecond}, {Type: "status", Result: "FAIL"}, {Type: "summary", Result: "FAIL", Name: "package/name2", Duration: 1 * time.Millisecond}, + {Type: "output", Data: "goarch: amd64"}, + {Type: "benchmark", Name: "BenchmarkOne", NsPerOp: 100}, + {Type: "benchmark", Name: "BenchmarkOne", NsPerOp: 300}, + {Type: "status", Result: "PASS"}, + {Type: "summary", Result: "ok", Name: "package/name3", Duration: 1234 * time.Millisecond}, } expected := Report{ Packages: []Package{ @@ -31,7 +36,7 @@ func TestFromEvents(t *testing.T) { { Name: "TestOne", Duration: 1 * time.Millisecond, - Result: PASS, + Result: Pass, Output: []string{ "\tHello", // TODO: strip tabs? }, @@ -39,7 +44,7 @@ func TestFromEvents(t *testing.T) { { Name: "TestSkip", Duration: 1 * time.Millisecond, - Result: SKIP, + Result: Skip, }, }, }, @@ -50,13 +55,30 @@ func TestFromEvents(t *testing.T) { { Name: "TestOne", Duration: 1 * time.Millisecond, - Result: FAIL, + Result: Fail, Output: []string{ "\tfile_test.go:10: error", }, }, }, }, + { + Name: "package/name3", + Duration: 1234 * time.Millisecond, + Benchmarks: []Benchmark{ + { + Name: "BenchmarkOne", + Result: Pass, + NsPerOp: 100, + }, + { + Name: "BenchmarkOne", + Result: Pass, + NsPerOp: 300, + }, + }, + Output: []string{"goarch: amd64"}, + }, }, }