233 lines
5.2 KiB
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()
|
|
}
|