diff --git a/cmd/mbf/internal/pkgserver/ui/jstest/go_test_entrypoint.ts b/cmd/mbf/internal/pkgserver/ui/jstest/go_test_entrypoint.ts new file mode 100644 index 00000000..eaae0ad5 --- /dev/null +++ b/cmd/mbf/internal/pkgserver/ui/jstest/go_test_entrypoint.ts @@ -0,0 +1,3 @@ +import "../all_tests.js"; +import { GoTestReporter, GLOBAL_REGISTRAR } from "./jstest.js"; +GLOBAL_REGISTRAR.run(new GoTestReporter()); diff --git a/cmd/mbf/internal/pkgserver/ui/jstest/jstest.ts b/cmd/mbf/internal/pkgserver/ui/jstest/jstest.ts index 3b7b9fd7..aa3ca4fc 100644 --- a/cmd/mbf/internal/pkgserver/ui/jstest/jstest.ts +++ b/cmd/mbf/internal/pkgserver/ui/jstest/jstest.ts @@ -23,6 +23,7 @@ export class TestRegistrar { } run(reporter: Reporter) { + reporter.register(this.#suites); for (const suite of this.#suites) { for (const c of suite.children) runTests(reporter, [suite.name], c); } @@ -141,6 +142,9 @@ function extractExceptionString(e: any): string { // Reporting export interface Reporter { + // A notable feature—or flaw, to some—of the DSL is that the tree of tests + // is statically known, which might greatly aid in implementing a reporter. + register(suites: TestGroup[]): void; // While we could simply call a function with a tree representing all // results, which would indeed greatly simplify implementation of reporters, // simply registering a path and allowing the reporter to—either implicitly @@ -170,14 +174,20 @@ export interface Reporter { // underlying result tree; this makes it extremely convenient in some cases like // testing ourself. export class NoOpReporter implements Reporter { + suites: TestGroup[]; results: ({ path: string[] } & TestResult)[]; finalized: boolean; constructor() { + this.suites = []; this.results = []; this.finalized = false; } + register(suites: TestGroup[]) { + this.suites = suites; + } + update(path: string[], result: TestResult) { this.results.push({ path, ...result }); } @@ -211,6 +221,9 @@ export class StreamReporter implements Reporter { return this.counts.successes > 0 && this.counts.failures === 0; } + // We don't need the structure for reporting. + register(suites: TestGroup[]) {} + update(path: string[], result: TestResult) { if (path.length === 0) throw new RangeError("path is empty"); const pathStr = path.join(SEP); @@ -300,6 +313,13 @@ function assertGetElementById(id: string): HTMLElement { // A reporter that directly translates a tree of results into a tree of // collapsible elements in the DOM. export class DOMReporter implements Reporter { + // It is very difficult to implement this using the statically known tree, + // because Map doesn't handle array keys properly (to store the path), and + // it's unknown of there's any way to implement it without writing one's own + // data types. Oh well; using the DOM as a data structure might seem hacky + // but it does have its benefits, apart from encouraging a tagless final. + register(suites: TestGroup[]) {} + update(path: string[], result: TestResult) { if (path.length === 0) throw new RangeError("path is empty"); const counter = assertGetElementById(result.success ? "successes" : "failures"); @@ -349,3 +369,34 @@ export class DOMReporter implements Reporter { finalize() {} } + +interface GoNode { + name: string; + subtests?: GoNode[]; +} + +// Used to display results via `go test`, via some glue code from the Go side. +// TODO(Ophestra): said glue code has to be written. +export class GoTestReporter implements Reporter { + register(suites: TestGroup[]) { + console.log(JSON.stringify(suites.map(GoTestReporter.serialize))); + } + + // Convert a test tree into the one expected by the Go code. + static serialize(node: TestTree): GoNode { + return { + name: node.name, + subtests: "children" in node ? node.children.map(GoTestReporter.serialize) : undefined, + }; + } + + update(path: string[], result: TestResult) { + console.log(JSON.stringify({ path, ...result })); + } + + // Unnecessary but convenient on the Go side, so that it doesn't have to + // infer this via process exit. + finalize() { + console.log("null"); + } +} diff --git a/cmd/mbf/internal/pkgserver/ui/sample_test.ts b/cmd/mbf/internal/pkgserver/ui/sample_test.ts index 918cd763..3c60a849 100644 --- a/cmd/mbf/internal/pkgserver/ui/sample_test.ts +++ b/cmd/mbf/internal/pkgserver/ui/sample_test.ts @@ -46,6 +46,23 @@ suite("cat", [ ]); const reporter = new NoOpReporter(); r.run(reporter); + if (reporter.suites.length !== 1) { + t.fatal(`incorrect number of suites registered got=${reporter.suites.length} want=1`); + } + const suite = reporter.suites[0]; + if (suite.name !== "explod") { + t.error(`suite name incorrect got='${suite.name}' want='explod'`); + } + if (suite.children.length !== 1) { + t.fatal(`incorrect number of suite children got=${suite.children.length} want=1`); + } + const test_ = suite.children[0]; + if (test_.name !== "with yarn") { + t.error(`incorrect test name got='${test_.name}' want='with yarn'`); + } + if ("children" in test_) { + t.error(`expected leaf node, got group of ${test_.children.length} children`); + } if (!reporter.finalized) t.error(`expected reporter to have been finalized`); if (reporter.results.length !== 1) { t.fatal(`incorrect result count got=${reporter.results.length} want=1`);