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(), "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")
}