From e9b2cab0fa6fd0536cf472967cd82f418218c81c Mon Sep 17 00:00:00 2001 From: Tommi Virtanen Date: Sun, 23 Mar 2014 14:40:08 -0700 Subject: [PATCH] Re-add tests for write failures Commit d2173f5f0ecbf4ed93c768e975435b04df3186ec removed the complete os & syscall mocking layer as overly complex. This commit adds back the simplest possible thing: hooks to control the database file writes. Missing tests: TestDBOpenMetaFileError, TestDBMmapStatError. These are harder to test without more extensive mocking. Conflicts: db_test.go --- db.go | 15 ++++++++++++- db_test.go | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ tx.go | 4 ++-- 3 files changed, 78 insertions(+), 3 deletions(-) diff --git a/db.go b/db.go index 51c24d3..5ec75b0 100644 --- a/db.go +++ b/db.go @@ -34,6 +34,11 @@ type DB struct { rwlock sync.Mutex // Allows only one writer at a time. metalock sync.Mutex // Protects meta page access. mmaplock sync.RWMutex // Protects mmap access during remapping. + + ops struct { + writeAt func(b []byte, off int64) (n int, err error) + metaWriteAt func(b []byte, off int64) (n int, err error) + } } // Path returns the path to currently open database file. @@ -74,6 +79,14 @@ func (db *DB) Open(path string, mode os.FileMode) error { return err } + // default values for test hooks + if db.ops.writeAt == nil { + db.ops.writeAt = db.file.WriteAt + } + if db.ops.metaWriteAt == nil { + db.ops.metaWriteAt = db.metafile.WriteAt + } + // Initialize the database if it doesn't exist. if info, err := db.file.Stat(); err != nil { return &Error{"stat error", err} @@ -226,7 +239,7 @@ func (db *DB) init() error { p.count = 0 // Write the buffer to our data file. - if _, err := db.metafile.WriteAt(buf, 0); err != nil { + if _, err := db.ops.metaWriteAt(buf, 0); err != nil { return err } diff --git a/db_test.go b/db_test.go index 2882ba8..b363486 100644 --- a/db_test.go +++ b/db_test.go @@ -1,6 +1,7 @@ package bolt import ( + "io" "io/ioutil" "math/rand" "os" @@ -50,6 +51,67 @@ func TestDBReopen(t *testing.T) { }) } +// Ensure that the database returns an error if the file handle cannot be open. +func TestDBOpenFileError(t *testing.T) { + withDBFile(func(db *DB, path string) { + exp := &os.PathError{ + Op: "open", + Path: path + "/youre-not-my-real-parent", + Err: syscall.ENOTDIR, + } + err := db.Open(path+"/youre-not-my-real-parent", 0666) + assert.Equal(t, err, exp) + }) +} + +// Ensure that write errors to the meta file handler during initialization are returned. +func TestDBMetaInitWriteError(t *testing.T) { + withDB(func(db *DB, path string) { + // Mock the file system. + db.ops.metaWriteAt = func(p []byte, offset int64) (n int, err error) { return 0, io.ErrShortWrite } + + // Open the database. + err := db.Open(path, 0666) + assert.Equal(t, err, io.ErrShortWrite) + }) +} + +// Ensure that a database that is too small returns an error. +func TestDBFileTooSmall(t *testing.T) { + withDBFile(func(db *DB, path string) { + // corrupt the database + err := os.Truncate(path, int64(os.Getpagesize())) + assert.NoError(t, err) + + err = db.Open(path, 0666) + assert.Equal(t, err, &Error{"file size too small", nil}) + }) +} + +// Ensure that corrupt meta0 page errors get returned. +func TestDBCorruptMeta0(t *testing.T) { + withDB(func(db *DB, path string) { + var m meta + m.magic = magic + m.version = version + m.pageSize = 0x8000 + + // Create a file with bad magic. + b := make([]byte, 0x10000) + p0, p1 := (*page)(unsafe.Pointer(&b[0x0000])), (*page)(unsafe.Pointer(&b[0x8000])) + p0.meta().magic = 0 + p0.meta().version = version + p1.meta().magic = magic + p1.meta().version = version + err := ioutil.WriteFile(path, b, 0666) + assert.NoError(t, err) + + // Open the database. + err = db.Open(path, 0666) + assert.Equal(t, err, &Error{"meta error", ErrInvalid}) + }) +} + // Ensure that a database cannot open a transaction when it's not open. func TestDBTxErrDatabaseNotOpen(t *testing.T) { withDB(func(db *DB, path string) { diff --git a/tx.go b/tx.go index cb1ce5e..6464c2b 100644 --- a/tx.go +++ b/tx.go @@ -337,7 +337,7 @@ func (t *Tx) write() error { size := (int(p.overflow) + 1) * t.db.pageSize buf := (*[maxAllocSize]byte)(unsafe.Pointer(p))[:size] offset := int64(p.id) * int64(t.db.pageSize) - if _, err := t.db.file.WriteAt(buf, offset); err != nil { + if _, err := t.db.ops.writeAt(buf, offset); err != nil { return err } } @@ -359,7 +359,7 @@ func (t *Tx) writeMeta() error { t.meta.write(p) // Write the meta page to file. - if _, err := t.db.metafile.WriteAt(buf, int64(p.id)*int64(t.db.pageSize)); err != nil { + if _, err := t.db.ops.metaWriteAt(buf, int64(p.id)*int64(t.db.pageSize)); err != nil { return err }