|
1 | 1 | import * as NodeCrypto from "@effect/platform-node/NodeCrypto"; |
| 2 | +import * as NodeServices from "@effect/platform-node/NodeServices"; |
2 | 3 | import { assert, it, vi } from "@effect/vitest"; |
3 | 4 | import { CheckpointRef, CheckpointScopeId, RunId, ThreadId } from "@t3tools/contracts"; |
4 | 5 | import * as Effect from "effect/Effect"; |
| 6 | +import * as FileSystem from "effect/FileSystem"; |
5 | 7 | import * as Layer from "effect/Layer"; |
| 8 | +import * as Path from "effect/Path"; |
6 | 9 |
|
7 | 10 | import { checkpointRefForScopeOrdinal } from "../orchestration-v2/CheckpointService.ts"; |
8 | 11 | import { OrchestratorProjectionError } from "../orchestration-v2/Orchestrator.ts"; |
9 | 12 | import type { ProjectionCheckpointContext } from "../orchestration-v2/ProjectionStore.ts"; |
10 | 13 | import * as ThreadManagement from "../orchestration-v2/ThreadManagementService.ts"; |
| 14 | +import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts"; |
| 15 | +import * as VcsProcess from "../vcs/VcsProcess.ts"; |
11 | 16 | import * as CheckpointDiffQuery from "./CheckpointDiffQuery.ts"; |
12 | 17 | import * as CheckpointStore from "./CheckpointStore.ts"; |
13 | 18 | import { |
@@ -83,7 +88,7 @@ it.effect("computes V2 run diffs from projected checkpoint scopes", () => { |
83 | 88 | toTurnCount: 2, |
84 | 89 | diff: "diff --git a/file b/file", |
85 | 90 | }); |
86 | | - assert.deepEqual(diffCheckpoints.mock.calls[0]?.[0], { |
| 91 | + assert.containSubset(diffCheckpoints.mock.calls[0]?.[0], { |
87 | 92 | cwd: "/repo", |
88 | 93 | fromCheckpointRef: yield* checkpointRefForScopeOrdinal({ |
89 | 94 | scopeId: firstScopeId, |
@@ -310,3 +315,157 @@ it.effect("preserves the typed missing-baseline-ref error contract", () => { |
310 | 315 | ); |
311 | 316 | }).pipe(Effect.provide(layer)); |
312 | 317 | }); |
| 318 | + |
| 319 | +const layerVcsProcess = VcsProcess.layer.pipe(Layer.provide(NodeServices.layer)); |
| 320 | +const layerStore = CheckpointStore.layer.pipe( |
| 321 | + Layer.provide(VcsDriverRegistry.layer.pipe(Layer.provide(layerVcsProcess))), |
| 322 | + Layer.provide(NodeServices.layer), |
| 323 | +); |
| 324 | +const layerTest = Layer.merge(layerStore, layerVcsProcess).pipe( |
| 325 | + Layer.provideMerge(NodeServices.layer), |
| 326 | + Layer.provideMerge(NodeCrypto.layer), |
| 327 | +); |
| 328 | + |
| 329 | +it.effect("shares the turn and full-thread diff budget across real Git batches", () => |
| 330 | + Effect.gen(function* () { |
| 331 | + const fs = yield* FileSystem.FileSystem; |
| 332 | + const path = yield* Path.Path; |
| 333 | + const process = yield* VcsProcess.VcsProcess; |
| 334 | + const store = yield* CheckpointStore.CheckpointStore; |
| 335 | + const cwd = yield* fs.makeTempDirectoryScoped({ prefix: "t3-diff-aggregate-" }); |
| 336 | + const git = (args: ReadonlyArray<string>, env?: NodeJS.ProcessEnv) => |
| 337 | + process.run({ |
| 338 | + operation: "CheckpointDiffQuery.test.git", |
| 339 | + command: "git", |
| 340 | + cwd, |
| 341 | + args, |
| 342 | + ...(env === undefined ? {} : { env }), |
| 343 | + }); |
| 344 | + yield* git(["init"]); |
| 345 | + yield* git(["config", "user.email", "test@test.com"]); |
| 346 | + yield* git(["config", "user.name", "Test"]); |
| 347 | + yield* fs.writeFileString(path.join(cwd, "README.md"), "initial\n"); |
| 348 | + yield* git(["add", "."]); |
| 349 | + yield* git(["commit", "-m", "initial"]); |
| 350 | + |
| 351 | + const threadId = ThreadId.make("thread:aggregate-runtime"); |
| 352 | + const runId = RunId.make("run:aggregate-runtime"); |
| 353 | + const scopeId = CheckpointScopeId.make("scope:aggregate-runtime"); |
| 354 | + const fromCheckpointRef = yield* checkpointRefForScopeOrdinal({ |
| 355 | + scopeId, |
| 356 | + ordinalWithinScope: 0, |
| 357 | + }); |
| 358 | + const toCheckpointRef = CheckpointRef.make("refs/t3/test/aggregate-after"); |
| 359 | + yield* store.captureCheckpoint({ cwd, checkpointRef: fromCheckpointRef }); |
| 360 | + // The old commit models files imported by a pull after the baseline was captured. |
| 361 | + yield* fs.writeFileString(path.join(cwd, "upstream.ts"), "imported\n"); |
| 362 | + yield* git(["add", "."]); |
| 363 | + yield* git(["commit", "-m", "imported"], { |
| 364 | + ...globalThis.process.env, |
| 365 | + GIT_COMMITTER_DATE: "2000-01-01T00:00:00Z", |
| 366 | + }); |
| 367 | + const ownPaths = Array.from({ length: 1_600 }, (_, index) => `src/module-${index}/file.ts`); |
| 368 | + yield* Effect.forEach( |
| 369 | + ownPaths, |
| 370 | + (file) => |
| 371 | + Effect.gen(function* () { |
| 372 | + yield* fs.makeDirectory(path.dirname(path.join(cwd, file)), { recursive: true }); |
| 373 | + yield* fs.writeFileString(path.join(cwd, file), `${"x".repeat(12_000)}\n`); |
| 374 | + }), |
| 375 | + { concurrency: 16, discard: true }, |
| 376 | + ); |
| 377 | + yield* store.captureCheckpoint({ cwd, checkpointRef: toCheckpointRef }); |
| 378 | + |
| 379 | + const patchSizes: number[] = []; |
| 380 | + const query = yield* CheckpointDiffQuery.make.pipe( |
| 381 | + Effect.provide( |
| 382 | + Layer.mock(ThreadManagement.ThreadManagementService)({ |
| 383 | + getCheckpointContext: () => |
| 384 | + Effect.succeed({ |
| 385 | + runs: [{ id: runId, ordinal: 1, status: "completed" }], |
| 386 | + checkpointScopes: [{ id: scopeId, runId, kind: "root_run", cwd }], |
| 387 | + checkpoints: [ |
| 388 | + { |
| 389 | + scopeId, |
| 390 | + runId, |
| 391 | + ordinalWithinScope: 1, |
| 392 | + appRunOrdinal: 1, |
| 393 | + status: "ready", |
| 394 | + ref: toCheckpointRef, |
| 395 | + }, |
| 396 | + ], |
| 397 | + }), |
| 398 | + }), |
| 399 | + ), |
| 400 | + Effect.provideService(CheckpointStore.CheckpointStore, { |
| 401 | + ...store, |
| 402 | + diffCheckpoints: (input) => |
| 403 | + store.diffCheckpoints(input).pipe( |
| 404 | + Effect.tap((patch) => |
| 405 | + Effect.sync(() => { |
| 406 | + if (input.format !== "numstat") { |
| 407 | + patchSizes.push(Buffer.byteLength(patch)); |
| 408 | + } |
| 409 | + }), |
| 410 | + ), |
| 411 | + ), |
| 412 | + }), |
| 413 | + ); |
| 414 | + const turn = yield* query.getTurnDiff({ threadId, fromTurnCount: 0, toTurnCount: 1 }); |
| 415 | + const turnPatchSizes = patchSizes.splice(0); |
| 416 | + const fullThread = yield* query.getFullThreadDiff({ threadId, toTurnCount: 1 }); |
| 417 | + assert.isTrue(fullThread.diff === turn.diff); |
| 418 | + assert.isAtMost(Buffer.byteLength(turn.diff), 10_000_000); |
| 419 | + assert.isTrue(turn.diff.endsWith("\n\n[truncated]")); |
| 420 | + assert.include(turn.diff, "src/module-0/file.ts"); |
| 421 | + assert.notInclude(turn.diff, "upstream.ts"); |
| 422 | + assert.lengthOf(turnPatchSizes, 2); |
| 423 | + assert.deepEqual(patchSizes, turnPatchSizes); |
| 424 | + assert.isBelow(turnPatchSizes[0]!, 10_000_000); |
| 425 | + assert.strictEqual( |
| 426 | + turnPatchSizes.reduce((sum, size) => sum + size, 0), |
| 427 | + 10_000_001, |
| 428 | + ); |
| 429 | + }).pipe(Effect.provide(layerTest), Effect.scoped), |
| 430 | +); |
| 431 | + |
| 432 | +it.effect("truncates a storage-cut UTF-8 character without replacement text", () => { |
| 433 | + const prefix = "x".repeat(9_999_985); |
| 434 | + const layer = layerFor({ |
| 435 | + projection: Effect.succeed(makeProjection()), |
| 436 | + diffCheckpoints: (input) => |
| 437 | + Effect.succeed( |
| 438 | + Buffer.from(`${prefix}😀${"x".repeat(9)}😀tail`) |
| 439 | + .subarray(0, input.maxOutputBytes) |
| 440 | + .toString("utf8"), |
| 441 | + ), |
| 442 | + }); |
| 443 | + return Effect.gen(function* () { |
| 444 | + const query = yield* CheckpointDiffQuery.CheckpointDiffQuery; |
| 445 | + const result = yield* query.getTurnDiff({ threadId, fromTurnCount: 0, toTurnCount: 2 }); |
| 446 | + assert.isAtMost(Buffer.byteLength(result.diff), 10_000_000); |
| 447 | + assert.isTrue(result.diff === `${prefix}\n\n[truncated]`); |
| 448 | + }).pipe(Effect.provide(layer)); |
| 449 | +}); |
| 450 | + |
| 451 | +it.effect("preserves an exact-fit filtered patch across batches without a marker", () => { |
| 452 | + const ownPaths = Array.from({ length: 1_000 }, (_, index) => `src/module-${index}/file.ts`); |
| 453 | + const patches = ["é".repeat(3_000_000), "x".repeat(4_000_000)]; |
| 454 | + let patchReads = 0; |
| 455 | + const layer = layerFor({ |
| 456 | + projection: Effect.succeed(makeProjection()), |
| 457 | + authoredPaths: new Set(ownPaths), |
| 458 | + diffCheckpoints: (input) => |
| 459 | + Effect.sync(() => |
| 460 | + input.format === "numstat" |
| 461 | + ? [...ownPaths, "upstream.ts"].map((path) => `1\t0\t${path}\0`).join("") |
| 462 | + : patches[patchReads++]!, |
| 463 | + ), |
| 464 | + }); |
| 465 | + return Effect.gen(function* () { |
| 466 | + const query = yield* CheckpointDiffQuery.CheckpointDiffQuery; |
| 467 | + const result = yield* query.getFullThreadDiff({ threadId, toTurnCount: 2 }); |
| 468 | + assert.isTrue(result.diff === patches.join("")); |
| 469 | + assert.strictEqual(patchReads, 2); |
| 470 | + }).pipe(Effect.provide(layer)); |
| 471 | +}); |
0 commit comments