diff --git a/docs/index.md b/docs/index.md index 5d527b60e7..ab00c3f098 100644 --- a/docs/index.md +++ b/docs/index.md @@ -947,6 +947,12 @@ Enforce a rule that tests may not be exclusive (use of e.g., `describe.only()` o `--forbid-only` causes Mocha to fail when an exclusive ("only'd") test or suite is encountered, and it will abort further test execution. +Defaults: + +1. Before v12: false +2. After v12: false, unless an environment variable called `CI` is set. Many popular + CI providers, like github actions or gitlab, do this automatically. + ### `--forbid-pending` Enforce a rule that tests may not be skipped (use of e.g., `describe.skip()`, `it.skip()`, or `this.skip()` anywhere is disallowed). diff --git a/lib/cli/run.js b/lib/cli/run.js index 561592b916..5984b561f1 100644 --- a/lib/cli/run.js +++ b/lib/cli/run.js @@ -26,6 +26,7 @@ const {ONE_AND_DONES, ONE_AND_DONE_ARGS} = require('./one-and-dones'); const debug = require('debug')('mocha:cli:run'); const defaults = require('../mocharc.json'); const {types, aliases} = require('./run-option-metadata'); +const {isCI} = require('../utils'); /** * Logical option groups @@ -123,7 +124,8 @@ exports.builder = yargs => }, 'forbid-only': { description: 'Fail if exclusive test(s) encountered', - group: GROUPS.RULES + group: GROUPS.RULES, + default: isCI() }, 'forbid-pending': { description: 'Fail if pending test(s) encountered', diff --git a/lib/errors.js b/lib/errors.js index 79786c24c1..bcf842fa51 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -9,6 +9,7 @@ const {format} = require('node:util'); const { constants } = require('./error-constants.js'); +const { isCI } = require('./utils'); /** * Contains error codes, factory functions to create throwable error objects, @@ -329,11 +330,17 @@ function createMultipleDoneError(runnable, originalErr) { * @returns {Error} Error with code {@link constants.FORBIDDEN_EXCLUSIVITY} */ function createForbiddenExclusivityError(mocha) { - var err = new Error( - mocha.isWorker - ? '`.only` is not supported in parallel mode' - : '`.only` forbidden by --forbid-only' - ); + var message; + if (mocha.isWorker) { + message = '`.only` is not supported in parallel mode'; + } else { + message = '`.only` forbidden by --forbid-only'; + if (isCI()) { + message += ' (default in CI, add `--no-forbid-only` to allow `.only`)'; + } + } + + var err = new Error(message); err.code = constants.FORBIDDEN_EXCLUSIVITY; return err; } diff --git a/lib/utils.js b/lib/utils.js index 4971b8de46..eaaa6afe89 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -696,3 +696,17 @@ exports.breakCircularDeps = inputObj => { exports.isNumeric = input => { return !isNaN(parseFloat(input)); }; + +/** + * Checks if being ran in a CI environment. + * + * This uses the CI env variable, which is set by most popular CI providers. Some + * examples include: + * Github: https://docs.github.com/en/actions/reference/workflows-and-actions/variables + * Gitlab: https://docs.gitlab.com/ci/variables/predefined_variables/ + * CircleCI: https://circleci.com/docs/reference/variables/#built-in-environment-variables + * Bitbucket: https://support.atlassian.com/bitbucket-cloud/docs/variables-and-secrets/ + */ +exports.isCI = () => { + return !!process.env.CI; +}; diff --git a/package.json b/package.json index 819f7d5040..22a757c847 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,7 @@ "test-browser": "run-s clean build test-browser:*", "test-coverage-clean": "rimraf .nyc_output coverage", "test-coverage-generate": "nyc report --reporter=lcov --reporter=text", - "test-node-run-only": "nyc --no-clean --reporter=json node bin/mocha.js", + "test-node-run-only": "nyc --no-clean --reporter=json node bin/mocha.js --no-forbid-only", "test-node-run": "nyc --no-clean --reporter=json node bin/mocha.js --forbid-only", "test-node:integration": "run-s clean build && npm run -s test-node-run -- --parallel --timeout 10000 --slow 3750 \"test/integration/**/*.spec.js\"", "test-node:integration:watch": "run-s clean build && npm run -s test-node-run -- --parallel --timeout 10000 --slow 3750 \"test/integration/options/watch.spec.js\"", diff --git a/test/integration/common-js-require.spec.js b/test/integration/common-js-require.spec.js index 43a59d6532..5418b31c7f 100644 --- a/test/integration/common-js-require.spec.js +++ b/test/integration/common-js-require.spec.js @@ -5,7 +5,8 @@ const {runMochaAsync} = require('./helpers'); describe('common js require', () => { it('should be able to run a test where all mocha exports are used', async () => { const result = await runMochaAsync('common-js-require.fixture.js', [ - '--delay' + '--delay', + '--no-forbid-only' ]); expect(result.output, 'to contain', 'running before'); expect(result.output, 'to contain', 'running suiteSetup'); diff --git a/test/integration/only.spec.js b/test/integration/only.spec.js index f7a317a054..65d5a05af2 100644 --- a/test/integration/only.spec.js +++ b/test/integration/only.spec.js @@ -6,7 +6,7 @@ var assert = require('node:assert'); describe('.only()', function () { describe('bdd', function () { it('should run only tests that marked as `only`', function (done) { - run('options/only/bdd.fixture.js', ['--ui', 'bdd'], function (err, res) { + run('options/only/bdd.fixture.js', ['--ui', 'bdd', '--no-forbid-only'], function (err, res) { if (err) { done(err); return; @@ -22,7 +22,7 @@ describe('.only()', function () { describe('tdd', function () { it('should run only tests that marked as `only`', function (done) { - run('options/only/tdd.fixture.js', ['--ui', 'tdd'], function (err, res) { + run('options/only/tdd.fixture.js', ['--ui', 'tdd', '--no-forbid-only'], function (err, res) { if (err) { done(err); return; @@ -40,7 +40,7 @@ describe('.only()', function () { it('should run only tests that marked as `only`', function (done) { run( 'options/only/qunit.fixture.js', - ['--ui', 'qunit'], + ['--ui', 'qunit', '--no-forbid-only'], function (err, res) { if (err) { done(err); diff --git a/test/integration/options/delay.spec.js b/test/integration/options/delay.spec.js index c1da3afa02..0ee0c53f4e 100644 --- a/test/integration/options/delay.spec.js +++ b/test/integration/options/delay.spec.js @@ -5,11 +5,7 @@ var helpers = require('../helpers'); var runMochaJSON = helpers.runMochaJSON; describe('--delay', function () { - var args = []; - - before(function () { - args = ['--delay']; - }); + var args = ['--delay', '--no-forbid-only']; it('should run the generated test suite', function (done) { var fixture = path.join('options', 'delay'); diff --git a/test/integration/options/dryRun.spec.js b/test/integration/options/dryRun.spec.js index 759cc0bc27..25a216ad29 100644 --- a/test/integration/options/dryRun.spec.js +++ b/test/integration/options/dryRun.spec.js @@ -5,7 +5,7 @@ var helpers = require('../helpers'); var runMochaJSON = helpers.runMochaJSON; describe('--dry-run', function () { - var args = ['--dry-run']; + var args = ['--dry-run', '--no-forbid-only']; it('should only report, but not execute any test', function (done) { var fixture = path.join('options/dry-run', 'dry-run'); diff --git a/test/integration/regression.spec.js b/test/integration/regression.spec.js index f6ae4c1c33..2dad065184 100644 --- a/test/integration/regression.spec.js +++ b/test/integration/regression.spec.js @@ -51,7 +51,7 @@ describe('regressions', function () { }); it('issue-2406: should run nested describe.only suites', function (done) { - runJSON('regression/issue-2406.fixture.js', [], function (err, res) { + runJSON('regression/issue-2406.fixture.js', ['--no-forbid-only'], function (err, res) { if (err) { done(err); return; @@ -64,7 +64,7 @@ describe('regressions', function () { }); it('issue-2417: should not recurse infinitely with .only suites nested within each other', function (done) { - runJSON('regression/issue-2417.fixture.js', [], function (err, res) { + runJSON('regression/issue-2417.fixture.js', ['--no-forbid-only'], function (err, res) { if (err) { done(err); return;