environment-modal.test.ts 2.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
  1. import fs from "node:fs"
  2. import os from "node:os"
  3. import path from "node:path"
  4. import { afterAll, expect, test } from "bun:test"
  5. import { Effect } from "effect"
  6. import { ChildProcess } from "effect/unstable/process"
  7. import { Failed, makeFiles } from "@opencode-ai/core/environment"
  8. import { environmentConformance } from "@opencode-ai/core/testing/environment-conformance"
  9. import { createModalSandbox } from "../src/workspace/modal"
  10. const enabled =
  11. !!process.env.OPENCODE_TEST_MODAL &&
  12. ((!!process.env.MODAL_TOKEN_ID && !!process.env.MODAL_TOKEN_SECRET) ||
  13. fs.existsSync(path.join(os.homedir(), ".modal.toml")))
  14. const modalTest = enabled ? test : test.skip
  15. const root = `/tmp/opencode-environment-${crypto.randomUUID()}`
  16. const sandbox = enabled
  17. ? createModalSandbox({
  18. app: "opencode-environment-tests",
  19. sandbox: { timeoutMs: 10 * 60 * 1000 },
  20. })
  21. : undefined
  22. modalTest(
  23. "kills a running modal process",
  24. async () => {
  25. const value = await sandbox!
  26. const started = performance.now()
  27. const exitCode = await Effect.runPromise(
  28. Effect.scoped(
  29. Effect.gen(function* () {
  30. const handle = yield* value.driver.spawner.spawn(ChildProcess.make("sleep", ["300"]))
  31. yield* handle.kill()
  32. return yield* handle.exitCode
  33. }),
  34. ).pipe(Effect.timeout("10 seconds")),
  35. )
  36. expect(exitCode).not.toBe(0)
  37. expect(performance.now() - started).toBeLessThan(10_000)
  38. },
  39. 15_000,
  40. )
  41. environmentConformance(
  42. "modal environment",
  43. () =>
  44. Effect.promise(async () => {
  45. const value = await sandbox!
  46. return {
  47. files: makeFiles(value.driver),
  48. root,
  49. symlink: (target, link) =>
  50. Effect.scoped(value.driver.spawner.exitCode(ChildProcess.make("ln", ["-s", "--", target, link]))).pipe(
  51. Effect.flatMap((code) =>
  52. code === 0 ? Effect.void : Effect.fail(new Failed({ path: link, cause: new Error(`ln exited ${code}`) })),
  53. ),
  54. Effect.mapError((cause) => (cause instanceof Failed ? cause : new Failed({ path: link, cause }))),
  55. ),
  56. }
  57. }),
  58. !enabled,
  59. )
  60. afterAll(async () => {
  61. if (!sandbox) return
  62. await (await sandbox).terminate()
  63. })