diff --git a/repository/master_index_test.go b/repository/master_index_test.go new file mode 100644 index 000000000..923eef338 --- /dev/null +++ b/repository/master_index_test.go @@ -0,0 +1,146 @@ +package repository + +import ( + "bytes" + "crypto/rand" + "errors" + "io" + mrand "math/rand" + "sync" + "testing" + "time" + + "github.com/restic/restic/backend" + "github.com/restic/restic/pack" +) + +const parallelSaves = 20 +const saveIndexTime = 100 * time.Millisecond +const testTimeout = 1 * time.Second + +var DupID backend.ID + +func randomID() backend.ID { + if mrand.Float32() < 0.5 { + return DupID + } + + id := backend.ID{} + _, err := io.ReadFull(rand.Reader, id[:]) + if err != nil { + panic(err) + } + return id +} + +// forgetfulBackend returns a backend that forgets everything. +func forgetfulBackend() backend.Backend { + be := &backend.MockBackend{} + + be.TestFn = func(t backend.Type, name string) (bool, error) { + return false, nil + } + + be.LoadFn = func(h backend.Handle, p []byte, off int64) (int, error) { + return 0, errors.New("not found") + } + + be.SaveFn = func(h backend.Handle, p []byte) error { + return nil + } + + be.StatFn = func(h backend.Handle) (backend.BlobInfo, error) { + return backend.BlobInfo{}, errors.New("not found") + } + + be.RemoveFn = func(t backend.Type, name string) error { + return nil + } + + be.ListFn = func(t backend.Type, done <-chan struct{}) <-chan string { + ch := make(chan string) + close(ch) + return ch + } + + be.DeleteFn = func() error { + return nil + } + + return be +} + +func testMasterIndex(t *testing.T) { + _, err := io.ReadFull(rand.Reader, DupID[:]) + if err != nil { + t.Fatal(err) + } + + repo := New(forgetfulBackend()) + err = repo.Init("foo") + if err != nil { + t.Fatal(err) + } + + wg := &sync.WaitGroup{} + done := make(chan struct{}) + for i := 0; i < parallelSaves; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for { + select { + case <-done: + return + default: + } + + id := randomID() + + if repo.Index().Has(id) { + continue + } + + buf := make([]byte, 50) + + err := repo.SaveFrom(pack.Data, &id, uint(len(buf)), bytes.NewReader(buf)) + if err != nil { + t.Fatal(err) + } + } + }() + } + + saveIndexes := func() { + defer wg.Done() + + ticker := time.NewTicker(saveIndexTime) + defer ticker.Stop() + + for { + select { + case <-done: + return + case <-ticker.C: + err := repo.SaveFullIndex() + if err != nil { + t.Fatal(err) + } + } + } + } + + wg.Add(1) + go saveIndexes() + + <-time.After(testTimeout) + close(done) + + wg.Wait() +} + +func TestMasterIndex(t *testing.T) { + for i := 0; i < 5; i++ { + testMasterIndex(t) + } +}