Skip to content
quarry Artifact [0075da5c9b]
Login

Artifact [0075da5c9b]

Artifact 0075da5c9b2d67e8d1cb578c17a4b179e1c0442848a0973906270b1b83cc5ca5:


package runner

import (
	"context"
	"net/http/httptest"
	"os"
	"path/filepath"
	"sort"
	"sync"
	"testing"
	"time"
)

// dirCheckout records the workspace each checkout is asked for, and
// leaves a marker the first time a workspace is used, so a reused one
// is seen to keep its checkout.
type dirCheckout struct {
	manifest string
	mu       sync.Mutex
	dirs     []string
	fresh    []bool
}

func (d *dirCheckout) Checkout(_ context.Context, dir, _ string) error {
	marker := filepath.Join(dir, "marker")
	_, err := os.Stat(marker)
	d.mu.Lock()
	d.dirs = append(d.dirs, dir)
	d.fresh = append(d.fresh, err != nil)
	d.mu.Unlock()
	if err := os.MkdirAll(filepath.Join(dir, "ci"), 0o755); err != nil {
		return err
	}
	if err := os.WriteFile(marker, nil, 0o644); err != nil {
		return err
	}
	return os.WriteFile(filepath.Join(dir, ManifestPath), []byte(d.manifest), 0o644)
}

// runManifestTasks runs n one-second manifest tasks of one type and
// returns how long they took, and what the checkouts saw.
func runManifestTasks(t *testing.T, n, maxTasks int) (time.Duration, *dirCheckout) {
	t.Helper()
	co := &dirCheckout{manifest: `{"tasks":{"t-race":["sleep","1"]}}`}
	var queue []polled
	for i := 0; i < n; i++ {
		queue = append(queue, task(string(rune('a'+i)), "c1"))
	}
	br := &fakeBridge{queue: map[string][]polled{"t-race": queue}, resolved: map[string]map[string]any{}, logs: map[string]string{}}
	srv := httptest.NewServer(br.handler())
	defer srv.Close()
	r, err := New(Config{
		Bridge: srv.URL, Token: "WT", Tasks: []string{"t-race"}, MaxTasks: maxTasks,
		WorkDir: t.TempDir(), Checkout: co, Heartbeat: 50 * time.Millisecond, Logf: t.Logf,
	})
	if err != nil {
		t.Fatal(err)
	}
	ctx, cancel := context.WithCancel(context.Background())
	done := make(chan struct{})
	go func() { r.Run(ctx); close(done) }()
	start := time.Now()
	for deadline := start.Add(15 * time.Second); time.Now().Before(deadline); time.Sleep(20 * time.Millisecond) {
		br.mu.Lock()
		got := len(br.resolved)
		br.mu.Unlock()
		if got == n {
			break
		}
	}
	took := time.Since(start)
	cancel()
	<-done
	br.mu.Lock()
	defer br.mu.Unlock()
	for id, res := range br.resolved {
		if res["action"] != "complete" {
			t.Fatalf("FAIL: task %s resolved %v", id, res)
		}
	}
	if len(br.resolved) != n {
		t.Fatalf("FAIL: %d of %d tasks resolved", len(br.resolved), n)
	}
	return took, co
}

// MANIFEST TASKS OF ONE TYPE RUN SIDE BY SIDE UP TO --max-tasks.
//
// Two landings' race checks used to queue behind one workspace per task
// type. Each now takes a numbered workspace, so two one-second tasks of
// one type finish in about one second, each in a workspace of its own.
func TestManifestTasksOfOneTypeRunSideBySide(t *testing.T) {
	took, co := runManifestTasks(t, 2, 2)
	if took >= 1900*time.Millisecond {
		t.Errorf("FAIL: two one-second tasks took %s with --max-tasks 2; want them side by side", took)
	}
	if len(co.dirs) != 2 || co.dirs[0] == co.dirs[1] {
		t.Errorf("FAIL: the two tasks shared a workspace: %v", co.dirs)
	}
	t.Logf("ok: two one-second tasks in %s", took.Round(10*time.Millisecond))
}

// A THIRD TASK WAITS ONLY WHEN --max-tasks ARE BUSY.
func TestManifestTaskWaitsOnlyWhenMaxTasksAreBusy(t *testing.T) {
	took, _ := runManifestTasks(t, 3, 2)
	if took < 1900*time.Millisecond || took >= 2900*time.Millisecond {
		t.Errorf("FAIL: three one-second tasks took %s with --max-tasks 2; want two rounds", took)
	}
}

// A REUSED WORKSPACE KEEPS ITS CHECKOUT; A NEW ONE STARTS CLEAN.
func TestManifestWorkspaceIsReusedIncrementally(t *testing.T) {
	_, co := runManifestTasks(t, 3, 2)
	seen := map[string]bool{}
	for i, dir := range co.dirs {
		if co.fresh[i] != !seen[dir] {
			t.Errorf("FAIL: checkout %d of %s fresh=%v, want %v", i, dir, co.fresh[i], !seen[dir])
		}
		seen[dir] = true
	}
	var names []string
	for d := range seen {
		names = append(names, filepath.Base(d))
	}
	sort.Strings(names)
	if len(names) != 2 {
		t.Errorf("FAIL: three tasks with --max-tasks 2 used %d workspaces: %v", len(names), names)
	}
}