github.com/petermattis/pebble@v0.0.0-20190905164901-ab51a2166067/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 "io/ioutil" 21 "math" 22 "path/filepath" 23 "testing" 24 25 "github.com/petermattis/pebble/bloom" 26 "github.com/petermattis/pebble/internal/base" 27 ) 28 29 const ( 30 uncompressed = false 31 compressed = true 32 33 noPrefixFilter = false 34 prefixFilter = true 35 36 noFullKeyBloom = false 37 fullKeyBloom = true 38 39 defaultIndexBlockSize = math.MaxInt32 40 smallIndexBlockSize = 128 41 ) 42 43 type keyCountPropertyCollector struct { 44 count int 45 } 46 47 func (c *keyCountPropertyCollector) Add(key InternalKey, value []byte) error { 48 c.count++ 49 return nil 50 } 51 52 func (c *keyCountPropertyCollector) Finish(userProps map[string]string) error { 53 userProps["test.key-count"] = fmt.Sprint(c.count) 54 return nil 55 } 56 57 func (c *keyCountPropertyCollector) Name() string { 58 return "KeyCountPropertyCollector" 59 } 60 61 //go:generate make -C ./testdata 62 var fixtureComparer = func() *Comparer { 63 c := *base.DefaultComparer 64 // NB: this is named as such only to match the built-in RocksDB comparer. 65 c.Name = "leveldb.BytewiseComparator" 66 c.Split = func(a []byte) int { 67 // TODO(tbg): this matches logic in testdata/make-table.cc. It's 68 // difficult to provide a more meaningful prefix extractor on the given 69 // dataset since it's not MVCC, and so it's impossible to come up with a 70 // sensible one. We need to add a better dataset and use that instead to 71 // get confidence that prefix extractors are working as intended. 72 return len(a) 73 } 74 return &c 75 }() 76 77 type fixtureOpts struct { 78 compression bool 79 fullKeyFilter bool 80 prefixFilter bool 81 indexBlockSize int 82 } 83 84 func (o fixtureOpts) String() string { 85 return fmt.Sprintf( 86 "compressed=%t,fullKeyFilter=%t,prefixFilter=%t", 87 o.compression, o.fullKeyFilter, o.prefixFilter, 88 ) 89 } 90 91 var fixtures = map[fixtureOpts]struct { 92 filename string 93 comparer *Comparer 94 propCollector func() TablePropertyCollector 95 }{ 96 {compressed, noFullKeyBloom, noPrefixFilter, defaultIndexBlockSize}: { 97 "testdata/h.sst", nil, 98 func() TablePropertyCollector { 99 return &keyCountPropertyCollector{} 100 }, 101 }, 102 {uncompressed, noFullKeyBloom, noPrefixFilter, defaultIndexBlockSize}: { 103 "testdata/h.no-compression.sst", nil, 104 func() TablePropertyCollector { 105 return &keyCountPropertyCollector{} 106 }, 107 }, 108 {uncompressed, fullKeyBloom, noPrefixFilter, defaultIndexBlockSize}: { 109 "testdata/h.table-bloom.no-compression.sst", nil, nil, 110 }, 111 {uncompressed, noFullKeyBloom, prefixFilter, defaultIndexBlockSize}: { 112 "testdata/h.table-bloom.no-compression.prefix_extractor.no_whole_key_filter.sst", 113 fixtureComparer, nil, 114 }, 115 {uncompressed, noFullKeyBloom, noPrefixFilter, smallIndexBlockSize}: { 116 "testdata/h.no-compression.two_level_index.sst", nil, 117 func() TablePropertyCollector { 118 return &keyCountPropertyCollector{} 119 }, 120 }, 121 } 122 123 func runTestFixtureOutput(opts fixtureOpts) error { 124 fixture, ok := fixtures[opts] 125 if !ok { 126 return fmt.Errorf("fixture missing: %+v", opts) 127 } 128 129 compression := base.NoCompression 130 if opts.compression { 131 compression = base.SnappyCompression 132 } 133 134 var fp base.FilterPolicy 135 if opts.fullKeyFilter || opts.prefixFilter { 136 fp = bloom.FilterPolicy(10) 137 } 138 ftype := base.TableFilter 139 140 // Check that a freshly made table is byte-for-byte equal to a pre-made 141 // table. 142 want, err := ioutil.ReadFile(filepath.FromSlash(fixture.filename)) 143 if err != nil { 144 return err 145 } 146 147 f, err := build(compression, fp, ftype, fixture.comparer, fixture.propCollector, 2048, opts.indexBlockSize) 148 if err != nil { 149 return err 150 } 151 stat, err := f.Stat() 152 if err != nil { 153 return err 154 } 155 got := make([]byte, stat.Size()) 156 _, err = f.ReadAt(got, 0) 157 if err != nil { 158 return err 159 } 160 161 if !bytes.Equal(got, want) { 162 i := 0 163 for ; i < len(got) && i < len(want) && got[i] == want[i]; i++ { 164 } 165 ioutil.WriteFile("fail.txt", got, 0644) 166 return fmt.Errorf("built table %s does not match pre-made table. From byte %d onwards,\ngot:\n% x\nwant:\n% x", 167 fixture.filename, i, got[i:], want[i:]) 168 } 169 return nil 170 } 171 172 func TestFixtureOutput(t *testing.T) { 173 for opt := range fixtures { 174 t.Run(opt.String(), func(t *testing.T) { 175 if err := runTestFixtureOutput(opt); err != nil { 176 t.Fatal(err) 177 } 178 }) 179 } 180 }