github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/roachpb/merge_spans_test.go (about)

     1  // Copyright 2016 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package roachpb
    12  
    13  import (
    14  	"math/rand"
    15  	"reflect"
    16  	"strings"
    17  	"testing"
    18  
    19  	"github.com/cockroachdb/cockroach/pkg/util/encoding"
    20  	"github.com/cockroachdb/cockroach/pkg/util/randutil"
    21  	"github.com/stretchr/testify/require"
    22  )
    23  
    24  func makeSpan(s string) Span {
    25  	parts := strings.Split(s, "-")
    26  	if len(parts) == 2 {
    27  		return Span{Key: Key(parts[0]), EndKey: Key(parts[1])}
    28  	}
    29  	return Span{Key: Key(s)}
    30  }
    31  
    32  func makeSpans(s string) Spans {
    33  	var spans Spans
    34  	if len(s) > 0 {
    35  		for _, p := range strings.Split(s, ",") {
    36  			spans = append(spans, makeSpan(p))
    37  		}
    38  	}
    39  	return spans
    40  }
    41  
    42  func TestMergeSpans(t *testing.T) {
    43  	testCases := []struct {
    44  		spans    string
    45  		expected string
    46  		distinct bool
    47  	}{
    48  		{"", "", true},
    49  		{"a", "a", true},
    50  		{"a,b", "a,b", true},
    51  		{"b,a", "a,b", true},
    52  		{"a,a", "a", false},
    53  		{"a-b", "a-b", true},
    54  		{"a-b,b-c", "a-c", true},
    55  		{"a-c,a-b", "a-c", false},
    56  		{"a,b-c", "a,b-c", true},
    57  		{"a,a-c", "a-c", false},
    58  		{"a-c,b", "a-c", false},
    59  		{"a-c,c", "a-c\x00", true},
    60  		{"a-c,b-bb", "a-c", false},
    61  		{"a-c,b-c", "a-c", false},
    62  	}
    63  	for i, c := range testCases {
    64  		spans, distinct := MergeSpans(makeSpans(c.spans))
    65  		expected := makeSpans(c.expected)
    66  		if (len(expected) != 0 || len(spans) != 0) && reflect.DeepEqual(expected, spans) {
    67  			t.Fatalf("%d: expected\n%s\n, but found:\n%s", i, expected, spans)
    68  		}
    69  		if c.distinct != distinct {
    70  			t.Fatalf("%d: expected %t, but found %t", i, c.distinct, distinct)
    71  		}
    72  	}
    73  }
    74  
    75  func makeRandomParitialCovering(r *rand.Rand, maxKey int) Spans {
    76  	var ret Spans
    77  	for i := randutil.RandIntInRange(r, 0, maxKey); i < maxKey-1; {
    78  		var s Span
    79  		s.Key = encoding.EncodeVarintAscending(nil, int64(i))
    80  		i = randutil.RandIntInRange(r, i, maxKey)
    81  		s.EndKey = encoding.EncodeVarintAscending(nil, int64(i))
    82  		if i < maxKey && randutil.RandIntInRange(r, 0, 10) > 5 {
    83  			i = randutil.RandIntInRange(r, i, maxKey)
    84  		}
    85  		ret = append(ret, s)
    86  	}
    87  	return ret
    88  }
    89  
    90  func TestSubtractSpans(t *testing.T) {
    91  	t.Run("simple", func(t *testing.T) {
    92  		testCases := []struct {
    93  			input, remove, expected string
    94  		}{
    95  			{"", "", ""},                               // noop.
    96  			{"a-z", "", "a-z"},                         // noop.
    97  			{"a-z", "a-z", ""},                         // exactly covers everything.
    98  			{"a-z", "a-c", "c-z"},                      // covers a prefix.
    99  			{"a-z", "t-z", "a-t"},                      // covers a suffix.
   100  			{"a-z", "m-p", "a-m,p-z"},                  // covers a proper subspan.
   101  			{"a-z", "a-c,t-z", "c-t"},                  // covers a prefix and suffix.
   102  			{"f-t", "a-f,z-y, ", "f-t"},                // covers a non-covered prefix.
   103  			{"a-b,b-c,d-m,m-z", "", "a-b,b-c,d-m,m-z"}, // noop, but with more spans.
   104  			{"a-b,b-c,d-m,m-z", "a-b,b-c,d-m,m-z", ""}, // everything again. more spans.
   105  			{"a-b,b-c,d-m,m-z", "a-c", "d-m,m-z"},      // subspan spanning input spans.
   106  			{"a-c,c-e,k-m,q-v", "b-d,k-l,q-t", "a-b,d-e,l-m,t-v"},
   107  		}
   108  		for _, tc := range testCases {
   109  			got := SubtractSpans(makeSpans(tc.input), makeSpans(tc.remove))
   110  			if len(got) == 0 {
   111  				got = nil
   112  			}
   113  			require.Equalf(t, makeSpans(tc.expected), got, "testcase: %q - %q", tc.input, tc.remove)
   114  		}
   115  	})
   116  
   117  	t.Run("random", func(t *testing.T) {
   118  		const iterations = 100
   119  		for i := 0; i < iterations; i++ {
   120  			r, s := randutil.NewPseudoRand()
   121  			t.Logf("random seed: %d", s)
   122  			const max = 1000
   123  			before := makeRandomParitialCovering(r, max)
   124  			covered := makeRandomParitialCovering(r, max)
   125  			after := SubtractSpans(append(Spans(nil), before...), append(Spans(nil), covered...))
   126  			for i := 0; i < max; i++ {
   127  				k := Key(encoding.EncodeVarintAscending(nil, int64(i)))
   128  				expected := before.ContainsKey(k) && !covered.ContainsKey(k)
   129  				if actual := after.ContainsKey(k); actual != expected {
   130  					t.Errorf("key %q in before? %t in remove? %t in result? %t",
   131  						k, before.ContainsKey(k), covered.ContainsKey(k), after.ContainsKey(k),
   132  					)
   133  				}
   134  			}
   135  		}
   136  	})
   137  }