github.com/fawick/restic@v0.1.1-0.20171126184616-c02923fbfc79/internal/backend/test/tests.go (about) 1 package test 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "math/rand" 10 "os" 11 "reflect" 12 "sort" 13 "strings" 14 "testing" 15 "time" 16 17 "github.com/restic/restic/internal/errors" 18 "github.com/restic/restic/internal/restic" 19 20 "github.com/restic/restic/internal/test" 21 22 "github.com/restic/restic/internal/backend" 23 ) 24 25 func seedRand(t testing.TB) { 26 seed := time.Now().UnixNano() 27 rand.Seed(seed) 28 t.Logf("rand initialized with seed %d", seed) 29 } 30 31 // TestCreateWithConfig tests that creating a backend in a location which already 32 // has a config file fails. 33 func (s *Suite) TestCreateWithConfig(t *testing.T) { 34 b := s.open(t) 35 defer s.close(t, b) 36 37 // remove a config if present 38 cfgHandle := restic.Handle{Type: restic.ConfigFile} 39 cfgPresent, err := b.Test(context.TODO(), cfgHandle) 40 if err != nil { 41 t.Fatalf("unable to test for config: %+v", err) 42 } 43 44 if cfgPresent { 45 remove(t, b, cfgHandle) 46 } 47 48 // save a config 49 store(t, b, restic.ConfigFile, []byte("test config")) 50 51 // now create the backend again, this must fail 52 _, err = s.Create(s.Config) 53 if err == nil { 54 t.Fatalf("expected error not found for creating a backend with an existing config file") 55 } 56 57 // remove config 58 err = b.Remove(context.TODO(), restic.Handle{Type: restic.ConfigFile, Name: ""}) 59 if err != nil { 60 t.Fatalf("unexpected error removing config: %+v", err) 61 } 62 } 63 64 // TestLocation tests that a location string is returned. 65 func (s *Suite) TestLocation(t *testing.T) { 66 b := s.open(t) 67 defer s.close(t, b) 68 69 l := b.Location() 70 if l == "" { 71 t.Fatalf("invalid location string %q", l) 72 } 73 } 74 75 // TestConfig saves and loads a config from the backend. 76 func (s *Suite) TestConfig(t *testing.T) { 77 b := s.open(t) 78 defer s.close(t, b) 79 80 var testString = "Config" 81 82 // create config and read it back 83 _, err := backend.LoadAll(context.TODO(), b, restic.Handle{Type: restic.ConfigFile}) 84 if err == nil { 85 t.Fatalf("did not get expected error for non-existing config") 86 } 87 88 err = b.Save(context.TODO(), restic.Handle{Type: restic.ConfigFile}, strings.NewReader(testString)) 89 if err != nil { 90 t.Fatalf("Save() error: %+v", err) 91 } 92 93 // try accessing the config with different names, should all return the 94 // same config 95 for _, name := range []string{"", "foo", "bar", "0000000000000000000000000000000000000000000000000000000000000000"} { 96 h := restic.Handle{Type: restic.ConfigFile, Name: name} 97 buf, err := backend.LoadAll(context.TODO(), b, h) 98 if err != nil { 99 t.Fatalf("unable to read config with name %q: %+v", name, err) 100 } 101 102 if string(buf) != testString { 103 t.Fatalf("wrong data returned, want %q, got %q", testString, string(buf)) 104 } 105 } 106 107 // remove the config 108 remove(t, b, restic.Handle{Type: restic.ConfigFile}) 109 } 110 111 // TestLoad tests the backend's Load function. 112 func (s *Suite) TestLoad(t *testing.T) { 113 seedRand(t) 114 115 b := s.open(t) 116 defer s.close(t, b) 117 118 rd, err := b.Load(context.TODO(), restic.Handle{}, 0, 0) 119 if err == nil { 120 t.Fatalf("Load() did not return an error for invalid handle") 121 } 122 if rd != nil { 123 _ = rd.Close() 124 } 125 126 err = testLoad(b, restic.Handle{Type: restic.DataFile, Name: "foobar"}, 0, 0) 127 if err == nil { 128 t.Fatalf("Load() did not return an error for non-existing blob") 129 } 130 131 length := rand.Intn(1<<24) + 2000 132 133 data := test.Random(23, length) 134 id := restic.Hash(data) 135 136 handle := restic.Handle{Type: restic.DataFile, Name: id.String()} 137 err = b.Save(context.TODO(), handle, bytes.NewReader(data)) 138 if err != nil { 139 t.Fatalf("Save() error: %+v", err) 140 } 141 142 t.Logf("saved %d bytes as %v", length, handle) 143 144 rd, err = b.Load(context.TODO(), handle, 100, -1) 145 if err == nil { 146 t.Fatalf("Load() returned no error for negative offset!") 147 } 148 149 if rd != nil { 150 t.Fatalf("Load() returned a non-nil reader for negative offset!") 151 } 152 153 loadTests := 50 154 if s.MinimalData { 155 loadTests = 10 156 } 157 158 for i := 0; i < loadTests; i++ { 159 l := rand.Intn(length + 2000) 160 o := rand.Intn(length + 2000) 161 162 d := data 163 if o < len(d) { 164 d = d[o:] 165 } else { 166 t.Logf("offset == length, skipping test") 167 continue 168 } 169 170 getlen := l 171 if l >= len(d) && rand.Float32() >= 0.5 { 172 getlen = 0 173 } 174 175 if l > 0 && l < len(d) { 176 d = d[:l] 177 } 178 179 rd, err := b.Load(context.TODO(), handle, getlen, int64(o)) 180 if err != nil { 181 t.Logf("Load, l %v, o %v, len(d) %v, getlen %v", l, o, len(d), getlen) 182 t.Errorf("Load(%d, %d) returned unexpected error: %+v", l, o, err) 183 continue 184 } 185 186 buf, err := ioutil.ReadAll(rd) 187 if err != nil { 188 t.Logf("Load, l %v, o %v, len(d) %v, getlen %v", l, o, len(d), getlen) 189 t.Errorf("Load(%d, %d) ReadAll() returned unexpected error: %+v", l, o, err) 190 if err = rd.Close(); err != nil { 191 t.Errorf("Load(%d, %d) rd.Close() returned error: %+v", l, o, err) 192 } 193 continue 194 } 195 196 if l == 0 && len(buf) != len(d) { 197 t.Logf("Load, l %v, o %v, len(d) %v, getlen %v", l, o, len(d), getlen) 198 t.Errorf("Load(%d, %d) wrong number of bytes read: want %d, got %d", l, o, len(d), len(buf)) 199 if err = rd.Close(); err != nil { 200 t.Errorf("Load(%d, %d) rd.Close() returned error: %+v", l, o, err) 201 } 202 continue 203 } 204 205 if l > 0 && l <= len(d) && len(buf) != l { 206 t.Logf("Load, l %v, o %v, len(d) %v, getlen %v", l, o, len(d), getlen) 207 t.Errorf("Load(%d, %d) wrong number of bytes read: want %d, got %d", l, o, l, len(buf)) 208 if err = rd.Close(); err != nil { 209 t.Errorf("Load(%d, %d) rd.Close() returned error: %+v", l, o, err) 210 } 211 continue 212 } 213 214 if l > len(d) && len(buf) != len(d) { 215 t.Logf("Load, l %v, o %v, len(d) %v, getlen %v", l, o, len(d), getlen) 216 t.Errorf("Load(%d, %d) wrong number of bytes read for overlong read: want %d, got %d", l, o, l, len(buf)) 217 if err = rd.Close(); err != nil { 218 t.Errorf("Load(%d, %d) rd.Close() returned error: %+v", l, o, err) 219 } 220 continue 221 } 222 223 if !bytes.Equal(buf, d) { 224 t.Logf("Load, l %v, o %v, len(d) %v, getlen %v", l, o, len(d), getlen) 225 t.Errorf("Load(%d, %d) returned wrong bytes", l, o) 226 if err = rd.Close(); err != nil { 227 t.Errorf("Load(%d, %d) rd.Close() returned error: %+v", l, o, err) 228 } 229 continue 230 } 231 232 err = rd.Close() 233 if err != nil { 234 t.Logf("Load, l %v, o %v, len(d) %v, getlen %v", l, o, len(d), getlen) 235 t.Errorf("Load(%d, %d) rd.Close() returned unexpected error: %+v", l, o, err) 236 continue 237 } 238 } 239 240 test.OK(t, b.Remove(context.TODO(), handle)) 241 } 242 243 // TestList makes sure that the backend implements List() pagination correctly. 244 func (s *Suite) TestList(t *testing.T) { 245 seedRand(t) 246 247 numTestFiles := rand.Intn(20) + 20 248 249 b := s.open(t) 250 defer s.close(t, b) 251 252 list1 := restic.NewIDSet() 253 254 for i := 0; i < numTestFiles; i++ { 255 data := []byte(fmt.Sprintf("random test blob %v", i)) 256 id := restic.Hash(data) 257 h := restic.Handle{Type: restic.DataFile, Name: id.String()} 258 err := b.Save(context.TODO(), h, bytes.NewReader(data)) 259 if err != nil { 260 t.Fatal(err) 261 } 262 list1.Insert(id) 263 } 264 265 t.Logf("wrote %v files", len(list1)) 266 267 var tests = []struct { 268 maxItems int 269 }{ 270 {11}, {23}, {numTestFiles}, {numTestFiles + 10}, {numTestFiles + 1123}, 271 } 272 273 for _, test := range tests { 274 t.Run(fmt.Sprintf("max-%v", test.maxItems), func(t *testing.T) { 275 list2 := restic.NewIDSet() 276 277 type setter interface { 278 SetListMaxItems(int) 279 } 280 281 if s, ok := b.(setter); ok { 282 t.Logf("setting max list items to %d", test.maxItems) 283 s.SetListMaxItems(test.maxItems) 284 } 285 286 for name := range b.List(context.TODO(), restic.DataFile) { 287 id, err := restic.ParseID(name) 288 if err != nil { 289 t.Fatal(err) 290 } 291 list2.Insert(id) 292 } 293 294 t.Logf("loaded %v IDs from backend", len(list2)) 295 296 if !list1.Equals(list2) { 297 t.Errorf("lists are not equal, list1 %d entries, list2 %d entries", 298 len(list1), len(list2)) 299 } 300 }) 301 } 302 303 t.Logf("remove %d files", numTestFiles) 304 handles := make([]restic.Handle, 0, len(list1)) 305 for id := range list1 { 306 handles = append(handles, restic.Handle{Type: restic.DataFile, Name: id.String()}) 307 } 308 309 err := s.delayedRemove(t, b, handles...) 310 if err != nil { 311 t.Fatal(err) 312 } 313 } 314 315 type errorCloser struct { 316 io.Reader 317 l int 318 t testing.TB 319 } 320 321 func (ec errorCloser) Close() error { 322 ec.t.Error("forbidden method close was called") 323 return errors.New("forbidden method close was called") 324 } 325 326 func (ec errorCloser) Len() int { 327 return ec.l 328 } 329 330 // TestSave tests saving data in the backend. 331 func (s *Suite) TestSave(t *testing.T) { 332 seedRand(t) 333 334 b := s.open(t) 335 defer s.close(t, b) 336 var id restic.ID 337 338 saveTests := 10 339 if s.MinimalData { 340 saveTests = 2 341 } 342 343 for i := 0; i < saveTests; i++ { 344 length := rand.Intn(1<<23) + 200000 345 data := test.Random(23, length) 346 // use the first 32 byte as the ID 347 copy(id[:], data) 348 349 h := restic.Handle{ 350 Type: restic.DataFile, 351 Name: fmt.Sprintf("%s-%d", id, i), 352 } 353 err := b.Save(context.TODO(), h, bytes.NewReader(data)) 354 test.OK(t, err) 355 356 buf, err := backend.LoadAll(context.TODO(), b, h) 357 test.OK(t, err) 358 if len(buf) != len(data) { 359 t.Fatalf("number of bytes does not match, want %v, got %v", len(data), len(buf)) 360 } 361 362 if !bytes.Equal(buf, data) { 363 t.Fatalf("data not equal") 364 } 365 366 fi, err := b.Stat(context.TODO(), h) 367 test.OK(t, err) 368 369 if fi.Size != int64(len(data)) { 370 t.Fatalf("Stat() returned different size, want %q, got %d", len(data), fi.Size) 371 } 372 373 err = b.Remove(context.TODO(), h) 374 if err != nil { 375 t.Fatalf("error removing item: %+v", err) 376 } 377 } 378 379 // test saving from a tempfile 380 tmpfile, err := ioutil.TempFile("", "restic-backend-save-test-") 381 if err != nil { 382 t.Fatal(err) 383 } 384 385 length := rand.Intn(1<<23) + 200000 386 data := test.Random(23, length) 387 copy(id[:], data) 388 389 if _, err = tmpfile.Write(data); err != nil { 390 t.Fatal(err) 391 } 392 393 if _, err = tmpfile.Seek(0, io.SeekStart); err != nil { 394 t.Fatal(err) 395 } 396 397 h := restic.Handle{Type: restic.DataFile, Name: id.String()} 398 399 // wrap the tempfile in an errorCloser, so we can detect if the backend 400 // closes the reader 401 err = b.Save(context.TODO(), h, errorCloser{t: t, l: length, Reader: tmpfile}) 402 if err != nil { 403 t.Fatal(err) 404 } 405 406 err = s.delayedRemove(t, b, h) 407 if err != nil { 408 t.Fatalf("error removing item: %+v", err) 409 } 410 411 // try again directly with the temp file 412 if _, err = tmpfile.Seek(588, io.SeekStart); err != nil { 413 t.Fatal(err) 414 } 415 416 err = b.Save(context.TODO(), h, tmpfile) 417 if err != nil { 418 t.Fatal(err) 419 } 420 421 if err = tmpfile.Close(); err != nil { 422 t.Fatal(err) 423 } 424 425 err = b.Remove(context.TODO(), h) 426 if err != nil { 427 t.Fatalf("error removing item: %+v", err) 428 } 429 430 if err = os.Remove(tmpfile.Name()); err != nil { 431 t.Fatal(err) 432 } 433 } 434 435 var filenameTests = []struct { 436 name string 437 data string 438 }{ 439 {"1dfc6bc0f06cb255889e9ea7860a5753e8eb9665c9a96627971171b444e3113e", "x"}, 440 {"f00b4r", "foobar"}, 441 { 442 "1dfc6bc0f06cb255889e9ea7860a5753e8eb9665c9a96627971171b444e3113e4bf8f2d9144cc5420a80f04a4880ad6155fc58903a4fb6457c476c43541dcaa6-5", 443 "foobar content of data blob", 444 }, 445 } 446 447 // TestSaveFilenames tests saving data with various file names in the backend. 448 func (s *Suite) TestSaveFilenames(t *testing.T) { 449 b := s.open(t) 450 defer s.close(t, b) 451 452 for i, test := range filenameTests { 453 h := restic.Handle{Name: test.name, Type: restic.DataFile} 454 err := b.Save(context.TODO(), h, strings.NewReader(test.data)) 455 if err != nil { 456 t.Errorf("test %d failed: Save() returned %+v", i, err) 457 continue 458 } 459 460 buf, err := backend.LoadAll(context.TODO(), b, h) 461 if err != nil { 462 t.Errorf("test %d failed: Load() returned %+v", i, err) 463 continue 464 } 465 466 if !bytes.Equal(buf, []byte(test.data)) { 467 t.Errorf("test %d: returned wrong bytes", i) 468 } 469 470 err = b.Remove(context.TODO(), h) 471 if err != nil { 472 t.Errorf("test %d failed: Remove() returned %+v", i, err) 473 continue 474 } 475 } 476 } 477 478 var testStrings = []struct { 479 id string 480 data string 481 }{ 482 {"c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2", "foobar"}, 483 {"248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"}, 484 {"cc5d46bdb4991c6eae3eb739c9c8a7a46fe9654fab79c47b4fe48383b5b25e1c", "foo/bar"}, 485 {"4e54d2c721cbdb730f01b10b62dec622962b36966ec685880effa63d71c808f2", "foo/../../baz"}, 486 } 487 488 func store(t testing.TB, b restic.Backend, tpe restic.FileType, data []byte) restic.Handle { 489 id := restic.Hash(data) 490 h := restic.Handle{Name: id.String(), Type: tpe} 491 err := b.Save(context.TODO(), h, bytes.NewReader(data)) 492 test.OK(t, err) 493 return h 494 } 495 496 // testLoad loads a blob (but discards its contents). 497 func testLoad(b restic.Backend, h restic.Handle, length int, offset int64) error { 498 rd, err := b.Load(context.TODO(), h, 0, 0) 499 if err != nil { 500 return err 501 } 502 503 _, err = io.Copy(ioutil.Discard, rd) 504 cerr := rd.Close() 505 if err == nil { 506 err = cerr 507 } 508 return err 509 } 510 511 func (s *Suite) delayedRemove(t testing.TB, be restic.Backend, handles ...restic.Handle) error { 512 // Some backend (swift, I'm looking at you) may implement delayed 513 // removal of data. Let's wait a bit if this happens. 514 515 for _, h := range handles { 516 err := be.Remove(context.TODO(), h) 517 if s.ErrorHandler != nil { 518 err = s.ErrorHandler(t, be, err) 519 } 520 if err != nil { 521 return err 522 } 523 } 524 525 for _, h := range handles { 526 start := time.Now() 527 attempt := 0 528 var found bool 529 var err error 530 for time.Since(start) <= s.WaitForDelayedRemoval { 531 found, err = be.Test(context.TODO(), h) 532 if s.ErrorHandler != nil { 533 err = s.ErrorHandler(t, be, err) 534 } 535 if err != nil { 536 return err 537 } 538 539 if !found { 540 break 541 } 542 543 time.Sleep(2 * time.Second) 544 attempt++ 545 } 546 547 if found { 548 t.Fatalf("removed blob %v still present after %v (%d attempts)", h, time.Since(start), attempt) 549 } 550 } 551 552 return nil 553 } 554 555 func delayedList(t testing.TB, b restic.Backend, tpe restic.FileType, max int, maxwait time.Duration) restic.IDs { 556 list := restic.NewIDSet() 557 start := time.Now() 558 for i := 0; i < max; i++ { 559 for s := range b.List(context.TODO(), tpe) { 560 id := restic.TestParseID(s) 561 list.Insert(id) 562 } 563 if len(list) < max && time.Since(start) < maxwait { 564 time.Sleep(500 * time.Millisecond) 565 } 566 } 567 568 return list.List() 569 } 570 571 // TestBackend tests all functions of the backend. 572 func (s *Suite) TestBackend(t *testing.T) { 573 b := s.open(t) 574 defer s.close(t, b) 575 576 for _, tpe := range []restic.FileType{ 577 restic.DataFile, restic.KeyFile, restic.LockFile, 578 restic.SnapshotFile, restic.IndexFile, 579 } { 580 // detect non-existing files 581 for _, ts := range testStrings { 582 id, err := restic.ParseID(ts.id) 583 test.OK(t, err) 584 585 // test if blob is already in repository 586 h := restic.Handle{Type: tpe, Name: id.String()} 587 ret, err := b.Test(context.TODO(), h) 588 test.OK(t, err) 589 test.Assert(t, !ret, "blob was found to exist before creating") 590 591 // try to stat a not existing blob 592 _, err = b.Stat(context.TODO(), h) 593 test.Assert(t, err != nil, "blob data could be extracted before creation") 594 595 // try to read not existing blob 596 err = testLoad(b, h, 0, 0) 597 test.Assert(t, err != nil, "blob could be read before creation") 598 599 // try to get string out, should fail 600 ret, err = b.Test(context.TODO(), h) 601 test.OK(t, err) 602 test.Assert(t, !ret, "id %q was found (but should not have)", ts.id) 603 } 604 605 // add files 606 for _, ts := range testStrings { 607 store(t, b, tpe, []byte(ts.data)) 608 609 // test Load() 610 h := restic.Handle{Type: tpe, Name: ts.id} 611 buf, err := backend.LoadAll(context.TODO(), b, h) 612 test.OK(t, err) 613 test.Equals(t, ts.data, string(buf)) 614 615 // try to read it out with an offset and a length 616 start := 1 617 end := len(ts.data) - 2 618 length := end - start 619 620 buf2 := make([]byte, length) 621 rd, err := b.Load(context.TODO(), h, len(buf2), int64(start)) 622 test.OK(t, err) 623 n, err := io.ReadFull(rd, buf2) 624 test.OK(t, err) 625 test.Equals(t, len(buf2), n) 626 627 remaining, err := io.Copy(ioutil.Discard, rd) 628 test.OK(t, err) 629 test.Equals(t, int64(0), remaining) 630 631 test.OK(t, rd.Close()) 632 633 test.Equals(t, ts.data[start:end], string(buf2)) 634 } 635 636 // test adding the first file again 637 ts := testStrings[0] 638 639 // create blob 640 h := restic.Handle{Type: tpe, Name: ts.id} 641 err := b.Save(context.TODO(), h, strings.NewReader(ts.data)) 642 test.Assert(t, err != nil, "expected error for %v, got %v", h, err) 643 644 // remove and recreate 645 err = s.delayedRemove(t, b, h) 646 test.OK(t, err) 647 648 // test that the blob is gone 649 ok, err := b.Test(context.TODO(), h) 650 test.OK(t, err) 651 test.Assert(t, !ok, "removed blob still present") 652 653 // create blob 654 err = b.Save(context.TODO(), h, strings.NewReader(ts.data)) 655 test.OK(t, err) 656 657 // list items 658 IDs := restic.IDs{} 659 660 for _, ts := range testStrings { 661 id, err := restic.ParseID(ts.id) 662 test.OK(t, err) 663 IDs = append(IDs, id) 664 } 665 666 list := delayedList(t, b, tpe, len(IDs), s.WaitForDelayedRemoval) 667 if len(IDs) != len(list) { 668 t.Fatalf("wrong number of IDs returned: want %d, got %d", len(IDs), len(list)) 669 } 670 671 sort.Sort(IDs) 672 sort.Sort(list) 673 674 if !reflect.DeepEqual(IDs, list) { 675 t.Fatalf("lists aren't equal, want:\n %v\n got:\n%v\n", IDs, list) 676 } 677 678 var handles []restic.Handle 679 for _, ts := range testStrings { 680 id, err := restic.ParseID(ts.id) 681 test.OK(t, err) 682 683 h := restic.Handle{Type: tpe, Name: id.String()} 684 685 found, err := b.Test(context.TODO(), h) 686 test.OK(t, err) 687 test.Assert(t, found, fmt.Sprintf("id %q not found", id)) 688 689 handles = append(handles, h) 690 } 691 692 test.OK(t, s.delayedRemove(t, b, handles...)) 693 } 694 } 695 696 // TestZZZDelete tests the Delete function. The name ensures that this test is executed last. 697 func (s *Suite) TestZZZDelete(t *testing.T) { 698 if !test.TestCleanupTempDirs { 699 t.Skipf("not removing backend, TestCleanupTempDirs is false") 700 } 701 702 b := s.open(t) 703 defer s.close(t, b) 704 705 err := b.Delete(context.TODO()) 706 if err != nil { 707 t.Fatalf("error deleting backend: %+v", err) 708 } 709 }