diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d655334ef71..4be1eb9b4a4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - `[jest-circus]` [**BREAKING**] Fail tests when multiple `done()` calls are made ([#10624](https://github.com/facebook/jest/pull/10624)) - `[jest-circus, jest-jasmine2]` [**BREAKING**] Fail the test instead of just warning when describe returns a value ([#10947](https://github.com/facebook/jest/pull/10947)) +- `[jest-circus]` Expose the test's timeout as `expect.deadline()` ([#10993](https://github.com/facebook/jest/pull/10993)) - `[jest-config]` [**BREAKING**] Default to Node testing environment instead of browser (JSDOM) ([#9874](https://github.com/facebook/jest/pull/9874)) - `[jest-config]` [**BREAKING**] Use `jest-circus` as default test runner ([#10686](https://github.com/facebook/jest/pull/10686)) - `[jest-config, jest-runtime]` Support ESM for files other than `.js` and `.mjs` ([#10823](https://github.com/facebook/jest/pull/10823)) diff --git a/e2e/__tests__/deadlines.ts b/e2e/__tests__/deadlines.ts new file mode 100644 index 000000000000..f23a80b16596 --- /dev/null +++ b/e2e/__tests__/deadlines.ts @@ -0,0 +1,15 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * 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 from '../runJest'; + +skipSuiteOnJasmine(); + +it('self checks the deadlines are within bounds', () => { + const result = runJest('deadlines', ['timings.js']); + expect(result.exitCode).toBe(0); +}); diff --git a/e2e/deadlines/__tests__/timings.js b/e2e/deadlines/__tests__/timings.js new file mode 100644 index 000000000000..b4104d33c6f1 --- /dev/null +++ b/e2e/deadlines/__tests__/timings.js @@ -0,0 +1,28 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * 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 fileStarted = Date.now(); + +describe('describe', () => { + jest.setTimeout(789); + + it('is present for global timeout', () => { + const testStarted = Date.now(); + const deadline = expect.deadline(); + expect(deadline).toBeGreaterThanOrEqual(testStarted); + expect(deadline).toBeLessThanOrEqual(testStarted + 789); + }); + + it('explicit override', () => { + const testStarted = Date.now(); + const deadline = expect.deadline(); + expect(deadline).toBeGreaterThanOrEqual(testStarted); + expect(deadline).toBeLessThanOrEqual(testStarted + 456); + }, 456); +}); diff --git a/e2e/deadlines/package.json b/e2e/deadlines/package.json new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/e2e/deadlines/package.json @@ -0,0 +1 @@ +{} diff --git a/packages/expect/src/index.ts b/packages/expect/src/index.ts index e205722ab9cf..ec423bf03e39 100644 --- a/packages/expect/src/index.ts +++ b/packages/expect/src/index.ts @@ -412,6 +412,9 @@ setMatchers(spyMatchers, true, expect as Expect); setMatchers(toThrowMatchers, true, expect as Expect); expect.addSnapshotSerializer = () => void 0; +expect.deadline = () => { + throw new Error('deadline must be implemented by the runtime'); +}; expect.assertions = assertions; expect.hasAssertions = hasAssertions; expect.getState = getState; diff --git a/packages/expect/src/types.ts b/packages/expect/src/types.ts index 04246ef95dd3..7a6a903dce56 100644 --- a/packages/expect/src/types.ts +++ b/packages/expect/src/types.ts @@ -75,6 +75,9 @@ export type Expect = { hasAssertions(): void; setState(state: Partial): void; + // TODO: this is added by test runners, not `expect` itself + deadline(): number; + any(expectedObject: any): AsymmetricMatcher; anything(): AsymmetricMatcher; arrayContaining(sample: Array): AsymmetricMatcher; diff --git a/packages/jest-circus/src/deadlineTimeout.ts b/packages/jest-circus/src/deadlineTimeout.ts new file mode 100644 index 000000000000..07283283cfe9 --- /dev/null +++ b/packages/jest-circus/src/deadlineTimeout.ts @@ -0,0 +1,16 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {getState} from './state'; + +export function deadline(): number { + const deadline = getState()?.currentlyRunningChildDeadline; + if (deadline === null) { + throw new Error('bug! no deadline available'); + } + return deadline; +} diff --git a/packages/jest-circus/src/legacy-code-todo-rewrite/jestExpect.ts b/packages/jest-circus/src/legacy-code-todo-rewrite/jestExpect.ts index a13ce01bd3a8..b7a583ad2da6 100644 --- a/packages/jest-circus/src/legacy-code-todo-rewrite/jestExpect.ts +++ b/packages/jest-circus/src/legacy-code-todo-rewrite/jestExpect.ts @@ -14,6 +14,7 @@ import { toThrowErrorMatchingInlineSnapshot, toThrowErrorMatchingSnapshot, } from 'jest-snapshot'; +import {deadline} from '../deadlineTimeout'; export type Expect = typeof expect; @@ -27,6 +28,7 @@ export default (config: Pick): Expect => { }); expect.addSnapshotSerializer = addSerializer; + expect.deadline = deadline; return expect; }; diff --git a/packages/jest-circus/src/run.ts b/packages/jest-circus/src/run.ts index d49cfdcb31bf..c8cafc218be6 100644 --- a/packages/jest-circus/src/run.ts +++ b/packages/jest-circus/src/run.ts @@ -150,6 +150,7 @@ const _callCircusHook = async ({ }): Promise => { await dispatch({hook, name: 'hook_start'}); const timeout = hook.timeout || getState().testTimeout; + _updateDeadline(timeout); try { await callAsyncCircusFn(hook, testContext, { @@ -168,6 +169,7 @@ const _callCircusTest = async ( ): Promise => { await dispatch({name: 'test_fn_start', test}); const timeout = test.timeout || getState().testTimeout; + _updateDeadline(timeout); invariant(test.fn, `Tests with no 'fn' should have 'mode' set to 'skipped'`); if (test.errors.length) { @@ -185,4 +187,8 @@ const _callCircusTest = async ( } }; +const _updateDeadline = (timeout: number): void => { + getState().currentlyRunningChildDeadline = Date.now() + timeout - 20; +}; + export default run; diff --git a/packages/jest-circus/src/state.ts b/packages/jest-circus/src/state.ts index 5af3c07c22b0..137ea3ea0ff3 100644 --- a/packages/jest-circus/src/state.ts +++ b/packages/jest-circus/src/state.ts @@ -21,6 +21,7 @@ export const ROOT_DESCRIBE_BLOCK_NAME = 'ROOT_DESCRIBE_BLOCK'; const ROOT_DESCRIBE_BLOCK = makeDescribe(ROOT_DESCRIBE_BLOCK_NAME); const INITIAL_STATE: Circus.State = { currentDescribeBlock: ROOT_DESCRIBE_BLOCK, + currentlyRunningChildDeadline: null, currentlyRunningTest: null, expand: undefined, hasFocusedTests: false, diff --git a/packages/jest-types/src/Circus.ts b/packages/jest-types/src/Circus.ts index d93e653d7822..dec60626dcfa 100644 --- a/packages/jest-types/src/Circus.ts +++ b/packages/jest-types/src/Circus.ts @@ -200,6 +200,7 @@ export type GlobalErrorHandlers = { export type State = { currentDescribeBlock: DescribeBlock; currentlyRunningTest?: TestEntry | null; // including when hooks are being executed + currentlyRunningChildDeadline: number | null; expand?: boolean; // expand error messages hasFocusedTests: boolean; // that are defined using test.only hasStarted: boolean; // whether the rootDescribeBlock has started running