aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/testing/testing.go
diff options
context:
space:
mode:
authorDan Kegel <[email protected]>2022-01-16 20:04:34 -0800
committerRon Evans <[email protected]>2022-01-17 21:54:20 +0100
commitdd6adcacb634e485d45fb77f3b3fc16c642d6a03 (patch)
treed0e30d86804d83f3728f3b7372007bfbf733c49e /src/testing/testing.go
parent610a20c4d5fdef42e462ffde30fae41f2ba832c4 (diff)
downloadtinygo-dd6adcacb634e485d45fb77f3b3fc16c642d6a03.tar.gz
tinygo-dd6adcacb634e485d45fb77f3b3fc16c642d6a03.zip
testing: --run now allows filtering of subtests
Also fix typo in error message in sub_test.go from upstream, and move a few members from B to common where they belonged. Note that testdata/testing.go seems to be pushing the edge of what the emulated cortex-m3 target can handle; using regexp in that test causes it to fail on that target with an out of memory error. TODO: once tinygo supports runtime.Goexit, consider just using upstream's testing directory...
Diffstat (limited to 'src/testing/testing.go')
-rw-r--r--src/testing/testing.go109
1 files changed, 65 insertions, 44 deletions
diff --git a/src/testing/testing.go b/src/testing/testing.go
index a38db998a..5ca60de4a 100644
--- a/src/testing/testing.go
+++ b/src/testing/testing.go
@@ -12,7 +12,6 @@ import (
"bytes"
"flag"
"fmt"
- "io"
"os"
"strings"
"time"
@@ -45,7 +44,6 @@ func Init() {
// captures common methods such as Errorf.
type common struct {
output bytes.Buffer
- w io.Writer // either &output, or at top level, os.Stdout
indent string
ran bool // Test or benchmark (or one of its subtests) was executed.
failed bool // Test or benchmark has failed.
@@ -53,6 +51,8 @@ type common struct {
cleanups []func() // optional functions to be called at the end of the test
finished bool // Test function has completed.
+ hasSub bool // TODO: should be atomic
+
parent *common
level int // Nesting depth of test or benchmark.
name string // Name of test or benchmark.
@@ -77,6 +77,19 @@ func Verbose() bool {
return flagVerbose
}
+// flushToParent writes c.output to the parent after first writing the header
+// with the given format and arguments.
+func (c *common) flushToParent(testName, format string, args ...interface{}) {
+ if c.parent == nil {
+ // The fake top-level test doesn't want a FAIL or PASS banner.
+ // Not quite sure how this works upstream.
+ c.output.WriteTo(os.Stdout)
+ } else {
+ fmt.Fprintf(&c.parent.output, format, args...)
+ c.output.WriteTo(&c.parent.output)
+ }
+}
+
// fmtDuration returns a string representing d in the form "87.00s".
func fmtDuration(d time.Duration) string {
return fmt.Sprintf("%.2fs", d.Seconds())
@@ -110,6 +123,7 @@ var _ TB = (*B)(nil)
//
type T struct {
common
+ context *testContext // For running tests and subtests.
}
// Name returns the name of the running test or benchmark.
@@ -117,6 +131,13 @@ func (c *common) Name() string {
return c.name
}
+func (c *common) setRan() {
+ if c.parent != nil {
+ c.parent.setRan()
+ }
+ c.ran = true
+}
+
// Fail marks the function as having failed but continues execution.
func (c *common) Fail() {
c.failed = true
@@ -270,35 +291,57 @@ func tRunner(t *T, fn func(t *T)) {
}()
// Run the test.
- if flagVerbose {
- fmt.Fprintf(t.w, "=== RUN %s\n", t.name)
- }
-
t.start = time.Now()
fn(t)
t.duration += time.Since(t.start) // TODO: capture cleanup time, too.
t.report() // Report after all subtests have finished.
- t.ran = true
+ if t.parent != nil && !t.hasSub {
+ t.setRan()
+ }
}
// Run runs f as a subtest of t called name. It waits until the subtest is finished
// and returns whether the subtest succeeded.
func (t *T) Run(name string, f func(t *T)) bool {
+ t.hasSub = true
+ testName, ok, _ := t.context.match.fullName(&t.common, name)
+ if !ok {
+ return true
+ }
+
// Create a subtest.
sub := T{
common: common{
- name: t.name + "/" + rewrite(name),
- indent: t.indent + " ",
- w: &t.output,
+ name: testName,
parent: &t.common,
+ level: t.level + 1,
},
+ context: t.context,
+ }
+ if t.level > 0 {
+ sub.indent = sub.indent + " "
+ }
+ if flagVerbose {
+ fmt.Fprintf(&t.output, "=== RUN %s\n", sub.name)
}
tRunner(&sub, f)
return !sub.failed
}
+// testContext holds all fields that are common to all tests. This includes
+// synchronization primitives to run at most *parallel tests.
+type testContext struct {
+ match *matcher
+}
+
+func newTestContext(m *matcher) *testContext {
+ return &testContext{
+ match: m,
+ }
+}
+
// M is a test suite.
type M struct {
// tests is a list of the test names to execute
@@ -353,40 +396,20 @@ func (m *M) Run() (code int) {
func runTests(matchString func(pat, str string) (bool, error), tests []InternalTest) (ran, ok bool) {
ok = true
- if flagRunRegexp != "" {
- var filtered []InternalTest
- // pre-test the regexp; we don't want to bother logging one failure for every test name if the regexp is broken
- if _, err := matchString(flagRunRegexp, "some-test-name"); err != nil {
- fmt.Println("testing: invalid regexp for -test.run:", err.Error())
- return false, false
- }
-
- // filter the list of tests before we try to run them
- for _, test := range tests {
- // ignore the error; we already tested that the regexp compiles fine above
- if match, _ := matchString(flagRunRegexp, test.Name); match {
- filtered = append(filtered, test)
- }
- }
-
- tests = filtered
+ ctx := newTestContext(newMatcher(matchString, flagRunRegexp, "-test.run"))
+ t := &T{
+ context: ctx,
}
- for _, test := range tests {
- t := &T{
- common: common{
- name: test.Name,
- w: os.Stdout,
- },
+ tRunner(t, func(t *T) {
+ for _, test := range tests {
+ t.Run(test.Name, test.F)
+ ok = ok && !t.Failed()
}
+ })
- tRunner(t, test.F)
-
- ok = ok && !t.Failed()
- ran = ran || t.ran
- }
- return ran, ok
+ return t.ran, ok
}
func (t *T) report() {
@@ -396,15 +419,13 @@ func (t *T) report() {
if t.parent != nil {
t.parent.failed = true
}
- fmt.Fprintf(t.w, format, "FAIL", t.name, dstr)
- t.w.Write(t.output.Bytes())
+ t.flushToParent(t.name, format, "FAIL", t.name, dstr)
} else if flagVerbose {
if t.Skipped() {
- fmt.Fprintf(t.w, format, "SKIP", t.name, dstr)
+ t.flushToParent(t.name, format, "SKIP", t.name, dstr)
} else {
- fmt.Fprintf(t.w, format, "PASS", t.name, dstr)
+ t.flushToParent(t.name, format, "PASS", t.name, dstr)
}
- t.w.Write(t.output.Bytes())
}
}