github.com/petermattis/pebble@v0.0.0-20190905164901-ab51a2166067/internal/rangedel/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 rangedel 6 7 import ( 8 "bytes" 9 "fmt" 10 "regexp" 11 "strconv" 12 "strings" 13 "testing" 14 15 "github.com/petermattis/pebble/internal/base" 16 "github.com/petermattis/pebble/internal/datadriven" 17 ) 18 19 var tombstoneRe = regexp.MustCompile(`(\d+):\s*(\w+)-*(\w+)`) 20 21 func parseTombstone(t *testing.T, s string) Tombstone { 22 m := tombstoneRe.FindStringSubmatch(s) 23 if len(m) != 4 { 24 t.Fatalf("expected 4 components, but found %d: %s", len(m), s) 25 } 26 seqNum, err := strconv.Atoi(m[1]) 27 if err != nil { 28 t.Fatal(err) 29 } 30 return Tombstone{ 31 Start: base.MakeInternalKey([]byte(m[2]), uint64(seqNum), base.InternalKeyKindRangeDelete), 32 End: []byte(m[3]), 33 } 34 } 35 36 func buildTombstones(t *testing.T, cmp base.Compare, s string) []Tombstone { 37 var tombstones []Tombstone 38 f := &Fragmenter{ 39 Cmp: cmp, 40 Emit: func(fragmented []Tombstone) { 41 tombstones = append(tombstones, fragmented...) 42 }, 43 } 44 for _, line := range strings.Split(s, "\n") { 45 t := parseTombstone(t, line) 46 f.Add(t.Start, t.End) 47 } 48 f.Finish() 49 return tombstones 50 } 51 52 func formatTombstones(tombstones []Tombstone) string { 53 isLetter := func(b []byte) bool { 54 if len(b) != 1 { 55 return false 56 } 57 return b[0] >= 'a' && b[0] <= 'z' 58 } 59 60 var buf bytes.Buffer 61 for _, v := range tombstones { 62 if v.Empty() { 63 fmt.Fprintf(&buf, "<empty>\n") 64 continue 65 } 66 if !isLetter(v.Start.UserKey) || !isLetter(v.End) || v.Start.UserKey[0] == v.End[0] { 67 fmt.Fprintf(&buf, "%d: %s-%s\n", v.Start.SeqNum(), v.Start.UserKey, v.End) 68 continue 69 } 70 fmt.Fprintf(&buf, "%d: %s%s%s%s\n", 71 v.Start.SeqNum(), 72 strings.Repeat(" ", int(v.Start.UserKey[0]-'a')), 73 v.Start.UserKey, 74 strings.Repeat("-", int(v.End[0]-v.Start.UserKey[0]-1)), 75 v.End) 76 } 77 return buf.String() 78 } 79 80 func TestFragmenter(t *testing.T) { 81 cmp := base.DefaultComparer.Compare 82 83 var getRe = regexp.MustCompile(`(\w+)#(\d+)`) 84 85 parseGet := func(t *testing.T, s string) (string, int) { 86 m := getRe.FindStringSubmatch(s) 87 if len(m) != 3 { 88 t.Fatalf("expected 3 components, but found %d", len(m)) 89 } 90 seq, err := strconv.Atoi(m[2]) 91 if err != nil { 92 t.Fatal(err) 93 } 94 return m[1], seq 95 } 96 97 var iter iterator 98 99 // Returns true if the specified <key,seq> pair is deleted at the specified 100 // read sequence number. Get ignores tombstones newer than the read sequence 101 // number. This is a simple version of what full processing of range 102 // tombstones looks like. 103 deleted := func(key []byte, seq, readSeq uint64) bool { 104 tombstone := Get(cmp, iter, key, readSeq) 105 return tombstone.Deletes(seq) 106 } 107 108 datadriven.RunTest(t, "testdata/fragmenter", func(d *datadriven.TestData) string { 109 switch d.Cmd { 110 case "build": 111 tombstones := buildTombstones(t, cmp, d.Input) 112 iter = NewIter(cmp, tombstones) 113 return formatTombstones(tombstones) 114 115 case "get": 116 if len(d.CmdArgs) != 1 { 117 return fmt.Sprintf("expected 1 argument, but found %s", d.CmdArgs) 118 } 119 if d.CmdArgs[0].Key != "t" { 120 return fmt.Sprintf("expected timestamp argument, but found %s", d.CmdArgs[0]) 121 } 122 readSeq, err := strconv.Atoi(d.CmdArgs[0].Vals[0]) 123 if err != nil { 124 t.Fatal(err) 125 } 126 127 var results []string 128 for _, p := range strings.Split(d.Input, " ") { 129 key, seq := parseGet(t, p) 130 if deleted([]byte(key), uint64(seq), uint64(readSeq)) { 131 results = append(results, "deleted") 132 } else { 133 results = append(results, "alive") 134 } 135 } 136 return strings.Join(results, " ") 137 138 default: 139 return fmt.Sprintf("unknown command: %s", d.Cmd) 140 } 141 }) 142 } 143 144 func TestFragmenterDeleted(t *testing.T) { 145 datadriven.RunTest(t, "testdata/fragmenter_deleted", func(d *datadriven.TestData) string { 146 switch d.Cmd { 147 case "build": 148 f := &Fragmenter{ 149 Cmp: base.DefaultComparer.Compare, 150 Emit: func(fragmented []Tombstone) { 151 }, 152 } 153 var buf bytes.Buffer 154 for _, line := range strings.Split(d.Input, "\n") { 155 switch { 156 case strings.HasPrefix(line, "add "): 157 t := parseTombstone(t, strings.TrimPrefix(line, "add ")) 158 f.Add(t.Start, t.End) 159 case strings.HasPrefix(line, "deleted "): 160 key := base.ParseInternalKey(strings.TrimPrefix(line, "deleted ")) 161 func() { 162 defer func() { 163 if r := recover(); r != nil { 164 fmt.Fprintf(&buf, "%s: %s\n", key, r) 165 } 166 }() 167 fmt.Fprintf(&buf, "%s: %t\n", key, f.Deleted(key, base.InternalKeySeqNumMax)) 168 }() 169 } 170 } 171 return buf.String() 172 173 default: 174 return fmt.Sprintf("unknown command: %s", d.Cmd) 175 } 176 }) 177 }