mirror of https://github.com/facebook/jest.git
fix(jest-circus) correct concurrent event ordering (#15381)
This commit is contained in:
parent
95f21e4f82
commit
fba7764631
|
@ -16,6 +16,12 @@
|
|||
"windows": {
|
||||
"program": "${workspaceFolder}/node_modules/jest/bin/jest"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Attach to jest",
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"port": 9229
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -62,6 +62,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))
|
||||
|
|
|
@ -0,0 +1,230 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`all passing runs the tests in the correct order 1`] = `
|
||||
" console.log
|
||||
beforeAll
|
||||
|
||||
at log (__tests__/concurrent.test.js:15:11)
|
||||
|
||||
console.log
|
||||
START "one"
|
||||
|
||||
at log (__tests__/concurrent.test.js:15:11)
|
||||
|
||||
console.log
|
||||
START "two"
|
||||
|
||||
at log (__tests__/concurrent.test.js:15:11)
|
||||
|
||||
console.log
|
||||
START "three"
|
||||
|
||||
at log (__tests__/concurrent.test.js:15:11)
|
||||
|
||||
console.log
|
||||
START "four"
|
||||
|
||||
at log (__tests__/concurrent.test.js:15:11)
|
||||
|
||||
console.log
|
||||
START "five"
|
||||
|
||||
at log (__tests__/concurrent.test.js:15:11)
|
||||
|
||||
console.log
|
||||
END: "three"
|
||||
|
||||
at log (__tests__/concurrent.test.js:15:11)
|
||||
|
||||
console.log
|
||||
START "six"
|
||||
|
||||
at log (__tests__/concurrent.test.js:15:11)
|
||||
|
||||
console.log
|
||||
END: "one"
|
||||
|
||||
at log (__tests__/concurrent.test.js:15:11)
|
||||
|
||||
console.log
|
||||
START "seven"
|
||||
|
||||
at log (__tests__/concurrent.test.js:15:11)
|
||||
|
||||
console.log
|
||||
END: "two"
|
||||
|
||||
at log (__tests__/concurrent.test.js:15:11)
|
||||
|
||||
console.log
|
||||
START "eight"
|
||||
|
||||
at log (__tests__/concurrent.test.js:15:11)
|
||||
|
||||
console.log
|
||||
END: "four"
|
||||
|
||||
at log (__tests__/concurrent.test.js:15:11)
|
||||
|
||||
console.log
|
||||
START "nine"
|
||||
|
||||
at log (__tests__/concurrent.test.js:15:11)
|
||||
|
||||
console.log
|
||||
END: "nine"
|
||||
|
||||
at log (__tests__/concurrent.test.js:15:11)
|
||||
|
||||
console.log
|
||||
START "ten"
|
||||
|
||||
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)
|
||||
"
|
||||
`;
|
|
@ -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)"
|
||||
`;
|
||||
|
||||
|
|
|
@ -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 | });
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
/**
|
||||
* 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 exp = require('constants');
|
||||
import {skipSuiteOnJasmine} from '@jest/test-utils';
|
||||
import runJest, {json as runWithJson} from '../runJest';
|
||||
|
||||
skipSuiteOnJasmine();
|
||||
|
||||
describe('all passing', () => {
|
||||
it('runs the correct number of tests', () => {
|
||||
const {json, exitCode} = runWithJson('circus-concurrent', [
|
||||
'concurrent.test.js',
|
||||
]);
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
|
@ -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: [
|
||||
['<rootDir>/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: [
|
||||
['<rootDir>/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: [
|
||||
['<rootDir>/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);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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));
|
|
@ -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));
|
|
@ -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));
|
|
@ -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('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('five', testFn('five', 160));
|
||||
|
||||
it.concurrent('six', testFn('six', 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));
|
|
@ -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', () => {
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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);
|
||||
});
|
|
@ -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();
|
||||
});
|
|
@ -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);
|
||||
});
|
|
@ -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();
|
||||
});
|
|
@ -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();
|
||||
});
|
|
@ -1,5 +1,77 @@
|
|||
// 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
|
||||
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_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
|
||||
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
|
||||
|
|
|
@ -42,3 +42,44 @@ 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();
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
|
|
|
@ -30,6 +30,7 @@ const {setTimeout} = globalThis;
|
|||
|
||||
type ConcurrentTestEntry = Omit<Circus.TestEntry, 'fn'> & {
|
||||
fn: Circus.ConcurrentTestFn;
|
||||
done: Promise<void>;
|
||||
};
|
||||
|
||||
const run = async (): Promise<Circus.RunResult> => {
|
||||
|
@ -63,7 +64,7 @@ const _runTestsForDescribeBlock = async (
|
|||
if (isRootBlock) {
|
||||
const concurrentTests = collectConcurrentTests(describeBlock);
|
||||
if (concurrentTests.length > 0) {
|
||||
startTestsConcurrently(concurrentTests);
|
||||
startTestsConcurrently(concurrentTests, isSkipped);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -81,7 +82,7 @@ const _runTestsForDescribeBlock = async (
|
|||
const retryImmediately: boolean =
|
||||
((globalThis as Global.Global)[RETRY_IMMEDIATELY] as any) || false;
|
||||
|
||||
const deferredRetryTests = [];
|
||||
const deferredRetryTests: Array<Circus.TestEntry> = [];
|
||||
|
||||
if (rng) {
|
||||
describeBlock.children = shuffleArray(describeBlock.children, rng);
|
||||
|
@ -103,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': {
|
||||
|
@ -112,29 +134,24 @@ const _runTestsForDescribeBlock = async (
|
|||
case 'test': {
|
||||
const hasErrorsBeforeTestRun = child.errors.length > 0;
|
||||
const hasRetryTimes = retryTimes > 0;
|
||||
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);
|
||||
if (child.concurrent) {
|
||||
concurrentTests.push(
|
||||
(child as ConcurrentTestEntry).done.then(() =>
|
||||
handleRetry(child, hasErrorsBeforeTestRun, hasRetryTimes),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
await _runTest(child, isSkipped);
|
||||
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);
|
||||
|
@ -155,23 +172,23 @@ 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 [];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function startTestsConcurrently(concurrentTests: Array<ConcurrentTestEntry>) {
|
||||
function startTestsConcurrently(
|
||||
concurrentTests: Array<ConcurrentTestEntry>,
|
||||
parentSkipped: boolean,
|
||||
) {
|
||||
const mutex = pLimit(getState().maxConcurrency);
|
||||
const testNameStorage = new AsyncLocalStorage<string>();
|
||||
jestExpect.setState({
|
||||
|
@ -179,13 +196,16 @@ function startTestsConcurrently(concurrentTests: Array<ConcurrentTestEntry>) {
|
|||
});
|
||||
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;
|
||||
|
|
Loading…
Reference in New Issue