forked from openshift/origin
-
Notifications
You must be signed in to change notification settings - Fork 0
/
test_suites_builder.go
181 lines (153 loc) · 6.45 KB
/
test_suites_builder.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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
package nested
import (
"strings"
"github.com/openshift/origin/tools/junitreport/pkg/api"
"github.com/openshift/origin/tools/junitreport/pkg/builder"
"github.com/openshift/origin/tools/junitreport/pkg/errors"
)
// NewTestSuitesBuilder returns a new nested test suites builder. All test suites consumed by
// this builder will be added to a multitree of suites rooted at the suites with the given names.
func NewTestSuitesBuilder(rootSuiteNames []string) builder.TestSuitesBuilder {
rootSuites := []*api.TestSuite{}
for _, name := range rootSuiteNames {
rootSuites = append(rootSuites, &api.TestSuite{Name: name})
}
return &nestedTestSuitesBuilder{
restrictedRoots: len(rootSuites) > 0, // i given they are the only roots allowed
testSuites: &api.TestSuites{
Suites: rootSuites,
},
}
}
const (
// TestSuiteNameDelimiter is the default delimeter for test suite names
TestSuiteNameDelimiter = "/"
)
// nestedTestSuitesBuilder is a test suites builder that nests suites under a root suite
type nestedTestSuitesBuilder struct {
// restrictedRoots determines if the builder is able to add new roots to the tree or if all
// new suits are to be added only if they are leaves of the original set of roots created
// by the constructor
restrictedRoots bool
testSuites *api.TestSuites
}
// AddSuite adds a test suite to the test suites collection being built if the suite is not in
// the collection, otherwise it overwrites the current record of the suite in the collection. In
// both cases, it updates the metrics of any parent suites to include those of the new suite. If
// the parent of the test suite to be added is found in the collection, the test suite is added
// as a child of that suite. Otherwise, parent suites are created by successively removing one
// layer of package specificity until the root name is found. For instance, if the suite named
// "root/package/subpackage/subsubpackage" were to be added to an empty collection, the suites
// named "root", "root/package", and "root/package/subpackage" would be created and added first,
// then the suite could be added as a child of the latter parent package. If roots are restricted,
// then test suites to be added are asssumed to be nested under one of the root suites created by
// the constructor method and the attempted addition of a suite not rooted in those suites will
// fail silently to allow for selective tree-building given a root.
func (b *nestedTestSuitesBuilder) AddSuite(suite *api.TestSuite) error {
if recordedSuite := b.findSuite(suite.Name); recordedSuite != nil {
// if we are trying to add a suite that already exists, we just need to overwrite our
// current record with the data in the new suite to be added
recordedSuite.NumTests = suite.NumTests
recordedSuite.NumSkipped = suite.NumSkipped
recordedSuite.NumFailed = suite.NumFailed
recordedSuite.Duration = suite.Duration
recordedSuite.Properties = suite.Properties
recordedSuite.TestCases = suite.TestCases
recordedSuite.Children = suite.Children
return nil
}
if err := b.addToParent(suite); err != nil {
if errors.IsSuiteOutOfBoundsError(err) {
// if we were trying to add something out of bounds, we ignore the request but do not
// throw an error so we can selectively build sub-trees with a set of specified roots
return nil
}
return err
}
b.updateMetrics(suite)
return nil
}
// addToParent will find or create the parent for the test suite and add the given suite as a child
func (b *nestedTestSuitesBuilder) addToParent(child *api.TestSuite) error {
name := child.Name
if !b.isChildOfRoots(name) && b.restrictedRoots {
// if we were asked to add a new test suite that isn't a child of any current root,
// and we aren't allowed to add new roots, we can't fulfill this request
return errors.NewSuiteOutOfBoundsError(name)
}
parentName := getParentName(name)
if len(parentName) == 0 {
// this suite does not have a parent, we just need to add it as a root
b.testSuites.Suites = append(b.testSuites.Suites, child)
return nil
}
parent := b.findSuite(parentName)
if parent == nil {
// no parent is currently registered, we need to create it and add it to the tree
parent = &api.TestSuite{
Name: parentName,
Children: []*api.TestSuite{child},
}
return b.addToParent(parent)
}
parent.Children = append(parent.Children, child)
return nil
}
// getParentName returns the name of the parent package, if it exists in the multitree
func getParentName(name string) string {
if !strings.Contains(name, TestSuiteNameDelimiter) {
return ""
}
delimeterIndex := strings.LastIndex(name, TestSuiteNameDelimiter)
return name[0:delimeterIndex]
}
func (b *nestedTestSuitesBuilder) isChildOfRoots(name string) bool {
for _, rootSuite := range b.testSuites.Suites {
if strings.HasPrefix(name, rootSuite.Name) {
return true
}
}
return false
}
// findSuite finds a test suite in a collection of test suites
func (b *nestedTestSuitesBuilder) findSuite(name string) *api.TestSuite {
return findSuite(b.testSuites.Suites, name)
}
// findSuite walks a test suite tree to find a test suite with the given name
func findSuite(suites []*api.TestSuite, name string) *api.TestSuite {
for _, suite := range suites {
if suite.Name == name {
return suite
}
if strings.HasPrefix(name, suite.Name) {
return findSuite(suite.Children, name)
}
}
return nil
}
// updateMetrics updates the metrics for all parents of a test suite
func (b *nestedTestSuitesBuilder) updateMetrics(newSuite *api.TestSuite) {
updateMetrics(b.testSuites.Suites, newSuite)
}
// updateMetrics walks a test suite tree to update metrics of parents of the given suite
func updateMetrics(suites []*api.TestSuite, newSuite *api.TestSuite) {
for _, suite := range suites {
if suite.Name == newSuite.Name || !strings.HasPrefix(newSuite.Name, suite.Name) {
// if we're considering the suite itself or another suite that is not a pure
// prefix of the new suite, we are not considering a parent suite and therefore
// do not need to update any metrics
continue
}
suite.NumTests += newSuite.NumTests
suite.NumSkipped += newSuite.NumSkipped
suite.NumFailed += newSuite.NumFailed
suite.Duration += newSuite.Duration
// we round to the millisecond on duration
suite.Duration = float64(int(suite.Duration*1000)) / 1000
updateMetrics(suite.Children, newSuite)
}
}
// Build releases the test suites collection being built at whatever current state it is in
func (b *nestedTestSuitesBuilder) Build() *api.TestSuites {
return b.testSuites
}