-
Notifications
You must be signed in to change notification settings - Fork 29.6k
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
concurrent tests are slow #47365
Comments
The fact that you're seeing almost exactly a 2x slowdown seems to me like something is being done in serial. It's impossible to say without knowing more about the test suite itself. I will say that I personally wouldn't recommend using
This sounds to me like trying to run too many things in parallel lead to a slowdown, which is understandable and will also depend on your system and the nature of the tests. I did a small comparison with ava. Given the following files and Node 19.8.1: // core.mjs
import test from 'node:test';
for (let i = 0; i < 250; i++) {
test(`test ${i}`, t => {});
} and // ava.mjs
import test from 'ava';
for (let i = 0; i < 250; i++) {
test(`test ${i}`, t => {
t.pass();
});
} On my M1, |
@cjihrig screenshots of system monitor while running tests with ava (left) then node:test (right) node:test uses one cpu it appears. No blip at "memory pressure" when running either test runner, |
It would be really nice if node:test supported a cli option |
+1 or otherwise |
I wrote this little test forumula to multiple files to try and reproduce the single-processor issue, but node handles these tests correctly using three cores and they finish relatively quickly. There must be something in my application tests that is causing tests to run sequentially, but I don't know what that could be... import { describe, it, beforeEach } from 'node:test';
import assert from 'node:assert/strict';
import util from 'util';
import crypto from 'crypto';
const lorem = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit,'
+ 'sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.';
const scrypt = async (plaintext, salt) => (
util.promisify(crypto.scrypt)(plaintext, salt, 64));
const testNumber = 1000;
beforeEach(() => 1 + 1 === 2);
const test = (tests => Object.assign((name, fn) => tests.push([name, fn]), {
go: descr => describe(
descr, { concurrency: true }, async () => tests
.map(([name, fn]) => it(name, fn)))
}))([]);
for (let x = testNumber; x--;) {
test(`test-${x}`, async () => {
await scrypt(x + lorem + x, 'salt' + x);
assert.ok(true);
});
}
test.go(); |
Update: possible reproduction here when using describe('legacy_routeAppV2', { concurrency: true }, async () => {
beforeEach(() => {
nock.disableNetConnect();
});
for (let x = 50; x--;) {
it(`test-${x}`, async () => {
const mockdb = await mockdbPopulate(mockdbData);
// the below call causes tests to slow and cpu usage to stay at one core
// increasing loop number from 50 to 1000 causes test runner to hang
const {
routeAppGetAuthorized
} = await esmock('../../src/routes/legacy_routeAppV2.js', {}, {
db: mockdb
});
assert.ok(String(routeAppGetAuthorized));
});
}
}); at each test (above), esmock creates a deeply mocked import tree using query params that include a unique id for the tree. Adding esmock to the crypto-ciphering describe/it test loop submitted here an hour or so ago did not reproduce the issue, so the issue is not trivially reproduced by using "--loader" cc @cjihrig possibly this can be reproduced with tests that generate deeply mocked import trees. In the loop above, increasing the loop number from 50 to 1000 causes the test runner to completely hang. attached: updated describe/it test, using esmock. (using --loader and esmock here does not reproduce the issue. Tests complete quickly and use maximum number of cores, 3)test.target.js import util from 'util';
import crypto from 'crypto';
const lorem = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit,'
+ 'sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.';
const scrypt = async (plaintext, salt) => (
util.promisify(crypto.scrypt)(plaintext, salt, 64));
export {
scrypt,
lorem
}; node.describe.many.test.js import { describe, it, beforeEach } from 'node:test';
import esmock from 'esmock';
import assert from 'node:assert/strict';
const testNumber = 1000;
const test = (tests => Object.assign((name, fn) => tests.push([name, fn]), {
go: descr => {
beforeEach(() => 1 + 1 === 2);
describe(
descr, { concurrency: true }, async () => tests
.map(([name, fn]) => it(name, fn)));
}
}))([]);
for (let x = testNumber; x--;) {
test(`test-${x}`, async () => {
// this creates a "shallow" mock rather than an entirely new "deep" mock
// import tree. The crypto-ciphering test above uses "deep" mocking
const { scrypt, lorem } = await esmock('./test.target.js', {
crypto: {
salt: () => 'salty'
}
});
await scrypt(x + lorem + x, 'salt' + x);
assert.ok(true);
});
}
test.go('with esmock'); |
The esmock usage here triggers a situation that ava is better able to manage than node:test |
@iambumblehead I still can't catch the root cause - there are no equal scripts for node:test / ava to compare them. |
I apologize for expressing opinions without having a clear reproduction to share here. @koshic thanks, I'll try to create a reproduction before next week. |
sorry to not create a reproduction yet, I plan to make a reproduction soon |
Could we clarify both the expectations and the actual implementation regarding concurrency? The It seems that different files are executed in different processes: Lines 376 to 377 in 9a7b971
It seems that the Lines 733 to 734 in 9a7b971
But I am not sure what the Lines 797 to 803 in 9a7b971
But what mechanism is used for concurrency? Is the Aside from these questions, the kind of workload makes a big difference. For example, synchronous tasks are going to exhaust the application thread, whereas asynchronous thread pool tasks (such as |
I agree, we should probably document this better.
both
it is indeed a queue running parallel asynchronous operations, with no isolation between tests in the same file.
using Additionally, these choices take into account not just speed but also the isolation of tests, |
I'm reproducing the issue with the package at this repo https://github.com/iambumblehead/demo-slow-node-test from the package,
increasing the number defined in the package.json can make the node test runner hang, but ava reasonably handles any number. cc @cjihrig |
Just want to mention that it may be a common case where some/all tests are intended to run sequentially. One might not want The current implementation isn't really ideal for this and I can't really use I can provide some more in-depth examples that relate to projects I'm using |
Thanks for the detailed response @MoLow, that's very helpful. So every test file has its own process, and within a test file, there is no real concurrency in the classical sense: all tests run within the same application thread. That seems be confirmed by @iambumblehead's statement above: "node:test uses one cpu it appears." What surprises me is that the docs say that |
@tniessen what would be a good default value in your opinion? |
@iambumblehead Running this on a 16-core CPU, it takes less than 5 seconds wall clock time, but about 30 seconds of user CPU time. In other words, it utilizes about 50 % of all available CPU cores.
Running this on the same CPU, it takes about 20 seconds wall clock time, and about the same amount of user CPU time. In other words, it utilizes just a single CPU core out of 16. |
I have not yet had the time to look into the repro, but if |
@iambumblehead test files were actually running serially, thanks for the good repro! |
@MoLow awesome it will be great to try it out soon! |
The documentation appears to still be wrong w.r.t. the meaning of the concurrency option of the test() function. The implementation appears to default to Infinity when the option is set to true. Is that intended or a good idea? I don't know. It certainly makes more sense than what the documentation says (which is basing the number of concurrent tasks within a single thread on the number of CPU cores). This changes the documentation to hopefully match the implementation and adds a test that rules out the (rather arbitrary) behavior described in the documentation. Refs: nodejs#47365 Refs: nodejs#47642
@targos It seems like the documentation is incorrect and the implementation does use PR for more doc fixes and test: #47734 |
Refs: nodejs#47365 PR-URL: nodejs#47642 Reviewed-By: Moshe Atlow <moshe@atlow.co.il> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
PR-URL: nodejs#47675 Fixes: nodejs#47365 Fixes: nodejs#47696 Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
PR-URL: nodejs#47675 Fixes: nodejs#47365 Fixes: nodejs#47696 Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Refs: nodejs#47365 PR-URL: nodejs#47642 Reviewed-By: Moshe Atlow <moshe@atlow.co.il> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
PR-URL: nodejs#47675 Fixes: nodejs#47365 Fixes: nodejs#47696 Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
PR-URL: nodejs#47675 Fixes: nodejs#47365 Fixes: nodejs#47696 Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
The documentation appears to still be wrong w.r.t. the meaning of the concurrency option of the test() function. The implementation appears to default to Infinity when the option is set to true. Is that intended or a good idea? I don't know. It certainly makes more sense than what the documentation says (which is basing the number of concurrent tasks within a single thread on the number of CPU cores). This changes the documentation to hopefully match the implementation and adds a test that rules out the (rather arbitrary) behavior described in the documentation. Refs: #47365 Refs: #47642 PR-URL: #47734 Reviewed-By: Moshe Atlow <moshe@atlow.co.il> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Refs: nodejs#47365 PR-URL: nodejs#47642 Reviewed-By: Moshe Atlow <moshe@atlow.co.il> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
PR-URL: nodejs#47675 Fixes: nodejs#47365 Fixes: nodejs#47696 Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
PR-URL: nodejs#47675 Fixes: nodejs#47365 Fixes: nodejs#47696 Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
The documentation appears to still be wrong w.r.t. the meaning of the concurrency option of the test() function. The implementation appears to default to Infinity when the option is set to true. Is that intended or a good idea? I don't know. It certainly makes more sense than what the documentation says (which is basing the number of concurrent tasks within a single thread on the number of CPU cores). This changes the documentation to hopefully match the implementation and adds a test that rules out the (rather arbitrary) behavior described in the documentation. Refs: nodejs#47365 Refs: nodejs#47642 PR-URL: nodejs#47734 Reviewed-By: Moshe Atlow <moshe@atlow.co.il> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
The documentation appears to still be wrong w.r.t. the meaning of the concurrency option of the test() function. The implementation appears to default to Infinity when the option is set to true. Is that intended or a good idea? I don't know. It certainly makes more sense than what the documentation says (which is basing the number of concurrent tasks within a single thread on the number of CPU cores). This changes the documentation to hopefully match the implementation and adds a test that rules out the (rather arbitrary) behavior described in the documentation. Refs: #47365 Refs: #47642 PR-URL: #47734 Reviewed-By: Moshe Atlow <moshe@atlow.co.il> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
The documentation appears to still be wrong w.r.t. the meaning of the concurrency option of the test() function. The implementation appears to default to Infinity when the option is set to true. Is that intended or a good idea? I don't know. It certainly makes more sense than what the documentation says (which is basing the number of concurrent tasks within a single thread on the number of CPU cores). This changes the documentation to hopefully match the implementation and adds a test that rules out the (rather arbitrary) behavior described in the documentation. Refs: #47365 Refs: #47642 PR-URL: #47734 Reviewed-By: Moshe Atlow <moshe@atlow.co.il> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
The documentation appears to still be wrong w.r.t. the meaning of the concurrency option of the test() function. The implementation appears to default to Infinity when the option is set to true. Is that intended or a good idea? I don't know. It certainly makes more sense than what the documentation says (which is basing the number of concurrent tasks within a single thread on the number of CPU cores). This changes the documentation to hopefully match the implementation and adds a test that rules out the (rather arbitrary) behavior described in the documentation. Refs: #47365 Refs: #47642 PR-URL: #47734 Reviewed-By: Moshe Atlow <moshe@atlow.co.il> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Refs: nodejs#47365 PR-URL: nodejs#47642 Reviewed-By: Moshe Atlow <moshe@atlow.co.il> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
PR-URL: nodejs#47675 Fixes: nodejs#47365 Fixes: nodejs#47696 Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
PR-URL: nodejs#47675 Fixes: nodejs#47365 Fixes: nodejs#47696 Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
The documentation appears to still be wrong w.r.t. the meaning of the concurrency option of the test() function. The implementation appears to default to Infinity when the option is set to true. Is that intended or a good idea? I don't know. It certainly makes more sense than what the documentation says (which is basing the number of concurrent tasks within a single thread on the number of CPU cores). This changes the documentation to hopefully match the implementation and adds a test that rules out the (rather arbitrary) behavior described in the documentation. Refs: nodejs#47365 Refs: nodejs#47642 PR-URL: nodejs#47734 Reviewed-By: Moshe Atlow <moshe@atlow.co.il> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com> Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Version
v19.8.1
Platform
Darwin Bumbles-MBP.home 21.6.0 Darwin Kernel Version 21.6.0: Mon Dec 19 20:44:01 PST 2022; root:xnu-8020.240.18~2/RELEASE_X86_64 x86_64
What steps will reproduce the bug?
Use this repo https://github.com/iambumblehead/demo-slow-node-test
from the package,
npm run test-ava
takes about 10 secondsnpm run test-node-describe
takes about 30 secondsnpm run test-node-test
takes about 30 secondsHow often does it reproduce? Is there a required condition?
It is reproduced any time the test repo is used on this machine
What is the expected behavior? Why is that the expected behavior?
node:test should use multiple cores and should not be slow
What do you see instead?
Hello, I recently replaced node:test with ava in a project using ~250 tests. The node:test runner would complete in ~18 minutes and the ava runner completes in ~9 minutes. The node:test version of the project used describe/it tests with the following pattern,
Additional information
Test concurrency might have some problems. Initially, I tried using
{ concurrency: true }
with a single file that contained about a dozen tests and tests completed in a time that was observably faster. When{ concurrency: true }
was used with multiple files, the improvement seemed to go away and each group of tests would run slowly,Test concurrency requires extra boilerplate. Eg, to run tests concurrently, one needs to use nested describe/it tests, nested tests wrapped in a promise or use a configuration file. related links here, and here. It would be nice if concurrent tests were easier to achieve with a special import
import test from 'node:test/concurrent'
or with a cli option--test-concurrency=true
, link to a related comment here: do not expose a test-concurrency flag.Thank you
The text was updated successfully, but these errors were encountered: