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
55 lines
2.4 KiB
Swift
55 lines
2.4 KiB
Swift
@testable import VPhoneCore
|
|
import Foundation
|
|
import Testing
|
|
|
|
struct ProcessRunnerTests {
|
|
@Test func capturesStdoutAndZeroExit() throws {
|
|
let r = try VPhoneProcessRunner.runCapturing(
|
|
URL(fileURLWithPath: "/bin/echo"), ["hello", "world"])
|
|
#expect(r.exitCode == 0)
|
|
#expect(r.succeeded)
|
|
#expect(r.stdout.trimmingCharacters(in: .whitespacesAndNewlines) == "hello world")
|
|
}
|
|
|
|
@Test func capturesNonzeroExit() throws {
|
|
// `/usr/bin/false` exits 1 with no output.
|
|
let r = try VPhoneProcessRunner.runCapturing(
|
|
URL(fileURLWithPath: "/usr/bin/false"), [])
|
|
#expect(r.exitCode == 1)
|
|
#expect(!r.succeeded)
|
|
}
|
|
|
|
@Test func passesCwd() throws {
|
|
let r = try VPhoneProcessRunner.runCapturing(
|
|
URL(fileURLWithPath: "/bin/pwd"), [], cwd: URL(fileURLWithPath: "/tmp"))
|
|
// /tmp is a symlink to /private/tmp on macOS; accept either.
|
|
let out = r.stdout.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
#expect(out == "/tmp" || out == "/private/tmp")
|
|
}
|
|
|
|
@Test func drainsBothPipesConcurrentlyWithoutDeadlock() throws {
|
|
// Child writes 100 KB to stderr BEFORE any stdout; a sequential
|
|
// "read stdout fully first" drain would deadlock at the ~64 KB stderr
|
|
// pipe buffer. Concurrent draining must complete without hanging.
|
|
let r = try VPhoneProcessRunner.runCapturing(
|
|
URL(fileURLWithPath: "/bin/sh"),
|
|
["-c", "yes E | head -c 100000 1>&2; yes X | head -c 100000"])
|
|
#expect(r.exitCode == 0)
|
|
#expect(r.stdout.utf8.count == 100000)
|
|
#expect(r.stderr.utf8.count == 100000)
|
|
}
|
|
|
|
@Test func runStreamingReturnsExitCode() throws {
|
|
#expect(try VPhoneProcessRunner.runStreaming(URL(fileURLWithPath: "/usr/bin/true"), []) == 0)
|
|
#expect(try VPhoneProcessRunner.runStreaming(URL(fileURLWithPath: "/usr/bin/false"), []) == 1)
|
|
}
|
|
|
|
@Test func runStreamingEchoFalseStillReturnsExitCode() throws {
|
|
// Child writes to stdout but echo:false discards it; exit code still propagates.
|
|
#expect(try VPhoneProcessRunner.runStreaming(
|
|
URL(fileURLWithPath: "/bin/sh"), ["-c", "echo noise; exit 0"], echo: false) == 0)
|
|
#expect(try VPhoneProcessRunner.runStreaming(
|
|
URL(fileURLWithPath: "/bin/sh"), ["-c", "echo noise >&2; exit 7"], echo: false) == 7)
|
|
}
|
|
}
|