aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTommi Virtanen <tv@eagain.net>2014-03-23 14:40:08 -0700
committerBen Johnson <benbjohnson@yahoo.com>2014-03-24 07:47:33 -0600
commite9b2cab0fa6fd0536cf472967cd82f418218c81c (patch)
treeb87399eb867a2a4bfd7fe84c54d25108ebe625f3
parentResolve remaining errcheck warnings. (diff)
downloaddedo-e9b2cab0fa6fd0536cf472967cd82f418218c81c.tar.gz
dedo-e9b2cab0fa6fd0536cf472967cd82f418218c81c.tar.xz
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
-rw-r--r--db.go15
-rw-r--r--db_test.go62
-rw-r--r--tx.go4
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
}