obsidian-integration-testing 12.1.1 → 12.2.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/dist/lib/cjs/app-config.cjs +137 -0
- package/dist/lib/cjs/app-config.d.cts +190 -0
- package/dist/lib/cjs/appium-session-config.cjs +9 -1
- package/dist/lib/cjs/appium-session-config.d.cts +23 -0
- package/dist/lib/cjs/cdp-command-timeout-error.cjs +50 -0
- package/dist/lib/cjs/cdp-command-timeout-error.d.cts +48 -0
- package/dist/lib/cjs/emulator-arguments.cjs +19 -3
- package/dist/lib/cjs/emulator-arguments.d.cts +44 -0
- package/dist/lib/cjs/emulator-marker.cjs +169 -0
- package/dist/lib/cjs/emulator-marker.d.cts +224 -0
- package/dist/lib/cjs/emulator-reaper.cjs +248 -0
- package/dist/lib/cjs/emulator-reaper.d.cts +225 -0
- package/dist/lib/cjs/emulator-reclaim.cjs +312 -0
- package/dist/lib/cjs/emulator-reclaim.d.cts +179 -0
- package/dist/lib/cjs/eval-cap-exceeded-error.cjs +61 -0
- package/dist/lib/cjs/eval-cap-exceeded-error.d.cts +92 -0
- package/dist/lib/cjs/global-setup-core.cjs +2 -3
- package/dist/lib/cjs/index.cjs +18 -1
- package/dist/lib/cjs/index.d.cts +6 -0
- package/dist/lib/cjs/library.cjs +1 -1
- package/dist/lib/cjs/mobile-input.cjs +14 -1
- package/dist/lib/cjs/mobile-input.d.cts +30 -0
- package/dist/lib/cjs/namespace-bootstrap.cjs +9 -1
- package/dist/lib/cjs/poll-in-obsidian.cjs +1 -1
- package/dist/lib/cjs/poll-in-obsidian.d.cts +6 -2
- package/dist/lib/cjs/setup-lock.cjs +56 -8
- package/dist/lib/cjs/setup-lock.d.cts +53 -0
- package/dist/lib/cjs/transport-appium.cjs +73 -4
- package/dist/lib/cjs/transport-appium.d.cts +35 -0
- package/dist/lib/cjs/transport-desktop-cdp.cjs +22 -7
- package/dist/lib/cjs/transport-factory.cjs +297 -190
- package/dist/lib/cjs/transport-factory.d.cts +15 -0
- package/dist/lib/cjs/transport-options.cjs +1 -1
- package/dist/lib/cjs/transport-options.d.cts +27 -0
- package/dist/lib/esm/app-config.d.mts +190 -0
- package/dist/lib/esm/app-config.mjs +110 -0
- package/dist/lib/esm/appium-session-config.d.mts +23 -0
- package/dist/lib/esm/appium-session-config.mjs +7 -1
- package/dist/lib/esm/cdp-command-timeout-error.d.mts +48 -0
- package/dist/lib/esm/cdp-command-timeout-error.mjs +26 -0
- package/dist/lib/esm/emulator-arguments.d.mts +44 -0
- package/dist/lib/esm/emulator-arguments.mjs +16 -2
- package/dist/lib/esm/emulator-marker.d.mts +224 -0
- package/dist/lib/esm/emulator-marker.mjs +134 -0
- package/dist/lib/esm/emulator-reaper.d.mts +225 -0
- package/dist/lib/esm/emulator-reaper.mjs +211 -0
- package/dist/lib/esm/emulator-reclaim.d.mts +179 -0
- package/dist/lib/esm/emulator-reclaim.mjs +291 -0
- package/dist/lib/esm/eval-cap-exceeded-error.d.mts +92 -0
- package/dist/lib/esm/eval-cap-exceeded-error.mjs +36 -0
- package/dist/lib/esm/global-setup-core.mjs +6 -4
- package/dist/lib/esm/index.d.mts +6 -0
- package/dist/lib/esm/index.mjs +19 -1
- package/dist/lib/esm/library.mjs +1 -1
- package/dist/lib/esm/mobile-input.d.mts +30 -0
- package/dist/lib/esm/mobile-input.mjs +12 -1
- package/dist/lib/esm/namespace-bootstrap.mjs +9 -1
- package/dist/lib/esm/poll-in-obsidian.d.mts +6 -2
- package/dist/lib/esm/poll-in-obsidian.mjs +1 -1
- package/dist/lib/esm/setup-lock.d.mts +53 -0
- package/dist/lib/esm/setup-lock.mjs +52 -7
- package/dist/lib/esm/transport-appium.d.mts +35 -0
- package/dist/lib/esm/transport-appium.mjs +78 -4
- package/dist/lib/esm/transport-desktop-cdp.mjs +22 -7
- package/dist/lib/esm/transport-factory.d.mts +15 -0
- package/dist/lib/esm/transport-factory.mjs +305 -184
- package/dist/lib/esm/transport-options.d.mts +27 -0
- package/dist/lib/esm/transport-options.mjs +1 -1
- package/package.json +4 -4
|
@@ -20,7 +20,25 @@
|
|
|
20
20
|
* default is a full cold boot, and snapshot reuse is an explicit opt-in that
|
|
21
21
|
* takes **both** halves: load and save together, so the snapshot the next run
|
|
22
22
|
* resumes is one this run wrote.
|
|
23
|
+
*
|
|
24
|
+
* **The environment quiets `netsimd`, because nothing else can.** The emulator
|
|
25
|
+
* starts `netsimd`, its network simulator, and netsimd has a loop that never
|
|
26
|
+
* checks its hostapd socket for end-of-file: once that socket closes, every
|
|
27
|
+
* pass decodes an empty Wi-Fi frame and logs the failure, with no rate limit.
|
|
28
|
+
* On 2026-09-10 an idle emulator's netsimd wrote 278 GB of that one warning to
|
|
29
|
+
* `%TEMP%\netsimd\netsim_stderr.log` in six hours, filling the drive. netsimd
|
|
30
|
+
* has no flag to quiet it; its log level is `RUST_LOG`, read from the
|
|
31
|
+
* environment it inherits. See **L56**.
|
|
32
|
+
*/
|
|
33
|
+
/**
|
|
34
|
+
* The `RUST_LOG` filter the harness hands the emulator, and through it `netsimd`.
|
|
35
|
+
*
|
|
36
|
+
* `error` rather than a directive aimed at the one noisy module: nothing reads
|
|
37
|
+
* netsimd's log, real failures are still recorded, and a level can be verified
|
|
38
|
+
* by the absence of `netsimd I` lines where a module directive could only be
|
|
39
|
+
* verified by reproducing the flood.
|
|
23
40
|
*/
|
|
41
|
+
export declare const NETSIM_LOG_FILTER = "error";
|
|
24
42
|
/**
|
|
25
43
|
* Parameters for {@link buildEmulatorArguments}.
|
|
26
44
|
*/
|
|
@@ -43,6 +61,21 @@ export interface BuildEmulatorArgumentsParams {
|
|
|
43
61
|
*/
|
|
44
62
|
readonly shouldReuseSnapshot: boolean;
|
|
45
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Result of {@link buildEmulatorEnvironment}.
|
|
66
|
+
*/
|
|
67
|
+
export interface EmulatorEnvironment {
|
|
68
|
+
/**
|
|
69
|
+
The environment to spawn the emulator with.
|
|
70
|
+
*/
|
|
71
|
+
readonly environment: NodeJS.ProcessEnv;
|
|
72
|
+
/**
|
|
73
|
+
* Whether the harness set `RUST_LOG` itself. `false` means the caller's
|
|
74
|
+
* environment already carried one, which wins — and which leaves netsimd's
|
|
75
|
+
* log unbounded if it is `warn` or chattier.
|
|
76
|
+
*/
|
|
77
|
+
readonly isNetsimLogGuarded: boolean;
|
|
78
|
+
}
|
|
46
79
|
/**
|
|
47
80
|
* Builds the argument list for spawning the Android emulator.
|
|
48
81
|
*
|
|
@@ -50,3 +83,14 @@ export interface BuildEmulatorArgumentsParams {
|
|
|
50
83
|
* @returns The argument array to pass to the emulator binary.
|
|
51
84
|
*/
|
|
52
85
|
export declare function buildEmulatorArguments(params: BuildEmulatorArgumentsParams): string[];
|
|
86
|
+
/**
|
|
87
|
+
* Builds the environment for spawning the Android emulator: the caller's own,
|
|
88
|
+
* plus a `RUST_LOG` that keeps `netsimd`'s log bounded.
|
|
89
|
+
*
|
|
90
|
+
* An explicit `RUST_LOG` is left alone — someone who set it asked for that
|
|
91
|
+
* level — and reported back so the caller can say the guard is off.
|
|
92
|
+
*
|
|
93
|
+
* @param baseEnvironment - The environment to extend, normally `process.env`.
|
|
94
|
+
* @returns The environment, and whether the harness supplied the log filter.
|
|
95
|
+
*/
|
|
96
|
+
export declare function buildEmulatorEnvironment(baseEnvironment: NodeJS.ProcessEnv): EmulatorEnvironment;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var emulator_marker_exports = {};
|
|
30
|
+
__export(emulator_marker_exports, {
|
|
31
|
+
checkIsMarkedEmulatorRunning: () => checkIsMarkedEmulatorRunning,
|
|
32
|
+
clearEmulatorMarker: () => clearEmulatorMarker,
|
|
33
|
+
clearEmulatorMarkerIfStopped: () => clearEmulatorMarkerIfStopped,
|
|
34
|
+
listEmulatorMarkers: () => listEmulatorMarkers,
|
|
35
|
+
readEmulatorMarker: () => readEmulatorMarker,
|
|
36
|
+
resolveEmulatorMarkerVerdict: () => resolveEmulatorMarkerVerdict,
|
|
37
|
+
selectLiveMarkedPids: () => selectLiveMarkedPids,
|
|
38
|
+
writeEmulatorMarker: () => writeEmulatorMarker
|
|
39
|
+
});
|
|
40
|
+
module.exports = __toCommonJS(emulator_marker_exports);
|
|
41
|
+
var import_node_fs = require("node:fs");
|
|
42
|
+
var import_node_os = require("node:os");
|
|
43
|
+
var import_node_path = require("node:path");
|
|
44
|
+
var import_node_process = __toESM(require("node:process"), 1);
|
|
45
|
+
var import_leftover_cleanup = require("./leftover-cleanup.cjs");
|
|
46
|
+
var import_log = require("./log.cjs");
|
|
47
|
+
var import_process_liveness = require("./process-liveness.cjs");
|
|
48
|
+
const MARKER_FILE_SUFFIX = ".emulator.json";
|
|
49
|
+
function checkIsMarkedEmulatorRunning(marker) {
|
|
50
|
+
return marker?.ownedEmulatorPids.some((pid) => (0, import_process_liveness.checkIsProcessAlive)(pid)) ?? false;
|
|
51
|
+
}
|
|
52
|
+
function clearEmulatorMarker(avdName) {
|
|
53
|
+
try {
|
|
54
|
+
(0, import_node_fs.rmSync)(getMarkerFilePath(avdName), { force: true });
|
|
55
|
+
} catch (error) {
|
|
56
|
+
(0, import_log.log)(`[emulator-marker] Could not remove the marker for AVD "${avdName}": ${getErrorMessage(error)}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function clearEmulatorMarkerIfStopped(avdName) {
|
|
60
|
+
if (checkIsMarkedEmulatorRunning(readEmulatorMarker(avdName))) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
clearEmulatorMarker(avdName);
|
|
64
|
+
}
|
|
65
|
+
function listEmulatorMarkers() {
|
|
66
|
+
let fileNames;
|
|
67
|
+
try {
|
|
68
|
+
fileNames = (0, import_node_fs.readdirSync)(getMarkerDirectory());
|
|
69
|
+
} catch {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
const markers = [];
|
|
73
|
+
for (const fileName of fileNames) {
|
|
74
|
+
if (!fileName.endsWith(MARKER_FILE_SUFFIX)) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const marker = readEmulatorMarker(fileName.slice(0, -MARKER_FILE_SUFFIX.length));
|
|
78
|
+
if (marker) {
|
|
79
|
+
markers.push(marker);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return markers;
|
|
83
|
+
}
|
|
84
|
+
function readEmulatorMarker(avdName) {
|
|
85
|
+
let parsed;
|
|
86
|
+
try {
|
|
87
|
+
parsed = JSON.parse((0, import_node_fs.readFileSync)(getMarkerFilePath(avdName), "utf-8"));
|
|
88
|
+
} catch {
|
|
89
|
+
return void 0;
|
|
90
|
+
}
|
|
91
|
+
return parseMarker(parsed, avdName);
|
|
92
|
+
}
|
|
93
|
+
function resolveEmulatorMarkerVerdict(params) {
|
|
94
|
+
if (selectLiveMarkedPids(params.marker, params.liveEmulatorPids).length === 0) {
|
|
95
|
+
return "stale-marker";
|
|
96
|
+
}
|
|
97
|
+
if (params.marker.ownerPid !== params.currentPid && params.isOwnerAlive) {
|
|
98
|
+
return "in-use-by-live-run";
|
|
99
|
+
}
|
|
100
|
+
return "harness-leftover";
|
|
101
|
+
}
|
|
102
|
+
function selectLiveMarkedPids(marker, liveEmulatorPids) {
|
|
103
|
+
const livePids = marker.ownedEmulatorPids.filter((pid) => liveEmulatorPids.includes(pid));
|
|
104
|
+
const { preLaunchEmulatorPids } = marker;
|
|
105
|
+
if (preLaunchEmulatorPids === void 0) {
|
|
106
|
+
return livePids;
|
|
107
|
+
}
|
|
108
|
+
return [...livePids, ...liveEmulatorPids.filter((pid) => !preLaunchEmulatorPids.includes(pid) && !livePids.includes(pid))];
|
|
109
|
+
}
|
|
110
|
+
function writeEmulatorMarker(params) {
|
|
111
|
+
const marker = {
|
|
112
|
+
avdName: params.avdName,
|
|
113
|
+
// Omitted rather than written empty while the emulator is still booting: the stop reads its absence as "no console to shut down".
|
|
114
|
+
...params.deviceId !== void 0 && { deviceId: params.deviceId },
|
|
115
|
+
ownedEmulatorPids: [...params.ownedEmulatorPids],
|
|
116
|
+
ownerPid: import_node_process.default.pid,
|
|
117
|
+
// Dropped by the write that learns the backend's PID: from then on the owned set is exact and a diff would only widen it.
|
|
118
|
+
...params.preLaunchEmulatorPids !== void 0 && { preLaunchEmulatorPids: [...params.preLaunchEmulatorPids] },
|
|
119
|
+
startedAtInMilliseconds: params.startedAtInMilliseconds ?? Date.now()
|
|
120
|
+
};
|
|
121
|
+
try {
|
|
122
|
+
(0, import_node_fs.mkdirSync)(getMarkerDirectory(), { recursive: true });
|
|
123
|
+
(0, import_node_fs.writeFileSync)(getMarkerFilePath(marker.avdName), JSON.stringify(marker));
|
|
124
|
+
} catch (error) {
|
|
125
|
+
(0, import_log.log)(`[emulator-marker] Could not record the emulator for AVD "${marker.avdName}": ${getErrorMessage(error)}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function checkIsNumberArray(value) {
|
|
129
|
+
return Array.isArray(value) && value.every((item) => typeof item === "number");
|
|
130
|
+
}
|
|
131
|
+
function getErrorMessage(error) {
|
|
132
|
+
return error instanceof Error ? error.message : String(error);
|
|
133
|
+
}
|
|
134
|
+
function getMarkerDirectory() {
|
|
135
|
+
return (0, import_node_path.join)((0, import_node_os.tmpdir)(), import_leftover_cleanup.HARNESS_TEMP_DIR_NAME);
|
|
136
|
+
}
|
|
137
|
+
function getMarkerFilePath(avdName) {
|
|
138
|
+
return (0, import_node_path.join)(getMarkerDirectory(), `${avdName}${MARKER_FILE_SUFFIX}`);
|
|
139
|
+
}
|
|
140
|
+
function parseMarker(parsed, avdName) {
|
|
141
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
142
|
+
return void 0;
|
|
143
|
+
}
|
|
144
|
+
const record = parsed;
|
|
145
|
+
const { avdName: markedAvdName, deviceId, ownedEmulatorPids, ownerPid, preLaunchEmulatorPids, startedAtInMilliseconds } = record;
|
|
146
|
+
if (markedAvdName !== avdName || deviceId !== void 0 && typeof deviceId !== "string" || typeof ownerPid !== "number" || typeof startedAtInMilliseconds !== "number" || !checkIsNumberArray(ownedEmulatorPids) || preLaunchEmulatorPids !== void 0 && !checkIsNumberArray(preLaunchEmulatorPids)) {
|
|
147
|
+
return void 0;
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
avdName,
|
|
151
|
+
...deviceId !== void 0 && { deviceId },
|
|
152
|
+
ownedEmulatorPids,
|
|
153
|
+
ownerPid,
|
|
154
|
+
...preLaunchEmulatorPids !== void 0 && { preLaunchEmulatorPids },
|
|
155
|
+
startedAtInMilliseconds
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
159
|
+
0 && (module.exports = {
|
|
160
|
+
checkIsMarkedEmulatorRunning,
|
|
161
|
+
clearEmulatorMarker,
|
|
162
|
+
clearEmulatorMarkerIfStopped,
|
|
163
|
+
listEmulatorMarkers,
|
|
164
|
+
readEmulatorMarker,
|
|
165
|
+
resolveEmulatorMarkerVerdict,
|
|
166
|
+
selectLiveMarkedPids,
|
|
167
|
+
writeEmulatorMarker
|
|
168
|
+
});
|
|
169
|
+
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vLi4vc3JjL2VtdWxhdG9yLW1hcmtlci50cyJdLAogICJzb3VyY2VzQ29udGVudCI6IFsiLyoqXG4gKiBAZmlsZVxuICpcbiAqIFJlY29yZHMgd2hpY2ggZW11bGF0b3IgdGhpcyBoYXJuZXNzIHN0YXJ0ZWQgZm9yIGFuIEFWRCwgc28gdGhlIGVtdWxhdG9yXG4gKiBzdGF5cyAqKnRoZSBoYXJuZXNzJ3MqKiB1bnRpbCBhIHN0b3Agb2YgaXQgaGFzIGJlZW4gdmVyaWZpZWQgXHUyMDE0IHdoaWNoZXZlciBydW4sXG4gKiBvciB3aGljaGV2ZXIgcHJvY2VzcyBvZiBhIHJ1biwgZ2V0cyB0byB0aGF0IHN0b3AuXG4gKlxuICogT3duZXJzaGlwIHVzZWQgdG8gbGl2ZSBvbmx5IGluIHRoZSBtZW1vcnkgb2YgdGhlIHJ1biB0aGF0IGxhdW5jaGVkIHRoZVxuICogZW11bGF0b3IsIGFuZCB0aHJlZSBvcmRpbmFyeSB0aGluZ3MgbG9zZSB0aGF0IG1lbW9yeTpcbiAqXG4gKiAtICoqVGhlIGxhdW5jaGluZyBwcm9jZXNzIGlzIGEgdGVzdCB3b3JrZXIuKiogQSBwcm9qZWN0IHdpdGggbm8gdHJhbnNwb3J0XG4gKiAgIGdsb2JhbCBzZXR1cCBjcmVhdGVzIGl0cyB0cmFuc3BvcnQgXHUyMDE0IGFuZCBhdXRvLXN0YXJ0cyBpdHMgZW11bGF0b3IgXHUyMDE0IGluc2lkZSBhXG4gKiAgIFZpdGVzdCB3b3JrZXIsIGFuZCBWaXRlc3QgZW5kcyB3b3JrZXJzIGFicnVwdGx5LCBzbyB0aGUgd29ya2VyJ3MgdGVhcmRvd25cbiAqICAgbmV2ZXIgZ2V0cyBhIHR1cm4uIFRoZSBwcm9qZWN0J3Mgb3duIGdsb2JhbCB0ZWFyZG93biBydW5zLCBidXQgaGVsZCBub1xuICogICBoYW5kbGUgdG8gdGhlIGVtdWxhdG9yLiBUaGlzIHJlcG8ncyBgaW50ZWdyYXRpb24tdGVzdHM6YW5kcm9pZGBcbiAqICAgcHJvamVjdCBpcyBleGFjdGx5IHRoaXMsIGFuZCBvbiAyMDI2LTA5LTEwIHRoZSBlbXVsYXRvciBpdCBsZWZ0IGlkbGUga2VwdFxuICogICBgbmV0c2ltZGAgd3JpdGluZyB1bnRpbCBhIDI3OCBHQiBsb2cgZmlsbGVkIHRoZSBkcml2ZSAoKipMNTYqKikuXG4gKiAtICoqVGhlIHJ1bm5lciBpcyBraWxsZWQqKiBcdTIwMTQgU0lHS0lMTCwgVGFzayBNYW5hZ2VyLCBhbiBJREUgc3RvcCBidXR0b24gXHUyMDE0IGFuZFxuICogICBuZWl0aGVyIHRlYXJkb3duIHBhdGggcnVucyBhdCBhbGwuXG4gKiAtICoqQSBsYXRlciBydW4gYWRvcHRzIHRoZSBsZWZ0b3Zlci4qKiBBIGRldmljZSB0aGlzIHJ1biBkaWQgbm90IHN0YXJ0IGlzXG4gKiAgIGNvcnJlY3RseSBub3QgdGhpcyBydW4ncyB0byBzdG9wIFx1MjAxNCB3aGljaCwgd2l0aG91dCBhIHJlY29yZCwgbWFkZSBldmVyeVxuICogICBsZWZ0b3ZlciBub2JvZHkncyB0byBzdG9wLCBmb3IgZXZlci5cbiAqXG4gKiBUaGUgbWFya2VyIGlzIHRoZSByZWNvcmQ6IGEgc21hbGwgSlNPTiBzZW50aW5lbCBwZXIgQVZEIG5leHQgdG8gdGhlIHNldHVwIGxvY2ssXG4gKiB3cml0dGVuIGZyb20gdGhlIG1vbWVudCB0aGUgZW11bGF0b3IgaXMgbGF1bmNoZWQgYW5kIHJlbW92ZWQgb25seSBieSBhIHZlcmlmaWVkXG4gKiBzdG9wLiBJdCBpcyBkZWxpYmVyYXRlbHkgdG9sZXJhbnQgXHUyMDE0IGEgbWlzc2luZywgdW5yZWFkYWJsZSBvciBtaXNtYXRjaGVkIG1hcmtlclxuICogcmVhZHMgYXMgXCJub3Qgb3Vyc1wiLCB0aGUgc2FtZSBhbnN3ZXIgdGhlIGhhcm5lc3MgZ2F2ZSBiZWZvcmUgbWFya2VycyBleGlzdGVkLlxuICpcbiAqICoqSXQgcmVjb3JkcyBhIGxhdW5jaCwgbm90IGEgYm9vdGVkIGVtdWxhdG9yKiosIGFuZCB0aGF0IGlzIHRoZSBkaWZmZXJlbmNlXG4gKiBiZXR3ZWVuIGNvdmVyaW5nIHRoZSB0aGlyZCBjYXNlIGFib3ZlIGFuZCBjb3ZlcmluZyBpdCBvbmx5IGFmdGVyIHRoZSB+NDVzIGFcbiAqIGNvbGQgYm9vdCB0YWtlcy4gQSBsYXVuY2gtdGltZSBtYXJrZXIgbmFtZXMgdGhlIGBlbXVsYXRvcmAgbGF1bmNoZXIgYWxvbmUgYW5kXG4gKiBubyBkZXZpY2UgeWV0OyB0aGUgZGV2aWNlIGlkIGFycml2ZXMgd2hlbiBvbmUgYXBwZWFycywgYW5kIHRoZSBRRU1VIGJhY2tlbmQnc1xuICogUElEIG9uY2UgdGhlIGJvb3QgY29tcGxldGVzLiBFYWNoIHdyaXRlIGtlZXBzIHRoZSBvcmlnaW5hbCBsYXVuY2ggdGltZSwgc28gdGhlXG4gKiByZWFwZXIgYXJtZWQgYnkgdGhlIGZpcnN0IHN0aWxsIHJlY29nbml6ZXMgdGhlIGxhc3QgYXMgdGhlIHNhbWUgZW11bGF0b3IuXG4gKlxuICogV2hhdCBtYWtlcyBhIG1hcmtlcidzIGVtdWxhdG9yIG91cnMgaXMgZXZpZGVuY2UsIG5vdCB0aGUgZmlsZTogYXQgbGVhc3Qgb25lXG4gKiBQSUQgaXQgcmVjb3JkcyBtdXN0IHN0aWxsIGJlIGEgKipsaXZlIGVtdWxhdG9yIHByb2Nlc3MqKiBvbiB0aGUgaG9zdCAodGhlXG4gKiBjYWxsZXIgaW50ZXJzZWN0cyB3aXRoIHRoZSBwcm9jZXNzIGxpc3RpbmcsIG5vdCBhIGJhcmUgc2lnbmFsLTAgcHJvYmUpLCBzbyBhXG4gKiByZWN5Y2xlZCBQSUQgb3IgYW4gZW11bGF0b3Igc29tZWJvZHkgYm9vdGVkIGJ5IGhhbmQgaXMgbmV2ZXIgbWlzdGFrZW4gZm9yIG9uZVxuICogdGhpcyBoYXJuZXNzIHN0YXJ0ZWQuIEEgbGF1bmNoLXRpbWUgbWFya2VyIHNhdGlzZmllcyB0aGF0IGFzIHdyaXR0ZW4sIGJlY2F1c2VcbiAqIGBlbXVsYXRvci1iYWNrZW5kLnRzYCBjb3VudHMgdGhlIGBlbXVsYXRvcmAgbGF1bmNoZXIgYXMgb25lIG9mIHRoZSBlbXVsYXRvclxuICogcHJvY2Vzc2VzIFx1MjAxNCB3aGljaCBpcyB3aHkgcGhhc2luZyB0aGUgbWFya2VyIG5lZWRlZCBubyBjaGFuZ2UgdG8gdGhlIHZlcmRpY3QuXG4gKi9cblxuaW1wb3J0IHtcbiAgbWtkaXJTeW5jLFxuICByZWFkZGlyU3luYyxcbiAgcmVhZEZpbGVTeW5jLFxuICBybVN5bmMsXG4gIHdyaXRlRmlsZVN5bmNcbn0gZnJvbSAnbm9kZTpmcyc7XG5pbXBvcnQgeyB0bXBkaXIgfSBmcm9tICdub2RlOm9zJztcbmltcG9ydCB7IGpvaW4gfSBmcm9tICdub2RlOnBhdGgnO1xuaW1wb3J0IHByb2Nlc3MgZnJvbSAnbm9kZTpwcm9jZXNzJztcblxuaW1wb3J0IHsgSEFSTkVTU19URU1QX0RJUl9OQU1FIH0gZnJvbSAnLi9sZWZ0b3Zlci1jbGVhbnVwLmNqcyc7XG5pbXBvcnQgeyBsb2cgfSBmcm9tICcuL2xvZy5janMnO1xuaW1wb3J0IHsgY2hlY2tJc1Byb2Nlc3NBbGl2ZSB9IGZyb20gJy4vcHJvY2Vzcy1saXZlbmVzcy5janMnO1xuXG5jb25zdCBNQVJLRVJfRklMRV9TVUZGSVggPSAnLmVtdWxhdG9yLmpzb24nO1xuXG4vKipcbiAqIElkZW50aWZpZXMgdGhlIGVtdWxhdG9yIHRoaXMgaGFybmVzcyBzdGFydGVkIGZvciBhbiBBVkQuXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgRW11bGF0b3JNYXJrZXIge1xuICAvKipcbiAgVGhlIEFWRCB0aGUgZW11bGF0b3IgaXMgcnVubmluZy5cbiAgICovXG4gIHJlYWRvbmx5IGF2ZE5hbWU6IHN0cmluZztcblxuICAvKipcbiAgICogVGhlIGFkYiBzZXJpYWwgb2YgdGhlIGRldmljZSBpdCBzZXJ2ZXMsIGUuZy4gYGVtdWxhdG9yLTU1NTRgLiBBYnNlbnQgd2hpbGVcbiAgICogdGhlIGVtdWxhdG9yIGlzIHN0aWxsIGJvb3RpbmcgYW5kIG5vIGRldmljZSBoYXMgYXBwZWFyZWQgeWV0IFx1MjAxNCBhIG1hcmtlciBpc1xuICAgKiB3cml0dGVuIGZyb20gdGhlIGxhdW5jaCwgYmVmb3JlIHRoZXJlIGlzIGFueXRoaW5nIHRvIG5hbWUuXG4gICAqL1xuICByZWFkb25seSBkZXZpY2VJZD86IHN0cmluZyB8IHVuZGVmaW5lZDtcblxuICAvKipcbiAgICogVGhlIGVtdWxhdG9yIHByb2Nlc3NlcyB0aGUgaGFybmVzcyBvd25zOiB0aGUgbGF1bmNoZXIgcGx1cyB0aGUgUUVNVSBiYWNrZW5kXG4gICAqIGl0IGZvcmtlZCBcdTIwMTQgb3IsIHVudGlsIHRoZSBkZXZpY2UgYXBwZWFycywgdGhlIGxhdW5jaGVyIGFsb25lLlxuICAgKi9cbiAgcmVhZG9ubHkgb3duZWRFbXVsYXRvclBpZHM6IHJlYWRvbmx5IG51bWJlcltdO1xuXG4gIC8qKlxuICAgKiBQSUQgb2YgdGhlIGhhcm5lc3MgcHJvY2VzcyByZXNwb25zaWJsZSBmb3Igc3RvcHBpbmcgdGhlIGVtdWxhdG9yIFx1MjAxNCB0aGUgb25lXG4gICAqIHRoYXQgc3RhcnRlZCBpdCwgb3IgdGhlIG9uZSB0aGF0IGxhdGVyIHRvb2sgaXQgb3ZlciBhcyBhIGxlZnRvdmVyLlxuICAgKi9cbiAgcmVhZG9ubHkgb3duZXJQaWQ6IG51bWJlcjtcblxuICAvKipcbiAgICogVGhlIGVtdWxhdG9yIHByb2Nlc3NlcyB0aGF0IHdlcmUgYWxyZWFkeSBydW5uaW5nIHdoZW4gdGhpcyBsYXVuY2ggc3Bhd25lZFxuICAgKiBpdHMgb3duIFx1MjAxNCBwcmVzZW50IG9ubHkgd2hpbGUgdGhlIHJlY29yZCBpcyBzdGlsbCBwcm92aXNpb25hbCwgaS5lLiBiZWZvcmVcbiAgICogdGhlIGJhY2tlbmQncyBvd24gUElEIGlzIGtub3duLlxuICAgKlxuICAgKiBJdCBpcyB3aGF0IG1ha2VzIGEgbGF1bmNoLXRpbWUgbWFya2VyIGNvbnZpY3RhYmxlIGF0IGFsbC4gS2lsbGluZyB0aGVcbiAgICogbGF1bmNoZXIgZG9lcyAqKm5vdCoqIGtpbGwgdGhlIGBxZW11LXN5c3RlbS0qLWhlYWRsZXNzYCBiYWNrZW5kIGl0IGZvcmtlZFxuICAgKiAoKipMNDYqKiksIHNvIGEgcmVjb3JkIG5hbWluZyB0aGUgbGF1bmNoZXIgYWxvbmUgZ29lcyBzdGFsZSBvdmVyIGFuXG4gICAqIGVtdWxhdG9yIHRoYXQgaXMgc3RpbGwgcnVubmluZyBcdTIwMTQgdGhlIGxlYWsgdGhpcyBtYXJrZXIgZXhpc3RzIHRvIHByZXZlbnQuXG4gICAqIFdpdGggdGhlIHNuYXBzaG90LCBhbnl0aGluZyBpbiB0aGUgaG9zdCdzIGVtdWxhdG9yIGxpc3RpbmcgdGhhdCBpcyBub3QgaW5cbiAgICogaXQgd2FzIGZvcmtlZCBieSB0aGlzIGxhdW5jaCwgd2hpY2ggaXMgdGhlIHNhbWUgcHJlLWxhdW5jaCBkaWZmIHRoZVxuICAgKiBzdWNjZXNzZnVsIHBhdGggdXNlcyAoYGVtdWxhdG9yLWJhY2tlbmQudHNgKSBhbmQgdGhlIHNhbWUgb25lICoqTDQ2KipcbiAgICogcmVxdWlyZXMgaW5zdGVhZCBvZiBhIGBxZW11KmAgc3dlZXAuXG4gICAqL1xuICByZWFkb25seSBwcmVMYXVuY2hFbXVsYXRvclBpZHM/OiByZWFkb25seSBudW1iZXJbXSB8IHVuZGVmaW5lZDtcblxuICAvKipcbiAgV2hlbiB0aGUgZW11bGF0b3Igd2FzIHN0YXJ0ZWQgKGBEYXRlLm5vdygpYCBlcG9jaCBtaWxsaXNlY29uZHMpLlxuICAgKi9cbiAgcmVhZG9ubHkgc3RhcnRlZEF0SW5NaWxsaXNlY29uZHM6IG51bWJlcjtcbn1cblxuLyoqXG4gKiBXaGF0IGEgbWFya2VyIHNheXMgYWJvdXQgdGhlIGVtdWxhdG9yIGl0IGRlc2NyaWJlcy5cbiAqXG4gKiAtIGBoYXJuZXNzLWxlZnRvdmVyYCBcdTIwMTQgb3VycywgYW5kIG5vIGxpdmUgaGFybmVzcyBwcm9jZXNzIGlzIHJlc3BvbnNpYmxlIGZvclxuICogICBpdDogdGhlIGNhbGxlciBtYXkgdGFrZSBpdCBvdmVyIG9yIHN0b3AgaXQuXG4gKiAtIGBpbi11c2UtYnktbGl2ZS1ydW5gIFx1MjAxNCBvdXJzLCBidXQgYW5vdGhlciBsaXZlIGhhcm5lc3MgcHJvY2VzcyBvd25zIGl0LlxuICogLSBgc3RhbGUtbWFya2VyYCBcdTIwMTQgbm9uZSBvZiBpdHMgUElEcyBpcyBhIGxpdmUgZW11bGF0b3IgYW55IG1vcmU7IHRoZSBtYXJrZXJcbiAqICAgb3V0bGl2ZWQgaXRzIGVtdWxhdG9yIGFuZCBwcm92ZXMgbm90aGluZy5cbiAqL1xuZXhwb3J0IHR5cGUgRW11bGF0b3JNYXJrZXJWZXJkaWN0ID0gJ2hhcm5lc3MtbGVmdG92ZXInIHwgJ2luLXVzZS1ieS1saXZlLXJ1bicgfCAnc3RhbGUtbWFya2VyJztcblxuLyoqXG4gKiBQYXJhbWV0ZXJzIGZvciB7QGxpbmsgcmVzb2x2ZUVtdWxhdG9yTWFya2VyVmVyZGljdH0uXG4gKi9cbmV4cG9ydCBpbnRlcmZhY2UgUmVzb2x2ZUVtdWxhdG9yTWFya2VyVmVyZGljdFBhcmFtcyB7XG4gIC8qKlxuICBQSUQgb2YgdGhlIHByb2Nlc3MgYXNraW5nLlxuICAgKi9cbiAgcmVhZG9ubHkgY3VycmVudFBpZDogbnVtYmVyO1xuXG4gIC8qKlxuICBXaGV0aGVyIHRoZSBtYXJrZXIncyBvd25lciBwcm9jZXNzIGlzIHN0aWxsIHJ1bm5pbmcuXG4gICAqL1xuICByZWFkb25seSBpc093bmVyQWxpdmU6IGJvb2xlYW47XG5cbiAgLyoqXG4gIFRoZSBlbXVsYXRvciBwcm9jZXNzZXMgY3VycmVudGx5IHJ1bm5pbmcgb24gdGhlIGhvc3QuXG4gICAqL1xuICByZWFkb25seSBsaXZlRW11bGF0b3JQaWRzOiByZWFkb25seSBudW1iZXJbXTtcblxuICAvKipcbiAgVGhlIG1hcmtlciB0byBqdWRnZS5cbiAgICovXG4gIHJlYWRvbmx5IG1hcmtlcjogRW11bGF0b3JNYXJrZXI7XG59XG5cbi8qKlxuICogUGFyYW1ldGVycyBmb3Ige0BsaW5rIHdyaXRlRW11bGF0b3JNYXJrZXJ9LlxuICovXG5leHBvcnQgaW50ZXJmYWNlIFdyaXRlRW11bGF0b3JNYXJrZXJQYXJhbXMge1xuICAvKipcbiAgVGhlIEFWRCB0aGUgZW11bGF0b3IgaXMgcnVubmluZy5cbiAgICovXG4gIHJlYWRvbmx5IGF2ZE5hbWU6IHN0cmluZztcblxuICAvKipcbiAgVGhlIGFkYiBzZXJpYWwgb2YgdGhlIGRldmljZSBpdCBzZXJ2ZXM7IG9taXR0ZWQgd2hpbGUgdGhlIGVtdWxhdG9yIGlzIHN0aWxsIGJvb3RpbmcuXG4gICAqL1xuICByZWFkb25seSBkZXZpY2VJZD86IHN0cmluZyB8IHVuZGVmaW5lZDtcblxuICAvKipcbiAgVGhlIGVtdWxhdG9yIHByb2Nlc3NlcyB0aGUgaGFybmVzcyBvd25zLlxuICAgKi9cbiAgcmVhZG9ubHkgb3duZWRFbXVsYXRvclBpZHM6IHJlYWRvbmx5IG51bWJlcltdO1xuXG4gIC8qKlxuICBUaGUgZW11bGF0b3IgcHJvY2Vzc2VzIHRoYXQgcHJlZGF0ZSB0aGlzIGxhdW5jaC4gV3JpdHRlbiBvbmx5IHdoaWxlIHRoZSBiYWNrZW5kJ3Mgb3duIFBJRCBpcyBub3Qga25vd24geWV0LlxuICAgKi9cbiAgcmVhZG9ubHkgcHJlTGF1bmNoRW11bGF0b3JQaWRzPzogcmVhZG9ubHkgbnVtYmVyW10gfCB1bmRlZmluZWQ7XG5cbiAgLyoqXG4gIFdoZW4gdGhlIGVtdWxhdG9yIHdhcyBsYXVuY2hlZDsgb21pdHRlZCBcdTIxOTIgbm93LiBBIHRha2VvdmVyIGtlZXBzIHRoZSBvcmlnaW5hbCBsYXVuY2ggdGltZS5cbiAgICovXG4gIHJlYWRvbmx5IHN0YXJ0ZWRBdEluTWlsbGlzZWNvbmRzPzogbnVtYmVyIHwgdW5kZWZpbmVkO1xufVxuXG4vKipcbiAqIFNheXMgd2hldGhlciBhIG1hcmtlciBzdGlsbCBuYW1lcyBhIHByb2Nlc3MgdGhhdCBpcyBydW5uaW5nLlxuICpcbiAqIEEgc2lnbmFsLTAgcHJvYmUsIG5vdCB0aGUgZW11bGF0b3IgbGlzdGluZyBgcmVzb2x2ZUVtdWxhdG9yTWFya2VyVmVyZGljdGBcbiAqIGludGVyc2VjdHMgd2l0aDogdGhpcyBhbnN3ZXJzIHRoZSBjaGVhcCBxdWVzdGlvbiBcImlzIHRoZXJlIGFueXRoaW5nIGxlZnQgb2ZcbiAqIHdoYXQgdGhpcyBtYXJrZXIgcmVjb3JkZWRcIiwgd2hpY2ggaXMgd2hhdCBkZWNpZGluZyB3aGV0aGVyIGEgbWFya2VyIG1heSBiZVxuICogb3ZlcndyaXR0ZW4gb3IgcmVtb3ZlZCBuZWVkcy4gQ29udmljdGluZyBvbmUgYXMgYW5vdGhlciBydW4ncyBsZWZ0b3ZlciBpcyB0aGVcbiAqIHN0cm9uZ2VyIHF1ZXN0aW9uLCBhbmQgY29zdHMgYSBob3N0IHByb2Nlc3MgbGlzdGluZy5cbiAqXG4gKiBAcGFyYW0gbWFya2VyIC0gVGhlIG1hcmtlciwgb3IgYHVuZGVmaW5lZGAgd2hlbiB0aGVyZSBpcyBub25lLlxuICogQHJldHVybnMgYHRydWVgIHdoZW4gdGhlIG1hcmtlciByZWNvcmRzIGEgUElEIHRoYXQgaXMgc3RpbGwgYWxpdmUuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBjaGVja0lzTWFya2VkRW11bGF0b3JSdW5uaW5nKG1hcmtlcjogRW11bGF0b3JNYXJrZXIgfCB1bmRlZmluZWQpOiBib29sZWFuIHtcbiAgcmV0dXJuIG1hcmtlcj8ub3duZWRFbXVsYXRvclBpZHMuc29tZSgocGlkKSA9PiBjaGVja0lzUHJvY2Vzc0FsaXZlKHBpZCkpID8/IGZhbHNlO1xufVxuXG4vKipcbiAqIFJlbW92ZXMgdGhlIG1hcmtlciBmb3IgYW4gQVZELiBDYWxsZWQgb25jZSBpdHMgZW11bGF0b3IgaXMgdmVyaWZpYWJseSBnb25lLCBzb1xuICogYSBsYXRlciBydW4gY2Fubm90IG1pc3Rha2UgYSByZWN5Y2xlZCBQSUQgZm9yIG91cnMuXG4gKlxuICogQHBhcmFtIGF2ZE5hbWUgLSBUaGUgQVZEIG5hbWUuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBjbGVhckVtdWxhdG9yTWFya2VyKGF2ZE5hbWU6IHN0cmluZyk6IHZvaWQge1xuICB0cnkge1xuICAgIHJtU3luYyhnZXRNYXJrZXJGaWxlUGF0aChhdmROYW1lKSwgeyBmb3JjZTogdHJ1ZSB9KTtcbiAgfSBjYXRjaCAoZXJyb3I6IHVua25vd24pIHtcbiAgICBsb2coYFtlbXVsYXRvci1tYXJrZXJdIENvdWxkIG5vdCByZW1vdmUgdGhlIG1hcmtlciBmb3IgQVZEIFwiJHthdmROYW1lfVwiOiAke2dldEVycm9yTWVzc2FnZShlcnJvcil9YCk7XG4gIH1cbn1cblxuLyoqXG4gKiBSZW1vdmVzIHRoZSBtYXJrZXIgZm9yIGFuIEFWRCBvbmNlIG5vbmUgb2YgdGhlIHByb2Nlc3NlcyBpdCByZWNvcmRzIGlzIHN0aWxsXG4gKiBydW5uaW5nIFx1MjAxNCB0aGUgZm9ybSBhIHN0b3AgdXNlcy5cbiAqXG4gKiBBIHN0b3AgaXMgdmVyaWZpZWQgYWdhaW5zdCB0aGUgUElEcyAqKml0Kiogb3duZWQsIHdoaWNoIGlzIG5vdCBhbHdheXMgdGhlXG4gKiBlbXVsYXRvciB0aGUgbWFya2VyIGRlc2NyaWJlczogYSBsYXVuY2ggdGhhdCBkaWVkIGF0IG9uY2UgKHRoZSBlbXVsYXRvcidzIG93blxuICogYFJ1bm5pbmcgbXVsdGlwbGUgZW11bGF0b3JzIHdpdGggdGhlIHNhbWUgQVZEYCBGQVRBTCkgb3ducyBub3RoaW5nLCB2ZXJpZmllc1xuICogdHJpdmlhbGx5LCBhbmQgbXVzdCBub3QgZXJhc2UgdGhlIHJlY29yZCBvZiBhbiBlbXVsYXRvciB0aGF0IGlzIHN0aWxsIHVwLlxuICpcbiAqIEBwYXJhbSBhdmROYW1lIC0gVGhlIEFWRCBuYW1lLlxuICovXG5leHBvcnQgZnVuY3Rpb24gY2xlYXJFbXVsYXRvck1hcmtlcklmU3RvcHBlZChhdmROYW1lOiBzdHJpbmcpOiB2b2lkIHtcbiAgaWYgKGNoZWNrSXNNYXJrZWRFbXVsYXRvclJ1bm5pbmcocmVhZEVtdWxhdG9yTWFya2VyKGF2ZE5hbWUpKSkge1xuICAgIHJldHVybjtcbiAgfVxuXG4gIGNsZWFyRW11bGF0b3JNYXJrZXIoYXZkTmFtZSk7XG59XG5cbi8qKlxuICogTGlzdHMgZXZlcnkgZW11bGF0b3IgbWFya2VyIG9uIHRoaXMgaG9zdC5cbiAqXG4gKiBAcmV0dXJucyBUaGUgcmVhZGFibGUgbWFya2VycywgaW4gZGlyZWN0b3J5IG9yZGVyLlxuICovXG5leHBvcnQgZnVuY3Rpb24gbGlzdEVtdWxhdG9yTWFya2VycygpOiBFbXVsYXRvck1hcmtlcltdIHtcbiAgbGV0IGZpbGVOYW1lczogc3RyaW5nW107XG4gIHRyeSB7XG4gICAgZmlsZU5hbWVzID0gcmVhZGRpclN5bmMoZ2V0TWFya2VyRGlyZWN0b3J5KCkpO1xuICB9IGNhdGNoIHtcbiAgICByZXR1cm4gW107XG4gIH1cblxuICBjb25zdCBtYXJrZXJzOiBFbXVsYXRvck1hcmtlcltdID0gW107XG4gIGZvciAoY29uc3QgZmlsZU5hbWUgb2YgZmlsZU5hbWVzKSB7XG4gICAgaWYgKCFmaWxlTmFtZS5lbmRzV2l0aChNQVJLRVJfRklMRV9TVUZGSVgpKSB7XG4gICAgICBjb250aW51ZTtcbiAgICB9XG5cbiAgICBjb25zdCBtYXJrZXIgPSByZWFkRW11bGF0b3JNYXJrZXIoZmlsZU5hbWUuc2xpY2UoMCwgLU1BUktFUl9GSUxFX1NVRkZJWC5sZW5ndGgpKTtcbiAgICBpZiAobWFya2VyKSB7XG4gICAgICBtYXJrZXJzLnB1c2gobWFya2VyKTtcbiAgICB9XG4gIH1cblxuICByZXR1cm4gbWFya2Vycztcbn1cblxuLyoqXG4gKiBSZWFkcyB0aGUgbWFya2VyIGZvciBhbiBBVkQuXG4gKlxuICogQHBhcmFtIGF2ZE5hbWUgLSBUaGUgQVZEIG5hbWUuXG4gKiBAcmV0dXJucyBUaGUgbWFya2VyLCBvciBgdW5kZWZpbmVkYCB3aGVuIHRoZXJlIGlzIG5vbmUsIGl0IGlzIHVucmVhZGFibGUsIG9yIGl0IGRlc2NyaWJlcyBhbm90aGVyIEFWRC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHJlYWRFbXVsYXRvck1hcmtlcihhdmROYW1lOiBzdHJpbmcpOiBFbXVsYXRvck1hcmtlciB8IHVuZGVmaW5lZCB7XG4gIGxldCBwYXJzZWQ6IHVua25vd247XG4gIHRyeSB7XG4gICAgcGFyc2VkID0gSlNPTi5wYXJzZShyZWFkRmlsZVN5bmMoZ2V0TWFya2VyRmlsZVBhdGgoYXZkTmFtZSksICd1dGYtOCcpKTtcbiAgfSBjYXRjaCB7XG4gICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgfVxuXG4gIHJldHVybiBwYXJzZU1hcmtlcihwYXJzZWQsIGF2ZE5hbWUpO1xufVxuXG4vKipcbiAqIERlY2lkZXMgd2hhdCBhIG1hcmtlciBzYXlzIGFib3V0IHRoZSBlbXVsYXRvciBpdCBkZXNjcmliZXMuXG4gKlxuICogRXZpZGVuY2UgZmlyc3Q6IGEgbWFya2VyIG5vbmUgb2Ygd2hvc2UgUElEcyBpcyBhIGxpdmUgZW11bGF0b3IgaXMgc3RhbGUsXG4gKiB3aG9ldmVyIG93bnMgaXQuIE9ubHkgdGhlbiBkb2VzIG93bmVyc2hpcCBtYXR0ZXIgXHUyMDE0IGFuZCBhIGxpdmUgb3duZXIgdGhhdCBpc1xuICogbm90IHRoZSBjYWxsZXIga2VlcHMgaXQsIGJlY2F1c2UgdGhhdCBydW4gd2lsbCBzdG9wIGl0IGl0c2VsZi5cbiAqXG4gKiBAcGFyYW0gcGFyYW1zIC0gVGhlIG1hcmtlciwgdGhlIGhvc3QncyBsaXZlIGVtdWxhdG9yIFBJRHMsIGFuZCB3aG8gaXMgYXNraW5nLlxuICogQHJldHVybnMgVGhlIHZlcmRpY3QuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiByZXNvbHZlRW11bGF0b3JNYXJrZXJWZXJkaWN0KHBhcmFtczogUmVzb2x2ZUVtdWxhdG9yTWFya2VyVmVyZGljdFBhcmFtcyk6IEVtdWxhdG9yTWFya2VyVmVyZGljdCB7XG4gIGlmIChzZWxlY3RMaXZlTWFya2VkUGlkcyhwYXJhbXMubWFya2VyLCBwYXJhbXMubGl2ZUVtdWxhdG9yUGlkcykubGVuZ3RoID09PSAwKSB7XG4gICAgcmV0dXJuICdzdGFsZS1tYXJrZXInO1xuICB9XG5cbiAgaWYgKHBhcmFtcy5tYXJrZXIub3duZXJQaWQgIT09IHBhcmFtcy5jdXJyZW50UGlkICYmIHBhcmFtcy5pc093bmVyQWxpdmUpIHtcbiAgICByZXR1cm4gJ2luLXVzZS1ieS1saXZlLXJ1bic7XG4gIH1cblxuICByZXR1cm4gJ2hhcm5lc3MtbGVmdG92ZXInO1xufVxuXG4vKipcbiAqIFNlbGVjdHMgdGhlIGVtdWxhdG9yIHByb2Nlc3NlcyBhIG1hcmtlciBjb252aWN0czogdGhlIFBJRHMgaXQgcmVjb3JkcyB0aGF0XG4gKiBhcmUgc3RpbGwgcnVubmluZywgcGx1cyBcdTIwMTQgd2hpbGUgdGhlIHJlY29yZCBpcyBzdGlsbCBwcm92aXNpb25hbCBcdTIwMTQgd2hhdGV2ZXJcbiAqIHRoZSBsYXVuY2ggaXQgZGVzY3JpYmVzIGhhcyBmb3JrZWQgc2luY2UuXG4gKlxuICogVGhlIHNlY29uZCBoYWxmIGlzIHdoYXQgbWFrZXMgYSBsYXVuY2gtdGltZSBtYXJrZXIgdXNhYmxlLiBJdCBuYW1lcyB0aGVcbiAqIGxhdW5jaGVyLCBhbmQga2lsbGluZyB0aGUgbGF1bmNoZXIgbGVhdmVzIHRoZSBRRU1VIGJhY2tlbmQgcnVubmluZ1xuICogKCoqTDQ2KiopLCBzbyB3aXRob3V0IHRoZSBgcHJlTGF1bmNoRW11bGF0b3JQaWRzYCBkaWZmIGEgc3RvcCB3b3VsZCBraWxsIHRoZVxuICogb25lIHByb2Nlc3MgdGhhdCBkb2VzIG5vdCBob2xkIHRoZSBBVkQgYW5kIHRoZSBtYXJrZXIgd291bGQgdGhlbiByZWFkIGFzXG4gKiBzdGFsZSBvdmVyIGEgbGl2ZSBlbXVsYXRvci4gVGhlIGRpZmYgY2xhaW1zIGV4YWN0bHkgd2hhdCB0aGUgc3VjY2Vzc2Z1bCBwYXRoXG4gKiBjbGFpbXMgXHUyMDE0IHRoZSBlbXVsYXRvciBwcm9jZXNzZXMgdGhhdCBhcHBlYXJlZCBhY3Jvc3MgdGhpcyBsYXVuY2ggXHUyMDE0IGFuZCBuZXZlclxuICogb25lIHRoYXQgcHJlZGF0ZXMgaXQuXG4gKlxuICogQHBhcmFtIG1hcmtlciAtIFRoZSBtYXJrZXIuXG4gKiBAcGFyYW0gbGl2ZUVtdWxhdG9yUGlkcyAtIFRoZSBlbXVsYXRvciBwcm9jZXNzZXMgY3VycmVudGx5IHJ1bm5pbmcgb24gdGhlIGhvc3QuXG4gKiBAcmV0dXJucyBUaGUgbWFya2VkIFBJRHMgc3RpbGwgcnVubmluZywgaW4gbWFya2VyIG9yZGVyLCB0aGVuIGFueSB0aGlzIGxhdW5jaCBmb3JrZWQsIGluIGxpc3RlZCBvcmRlci5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHNlbGVjdExpdmVNYXJrZWRQaWRzKG1hcmtlcjogRW11bGF0b3JNYXJrZXIsIGxpdmVFbXVsYXRvclBpZHM6IHJlYWRvbmx5IG51bWJlcltdKTogbnVtYmVyW10ge1xuICBjb25zdCBsaXZlUGlkcyA9IG1hcmtlci5vd25lZEVtdWxhdG9yUGlkcy5maWx0ZXIoKHBpZCkgPT4gbGl2ZUVtdWxhdG9yUGlkcy5pbmNsdWRlcyhwaWQpKTtcbiAgY29uc3QgeyBwcmVMYXVuY2hFbXVsYXRvclBpZHMgfSA9IG1hcmtlcjtcbiAgaWYgKHByZUxhdW5jaEVtdWxhdG9yUGlkcyA9PT0gdW5kZWZpbmVkKSB7XG4gICAgcmV0dXJuIGxpdmVQaWRzO1xuICB9XG5cbiAgcmV0dXJuIFsuLi5saXZlUGlkcywgLi4ubGl2ZUVtdWxhdG9yUGlkcy5maWx0ZXIoKHBpZCkgPT4gIXByZUxhdW5jaEVtdWxhdG9yUGlkcy5pbmNsdWRlcyhwaWQpICYmICFsaXZlUGlkcy5pbmNsdWRlcyhwaWQpKV07XG59XG5cbi8qKlxuICogUmVjb3JkcyB0aGF0IHRoZSBjdXJyZW50IHByb2Nlc3MgaXMgcmVzcG9uc2libGUgZm9yIHN0b3BwaW5nIGFuIEFWRCdzXG4gKiBlbXVsYXRvciBcdTIwMTQgb25lIGl0IGp1c3Qgc3RhcnRlZCwgb3IgYSBsZWZ0b3ZlciBpdCBpcyB0YWtpbmcgb3Zlci5cbiAqXG4gKiBAcGFyYW0gcGFyYW1zIC0gVGhlIGVtdWxhdG9yJ3MgQVZELCBkZXZpY2UgYW5kIFBJRHMuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiB3cml0ZUVtdWxhdG9yTWFya2VyKHBhcmFtczogV3JpdGVFbXVsYXRvck1hcmtlclBhcmFtcyk6IHZvaWQge1xuICBjb25zdCBtYXJrZXI6IEVtdWxhdG9yTWFya2VyID0ge1xuICAgIGF2ZE5hbWU6IHBhcmFtcy5hdmROYW1lLFxuICAgIC8vIE9taXR0ZWQgcmF0aGVyIHRoYW4gd3JpdHRlbiBlbXB0eSB3aGlsZSB0aGUgZW11bGF0b3IgaXMgc3RpbGwgYm9vdGluZzogdGhlIHN0b3AgcmVhZHMgaXRzIGFic2VuY2UgYXMgXCJubyBjb25zb2xlIHRvIHNodXQgZG93blwiLlxuICAgIC4uLihwYXJhbXMuZGV2aWNlSWQgIT09IHVuZGVmaW5lZCAmJiB7IGRldmljZUlkOiBwYXJhbXMuZGV2aWNlSWQgfSksXG4gICAgb3duZWRFbXVsYXRvclBpZHM6IFsuLi5wYXJhbXMub3duZWRFbXVsYXRvclBpZHNdLFxuICAgIG93bmVyUGlkOiBwcm9jZXNzLnBpZCxcbiAgICAvLyBEcm9wcGVkIGJ5IHRoZSB3cml0ZSB0aGF0IGxlYXJucyB0aGUgYmFja2VuZCdzIFBJRDogZnJvbSB0aGVuIG9uIHRoZSBvd25lZCBzZXQgaXMgZXhhY3QgYW5kIGEgZGlmZiB3b3VsZCBvbmx5IHdpZGVuIGl0LlxuICAgIC4uLihwYXJhbXMucHJlTGF1bmNoRW11bGF0b3JQaWRzICE9PSB1bmRlZmluZWQgJiYgeyBwcmVMYXVuY2hFbXVsYXRvclBpZHM6IFsuLi5wYXJhbXMucHJlTGF1bmNoRW11bGF0b3JQaWRzXSB9KSxcbiAgICBzdGFydGVkQXRJbk1pbGxpc2Vjb25kczogcGFyYW1zLnN0YXJ0ZWRBdEluTWlsbGlzZWNvbmRzID8/IERhdGUubm93KClcbiAgfTtcblxuICB0cnkge1xuICAgIG1rZGlyU3luYyhnZXRNYXJrZXJEaXJlY3RvcnkoKSwgeyByZWN1cnNpdmU6IHRydWUgfSk7XG4gICAgd3JpdGVGaWxlU3luYyhnZXRNYXJrZXJGaWxlUGF0aChtYXJrZXIuYXZkTmFtZSksIEpTT04uc3RyaW5naWZ5KG1hcmtlcikpO1xuICB9IGNhdGNoIChlcnJvcjogdW5rbm93bikge1xuICAgIGxvZyhgW2VtdWxhdG9yLW1hcmtlcl0gQ291bGQgbm90IHJlY29yZCB0aGUgZW11bGF0b3IgZm9yIEFWRCBcIiR7bWFya2VyLmF2ZE5hbWV9XCI6ICR7Z2V0RXJyb3JNZXNzYWdlKGVycm9yKX1gKTtcbiAgfVxufVxuXG5mdW5jdGlvbiBjaGVja0lzTnVtYmVyQXJyYXkodmFsdWU6IHVua25vd24pOiB2YWx1ZSBpcyBudW1iZXJbXSB7XG4gIHJldHVybiBBcnJheS5pc0FycmF5KHZhbHVlKSAmJiB2YWx1ZS5ldmVyeSgoaXRlbSkgPT4gdHlwZW9mIGl0ZW0gPT09ICdudW1iZXInKTtcbn1cblxuZnVuY3Rpb24gZ2V0RXJyb3JNZXNzYWdlKGVycm9yOiB1bmtub3duKTogc3RyaW5nIHtcbiAgcmV0dXJuIGVycm9yIGluc3RhbmNlb2YgRXJyb3IgPyBlcnJvci5tZXNzYWdlIDogU3RyaW5nKGVycm9yKTtcbn1cblxuZnVuY3Rpb24gZ2V0TWFya2VyRGlyZWN0b3J5KCk6IHN0cmluZyB7XG4gIHJldHVybiBqb2luKHRtcGRpcigpLCBIQVJORVNTX1RFTVBfRElSX05BTUUpO1xufVxuXG5mdW5jdGlvbiBnZXRNYXJrZXJGaWxlUGF0aChhdmROYW1lOiBzdHJpbmcpOiBzdHJpbmcge1xuICByZXR1cm4gam9pbihnZXRNYXJrZXJEaXJlY3RvcnkoKSwgYCR7YXZkTmFtZX0ke01BUktFUl9GSUxFX1NVRkZJWH1gKTtcbn1cblxuZnVuY3Rpb24gcGFyc2VNYXJrZXIocGFyc2VkOiB1bmtub3duLCBhdmROYW1lOiBzdHJpbmcpOiBFbXVsYXRvck1hcmtlciB8IHVuZGVmaW5lZCB7XG4gIGlmICh0eXBlb2YgcGFyc2VkICE9PSAnb2JqZWN0JyB8fCBwYXJzZWQgPT09IG51bGwpIHtcbiAgICByZXR1cm4gdW5kZWZpbmVkO1xuICB9XG5cbiAgY29uc3QgcmVjb3JkID0gcGFyc2VkIGFzIFJlY29yZDxzdHJpbmcsIHVua25vd24+O1xuICBjb25zdCB7IGF2ZE5hbWU6IG1hcmtlZEF2ZE5hbWUsIGRldmljZUlkLCBvd25lZEVtdWxhdG9yUGlkcywgb3duZXJQaWQsIHByZUxhdW5jaEVtdWxhdG9yUGlkcywgc3RhcnRlZEF0SW5NaWxsaXNlY29uZHMgfSA9IHJlY29yZDtcblxuICBpZiAoXG4gICAgbWFya2VkQXZkTmFtZSAhPT0gYXZkTmFtZVxuICAgIHx8IChkZXZpY2VJZCAhPT0gdW5kZWZpbmVkICYmIHR5cGVvZiBkZXZpY2VJZCAhPT0gJ3N0cmluZycpXG4gICAgfHwgdHlwZW9mIG93bmVyUGlkICE9PSAnbnVtYmVyJ1xuICAgIHx8IHR5cGVvZiBzdGFydGVkQXRJbk1pbGxpc2Vjb25kcyAhPT0gJ251bWJlcidcbiAgICB8fCAhY2hlY2tJc051bWJlckFycmF5KG93bmVkRW11bGF0b3JQaWRzKVxuICAgIHx8IChwcmVMYXVuY2hFbXVsYXRvclBpZHMgIT09IHVuZGVmaW5lZCAmJiAhY2hlY2tJc051bWJlckFycmF5KHByZUxhdW5jaEVtdWxhdG9yUGlkcykpXG4gICkge1xuICAgIHJldHVybiB1bmRlZmluZWQ7XG4gIH1cblxuICByZXR1cm4ge1xuICAgIGF2ZE5hbWUsXG4gICAgLi4uKGRldmljZUlkICE9PSB1bmRlZmluZWQgJiYgeyBkZXZpY2VJZCB9KSxcbiAgICBvd25lZEVtdWxhdG9yUGlkcyxcbiAgICBvd25lclBpZCxcbiAgICAuLi4ocHJlTGF1bmNoRW11bGF0b3JQaWRzICE9PSB1bmRlZmluZWQgJiYgeyBwcmVMYXVuY2hFbXVsYXRvclBpZHMgfSksXG4gICAgc3RhcnRlZEF0SW5NaWxsaXNlY29uZHNcbiAgfTtcbn1cbiJdLAogICJtYXBwaW5ncyI6ICI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUE0Q0EscUJBTU87QUFDUCxxQkFBdUI7QUFDdkIsdUJBQXFCO0FBQ3JCLDBCQUFvQjtBQUVwQiw4QkFBc0M7QUFDdEMsaUJBQW9CO0FBQ3BCLDhCQUFvQztBQUVwQyxNQUFNLHFCQUFxQjtBQWtJcEIsU0FBUyw2QkFBNkIsUUFBNkM7QUFDeEYsU0FBTyxRQUFRLGtCQUFrQixLQUFLLENBQUMsWUFBUSw2Q0FBb0IsR0FBRyxDQUFDLEtBQUs7QUFDOUU7QUFRTyxTQUFTLG9CQUFvQixTQUF1QjtBQUN6RCxNQUFJO0FB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
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file
|
|
3
|
+
*
|
|
4
|
+
* Records which emulator this harness started for an AVD, so the emulator
|
|
5
|
+
* stays **the harness's** until a stop of it has been verified — whichever run,
|
|
6
|
+
* or whichever process of a run, gets to that stop.
|
|
7
|
+
*
|
|
8
|
+
* Ownership used to live only in the memory of the run that launched the
|
|
9
|
+
* emulator, and three ordinary things lose that memory:
|
|
10
|
+
*
|
|
11
|
+
* - **The launching process is a test worker.** A project with no transport
|
|
12
|
+
* global setup creates its transport — and auto-starts its emulator — inside a
|
|
13
|
+
* Vitest worker, and Vitest ends workers abruptly, so the worker's teardown
|
|
14
|
+
* never gets a turn. The project's own global teardown runs, but held no
|
|
15
|
+
* handle to the emulator. This repo's `integration-tests:android`
|
|
16
|
+
* project is exactly this, and on 2026-09-10 the emulator it left idle kept
|
|
17
|
+
* `netsimd` writing until a 278 GB log filled the drive (**L56**).
|
|
18
|
+
* - **The runner is killed** — SIGKILL, Task Manager, an IDE stop button — and
|
|
19
|
+
* neither teardown path runs at all.
|
|
20
|
+
* - **A later run adopts the leftover.** A device this run did not start is
|
|
21
|
+
* correctly not this run's to stop — which, without a record, made every
|
|
22
|
+
* leftover nobody's to stop, for ever.
|
|
23
|
+
*
|
|
24
|
+
* The marker is the record: a small JSON sentinel per AVD next to the setup lock,
|
|
25
|
+
* written from the moment the emulator is launched and removed only by a verified
|
|
26
|
+
* stop. It is deliberately tolerant — a missing, unreadable or mismatched marker
|
|
27
|
+
* reads as "not ours", the same answer the harness gave before markers existed.
|
|
28
|
+
*
|
|
29
|
+
* **It records a launch, not a booted emulator**, and that is the difference
|
|
30
|
+
* between covering the third case above and covering it only after the ~45s a
|
|
31
|
+
* cold boot takes. A launch-time marker names the `emulator` launcher alone and
|
|
32
|
+
* no device yet; the device id arrives when one appears, and the QEMU backend's
|
|
33
|
+
* PID once the boot completes. Each write keeps the original launch time, so the
|
|
34
|
+
* reaper armed by the first still recognizes the last as the same emulator.
|
|
35
|
+
*
|
|
36
|
+
* What makes a marker's emulator ours is evidence, not the file: at least one
|
|
37
|
+
* PID it records must still be a **live emulator process** on the host (the
|
|
38
|
+
* caller intersects with the process listing, not a bare signal-0 probe), so a
|
|
39
|
+
* recycled PID or an emulator somebody booted by hand is never mistaken for one
|
|
40
|
+
* this harness started. A launch-time marker satisfies that as written, because
|
|
41
|
+
* `emulator-backend.ts` counts the `emulator` launcher as one of the emulator
|
|
42
|
+
* processes — which is why phasing the marker needed no change to the verdict.
|
|
43
|
+
*/
|
|
44
|
+
/**
|
|
45
|
+
* Identifies the emulator this harness started for an AVD.
|
|
46
|
+
*/
|
|
47
|
+
export interface EmulatorMarker {
|
|
48
|
+
/**
|
|
49
|
+
The AVD the emulator is running.
|
|
50
|
+
*/
|
|
51
|
+
readonly avdName: string;
|
|
52
|
+
/**
|
|
53
|
+
* The adb serial of the device it serves, e.g. `emulator-5554`. Absent while
|
|
54
|
+
* the emulator is still booting and no device has appeared yet — a marker is
|
|
55
|
+
* written from the launch, before there is anything to name.
|
|
56
|
+
*/
|
|
57
|
+
readonly deviceId?: string | undefined;
|
|
58
|
+
/**
|
|
59
|
+
* The emulator processes the harness owns: the launcher plus the QEMU backend
|
|
60
|
+
* it forked — or, until the device appears, the launcher alone.
|
|
61
|
+
*/
|
|
62
|
+
readonly ownedEmulatorPids: readonly number[];
|
|
63
|
+
/**
|
|
64
|
+
* PID of the harness process responsible for stopping the emulator — the one
|
|
65
|
+
* that started it, or the one that later took it over as a leftover.
|
|
66
|
+
*/
|
|
67
|
+
readonly ownerPid: number;
|
|
68
|
+
/**
|
|
69
|
+
* The emulator processes that were already running when this launch spawned
|
|
70
|
+
* its own — present only while the record is still provisional, i.e. before
|
|
71
|
+
* the backend's own PID is known.
|
|
72
|
+
*
|
|
73
|
+
* It is what makes a launch-time marker convictable at all. Killing the
|
|
74
|
+
* launcher does **not** kill the `qemu-system-*-headless` backend it forked
|
|
75
|
+
* (**L46**), so a record naming the launcher alone goes stale over an
|
|
76
|
+
* emulator that is still running — the leak this marker exists to prevent.
|
|
77
|
+
* With the snapshot, anything in the host's emulator listing that is not in
|
|
78
|
+
* it was forked by this launch, which is the same pre-launch diff the
|
|
79
|
+
* successful path uses (`emulator-backend.ts`) and the same one **L46**
|
|
80
|
+
* requires instead of a `qemu*` sweep.
|
|
81
|
+
*/
|
|
82
|
+
readonly preLaunchEmulatorPids?: readonly number[] | undefined;
|
|
83
|
+
/**
|
|
84
|
+
When the emulator was started (`Date.now()` epoch milliseconds).
|
|
85
|
+
*/
|
|
86
|
+
readonly startedAtInMilliseconds: number;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* What a marker says about the emulator it describes.
|
|
90
|
+
*
|
|
91
|
+
* - `harness-leftover` — ours, and no live harness process is responsible for
|
|
92
|
+
* it: the caller may take it over or stop it.
|
|
93
|
+
* - `in-use-by-live-run` — ours, but another live harness process owns it.
|
|
94
|
+
* - `stale-marker` — none of its PIDs is a live emulator any more; the marker
|
|
95
|
+
* outlived its emulator and proves nothing.
|
|
96
|
+
*/
|
|
97
|
+
export type EmulatorMarkerVerdict = 'harness-leftover' | 'in-use-by-live-run' | 'stale-marker';
|
|
98
|
+
/**
|
|
99
|
+
* Parameters for {@link resolveEmulatorMarkerVerdict}.
|
|
100
|
+
*/
|
|
101
|
+
export interface ResolveEmulatorMarkerVerdictParams {
|
|
102
|
+
/**
|
|
103
|
+
PID of the process asking.
|
|
104
|
+
*/
|
|
105
|
+
readonly currentPid: number;
|
|
106
|
+
/**
|
|
107
|
+
Whether the marker's owner process is still running.
|
|
108
|
+
*/
|
|
109
|
+
readonly isOwnerAlive: boolean;
|
|
110
|
+
/**
|
|
111
|
+
The emulator processes currently running on the host.
|
|
112
|
+
*/
|
|
113
|
+
readonly liveEmulatorPids: readonly number[];
|
|
114
|
+
/**
|
|
115
|
+
The marker to judge.
|
|
116
|
+
*/
|
|
117
|
+
readonly marker: EmulatorMarker;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Parameters for {@link writeEmulatorMarker}.
|
|
121
|
+
*/
|
|
122
|
+
export interface WriteEmulatorMarkerParams {
|
|
123
|
+
/**
|
|
124
|
+
The AVD the emulator is running.
|
|
125
|
+
*/
|
|
126
|
+
readonly avdName: string;
|
|
127
|
+
/**
|
|
128
|
+
The adb serial of the device it serves; omitted while the emulator is still booting.
|
|
129
|
+
*/
|
|
130
|
+
readonly deviceId?: string | undefined;
|
|
131
|
+
/**
|
|
132
|
+
The emulator processes the harness owns.
|
|
133
|
+
*/
|
|
134
|
+
readonly ownedEmulatorPids: readonly number[];
|
|
135
|
+
/**
|
|
136
|
+
The emulator processes that predate this launch. Written only while the backend's own PID is not known yet.
|
|
137
|
+
*/
|
|
138
|
+
readonly preLaunchEmulatorPids?: readonly number[] | undefined;
|
|
139
|
+
/**
|
|
140
|
+
When the emulator was launched; omitted → now. A takeover keeps the original launch time.
|
|
141
|
+
*/
|
|
142
|
+
readonly startedAtInMilliseconds?: number | undefined;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Says whether a marker still names a process that is running.
|
|
146
|
+
*
|
|
147
|
+
* A signal-0 probe, not the emulator listing `resolveEmulatorMarkerVerdict`
|
|
148
|
+
* intersects with: this answers the cheap question "is there anything left of
|
|
149
|
+
* what this marker recorded", which is what deciding whether a marker may be
|
|
150
|
+
* overwritten or removed needs. Convicting one as another run's leftover is the
|
|
151
|
+
* stronger question, and costs a host process listing.
|
|
152
|
+
*
|
|
153
|
+
* @param marker - The marker, or `undefined` when there is none.
|
|
154
|
+
* @returns `true` when the marker records a PID that is still alive.
|
|
155
|
+
*/
|
|
156
|
+
export declare function checkIsMarkedEmulatorRunning(marker: EmulatorMarker | undefined): boolean;
|
|
157
|
+
/**
|
|
158
|
+
* Removes the marker for an AVD. Called once its emulator is verifiably gone, so
|
|
159
|
+
* a later run cannot mistake a recycled PID for ours.
|
|
160
|
+
*
|
|
161
|
+
* @param avdName - The AVD name.
|
|
162
|
+
*/
|
|
163
|
+
export declare function clearEmulatorMarker(avdName: string): void;
|
|
164
|
+
/**
|
|
165
|
+
* Removes the marker for an AVD once none of the processes it records is still
|
|
166
|
+
* running — the form a stop uses.
|
|
167
|
+
*
|
|
168
|
+
* A stop is verified against the PIDs **it** owned, which is not always the
|
|
169
|
+
* emulator the marker describes: a launch that died at once (the emulator's own
|
|
170
|
+
* `Running multiple emulators with the same AVD` FATAL) owns nothing, verifies
|
|
171
|
+
* trivially, and must not erase the record of an emulator that is still up.
|
|
172
|
+
*
|
|
173
|
+
* @param avdName - The AVD name.
|
|
174
|
+
*/
|
|
175
|
+
export declare function clearEmulatorMarkerIfStopped(avdName: string): void;
|
|
176
|
+
/**
|
|
177
|
+
* Lists every emulator marker on this host.
|
|
178
|
+
*
|
|
179
|
+
* @returns The readable markers, in directory order.
|
|
180
|
+
*/
|
|
181
|
+
export declare function listEmulatorMarkers(): EmulatorMarker[];
|
|
182
|
+
/**
|
|
183
|
+
* Reads the marker for an AVD.
|
|
184
|
+
*
|
|
185
|
+
* @param avdName - The AVD name.
|
|
186
|
+
* @returns The marker, or `undefined` when there is none, it is unreadable, or it describes another AVD.
|
|
187
|
+
*/
|
|
188
|
+
export declare function readEmulatorMarker(avdName: string): EmulatorMarker | undefined;
|
|
189
|
+
/**
|
|
190
|
+
* Decides what a marker says about the emulator it describes.
|
|
191
|
+
*
|
|
192
|
+
* Evidence first: a marker none of whose PIDs is a live emulator is stale,
|
|
193
|
+
* whoever owns it. Only then does ownership matter — and a live owner that is
|
|
194
|
+
* not the caller keeps it, because that run will stop it itself.
|
|
195
|
+
*
|
|
196
|
+
* @param params - The marker, the host's live emulator PIDs, and who is asking.
|
|
197
|
+
* @returns The verdict.
|
|
198
|
+
*/
|
|
199
|
+
export declare function resolveEmulatorMarkerVerdict(params: ResolveEmulatorMarkerVerdictParams): EmulatorMarkerVerdict;
|
|
200
|
+
/**
|
|
201
|
+
* Selects the emulator processes a marker convicts: the PIDs it records that
|
|
202
|
+
* are still running, plus — while the record is still provisional — whatever
|
|
203
|
+
* the launch it describes has forked since.
|
|
204
|
+
*
|
|
205
|
+
* The second half is what makes a launch-time marker usable. It names the
|
|
206
|
+
* launcher, and killing the launcher leaves the QEMU backend running
|
|
207
|
+
* (**L46**), so without the `preLaunchEmulatorPids` diff a stop would kill the
|
|
208
|
+
* one process that does not hold the AVD and the marker would then read as
|
|
209
|
+
* stale over a live emulator. The diff claims exactly what the successful path
|
|
210
|
+
* claims — the emulator processes that appeared across this launch — and never
|
|
211
|
+
* one that predates it.
|
|
212
|
+
*
|
|
213
|
+
* @param marker - The marker.
|
|
214
|
+
* @param liveEmulatorPids - The emulator processes currently running on the host.
|
|
215
|
+
* @returns The marked PIDs still running, in marker order, then any this launch forked, in listed order.
|
|
216
|
+
*/
|
|
217
|
+
export declare function selectLiveMarkedPids(marker: EmulatorMarker, liveEmulatorPids: readonly number[]): number[];
|
|
218
|
+
/**
|
|
219
|
+
* Records that the current process is responsible for stopping an AVD's
|
|
220
|
+
* emulator — one it just started, or a leftover it is taking over.
|
|
221
|
+
*
|
|
222
|
+
* @param params - The emulator's AVD, device and PIDs.
|
|
223
|
+
*/
|
|
224
|
+
export declare function writeEmulatorMarker(params: WriteEmulatorMarkerParams): void;
|