Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: refactor test runner run plan tests #57304

Merged
Merged
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
7 changes: 7 additions & 0 deletions test/fixtures/test-runner/plan/less.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import test from 'node:test';

test('less assertions than planned', (t) => {
t.plan(2);
t.assert.ok(true, 'only one assertion');
// Missing second assertion
});
7 changes: 7 additions & 0 deletions test/fixtures/test-runner/plan/match.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import test from 'node:test';

test('matching assertions', (t) => {
t.plan(2);
t.assert.ok(true, 'first assertion');
t.assert.ok(true, 'second assertion');
});
7 changes: 7 additions & 0 deletions test/fixtures/test-runner/plan/more.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import test from 'node:test';

test('more assertions than planned', (t) => {
t.plan(1);
t.assert.ok(true, 'first assertion');
t.assert.ok(true, 'extra assertion'); // This should cause failure
});
14 changes: 14 additions & 0 deletions test/fixtures/test-runner/plan/nested-subtests.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import test from 'node:test';

test('deeply nested tests', async (t) => {
t.plan(1);

await t.test('level 1', async (t) => {
t.plan(1);

await t.test('level 2', (t) => {
t.plan(1);
t.assert.ok(true, 'deepest assertion');
});
});
});
9 changes: 9 additions & 0 deletions test/fixtures/test-runner/plan/plan-via-options.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import test from 'node:test';

test('failing planning by options', { plan: 1 }, () => {
// Should fail - no assertions
});

test('passing planning by options', { plan: 1 }, (t) => {
t.assert.ok(true);
});
20 changes: 20 additions & 0 deletions test/fixtures/test-runner/plan/streaming.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import test from 'node:test';
import { Readable } from 'node:stream';

test('planning with streams', (t, done) => {
function* generate() {
yield 'a';
yield 'b';
yield 'c';
}
const expected = ['a', 'b', 'c'];
t.plan(expected.length);
const stream = Readable.from(generate());
stream.on('data', (chunk) => {
t.assert.strictEqual(chunk, expected.shift());
});

stream.on('end', () => {
done();
});
});
9 changes: 9 additions & 0 deletions test/fixtures/test-runner/plan/subtest.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import test from 'node:test';

test('parent test', async (t) => {
t.plan(1);
await t.test('child test', (t) => {
t.plan(1);
t.assert.ok(true, 'child assertion');
});
});
15 changes: 15 additions & 0 deletions test/fixtures/test-runner/plan/timeout-basic.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import test from 'node:test';

test('planning with wait should PASS within timeout', async (t) => {
t.plan(1, { wait: 5000 });
setTimeout(() => {
t.assert.ok(true);
}, 250);
});

test('planning with wait should FAIL within timeout', async (t) => {
t.plan(1, { wait: 5000 });
setTimeout(() => {
t.assert.ok(false);
}, 250);
});
8 changes: 8 additions & 0 deletions test/fixtures/test-runner/plan/timeout-expired.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import test from 'node:test';

test('planning should FAIL when wait time expires before plan is met', (t) => {
t.plan(2, { wait: 500 });
setTimeout(() => {
t.assert.ok(true);
}, 30_000).unref();
});
11 changes: 11 additions & 0 deletions test/fixtures/test-runner/plan/timeout-wait-false.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import test from 'node:test';

test('should not wait for assertions and fail immediately', async (t) => {
t.plan(1, { wait: false });

// Set up an async operation that won't complete before the test finishes
// Since wait:false, the test should fail immediately without waiting
setTimeout(() => {
t.assert.ok(true);
}, 1000).unref();
});
17 changes: 17 additions & 0 deletions test/fixtures/test-runner/plan/timeout-wait-true.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import test from 'node:test';

test('should pass when assertions are eventually met', async (t) => {
t.plan(1, { wait: true });

setTimeout(() => {
t.assert.ok(true);
}, 250);
});

test('should fail when assertions fail', async (t) => {
t.plan(1, { wait: true });

setTimeout(() => {
t.assert.ok(false);
}, 250).unref();
});
171 changes: 171 additions & 0 deletions test/parallel/test-runner-plan.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import * as common from '../common/index.mjs';
import * as fixtures from '../common/fixtures.mjs';
import { describe, it, run } from 'node:test';
import { join } from 'node:path';

const testFixtures = fixtures.path('test-runner', 'plan');

