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  }