|
1 | | -import { afterAll, afterEach, describe, test, expect } from "bun:test" |
| 1 | +import { afterEach, describe, expect } from "bun:test" |
2 | 2 | import path from "path" |
3 | 3 | import fs from "fs/promises" |
4 | | -import { Cause, Deferred, Effect, Exit, Layer, ManagedRuntime } from "effect" |
| 4 | +import { Cause, Deferred, Effect, Exit, Fiber, Layer } from "effect" |
5 | 5 | import { EditTool } from "../../src/tool/edit" |
6 | | -import { WithInstance } from "../../src/project/with-instance" |
7 | | -import { disposeAllInstances, TestInstance, tmpdir } from "../fixture/fixture" |
| 6 | +import { disposeAllInstances, TestInstance } from "../fixture/fixture" |
8 | 7 | import { LSP } from "@/lsp/lsp" |
9 | 8 | import { AppFileSystem } from "@opencode-ai/core/filesystem" |
10 | 9 | import { Format } from "../../src/format" |
@@ -42,20 +41,6 @@ const layer = Layer.mergeAll( |
42 | 41 |
|
43 | 42 | const it = testEffect(layer) |
44 | 43 |
|
45 | | -const runtime = ManagedRuntime.make(layer) |
46 | | - |
47 | | -afterAll(async () => { |
48 | | - await runtime.dispose() |
49 | | -}) |
50 | | - |
51 | | -const resolve = () => |
52 | | - runtime.runPromise( |
53 | | - Effect.gen(function* () { |
54 | | - const info = yield* EditTool |
55 | | - return yield* info.init() |
56 | | - }), |
57 | | - ) |
58 | | - |
59 | 44 | const init = Effect.fn("EditToolTest.init")(function* () { |
60 | 45 | const info = yield* EditTool |
61 | 46 | return yield* info.init() |
@@ -500,58 +485,49 @@ describe("tool.edit", () => { |
500 | 485 | }) |
501 | 486 |
|
502 | 487 | describe("concurrent editing", () => { |
503 | | - test("preserves concurrent edits to different sections of the same file", async () => { |
504 | | - await using tmp = await tmpdir() |
505 | | - const filepath = path.join(tmp.path, "file.txt") |
506 | | - await fs.writeFile(filepath, "top = 0\nmiddle = keep\nbottom = 0\n", "utf-8") |
507 | | - |
508 | | - await WithInstance.provide({ |
509 | | - directory: tmp.path, |
510 | | - fn: async () => { |
511 | | - const edit = await resolve() |
512 | | - let asks = 0 |
513 | | - const firstAsk = Promise.withResolvers<void>() |
514 | | - const delayedCtx = { |
515 | | - ...ctx, |
516 | | - ask: () => |
517 | | - Effect.gen(function* () { |
518 | | - asks++ |
519 | | - if (asks !== 1) return |
520 | | - firstAsk.resolve() |
521 | | - yield* Effect.promise(() => Bun.sleep(50)) |
522 | | - }), |
523 | | - } |
524 | | - |
525 | | - const promise1 = Effect.runPromise( |
526 | | - edit.execute( |
527 | | - { |
528 | | - filePath: filepath, |
529 | | - oldString: "top = 0", |
530 | | - newString: "top = 1", |
531 | | - }, |
532 | | - delayedCtx, |
533 | | - ), |
534 | | - ) |
535 | | - |
536 | | - await firstAsk.promise |
537 | | - |
538 | | - const promise2 = Effect.runPromise( |
539 | | - edit.execute( |
540 | | - { |
541 | | - filePath: filepath, |
542 | | - oldString: "bottom = 0", |
543 | | - newString: "bottom = 2", |
544 | | - }, |
545 | | - delayedCtx, |
546 | | - ), |
547 | | - ) |
548 | | - |
549 | | - const results = await Promise.allSettled([promise1, promise2]) |
550 | | - expect(results[0]?.status).toBe("fulfilled") |
551 | | - expect(results[1]?.status).toBe("fulfilled") |
552 | | - expect(await fs.readFile(filepath, "utf-8")).toBe("top = 1\nmiddle = keep\nbottom = 2\n") |
553 | | - }, |
554 | | - }) |
555 | | - }) |
| 488 | + it.instance("preserves concurrent edits to different sections of the same file", () => |
| 489 | + Effect.gen(function* () { |
| 490 | + const test = yield* TestInstance |
| 491 | + const filepath = path.join(test.directory, "file.txt") |
| 492 | + yield* put(filepath, "top = 0\nmiddle = keep\nbottom = 0\n") |
| 493 | + |
| 494 | + const firstAsk = yield* Deferred.make<void>() |
| 495 | + let asks = 0 |
| 496 | + const delayedCtx = { |
| 497 | + ...ctx, |
| 498 | + ask: () => |
| 499 | + Effect.gen(function* () { |
| 500 | + asks++ |
| 501 | + if (asks !== 1) return |
| 502 | + yield* Deferred.succeed(firstAsk, undefined) |
| 503 | + yield* Effect.promise(() => Bun.sleep(50)) |
| 504 | + }), |
| 505 | + } |
| 506 | + |
| 507 | + const first = yield* run( |
| 508 | + { |
| 509 | + filePath: filepath, |
| 510 | + oldString: "top = 0", |
| 511 | + newString: "top = 1", |
| 512 | + }, |
| 513 | + delayedCtx, |
| 514 | + ).pipe(Effect.forkScoped) |
| 515 | + |
| 516 | + yield* Deferred.await(firstAsk) |
| 517 | + yield* Effect.all([ |
| 518 | + Fiber.join(first), |
| 519 | + run( |
| 520 | + { |
| 521 | + filePath: filepath, |
| 522 | + oldString: "bottom = 0", |
| 523 | + newString: "bottom = 2", |
| 524 | + }, |
| 525 | + delayedCtx, |
| 526 | + ), |
| 527 | + ]) |
| 528 | + |
| 529 | + expect(yield* load(filepath)).toBe("top = 1\nmiddle = keep\nbottom = 2\n") |
| 530 | + }), |
| 531 | + ) |
556 | 532 | }) |
557 | 533 | }) |
0 commit comments