github.com/zuoyebang/bitalostable@v1.0.1-0.20240229032404-e3b99a834294/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/stretchr/testify/require" 16 "github.com/zuoyebang/bitalostable/internal/base" 17 "github.com/zuoyebang/bitalostable/internal/datadriven" 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(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 TestFragmenterDeleted(t *testing.T) { 181 datadriven.RunTest(t, "testdata/fragmenter_deleted", func(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 key := base.ParseInternalKey(strings.TrimPrefix(line, "deleted ")) 198 func() { 199 defer func() { 200 if r := recover(); r != nil { 201 fmt.Fprintf(&buf, "%s: %s\n", key, r) 202 } 203 }() 204 fmt.Fprintf(&buf, "%s: %t\n", key, f.Covers(key, base.InternalKeySeqNumMax)) 205 }() 206 } 207 } 208 return buf.String() 209 210 default: 211 return fmt.Sprintf("unknown command: %s", d.Cmd) 212 } 213 }) 214 } 215 216 func TestFragmenterTruncateAndFlushTo(t *testing.T) { 217 cmp := base.DefaultComparer.Compare 218 fmtKey := base.DefaultComparer.FormatKey 219 220 datadriven.RunTest(t, "testdata/fragmenter_truncate_and_flush_to", func(d *datadriven.TestData) string { 221 switch d.Cmd { 222 case "build": 223 return func() (result string) { 224 defer func() { 225 if r := recover(); r != nil { 226 result = fmt.Sprint(r) 227 } 228 }() 229 230 spans := buildSpans(t, cmp, fmtKey, d.Input, base.InternalKeyKindRangeDelete) 231 return formatAlphabeticSpans(spans) 232 }() 233 234 default: 235 return fmt.Sprintf("unknown command: %s", d.Cmd) 236 } 237 }) 238 } 239 240 func TestFragmenter_Values(t *testing.T) { 241 cmp := base.DefaultComparer.Compare 242 fmtKey := base.DefaultComparer.FormatKey 243 244 datadriven.RunTest(t, "testdata/fragmenter_values", func(d *datadriven.TestData) string { 245 switch d.Cmd { 246 case "build": 247 return func() (result string) { 248 defer func() { 249 if r := recover(); r != nil { 250 result = fmt.Sprint(r) 251 } 252 }() 253 254 spans := buildSpans(t, cmp, fmtKey, d.Input, base.InternalKeyKindRangeKeySet) 255 return formatAlphabeticSpans(spans) 256 }() 257 258 default: 259 return fmt.Sprintf("unknown command: %s", d.Cmd) 260 } 261 }) 262 } 263 264 func TestFragmenter_EmitOrder(t *testing.T) { 265 var buf bytes.Buffer 266 267 datadriven.RunTest(t, "testdata/fragmenter_emit_order", func(d *datadriven.TestData) string { 268 switch d.Cmd { 269 case "build": 270 buf.Reset() 271 f := Fragmenter{ 272 Cmp: base.DefaultComparer.Compare, 273 Format: base.DefaultComparer.FormatKey, 274 Emit: func(span Span) { 275 fmt.Fprintf(&buf, "%s %s:", 276 base.DefaultComparer.FormatKey(span.Start), 277 base.DefaultComparer.FormatKey(span.End)) 278 for i, k := range span.Keys { 279 if i == 0 { 280 fmt.Fprint(&buf, " ") 281 } else { 282 fmt.Fprint(&buf, ", ") 283 } 284 fmt.Fprintf(&buf, "#%d,%s", k.SeqNum(), k.Kind()) 285 } 286 fmt.Fprintln(&buf, "\n-") 287 }, 288 } 289 for _, line := range strings.Split(d.Input, "\n") { 290 fields := strings.Fields(line) 291 if len(fields) != 2 { 292 panic(fmt.Sprintf("datadriven test: expect 2 fields, found %d", len(fields))) 293 } 294 k := base.ParseInternalKey(fields[0]) 295 f.Add(Span{ 296 Start: k.UserKey, 297 End: []byte(fields[1]), 298 Keys: []Key{{Trailer: k.Trailer}}, 299 }) 300 } 301 302 f.Finish() 303 return buf.String() 304 default: 305 panic(fmt.Sprintf("unrecognized command %q", d.Cmd)) 306 } 307 }) 308 }