1package bbolt_test
2
3import (
4	"bytes"
5	"encoding/binary"
6	"errors"
7	"flag"
8	"fmt"
9	"hash/fnv"
10	"io/ioutil"
11	"log"
12	"math/rand"
13	"os"
14	"path/filepath"
15	"regexp"
16	"sync"
17	"testing"
18	"time"
19	"unsafe"
20
21	bolt "go.etcd.io/bbolt"
22)
23
24var statsFlag = flag.Bool("stats", false, "show performance stats")
25
26// pageSize is the size of one page in the data file.
27const pageSize = 4096
28
29// pageHeaderSize is the size of a page header.
30const pageHeaderSize = 16
31
32// meta represents a simplified version of a database meta page for testing.
33type meta struct {
34	magic    uint32
35	version  uint32
36	_        uint32
37	_        uint32
38	_        [16]byte
39	_        uint64
40	pgid     uint64
41	_        uint64
42	checksum uint64
43}
44
45// Ensure that a database can be opened without error.
46func TestOpen(t *testing.T) {
47	path := tempfile()
48	defer os.RemoveAll(path)
49
50	db, err := bolt.Open(path, 0666, nil)
51	if err != nil {
52		t.Fatal(err)
53	} else if db == nil {
54		t.Fatal("expected db")
55	}
56
57	if s := db.Path(); s != path {
58		t.Fatalf("unexpected path: %s", s)
59	}
60
61	if err := db.Close(); err != nil {
62		t.Fatal(err)
63	}
64}
65
66// Regression validation for https://github.com/etcd-io/bbolt/pull/122.
67// Tests multiple goroutines simultaneously opening a database.
68func TestOpen_MultipleGoroutines(t *testing.T) {
69	const (
70		instances  = 30
71		iterations = 30
72	)
73	path := tempfile()
74	defer os.RemoveAll(path)
75	var wg sync.WaitGroup
76	errCh := make(chan error, iterations*instances)
77	for iteration := 0; iteration < iterations; iteration++ {
78		for instance := 0; instance < instances; instance++ {
79			wg.Add(1)
80			go func() {
81				defer wg.Done()
82				db, err := bolt.Open(path, 0600, nil)
83				if err != nil {
84					errCh <- err
85					return
86				}
87				if err := db.Close(); err != nil {
88					errCh <- err
89					return
90				}
91			}()
92		}
93		wg.Wait()
94	}
95	close(errCh)
96	for err := range errCh {
97		if err != nil {
98			t.Fatalf("error from inside goroutine: %v", err)
99		}
100	}
101}
102
103// Ensure that opening a database with a blank path returns an error.
104func TestOpen_ErrPathRequired(t *testing.T) {
105	_, err := bolt.Open("", 0666, nil)
106	if err == nil {
107		t.Fatalf("expected error")
108	}
109}
110
111// Ensure that opening a database with a bad path returns an error.
112func TestOpen_ErrNotExists(t *testing.T) {
113	_, err := bolt.Open(filepath.Join(tempfile(), "bad-path"), 0666, nil)
114	if err == nil {
115		t.Fatal("expected error")
116	}
117}
118
119// Ensure that opening a file that is not a Bolt database returns ErrInvalid.
120func TestOpen_ErrInvalid(t *testing.T) {
121	path := tempfile()
122	defer os.RemoveAll(path)
123
124	f, err := os.Create(path)
125	if err != nil {
126		t.Fatal(err)
127	}
128	if _, err := fmt.Fprintln(f, "this is not a bolt database"); err != nil {
129		t.Fatal(err)
130	}
131	if err := f.Close(); err != nil {
132		t.Fatal(err)
133	}
134
135	if _, err := bolt.Open(path, 0666, nil); err != bolt.ErrInvalid {
136		t.Fatalf("unexpected error: %s", err)
137	}
138}
139
140// Ensure that opening a file with two invalid versions returns ErrVersionMismatch.
141func TestOpen_ErrVersionMismatch(t *testing.T) {
142	if pageSize != os.Getpagesize() {
143		t.Skip("page size mismatch")
144	}
145
146	// Create empty database.
147	db := MustOpenDB()
148	path := db.Path()
149	defer db.MustClose()
150
151	// Close database.
152	if err := db.DB.Close(); err != nil {
153		t.Fatal(err)
154	}
155
156	// Read data file.
157	buf, err := ioutil.ReadFile(path)
158	if err != nil {
159		t.Fatal(err)
160	}
161
162	// Rewrite meta pages.
163	meta0 := (*meta)(unsafe.Pointer(&buf[pageHeaderSize]))
164	meta0.version++
165	meta1 := (*meta)(unsafe.Pointer(&buf[pageSize+pageHeaderSize]))
166	meta1.version++
167	if err := ioutil.WriteFile(path, buf, 0666); err != nil {
168		t.Fatal(err)
169	}
170
171	// Reopen data file.
172	if _, err := bolt.Open(path, 0666, nil); err != bolt.ErrVersionMismatch {
173		t.Fatalf("unexpected error: %s", err)
174	}
175}
176
177// Ensure that opening a file with two invalid checksums returns ErrChecksum.
178func TestOpen_ErrChecksum(t *testing.T) {
179	if pageSize != os.Getpagesize() {
180		t.Skip("page size mismatch")
181	}
182
183	// Create empty database.
184	db := MustOpenDB()
185	path := db.Path()
186	defer db.MustClose()
187
188	// Close database.
189	if err := db.DB.Close(); err != nil {
190		t.Fatal(err)
191	}
192
193	// Read data file.
194	buf, err := ioutil.ReadFile(path)
195	if err != nil {
196		t.Fatal(err)
197	}
198
199	// Rewrite meta pages.
200	meta0 := (*meta)(unsafe.Pointer(&buf[pageHeaderSize]))
201	meta0.pgid++
202	meta1 := (*meta)(unsafe.Pointer(&buf[pageSize+pageHeaderSize]))
203	meta1.pgid++
204	if err := ioutil.WriteFile(path, buf, 0666); err != nil {
205		t.Fatal(err)
206	}
207
208	// Reopen data file.
209	if _, err := bolt.Open(path, 0666, nil); err != bolt.ErrChecksum {
210		t.Fatalf("unexpected error: %s", err)
211	}
212}
213
214// Ensure that opening a database does not increase its size.
215// https://github.com/boltdb/bolt/issues/291
216func TestOpen_Size(t *testing.T) {
217	// Open a data file.
218	db := MustOpenDB()
219	path := db.Path()
220	defer db.MustClose()
221
222	pagesize := db.Info().PageSize
223
224	// Insert until we get above the minimum 4MB size.
225	if err := db.Update(func(tx *bolt.Tx) error {
226		b, _ := tx.CreateBucketIfNotExists([]byte("data"))
227		for i := 0; i < 10000; i++ {
228			if err := b.Put([]byte(fmt.Sprintf("%04d", i)), make([]byte, 1000)); err != nil {
229				t.Fatal(err)
230			}
231		}
232		return nil
233	}); err != nil {
234		t.Fatal(err)
235	}
236
237	// Close database and grab the size.
238	if err := db.DB.Close(); err != nil {
239		t.Fatal(err)
240	}
241	sz := fileSize(path)
242	if sz == 0 {
243		t.Fatalf("unexpected new file size: %d", sz)
244	}
245
246	// Reopen database, update, and check size again.
247	db0, err := bolt.Open(path, 0666, nil)
248	if err != nil {
249		t.Fatal(err)
250	}
251	if err := db0.Update(func(tx *bolt.Tx) error {
252		if err := tx.Bucket([]byte("data")).Put([]byte{0}, []byte{0}); err != nil {
253			t.Fatal(err)
254		}
255		return nil
256	}); err != nil {
257		t.Fatal(err)
258	}
259	if err := db0.Close(); err != nil {
260		t.Fatal(err)
261	}
262	newSz := fileSize(path)
263	if newSz == 0 {
264		t.Fatalf("unexpected new file size: %d", newSz)
265	}
266
267	// Compare the original size with the new size.
268	// db size might increase by a few page sizes due to the new small update.
269	if sz < newSz-5*int64(pagesize) {
270		t.Fatalf("unexpected file growth: %d => %d", sz, newSz)
271	}
272}
273
274// Ensure that opening a database beyond the max step size does not increase its size.
275// https://github.com/boltdb/bolt/issues/303
276func TestOpen_Size_Large(t *testing.T) {
277	if testing.Short() {
278		t.Skip("short mode")
279	}
280
281	// Open a data file.
282	db := MustOpenDB()
283	path := db.Path()
284	defer db.MustClose()
285
286	pagesize := db.Info().PageSize
287
288	// Insert until we get above the minimum 4MB size.
289	var index uint64
290	for i := 0; i < 10000; i++ {
291		if err := db.Update(func(tx *bolt.Tx) error {
292			b, _ := tx.CreateBucketIfNotExists([]byte("data"))
293			for j := 0; j < 1000; j++ {
294				if err := b.Put(u64tob(index), make([]byte, 50)); err != nil {
295					t.Fatal(err)
296				}
297				index++
298			}
299			return nil
300		}); err != nil {
301			t.Fatal(err)
302		}
303	}
304
305	// Close database and grab the size.
306	if err := db.DB.Close(); err != nil {
307		t.Fatal(err)
308	}
309	sz := fileSize(path)
310	if sz == 0 {
311		t.Fatalf("unexpected new file size: %d", sz)
312	} else if sz < (1 << 30) {
313		t.Fatalf("expected larger initial size: %d", sz)
314	}
315
316	// Reopen database, update, and check size again.
317	db0, err := bolt.Open(path, 0666, nil)
318	if err != nil {
319		t.Fatal(err)
320	}
321	if err := db0.Update(func(tx *bolt.Tx) error {
322		return tx.Bucket([]byte("data")).Put([]byte{0}, []byte{0})
323	}); err != nil {
324		t.Fatal(err)
325	}
326	if err := db0.Close(); err != nil {
327		t.Fatal(err)
328	}
329
330	newSz := fileSize(path)
331	if newSz == 0 {
332		t.Fatalf("unexpected new file size: %d", newSz)
333	}
334
335	// Compare the original size with the new size.
336	// db size might increase by a few page sizes due to the new small update.
337	if sz < newSz-5*int64(pagesize) {
338		t.Fatalf("unexpected file growth: %d => %d", sz, newSz)
339	}
340}
341
342// Ensure that a re-opened database is consistent.
343func TestOpen_Check(t *testing.T) {
344	path := tempfile()
345	defer os.RemoveAll(path)
346
347	db, err := bolt.Open(path, 0666, nil)
348	if err != nil {
349		t.Fatal(err)
350	}
351	if err = db.View(func(tx *bolt.Tx) error { return <-tx.Check() }); err != nil {
352		t.Fatal(err)
353	}
354	if err = db.Close(); err != nil {
355		t.Fatal(err)
356	}
357
358	db, err = bolt.Open(path, 0666, nil)
359	if err != nil {
360		t.Fatal(err)
361	}
362	if err := db.View(func(tx *bolt.Tx) error { return <-tx.Check() }); err != nil {
363		t.Fatal(err)
364	}
365	if err := db.Close(); err != nil {
366		t.Fatal(err)
367	}
368}
369
370// Ensure that write errors to the meta file handler during initialization are returned.
371func TestOpen_MetaInitWriteError(t *testing.T) {
372	t.Skip("pending")
373}
374
375// Ensure that a database that is too small returns an error.
376func TestOpen_FileTooSmall(t *testing.T) {
377	path := tempfile()
378	defer os.RemoveAll(path)
379
380	db, err := bolt.Open(path, 0666, nil)
381	if err != nil {
382		t.Fatal(err)
383	}
384	pageSize := int64(db.Info().PageSize)
385	if err = db.Close(); err != nil {
386		t.Fatal(err)
387	}
388
389	// corrupt the database
390	if err = os.Truncate(path, pageSize); err != nil {
391		t.Fatal(err)
392	}
393
394	db, err = bolt.Open(path, 0666, nil)
395	if err == nil || err.Error() != "file size too small" {
396		t.Fatalf("unexpected error: %s", err)
397	}
398}
399
400// TestDB_Open_InitialMmapSize tests if having InitialMmapSize large enough
401// to hold data from concurrent write transaction resolves the issue that
402// read transaction blocks the write transaction and causes deadlock.
403// This is a very hacky test since the mmap size is not exposed.
404func TestDB_Open_InitialMmapSize(t *testing.T) {
405	path := tempfile()
406	defer os.Remove(path)
407
408	initMmapSize := 1 << 30  // 1GB
409	testWriteSize := 1 << 27 // 134MB
410
411	db, err := bolt.Open(path, 0666, &bolt.Options{InitialMmapSize: initMmapSize})
412	if err != nil {
413		t.Fatal(err)
414	}
415
416	// create a long-running read transaction
417	// that never gets closed while writing
418	rtx, err := db.Begin(false)
419	if err != nil {
420		t.Fatal(err)
421	}
422
423	// create a write transaction
424	wtx, err := db.Begin(true)
425	if err != nil {
426		t.Fatal(err)
427	}
428
429	b, err := wtx.CreateBucket([]byte("test"))
430	if err != nil {
431		t.Fatal(err)
432	}
433
434	// and commit a large write
435	err = b.Put([]byte("foo"), make([]byte, testWriteSize))
436	if err != nil {
437		t.Fatal(err)
438	}
439
440	done := make(chan error, 1)
441
442	go func() {
443		err := wtx.Commit()
444		done <- err
445	}()
446
447	select {
448	case <-time.After(5 * time.Second):
449		t.Errorf("unexpected that the reader blocks writer")
450	case err := <-done:
451		if err != nil {
452			t.Fatal(err)
453		}
454	}
455
456	if err := rtx.Rollback(); err != nil {
457		t.Fatal(err)
458	}
459}
460
461// TestDB_Open_ReadOnly checks a database in read only mode can read but not write.
462func TestDB_Open_ReadOnly(t *testing.T) {
463	// Create a writable db, write k-v and close it.
464	db := MustOpenDB()
465	defer db.MustClose()
466
467	if err := db.Update(func(tx *bolt.Tx) error {
468		b, err := tx.CreateBucket([]byte("widgets"))
469		if err != nil {
470			t.Fatal(err)
471		}
472		if err := b.Put([]byte("foo"), []byte("bar")); err != nil {
473			t.Fatal(err)
474		}
475		return nil
476	}); err != nil {
477		t.Fatal(err)
478	}
479	if err := db.DB.Close(); err != nil {
480		t.Fatal(err)
481	}
482
483	f := db.f
484	o := &bolt.Options{ReadOnly: true}
485	readOnlyDB, err := bolt.Open(f, 0666, o)
486	if err != nil {
487		panic(err)
488	}
489
490	if !readOnlyDB.IsReadOnly() {
491		t.Fatal("expect db in read only mode")
492	}
493
494	// Read from a read-only transaction.
495	if err := readOnlyDB.View(func(tx *bolt.Tx) error {
496		value := tx.Bucket([]byte("widgets")).Get([]byte("foo"))
497		if !bytes.Equal(value, []byte("bar")) {
498			t.Fatal("expect value 'bar', got", value)
499		}
500		return nil
501	}); err != nil {
502		t.Fatal(err)
503	}
504
505	// Can't launch read-write transaction.
506	if _, err := readOnlyDB.Begin(true); err != bolt.ErrDatabaseReadOnly {
507		t.Fatalf("unexpected error: %s", err)
508	}
509
510	if err := readOnlyDB.Close(); err != nil {
511		t.Fatal(err)
512	}
513}
514
515// TestOpen_BigPage checks the database uses bigger pages when
516// changing PageSize.
517func TestOpen_BigPage(t *testing.T) {
518	pageSize := os.Getpagesize()
519
520	db1 := MustOpenWithOption(&bolt.Options{PageSize: pageSize * 2})
521	defer db1.MustClose()
522
523	db2 := MustOpenWithOption(&bolt.Options{PageSize: pageSize * 4})
524	defer db2.MustClose()
525
526	if db1sz, db2sz := fileSize(db1.f), fileSize(db2.f); db1sz >= db2sz {
527		t.Errorf("expected %d < %d", db1sz, db2sz)
528	}
529}
530
531// TestOpen_RecoverFreeList tests opening the DB with free-list
532// write-out after no free list sync will recover the free list
533// and write it out.
534func TestOpen_RecoverFreeList(t *testing.T) {
535	db := MustOpenWithOption(&bolt.Options{NoFreelistSync: true})
536	defer db.MustClose()
537
538	// Write some pages.
539	tx, err := db.Begin(true)
540	if err != nil {
541		t.Fatal(err)
542	}
543	wbuf := make([]byte, 8192)
544	for i := 0; i < 100; i++ {
545		s := fmt.Sprintf("%d", i)
546		b, err := tx.CreateBucket([]byte(s))
547		if err != nil {
548			t.Fatal(err)
549		}
550		if err = b.Put([]byte(s), wbuf); err != nil {
551			t.Fatal(err)
552		}
553	}
554	if err = tx.Commit(); err != nil {
555		t.Fatal(err)
556	}
557
558	// Generate free pages.
559	if tx, err = db.Begin(true); err != nil {
560		t.Fatal(err)
561	}
562	for i := 0; i < 50; i++ {
563		s := fmt.Sprintf("%d", i)
564		b := tx.Bucket([]byte(s))
565		if b == nil {
566			t.Fatal(err)
567		}
568		if err := b.Delete([]byte(s)); err != nil {
569			t.Fatal(err)
570		}
571	}
572	if err := tx.Commit(); err != nil {
573		t.Fatal(err)
574	}
575	if err := db.DB.Close(); err != nil {
576		t.Fatal(err)
577	}
578
579	// Record freelist count from opening with NoFreelistSync.
580	db.MustReopen()
581	freepages := db.Stats().FreePageN
582	if freepages == 0 {
583		t.Fatalf("no free pages on NoFreelistSync reopen")
584	}
585	if err := db.DB.Close(); err != nil {
586		t.Fatal(err)
587	}
588
589	// Check free page count is reconstructed when opened with freelist sync.
590	db.o = &bolt.Options{}
591	db.MustReopen()
592	// One less free page for syncing the free list on open.
593	freepages--
594	if fp := db.Stats().FreePageN; fp < freepages {
595		t.Fatalf("closed with %d free pages, opened with %d", freepages, fp)
596	}
597}
598
599// Ensure that a database cannot open a transaction when it's not open.
600func TestDB_Begin_ErrDatabaseNotOpen(t *testing.T) {
601	var db bolt.DB
602	if _, err := db.Begin(false); err != bolt.ErrDatabaseNotOpen {
603		t.Fatalf("unexpected error: %s", err)
604	}
605}
606
607// Ensure that a read-write transaction can be retrieved.
608func TestDB_BeginRW(t *testing.T) {
609	db := MustOpenDB()
610	defer db.MustClose()
611
612	tx, err := db.Begin(true)
613	if err != nil {
614		t.Fatal(err)
615	} else if tx == nil {
616		t.Fatal("expected tx")
617	}
618
619	if tx.DB() != db.DB {
620		t.Fatal("unexpected tx database")
621	} else if !tx.Writable() {
622		t.Fatal("expected writable tx")
623	}
624
625	if err := tx.Commit(); err != nil {
626		t.Fatal(err)
627	}
628}
629
630// TestDB_Concurrent_WriteTo checks that issuing WriteTo operations concurrently
631// with commits does not produce corrupted db files.
632func TestDB_Concurrent_WriteTo(t *testing.T) {
633	o := &bolt.Options{NoFreelistSync: false}
634	db := MustOpenWithOption(o)
635	defer db.MustClose()
636
637	var wg sync.WaitGroup
638	wtxs, rtxs := 5, 5
639	wg.Add(wtxs * rtxs)
640	f := func(tx *bolt.Tx) {
641		defer wg.Done()
642		f, err := ioutil.TempFile("", "bolt-")
643		if err != nil {
644			panic(err)
645		}
646		time.Sleep(time.Duration(rand.Intn(20)+1) * time.Millisecond)
647		tx.WriteTo(f)
648		tx.Rollback()
649		f.Close()
650		snap := &DB{nil, f.Name(), o}
651		snap.MustReopen()
652		defer snap.MustClose()
653		snap.MustCheck()
654	}
655
656	tx1, err := db.Begin(true)
657	if err != nil {
658		t.Fatal(err)
659	}
660	if _, err := tx1.CreateBucket([]byte("abc")); err != nil {
661		t.Fatal(err)
662	}
663	if err := tx1.Commit(); err != nil {
664		t.Fatal(err)
665	}
666
667	for i := 0; i < wtxs; i++ {
668		tx, err := db.Begin(true)
669		if err != nil {
670			t.Fatal(err)
671		}
672		if err := tx.Bucket([]byte("abc")).Put([]byte{0}, []byte{0}); err != nil {
673			t.Fatal(err)
674		}
675		for j := 0; j < rtxs; j++ {
676			rtx, rerr := db.Begin(false)
677			if rerr != nil {
678				t.Fatal(rerr)
679			}
680			go f(rtx)
681		}
682		if err := tx.Commit(); err != nil {
683			t.Fatal(err)
684		}
685	}
686	wg.Wait()
687}
688
689// Ensure that opening a transaction while the DB is closed returns an error.
690func TestDB_BeginRW_Closed(t *testing.T) {
691	var db bolt.DB
692	if _, err := db.Begin(true); err != bolt.ErrDatabaseNotOpen {
693		t.Fatalf("unexpected error: %s", err)
694	}
695}
696
697func TestDB_Close_PendingTx_RW(t *testing.T) { testDB_Close_PendingTx(t, true) }
698func TestDB_Close_PendingTx_RO(t *testing.T) { testDB_Close_PendingTx(t, false) }
699
700// Ensure that a database cannot close while transactions are open.
701func testDB_Close_PendingTx(t *testing.T, writable bool) {
702	db := MustOpenDB()
703
704	// Start transaction.
705	tx, err := db.Begin(writable)
706	if err != nil {
707		t.Fatal(err)
708	}
709
710	// Open update in separate goroutine.
711	done := make(chan error, 1)
712	go func() {
713		err := db.Close()
714		done <- err
715	}()
716
717	// Ensure database hasn't closed.
718	time.Sleep(100 * time.Millisecond)
719	select {
720	case err := <-done:
721		if err != nil {
722			t.Errorf("error from inside goroutine: %v", err)
723		}
724		t.Fatal("database closed too early")
725	default:
726	}
727
728	// Commit/close transaction.
729	if writable {
730		err = tx.Commit()
731	} else {
732		err = tx.Rollback()
733	}
734	if err != nil {
735		t.Fatal(err)
736	}
737
738	// Ensure database closed now.
739	time.Sleep(100 * time.Millisecond)
740	select {
741	case err := <-done:
742		if err != nil {
743			t.Fatalf("error from inside goroutine: %v", err)
744		}
745	default:
746		t.Fatal("database did not close")
747	}
748}
749
750// Ensure a database can provide a transactional block.
751func TestDB_Update(t *testing.T) {
752	db := MustOpenDB()
753	defer db.MustClose()
754	if err := db.Update(func(tx *bolt.Tx) error {
755		b, err := tx.CreateBucket([]byte("widgets"))
756		if err != nil {
757			t.Fatal(err)
758		}
759		if err := b.Put([]byte("foo"), []byte("bar")); err != nil {
760			t.Fatal(err)
761		}
762		if err := b.Put([]byte("baz"), []byte("bat")); err != nil {
763			t.Fatal(err)
764		}
765		if err := b.Delete([]byte("foo")); err != nil {
766			t.Fatal(err)
767		}
768		return nil
769	}); err != nil {
770		t.Fatal(err)
771	}
772	if err := db.View(func(tx *bolt.Tx) error {
773		b := tx.Bucket([]byte("widgets"))
774		if v := b.Get([]byte("foo")); v != nil {
775			t.Fatalf("expected nil value, got: %v", v)
776		}
777		if v := b.Get([]byte("baz")); !bytes.Equal(v, []byte("bat")) {
778			t.Fatalf("unexpected value: %v", v)
779		}
780		return nil
781	}); err != nil {
782		t.Fatal(err)
783	}
784}
785
786// Ensure a closed database returns an error while running a transaction block
787func TestDB_Update_Closed(t *testing.T) {
788	var db bolt.DB
789	if err := db.Update(func(tx *bolt.Tx) error {
790		if _, err := tx.CreateBucket([]byte("widgets")); err != nil {
791			t.Fatal(err)
792		}
793		return nil
794	}); err != bolt.ErrDatabaseNotOpen {
795		t.Fatalf("unexpected error: %s", err)
796	}
797}
798
799// Ensure a panic occurs while trying to commit a managed transaction.
800func TestDB_Update_ManualCommit(t *testing.T) {
801	db := MustOpenDB()
802	defer db.MustClose()
803
804	var panicked bool
805	if err := db.Update(func(tx *bolt.Tx) error {
806		func() {
807			defer func() {
808				if r := recover(); r != nil {
809					panicked = true
810				}
811			}()
812
813			if err := tx.Commit(); err != nil {
814				t.Fatal(err)
815			}
816		}()
817		return nil
818	}); err != nil {
819		t.Fatal(err)
820	} else if !panicked {
821		t.Fatal("expected panic")
822	}
823}
824
825// Ensure a panic occurs while trying to rollback a managed transaction.
826func TestDB_Update_ManualRollback(t *testing.T) {
827	db := MustOpenDB()
828	defer db.MustClose()
829
830	var panicked bool
831	if err := db.Update(func(tx *bolt.Tx) error {
832		func() {
833			defer func() {
834				if r := recover(); r != nil {
835					panicked = true
836				}
837			}()
838
839			if err := tx.Rollback(); err != nil {
840				t.Fatal(err)
841			}
842		}()
843		return nil
844	}); err != nil {
845		t.Fatal(err)
846	} else if !panicked {
847		t.Fatal("expected panic")
848	}
849}
850
851// Ensure a panic occurs while trying to commit a managed transaction.
852func TestDB_View_ManualCommit(t *testing.T) {
853	db := MustOpenDB()
854	defer db.MustClose()
855
856	var panicked bool
857	if err := db.View(func(tx *bolt.Tx) error {
858		func() {
859			defer func() {
860				if r := recover(); r != nil {
861					panicked = true
862				}
863			}()
864
865			if err := tx.Commit(); err != nil {
866				t.Fatal(err)
867			}
868		}()
869		return nil
870	}); err != nil {
871		t.Fatal(err)
872	} else if !panicked {
873		t.Fatal("expected panic")
874	}
875}
876
877// Ensure a panic occurs while trying to rollback a managed transaction.
878func TestDB_View_ManualRollback(t *testing.T) {
879	db := MustOpenDB()
880	defer db.MustClose()
881
882	var panicked bool
883	if err := db.View(func(tx *bolt.Tx) error {
884		func() {
885			defer func() {
886				if r := recover(); r != nil {
887					panicked = true
888				}
889			}()
890
891			if err := tx.Rollback(); err != nil {
892				t.Fatal(err)
893			}
894		}()
895		return nil
896	}); err != nil {
897		t.Fatal(err)
898	} else if !panicked {
899		t.Fatal("expected panic")
900	}
901}
902
903// Ensure a write transaction that panics does not hold open locks.
904func TestDB_Update_Panic(t *testing.T) {
905	db := MustOpenDB()
906	defer db.MustClose()
907
908	// Panic during update but recover.
909	func() {
910		defer func() {
911			if r := recover(); r != nil {
912				t.Log("recover: update", r)
913			}
914		}()
915
916		if err := db.Update(func(tx *bolt.Tx) error {
917			if _, err := tx.CreateBucket([]byte("widgets")); err != nil {
918				t.Fatal(err)
919			}
920			panic("omg")
921		}); err != nil {
922			t.Fatal(err)
923		}
924	}()
925
926	// Verify we can update again.
927	if err := db.Update(func(tx *bolt.Tx) error {
928		if _, err := tx.CreateBucket([]byte("widgets")); err != nil {
929			t.Fatal(err)
930		}
931		return nil
932	}); err != nil {
933		t.Fatal(err)
934	}
935
936	// Verify that our change persisted.
937	if err := db.Update(func(tx *bolt.Tx) error {
938		if tx.Bucket([]byte("widgets")) == nil {
939			t.Fatal("expected bucket")
940		}
941		return nil
942	}); err != nil {
943		t.Fatal(err)
944	}
945}
946
947// Ensure a database can return an error through a read-only transactional block.
948func TestDB_View_Error(t *testing.T) {
949	db := MustOpenDB()
950	defer db.MustClose()
951
952	if err := db.View(func(tx *bolt.Tx) error {
953		return errors.New("xxx")
954	}); err == nil || err.Error() != "xxx" {
955		t.Fatalf("unexpected error: %s", err)
956	}
957}
958
959// Ensure a read transaction that panics does not hold open locks.
960func TestDB_View_Panic(t *testing.T) {
961	db := MustOpenDB()
962	defer db.MustClose()
963
964	if err := db.Update(func(tx *bolt.Tx) error {
965		if _, err := tx.CreateBucket([]byte("widgets")); err != nil {
966			t.Fatal(err)
967		}
968		return nil
969	}); err != nil {
970		t.Fatal(err)
971	}
972
973	// Panic during view transaction but recover.
974	func() {
975		defer func() {
976			if r := recover(); r != nil {
977				t.Log("recover: view", r)
978			}
979		}()
980
981		if err := db.View(func(tx *bolt.Tx) error {
982			if tx.Bucket([]byte("widgets")) == nil {
983				t.Fatal("expected bucket")
984			}
985			panic("omg")
986		}); err != nil {
987			t.Fatal(err)
988		}
989	}()
990
991	// Verify that we can still use read transactions.
992	if err := db.View(func(tx *bolt.Tx) error {
993		if tx.Bucket([]byte("widgets")) == nil {
994			t.Fatal("expected bucket")
995		}
996		return nil
997	}); err != nil {
998		t.Fatal(err)
999	}
1000}
1001
1002// Ensure that DB stats can be returned.
1003func TestDB_Stats(t *testing.T) {
1004	db := MustOpenDB()
1005	defer db.MustClose()
1006	if err := db.Update(func(tx *bolt.Tx) error {
1007		_, err := tx.CreateBucket([]byte("widgets"))
1008		return err
1009	}); err != nil {
1010		t.Fatal(err)
1011	}
1012
1013	stats := db.Stats()
1014	if stats.TxStats.PageCount != 2 {
1015		t.Fatalf("unexpected TxStats.PageCount: %d", stats.TxStats.PageCount)
1016	} else if stats.FreePageN != 0 {
1017		t.Fatalf("unexpected FreePageN != 0: %d", stats.FreePageN)
1018	} else if stats.PendingPageN != 2 {
1019		t.Fatalf("unexpected PendingPageN != 2: %d", stats.PendingPageN)
1020	}
1021}
1022
1023// Ensure that database pages are in expected order and type.
1024func TestDB_Consistency(t *testing.T) {
1025	db := MustOpenDB()
1026	defer db.MustClose()
1027	if err := db.Update(func(tx *bolt.Tx) error {
1028		_, err := tx.CreateBucket([]byte("widgets"))
1029		return err
1030	}); err != nil {
1031		t.Fatal(err)
1032	}
1033
1034	for i := 0; i < 10; i++ {
1035		if err := db.Update(func(tx *bolt.Tx) error {
1036			if err := tx.Bucket([]byte("widgets")).Put([]byte("foo"), []byte("bar")); err != nil {
1037				t.Fatal(err)
1038			}
1039			return nil
1040		}); err != nil {
1041			t.Fatal(err)
1042		}
1043	}
1044
1045	if err := db.Update(func(tx *bolt.Tx) error {
1046		if p, _ := tx.Page(0); p == nil {
1047			t.Fatal("expected page")
1048		} else if p.Type != "meta" {
1049			t.Fatalf("unexpected page type: %s", p.Type)
1050		}
1051
1052		if p, _ := tx.Page(1); p == nil {
1053			t.Fatal("expected page")
1054		} else if p.Type != "meta" {
1055			t.Fatalf("unexpected page type: %s", p.Type)
1056		}
1057
1058		if p, _ := tx.Page(2); p == nil {
1059			t.Fatal("expected page")
1060		} else if p.Type != "free" {
1061			t.Fatalf("unexpected page type: %s", p.Type)
1062		}
1063
1064		if p, _ := tx.Page(3); p == nil {
1065			t.Fatal("expected page")
1066		} else if p.Type != "free" {
1067			t.Fatalf("unexpected page type: %s", p.Type)
1068		}
1069
1070		if p, _ := tx.Page(4); p == nil {
1071			t.Fatal("expected page")
1072		} else if p.Type != "leaf" {
1073			t.Fatalf("unexpected page type: %s", p.Type)
1074		}
1075
1076		if p, _ := tx.Page(5); p == nil {
1077			t.Fatal("expected page")
1078		} else if p.Type != "freelist" {
1079			t.Fatalf("unexpected page type: %s", p.Type)
1080		}
1081
1082		if p, _ := tx.Page(6); p != nil {
1083			t.Fatal("unexpected page")
1084		}
1085		return nil
1086	}); err != nil {
1087		t.Fatal(err)
1088	}
1089}
1090
1091// Ensure that DB stats can be subtracted from one another.
1092func TestDBStats_Sub(t *testing.T) {
1093	var a, b bolt.Stats
1094	a.TxStats.PageCount = 3
1095	a.FreePageN = 4
1096	b.TxStats.PageCount = 10
1097	b.FreePageN = 14
1098	diff := b.Sub(&a)
1099	if diff.TxStats.PageCount != 7 {
1100		t.Fatalf("unexpected TxStats.PageCount: %d", diff.TxStats.PageCount)
1101	}
1102
1103	// free page stats are copied from the receiver and not subtracted
1104	if diff.FreePageN != 14 {
1105		t.Fatalf("unexpected FreePageN: %d", diff.FreePageN)
1106	}
1107}
1108
1109// Ensure two functions can perform updates in a single batch.
1110func TestDB_Batch(t *testing.T) {
1111	db := MustOpenDB()
1112	defer db.MustClose()
1113
1114	if err := db.Update(func(tx *bolt.Tx) error {
1115		if _, err := tx.CreateBucket([]byte("widgets")); err != nil {
1116			t.Fatal(err)
1117		}
1118		return nil
1119	}); err != nil {
1120		t.Fatal(err)
1121	}
1122
1123	// Iterate over multiple updates in separate goroutines.
1124	n := 2
1125	ch := make(chan error)
1126	for i := 0; i < n; i++ {
1127		go func(i int) {
1128			ch <- db.Batch(func(tx *bolt.Tx) error {
1129				return tx.Bucket([]byte("widgets")).Put(u64tob(uint64(i)), []byte{})
1130			})
1131		}(i)
1132	}
1133
1134	// Check all responses to make sure there's no error.
1135	for i := 0; i < n; i++ {
1136		if err := <-ch; err != nil {
1137			t.Fatal(err)
1138		}
1139	}
1140
1141	// Ensure data is correct.
1142	if err := db.View(func(tx *bolt.Tx) error {
1143		b := tx.Bucket([]byte("widgets"))
1144		for i := 0; i < n; i++ {
1145			if v := b.Get(u64tob(uint64(i))); v == nil {
1146				t.Errorf("key not found: %d", i)
1147			}
1148		}
1149		return nil
1150	}); err != nil {
1151		t.Fatal(err)
1152	}
1153}
1154
1155func TestDB_Batch_Panic(t *testing.T) {
1156	db := MustOpenDB()
1157	defer db.MustClose()
1158
1159	var sentinel int
1160	var bork = &sentinel
1161	var problem interface{}
1162	var err error
1163
1164	// Execute a function inside a batch that panics.
1165	func() {
1166		defer func() {
1167			if p := recover(); p != nil {
1168				problem = p
1169			}
1170		}()
1171		err = db.Batch(func(tx *bolt.Tx) error {
1172			panic(bork)
1173		})
1174	}()
1175
1176	// Verify there is no error.
1177	if g, e := err, error(nil); g != e {
1178		t.Fatalf("wrong error: %v != %v", g, e)
1179	}
1180	// Verify the panic was captured.
1181	if g, e := problem, bork; g != e {
1182		t.Fatalf("wrong error: %v != %v", g, e)
1183	}
1184}
1185
1186func TestDB_BatchFull(t *testing.T) {
1187	db := MustOpenDB()
1188	defer db.MustClose()
1189	if err := db.Update(func(tx *bolt.Tx) error {
1190		_, err := tx.CreateBucket([]byte("widgets"))
1191		return err
1192	}); err != nil {
1193		t.Fatal(err)
1194	}
1195
1196	const size = 3
1197	// buffered so we never leak goroutines
1198	ch := make(chan error, size)
1199	put := func(i int) {
1200		ch <- db.Batch(func(tx *bolt.Tx) error {
1201			return tx.Bucket([]byte("widgets")).Put(u64tob(uint64(i)), []byte{})
1202		})
1203	}
1204
1205	db.MaxBatchSize = size
1206	// high enough to never trigger here
1207	db.MaxBatchDelay = 1 * time.Hour
1208
1209	go put(1)
1210	go put(2)
1211
1212	// Give the batch a chance to exhibit bugs.
1213	time.Sleep(10 * time.Millisecond)
1214
1215	// not triggered yet
1216	select {
1217	case <-ch:
1218		t.Fatalf("batch triggered too early")
1219	default:
1220	}
1221
1222	go put(3)
1223
1224	// Check all responses to make sure there's no error.
1225	for i := 0; i < size; i++ {
1226		if err := <-ch; err != nil {
1227			t.Fatal(err)
1228		}
1229	}
1230
1231	// Ensure data is correct.
1232	if err := db.View(func(tx *bolt.Tx) error {
1233		b := tx.Bucket([]byte("widgets"))
1234		for i := 1; i <= size; i++ {
1235			if v := b.Get(u64tob(uint64(i))); v == nil {
1236				t.Errorf("key not found: %d", i)
1237			}
1238		}
1239		return nil
1240	}); err != nil {
1241		t.Fatal(err)
1242	}
1243}
1244
1245func TestDB_BatchTime(t *testing.T) {
1246	db := MustOpenDB()
1247	defer db.MustClose()
1248	if err := db.Update(func(tx *bolt.Tx) error {
1249		_, err := tx.CreateBucket([]byte("widgets"))
1250		return err
1251	}); err != nil {
1252		t.Fatal(err)
1253	}
1254
1255	const size = 1
1256	// buffered so we never leak goroutines
1257	ch := make(chan error, size)
1258	put := func(i int) {
1259		ch <- db.Batch(func(tx *bolt.Tx) error {
1260			return tx.Bucket([]byte("widgets")).Put(u64tob(uint64(i)), []byte{})
1261		})
1262	}
1263
1264	db.MaxBatchSize = 1000
1265	db.MaxBatchDelay = 0
1266
1267	go put(1)
1268
1269	// Batch must trigger by time alone.
1270
1271	// Check all responses to make sure there's no error.
1272	for i := 0; i < size; i++ {
1273		if err := <-ch; err != nil {
1274			t.Fatal(err)
1275		}
1276	}
1277
1278	// Ensure data is correct.
1279	if err := db.View(func(tx *bolt.Tx) error {
1280		b := tx.Bucket([]byte("widgets"))
1281		for i := 1; i <= size; i++ {
1282			if v := b.Get(u64tob(uint64(i))); v == nil {
1283				t.Errorf("key not found: %d", i)
1284			}
1285		}
1286		return nil
1287	}); err != nil {
1288		t.Fatal(err)
1289	}
1290}
1291
1292func ExampleDB_Update() {
1293	// Open the database.
1294	db, err := bolt.Open(tempfile(), 0666, nil)
1295	if err != nil {
1296		log.Fatal(err)
1297	}
1298	defer os.Remove(db.Path())
1299
1300	// Execute several commands within a read-write transaction.
1301	if err := db.Update(func(tx *bolt.Tx) error {
1302		b, err := tx.CreateBucket([]byte("widgets"))
1303		if err != nil {
1304			return err
1305		}
1306		if err := b.Put([]byte("foo"), []byte("bar")); err != nil {
1307			return err
1308		}
1309		return nil
1310	}); err != nil {
1311		log.Fatal(err)
1312	}
1313
1314	// Read the value back from a separate read-only transaction.
1315	if err := db.View(func(tx *bolt.Tx) error {
1316		value := tx.Bucket([]byte("widgets")).Get([]byte("foo"))
1317		fmt.Printf("The value of 'foo' is: %s\n", value)
1318		return nil
1319	}); err != nil {
1320		log.Fatal(err)
1321	}
1322
1323	// Close database to release the file lock.
1324	if err := db.Close(); err != nil {
1325		log.Fatal(err)
1326	}
1327
1328	// Output:
1329	// The value of 'foo' is: bar
1330}
1331
1332func ExampleDB_View() {
1333	// Open the database.
1334	db, err := bolt.Open(tempfile(), 0666, nil)
1335	if err != nil {
1336		log.Fatal(err)
1337	}
1338	defer os.Remove(db.Path())
1339
1340	// Insert data into a bucket.
1341	if err := db.Update(func(tx *bolt.Tx) error {
1342		b, err := tx.CreateBucket([]byte("people"))
1343		if err != nil {
1344			return err
1345		}
1346		if err := b.Put([]byte("john"), []byte("doe")); err != nil {
1347			return err
1348		}
1349		if err := b.Put([]byte("susy"), []byte("que")); err != nil {
1350			return err
1351		}
1352		return nil
1353	}); err != nil {
1354		log.Fatal(err)
1355	}
1356
1357	// Access data from within a read-only transactional block.
1358	if err := db.View(func(tx *bolt.Tx) error {
1359		v := tx.Bucket([]byte("people")).Get([]byte("john"))
1360		fmt.Printf("John's last name is %s.\n", v)
1361		return nil
1362	}); err != nil {
1363		log.Fatal(err)
1364	}
1365
1366	// Close database to release the file lock.
1367	if err := db.Close(); err != nil {
1368		log.Fatal(err)
1369	}
1370
1371	// Output:
1372	// John's last name is doe.
1373}
1374
1375func ExampleDB_Begin() {
1376	// Open the database.
1377	db, err := bolt.Open(tempfile(), 0666, nil)
1378	if err != nil {
1379		log.Fatal(err)
1380	}
1381	defer os.Remove(db.Path())
1382
1383	// Create a bucket using a read-write transaction.
1384	if err = db.Update(func(tx *bolt.Tx) error {
1385		_, err := tx.CreateBucket([]byte("widgets"))
1386		return err
1387	}); err != nil {
1388		log.Fatal(err)
1389	}
1390
1391	// Create several keys in a transaction.
1392	tx, err := db.Begin(true)
1393	if err != nil {
1394		log.Fatal(err)
1395	}
1396	b := tx.Bucket([]byte("widgets"))
1397	if err = b.Put([]byte("john"), []byte("blue")); err != nil {
1398		log.Fatal(err)
1399	}
1400	if err = b.Put([]byte("abby"), []byte("red")); err != nil {
1401		log.Fatal(err)
1402	}
1403	if err = b.Put([]byte("zephyr"), []byte("purple")); err != nil {
1404		log.Fatal(err)
1405	}
1406	if err = tx.Commit(); err != nil {
1407		log.Fatal(err)
1408	}
1409
1410	// Iterate over the values in sorted key order.
1411	tx, err = db.Begin(false)
1412	if err != nil {
1413		log.Fatal(err)
1414	}
1415	c := tx.Bucket([]byte("widgets")).Cursor()
1416	for k, v := c.First(); k != nil; k, v = c.Next() {
1417		fmt.Printf("%s likes %s\n", k, v)
1418	}
1419
1420	if err = tx.Rollback(); err != nil {
1421		log.Fatal(err)
1422	}
1423
1424	if err = db.Close(); err != nil {
1425		log.Fatal(err)
1426	}
1427
1428	// Output:
1429	// abby likes red
1430	// john likes blue
1431	// zephyr likes purple
1432}
1433
1434func BenchmarkDBBatchAutomatic(b *testing.B) {
1435	db := MustOpenDB()
1436	defer db.MustClose()
1437	if err := db.Update(func(tx *bolt.Tx) error {
1438		_, err := tx.CreateBucket([]byte("bench"))
1439		return err
1440	}); err != nil {
1441		b.Fatal(err)
1442	}
1443
1444	b.ResetTimer()
1445	for i := 0; i < b.N; i++ {
1446		start := make(chan struct{})
1447		var wg sync.WaitGroup
1448
1449		for round := 0; round < 1000; round++ {
1450			wg.Add(1)
1451
1452			go func(id uint32) {
1453				defer wg.Done()
1454				<-start
1455
1456				h := fnv.New32a()
1457				buf := make([]byte, 4)
1458				binary.LittleEndian.PutUint32(buf, id)
1459				_, _ = h.Write(buf[:])
1460				k := h.Sum(nil)
1461				insert := func(tx *bolt.Tx) error {
1462					b := tx.Bucket([]byte("bench"))
1463					return b.Put(k, []byte("filler"))
1464				}
1465				if err := db.Batch(insert); err != nil {
1466					b.Error(err)
1467					return
1468				}
1469			}(uint32(round))
1470		}
1471		close(start)
1472		wg.Wait()
1473	}
1474
1475	b.StopTimer()
1476	validateBatchBench(b, db)
1477}
1478
1479func BenchmarkDBBatchSingle(b *testing.B) {
1480	db := MustOpenDB()
1481	defer db.MustClose()
1482	if err := db.Update(func(tx *bolt.Tx) error {
1483		_, err := tx.CreateBucket([]byte("bench"))
1484		return err
1485	}); err != nil {
1486		b.Fatal(err)
1487	}
1488
1489	b.ResetTimer()
1490	for i := 0; i < b.N; i++ {
1491		start := make(chan struct{})
1492		var wg sync.WaitGroup
1493
1494		for round := 0; round < 1000; round++ {
1495			wg.Add(1)
1496			go func(id uint32) {
1497				defer wg.Done()
1498				<-start
1499
1500				h := fnv.New32a()
1501				buf := make([]byte, 4)
1502				binary.LittleEndian.PutUint32(buf, id)
1503				_, _ = h.Write(buf[:])
1504				k := h.Sum(nil)
1505				insert := func(tx *bolt.Tx) error {
1506					b := tx.Bucket([]byte("bench"))
1507					return b.Put(k, []byte("filler"))
1508				}
1509				if err := db.Update(insert); err != nil {
1510					b.Error(err)
1511					return
1512				}
1513			}(uint32(round))
1514		}
1515		close(start)
1516		wg.Wait()
1517	}
1518
1519	b.StopTimer()
1520	validateBatchBench(b, db)
1521}
1522
1523func BenchmarkDBBatchManual10x100(b *testing.B) {
1524	db := MustOpenDB()
1525	defer db.MustClose()
1526	if err := db.Update(func(tx *bolt.Tx) error {
1527		_, err := tx.CreateBucket([]byte("bench"))
1528		return err
1529	}); err != nil {
1530		b.Fatal(err)
1531	}
1532
1533	b.ResetTimer()
1534	for i := 0; i < b.N; i++ {
1535		start := make(chan struct{})
1536		var wg sync.WaitGroup
1537		errCh := make(chan error, 10)
1538
1539		for major := 0; major < 10; major++ {
1540			wg.Add(1)
1541			go func(id uint32) {
1542				defer wg.Done()
1543				<-start
1544
1545				insert100 := func(tx *bolt.Tx) error {
1546					h := fnv.New32a()
1547					buf := make([]byte, 4)
1548					for minor := uint32(0); minor < 100; minor++ {
1549						binary.LittleEndian.PutUint32(buf, uint32(id*100+minor))
1550						h.Reset()
1551						_, _ = h.Write(buf[:])
1552						k := h.Sum(nil)
1553						b := tx.Bucket([]byte("bench"))
1554						if err := b.Put(k, []byte("filler")); err != nil {
1555							return err
1556						}
1557					}
1558					return nil
1559				}
1560				err := db.Update(insert100)
1561				errCh <- err
1562			}(uint32(major))
1563		}
1564		close(start)
1565		wg.Wait()
1566		close(errCh)
1567		for err := range errCh {
1568			if err != nil {
1569				b.Fatal(err)
1570			}
1571		}
1572	}
1573
1574	b.StopTimer()
1575	validateBatchBench(b, db)
1576}
1577
1578func validateBatchBench(b *testing.B, db *DB) {
1579	var rollback = errors.New("sentinel error to cause rollback")
1580	validate := func(tx *bolt.Tx) error {
1581		bucket := tx.Bucket([]byte("bench"))
1582		h := fnv.New32a()
1583		buf := make([]byte, 4)
1584		for id := uint32(0); id < 1000; id++ {
1585			binary.LittleEndian.PutUint32(buf, id)
1586			h.Reset()
1587			_, _ = h.Write(buf[:])
1588			k := h.Sum(nil)
1589			v := bucket.Get(k)
1590			if v == nil {
1591				b.Errorf("not found id=%d key=%x", id, k)
1592				continue
1593			}
1594			if g, e := v, []byte("filler"); !bytes.Equal(g, e) {
1595				b.Errorf("bad value for id=%d key=%x: %s != %q", id, k, g, e)
1596			}
1597			if err := bucket.Delete(k); err != nil {
1598				return err
1599			}
1600		}
1601		// should be empty now
1602		c := bucket.Cursor()
1603		for k, v := c.First(); k != nil; k, v = c.Next() {
1604			b.Errorf("unexpected key: %x = %q", k, v)
1605		}
1606		return rollback
1607	}
1608	if err := db.Update(validate); err != nil && err != rollback {
1609		b.Error(err)
1610	}
1611}
1612
1613// DB is a test wrapper for bolt.DB.
1614type DB struct {
1615	*bolt.DB
1616	f string
1617	o *bolt.Options
1618}
1619
1620// MustOpenDB returns a new, open DB at a temporary location.
1621func MustOpenDB() *DB {
1622	return MustOpenWithOption(nil)
1623}
1624
1625// MustOpenDBWithOption returns a new, open DB at a temporary location with given options.
1626func MustOpenWithOption(o *bolt.Options) *DB {
1627	f := tempfile()
1628	if o == nil {
1629		o = bolt.DefaultOptions
1630	}
1631
1632	freelistType := bolt.FreelistArrayType
1633	if env := os.Getenv(bolt.TestFreelistType); env == string(bolt.FreelistMapType) {
1634		freelistType = bolt.FreelistMapType
1635	}
1636	o.FreelistType = freelistType
1637
1638	db, err := bolt.Open(f, 0666, o)
1639	if err != nil {
1640		panic(err)
1641	}
1642	return &DB{
1643		DB: db,
1644		f:  f,
1645		o:  o,
1646	}
1647}
1648
1649// Close closes the database and deletes the underlying file.
1650func (db *DB) Close() error {
1651	// Log statistics.
1652	if *statsFlag {
1653		db.PrintStats()
1654	}
1655
1656	// Check database consistency after every test.
1657	db.MustCheck()
1658
1659	// Close database and remove file.
1660	defer os.Remove(db.Path())
1661	return db.DB.Close()
1662}
1663
1664// MustClose closes the database and deletes the underlying file. Panic on error.
1665func (db *DB) MustClose() {
1666	if err := db.Close(); err != nil {
1667		panic(err)
1668	}
1669}
1670
1671// MustReopen reopen the database. Panic on error.
1672func (db *DB) MustReopen() {
1673	indb, err := bolt.Open(db.f, 0666, db.o)
1674	if err != nil {
1675		panic(err)
1676	}
1677	db.DB = indb
1678}
1679
1680// PrintStats prints the database stats
1681func (db *DB) PrintStats() {
1682	var stats = db.Stats()
1683	fmt.Printf("[db] %-20s %-20s %-20s\n",
1684		fmt.Sprintf("pg(%d/%d)", stats.TxStats.PageCount, stats.TxStats.PageAlloc),
1685		fmt.Sprintf("cur(%d)", stats.TxStats.CursorCount),
1686		fmt.Sprintf("node(%d/%d)", stats.TxStats.NodeCount, stats.TxStats.NodeDeref),
1687	)
1688	fmt.Printf("     %-20s %-20s %-20s\n",
1689		fmt.Sprintf("rebal(%d/%v)", stats.TxStats.Rebalance, truncDuration(stats.TxStats.RebalanceTime)),
1690		fmt.Sprintf("spill(%d/%v)", stats.TxStats.Spill, truncDuration(stats.TxStats.SpillTime)),
1691		fmt.Sprintf("w(%d/%v)", stats.TxStats.Write, truncDuration(stats.TxStats.WriteTime)),
1692	)
1693}
1694
1695// MustCheck runs a consistency check on the database and panics if any errors are found.
1696func (db *DB) MustCheck() {
1697	if err := db.Update(func(tx *bolt.Tx) error {
1698		// Collect all the errors.
1699		var errors []error
1700		for err := range tx.Check() {
1701			errors = append(errors, err)
1702			if len(errors) > 10 {
1703				break
1704			}
1705		}
1706
1707		// If errors occurred, copy the DB and print the errors.
1708		if len(errors) > 0 {
1709			var path = tempfile()
1710			if err := tx.CopyFile(path, 0600); err != nil {
1711				panic(err)
1712			}
1713
1714			// Print errors.
1715			fmt.Print("\n\n")
1716			fmt.Printf("consistency check failed (%d errors)\n", len(errors))
1717			for _, err := range errors {
1718				fmt.Println(err)
1719			}
1720			fmt.Println("")
1721			fmt.Println("db saved to:")
1722			fmt.Println(path)
1723			fmt.Print("\n\n")
1724			os.Exit(-1)
1725		}
1726
1727		return nil
1728	}); err != nil && err != bolt.ErrDatabaseNotOpen {
1729		panic(err)
1730	}
1731}
1732
1733// CopyTempFile copies a database to a temporary file.
1734func (db *DB) CopyTempFile() {
1735	path := tempfile()
1736	if err := db.View(func(tx *bolt.Tx) error {
1737		return tx.CopyFile(path, 0600)
1738	}); err != nil {
1739		panic(err)
1740	}
1741	fmt.Println("db copied to: ", path)
1742}
1743
1744// tempfile returns a temporary file path.
1745func tempfile() string {
1746	f, err := ioutil.TempFile("", "bolt-")
1747	if err != nil {
1748		panic(err)
1749	}
1750	if err := f.Close(); err != nil {
1751		panic(err)
1752	}
1753	if err := os.Remove(f.Name()); err != nil {
1754		panic(err)
1755	}
1756	return f.Name()
1757}
1758
1759func trunc(b []byte, length int) []byte {
1760	if length < len(b) {
1761		return b[:length]
1762	}
1763	return b
1764}
1765
1766func truncDuration(d time.Duration) string {
1767	return regexp.MustCompile(`^(\d+)(\.\d+)`).ReplaceAllString(d.String(), "$1")
1768}
1769
1770func fileSize(path string) int64 {
1771	fi, err := os.Stat(path)
1772	if err != nil {
1773		return 0
1774	}
1775	return fi.Size()
1776}
1777
1778// u64tob converts a uint64 into an 8-byte slice.
1779func u64tob(v uint64) []byte {
1780	b := make([]byte, 8)
1781	binary.BigEndian.PutUint64(b, v)
1782	return b
1783}
1784