mirror of
https://github.com/morluto/rea.git
synced 2026-11-03 21:36:13 +00:00
236 lines
6.3 KiB
TypeScript
236 lines
6.3 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
|
|
import {
|
|
functionBodySchema,
|
|
addressDistance,
|
|
procedureIdentitySchema,
|
|
parseDocuments,
|
|
parseListCount,
|
|
parseRelatedAddresses,
|
|
parseSegments,
|
|
} from "./hopperValues.js";
|
|
|
|
describe("Hopper boundary values", () => {
|
|
it("distinguishes measured zero, malformed, incomparable, reversed, and oversized extents", () => {
|
|
expect(addressDistance("not-an-address", "0x2000")).toBeNull();
|
|
expect(addressDistance("ram:0x1000", "rom:0x2000")).toBeNull();
|
|
expect(addressDistance("0x2000", "0x1000")).toBeNull();
|
|
expect(addressDistance("0x1000", "0x1000")).toBe(0);
|
|
expect(addressDistance("0x1000", "0x2000")).toBe(4096);
|
|
expect(addressDistance("0x0", "0x20000000000000")).toBeNull();
|
|
});
|
|
|
|
it("counts Hopper's address-keyed string maps", () => {
|
|
expect(
|
|
parseListCount({ "0x1000": "hello", "0x2000": "world" }, "strings"),
|
|
).toEqual({ ok: true, value: 2 });
|
|
});
|
|
|
|
it("parses direct and wrapped protocol shapes", () => {
|
|
expect(parseRelatedAddresses({ callers: ["0x3"] }, "callers")).toEqual({
|
|
ok: true,
|
|
value: ["0x3"],
|
|
});
|
|
expect(
|
|
parseSegments([
|
|
{
|
|
name: "__TEXT",
|
|
start: "0x1",
|
|
end: "0x2",
|
|
readable: true,
|
|
writable: false,
|
|
executable: null,
|
|
},
|
|
]),
|
|
).toEqual({
|
|
ok: true,
|
|
value: [
|
|
{
|
|
name: "__TEXT",
|
|
start: "0x1",
|
|
end: "0x2",
|
|
readable: true,
|
|
writable: false,
|
|
executable: null,
|
|
},
|
|
],
|
|
});
|
|
expect(parseDocuments(["fixture"])).toEqual({
|
|
ok: true,
|
|
value: ["fixture"],
|
|
});
|
|
});
|
|
|
|
it.each([
|
|
["relations", () => parseRelatedAddresses([1], "callees")],
|
|
["segments", () => parseSegments([{ name: 1 }])],
|
|
["documents", () => parseDocuments([1])],
|
|
])(
|
|
"rejects malformed %s instead of manufacturing empty output",
|
|
(_label, parse) => {
|
|
const result = parse();
|
|
expect(result.ok).toBe(false);
|
|
if (!result.ok) expect(result.error._tag).toBe("AnalysisOutputError");
|
|
},
|
|
);
|
|
|
|
it("uses provider-neutral wording for malformed shared values", () => {
|
|
const result = parseSegments([{ name: 1 }]);
|
|
expect(result.ok).toBe(false);
|
|
if (!result.ok) {
|
|
expect(result.error._tag).toBe("AnalysisOutputError");
|
|
expect(result.error.operation).toBe("list_segments");
|
|
expect(result.error.reason).toBe(
|
|
"Provider returned an invalid segment list",
|
|
);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("provider-neutral inclusive function body evidence", () => {
|
|
const observed = () => ({
|
|
available: true,
|
|
provenance: "test-address-set",
|
|
ranges: [
|
|
{ start: "0x1000", end: "0x1002" },
|
|
{ start: "0x1010", end: "0x1011" },
|
|
],
|
|
total_bytes: 5,
|
|
span_bytes: 18,
|
|
non_contiguous: true,
|
|
contains_entry: true,
|
|
});
|
|
it("rejects missing provider body metadata", () => {
|
|
expect(
|
|
procedureIdentitySchema.safeParse({ address: "0x1000", name: "main" })
|
|
.success,
|
|
).toBe(false);
|
|
});
|
|
it("rejects segments when required coordinates or unknown permissions are omitted", () => {
|
|
expect(
|
|
parseSegments([
|
|
{
|
|
name: "__TEXT",
|
|
start: "0x1",
|
|
end: "0x2",
|
|
readable: null,
|
|
writable: null,
|
|
},
|
|
]).ok,
|
|
).toBe(false);
|
|
expect(
|
|
parseSegments([{ name: "__TEXT", start: "0x1", end: "0x2" }]).ok,
|
|
).toBe(false);
|
|
});
|
|
it("counts inclusive one-byte ranges without assuming an enclosing continuous body", () => {
|
|
expect(
|
|
functionBodySchema.safeParse({
|
|
...observed(),
|
|
ranges: [{ start: "0x1000", end: "0x1000" }],
|
|
total_bytes: 1,
|
|
span_bytes: 1,
|
|
non_contiguous: false,
|
|
}).success,
|
|
).toBe(true);
|
|
expect(functionBodySchema.parse(observed())).toMatchObject({
|
|
total_bytes: 5,
|
|
span_bytes: 18,
|
|
});
|
|
});
|
|
it("preserves address-space identity and leaves a multi-space span unknown", () => {
|
|
expect(
|
|
functionBodySchema.safeParse({
|
|
...observed(),
|
|
ranges: [
|
|
{ start: "a:0x1000", end: "a:0x1000" },
|
|
{ start: "b:0x1000", end: "b:0x1001" },
|
|
],
|
|
total_bytes: 3,
|
|
span_bytes: null,
|
|
}).success,
|
|
).toBe(true);
|
|
expect(
|
|
functionBodySchema.safeParse({
|
|
...observed(),
|
|
ranges: [{ start: "a:0x1000", end: "b:0x1000" }],
|
|
}).success,
|
|
).toBe(false);
|
|
});
|
|
it("allows an observed empty set without manufacturing entry membership", () => {
|
|
expect(
|
|
procedureIdentitySchema.safeParse({
|
|
address: "0x1000",
|
|
name: "external",
|
|
classification: null,
|
|
body: {
|
|
...observed(),
|
|
ranges: [],
|
|
total_bytes: 0,
|
|
span_bytes: 0,
|
|
non_contiguous: false,
|
|
contains_entry: false,
|
|
},
|
|
}).success,
|
|
).toBe(true);
|
|
});
|
|
it("checks entry membership using address space and inclusive endpoints", () => {
|
|
expect(
|
|
procedureIdentitySchema.safeParse({
|
|
address: "0x1002",
|
|
name: "last",
|
|
classification: null,
|
|
body: observed(),
|
|
}).success,
|
|
).toBe(true);
|
|
expect(
|
|
procedureIdentitySchema.safeParse({
|
|
address: "0x1003",
|
|
name: "gap",
|
|
classification: null,
|
|
body: observed(),
|
|
}).success,
|
|
).toBe(false);
|
|
expect(
|
|
procedureIdentitySchema.safeParse({
|
|
address: "other:0x1000",
|
|
name: "other",
|
|
classification: null,
|
|
body: observed(),
|
|
}).success,
|
|
).toBe(false);
|
|
});
|
|
it.each([
|
|
{ total_bytes: 18 },
|
|
{ span_bytes: 5 },
|
|
{ non_contiguous: false },
|
|
{
|
|
ranges: [
|
|
{ start: "0x1000", end: "0x1002" },
|
|
{ start: "0x1003", end: "0x1004" },
|
|
],
|
|
},
|
|
{
|
|
ranges: [
|
|
{ start: "0x1000", end: "0x1002" },
|
|
{ start: "0x1002", end: "0x1004" },
|
|
],
|
|
},
|
|
{
|
|
ranges: [
|
|
{ start: "0x1010", end: "0x1011" },
|
|
{ start: "0x1000", end: "0x1002" },
|
|
],
|
|
},
|
|
{
|
|
ranges: [{ start: "0x0", end: "0x20000000000000" }],
|
|
total_bytes: Number.MAX_SAFE_INTEGER,
|
|
span_bytes: Number.MAX_SAFE_INTEGER,
|
|
non_contiguous: false,
|
|
},
|
|
])("rejects contradictory or unsafe range facts %j", (change) => {
|
|
expect(
|
|
functionBodySchema.safeParse({ ...observed(), ...change }).success,
|
|
).toBe(false);
|
|
});
|
|
});
|