1 // Copyright Josh Komoroske. All rights reserved. 2 // Use of this source code is governed by the MIT license, 3 // a copy of which can be found in the LICENSE.txt file. 4 5 package junit 6 7 import ( 8 "strings" 9 "time" 10 ) 11 12 // Status represents the result of a single a JUnit testcase. Indicates if a 13 // testcase was run, and if it was successful. 14 type Status string 15 16 const ( 17 // StatusPassed represents a JUnit testcase that was run, and did not 18 // result in an error or a failure. 19 StatusPassed Status = "passed" 20 21 // StatusSkipped represents a JUnit testcase that was intentionally 22 // skipped. 23 StatusSkipped Status = "skipped" 24 25 // StatusFailed represents a JUnit testcase that was run, but resulted in 26 // a failure. Failures are violations of declared test expectations, 27 // such as a failed assertion. 28 StatusFailed Status = "failed" 29 30 // StatusError represents a JUnit testcase that was run, but resulted in 31 // an error. Errors are unexpected violations of the test itself, such as 32 // an uncaught exception. 33 StatusError Status = "error" 34 ) 35 36 // Totals contains aggregated results across a set of test runs. Is usually 37 // calculated as a sum of all given test runs, and overrides whatever was given 38 // at the suite level. 39 // 40 // The following relation should hold true. 41 // Tests == (Passed + Skipped + Failed + Error) 42 type Totals struct { 43 // Tests is the total number of tests run. 44 Tests int `json:"tests" yaml:"tests"` 45 46 // Passed is the total number of tests that passed successfully. 47 Passed int `json:"passed" yaml:"passed"` 48 49 // Skipped is the total number of tests that were skipped. 50 Skipped int `json:"skipped" yaml:"skipped"` 51 52 // Failed is the total number of tests that resulted in a failure. 53 Failed int `json:"failed" yaml:"failed"` 54 55 // Error is the total number of tests that resulted in an error. 56 Error int `json:"error" yaml:"error"` 57 58 // Duration is the total time taken to run all tests. 59 Duration time.Duration `json:"duration" yaml:"duration"` 60 } 61 62 // Suite represents a logical grouping (suite) of tests. 63 type Suite struct { 64 // Name is a descriptor given to the suite. 65 Name string `json:"name" yaml:"name"` 66 67 // Package is an additional descriptor for the hierarchy of the suite. 68 Package string `json:"package" yaml:"package"` 69 70 // Properties is a mapping of key-value pairs that were available when the 71 // tests were run. 72 Properties map[string]string `json:"properties,omitempty" yaml:"properties,omitempty"` 73 74 // Tests is an ordered collection of tests with associated results. 75 Tests []Test `json:"tests,omitempty" yaml:"tests,omitempty"` 76 77 // Suites is an ordered collection of suites with associated tests. 78 Suites []Suite `json:"suites,omitempty" yaml:"suites,omitempty"` 79 80 // SystemOut is textual test output for the suite. Usually output that is 81 // written to stdout. 82 SystemOut string `json:"stdout,omitempty" yaml:"stdout,omitempty"` 83 84 // SystemErr is textual test error output for the suite. Usually output that is 85 // written to stderr. 86 SystemErr string `json:"stderr,omitempty" yaml:"stderr,omitempty"` 87 88 // Totals is the aggregated results of all tests. 89 Totals Totals `json:"totals" yaml:"totals"` 90 } 91 92 // Aggregate calculates result sums across all tests and nested suites. 93 func (s *Suite) Aggregate() { 94 totals := Totals{Tests: len(s.Tests)} 95 96 for _, test := range s.Tests { 97 totals.Duration += test.Duration 98 switch test.Status { 99 case StatusPassed: 100 totals.Passed++ 101 case StatusSkipped: 102 totals.Skipped++ 103 case StatusFailed: 104 totals.Failed++ 105 case StatusError: 106 totals.Error++ 107 } 108 } 109 110 // just summing totals from nested suites 111 for _, suite := range s.Suites { 112 suite.Aggregate() 113 totals.Tests += suite.Totals.Tests 114 totals.Duration += suite.Totals.Duration 115 totals.Passed += suite.Totals.Passed 116 totals.Skipped += suite.Totals.Skipped 117 totals.Failed += suite.Totals.Failed 118 totals.Error += suite.Totals.Error 119 } 120 121 s.Totals = totals 122 } 123 124 // Test represents the results of a single test run. 125 type Test struct { 126 // Name is a descriptor given to the test. 127 Name string `json:"name" yaml:"name"` 128 129 // Classname is an additional descriptor for the hierarchy of the test. 130 Classname string `json:"classname" yaml:"classname"` 131 132 // Duration is the total time taken to run the tests. 133 Duration time.Duration `json:"duration" yaml:"duration"` 134 135 // Status is the result of the test. Status values are passed, skipped, 136 // failure, & error. 137 Status Status `json:"status" yaml:"status"` 138 139 // Message is an textual description optionally included with a skipped, 140 // failure, or error test case. 141 Message string `json:"message" yaml:"message"` 142 143 // Error is a record of the failure or error of a test, if applicable. 144 // 145 // The following relations should hold true. 146 // Error == nil && (Status == Passed || Status == Skipped) 147 // Error != nil && (Status == Failed || Status == Error) 148 Error error `json:"error" yaml:"error"` 149 150 // Additional properties from XML node attributes. 151 // Some tools use them to store additional information about test location. 152 Properties map[string]string `json:"properties" yaml:"properties"` 153 154 // SystemOut is textual output for the test case. Usually output that is 155 // written to stdout. 156 SystemOut string `json:"stdout,omitempty" yaml:"stdout,omitempty"` 157 158 // SystemErr is textual error output for the test case. Usually output that is 159 // written to stderr. 160 SystemErr string `json:"stderr,omitempty" yaml:"stderr,omitempty"` 161 } 162 163 // Error represents an erroneous test result. 164 type Error struct { 165 // Message is a descriptor given to the error. Purpose and values differ by 166 // environment. 167 Message string `json:"message,omitempty" yaml:"message,omitempty"` 168 169 // Type is a descriptor given to the error. Purpose and values differ by 170 // framework. Value is typically an exception class, such as an assertion. 171 Type string `json:"type,omitempty" yaml:"type,omitempty"` 172 173 // Body is extended text for the error. Purpose and values differ by 174 // framework. Value is typically a stacktrace. 175 Body string `json:"body,omitempty" yaml:"body,omitempty"` 176 } 177 178 // Error returns a textual description of the test error. 179 func (err Error) Error() string { 180 switch { 181 case strings.TrimSpace(err.Body) != "": 182 return err.Body 183 184 case strings.TrimSpace(err.Message) != "": 185 return err.Message 186 187 default: 188 return err.Type 189 } 190 } 191