package release import ( "context" "errors" "os" "sync" "testing" "time" "github.com/carlosframework/platform/internal/rootstate" ) type exclusionDisk struct { rootstate.Store mu sync.Mutex } func (d *exclusionDisk) Lock(ctx context.Context, name string) (func(), error) { d.mu.Lock() return d.mu.Unlock, nil } func (d *exclusionDisk) Close() error { return nil } func TestPlan14ReleaseExclusionToken(t *testing.T) { disk := &exclusionDisk{} a := &ReleaseExclusion{Open: func() (rootstate.Store, error) { return disk, nil }} b := &ReleaseExclusion{Open: a.Open} if !errors.Is(a.Check(t.Context()), os.ErrPermission) { t.Fatal("missing token accepted") } var stale context.Context if err := a.With(t.Context(), func(ctx context.Context) error { stale = ctx if err := a.Check(ctx); err != nil { t.Fatal(err) } if !errors.Is(b.Check(ctx), os.ErrPermission) { t.Fatal("foreign token accepted") } if !errors.Is(b.With(ctx, func(context.Context) error { t.Fatal("foreign nested lock"); return nil }), os.ErrPermission) { t.Fatal("foreign nesting accepted") } return a.With(ctx, func(nested context.Context) error { return a.Check(nested) }) }); err != nil { t.Fatal(err) } if !errors.Is(a.Check(stale), os.ErrPermission) { t.Fatal("stale token accepted") } if !errors.Is(a.With(stale, func(context.Context) error { t.Fatal("stale reused"); return nil }), os.ErrPermission) { t.Fatal("stale context reacquired") } } type deadlineExclusionDisk struct { rootstate.Store wait bool } func (d deadlineExclusionDisk) Lock(ctx context.Context, name string) (func(), error) { if _, ok := ctx.Deadline(); !ok { return nil, os.ErrPermission } if d.wait { <-ctx.Done() return nil, ctx.Err() } return func() {}, nil } func (d deadlineExclusionDisk) Close() error { return nil } func TestPlan14ReleaseExclusionWaitBudget(t *testing.T) { for _, wait := range []bool{false, true} { x := &ReleaseExclusion{Wait: 10 * time.Millisecond, Open: func() (rootstate.Store, error) { return deadlineExclusionDisk{wait: wait}, nil }} called := false err := x.With(t.Context(), func(ctx context.Context) error { called = true; time.Sleep(20 * time.Millisecond); return x.Check(ctx) }) if wait { if !errors.Is(err, context.DeadlineExceeded) || called { t.Fatal("unbounded wait", err, called) } } else if err != nil || !called { t.Fatal("wait budget leaked into effect", err, called) } } }