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  }