github.com/cockroachdb/pebble@v1.1.1-0.20240513155919-3622ade60459/internal/keyspan/fragmenter_test.go (about) 1 // Copyright 2018 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 keyspan 6 7 import ( 8 "bytes" 9 "fmt" 10 "regexp" 11 "strconv" 12 "strings" 13 "testing" 14 15 "github.com/cockroachdb/datadriven" 16 "github.com/cockroachdb/pebble/internal/base" 17 "github.com/stretchr/testify/require" 18 ) 19 20 var spanRe = regexp.MustCompile(`(\d+):\s*(\w+)-*(\w+)\w*([^\n]*)`) 21 22 func parseSpanSingleKey(t *testing.T, s string, kind base.InternalKeyKind) Span { 23 m := spanRe.FindStringSubmatch(s) 24 if len(m) != 5 { 25 t.Fatalf("expected 5 components, but found %d: %s", len(m), s) 26 } 27 seqNum, err := strconv.Atoi(m[1]) 28 require.NoError(t, err) 29 return Span{ 30 Start: []byte(m[2]), 31 End: []byte(m[3]), 32 Keys: []Key{ 33 { 34 Trailer: base.MakeTrailer(uint64(seqNum), kind), 35 Value: []byte(strings.TrimSpace(m[4])), 36 }, 37 }, 38 } 39 } 40 41 func buildSpans( 42 t *testing.T, cmp base.Compare, formatKey base.FormatKey, s string, kind base.InternalKeyKind, 43 ) []Span { 44 var spans []Span 45 f := &Fragmenter{ 46 Cmp: cmp, 47 Format: formatKey, 48 Emit: func(fragmented Span) { 49 spans = append(spans, fragmented) 50 }, 51 } 52 for _, line := range strings.Split(s, "\n") { 53 if strings.HasPrefix(line, "truncate-and-flush-to ") { 54 parts := strings.Split(line, " ") 55 if len(parts) != 2 { 56 t.Fatalf("expected 2 components, but found %d: %s", len(parts), line) 57 } 58 f.TruncateAndFlushTo([]byte(parts[1])) 59 continue 60 } 61 62 f.Add(parseSpanSingleKey(t, line, kind)) 63 } 64 f.Finish() 65 return spans 66 } 67 68 func formatAlphabeticSpans(spans []Span) string { 69 isLetter := func(b []byte) bool { 70 if len(b) != 1 { 71 return false 72 } 73 return b[0] >= 'a' && b[0] <= 'z' 74 } 75 76 var buf bytes.Buffer 77 for _, v := range spans { 78 switch { 79 case !v.Valid(): 80 fmt.Fprintf(&buf, "<invalid>\n") 81 case v.Empty(): 82 fmt.Fprintf(&buf, "<empty>\n") 83 case !isLetter(v.Start) || !isLetter(v.End) || v.Start[0] == v.End[0]: 84 for _, k := range v.Keys { 85 fmt.Fprintf(&buf, "%d: %s-%s", k.SeqNum(), v.Start, v.End) 86 if len(k.Value) > 0 { 87 buf.WriteString(strings.Repeat(" ", int('z'-v.End[0]+1))) 88 buf.WriteString(string(k.Value)) 89 } 90 fmt.Fprintln(&buf) 91 } 92 default: 93 for _, k := range v.Keys { 94 fmt.Fprintf(&buf, "%d: %s%s%s%s", 95 k.SeqNum(), 96 strings.Repeat(" ", int(v.Start[0]-'a')), 97 v.Start, 98 strings.Repeat("-", int(v.End[0]-v.Start[0]-1)), 99 v.End) 100 if len(k.Value) > 0 { 101 buf.WriteString(strings.Repeat(" ", int('z'-v.End[0]+1))) 102 buf.WriteString(string(k.Value)) 103 } 104 fmt.Fprintln(&buf) 105 } 106 } 107 } 108 return buf.String() 109 } 110 111 func TestFragmenter(t *testing.T) { 112 cmp := base.DefaultComparer.Compare 113 fmtKey := base.DefaultComparer.FormatKey 114 115 var getRe = regexp.MustCompile(`(\w+)#(\d+)`) 116 117 parseGet := func(t *testing.T, s string) (string, int) { 118 m := getRe.FindStringSubmatch(s) 119 if len(m) != 3 { 120 t.Fatalf("expected 3 components, but found %d", len(m)) 121 } 122 seq, err := strconv.Atoi(m[2]) 123 require.NoError(t, err) 124 return m[1], seq 125 } 126 127 var iter FragmentIterator 128 129 // Returns true if the specified <key,seq> pair is deleted at the specified 130 // read sequence number. Get ignores spans newer than the read sequence 131 // number. This is a simple version of what full processing of range 132 // tombstones looks like. 133 deleted := func(key []byte, seq, readSeq uint64) bool { 134 s := Get(cmp, iter, key) 135 return s != nil && s.CoversAt(readSeq, seq) 136 } 137 138 datadriven.RunTest(t, "testdata/fragmenter", func(t *testing.T, d *datadriven.TestData) string { 139 switch d.Cmd { 140 case "build": 141 return func() (result string) { 142 defer func() { 143 if r := recover(); r != nil { 144 result = fmt.Sprint(r) 145 } 146 }() 147 148 spans := buildSpans(t, cmp, fmtKey, d.Input, base.InternalKeyKindRangeDelete) 149 iter = NewIter(cmp, spans) 150 return formatAlphabeticSpans(spans) 151 }() 152 153 case "get": 154 if len(d.CmdArgs) != 1 { 155 return fmt.Sprintf("expected 1 argument, but found %s", d.CmdArgs) 156 } 157 if d.CmdArgs[0].Key != "t" { 158 return fmt.Sprintf("expected timestamp argument, but found %s", d.CmdArgs[0]) 159 } 160 readSeq, err := strconv.Atoi(d.CmdArgs[0].Vals[0]) 161 require.NoError(t, err) 162 163 var results []string 164 for _, p := range strings.Split(d.Input, " ") { 165 key, seq := parseGet(t, p) 166 if deleted([]byte(key), uint64(seq), uint64(readSeq)) { 167 results = append(results, "deleted") 168 } else { 169 results = append(results, "alive") 170 } 171 } 172 return strings.Join(results, " ") 173 174 default: 175 return fmt.Sprintf("unknown command: %s", d.Cmd) 176 } 177 }) 178 } 179 180 func TestFragmenterCovers(t *testing.T) { 181 datadriven.RunTest(t, "testdata/fragmenter_covers", func(t *testing.T, d *datadriven.TestData) string { 182 switch d.Cmd { 183 case "build": 184 f := &Fragmenter{ 185 Cmp: base.DefaultComparer.Compare, 186 Format: base.DefaultComparer.FormatKey, 187 Emit: func(fragmented Span) { 188 }, 189 } 190 var buf bytes.Buffer 191 for _, line := range strings.Split(d.Input, "\n") { 192 switch { 193 case strings.HasPrefix(line, "add "): 194 t := parseSpanSingleKey(t, strings.TrimPrefix(line, "add "), base.InternalKeyKindRangeDelete) 195 f.Add(t) 196 case strings.HasPrefix(line, "deleted "): 197 fields := strings.Fields(strings.TrimPrefix(line, "deleted ")) 198 key := base.ParseInternalKey(fields[0]) 199 snapshot, err := strconv.ParseUint(fields[1], 10, 64) 200 if err != nil { 201 return err.Error() 202 } 203 func() { 204 defer func() { 205 if r := recover(); r != nil { 206 fmt.Fprintf(&buf, "%s: %s\n", key, r) 207 } 208 }() 209 switch f.Covers(key, snapshot) { 210 case NoCover: 211 fmt.Fprintf(&buf, "%s: none\n", key) 212 case CoversInvisibly: 213 fmt.Fprintf(&buf, "%s: invisibly\n", key) 214 case CoversVisibly: 215 fmt.Fprintf(&buf, "%s: visibly\n", key) 216 } 217 }() 218 } 219 } 220 return buf.String() 221 222 default: 223 return fmt.Sprintf("unknown command: %s", d.Cmd) 224 } 225 }) 226 } 227 228 func TestFragmenterTruncateAndFlushTo(t *testing.T) { 229 cmp := base.DefaultComparer.Compare 230 fmtKey := base.DefaultComparer.FormatKey 231 232 datadriven.RunTest(t, "testdata/fragmenter_truncate_and_flush_to", func(t *testing.T, d *datadriven.TestData) string { 233 switch d.Cmd { 234 case "build": 235 return func() (result string) { 236 defer func() { 237 if r := recover(); r != nil { 238 result = fmt.Sprint(r) 239 } 240 }() 241 242 spans := buildSpans(t, cmp, fmtKey, d.Input, base.InternalKeyKindRangeDelete) 243 return formatAlphabeticSpans(spans) 244 }() 245 246 default: 247 return fmt.Sprintf("unknown command: %s", d.Cmd) 248 } 249 }) 250 } 251 252 func TestFragmenter_Values(t *testing.T) { 253 cmp := base.DefaultComparer.Compare 254 fmtKey := base.DefaultComparer.FormatKey 255 256 datadriven.RunTest(t, "testdata/fragmenter_values", func(t *testing.T, d *datadriven.TestData) string { 257 switch d.Cmd { 258 case "build": 259 return func() (result string) { 260 defer func() { 261 if r := recover(); r != nil { 262 result = fmt.Sprint(r) 263 } 264 }() 265 266 spans := buildSpans(t, cmp, fmtKey, d.Input, base.InternalKeyKindRangeKeySet) 267 return formatAlphabeticSpans(spans) 268 }() 269 270 default: 271 return fmt.Sprintf("unknown command: %s", d.Cmd) 272 } 273 }) 274 } 275 276 func TestFragmenter_EmitOrder(t *testing.T) { 277 var buf bytes.Buffer 278 279 datadriven.RunTest(t, "testdata/fragmenter_emit_order", func(t *testing.T, d *datadriven.TestData) string { 280 switch d.Cmd { 281 case "build": 282 buf.Reset() 283 f := Fragmenter{ 284 Cmp: base.DefaultComparer.Compare, 285 Format: base.DefaultComparer.FormatKey, 286 Emit: func(span Span) { 287 fmt.Fprintf(&buf, "%s %s:", 288 base.DefaultComparer.FormatKey(span.Start), 289 base.DefaultComparer.FormatKey(span.End)) 290 for i, k := range span.Keys { 291 if i == 0 { 292 fmt.Fprint(&buf, " ") 293 } else { 294 fmt.Fprint(&buf, ", ") 295 } 296 fmt.Fprintf(&buf, "#%d,%s", k.SeqNum(), k.Kind()) 297 } 298 fmt.Fprintln(&buf, "\n-") 299 }, 300 } 301 for _, line := range strings.Split(d.Input, "\n") { 302 fields := strings.Fields(line) 303 if len(fields) != 2 { 304 panic(fmt.Sprintf("datadriven test: expect 2 fields, found %d", len(fields))) 305 } 306 k := base.ParseInternalKey(fields[0]) 307 f.Add(Span{ 308 Start: k.UserKey, 309 End: []byte(fields[1]), 310 Keys: []Key{{Trailer: k.Trailer}}, 311 }) 312 } 313 314 f.Finish() 315 return buf.String() 316 default: 317 panic(fmt.Sprintf("unrecognized command %q", d.Cmd)) 318 } 319 }) 320 }