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 | ||||||
|  |  | ||||||
		Загрузка…
	
	Создание таблицы
		
		Сослаться в новой задаче
	
	 Dan Kegel
						Dan Kegel