// Copyright (c) 2012, Suryandaru Triandana // All rights reserved. // // Use of this source code is governed by a BSD-style license that can be // found in the LICENE file. package leveldb import ( "errors" "fmt" "io" "io/ioutil" "math/rand" "os" "path/filepath" "sync" "testing" "github.com/syndtr/goleveldb/leveldb/storage" "github.com/syndtr/goleveldb/leveldb/util" ) const typeShift = 4 var ( tsErrInvalidFile = errors.New("leveldb.testStorage: invalid file for argument") tsErrFileOpen = errors.New("leveldb.testStorage: file still open") ) var ( tsFSEnv = os.Getenv("GOLEVELDB_USEFS") tsTempdir = os.Getenv("GOLEVELDB_TEMPDIR") tsKeepFS = tsFSEnv == "2" tsFS = tsKeepFS || tsFSEnv == "" || tsFSEnv == "1" tsMU = &sync.Mutex{} tsNum = 0 ) type tsOp uint const ( tsOpOpen tsOp = iota tsOpCreate tsOpRead tsOpReadAt tsOpWrite tsOpSync tsOpNum ) type tsLock struct { ts *testStorage r util.Releaser } func (l tsLock) Release() { l.r.Release() l.ts.t.Log("I: storage lock released") } type tsReader struct { tf tsFile storage.Reader } func (tr tsReader) Read(b []byte) (n int, err error) { ts := tr.tf.ts ts.countRead(tr.tf.Type()) if tr.tf.shouldErrLocked(tsOpRead) { return 0, errors.New("leveldb.testStorage: emulated read error") } n, err = tr.Reader.Read(b) if err != nil && err != io.EOF { ts.t.Errorf("E: read error, num=%d type=%v n=%d: %v", tr.tf.Num(), tr.tf.Type(), n, err) } return } func (tr tsReader) ReadAt(b []byte, off int64) (n int, err error) { ts := tr.tf.ts ts.countRead(tr.tf.Type()) if tr.tf.shouldErrLocked(tsOpReadAt) { return 0, errors.New("leveldb.testStorage: emulated readAt error") } n, err = tr.Reader.ReadAt(b, off) if err != nil && err != io.EOF { ts.t.Errorf("E: readAt error, num=%d type=%v off=%d n=%d: %v", tr.tf.Num(), tr.tf.Type(), off, n, err) } return } func (tr tsReader) Close() (err error) { err = tr.Reader.Close() tr.tf.close("reader", err) return } type tsWriter struct { tf tsFile storage.Writer } func (tw tsWriter) Write(b []byte) (n int, err error) { if tw.tf.shouldErrLocked(tsOpWrite) { return 0, errors.New("leveldb.testStorage: emulated write error") } n, err = tw.Writer.Write(b) if err != nil { tw.tf.ts.t.Errorf("E: write error, num=%d type=%v n=%d: %v", tw.tf.Num(), tw.tf.Type(), n, err) } return } func (tw tsWriter) Sync() (err error) { ts := tw.tf.ts ts.mu.Lock() for ts.emuDelaySync&tw.tf.Type() != 0 { ts.cond.Wait() } ts.mu.Unlock() if tw.tf.shouldErrLocked(tsOpSync) { return errors.New("leveldb.testStorage: emulated sync error") } err = tw.Writer.Sync() if err != nil { tw.tf.ts.t.Errorf("E: sync error, num=%d type=%v: %v", tw.tf.Num(), tw.tf.Type(), err) } return } func (tw tsWriter) Close() (err error) { err = tw.Writer.Close() tw.tf.close("writer", err) return } type tsFile struct { ts *testStorage storage.File } func (tf tsFile) x() uint64 { return tf.Num()<>typeShift, storage.FileType(x)&storage.TypeAll ts.t.Errorf("E: * num=%d type=%v writer=%v", num, tt, writer) } } ts.mu.Unlock() } func newTestStorage(t *testing.T) *testStorage { var stor storage.Storage var closeFn func() error if tsFS { for { tsMU.Lock() num := tsNum tsNum++ tsMU.Unlock() tempdir := tsTempdir if tempdir == "" { tempdir = os.TempDir() } path := filepath.Join(tempdir, fmt.Sprintf("goleveldb-test%d0%d0%d", os.Getuid(), os.Getpid(), num)) if _, err := os.Stat(path); err != nil { stor, err = storage.OpenFile(path) if err != nil { t.Fatalf("F: cannot create storage: %v", err) } t.Logf("I: storage created: %s", path) closeFn = func() error { for _, name := range []string{"LOG.old", "LOG"} { f, err := os.Open(filepath.Join(path, name)) if err != nil { continue } if log, err := ioutil.ReadAll(f); err != nil { t.Logf("---------------------- %s ----------------------", name) t.Logf("cannot read log: %v", err) t.Logf("---------------------- %s ----------------------", name) } else if len(log) > 0 { t.Logf("---------------------- %s ----------------------\n%s", name, string(log)) t.Logf("---------------------- %s ----------------------", name) } f.Close() } if t.Failed() { t.Logf("testing failed, test DB preserved at %s", path) return nil } if tsKeepFS { return nil } return os.RemoveAll(path) } break } } } else { stor = storage.NewMemStorage() } ts := &testStorage{ t: t, Storage: stor, closeFn: closeFn, opens: make(map[uint64]bool), emuErrOnceMap: make(map[uint64]uint), emuRandErrProb: 0x999, emuRandRand: rand.New(rand.NewSource(0xfacedead)), } ts.cond.L = &ts.mu return ts }