Files
terminalX/vendor/libghostty-spm/Tests/GhosttyKitTest/InMemoryTerminalSessionOutputQueueTests.swift
kid aa92d0e676 初始提交:terminalX 可运行态(M0/M1/M1.5 已真机验证)
- M0: libghostty SSH 终端(渲染/输入/连接)+ 白屏修复(OutputGate) + 会话状态机/自动重连
- M1: tsnet 用户态组网 + SSH-over-tsnet(fd 桥),shell 级真机验证;R5(Go+gvisor+C+++Swift 同进程) retire
- M1.5: tmux -CC 原生 tab(MVP)
- 结构: packages/(TXCore·TXTransport), apps/TerminalX, vendor/(libghostty-spm/libssh2/mbedtls/tsnet-bridge), artifacts/
- 文档: CLAUDE.md + docs/HANDOFF.md(新会话入口)
- 环境: 认证代理→依赖 vendor 本地化;Go 在 ~/.local/go;仅模拟器/未签名
- 待续: M2 mosh, tmux 多 pane, M4 安全(host key/SE)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 10:20:46 +08:00

159 lines
5.0 KiB
Swift

@testable import GhosttyTerminal
import Darwin
import Foundation
import GhosttyKit
import Testing
struct InMemoryTerminalSessionOutputQueueTests {
@Test
func `receive returns before surface write completes`() {
let writeStarted = DispatchSemaphore(value: 0)
let allowWriteToFinish = DispatchSemaphore(value: 0)
let session = makeSession { _, _ in
writeStarted.signal()
allowWriteToFinish.wait()
}
session.setSurface(testSurface(1))
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
allowWriteToFinish.signal()
}
let start = ProcessInfo.processInfo.systemUptime
session.receive(Data("hello".utf8))
let elapsed = ProcessInfo.processInfo.systemUptime - start
#expect(elapsed < 0.2)
#expect(writeStarted.wait(timeout: .now() + 1) == .success)
allowWriteToFinish.signal()
session.waitForPendingOutput()
}
@Test
func `writes and process exit preserve enqueue order`() {
let events = LockedValues<String>()
let session = InMemoryTerminalSession(
write: { _ in },
resize: { _ in },
surfaceWrite: { _, data in
events.append(String(decoding: data, as: UTF8.self))
},
processExit: { _, exitCode, runtimeMilliseconds in
events.append("exit:\(exitCode):\(runtimeMilliseconds)")
}
)
session.setSurface(testSurface(2))
session.receive("first")
session.receive("second")
session.finish(exitCode: 7, runtimeMilliseconds: 42)
session.waitForPendingOutput()
#expect(events.values == ["first", "second", "exit:7:42"])
}
@Test
func `surface teardown waits for active write and drops queued stale writes`() {
let firstWriteStarted = DispatchSemaphore(value: 0)
let allowFirstWriteToFinish = DispatchSemaphore(value: 0)
let clearFinished = DispatchSemaphore(value: 0)
let writes = LockedValues<String>()
let surface = SendableSurface(testSurface(3))
let session = makeSession { _, data in
let value = String(decoding: data, as: UTF8.self)
writes.append(value)
if value == "first" {
firstWriteStarted.signal()
allowFirstWriteToFinish.wait()
}
}
session.setSurface(surface.rawValue)
session.receive("first")
session.receive("stale")
#expect(firstWriteStarted.wait(timeout: .now() + 1) == .success)
DispatchQueue.global().async {
session.clearSurface(ifMatches: surface.rawValue)
clearFinished.signal()
}
let clearDeadline = ProcessInfo.processInfo.systemUptime + 1
while session.currentSurface != nil,
ProcessInfo.processInfo.systemUptime < clearDeadline
{
sched_yield()
}
#expect(session.currentSurface == nil)
allowFirstWriteToFinish.signal()
#expect(clearFinished.wait(timeout: .now() + 1) == .success)
session.waitForPendingOutput()
#expect(writes.values == ["first"])
}
@Test
func `blocked session does not block another session`() {
let firstWriteStarted = DispatchSemaphore(value: 0)
let allowFirstWriteToFinish = DispatchSemaphore(value: 0)
let secondWriteFinished = DispatchSemaphore(value: 0)
let firstSession = makeSession { _, _ in
firstWriteStarted.signal()
allowFirstWriteToFinish.wait()
}
let secondSession = makeSession { _, _ in
secondWriteFinished.signal()
}
firstSession.setSurface(testSurface(4))
secondSession.setSurface(testSurface(5))
firstSession.receive("blocked")
#expect(firstWriteStarted.wait(timeout: .now() + 1) == .success)
secondSession.receive("independent")
#expect(secondWriteFinished.wait(timeout: .now() + 1) == .success)
allowFirstWriteToFinish.signal()
firstSession.waitForPendingOutput()
secondSession.waitForPendingOutput()
}
}
private func makeSession(
surfaceWrite: @escaping InMemoryTerminalSurfaceAccess.Write
) -> InMemoryTerminalSession {
InMemoryTerminalSession(
write: { _ in },
resize: { _ in },
surfaceWrite: surfaceWrite
)
}
private func testSurface(_ address: Int) -> ghostty_surface_t {
UnsafeMutableRawPointer(bitPattern: address)!
}
private struct SendableSurface: @unchecked Sendable {
let rawValue: ghostty_surface_t
init(_ rawValue: ghostty_surface_t) {
self.rawValue = rawValue
}
}
private final class LockedValues<Value: Sendable>: @unchecked Sendable {
private let lock = NSLock()
private var storage: [Value] = []
var values: [Value] {
lock.lock()
defer { lock.unlock() }
return storage
}
func append(_ value: Value) {
lock.lock()
storage.append(value)
lock.unlock()
}
}