Skip to content
quarry Artifact [5f7acad9fc]
Login

Artifact [5f7acad9fc]

Artifact 5f7acad9fc371cfc838d41f3331cba22ad0d00147a2d97d3fa8d81367a47828f:


package zitcli

import (
	"context"
	"errors"
	"testing"
	"time"

	"craftdesign.group/quarry/internal/ops"
)

// The gate admits its bound and no more: the next caller waits, and a
// caller whose request is cancelled while waiting is told busy rather
// than queued. Busy is an ErrUnavailable, so the surfaces above answer
// 503 with Retry-After.
func TestAdmissionBoundsSubprocesses(t *testing.T) {
	a := newAdmission(2)
	if !a.acquire(context.Background()) || !a.acquire(context.Background()) {
		t.Fatal("the first two callers were refused")
	}
	ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
	defer cancel()
	start := time.Now()
	if a.acquire(ctx) {
		t.Fatal("a third caller was admitted past the bound")
	}
	if time.Since(start) > busyWait {
		t.Errorf("a cancelled caller waited the full busyWait")
	}
	a.release()
	if !a.acquire(context.Background()) {
		t.Fatal("a released slot was not reusable")
	}
	if !errors.Is(ErrBusy, ops.ErrUnavailable) {
		t.Errorf("ErrBusy is not an ErrUnavailable")
	}
}

// A command minted while the gate is full fails at once with ErrBusy
// instead of spawning.
func TestCommandFailsBusyWhenTheGateIsFull(t *testing.T) {
	old := Parallel()
	SetParallel(1)
	t.Cleanup(func() { SetParallel(old) })
	if !gate.acquire(context.Background()) {
		t.Fatal("could not fill the gate")
	}
	defer gate.release()
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Millisecond)
	defer cancel()
	cmd, done := New(t.TempDir()).command(ctx, "version")
	defer done()
	if _, err := cmd.Output(); !errors.Is(err, ErrBusy) {
		t.Fatalf("command past the bound ran or failed otherwise: %v", err)
	}
}