From 953bbed700b5a4f9cf4858039995c843939abdb5 Mon Sep 17 00:00:00 2001 From: Ian Mckay Date: Wed, 25 Oct 2023 13:46:50 -0700 Subject: [PATCH 01/13] Add base test for concurrent --- .../__snapshots__/baseTest.test.ts.snap | 36 +++++++++++++++++++ .../src/__tests__/baseTest.test.ts | 22 ++++++++++++ 2 files changed, 58 insertions(+) diff --git a/packages/jest-circus/src/__tests__/__snapshots__/baseTest.test.ts.snap b/packages/jest-circus/src/__tests__/__snapshots__/baseTest.test.ts.snap index 531ff0e13f1f..0b06b861becd 100644 --- a/packages/jest-circus/src/__tests__/__snapshots__/baseTest.test.ts.snap +++ b/packages/jest-circus/src/__tests__/__snapshots__/baseTest.test.ts.snap @@ -1,5 +1,41 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`concurrent 1`] = ` +"start_describe_definition: describe +add_hook: beforeEach +add_hook: afterEach +add_test: one +add_test: two +add_test: three +finish_describe_definition: describe +run_start +run_describe_start: ROOT_DESCRIBE_BLOCK +hello one +hello two +hello three +run_describe_start: describe +test_start: one +test_started: one +test_fn_start: one +test_fn_failure: one +test_done: one +test_start: two +test_started: two +test_fn_start: two +test_fn_success: two +test_done: two +test_start: three +test_started: three +test_fn_start: three +test_fn_success: three +test_done: three +run_describe_finish: describe +run_describe_finish: ROOT_DESCRIBE_BLOCK +run_finish + +unhandledErrors: 0" +`; + exports[`failures 1`] = ` "start_describe_definition: describe add_hook: beforeEach diff --git a/packages/jest-circus/src/__tests__/baseTest.test.ts b/packages/jest-circus/src/__tests__/baseTest.test.ts index 170ac57b159f..0c29aad6036c 100644 --- a/packages/jest-circus/src/__tests__/baseTest.test.ts +++ b/packages/jest-circus/src/__tests__/baseTest.test.ts @@ -42,3 +42,25 @@ test('failures', () => { expect(stdout).toMatchSnapshot(); }); + +test('concurrent', () => { + const {stdout} = runTest(` + describe('describe', () => { + beforeEach(() => {}); + afterEach(() => { throw new Error('banana')}); + test.concurrent('one', () => { + console.log('hello one'); + throw new Error('kentucky') + }); + test.concurrent('two', () => { + console.log('hello two'); + }); + test.concurrent('three', async () => { + console.log('hello three'); + await Promise.resolve(); + }); + }) + `); + + expect(stdout).toMatchSnapshot(); +}); From f0a570c5902e9f65c6181d847c5838c9343d2692 Mon Sep 17 00:00:00 2001 From: Ian Mckay Date: Wed, 25 Oct 2023 13:53:30 -0700 Subject: [PATCH 02/13] call _runTest concurrently instead of just the test.fn --- .../__snapshots__/baseTest.test.ts.snap | 22 +++++++++--------- packages/jest-circus/src/run.ts | 23 ++++++++++++++----- 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/packages/jest-circus/src/__tests__/__snapshots__/baseTest.test.ts.snap b/packages/jest-circus/src/__tests__/__snapshots__/baseTest.test.ts.snap index 0b06b861becd..7f6ab7307a65 100644 --- a/packages/jest-circus/src/__tests__/__snapshots__/baseTest.test.ts.snap +++ b/packages/jest-circus/src/__tests__/__snapshots__/baseTest.test.ts.snap @@ -10,24 +10,24 @@ add_test: three finish_describe_definition: describe run_start run_describe_start: ROOT_DESCRIBE_BLOCK -hello one -hello two -hello three run_describe_start: describe test_start: one -test_started: one -test_fn_start: one -test_fn_failure: one -test_done: one test_start: two -test_started: two -test_fn_start: two -test_fn_success: two -test_done: two test_start: three +test_started: one +test_started: two test_started: three +test_fn_start: one +test_fn_start: two test_fn_start: three +hello one +hello two +hello three +test_fn_failure: one +test_fn_success: two test_fn_success: three +test_done: one +test_done: two test_done: three run_describe_finish: describe run_describe_finish: ROOT_DESCRIBE_BLOCK diff --git a/packages/jest-circus/src/run.ts b/packages/jest-circus/src/run.ts index 72c6ed36550b..c2e41dad7ed2 100644 --- a/packages/jest-circus/src/run.ts +++ b/packages/jest-circus/src/run.ts @@ -30,6 +30,7 @@ const {setTimeout} = globalThis; type ConcurrentTestEntry = Omit & { fn: Circus.ConcurrentTestFn; + done: Promise; }; const run = async (): Promise => { @@ -63,7 +64,7 @@ const _runTestsForDescribeBlock = async ( if (isRootBlock) { const concurrentTests = collectConcurrentTests(describeBlock); if (concurrentTests.length > 0) { - startTestsConcurrently(concurrentTests); + startTestsConcurrently(concurrentTests, isSkipped); } } @@ -112,7 +113,11 @@ const _runTestsForDescribeBlock = async ( case 'test': { const hasErrorsBeforeTestRun = child.errors.length > 0; const hasRetryTimes = retryTimes > 0; - await _runTest(child, isSkipped); + if (child.concurrent) { + await (child as ConcurrentTestEntry).done; + } else { + await _runTest(child, isSkipped); + } // If immediate retry is set, we retry the test immediately after the first run if ( @@ -171,7 +176,10 @@ function collectConcurrentTests( }); } -function startTestsConcurrently(concurrentTests: Array) { +function startTestsConcurrently( + concurrentTests: Array, + parentSkipped: boolean, +) { const mutex = pLimit(getState().maxConcurrency); const testNameStorage = new AsyncLocalStorage(); jestExpect.setState({ @@ -179,13 +187,16 @@ function startTestsConcurrently(concurrentTests: Array) { }); for (const test of concurrentTests) { try { - const testFn = test.fn; - const promise = mutex(() => testNameStorage.run(getTestID(test), testFn)); + const promise = mutex(() => + testNameStorage.run(getTestID(test), () => + _runTest(test, parentSkipped), + ), + ); // Avoid triggering the uncaught promise rejection handler in case the // test fails before being awaited on. // eslint-disable-next-line @typescript-eslint/no-empty-function promise.catch(() => {}); - test.fn = () => promise; + test.done = promise; } catch (error) { test.fn = () => { throw error; From 849a1f95d1219687f0f5ec4cb52b170e571d94d5 Mon Sep 17 00:00:00 2001 From: Ian Mckay Date: Thu, 26 Oct 2023 11:37:11 -0700 Subject: [PATCH 03/13] Add unit test for concurrent.each --- .../__snapshots__/baseTest.test.ts.snap | 36 +++++++++++++++++++ .../src/__tests__/baseTest.test.ts | 19 ++++++++++ 2 files changed, 55 insertions(+) diff --git a/packages/jest-circus/src/__tests__/__snapshots__/baseTest.test.ts.snap b/packages/jest-circus/src/__tests__/__snapshots__/baseTest.test.ts.snap index 7f6ab7307a65..8b730e56d985 100644 --- a/packages/jest-circus/src/__tests__/__snapshots__/baseTest.test.ts.snap +++ b/packages/jest-circus/src/__tests__/__snapshots__/baseTest.test.ts.snap @@ -36,6 +36,42 @@ run_finish unhandledErrors: 0" `; +exports[`concurrent.each 1`] = ` +"start_describe_definition: describe +add_hook: beforeEach +add_hook: afterEach +add_test: one +add_test: two +add_test: three +finish_describe_definition: describe +run_start +run_describe_start: ROOT_DESCRIBE_BLOCK +run_describe_start: describe +test_start: one +test_start: two +test_start: three +test_started: one +test_started: two +test_started: three +test_fn_start: one +test_fn_start: two +test_fn_start: three +hello one +hello two +hello three +test_fn_success: one +test_fn_success: two +test_fn_success: three +test_done: one +test_done: two +test_done: three +run_describe_finish: describe +run_describe_finish: ROOT_DESCRIBE_BLOCK +run_finish + +unhandledErrors: 0" +`; + exports[`failures 1`] = ` "start_describe_definition: describe add_hook: beforeEach diff --git a/packages/jest-circus/src/__tests__/baseTest.test.ts b/packages/jest-circus/src/__tests__/baseTest.test.ts index 0c29aad6036c..e89edf4ea973 100644 --- a/packages/jest-circus/src/__tests__/baseTest.test.ts +++ b/packages/jest-circus/src/__tests__/baseTest.test.ts @@ -64,3 +64,22 @@ test('concurrent', () => { expect(stdout).toMatchSnapshot(); }); + +test('concurrent.each', () => { + const {stdout} = runTest(` + describe('describe', () => { + beforeEach(() => {}); + afterEach(() => { throw new Error('banana')}); + test.concurrent.each([ + ['one'], + ['two'], + ['three'], + ])('%s', async (name) => { + console.log('hello %s', name); + await Promise.resolve(); + }); + }) + `); + + expect(stdout).toMatchSnapshot(); +}); From df577c2cf4328828d3127bed2401de644025b8c9 Mon Sep 17 00:00:00 2001 From: Ian McKay Date: Fri, 15 Nov 2024 12:29:25 -0800 Subject: [PATCH 04/13] failing tests were not configured to actually fail --- e2e/test-failing/__tests__/worksWithConcurrentMode.test.js | 4 ++-- .../__tests__/worksWithConcurrentOnlyMode.test.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/e2e/test-failing/__tests__/worksWithConcurrentMode.test.js b/e2e/test-failing/__tests__/worksWithConcurrentMode.test.js index 7819bee3d1b2..5f32fecb3b38 100644 --- a/e2e/test-failing/__tests__/worksWithConcurrentMode.test.js +++ b/e2e/test-failing/__tests__/worksWithConcurrentMode.test.js @@ -11,7 +11,7 @@ describe('block with concurrent', () => { }); it.concurrent.failing('failing passes = fails', () => { - expect(10).toBe(10); + expect(10).toBe(101); }); test.concurrent.failing.each([ @@ -19,7 +19,7 @@ describe('block with concurrent', () => { {a: 1, b: 2, expected: 3}, {a: 2, b: 1, expected: 3}, ])('.add($a, $b)', ({a, b, expected}) => { - expect(a + b).toBe(expected); + expect(a + b).toBe(expected + 1); }); it.concurrent.failing('failing fails = passes', () => { diff --git a/e2e/test-failing/__tests__/worksWithConcurrentOnlyMode.test.js b/e2e/test-failing/__tests__/worksWithConcurrentOnlyMode.test.js index fdaa1fadb9f2..e5bdbd30ccdc 100644 --- a/e2e/test-failing/__tests__/worksWithConcurrentOnlyMode.test.js +++ b/e2e/test-failing/__tests__/worksWithConcurrentOnlyMode.test.js @@ -11,7 +11,7 @@ describe('block with concurrent', () => { }); it.concurrent.only.failing('failing passes = fails', () => { - expect(10).toBe(10); + expect(10).toBe(101); }); test.concurrent.only.failing.each([ From 8d047ba76199478beebaf69fa886a30936390b1a Mon Sep 17 00:00:00 2001 From: Ian McKay Date: Fri, 15 Nov 2024 12:29:55 -0800 Subject: [PATCH 05/13] Add attach jest launch --- .vscode/launch.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.vscode/launch.json b/.vscode/launch.json index 5de4feacd272..f33ceb065b03 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -16,6 +16,12 @@ "windows": { "program": "${workspaceFolder}/node_modules/jest/bin/jest" } + }, + { + "name": "Attach to jest", + "type": "node", + "request": "attach", + "port": 9229 } ] } From e31eb485647adec06f84f2578767b7c78844089d Mon Sep 17 00:00:00 2001 From: Ian McKay Date: Fri, 15 Nov 2024 12:30:57 -0800 Subject: [PATCH 06/13] Skipped tests should still be executed, move concurrent await to the end of the sync loop --- packages/jest-circus/src/run.ts | 61 +++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/packages/jest-circus/src/run.ts b/packages/jest-circus/src/run.ts index c2e41dad7ed2..e4e87b06bf7c 100644 --- a/packages/jest-circus/src/run.ts +++ b/packages/jest-circus/src/run.ts @@ -82,7 +82,7 @@ const _runTestsForDescribeBlock = async ( const retryImmediately: boolean = ((globalThis as Global.Global)[RETRY_IMMEDIATELY] as any) || false; - const deferredRetryTests = []; + const deferredRetryTests: Array = []; if (rng) { describeBlock.children = shuffleArray(describeBlock.children, rng); @@ -104,6 +104,27 @@ const _runTestsForDescribeBlock = async ( } }; + const handleRetry = async ( + test: Circus.TestEntry, + hasErrorsBeforeTestRun: boolean, + hasRetryTimes: boolean, + ) => { + // no retry if the test passed or had errors before the test ran + if (test.errors.length === 0 || hasErrorsBeforeTestRun || !hasRetryTimes) { + return; + } + + if (!retryImmediately) { + deferredRetryTests.push(test); + return; + } + + // If immediate retry is set, we retry the test immediately after the first run + await rerunTest(test); + }; + + const concurrentTests = []; + for (const child of describeBlock.children) { switch (child.type) { case 'describeBlock': { @@ -114,32 +135,23 @@ const _runTestsForDescribeBlock = async ( const hasErrorsBeforeTestRun = child.errors.length > 0; const hasRetryTimes = retryTimes > 0; if (child.concurrent) { - await (child as ConcurrentTestEntry).done; + concurrentTests.push( + (child as ConcurrentTestEntry).done.then(() => + handleRetry(child, hasErrorsBeforeTestRun, hasRetryTimes), + ), + ); } else { await _runTest(child, isSkipped); - } - - // If immediate retry is set, we retry the test immediately after the first run - if ( - retryImmediately && - hasErrorsBeforeTestRun === false && - hasRetryTimes - ) { - await rerunTest(child); - } - - if ( - hasErrorsBeforeTestRun === false && - hasRetryTimes && - !retryImmediately - ) { - deferredRetryTests.push(child); + await handleRetry(child, hasErrorsBeforeTestRun, hasRetryTimes); } break; } } } + // wait for concurrent tests to finish + await Promise.all(concurrentTests); + // Re-run failed tests n-times if configured for (const test of deferredRetryTests) { await rerunTest(test); @@ -160,18 +172,15 @@ function collectConcurrentTests( if (describeBlock.mode === 'skip') { return []; } - const {hasFocusedTests, testNamePattern} = getState(); return describeBlock.children.flatMap(child => { switch (child.type) { case 'describeBlock': return collectConcurrentTests(child); case 'test': - const skip = - !child.concurrent || - child.mode === 'skip' || - (hasFocusedTests && child.mode !== 'only') || - (testNamePattern && !testNamePattern.test(getTestID(child))); - return skip ? [] : [child as ConcurrentTestEntry]; + if (child.concurrent) { + return [child as ConcurrentTestEntry]; + } + return []; } }); } From 11c32f111fc7250c081b7d25b9b2af9142823e0a Mon Sep 17 00:00:00 2001 From: Ian McKay Date: Fri, 15 Nov 2024 12:47:09 -0800 Subject: [PATCH 07/13] Revert "failing tests were not configured to actually fail" This reverts commit df577c2cf4328828d3127bed2401de644025b8c9. --- e2e/test-failing/__tests__/worksWithConcurrentMode.test.js | 4 ++-- .../__tests__/worksWithConcurrentOnlyMode.test.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/e2e/test-failing/__tests__/worksWithConcurrentMode.test.js b/e2e/test-failing/__tests__/worksWithConcurrentMode.test.js index 5f32fecb3b38..7819bee3d1b2 100644 --- a/e2e/test-failing/__tests__/worksWithConcurrentMode.test.js +++ b/e2e/test-failing/__tests__/worksWithConcurrentMode.test.js @@ -11,7 +11,7 @@ describe('block with concurrent', () => { }); it.concurrent.failing('failing passes = fails', () => { - expect(10).toBe(101); + expect(10).toBe(10); }); test.concurrent.failing.each([ @@ -19,7 +19,7 @@ describe('block with concurrent', () => { {a: 1, b: 2, expected: 3}, {a: 2, b: 1, expected: 3}, ])('.add($a, $b)', ({a, b, expected}) => { - expect(a + b).toBe(expected + 1); + expect(a + b).toBe(expected); }); it.concurrent.failing('failing fails = passes', () => { diff --git a/e2e/test-failing/__tests__/worksWithConcurrentOnlyMode.test.js b/e2e/test-failing/__tests__/worksWithConcurrentOnlyMode.test.js index e5bdbd30ccdc..fdaa1fadb9f2 100644 --- a/e2e/test-failing/__tests__/worksWithConcurrentOnlyMode.test.js +++ b/e2e/test-failing/__tests__/worksWithConcurrentOnlyMode.test.js @@ -11,7 +11,7 @@ describe('block with concurrent', () => { }); it.concurrent.only.failing('failing passes = fails', () => { - expect(10).toBe(101); + expect(10).toBe(10); }); test.concurrent.only.failing.each([ From b32b39a70c2f36a0b32a77f04cee80f607a975f6 Mon Sep 17 00:00:00 2001 From: Ian McKay Date: Fri, 15 Nov 2024 12:56:34 -0800 Subject: [PATCH 08/13] Update snapshots for failing e2e and add missing cases --- .../__snapshots__/testFailing.test.ts.snap | 141 ++++++++++-------- .../__tests__/worksWithConcurrentMode.test.js | 18 ++- .../worksWithConcurrentOnlyMode.test.js | 24 ++- 3 files changed, 108 insertions(+), 75 deletions(-) diff --git a/e2e/__tests__/__snapshots__/testFailing.test.ts.snap b/e2e/__tests__/__snapshots__/testFailing.test.ts.snap index 899e759572c4..26b5e65a49c4 100644 --- a/e2e/__tests__/__snapshots__/testFailing.test.ts.snap +++ b/e2e/__tests__/__snapshots__/testFailing.test.ts.snap @@ -94,21 +94,27 @@ exports[`works with all statuses 1`] = ` exports[`works with concurrent and only mode 1`] = ` "FAIL __tests__/worksWithConcurrentOnlyMode.test.js block with concurrent - ✕ failing passes = fails - ✕ .add(1, 1) - ✕ .add(1, 2) - ✕ .add(2, 1) - ✓ failing fails = passes + ✕ .only.failing() should fail + ✓ .only.failing() should pass + ✕ .add(1, 1) .only.failing.each() should fail + ✕ .add(1, 2) .only.failing.each() should fail + ✕ .add(2, 1) .only.failing.each() should fail + ✓ .add(1, 1) .only.failing.each() should pass + ✓ .add(1, 2) .only.failing.each() should pass + ✓ .add(2, 1) .only.failing.each() should pass ○ skipped skipped failing test + ○ skipped .add(1, 1) skipped each + ○ skipped .add(1, 2) skipped each + ○ skipped .add(2, 1) skipped each ○ skipped skipped failing fails - ● block with concurrent › failing passes = fails + ● block with concurrent › .only.failing() should fail Failing test passed even though it was supposed to fail. Remove \`.failing\` to remove error. 11 | }); 12 | - > 13 | it.concurrent.only.failing('failing passes = fails', () => { + > 13 | it.concurrent.only.failing('.only.failing() should fail', () => { | ^ 14 | expect(10).toBe(10); 15 | }); @@ -117,64 +123,67 @@ exports[`works with concurrent and only mode 1`] = ` at failing (__tests__/worksWithConcurrentOnlyMode.test.js:13:22) at Object.describe (__tests__/worksWithConcurrentOnlyMode.test.js:8:1) - ● block with concurrent › .add(1, 1) + ● block with concurrent › .add(1, 1) .only.failing.each() should fail Failing test passed even though it was supposed to fail. Remove \`.failing\` to remove error. - 15 | }); - 16 | - > 17 | test.concurrent.only.failing.each([ + 19 | }); + 20 | + > 21 | test.concurrent.only.failing.each([ | ^ - 18 | {a: 1, b: 1, expected: 2}, - 19 | {a: 1, b: 2, expected: 3}, - 20 | {a: 2, b: 1, expected: 3}, + 22 | {a: 1, b: 1, expected: 2}, + 23 | {a: 1, b: 2, expected: 3}, + 24 | {a: 2, b: 1, expected: 3}, - at each (__tests__/worksWithConcurrentOnlyMode.test.js:17:32) + at each (__tests__/worksWithConcurrentOnlyMode.test.js:21:32) at Object.describe (__tests__/worksWithConcurrentOnlyMode.test.js:8:1) - ● block with concurrent › .add(1, 2) + ● block with concurrent › .add(1, 2) .only.failing.each() should fail Failing test passed even though it was supposed to fail. Remove \`.failing\` to remove error. - 15 | }); - 16 | - > 17 | test.concurrent.only.failing.each([ + 19 | }); + 20 | + > 21 | test.concurrent.only.failing.each([ | ^ - 18 | {a: 1, b: 1, expected: 2}, - 19 | {a: 1, b: 2, expected: 3}, - 20 | {a: 2, b: 1, expected: 3}, + 22 | {a: 1, b: 1, expected: 2}, + 23 | {a: 1, b: 2, expected: 3}, + 24 | {a: 2, b: 1, expected: 3}, - at each (__tests__/worksWithConcurrentOnlyMode.test.js:17:32) + at each (__tests__/worksWithConcurrentOnlyMode.test.js:21:32) at Object.describe (__tests__/worksWithConcurrentOnlyMode.test.js:8:1) - ● block with concurrent › .add(2, 1) + ● block with concurrent › .add(2, 1) .only.failing.each() should fail Failing test passed even though it was supposed to fail. Remove \`.failing\` to remove error. - 15 | }); - 16 | - > 17 | test.concurrent.only.failing.each([ + 19 | }); + 20 | + > 21 | test.concurrent.only.failing.each([ | ^ - 18 | {a: 1, b: 1, expected: 2}, - 19 | {a: 1, b: 2, expected: 3}, - 20 | {a: 2, b: 1, expected: 3}, + 22 | {a: 1, b: 1, expected: 2}, + 23 | {a: 1, b: 2, expected: 3}, + 24 | {a: 2, b: 1, expected: 3}, - at each (__tests__/worksWithConcurrentOnlyMode.test.js:17:32) + at each (__tests__/worksWithConcurrentOnlyMode.test.js:21:32) at Object.describe (__tests__/worksWithConcurrentOnlyMode.test.js:8:1)" `; exports[`works with concurrent mode 1`] = ` "FAIL __tests__/worksWithConcurrentMode.test.js block with concurrent - ✕ failing test - ✕ failing passes = fails - ✕ .add(1, 1) - ✕ .add(1, 2) - ✕ .add(2, 1) - ✓ failing fails = passes + ✕ test should fail + ✕ .failing() should fail + ✓ .failing() should pass + ✕ .add(1, 1) .failing.each() should fail + ✕ .add(1, 2) .failing.each() should fail + ✕ .add(2, 1) .failing.each() should fail + ✓ .add(1, 1) .failing.each() should pass + ✓ .add(1, 2) .failing.each() should pass + ✓ .add(2, 1) .failing.each() should pass ○ skipped skipped failing fails - ● block with concurrent › failing test + ● block with concurrent › test should fail expect(received).toBe(expected) // Object.is equality @@ -182,22 +191,22 @@ exports[`works with concurrent mode 1`] = ` Received: 10 8 | describe('block with concurrent', () => { - 9 | it('failing test', () => { + 9 | it('test should fail', () => { > 10 | expect(10).toBe(101); | ^ 11 | }); 12 | - 13 | it.concurrent.failing('failing passes = fails', () => { + 13 | it.concurrent.failing('.failing() should fail', () => { at Object.toBe (__tests__/worksWithConcurrentMode.test.js:10:16) - ● block with concurrent › failing passes = fails + ● block with concurrent › .failing() should fail Failing test passed even though it was supposed to fail. Remove \`.failing\` to remove error. 11 | }); 12 | - > 13 | it.concurrent.failing('failing passes = fails', () => { + > 13 | it.concurrent.failing('.failing() should fail', () => { | ^ 14 | expect(10).toBe(10); 15 | }); @@ -206,49 +215,49 @@ exports[`works with concurrent mode 1`] = ` at failing (__tests__/worksWithConcurrentMode.test.js:13:17) at Object.describe (__tests__/worksWithConcurrentMode.test.js:8:1) - ● block with concurrent › .add(1, 1) + ● block with concurrent › .add(1, 1) .failing.each() should fail Failing test passed even though it was supposed to fail. Remove \`.failing\` to remove error. - 15 | }); - 16 | - > 17 | test.concurrent.failing.each([ + 19 | }); + 20 | + > 21 | test.concurrent.failing.each([ | ^ - 18 | {a: 1, b: 1, expected: 2}, - 19 | {a: 1, b: 2, expected: 3}, - 20 | {a: 2, b: 1, expected: 3}, + 22 | {a: 1, b: 1, expected: 2}, + 23 | {a: 1, b: 2, expected: 3}, + 24 | {a: 2, b: 1, expected: 3}, - at each (__tests__/worksWithConcurrentMode.test.js:17:27) + at each (__tests__/worksWithConcurrentMode.test.js:21:27) at Object.describe (__tests__/worksWithConcurrentMode.test.js:8:1) - ● block with concurrent › .add(1, 2) + ● block with concurrent › .add(1, 2) .failing.each() should fail Failing test passed even though it was supposed to fail. Remove \`.failing\` to remove error. - 15 | }); - 16 | - > 17 | test.concurrent.failing.each([ + 19 | }); + 20 | + > 21 | test.concurrent.failing.each([ | ^ - 18 | {a: 1, b: 1, expected: 2}, - 19 | {a: 1, b: 2, expected: 3}, - 20 | {a: 2, b: 1, expected: 3}, + 22 | {a: 1, b: 1, expected: 2}, + 23 | {a: 1, b: 2, expected: 3}, + 24 | {a: 2, b: 1, expected: 3}, - at each (__tests__/worksWithConcurrentMode.test.js:17:27) + at each (__tests__/worksWithConcurrentMode.test.js:21:27) at Object.describe (__tests__/worksWithConcurrentMode.test.js:8:1) - ● block with concurrent › .add(2, 1) + ● block with concurrent › .add(2, 1) .failing.each() should fail Failing test passed even though it was supposed to fail. Remove \`.failing\` to remove error. - 15 | }); - 16 | - > 17 | test.concurrent.failing.each([ + 19 | }); + 20 | + > 21 | test.concurrent.failing.each([ | ^ - 18 | {a: 1, b: 1, expected: 2}, - 19 | {a: 1, b: 2, expected: 3}, - 20 | {a: 2, b: 1, expected: 3}, + 22 | {a: 1, b: 1, expected: 2}, + 23 | {a: 1, b: 2, expected: 3}, + 24 | {a: 2, b: 1, expected: 3}, - at each (__tests__/worksWithConcurrentMode.test.js:17:27) + at each (__tests__/worksWithConcurrentMode.test.js:21:27) at Object.describe (__tests__/worksWithConcurrentMode.test.js:8:1)" `; diff --git a/e2e/test-failing/__tests__/worksWithConcurrentMode.test.js b/e2e/test-failing/__tests__/worksWithConcurrentMode.test.js index 7819bee3d1b2..c34f73834874 100644 --- a/e2e/test-failing/__tests__/worksWithConcurrentMode.test.js +++ b/e2e/test-failing/__tests__/worksWithConcurrentMode.test.js @@ -6,24 +6,32 @@ */ describe('block with concurrent', () => { - it('failing test', () => { + it('test should fail', () => { expect(10).toBe(101); }); - it.concurrent.failing('failing passes = fails', () => { + it.concurrent.failing('.failing() should fail', () => { expect(10).toBe(10); }); + it.concurrent.failing('.failing() should pass', () => { + expect(10).toBe(101); + }); + test.concurrent.failing.each([ {a: 1, b: 1, expected: 2}, {a: 1, b: 2, expected: 3}, {a: 2, b: 1, expected: 3}, - ])('.add($a, $b)', ({a, b, expected}) => { + ])('.add($a, $b) .failing.each() should fail', ({a, b, expected}) => { expect(a + b).toBe(expected); }); - it.concurrent.failing('failing fails = passes', () => { - expect(10).toBe(101); + test.concurrent.failing.each([ + {a: 1, b: 1, expected: 2}, + {a: 1, b: 2, expected: 3}, + {a: 2, b: 1, expected: 3}, + ])('.add($a, $b) .failing.each() should pass', ({a, b, expected}) => { + expect(a + b).toBe(expected + 10); }); it.concurrent.skip.failing('skipped failing fails', () => { diff --git a/e2e/test-failing/__tests__/worksWithConcurrentOnlyMode.test.js b/e2e/test-failing/__tests__/worksWithConcurrentOnlyMode.test.js index fdaa1fadb9f2..f988d72292c9 100644 --- a/e2e/test-failing/__tests__/worksWithConcurrentOnlyMode.test.js +++ b/e2e/test-failing/__tests__/worksWithConcurrentOnlyMode.test.js @@ -10,20 +10,36 @@ describe('block with concurrent', () => { expect(10).toBe(101); }); - it.concurrent.only.failing('failing passes = fails', () => { + it.concurrent.only.failing('.only.failing() should fail', () => { expect(10).toBe(10); }); + it.concurrent.only.failing('.only.failing() should pass', () => { + expect(10).toBe(101); + }); + test.concurrent.only.failing.each([ {a: 1, b: 1, expected: 2}, {a: 1, b: 2, expected: 3}, {a: 2, b: 1, expected: 3}, - ])('.add($a, $b)', ({a, b, expected}) => { + ])('.add($a, $b) .only.failing.each() should fail', ({a, b, expected}) => { expect(a + b).toBe(expected); }); - it.concurrent.only.failing('failing fails = passes', () => { - expect(10).toBe(101); + test.concurrent.only.failing.each([ + {a: 1, b: 1, expected: 2}, + {a: 1, b: 2, expected: 3}, + {a: 2, b: 1, expected: 3}, + ])('.add($a, $b) .only.failing.each() should pass', ({a, b, expected}) => { + expect(a + b).toBe(expected + 10); + }); + + test.concurrent.failing.each([ + {a: 1, b: 1, expected: 2}, + {a: 1, b: 2, expected: 3}, + {a: 2, b: 1, expected: 3}, + ])('.add($a, $b) skipped each', ({a, b, expected}) => { + expect(a + b).toBe(expected + 10); }); it.concurrent.failing('skipped failing fails', () => { From 9d167c6b2a9a4ee237458d1620d6f21213af844f Mon Sep 17 00:00:00 2001 From: Ian McKay Date: Fri, 15 Nov 2024 13:32:06 -0800 Subject: [PATCH 09/13] Add retry e2e tests --- .../__snapshots__/testRetries.test.ts.snap | 75 +++++++++ e2e/__tests__/testRetries.test.ts | 146 ++++++++++++++++++ .../beforeAllFailureConcurrent.test.js | 21 +++ .../__tests__/controlConcurrent.test.js | 11 ++ .../__tests__/e2eConcurrent.test.js | 29 ++++ .../__tests__/retryConcurrent.test.js | 17 ++ .../retryImmediatelyConcurrent.test.js | 34 ++++ 7 files changed, 333 insertions(+) create mode 100644 e2e/test-retries/__tests__/beforeAllFailureConcurrent.test.js create mode 100644 e2e/test-retries/__tests__/controlConcurrent.test.js create mode 100644 e2e/test-retries/__tests__/e2eConcurrent.test.js create mode 100644 e2e/test-retries/__tests__/retryConcurrent.test.js create mode 100644 e2e/test-retries/__tests__/retryImmediatelyConcurrent.test.js diff --git a/e2e/__tests__/__snapshots__/testRetries.test.ts.snap b/e2e/__tests__/__snapshots__/testRetries.test.ts.snap index ff71c0d297a7..0fe26bec499e 100644 --- a/e2e/__tests__/__snapshots__/testRetries.test.ts.snap +++ b/e2e/__tests__/__snapshots__/testRetries.test.ts.snap @@ -1,5 +1,80 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`Concurrent Test Retries with flag retryImmediately retry immediately after failed test 1`] = ` +"LOGGING RETRY ERRORS retryable test 1 + RETRY 1 + + expect(received).toBeFalsy() + + Received: true + + 15 | expect(true).toBeTruthy(); + 16 | } else { + > 17 | expect(true).toBeFalsy(); + | ^ + 18 | } + 19 | }); + 20 | + + at Object.toBeFalsy (__tests__/retryImmediatelyConcurrent.test.js:17:18) + + RETRY 2 + + expect(received).toBeFalsy() + + Received: true + + 15 | expect(true).toBeTruthy(); + 16 | } else { + > 17 | expect(true).toBeFalsy(); + | ^ + 18 | } + 19 | }); + 20 | + + at Object.toBeFalsy (__tests__/retryImmediatelyConcurrent.test.js:17:18) + at async Promise.all (index 0) + + LOGGING RETRY ERRORS retryable test 2 + RETRY 1 + + expect(received).toBeFalsy() + + Received: true + + 26 | expect(true).toBeTruthy(); + 27 | } else { + > 28 | expect(true).toBeFalsy(); + | ^ + 29 | } + 30 | }); + 31 | it.concurrent('truthy test', () => { + + at Object.toBeFalsy (__tests__/retryImmediatelyConcurrent.test.js:28:18) + + RETRY 2 + + expect(received).toBeFalsy() + + Received: true + + 26 | expect(true).toBeTruthy(); + 27 | } else { + > 28 | expect(true).toBeFalsy(); + | ^ + 29 | } + 30 | }); + 31 | it.concurrent('truthy test', () => { + + at Object.toBeFalsy (__tests__/retryImmediatelyConcurrent.test.js:28:18) + at async Promise.all (index 1) + +PASS __tests__/retryImmediatelyConcurrent.test.js + ✓ retryable test 1 + ✓ retryable test 2 + ✓ truthy test" +`; + exports[`Test Retries logs error(s) before retry 1`] = ` "LOGGING RETRY ERRORS retryTimes set RETRY 1 diff --git a/e2e/__tests__/testRetries.test.ts b/e2e/__tests__/testRetries.test.ts index 4eac7e19be16..ca07427a0be6 100644 --- a/e2e/__tests__/testRetries.test.ts +++ b/e2e/__tests__/testRetries.test.ts @@ -173,3 +173,149 @@ describe('Test Retries', () => { expect(jsonResult.testResults[0].testResults[0].invocations).toBe(1); }); }); + +describe('Concurrent Test Retries', () => { + const outputFileName = 'retries.result.json'; + const outputFilePath = path.join( + process.cwd(), + 'e2e/test-retries/', + outputFileName, + ); + const logErrorsBeforeRetryErrorMessage = 'LOGGING RETRY ERRORS'; + + afterAll(() => { + fs.unlinkSync(outputFilePath); + }); + + it('retries failed tests', () => { + const result = runJest('test-retries', ['e2eConcurrent.test.js']); + + expect(result.exitCode).toBe(0); + expect(result.failed).toBe(false); + expect(result.stderr).not.toContain(logErrorsBeforeRetryErrorMessage); + }); + + it('with flag retryImmediately retry immediately after failed test', () => { + const logMessage = `console.log + FIRST TRUTHY TEST + + at Object.log (__tests__/retryImmediatelyConcurrent.test.js:32:11) + + console.log + SECOND TRUTHY TEST + + at Object.log (__tests__/retryImmediatelyConcurrent.test.js:14:13) + at async Promise.all (index 0) + + console.log + THIRD TRUTHY TEST + + at Object.log (__tests__/retryImmediatelyConcurrent.test.js:25:13) + at async Promise.all (index 1)`; + + const result = runJest('test-retries', [ + 'retryImmediatelyConcurrent.test.js', + ]); + const stdout = result.stdout.trim(); + expect(result.exitCode).toBe(0); + expect(result.failed).toBe(false); + expect(result.stderr).toContain(logErrorsBeforeRetryErrorMessage); + expect(stdout).toBe(logMessage); + expect(extractSummary(result.stderr).rest).toMatchSnapshot(); + }); + + it('reporter shows more than 1 invocation if test is retried', () => { + let jsonResult; + + const reporterConfig = { + reporters: [ + ['/reporters/RetryReporter.js', {output: outputFilePath}], + ], + }; + + runJest('test-retries', [ + '--config', + JSON.stringify(reporterConfig), + '__tests__/retryConcurrent.test.js', + ]); + + const testOutput = fs.readFileSync(outputFilePath, 'utf8'); + + try { + jsonResult = JSON.parse(testOutput); + } catch (error: any) { + throw new Error( + `Can't parse the JSON result from ${outputFileName}, ${error.toString()}`, + ); + } + + expect(jsonResult.numPassedTests).toBe(1); + expect(jsonResult.numFailedTests).toBe(1); + expect(jsonResult.numPendingTests).toBe(0); + expect(jsonResult.testResults[0].testResults[0].invocations).toBe(4); + expect(jsonResult.testResults[0].testResults[1].invocations).toBe(1); + }); + + it('reporter shows 1 invocation if tests are not retried', () => { + let jsonResult; + + const reporterConfig = { + reporters: [ + ['/reporters/RetryReporter.js', {output: outputFilePath}], + ], + }; + + runJest('test-retries', [ + '--config', + JSON.stringify(reporterConfig), + 'controlConcurrent.test.js', + ]); + + const testOutput = fs.readFileSync(outputFilePath, 'utf8'); + + try { + jsonResult = JSON.parse(testOutput); + } catch (error: any) { + throw new Error( + `Can't parse the JSON result from ${outputFileName}, ${error.toString()}`, + ); + } + + expect(jsonResult.numPassedTests).toBe(0); + expect(jsonResult.numFailedTests).toBe(1); + expect(jsonResult.numPendingTests).toBe(0); + expect(jsonResult.testResults[0].testResults[0].invocations).toBe(1); + }); + + it('tests are not retried if beforeAll hook failure occurs', () => { + let jsonResult; + + const reporterConfig = { + reporters: [ + ['/reporters/RetryReporter.js', {output: outputFilePath}], + ], + }; + + runJest('test-retries', [ + '--config', + JSON.stringify(reporterConfig), + 'beforeAllFailureConcurrent.test.js', + ]); + + const testOutput = fs.readFileSync(outputFilePath, 'utf8'); + + try { + jsonResult = JSON.parse(testOutput); + } catch (error: any) { + throw new Error( + `Can't parse the JSON result from ${outputFileName}, ${error.toString()}`, + ); + } + + expect(jsonResult.numPassedTests).toBe(0); + expect(jsonResult.numFailedTests).toBe(2); + expect(jsonResult.numPendingTests).toBe(0); + expect(jsonResult.testResults[0].testResults[0].invocations).toBe(1); + expect(jsonResult.testResults[0].testResults[1].invocations).toBe(1); + }); +}); diff --git a/e2e/test-retries/__tests__/beforeAllFailureConcurrent.test.js b/e2e/test-retries/__tests__/beforeAllFailureConcurrent.test.js new file mode 100644 index 000000000000..cd6591c2bef8 --- /dev/null +++ b/e2e/test-retries/__tests__/beforeAllFailureConcurrent.test.js @@ -0,0 +1,21 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +'use strict'; + +jest.retryTimes(3); + +beforeAll(() => { + throw new Error('Failure in beforeAll'); +}); + +it.concurrent('should not be retried because hook failure occurred', () => { + throw new Error('should not be invoked'); +}); + +it.concurrent('should fail due to the beforeAll', () => { + expect(10).toBe(10); +}); diff --git a/e2e/test-retries/__tests__/controlConcurrent.test.js b/e2e/test-retries/__tests__/controlConcurrent.test.js new file mode 100644 index 000000000000..28189f8afca9 --- /dev/null +++ b/e2e/test-retries/__tests__/controlConcurrent.test.js @@ -0,0 +1,11 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +'use strict'; + +it('retryTimes not set', () => { + expect(true).toBeFalsy(); +}); diff --git a/e2e/test-retries/__tests__/e2eConcurrent.test.js b/e2e/test-retries/__tests__/e2eConcurrent.test.js new file mode 100644 index 000000000000..b0fdaa9a032e --- /dev/null +++ b/e2e/test-retries/__tests__/e2eConcurrent.test.js @@ -0,0 +1,29 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +'use strict'; + +const fs = require('fs'); +const path = require('path'); + +const countPath = path.join(__dirname, '.tries'); + +beforeAll(() => { + fs.writeFileSync(countPath, '0', 'utf8'); +}); + +jest.retryTimes(3); + +it.concurrent('retries', () => { + const tries = Number.parseInt(fs.readFileSync(countPath, 'utf8'), 10); + fs.writeFileSync(countPath, `${tries + 1}`, 'utf8'); + expect(tries).toBe(3); +}); + +afterAll(() => { + // cleanup + fs.unlinkSync(countPath); +}); diff --git a/e2e/test-retries/__tests__/retryConcurrent.test.js b/e2e/test-retries/__tests__/retryConcurrent.test.js new file mode 100644 index 000000000000..62f5f770b50b --- /dev/null +++ b/e2e/test-retries/__tests__/retryConcurrent.test.js @@ -0,0 +1,17 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +'use strict'; + +jest.retryTimes(3); + +it.concurrent('retryTimes set', () => { + expect(true).toBeFalsy(); +}); + +it.concurrent('truthy test', () => { + expect(true).toBeTruthy(); +}); diff --git a/e2e/test-retries/__tests__/retryImmediatelyConcurrent.test.js b/e2e/test-retries/__tests__/retryImmediatelyConcurrent.test.js new file mode 100644 index 000000000000..a1f198082221 --- /dev/null +++ b/e2e/test-retries/__tests__/retryImmediatelyConcurrent.test.js @@ -0,0 +1,34 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +'use strict'; + +jest.retryTimes(3, {logErrorsBeforeRetry: true, retryImmediately: true}); +let i1 = 0; +it.concurrent('retryable test 1', () => { + i1++; + if (i1 === 3) { + console.log('SECOND TRUTHY TEST'); + expect(true).toBeTruthy(); + } else { + expect(true).toBeFalsy(); + } +}); + +let i2 = 0; +it.concurrent('retryable test 2', () => { + i2++; + if (i2 === 3) { + console.log('THIRD TRUTHY TEST'); + expect(true).toBeTruthy(); + } else { + expect(true).toBeFalsy(); + } +}); +it.concurrent('truthy test', () => { + console.log('FIRST TRUTHY TEST'); + expect(true).toBeTruthy(); +}); From f650b5f188176e3eb28a22275facfe31951bc689 Mon Sep 17 00:00:00 2001 From: Ian McKay Date: Fri, 15 Nov 2024 14:27:20 -0800 Subject: [PATCH 10/13] Add concurrent e2e test --- .../circusConcurrent.test.ts.snap | 84 +++++++++++++++++++ e2e/__tests__/circusConcurrent.test.ts | 24 ++++++ .../__tests__/concurrent.test.js | 58 +++++++++++++ 3 files changed, 166 insertions(+) create mode 100644 e2e/__tests__/__snapshots__/circusConcurrent.test.ts.snap create mode 100644 e2e/__tests__/circusConcurrent.test.ts create mode 100644 e2e/circus-concurrent/__tests__/concurrent.test.js diff --git a/e2e/__tests__/__snapshots__/circusConcurrent.test.ts.snap b/e2e/__tests__/__snapshots__/circusConcurrent.test.ts.snap new file mode 100644 index 000000000000..c7183e5df107 --- /dev/null +++ b/e2e/__tests__/__snapshots__/circusConcurrent.test.ts.snap @@ -0,0 +1,84 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`runs the tests in the correct order 1`] = ` +" console.log + [[beforeAll]] + + at log (__tests__/concurrent.test.js:12:29) + + console.log + [[test one start]] + + at log (__tests__/concurrent.test.js:12:29) + + console.log + [[test three start]] + + at log (__tests__/concurrent.test.js:12:29) + + console.log + [[test five start]] + + at log (__tests__/concurrent.test.js:12:29) + + console.log + [[test six start]] + + at log (__tests__/concurrent.test.js:12:29) + + console.log + [[test seven (fails) start]] + + at log (__tests__/concurrent.test.js:12:29) + + console.log + [[test three end]] + + at log (__tests__/concurrent.test.js:12:29) + + console.log + [[test eight start]] + + at log (__tests__/concurrent.test.js:12:29) + + console.log + [[test one end]] + + at log (__tests__/concurrent.test.js:12:29) + + console.log + [[test nine start]] + + at log (__tests__/concurrent.test.js:12:29) + + console.log + [[test six end]] + + at log (__tests__/concurrent.test.js:12:29) + + console.log + [[test ten (fails) start]] + + at log (__tests__/concurrent.test.js:12:29) + + console.log + [[test eight end]] + + at log (__tests__/concurrent.test.js:12:29) + + console.log + [[test nine end]] + + at log (__tests__/concurrent.test.js:12:29) + + console.log + [[test five end]] + + at log (__tests__/concurrent.test.js:12:29) + + console.log + [[afterAll]] + + at log (__tests__/concurrent.test.js:12:29) +" +`; diff --git a/e2e/__tests__/circusConcurrent.test.ts b/e2e/__tests__/circusConcurrent.test.ts new file mode 100644 index 000000000000..6dde79f6a1f9 --- /dev/null +++ b/e2e/__tests__/circusConcurrent.test.ts @@ -0,0 +1,24 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import {skipSuiteOnJasmine} from '@jest/test-utils'; +import runJest, {json as runWithJson} from '../runJest'; + +skipSuiteOnJasmine(); + +it('runs the correct number of tests', () => { + const {json} = runWithJson('circus-concurrent', ['concurrent.test.js']); + + expect(json.numTotalTests).toBe(10); + expect(json.numPassedTests).toBe(6); + expect(json.numFailedTests).toBe(2); + expect(json.numPendingTests).toBe(2); +}); + +it('runs the tests in the correct order', () => { + const {stdout} = runJest('circus-concurrent', ['concurrent.test.js']); + expect(stdout).toMatchSnapshot(); +}); diff --git a/e2e/circus-concurrent/__tests__/concurrent.test.js b/e2e/circus-concurrent/__tests__/concurrent.test.js new file mode 100644 index 000000000000..def119be6f9b --- /dev/null +++ b/e2e/circus-concurrent/__tests__/concurrent.test.js @@ -0,0 +1,58 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +'use strict'; + +const {setTimeout} = require('timers/promises'); + +const marker = s => console.log(`[[${s}]]`); + +beforeAll(() => marker('beforeAll')); +afterAll(() => marker('afterAll')); + +beforeEach(() => marker('beforeEach')); +afterEach(() => marker('afterEach')); + +const testFn = (name, delay, fail) => { + return async () => { + marker(`test ${name} start`); + await setTimeout(delay); + if (fail) { + throw new Error(`${name} failed`); + } + expect(name).toBe(name); + expect.assertions(1); + marker(`test ${name} end`); + }; +}; + +it.concurrent('one', testFn('one', 100)); +it.concurrent.skip('two (skipped)', testFn('two (skipped)', 100)); + +describe('level 1', () => { + beforeEach(() => marker('beforeEach level 1')); + afterEach(() => marker('afterEach level 1')); + + it.concurrent('three', testFn('three', 60)); + + it.concurrent.skip('four (skipped)', testFn('four (skipped)', 100)); + + describe('level 2', () => { + beforeEach(() => marker('beforeEach level 2')); + afterEach(() => marker('afterEach level 2')); + it.concurrent('five', testFn('five', 150)); + + it.concurrent('six', testFn('six', 100)); + }); + + it.concurrent('seven (fails)', testFn('seven (fails)', 100, true)); + it.concurrent('eight', testFn('eight', 50)); +}); + +it.concurrent('nine', testFn('nine', 20)); + +it.concurrent('ten (fails)', testFn('ten (fails)', 30, true)); From 7f57280286015dd282e55d16aa235fe11644d256 Mon Sep 17 00:00:00 2001 From: Ian McKay Date: Fri, 15 Nov 2024 15:05:54 -0800 Subject: [PATCH 11/13] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 363abee8a97e..677020a6d907 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,7 @@ - `[jest-circus]` Replace recursive `makeTestResults` implementation with iterative one ([#14760](https://github.com/jestjs/jest/pull/14760)) - `[jest-circus]` Omit `expect.hasAssertions()` errors if a test already has errors ([#14866](https://github.com/jestjs/jest/pull/14866)) - `[jest-circus, jest-expect, jest-snapshot]` Pass `test.failing` tests when containing failing snapshot matchers ([#14313](https://github.com/jestjs/jest/pull/14313)) +- `[jest-circus]` Concurrent tests now emit jest circus events at the correct point and in the expected order. ([#15381](https://github.com/jestjs/jest/pull/15381)) - `[jest-cli]` [**BREAKING**] Validate CLI flags that require arguments receives them ([#14783](https://github.com/jestjs/jest/pull/14783)) - `[jest-config]` Make sure to respect `runInBand` option ([#14578](https://github.com/jestjs/jest/pull/14578)) - `[jest-config]` Support `testTimeout` in project config ([#14697](https://github.com/jestjs/jest/pull/14697)) From 871e0827e97da93623cbf76cd0e3f7adfdec1250 Mon Sep 17 00:00:00 2001 From: Ian McKay Date: Fri, 15 Nov 2024 15:21:08 -0800 Subject: [PATCH 12/13] Update jasmine snapshots --- .../__snapshots__/testFailingJasmine.test.ts.snap | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/e2e/__tests__/__snapshots__/testFailingJasmine.test.ts.snap b/e2e/__tests__/__snapshots__/testFailingJasmine.test.ts.snap index bddc0de89505..d97999b98e4a 100644 --- a/e2e/__tests__/__snapshots__/testFailingJasmine.test.ts.snap +++ b/e2e/__tests__/__snapshots__/testFailingJasmine.test.ts.snap @@ -17,7 +17,7 @@ exports[`throws an error about unsupported modifier 1`] = ` at Object.failing (__tests__/statuses.test.js:22:4) FAIL __tests__/worksWithConcurrentMode.test.js - ● block with concurrent › failing test + ● block with concurrent › test should fail expect(received).toBe(expected) // Object.is equality @@ -25,12 +25,12 @@ FAIL __tests__/worksWithConcurrentMode.test.js Received: 10 8 | describe('block with concurrent', () => { - 9 | it('failing test', () => { + 9 | it('test should fail', () => { > 10 | expect(10).toBe(101); | ^ 11 | }); 12 | - 13 | it.concurrent.failing('failing passes = fails', () => { + 13 | it.concurrent.failing('.failing() should fail', () => { at Object.toBe (__tests__/worksWithConcurrentMode.test.js:10:16) @@ -40,7 +40,7 @@ FAIL __tests__/worksWithConcurrentMode.test.js 11 | }); 12 | - > 13 | it.concurrent.failing('failing passes = fails', () => { + > 13 | it.concurrent.failing('.failing() should fail', () => { | ^ 14 | expect(10).toBe(10); 15 | }); @@ -64,7 +64,7 @@ FAIL __tests__/worksWithConcurrentOnlyMode.test.js | ^ 11 | }); 12 | - 13 | it.concurrent.only.failing('failing passes = fails', () => { + 13 | it.concurrent.only.failing('.only.failing() should fail', () => { at Object.toBe (__tests__/worksWithConcurrentOnlyMode.test.js:10:16) @@ -74,7 +74,7 @@ FAIL __tests__/worksWithConcurrentOnlyMode.test.js 11 | }); 12 | - > 13 | it.concurrent.only.failing('failing passes = fails', () => { + > 13 | it.concurrent.only.failing('.only.failing() should fail', () => { | ^ 14 | expect(10).toBe(10); 15 | }); From 0578d541450b801de389c57c87deaf988bc8cc5d Mon Sep 17 00:00:00 2001 From: Ian McKay Date: Fri, 15 Nov 2024 16:53:19 -0800 Subject: [PATCH 13/13] Add more e2e tests, ensure concurrent tests end with a time gap --- .../circusConcurrent.test.ts.snap | 212 +++++++++++++++--- e2e/__tests__/circusConcurrent.test.ts | 62 ++++- .../__tests__/concurrent-mixed.test.js | 63 ++++++ .../__tests__/concurrent-only.test.js | 63 ++++++ .../__tests__/concurrent-skip.test.js | 63 ++++++ .../__tests__/concurrent.test.js | 27 ++- 6 files changed, 437 insertions(+), 53 deletions(-) create mode 100644 e2e/circus-concurrent/__tests__/concurrent-mixed.test.js create mode 100644 e2e/circus-concurrent/__tests__/concurrent-only.test.js create mode 100644 e2e/circus-concurrent/__tests__/concurrent-skip.test.js diff --git a/e2e/__tests__/__snapshots__/circusConcurrent.test.ts.snap b/e2e/__tests__/__snapshots__/circusConcurrent.test.ts.snap index c7183e5df107..1d46297fa0e1 100644 --- a/e2e/__tests__/__snapshots__/circusConcurrent.test.ts.snap +++ b/e2e/__tests__/__snapshots__/circusConcurrent.test.ts.snap @@ -1,84 +1,230 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`runs the tests in the correct order 1`] = ` +exports[`all passing runs the tests in the correct order 1`] = ` " console.log - [[beforeAll]] + beforeAll - at log (__tests__/concurrent.test.js:12:29) + at log (__tests__/concurrent.test.js:15:11) console.log - [[test one start]] + START "one" - at log (__tests__/concurrent.test.js:12:29) + at log (__tests__/concurrent.test.js:15:11) console.log - [[test three start]] + START "two" - at log (__tests__/concurrent.test.js:12:29) + at log (__tests__/concurrent.test.js:15:11) console.log - [[test five start]] + START "three" - at log (__tests__/concurrent.test.js:12:29) + at log (__tests__/concurrent.test.js:15:11) console.log - [[test six start]] + START "four" - at log (__tests__/concurrent.test.js:12:29) + at log (__tests__/concurrent.test.js:15:11) console.log - [[test seven (fails) start]] + START "five" - at log (__tests__/concurrent.test.js:12:29) + at log (__tests__/concurrent.test.js:15:11) console.log - [[test three end]] + END: "three" - at log (__tests__/concurrent.test.js:12:29) + at log (__tests__/concurrent.test.js:15:11) console.log - [[test eight start]] + START "six" - at log (__tests__/concurrent.test.js:12:29) + at log (__tests__/concurrent.test.js:15:11) console.log - [[test one end]] + END: "one" - at log (__tests__/concurrent.test.js:12:29) + at log (__tests__/concurrent.test.js:15:11) console.log - [[test nine start]] + START "seven" - at log (__tests__/concurrent.test.js:12:29) + at log (__tests__/concurrent.test.js:15:11) console.log - [[test six end]] + END: "two" - at log (__tests__/concurrent.test.js:12:29) + at log (__tests__/concurrent.test.js:15:11) console.log - [[test ten (fails) start]] + START "eight" - at log (__tests__/concurrent.test.js:12:29) + at log (__tests__/concurrent.test.js:15:11) console.log - [[test eight end]] + END: "four" - at log (__tests__/concurrent.test.js:12:29) + at log (__tests__/concurrent.test.js:15:11) console.log - [[test nine end]] + START "nine" - at log (__tests__/concurrent.test.js:12:29) + at log (__tests__/concurrent.test.js:15:11) console.log - [[test five end]] + END: "nine" - at log (__tests__/concurrent.test.js:12:29) + at log (__tests__/concurrent.test.js:15:11) console.log - [[afterAll]] + START "ten" - at log (__tests__/concurrent.test.js:12:29) + at log (__tests__/concurrent.test.js:15:11) + + console.log + END: "five" + + at log (__tests__/concurrent.test.js:15:11) + + console.log + END: "six" + + at log (__tests__/concurrent.test.js:15:11) + + console.log + END: "seven" + + at log (__tests__/concurrent.test.js:15:11) + + console.log + END: "ten" + + at log (__tests__/concurrent.test.js:15:11) + + console.log + END: "eight" + + at log (__tests__/concurrent.test.js:15:11) + + console.log + afterAll + + at log (__tests__/concurrent.test.js:15:11) +" +`; + +exports[`with only runs the tests in the correct order 1`] = ` +" console.log + beforeAll + + at log (__tests__/concurrent-only.test.js:15:11) + + console.log + START "four" + + at log (__tests__/concurrent-only.test.js:15:11) + + console.log + START "six" + + at log (__tests__/concurrent-only.test.js:15:11) + + console.log + START "nine" + + at log (__tests__/concurrent-only.test.js:15:11) + + console.log + END: "nine" + + at log (__tests__/concurrent-only.test.js:15:11) + + console.log + END: "six" + + at log (__tests__/concurrent-only.test.js:15:11) + + console.log + END: "four" + + at log (__tests__/concurrent-only.test.js:15:11) + + console.log + afterAll + + at log (__tests__/concurrent-only.test.js:15:11) +" +`; + +exports[`with skip runs the tests in the correct order 1`] = ` +" console.log + beforeAll + + at log (__tests__/concurrent-skip.test.js:15:11) + + console.log + START "one" + + at log (__tests__/concurrent-skip.test.js:15:11) + + console.log + START "two" + + at log (__tests__/concurrent-skip.test.js:15:11) + + console.log + START "four" + + at log (__tests__/concurrent-skip.test.js:15:11) + + console.log + START "seven" + + at log (__tests__/concurrent-skip.test.js:15:11) + + console.log + START "eight" + + at log (__tests__/concurrent-skip.test.js:15:11) + + console.log + END: "one" + + at log (__tests__/concurrent-skip.test.js:15:11) + + console.log + START "ten" + + at log (__tests__/concurrent-skip.test.js:15:11) + + console.log + END: "two" + + at log (__tests__/concurrent-skip.test.js:15:11) + + console.log + END: "seven" + + at log (__tests__/concurrent-skip.test.js:15:11) + + console.log + END: "four" + + at log (__tests__/concurrent-skip.test.js:15:11) + + console.log + END: "eight" + + at log (__tests__/concurrent-skip.test.js:15:11) + + console.log + END: "ten" + + at log (__tests__/concurrent-skip.test.js:15:11) + + console.log + afterAll + + at log (__tests__/concurrent-skip.test.js:15:11) " `; diff --git a/e2e/__tests__/circusConcurrent.test.ts b/e2e/__tests__/circusConcurrent.test.ts index 6dde79f6a1f9..93a443027066 100644 --- a/e2e/__tests__/circusConcurrent.test.ts +++ b/e2e/__tests__/circusConcurrent.test.ts @@ -4,21 +4,65 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ +import exp = require('constants'); import {skipSuiteOnJasmine} from '@jest/test-utils'; import runJest, {json as runWithJson} from '../runJest'; skipSuiteOnJasmine(); -it('runs the correct number of tests', () => { - const {json} = runWithJson('circus-concurrent', ['concurrent.test.js']); +describe('all passing', () => { + it('runs the correct number of tests', () => { + const {json, exitCode} = runWithJson('circus-concurrent', [ + 'concurrent.test.js', + ]); - expect(json.numTotalTests).toBe(10); - expect(json.numPassedTests).toBe(6); - expect(json.numFailedTests).toBe(2); - expect(json.numPendingTests).toBe(2); + expect(exitCode).toBe(0); + expect(json.numTotalTests).toBe(10); + expect(json.numPassedTests).toBe(10); + expect(json.numFailedTests).toBe(0); + expect(json.numPendingTests).toBe(0); + }); + + it('runs the tests in the correct order', () => { + const {stdout} = runJest('circus-concurrent', ['concurrent.test.js']); + expect(stdout).toMatchSnapshot(); + }); }); -it('runs the tests in the correct order', () => { - const {stdout} = runJest('circus-concurrent', ['concurrent.test.js']); - expect(stdout).toMatchSnapshot(); +describe('with skip', () => { + it('runs the correct number of tests', () => { + const {json, exitCode} = runWithJson('circus-concurrent', [ + 'concurrent-skip.test.js', + ]); + + expect(exitCode).toBe(0); + expect(json.numTotalTests).toBe(10); + expect(json.numPassedTests).toBe(6); + expect(json.numFailedTests).toBe(0); + expect(json.numPendingTests).toBe(4); + }); + + it('runs the tests in the correct order', () => { + const {stdout} = runJest('circus-concurrent', ['concurrent-skip.test.js']); + expect(stdout).toMatchSnapshot(); + }); +}); + +describe('with only', () => { + it('runs the correct number of tests', () => { + const {json, exitCode} = runWithJson('circus-concurrent', [ + 'concurrent-only.test.js', + ]); + + expect(exitCode).toBe(0); + expect(json.numTotalTests).toBe(10); + expect(json.numPassedTests).toBe(3); + expect(json.numFailedTests).toBe(0); + expect(json.numPendingTests).toBe(7); + }); + + it('runs the tests in the correct order', () => { + const {stdout} = runJest('circus-concurrent', ['concurrent-only.test.js']); + expect(stdout).toMatchSnapshot(); + }); }); diff --git a/e2e/circus-concurrent/__tests__/concurrent-mixed.test.js b/e2e/circus-concurrent/__tests__/concurrent-mixed.test.js new file mode 100644 index 000000000000..097244658124 --- /dev/null +++ b/e2e/circus-concurrent/__tests__/concurrent-mixed.test.js @@ -0,0 +1,63 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +'use strict'; + +const {setTimeout} = require('timers/promises'); + +let delta = Date.now(); +const includeDelta = false; +const marker = s => { + console.log(s, includeDelta ? `+${Date.now() - delta}ms` : ''); + delta = Date.now(); +}; + +beforeAll(() => marker('beforeAll')); +afterAll(() => marker('afterAll')); + +beforeEach(() => marker('beforeEach')); +afterEach(() => marker('afterEach')); + +const testFn = (name, delay, fail) => { + return async () => { + marker(`START "${name}"`); + await setTimeout(delay); + if (fail) { + throw new Error(`${name} failed`); + } + expect(name).toBe(name); + expect.assertions(1); + marker(`END: "${name}"`); + }; +}; + +it.concurrent('one', testFn('one', 85)); +it('two (sequential)', testFn('two (sequential)', 100)); + +describe('level 1', () => { + beforeEach(() => marker('beforeEach level 1')); + afterEach(() => marker('afterEach level 1')); + + it.concurrent('three', testFn('three', 70)); + + it('four (sequential)', testFn('four (sequential)', 120)); + + describe('level 2', () => { + beforeEach(() => marker('beforeEach level 2')); + afterEach(() => marker('afterEach level 2')); + it.concurrent('five', testFn('five', 160)); + + it('six (sequential)', testFn('six (sequential)', 100)); + }); + + it.concurrent('seven', testFn('seven', 100)); + it.concurrent('eight', testFn('eight', 120)); +}); + +it.concurrent('nine', testFn('nine', 20)); + +it.concurrent('ten', testFn('ten', 50)); diff --git a/e2e/circus-concurrent/__tests__/concurrent-only.test.js b/e2e/circus-concurrent/__tests__/concurrent-only.test.js new file mode 100644 index 000000000000..610f8e90cfe2 --- /dev/null +++ b/e2e/circus-concurrent/__tests__/concurrent-only.test.js @@ -0,0 +1,63 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +'use strict'; + +const {setTimeout} = require('timers/promises'); + +let delta = Date.now(); +const includeDelta = false; +const marker = s => { + console.log(s, includeDelta ? `+${Date.now() - delta}ms` : ''); + delta = Date.now(); +}; + +beforeAll(() => marker('beforeAll')); +afterAll(() => marker('afterAll')); + +beforeEach(() => marker('beforeEach')); +afterEach(() => marker('afterEach')); + +const testFn = (name, delay, fail) => { + return async () => { + marker(`START "${name}"`); + await setTimeout(delay); + if (fail) { + throw new Error(`${name} failed`); + } + expect(name).toBe(name); + expect.assertions(1); + marker(`END: "${name}"`); + }; +}; + +it.concurrent('one', testFn('one', 85)); +it.concurrent('two', testFn('two', 100, true)); + +describe('level 1', () => { + beforeEach(() => marker('beforeEach level 1')); + afterEach(() => marker('afterEach level 1')); + + it.concurrent('three', testFn('three', 70)); + + it.concurrent.only('four', testFn('four', 120)); + + describe('level 2', () => { + beforeEach(() => marker('beforeEach level 2')); + afterEach(() => marker('afterEach level 2')); + it.concurrent('five', testFn('five', 160, true)); + + it.concurrent.only('six', testFn('six', 100)); + }); + + it.concurrent('seven', testFn('seven', 100)); + it.concurrent('eight', testFn('eight', 120)); +}); + +it.concurrent.only('nine', testFn('nine', 20)); + +it.concurrent('ten', testFn('ten', 50)); diff --git a/e2e/circus-concurrent/__tests__/concurrent-skip.test.js b/e2e/circus-concurrent/__tests__/concurrent-skip.test.js new file mode 100644 index 000000000000..eef4812899ff --- /dev/null +++ b/e2e/circus-concurrent/__tests__/concurrent-skip.test.js @@ -0,0 +1,63 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +'use strict'; + +const {setTimeout} = require('timers/promises'); + +let delta = Date.now(); +const includeDelta = false; +const marker = s => { + console.log(s, includeDelta ? `+${Date.now() - delta}ms` : ''); + delta = Date.now(); +}; + +beforeAll(() => marker('beforeAll')); +afterAll(() => marker('afterAll')); + +beforeEach(() => marker('beforeEach')); +afterEach(() => marker('afterEach')); + +const testFn = (name, delay, fail) => { + return async () => { + marker(`START "${name}"`); + await setTimeout(delay); + if (fail) { + throw new Error(`${name} failed`); + } + expect(name).toBe(name); + expect.assertions(1); + marker(`END: "${name}"`); + }; +}; + +it.concurrent('one', testFn('one', 85)); +it.concurrent('two', testFn('two', 100)); + +describe('level 1', () => { + beforeEach(() => marker('beforeEach level 1')); + afterEach(() => marker('afterEach level 1')); + + it.concurrent.skip('skipped three', testFn('three', 70)); + + it.concurrent('four', testFn('four', 120)); + + describe('level 2', () => { + beforeEach(() => marker('beforeEach level 2')); + afterEach(() => marker('afterEach level 2')); + it.concurrent.skip('five (skipped)', testFn('five', 160)); + + it.concurrent.skip('six (skipped)', testFn('six', 100)); + }); + + it.concurrent('seven', testFn('seven', 100)); + it.concurrent('eight', testFn('eight', 120)); +}); + +it.concurrent.skip('nine (skipped)', testFn('nine', 20)); + +it.concurrent('ten', testFn('ten', 50)); diff --git a/e2e/circus-concurrent/__tests__/concurrent.test.js b/e2e/circus-concurrent/__tests__/concurrent.test.js index def119be6f9b..71fd346c1ec0 100644 --- a/e2e/circus-concurrent/__tests__/concurrent.test.js +++ b/e2e/circus-concurrent/__tests__/concurrent.test.js @@ -9,7 +9,12 @@ const {setTimeout} = require('timers/promises'); -const marker = s => console.log(`[[${s}]]`); +let delta = Date.now(); +const includeDelta = false; +const marker = s => { + console.log(s, includeDelta ? `+${Date.now() - delta}ms` : ''); + delta = Date.now(); +}; beforeAll(() => marker('beforeAll')); afterAll(() => marker('afterAll')); @@ -19,40 +24,40 @@ afterEach(() => marker('afterEach')); const testFn = (name, delay, fail) => { return async () => { - marker(`test ${name} start`); + marker(`START "${name}"`); await setTimeout(delay); if (fail) { throw new Error(`${name} failed`); } expect(name).toBe(name); expect.assertions(1); - marker(`test ${name} end`); + marker(`END: "${name}"`); }; }; -it.concurrent('one', testFn('one', 100)); -it.concurrent.skip('two (skipped)', testFn('two (skipped)', 100)); +it.concurrent('one', testFn('one', 85)); +it.concurrent('two', testFn('two', 100)); describe('level 1', () => { beforeEach(() => marker('beforeEach level 1')); afterEach(() => marker('afterEach level 1')); - it.concurrent('three', testFn('three', 60)); + it.concurrent('three', testFn('three', 70)); - it.concurrent.skip('four (skipped)', testFn('four (skipped)', 100)); + it.concurrent('four', testFn('four', 120)); describe('level 2', () => { beforeEach(() => marker('beforeEach level 2')); afterEach(() => marker('afterEach level 2')); - it.concurrent('five', testFn('five', 150)); + it.concurrent('five', testFn('five', 160)); it.concurrent('six', testFn('six', 100)); }); - it.concurrent('seven (fails)', testFn('seven (fails)', 100, true)); - it.concurrent('eight', testFn('eight', 50)); + it.concurrent('seven', testFn('seven', 100)); + it.concurrent('eight', testFn('eight', 120)); }); it.concurrent('nine', testFn('nine', 20)); -it.concurrent('ten (fails)', testFn('ten (fails)', 30, true)); +it.concurrent('ten', testFn('ten', 50));