mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-10 14:03:39 +00:00
testing: import new version of match.go
This commit is contained in:
committed by
Damian Gryski
parent
50d681359d
commit
ee81c31884
+193
-54
@@ -14,41 +14,73 @@ import (
|
||||
|
||||
// matcher sanitizes, uniques, and filters names of subtests and subbenchmarks.
|
||||
type matcher struct {
|
||||
filter []string
|
||||
filter filterMatch
|
||||
skip filterMatch
|
||||
matchFunc func(pat, str string) (bool, error)
|
||||
|
||||
mu sync.Mutex
|
||||
subNames map[string]int64
|
||||
mu sync.Mutex
|
||||
|
||||
// subNames is used to deduplicate subtest names.
|
||||
// Each key is the subtest name joined to the deduplicated name of the parent test.
|
||||
// Each value is the count of the number of occurrences of the given subtest name
|
||||
// already seen.
|
||||
subNames map[string]int32
|
||||
}
|
||||
|
||||
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 {
|
||||
func allMatcher() *matcher {
|
||||
return newMatcher(nil, "", "", "")
|
||||
}
|
||||
|
||||
func newMatcher(matchString func(pat, str string) (bool, error), patterns, name, skips string) *matcher {
|
||||
if isBaremetal {
|
||||
// Probably not enough ram to load regexp, substitute something simpler.
|
||||
matchString = fakeMatchString
|
||||
}
|
||||
|
||||
var filter []string
|
||||
if patterns != "" {
|
||||
var filter, skip filterMatch
|
||||
if patterns == "" {
|
||||
filter = simpleMatch{} // always partial true
|
||||
} else {
|
||||
filter = splitRegexp(patterns)
|
||||
for i, s := range filter {
|
||||
filter[i] = rewrite(s)
|
||||
if err := filter.verify(name, matchString); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "testing: invalid regexp for %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
if skips == "" {
|
||||
skip = alternationMatch{} // always false
|
||||
} else {
|
||||
skip = splitRegexp(skips)
|
||||
if err := skip.verify("-test.skip", matchString); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "testing: invalid regexp for %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
return &matcher{
|
||||
filter: filter,
|
||||
skip: skip,
|
||||
matchFunc: matchString,
|
||||
subNames: map[string]int64{},
|
||||
subNames: map[string]int32{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,22 +97,83 @@ func (m *matcher) fullName(c *common, subname string) (name string, ok, partial
|
||||
matchMutex.Lock()
|
||||
defer matchMutex.Unlock()
|
||||
|
||||
// We check the full array of paths each time to allow for the case that
|
||||
// a pattern contains a '/'.
|
||||
// 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) {
|
||||
break
|
||||
}
|
||||
if ok, _ := m.matchFunc(m.filter[i], s); !ok {
|
||||
return name, false, false
|
||||
}
|
||||
|
||||
// filter must match.
|
||||
// accept partial match that may produce full match later.
|
||||
ok, partial = m.filter.matches(elem, m.matchFunc)
|
||||
if !ok {
|
||||
return name, false, false
|
||||
}
|
||||
return name, true, len(elem) < len(m.filter)
|
||||
|
||||
// skip must not match.
|
||||
// ignore partial match so we can get to more precise match later.
|
||||
skip, partialSkip := m.skip.matches(elem, m.matchFunc)
|
||||
if skip && !partialSkip {
|
||||
return name, false, false
|
||||
}
|
||||
|
||||
return name, ok, partial
|
||||
}
|
||||
|
||||
func splitRegexp(s string) []string {
|
||||
a := make([]string, 0, strings.Count(s, "/"))
|
||||
// clearSubNames clears the matcher's internal state, potentially freeing
|
||||
// memory. After this is called, T.Name may return the same strings as it did
|
||||
// for earlier subtests.
|
||||
func (m *matcher) clearSubNames() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
for key := range m.subNames {
|
||||
delete(m.subNames, key)
|
||||
}
|
||||
}
|
||||
|
||||
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, _ := 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 nil
|
||||
}
|
||||
|
||||
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); {
|
||||
@@ -108,33 +201,90 @@ 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
|
||||
// with one or more counts, if necessary.
|
||||
func (m *matcher) unique(parent, subname string) string {
|
||||
name := fmt.Sprintf("%s/%s", parent, subname)
|
||||
empty := subname == ""
|
||||
for {
|
||||
next, exists := m.subNames[name]
|
||||
if !empty && !exists {
|
||||
m.subNames[name] = 1 // next count is 1
|
||||
return name
|
||||
}
|
||||
// Name was already used. We increment with the count and append a
|
||||
// string with the count.
|
||||
m.subNames[name] = next + 1
|
||||
base := parent + "/" + subname
|
||||
|
||||
// Add a count to guarantee uniqueness.
|
||||
name = fmt.Sprintf("%s#%02d", name, next)
|
||||
empty = false
|
||||
for {
|
||||
n := m.subNames[base]
|
||||
if n < 0 {
|
||||
panic("subtest count overflow")
|
||||
}
|
||||
m.subNames[base] = n + 1
|
||||
|
||||
if n == 0 && subname != "" {
|
||||
prefix, nn := parseSubtestNumber(base)
|
||||
if len(prefix) < len(base) && nn < m.subNames[prefix] {
|
||||
// This test is explicitly named like "parent/subname#NN",
|
||||
// and #NN was already used for the NNth occurrence of "parent/subname".
|
||||
// Loop to add a disambiguating suffix.
|
||||
continue
|
||||
}
|
||||
return base
|
||||
}
|
||||
|
||||
name := fmt.Sprintf("%s#%02d", base, n)
|
||||
if m.subNames[name] != 0 {
|
||||
// This is the nth occurrence of base, but the name "parent/subname#NN"
|
||||
// collides with the first occurrence of a subtest *explicitly* named
|
||||
// "parent/subname#NN". Try the next number.
|
||||
continue
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
||||
}
|
||||
|
||||
// parseSubtestNumber splits a subtest name into a "#%02d"-formatted int32
|
||||
// suffix (if present), and a prefix preceding that suffix (always).
|
||||
func parseSubtestNumber(s string) (prefix string, nn int32) {
|
||||
i := strings.LastIndex(s, "#")
|
||||
if i < 0 {
|
||||
return s, 0
|
||||
}
|
||||
|
||||
prefix, suffix := s[:i], s[i+1:]
|
||||
if len(suffix) < 2 || (len(suffix) > 2 && suffix[0] == '0') {
|
||||
// Even if suffix is numeric, it is not a possible output of a "%02" format
|
||||
// string: it has either too few digits or too many leading zeroes.
|
||||
return s, 0
|
||||
}
|
||||
if suffix == "00" {
|
||||
if !strings.HasSuffix(prefix, "/") {
|
||||
// We only use "#00" as a suffix for subtests named with the empty
|
||||
// string — it isn't a valid suffix if the subtest name is non-empty.
|
||||
return s, 0
|
||||
}
|
||||
}
|
||||
|
||||
n, err := strconv.ParseInt(suffix, 10, 32)
|
||||
if err != nil || n < 0 {
|
||||
return s, 0
|
||||
}
|
||||
return prefix, int32(n)
|
||||
}
|
||||
|
||||
// rewrite rewrites a subname to having only printable characters and no white
|
||||
// space.
|
||||
func rewrite(s string) string {
|
||||
@@ -171,14 +321,3 @@ func isSpace(r rune) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// A fake regexp matcher.
|
||||
// Inflexible, but saves 50KB of flash and 50KB of RAM per -size full,
|
||||
// and lets tests pass on cortex-m.
|
||||
func fakeMatchString(pat, str string) (bool, error) {
|
||||
if pat == ".*" {
|
||||
return true, nil
|
||||
}
|
||||
matched := strings.Contains(str, pat)
|
||||
return matched, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user