Skip to content
quarry Artifact [549ec2d523]
Login

Artifact [549ec2d523]

Artifact 549ec2d523be7e14147e47aff044c2e6f5e57087f57d3bfdab872035e3c34475:


package runner

import (
	"context"
	"encoding/json"
	"net/http/httptest"
	"testing"
	"time"
)

// WORKFLOW JOBS RUN SIDE BY SIDE UP TO --max-tasks.
//
// Every job is task type <group>.job, and the runner kept one workspace
// per task type behind a lock, so jobs ran one at a time whatever
// --max-tasks said (measured on zeitforge by an outside tester). Two
// jobs that each sleep a second finish in about one second, not two.
func TestWorkflowJobsRunSideBySide(t *testing.T) {
	job, _ := json.Marshal(Job{Steps: []JobStep{{Run: "sleep 1"}}})
	meta := map[string]string{JobKey: string(job)}
	a, b := task("a", "c1"), task("b", "c1")
	a.Metadata, b.Metadata = meta, meta
	br := &fakeBridge{queue: map[string][]polled{"t-job": {a, b}}, 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-job"}, MaxTasks: 2,
		WorkDir: t.TempDir(), Checkout: appCheckout{}, 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(10 * time.Second); time.Now().Before(deadline); time.Sleep(20 * time.Millisecond) {
		br.mu.Lock()
		n := len(br.resolved)
		br.mu.Unlock()
		if n == 2 {
			break
		}
	}
	took := time.Since(start)
	cancel()
	<-done
	br.mu.Lock()
	defer br.mu.Unlock()
	if br.resolved["a"]["action"] != "complete" || br.resolved["b"]["action"] != "complete" {
		t.Fatalf("FAIL: resolved %v", br.resolved)
	}
	if took >= 1900*time.Millisecond {
		t.Errorf("FAIL: two one-second jobs took %s with --max-tasks 2; want them side by side", took)
	}
	t.Logf("ok: two one-second jobs in %s", took.Round(10*time.Millisecond))
}