github.com/lmorg/murex@v0.0.0-20240217211045-e081c89cd4ef/lang/test_results.go (about) 1 package lang 2 3 /* 4 This test library relates to the testing framework within the murex 5 language itself rather than Go's test framework within the murex project. 6 7 The naming convention here is basically the inverse of Go's test naming 8 convention. ie Go source files will be named "test_unit.go" (because 9 calling it unit_test.go would mean it's a Go test rather than murex test) 10 and the code is named UnitTestPlans (etc) rather than TestUnitPlans (etc) 11 because the latter might suggest they would be used by `go test`. This 12 naming convention is a little counterintuitive but it at least avoids 13 naming conflicts with `go test`. 14 */ 15 16 import ( 17 "errors" 18 "fmt" 19 "strings" 20 21 "github.com/lmorg/murex/config" 22 "github.com/lmorg/murex/lang/ref" 23 "github.com/lmorg/murex/lang/stdio" 24 "github.com/lmorg/murex/lang/types" 25 "github.com/lmorg/murex/utils/ansi/codes" 26 "github.com/lmorg/murex/utils/json" 27 ) 28 29 // SetStreams is called when a particular test case is run. eg 30 // 31 // out <test_example> "Run this test" 32 func (tests *Tests) SetStreams(name string, stdout, stderr stdio.Io, exitNumPtr *int) error { 33 tests.mutex.Lock() 34 35 var i int 36 for ; i < len(tests.test); i++ { 37 if tests.test[i].Name == name { 38 goto set 39 } 40 } 41 42 tests.mutex.Unlock() 43 return errors.New("Test named but there is no test defined for '" + name + "'.") 44 45 set: 46 tests.test[i].out.stdio = stdout 47 tests.test[i].err.stdio = stderr 48 tests.test[i].exitNumPtr = exitNumPtr 49 tests.mutex.Unlock() 50 return nil 51 } 52 53 // AddResult is called after the test has run so the result can be recorded 54 func (tests *Tests) AddResult(test *TestProperties, p *Process, status TestStatus, message string) { 55 // p.FileRef might be nil for missed tests 56 var fileRef ref.File 57 if p.FileRef != nil { 58 fileRef = *p.FileRef 59 } 60 61 tests.Results.Add(&TestResult{ 62 TestName: test.Name, 63 Exec: p.Name.String(), 64 Params: p.Parameters.StringArray(), 65 LineNumber: fileRef.Line, 66 ColNumber: fileRef.Column, 67 Status: status, 68 Message: message, 69 }) 70 } 71 72 // WriteResults is the reporting tool 73 func (tests *Tests) WriteResults(config *config.Config, pipe stdio.Io) error { 74 params := func(exec string, params []string) (s string) { 75 if len(params) > 1 { 76 //s = exec + " '" + strings.Join(params, "' '") + "'" 77 s = exec + " " + strings.Join(params, " ") 78 } else { 79 s = exec 80 } 81 if len(s) > 50 { 82 s = s[:49] + "…" 83 } 84 return 85 } 86 87 escape := func(s string) string { 88 s = strings.Replace(s, "\n", `\n`, -1) 89 s = strings.Replace(s, "\r", `\r`, -1) 90 s = strings.Replace(s, "\t", `\t`, -1) 91 return s 92 } 93 94 left := func(s string) string { 95 crop, err := config.Get("test", "crop-message", types.Integer) 96 if err != nil || crop.(int) == 0 { 97 return s 98 } 99 100 if len(s) < crop.(int) { 101 return s 102 } 103 104 return s[:crop.(int)-1] + "…" 105 } 106 107 tests.mutex.Lock() 108 defer tests.mutex.Unlock() 109 110 if tests.Results.Len() == 0 { 111 return nil 112 } 113 114 reportType, err := config.Get("test", "report-format", types.String) 115 if err != nil { 116 return err 117 } 118 119 reportPipe, err := config.Get("test", "report-pipe", types.String) 120 if err != nil { 121 reportPipe = "" 122 } 123 124 verbose, err := config.Get("test", "verbose", types.Boolean) 125 if err != nil { 126 verbose = false 127 } 128 129 ansiColour, err := config.Get("shell", "color", types.Boolean) 130 if err != nil { 131 ansiColour = false 132 } 133 134 if reportPipe.(string) != "" { 135 pipe, err = GlobalPipes.Get(reportPipe.(string)) 136 if err != nil { 137 return err 138 } 139 } 140 141 defer func() { 142 tests.Results.results = make([]*TestResult, 0) 143 }() 144 145 switch reportType.(string) { 146 case "json": 147 pipe.SetDataType(types.Json) 148 149 b, err := json.Marshal(tests.Results.results, pipe.IsTTY()) 150 if err != nil { 151 return err 152 } 153 154 _, err = pipe.Writeln(b) 155 return err 156 157 case "table": 158 pipe.SetDataType(types.Generic) 159 160 //if reportPipe.(string) == "" { 161 // pipe.Writeln([]byte(consts.TestTableHeadings)) 162 //} 163 164 var colour, reset string 165 if ansiColour.(bool) { 166 reset = codes.Reset 167 } 168 for _, r := range tests.Results.results { 169 if !verbose.(bool) && (r.Status == TestMissed || r.Status == TestInfo) { 170 continue 171 } 172 173 if ansiColour.(bool) { 174 switch r.Status { 175 case TestPassed: 176 colour = codes.FgGreen 177 case TestFailed, TestError: 178 colour = codes.FgRed 179 case TestMissed, TestInfo: 180 colour = codes.FgBlue 181 case TestState: 182 colour = codes.FgYellow 183 } 184 } 185 186 s := fmt.Sprintf("%s[%s%-6s%s] %-10s %4d:%-5d %-50s %s", 187 reset, colour, r.Status, reset, 188 r.TestName, 189 r.LineNumber, r.ColNumber, 190 params(r.Exec, r.Params), 191 left(escape(r.Message)), 192 ) 193 194 pipe.Writeln([]byte(s)) 195 196 } 197 return nil 198 199 case "csv": 200 pipe.SetDataType("csv") 201 s := fmt.Sprintf(`%s %-13s %-53s %-7s %-7s %s`, 202 `"Status",`, 203 `"Test Name",`, 204 `"Process",`, 205 `"Line",`, 206 `"Col.",`, 207 `"Message"`, 208 ) 209 pipe.Writeln([]byte(s)) 210 211 for _, r := range tests.Results.results { 212 if !verbose.(bool) && (r.Status == TestMissed || r.Status == TestInfo) { 213 continue 214 } 215 216 s = fmt.Sprintf(`%-9s %-13s %-53s %6d, %6d, %s`, 217 `"`+r.Status+`",`, 218 `"`+r.TestName+`",`, 219 `"`+params(r.Exec, r.Params)+`",`, 220 r.LineNumber, 221 r.ColNumber, 222 `"`+strings.Replace(escape(r.Message), `"`, `""`, -1)+`"`, 223 ) 224 225 pipe.Writeln([]byte(s)) 226 227 } 228 return nil 229 230 default: 231 return errors.New("invalid report type requested via `config set test report-format`") 232 } 233 }