mirror of
https://github.com/morluto/rea.git
synced 2026-11-07 05:56:19 -10:00
fix(managed): surface capacity limits in member comparison
Preserve per-side capacity diagnostics in shared managed member comparisons and apply repeatable CLI type selection to both source inspections. Keep partial and historical unknown semantics intact, with one typed diagnostic projection and side schema. Validated with 159 focused tests, final-main CLI/MCP boundary checks, schema validation and official public WPF 8.0.32/8.0.33 assemblies. Full source Evidence remains available through the retained-record MCP workflow. No target execution or budget increase. Fixes #9021 Assisted-by: Reimu (gensokyo-skills:reimu-incident-triage)
This commit is contained in:
10 files changed
+296
-38
No files matched your search
+2
-1
@@ -796,7 +796,8 @@ from the bundle for every supported view kind. Bundles exported from MCP work
|
||||
for these commands. Its inline source form cannot also select a bundle.
|
||||
A missing ID is refused with `details.reason: "missing"`;
|
||||
supplying a bundle does not make incompatible Evidence valid for a workflow.
|
||||
`compare-managed-members` keeps its two assembly paths, and
|
||||
`compare-managed-members` keeps its two assembly paths and an optional
|
||||
repeatable `--type` selection applied to both sides, and
|
||||
`compare-process-captures` keeps its two capture Evidence JSON paths. The bundle
|
||||
option above selects an input; producer options that write a new bundle, such
|
||||
as `inspect-managed-members --evidence-bundle`, retain their existing meaning.
|
||||
|
||||
@@ -280,6 +280,21 @@ inspects its two selected paths sequentially, retaining each inspection rather
|
||||
than both full byte snapshots. The limit therefore remains a per-file resource
|
||||
admission rule, not an aggregate comparison limit. Comparison Evidence keeps
|
||||
the caller-selected paths as its parameters and artifact locations.
|
||||
When either side's member inspection exceeds its representation budget, the
|
||||
comparison limitations name that side and its `resource-limit` diagnostic; its
|
||||
empty member collections do not establish added or removed members. Each side's
|
||||
`resource_limits` preserves the reported diagnostic's code, scope, detail and
|
||||
nullable offset. An empty array means no resource limit was reported; historical
|
||||
comparisons omit this field, leaving those diagnostics unrecorded. Always read
|
||||
the comparison coverage alongside member counts. To compare
|
||||
a smaller selection, pass the same repeatable `--type` full names that
|
||||
`inspect-managed-members --type` accepts to `compare-managed-members`, which
|
||||
applies them to both sides and records them as `type_names`; over MCP, compare
|
||||
two `inspect_managed_members` Evidence records selected with the same
|
||||
`type_names`. A single selected type can still exceed the budget.
|
||||
The two-record MCP workflow keeps full source Evidence available for
|
||||
`inspect_analysis_view` field selection and paging; path-based CLI comparison
|
||||
records the selected paths, type names and per-side diagnostics.
|
||||
|
||||
Input overflow returns a resource constraint. Decode or projection overflow
|
||||
reports `resource-limit` with unavailable or partial coverage; omitted facts do
|
||||
|
||||
@@ -102,6 +102,8 @@ export const compareManagedMemberPaths = async (
|
||||
input: {
|
||||
readonly leftPath: string;
|
||||
readonly rightPath: string;
|
||||
/** Applied to both sides with inspect_managed_members selection semantics. */
|
||||
readonly typeNames?: readonly string[];
|
||||
},
|
||||
dependencies: Partial<ManagedMemberComparisonPathDependencies> = {},
|
||||
resources?: ArtifactResourceScope,
|
||||
@@ -159,6 +161,9 @@ export const compareManagedMemberPaths = async (
|
||||
try {
|
||||
inspection = inspectManagedMembersBytes(snapshot.bytes, target, {
|
||||
userStrings: false,
|
||||
...(input.typeNames === undefined
|
||||
? {}
|
||||
: { typeNames: input.typeNames }),
|
||||
});
|
||||
} catch (cause: unknown) {
|
||||
throw (
|
||||
@@ -172,7 +177,10 @@ export const compareManagedMemberPaths = async (
|
||||
}
|
||||
const evidence = createEvidence(target, MANAGED_STATIC_PROVIDER, {
|
||||
operation: "inspect_managed_members",
|
||||
parameters: {},
|
||||
parameters:
|
||||
input.typeNames === undefined
|
||||
? {}
|
||||
: { type_names: [...input.typeNames] },
|
||||
result: inspection,
|
||||
rawResult: null,
|
||||
limitations: inspection.limitations,
|
||||
@@ -198,7 +206,7 @@ export const compareManagedMemberPaths = async (
|
||||
left.evidence,
|
||||
right.evidence,
|
||||
result,
|
||||
{ leftPath: input.leftPath, rightPath: input.rightPath },
|
||||
input,
|
||||
),
|
||||
);
|
||||
} catch (cause: unknown) {
|
||||
@@ -215,7 +223,11 @@ const createManagedMemberComparisonEvidence = (
|
||||
left: Evidence,
|
||||
right: Evidence,
|
||||
result: ManagedMemberComparisonResult,
|
||||
sourcePaths?: { readonly leftPath: string; readonly rightPath: string },
|
||||
sourcePaths?: {
|
||||
readonly leftPath: string;
|
||||
readonly rightPath: string;
|
||||
readonly typeNames?: readonly string[];
|
||||
},
|
||||
): Evidence =>
|
||||
createEvidence(undefined, MANAGED_WORKFLOW_PROVIDER, {
|
||||
predicateType: "rea.managed-member-comparison",
|
||||
@@ -229,6 +241,9 @@ const createManagedMemberComparisonEvidence = (
|
||||
left_path: sourcePaths.leftPath,
|
||||
right_path: sourcePaths.rightPath,
|
||||
}),
|
||||
...(sourcePaths?.typeNames === undefined
|
||||
? {}
|
||||
: { type_names: [...sourcePaths.typeNames] }),
|
||||
},
|
||||
result,
|
||||
rawResult: null,
|
||||
|
||||
@@ -152,7 +152,12 @@ const registerCompareManagedMembers = (
|
||||
description:
|
||||
"Compare managed PE/CLI member inventories by exact declared type, name, and signature; names alone never form a match",
|
||||
args: compareManagedMemberArgs,
|
||||
run: ({ args }) =>
|
||||
options: z.object({
|
||||
type: managedMembersInputSchema.shape.type_names.describe(
|
||||
"Compare only members declared by this exact type full name on both sides, as selected by inspect-managed-members --type; use it when a whole-assembly comparison exceeds capacity (repeatable)",
|
||||
),
|
||||
}),
|
||||
run: ({ args, options }) =>
|
||||
withCommandCancellation((signal) =>
|
||||
logCliCommand(logger, "compare-managed-members", async () => {
|
||||
const resources = new ArtifactResourceScope();
|
||||
@@ -162,6 +167,9 @@ const registerCompareManagedMembers = (
|
||||
{
|
||||
leftPath: args.leftPath,
|
||||
rightPath: args.rightPath,
|
||||
...(options.type === undefined
|
||||
? {}
|
||||
: { typeNames: options.type }),
|
||||
},
|
||||
{},
|
||||
resources,
|
||||
|
||||
@@ -7,6 +7,7 @@ import { evidenceInputSchema } from "../evidenceReference.js";
|
||||
import {
|
||||
cliMetadataGuidSchema,
|
||||
managedMetadataTokenSchema,
|
||||
managedMemberInspectionSchema,
|
||||
type ManagedMemberInspection,
|
||||
} from "./managedArtifact.js";
|
||||
import {
|
||||
@@ -173,6 +174,26 @@ const fieldComparisonItemSchema = comparisonItemSchema(
|
||||
memberIdentitySchema.omit({ normalized_il_sha256: true }),
|
||||
);
|
||||
|
||||
const comparisonSideSchema = z.strictObject({
|
||||
evidence_id: evidenceIdSchema,
|
||||
artifact_sha256: digestSchema,
|
||||
mvid: cliMetadataGuidSchema.nullable(),
|
||||
module_name: z.string().nullable(),
|
||||
metadata_status: z.enum(["absent", "complete", "partial", "malformed"]),
|
||||
methods_total: z.int().min(0),
|
||||
fields_total: z.int().min(0),
|
||||
resource_limits: z
|
||||
.array(
|
||||
managedMemberInspectionSchema.shape.coverage.shape.issues.element.extend({
|
||||
code: z.literal("resource-limit"),
|
||||
}),
|
||||
)
|
||||
.optional()
|
||||
.describe(
|
||||
"Resource-limit diagnostics reported by this source; an empty array means none were reported. Historical comparisons omit this field; omission does not establish that no resource limit occurred.",
|
||||
),
|
||||
});
|
||||
|
||||
/** Obfuscation-resistant, execution-free managed member comparison. */
|
||||
export const managedMemberComparisonResultSchema = z.strictObject({
|
||||
comparison_id: prefixedDigestSchema("mmc"),
|
||||
@@ -181,24 +202,8 @@ export const managedMemberComparisonResultSchema = z.strictObject({
|
||||
token_identity: z.literal("build-local"),
|
||||
name_matching: z.literal("exact-signature-fallback"),
|
||||
}),
|
||||
left: z.strictObject({
|
||||
evidence_id: evidenceIdSchema,
|
||||
artifact_sha256: digestSchema,
|
||||
mvid: cliMetadataGuidSchema.nullable(),
|
||||
module_name: z.string().nullable(),
|
||||
metadata_status: z.enum(["absent", "complete", "partial", "malformed"]),
|
||||
methods_total: z.int().min(0),
|
||||
fields_total: z.int().min(0),
|
||||
}),
|
||||
right: z.strictObject({
|
||||
evidence_id: evidenceIdSchema,
|
||||
artifact_sha256: digestSchema,
|
||||
mvid: cliMetadataGuidSchema.nullable(),
|
||||
module_name: z.string().nullable(),
|
||||
metadata_status: z.enum(["absent", "complete", "partial", "malformed"]),
|
||||
methods_total: z.int().min(0),
|
||||
fields_total: z.int().min(0),
|
||||
}),
|
||||
left: comparisonSideSchema,
|
||||
right: comparisonSideSchema,
|
||||
summary: z.strictObject({
|
||||
unchanged: z.int().min(0),
|
||||
changed: z.int().min(0),
|
||||
|
||||
@@ -131,6 +131,14 @@ const sideCoverageStatus = (
|
||||
? "complete"
|
||||
: "partial";
|
||||
|
||||
/** Copy the source's typed capacity facts without retaining unrelated issue inventories. */
|
||||
const resourceLimits = (
|
||||
result: ManagedMemberInspection,
|
||||
): NonNullable<ManagedMemberComparisonResult["left"]["resource_limits"]> =>
|
||||
result.coverage.issues.flatMap(({ code, scope, detail, offset }) =>
|
||||
code === "resource-limit" ? [{ code, scope, detail, offset }] : [],
|
||||
);
|
||||
|
||||
export const sideManifest = (
|
||||
side: ManagedMemberComparisonSide,
|
||||
): ManagedMemberComparisonResult["left"] => ({
|
||||
@@ -141,6 +149,7 @@ export const sideManifest = (
|
||||
metadata_status: side.result.metadata.status,
|
||||
methods_total: side.result.methods.length,
|
||||
fields_total: side.result.fields.length,
|
||||
resource_limits: resourceLimits(side.result),
|
||||
});
|
||||
|
||||
export const buildComparisonSummary = (
|
||||
@@ -180,6 +189,27 @@ export const buildComparisonMatching = (
|
||||
).length,
|
||||
});
|
||||
|
||||
/** Carry each side's representation-budget diagnostic and its recovery. */
|
||||
const capacityLimitations = (
|
||||
left: ManagedMemberInspection,
|
||||
right: ManagedMemberInspection,
|
||||
): string[] => {
|
||||
const exceeded = new Set(
|
||||
(["left", "right"] as const).flatMap((side) =>
|
||||
resourceLimits(side === "left" ? left : right).map(
|
||||
({ scope, detail }) =>
|
||||
`The ${side} managed member observation exceeded capacity at ${scope}: ${detail}. Its member collections are not examined to completion, so empty or missing members do not establish absence, addition or removal.`,
|
||||
),
|
||||
),
|
||||
);
|
||||
return exceeded.size === 0
|
||||
? []
|
||||
: [
|
||||
...exceeded,
|
||||
"Compare a smaller selection by inspecting both sides with the same type_names, or by passing --type to CLI compare-managed-members; a single selected type can still exceed capacity.",
|
||||
];
|
||||
};
|
||||
|
||||
export const comparisonLimitations = (
|
||||
left: ManagedMemberInspection,
|
||||
right: ManagedMemberInspection,
|
||||
@@ -220,6 +250,7 @@ export const comparisonLimitations = (
|
||||
);
|
||||
if (left.coverage.state !== "complete" || right.coverage.state !== "complete")
|
||||
limitations.push("At least one managed member observation is partial.");
|
||||
limitations.push(...capacityLimitations(left, right));
|
||||
if (selectionAgreement === "different")
|
||||
limitations.push(
|
||||
"The observations select different declaring types; a member outside the other side's selection is unknown, not added or removed.",
|
||||
|
||||
@@ -8,7 +8,10 @@ import {
|
||||
buildManagedNestedTypeFixture,
|
||||
managedPeFixtureTarget,
|
||||
} from "./ManagedPe.fixture.js";
|
||||
import { buildManagedMemberCapacityFixture } from "../../tests/fixtures/managedMemberCapacity.js";
|
||||
import {
|
||||
buildManagedMemberCapacityFixture,
|
||||
buildManagedMemberSelectionCapacityFixture,
|
||||
} from "../../tests/fixtures/managedMemberCapacity.js";
|
||||
import { readManagedPeLayout } from "./ManagedPeReader.js";
|
||||
import type { ManagedParseIssue } from "../domain/managed/managedArtifact.js";
|
||||
import {
|
||||
@@ -89,16 +92,7 @@ it("decodes a single selected type to completion on a game-scale assembly", () =
|
||||
// one field and one method while the rest of the member inventory stays
|
||||
// on the last type, so this also proves the selected members decode
|
||||
// instead of merely proving that skipping unselected rows saves budget.
|
||||
const bytes = buildManagedPeFixture({
|
||||
additionalEmptyTypes: 9400,
|
||||
fieldRowCount: 40037,
|
||||
methodRowCount: 49738,
|
||||
memberRefRowCount: 11442,
|
||||
customAttributeRowCount: 58698,
|
||||
distinctMemberNames: true,
|
||||
firstTypeFieldRowCount: 1,
|
||||
firstTypeMethodRowCount: 1,
|
||||
});
|
||||
const bytes = buildManagedMemberSelectionCapacityFixture();
|
||||
const target = managedPeFixtureTarget(bytes);
|
||||
const inventory = inspectManagedMembersBytes(bytes, target);
|
||||
const selected = inventory.types.find(
|
||||
|
||||
@@ -16,6 +16,8 @@ import { parseEvidence } from "../../../src/domain/evidence.js";
|
||||
import { managedMemberInspectionSchema } from "../../../src/domain/managed/managedArtifact.js";
|
||||
import { managedMemberComparisonResultSchema } from "../../../src/domain/managed/managedMemberComparison.js";
|
||||
import { buildManagedPeFixture } from "../../../src/dotnet/ManagedPe.fixture.js";
|
||||
import { comparisonLimitations } from "../../../src/domain/managed/managedMemberComparisonCoverage.js";
|
||||
import { buildManagedMemberSelectionCapacityFixture } from "../../fixtures/managedMemberCapacity.js";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
import { cliTest } from "../../support/cli/cliFixture.js";
|
||||
|
||||
@@ -193,6 +195,134 @@ cliTest(
|
||||
},
|
||||
);
|
||||
|
||||
cliTest(
|
||||
"surfaces one side's member capacity limit in the comparison and recovers with a shared --type selection",
|
||||
async ({ cli }) => {
|
||||
const directory = await createTestTempDirectory("rea-managed-capacity-");
|
||||
const left = join(directory, "left.dll");
|
||||
const right = join(directory, "right.dll");
|
||||
await writeFile(
|
||||
left,
|
||||
buildManagedPeFixture({
|
||||
additionalEmptyTypes: 1,
|
||||
fieldRowCount: 2,
|
||||
methodRowCount: 2,
|
||||
firstTypeFieldRowCount: 1,
|
||||
firstTypeMethodRowCount: 1,
|
||||
distinctMemberNames: true,
|
||||
}),
|
||||
);
|
||||
await writeFile(right, buildManagedMemberSelectionCapacityFixture(1));
|
||||
const call = await cli.sharedMcpCall!();
|
||||
const sources = [];
|
||||
for (const path of [left, right]) {
|
||||
const reply = await call("inspect_managed_members", { path });
|
||||
expect(reply.isError, JSON.stringify(reply)).not.toBe(true);
|
||||
const evidence = parseEvidence(reply.structuredContent);
|
||||
sources.push({
|
||||
evidence,
|
||||
report: managedMemberInspectionSchema.parse(evidence.normalized_result),
|
||||
});
|
||||
}
|
||||
const [leftSource, rightSource] = sources;
|
||||
if (leftSource === undefined || rightSource === undefined)
|
||||
throw new Error("Missing source inspection");
|
||||
const limits = rightSource.report.coverage.issues.filter(
|
||||
({ code }) => code === "resource-limit",
|
||||
);
|
||||
expect(limits).toEqual([
|
||||
expect.objectContaining({
|
||||
code: "resource-limit",
|
||||
scope: "metadata.representation",
|
||||
offset: null,
|
||||
}),
|
||||
]);
|
||||
|
||||
const whole = await cli.run({
|
||||
arguments: ["compare-managed-members", left, right, "--json"],
|
||||
});
|
||||
expect(whole.exitCode, whole.stderr).toBe(0);
|
||||
const wholeEvidence = parseEvidence(whole.json);
|
||||
const wholeReport = managedMemberComparisonResultSchema.parse(
|
||||
wholeEvidence.normalized_result,
|
||||
);
|
||||
expect(wholeReport.coverage).toEqual({
|
||||
status: "partial",
|
||||
left_status: "complete",
|
||||
right_status: "partial",
|
||||
});
|
||||
expect(wholeReport.right.methods_total).toBe(0);
|
||||
expect(wholeReport.left.resource_limits).toEqual([]);
|
||||
expect(wholeReport.right.resource_limits).toEqual(limits);
|
||||
expect(wholeReport.limitations).toEqual(
|
||||
comparisonLimitations(leftSource.report, rightSource.report),
|
||||
);
|
||||
const mcpCompared = await call("compare_managed_members", {
|
||||
left: leftSource.evidence,
|
||||
right: rightSource.evidence,
|
||||
});
|
||||
expect(mcpCompared.isError, JSON.stringify(mcpCompared)).not.toBe(true);
|
||||
expect(
|
||||
parseEvidence(mcpCompared.structuredContent).normalized_result,
|
||||
).toMatchObject({
|
||||
left: { resource_limits: [] },
|
||||
right: { resource_limits: limits },
|
||||
coverage: wholeReport.coverage,
|
||||
});
|
||||
expect(wholeEvidence.limitations).toEqual(
|
||||
expect.arrayContaining(wholeReport.limitations),
|
||||
);
|
||||
|
||||
const selected = await cli.run({
|
||||
arguments: [
|
||||
"compare-managed-members",
|
||||
left,
|
||||
right,
|
||||
"--type",
|
||||
"Fixture.Empty0",
|
||||
"--json",
|
||||
],
|
||||
});
|
||||
expect(selected.exitCode, selected.stderr).toBe(0);
|
||||
const selectedEvidence = parseEvidence(selected.json);
|
||||
expect(selectedEvidence.parameters).toMatchObject({
|
||||
left_path: left,
|
||||
right_path: right,
|
||||
type_names: ["Fixture.Empty0"],
|
||||
});
|
||||
const selectedReport = managedMemberComparisonResultSchema.parse(
|
||||
selectedEvidence.normalized_result,
|
||||
);
|
||||
expect(selectedReport.coverage).toMatchObject({
|
||||
left_status: "complete",
|
||||
right_status: "complete",
|
||||
});
|
||||
expect(selectedReport.left.resource_limits).toEqual([]);
|
||||
expect(selectedReport.right.resource_limits).toEqual([]);
|
||||
expect(selectedReport.summary).toEqual({
|
||||
unchanged: 2,
|
||||
changed: 0,
|
||||
added: 0,
|
||||
removed: 0,
|
||||
unknown: 0,
|
||||
});
|
||||
expect(selectedReport.fields).toMatchObject([
|
||||
{
|
||||
status: "unchanged",
|
||||
left: { name: "Field0" },
|
||||
right: { name: "Field0" },
|
||||
},
|
||||
]);
|
||||
expect(selectedReport.methods).toMatchObject([
|
||||
{
|
||||
status: "unchanged",
|
||||
left: { name: "Method0" },
|
||||
right: { name: "Method0" },
|
||||
},
|
||||
]);
|
||||
},
|
||||
);
|
||||
|
||||
const managedComparisonEvidence = async () => {
|
||||
const directory = await createTestTempDirectory("rea-managed-finalization-");
|
||||
const path = join(directory, "comparison.dll");
|
||||
@@ -248,6 +378,7 @@ it.each([true, false])(
|
||||
try {
|
||||
const result = await registeredComparisonCommand().run({
|
||||
args: { leftPath: "left.dll", rightPath: "right.dll" },
|
||||
options: {},
|
||||
});
|
||||
expect(result).toMatchObject({
|
||||
error: "Managed member comparison failed",
|
||||
@@ -296,6 +427,7 @@ it("retains completed comparison Evidence when interrupted during final cleanup"
|
||||
try {
|
||||
const result = await registeredComparisonCommand().run({
|
||||
args: { leftPath: "left.dll", rightPath: "right.dll" },
|
||||
options: {},
|
||||
});
|
||||
expect(result).toMatchObject({
|
||||
error: "Managed member comparison failed",
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
compareManagedMembersInputSchema,
|
||||
} from "../../../../src/domain/managed/managedMemberComparison.js";
|
||||
import { readManagedMemberEvidence } from "../../../../src/domain/managed/managedMemberComparisonMatch.js";
|
||||
import { comparisonLimitations } from "../../../../src/domain/managed/managedMemberComparisonCoverage.js";
|
||||
import { createEvidence } from "../../../../src/domain/evidence.js";
|
||||
import { projectAnalysisError } from "../../../../src/domain/analysisErrorProjection.js";
|
||||
|
||||
@@ -94,11 +95,27 @@ it("keeps missing members unknown when the opposite inspection retains only type
|
||||
);
|
||||
const completePath = join(directory, "complete.dll");
|
||||
const limitedPath = join(directory, "limited.dll");
|
||||
await writeFile(
|
||||
completePath,
|
||||
buildManagedPeFixture({ additionalEmptyTypes: 2 }),
|
||||
const completeBytes = buildManagedPeFixture({ additionalEmptyTypes: 2 });
|
||||
const limitedBytes = buildManagedMemberCapacityFixture();
|
||||
await writeFile(completePath, completeBytes);
|
||||
await writeFile(limitedPath, limitedBytes);
|
||||
const complete = inspectManagedMembersBytes(
|
||||
completeBytes,
|
||||
managedPeFixtureTarget(completeBytes, completePath),
|
||||
{ userStrings: false },
|
||||
);
|
||||
const limited = inspectManagedMembersBytes(
|
||||
limitedBytes,
|
||||
managedPeFixtureTarget(limitedBytes, limitedPath),
|
||||
{ userStrings: false },
|
||||
);
|
||||
expect(limited.coverage.issues).toContainEqual(
|
||||
expect.objectContaining({
|
||||
code: "resource-limit",
|
||||
scope: "metadata.representation",
|
||||
offset: expect.any(Number),
|
||||
}),
|
||||
);
|
||||
await writeFile(limitedPath, buildManagedMemberCapacityFixture());
|
||||
for (const [leftPath, rightPath] of [
|
||||
[completePath, limitedPath],
|
||||
[limitedPath, completePath],
|
||||
@@ -116,6 +133,18 @@ it("keeps missing members unknown when the opposite inspection retains only type
|
||||
expect(report.fields).toHaveLength(1);
|
||||
expect(report.methods[0]?.status).toBe("unknown");
|
||||
expect(report.fields[0]?.status).toBe("unknown");
|
||||
const sources =
|
||||
leftPath === completePath
|
||||
? ([complete, limited] as const)
|
||||
: ([limited, complete] as const);
|
||||
for (const [side, source] of [
|
||||
[report.left, sources[0]],
|
||||
[report.right, sources[1]],
|
||||
] as const)
|
||||
expect(side.resource_limits).toEqual(
|
||||
source.coverage.issues.filter(({ code }) => code === "resource-limit"),
|
||||
);
|
||||
expect(report.limitations).toEqual(comparisonLimitations(...sources));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -278,6 +307,19 @@ const pairs = (comparison: ReturnType<typeof compare>) => {
|
||||
const original = nestedInspection();
|
||||
const swapped = nestedInspection({ swapEnclosing: true });
|
||||
|
||||
it("preserves unrecorded resource-limit facts in historical comparison manifests", () => {
|
||||
const current = compare(original, swapped);
|
||||
expect(current.left.resource_limits).toEqual([]);
|
||||
expect(current.right.resource_limits).toEqual([]);
|
||||
const historical = structuredClone(current);
|
||||
delete historical.left.resource_limits;
|
||||
delete historical.right.resource_limits;
|
||||
const parsed = managedMemberComparisonResultSchema.parse(historical);
|
||||
expect(parsed.left).not.toHaveProperty("resource_limits");
|
||||
expect(parsed.right).not.toHaveProperty("resource_limits");
|
||||
expect(parsed.summary).toEqual(current.summary);
|
||||
});
|
||||
|
||||
it("does not equate missing nested parents with genuinely top-level types", () => {
|
||||
const missing = nestedInspection({ nestedClassRows: [] });
|
||||
const topLevel = nestedInspection({ topLevelTypes: true });
|
||||
|
||||
+15
@@ -1,5 +1,20 @@
|
||||
import { buildManagedPeFixture } from "../../src/dotnet/ManagedPe.fixture.js";
|
||||
|
||||
/** Large member inventories with one small, nonempty type available for selection. */
|
||||
export const buildManagedMemberSelectionCapacityFixture = (
|
||||
additionalEmptyTypes = 9400,
|
||||
): Buffer =>
|
||||
buildManagedPeFixture({
|
||||
additionalEmptyTypes,
|
||||
fieldRowCount: 40037,
|
||||
methodRowCount: 49738,
|
||||
memberRefRowCount: 11442,
|
||||
customAttributeRowCount: 58698,
|
||||
distinctMemberNames: true,
|
||||
firstTypeFieldRowCount: 1,
|
||||
firstTypeMethodRowCount: 1,
|
||||
});
|
||||
|
||||
/** Valid dense CIL exceeds the unchanged decoding bound, after TypeDef admission. */
|
||||
export const buildManagedMemberCapacityFixture = (
|
||||
options: Parameters<typeof buildManagedPeFixture>[0] = {},
|
||||
|
||||
Reference in new issue
Block a user