Files
rea/tests/fixtures/nativeCommands.ts
Kaoru0822andClaude Opus 5.5 3f5b703379 fix(native): derive Mach-O word size and byte order from the selected slice (#3163)
* fix(native): derive Mach-O word size and byte order from the selected slice

inspect_macho read endian and word_size from `file -b` for the whole
file. macOS `file` never prints a Mach-O byte order, so endian was null
for every target. For a universal binary, word_size matched "64-bit"
anywhere in the summary instead of describing the selected slice, and it
was null for a thin 32-bit slice.

Both now come from the selected slice's otool header: the magic gives
the word size and the CPU family gives the byte order (x86 and ARM
little, PowerPC big, other families unknown). The file capture is no
longer needed by inspect_macho, so it is no longer run there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(native): select a 32-bit slice beside a 64-bit slice through inspect_macho

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 22:44:41 -07:00

144 lines
4.6 KiB
TypeScript

import { createHash } from "node:crypto";
import { readFile } from "node:fs/promises";
import type { BinaryTarget } from "../../src/domain/binaryTargetTypes.js";
import { ok } from "../../src/domain/result.js";
import type {
NativeCommandCapture,
NativeCommandRunner,
} from "../../src/native/CommandRunner.js";
/** Replay captured native command outputs through the production runner seam. */
export class NativeFixtureRunner implements NativeCommandRunner {
constructor(
private readonly overrides: Readonly<Record<string, string>> = {},
private readonly exitCode = 0,
) {}
async run(tool: string, arguments_: readonly string[]) {
const output =
this.overrides[`${tool}:${arguments_[0] ?? ""}`] ??
this.overrides[tool] ??
(await outputFor(tool, arguments_));
return ok({
...capture(tool, arguments_, output),
exitCode: this.exitCode,
});
}
}
const outputFor = async (
tool: string,
arguments_: readonly string[],
): Promise<string> => {
if (tool === "lipo") return nativeFixture("lipo-fat.txt");
if (tool === "otool") return nativeFixture("otool-load.txt");
if (tool === "nm") return "_main\n_$s4Test3fooyyF\n";
if (tool === "dyld_info")
return nativeFixture(
arguments_[0] === "-imports" ? "dyld-imports.txt" : "dyld-exports.txt",
);
if (tool === "dwarfdump")
return `UUID: 01234567-89AB-CDEF-0123-456789ABCDEF (arm64) ${arguments_.at(-1)}\n`;
if (tool === "file")
return arguments_.at(-1)?.endsWith(".plist") === true
? "XML 1.0 document text\n"
: "Mach-O 64-bit executable arm64\n";
if (tool === "vtool") return "Load command 3 LC_BUILD_VERSION\n";
if (tool === "swift-demangle") return nativeFixture("demangle.txt");
if (tool === "plutil") return nativeFixture("plist.json");
if (tool === "codesign") {
if (arguments_.includes("--entitlements"))
return nativeFixture("entitlements.xml");
if (arguments_.includes("-r-"))
return "designated => identifier com.example.fixture\n";
return nativeFixture("codesign.txt");
}
throw new Error(`Unexpected fixture tool ${tool}`);
};
/**
* codesign prints display diagnostics to stderr and requirements or
* entitlements to stdout.
*/
const fixtureStreams = (
tool: string,
arguments_: readonly string[],
output: string,
) => {
const stderr =
tool === "codesign" &&
!arguments_.includes("-r-") &&
!arguments_.includes("--entitlements");
return {
stdout: stderr ? "" : output,
stderr: stderr ? output : "",
stdoutBytes: Buffer.byteLength(stderr ? "" : output),
stderrBytes: Buffer.byteLength(stderr ? output : ""),
};
};
const capture = (
tool: string,
arguments_: readonly string[],
output: string,
): NativeCommandCapture => ({
tool,
executable: `/usr/bin/${tool}`,
executableSha256: "a".repeat(64),
toolVersion: null,
versionReason: "fixture",
arguments: [...arguments_],
...fixtureStreams(tool, arguments_, output),
exitCode: 0,
signal: null,
});
/** Build the Mach-O target identity used by native boundary fixtures. */
export const nativeMachoTarget = (
path: string,
sourcePath?: string,
): BinaryTarget & {
readonly kind: "executable";
readonly format: "mach-o";
} => ({
path,
...(sourcePath === undefined ? {} : { sourcePath }),
sha256: "0".repeat(64),
kind: "executable",
format: "mach-o",
architecture: "arm64",
availableArchitectures: ["x86_64", "arm64"],
});
/** Register the actual bytes of a filesystem-backed native test target. */
export const nativeMachoTargetForFile = async (
path: string,
sourcePath?: string,
): Promise<ReturnType<typeof nativeMachoTarget>> => ({
...nativeMachoTarget(path, sourcePath),
sha256: createHash("sha256")
.update(await readFile(path))
.digest("hex"),
});
/** Load one captured native tool output. */
export const nativeFixture = (name: string): Promise<string> =>
readFile(new URL(`./native-macos/${name}`, import.meta.url), "utf8");
/** Add consistent Mach command-count metadata to a command-only test capture. */
export const withOtoolMachHeader = (output: string): string => {
const count = [...output.matchAll(/^Load command (\d+)$/gmu)].length;
const sizes = [...output.matchAll(/^\s*cmdsize\s+(\d+)\s*$/gmu)].map(
([, size]) => Number(size),
);
if (sizes.length !== count)
throw new TypeError("command-only fixture must provide every cmdsize");
const bytes = sizes.reduce((sum, size) => sum + size, 0);
return [
"Mach header",
" magic cputype cpusubtype caps filetype ncmds sizeofcmds flags",
` 0xfeedfacf 16777228 0 0x00 2 ${count} ${bytes} 0x00000000`,
output,
].join("\n");
};