github.com/core-coin/go-core/v2@v2.1.9/internal/utesting/utesting.go (about) 1 // Copyright 2020 by the Authors 2 // This file is part of the go-core library. 3 // 4 // The go-core library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-core library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-core library. If not, see <http://www.gnu.org/licenses/>. 16 17 // Package utesting provides a standalone replacement for package testing. 18 // 19 // This package exists because package testing cannot easily be embedded into a 20 // standalone go program. It provides an API that mirrors the standard library 21 // testing API. 22 package utesting 23 24 import ( 25 "bytes" 26 "fmt" 27 "io" 28 "io/ioutil" 29 "regexp" 30 "runtime" 31 "sync" 32 "time" 33 ) 34 35 // Test represents a single test. 36 type Test struct { 37 Name string 38 Fn func(*T) 39 } 40 41 // Result is the result of a test execution. 42 type Result struct { 43 Name string 44 Failed bool 45 Output string 46 Duration time.Duration 47 } 48 49 // MatchTests returns the tests whose name matches a regular expression. 50 func MatchTests(tests []Test, expr string) []Test { 51 var results []Test 52 re, err := regexp.Compile(expr) 53 if err != nil { 54 return nil 55 } 56 for _, test := range tests { 57 if re.MatchString(test.Name) { 58 results = append(results, test) 59 } 60 } 61 return results 62 } 63 64 // RunTests executes all given tests in order and returns their results. 65 // If the report writer is non-nil, a test report is written to it in real time. 66 func RunTests(tests []Test, report io.Writer) []Result { 67 if report == nil { 68 report = ioutil.Discard 69 } 70 results := run(tests, newConsoleOutput(report)) 71 fails := CountFailures(results) 72 fmt.Fprintf(report, "%v/%v tests passed.\n", len(tests)-fails, len(tests)) 73 return results 74 } 75 76 // RunTAP runs the given tests and writes Test Anything Protocol output 77 // to the report writer. 78 func RunTAP(tests []Test, report io.Writer) []Result { 79 return run(tests, newTAP(report, len(tests))) 80 } 81 82 func run(tests []Test, output testOutput) []Result { 83 var results = make([]Result, len(tests)) 84 for i, test := range tests { 85 buffer := new(bytes.Buffer) 86 logOutput := io.MultiWriter(buffer, output) 87 88 output.testStart(test.Name) 89 start := time.Now() 90 results[i].Name = test.Name 91 results[i].Failed = runTest(test, logOutput) 92 results[i].Duration = time.Since(start) 93 results[i].Output = buffer.String() 94 output.testResult(results[i]) 95 } 96 return results 97 } 98 99 // testOutput is implemented by output formats. 100 type testOutput interface { 101 testStart(name string) 102 Write([]byte) (int, error) 103 testResult(Result) 104 } 105 106 // consoleOutput prints test results similarly to go test. 107 type consoleOutput struct { 108 out io.Writer 109 indented *indentWriter 110 curTest string 111 wroteHeader bool 112 } 113 114 func newConsoleOutput(w io.Writer) *consoleOutput { 115 return &consoleOutput{ 116 out: w, 117 indented: newIndentWriter(" ", w), 118 } 119 } 120 121 // testStart signals the start of a new test. 122 func (c *consoleOutput) testStart(name string) { 123 c.curTest = name 124 c.wroteHeader = false 125 } 126 127 // Write handles test log output. 128 func (c *consoleOutput) Write(b []byte) (int, error) { 129 if !c.wroteHeader { 130 // This is the first output line from the test. Print a "-- RUN" header. 131 fmt.Fprintln(c.out, "-- RUN", c.curTest) 132 c.wroteHeader = true 133 } 134 return c.indented.Write(b) 135 } 136 137 // testResult prints the final test result line. 138 func (c *consoleOutput) testResult(r Result) { 139 c.indented.flush() 140 pd := r.Duration.Truncate(100 * time.Microsecond) 141 if r.Failed { 142 fmt.Fprintf(c.out, "-- FAIL %s (%v)\n", r.Name, pd) 143 } else { 144 fmt.Fprintf(c.out, "-- OK %s (%v)\n", r.Name, pd) 145 } 146 } 147 148 // tapOutput produces Test Anything Protocol v13 output. 149 type tapOutput struct { 150 out io.Writer 151 indented *indentWriter 152 counter int 153 } 154 155 func newTAP(out io.Writer, numTests int) *tapOutput { 156 fmt.Fprintf(out, "1..%d\n", numTests) 157 return &tapOutput{ 158 out: out, 159 indented: newIndentWriter("# ", out), 160 } 161 } 162 163 func (t *tapOutput) testStart(name string) { 164 t.counter++ 165 } 166 167 // Write does nothing for TAP because there is no real-time output of test logs. 168 func (t *tapOutput) Write(b []byte) (int, error) { 169 return len(b), nil 170 } 171 172 func (t *tapOutput) testResult(r Result) { 173 status := "ok" 174 if r.Failed { 175 status = "not ok" 176 } 177 fmt.Fprintln(t.out, status, t.counter, r.Name) 178 t.indented.Write([]byte(r.Output)) 179 t.indented.flush() 180 } 181 182 // indentWriter indents all written text. 183 type indentWriter struct { 184 out io.Writer 185 indent string 186 inLine bool 187 } 188 189 func newIndentWriter(indent string, out io.Writer) *indentWriter { 190 return &indentWriter{out: out, indent: indent} 191 } 192 193 func (w *indentWriter) Write(b []byte) (n int, err error) { 194 for len(b) > 0 { 195 if !w.inLine { 196 if _, err = io.WriteString(w.out, w.indent); err != nil { 197 return n, err 198 } 199 w.inLine = true 200 } 201 202 end := bytes.IndexByte(b, '\n') 203 if end == -1 { 204 nn, err := w.out.Write(b) 205 n += nn 206 return n, err 207 } 208 209 line := b[:end+1] 210 nn, err := w.out.Write(line) 211 n += nn 212 if err != nil { 213 return n, err 214 } 215 b = b[end+1:] 216 w.inLine = false 217 } 218 return n, err 219 } 220 221 // flush ensures the current line is terminated. 222 func (w *indentWriter) flush() { 223 if w.inLine { 224 fmt.Println(w.out) 225 w.inLine = false 226 } 227 } 228 229 // CountFailures returns the number of failed tests in the result slice. 230 func CountFailures(rr []Result) int { 231 count := 0 232 for _, r := range rr { 233 if r.Failed { 234 count++ 235 } 236 } 237 return count 238 } 239 240 // Run executes a single test. 241 func Run(test Test) (bool, string) { 242 output := new(bytes.Buffer) 243 failed := runTest(test, output) 244 return failed, output.String() 245 } 246 247 func runTest(test Test, output io.Writer) bool { 248 t := &T{output: output} 249 done := make(chan struct{}) 250 go func() { 251 defer close(done) 252 defer func() { 253 if err := recover(); err != nil { 254 buf := make([]byte, 4096) 255 i := runtime.Stack(buf, false) 256 t.Logf("panic: %v\n\n%s", err, buf[:i]) 257 t.Fail() 258 } 259 }() 260 test.Fn(t) 261 }() 262 <-done 263 return t.failed 264 } 265 266 // T is the value given to the test function. The test can signal failures 267 // and log output by calling methods on this object. 268 type T struct { 269 mu sync.Mutex 270 failed bool 271 output io.Writer 272 } 273 274 // Helper exists for compatibility with testing.T. 275 func (t *T) Helper() {} 276 277 // FailNow marks the test as having failed and stops its execution by calling 278 // runtime.Goexit (which then runs all deferred calls in the current goroutine). 279 func (t *T) FailNow() { 280 t.Fail() 281 runtime.Goexit() 282 } 283 284 // Fail marks the test as having failed but continues execution. 285 func (t *T) Fail() { 286 t.mu.Lock() 287 defer t.mu.Unlock() 288 t.failed = true 289 } 290 291 // Failed reports whether the test has failed. 292 func (t *T) Failed() bool { 293 t.mu.Lock() 294 defer t.mu.Unlock() 295 return t.failed 296 } 297 298 // Log formats its arguments using default formatting, analogous to Println, and records 299 // the text in the error log. 300 func (t *T) Log(vs ...interface{}) { 301 t.mu.Lock() 302 defer t.mu.Unlock() 303 fmt.Fprintln(t.output, vs...) 304 } 305 306 // Logf formats its arguments according to the format, analogous to Printf, and records 307 // the text in the error log. A final newline is added if not provided. 308 func (t *T) Logf(format string, vs ...interface{}) { 309 t.mu.Lock() 310 defer t.mu.Unlock() 311 if len(format) == 0 || format[len(format)-1] != '\n' { 312 format += "\n" 313 } 314 fmt.Fprintf(t.output, format, vs...) 315 } 316 317 // Error is equivalent to Log followed by Fail. 318 func (t *T) Error(vs ...interface{}) { 319 t.Log(vs...) 320 t.Fail() 321 } 322 323 // Errorf is equivalent to Logf followed by Fail. 324 func (t *T) Errorf(format string, vs ...interface{}) { 325 t.Logf(format, vs...) 326 t.Fail() 327 } 328 329 // Fatal is equivalent to Log followed by FailNow. 330 func (t *T) Fatal(vs ...interface{}) { 331 t.Log(vs...) 332 t.FailNow() 333 } 334 335 // Fatalf is equivalent to Logf followed by FailNow. 336 func (t *T) Fatalf(format string, vs ...interface{}) { 337 t.Logf(format, vs...) 338 t.FailNow() 339 }