Skip to content
quarry Artifact [c8efb0699f]
Login

Artifact [c8efb0699f]

Artifact c8efb0699f65446cdec988ca06e68d4043dc0a89bf766c0b4260d2262e31490a:


package runner

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

// A TASK WHOSE RUN IS CANCELLED STOPS.
//
// jva measured a closed landing's merge job running its full four
// minutes after the run was cancelled, holding the runner. dagnats#737
// answers 409 to a heartbeat of such a task; the runner then kills the
// job, its children included, and reports nothing.
func TestATaskWhoseRunIsCancelledStops(t *testing.T) {
	job, _ := json.Marshal(Job{Steps: []JobStep{{Run: "sleep 30; echo ran-to-$((40+2))"}}})
	tk := task("doomed", "c1")
	tk.Metadata = map[string]string{JobKey: string(job)}
	br := &fakeBridge{queue: map[string][]polled{"t-job": {tk}}, resolved: map[string]map[string]any{}, logs: map[string]string{},
		cancelled: map[string]bool{}}
	srv := httptest.NewServer(br.handler())
	defer srv.Close()
	var logged []string
	r, err := New(Config{
		Bridge: srv.URL, Token: "WT", Tasks: []string{"t-job"}, MaxTasks: 1,
		WorkDir: t.TempDir(), Checkout: appCheckout{}, Heartbeat: 100 * time.Millisecond,
		Logf: func(f string, a ...any) { logged = append(logged, f) },
	})
	if err != nil {
		t.Fatal(err)
	}
	ctx, cancel := context.WithCancel(context.Background())
	done := make(chan struct{})
	go func() { r.Run(ctx); close(done) }()
	// Let it start, then cancel the run.
	time.Sleep(500 * time.Millisecond)
	br.mu.Lock()
	br.cancelled["doomed"] = true
	br.mu.Unlock()
	cancelledAt := time.Now()
	stopped := false
	for deadline := time.Now().Add(15 * time.Second); time.Now().Before(deadline); time.Sleep(50 * time.Millisecond) {
		br.mu.Lock()
		l := br.logs["doomed"]
		br.mu.Unlock()
		if strings.Contains(l, "stopped: this task's run was cancelled") {
			stopped = true
			break
		}
	}
	took := time.Since(cancelledAt)
	cancel()
	<-done
	if !stopped {
		t.Fatalf("FAIL: the task did not stop within 15s of its run being cancelled")
	}
	if took > 8*time.Second {
		t.Errorf("FAIL: stopping took %s; want it within a few heartbeats", took)
	}
	br.mu.Lock()
	defer br.mu.Unlock()
	if strings.Contains(br.logs["doomed"], "ran-to-42") {
		t.Error("FAIL: the job ran to its end")
	}
	for _, a := range br.actions {
		if a == "doomed complete" || a == "doomed fail" {
			t.Errorf("FAIL: the runner reported %q for a withdrawn task", a)
		}
	}
	t.Logf("ok: stopped %s after its run was cancelled, reported nothing", took.Round(10*time.Millisecond))
}