github.com/cockroachdb/pebble@v0.0.0-20231214172447-ab4952c5f87b/metamorphic/key_manager_test.go (about) 1 package metamorphic 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "strings" 8 "testing" 9 10 "github.com/cockroachdb/datadriven" 11 "github.com/cockroachdb/pebble/internal/randvar" 12 "github.com/stretchr/testify/require" 13 ) 14 15 func TestObjKey(t *testing.T) { 16 testCases := []struct { 17 key objKey 18 want string 19 }{ 20 { 21 key: makeObjKey(makeObjID(dbTag, 1), []byte("foo")), 22 want: "db1:foo", 23 }, 24 { 25 key: makeObjKey(makeObjID(batchTag, 1), []byte("bar")), 26 want: "batch1:bar", 27 }, 28 } 29 30 for _, tc := range testCases { 31 t.Run("", func(t *testing.T) { 32 require.Equal(t, tc.want, tc.key.String()) 33 }) 34 } 35 } 36 37 func TestKeyManager_AddKey(t *testing.T) { 38 m := newKeyManager(1 /* numInstances */) 39 require.Empty(t, m.globalKeys) 40 41 k1 := []byte("foo") 42 require.True(t, m.addNewKey(k1)) 43 require.Len(t, m.globalKeys, 1) 44 require.Len(t, m.globalKeyPrefixes, 1) 45 require.Contains(t, m.globalKeys, k1) 46 require.Contains(t, m.globalKeyPrefixes, k1) 47 require.False(t, m.addNewKey(k1)) 48 require.True(t, m.prefixExists([]byte("foo"))) 49 require.False(t, m.prefixExists([]byte("bar"))) 50 51 k2 := []byte("bar") 52 require.True(t, m.addNewKey(k2)) 53 require.Len(t, m.globalKeys, 2) 54 require.Len(t, m.globalKeyPrefixes, 2) 55 require.Contains(t, m.globalKeys, k2) 56 require.Contains(t, m.globalKeyPrefixes, k2) 57 require.True(t, m.prefixExists([]byte("bar"))) 58 k3 := []byte("bax@4") 59 require.True(t, m.addNewKey(k3)) 60 require.Len(t, m.globalKeys, 3) 61 require.Len(t, m.globalKeyPrefixes, 3) 62 require.Contains(t, m.globalKeys, k3) 63 require.Contains(t, m.globalKeyPrefixes, []byte("bax")) 64 require.True(t, m.prefixExists([]byte("bax"))) 65 k4 := []byte("foo@6") 66 require.True(t, m.addNewKey(k4)) 67 require.Len(t, m.globalKeys, 4) 68 require.Len(t, m.globalKeyPrefixes, 3) 69 require.Contains(t, m.globalKeys, k4) 70 require.True(t, m.prefixExists([]byte("foo"))) 71 72 require.Equal(t, [][]byte{ 73 []byte("bar"), []byte("bax"), []byte("foo"), 74 }, m.prefixes()) 75 } 76 77 func TestKeyManager_GetOrInit(t *testing.T) { 78 id := makeObjID(batchTag, 1) 79 key := []byte("foo") 80 o := makeObjKey(id, key) 81 82 m := newKeyManager(1 /* numInstances */) 83 require.NotContains(t, m.byObjKey, o.String()) 84 require.NotContains(t, m.byObj, id) 85 require.Contains(t, m.byObj, makeObjID(dbTag, 1)) // Always contains the DB key. 86 87 meta1 := m.getOrInit(id, key) 88 require.Contains(t, m.byObjKey, o.String()) 89 require.Contains(t, m.byObj, id) 90 91 // Idempotent. 92 meta2 := m.getOrInit(id, key) 93 require.Equal(t, meta1, meta2) 94 } 95 96 func mustParseObjID(s string) objID { 97 id, err := parseObjID(s) 98 if err != nil { 99 panic(err) 100 } 101 return id 102 } 103 104 func printKeys(w io.Writer, keys [][]byte) { 105 if len(keys) == 0 { 106 fmt.Fprintln(w, "(none)") 107 return 108 } 109 for i, key := range keys { 110 if i > 0 { 111 fmt.Fprint(w, ", ") 112 } 113 fmt.Fprintf(w, "%q", key) 114 } 115 fmt.Fprintln(w) 116 } 117 118 func TestKeyManager(t *testing.T) { 119 var buf bytes.Buffer 120 km := newKeyManager(1 /* numInstances */) 121 datadriven.RunTest(t, "testdata/key_manager", func(t *testing.T, td *datadriven.TestData) string { 122 switch td.Cmd { 123 case "reset": 124 km = newKeyManager(1 /* numInstances */) 125 return "" 126 case "run": 127 buf.Reset() 128 for _, line := range strings.Split(td.Input, "\n") { 129 fields := strings.Fields(line) 130 switch fields[0] { 131 case "add-new-key": 132 if km.addNewKey([]byte(fields[1])) { 133 fmt.Fprintf(&buf, "%q is new\n", fields[1]) 134 } else { 135 fmt.Fprintf(&buf, "%q already tracked\n", fields[1]) 136 } 137 case "bounds": 138 for i := 1; i < len(fields); i++ { 139 objID := mustParseObjID(fields[1]) 140 fmt.Fprintf(&buf, "%s: %s\n", objID, km.boundsByObj[objID]) 141 } 142 case "keys": 143 fmt.Fprintf(&buf, "keys: ") 144 printKeys(&buf, km.globalKeys) 145 case "singledel-keys": 146 objID := mustParseObjID(fields[1]) 147 fmt.Fprintf(&buf, "can singledel on %s: ", objID) 148 printKeys(&buf, km.eligibleSingleDeleteKeys(objID)) 149 case "conflicts": 150 var collapsed bool 151 args := fields[1:] 152 if args[0] == "collapsed" { 153 collapsed = true 154 args = args[1:] 155 } 156 src := mustParseObjID(args[0]) 157 dst := mustParseObjID(args[1]) 158 fmt.Fprintf(&buf, "conflicts merging %s", src) 159 if collapsed { 160 fmt.Fprint(&buf, " (collapsed)") 161 } 162 fmt.Fprintf(&buf, " into %s: ", dst) 163 printKeys(&buf, km.checkForSingleDelConflicts(src, dst, collapsed)) 164 case "op": 165 ops, err := parse([]byte(strings.TrimPrefix(line, "op")), parserOpts{ 166 allowUndefinedObjs: true, 167 }) 168 if err != nil { 169 t.Fatalf("parsing line %q: %s", line, err) 170 } else if len(ops) != 1 { 171 t.Fatalf("expected 1 op but found %d", len(ops)) 172 } 173 km.update(ops[0]) 174 fmt.Fprintf(&buf, "[%s]\n", ops[0]) 175 default: 176 return fmt.Sprintf("unrecognized subcommand %q", fields[0]) 177 } 178 } 179 return buf.String() 180 default: 181 return fmt.Sprintf("unrecognized command %q", td.Cmd) 182 } 183 }) 184 } 185 186 func TestOpWrittenKeys(t *testing.T) { 187 for name, info := range methods { 188 t.Run(name, func(t *testing.T) { 189 // Any operations that exist in methods but are not handled in 190 // opWrittenKeys will result in a panic, failing the subtest. 191 opWrittenKeys(info.constructor()) 192 }) 193 } 194 } 195 196 func TestLoadPrecedingKeys(t *testing.T) { 197 rng := randvar.NewRand() 198 cfg := defaultConfig() 199 km := newKeyManager(1 /* numInstances */) 200 ops := generate(rng, 1000, cfg, km) 201 202 cfg2 := defaultConfig() 203 km2 := newKeyManager(1 /* numInstances */) 204 loadPrecedingKeys(t, ops, &cfg2, km2) 205 206 // NB: We can't assert equality, because the original run may not have 207 // ever used the max of the distribution. 208 require.Greater(t, cfg2.writeSuffixDist.Max(), uint64(1)) 209 210 // NB: We can't assert equality, because the original run may have generated 211 // keys that it didn't end up using in operations. 212 require.Subset(t, km.globalKeys, km2.globalKeys) 213 require.Subset(t, km.globalKeyPrefixes, km2.globalKeyPrefixes) 214 }