ava 3.15.0 → 4.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/cli.mjs +4 -0
- package/entrypoints/eslint-plugin-helper.cjs +109 -0
- package/entrypoints/main.cjs +2 -0
- package/entrypoints/main.mjs +1 -0
- package/entrypoints/plugin.cjs +2 -0
- package/entrypoints/plugin.mjs +4 -0
- package/index.d.ts +6 -816
- package/lib/api.js +108 -49
- package/lib/assert.js +255 -270
- package/lib/chalk.js +9 -14
- package/lib/cli.js +118 -112
- package/lib/code-excerpt.js +12 -17
- package/lib/concordance-options.js +29 -65
- package/lib/context-ref.js +3 -6
- package/lib/create-chain.js +32 -20
- package/lib/environment-variables.js +1 -4
- package/lib/eslint-plugin-helper-worker.js +73 -0
- package/lib/extensions.js +2 -2
- package/lib/fork.js +81 -84
- package/lib/glob-helpers.cjs +140 -0
- package/lib/globs.js +136 -163
- package/lib/{ipc-flow-control.js → ipc-flow-control.cjs} +1 -0
- package/lib/is-ci.js +4 -2
- package/lib/like-selector.js +7 -13
- package/lib/line-numbers.js +11 -18
- package/lib/load-config.js +56 -180
- package/lib/module-types.js +3 -7
- package/lib/node-arguments.js +4 -5
- package/lib/{now-and-timers.js → now-and-timers.cjs} +0 -0
- package/lib/parse-test-args.js +22 -11
- package/lib/pkg.cjs +2 -0
- package/lib/plugin-support/shared-worker-loader.js +45 -48
- package/lib/plugin-support/shared-workers.js +24 -46
- package/lib/provider-manager.js +20 -14
- package/lib/reporters/beautify-stack.js +6 -12
- package/lib/reporters/colors.js +40 -15
- package/lib/reporters/default.js +114 -364
- package/lib/reporters/format-serialized-error.js +7 -18
- package/lib/reporters/improper-usage-messages.js +8 -9
- package/lib/reporters/prefix-title.js +17 -15
- package/lib/reporters/tap.js +18 -25
- package/lib/run-status.js +29 -23
- package/lib/runner.js +157 -172
- package/lib/scheduler.js +53 -0
- package/lib/serialize-error.js +61 -64
- package/lib/snapshot-manager.js +271 -289
- package/lib/test.js +135 -291
- package/lib/watcher.js +69 -44
- package/lib/worker/base.js +208 -0
- package/lib/worker/channel.cjs +290 -0
- package/lib/worker/dependency-tracker.js +24 -23
- package/lib/worker/{ensure-forked.js → guard-environment.cjs} +5 -4
- package/lib/worker/line-numbers.js +58 -20
- package/lib/worker/main.cjs +12 -0
- package/lib/worker/{options.js → options.cjs} +0 -0
- package/lib/worker/{plugin.js → plugin.cjs} +30 -21
- package/lib/worker/state.cjs +5 -0
- package/lib/worker/utils.cjs +6 -0
- package/package.json +71 -68
- package/plugin.d.ts +51 -53
- package/readme.md +5 -13
- package/types/assertions.d.ts +327 -0
- package/types/subscribable.ts +6 -0
- package/types/test-fn.d.ts +231 -0
- package/types/try-fn.d.ts +58 -0
- package/cli.js +0 -11
- package/eslint-plugin-helper.js +0 -201
- package/index.js +0 -8
- package/lib/worker/ipc.js +0 -201
- package/lib/worker/main.js +0 -21
- package/lib/worker/subprocess.js +0 -266
- package/plugin.js +0 -9
package/lib/snapshot-manager.js
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
1
|
+
import {Buffer} from 'node:buffer';
|
|
2
|
+
import crypto from 'node:crypto';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import {findSourceMap} from 'node:module';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import {fileURLToPath} from 'node:url';
|
|
7
|
+
import zlib from 'node:zlib';
|
|
8
|
+
|
|
9
|
+
import cbor from 'cbor';
|
|
10
|
+
import concordance from 'concordance';
|
|
11
|
+
import indentString from 'indent-string';
|
|
12
|
+
import mem from 'mem';
|
|
13
|
+
import slash from 'slash';
|
|
14
|
+
import writeFileAtomic from 'write-file-atomic';
|
|
15
|
+
|
|
16
|
+
import {snapshotManager as concordanceOptions} from './concordance-options.js';
|
|
17
17
|
|
|
18
18
|
// Increment if encoding layout or Concordance serialization versions change. Previous AVA versions will not be able to
|
|
19
19
|
// decode buffers generated by a newer version, so changing this value will require a major version bump of AVA itself.
|
|
20
20
|
// The version is encoded as an unsigned 16 bit integer.
|
|
21
|
-
const VERSION =
|
|
21
|
+
const VERSION = 3;
|
|
22
22
|
|
|
23
23
|
const VERSION_HEADER = Buffer.alloc(2);
|
|
24
24
|
VERSION_HEADER.writeUInt16LE(VERSION);
|
|
@@ -28,26 +28,24 @@ const READABLE_PREFIX = Buffer.from(`AVA Snapshot v${VERSION}\n`, 'ascii');
|
|
|
28
28
|
const REPORT_SEPARATOR = Buffer.from('\n\n', 'ascii');
|
|
29
29
|
const REPORT_TRAILING_NEWLINE = Buffer.from('\n', 'ascii');
|
|
30
30
|
|
|
31
|
-
const
|
|
31
|
+
const SHA_256_HASH_LENGTH = 32;
|
|
32
32
|
|
|
33
|
-
class SnapshotError extends Error {
|
|
33
|
+
export class SnapshotError extends Error {
|
|
34
34
|
constructor(message, snapPath) {
|
|
35
35
|
super(message);
|
|
36
36
|
this.name = 'SnapshotError';
|
|
37
37
|
this.snapPath = snapPath;
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
|
-
exports.SnapshotError = SnapshotError;
|
|
41
40
|
|
|
42
|
-
class ChecksumError extends SnapshotError {
|
|
41
|
+
export class ChecksumError extends SnapshotError {
|
|
43
42
|
constructor(snapPath) {
|
|
44
43
|
super('Checksum mismatch', snapPath);
|
|
45
44
|
this.name = 'ChecksumError';
|
|
46
45
|
}
|
|
47
46
|
}
|
|
48
|
-
exports.ChecksumError = ChecksumError;
|
|
49
47
|
|
|
50
|
-
class VersionMismatchError extends SnapshotError {
|
|
48
|
+
export class VersionMismatchError extends SnapshotError {
|
|
51
49
|
constructor(snapPath, version) {
|
|
52
50
|
super('Unexpected snapshot version', snapPath);
|
|
53
51
|
this.name = 'VersionMismatchError';
|
|
@@ -55,20 +53,25 @@ class VersionMismatchError extends SnapshotError {
|
|
|
55
53
|
this.expectedVersion = VERSION;
|
|
56
54
|
}
|
|
57
55
|
}
|
|
58
|
-
|
|
56
|
+
|
|
57
|
+
export class InvalidSnapshotError extends SnapshotError {
|
|
58
|
+
constructor(snapPath) {
|
|
59
|
+
super('Invalid snapshot file', snapPath);
|
|
60
|
+
this.name = 'InvalidSnapshotError';
|
|
61
|
+
}
|
|
62
|
+
}
|
|
59
63
|
|
|
60
64
|
const LEGACY_SNAPSHOT_HEADER = Buffer.from('// Jest Snapshot v1');
|
|
61
65
|
function isLegacySnapshot(buffer) {
|
|
62
66
|
return LEGACY_SNAPSHOT_HEADER.equals(buffer.slice(0, LEGACY_SNAPSHOT_HEADER.byteLength));
|
|
63
67
|
}
|
|
64
68
|
|
|
65
|
-
class LegacyError extends SnapshotError {
|
|
69
|
+
export class LegacyError extends SnapshotError {
|
|
66
70
|
constructor(snapPath) {
|
|
67
71
|
super('Legacy snapshot file', snapPath);
|
|
68
72
|
this.name = 'LegacyError';
|
|
69
73
|
}
|
|
70
74
|
}
|
|
71
|
-
exports.LegacyError = LegacyError;
|
|
72
75
|
|
|
73
76
|
function tryRead(file) {
|
|
74
77
|
try {
|
|
@@ -82,168 +85,117 @@ function tryRead(file) {
|
|
|
82
85
|
}
|
|
83
86
|
}
|
|
84
87
|
|
|
85
|
-
function
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}
|
|
88
|
+
function formatEntry(snapshot, index) {
|
|
89
|
+
const {
|
|
90
|
+
data,
|
|
91
|
+
label = `Snapshot ${index + 1}`, // Human-readable labels start counting at 1.
|
|
92
|
+
} = snapshot;
|
|
90
93
|
|
|
91
|
-
|
|
92
|
-
|
|
94
|
+
const description = data
|
|
95
|
+
? concordance.formatDescriptor(concordance.deserialize(data), concordanceOptions)
|
|
96
|
+
: '<No Data>';
|
|
93
97
|
|
|
94
|
-
|
|
95
|
-
if (label) {
|
|
96
|
-
label = `> ${label}\n\n`;
|
|
97
|
-
}
|
|
98
|
+
const blockquote = label.split(/\n/).map(line => '> ' + line).join('\n');
|
|
98
99
|
|
|
99
|
-
|
|
100
|
-
return Buffer.from(label + codeBlock, 'utf8');
|
|
100
|
+
return `${blockquote}\n\n${indentString(description, 4)}`;
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
function combineEntries(
|
|
104
|
-
const
|
|
105
|
-
let byteLength = 0;
|
|
103
|
+
function combineEntries({blocks}) {
|
|
104
|
+
const combined = new BufferBuilder();
|
|
106
105
|
|
|
107
|
-
const
|
|
108
|
-
const
|
|
109
|
-
|
|
106
|
+
for (const {title, snapshots} of blocks) {
|
|
107
|
+
const last = snapshots[snapshots.length - 1];
|
|
108
|
+
combined.write(`\n\n## ${title}\n\n`);
|
|
110
109
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const [assocA, assocB] = [a.associatedTaskIndex, b.associatedTaskIndex];
|
|
116
|
-
if (assocA !== undefined && assocB !== undefined) {
|
|
117
|
-
const assocDifference = assocA - assocB;
|
|
110
|
+
for (const [index, snapshot] of snapshots.entries()) {
|
|
111
|
+
combined.write(formatEntry(snapshot, index));
|
|
118
112
|
|
|
119
|
-
if (
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return a.snapIndex - b.snapIndex;
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
for (const key of sortedKeys) {
|
|
128
|
-
const keyBuffer = Buffer.from(`\n\n## ${key}\n\n`, 'utf8');
|
|
129
|
-
buffers.push(keyBuffer);
|
|
130
|
-
byteLength += keyBuffer.byteLength;
|
|
131
|
-
|
|
132
|
-
const formattedEntries = entries.get(key).buffers;
|
|
133
|
-
const last = formattedEntries[formattedEntries.length - 1];
|
|
134
|
-
for (const entry of formattedEntries) {
|
|
135
|
-
buffers.push(entry);
|
|
136
|
-
byteLength += entry.byteLength;
|
|
137
|
-
|
|
138
|
-
if (entry !== last) {
|
|
139
|
-
buffers.push(REPORT_SEPARATOR);
|
|
140
|
-
byteLength += REPORT_SEPARATOR.byteLength;
|
|
113
|
+
if (snapshot !== last) {
|
|
114
|
+
combined.write(REPORT_SEPARATOR);
|
|
141
115
|
}
|
|
142
116
|
}
|
|
143
117
|
}
|
|
144
118
|
|
|
145
|
-
return
|
|
119
|
+
return combined;
|
|
146
120
|
}
|
|
147
121
|
|
|
148
|
-
function generateReport(relFile, snapFile,
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
let {byteLength} = combined;
|
|
152
|
-
|
|
153
|
-
const header = Buffer.from(`# Snapshot report for \`${slash(relFile)}\`
|
|
122
|
+
function generateReport(relFile, snapFile, snapshots) {
|
|
123
|
+
return new BufferBuilder()
|
|
124
|
+
.write(`# Snapshot report for \`${slash(relFile)}\`
|
|
154
125
|
|
|
155
126
|
The actual snapshot is saved in \`${snapFile}\`.
|
|
156
127
|
|
|
157
|
-
Generated by [AVA](https://avajs.dev)
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
buffers.push(REPORT_TRAILING_NEWLINE);
|
|
162
|
-
byteLength += REPORT_TRAILING_NEWLINE.byteLength;
|
|
163
|
-
return Buffer.concat(buffers, byteLength);
|
|
128
|
+
Generated by [AVA](https://avajs.dev).`)
|
|
129
|
+
.append(combineEntries(snapshots))
|
|
130
|
+
.write(REPORT_TRAILING_NEWLINE)
|
|
131
|
+
.toBuffer();
|
|
164
132
|
}
|
|
165
133
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const prepend = withoutLineEndings(existingReport);
|
|
172
|
-
buffers.unshift(prepend);
|
|
173
|
-
byteLength += prepend.byteLength;
|
|
134
|
+
class BufferBuilder {
|
|
135
|
+
constructor() {
|
|
136
|
+
this.buffers = [];
|
|
137
|
+
this.byteOffset = 0;
|
|
138
|
+
}
|
|
174
139
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
140
|
+
append(builder) {
|
|
141
|
+
this.buffers.push(...builder.buffers);
|
|
142
|
+
this.byteOffset += builder.byteOffset;
|
|
143
|
+
return this;
|
|
144
|
+
}
|
|
179
145
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
// having to rewrite pointers for existing entries.
|
|
187
|
-
const headerLength = Buffer.alloc(4);
|
|
188
|
-
buffers.push(headerLength);
|
|
189
|
-
byteOffset += 4;
|
|
190
|
-
|
|
191
|
-
// Allows 65535 hashes (tests or identified snapshots) per file.
|
|
192
|
-
const numberHashes = Buffer.alloc(2);
|
|
193
|
-
numberHashes.writeUInt16LE(buffersByHash.size);
|
|
194
|
-
buffers.push(numberHashes);
|
|
195
|
-
byteOffset += 2;
|
|
196
|
-
|
|
197
|
-
const entries = [];
|
|
198
|
-
// Maps can't have duplicate keys, so all items in [...buffersByHash.keys()]
|
|
199
|
-
// are unique, so sortedHashes should be deterministic.
|
|
200
|
-
const sortedHashes = [...buffersByHash.keys()].sort();
|
|
201
|
-
const sortedBuffersByHash = [...sortedHashes.map(hash => [hash, buffersByHash.get(hash)])];
|
|
202
|
-
for (const [hash, snapshotBuffers] of sortedBuffersByHash) {
|
|
203
|
-
buffers.push(Buffer.from(hash, 'hex'));
|
|
204
|
-
byteOffset += MD5_HASH_LENGTH;
|
|
205
|
-
|
|
206
|
-
// Allows 65535 snapshots per hash.
|
|
207
|
-
const numberSnapshots = Buffer.alloc(2);
|
|
208
|
-
numberSnapshots.writeUInt16LE(snapshotBuffers.length, 0);
|
|
209
|
-
buffers.push(numberSnapshots);
|
|
210
|
-
byteOffset += 2;
|
|
211
|
-
|
|
212
|
-
for (const value of snapshotBuffers) {
|
|
213
|
-
// Each pointer is 32 bits, restricting the total, uncompressed buffer to
|
|
214
|
-
// 4 GiB.
|
|
215
|
-
const start = Buffer.alloc(4);
|
|
216
|
-
const end = Buffer.alloc(4);
|
|
217
|
-
entries.push({start, end, value});
|
|
218
|
-
|
|
219
|
-
buffers.push(start, end);
|
|
220
|
-
byteOffset += 8;
|
|
146
|
+
write(data) {
|
|
147
|
+
if (typeof data === 'string') {
|
|
148
|
+
this.write(Buffer.from(data, 'utf8'));
|
|
149
|
+
} else {
|
|
150
|
+
this.buffers.push(data);
|
|
151
|
+
this.byteOffset += data.byteLength;
|
|
221
152
|
}
|
|
222
|
-
}
|
|
223
153
|
|
|
224
|
-
|
|
154
|
+
return this;
|
|
155
|
+
}
|
|
225
156
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
const start = bodyOffset;
|
|
229
|
-
const end = bodyOffset + entry.value.byteLength;
|
|
230
|
-
entry.start.writeUInt32LE(start, 0);
|
|
231
|
-
entry.end.writeUInt32LE(end, 0);
|
|
232
|
-
buffers.push(entry.value);
|
|
233
|
-
bodyOffset = end;
|
|
157
|
+
toBuffer() {
|
|
158
|
+
return Buffer.concat(this.buffers, this.byteOffset);
|
|
234
159
|
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function sortBlocks(blocksByTitle, blockIndices) {
|
|
163
|
+
return [...blocksByTitle].sort(
|
|
164
|
+
([aTitle], [bTitle]) => {
|
|
165
|
+
const a = blockIndices.get(aTitle);
|
|
166
|
+
const b = blockIndices.get(bTitle);
|
|
167
|
+
|
|
168
|
+
if (a === undefined) {
|
|
169
|
+
if (b === undefined) {
|
|
170
|
+
return 0;
|
|
171
|
+
}
|
|
235
172
|
|
|
236
|
-
|
|
173
|
+
return 1;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (b === undefined) {
|
|
177
|
+
return -1;
|
|
178
|
+
}
|
|
237
179
|
|
|
238
|
-
|
|
180
|
+
return a - b;
|
|
181
|
+
},
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function encodeSnapshots(snapshotData) {
|
|
186
|
+
const encoded = cbor.encodeOne(snapshotData, {
|
|
187
|
+
omitUndefinedProperties: true,
|
|
188
|
+
canonical: true,
|
|
189
|
+
});
|
|
190
|
+
const compressed = zlib.gzipSync(encoded);
|
|
239
191
|
compressed[9] = 0x03; // Override the GZip header containing the OS to always be Linux
|
|
240
|
-
const
|
|
192
|
+
const sha256sum = crypto.createHash('sha256').update(compressed).digest();
|
|
241
193
|
return Buffer.concat([
|
|
242
194
|
READABLE_PREFIX,
|
|
243
195
|
VERSION_HEADER,
|
|
244
|
-
|
|
245
|
-
compressed
|
|
246
|
-
], READABLE_PREFIX.byteLength + VERSION_HEADER.byteLength +
|
|
196
|
+
sha256sum,
|
|
197
|
+
compressed,
|
|
198
|
+
], READABLE_PREFIX.byteLength + VERSION_HEADER.byteLength + SHA_256_HASH_LENGTH + compressed.byteLength);
|
|
247
199
|
}
|
|
248
200
|
|
|
249
201
|
function decodeSnapshots(buffer, snapPath) {
|
|
@@ -253,182 +205,205 @@ function decodeSnapshots(buffer, snapPath) {
|
|
|
253
205
|
|
|
254
206
|
// The version starts after the readable prefix, which is ended by a newline
|
|
255
207
|
// byte (0x0A).
|
|
256
|
-
const
|
|
208
|
+
const newline = buffer.indexOf(0x0A);
|
|
209
|
+
if (newline === -1) {
|
|
210
|
+
throw new InvalidSnapshotError(snapPath);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const versionOffset = newline + 1;
|
|
257
214
|
const version = buffer.readUInt16LE(versionOffset);
|
|
258
215
|
if (version !== VERSION) {
|
|
259
216
|
throw new VersionMismatchError(snapPath, version);
|
|
260
217
|
}
|
|
261
218
|
|
|
262
|
-
const
|
|
263
|
-
const compressedOffset =
|
|
219
|
+
const sha256sumOffset = versionOffset + 2;
|
|
220
|
+
const compressedOffset = sha256sumOffset + SHA_256_HASH_LENGTH;
|
|
264
221
|
const compressed = buffer.slice(compressedOffset);
|
|
265
222
|
|
|
266
|
-
const
|
|
267
|
-
const expectedSum = buffer.slice(
|
|
268
|
-
if (!
|
|
223
|
+
const sha256sum = crypto.createHash('sha256').update(compressed).digest();
|
|
224
|
+
const expectedSum = buffer.slice(sha256sumOffset, compressedOffset);
|
|
225
|
+
if (!sha256sum.equals(expectedSum)) {
|
|
269
226
|
throw new ChecksumError(snapPath);
|
|
270
227
|
}
|
|
271
228
|
|
|
272
229
|
const decompressed = zlib.gunzipSync(compressed);
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
const headerLength = decompressed.readUInt32LE(byteOffset);
|
|
276
|
-
byteOffset += 4;
|
|
277
|
-
|
|
278
|
-
const snapshotsByHash = new Map();
|
|
279
|
-
const numberHashes = decompressed.readUInt16LE(byteOffset);
|
|
280
|
-
byteOffset += 2;
|
|
281
|
-
|
|
282
|
-
for (let count = 0; count < numberHashes; count++) {
|
|
283
|
-
const hash = decompressed.toString('hex', byteOffset, byteOffset + MD5_HASH_LENGTH);
|
|
284
|
-
byteOffset += MD5_HASH_LENGTH;
|
|
285
|
-
|
|
286
|
-
const numberSnapshots = decompressed.readUInt16LE(byteOffset);
|
|
287
|
-
byteOffset += 2;
|
|
288
|
-
|
|
289
|
-
const snapshotsBuffers = new Array(numberSnapshots);
|
|
290
|
-
for (let index = 0; index < numberSnapshots; index++) {
|
|
291
|
-
const start = decompressed.readUInt32LE(byteOffset) + headerLength;
|
|
292
|
-
byteOffset += 4;
|
|
293
|
-
const end = decompressed.readUInt32LE(byteOffset) + headerLength;
|
|
294
|
-
byteOffset += 4;
|
|
295
|
-
snapshotsBuffers[index] = decompressed.slice(start, end);
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
// Allow for new entries to be appended to an existing header, which could
|
|
299
|
-
// lead to the same hash being present multiple times.
|
|
300
|
-
if (snapshotsByHash.has(hash)) {
|
|
301
|
-
snapshotsByHash.set(hash, snapshotsByHash.get(hash).concat(snapshotsBuffers));
|
|
302
|
-
} else {
|
|
303
|
-
snapshotsByHash.set(hash, snapshotsBuffers);
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
return snapshotsByHash;
|
|
230
|
+
return cbor.decode(decompressed);
|
|
308
231
|
}
|
|
309
232
|
|
|
310
233
|
class Manager {
|
|
311
234
|
constructor(options) {
|
|
312
|
-
this.appendOnly = options.appendOnly;
|
|
313
235
|
this.dir = options.dir;
|
|
314
236
|
this.recordNewSnapshots = options.recordNewSnapshots;
|
|
237
|
+
this.updating = options.updating;
|
|
315
238
|
this.relFile = options.relFile;
|
|
316
239
|
this.reportFile = options.reportFile;
|
|
240
|
+
this.reportPath = options.reportPath;
|
|
317
241
|
this.snapFile = options.snapFile;
|
|
318
242
|
this.snapPath = options.snapPath;
|
|
319
|
-
this.
|
|
243
|
+
this.oldBlocksByTitle = options.oldBlocksByTitle;
|
|
244
|
+
this.newBlocksByTitle = options.newBlocksByTitle;
|
|
245
|
+
this.blockIndices = new Map();
|
|
246
|
+
this.error = options.error;
|
|
320
247
|
|
|
321
248
|
this.hasChanges = false;
|
|
322
|
-
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
touch(title, taskIndex) {
|
|
252
|
+
this.blockIndices.set(title, taskIndex);
|
|
323
253
|
}
|
|
324
254
|
|
|
325
255
|
compare(options) {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
256
|
+
if (this.error) {
|
|
257
|
+
throw this.error;
|
|
258
|
+
}
|
|
329
259
|
|
|
330
|
-
|
|
260
|
+
const block = this.newBlocksByTitle.get(options.belongsTo);
|
|
261
|
+
|
|
262
|
+
const snapshot = block && block.snapshots[options.index];
|
|
263
|
+
const data = snapshot && snapshot.data;
|
|
264
|
+
|
|
265
|
+
if (!data) {
|
|
331
266
|
if (!this.recordNewSnapshots) {
|
|
332
267
|
return {pass: false};
|
|
333
268
|
}
|
|
334
269
|
|
|
335
270
|
if (options.deferRecording) {
|
|
336
|
-
const record = this.deferRecord(
|
|
271
|
+
const record = this.deferRecord(options);
|
|
337
272
|
return {pass: true, record};
|
|
338
273
|
}
|
|
339
274
|
|
|
340
|
-
this.record(
|
|
275
|
+
this.record(options);
|
|
341
276
|
return {pass: true};
|
|
342
277
|
}
|
|
343
278
|
|
|
344
|
-
const actual = concordance.deserialize(
|
|
279
|
+
const actual = concordance.deserialize(data, concordanceOptions);
|
|
345
280
|
const expected = concordance.describe(options.expected, concordanceOptions);
|
|
346
281
|
const pass = concordance.compareDescriptors(actual, expected);
|
|
347
282
|
|
|
348
283
|
return {actual, expected, pass};
|
|
349
284
|
}
|
|
350
285
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
286
|
+
recordSerialized({data, label, belongsTo, index}) {
|
|
287
|
+
let block = this.newBlocksByTitle.get(belongsTo);
|
|
288
|
+
if (!block) {
|
|
289
|
+
block = {snapshots: []};
|
|
290
|
+
}
|
|
356
291
|
|
|
357
|
-
|
|
358
|
-
this.hasChanges = true;
|
|
292
|
+
const {snapshots} = block;
|
|
359
293
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
294
|
+
if (index > snapshots.length) {
|
|
295
|
+
throw new RangeError(`Cannot record snapshot ${index} for ${JSON.stringify(belongsTo)}, exceeds expected index of ${snapshots.length}`);
|
|
296
|
+
} else if (index < snapshots.length) {
|
|
297
|
+
if (snapshots[index].data) {
|
|
298
|
+
throw new RangeError(`Cannot record snapshot ${index} for ${JSON.stringify(belongsTo)}, already exists`);
|
|
364
299
|
}
|
|
365
300
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
}
|
|
301
|
+
snapshots[index] = {data, label};
|
|
302
|
+
} else {
|
|
303
|
+
snapshots.push({data, label});
|
|
304
|
+
}
|
|
369
305
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
}
|
|
306
|
+
this.newBlocksByTitle.set(belongsTo, block);
|
|
307
|
+
}
|
|
373
308
|
|
|
374
|
-
|
|
309
|
+
deferRecord(options) {
|
|
310
|
+
const {expected, belongsTo, label, index} = options;
|
|
311
|
+
const descriptor = concordance.describe(expected, concordanceOptions);
|
|
312
|
+
const data = concordance.serialize(descriptor);
|
|
375
313
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
this.reportEntries.set(options.belongsTo, {buffers: [entry], taskIndex, snapIndex, associatedTaskIndex});
|
|
380
|
-
}
|
|
314
|
+
return () => { // Must be called in order!
|
|
315
|
+
this.hasChanges = true;
|
|
316
|
+
this.recordSerialized({data, label, belongsTo, index});
|
|
381
317
|
};
|
|
382
318
|
}
|
|
383
319
|
|
|
384
|
-
record(
|
|
385
|
-
const record = this.deferRecord(
|
|
320
|
+
record(options) {
|
|
321
|
+
const record = this.deferRecord(options);
|
|
386
322
|
record();
|
|
387
323
|
}
|
|
388
324
|
|
|
325
|
+
skipBlock(title) {
|
|
326
|
+
const block = this.oldBlocksByTitle.get(title);
|
|
327
|
+
|
|
328
|
+
if (block) {
|
|
329
|
+
this.newBlocksByTitle.set(title, block);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
skipSnapshot({belongsTo, index, deferRecording}) {
|
|
334
|
+
const oldBlock = this.oldBlocksByTitle.get(belongsTo);
|
|
335
|
+
let snapshot = oldBlock && oldBlock.snapshots[index];
|
|
336
|
+
|
|
337
|
+
if (!snapshot) {
|
|
338
|
+
snapshot = {};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Retain the label from the old snapshot, so as not to assume that the
|
|
342
|
+
// snapshot.skip() arguments are well-formed.
|
|
343
|
+
|
|
344
|
+
// Defer recording if called in a try().
|
|
345
|
+
if (deferRecording) {
|
|
346
|
+
return () => { // Must be called in order!
|
|
347
|
+
this.recordSerialized({belongsTo, index, ...snapshot});
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
this.recordSerialized({belongsTo, index, ...snapshot});
|
|
352
|
+
}
|
|
353
|
+
|
|
389
354
|
save() {
|
|
355
|
+
const {dir, relFile, snapFile, snapPath, reportPath} = this;
|
|
356
|
+
|
|
357
|
+
if (this.updating && this.newBlocksByTitle.size === 0) {
|
|
358
|
+
return {
|
|
359
|
+
changedFiles: [cleanFile(snapPath), cleanFile(reportPath)].flat(),
|
|
360
|
+
temporaryFiles: [],
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
390
364
|
if (!this.hasChanges) {
|
|
391
365
|
return null;
|
|
392
366
|
}
|
|
393
367
|
|
|
394
|
-
const
|
|
395
|
-
|
|
368
|
+
const snapshots = {
|
|
369
|
+
blocks: sortBlocks(this.newBlocksByTitle, this.blockIndices).map(
|
|
370
|
+
([title, block]) => ({title, ...block}),
|
|
371
|
+
),
|
|
372
|
+
};
|
|
396
373
|
|
|
397
|
-
const
|
|
398
|
-
const
|
|
399
|
-
const reportBuffer = existingReport ?
|
|
400
|
-
appendReportEntries(existingReport, this.reportEntries) :
|
|
401
|
-
generateReport(this.relFile, this.snapFile, this.reportEntries);
|
|
374
|
+
const buffer = encodeSnapshots(snapshots);
|
|
375
|
+
const reportBuffer = generateReport(relFile, snapFile, snapshots);
|
|
402
376
|
|
|
403
|
-
fs.mkdirSync(
|
|
377
|
+
fs.mkdirSync(dir, {recursive: true});
|
|
404
378
|
|
|
405
|
-
const
|
|
406
|
-
const tmpfileCreated =
|
|
379
|
+
const temporaryFiles = [];
|
|
380
|
+
const tmpfileCreated = file => temporaryFiles.push(file);
|
|
407
381
|
writeFileAtomic.sync(snapPath, buffer, {tmpfileCreated});
|
|
408
382
|
writeFileAtomic.sync(reportPath, reportBuffer, {tmpfileCreated});
|
|
409
|
-
return
|
|
383
|
+
return {
|
|
384
|
+
changedFiles: [snapPath, reportPath],
|
|
385
|
+
temporaryFiles,
|
|
386
|
+
};
|
|
410
387
|
}
|
|
411
388
|
}
|
|
412
389
|
|
|
413
390
|
const resolveSourceFile = mem(file => {
|
|
414
|
-
const
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
return file; // Assume the file is stubbed in our test suite.
|
|
391
|
+
const sourceMap = findSourceMap(file);
|
|
392
|
+
if (sourceMap === undefined) {
|
|
393
|
+
return file;
|
|
418
394
|
}
|
|
419
395
|
|
|
420
|
-
const
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
const map = converter.toObject();
|
|
424
|
-
const firstSource = `${map.sourceRoot || ''}${map.sources[0]}`;
|
|
425
|
-
return path.resolve(testDir, firstSource);
|
|
396
|
+
const {payload} = sourceMap;
|
|
397
|
+
if (payload.sources.length === 0) { // Hypothetical?
|
|
398
|
+
return file;
|
|
426
399
|
}
|
|
427
400
|
|
|
428
|
-
return file
|
|
401
|
+
return payload.sources[0].startsWith('file://')
|
|
402
|
+
? fileURLToPath(payload.sources[0])
|
|
403
|
+
: payload.sources[0];
|
|
429
404
|
});
|
|
430
405
|
|
|
431
|
-
const determineSnapshotDir = mem(({file, fixedLocation, projectDir}) => {
|
|
406
|
+
export const determineSnapshotDir = mem(({file, fixedLocation, projectDir}) => {
|
|
432
407
|
const testDir = path.dirname(resolveSourceFile(file));
|
|
433
408
|
if (fixedLocation) {
|
|
434
409
|
const relativeTestLocation = path.relative(projectDir, testDir);
|
|
@@ -447,8 +422,6 @@ const determineSnapshotDir = mem(({file, fixedLocation, projectDir}) => {
|
|
|
447
422
|
return testDir;
|
|
448
423
|
}, {cacheKey: ([{file}]) => file});
|
|
449
424
|
|
|
450
|
-
exports.determineSnapshotDir = determineSnapshotDir;
|
|
451
|
-
|
|
452
425
|
function determineSnapshotPaths({file, fixedLocation, projectDir}) {
|
|
453
426
|
const dir = determineSnapshotDir({file, fixedLocation, projectDir});
|
|
454
427
|
const relFile = path.relative(projectDir, resolveSourceFile(file));
|
|
@@ -460,7 +433,9 @@ function determineSnapshotPaths({file, fixedLocation, projectDir}) {
|
|
|
460
433
|
dir,
|
|
461
434
|
relFile,
|
|
462
435
|
snapFile,
|
|
463
|
-
reportFile
|
|
436
|
+
reportFile,
|
|
437
|
+
snapPath: path.join(dir, snapFile),
|
|
438
|
+
reportPath: path.join(dir, reportFile),
|
|
464
439
|
};
|
|
465
440
|
}
|
|
466
441
|
|
|
@@ -477,45 +452,52 @@ function cleanFile(file) {
|
|
|
477
452
|
}
|
|
478
453
|
}
|
|
479
454
|
|
|
480
|
-
|
|
481
|
-
//
|
|
482
|
-
|
|
483
|
-
|
|
455
|
+
export function load({file, fixedLocation, projectDir, recordNewSnapshots, updating}) {
|
|
456
|
+
// Keep runner unit tests that use `new Runner()` happy
|
|
457
|
+
if (file === undefined || projectDir === undefined) {
|
|
458
|
+
return new Manager({
|
|
459
|
+
recordNewSnapshots,
|
|
460
|
+
updating,
|
|
461
|
+
oldBlocksByTitle: new Map(),
|
|
462
|
+
newBlocksByTitle: new Map(),
|
|
463
|
+
});
|
|
464
|
+
}
|
|
484
465
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
...cleanFile(path.join(dir, reportFile))
|
|
488
|
-
];
|
|
489
|
-
}
|
|
466
|
+
const paths = determineSnapshotPaths({file, fixedLocation, projectDir});
|
|
467
|
+
const buffer = tryRead(paths.snapPath);
|
|
490
468
|
|
|
491
|
-
|
|
469
|
+
if (!buffer) {
|
|
470
|
+
return new Manager({
|
|
471
|
+
recordNewSnapshots,
|
|
472
|
+
updating,
|
|
473
|
+
...paths,
|
|
474
|
+
oldBlocksByTitle: new Map(),
|
|
475
|
+
newBlocksByTitle: new Map(),
|
|
476
|
+
});
|
|
477
|
+
}
|
|
492
478
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
const snapPath = path.join(dir, snapFile);
|
|
479
|
+
let blocksByTitle;
|
|
480
|
+
let snapshotError;
|
|
496
481
|
|
|
497
|
-
|
|
498
|
-
|
|
482
|
+
try {
|
|
483
|
+
const data = decodeSnapshots(buffer, paths.snapPath);
|
|
484
|
+
blocksByTitle = new Map(data.blocks.map(({title, ...block}) => [title, block]));
|
|
485
|
+
} catch (error) {
|
|
486
|
+
blocksByTitle = new Map();
|
|
499
487
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
} else {
|
|
505
|
-
appendOnly = false;
|
|
488
|
+
if (!updating) { // Discard all decoding errors when updating snapshots
|
|
489
|
+
snapshotError = error instanceof SnapshotError
|
|
490
|
+
? error
|
|
491
|
+
: new InvalidSnapshotError(paths.snapPath);
|
|
506
492
|
}
|
|
507
493
|
}
|
|
508
494
|
|
|
509
495
|
return new Manager({
|
|
510
|
-
appendOnly,
|
|
511
|
-
dir,
|
|
512
496
|
recordNewSnapshots,
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
497
|
+
updating,
|
|
498
|
+
...paths,
|
|
499
|
+
oldBlocksByTitle: blocksByTitle,
|
|
500
|
+
newBlocksByTitle: updating ? new Map() : blocksByTitle,
|
|
501
|
+
error: snapshotError,
|
|
518
502
|
});
|
|
519
503
|
}
|
|
520
|
-
|
|
521
|
-
exports.load = load;
|