forked from openshift/origin
-
Notifications
You must be signed in to change notification settings - Fork 1
/
parser.go
138 lines (115 loc) · 4.5 KB
/
parser.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
package stack
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, testParser TestDataParser, suiteParser TestSuiteDataParser, stream bool) parser.TestOutputParser {
return &testOutputParser{
builder: builder,
testParser: testParser,
suiteParser: suiteParser,
stream: stream,
}
}
// testOutputParser uses a stack to parse test output. Critical assumptions that this parser makes are:
// 1 - packages may be nested but tests may not
// 2 - no package declarations will occur within the boundaries of a test
// 3 - all tests and packages are fully bounded by a start and result line
// 4 - if a package or test declaration occurs after the start of a package but before its result,
// the sub-package's or member test's result line will occur before that of the parent package
// i.e. any test or package overlap will necessarily mean that one package's lines are a superset
// of any lines of tests or other packages overlapping with it
// 5 - any text in the input file that doesn't match the parser regex is necessarily the output of the
// current test being built
type testOutputParser struct {
builder builder.TestSuitesBuilder
testParser TestDataParser
suiteParser TestSuiteDataParser
stream bool
}
// 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) {
currentOutput = append(currentOutput, 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.
switch currentResult {
case api.TestResultSkip:
currentTest.MarkSkipped(currentMessage)
case api.TestResultFail:
output := strings.Join(currentOutput, "\n")
currentTest.MarkFailed(currentMessage, output)
}
if inProgress.Peek() == nil {
return nil, fmt.Errorf("found test case %q outside of a test suite", currentTest.Name)
}
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 p.stream {
fmt.Fprintln(os.Stdout, 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
}