fix: bulk windows apply-patch updates

This commit is contained in:
Codex
2026-06-07 00:28:38 +00:00
parent b81585bcab
commit d4b7fc95f9
5 changed files with 173 additions and 13 deletions
+106 -1
View File
@@ -5,7 +5,7 @@ import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, wr
import os from "node:os";
import path from "node:path";
import { sshHelp } from "./src/help";
import { runApplyPatchV2, type ApplyPatchV2TimingSummary } from "./src/apply-patch-v2";
import { runApplyPatchV2, type ApplyPatchV2TimingSummary, type ApplyPatchV2BulkReplacementWritePlan } from "./src/apply-patch-v2";
import { providerTriageRecommendedCrossChecks } from "./src/provider-triage";
import { extractRemoteCliOptions, remoteSshFrontendPlanForTest } from "./src/remote";
import { runSshFileTransferOperation, type SshFileTransferCommandBuilders, type SshRemoteCommandExecutor } from "./src/ssh-file-transfer";
@@ -341,6 +341,95 @@ async function applyPatchV2Fixture(patch: string, files: Record<string, string>)
return { stdout: result.stdout, files: result.files, commands: result.commands };
}
async function applyPatchV2FsBulkFixtureAttempt(patch: string, files: Record<string, string>): Promise<{ stdout: string; stderr: string; exitCode: number | null; files: Record<string, string>; operations: string[]; error: unknown | null }> {
const state = new Map(Object.entries(files));
const operations: string[] = [];
const stdin = new PassThrough();
stdin.end(patch);
let stdout = "";
let stderr = "";
const stdoutSink = new Writable({
write(chunk, _encoding, callback) {
stdout += Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk);
callback();
},
});
const stderrSink = new Writable({
write(chunk, _encoding, callback) {
stderr += Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk);
callback();
},
});
let error: unknown | null = null;
let exitCode: number | null = null;
try {
exitCode = await runApplyPatchV2({
stdin,
stdout: stdoutSink,
stderr: stderrSink,
executor: {
fs: {
async stat(filePath) {
operations.push(`stat ${filePath}`);
const content = state.get(filePath);
if (content === undefined) throw new Error(`missing ${filePath}`);
const buffer = Buffer.from(content, "utf8");
return { bytes: buffer.length, sha256: sha256BufferHex(buffer) };
},
async readBlock(filePath, blockIndex, blockBytes) {
operations.push(`readBlock ${filePath}`);
const content = state.get(filePath);
if (content === undefined) throw new Error(`missing ${filePath}`);
const buffer = Buffer.from(content, "utf8");
return buffer.subarray(blockIndex * blockBytes, (blockIndex + 1) * blockBytes);
},
async writeFile(filePath, content) {
operations.push(`writeFile ${filePath}`);
state.set(filePath, content.toString("utf8"));
},
async deleteFile(filePath) {
operations.push(`deleteFile ${filePath}`);
state.delete(filePath);
},
async readFiles(paths) {
operations.push(`readFiles ${paths.join(",")}`);
const result = new Map<string, string>();
for (const filePath of paths) {
const content = state.get(filePath);
if (content === undefined) throw new Error(`missing ${filePath}`);
result.set(filePath, content);
}
return result;
},
async applyReplacementsBulk(paths: Iterable<string>, plans: Map<string, ApplyPatchV2BulkReplacementWritePlan>) {
const targets = Array.from(paths);
operations.push(`applyReplacementsBulk ${targets.join(",")}`);
for (const filePath of targets) {
const plan = plans.get(filePath);
const original = state.get(filePath);
if (plan === undefined || original === undefined) throw new Error(`missing replacement plan ${filePath}`);
const originalBuffer = Buffer.from(original, "utf8");
assertCondition(originalBuffer.length === plan.originalBytes && sha256BufferHex(originalBuffer) === plan.originalSha256, "fs bulk fixture original integrity mismatch", { filePath, plan });
const lines = original.split("\n");
if (lines.at(-1) === "") lines.pop();
for (const [start, oldLength, newLines] of [...plan.replacements].reverse()) {
lines.splice(start, oldLength, ...newLines);
}
const updated = lines.length === 0 ? "" : `${lines.join("\n")}\n`;
const updatedBuffer = Buffer.from(updated, "utf8");
assertCondition(updatedBuffer.length === plan.finalBytes && sha256BufferHex(updatedBuffer) === plan.finalSha256, "fs bulk fixture final integrity mismatch", { filePath, plan });
state.set(filePath, updated);
}
},
},
},
});
} catch (caught) {
error = caught;
}
return { stdout, stderr, exitCode, files: Object.fromEntries(state), operations, error };
}
function fileTransferFixture(initial: Record<string, Buffer> = {}, options: { emptyReadOnce?: Record<string, number[]>; shortReadOnce?: Record<string, Record<string, number>> } = {}): {
state: Map<string, Buffer>;
commands: Array<{ operation: string; stdin: boolean }>;
@@ -865,6 +954,22 @@ export async function runSshArgvGuidanceContract(): Promise<JsonRecord> {
const bulkTiming = applyPatchTimingFromStderr(bulkV2.stderr);
assertCondition(bulkTiming.remoteOperationCount === 2 && bulkTiming.remoteOperationCounts["read-bulk-b64"] === 1 && bulkTiming.remoteOperationCounts["apply-replacements-bulk-stdin"] === 1, "v2 timing summary should classify bulk read and line-level apply operations", bulkTiming);
const fsBulkV2 = await applyPatchV2FsBulkFixtureAttempt(bulkPatchLines.join("\n"), bulkFiles);
assertCondition(fsBulkV2.exitCode === 0 && fsBulkV2.error === null, "v2 fs executor should support the same multi-file bulk update path", fsBulkV2);
assertCondition(
fsBulkV2.operations.length === 2
&& fsBulkV2.operations[0]?.startsWith("readFiles ")
&& fsBulkV2.operations[1]?.startsWith("applyReplacementsBulk "),
"v2 fs multi-file update path should use fs bulk read and line-level apply operations",
fsBulkV2.operations,
);
assertCondition(!fsBulkV2.operations.some((operation) => operation.startsWith("stat ") || operation.startsWith("readBlock ") || operation.startsWith("writeFile ")), "v2 fs bulk path should avoid per-file stat/read/write operations", fsBulkV2.operations);
for (let fileIndex = 0; fileIndex < 4; fileIndex += 1) {
assertCondition(fsBulkV2.files[`bulk-${fileIndex}.txt`]?.includes(`file=${fileIndex} line=40 value=beta`), "v2 fs bulk path should write all changed files", { fileIndex, files: fsBulkV2.files });
}
const fsBulkTiming = applyPatchTimingFromStderr(fsBulkV2.stderr);
assertCondition(fsBulkTiming.remoteOperationCount === 2 && fsBulkTiming.remoteOperationCounts["fs.readFiles"] === 1 && fsBulkTiming.remoteOperationCounts["fs.applyReplacementsBulk"] === 1, "v2 timing summary should classify fs bulk read and line-level apply operations", fsBulkTiming);
const unprefixedUpdateContextV2 = await applyPatchV2FixtureAttempt([
"*** Begin Patch",
"*** Update File: internal/cloud/access-control.ts",