open-nomad/e2e/cli/command/test_decoder.go

233 lines
5.2 KiB
Go

package command
import (
"encoding/json"
"fmt"
"io"
"strings"
"text/tabwriter"
"time"
"github.com/fatih/color"
hclog "github.com/hashicorp/go-hclog"
)
type EventDecoder struct {
r io.Reader
dec *json.Decoder
report *TestReport
}
type TestReport struct {
Events []*TestEvent
Suites map[string]*TestSuite
TotalSuites int
TotalFailedSuites int
TotalCases int
TotalFailedCases int
TotalTests int
TotalFailedTests int
Elapsed float64
Output []string
}
type TestEvent struct {
Time time.Time // encodes as an RFC3339-format string
Action string
Package string
Test string
Elapsed float64 // seconds
Output string
suiteName string
caseName string
testName string
}
type TestSuite struct {
Name string
Cases map[string]*TestCase
Failed int
Elapsed float64
Output []string
}
type TestCase struct {
Name string
Tests map[string]*Test
Failed int
Elapsed float64
Output []string
}
type Test struct {
Name string
Output []string
Failed bool
Elapsed float64
}
func NewDecoder(r io.Reader) *EventDecoder {
return &EventDecoder{
r: r,
dec: json.NewDecoder(r),
report: &TestReport{
Suites: map[string]*TestSuite{},
Events: []*TestEvent{},
},
}
}
func (d *EventDecoder) Decode(logger hclog.Logger) (*TestReport, error) {
for d.dec.More() {
var e TestEvent
err := d.dec.Decode(&e)
if err != nil {
return nil, err
}
d.report.record(&e)
if logger != nil && e.Output != "" {
logger.Debug(strings.TrimRight(e.Output, "\n"))
}
}
return d.report, nil
}
func (r *TestReport) record(event *TestEvent) {
if !strings.HasPrefix(event.Test, "TestE2E") {
return
}
parts := strings.Split(event.Test, "/")
switch len(parts) {
case 1:
r.recordRoot(event)
case 2:
event.suiteName = parts[1]
r.recordSuite(event)
case 3:
event.suiteName = parts[1]
event.caseName = parts[2]
r.recordCase(event, r.Suites[event.suiteName])
case 4:
event.suiteName = parts[1]
event.caseName = parts[2]
event.testName = strings.Join(parts[3:], "/")
suite := r.Suites[event.suiteName]
r.recordTest(event, suite, suite.Cases[event.caseName])
}
r.Events = append(r.Events, event)
}
func (r *TestReport) recordRoot(event *TestEvent) {
switch event.Action {
case "run":
case "output":
r.Output = append(r.Output, event.Output)
case "pass", "fail":
r.Elapsed = event.Elapsed
}
}
func (r *TestReport) recordSuite(event *TestEvent) {
switch event.Action {
case "run":
r.Suites[event.suiteName] = &TestSuite{
Name: event.suiteName,
Cases: map[string]*TestCase{},
}
r.TotalSuites += 1
case "output":
r.Suites[event.suiteName].Output = append(r.Suites[event.suiteName].Output, event.Output)
case "pass":
r.Suites[event.suiteName].Elapsed = event.Elapsed
case "fail":
r.Suites[event.suiteName].Elapsed = event.Elapsed
r.TotalFailedSuites += 1
}
}
func (r *TestReport) recordCase(event *TestEvent, suite *TestSuite) {
switch event.Action {
case "run":
suite.Cases[event.caseName] = &TestCase{
Name: event.caseName,
Tests: map[string]*Test{},
}
r.TotalCases += 1
case "output":
suite.Cases[event.caseName].Output = append(suite.Cases[event.caseName].Output, event.Output)
case "pass":
suite.Cases[event.caseName].Elapsed = event.Elapsed
case "fail":
suite.Cases[event.caseName].Elapsed = event.Elapsed
suite.Failed += 1
r.TotalFailedCases += 1
}
}
func (r *TestReport) recordTest(event *TestEvent, suite *TestSuite, c *TestCase) {
switch event.Action {
case "run":
c.Tests[event.testName] = &Test{
Name: event.testName,
}
r.TotalTests += 1
case "output":
c.Tests[event.testName].Output = append(c.Tests[event.testName].Output, event.Output)
case "pass":
c.Tests[event.testName].Elapsed = event.Elapsed
case "fail":
c.Tests[event.testName].Elapsed = event.Elapsed
c.Tests[event.testName].Failed = true
c.Failed += 1
r.TotalFailedTests += 1
}
}
func (r *TestReport) Summary() string {
green := color.New(color.FgGreen).SprintFunc()
red := color.New(color.FgRed).SprintFunc()
sb := strings.Builder{}
sb.WriteString(
fmt.Sprintf("Summary: %v/%v suites failed | %v/%v cases failed | %v/%v tests failed\n",
r.TotalFailedSuites, r.TotalSuites,
r.TotalFailedCases, r.TotalCases,
r.TotalFailedTests, r.TotalTests))
sb.WriteString("Details:\n")
w := tabwriter.NewWriter(&sb, 0, 0, 1, ' ', tabwriter.AlignRight)
for sname, suite := range r.Suites {
status := red("FAIL")
if suite.Failed == 0 {
status = green("PASS")
}
fmt.Fprintf(w, "[%s]\t%s\t\t\t (%vs)\n", status, sname, suite.Elapsed)
for cname, c := range suite.Cases {
status := red("FAIL")
if c.Failed == 0 {
status = green("PASS")
}
fmt.Fprintf(w, "[%s]\t↳\t%s\t\t (%vs)\n", status, cname, c.Elapsed)
for tname, test := range c.Tests {
status := red("FAIL")
if !test.Failed {
status = green("PASS")
}
fmt.Fprintf(w, "[%s]\t\t↳\t%s\t (%vs)\n", status, tname, test.Elapsed)
if test.Failed {
for _, line := range test.Output[2:] {
fmt.Fprintf(w, "\t\t\t%s\n", strings.Replace(strings.TrimSpace(line), "\t", " ", -1))
}
fmt.Fprintln(w, "\t\t\t----------")
}
}
}
}
w.Flush()
return sb.String()
}