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