wsb 0.251.6311

Workspace tools for every project under $PRJ: git across submodules, template stamping, GitHub, and the workbench
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
//! What the catalogue says, once it has been scanned.
//!
//! Everything here reads the table. Nothing walks the tree — that is
//! [`crate::catalogue::scan`]'s job, and keeping the two apart is what
//! makes "how many tests are there" a question with one answer instead of one
//! per program that counted.

use std::collections::BTreeMap;
use std::fmt::Write;

use crate::persist::local::models::catalogued_test::CataloguedTest;
use crate::persist::local::repos::catalogue_repo::CatalogueRepo;
use crate::catalogue::scan::display_repo;

/// Everything a person wants to know about the catalogue at once.
pub struct Survey {
    pub per_repo: Vec<(String, i64, i64)>,
    pub duplicates: Vec<(String, i64, Vec<CataloguedTest>)>,
    pub misnumbered: Vec<CataloguedTest>,
    pub total: i64,
    pub numbered: i64,
}

impl Survey {
    pub fn read(repo: &CatalogueRepo) -> anyhow::Result<Self> {
        let per_repo = repo.per_repo_counts()?;
        let total = per_repo.iter().map(|(_, all, _)| all).sum();
        let numbered = per_repo.iter().map(|(_, _, numbered)| numbered).sum();
        Ok(Self {
            per_repo,
            duplicates: repo.duplicates_within_repos()?,
            misnumbered: repo.misnumbered()?,
            total,
            numbered,
        })
    }

    /// True when nothing in the catalogue is wrong — no number used twice in a
    /// repository, no test whose name and description disagree.
    pub fn is_clean(&self) -> bool {
        self.duplicates.is_empty() && self.misnumbered.is_empty()
    }

    pub fn render(&self) -> String {
        let mut out = String::new();
        let _ = writeln!(
            out,
            "{} tests, {} numbered, across {} repositories",
            self.total,
            self.numbered,
            self.per_repo.len()
        );
        let width = self
            .per_repo
            .iter()
            .map(|(repo, ..)| display_repo(repo).len())
            .max()
            .unwrap_or(0);
        for (repo, all, numbered) in &self.per_repo {
            let _ = writeln!(
                out,
                "  {:width$}  {:>5} tests  {:>5} numbered",
                display_repo(repo),
                all,
                numbered,
                width = width
            );
        }

        if self.duplicates.is_empty() {
            let _ = writeln!(out, "\nNo number is used twice within a repository.");
        } else {
            let _ = writeln!(
                out,
                "\n{} number(s) used more than once within one repository:",
                self.duplicates.len()
            );
            for (repo, number, members) in &self.duplicates {
                let _ = writeln!(out, "  {} TEST{number}", display_repo(repo));
                for member in members {
                    let _ = writeln!(out, "      {} — {}", member.location(), member.function_name);
                }
            }
        }

        if self.misnumbered.is_empty() {
            let _ = writeln!(out, "\nEvery description agrees with the name above it.");
        } else {
            let _ = writeln!(
                out,
                "\n{} test(s) whose name and description claim different numbers:",
                self.misnumbered.len()
            );
            for test in &self.misnumbered {
                let _ = writeln!(
                    out,
                    "  {} {} — named TEST{}, described as TEST{}",
                    display_repo(&test.repo),
                    test.location(),
                    test.number.unwrap_or_default(),
                    test.described_number().unwrap_or_default()
                );
            }
        }

        out
    }
}

/// The number×repository matrix for one family of mirrors.
///
/// One protocol implemented five times means a number should appear in all
/// five. Which numbers do not is the only question the parity tooling ever
/// really answered, and here it is a `GROUP BY`.
pub struct Parity {
    pub members: Vec<String>,
    /// number → the members that have it, in `members` order.
    pub rows: BTreeMap<i64, Vec<bool>>,
}

