| // Copyright (c) 2012, Suryandaru Triandana <syndtr@gmail.com> |
| // 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 | uint64(tf.Type()) |
| } |
| |
| func (tf tsFile) shouldErr(op tsOp) bool { |
| return tf.ts.shouldErr(tf, op) |
| } |
| |
| func (tf tsFile) shouldErrLocked(op tsOp) bool { |
| tf.ts.mu.Lock() |
| defer tf.ts.mu.Unlock() |
| return tf.shouldErr(op) |
| } |
| |
| func (tf tsFile) checkOpen(m string) error { |
| ts := tf.ts |
| if writer, ok := ts.opens[tf.x()]; ok { |
| if writer { |
| ts.t.Errorf("E: cannot %s file, num=%d type=%v: a writer still open", m, tf.Num(), tf.Type()) |
| } else { |
| ts.t.Errorf("E: cannot %s file, num=%d type=%v: a reader still open", m, tf.Num(), tf.Type()) |
| } |
| return tsErrFileOpen |
| } |
| return nil |
| } |
| |
| func (tf tsFile) close(m string, err error) { |
| ts := tf.ts |
| ts.mu.Lock() |
| defer ts.mu.Unlock() |
| if _, ok := ts.opens[tf.x()]; !ok { |
| ts.t.Errorf("E: %s: redudant file closing, num=%d type=%v", m, tf.Num(), tf.Type()) |
| } else if err == nil { |
| ts.t.Logf("I: %s: file closed, num=%d type=%v", m, tf.Num(), tf.Type()) |
| } |
| delete(ts.opens, tf.x()) |
| if err != nil { |
| ts.t.Errorf("E: %s: cannot close file, num=%d type=%v: %v", m, tf.Num(), tf.Type(), err) |
| } |
| } |
| |
| func (tf tsFile) Open() (r storage.Reader, err error) { |
| ts := tf.ts |
| ts.mu.Lock() |
| defer ts.mu.Unlock() |
| err = tf.checkOpen("open") |
| if err != nil { |
| return |
| } |
| if tf.shouldErr(tsOpOpen) { |
| err = errors.New("leveldb.testStorage: emulated open error") |
| return |
| } |
| r, err = tf.File.Open() |
| if err != nil { |
| if ts.ignoreOpenErr&tf.Type() != 0 { |
| ts.t.Logf("I: cannot open file, num=%d type=%v: %v (ignored)", tf.Num(), tf.Type(), err) |
| } else { |
| ts.t.Errorf("E: cannot open file, num=%d type=%v: %v", tf.Num(), tf.Type(), err) |
| } |
| } else { |
| ts.t.Logf("I: file opened, num=%d type=%v", tf.Num(), tf.Type()) |
| ts.opens[tf.x()] = false |
| r = tsReader{tf, r} |
| } |
| return |
| } |
| |
| func (tf tsFile) Create() (w storage.Writer, err error) { |
| ts := tf.ts |
| ts.mu.Lock() |
| defer ts.mu.Unlock() |
| err = tf.checkOpen("create") |
| if err != nil { |
| return |
| } |
| if tf.shouldErr(tsOpCreate) { |
| err = errors.New("leveldb.testStorage: emulated create error") |
| return |
| } |
| w, err = tf.File.Create() |
| if err != nil { |
| ts.t.Errorf("E: cannot create file, num=%d type=%v: %v", tf.Num(), tf.Type(), err) |
| } else { |
| ts.t.Logf("I: file created, num=%d type=%v", tf.Num(), tf.Type()) |
| ts.opens[tf.x()] = true |
| w = tsWriter{tf, w} |
| } |
| return |
| } |
| |
| func (tf tsFile) Remove() (err error) { |
| ts := tf.ts |
| ts.mu.Lock() |
| defer ts.mu.Unlock() |
| err = tf.checkOpen("remove") |
| if err != nil { |
| return |
| } |
| err = tf.File.Remove() |
| if err != nil { |
| ts.t.Errorf("E: cannot remove file, num=%d type=%v: %v", tf.Num(), tf.Type(), err) |
| } else { |
| ts.t.Logf("I: file removed, num=%d type=%v", tf.Num(), tf.Type()) |
| } |
| return |
| } |
| |
| type testStorage struct { |
| t *testing.T |
| storage.Storage |
| closeFn func() error |
| |
| mu sync.Mutex |
| cond sync.Cond |
| // Open files, true=writer, false=reader |
| opens map[uint64]bool |
| emuDelaySync storage.FileType |
| ignoreOpenErr storage.FileType |
| readCnt uint64 |
| readCntEn storage.FileType |
| |
| emuErr [tsOpNum]storage.FileType |
| emuErrOnce [tsOpNum]storage.FileType |
| emuRandErr [tsOpNum]storage.FileType |
| emuRandErrProb int |
| emuErrOnceMap map[uint64]uint |
| emuRandRand *rand.Rand |
| } |
| |
| func (ts *testStorage) shouldErr(tf tsFile, op tsOp) bool { |
| if ts.emuErr[op]&tf.Type() != 0 { |
| return true |
| } else if ts.emuRandErr[op]&tf.Type() != 0 || ts.emuErrOnce[op]&tf.Type() != 0 { |
| sop := uint(1) << op |
| eop := ts.emuErrOnceMap[tf.x()] |
| if eop&sop == 0 && (ts.emuRandRand.Int()%ts.emuRandErrProb == 0 || ts.emuErrOnce[op]&tf.Type() != 0) { |
| ts.emuErrOnceMap[tf.x()] = eop | sop |
| ts.t.Logf("I: emulated error: file=%d type=%v op=%v", tf.Num(), tf.Type(), op) |
| return true |
| } |
| } |
| return false |
| } |
| |
| func (ts *testStorage) SetEmuErr(t storage.FileType, ops ...tsOp) { |
| ts.mu.Lock() |
| for _, op := range ops { |
| ts.emuErr[op] = t |
| } |
| ts.mu.Unlock() |
| } |
| |
| func (ts *testStorage) SetEmuErrOnce(t storage.FileType, ops ...tsOp) { |
| ts.mu.Lock() |
| for _, op := range ops { |
| ts.emuErrOnce[op] = t |
| } |
| ts.mu.Unlock() |
| } |
| |
| func (ts *testStorage) SetEmuRandErr(t storage.FileType, ops ...tsOp) { |
| ts.mu.Lock() |
| for _, op := range ops { |
| ts.emuRandErr[op] = t |
| } |
| ts.mu.Unlock() |
| } |
| |
| func (ts *testStorage) SetEmuRandErrProb(prob int) { |
| ts.mu.Lock() |
| ts.emuRandErrProb = prob |
| ts.mu.Unlock() |
| } |
| |
| func (ts *testStorage) DelaySync(t storage.FileType) { |
| ts.mu.Lock() |
| ts.emuDelaySync |= t |
| ts.cond.Broadcast() |
| ts.mu.Unlock() |
| } |
| |
| func (ts *testStorage) ReleaseSync(t storage.FileType) { |
| ts.mu.Lock() |
| ts.emuDelaySync &= ^t |
| ts.cond.Broadcast() |
| ts.mu.Unlock() |
| } |
| |
| func (ts *testStorage) ReadCounter() uint64 { |
| ts.mu.Lock() |
| defer ts.mu.Unlock() |
| return ts.readCnt |
| } |
| |
| func (ts *testStorage) ResetReadCounter() { |
| ts.mu.Lock() |
| ts.readCnt = 0 |
| ts.mu.Unlock() |
| } |
| |
| func (ts *testStorage) SetReadCounter(t storage.FileType) { |
| ts.mu.Lock() |
| ts.readCntEn = t |
| ts.mu.Unlock() |
| } |
| |
| func (ts *testStorage) countRead(t storage.FileType) { |
| ts.mu.Lock() |
| if ts.readCntEn&t != 0 { |
| ts.readCnt++ |
| } |
| ts.mu.Unlock() |
| } |
| |
| func (ts *testStorage) SetIgnoreOpenErr(t storage.FileType) { |
| ts.ignoreOpenErr = t |
| } |
| |
| func (ts *testStorage) Lock() (r util.Releaser, err error) { |
| r, err = ts.Storage.Lock() |
| if err != nil { |
| ts.t.Logf("W: storage locking failed: %v", err) |
| } else { |
| ts.t.Log("I: storage locked") |
| r = tsLock{ts, r} |
| } |
| return |
| } |
| |
| func (ts *testStorage) Log(str string) { |
| ts.t.Log("L: " + str) |
| ts.Storage.Log(str) |
| } |
| |
| func (ts *testStorage) GetFile(num uint64, t storage.FileType) storage.File { |
| return tsFile{ts, ts.Storage.GetFile(num, t)} |
| } |
| |
| func (ts *testStorage) GetFiles(t storage.FileType) (ff []storage.File, err error) { |
| ff0, err := ts.Storage.GetFiles(t) |
| if err != nil { |
| ts.t.Errorf("E: get files failed: %v", err) |
| return |
| } |
| ff = make([]storage.File, len(ff0)) |
| for i, f := range ff0 { |
| ff[i] = tsFile{ts, f} |
| } |
| ts.t.Logf("I: get files, type=0x%x count=%d", int(t), len(ff)) |
| return |
| } |
| |
| func (ts *testStorage) GetManifest() (f storage.File, err error) { |
| f0, err := ts.Storage.GetManifest() |
| if err != nil { |
| if !os.IsNotExist(err) { |
| ts.t.Errorf("E: get manifest failed: %v", err) |
| } |
| return |
| } |
| f = tsFile{ts, f0} |
| ts.t.Logf("I: get manifest, num=%d", f.Num()) |
| return |
| } |
| |
| func (ts *testStorage) SetManifest(f storage.File) error { |
| tf, ok := f.(tsFile) |
| if !ok { |
| ts.t.Error("E: set manifest failed: type assertion failed") |
| return tsErrInvalidFile |
| } else if tf.Type() != storage.TypeManifest { |
| ts.t.Errorf("E: set manifest failed: invalid file type: %s", tf.Type()) |
| return tsErrInvalidFile |
| } |
| err := ts.Storage.SetManifest(tf.File) |
| if err != nil { |
| ts.t.Errorf("E: set manifest failed: %v", err) |
| } else { |
| ts.t.Logf("I: set manifest, num=%d", tf.Num()) |
| } |
| return err |
| } |
| |
| func (ts *testStorage) Close() error { |
| ts.CloseCheck() |
| err := ts.Storage.Close() |
| if err != nil { |
| ts.t.Errorf("E: closing storage failed: %v", err) |
| } else { |
| ts.t.Log("I: storage closed") |
| } |
| if ts.closeFn != nil { |
| if err := ts.closeFn(); err != nil { |
| ts.t.Errorf("E: close function: %v", err) |
| } |
| } |
| return err |
| } |
| |
| func (ts *testStorage) CloseCheck() { |
| ts.mu.Lock() |
| if len(ts.opens) == 0 { |
| ts.t.Log("I: all files are closed") |
| } else { |
| ts.t.Errorf("E: %d files still open", len(ts.opens)) |
| for x, writer := range ts.opens { |
| num, tt := x>>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 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 |
| } |