| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 |
- import { describe, expect, test } from "bun:test"
- import { Instance } from "../../src/project/instance"
- import { Pty } from "../../src/pty"
- import { tmpdir } from "../fixture/fixture"
- describe("pty", () => {
- test("does not leak output when websocket objects are reused", async () => {
- await using dir = await tmpdir({ git: true })
- await Instance.provide({
- directory: dir.path,
- fn: async () => {
- const a = await Pty.create({ command: "cat", title: "a" })
- const b = await Pty.create({ command: "cat", title: "b" })
- try {
- const outA: string[] = []
- const outB: string[] = []
- const ws = {
- readyState: 1,
- data: { events: { connection: "a" } },
- send: (data: unknown) => {
- outA.push(typeof data === "string" ? data : Buffer.from(data as Uint8Array).toString("utf8"))
- },
- close: () => {
- // no-op (simulate abrupt drop)
- },
- }
- // Connect "a" first with ws.
- Pty.connect(a.id, ws as any)
- // Now "reuse" the same ws object for another connection.
- ws.data = { events: { connection: "b" } }
- ws.send = (data: unknown) => {
- outB.push(typeof data === "string" ? data : Buffer.from(data as Uint8Array).toString("utf8"))
- }
- Pty.connect(b.id, ws as any)
- // Clear connect metadata writes.
- outA.length = 0
- outB.length = 0
- // Output from a must never show up in b.
- Pty.write(a.id, "AAA\n")
- await Bun.sleep(100)
- expect(outB.join("")).not.toContain("AAA")
- } finally {
- await Pty.remove(a.id)
- await Pty.remove(b.id)
- }
- },
- })
- })
- test("does not leak output when Bun recycles websocket objects before re-connect", async () => {
- await using dir = await tmpdir({ git: true })
- await Instance.provide({
- directory: dir.path,
- fn: async () => {
- const a = await Pty.create({ command: "cat", title: "a" })
- try {
- const outA: string[] = []
- const outB: string[] = []
- const ws = {
- readyState: 1,
- data: { events: { connection: "a" } },
- send: (data: unknown) => {
- outA.push(typeof data === "string" ? data : Buffer.from(data as Uint8Array).toString("utf8"))
- },
- close: () => {
- // no-op (simulate abrupt drop)
- },
- }
- // Connect "a" first.
- Pty.connect(a.id, ws as any)
- outA.length = 0
- // Simulate Bun reusing the same websocket object for another
- // connection before the next onOpen calls Pty.connect.
- ws.data = { events: { connection: "b" } }
- ws.send = (data: unknown) => {
- outB.push(typeof data === "string" ? data : Buffer.from(data as Uint8Array).toString("utf8"))
- }
- Pty.write(a.id, "AAA\n")
- await Bun.sleep(100)
- expect(outB.join("")).not.toContain("AAA")
- } finally {
- await Pty.remove(a.id)
- }
- },
- })
- })
- test("treats in-place socket data mutation as the same connection", async () => {
- await using dir = await tmpdir({ git: true })
- await Instance.provide({
- directory: dir.path,
- fn: async () => {
- const a = await Pty.create({ command: "cat", title: "a" })
- try {
- const out: string[] = []
- const ctx = { connId: 1 }
- const ws = {
- readyState: 1,
- data: ctx,
- send: (data: unknown) => {
- out.push(typeof data === "string" ? data : Buffer.from(data as Uint8Array).toString("utf8"))
- },
- close: () => {
- // no-op
- },
- }
- Pty.connect(a.id, ws as any)
- out.length = 0
- // Mutating fields on ws.data should not look like a new
- // connection lifecycle when the object identity stays stable.
- ctx.connId = 2
- Pty.write(a.id, "AAA\n")
- await Bun.sleep(100)
- expect(out.join("")).toContain("AAA")
- } finally {
- await Pty.remove(a.id)
- }
- },
- })
- })
- })
|