gtr,parser/gotest: move Event and building a report to parser/gotest

The Parse method now directly returns a report, rather than a list of
events that then need to be converted into a report. As part of this
change, the Event struct has also been moved to the gotest package. It's
now the responsibility of the parser to construct a gtr.Report.
This commit is contained in:
Joël Stemmer
2022-03-14 23:17:57 +00:00
parent 832cc97037
commit c78e04707f
9 changed files with 272 additions and 252 deletions

View File

@ -0,0 +1,25 @@
package gotest
import "time"
// Event is a single event in a test or benchmark.
type Event struct {
Type string
Name string
Result string
Duration time.Duration
Data string
Indent int
// Code coverage
CovPct float64
CovPackages []string
// Benchmarks
Iterations int64
NsPerOp float64
MBPerSec float64
BytesPerOp int64
AllocsPerOp int64
}

View File

@ -59,16 +59,58 @@ func New(options ...Option) *Parser {
type Parser struct {
packageName string
events []gtr.Event
events []Event
}
// Parse parses Go test output from the given io.Reader r.
func (p *Parser) Parse(r io.Reader) ([]gtr.Event, error) {
// Parse parses Go test output from the given io.Reader r and returns
// gtr.Report.
func (p *Parser) Parse(r io.Reader) (gtr.Report, error) {
p.events = nil
s := bufio.NewScanner(r)
for s.Scan() {
p.parseLine(s.Text())
}
return p.events, s.Err()
return p.report(p.events), s.Err()
}
// report generates a gtr.Report from the given list of events.
func (p *Parser) report(events []Event) gtr.Report {
rb := gtr.NewReportBuilder()
rb.PackageName = p.packageName
for _, ev := range events {
switch ev.Type {
case "run_test":
rb.CreateTest(ev.Name)
case "pause_test":
rb.PauseTest(ev.Name)
case "cont_test":
rb.ContinueTest(ev.Name)
case "end_test":
rb.EndTest(ev.Name, ev.Result, ev.Duration, ev.Indent)
case "benchmark":
rb.Benchmark(ev.Name, ev.Iterations, ev.NsPerOp, ev.MBPerSec, ev.BytesPerOp, ev.AllocsPerOp)
case "status":
rb.End()
case "summary":
rb.CreatePackage(ev.Name, ev.Result, ev.Duration, ev.Data)
case "coverage":
rb.Coverage(ev.CovPct, ev.CovPackages)
case "build_output":
rb.CreateBuildError(ev.Name)
case "output":
rb.AppendOutput(ev.Data)
default:
fmt.Printf("unhandled event type: %v\n", ev.Type)
}
}
return rb.Build()
}
// Events returns the events created by the parser.
func (p *Parser) Events() []Event {
events := make([]Event, len(p.events))
copy(events, p.events)
return events
}
func (p *Parser) parseLine(line string) {
@ -101,20 +143,20 @@ func (p *Parser) parseLine(line string) {
}
}
func (p *Parser) add(event gtr.Event) {
func (p *Parser) add(event Event) {
p.events = append(p.events, event)
}
func (p *Parser) runTest(name string) {
p.add(gtr.Event{Type: "run_test", Name: name})
p.add(Event{Type: "run_test", Name: name})
}
func (p *Parser) pauseTest(name string) {
p.add(gtr.Event{Type: "pause_test", Name: name})
p.add(Event{Type: "pause_test", Name: name})
}
func (p *Parser) contTest(name string) {
p.add(gtr.Event{Type: "cont_test", Name: name})
p.add(Event{Type: "cont_test", Name: name})
}
func (p *Parser) endTest(line, indent, result, name, duration string) {
@ -122,7 +164,7 @@ func (p *Parser) endTest(line, indent, result, name, duration string) {
p.output(line[:idx])
}
_, n := stripIndent(indent)
p.add(gtr.Event{
p.add(Event{
Type: "end_test",
Name: name,
Result: result,
@ -132,11 +174,11 @@ func (p *Parser) endTest(line, indent, result, name, duration string) {
}
func (p *Parser) status(result string) {
p.add(gtr.Event{Type: "status", Result: result})
p.add(Event{Type: "status", Result: result})
}
func (p *Parser) summary(result, name, duration, cached, status, covpct, packages string) {
p.add(gtr.Event{
p.add(Event{
Type: "summary",
Result: result,
Name: name,
@ -148,7 +190,7 @@ func (p *Parser) summary(result, name, duration, cached, status, covpct, package
}
func (p *Parser) coverage(percent, packages string) {
p.add(gtr.Event{
p.add(Event{
Type: "coverage",
CovPct: parseFloat(percent),
CovPackages: parsePackages(packages),
@ -156,7 +198,7 @@ func (p *Parser) coverage(percent, packages string) {
}
func (p *Parser) benchmark(name, iterations, nsPerOp, mbPerSec, bytesPerOp, allocsPerOp string) {
p.add(gtr.Event{
p.add(Event{
Type: "benchmark",
Name: name,
Iterations: parseInt(iterations),
@ -168,14 +210,14 @@ func (p *Parser) benchmark(name, iterations, nsPerOp, mbPerSec, bytesPerOp, allo
}
func (p *Parser) buildOutput(packageName string) {
p.add(gtr.Event{
p.add(Event{
Type: "build_output",
Name: packageName,
})
}
func (p *Parser) output(line string) {
p.add(gtr.Event{Type: "output", Data: line})
p.add(Event{Type: "output", Data: line})
}
func parseSeconds(s string) time.Duration {

View File

@ -6,9 +6,8 @@ import (
"testing"
"time"
"github.com/jstemmer/go-junit-report/v2/pkg/gtr"
"github.com/google/go-cmp/cmp"
"github.com/jstemmer/go-junit-report/v2/pkg/gtr"
)
type parseLineTest struct {
@ -19,160 +18,160 @@ type parseLineTest struct {
var parseLineTests = []parseLineTest{
{
"=== RUN TestOne",
gtr.Event{Type: "run_test", Name: "TestOne"},
Event{Type: "run_test", Name: "TestOne"},
},
{
"=== RUN TestTwo/Subtest",
gtr.Event{Type: "run_test", Name: "TestTwo/Subtest"},
Event{Type: "run_test", Name: "TestTwo/Subtest"},
},
{
"=== PAUSE TestOne",
gtr.Event{Type: "pause_test", Name: "TestOne"},
Event{Type: "pause_test", Name: "TestOne"},
},
{
"=== CONT TestOne",
gtr.Event{Type: "cont_test", Name: "TestOne"},
Event{Type: "cont_test", Name: "TestOne"},
},
{
"--- PASS: TestOne (12.34 seconds)",
gtr.Event{Type: "end_test", Name: "TestOne", Result: "PASS", Duration: 12_340 * time.Millisecond},
Event{Type: "end_test", Name: "TestOne", Result: "PASS", Duration: 12_340 * time.Millisecond},
},
{
" --- SKIP: TestOne/Subtest (0.00s)",
gtr.Event{Type: "end_test", Name: "TestOne/Subtest", Result: "SKIP", Indent: 1},
Event{Type: "end_test", Name: "TestOne/Subtest", Result: "SKIP", Indent: 1},
},
{
" --- FAIL: TestOne/Subtest/#01 (0.35s)",
gtr.Event{Type: "end_test", Name: "TestOne/Subtest/#01", Result: "FAIL", Duration: 350 * time.Millisecond, Indent: 2},
Event{Type: "end_test", Name: "TestOne/Subtest/#01", Result: "FAIL", Duration: 350 * time.Millisecond, Indent: 2},
},
{
"some text--- PASS: TestTwo (0.06 seconds)",
[]gtr.Event{
[]Event{
{Type: "output", Data: "some text"},
{Type: "end_test", Name: "TestTwo", Result: "PASS", Duration: 60 * time.Millisecond},
},
},
{
"PASS",
gtr.Event{Type: "status", Result: "PASS"},
Event{Type: "status", Result: "PASS"},
},
{
"FAIL",
gtr.Event{Type: "status", Result: "FAIL"},
Event{Type: "status", Result: "FAIL"},
},
{
"SKIP",
gtr.Event{Type: "status", Result: "SKIP"},
Event{Type: "status", Result: "SKIP"},
},
{
"ok package/name/ok 0.100s",
gtr.Event{Type: "summary", Name: "package/name/ok", Result: "ok", Duration: 100 * time.Millisecond},
Event{Type: "summary", Name: "package/name/ok", Result: "ok", Duration: 100 * time.Millisecond},
},
{
"FAIL package/name/failing [build failed]",
gtr.Event{Type: "summary", Name: "package/name/failing", Result: "FAIL", Data: "[build failed]"},
Event{Type: "summary", Name: "package/name/failing", Result: "FAIL", Data: "[build failed]"},
},
{
"FAIL package/other/failing [setup failed]",
gtr.Event{Type: "summary", Name: "package/other/failing", Result: "FAIL", Data: "[setup failed]"},
Event{Type: "summary", Name: "package/other/failing", Result: "FAIL", Data: "[setup failed]"},
},
{
"ok package/other (cached)",
gtr.Event{Type: "summary", Name: "package/other", Result: "ok", Data: "(cached)"},
Event{Type: "summary", Name: "package/other", Result: "ok", Data: "(cached)"},
},
{
"ok package/name 0.400s coverage: 10.0% of statements",
gtr.Event{Type: "summary", Name: "package/name", Result: "ok", Duration: 400 * time.Millisecond, CovPct: 10},
Event{Type: "summary", Name: "package/name", Result: "ok", Duration: 400 * time.Millisecond, CovPct: 10},
},
{
"ok package/name 4.200s coverage: 99.8% of statements in fmt, encoding/xml",
gtr.Event{Type: "summary", Name: "package/name", Result: "ok", Duration: 4200 * time.Millisecond, CovPct: 99.8, CovPackages: []string{"fmt", "encoding/xml"}},
Event{Type: "summary", Name: "package/name", Result: "ok", Duration: 4200 * time.Millisecond, CovPct: 99.8, CovPackages: []string{"fmt", "encoding/xml"}},
},
{
"? package/name [no test files]",
gtr.Event{Type: "summary", Name: "package/name", Result: "?", Data: "[no test files]"},
Event{Type: "summary", Name: "package/name", Result: "?", Data: "[no test files]"},
},
{
"ok package/name 0.001s [no tests to run]",
gtr.Event{Type: "summary", Name: "package/name", Result: "ok", Duration: 1 * time.Millisecond, Data: "[no tests to run]"},
Event{Type: "summary", Name: "package/name", Result: "ok", Duration: 1 * time.Millisecond, Data: "[no tests to run]"},
},
{
"ok package/name (cached) [no tests to run]",
gtr.Event{Type: "summary", Name: "package/name", Result: "ok", Data: "(cached) [no tests to run]"},
Event{Type: "summary", Name: "package/name", Result: "ok", Data: "(cached) [no tests to run]"},
},
{
"coverage: 10% of statements",
gtr.Event{Type: "coverage", CovPct: 10},
Event{Type: "coverage", CovPct: 10},
},
{
"coverage: 10% of statements in fmt, encoding/xml",
gtr.Event{Type: "coverage", CovPct: 10, CovPackages: []string{"fmt", "encoding/xml"}},
Event{Type: "coverage", CovPct: 10, CovPackages: []string{"fmt", "encoding/xml"}},
},
{
"coverage: 13.37% of statements",
gtr.Event{Type: "coverage", CovPct: 13.37},
Event{Type: "coverage", CovPct: 13.37},
},
{
"coverage: 99.8% of statements in fmt, encoding/xml",
gtr.Event{Type: "coverage", CovPct: 99.8, CovPackages: []string{"fmt", "encoding/xml"}},
Event{Type: "coverage", CovPct: 99.8, CovPackages: []string{"fmt", "encoding/xml"}},
},
{
"BenchmarkOne-8 2000000 604 ns/op",
gtr.Event{Type: "benchmark", Name: "BenchmarkOne", Iterations: 2_000_000, NsPerOp: 604},
Event{Type: "benchmark", Name: "BenchmarkOne", Iterations: 2_000_000, NsPerOp: 604},
},
{
"BenchmarkTwo-16 30000 52568 ns/op 24879 B/op 494 allocs/op",
gtr.Event{Type: "benchmark", Name: "BenchmarkTwo", Iterations: 30_000, NsPerOp: 52_568, BytesPerOp: 24_879, AllocsPerOp: 494},
Event{Type: "benchmark", Name: "BenchmarkTwo", Iterations: 30_000, NsPerOp: 52_568, BytesPerOp: 24_879, AllocsPerOp: 494},
},
{
"BenchmarkThree 2000000000 0.26 ns/op",
gtr.Event{Type: "benchmark", Name: "BenchmarkThree", Iterations: 2_000_000_000, NsPerOp: 0.26},
Event{Type: "benchmark", Name: "BenchmarkThree", Iterations: 2_000_000_000, NsPerOp: 0.26},
},
{
"BenchmarkFour-8 10000 104427 ns/op 95.76 MB/s 40629 B/op 5 allocs/op",
gtr.Event{Type: "benchmark", Name: "BenchmarkFour", Iterations: 10_000, NsPerOp: 104_427, MBPerSec: 95.76, BytesPerOp: 40_629, AllocsPerOp: 5},
Event{Type: "benchmark", Name: "BenchmarkFour", Iterations: 10_000, NsPerOp: 104_427, MBPerSec: 95.76, BytesPerOp: 40_629, AllocsPerOp: 5},
},
{
"# package/name/failing1",
gtr.Event{Type: "build_output", Name: "package/name/failing1"},
Event{Type: "build_output", Name: "package/name/failing1"},
},
{
"# package/name/failing2 [package/name/failing2.test]",
gtr.Event{Type: "build_output", Name: "package/name/failing2"},
Event{Type: "build_output", Name: "package/name/failing2"},
},
{
"single line stdout",
gtr.Event{Type: "output", Data: "single line stdout"},
Event{Type: "output", Data: "single line stdout"},
},
{
"# some more output",
gtr.Event{Type: "output", Data: "# some more output"},
Event{Type: "output", Data: "# some more output"},
},
{
"\tfile_test.go:11: Error message",
gtr.Event{Type: "output", Data: "\tfile_test.go:11: Error message"},
Event{Type: "output", Data: "\tfile_test.go:11: Error message"},
},
{
"\tfile_test.go:12: Longer",
gtr.Event{Type: "output", Data: "\tfile_test.go:12: Longer"},
Event{Type: "output", Data: "\tfile_test.go:12: Longer"},
},
{
"\t\terror",
gtr.Event{Type: "output", Data: "\t\terror"},
Event{Type: "output", Data: "\t\terror"},
},
{
"\t\tmessage.",
gtr.Event{Type: "output", Data: "\t\tmessage."},
Event{Type: "output", Data: "\t\tmessage."},
},
}
func TestParseLine(t *testing.T) {
for i, test := range parseLineTests {
var want []gtr.Event
var want []Event
switch e := test.events.(type) {
case gtr.Event:
want = []gtr.Event{e}
case []gtr.Event:
case Event:
want = []Event{e}
case []Event:
want = e
default:
panic("invalid events type")
@ -194,3 +193,96 @@ func TestParseLine(t *testing.T) {
})
}
}
func TestReport(t *testing.T) {
events := []Event{
{Type: "run_test", Name: "TestOne"},
{Type: "output", Data: "\tHello"},
{Type: "end_test", Name: "TestOne", Result: "PASS", Duration: 1 * time.Millisecond},
{Type: "status", Result: "PASS"},
{Type: "run_test", Name: "TestSkip"},
{Type: "end_test", Name: "TestSkip", Result: "SKIP", Duration: 1 * time.Millisecond},
{Type: "summary", Result: "ok", Name: "package/name", Duration: 1 * time.Millisecond},
{Type: "run_test", Name: "TestOne"},
{Type: "output", Data: "\tfile_test.go:10: error"},
{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},
{Type: "build_output", Name: "package/failing1"},
{Type: "output", Data: "error message"},
{Type: "summary", Result: "FAIL", Name: "package/failing1", Data: "[build failed]"},
}
expected := gtr.Report{
Packages: []gtr.Package{
{
Name: "package/name",
Duration: 1 * time.Millisecond,
Tests: []gtr.Test{
{
Name: "TestOne",
Duration: 1 * time.Millisecond,
Result: gtr.Pass,
Output: []string{
"\tHello", // TODO: strip tabs?
},
},
{
Name: "TestSkip",
Duration: 1 * time.Millisecond,
Result: gtr.Skip,
},
},
},
{
Name: "package/name2",
Duration: 1 * time.Millisecond,
Tests: []gtr.Test{
{
Name: "TestOne",
Duration: 1 * time.Millisecond,
Result: gtr.Fail,
Output: []string{
"\tfile_test.go:10: error",
},
},
},
},
{
Name: "package/name3",
Duration: 1234 * time.Millisecond,
Benchmarks: []gtr.Benchmark{
{
Name: "BenchmarkOne",
Result: gtr.Pass,
NsPerOp: 100,
},
{
Name: "BenchmarkOne",
Result: gtr.Pass,
NsPerOp: 300,
},
},
Output: []string{"goarch: amd64"},
},
{
Name: "package/failing1",
BuildError: gtr.Error{
Name: "package/failing1",
Cause: "[build failed]",
Output: []string{"error message"},
},
},
},
}
parser := &Parser{}
actual := parser.report(events)
if diff := cmp.Diff(actual, expected); diff != "" {
t.Errorf("FromEvents report incorrect, diff (-got, +want):\n%v", diff)
}
}