diff --git a/.eslintrc b/.eslintrc
index a9102333..b8af9191 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -53,7 +53,7 @@
},
},
{
- "files": ["lib/results.js"],
+ "files": ["packages/lib/results.js"],
"rules": {
"no-cond-assign": "warn",
"no-param-reassign": "warn",
@@ -61,7 +61,7 @@
},
},
{
- "files": ["lib/test.js"],
+ "files": ["packages/lib/test.js"],
"rules": {
"eqeqeq": "warn",
"func-name-matching": "off",
@@ -120,13 +120,13 @@
},
},
{
- "files": ["lib/default_stream.js"],
+ "files": ["packages/lib/default_stream.js"],
"rules": {
"no-use-before-define": "warn",
},
},
{
- "files": ["lib/test.js"],
+ "files": ["packages/lib/test.js"],
"rules": {
"max-lines": "off",
},
diff --git a/.github/workflows/node-aught.yml b/.github/workflows/node-aught.yml
index 4213896b..e397fae8 100644
--- a/.github/workflows/node-aught.yml
+++ b/.github/workflows/node-aught.yml
@@ -9,3 +9,4 @@ jobs:
range: '< 10'
type: minors
command: npm run tests-only
+ subpackage: ./lib
diff --git a/.github/workflows/node-pretest.yml b/.github/workflows/node-pretest.yml
index 72bdfa3a..bea9a823 100644
--- a/.github/workflows/node-pretest.yml
+++ b/.github/workflows/node-pretest.yml
@@ -6,4 +6,19 @@ jobs:
tests:
uses: ljharb/actions/.github/workflows/pretest.yml@main
with:
- skip-engines: true # bin/tape requires node 8+, but the rest of tape supports 0.4+
+ skip-engines: true # bin/tape requires node 8+, but tape-lib supports 0.4+. TODO: fix and add engines.node in v6
+
+ engines-lib:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: ljharb/actions/node/engines@main
+ with:
+ working-directory: lib
+
+ pack-lib:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: ljharb/actions/node/pack@main
+ with:
+ after_install: cd ../ && npm install
+ working-directory: lib
diff --git a/.github/workflows/node-tens.yml b/.github/workflows/node-tens.yml
index 1f55be09..a33dcae1 100644
--- a/.github/workflows/node-tens.yml
+++ b/.github/workflows/node-tens.yml
@@ -9,3 +9,4 @@ jobs:
range: '>= 10'
type: minors
command: npm run tests-only
+ subpackage: ./lib
diff --git a/.gitignore b/.gitignore
index a63afe6d..cf7f1e58 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,8 @@
# gitignore
/node_modules
+**/node_modules
+!node_modules/tape-lib
# Only apps should have lockfiles
yarn.lock
diff --git a/bin/tape b/bin/tape
index aa8cea32..c21674fc 100755
--- a/bin/tape
+++ b/bin/tape
@@ -93,7 +93,7 @@ var files = opts._.reduce(function (result, arg) {
var hasImport = require('has-dynamic-import');
-var tape = require('../');
+var tape = require('tape-lib');
function importFiles(hasSupport) {
if (!hasSupport) {
diff --git a/index.js b/index.js
new file mode 100644
index 00000000..ea90786e
--- /dev/null
+++ b/index.js
@@ -0,0 +1,3 @@
+'use strict';
+
+module.exports = require('tape-lib');
diff --git a/lib/.npmrc b/lib/.npmrc
new file mode 100644
index 00000000..eacea13e
--- /dev/null
+++ b/lib/.npmrc
@@ -0,0 +1,3 @@
+package-lock=false
+allow-same-version=true
+message=v%s
diff --git a/lib/default_stream.js b/lib/default_stream.js
index ffc2ad11..83b8eb38 100644
--- a/lib/default_stream.js
+++ b/lib/default_stream.js
@@ -1,52 +1,3 @@
'use strict';
-var through = require('@ljharb/through');
-var fs = require('fs');
-
-module.exports = function () {
- var line = '';
- var stream = through(write, flush);
- return stream;
-
- function write(buf) {
- if (
- buf == null // eslint-disable-line eqeqeq
- || (Object(buf) !== buf && typeof buf !== 'string')
- ) {
- flush();
- return;
- }
- for (var i = 0; i < buf.length; i++) {
- var c = typeof buf === 'string'
- ? buf.charAt(i)
- : String.fromCharCode(buf[i]);
- if (c === '\n') {
- flush();
- } else {
- line += c;
- }
- }
- }
-
- function flush() {
- if (fs.writeSync && (/^win/).test(process.platform)) {
- try {
- fs.writeSync(1, line + '\n');
- } catch (e) {
- stream.emit('error', e);
- }
- } else {
- try {
- if (typeof console !== 'undefined' && console.log) { // eslint-disable-line no-console
- console.log(line); // eslint-disable-line no-console
- } else if (typeof document !== 'undefined') {
- // for IE < 9
- document.body.innerHTML += line + '
';
- }
- } catch (e) {
- stream.emit('error', e);
- }
- }
- line = '';
- }
-};
+module.exports = require('tape-lib/default_stream');
diff --git a/lib/package.json b/lib/package.json
new file mode 100644
index 00000000..c64962a1
--- /dev/null
+++ b/lib/package.json
@@ -0,0 +1,71 @@
+{
+ "name": "tape-lib",
+ "version": "0.0.0",
+ "description": "TAP-producing test harness library for node and browsers",
+ "main": "index.js",
+ "browser": {
+ "fs": false
+ },
+ "exports": {
+ ".": "./index.js",
+ "./default_stream": "./default_stream.js",
+ "./results": "./results.js",
+ "./test": "./test.js",
+ "./package.json": "./package.json"
+ },
+ "scripts": {
+ "prepack": "npmignore --auto --commentLines=autogenerated --gitignore=../.gitignore",
+ "version": "auto-changelog && git add CHANGELOG.md",
+ "postversion": "auto-changelog && git add CHANGELOG.md && git commit --no-edit --amend && git tag -f \"v$(node -e \"console.log(require('./package.json').version)\")\"",
+ "prepublishOnly": "safe-publish-latest",
+ "prepublish": "not-in-publish || npm run prepublishOnly",
+ "tests-only": "cd ../ && npm run tests-only",
+ "test": "cd ../ && npm test"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/ljharb/tape.git",
+ "directory": "lib"
+ },
+ "author": "Jordan Harband ",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/ljharb/tape/issues"
+ },
+ "homepage": "https://github.com/ljharb/tape/#readme",
+ "keywords": [
+ "tap",
+ "test",
+ "harness",
+ "assert",
+ "browser"
+ ],
+ "dependencies": {
+ "@ljharb/resumer": "^0.0.1",
+ "@ljharb/through": "^2.3.11",
+ "array.prototype.every": "^1.1.5",
+ "call-bind": "^1.0.5",
+ "deep-equal": "^2.2.3",
+ "defined": "^1.0.1",
+ "for-each": "^0.3.3",
+ "hasown": "^2.0.0",
+ "inherits": "^2.0.4",
+ "is-regex": "^1.1.4",
+ "mock-property": "^1.0.3",
+ "object-inspect": "^1.13.1",
+ "object-is": "^1.1.5",
+ "object-keys": "^1.1.1",
+ "string.prototype.trim": "^1.2.8"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "publishConfig": {
+ "ignore": []
+ },
+ "devDependencies": {
+ "in-publish": "^2.0.1",
+ "npmignore": "^0.3.0",
+ "safe-publish-latest": "^2.0.0"
+ }
+}
diff --git a/lib/results.js b/lib/results.js
index 3c44b01b..994b7ad7 100644
--- a/lib/results.js
+++ b/lib/results.js
@@ -1,236 +1,3 @@
'use strict';
-var defined = require('defined');
-var EventEmitter = require('events').EventEmitter;
-var inherits = require('inherits');
-var through = require('@ljharb/through');
-var resumer = require('@ljharb/resumer');
-var inspect = require('object-inspect');
-var callBound = require('call-bind/callBound');
-var hasOwn = require('hasown');
-var $exec = callBound('RegExp.prototype.exec');
-var $split = callBound('String.prototype.split');
-var $replace = callBound('String.prototype.replace');
-var $shift = callBound('Array.prototype.shift');
-var $push = callBound('Array.prototype.push');
-var yamlIndicators = /:|-|\?/;
-var nextTick = typeof setImmediate !== 'undefined'
- ? setImmediate
- : process.nextTick;
-
-function coalesceWhiteSpaces(str) {
- return $replace(String(str), /\s+/g, ' ');
-}
-
-function getNextTest(results) {
- if (!results._only) {
- return $shift(results.tests);
- }
-
- do {
- var t = $shift(results.tests);
- if (t && results._only === t) {
- return t;
- }
- } while (results.tests.length !== 0);
-
- return void undefined;
-}
-
-function invalidYaml(str) {
- return $exec(yamlIndicators, str) !== null;
-}
-
-function encodeResult(res, count, todoIsOK) {
- var output = '';
- output += (res.ok || (todoIsOK && res.todo) ? 'ok ' : 'not ok ') + count;
- output += res.name ? ' ' + coalesceWhiteSpaces(res.name) : '';
-
- if (res.skip) {
- output += ' # SKIP' + (typeof res.skip === 'string' ? ' ' + coalesceWhiteSpaces(res.skip) : '');
- } else if (res.todo) {
- output += ' # TODO' + (typeof res.todo === 'string' ? ' ' + coalesceWhiteSpaces(res.todo) : '');
- }
-
- output += '\n';
- if (res.ok) { return output; }
-
- var outer = ' ';
- var inner = outer + ' ';
- output += outer + '---\n';
- output += inner + 'operator: ' + res.operator + '\n';
-
- if (hasOwn(res, 'expected') || hasOwn(res, 'actual')) {
- var ex = inspect(res.expected, { depth: res.objectPrintDepth });
- var ac = inspect(res.actual, { depth: res.objectPrintDepth });
-
- if (Math.max(ex.length, ac.length) > 65 || invalidYaml(ex) || invalidYaml(ac)) {
- output += inner + 'expected: |-\n' + inner + ' ' + ex + '\n';
- output += inner + 'actual: |-\n' + inner + ' ' + ac + '\n';
- } else {
- output += inner + 'expected: ' + ex + '\n';
- output += inner + 'actual: ' + ac + '\n';
- }
- }
- if (res.at) {
- output += inner + 'at: ' + res.at + '\n';
- }
-
- var actualStack = res.actual && (typeof res.actual === 'object' || typeof res.actual === 'function') ? res.actual.stack : undefined;
- var errorStack = res.error && res.error.stack;
- var stack = defined(actualStack, errorStack);
- if (stack) {
- var lines = $split(String(stack), '\n');
- output += inner + 'stack: |-\n';
- for (var i = 0; i < lines.length; i++) {
- output += inner + ' ' + lines[i] + '\n';
- }
- }
-
- output += outer + '...\n';
- return output;
-}
-
-function Results(options) {
- if (!(this instanceof Results)) { return new Results(options); }
- var opts = (arguments.length > 0 ? arguments[0] : options) || {};
- this.count = 0;
- this.fail = 0;
- this.pass = 0;
- this.todo = 0;
- this._stream = through();
- this.tests = [];
- this._only = null;
- this._isRunning = false;
- this.todoIsOK = !!opts.todoIsOK;
-}
-
-inherits(Results, EventEmitter);
-
-Results.prototype.createStream = function (opts) {
- if (!opts) { opts = {}; }
- var self = this;
- var output;
- var testId = 0;
- if (opts.objectMode) {
- output = through();
- self.on('_push', function ontest(t, extra) {
- var id = testId++;
- t.once('prerun', function () {
- var row = {
- type: 'test',
- name: t.name,
- id: id,
- skip: t._skip,
- todo: t._todo
- };
- if (extra && hasOwn(extra, 'parent')) {
- row.parent = extra.parent;
- }
- output.queue(row);
- });
- t.on('test', function (st) {
- ontest(st, { parent: id });
- });
- t.on('result', function (res) {
- if (res && typeof res === 'object') {
- res.test = id;
- res.type = 'assert';
- }
- output.queue(res);
- });
- t.on('end', function () {
- output.queue({ type: 'end', test: id });
- });
- });
- self.on('done', function () { output.queue(null); });
- } else {
- output = resumer();
- output.queue('TAP version 13\n');
- self._stream.pipe(output);
- }
-
- if (!this._isRunning) {
- this._isRunning = true;
- nextTick(function next() {
- var t;
- while (t = getNextTest(self)) {
- t.run();
- if (!t.ended) {
- t.once('end', function () { nextTick(next); });
- return;
- }
- }
- self.emit('done');
- });
- }
-
- return output;
-};
-
-Results.prototype.push = function (t) {
- $push(this.tests, t);
- this._watch(t);
- this.emit('_push', t);
-};
-
-Results.prototype.only = function (t) {
- this._only = t;
-};
-
-Results.prototype._watch = function (t) {
- var self = this;
- function write(s) { self._stream.queue(s); }
-
- t.once('prerun', function () {
- var premsg = '';
- var postmsg = '';
- if (t._skip) {
- premsg = 'SKIP ';
- postmsg = typeof t._skip === 'string' ? ' ' + coalesceWhiteSpaces(t._skip) : '';
- } else if (t._todo) {
- premsg = 'TODO ';
- }
- write('# ' + premsg + coalesceWhiteSpaces(t.name) + postmsg + '\n');
- });
-
- t.on('result', function (res) {
- if (typeof res === 'string') {
- write('# ' + res + '\n');
- return;
- }
- write(encodeResult(res, self.count + 1, self.todoIsOK));
- self.count++;
-
- if (res.ok || res.todo) {
- self.pass++;
- } else {
- self.fail++;
- self.emit('fail');
- }
- });
-
- t.on('test', function (st) { self._watch(st); });
-};
-
-Results.prototype.close = function () {
- var self = this;
- if (self.closed) { self._stream.emit('error', new Error('ALREADY CLOSED')); }
- self.closed = true;
-
- function write(s) { self._stream.queue(s); }
-
- write('\n1..' + self.count + '\n');
- write('# tests ' + self.count + '\n');
- write('# pass ' + (self.pass + self.todo) + '\n');
- if (self.todo) { write('# todo ' + self.todo + '\n'); }
- if (self.fail) {
- write('# fail ' + self.fail + '\n');
- } else {
- write('\n# ok\n');
- }
-
- self._stream.queue(null);
-};
-
-module.exports = Results;
+module.exports = require('tape-lib/results');
diff --git a/lib/test.js b/lib/test.js
index 13a59450..ed593ddf 100644
--- a/lib/test.js
+++ b/lib/test.js
@@ -1,991 +1,3 @@
'use strict';
-var deepEqual = require('deep-equal');
-var defined = require('defined');
-var path = require('path');
-var inherits = require('inherits');
-var EventEmitter = require('events').EventEmitter;
-var hasOwn = require('hasown');
-var isRegExp = require('is-regex');
-var trim = require('string.prototype.trim');
-var callBind = require('call-bind');
-var callBound = require('call-bind/callBound');
-var forEach = require('for-each');
-var inspect = require('object-inspect');
-var is = require('object-is/polyfill')();
-var objectKeys = require('object-keys');
-var every = require('array.prototype.every');
-var mockProperty = require('mock-property');
-
-var isEnumerable = callBound('Object.prototype.propertyIsEnumerable');
-var toLowerCase = callBound('String.prototype.toLowerCase');
-var isProto = callBound('Object.prototype.isPrototypeOf');
-var $exec = callBound('RegExp.prototype.exec');
-var objectToString = callBound('Object.prototype.toString');
-var $split = callBound('String.prototype.split');
-var $replace = callBound('String.prototype.replace');
-var $strSlice = callBound('String.prototype.slice');
-var $push = callBound('Array.prototype.push');
-var $shift = callBound('Array.prototype.shift');
-var $slice = callBound('Array.prototype.slice');
-
-var nextTick = typeof setImmediate !== 'undefined'
- ? setImmediate
- : process.nextTick;
-var safeSetTimeout = setTimeout;
-var safeClearTimeout = clearTimeout;
-
-var isErrorConstructor = isProto(Error, TypeError) // IE 8 is `false` here
- ? function isErrorConstructor(C) {
- return isProto(Error, C);
- }
- : function isErrorConstructor(C) {
- return isProto(Error, C)
- || isProto(TypeError, C)
- || isProto(RangeError, C)
- || isProto(SyntaxError, C)
- || isProto(ReferenceError, C)
- || isProto(EvalError, C)
- || isProto(URIError, C);
- };
-
-// eslint-disable-next-line no-unused-vars
-function getTestArgs(name_, opts_, cb_) {
- var name = '(anonymous)';
- var opts = {};
- var cb;
-
- for (var i = 0; i < arguments.length; i++) {
- var arg = arguments[i];
- if (typeof arg === 'string') {
- name = arg;
- } else if (typeof arg === 'object') {
- opts = arg || opts;
- } else if (typeof arg === 'function') {
- cb = arg;
- }
- }
- return {
- name: name,
- opts: opts,
- cb: cb
- };
-}
-
-function Test(name_, opts_, cb_) {
- if (!(this instanceof Test)) {
- return new Test(name_, opts_, cb_);
- }
-
- var args = getTestArgs(name_, opts_, cb_);
-
- this.readable = true;
- this.name = args.name || '(anonymous)';
- this.assertCount = 0;
- this.pendingCount = 0;
- this._skip = args.opts.skip || false;
- this._todo = args.opts.todo || false;
- this._timeout = args.opts.timeout;
- this._plan = undefined;
- this._cb = args.cb;
- this.ended = false;
- this._progeny = [];
- this._teardown = [];
- this._ok = true;
- this._objectPrintDepth = 5;
- var depthEnvVar = process.env.NODE_TAPE_OBJECT_PRINT_DEPTH;
- if (args.opts.objectPrintDepth) {
- this._objectPrintDepth = args.opts.objectPrintDepth;
- } else if (depthEnvVar) {
- if (toLowerCase(depthEnvVar) === 'infinity') {
- this._objectPrintDepth = Infinity;
- } else {
- this._objectPrintDepth = depthEnvVar;
- }
- }
-
- for (var prop in this) {
- if (typeof this[prop] === 'function') {
- this[prop] = callBind(this[prop], this);
- }
- }
-}
-
-inherits(Test, EventEmitter);
-
-Test.prototype.run = function run() {
- this.emit('prerun');
- if (!this._cb || this._skip) {
- this._end();
- return;
- }
- if (this._timeout != null) {
- this.timeoutAfter(this._timeout);
- }
-
- var callbackReturn = this._cb(this);
-
- if (
- typeof Promise === 'function'
- && callbackReturn
- && typeof callbackReturn.then === 'function'
- ) {
- var self = this;
- Promise.resolve(callbackReturn).then(
- function onResolve() {
- if (!self.calledEnd) {
- self.end();
- }
- },
- function onError(err) {
- if (err instanceof Error || objectToString(err) === '[object Error]') {
- self.ifError(err);
- } else {
- self.fail(err);
- }
- self.end();
- }
- );
- return;
- }
-
- this.emit('run');
-};
-
-Test.prototype.test = function test(name, opts, cb) {
- var self = this;
- var t = new Test(name, opts, cb);
- $push(this._progeny, t);
- this.pendingCount++;
- this.emit('test', t);
- t.on('prerun', function () {
- self.assertCount++;
- });
-
- if (!self._pendingAsserts()) {
- nextTick(function () {
- self._end();
- });
- }
-
- nextTick(function () {
- if (!self._plan && self.pendingCount == self._progeny.length) {
- self._end();
- }
- });
-};
-
-Test.prototype.comment = function comment(msg) {
- var that = this;
- forEach($split(trim(msg), '\n'), function (aMsg) {
- that.emit('result', $replace(trim(aMsg), /^#\s*/, ''));
- });
-};
-
-Test.prototype.plan = function plan(n) {
- this._plan = n;
- this.emit('plan', n);
-};
-
-Test.prototype.timeoutAfter = function timeoutAfter(ms) {
- if (!ms) { throw new Error('timeoutAfter requires a timespan'); }
- var self = this;
- var timeout = safeSetTimeout(function () {
- self.fail(self.name + ' timed out after ' + ms + 'ms');
- self.end();
- }, ms);
- this.once('end', function () {
- safeClearTimeout(timeout);
- });
-};
-
-Test.prototype.end = function end(err) {
- if (arguments.length >= 1 && !!err) {
- this.ifError(err);
- }
-
- if (this.calledEnd) {
- this.fail('.end() already called');
- }
- this.calledEnd = true;
- this._end();
-};
-
-Test.prototype.teardown = function teardown(fn) {
- if (typeof fn !== 'function') {
- this.fail('teardown: ' + inspect(fn) + ' is not a function');
- } else {
- this._teardown.push(fn);
- }
-};
-
-function wrapFunction(original) {
- if (typeof original !== 'undefined' && typeof original !== 'function') {
- throw new TypeError('`original` must be a function or `undefined`');
- }
-
- var bound = original && callBind.apply(original);
-
- var calls = [];
-
- var wrapObject = {
- __proto__: null,
- wrapped: function wrapped() {
- var args = $slice(arguments);
- var completed = false;
- try {
- var returned = bound ? bound(this, arguments) : void undefined;
- $push(calls, { args: args, receiver: this, returned: returned });
- completed = true;
- return returned;
- } finally {
- if (!completed) {
- $push(calls, { args: args, receiver: this, threw: true });
- }
- }
- },
- calls: calls,
- results: function results() {
- try {
- return calls;
- } finally {
- calls = [];
- wrapObject.calls = calls;
- }
- }
- };
- return wrapObject;
-}
-
-Test.prototype.capture = function capture(obj, method) {
- if (!obj || (typeof obj !== 'object' && typeof obj !== 'function')) {
- throw new TypeError('`obj` must be an object');
- }
- if (typeof method !== 'string' && typeof method !== 'symbol') {
- throw new TypeError('`method` must be a string or a symbol');
- }
- var implementation = arguments.length > 2 ? arguments[2] : void undefined;
- if (typeof implementation !== 'undefined' && typeof implementation !== 'function') {
- throw new TypeError('`implementation`, if provided, must be a function');
- }
-
- var wrapper = wrapFunction(implementation);
- var restore = mockProperty(obj, method, { value: wrapper.wrapped });
- this.teardown(restore);
-
- wrapper.results.restore = restore;
-
- return wrapper.results;
-};
-
-Test.prototype.captureFn = function captureFn(original) {
- if (typeof original !== 'function') {
- throw new TypeError('`original` must be a function');
- }
-
- var wrapObject = wrapFunction(original);
- wrapObject.wrapped.calls = wrapObject.calls;
- return wrapObject.wrapped;
-};
-
-Test.prototype.intercept = function intercept(obj, property) {
- if (!obj || (typeof obj !== 'object' && typeof obj !== 'function')) {
- throw new TypeError('`obj` must be an object');
- }
- if (typeof property !== 'string' && typeof property !== 'symbol') {
- throw new TypeError('`property` must be a string or a symbol');
- }
- var desc = arguments.length > 2 ? arguments[2] : { __proto__: null };
- if (typeof desc !== 'undefined' && (!desc || typeof desc !== 'object')) {
- throw new TypeError('`desc`, if provided, must be an object');
- }
- if ('configurable' in desc && !desc.configurable) {
- throw new TypeError('`desc.configurable`, if provided, must be `true`, so that the interception can be restored later');
- }
- var isData = 'writable' in desc || 'value' in desc;
- var isAccessor = 'get' in desc || 'set' in desc;
- if (isData && isAccessor) {
- throw new TypeError('`value` and `writable` can not be mixed with `get` and `set`');
- }
- var strictMode = arguments.length > 3 ? arguments[3] : true;
- if (typeof strictMode !== 'boolean') {
- throw new TypeError('`strictMode`, if provided, must be a boolean');
- }
-
- var calls = [];
- var getter = desc.get && callBind.apply(desc.get);
- var setter = desc.set && callBind.apply(desc.set);
- var value = !isAccessor ? desc.value : void undefined;
- var writable = !!desc.writable;
-
- function getInterceptor() {
- var args = $slice(arguments);
- if (isAccessor) {
- if (getter) {
- var completed = false;
- try {
- var returned = getter(this, arguments);
- completed = true;
- $push(calls, { type: 'get', success: true, value: returned, args: args, receiver: this });
- return returned;
- } finally {
- if (!completed) {
- $push(calls, { type: 'get', success: false, threw: true, args: args, receiver: this });
- }
- }
- }
- }
- $push(calls, { type: 'get', success: true, value: value, args: args, receiver: this });
- return value;
- }
-
- function setInterceptor(v) {
- var args = $slice(arguments);
- if (isAccessor && setter) {
- var completed = false;
- try {
- var returned = setter(this, arguments);
- completed = true;
- $push(calls, { type: 'set', success: true, value: v, args: args, receiver: this });
- return returned;
- } finally {
- if (!completed) {
- $push(calls, { type: 'set', success: false, threw: true, args: args, receiver: this });
- }
- }
- }
- var canSet = isAccessor || writable;
- if (canSet) {
- value = v;
- }
- $push(calls, { type: 'set', success: !!canSet, value: value, args: args, receiver: this });
-
- if (!canSet && strictMode) {
- throw new TypeError('Cannot assign to read only property `' + inspect(property) + '` of object `' + inspect(obj) + '`');
- }
- return value;
- }
-
- var restore = mockProperty(obj, property, {
- nonEnumerable: !!desc.enumerable,
- get: getInterceptor,
- set: setInterceptor
- });
- this.teardown(restore);
-
- function results() {
- try {
- return calls;
- } finally {
- calls = [];
- }
- }
- results.restore = restore;
-
- return results;
-};
-
-Test.prototype._end = function _end(err) {
- var self = this;
-
- if (!this._cb && !this._todo && !this._skip) {
- this.fail('# TODO ' + this.name);
- }
-
- if (this._progeny.length) {
- var t = $shift(this._progeny);
- t.on('end', function () { self._end(); });
- t.run();
- return;
- }
-
- function completeEnd() {
- if (!self.ended) { self.emit('end'); }
- var pendingAsserts = self._pendingAsserts();
- if (!self._planError && self._plan !== undefined && pendingAsserts) {
- self._planError = true;
- self.fail('plan != count', {
- expected: self._plan,
- actual: self.assertCount
- });
- }
- self.ended = true;
- }
-
- function next() {
- if (self._teardown.length === 0) {
- completeEnd();
- return;
- }
- var fn = self._teardown.shift();
- var res;
- try {
- res = fn();
- } catch (e) {
- self.fail(e);
- }
- if (res && typeof res.then === 'function') {
- res.then(next, function (_err) {
- // TODO: wth?
- err = err || _err;
- });
- } else {
- next();
- }
- }
-
- next();
-};
-
-Test.prototype._exit = function _exit() {
- if (this._plan !== undefined && !this._planError && this.assertCount !== this._plan) {
- this._planError = true;
- this.fail('plan != count', {
- expected: this._plan,
- actual: this.assertCount,
- exiting: true
- });
- } else if (!this.ended) {
- this.fail('test exited without ending: ' + this.name, {
- exiting: true
- });
- }
-};
-
-Test.prototype._pendingAsserts = function _pendingAsserts() {
- if (this._plan === undefined) {
- return 1;
- }
- return this._plan - (this._progeny.length + this.assertCount);
-};
-
-Test.prototype._assert = function assert(ok, opts) {
- var self = this;
- var extra = opts.extra || {};
-
- var actualOK = !!ok || !!extra.skip;
-
- var name = defined(extra.message, opts.message, '(unnamed assert)');
- if (this.calledEnd && opts.operator !== 'fail') {
- this.fail('.end() already called: ' + name);
- return;
- }
-
- var res = {
- id: self.assertCount++,
- ok: actualOK,
- skip: defined(extra.skip, opts.skip),
- todo: defined(extra.todo, opts.todo, self._todo),
- name: name,
- operator: defined(extra.operator, opts.operator),
- objectPrintDepth: self._objectPrintDepth
- };
- if (hasOwn(opts, 'actual') || hasOwn(extra, 'actual')) {
- res.actual = defined(extra.actual, opts.actual);
- }
- if (hasOwn(opts, 'expected') || hasOwn(extra, 'expected')) {
- res.expected = defined(extra.expected, opts.expected);
- }
- this._ok = !!(this._ok && actualOK);
-
- if (!actualOK && !res.todo) {
- res.error = defined(extra.error, opts.error, new Error(res.name));
- }
-
- if (!actualOK) {
- var e = new Error('exception');
- var err = $split(e.stack || '', '\n');
- var tapeDir = __dirname + path.sep;
- var index = path.sep + 'index.js';
-
- for (var i = 0; i < err.length; i++) {
- /*
- Stack trace lines may resemble one of the following.
- We need to correctly extract a function name (if any) and path / line number for each line.
-
- at myFunction (/path/to/file.js:123:45)
- at myFunction (/path/to/file.other-ext:123:45)
- at myFunction (/path to/file.js:123:45)
- at myFunction (C:\path\to\file.js:123:45)
- at myFunction (/path/to/file.js:123)
- at Test. (/path/to/file.js:123:45)
- at Test.bound [as run] (/path/to/file.js:123:45)
- at /path/to/file.js:123:45
-
- Regex has three parts. First is non-capturing group for 'at ' (plus anything preceding it).
-
- /^(?:[^\s]*\s*\bat\s+)/
-
- Second captures function call description (optional).
- This is not necessarily a valid JS function name, but just what the stack trace is using to represent a function call.
- It may look like `` or 'Test.bound [as run]'.
-
- For our purposes, we assume that, if there is a function name, it's everything leading up to the first open parentheses (trimmed) before our pathname.
-
- /(?:(.*)\s+\()?/
-
- Last part captures file path plus line no (and optional column no).
-
- /((?:[/\\]|[a-zA-Z]:\\)[^:\)]+:(\d+)(?::(\d+))?)\)?/
-
- In the future, if node supports more ESM URL protocols than `file`, the `file:` below will need to be expanded.
- */
- var re = /^(?:[^\s]*\s*\bat\s+)(?:(.*)\s+\()?((?:[/\\]|[a-zA-Z]:\\|file:\/\/)[^:)]+:(\d+)(?::(\d+))?)\)?$/;
- // first tokenize the PWD, then tokenize tape
- var lineWithTokens = $replace(
- $replace(
- err[i],
- process.cwd(),
- path.sep + '$CWD'
- ),
- tapeDir,
- path.sep + '$TEST' + path.sep
- );
- var m = re.exec(lineWithTokens);
-
- if (!m) {
- continue;
- }
-
- var callDescription = m[1] || '';
- // first untokenize tape, and then untokenize the PWD, then strip the line/column
- var filePath = $replace(
- $replace(
- $replace(m[2], path.sep + '$TEST' + path.sep, tapeDir),
- path.sep + '$CWD',
- process.cwd()
- ),
- /:\d+:\d+$/,
- ''
- );
-
- if (
- $strSlice(filePath, 0, tapeDir.length) === tapeDir
- && $strSlice(filePath, -index.length) !== index // index.js is inside lib/
- ) {
- continue;
- }
-
- // Function call description may not (just) be a function name.
- // Try to extract function name by looking at first "word" only.
- res.functionName = $split(callDescription, /\s+/)[0];
- res.file = filePath;
- res.line = Number(m[3]);
- if (m[4]) { res.column = Number(m[4]); }
-
- res.at = callDescription + ' (' + filePath + ':' + res.line + (res.column ? ':' + res.column : '') + ')';
- break;
- }
- }
-
- self.emit('result', res);
-
- var pendingAsserts = self._pendingAsserts();
- if (!pendingAsserts) {
- if (extra.exiting) {
- self._end();
- } else {
- nextTick(function () {
- self._end();
- });
- }
- }
-
- if (!self._planError && pendingAsserts < 0) {
- self._planError = true;
- self.fail('plan != count', {
- expected: self._plan,
- actual: self._plan - pendingAsserts
- });
- }
-};
-
-Test.prototype.fail = function fail(msg, extra) {
- this._assert(false, {
- message: msg,
- operator: 'fail',
- extra: extra
- });
-};
-
-Test.prototype.pass = function pass(msg, extra) {
- this._assert(true, {
- message: msg,
- operator: 'pass',
- extra: extra
- });
-};
-
-Test.prototype.skip = function skip(msg, extra) {
- this._assert(true, {
- message: msg,
- operator: 'skip',
- skip: true,
- extra: extra
- });
-};
-
-var testAssert = function assert(value, msg, extra) { // eslint-disable-line func-style
- this._assert(value, {
- message: defined(msg, 'should be truthy'),
- operator: 'ok',
- expected: true,
- actual: value,
- extra: extra
- });
-};
-Test.prototype.ok
-= Test.prototype['true']
-= Test.prototype.assert
-= testAssert;
-
-function notOK(value, msg, extra) {
- this._assert(!value, {
- message: defined(msg, 'should be falsy'),
- operator: 'notOk',
- expected: false,
- actual: value,
- extra: extra
- });
-}
-Test.prototype.notOk
-= Test.prototype['false']
-= Test.prototype.notok
-= notOK;
-
-function error(err, msg, extra) {
- this._assert(!err, {
- message: defined(msg, String(err)),
- operator: 'error',
- error: err,
- extra: extra
- });
-}
-Test.prototype.error
-= Test.prototype.ifError
-= Test.prototype.ifErr
-= Test.prototype.iferror
-= error;
-
-function strictEqual(a, b, msg, extra) {
- if (arguments.length < 2) {
- throw new TypeError('two arguments must be provided to compare');
- }
- this._assert(is(a, b), {
- message: defined(msg, 'should be strictly equal'),
- operator: 'equal',
- actual: a,
- expected: b,
- extra: extra
- });
-}
-Test.prototype.equal
-= Test.prototype.equals
-= Test.prototype.isEqual
-= Test.prototype.strictEqual
-= Test.prototype.strictEquals
-= Test.prototype.is
-= strictEqual;
-
-function notStrictEqual(a, b, msg, extra) {
- if (arguments.length < 2) {
- throw new TypeError('two arguments must be provided to compare');
- }
- this._assert(!is(a, b), {
- message: defined(msg, 'should not be strictly equal'),
- operator: 'notEqual',
- actual: a,
- expected: b,
- extra: extra
- });
-}
-
-Test.prototype.notEqual
-= Test.prototype.notEquals
-= Test.prototype.isNotEqual
-= Test.prototype.doesNotEqual
-= Test.prototype.isInequal
-= Test.prototype.notStrictEqual
-= Test.prototype.notStrictEquals
-= Test.prototype.isNot
-= Test.prototype.not
-= notStrictEqual;
-
-function looseEqual(a, b, msg, extra) {
- if (arguments.length < 2) {
- throw new TypeError('two arguments must be provided to compare');
- }
- this._assert(a == b, {
- message: defined(msg, 'should be loosely equal'),
- operator: 'looseEqual',
- actual: a,
- expected: b,
- extra: extra
- });
-}
-
-Test.prototype.looseEqual
-= Test.prototype.looseEquals
-= looseEqual;
-
-function notLooseEqual(a, b, msg, extra) {
- if (arguments.length < 2) {
- throw new TypeError('two arguments must be provided to compare');
- }
- this._assert(a != b, {
- message: defined(msg, 'should not be loosely equal'),
- operator: 'notLooseEqual',
- actual: a,
- expected: b,
- extra: extra
- });
-}
-Test.prototype.notLooseEqual
-= Test.prototype.notLooseEquals
-= notLooseEqual;
-
-function tapeDeepEqual(a, b, msg, extra) {
- if (arguments.length < 2) {
- throw new TypeError('two arguments must be provided to compare');
- }
- this._assert(deepEqual(a, b, { strict: true }), {
- message: defined(msg, 'should be deeply equivalent'),
- operator: 'deepEqual',
- actual: a,
- expected: b,
- extra: extra
- });
-}
-Test.prototype.deepEqual
-= Test.prototype.deepEquals
-= Test.prototype.isEquivalent
-= Test.prototype.same
-= tapeDeepEqual;
-
-function notDeepEqual(a, b, msg, extra) {
- if (arguments.length < 2) {
- throw new TypeError('two arguments must be provided to compare');
- }
- this._assert(!deepEqual(a, b, { strict: true }), {
- message: defined(msg, 'should not be deeply equivalent'),
- operator: 'notDeepEqual',
- actual: a,
- expected: b,
- extra: extra
- });
-}
-Test.prototype.notDeepEqual
-= Test.prototype.notDeepEquals
-= Test.prototype.notEquivalent
-= Test.prototype.notDeeply
-= Test.prototype.notSame
-= Test.prototype.isNotDeepEqual
-= Test.prototype.isNotDeeply
-= Test.prototype.isNotEquivalent
-= Test.prototype.isInequivalent
-= notDeepEqual;
-
-function deepLooseEqual(a, b, msg, extra) {
- if (arguments.length < 2) {
- throw new TypeError('two arguments must be provided to compare');
- }
- this._assert(deepEqual(a, b), {
- message: defined(msg, 'should be loosely deeply equivalent'),
- operator: 'deepLooseEqual',
- actual: a,
- expected: b,
- extra: extra
- });
-}
-
-Test.prototype.deepLooseEqual
-= deepLooseEqual;
-
-function notDeepLooseEqual(a, b, msg, extra) {
- if (arguments.length < 2) {
- throw new TypeError('two arguments must be provided to compare');
- }
- this._assert(!deepEqual(a, b), {
- message: defined(msg, 'should not be loosely deeply equivalent'),
- operator: 'notDeepLooseEqual',
- actual: a,
- expected: b,
- extra: extra
- });
-}
-Test.prototype.notDeepLooseEqual
-= notDeepLooseEqual;
-
-Test.prototype['throws'] = function (fn, expected, msg, extra) {
- if (typeof expected === 'string') {
- msg = expected;
- expected = undefined;
- }
-
- var caught;
-
- try {
- fn();
- } catch (err) {
- caught = { error: err };
- if (Object(err) === err && 'message' in err && (!isEnumerable(err, 'message') || !hasOwn(err, 'message'))) {
- try {
- var message = err.message;
- delete err.message;
- err.message = message;
- } catch (e) { /**/ }
- }
- }
-
- var passed = caught;
-
- if (caught) {
- if (typeof expected === 'string' && caught.error && caught.error.message === expected) {
- throw new TypeError('The "error/message" argument is ambiguous. The error message ' + inspect(expected) + ' is identical to the message.');
- }
- if (typeof expected === 'function') {
- if (typeof expected.prototype !== 'undefined' && caught.error instanceof expected) {
- passed = true;
- } else if (isErrorConstructor(expected)) {
- passed = false;
- } else {
- passed = expected.call({}, caught.error) === true;
- }
- } else if (isRegExp(expected)) {
- passed = $exec(expected, caught.error) !== null;
- expected = inspect(expected);
- } else if (expected && typeof expected === 'object') { // Handle validation objects.
- if (caught.error && typeof caught.error === 'object') {
- var keys = objectKeys(expected);
- // Special handle errors to make sure the name and the message are compared as well.
- if (expected instanceof Error) {
- $push(keys, 'name', 'message');
- } else if (keys.length === 0) {
- throw new TypeError('`throws` validation object must not be empty');
- }
- passed = every(keys, function (key) {
- if (typeof caught.error[key] === 'string' && isRegExp(expected[key]) && $exec(expected[key], caught.error[key]) !== null) {
- return true;
- }
- if (key in caught.error && deepEqual(caught.error[key], expected[key], { strict: true })) {
- return true;
- }
- return false;
- });
- } else {
- passed = false;
- }
- }
- }
-
- this._assert(!!passed, {
- message: defined(msg, 'should throw'),
- operator: 'throws',
- actual: caught && caught.error,
- expected: expected,
- error: !passed && caught && caught.error,
- extra: extra
- });
-};
-
-Test.prototype.doesNotThrow = function doesNotThrow(fn, expected, msg, extra) {
- if (typeof expected === 'string') {
- msg = expected;
- expected = undefined;
- }
- var caught;
- try {
- fn();
- } catch (err) {
- caught = { error: err };
- }
- this._assert(!caught, {
- message: defined(msg, 'should not throw'),
- operator: 'throws',
- actual: caught && caught.error,
- expected: expected,
- error: caught && caught.error,
- extra: extra
- });
-};
-
-Test.prototype.match = function match(string, regexp, msg, extra) {
- if (!isRegExp(regexp)) {
- this._assert(false, {
- message: defined(msg, 'The "regexp" argument must be an instance of RegExp. Received type ' + typeof regexp + ' (' + inspect(regexp) + ')'),
- operator: 'match',
- actual: objectToString(regexp),
- expected: '[object RegExp]',
- extra: extra
- });
- } else if (typeof string !== 'string') {
- this._assert(false, {
- message: defined(msg, 'The "string" argument must be of type string. Received type ' + typeof string + ' (' + inspect(string) + ')'),
- operator: 'match',
- actual: string === null ? null : typeof string,
- expected: 'string',
- extra: extra
- });
- } else {
- var matches = $exec(regexp, string) !== null;
- var message = defined(
- msg,
- 'The input ' + (matches ? 'matched' : 'did not match') + ' the regular expression ' + inspect(regexp) + '. Input: ' + inspect(string)
- );
- this._assert(matches, {
- message: message,
- operator: 'match',
- actual: string,
- expected: regexp,
- extra: extra
- });
- }
-};
-
-Test.prototype.doesNotMatch = function doesNotMatch(string, regexp, msg, extra) {
- if (!isRegExp(regexp)) {
- this._assert(false, {
- message: defined(msg, 'The "regexp" argument must be an instance of RegExp. Received type ' + typeof regexp + ' (' + inspect(regexp) + ')'),
- operator: 'doesNotMatch',
- actual: objectToString(regexp),
- expected: '[object RegExp]',
- extra: extra
- });
- } else if (typeof string !== 'string') {
- this._assert(false, {
- message: defined(msg, 'The "string" argument must be of type string. Received type ' + typeof string + ' (' + inspect(string) + ')'),
- operator: 'doesNotMatch',
- actual: string === null ? null : typeof string,
- expected: 'string',
- extra: extra
- });
- } else {
- var matches = $exec(regexp, string) !== null;
- var message = defined(
- msg,
- 'The input ' + (matches ? 'was expected to not match' : 'did not match') + ' the regular expression ' + inspect(regexp) + '. Input: ' + inspect(string)
- );
- this._assert(!matches, {
- message: message,
- operator: 'doesNotMatch',
- actual: string,
- expected: regexp,
- extra: extra
- });
- }
-};
-
-Test.prototype.assertion = function assertion(fn) {
- return callBind.apply(fn)(this, $slice(arguments, 1));
-};
-
-// eslint-disable-next-line no-unused-vars
-Test.skip = function skip(name_, _opts, _cb) {
- var args = getTestArgs.apply(null, arguments);
- args.opts.skip = true;
- return new Test(args.name, args.opts, args.cb);
-};
-
-module.exports = Test;
-
-// vim: set softtabstop=4 shiftwidth=4:
+module.exports = require('tape-lib/test');
diff --git a/package.json b/package.json
index 49066f98..4cea9f90 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "tape",
"version": "5.8.1",
- "description": "tap-producing test harness for node and browsers",
+ "description": "TAP-producing test harness for node and browsers",
"main": "index.js",
"browser": {
"fs": false
@@ -41,10 +41,12 @@
"object-keys": "^1.1.1",
"object.assign": "^4.1.5",
"resolve": "^2.0.0-next.5",
- "string.prototype.trim": "^1.2.9"
+ "string.prototype.trim": "^1.2.9",
+ "tape-lib": "^0.0.0-placeholder"
},
"devDependencies": {
"@ljharb/eslint-config": "^21.1.1",
+ "@ljharb/through": "^2.3.11",
"array.prototype.flatmap": "^1.3.2",
"aud": "^2.0.4",
"auto-changelog": "^2.4.0",
@@ -55,11 +57,13 @@
"es-value-fixtures": "^1.4.2",
"eslint": "=8.8.0",
"falafel": "^2.2.5",
+ "in-publish": "^2.0.1",
"jackspeak": "=2.1.1",
"js-yaml": "^3.14.0",
"npm-run-posix-or-windows": "^2.0.2",
"npmignore": "^0.3.1",
"nyc": "^10.3.2",
+ "object-inspect": "^1.13.1",
"safe-publish-latest": "^2.0.0",
"semver": "^6.3.1",
"tap": "^8.0.1",
@@ -122,7 +126,8 @@
},
"publishConfig": {
"ignore": [
- ".github/workflows"
+ ".github/workflows",
+ "packages"
]
}
}
diff --git a/packages/lib/default_stream.js b/packages/lib/default_stream.js
new file mode 100644
index 00000000..ffc2ad11
--- /dev/null
+++ b/packages/lib/default_stream.js
@@ -0,0 +1,52 @@
+'use strict';
+
+var through = require('@ljharb/through');
+var fs = require('fs');
+
+module.exports = function () {
+ var line = '';
+ var stream = through(write, flush);
+ return stream;
+
+ function write(buf) {
+ if (
+ buf == null // eslint-disable-line eqeqeq
+ || (Object(buf) !== buf && typeof buf !== 'string')
+ ) {
+ flush();
+ return;
+ }
+ for (var i = 0; i < buf.length; i++) {
+ var c = typeof buf === 'string'
+ ? buf.charAt(i)
+ : String.fromCharCode(buf[i]);
+ if (c === '\n') {
+ flush();
+ } else {
+ line += c;
+ }
+ }
+ }
+
+ function flush() {
+ if (fs.writeSync && (/^win/).test(process.platform)) {
+ try {
+ fs.writeSync(1, line + '\n');
+ } catch (e) {
+ stream.emit('error', e);
+ }
+ } else {
+ try {
+ if (typeof console !== 'undefined' && console.log) { // eslint-disable-line no-console
+ console.log(line); // eslint-disable-line no-console
+ } else if (typeof document !== 'undefined') {
+ // for IE < 9
+ document.body.innerHTML += line + '
';
+ }
+ } catch (e) {
+ stream.emit('error', e);
+ }
+ }
+ line = '';
+ }
+};
diff --git a/lib/index.js b/packages/lib/index.js
similarity index 100%
rename from lib/index.js
rename to packages/lib/index.js
diff --git a/packages/lib/results.js b/packages/lib/results.js
new file mode 100644
index 00000000..3c44b01b
--- /dev/null
+++ b/packages/lib/results.js
@@ -0,0 +1,236 @@
+'use strict';
+
+var defined = require('defined');
+var EventEmitter = require('events').EventEmitter;
+var inherits = require('inherits');
+var through = require('@ljharb/through');
+var resumer = require('@ljharb/resumer');
+var inspect = require('object-inspect');
+var callBound = require('call-bind/callBound');
+var hasOwn = require('hasown');
+var $exec = callBound('RegExp.prototype.exec');
+var $split = callBound('String.prototype.split');
+var $replace = callBound('String.prototype.replace');
+var $shift = callBound('Array.prototype.shift');
+var $push = callBound('Array.prototype.push');
+var yamlIndicators = /:|-|\?/;
+var nextTick = typeof setImmediate !== 'undefined'
+ ? setImmediate
+ : process.nextTick;
+
+function coalesceWhiteSpaces(str) {
+ return $replace(String(str), /\s+/g, ' ');
+}
+
+function getNextTest(results) {
+ if (!results._only) {
+ return $shift(results.tests);
+ }
+
+ do {
+ var t = $shift(results.tests);
+ if (t && results._only === t) {
+ return t;
+ }
+ } while (results.tests.length !== 0);
+
+ return void undefined;
+}
+
+function invalidYaml(str) {
+ return $exec(yamlIndicators, str) !== null;
+}
+
+function encodeResult(res, count, todoIsOK) {
+ var output = '';
+ output += (res.ok || (todoIsOK && res.todo) ? 'ok ' : 'not ok ') + count;
+ output += res.name ? ' ' + coalesceWhiteSpaces(res.name) : '';
+
+ if (res.skip) {
+ output += ' # SKIP' + (typeof res.skip === 'string' ? ' ' + coalesceWhiteSpaces(res.skip) : '');
+ } else if (res.todo) {
+ output += ' # TODO' + (typeof res.todo === 'string' ? ' ' + coalesceWhiteSpaces(res.todo) : '');
+ }
+
+ output += '\n';
+ if (res.ok) { return output; }
+
+ var outer = ' ';
+ var inner = outer + ' ';
+ output += outer + '---\n';
+ output += inner + 'operator: ' + res.operator + '\n';
+
+ if (hasOwn(res, 'expected') || hasOwn(res, 'actual')) {
+ var ex = inspect(res.expected, { depth: res.objectPrintDepth });
+ var ac = inspect(res.actual, { depth: res.objectPrintDepth });
+
+ if (Math.max(ex.length, ac.length) > 65 || invalidYaml(ex) || invalidYaml(ac)) {
+ output += inner + 'expected: |-\n' + inner + ' ' + ex + '\n';
+ output += inner + 'actual: |-\n' + inner + ' ' + ac + '\n';
+ } else {
+ output += inner + 'expected: ' + ex + '\n';
+ output += inner + 'actual: ' + ac + '\n';
+ }
+ }
+ if (res.at) {
+ output += inner + 'at: ' + res.at + '\n';
+ }
+
+ var actualStack = res.actual && (typeof res.actual === 'object' || typeof res.actual === 'function') ? res.actual.stack : undefined;
+ var errorStack = res.error && res.error.stack;
+ var stack = defined(actualStack, errorStack);
+ if (stack) {
+ var lines = $split(String(stack), '\n');
+ output += inner + 'stack: |-\n';
+ for (var i = 0; i < lines.length; i++) {
+ output += inner + ' ' + lines[i] + '\n';
+ }
+ }
+
+ output += outer + '...\n';
+ return output;
+}
+
+function Results(options) {
+ if (!(this instanceof Results)) { return new Results(options); }
+ var opts = (arguments.length > 0 ? arguments[0] : options) || {};
+ this.count = 0;
+ this.fail = 0;
+ this.pass = 0;
+ this.todo = 0;
+ this._stream = through();
+ this.tests = [];
+ this._only = null;
+ this._isRunning = false;
+ this.todoIsOK = !!opts.todoIsOK;
+}
+
+inherits(Results, EventEmitter);
+
+Results.prototype.createStream = function (opts) {
+ if (!opts) { opts = {}; }
+ var self = this;
+ var output;
+ var testId = 0;
+ if (opts.objectMode) {
+ output = through();
+ self.on('_push', function ontest(t, extra) {
+ var id = testId++;
+ t.once('prerun', function () {
+ var row = {
+ type: 'test',
+ name: t.name,
+ id: id,
+ skip: t._skip,
+ todo: t._todo
+ };
+ if (extra && hasOwn(extra, 'parent')) {
+ row.parent = extra.parent;
+ }
+ output.queue(row);
+ });
+ t.on('test', function (st) {
+ ontest(st, { parent: id });
+ });
+ t.on('result', function (res) {
+ if (res && typeof res === 'object') {
+ res.test = id;
+ res.type = 'assert';
+ }
+ output.queue(res);
+ });
+ t.on('end', function () {
+ output.queue({ type: 'end', test: id });
+ });
+ });
+ self.on('done', function () { output.queue(null); });
+ } else {
+ output = resumer();
+ output.queue('TAP version 13\n');
+ self._stream.pipe(output);
+ }
+
+ if (!this._isRunning) {
+ this._isRunning = true;
+ nextTick(function next() {
+ var t;
+ while (t = getNextTest(self)) {
+ t.run();
+ if (!t.ended) {
+ t.once('end', function () { nextTick(next); });
+ return;
+ }
+ }
+ self.emit('done');
+ });
+ }
+
+ return output;
+};
+
+Results.prototype.push = function (t) {
+ $push(this.tests, t);
+ this._watch(t);
+ this.emit('_push', t);
+};
+
+Results.prototype.only = function (t) {
+ this._only = t;
+};
+
+Results.prototype._watch = function (t) {
+ var self = this;
+ function write(s) { self._stream.queue(s); }
+
+ t.once('prerun', function () {
+ var premsg = '';
+ var postmsg = '';
+ if (t._skip) {
+ premsg = 'SKIP ';
+ postmsg = typeof t._skip === 'string' ? ' ' + coalesceWhiteSpaces(t._skip) : '';
+ } else if (t._todo) {
+ premsg = 'TODO ';
+ }
+ write('# ' + premsg + coalesceWhiteSpaces(t.name) + postmsg + '\n');
+ });
+
+ t.on('result', function (res) {
+ if (typeof res === 'string') {
+ write('# ' + res + '\n');
+ return;
+ }
+ write(encodeResult(res, self.count + 1, self.todoIsOK));
+ self.count++;
+
+ if (res.ok || res.todo) {
+ self.pass++;
+ } else {
+ self.fail++;
+ self.emit('fail');
+ }
+ });
+
+ t.on('test', function (st) { self._watch(st); });
+};
+
+Results.prototype.close = function () {
+ var self = this;
+ if (self.closed) { self._stream.emit('error', new Error('ALREADY CLOSED')); }
+ self.closed = true;
+
+ function write(s) { self._stream.queue(s); }
+
+ write('\n1..' + self.count + '\n');
+ write('# tests ' + self.count + '\n');
+ write('# pass ' + (self.pass + self.todo) + '\n');
+ if (self.todo) { write('# todo ' + self.todo + '\n'); }
+ if (self.fail) {
+ write('# fail ' + self.fail + '\n');
+ } else {
+ write('\n# ok\n');
+ }
+
+ self._stream.queue(null);
+};
+
+module.exports = Results;
diff --git a/packages/lib/test.js b/packages/lib/test.js
new file mode 100644
index 00000000..13a59450
--- /dev/null
+++ b/packages/lib/test.js
@@ -0,0 +1,991 @@
+'use strict';
+
+var deepEqual = require('deep-equal');
+var defined = require('defined');
+var path = require('path');
+var inherits = require('inherits');
+var EventEmitter = require('events').EventEmitter;
+var hasOwn = require('hasown');
+var isRegExp = require('is-regex');
+var trim = require('string.prototype.trim');
+var callBind = require('call-bind');
+var callBound = require('call-bind/callBound');
+var forEach = require('for-each');
+var inspect = require('object-inspect');
+var is = require('object-is/polyfill')();
+var objectKeys = require('object-keys');
+var every = require('array.prototype.every');
+var mockProperty = require('mock-property');
+
+var isEnumerable = callBound('Object.prototype.propertyIsEnumerable');
+var toLowerCase = callBound('String.prototype.toLowerCase');
+var isProto = callBound('Object.prototype.isPrototypeOf');
+var $exec = callBound('RegExp.prototype.exec');
+var objectToString = callBound('Object.prototype.toString');
+var $split = callBound('String.prototype.split');
+var $replace = callBound('String.prototype.replace');
+var $strSlice = callBound('String.prototype.slice');
+var $push = callBound('Array.prototype.push');
+var $shift = callBound('Array.prototype.shift');
+var $slice = callBound('Array.prototype.slice');
+
+var nextTick = typeof setImmediate !== 'undefined'
+ ? setImmediate
+ : process.nextTick;
+var safeSetTimeout = setTimeout;
+var safeClearTimeout = clearTimeout;
+
+var isErrorConstructor = isProto(Error, TypeError) // IE 8 is `false` here
+ ? function isErrorConstructor(C) {
+ return isProto(Error, C);
+ }
+ : function isErrorConstructor(C) {
+ return isProto(Error, C)
+ || isProto(TypeError, C)
+ || isProto(RangeError, C)
+ || isProto(SyntaxError, C)
+ || isProto(ReferenceError, C)
+ || isProto(EvalError, C)
+ || isProto(URIError, C);
+ };
+
+// eslint-disable-next-line no-unused-vars
+function getTestArgs(name_, opts_, cb_) {
+ var name = '(anonymous)';
+ var opts = {};
+ var cb;
+
+ for (var i = 0; i < arguments.length; i++) {
+ var arg = arguments[i];
+ if (typeof arg === 'string') {
+ name = arg;
+ } else if (typeof arg === 'object') {
+ opts = arg || opts;
+ } else if (typeof arg === 'function') {
+ cb = arg;
+ }
+ }
+ return {
+ name: name,
+ opts: opts,
+ cb: cb
+ };
+}
+
+function Test(name_, opts_, cb_) {
+ if (!(this instanceof Test)) {
+ return new Test(name_, opts_, cb_);
+ }
+
+ var args = getTestArgs(name_, opts_, cb_);
+
+ this.readable = true;
+ this.name = args.name || '(anonymous)';
+ this.assertCount = 0;
+ this.pendingCount = 0;
+ this._skip = args.opts.skip || false;
+ this._todo = args.opts.todo || false;
+ this._timeout = args.opts.timeout;
+ this._plan = undefined;
+ this._cb = args.cb;
+ this.ended = false;
+ this._progeny = [];
+ this._teardown = [];
+ this._ok = true;
+ this._objectPrintDepth = 5;
+ var depthEnvVar = process.env.NODE_TAPE_OBJECT_PRINT_DEPTH;
+ if (args.opts.objectPrintDepth) {
+ this._objectPrintDepth = args.opts.objectPrintDepth;
+ } else if (depthEnvVar) {
+ if (toLowerCase(depthEnvVar) === 'infinity') {
+ this._objectPrintDepth = Infinity;
+ } else {
+ this._objectPrintDepth = depthEnvVar;
+ }
+ }
+
+ for (var prop in this) {
+ if (typeof this[prop] === 'function') {
+ this[prop] = callBind(this[prop], this);
+ }
+ }
+}
+
+inherits(Test, EventEmitter);
+
+Test.prototype.run = function run() {
+ this.emit('prerun');
+ if (!this._cb || this._skip) {
+ this._end();
+ return;
+ }
+ if (this._timeout != null) {
+ this.timeoutAfter(this._timeout);
+ }
+
+ var callbackReturn = this._cb(this);
+
+ if (
+ typeof Promise === 'function'
+ && callbackReturn
+ && typeof callbackReturn.then === 'function'
+ ) {
+ var self = this;
+ Promise.resolve(callbackReturn).then(
+ function onResolve() {
+ if (!self.calledEnd) {
+ self.end();
+ }
+ },
+ function onError(err) {
+ if (err instanceof Error || objectToString(err) === '[object Error]') {
+ self.ifError(err);
+ } else {
+ self.fail(err);
+ }
+ self.end();
+ }
+ );
+ return;
+ }
+
+ this.emit('run');
+};
+
+Test.prototype.test = function test(name, opts, cb) {
+ var self = this;
+ var t = new Test(name, opts, cb);
+ $push(this._progeny, t);
+ this.pendingCount++;
+ this.emit('test', t);
+ t.on('prerun', function () {
+ self.assertCount++;
+ });
+
+ if (!self._pendingAsserts()) {
+ nextTick(function () {
+ self._end();
+ });
+ }
+
+ nextTick(function () {
+ if (!self._plan && self.pendingCount == self._progeny.length) {
+ self._end();
+ }
+ });
+};
+
+Test.prototype.comment = function comment(msg) {
+ var that = this;
+ forEach($split(trim(msg), '\n'), function (aMsg) {
+ that.emit('result', $replace(trim(aMsg), /^#\s*/, ''));
+ });
+};
+
+Test.prototype.plan = function plan(n) {
+ this._plan = n;
+ this.emit('plan', n);
+};
+
+Test.prototype.timeoutAfter = function timeoutAfter(ms) {
+ if (!ms) { throw new Error('timeoutAfter requires a timespan'); }
+ var self = this;
+ var timeout = safeSetTimeout(function () {
+ self.fail(self.name + ' timed out after ' + ms + 'ms');
+ self.end();
+ }, ms);
+ this.once('end', function () {
+ safeClearTimeout(timeout);
+ });
+};
+
+Test.prototype.end = function end(err) {
+ if (arguments.length >= 1 && !!err) {
+ this.ifError(err);
+ }
+
+ if (this.calledEnd) {
+ this.fail('.end() already called');
+ }
+ this.calledEnd = true;
+ this._end();
+};
+
+Test.prototype.teardown = function teardown(fn) {
+ if (typeof fn !== 'function') {
+ this.fail('teardown: ' + inspect(fn) + ' is not a function');
+ } else {
+ this._teardown.push(fn);
+ }
+};
+
+function wrapFunction(original) {
+ if (typeof original !== 'undefined' && typeof original !== 'function') {
+ throw new TypeError('`original` must be a function or `undefined`');
+ }
+
+ var bound = original && callBind.apply(original);
+
+ var calls = [];
+
+ var wrapObject = {
+ __proto__: null,
+ wrapped: function wrapped() {
+ var args = $slice(arguments);
+ var completed = false;
+ try {
+ var returned = bound ? bound(this, arguments) : void undefined;
+ $push(calls, { args: args, receiver: this, returned: returned });
+ completed = true;
+ return returned;
+ } finally {
+ if (!completed) {
+ $push(calls, { args: args, receiver: this, threw: true });
+ }
+ }
+ },
+ calls: calls,
+ results: function results() {
+ try {
+ return calls;
+ } finally {
+ calls = [];
+ wrapObject.calls = calls;
+ }
+ }
+ };
+ return wrapObject;
+}
+
+Test.prototype.capture = function capture(obj, method) {
+ if (!obj || (typeof obj !== 'object' && typeof obj !== 'function')) {
+ throw new TypeError('`obj` must be an object');
+ }
+ if (typeof method !== 'string' && typeof method !== 'symbol') {
+ throw new TypeError('`method` must be a string or a symbol');
+ }
+ var implementation = arguments.length > 2 ? arguments[2] : void undefined;
+ if (typeof implementation !== 'undefined' && typeof implementation !== 'function') {
+ throw new TypeError('`implementation`, if provided, must be a function');
+ }
+
+ var wrapper = wrapFunction(implementation);
+ var restore = mockProperty(obj, method, { value: wrapper.wrapped });
+ this.teardown(restore);
+
+ wrapper.results.restore = restore;
+
+ return wrapper.results;
+};
+
+Test.prototype.captureFn = function captureFn(original) {
+ if (typeof original !== 'function') {
+ throw new TypeError('`original` must be a function');
+ }
+
+ var wrapObject = wrapFunction(original);
+ wrapObject.wrapped.calls = wrapObject.calls;
+ return wrapObject.wrapped;
+};
+
+Test.prototype.intercept = function intercept(obj, property) {
+ if (!obj || (typeof obj !== 'object' && typeof obj !== 'function')) {
+ throw new TypeError('`obj` must be an object');
+ }
+ if (typeof property !== 'string' && typeof property !== 'symbol') {
+ throw new TypeError('`property` must be a string or a symbol');
+ }
+ var desc = arguments.length > 2 ? arguments[2] : { __proto__: null };
+ if (typeof desc !== 'undefined' && (!desc || typeof desc !== 'object')) {
+ throw new TypeError('`desc`, if provided, must be an object');
+ }
+ if ('configurable' in desc && !desc.configurable) {
+ throw new TypeError('`desc.configurable`, if provided, must be `true`, so that the interception can be restored later');
+ }
+ var isData = 'writable' in desc || 'value' in desc;
+ var isAccessor = 'get' in desc || 'set' in desc;
+ if (isData && isAccessor) {
+ throw new TypeError('`value` and `writable` can not be mixed with `get` and `set`');
+ }
+ var strictMode = arguments.length > 3 ? arguments[3] : true;
+ if (typeof strictMode !== 'boolean') {
+ throw new TypeError('`strictMode`, if provided, must be a boolean');
+ }
+
+ var calls = [];
+ var getter = desc.get && callBind.apply(desc.get);
+ var setter = desc.set && callBind.apply(desc.set);
+ var value = !isAccessor ? desc.value : void undefined;
+ var writable = !!desc.writable;
+
+ function getInterceptor() {
+ var args = $slice(arguments);
+ if (isAccessor) {
+ if (getter) {
+ var completed = false;
+ try {
+ var returned = getter(this, arguments);
+ completed = true;
+ $push(calls, { type: 'get', success: true, value: returned, args: args, receiver: this });
+ return returned;
+ } finally {
+ if (!completed) {
+ $push(calls, { type: 'get', success: false, threw: true, args: args, receiver: this });
+ }
+ }
+ }
+ }
+ $push(calls, { type: 'get', success: true, value: value, args: args, receiver: this });
+ return value;
+ }
+
+ function setInterceptor(v) {
+ var args = $slice(arguments);
+ if (isAccessor && setter) {
+ var completed = false;
+ try {
+ var returned = setter(this, arguments);
+ completed = true;
+ $push(calls, { type: 'set', success: true, value: v, args: args, receiver: this });
+ return returned;
+ } finally {
+ if (!completed) {
+ $push(calls, { type: 'set', success: false, threw: true, args: args, receiver: this });
+ }
+ }
+ }
+ var canSet = isAccessor || writable;
+ if (canSet) {
+ value = v;
+ }
+ $push(calls, { type: 'set', success: !!canSet, value: value, args: args, receiver: this });
+
+ if (!canSet && strictMode) {
+ throw new TypeError('Cannot assign to read only property `' + inspect(property) + '` of object `' + inspect(obj) + '`');
+ }
+ return value;
+ }
+
+ var restore = mockProperty(obj, property, {
+ nonEnumerable: !!desc.enumerable,
+ get: getInterceptor,
+ set: setInterceptor
+ });
+ this.teardown(restore);
+
+ function results() {
+ try {
+ return calls;
+ } finally {
+ calls = [];
+ }
+ }
+ results.restore = restore;
+
+ return results;
+};
+
+Test.prototype._end = function _end(err) {
+ var self = this;
+
+ if (!this._cb && !this._todo && !this._skip) {
+ this.fail('# TODO ' + this.name);
+ }
+
+ if (this._progeny.length) {
+ var t = $shift(this._progeny);
+ t.on('end', function () { self._end(); });
+ t.run();
+ return;
+ }
+
+ function completeEnd() {
+ if (!self.ended) { self.emit('end'); }
+ var pendingAsserts = self._pendingAsserts();
+ if (!self._planError && self._plan !== undefined && pendingAsserts) {
+ self._planError = true;
+ self.fail('plan != count', {
+ expected: self._plan,
+ actual: self.assertCount
+ });
+ }
+ self.ended = true;
+ }
+
+ function next() {
+ if (self._teardown.length === 0) {
+ completeEnd();
+ return;
+ }
+ var fn = self._teardown.shift();
+ var res;
+ try {
+ res = fn();
+ } catch (e) {
+ self.fail(e);
+ }
+ if (res && typeof res.then === 'function') {
+ res.then(next, function (_err) {
+ // TODO: wth?
+ err = err || _err;
+ });
+ } else {
+ next();
+ }
+ }
+
+ next();
+};
+
+Test.prototype._exit = function _exit() {
+ if (this._plan !== undefined && !this._planError && this.assertCount !== this._plan) {
+ this._planError = true;
+ this.fail('plan != count', {
+ expected: this._plan,
+ actual: this.assertCount,
+ exiting: true
+ });
+ } else if (!this.ended) {
+ this.fail('test exited without ending: ' + this.name, {
+ exiting: true
+ });
+ }
+};
+
+Test.prototype._pendingAsserts = function _pendingAsserts() {
+ if (this._plan === undefined) {
+ return 1;
+ }
+ return this._plan - (this._progeny.length + this.assertCount);
+};
+
+Test.prototype._assert = function assert(ok, opts) {
+ var self = this;
+ var extra = opts.extra || {};
+
+ var actualOK = !!ok || !!extra.skip;
+
+ var name = defined(extra.message, opts.message, '(unnamed assert)');
+ if (this.calledEnd && opts.operator !== 'fail') {
+ this.fail('.end() already called: ' + name);
+ return;
+ }
+
+ var res = {
+ id: self.assertCount++,
+ ok: actualOK,
+ skip: defined(extra.skip, opts.skip),
+ todo: defined(extra.todo, opts.todo, self._todo),
+ name: name,
+ operator: defined(extra.operator, opts.operator),
+ objectPrintDepth: self._objectPrintDepth
+ };
+ if (hasOwn(opts, 'actual') || hasOwn(extra, 'actual')) {
+ res.actual = defined(extra.actual, opts.actual);
+ }
+ if (hasOwn(opts, 'expected') || hasOwn(extra, 'expected')) {
+ res.expected = defined(extra.expected, opts.expected);
+ }
+ this._ok = !!(this._ok && actualOK);
+
+ if (!actualOK && !res.todo) {
+ res.error = defined(extra.error, opts.error, new Error(res.name));
+ }
+
+ if (!actualOK) {
+ var e = new Error('exception');
+ var err = $split(e.stack || '', '\n');
+ var tapeDir = __dirname + path.sep;
+ var index = path.sep + 'index.js';
+
+ for (var i = 0; i < err.length; i++) {
+ /*
+ Stack trace lines may resemble one of the following.
+ We need to correctly extract a function name (if any) and path / line number for each line.
+
+ at myFunction (/path/to/file.js:123:45)
+ at myFunction (/path/to/file.other-ext:123:45)
+ at myFunction (/path to/file.js:123:45)
+ at myFunction (C:\path\to\file.js:123:45)
+ at myFunction (/path/to/file.js:123)
+ at Test. (/path/to/file.js:123:45)
+ at Test.bound [as run] (/path/to/file.js:123:45)
+ at /path/to/file.js:123:45
+
+ Regex has three parts. First is non-capturing group for 'at ' (plus anything preceding it).
+
+ /^(?:[^\s]*\s*\bat\s+)/
+
+ Second captures function call description (optional).
+ This is not necessarily a valid JS function name, but just what the stack trace is using to represent a function call.
+ It may look like `` or 'Test.bound [as run]'.
+
+ For our purposes, we assume that, if there is a function name, it's everything leading up to the first open parentheses (trimmed) before our pathname.
+
+ /(?:(.*)\s+\()?/
+
+ Last part captures file path plus line no (and optional column no).
+
+ /((?:[/\\]|[a-zA-Z]:\\)[^:\)]+:(\d+)(?::(\d+))?)\)?/
+
+ In the future, if node supports more ESM URL protocols than `file`, the `file:` below will need to be expanded.
+ */
+ var re = /^(?:[^\s]*\s*\bat\s+)(?:(.*)\s+\()?((?:[/\\]|[a-zA-Z]:\\|file:\/\/)[^:)]+:(\d+)(?::(\d+))?)\)?$/;
+ // first tokenize the PWD, then tokenize tape
+ var lineWithTokens = $replace(
+ $replace(
+ err[i],
+ process.cwd(),
+ path.sep + '$CWD'
+ ),
+ tapeDir,
+ path.sep + '$TEST' + path.sep
+ );
+ var m = re.exec(lineWithTokens);
+
+ if (!m) {
+ continue;
+ }
+
+ var callDescription = m[1] || '';
+ // first untokenize tape, and then untokenize the PWD, then strip the line/column
+ var filePath = $replace(
+ $replace(
+ $replace(m[2], path.sep + '$TEST' + path.sep, tapeDir),
+ path.sep + '$CWD',
+ process.cwd()
+ ),
+ /:\d+:\d+$/,
+ ''
+ );
+
+ if (
+ $strSlice(filePath, 0, tapeDir.length) === tapeDir
+ && $strSlice(filePath, -index.length) !== index // index.js is inside lib/
+ ) {
+ continue;
+ }
+
+ // Function call description may not (just) be a function name.
+ // Try to extract function name by looking at first "word" only.
+ res.functionName = $split(callDescription, /\s+/)[0];
+ res.file = filePath;
+ res.line = Number(m[3]);
+ if (m[4]) { res.column = Number(m[4]); }
+
+ res.at = callDescription + ' (' + filePath + ':' + res.line + (res.column ? ':' + res.column : '') + ')';
+ break;
+ }
+ }
+
+ self.emit('result', res);
+
+ var pendingAsserts = self._pendingAsserts();
+ if (!pendingAsserts) {
+ if (extra.exiting) {
+ self._end();
+ } else {
+ nextTick(function () {
+ self._end();
+ });
+ }
+ }
+
+ if (!self._planError && pendingAsserts < 0) {
+ self._planError = true;
+ self.fail('plan != count', {
+ expected: self._plan,
+ actual: self._plan - pendingAsserts
+ });
+ }
+};
+
+Test.prototype.fail = function fail(msg, extra) {
+ this._assert(false, {
+ message: msg,
+ operator: 'fail',
+ extra: extra
+ });
+};
+
+Test.prototype.pass = function pass(msg, extra) {
+ this._assert(true, {
+ message: msg,
+ operator: 'pass',
+ extra: extra
+ });
+};
+
+Test.prototype.skip = function skip(msg, extra) {
+ this._assert(true, {
+ message: msg,
+ operator: 'skip',
+ skip: true,
+ extra: extra
+ });
+};
+
+var testAssert = function assert(value, msg, extra) { // eslint-disable-line func-style
+ this._assert(value, {
+ message: defined(msg, 'should be truthy'),
+ operator: 'ok',
+ expected: true,
+ actual: value,
+ extra: extra
+ });
+};
+Test.prototype.ok
+= Test.prototype['true']
+= Test.prototype.assert
+= testAssert;
+
+function notOK(value, msg, extra) {
+ this._assert(!value, {
+ message: defined(msg, 'should be falsy'),
+ operator: 'notOk',
+ expected: false,
+ actual: value,
+ extra: extra
+ });
+}
+Test.prototype.notOk
+= Test.prototype['false']
+= Test.prototype.notok
+= notOK;
+
+function error(err, msg, extra) {
+ this._assert(!err, {
+ message: defined(msg, String(err)),
+ operator: 'error',
+ error: err,
+ extra: extra
+ });
+}
+Test.prototype.error
+= Test.prototype.ifError
+= Test.prototype.ifErr
+= Test.prototype.iferror
+= error;
+
+function strictEqual(a, b, msg, extra) {
+ if (arguments.length < 2) {
+ throw new TypeError('two arguments must be provided to compare');
+ }
+ this._assert(is(a, b), {
+ message: defined(msg, 'should be strictly equal'),
+ operator: 'equal',
+ actual: a,
+ expected: b,
+ extra: extra
+ });
+}
+Test.prototype.equal
+= Test.prototype.equals
+= Test.prototype.isEqual
+= Test.prototype.strictEqual
+= Test.prototype.strictEquals
+= Test.prototype.is
+= strictEqual;
+
+function notStrictEqual(a, b, msg, extra) {
+ if (arguments.length < 2) {
+ throw new TypeError('two arguments must be provided to compare');
+ }
+ this._assert(!is(a, b), {
+ message: defined(msg, 'should not be strictly equal'),
+ operator: 'notEqual',
+ actual: a,
+ expected: b,
+ extra: extra
+ });
+}
+
+Test.prototype.notEqual
+= Test.prototype.notEquals
+= Test.prototype.isNotEqual
+= Test.prototype.doesNotEqual
+= Test.prototype.isInequal
+= Test.prototype.notStrictEqual
+= Test.prototype.notStrictEquals
+= Test.prototype.isNot
+= Test.prototype.not
+= notStrictEqual;
+
+function looseEqual(a, b, msg, extra) {
+ if (arguments.length < 2) {
+ throw new TypeError('two arguments must be provided to compare');
+ }
+ this._assert(a == b, {
+ message: defined(msg, 'should be loosely equal'),
+ operator: 'looseEqual',
+ actual: a,
+ expected: b,
+ extra: extra
+ });
+}
+
+Test.prototype.looseEqual
+= Test.prototype.looseEquals
+= looseEqual;
+
+function notLooseEqual(a, b, msg, extra) {
+ if (arguments.length < 2) {
+ throw new TypeError('two arguments must be provided to compare');
+ }
+ this._assert(a != b, {
+ message: defined(msg, 'should not be loosely equal'),
+ operator: 'notLooseEqual',
+ actual: a,
+ expected: b,
+ extra: extra
+ });
+}
+Test.prototype.notLooseEqual
+= Test.prototype.notLooseEquals
+= notLooseEqual;
+
+function tapeDeepEqual(a, b, msg, extra) {
+ if (arguments.length < 2) {
+ throw new TypeError('two arguments must be provided to compare');
+ }
+ this._assert(deepEqual(a, b, { strict: true }), {
+ message: defined(msg, 'should be deeply equivalent'),
+ operator: 'deepEqual',
+ actual: a,
+ expected: b,
+ extra: extra
+ });
+}
+Test.prototype.deepEqual
+= Test.prototype.deepEquals
+= Test.prototype.isEquivalent
+= Test.prototype.same
+= tapeDeepEqual;
+
+function notDeepEqual(a, b, msg, extra) {
+ if (arguments.length < 2) {
+ throw new TypeError('two arguments must be provided to compare');
+ }
+ this._assert(!deepEqual(a, b, { strict: true }), {
+ message: defined(msg, 'should not be deeply equivalent'),
+ operator: 'notDeepEqual',
+ actual: a,
+ expected: b,
+ extra: extra
+ });
+}
+Test.prototype.notDeepEqual
+= Test.prototype.notDeepEquals
+= Test.prototype.notEquivalent
+= Test.prototype.notDeeply
+= Test.prototype.notSame
+= Test.prototype.isNotDeepEqual
+= Test.prototype.isNotDeeply
+= Test.prototype.isNotEquivalent
+= Test.prototype.isInequivalent
+= notDeepEqual;
+
+function deepLooseEqual(a, b, msg, extra) {
+ if (arguments.length < 2) {
+ throw new TypeError('two arguments must be provided to compare');
+ }
+ this._assert(deepEqual(a, b), {
+ message: defined(msg, 'should be loosely deeply equivalent'),
+ operator: 'deepLooseEqual',
+ actual: a,
+ expected: b,
+ extra: extra
+ });
+}
+
+Test.prototype.deepLooseEqual
+= deepLooseEqual;
+
+function notDeepLooseEqual(a, b, msg, extra) {
+ if (arguments.length < 2) {
+ throw new TypeError('two arguments must be provided to compare');
+ }
+ this._assert(!deepEqual(a, b), {
+ message: defined(msg, 'should not be loosely deeply equivalent'),
+ operator: 'notDeepLooseEqual',
+ actual: a,
+ expected: b,
+ extra: extra
+ });
+}
+Test.prototype.notDeepLooseEqual
+= notDeepLooseEqual;
+
+Test.prototype['throws'] = function (fn, expected, msg, extra) {
+ if (typeof expected === 'string') {
+ msg = expected;
+ expected = undefined;
+ }
+
+ var caught;
+
+ try {
+ fn();
+ } catch (err) {
+ caught = { error: err };
+ if (Object(err) === err && 'message' in err && (!isEnumerable(err, 'message') || !hasOwn(err, 'message'))) {
+ try {
+ var message = err.message;
+ delete err.message;
+ err.message = message;
+ } catch (e) { /**/ }
+ }
+ }
+
+ var passed = caught;
+
+ if (caught) {
+ if (typeof expected === 'string' && caught.error && caught.error.message === expected) {
+ throw new TypeError('The "error/message" argument is ambiguous. The error message ' + inspect(expected) + ' is identical to the message.');
+ }
+ if (typeof expected === 'function') {
+ if (typeof expected.prototype !== 'undefined' && caught.error instanceof expected) {
+ passed = true;
+ } else if (isErrorConstructor(expected)) {
+ passed = false;
+ } else {
+ passed = expected.call({}, caught.error) === true;
+ }
+ } else if (isRegExp(expected)) {
+ passed = $exec(expected, caught.error) !== null;
+ expected = inspect(expected);
+ } else if (expected && typeof expected === 'object') { // Handle validation objects.
+ if (caught.error && typeof caught.error === 'object') {
+ var keys = objectKeys(expected);
+ // Special handle errors to make sure the name and the message are compared as well.
+ if (expected instanceof Error) {
+ $push(keys, 'name', 'message');
+ } else if (keys.length === 0) {
+ throw new TypeError('`throws` validation object must not be empty');
+ }
+ passed = every(keys, function (key) {
+ if (typeof caught.error[key] === 'string' && isRegExp(expected[key]) && $exec(expected[key], caught.error[key]) !== null) {
+ return true;
+ }
+ if (key in caught.error && deepEqual(caught.error[key], expected[key], { strict: true })) {
+ return true;
+ }
+ return false;
+ });
+ } else {
+ passed = false;
+ }
+ }
+ }
+
+ this._assert(!!passed, {
+ message: defined(msg, 'should throw'),
+ operator: 'throws',
+ actual: caught && caught.error,
+ expected: expected,
+ error: !passed && caught && caught.error,
+ extra: extra
+ });
+};
+
+Test.prototype.doesNotThrow = function doesNotThrow(fn, expected, msg, extra) {
+ if (typeof expected === 'string') {
+ msg = expected;
+ expected = undefined;
+ }
+ var caught;
+ try {
+ fn();
+ } catch (err) {
+ caught = { error: err };
+ }
+ this._assert(!caught, {
+ message: defined(msg, 'should not throw'),
+ operator: 'throws',
+ actual: caught && caught.error,
+ expected: expected,
+ error: caught && caught.error,
+ extra: extra
+ });
+};
+
+Test.prototype.match = function match(string, regexp, msg, extra) {
+ if (!isRegExp(regexp)) {
+ this._assert(false, {
+ message: defined(msg, 'The "regexp" argument must be an instance of RegExp. Received type ' + typeof regexp + ' (' + inspect(regexp) + ')'),
+ operator: 'match',
+ actual: objectToString(regexp),
+ expected: '[object RegExp]',
+ extra: extra
+ });
+ } else if (typeof string !== 'string') {
+ this._assert(false, {
+ message: defined(msg, 'The "string" argument must be of type string. Received type ' + typeof string + ' (' + inspect(string) + ')'),
+ operator: 'match',
+ actual: string === null ? null : typeof string,
+ expected: 'string',
+ extra: extra
+ });
+ } else {
+ var matches = $exec(regexp, string) !== null;
+ var message = defined(
+ msg,
+ 'The input ' + (matches ? 'matched' : 'did not match') + ' the regular expression ' + inspect(regexp) + '. Input: ' + inspect(string)
+ );
+ this._assert(matches, {
+ message: message,
+ operator: 'match',
+ actual: string,
+ expected: regexp,
+ extra: extra
+ });
+ }
+};
+
+Test.prototype.doesNotMatch = function doesNotMatch(string, regexp, msg, extra) {
+ if (!isRegExp(regexp)) {
+ this._assert(false, {
+ message: defined(msg, 'The "regexp" argument must be an instance of RegExp. Received type ' + typeof regexp + ' (' + inspect(regexp) + ')'),
+ operator: 'doesNotMatch',
+ actual: objectToString(regexp),
+ expected: '[object RegExp]',
+ extra: extra
+ });
+ } else if (typeof string !== 'string') {
+ this._assert(false, {
+ message: defined(msg, 'The "string" argument must be of type string. Received type ' + typeof string + ' (' + inspect(string) + ')'),
+ operator: 'doesNotMatch',
+ actual: string === null ? null : typeof string,
+ expected: 'string',
+ extra: extra
+ });
+ } else {
+ var matches = $exec(regexp, string) !== null;
+ var message = defined(
+ msg,
+ 'The input ' + (matches ? 'was expected to not match' : 'did not match') + ' the regular expression ' + inspect(regexp) + '. Input: ' + inspect(string)
+ );
+ this._assert(!matches, {
+ message: message,
+ operator: 'doesNotMatch',
+ actual: string,
+ expected: regexp,
+ extra: extra
+ });
+ }
+};
+
+Test.prototype.assertion = function assertion(fn) {
+ return callBind.apply(fn)(this, $slice(arguments, 1));
+};
+
+// eslint-disable-next-line no-unused-vars
+Test.skip = function skip(name_, _opts, _cb) {
+ var args = getTestArgs.apply(null, arguments);
+ args.opts.skip = true;
+ return new Test(args.name, args.opts, args.cb);
+};
+
+module.exports = Test;
+
+// vim: set softtabstop=4 shiftwidth=4:
diff --git a/test/common.js b/test/common.js
index a1ef4f8f..322c14ed 100644
--- a/test/common.js
+++ b/test/common.js
@@ -42,7 +42,8 @@ var stripChangingData = function (line) {
var withoutTestDir = line.replace(__dirname, '$TEST');
var withoutPackageDir = withoutTestDir.replace(path.dirname(__dirname), '$TAPE');
var withoutPathSep = withoutPackageDir.replace(new RegExp('\\' + path.sep, 'g'), '/');
- var withoutLineNumbers = withoutPathSep.replace(/:\d+:\d+/g, ':$LINE:$COL');
+ var withoutLibDir = withoutPathSep.replace('$TAPE/node_modules/tape-lib/', '$TAPE/lib/');
+ var withoutLineNumbers = withoutLibDir.replace(/:\d+:\d+/g, ':$LINE:$COL');
var withoutNestedLineNumbers = withoutLineNumbers.replace(/, :\$LINE:\$COL\)$/, ')');
var withoutProcessImmediate = withoutNestedLineNumbers.replace(
/^(\s+)at (?:process\.)?(processImmediate|startup\.processNextTick\.process\._tickCallback) (?:\[as _immediateCallback\] )?\((node:internal\/timers|(?:internal\/)?timers\.js|node\.js):\$LINE:\$COL\)$/g,
diff --git a/test/default_stream.js b/test/default_stream.js
index eb1435f9..b2abec4b 100644
--- a/test/default_stream.js
+++ b/test/default_stream.js
@@ -2,7 +2,7 @@
var tap = require('tap');
-var getDefaultStream = require('../lib/default_stream');
+var getDefaultStream = require('tape-lib/default_stream');
tap.test('getDefaultStream', function (tt) {
tt.plan(5);