pty-output-isolation.test.ts 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. import { describe, expect, test } from "bun:test"
  2. import { Instance } from "../../src/project/instance"
  3. import { Pty } from "../../src/pty"
  4. import { tmpdir } from "../fixture/fixture"
  5. describe("pty", () => {
  6. test("does not leak output when websocket objects are reused", async () => {
  7. await using dir = await tmpdir({ git: true })
  8. await Instance.provide({
  9. directory: dir.path,
  10. fn: async () => {
  11. const a = await Pty.create({ command: "cat", title: "a" })
  12. const b = await Pty.create({ command: "cat", title: "b" })
  13. try {
  14. const outA: string[] = []
  15. const outB: string[] = []
  16. const ws = {
  17. readyState: 1,
  18. data: { events: { connection: "a" } },
  19. send: (data: unknown) => {
  20. outA.push(typeof data === "string" ? data : Buffer.from(data as Uint8Array).toString("utf8"))
  21. },
  22. close: () => {
  23. // no-op (simulate abrupt drop)
  24. },
  25. }
  26. // Connect "a" first with ws.
  27. Pty.connect(a.id, ws as any)
  28. // Now "reuse" the same ws object for another connection.
  29. ws.data = { events: { connection: "b" } }
  30. ws.send = (data: unknown) => {
  31. outB.push(typeof data === "string" ? data : Buffer.from(data as Uint8Array).toString("utf8"))
  32. }
  33. Pty.connect(b.id, ws as any)
  34. // Clear connect metadata writes.
  35. outA.length = 0
  36. outB.length = 0
  37. // Output from a must never show up in b.
  38. Pty.write(a.id, "AAA\n")
  39. await Bun.sleep(100)
  40. expect(outB.join("")).not.toContain("AAA")
  41. } finally {
  42. await Pty.remove(a.id)
  43. await Pty.remove(b.id)
  44. }
  45. },
  46. })
  47. })
  48. test("does not leak output when Bun recycles websocket objects before re-connect", async () => {
  49. await using dir = await tmpdir({ git: true })
  50. await Instance.provide({
  51. directory: dir.path,
  52. fn: async () => {
  53. const a = await Pty.create({ command: "cat", title: "a" })
  54. try {
  55. const outA: string[] = []
  56. const outB: string[] = []
  57. const ws = {
  58. readyState: 1,
  59. data: { events: { connection: "a" } },
  60. send: (data: unknown) => {
  61. outA.push(typeof data === "string" ? data : Buffer.from(data as Uint8Array).toString("utf8"))
  62. },
  63. close: () => {
  64. // no-op (simulate abrupt drop)
  65. },
  66. }
  67. // Connect "a" first.
  68. Pty.connect(a.id, ws as any)
  69. outA.length = 0
  70. // Simulate Bun reusing the same websocket object for another
  71. // connection before the next onOpen calls Pty.connect.
  72. ws.data = { events: { connection: "b" } }
  73. ws.send = (data: unknown) => {
  74. outB.push(typeof data === "string" ? data : Buffer.from(data as Uint8Array).toString("utf8"))
  75. }
  76. Pty.write(a.id, "AAA\n")
  77. await Bun.sleep(100)
  78. expect(outB.join("")).not.toContain("AAA")
  79. } finally {
  80. await Pty.remove(a.id)
  81. }
  82. },
  83. })
  84. })
  85. test("treats in-place socket data mutation as the same connection", async () => {
  86. await using dir = await tmpdir({ git: true })
  87. await Instance.provide({
  88. directory: dir.path,
  89. fn: async () => {
  90. const a = await Pty.create({ command: "cat", title: "a" })
  91. try {
  92. const out: string[] = []
  93. const ctx = { connId: 1 }
  94. const ws = {
  95. readyState: 1,
  96. data: ctx,
  97. send: (data: unknown) => {
  98. out.push(typeof data === "string" ? data : Buffer.from(data as Uint8Array).toString("utf8"))
  99. },
  100. close: () => {
  101. // no-op
  102. },
  103. }
  104. Pty.connect(a.id, ws as any)
  105. out.length = 0
  106. // Mutating fields on ws.data should not look like a new
  107. // connection lifecycle when the object identity stays stable.
  108. ctx.connId = 2
  109. Pty.write(a.id, "AAA\n")
  110. await Bun.sleep(100)
  111. expect(out.join("")).toContain("AAA")
  112. } finally {
  113. await Pty.remove(a.id)
  114. }
  115. },
  116. })
  117. })
  118. })