| 1 | // approved_run_copy_test.go |
| 2 | package main |
| 3 | |
| 4 | import ( |
| 5 | "fmt" |
| 6 | "os" |
| 7 | "path/filepath" |
| 8 | "strconv" |
| 9 | "strings" |
| 10 | "testing" |
| 11 | ) |
| 12 | |
| 13 | // The copy gate of the carlos run series (run Plan 1 Task 1). The deck is a |
| 14 | // markdown table in the moves record's format, read with the moves gate's |
| 15 | // parser and checked with its helpers, so "applied" means the same thing in |
| 16 | // both gates: |
| 17 | // |
| 18 | // - TestRunCopyRecordIsComplete: the deck names exactly RUN-C1 to RUN-C87; |
| 19 | // - TestRunBuiltCopyIsApproved: an id in runCopyBuilt is an approved row |
| 20 | // (a string reaches code only after Paul has approved it); |
| 21 | // - TestRunBuiltCopyIsApplied: a built row's text is in the file it names; |
| 22 | // - TestRunCopyHasNoEmDash. |
| 23 | const runCopyRecordPath = "docs/superpowers/plans/2026-10-08-carlos-run-copy.md" |
| 24 | |
| 25 | // runCopyIDs is the deck's inventory: RUN-C1 to RUN-C87, pinned. |
| 26 | var runCopyIDs = func() []string { |
| 27 | ids := make([]string, 0, 87) |
| 28 | for i := 1; i <= 87; i++ { |
| 29 | ids = append(ids, "RUN-C"+strconv.Itoa(i)) |
| 30 | } |
| 31 | return ids |
| 32 | }() |
| 33 | |
| 34 | // runCopyBuilt is every deck id whose text the code or docs now carry. The |
| 35 | // task that writes a row's text adds its id here in the same commit. |
| 36 | var runCopyBuilt = []string{"RUN-C1", "RUN-C2", "RUN-C3", "RUN-C4", "RUN-C5", "RUN-C6", "RUN-C7", "RUN-C8", "RUN-C9", "RUN-C10", "RUN-C11", "RUN-C12", "RUN-C13", "RUN-C14", "RUN-C15", "RUN-C16", "RUN-C17", "RUN-C18", "RUN-C19", "RUN-C20", "RUN-C21", "RUN-C22", "RUN-C23", "RUN-C24", "RUN-C25", "RUN-C26", "RUN-C27", "RUN-C28", "RUN-C29", "RUN-C30", "RUN-C31", "RUN-C32", "RUN-C33", "RUN-C34", "RUN-C35", "RUN-C36", "RUN-C37", "RUN-C38", "RUN-C39", "RUN-C40", "RUN-C41", "RUN-C42", "RUN-C43", "RUN-C44", "RUN-C45", "RUN-C46", "RUN-C47", "RUN-C48", "RUN-C49", "RUN-C50", "RUN-C51", "RUN-C52", "RUN-C53", "RUN-C54", "RUN-C55", "RUN-C56", "RUN-C57", "RUN-C58", "RUN-C59", "RUN-C60", "RUN-C61", "RUN-C62", "RUN-C63", "RUN-C64", "RUN-C65", "RUN-C66", "RUN-C67", "RUN-C68", "RUN-C69", "RUN-C70", "RUN-C71", "RUN-C72", "RUN-C73", "RUN-C74", "RUN-C75", "RUN-C76", "RUN-C77", "RUN-C78", "RUN-C79", "RUN-C80", "RUN-C81", "RUN-C82", "RUN-C83", "RUN-C84", "RUN-C85", "RUN-C86", "RUN-C87"} |
| 37 | |
| 38 | func mustParseRunRecord(t *testing.T) []copyRow { |
| 39 | t.Helper() |
| 40 | rows, err := parseMovesCopyRecord(runCopyRecordPath) |
| 41 | if err != nil { |
| 42 | t.Fatal(err) |
| 43 | } |
| 44 | if len(rows) == 0 { |
| 45 | t.Fatalf("%s has no rows", runCopyRecordPath) |
| 46 | } |
| 47 | for _, r := range rows { |
| 48 | if !strings.HasPrefix(r.ID, "RUN-C") { |
| 49 | t.Fatalf("%s: %s is not a RUN-C id", runCopyRecordPath, r.ID) |
| 50 | } |
| 51 | } |
| 52 | return rows |
| 53 | } |
| 54 | |
| 55 | // checkRunBuilt: every built id is named once, names a row, and that row is |
| 56 | // approved. |
| 57 | func checkRunBuilt(rows []copyRow, built []string) []string { |
| 58 | byID := map[string]copyRow{} |
| 59 | for _, r := range rows { |
| 60 | byID[r.ID] = r |
| 61 | } |
| 62 | var out []string |
| 63 | seen := map[string]bool{} |
| 64 | for _, id := range built { |
| 65 | if seen[id] { |
| 66 | out = append(out, fmt.Sprintf("runCopyBuilt names %s twice", id)) |
| 67 | } |
| 68 | seen[id] = true |
| 69 | r, ok := byID[id] |
| 70 | switch { |
| 71 | case !ok: |
| 72 | out = append(out, fmt.Sprintf("runCopyBuilt names %s, which the deck has no row for", id)) |
| 73 | case r.Status != "approved": |
| 74 | out = append(out, fmt.Sprintf("%s is built but %s: a string reaches code only after Paul approves it", id, r.Status)) |
| 75 | } |
| 76 | } |
| 77 | return out |
| 78 | } |
| 79 | |
| 80 | // builtRows is the rows runCopyBuilt names, in deck order. |
| 81 | func builtRows(rows []copyRow, built []string) []copyRow { |
| 82 | want := map[string]bool{} |
| 83 | for _, id := range built { |
| 84 | want[id] = true |
| 85 | } |
| 86 | var out []copyRow |
| 87 | for _, r := range rows { |
| 88 | if want[r.ID] { |
| 89 | out = append(out, r) |
| 90 | } |
| 91 | } |
| 92 | return out |
| 93 | } |
| 94 | |
| 95 | func TestRunCopyRecordIsComplete(t *testing.T) { |
| 96 | reportAll(t, checkMovesInventory(mustParseRunRecord(t), runCopyIDs)) |
| 97 | } |
| 98 | |
| 99 | func TestRunCopyHasNoEmDash(t *testing.T) { |
| 100 | reportAll(t, checkMovesNoEmDash(mustParseRunRecord(t))) |
| 101 | } |
| 102 | |
| 103 | func TestRunBuiltCopyIsApproved(t *testing.T) { |
| 104 | reportAll(t, checkRunBuilt(mustParseRunRecord(t), runCopyBuilt)) |
| 105 | } |
| 106 | |
| 107 | // runFragment reports whether a row's text is a piece of a larger literal |
| 108 | // (its file cell says "a fragment"): the top-level usage text, which no |
| 109 | // whole-expression match can find. |
| 110 | func runFragment(r copyRow) bool { return strings.Contains(r.File, "(a fragment") } |
| 111 | |
| 112 | // checkRunApplied is checkMovesApplied, plus the fragment rows: those must |
| 113 | // sit inside some string literal of their file (goLiteralMatches, |
| 114 | // approved_copy_test.go), never only in a comment. |
| 115 | func checkRunApplied(rows []copyRow, root string) []string { |
| 116 | var whole []copyRow |
| 117 | var out []string |
| 118 | for _, r := range rows { |
| 119 | if !runFragment(r) { |
| 120 | whole = append(whole, r) |
| 121 | continue |
| 122 | } |
| 123 | if r.Status != "approved" { |
| 124 | continue |
| 125 | } |
| 126 | body, err := os.ReadFile(filepath.Join(root, r.Path())) |
| 127 | if err != nil { |
| 128 | out = append(out, fmt.Sprintf("%s: %v", r.ID, err)) |
| 129 | continue |
| 130 | } |
| 131 | if !goLiteralMatches(body, func(v string) bool { return strings.Contains(v, r.Text) }) { |
| 132 | out = append(out, fmt.Sprintf("%s: %s has no string literal holding the approved text %q", r.ID, r.Path(), r.Text)) |
| 133 | } |
| 134 | } |
| 135 | return append(checkMovesApplied(whole, root), out...) |
| 136 | } |
| 137 | |
| 138 | func TestRunBuiltCopyIsApplied(t *testing.T) { |
| 139 | reportAll(t, checkRunApplied(builtRows(mustParseRunRecord(t), runCopyBuilt), ".")) |
| 140 | } |
| 141 | |
| 142 | // Each fixture case is a state the gate must refuse, for the right reason. |
| 143 | func TestRunCopyGateRefusesTheNegativeFixtures(t *testing.T) { |
| 144 | dir := filepath.Join("testdata", "run-copy") |
| 145 | rows, err := parseMovesCopyRecord(filepath.Join(dir, "record.md")) |
| 146 | if err != nil { |
| 147 | t.Fatal(err) |
| 148 | } |
| 149 | t.Run("a built row still pending", func(t *testing.T) { |
| 150 | got := checkRunBuilt(rows, []string{"RUN-C2"}) |
| 151 | if len(got) != 1 || !strings.Contains(got[0], "RUN-C2 is built but pending") { |
| 152 | t.Errorf("problems = %q", got) |
| 153 | } |
| 154 | }) |
| 155 | t.Run("a built id with no row", func(t *testing.T) { |
| 156 | got := checkRunBuilt(rows, []string{"RUN-C9"}) |
| 157 | if len(got) != 1 || !strings.Contains(got[0], "no row for") { |
| 158 | t.Errorf("problems = %q", got) |
| 159 | } |
| 160 | }) |
| 161 | t.Run("a built id named twice", func(t *testing.T) { |
| 162 | got := checkRunBuilt(rows, []string{"RUN-C1", "RUN-C1"}) |
| 163 | if len(got) != 1 || !strings.Contains(got[0], "twice") { |
| 164 | t.Errorf("problems = %q", got) |
| 165 | } |
| 166 | }) |
| 167 | t.Run("an approved row whose text is only in a comment", func(t *testing.T) { |
| 168 | got := checkMovesApplied(builtRows(rows, []string{"RUN-C3"}), dir) |
| 169 | if len(got) != 1 || !strings.Contains(got[0], "RUN-C3") { |
| 170 | t.Errorf("problems = %q", got) |
| 171 | } |
| 172 | }) |
| 173 | t.Run("an approved row that is applied", func(t *testing.T) { |
| 174 | if got := checkRunApplied(builtRows(rows, []string{"RUN-C1"}), dir); len(got) != 0 { |
| 175 | t.Errorf("problems = %q", got) |
| 176 | } |
| 177 | }) |
| 178 | t.Run("a fragment inside a larger literal", func(t *testing.T) { |
| 179 | if got := checkRunApplied(builtRows(rows, []string{"RUN-C4"}), dir); len(got) != 0 { |
| 180 | t.Errorf("problems = %q", got) |
| 181 | } |
| 182 | }) |
| 183 | t.Run("a fragment only in a comment", func(t *testing.T) { |
| 184 | got := checkRunApplied(builtRows(rows, []string{"RUN-C5"}), dir) |
| 185 | if len(got) != 1 || !strings.Contains(got[0], "RUN-C5") { |
| 186 | t.Errorf("problems = %q", got) |
| 187 | } |
| 188 | }) |
| 189 | } |
| 190 | |
| 191 | // The series is done only when every deck row is in the code or the docs. |
| 192 | func TestRunCopyIsFullyBuilt(t *testing.T) { |
| 193 | built := map[string]bool{} |
| 194 | for _, id := range runCopyBuilt { |
| 195 | built[id] = true |
| 196 | } |
| 197 | for _, id := range runCopyIDs { |
| 198 | if !built[id] { |
| 199 | t.Errorf("%s is in the deck but no task built it", id) |
| 200 | } |
| 201 | } |
| 202 | } |
| 203 | |