github.com/zuoyebang/bitalostable@v1.0.1-0.20240229032404-e3b99a834294/internal/metamorphic/test.go (about) 1 // Copyright 2019 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 metamorphic 6 7 import ( 8 "fmt" 9 "io" 10 "os" 11 "strings" 12 13 "github.com/cockroachdb/errors" 14 "github.com/zuoyebang/bitalostable" 15 "github.com/zuoyebang/bitalostable/internal/errorfs" 16 "github.com/zuoyebang/bitalostable/vfs" 17 ) 18 19 type test struct { 20 // The list of ops to execute. The ops refer to slots in the batches, iters, 21 // and snapshots slices. 22 ops []op 23 idx int 24 // The DB the test is run on. 25 dir string 26 db *bitalostable.DB 27 opts *bitalostable.Options 28 testOpts *testOptions 29 writeOpts *bitalostable.WriteOptions 30 tmpDir string 31 // The slots for the batches, iterators, and snapshots. These are read and 32 // written by the ops to pass state from one op to another. 33 batches []*bitalostable.Batch 34 iters []*retryableIter 35 snapshots []*bitalostable.Snapshot 36 } 37 38 func newTest(ops []op) *test { 39 return &test{ 40 ops: ops, 41 } 42 } 43 44 func (t *test) init(h *history, dir string, testOpts *testOptions) error { 45 t.dir = dir 46 t.testOpts = testOpts 47 t.writeOpts = bitalostable.NoSync 48 if testOpts.strictFS { 49 t.writeOpts = bitalostable.Sync 50 } 51 t.opts = testOpts.opts.EnsureDefaults() 52 t.opts.Logger = bitalostable.DefaultLogger 53 t.opts.EventListener = bitalostable.MakeLoggingEventListener(t.opts.Logger) 54 t.opts.DebugCheck = func(db *bitalostable.DB) error { 55 // Wrap the ordinary DebugCheckLevels with retrying 56 // of injected errors. 57 return withRetries(func() error { 58 return bitalostable.DebugCheckLevels(db) 59 }) 60 } 61 62 defer t.opts.Cache.Unref() 63 64 // If an error occurs and we were using an in-memory FS, attempt to clone to 65 // on-disk in order to allow post-mortem debugging. Note that always using 66 // the on-disk FS isn't desirable because there is a large performance 67 // difference between in-memory and on-disk which causes different code paths 68 // and timings to be exercised. 69 maybeExit := func(err error) { 70 if err == nil || errors.Is(err, errorfs.ErrInjected) { 71 return 72 } 73 t.maybeSaveData() 74 fmt.Fprintln(os.Stderr, err) 75 os.Exit(1) 76 } 77 78 // Exit early on any error from a background operation. 79 t.opts.EventListener.BackgroundError = func(err error) { 80 t.opts.Logger.Infof("background error: %s", err) 81 maybeExit(err) 82 } 83 t.opts.EventListener.CompactionEnd = func(info bitalostable.CompactionInfo) { 84 t.opts.Logger.Infof("%s", info) 85 maybeExit(info.Err) 86 } 87 t.opts.EventListener.FlushEnd = func(info bitalostable.FlushInfo) { 88 t.opts.Logger.Infof("%s", info) 89 if info.Err != nil && !strings.Contains(info.Err.Error(), "bitalostable: empty table") { 90 maybeExit(info.Err) 91 } 92 } 93 t.opts.EventListener.ManifestCreated = func(info bitalostable.ManifestCreateInfo) { 94 t.opts.Logger.Infof("%s", info) 95 maybeExit(info.Err) 96 } 97 t.opts.EventListener.ManifestDeleted = func(info bitalostable.ManifestDeleteInfo) { 98 t.opts.Logger.Infof("%s", info) 99 maybeExit(info.Err) 100 } 101 t.opts.EventListener.TableDeleted = func(info bitalostable.TableDeleteInfo) { 102 t.opts.Logger.Infof("%s", info) 103 maybeExit(info.Err) 104 } 105 t.opts.EventListener.TableIngested = func(info bitalostable.TableIngestInfo) { 106 t.opts.Logger.Infof("%s", info) 107 maybeExit(info.Err) 108 } 109 t.opts.EventListener.WALCreated = func(info bitalostable.WALCreateInfo) { 110 t.opts.Logger.Infof("%s", info) 111 maybeExit(info.Err) 112 } 113 t.opts.EventListener.WALDeleted = func(info bitalostable.WALDeleteInfo) { 114 t.opts.Logger.Infof("%s", info) 115 maybeExit(info.Err) 116 } 117 118 var db *bitalostable.DB 119 var err error 120 err = withRetries(func() error { 121 db, err = bitalostable.Open(dir, t.opts) 122 return err 123 }) 124 if err != nil { 125 return err 126 } 127 h.Recordf("db.Open() // %v", err) 128 129 t.tmpDir = t.opts.FS.PathJoin(dir, "tmp") 130 if err = t.opts.FS.MkdirAll(t.tmpDir, 0755); err != nil { 131 return err 132 } 133 if t.testOpts.strictFS { 134 // Sync the whole directory path for the tmpDir, since restartDB() is executed during 135 // the test. That would reset MemFS to the synced state, which would make an unsynced 136 // directory disappear in the middle of the test. It is the responsibility of the test 137 // (not Pebble) to ensure that it can write the ssts that it will subsequently ingest 138 // into Pebble. 139 for { 140 f, err := t.opts.FS.OpenDir(dir) 141 if err != nil { 142 return err 143 } 144 if err = f.Sync(); err != nil { 145 return err 146 } 147 if err = f.Close(); err != nil { 148 return err 149 } 150 if len(dir) == 1 { 151 break 152 } 153 dir = t.opts.FS.PathDir(dir) 154 // TODO(sbhola): PathDir returns ".", which OpenDir() complains about. Fix. 155 if len(dir) == 1 { 156 dir = "/" 157 } 158 } 159 } 160 161 t.db = db 162 return nil 163 } 164 165 func (t *test) restartDB() error { 166 if !t.testOpts.strictFS { 167 return nil 168 } 169 t.opts.Cache.Ref() 170 // The fs isn't necessarily a MemFS. 171 fs, ok := vfs.Root(t.opts.FS).(*vfs.MemFS) 172 if ok { 173 fs.SetIgnoreSyncs(true) 174 } 175 if err := t.db.Close(); err != nil { 176 return err 177 } 178 if ok { 179 fs.ResetToSyncedState() 180 fs.SetIgnoreSyncs(false) 181 } 182 err := withRetries(func() (err error) { 183 t.db, err = bitalostable.Open(t.dir, t.opts) 184 return err 185 }) 186 t.opts.Cache.Unref() 187 return err 188 } 189 190 // If an in-memory FS is being used, save the contents to disk. 191 func (t *test) maybeSaveData() { 192 rootFS := vfs.Root(t.opts.FS) 193 if rootFS == vfs.Default { 194 return 195 } 196 _ = os.RemoveAll(t.dir) 197 if _, err := vfs.Clone(rootFS, vfs.Default, t.dir, t.dir); err != nil { 198 t.opts.Logger.Infof("unable to clone: %s: %v", t.dir, err) 199 } 200 } 201 202 func (t *test) step(h *history) bool { 203 if t.idx >= len(t.ops) { 204 return false 205 } 206 t.ops[t.idx].run(t, h) 207 t.idx++ 208 return true 209 } 210 211 func (t *test) setBatch(id objID, b *bitalostable.Batch) { 212 if id.tag() != batchTag { 213 panic(fmt.Sprintf("invalid batch ID: %s", id)) 214 } 215 t.batches[id.slot()] = b 216 } 217 218 func (t *test) setIter(id objID, i *bitalostable.Iterator, filterMin, filterMax uint64) { 219 if id.tag() != iterTag { 220 panic(fmt.Sprintf("invalid iter ID: %s", id)) 221 } 222 t.iters[id.slot()] = &retryableIter{ 223 iter: i, 224 lastKey: nil, 225 filterMin: filterMin, 226 filterMax: filterMax, 227 } 228 } 229 230 func (t *test) setSnapshot(id objID, s *bitalostable.Snapshot) { 231 if id.tag() != snapTag { 232 panic(fmt.Sprintf("invalid snapshot ID: %s", id)) 233 } 234 t.snapshots[id.slot()] = s 235 } 236 237 func (t *test) clearObj(id objID) { 238 switch id.tag() { 239 case dbTag: 240 t.db = nil 241 case batchTag: 242 t.batches[id.slot()] = nil 243 case iterTag: 244 t.iters[id.slot()] = nil 245 case snapTag: 246 t.snapshots[id.slot()] = nil 247 } 248 } 249 250 func (t *test) getBatch(id objID) *bitalostable.Batch { 251 if id.tag() != batchTag { 252 panic(fmt.Sprintf("invalid batch ID: %s", id)) 253 } 254 return t.batches[id.slot()] 255 } 256 257 func (t *test) getCloser(id objID) io.Closer { 258 switch id.tag() { 259 case dbTag: 260 return t.db 261 case batchTag: 262 return t.batches[id.slot()] 263 case iterTag: 264 return t.iters[id.slot()] 265 case snapTag: 266 return t.snapshots[id.slot()] 267 } 268 panic(fmt.Sprintf("cannot close ID: %s", id)) 269 } 270 271 func (t *test) getIter(id objID) *retryableIter { 272 if id.tag() != iterTag { 273 panic(fmt.Sprintf("invalid iter ID: %s", id)) 274 } 275 return t.iters[id.slot()] 276 } 277 278 func (t *test) getReader(id objID) bitalostable.Reader { 279 switch id.tag() { 280 case dbTag: 281 return t.db 282 case batchTag: 283 return t.batches[id.slot()] 284 case snapTag: 285 return t.snapshots[id.slot()] 286 } 287 panic(fmt.Sprintf("invalid reader ID: %s", id)) 288 } 289 290 func (t *test) getWriter(id objID) bitalostable.Writer { 291 switch id.tag() { 292 case dbTag: 293 return t.db 294 case batchTag: 295 return t.batches[id.slot()] 296 } 297 panic(fmt.Sprintf("invalid writer ID: %s", id)) 298 }