github.com/m3db/m3@v1.5.0/src/dbnode/storage/index/fields_terms_iterator_prop_test.go (about)

     1  // +build big
     2  
     3  // Copyright (c) 2019 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  	"sort"
    30  	"strings"
    31  	"testing"
    32  	"time"
    33  
    34  	"github.com/golang/mock/gomock"
    35  	"github.com/leanovate/gopter"
    36  	"github.com/leanovate/gopter/gen"
    37  	"github.com/leanovate/gopter/prop"
    38  	"github.com/stretchr/testify/require"
    39  
    40  	"github.com/m3db/m3/src/m3ninx/index/segment"
    41  	"github.com/m3db/m3/src/x/context"
    42  	xtest "github.com/m3db/m3/src/x/test"
    43  )
    44  
    45  func TestFieldsTermsIteratorPropertyTest(t *testing.T) {
    46  	t.Skip("TODO: fix flaky test")
    47  	parameters := gopter.DefaultTestParameters()
    48  	seed := time.Now().UnixNano()
    49  	parameters.MinSuccessfulTests = 100
    50  	parameters.MaxSize = 40
    51  	parameters.Rng = rand.New(rand.NewSource(seed))
    52  	properties := gopter.NewProperties(parameters)
    53  
    54  	properties.Property("Fields Terms Iteration works", prop.ForAll(
    55  		func(i fieldsTermsIteratorPropInput) (bool, error) {
    56  			ctx := context.NewBackground()
    57  			expected := i.expected()
    58  			reader, err := i.setup.asSegment(t).Reader()
    59  			if err != nil {
    60  				return false, err
    61  			}
    62  			iter, err := newFieldsAndTermsIterator(
    63  				ctx,
    64  				reader,
    65  				fieldsAndTermsIteratorOpts{
    66  					iterateTerms: i.iterateTerms,
    67  					allowFn:      i.allowFn,
    68  				},
    69  			)
    70  			if err != nil {
    71  				return false, err
    72  			}
    73  			observed, err := toSlice(iter)
    74  			require.NoError(t, err)
    75  			requireSlicesEqual(t, expected, observed)
    76  			return true, nil
    77  		},
    78  		genFieldsTermsIteratorPropInput(),
    79  	))
    80  
    81  	reporter := gopter.NewFormatedReporter(true, 160, os.Stdout)
    82  	if !properties.Run(reporter) {
    83  		t.Errorf("failed with initial seed: %d", seed)
    84  	}
    85  }
    86  
    87  func TestFieldsTermsIteratorPropertyTestNoPanic(t *testing.T) {
    88  	ctrl := gomock.NewController(xtest.Reporter{t})
    89  	defer ctrl.Finish()
    90  
    91  	parameters := gopter.DefaultTestParameters()
    92  	seed := time.Now().UnixNano()
    93  	parameters.MinSuccessfulTests = 100
    94  	parameters.MaxSize = 40
    95  	parameters.Rng = rand.New(rand.NewSource(seed))
    96  	properties := gopter.NewProperties(parameters)
    97  
    98  	// the correctness prop test TestFieldsTermsIteratorPropertyTest, ensures we behave correctly
    99  	// on the happy path; this prop tests ensures we don't panic unless the underlying iterator
   100  	// itself panics.
   101  	properties.Property("Fields Terms Iteration doesn't blow up", prop.ForAll(
   102  		func(reader segment.Reader, iterate bool) (bool, error) {
   103  			iter, err := newFieldsAndTermsIterator(
   104  				context.NewBackground(),
   105  				reader,
   106  				fieldsAndTermsIteratorOpts{
   107  					iterateTerms: iterate,
   108  				},
   109  			)
   110  			if err != nil {
   111  				return false, err
   112  			}
   113  			_, _ = toSlice(iter)
   114  			return true, nil
   115  		},
   116  		genIterableSegment(ctrl),
   117  		gen.Bool(),
   118  	))
   119  
   120  	reporter := gopter.NewFormatedReporter(true, 160, os.Stdout)
   121  	if !properties.Run(reporter) {
   122  		t.Errorf("failed with initial seed: %d", seed)
   123  	}
   124  }
   125  
   126  type fieldsTermsIteratorPropInput struct {
   127  	setup        fieldsTermsIterSetup
   128  	iterateTerms bool
   129  	allowFn      allowFn
   130  }
   131  
   132  func (i fieldsTermsIteratorPropInput) expected() []pair {
   133  	fields := i.setup.fields
   134  	expected := make([]pair, 0, len(fields))
   135  	seen := make(map[string]bool, len(fields))
   136  	for _, f := range fields {
   137  		if !i.allowFn([]byte(f.Name)) {
   138  			continue
   139  		}
   140  		if seen[f.Name] {
   141  			continue
   142  		}
   143  		seen[f.Name] = true
   144  		if !i.iterateTerms {
   145  			f.Value = ""
   146  		}
   147  		expected = append(expected, f)
   148  	}
   149  	return expected
   150  }
   151  
   152  func genIterableSegment(ctrl *gomock.Controller) gopter.Gen {
   153  	return gen.MapOf(genIterpoint(), gen.SliceOf(genIterpoint())).
   154  		Map(func(tagValues map[iterpoint][]iterpoint) segment.Reader {
   155  			fields := make([]iterpoint, 0, len(tagValues))
   156  			for f := range tagValues {
   157  				fields = append(fields, f)
   158  			}
   159  			sort.Slice(fields, func(i, j int) bool {
   160  				return strings.Compare(fields[i].value, fields[j].value) < 0
   161  			})
   162  
   163  			r := segment.NewMockReader(ctrl)
   164  
   165  			fieldsPostingsListIterator := &stubFieldsPostingsListIterator{points: fields}
   166  
   167  			r.EXPECT().FieldsPostingsList().Return(fieldsPostingsListIterator, nil).AnyTimes()
   168  
   169  			for f, values := range tagValues {
   170  				sort.Slice(values, func(i, j int) bool {
   171  					return strings.Compare(values[i].value, values[j].value) < 0
   172  				})
   173  				termIterator := &stubTermIterator{points: values}
   174  				r.EXPECT().Terms([]byte(f.value)).Return(termIterator, nil).AnyTimes()
   175  			}
   176  			return r
   177  		})
   178  }
   179  
   180  func genIterpoint() gopter.Gen {
   181  	return gen.Identifier().Map(func(s string, params *gopter.GenParameters) iterpoint {
   182  		ip := iterpoint{value: s}
   183  		if params.NextBool() {
   184  			ip.err = fmt.Errorf(s)
   185  		}
   186  		return ip
   187  	})
   188  }
   189  
   190  func genFieldsTermsIteratorPropInput() gopter.Gen {
   191  	return genFieldsTermsIteratorSetup().
   192  		Map(func(s fieldsTermsIterSetup, params *gopter.GenParameters) fieldsTermsIteratorPropInput {
   193  			allowedFields := make(map[string]bool, len(s.fields))
   194  			for _, f := range s.fields {
   195  				if params.NextBool() {
   196  					allowedFields[f.Name] = true
   197  				}
   198  			}
   199  			return fieldsTermsIteratorPropInput{
   200  				setup:        s,
   201  				iterateTerms: params.NextBool(),
   202  				allowFn: func(f []byte) bool {
   203  					return allowedFields[string(f)]
   204  				},
   205  			}
   206  		})
   207  }
   208  
   209  func genFieldsTermsIteratorSetup() gopter.Gen {
   210  	return gen.SliceOf(
   211  		gen.Identifier()).
   212  		SuchThat(func(items []string) bool {
   213  			return len(items)%2 == 0 && len(items) > 0
   214  		}).
   215  		Map(func(items []string) fieldsTermsIterSetup {
   216  			pairs := make([]pair, 0, len(items)/2)
   217  			for i := 0; i < len(items); i += 2 {
   218  				name, value := items[i], items[i+1]
   219  				pairs = append(pairs, pair{name, value})
   220  			}
   221  			return newFieldsTermsIterSetup(pairs...)
   222  		})
   223  }