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...
Этот коммит содержится в:
родитель
610a20c4d5
коммит
dd6adcacb6
5 изменённых файлов: 117 добавлений и 56 удалений
|
@ -77,9 +77,6 @@ type InternalBenchmark struct {
|
||||||
// affecting benchmark results.
|
// affecting benchmark results.
|
||||||
type B struct {
|
type B struct {
|
||||||
common
|
common
|
||||||
hasSub bool // TODO: should be in common, and atomic
|
|
||||||
start time.Time // TODO: should be in common
|
|
||||||
duration time.Duration // TODO: should be in common
|
|
||||||
context *benchContext
|
context *benchContext
|
||||||
N int
|
N int
|
||||||
benchFunc func(b *B)
|
benchFunc func(b *B)
|
||||||
|
|
|
@ -32,7 +32,7 @@ func TestRunCleanup(t *T) {
|
||||||
t.Errorf("unexpected inner cleanup count; got %d want 1", innerCleanup)
|
t.Errorf("unexpected inner cleanup count; got %d want 1", innerCleanup)
|
||||||
}
|
}
|
||||||
if outerCleanup != 1 {
|
if outerCleanup != 1 {
|
||||||
t.Errorf("unexpected outer cleanup count; got %d want 0", outerCleanup)
|
t.Errorf("unexpected outer cleanup count; got %d want 1", outerCleanup) // wrong upstream!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,6 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -45,7 +44,6 @@ func Init() {
|
||||||
// captures common methods such as Errorf.
|
// captures common methods such as Errorf.
|
||||||
type common struct {
|
type common struct {
|
||||||
output bytes.Buffer
|
output bytes.Buffer
|
||||||
w io.Writer // either &output, or at top level, os.Stdout
|
|
||||||
indent string
|
indent string
|
||||||
ran bool // Test or benchmark (or one of its subtests) was executed.
|
ran bool // Test or benchmark (or one of its subtests) was executed.
|
||||||
failed bool // Test or benchmark has failed.
|
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
|
cleanups []func() // optional functions to be called at the end of the test
|
||||||
finished bool // Test function has completed.
|
finished bool // Test function has completed.
|
||||||
|
|
||||||
|
hasSub bool // TODO: should be atomic
|
||||||
|
|
||||||
parent *common
|
parent *common
|
||||||
level int // Nesting depth of test or benchmark.
|
level int // Nesting depth of test or benchmark.
|
||||||
name string // Name of test or benchmark.
|
name string // Name of test or benchmark.
|
||||||
|
@ -77,6 +77,19 @@ func Verbose() bool {
|
||||||
return flagVerbose
|
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".
|
// fmtDuration returns a string representing d in the form "87.00s".
|
||||||
func fmtDuration(d time.Duration) string {
|
func fmtDuration(d time.Duration) string {
|
||||||
return fmt.Sprintf("%.2fs", d.Seconds())
|
return fmt.Sprintf("%.2fs", d.Seconds())
|
||||||
|
@ -110,6 +123,7 @@ var _ TB = (*B)(nil)
|
||||||
//
|
//
|
||||||
type T struct {
|
type T struct {
|
||||||
common
|
common
|
||||||
|
context *testContext // For running tests and subtests.
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name returns the name of the running test or benchmark.
|
// Name returns the name of the running test or benchmark.
|
||||||
|
@ -117,6 +131,13 @@ func (c *common) Name() string {
|
||||||
return c.name
|
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.
|
// Fail marks the function as having failed but continues execution.
|
||||||
func (c *common) Fail() {
|
func (c *common) Fail() {
|
||||||
c.failed = true
|
c.failed = true
|
||||||
|
@ -270,35 +291,57 @@ func tRunner(t *T, fn func(t *T)) {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// Run the test.
|
// Run the test.
|
||||||
if flagVerbose {
|
|
||||||
fmt.Fprintf(t.w, "=== RUN %s\n", t.name)
|
|
||||||
}
|
|
||||||
|
|
||||||
t.start = time.Now()
|
t.start = time.Now()
|
||||||
fn(t)
|
fn(t)
|
||||||
t.duration += time.Since(t.start) // TODO: capture cleanup time, too.
|
t.duration += time.Since(t.start) // TODO: capture cleanup time, too.
|
||||||
|
|
||||||
t.report() // Report after all subtests have finished.
|
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
|
// Run runs f as a subtest of t called name. It waits until the subtest is finished
|
||||||
// and returns whether the subtest succeeded.
|
// and returns whether the subtest succeeded.
|
||||||
func (t *T) Run(name string, f func(t *T)) bool {
|
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.
|
// Create a subtest.
|
||||||
sub := T{
|
sub := T{
|
||||||
common: common{
|
common: common{
|
||||||
name: t.name + "/" + rewrite(name),
|
name: testName,
|
||||||
indent: t.indent + " ",
|
|
||||||
w: &t.output,
|
|
||||||
parent: &t.common,
|
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)
|
tRunner(&sub, f)
|
||||||
return !sub.failed
|
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.
|
// M is a test suite.
|
||||||
type M struct {
|
type M struct {
|
||||||
// tests is a list of the test names to execute
|
// 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) {
|
func runTests(matchString func(pat, str string) (bool, error), tests []InternalTest) (ran, ok bool) {
|
||||||
ok = true
|
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
|
ctx := newTestContext(newMatcher(matchString, flagRunRegexp, "-test.run"))
|
||||||
if _, err := matchString(flagRunRegexp, "some-test-name"); err != nil {
|
t := &T{
|
||||||
fmt.Println("testing: invalid regexp for -test.run:", err.Error())
|
context: ctx,
|
||||||
return false, false
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// filter the list of tests before we try to run them
|
tRunner(t, func(t *T) {
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
// ignore the error; we already tested that the regexp compiles fine above
|
t.Run(test.Name, test.F)
|
||||||
if match, _ := matchString(flagRunRegexp, test.Name); match {
|
ok = ok && !t.Failed()
|
||||||
filtered = append(filtered, test)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
tests = filtered
|
return t.ran, ok
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
t := &T{
|
|
||||||
common: common{
|
|
||||||
name: test.Name,
|
|
||||||
w: os.Stdout,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
tRunner(t, test.F)
|
|
||||||
|
|
||||||
ok = ok && !t.Failed()
|
|
||||||
ran = ran || t.ran
|
|
||||||
}
|
|
||||||
return ran, ok
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *T) report() {
|
func (t *T) report() {
|
||||||
|
@ -396,15 +419,13 @@ func (t *T) report() {
|
||||||
if t.parent != nil {
|
if t.parent != nil {
|
||||||
t.parent.failed = true
|
t.parent.failed = true
|
||||||
}
|
}
|
||||||
fmt.Fprintf(t.w, format, "FAIL", t.name, dstr)
|
t.flushToParent(t.name, format, "FAIL", t.name, dstr)
|
||||||
t.w.Write(t.output.Bytes())
|
|
||||||
} else if flagVerbose {
|
} else if flagVerbose {
|
||||||
if t.Skipped() {
|
if t.Skipped() {
|
||||||
fmt.Fprintf(t.w, format, "SKIP", t.name, dstr)
|
t.flushToParent(t.name, format, "SKIP", t.name, dstr)
|
||||||
} else {
|
} 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())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
54
testdata/testing.go
предоставленный
54
testdata/testing.go
предоставленный
|
@ -4,6 +4,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"flag"
|
||||||
"io"
|
"io"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
@ -26,15 +27,60 @@ func TestBar(t *testing.T) {
|
||||||
t.Log("log Bar end")
|
t.Log("log Bar end")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAllLowercase(t *testing.T) {
|
||||||
|
names := []string {
|
||||||
|
"alpha",
|
||||||
|
"BETA",
|
||||||
|
"gamma",
|
||||||
|
"DELTA",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, name := range names {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
if 'a' <= name[0] && name[0] <= 'a' {
|
||||||
|
t.Logf("expected lowercase name, and got one, so I'm happy")
|
||||||
|
} else {
|
||||||
|
t.Errorf("expected lowercase name, got %s", name)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var tests = []testing.InternalTest{
|
var tests = []testing.InternalTest{
|
||||||
{"TestFoo", TestFoo},
|
{"TestFoo", TestFoo},
|
||||||
{"TestBar", TestBar},
|
{"TestBar", TestBar},
|
||||||
|
{"TestAllLowercase", TestAllLowercase},
|
||||||
}
|
}
|
||||||
|
|
||||||
var benchmarks = []testing.InternalBenchmark{}
|
var benchmarks = []testing.InternalBenchmark{}
|
||||||
|
|
||||||
var examples = []testing.InternalExample{}
|
var examples = []testing.InternalExample{}
|
||||||
|
|
||||||
|
// A fake regexp matcher that can only handle two patterns.
|
||||||
|
// Inflexible, but saves 50KB of flash and 50KB of RAM per -size full,
|
||||||
|
// and lets tests pass on cortex-m3.
|
||||||
|
func fakeMatchString(pat, str string) (bool, error) {
|
||||||
|
if pat == ".*" {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
if pat == "[BD]" {
|
||||||
|
return (str[0] == 'B' || str[0] == 'D'), nil
|
||||||
|
}
|
||||||
|
println("BUG: fakeMatchString does not grok", pat)
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
testing.Init()
|
||||||
|
flag.Set("test.run", ".*/[BD]")
|
||||||
|
m := testing.MainStart(matchStringOnly(fakeMatchString /*regexp.MatchString*/), tests, benchmarks, examples)
|
||||||
|
|
||||||
|
exitcode := m.Run()
|
||||||
|
if exitcode != 0 {
|
||||||
|
println("exitcode:", exitcode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var errMain = errors.New("testing: unexpected use of func Main")
|
var errMain = errors.New("testing: unexpected use of func Main")
|
||||||
|
|
||||||
// matchStringOnly is part of upstream, and is used below to provide a dummy deps to pass to MainStart
|
// matchStringOnly is part of upstream, and is used below to provide a dummy deps to pass to MainStart
|
||||||
|
@ -50,11 +96,3 @@ func (f matchStringOnly) ImportPath() string { return "
|
||||||
func (f matchStringOnly) StartTestLog(io.Writer) {}
|
func (f matchStringOnly) StartTestLog(io.Writer) {}
|
||||||
func (f matchStringOnly) StopTestLog() error { return errMain }
|
func (f matchStringOnly) StopTestLog() error { return errMain }
|
||||||
func (f matchStringOnly) SetPanicOnExit0(bool) {}
|
func (f matchStringOnly) SetPanicOnExit0(bool) {}
|
||||||
|
|
||||||
func main() {
|
|
||||||
m := testing.MainStart(matchStringOnly(nil), tests, benchmarks, examples)
|
|
||||||
exitcode := m.Run()
|
|
||||||
if exitcode != 0 {
|
|
||||||
println("exitcode:", exitcode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
5
testdata/testing.txt
предоставленный
5
testdata/testing.txt
предоставленный
|
@ -12,5 +12,10 @@
|
||||||
failed
|
failed
|
||||||
after failed
|
after failed
|
||||||
log Bar end
|
log Bar end
|
||||||
|
--- FAIL: TestAllLowercase (0.00s)
|
||||||
|
--- FAIL: TestAllLowercase/BETA (0.00s)
|
||||||
|
expected lowercase name, got BETA
|
||||||
|
--- FAIL: TestAllLowercase/DELTA (0.00s)
|
||||||
|
expected lowercase name, got DELTA
|
||||||
FAIL
|
FAIL
|
||||||
exitcode: 1
|
exitcode: 1
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче