github.com/petermattis/pebble@v0.0.0-20190905164901-ab51a2166067/sstable/table_test.go (about)

     1  // Copyright 2011 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 sstable
     6  
     7  import (
     8  	"bufio"
     9  	"bytes"
    10  	"encoding/binary"
    11  	"fmt"
    12  	"io"
    13  	"math"
    14  	"os"
    15  	"path/filepath"
    16  	"sort"
    17  	"strings"
    18  	"testing"
    19  	"time"
    20  
    21  	"github.com/kr/pretty"
    22  	"github.com/petermattis/pebble/bloom"
    23  	"github.com/petermattis/pebble/internal/base"
    24  	"github.com/petermattis/pebble/vfs"
    25  	"github.com/stretchr/testify/require"
    26  	"golang.org/x/exp/rand"
    27  )
    28  
    29  // nonsenseWords are words that aren't in testdata/h.txt.
    30  var nonsenseWords = []string{
    31  	// Edge cases.
    32  	"",
    33  	"\x00",
    34  	"\xff",
    35  	"`",
    36  	"a\x00",
    37  	"aaaaaa",
    38  	"pol\x00nius",
    39  	"youth\x00",
    40  	"youti",
    41  	"zzzzzz",
    42  	// Capitalized versions of actual words in testdata/h.txt.
    43  	"A",
    44  	"Hamlet",
    45  	"thEE",
    46  	"YOUTH",
    47  	// The following were generated by http://soybomb.com/tricks/words/
    48  	"pectures",
    49  	"exectly",
    50  	"tricatrippian",
    51  	"recens",
    52  	"whiratroce",
    53  	"troped",
    54  	"balmous",
    55  	"droppewry",
    56  	"toilizing",
    57  	"crocias",
    58  	"eathrass",
    59  	"cheakden",
    60  	"speablett",
    61  	"skirinies",
    62  	"prefing",
    63  	"bonufacision",
    64  }
    65  
    66  var (
    67  	wordCount = map[string]string{}
    68  	minWord   = ""
    69  	maxWord   = ""
    70  )
    71  
    72  func init() {
    73  	f, err := os.Open(filepath.FromSlash("testdata/h.txt"))
    74  	if err != nil {
    75  		panic(err)
    76  	}
    77  	defer f.Close()
    78  	r := bufio.NewReader(f)
    79  
    80  	for first := true; ; {
    81  		s, err := r.ReadBytes('\n')
    82  		if err == io.EOF {
    83  			break
    84  		}
    85  		if err != nil {
    86  			panic(err)
    87  		}
    88  		k := strings.TrimSpace(string(s[8:]))
    89  		v := strings.TrimSpace(string(s[:8]))
    90  		wordCount[k] = v
    91  
    92  		if first {
    93  			first = false
    94  			minWord = k
    95  			maxWord = k
    96  			continue
    97  		}
    98  		if minWord > k {
    99  			minWord = k
   100  		}
   101  		if maxWord < k {
   102  			maxWord = k
   103  		}
   104  	}
   105  
   106  	if len(wordCount) != 1710 {
   107  		panic(fmt.Sprintf("h.txt entry count: got %d, want %d", len(wordCount), 1710))
   108  	}
   109  
   110  	for _, s := range nonsenseWords {
   111  		if _, ok := wordCount[s]; ok {
   112  			panic(fmt.Sprintf("nonsense word %q was in h.txt", s))
   113  		}
   114  	}
   115  }
   116  
   117  func check(f vfs.File, comparer *Comparer, fp FilterPolicy) error {
   118  	r, err := NewReader(f, 0, 0, &Options{
   119  		Comparer: comparer,
   120  		Levels: []TableOptions{{
   121  			FilterPolicy: fp,
   122  		}},
   123  	})
   124  	if err != nil {
   125  		return err
   126  	}
   127  
   128  	// Check that each key/value pair in wordCount is also in the table.
   129  	words := make([]string, 0, len(wordCount))
   130  	for k, v := range wordCount {
   131  		words = append(words, k)
   132  		// Check using Get.
   133  		if v1, err := r.get([]byte(k)); string(v1) != string(v) || err != nil {
   134  			return fmt.Errorf("Get %q: got (%q, %v), want (%q, %v)", k, v1, err, v, error(nil))
   135  		} else if len(v1) != cap(v1) {
   136  			return fmt.Errorf("Get %q: len(v1)=%d, cap(v1)=%d", k, len(v1), cap(v1))
   137  		}
   138  
   139  		// Check using SeekGE.
   140  		i := iterAdapter{r.NewIter(nil /* lower */, nil /* upper */)}
   141  		if !i.SeekGE([]byte(k)) || string(i.Key().UserKey) != k {
   142  			return fmt.Errorf("Find %q: key was not in the table", k)
   143  		}
   144  		if k1 := i.Key().UserKey; len(k1) != cap(k1) {
   145  			return fmt.Errorf("Find %q: len(k1)=%d, cap(k1)=%d", k, len(k1), cap(k1))
   146  		}
   147  		if string(i.Value()) != v {
   148  			return fmt.Errorf("Find %q: got value %q, want %q", k, i.Value(), v)
   149  		}
   150  		if v1 := i.Value(); len(v1) != cap(v1) {
   151  			return fmt.Errorf("Find %q: len(v1)=%d, cap(v1)=%d", k, len(v1), cap(v1))
   152  		}
   153  
   154  		// Check using SeekLT.
   155  		if !i.SeekLT([]byte(k)) {
   156  			i.First()
   157  		} else {
   158  			i.Next()
   159  		}
   160  		if string(i.Key().UserKey) != k {
   161  			return fmt.Errorf("Find %q: key was not in the table", k)
   162  		}
   163  		if k1 := i.Key().UserKey; len(k1) != cap(k1) {
   164  			return fmt.Errorf("Find %q: len(k1)=%d, cap(k1)=%d", k, len(k1), cap(k1))
   165  		}
   166  		if string(i.Value()) != v {
   167  			return fmt.Errorf("Find %q: got value %q, want %q", k, i.Value(), v)
   168  		}
   169  		if v1 := i.Value(); len(v1) != cap(v1) {
   170  			return fmt.Errorf("Find %q: len(v1)=%d, cap(v1)=%d", k, len(v1), cap(v1))
   171  		}
   172  
   173  		if err := i.Close(); err != nil {
   174  			return err
   175  		}
   176  	}
   177  
   178  	// Check that nonsense words are not in the table.
   179  	for _, s := range nonsenseWords {
   180  		// Check using Get.
   181  		if _, err := r.get([]byte(s)); err != base.ErrNotFound {
   182  			return fmt.Errorf("Get %q: got %v, want ErrNotFound", s, err)
   183  		}
   184  
   185  		// Check using Find.
   186  		i := iterAdapter{r.NewIter(nil /* lower */, nil /* upper */)}
   187  		if i.SeekGE([]byte(s)) && s == string(i.Key().UserKey) {
   188  			return fmt.Errorf("Find %q: unexpectedly found key in the table", s)
   189  		}
   190  		if err := i.Close(); err != nil {
   191  			return err
   192  		}
   193  	}
   194  
   195  	// Check that the number of keys >= a given start key matches the expected number.
   196  	var countTests = []struct {
   197  		count int
   198  		start string
   199  	}{
   200  		// cat h.txt | cut -c 9- | wc -l gives 1710.
   201  		{1710, ""},
   202  		// cat h.txt | cut -c 9- | grep -v "^[a-b]" | wc -l gives 1522.
   203  		{1522, "c"},
   204  		// cat h.txt | cut -c 9- | grep -v "^[a-j]" | wc -l gives 940.
   205  		{940, "k"},
   206  		// cat h.txt | cut -c 9- | grep -v "^[a-x]" | wc -l gives 12.
   207  		{12, "y"},
   208  		// cat h.txt | cut -c 9- | grep -v "^[a-z]" | wc -l gives 0.
   209  		{0, "~"},
   210  	}
   211  	for _, ct := range countTests {
   212  		n, i := 0, iterAdapter{r.NewIter(nil /* lower */, nil /* upper */)}
   213  		for valid := i.SeekGE([]byte(ct.start)); valid; valid = i.Next() {
   214  			n++
   215  		}
   216  		if n != ct.count {
   217  			return fmt.Errorf("count %q: got %d, want %d", ct.start, n, ct.count)
   218  		}
   219  		n = 0
   220  		for valid := i.Last(); valid; valid = i.Prev() {
   221  			if bytes.Compare(i.Key().UserKey, []byte(ct.start)) < 0 {
   222  				break
   223  			}
   224  			n++
   225  		}
   226  		if n != ct.count {
   227  			return fmt.Errorf("count %q: got %d, want %d", ct.start, n, ct.count)
   228  		}
   229  		if err := i.Close(); err != nil {
   230  			return err
   231  		}
   232  	}
   233  
   234  	// Check lower/upper bounds behavior. Randomly choose a lower and upper bound
   235  	// and then guarantee that iteration finds the expected number if entries.
   236  	rng := rand.New(rand.NewSource(uint64(time.Now().UnixNano())))
   237  	sort.Strings(words)
   238  	for i := 0; i < 10; i++ {
   239  		lowerIdx := -1
   240  		upperIdx := len(words)
   241  		if rng.Intn(5) != 0 {
   242  			lowerIdx = rng.Intn(len(words))
   243  		}
   244  		if rng.Intn(5) != 0 {
   245  			upperIdx = rng.Intn(len(words))
   246  		}
   247  		if lowerIdx > upperIdx {
   248  			lowerIdx, upperIdx = upperIdx, lowerIdx
   249  		}
   250  
   251  		var lower, upper []byte
   252  		if lowerIdx >= 0 {
   253  			lower = []byte(words[lowerIdx])
   254  		} else {
   255  			lowerIdx = 0
   256  		}
   257  		if upperIdx < len(words) {
   258  			upper = []byte(words[upperIdx])
   259  		}
   260  
   261  		i := iterAdapter{r.NewIter(lower, upper)}
   262  
   263  		{
   264  			// NB: the semantics of First are that it starts iteration from the
   265  			// beginning, not respecting the lower bound.
   266  			n := 0
   267  			for valid := i.First(); valid; valid = i.Next() {
   268  				n++
   269  			}
   270  			if expected := upperIdx; expected != n {
   271  				return fmt.Errorf("expected %d, but found %d", expected, n)
   272  			}
   273  		}
   274  
   275  		{
   276  			// NB: the semantics of Last are that it starts iteration from the end, not
   277  			// respecting the upper bound.
   278  			n := 0
   279  			for valid := i.Last(); valid; valid = i.Prev() {
   280  				n++
   281  			}
   282  			if expected := len(words) - lowerIdx; expected != n {
   283  				return fmt.Errorf("expected %d, but found %d", expected, n)
   284  			}
   285  		}
   286  
   287  		if lower != nil {
   288  			n := 0
   289  			for valid := i.SeekGE(lower); valid; valid = i.Next() {
   290  				n++
   291  			}
   292  			if expected := upperIdx - lowerIdx; expected != n {
   293  				return fmt.Errorf("expected %d, but found %d", expected, n)
   294  			}
   295  		}
   296  
   297  		if upper != nil {
   298  			n := 0
   299  			for valid := i.SeekLT(upper); valid; valid = i.Prev() {
   300  				n++
   301  			}
   302  			if expected := upperIdx - lowerIdx; expected != n {
   303  				return fmt.Errorf("expected %d, but found %d", expected, n)
   304  			}
   305  		}
   306  
   307  		if err := i.Close(); err != nil {
   308  			return err
   309  		}
   310  	}
   311  
   312  	return r.Close()
   313  }
   314  
   315  var (
   316  	memFileSystem = vfs.NewMem()
   317  	tmpFileCount  int
   318  )
   319  
   320  func build(
   321  	compression Compression,
   322  	fp FilterPolicy,
   323  	ftype FilterType,
   324  	comparer *Comparer,
   325  	propCollector func() TablePropertyCollector,
   326  	blockSize int,
   327  	indexBlockSize int,
   328  ) (vfs.File, error) {
   329  	// Create a sorted list of wordCount's keys.
   330  	keys := make([]string, len(wordCount))
   331  	i := 0
   332  	for k := range wordCount {
   333  		keys[i] = k
   334  		i++
   335  	}
   336  	sort.Strings(keys)
   337  
   338  	// Write the key/value pairs to a new table, in increasing key order.
   339  	filename := fmt.Sprintf("/tmp%d", tmpFileCount)
   340  	f0, err := memFileSystem.Create(filename)
   341  	if err != nil {
   342  		return nil, err
   343  	}
   344  	defer f0.Close()
   345  	tmpFileCount++
   346  
   347  	opts := &Options{
   348  		Merger: &base.Merger{
   349  			Name: "nullptr",
   350  		},
   351  		Comparer: comparer,
   352  	}
   353  	if propCollector != nil {
   354  		opts.TablePropertyCollectors = append(opts.TablePropertyCollectors, propCollector)
   355  	}
   356  
   357  	tableOpts := TableOptions{
   358  		BlockSize:      blockSize,
   359  		Compression:    compression,
   360  		FilterPolicy:   fp,
   361  		FilterType:     ftype,
   362  		IndexBlockSize: indexBlockSize,
   363  	}
   364  
   365  	w := NewWriter(f0, opts, tableOpts)
   366  	// Use rangeDelV1Format for testing byte equality with RocksDB.
   367  	w.rangeDelV1Format = true
   368  	var rangeDelLength int
   369  	var rangeDelCounter int
   370  	var rangeDelStart InternalKey
   371  	for i, k := range keys {
   372  		v := wordCount[k]
   373  		ikey := base.MakeInternalKey([]byte(k), 0, InternalKeyKindSet)
   374  		if err := w.Add(ikey, []byte(v)); err != nil {
   375  			return nil, err
   376  		}
   377  		// This mirrors the logic in `make-table.cc`. It adds range deletions of
   378  		// increasing length for every 100 keys added.
   379  		if i % 100 == 0 {
   380  			rangeDelStart = ikey.Clone()
   381  			rangeDelCounter = 0
   382  			rangeDelLength++
   383  		}
   384  		rangeDelCounter++
   385  
   386  		if rangeDelCounter == rangeDelLength {
   387  			if err := w.DeleteRange(rangeDelStart.UserKey, ikey.UserKey); err != nil {
   388  				return nil, err
   389  			}
   390  		}
   391  	}
   392  	if err := w.Close(); err != nil {
   393  		return nil, err
   394  	}
   395  
   396  	// Re-open that filename for reading.
   397  	f1, err := memFileSystem.Open(filename)
   398  	if err != nil {
   399  		return nil, err
   400  	}
   401  	return f1, nil
   402  }
   403  
   404  func testReader(t *testing.T, filename string, comparer *Comparer, fp FilterPolicy) {
   405  	// Check that we can read a pre-made table.
   406  	f, err := os.Open(filepath.FromSlash("testdata/" + filename))
   407  	if err != nil {
   408  		t.Error(err)
   409  		return
   410  	}
   411  	err = check(f, comparer, fp)
   412  	if err != nil {
   413  		t.Error(err)
   414  		return
   415  	}
   416  }
   417  
   418  func TestReaderLevelDB(t *testing.T)            { testReader(t, "h.ldb", nil, nil) }
   419  func TestReaderDefaultCompression(t *testing.T) { testReader(t, "h.sst", nil, nil) }
   420  func TestReaderNoCompression(t *testing.T)      { testReader(t, "h.no-compression.sst", nil, nil) }
   421  func TestReaderBlockBloomIgnored(t *testing.T) {
   422  	testReader(t, "h.block-bloom.no-compression.sst", nil, nil)
   423  }
   424  func TestReaderTableBloomIgnored(t *testing.T) {
   425  	testReader(t, "h.table-bloom.no-compression.sst", nil, nil)
   426  }
   427  
   428  func TestReaderBloomUsed(t *testing.T) {
   429  	// wantActualNegatives is the minimum number of nonsense words (i.e. false
   430  	// positives or true negatives) to run through our filter. Some nonsense
   431  	// words might be rejected even before the filtering step, if they are out
   432  	// of the [minWord, maxWord] range of keys in the table.
   433  	wantActualNegatives := 0
   434  	for _, s := range nonsenseWords {
   435  		if minWord < s && s < maxWord {
   436  			wantActualNegatives++
   437  		}
   438  	}
   439  
   440  	files := []struct {
   441  		path     string
   442  		comparer *Comparer
   443  	}{
   444  		{"h.table-bloom.no-compression.sst", nil},
   445  		{"h.table-bloom.no-compression.prefix_extractor.no_whole_key_filter.sst", fixtureComparer},
   446  	}
   447  	for _, tc := range files {
   448  		t.Run(tc.path, func(t *testing.T) {
   449  			for _, degenerate := range []bool{false, true} {
   450  				t.Run(fmt.Sprintf("degenerate=%t", degenerate), func(t *testing.T) {
   451  					c := &countingFilterPolicy{
   452  						FilterPolicy: bloom.FilterPolicy(10),
   453  						degenerate:   degenerate,
   454  					}
   455  					testReader(t, tc.path, tc.comparer, c)
   456  
   457  					if c.truePositives != len(wordCount) {
   458  						t.Errorf("degenerate=%t: true positives: got %d, want %d", degenerate, c.truePositives, len(wordCount))
   459  					}
   460  					if c.falseNegatives != 0 {
   461  						t.Errorf("degenerate=%t: false negatives: got %d, want %d", degenerate, c.falseNegatives, 0)
   462  					}
   463  
   464  					if got := c.falsePositives + c.trueNegatives; got < wantActualNegatives {
   465  						t.Errorf("degenerate=%t: actual negatives (false positives + true negatives): "+
   466  							"got %d (%d + %d), want >= %d",
   467  							degenerate, got, c.falsePositives, c.trueNegatives, wantActualNegatives)
   468  					}
   469  
   470  					if !degenerate {
   471  						// The true negative count should be much greater than the false
   472  						// positive count.
   473  						if c.trueNegatives < 10*c.falsePositives {
   474  							t.Errorf("degenerate=%t: true negative to false positive ratio (%d:%d) is too small",
   475  								degenerate, c.trueNegatives, c.falsePositives)
   476  						}
   477  					}
   478  				})
   479  			}
   480  		})
   481  	}
   482  }
   483  
   484  func TestBloomFilterFalsePositiveRate(t *testing.T) {
   485  	f, err := os.Open(filepath.FromSlash("testdata/h.table-bloom.no-compression.sst"))
   486  	if err != nil {
   487  		t.Fatal(err)
   488  	}
   489  	c := &countingFilterPolicy{
   490  		FilterPolicy: bloom.FilterPolicy(1),
   491  	}
   492  	r, err := NewReader(f, 0, 0, &Options{
   493  		Levels: []TableOptions{{
   494  			FilterPolicy: c,
   495  		}},
   496  	})
   497  	if err != nil {
   498  		t.Fatal(err)
   499  	}
   500  
   501  	const n = 10000
   502  	// key is a buffer that will be re-used for n Get calls, each with a
   503  	// different key. The "m" in the 2-byte prefix means that the key falls in
   504  	// the [minWord, maxWord] range and so will not be rejected prior to
   505  	// applying the Bloom filter. The "!" in the 2-byte prefix means that the
   506  	// key is not actually in the table. The filter will only see actual
   507  	// negatives: false positives or true negatives.
   508  	key := []byte("m!....")
   509  	for i := 0; i < n; i++ {
   510  		binary.LittleEndian.PutUint32(key[2:6], uint32(i))
   511  		r.get(key)
   512  	}
   513  
   514  	if c.truePositives != 0 {
   515  		t.Errorf("true positives: got %d, want 0", c.truePositives)
   516  	}
   517  	if c.falseNegatives != 0 {
   518  		t.Errorf("false negatives: got %d, want 0", c.falseNegatives)
   519  	}
   520  	if got := c.falsePositives + c.trueNegatives; got != n {
   521  		t.Errorf("actual negatives (false positives + true negatives): got %d (%d + %d), want %d",
   522  			got, c.falsePositives, c.trueNegatives, n)
   523  	}
   524  
   525  	// According the the comments in the C++ LevelDB code, the false positive
   526  	// rate should be approximately 1% for for bloom.FilterPolicy(10). The 10
   527  	// was the parameter used to write the .sst file. When reading the file,
   528  	// the 1 in the bloom.FilterPolicy(1) above doesn't matter, only the
   529  	// bloom.FilterPolicy matters.
   530  	if got := float64(100*c.falsePositives) / n; got < 0.2 || 5 < got {
   531  		t.Errorf("false positive rate: got %v%%, want approximately 1%%", got)
   532  	}
   533  }
   534  
   535  type countingFilterPolicy struct {
   536  	FilterPolicy
   537  	degenerate bool
   538  
   539  	truePositives  int
   540  	falsePositives int
   541  	falseNegatives int
   542  	trueNegatives  int
   543  }
   544  
   545  func (c *countingFilterPolicy) MayContain(ftype FilterType, filter, key []byte) bool {
   546  	got := true
   547  	if c.degenerate {
   548  		// When degenerate is true, we override the embedded FilterPolicy's
   549  		// MayContain method to always return true. Doing so is a valid, if
   550  		// inefficient, implementation of the FilterPolicy interface.
   551  	} else {
   552  		got = c.FilterPolicy.MayContain(ftype, filter, key)
   553  	}
   554  	_, want := wordCount[string(key)]
   555  
   556  	switch {
   557  	case got && want:
   558  		c.truePositives++
   559  	case got && !want:
   560  		c.falsePositives++
   561  	case !got && want:
   562  		c.falseNegatives++
   563  	case !got && !want:
   564  		c.trueNegatives++
   565  	}
   566  	return got
   567  }
   568  
   569  func TestWriterRoundTrip(t *testing.T) {
   570  	blockSizes := []int{100, 1000, 2048, 4096, math.MaxInt32}
   571  	for _, blockSize := range blockSizes {
   572  		for _, indexBlockSize := range blockSizes {
   573  			for name, fp := range map[string]FilterPolicy{
   574  				"none":       nil,
   575  				"bloom10bit": bloom.FilterPolicy(10),
   576  			} {
   577  				t.Run(fmt.Sprintf("bloom=%s", name), func(t *testing.T) {
   578  					f, err := build(base.DefaultCompression, fp, TableFilter,
   579  						nil, nil, blockSize, indexBlockSize)
   580  					if err != nil {
   581  						t.Fatal(err)
   582  					}
   583  					// Check that we can read a freshly made table.
   584  
   585  					err = check(f, nil, nil)
   586  					if err != nil {
   587  						t.Fatal(err)
   588  					}
   589  				})
   590  			}
   591  		}
   592  	}
   593  }
   594  
   595  func TestFinalBlockIsWritten(t *testing.T) {
   596  	keys := []string{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J"}
   597  	valueLengths := []int{0, 1, 22, 28, 33, 40, 50, 61, 87, 100, 143, 200}
   598  	xxx := bytes.Repeat([]byte("x"), valueLengths[len(valueLengths)-1])
   599  	for _, blockSize := range []int{5, 10, 25, 50, 100} {
   600  		for _, indexBlockSize := range []int{5, 10, 25, 50, 100, math.MaxInt32} {
   601  			for nk := 0; nk <= len(keys); nk++ {
   602  			loop:
   603  				for _, vLen := range valueLengths {
   604  					got, memFS := 0, vfs.NewMem()
   605  
   606  					wf, err := memFS.Create("foo")
   607  					if err != nil {
   608  						t.Errorf("nk=%d, vLen=%d: memFS create: %v", nk, vLen, err)
   609  						continue
   610  					}
   611  					w := NewWriter(wf, nil, TableOptions{
   612  						BlockSize:      blockSize,
   613  						IndexBlockSize: indexBlockSize,
   614  					})
   615  					for _, k := range keys[:nk] {
   616  						if err := w.Add(InternalKey{UserKey: []byte(k)}, xxx[:vLen]); err != nil {
   617  							t.Errorf("nk=%d, vLen=%d: set: %v", nk, vLen, err)
   618  							continue loop
   619  						}
   620  					}
   621  					if err := w.Close(); err != nil {
   622  						t.Errorf("nk=%d, vLen=%d: writer close: %v", nk, vLen, err)
   623  						continue
   624  					}
   625  
   626  					rf, err := memFS.Open("foo")
   627  					if err != nil {
   628  						t.Errorf("nk=%d, vLen=%d: memFS open: %v", nk, vLen, err)
   629  						continue
   630  					}
   631  					r, err := NewReader(rf, 0, 0, nil)
   632  					if err != nil {
   633  						t.Errorf("nk=%d, vLen=%d: reader open: %v", nk, vLen, err)
   634  					}
   635  					i := iterAdapter{r.NewIter(nil /* lower */, nil /* upper */)}
   636  					for valid := i.First(); valid; valid = i.Next() {
   637  						got++
   638  					}
   639  					if err := i.Close(); err != nil {
   640  						t.Errorf("nk=%d, vLen=%d: Iterator close: %v", nk, vLen, err)
   641  						continue
   642  					}
   643  					if err := r.Close(); err != nil {
   644  						t.Errorf("nk=%d, vLen=%d: reader close: %v", nk, vLen, err)
   645  						continue
   646  					}
   647  
   648  					if got != nk {
   649  						t.Errorf("nk=%2d, vLen=%3d: got %2d keys, want %2d", nk, vLen, got, nk)
   650  						continue
   651  					}
   652  				}
   653  			}
   654  		}
   655  	}
   656  }
   657  
   658  func TestReaderGlobalSeqNum(t *testing.T) {
   659  	f, err := os.Open(filepath.FromSlash("testdata/h.sst"))
   660  	if err != nil {
   661  		t.Fatal(err)
   662  	}
   663  	r, err := NewReader(f, 0, 0, nil)
   664  	if err != nil {
   665  		t.Fatal(err)
   666  	}
   667  	defer r.Close()
   668  
   669  	const globalSeqNum = 42
   670  	r.Properties.GlobalSeqNum = globalSeqNum
   671  
   672  	i := iterAdapter{r.NewIter(nil /* lower */, nil /* upper */)}
   673  	for valid := i.First(); valid; valid = i.Next() {
   674  		if globalSeqNum != i.Key().SeqNum() {
   675  			t.Fatalf("expected %d, but found %d", globalSeqNum, i.Key().SeqNum())
   676  		}
   677  	}
   678  }
   679  
   680  func TestMetaIndexEntriesSorted(t *testing.T) {
   681  	f, err := build(base.DefaultCompression, nil /* filter policy */,
   682  		TableFilter, nil, nil, 4096, 4096)
   683  	if err != nil {
   684  		t.Fatal(err)
   685  	}
   686  
   687  	r, err := NewReader(f, 0 /* dbNum */, 0 /* fileNum */, nil /* extra opts */)
   688  	if err != nil {
   689  		t.Fatal(err)
   690  	}
   691  
   692  	b, err := r.readBlock(r.metaIndexBH, nil /* transform */)
   693  	if err != nil {
   694  		t.Fatal(err)
   695  	}
   696  	i, err := newRawBlockIter(bytes.Compare, b.Get())
   697  	b.Release()
   698  	if err != nil {
   699  		t.Fatal(err)
   700  	}
   701  
   702  	var keys []string
   703  	for valid := i.First(); valid; valid = i.Next() {
   704  		keys = append(keys, string(i.Key().UserKey))
   705  	}
   706  	if !sort.StringsAreSorted(keys) {
   707  		t.Fatalf("metaindex block out of order: %v", keys)
   708  	}
   709  
   710  	if err := i.Close(); err != nil {
   711  		t.Fatal(err)
   712  	}
   713  }
   714  
   715  func TestFooterRoundTrip(t *testing.T) {
   716  	buf := make([]byte, 100+maxFooterLen)
   717  	for _, format := range []TableFormat{
   718  		TableFormatRocksDBv2,
   719  		TableFormatLevelDB,
   720  	} {
   721  		t.Run(fmt.Sprintf("format=%d", format), func(t *testing.T) {
   722  			for _, checksum := range []uint8{checksumCRC32c} {
   723  				t.Run(fmt.Sprintf("checksum=%d", checksum), func(t *testing.T) {
   724  					footer := footer{
   725  						format:      format,
   726  						checksum:    checksum,
   727  						metaindexBH: BlockHandle{Offset: 1, Length: 2},
   728  						indexBH:     BlockHandle{Offset: 3, Length: 4},
   729  					}
   730  					for _, offset := range []int64{0, 1, 100} {
   731  						t.Run(fmt.Sprintf("offset=%d", offset), func(t *testing.T) {
   732  							mem := vfs.NewMem()
   733  							f, err := mem.Create("test")
   734  							if err != nil {
   735  								t.Fatal(err)
   736  							}
   737  							if _, err := f.Write(buf[:offset]); err != nil {
   738  								t.Fatal(err)
   739  							}
   740  							encoded := footer.encode(buf[100:])
   741  							if _, err := f.Write(encoded); err != nil {
   742  								t.Fatal(err)
   743  							}
   744  							if err := f.Close(); err != nil {
   745  								t.Fatal(err)
   746  							}
   747  							footer.footerBH.Offset = uint64(offset)
   748  							footer.footerBH.Length = uint64(len(encoded))
   749  
   750  							f, err = mem.Open("test")
   751  							if err != nil {
   752  								t.Fatal(err)
   753  							}
   754  							result, err := readFooter(f)
   755  							if err != nil {
   756  								t.Fatal(err)
   757  							}
   758  							if err := f.Close(); err != nil {
   759  								t.Fatal(err)
   760  							}
   761  
   762  							if diff := pretty.Diff(footer, result); diff != nil {
   763  								t.Fatalf("expected %+v, but found %+v\n%s",
   764  									footer, result, strings.Join(diff, "\n"))
   765  							}
   766  						})
   767  					}
   768  				})
   769  			}
   770  		})
   771  	}
   772  }
   773  
   774  func TestReadFooter(t *testing.T) {
   775  	encode := func(format TableFormat, checksum uint8) string {
   776  		f := footer{
   777  			format:   format,
   778  			checksum: checksum,
   779  		}
   780  		return string(f.encode(make([]byte, maxFooterLen)))
   781  	}
   782  
   783  	testCases := []struct {
   784  		encoded  string
   785  		expected string
   786  	}{
   787  		{strings.Repeat("a", minFooterLen-1), "file size is too small"},
   788  		{strings.Repeat("a", levelDBFooterLen), "bad magic number"},
   789  		{strings.Repeat("a", rocksDBFooterLen), "bad magic number"},
   790  		{encode(TableFormatLevelDB, 0)[1:], "file size is too small"},
   791  		{encode(TableFormatRocksDBv2, 0)[1:], "footer too short"},
   792  		{encode(TableFormatRocksDBv2, noChecksum), "unsupported checksum type"},
   793  		{encode(TableFormatRocksDBv2, checksumXXHash), "unsupported checksum type"},
   794  	}
   795  	for _, c := range testCases {
   796  		t.Run("", func(t *testing.T) {
   797  			mem := vfs.NewMem()
   798  			f, err := mem.Create("test")
   799  			if err != nil {
   800  				t.Fatal(err)
   801  			}
   802  			if _, err := f.Write([]byte(c.encoded)); err != nil {
   803  				t.Fatal(err)
   804  			}
   805  			if err := f.Close(); err != nil {
   806  				t.Fatal(err)
   807  			}
   808  
   809  			f, err = mem.Open("test")
   810  			if err != nil {
   811  				t.Fatal(err)
   812  			}
   813  			if _, err := readFooter(f); err == nil {
   814  				t.Fatalf("expected %q, but found success", c.expected)
   815  			} else if !strings.Contains(err.Error(), c.expected) {
   816  				t.Fatalf("expected %q, but found %v", c.expected, err)
   817  			}
   818  		})
   819  	}
   820  }
   821  
   822  type errorPropCollector struct{}
   823  
   824  func (errorPropCollector) Add(key InternalKey, _ []byte) error {
   825  	return fmt.Errorf("add %s failed", key)
   826  }
   827  
   828  func (errorPropCollector) Finish(_ map[string]string) error {
   829  	return fmt.Errorf("finish failed")
   830  }
   831  
   832  func (errorPropCollector) Name() string {
   833  	return "errorPropCollector"
   834  }
   835  
   836  func TestTablePropertyCollectorErrors(t *testing.T) {
   837  	mem := vfs.NewMem()
   838  	f, err := mem.Create("foo")
   839  	if err != nil {
   840  		t.Fatal(err)
   841  	}
   842  
   843  	opts := &Options{}
   844  	opts.TablePropertyCollectors = append(opts.TablePropertyCollectors,
   845  		func() TablePropertyCollector {
   846  			return errorPropCollector{}
   847  		})
   848  
   849  	w := NewWriter(f, opts, TableOptions{})
   850  	require.Regexp(t, `add a#0,1 failed`, w.Set([]byte("a"), []byte("b")))
   851  	require.Regexp(t, `add c#0,0 failed`, w.Delete([]byte("c")))
   852  	require.Regexp(t, `add d#0,15 failed`, w.DeleteRange([]byte("d"), []byte("e")))
   853  	require.Regexp(t, `add f#0,2 failed`, w.Merge([]byte("f"), []byte("g")))
   854  	require.Regexp(t, `finish failed`, w.Close())
   855  }