diff --git a/.eslintrc b/.eslintrc index ed08518d..796fc4a1 100644 --- a/.eslintrc +++ b/.eslintrc @@ -21,7 +21,6 @@ "multiline-comment-style": "off", "no-negated-condition": "off", "no-underscore-dangle": "warn", - "no-use-before-define": "warn", "object-curly-newline": "off", "sort-keys": "warn", }, @@ -119,5 +118,11 @@ "camelcase": "off", }, }, + { + "files": ["lib/default_stream.js"], + "rules": { + "no-use-before-define": "warn", + }, + } ], } diff --git a/bin/tape b/bin/tape index 3f713be2..0525f238 100755 --- a/bin/tape +++ b/bin/tape @@ -71,12 +71,6 @@ var files = opts._.reduce(function (result, arg) { var hasImport = require('has-dynamic-import'); -hasImport().then(function (hasSupport) { - // the nextTick callback gets called outside the promise chain, avoiding - // promises and unhandled rejections when only loading commonjs files - process.nextTick(importFiles, hasSupport); -}); - var tape = require('../'); function importFiles(hasSupport) { @@ -97,4 +91,10 @@ function importFiles(hasSupport) { return filesPromise ? filesPromise.then(function () { tape.run(); }) : tape.run(); } +hasImport().then(function (hasSupport) { + // the nextTick callback gets called outside the promise chain, avoiding + // promises and unhandled rejections when only loading commonjs files + process.nextTick(importFiles, hasSupport); +}); + // vim: ft=javascript diff --git a/index.js b/index.js index 4b9fd673..efd4bb3f 100644 --- a/index.js +++ b/index.js @@ -14,6 +14,16 @@ var canExit = typeof process !== 'undefined' && process module.exports = (function () { var wait = false; var harness; + + function getHarness(opts) { + if (!opts) { opts = {}; } + opts.autoclose = !canEmitExit; + // this override is here since tests fail via nyc if createHarness is moved upwards + // eslint-disable-next-line no-use-before-define + if (!harness) { harness = createExitHarness(opts, wait); } + return harness; + } + var lazyLoad = function () { // eslint-disable-next-line no-invalid-this return getHarness().apply(this, arguments); @@ -54,75 +64,8 @@ module.exports = (function () { lazyLoad.getHarness = getHarness; return lazyLoad; - - function getHarness(opts) { - if (!opts) { opts = {}; } - opts.autoclose = !canEmitExit; - if (!harness) { harness = createExitHarness(opts, wait); } - return harness; - } }()); -function createExitHarness(conf, wait) { - var config = conf || {}; - var harness = createHarness({ - autoclose: defined(config.autoclose, false), - noOnly: defined(conf.noOnly, defined(process.env.NODE_TAPE_NO_ONLY_TEST, false)) - }); - var running = false; - var ended = false; - - if (wait) { - harness.run = run; - } else { - run(); - } - - if (config.exit === false) { return harness; } - if (!canEmitExit || !canExit) { return harness; } - - process.on('exit', function (code) { - // let the process exit cleanly. - if (typeof code === 'number' && code !== 0) { - return; - } - - if (!ended) { - var only = harness._results._only; - for (var i = 0; i < harness._tests.length; i++) { - var t = harness._tests[i]; - if (!only || t === only) { - t._exit(); - } - } - } - harness.close(); - - process.removeAllListeners('exit'); // necessary for node v0.6 - process.exit(code || harness._exitCode); // eslint-disable-line no-process-exit - }); - - return harness; - - function run() { - if (running) { return; } - running = true; - var stream = harness.createStream({ objectMode: config.objectMode }); - var es = stream.pipe(config.stream || createDefaultStream()); - if (canEmitExit && es) { // in node v0.4, `es` is `undefined` - // TODO: use `err` arg? - // eslint-disable-next-line no-unused-vars - es.on('error', function (err) { harness._exitCode = 1; }); - } - stream.on('end', function () { ended = true; }); - } -} - -module.exports.createHarness = createHarness; -module.exports.Test = Test; -module.exports.test = module.exports; // tap compat -module.exports.test.skip = Test.skip; - function createHarness(conf_) { var results = createResult(); if (!conf_ || conf_.autoclose !== false) { @@ -176,3 +119,63 @@ function createHarness(conf_) { return test; } + +function createExitHarness(conf, wait) { + var config = conf || {}; + var harness = createHarness({ + autoclose: defined(config.autoclose, false), + noOnly: defined(conf.noOnly, defined(process.env.NODE_TAPE_NO_ONLY_TEST, false)) + }); + var running = false; + var ended = false; + + function run() { + if (running) { return; } + running = true; + var stream = harness.createStream({ objectMode: config.objectMode }); + var es = stream.pipe(config.stream || createDefaultStream()); + if (canEmitExit && es) { // in node v0.4, `es` is `undefined` + // TODO: use `err` arg? + // eslint-disable-next-line no-unused-vars + es.on('error', function (err) { harness._exitCode = 1; }); + } + stream.on('end', function () { ended = true; }); + } + + if (wait) { + harness.run = run; + } else { + run(); + } + + if (config.exit === false) { return harness; } + if (!canEmitExit || !canExit) { return harness; } + + process.on('exit', function (code) { + // let the process exit cleanly. + if (typeof code === 'number' && code !== 0) { + return; + } + + if (!ended) { + var only = harness._results._only; + for (var i = 0; i < harness._tests.length; i++) { + var t = harness._tests[i]; + if (!only || t === only) { + t._exit(); + } + } + } + harness.close(); + + process.removeAllListeners('exit'); // necessary for node v0.6 + process.exit(code || harness._exitCode); // eslint-disable-line no-process-exit + }); + + return harness; +} + +module.exports.createHarness = createHarness; +module.exports.Test = Test; +module.exports.test = module.exports; // tap compat +module.exports.test.skip = Test.skip; diff --git a/lib/results.js b/lib/results.js index 5a120baa..97a731fc 100644 --- a/lib/results.js +++ b/lib/results.js @@ -17,13 +17,80 @@ var yamlIndicators = /:|-|\?/; var nextTick = typeof setImmediate !== 'undefined' ? setImmediate : process.nextTick; -module.exports = Results; -inherits(Results, EventEmitter); 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) { + var output = ''; + output += (res.ok ? '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 (has(res, 'expected') || has(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() { if (!(this instanceof Results)) { return new Results(); } this.count = 0; @@ -36,6 +103,8 @@ function Results() { this._isRunning = false; } +inherits(Results, EventEmitter); + Results.prototype.createStream = function (opts) { if (!opts) { opts = {}; } var self = this; @@ -160,71 +229,4 @@ Results.prototype.close = function () { self._stream.queue(null); }; -function encodeResult(res, count) { - var output = ''; - output += (res.ok ? '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 (has(res, 'expected') || has(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 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; -} +module.exports = Results; diff --git a/lib/test.js b/lib/test.js index bcac77d5..99225dca 100644 --- a/lib/test.js +++ b/lib/test.js @@ -26,16 +26,12 @@ var $strSlice = callBound('String.prototype.slice'); var $push = callBound('Array.prototype.push'); var $shift = callBound('Array.prototype.shift'); -module.exports = Test; - var nextTick = typeof setImmediate !== 'undefined' ? setImmediate : process.nextTick; var safeSetTimeout = setTimeout; var safeClearTimeout = clearTimeout; -inherits(Test, EventEmitter); - // eslint-disable-next-line no-unused-vars var getTestArgs = function (name_, opts_, cb_) { var name = '(anonymous)'; @@ -104,6 +100,8 @@ function Test(name_, opts_, cb_) { } } +inherits(Test, EventEmitter); + Test.prototype.run = function run() { this.emit('prerun'); if (!this._cb || this._skip) { @@ -221,6 +219,19 @@ Test.prototype._end = function _end(err) { 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(); @@ -244,19 +255,6 @@ Test.prototype._end = function _end(err) { } next(); - - 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; - } }; Test.prototype._exit = function _exit() { @@ -780,4 +778,6 @@ Test.skip = function skip(name_, _opts, _cb) { return new Test(args.name, args.opts, args.cb); }; +module.exports = Test; + // vim: set softtabstop=4 shiftwidth=4: diff --git a/package.json b/package.json index 57e6a411..3c1e6af3 100644 --- a/package.json +++ b/package.json @@ -103,10 +103,9 @@ "assert", "browser" ], - "author": { - "name": "Jordan Harband", - "email": "ljharb@gmail.com", - "url": "http://ljharb.codes" + "author": "Jordan Harband ", + "funding": { + "url": "https://github.com/sponsors/ljharb" }, "license": "MIT", "publishConfig": { diff --git a/test/anonymous-fn/test-wrapper.js b/test/anonymous-fn/test-wrapper.js index 0c42f00e..7a35bae9 100644 --- a/test/anonymous-fn/test-wrapper.js +++ b/test/anonymous-fn/test-wrapper.js @@ -1,5 +1,13 @@ 'use strict'; +function setUp() { + // ... example ... +} + +function tearDown() { + // ... example ... +} + // Example of wrapper function that would invoke tape module.exports = function (testCase) { return function (t) { @@ -8,11 +16,3 @@ module.exports = function (testCase) { tearDown(); }; }; - -function setUp() { - // ... example ... -} - -function tearDown() { - // ... example ... -} diff --git a/test/async-await/async-bug.js b/test/async-await/async-bug.js index 263e7eaf..8c16ccf4 100644 --- a/test/async-await/async-bug.js +++ b/test/async-await/async-bug.js @@ -11,6 +11,12 @@ function myCode(arr) { return sum; } +function sleep(ms) { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); +} + test('async-error', async function myTest(t) { await sleep(100); t.ok(true, 'before throw'); @@ -23,9 +29,3 @@ test('async-error', async function myTest(t) { t.end(); }); - -function sleep(ms) { - return new Promise((resolve) => { - setTimeout(resolve, ms); - }); -} diff --git a/test/end-as-callback.js b/test/end-as-callback.js index 4bad681b..d86e6e50 100644 --- a/test/end-as-callback.js +++ b/test/end-as-callback.js @@ -5,6 +5,45 @@ var forEach = require('for-each'); var tape = require('../'); var concat = require('concat-stream'); +/** + * extract the stack trace for the failed test. + * this will change dependent on the environment + * so no point hard-coding it in the test assertion + * see: https://git.io/v6hGG for example + * @param String rows - the tap output lines + * @returns String stacktrace - just the error stack part + */ +function getStackTrace(rows) { + var stacktrace = ' ---\n'; + var extract = false; + forEach(rows.toString('utf8').split('\n'), function (row) { + if (!extract) { + if (row.indexOf('---') > -1) { // start of stack trace + extract = true; + } + } else if (row.indexOf('...') > -1) { // end of stack trace + extract = false; + stacktrace += ' ...'; + } else { + stacktrace += row + '\n'; + } + }); + // console.log(stacktrace); + return stacktrace; +} + +function fakeAsyncTask(name, cb) { + cb(null, 'task' + name); +} + +function fakeAsyncWrite(name, cb) { + cb(null); +} + +function fakeAsyncWriteFail(name, cb) { + cb(new Error('fail')); +} + tap.test('tape assert.end as callback', function (tt) { var test = tape.createHarness({ exit: false }); @@ -46,42 +85,3 @@ tap.test('tape assert.end as callback', function (tt) { }); }); }); - -function fakeAsyncTask(name, cb) { - cb(null, 'task' + name); -} - -function fakeAsyncWrite(name, cb) { - cb(null); -} - -function fakeAsyncWriteFail(name, cb) { - cb(new Error('fail')); -} - -/** - * extract the stack trace for the failed test. - * this will change dependent on the environment - * so no point hard-coding it in the test assertion - * see: https://git.io/v6hGG for example - * @param String rows - the tap output lines - * @returns String stacktrace - just the error stack part - */ -function getStackTrace(rows) { - var stacktrace = ' ---\n'; - var extract = false; - forEach(rows.toString('utf8').split('\n'), function (row) { - if (!extract) { - if (row.indexOf('---') > -1) { // start of stack trace - extract = true; - } - } else if (row.indexOf('...') > -1) { // end of stack trace - extract = false; - stacktrace += ' ...'; - } else { - stacktrace += row + '\n'; - } - }); - // console.log(stacktrace); - return stacktrace; -} diff --git a/test/import.js b/test/import.js index 8688c149..af519c73 100644 --- a/test/import.js +++ b/test/import.js @@ -6,6 +6,12 @@ var concat = require('concat-stream'); var hasDynamicImport = require('has-dynamic-import'); var assign = require('object.assign'); +function tape(args, options) { + var bin = __dirname + '/../bin/tape'; + + return spawn('node', [bin].concat(args.split(' ')), assign({ cwd: __dirname }, options)); +} + tap.test('importing mjs files', function (t) { hasDynamicImport().then(function (hasSupport) { if (hasSupport) { @@ -187,9 +193,3 @@ tap.test('errors importing test files', function (t) { } }); }); - -function tape(args, options) { - var bin = __dirname + '/../bin/tape'; - - return spawn('node', [bin].concat(args.split(' ')), assign({ cwd: __dirname }, options)); -} diff --git a/test/require.js b/test/require.js index bb0dca74..70334153 100644 --- a/test/require.js +++ b/test/require.js @@ -5,6 +5,12 @@ var spawn = require('child_process').spawn; var concat = require('concat-stream'); var stripFullStack = require('./common').stripFullStack; +function tape(args) { + var bin = __dirname + '/../bin/tape'; + + return spawn('node', [bin].concat(args.split(' ')), { cwd: __dirname }); +} + tap.test('requiring a single module', function (t) { t.plan(2); @@ -65,9 +71,3 @@ tap.test('requiring multiple modules', function (t) { t.equal(code, 0); }); }); - -function tape(args) { - var bin = __dirname + '/../bin/tape'; - - return spawn('node', [bin].concat(args.split(' ')), { cwd: __dirname }); -} diff --git a/test/stackTrace.js b/test/stackTrace.js index a761d767..ecd0f37e 100644 --- a/test/stackTrace.js +++ b/test/stackTrace.js @@ -8,6 +8,10 @@ var common = require('./common'); var getDiag = common.getDiag; +function stripAt(body) { + return body.replace(/^\s*at:\s+Test.*$\n/m, ''); +} + tap.test('preserves stack trace with newlines', function (tt) { tt.plan(3); @@ -284,7 +288,3 @@ tap.test('preserves stack trace for failed assertions where actual===falsy', fun t.equal(false, true, 'false should be true'); }); }); - -function stripAt(body) { - return body.replace(/^\s*at:\s+Test.*$\n/m, ''); -}