Skip to content
quarry Artifact [d8a4c16cdd]
Login

Artifact [d8a4c16cdd]

Artifact d8a4c16cddf1f98a4e407a599887f67efaaad15ed8821ff9f98bd21e23c4f2b1:


package runner

import (
	"context"
	"errors"
	"io"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"testing"
	"time"
)

// flakyCheckout fails its first n calls, the way a clone fails while a
// deploy restarts the forge, then checks out for real.
type flakyCheckout struct {
	mu    sync.Mutex
	fails int
	calls int
	inner fakeCheckout
}

func (f *flakyCheckout) Checkout(ctx context.Context, dir, checkin string) error {
	f.mu.Lock()
	f.calls++
	failing := f.calls <= f.fails
	f.mu.Unlock()
	if failing {
		return errors.New("connection refused")
	}
	return f.inner.Checkout(ctx, dir, checkin)
}

// THE CHECKOUT IS RETRIED IN PLACE; THE COMMAND NEVER IS.
//
// Getting the tree retries inside the task; a command that fails is
// reported on its first run. Why: ci/quarry.json's _comment.
func TestTheCheckoutRetriesAndTheCommandRunsOnce(t *testing.T) {
	ran := filepath.Join(t.TempDir(), "ran")
	manifest := func(exit string) string {
		return `{"tasks":{"t":["sh","-c","echo x >> ` + ran + `; exit ` + exit + `"]}}`
	}
	runs := func() int {
		b, _ := os.ReadFile(ran)
		return strings.Count(string(b), "x")
	}
	newRunner := func(co Checkout) *Runner {
		r, err := New(Config{
			Bridge: "http://127.0.0.1:1", Token: "WT", Tasks: []string{"t"},
			WorkDir: t.TempDir(), Checkout: co, Logf: t.Logf,
			CheckoutRetry: []time.Duration{time.Millisecond, time.Millisecond, time.Millisecond},
		})
		if err != nil {
			t.Fatal(err)
		}
		return r
	}
	ctx := context.Background()

	co := &flakyCheckout{fails: 2, inner: fakeCheckout{map[string]string{"c1": manifest("0")}}}
	if err := newRunner(co).execute(ctx, "t", "c1", "", io.Discard, io.Discard); err != nil {
		t.Errorf("FAIL: a checkout that failed twice then worked failed the task: %v", err)
	}
	if co.calls != 3 || runs() != 1 {
		t.Errorf("FAIL: checkout calls = %d, command runs = %d; want 3 and 1", co.calls, runs())
	}

	co = &flakyCheckout{fails: 99, inner: fakeCheckout{map[string]string{"c1": manifest("0")}}}
	err := newRunner(co).execute(ctx, "t", "c1", "", io.Discard, io.Discard)
	if err == nil || !strings.Contains(err.Error(), "checkout") || co.calls != 4 {
		t.Errorf("FAIL: a checkout that never works: err %v after %d calls; want a checkout error after 4", err, co.calls)
	}

	co = &flakyCheckout{inner: fakeCheckout{map[string]string{"c2": manifest("3")}}}
	if err := newRunner(co).execute(ctx, "t", "c2", "", io.Discard, io.Discard); err == nil {
		t.Error("FAIL: a failing command passed")
	}
	if co.calls != 1 || runs() != 2 {
		t.Errorf("FAIL: a failing command: checkout calls = %d, total command runs = %d; want 1 and 2", co.calls, runs())
	}
	t.Log("ok: the checkout retries in place; a failing command runs once")
}

// A stop during a checkout retry's wait ends the task at once, as a
// checkout failure, without running the command.
func TestAStopDuringACheckoutRetryWaitEndsTheTask(t *testing.T) {
	ran := filepath.Join(t.TempDir(), "ran")
	co := &flakyCheckout{fails: 99, inner: fakeCheckout{map[string]string{
		"c1": `{"tasks":{"t":["sh","-c","echo x >> ` + ran + `"]}}`,
	}}}
	r, err := New(Config{
		Bridge: "http://127.0.0.1:1", Token: "WT", Tasks: []string{"t"},
		WorkDir: t.TempDir(), Checkout: co, Logf: t.Logf,
		CheckoutRetry: []time.Duration{time.Hour},
	})
	if err != nil {
		t.Fatal(err)
	}
	ctx, cancel := context.WithCancel(context.Background())
	time.AfterFunc(50*time.Millisecond, cancel)
	done := make(chan error, 1)
	go func() { done <- r.execute(ctx, "t", "c1", "", io.Discard, io.Discard) }()
	select {
	case err := <-done:
		if err == nil || !strings.Contains(err.Error(), "checkout") {
			t.Errorf("FAIL: a stop mid-wait returned %v; want a checkout error", err)
		}
	case <-time.After(10 * time.Second):
		t.Fatal("FAIL: a stop mid-wait did not end the task")
	}
	if _, err := os.Stat(ran); !os.IsNotExist(err) {
		t.Error("FAIL: the command ran after the stop")
	}
	if co.calls != 1 {
		t.Errorf("FAIL: checkout calls = %d, want 1", co.calls)
	}
	t.Log("ok: a stop during the wait ends the task as a checkout failure")
}