项目文件夹

文件
wehub-resource-sync 91e75e620b
CI: cua-driver distro-compat matrix / debian:12 (glibc 2.36) (push) Has been cancelled
CI: SPDX Headers / Check SPDX headers (warn-only) (push) Has been cancelled
CD: Docs MCP Server / build (linux/amd64) (push) Has been cancelled
CD: Docs MCP Server / build (linux/arm64) (push) Has been cancelled
CD: Docs MCP Server / merge (push) Has been cancelled
CI: cua-driver distro-compat matrix / Resolve release version (push) Has been cancelled
CI: cua-driver distro-compat matrix / fedora:41 (glibc 2.40) (push) Has been cancelled
CI: cua-driver distro-compat matrix / rockylinux:9 (glibc 2.34) (push) Has been cancelled
CI: cua-driver distro-compat matrix / ubuntu:22.04 (glibc 2.35) (push) Has been cancelled
CI: cua-driver distro-compat matrix / ubuntu:24.04 (glibc 2.39) (push) Has been cancelled
CI: cua-driver distro-compat matrix / Distro compat summary (push) Has been cancelled
CI: Rust Linux unit / Rust Linux unit and compile (push) Has been cancelled
CI: Rust Windows unit / Rust Windows unit and compile (push) Has been cancelled
CI: Nix Linux Rust source / Nix / compositor build (push) Has been cancelled
CI: Nix Linux Rust source / Nix / driver package (push) Has been cancelled
CI: Nix Linux Rust source / Nix / Rust unit tests (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:03:19 +08:00

167 行
6.8 KiB
Swift

import Foundation
import Testing
@testable import lume
@MainActor private final class MockRecoveryVNCDriver: RecoveryVNCDriving {
var events: [String] = []
var receivedSensitiveInput = false
var usesDirectAccountPrompt = false
func waitForListening(deadlineSeconds: Double, heartbeat: () throws -> Void) async throws {
try heartbeat()
events.append("listening")
}
func waitForFramebuffer(minSize: Int, deadlineSeconds: Double, label: String, heartbeat: () throws -> Void)
async throws
{
try heartbeat()
events.append("framebuffer:\(label)")
}
func waitForText(
containing expectedPhrases: [String], rejecting failurePhrases: [String], deadlineSeconds: Double,
label: String, heartbeat: () throws -> Void
) async throws -> String {
try heartbeat()
let text: String
if label == "csrutil prompt" {
text = usesDirectAccountPrompt ? expectedPhrases.last ?? "" : "[y/n]"
} else {
text = expectedPhrases.first ?? ""
}
events.append("text:\(text)")
return text
}
func type(_ text: String) throws { events.append("type:\(text)") }
func typeSensitive(_ text: String) throws {
receivedSensitiveInput = !text.isEmpty
events.append("sensitive-input")
}
func key(_ name: String) throws { events.append("key:\(name)") }
func click(x: Int, y: Int) throws { events.append("click:\(x),\(y)") }
func snapshot(_ tag: String) throws { events.append("snapshot:\(tag)") }
}
@MainActor @Test("sip parses stdin credentials and the short confirmation flag") func sipCommandParsing() throws {
let root = try Lume.parseAsRoot(["sip", "off", "test-vm", "-y", "--admin-password-stdin"])
let command = try #require(root as? Sip)
#expect(command.state == .off)
#expect(command.name == "test-vm")
#expect(command.yes)
#expect(command.adminPasswordStdin)
}
@Test("sip validates VM state and target status") func sipValidation() throws {
let stoppedMac = vmDetails(os: "macOS", status: "stopped")
try Sip.validateInitialState(stoppedMac, expectedName: "test-vm")
#expect(Sip.statusMatches("System Integrity Protection status: disabled.", expected: .off))
#expect(Sip.statusMatches("System Integrity Protection status: enabled.", expected: .on))
#expect(!Sip.statusMatches("System Integrity Protection status: enabled.", expected: .off))
let customized = """
System Integrity Protection status: enabled (Custom Configuration).
Filesystem Protections: disabled
Debugging Restrictions: enabled
"""
#expect(!Sip.statusMatches(customized, expected: .off))
#expect(!Sip.statusMatches(customized, expected: .on))
#expect(throws: SipError.self) {
try Sip.validateInitialState(vmDetails(os: "linux", status: "stopped"), expectedName: "test-vm")
}
#expect(throws: SipError.self) {
try Sip.validateInitialState(vmDetails(os: "macOS", status: "running"), expectedName: "test-vm")
}
#expect(throws: SipError.self) { try Sip.requireVNCExecutable(nil) }
#expect(throws: SipError.self) { try Sip.validateVNCPasswordCharacters("Uppercase") }
let vncPasswords = Swift.Set<String>((0..<8).map { _ in Sip.makeVNCPassword() })
#expect(vncPasswords.allSatisfy { $0.utf8.count == 8 })
#expect(vncPasswords.count > 1)
}
@MainActor @Test("recovery automation enters the requested csrutil command and verifies its result")
func sipRecoveryAutomation() async throws {
for state in [Sip.State.off, .on] {
let driver = MockRecoveryVNCDriver()
driver.usesDirectAccountPrompt = state == .on
var heartbeats = 0
let automator = SipRecoveryAutomator(sleeper: { _ in })
try await automator.perform(
driver: driver, state: state, adminUser: "lume", adminPassword: "secret", heartbeat: { heartbeats += 1 },
timeout: { limit, _ in limit })
let verb = state == .off ? "disable" : "enable"
let result = state == .off ? "off" : "on"
#expect(driver.events.contains("type:csrutil \(verb)"))
#expect(driver.events.contains("text:enter password for user lume:"))
#expect(driver.events.contains("key:y") == (state == .off))
#expect(driver.events.contains("text:system integrity protection is \(result)."))
#expect(driver.events.contains("sensitive-input"))
#expect(driver.receivedSensitiveInput)
#expect(driver.events.suffix(3).contains("type:sync; shutdown -h now"))
#expect(heartbeats > 0)
}
}
@MainActor @Test("sensitive VNC input is sent on stdin and never in argv") func vncSensitiveInputUsesStdin() throws {
let directory = FileManager.default.temporaryDirectory.appendingPathComponent(
"lume-sip-vnc-test-\(UUID().uuidString)")
try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
defer { try? FileManager.default.removeItem(at: directory) }
let executable = directory.appendingPathComponent("vncdo")
let argumentsLog = directory.appendingPathComponent("arguments")
let stdinLog = directory.appendingPathComponent("stdin")
let script = """
#!/bin/sh
printf '%s\\n' "$@" > '\(argumentsLog.path)'
head -c 262144 /dev/zero
cat > '\(stdinLog.path)'
"""
try script.write(to: executable, atomically: true, encoding: .utf8)
try FileManager.default.setAttributes([.posixPermissions: 0o755], ofItemAtPath: executable.path)
let secret = "sentinel-secret"
let driver = VNCDriver(
vncdo: executable.path, host: "127.0.0.1", port: 5999, password: "transient-vnc-password", screenshotDir: nil)
try driver.typeSensitive(secret)
let arguments = try String(contentsOf: argumentsLog, encoding: .utf8)
let standardInput = try String(contentsOf: stdinLog, encoding: .utf8)
#expect(!arguments.contains(secret))
#expect(arguments.contains("typefile\n-"))
#expect(standardInput == secret)
}
@MainActor @Test("lume child output cannot fill an undrained pipe") func sipChildProcessHandlesLargeOutput()
async throws
{
let child = try SipChildProcess(
executable: "/bin/sh", arguments: ["-c", "yes output | head -c 262144"], label: "large-output-test")
defer { child.cleanup() }
try child.start()
for _ in 0..<100 where child.isRunning { try await Task.sleep(for: .milliseconds(20)) }
if child.isRunning { await child.terminateAndWait() }
#expect(!child.isRunning)
#expect(child.terminationStatus == 0)
#expect(!child.outputTail(redacting: []).isEmpty)
}
private func vmDetails(os: String, status: String) -> VMDetails {
VMDetails(
name: "test-vm", os: os, cpuCount: 4, memorySize: 8 * 1024 * 1024 * 1024,
diskSize: DiskSize(allocated: 1, total: 2), display: "1024x768", status: status, vncUrl: nil, ipAddress: nil,
locationName: "home")
}