github.com/m3db/m3@v1.5.0/src/m3ninx/index/regexp_prop_test.go (about) 1 // +build big 2 // 3 // Copyright (c) 2018 Uber Technologies, Inc. 4 // 5 // Permission is hereby granted, free of charge, to any person obtaining a copy 6 // of this software and associated documentation files (the "Software"), to deal 7 // in the Software without restriction, including without limitation the rights 8 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 // copies of the Software, and to permit persons to whom the Software is 10 // furnished to do so, subject to the following conditions: 11 // 12 // The above copyright notice and this permission notice shall be included in 13 // all copies or substantial portions of the Software. 14 // 15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 // THE SOFTWARE. 22 23 package index 24 25 import ( 26 "fmt" 27 "math/rand" 28 "os" 29 "regexp" 30 "regexp/syntax" 31 "strings" 32 "testing" 33 "time" 34 "unicode" 35 36 "github.com/leanovate/gopter" 37 "github.com/leanovate/gopter/gen" 38 "github.com/leanovate/gopter/prop" 39 "github.com/stretchr/testify/require" 40 ) 41 42 func TestRegexpCompilationProperty(t *testing.T) { 43 parameters := gopter.DefaultTestParameters() 44 seed := time.Now().UnixNano() 45 parameters.MinSuccessfulTests = 100000 46 parameters.MaxSize = 40 47 parameters.Rng = rand.New(rand.NewSource(seed)) 48 properties := gopter.NewProperties(parameters) 49 50 properties.Property("Regexp matches same strings after modifications", prop.ForAll( 51 func(x *inputCase) (bool, error) { 52 compiled := compileRegexp(x.re, t) 53 54 re, err := regexp.Compile(x.re) 55 if err != nil { 56 return false, fmt.Errorf("unable to compile re [%v]: %v", x.re, err) 57 } 58 input := []byte(x.str) 59 60 originalMatch := re.Match(input) 61 compiledMatch := compiled.Match(input) 62 if originalMatch != compiledMatch { 63 return false, fmt.Errorf("don't match %v %v %+v", originalMatch, compiled, x) 64 } 65 66 return true, nil 67 }, 68 genInputCase(), 69 )) 70 71 reporter := gopter.NewFormatedReporter(true, 160, os.Stdout) 72 if !properties.Run(reporter) { 73 t.Errorf("failed with initial seed: %d", seed) 74 } 75 } 76 77 func compileRegexp(x string, t *testing.T) *regexp.Regexp { 78 ast, err := parseRegexp(x) 79 require.NoError(t, err) 80 astp, err := ensureRegexpUnanchored(ast) 81 require.NoError(t, err) 82 ast2p, err := ensureRegexpAnchored(astp) 83 require.NoError(t, err) 84 re, err := regexp.Compile(ast2p.String()) 85 require.NoError(t, err) 86 return re 87 } 88 89 func genInputCase() gopter.Gen { 90 return genRegexp(unicode.ASCII_Hex_Digit).Map( 91 func(reString string, params *gopter.GenParameters) *inputCase { 92 val := gen.RegexMatch(reString)(params) 93 strAny, ok := val.Retrieve() 94 if !ok { 95 return nil 96 } 97 return &inputCase{ 98 re: reString, 99 str: strAny.(string), 100 } 101 }).SuchThat(func(ic *inputCase) bool { 102 return ic != nil 103 }) 104 } 105 106 type inputCase struct { 107 re string 108 str string 109 } 110 111 const regexpFlags = syntax.Perl 112 113 func genRegexp(language *unicode.RangeTable) gopter.Gen { 114 return genRegexpAst(language).Map(func(r *syntax.Regexp) string { 115 return strings.Replace(r.Simplify().String(), "\\", "\\\\", -1) 116 }) 117 } 118 119 func genRegexpAst(language *unicode.RangeTable) gopter.Gen { 120 return gen.OneGenOf( 121 genRegexpNoOperands(language), 122 genRegexpLiteral(language), 123 genRegexpSingleOperands(language), 124 genRegexpAnyOperands(language), 125 ) 126 } 127 128 func genRegexpNoOperands(l *unicode.RangeTable) gopter.Gen { 129 return gen.OneConstOf( 130 syntax.OpEmptyMatch, 131 syntax.OpAnyChar, 132 syntax.OpBeginText, 133 syntax.OpEndText, 134 syntax.OpWordBoundary, 135 syntax.OpNoWordBoundary, 136 ).Map(func(op syntax.Op) *syntax.Regexp { 137 return &syntax.Regexp{ 138 Op: op, 139 Flags: regexpFlags, 140 } 141 }) 142 } 143 144 func genRegexpLiteral(language *unicode.RangeTable) gopter.Gen { 145 return gen.UnicodeString(language).Map(func(s string) *syntax.Regexp { 146 return &syntax.Regexp{ 147 Op: syntax.OpLiteral, 148 Flags: regexpFlags, 149 Rune: []rune(s), 150 } 151 }) 152 } 153 154 func genRegexpSingleOperands(l *unicode.RangeTable) gopter.Gen { 155 return gopter.CombineGens( 156 gen.OneGenOf(genRegexpLiteral(l), genRegexpNoOperands(l)), 157 gen.OneConstOf( 158 syntax.OpCapture, 159 syntax.OpStar, 160 syntax.OpPlus, 161 syntax.OpQuest, 162 syntax.OpRepeat), 163 ).Map(func(vals []interface{}) *syntax.Regexp { 164 return &syntax.Regexp{ 165 Op: vals[1].(syntax.Op), 166 Flags: regexpFlags, 167 Sub: []*syntax.Regexp{vals[0].(*syntax.Regexp)}, 168 } 169 }) 170 } 171 172 func genRegexpAnyOperands(l *unicode.RangeTable) gopter.Gen { 173 return gopter.CombineGens( 174 gen.SliceOf( 175 gen.OneGenOf( 176 genRegexpLiteral(l), 177 genRegexpNoOperands(l), 178 genRegexpSingleOperands(l), 179 )), 180 gen.OneConstOf( 181 syntax.OpConcat, 182 syntax.OpAlternate), 183 ).Map(func(vals []interface{}) *syntax.Regexp { 184 return &syntax.Regexp{ 185 Op: vals[1].(syntax.Op), 186 Flags: regexpFlags, 187 Sub: vals[0].([]*syntax.Regexp), 188 } 189 }) 190 }