mirror of
https://github.com/restic/restic.git
synced 2024-12-23 08:16:36 +00:00
lock: Add integration test
The tests check that the wrapped context is properly canceled whenever the repository is unlock or when the lock refresh fails.
This commit is contained in:
parent
c3538b063a
commit
9959190e39
1 changed files with 130 additions and 0 deletions
130
cmd/restic/lock_test.go
Normal file
130
cmd/restic/lock_test.go
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/repository"
|
||||||
|
"github.com/restic/restic/internal/restic"
|
||||||
|
rtest "github.com/restic/restic/internal/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func openTestRepo(t *testing.T, wrapper backendWrapper) (*repository.Repository, func(), *testEnvironment) {
|
||||||
|
env, cleanup := withTestEnvironment(t)
|
||||||
|
if wrapper != nil {
|
||||||
|
env.gopts.backendTestHook = wrapper
|
||||||
|
}
|
||||||
|
testRunInit(t, env.gopts)
|
||||||
|
|
||||||
|
repo, err := OpenRepository(context.TODO(), env.gopts)
|
||||||
|
rtest.OK(t, err)
|
||||||
|
return repo, cleanup, env
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkedLockRepo(ctx context.Context, t *testing.T, repo restic.Repository) (*restic.Lock, context.Context) {
|
||||||
|
lock, wrappedCtx, err := lockRepo(ctx, repo)
|
||||||
|
rtest.OK(t, err)
|
||||||
|
rtest.OK(t, wrappedCtx.Err())
|
||||||
|
if lock.Stale() {
|
||||||
|
t.Fatal("lock returned stale lock")
|
||||||
|
}
|
||||||
|
return lock, wrappedCtx
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLock(t *testing.T) {
|
||||||
|
repo, cleanup, _ := openTestRepo(t, nil)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
lock, wrappedCtx := checkedLockRepo(context.Background(), t, repo)
|
||||||
|
unlockRepo(lock)
|
||||||
|
if wrappedCtx.Err() == nil {
|
||||||
|
t.Fatal("unlock did not cancel context")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLockCancel(t *testing.T) {
|
||||||
|
repo, cleanup, _ := openTestRepo(t, nil)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
lock, wrappedCtx := checkedLockRepo(ctx, t, repo)
|
||||||
|
cancel()
|
||||||
|
if wrappedCtx.Err() == nil {
|
||||||
|
t.Fatal("canceled parent context did not cancel context")
|
||||||
|
}
|
||||||
|
|
||||||
|
// unlockRepo should not crash
|
||||||
|
unlockRepo(lock)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLockUnlockAll(t *testing.T) {
|
||||||
|
repo, cleanup, _ := openTestRepo(t, nil)
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
lock, wrappedCtx := checkedLockRepo(context.Background(), t, repo)
|
||||||
|
_, err := unlockAll(0)
|
||||||
|
rtest.OK(t, err)
|
||||||
|
if wrappedCtx.Err() == nil {
|
||||||
|
t.Fatal("canceled parent context did not cancel context")
|
||||||
|
}
|
||||||
|
|
||||||
|
// unlockRepo should not crash
|
||||||
|
unlockRepo(lock)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLockConflict(t *testing.T) {
|
||||||
|
repo, cleanup, env := openTestRepo(t, nil)
|
||||||
|
defer cleanup()
|
||||||
|
repo2, err := OpenRepository(context.TODO(), env.gopts)
|
||||||
|
rtest.OK(t, err)
|
||||||
|
|
||||||
|
lock, _, err := lockRepoExclusive(context.Background(), repo)
|
||||||
|
rtest.OK(t, err)
|
||||||
|
defer unlockRepo(lock)
|
||||||
|
_, _, err = lockRepo(context.Background(), repo2)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("second lock should have failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type writeOnceBackend struct {
|
||||||
|
restic.Backend
|
||||||
|
written bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *writeOnceBackend) Save(ctx context.Context, h restic.Handle, rd restic.RewindReader) error {
|
||||||
|
if b.written {
|
||||||
|
return fmt.Errorf("fail after first write")
|
||||||
|
}
|
||||||
|
b.written = true
|
||||||
|
return b.Backend.Save(ctx, h, rd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLockFailedRefresh(t *testing.T) {
|
||||||
|
repo, cleanup, _ := openTestRepo(t, func(r restic.Backend) (restic.Backend, error) {
|
||||||
|
return &writeOnceBackend{Backend: r}, nil
|
||||||
|
})
|
||||||
|
defer cleanup()
|
||||||
|
|
||||||
|
// reduce locking intervals to be suitable for testing
|
||||||
|
ri, rt := refreshInterval, refreshabilityTimeout
|
||||||
|
refreshInterval = 20 * time.Millisecond
|
||||||
|
refreshabilityTimeout = 100 * time.Millisecond
|
||||||
|
defer func() {
|
||||||
|
refreshInterval, refreshabilityTimeout = ri, rt
|
||||||
|
}()
|
||||||
|
|
||||||
|
lock, wrappedCtx := checkedLockRepo(context.Background(), t, repo)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-wrappedCtx.Done():
|
||||||
|
// expected lock refresh failure
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
t.Fatal("failed lock refresh did not cause context cancellation")
|
||||||
|
}
|
||||||
|
// unlockRepo should not crash
|
||||||
|
unlockRepo(lock)
|
||||||
|
}
|
Loading…
Reference in a new issue