Skip to content

Commit 132e3d1

Browse files
authored
fix: don't assume stack is always a string (#10697)
1 parent 7b1568d commit 132e3d1

File tree

14 files changed

+113
-36
lines changed

14 files changed

+113
-36
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
- `[jest-config]` Fix bug introduced in watch mode by PR[#10678](https://github.com/facebook/jest/pull/10678/files#r511037803) ([#10692](https://github.com/facebook/jest/pull/10692))
1010
- `[expect]` Stop modifying the sample in `expect.objectContaining()` ([#10711](https://github.com/facebook/jest/pull/10711))
11+
- `[jest-circus, jest-jasmine2]` fix: don't assume `stack` is always a string ([#10697](https://github.com/facebook/jest/pull/10697))
1112
- `[jest-resolve-dependencies]` Resolve mocks as dependencies ([#10713](https://github.com/facebook/jest/pull/10713))
1213

1314
### Chore & Maintenance

e2e/__tests__/__snapshots__/failures.test.ts.snap

Lines changed: 54 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ exports[`not throwing Error objects 3`] = `
1818
FAIL __tests__/throwObject.test.js
1919
● Test suite failed to run
2020
21-
Error: No message was provided
21+
thrown: Object {}
2222
`;
2323

2424
exports[`not throwing Error objects 4`] = `
@@ -117,6 +117,7 @@ FAIL __tests__/duringTests.test.js
117117
Boolean thrown during test
118118
undefined thrown during test
119119
Object thrown during test
120+
Object with stack prop thrown during test
120121
Error during test
121122
done(Error)
122123
done(non-error)
@@ -185,33 +186,49 @@ FAIL __tests__/duringTests.test.js
185186
186187
at Object.test (__tests__/duringTests.test.js:28:1)
187188
189+
● Object with stack prop thrown during test
190+
191+
thrown: Object {
192+
"stack": 42,
193+
}
194+
195+
30 | });
196+
31 |
197+
> 32 | test('Object with stack prop thrown during test', () => {
198+
| ^
199+
33 | // eslint-disable-next-line no-throw-literal
200+
34 | throw {stack: 42};
201+
35 | });
202+
203+
at Object.test (__tests__/duringTests.test.js:32:1)
204+
188205
● Error during test
189206
190207
ReferenceError: doesNotExist is not defined
191208
192-
32 | test('Error during test', () => {
193-
33 | // eslint-disable-next-line no-undef
194-
> 34 | doesNotExist.alsoThisNot;
209+
37 | test('Error during test', () => {
210+
38 | // eslint-disable-next-line no-undef
211+
> 39 | doesNotExist.alsoThisNot;
195212
| ^
196-
35 | });
197-
36 |
198-
37 | test('done(Error)', done => {
213+
40 | });
214+
41 |
215+
42 | test('done(Error)', done => {
199216
200-
at Object.doesNotExist (__tests__/duringTests.test.js:34:3)
217+
at Object.doesNotExist (__tests__/duringTests.test.js:39:3)
201218
202219
done(Error)
203220
204221
this is an error
205222
206-
36 |
207-
37 | test('done(Error)', done => {
208-
> 38 | done(new Error('this is an error'));
223+
41 |
224+
42 | test('done(Error)', done => {
225+
> 43 | done(new Error('this is an error'));
209226
| ^
210-
39 | });
211-
40 |
212-
41 | test('done(non-error)', done => {
227+
44 | });
228+
45 |
229+
46 | test('done(non-error)', done => {
213230
214-
at Object.<anonymous> (__tests__/duringTests.test.js:38:8)
231+
at Object.<anonymous> (__tests__/duringTests.test.js:43:8)
215232
216233
● done(non-error)
217234
@@ -224,15 +241,15 @@ FAIL __tests__/duringTests.test.js
224241
],
225242
}
226243
227-
40 |
228-
41 | test('done(non-error)', done => {
229-
> 42 | done(deepObject);
244+
45 |
245+
46 | test('done(non-error)', done => {
246+
> 47 | done(deepObject);
230247
| ^
231-
43 | });
232-
44 |
233-
45 | test('returned promise rejection', () => Promise.reject(deepObject));
248+
48 | });
249+
49 |
250+
50 | test('returned promise rejection', () => Promise.reject(deepObject));
234251
235-
at Object.done (__tests__/duringTests.test.js:42:3)
252+
at Object.done (__tests__/duringTests.test.js:47:3)
236253
237254
● returned promise rejection
238255
@@ -245,13 +262,23 @@ FAIL __tests__/duringTests.test.js
245262
],
246263
}
247264
248-
43 | });
249-
44 |
250-
> 45 | test('returned promise rejection', () => Promise.reject(deepObject));
265+
48 | });
266+
49 |
267+
> 50 | test('returned promise rejection', () => Promise.reject(deepObject));
251268
| ^
252-
46 |
269+
51 |
253270
254-
at Object.test (__tests__/duringTests.test.js:45:1)
271+
at Object.test (__tests__/duringTests.test.js:50:1)
272+
`;
273+
274+
exports[`not throwing Error objects 6`] = `
275+
FAIL __tests__/throwObjectWithStackProp.test.js
276+
● Test suite failed to run
277+
278+
thrown: Object {
279+
280+
"stack": 42,
281+
}
255282
`;
256283
257284
exports[`works with assertions in separate files 1`] = `

e2e/__tests__/failures.test.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ test('not throwing Error objects', () => {
4040
stderr = runJest(dir, ['duringTests.test.js']).stderr;
4141

4242
if (nodeMajorVersion < 12) {
43-
const lineEntry = '(__tests__/duringTests.test.js:38:8)';
43+
const lineEntry = '(__tests__/duringTests.test.js:43:8)';
4444

4545
expect(stderr).toContain(`at Object.<anonymous>.done ${lineEntry}`);
4646

@@ -51,6 +51,8 @@ test('not throwing Error objects', () => {
5151
}
5252

5353
expect(wrap(cleanStderr(stderr))).toMatchSnapshot();
54+
stderr = runJest(dir, ['throwObjectWithStackProp.test.js']).stderr;
55+
expect(wrap(cleanStderr(stderr))).toMatchSnapshot();
5456
});
5557

5658
test('works with node assert', () => {

e2e/failures/__tests__/duringTests.test.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ test('Object thrown during test', () => {
2929
throw deepObject;
3030
});
3131

32+
test('Object with stack prop thrown during test', () => {
33+
// eslint-disable-next-line no-throw-literal
34+
throw {stack: 42};
35+
});
36+
3237
test('Error during test', () => {
3338
// eslint-disable-next-line no-undef
3439
doesNotExist.alsoThisNot;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
'use strict';
9+
10+
// eslint-disable-next-line no-throw-literal
11+
throw {stack: 42};

packages/jest-circus/src/utils.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ const _getError = (
383383
asyncError = new Error();
384384
}
385385

386-
if (error && (error.stack || error.message)) {
386+
if (error && (typeof error.stack === 'string' || error.message)) {
387387
return error;
388388
}
389389

@@ -392,7 +392,8 @@ const _getError = (
392392
return asyncError;
393393
};
394394

395-
const getErrorStack = (error: Error): string => error.stack || error.message;
395+
const getErrorStack = (error: Error): string =>
396+
typeof error.stack === 'string' ? error.stack : error.message;
396397

397398
export const addErrorToEachTestUnderDescribe = (
398399
describeBlock: Circus.DescribeBlock,

packages/jest-jasmine2/src/__tests__/__snapshots__/expectationResultFactory.test.ts.snap

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3+
exports[`expectationResultFactory returns the result if failed (with \`error.stack\` not as a string). 1`] = `
4+
"thrown: Object {
5+
\\"stack\\": 42,
6+
}"
7+
`;
8+
39
exports[`expectationResultFactory returns the result if passed. 1`] = `
410
Object {
511
"error": undefined,

packages/jest-jasmine2/src/__tests__/expectationResultFactory.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,16 @@ describe('expectationResultFactory', () => {
7878
const result = expectationResultFactory(options);
7979
expect(result.message).toEqual('Expected `Pass`, received `Fail`.');
8080
});
81+
82+
it('returns the result if failed (with `error.stack` not as a string).', () => {
83+
const options = {
84+
actual: 'Fail',
85+
error: {stack: 42},
86+
expected: 'Pass',
87+
matcherName: 'testMatcher',
88+
passed: false,
89+
};
90+
const result = expectationResultFactory(options);
91+
expect(result.message).toMatchSnapshot();
92+
});
8193
});

packages/jest-jasmine2/src/expectationResultFactory.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ function stackFormatter(
4343
}
4444

4545
if (options.error) {
46-
if (options.error.stack) {
46+
if (typeof options.error.stack === 'string') {
4747
return options.error.stack;
4848
}
4949

packages/jest-jasmine2/src/reporter.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ export default class Jasmine2Reporter implements Reporter {
152152

153153
specResult.failedExpectations.forEach(failed => {
154154
const message =
155-
!failed.matcherName && failed.stack
155+
!failed.matcherName && typeof failed.stack === 'string'
156156
? this._addMissingMessageToStack(failed.stack, failed.message)
157157
: failed.message || '';
158158
results.failureMessages.push(message);

packages/jest-message-util/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"chalk": "^4.0.0",
2020
"graceful-fs": "^4.2.4",
2121
"micromatch": "^4.0.2",
22+
"pretty-format": "^26.6.1",
2223
"slash": "^3.0.0",
2324
"stack-utils": "^2.0.2"
2425
},

packages/jest-message-util/src/index.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import * as fs from 'graceful-fs';
1010
import type {Config, TestResult} from '@jest/types';
1111
import chalk = require('chalk');
1212
import micromatch = require('micromatch');
13+
import prettyFormat = require('pretty-format');
1314
import slash = require('slash');
1415
import {codeFrameColumns} from '@babel/code-frame';
1516
import StackUtils = require('stack-utils');
@@ -140,7 +141,10 @@ export const formatExecError = (
140141
stack = error;
141142
} else {
142143
message = error.message;
143-
stack = error.stack;
144+
stack =
145+
typeof error.stack === 'string'
146+
? error.stack
147+
: `thrown: ${prettyFormat(error, {maxDepth: 3})}`;
144148
}
145149

146150
const separated = separateMessageFromStack(stack || '');
@@ -160,9 +164,12 @@ export const formatExecError = (
160164
? '\n' + formatStackTrace(stack, config, options, testPath)
161165
: '';
162166

163-
if (blankStringRegexp.test(message) && blankStringRegexp.test(stack)) {
167+
if (
168+
typeof stack !== 'string' ||
169+
(blankStringRegexp.test(message) && blankStringRegexp.test(stack))
170+
) {
164171
// this can happen if an empty object is thrown.
165-
message = MESSAGE_INDENT + 'Error: No message was provided';
172+
message = `thrown: ${prettyFormat(error, {maxDepth: 3})}`;
166173
}
167174

168175
let messageToUse;

packages/jest-message-util/tsconfig.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,8 @@
44
"rootDir": "src",
55
"outDir": "build"
66
},
7-
"references": [{"path": "../jest-types"}]
7+
"references": [
8+
{"path": "../jest-types"},
9+
{"path": "../pretty-format"}
10+
]
811
}

yarn.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11728,6 +11728,7 @@ fsevents@^1.2.7:
1172811728
chalk: ^4.0.0
1172911729
graceful-fs: ^4.2.4
1173011730
micromatch: ^4.0.2
11731+
pretty-format: ^26.6.1
1173111732
slash: ^3.0.0
1173211733
stack-utils: ^2.0.2
1173311734
languageName: unknown

0 commit comments

Comments
 (0)