package redislock import ( "context" "fmt" "strings" "testing" "time" ) type fakeClient struct { value string ttlMS int64 } func (f *fakeClient) SetnxExCtx(ctx context.Context, key, value string, seconds int) (bool, error) { if err := ctx.Err(); err != nil { return false, err } if f.value != "" { return false, nil } f.value = value f.ttlMS = int64(seconds) * 1000 return true, nil } func (f *fakeClient) EvalCtx(ctx context.Context, script string, keys []string, args ...any) (any, error) { if err := ctx.Err(); err != nil { return nil, err } owner := fmt.Sprint(args[0]) if f.value != owner { return int64(0), nil } if script == renewScript { f.ttlMS = args[1].(int64) return int64(1), nil } if script == releaseScript { f.value = "" return int64(1), nil } return nil, fmt.Errorf("unexpected script") } func TestLockUsesUniqueTokenPerAcquisition(t *testing.T) { client := &fakeClient{} lock := NewWithClient(client, "locks:outbox", "worker-7", time.Minute) acquired, err := lock.Acquire(context.Background()) if err != nil || !acquired { t.Fatalf("Acquire() = %v, %v", acquired, err) } first := client.value if !strings.HasPrefix(first, "worker-7:") { t.Fatalf("token %q has no worker prefix", first) } if err := lock.Release(context.Background()); err != nil { t.Fatal(err) } acquired, err = lock.Acquire(context.Background()) if err != nil || !acquired { t.Fatalf("second Acquire() = %v, %v", acquired, err) } if client.value == first { t.Fatal("successful acquisitions reused a token") } } func TestRenewAndReleaseRequireCurrentOwnership(t *testing.T) { client := &fakeClient{} lock := NewWithClient(client, "locks:outbox", "worker-7", 3*time.Minute) if acquired, err := lock.Acquire(context.Background()); err != nil || !acquired { t.Fatalf("Acquire() = %v, %v", acquired, err) } client.ttlMS = 1 renewed, err := lock.Renew(context.Background()) if err != nil || !renewed || client.ttlMS != int64(3*time.Minute/time.Millisecond) { t.Fatalf("Renew() = %v, %v, ttl=%d", renewed, err, client.ttlMS) } client.value = "worker-8:successor" renewed, err = lock.Renew(context.Background()) if err != nil || renewed { t.Fatalf("lost-owner Renew() = %v, %v", renewed, err) } if err := lock.Release(context.Background()); err != nil { t.Fatal(err) } if client.value != "worker-8:successor" { t.Fatalf("Release removed successor lease: %q", client.value) } } func TestLockMethodsHonorCanceledContext(t *testing.T) { client := &fakeClient{} lock := NewWithClient(client, "locks:outbox", "worker-7", time.Minute) ctx, cancel := context.WithCancel(context.Background()) cancel() if _, err := lock.Acquire(ctx); err == nil { t.Fatal("Acquire succeeded with canceled context") } }