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

feat: report Individual Test Cases #10227

Merged
merged 54 commits into from Jul 30, 2020
Merged
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
71da2fc
Add custom message support to jest-worker
rogeliog Sep 28, 2019
4283f01
WIP
rogeliog Sep 29, 2019
f34cfc7
Add @SimenB's code
rogeliog Sep 29, 2019
6670252
It kind of works
rogeliog Sep 30, 2019
d13455f
WIP
rogeliog Sep 30, 2019
4bb9b1f
Better types
rogeliog Sep 30, 2019
4d475c1
Update types
rogeliog Sep 30, 2019
c0cd04f
Add support for runInBand
rogeliog Sep 30, 2019
a6bb2ec
Remove jest-worker dependency from jest-circus
rogeliog Oct 1, 2019
6331ae2
WIP
rogeliog Oct 1, 2019
df62713
Fix tests and prevent breaking api
rogeliog Oct 2, 2019
a7741c3
Fix lint issues
rogeliog Oct 3, 2019
a045229
Add copyright header to new files
rogeliog Oct 3, 2019
8042d4e
Merge branch 'master' into per-test-report
kunal-kushwaha Jun 15, 2020
0a135c6
merged feature/custom-messages
kunal-kushwaha Jun 21, 2020
295f7fe
wip: jest circus issue remains
sauravhiremath Jun 23, 2020
0937cf3
fix: circus testResults concat only when type `DescribeBlock`
sauravhiremath Jun 23, 2020
6aee98d
fix: removed test logs and comments
sauravhiremath Jun 23, 2020
b34fb54
Merge branch 'master' into per-test-report
kunal-kushwaha Jun 23, 2020
15279db
requested changes made
kunal-kushwaha Jun 24, 2020
679d59e
fix: changes + type updated
sauravhiremath Jun 24, 2020
f66dac9
Merge remote 'per-test-report' into per-test-report
sauravhiremath Jun 24, 2020
4be1894
fix: `sourcesRelatedToTestsInChangedFiles` added as paramter
sauravhiremath Jun 25, 2020
86ffc36
Merge branch 'master'
sauravhiremath Jun 29, 2020
92e407e
update: removed `testCase` not needed anymore
sauravhiremath Jul 1, 2020
f7e4d8e
chore: added @deprecated message
sauravhiremath Jul 1, 2020
57bc280
`ParseTestResults` not needed anymore
sauravhiremath Jul 2, 2020
f57b2a7
feat: memory-leak fixed
sauravhiremath Jul 2, 2020
44d9c4e
fixes + opt-in breaking change + event-types
sauravhiremath Jul 9, 2020
0183785
tests fixed
sauravhiremath Jul 9, 2020
7960414
Merge branch 'master' of github.com:MLH-Fellowship/jest into per-test…
sauravhiremath Jul 20, 2020
dbd920a
Merge branch 'master'
sauravhiremath Jul 21, 2020
7f314d4
update: added emittery and enable test-reporting
sauravhiremath Jul 21, 2020
dc8f206
fix: types updated + minor fixes
sauravhiremath Jul 23, 2020
2990386
Merge branch master
sauravhiremath Jul 24, 2020
8d4f25a
update: changelog added
sauravhiremath Jul 24, 2020
1106c1e
update: cleaner typings
sauravhiremath Jul 27, 2020
6b8e2da
Merge branch 'master'
sauravhiremath Jul 27, 2020
056eec3
add dep on runner to circus, and remove unused jasmine dep from runner
SimenB Jul 27, 2020
489423e
update: add @SimenB's changes
sauravhiremath Jul 27, 2020
7221be7
hotfix: missed changes
sauravhiremath Jul 27, 2020
5a8a905
Merge branch @SimenB:per-test-report-fork
sauravhiremath Jul 27, 2020
ce361db
requested changes made
kunal-kushwaha Jul 28, 2020
5540d61
added on method in TestRunner
kunal-kushwaha Jul 28, 2020
4538211
update: add @SimenB's changes
kunal-kushwaha Jul 28, 2020
b235a1b
merged master & resolved conflicts
kunal-kushwaha Jul 28, 2020
69a6aa7
minor changes
kunal-kushwaha Jul 28, 2020
6ffa6ae
update: add @SimenB's changes
kunal-kushwaha Jul 28, 2020
d08c35d
removed the UNSTABLE_prefix & resolved usages
kunal-kushwaha Jul 28, 2020
1f3d3f5
fix: update test progress output
sauravhiremath Jul 28, 2020
5cc0b97
update: avoid breaking change + tests updated
sauravhiremath Jul 28, 2020
9c7013e
fix: added invariant for context and diffs removed
sauravhiremath Jul 29, 2020
7b7797c
minor fix
sauravhiremath Jul 30, 2020
090296d
Merge branch 'master' and changelog update
sauravhiremath Jul 30, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -2,6 +2,7 @@

### Features

