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  }