github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/src/testing/testing.go (about) 1 // Copyright 2009 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 // 5 // This file has been modified for use by the TinyGo compiler. 6 // src: https://github.com/golang/go/blob/61bb56ad/src/testing/testing.go 7 8 // Package testing provides support for automated testing of Go packages. 9 package testing 10 11 import ( 12 "bytes" 13 "errors" 14 "flag" 15 "fmt" 16 "io" 17 "io/fs" 18 "math/rand" 19 "os" 20 "strconv" 21 "strings" 22 "time" 23 "unicode" 24 "unicode/utf8" 25 ) 26 27 // Testing flags. 28 var ( 29 flagVerbose bool 30 flagShort bool 31 flagRunRegexp string 32 flagSkipRegexp string 33 flagShuffle string 34 flagCount int 35 ) 36 37 var initRan bool 38 39 // Init registers testing flags. It has no effect if it has already run. 40 func Init() { 41 if initRan { 42 return 43 } 44 initRan = true 45 46 flag.BoolVar(&flagVerbose, "test.v", false, "verbose: print additional output") 47 flag.BoolVar(&flagShort, "test.short", false, "short: run smaller test suite to save time") 48 flag.StringVar(&flagRunRegexp, "test.run", "", "run: regexp of tests to run") 49 flag.StringVar(&flagSkipRegexp, "test.skip", "", "skip: regexp of tests to run") 50 flag.StringVar(&flagShuffle, "test.shuffle", "off", "shuffle: off, on, <numeric-seed>") 51 52 flag.IntVar(&flagCount, "test.count", 1, "run each test or benchmark `count` times") 53 54 initBenchmarkFlags() 55 } 56 57 // common holds the elements common between T and B and 58 // captures common methods such as Errorf. 59 type common struct { 60 output *logger 61 indent string 62 ran bool // Test or benchmark (or one of its subtests) was executed. 63 failed bool // Test or benchmark has failed. 64 skipped bool // Test of benchmark has been skipped. 65 cleanups []func() // optional functions to be called at the end of the test 66 finished bool // Test function has completed. 67 68 hasSub bool // TODO: should be atomic 69 70 parent *common 71 level int // Nesting depth of test or benchmark. 72 name string // Name of test or benchmark. 73 start time.Time // Time test or benchmark started 74 duration time.Duration 75 76 tempDir string 77 tempDirErr error 78 tempDirSeq int32 79 } 80 81 type logger struct { 82 logToStdout bool 83 b bytes.Buffer 84 } 85 86 func (l *logger) Write(p []byte) (int, error) { 87 if l.logToStdout { 88 return os.Stdout.Write(p) 89 } 90 return l.b.Write(p) 91 } 92 93 func (l *logger) WriteTo(w io.Writer) (int64, error) { 94 if l.logToStdout { 95 // We've already been logging to stdout; nothing to do. 96 return 0, nil 97 } 98 return l.b.WriteTo(w) 99 100 } 101 102 func (l *logger) Len() int { 103 return l.b.Len() 104 } 105 106 // Short reports whether the -test.short flag is set. 107 func Short() bool { 108 return flagShort 109 } 110 111 // CoverMode reports what the test coverage mode is set to. 112 // 113 // Test coverage is not supported; this returns the empty string. 114 func CoverMode() string { 115 return "" 116 } 117 118 // Verbose reports whether the -test.v flag is set. 119 func Verbose() bool { 120 return flagVerbose 121 } 122 123 // String constant that is being set when running a test. 124 var testBinary string 125 126 // Testing returns whether the program was compiled as a test, using "tinygo 127 // test". It returns false when built using "tinygo build", "tinygo flash", etc. 128 func Testing() bool { 129 return testBinary == "1" 130 } 131 132 // flushToParent writes c.output to the parent after first writing the header 133 // with the given format and arguments. 134 func (c *common) flushToParent(testName, format string, args ...interface{}) { 135 if c.parent == nil { 136 // The fake top-level test doesn't want a FAIL or PASS banner. 137 // Not quite sure how this works upstream. 138 c.output.WriteTo(os.Stdout) 139 } else { 140 fmt.Fprintf(c.parent.output, format, args...) 141 c.output.WriteTo(c.parent.output) 142 } 143 } 144 145 // fmtDuration returns a string representing d in the form "87.00s". 146 func fmtDuration(d time.Duration) string { 147 return fmt.Sprintf("%.2fs", d.Seconds()) 148 } 149 150 // TB is the interface common to T and B. 151 type TB interface { 152 Cleanup(func()) 153 Error(args ...interface{}) 154 Errorf(format string, args ...interface{}) 155 Fail() 156 FailNow() 157 Failed() bool 158 Fatal(args ...interface{}) 159 Fatalf(format string, args ...interface{}) 160 Helper() 161 Log(args ...interface{}) 162 Logf(format string, args ...interface{}) 163 Name() string 164 Setenv(key, value string) 165 Skip(args ...interface{}) 166 SkipNow() 167 Skipf(format string, args ...interface{}) 168 Skipped() bool 169 TempDir() string 170 } 171 172 var _ TB = (*T)(nil) 173 var _ TB = (*B)(nil) 174 175 // T is a type passed to Test functions to manage test state and support formatted test logs. 176 // Logs are accumulated during execution and dumped to standard output when done. 177 type T struct { 178 common 179 context *testContext // For running tests and subtests. 180 } 181 182 // Name returns the name of the running test or benchmark. 183 func (c *common) Name() string { 184 return c.name 185 } 186 187 func (c *common) setRan() { 188 if c.parent != nil { 189 c.parent.setRan() 190 } 191 c.ran = true 192 } 193 194 // Fail marks the function as having failed but continues execution. 195 func (c *common) Fail() { 196 c.failed = true 197 } 198 199 // Failed reports whether the function has failed. 200 func (c *common) Failed() bool { 201 failed := c.failed 202 return failed 203 } 204 205 // FailNow marks the function as having failed and stops its execution 206 // by calling runtime.Goexit (which then runs all deferred calls in the 207 // current goroutine). 208 func (c *common) FailNow() { 209 c.Fail() 210 211 c.finished = true 212 c.Error("FailNow is incomplete, requires runtime.Goexit()") 213 } 214 215 // log generates the output. 216 func (c *common) log(s string) { 217 // This doesn't print the same as in upstream go, but works for now. 218 if len(s) != 0 && s[len(s)-1] == '\n' { 219 s = s[:len(s)-1] 220 } 221 lines := strings.Split(s, "\n") 222 // First line. 223 fmt.Fprintf(c.output, "%s %s\n", c.indent, lines[0]) 224 // More lines. 225 for _, line := range lines[1:] { 226 fmt.Fprintf(c.output, "%s %s\n", c.indent, line) 227 } 228 } 229 230 // Log formats its arguments using default formatting, analogous to Println, 231 // and records the text in the error log. For tests, the text will be printed only if 232 // the test fails or the -test.v flag is set. For benchmarks, the text is always 233 // printed to avoid having performance depend on the value of the -test.v flag. 234 func (c *common) Log(args ...interface{}) { c.log(fmt.Sprintln(args...)) } 235 236 // Logf formats its arguments according to the format, analogous to Printf, and 237 // records the text in the error log. A final newline is added if not provided. For 238 // tests, the text will be printed only if the test fails or the -test.v flag is 239 // set. For benchmarks, the text is always printed to avoid having performance 240 // depend on the value of the -test.v flag. 241 func (c *common) Logf(format string, args ...interface{}) { c.log(fmt.Sprintf(format, args...)) } 242 243 // Error is equivalent to Log followed by Fail. 244 func (c *common) Error(args ...interface{}) { 245 c.log(fmt.Sprintln(args...)) 246 c.Fail() 247 } 248 249 // Errorf is equivalent to Logf followed by Fail. 250 func (c *common) Errorf(format string, args ...interface{}) { 251 c.log(fmt.Sprintf(format, args...)) 252 c.Fail() 253 } 254 255 // Fatal is equivalent to Log followed by FailNow. 256 func (c *common) Fatal(args ...interface{}) { 257 c.log(fmt.Sprintln(args...)) 258 c.FailNow() 259 } 260 261 // Fatalf is equivalent to Logf followed by FailNow. 262 func (c *common) Fatalf(format string, args ...interface{}) { 263 c.log(fmt.Sprintf(format, args...)) 264 c.FailNow() 265 } 266 267 // Skip is equivalent to Log followed by SkipNow. 268 func (c *common) Skip(args ...interface{}) { 269 c.log(fmt.Sprintln(args...)) 270 c.SkipNow() 271 } 272 273 // Skipf is equivalent to Logf followed by SkipNow. 274 func (c *common) Skipf(format string, args ...interface{}) { 275 c.log(fmt.Sprintf(format, args...)) 276 c.SkipNow() 277 } 278 279 // SkipNow marks the test as having been skipped and stops its execution 280 // by calling runtime.Goexit. 281 func (c *common) SkipNow() { 282 c.skip() 283 c.finished = true 284 c.Error("SkipNow is incomplete, requires runtime.Goexit()") 285 } 286 287 func (c *common) skip() { 288 c.skipped = true 289 } 290 291 // Skipped reports whether the test was skipped. 292 func (c *common) Skipped() bool { 293 return c.skipped 294 } 295 296 // Helper is not implemented, it is only provided for compatibility. 297 func (c *common) Helper() { 298 // Unimplemented. 299 } 300 301 // Cleanup registers a function to be called when the test (or subtest) and all its 302 // subtests complete. Cleanup functions will be called in last added, 303 // first called order. 304 func (c *common) Cleanup(f func()) { 305 c.cleanups = append(c.cleanups, f) 306 } 307 308 // TempDir returns a temporary directory for the test to use. 309 // The directory is automatically removed by Cleanup when the test and 310 // all its subtests complete. 311 // Each subsequent call to t.TempDir returns a unique directory; 312 // if the directory creation fails, TempDir terminates the test by calling Fatal. 313 func (c *common) TempDir() string { 314 // Use a single parent directory for all the temporary directories 315 // created by a test, each numbered sequentially. 316 var nonExistent bool 317 if c.tempDir == "" { // Usually the case with js/wasm 318 nonExistent = true 319 } else { 320 _, err := os.Stat(c.tempDir) 321 nonExistent = errors.Is(err, fs.ErrNotExist) 322 if err != nil && !nonExistent { 323 c.Fatalf("TempDir: %v", err) 324 } 325 } 326 327 if nonExistent { 328 c.Helper() 329 330 // Drop unusual characters (such as path separators or 331 // characters interacting with globs) from the directory name to 332 // avoid surprising os.MkdirTemp behavior. 333 mapper := func(r rune) rune { 334 if r < utf8.RuneSelf { 335 const allowed = "!#$%&()+,-.=@^_{}~ " 336 if '0' <= r && r <= '9' || 337 'a' <= r && r <= 'z' || 338 'A' <= r && r <= 'Z' { 339 return r 340 } 341 if strings.ContainsRune(allowed, r) { 342 return r 343 } 344 } else if unicode.IsLetter(r) || unicode.IsNumber(r) { 345 return r 346 } 347 return -1 348 } 349 pattern := strings.Map(mapper, c.Name()) 350 c.tempDir, c.tempDirErr = os.MkdirTemp("", pattern) 351 if c.tempDirErr == nil { 352 c.Cleanup(func() { 353 if err := os.RemoveAll(c.tempDir); err != nil { 354 c.Errorf("TempDir RemoveAll cleanup: %v", err) 355 } 356 }) 357 } 358 } 359 360 if c.tempDirErr != nil { 361 c.Fatalf("TempDir: %v", c.tempDirErr) 362 } 363 seq := c.tempDirSeq 364 c.tempDirSeq++ 365 dir := fmt.Sprintf("%s%c%03d", c.tempDir, os.PathSeparator, seq) 366 if err := os.Mkdir(dir, 0777); err != nil { 367 c.Fatalf("TempDir: %v", err) 368 } 369 return dir 370 } 371 372 // Setenv calls os.Setenv(key, value) and uses Cleanup to 373 // restore the environment variable to its original value 374 // after the test. 375 func (c *common) Setenv(key, value string) { 376 prevValue, ok := os.LookupEnv(key) 377 378 if err := os.Setenv(key, value); err != nil { 379 c.Fatalf("cannot set environment variable: %v", err) 380 } 381 382 if ok { 383 c.Cleanup(func() { 384 os.Setenv(key, prevValue) 385 }) 386 } else { 387 c.Cleanup(func() { 388 os.Unsetenv(key) 389 }) 390 } 391 } 392 393 // runCleanup is called at the end of the test. 394 func (c *common) runCleanup() { 395 for { 396 var cleanup func() 397 if len(c.cleanups) > 0 { 398 last := len(c.cleanups) - 1 399 cleanup = c.cleanups[last] 400 c.cleanups = c.cleanups[:last] 401 } 402 if cleanup == nil { 403 return 404 } 405 cleanup() 406 } 407 } 408 409 // Parallel is not implemented, it is only provided for compatibility. 410 func (t *T) Parallel() { 411 // Unimplemented. 412 } 413 414 // InternalTest is a reference to a test that should be called during a test suite run. 415 type InternalTest struct { 416 Name string 417 F func(*T) 418 } 419 420 func tRunner(t *T, fn func(t *T)) { 421 defer func() { 422 t.runCleanup() 423 }() 424 425 // Run the test. 426 t.start = time.Now() 427 fn(t) 428 t.duration += time.Since(t.start) // TODO: capture cleanup time, too. 429 430 t.report() // Report after all subtests have finished. 431 if t.parent != nil && !t.hasSub { 432 t.setRan() 433 } 434 } 435 436 // Run runs f as a subtest of t called name. It waits until the subtest is finished 437 // and returns whether the subtest succeeded. 438 func (t *T) Run(name string, f func(t *T)) bool { 439 t.hasSub = true 440 testName, ok, _ := t.context.match.fullName(&t.common, name) 441 if !ok { 442 return true 443 } 444 445 // Create a subtest. 446 sub := T{ 447 common: common{ 448 output: &logger{logToStdout: flagVerbose}, 449 name: testName, 450 parent: &t.common, 451 level: t.level + 1, 452 }, 453 context: t.context, 454 } 455 if t.level > 0 { 456 sub.indent = sub.indent + " " 457 } 458 if flagVerbose { 459 fmt.Fprintf(t.output, "=== RUN %s\n", sub.name) 460 } 461 462 tRunner(&sub, f) 463 return !sub.failed 464 } 465 466 // testContext holds all fields that are common to all tests. This includes 467 // synchronization primitives to run at most *parallel tests. 468 type testContext struct { 469 match *matcher 470 } 471 472 func newTestContext(m *matcher) *testContext { 473 return &testContext{ 474 match: m, 475 } 476 } 477 478 // M is a test suite. 479 type M struct { 480 // tests is a list of the test names to execute 481 Tests []InternalTest 482 Benchmarks []InternalBenchmark 483 484 deps testDeps 485 486 // value to pass to os.Exit, the outer test func main 487 // harness calls os.Exit with this code. See #34129. 488 exitCode int 489 } 490 491 type testDeps interface { 492 MatchString(pat, str string) (bool, error) 493 } 494 495 func (m *M) shuffle() error { 496 var n int64 497 498 if flagShuffle == "on" { 499 n = time.Now().UnixNano() 500 } else { 501 var err error 502 n, err = strconv.ParseInt(flagShuffle, 10, 64) 503 if err != nil { 504 m.exitCode = 2 505 return fmt.Errorf(`testing: -shuffle should be "off", "on", or a valid integer: %v`, err) 506 } 507 } 508 509 fmt.Println("-test.shuffle", n) 510 rng := rand.New(rand.NewSource(n)) 511 rng.Shuffle(len(m.Tests), func(i, j int) { m.Tests[i], m.Tests[j] = m.Tests[j], m.Tests[i] }) 512 rng.Shuffle(len(m.Benchmarks), func(i, j int) { m.Benchmarks[i], m.Benchmarks[j] = m.Benchmarks[j], m.Benchmarks[i] }) 513 return nil 514 } 515 516 // Run runs the tests. It returns an exit code to pass to os.Exit. 517 func (m *M) Run() (code int) { 518 defer func() { 519 code = m.exitCode 520 }() 521 522 if !flag.Parsed() { 523 flag.Parse() 524 } 525 526 if flagShuffle != "off" { 527 if err := m.shuffle(); err != nil { 528 fmt.Fprintln(os.Stderr, err) 529 return 530 } 531 } 532 533 testRan, testOk := runTests(m.deps.MatchString, m.Tests) 534 if !testRan && *matchBenchmarks == "" { 535 fmt.Fprintln(os.Stderr, "testing: warning: no tests to run") 536 } 537 if !testOk || !runBenchmarks(m.deps.MatchString, m.Benchmarks) { 538 fmt.Println("FAIL") 539 m.exitCode = 1 540 } else { 541 fmt.Println("PASS") 542 m.exitCode = 0 543 } 544 return 545 } 546 547 func runTests(matchString func(pat, str string) (bool, error), tests []InternalTest) (ran, ok bool) { 548 ok = true 549 550 ctx := newTestContext(newMatcher(matchString, flagRunRegexp, "-test.run", flagSkipRegexp)) 551 t := &T{ 552 common: common{ 553 output: &logger{logToStdout: flagVerbose}, 554 }, 555 context: ctx, 556 } 557 558 for i := 0; i < flagCount; i++ { 559 tRunner(t, func(t *T) { 560 for _, test := range tests { 561 t.Run(test.Name, test.F) 562 ok = ok && !t.Failed() 563 } 564 }) 565 } 566 567 return t.ran, ok 568 } 569 570 func (t *T) report() { 571 dstr := fmtDuration(t.duration) 572 format := t.indent + "--- %s: %s (%s)\n" 573 if t.Failed() { 574 if t.parent != nil { 575 t.parent.failed = true 576 } 577 t.flushToParent(t.name, format, "FAIL", t.name, dstr) 578 } else if flagVerbose { 579 if t.Skipped() { 580 t.flushToParent(t.name, format, "SKIP", t.name, dstr) 581 } else { 582 t.flushToParent(t.name, format, "PASS", t.name, dstr) 583 } 584 } 585 } 586 587 // AllocsPerRun returns the average number of allocations during calls to f. 588 // Although the return value has type float64, it will always be an integral 589 // value. 590 // 591 // Not implemented. 592 func AllocsPerRun(runs int, f func()) (avg float64) { 593 f() 594 for i := 0; i < runs; i++ { 595 f() 596 } 597 return 0 598 } 599 600 type InternalExample struct { 601 Name string 602 F func() 603 Output string 604 Unordered bool 605 } 606 607 // MainStart is meant for use by tests generated by 'go test'. 608 // It is not meant to be called directly and is not subject to the Go 1 compatibility document. 609 // It may change signature from release to release. 610 func MainStart(deps interface{}, tests []InternalTest, benchmarks []InternalBenchmark, fuzzTargets []InternalFuzzTarget, examples []InternalExample) *M { 611 Init() 612 return &M{ 613 Tests: tests, 614 Benchmarks: benchmarks, 615 deps: deps.(testDeps), 616 } 617 } 618 619 // A fake regexp matcher. 620 // Inflexible, but saves 50KB of flash and 50KB of RAM per -size full, 621 // and lets tests pass on cortex-m. 622 func fakeMatchString(pat, str string) (bool, error) { 623 if pat == ".*" { 624 return true, nil 625 } 626 matched := strings.Contains(str, pat) 627 return matched, nil 628 }