Aiden Scandella 1b60a11396 Add support for Go 1.5 test results
An alternate approach that does not involve regular expressions.

Fixes #15
2015-08-23 10:55:52 -07:00

180 lines
3.7 KiB
Go

package parser
import (
"bufio"
"io"
"regexp"
"strconv"
"strings"
)
// Result represents a test result.
type Result int
// Test result constants
const (
PASS Result = iota
FAIL
SKIP
)
// Report is a collection of package tests.
type Report struct {
Packages []Package
}
// Package contains the test results of a single package.
type Package struct {
Name string
Time int
Tests []*Test
CoveragePct string
}
// Test contains the results of a single test.
type Test struct {
Name string
Time int
Result Result
Output []string
}
var (
regexStatus = regexp.MustCompile(`^--- (PASS|FAIL|SKIP): (.+) \((\d+\.\d+)(?: seconds|s)\)$`)
regexCoverage = regexp.MustCompile(`^coverage:\s+(\d+\.\d+)%\s+of\s+statements$`)
regexResult = regexp.MustCompile(`^(ok|FAIL)\s+(.+)\s(\d+\.\d+)s(?:\s+coverage:\s+(\d+\.\d+)%\s+of\s+statements)?$`)
)
// Parse parses go test output from reader r and returns a report with the
// results. An optional pkgName can be given, which is used in case a package
// result line is missing.
func Parse(r io.Reader, pkgName string) (*Report, error) {
reader := bufio.NewReader(r)
report := &Report{make([]Package, 0)}
// keep track of tests we find
var tests []*Test
// sum of tests' time, use this if current test has no result line (when it is compiled test)
testsTime := 0
// current test
var cur string
// coverage percentage report for current package
var coveragePct string
// parse lines
for {
l, _, err := reader.ReadLine()
if err != nil && err == io.EOF {
break
} else if err != nil {
return nil, err
}
line := string(l)
if strings.HasPrefix(line, "=== RUN ") {
// new test
cur = strings.TrimSpace(line[8:])
tests = append(tests, &Test{
Name: cur,
Result: FAIL,
Output: make([]string, 0),
})
} else if matches := regexResult.FindStringSubmatch(line); len(matches) == 5 {
if matches[4] != "" {
coveragePct = matches[4]
}
// all tests in this package are finished
report.Packages = append(report.Packages, Package{
Name: matches[2],
Time: parseTime(matches[3]),
Tests: tests,
CoveragePct: coveragePct,
})
tests = make([]*Test, 0)
coveragePct = ""
cur = ""
testsTime = 0
} else if matches := regexStatus.FindStringSubmatch(line); len(matches) == 4 {
cur = matches[2]
test := findTest(tests, cur)
if test == nil {
continue
}
// test status
if matches[1] == "PASS" {
test.Result = PASS
} else if matches[1] == "SKIP" {
test.Result = SKIP
} else {
test.Result = FAIL
}
test.Name = matches[2]
testTime := parseTime(matches[3]) * 10
test.Time = testTime
testsTime += testTime
} else if matches := regexCoverage.FindStringSubmatch(line); len(matches) == 2 {
coveragePct = matches[1]
} else if strings.HasPrefix(line, "\t") {
// test output
test := findTest(tests, cur)
if test == nil {
continue
}
test.Output = append(test.Output, line[1:])
}
}
if len(tests) > 0 {
// no result line found
report.Packages = append(report.Packages, Package{
Name: pkgName,
Time: testsTime,
Tests: tests,
CoveragePct: coveragePct,
})
}
return report, nil
}
func parseTime(time string) int {
t, err := strconv.Atoi(strings.Replace(time, ".", "", -1))
if err != nil {
return 0
}
return t
}
func findTest(tests []*Test, name string) *Test {
for i := 0; i < len(tests); i++ {
if tests[i].Name == name {
return tests[i]
}
}
return nil
}
// Failures counts the number of failed tests in this report
func (r *Report) Failures() int {
count := 0
for _, p := range r.Packages {
for _, t := range p.Tests {
if t.Result == FAIL {
count++
}
}
}
return count
}