mirror of
https://github.com/Lakr233/vphone-cli.git
synced 2026-09-02 02:34:29 +00:00
The library the consolidated CLI is built on: - bundle/library model (VPhoneBundle, VPhoneLibrary, VPhoneVirtualMachineManifest), bundle ops + reporting, restore helpers (VPhoneRestoreOps) - host process primitives: VPhoneProcessRunner, VPhoneManagedProcess (spawn + stdout pattern-match + SIGKILL-escalating terminate), VPhoneLaunchLayout, VPhoneBootPatterns - VPhoneResources: bundled-.app vs dev asset resolution, and Python resolution that provisions a per-user venv (~/.vphone/venv) on demand so the app is portable — never depends on the repo's .venv - VPhoneVerbosity (quiet/info/debug/trace) and VPhoneVMPicker Manifest moves out of the executable target into VPhoneCore. Full unit-test suite under tests/VPhoneCoreTests. Co-Authored-By: Claude Opus 4.8 <[email protected]> Claude-Session: https://claude.ai/code/session_01Y4VDqWf5pVakcFLqB23CKe
56 lines
2.1 KiB
Swift
56 lines
2.1 KiB
Swift
@testable import VPhoneCore
|
|
import Foundation
|
|
import Testing
|
|
|
|
struct ManagedProcessTests {
|
|
@Test func waitsForOutputMatch() throws {
|
|
// Emit a line after a short delay, then sleep; the matcher should catch it.
|
|
let p = VPhoneManagedProcess(
|
|
URL(fileURLWithPath: "/bin/sh"),
|
|
["-c", "sleep 0.2; echo READY-NOW; sleep 2"], echo: false)
|
|
try p.start()
|
|
let r = p.waitForOutput(matching: "READY-NOW", timeout: 5)
|
|
#expect(r == .matched)
|
|
p.terminate()
|
|
}
|
|
|
|
@Test func reportsExitBeforeMatch() throws {
|
|
let p = VPhoneManagedProcess(URL(fileURLWithPath: "/bin/sh"),
|
|
["-c", "echo nope; exit 3"], echo: false)
|
|
try p.start()
|
|
let r = p.waitForOutput(matching: "WILL-NOT-APPEAR", timeout: 5)
|
|
#expect(r == .exited(3))
|
|
}
|
|
|
|
@Test func timesOutWhenNoMatch() throws {
|
|
let p = VPhoneManagedProcess(URL(fileURLWithPath: "/bin/sh"),
|
|
["-c", "sleep 3"], echo: false)
|
|
try p.start()
|
|
let r = p.waitForOutput(matching: "NEVER", timeout: 0.5)
|
|
#expect(r == .timedOut)
|
|
p.terminate()
|
|
}
|
|
|
|
@Test func sendWritesToStdinAndDrivesTheChild() throws {
|
|
// Child echoes a marker only after it reads a line from stdin.
|
|
let p = VPhoneManagedProcess(URL(fileURLWithPath: "/bin/sh"),
|
|
["-c", "read line; echo GOT:$line"], echo: false)
|
|
try p.start()
|
|
p.send("hello")
|
|
let r = p.waitForOutput(matching: "GOT:hello", timeout: 5)
|
|
#expect(r == .matched)
|
|
#expect(p.waitUntilExit() == 0)
|
|
}
|
|
|
|
@Test func terminateForceKillsAResistantChild() throws {
|
|
// Child IGNORES SIGINT and SIGTERM, so terminate() must escalate to SIGKILL
|
|
// and waitUntilExit() must return (not hang).
|
|
let p = VPhoneManagedProcess(URL(fileURLWithPath: "/bin/sh"),
|
|
["-c", "trap '' INT TERM; while :; do sleep 1; done"], echo: false)
|
|
try p.start()
|
|
_ = p.waitForOutput(matching: "NEVER", timeout: 0.4) // let it install the traps
|
|
p.terminate()
|
|
#expect(p.waitUntilExit() != 0) // SIGKILL → nonzero/signal status, and it RETURNED
|
|
}
|
|
}
|