impl Parity {
    pub fn read(repo: &CatalogueRepo, members: &[String]) -> anyhow::Result<Self> {
        let coverage = repo.number_coverage()?;
        let mut rows = BTreeMap::new();
        for (number, repos) in coverage {
            let present: Vec<bool> = members.iter().map(|m| repos.contains(m)).collect();
            // A number no member of this family claims is not this family's
            // business; listing it would bury the gaps in the whole workspace.
            if present.iter().any(|p| *p) {
                rows.insert(number, present);
            }
        }
        Ok(Self {
            members: members.to_vec(),
            rows,
        })
    }

    /// Numbers present in some members and missing from others.
    pub fn gaps(&self) -> Vec<(i64, Vec<String>)> {
        self.rows
            .iter()
            .filter(|(_, present)| present.iter().any(|p| *p) && present.iter().any(|p| !*p))
            .map(|(number, present)| {
                let missing = self
                    .members
                    .iter()
                    .zip(present)
                    .filter(|(_, have)| !**have)
                    .map(|(member, _)| member.clone())
                    .collect();
                (*number, missing)
            })
            .collect()
    }

    pub fn render(&self) -> String {
        let mut out = String::new();
        let gaps = self.gaps();
        let _ = writeln!(
            out,
            "{} numbers across {} members; {} complete, {} with gaps",
            self.rows.len(),
            self.members.len(),
            self.rows.len() - gaps.len(),
            gaps.len()
        );
        if gaps.is_empty() {
            return out;
        }
        let _ = writeln!(out);
        for (number, missing) in gaps {
            let _ = writeln!(out, "  TEST{number}  missing from {}", missing.join(", "));
        }
        out
    }
}

