From e5f2e933b16f7c779615d6efb58ade7e39236af1 Mon Sep 17 00:00:00 2001 From: Timo Tijhof Date: Mon, 15 Feb 2021 02:13:34 +0000 Subject: [PATCH] TapeAdapter: Add new adapter for tape Ref https://github.com/js-reporters/js-reporters/issues/118. --- index.js | 2 + lib/adapters/TapeAdapter.js | 134 +++++++++++++++++++ package-lock.json | 215 ++++++++++++++++++++++++++++++ package.json | 3 +- test/fixtures/integration/tape.js | 60 +++++++++ test/integration/adapters-run.js | 11 ++ test/integration/adapters.js | 7 +- 7 files changed, 429 insertions(+), 3 deletions(-) create mode 100644 lib/adapters/TapeAdapter.js create mode 100644 test/fixtures/integration/tape.js diff --git a/index.js b/index.js index fc33603..19e77fc 100644 --- a/index.js +++ b/index.js @@ -2,6 +2,7 @@ const EventEmitter = require('events'); const QUnitAdapter = require('./lib/adapters/QUnitAdapter.js'); const JasmineAdapter = require('./lib/adapters/JasmineAdapter.js'); const MochaAdapter = require('./lib/adapters/MochaAdapter.js'); +const TapeAdapter = require('./lib/adapters/TapeAdapter.js'); const TapReporter = require('./lib/reporters/TapReporter.js'); const ConsoleReporter = require('./lib/reporters/ConsoleReporter.js'); const SummaryReporter = require('./lib/reporters/SummaryReporter.js'); @@ -14,6 +15,7 @@ module.exports = { QUnitAdapter, JasmineAdapter, MochaAdapter, + TapeAdapter, TapReporter, ConsoleReporter, SummaryReporter, diff --git a/lib/adapters/TapeAdapter.js b/lib/adapters/TapeAdapter.js new file mode 100644 index 0000000..ebd6092 --- /dev/null +++ b/lib/adapters/TapeAdapter.js @@ -0,0 +1,134 @@ +const EventEmitter = require('events'); + +module.exports = class TapeAdapter extends EventEmitter { + /** + * Unlike other adapters, this adapter ends up disabling the default + * reporter of its framework (in this case, Tape's TAP reporter to + * console.log). This is the default Tape behaviour, and we choose not to + * change that. If you wish to enable Tape's default reporter also, and + * e.g. use CRI only for additional reports to network or artefact files, + * then run `tape.createStream().pipe(process.stdout);` to have both. + * + * @param {Tape} tape + */ + constructor (tape) { + super(); + + // TODO: Unable to observe 'prerun' event for some reason. + let started = false; + let runStartTime; + let runStatus = 'passed'; + const runCounts = { passed: 0, failed: 0, skipped: 0, todo: 0, total: 0 }; + const startOnce = () => { + if (!started) { + started = true; + this.emit('runStart', { name: null, counts: { total: null } }); + runStartTime = new Date().getTime(); + } + }; + + let fakeTestId = -1; + const activeTests = {}; + const handleRow = (row) => { + let test; + switch (row.type) { + // {type: test, name: '…', id: 0, skip: false, todo: false} + // {type: test, name: '…', id: 1, skip: false, todo: false, parent: 0} + case 'test': + test = activeTests[row.id] = { + startTime: new Date().getTime(), + name: row.name, + parentName: row.parent ? activeTests[row.parent].name : null, + fullName: row.parent ? [...activeTests[row.parent].fullName, row.name] : [row.name], + status: row.skip ? 'skipped' : (row.todo ? 'todo' : 'passed'), + assertions: [], + errors: [], + parent: row.parent ? activeTests[row.parent] : null + }; + this.emit('testStart', { + name: test.name, + parentName: test.parentName, + fullName: test.fullName + }); + break; + + // {type: 'end', test: 0} + case 'end': + test = activeTests[row.test]; + delete activeTests[row.test]; + this.emit('testEnd', { + name: test.name, + parentName: test.parentName, + fullName: test.fullName, + status: test.status, + assertions: test.assertions, + errors: test.errors, + runtime: test.status === 'skipped' ? null : new Date().getTime() - test.startTime + }); + runCounts.total++; + runCounts[test.status]++; + if (test.status === 'failed') { + if (test.parent) { + test.parent.status = test.status; + } + runStatus = 'failed'; + } + break; + + // {type: 'assert', test: 0, ok: true, skip: false, name: '…', actual: '…', expected: '…', error: Error} + case 'assert': + if (row.skip) { + // Replay as skipped test, ref https://github.com/substack/tape/issues/545 + const testId = fakeTestId--; + handleRow({ + type: 'test', + name: row.name, + id: testId, + skip: row.skip, + todo: row.todo, + parent: row.test + }); + handleRow({ type: 'end', test: testId }); + return; + } + + activeTests[row.test].assertions.push({ + passed: row.ok, + message: row.name + }); + if (!row.ok) { + if (!row.todo) { + activeTests[row.test].status = 'failed'; + } + activeTests[row.test].errors.push({ + passed: row.ok, + message: row.name, + // actual and expected are optional, e.g. not set for t.fail() + actual: row.actual, + expected: row.expected, + stack: (row.error && row.error.stack) || null + }); + } + break; + } + }; + + const stream = tape.createStream({ objectMode: true }); + stream.on('data', (row) => { + startOnce(); + handleRow(row); + }); + stream.on('end', (row) => { + startOnce(); + if (row) { + handleRow(row); + } + this.emit('runEnd', { + name: null, + status: runStatus, + counts: runCounts, + runtime: new Date().getTime() - runStartTime + }); + }); + } +}; diff --git a/package-lock.json b/package-lock.json index 2037a76..48d05b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2658,6 +2658,37 @@ } } }, + "deep-equal": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.0.5.tgz", + "integrity": "sha512-nPiRgmbAtm1a3JsnLCf6/SLfXcjyN5v8L1TXzdCmHrXJ4hx+gW/w1YCcn7z8gJtSiDArZCgYtbao3QqLm/N1Sw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "es-get-iterator": "^1.1.1", + "get-intrinsic": "^1.0.1", + "is-arguments": "^1.0.4", + "is-date-object": "^1.0.2", + "is-regex": "^1.1.1", + "isarray": "^2.0.5", + "object-is": "^1.1.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "regexp.prototype.flags": "^1.3.0", + "side-channel": "^1.0.3", + "which-boxed-primitive": "^1.0.1", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.2" + }, + "dependencies": { + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + } + } + }, "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", @@ -2694,6 +2725,12 @@ "object-keys": "^1.0.12" } }, + "defined": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", + "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", + "dev": true + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -2786,6 +2823,15 @@ "tslib": "^2.0.3" } }, + "dotignore": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dotignore/-/dotignore-0.1.2.tgz", + "integrity": "sha512-UGGGWfSauusaVJC+8fgV+NVvBXkCTmVv7sk6nojDZZvuOUNGUy0Zk4UpHQD6EDjS0jpBwcACvH4eofvyzBcRDw==", + "dev": true, + "requires": { + "minimatch": "^3.0.4" + } + }, "download": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/download/-/download-7.1.0.tgz", @@ -3029,6 +3075,41 @@ "string.prototype.trimstart": "^1.0.3" } }, + "es-get-iterator": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.2.tgz", + "integrity": "sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.0", + "has-symbols": "^1.0.1", + "is-arguments": "^1.1.0", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.5", + "isarray": "^2.0.5" + }, + "dependencies": { + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + } + } + }, "es-to-primitive": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", @@ -3875,6 +3956,15 @@ "integrity": "sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg==", "dev": true }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "requires": { + "is-callable": "^1.1.3" + } + }, "foreach": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", @@ -4620,6 +4710,12 @@ "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", "dev": true }, + "is-bigint": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.1.tgz", + "integrity": "sha512-J0ELF4yHFxHy0cmSxZuheDOz2luOdVvqjwmEcj8H/L1JHeuEDSDbeRP+Dk9kFVk5RTFzbucJ2Kb9F7ixY2QaCg==", + "dev": true + }, "is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -4629,6 +4725,15 @@ "binary-extensions": "^2.0.0" } }, + "is-boolean-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.0.tgz", + "integrity": "sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA==", + "dev": true, + "requires": { + "call-bind": "^1.0.0" + } + }, "is-callable": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", @@ -4683,6 +4788,12 @@ "is-extglob": "^2.1.1" } }, + "is-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", + "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "dev": true + }, "is-module": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", @@ -4707,6 +4818,12 @@ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, + "is-number-object": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.4.tgz", + "integrity": "sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw==", + "dev": true + }, "is-object": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.2.tgz", @@ -4743,6 +4860,12 @@ "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==", "dev": true }, + "is-set": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", + "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "dev": true + }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", @@ -4783,6 +4906,18 @@ "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", "dev": true }, + "is-weakmap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "dev": true + }, + "is-weakset": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.1.tgz", + "integrity": "sha512-pi4vhbhVHGLxohUw7PhGsueT4vRGFoXhP7+RGN0jKIv9+8PWYCQTqtADngrxOm2g46hoH0+g8uZZBzMrvVGDmw==", + "dev": true + }, "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", @@ -5876,6 +6011,16 @@ "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==", "dev": true }, + "object-is": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.4.tgz", + "integrity": "sha512-1ZvAZ4wlF7IyPVOcE1Omikt7UpaFlOQq0HlSti+ZvDH3UiD2brwGMwDbyV43jao2bKJ+4+WdPJHSd7kgzKYVqg==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + } + }, "object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", @@ -6776,6 +6921,15 @@ } } }, + "resumer": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/resumer/-/resumer-0.0.0.tgz", + "integrity": "sha1-8ej0YeQGS6Oegq883CqMiT0HZ1k=", + "dev": true, + "requires": { + "through": "~2.3.4" + } + }, "rfdc": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.2.0.tgz", @@ -7426,6 +7580,17 @@ "side-channel": "^1.0.3" } }, + "string.prototype.trim": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.3.tgz", + "integrity": "sha512-16IL9pIBA5asNOSukPfxX2W68BaBvxyiRK16H3RA/lWW9BDosh+w7f+LhomPHpXJ82QEe7w7/rY/S1CV97raLg==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1" + } + }, "string.prototype.trimend": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz", @@ -7561,6 +7726,31 @@ } } }, + "tape": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tape/-/tape-5.1.1.tgz", + "integrity": "sha512-ujhT+ZJPqSGY9Le02mIGBnyWo7Ks05FEGS9PnlqECr3sM3KyV4CSCXAvSBJKMN+t+aZYLKEFUEo0l4wFJMhppQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "deep-equal": "^2.0.5", + "defined": "^1.0.0", + "dotignore": "^0.1.2", + "for-each": "^0.3.3", + "glob": "^7.1.6", + "has": "^1.0.3", + "inherits": "^2.0.4", + "is-regex": "^1.1.1", + "minimist": "^1.2.5", + "object-inspect": "^1.9.0", + "object-is": "^1.1.4", + "object.assign": "^4.1.2", + "resolve": "^1.19.0", + "resumer": "^0.0.0", + "string.prototype.trim": "^1.2.3", + "through": "^2.3.8" + } + }, "tar-fs": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", @@ -8077,6 +8267,31 @@ "isexe": "^2.0.0" } }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, + "which-collection": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", + "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "dev": true, + "requires": { + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" + } + }, "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", diff --git a/package.json b/package.json index 5b049d7..6a5376e 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,8 @@ "rollup": "2.37.1", "semistandard": "16.0.0", "semver": "7.3.4", - "sinon": "1.17.4" + "sinon": "1.17.4", + "tape": "^5.1.1" }, "nyc": { "include": [ diff --git a/test/fixtures/integration/tape.js b/test/fixtures/integration/tape.js new file mode 100644 index 0000000..70d5d23 --- /dev/null +++ b/test/fixtures/integration/tape.js @@ -0,0 +1,60 @@ +const tape = require('tape'); + +tape.test('global test', function (t) { + t.ok(true); + t.end(); +}); + +tape.test('suite with passing test', function (t) { + t.test('should pass', function (t) { + t.ok(true); + t.end(); + }); + t.end(); +}); + +tape.test('suite with skipped test', function (t) { + t.skip('should skip', function (t) { + t.end(); + }); + t.end(); +}); + +tape.test('suite with failing test', function (t) { + t.test('should fail', function (t) { + t.fail(new Error('error')); + t.end(); + }); + t.end(); +}); + +tape.test('suite with tests', function (t) { + t.test('should pass', function (t) { + t.ok(true); + t.end(); + }); + t.test('should skip', { skip: true }, function (t) { + t.end(); + }); + t.test('should fail', function (t) { + t.fail(new Error('error')); + t.end(); + }); + t.end(); +}); + +tape.test('outer suite', function (t) { + t.test('outer test', function (t) { + t.ok(true); + t.end(); + }); + + t.test('inner suite', function (t) { + t.test('inner test', function (t) { + t.ok(true); + t.end(); + }); + t.end(); + }); + t.end(); +}); diff --git a/test/integration/adapters-run.js b/test/integration/adapters-run.js index 5f0806a..ba6d218 100644 --- a/test/integration/adapters-run.js +++ b/test/integration/adapters-run.js @@ -88,5 +88,16 @@ module.exports = { collectData(mochaRunner); mocha.run(); + }, + Tape: function (collectData) { + // Disable process exit by pretending to be a browser + process.browser = true; + + const tape = require('tape'); + const tapeRunner = new JsReporters.TapeAdapter(tape); + rerequire(path.join(testDir, 'tape.js')); + collectData(tapeRunner); + + delete process.browser; } }; diff --git a/test/integration/adapters.js b/test/integration/adapters.js index 7618629..bf0a1ff 100644 --- a/test/integration/adapters.js +++ b/test/integration/adapters.js @@ -66,9 +66,12 @@ function normalizeRunEnd (runEnd) { function fixExpectedData (adapter, expectedData) { expectedData.forEach(([eventName, data]) => { + if (eventName === 'runStart' && adapter === 'Tape') { + // Stating test plan is optional, tape doesn't. + data.counts.total = null; + } if (eventName === 'testEnd') { - // Don't expect passed assertion for testing frameworks - // that don't record all assertions. + // Recording passed assertions is optional, Mocha doesn't. if (adapter === 'Mocha' && data.status === 'passed') { data.assertions = []; }