github.com/mckael/restic@v0.8.3/internal/restic/lock_test.go (about)

     1  package restic_test
     2  
     3  import (
     4  	"context"
     5  	"os"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/restic/restic/internal/repository"
    10  	"github.com/restic/restic/internal/restic"
    11  	rtest "github.com/restic/restic/internal/test"
    12  )
    13  
    14  func TestLock(t *testing.T) {
    15  	repo, cleanup := repository.TestRepository(t)
    16  	defer cleanup()
    17  
    18  	lock, err := restic.NewLock(context.TODO(), repo)
    19  	rtest.OK(t, err)
    20  
    21  	rtest.OK(t, lock.Unlock())
    22  }
    23  
    24  func TestDoubleUnlock(t *testing.T) {
    25  	repo, cleanup := repository.TestRepository(t)
    26  	defer cleanup()
    27  
    28  	lock, err := restic.NewLock(context.TODO(), repo)
    29  	rtest.OK(t, err)
    30  
    31  	rtest.OK(t, lock.Unlock())
    32  
    33  	err = lock.Unlock()
    34  	rtest.Assert(t, err != nil,
    35  		"double unlock didn't return an error, got %v", err)
    36  }
    37  
    38  func TestMultipleLock(t *testing.T) {
    39  	repo, cleanup := repository.TestRepository(t)
    40  	defer cleanup()
    41  
    42  	lock1, err := restic.NewLock(context.TODO(), repo)
    43  	rtest.OK(t, err)
    44  
    45  	lock2, err := restic.NewLock(context.TODO(), repo)
    46  	rtest.OK(t, err)
    47  
    48  	rtest.OK(t, lock1.Unlock())
    49  	rtest.OK(t, lock2.Unlock())
    50  }
    51  
    52  func TestLockExclusive(t *testing.T) {
    53  	repo, cleanup := repository.TestRepository(t)
    54  	defer cleanup()
    55  
    56  	elock, err := restic.NewExclusiveLock(context.TODO(), repo)
    57  	rtest.OK(t, err)
    58  	rtest.OK(t, elock.Unlock())
    59  }
    60  
    61  func TestLockOnExclusiveLockedRepo(t *testing.T) {
    62  	repo, cleanup := repository.TestRepository(t)
    63  	defer cleanup()
    64  
    65  	elock, err := restic.NewExclusiveLock(context.TODO(), repo)
    66  	rtest.OK(t, err)
    67  
    68  	lock, err := restic.NewLock(context.TODO(), repo)
    69  	rtest.Assert(t, err != nil,
    70  		"create normal lock with exclusively locked repo didn't return an error")
    71  	rtest.Assert(t, restic.IsAlreadyLocked(err),
    72  		"create normal lock with exclusively locked repo didn't return the correct error")
    73  
    74  	rtest.OK(t, lock.Unlock())
    75  	rtest.OK(t, elock.Unlock())
    76  }
    77  
    78  func TestExclusiveLockOnLockedRepo(t *testing.T) {
    79  	repo, cleanup := repository.TestRepository(t)
    80  	defer cleanup()
    81  
    82  	elock, err := restic.NewLock(context.TODO(), repo)
    83  	rtest.OK(t, err)
    84  
    85  	lock, err := restic.NewExclusiveLock(context.TODO(), repo)
    86  	rtest.Assert(t, err != nil,
    87  		"create normal lock with exclusively locked repo didn't return an error")
    88  	rtest.Assert(t, restic.IsAlreadyLocked(err),
    89  		"create normal lock with exclusively locked repo didn't return the correct error")
    90  
    91  	rtest.OK(t, lock.Unlock())
    92  	rtest.OK(t, elock.Unlock())
    93  }
    94  
    95  func createFakeLock(repo restic.Repository, t time.Time, pid int) (restic.ID, error) {
    96  	hostname, err := os.Hostname()
    97  	if err != nil {
    98  		return restic.ID{}, err
    99  	}
   100  
   101  	newLock := &restic.Lock{Time: t, PID: pid, Hostname: hostname}
   102  	return repo.SaveJSONUnpacked(context.TODO(), restic.LockFile, &newLock)
   103  }
   104  
   105  func removeLock(repo restic.Repository, id restic.ID) error {
   106  	h := restic.Handle{Type: restic.LockFile, Name: id.String()}
   107  	return repo.Backend().Remove(context.TODO(), h)
   108  }
   109  
   110  var staleLockTests = []struct {
   111  	timestamp        time.Time
   112  	stale            bool
   113  	staleOnOtherHost bool
   114  	pid              int
   115  }{
   116  	{
   117  		timestamp:        time.Now(),
   118  		stale:            false,
   119  		staleOnOtherHost: false,
   120  		pid:              os.Getpid(),
   121  	},
   122  	{
   123  		timestamp:        time.Now().Add(-time.Hour),
   124  		stale:            true,
   125  		staleOnOtherHost: true,
   126  		pid:              os.Getpid(),
   127  	},
   128  	{
   129  		timestamp:        time.Now().Add(3 * time.Minute),
   130  		stale:            false,
   131  		staleOnOtherHost: false,
   132  		pid:              os.Getpid(),
   133  	},
   134  	{
   135  		timestamp:        time.Now(),
   136  		stale:            true,
   137  		staleOnOtherHost: false,
   138  		pid:              os.Getpid() + 500000,
   139  	},
   140  }
   141  
   142  func TestLockStale(t *testing.T) {
   143  	hostname, err := os.Hostname()
   144  	rtest.OK(t, err)
   145  
   146  	otherHostname := "other-" + hostname
   147  
   148  	for i, test := range staleLockTests {
   149  		lock := restic.Lock{
   150  			Time:     test.timestamp,
   151  			PID:      test.pid,
   152  			Hostname: hostname,
   153  		}
   154  
   155  		rtest.Assert(t, lock.Stale() == test.stale,
   156  			"TestStaleLock: test %d failed: expected stale: %v, got %v",
   157  			i, test.stale, !test.stale)
   158  
   159  		lock.Hostname = otherHostname
   160  		rtest.Assert(t, lock.Stale() == test.staleOnOtherHost,
   161  			"TestStaleLock: test %d failed: expected staleOnOtherHost: %v, got %v",
   162  			i, test.staleOnOtherHost, !test.staleOnOtherHost)
   163  	}
   164  }
   165  
   166  func lockExists(repo restic.Repository, t testing.TB, id restic.ID) bool {
   167  	h := restic.Handle{Type: restic.LockFile, Name: id.String()}
   168  	exists, err := repo.Backend().Test(context.TODO(), h)
   169  	rtest.OK(t, err)
   170  
   171  	return exists
   172  }
   173  
   174  func TestLockWithStaleLock(t *testing.T) {
   175  	repo, cleanup := repository.TestRepository(t)
   176  	defer cleanup()
   177  
   178  	id1, err := createFakeLock(repo, time.Now().Add(-time.Hour), os.Getpid())
   179  	rtest.OK(t, err)
   180  
   181  	id2, err := createFakeLock(repo, time.Now().Add(-time.Minute), os.Getpid())
   182  	rtest.OK(t, err)
   183  
   184  	id3, err := createFakeLock(repo, time.Now().Add(-time.Minute), os.Getpid()+500000)
   185  	rtest.OK(t, err)
   186  
   187  	rtest.OK(t, restic.RemoveStaleLocks(context.TODO(), repo))
   188  
   189  	rtest.Assert(t, lockExists(repo, t, id1) == false,
   190  		"stale lock still exists after RemoveStaleLocks was called")
   191  	rtest.Assert(t, lockExists(repo, t, id2) == true,
   192  		"non-stale lock was removed by RemoveStaleLocks")
   193  	rtest.Assert(t, lockExists(repo, t, id3) == false,
   194  		"stale lock still exists after RemoveStaleLocks was called")
   195  
   196  	rtest.OK(t, removeLock(repo, id2))
   197  }
   198  
   199  func TestRemoveAllLocks(t *testing.T) {
   200  	repo, cleanup := repository.TestRepository(t)
   201  	defer cleanup()
   202  
   203  	id1, err := createFakeLock(repo, time.Now().Add(-time.Hour), os.Getpid())
   204  	rtest.OK(t, err)
   205  
   206  	id2, err := createFakeLock(repo, time.Now().Add(-time.Minute), os.Getpid())
   207  	rtest.OK(t, err)
   208  
   209  	id3, err := createFakeLock(repo, time.Now().Add(-time.Minute), os.Getpid()+500000)
   210  	rtest.OK(t, err)
   211  
   212  	rtest.OK(t, restic.RemoveAllLocks(context.TODO(), repo))
   213  
   214  	rtest.Assert(t, lockExists(repo, t, id1) == false,
   215  		"lock still exists after RemoveAllLocks was called")
   216  	rtest.Assert(t, lockExists(repo, t, id2) == false,
   217  		"lock still exists after RemoveAllLocks was called")
   218  	rtest.Assert(t, lockExists(repo, t, id3) == false,
   219  		"lock still exists after RemoveAllLocks was called")
   220  }
   221  
   222  func TestLockRefresh(t *testing.T) {
   223  	repo, cleanup := repository.TestRepository(t)
   224  	defer cleanup()
   225  
   226  	lock, err := restic.NewLock(context.TODO(), repo)
   227  	rtest.OK(t, err)
   228  
   229  	var lockID *restic.ID
   230  	err = repo.List(context.TODO(), restic.LockFile, func(id restic.ID, size int64) error {
   231  		if lockID != nil {
   232  			t.Error("more than one lock found")
   233  		}
   234  		lockID = &id
   235  		return nil
   236  	})
   237  	if err != nil {
   238  		t.Fatal(err)
   239  	}
   240  
   241  	rtest.OK(t, lock.Refresh(context.TODO()))
   242  
   243  	var lockID2 *restic.ID
   244  	err = repo.List(context.TODO(), restic.LockFile, func(id restic.ID, size int64) error {
   245  		if lockID2 != nil {
   246  			t.Error("more than one lock found")
   247  		}
   248  		lockID2 = &id
   249  		return nil
   250  	})
   251  	if err != nil {
   252  		t.Fatal(err)
   253  	}
   254  
   255  	rtest.Assert(t, !lockID.Equal(*lockID2),
   256  		"expected a new ID after lock refresh, got the same")
   257  	rtest.OK(t, lock.Unlock())
   258  }