mirror of
https://github.com/morluto/rea.git
synced 2026-11-09 16:26:36 +00:00
fix(adb): report observed output after failed file pull (#9596)
This commit is contained in:
1 parent
a6c10a28f4
commit
a874cef9af
4 files changed
+113
-2
No files matched your search
@@ -188,7 +188,7 @@ and during shutdown.
|
||||
property value remains available in `properties`.
|
||||
- **Device paths are not host paths.** `base_apk_device_path` values live on
|
||||
the device filesystem. Only pulls and captures produce host-local paths,
|
||||
each paired with byte size and SHA-256 digest. A pulled file's host name is
|
||||
with byte size and SHA-256 digest on success. A pulled file's host name is
|
||||
the device path's final POSIX segment. REA preserves the original device
|
||||
path and filename; a missing final segment or `.`/`..` is refused before
|
||||
output creation. Windows hosts follow [Windows filename rules](https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file),
|
||||
@@ -249,6 +249,10 @@ and during shutdown.
|
||||
- Pull and capture outputs are additive: the destination must not already
|
||||
exist. Re-running a completed pull refuses the existing directory instead
|
||||
of overwriting evidence.
|
||||
- If a single-file pull fails and REA observes a regular file at the destination,
|
||||
the error includes its `local_path` with `transfer_status: "unconfirmed"`
|
||||
and `digest_status: "unknown"`. The file remains in place; the receipt does
|
||||
not establish that the transfer completed.
|
||||
- Cancellation during a package pull preserves caller-selected output. The
|
||||
error includes partial observations identifying completed artifacts and the
|
||||
interrupted local path; remaining APKs were not attempted.
|
||||
|
||||
+20
-1
@@ -2333,7 +2333,26 @@ export class AdbProvider {
|
||||
{ timeoutMs: PULL_TIMEOUT_MS, maxOutputBytes: PULL_MAX_OUTPUT_BYTES },
|
||||
signal,
|
||||
);
|
||||
if (!pulled.ok) return pulled;
|
||||
if (!pulled.ok) {
|
||||
// A failed adb process can leave caller-visible bytes at the selected
|
||||
// destination. Report only an observed regular file; its transfer and
|
||||
// digest are unconfirmed, and the caller owns the output.
|
||||
const output = await lstat(localPath).catch(() => undefined);
|
||||
if (output?.isFile() === true && !output.isSymbolicLink())
|
||||
pulled.error.retainPartialObservation({
|
||||
provider_id: "adb",
|
||||
operation,
|
||||
result: {
|
||||
client,
|
||||
serial: input.serial,
|
||||
local_path: localPath,
|
||||
device_path: input.device_path,
|
||||
digest_status: "unknown",
|
||||
transfer_status: "unconfirmed",
|
||||
},
|
||||
});
|
||||
return pulled;
|
||||
}
|
||||
try {
|
||||
const digest = await hashAdbFile(
|
||||
localPath,
|
||||
|
||||
@@ -892,6 +892,7 @@ export type AdbAcquiredFilePartialObservation =
|
||||
readonly local_path: string;
|
||||
readonly device_path: string;
|
||||
readonly digest_status: "unknown";
|
||||
readonly transfer_status?: "unconfirmed";
|
||||
}
|
||||
| z.infer<typeof adbResultSchemas.pull_adb_file>;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import { dirname, join } from "node:path";
|
||||
import { expect, onTestFinished, test, vi } from "vitest";
|
||||
|
||||
import { AdbProvider } from "../../../../src/adb/AdbProvider.js";
|
||||
import { projectAnalysisError } from "../../../../src/domain/analysisErrorProjection.js";
|
||||
import { AdbDeviceAnalysisService } from "../../../../src/application/adb/AdbDeviceAnalysisService.js";
|
||||
import { PrivateRuntimeRoot } from "../../../../src/process/PrivateRuntimeRoot.js";
|
||||
import { createTestTempDirectory } from "../../../fixtures/temporaryDirectory.js";
|
||||
@@ -246,3 +247,89 @@ it.each([
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it.each([true, false])(
|
||||
"reports only observed output when adb pull fails (file created: %s)",
|
||||
async (created) => {
|
||||
const root = await createTestTempDirectory("rea-adb-partial-pull-");
|
||||
const binary = join(root, "adb");
|
||||
await writeFile(
|
||||
binary,
|
||||
`#!/bin/sh
|
||||
if [ "$1" = version ]; then printf 'Android Debug Bridge version 1.0.41\\nVersion 34.0.5-stub\\nInstalled as %s\\n' "$0"; exit 0; fi
|
||||
if [ "$3" = pull ]; then ${created ? `printf 'partial bytes' > "$5";` : ""} printf 'transfer interrupted\\n' >&2; exit 1; fi
|
||||
exit 1
|
||||
`,
|
||||
{ mode: 0o755 },
|
||||
);
|
||||
const provider = new AdbProvider({ environment: { REA_ADB_PATH: binary } });
|
||||
onTestFinished(() => provider.close());
|
||||
const result = await provider.execute({
|
||||
operation: "pull_adb_file",
|
||||
input: {
|
||||
serial: "fixture",
|
||||
device_path: "/sdcard/payload.bin",
|
||||
output_directory: root,
|
||||
},
|
||||
});
|
||||
if (result.ok) throw new Error("expected failed pull");
|
||||
if (created) {
|
||||
expect(await readFile(join(root, "payload.bin"), "utf8")).toBe(
|
||||
"partial bytes",
|
||||
);
|
||||
expect(
|
||||
projectAnalysisError(result.error).details?.partial_observation,
|
||||
).toMatchObject({
|
||||
operation: "pull_adb_file",
|
||||
result: {
|
||||
local_path: join(root, "payload.bin"),
|
||||
digest_status: "unknown",
|
||||
transfer_status: "unconfirmed",
|
||||
},
|
||||
});
|
||||
} else {
|
||||
await expect(access(join(root, "payload.bin"))).rejects.toMatchObject({
|
||||
code: "ENOENT",
|
||||
});
|
||||
expect(result.error.partialObservation).toBeUndefined();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it.each(["file", "symlink"] as const)(
|
||||
"refuses a preexisting %s destination without invoking adb pull",
|
||||
async (kind) => {
|
||||
const root = await createTestTempDirectory("rea-adb-existing-pull-");
|
||||
const binary = join(root, "adb");
|
||||
const destination = join(root, "payload.bin");
|
||||
const source = join(root, "original");
|
||||
await writeFile(source, "original bytes");
|
||||
if (kind === "file") await writeFile(destination, "original bytes");
|
||||
else await symlink(source, destination);
|
||||
await writeFile(
|
||||
binary,
|
||||
`#!/bin/sh
|
||||
if [ "$1" = version ]; then printf 'Android Debug Bridge version 1.0.41\\nVersion 34.0.5-stub\\nInstalled as %s\\n' "$0"; exit 0; fi
|
||||
if [ "$3" = pull ]; then printf 'invoked' > "$0.called"; exit 1; fi
|
||||
exit 1
|
||||
`,
|
||||
{ mode: 0o755 },
|
||||
);
|
||||
const provider = new AdbProvider({ environment: { REA_ADB_PATH: binary } });
|
||||
onTestFinished(() => provider.close());
|
||||
const result = await provider.execute({
|
||||
operation: "pull_adb_file",
|
||||
input: {
|
||||
serial: "fixture",
|
||||
device_path: "/sdcard/payload.bin",
|
||||
output_directory: root,
|
||||
},
|
||||
});
|
||||
if (result.ok) throw new Error("expected destination refusal");
|
||||
expect(result.error.partialObservation).toBeUndefined();
|
||||
expect(await readFile(destination, "utf8")).toBe("original bytes");
|
||||
await expect(access(`${binary}.called`)).rejects.toMatchObject({
|
||||
code: "ENOENT",
|
||||
});
|
||||
},
|
||||
);
|
||||
Reference in new issue
Block a user