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
70 lines
3.0 KiB
Swift
70 lines
3.0 KiB
Swift
@testable import VPhoneCore
|
|
import Foundation
|
|
import Testing
|
|
|
|
struct LibraryTests {
|
|
private func makeRoot() throws -> URL {
|
|
let dir = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString)
|
|
try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
|
|
return dir
|
|
}
|
|
|
|
private func writeBundle(_ name: String, in root: URL) throws {
|
|
let dir = root.appendingPathComponent(name)
|
|
try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
|
|
let manifest = VPhoneVirtualMachineManifest(
|
|
cpuCount: 8, memorySize: 8 * 1024 * 1024 * 1024,
|
|
romImages: .init(avpBooter: "AVPBooter.vresearch1.bin",
|
|
avpSEPBooter: "AVPSEPBooter.vresearch1.bin"))
|
|
try manifest.write(to: dir.appendingPathComponent("config.plist"))
|
|
}
|
|
|
|
@Test func scansOnlyDirsWithManifest() throws {
|
|
let root = try makeRoot()
|
|
defer { try? FileManager.default.removeItem(at: root) }
|
|
try writeBundle("alpha", in: root)
|
|
try writeBundle("beta", in: root)
|
|
// A stray dir without config.plist must be ignored.
|
|
try FileManager.default.createDirectory(
|
|
at: root.appendingPathComponent("junk"), withIntermediateDirectories: true)
|
|
|
|
let names = try VPhoneLibrary(root: root).bundles().map(\.name)
|
|
#expect(names == ["alpha", "beta"])
|
|
}
|
|
|
|
@Test func bundleNamedThrowsWhenMissing() throws {
|
|
let root = try makeRoot()
|
|
defer { try? FileManager.default.removeItem(at: root) }
|
|
#expect(throws: VPhoneLibraryError.self) {
|
|
_ = try VPhoneLibrary(root: root).bundle(named: "nope")
|
|
}
|
|
}
|
|
|
|
@Test func defaultRootHonorsEnvOverride() {
|
|
setenv("VPHONE_LIBRARY_ROOT", "/tmp/vphone-test-root", 1)
|
|
defer { unsetenv("VPHONE_LIBRARY_ROOT") }
|
|
#expect(VPhoneLibrary.defaultRoot().path == "/tmp/vphone-test-root")
|
|
}
|
|
|
|
@Test func defaultRootIsShellSafe() {
|
|
// The default root feeds the shell/make firmware pipeline; a space in it
|
|
// (e.g. "Application Support") breaks unquoted expansion. Must stay space-free.
|
|
unsetenv("VPHONE_LIBRARY_ROOT")
|
|
#expect(!VPhoneLibrary.defaultRoot().path.contains(" "))
|
|
}
|
|
|
|
@Test func scanReportsCorruptBundlesInsteadOfDropping() throws {
|
|
let root = try makeRoot()
|
|
defer { try? FileManager.default.removeItem(at: root) }
|
|
try writeBundle("good", in: root)
|
|
// A directory WITH config.plist but corrupt contents must be reported, not silently dropped.
|
|
let bad = root.appendingPathComponent("bad")
|
|
try FileManager.default.createDirectory(at: bad, withIntermediateDirectories: true)
|
|
try Data("not a plist".utf8).write(to: bad.appendingPathComponent("config.plist"))
|
|
|
|
let result = try VPhoneLibrary(root: root).scan()
|
|
#expect(result.bundles.map(\.name) == ["good"])
|
|
#expect(result.skipped.map(\.name) == ["bad"])
|
|
}
|
|
}
|