Skip to content
quarry Artifact [c8c6e96ef0]
Login

Artifact [c8c6e96ef0]

Artifact c8c6e96ef0ad52e0233a2c8a386f1867c50c376218b315aca4b99ba7f8f4185e:


package web

import (
	"bytes"
	"encoding/json"
	"strings"
	"testing"
)

// A stack offers to land only what will land. The walk accepts each
// rung in turn and an accept refuses a rung whose required checks are
// not green, so a Land stack button over a waiting base landed nothing
// and said the rung would not merge. While a rung waits, the stack says
// which and for what, its green verdict is not shown, and the control
// lands the rungs below it or is not drawn.
func TestAStackOffersToLandOnlyWhatWillLand(t *testing.T) {
	ids := []string{strings.Repeat("a1", 32), strings.Repeat("b2", 32), strings.Repeat("c3", 32)}
	page := func(rungs, here int, stack string) string {
		t.Helper()
		var sv stackView
		if err := json.Unmarshal([]byte(stack), &sv); err != nil {
			t.Fatal(err)
		}
		sv.Segments = sv.Segments[:rungs]
		for i := range sv.Segments {
			sv.Segments[i].ID, sv.Segments[i].Position, sv.Segments[i].State = ids[i], i+1, "open"
			sv.Segments[i].Branch = "b" + string(rune('1'+i))
		}
		sv.Top, sv.Base, sv.CI = ids[rungs-1], ids[0], "ci-pass"
		d := landingDetail{
			landingRow: landingRow{ID: ids[here-1], Branch: sv.Segments[here-1].Branch, Target: "trunk", State: "open"},
			CanAccept:  true, Mergeable: "clean", Stack: sv, Position: here,
		}
		if here > 1 {
			d.Mergeable = "stacked"
		}
		var buf bytes.Buffer
		if err := noHooks.template(landingPage).Execute(&buf, pageData{Title: "Landing", Repo: "quarry",
			Section: "landings", Landing: &d}); err != nil {
			t.Fatal(err)
		}
		return buf.String()
	}
	segs := `"segments":[{"preflight":"clean"},{"preflight":"clean"},{"preflight":"clean"}]`

	// The base waits for its checks: nothing lands from the top.
	top := page(2, 2, `{"preflight":"lands","landTo":"","landCount":0,"waitsAt":1,`+
		`"segments":[{"preflight":"clean","waiting":["quarry","race"]},{"preflight":"clean"}]}`)
	if strings.Contains(top, `/land"`) {
		t.Error("FAIL: a stack whose base waits still offers to land")
	}
	if !strings.Contains(top, `data-hook="stack-blocked"`) || strings.Count(top, "waits for checks") != 1 {
		t.Error("FAIL: the stack does not say once which rung waits")
	}
	if !strings.Contains(top, `data-hook="rung-waiting"`) || !strings.Contains(top, "<code>race</code>") {
		t.Error("FAIL: the waiting rung does not name its checks")
	}
	if m := top[strings.Index(top, `data-hook="strata-map"`):]; strings.Contains(m[:strings.Index(m, "</p>")], "ci-pass") {
		t.Error("FAIL: a waiting stack still shows a green verdict")
	}

	// Every rung green: the top lands up to itself, the whole stack.
	green := page(2, 2, `{"preflight":"lands","landTo":"`+ids[1]+`","landCount":2,`+segs+`}`)
	if !strings.Contains(green, `data-hook="land-stack" data-count="2" data-accept="stacked" formaction="/r/quarry/landing/`+ids[1][:12]+`/land"`) {
		t.Error("FAIL: a green stack does not offer to land from its top")
	}

	// The third rung waits: the base lands the two below it.
	part := page(3, 1, `{"preflight":"lands","landTo":"`+ids[1]+`","landCount":2,"waitsAt":3,`+segs+`}`)
	if !strings.Contains(part, `data-hook="land-up-to" data-count="2" formaction="/r/quarry/landing/`+ids[1][:12]+`/land"`) {
		t.Error("FAIL: the base does not offer to land the rungs below the waiting one")
	}
	if !strings.Contains(part, `data-hook="strata-waits" data-at="3"`) {
		t.Error("FAIL: a stack landing in part does not say which rung waits")
	}
	// From the second rung of the same stack: up to itself, two rungs.
	mid := page(3, 2, `{"preflight":"lands","landTo":"`+ids[2]+`","landCount":3,`+segs+`}`)
	if !strings.Contains(mid, `data-count="2" data-accept="stacked" formaction="/r/quarry/landing/`+ids[1][:12]+`/land"`) {
		t.Error("FAIL: a middle rung lands past itself")
	}
}

