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 }