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  }