/// The parity view as a document.
///
/// # Why this is here and not in a script
///
/// One protocol implemented five times is only ONE protocol for as long as the
/// same number means the same thing in each mirror, and that is not a property
/// anybody holds in their head across nine thousand tests. So it is written
/// down — and the written form has to be a rendering of the table, because a
/// parity report maintained any other way is a claim about the tests rather
/// than a reading of them.
///
/// The document this replaces was generated once by a tool that no longer
/// exists. It sat in a published repository describing mechanisms that had
/// since been retired, under a header telling readers not to hand-edit it. A
/// generated document with no generator is worse than no document: it is read
/// as current.
///
/// Members are named by their directory rather than by a shorthand. The old
/// report called them `rust`, `go`, `py`, `js`, `objc` — five names that are
/// not the names of the five repositories, and that a reader had to map back
/// by hand.
pub fn parity_markdown(repo: &CatalogueRepo, members: &[String]) -> anyhow::Result<String> {
    /// The shared range: below this a number must mean the same thing
    /// everywhere; at or above it a number is one mirror's own business.
    const SHARED_CEILING: i64 = 8000;

    let parity = Parity::read(repo, members)?;
    let all = repo.all()?;
    let survey = Survey::read(repo)?;

    // Every catalogued test that belongs to this family, by number then member.
    let mut by_number: BTreeMap<i64, BTreeMap<String, Vec<&CataloguedTest>>> = BTreeMap::new();
    for test in &all {
        let (Some(number), true) = (test.number, members.contains(&test.repo)) else {
            continue;
        };
        by_number
            .entry(number)
            .or_default()
            .entry(test.repo.clone())
            .or_default()
            .push(test);
    }

    let label = |member: &str| -> String { member.rsplit('/').next().unwrap_or(member).to_string() };
    // A description reduced to what it CLAIMS, so wording that differs only in
    // whitespace or case is not reported as a divergence.
    let normalized = |text: &str| -> String {
        text.split_whitespace().collect::<Vec<_>>().join(" ").to_lowercase()
    };

    let mut out = String::new();
    let _ = writeln!(out, "# Cross-Mirror Test Parity\n");
    let _ = writeln!(
        out,
        "> Generated from the test catalogue — do not hand-edit. Rescan and regenerate after \
         changing any mirror's tests.\n"
    );
    let _ = writeln!(
        out,
        "Numbers **1–{}** are the SHARED range: the same number must test the same thing, with \
         the same method, in every mirror that implements that feature. Numbers \
         **{SHARED_CEILING}+** are implementation-specific.\n",
        SHARED_CEILING - 1
    );

    let _ = writeln!(out, "## Mirrors scanned\n");
    let _ = writeln!(out, "| Mirror | Numbered tests |");
    let _ = writeln!(out, "|---|---|");
    for member in members {
        let count = all.iter().filter(|t| t.repo == *member && t.number.is_some()).count();
        let _ = writeln!(out, "| `{}` | {count} |", display_repo(member));
    }
    let _ = writeln!(out);

    let solo: Vec<i64> = by_number
        .iter()
        .filter(|(_, per_member)| per_member.len() == 1)
        .map(|(number, _)| *number)
        .collect();
    let solo_shared: Vec<i64> = solo.iter().copied().filter(|n| *n < SHARED_CEILING).collect();
    // A gap in the implementation-specific range is not a gap: a number one
    // mirror alone claims is exactly what that range is for.
    let gaps = parity.gaps();
    let shared_gaps: Vec<&(i64, Vec<String>)> = gaps
        .iter()
        .filter(|(number, _)| *number < SHARED_CEILING)
        .filter(|(number, _)| by_number.get(number).is_some_and(|m| m.len() > 1))
        .collect();

    // Numbers whose mirrors describe them differently, reported with every
    // wording — the useful question (wording drift, or two different tests
    // wearing one number) can only be answered by reading them side by side.
    let divergent: Vec<i64> = by_number
        .iter()
        .filter(|(_, per_member)| per_member.len() > 1)
        .filter(|(_, per_member)| {
            let mut seen: Vec<String> = per_member
                .values()
                .filter_map(|tests| tests.first())
                .map(|t| normalized(&t.description))
                .filter(|d| !d.is_empty())
                .collect();
            seen.sort();
            seen.dedup();
            seen.len() > 1
        })
        .map(|(number, _)| *number)
        .collect();

    let family_duplicates: Vec<&(String, i64, Vec<CataloguedTest>)> = survey
        .duplicates
        .iter()
        .filter(|(member, ..)| members.contains(member))
        .collect();

    let _ = writeln!(out, "## Summary\n");
    let _ = writeln!(out, "- Distinct numbered tests across all mirrors: **{}**", by_number.len());
    let _ = writeln!(out, "- Shared (in \u{2265}2 mirrors): **{}**", by_number.len() - solo.len());
    let _ = writeln!(out, "- Solo (in exactly 1 mirror): **{}**", solo.len());
    let _ = writeln!(
        out,
        "  - \u{2026}in the shared range 1\u{2013}{} \u{2014} **port targets**, unless the \
         behaviour is genuinely implementation-specific, in which case the test moves to \
         {SHARED_CEILING}+: **{}**",
        SHARED_CEILING - 1,
        solo_shared.len()
    );
    let _ = writeln!(
        out,
        "  - \u{2026}already in the {SHARED_CEILING}+ implementation-specific range: **{}**",
        solo.len() - solo_shared.len()
    );
    let _ = writeln!(
        out,
        "- Shared numbers with a parity gap (missing from \u{2265}1 mirror): **{}**",
        shared_gaps.len()
    );
    let _ = writeln!(
        out,
        "- Shared numbers described differently by different mirrors: **{}**",
        divergent.len()
    );
    let _ = writeln!(out, "- Within-mirror duplicate numbers: **{}**\n", family_duplicates.len());
    let _ = writeln!(out, "---\n");

    let _ = writeln!(out, "## Solo tests in the shared range \u{2192} port worklist\n");
    let _ = writeln!(
        out,
        "These numbered tests exist in exactly ONE mirror but occupy the shared range. The \
         default disposition is to port them to the others keeping the number.\n"
    );
    let _ = writeln!(out, "| Test # | Mirror | Function | Description | File |");
    let _ = writeln!(out, "|---|---|---|---|---|");
    for number in &solo_shared {
        let Some(per_member) = by_number.get(number) else { continue };
        for (member, tests) in per_member {
            for test in tests {
                let _ = writeln!(
                    out,
                    "| TEST{number} | `{}` | `{}` | {} | {} |",
                    label(member),
                    test.function_name,
                    cell(&test.description),
                    test.location()
                );
            }
        }
    }
    let _ = writeln!(out);

    let _ = writeln!(out, "## Parity gaps (shared number missing from a mirror)\n");
    let _ = writeln!(
        out,
        "A shared-range number present in some mirrors and absent from others. A gap is \
         legitimate only when the missing mirror does not implement the feature.\n"
    );
    let _ = writeln!(out, "| Test # | Present in | Missing from | Description (first present) |");
    let _ = writeln!(out, "|---|---|---|---|");
    for (number, missing) in &shared_gaps {
        let Some(per_member) = by_number.get(number) else { continue };
        let present: Vec<String> = per_member.keys().map(|m| label(m)).collect();
        let description = per_member
            .values()
            .filter_map(|tests| tests.first())
            .map(|t| t.description.clone())
            .find(|d| !d.is_empty())
            .unwrap_or_default();
        let _ = writeln!(
            out,
            "| TEST{number} | {} | {} | {} |",
            present.join(", "),
            missing.iter().map(|m| label(m)).collect::<Vec<_>>().join(", "),
            cell(&description)
        );
    }
    let _ = writeln!(out);

    let _ = writeln!(out, "## Divergent descriptions (same number, different wording)\n");
    let _ = writeln!(
        out,
        "Same number, materially different descriptions. Read each: wording drift is fine, two \
         different tests wearing one number is not.\n"
    );
    for number in &divergent {
        let Some(per_member) = by_number.get(number) else { continue };
        let _ = writeln!(out, "### TEST{number}\n");
        for (member, tests) in per_member {
            for test in tests {
                let _ = writeln!(out, "- **{}**: {}", label(member), cell(&test.description));
            }
        }
        let _ = writeln!(out);
    }

    let _ = writeln!(out, "## Within-mirror duplicate numbers\n");
    if family_duplicates.is_empty() {
        let _ = writeln!(out, "None.\n");
    } else {
        let _ = writeln!(
            out,
            "A number assigned to more than one function inside a single mirror. These must be \
             fixed \u{2014} renumber all but the first \u{2014} whatever the cross-mirror picture \
             says.\n"
        );
        for (member, number, tests) in &family_duplicates {
            let _ = writeln!(
                out,
                "- `{}` TEST{number}: {}",
                label(member),
                tests
                    .iter()
                    .map(|t| format!("`{}` ({})", t.function_name, t.location()))
                    .collect::<Vec<_>>()
                    .join(", ")
            );
        }
        let _ = writeln!(out);
    }

    let _ = writeln!(out, "## Full matrix\n");
    let _ = writeln!(
        out,
        "\u{2713} = present, \u{b7} = absent. Range: `shared` below {SHARED_CEILING}, `impl` at \
         or above it.\n"
    );
    let _ = writeln!(
        out,
        "| Test # | Range | {} | Kind |",
        members.iter().map(|m| label(m)).collect::<Vec<_>>().join(" | ")
    );
    let _ = writeln!(
        out,
        "|---|---|{}---|",
        members.iter().map(|_| "---|").collect::<String>()
    );
    for (number, present) in &parity.rows {
        let range = if *number < SHARED_CEILING { "shared" } else { "impl" };
        let kind = if present.iter().filter(|p| **p).count() > 1 { "shared" } else { "solo" };
        let _ = writeln!(
            out,
            "| TEST{number} | {range} | {} | {kind} |",
            present
                .iter()
                .map(|have| if *have { "\u{2713}" } else { "\u{b7}" })
                .collect::<Vec<_>>()
                .join(" | ")
        );
    }

    Ok(out)
}

