Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
4 changes: 3 additions & 1 deletion lib/cli/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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',
Expand Down
17 changes: 12 additions & 5 deletions lib/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
}
Expand Down
14 changes: 14 additions & 0 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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\"",
Expand Down
3 changes: 2 additions & 1 deletion test/integration/common-js-require.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
6 changes: 3 additions & 3 deletions test/integration/only.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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);
Expand Down
6 changes: 1 addition & 5 deletions test/integration/options/delay.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
2 changes: 1 addition & 1 deletion test/integration/options/dryRun.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
4 changes: 2 additions & 2 deletions test/integration/regression.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down