Skip to content

Commit 0d6a381

Browse files
flag test when bail
1 parent f20819b commit 0d6a381

File tree

9 files changed

+56
-49
lines changed

9 files changed

+56
-49
lines changed

doc/api/errors.md

+7
Original file line numberDiff line numberDiff line change
@@ -2738,6 +2738,13 @@ the token causing the error is available via the `cause` property.
27382738

27392739
This error represents a failed TAP validation.
27402740

2741+
<a id="ERR_TEST_BAILOUT"></a>
2742+
2743+
### `ERR_TEST_BAILOUT`
2744+
2745+
This error represents a test that has bailed out after failure.
2746+
This error occurs only when the flag `--test-bail` is passed.
2747+
27412748
<a id="ERR_TEST_FAILURE"></a>
27422749

27432750
### `ERR_TEST_FAILURE`

doc/api/test.md

+7-6
Original file line numberDiff line numberDiff line change
@@ -610,14 +610,15 @@ added:
610610
-->
611611

612612
```bash
613-
node --test --test-bail=1
613+
node --test-bail
614614
```
615615

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

622623
## Test reporters
623624

lib/internal/errors.js

+4
Original file line numberDiff line numberDiff line change
@@ -1613,6 +1613,10 @@ E('ERR_TAP_VALIDATION_ERROR', function(errorMsg) {
16131613
hideInternalStackFrames(this);
16141614
return errorMsg;
16151615
}, Error);
1616+
E('ERR_TEST_BAILOUT', function(errorMsg) {
1617+
hideInternalStackFrames(this);
1618+
return errorMsg;
1619+
}, Error);
16161620
E('ERR_TEST_FAILURE', function(error, failureType) {
16171621
hideInternalStackFrames(this);
16181622
assert(typeof failureType === 'string' || typeof failureType === 'symbol',

lib/internal/test_runner/harness.js

-1
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,6 @@ function setup(root) {
179179
topLevel: 0,
180180
suites: 0,
181181
},
182-
bailAmount: globalOptions.bail,
183182
shouldColorizeTestFiles: false,
184183
};
185184
root.startTime = hrtime();

lib/internal/test_runner/runner.js

+1-16
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,6 @@ class FileTest extends Test {
136136
#rawBufferSize = 0;
137137
#reportedChildren = 0;
138138
failedSubtests = false;
139-
140-
#bailAmount = this.root.harness.bailAmount;
141-
#failedTestsCount = 0;
142-
143139
#skipReporting() {
144140
return this.#reportedChildren > 0 && (!this.error || this.error.failureType === kSubtestsFailed);
145141
}
@@ -184,10 +180,6 @@ class FileTest extends Test {
184180
return;
185181
}
186182

187-
if (item.type === 'test:fail') {
188-
this.#failedTestsCount++;
189-
}
190-
191183
this.#reportedChildren++;
192184
if (item.data.nesting === 0 && item.type === 'test:fail') {
193185
this.failedSubtests = true;
@@ -200,9 +192,6 @@ class FileTest extends Test {
200192
}
201193
}
202194
addToReport(item) {
203-
if (this.#bailAmount && this.#failedTestsCount > this.#bailAmount) {
204-
return;
205-
}
206195
this.#accumulateReportItem(item);
207196
if (!this.isClearToSend()) {
208197
ArrayPrototypePush(this.#reportBuffer, item);
@@ -437,7 +426,7 @@ function run(options) {
437426
options = kEmptyObject;
438427
}
439428
let { testNamePatterns, shard } = options;
440-
const { concurrency, timeout, signal, files, inspectPort, watch, setup, bail } = options;
429+
const { concurrency, timeout, signal, files, inspectPort, watch, setup } = options;
441430

442431
if (files != null) {
443432
validateArray(files, 'options.files');
@@ -481,10 +470,6 @@ function run(options) {
481470
});
482471
}
483472

484-
if (bail != null) {
485-
validateInteger(bail, 'options.bail', 0);
486-
}
487-
488473
const root = createTestTree({ concurrency, timeout, signal });
489474
let testFiles = files ?? createTestFileList();
490475

lib/internal/test_runner/test.js

+14-5
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const {
2828
codes: {
2929
ERR_INVALID_ARG_TYPE,
3030
ERR_TEST_FAILURE,
31+
ERR_TEST_BAILOUT,
3132
},
3233
AbortError,
3334
} = require('internal/errors');
@@ -71,8 +72,9 @@ const kHookNames = ObjectSeal(['before', 'after', 'beforeEach', 'afterEach']);
7172
const kUnwrapErrors = new SafeSet()
7273
.add(kTestCodeFailure).add(kHookFailure)
7374
.add('uncaughtException').add('unhandledRejection');
74-
const { testNamePatterns, testOnlyFlag } = parseCommandLine();
75+
const { testNamePatterns, testOnlyFlag, bail } = parseCommandLine();
7576
let kResistStopPropagation;
77+
let bailedOut = false;
7678

7779
function stopTest(timeout, signal) {
7880
if (timeout === kDefaultTimeout) {
@@ -421,11 +423,13 @@ class Test extends AsyncResource {
421423
return;
422424
}
423425

426+
const unknownError = bailedOut ? new ERR_TEST_BAILOUT('test bailed out') : new ERR_TEST_FAILURE(
427+
'test did not finish before its parent and was cancelled',
428+
kCancelledByParent,
429+
);
430+
424431
this.fail(error ||
425-
new ERR_TEST_FAILURE(
426-
'test did not finish before its parent and was cancelled',
427-
kCancelledByParent,
428-
),
432+
unknownError,
429433
);
430434
this.startTime = this.startTime || this.endTime; // If a test was canceled before it was started, e.g inside a hook
431435
this.cancelled = true;
@@ -444,6 +448,7 @@ class Test extends AsyncResource {
444448
}
445449

446450
fail(err) {
451+
bailedOut = bail;
447452
if (this.error !== null) {
448453
return;
449454
}
@@ -526,6 +531,10 @@ class Test extends AsyncResource {
526531
}
527532

528533
async run(pendingSubtestsError) {
534+
if (bailedOut) {
535+
return;
536+
}
537+
529538
if (this.parent !== null) {
530539
this.parent.activeSubtests++;
531540
}

src/node_options.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ class EnvironmentOptions : public Options {
159159
std::string redirect_warnings;
160160
std::string diagnostic_dir;
161161
bool test_runner = false;
162-
uint64_t test_bail = 0;
162+
bool test_bail = false;
163163
bool test_runner_coverage = false;
164164
std::vector<std::string> test_name_pattern;
165165
std::vector<std::string> test_reporter;
+8-6
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
1+
const assert = require('assert');
12
const test = require('node:test');
23

34
test('nested', (t) => {
4-
t.test('ok', () => {});
5-
t.test('failing', () => {
6-
throw new Error('first');
5+
t.test('first', () => {});
6+
t.test('second', () => {
7+
throw new Error();
78
});
9+
t.test('third', () => {});
810
});
911

1012
test('top level', (t) => {
11-
t.test('ok', () => {});
12-
t.test('failing', () => {
13-
throw new Error('second');
13+
t.test('forth', () => {});
14+
t.test('fifth', () => {
15+
throw new Error();
1416
});
1517
});
1618

test/parallel/test-runner-bail.js

+14-14
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,26 @@ tmpdir.refresh();
1212

1313
describe('node:test bail', () => {
1414
it('should exit at first failure', async () => {
15-
const child = spawnSync(process.execPath, ['--test', '--test-bail=1', testFile]);
16-
console.log(child.stdout.toString());
15+
const child = spawnSync(process.execPath, ['--test', '--test-bail', testFile]);
1716
assert.strictEqual(child.stderr.toString(), '');
18-
assert.match(child.stdout.toString(), /TAP version 13/);
19-
assert.match(child.stdout.toString(), /ok 1 - ok/);
20-
assert.match(child.stdout.toString(), /not ok 2 - failing/);
17+
assert.match(child.stdout.toString(), /ok 1 - first/);
18+
assert.match(child.stdout.toString(), /not ok 2 - second/);
19+
assert.match(child.stdout.toString(), /ok 3 - third/);
2120
assert.match(child.stdout.toString(), /not ok 1 - nested/);
22-
assert.doesNotMatch(child.stdout.toString(), /not ok 2 - top level/);
21+
assert.doesNotMatch(child.stdout.toString(), /ok 1 - ok forth/);
22+
assert.doesNotMatch(child.stdout.toString(), /not ok 2 - fifth/);
2323
assert.doesNotMatch(child.stdout.toString(), /Subtest: top level/);
2424
});
2525

26-
it('should exit at second failure', async () => {
27-
const child = spawnSync(process.execPath, ['--test', '--test-bail=2', testFile]);
28-
console.log(child.stdout.toString());
26+
it('should exit not exit if bail isnt set', async () => {
27+
const child = spawnSync(process.execPath, ['--test', testFile]);
2928
assert.strictEqual(child.stderr.toString(), '');
30-
assert.match(child.stdout.toString(), /TAP version 13/);
31-
assert.match(child.stdout.toString(), /ok 1 - ok/);
32-
assert.match(child.stdout.toString(), /not ok 2 - failing/);
29+
assert.match(child.stdout.toString(), /ok 1 - first/);
30+
assert.match(child.stdout.toString(), /not ok 2 - second/);
31+
assert.match(child.stdout.toString(), /not ok 3 - third/);
3332
assert.match(child.stdout.toString(), /not ok 1 - nested/);
34-
assert.match(child.stdout.toString(), /not ok 2 - failing/);
35-
assert.match(child.stdout.toString(), /Subtest: top level/);
33+
assert.match(child.stdout.toString(), /ok 1 - forth/);
34+
assert.match(child.stdout.toString(), /not ok 2 - fifth/);
35+
assert.match(child.stdout.toString(), /not ok 2 - top level/);
3636
});
3737
});

0 commit comments

Comments
 (0)