/// One description, as a markdown table cell.
///
/// A description is a doc comment: it holds newlines, leading comment markers
/// and pipes, and every one of those ends a table row early. Flattened rather
/// than truncated \u{2014} the description is the only thing in the row that
/// says what the test is for.
fn cell(description: &str) -> String {
    let flattened = description
        .split_whitespace()
        .map(|word| word.trim_start_matches(['/', '#', '*']))
        .filter(|word| !word.is_empty())
        .collect::<Vec<_>>()
        .join(" ");
    if flattened.is_empty() {
        "\u{2014}".to_string()
    } else {
        flattened.replace('|', "\\|")
    }
}

/// The catalogue as `TEST_CATALOG.md`.
///
/// An export, in the same sense `workspace.json` is: the table is what is true,
/// this is a rendering of it that reads well in a diff. Thirty-one of these
/// files were maintained by hand, each by a different pass, none agreeing.
pub fn markdown(repo: &CatalogueRepo, only_repo: Option<&str>) -> anyhow::Result<String> {
    let tests: Vec<CataloguedTest> = repo
        .all()?
        .into_iter()
        .filter(|test| only_repo.is_none_or(|wanted| test.repo == wanted))
        .collect();

    let mut out = String::new();
    let scope = match only_repo {
        Some(repo) => display_repo(repo).to_string(),
        None => "the workspace".to_string(),
    };
    let _ = writeln!(out, "# Test catalogue — {scope}\n");
    // The tool is deliberately not named here. This file is written into every
    // repository the catalogue covers, and several of those are PUBLISHED —
    // capdag's five mirrors and the cartridge SDK's four are public on GitHub.
    // A generated header naming the private tool that wrote it puts that name
    // in a public repository, which is the one thing the workspace's own rule
    // about them forbids. What a reader of any of them needs is the same
    // either way: this file is generated, so edit the tests.
    let _ = writeln!(
        out,
        "Generated from the test catalogue. Edit the tests, not this file.\n"
    );

    let numbered: Vec<&CataloguedTest> = tests.iter().filter(|t| t.number.is_some()).collect();
    let unnumbered: Vec<&CataloguedTest> = tests.iter().filter(|t| t.number.is_none()).collect();
    let _ = writeln!(
        out,
        "{} tests: {} numbered, {} unnumbered.\n",
        tests.len(),
        numbered.len(),
        unnumbered.len()
    );

    if !numbered.is_empty() {
        let _ = writeln!(out, "## Numbered\n");
        let _ = writeln!(out, "| Number | Repository | Language | Test | Location | Description |");
        let _ = writeln!(out, "|---|---|---|---|---|---|");
        for test in numbered {
            let _ = writeln!(
                out,
                "| TEST{} | {} | {} | `{}` | {} | {} |",
                test.number.unwrap_or_default(),
                display_repo(&test.repo),
                test.language.as_str(),
                test.function_name,
                test.location(),
                test.description.replace('|', "\\|")
            );
        }
        let _ = writeln!(out);
    }

    if !unnumbered.is_empty() {
        let _ = writeln!(out, "## Unnumbered\n");
        let _ = writeln!(out, "| Repository | Language | Test | Location |");
        let _ = writeln!(out, "|---|---|---|---|");
        for test in unnumbered {
            let _ = writeln!(
                out,
                "| {} | {} | `{}` | {} |",
                display_repo(&test.repo),
                test.language.as_str(),
                test.function_name,
                test.location()
            );
        }
    }

    Ok(out)
}

#[cfg(test)]
#[path = "report_tests.rs"]
mod tests;