test(ci): align source preservation and catalog assertions
This commit is contained in:
1 parent
b4b5c37ab3
commit
741f2de751
4 files changed
+102
-15
No files matched your search
@@ -26,10 +26,7 @@ import {
|
||||
locateApkSigningBlock,
|
||||
parseZipTail,
|
||||
} from "../dist/domain/apkSignature/apkSigningBlock.js";
|
||||
import {
|
||||
mcpTextValue,
|
||||
requireMcpOutput,
|
||||
} from "./lib/mcp-verifier-results.mjs";
|
||||
import { mcpTextValue, requireMcpOutput } from "./lib/mcp-verifier-results.mjs";
|
||||
import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
|
||||
|
||||
const execute = promisify(execFile);
|
||||
|
||||
@@ -27,7 +27,15 @@ const processRow = (
|
||||
pid: number,
|
||||
ppid: number | null,
|
||||
rss: number | null,
|
||||
) => ({ user: "shell", pid, ppid, rss, state: "S", name });
|
||||
) => ({
|
||||
user: "shell",
|
||||
pid,
|
||||
ppid,
|
||||
rss,
|
||||
state: "S",
|
||||
// The final NAME column retains the exact suffix emitted by processLine.
|
||||
name: `${name} \t`,
|
||||
});
|
||||
|
||||
const processListing = (variant: Variant) => {
|
||||
const boundary = Number.MAX_SAFE_INTEGER;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { mkdir, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { expect, it, onTestFinished, vi } from "vitest";
|
||||
@@ -55,6 +56,10 @@ import { LocalWebModuleArtifacts } from "../../../src/browser/modules/WebModuleA
|
||||
import { ProviderCleanupError } from "../../../src/domain/providerCleanupError.js";
|
||||
import { AnalysisCancelledError } from "../../../src/domain/analysisErrorCore.js";
|
||||
import { projectAnalysisError } from "../../../src/domain/analysisErrorProjection.js";
|
||||
import {
|
||||
webArtifactAcquisitionSchema,
|
||||
type WebArtifactAcquisition,
|
||||
} from "../../../src/domain/webArtifactAcquisition.js";
|
||||
import { primaryAnalysisError } from "../../../src/domain/analysisErrorBase.js";
|
||||
import { webModuleArtifactsFixture } from "../../fixtures/webModuleTrace.js";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
@@ -65,15 +70,24 @@ const sourceFixture = async (source: string) => {
|
||||
const manifestPath = join(root, "manifest.json");
|
||||
const sourcePath = join(root, "files", "modules", "main.js");
|
||||
await mkdir(join(root, "files", "modules"), { recursive: true });
|
||||
await writeFile(sourcePath, source);
|
||||
await writeFile(manifestPath, JSON.stringify(fixture.manifest));
|
||||
const sourceBytes = Buffer.from(source);
|
||||
const manifestBytes = Buffer.from(JSON.stringify(fixture.manifest));
|
||||
await writeFile(sourcePath, sourceBytes);
|
||||
await writeFile(manifestPath, manifestBytes);
|
||||
return {
|
||||
input: { manifest_path: manifestPath, script_index: 0 },
|
||||
sourcePath,
|
||||
fixture,
|
||||
sourceBytes,
|
||||
manifestBytes,
|
||||
};
|
||||
};
|
||||
|
||||
const fileIdentity = (path: string, bytes: Buffer) => ({
|
||||
path,
|
||||
sha256: createHash("sha256").update(bytes).digest("hex"),
|
||||
bytes: bytes.length,
|
||||
});
|
||||
|
||||
it("retains cancelled source read ownership until service close retries it", async () => {
|
||||
onTestFinished(() => {
|
||||
injected.sourcePath = undefined;
|
||||
@@ -85,7 +99,8 @@ it("retains cancelled source read ownership until service close retries it", asy
|
||||
injected.closeAttempts = 0;
|
||||
injected.sourceHandle = undefined;
|
||||
});
|
||||
const { input, sourcePath } = await sourceFixture('import "./dep.js";');
|
||||
const { input, sourcePath, manifestBytes } =
|
||||
await sourceFixture('import "./dep.js";');
|
||||
|
||||
injected.sourcePath = sourcePath;
|
||||
injected.failClose = true;
|
||||
@@ -109,6 +124,23 @@ it("retains cancelled source read ownership until service close retries it", asy
|
||||
const cancelled = await service.trace(input, { signal: controller.signal });
|
||||
expect(cancelled.ok).toBe(false);
|
||||
if (cancelled.ok) throw new Error("Expected cancelled source read");
|
||||
const expectedAcquisition: WebArtifactAcquisition = {
|
||||
kind: "web-artifact-acquisition",
|
||||
analysis: "not-performed",
|
||||
files: [
|
||||
{
|
||||
role: "manifest",
|
||||
file: fileIdentity(input.manifest_path, manifestBytes),
|
||||
admission: "manifest-validated",
|
||||
},
|
||||
],
|
||||
};
|
||||
expect(
|
||||
webArtifactAcquisitionSchema.parse(cancelled.error.partialObservation),
|
||||
).toEqual(expectedAcquisition);
|
||||
expect(
|
||||
projectAnalysisError(cancelled.error).details?.partial_observation,
|
||||
).toEqual(expectedAcquisition);
|
||||
expect(cancelled.error.cleanupIncomplete).toBe(true);
|
||||
expect(cancelled.error.cleanupResources).toContain(sourcePath);
|
||||
expect(resolver.resolve).not.toHaveBeenCalled();
|
||||
@@ -158,6 +190,13 @@ it("retains cancelled source read ownership until service close retries it", asy
|
||||
expect(injected.closeAttempts).toBeGreaterThan(attemptsBeforeRetry);
|
||||
expect(resolver.close).toHaveBeenCalledTimes(3);
|
||||
expect(injected.sourceHandle?.fd).toBe(-1);
|
||||
expect(primaryAnalysisError(cancelled.error).partialObservation).toEqual(
|
||||
expectedAcquisition,
|
||||
);
|
||||
expect(
|
||||
projectAnalysisError(primaryAnalysisError(cancelled.error)).details
|
||||
?.partial_observation,
|
||||
).toEqual(expectedAcquisition);
|
||||
for (const error of [cancelled.error, blocked.error, cleanupFailure]) {
|
||||
expect(error).toMatchObject({
|
||||
cleanupIncomplete: false,
|
||||
@@ -176,9 +215,8 @@ it.each([
|
||||
])(
|
||||
"keeps physical source read failure and exact cleanup ownership %j",
|
||||
async ({ readFails, closeFails, cancel }) => {
|
||||
const { input, sourcePath, fixture } = await sourceFixture(
|
||||
"export const value = 1;",
|
||||
);
|
||||
const { input, sourcePath, sourceBytes, manifestBytes } =
|
||||
await sourceFixture("export const value = 1;");
|
||||
const physical = Object.assign(new Error("fixture descriptor EIO"), {
|
||||
code: "EIO",
|
||||
syscall: "read",
|
||||
@@ -212,7 +250,10 @@ it.each([
|
||||
if (!readFails && !cancel) {
|
||||
if (!result.ok) throw result.error;
|
||||
expect(result.value.normalized_result).toMatchObject({
|
||||
source: { path: sourcePath, sha256: fixture.sourceFile.sha256 },
|
||||
source: {
|
||||
path: sourcePath,
|
||||
sha256: fileIdentity(sourcePath, sourceBytes).sha256,
|
||||
},
|
||||
manifest: { path: input.manifest_path },
|
||||
});
|
||||
await service.close();
|
||||
@@ -229,7 +270,34 @@ it.each([
|
||||
});
|
||||
else expect(primary).toBeInstanceOf(AnalysisCancelledError);
|
||||
expect(primary.cause).toBe(readFails ? physical : reason);
|
||||
expect(result.error.partialObservation).toBeUndefined();
|
||||
const expectedAcquisition: WebArtifactAcquisition = {
|
||||
kind: "web-artifact-acquisition",
|
||||
analysis: "not-performed",
|
||||
files: [
|
||||
{
|
||||
role: "manifest",
|
||||
file: fileIdentity(input.manifest_path, manifestBytes),
|
||||
admission: "manifest-validated",
|
||||
},
|
||||
// Descriptor-finalization cancellation precedes selected-source digest/length admission.
|
||||
...(!readFails
|
||||
? [
|
||||
{
|
||||
role: "source" as const,
|
||||
file: fileIdentity(sourcePath, sourceBytes),
|
||||
admission: "read" as const,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
};
|
||||
expect(
|
||||
webArtifactAcquisitionSchema.parse(result.error.partialObservation),
|
||||
).toEqual(expectedAcquisition);
|
||||
expect(primary.partialObservation).toEqual(expectedAcquisition);
|
||||
expect(
|
||||
projectAnalysisError(result.error).details?.partial_observation,
|
||||
).toEqual(expectedAcquisition);
|
||||
expect(result.error.cleanupIncomplete).toBe(closeFails);
|
||||
if (closeFails) {
|
||||
expect(result.error.cleanupResources).toEqual([sourcePath]);
|
||||
@@ -242,6 +310,10 @@ it.each([
|
||||
expect(result.error.cleanupIncomplete).toBe(false);
|
||||
expect(result.error.cleanupResources).toEqual([]);
|
||||
expect(primaryAnalysisError(result.error)).toBe(primary);
|
||||
expect(primary.partialObservation).toEqual(expectedAcquisition);
|
||||
expect(projectAnalysisError(primary).details?.partial_observation).toEqual(
|
||||
expectedAcquisition,
|
||||
);
|
||||
expect(projectAnalysisError(result.error)).toMatchObject({
|
||||
code: readFails ? "artifact_operation_failed" : "cancelled",
|
||||
...(readFails
|
||||
|
||||
@@ -61,10 +61,20 @@ it("verifies canonical names, converted schemas and session metadata together",
|
||||
}))
|
||||
.filter((group) => group.names.length > 0);
|
||||
toolContract("binary_session").outputSchema.parse(body);
|
||||
const matchingOmission = {
|
||||
const disagreeingOmission = {
|
||||
...status,
|
||||
content: [{ type: "text" as const, text: JSON.stringify(body) }],
|
||||
};
|
||||
const matchingOmission = {
|
||||
...disagreeingOmission,
|
||||
structuredContent: body,
|
||||
};
|
||||
await expect(
|
||||
verifyCompleteToolCatalog({
|
||||
...client,
|
||||
callTool: async () => disagreeingOmission,
|
||||
}),
|
||||
).rejects.toThrow("binary_session text and structured results disagree");
|
||||
// Equal inventories are insufficient when both omit the same canonical tool.
|
||||
await expect(
|
||||
verifyCompleteToolCatalog({
|
||||
|
||||
Reference in new issue
Block a user