describe('input validation', () => {
it('throws if options is not an object', (t) => {
t.assert.throws(() => {
t.plan(1, null);
}, {
code: 'ERR_INVALID_ARG_TYPE',
message: /The "options" argument must be of type object/,
});
});

it('throws if options.wait is not a number or a boolean', (t) => {
t.assert.throws(() => {
t.plan(1, { wait: 'foo' });
}, {
code: 'ERR_INVALID_ARG_TYPE',
message: /The "options\.wait" property must be one of type boolean or number\. Received type string/,
});
});

it('throws if count is not a number', (t) => {
t.assert.throws(() => {
t.plan('foo');
}, {
code: 'ERR_INVALID_ARG_TYPE',
message: /The "count" argument must be of type number/,
});
});
});

describe('test planning', () => {
it('should pass when assertions match plan', async () => {
const stream = run({
files: [join(testFixtures, 'match.mjs')]
});

stream.on('test:fail', common.mustNotCall());
stream.on('test:pass', common.mustCall(1));

// eslint-disable-next-line no-unused-vars
for await (const _ of stream);
});

it('should fail when less assertions than planned', async () => {
const stream = run({
files: [join(testFixtures, 'less.mjs')]
});

stream.on('test:fail', common.mustCall(1));
stream.on('test:pass', common.mustNotCall());

// eslint-disable-next-line no-unused-vars
for await (const _ of stream);
});

it('should fail when more assertions than planned', async () => {
const stream = run({
files: [join(testFixtures, 'more.mjs')]
});

stream.on('test:fail', common.mustCall(1));
stream.on('test:pass', common.mustNotCall());

// eslint-disable-next-line no-unused-vars
for await (const _ of stream);
});

it('should handle plan with subtests correctly', async () => {
const stream = run({
files: [join(testFixtures, 'subtest.mjs')]
});

stream.on('test:fail', common.mustNotCall());
stream.on('test:pass', common.mustCall(2)); // Parent + child test

// eslint-disable-next-line no-unused-vars
for await (const _ of stream);
});

it('should handle plan via options', async () => {
const stream = run({
files: [join(testFixtures, 'plan-via-options.mjs')]
});

stream.on('test:fail', common.mustCall(1));
stream.on('test:pass', common.mustCall(1));

// eslint-disable-next-line no-unused-vars
for await (const _ of stream);
});

it('should handle streaming with plan', async () => {
const stream = run({
files: [join(testFixtures, 'streaming.mjs')]
});

stream.on('test:fail', common.mustNotCall());
stream.on('test:pass', common.mustCall(1));

// eslint-disable-next-line no-unused-vars
for await (const _ of stream);
});

it('should handle nested subtests with plan', async () => {
const stream = run({
files: [join(testFixtures, 'nested-subtests.mjs')]
});

stream.on('test:fail', common.mustNotCall());
stream.on('test:pass', common.mustCall(3)); // Parent + 2 levels of nesting

// eslint-disable-next-line no-unused-vars
for await (const _ of stream);
});

describe('with timeout', () => {
it('should handle basic timeout scenarios', async () => {
const stream = run({
files: [join(testFixtures, 'timeout-basic.mjs')]
});

stream.on('test:fail', common.mustCall(1));
stream.on('test:pass', common.mustCall(1));

// eslint-disable-next-line no-unused-vars
for await (const _ of stream);
});

it('should fail when timeout expires before plan is met', async (t) => {
const stream = run({
files: [join(testFixtures, 'timeout-expired.mjs')]
});

stream.on('test:fail', common.mustCall(1));
stream.on('test:pass', common.mustNotCall());

// eslint-disable-next-line no-unused-vars
for await (const _ of stream);
});

it('should handle wait:true option specifically', async () => {
const stream = run({
files: [join(testFixtures, 'timeout-wait-true.mjs')]
});

stream.on('test:fail', common.mustCall(1));
stream.on('test:pass', common.mustCall(1));

// eslint-disable-next-line no-unused-vars
for await (const _ of stream);
});

it('should handle wait:false option (should not wait)', async () => {
const stream = run({
files: [join(testFixtures, 'timeout-wait-false.mjs')]
});

stream.on('test:fail', common.mustCall(1)); // Fails because plan is not met immediately
stream.on('test:pass', common.mustNotCall());

// eslint-disable-next-line no-unused-vars
for await (const _ of stream);
});
});
});
Loading