Esempio n. 1
0
// 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
}
Esempio n. 2
0
// 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
}