@@ -11,6 +11,7 @@ import (
1111 "os"
1212 "path/filepath"
1313 "reflect"
14+ "runtime"
1415 "sync"
1516 "testing"
1617 "time"
@@ -724,6 +725,114 @@ func TestDB_Concurrent_WriteTo(t *testing.T) {
724725 wg .Wait ()
725726}
726727
728+ // TestDB_WriteTo_and_Overwrite verifies that `(tx *Tx) WriteTo` can still
729+ // work even the underlying file is overwritten between the time a read-only
730+ // transaction is created and the time the file is actually opened
731+ func TestDB_WriteTo_and_Overwrite (t * testing.T ) {
732+ testCases := []struct {
733+ name string
734+ writeFlag int
735+ }{
736+ {
737+ name : "writeFlag not set" ,
738+ writeFlag : 0 ,
739+ },
740+ /* syscall.O_DIRECT not supported on some platforms, i.e. Windows and MacOS
741+ {
742+ name: "writeFlag set",
743+ writeFlag: syscall.O_DIRECT,
744+ },*/
745+ }
746+
747+ fRead := func (db * bolt.DB , bucketName []byte ) map [string ]string {
748+ data := make (map [string ]string )
749+ _ = db .View (func (tx * bolt.Tx ) error {
750+ b := tx .Bucket (bucketName )
751+ berr := b .ForEach (func (k , v []byte ) error {
752+ data [string (k )] = string (v )
753+ return nil
754+ })
755+ require .NoError (t , berr )
756+ return nil
757+ })
758+ return data
759+ }
760+
761+ for _ , tc := range testCases {
762+ t .Run (tc .name , func (t * testing.T ) {
763+ db := btesting .MustCreateDBWithOption (t , & bolt.Options {
764+ PageSize : 4096 ,
765+ })
766+ filePathOfDb := db .Path ()
767+
768+ var (
769+ bucketName = []byte ("data" )
770+ dataExpected map [string ]string
771+ dataActual map [string ]string
772+ )
773+
774+ t .Log ("Populate some data" )
775+ err := db .Update (func (tx * bolt.Tx ) error {
776+ b , berr := tx .CreateBucket (bucketName )
777+ if berr != nil {
778+ return berr
779+ }
780+ for k := 0 ; k < 10 ; k ++ {
781+ key , value := fmt .Sprintf ("key_%d" , rand .Intn (10 )), fmt .Sprintf ("value_%d" , rand .Intn (100 ))
782+ if perr := b .Put ([]byte (key ), []byte (value )); perr != nil {
783+ return perr
784+ }
785+ }
786+ return nil
787+ })
788+ require .NoError (t , err )
789+
790+ t .Log ("Read all the data before calling WriteTo" )
791+ dataExpected = fRead (db .DB , bucketName )
792+
793+ t .Log ("Create a readonly transaction for WriteTo" )
794+ rtx , rerr := db .Begin (false )
795+ require .NoError (t , rerr )
796+
797+ // Some platforms (i.e. Windows) don't support renaming a file
798+ // when the target file already exist and is opened.
799+ if runtime .GOOS == "linux" {
800+ t .Log ("Create another empty db file" )
801+ db2 := btesting .MustCreateDBWithOption (t , & bolt.Options {
802+ PageSize : 4096 ,
803+ })
804+ db2 .MustClose ()
805+ filePathOfDb2 := db2 .Path ()
806+
807+ t .Logf ("Renaming the new empty db file (%s) to the original db path (%s)" , filePathOfDb2 , filePathOfDb )
808+ err = os .Rename (filePathOfDb2 , filePathOfDb )
809+ require .NoError (t , err )
810+ } else {
811+ t .Log ("Ignore renaming step on non-Linux platform" )
812+ }
813+
814+ t .Logf ("Call WriteTo to copy the data of the original db file" )
815+ f := filepath .Join (t .TempDir (), "-backup-db" )
816+ err = rtx .CopyFile (f , 0600 )
817+ require .NoError (t , err )
818+ require .NoError (t , rtx .Rollback ())
819+
820+ t .Logf ("Read all the data from the backup db after calling WriteTo" )
821+ newDB , err := bolt .Open (f , 0600 , & bolt.Options {
822+ ReadOnly : true ,
823+ })
824+ require .NoError (t , err )
825+ dataActual = fRead (newDB , bucketName )
826+ err = newDB .Close ()
827+ require .NoError (t , err )
828+
829+ t .Log ("Compare the dataExpected and dataActual" )
830+ same := reflect .DeepEqual (dataExpected , dataActual )
831+ require .True (t , same , fmt .Sprintf ("found inconsistent data, dataExpected: %v, ddataActual : %v" , dataExpected , dataActual ))
832+ })
833+ }
834+ }
835+
727836// Ensure that opening a transaction while the DB is closed returns an error.
728837func TestDB_BeginRW_Closed (t * testing.T ) {
729838 var db bolt.DB
0 commit comments