@rehearsal-labs/retest 0.2.0 → 0.3.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/README.md +6 -5
- package/dist/api/source-location.d.ts.map +1 -1
- package/dist/api/source-location.js +3 -1
- package/dist/browser/builds.d.ts +35 -6
- package/dist/browser/builds.d.ts.map +1 -1
- package/dist/browser/builds.js +115 -16
- package/dist/browser/cdp/transport.d.ts.map +1 -1
- package/dist/browser/cdp/transport.js +4 -4
- package/dist/browser/chromium-process.d.ts +23 -0
- package/dist/browser/chromium-process.d.ts.map +1 -1
- package/dist/browser/chromium-process.js +78 -13
- package/dist/browser/electron.d.ts +1 -1
- package/dist/browser/electron.d.ts.map +1 -1
- package/dist/browser/electron.js +6 -3
- package/dist/browser/executables.d.ts +16 -1
- package/dist/browser/executables.d.ts.map +1 -1
- package/dist/browser/executables.js +50 -18
- package/dist/browser/firefox/accessible-names.d.ts +5 -4
- package/dist/browser/firefox/accessible-names.d.ts.map +1 -1
- package/dist/browser/firefox/accessible-names.js +11 -6
- package/dist/browser/firefox/bridge.js +3 -2
- package/dist/browser/firefox/executable.d.ts +14 -14
- package/dist/browser/firefox/executable.d.ts.map +1 -1
- package/dist/browser/firefox/executable.js +29 -21
- package/dist/browser/firefox/launch.js +1 -1
- package/dist/browser/firefox/navigation.d.ts +5 -2
- package/dist/browser/firefox/navigation.d.ts.map +1 -1
- package/dist/browser/firefox/navigation.js +48 -14
- package/dist/browser/firefox/orphans.d.ts.map +1 -1
- package/dist/browser/firefox/orphans.js +34 -2
- package/dist/browser/firefox/page.d.ts.map +1 -1
- package/dist/browser/firefox/page.js +113 -19
- package/dist/browser/firefox/process.d.ts +20 -1
- package/dist/browser/firefox/process.d.ts.map +1 -1
- package/dist/browser/firefox/process.js +124 -22
- package/dist/browser/firefox/profile.d.ts +5 -0
- package/dist/browser/firefox/profile.d.ts.map +1 -1
- package/dist/browser/firefox/profile.js +17 -1
- package/dist/browser/firefox/storage.d.ts.map +1 -1
- package/dist/browser/firefox/storage.js +4 -2
- package/dist/browser/firefox/subscriptions.d.ts +26 -0
- package/dist/browser/firefox/subscriptions.d.ts.map +1 -0
- package/dist/browser/firefox/subscriptions.js +10 -0
- package/dist/browser/launch.d.ts +6 -0
- package/dist/browser/launch.d.ts.map +1 -1
- package/dist/browser/launch.js +14 -1
- package/dist/cli/commands/doctor.js +3 -3
- package/dist/cli/commands/inspect.js +3 -2
- package/dist/cli/commands/install.d.ts.map +1 -1
- package/dist/cli/commands/install.js +26 -13
- package/dist/cli/commands/licences.d.ts +22 -0
- package/dist/cli/commands/licences.d.ts.map +1 -1
- package/dist/cli/commands/licences.js +62 -20
- package/dist/cli/commands/run.js +2 -2
- package/dist/cli/doctor/checks.js +4 -1
- package/dist/cli/inspect/diagnostics.js +3 -3
- package/dist/cli/inspect/test-timeline.js +10 -10
- package/dist/cli/install/doctor-rows.d.ts.map +1 -1
- package/dist/cli/install/doctor-rows.js +5 -4
- package/dist/cli/install/file-changes.d.ts +44 -0
- package/dist/cli/install/file-changes.d.ts.map +1 -0
- package/dist/cli/install/file-changes.js +81 -0
- package/dist/cli/install/install-archive.d.ts +3 -1
- package/dist/cli/install/install-archive.d.ts.map +1 -1
- package/dist/cli/install/install-archive.js +80 -33
- package/dist/cli/install/lock.d.ts.map +1 -1
- package/dist/cli/install/lock.js +108 -21
- package/dist/cli/install/media-doctor.d.ts.map +1 -1
- package/dist/cli/install/media-doctor.js +2 -1
- package/dist/cli/install/media-install.d.ts.map +1 -1
- package/dist/cli/install/media-install.js +38 -19
- package/dist/cli/install/media-pins.d.ts +7 -1
- package/dist/cli/install/media-pins.d.ts.map +1 -1
- package/dist/cli/install/media-pins.js +23 -12
- package/dist/cli/install/media-record.d.ts.map +1 -1
- package/dist/cli/install/media-record.js +10 -9
- package/dist/cli/install/media-report.d.ts +3 -1
- package/dist/cli/install/media-report.d.ts.map +1 -1
- package/dist/cli/install/media-report.js +7 -5
- package/dist/cli/install/media-tools.d.ts.map +1 -1
- package/dist/cli/install/media-tools.js +6 -4
- package/dist/cli/install/process-start.d.ts +3 -2
- package/dist/cli/install/process-start.d.ts.map +1 -1
- package/dist/cli/install/process-start.js +4 -0
- package/dist/cli/install/report.d.ts.map +1 -1
- package/dist/cli/install/report.js +11 -7
- package/dist/cli/install/supervisor-install.d.ts +33 -0
- package/dist/cli/install/supervisor-install.d.ts.map +1 -0
- package/dist/cli/install/supervisor-install.js +158 -0
- package/dist/cli/install/supervisor-locate.d.ts +47 -0
- package/dist/cli/install/supervisor-locate.d.ts.map +1 -0
- package/dist/cli/install/supervisor-locate.js +113 -0
- package/dist/cli/install/supervisor-pins.d.ts +21 -0
- package/dist/cli/install/supervisor-pins.d.ts.map +1 -0
- package/dist/cli/install/supervisor-pins.js +28 -0
- package/dist/cli/install/supervisor-record.d.ts +31 -0
- package/dist/cli/install/supervisor-record.d.ts.map +1 -0
- package/dist/cli/install/supervisor-record.js +104 -0
- package/dist/cli/install/supervisor-report.d.ts +11 -0
- package/dist/cli/install/supervisor-report.d.ts.map +1 -0
- package/dist/cli/install/supervisor-report.js +33 -0
- package/dist/cli/install/supervisor-tools.d.ts +43 -0
- package/dist/cli/install/supervisor-tools.d.ts.map +1 -0
- package/dist/cli/install/supervisor-tools.js +114 -0
- package/dist/cli/install/unpack.d.ts +23 -3
- package/dist/cli/install/unpack.d.ts.map +1 -1
- package/dist/cli/install/unpack.js +124 -10
- package/dist/cli/install/zip-member.d.ts +15 -0
- package/dist/cli/install/zip-member.d.ts.map +1 -0
- package/dist/cli/install/zip-member.js +77 -0
- package/dist/cli/interrupt.d.ts +1 -1
- package/dist/cli/interrupt.d.ts.map +1 -1
- package/dist/cli/interrupt.js +15 -10
- package/dist/cli/terminal.d.ts +9 -1
- package/dist/cli/terminal.d.ts.map +1 -1
- package/dist/cli/terminal.js +14 -0
- package/dist/config/define.d.ts +2 -2
- package/dist/config/define.js +2 -2
- package/dist/diagnostics/firefox-collector.d.ts +11 -10
- package/dist/diagnostics/firefox-collector.d.ts.map +1 -1
- package/dist/diagnostics/firefox-collector.js +16 -11
- package/dist/media/client.d.ts +2 -1
- package/dist/media/client.d.ts.map +1 -1
- package/dist/media/client.js +16 -5
- package/dist/media/host.d.ts +32 -0
- package/dist/media/host.d.ts.map +1 -0
- package/dist/media/host.js +50 -0
- package/dist/media/locate.d.ts.map +1 -1
- package/dist/media/locate.js +4 -3
- package/dist/native/processes.d.ts.map +1 -1
- package/dist/native/processes.js +7 -3
- package/dist/protocol/files.d.ts +1 -1
- package/dist/protocol/files.d.ts.map +1 -1
- package/dist/protocol/files.js +10 -5
- package/dist/reporters/commands.d.ts +12 -3
- package/dist/reporters/commands.d.ts.map +1 -1
- package/dist/reporters/commands.js +29 -11
- package/dist/reporters/human-card.js +1 -1
- package/dist/reporters/human.d.ts +3 -1
- package/dist/reporters/human.d.ts.map +1 -1
- package/dist/reporters/human.js +8 -8
- package/dist/reporters/style.d.ts +21 -2
- package/dist/reporters/style.d.ts.map +1 -1
- package/dist/reporters/style.js +11 -2
- package/dist/runner/app-server.d.ts +27 -7
- package/dist/runner/app-server.d.ts.map +1 -1
- package/dist/runner/app-server.js +74 -24
- package/dist/runner/child.js +6 -2
- package/dist/runner/media-leftovers.d.ts +7 -3
- package/dist/runner/media-leftovers.d.ts.map +1 -1
- package/dist/runner/media-leftovers.js +27 -6
- package/dist/runner/resources.d.ts +8 -0
- package/dist/runner/resources.d.ts.map +1 -1
- package/dist/runner/resources.js +12 -1
- package/dist/runner/run-session.d.ts.map +1 -1
- package/dist/runner/run-session.js +9 -4
- package/dist/runner/run.d.ts.map +1 -1
- package/dist/runner/run.js +2 -1
- package/dist/runner/target-drivers.d.ts +5 -2
- package/dist/runner/target-drivers.d.ts.map +1 -1
- package/dist/runner/target-drivers.js +10 -1
- package/dist/runner/test-file-process.d.ts +8 -0
- package/dist/runner/test-file-process.d.ts.map +1 -1
- package/dist/runner/test-file-process.js +26 -3
- package/dist/runner/test-pages.d.ts.map +1 -1
- package/dist/runner/test-pages.js +5 -2
- package/dist/shared/environment.d.ts +17 -0
- package/dist/shared/environment.d.ts.map +1 -0
- package/dist/shared/environment.js +27 -0
- package/dist/shared/metadata-process-worker.js +15 -7
- package/dist/shared/metadata-process.d.ts +5 -0
- package/dist/shared/metadata-process.d.ts.map +1 -1
- package/dist/shared/metadata-process.js +36 -6
- package/dist/shared/posix-path.d.ts +12 -0
- package/dist/shared/posix-path.d.ts.map +1 -1
- package/dist/shared/posix-path.js +16 -0
- package/dist/shared/private-file.d.ts +38 -0
- package/dist/shared/private-file.d.ts.map +1 -0
- package/dist/shared/private-file.js +132 -0
- package/dist/shared/process-ownership.d.ts +52 -2
- package/dist/shared/process-ownership.d.ts.map +1 -1
- package/dist/shared/process-ownership.js +104 -11
- package/dist/shared/process-supervisor.d.ts +309 -0
- package/dist/shared/process-supervisor.d.ts.map +1 -0
- package/dist/shared/process-supervisor.js +737 -0
- package/dist/shared/process-system.d.ts +113 -0
- package/dist/shared/process-system.d.ts.map +1 -0
- package/dist/shared/process-system.js +254 -0
- package/dist/shared/regular-file.d.ts +2 -2
- package/dist/shared/regular-file.d.ts.map +1 -1
- package/dist/shared/regular-file.js +19 -2
- package/dist/store/artifacts.d.ts +5 -4
- package/dist/store/artifacts.d.ts.map +1 -1
- package/dist/store/artifacts.js +18 -5
- package/dist/store/run-store.d.ts +5 -2
- package/dist/store/run-store.d.ts.map +1 -1
- package/dist/store/run-store.js +48 -4
- package/media/Cargo.lock +16 -0
- package/media/Cargo.toml +16 -1
- package/media/src/encoder.rs +56 -12
- package/media/src/frame.rs +7 -1
- package/media/src/main.rs +20 -2
- package/media/src/place.rs +230 -11
- package/media/src/process_ownership_windows.rs +1058 -0
- package/media/src/recording.rs +15 -7
- package/media/src/replies.rs +55 -0
- package/media/src/server.rs +72 -2
- package/media/src/store.rs +71 -2
- package/package.json +9 -4
- package/src/cli/install/media-notices.txt +465 -1
- package/supervisor/Cargo.lock +25 -0
- package/supervisor/Cargo.toml +22 -0
- package/supervisor/NOTICES +464 -0
- package/supervisor/build.rs +44 -0
- package/supervisor/src/command.rs +612 -0
- package/supervisor/src/jobs.rs +1650 -0
- package/supervisor/src/main.rs +71 -0
- package/supervisor/src/reply.rs +367 -0
|
@@ -1,27 +1,137 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createReadStream, createWriteStream } from 'node:fs';
|
|
2
|
+
import { lstat, mkdtemp, open, realpath, rmdir } from 'node:fs/promises';
|
|
2
3
|
import { tmpdir } from 'node:os';
|
|
3
|
-
import { join } from 'node:path';
|
|
4
|
+
import { join, win32 } from 'node:path';
|
|
5
|
+
import { pipeline } from 'node:stream/promises';
|
|
4
6
|
import { setTimeout as sleep } from 'node:timers/promises';
|
|
7
|
+
import { crc32 } from 'node:zlib';
|
|
8
|
+
import { environmentVariable } from "../../browser/builds.js";
|
|
5
9
|
import { describeCommand, runCommand } from "../../native/processes.js";
|
|
6
10
|
import { Deadline } from "../../protocol/deadline.js";
|
|
11
|
+
import { errorMessage } from "../../protocol/failures.js";
|
|
7
12
|
import { errorCode, isMissingFile } from "../../shared/error-code.js";
|
|
8
|
-
|
|
13
|
+
import { removePath } from "./file-changes.js";
|
|
14
|
+
// The tar a Git shell puts first on PATH is GNU tar, which reads neither zip nor 7-Zip, so Windows' own is named by the
|
|
15
|
+
// Windows folder.
|
|
16
|
+
const windowsTar = win32.join(environmentVariable(process.env, 'SystemRoot', 'win32') ?? 'C:\\Windows', 'System32', 'tar.exe');
|
|
17
|
+
export const systemUnpackTools = { ditto: '/usr/bin/ditto', unzip: '/usr/bin/unzip', hdiutil: '/usr/bin/hdiutil', tar: windowsTar };
|
|
9
18
|
/**
|
|
10
19
|
* Unpacks a verified archive into an empty folder, and returns why it could not, or undefined once it has. A zip is
|
|
11
20
|
* unpacked whole, keeping its links and modes; from a disk image, only the app bundle the pin names is copied, and the
|
|
12
|
-
* image is detached again whatever happened
|
|
21
|
+
* image is detached again whatever happened; from a Windows installer, only the folder the pin names is unpacked, out
|
|
22
|
+
* of the 7-Zip archive inside it, and its self-extracting stub never runs.
|
|
13
23
|
*
|
|
14
|
-
* @example await unpackArchive({ archive, format: { format: 'zip' }, into, platform: 'darwin', tools: systemUnpackTools, signal, timeoutMs: 600_000 })
|
|
24
|
+
* @example await unpackArchive({ archive, format: { format: 'zip' }, into, platform: 'darwin', tools: systemUnpackTools, signal, timeoutMs: 600_000, report: console.log })
|
|
15
25
|
*/
|
|
16
26
|
export async function unpackArchive(request) {
|
|
17
27
|
if (request.format.format === 'dmg')
|
|
18
28
|
return request.platform === 'darwin' ? copyFromDiskImage(request, request.format.app) : 'A disk image is unpacked only on macOS.';
|
|
29
|
+
if (request.format.format === 'exe')
|
|
30
|
+
return request.platform === 'win32' ? unpackInstaller(request, request.format.folder) : 'A Windows installer is unpacked only on Windows.';
|
|
31
|
+
if (request.platform === 'win32')
|
|
32
|
+
return unpackWithTar(request, request.archive, []);
|
|
19
33
|
if (request.platform === 'darwin')
|
|
20
34
|
return run(request.tools.ditto, ['-x', '-k', request.archive, request.into], request, 'ditto');
|
|
21
35
|
if (!(await exists(request.tools.unzip)))
|
|
22
36
|
return `Unpacking a zip on Linux needs unzip at ${request.tools.unzip}. Install it, for example with apt-get install unzip, and run the install again.`;
|
|
23
37
|
return run(request.tools.unzip, ['-q', request.archive, '-d', request.into], request, 'unzip');
|
|
24
38
|
}
|
|
39
|
+
// bsdtar refuses a member whose path is absolute or climbs out with `..`, and makes no link a Windows build would need.
|
|
40
|
+
async function unpackWithTar(request, archive, members) {
|
|
41
|
+
if (!(await exists(request.tools.tar)))
|
|
42
|
+
return `Unpacking on Windows needs its own tar.exe at ${request.tools.tar}, which Windows includes from Windows 10 version 1803.`;
|
|
43
|
+
return run(request.tools.tar, ['-x', '-f', archive, '-C', request.into, ...members], request, 'tar');
|
|
44
|
+
}
|
|
45
|
+
// bsdtar names the libraries it was built with in its version line, and reads LZMA only with liblzma among them; asking
|
|
46
|
+
// first names a tar.exe that lacks it, where the unpack itself would only end with an exit code.
|
|
47
|
+
async function lzmaProblem(request) {
|
|
48
|
+
if (!(await exists(request.tools.tar)))
|
|
49
|
+
return undefined;
|
|
50
|
+
const version = await runCommand(request.tools.tar, ['--version'], { timeoutMs: 30_000, signal: request.signal });
|
|
51
|
+
if (version.code !== 0)
|
|
52
|
+
return `Retest could not read which archives ${request.tools.tar} unpacks: ${clause(describeCommand('tar --version', version))}.`;
|
|
53
|
+
if (/\bliblzma\//.test(version.stdout))
|
|
54
|
+
return undefined;
|
|
55
|
+
const named = version.stdout.trim().split('\n')[0] ?? 'tar';
|
|
56
|
+
return `${request.tools.tar} (${named}) cannot unpack this installer, since it was built without LZMA. Update Windows, or give the target an executablePath.`;
|
|
57
|
+
}
|
|
58
|
+
// The installer's stub is a program, and running it would install Firefox the way a person does, so the archive is
|
|
59
|
+
// copied out of it byte for byte and only the named folder is unpacked from the copy. bsdtar finds the archive inside
|
|
60
|
+
// a stub only at some offsets, and the copy has it at the start.
|
|
61
|
+
async function unpackInstaller(request, folder) {
|
|
62
|
+
const start = await sevenZipStart(request.archive);
|
|
63
|
+
if (!start.ok)
|
|
64
|
+
return start.problem;
|
|
65
|
+
const lzma = await lzmaProblem(request);
|
|
66
|
+
if (lzma !== undefined)
|
|
67
|
+
return lzma;
|
|
68
|
+
const workspace = await mkdtemp(join(tmpdir(), 'retest-install-installer-'));
|
|
69
|
+
try {
|
|
70
|
+
const inner = join(workspace, 'installer.7z');
|
|
71
|
+
try {
|
|
72
|
+
await pipeline(createReadStream(request.archive, { start: start.offset }), createWriteStream(inner, { flags: 'wx' }), { signal: request.signal });
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
return `The archive inside the installer could not be copied out: ${errorMessage(error)}.`;
|
|
76
|
+
}
|
|
77
|
+
const problem = await unpackWithTar(request, inner, [folder]);
|
|
78
|
+
if (problem !== undefined)
|
|
79
|
+
return problem;
|
|
80
|
+
const unpacked = await lstat(join(request.into, folder)).then((stats) => stats.isDirectory(), () => false);
|
|
81
|
+
return unpacked ? undefined : `The installer holds no ${folder} folder.`;
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
// The copy is of no further use whether the unpack worked or not; one left behind is said, and changes neither.
|
|
85
|
+
const removed = await removePath(workspace, { platform: request.platform, signal: request.signal });
|
|
86
|
+
if (!removed.ok)
|
|
87
|
+
request.report(`Retest could not remove ${workspace}: ${removed.reason}. Remove it by hand.`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const sevenZipSignature = Buffer.from([0x37, 0x7a, 0xbc, 0xaf, 0x27, 0x1c]);
|
|
91
|
+
/** A self-extracting stub is a small program; the archive begins within this many bytes of the file's start. */
|
|
92
|
+
const stubSearchBytes = 4 * 1024 * 1024;
|
|
93
|
+
/** No archive Retest pins has an index near this size. */
|
|
94
|
+
const maximumIndexBytes = 64 * 1024 * 1024;
|
|
95
|
+
/**
|
|
96
|
+
* Where the 7-Zip archive inside a file begins: the first place its signature stands, within the stub's reach, with a
|
|
97
|
+
* start header whose checksum holds and whose index lies inside the file with the checksum the header gives it. A
|
|
98
|
+
* signature that is only bytes of the stub fails those checks and is passed over.
|
|
99
|
+
*
|
|
100
|
+
* @example await sevenZipStart('/…/Firefox Setup 158.0.1.exe') // { ok: true, offset: 131665 }
|
|
101
|
+
*/
|
|
102
|
+
export async function sevenZipStart(path) {
|
|
103
|
+
const handle = await open(path, 'r');
|
|
104
|
+
try {
|
|
105
|
+
const { size } = await handle.stat();
|
|
106
|
+
const head = Buffer.alloc(Math.min(size, stubSearchBytes));
|
|
107
|
+
await handle.read(head, 0, head.length, 0);
|
|
108
|
+
for (let at = head.indexOf(sevenZipSignature); at >= 0; at = head.indexOf(sevenZipSignature, at + 1)) {
|
|
109
|
+
if (await isArchiveStart(handle, size, at))
|
|
110
|
+
return { ok: true, offset: at };
|
|
111
|
+
}
|
|
112
|
+
return { ok: false, problem: `The installer holds no 7-Zip archive in its first ${stubSearchBytes / 1024 / 1024} MiB, so Retest cannot unpack it without running it.` };
|
|
113
|
+
}
|
|
114
|
+
finally {
|
|
115
|
+
await handle.close();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// The start header is the signature, two version bytes, the CRC-32 of the next 20 bytes, then the index's offset from
|
|
119
|
+
// the header's end, its size and its CRC-32, all little-endian.
|
|
120
|
+
async function isArchiveStart(handle, size, at) {
|
|
121
|
+
const header = Buffer.alloc(32);
|
|
122
|
+
if ((await handle.read(header, 0, 32, at)).bytesRead !== 32 || header[6] !== 0)
|
|
123
|
+
return false;
|
|
124
|
+
if (crc32(header.subarray(12, 32)) !== header.readUInt32LE(8))
|
|
125
|
+
return false;
|
|
126
|
+
const indexAt = BigInt(at + 32) + header.readBigUInt64LE(12);
|
|
127
|
+
const indexSize = header.readBigUInt64LE(20);
|
|
128
|
+
if (indexSize === 0n || indexSize > BigInt(maximumIndexBytes) || indexAt + indexSize > BigInt(size))
|
|
129
|
+
return false;
|
|
130
|
+
const index = Buffer.alloc(Number(indexSize));
|
|
131
|
+
if ((await handle.read(index, 0, index.length, Number(indexAt))).bytesRead !== index.length)
|
|
132
|
+
return false;
|
|
133
|
+
return crc32(index) === header.readUInt32LE(28);
|
|
134
|
+
}
|
|
25
135
|
async function copyFromDiskImage(request, app) {
|
|
26
136
|
const mountPoint = await mkdtemp(join(tmpdir(), 'retest-install-image-'));
|
|
27
137
|
// -nobrowse keeps the image out of Finder; with no input, an image that asks for a licence agreement fails rather
|
|
@@ -32,7 +142,7 @@ async function copyFromDiskImage(request, app) {
|
|
|
32
142
|
// can still finish after hdiutil is gone. Whatever the helper attached from this archive is detached again.
|
|
33
143
|
const left = await detachLeftAttach(request, mountPoint, attached.timedOut || attached.stopped ? leftAttachWaitMs : 0);
|
|
34
144
|
const cleanup = left ?? (await removeMountPoint(mountPoint));
|
|
35
|
-
return `The disk image could not be attached: ${describeCommand('hdiutil attach', attached)}.${cleanup === undefined ? '' : ` ${cleanup}`}`;
|
|
145
|
+
return `The disk image could not be attached: ${clause(describeCommand('hdiutil attach', attached))}.${cleanup === undefined ? '' : ` ${cleanup}`}`;
|
|
36
146
|
}
|
|
37
147
|
let problem;
|
|
38
148
|
try {
|
|
@@ -60,7 +170,7 @@ async function detach(request, target) {
|
|
|
60
170
|
const forced = await runCommand(request.tools.hdiutil, ['detach', '-force', target], options);
|
|
61
171
|
if (forced.code === 0)
|
|
62
172
|
return undefined;
|
|
63
|
-
return `The disk image is still attached at ${target}: ${describeCommand('hdiutil detach -force', forced)}. Detach it with hdiutil detach ${target}.`;
|
|
173
|
+
return `The disk image is still attached at ${target}: ${clause(describeCommand('hdiutil detach -force', forced))}. Detach it with hdiutil detach ${target}.`;
|
|
64
174
|
}
|
|
65
175
|
/** How long an empty mount point is tried again after a detach, while the system lets go of the volume. */
|
|
66
176
|
const mountPointReleaseMs = 2000;
|
|
@@ -96,7 +206,7 @@ async function detachLeftAttach(request, mountPoint, waitMs) {
|
|
|
96
206
|
for (;;) {
|
|
97
207
|
const info = await runCommand(request.tools.hdiutil, ['info'], { timeoutMs: 60_000 });
|
|
98
208
|
if (info.code !== 0)
|
|
99
|
-
return `Retest could not read whether the image is still attached: ${describeCommand('hdiutil info', info)}. If hdiutil info lists ${request.archive}, detach it with hdiutil detach.`;
|
|
209
|
+
return `Retest could not read whether the image is still attached: ${clause(describeCommand('hdiutil info', info))}. If hdiutil info lists ${request.archive}, detach it with hdiutil detach.`;
|
|
100
210
|
const devices = attachedDevices(info.stdout, names);
|
|
101
211
|
if (devices.length > 0)
|
|
102
212
|
return detachAll(request, devices);
|
|
@@ -118,7 +228,7 @@ async function detachAll(request, devices) {
|
|
|
118
228
|
* The whole-disk devices `hdiutil info` lists for images mounted on one of `mountPoints`, or attached from one of
|
|
119
229
|
* `images` with no mount point at all. An image mounted elsewhere is not counted.
|
|
120
230
|
*
|
|
121
|
-
* @example attachedDevices(text, { images: ['/…/Firefox
|
|
231
|
+
* @example attachedDevices(text, { images: ['/…/Firefox 158.0.1.dmg'], mountPoints: ['/private/var/…/retest-install-image-a1b2'] }) // ['/dev/disk6']
|
|
122
232
|
*/
|
|
123
233
|
export function attachedDevices(info, names) {
|
|
124
234
|
const devices = [];
|
|
@@ -142,7 +252,11 @@ async function bothForms(path) {
|
|
|
142
252
|
}
|
|
143
253
|
async function run(tool, args, request, name) {
|
|
144
254
|
const result = await runCommand(tool, args, { timeoutMs: request.timeoutMs, signal: request.signal });
|
|
145
|
-
return result.code === 0 ? undefined : `Unpacking the archive failed: ${describeCommand(name, result)}.`;
|
|
255
|
+
return result.code === 0 ? undefined : `Unpacking the archive failed: ${clause(describeCommand(name, result))}.`;
|
|
256
|
+
}
|
|
257
|
+
// A command's account as a clause the sentence around it ends, without the full stop its last line may bring.
|
|
258
|
+
function clause(text) {
|
|
259
|
+
return text.replace(/\.+$/, '');
|
|
146
260
|
}
|
|
147
261
|
async function exists(path) {
|
|
148
262
|
return lstat(path).then(() => true, (error) => !isMissingFile(error));
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** One member's bytes, or why they were not read. */
|
|
2
|
+
export type ZipMemberReading = {
|
|
3
|
+
readonly ok: true;
|
|
4
|
+
readonly bytes: Buffer;
|
|
5
|
+
} | {
|
|
6
|
+
readonly ok: false;
|
|
7
|
+
readonly problem: string;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* The bytes of the member `name` of the zip `archive`, stored or deflated, when they are at most `maximumBytes`.
|
|
11
|
+
*
|
|
12
|
+
* @example readZipMember(omni, 'chrome/toolkit/content/global/license.html', 16 * 1024 * 1024) // { ok: true, bytes }
|
|
13
|
+
*/
|
|
14
|
+
export declare function readZipMember(archive: Buffer, name: string, maximumBytes: number): ZipMemberReading;
|
|
15
|
+
//# sourceMappingURL=zip-member.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zip-member.d.ts","sourceRoot":"","sources":["../../../src/cli/install/zip-member.ts"],"names":[],"mappings":"AAOA,qDAAqD;AACrD,MAAM,MAAM,gBAAgB,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAA;AAQ/H;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,gBAAgB,CAkBnG"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { crc32, inflateRawSync } from 'node:zlib';
|
|
2
|
+
const endSignature = 0x06054b50;
|
|
3
|
+
const entrySignature = 0x02014b50;
|
|
4
|
+
const localSignature = 0x04034b50;
|
|
5
|
+
// The end record is 22 bytes and may be followed by a comment of up to 65535.
|
|
6
|
+
const endSearchBytes = 22 + 0xffff;
|
|
7
|
+
/**
|
|
8
|
+
* The bytes of the member `name` of the zip `archive`, stored or deflated, when they are at most `maximumBytes`.
|
|
9
|
+
*
|
|
10
|
+
* @example readZipMember(omni, 'chrome/toolkit/content/global/license.html', 16 * 1024 * 1024) // { ok: true, bytes }
|
|
11
|
+
*/
|
|
12
|
+
export function readZipMember(archive, name, maximumBytes) {
|
|
13
|
+
const end = findEnd(archive);
|
|
14
|
+
if (end === undefined)
|
|
15
|
+
return { ok: false, problem: 'it is not a zip archive' };
|
|
16
|
+
const entries = archive.readUInt16LE(end + 10);
|
|
17
|
+
const directorySize = archive.readUInt32LE(end + 12);
|
|
18
|
+
const directoryAt = archive.readUInt32LE(end + 16);
|
|
19
|
+
if (directoryAt + directorySize > end)
|
|
20
|
+
return { ok: false, problem: 'its central directory lies outside it' };
|
|
21
|
+
const wanted = Buffer.from(name, 'utf8');
|
|
22
|
+
let at = directoryAt;
|
|
23
|
+
for (let index = 0; index < entries; index += 1) {
|
|
24
|
+
if (at + 46 > end || archive.readUInt32LE(at) !== entrySignature)
|
|
25
|
+
return { ok: false, problem: 'its central directory is damaged' };
|
|
26
|
+
const nameLength = archive.readUInt16LE(at + 28);
|
|
27
|
+
const next = at + 46 + nameLength + archive.readUInt16LE(at + 30) + archive.readUInt16LE(at + 32);
|
|
28
|
+
if (next > end)
|
|
29
|
+
return { ok: false, problem: 'its central directory is damaged' };
|
|
30
|
+
if (archive.subarray(at + 46, at + 46 + nameLength).equals(wanted))
|
|
31
|
+
return readEntry(archive, at, maximumBytes);
|
|
32
|
+
at = next;
|
|
33
|
+
}
|
|
34
|
+
return { ok: false, problem: `it holds no ${name}` };
|
|
35
|
+
}
|
|
36
|
+
function readEntry(archive, entry, maximumBytes) {
|
|
37
|
+
const flags = archive.readUInt16LE(entry + 8);
|
|
38
|
+
const method = archive.readUInt16LE(entry + 10);
|
|
39
|
+
const crc = archive.readUInt32LE(entry + 16);
|
|
40
|
+
const compressedSize = archive.readUInt32LE(entry + 20);
|
|
41
|
+
const size = archive.readUInt32LE(entry + 24);
|
|
42
|
+
const local = archive.readUInt32LE(entry + 42);
|
|
43
|
+
if ((flags & 1) !== 0)
|
|
44
|
+
return { ok: false, problem: 'the member is encrypted' };
|
|
45
|
+
if (size > maximumBytes)
|
|
46
|
+
return { ok: false, problem: 'the member exceeds its byte limit' };
|
|
47
|
+
if (local + 30 > archive.length || archive.readUInt32LE(local) !== localSignature)
|
|
48
|
+
return { ok: false, problem: "the member's header is damaged" };
|
|
49
|
+
const start = local + 30 + archive.readUInt16LE(local + 26) + archive.readUInt16LE(local + 28);
|
|
50
|
+
if (start + compressedSize > archive.length)
|
|
51
|
+
return { ok: false, problem: 'the member lies outside the archive' };
|
|
52
|
+
const stored = archive.subarray(start, start + compressedSize);
|
|
53
|
+
let bytes;
|
|
54
|
+
if (method === 0)
|
|
55
|
+
bytes = stored;
|
|
56
|
+
else if (method === 8) {
|
|
57
|
+
try {
|
|
58
|
+
bytes = inflateRawSync(stored, { maxOutputLength: Math.max(1, size) });
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return { ok: false, problem: 'the member does not inflate' };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else
|
|
65
|
+
return { ok: false, problem: `the member is packed with method ${method}, which Retest does not read` };
|
|
66
|
+
if (bytes.length !== size || crc32(bytes) !== crc)
|
|
67
|
+
return { ok: false, problem: "the member's checksum does not hold" };
|
|
68
|
+
return { ok: true, bytes };
|
|
69
|
+
}
|
|
70
|
+
// The last end record in the archive whose comment runs exactly to the archive's end.
|
|
71
|
+
function findEnd(archive) {
|
|
72
|
+
for (let at = archive.length - 22; at >= Math.max(0, archive.length - endSearchBytes); at -= 1) {
|
|
73
|
+
if (archive.readUInt32LE(at) === endSignature && at + 22 + archive.readUInt16LE(at + 20) === archive.length)
|
|
74
|
+
return at;
|
|
75
|
+
}
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
package/dist/cli/interrupt.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { Writer } from '../reporters/style.ts';
|
|
|
2
2
|
import type { StopSignal } from '../runner/contract.ts';
|
|
3
3
|
/** Where interrupts come from, such as `process`. */
|
|
4
4
|
export type InterruptSource = {
|
|
5
|
-
on(event: StopSignal, listener: () => void): unknown;
|
|
5
|
+
on(event: StopSignal | 'SIGBREAK', listener: () => void): unknown;
|
|
6
6
|
};
|
|
7
7
|
export type InterruptOptions = {
|
|
8
8
|
source: InterruptSource;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interrupt.d.ts","sourceRoot":"","sources":["../../src/cli/interrupt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAA;AACnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAA;
|
|
1
|
+
{"version":3,"file":"interrupt.d.ts","sourceRoot":"","sources":["../../src/cli/interrupt.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAA;AACnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAA;AAIvD,qDAAqD;AACrD,MAAM,MAAM,eAAe,GAAG;IAAE,EAAE,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,OAAO,CAAA;CAAE,CAAA;AAEnG,MAAM,MAAM,gBAAgB,GAAG;IAAE,MAAM,EAAE,eAAe,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,CAAC,IAAI,EAAE,GAAG,GAAG,GAAG,KAAK,IAAI,CAAA;CAAE,CAAA;AAS3G;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,gBAAgB,GAAG,WAAW,CAevE"}
|
package/dist/cli/interrupt.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { stoppedExitCode } from "../runner/outcome.js";
|
|
2
|
+
import { launchesHeldByJob } from "../shared/process-system.js";
|
|
2
3
|
const stopSignals = ['SIGINT', 'SIGTERM'];
|
|
3
4
|
const stopping = {
|
|
4
5
|
SIGINT: '\nStopping. Press Ctrl+C again to quit at once.\n',
|
|
@@ -12,15 +13,19 @@ const stopping = {
|
|
|
12
13
|
*/
|
|
13
14
|
export function abortOnInterrupt(options) {
|
|
14
15
|
const controller = new AbortController();
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
16
|
+
const stop = (signal) => {
|
|
17
|
+
if (controller.signal.aborted) {
|
|
18
|
+
options.exit(stoppedExitCode(signal));
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
options.stderr.write(stopping[signal]);
|
|
22
|
+
controller.abort(signal);
|
|
23
|
+
};
|
|
24
|
+
for (const signal of stopSignals)
|
|
25
|
+
options.source.on(signal, () => stop(signal));
|
|
26
|
+
// Windows sends a console program no SIGTERM. Ctrl+Break, which Node gives as SIGBREAK and which a console cannot be
|
|
27
|
+
// set to ignore as it can Ctrl+C, is how one is asked to stop there, so it stops a run as SIGTERM does.
|
|
28
|
+
if (launchesHeldByJob)
|
|
29
|
+
options.source.on('SIGBREAK', () => stop('SIGTERM'));
|
|
25
30
|
return controller.signal;
|
|
26
31
|
}
|
package/dist/cli/terminal.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ExitCode } from '../protocol/events.ts';
|
|
2
|
-
import type { Writer } from '../reporters/style.ts';
|
|
2
|
+
import type { Marks, Writer } from '../reporters/style.ts';
|
|
3
3
|
export type Environment = Readonly<Record<string, string | undefined>>;
|
|
4
4
|
/** An output stream and whether a person's terminal is on the other end. */
|
|
5
5
|
export type Terminal = Writer & {
|
|
@@ -21,6 +21,14 @@ export type ReportTerminal = {
|
|
|
21
21
|
* @example shouldUseColor(stdout, process.env)
|
|
22
22
|
*/
|
|
23
23
|
export declare function shouldUseColor(terminal: Terminal, env: Environment): boolean;
|
|
24
|
+
/**
|
|
25
|
+
* The marks a terminal draws: the glyphs, except in a Windows console that names no terminal of its own, whose fonts
|
|
26
|
+
* lack ✓, ✗, ⏎ or ▸. Windows Terminal (`WT_SESSION`), an editor's terminal (`TERM_PROGRAM`) and one that sets `TERM`
|
|
27
|
+
* draw them, and output that is not a terminal is UTF-8 for whatever reads it.
|
|
28
|
+
*
|
|
29
|
+
* @example terminalMarks(stdout, process.env).passed // '✓', or '√' in a Windows console
|
|
30
|
+
*/
|
|
31
|
+
export declare function terminalMarks(terminal: Terminal, env: Environment, platform?: NodeJS.Platform): Marks;
|
|
24
32
|
/**
|
|
25
33
|
* The terminal for the report. A closed pipe does not crash the process; once the stream has failed,
|
|
26
34
|
* writing throws, so the reporter fails and the run records that nobody could read its report.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"terminal.d.ts","sourceRoot":"","sources":["../../src/cli/terminal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AACrD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAA;
|
|
1
|
+
{"version":3,"file":"terminal.d.ts","sourceRoot":"","sources":["../../src/cli/terminal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAA;AACrD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAA;AAG1D,MAAM,MAAM,WAAW,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAA;AAEtE,4EAA4E;AAC5E,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAA;AAE3D,kGAAkG;AAClG,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG;IAClC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;IACxB,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,OAAO,CAAA;CAC9D,CAAA;AAED,gFAAgF;AAChF,MAAM,MAAM,cAAc,GAAG;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,OAAO,IAAI,KAAK,GAAG,SAAS,CAAA;CAAE,CAAA;AAEjF;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,WAAW,GAAG,OAAO,CAG5E;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,WAAW,EAAE,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAAG,KAAK,CAIvH;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,GAAG,cAAc,CAWnE;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,YAAY,GAAG,QAAQ,CAG9D;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,QAAQ,EAAE,YAAY,EAAE,OAAO,GAAG,QAAQ,CAEnF"}
|
package/dist/cli/terminal.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { consoleMarks, unicodeMarks } from "../reporters/style.js";
|
|
1
2
|
/**
|
|
2
3
|
* Colour only on a terminal, and never when `NO_COLOR` is set to anything but an empty string.
|
|
3
4
|
*
|
|
@@ -7,6 +8,19 @@ export function shouldUseColor(terminal, env) {
|
|
|
7
8
|
const noColor = env['NO_COLOR'];
|
|
8
9
|
return terminal.isTTY && (noColor === undefined || noColor === '');
|
|
9
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* The marks a terminal draws: the glyphs, except in a Windows console that names no terminal of its own, whose fonts
|
|
13
|
+
* lack ✓, ✗, ⏎ or ▸. Windows Terminal (`WT_SESSION`), an editor's terminal (`TERM_PROGRAM`) and one that sets `TERM`
|
|
14
|
+
* draw them, and output that is not a terminal is UTF-8 for whatever reads it.
|
|
15
|
+
*
|
|
16
|
+
* @example terminalMarks(stdout, process.env).passed // '✓', or '√' in a Windows console
|
|
17
|
+
*/
|
|
18
|
+
export function terminalMarks(terminal, env, platform = process.platform) {
|
|
19
|
+
if (platform !== 'win32' || !terminal.isTTY)
|
|
20
|
+
return unicodeMarks;
|
|
21
|
+
const named = [env['WT_SESSION'], env['TERM_PROGRAM'], env['TERM']].some((value) => value !== undefined && value !== '');
|
|
22
|
+
return named ? unicodeMarks : consoleMarks;
|
|
23
|
+
}
|
|
10
24
|
/**
|
|
11
25
|
* The terminal for the report. A closed pipe does not crash the process; once the stream has failed,
|
|
12
26
|
* writing throws, so the reporter fails and the run records that nobody could read its report.
|
package/dist/config/define.d.ts
CHANGED
|
@@ -31,7 +31,7 @@ export declare function chromium<const Options extends ChromiumOptions & AppSett
|
|
|
31
31
|
readonly browser: 'chromium';
|
|
32
32
|
};
|
|
33
33
|
/**
|
|
34
|
-
* Google Chrome, found where its channel installs on macOS and
|
|
34
|
+
* Google Chrome, found where its channel installs on macOS, Linux and Windows.
|
|
35
35
|
*
|
|
36
36
|
* @example chrome({ channel: 'beta' })
|
|
37
37
|
*/
|
|
@@ -39,7 +39,7 @@ export declare function chrome<const Options extends BrandedOptions & AppSetting
|
|
|
39
39
|
readonly browser: 'chrome';
|
|
40
40
|
};
|
|
41
41
|
/**
|
|
42
|
-
* Microsoft Edge, found where its channel installs on macOS and
|
|
42
|
+
* Microsoft Edge, found where its channel installs on macOS, Linux and Windows.
|
|
43
43
|
*
|
|
44
44
|
* @example edge({ channel: 'dev' })
|
|
45
45
|
*/
|
package/dist/config/define.js
CHANGED
|
@@ -26,7 +26,7 @@ export function chromium(options) {
|
|
|
26
26
|
return tagged(options, 'chromium');
|
|
27
27
|
}
|
|
28
28
|
/**
|
|
29
|
-
* Google Chrome, found where its channel installs on macOS and
|
|
29
|
+
* Google Chrome, found where its channel installs on macOS, Linux and Windows.
|
|
30
30
|
*
|
|
31
31
|
* @example chrome({ channel: 'beta' })
|
|
32
32
|
*/
|
|
@@ -34,7 +34,7 @@ export function chrome(options) {
|
|
|
34
34
|
return tagged(options, 'chrome');
|
|
35
35
|
}
|
|
36
36
|
/**
|
|
37
|
-
* Microsoft Edge, found where its channel installs on macOS and
|
|
37
|
+
* Microsoft Edge, found where its channel installs on macOS, Linux and Windows.
|
|
38
38
|
*
|
|
39
39
|
* @example edge({ channel: 'dev' })
|
|
40
40
|
*/
|
|
@@ -18,22 +18,23 @@ export type FirefoxCollectorOptions = {
|
|
|
18
18
|
maxTrackedRequests?: number;
|
|
19
19
|
};
|
|
20
20
|
/**
|
|
21
|
-
* Why a Firefox capture holds no console messages or runtime errors. Firefox
|
|
21
|
+
* Why a Firefox capture holds no console messages or runtime errors. Firefox sends both through one event,
|
|
22
22
|
* `log.entryAdded`. Strings, numbers, plain data objects and a DOM node left the probe's marker untouched, but logged
|
|
23
23
|
* objects with enumerable getters changed the page's title before the entry arrived, including nested getters.
|
|
24
24
|
* Firefox ignores extra serialization fields on subscriptions and session.new, and rejects serialization
|
|
25
25
|
* capabilities. Filtering delivered entries for primitive arguments is too late to prevent that mutation.
|
|
26
|
-
* A depth limit avoids the probed object's getters in script results, not in log events.
|
|
26
|
+
* A depth limit avoids the probed object's getters in script results, not in log events. Firefox 133 and 158 both
|
|
27
|
+
* behave so (`tests/integration/firefox-diagnostics.test.ts`).
|
|
27
28
|
*/
|
|
28
|
-
export declare const firefoxConsoleUnavailable = "Firefox
|
|
29
|
+
export declare const firefoxConsoleUnavailable = "Firefox's console source runs the page's own enumerable getters when it serializes logged objects, before Retest can filter an entry. The log subscription ignores serialization options and offers no primitive-only filter, so Retest does not subscribe and records no console message or runtime error on Firefox.";
|
|
29
30
|
/**
|
|
30
|
-
* What Firefox's WebDriver BiDi network events cover for a tab, from what Retest exercised on Firefox 133: the
|
|
31
|
-
* document and the frames inside it, of its own site and of others, each request named by the browsing context
|
|
32
|
-
* made it. A request a dedicated worker makes is named by the page that started the worker, so it cannot be told
|
|
33
|
-
* the page's own; workers' requests are not claimed, and the reason says they may appear as the page's.
|
|
34
|
-
* no resource type: a request is a document only when Firefox ties it to a navigation. A response
|
|
35
|
-
* its cache says so without naming which cache, so none is named. Console messages and runtime
|
|
36
|
-
* captured at all (`firefoxConsoleUnavailable`).
|
|
31
|
+
* What Firefox's WebDriver BiDi network events cover for a tab, from what Retest exercised on Firefox 133 and 158: the
|
|
32
|
+
* tab's document and the frames inside it, of its own site and of others, each request named by the browsing context
|
|
33
|
+
* that made it. A request a dedicated worker makes is named by the page that started the worker, so it cannot be told
|
|
34
|
+
* from the page's own; workers' requests are not claimed, and the reason says they may appear as the page's. Retest
|
|
35
|
+
* reads no resource type from Firefox: a request is a document only when Firefox ties it to a navigation. A response
|
|
36
|
+
* Firefox served from its cache says so without naming which cache, so none is named. Console messages and runtime
|
|
37
|
+
* errors are not captured at all (`firefoxConsoleUnavailable`).
|
|
37
38
|
*/
|
|
38
39
|
export declare const firefoxScope: DiagnosticScope;
|
|
39
40
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"firefox-collector.d.ts","sourceRoot":"","sources":["../../src/diagnostics/firefox-collector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mCAAmC,CAAA;AACnE,OAAO,KAAK,EAAE,eAAe,EAAa,MAAM,4BAA4B,CAAA;AAE5E,OAAO,KAAK,EAAE,oBAAoB,EAAE,cAAc,EAAe,MAAM,mBAAmB,CAAA;AAI1F,MAAM,MAAM,uBAAuB,GAAG;IACpC,MAAM,EAAE,UAAU,CAAA;IAClB,8FAA8F;IAC9F,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,cAAc,CAAA;IACpB;;;OAGG;IACH,aAAa,EAAE;QAAE,SAAS,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QAAC,WAAW,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAAA;KAAE,CAAA;IACvI,mGAAmG;IACnG,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAC5B,CAAA;AAED
|
|
1
|
+
{"version":3,"file":"firefox-collector.d.ts","sourceRoot":"","sources":["../../src/diagnostics/firefox-collector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mCAAmC,CAAA;AACnE,OAAO,KAAK,EAAE,eAAe,EAAa,MAAM,4BAA4B,CAAA;AAE5E,OAAO,KAAK,EAAE,oBAAoB,EAAE,cAAc,EAAe,MAAM,mBAAmB,CAAA;AAI1F,MAAM,MAAM,uBAAuB,GAAG;IACpC,MAAM,EAAE,UAAU,CAAA;IAClB,8FAA8F;IAC9F,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,cAAc,CAAA;IACpB;;;OAGG;IACH,aAAa,EAAE;QAAE,SAAS,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;QAAC,WAAW,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAAA;KAAE,CAAA;IACvI,mGAAmG;IACnG,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAC5B,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,yBAAyB,2TAA2T,CAAA;AAEjW;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,EAAE,eAY1B,CAAA;AAyBD;;;;;;GAMG;AACH,qBAAa,gBAAiB,YAAW,oBAAoB;;IAC3D,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAe;IAC9C,QAAQ,CAAC,WAAW,EAAE;QAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAyC;gBAY/E,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,kBAAyB,EAAE,EAAE,uBAAuB;IA0BxG,sIAAsI;IAChI,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAU7C,IAAI,IAAI,IAAI;CAqFb"}
|
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
import { readBidi } from "../browser/firefox/bidi-client.js";
|
|
2
2
|
import { s } from "../protocol/schema.js";
|
|
3
3
|
/**
|
|
4
|
-
* Why a Firefox capture holds no console messages or runtime errors. Firefox
|
|
4
|
+
* Why a Firefox capture holds no console messages or runtime errors. Firefox sends both through one event,
|
|
5
5
|
* `log.entryAdded`. Strings, numbers, plain data objects and a DOM node left the probe's marker untouched, but logged
|
|
6
6
|
* objects with enumerable getters changed the page's title before the entry arrived, including nested getters.
|
|
7
7
|
* Firefox ignores extra serialization fields on subscriptions and session.new, and rejects serialization
|
|
8
8
|
* capabilities. Filtering delivered entries for primitive arguments is too late to prevent that mutation.
|
|
9
|
-
* A depth limit avoids the probed object's getters in script results, not in log events.
|
|
9
|
+
* A depth limit avoids the probed object's getters in script results, not in log events. Firefox 133 and 158 both
|
|
10
|
+
* behave so (`tests/integration/firefox-diagnostics.test.ts`).
|
|
10
11
|
*/
|
|
11
|
-
export const firefoxConsoleUnavailable = "Firefox
|
|
12
|
+
export const firefoxConsoleUnavailable = "Firefox's console source runs the page's own enumerable getters when it serializes logged objects, before Retest can filter an entry. The log subscription ignores serialization options and offers no primitive-only filter, so Retest does not subscribe and records no console message or runtime error on Firefox.";
|
|
12
13
|
/**
|
|
13
|
-
* What Firefox's WebDriver BiDi network events cover for a tab, from what Retest exercised on Firefox 133: the
|
|
14
|
-
* document and the frames inside it, of its own site and of others, each request named by the browsing context
|
|
15
|
-
* made it. A request a dedicated worker makes is named by the page that started the worker, so it cannot be told
|
|
16
|
-
* the page's own; workers' requests are not claimed, and the reason says they may appear as the page's.
|
|
17
|
-
* no resource type: a request is a document only when Firefox ties it to a navigation. A response
|
|
18
|
-
* its cache says so without naming which cache, so none is named. Console messages and runtime
|
|
19
|
-
* captured at all (`firefoxConsoleUnavailable`).
|
|
14
|
+
* What Firefox's WebDriver BiDi network events cover for a tab, from what Retest exercised on Firefox 133 and 158: the
|
|
15
|
+
* tab's document and the frames inside it, of its own site and of others, each request named by the browsing context
|
|
16
|
+
* that made it. A request a dedicated worker makes is named by the page that started the worker, so it cannot be told
|
|
17
|
+
* from the page's own; workers' requests are not claimed, and the reason says they may appear as the page's. Retest
|
|
18
|
+
* reads no resource type from Firefox: a request is a document only when Firefox ties it to a navigation. A response
|
|
19
|
+
* Firefox served from its cache says so without naming which cache, so none is named. Console messages and runtime
|
|
20
|
+
* errors are not captured at all (`firefoxConsoleUnavailable`).
|
|
20
21
|
*/
|
|
21
22
|
export const firefoxScope = {
|
|
22
23
|
engine: 'firefox',
|
|
@@ -29,7 +30,7 @@ export const firefoxScope = {
|
|
|
29
30
|
covered: ['top_level_document', 'same_process_frames', 'out_of_process_frames'],
|
|
30
31
|
notCovered: ['dedicated_workers', 'shared_workers', 'service_workers'],
|
|
31
32
|
},
|
|
32
|
-
reason: `${firefoxConsoleUnavailable} Firefox names each request by the browsing context that made it, so a dedicated worker's request is recorded as its page's, and
|
|
33
|
+
reason: `${firefoxConsoleUnavailable} Firefox names each request by the browsing context that made it, so a dedicated worker's request is recorded as its page's, and Retest reads no resource type from it beyond a navigation's document.`,
|
|
33
34
|
};
|
|
34
35
|
const events = ['network.beforeRequestSent', 'network.responseStarted', 'network.responseCompleted', 'network.fetchError'];
|
|
35
36
|
const requestSchema = s.object({ request: s.string(), url: s.string(), method: s.string() });
|
|
@@ -154,6 +155,10 @@ export class FirefoxCollector {
|
|
|
154
155
|
#requestSent({ context, navigation, redirectCount, timestamp, request }) {
|
|
155
156
|
if (!this.#inTab(context))
|
|
156
157
|
return;
|
|
158
|
+
// Firefox 158 tells a request for a data: address, such as a page's `<link rel="icon" href="data:,">`, which reaches
|
|
159
|
+
// no network; Firefox 133 and Chrome tell none, and none is recorded.
|
|
160
|
+
if (/^data:/i.test(request.url))
|
|
161
|
+
return;
|
|
157
162
|
const previous = this.#hops.get(request.request);
|
|
158
163
|
if (previous === undefined && this.#hops.size >= this.#maxTrackedRequests) {
|
|
159
164
|
if (this.#sink.limited !== undefined)
|
package/dist/media/client.d.ts
CHANGED
|
@@ -130,7 +130,8 @@ export type EncoderOwnership = {
|
|
|
130
130
|
export declare function captureEncoderOwnership(worker: OwnedProcessGroup, encoderPid: number): EncoderOwnership;
|
|
131
131
|
/**
|
|
132
132
|
* Retest's media process, `retest-media`, run as a child in a process group of its own; each encoder it starts
|
|
133
|
-
* leads a group of its own too.
|
|
133
|
+
* leads a group of its own too. On Windows the process is held in a job of its own instead, and its encoders are born
|
|
134
|
+
* in that job. Frames go to it over a pipe with their bytes untouched; each recording ends with
|
|
134
135
|
* exactly one `Ended` from the process. The client never invents an outcome: if the process ends before a
|
|
135
136
|
* recording does, the client stops only the encoder processes whose launch ownership it recorded, and the promise
|
|
136
137
|
* rejects with how the process ended and any file left. Unreadable or changed ownership is a cleanup failure.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/media/client.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAA;AAC5D,OAAO,KAAK,EAAwB,sBAAsB,EAAE,MAAM,gCAAgC,CAAA;AAClG,OAAO,KAAK,EACV,GAAG,EACH,UAAU,EACV,YAAY,EACZ,KAAK,EACL,WAAW,EACX,aAAa,EACb,oBAAoB,EACpB,KAAK,EACL,SAAS,EACT,SAAS,EACT,UAAU,EAIV,iBAAiB,EACjB,QAAQ,EACR,cAAc,EACd,OAAO,EACP,SAAS,EACT,gBAAgB,EAChB,UAAU,EACV,QAAQ,EACR,YAAY,EACb,MAAM,eAAe,CAAA;AAQtB,OAAO,EAAE,mBAAmB,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAA;AAC7F,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/media/client.ts"],"names":[],"mappings":"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"}
|