// A lower rung waiting on its checks offers to land the whole stack
// when green, by arming the top rung, beside arming this rung alone;
// and the sentence counts what each lands rather than always saying
// "the whole stack".
func TestALowerRungWaitingOffersToLandTheStack(t *testing.T) {
	ids := []string{strings.Repeat("a1", 32), strings.Repeat("b2", 32)}
	var sv stackView
	if err := json.Unmarshal([]byte(`{"preflight":"lands","landTo":"","landCount":0,"waitsAt":1,`+
		`"segments":[{"preflight":"clean","waiting":["quarry"]},{"preflight":"clean"}]}`), &sv); err != nil {
		t.Fatal(err)
	}
	for i := range sv.Segments {
		sv.Segments[i].ID, sv.Segments[i].Position, sv.Segments[i].State = ids[i], i+1, "open"
		sv.Segments[i].Branch = "b" + string(rune('1'+i))
	}
	sv.Top, sv.Base = ids[1], ids[0]
	d := landingDetail{
		landingRow: landingRow{ID: ids[0], Branch: "b1", Target: "trunk", State: "open"},
		CanAccept:  true, Mergeable: "clean", Stack: sv, Position: 1, Unmet: []string{"quarry"},
	}
	var buf bytes.Buffer
	if err := noHooks.template(landingPage).Execute(&buf, pageData{Title: "Landing", Repo: "quarry",
		Section: "landings", Landing: &d}); err != nil {
		t.Fatal(err)
	}
	page := buf.String()
	if !strings.Contains(page, `data-hook="stack-auto"`) || !strings.Contains(page, `action="/r/quarry/landing/`+ids[1][:12]+`/auto"`) {
		t.Errorf("FAIL: the waiting base does not offer to arm the stack's top")
	}
	if !strings.Contains(page, `formaction="/r/quarry/landing/`+ids[0][:12]+`/auto"`) {
		t.Errorf("FAIL: arming this rung alone is gone")
	}
	t.Log("ok: auto the stack")
}

// jva, ^2648: a lower rung shows the stack above it is armed, and the
// stack-wide arm is not offered while the base is behind its target.
func TestALowerRungShowsTheArmedStackAndNoArmWhileBehind(t *testing.T) {
	ids := []string{strings.Repeat("a1", 32), strings.Repeat("b2", 32)}
	render := func(behind int, armed string) string {
		var sv stackView
		if err := json.Unmarshal([]byte(`{"preflight":"lands","landTo":"","landCount":0,"waitsAt":1,`+
			`"segments":[{"preflight":"clean","waiting":["quarry"]},{"preflight":"clean"}]}`), &sv); err != nil {
			t.Fatal(err)
		}
		for i := range sv.Segments {
			sv.Segments[i].ID, sv.Segments[i].Position, sv.Segments[i].State = ids[i], i+1, "open"
			sv.Segments[i].Branch = "b" + string(rune('1'+i))
		}
		sv.Segments[0].Behind = behind
		sv.Top, sv.Base = ids[1], ids[0]
		d := landingDetail{
			landingRow: landingRow{ID: ids[0], Branch: "b1", Target: "trunk", State: "open"},
			CanAccept:  true, Mergeable: "clean", Stack: sv, Position: 1, Unmet: []string{"quarry"},
			StackArmedBy: armed, StackArmedAt: 2,
		}
		var buf bytes.Buffer
		if err := noHooks.template(landingPage).Execute(&buf, pageData{Title: "Landing", Repo: "quarry",
			Section: "landings", Landing: &d}); err != nil {
			t.Fatal(err)
		}
		return buf.String()
	}
	if p := render(0, "dmestas"); !strings.Contains(p, `data-hook="stack-armed"`) {
		t.Errorf("FAIL: a lower rung under an armed top does not say so")
	}
	if p := render(2, ""); strings.Contains(p, `data-hook="stack-auto"`) {
		t.Errorf("FAIL: the stack-wide arm is offered while the base is behind")
	}
	t.Log("ok: armed shown, no arm while behind")
}

