github.com/cockroachdb/pebble@v0.0.0-20231214172447-ab4952c5f87b/internal/testkeys/testkeys_test.go (about)

     1  // Copyright 2021 The LevelDB-Go and Pebble Authors. All rights reserved. Use
     2  // of this source code is governed by a BSD-style license that can be found in
     3  // the LICENSE file.
     4  
     5  package testkeys
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"slices"
    11  	"testing"
    12  
    13  	"github.com/cockroachdb/datadriven"
    14  	"github.com/stretchr/testify/require"
    15  	"golang.org/x/exp/rand"
    16  )
    17  
    18  func TestGenerateAlphabetKey(t *testing.T) {
    19  	testCases := []struct {
    20  		i     int64
    21  		depth int
    22  		want  string
    23  	}{
    24  		{0, 1, "a"},
    25  		{0, 2, "a"},
    26  		{0, 3, "a"},
    27  
    28  		{1, 1, "b"},
    29  		{2, 1, "c"},
    30  
    31  		{0, 2, "a"},
    32  		{1, 2, "aa"},
    33  		{2, 2, "ab"},
    34  		{3, 2, "ac"},
    35  		{4, 2, "b"},
    36  		{5, 2, "ba"},
    37  		{6, 2, "bb"},
    38  		{7, 2, "bc"},
    39  		{8, 2, "c"},
    40  		{9, 2, "ca"},
    41  		{10, 2, "cb"},
    42  		{11, 2, "cc"},
    43  	}
    44  	testAlphabet := []byte{byte('a'), byte('b'), byte('c')}
    45  	testInverseAlphabet := map[byte]int64{byte('a'): 0, byte('b'): 1, byte('c'): 2}
    46  
    47  	buf := make([]byte, 10)
    48  	for _, tc := range testCases {
    49  		kc := keyCount(len(testAlphabet), tc.depth)
    50  		n := generateAlphabetKey(buf, testAlphabet, tc.i, kc)
    51  		got := string(buf[:n])
    52  		if got != tc.want {
    53  			t.Errorf("generateAlphabetKey(%q, %d, %d) = %q, want %q", testAlphabet, tc.i, kc, got, tc.want)
    54  		}
    55  		i := computeAlphabetKeyIndex([]byte(got), testInverseAlphabet, tc.depth)
    56  		if i != tc.i {
    57  			t.Errorf("computeAlphabetKeyIndex(%q, %d) = %d, want %d", got, tc.depth, i, tc.i)
    58  		}
    59  	}
    60  }
    61  
    62  func TestKeyCount(t *testing.T) {
    63  	type params struct {
    64  		n, l int
    65  	}
    66  	testCases := map[params]int64{
    67  		{26, 1}: 26,
    68  		{52, 1}: 52,
    69  		{2, 2}:  6,
    70  		{2, 3}:  14,
    71  		{2, 4}:  30,
    72  		{3, 2}:  12,
    73  	}
    74  	for p, want := range testCases {
    75  		got := keyCount(p.n, p.l)
    76  		if got != want {
    77  			t.Errorf("keyCount(%d, %d) = %d, want %d", p.n, p.l, got, want)
    78  		}
    79  	}
    80  }
    81  
    82  func TestFullKeyspaces(t *testing.T) {
    83  	testCases := []struct {
    84  		ks   Keyspace
    85  		want string
    86  	}{
    87  		{
    88  			Alpha(1),
    89  			"a b c d e f g h i j k l m n o p q r s t u v w x y z",
    90  		},
    91  		{
    92  			alphabet{[]byte("abc"), 2, 0, 0, 1},
    93  			"a aa ab ac b ba bb bc c ca cb cc",
    94  		},
    95  		{
    96  			alphabet{[]byte("abc"), 2, 0, 0, 2},
    97  			"a ab b bb c cb",
    98  		},
    99  		{
   100  			alphabet{[]byte("abc"), 3, 0, 0, 1},
   101  			"a aa aaa aab aac ab aba abb abc ac aca acb acc b ba baa bab bac bb bba bbb bbc bc bca bcb bcc c ca caa cab cac cb cba cbb cbc cc cca ccb ccc",
   102  		},
   103  		{
   104  			alphabet{[]byte("abc"), 3, 7, 10, 1},
   105  			"abb abc ac aca acb acc b ba baa bab bac bb bba bbb bbc bc bca bcb bcc c ca caa",
   106  		},
   107  	}
   108  	for _, tc := range testCases {
   109  		require.Equal(t, tc.want, keyspaceToString(tc.ks))
   110  	}
   111  }
   112  
   113  func TestSlice(t *testing.T) {
   114  	testCases := []struct {
   115  		orig Keyspace
   116  		i, j int64
   117  		want string
   118  	}{
   119  		{Alpha(1), 1, 25, "b c d e f g h i j k l m n o p q r s t u v w x y"},
   120  		{Alpha(1).Slice(1, 25), 1, 23, "c d e f g h i j k l m n o p q r s t u v w x"},
   121  		{Alpha(1).Slice(1, 25).Slice(1, 23), 10, 22, "m n o p q r s t u v w x"},
   122  	}
   123  	for _, tc := range testCases {
   124  		got := keyspaceToString(tc.orig.Slice(tc.i, tc.j))
   125  		if got != tc.want {
   126  			t.Errorf("(%q).Slice(%d, %d) = %q, want %q",
   127  				keyspaceToString(tc.orig), tc.i, tc.j, got, tc.want)
   128  		}
   129  	}
   130  }
   131  
   132  func TestSuffix(t *testing.T) {
   133  	ks := Alpha(3)
   134  	require.Equal(t, "a@1", string(KeyAt(ks, 0, 1)))
   135  	require.Equal(t, "a@10", string(KeyAt(ks, 0, 10)))
   136  	require.Equal(t, "aab@5", string(KeyAt(ks, 3, 5)))
   137  
   138  	assertCmp := func(want int, a, b []byte) {
   139  		got := Comparer.Compare(a, b)
   140  		if got != want {
   141  			t.Helper()
   142  			t.Errorf("Compare(%q, %q) = %d, want %d", a, b, got, want)
   143  		}
   144  	}
   145  
   146  	for i := int64(1); i < ks.Count(); i++ {
   147  		assertCmp(-1, KeyAt(ks, i-1, 1), KeyAt(ks, i, 1))
   148  		assertCmp(-1, Key(ks, i-1), Key(ks, i))
   149  		assertCmp(0, Key(ks, i), Key(ks, i))
   150  		for ts := int64(2); ts < 11; ts++ {
   151  			assertCmp(+1, KeyAt(ks, i, ts-1), KeyAt(ks, i, ts))
   152  			assertCmp(-1, KeyAt(ks, i-1, ts-1), KeyAt(ks, i, ts))
   153  		}
   154  	}
   155  
   156  	// Suffixes should be comparable on their own too.
   157  	a, b := make([]byte, MaxSuffixLen), make([]byte, MaxSuffixLen)
   158  	for ts := int64(2); ts < 150; ts++ {
   159  		an := WriteSuffix(a, ts-1)
   160  		bn := WriteSuffix(b, ts)
   161  		assertCmp(+1, a[:an], b[:bn])
   162  	}
   163  }
   164  
   165  func TestSuffixLen(t *testing.T) {
   166  	testCases := map[int64]int{
   167  		0:    2,
   168  		1:    2,
   169  		5:    2,
   170  		9:    2,
   171  		10:   3,
   172  		17:   3,
   173  		20:   3,
   174  		99:   3,
   175  		100:  4,
   176  		101:  4,
   177  		999:  4,
   178  		1000: 5,
   179  	}
   180  	for ts, want := range testCases {
   181  		if got := SuffixLen(ts); got != want {
   182  			t.Errorf("SuffixLen(%d) = %d, want %d", ts, got, want)
   183  		}
   184  	}
   185  }
   186  
   187  func TestDivvy(t *testing.T) {
   188  	var buf bytes.Buffer
   189  	datadriven.RunTest(t, "testdata/divvy", func(t *testing.T, d *datadriven.TestData) string {
   190  		buf.Reset()
   191  		switch d.Cmd {
   192  		case "divvy":
   193  			var alphaLen int
   194  			var portions int64
   195  			d.ScanArgs(t, "alpha", &alphaLen)
   196  			d.ScanArgs(t, "portions", &portions)
   197  
   198  			input := Alpha(alphaLen)
   199  			for _, ks := range Divvy(input, portions) {
   200  				fmt.Fprintln(&buf, keyspaceToString(ks))
   201  			}
   202  			return buf.String()
   203  		default:
   204  			return fmt.Sprintf("unrecognized command %q", d.Cmd)
   205  		}
   206  	})
   207  }
   208  
   209  func keyspaceToString(ks Keyspace) string {
   210  	var buf bytes.Buffer
   211  	b := make([]byte, ks.MaxLen())
   212  	for i := int64(0); i < ks.Count(); i++ {
   213  		n := ks.key(b, i)
   214  		if i > 0 {
   215  			buf.WriteRune(' ')
   216  		}
   217  		buf.Write(b[:n])
   218  	}
   219  	return buf.String()
   220  }
   221  
   222  func TestRandomSeparator(t *testing.T) {
   223  	rng := rand.New(rand.NewSource(0))
   224  	keys := [][]byte{[]byte("a"), []byte("zzz@9")}
   225  	for n := 0; n < 1000; n++ {
   226  		i := rng.Intn(len(keys))
   227  		j := rng.Intn(len(keys))
   228  		for i == j {
   229  			j = rng.Intn(len(keys))
   230  		}
   231  		if i > j {
   232  			i, j = j, i
   233  		}
   234  
   235  		a := keys[i]
   236  		b := keys[j]
   237  		suffix := rng.Int63n(10)
   238  		sep := RandomSeparator(nil, a, b, suffix, 3, rng)
   239  		t.Logf("RandomSeparator(%q, %q, %d) = %q\n", a, b, suffix, sep)
   240  		if sep == nil {
   241  			continue
   242  		}
   243  		for k := 0; k < len(keys); k++ {
   244  			v := Comparer.Compare(sep, keys[k])
   245  			if k <= i && v <= 0 || k >= j && v >= 0 {
   246  				t.Fatalf("RandomSeparator(%q, %q, %d) = %q; but Compare(%q,%q) = %d\n", a, b, suffix, sep, sep, keys[k], v)
   247  			}
   248  		}
   249  		keys = append(keys, sep)
   250  		slices.SortFunc(keys, Comparer.Compare)
   251  	}
   252  }