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
72 lines
2.7 KiB
Swift
72 lines
2.7 KiB
Swift
@testable import VPhoneCore
|
|
import Testing
|
|
|
|
struct VMPickerTests {
|
|
/// A scripted stdin: pops one line per `read()` call.
|
|
private func reader(_ lines: [String?]) -> () -> String? {
|
|
var i = 0
|
|
return { defer { i += 1 }; return i < lines.count ? lines[i] : nil }
|
|
}
|
|
|
|
@Test func returnsProvidedNameUnchanged() throws {
|
|
let out = try VPhoneVMPicker.resolve(
|
|
provided: "myvm", names: ["a", "b"], libraryRoot: "/r", isInteractive: true,
|
|
read: { nil }, write: { _ in })
|
|
#expect(out == "myvm") // no prompt when a name is supplied
|
|
}
|
|
|
|
@Test func nonInteractiveWithoutNameThrows() {
|
|
#expect(throws: VPhoneVMPickerError.notInteractive) {
|
|
_ = try VPhoneVMPicker.resolve(
|
|
provided: nil, names: ["a"], libraryRoot: "/r", isInteractive: false,
|
|
read: { nil }, write: { _ in })
|
|
}
|
|
}
|
|
|
|
@Test func emptyLibraryThrows() {
|
|
#expect(throws: VPhoneVMPickerError.emptyLibrary(root: "/r")) {
|
|
_ = try VPhoneVMPicker.resolve(
|
|
provided: nil, names: [], libraryRoot: "/r", isInteractive: true,
|
|
read: { nil }, write: { _ in })
|
|
}
|
|
}
|
|
|
|
@Test func selectsByIndex() throws {
|
|
let out = try VPhoneVMPicker.resolve(
|
|
provided: nil, names: ["alpha", "beta", "gamma"], libraryRoot: "/r",
|
|
isInteractive: true, read: reader(["2"]), write: { _ in })
|
|
#expect(out == "beta")
|
|
}
|
|
|
|
@Test func selectsByExactName() throws {
|
|
let out = try VPhoneVMPicker.resolve(
|
|
provided: nil, names: ["alpha", "beta"], libraryRoot: "/r",
|
|
isInteractive: true, read: reader(["alpha"]), write: { _ in })
|
|
#expect(out == "alpha")
|
|
}
|
|
|
|
@Test func retriesThenSucceeds() throws {
|
|
// blank, out-of-range, bad-name, then a good index.
|
|
let out = try VPhoneVMPicker.resolve(
|
|
provided: nil, names: ["alpha", "beta"], libraryRoot: "/r",
|
|
isInteractive: true, read: reader(["", "9", "nope", "1"]), write: { _ in })
|
|
#expect(out == "alpha")
|
|
}
|
|
|
|
@Test func eofAborts() {
|
|
#expect(throws: VPhoneVMPickerError.aborted) {
|
|
_ = try VPhoneVMPicker.resolve(
|
|
provided: nil, names: ["alpha"], libraryRoot: "/r", isInteractive: true,
|
|
read: { nil }, write: { _ in })
|
|
}
|
|
}
|
|
|
|
@Test func tooManyInvalidThrows() {
|
|
#expect(throws: VPhoneVMPickerError.invalidSelection) {
|
|
_ = try VPhoneVMPicker.resolve(
|
|
provided: nil, names: ["alpha"], libraryRoot: "/r", isInteractive: true,
|
|
maxRetries: 2, read: reader(["x", "y", "z"]), write: { _ in })
|
|
}
|
|
}
|
|
}
|