package gotest import ( "bufio" "fmt" "os" "strings" "github.com/openshift/origin/tools/junitreport/pkg/api" "github.com/openshift/origin/tools/junitreport/pkg/builder" "github.com/openshift/origin/tools/junitreport/pkg/parser" ) // NewParser returns a new parser that's capable of parsing Go unit test output func NewParser(builder builder.TestSuitesBuilder, stream bool) parser.TestOutputParser { return &testOutputParser{ builder: builder, testParser: newTestDataParser(), suiteParser: newTestSuiteDataParser(), stream: stream, } } type testOutputParser struct { builder builder.TestSuitesBuilder testParser testDataParser suiteParser testSuiteDataParser stream bool } // 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 }