From 3287a68eb1f176a6d0d85390b79c05ffc3c50615 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sat, 13 Nov 2021 16:30:25 -0800 Subject: [PATCH 01/18] [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `safe-publish-latest`, `array.prototype.flatmap` --- .eslintrc | 5 ++++- package.json | 8 ++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.eslintrc b/.eslintrc index 0f0ffc01..b1f1fd73 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,10 +1,13 @@ { "root": true, + "extends": "@ljharb", "env": { "browser": true, "node": true, }, - "extends": "@ljharb", + "parserOptions": { + "ecmaVersion": 5, + }, "globals": { "Promise": false, }, diff --git a/package.json b/package.json index 87c9881d..7ca2671d 100644 --- a/package.json +++ b/package.json @@ -26,17 +26,17 @@ "through": "~2.3.8" }, "devDependencies": { - "@ljharb/eslint-config": "^17.6.0", - "array.prototype.flatmap": "^1.2.4", + "@ljharb/eslint-config": "^19.0.1", + "array.prototype.flatmap": "^1.2.5", "aud": "^1.1.5", "concat-stream": "^1.6.2", "eclint": "^2.8.1", "ecstatic": "^4.1.4", "es-value-fixtures": "^1.2.1", - "eslint": "^7.31.0", + "eslint": "^8.2.0", "falafel": "^2.2.4", "js-yaml": "^3.14.0", - "safe-publish-latest": "^1.1.4", + "safe-publish-latest": "^2.0.0", "tap": "^8.0.1", "tap-parser": "^3.0.5" }, From 470ca1c0f41155d2c08537bb6cc94e04edd063bc Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sat, 13 Nov 2021 16:38:55 -0800 Subject: [PATCH 02/18] [Deps] update `glob`, `is-regex`, `string.prototype.trim` --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 7ca2671d..e3e5a23f 100644 --- a/package.json +++ b/package.json @@ -14,15 +14,15 @@ "defined": "~1.0.0", "dotignore": "~0.1.2", "for-each": "~0.3.3", - "glob": "~7.1.7", + "glob": "~7.2.0", "has": "~1.0.3", "inherits": "~2.0.4", - "is-regex": "~1.1.3", + "is-regex": "~1.1.4", "minimist": "~1.2.5", "object-inspect": "~1.11.0", "resolve": "~1.20.0", "resumer": "~0.0.0", - "string.prototype.trim": "~1.2.4", + "string.prototype.trim": "~1.2.5", "through": "~2.3.8" }, "devDependencies": { From 8d6aa6c4b084a8cec9860d868ac353862fc8b545 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 13 Oct 2021 10:32:15 -0700 Subject: [PATCH 03/18] [actions] update codecov uploader --- .github/workflows/node-4+.yml | 4 ++-- .github/workflows/node-iojs.yml | 4 ++-- .github/workflows/node-zero.yml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/node-4+.yml b/.github/workflows/node-4+.yml index 007f7736..dc07b85b 100644 --- a/.github/workflows/node-4+.yml +++ b/.github/workflows/node-4+.yml @@ -30,7 +30,7 @@ jobs: with: node-version: ${{ matrix.node-version }} - run: npm run tests-only - - uses: codecov/codecov-action@v1 + - uses: codecov/codecov-action@v2 minors: needs: [matrix, latest] @@ -50,7 +50,7 @@ jobs: with: node-version: ${{ matrix.node-version }} - run: npm run tests-only - - uses: codecov/codecov-action@v1 + - uses: codecov/codecov-action@v2 node: name: 'node 4+' diff --git a/.github/workflows/node-iojs.yml b/.github/workflows/node-iojs.yml index 891b73d0..ac64c55b 100644 --- a/.github/workflows/node-iojs.yml +++ b/.github/workflows/node-iojs.yml @@ -31,7 +31,7 @@ jobs: node-version: ${{ matrix.node-version }} skip-ls-check: true - run: npm run tests-only - - uses: codecov/codecov-action@v1 + - uses: codecov/codecov-action@v2 minors: needs: [matrix, latest] @@ -52,7 +52,7 @@ jobs: node-version: ${{ matrix.node-version }} skip-ls-check: true - run: npm run tests-only - - uses: codecov/codecov-action@v1 + - uses: codecov/codecov-action@v2 node: name: 'io.js' diff --git a/.github/workflows/node-zero.yml b/.github/workflows/node-zero.yml index 18dcd1a1..ad75396d 100644 --- a/.github/workflows/node-zero.yml +++ b/.github/workflows/node-zero.yml @@ -32,7 +32,7 @@ jobs: cache-node-modules-key: node_modules-${{ github.workflow }}-${{ github.action }}-${{ github.run_id }} skip-ls-check: true - run: npm run tests-only - - uses: codecov/codecov-action@v1 + - uses: codecov/codecov-action@v2 unstable: needs: [matrix, stable] @@ -54,7 +54,7 @@ jobs: cache-node-modules-key: node_modules-${{ github.workflow }}-${{ github.action }}-${{ github.run_id }} skip-ls-check: true - run: npm run tests-only - - uses: codecov/codecov-action@v1 + - uses: codecov/codecov-action@v2 node: name: 'node 0.x' From 87f9b293baeb48b507a4e9e16bba62c7ffcc4eb7 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 29 Sep 2021 13:50:21 -0700 Subject: [PATCH 04/18] [readme] port changes from v5 --- readme.markdown | 177 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 124 insertions(+), 53 deletions(-) diff --git a/readme.markdown b/readme.markdown index 3288a82c..f3c05a54 100644 --- a/readme.markdown +++ b/readme.markdown @@ -1,9 +1,15 @@ -# tape +# tape [![Version Badge][npm-version-svg][package-url]] tap-producing test harness for node and browsers [![github actions][actions-image]][actions-url] [![coverage][codecov-image]][codecov-url] +[![dependency status][deps-svg]][deps-url] +[![dev dependency status][dev-deps-svg]][dev-deps-url] +[![License][license-image]][license-url] +[![Downloads][downloads-image]][downloads-url] + +[![npm badge][npm-badge-png]][package-url] ![tape](https://web.archive.org/web/20170612184731if_/http://substack.net/images/tape_drive.png) @@ -44,19 +50,16 @@ not ok 2 should be equal # usage -You always need to `require('tape')` in test files. You can run the tests by -usual node means (`require('test-file.js')` or `node test-file.js`). You can -also run tests using the `tape` binary to utilize globbing, on Windows for -example: +You always need to `require('tape')` in test files. +You can run the tests by usual node means (`require('test-file.js')` or `node test-file.js`). +You can also run tests using the `tape` binary to utilize globbing, on Windows for example: ```sh $ tape tests/**/*.js ``` -`tape`'s arguments are passed to the -[`glob`](https://www.npmjs.com/package/glob) module. If you want `glob` to -perform the expansion on a system where the shell performs such expansion, quote -the arguments as necessary: +`tape`'s arguments are passed to the [`glob`](https://www.npmjs.com/package/glob) module. +If you want `glob` to perform the expansion on a system where the shell performs such expansion, quote the arguments as necessary: ```sh $ tape 'tests/**/*.js' @@ -91,8 +94,7 @@ Please note that all modules loaded using the `-r` flag will run *before* any te The default TAP output is good for machines and humans that are robots. -If you want a more colorful / pretty output there are lots of modules on npm -that will output something pretty if you pipe TAP into them: +If you want a more colorful / pretty output there are lots of modules on npm that will output something pretty if you pipe TAP into them: - [tap-spec](https://github.com/scottcorgan/tap-spec) - [tap-dot](https://github.com/scottcorgan/tap-dot) @@ -118,9 +120,9 @@ that will output something pretty if you pipe TAP into them: - [tap-nyc](https://github.com/MegaArman/tap-nyc) - [tap-spec (emoji patch)](https://github.com/Sceat/tap-spec-emoji) - [tape-repeater](https://github.com/rgruesbeck/tape-repeater) +- [tabe](https://github.com/Josenzo/tabe) -To use them, try `node test/index.js | tap-spec` or pipe it into one -of the modules of your choice! +To use them, try `node test/index.js | tap-spec` or pipe it into one of the modules of your choice! ## uncaught exceptions @@ -139,8 +141,7 @@ By default, uncaught exceptions in your tests will not be intercepted, and will # methods -The assertion methods in `tape` are heavily influenced or copied from the methods -in [node-tap](https://github.com/isaacs/node-tap). +The assertion methods in `tape` are heavily influenced or copied from the methods in [node-tap](https://github.com/isaacs/node-tap). ```js var test = require('tape') @@ -149,8 +150,7 @@ var test = require('tape') ## test([name], [opts], cb) Create a new test with an optional `name` string and optional `opts` object. -`cb(t)` fires with the new test object `t` once all preceding tests have -finished. Tests execute serially. +`cb(t)` fires with the new test object `t` once all preceding tests have finished. Tests execute serially. Available `opts` options are: - opts.skip = true/false. See test.skip. @@ -158,36 +158,38 @@ Available `opts` options are: - opts.objectPrintDepth = 5. Configure max depth of expected / actual object printing. Environmental variable `NODE_TAPE_OBJECT_PRINT_DEPTH` can set the desired default depth for all tests; locally-set values will take precedence. - opts.todo = true/false. Test will be allowed to fail. -If you forget to `t.plan()` out how many assertions you are going to run and you -don't call `t.end()` explicitly, your test will hang. +If you forget to `t.plan()` out how many assertions you are going to run and you don't call `t.end()` explicitly, your test will hang. ## test.skip([name], [opts], cb) Generate a new test that will be skipped over. -## test.teardown(cb) - -Register a callback to run after the individual test has completed. Multiple registered teardown callbacks will run in order. Useful for undoing side effects, closing network connections, etc. - ## test.onFinish(fn) -The onFinish hook will get invoked when ALL `tape` tests have finished -right before `tape` is about to print the test summary. +The onFinish hook will get invoked when ALL `tape` tests have finished right before `tape` is about to print the test summary. + +`fn` is called with no arguments, and its return value is ignored. ## test.onFailure(fn) The onFailure hook will get invoked whenever any `tape` tests has failed. +`fn` is called with no arguments, and its return value is ignored. + ## t.plan(n) -Declare that `n` assertions should be run. `t.end()` will be called -automatically after the `n`th assertion. If there are any more assertions after -the `n`th, or after `t.end()` is called, they will generate errors. +Declare that `n` assertions should be run. `t.end()` will be called automatically after the `n`th assertion. +If there are any more assertions after the `n`th, or after `t.end()` is called, they will generate errors. ## t.end(err) -Declare the end of a test explicitly. If `err` is passed in `t.end` will assert -that it is falsey. +Declare the end of a test explicitly. If `err` is passed in `t.end` will assert that it is falsy. + +Do not call `t.end()` if your test callback returns a Promise. + +## t.teardown(cb) + +Register a callback to run after the individual test has completed. Multiple registered teardown callbacks will run in order. Useful for undoing side effects, closing network connections, etc. ## t.fail(msg) @@ -219,8 +221,7 @@ Aliases: `t.false()`, `t.notok()` ## t.error(err, msg) -Assert that `err` is falsy. If `err` is non-falsy, use its `err.message` as the -description message. +Assert that `err` is falsy. If `err` is non-falsy, use its `err.message` as the description message. Aliases: `t.ifError()`, `t.ifErr()`, `t.iferror()` @@ -266,9 +267,7 @@ Aliases: `t.looseEqual()`, `t.looseEquals()` ## t.notDeepLooseEqual(actual, expected, msg) -Assert that `actual` and `expected` do not have the same structure and nested values using -[node's deepEqual() algorithm](https://github.com/substack/node-deep-equal) -with loose comparisons (`==`) on leaf nodes and an optional description of the assertion `msg`. +Assert that `actual` and `expected` do not have the same structure and nested values using [node's deepEqual() algorithm](https://github.com/substack/node-deep-equal) with loose comparisons (`==`) on leaf nodes and an optional description of the assertion `msg`. Aliases: `t.notLooseEqual()`, `t.notLooseEquals()` @@ -319,13 +318,13 @@ Please note that the second parameter, `expected`, cannot be of type `string`. I ## t.doesNotThrow(fn, expected, msg) -Assert that the function call `fn()` does not throw an exception. `expected`, if present, limits what should not be thrown. For example, set `expected` to `/user/` to fail the test only if the string representation of the exception contains the word `user`. Any other exception would pass the test. If `expected` is omitted, any exception will fail the test. `msg` is an optional description of the assertion. +Assert that the function call `fn()` does not throw an exception. `expected`, if present, limits what should not be thrown, and must be a `RegExp` or `Function`. The `RegExp` matches the string representation of the exception, as generated by `err.toString()`. For example, if you set `expected` to `/user/`, the test will fail only if the string representation of the exception contains the word `user`. Any other exception will result in a passed test. The `Function` is the exception thrown (e.g. `Error`). If `expected` is not of type `RegExp` or `Function`, or omitted entirely, any exception will result in a failed test. `msg` is an optional description of the assertion. + +Please note that the second parameter, `expected`, cannot be of type `string`. If a value of type `string` is provided for `expected`, then `t.doesNotThrows(fn, expected, msg)` will execute, but the value of `expected` will be set to `undefined`, and the specified string will be set as the value for the `msg` parameter (regardless of what _actually_ passed as the third parameter). This can cause unexpected results, so please be mindful. ## t.test(name, [opts], cb) -Create a subtest with a new test handle `st` from `cb(st)` inside the current -test `t`. `cb(st)` will only fire when `t` finishes. Additional tests queued up -after `t` will not be run until all subtests finish. +Create a subtest with a new test handle `st` from `cb(st)` inside the current test `t`. `cb(st)` will only fire when `t` finishes. Additional tests queued up after `t` will not be run until all subtests finish. You may pass the same options that [`test()`](#testname-opts-cb) accepts. @@ -333,6 +332,8 @@ You may pass the same options that [`test()`](#testname-opts-cb) accepts. Print a message without breaking the tap output. (Useful when using e.g. `tap-colorize` where output is buffered & `console.log` will print in incorrect order vis-a-vis tap output.) +Multiline output will be split by `\n` characters, and each one printed as a comment. + ## t.match(string, regexp, message) Assert that `string` matches the RegExp `regexp`. Will throw (not just fail) when the first two arguments are the wrong type. @@ -343,25 +344,17 @@ Assert that `string` does not match the RegExp `regexp`. Will throw (not just fa ## var htest = test.createHarness() -Create a new test harness instance, which is a function like `test()`, but with -a new pending stack and test state. +Create a new test harness instance, which is a function like `test()`, but with a new pending stack and test state. -By default the TAP output goes to `console.log()`. You can pipe the output to -someplace else if you `htest.createStream().pipe()` to a destination stream on -the first tick. +By default the TAP output goes to `console.log()`. You can pipe the output to someplace else if you `htest.createStream().pipe()` to a destination stream on the first tick. ## test.only([name], [opts], cb) -Like `test([name], [opts], cb)` except if you use `.only` this is the only test case -that will run for the entire process, all other test cases using `tape` will -be ignored. +Like `test([name], [opts], cb)` except if you use `.only` this is the only test case that will run for the entire process, all other test cases using `tape` will be ignored. ## var stream = test.createStream(opts) -Create a stream of output, bypassing the default output stream that writes -messages to `console.log()`. By default `stream` will be a text stream of TAP -output, but you can get an object stream instead by setting `opts.objectMode` to -`true`. +Create a stream of output, bypassing the default output stream that writes messages to `console.log()`. By default `stream` will be a text stream of TAP output, but you can get an object stream instead by setting `opts.objectMode` to `true`. ### tap stream reporter @@ -378,8 +371,7 @@ process.argv.slice(2).forEach(function (file) { }); ``` -You could substitute `process.stdout` for whatever other output stream you want, -like a network connection or a file. +You could substitute `process.stdout` for whatever other output stream you want, like a network connection or a file. Pass in test files to run as arguments: @@ -460,10 +452,89 @@ With [npm](https://npmjs.org) do: npm install tape --save-dev ``` +# troubleshooting + +Sometimes `t.end()` doesn’t preserve the expected output ordering. + +For instance the following: + +```js +var test = require('tape'); + +test('first', function (t) { + + setTimeout(function () { + t.ok(1, 'first test'); + t.end(); + }, 200); + + t.test('second', function (t) { + t.ok(1, 'second test'); + t.end(); + }); +}); + +test('third', function (t) { + setTimeout(function () { + t.ok(1, 'third test'); + t.end(); + }, 100); +}); +``` + +will output: + +``` +ok 1 second test +ok 2 third test +ok 3 first test +``` + +because `second` and `third` assume `first` has ended before it actually does. + +Use `t.plan()` instead to let other tests know they should wait: + +```diff +var test = require('tape'); + +test('first', function (t) { + ++ t.plan(2); + + setTimeout(function () { + t.ok(1, 'first test'); +- t.end(); + }, 200); + + t.test('second', function (t) { + t.ok(1, 'second test'); + t.end(); + }); +}); + +test('third', function (t) { + setTimeout(function () { + t.ok(1, 'third test'); + t.end(); + }, 100); +}); +``` + # license MIT +[package-url]: https://npmjs.org/package/tape +[npm-version-svg]: https://versionbadg.es/substack/tape.svg +[deps-svg]: https://david-dm.org/substack/tape.svg +[deps-url]: https://david-dm.org/substack/tape +[dev-deps-svg]: https://david-dm.org/substack/tape/dev-status.svg +[dev-deps-url]: https://david-dm.org/substack/tape#info=devDependencies +[npm-badge-png]: https://nodei.co/npm/tape.png?downloads=true&stars=true +[license-image]: https://img.shields.io/npm/l/tape.svg +[license-url]: LICENSE +[downloads-image]: https://img.shields.io/npm/dm/tape.svg +[downloads-url]: https://npm-stat.com/charts.html?package=tape [codecov-image]: https://codecov.io/gh/substack/tape/branch/master/graphs/badge.svg [codecov-url]: https://app.codecov.io/gh/substack/tape/ [actions-image]: https://img.shields.io/endpoint?url=https://github-actions-badge-u3jn4tfpocch.runkit.sh/substack/tape From 51ae645c8325d6037d4389260a442e27c2efce73 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 24 Jan 2022 19:50:50 -0800 Subject: [PATCH 05/18] [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `aud` --- .eslintignore | 2 -- .eslintrc | 10 ++++------ package.json | 6 +++--- 3 files changed, 7 insertions(+), 11 deletions(-) delete mode 100644 .eslintignore diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index ed88d37f..00000000 --- a/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -coverage/ -.nyc_output/ diff --git a/.eslintrc b/.eslintrc index b1f1fd73..365f8bc0 100644 --- a/.eslintrc +++ b/.eslintrc @@ -7,17 +7,13 @@ }, "parserOptions": { "ecmaVersion": 5, - }, - "globals": { - "Promise": false, + "allowReserved": false, }, "rules": { "array-bracket-spacing": "off", "complexity": "off", - "eqeqeq": ["error", "always", { "null": "ignore" }], "func-style": "warn", "indent": ["error", 4], - "no-magic-numbers": "off", "max-lines": "warn", "max-lines-per-function": "warn", "max-statements": "warn", @@ -30,7 +26,9 @@ "operator-linebreak": ["error", "before"], "sort-keys": "warn", }, - "ignorePatterns": [ "syntax-error.*" ], + "ignorePatterns": [ + "syntax-error.*", + ], "overrides": [ { "files": ["*.mjs", "test/import/package_type/*.js"], diff --git a/package.json b/package.json index e3e5a23f..68a5df94 100644 --- a/package.json +++ b/package.json @@ -26,14 +26,14 @@ "through": "~2.3.8" }, "devDependencies": { - "@ljharb/eslint-config": "^19.0.1", + "@ljharb/eslint-config": "^20.2.0", "array.prototype.flatmap": "^1.2.5", - "aud": "^1.1.5", + "aud": "^2.0.0", "concat-stream": "^1.6.2", "eclint": "^2.8.1", "ecstatic": "^4.1.4", "es-value-fixtures": "^1.2.1", - "eslint": "^8.2.0", + "eslint": "^8.7.0", "falafel": "^2.2.4", "js-yaml": "^3.14.0", "safe-publish-latest": "^2.0.0", From b803fd8e24b1dff96cd43092e727826873c6c571 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Fri, 6 Aug 2021 16:17:02 -0700 Subject: [PATCH 06/18] [Fix] `bin/tape`: delay requires until needed This ensures that `--require`s happen before everything tape needs, as much as possible --- bin/tape | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/bin/tape b/bin/tape index a0c755a1..fbd58152 100755 --- a/bin/tape +++ b/bin/tape @@ -2,12 +2,7 @@ 'use strict'; -var resolveModule = require('resolve').sync; -var resolvePath = require('path').resolve; -var readFileSync = require('fs').readFileSync; var parseOpts = require('minimist'); -var glob = require('glob'); -var ignore = require('dotignore'); var opts = parseOpts(process.argv.slice(2), { alias: { r: 'require', i: 'ignore' }, @@ -21,23 +16,32 @@ if (typeof opts.require === 'string') { opts.require = [opts.require]; } +var resolveModule; opts.require.forEach(function (module) { if (module) { + if (!resolveModule) { resolveModule = require('resolve').sync; } // This check ensures we ignore `-r ""`, trailing `-r`, or other silly things the user might (inadvertently) be doing. require(resolveModule(module, { basedir: cwd })); } }); +var resolvePath = require('path').resolve; + +var matcher; if (typeof opts.ignore === 'string') { + var readFileSync = require('fs').readFileSync; try { var ignoreStr = readFileSync(resolvePath(cwd, opts.ignore || '.gitignore'), 'utf-8'); } catch (e) { console.error(e.message); process.exit(2); } - var matcher = ignore.createMatcher(ignoreStr); + var ignore = require('dotignore'); + matcher = ignore.createMatcher(ignoreStr); } +var glob = require('glob'); + opts._.forEach(function (arg) { // If glob does not match, `files` will be an empty array. Note: `glob.sync` may throw an error and crash the node process. var files = glob.sync(arg); From 50ea080561a71d8a6a2d41955441c8a094039227 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 24 Jan 2022 19:54:32 -0800 Subject: [PATCH 07/18] [Deps] update `object-inspect`, `resolve` --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 68a5df94..8c9265d0 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,8 @@ "inherits": "~2.0.4", "is-regex": "~1.1.4", "minimist": "~1.2.5", - "object-inspect": "~1.11.0", - "resolve": "~1.20.0", + "object-inspect": "~1.12.0", + "resolve": "~1.22.0", "resumer": "~0.0.0", "string.prototype.trim": "~1.2.5", "through": "~2.3.8" From 418dc94b14b440568fb58aa666df5cb2861fe22c Mon Sep 17 00:00:00 2001 From: Federico Brigante Date: Mon, 13 Sep 2021 03:02:43 +0700 Subject: [PATCH 08/18] [meta] Exclude `fs` from browser bundles (#565) --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index 8c9265d0..d11effd5 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,9 @@ "version": "4.14.0", "description": "tap-producing test harness for node and browsers", "main": "index.js", + "browser": { + "fs": false + }, "bin": "./bin/tape", "directories": { "example": "example", From f79acdfb850d94f71a01970bff96337f52bb4e5a Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sun, 14 Nov 2021 23:58:56 -0800 Subject: [PATCH 09/18] [Tests] handle carriage returns in stack traces on Windows See https://github.com/substack/tape/pull/571#issuecomment-968173432 --- test/common.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/common.js b/test/common.js index eedc70ca..53ab4f83 100644 --- a/test/common.js +++ b/test/common.js @@ -45,7 +45,7 @@ var stripChangingData = function (line) { module.exports.stripFullStack = function (output) { var stripped = ' [... stack stripped ...]'; - var withDuplicates = output.split('\n').map(stripChangingData).map(function (line) { + var withDuplicates = output.split(/\r?\n/g).map(stripChangingData).map(function (line) { var m = line.match(/[ ]{8}at .*\((.*)\)/); if (m && m[1].slice(0, 5) !== '$TEST') { @@ -73,7 +73,7 @@ module.exports.stripFullStack = function (output) { 'at$1 $2' ).replace( // Handle stack trace variation in Node v0.8 - /(\[\.\.\. stack stripped \.\.\.\]\n *at) \(([^)]+)\)/g, + /(\[\.\.\. stack stripped \.\.\.\]\r?\n *at) \(([^)]+)\)/g, '$1 $2' - ).split('\n'); + ).split(/\r?\n/g); }; From fe6978d0a87a881a59ba7de23f2e68ff70d31074 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Tue, 11 Jan 2022 11:52:40 -0800 Subject: [PATCH 10/18] [meta] better `eccheck` command --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index d11effd5..13a1cf62 100644 --- a/package.json +++ b/package.json @@ -46,8 +46,8 @@ "scripts": { "prepublishOnly": "safe-publish-latest", "prepublish": "!(type not-in-publish) || not-in-publish || npm run prepublishOnly", - "prelint": "eclint check", - "lint": "eslint . bin/*", + "prelint": "eclint check $(git ls-files | xargs find 2> /dev/null | grep -vE 'node_modules|\\.git')", + "lint": "eslint --ext .js,.cjs,.mjs . bin/*", "pretest": "npm run lint", "test": "npm run tests-only", "posttest": "aud --production", From 82af5ed68577c15526e5dc7ecd290f2e88494170 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sat, 13 Nov 2021 16:46:37 -0800 Subject: [PATCH 11/18] [readme] hard wraps bad, soft wraps good --- readme.markdown | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/readme.markdown b/readme.markdown index f3c05a54..0e60b68f 100644 --- a/readme.markdown +++ b/readme.markdown @@ -150,7 +150,8 @@ var test = require('tape') ## test([name], [opts], cb) Create a new test with an optional `name` string and optional `opts` object. -`cb(t)` fires with the new test object `t` once all preceding tests have finished. Tests execute serially. +`cb(t)` fires with the new test object `t` once all preceding tests have finished. +Tests execute serially. Available `opts` options are: - opts.skip = true/false. See test.skip. @@ -241,17 +242,13 @@ Aliases: `t.notEquals()`, `t.notStrictEqual()`, `t.notStrictEquals()`, ## t.deepEqual(actual, expected, msg) -Assert that `actual` and `expected` have the same structure and nested values using -[node's deepEqual() algorithm](https://github.com/substack/node-deep-equal) -with strict comparisons (`===`) on leaf nodes and an optional description of the assertion `msg`. +Assert that `actual` and `expected` have the same structure and nested values using [node's deepEqual() algorithm](https://github.com/substack/node-deep-equal) with strict comparisons (`===`) on leaf nodes and an optional description of the assertion `msg`. Aliases: `t.deepEquals()`, `t.isEquivalent()`, `t.same()` ## t.notDeepEqual(actual, expected, msg) -Assert that `actual` and `expected` do not have the same structure and nested values using -[node's deepEqual() algorithm](https://github.com/substack/node-deep-equal) -with strict comparisons (`===`) on leaf nodes and an optional description of the assertion `msg`. +Assert that `actual` and `expected` do not have the same structure and nested values using [node's deepEqual() algorithm](https://github.com/substack/node-deep-equal) with strict comparisons (`===`) on leaf nodes and an optional description of the assertion `msg`. Aliases: `t.notDeepEquals`, `t.notEquivalent()`, `t.notDeeply()`, `t.notSame()`, `t.isNotDeepEqual()`, `t.isNotDeeply()`, `t.isNotEquivalent()`, @@ -259,9 +256,7 @@ Aliases: `t.notDeepEquals`, `t.notEquivalent()`, `t.notDeeply()`, `t.notSame()`, ## t.deepLooseEqual(actual, expected, msg) -Assert that `actual` and `expected` have the same structure and nested values using -[node's deepEqual() algorithm](https://github.com/substack/node-deep-equal) -with loose comparisons (`==`) on leaf nodes and an optional description of the assertion `msg`. +Assert that `actual` and `expected` have the same structure and nested values using [node's deepEqual() algorithm](https://github.com/substack/node-deep-equal) with loose comparisons (`==`) on leaf nodes and an optional description of the assertion `msg`. Aliases: `t.looseEqual()`, `t.looseEquals()` From d3b4f467445bb6da3a2e617c4b29e71528f32425 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Tue, 25 Jan 2022 20:04:10 -0800 Subject: [PATCH 12/18] [actions] reuse common workflows --- .github/workflows/node-4+.yml | 60 --------------------- .github/workflows/node-aught.yml | 18 +++++++ .github/workflows/node-iojs.yml | 62 ---------------------- .github/workflows/node-pretest.yml | 23 +------- .github/workflows/node-tens.yml | 18 +++++++ .github/workflows/node-zero.yml | 64 ----------------------- .github/workflows/rebase.yml | 8 +-- .github/workflows/require-allow-edits.yml | 2 +- 8 files changed, 43 insertions(+), 212 deletions(-) delete mode 100644 .github/workflows/node-4+.yml create mode 100644 .github/workflows/node-aught.yml delete mode 100644 .github/workflows/node-iojs.yml create mode 100644 .github/workflows/node-tens.yml delete mode 100644 .github/workflows/node-zero.yml diff --git a/.github/workflows/node-4+.yml b/.github/workflows/node-4+.yml deleted file mode 100644 index dc07b85b..00000000 --- a/.github/workflows/node-4+.yml +++ /dev/null @@ -1,60 +0,0 @@ -name: 'Tests: node.js' - -on: [pull_request, push] - -jobs: - matrix: - runs-on: ubuntu-latest - outputs: - latest: ${{ steps.set-matrix.outputs.requireds }} - minors: ${{ steps.set-matrix.outputs.optionals }} - steps: - - uses: ljharb/actions/node/matrix@main - id: set-matrix - with: - preset: '>=4' - - latest: - needs: [matrix] - name: 'latest minors' - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.matrix.outputs.latest) }} - - steps: - - uses: actions/checkout@v2 - - uses: ljharb/actions/node/install@main - name: 'nvm install ${{ matrix.node-version }} && npm install' - with: - node-version: ${{ matrix.node-version }} - - run: npm run tests-only - - uses: codecov/codecov-action@v2 - - minors: - needs: [matrix, latest] - name: 'non-latest minors' - continue-on-error: true - if: ${{ !github.head_ref || !startsWith(github.head_ref, 'renovate') }} - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.matrix.outputs.minors) }} - - steps: - - uses: actions/checkout@v2 - - uses: ljharb/actions/node/install@main - name: 'nvm install ${{ matrix.node-version }} && npm install' - with: - node-version: ${{ matrix.node-version }} - - run: npm run tests-only - - uses: codecov/codecov-action@v2 - - node: - name: 'node 4+' - needs: [latest, minors] - runs-on: ubuntu-latest - steps: - - run: 'echo tests completed' diff --git a/.github/workflows/node-aught.yml b/.github/workflows/node-aught.yml new file mode 100644 index 00000000..f3cddd85 --- /dev/null +++ b/.github/workflows/node-aught.yml @@ -0,0 +1,18 @@ +name: 'Tests: node.js < 10' + +on: [pull_request, push] + +jobs: + tests: + uses: ljharb/actions/.github/workflows/node.yml@main + with: + range: '< 10' + type: minors + command: npm run tests-only + + node: + name: 'node < 10' + needs: [tests] + runs-on: ubuntu-latest + steps: + - run: 'echo tests completed' diff --git a/.github/workflows/node-iojs.yml b/.github/workflows/node-iojs.yml deleted file mode 100644 index ac64c55b..00000000 --- a/.github/workflows/node-iojs.yml +++ /dev/null @@ -1,62 +0,0 @@ -name: 'Tests: node.js (io.js)' - -on: [pull_request, push] - -jobs: - matrix: - runs-on: ubuntu-latest - outputs: - latest: ${{ steps.set-matrix.outputs.requireds }} - minors: ${{ steps.set-matrix.outputs.optionals }} - steps: - - uses: ljharb/actions/node/matrix@main - id: set-matrix - with: - preset: 'iojs' - - latest: - needs: [matrix] - name: 'latest minors' - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.matrix.outputs.latest) }} - - steps: - - uses: actions/checkout@v2 - - uses: ljharb/actions/node/install@main - name: 'nvm install ${{ matrix.node-version }} && npm install' - with: - node-version: ${{ matrix.node-version }} - skip-ls-check: true - - run: npm run tests-only - - uses: codecov/codecov-action@v2 - - minors: - needs: [matrix, latest] - name: 'non-latest minors' - continue-on-error: true - if: ${{ !github.head_ref || !startsWith(github.head_ref, 'renovate') }} - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.matrix.outputs.minors) }} - - steps: - - uses: actions/checkout@v2 - - uses: ljharb/actions/node/install@main - name: 'nvm install ${{ matrix.node-version }} && npm install' - with: - node-version: ${{ matrix.node-version }} - skip-ls-check: true - - run: npm run tests-only - - uses: codecov/codecov-action@v2 - - node: - name: 'io.js' - needs: [latest, minors] - runs-on: ubuntu-latest - steps: - - run: 'echo tests completed' diff --git a/.github/workflows/node-pretest.yml b/.github/workflows/node-pretest.yml index 969d1386..765edf79 100644 --- a/.github/workflows/node-pretest.yml +++ b/.github/workflows/node-pretest.yml @@ -3,24 +3,5 @@ name: 'Tests: pretest/posttest' on: [pull_request, push] jobs: - pretest: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - uses: ljharb/actions/node/install@main - name: 'nvm install lts/* && npm install' - with: - node-version: 'lts/*' - - run: npm run pretest - - posttest: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - uses: ljharb/actions/node/install@main - name: 'nvm install lts/* && npm install' - with: - node-version: 'lts/*' - - run: npm run posttest + tests: + uses: ljharb/actions/.github/workflows/pretest.yml@main diff --git a/.github/workflows/node-tens.yml b/.github/workflows/node-tens.yml new file mode 100644 index 00000000..b49ceb1f --- /dev/null +++ b/.github/workflows/node-tens.yml @@ -0,0 +1,18 @@ +name: 'Tests: node.js >= 10' + +on: [pull_request, push] + +jobs: + tests: + uses: ljharb/actions/.github/workflows/node.yml@main + with: + range: '>= 10' + type: minors + command: npm run tests-only + + node: + name: 'node >= 10' + needs: [tests] + runs-on: ubuntu-latest + steps: + - run: 'echo tests completed' diff --git a/.github/workflows/node-zero.yml b/.github/workflows/node-zero.yml deleted file mode 100644 index ad75396d..00000000 --- a/.github/workflows/node-zero.yml +++ /dev/null @@ -1,64 +0,0 @@ -name: 'Tests: node.js (0.x)' - -on: [pull_request, push] - -jobs: - matrix: - runs-on: ubuntu-latest - outputs: - stable: ${{ steps.set-matrix.outputs.requireds }} - unstable: ${{ steps.set-matrix.outputs.optionals }} - steps: - - uses: ljharb/actions/node/matrix@main - id: set-matrix - with: - preset: '0.x' - - stable: - needs: [matrix] - name: 'stable minors' - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.matrix.outputs.stable) }} - - steps: - - uses: actions/checkout@v2 - - uses: ljharb/actions/node/install@main - name: 'nvm install ${{ matrix.node-version }} && npm install' - with: - node-version: ${{ matrix.node-version }} - cache-node-modules-key: node_modules-${{ github.workflow }}-${{ github.action }}-${{ github.run_id }} - skip-ls-check: true - - run: npm run tests-only - - uses: codecov/codecov-action@v2 - - unstable: - needs: [matrix, stable] - name: 'unstable minors' - continue-on-error: true - if: ${{ !github.head_ref || !startsWith(github.head_ref, 'renovate') }} - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: ${{ fromJson(needs.matrix.outputs.unstable) }} - - steps: - - uses: actions/checkout@v2 - - uses: ljharb/actions/node/install@main - name: 'nvm install ${{ matrix.node-version }} && npm install' - with: - node-version: ${{ matrix.node-version }} - cache-node-modules-key: node_modules-${{ github.workflow }}-${{ github.action }}-${{ github.run_id }} - skip-ls-check: true - - run: npm run tests-only - - uses: codecov/codecov-action@v2 - - node: - name: 'node 0.x' - needs: [stable, unstable] - runs-on: ubuntu-latest - steps: - - run: 'echo tests completed' diff --git a/.github/workflows/rebase.yml b/.github/workflows/rebase.yml index 027aed07..5b6d04b8 100644 --- a/.github/workflows/rebase.yml +++ b/.github/workflows/rebase.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: ljharb/rebase@master - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - uses: actions/checkout@v2 + - uses: ljharb/rebase@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/require-allow-edits.yml b/.github/workflows/require-allow-edits.yml index 549d7b48..7b842f89 100644 --- a/.github/workflows/require-allow-edits.yml +++ b/.github/workflows/require-allow-edits.yml @@ -9,4 +9,4 @@ jobs: runs-on: ubuntu-latest steps: - - uses: ljharb/require-allow-edits@main + - uses: ljharb/require-allow-edits@main From a1c266bf9577420702e1067c40a4a65677add63a Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Sat, 25 Dec 2021 20:27:59 -0800 Subject: [PATCH 13/18] [New] `t.match`/`t.doesNotMatch: fail the test instead of throw on wrong input types. Turns out throwing is much less useful than failing the test. --- lib/test.js | 90 ++++++++++++++++---------- readme.markdown | 4 +- test/match.js | 167 ++++++++++++++++++++++++++++++++++++------------ 3 files changed, 185 insertions(+), 76 deletions(-) diff --git a/lib/test.js b/lib/test.js index 94fb175c..37a81e00 100644 --- a/lib/test.js +++ b/lib/test.js @@ -14,6 +14,7 @@ var inspect = require('object-inspect'); var isEnumerable = callBound('Object.prototype.propertyIsEnumerable'); var toLowerCase = callBound('String.prototype.toLowerCase'); var $test = callBound('RegExp.prototype.test'); +var objectToString = callBound('Object.prototype.toString'); module.exports = Test; @@ -593,45 +594,68 @@ Test.prototype.doesNotThrow = function (fn, expected, msg, extra) { Test.prototype.match = function match(string, regexp, msg, extra) { if (!isRegExp(regexp)) { - throw new TypeError('The "regexp" argument must be an instance of RegExp. Received type ' + typeof regexp + ' (' + inspect(regexp) + ')'); - } - if (typeof string !== 'string') { - throw new TypeError('The "string" argument must be of type string. Received type ' + typeof string + ' (' + inspect(string) + ')'); + 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 = $test(regexp, string); + 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 + }); } - - var matches = $test(regexp, string); - 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)) { - throw new TypeError('The "regexp" argument must be an instance of RegExp. Received type ' + typeof regexp + ' (' + inspect(regexp) + ')'); - } - if (typeof string !== 'string') { - throw new TypeError('The "string" argument must be of type string. Received type ' + typeof string + ' (' + inspect(string) + ')'); + 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 = $test(regexp, string); + 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 + }); } - var matches = $test(regexp, string); - 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 - }); }; // eslint-disable-next-line no-unused-vars diff --git a/readme.markdown b/readme.markdown index 0e60b68f..c322bb50 100644 --- a/readme.markdown +++ b/readme.markdown @@ -331,11 +331,11 @@ Multiline output will be split by `\n` characters, and each one printed as a com ## t.match(string, regexp, message) -Assert that `string` matches the RegExp `regexp`. Will throw (not just fail) when the first two arguments are the wrong type. +Assert that `string` matches the RegExp `regexp`. Will fail when the first two arguments are the wrong type. ## t.doesNotMatch(string, regexp, message) -Assert that `string` does not match the RegExp `regexp`. Will throw (not just fail) when the first two arguments are the wrong type. +Assert that `string` does not match the RegExp `regexp`. Will fail when the first two arguments are the wrong type. ## var htest = test.createHarness() diff --git a/test/match.js b/test/match.js index 407d6b5d..c7c6a097 100644 --- a/test/match.js +++ b/test/match.js @@ -14,9 +14,55 @@ tap.test('match', function (tt) { tt.same(stripFullStack(rows.toString('utf8')), [ 'TAP version 13', '# match', - 'ok 1 regex arg must be a regex', - 'ok 2 string arg must be a string', - 'not ok 3 The input did not match the regular expression /abc/. Input: \'string\'', + 'not ok 1 The "regexp" argument must be an instance of RegExp. Received type string (\'string\')', + ' ---', + ' operator: match', + ' expected: \'[object RegExp]\'', + ' actual: \'[object String]\'', + ' at: Test. ($TEST/match.js:$LINE:$COL)', + ' stack: |-', + ' Error: The "regexp" argument must be an instance of RegExp. Received type string (\'string\')', + ' [... stack stripped ...]', + ' at Test. ($TEST/match.js:$LINE:$COL)', + ' [... stack stripped ...]', + ' ...', + 'not ok 2 regex arg must not be a string', + ' ---', + ' operator: match', + ' expected: \'[object RegExp]\'', + ' actual: \'[object String]\'', + ' at: Test. ($TEST/match.js:$LINE:$COL)', + ' stack: |-', + ' Error: regex arg must not be a string', + ' [... stack stripped ...]', + ' at Test. ($TEST/match.js:$LINE:$COL)', + ' [... stack stripped ...]', + ' ...', + 'not ok 3 The "string" argument must be of type string. Received type object ({ abc: 123 })', + ' ---', + ' operator: match', + ' expected: \'string\'', + ' actual: \'object\'', + ' at: Test. ($TEST/match.js:$LINE:$COL)', + ' stack: |-', + ' Error: The "string" argument must be of type string. Received type object ({ abc: 123 })', + ' [... stack stripped ...]', + ' at Test. ($TEST/match.js:$LINE:$COL)', + ' [... stack stripped ...]', + ' ...', + 'not ok 4 string arg must not be an object', + ' ---', + ' operator: match', + ' expected: \'string\'', + ' actual: \'object\'', + ' at: Test. ($TEST/match.js:$LINE:$COL)', + ' stack: |-', + ' Error: string arg must not be an object', + ' [... stack stripped ...]', + ' at Test. ($TEST/match.js:$LINE:$COL)', + ' [... stack stripped ...]', + ' ...', + 'not ok 5 The input did not match the regular expression /abc/. Input: \'string\'', ' ---', ' operator: match', ' expected: /abc/', @@ -28,7 +74,7 @@ tap.test('match', function (tt) { ' at Test. ($TEST/match.js:$LINE:$COL)', ' [... stack stripped ...]', ' ...', - 'not ok 4 "string" does not match /abc/', + 'not ok 6 "string" does not match /abc/', ' ---', ' operator: match', ' expected: /abc/', @@ -40,13 +86,13 @@ tap.test('match', function (tt) { ' at Test. ($TEST/match.js:$LINE:$COL)', ' [... stack stripped ...]', ' ...', - 'ok 5 The input matched the regular expression /pass$/. Input: \'I will pass\'', - 'ok 6 "I will pass" matches /pass$/', + 'ok 7 The input matched the regular expression /pass$/. Input: \'I will pass\'', + 'ok 8 "I will pass" matches /pass$/', '', - '1..6', - '# tests 6', - '# pass 4', - '# fail 2', + '1..8', + '# tests 8', + '# pass 2', + '# fail 6', '' ]); }; @@ -54,19 +100,13 @@ tap.test('match', function (tt) { test.createStream().pipe(concat(tc)); test('match', function (t) { - t.plan(6); + t.plan(8); - t['throws']( - function () { t.match(/abc/, 'string'); }, - TypeError, - 'regex arg must be a regex' - ); + t.match(/abc/, 'string'); + t.match(/abc/, 'string', 'regex arg must not be a string'); - t['throws']( - function () { t.match({ abc: 123 }, /abc/); }, - TypeError, - 'string arg must be a string' - ); + t.match({ abc: 123 }, /abc/); + t.match({ abc: 123 }, /abc/, 'string arg must not be an object'); t.match('string', /abc/); t.match('string', /abc/, '"string" does not match /abc/'); @@ -86,9 +126,55 @@ tap.test('doesNotMatch', function (tt) { tt.same(stripFullStack(rows.toString('utf8')), [ 'TAP version 13', '# doesNotMatch', - 'ok 1 regex arg must be a regex', - 'ok 2 string arg must be a string', - 'not ok 3 The input was expected to not match the regular expression /string/. Input: \'string\'', + 'not ok 1 The "regexp" argument must be an instance of RegExp. Received type string (\'string\')', + ' ---', + ' operator: doesNotMatch', + ' expected: \'[object RegExp]\'', + ' actual: \'[object String]\'', + ' at: Test. ($TEST/match.js:$LINE:$COL)', + ' stack: |-', + ' Error: The "regexp" argument must be an instance of RegExp. Received type string (\'string\')', + ' [... stack stripped ...]', + ' at Test. ($TEST/match.js:$LINE:$COL)', + ' [... stack stripped ...]', + ' ...', + 'not ok 2 regex arg must not be a string', + ' ---', + ' operator: doesNotMatch', + ' expected: \'[object RegExp]\'', + ' actual: \'[object String]\'', + ' at: Test. ($TEST/match.js:$LINE:$COL)', + ' stack: |-', + ' Error: regex arg must not be a string', + ' [... stack stripped ...]', + ' at Test. ($TEST/match.js:$LINE:$COL)', + ' [... stack stripped ...]', + ' ...', + 'not ok 3 The "string" argument must be of type string. Received type object ({ abc: 123 })', + ' ---', + ' operator: doesNotMatch', + ' expected: \'string\'', + ' actual: \'object\'', + ' at: Test. ($TEST/match.js:$LINE:$COL)', + ' stack: |-', + ' Error: The "string" argument must be of type string. Received type object ({ abc: 123 })', + ' [... stack stripped ...]', + ' at Test. ($TEST/match.js:$LINE:$COL)', + ' [... stack stripped ...]', + ' ...', + 'not ok 4 string arg must not be an object', + ' ---', + ' operator: doesNotMatch', + ' expected: \'string\'', + ' actual: \'object\'', + ' at: Test. ($TEST/match.js:$LINE:$COL)', + ' stack: |-', + ' Error: string arg must not be an object', + ' [... stack stripped ...]', + ' at Test. ($TEST/match.js:$LINE:$COL)', + ' [... stack stripped ...]', + ' ...', + 'not ok 5 The input was expected to not match the regular expression /string/. Input: \'string\'', ' ---', ' operator: doesNotMatch', ' expected: /string/', @@ -100,7 +186,7 @@ tap.test('doesNotMatch', function (tt) { ' at Test. ($TEST/match.js:$LINE:$COL)', ' [... stack stripped ...]', ' ...', - 'not ok 4 "string" should not match /string/', + 'not ok 6 "string" should not match /string/', ' ---', ' operator: doesNotMatch', ' expected: /string/', @@ -112,7 +198,7 @@ tap.test('doesNotMatch', function (tt) { ' at Test. ($TEST/match.js:$LINE:$COL)', ' [... stack stripped ...]', ' ...', - 'not ok 5 The input was expected to not match the regular expression /pass$/. Input: \'I will pass\'', + 'not ok 7 The input was expected to not match the regular expression /pass$/. Input: \'I will pass\'', ' ---', ' operator: doesNotMatch', ' expected: /pass$/', @@ -124,7 +210,7 @@ tap.test('doesNotMatch', function (tt) { ' at Test. ($TEST/match.js:$LINE:$COL)', ' [... stack stripped ...]', ' ...', - 'not ok 6 "I will pass" should not match /pass$/', + 'not ok 8 "I will pass" should not match /pass$/', ' ---', ' operator: doesNotMatch', ' expected: /pass$/', @@ -136,11 +222,13 @@ tap.test('doesNotMatch', function (tt) { ' at Test. ($TEST/match.js:$LINE:$COL)', ' [... stack stripped ...]', ' ...', + 'ok 9 The input did not match the regular expression /pass$/. Input: \'I will fail\'', + 'ok 10 "I will fail" does not match /pass$/', '', - '1..6', - '# tests 6', + '1..10', + '# tests 10', '# pass 2', - '# fail 4', + '# fail 8', '' ]); }; @@ -148,19 +236,13 @@ tap.test('doesNotMatch', function (tt) { test.createStream().pipe(concat(tc)); test('doesNotMatch', function (t) { - t.plan(6); + t.plan(10); - t['throws']( - function () { t.doesNotMatch(/abc/, 'string'); }, - TypeError, - 'regex arg must be a regex' - ); + t.doesNotMatch(/abc/, 'string'); + t.doesNotMatch(/abc/, 'string', 'regex arg must not be a string'); - t['throws']( - function () { t.doesNotMatch({ abc: 123 }, /abc/); }, - TypeError, - 'string arg must be a string' - ); + t.doesNotMatch({ abc: 123 }, /abc/); + t.doesNotMatch({ abc: 123 }, /abc/, 'string arg must not be an object'); t.doesNotMatch('string', /string/); t.doesNotMatch('string', /string/, '"string" should not match /string/'); @@ -168,6 +250,9 @@ tap.test('doesNotMatch', function (tt) { t.doesNotMatch('I will pass', /pass$/); t.doesNotMatch('I will pass', /pass$/, '"I will pass" should not match /pass$/'); + t.doesNotMatch('I will fail', /pass$/); + t.doesNotMatch('I will fail', /pass$/, '"I will fail" does not match /pass$/'); + t.end(); }); }); From 86ec0b262e0405ef0616201c1ffebbc5f278a217 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Thu, 13 Jan 2022 23:42:57 -0800 Subject: [PATCH 14/18] [Robustness] use cached `.test` --- lib/test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/test.js b/lib/test.js index 37a81e00..e582b876 100644 --- a/lib/test.js +++ b/lib/test.js @@ -553,7 +553,7 @@ Test.prototype['throws'] = function (fn, expected, msg, extra) { var passed = caught; if (isRegExp(expected)) { - passed = expected.test(caught && caught.error); + passed = $test(expected, caught && caught.error); expected = String(expected); } From a09133e71d3925bf830f721d05bad72550dd3517 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 17 Jan 2022 09:31:31 -0800 Subject: [PATCH 15/18] [meta] fix `prelint` so it does not fail outside of a git repo per https://github.com/nodejs/node/pull/41557#issuecomment-1014664887 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 13a1cf62..70821890 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "scripts": { "prepublishOnly": "safe-publish-latest", "prepublish": "!(type not-in-publish) || not-in-publish || npm run prepublishOnly", - "prelint": "eclint check $(git ls-files | xargs find 2> /dev/null | grep -vE 'node_modules|\\.git')", + "prelint": "eclint check $(git ls-files 2>/dev/null | xargs find 2> /dev/null | grep -vE 'node_modules|\\.git' || echo '*.md *.js test/*.js')", "lint": "eslint --ext .js,.cjs,.mjs . bin/*", "pretest": "npm run lint", "test": "npm run tests-only", From ca1b90616c2a8fb838b1bd99c90da758c5d80a72 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 24 Jan 2022 10:10:10 -0800 Subject: [PATCH 16/18] [Tests] handle a broken error `cause` in node 16.9/16.10 --- test/throws.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/throws.js b/test/throws.js index a683bfcf..77d92cb5 100644 --- a/test/throws.js +++ b/test/throws.js @@ -169,7 +169,7 @@ tap.test('failures', function (tt) { ' expected: |-', ' [Function: TypeError]', ' actual: |-', - " { [RangeError: actual!] message: 'actual!' }", + ' { [RangeError: actual!] ' + ('cause' in Error.prototype ? '[cause]: undefined, ' : '') + "message: 'actual!' }", ' at: Test. ($TEST/throws.js:$LINE:$COL)', ' stack: |-', ' RangeError: actual!', From 7ca944f9ce8b0372a173a50bcadd48d080bf2e5e Mon Sep 17 00:00:00 2001 From: Joshua Ocrah Date: Sat, 22 Jan 2022 20:56:52 +0000 Subject: [PATCH 17/18] [New] add `--no-only` flag/`NODE_TAPE_NO_ONLY_TEST` (#572) Co-authored-by: Joshua Ocrah Co-authored-by: Jordan Harband --- .eslintrc | 9 ++-- bin/tape | 7 ++- index.js | 4 +- readme.markdown | 37 ++++++++++++++ test/no_only.js | 110 +++++++++++++++++++++++++++++++++++++++++ test/no_only/test-a.js | 8 +++ test/no_only/test-b.js | 14 ++++++ 7 files changed, 183 insertions(+), 6 deletions(-) create mode 100644 test/no_only.js create mode 100644 test/no_only/test-a.js create mode 100644 test/no_only/test-b.js diff --git a/.eslintrc b/.eslintrc index 365f8bc0..97c0a033 100644 --- a/.eslintrc +++ b/.eslintrc @@ -14,15 +14,16 @@ "complexity": "off", "func-style": "warn", "indent": ["error", 4], - "max-lines": "warn", "max-lines-per-function": "warn", - "max-statements": "warn", + "max-lines": "warn", "max-statements-per-line": [2, { "max": 2 }], + "max-statements": "warn", "multiline-comment-style": "off", - "no-param-reassign": "warn", "no-negated-condition": "off", - "no-use-before-define": "warn", + "no-param-reassign": "warn", "no-underscore-dangle": "warn", + "no-use-before-define": "warn", + "object-curly-newline": "off", "operator-linebreak": ["error", "before"], "sort-keys": "warn", }, diff --git a/bin/tape b/bin/tape index fbd58152..84f0f0ec 100755 --- a/bin/tape +++ b/bin/tape @@ -7,9 +7,14 @@ var parseOpts = require('minimist'); var opts = parseOpts(process.argv.slice(2), { alias: { r: 'require', i: 'ignore' }, string: ['require', 'ignore'], - default: { r: [], i: null } + boolean: ['only'], + default: { r: [], i: null, only: null } }); +if (typeof opts.only === 'boolean') { + process.env.NODE_TAPE_NO_ONLY_TEST = !opts.only; +} + var cwd = process.cwd(); if (typeof opts.require === 'string') { diff --git a/index.js b/index.js index 7e402af3..6d32e99b 100644 --- a/index.js +++ b/index.js @@ -55,7 +55,8 @@ module.exports = (function () { function createExitHarness(conf) { var config = conf || {}; var harness = createHarness({ - autoclose: defined(config.autoclose, false) + autoclose: defined(config.autoclose, false), + noOnly: defined(conf.noOnly, defined(process.env.NODE_TAPE_NO_ONLY_TEST, false)) }); var stream = harness.createStream({ objectMode: conf.objectMode }); @@ -139,6 +140,7 @@ function createHarness(conf_) { var only = false; test.only = function () { if (only) { throw new Error('there can only be one only test'); } + if (conf_.noOnly) { throw new Error('`only` tests are prohibited'); } only = true; var t = test.apply(null, arguments); results.only(t); diff --git a/readme.markdown b/readme.markdown index c322bb50..03fe7e14 100644 --- a/readme.markdown +++ b/readme.markdown @@ -139,6 +139,41 @@ By default, uncaught exceptions in your tests will not be intercepted, and will - In-process reporting with https://github.com/DavidAnson/tape-player - Describe blocks with https://github.com/mattriley/tape-describe +# command-line flags + +While running tests, top-level configurations can be passed via the command line to specify desired behavior. + +Available configurations are listed below: + +## --require + +**Alias**: `-r` + +This is used to load modules before running tests and is explained extensively in the [preloading modules](#preloading-modules) section. + +## --ignore + +**Alias**: `-i` + +This flag is used when tests from certain folders and/or files are not intended to be run. It defaults to `.gitignore` file when passed with no argument. + +```sh +tape -i .ignore **/*.js +``` + +An error is thrown if the specified file passed as argument does not exist. + +## --no-only +This is particularly useful in a CI environment where an [only test](#testonlyname-opts-cb) is not supposed to go unnoticed. + +By passing the `--no-only` flag, any existing [only test](#testonlyname-opts-cb) causes tests to fail. + +```sh +tape --no-only **/*.js +``` + +Alternatively, the environment variable `NODE_TAPE_NO_ONLY_TEST` can be set to `true` to achieve the same behavior; the command-line flag takes precedence. + # methods The assertion methods in `tape` are heavily influenced or copied from the methods in [node-tap](https://github.com/isaacs/node-tap). @@ -347,6 +382,8 @@ By default the TAP output goes to `console.log()`. You can pipe the output to so Like `test([name], [opts], cb)` except if you use `.only` this is the only test case that will run for the entire process, all other test cases using `tape` will be ignored. +Check out how the usage of [the --no-only flag](#--no-only) could help ensure there is no `.only` test running in a specified environment. + ## var stream = test.createStream(opts) Create a stream of output, bypassing the default output stream that writes messages to `console.log()`. By default `stream` will be a text stream of TAP output, but you can get an object stream instead by setting `opts.objectMode` to `true`. diff --git a/test/no_only.js b/test/no_only.js new file mode 100644 index 00000000..693ab110 --- /dev/null +++ b/test/no_only.js @@ -0,0 +1,110 @@ +'use strict'; + +var tap = require('tap'); +var path = require('path'); +var exec = require('child_process').exec; + +var stripFullStack = require('./common').stripFullStack; + +var tapeBin = path.join(__dirname, '../bin/tape'); + +var expectedExitCodeFailure = (/^0\.10\.\d+$/).test(process.versions.node); +var expectedStackTraceBug = (/^3\.[012]\.\d+$/).test(process.versions.node); // https://github.com/nodejs/node/issues/2581 + +tap.test( + 'Should throw error when --no-only is passed via cli and there is a .only test', + { todo: expectedExitCodeFailure || expectedStackTraceBug ? 'Fails on these node versions' : false }, + function (tt) { + tt.plan(3); + + exec(tapeBin + ' --no-only "**/*.js"', { + cwd: path.join(__dirname, 'no_only') + }, function (err, stdout, stderr) { + tt.same(stdout.toString('utf8'), ''); + tt.match(stripFullStack(stderr.toString('utf8')).join('\n'), /Error: `only` tests are prohibited\n/); + tt.equal(err.code, 1); + }); + } +); + +tap.test( + 'Should throw error when NODE_TAPE_NO_ONLY_TEST is passed via envs and there is an .only test', + { todo: expectedExitCodeFailure || expectedStackTraceBug ? 'Fails on these node versions' : false }, + function (tt) { + tt.plan(3); + + exec(tapeBin + ' "**/*.js"', { + cwd: path.join(__dirname, 'no_only'), + env: { PATH: process.env.PATH, NODE_TAPE_NO_ONLY_TEST: 'true' } + }, function (err, stdout, stderr) { + tt.same(stdout.toString('utf8'), ''); + tt.match(stripFullStack(stderr.toString('utf8')).join('\n'), /Error: `only` tests are prohibited\n/); + tt.equal(err.code, 1); + }); + } +); + +tap.test( + 'Should override NODE_TAPE_NO_ONLY_TEST env if --no-only is passed from cli', + { todo: expectedExitCodeFailure || expectedStackTraceBug ? 'Fails on these node versions' : false }, + function (tt) { + tt.plan(3); + + exec(tapeBin + ' --no-only "**/*.js"', { + cwd: path.join(__dirname, 'no_only'), + env: { PATH: process.env.PATH, NODE_TAPE_NO_ONLY_TEST: 'false' } + }, function (err, stdout, stderr) { + tt.same(stdout.toString('utf8'), ''); + tt.match(stripFullStack(stderr.toString('utf8')).join('\n'), /Error: `only` tests are prohibited\n/); + tt.equal(err.code, 1); + }); + } +); + +tap.test('Should run successfully if there is no only test', function (tt) { + tt.plan(3); + + exec(tapeBin + ' --no-only "**/test-a.js"', { + cwd: path.join(__dirname, 'no_only') + }, function (err, stdout, stderr) { + tt.match(stderr.toString('utf8'), /^\s*(\(node:\d+\) ExperimentalWarning: The ESM module loader is experimental\.)?\s*$/); + tt.same(stripFullStack(stdout.toString('utf8')), [ + 'TAP version 13', + '# should pass', + 'ok 1 should be truthy', + '', + '1..1', + '# tests 1', + '# pass 1', + '', + '# ok', + '', + '' + ]); + tt.equal(err, null); // code 0 + }); +}); + +tap.test('Should run successfully if there is an only test and no --no-only flag', function (tt) { + tt.plan(3); + + exec(tapeBin + ' "**/test-b.js"', { + cwd: path.join(__dirname, 'no_only') + }, function (err, stdout, stderr) { + tt.same(stripFullStack(stdout.toString('utf8')), [ + 'TAP version 13', + '# should pass again', + 'ok 1 should be truthy', + '', + '1..1', + '# tests 1', + '# pass 1', + '', + '# ok', + '', + '' + ]); + tt.match(stderr.toString('utf8'), /^\s*(\(node:\d+\) ExperimentalWarning: The ESM module loader is experimental\.)?\s*$/); + tt.equal(err, null); // code 0 + }); +}); diff --git a/test/no_only/test-a.js b/test/no_only/test-a.js new file mode 100644 index 00000000..41107d38 --- /dev/null +++ b/test/no_only/test-a.js @@ -0,0 +1,8 @@ +'use strict'; + +var tape = require('../../'); + +tape.test('should pass', function (t) { + t.plan(1); + t.ok(1); +}); diff --git a/test/no_only/test-b.js b/test/no_only/test-b.js new file mode 100644 index 00000000..c6f6eb6e --- /dev/null +++ b/test/no_only/test-b.js @@ -0,0 +1,14 @@ +'use strict'; + +var tape = require('../../'); + +tape.test('should pass', function (t) { + t.plan(1); + t.ok(1); +}); + +tape.test.only('should pass again', function (t) { + t.plan(1); + t.ok(1); +}); + From e00bfb035b966dee4e6f03e6e441ae0017df6147 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Wed, 26 Jan 2022 11:16:36 -0800 Subject: [PATCH 18/18] v4.15.0 - [New] add `--no-only` flag/`NODE_TAPE_NO_ONLY_TEST` (#572) - [New] `t.match`/`t.doesNotMatch: fail the test instead of throw on wrong input types. - [Fix] `bin/tape`: delay requires until needed - [Robustness] use cached `.test` - [readme] hard wraps bad, soft wraps good - [readme] port changes from v5 - [meta] fix `prelint` so it does not fail outside of a git repo - [meta] better `eccheck` command - [meta] Exclude `fs` from browser bundles (#565) - [actions] reuse common workflows - [actions] update codecov uploader - [Deps] update `object-inspect`, `resolve`, `glob`, `is-regex`, `string.prototype.trim` - [Dev Deps] update `eslint`, `@ljharb/eslint-config`, `aud`, `safe-publish-latest`, `array.prototype.flatmap` - [Tests] handle a broken error `cause` in node 16.9/16.10 - [Tests] handle carriage returns in stack traces on Windows --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 70821890..28f756ef 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tape", - "version": "4.14.0", + "version": "4.15.0", "description": "tap-producing test harness for node and browsers", "main": "index.js", "browser": {