diff --git a/src/artifacts/MachOSliceArtifactReader.ts b/src/artifacts/MachOSliceArtifactReader.ts index f567458c7..b57b0247e 100644 --- a/src/artifacts/MachOSliceArtifactReader.ts +++ b/src/artifacts/MachOSliceArtifactReader.ts @@ -8,7 +8,7 @@ import { type NativeCommandRunner, } from "../native/CommandRunner.js"; import { - parseLipoArchitectures, + parseLipoOutput, type LipoArchitecture, } from "../native/parsers/lipo.js"; import type { MachoSlice } from "../domain/apple/dylibResolution.js"; @@ -114,7 +114,7 @@ export class MachOSliceArtifactReader implements ArtifactReader { }; await this.#verifySource(source); const fileSize = source.initial.size; - const architectures = parseLipoArchitectures(captured.value.stdout); + const { architectures } = parseLipoOutput(captured.value.stdout, this.path); const structural = await readStructuralSlices(source.handle, fileSize); await this.#verifySource(source); if (structural.status === "malformed") diff --git a/src/native/NativeMacOSProvider.ts b/src/native/NativeMacOSProvider.ts index a59ec90a7..691e830d9 100644 --- a/src/native/NativeMacOSProvider.ts +++ b/src/native/NativeMacOSProvider.ts @@ -73,7 +73,7 @@ import { } from "./SignatureTargetBinding.js"; import { parseCodeSignature } from "./parsers/codesign.js"; import { parseDemangledSymbols } from "./parsers/demangle.js"; -import { parseLipoArchitectures } from "./parsers/lipo.js"; +import { parseLipoOutput } from "./parsers/lipo.js"; import { parsePlistJson, parsePlistXml, @@ -507,7 +507,10 @@ class NativeMacOSClient implements AnalysisClient { ); if (!capture.ok) return capture; const provenance = [invocation(capture.value, this.target.path)]; - const architectures = parseLipoArchitectures(capture.value.stdout); + const { architectures } = parseLipoOutput( + capture.value.stdout, + this.target.path, + ); const result = listArchitecturesSchema.parse({ architectures: { items: architectures, diff --git a/src/native/NativeMachoInspection.ts b/src/native/NativeMachoInspection.ts index 5fca089cb..bb1966c3d 100644 --- a/src/native/NativeMachoInspection.ts +++ b/src/native/NativeMachoInspection.ts @@ -14,10 +14,7 @@ import { selectMachoArchitectureName, } from "./MachoArchitectureSelection.js"; import { parseDyldSymbols } from "./parsers/dyldInfo.js"; -import { - parseLipoArchitectures, - type LipoArchitecture, -} from "./parsers/lipo.js"; +import { parseLipoOutput, type LipoArchitecture } from "./parsers/lipo.js"; import { withoutEchoedPathHeader } from "./parsers/echoedPath.js"; import { parseOtoolLoadCommands } from "./parsers/otool.js"; import { reconcileMachoUuid } from "./parsers/machoUuid.js"; @@ -74,7 +71,10 @@ export const inspectNativeMacho = async ( } const lipo = captures.find(({ tool }) => tool === "lipo"); if (lipo === undefined) throw new TypeError("Missing lipo capture"); - const architectures = parseLipoArchitectures(lipo.stdout); + const { format, architectures } = parseLipoOutput( + lipo.stdout, + context.target.path, + ); const selection = selectArchitecture(context.target, architectures); if (selection.status === "absent") return err( @@ -145,7 +145,7 @@ export const inspectNativeMacho = async ( const containerOffset = selectedArchitectureName === null ? undefined - : /^Non-fat file:/mu.test(lipo.stdout) + : format === "thin" ? 0 : (architectures.find(({ name }) => name === selectedArchitectureName) ?.file_offset ?? undefined); @@ -185,7 +185,7 @@ const normalizeMacho = ( toInvocation: (capture: NativeCommandCapture) => NativeCommandInvocation, additionalLimitations: readonly string[], load: ReturnType, - architectures: ReturnType, + architectures: readonly LipoArchitecture[], artifactPath: string, selectedArchitecture: { readonly name: string | null; diff --git a/src/native/parsers/lipo.test.ts b/src/native/parsers/lipo.test.ts index 96f2afbbc..b5c97a415 100644 --- a/src/native/parsers/lipo.test.ts +++ b/src/native/parsers/lipo.test.ts @@ -1,10 +1,28 @@ import { describe, expect, it } from "vitest"; -import { parseLipoArchitectures } from "./lipo.js"; +import { parseLipoOutput } from "./lipo.js"; + +const fatRecords = `fat_magic 0xcafebabe +nfat_arch 2 +architecture arm64 + cputype CPU_TYPE_ARM64 + cpusubtype CPU_SUBTYPE_ARM64_ALL + capabilities 0x0 + offset 4096 + size 32 + align 2^12 (4096) +architecture x86_64 + cputype CPU_TYPE_X86_64 + cpusubtype CPU_SUBTYPE_X86_64_ALL + capabilities 0x0 + offset 8192 + size 32 + align 2^12 (4096) +`; describe("lipo architecture facts", () => { it("normalizes symbolic CPU fields and arm64e capability bits", () => { - const architectures = parseLipoArchitectures(`Fat header in: /bin/ls + const { format, architectures } = parseLipoOutput(`Fat header in: /bin/ls fat_magic 0xcafebabe nfat_arch 2 architecture x86_64 @@ -22,6 +40,7 @@ architecture arm64e size 89088 align 2^14 (16384) `); + expect(format).toBe("fat"); expect(architectures).toEqual([ expect.objectContaining({ name: "x86_64", @@ -50,9 +69,9 @@ architecture arm64e it("keeps unknown symbolic CPU values as source text without a code", () => { expect( - parseLipoArchitectures( + parseLipoOutput( "architecture future\n cputype CPU_TYPE_FUTURE\n cpusubtype CPU_SUBTYPE_FUTURE\n offset 4096\n size 64\n align 2^12\n", - ), + ).architectures, ).toMatchObject([ { cpu_type: "CPU_TYPE_FUTURE", @@ -70,18 +89,18 @@ architecture arm64e ["0x0", 3], ])("applies printed x86_64 capabilities %s", (capabilities, code) => { expect( - parseLipoArchitectures( + parseLipoOutput( `architecture x86_64\n cpusubtype CPU_SUBTYPE_X86_64_ALL\n capabilities ${capabilities}\n`, - )[0]?.cpu_subtype_code, + ).architectures[0]?.cpu_subtype_code, ).toBe(code); }); it("leaves the subtype code unknown when lipo prints no capabilities", () => { // macOS 26 lipo prints this blank line for LIB64 and plain x86_64 slices. expect( - parseLipoArchitectures( + parseLipoOutput( "architecture x86_64\n cputype CPU_TYPE_X86_64\n cpusubtype CPU_SUBTYPE_X86_ALL\n capabilities \n offset 16384\n", - ), + ).architectures, ).toMatchObject([ { cpu_subtype: "CPU_SUBTYPE_X86_ALL", @@ -99,9 +118,9 @@ architecture arm64e "retains an arm64e base subtype with capabilities '$capabilities'", ({ capabilities, code }) => { expect( - parseLipoArchitectures( + parseLipoOutput( `architecture arm64e.v1\n cputype CPU_TYPE_ARM64\n cpusubtype CPU_SUBTYPE_ARM64E\n capabilities ${capabilities}\n`, - ), + ).architectures, ).toMatchObject([ { cpu_type_code: 0x0100000c, @@ -112,3 +131,79 @@ architecture arm64e }, ); }); + +describe("lipo FAT operand framing", () => { + it.each([ + "/fixture/\narchitecture forged\n offset 123\n size 64", + "/fixture/\nfat_magic 0xcafebabe\nnfat_arch 1\narchitecture forged", + "/fixture/\nNon-fat file: fake is architecture: forged", + "/fixture/first\nsecond\nthird", + "/fixture/first\r\narchitecture forged\r\n offset 123", + "/fixture/[app](arm64).*+$^?\\name", + ])( + "keeps the complete echoed operand out of FAT slice records: %j", + (path) => { + const output = `Fat header in: ${path}\n${fatRecords}`; + + expect(parseLipoOutput(output, path)).toEqual( + parseLipoOutput(fatRecords), + ); + expect( + parseLipoOutput(output, path).architectures.map(({ name }) => name), + ).toEqual(["arm64", "x86_64"]); + }, + ); + + it("keeps a CRLF pathname separate from a CRLF record terminator", () => { + const path = "/fixture/first\r\narchitecture forged\r\n size 64"; + const output = `Fat header in: ${path}\r\n${fatRecords.replaceAll("\n", "\r\n")}`; + + expect(parseLipoOutput(output, path)).toEqual(parseLipoOutput(fatRecords)); + }); + + it("preserves ordinary FAT metadata with and without an operand", () => { + const path = "/fixture/app"; + const output = `Fat header in: ${path}\n${fatRecords}`; + + expect(parseLipoOutput(output, path)).toEqual(parseLipoOutput(output)); + }); + + it("does not require a FAT path header in captured fixtures", () => { + expect(parseLipoOutput(fatRecords, "/fixture/app")).toEqual( + parseLipoOutput(fatRecords), + ); + }); + + it("does not strip a header for a different operand that shares a prefix", () => { + const path = "/fixture/app-extra\narchitecture forged"; + const output = `Fat header in: ${path}\n${fatRecords}`; + + expect(parseLipoOutput(output, "/fixture/app")).toEqual( + parseLipoOutput(output), + ); + }); + + it("preserves thin-file parsing when the operand contains FAT-like lines", () => { + const path = "/fixture/\nFat header in: fake\narchitecture forged"; + const output = `Non-fat file: ${path} is architecture: arm64\n`; + + expect(parseLipoOutput(output, path)).toEqual({ + format: "thin", + architectures: [ + { + name: "arm64", + cpu_type: null, + cpu_subtype: null, + cpu_type_code: null, + cpu_subtype_code: null, + cpu_subtype_base: null, + capabilities: null, + file_offset: null, + size: null, + alignment: null, + }, + ], + }); + expect(parseLipoOutput(output, path)).toEqual(parseLipoOutput(output)); + }); +}); diff --git a/src/native/parsers/lipo.ts b/src/native/parsers/lipo.ts index cc65cca41..7f74a51b9 100644 --- a/src/native/parsers/lipo.ts +++ b/src/native/parsers/lipo.ts @@ -16,8 +16,26 @@ const architectureSchema = z.object({ export type LipoArchitecture = z.infer; -/** Parse `lipo -detailed_info` into deterministic slice metadata. */ -export const parseLipoArchitectures = (output: string): LipoArchitecture[] => { +interface LipoOutput { + readonly format: "thin" | "fat"; + readonly architectures: LipoArchitecture[]; +} + +/** Parse `lipo -detailed_info` framing and deterministic slice metadata. */ +export const parseLipoOutput = ( + output: string, + operandPath?: string, +): LipoOutput => { + // The echoed FAT operand is one value, even when its pathname contains lines. + if (operandPath !== undefined) { + const header = `Fat header in: ${operandPath}`; + if (output.startsWith(header)) { + if (output.startsWith("\r\n", header.length)) + output = output.slice(header.length + 2); + else if (output.startsWith("\n", header.length)) + output = output.slice(header.length + 1); + } + } // lipo echoes a thin file's pathname verbatim, including embedded newlines. // Match its final architecture field before splitting records into lines. const thinArchitecture = @@ -25,20 +43,23 @@ export const parseLipoArchitectures = (output: string): LipoArchitecture[] => { output, )?.[1]; if (thinArchitecture !== undefined) - return [ - architectureSchema.parse({ - name: thinArchitecture, - cpu_type: null, - cpu_subtype: null, - cpu_type_code: null, - cpu_subtype_code: null, - cpu_subtype_base: null, - capabilities: null, - file_offset: null, - size: null, - alignment: null, - }), - ]; + return { + format: "thin", + architectures: [ + architectureSchema.parse({ + name: thinArchitecture, + cpu_type: null, + cpu_subtype: null, + cpu_type_code: null, + cpu_subtype_code: null, + cpu_subtype_base: null, + capabilities: null, + file_offset: null, + size: null, + alignment: null, + }), + ], + }; const architectures: LipoArchitecture[] = []; let current: Record | undefined; const flush = (): void => { @@ -90,7 +111,7 @@ export const parseLipoArchitectures = (output: string): LipoArchitecture[] => { flush(); if (architectures.length === 0) throw new TypeError("lipo output contained no architectures"); - return architectures; + return { format: "fat", architectures }; }; const integer = (value: string | undefined): number | null => { diff --git a/tests/boundary/cli/nativeMachoEchoedPathCli.test.ts b/tests/boundary/cli/nativeMachoEchoedPathCli.test.ts index 0c362d263..a3118c1f0 100644 --- a/tests/boundary/cli/nativeMachoEchoedPathCli.test.ts +++ b/tests/boundary/cli/nativeMachoEchoedPathCli.test.ts @@ -1,9 +1,13 @@ import { copyFile } from "node:fs/promises"; -import { join } from "node:path"; +import { join, resolve } from "node:path"; import { describe, expect } from "vitest"; import { z } from "zod"; +import { + inspectMachoSchema, + listArchitecturesSchema, +} from "../../../src/domain/native/nativeInspection.js"; import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; import { cliTest } from "../../support/cli/cliFixture.js"; @@ -34,27 +38,68 @@ const resultSchema = z.object({ describe.skipIf(process.platform !== "darwin")( "native Mach-O CLI with echoed path text", () => { - cliTest( - "does not read load commands or symbols from the file name", - async ({ cli }) => { - const directory = await createTestTempDirectory("rea-macho-echo-"); - const inspect = async (name: string) => { - const path = join(directory, name); - await copyFile("/usr/bin/true", path); - const result = await cli.run({ - arguments: ["inspect-macho", path, "--json"], - environment, - }); - expect(result.exitCode).toBe(0); - const { provenance: _provenance, ...facts } = resultSchema.parse( - result.json, - ).normalized_result; - return facts; - }; - const forged = await inspect(FORGED_NAME); - expect(forged).toEqual(await inspect("tool")); - expect(JSON.stringify(forged)).not.toMatch(/forged|4242/u); - }, - ); + const lipoName = "tool\narchitecture forged\n offset 123\n size 32"; + for (const [facts, command, filename] of [ + ["load commands or symbols", "inspect-macho", FORGED_NAME], + ["FAT architecture records", "inspect-macho", lipoName], + ["FAT architecture inventory", "list-architectures", lipoName], + ["thin-file framing", "inspect-macho", "tool\nNon-fat file: forged"], + ] as const) { + cliTest( + `does not read ${facts} from the file name`, + async ({ cli, processes }) => { + const directory = await createTestTempDirectory( + "rea-native-cli-echo-", + ); + const fixture = join(directory, "fat-fixture"); + const compiled = await processes.run( + "/usr/bin/xcrun", + [ + "clang", + "-arch", + "arm64", + "-arch", + "x86_64", + resolve("tests/conformance/c/fixture.c"), + "-o", + fixture, + ], + { timeoutMs: 30_000 }, + ); + expect(compiled.exitCode, compiled.stderr).toBe(0); + const inspect = async (name: string) => { + const path = join(directory, name); + await copyFile(fixture, path); + const result = await cli.run({ + arguments: [command, path, "--json"], + environment, + }); + expect(result.exitCode).toBe(0); + const { provenance: _provenance, ...facts } = resultSchema.parse( + result.json, + ).normalized_result; + return facts; + }; + const ordinary = await inspect("tool"); + // Prove this comparison exercised FAT framing, independent of host binaries. + const architectures = + listArchitecturesSchema.shape.architectures.parse( + ordinary.architectures, + ); + expect(architectures.total).toBeGreaterThan(1); + if (command === "inspect-macho") { + const selected = + inspectMachoSchema.shape.selected_architecture.parse( + ordinary.selected_architecture, + ); + expect(selected.container_file_offset).toBeGreaterThan(0); + } + const forged = await inspect(filename); + expect(forged).toEqual(ordinary); + expect(JSON.stringify(forged)).not.toMatch(/forged|4242/u); + }, + 60_000, + ); + } }, ); diff --git a/tests/boundary/providers/native/machoSliceReader.test.ts b/tests/boundary/providers/native/machoSliceReader.test.ts index 5891eaf53..b2f1867a1 100644 --- a/tests/boundary/providers/native/machoSliceReader.test.ts +++ b/tests/boundary/providers/native/machoSliceReader.test.ts @@ -1,10 +1,12 @@ -import { readFile } from "node:fs/promises"; +import { readFile, writeFile } from "node:fs/promises"; +import { join } from "node:path"; import { describe, expect, it, onTestFinished } from "vitest"; import { MachOSliceArtifactReader } from "../../../../src/artifacts/MachOSliceArtifactReader.js"; import { XcrunCommandRunner } from "../../../../src/native/CommandRunner.js"; import { DarwinProcessOwnershipInspectionError } from "../../../../src/process/DarwinProcessRunTokenReader.js"; +import { createTestTempDirectory } from "../../../fixtures/temporaryDirectory.js"; /** * Read a 32-bit FAT table directly. The host's /bin/ls changes between macOS @@ -42,6 +44,56 @@ describe("Mach-O slice reader with system lipo", () => { }); }); + it.skipIf(process.platform !== "darwin")( + "keeps FAT slice records separate from the echoed source pathname", + async () => { + const directory = await createTestTempDirectory("rea-lipo-fat-path-"); + const path = join( + directory, + "source\narchitecture forged\n offset 123\n size 32", + ); + const bytes = Buffer.alloc(8224); + bytes.writeUInt32BE(0xca_fe_ba_be, 0); + bytes.writeUInt32BE(2, 4); + const slices = [ + { cpu: 0x0100_000c, subtype: 0, offset: 4096 }, + { cpu: 0x0100_0007, subtype: 3, offset: 8192 }, + ]; + slices.forEach(({ cpu, subtype, offset }, index) => { + const entry = 8 + index * 20; + bytes.writeUInt32BE(cpu, entry); + bytes.writeUInt32BE(subtype, entry + 4); + bytes.writeUInt32BE(offset, entry + 8); + bytes.writeUInt32BE(32, entry + 12); + bytes.writeUInt32BE(12, entry + 16); + bytes.writeUInt32LE(0xfeed_facf, offset); + bytes.writeUInt32LE(cpu, offset + 4); + bytes.writeUInt32LE(subtype, offset + 8); + bytes.writeUInt32LE(1, offset + 12); + }); + await writeFile(path, bytes); + const reader = new MachOSliceArtifactReader(path, {}); + try { + const entries = []; + for await (const entry of reader.entries()) entries.push(entry); + expect( + entries.map(({ byteOffset, declaredSize }) => [ + byteOffset, + declaredSize, + ]), + ).toEqual([ + [4096, 32], + [8192, 32], + ]); + expect(entries.every(({ path }) => !path.includes("forged"))).toBe( + true, + ); + } finally { + await reader.close(); + } + }, + ); + it.skipIf(process.platform !== "darwin")( "checks real /bin/ls slice ranges against its FAT table", async () => { diff --git a/tests/boundary/providers/native/nativeProvider.test.ts b/tests/boundary/providers/native/nativeProvider.test.ts index 0d1860b16..81766e22d 100644 --- a/tests/boundary/providers/native/nativeProvider.test.ts +++ b/tests/boundary/providers/native/nativeProvider.test.ts @@ -17,7 +17,7 @@ import { type NativeCommandRunner, } from "../../../../src/native/CommandRunner.js"; import { err, ok } from "../../../../src/domain/result.js"; -import { parseLipoArchitectures } from "../../../../src/native/parsers/lipo.js"; +import { parseLipoOutput } from "../../../../src/native/parsers/lipo.js"; import { parseCodeSignature } from "../../../../src/native/parsers/codesign.js"; import { prepareProcessOwnershipInspection } from "../../../../src/process/ProcessOwnershipObservation.js"; import { projectAnalysisError } from "../../../../src/domain/analysisErrorProjection.js"; @@ -960,15 +960,17 @@ describe("native macOS provider failures and parsing", () => { }); it("parses thin and universal lipo fixtures", async () => { - expect( - parseLipoArchitectures(await fixture("lipo-thin.txt")), - ).toMatchObject([{ name: "arm64" }]); - expect(parseLipoArchitectures(await fixture("lipo-fat.txt"))).toMatchObject( - [ + expect(parseLipoOutput(await fixture("lipo-thin.txt"))).toMatchObject({ + format: "thin", + architectures: [{ name: "arm64" }], + }); + expect(parseLipoOutput(await fixture("lipo-fat.txt"))).toMatchObject({ + format: "fat", + architectures: [ { name: "x86_64", file_offset: 16384, size: 4096 }, { name: "arm64", file_offset: 32768, size: 8192 }, ], - ); + }); }); it("rejects trailing text in lipo numeric fields instead of truncating", () => { @@ -980,7 +982,7 @@ describe("native macOS provider failures and parsing", () => { " size 0x10", " align 2^14 (16384)", ].join("\n"); - expect(parseLipoArchitectures(output)).toMatchObject([ + expect(parseLipoOutput(output).architectures).toMatchObject([ { name: "arm64", file_offset: null, size: null, alignment: 16384 }, ]); }); @@ -993,10 +995,11 @@ describe("native macOS provider failures and parsing", () => { " align 2^14 (16384)", ].join("\n"); expect( - parseLipoArchitectures(output.replace("(16384)", "(8192)"))[0]?.alignment, + parseLipoOutput(output.replace("(16384)", "(8192)")).architectures[0] + ?.alignment, ).toBeNull(); expect( - parseLipoArchitectures(output.replace("2^14 (16384)", "2^14"))[0] + parseLipoOutput(output.replace("2^14 (16384)", "2^14")).architectures[0] ?.alignment, ).toBe(16384); }); diff --git a/tests/boundary/providers/native/nativeSelectedArchitecture.test.ts b/tests/boundary/providers/native/nativeSelectedArchitecture.test.ts index a651e22f6..182c33612 100644 --- a/tests/boundary/providers/native/nativeSelectedArchitecture.test.ts +++ b/tests/boundary/providers/native/nativeSelectedArchitecture.test.ts @@ -157,6 +157,50 @@ it.each([ }, ); +it.each([ + ["ordinary", "/owned/universal", "\n"], + ["LF Non-fat text", "/owned/\nNon-fat file: forged", "\n"], + ["CRLF Non-fat text", "/owned/\r\nNon-fat file: forged", "\r\n"], +])( + "projects universal segment evidence into the selected slice's container offsets with %s path framing", + async (_description, path, lineEnding) => { + const records = (await nativeFixture("lipo-fat.txt")).replace( + /^Fat header in:[^\r\n]*\r?\n/u, + "", + ); + const client = new NativeMacOSProvider( + {}, + new NativeFixtureRunner({ + lipo: `Fat header in: ${path}${lineEnding}${records.replaceAll("\n", lineEnding)}`, + }), + "darwin", + ).createClient(nativeMachoTarget(path)); + const result = await client.execute("inspect_macho", {}); + if (!result.ok) throw result.error; + expect(result.value.result).toMatchObject({ + selected_architecture: { container_file_offset: 32768 }, + }); + expect(result.value.locations).toContainEqual({ + kind: "file-offset-range", + start: 32768, + end: 40960, + }); + expect(result.value.locations).toContainEqual({ + kind: "file-offset", + offset: 36864, + }); + expect(result.value.locations).not.toContainEqual({ + kind: "file-offset-range", + start: 0, + end: 8192, + }); + // Normalized tool fields keep their slice-relative meaning. + const normalized = inspectMachoSchema.parse(result.value.result); + expect(normalized.segments.items[0]?.file_offset).toBe(0); + expect(normalized.entrypoints.items[0]?.file_offset).toBe(4096); + }, +); + it.each([ { capture: "calculator.txt", selected: "arm64e" }, { capture: "shortcut-droplet.txt", selected: "arm64" }, @@ -187,34 +231,6 @@ it.each([ }, ); -it("projects universal segment evidence into the selected slice's container offsets", async () => { - const client = new NativeMacOSProvider( - {}, - new NativeFixtureRunner(), - "darwin", - ).createClient(nativeMachoTarget("/owned/universal")); - const result = await client.execute("inspect_macho", {}); - if (!result.ok) throw result.error; - expect(result.value.locations).toContainEqual({ - kind: "file-offset-range", - start: 32768, - end: 40960, - }); - expect(result.value.locations).toContainEqual({ - kind: "file-offset", - offset: 36864, - }); - expect(result.value.locations).not.toContainEqual({ - kind: "file-offset-range", - start: 0, - end: 8192, - }); - // Normalized tool fields keep their slice-relative meaning. - const normalized = inspectMachoSchema.parse(result.value.result); - expect(normalized.segments.items[0]?.file_offset).toBe(0); - expect(normalized.entrypoints.items[0]?.file_offset).toBe(4096); -}); - it.each([ { description: "thin", diff --git a/tests/boundary/providers/native/nativeTextRobustness.test.ts b/tests/boundary/providers/native/nativeTextRobustness.test.ts index 256aa0a98..ce82bd520 100644 --- a/tests/boundary/providers/native/nativeTextRobustness.test.ts +++ b/tests/boundary/providers/native/nativeTextRobustness.test.ts @@ -2,7 +2,7 @@ import { expect, it } from "vitest"; import { parseOtoolLoadCommands } from "../../../../src/native/parsers/otool.js"; import { parsePlistJson } from "../../../../src/native/parsers/plist.js"; -import { parseLipoArchitectures } from "../../../../src/native/parsers/lipo.js"; +import { parseLipoOutput } from "../../../../src/native/parsers/lipo.js"; import { nativeFixture } from "../../../fixtures/nativeCommands.js"; const toCrlf = (value: string): string => value.replaceAll("\n", "\r\n"); @@ -15,7 +15,10 @@ it.each([ "retains the reported thin architecture when lipo echoes filename %j", (filename) => { const output = `input file /owned/${filename} is not a fat file\nNon-fat file: /owned/${filename} is architecture: arm64\n`; - expect(parseLipoArchitectures(output)).toMatchObject([{ name: "arm64" }]); + expect(parseLipoOutput(output)).toMatchObject({ + format: "thin", + architectures: [{ name: "arm64" }], + }); }, ); @@ -38,10 +41,13 @@ it("does not mistake fat slice output for a thin Non-fat file record", () => { " align 2^12 (4096)", "", ].join("\n"); - expect(parseLipoArchitectures(output)).toMatchObject([ - { name: "arm64", file_offset: 16384 }, - { name: "x86_64", file_offset: 16793600 }, - ]); + expect(parseLipoOutput(output)).toMatchObject({ + format: "fat", + architectures: [ + { name: "arm64", file_offset: 16384 }, + { name: "x86_64", file_offset: 16793600 }, + ], + }); }); it("keeps section blocks separate under CRLF captures", async () => {