Compare commits

...

2 Commits

Author SHA1 Message Date
4d73f75ec8 packaging: npm configuration 2024-04-03 17:47:01 -04:00
431b25cdfd feat: Properly handle node async output 2024-04-03 17:15:43 -04:00
6 changed files with 151 additions and 77 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
node_modules/

24
package-lock.json generated Normal file
View File

@ -0,0 +1,24 @@
{
"name": "test-tap",
"version": "0.0.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "test-tap",
"version": "0.0.1",
"license": "Artistic-2.0",
"dependencies": {
"esm": "^3.2.25"
}
},
"node_modules/esm": {
"version": "3.2.25",
"resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz",
"integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==",
"engines": {
"node": ">=6"
}
}
}
}

34
package.json Normal file
View File

@ -0,0 +1,34 @@
{
"name": "test-tap",
"version": "0.0.1",
"description": "Test-Tap a 0 dependency single file Test Anything Protocol library",
"type": "module",
"directories": {
"test": "tests",
"lib": "src"
},
"files": [
"src/*.mjs",
"src/*.js"
],
"scripts": {
"test": "node tests/01_tap.t.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/zaphar/test-tap.git"
},
"keywords": [
"testing",
"tap"
],
"author": "Jeremy Wall (jeremy@marzhillstudios.com)",
"license": "Artistic-2.0",
"bugs": {
"url": "https://github.com/zaphar/test-tap/issues"
},
"homepage": "https://github.com/zaphar/test-tap#readme",
"dependencies": {
"esm": "^3.2.25"
}
}

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(
loaded.stdout.write(text + "\n"); // 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");
}));
} }
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 */
@ -86,32 +100,44 @@ class Tap {
/** @type Number */ /** @type Number */
counter = 0; counter = 0;
/** @type Number */ /** @type Number */
passed = 0; passed = 0;
/** @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);
}; };
/** /**
@ -120,8 +146,8 @@ class Tap {
* @param {boolean} ok * @param {boolean} ok
* @param {string=} description * @param {string=} description
*/ */
mk_tap(ok, description){ mk_tap(ok, description) {
if(!this.planned){ if (!this.planned) {
this.out("You tried to run tests without a plan. Gotta have a plan."); this.out("You tried to run tests without a plan. Gotta have a plan.");
throw new Error("You tried to run tests without a plan. Gotta have a plan."); throw new Error("You tried to run tests without a plan. Gotta have a plan.");
} }
@ -130,12 +156,12 @@ class Tap {
}; };
comment(msg){ comment(msg) {
if(!msg) { if (!msg) {
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.
@ -162,7 +188,7 @@ class Tap {
var self = this; var self = this;
var tapper = self.mk_tap; var tapper = self.mk_tap;
self.mk_tap = function(ok, desc) { self.mk_tap = function(ok, desc) {
tapper.apply(self, [ok, "# TODO: "+desc]); tapper.apply(self, [ok, "# TODO: " + desc]);
} }
func(); func();
self.mk_tap = tapper; self.mk_tap = tapper;
@ -182,8 +208,8 @@ class Tap {
self.mk_tap = function(ok, desc) { self.mk_tap = function(ok, desc) {
tapper.apply(self, [ok, desc]); tapper.apply(self, [ok, desc]);
} }
for(var i = 0; i < count; i++) { for (var i = 0; i < count; i++) {
self.fail("# SKIP "+reason) self.fail("# SKIP " + reason)
} }
self.mk_tap = tapper; self.mk_tap = tapper;
} else { } else {
@ -200,7 +226,7 @@ class Tap {
* @param {Number=} testCount * @param {Number=} testCount
*/ */
plan(testCount) { plan(testCount) {
if(this.planned){ if (this.planned) {
throw new Error("you tried to set the plan twice!"); throw new Error("you tried to set the plan twice!");
} }
if (!testCount) { if (!testCount) {
@ -211,10 +237,10 @@ class Tap {
} }
}; };
#pass_if(func, desc){ #pass_if(func, desc) {
var result = func(); var result = func();
if(result) { this.pass(desc) } if (result) { this.pass(desc) }
else { this.fail(desc) } else { this.fail(desc) }
} }
// exception tests // exception tests
@ -233,8 +259,8 @@ class Tap {
try { try {
func(); func();
} }
catch(err) { catch (err) {
errormsg = err+''; errormsg = err + '';
} }
this.like(errormsg, msg, 'code threw [' + errormsg + '] expected: [' + msg + ']'); this.like(errormsg, msg, 'code threw [' + errormsg + '] expected: [' + msg + ']');
} }
@ -253,8 +279,8 @@ class Tap {
try { try {
func(); func();
} }
catch(err) { catch (err) {
errormsg = err+''; errormsg = err + '';
msg = true; msg = true;
} }
this.ok(msg, 'code died with [' + errormsg + ']'); this.ok(msg, 'code died with [' + errormsg + ']');
@ -273,7 +299,7 @@ class Tap {
try { try {
func(); func();
} }
catch(err) { catch (err) {
errormsg = false; errormsg = false;
} }
this.ok(errormsg, msg); this.ok(errormsg, msg);
@ -287,9 +313,9 @@ class Tap {
can_ok(obj) { can_ok(obj) {
var desc = 'object can ['; var desc = 'object can [';
var pass = true; var pass = true;
for (var i=1; i<arguments.length; i++) { for (var i = 1; i < arguments.length; i++) {
if (typeof(obj[arguments[i]]) != 'function') { if (typeof (obj[arguments[i]]) != 'function') {
if (typeof(obj.prototype) != 'undefined') { if (typeof (obj.prototype) != 'undefined') {
var result = typeof eval('obj.prototype.' + arguments[i]); var result = typeof eval('obj.prototype.' + arguments[i]);
if (result == 'undefined') { if (result == 'undefined') {
pass = false; pass = false;
@ -303,8 +329,8 @@ class Tap {
desc += ' ' + arguments[i]; desc += ' ' + arguments[i];
} }
desc += ' ]'; desc += ' ]';
this.#pass_if(function(){ this.#pass_if(function() {
return pass; return pass;
}, desc); }, desc);
} }
@ -317,7 +343,7 @@ class Tap {
* @param {string} desc * @param {string} desc
*/ */
is(got, expected, desc) { is(got, expected, desc) {
this.#pass_if(function(){ return got == expected; }, desc); this.#pass_if(function() { return got == expected; }, desc);
}; };
@ -328,7 +354,7 @@ class Tap {
* @param {string} desc * @param {string} desc
*/ */
ok(got, desc) { ok(got, desc) {
this.#pass_if(function(){ return !!got; }, desc); this.#pass_if(function() { return !!got; }, desc);
}; };
@ -340,13 +366,13 @@ class Tap {
* @param {string} desc * @param {string} desc
*/ */
like(string, regex, desc) { like(string, regex, desc) {
this.#pass_if(function(){ this.#pass_if(function() {
if(regex instanceof RegExp) { if (regex instanceof RegExp) {
return string.match(regex) return string.match(regex)
} else { } else {
return string.indexOf(regex) != -1 return string.indexOf(regex) != -1
} }
}, desc) }, desc)
} }
/** /**
@ -357,9 +383,9 @@ class Tap {
* @param {string} desc * @param {string} desc
*/ */
unlike(string, regex, desc) { unlike(string, regex, desc) {
this.#pass_if(function(){ this.#pass_if(function() {
return !string.match(regex); return !string.match(regex);
}, desc) }, desc)
} }
} }

View File

@ -1,19 +1,8 @@
/** @implements TapRenderer */ import { Tap, runTest } from '../src/Tap.mjs';
class FakeRenderer { import { tapSuite } from './suite.mjs';
output = "nothing yet";
commentOutput = "";
out(text) { const pair = Tap.Node();
this.output = text; runTest(pair.Tap, "Tap dogfood test suite", tapSuite);
} // Note output requires some async machinery because it uses some dynamic inputs.
await pair.Renderer.renderAll();
comment(lines) { process.exit(pair.Tap.isPass() ? 0 : 1);
for (var line of lines) {
this.commentOutput += line;
}
}
}
import('./suite.mjs').then(m => {
m.runTest(m.Tap.Node(), "Tap dogfood test suite", m.tapSuite);
});

View File

@ -1,5 +1,5 @@
/** @implements TapRenderer */ /** @implements TapRenderer */
import {Tap, runTest} from '../src/TAP.mjs'; import { Tap } from '../src/Tap.mjs';
class FakeRenderer { class FakeRenderer {
output = "nothing yet"; output = "nothing yet";
@ -159,4 +159,4 @@ function tapSuite(t) {
return t; return t;
} }
export { tapSuite, runTest, Tap }; export { tapSuite };