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