Home | History | Annotate | Download | only in testing
      1 // Copyright 2015 The Go Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 package testing
      6 
      7 import (
      8 	"fmt"
      9 	"os"
     10 	"strconv"
     11 	"strings"
     12 	"sync"
     13 )
     14 
     15 // matcher sanitizes, uniques, and filters names of subtests and subbenchmarks.
     16 type matcher struct {
     17 	filter    []string
     18 	matchFunc func(pat, str string) (bool, error)
     19 
     20 	mu       sync.Mutex
     21 	subNames map[string]int64
     22 }
     23 
     24 // TODO: fix test_main to avoid race and improve caching, also allowing to
     25 // eliminate this Mutex.
     26 var matchMutex sync.Mutex
     27 
     28 func newMatcher(matchString func(pat, str string) (bool, error), patterns, name string) *matcher {
     29 	var filter []string
     30 	if patterns != "" {
     31 		filter = splitRegexp(patterns)
     32 		for i, s := range filter {
     33 			filter[i] = rewrite(s)
     34 		}
     35 		// Verify filters before doing any processing.
     36 		for i, s := range filter {
     37 			if _, err := matchString(s, "non-empty"); err != nil {
     38 				fmt.Fprintf(os.Stderr, "testing: invalid regexp for element %d of %s (%q): %s\n", i, name, s, err)
     39 				os.Exit(1)
     40 			}
     41 		}
     42 	}
     43 	return &matcher{
     44 		filter:    filter,
     45 		matchFunc: matchString,
     46 		subNames:  map[string]int64{},
     47 	}
     48 }
     49 
     50 func (m *matcher) fullName(c *common, subname string) (name string, ok bool) {
     51 	name = subname
     52 
     53 	m.mu.Lock()
     54 	defer m.mu.Unlock()
     55 
     56 	if c != nil && c.level > 0 {
     57 		name = m.unique(c.name, rewrite(subname))
     58 	}
     59 
     60 	matchMutex.Lock()
     61 	defer matchMutex.Unlock()
     62 
     63 	// We check the full array of paths each time to allow for the case that
     64 	// a pattern contains a '/'.
     65 	for i, s := range strings.Split(name, "/") {
     66 		if i >= len(m.filter) {
     67 			break
     68 		}
     69 		if ok, _ := m.matchFunc(m.filter[i], s); !ok {
     70 			return name, false
     71 		}
     72 	}
     73 	return name, true
     74 }
     75 
     76 func splitRegexp(s string) []string {
     77 	a := make([]string, 0, strings.Count(s, "/"))
     78 	cs := 0
     79 	cp := 0
     80 	for i := 0; i < len(s); {
     81 		switch s[i] {
     82 		case '[':
     83 			cs++
     84 		case ']':
     85 			if cs--; cs < 0 { // An unmatched ']' is legal.
     86 				cs = 0
     87 			}
     88 		case '(':
     89 			if cs == 0 {
     90 				cp++
     91 			}
     92 		case ')':
     93 			if cs == 0 {
     94 				cp--
     95 			}
     96 		case '\\':
     97 			i++
     98 		case '/':
     99 			if cs == 0 && cp == 0 {
    100 				a = append(a, s[:i])
    101 				s = s[i+1:]
    102 				i = 0
    103 				continue
    104 			}
    105 		}
    106 		i++
    107 	}
    108 	return append(a, s)
    109 }
    110 
    111 // unique creates a unique name for the given parent and subname by affixing it
    112 // with one ore more counts, if necessary.
    113 func (m *matcher) unique(parent, subname string) string {
    114 	name := fmt.Sprintf("%s/%s", parent, subname)
    115 	empty := subname == ""
    116 	for {
    117 		next, exists := m.subNames[name]
    118 		if !empty && !exists {
    119 			m.subNames[name] = 1 // next count is 1
    120 			return name
    121 		}
    122 		// Name was already used. We increment with the count and append a
    123 		// string with the count.
    124 		m.subNames[name] = next + 1
    125 
    126 		// Add a count to guarantee uniqueness.
    127 		name = fmt.Sprintf("%s#%02d", name, next)
    128 		empty = false
    129 	}
    130 }
    131 
    132 // rewrite rewrites a subname to having only printable characters and no white
    133 // space.
    134 func rewrite(s string) string {
    135 	b := []byte{}
    136 	for _, r := range s {
    137 		switch {
    138 		case isSpace(r):
    139 			b = append(b, '_')
    140 		case !strconv.IsPrint(r):
    141 			s := strconv.QuoteRune(r)
    142 			b = append(b, s[1:len(s)-1]...)
    143 		default:
    144 			b = append(b, string(r)...)
    145 		}
    146 	}
    147 	return string(b)
    148 }
    149 
    150 func isSpace(r rune) bool {
    151 	if r < 0x2000 {
    152 		switch r {
    153 		// Note: not the same as Unicode Z class.
    154 		case '\t', '\n', '\v', '\f', '\r', ' ', 0x85, 0xA0, 0x1680:
    155 			return true
    156 		}
    157 	} else {
    158 		if r <= 0x200a {
    159 			return true
    160 		}
    161 		switch r {
    162 		case 0x2028, 0x2029, 0x202f, 0x205f, 0x3000:
    163 			return true
    164 		}
    165 	}
    166 	return false
    167 }
    168