117 lines
4.6 KiB
Swift
117 lines
4.6 KiB
Swift
import Foundation
|
|
import Testing
|
|
|
|
@testable import RunnerCore
|
|
|
|
/// Tests for ``XcodeInstall`` — the decidable parts of installing Xcode.
|
|
///
|
|
/// The bug these exist for: the installer assumed the archive expanded to
|
|
/// `Xcode.app`. A beta expands to `Xcode-beta.app`, so a 12 GB upload and a
|
|
/// half-hour expansion both succeeded and then the final `mv` failed with
|
|
/// "No such file or directory". Nothing about that is worth discovering from a
|
|
/// forty-minute live run twice.
|
|
@Suite("XcodeInstall")
|
|
struct XcodeInstallTests {
|
|
|
|
// MARK: - Discovering the expanded app
|
|
|
|
@Test("a release archive's Xcode.app is found")
|
|
func findsReleaseApp() throws {
|
|
let path = try XcodeInstall.expandedAppPath(
|
|
fromListing: "/tmp/xcode-expand/Xcode.app\n", staging: "/tmp/xcode-expand")
|
|
#expect(path == "/tmp/xcode-expand/Xcode.app")
|
|
}
|
|
|
|
/// The reported failure, in one line.
|
|
@Test("a beta archive's Xcode-beta.app is found")
|
|
func findsBetaApp() throws {
|
|
let path = try XcodeInstall.expandedAppPath(
|
|
fromListing: "/tmp/xcode-expand/Xcode-beta.app", staging: "/tmp/xcode-expand")
|
|
#expect(path == "/tmp/xcode-expand/Xcode-beta.app")
|
|
}
|
|
|
|
@Test("a version-qualified name is found")
|
|
func findsVersionQualifiedApp() throws {
|
|
let path = try XcodeInstall.expandedAppPath(
|
|
fromListing: " /tmp/xcode-expand/Xcode_16.2.app \n\n", staging: "/tmp/xcode-expand")
|
|
#expect(path == "/tmp/xcode-expand/Xcode_16.2.app")
|
|
}
|
|
|
|
/// `/bin/sh` echoes an unmatched glob back verbatim, so "no match" arrives
|
|
/// as a plausible-looking path rather than as empty output.
|
|
@Test("an unmatched glob reads as no match, not as a path")
|
|
func unmatchedGlobIsNotAPath() {
|
|
#expect(throws: CoreError.self) {
|
|
try XcodeInstall.expandedAppPath(
|
|
fromListing: "/tmp/xcode-expand/*.app\n", staging: "/tmp/xcode-expand")
|
|
}
|
|
}
|
|
|
|
@Test("empty output is an error naming the staging directory")
|
|
func emptyListingThrows() {
|
|
do {
|
|
_ = try XcodeInstall.expandedAppPath(fromListing: "\n \n", staging: "/tmp/xcode-expand")
|
|
Issue.record("expected a failure")
|
|
} catch {
|
|
#expect("\(error)".contains("/tmp/xcode-expand"))
|
|
}
|
|
}
|
|
|
|
/// Guessing between two candidates would install something the operator did
|
|
/// not ask for, so this stops instead.
|
|
@Test("several apps is an error listing them")
|
|
func ambiguousListingThrows() {
|
|
do {
|
|
_ = try XcodeInstall.expandedAppPath(
|
|
fromListing: "/tmp/x/Xcode.app\n/tmp/x/Xcode-beta.app\n", staging: "/tmp/x")
|
|
Issue.record("expected a failure")
|
|
} catch {
|
|
let text = "\(error)"
|
|
#expect(text.contains("Xcode.app"))
|
|
#expect(text.contains("Xcode-beta.app"))
|
|
}
|
|
}
|
|
|
|
// MARK: - Disk arithmetic
|
|
|
|
@Test("the requirement covers the archive and its expansion")
|
|
func requiredFreeSpaceCoversBoth() {
|
|
let xip = 12_000_000_000
|
|
#expect(XcodeInstall.expansionEstimateBytes(xipBytes: xip) == 42_000_000_000)
|
|
#expect(XcodeInstall.requiredFreeBytes(xipBytes: xip) == 54_000_000_000)
|
|
}
|
|
|
|
@Test("df -Pk output yields available bytes")
|
|
func parsesDF() {
|
|
let output = """
|
|
Filesystem 1024-blocks Used Available Capacity Mounted on
|
|
/dev/disk3s5 488245288 120000000 62914560 66% /
|
|
"""
|
|
#expect(XcodeInstall.availableBytes(dfOutput: output) == 62_914_560 * 1024)
|
|
}
|
|
|
|
/// Unparseable output means "could not check", not "no space" — the caller
|
|
/// must not refuse to install because `df` printed something unexpected.
|
|
@Test("unparseable df output yields nil rather than zero")
|
|
func unparseableDFIsNil() {
|
|
#expect(XcodeInstall.availableBytes(dfOutput: "df: /nope: No such file or directory") == nil)
|
|
#expect(XcodeInstall.availableBytes(dfOutput: "") == nil)
|
|
}
|
|
|
|
@Test("the shortfall message names every number and a remedy")
|
|
func messageNamesTheNumbers() {
|
|
let text = XcodeInstall.insufficientDiskMessage(
|
|
xipBytes: 12_000_000_000, availableBytes: 20_000_000_000)
|
|
#expect(text.contains("20.0 GB")) // available
|
|
#expect(text.contains("54.0 GB")) // needed
|
|
#expect(text.contains("12.0 GB")) // the archive
|
|
#expect(text.contains("--disk-gb"))
|
|
}
|
|
|
|
@Test("byte counts render as decimal gigabytes")
|
|
func formatsGigabytes() {
|
|
#expect(XcodeInstall.formatGB(12_400_000_000) == "12.4 GB")
|
|
#expect(XcodeInstall.formatGB(0) == "0.0 GB")
|
|
}
|
|
}
|