You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
178 lines
4.6 KiB
178 lines
4.6 KiB
package parser |
|
|
|
import ( |
|
"fmt" |
|
"regexp" |
|
"sort" |
|
"strconv" |
|
"strings" |
|
|
|
"github.com/smartystreets/goconvey/web/server/contract" |
|
) |
|
|
|
var ( |
|
testNamePattern = regexp.MustCompile("^=== RUN:? +(.+)$") |
|
) |
|
|
|
func ParsePackageResults(result *contract.PackageResult, rawOutput string) { |
|
newOutputParser(result, rawOutput).parse() |
|
} |
|
|
|
type outputParser struct { |
|
raw string |
|
lines []string |
|
result *contract.PackageResult |
|
tests []*contract.TestResult |
|
|
|
// place holders for loops |
|
line string |
|
test *contract.TestResult |
|
testMap map[string]*contract.TestResult |
|
} |
|
|
|
func newOutputParser(result *contract.PackageResult, rawOutput string) *outputParser { |
|
self := new(outputParser) |
|
self.raw = strings.TrimSpace(rawOutput) |
|
self.lines = strings.Split(self.raw, "\n") |
|
self.result = result |
|
self.tests = []*contract.TestResult{} |
|
self.testMap = make(map[string]*contract.TestResult) |
|
return self |
|
} |
|
|
|
func (self *outputParser) parse() { |
|
self.separateTestFunctionsAndMetadata() |
|
self.parseEachTestFunction() |
|
} |
|
|
|
func (self *outputParser) separateTestFunctionsAndMetadata() { |
|
for _, self.line = range self.lines { |
|
if self.processNonTestOutput() { |
|
break |
|
} |
|
self.processTestOutput() |
|
} |
|
} |
|
func (self *outputParser) processNonTestOutput() bool { |
|
if noGoFiles(self.line) { |
|
self.recordFinalOutcome(contract.NoGoFiles) |
|
|
|
} else if buildFailed(self.line) { |
|
self.recordFinalOutcome(contract.BuildFailure) |
|
|
|
} else if noTestFiles(self.line) { |
|
self.recordFinalOutcome(contract.NoTestFiles) |
|
|
|
} else if noTestFunctions(self.line) { |
|
self.recordFinalOutcome(contract.NoTestFunctions) |
|
|
|
} else { |
|
return false |
|
} |
|
return true |
|
} |
|
|
|
func (self *outputParser) recordFinalOutcome(outcome string) { |
|
self.result.Outcome = outcome |
|
self.result.BuildOutput = strings.Join(self.lines, "\n") |
|
} |
|
|
|
func (self *outputParser) processTestOutput() { |
|
self.line = strings.TrimSpace(self.line) |
|
if isNewTest(self.line) { |
|
self.registerTestFunction() |
|
|
|
} else if isTestResult(self.line) { |
|
self.recordTestMetadata() |
|
|
|
} else if isPackageReport(self.line) { |
|
self.recordPackageMetadata() |
|
|
|
} else { |
|
self.saveLineForParsingLater() |
|
|
|
} |
|
} |
|
|
|
func (self *outputParser) registerTestFunction() { |
|
testNameReg := testNamePattern.FindStringSubmatch(self.line) |
|
if len(testNameReg) < 2 { // Test-related lines that aren't about a new test |
|
return |
|
} |
|
self.test = contract.NewTestResult(testNameReg[1]) |
|
self.tests = append(self.tests, self.test) |
|
self.testMap[self.test.TestName] = self.test |
|
} |
|
func (self *outputParser) recordTestMetadata() { |
|
testName := strings.Split(self.line, " ")[2] |
|
if test, ok := self.testMap[testName]; ok { |
|
self.test = test |
|
self.test.Passed = !strings.HasPrefix(self.line, "--- FAIL: ") |
|
self.test.Skipped = strings.HasPrefix(self.line, "--- SKIP: ") |
|
self.test.Elapsed = parseTestFunctionDuration(self.line) |
|
} |
|
} |
|
func (self *outputParser) recordPackageMetadata() { |
|
if packageFailed(self.line) { |
|
self.recordTestingOutcome(contract.Failed) |
|
|
|
} else if packagePassed(self.line) { |
|
self.recordTestingOutcome(contract.Passed) |
|
|
|
} else if isCoverageSummary(self.line) { |
|
self.recordCoverageSummary(self.line) |
|
} |
|
} |
|
func (self *outputParser) recordTestingOutcome(outcome string) { |
|
self.result.Outcome = outcome |
|
fields := strings.Split(self.line, "\t") |
|
self.result.PackageName = strings.TrimSpace(fields[1]) |
|
self.result.Elapsed = parseDurationInSeconds(fields[2], 3) |
|
} |
|
func (self *outputParser) recordCoverageSummary(summary string) { |
|
start := len("coverage: ") |
|
end := strings.Index(summary, "%") |
|
value := summary[start:end] |
|
parsed, err := strconv.ParseFloat(value, 64) |
|
if err != nil { |
|
self.result.Coverage = -1 |
|
} else { |
|
self.result.Coverage = parsed |
|
} |
|
} |
|
func (self *outputParser) saveLineForParsingLater() { |
|
self.line = strings.TrimLeft(self.line, "\t") |
|
if self.test == nil { |
|
fmt.Println("Potential error parsing output of", self.result.PackageName, "; couldn't handle this stray line:", self.line) |
|
return |
|
} |
|
self.test.RawLines = append(self.test.RawLines, self.line) |
|
} |
|
|
|
// TestResults is a collection of TestResults that implements sort.Interface. |
|
type TestResults []contract.TestResult |
|
|
|
func (r TestResults) Len() int { |
|
return len(r) |
|
} |
|
|
|
// Less compares TestResults on TestName |
|
func (r TestResults) Less(i, j int) bool { |
|
return r[i].TestName < r[j].TestName |
|
} |
|
|
|
func (r TestResults) Swap(i, j int) { |
|
r[i], r[j] = r[j], r[i] |
|
} |
|
|
|
func (self *outputParser) parseEachTestFunction() { |
|
for _, self.test = range self.tests { |
|
self.test = parseTestOutput(self.test) |
|
if self.test.Error != "" { |
|
self.result.Outcome = contract.Panicked |
|
} |
|
self.test.RawLines = []string{} |
|
self.result.TestResults = append(self.result.TestResults, *self.test) |
|
} |
|
sort.Sort(TestResults(self.result.TestResults)) |
|
}
|
|
|