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  }