// Parse parses `go test -v` output into test suites. Test output from `go test -v` is not bookmarked for packages, so // the parsing strategy is to advance line-by-line, building up a slice of test cases until a package declaration is found, // at which point all tests cases are added to that package and the process can start again. func (p *testOutputParser) Parse(input *bufio.Scanner) (*api.TestSuites, error) { currentSuite := &api.TestSuite{} var currentTest *api.TestCase var currentTestResult api.TestResult var currentTestOutput []string for input.Scan() { line := input.Text() isTestOutput := true if p.testParser.MarksBeginning(line) || p.suiteParser.MarksCompletion(line) { if currentTest != nil { // we can't mark the test as failed or skipped until we have all of the test output, which we don't know // we have until we see the next test or the beginning of suite output, so we add it here output := strings.Join(currentTestOutput, "\n") switch currentTestResult { case api.TestResultSkip: currentTest.MarkSkipped(output) case api.TestResultFail: currentTest.MarkFailed("", output) } currentSuite.AddTestCase(currentTest) } currentTest = &api.TestCase{} currentTestResult = api.TestResultFail currentTestOutput = []string{} } if name, matched := p.testParser.ExtractName(line); matched { currentTest.Name = name } if result, matched := p.testParser.ExtractResult(line); matched { currentTestResult = result } if duration, matched := p.testParser.ExtractDuration(line); matched { if err := currentTest.SetDuration(duration); err != nil { return nil, err } } if properties, matched := p.suiteParser.ExtractProperties(line); matched { for name := range properties { currentSuite.AddProperty(name, properties[name]) } isTestOutput = false } if name, matched := p.suiteParser.ExtractName(line); matched { currentSuite.Name = name isTestOutput = false } if duration, matched := p.suiteParser.ExtractDuration(line); matched { if err := currentSuite.SetDuration(duration); err != nil { return nil, err } } if p.suiteParser.MarksCompletion(line) { if p.stream { fmt.Fprintln(os.Stdout, line) } p.builder.AddSuite(currentSuite) currentSuite = &api.TestSuite{} currentTest = nil isTestOutput = false } // we want to associate any line not directly related to a test suite with a test case to ensure we capture all output if isTestOutput { currentTestOutput = append(currentTestOutput, line) } } return p.builder.Build(), nil }
// Parse parses output syntax of a specific class, the assumptions of which are outlined in the struct definition. // The specific boundary markers and metadata encodings are free to vary as long as regex can be build to extract them // from test output. func (p *testOutputParser) Parse(input *bufio.Scanner) (*api.TestSuites, error) { inProgress := NewTestSuiteStack() var currentTest *api.TestCase var currentResult api.TestResult var currentOutput []string var currentMessage string for input.Scan() { line := input.Text() isTestOutput := true if p.testParser.MarksBeginning(line) { currentTest = &api.TestCase{} currentResult = api.TestResultFail currentOutput = []string{} currentMessage = "" } if name, contained := p.testParser.ExtractName(line); contained { currentTest.Name = name } if result, contained := p.testParser.ExtractResult(line); contained { currentResult = result } if duration, contained := p.testParser.ExtractDuration(line); contained { if err := currentTest.SetDuration(duration); err != nil { return nil, err } } if message, contained := p.testParser.ExtractMessage(line); contained { currentMessage = message } if p.testParser.MarksCompletion(line) { // if we have finished the current test case, we finalize our current test, add it to the package // at the head of our in progress package stack, and clear our current test record. output := strings.Join(currentOutput, "\n") switch currentResult { case api.TestResultSkip: currentTest.MarkSkipped(currentMessage) case api.TestResultFail: currentTest.MarkFailed(currentMessage, output) } inProgress.Peek().AddTestCase(currentTest) currentTest = &api.TestCase{} } if p.suiteParser.MarksBeginning(line) { // if we encounter the beginning of a suite, we create a new suite to be considered and // add it to the head of our in progress package stack inProgress.Push(&api.TestSuite{}) isTestOutput = false } if name, contained := p.suiteParser.ExtractName(line); contained { inProgress.Peek().Name = name isTestOutput = false } if properties, contained := p.suiteParser.ExtractProperties(line); contained { for propertyName := range properties { inProgress.Peek().AddProperty(propertyName, properties[propertyName]) } isTestOutput = false } if p.suiteParser.MarksCompletion(line) { // if we encounter the end of a suite, we remove the suite at the head of the in progress stack p.builder.AddSuite(inProgress.Pop()) isTestOutput = false } // we want to associate every line other than those directly involved with test suites as output of a test case if isTestOutput { currentOutput = append(currentOutput, line) } } return p.builder.Build(), nil }