mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
fix(adb): preserve process names and pin fixture interpreters
Consume Toybox’s single NAME separator without stripping source whitespace. Keep MCP subprocess fixtures independent of ambient PATH and validate calls through the existing named-schema helper. Validation: focused ADB provider/parser and MCP tests passed; no real Android device was used.
This commit is contained in:
1 parent
4da7e46e9c
commit
ab44487ff8
3 files changed
+39
-19
No files matched your search
@@ -1505,6 +1505,24 @@ exit 1`,
|
||||
}
|
||||
});
|
||||
|
||||
it("preserves process names printed by Toybox without treating their whitespace as column padding", async () => {
|
||||
// Toybox's final NAME column is left justified without padding; only the
|
||||
// single separator after S belongs to the table (toys/posix/ps.c show_ps).
|
||||
const name = " native tool ";
|
||||
const { binary } = await writeAdbStub(
|
||||
shellStub({
|
||||
ps: `USER PID PPID VSZ RSS WCHAN ADDR S NAME\nroot 1234 567 123456 7890 0 0 S ${name}\r\n`,
|
||||
}),
|
||||
);
|
||||
const result = await evidenceResult(providerOver(binary), {
|
||||
operation: "list_adb_processes",
|
||||
input: { serial: "emulator-5554" },
|
||||
});
|
||||
expect(result.processes).toEqual([
|
||||
{ user: "root", pid: 1234, ppid: 567, rss: 7890, state: "S", name },
|
||||
]);
|
||||
});
|
||||
|
||||
it("lists processes, features, and services through fixed commands", async () => {
|
||||
const { binary } = await writeAdbStub(
|
||||
shellStub({
|
||||
|
||||
@@ -27,8 +27,10 @@ export interface AdbProcessEntry {
|
||||
readonly name: string;
|
||||
}
|
||||
|
||||
// Toybox emits one separator before the unpadded final NAME column.
|
||||
// Remaining whitespace belongs to argv[0], not the table layout.
|
||||
const PROCESS_LINE =
|
||||
/^(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.+)$/u;
|
||||
/^(\S+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(\S+)\s+(\S+) (.+)$/u;
|
||||
|
||||
const safeInteger = (text: string): number | null => {
|
||||
const value = Number(text);
|
||||
@@ -46,22 +48,21 @@ export const parseProcessListOutput = (
|
||||
const processes: AdbProcessEntry[] = [];
|
||||
const unparsed = collectUnparsedLines();
|
||||
let columns: string | null = null;
|
||||
for (const rawLine of stdout.split(/\r?\n/u)) {
|
||||
const line = rawLine.trimEnd();
|
||||
if (line === "") continue;
|
||||
for (const line of stdout.split(/\r?\n/u)) {
|
||||
if (line.trim() === "") continue;
|
||||
if (/^USER\s+PID\s+PPID\b/u.test(line)) {
|
||||
columns = line.trim();
|
||||
continue;
|
||||
}
|
||||
const match = PROCESS_LINE.exec(line);
|
||||
if (match === null) {
|
||||
unparsed.add(rawLine);
|
||||
unparsed.add(line);
|
||||
continue;
|
||||
}
|
||||
const pid = safeInteger(match[2]!);
|
||||
const ppid = safeInteger(match[3]!);
|
||||
const rss = safeInteger(match[5]!);
|
||||
if (pid === null || ppid === null || rss === null) unparsed.add(rawLine);
|
||||
if (pid === null || ppid === null || rss === null) unparsed.add(line);
|
||||
if (pid === null) continue;
|
||||
processes.push({
|
||||
user: match[1]!,
|
||||
@@ -69,7 +70,7 @@ export const parseProcessListOutput = (
|
||||
ppid,
|
||||
rss,
|
||||
state: match[8]!,
|
||||
name: match[9]!.trim(),
|
||||
name: match[9]!,
|
||||
});
|
||||
}
|
||||
return { columns, processes, unparsedLines: unparsed.facts() };
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { artifactMcpCall } from "../../../scripts/lib/artifact-e2e.mjs";
|
||||
import { parseMcpToolError } from "../../fixtures/mcpToolError.js";
|
||||
import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
|
||||
import { Ajv2020 } from "ajv/dist/2020.js";
|
||||
@@ -28,7 +29,7 @@ it.skipIf(process.platform === "win32")(
|
||||
await chmod(localPath, 0o755);
|
||||
await writeFile(
|
||||
adbPath,
|
||||
`#!/usr/bin/env node
|
||||
`#!${process.execPath}
|
||||
const fs = require("node:fs");
|
||||
const args = process.argv.slice(2);
|
||||
if (args[0] === "version") { process.stdout.write("Android Debug Bridge version 1.0.41\\nVersion 34.0.5-stub\\n"); }
|
||||
@@ -63,7 +64,7 @@ else if (args[0] === "-s" && args[2] === "shell" && args[3] === "test") {
|
||||
const client = new Client({ name: "adb-push-boundary", version: "0.0.0" });
|
||||
onTestFinished(() => client.close());
|
||||
await client.connect(clientTransport);
|
||||
const response = await client.callTool({
|
||||
const response = await artifactMcpCall(client, {
|
||||
name: "push_adb_file",
|
||||
arguments: {
|
||||
serial: "emulator-5554",
|
||||
@@ -104,7 +105,7 @@ const createFakeAdb = async () => {
|
||||
const stderrFile = join(directory, "stderr");
|
||||
const callsFile = join(directory, "calls");
|
||||
const exitCodeFile = join(directory, "exit-code");
|
||||
const fakeAdbSource = `#!/usr/bin/env node
|
||||
const fakeAdbSource = `#!${process.execPath}
|
||||
const fs = require("node:fs");
|
||||
const [stdoutPath, stderrPath, callsPath, exitCodePath] = ${JSON.stringify([stdoutFile, stderrFile, callsFile, exitCodeFile])};
|
||||
const args = process.argv.slice(2);
|
||||
@@ -158,7 +159,7 @@ const assertAdvertisedActivityExtras = async (
|
||||
).toBe(true);
|
||||
// Canonical validation rejects malformed extras before starting ADB.
|
||||
const callsBeforeInvalidExtra = await calls();
|
||||
const invalidExtra = await client.callTool({
|
||||
const invalidExtra = await artifactMcpCall(client, {
|
||||
name: "start_adb_activity",
|
||||
arguments: {
|
||||
...input,
|
||||
@@ -220,7 +221,7 @@ it
|
||||
{ key: "scale", type: "float" as const, value: "1e-3" },
|
||||
],
|
||||
};
|
||||
const success = await client.callTool({
|
||||
const success = await artifactMcpCall(client, {
|
||||
name: operation,
|
||||
arguments: input,
|
||||
});
|
||||
@@ -241,7 +242,7 @@ it
|
||||
|
||||
stdout = `${starting}Status: timeout\nActivity: com.example/.Main\nWaitTime: 10004\nComplete\n`;
|
||||
await fakeAdb.setReport(stdout, stderr);
|
||||
const timeout = await client.callTool({
|
||||
const timeout = await artifactMcpCall(client, {
|
||||
name: operation,
|
||||
arguments: input,
|
||||
});
|
||||
@@ -272,7 +273,7 @@ it
|
||||
stdout = `${starting}Status: ok\nActivity: com.example/.Main\nComplete\n`;
|
||||
stderr = "Exception: Activity class does not exist\n";
|
||||
await fakeAdb.setReport(stdout, stderr);
|
||||
const hardError = await client.callTool({
|
||||
const hardError = await artifactMcpCall(client, {
|
||||
name: operation,
|
||||
arguments: input,
|
||||
});
|
||||
@@ -362,7 +363,7 @@ it.skipIf(process.platform === "win32")(
|
||||
validateFormats: false,
|
||||
}).compile(advertised.inputSchema);
|
||||
expect(validateInput(input)).toBe(true);
|
||||
const refused = await client.callTool({
|
||||
const refused = await artifactMcpCall(client, {
|
||||
name: "start_adb_activity",
|
||||
arguments: input,
|
||||
});
|
||||
@@ -393,7 +394,7 @@ it.skipIf(process.platform === "win32")(
|
||||
"Starting: Intent { act=android.intent.action.MAIN cmp=com.example/.Outer$InnerActivity }\n";
|
||||
const transportStderr = "error: device offline\n";
|
||||
await fakeAdb.setReport(transportStdout, transportStderr, 1);
|
||||
const transport = await client.callTool({
|
||||
const transport = await artifactMcpCall(client, {
|
||||
name: "start_adb_activity",
|
||||
arguments: input,
|
||||
});
|
||||
@@ -411,7 +412,7 @@ it.skipIf(process.platform === "win32")(
|
||||
"Starting: Intent { act=android.intent.action.MAIN cmp=com.example/.Outer$InnerActivity }\n";
|
||||
const incompleteStderr = "transport disconnected by peer\n";
|
||||
await fakeAdb.setReport(incompleteStdout, incompleteStderr, 1);
|
||||
const incomplete = await client.callTool({
|
||||
const incomplete = await artifactMcpCall(client, {
|
||||
name: "start_adb_activity",
|
||||
arguments: input,
|
||||
});
|
||||
@@ -435,7 +436,7 @@ it.skipIf(process.platform === "win32")(
|
||||
const stderrOnly =
|
||||
"Error: Activity class {com.example/com.example.Outer$InnerActivity} does not exist.\n";
|
||||
await fakeAdb.setReport("", stderrOnly, 1);
|
||||
const stderrOnlyFailure = await client.callTool({
|
||||
const stderrOnlyFailure = await artifactMcpCall(client, {
|
||||
name: "start_adb_activity",
|
||||
arguments: input,
|
||||
});
|
||||
@@ -461,7 +462,7 @@ it.skipIf(process.platform === "win32")(
|
||||
"Error: " +
|
||||
"x".repeat(70 * 1024);
|
||||
await fakeAdb.setReport(oversizedReport, "", 1);
|
||||
const truncated = await client.callTool({
|
||||
const truncated = await artifactMcpCall(client, {
|
||||
name: "start_adb_activity",
|
||||
arguments: input,
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user