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  }