feat: Properly handle node async output

This commit is contained in:
Jeremy Wall 2024-04-03 17:10:03 -04:00
parent 975d7f22af
commit 431b25cdfd
3 changed files with 90 additions and 60 deletions

View File

@ -21,10 +21,16 @@
/** @implements TapRenderer */ /** @implements TapRenderer */
class NodeRenderer { class NodeRenderer {
/** @type {Array<PromiseLike>} */
#thunks = [];
out(text) { out(text) {
import('node:process').then(loaded => {; this.#thunks.push(
// Because this is a ECMAScript module we have to do dynamic module loads
// of the node ecosystem when running in Node.js.
import('node:process').then(loaded => {
loaded.stdout.write(text + "\n"); loaded.stdout.write(text + "\n");
}) }));
} }
comment(lines) { comment(lines) {
@ -32,6 +38,14 @@ class NodeRenderer {
this.out('# ' + line); this.out('# ' + line);
} }
} }
// This gives us a way to block on output. It's ghetto but async is a harsh task master.
async renderAll() {
for (var thunk of this.#thunks) {
await thunk;
}
}
} }
/** @implements TapRenderer */ /** @implements TapRenderer */
@ -90,28 +104,40 @@ class Tap {
/** @type Number */ /** @type Number */
failed = 0; failed = 0;
/** @type TapRenderer */ /** @type TapRenderer */
#renderer renderer
/** /**
* Construct a new Tap Suite with a renderLine function. * Construct a new Tap Suite with a renderLine function.
* @param {TapRenderer} * @param {TapRenderer}
*/ */
constructor(renderer) { constructor(renderer) {
this.#renderer = renderer; this.renderer = renderer;
} }
/**
* @return {{"Renderer": BrowserRenderer, "Tap": Tap}}
*/
static Browser() { static Browser() {
var r = new BrowserRenderer();
return {"Renderer": r, "Tap": new Tap(r)};
return new Tap(new BrowserRenderer()); return new Tap(new BrowserRenderer());
} }
/**
* @return {{"Renderer": NodeRenderer, "Tap": Tap}}
*/
static Node() { static Node() {
return new Tap(new NodeRenderer()); var r = new NodeRenderer();
return {"Renderer": r, "Tap": new Tap(r)};
}
isPass() {
return this.passed != 0;
} }
/** Renders output for the test results */ /** Renders output for the test results */
out(text) { out(text) {
this.#renderer.out(text); this.renderer.out(text);
}; };
/** /**
@ -135,7 +161,7 @@ class Tap {
msg = " "; msg = " ";
} }
var lines = msg.split("\n"); var lines = msg.split("\n");
this.#renderer.comment(lines); this.renderer.comment(lines);
}; };
/** Render a pass TAP output message. /** Render a pass TAP output message.

View File

@ -14,6 +14,10 @@ class FakeRenderer {
} }
} }
import('./suite.mjs').then(m => { import('./suite.mjs').then(async m => {
m.runTest(m.Tap.Node(), "Tap dogfood test suite", m.tapSuite); const pair = m.Tap.Node();
m.runTest(pair.Tap, "Tap dogfood test suite", m.tapSuite);
// Note output requires some async machinery because it uses some dynamic inputs.
await pair.Renderer.renderAll();
process.exit(pair.Tap.isPass() ? 0 : 1);
}); });

View File

@ -1,5 +1,5 @@
/** @implements TapRenderer */ /** @implements TapRenderer */
import {Tap, runTest} from '../src/TAP.mjs'; import {Tap, runTest, NodeRenderer, BrowserRenderer} from '../src/TAP.mjs';
class FakeRenderer { class FakeRenderer {
output = "nothing yet"; output = "nothing yet";