- `[jest-circus]` Added support for reporting individual test cases using jest-circus ([#10227](https://github.com/facebook/jest/pull/10227))
- `[jest-worker]` Added support for workers to send custom messages to parent in jest-worker ([#10293](https://github.com/facebook/jest/pull/10293))
- `[pretty-format]` Added support for serializing custom elements (web components) ([#10217](https://github.com/facebook/jest/pull/10237))

Expand Down
Expand Up @@ -9,6 +9,7 @@ import * as path from 'path';
import type {Config} from '@jest/types';
import type {JestEnvironment} from '@jest/environment';
import type {TestResult} from '@jest/test-result';
import TestRunner = require('jest-runner');
SimenB marked this conversation as resolved.
Show resolved Hide resolved
import type {RuntimeType as Runtime} from 'jest-runtime';
import type {SnapshotStateType} from 'jest-snapshot';
import {deepCyclicCopy} from 'jest-util';
Expand All @@ -22,6 +23,7 @@ const jestAdapter = async (
environment: JestEnvironment,
runtime: Runtime,
testPath: string,
sendMessageToJest?: TestRunner.TestFileEvent,
): Promise<TestResult> => {
const {
initialize,
Expand All @@ -46,6 +48,7 @@ const jestAdapter = async (
globalConfig,
localRequire: runtime.requireModule.bind(runtime),
parentProcess: process,
sendMessageToJest,
testPath,
});

Expand Down
Expand Up @@ -15,6 +15,7 @@ import {
} from '@jest/test-result';
import {extractExpectedAssertionsErrors, getState, setState} from 'expect';
import {formatExecError, formatResultsErrors} from 'jest-message-util';
import TestRunner = require('jest-runner');
import {
SnapshotState,
SnapshotStateType,
Expand All @@ -30,6 +31,7 @@ import {
} from '../state';
import {getTestID} from '../utils';
import run from '../run';
import testCaseReportHandler from '../testCaseReportHandler';
import globals from '..';

type Process = NodeJS.Process;
Expand All @@ -45,6 +47,7 @@ export const initialize = async ({
localRequire,
parentProcess,
testPath,
sendMessageToJest,
}: {
config: Config.ProjectConfig;
environment: JestEnvironment;
Expand All @@ -54,6 +57,7 @@ export const initialize = async ({
localRequire: (path: Config.Path) => any;
testPath: Config.Path;
parentProcess: Process;
sendMessageToJest?: TestRunner.TestFileEvent;
}) => {
if (globalConfig.testTimeout) {
getRunnerState().testTimeout = globalConfig.testTimeout;
Expand Down Expand Up @@ -140,6 +144,9 @@ export const initialize = async ({
setState({snapshotState, testPath});

addEventHandler(handleSnapshotStateAfterRetry(snapshotState));
if (sendMessageToJest) {
addEventHandler(testCaseReportHandler(testPath, sendMessageToJest));
}

// Return it back to the outer scope (test runner outside the VM).
return {globals, snapshotState};
Expand Down
26 changes: 26 additions & 0 deletions packages/jest-circus/src/testCaseReportHandler.ts
@@ -0,0 +1,26 @@
/**
* 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 type {Circus} from '@jest/types';
import TestRunner = require('jest-runner');
import {makeSingleTestResult, parseSingleTestResult} from './utils';

const testCaseReportHandler = (
testPath: string,
sendMessageToJest: TestRunner.TestFileEvent,
) => (event: Circus.Event) => {
switch (event.name) {
case 'test_done': {
const testResult = makeSingleTestResult(event.test);
const testCaseResult = parseSingleTestResult(testResult);
sendMessageToJest('test-case-result', [testPath, testCaseResult]);
SimenB marked this conversation as resolved.
Show resolved Hide resolved
break;
}
}
};

export default testCaseReportHandler;
124 changes: 80 additions & 44 deletions packages/jest-circus/src/utils.ts
Expand Up @@ -12,7 +12,8 @@ import co from 'co';
import dedent = require('dedent');
import StackUtils = require('stack-utils');
import prettyFormat = require('pretty-format');
import {getState} from './state';
import type {AssertionResult, Status} from '@jest/test-result';
import {ROOT_DESCRIBE_BLOCK_NAME, getState} from './state';

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

Expand Down Expand Up @@ -282,60 +283,61 @@ export const makeRunResult = (
unhandledErrors: unhandledErrors.map(_formatError),
});

export const makeSingleTestResult = (
test: Circus.TestEntry,
): Circus.TestResult => {
const {includeTestLocationInResult} = getState();
const testPath = [];
let parent: Circus.TestEntry | Circus.DescribeBlock | undefined = test;

const {status} = test;
invariant(status, 'Status should be present after tests are run.');

do {
testPath.unshift(parent.name);
} while ((parent = parent.parent));

let location = null;
if (includeTestLocationInResult) {
const stackLine = test.asyncError.stack.split('\n')[1];
const parsedLine = stackUtils.parseLine(stackLine);
if (
parsedLine &&
typeof parsedLine.column === 'number' &&
typeof parsedLine.line === 'number'
) {
location = {
column: parsedLine.column,
line: parsedLine.line,
};
}
}

return {
duration: test.duration,
errors: test.errors.map(_formatError),
invocations: test.invocations,
location,
status,
testPath: Array.from(testPath),
};
};

const makeTestResults = (
describeBlock: Circus.DescribeBlock,
): Circus.TestResults => {
const {includeTestLocationInResult} = getState();
const testResults: Circus.TestResults = [];

for (const child of describeBlock.children) {
switch (child.type) {
case 'describeBlock': {
testResults.push(...makeTestResults(child));
break;
}
case 'test':
{
const testPath = [];
let parent:
| Circus.TestEntry
| Circus.DescribeBlock
| undefined = child;
do {
testPath.unshift(parent.name);
} while ((parent = parent.parent));

const {status} = child;

if (!status) {
throw new Error('Status should be present after tests are run.');
}

let location = null;
if (includeTestLocationInResult) {
const stackLine = child.asyncError.stack.split('\n')[1];
const parsedLine = stackUtils.parseLine(stackLine);
if (
parsedLine &&
typeof parsedLine.column === 'number' &&
typeof parsedLine.line === 'number'
) {
location = {
column: parsedLine.column,
line: parsedLine.line,
};
}
}

testResults.push({
duration: child.duration,
errors: child.errors.map(_formatError),
invocations: child.invocations,
location,
status,
testPath,
});
}
case 'test': {
testResults.push(makeSingleTestResult(child));
break;
}
}
}

Expand Down Expand Up @@ -408,3 +410,37 @@ export function invariant(
throw new Error(message);
}
}

export const parseSingleTestResult = (
testResult: Circus.TestResult,
): AssertionResult => {
let status: Status;
if (testResult.status === 'skip') {
status = 'pending';
} else if (testResult.status === 'todo') {
status = 'todo';
} else if (testResult.errors.length) {
sauravhiremath marked this conversation as resolved.
Show resolved Hide resolved
status = 'failed';
} else {
status = 'passed';
}

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

return {
ancestorTitles,
duration: testResult.duration,
failureMessages: Array.from(testResult.errors),
fullName: title
? ancestorTitles.concat(title).join(' ')
: ancestorTitles.join(' '),
invocations: testResult.invocations,
location: testResult.location,
numPassingAsserts: 0,
status,
title: testResult.testPath[testResult.testPath.length - 1],
};
};
39 changes: 31 additions & 8 deletions packages/jest-core/src/ReporterDispatcher.ts
Expand Up @@ -5,7 +5,11 @@
* LICENSE file in the root directory of this source tree.
*/

import type {AggregatedResult, TestResult} from '@jest/test-result';
import type {
AggregatedResult,
TestCaseResult,
TestResult,
} from '@jest/test-result';
import type {Test} from 'jest-runner';
import type {Context} from 'jest-runtime';
import type {Reporter, ReporterOnStartOptions} from '@jest/reporters';
Expand All @@ -27,14 +31,17 @@ export default class ReporterDispatcher {
);
}

async onTestResult(
async onTestFileResult(
test: Test,
testResult: TestResult,
results: AggregatedResult,
): Promise<void> {
for (const reporter of this._reporters) {
reporter.onTestResult &&
(await reporter.onTestResult(test, testResult, results));
if (reporter.onTestFileResult) {
await reporter.onTestFileResult(test, testResult, results);
} else if (reporter.onTestResult) {
await reporter.onTestResult(test, testResult, results);
}
}

// Release memory if unused later.
Expand All @@ -43,9 +50,13 @@ export default class ReporterDispatcher {
testResult.console = undefined;
}

async onTestStart(test: Test): Promise<void> {
async onTestFileStart(test: Test): Promise<void> {
for (const reporter of this._reporters) {
reporter.onTestStart && (await reporter.onTestStart(test));
if (reporter.onTestFileStart) {
await reporter.onTestFileStart(test);
} else if (reporter.onTestStart) {
await reporter.onTestStart(test);
}
}
}

Expand All @@ -58,13 +69,25 @@ export default class ReporterDispatcher {
}
}

async onTestCaseResult(
test: Test,
testCaseResult: TestCaseResult,
): Promise<void> {
for (const reporter of this._reporters) {
if (reporter.onTestCaseResult) {
await reporter.onTestCaseResult(test, testCaseResult);
}
}
}

async onRunComplete(
contexts: Set<Context>,
results: AggregatedResult,
): Promise<void> {
for (const reporter of this._reporters) {
reporter.onRunComplete &&
(await reporter.onRunComplete(contexts, results));
if (reporter.onRunComplete) {
await reporter.onRunComplete(contexts, results);
}
}
}

Expand Down