github.com/coreos/mantle@v0.13.0/harness/match.go (about)

     1  // Copyright 2017 CoreOS, Inc.
     2  // Copyright 2015 The Go Authors.
     3  //
     4  // Licensed under the Apache License, Version 2.0 (the "License");
     5  // you may not use this file except in compliance with the License.
     6  // You may obtain a copy of the License at
     7  //
     8  //     http://www.apache.org/licenses/LICENSE-2.0
     9  //
    10  // Unless required by applicable law or agreed to in writing, software
    11  // distributed under the License is distributed on an "AS IS" BASIS,
    12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  // See the License for the specific language governing permissions and
    14  // limitations under the License.
    15  
    16  package harness
    17  
    18  import (
    19  	"fmt"
    20  	"os"
    21  	"regexp"
    22  	"strconv"
    23  	"strings"
    24  	"sync"
    25  	"unicode"
    26  )
    27  
    28  // matcher sanitizes, uniques, and filters names of subtests and subbenchmarks.
    29  type matcher struct {
    30  	filter []string
    31  
    32  	mu       sync.Mutex
    33  	subNames map[string]int64
    34  }
    35  
    36  // TODO: fix test_main to avoid race and improve caching, also allowing to
    37  // eliminate this Mutex.
    38  var matchMutex sync.Mutex
    39  var matchPat string
    40  var matchRe *regexp.Regexp
    41  
    42  func matchString(pat, str string) (result bool, err error) {
    43  	if matchRe == nil || matchPat != pat {
    44  		matchPat = pat
    45  		matchRe, err = regexp.Compile(matchPat)
    46  		if err != nil {
    47  			return
    48  		}
    49  	}
    50  	return matchRe.MatchString(str), nil
    51  }
    52  
    53  func newMatcher(patterns, name string) *matcher {
    54  	var filter []string
    55  	if patterns != "" {
    56  		filter = splitRegexp(patterns)
    57  		for i, s := range filter {
    58  			filter[i] = rewrite(s)
    59  		}
    60  		// Verify filters before doing any processing.
    61  		for i, s := range filter {
    62  			if _, err := matchString(s, "non-empty"); err != nil {
    63  				fmt.Fprintf(os.Stderr, "testing: invalid regexp for element %d of %s (%q): %s\n", i, name, s, err)
    64  				os.Exit(1)
    65  			}
    66  		}
    67  	}
    68  	return &matcher{
    69  		filter:   filter,
    70  		subNames: map[string]int64{},
    71  	}
    72  }
    73  
    74  func (m *matcher) fullName(c *H, subname string) (name string, ok bool) {
    75  	name = subname
    76  
    77  	m.mu.Lock()
    78  	defer m.mu.Unlock()
    79  
    80  	if c != nil && c.level > 0 {
    81  		name = m.unique(c.name, rewrite(subname))
    82  	}
    83  
    84  	matchMutex.Lock()
    85  	defer matchMutex.Unlock()
    86  
    87  	// We check the full array of paths each time to allow for the case that
    88  	// a pattern contains a '/'.
    89  	for i, s := range strings.Split(name, "/") {
    90  		if i >= len(m.filter) {
    91  			break
    92  		}
    93  		if ok, _ := matchString(m.filter[i], s); !ok {
    94  			return name, false
    95  		}
    96  	}
    97  	return name, true
    98  }
    99  
   100  func splitRegexp(s string) []string {
   101  	a := make([]string, 0, strings.Count(s, "/"))
   102  	cs := 0
   103  	cp := 0
   104  	for i := 0; i < len(s); {
   105  		switch s[i] {
   106  		case '[':
   107  			cs++
   108  		case ']':
   109  			if cs--; cs < 0 { // An unmatched ']' is legal.
   110  				cs = 0
   111  			}
   112  		case '(':
   113  			if cs == 0 {
   114  				cp++
   115  			}
   116  		case ')':
   117  			if cs == 0 {
   118  				cp--
   119  			}
   120  		case '\\':
   121  			i++
   122  		case '/':
   123  			if cs == 0 && cp == 0 {
   124  				a = append(a, s[:i])
   125  				s = s[i+1:]
   126  				i = 0
   127  				continue
   128  			}
   129  		}
   130  		i++
   131  	}
   132  	return append(a, s)
   133  }
   134  
   135  // unique creates a unique name for the given parent and subname by affixing it
   136  // with one ore more counts, if necessary.
   137  func (m *matcher) unique(parent, subname string) string {
   138  	name := fmt.Sprintf("%s/%s", parent, subname)
   139  	empty := subname == ""
   140  	for {
   141  		next, exists := m.subNames[name]
   142  		if !empty && !exists {
   143  			m.subNames[name] = 1 // next count is 1
   144  			return name
   145  		}
   146  		// Name was already used. We increment with the count and append a
   147  		// string with the count.
   148  		m.subNames[name] = next + 1
   149  
   150  		// Add a count to guarantee uniqueness.
   151  		name = fmt.Sprintf("%s#%02d", name, next)
   152  		empty = false
   153  	}
   154  }
   155  
   156  // rewrite rewrites a subname to having only printable characters and no white
   157  // space.
   158  func rewrite(s string) string {
   159  	b := []byte{}
   160  	for _, r := range s {
   161  		switch {
   162  		case unicode.IsSpace(r):
   163  			b = append(b, '_')
   164  		case !strconv.IsPrint(r):
   165  			s := strconv.QuoteRune(r)
   166  			b = append(b, s[1:len(s)-1]...)
   167  		default:
   168  			b = append(b, string(r)...)
   169  		}
   170  	}
   171  	return string(b)
   172  }