forked from openshift/origin
-
Notifications
You must be signed in to change notification settings - Fork 1
/
test_suites_builder.go
185 lines (153 loc) · 5.85 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
182
183
184
185
package nested
import (
"sort"
"strings"
"github.com/openshift/origin/tools/junitreport/pkg/api"
"github.com/openshift/origin/tools/junitreport/pkg/builder"
)
// 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 {
restrictedRoots := []*treeNode{}
nodes := map[string]*treeNode{}
for _, name := range rootSuiteNames {
root := &treeNode{suite: &api.TestSuite{Name: name}}
restrictedRoots = append(restrictedRoots, root)
nodes[name] = root
}
return &nestedTestSuitesBuilder{
restrictedRoots: restrictedRoots,
nodes: nodes,
}
}
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 is the original set of roots created by the constructor, and is populated only
// if the builder is not able to add new roots to the tree, instead needing to add all nodes as
// children of these restricted roots
restrictedRoots []*treeNode
nodes map[string]*treeNode
}
type treeNode struct {
// suite is the test suite in this node
suite *api.TestSuite
// children are child nodes in the tree. this field will remain empty until the tree is built
children []*treeNode
// parent is the parent of this node. this field can be null
parent *treeNode
}
// AddSuite adds a suite, encapsulated in a treeNode, to the list of suites that this builder cares about.
// If a suite already exists with the same name as that which is being added, the existing record is over-
// written. 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) {
if !allowedToCreate(suite.Name, b.restrictedRoots) {
return
}
oldVersion, exists := b.nodes[suite.Name]
if exists {
oldVersion.suite = suite
return
}
b.nodes[suite.Name] = &treeNode{suite: suite}
}
// allowedToCreate determines if the given name is allowed to be created in light of the restricted roots
func allowedToCreate(name string, restrictedRoots []*treeNode) bool {
if len(restrictedRoots) == 0 {
return true
}
for _, root := range restrictedRoots {
if strings.HasPrefix(name, root.suite.Name) {
return true
}
}
return false
}
// Build builds an api.TestSuites from the list of nodes that is contained in the builder.
func (b *nestedTestSuitesBuilder) Build() *api.TestSuites {
// build a tree from our list of nodes
nodesToAdd := []*treeNode{}
for _, node := range b.nodes {
// make a copy of which nodes we're interested in, otherwise we'll be concurrently modifying b.nodes
nodesToAdd = append(nodesToAdd, node)
}
for _, node := range nodesToAdd {
parentNode, exists := b.nodes[getParentName(node.suite.Name)]
if !exists {
makeParentsFor(node, b.nodes, b.restrictedRoots)
continue
}
parentNode.children = append(parentNode.children, node)
node.parent = parentNode
}
// find the tree's roots
roots := []*treeNode{}
for _, node := range b.nodes {
if node.parent == nil {
roots = append(roots, node)
}
}
// update all metrics inside of test suites so they encompass those of their children
rootSuites := []*api.TestSuite{}
for _, root := range roots {
updateMetrics(root)
rootSuites = append(rootSuites, root.suite)
}
// we need to sort our children so that we can ensure reproducible output for testing
sort.Sort(api.ByName(rootSuites))
return &api.TestSuites{Suites: rootSuites}
}
// makeParentsFor recursively creates parents for the child node until a parent is created that doesn't
// contain the delimiter in its name or a restricted root is reached.
func makeParentsFor(child *treeNode, register map[string]*treeNode, restrictedRoots []*treeNode) {
parentName := getParentName(child.suite.Name)
if parentName == "" {
// if there is no parent for this child, we give up
return
}
if parentNode, exists := register[parentName]; exists {
// if the parent we're trying to add already exists, just use it
parentNode.children = append(parentNode.children, child)
child.parent = parentNode
return
}
if !allowedToCreate(parentName, restrictedRoots) {
// if the parent we're trying to create doesn't exist but we can't make it, give up
return
}
parentNode := &treeNode{
suite: &api.TestSuite{Name: parentName},
children: []*treeNode{child},
}
child.parent = parentNode
register[parentName] = parentNode
makeParentsFor(parentNode, register, restrictedRoots)
}
// getParentName returns the name of the parent package, regardless of 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]
}
// updateMetrics recursively updates all fields in a treeNode's TestSuite
func updateMetrics(root *treeNode) {
for _, child := range root.children {
updateMetrics(child)
// we should be building a tree, so updates on children are independent and we can bring
// in the updated data for this child right away
root.suite.NumTests += child.suite.NumTests
root.suite.NumSkipped += child.suite.NumSkipped
root.suite.NumFailed += child.suite.NumFailed
root.suite.Duration += child.suite.Duration
root.suite.Children = append(root.suite.Children, child.suite)
}
// we need to sort our children so that we can ensure reproducible output for testing
sort.Sort(api.ByName(root.suite.Children))
}