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  }