aboutsummaryrefslogtreecommitdiff
path: root/db_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'db_test.go')
-rw-r--r--db_test.go115
1 files changed, 114 insertions, 1 deletions
diff --git a/db_test.go b/db_test.go
index ad17e87..dddf22b 100644
--- a/db_test.go
+++ b/db_test.go
@@ -224,6 +224,76 @@ func TestDB_Open_FileTooSmall(t *testing.T) {
equals(t, errors.New("file size too small"), err)
}
+// Ensure that a database can be opened in read-only mode by multiple processes
+// and that a database can not be opened in read-write mode and in read-only
+// mode at the same time.
+func TestOpen_ReadOnly(t *testing.T) {
+ bucket, key, value := []byte(`bucket`), []byte(`key`), []byte(`value`)
+
+ path := tempfile()
+ defer os.Remove(path)
+
+ // Open in read-write mode.
+ db, err := bolt.Open(path, 0666, nil)
+ ok(t, db.Update(func(tx *bolt.Tx) error {
+ b, err := tx.CreateBucket(bucket)
+ if err != nil {
+ return err
+ }
+ return b.Put(key, value)
+ }))
+ assert(t, db != nil, "")
+ assert(t, !db.IsReadOnly(), "")
+ ok(t, err)
+ ok(t, db.Close())
+
+ // Open in read-only mode.
+ db0, err := bolt.Open(path, 0666, &bolt.Options{ReadOnly: true})
+ ok(t, err)
+ defer db0.Close()
+
+ // Opening in read-write mode should return an error.
+ _, err = bolt.Open(path, 0666, &bolt.Options{Timeout: time.Millisecond * 100})
+ assert(t, err != nil, "")
+
+ // And again (in read-only mode).
+ db1, err := bolt.Open(path, 0666, &bolt.Options{ReadOnly: true})
+ ok(t, err)
+ defer db1.Close()
+
+ // Verify both read-only databases are accessible.
+ for _, db := range []*bolt.DB{db0, db1} {
+ // Verify is is in read only mode indeed.
+ assert(t, db.IsReadOnly(), "")
+
+ // Read-only databases should not allow updates.
+ assert(t,
+ bolt.ErrDatabaseReadOnly == db.Update(func(*bolt.Tx) error {
+ panic(`should never get here`)
+ }),
+ "")
+
+ // Read-only databases should not allow beginning writable txns.
+ _, err = db.Begin(true)
+ assert(t, bolt.ErrDatabaseReadOnly == err, "")
+
+ // Verify the data.
+ ok(t, db.View(func(tx *bolt.Tx) error {
+ b := tx.Bucket(bucket)
+ if b == nil {
+ return fmt.Errorf("expected bucket `%s`", string(bucket))
+ }
+
+ got := string(b.Get(key))
+ expected := string(value)
+ if got != expected {
+ return fmt.Errorf("expected `%s`, got `%s`", expected, got)
+ }
+ return nil
+ }))
+ }
+}
+
// TODO(benbjohnson): Test corruption at every byte of the first two pages.
// Ensure that a database cannot open a transaction when it's not open.
@@ -254,6 +324,49 @@ func TestDB_BeginRW_Closed(t *testing.T) {
assert(t, tx == nil, "")
}
+func TestDB_Close_PendingTx_RW(t *testing.T) { testDB_Close_PendingTx(t, true) }
+func TestDB_Close_PendingTx_RO(t *testing.T) { testDB_Close_PendingTx(t, false) }
+
+// Ensure that a database cannot close while transactions are open.
+func testDB_Close_PendingTx(t *testing.T, writable bool) {
+ db := NewTestDB()
+ defer db.Close()
+
+ // Start transaction.
+ tx, err := db.Begin(true)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Open update in separate goroutine.
+ done := make(chan struct{})
+ go func() {
+ db.Close()
+ close(done)
+ }()
+
+ // Ensure database hasn't closed.
+ time.Sleep(100 * time.Millisecond)
+ select {
+ case <-done:
+ t.Fatal("database closed too early")
+ default:
+ }
+
+ // Commit transaction.
+ if err := tx.Commit(); err != nil {
+ t.Fatal(err)
+ }
+
+ // Ensure database closed now.
+ time.Sleep(100 * time.Millisecond)
+ select {
+ case <-done:
+ default:
+ t.Fatal("database did not close")
+ }
+}
+
// Ensure a database can provide a transactional block.
func TestDB_Update(t *testing.T) {
db := NewTestDB()
@@ -678,7 +791,7 @@ func (db *TestDB) PrintStats() {
// MustCheck runs a consistency check on the database and panics if any errors are found.
func (db *TestDB) MustCheck() {
- db.View(func(tx *bolt.Tx) error {
+ db.Update(func(tx *bolt.Tx) error {
// Collect all the errors.
var errors []error
for err := range tx.Check() {