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 }