diff --git a/compileopts/config.go b/compileopts/config.go index c94523f72e..c876682082 100644 --- a/compileopts/config.go +++ b/compileopts/config.go @@ -10,6 +10,7 @@ import ( "regexp" "strconv" "strings" + "time" "github.com/google/shlex" "github.com/tinygo-org/tinygo/goenv" @@ -659,4 +660,5 @@ type TestConfig struct { BenchTime string BenchMem bool Shuffle string + Timeout time.Duration } diff --git a/main.go b/main.go index 71d48a3e95..d0c3304930 100644 --- a/main.go +++ b/main.go @@ -238,6 +238,9 @@ func Test(pkgName string, stdout, stderr io.Writer, options *compileopts.Options if testConfig.Shuffle != "" { flags = append(flags, "-test.shuffle="+testConfig.Shuffle) } + if t := testConfig.Timeout; t != 0 { + flags = append(flags, "-test.timeout="+t.String()) + } logToStdout := testConfig.Verbose || testConfig.BenchRegexp != "" @@ -1628,7 +1631,7 @@ func main() { ocdCommandsString := flag.String("ocd-commands", "", "OpenOCD commands, overriding target spec (can specify multiple separated by commas)") ocdOutput := flag.Bool("ocd-output", false, "print OCD daemon output during debug") port := flag.String("port", "", "flash port (can specify multiple candidates separated by commas)") - timeout := flag.Duration("timeout", 20*time.Second, "the length of time to retry locating the MSD volume to be used for flashing") + timeout := flag.Duration("volume-timeout", 20*time.Second, "the length of time to retry locating the MSD volume to be used for flashing") programmer := flag.String("programmer", "", "which hardware programmer to use") ldflags := flag.String("ldflags", "", "Go link tool compatible ldflags") llvmFeatures := flag.String("llvm-features", "", "comma separated LLVM features to enable") @@ -1675,6 +1678,7 @@ func main() { flag.StringVar(&testConfig.BenchTime, "benchtime", "", "run each benchmark for duration `d`") flag.BoolVar(&testConfig.BenchMem, "benchmem", false, "show memory stats for benchmarks") flag.StringVar(&testConfig.Shuffle, "shuffle", "", "shuffle the order the tests and benchmarks run") + flag.DurationVar(&testConfig.Timeout, "timeout", 10*time.Minute, "panic test binary after duration `d`") } // Early command processing, before commands are interpreted by the Go flag diff --git a/src/testing/testing.go b/src/testing/testing.go index c4449cbb0a..dc81c661be 100644 --- a/src/testing/testing.go +++ b/src/testing/testing.go @@ -34,6 +34,7 @@ var ( flagSkipRegexp string flagShuffle string flagCount int + flagTimeout time.Duration ) var initRan bool @@ -52,6 +53,7 @@ func Init() { flag.StringVar(&flagShuffle, "test.shuffle", "off", "shuffle: off, on, ") flag.IntVar(&flagCount, "test.count", 1, "run each test or benchmark `count` times") + flag.DurationVar(&flagTimeout, "test.timeout", 0, "panic test binary after duration d (default 0, timeout disabled)") initBenchmarkFlags() } @@ -513,8 +515,6 @@ func (t *T) Run(name string, f func(t *T)) bool { // // The ok result is false if the -timeout flag indicates “no timeout” (0). // For now tinygo always return 0, false. -// -// Not Implemented. func (t *T) Deadline() (deadline time.Time, ok bool) { deadline = t.context.deadline return deadline, !deadline.IsZero() @@ -527,7 +527,7 @@ type testContext struct { deadline time.Time } -func newTestContext(m *matcher) *testContext { +func newTestContext(deadline time.Time, m *matcher) *testContext { return &testContext{ match: m, } @@ -544,6 +544,8 @@ type M struct { // value to pass to os.Exit, the outer test func main // harness calls os.Exit with this code. See #34129. exitCode int + + timer *time.Timer } type testDeps interface { @@ -588,7 +590,8 @@ func (m *M) Run() (code int) { } } - testRan, testOk := runTests(m.deps.MatchString, m.Tests) + deadline := m.startAlarm() + testRan, testOk := runTests(deadline, m.deps.MatchString, m.Tests) if !testRan && *matchBenchmarks == "" { fmt.Fprintln(os.Stderr, "testing: warning: no tests to run") } @@ -599,13 +602,34 @@ func (m *M) Run() (code int) { fmt.Println("PASS") m.exitCode = 0 } + m.stopAlarm() return } -func runTests(matchString func(pat, str string) (bool, error), tests []InternalTest) (ran, ok bool) { +// startAlarm starts an alarm if requested. +func (m *M) startAlarm() time.Time { + if flagTimeout <= 0 { + return time.Time{} + } + + deadline := time.Now().Add(flagTimeout) + m.timer = time.AfterFunc(flagTimeout, func() { + panic(fmt.Sprintf("test timed out after %v", flagTimeout)) + }) + return deadline +} + +// stopAlarm turns off the alarm. +func (m *M) stopAlarm() { + if flagTimeout > 0 { + m.timer.Stop() + } +} + +func runTests(deadline time.Time, matchString func(pat, str string) (bool, error), tests []InternalTest) (ran, ok bool) { ok = true - ctx := newTestContext(newMatcher(matchString, flagRunRegexp, "-test.run", flagSkipRegexp)) + ctx := newTestContext(deadline, newMatcher(matchString, flagRunRegexp, "-test.run", flagSkipRegexp)) t := &T{ common: common{ output: &logger{logToStdout: flagVerbose},