Skip to content

Commit

Permalink
flag test when bail
Browse files Browse the repository at this point in the history
  • Loading branch information
marco-ippolito committed Jul 26, 2023
1 parent f20819b commit 0d6a381
Show file tree
Hide file tree
Showing 9 changed files with 56 additions and 49 deletions.
7 changes: 7 additions & 0 deletions doc/api/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -2738,6 +2738,13 @@ the token causing the error is available via the `cause` property.

This error represents a failed TAP validation.

<a id="ERR_TEST_BAILOUT"></a>

### `ERR_TEST_BAILOUT`

This error represents a test that has bailed out after failure.
This error occurs only when the flag `--test-bail` is passed.

<a id="ERR_TEST_FAILURE"></a>

### `ERR_TEST_FAILURE`
Expand Down
13 changes: 7 additions & 6 deletions doc/api/test.md
Original file line number Diff line number Diff line change
Expand Up @@ -610,14 +610,15 @@ added:
-->

```bash
node --test --test-bail=1
node --test-bail
```

An integer value representing the number of test failures after
which the test execution should stop.
If set to `0`, the test runner will not bail out,
and all tests will run regardless of failures.
**Default:** `0`.
The `--test-bail` flag provides a way to stop the test execution
as soon as a test fails.
By enabling this flag, the test runner will exit the test suite early
when it encounters the first failing test, preventing
the execution of subsequent tests.
**Default:** `false`.

## Test reporters

