github.com/zuoyebang/bitalosdb@v1.1.1-0.20240516111551-79a8c4d8ce20/bitree/bdb/db_test.go (about)

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