106 lines
3.3 KiB
JavaScript
106 lines
3.3 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { spawn } from "node:child_process";
|
|
import { stat, writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import { createInterface } from "node:readline";
|
|
import { setTimeout as delay } from "node:timers/promises";
|
|
import { fileURLToPath } from "node:url";
|
|
import { createJadxWorkspaceRoot } from "../../../dist/android/JadxWorkspaceRoot.js";
|
|
|
|
const source = fileURLToPath(import.meta.url);
|
|
const marker = "REA synthetic guardian isolation test\n";
|
|
const exists = (path) =>
|
|
stat(path).then(
|
|
() => true,
|
|
() => false,
|
|
);
|
|
|
|
if (process.argv[1] === source && process.argv[2] === "owner") {
|
|
const root = await createJadxWorkspaceRoot();
|
|
await writeFile(join(root.path, "synthetic.txt"), marker);
|
|
process.stdout.write(JSON.stringify({ path: root.path }) + "\n");
|
|
await new Promise(() => {});
|
|
}
|
|
|
|
const bounded = async (promise, milliseconds, label) => {
|
|
let timer;
|
|
try {
|
|
return await Promise.race([
|
|
promise,
|
|
new Promise((_, reject) => {
|
|
timer = setTimeout(() => reject(new Error(label)), milliseconds);
|
|
}),
|
|
]);
|
|
} finally {
|
|
clearTimeout(timer);
|
|
}
|
|
};
|
|
|
|
const launch = async () => {
|
|
const child = spawn(process.execPath, [source, "owner"], {
|
|
stdio: ["ignore", "pipe", "pipe"],
|
|
windowsHide: true,
|
|
});
|
|
let stderr = "";
|
|
child.stderr.on("data", (chunk) => {
|
|
stderr = (stderr + chunk.toString()).slice(-8192);
|
|
});
|
|
const exit = new Promise((resolve, reject) => {
|
|
child.once("close", resolve);
|
|
child.once("error", reject);
|
|
});
|
|
const lines = createInterface({ input: child.stdout, crlfDelay: Infinity });
|
|
try {
|
|
const ready = await bounded(
|
|
lines[Symbol.asyncIterator]().next(),
|
|
20000,
|
|
`Android guardian owner readiness timed out: ${stderr}`,
|
|
);
|
|
assert.equal(ready.done, false, stderr);
|
|
const { path } = JSON.parse(ready.value);
|
|
assert.equal(typeof path, "string");
|
|
assert.equal(await exists(join(path, "synthetic.txt")), true);
|
|
return { child, exit, path };
|
|
} catch (cause) {
|
|
child.kill();
|
|
await bounded(exit, 15000, "Android guardian owner did not exit");
|
|
throw cause;
|
|
}
|
|
};
|
|
|
|
const stop = async ({ child, exit, path }) => {
|
|
assert.equal(child.kill(), true);
|
|
await bounded(exit, 15000, "Android guardian owner did not exit");
|
|
for (let attempt = 0; attempt < 100; attempt++) {
|
|
if (!(await exists(path))) return;
|
|
await delay(100);
|
|
}
|
|
throw new Error(`Android guardian retained ${path} after owner death`);
|
|
};
|
|
|
|
/** Two independent owners must never delete one another's private workspace. */
|
|
export async function verifyWindowsGuardianIsolation() {
|
|
assert.equal(process.platform, "win32");
|
|
const first = await launch();
|
|
let second;
|
|
try {
|
|
second = await launch();
|
|
assert.notEqual(first.path, second.path);
|
|
await stop(first);
|
|
assert.equal(
|
|
await exists(join(second.path, "synthetic.txt")),
|
|
true,
|
|
"First owner deleted the second owner's workspace",
|
|
);
|
|
await stop(second);
|
|
console.log("PASS Windows Android guardian two-owner cleanup isolation");
|
|
} finally {
|
|
for (const owner of [first, second]) {
|
|
if (owner === undefined) continue;
|
|
if (owner.child.exitCode === null && owner.child.signalCode === null)
|
|
owner.child.kill();
|
|
await bounded(owner.exit, 15000, "Android guardian owner did not exit");
|
|
}
|
|
}
|
|
}
|