| 1 |
// Flow's test reports in VS Code's Testing view (spec §9.2): the newest |
| 2 |
// report on each repository's current bookmark, its suites and cases with |
| 3 |
// their results, messages and output. Nothing runs here: running a test |
| 4 |
// loads the hub's latest results. |
| 5 |
|
| 6 |
import * as vscode from "vscode"; |
| 7 |
import { HubAuth } from "./auth"; |
| 8 |
import { caseId, caseOutcome, casesBySuite, duration } from "./flowview"; |
| 9 |
import type { HubRepo } from "./proposals"; |
| 10 |
|
| 11 |
export class HubTests implements vscode.Disposable { |
| 12 |
private readonly controller: vscode.TestController; |
| 13 |
|
| 14 |
constructor( |
| 15 |
private readonly auth: HubAuth, |
| 16 |
private readonly repos: () => Promise<HubRepo[]>, |
| 17 |
) { |
| 18 |
this.controller = vscode.tests.createTestController("oxygenhub", "OxygenFlow test reports"); |
| 19 |
this.controller.refreshHandler = () => this.load(); |
| 20 |
this.controller.createRunProfile("Load the hub's latest results", vscode.TestRunProfileKind.Run, () => this.load(), true); |
| 21 |
} |
| 22 |
|
| 23 |
dispose(): void { |
| 24 |
this.controller.dispose(); |
| 25 |
} |
| 26 |
|
| 27 |
/** Fetch each repository's newest test report and show its results. */ |
| 28 |
async load(): Promise<void> { |
| 29 |
const repos = await this.repos().catch(() => [] as HubRepo[]); |
| 30 |
const roots: vscode.TestItem[] = []; |
| 31 |
const results: [vscode.TestItem, string, string, string, number][] = []; |
| 32 |
for (const repo of repos) { |
| 33 |
const client = this.auth.clientFor(repo.base); |
| 34 |
const bookmark = repo.handle.currentBookmark ?? undefined; |
| 35 |
const [newest] = await client.testReports(repo.owner, repo.name, bookmark, 1).catch(() => []); |
| 36 |
if (!newest) { |
| 37 |
continue; |
| 38 |
} |
| 39 |
const detail = await client.testReport(repo.owner, repo.name, newest.id).catch(() => undefined); |
| 40 |
if (!detail) { |
| 41 |
continue; |
| 42 |
} |
| 43 |
const key = `${repo.base}/${repo.owner}/${repo.name}`; |
| 44 |
const r = detail.report; |
| 45 |
const root = this.controller.createTestItem(key, `${repo.owner}/${repo.name}`); |
| 46 |
root.description = `${r.passed}/${r.tests} passed${r.failed ? ` · ${r.failed} failed` : ""} · run #${r.run} ${r.job} on ${r.bookmark}`; |
| 47 |
for (const [suite, cases] of casesBySuite(detail.cases)) { |
| 48 |
const s = this.controller.createTestItem(`${key}::${suite}`, suite); |
| 49 |
for (const c of cases) { |
| 50 |
const item = this.controller.createTestItem(caseId(key, c), c.name); |
| 51 |
item.description = c.classname && c.classname !== suite ? c.classname : undefined; |
| 52 |
s.children.add(item); |
| 53 |
results.push([item, c.status, c.message, c.output, c.time_ms]); |
| 54 |
} |
| 55 |
root.children.add(s); |
| 56 |
} |
| 57 |
roots.push(root); |
| 58 |
} |
| 59 |
this.controller.items.replace(roots); |
| 60 |
const run = this.controller.createTestRun(new vscode.TestRunRequest(), "OxygenFlow", false); |
| 61 |
for (const [item, status, message, output, ms] of results) { |
| 62 |
const outcome = caseOutcome(status); |
| 63 |
if (outcome === "passed") { |
| 64 |
run.passed(item, ms); |
| 65 |
} else if (outcome === "skipped") { |
| 66 |
run.skipped(item); |
| 67 |
} else { |
| 68 |
const m = new vscode.TestMessage(message || `${status} after ${duration(ms)}`); |
| 69 |
if (output) { |
| 70 |
m.actualOutput = output; |
| 71 |
} |
| 72 |
(outcome === "failed" ? run.failed : run.errored).call(run, item, m, ms); |
| 73 |
} |
| 74 |
} |
| 75 |
run.end(); |
| 76 |
} |
| 77 |
} |