package engine import ( "context" "errors" "testing" "time" "tangled.org/core/spindle/models" ) func TestSemaphoreSlotterDisabledDoesNotBlock(t *testing.T) { t.Parallel() slotter := NewSemaphoreSlotter(0) for range 10 { slot, err := slotter.AcquireWorkflowSlot(context.Background(), zeroWorkflowID(), nil) if err != nil { t.Fatalf("AcquireWorkflowSlot() error = %v", err) } slot.Release() } } func TestSemaphoreSlotterBlocksUntilRelease(t *testing.T) { t.Parallel() slotter := NewSemaphoreSlotter(1) first, err := slotter.AcquireWorkflowSlot(context.Background(), zeroWorkflowID(), nil) if err != nil { t.Fatalf("first AcquireWorkflowSlot() error = %v", err) } releasedFirst := false defer func() { if !releasedFirst { first.Release() } }() acquired := make(chan WorkflowSlot, 1) errs := make(chan error, 1) go func() { slot, err := slotter.AcquireWorkflowSlot(context.Background(), zeroWorkflowID(), nil) if err != nil { errs <- err return } acquired <- slot }() assertNotAcquired(t, acquired, errs) first.Release() releasedFirst = true second := waitForSlot(t, acquired, errs) second.Release() } func TestSemaphoreSlotterHonorsContextCancellation(t *testing.T) { t.Parallel() slotter := NewSemaphoreSlotter(1) first, err := slotter.AcquireWorkflowSlot(context.Background(), zeroWorkflowID(), nil) if err != nil { t.Fatalf("first AcquireWorkflowSlot() error = %v", err) } defer first.Release() ctx, cancel := context.WithCancel(context.Background()) cancel() _, err = slotter.AcquireWorkflowSlot(ctx, zeroWorkflowID(), nil) if !errors.Is(err, context.Canceled) { t.Fatalf("AcquireWorkflowSlot() error = %v, want context.Canceled", err) } } func assertNotAcquired(t *testing.T, acquired <-chan WorkflowSlot, errs <-chan error) { t.Helper() select { case slot := <-acquired: slot.Release() t.Fatal("AcquireWorkflowSlot() acquired a slot before one was released") case err := <-errs: t.Fatalf("AcquireWorkflowSlot() returned unexpected error: %v", err) case <-time.After(25 * time.Millisecond): } } func waitForSlot(t *testing.T, acquired <-chan WorkflowSlot, errs <-chan error) WorkflowSlot { t.Helper() select { case slot := <-acquired: return slot case err := <-errs: t.Fatalf("AcquireWorkflowSlot() returned error: %v", err) case <-time.After(time.Second): t.Fatal("timed out waiting for slot acquisition") } return nil } func zeroWorkflowID() models.WorkflowId { return models.WorkflowId{} }