Skip to content
quarry Artifact [e071d8cd91]
Login

Artifact [e071d8cd91]

Artifact e071d8cd91083b3b657fa67b8b9d4bfd3db4ebd8faa0616882a6dc97356dc442:


package runner

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"net/http"
	"net/http/httptest"
	"path/filepath"
	"strings"
	"sync/atomic"
	"testing"
	"time"
)

// A PAIR THAT DOES NOT MERGE FAILS ITS STEP ONCE, NAMING THE FILES.
//
// The forge answers a conflicting pair's archive with a merge_conflict
// refusal. That is the verdict, so the runner does not retry it the way
// it retries a forge that is restarting, and the conflicting paths are
// the step's log.
func TestAConflictingPairFailsOnceWithItsPaths(t *testing.T) {
	var asked atomic.Int32
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		asked.Add(1)
		if !strings.Contains(req.URL.Path, "/archive/pair:") {
			t.Errorf("fetched %s, want the pair's archive", req.URL.Path)
		}
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusBadRequest)
		json.NewEncoder(w).Encode(map[string]string{"code": "merge_conflict",
			"error": "the merge does not apply: conflict in g.txt, k/h.txt", "paths": "g.txt\nk/h.txt"})
	}))
	defer srv.Close()
	r, err := New(Config{
		Bridge: "http://127.0.0.1:1", Token: "WT", Tasks: []string{"t"},
		WorkDir: t.TempDir(), Checkout: ArchiveCheckout{URL: srv.URL + "/r/demo"}, Logf: t.Logf,
		CheckoutRetry: []time.Duration{time.Millisecond, time.Millisecond},
	})
	if err != nil {
		t.Fatal(err)
	}
	var logs bytes.Buffer
	err = r.execute(context.Background(), "task", "t", "pair:"+strings.Repeat("a", 64)+":"+strings.Repeat("b", 64), "", &logs, &logs)
	var conflict *MergeConflict
	if !errors.As(err, &conflict) {
		t.Fatalf("FAIL: a conflicting pair failed with %v, want a MergeConflict", err)
	}
	if asked.Load() != 1 {
		t.Errorf("FAIL: the pair was fetched %d times, want once: a conflict is not retried", asked.Load())
	}
	for _, p := range []string{"g.txt", "k/h.txt"} {
		if !strings.Contains(logs.String(), p) {
			t.Errorf("FAIL: the step's log does not name %s:\n%s", p, logs.String())
		}
	}
	t.Log("ok: one fetch, a MergeConflict, and the paths in the log")
}

// zit mode has no check-in to update to for a pair, so it takes the
// forge's merged tree like archive mode does.
func TestZitModeFetchesAPairAsAnArchive(t *testing.T) {
	var path atomic.Value
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		path.Store(req.URL.Path)
		http.Error(w, "gone", http.StatusNotFound)
	}))
	defer srv.Close()
	pair := "pair:" + strings.Repeat("a", 64) + ":" + strings.Repeat("b", 64)
	_ = ZitCheckout{Zit: "/nonexistent/zit", URL: srv.URL + "/r/demo"}.Checkout(context.Background(), filepath.Join(t.TempDir(), "ws"), pair)
	if got, _ := path.Load().(string); !strings.HasSuffix(got, "/archive/"+pair) {
		t.Fatalf("FAIL: zit mode fetched %q for a pair, want the forge's archive of it", got)
	}
	t.Log("ok: zit mode fetches a pair from the forge's archive")
}