diff options
Diffstat (limited to 'src/testing')
-rw-r--r-- | src/testing/helper_test.go | 35 | ||||
-rw-r--r-- | src/testing/helperfuncs_test.go | 32 | ||||
-rw-r--r-- | src/testing/match.go | 108 | ||||
-rw-r--r-- | src/testing/match_test.go | 29 | ||||
-rw-r--r-- | src/testing/testing.go | 3 |
5 files changed, 148 insertions, 59 deletions
diff --git a/src/testing/helper_test.go b/src/testing/helper_test.go index b27fd62ee8..6175410f18 100644 --- a/src/testing/helper_test.go +++ b/src/testing/helper_test.go @@ -33,6 +33,9 @@ helperfuncs_test.go:45: 5 helperfuncs_test.go:21: 6 helperfuncs_test.go:44: 7 helperfuncs_test.go:56: 8 +--- FAIL: Test/sub2 (?s) +helperfuncs_test.go:71: 11 +helperfuncs_test.go:75: recover 12 helperfuncs_test.go:64: 9 helperfuncs_test.go:60: 10 ` @@ -71,38 +74,6 @@ func TestTBHelperParallel(t *T) { } } -func TestTBHelperLineNumer(t *T) { - var buf bytes.Buffer - ctx := newTestContext(1, newMatcher(regexp.MatchString, "", "")) - t1 := &T{ - common: common{ - signal: make(chan bool), - w: &buf, - }, - context: ctx, - } - t1.Run("Test", func(t *T) { - helperA := func(t *T) { - t.Helper() - t.Run("subtest", func(t *T) { - t.Helper() - t.Fatal("fatal error message") - }) - } - helperA(t) - }) - - want := "helper_test.go:92: fatal error message" - got := "" - lines := strings.Split(strings.TrimSpace(buf.String()), "\n") - if len(lines) > 0 { - got = strings.TrimSpace(lines[len(lines)-1]) - } - if got != want { - t.Errorf("got output:\n\n%v\nwant:\n\n%v", got, want) - } -} - type noopWriter int func (nw *noopWriter) Write(b []byte) (int, error) { return len(b), nil } diff --git a/src/testing/helperfuncs_test.go b/src/testing/helperfuncs_test.go index df0476ed73..272b33c0e5 100644 --- a/src/testing/helperfuncs_test.go +++ b/src/testing/helperfuncs_test.go @@ -65,6 +65,14 @@ func testHelper(t *T) { t.Helper() t.Error("9") }) + + // Check that helper-ness propagates up through subtests + // to helpers above. See https://golang.org/issue/44887. + helperSubCallingHelper(t, "11") + + // Check that helper-ness propagates up through panic/recover. + // See https://golang.org/issue/31154. + recoverHelper(t, "12") } func parallelTestHelper(t *T) { @@ -78,3 +86,27 @@ func parallelTestHelper(t *T) { } wg.Wait() } + +func helperSubCallingHelper(t *T, msg string) { + t.Helper() + t.Run("sub2", func(t *T) { + t.Helper() + t.Fatal(msg) + }) +} + +func recoverHelper(t *T, msg string) { + t.Helper() + defer func() { + t.Helper() + if err := recover(); err != nil { + t.Errorf("recover %s", err) + } + }() + doPanic(t, msg) +} + +func doPanic(t *T, msg string) { + t.Helper() + panic(msg) +} diff --git a/src/testing/match.go b/src/testing/match.go index b18c6e7f38..d97e415765 100644 --- a/src/testing/match.go +++ b/src/testing/match.go @@ -14,34 +14,45 @@ import ( // matcher sanitizes, uniques, and filters names of subtests and subbenchmarks. type matcher struct { - filter []string + filter filterMatch matchFunc func(pat, str string) (bool, error) mu sync.Mutex subNames map[string]int64 } +type filterMatch interface { + // matches checks the name against the receiver's pattern strings using the + // given match function. + matches(name []string, matchString func(pat, str string) (bool, error)) (ok, partial bool) + + // verify checks that the receiver's pattern strings are valid filters by + // calling the given match function. + verify(name string, matchString func(pat, str string) (bool, error)) error +} + +// simpleMatch matches a test name if all of the pattern strings match in +// sequence. +type simpleMatch []string + +// alternationMatch matches a test name if one of the alternations match. +type alternationMatch []filterMatch + // TODO: fix test_main to avoid race and improve caching, also allowing to // eliminate this Mutex. var matchMutex sync.Mutex func newMatcher(matchString func(pat, str string) (bool, error), patterns, name string) *matcher { - var filter []string + var impl filterMatch if patterns != "" { - filter = splitRegexp(patterns) - for i, s := range filter { - filter[i] = rewrite(s) - } - // Verify filters before doing any processing. - for i, s := range filter { - if _, err := matchString(s, "non-empty"); err != nil { - fmt.Fprintf(os.Stderr, "testing: invalid regexp for element %d of %s (%q): %s\n", i, name, s, err) - os.Exit(1) - } + impl = splitRegexp(patterns) + if err := impl.verify(name, matchString); err != nil { + fmt.Fprintf(os.Stderr, "testing: invalid regexp for %s\n", err) + os.Exit(1) } } return &matcher{ - filter: filter, + filter: impl, matchFunc: matchString, subNames: map[string]int64{}, } @@ -60,22 +71,63 @@ func (m *matcher) fullName(c *common, subname string) (name string, ok, partial matchMutex.Lock() defer matchMutex.Unlock() + if m.filter == nil { + return name, true, false + } + // We check the full array of paths each time to allow for the case that // a pattern contains a '/'. elem := strings.Split(name, "/") - for i, s := range elem { - if i >= len(m.filter) { + ok, partial = m.filter.matches(elem, m.matchFunc) + return name, ok, partial +} + +func (m simpleMatch) matches(name []string, matchString func(pat, str string) (bool, error)) (ok, partial bool) { + for i, s := range name { + if i >= len(m) { break } - if ok, _ := m.matchFunc(m.filter[i], s); !ok { - return name, false, false + if ok, _ := matchString(m[i], s); !ok { + return false, false + } + } + return true, len(name) < len(m) +} + +func (m simpleMatch) verify(name string, matchString func(pat, str string) (bool, error)) error { + for i, s := range m { + m[i] = rewrite(s) + } + // Verify filters before doing any processing. + for i, s := range m { + if _, err := matchString(s, "non-empty"); err != nil { + return fmt.Errorf("element %d of %s (%q): %s", i, name, s, err) + } + } + return nil +} + +func (m alternationMatch) matches(name []string, matchString func(pat, str string) (bool, error)) (ok, partial bool) { + for _, m := range m { + if ok, partial = m.matches(name, matchString); ok { + return ok, partial + } + } + return false, false +} + +func (m alternationMatch) verify(name string, matchString func(pat, str string) (bool, error)) error { + for i, m := range m { + if err := m.verify(name, matchString); err != nil { + return fmt.Errorf("alternation %d of %s", i, err) } } - return name, true, len(elem) < len(m.filter) + return nil } -func splitRegexp(s string) []string { - a := make([]string, 0, strings.Count(s, "/")) +func splitRegexp(s string) filterMatch { + a := make(simpleMatch, 0, strings.Count(s, "/")) + b := make(alternationMatch, 0, strings.Count(s, "|")) cs := 0 cp := 0 for i := 0; i < len(s); { @@ -103,10 +155,24 @@ func splitRegexp(s string) []string { i = 0 continue } + case '|': + if cs == 0 && cp == 0 { + a = append(a, s[:i]) + s = s[i+1:] + i = 0 + b = append(b, a) + a = make(simpleMatch, 0, len(a)) + continue + } } i++ } - return append(a, s) + + a = append(a, s) + if len(b) == 0 { + return a + } + return append(b, a) } // unique creates a unique name for the given parent and subname by affixing it diff --git a/src/testing/match_test.go b/src/testing/match_test.go index 8c09dc660f..9ceadbb31d 100644 --- a/src/testing/match_test.go +++ b/src/testing/match_test.go @@ -5,8 +5,10 @@ package testing import ( + "fmt" "reflect" "regexp" + "strings" "unicode" ) @@ -25,10 +27,11 @@ func TestIsSpace(t *T) { } func TestSplitRegexp(t *T) { - res := func(s ...string) []string { return s } + res := func(s ...string) filterMatch { return simpleMatch(s) } + alt := func(m ...filterMatch) filterMatch { return alternationMatch(m) } testCases := []struct { pattern string - result []string + result filterMatch }{ // Correct patterns // If a regexp pattern is correct, all split regexps need to be correct @@ -49,6 +52,8 @@ func TestSplitRegexp(t *T) { {`([)/][(])`, res(`([)/][(])`)}, {"[(]/[)]", res("[(]", "[)]")}, + {"A/B|C/D", alt(res("A", "B"), res("C", "D"))}, + // Faulty patterns // Errors in original should produce at least one faulty regexp in results. {")/", res(")/")}, @@ -71,10 +76,8 @@ func TestSplitRegexp(t *T) { // needs to have an error as well. if _, err := regexp.Compile(tc.pattern); err != nil { ok := true - for _, re := range a { - if _, err := regexp.Compile(re); err != nil { - ok = false - } + if err := a.verify("", regexp.MatchString); err != nil { + ok = false } if ok { t.Errorf("%s: expected error in any of %q", tc.pattern, a) @@ -113,6 +116,10 @@ func TestMatcher(t *T) { {"TestFoo/", "TestBar", "x", false, false}, {"TestFoo/bar/baz", "TestBar", "x/bar/baz", false, false}, + {"A/B|C/D", "TestA", "B", true, false}, + {"A/B|C/D", "TestC", "D", true, false}, + {"A/B|C/D", "TestA", "C", false, false}, + // subtests only {"", "TestFoo", "x", true, false}, {"/", "TestFoo", "x", true, false}, @@ -184,3 +191,13 @@ func TestNaming(t *T) { } } } + +// GoString returns a string that is more readable than the default, which makes +// it easier to read test errors. +func (m alternationMatch) GoString() string { + s := make([]string, len(m)) + for i, m := range m { + s[i] = fmt.Sprintf("%#v", m) + } + return fmt.Sprintf("(%s)", strings.Join(s, " | ")) +} diff --git a/src/testing/testing.go b/src/testing/testing.go index 18a0657561..567eb0dfa3 100644 --- a/src/testing/testing.go +++ b/src/testing/testing.go @@ -533,6 +533,9 @@ func (c *common) frameSkip(skip int) runtime.Frame { var firstFrame, prevFrame, frame runtime.Frame for more := true; more; prevFrame = frame { frame, more = frames.Next() + if frame.Function == "runtime.gopanic" { + continue + } if frame.Function == c.cleanupName { frames = runtime.CallersFrames(c.cleanupPc) continue |