初始提交: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>
This commit is contained in:
158
vendor/libghostty-spm/Tests/GhosttyKitTest/InMemoryTerminalSessionOutputQueueTests.swift
vendored
Normal file
158
vendor/libghostty-spm/Tests/GhosttyKitTest/InMemoryTerminalSessionOutputQueueTests.swift
vendored
Normal file
@@ -0,0 +1,158 @@
|
||||
@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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user