standalone.test.ts 1.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566
  1. import { expect, test } from "bun:test"
  2. import path from "node:path"
  3. test("standalone server exits when its owner is killed", async () => {
  4. const owner = Bun.spawn([process.execPath, path.join(import.meta.dir, "fixture/standalone-owner.ts")], {
  5. cwd: path.join(import.meta.dir, ".."),
  6. env: { ...process.env, OPENCODE_SERVER_USERNAME: "custom" },
  7. stdin: "ignore",
  8. stdout: "pipe",
  9. stderr: "pipe",
  10. })
  11. const line = await Promise.race([readLine(owner.stdout), Bun.sleep(10_000).then(() => undefined)])
  12. const [rawPID, url, status] = line?.split(" ") ?? []
  13. const pid = Number(rawPID)
  14. try {
  15. expect(pid).toBeGreaterThan(0)
  16. expect(url).toStartWith("http://127.0.0.1:")
  17. expect(status).toBe("200")
  18. expect(running(pid)).toBe(true)
  19. owner.kill("SIGKILL")
  20. await owner.exited
  21. expect(await waitForExit(pid)).toBe(true)
  22. } finally {
  23. owner.kill("SIGKILL")
  24. if (running(pid)) process.kill(pid, "SIGKILL")
  25. }
  26. })
  27. async function readLine(stream: ReadableStream<Uint8Array>) {
  28. const reader = stream.getReader()
  29. const decoder = new TextDecoder()
  30. const chunks: string[] = []
  31. while (true) {
  32. const result = await reader.read()
  33. if (result.done) break
  34. chunks.push(decoder.decode(result.value, { stream: true }))
  35. const output = chunks.join("")
  36. const newline = output.indexOf("\n")
  37. if (newline !== -1) {
  38. reader.releaseLock()
  39. return output.slice(0, newline)
  40. }
  41. }
  42. reader.releaseLock()
  43. return chunks.join("") + decoder.decode()
  44. }
  45. async function waitForExit(pid: number, attempts = 100): Promise<boolean> {
  46. if (!running(pid)) return true
  47. if (attempts === 0) return false
  48. await Bun.sleep(50)
  49. return waitForExit(pid, attempts - 1)
  50. }
  51. function running(pid: number) {
  52. if (!Number.isSafeInteger(pid) || pid <= 0) return false
  53. try {
  54. process.kill(pid, 0)
  55. return true
  56. } catch {
  57. return false
  58. }
  59. }