github.com/cockroachdb/pebble@v1.1.1-0.20240513155919-3622ade60459/sstable/writer_fixture_test.go (about)

     1  // Copyright 2019 The Cockroach Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
    12  // implied. See the License for the specific language governing
    13  // permissions and limitations under the License.
    14  
    15  package sstable
    16  
    17  import (
    18  	"bytes"
    19  	"fmt"
    20  	"math"
    21  	"os"
    22  	"path/filepath"
    23  	"testing"
    24  
    25  	"github.com/cockroachdb/errors"
    26  	"github.com/cockroachdb/pebble/bloom"
    27  	"github.com/cockroachdb/pebble/internal/base"
    28  	"github.com/stretchr/testify/require"
    29  )
    30  
    31  const (
    32  	noPrefixFilter = false
    33  	prefixFilter   = true
    34  
    35  	noFullKeyBloom = false
    36  	fullKeyBloom   = true
    37  
    38  	defaultIndexBlockSize = math.MaxInt32
    39  	smallIndexBlockSize   = 128
    40  )
    41  
    42  type keyCountPropertyCollector struct {
    43  	count int
    44  }
    45  
    46  func (c *keyCountPropertyCollector) Add(key InternalKey, value []byte) error {
    47  	c.count++
    48  	return nil
    49  }
    50  
    51  func (c *keyCountPropertyCollector) Finish(userProps map[string]string) error {
    52  	userProps["test.key-count"] = fmt.Sprint(c.count)
    53  	return nil
    54  }
    55  
    56  func (c *keyCountPropertyCollector) Name() string {
    57  	return "KeyCountPropertyCollector"
    58  }
    59  
    60  var fixtureComparer = func() *Comparer {
    61  	c := *base.DefaultComparer
    62  	// NB: this is named as such only to match the built-in RocksDB comparer.
    63  	c.Name = "leveldb.BytewiseComparator"
    64  	c.Split = func(a []byte) int {
    65  		// TODO(tbg): this matches logic in testdata/make-table.cc. It's
    66  		// difficult to provide a more meaningful prefix extractor on the given
    67  		// dataset since it's not MVCC, and so it's impossible to come up with a
    68  		// sensible one. We need to add a better dataset and use that instead to
    69  		// get confidence that prefix extractors are working as intended.
    70  		return len(a)
    71  	}
    72  	return &c
    73  }()
    74  
    75  type fixtureOpts struct {
    76  	compression    Compression
    77  	fullKeyFilter  bool
    78  	prefixFilter   bool
    79  	indexBlockSize int
    80  }
    81  
    82  func (o fixtureOpts) String() string {
    83  	return fmt.Sprintf(
    84  		"compression=%s,fullKeyFilter=%t,prefixFilter=%t",
    85  		o.compression, o.fullKeyFilter, o.prefixFilter,
    86  	)
    87  }
    88  
    89  var fixtures = map[fixtureOpts]struct {
    90  	filename      string
    91  	comparer      *Comparer
    92  	propCollector func() TablePropertyCollector
    93  }{
    94  	{SnappyCompression, noFullKeyBloom, noPrefixFilter, defaultIndexBlockSize}: {
    95  		"testdata/h.sst", nil,
    96  		func() TablePropertyCollector {
    97  			return &keyCountPropertyCollector{}
    98  		},
    99  	},
   100  	{SnappyCompression, fullKeyBloom, noPrefixFilter, defaultIndexBlockSize}: {
   101  		"testdata/h.table-bloom.sst", nil, nil,
   102  	},
   103  	{NoCompression, noFullKeyBloom, noPrefixFilter, defaultIndexBlockSize}: {
   104  		"testdata/h.no-compression.sst", nil,
   105  		func() TablePropertyCollector {
   106  			return &keyCountPropertyCollector{}
   107  		},
   108  	},
   109  	{NoCompression, fullKeyBloom, noPrefixFilter, defaultIndexBlockSize}: {
   110  		"testdata/h.table-bloom.no-compression.sst", nil, nil,
   111  	},
   112  	{NoCompression, noFullKeyBloom, prefixFilter, defaultIndexBlockSize}: {
   113  		"testdata/h.table-bloom.no-compression.prefix_extractor.no_whole_key_filter.sst",
   114  		fixtureComparer, nil,
   115  	},
   116  	{NoCompression, noFullKeyBloom, noPrefixFilter, smallIndexBlockSize}: {
   117  		"testdata/h.no-compression.two_level_index.sst", nil,
   118  		func() TablePropertyCollector {
   119  			return &keyCountPropertyCollector{}
   120  		},
   121  	},
   122  	{ZstdCompression, noFullKeyBloom, noPrefixFilter, defaultIndexBlockSize}: {
   123  		"testdata/h.zstd-compression.sst", nil,
   124  		func() TablePropertyCollector {
   125  			return &keyCountPropertyCollector{}
   126  		},
   127  	},
   128  }
   129  
   130  func runTestFixtureOutput(opts fixtureOpts) error {
   131  	fixture, ok := fixtures[opts]
   132  	if !ok {
   133  		return errors.Errorf("fixture missing: %+v", opts)
   134  	}
   135  
   136  	compression := opts.compression
   137  
   138  	var fp base.FilterPolicy
   139  	if opts.fullKeyFilter || opts.prefixFilter {
   140  		fp = bloom.FilterPolicy(10)
   141  	}
   142  	ftype := base.TableFilter
   143  
   144  	// Check that a freshly made table is byte-for-byte equal to a pre-made
   145  	// table.
   146  	want, err := os.ReadFile(filepath.FromSlash(fixture.filename))
   147  	if err != nil {
   148  		return err
   149  	}
   150  
   151  	f, err := build(compression, fp, ftype, fixture.comparer, fixture.propCollector, 2048, opts.indexBlockSize)
   152  	if err != nil {
   153  		return err
   154  	}
   155  	stat, err := f.Stat()
   156  	if err != nil {
   157  		return err
   158  	}
   159  	got := make([]byte, stat.Size())
   160  	_, err = f.ReadAt(got, 0)
   161  	if err != nil {
   162  		return err
   163  	}
   164  
   165  	if !bytes.Equal(got, want) {
   166  		i := 0
   167  		for ; i < len(got) && i < len(want) && got[i] == want[i]; i++ {
   168  		}
   169  		os.WriteFile("fail.txt", got, 0644)
   170  		return errors.Errorf("built table %s does not match pre-made table. From byte %d onwards,\ngot:\n% x\nwant:\n% x",
   171  			fixture.filename, i, got[i:], want[i:])
   172  	}
   173  	return nil
   174  }
   175  
   176  func TestFixtureOutput(t *testing.T) {
   177  	for opt := range fixtures {
   178  		// Note: we disabled the zstd fixture test when CGO_ENABLED=0, because the
   179  		// implementation between DataDog/zstd and klauspost/compress are
   180  		// different, which leads to different compression output
   181  		// <https://github.com/klauspost/compress/issues/109#issuecomment-498763233>.
   182  		// Since the fixture test requires bit-to-bit reproducibility, we cannot
   183  		// run the zstd test when the implementation is not based on facebook/zstd.
   184  		if !useStandardZstdLib && opt.compression == ZstdCompression {
   185  			continue
   186  		}
   187  		t.Run(opt.String(), func(t *testing.T) {
   188  			require.NoError(t, runTestFixtureOutput(opt))
   189  		})
   190  	}
   191  }