obsidian-integration-testing 12.1.2 → 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/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 +69 -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/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 +72 -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
|
@@ -0,0 +1,248 @@
|
|
|
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_reaper_exports = {};
|
|
30
|
+
__export(emulator_reaper_exports, {
|
|
31
|
+
EMULATOR_REAPER_ENTRY_NAME: () => EMULATOR_REAPER_ENTRY_NAME,
|
|
32
|
+
EMULATOR_REAPER_LOCK_SILENCE_IN_MILLISECONDS: () => EMULATOR_REAPER_LOCK_SILENCE_IN_MILLISECONDS,
|
|
33
|
+
EMULATOR_REAPER_LOG_MAX_SIZE_IN_BYTES: () => EMULATOR_REAPER_LOG_MAX_SIZE_IN_BYTES,
|
|
34
|
+
EMULATOR_REAPER_POLL_INTERVAL_IN_MILLISECONDS: () => EMULATOR_REAPER_POLL_INTERVAL_IN_MILLISECONDS,
|
|
35
|
+
buildEmulatorReaperArguments: () => buildEmulatorReaperArguments,
|
|
36
|
+
buildEmulatorReaperBootstrap: () => buildEmulatorReaperBootstrap,
|
|
37
|
+
buildEmulatorReaperRelayBootstrap: () => buildEmulatorReaperRelayBootstrap,
|
|
38
|
+
parseEmulatorReaperArguments: () => parseEmulatorReaperArguments,
|
|
39
|
+
resolveEmulatorReaperLogOpenFlag: () => resolveEmulatorReaperLogOpenFlag,
|
|
40
|
+
resolveEmulatorReaperWatch: () => resolveEmulatorReaperWatch,
|
|
41
|
+
runEmulatorReaper: () => runEmulatorReaper,
|
|
42
|
+
spawnEmulatorReaper: () => spawnEmulatorReaper
|
|
43
|
+
});
|
|
44
|
+
module.exports = __toCommonJS(emulator_reaper_exports);
|
|
45
|
+
var import_node_child_process = require("node:child_process");
|
|
46
|
+
var import_node_fs = require("node:fs");
|
|
47
|
+
var import_node_os = require("node:os");
|
|
48
|
+
var import_node_path = require("node:path");
|
|
49
|
+
var import_node_process = __toESM(require("node:process"), 1);
|
|
50
|
+
var import_node_url = require("node:url");
|
|
51
|
+
var import_emulator_marker = require("./emulator-marker.cjs");
|
|
52
|
+
var import_emulator_reclaim = require("./emulator-reclaim.cjs");
|
|
53
|
+
var import_leftover_cleanup = require("./leftover-cleanup.cjs");
|
|
54
|
+
var import_log = require("./log.cjs");
|
|
55
|
+
var import_process_liveness = require("./process-liveness.cjs");
|
|
56
|
+
var import_setup_lock = require("./setup-lock.cjs");
|
|
57
|
+
const import_meta = {};
|
|
58
|
+
const EMULATOR_REAPER_POLL_INTERVAL_IN_MILLISECONDS = 5e3;
|
|
59
|
+
const EMULATOR_REAPER_LOCK_SILENCE_IN_MILLISECONDS = 18e5;
|
|
60
|
+
const EMULATOR_REAPER_LOG_MAX_SIZE_IN_BYTES = 1048576;
|
|
61
|
+
const EMULATOR_REAPER_ENTRY_NAME = "runEmulatorReaper";
|
|
62
|
+
const LOCK_LABEL = "emulator-reaper";
|
|
63
|
+
const REAPER_LOG_FILE_SUFFIX = ".emulator-reaper.log";
|
|
64
|
+
function buildEmulatorReaperArguments(reaperArguments) {
|
|
65
|
+
return [reaperArguments.avdName, String(reaperArguments.startedAtInMilliseconds)];
|
|
66
|
+
}
|
|
67
|
+
function buildEmulatorReaperBootstrap() {
|
|
68
|
+
return [
|
|
69
|
+
"import(process.argv[1]).then((reaperModule) => {",
|
|
70
|
+
` const run = reaperModule.${EMULATOR_REAPER_ENTRY_NAME} ?? reaperModule.default.${EMULATOR_REAPER_ENTRY_NAME};`,
|
|
71
|
+
" return run(process.argv.slice(2));",
|
|
72
|
+
"}).catch((error) => {",
|
|
73
|
+
" console.error(error);",
|
|
74
|
+
" process.exitCode = 1;",
|
|
75
|
+
"});"
|
|
76
|
+
].join("\n");
|
|
77
|
+
}
|
|
78
|
+
function buildEmulatorReaperRelayBootstrap() {
|
|
79
|
+
return [
|
|
80
|
+
'const { spawn } = require("node:child_process");',
|
|
81
|
+
"try {",
|
|
82
|
+
' const child = spawn(process.execPath, ["-e", process.argv[1], ...process.argv.slice(2)], {',
|
|
83
|
+
" detached: true,",
|
|
84
|
+
' stdio: ["ignore", 1, 2],',
|
|
85
|
+
" windowsHide: true",
|
|
86
|
+
" });",
|
|
87
|
+
' child.once("error", (error) => { console.error(error); });',
|
|
88
|
+
" child.unref();",
|
|
89
|
+
"} catch (error) {",
|
|
90
|
+
" console.error(error);",
|
|
91
|
+
" process.exitCode = 1;",
|
|
92
|
+
"}"
|
|
93
|
+
].join("\n");
|
|
94
|
+
}
|
|
95
|
+
function parseEmulatorReaperArguments(argv) {
|
|
96
|
+
const [avdName, startedAtText, ...rest] = argv;
|
|
97
|
+
if (!avdName || startedAtText === void 0 || rest.length > 0) {
|
|
98
|
+
return void 0;
|
|
99
|
+
}
|
|
100
|
+
const startedAtInMilliseconds = Number(startedAtText);
|
|
101
|
+
if (!Number.isSafeInteger(startedAtInMilliseconds) || startedAtInMilliseconds <= 0) {
|
|
102
|
+
return void 0;
|
|
103
|
+
}
|
|
104
|
+
return { avdName, startedAtInMilliseconds };
|
|
105
|
+
}
|
|
106
|
+
function resolveEmulatorReaperLogOpenFlag(sizeInBytes) {
|
|
107
|
+
return sizeInBytes !== void 0 && sizeInBytes > EMULATOR_REAPER_LOG_MAX_SIZE_IN_BYTES ? "w" : "a";
|
|
108
|
+
}
|
|
109
|
+
function resolveEmulatorReaperWatch(params) {
|
|
110
|
+
const { marker } = params;
|
|
111
|
+
if (marker === void 0) {
|
|
112
|
+
return "emulator-stopped";
|
|
113
|
+
}
|
|
114
|
+
if (marker.startedAtInMilliseconds !== params.armedStartedAtInMilliseconds) {
|
|
115
|
+
return "superseded";
|
|
116
|
+
}
|
|
117
|
+
if (marker.preLaunchEmulatorPids !== void 0) {
|
|
118
|
+
return "watch";
|
|
119
|
+
}
|
|
120
|
+
return marker.ownedEmulatorPids.some((pid) => params.checkIsPidAlive(pid)) ? "watch" : "emulator-stopped";
|
|
121
|
+
}
|
|
122
|
+
async function runEmulatorReaper(argv) {
|
|
123
|
+
const reaperArguments = parseEmulatorReaperArguments(argv);
|
|
124
|
+
if (!reaperArguments) {
|
|
125
|
+
(0, import_log.log)(`[emulator-reaper] Refusing to run: expected <avdName> <startedAtInMilliseconds>, got ${JSON.stringify(argv)}.`);
|
|
126
|
+
import_node_process.default.exitCode = 1;
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const { avdName, startedAtInMilliseconds } = reaperArguments;
|
|
130
|
+
reaperLog(
|
|
131
|
+
`Watching the emulator started at ${new Date(startedAtInMilliseconds).toISOString()} (reaper PID ${String(import_node_process.default.pid)}); it is stopped once no Android run holds the '${import_setup_lock.ANDROID_SETUP_LOCK_SCOPE}' setup lock.`
|
|
132
|
+
);
|
|
133
|
+
for (; ; ) {
|
|
134
|
+
const verdict = readWatchVerdict(reaperArguments);
|
|
135
|
+
if (verdict !== "watch") {
|
|
136
|
+
reaperLog(describeExit(verdict));
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
let lock;
|
|
140
|
+
try {
|
|
141
|
+
lock = (0, import_setup_lock.tryAcquireSetupLock)({
|
|
142
|
+
label: LOCK_LABEL,
|
|
143
|
+
scope: import_setup_lock.ANDROID_SETUP_LOCK_SCOPE,
|
|
144
|
+
staleAfterSilenceInMilliseconds: EMULATOR_REAPER_LOCK_SILENCE_IN_MILLISECONDS
|
|
145
|
+
});
|
|
146
|
+
} catch (error) {
|
|
147
|
+
reaperLog(`Giving up: the setup lock could not be read (${error instanceof Error ? error.message : String(error)}). The next Android run reclaims the emulator instead.`);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (lock) {
|
|
151
|
+
try {
|
|
152
|
+
const lockedVerdict = readWatchVerdict(reaperArguments);
|
|
153
|
+
if (lockedVerdict !== "watch") {
|
|
154
|
+
reaperLog(describeExit(lockedVerdict));
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
reaperLog("No Android run holds the setup lock any more, so nothing is left to stop the emulator. Stopping the leftovers this harness started...");
|
|
158
|
+
await new import_emulator_reclaim.EmulatorReclaimer(reaperLog).reclaimLeftoverEmulators({ scope: "end-of-run" });
|
|
159
|
+
} finally {
|
|
160
|
+
lock.release();
|
|
161
|
+
}
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
await new Promise((resolve) => {
|
|
165
|
+
setTimeout(resolve, EMULATOR_REAPER_POLL_INTERVAL_IN_MILLISECONDS);
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
function reaperLog(message) {
|
|
169
|
+
(0, import_log.log)(`[emulator-reaper:${avdName}] ${message}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
function spawnEmulatorReaper(params) {
|
|
173
|
+
const { avdName } = params;
|
|
174
|
+
if (!(0, import_setup_lock.checkIsSetupLockHeld)(import_setup_lock.ANDROID_SETUP_LOCK_SCOPE)) {
|
|
175
|
+
params.log(
|
|
176
|
+
`No emulator reaper armed for AVD "${avdName}": no run holds the '${import_setup_lock.ANDROID_SETUP_LOCK_SCOPE}' setup lock, so a reaper could not tell this run ending from it being killed. If this process is killed, only the next Android run on this host stops the emulator.`
|
|
177
|
+
);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const logFilePath = (0, import_node_path.join)(getReaperLogDirectory(), `${avdName}${REAPER_LOG_FILE_SUFFIX}`);
|
|
181
|
+
try {
|
|
182
|
+
(0, import_node_fs.mkdirSync)(getReaperLogDirectory(), { recursive: true });
|
|
183
|
+
const logFileDescriptor = (0, import_node_fs.openSync)(logFilePath, resolveEmulatorReaperLogOpenFlag(readFileSize(logFilePath)));
|
|
184
|
+
try {
|
|
185
|
+
const relay = (0, import_node_child_process.spawn)(
|
|
186
|
+
import_node_process.default.execPath,
|
|
187
|
+
[
|
|
188
|
+
"-e",
|
|
189
|
+
buildEmulatorReaperRelayBootstrap(),
|
|
190
|
+
buildEmulatorReaperBootstrap(),
|
|
191
|
+
resolveOwnModuleUrl(),
|
|
192
|
+
...buildEmulatorReaperArguments({ avdName, startedAtInMilliseconds: params.startedAtInMilliseconds })
|
|
193
|
+
],
|
|
194
|
+
{ detached: true, stdio: ["ignore", logFileDescriptor, logFileDescriptor], windowsHide: true }
|
|
195
|
+
);
|
|
196
|
+
relay.once("error", (error) => {
|
|
197
|
+
params.log(`The emulator reaper relay for AVD "${avdName}" failed to start: ${error.message}`);
|
|
198
|
+
});
|
|
199
|
+
relay.unref();
|
|
200
|
+
params.log(
|
|
201
|
+
`Emulator reaper armed for AVD "${avdName}": if this run is killed \u2014 a process-tree kill included, since the reaper is started through a relay that exits at once \u2014 it stops the emulator once no Android run holds the '${import_setup_lock.ANDROID_SETUP_LOCK_SCOPE}' setup lock. Its PID is the first line of its log: ${logFilePath}`
|
|
202
|
+
);
|
|
203
|
+
} finally {
|
|
204
|
+
(0, import_node_fs.closeSync)(logFileDescriptor);
|
|
205
|
+
}
|
|
206
|
+
} catch (error) {
|
|
207
|
+
params.log(`Could not arm the emulator reaper for AVD "${avdName}": ${error instanceof Error ? error.message : String(error)}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
function describeExit(verdict) {
|
|
211
|
+
return verdict === "superseded" ? "The marker now records a later emulator of this AVD, which has its own reaper; nothing left to watch." : "The emulator was stopped; nothing left to watch.";
|
|
212
|
+
}
|
|
213
|
+
function getReaperLogDirectory() {
|
|
214
|
+
return (0, import_node_path.join)((0, import_node_os.tmpdir)(), import_leftover_cleanup.HARNESS_TEMP_DIR_NAME);
|
|
215
|
+
}
|
|
216
|
+
function readFileSize(path) {
|
|
217
|
+
try {
|
|
218
|
+
return (0, import_node_fs.statSync)(path).size;
|
|
219
|
+
} catch {
|
|
220
|
+
return void 0;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
function readWatchVerdict(reaperArguments) {
|
|
224
|
+
return resolveEmulatorReaperWatch({
|
|
225
|
+
armedStartedAtInMilliseconds: reaperArguments.startedAtInMilliseconds,
|
|
226
|
+
checkIsPidAlive: import_process_liveness.checkIsProcessAlive,
|
|
227
|
+
marker: (0, import_emulator_marker.readEmulatorMarker)(reaperArguments.avdName)
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
function resolveOwnModuleUrl() {
|
|
231
|
+
return typeof __filename === "string" ? (0, import_node_url.pathToFileURL)(__filename).href : import_meta.url;
|
|
232
|
+
}
|
|
233
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
234
|
+
0 && (module.exports = {
|
|
235
|
+
EMULATOR_REAPER_ENTRY_NAME,
|
|
236
|
+
EMULATOR_REAPER_LOCK_SILENCE_IN_MILLISECONDS,
|
|
237
|
+
EMULATOR_REAPER_LOG_MAX_SIZE_IN_BYTES,
|
|
238
|
+
EMULATOR_REAPER_POLL_INTERVAL_IN_MILLISECONDS,
|
|
239
|
+
buildEmulatorReaperArguments,
|
|
240
|
+
buildEmulatorReaperBootstrap,
|
|
241
|
+
buildEmulatorReaperRelayBootstrap,
|
|
242
|
+
parseEmulatorReaperArguments,
|
|
243
|
+
resolveEmulatorReaperLogOpenFlag,
|
|
244
|
+
resolveEmulatorReaperWatch,
|
|
245
|
+
runEmulatorReaper,
|
|
246
|
+
spawnEmulatorReaper
|
|
247
|
+
});
|
|
248
|
+
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi4vLi4vLi4vc3JjL2VtdWxhdG9yLXJlYXBlci50cyJdLAogICJzb3VyY2VzQ29udGVudCI6IFsiLyoqXG4gKiBAZmlsZVxuICpcbiAqIFN0b3BzIGFuIGVtdWxhdG9yIHRoaXMgaGFybmVzcyBzdGFydGVkIG9uY2UgaXRzIHJ1biBpcyBnb25lIGZvciBnb29kIFx1MjAxNCB0aGVcbiAqIGNhc2Ugbm90aGluZyBlbHNlIGNvdmVycywgKipMNTYqKidzIHBhdGggQiB3aXRoIG5vIGxhdGVyIHJ1bjogdGhlIHJ1bm5lciBpc1xuICoga2lsbGVkIChTSUdLSUxMLCBUYXNrIE1hbmFnZXIsIGFuIElERSBzdG9wIGJ1dHRvbiksIG5vIHRlYXJkb3duIHJ1bnMsIGFuZCBub1xuICogb3RoZXIgQW5kcm9pZCBydW4gZXZlciBjb21lcyBhbG9uZyB0byByZWNsYWltIHRoZSBsZWZ0b3ZlciBmcm9tIGl0cyBtYXJrZXIuXG4gKiBPbiAyMDI2LTA5LTEwIGFuIGVtdWxhdG9yIGxlZnQgbGlrZSB0aGF0IGtlcHQgYG5ldHNpbWRgIHNwaW5uaW5nIGZvciBzaXggaG91cnMuXG4gKlxuICogVGhlIHJlYXBlciBpcyBhIHNtYWxsIGRldGFjaGVkIE5vZGUgcHJvY2VzcyBzcGF3bmVkIGJlc2lkZSBldmVyeSBlbXVsYXRvciB0aGVcbiAqIGhhcm5lc3Mgc3RhcnRzIG9yIHRha2VzIG92ZXIuIEl0IGhvbGRzIG5vIGhhbmRsZSB0byB0aGUgZW11bGF0b3IgYW5kIG5lZWRzXG4gKiBub25lOiB0aGUgbWFya2VyIChgZW11bGF0b3ItbWFya2VyLnRzYCkgc2F5cyB3aGF0IHRvIHN0b3AsIGFuZCB0aGUgdmVyaWZpZWRcbiAqIHN0b3AgKGBlbXVsYXRvci1yZWNsYWltLnRzYCkgaXMgdGhlIG9uZSB0aGUgcnVuJ3Mgb3duIHRlYXJkb3duIHVzZXMuXG4gKlxuICogIyMgSXQgd2F0Y2hlcyB0aGUgYGFuZHJvaWRgIHNldHVwIGxvY2ssIG5vdCB0aGUgcHJvY2VzcyB0aGF0IHN0YXJ0ZWQgaXRcbiAqXG4gKiBUaGUgcHJvY2VzcyB0aGF0IHN0YXJ0cyB0aGUgZW11bGF0b3IgaXMgbm90IGFsd2F5cyB0aGUgcnVuLiBBIHByb2plY3Qgd2l0aCBub1xuICogdHJhbnNwb3J0IGdsb2JhbCBzZXR1cCBib290cyBpdCBmcm9tIGEgKip0ZXN0IHdvcmtlcioqICgqKkw1NioqIHBhdGggQSksIGFuZFxuICogVml0ZXN0IGVuZHMgd29ya2VycyBkdXJpbmcgYSBwZXJmZWN0bHkgaGVhbHRoeSBydW4gXHUyMDE0IHNvIGEgcmVhcGVyIHRpZWQgdG8gaXRzXG4gKiBzcGF3bmVyJ3MgbGlmZXRpbWUgKHRoZSAqKkwzMyoqIGxpdmVuZXNzIHNvY2tldCwgdGhlIGZpcnN0IGlkZWEpIHdvdWxkIHN0b3BcbiAqIHRoZSBlbXVsYXRvciBtaWQtcnVuLCBvciByYWNlIHRoZSBuZXh0IHdvcmtlciB0YWtpbmcgaXQgb3Zlci4gVGhlIGBhbmRyb2lkYFxuICogc2V0dXAgbG9jayAoKipMNyoqKSBpcyBoZWxkIGJ5IHRoZSBydW4ncyBtYWluIHByb2Nlc3MgZm9yIHRoZSB3aG9sZSBydW4sIGFuZFxuICogaXRzIGhvbGRlciBiZWluZyBhbGl2ZSBpcyBleGFjdGx5IHdoYXQgXCJhbiBBbmRyb2lkIHJ1biBpcyBpbiBmbGlnaHRcIiBtZWFucy5cbiAqXG4gKiBTbyB0aGUgcmVhcGVyIHBvbGxzIHRoYXQgbG9jaywgYW5kIG9uY2UgaXQgY2FuICoqdGFrZSoqIGl0IFx1MjAxNCBubyBsaXZlIHJ1blxuICogaG9sZHMgaXQgXHUyMDE0IGl0IHN0b3BzIHRoZSBoYXJuZXNzJ3MgbGVmdG92ZXJzIHdoaWxlIGhvbGRpbmcgaXQsIHdoaWNoIGlzIHRoZVxuICogcHJlY29uZGl0aW9uIGV2ZXJ5IGxlZnRvdmVyIHN0b3AgZG9jdW1lbnRzOiBubyBydW4gY2FuIGFkb3B0IGFuIGVtdWxhdG9yIHRoYXRcbiAqIGlzIG1pZC1zaHV0ZG93bi4gVGhhdCBpcyBwcmVjaXNlbHkgd2hhdCB0aGUgbmV4dCBydW4ncyBwcmVmbGlnaHQgd291bGQgZG8sXG4gKiBkb25lIGF0IHRoZSBmaXJzdCBtb21lbnQgdGhhdCBydW4gY291bGQgaGF2ZSBkb25lIGl0LlxuICpcbiAqIEEgZGVhZCBzYW1lLWhvc3QgaG9sZGVyIGlzIGFiYW5kb25lZCBhdCBvbmNlLiBBIGxpdmUtUElEIGhvbGRlciB0aGF0IGhhcyBnb25lXG4gKiBzaWxlbnQgaXMgb25seSBhYmFuZG9uZWQgYWZ0ZXJcbiAqIHtAbGluayBFTVVMQVRPUl9SRUFQRVJfTE9DS19TSUxFTkNFX0lOX01JTExJU0VDT05EU30sIGZhciB3aWRlciB0aGFuIHRoZVxuICogdHdvIG1pbnV0ZXMgYSB3YWl0aW5nIHJ1biBzdGVhbHMgb246IGEgcmVhcGVyIGlzIGFsd2F5cyB3YWl0aW5nLCBzbyBhdCB0d29cbiAqIG1pbnV0ZXMgYSBsaXZlIHJ1biB0aGF0IGJsb2NrZWQgaXRzIGV2ZW50IGxvb3Agd291bGQgbG9zZSBpdHMgZW11bGF0b3IuXG4gKlxuICogIyMgRmFpbC1vcGVuLCBsaWtlIHRoZSByZW5kZXJlciB3YXRjaGRvZ1xuICpcbiAqIEEgcmVhcGVyIGlzIGFybWVkIG9ubHkgd2hlbiBhIGxpdmUgcnVuIGhvbGRzIHRoZSBsb2NrIGF0IHRoZSBtb21lbnQgdGhlXG4gKiBlbXVsYXRvciBpcyByZWNvcmRlZC4gQSBydW4gdGhhdCB0YWtlcyBubyBsb2NrIFx1MjAxNCBhIGhhbmQtd2lyZWRcbiAqIGBjcmVhdGVUcmFuc3BvcnRGcm9tT3B0aW9uc2AgXHUyMDE0IGdldHMgbm9uZSwgYW5kIHNheXMgc28sIHJhdGhlciB0aGFuIGEgcmVhcGVyXG4gKiB0aGF0IHdvdWxkIHJlYWQgaXRzIG1pc3NpbmcgbG9jayBhcyBcInRoZSBydW4gaXMgb3ZlclwiIGFuZCBzdG9wIGFuIGVtdWxhdG9yXG4gKiBzb21lYm9keSBpcyB1c2luZy4gQSBzcGF3biB0aGF0IGZhaWxzIGlzIGxvZ2dlZCBhbmQgbmV2ZXIgZmFpbHMgdGhlIGxhdW5jaC5cbiAqXG4gKiAjIyBIb3cgaXQgcnVuc1xuICpcbiAqIGBwcm9jZXNzLmV4ZWNQYXRoIC1lIDxib290c3RyYXA+IDx0aGlzIG1vZHVsZSdzIFVSTD4gPGF2ZE5hbWU+IDxzdGFydGVkQXQ+YDpcbiAqIHRoZSBib290c3RyYXAgYGltcG9ydCgpYHMgdGhpcyB2ZXJ5IG1vZHVsZSBhbmQgY2FsbHMge0BsaW5rIHJ1bkVtdWxhdG9yUmVhcGVyfS5cbiAqIFRoZSBVUkwgaXMgdGhlIGJ1aWx0IGAubWpzYCAvIGAuY2pzYCBpbiBhIGNvbnN1bWVyJ3MgaW5zdGFsbCwgYW5kIHRoZSBgLnRzYFxuICogc291cmNlIGluIHRoaXMgcmVwbydzIG93biBzdWl0ZXMsIHdoaWNoIE5vZGUncyB0eXBlIHN0cmlwcGluZyBydW5zIGFzLWlzIFx1MjAxNCBzb1xuICogbm90aGluZyB0aGlzIG1vZHVsZSBpbXBvcnRzIG1heSByZWFjaCBhIG1vZHVsZSB0aGF0IHJlYWRzIHRoZSBidWlsZC10aW1lXG4gKiBgT0JTSURJQU5fTUVUQURBVEFgIGdsb2JhbC4gSXRzIHN0ZG91dC9zdGRlcnIgZ28gdG8gYSBwZXItQVZEIGxvZyBmaWxlLCBjYXBwZWRcbiAqIGF0IHtAbGluayBFTVVMQVRPUl9SRUFQRVJfTE9HX01BWF9TSVpFX0lOX0JZVEVTfSwgc28gdGhlIHZlcmRpY3QgbGluZXMgdGhlIHN0b3BcbiAqIHByaW50cyBhcmUgb24gcmVjb3JkIGV2ZW4gdGhvdWdoIG5vIHRlcm1pbmFsIGlzIGxlZnQgdG8gc2hvdyB0aGVtLlxuICpcbiAqICMjIEl0IGlzIHN0YXJ0ZWQgdGhyb3VnaCBhIHJlbGF5LCBzbyBhIHByb2Nlc3MtdHJlZSBraWxsIGNhbm5vdCB0YWtlIGl0XG4gKlxuICogYGRldGFjaGVkYCBkb2VzIG5vdCByZXBhcmVudCBvbiBXaW5kb3dzOiBhIGNoaWxkIHNwYXduZWQgc3RyYWlnaHQgZnJvbSB0aGUgcnVuXG4gKiBzdGF5cyBpbiB0aGUgcnVuJ3MgcHJvY2VzcyB0cmVlLCBzbyB0aGUga2lsbCBzaGFwZXMgdGhhdCB3YWxrIHRoYXQgdHJlZSB0YWtlXG4gKiB0aGUgc2FmZXR5IG5ldCBhbG9uZyB3aXRoIHRoZSB0aGluZyBpdCB3YXMgZ3VhcmRpbmcgXHUyMDE0IFRhc2sgTWFuYWdlcidzICpFbmRcbiAqIHRhc2sqLCBgdGFza2tpbGwgL1RgLCBhbiBJREUgc3RvcCBidXR0b24sIGEgYFBhcmVudFByb2Nlc3NJZGAgZGVzY2VuZGFudFxuICogc3dlZXAuIE1lYXN1cmVkIG9uIDIwMjYtMDktMTE6IGEgcmVhcGVyIGRpZWQgd2l0aCB0aGUgcnVuIGl0IHdhcyB3YXRjaGluZywgYW5kXG4gKiB0aGUgZW11bGF0b3IgaXQgd291bGQgaGF2ZSBzdG9wcGVkIGlkbGVkIG9uIGZvciB0ZW4gbWludXRlcy5cbiAqXG4gKiBTbyB0aGUgc3Bhd24gZ29lcyB0aHJvdWdoIGEgcmVsYXkgXHUyMDE0IHtAbGluayBidWlsZEVtdWxhdG9yUmVhcGVyUmVsYXlCb290c3RyYXB9IFx1MjAxNFxuICogd2hpY2ggc3RhcnRzIHRoZSByZWFwZXIgYW5kIGV4aXRzIGF0IG9uY2UuIEJ5IHRoZSB0aW1lIGFueSB0cmVlLXdhbGtlciB0YWtlc1xuICogaXRzIHNuYXBzaG90IHRoZSByZWxheSBpcyBnb25lLCBzbyB0aGVyZSBpcyBubyBlZGdlIGZyb20gdGhlIHJ1biB0byB0aGUgcmVhcGVyXG4gKiBsZWZ0IHRvIGZvbGxvdy4gVGhpcyBpcyB0aGUgZG91YmxlLWZvcmsgaWRpb20sIGFuZCBpdCBob2xkcyBvbiBQT1NJWCBmb3IgdGhlXG4gKiBzYW1lIHJlYXNvbjogb25jZSB0aGUgcmVsYXkgZXhpdHMsIHRoZSByZWFwZXIgYmVsb25ncyB0byBgaW5pdGAuXG4gKi9cblxuaW1wb3J0IHsgc3Bhd24gfSBmcm9tICdub2RlOmNoaWxkX3Byb2Nlc3MnO1xuaW1wb3J0IHtcbiAgY2xvc2VTeW5jLFxuICBta2RpclN5bmMsXG4gIG9wZW5TeW5jLFxuICBzdGF0U3luY1xufSBmcm9tICdub2RlOmZzJztcbmltcG9ydCB7IHRtcGRpciB9IGZyb20gJ25vZGU6b3MnO1xuaW1wb3J0IHsgam9pbiB9IGZyb20gJ25vZGU6cGF0aCc7XG5pbXBvcnQgcHJvY2VzcyBmcm9tICdub2RlOnByb2Nlc3MnO1xuaW1wb3J0IHsgcGF0aFRvRmlsZVVSTCB9IGZyb20gJ25vZGU6dXJsJztcblxuaW1wb3J0IHR5cGUgeyBFbXVsYXRvck1hcmtlciB9IGZyb20gJy4vZW11bGF0b3ItbWFya2VyLmNqcyc7XG5pbXBvcnQgdHlwZSB7IFNldHVwTG9jayB9IGZyb20gJy4vc2V0dXAtbG9jay5janMnO1xuXG5pbXBvcnQgeyByZWFkRW11bGF0b3JNYXJrZXIgfSBmcm9tICcuL2VtdWxhdG9yLW1hcmtlci5janMnO1xuaW1wb3J0IHsgRW11bGF0b3JSZWNsYWltZXIgfSBmcm9tICcuL2VtdWxhdG9yLXJlY2xhaW0uY2pzJztcbmltcG9ydCB7IEhBUk5FU1NfVEVNUF9ESVJfTkFNRSB9IGZyb20gJy4vbGVmdG92ZXItY2xlYW51cC5janMnO1xuaW1wb3J0IHsgbG9nIH0gZnJvbSAnLi9sb2cuY2pzJztcbmltcG9ydCB7IGNoZWNrSXNQcm9jZXNzQWxpdmUgfSBmcm9tICcuL3Byb2Nlc3MtbGl2ZW5lc3MuY2pzJztcbmltcG9ydCB7XG4gIEFORFJPSURfU0VUVVBfTE9DS19TQ09QRSxcbiAgY2hlY2tJc1NldHVwTG9ja0hlbGQsXG4gIHRyeUFjcXVpcmVTZXR1cExvY2tcbn0gZnJvbSAnLi9zZXR1cC1sb2NrLmNqcyc7XG5cbi8qKlxuICogSG93IG9mdGVuIHRoZSByZWFwZXIgbG9va3MgYXQgdGhlIG1hcmtlciBhbmQgdGhlIGxvY2sgXHUyMDE0IHRoZSBsb2NrJ3Mgb3duXG4gKiBoZWFydGJlYXQgaW50ZXJ2YWwsIHNvIGEga2lsbGVkIHJ1biBpcyBub3RpY2VkIHdpdGhpbiBvbmUgYmVhdC5cbiAqL1xuZXhwb3J0IGNvbnN0IEVNVUxBVE9SX1JFQVBFUl9QT0xMX0lOVEVSVkFMX0lOX01JTExJU0VDT05EUyA9IDUwMDA7XG5cbi8qKlxuICogSG93IGxvbmcgYSBzYW1lLWhvc3QgbG9jayBob2xkZXIgd2hvc2UgUElEIGlzIHN0aWxsIGFsaXZlIG1heSBzdGF5IHNpbGVudFxuICogYmVmb3JlIHRoZSByZWFwZXIgdHJlYXRzIGl0cyBydW4gYXMgZ29uZTogMzAgbWludXRlcywgdGhlIHRocmVzaG9sZCB0aGUgbG9ja1xuICogYWxyZWFkeSB1c2VzIGFjcm9zcyBob3N0cy4gT25seSBhIHJlY3ljbGVkIFBJRCBzaG91bGQgZXZlciByZWFjaCBpdC5cbiAqL1xuZXhwb3J0IGNvbnN0IEVNVUxBVE9SX1JFQVBFUl9MT0NLX1NJTEVOQ0VfSU5fTUlMTElTRUNPTkRTID0gMV84MDBfMDAwO1xuXG4vKipcbiAqIFRoZSBzaXplIHBhc3Qgd2hpY2ggdGhlIHJlYXBlciBsb2cgaXMgc3RhcnRlZCBhZnJlc2ggcmF0aGVyIHRoYW4gYXBwZW5kZWQgdG8uXG4gKi9cbmV4cG9ydCBjb25zdCBFTVVMQVRPUl9SRUFQRVJfTE9HX01BWF9TSVpFX0lOX0JZVEVTID0gMV8wNDhfNTc2O1xuXG4vKipcbiAqIFRoZSBleHBvcnQgdGhlIGJvb3RzdHJhcCBjYWxscy4gTmFtZWQgaGVyZSBzbyB0aGUgYm9vdHN0cmFwIGFuZCB0aGUgZXhwb3J0XG4gKiBjYW5ub3QgZHJpZnQgYXBhcnQuXG4gKi9cbmV4cG9ydCBjb25zdCBFTVVMQVRPUl9SRUFQRVJfRU5UUllfTkFNRSA9ICdydW5FbXVsYXRvclJlYXBlcic7XG5cbmNvbnN0IExPQ0tfTEFCRUwgPSAnZW11bGF0b3ItcmVhcGVyJztcbmNvbnN0IFJFQVBFUl9MT0dfRklMRV9TVUZGSVggPSAnLmVtdWxhdG9yLXJlYXBlci5sb2cnO1xuXG4vKipcbiAqIFdoYXQgdGhlIHJlYXBlciB3YXRjaGVzIGZvcjogdGhlIGVtdWxhdG9yIGEgbWFya2VyIHJlY29yZGVkIHdoZW4gaXQgd2FzIGFybWVkLlxuICovXG5leHBvcnQgaW50ZXJmYWNlIEVtdWxhdG9yUmVhcGVyQXJndW1lbnRzIHtcbiAgLyoqXG4gIFRoZSBBVkQgd2hvc2UgbWFya2VyIGl0IHdhdGNoZXMuXG4gICAqL1xuICByZWFkb25seSBhdmROYW1lOiBzdHJpbmc7XG5cbiAgLyoqXG4gICAqIFdoZW4gdGhlIHdhdGNoZWQgZW11bGF0b3Igd2FzIGxhdW5jaGVkLiBBIHRha2VvdmVyIGtlZXBzIGl0LCBzbyBpdCBuYW1lcyBvbmVcbiAgICogZW11bGF0b3IgYWNyb3NzIGV2ZXJ5IHJ1biB0aGF0IG93bnMgaXQsIGFuZCB0ZWxscyBhIGxhdGVyIGVtdWxhdG9yIG9mIHRoZVxuICAgKiBzYW1lIEFWRCBhcGFydC5cbiAgICovXG4gIHJlYWRvbmx5IHN0YXJ0ZWRBdEluTWlsbGlzZWNvbmRzOiBudW1iZXI7XG59XG5cbi8qKlxuICogV2hhdCB0aGUgbWFya2VyIHNheXMgdGhlIHJlYXBlciBzaG91bGQgZG8gbmV4dC5cbiAqXG4gKiAtIGBlbXVsYXRvci1zdG9wcGVkYCBcdTIwMTQgdGhlIG1hcmtlciBpcyBnb25lLCBvciBub25lIG9mIGl0cyBQSURzIGlzIGFsaXZlOiB0aGVcbiAqICAgZW11bGF0b3Igd2FzIHN0b3BwZWQsIHNvIHRoZXJlIGlzIG5vdGhpbmcgbGVmdCB0byB3YXRjaC5cbiAqIC0gYHN1cGVyc2VkZWRgIFx1MjAxNCB0aGUgbWFya2VyIG5vdyByZWNvcmRzIGEgZGlmZmVyZW50IGVtdWxhdG9yIG9mIHRoZSBzYW1lIEFWRCxcbiAqICAgd2hpY2ggaGFzIGl0cyBvd24gcmVhcGVyLlxuICogLSBgd2F0Y2hgIFx1MjAxNCB0aGUgZW11bGF0b3IgaXMgc3RpbGwgdXAuXG4gKi9cbmV4cG9ydCB0eXBlIEVtdWxhdG9yUmVhcGVyV2F0Y2hWZXJkaWN0ID0gJ2VtdWxhdG9yLXN0b3BwZWQnIHwgJ3N1cGVyc2VkZWQnIHwgJ3dhdGNoJztcblxuLyoqXG4gKiBQYXJhbWV0ZXJzIGZvciB7QGxpbmsgcmVzb2x2ZUVtdWxhdG9yUmVhcGVyV2F0Y2h9LlxuICovXG5leHBvcnQgaW50ZXJmYWNlIFJlc29sdmVFbXVsYXRvclJlYXBlcldhdGNoUGFyYW1zIHtcbiAgLyoqXG4gIFRoZSBsYXVuY2ggdGltZSBvZiB0aGUgZW11bGF0b3IgdGhpcyByZWFwZXIgd2FzIGFybWVkIGZvci5cbiAgICovXG4gIHJlYWRvbmx5IGFybWVkU3RhcnRlZEF0SW5NaWxsaXNlY29uZHM6IG51bWJlcjtcblxuICAvKipcbiAgV2hldGhlciBhIFBJRCBpcyBzdGlsbCBydW5uaW5nLlxuICAgKi9cbiAgcmVhZG9ubHkgY2hlY2tJc1BpZEFsaXZlOiAocGlkOiBudW1iZXIpID0+IGJvb2xlYW47XG5cbiAgLyoqXG4gIFRoZSBBVkQncyBtYXJrZXIgYXMgaXQgcmVhZHMgbm93LCBpZiBhbnkuXG4gICAqL1xuICByZWFkb25seSBtYXJrZXI6IEVtdWxhdG9yTWFya2VyIHwgdW5kZWZpbmVkO1xufVxuXG4vKipcbiAqIFBhcmFtZXRlcnMgZm9yIHtAbGluayBzcGF3bkVtdWxhdG9yUmVhcGVyfS5cbiAqL1xuZXhwb3J0IGludGVyZmFjZSBTcGF3bkVtdWxhdG9yUmVhcGVyUGFyYW1zIHtcbiAgLyoqXG4gIFRoZSBBVkQgd2hvc2UgZW11bGF0b3Igd2FzIGp1c3QgcmVjb3JkZWQuXG4gICAqL1xuICByZWFkb25seSBhdmROYW1lOiBzdHJpbmc7XG5cbiAgLyoqXG4gIFJlY2VpdmVzIHRoZSBhcm1lZCAvIG5vdC1hcm1lZCBsaW5lLlxuICAgKi9cbiAgcmVhZG9ubHkgbG9nOiAobWVzc2FnZTogc3RyaW5nKSA9PiB2b2lkO1xuXG4gIC8qKlxuICBUaGUgcmVjb3JkZWQgZW11bGF0b3IncyBsYXVuY2ggdGltZSwgYXMgd3JpdHRlbiBpbnRvIGl0cyBtYXJrZXIuXG4gICAqL1xuICByZWFkb25seSBzdGFydGVkQXRJbk1pbGxpc2Vjb25kczogbnVtYmVyO1xufVxuXG4vKipcbiAqIEVuY29kZXMgdGhlIHJlYXBlcidzIGFyZ3VtZW50cyBmb3IgaXRzIGNvbW1hbmQgbGluZS5cbiAqXG4gKiBAcGFyYW0gcmVhcGVyQXJndW1lbnRzIC0gV2hhdCB0aGUgcmVhcGVyIHdhdGNoZXMgZm9yLlxuICogQHJldHVybnMgVGhlIGNvbW1hbmQtbGluZSBhcmd1bWVudHMsIGluIHRoZSBvcmRlciB7QGxpbmsgcGFyc2VFbXVsYXRvclJlYXBlckFyZ3VtZW50c30gcmVhZHMgdGhlbS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGJ1aWxkRW11bGF0b3JSZWFwZXJBcmd1bWVudHMocmVhcGVyQXJndW1lbnRzOiBFbXVsYXRvclJlYXBlckFyZ3VtZW50cyk6IHN0cmluZ1tdIHtcbiAgcmV0dXJuIFtyZWFwZXJBcmd1bWVudHMuYXZkTmFtZSwgU3RyaW5nKHJlYXBlckFyZ3VtZW50cy5zdGFydGVkQXRJbk1pbGxpc2Vjb25kcyldO1xufVxuXG4vKipcbiAqIEJ1aWxkcyB0aGUgc2NyaXB0IGBub2RlIC1lYCBydW5zIHRvIHN0YXJ0IHRoZSByZWFwZXIuXG4gKlxuICogSXQgYGltcG9ydCgpYHMgdGhlIG1vZHVsZSB3aG9zZSBVUkwgaXMgaXRzIGZpcnN0IGFyZ3VtZW50IGFuZCBoYW5kcyB0aGUgcmVzdFxuICogdG8ge0BsaW5rIHJ1bkVtdWxhdG9yUmVhcGVyfS4gYGltcG9ydCgpYCByYXRoZXIgdGhhbiBgcmVxdWlyZWAsIGJlY2F1c2UgaXRcbiAqIGxvYWRzIGFsbCB0aHJlZSBmb3JtcyB0aGUgbW9kdWxlIHNoaXBzIGFzIFx1MjAxNCBFU00sIENKUywgYW5kIHRoaXMgcmVwbydzIG93blxuICogYC50c2Agc291cmNlIHVuZGVyIE5vZGUncyB0eXBlIHN0cmlwcGluZy4gQSBDSlMgbW9kdWxlJ3MgbmFtZWQgZXhwb3J0cyBhcmVcbiAqIGZvdW5kIGJ5IE5vZGUncyBzdGF0aWMgYW5hbHlzaXM7IGBkZWZhdWx0YCBpcyB0aGUgZmFsbGJhY2sgd2hlbiB0aGV5IGFyZSBub3QuXG4gKlxuICogQHJldHVybnMgVGhlIHNjcmlwdC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGJ1aWxkRW11bGF0b3JSZWFwZXJCb290c3RyYXAoKTogc3RyaW5nIHtcbiAgcmV0dXJuIFtcbiAgICAnaW1wb3J0KHByb2Nlc3MuYXJndlsxXSkudGhlbigocmVhcGVyTW9kdWxlKSA9PiB7JyxcbiAgICBgICBjb25zdCBydW4gPSByZWFwZXJNb2R1bGUuJHtFTVVMQVRPUl9SRUFQRVJfRU5UUllfTkFNRX0gPz8gcmVhcGVyTW9kdWxlLmRlZmF1bHQuJHtFTVVMQVRPUl9SRUFQRVJfRU5UUllfTkFNRX07YCxcbiAgICAnICByZXR1cm4gcnVuKHByb2Nlc3MuYXJndi5zbGljZSgyKSk7JyxcbiAgICAnfSkuY2F0Y2goKGVycm9yKSA9PiB7JyxcbiAgICAnICBjb25zb2xlLmVycm9yKGVycm9yKTsnLFxuICAgICcgIHByb2Nlc3MuZXhpdENvZGUgPSAxOycsXG4gICAgJ30pOydcbiAgXS5qb2luKCdcXG4nKTtcbn1cblxuLyoqXG4gKiBCdWlsZHMgdGhlIHNjcmlwdCBgbm9kZSAtZWAgcnVucyBhcyB0aGUgKipyZWxheSoqOiBpdCBzdGFydHMgdGhlIHJlYXBlciBhbmRcbiAqIGV4aXRzIGF0IG9uY2UsIHNvIHRoZSByZWFwZXIgaXMgbGVmdCB3aXRoIGEgZGVhZCBwYXJlbnQgYW5kIG5vIHRyZWUtd2Fsa2luZ1xuICoga2lsbCBvZiB0aGUgcnVuIGNhbiByZWFjaCBpdC4gU2VlIHRoZSByZWxheSBzZWN0aW9uIGluIHRoaXMgbW9kdWxlJ3MgaGVhZGVyXG4gKiBmb3Igd2h5IHRoYXQgaW5kaXJlY3Rpb24gZXhpc3RzIGF0IGFsbC5cbiAqXG4gKiBJdHMgb3duIGFyZ3VtZW50cyBhcmUgdGhlIHJlYXBlcidzIHdob2xlIGNvbW1hbmQgbGluZSBcdTIwMTQgdGhlIGJvb3RzdHJhcCBzY3JpcHRcbiAqIGZpcnN0LCB0aGVuIGV2ZXJ5dGhpbmcgdGhhdCBib290c3RyYXAgcmVhZHMgXHUyMDE0IHNvIHRoZSByZWxheSBuZXZlciBoYXMgdG8ga25vd1xuICogd2hhdCBhbnkgb2YgdGhlbSBtZWFuLiBJdCBoYW5kcyB0aGUgcmVhcGVyIGl0cyBvd24gc3Rkb3V0IGFuZCBzdGRlcnIsIHdoaWNoXG4gKiBhcmUgdGhlIGNhcHBlZCBsb2cgZmlsZSB0aGUgc3Bhd25lciBvcGVuZWQsIHNvIHRoZSBsb2cgaXMgdW5hZmZlY3RlZCBieSB0aGVcbiAqIGV4dHJhIGhvcC5cbiAqXG4gKiBAcmV0dXJucyBUaGUgc2NyaXB0LlxuICovXG5leHBvcnQgZnVuY3Rpb24gYnVpbGRFbXVsYXRvclJlYXBlclJlbGF5Qm9vdHN0cmFwKCk6IHN0cmluZyB7XG4gIHJldHVybiBbXG4gICAgJ2NvbnN0IHsgc3Bhd24gfSA9IHJlcXVpcmUoXCJub2RlOmNoaWxkX3Byb2Nlc3NcIik7JyxcbiAgICAndHJ5IHsnLFxuICAgICcgIGNvbnN0IGNoaWxkID0gc3Bhd24ocHJvY2Vzcy5leGVjUGF0aCwgW1wiLWVcIiwgcHJvY2Vzcy5hcmd2WzFdLCAuLi5wcm9jZXNzLmFyZ3Yuc2xpY2UoMildLCB7JyxcbiAgICAnICAgIGRldGFjaGVkOiB0cnVlLCcsXG4gICAgJyAgICBzdGRpbzogW1wiaWdub3JlXCIsIDEsIDJdLCcsXG4gICAgJyAgICB3aW5kb3dzSGlkZTogdHJ1ZScsXG4gICAgJyAgfSk7JyxcbiAgICAnICBjaGlsZC5vbmNlKFwiZXJyb3JcIiwgKGVycm9yKSA9PiB7IGNvbnNvbGUuZXJyb3IoZXJyb3IpOyB9KTsnLFxuICAgICcgIGNoaWxkLnVucmVmKCk7JyxcbiAgICAnfSBjYXRjaCAoZXJyb3IpIHsnLFxuICAgICcgIGNvbnNvbGUuZXJyb3IoZXJyb3IpOycsXG4gICAgJyAgcHJvY2Vzcy5leGl0Q29kZSA9IDE7JyxcbiAgICAnfSdcbiAgXS5qb2luKCdcXG4nKTtcbn1cblxuLyoqXG4gKiBEZWNvZGVzIHRoZSByZWFwZXIncyBjb21tYW5kLWxpbmUgYXJndW1lbnRzLlxuICpcbiAqIEBwYXJhbSBhcmd2IC0gVGhlIGFyZ3VtZW50cyBhZnRlciB0aGUgbW9kdWxlIFVSTC5cbiAqIEByZXR1cm5zIFRoZSBhcmd1bWVudHMsIG9yIGB1bmRlZmluZWRgIHdoZW4gdGhleSBhcmUgbm90IHRoZSB0d28ge0BsaW5rIGJ1aWxkRW11bGF0b3JSZWFwZXJBcmd1bWVudHN9IHdyaXRlcy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHBhcnNlRW11bGF0b3JSZWFwZXJBcmd1bWVudHMoYXJndjogcmVhZG9ubHkgc3RyaW5nW10pOiBFbXVsYXRvclJlYXBlckFyZ3VtZW50cyB8IHVuZGVmaW5lZCB7XG4gIGNvbnN0IFthdmROYW1lLCBzdGFydGVkQXRUZXh0LCAuLi5yZXN0XSA9IGFyZ3Y7XG4gIGlmICghYXZkTmFtZSB8fCBzdGFydGVkQXRUZXh0ID09PSB1bmRlZmluZWQgfHwgcmVzdC5sZW5ndGggPiAwKSB7XG4gICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgfVxuXG4gIGNvbnN0IHN0YXJ0ZWRBdEluTWlsbGlzZWNvbmRzID0gTnVtYmVyKHN0YXJ0ZWRBdFRleHQpO1xuICBpZiAoIU51bWJlci5pc1NhZmVJbnRlZ2VyKHN0YXJ0ZWRBdEluTWlsbGlzZWNvbmRzKSB8fCBzdGFydGVkQXRJbk1pbGxpc2Vjb25kcyA8PSAwKSB7XG4gICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgfVxuXG4gIHJldHVybiB7IGF2ZE5hbWUsIHN0YXJ0ZWRBdEluTWlsbGlzZWNvbmRzIH07XG59XG5cbi8qKlxuICogRGVjaWRlcyBob3cgdGhlIHJlYXBlciBsb2cgaXMgb3BlbmVkLCBzbyBpdCBjYW4gbmV2ZXIgZ3JvdyB3aXRob3V0IGJvdW5kLlxuICpcbiAqIEBwYXJhbSBzaXplSW5CeXRlcyAtIFRoZSBsb2cncyBjdXJyZW50IHNpemUsIG9yIGB1bmRlZmluZWRgIHdoZW4gdGhlcmUgaXMgbm9uZS5cbiAqIEByZXR1cm5zIGAnYSdgIHRvIGFwcGVuZCwgb3IgYCd3J2AgdG8gc3RhcnQgaXQgYWZyZXNoIG9uY2UgaXQgaGFzIHBhc3NlZCB0aGUgY2FwLlxuICovXG5leHBvcnQgZnVuY3Rpb24gcmVzb2x2ZUVtdWxhdG9yUmVhcGVyTG9nT3BlbkZsYWcoc2l6ZUluQnl0ZXM6IG51bWJlciB8IHVuZGVmaW5lZCk6ICdhJyB8ICd3JyB7XG4gIHJldHVybiBzaXplSW5CeXRlcyAhPT0gdW5kZWZpbmVkICYmIHNpemVJbkJ5dGVzID4gRU1VTEFUT1JfUkVBUEVSX0xPR19NQVhfU0laRV9JTl9CWVRFUyA/ICd3JyA6ICdhJztcbn1cblxuLyoqXG4gKiBEZWNpZGVzIHdoYXQgdGhlIG1hcmtlciBzYXlzIHRoZSByZWFwZXIgc2hvdWxkIGRvIG5leHQuXG4gKlxuICogQHBhcmFtIHBhcmFtcyAtIFRoZSBtYXJrZXIgYXMgaXQgcmVhZHMgbm93LCBhbmQgdGhlIGVtdWxhdG9yIHRoZSByZWFwZXIgd2FzIGFybWVkIGZvci5cbiAqIEByZXR1cm5zIFRoZSB2ZXJkaWN0LlxuICovXG5leHBvcnQgZnVuY3Rpb24gcmVzb2x2ZUVtdWxhdG9yUmVhcGVyV2F0Y2gocGFyYW1zOiBSZXNvbHZlRW11bGF0b3JSZWFwZXJXYXRjaFBhcmFtcyk6IEVtdWxhdG9yUmVhcGVyV2F0Y2hWZXJkaWN0IHtcbiAgY29uc3QgeyBtYXJrZXIgfSA9IHBhcmFtcztcbiAgaWYgKG1hcmtlciA9PT0gdW5kZWZpbmVkKSB7XG4gICAgcmV0dXJuICdlbXVsYXRvci1zdG9wcGVkJztcbiAgfVxuXG4gIGlmIChtYXJrZXIuc3RhcnRlZEF0SW5NaWxsaXNlY29uZHMgIT09IHBhcmFtcy5hcm1lZFN0YXJ0ZWRBdEluTWlsbGlzZWNvbmRzKSB7XG4gICAgcmV0dXJuICdzdXBlcnNlZGVkJztcbiAgfVxuXG4gIC8qXG4gICAqIEEgbGF1bmNoLXRpbWUgbWFya2VyJ3MgUElEIGxpc3QgaXMgZGVsaWJlcmF0ZWx5IGluY29tcGxldGUgXHUyMDE0IGl0IG5hbWVzIHRoZVxuICAgKiBsYXVuY2hlciwgYW5kIHRoZSBiYWNrZW5kIGl0IGZvcmtzIGlzIGlkZW50aWZpZWQgYnkgdGhlIHByZS1sYXVuY2ggZGlmZixcbiAgICogd2hpY2ggY29zdHMgYSBob3N0IHByb2Nlc3MgbGlzdGluZyB0aGlzIHBvbGwgZGVsaWJlcmF0ZWx5IGRvZXMgbm90IG1ha2UuXG4gICAqIFNvIGl0IGlzIG5ldmVyIHJlYWQgYXMgc3RvcHBlZCBmcm9tIFBJRHMgYWxvbmU6IHRoZSByZWNsYWltLCB3aGljaCBkb2VzXG4gICAqIG1ha2UgdGhhdCBsaXN0aW5nLCBpcyB3aGF0IGp1ZGdlcyBpdC5cbiAgICovXG4gIGlmIChtYXJrZXIucHJlTGF1bmNoRW11bGF0b3JQaWRzICE9PSB1bmRlZmluZWQpIHtcbiAgICByZXR1cm4gJ3dhdGNoJztcbiAgfVxuXG4gIHJldHVybiBtYXJrZXIub3duZWRFbXVsYXRvclBpZHMuc29tZSgocGlkKSA9PiBwYXJhbXMuY2hlY2tJc1BpZEFsaXZlKHBpZCkpID8gJ3dhdGNoJyA6ICdlbXVsYXRvci1zdG9wcGVkJztcbn1cblxuLyogdjggaWdub3JlIHN0YXJ0IC0tIERldGFjaGVkLXByb2Nlc3MgZ2x1ZSBjb3ZlcmVkIGJ5IHRoZSBBbmRyb2lkIGludGVncmF0aW9uIHN1aXRlLCBub3QgdW5pdCB0ZXN0cy4gKi9cblxuLyoqXG4gKiBUaGUgcmVhcGVyIHByb2Nlc3MncyBib2R5OiB3YXRjaGVzIHRoZSBtYXJrZXIgYW5kIHRoZSBsb2NrIHVudGlsIGVpdGhlciB0aGVcbiAqIGVtdWxhdG9yIGlzIGdvbmUgb3Igbm8gcnVuIGhvbGRzIHRoZSBsb2NrLCBhbmQgaW4gdGhlIHNlY29uZCBjYXNlIHN0b3BzIHRoZVxuICogaGFybmVzcydzIGxlZnRvdmVycyB3aGlsZSBob2xkaW5nIGl0LlxuICpcbiAqIEV4cG9ydGVkIGZvciB0aGUgYm9vdHN0cmFwLCB3aGljaCBpcyBpdHMgb25seSBjYWxsZXIuXG4gKlxuICogQHBhcmFtIGFyZ3YgLSBUaGUgcmVhcGVyJ3MgYXJndW1lbnRzLCBhcyB7QGxpbmsgYnVpbGRFbXVsYXRvclJlYXBlckFyZ3VtZW50c30gd3JvdGUgdGhlbS5cbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIHJ1bkVtdWxhdG9yUmVhcGVyKGFyZ3Y6IHJlYWRvbmx5IHN0cmluZ1tdKTogUHJvbWlzZTx2b2lkPiB7XG4gIGNvbnN0IHJlYXBlckFyZ3VtZW50cyA9IHBhcnNlRW11bGF0b3JSZWFwZXJBcmd1bWVudHMoYXJndik7XG4gIGlmICghcmVhcGVyQXJndW1lbnRzKSB7XG4gICAgbG9nKGBbZW11bGF0b3ItcmVhcGVyXSBSZWZ1c2luZyB0byBydW46IGV4cGVjdGVkIDxhdmROYW1lPiA8c3RhcnRlZEF0SW5NaWxsaXNlY29uZHM+LCBnb3QgJHtKU09OLnN0cmluZ2lmeShhcmd2KX0uYCk7XG4gICAgcHJvY2Vzcy5leGl0Q29kZSA9IDE7XG4gICAgcmV0dXJuO1xuICB9XG5cbiAgY29uc3QgeyBhdmROYW1lLCBzdGFydGVkQXRJbk1pbGxpc2Vjb25kcyB9ID0gcmVhcGVyQXJndW1lbnRzO1xuICByZWFwZXJMb2coXG4gICAgYFdhdGNoaW5nIHRoZSBlbXVsYXRvciBzdGFydGVkIGF0ICR7bmV3IERhdGUoc3RhcnRlZEF0SW5NaWxsaXNlY29uZHMpLnRvSVNPU3RyaW5nKCl9IChyZWFwZXIgUElEICR7U3RyaW5nKHByb2Nlc3MucGlkKX0pOyBpdCBpcyBzdG9wcGVkIG9uY2Ugbm8gQW5kcm9pZCBydW4gaG9sZHMgdGhlICcke0FORFJPSURfU0VUVVBfTE9DS19TQ09QRX0nIHNldHVwIGxvY2suYFxuICApO1xuXG4gIGZvciAoOzspIHtcbiAgICBjb25zdCB2ZXJkaWN0ID0gcmVhZFdhdGNoVmVyZGljdChyZWFwZXJBcmd1bWVudHMpO1xuICAgIGlmICh2ZXJkaWN0ICE9PSAnd2F0Y2gnKSB7XG4gICAgICByZWFwZXJMb2coZGVzY3JpYmVFeGl0KHZlcmRpY3QpKTtcbiAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICBsZXQgbG9jazogU2V0dXBMb2NrIHwgdW5kZWZpbmVkO1xuICAgIHRyeSB7XG4gICAgICBsb2NrID0gdHJ5QWNxdWlyZVNldHVwTG9jayh7XG4gICAgICAgIGxhYmVsOiBMT0NLX0xBQkVMLFxuICAgICAgICBzY29wZTogQU5EUk9JRF9TRVRVUF9MT0NLX1NDT1BFLFxuICAgICAgICBzdGFsZUFmdGVyU2lsZW5jZUluTWlsbGlzZWNvbmRzOiBFTVVMQVRPUl9SRUFQRVJfTE9DS19TSUxFTkNFX0lOX01JTExJU0VDT05EU1xuICAgICAgfSk7XG4gICAgfSBjYXRjaCAoZXJyb3I6IHVua25vd24pIHtcbiAgICAgIC8vIEZhaWwgb3BlbjogYSBsb2NrIHRoaXMgcHJvY2VzcyBjYW5ub3QgZXZlbiBsb29rIGF0IGlzIG5vIGV2aWRlbmNlIHRoZSBydW4gaXMgb3Zlci5cbiAgICAgIHJlYXBlckxvZyhgR2l2aW5nIHVwOiB0aGUgc2V0dXAgbG9jayBjb3VsZCBub3QgYmUgcmVhZCAoJHtlcnJvciBpbnN0YW5jZW9mIEVycm9yID8gZXJyb3IubWVzc2FnZSA6IFN0cmluZyhlcnJvcil9KS4gVGhlIG5leHQgQW5kcm9pZCBydW4gcmVjbGFpbXMgdGhlIGVtdWxhdG9yIGluc3RlYWQuYCk7XG4gICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgaWYgKGxvY2spIHtcbiAgICAgIHRyeSB7XG4gICAgICAgIC8vIFRoZSBydW4gbWF5IGhhdmUgZW5kZWQgbm9ybWFsbHkgYmV0d2VlbiB0aGUgbG9vayBhYm92ZSBhbmQgdGhlIGxvY2s6IGl0cyB0ZWFyZG93biB0aGVuIHN0b3BwZWQgdGhlIGVtdWxhdG9yLlxuICAgICAgICBjb25zdCBsb2NrZWRWZXJkaWN0ID0gcmVhZFdhdGNoVmVyZGljdChyZWFwZXJBcmd1bWVudHMpO1xuICAgICAgICBpZiAobG9ja2VkVmVyZGljdCAhPT0gJ3dhdGNoJykge1xuICAgICAgICAgIHJlYXBlckxvZyhkZXNjcmliZUV4aXQobG9ja2VkVmVyZGljdCkpO1xuICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuXG4gICAgICAgIHJlYXBlckxvZygnTm8gQW5kcm9pZCBydW4gaG9sZHMgdGhlIHNldHVwIGxvY2sgYW55IG1vcmUsIHNvIG5vdGhpbmcgaXMgbGVmdCB0byBzdG9wIHRoZSBlbXVsYXRvci4gU3RvcHBpbmcgdGhlIGxlZnRvdmVycyB0aGlzIGhhcm5lc3Mgc3RhcnRlZC4uLicpO1xuICAgICAgICBhd2FpdCBuZXcgRW11bGF0b3JSZWNsYWltZXIocmVhcGVyTG9nKS5yZWNsYWltTGVmdG92ZXJFbXVsYXRvcnMoeyBzY29wZTogJ2VuZC1vZi1ydW4nIH0pO1xuICAgICAgfSBmaW5hbGx5IHtcbiAgICAgICAgbG9jay5yZWxlYXNlKCk7XG4gICAgICB9XG4gICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgYXdhaXQgbmV3IFByb21pc2UoKHJlc29sdmUpID0+IHtcbiAgICAgIHNldFRpbWVvdXQocmVzb2x2ZSwgRU1VTEFUT1JfUkVBUEVSX1BPTExfSU5URVJWQUxfSU5fTUlMTElTRUNPTkRTKTtcbiAgICB9KTtcbiAgfVxuXG4gIGZ1bmN0aW9uIHJlYXBlckxvZyhtZXNzYWdlOiBzdHJpbmcpOiB2b2lkIHtcbiAgICBsb2coYFtlbXVsYXRvci1yZWFwZXI6JHthdmROYW1lfV0gJHttZXNzYWdlfWApO1xuICB9XG59XG5cbi8qKlxuICogU3RhcnRzIHRoZSByZWFwZXIgZm9yIGFuIGVtdWxhdG9yIHRoZSBoYXJuZXNzIGp1c3QgcmVjb3JkZWQgYXMgaXRzIG93biwgd2hlblxuICogYSBsaXZlIHJ1biBob2xkcyB0aGUgYGFuZHJvaWRgIHNldHVwIGxvY2sgXHUyMDE0IGFuZCBzYXlzIHdoaWNoIGl0IGRpZC5cbiAqXG4gKiBOZXZlciB0aHJvd3M6IGEgcmVhcGVyIHRoYXQgY2Fubm90IGJlIHNwYXduZWQgaXMgYSBsb3N0IHNhZmV0eSBuZXQsIG5vdCBhXG4gKiByZWFzb24gdG8gZmFpbCB0aGUgbGF1bmNoIGl0IHdvdWxkIGhhdmUgZ3VhcmRlZC5cbiAqXG4gKiBAcGFyYW0gcGFyYW1zIC0gVGhlIHJlY29yZGVkIGVtdWxhdG9yLCBhbmQgd2hlcmUgdG8gcmVwb3J0LlxuICovXG5leHBvcnQgZnVuY3Rpb24gc3Bhd25FbXVsYXRvclJlYXBlcihwYXJhbXM6IFNwYXduRW11bGF0b3JSZWFwZXJQYXJhbXMpOiB2b2lkIHtcbiAgY29uc3QgeyBhdmROYW1lIH0gPSBwYXJhbXM7XG4gIGlmICghY2hlY2tJc1NldHVwTG9ja0hlbGQoQU5EUk9JRF9TRVRVUF9MT0NLX1NDT1BFKSkge1xuICAgIHBhcmFtcy5sb2coXG4gICAgICBgTm8gZW11bGF0b3IgcmVhcGVyIGFybWVkIGZvciBBVkQgXCIke2F2ZE5hbWV9XCI6IG5vIHJ1biBob2xkcyB0aGUgJyR7QU5EUk9JRF9TRVRVUF9MT0NLX1NDT1BFfScgc2V0dXAgbG9jaywgc28gYSByZWFwZXIgY291bGQgbm90IHRlbGwgdGhpcyBydW4gZW5kaW5nIGZyb20gaXQgYmVpbmcga2lsbGVkLiBJZiB0aGlzIHByb2Nlc3MgaXMga2lsbGVkLCBvbmx5IHRoZSBuZXh0IEFuZHJvaWQgcnVuIG9uIHRoaXMgaG9zdCBzdG9wcyB0aGUgZW11bGF0b3IuYFxuICAgICk7XG4gICAgcmV0dXJuO1xuICB9XG5cbiAgY29uc3QgbG9nRmlsZVBhdGggPSBqb2luKGdldFJlYXBlckxvZ0RpcmVjdG9yeSgpLCBgJHthdmROYW1lfSR7UkVBUEVSX0xPR19GSUxFX1NVRkZJWH1gKTtcbiAgdHJ5IHtcbiAgICBta2RpclN5bmMoZ2V0UmVhcGVyTG9nRGlyZWN0b3J5KCksIHsgcmVjdXJzaXZlOiB0cnVlIH0pO1xuICAgIGNvbnN0IGxvZ0ZpbGVEZXNjcmlwdG9yID0gb3BlblN5bmMobG9nRmlsZVBhdGgsIHJlc29sdmVFbXVsYXRvclJlYXBlckxvZ09wZW5GbGFnKHJlYWRGaWxlU2l6ZShsb2dGaWxlUGF0aCkpKTtcbiAgICB0cnkge1xuICAgICAgLypcbiAgICAgICAqIFRocm91Z2ggdGhlIHJlbGF5LCBuZXZlciBzdHJhaWdodCBhdCB0aGUgcmVhcGVyOiBhIHJlYXBlciBzcGF3bmVkIGhlcmVcbiAgICAgICAqIHdvdWxkIGJlIGEgY2hpbGQgb2YgdGhpcyBydW4gYW5kIHdvdWxkIGRpZSB3aXRoIGl0IHVuZGVyIGFueSBraWxsIHRoYXRcbiAgICAgICAqIHdhbGtzIHRoZSBwcm9jZXNzIHRyZWUuIFRoZSByZWxheSBleGl0cyBhdCBvbmNlLCBsZWF2aW5nIHRoZSByZWFwZXIgd2l0aFxuICAgICAgICogYSBkZWFkIHBhcmVudCBhbmQgbm8gZWRnZSBiYWNrIHRvIHRoaXMgcnVuLlxuICAgICAgICovXG4gICAgICBjb25zdCByZWxheSA9IHNwYXduKFxuICAgICAgICBwcm9jZXNzLmV4ZWNQYXRoLFxuICAgICAgICBbXG4gICAgICAgICAgJy1lJyxcbiAgICAgICAgICBidWlsZEVtdWxhdG9yUmVhcGVyUmVsYXlCb290c3RyYXAoKSxcbiAgICAgICAgICBidWlsZEVtdWxhdG9yUmVhcGVyQm9vdHN0cmFwKCksXG4gICAgICAgICAgcmVzb2x2ZU93bk1vZHVsZVVybCgpLFxuICAgICAgICAgIC4uLmJ1aWxkRW11bGF0b3JSZWFwZXJBcmd1bWVudHMoeyBhdmROYW1lLCBzdGFydGVkQXRJbk1pbGxpc2Vjb25kczogcGFyYW1zLnN0YXJ0ZWRBdEluTWlsbGlzZWNvbmRzIH0pXG4gICAgICAgIF0sXG4gICAgICAgIHsgZGV0YWNoZWQ6IHRydWUsIHN0ZGlvOiBbJ2lnbm9yZScsIGxvZ0ZpbGVEZXNjcmlwdG9yLCBsb2dGaWxlRGVzY3JpcHRvcl0sIHdpbmRvd3NIaWRlOiB0cnVlIH1cbiAgICAgICk7XG4gICAgICByZWxheS5vbmNlKCdlcnJvcicsIChlcnJvcikgPT4ge1xuICAgICAgICBwYXJhbXMubG9nKGBUaGUgZW11bGF0b3IgcmVhcGVyIHJlbGF5IGZvciBBVkQgXCIke2F2ZE5hbWV9XCIgZmFpbGVkIHRvIHN0YXJ0OiAke2Vycm9yLm1lc3NhZ2V9YCk7XG4gICAgICB9KTtcbiAgICAgIHJlbGF5LnVucmVmKCk7XG4gICAgICBwYXJhbXMubG9nKFxuICAgICAgICBgRW11bGF0b3IgcmVhcGVyIGFybWVkIGZvciBBVkQgXCIke2F2ZE5hbWV9XCI6IGlmIHRoaXMgcnVuIGlzIGtpbGxlZCBcdTIwMTQgYSBwcm9jZXNzLXRyZWUga2lsbCBpbmNsdWRlZCwgc2luY2UgdGhlIHJlYXBlciBpcyBzdGFydGVkIHRocm91Z2ggYSByZWxheSB0aGF0IGV4aXRzIGF0IG9uY2UgXHUyMDE0IGl0IHN0b3BzIHRoZSBlbXVsYXRvciBvbmNlIG5vIEFuZHJvaWQgcnVuIGhvbGRzIHRoZSAnJHtBTkRST0lEX1NFVFVQX0xPQ0tfU0NPUEV9JyBzZXR1cCBsb2NrLiBJdHMgUElEIGlzIHRoZSBmaXJzdCBsaW5lIG9mIGl0cyBsb2c6ICR7bG9nRmlsZVBhdGh9YFxuICAgICAgKTtcbiAgICB9IGZpbmFsbHkge1xuICAgICAgY2xvc2VTeW5jKGxvZ0ZpbGVEZXNjcmlwdG9yKTtcbiAgICB9XG4gIH0gY2F0Y2ggKGVycm9yOiB1bmtub3duKSB7XG4gICAgcGFyYW1zLmxvZyhgQ291bGQgbm90IGFybSB0aGUgZW11bGF0b3IgcmVhcGVyIGZvciBBVkQgXCIke2F2ZE5hbWV9XCI6ICR7ZXJyb3IgaW5zdGFuY2VvZiBFcnJvciA/IGVycm9yLm1lc3NhZ2UgOiBTdHJpbmcoZXJyb3IpfWApO1xuICB9XG59XG5cbmZ1bmN0aW9uIGRlc2NyaWJlRXhpdCh2ZXJkaWN0OiBFeGNsdWRlPEVtdWxhdG9yUmVhcGVyV2F0Y2hWZXJkaWN0LCAnd2F0Y2gnPik6IHN0cmluZyB7XG4gIHJldHVybiB2ZXJkaWN0ID09PSAnc3VwZXJzZWRlZCdcbiAgICA/ICdUaGUgbWFya2VyIG5vdyByZWNvcmRzIGEgbGF0ZXIgZW11bGF0b3Igb2YgdGhpcyBBVkQsIHdoaWNoIGhhcyBpdHMgb3duIHJlYXBlcjsgbm90aGluZyBsZWZ0IHRvIHdhdGNoLidcbiAgICA6ICdUaGUgZW11bGF0b3Igd2FzIHN0b3BwZWQ7IG5vdGhpbmcgbGVmdCB0byB3YXRjaC4nO1xufVxuXG5mdW5jdGlvbiBnZXRSZWFwZXJMb2dEaXJlY3RvcnkoKTogc3RyaW5nIHtcbiAgcmV0dXJuIGpvaW4odG1wZGlyKCksIEhBUk5FU1NfVEVNUF9ESVJfTkFNRSk7XG59XG5cbmZ1bmN0aW9uIHJlYWRGaWxlU2l6ZShwYXRoOiBzdHJpbmcpOiBudW1iZXIgfCB1bmRlZmluZWQge1xuICB0cnkge1xuICAgIHJldHVybiBzdGF0U3luYyhwYXRoKS5zaXplO1xuICB9IGNhdGNoIHtcbiAgICByZXR1cm4gdW5kZWZpbmVkO1xuICB9XG59XG5cbmZ1bmN0aW9uIHJlYWRXYXRjaFZlcmRpY3QocmVhcGVyQXJndW1lbnRzOiBFbXVsYXRvclJlYXBlckFyZ3VtZW50cyk6IEVtdWxhdG9yUmVhcGVyV2F0Y2hWZXJkaWN0IHtcbiAgcmV0dXJuIHJlc29sdmVFbXVsYXRvclJlYXBlcldhdGNoKHtcbiAgICBhcm1lZFN0YXJ0ZWRBdEluTWlsbGlzZWNvbmRzOiByZWFwZXJBcmd1bWVudHMuc3RhcnRlZEF0SW5NaWxsaXNlY29uZHMsXG4gICAgY2hlY2tJc1BpZEFsaXZlOiBjaGVja0lzUHJvY2Vzc0FsaXZlLFxuICAgIG1hcmtlcjogcmVhZEVtdWxhdG9yTWFya2VyKHJlYXBlckFyZ3VtZW50cy5hdmROYW1lKVxuICB9KTtcbn1cblxuLyoqXG4gKiBGaW5kcyB0aGlzIG1vZHVsZSdzIG93biBVUkwgXHUyMDE0IHRoZSBvbmUgdGhlIGJvb3RzdHJhcCBpbXBvcnRzLlxuICpcbiAqIFRoZSBDSlMgYnVpbGQgaGFzIG5vIGBpbXBvcnQubWV0YWAgKGVzYnVpbGQgZW1wdGllcyBpdCksIGJ1dCBpdCBoYXNcbiAqIGBfX2ZpbGVuYW1lYDsgdGhlIEVTTSBidWlsZCBhbmQgdGhlIGAudHNgIHNvdXJjZSBoYXZlIGBpbXBvcnQubWV0YS51cmxgLlxuICpcbiAqIEByZXR1cm5zIFRoZSBVUkwgb2YgdGhpcyBtb2R1bGUgYXMgaXQgaXMgcnVubmluZy5cbiAqL1xuZnVuY3Rpb24gcmVzb2x2ZU93bk1vZHVsZVVybCgpOiBzdHJpbmcge1xuICByZXR1cm4gdHlwZW9mIF9fZmlsZW5hbWUgPT09ICdzdHJpbmcnID8gcGF0aFRvRmlsZVVSTChfX2ZpbGVuYW1lKS5ocmVmIDogaW1wb3J0Lm1ldGEudXJsO1xufVxuXG4vKiB2OCBpZ25vcmUgc3RvcCAqL1xuIl0sCiAgIm1hcHBpbmdzIjogIjs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBQUE7QUFBQTtBQUFBO0FBdUVBLGdDQUFzQjtBQUN0QixxQkFLTztBQUNQLHFCQUF1QjtBQUN2Qix1QkFBcUI7QUFDckIsMEJBQW9C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
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file
|
|
3
|
+
*
|
|
4
|
+
* Stops an emulator this harness started once its run is gone for good — the
|
|
5
|
+
* case nothing else covers, **L56**'s path B with no later run: the runner is
|
|
6
|
+
* killed (SIGKILL, Task Manager, an IDE stop button), no teardown runs, and no
|
|
7
|
+
* other Android run ever comes along to reclaim the leftover from its marker.
|
|
8
|
+
* On 2026-09-10 an emulator left like that kept `netsimd` spinning for six hours.
|
|
9
|
+
*
|
|
10
|
+
* The reaper is a small detached Node process spawned beside every emulator the
|
|
11
|
+
* harness starts or takes over. It holds no handle to the emulator and needs
|
|
12
|
+
* none: the marker (`emulator-marker.ts`) says what to stop, and the verified
|
|
13
|
+
* stop (`emulator-reclaim.ts`) is the one the run's own teardown uses.
|
|
14
|
+
*
|
|
15
|
+
* ## It watches the `android` setup lock, not the process that started it
|
|
16
|
+
*
|
|
17
|
+
* The process that starts the emulator is not always the run. A project with no
|
|
18
|
+
* transport global setup boots it from a **test worker** (**L56** path A), and
|
|
19
|
+
* Vitest ends workers during a perfectly healthy run — so a reaper tied to its
|
|
20
|
+
* spawner's lifetime (the **L33** liveness socket, the first idea) would stop
|
|
21
|
+
* the emulator mid-run, or race the next worker taking it over. The `android`
|
|
22
|
+
* setup lock (**L7**) is held by the run's main process for the whole run, and
|
|
23
|
+
* its holder being alive is exactly what "an Android run is in flight" means.
|
|
24
|
+
*
|
|
25
|
+
* So the reaper polls that lock, and once it can **take** it — no live run
|
|
26
|
+
* holds it — it stops the harness's leftovers while holding it, which is the
|
|
27
|
+
* precondition every leftover stop documents: no run can adopt an emulator that
|
|
28
|
+
* is mid-shutdown. That is precisely what the next run's preflight would do,
|
|
29
|
+
* done at the first moment that run could have done it.
|
|
30
|
+
*
|
|
31
|
+
* A dead same-host holder is abandoned at once. A live-PID holder that has gone
|
|
32
|
+
* silent is only abandoned after
|
|
33
|
+
* {@link EMULATOR_REAPER_LOCK_SILENCE_IN_MILLISECONDS}, far wider than the
|
|
34
|
+
* two minutes a waiting run steals on: a reaper is always waiting, so at two
|
|
35
|
+
* minutes a live run that blocked its event loop would lose its emulator.
|
|
36
|
+
*
|
|
37
|
+
* ## Fail-open, like the renderer watchdog
|
|
38
|
+
*
|
|
39
|
+
* A reaper is armed only when a live run holds the lock at the moment the
|
|
40
|
+
* emulator is recorded. A run that takes no lock — a hand-wired
|
|
41
|
+
* `createTransportFromOptions` — gets none, and says so, rather than a reaper
|
|
42
|
+
* that would read its missing lock as "the run is over" and stop an emulator
|
|
43
|
+
* somebody is using. A spawn that fails is logged and never fails the launch.
|
|
44
|
+
*
|
|
45
|
+
* ## How it runs
|
|
46
|
+
*
|
|
47
|
+
* `process.execPath -e <bootstrap> <this module's URL> <avdName> <startedAt>`:
|
|
48
|
+
* the bootstrap `import()`s this very module and calls {@link runEmulatorReaper}.
|
|
49
|
+
* The URL is the built `.mjs` / `.cjs` in a consumer's install, and the `.ts`
|
|
50
|
+
* source in this repo's own suites, which Node's type stripping runs as-is — so
|
|
51
|
+
* nothing this module imports may reach a module that reads the build-time
|
|
52
|
+
* `OBSIDIAN_METADATA` global. Its stdout/stderr go to a per-AVD log file, capped
|
|
53
|
+
* at {@link EMULATOR_REAPER_LOG_MAX_SIZE_IN_BYTES}, so the verdict lines the stop
|
|
54
|
+
* prints are on record even though no terminal is left to show them.
|
|
55
|
+
*
|
|
56
|
+
* ## It is started through a relay, so a process-tree kill cannot take it
|
|
57
|
+
*
|
|
58
|
+
* `detached` does not reparent on Windows: a child spawned straight from the run
|
|
59
|
+
* stays in the run's process tree, so the kill shapes that walk that tree take
|
|
60
|
+
* the safety net along with the thing it was guarding — Task Manager's *End
|
|
61
|
+
* task*, `taskkill /T`, an IDE stop button, a `ParentProcessId` descendant
|
|
62
|
+
* sweep. Measured on 2026-09-11: a reaper died with the run it was watching, and
|
|
63
|
+
* the emulator it would have stopped idled on for ten minutes.
|
|
64
|
+
*
|
|
65
|
+
* So the spawn goes through a relay — {@link buildEmulatorReaperRelayBootstrap} —
|
|
66
|
+
* which starts the reaper and exits at once. By the time any tree-walker takes
|
|
67
|
+
* its snapshot the relay is gone, so there is no edge from the run to the reaper
|
|
68
|
+
* left to follow. This is the double-fork idiom, and it holds on POSIX for the
|
|
69
|
+
* same reason: once the relay exits, the reaper belongs to `init`.
|
|
70
|
+
*/
|
|
71
|
+
import type { EmulatorMarker } from './emulator-marker.cjs';
|
|
72
|
+
/**
|
|
73
|
+
* How often the reaper looks at the marker and the lock — the lock's own
|
|
74
|
+
* heartbeat interval, so a killed run is noticed within one beat.
|
|
75
|
+
*/
|
|
76
|
+
export declare const EMULATOR_REAPER_POLL_INTERVAL_IN_MILLISECONDS = 5000;
|
|
77
|
+
/**
|
|
78
|
+
* How long a same-host lock holder whose PID is still alive may stay silent
|
|
79
|
+
* before the reaper treats its run as gone: 30 minutes, the threshold the lock
|
|
80
|
+
* already uses across hosts. Only a recycled PID should ever reach it.
|
|
81
|
+
*/
|
|
82
|
+
export declare const EMULATOR_REAPER_LOCK_SILENCE_IN_MILLISECONDS = 1800000;
|
|
83
|
+
/**
|
|
84
|
+
* The size past which the reaper log is started afresh rather than appended to.
|
|
85
|
+
*/
|
|
86
|
+
export declare const EMULATOR_REAPER_LOG_MAX_SIZE_IN_BYTES = 1048576;
|
|
87
|
+
/**
|
|
88
|
+
* The export the bootstrap calls. Named here so the bootstrap and the export
|
|
89
|
+
* cannot drift apart.
|
|
90
|
+
*/
|
|
91
|
+
export declare const EMULATOR_REAPER_ENTRY_NAME = "runEmulatorReaper";
|
|
92
|
+
/**
|
|
93
|
+
* What the reaper watches for: the emulator a marker recorded when it was armed.
|
|
94
|
+
*/
|
|
95
|
+
export interface EmulatorReaperArguments {
|
|
96
|
+
/**
|
|
97
|
+
The AVD whose marker it watches.
|
|
98
|
+
*/
|
|
99
|
+
readonly avdName: string;
|
|
100
|
+
/**
|
|
101
|
+
* When the watched emulator was launched. A takeover keeps it, so it names one
|
|
102
|
+
* emulator across every run that owns it, and tells a later emulator of the
|
|
103
|
+
* same AVD apart.
|
|
104
|
+
*/
|
|
105
|
+
readonly startedAtInMilliseconds: number;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* What the marker says the reaper should do next.
|
|
109
|
+
*
|
|
110
|
+
* - `emulator-stopped` — the marker is gone, or none of its PIDs is alive: the
|
|
111
|
+
* emulator was stopped, so there is nothing left to watch.
|
|
112
|
+
* - `superseded` — the marker now records a different emulator of the same AVD,
|
|
113
|
+
* which has its own reaper.
|
|
114
|
+
* - `watch` — the emulator is still up.
|
|
115
|
+
*/
|
|
116
|
+
export type EmulatorReaperWatchVerdict = 'emulator-stopped' | 'superseded' | 'watch';
|
|
117
|
+
/**
|
|
118
|
+
* Parameters for {@link resolveEmulatorReaperWatch}.
|
|
119
|
+
*/
|
|
120
|
+
export interface ResolveEmulatorReaperWatchParams {
|
|
121
|
+
/**
|
|
122
|
+
The launch time of the emulator this reaper was armed for.
|
|
123
|
+
*/
|
|
124
|
+
readonly armedStartedAtInMilliseconds: number;
|
|
125
|
+
/**
|
|
126
|
+
Whether a PID is still running.
|
|
127
|
+
*/
|
|
128
|
+
readonly checkIsPidAlive: (pid: number) => boolean;
|
|
129
|
+
/**
|
|
130
|
+
The AVD's marker as it reads now, if any.
|
|
131
|
+
*/
|
|
132
|
+
readonly marker: EmulatorMarker | undefined;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Parameters for {@link spawnEmulatorReaper}.
|
|
136
|
+
*/
|
|
137
|
+
export interface SpawnEmulatorReaperParams {
|
|
138
|
+
/**
|
|
139
|
+
The AVD whose emulator was just recorded.
|
|
140
|
+
*/
|
|
141
|
+
readonly avdName: string;
|
|
142
|
+
/**
|
|
143
|
+
Receives the armed / not-armed line.
|
|
144
|
+
*/
|
|
145
|
+
readonly log: (message: string) => void;
|
|
146
|
+
/**
|
|
147
|
+
The recorded emulator's launch time, as written into its marker.
|
|
148
|
+
*/
|
|
149
|
+
readonly startedAtInMilliseconds: number;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Encodes the reaper's arguments for its command line.
|
|
153
|
+
*
|
|
154
|
+
* @param reaperArguments - What the reaper watches for.
|
|
155
|
+
* @returns The command-line arguments, in the order {@link parseEmulatorReaperArguments} reads them.
|
|
156
|
+
*/
|
|
157
|
+
export declare function buildEmulatorReaperArguments(reaperArguments: EmulatorReaperArguments): string[];
|
|
158
|
+
/**
|
|
159
|
+
* Builds the script `node -e` runs to start the reaper.
|
|
160
|
+
*
|
|
161
|
+
* It `import()`s the module whose URL is its first argument and hands the rest
|
|
162
|
+
* to {@link runEmulatorReaper}. `import()` rather than `require`, because it
|
|
163
|
+
* loads all three forms the module ships as — ESM, CJS, and this repo's own
|
|
164
|
+
* `.ts` source under Node's type stripping. A CJS module's named exports are
|
|
165
|
+
* found by Node's static analysis; `default` is the fallback when they are not.
|
|
166
|
+
*
|
|
167
|
+
* @returns The script.
|
|
168
|
+
*/
|
|
169
|
+
export declare function buildEmulatorReaperBootstrap(): string;
|
|
170
|
+
/**
|
|
171
|
+
* Builds the script `node -e` runs as the **relay**: it starts the reaper and
|
|
172
|
+
* exits at once, so the reaper is left with a dead parent and no tree-walking
|
|
173
|
+
* kill of the run can reach it. See the relay section in this module's header
|
|
174
|
+
* for why that indirection exists at all.
|
|
175
|
+
*
|
|
176
|
+
* Its own arguments are the reaper's whole command line — the bootstrap script
|
|
177
|
+
* first, then everything that bootstrap reads — so the relay never has to know
|
|
178
|
+
* what any of them mean. It hands the reaper its own stdout and stderr, which
|
|
179
|
+
* are the capped log file the spawner opened, so the log is unaffected by the
|
|
180
|
+
* extra hop.
|
|
181
|
+
*
|
|
182
|
+
* @returns The script.
|
|
183
|
+
*/
|
|
184
|
+
export declare function buildEmulatorReaperRelayBootstrap(): string;
|
|
185
|
+
/**
|
|
186
|
+
* Decodes the reaper's command-line arguments.
|
|
187
|
+
*
|
|
188
|
+
* @param argv - The arguments after the module URL.
|
|
189
|
+
* @returns The arguments, or `undefined` when they are not the two {@link buildEmulatorReaperArguments} writes.
|
|
190
|
+
*/
|
|
191
|
+
export declare function parseEmulatorReaperArguments(argv: readonly string[]): EmulatorReaperArguments | undefined;
|
|
192
|
+
/**
|
|
193
|
+
* Decides how the reaper log is opened, so it can never grow without bound.
|
|
194
|
+
*
|
|
195
|
+
* @param sizeInBytes - The log's current size, or `undefined` when there is none.
|
|
196
|
+
* @returns `'a'` to append, or `'w'` to start it afresh once it has passed the cap.
|
|
197
|
+
*/
|
|
198
|
+
export declare function resolveEmulatorReaperLogOpenFlag(sizeInBytes: number | undefined): 'a' | 'w';
|
|
199
|
+
/**
|
|
200
|
+
* Decides what the marker says the reaper should do next.
|
|
201
|
+
*
|
|
202
|
+
* @param params - The marker as it reads now, and the emulator the reaper was armed for.
|
|
203
|
+
* @returns The verdict.
|
|
204
|
+
*/
|
|
205
|
+
export declare function resolveEmulatorReaperWatch(params: ResolveEmulatorReaperWatchParams): EmulatorReaperWatchVerdict;
|
|
206
|
+
/**
|
|
207
|
+
* The reaper process's body: watches the marker and the lock until either the
|
|
208
|
+
* emulator is gone or no run holds the lock, and in the second case stops the
|
|
209
|
+
* harness's leftovers while holding it.
|
|
210
|
+
*
|
|
211
|
+
* Exported for the bootstrap, which is its only caller.
|
|
212
|
+
*
|
|
213
|
+
* @param argv - The reaper's arguments, as {@link buildEmulatorReaperArguments} wrote them.
|
|
214
|
+
*/
|
|
215
|
+
export declare function runEmulatorReaper(argv: readonly string[]): Promise<void>;
|
|
216
|
+
/**
|
|
217
|
+
* Starts the reaper for an emulator the harness just recorded as its own, when
|
|
218
|
+
* a live run holds the `android` setup lock — and says which it did.
|
|
219
|
+
*
|
|
220
|
+
* Never throws: a reaper that cannot be spawned is a lost safety net, not a
|
|
221
|
+
* reason to fail the launch it would have guarded.
|
|
222
|
+
*
|
|
223
|
+
* @param params - The recorded emulator, and where to report.
|
|
224
|
+
*/
|
|
225
|
+
export declare function spawnEmulatorReaper(params: SpawnEmulatorReaperParams): void;
|