fix(android): decode guardian output as UTF-8 streams

This commit is contained in:
morluto committed 2026-10-22 09:11:44 +08:00
1 parent 8dbe92a4a3
commit 73be86c39e
2 files changed
+95 -4

No files matched your search

+89
View File
@@ -0,0 +1,89 @@
import { EventEmitter } from "node:events";
import { win32 } from "node:path";
import { PassThrough } from "node:stream";
import { afterEach, expect, it, vi } from "vitest";
const { spawnMock, tmpdirMock, lstatMock } = vi.hoisted(() => ({
spawnMock: vi.fn(),
tmpdirMock: vi.fn(),
lstatMock: vi.fn(),
}));
vi.mock("node:child_process", async (importOriginal) => ({
...(await importOriginal<typeof import("node:child_process")>()),
spawn: spawnMock,
}));
vi.mock("node:os", async (importOriginal) => ({
...(await importOriginal<typeof import("node:os")>()),
tmpdir: tmpdirMock,
}));
vi.mock("node:fs/promises", async (importOriginal) => ({
...(await importOriginal<typeof import("node:fs/promises")>()),
lstat: lstatMock,
}));
import { createJadxWorkspaceRoot } from "./JadxWorkspaceRoot.js";
afterEach(() => {
vi.restoreAllMocks();
vi.clearAllMocks();
});
it("decodes a Windows guardian path across split UTF-8 stdout writes", async () => {
const previousPlatform = process.platform;
Object.defineProperty(process, "platform", {
configurable: true,
value: "win32",
});
const parent = "C:\\Users\\研究者\\AppData\\Local\\Temp";
const path = win32.join(parent, `rea-android-${"a".repeat(32)}`);
const child = Object.assign(new EventEmitter(), {
stdin: new PassThrough(),
stdout: new PassThrough(),
stderr: new PassThrough(),
unref: vi.fn(),
});
child.stdin.once("finish", () => {
child.stdout.end();
child.stderr.end();
child.emit("close", 0, null);
});
tmpdirMock.mockReturnValue(parent);
const missingPath = Object.assign(new Error("missing after cleanup"), {
code: "ENOENT",
});
lstatMock.mockRejectedValue(missingPath);
spawnMock.mockReturnValue(child);
const ready = Buffer.from(
`${JSON.stringify({
path,
volumeSerial: "0123456789abcdef",
fileId: "0123456789abcdef0123456789abcdef",
privateDacl: true,
})}\n`,
);
const split = ready.indexOf(Buffer.from("研")) + 1;
try {
const start = createJadxWorkspaceRoot();
setImmediate(() => {
child.stdout.write(ready.subarray(0, split), () => {
setImmediate(() => child.stdout.write(ready.subarray(split)));
});
});
const root = await start;
try {
expect(root.path).toBe(path);
} finally {
await expect(root.close()).resolves.toBeUndefined();
}
expect(lstatMock).toHaveBeenCalledWith(path);
} finally {
Object.defineProperty(process, "platform", {
configurable: true,
value: previousPlatform,
});
}
});
+6 -4
View File
@@ -105,8 +105,9 @@ class WindowsJadxWorkspaceRoot implements JadxWorkspaceRoot {
},
});
let diagnostics = "";
child.stderr.on("data", (chunk: Buffer) => {
diagnostics = (diagnostics + chunk.toString()).slice(-8192);
child.stderr.setEncoding("utf8");
child.stderr.on("data", (chunk: string) => {
diagnostics = (diagnostics + chunk).slice(-8192);
});
const exit = new Promise<GuardianExit>((resolve) => {
child.once("error", (error: Error) =>
@@ -116,8 +117,9 @@ class WindowsJadxWorkspaceRoot implements JadxWorkspaceRoot {
});
const ready = new Promise<string>((resolve, reject) => {
let frame = "";
child.stdout.on("data", (chunk: Buffer) => {
frame += chunk.toString();
child.stdout.setEncoding("utf8");
child.stdout.on("data", (chunk: string) => {
frame += chunk;
if (frame.length > 4096) {
reject(
new Error("Android workspace guardian readiness exceeded 4 KiB"),