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  }