github.com/mckael/restic@v0.8.3/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 noop := func(rd io.Reader) error { 119 return nil 120 } 121 122 err := b.Load(context.TODO(), restic.Handle{}, 0, 0, noop) 123 if err == nil { 124 t.Fatalf("Load() did not return an error for invalid handle") 125 } 126 127 err = testLoad(b, restic.Handle{Type: restic.DataFile, Name: "foobar"}, 0, 0) 128 if err == nil { 129 t.Fatalf("Load() did not return an error for non-existing blob") 130 } 131 132 length := rand.Intn(1<<24) + 2000 133 134 data := test.Random(23, length) 135 id := restic.Hash(data) 136 137 handle := restic.Handle{Type: restic.DataFile, Name: id.String()} 138 err = b.Save(context.TODO(), handle, bytes.NewReader(data)) 139 if err != nil { 140 t.Fatalf("Save() error: %+v", err) 141 } 142 143 t.Logf("saved %d bytes as %v", length, handle) 144 145 err = b.Load(context.TODO(), handle, 100, -1, noop) 146 if err == nil { 147 t.Fatalf("Load() returned no error for negative offset!") 148 } 149 150 err = b.Load(context.TODO(), handle, 0, 0, func(rd io.Reader) error { 151 return errors.Errorf("deliberate error") 152 }) 153 if err == nil { 154 t.Fatalf("Load() did not propagate consumer error!") 155 } 156 if err.Error() != "deliberate error" { 157 t.Fatalf("Load() did not correctly propagate consumer error!") 158 } 159 160 loadTests := 50 161 if s.MinimalData { 162 loadTests = 10 163 } 164 165 for i := 0; i < loadTests; i++ { 166 l := rand.Intn(length + 2000) 167 o := rand.Intn(length + 2000) 168 169 d := data 170 if o < len(d) { 171 d = d[o:] 172 } else { 173 t.Logf("offset == length, skipping test") 174 continue 175 } 176 177 getlen := l 178 if l >= len(d) && rand.Float32() >= 0.5 { 179 getlen = 0 180 } 181 182 if l > 0 && l < len(d) { 183 d = d[:l] 184 } 185 186 var buf []byte 187 err := b.Load(context.TODO(), handle, getlen, int64(o), func(rd io.Reader) (ierr error) { 188 buf, ierr = ioutil.ReadAll(rd) 189 return ierr 190 }) 191 if err != nil { 192 t.Logf("Load, l %v, o %v, len(d) %v, getlen %v", l, o, len(d), getlen) 193 t.Errorf("Load(%d, %d) returned unexpected error: %+v", l, o, err) 194 continue 195 } 196 197 if l == 0 && len(buf) != len(d) { 198 t.Logf("Load, l %v, o %v, len(d) %v, getlen %v", l, o, len(d), getlen) 199 t.Errorf("Load(%d, %d) wrong number of bytes read: want %d, got %d", l, o, len(d), len(buf)) 200 continue 201 } 202 203 if l > 0 && l <= len(d) && len(buf) != l { 204 t.Logf("Load, l %v, o %v, len(d) %v, getlen %v", l, o, len(d), getlen) 205 t.Errorf("Load(%d, %d) wrong number of bytes read: want %d, got %d", l, o, l, len(buf)) 206 continue 207 } 208 209 if l > len(d) && len(buf) != len(d) { 210 t.Logf("Load, l %v, o %v, len(d) %v, getlen %v", l, o, len(d), getlen) 211 t.Errorf("Load(%d, %d) wrong number of bytes read for overlong read: want %d, got %d", l, o, l, len(buf)) 212 continue 213 } 214 215 if !bytes.Equal(buf, d) { 216 t.Logf("Load, l %v, o %v, len(d) %v, getlen %v", l, o, len(d), getlen) 217 t.Errorf("Load(%d, %d) returned wrong bytes", l, o) 218 continue 219 } 220 } 221 222 test.OK(t, b.Remove(context.TODO(), handle)) 223 } 224 225 // TestList makes sure that the backend implements List() pagination correctly. 226 func (s *Suite) TestList(t *testing.T) { 227 seedRand(t) 228 229 numTestFiles := rand.Intn(20) + 20 230 231 b := s.open(t) 232 defer s.close(t, b) 233 234 // Check that the backend is empty to start with 235 var found []string 236 err := b.List(context.TODO(), restic.DataFile, func(fi restic.FileInfo) error { 237 found = append(found, fi.Name) 238 return nil 239 }) 240 if err != nil { 241 t.Fatalf("List returned error %v", err) 242 } 243 if found != nil { 244 t.Fatalf("backend not empty at start of test - contains: %v", found) 245 } 246 247 list1 := make(map[restic.ID]int64) 248 249 for i := 0; i < numTestFiles; i++ { 250 data := test.Random(rand.Int(), rand.Intn(100)+55) 251 id := restic.Hash(data) 252 h := restic.Handle{Type: restic.DataFile, Name: id.String()} 253 err := b.Save(context.TODO(), h, bytes.NewReader(data)) 254 if err != nil { 255 t.Fatal(err) 256 } 257 list1[id] = int64(len(data)) 258 } 259 260 t.Logf("wrote %v files", len(list1)) 261 262 var tests = []struct { 263 maxItems int 264 }{ 265 {11}, {23}, {numTestFiles}, {numTestFiles + 10}, {numTestFiles + 1123}, 266 } 267 268 for _, test := range tests { 269 t.Run(fmt.Sprintf("max-%v", test.maxItems), func(t *testing.T) { 270 list2 := make(map[restic.ID]int64) 271 272 type setter interface { 273 SetListMaxItems(int) 274 } 275 276 if s, ok := b.(setter); ok { 277 t.Logf("setting max list items to %d", test.maxItems) 278 s.SetListMaxItems(test.maxItems) 279 } 280 281 err := b.List(context.TODO(), restic.DataFile, func(fi restic.FileInfo) error { 282 id, err := restic.ParseID(fi.Name) 283 if err != nil { 284 t.Fatal(err) 285 } 286 list2[id] = fi.Size 287 return nil 288 }) 289 290 if err != nil { 291 t.Fatalf("List returned error %v", err) 292 } 293 294 t.Logf("loaded %v IDs from backend", len(list2)) 295 296 for id, size := range list1 { 297 size2, ok := list2[id] 298 if !ok { 299 t.Errorf("id %v not returned by List()", id.Str()) 300 } 301 302 if size != size2 { 303 t.Errorf("wrong size for id %v returned: want %v, got %v", id.Str(), size, size2) 304 } 305 } 306 307 for id := range list2 { 308 _, ok := list1[id] 309 if !ok { 310 t.Errorf("extra id %v returned by List()", id.Str()) 311 } 312 } 313 }) 314 } 315 316 t.Logf("remove %d files", numTestFiles) 317 handles := make([]restic.Handle, 0, len(list1)) 318 for id := range list1 { 319 handles = append(handles, restic.Handle{Type: restic.DataFile, Name: id.String()}) 320 } 321 322 err = s.delayedRemove(t, b, handles...) 323 if err != nil { 324 t.Fatal(err) 325 } 326 } 327 328 // TestListCancel tests that the context is respected and the error is returned by List. 329 func (s *Suite) TestListCancel(t *testing.T) { 330 seedRand(t) 331 332 numTestFiles := 5 333 334 b := s.open(t) 335 defer s.close(t, b) 336 337 testFiles := make([]restic.Handle, 0, numTestFiles) 338 339 for i := 0; i < numTestFiles; i++ { 340 data := []byte(fmt.Sprintf("random test blob %v", i)) 341 id := restic.Hash(data) 342 h := restic.Handle{Type: restic.DataFile, Name: id.String()} 343 err := b.Save(context.TODO(), h, bytes.NewReader(data)) 344 if err != nil { 345 t.Fatal(err) 346 } 347 testFiles = append(testFiles, h) 348 } 349 350 t.Run("Cancelled", func(t *testing.T) { 351 ctx, cancel := context.WithCancel(context.TODO()) 352 cancel() 353 354 // pass in a cancelled context 355 err := b.List(ctx, restic.DataFile, func(fi restic.FileInfo) error { 356 t.Errorf("got FileInfo %v for cancelled context", fi) 357 return nil 358 }) 359 360 if errors.Cause(err) != context.Canceled { 361 t.Fatalf("expected error not found, want %v, got %v", context.Canceled, errors.Cause(err)) 362 } 363 }) 364 365 t.Run("First", func(t *testing.T) { 366 ctx, cancel := context.WithCancel(context.TODO()) 367 defer cancel() 368 369 i := 0 370 err := b.List(ctx, restic.DataFile, func(fi restic.FileInfo) error { 371 i++ 372 // cancel the context on the first file 373 if i == 1 { 374 cancel() 375 } 376 return nil 377 }) 378 379 if errors.Cause(err) != context.Canceled { 380 t.Fatalf("expected error not found, want %v, got %v", context.Canceled, err) 381 } 382 383 if i != 1 { 384 t.Fatalf("wrong number of files returned by List, want %v, got %v", 1, i) 385 } 386 }) 387 388 t.Run("Last", func(t *testing.T) { 389 ctx, cancel := context.WithCancel(context.TODO()) 390 defer cancel() 391 392 i := 0 393 err := b.List(ctx, restic.DataFile, func(fi restic.FileInfo) error { 394 // cancel the context at the last file 395 i++ 396 if i == numTestFiles { 397 cancel() 398 } 399 return nil 400 }) 401 402 if errors.Cause(err) != context.Canceled { 403 t.Fatalf("expected error not found, want %v, got %v", context.Canceled, err) 404 } 405 406 if i != numTestFiles { 407 t.Fatalf("wrong number of files returned by List, want %v, got %v", numTestFiles, i) 408 } 409 }) 410 411 t.Run("Timeout", func(t *testing.T) { 412 ctx, cancel := context.WithCancel(context.TODO()) 413 defer cancel() 414 415 // rather large timeout, let's try to get at least one item 416 timeout := time.Second 417 418 ctxTimeout, _ := context.WithTimeout(ctx, timeout) 419 420 i := 0 421 // pass in a context with a timeout 422 err := b.List(ctxTimeout, restic.DataFile, func(fi restic.FileInfo) error { 423 i++ 424 425 // wait until the context is cancelled 426 <-ctxTimeout.Done() 427 return nil 428 }) 429 430 if errors.Cause(err) != context.DeadlineExceeded { 431 t.Fatalf("expected error not found, want %#v, got %#v", context.DeadlineExceeded, err) 432 } 433 434 if i > 2 { 435 t.Fatalf("wrong number of files returned by List, want <= 2, got %v", i) 436 } 437 }) 438 439 err := s.delayedRemove(t, b, testFiles...) 440 if err != nil { 441 t.Fatal(err) 442 } 443 } 444 445 type errorCloser struct { 446 io.Reader 447 l int 448 t testing.TB 449 } 450 451 func (ec errorCloser) Close() error { 452 ec.t.Error("forbidden method close was called") 453 return errors.New("forbidden method close was called") 454 } 455 456 func (ec errorCloser) Len() int { 457 return ec.l 458 } 459 460 // TestSave tests saving data in the backend. 461 func (s *Suite) TestSave(t *testing.T) { 462 seedRand(t) 463 464 b := s.open(t) 465 defer s.close(t, b) 466 var id restic.ID 467 468 saveTests := 10 469 if s.MinimalData { 470 saveTests = 2 471 } 472 473 for i := 0; i < saveTests; i++ { 474 length := rand.Intn(1<<23) + 200000 475 data := test.Random(23, length) 476 // use the first 32 byte as the ID 477 copy(id[:], data) 478 479 h := restic.Handle{ 480 Type: restic.DataFile, 481 Name: fmt.Sprintf("%s-%d", id, i), 482 } 483 err := b.Save(context.TODO(), h, bytes.NewReader(data)) 484 test.OK(t, err) 485 486 buf, err := backend.LoadAll(context.TODO(), b, h) 487 test.OK(t, err) 488 if len(buf) != len(data) { 489 t.Fatalf("number of bytes does not match, want %v, got %v", len(data), len(buf)) 490 } 491 492 if !bytes.Equal(buf, data) { 493 t.Fatalf("data not equal") 494 } 495 496 fi, err := b.Stat(context.TODO(), h) 497 test.OK(t, err) 498 499 if fi.Name != h.Name { 500 t.Errorf("Stat() returned wrong name, want %q, got %q", h.Name, fi.Name) 501 } 502 503 if fi.Size != int64(len(data)) { 504 t.Errorf("Stat() returned different size, want %q, got %d", len(data), fi.Size) 505 } 506 507 err = b.Remove(context.TODO(), h) 508 if err != nil { 509 t.Fatalf("error removing item: %+v", err) 510 } 511 } 512 513 // test saving from a tempfile 514 tmpfile, err := ioutil.TempFile("", "restic-backend-save-test-") 515 if err != nil { 516 t.Fatal(err) 517 } 518 519 length := rand.Intn(1<<23) + 200000 520 data := test.Random(23, length) 521 copy(id[:], data) 522 523 if _, err = tmpfile.Write(data); err != nil { 524 t.Fatal(err) 525 } 526 527 if _, err = tmpfile.Seek(0, io.SeekStart); err != nil { 528 t.Fatal(err) 529 } 530 531 h := restic.Handle{Type: restic.DataFile, Name: id.String()} 532 533 // wrap the tempfile in an errorCloser, so we can detect if the backend 534 // closes the reader 535 err = b.Save(context.TODO(), h, errorCloser{t: t, l: length, Reader: tmpfile}) 536 if err != nil { 537 t.Fatal(err) 538 } 539 540 err = s.delayedRemove(t, b, h) 541 if err != nil { 542 t.Fatalf("error removing item: %+v", err) 543 } 544 545 // try again directly with the temp file 546 if _, err = tmpfile.Seek(588, io.SeekStart); err != nil { 547 t.Fatal(err) 548 } 549 550 err = b.Save(context.TODO(), h, tmpfile) 551 if err != nil { 552 t.Fatal(err) 553 } 554 555 if err = tmpfile.Close(); err != nil { 556 t.Fatal(err) 557 } 558 559 err = b.Remove(context.TODO(), h) 560 if err != nil { 561 t.Fatalf("error removing item: %+v", err) 562 } 563 564 if err = os.Remove(tmpfile.Name()); err != nil { 565 t.Fatal(err) 566 } 567 } 568 569 var filenameTests = []struct { 570 name string 571 data string 572 }{ 573 {"1dfc6bc0f06cb255889e9ea7860a5753e8eb9665c9a96627971171b444e3113e", "x"}, 574 {"f00b4r", "foobar"}, 575 { 576 "1dfc6bc0f06cb255889e9ea7860a5753e8eb9665c9a96627971171b444e3113e4bf8f2d9144cc5420a80f04a4880ad6155fc58903a4fb6457c476c43541dcaa6-5", 577 "foobar content of data blob", 578 }, 579 } 580 581 // TestSaveFilenames tests saving data with various file names in the backend. 582 func (s *Suite) TestSaveFilenames(t *testing.T) { 583 b := s.open(t) 584 defer s.close(t, b) 585 586 for i, test := range filenameTests { 587 h := restic.Handle{Name: test.name, Type: restic.DataFile} 588 err := b.Save(context.TODO(), h, strings.NewReader(test.data)) 589 if err != nil { 590 t.Errorf("test %d failed: Save() returned %+v", i, err) 591 continue 592 } 593 594 buf, err := backend.LoadAll(context.TODO(), b, h) 595 if err != nil { 596 t.Errorf("test %d failed: Load() returned %+v", i, err) 597 continue 598 } 599 600 if !bytes.Equal(buf, []byte(test.data)) { 601 t.Errorf("test %d: returned wrong bytes", i) 602 } 603 604 err = b.Remove(context.TODO(), h) 605 if err != nil { 606 t.Errorf("test %d failed: Remove() returned %+v", i, err) 607 continue 608 } 609 } 610 } 611 612 var testStrings = []struct { 613 id string 614 data string 615 }{ 616 {"c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2", "foobar"}, 617 {"248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1", "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"}, 618 {"cc5d46bdb4991c6eae3eb739c9c8a7a46fe9654fab79c47b4fe48383b5b25e1c", "foo/bar"}, 619 {"4e54d2c721cbdb730f01b10b62dec622962b36966ec685880effa63d71c808f2", "foo/../../baz"}, 620 } 621 622 func store(t testing.TB, b restic.Backend, tpe restic.FileType, data []byte) restic.Handle { 623 id := restic.Hash(data) 624 h := restic.Handle{Name: id.String(), Type: tpe} 625 err := b.Save(context.TODO(), h, bytes.NewReader(data)) 626 test.OK(t, err) 627 return h 628 } 629 630 // testLoad loads a blob (but discards its contents). 631 func testLoad(b restic.Backend, h restic.Handle, length int, offset int64) error { 632 return b.Load(context.TODO(), h, 0, 0, func(rd io.Reader) (ierr error) { 633 _, ierr = io.Copy(ioutil.Discard, rd) 634 return ierr 635 }) 636 } 637 638 func (s *Suite) delayedRemove(t testing.TB, be restic.Backend, handles ...restic.Handle) error { 639 // Some backend (swift, I'm looking at you) may implement delayed 640 // removal of data. Let's wait a bit if this happens. 641 642 for _, h := range handles { 643 err := be.Remove(context.TODO(), h) 644 if s.ErrorHandler != nil { 645 err = s.ErrorHandler(t, be, err) 646 } 647 if err != nil { 648 return err 649 } 650 } 651 652 for _, h := range handles { 653 start := time.Now() 654 attempt := 0 655 var found bool 656 var err error 657 for time.Since(start) <= s.WaitForDelayedRemoval { 658 found, err = be.Test(context.TODO(), h) 659 if s.ErrorHandler != nil { 660 err = s.ErrorHandler(t, be, err) 661 } 662 if err != nil { 663 return err 664 } 665 666 if !found { 667 break 668 } 669 670 time.Sleep(2 * time.Second) 671 attempt++ 672 } 673 674 if found { 675 t.Fatalf("removed blob %v still present after %v (%d attempts)", h, time.Since(start), attempt) 676 } 677 } 678 679 return nil 680 } 681 682 func delayedList(t testing.TB, b restic.Backend, tpe restic.FileType, max int, maxwait time.Duration) restic.IDs { 683 list := restic.NewIDSet() 684 start := time.Now() 685 for i := 0; i < max; i++ { 686 err := b.List(context.TODO(), tpe, func(fi restic.FileInfo) error { 687 id := restic.TestParseID(fi.Name) 688 list.Insert(id) 689 return nil 690 }) 691 692 if err != nil { 693 t.Fatal(err) 694 } 695 696 if len(list) < max && time.Since(start) < maxwait { 697 time.Sleep(500 * time.Millisecond) 698 } 699 } 700 701 return list.List() 702 } 703 704 // TestBackend tests all functions of the backend. 705 func (s *Suite) TestBackend(t *testing.T) { 706 b := s.open(t) 707 defer s.close(t, b) 708 709 for _, tpe := range []restic.FileType{ 710 restic.DataFile, restic.KeyFile, restic.LockFile, 711 restic.SnapshotFile, restic.IndexFile, 712 } { 713 // detect non-existing files 714 for _, ts := range testStrings { 715 id, err := restic.ParseID(ts.id) 716 test.OK(t, err) 717 718 // test if blob is already in repository 719 h := restic.Handle{Type: tpe, Name: id.String()} 720 ret, err := b.Test(context.TODO(), h) 721 test.OK(t, err) 722 test.Assert(t, !ret, "blob was found to exist before creating") 723 724 // try to stat a not existing blob 725 _, err = b.Stat(context.TODO(), h) 726 test.Assert(t, err != nil, "blob data could be extracted before creation") 727 728 // try to read not existing blob 729 err = testLoad(b, h, 0, 0) 730 test.Assert(t, err != nil, "blob could be read before creation") 731 732 // try to get string out, should fail 733 ret, err = b.Test(context.TODO(), h) 734 test.OK(t, err) 735 test.Assert(t, !ret, "id %q was found (but should not have)", ts.id) 736 } 737 738 // add files 739 for _, ts := range testStrings { 740 store(t, b, tpe, []byte(ts.data)) 741 742 // test Load() 743 h := restic.Handle{Type: tpe, Name: ts.id} 744 buf, err := backend.LoadAll(context.TODO(), b, h) 745 test.OK(t, err) 746 test.Equals(t, ts.data, string(buf)) 747 748 // try to read it out with an offset and a length 749 start := 1 750 end := len(ts.data) - 2 751 length := end - start 752 753 buf2 := make([]byte, length) 754 var n int 755 err = b.Load(context.TODO(), h, len(buf2), int64(start), func(rd io.Reader) (ierr error) { 756 n, ierr = io.ReadFull(rd, buf2) 757 return ierr 758 }) 759 test.OK(t, err) 760 test.OK(t, err) 761 test.Equals(t, len(buf2), n) 762 test.Equals(t, ts.data[start:end], string(buf2)) 763 } 764 765 // test adding the first file again 766 ts := testStrings[0] 767 h := restic.Handle{Type: tpe, Name: ts.id} 768 769 // remove and recreate 770 err := s.delayedRemove(t, b, h) 771 test.OK(t, err) 772 773 // test that the blob is gone 774 ok, err := b.Test(context.TODO(), h) 775 test.OK(t, err) 776 test.Assert(t, !ok, "removed blob still present") 777 778 // create blob 779 err = b.Save(context.TODO(), h, strings.NewReader(ts.data)) 780 test.OK(t, err) 781 782 // list items 783 IDs := restic.IDs{} 784 785 for _, ts := range testStrings { 786 id, err := restic.ParseID(ts.id) 787 test.OK(t, err) 788 IDs = append(IDs, id) 789 } 790 791 list := delayedList(t, b, tpe, len(IDs), s.WaitForDelayedRemoval) 792 if len(IDs) != len(list) { 793 t.Fatalf("wrong number of IDs returned: want %d, got %d", len(IDs), len(list)) 794 } 795 796 sort.Sort(IDs) 797 sort.Sort(list) 798 799 if !reflect.DeepEqual(IDs, list) { 800 t.Fatalf("lists aren't equal, want:\n %v\n got:\n%v\n", IDs, list) 801 } 802 803 var handles []restic.Handle 804 for _, ts := range testStrings { 805 id, err := restic.ParseID(ts.id) 806 test.OK(t, err) 807 808 h := restic.Handle{Type: tpe, Name: id.String()} 809 810 found, err := b.Test(context.TODO(), h) 811 test.OK(t, err) 812 test.Assert(t, found, fmt.Sprintf("id %q not found", id)) 813 814 handles = append(handles, h) 815 } 816 817 test.OK(t, s.delayedRemove(t, b, handles...)) 818 } 819 } 820 821 // TestZZZDelete tests the Delete function. The name ensures that this test is executed last. 822 func (s *Suite) TestZZZDelete(t *testing.T) { 823 if !test.TestCleanupTempDirs { 824 t.Skipf("not removing backend, TestCleanupTempDirs is false") 825 } 826 827 b := s.open(t) 828 defer s.close(t, b) 829 830 err := b.Delete(context.TODO()) 831 if err != nil { 832 t.Fatalf("error deleting backend: %+v", err) 833 } 834 }