github.com/fozzysec/SiaPrime@v0.0.0-20190612043147-66c8e8d11fe3/persist/boltdb_test.go (about)

     1  package persist
     2  
     3  import (
     4  	"os"
     5  	"path/filepath"
     6  	"testing"
     7  	"time"
     8  
     9  	"gitlab.com/NebulousLabs/bolt"
    10  	"SiaPrime/build"
    11  	"gitlab.com/NebulousLabs/fastrand"
    12  )
    13  
    14  // testInputs and testFilenames are global variables because most tests require
    15  // a variety of metadata and filename inputs (although only TestCheckMetadata
    16  // and TestIntegratedCheckMetadata use testInput.newMd and testInput.err).
    17  // Weird strings are from https://github.com/minimaxir/big-ltist-of-naughty-strings
    18  var (
    19  	testInputs = []struct {
    20  		md    Metadata
    21  		newMd Metadata
    22  		err   error
    23  	}{
    24  		{Metadata{"1sadf23", "12253"}, Metadata{"1sa-df23", "12253"}, ErrBadHeader},
    25  		{Metadata{"$@#$%^&", "$@#$%^&"}, Metadata{"$@#$%^&", "$@#$%!^&"}, ErrBadVersion},
    26  		{Metadata{"//", "//"}, Metadata{"////", "//"}, ErrBadHeader},
    27  		{Metadata{":]", ":)"}, Metadata{":]", ":("}, ErrBadVersion},
    28  		{Metadata{"¯|_(ツ)_|¯", "_|¯(ツ)¯|_"}, Metadata{"¯|_(ツ)_|¯", "_|¯(ツ)_|¯"}, ErrBadVersion},
    29  		{Metadata{"世界", "怎么办呢"}, Metadata{"世界", "怎么好呢"}, ErrBadVersion},
    30  		{Metadata{"     ", "     "}, Metadata{"\t", "     "}, ErrBadHeader},
    31  		{Metadata{"", ""}, Metadata{"asdf", ""}, ErrBadHeader},
    32  		{Metadata{"", "_"}, Metadata{"", ""}, ErrBadVersion},
    33  		{Metadata{"%&*", "#@$"}, Metadata{"", "#@$"}, ErrBadHeader},
    34  		{Metadata{"a.sdf", "0.30.2"}, Metadata{"a.sdf", "0.3.02"}, ErrBadVersion},
    35  		{Metadata{"/", "/"}, Metadata{"//", "/"}, ErrBadHeader},
    36  		{Metadata{"%*.*s", "%d"}, Metadata{"%*.*s", "%    d"}, ErrBadVersion},
    37  		{Metadata{" ", ""}, Metadata{"   ", ""}, ErrBadHeader},
    38  		{Metadata{"⒯⒣⒠ ⒬⒰⒤⒞⒦ ⒝⒭⒪⒲⒩ ⒡⒪⒳ ⒥⒰⒨⒫⒮ ⒪⒱⒠⒭ ⒯⒣⒠ ⒧⒜⒵⒴ ⒟⒪⒢", "undefined"}, Metadata{"⒯⒣⒠ ⒬⒰⒤⒞⒦ ⒝⒭⒪⒲⒩ ⒡⒪⒳ ⒥⒰⒨⒫⒮ ⒪⒱⒠⒭ ⒯⒣⒠ ⒧⒜⒵⒴ ⒟⒪⒢", "␢undefined"}, ErrBadVersion},
    39  		{Metadata{" ", "  "}, Metadata{"  ", "  "}, ErrBadHeader},
    40  		{Metadata{"\xF0\x9F\x98\x8F", "\xF0\x9F\x98\xBE"}, Metadata{"\xF0\x9F\x98\x8F", " \xF0\x9F\x98\xBE"}, ErrBadVersion},
    41  		{Metadata{"'", ""}, Metadata{"`", ""}, ErrBadHeader},
    42  		{Metadata{"", "-"}, Metadata{"", "-␡"}, ErrBadVersion},
    43  		{Metadata{"<foo val=“bar” />", "(ノಥ益ಥ ┻━┻"}, Metadata{"<foo val=“bar” />", "(ノ\nಥ益ಥ ┻━┻"}, ErrBadVersion},
    44  		{Metadata{"\n\n", "Ṱ̺̺o͞ ̷i̲̬n̝̗v̟̜o̶̙kè͚̮ ̖t̝͕h̼͓e͇̣ ̢̼h͚͎i̦̲v̻͍e̺̭-m̢iͅn̖̺d̵̼ ̞̥r̛̗e͙p͠r̼̞e̺̠s̘͇e͉̥ǹ̬͎t͍̬i̪̱n͠g̴͉ ͏͉c̬̟h͡a̫̻o̫̟s̗̦.̨̹"}, Metadata{"\n\n", "Ṱ̺̺o͞ ̷i̲̬n̝̗v̟̜o̶̙kè͚̮ t̝͕h̼͓e͇̣ ̢̼h͚͎i̦̲v̻͍e̺̭-m̢iͅn̖̺d̵̼ ̞̥r̛̗e͙p͠r̼̞e̺̠s̘͇e͉̥ǹ̬͎t͍̬i̪̱n͠g̴͉ ͏͉c̬̟h͡a̫̻o̫̟s̗̦.̨̹"}, ErrBadVersion},
    45  	}
    46  	testFilenames = []string{
    47  		"_",
    48  		"-",
    49  		"1234sg",
    50  		"@#$%@#",
    51  		"你好好q wgc好",
    52  		"\xF0\x9F\x99\x8A",
    53  		"␣",
    54  		" ",
    55  		"$HOME",
    56  		",.;'[]-=",
    57  		"%s",
    58  	}
    59  )
    60  
    61  // TestOpenDatabase tests calling OpenDatabase on the following types of
    62  // database:
    63  // - a database that has not yet been created
    64  // - an existing empty database
    65  // - an existing nonempty database
    66  // Along the way, it also tests calling Close on:
    67  // - a newly-created database
    68  // - a newly-filled database
    69  // - a newly-emptied database
    70  func TestOpenDatabase(t *testing.T) {
    71  	if testing.Short() {
    72  		t.SkipNow()
    73  	}
    74  	testBuckets := [][]byte{
    75  		[]byte("Fake Bucket123!@#$"),
    76  		[]byte("你好好好"),
    77  		[]byte("¯|_(ツ)_|¯"),
    78  		[]byte("Powerلُلُصّبُلُلصّبُررً ॣ ॣh ॣ ॣ冗"),
    79  		[]byte("﷽"),
    80  		[]byte("(ノಥ益ಥ ┻━┻"),
    81  		[]byte("Ṱ̺̺o͞ ̷i̲̬n̝̗v̟̜o̶̙kè͚̮ ̖t̝͕h̼͓e͇̣ ̢̼h͚͎i̦̲v̻͍e̺̭-m̢iͅn̖̺d̵̼ ̞̥r̛̗e͙p͠r̼̞e̺̠s̘͇e͉̥ǹ̬͎t͍̬i̪̱n͠g̴͉ ͏͉c̬̟h͡a̫̻o̫̟s̗̦.̨̹"),
    82  		[]byte("0xbadidea"),
    83  		[]byte("␣"),
    84  		[]byte("你好好好"),
    85  	}
    86  	// Create a folder for the database file. If a folder by that name exists
    87  	// already, it will be replaced by an empty folder.
    88  	testDir := build.TempDir(persistDir, t.Name())
    89  	err := os.MkdirAll(testDir, 0700)
    90  	if err != nil {
    91  		t.Fatal(err)
    92  	}
    93  	for i, in := range testInputs {
    94  		dbFilename := testFilenames[i%len(testFilenames)]
    95  		dbFilepath := filepath.Join(testDir, dbFilename)
    96  		// Create a new database.
    97  		db, err := OpenDatabase(in.md, dbFilepath)
    98  		if err != nil {
    99  			t.Errorf("calling OpenDatabase on a new database failed for metadata %v, filename %v; error was %v", in.md, dbFilename, err)
   100  			continue
   101  		}
   102  		// Close the newly-created, empty database.
   103  		err = db.Close()
   104  		if err != nil {
   105  			t.Errorf("closing a newly created database failed for metadata %v, filename %v; error was %v", in.md, dbFilename, err)
   106  			continue
   107  		}
   108  		// Call OpenDatabase again, this time on the existing empty database.
   109  		db, err = OpenDatabase(in.md, dbFilepath)
   110  		if err != nil {
   111  			t.Errorf("calling OpenDatabase on an existing empty database failed for metadata %v, filename %v; error was %v", in.md, dbFilename, err)
   112  			continue
   113  		}
   114  		// Create buckets in the database.
   115  		err = db.Update(func(tx *bolt.Tx) error {
   116  			for _, testBucket := range testBuckets {
   117  				_, err := tx.CreateBucketIfNotExists(testBucket)
   118  				if err != nil {
   119  					t.Errorf("db.Update failed on bucket name %v for metadata %v, filename %v; error was %v", testBucket, in.md, dbFilename, err)
   120  					return err
   121  				}
   122  			}
   123  			return nil
   124  		})
   125  		if err != nil {
   126  			t.Error(err)
   127  			continue
   128  		}
   129  		// Make sure CreateBucketIfNotExists method handles invalid (nil)
   130  		// bucket name.
   131  		err = db.Update(func(tx *bolt.Tx) error {
   132  			_, err := tx.CreateBucketIfNotExists(nil)
   133  			return err
   134  		})
   135  		if err != bolt.ErrBucketNameRequired {
   136  		}
   137  		// Fill each bucket with a random number (0-9, inclusive) of key/value
   138  		// pairs, where each key is a length-10 random byteslice and each value
   139  		// is a length-1000 random byteslice.
   140  		err = db.Update(func(tx *bolt.Tx) error {
   141  			for _, testBucket := range testBuckets {
   142  				b := tx.Bucket(testBucket)
   143  				x := fastrand.Intn(10)
   144  				for i := 0; i <= x; i++ {
   145  					err := b.Put(fastrand.Bytes(10), fastrand.Bytes(1e3))
   146  					if err != nil {
   147  						t.Errorf("db.Update failed to fill bucket %v for metadata %v, filename %v; error was %v", testBucket, in.md, dbFilename, err)
   148  						return err
   149  					}
   150  				}
   151  			}
   152  			return nil
   153  		})
   154  		if err != nil {
   155  			t.Error(err)
   156  			continue
   157  		}
   158  		// Close the newly-filled database.
   159  		err = db.Close()
   160  		if err != nil {
   161  			t.Errorf("closing a newly-filled database failed for metadata %v, filename %v; error was %v", in.md, dbFilename, err)
   162  			continue
   163  		}
   164  		// Call OpenDatabase on the database now that it's been filled.
   165  		db, err = OpenDatabase(in.md, dbFilepath)
   166  		if err != nil {
   167  			t.Error(err)
   168  			continue
   169  		}
   170  		// Empty every bucket in the database.
   171  		err = db.Update(func(tx *bolt.Tx) error {
   172  			for _, testBucket := range testBuckets {
   173  				b := tx.Bucket(testBucket)
   174  				err := b.ForEach(func(k, v []byte) error {
   175  					return b.Delete(k)
   176  				})
   177  				if err != nil {
   178  					return err
   179  				}
   180  			}
   181  			return nil
   182  		})
   183  		if err != nil {
   184  			t.Error(err)
   185  			continue
   186  		}
   187  		// Close and delete the newly emptied database.
   188  		err = db.Close()
   189  		if err != nil {
   190  			t.Errorf("closing a newly-emptied database for metadata %v, filename %v; error was %v", in.md, dbFilename, err)
   191  			continue
   192  		}
   193  		err = os.Remove(dbFilepath)
   194  		if err != nil {
   195  			t.Errorf("removing database file failed for metadata %v, filename %v; error was %v", in.md, dbFilename, err)
   196  			continue
   197  		}
   198  	}
   199  }
   200  
   201  // TestErrTxNotWritable checks that updateMetadata returns an error when called
   202  // from a read-only transaction.
   203  func TestErrTxNotWritable(t *testing.T) {
   204  	if testing.Short() {
   205  		t.SkipNow()
   206  	}
   207  	testDir := build.TempDir(persistDir, t.Name())
   208  	err := os.MkdirAll(testDir, 0700)
   209  	if err != nil {
   210  		t.Fatal(err)
   211  	}
   212  	for i, in := range testInputs {
   213  		dbFilename := testFilenames[i%len(testFilenames)]
   214  		dbFilepath := filepath.Join(testDir, dbFilename)
   215  		db, err := bolt.Open(dbFilepath, 0600, &bolt.Options{Timeout: 3 * time.Second})
   216  		if err != nil {
   217  			t.Fatal(err)
   218  		}
   219  		boltDB := &BoltDatabase{
   220  			Metadata: in.md,
   221  			DB:       db,
   222  		}
   223  		// Should return an error because updateMetadata is being called from
   224  		// a read-only transaction.
   225  		err = db.View(boltDB.updateMetadata)
   226  		if err != bolt.ErrTxNotWritable {
   227  			t.Errorf("updateMetadata returned wrong error for input %v, filename %v; expected tx not writable, got %v", in.md, dbFilename, err)
   228  		}
   229  		err = boltDB.Close()
   230  		if err != nil {
   231  			t.Fatal(err)
   232  		}
   233  		err = os.Remove(dbFilepath)
   234  		if err != nil {
   235  			t.Fatal(err)
   236  		}
   237  	}
   238  }
   239  
   240  // TestErrDatabaseNotOpen tests that checkMetadata returns an error when called
   241  // on a BoltDatabase that is closed.
   242  func TestErrDatabaseNotOpen(t *testing.T) {
   243  	if testing.Short() {
   244  		t.SkipNow()
   245  	}
   246  	testDir := build.TempDir(persistDir, t.Name())
   247  	err := os.MkdirAll(testDir, 0700)
   248  	if err != nil {
   249  		t.Fatal(err)
   250  	}
   251  	dbFilepath := filepath.Join(testDir, "fake_filename")
   252  	md := Metadata{"Fake Header", "Fake Version"}
   253  	db, err := bolt.Open(dbFilepath, 0600, &bolt.Options{Timeout: 3 * time.Second})
   254  	if err != nil {
   255  		t.Fatal(err)
   256  	}
   257  	boltDB := &BoltDatabase{
   258  		Metadata: md,
   259  		DB:       db,
   260  	}
   261  	err = boltDB.Close()
   262  	if err != nil {
   263  		t.Fatal(err)
   264  	}
   265  	// Should return an error since boltDB is closed.
   266  	err = boltDB.checkMetadata(md)
   267  	if err != bolt.ErrDatabaseNotOpen {
   268  		t.Errorf("expected database not open, got %v", err)
   269  	}
   270  	err = os.Remove(dbFilepath)
   271  	if err != nil {
   272  		t.Error(err)
   273  	}
   274  }
   275  
   276  // TestErrCheckMetadata tests that checkMetadata returns an error when called
   277  // on a BoltDatabase whose metadata has been changed.
   278  func TestErrCheckMetadata(t *testing.T) {
   279  	if testing.Short() {
   280  		t.SkipNow()
   281  	}
   282  	testDir := build.TempDir(persistDir, t.Name())
   283  	err := os.MkdirAll(testDir, 0700)
   284  	if err != nil {
   285  		t.Fatal(err)
   286  	}
   287  	for i, in := range testInputs {
   288  		dbFilename := testFilenames[i%len(testFilenames)]
   289  		dbFilepath := filepath.Join(testDir, dbFilename)
   290  		db, err := bolt.Open(dbFilepath, 0600, &bolt.Options{Timeout: 3 * time.Second})
   291  		if err != nil {
   292  			t.Fatal(err)
   293  		}
   294  		boltDB := &BoltDatabase{
   295  			Metadata: in.md,
   296  			DB:       db,
   297  		}
   298  		err = db.Update(func(tx *bolt.Tx) error {
   299  			bucket, err := tx.CreateBucketIfNotExists([]byte("Metadata"))
   300  			if err != nil {
   301  				return err
   302  			}
   303  			err = bucket.Put([]byte("Header"), []byte(in.newMd.Header))
   304  			if err != nil {
   305  				return err
   306  			}
   307  			err = bucket.Put([]byte("Version"), []byte(in.newMd.Version))
   308  			if err != nil {
   309  				return err
   310  			}
   311  			return nil
   312  		})
   313  		if err != nil {
   314  			t.Errorf("Put method failed for input %v, filename %v with error %v", in, dbFilename, err)
   315  			continue
   316  		}
   317  		// Should return an error because boltDB's metadata now differs from
   318  		// its original metadata.
   319  		err = (*boltDB).checkMetadata(in.md)
   320  		if err != in.err {
   321  			t.Errorf("expected %v, got %v for input %v -> %v", in.err, err, in.md, in.newMd)
   322  		}
   323  		err = boltDB.Close()
   324  		if err != nil {
   325  			t.Fatal(err)
   326  		}
   327  		err = os.Remove(dbFilepath)
   328  		if err != nil {
   329  			t.Fatal(err)
   330  		}
   331  	}
   332  }
   333  
   334  // TestErrIntegratedCheckMetadata checks that checkMetadata returns an error
   335  // within OpenDatabase when OpenDatabase is called on a BoltDatabase that has
   336  // already been set up with different metadata.
   337  func TestErrIntegratedCheckMetadata(t *testing.T) {
   338  	if testing.Short() {
   339  		t.SkipNow()
   340  	}
   341  	testDir := build.TempDir(persistDir, t.Name())
   342  	err := os.MkdirAll(testDir, 0700)
   343  	if err != nil {
   344  		t.Fatal(err)
   345  	}
   346  	for i, in := range testInputs {
   347  		dbFilename := testFilenames[i%len(testFilenames)]
   348  		dbFilepath := filepath.Join(testDir, dbFilename)
   349  		boltDB, err := OpenDatabase(in.md, dbFilepath)
   350  		if err != nil {
   351  			t.Errorf("OpenDatabase failed on input %v, filename %v; error was %v", in, dbFilename, err)
   352  			continue
   353  		}
   354  		err = boltDB.Close()
   355  		if err != nil {
   356  			t.Fatal(err)
   357  		}
   358  		// Should return an error because boltDB was set up with metadata in.md, not in.newMd
   359  		boltDB, err = OpenDatabase(in.newMd, dbFilepath)
   360  		if err != in.err {
   361  			t.Errorf("expected error %v for input %v and filename %v; got %v instead", in.err, in, dbFilename, err)
   362  		}
   363  		err = os.Remove(dbFilepath)
   364  		if err != nil {
   365  			t.Fatal(err)
   366  		}
   367  	}
   368  }