package runner
import (
"encoding/json"
"os"
"path/filepath"
"regexp"
"slices"
"testing"
"github.com/danmestas/dagnats/dag"
)
// quarry's own CI: every step in its pipeline names a task the runner
// manifest can run (a step no manifest entry serves sits queued until
// it times out), and one of those steps is the race detector, run as
// the same make target a local `make check` runs.
func TestQuarrysPipelineRunsTheRaceTargetAsItsOwnStep(t *testing.T) {
root := filepath.Join("..", "..")
b, err := os.ReadFile(filepath.Join(root, ManifestPath))
if err != nil {
t.Fatal(err)
}
m, err := ParseManifest(b)
if err != nil {
t.Fatal(err)
}
b, err = os.ReadFile(filepath.Join(root, "ci", "quarry.json"))
if err != nil {
t.Fatal(err)
}
var pipeline struct {
Steps []struct {
ID string `json:"id"`
Task string `json:"task"`
Retry *struct {
MaxAttempts *int `json:"max_attempts"`
} `json:"retry"`
} `json:"steps"`
}
if err := json.Unmarshal(b, &pipeline); err != nil {
t.Fatal(err)
}
// The engine's own reading: the file is a valid workflow, and after
// a first failed attempt (Attempts == 1) no step is scheduled again
// (dagnats' internal/engine/retry_handlers.go retries while Attempts
// <= MaxAttempts). Why every step runs once: ci/quarry.json's _comment.
var def dag.WorkflowDef
if err := json.Unmarshal(b, &def); err != nil {
t.Fatal(err)
}
if err := dag.Validate(def); err != nil {
t.Fatalf("FAIL: the engine would refuse ci/quarry.json: %v", err)
}
for _, st := range def.Steps {
if p := dag.ResolveRetryPolicy(def, st); p != nil && 1 <= p.MaxAttempts {
t.Errorf("FAIL: step %q re-runs a failed attempt (max_attempts %d)", st.ID, p.MaxAttempts)
}
}
raceStep := ""
for _, s := range pipeline.Steps {
if s.Retry == nil || s.Retry.MaxAttempts == nil {
t.Errorf("FAIL: step %q leaves its retry policy implicit", s.ID)
}
task, ok := m.Tasks[s.Task]
if !ok {
t.Errorf("FAIL: step %q runs task %q, which %s does not name", s.ID, s.Task, ManifestPath)
continue
}
if slices.Equal(task.Argv, []string{"make", "race"}) {
raceStep = s.ID
}
}
if raceStep == "" {
t.Fatal("FAIL: no pipeline step runs `make race`")
}
mk, err := os.ReadFile(filepath.Join(root, "Makefile"))
if err != nil {
t.Fatal(err)
}
if !regexp.MustCompile(`(?m)^check:.*\brace\b`).Match(mk) {
t.Error("FAIL: make check does not run the race target CI runs")
}
if !regexp.MustCompile(`(?m)^race:\n(\t.*\n)*\tgo test -race `).Match(mk) {
t.Error("FAIL: the Makefile has no race target running go test -race")
}
t.Logf("ok: step %q runs make race, and make check runs it too", raceStep)
}