ava 5.3.1 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/entrypoints/internal.d.mts +7 -0
- package/lib/api-event-iterator.js +12 -0
- package/lib/api.js +14 -23
- package/lib/assert.js +289 -444
- package/lib/cli.js +95 -61
- package/lib/code-excerpt.js +2 -2
- package/lib/eslint-plugin-helper-worker.js +3 -3
- package/lib/fork.js +3 -13
- package/lib/glob-helpers.cjs +1 -9
- package/lib/globs.js +7 -3
- package/lib/line-numbers.js +1 -1
- package/lib/load-config.js +3 -3
- package/lib/parse-test-args.js +3 -3
- package/lib/plugin-support/shared-workers.js +4 -4
- package/lib/provider-manager.js +11 -13
- package/lib/reporters/beautify-stack.js +0 -1
- package/lib/reporters/default.js +92 -45
- package/lib/reporters/format-serialized-error.js +6 -6
- package/lib/reporters/improper-usage-messages.js +5 -5
- package/lib/reporters/tap.js +30 -30
- package/lib/run-status.js +9 -0
- package/lib/runner.js +7 -7
- package/lib/scheduler.js +14 -1
- package/lib/serialize-error.js +44 -116
- package/lib/slash.cjs +1 -1
- package/lib/snapshot-manager.js +14 -8
- package/lib/test.js +90 -81
- package/lib/watcher.js +494 -365
- package/lib/worker/base.js +90 -51
- package/lib/worker/channel.cjs +9 -53
- package/license +1 -1
- package/package.json +36 -42
- package/readme.md +6 -12
- package/types/assertions.d.cts +107 -49
- package/types/shared-worker.d.cts +0 -2
- package/types/state-change-events.d.cts +143 -0
- package/types/test-fn.d.cts +10 -5
- package/lib/worker/dependency-tracker.js +0 -48
- /package/entrypoints/{main.d.ts → main.d.mts} +0 -0
- /package/entrypoints/{plugin.d.ts → plugin.d.mts} +0 -0
package/lib/serialize-error.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
2
|
+
import {pathToFileURL} from 'node:url';
|
|
3
|
+
import {isNativeError} from 'node:util/types';
|
|
4
4
|
|
|
5
|
-
import cleanYamlObject from 'clean-yaml-object';
|
|
6
5
|
import concordance from 'concordance';
|
|
7
|
-
import isError from 'is-error';
|
|
8
6
|
import StackUtils from 'stack-utils';
|
|
9
7
|
|
|
10
8
|
import {AssertionError} from './assert.js';
|
|
@@ -14,10 +12,6 @@ function isAvaAssertionError(source) {
|
|
|
14
12
|
return source instanceof AssertionError;
|
|
15
13
|
}
|
|
16
14
|
|
|
17
|
-
function filter(propertyName, isRoot) {
|
|
18
|
-
return !isRoot || (propertyName !== 'message' && propertyName !== 'name' && propertyName !== 'stack');
|
|
19
|
-
}
|
|
20
|
-
|
|
21
15
|
function normalizeFile(file, ...base) {
|
|
22
16
|
return file.startsWith('file://') ? file : pathToFileURL(path.resolve(...base, file)).toString();
|
|
23
17
|
}
|
|
@@ -45,126 +39,60 @@ function extractSource(stack, testFile) {
|
|
|
45
39
|
return null;
|
|
46
40
|
}
|
|
47
41
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
42
|
+
const workerErrors = new WeakSet();
|
|
43
|
+
export function tagWorkerError(error) {
|
|
44
|
+
// Track worker errors, which aren't native due to https://github.com/nodejs/node/issues/48716.
|
|
45
|
+
// Still include the check for isNativeError() in case the issue is fixed in the future.
|
|
46
|
+
if (isNativeError(error) || error instanceof Error) {
|
|
47
|
+
workerErrors.add(error);
|
|
51
48
|
}
|
|
52
49
|
|
|
53
|
-
|
|
54
|
-
// is only called in test workers, which are created with their working
|
|
55
|
-
// directory set to the project directory.
|
|
56
|
-
const projectDir = process.cwd();
|
|
57
|
-
|
|
58
|
-
const file = normalizeFile(source.file.trim(), projectDir);
|
|
59
|
-
const rel = path.relative(projectDir, fileURLToPath(file));
|
|
60
|
-
|
|
61
|
-
const [segment] = rel.split(path.sep);
|
|
62
|
-
const isWithinProject = segment !== '..' && (process.platform !== 'win32' || !segment.includes(':'));
|
|
63
|
-
const isDependency = isWithinProject && path.dirname(rel).split(path.sep).includes('node_modules');
|
|
64
|
-
|
|
65
|
-
return {
|
|
66
|
-
isDependency,
|
|
67
|
-
isWithinProject,
|
|
68
|
-
file,
|
|
69
|
-
line: source.line,
|
|
70
|
-
};
|
|
50
|
+
return error;
|
|
71
51
|
}
|
|
72
52
|
|
|
73
|
-
|
|
74
|
-
const stack = error.savedError ? error.savedError.stack : error.stack;
|
|
53
|
+
const isWorkerError = error => workerErrors.has(error);
|
|
75
54
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (error.actualStack) {
|
|
85
|
-
retval.stack = error.actualStack;
|
|
55
|
+
export default function serializeError(error, {testFile = null} = {}) {
|
|
56
|
+
if (!isNativeError(error) && !isWorkerError(error)) {
|
|
57
|
+
return {
|
|
58
|
+
type: 'unknown',
|
|
59
|
+
originalError: error, // Note that the main process receives a structured clone.
|
|
60
|
+
formattedError: concordance.formatDescriptor(concordance.describe(error, concordanceOptions), concordanceOptions),
|
|
61
|
+
};
|
|
86
62
|
}
|
|
87
63
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const source = buildSource(error.fixedSource);
|
|
96
|
-
if (source) {
|
|
97
|
-
retval.source = source;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (error.assertion) {
|
|
102
|
-
retval.assertion = error.assertion;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (error.operator) {
|
|
106
|
-
retval.operator = error.operator;
|
|
107
|
-
}
|
|
108
|
-
} else {
|
|
109
|
-
retval.object = cleanYamlObject(error, filter); // Cleanly copy non-standard properties
|
|
110
|
-
if (typeof error.message === 'string') {
|
|
111
|
-
retval.message = error.message;
|
|
112
|
-
}
|
|
64
|
+
const {message, name, stack} = error;
|
|
65
|
+
const base = {
|
|
66
|
+
message,
|
|
67
|
+
name,
|
|
68
|
+
originalError: error, // Note that the main process receives a structured clone.
|
|
69
|
+
stack,
|
|
70
|
+
};
|
|
113
71
|
|
|
114
|
-
|
|
115
|
-
|
|
72
|
+
if (!isAvaAssertionError(error)) {
|
|
73
|
+
if (name === 'AggregateError') {
|
|
74
|
+
return {
|
|
75
|
+
...base,
|
|
76
|
+
type: 'aggregate',
|
|
77
|
+
errors: error.errors.map(error => serializeError(error, {testFile})),
|
|
78
|
+
};
|
|
116
79
|
}
|
|
117
|
-
}
|
|
118
80
|
|
|
119
|
-
if (typeof error.stack === 'string') {
|
|
120
|
-
const lines = error.stack.split('\n');
|
|
121
|
-
if (error.name === 'SyntaxError' && !lines[0].startsWith('SyntaxError')) {
|
|
122
|
-
retval.summary = '';
|
|
123
|
-
for (const line of lines) {
|
|
124
|
-
retval.summary += line + '\n';
|
|
125
|
-
if (line.startsWith('SyntaxError')) {
|
|
126
|
-
break;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
retval.summary = retval.summary.trim();
|
|
131
|
-
} else {
|
|
132
|
-
retval.summary = '';
|
|
133
|
-
for (let index = 0; index < lines.length; index++) {
|
|
134
|
-
if (lines[index].startsWith(' at')) {
|
|
135
|
-
break;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const next = index + 1;
|
|
139
|
-
const end = next === lines.length || lines[next].startsWith(' at');
|
|
140
|
-
retval.summary += end ? lines[index] : lines[index] + '\n';
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return retval;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
export default function serializeError(origin, shouldBeautifyStack, error, testFile) {
|
|
149
|
-
if (!isError(error)) {
|
|
150
81
|
return {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
82
|
+
...base,
|
|
83
|
+
type: 'native',
|
|
84
|
+
source: extractSource(error.stack, testFile),
|
|
154
85
|
};
|
|
155
86
|
}
|
|
156
87
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
summary: replacement.message,
|
|
168
|
-
};
|
|
169
|
-
}
|
|
88
|
+
return {
|
|
89
|
+
...base,
|
|
90
|
+
type: 'ava',
|
|
91
|
+
assertion: error.assertion,
|
|
92
|
+
improperUsage: error.improperUsage,
|
|
93
|
+
formattedCause: error.cause ? concordance.formatDescriptor(concordance.describe(error.cause, concordanceOptions), concordanceOptions) : null,
|
|
94
|
+
formattedDetails: error.formattedDetails,
|
|
95
|
+
source: extractSource(error.assertionStack, testFile),
|
|
96
|
+
stack: isNativeError(error.cause) ? error.cause.stack : error.assertionStack,
|
|
97
|
+
};
|
|
170
98
|
}
|
package/lib/slash.cjs
CHANGED
package/lib/snapshot-manager.js
CHANGED
|
@@ -9,7 +9,7 @@ import zlib from 'node:zlib';
|
|
|
9
9
|
import cbor from 'cbor';
|
|
10
10
|
import concordance from 'concordance';
|
|
11
11
|
import indentString from 'indent-string';
|
|
12
|
-
import
|
|
12
|
+
import memoize from 'memoize';
|
|
13
13
|
import writeFileAtomic from 'write-file-atomic';
|
|
14
14
|
|
|
15
15
|
import {snapshotManager as concordanceOptions} from './concordance-options.js';
|
|
@@ -104,7 +104,7 @@ function combineEntries({blocks}) {
|
|
|
104
104
|
const combined = new BufferBuilder();
|
|
105
105
|
|
|
106
106
|
for (const {title, snapshots} of blocks) {
|
|
107
|
-
const last = snapshots
|
|
107
|
+
const last = snapshots.at(-1);
|
|
108
108
|
combined.write(`\n\n## ${title}\n\n`);
|
|
109
109
|
|
|
110
110
|
for (const [index, snapshot] of snapshots.entries()) {
|
|
@@ -198,7 +198,7 @@ async function encodeSnapshots(snapshotData) {
|
|
|
198
198
|
], READABLE_PREFIX.byteLength + VERSION_HEADER.byteLength + SHA_256_HASH_LENGTH + compressed.byteLength);
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
-
function
|
|
201
|
+
export function extractCompressedSnapshot(buffer, snapPath) {
|
|
202
202
|
if (isLegacySnapshot(buffer)) {
|
|
203
203
|
throw new LegacyError(snapPath);
|
|
204
204
|
}
|
|
@@ -220,6 +220,12 @@ function decodeSnapshots(buffer, snapPath) {
|
|
|
220
220
|
const compressedOffset = sha256sumOffset + SHA_256_HASH_LENGTH;
|
|
221
221
|
const compressed = buffer.slice(compressedOffset);
|
|
222
222
|
|
|
223
|
+
return {version, compressed, sha256sumOffset, compressedOffset};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function decodeSnapshots(buffer, snapPath) {
|
|
227
|
+
const {compressed, sha256sumOffset, compressedOffset} = extractCompressedSnapshot(buffer, snapPath);
|
|
228
|
+
|
|
223
229
|
const sha256sum = crypto.createHash('sha256').update(compressed).digest();
|
|
224
230
|
const expectedSum = buffer.slice(sha256sumOffset, compressedOffset);
|
|
225
231
|
if (!sha256sum.equals(expectedSum)) {
|
|
@@ -259,8 +265,8 @@ class Manager {
|
|
|
259
265
|
|
|
260
266
|
const block = this.newBlocksByTitle.get(options.belongsTo);
|
|
261
267
|
|
|
262
|
-
const snapshot = block
|
|
263
|
-
const data = snapshot
|
|
268
|
+
const snapshot = block?.snapshots[options.index];
|
|
269
|
+
const data = snapshot?.data;
|
|
264
270
|
|
|
265
271
|
if (!data) {
|
|
266
272
|
if (!this.recordNewSnapshots) {
|
|
@@ -332,7 +338,7 @@ class Manager {
|
|
|
332
338
|
|
|
333
339
|
skipSnapshot({belongsTo, index, deferRecording}) {
|
|
334
340
|
const oldBlock = this.oldBlocksByTitle.get(belongsTo);
|
|
335
|
-
let snapshot = oldBlock
|
|
341
|
+
let snapshot = oldBlock?.snapshots[index];
|
|
336
342
|
|
|
337
343
|
if (!snapshot) {
|
|
338
344
|
snapshot = {};
|
|
@@ -389,7 +395,7 @@ class Manager {
|
|
|
389
395
|
}
|
|
390
396
|
}
|
|
391
397
|
|
|
392
|
-
const resolveSourceFile =
|
|
398
|
+
const resolveSourceFile = memoize(file => {
|
|
393
399
|
const sourceMap = findSourceMap(file);
|
|
394
400
|
// Prior to Node.js 18.8.0, the value when a source map could not be found was `undefined`.
|
|
395
401
|
// This changed to `null` in <https://github.com/nodejs/node/pull/43875>. Check both.
|
|
@@ -407,7 +413,7 @@ const resolveSourceFile = mem(file => {
|
|
|
407
413
|
: payload.sources[0];
|
|
408
414
|
});
|
|
409
415
|
|
|
410
|
-
export const determineSnapshotDir =
|
|
416
|
+
export const determineSnapshotDir = memoize(({file, fixedLocation, projectDir}) => {
|
|
411
417
|
const testDir = path.dirname(resolveSourceFile(file));
|
|
412
418
|
if (fixedLocation) {
|
|
413
419
|
const relativeTestLocation = path.relative(projectDir, testDir);
|
package/lib/test.js
CHANGED
|
@@ -2,25 +2,23 @@ import concordance from 'concordance';
|
|
|
2
2
|
import isPromise from 'is-promise';
|
|
3
3
|
import plur from 'plur';
|
|
4
4
|
|
|
5
|
-
import {AssertionError, Assertions, checkAssertionMessage} from './assert.js';
|
|
5
|
+
import {AssertionError, Assertions, checkAssertionMessage, getAssertionStack} from './assert.js';
|
|
6
6
|
import concordanceOptions from './concordance-options.js';
|
|
7
7
|
import nowAndTimers from './now-and-timers.cjs';
|
|
8
8
|
import parseTestArgs from './parse-test-args.js';
|
|
9
9
|
|
|
10
|
-
const hasOwnProperty = (object, prop) => Object.prototype.hasOwnProperty.call(object, prop);
|
|
11
|
-
|
|
12
10
|
function isExternalAssertError(error) {
|
|
13
11
|
if (typeof error !== 'object' || error === null) {
|
|
14
12
|
return false;
|
|
15
13
|
}
|
|
16
14
|
|
|
17
15
|
// Match errors thrown by <https://www.npmjs.com/package/expect>.
|
|
18
|
-
if (
|
|
16
|
+
if (Object.hasOwn(error, 'matcherResult')) {
|
|
19
17
|
return true;
|
|
20
18
|
}
|
|
21
19
|
|
|
22
20
|
// Match errors thrown by <https://www.npmjs.com/package/chai> and <https://nodejs.org/api/assert.html>.
|
|
23
|
-
return
|
|
21
|
+
return Object.hasOwn(error, 'actual') && Object.hasOwn(error, 'expected');
|
|
24
22
|
}
|
|
25
23
|
|
|
26
24
|
function formatErrorValue(label, error) {
|
|
@@ -28,13 +26,12 @@ function formatErrorValue(label, error) {
|
|
|
28
26
|
return {label, formatted};
|
|
29
27
|
}
|
|
30
28
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
};
|
|
29
|
+
class TestFailure extends Error {
|
|
30
|
+
constructor() {
|
|
31
|
+
super('The test has failed');
|
|
32
|
+
this.name = 'TestFailure';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
38
35
|
|
|
39
36
|
const testMap = new WeakMap();
|
|
40
37
|
class ExecutionContext extends Assertions {
|
|
@@ -42,12 +39,16 @@ class ExecutionContext extends Assertions {
|
|
|
42
39
|
super({
|
|
43
40
|
pass() {
|
|
44
41
|
test.countPassedAssertion();
|
|
42
|
+
return true;
|
|
45
43
|
},
|
|
46
44
|
pending(promise) {
|
|
47
45
|
test.addPendingAssertion(promise);
|
|
48
46
|
},
|
|
49
47
|
fail(error) {
|
|
50
|
-
test.addFailedAssertion(error);
|
|
48
|
+
return test.addFailedAssertion(error);
|
|
49
|
+
},
|
|
50
|
+
failPending(error) {
|
|
51
|
+
return test.failPendingAssertion(error);
|
|
51
52
|
},
|
|
52
53
|
skip() {
|
|
53
54
|
test.countPassedAssertion();
|
|
@@ -72,7 +73,7 @@ class ExecutionContext extends Assertions {
|
|
|
72
73
|
};
|
|
73
74
|
|
|
74
75
|
this.plan = count => {
|
|
75
|
-
test.plan(count,
|
|
76
|
+
test.plan(count, getAssertionStack());
|
|
76
77
|
};
|
|
77
78
|
|
|
78
79
|
this.plan.skip = () => {};
|
|
@@ -81,6 +82,10 @@ class ExecutionContext extends Assertions {
|
|
|
81
82
|
test.timeout(ms, message);
|
|
82
83
|
};
|
|
83
84
|
|
|
85
|
+
this.timeout.clear = () => {
|
|
86
|
+
test.clearTimeout();
|
|
87
|
+
};
|
|
88
|
+
|
|
84
89
|
this.teardown = callback => {
|
|
85
90
|
test.addTeardown(callback);
|
|
86
91
|
};
|
|
@@ -132,7 +137,7 @@ class ExecutionContext extends Assertions {
|
|
|
132
137
|
|
|
133
138
|
if (discarded) {
|
|
134
139
|
test.saveFirstError(new Error('Can’t commit a result that was previously discarded'));
|
|
135
|
-
|
|
140
|
+
throw this.testFailure;
|
|
136
141
|
}
|
|
137
142
|
|
|
138
143
|
committed = true;
|
|
@@ -151,7 +156,7 @@ class ExecutionContext extends Assertions {
|
|
|
151
156
|
discard({retainLogs = false} = {}) {
|
|
152
157
|
if (committed) {
|
|
153
158
|
test.saveFirstError(new Error('Can’t discard a result that was previously committed'));
|
|
154
|
-
|
|
159
|
+
throw this.testFailure;
|
|
155
160
|
}
|
|
156
161
|
|
|
157
162
|
if (discarded) {
|
|
@@ -196,7 +201,7 @@ class ExecutionContext extends Assertions {
|
|
|
196
201
|
export default class Test {
|
|
197
202
|
constructor(options) {
|
|
198
203
|
this.contextRef = options.contextRef;
|
|
199
|
-
this.experiments = options.experiments
|
|
204
|
+
this.experiments = options.experiments ?? {};
|
|
200
205
|
this.failWithoutAssertions = options.failWithoutAssertions;
|
|
201
206
|
this.fn = options.fn;
|
|
202
207
|
this.isHook = options.isHook === true;
|
|
@@ -280,7 +285,7 @@ export default class Test {
|
|
|
280
285
|
};
|
|
281
286
|
|
|
282
287
|
this.assertCount = 0;
|
|
283
|
-
this.assertError =
|
|
288
|
+
this.assertError = null;
|
|
284
289
|
this.attemptCount = 0;
|
|
285
290
|
this.calledEnd = false;
|
|
286
291
|
this.duration = null;
|
|
@@ -291,7 +296,7 @@ export default class Test {
|
|
|
291
296
|
this.pendingAttemptCount = 0;
|
|
292
297
|
this.planCount = null;
|
|
293
298
|
this.startedAt = 0;
|
|
294
|
-
this.
|
|
299
|
+
this.testFailure = null;
|
|
295
300
|
this.timeoutTimer = null;
|
|
296
301
|
}
|
|
297
302
|
|
|
@@ -316,7 +321,7 @@ export default class Test {
|
|
|
316
321
|
this.logs.push(text);
|
|
317
322
|
}
|
|
318
323
|
|
|
319
|
-
addPendingAssertion(promise) {
|
|
324
|
+
async addPendingAssertion(promise) {
|
|
320
325
|
if (this.finishing) {
|
|
321
326
|
this.saveFirstError(new Error('Assertion started, but test has already finished'));
|
|
322
327
|
}
|
|
@@ -329,12 +334,14 @@ export default class Test {
|
|
|
329
334
|
this.pendingAssertionCount++;
|
|
330
335
|
this.refreshTimeout();
|
|
331
336
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
337
|
+
try {
|
|
338
|
+
await promise;
|
|
339
|
+
} catch {
|
|
340
|
+
// Ignore errors.
|
|
341
|
+
} finally {
|
|
342
|
+
this.pendingAssertionCount--;
|
|
343
|
+
this.refreshTimeout();
|
|
344
|
+
}
|
|
338
345
|
}
|
|
339
346
|
|
|
340
347
|
addFailedAssertion(error) {
|
|
@@ -349,6 +356,12 @@ export default class Test {
|
|
|
349
356
|
this.assertCount++;
|
|
350
357
|
this.refreshTimeout();
|
|
351
358
|
this.saveFirstError(error);
|
|
359
|
+
return this.testFailure;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
failPendingAssertion(error) {
|
|
363
|
+
this.saveFirstError(error);
|
|
364
|
+
return this.testFailure;
|
|
352
365
|
}
|
|
353
366
|
|
|
354
367
|
finishAttempt({commit, deferredSnapshotRecordings, errors, logs, passed, retainLogs, snapshotCount, startingSnapshotCount}) {
|
|
@@ -387,15 +400,17 @@ export default class Test {
|
|
|
387
400
|
}
|
|
388
401
|
|
|
389
402
|
this.refreshTimeout();
|
|
403
|
+
if (this.testFailure) {
|
|
404
|
+
throw this.testFailure;
|
|
405
|
+
}
|
|
390
406
|
}
|
|
391
407
|
|
|
392
408
|
saveFirstError(error) {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
}
|
|
409
|
+
this.assertError ??= error;
|
|
410
|
+
this.testFailure = new TestFailure();
|
|
396
411
|
}
|
|
397
412
|
|
|
398
|
-
plan(count,
|
|
413
|
+
plan(count, planAssertionStack) {
|
|
399
414
|
if (typeof count !== 'number') {
|
|
400
415
|
throw new TypeError('Expected a number');
|
|
401
416
|
}
|
|
@@ -404,11 +419,11 @@ export default class Test {
|
|
|
404
419
|
|
|
405
420
|
// In case the `planCount` doesn't match `assertCount, we need the stack of
|
|
406
421
|
// this function to throw with a useful stack.
|
|
407
|
-
this.
|
|
422
|
+
this.planAssertionStack = planAssertionStack;
|
|
408
423
|
}
|
|
409
424
|
|
|
410
425
|
timeout(ms, message) {
|
|
411
|
-
const result = checkAssertionMessage('timeout'
|
|
426
|
+
const result = checkAssertionMessage(message, 't.timeout()');
|
|
412
427
|
if (result !== true) {
|
|
413
428
|
this.saveFirstError(result);
|
|
414
429
|
// Allow the timeout to be set even when the message is invalid.
|
|
@@ -420,28 +435,19 @@ export default class Test {
|
|
|
420
435
|
}
|
|
421
436
|
|
|
422
437
|
this.clearTimeout();
|
|
423
|
-
this.timeoutMs = ms;
|
|
424
438
|
this.timeoutTimer = nowAndTimers.setCappedTimeout(() => {
|
|
425
|
-
this.saveFirstError(new Error(message
|
|
439
|
+
this.saveFirstError(new Error(message ?? 'Test timeout exceeded'));
|
|
426
440
|
|
|
427
441
|
if (this.finishDueToTimeout) {
|
|
428
442
|
this.finishDueToTimeout();
|
|
429
443
|
}
|
|
430
444
|
}, ms);
|
|
431
445
|
|
|
432
|
-
this.notifyTimeoutUpdate(
|
|
446
|
+
this.notifyTimeoutUpdate(ms);
|
|
433
447
|
}
|
|
434
448
|
|
|
435
449
|
refreshTimeout() {
|
|
436
|
-
|
|
437
|
-
return;
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
if (this.timeoutTimer.refresh) {
|
|
441
|
-
this.timeoutTimer.refresh();
|
|
442
|
-
} else {
|
|
443
|
-
this.timeout(this.timeoutMs);
|
|
444
|
-
}
|
|
450
|
+
this.timeoutTimer?.refresh();
|
|
445
451
|
}
|
|
446
452
|
|
|
447
453
|
clearTimeout() {
|
|
@@ -481,11 +487,9 @@ export default class Test {
|
|
|
481
487
|
|
|
482
488
|
verifyPlan() {
|
|
483
489
|
if (!this.assertError && this.planCount !== null && this.planCount !== this.assertCount) {
|
|
484
|
-
this.saveFirstError(new AssertionError({
|
|
485
|
-
assertion: 'plan',
|
|
486
|
-
|
|
487
|
-
operator: '===',
|
|
488
|
-
savedError: this.planError,
|
|
490
|
+
this.saveFirstError(new AssertionError(`Planned for ${this.planCount} ${plur('assertion', this.planCount)}, but got ${this.assertCount}.`, {
|
|
491
|
+
assertion: 't.plan()',
|
|
492
|
+
assertionStack: this.planAssertionStack,
|
|
489
493
|
}));
|
|
490
494
|
}
|
|
491
495
|
}
|
|
@@ -518,54 +522,53 @@ export default class Test {
|
|
|
518
522
|
|
|
519
523
|
callFn() {
|
|
520
524
|
try {
|
|
521
|
-
return
|
|
522
|
-
ok: true,
|
|
523
|
-
retval: this.fn.call(null, this.createExecutionContext()),
|
|
524
|
-
};
|
|
525
|
+
return [true, this.fn.call(null, this.createExecutionContext())];
|
|
525
526
|
} catch (error) {
|
|
526
|
-
return
|
|
527
|
-
ok: false,
|
|
528
|
-
error,
|
|
529
|
-
};
|
|
527
|
+
return [false, error];
|
|
530
528
|
}
|
|
531
529
|
}
|
|
532
530
|
|
|
533
531
|
run() {
|
|
534
532
|
this.startedAt = nowAndTimers.now();
|
|
535
533
|
|
|
536
|
-
const
|
|
537
|
-
if (!
|
|
538
|
-
if (
|
|
539
|
-
this.
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
534
|
+
const [syncOk, retval] = this.callFn();
|
|
535
|
+
if (!syncOk) {
|
|
536
|
+
if (this.testFailure !== null && retval === this.testFailure) {
|
|
537
|
+
return this.finish();
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
if (isExternalAssertError(retval)) {
|
|
541
|
+
this.saveFirstError(new AssertionError('Assertion failed', {
|
|
542
|
+
cause: retval,
|
|
543
|
+
formattedDetails: [{label: 'Assertion failed: ', formatted: retval.message}],
|
|
543
544
|
}));
|
|
544
545
|
} else {
|
|
545
|
-
this.saveFirstError(new AssertionError({
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
546
|
+
this.saveFirstError(new AssertionError('Error thrown in test', {
|
|
547
|
+
// TODO: Provide an assertion stack that traces to the test declaration,
|
|
548
|
+
// rather than AVA internals.
|
|
549
|
+
assertionStack: '',
|
|
550
|
+
cause: retval,
|
|
551
|
+
formattedDetails: [formatErrorValue('Error thrown in test:', retval)],
|
|
549
552
|
}));
|
|
550
553
|
}
|
|
551
554
|
|
|
552
555
|
return this.finish();
|
|
553
556
|
}
|
|
554
557
|
|
|
555
|
-
const returnedObservable =
|
|
556
|
-
const returnedPromise = isPromise(
|
|
558
|
+
const returnedObservable = retval !== null && typeof retval === 'object' && typeof retval.subscribe === 'function';
|
|
559
|
+
const returnedPromise = isPromise(retval);
|
|
557
560
|
|
|
558
561
|
let promise;
|
|
559
562
|
if (returnedObservable) {
|
|
560
563
|
promise = new Promise((resolve, reject) => {
|
|
561
|
-
|
|
564
|
+
retval.subscribe({
|
|
562
565
|
error: reject,
|
|
563
566
|
complete: () => resolve(),
|
|
564
567
|
});
|
|
565
568
|
});
|
|
566
569
|
} else if (returnedPromise) {
|
|
567
570
|
// `retval` can be any thenable, so convert to a proper promise.
|
|
568
|
-
promise = Promise.resolve(
|
|
571
|
+
promise = Promise.resolve(retval);
|
|
569
572
|
}
|
|
570
573
|
|
|
571
574
|
if (promise) {
|
|
@@ -588,17 +591,19 @@ export default class Test {
|
|
|
588
591
|
|
|
589
592
|
promise
|
|
590
593
|
.catch(error => {
|
|
594
|
+
if (this.testFailure !== null && error === this.testFailure) {
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
|
|
591
598
|
if (isExternalAssertError(error)) {
|
|
592
|
-
this.saveFirstError(new AssertionError({
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
values: [{label: 'Assertion failed: ', formatted: error.message}],
|
|
599
|
+
this.saveFirstError(new AssertionError('Assertion failed', {
|
|
600
|
+
cause: error,
|
|
601
|
+
formattedDetails: [{label: 'Assertion failed: ', formatted: error.message}],
|
|
596
602
|
}));
|
|
597
603
|
} else {
|
|
598
|
-
this.saveFirstError(new AssertionError({
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
values: [formatErrorValue('Rejected promise returned by test. Reason:', error)],
|
|
604
|
+
this.saveFirstError(new AssertionError('Rejected promise returned by test', {
|
|
605
|
+
cause: error,
|
|
606
|
+
formattedDetails: [formatErrorValue('Rejected promise returned by test. Reason:', error)],
|
|
602
607
|
}));
|
|
603
608
|
}
|
|
604
609
|
})
|
|
@@ -625,7 +630,11 @@ export default class Test {
|
|
|
625
630
|
if (this.metadata.failing) {
|
|
626
631
|
passed = !passed;
|
|
627
632
|
|
|
628
|
-
error = passed ? null : new
|
|
633
|
+
error = passed ? null : new AssertionError('Test was expected to fail, but succeeded, you should stop marking the test as failing', {
|
|
634
|
+
// TODO: Provide an assertion stack that traces to the test declaration,
|
|
635
|
+
// rather than AVA internals.
|
|
636
|
+
assertionStack: '',
|
|
637
|
+
});
|
|
629
638
|
}
|
|
630
639
|
|
|
631
640
|
return {
|