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_runner: remove Promise return values from test(), suite(), and t.test() #56664

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

cjihrig
Copy link
Contributor

@cjihrig cjihrig commented Jan 20, 2025

test_runner: automatically wait for subtests to finish

This commit updates the test runner to automatically wait for
subtests to finish. This makes the experience more consistent
with suites and removes the need to await anything.

test_runner: remove promises returned by test()

This commit updates the test() and suite() APIs to no longer
return a Promise.

Fixes: #51292

test_runner: remove promises returned by t.test()

This commit updates the TestContext.prototype.test() API to no
longer return a Promise.

Fixes: #51292

@cjihrig cjihrig added semver-major PRs that contain breaking changes and should be released in the next major version. commit-queue-rebase Add this label to allow the Commit Queue to land a PR in several commits. labels Jan 20, 2025
@nodejs-github-bot
Copy link
Collaborator

Review requested:

  • @nodejs/test_runner

Copy link

codecov bot commented Jan 20, 2025

Codecov Report

Attention: Patch coverage is 97.05882% with 1 line in your changes missing coverage. Please review.

Project coverage is 89.20%. Comparing base (fdad2fa) to head (2b34ffe).
Report is 9 commits behind head on main.

Files with missing lines Patch % Lines
lib/internal/test_runner/harness.js 94.73% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main   #56664   +/-   ##
=======================================
  Coverage   89.20%   89.20%           
=======================================
  Files         662      662           
  Lines      191899   191925   +26     
  Branches    36940    36942    +2     
=======================================
+ Hits       171184   171215   +31     
- Misses      13546    13550    +4     
+ Partials     7169     7160    -9     
Files with missing lines Coverage Δ
lib/internal/test_runner/test.js 97.09% <100.00%> (+0.02%) ⬆️
lib/internal/test_runner/harness.js 92.91% <94.73%> (+0.23%) ⬆️

... and 28 files with indirect coverage changes

Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

Copy link
Member

@pmarchini pmarchini left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aside from the nits: I think this is a great devEx enhancement! LGTM

doc/api/test.md Outdated

This comment was marked as resolved.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no change here regarding how a test is recognized as finished. The test is either a synchronous function, a function that returns a Promise, or a function that takes a callback. The test is finished after awaiting the return value or the callback is invoked. That part is already documented.

For example, how would it understand this one completes?

Unrelated to this change - I believe this first snippet has a bug, assuming someCallback() is asynchronous? If it is asynchronous, there should be a Promise or a done callback involved. The second code snippet looks correct, but a Promise could have been used as well.

Does this PR make the done callback required for all subtests?

No.

This commit updates the test runner to automatically wait for
subtests to finish. This makes the experience more consistent
with suites and removes the need to await anything.
This commit updates the test() and suite() APIs to no longer
return a Promise.

Fixes: nodejs#51292
This commit updates the TestContext.prototype.test() API to no
longer return a Promise.

Fixes: nodejs#51292
Copy link
Member

@JakobJingleheimer JakobJingleheimer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have a test for ensuring dynamically added test cases finish before the suite closes? This await behaviour was previously required in order to orchestrate that in userland, so I'm just concerned this could break that.

Ref nodejs/nodejs.org#7387

@cjihrig
Copy link
Contributor Author

cjihrig commented Jan 21, 2025

Yes, dynamically added tests still work. The breaking change (aside from the return value change) is that you can no longer await subtests for control flow purposes. For users of describe() there are no changes at all. For users of t.test() awaiting the result becomes await undefined.

@jsumners
Copy link
Contributor

The breaking change (aside from the return value change) is that you can no longer await subtests for control flow purposes.

I'm confused. The docs still say that a test can return a promise:

node/doc/api/test.md

Lines 32 to 43 in 2b34ffe

Tests created via the `test` module consist of a single function that is
processed in one of three ways:
1. A synchronous function that is considered failing if it throws an exception,
and is considered passing otherwise.
2. A function that returns a `Promise` that is considered failing if the
`Promise` rejects, and is considered passing if the `Promise` fulfills.
3. A function that receives a callback function. If the callback receives any
truthy value as its first argument, the test is considered failing. If a
falsy value is passed as the first argument to the callback, the test is
considered passing. If the test function receives a callback function and
also returns a `Promise`, the test will fail.

Are you saying that we can no longer await those returned promises in order to keep the parent test alive?

@cjihrig
Copy link
Contributor Author

cjihrig commented Jan 21, 2025

const r = test('foo', () => {
  return new Promise((resolve) => {
    resolve();
  });
});

This test returns a Promise (the Promise that is resolved()). When that Promise settles, the test is considered finished. That has not changed.

r is the value that node:test returns from test(). This is not, and never has been, the same Promise inside of the test.

Previously, if you were writing a test inside of another test, you needed to await r so that the parent test would not finish before the subtest. With this change, the test runner will automatically wait for you. That is already how describe() works.

The only scenario where this is breaking:

test('foo', async (t) => {
  await t.test('subtest 1');
  // It is no longer guaranteed that the subtest on the previous line has
  // finished by the time this line executes. But the test runner still ensures
  // the order that subtest 1 and subtest 2 are executed in.
  await t.test('subtest 2');
});

@jsumners
Copy link
Contributor

The only scenario where this is breaking:

Thank you for the clarification. I was unaware of await test to guarantee subtests executing in a specific order. That sounds like someone relying on side effects in tests and not something I do. 👍

@targos
Copy link
Member

targos commented Jan 21, 2025

So, is t.test() now identical to a nested test() call?

@cjihrig
Copy link
Contributor Author

cjihrig commented Jan 21, 2025

As far as I know, that was already the case. The difference was that describe() waited for its children, while test() and t.test() did not. Now, all of them wait for their children.

Copy link
Member

@targos targos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving the breaking change.

@cjihrig cjihrig added the request-ci Add this label to start a Jenkins CI on a PR. label Jan 21, 2025
@github-actions github-actions bot removed the request-ci Add this label to start a Jenkins CI on a PR. label Jan 21, 2025
@nodejs-github-bot
Copy link
Collaborator

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
commit-queue-rebase Add this label to allow the Commit Queue to land a PR in several commits. needs-ci PRs that need a full CI run. semver-major PRs that contain breaking changes and should be released in the next major version. test_runner Issues and PRs related to the test runner subsystem.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

node:test APIs returning Promises clashes with no-floating-promises lint rule
7 participants