Skip to content

Commit

Permalink
Filter API pre-filter setup hook. (#8142)
Browse files Browse the repository at this point in the history
* Filter API pre-filter setup hook.

* Ensure filter API setup Promise resolves before filtering.

* Pass through wrapped filter to avoid including it twice.

* Resolve lint error.

* Fix unit test for Node v6.

* Update changelog.

* Add tests and improve behavior for errors in filter and setup filter.

* Resolve lint error.

* Update CHANGELOG.md

* Fix broken filter test in Node 6.
  • Loading branch information
scotthovestadt committed Mar 19, 2019
1 parent d6af3af commit 4a57c24
Show file tree
Hide file tree
Showing 10 changed files with 154 additions and 14 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,7 @@

### Features

- `[@jest/core]` Filter API pre-filter setup hook ([#8142](https://github.com/facebook/jest/pull/8142))
- `[jest-snapshot]` Improve report when matcher fails, part 14 ([#8132](https://github.com/facebook/jest/pull/8132))
- `[@jest/reporter]` Display todo and skip test descriptions when verbose is true ([#8038](https://github.com/facebook/jest/pull/8038))

Expand Down
25 changes: 25 additions & 0 deletions e2e/__tests__/filter.test.ts
Expand Up @@ -43,4 +43,29 @@ describe('Dynamic test filtering', () => {
expect(result.stderr).toContain('did not return a valid test list');
expect(result.stderr).toContain('my-clowny-filter');
});

it('will call setup on filter before filtering', () => {
const result = runJest('filter', ['--filter=<rootDir>/my-setup-filter.js']);

expect(result.status).toBe(0);
expect(result.stderr).toContain('1 total');
});

it('will print error when filter throws', () => {
const result = runJest('filter', [
'--filter=<rootDir>/my-broken-filter.js',
]);

expect(result.status).toBe(1);
expect(result.stderr).toContain('Error: My broken filter error.');
});

it('will return no results when setup hook throws', () => {
const result = runJest('filter', [
'--filter=<rootDir>/my-broken-setup-filter.js',
]);

expect(result.status).toBe(1);
expect(result.stderr).toContain('Error: My broken setup filter error.');
});
});
9 changes: 9 additions & 0 deletions e2e/filter/my-broken-filter.js
@@ -0,0 +1,9 @@
// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.

'use strict';

module.exports = function(tests) {
return new Promise((resolve, reject) => {
reject(new Error('My broken filter error.'));
});
};
17 changes: 17 additions & 0 deletions e2e/filter/my-broken-setup-filter.js
@@ -0,0 +1,17 @@
// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.

'use strict';

module.exports = function(tests) {
return {
filtered: tests.filter(t => t.indexOf('foo') !== -1).map(test => ({test})),
};
};

module.exports.setup = function() {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject(new Error('My broken setup filter error.'));
}, 0);
});
};
24 changes: 24 additions & 0 deletions e2e/filter/my-setup-filter.js
@@ -0,0 +1,24 @@
// Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.

'use strict';

const setupData = {
filterText: 'this will return no tests',
};

module.exports = function(tests) {
return {
filtered: tests
.filter(t => t.indexOf(setupData.filterText) !== -1)
.map(test => ({test})),
};
};

module.exports.setup = function() {
return new Promise(resolve => {
setTimeout(() => {
setupData.filterText = 'foo';
resolve();
}, 1000);
});
};
14 changes: 4 additions & 10 deletions packages/jest-core/src/SearchSource.ts
Expand Up @@ -21,6 +21,7 @@ import {
TestPathCases,
TestPathCasesWithPathPattern,
TestPathCaseWithPathPatternStats,
Filter,
} from './types';

export type SearchResult = {
Expand All @@ -41,11 +42,6 @@ export type TestSelectionConfig = {
watch?: boolean;
};

type FilterResult = {
test: string;
message: string;
};

const globsToMatcher = (globs?: Array<Config.Glob> | null) => {
if (globs == null || globs.length === 0) {
return () => true;
Expand Down Expand Up @@ -290,18 +286,16 @@ export default class SearchSource {
async getTestPaths(
globalConfig: Config.GlobalConfig,
changedFiles: ChangedFiles | undefined,
filter?: Filter,
): Promise<SearchResult> {
const searchResult = this._getTestPaths(globalConfig, changedFiles);

const filterPath = globalConfig.filter;

if (filterPath && !globalConfig.skipFilter) {
if (filter) {
const tests = searchResult.tests;

const filter = require(filterPath);
const filterResult: {filtered: Array<FilterResult>} = await filter(
tests.map(test => test.path),
);
const filterResult = await filter(tests.map(test => test.path));

if (!Array.isArray(filterResult.filtered)) {
throw new Error(
Expand Down
56 changes: 54 additions & 2 deletions packages/jest-core/src/cli/index.ts
Expand Up @@ -16,6 +16,7 @@ import HasteMap from 'jest-haste-map';
import chalk from 'chalk';
import rimraf from 'rimraf';
import exit from 'exit';
import {Filter} from '../types';
import createContext from '../lib/create_context';
import getChangedFilesPromise from '../getChangedFilesPromise';
import {formatHandleErrors} from '../collectHandles';
Expand Down Expand Up @@ -145,6 +146,36 @@ const _run = async (
// as soon as possible, so by the time we need the result it's already there.
const changedFilesPromise = getChangedFilesPromise(globalConfig, configs);

// Filter may need to do an HTTP call or something similar to setup.
// We will wait on an async response from this before using the filter.
let filter: Filter | undefined;
if (globalConfig.filter && !globalConfig.skipFilter) {
const rawFilter = require(globalConfig.filter);
let filterSetupPromise: Promise<Error | undefined> | undefined;
if (rawFilter.setup) {
// Wrap filter setup Promise to avoid "uncaught Promise" error.
// If an error is returned, we surface it in the return value.
filterSetupPromise = (async () => {
try {
await rawFilter.setup();
} catch (err) {
return err;
}
return undefined;
})();
}
filter = async (testPaths: Array<string>) => {
if (filterSetupPromise) {
// Expect an undefined return value unless there was an error.
const err = await filterSetupPromise;
if (err) {
throw err;
}
}
return rawFilter(testPaths);
};
}

const {contexts, hasteMapInstances} = await buildContextsAndHasteMaps(
configs,
globalConfig,
Expand All @@ -159,13 +190,15 @@ const _run = async (
globalConfig,
outputStream,
hasteMapInstances,
filter,
)
: await runWithoutWatch(
globalConfig,
contexts,
outputStream,
onComplete,
changedFilesPromise,
filter,
);
};

Expand All @@ -176,17 +209,34 @@ const runWatch = async (
globalConfig: Config.GlobalConfig,
outputStream: NodeJS.WriteStream,
hasteMapInstances: Array<HasteMap>,
filter?: Filter,
) => {
if (hasDeprecationWarnings) {
try {
await handleDeprecationWarnings(outputStream, process.stdin);
return watch(globalConfig, contexts, outputStream, hasteMapInstances);
return watch(
globalConfig,
contexts,
outputStream,
hasteMapInstances,
undefined,
undefined,
filter,
);
} catch (e) {
exit(0);
}
}

return watch(globalConfig, contexts, outputStream, hasteMapInstances);
return watch(
globalConfig,
contexts,
outputStream,
hasteMapInstances,
undefined,
undefined,
filter,
);
};

const runWithoutWatch = async (
Expand All @@ -195,6 +245,7 @@ const runWithoutWatch = async (
outputStream: NodeJS.WritableStream,
onComplete: OnCompleteCallback,
changedFilesPromise?: ChangedFilesPromise,
filter?: Filter,
) => {
const startRun = async (): Promise<void | null> => {
if (!globalConfig.listTests) {
Expand All @@ -204,6 +255,7 @@ const runWithoutWatch = async (
changedFilesPromise,
contexts,
failedTestsCache: undefined,
filter,
globalConfig,
onComplete,
outputStream,
Expand Down
8 changes: 6 additions & 2 deletions packages/jest-core/src/runJest.ts
Expand Up @@ -29,17 +29,18 @@ import TestSequencer from './TestSequencer';
import FailedTestsCache from './FailedTestsCache';
import collectNodeHandles from './collectHandles';
import TestWatcher from './TestWatcher';
import {TestRunData} from './types';
import {TestRunData, Filter} from './types';

const getTestPaths = async (
globalConfig: Config.GlobalConfig,
context: Context,
outputStream: NodeJS.WritableStream,
changedFiles: ChangedFiles | undefined,
jestHooks: JestHookEmitter,
filter?: Filter,
) => {
const source = new SearchSource(context);
const data = await source.getTestPaths(globalConfig, changedFiles);
const data = await source.getTestPaths(globalConfig, changedFiles, filter);

if (!data.tests.length && globalConfig.onlyChanged && data.noSCM) {
new CustomConsole(outputStream, outputStream).log(
Expand Down Expand Up @@ -129,6 +130,7 @@ export default (async function runJest({
changedFilesPromise,
onComplete,
failedTestsCache,
filter,
}: {
globalConfig: Config.GlobalConfig;
contexts: Array<Context>;
Expand All @@ -139,6 +141,7 @@ export default (async function runJest({
changedFilesPromise?: ChangedFilesPromise;
onComplete: (testResults: AggregatedResult) => void;
failedTestsCache?: FailedTestsCache;
filter?: Filter;
}) {
const sequencer = new TestSequencer();
let allTests: Array<Test> = [];
Expand Down Expand Up @@ -168,6 +171,7 @@ export default (async function runJest({
outputStream,
changedFilesPromise && (await changedFilesPromise),
jestHooks,
filter,
);
allTests = allTests.concat(matches.tests);

Expand Down
11 changes: 11 additions & 0 deletions packages/jest-core/src/types.ts
Expand Up @@ -38,3 +38,14 @@ export type TestPathCases = {
export type TestPathCasesWithPathPattern = TestPathCases & {
testPathPattern: (path: Config.Path) => boolean;
};

export type FilterResult = {
test: string;
message: string;
};

export type Filter = (
testPaths: Array<string>,
) => Promise<{
filtered: Array<FilterResult>;
}>;
3 changes: 3 additions & 0 deletions packages/jest-core/src/watch.ts
Expand Up @@ -39,6 +39,7 @@ import {
filterInteractivePlugins,
} from './lib/watch_plugins_helpers';
import activeFilters from './lib/active_filters_message';
import {Filter} from './types';

type ReservedInfo = {
forbiddenOverwriteMessage?: string;
Expand Down Expand Up @@ -83,6 +84,7 @@ export default function watch(
hasteMapInstances: Array<HasteMap>,
stdin: NodeJS.ReadStream = process.stdin,
hooks: JestHook = new JestHook(),
filter?: Filter,
): Promise<void> {
// `globalConfig` will be constantly updated and reassigned as a result of
// watch mode interactions.
Expand Down Expand Up @@ -262,6 +264,7 @@ export default function watch(
changedFilesPromise,
contexts,
failedTestsCache,
filter,
globalConfig,
jestHooks: hooks.getEmitter(),
onComplete: results => {
Expand Down

0 comments on commit 4a57c24

Please sign in to comment.