Files
vphone-cli/tests/VPhoneCoreTests/ProcessRunnerTests.swift
T
zqxwceandClaude Opus 4.8 828be6dbc6 feat: add VPhoneCore library — VM bundle model + host primitives
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
2026-07-27 19:46:27 +03:00

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)
}
}