github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/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 := EnsureRegexpAnchored(astp) 83 re, err := regexp.Compile(ast2p.String()) 84 require.NoError(t, err) 85 return re 86 } 87 88 func genInputCase() gopter.Gen { 89 return genRegexp(unicode.ASCII_Hex_Digit).Map( 90 func(reString string, params *gopter.GenParameters) *inputCase { 91 val := gen.RegexMatch(reString)(params) 92 strAny, ok := val.Retrieve() 93 if !ok { 94 return nil 95 } 96 return &inputCase{ 97 re: reString, 98 str: strAny.(string), 99 } 100 }).SuchThat(func(ic *inputCase) bool { 101 return ic != nil 102 }) 103 } 104 105 type inputCase struct { 106 re string 107 str string 108 } 109 110 const regexpFlags = syntax.Perl 111 112 func genRegexp(language *unicode.RangeTable) gopter.Gen { 113 return genRegexpAst(language).Map(func(r *syntax.Regexp) string { 114 return strings.Replace(r.Simplify().String(), "\\", "\\\\", -1) 115 }) 116 } 117 118 func genRegexpAst(language *unicode.RangeTable) gopter.Gen { 119 return gen.OneGenOf( 120 genRegexpNoOperands(language), 121 genRegexpLiteral(language), 122 genRegexpSingleOperands(language), 123 genRegexpAnyOperands(language), 124 ) 125 } 126 127 func genRegexpNoOperands(l *unicode.RangeTable) gopter.Gen { 128 return gen.OneConstOf( 129 syntax.OpEmptyMatch, 130 syntax.OpAnyChar, 131 syntax.OpBeginText, 132 syntax.OpEndText, 133 syntax.OpWordBoundary, 134 syntax.OpNoWordBoundary, 135 ).Map(func(op syntax.Op) *syntax.Regexp { 136 return &syntax.Regexp{ 137 Op: op, 138 Flags: regexpFlags, 139 } 140 }) 141 } 142 143 func genRegexpLiteral(language *unicode.RangeTable) gopter.Gen { 144 return gen.UnicodeString(language).Map(func(s string) *syntax.Regexp { 145 return &syntax.Regexp{ 146 Op: syntax.OpLiteral, 147 Flags: regexpFlags, 148 Rune: []rune(s), 149 } 150 }) 151 } 152 153 func genRegexpSingleOperands(l *unicode.RangeTable) gopter.Gen { 154 return gopter.CombineGens( 155 gen.OneGenOf(genRegexpLiteral(l), genRegexpNoOperands(l)), 156 gen.OneConstOf( 157 syntax.OpCapture, 158 syntax.OpStar, 159 syntax.OpPlus, 160 syntax.OpQuest, 161 syntax.OpRepeat), 162 ).Map(func(vals []interface{}) *syntax.Regexp { 163 return &syntax.Regexp{ 164 Op: vals[1].(syntax.Op), 165 Flags: regexpFlags, 166 Sub: []*syntax.Regexp{vals[0].(*syntax.Regexp)}, 167 } 168 }) 169 } 170 171 func genRegexpAnyOperands(l *unicode.RangeTable) gopter.Gen { 172 return gopter.CombineGens( 173 gen.SliceOf( 174 gen.OneGenOf( 175 genRegexpLiteral(l), 176 genRegexpNoOperands(l), 177 genRegexpSingleOperands(l), 178 )), 179 gen.OneConstOf( 180 syntax.OpConcat, 181 syntax.OpAlternate), 182 ).Map(func(vals []interface{}) *syntax.Regexp { 183 return &syntax.Regexp{ 184 Op: vals[1].(syntax.Op), 185 Flags: regexpFlags, 186 Sub: vals[0].([]*syntax.Regexp), 187 } 188 }) 189 }