| new file mode 100644 |
| index 000000000..89bc87cd6 |
| --- /dev/null |
| +++ b/approved_run_copy_test.go |
| @@ -0,0 +1,189 @@ |
| +// approved_run_copy_test.go |
| +package main |
| + |
| +import ( |
| + "fmt" |
| + "os" |
| + "path/filepath" |
| + "strconv" |
| + "strings" |
| + "testing" |
| +) |
| + |
| +// The copy gate of the carlos run series (run Plan 1 Task 1). The deck is a |
| +// markdown table in the moves record's format, read with the moves gate's |
| +// parser and checked with its helpers, so "applied" means the same thing in |
| +// both gates: |
| +// |
| +// - TestRunCopyRecordIsComplete: the deck names exactly RUN-C1 to RUN-C83; |
| +// - TestRunBuiltCopyIsApproved: an id in runCopyBuilt is an approved row |
| +// (a string reaches code only after Paul has approved it); |
| +// - TestRunBuiltCopyIsApplied: a built row's text is in the file it names; |
| +// - TestRunCopyHasNoEmDash. |
| +const runCopyRecordPath = "docs/superpowers/plans/2026-10-08-carlos-run-copy.md" |
| + |
| +// runCopyIDs is the deck's inventory: RUN-C1 to RUN-C83, pinned. |
| +var runCopyIDs = func() []string { |
| + ids := make([]string, 0, 83) |
| + for i := 1; i <= 83; i++ { |
| + ids = append(ids, "RUN-C"+strconv.Itoa(i)) |
| + } |
| + return ids |
| +}() |
| + |
| +// runCopyBuilt is every deck id whose text the code or docs now carry. The |
| +// task that writes a row's text adds its id here in the same commit. |
| +var runCopyBuilt = []string{} |
| + |
| +func mustParseRunRecord(t *testing.T) []copyRow { |
| + t.Helper() |
| + rows, err := parseMovesCopyRecord(runCopyRecordPath) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + if len(rows) == 0 { |
| + t.Fatalf("%s has no rows", runCopyRecordPath) |
| + } |
| + for _, r := range rows { |
| + if !strings.HasPrefix(r.ID, "RUN-C") { |
| + t.Fatalf("%s: %s is not a RUN-C id", runCopyRecordPath, r.ID) |
| + } |
| + } |
| + return rows |
| +} |
| + |
| +// checkRunBuilt: every built id is named once, names a row, and that row is |
| +// approved. |
| +func checkRunBuilt(rows []copyRow, built []string) []string { |
| + byID := map[string]copyRow{} |
| + for _, r := range rows { |
| + byID[r.ID] = r |
| + } |
| + var out []string |
| + seen := map[string]bool{} |
| + for _, id := range built { |
| + if seen[id] { |
| + out = append(out, fmt.Sprintf("runCopyBuilt names %s twice", id)) |
| + } |
| + seen[id] = true |
| + r, ok := byID[id] |
| + switch { |
| + case !ok: |
| + out = append(out, fmt.Sprintf("runCopyBuilt names %s, which the deck has no row for", id)) |
| + case r.Status != "approved": |
| + out = append(out, fmt.Sprintf("%s is built but %s: a string reaches code only after Paul approves it", id, r.Status)) |
| + } |
| + } |
| + return out |
| +} |
| + |
| +// builtRows is the rows runCopyBuilt names, in deck order. |
| +func builtRows(rows []copyRow, built []string) []copyRow { |
| + want := map[string]bool{} |
| + for _, id := range built { |
| + want[id] = true |
| + } |
| + var out []copyRow |
| + for _, r := range rows { |
| + if want[r.ID] { |
| + out = append(out, r) |
| + } |
| + } |
| + return out |
| +} |
| + |
| +func TestRunCopyRecordIsComplete(t *testing.T) { |
| + reportAll(t, checkMovesInventory(mustParseRunRecord(t), runCopyIDs)) |
| +} |
| + |
| +func TestRunCopyHasNoEmDash(t *testing.T) { |
| + reportAll(t, checkMovesNoEmDash(mustParseRunRecord(t))) |
| +} |
| + |
| +func TestRunBuiltCopyIsApproved(t *testing.T) { |
| + reportAll(t, checkRunBuilt(mustParseRunRecord(t), runCopyBuilt)) |
| +} |
| + |
| +// runFragment reports whether a row's text is a piece of a larger literal |
| +// (its file cell says "a fragment"): the top-level usage text, which no |
| +// whole-expression match can find. |
| +func runFragment(r copyRow) bool { return strings.Contains(r.File, "(a fragment") } |
| + |
| +// checkRunApplied is checkMovesApplied, plus the fragment rows: those must |
| +// sit inside some string literal of their file (goLiteralMatches, |
| +// approved_copy_test.go), never only in a comment. |
| +func checkRunApplied(rows []copyRow, root string) []string { |
| + var whole []copyRow |
| + var out []string |
| + for _, r := range rows { |
| + if !runFragment(r) { |
| + whole = append(whole, r) |
| + continue |
| + } |
| + if r.Status != "approved" { |
| + continue |
| + } |
| + body, err := os.ReadFile(filepath.Join(root, r.Path())) |
| + if err != nil { |
| + out = append(out, fmt.Sprintf("%s: %v", r.ID, err)) |
| + continue |
| + } |
| + if !goLiteralMatches(body, func(v string) bool { return strings.Contains(v, r.Text) }) { |
| + out = append(out, fmt.Sprintf("%s: %s has no string literal holding the approved text %q", r.ID, r.Path(), r.Text)) |
| + } |
| + } |
| + return append(checkMovesApplied(whole, root), out...) |
| +} |
| + |
| +func TestRunBuiltCopyIsApplied(t *testing.T) { |
| + reportAll(t, checkRunApplied(builtRows(mustParseRunRecord(t), runCopyBuilt), ".")) |
| +} |
| + |
| +// Each fixture case is a state the gate must refuse, for the right reason. |
| +func TestRunCopyGateRefusesTheNegativeFixtures(t *testing.T) { |
| + dir := filepath.Join("testdata", "run-copy") |
| + rows, err := parseMovesCopyRecord(filepath.Join(dir, "record.md")) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + t.Run("a built row still pending", func(t *testing.T) { |
| + got := checkRunBuilt(rows, []string{"RUN-C2"}) |
| + if len(got) != 1 || !strings.Contains(got[0], "RUN-C2 is built but pending") { |
| + t.Errorf("problems = %q", got) |
| + } |
| + }) |
| + t.Run("a built id with no row", func(t *testing.T) { |
| + got := checkRunBuilt(rows, []string{"RUN-C9"}) |
| + if len(got) != 1 || !strings.Contains(got[0], "no row for") { |
| + t.Errorf("problems = %q", got) |
| + } |
| + }) |
| + t.Run("a built id named twice", func(t *testing.T) { |
| + got := checkRunBuilt(rows, []string{"RUN-C1", "RUN-C1"}) |
| + if len(got) != 1 || !strings.Contains(got[0], "twice") { |
| + t.Errorf("problems = %q", got) |
| + } |
| + }) |
| + t.Run("an approved row whose text is only in a comment", func(t *testing.T) { |
| + got := checkMovesApplied(builtRows(rows, []string{"RUN-C3"}), dir) |
| + if len(got) != 1 || !strings.Contains(got[0], "RUN-C3") { |
| + t.Errorf("problems = %q", got) |
| + } |
| + }) |
| + t.Run("an approved row that is applied", func(t *testing.T) { |
| + if got := checkRunApplied(builtRows(rows, []string{"RUN-C1"}), dir); len(got) != 0 { |
| + t.Errorf("problems = %q", got) |
| + } |
| + }) |
| + t.Run("a fragment inside a larger literal", func(t *testing.T) { |
| + if got := checkRunApplied(builtRows(rows, []string{"RUN-C4"}), dir); len(got) != 0 { |
| + t.Errorf("problems = %q", got) |
| + } |
| + }) |
| + t.Run("a fragment only in a comment", func(t *testing.T) { |
| + got := checkRunApplied(builtRows(rows, []string{"RUN-C5"}), dir) |
| + if len(got) != 1 || !strings.Contains(got[0], "RUN-C5") { |
| + t.Errorf("problems = %q", got) |
| + } |
| + }) |
| +} |