// jva, ^2659/^2660: an armed landing whose strip draws no Cancel (a
// stack's top rung) shows the arm with a Cancel, and the reason it waits.
func TestAnArmedTopRungCanBeCancelledAndSaysWhy(t *testing.T) {
	ids := []string{strings.Repeat("a1", 32), strings.Repeat("b2", 32)}
	var sv stackView
	if err := json.Unmarshal([]byte(`{"preflight":"lands","landTo":"","landCount":0,"waitsAt":1,`+
		`"segments":[{"preflight":"clean","waiting":["quarry"]},{"preflight":"clean"}]}`), &sv); err != nil {
		t.Fatal(err)
	}
	for i := range sv.Segments {
		sv.Segments[i].ID, sv.Segments[i].Position, sv.Segments[i].State = ids[i], i+1, "open"
		sv.Segments[i].Branch = "b" + string(rune('1'+i))
	}
	sv.Top, sv.Base = ids[1], ids[0]
	d := landingDetail{
		landingRow: landingRow{ID: ids[1], Branch: "b2", Target: "b1", State: "open"},
		CanAccept:  true, Mergeable: "stacked", Stack: sv, Position: 2,
		AutoBy: "dmestas", AutoWaiting: "failed: quarry. Re-run it from the landing",
	}
	var buf bytes.Buffer
	if err := noHooks.template(landingPage).Execute(&buf, pageData{Title: "Landing", Repo: "quarry",
		Section: "landings", Landing: &d}); err != nil {
		t.Fatal(err)
	}
	p := buf.String()
	if !strings.Contains(p, `data-hook="auto-cancel-row"`) || !strings.Contains(p, "failed: quarry") {
		t.Errorf("FAIL: an armed top rung offers no Cancel or does not say it waits on a failed check")
	}
	t.Log("ok: cancel and reason on the armed top rung")
}

// An armed stack does not offer the press again: it would arm what is
// already armed, and seemed to do nothing.
func TestAnArmedStackDoesNotOfferToLandAgain(t *testing.T) {
	ids := []string{strings.Repeat("a1", 32), strings.Repeat("b2", 32)}
	var sv stackView
	if err := json.Unmarshal([]byte(`{"preflight":"lands","landTo":"`+ids[1]+`","landCount":2,"waitsAt":1,`+
		`"segments":[{"preflight":"clean","waiting":["quarry"]},{"preflight":"clean"}]}`), &sv); err != nil {
		t.Fatal(err)
	}
	sv.Segments[0].ID, sv.Segments[0].Position, sv.Segments[0].State, sv.Segments[0].Branch = ids[0], 1, "open", "a"
	sv.Segments[1].ID, sv.Segments[1].Position, sv.Segments[1].State, sv.Segments[1].Branch = ids[1], 2, "open", "b"
	page := func(here int, armedTop, armedBelow bool) string {
		d := landingDetail{
			landingRow: landingRow{ID: ids[here-1], Branch: sv.Segments[here-1].Branch, Target: "trunk", State: "open"},
			CanAccept:  true, Mergeable: "clean", Stack: sv, Position: here,
		}
		if here > 1 {
			d.Mergeable = "stacked"
		}
		if armedTop {
			d.AutoBy = "dmestas"
		}
		if armedBelow {
			d.StackArmedBy, d.StackArmedAt, d.StackArmedID = "dmestas", 2, ids[1]
			d.Unmet = []string{"quarry"}
		}
		var buf bytes.Buffer
		if err := noHooks.template(landingPage).Execute(&buf, pageData{Title: "Landing", Repo: "quarry",
			Section: "landings", Landing: &d}); err != nil {
			t.Fatal(err)
		}
		return buf.String()
	}
	if !strings.Contains(page(2, false, false), `data-hook="land-stack"`) {
		t.Error("FAIL: an unarmed waiting stack no longer offers to arm by landing")
	}
	if strings.Contains(page(2, true, false), `data-hook="land-stack"`) {
		t.Error("FAIL: the armed top still offers Land stack")
	}
	if strings.Contains(page(1, false, true), `data-hook="land-stack"`) {
		t.Error("FAIL: a rung armed with the stack still offers Land stack")
	}
	// Armed from above, a lower rung offers no arm of its own, only the
	// way to take the stack's arm back (jva, ^4016).
	below := page(1, false, true)
	if strings.Contains(below, `data-hook="stack-auto"`) || strings.Contains(below, `name="on" value="true"`) {
		i := strings.Index(below, `value="true"`)
		t.Errorf("FAIL: a rung armed with the stack still offers to arm: %q", below[max(0, i-400):i+40])
	}
	if !strings.Contains(below, `data-hook="stack-auto-cancel"`) || !strings.Contains(below, `/landing/`+ids[1][:12]+`/auto?back=`+ids[0][:12]) {
		t.Error("FAIL: a rung armed with the stack offers no cancel of the stack's arm")
	}
	t.Log("ok")
}