Expand Down
4 changes: 4 additions & 0 deletions lib/internal/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -1613,6 +1613,10 @@ E('ERR_TAP_VALIDATION_ERROR', function(errorMsg) {
hideInternalStackFrames(this);
return errorMsg;
}, Error);
E('ERR_TEST_BAILOUT', function(errorMsg) {
hideInternalStackFrames(this);
return errorMsg;
}, Error);
E('ERR_TEST_FAILURE', function(error, failureType) {
hideInternalStackFrames(this);
assert(typeof failureType === 'string' || typeof failureType === 'symbol',
Expand Down
1 change: 0 additions & 1 deletion lib/internal/test_runner/harness.js
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,6 @@ function setup(root) {
topLevel: 0,
suites: 0,
},
bailAmount: globalOptions.bail,
shouldColorizeTestFiles: false,
};
root.startTime = hrtime();
Expand Down
17 changes: 1 addition & 16 deletions lib/internal/test_runner/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,6 @@ class FileTest extends Test {
#rawBufferSize = 0;
#reportedChildren = 0;
failedSubtests = false;

#bailAmount = this.root.harness.bailAmount;
#failedTestsCount = 0;

#skipReporting() {
return this.#reportedChildren > 0 && (!this.error || this.error.failureType === kSubtestsFailed);
}
Expand Down Expand Up @@ -184,10 +180,6 @@ class FileTest extends Test {
return;
}

if (item.type === 'test:fail') {
this.#failedTestsCount++;
}

this.#reportedChildren++;
if (item.data.nesting === 0 && item.type === 'test:fail') {
this.failedSubtests = true;
Expand All @@ -200,9 +192,6 @@ class FileTest extends Test {
}
}
addToReport(item) {
if (this.#bailAmount && this.#failedTestsCount > this.#bailAmount) {
return;
}
this.#accumulateReportItem(item);
if (!this.isClearToSend()) {
ArrayPrototypePush(this.#reportBuffer, item);
Expand Down Expand Up @@ -437,7 +426,7 @@ function run(options) {
options = kEmptyObject;
}
let { testNamePatterns, shard } = options;
const { concurrency, timeout, signal, files, inspectPort, watch, setup, bail } = options;
const { concurrency, timeout, signal, files, inspectPort, watch, setup } = options;

if (files != null) {
validateArray(files, 'options.files');
Expand Down Expand Up @@ -481,10 +470,6 @@ function run(options) {
});
}

if (bail != null) {
validateInteger(bail, 'options.bail', 0);
}

const root = createTestTree({ concurrency, timeout, signal });
let testFiles = files ?? createTestFileList();

Expand Down
19 changes: 14 additions & 5 deletions lib/internal/test_runner/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const {
codes: {
ERR_INVALID_ARG_TYPE,
ERR_TEST_FAILURE,
ERR_TEST_BAILOUT,
},
AbortError,
} = require('internal/errors');
Expand Down Expand Up @@ -71,8 +72,9 @@ const kHookNames = ObjectSeal(['before', 'after', 'beforeEach', 'afterEach']);
const kUnwrapErrors = new SafeSet()
.add(kTestCodeFailure).add(kHookFailure)
.add('uncaughtException').add('unhandledRejection');
const { testNamePatterns, testOnlyFlag } = parseCommandLine();
const { testNamePatterns, testOnlyFlag, bail } = parseCommandLine();
let kResistStopPropagation;
let bailedOut = false;

function stopTest(timeout, signal) {
if (timeout === kDefaultTimeout) {
Expand Down Expand Up @@ -421,11 +423,13 @@ class Test extends AsyncResource {
return;
}

const unknownError = bailedOut ? new ERR_TEST_BAILOUT('test bailed out') : new ERR_TEST_FAILURE(
'test did not finish before its parent and was cancelled',
kCancelledByParent,
);

this.fail(error ||
new ERR_TEST_FAILURE(
'test did not finish before its parent and was cancelled',
kCancelledByParent,
),
unknownError,
);
this.startTime = this.startTime || this.endTime; // If a test was canceled before it was started, e.g inside a hook
this.cancelled = true;
Expand All @@ -444,6 +448,7 @@ class Test extends AsyncResource {
}

fail(err) {
bailedOut = bail;
if (this.error !== null) {
return;
}
Expand Down Expand Up @@ -526,6 +531,10 @@ class Test extends AsyncResource {
}

async run(pendingSubtestsError) {
if (bailedOut) {
return;
}

if (this.parent !== null) {
this.parent.activeSubtests++;
}
Expand Down
2 changes: 1 addition & 1 deletion src/node_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ class EnvironmentOptions : public Options {
std::string redirect_warnings;
std::string diagnostic_dir;
bool test_runner = false;
uint64_t test_bail = 0;
bool test_bail = false;
bool test_runner_coverage = false;
std::vector<std::string> test_name_pattern;
std::vector<std::string> test_reporter;
Expand Down
14 changes: 8 additions & 6 deletions test/fixtures/test-runner/bail/bail.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
const assert = require('assert');
const test = require('node:test');

test('nested', (t) => {
t.test('ok', () => {});
t.test('failing', () => {
throw new Error('first');
t.test('first', () => {});
t.test('second', () => {
throw new Error();
});
t.test('third', () => {});
});

test('top level', (t) => {
t.test('ok', () => {});
t.test('failing', () => {
throw new Error('second');
t.test('forth', () => {});
t.test('fifth', () => {
throw new Error();
});
});

28 changes: 14 additions & 14 deletions test/parallel/test-runner-bail.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,26 +12,26 @@ tmpdir.refresh();

describe('node:test bail', () => {
it('should exit at first failure', async () => {
const child = spawnSync(process.execPath, ['--test', '--test-bail=1', testFile]);
console.log(child.stdout.toString());
const child = spawnSync(process.execPath, ['--test', '--test-bail', testFile]);
assert.strictEqual(child.stderr.toString(), '');
assert.match(child.stdout.toString(), /TAP version 13/);
assert.match(child.stdout.toString(), /ok 1 - ok/);
assert.match(child.stdout.toString(), /not ok 2 - failing/);
assert.match(child.stdout.toString(), /ok 1 - first/);
assert.match(child.stdout.toString(), /not ok 2 - second/);
assert.match(child.stdout.toString(), /ok 3 - third/);
assert.match(child.stdout.toString(), /not ok 1 - nested/);
assert.doesNotMatch(child.stdout.toString(), /not ok 2 - top level/);
assert.doesNotMatch(child.stdout.toString(), /ok 1 - ok forth/);
assert.doesNotMatch(child.stdout.toString(), /not ok 2 - fifth/);
assert.doesNotMatch(child.stdout.toString(), /Subtest: top level/);
});

it('should exit at second failure', async () => {
const child = spawnSync(process.execPath, ['--test', '--test-bail=2', testFile]);
console.log(child.stdout.toString());
it('should exit not exit if bail isnt set', async () => {
const child = spawnSync(process.execPath, ['--test', testFile]);
assert.strictEqual(child.stderr.toString(), '');
assert.match(child.stdout.toString(), /TAP version 13/);
assert.match(child.stdout.toString(), /ok 1 - ok/);
assert.match(child.stdout.toString(), /not ok 2 - failing/);
assert.match(child.stdout.toString(), /ok 1 - first/);
assert.match(child.stdout.toString(), /not ok 2 - second/);
assert.match(child.stdout.toString(), /not ok 3 - third/);
assert.match(child.stdout.toString(), /not ok 1 - nested/);
assert.match(child.stdout.toString(), /not ok 2 - failing/);
assert.match(child.stdout.toString(), /Subtest: top level/);
assert.match(child.stdout.toString(), /ok 1 - forth/);
assert.match(child.stdout.toString(), /not ok 2 - fifth/);
assert.match(child.stdout.toString(), /not ok 2 - top level/);
});
});

0 comments on commit 0d6a381

Please sign in to comment.