From 16714b4400d26e1f47890348e56c1b2b8d870adb Mon Sep 17 00:00:00 2001 From: "Chris West (Faux)" Date: Fri, 1 Jan 2021 13:39:13 +0000 Subject: [PATCH] feat(circus): expose expect.deadline() c.f. #10895, #10905 Expose the test's timeout back to the the test, so it can make decisions based on this, if it wants. Some proposed uses are included in the above issue, but the implementation has got rather big, and none of the rest absolutely have to be core. This part does. --- CHANGELOG.md | 1 + e2e/__tests__/deadlines.ts | 15 ++++++++++ e2e/deadlines/__tests__/timings.js | 28 +++++++++++++++++++ e2e/deadlines/package.json | 1 + packages/expect/src/index.ts | 3 ++ packages/expect/src/types.ts | 3 ++ packages/jest-circus/src/deadlineTimeout.ts | 16 +++++++++++ .../legacy-code-todo-rewrite/jestExpect.ts | 2 ++ packages/jest-circus/src/run.ts | 6 ++++ packages/jest-circus/src/state.ts | 1 + packages/jest-types/src/Circus.ts | 1 + 11 files changed, 77 insertions(+) create mode 100644 e2e/__tests__/deadlines.ts create mode 100644 e2e/deadlines/__tests__/timings.js create mode 100644 e2e/deadlines/package.json create mode 100644 packages/jest-circus/src/deadlineTimeout.ts 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..b864ed15a8c3 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 ( } }; +function 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