| ... | ... |
@@ -47,3 +47,18 @@ func (t *TestSuite) SetDuration(duration string) error {
|
| 47 | 47 |
t.Duration = float64(int(parsedDuration.Seconds()*1000)) / 1000 |
| 48 | 48 |
return nil |
| 49 | 49 |
} |
| 50 |
+ |
|
| 51 |
+// ByName implements sort.Interface for []*TestSuite based on the Name field |
|
| 52 |
+type ByName []*TestSuite |
|
| 53 |
+ |
|
| 54 |
+func (n ByName) Len() int {
|
|
| 55 |
+ return len(n) |
|
| 56 |
+} |
|
| 57 |
+ |
|
| 58 |
+func (n ByName) Swap(i, j int) {
|
|
| 59 |
+ n[i], n[j] = n[j], n[i] |
|
| 60 |
+} |
|
| 61 |
+ |
|
| 62 |
+func (n ByName) Less(i, j int) bool {
|
|
| 63 |
+ return n[i].Name < n[j].Name |
|
| 64 |
+} |
| ... | ... |
@@ -19,9 +19,8 @@ type flatTestSuitesBuilder struct {
|
| 19 | 19 |
} |
| 20 | 20 |
|
| 21 | 21 |
// AddSuite adds a test suite to the test suites collection being built |
| 22 |
-func (b *flatTestSuitesBuilder) AddSuite(suite *api.TestSuite) error {
|
|
| 22 |
+func (b *flatTestSuitesBuilder) AddSuite(suite *api.TestSuite) {
|
|
| 23 | 23 |
b.testSuites.Suites = append(b.testSuites.Suites, suite) |
| 24 |
- return nil |
|
| 25 | 24 |
} |
| 26 | 25 |
|
| 27 | 26 |
// Build releases the test suites collection being built at whatever current state it is in |
| ... | ... |
@@ -63,9 +63,7 @@ func TestAddSuite(t *testing.T) {
|
| 63 | 63 |
} |
| 64 | 64 |
|
| 65 | 65 |
for _, suite := range testCase.suitesToAdd {
|
| 66 |
- if err := builder.AddSuite(suite); err != nil {
|
|
| 67 |
- t.Errorf("%s: unexpected error adding test suite: %v", testCase.name, err)
|
|
| 68 |
- } |
|
| 66 |
+ builder.AddSuite(suite) |
|
| 69 | 67 |
} |
| 70 | 68 |
|
| 71 | 69 |
if expected, actual := testCase.expectedSuites, builder.Build(); !reflect.DeepEqual(expected, actual) {
|
| ... | ... |
@@ -5,7 +5,7 @@ import "github.com/openshift/origin/tools/junitreport/pkg/api" |
| 5 | 5 |
// TestSuitesBuilder knows how to aggregate data to form a collection of test suites. |
| 6 | 6 |
type TestSuitesBuilder interface {
|
| 7 | 7 |
// AddSuite adds a test suite to the collection |
| 8 |
- AddSuite(suite *api.TestSuite) error |
|
| 8 |
+ AddSuite(suite *api.TestSuite) |
|
| 9 | 9 |
|
| 10 | 10 |
// Build retuns the built structure |
| 11 | 11 |
Build() *api.TestSuites |
| ... | ... |
@@ -1,26 +1,27 @@ |
| 1 | 1 |
package nested |
| 2 | 2 |
|
| 3 | 3 |
import ( |
| 4 |
+ "sort" |
|
| 4 | 5 |
"strings" |
| 5 | 6 |
|
| 6 | 7 |
"github.com/openshift/origin/tools/junitreport/pkg/api" |
| 7 | 8 |
"github.com/openshift/origin/tools/junitreport/pkg/builder" |
| 8 |
- "github.com/openshift/origin/tools/junitreport/pkg/errors" |
|
| 9 | 9 |
) |
| 10 | 10 |
|
| 11 | 11 |
// NewTestSuitesBuilder returns a new nested test suites builder. All test suites consumed by |
| 12 | 12 |
// this builder will be added to a multitree of suites rooted at the suites with the given names. |
| 13 | 13 |
func NewTestSuitesBuilder(rootSuiteNames []string) builder.TestSuitesBuilder {
|
| 14 |
- rootSuites := []*api.TestSuite{}
|
|
| 14 |
+ restrictedRoots := []*treeNode{}
|
|
| 15 |
+ nodes := map[string]*treeNode{}
|
|
| 15 | 16 |
for _, name := range rootSuiteNames {
|
| 16 |
- rootSuites = append(rootSuites, &api.TestSuite{Name: name})
|
|
| 17 |
+ root := &treeNode{suite: &api.TestSuite{Name: name}}
|
|
| 18 |
+ restrictedRoots = append(restrictedRoots, root) |
|
| 19 |
+ nodes[name] = root |
|
| 17 | 20 |
} |
| 18 | 21 |
|
| 19 | 22 |
return &nestedTestSuitesBuilder{
|
| 20 |
- restrictedRoots: len(rootSuites) > 0, // i given they are the only roots allowed |
|
| 21 |
- testSuites: &api.TestSuites{
|
|
| 22 |
- Suites: rootSuites, |
|
| 23 |
- }, |
|
| 23 |
+ restrictedRoots: restrictedRoots, |
|
| 24 |
+ nodes: nodes, |
|
| 24 | 25 |
} |
| 25 | 26 |
} |
| 26 | 27 |
|
| ... | ... |
@@ -31,151 +32,154 @@ const ( |
| 31 | 31 |
|
| 32 | 32 |
// nestedTestSuitesBuilder is a test suites builder that nests suites under a root suite |
| 33 | 33 |
type nestedTestSuitesBuilder struct {
|
| 34 |
- // restrictedRoots determines if the builder is able to add new roots to the tree or if all |
|
| 35 |
- // new suits are to be added only if they are leaves of the original set of roots created |
|
| 36 |
- // by the constructor |
|
| 37 |
- restrictedRoots bool |
|
| 34 |
+ // restrictedRoots is the original set of roots created by the constructor, and is populated only |
|
| 35 |
+ // if the builder is not able to add new roots to the tree, instead needing to add all nodes as |
|
| 36 |
+ // children of these restricted roots |
|
| 37 |
+ restrictedRoots []*treeNode |
|
| 38 | 38 |
|
| 39 |
- testSuites *api.TestSuites |
|
| 39 |
+ nodes map[string]*treeNode |
|
| 40 | 40 |
} |
| 41 | 41 |
|
| 42 |
-// AddSuite adds a test suite to the test suites collection being built if the suite is not in |
|
| 43 |
-// the collection, otherwise it overwrites the current record of the suite in the collection. In |
|
| 44 |
-// both cases, it updates the metrics of any parent suites to include those of the new suite. If |
|
| 45 |
-// the parent of the test suite to be added is found in the collection, the test suite is added |
|
| 46 |
-// as a child of that suite. Otherwise, parent suites are created by successively removing one |
|
| 47 |
-// layer of package specificity until the root name is found. For instance, if the suite named |
|
| 48 |
-// "root/package/subpackage/subsubpackage" were to be added to an empty collection, the suites |
|
| 49 |
-// named "root", "root/package", and "root/package/subpackage" would be created and added first, |
|
| 50 |
-// then the suite could be added as a child of the latter parent package. If roots are restricted, |
|
| 51 |
-// then test suites to be added are asssumed to be nested under one of the root suites created by |
|
| 52 |
-// the constructor method and the attempted addition of a suite not rooted in those suites will |
|
| 53 |
-// fail silently to allow for selective tree-building given a root. |
|
| 54 |
-func (b *nestedTestSuitesBuilder) AddSuite(suite *api.TestSuite) error {
|
|
| 55 |
- if recordedSuite := b.findSuite(suite.Name); recordedSuite != nil {
|
|
| 56 |
- // if we are trying to add a suite that already exists, we just need to overwrite our |
|
| 57 |
- // current record with the data in the new suite to be added |
|
| 58 |
- recordedSuite.NumTests = suite.NumTests |
|
| 59 |
- recordedSuite.NumSkipped = suite.NumSkipped |
|
| 60 |
- recordedSuite.NumFailed = suite.NumFailed |
|
| 61 |
- recordedSuite.Duration = suite.Duration |
|
| 62 |
- recordedSuite.Properties = suite.Properties |
|
| 63 |
- recordedSuite.TestCases = suite.TestCases |
|
| 64 |
- recordedSuite.Children = suite.Children |
|
| 65 |
- return nil |
|
| 66 |
- } |
|
| 67 |
- |
|
| 68 |
- if err := b.addToParent(suite); err != nil {
|
|
| 69 |
- if errors.IsSuiteOutOfBoundsError(err) {
|
|
| 70 |
- // if we were trying to add something out of bounds, we ignore the request but do not |
|
| 71 |
- // throw an error so we can selectively build sub-trees with a set of specified roots |
|
| 72 |
- return nil |
|
| 73 |
- } |
|
| 74 |
- return err |
|
| 75 |
- } |
|
| 42 |
+type treeNode struct {
|
|
| 43 |
+ // suite is the test suite in this node |
|
| 44 |
+ suite *api.TestSuite |
|
| 76 | 45 |
|
| 77 |
- b.updateMetrics(suite) |
|
| 46 |
+ // children are child nodes in the tree. this field will remain empty until the tree is built |
|
| 47 |
+ children []*treeNode |
|
| 78 | 48 |
|
| 79 |
- return nil |
|
| 49 |
+ // parent is the parent of this node. this field can be null |
|
| 50 |
+ parent *treeNode |
|
| 80 | 51 |
} |
| 81 | 52 |
|
| 82 |
-// addToParent will find or create the parent for the test suite and add the given suite as a child |
|
| 83 |
-func (b *nestedTestSuitesBuilder) addToParent(child *api.TestSuite) error {
|
|
| 84 |
- name := child.Name |
|
| 85 |
- if !b.isChildOfRoots(name) && b.restrictedRoots {
|
|
| 86 |
- // if we were asked to add a new test suite that isn't a child of any current root, |
|
| 87 |
- // and we aren't allowed to add new roots, we can't fulfill this request |
|
| 88 |
- return errors.NewSuiteOutOfBoundsError(name) |
|
| 53 |
+// AddSuite adds a suite, encapsulated in a treeNode, to the list of suites that this builder cares about. |
|
| 54 |
+// If a suite already exists with the same name as that which is being added, the existing record is over- |
|
| 55 |
+// written. If roots are restricted, then test suites to be added are asssumed to be nested under one of |
|
| 56 |
+// the root suites created by the constructor method and the attempted addition of a suite not rooted in |
|
| 57 |
+// those suites will fail silently to allow for selective tree-building given a root. |
|
| 58 |
+func (b *nestedTestSuitesBuilder) AddSuite(suite *api.TestSuite) {
|
|
| 59 |
+ if !allowedToCreate(suite.Name, b.restrictedRoots) {
|
|
| 60 |
+ return |
|
| 89 | 61 |
} |
| 90 | 62 |
|
| 91 |
- parentName := getParentName(name) |
|
| 92 |
- if len(parentName) == 0 {
|
|
| 93 |
- // this suite does not have a parent, we just need to add it as a root |
|
| 94 |
- b.testSuites.Suites = append(b.testSuites.Suites, child) |
|
| 95 |
- return nil |
|
| 63 |
+ oldVersion, exists := b.nodes[suite.Name] |
|
| 64 |
+ if exists {
|
|
| 65 |
+ oldVersion.suite = suite |
|
| 66 |
+ return |
|
| 96 | 67 |
} |
| 97 | 68 |
|
| 98 |
- parent := b.findSuite(parentName) |
|
| 99 |
- if parent == nil {
|
|
| 100 |
- // no parent is currently registered, we need to create it and add it to the tree |
|
| 101 |
- parent = &api.TestSuite{
|
|
| 102 |
- Name: parentName, |
|
| 103 |
- Children: []*api.TestSuite{child},
|
|
| 104 |
- } |
|
| 105 |
- |
|
| 106 |
- return b.addToParent(parent) |
|
| 107 |
- } |
|
| 108 |
- |
|
| 109 |
- parent.Children = append(parent.Children, child) |
|
| 110 |
- return nil |
|
| 69 |
+ b.nodes[suite.Name] = &treeNode{suite: suite}
|
|
| 111 | 70 |
} |
| 112 | 71 |
|
| 113 |
-// getParentName returns the name of the parent package, if it exists in the multitree |
|
| 114 |
-func getParentName(name string) string {
|
|
| 115 |
- if !strings.Contains(name, TestSuiteNameDelimiter) {
|
|
| 116 |
- return "" |
|
| 72 |
+// allowedToCreate determines if the given name is allowed to be created in light of the restricted roots |
|
| 73 |
+func allowedToCreate(name string, restrictedRoots []*treeNode) bool {
|
|
| 74 |
+ if len(restrictedRoots) == 0 {
|
|
| 75 |
+ return true |
|
| 117 | 76 |
} |
| 118 | 77 |
|
| 119 |
- delimeterIndex := strings.LastIndex(name, TestSuiteNameDelimiter) |
|
| 120 |
- return name[0:delimeterIndex] |
|
| 121 |
-} |
|
| 122 |
- |
|
| 123 |
-func (b *nestedTestSuitesBuilder) isChildOfRoots(name string) bool {
|
|
| 124 |
- for _, rootSuite := range b.testSuites.Suites {
|
|
| 125 |
- if strings.HasPrefix(name, rootSuite.Name) {
|
|
| 78 |
+ for _, root := range restrictedRoots {
|
|
| 79 |
+ if strings.HasPrefix(name, root.suite.Name) {
|
|
| 126 | 80 |
return true |
| 127 | 81 |
} |
| 128 | 82 |
} |
| 83 |
+ |
|
| 129 | 84 |
return false |
| 130 | 85 |
} |
| 131 | 86 |
|
| 132 |
-// findSuite finds a test suite in a collection of test suites |
|
| 133 |
-func (b *nestedTestSuitesBuilder) findSuite(name string) *api.TestSuite {
|
|
| 134 |
- return findSuite(b.testSuites.Suites, name) |
|
| 135 |
-} |
|
| 87 |
+// Build builds an api.TestSuites from the list of nodes that is contained in the builder. |
|
| 88 |
+func (b *nestedTestSuitesBuilder) Build() *api.TestSuites {
|
|
| 89 |
+ // build a tree from our list of nodes |
|
| 90 |
+ nodesToAdd := []*treeNode{}
|
|
| 91 |
+ for _, node := range b.nodes {
|
|
| 92 |
+ // make a copy of which nodes we're interested in, otherwise we'll be concurrently modifying b.nodes |
|
| 93 |
+ nodesToAdd = append(nodesToAdd, node) |
|
| 94 |
+ } |
|
| 136 | 95 |
|
| 137 |
-// findSuite walks a test suite tree to find a test suite with the given name |
|
| 138 |
-func findSuite(suites []*api.TestSuite, name string) *api.TestSuite {
|
|
| 139 |
- for _, suite := range suites {
|
|
| 140 |
- if suite.Name == name {
|
|
| 141 |
- return suite |
|
| 96 |
+ for _, node := range nodesToAdd {
|
|
| 97 |
+ parentNode, exists := b.nodes[getParentName(node.suite.Name)] |
|
| 98 |
+ if !exists {
|
|
| 99 |
+ makeParentsFor(node, b.nodes, b.restrictedRoots) |
|
| 100 |
+ continue |
|
| 142 | 101 |
} |
| 143 | 102 |
|
| 144 |
- if strings.HasPrefix(name, suite.Name) {
|
|
| 145 |
- return findSuite(suite.Children, name) |
|
| 103 |
+ parentNode.children = append(parentNode.children, node) |
|
| 104 |
+ node.parent = parentNode |
|
| 105 |
+ } |
|
| 106 |
+ |
|
| 107 |
+ // find the tree's roots |
|
| 108 |
+ roots := []*treeNode{}
|
|
| 109 |
+ for _, node := range b.nodes {
|
|
| 110 |
+ if node.parent == nil {
|
|
| 111 |
+ roots = append(roots, node) |
|
| 146 | 112 |
} |
| 147 | 113 |
} |
| 148 | 114 |
|
| 149 |
- return nil |
|
| 150 |
-} |
|
| 115 |
+ // update all metrics inside of test suites so they encompass those of their children |
|
| 116 |
+ rootSuites := []*api.TestSuite{}
|
|
| 117 |
+ for _, root := range roots {
|
|
| 118 |
+ updateMetrics(root) |
|
| 119 |
+ rootSuites = append(rootSuites, root.suite) |
|
| 120 |
+ } |
|
| 121 |
+ |
|
| 122 |
+ // we need to sort our children so that we can ensure reproducible output for testing |
|
| 123 |
+ sort.Sort(api.ByName(rootSuites)) |
|
| 151 | 124 |
|
| 152 |
-// updateMetrics updates the metrics for all parents of a test suite |
|
| 153 |
-func (b *nestedTestSuitesBuilder) updateMetrics(newSuite *api.TestSuite) {
|
|
| 154 |
- updateMetrics(b.testSuites.Suites, newSuite) |
|
| 125 |
+ return &api.TestSuites{Suites: rootSuites}
|
|
| 155 | 126 |
} |
| 156 | 127 |
|
| 157 |
-// updateMetrics walks a test suite tree to update metrics of parents of the given suite |
|
| 158 |
-func updateMetrics(suites []*api.TestSuite, newSuite *api.TestSuite) {
|
|
| 159 |
- for _, suite := range suites {
|
|
| 160 |
- if suite.Name == newSuite.Name || !strings.HasPrefix(newSuite.Name, suite.Name) {
|
|
| 161 |
- // if we're considering the suite itself or another suite that is not a pure |
|
| 162 |
- // prefix of the new suite, we are not considering a parent suite and therefore |
|
| 163 |
- // do not need to update any metrics |
|
| 164 |
- continue |
|
| 165 |
- } |
|
| 128 |
+// makeParentsFor recursively creates parents for the child node until a parent is created that doesn't |
|
| 129 |
+// contain the delimiter in its name or a restricted root is reached. |
|
| 130 |
+func makeParentsFor(child *treeNode, register map[string]*treeNode, restrictedRoots []*treeNode) {
|
|
| 131 |
+ parentName := getParentName(child.suite.Name) |
|
| 132 |
+ if parentName == "" {
|
|
| 133 |
+ // if there is no parent for this child, we give up |
|
| 134 |
+ return |
|
| 135 |
+ } |
|
| 166 | 136 |
|
| 167 |
- suite.NumTests += newSuite.NumTests |
|
| 168 |
- suite.NumSkipped += newSuite.NumSkipped |
|
| 169 |
- suite.NumFailed += newSuite.NumFailed |
|
| 170 |
- suite.Duration += newSuite.Duration |
|
| 171 |
- // we round to the millisecond on duration |
|
| 172 |
- suite.Duration = float64(int(suite.Duration*1000)) / 1000 |
|
| 137 |
+ if parentNode, exists := register[parentName]; exists {
|
|
| 138 |
+ // if the parent we're trying to add already exists, just use it |
|
| 139 |
+ parentNode.children = append(parentNode.children, child) |
|
| 140 |
+ child.parent = parentNode |
|
| 141 |
+ return |
|
| 142 |
+ } |
|
| 143 |
+ |
|
| 144 |
+ if !allowedToCreate(parentName, restrictedRoots) {
|
|
| 145 |
+ // if the parent we're trying to create doesn't exist but we can't make it, give up |
|
| 146 |
+ return |
|
| 147 |
+ } |
|
| 173 | 148 |
|
| 174 |
- updateMetrics(suite.Children, newSuite) |
|
| 149 |
+ parentNode := &treeNode{
|
|
| 150 |
+ suite: &api.TestSuite{Name: parentName},
|
|
| 151 |
+ children: []*treeNode{child},
|
|
| 175 | 152 |
} |
| 153 |
+ child.parent = parentNode |
|
| 154 |
+ register[parentName] = parentNode |
|
| 155 |
+ |
|
| 156 |
+ makeParentsFor(parentNode, register, restrictedRoots) |
|
| 176 | 157 |
} |
| 177 | 158 |
|
| 178 |
-// Build releases the test suites collection being built at whatever current state it is in |
|
| 179 |
-func (b *nestedTestSuitesBuilder) Build() *api.TestSuites {
|
|
| 180 |
- return b.testSuites |
|
| 159 |
+// getParentName returns the name of the parent package, regardless of if it exists in the multitree |
|
| 160 |
+func getParentName(name string) string {
|
|
| 161 |
+ if !strings.Contains(name, TestSuiteNameDelimiter) {
|
|
| 162 |
+ return "" |
|
| 163 |
+ } |
|
| 164 |
+ |
|
| 165 |
+ delimeterIndex := strings.LastIndex(name, TestSuiteNameDelimiter) |
|
| 166 |
+ return name[0:delimeterIndex] |
|
| 167 |
+} |
|
| 168 |
+ |
|
| 169 |
+// updateMetrics recursively updates all fields in a treeNode's TestSuite |
|
| 170 |
+func updateMetrics(root *treeNode) {
|
|
| 171 |
+ for _, child := range root.children {
|
|
| 172 |
+ updateMetrics(child) |
|
| 173 |
+ // we should be building a tree, so updates on children are independent and we can bring |
|
| 174 |
+ // in the updated data for this child right away |
|
| 175 |
+ root.suite.NumTests += child.suite.NumTests |
|
| 176 |
+ root.suite.NumSkipped += child.suite.NumSkipped |
|
| 177 |
+ root.suite.NumFailed += child.suite.NumFailed |
|
| 178 |
+ root.suite.Duration += child.suite.Duration |
|
| 179 |
+ root.suite.Children = append(root.suite.Children, child.suite) |
|
| 180 |
+ } |
|
| 181 |
+ |
|
| 182 |
+ // we need to sort our children so that we can ensure reproducible output for testing |
|
| 183 |
+ sort.Sort(api.ByName(root.suite.Children)) |
|
| 181 | 184 |
} |
| ... | ... |
@@ -41,7 +41,7 @@ func TestAddSuite(t *testing.T) {
|
| 41 | 41 |
var testCases = []struct {
|
| 42 | 42 |
name string |
| 43 | 43 |
rootSuiteNames []string |
| 44 |
- seedSuites *api.TestSuites |
|
| 44 |
+ seedSuites map[string]*treeNode |
|
| 45 | 45 |
suiteToAdd *api.TestSuite |
| 46 | 46 |
expectedSuites *api.TestSuites |
| 47 | 47 |
}{
|
| ... | ... |
@@ -92,9 +92,9 @@ func TestAddSuite(t *testing.T) {
|
| 92 | 92 |
}, |
| 93 | 93 |
{
|
| 94 | 94 |
name: "populated adding child", |
| 95 |
- seedSuites: &api.TestSuites{
|
|
| 96 |
- Suites: []*api.TestSuite{
|
|
| 97 |
- {
|
|
| 95 |
+ seedSuites: map[string]*treeNode{
|
|
| 96 |
+ "root": {
|
|
| 97 |
+ suite: &api.TestSuite{
|
|
| 98 | 98 |
Name: "root", |
| 99 | 99 |
}, |
| 100 | 100 |
}, |
| ... | ... |
@@ -141,9 +141,9 @@ func TestAddSuite(t *testing.T) {
|
| 141 | 141 |
}, |
| 142 | 142 |
{
|
| 143 | 143 |
name: "populated overwriting record", |
| 144 |
- seedSuites: &api.TestSuites{
|
|
| 145 |
- Suites: []*api.TestSuite{
|
|
| 146 |
- {
|
|
| 144 |
+ seedSuites: map[string]*treeNode{
|
|
| 145 |
+ "root": {
|
|
| 146 |
+ suite: &api.TestSuite{
|
|
| 147 | 147 |
Name: "root", |
| 148 | 148 |
NumTests: 3, |
| 149 | 149 |
}, |
| ... | ... |
@@ -168,12 +168,10 @@ func TestAddSuite(t *testing.T) {
|
| 168 | 168 |
builder := NewTestSuitesBuilder(testCase.rootSuiteNames) |
| 169 | 169 |
|
| 170 | 170 |
if testCase.seedSuites != nil {
|
| 171 |
- builder.(*nestedTestSuitesBuilder).testSuites = testCase.seedSuites |
|
| 171 |
+ builder.(*nestedTestSuitesBuilder).nodes = testCase.seedSuites |
|
| 172 | 172 |
} |
| 173 | 173 |
|
| 174 |
- if err := builder.AddSuite(testCase.suiteToAdd); err != nil {
|
|
| 175 |
- t.Errorf("%s: unexpected error adding suite to suites: %v", testCase.name, err)
|
|
| 176 |
- } |
|
| 174 |
+ builder.AddSuite(testCase.suiteToAdd) |
|
| 177 | 175 |
|
| 178 | 176 |
if actual, expected := builder.Build(), testCase.expectedSuites; !reflect.DeepEqual(actual, expected) {
|
| 179 | 177 |
t.Errorf("%s: did not get correct test suites after addition of test suite:\n\texpected:\n\t%s,\n\tgot\n\t%s", testCase.name, expected, actual)
|