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  }