Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: add babel plugin to make sure Jest is unaffected by fake Promise implementations #7225

Merged
merged 13 commits into from
Oct 24, 2018
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
- `[jest-test-typescript-parser]` Remove from the repository ([#7232](https://github.com/facebook/jest/pull/7232))
- `[tests]` Free tests from the dependency on value of FORCE_COLOR ([#6585](https://github.com/facebook/jest/pull/6585/files))
- `[jest-diff]` Standardize filenames ([#7238](https://github.com/facebook/jest/pull/7238))
- `[*]` Add babel plugin to make sure Jest is unaffected by fake Promise implementations ([#7225](https://github.com/facebook/jest/pull/7225))

### Performance

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
* @flow
*/

import type {TestResult, Status} from 'types/TestResult';
import type {AssertionResult, TestResult, Status} from 'types/TestResult';
import type {GlobalConfig, Path, ProjectConfig} from 'types/Config';
import type {Event, TestEntry} from 'types/Circus';
import type {Event, RunResult, TestEntry} from 'types/Circus';

import {extractExpectedAssertionsErrors, getState, setState} from 'expect';
import {formatExecError, formatResultsErrors} from 'jest-message-util';
Expand All @@ -19,12 +19,11 @@ import {
buildSnapshotResolver,
} from 'jest-snapshot';
import {addEventHandler, dispatch, ROOT_DESCRIBE_BLOCK_NAME} from '../state';
import {getTestID, getOriginalPromise} from '../utils';
import {getTestID} from '../utils';
import run from '../run';
// eslint-disable-next-line import/default
import globals from '../index';

const Promise = getOriginalPromise();
export const initialize = ({
config,
getPrettier,
Expand Down Expand Up @@ -123,46 +122,48 @@ export const runAndTransformResultsToJestFormat = async ({
globalConfig: GlobalConfig,
testPath: string,
}): Promise<TestResult> => {
const runResult = await run();
const runResult: RunResult = await run();

let numFailingTests = 0;
let numPassingTests = 0;
let numPendingTests = 0;
let numTodoTests = 0;

const assertionResults = runResult.testResults.map(testResult => {
let status: Status;
if (testResult.status === 'skip') {
status = 'pending';
numPendingTests += 1;
} else if (testResult.status === 'todo') {
status = 'todo';
numTodoTests += 1;
} else if (testResult.errors.length) {
status = 'failed';
numFailingTests += 1;
} else {
status = 'passed';
numPassingTests += 1;
}

const ancestorTitles = testResult.testPath.filter(
name => name !== ROOT_DESCRIBE_BLOCK_NAME,
);
const title = ancestorTitles.pop();

return {
ancestorTitles,
duration: testResult.duration,
failureMessages: testResult.errors,
fullName: ancestorTitles.concat(title).join(' '),
invocations: testResult.invocations,
location: testResult.location,
numPassingAsserts: 0,
status,
title: testResult.testPath[testResult.testPath.length - 1],
};
});
const assertionResults: Array<AssertionResult> = runResult.testResults.map(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ignore whitespace when looking at this diff. I only added the type annotation

testResult => {
let status: Status;
if (testResult.status === 'skip') {
status = 'pending';
numPendingTests += 1;
} else if (testResult.status === 'todo') {
status = 'todo';
numTodoTests += 1;
} else if (testResult.errors.length) {
status = 'failed';
numFailingTests += 1;
} else {
status = 'passed';
numPassingTests += 1;
}

const ancestorTitles = testResult.testPath.filter(
name => name !== ROOT_DESCRIBE_BLOCK_NAME,
);
const title = ancestorTitles.pop();

return {
ancestorTitles,
duration: testResult.duration,
failureMessages: testResult.errors,
fullName: ancestorTitles.concat(title).join(' '),
invocations: testResult.invocations,
location: testResult.location,
numPassingAsserts: 0,
status,
title: testResult.testPath[testResult.testPath.length - 1],
};
},
);

let failureMessage = formatResultsErrors(
assertionResults,
Expand Down
3 changes: 0 additions & 3 deletions packages/jest-circus/src/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,8 @@ import {
getTestID,
invariant,
makeRunResult,
getOriginalPromise,
} from './utils';

const Promise = getOriginalPromise();

const run = async (): Promise<RunResult> => {
const {rootDescribeBlock} = getState();
dispatch({name: 'run_start'});
Expand Down
6 changes: 0 additions & 6 deletions packages/jest-circus/src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,6 @@ import prettyFormat from 'pretty-format';

import {getState} from './state';

// Try getting the real promise object from the context, if available. Someone
// could have overridden it in a test. Async functions return it implicitly.
// eslint-disable-next-line no-unused-vars
const Promise = global[Symbol.for('jest-native-promise')] || global.Promise;
export const getOriginalPromise = () => Promise;

const stackUtils = new StackUtils({cwd: 'A path that does not exist'});

export const makeDescribe = (
Expand Down
5 changes: 0 additions & 5 deletions packages/jest-jasmine2/src/jasmine/Env.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,6 @@ import checkIsError from '../is_error';
import assertionErrorMessage from '../assert_support';
import {ErrorWithStack} from 'jest-util';

// Try getting the real promise object from the context, if available. Someone
// could have overridden it in a test. Async functions return it implicitly.
// eslint-disable-next-line no-unused-vars
const Promise = global[Symbol.for('jest-native-promise')] || global.Promise;

export default function(j$) {
function Env(options) {
options = options || {};
Expand Down
4 changes: 0 additions & 4 deletions packages/jest-jasmine2/src/p_cancelable.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@

'use strict';

// Try getting the real promise object from the context, if available. Someone
// could have overridden it in a test.
const Promise = global[Symbol.for('jest-native-promise')] || global.Promise;

class CancelError extends Error {
constructor() {
super('Promise was canceled');
Expand Down
4 changes: 0 additions & 4 deletions packages/jest-jasmine2/src/p_timeout.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@
* @flow
*/

// Try getting the real promise object from the context, if available. Someone
// could have overridden it in a test.
const Promise = global[Symbol.for('jest-native-promise')] || global.Promise;

// A specialized version of `p-timeout` that does not touch globals.
// It does not throw on timeout.
export default function pTimeout(
Expand Down
8 changes: 2 additions & 6 deletions packages/jest-jasmine2/src/queue_runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,6 @@
* @flow
*/

// Try getting the real promise object from the context, if available. Someone
// could have overridden it in a test.
const Promise: Class<Promise> =
global[Symbol.for('jest-native-promise')] || global.Promise;

import PCancelable from './p_cancelable';
import pTimeout from './p_timeout';

Expand All @@ -27,14 +22,15 @@ type Options = {
type QueueableFn = {
fn: (next: () => void) => void,
timeout?: () => number,
initError?: Error,
};

export default function queueRunner(options: Options) {
const token = new PCancelable((onCancel, resolve) => {
onCancel(resolve);
});

const mapper = ({fn, timeout, initError = new Error()}) => {
const mapper = ({fn, timeout, initError = new Error()}: QueueableFn) => {
let promise = new Promise(resolve => {
const next = function(err) {
if (err) {
Expand Down
4 changes: 0 additions & 4 deletions packages/jest-jasmine2/src/reporter.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,6 @@ import type {
TestResult,
} from 'types/TestResult';

// Try getting the real promise object from the context, if available. Someone
// could have overridden it in a test.
const Promise = global[Symbol.for('jest-native-promise')] || global.Promise;

import {formatResultsErrors} from 'jest-message-util';

type Suite = {
Expand Down
5 changes: 0 additions & 5 deletions packages/jest-jasmine2/src/tree_processor.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,6 @@ type TreeNode = {
children?: Array<TreeNode>,
};

// Try getting the real promise object from the context, if available. Someone
// could have overridden it in a test. Async functions return it implicitly.
// eslint-disable-next-line no-unused-vars
const Promise = global[Symbol.for('jest-native-promise')] || global.Promise;

export default function treeProcessor(options: Options) {
const {
nodeComplete,
Expand Down
31 changes: 31 additions & 0 deletions scripts/babel-plugin-jest-native-promise.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* Copyright (c) 2014-present, Facebook, Inc. 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';

// This plugin exists to make sure that we use a `Promise` that has not been messed with by user code.
// Might consider extending this to other globals as well in the future

module.exports = ({template}) => {
const promiseDeclaration = template(`
var Promise = global[Symbol.for('jest-native-promise')] || global.Promise;
`);

return {
name: 'jest-native-promise',
visitor: {
ReferencedIdentifier(path, state) {
if (path.node.name === 'Promise' && !state.injectedPromise) {
state.injectedPromise = true;
path
.findParent(p => p.isProgram())
.unshiftContainer('body', promiseDeclaration());
}
},
},
};
};
8 changes: 7 additions & 1 deletion scripts/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,13 @@ function buildFile(file, silent) {
const options = Object.assign({}, transformOptions);
options.plugins = options.plugins.slice();

if (!INLINE_REQUIRE_BLACKLIST.test(file)) {
if (INLINE_REQUIRE_BLACKLIST.test(file)) {
// The modules in the blacklist are injected into the user's sandbox
// We need to guard `Promise` there.
options.plugins.push(
require.resolve('./babel-plugin-jest-native-promise')
);
} else {
// Remove normal plugin.
options.plugins = options.plugins.filter(
plugin =>
Expand Down
3 changes: 2 additions & 1 deletion types/Circus.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

export type DoneFn = (reason?: string | Error) => void;
export type BlockFn = () => void;
export type BlockName = string | Function;
export type BlockName = string;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aaronabramov whenever you find the time, does this make sense? We do not support just a function in describe.

image

thymikee marked this conversation as resolved.
Show resolved Hide resolved
export type BlockMode = void | 'skip' | 'only' | 'todo';
export type TestMode = BlockMode;
export type TestName = string;
Expand Down Expand Up @@ -153,6 +153,7 @@ export type TestStatus = 'skip' | 'done' | 'todo';
export type TestResult = {|
duration: ?number,
errors: Array<FormattedError>,
invocations: number,
status: TestStatus,
location: ?{|column: number, line: number|},
testPath: Array<TestName | BlockName>,
Expand Down
1 change: 1 addition & 0 deletions types/TestResult.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ export type AssertionResult = {|
duration?: ?Milliseconds,
failureMessages: Array<string>,
fullName: string,
invocations?: number,
location: ?Callsite,
numPassingAsserts: number,
status: Status,
Expand Down