mirror of
https://github.com/aculix/knotchy.git
synced 2026-10-27 07:15:53 +00:00
243 lines
9.9 KiB
Swift
243 lines
9.9 KiB
Swift
import Foundation
|
|
import Testing
|
|
@testable import KnotchyCore
|
|
|
|
@MainActor
|
|
@Suite(.timeLimit(.minutes(1)))
|
|
struct SetupFlowTests {
|
|
let host = FakeHost()
|
|
let installer = FakeInstaller()
|
|
let driver = FakeSetupDriver()
|
|
|
|
func makeFlow() -> SetupFlow {
|
|
SetupFlow(host: host, installer: installer, driver: driver, retryDelay: .zero)
|
|
}
|
|
|
|
@Test func aReadyMacShowsEveryRowDone() async {
|
|
let flow = makeFlow()
|
|
await flow.refresh()
|
|
#expect(flow.mac == .done("macOS 27.0 · 212 GB free"))
|
|
#expect(flow.runtime == .done("container 1.4.1 installed"))
|
|
#expect(flow.vm == .done("Ready"))
|
|
#expect(flow.allRequiredDone)
|
|
#expect(!flow.canSetUp)
|
|
}
|
|
|
|
@Test func aMissingRuntimeOffersAnInstallWithoutCheckingTheVM() async {
|
|
driver.runtimeStatuses = [.missing]
|
|
let flow = makeFlow()
|
|
await flow.refresh()
|
|
#expect(flow.runtimeAction == .install)
|
|
#expect(flow.runtime == .pending("Apple's container 1.4.1 · 118 MB · installs with Apple's Installer"))
|
|
#expect(flow.vm == .pending("About 3 minutes · downloads about 225 MB"))
|
|
#expect(flow.canSetUp)
|
|
#expect(driver.calls == ["runtimeStatus"])
|
|
}
|
|
|
|
@Test func anOldRuntimeOffersAnUpdate() async {
|
|
driver.runtimeStatuses = [.outdated("1.3.0")]
|
|
let flow = makeFlow()
|
|
await flow.refresh()
|
|
#expect(flow.runtimeAction == .update)
|
|
#expect(flow.runtime == .pending("container 1.3.0 is too old · updates to 1.4.1 with Apple's Installer"))
|
|
}
|
|
|
|
@Test func aMacThatCantRunVirtualMachinesShowsOnlyThat() async {
|
|
host.current.unsupported = .noVirtualization
|
|
let flow = makeFlow()
|
|
await flow.refresh()
|
|
#expect(flow.unsupported == .noVirtualization)
|
|
#expect(driver.calls.isEmpty)
|
|
#expect(!flow.canSetUp)
|
|
}
|
|
|
|
@Test func lowDiskBlocksOnlyWhileSomethingNeedsSpace() async {
|
|
host.current.bytesFree = 2_500_000_000
|
|
driver.runtimeStatuses = [.missing]
|
|
let flow = makeFlow()
|
|
await flow.refresh()
|
|
#expect(flow.mac == .failed("Setting up needs about 3 GB free. 2.5 GB is free.", details: []))
|
|
#expect(!flow.canSetUp)
|
|
|
|
driver.runtimeStatuses = [.current("1.4.1")]
|
|
await flow.refresh()
|
|
#expect(flow.mac == .done("macOS 27.0 · 2.5 GB free"))
|
|
#expect(flow.allRequiredDone)
|
|
}
|
|
|
|
@Test func installingWithInstallerFinishesTheChain() async throws {
|
|
driver.runtimeStatuses = [.missing, .current("1.4.1")]
|
|
installer.session.hasQuit = false
|
|
let flow = makeFlow()
|
|
await flow.setUp()
|
|
#expect(driver.calls == ["runtimeStatus", "begin(nil)", "runtimeStatus", "finish(installed)"])
|
|
#expect(flow.mac == .done("macOS 27.0 · 212 GB free"))
|
|
#expect(flow.runtime == .done("container 1.4.1 installed"))
|
|
#expect(flow.vm == .done("Ready"))
|
|
#expect(flow.allRequiredDone)
|
|
|
|
// Installer still reads the package while its summary is open.
|
|
try await Task.sleep(for: .milliseconds(50))
|
|
#expect(installer.calls == ["download", "open"])
|
|
installer.session.hasQuit = true
|
|
await waitUntil("the package is discarded") { installer.calls.contains("discard") }
|
|
#expect(installer.calls == ["download", "open", "discard"])
|
|
}
|
|
|
|
@Test func anUpdatePassesTheOldVersionOn() async {
|
|
driver.runtimeStatuses = [.outdated("1.3.0"), .current("1.4.1")]
|
|
let flow = makeFlow()
|
|
await flow.setUp()
|
|
#expect(driver.calls.contains("begin(1.3.0)"))
|
|
#expect(flow.allRequiredDone)
|
|
}
|
|
|
|
@Test func aMismatchedDownloadIsNeverInstalled() async {
|
|
driver.runtimeStatuses = [.missing]
|
|
installer.failDownload(with: .mismatch)
|
|
let flow = makeFlow()
|
|
await flow.setUp()
|
|
#expect(flow.runtime == .failed("The download didn't match the expected package, so it wasn't installed.", details: []))
|
|
#expect(installer.calls == ["download"])
|
|
#expect(!driver.calls.contains { $0.hasPrefix("begin") })
|
|
}
|
|
|
|
@Test func anOfflineDownloadSaysSo() async {
|
|
driver.runtimeStatuses = [.missing]
|
|
installer.failDownload(with: .failed("The Internet connection appears to be offline."))
|
|
let flow = makeFlow()
|
|
await flow.setUp()
|
|
guard case .failed(let message, _) = flow.runtime else {
|
|
Issue.record("Expected a failure")
|
|
return
|
|
}
|
|
#expect(message == "Couldn't download the runtime. Check your connection and try again.")
|
|
}
|
|
|
|
@Test func quittingInstallerWithoutInstallingIsCancelled() async {
|
|
driver.runtimeStatuses = [.missing]
|
|
let flow = makeFlow()
|
|
await flow.setUp()
|
|
#expect(flow.runtime == .pending("Installing was cancelled."))
|
|
#expect(flow.runtimeAction == .install)
|
|
#expect(flow.canSetUp)
|
|
#expect(driver.calls == ["runtimeStatus", "begin(nil)", "runtimeStatus", "runtimeStatus", "finish(cancelled)"])
|
|
await waitUntil("the package is discarded") { installer.calls.contains("discard") }
|
|
#expect(installer.calls == ["download", "open", "discard"])
|
|
}
|
|
|
|
@Test func aChangedDownloadIsNotOpened() async {
|
|
driver.runtimeStatuses = [.missing]
|
|
installer.open(with: .changed)
|
|
let flow = makeFlow()
|
|
await flow.setUp()
|
|
#expect(flow.runtime == .failed("The download changed after it was checked, so it wasn't opened.", details: []))
|
|
#expect(installer.calls == ["download", "open"])
|
|
#expect(driver.calls.last?.hasPrefix("finish(failed") == true)
|
|
}
|
|
|
|
@Test func installerThatWontOpenSaysSo() async {
|
|
driver.runtimeStatuses = [.missing]
|
|
installer.open(with: .failed("x"))
|
|
let flow = makeFlow()
|
|
await flow.setUp()
|
|
#expect(flow.runtime == .failed("Couldn't open Installer.", details: ["x"]))
|
|
#expect(installer.calls == ["download", "open", "discard"])
|
|
#expect(flow.canSetUp)
|
|
#expect(driver.calls.last?.hasPrefix("finish(failed") == true)
|
|
}
|
|
|
|
@Test func anUpdateWaitsForTheBrowserToClose() async {
|
|
driver.runtimeStatuses = [.outdated("1.3.0")]
|
|
driver.holdResult = .failure(.sessionOpen)
|
|
let flow = makeFlow()
|
|
await flow.setUp()
|
|
#expect(flow.runtime == .failed("Close the Knotchy browser first.", details: []))
|
|
#expect(installer.calls == ["download", "discard"])
|
|
}
|
|
|
|
@Test func quittingWhileInstallerIsOpenLeavesItToFinish() async {
|
|
driver.runtimeStatuses = [.missing]
|
|
installer.session.hasQuit = false
|
|
let flow = makeFlow()
|
|
flow.start()
|
|
await waitUntil("Installer is open") { flow.runtime == .running("Finish the install in Installer…", fraction: nil) }
|
|
flow.cancel()
|
|
await waitUntil("the flow stops") { !flow.isRunning }
|
|
#expect(!driver.calls.contains { $0.hasPrefix("finish(") })
|
|
#expect(!installer.calls.contains("discard"))
|
|
installer.session.hasQuit = true // Ends the cleanup task.
|
|
}
|
|
|
|
@Test func aClosingBrowserIsWaitedFor() async {
|
|
driver.runtimeStatuses = [.outdated("1.3.0")]
|
|
for state in [SessionState.destroying, .failed(.destroyStuck)] {
|
|
driver.state = state
|
|
let flow = makeFlow()
|
|
await flow.setUp()
|
|
#expect(flow.runtime == .failed("Wait for the Knotchy browser to finish closing, then try again.", details: []))
|
|
}
|
|
#expect(installer.calls.isEmpty)
|
|
}
|
|
|
|
@Test func aFailedServiceStartStopsSetUp() async {
|
|
driver.checkResults = [.failed(.serviceStopped, "Couldn't start the service: x")]
|
|
driver.setupOutput = ["x"]
|
|
let flow = makeFlow()
|
|
await flow.setUp()
|
|
#expect(flow.runtime == .failed("Couldn't start the service: x", details: ["x"]))
|
|
#expect(driver.calls.filter { $0 == "checkSetup" }.count == 1)
|
|
#expect(!driver.calls.contains { $0.hasPrefix("runSetup") })
|
|
}
|
|
|
|
@Test func aBuildFailureShowsItsOutput() async {
|
|
driver.checkResults = [.needs(.imageMissing), .needs(.imageMissing)]
|
|
driver.runResults = [.failed(.imageMissing, "The image build failed: out of space")]
|
|
driver.setupOutput = ["Step 7/9", "error: out of space"]
|
|
let flow = makeFlow()
|
|
await flow.setUp()
|
|
#expect(flow.vm == .failed("The image build failed: out of space", details: ["Step 7/9", "error: out of space"]))
|
|
#expect(driver.calls.contains("runSetup(imageMissing, continuing: true)"))
|
|
}
|
|
|
|
@Test func aSetupTheNotchStartedIsFollowed() async {
|
|
driver.state = .settingUp(.imageMissing)
|
|
driver.checkResults = [nil, nil, .ready]
|
|
let flow = makeFlow()
|
|
await flow.setUp()
|
|
#expect(driver.calls.contains("wait"))
|
|
#expect(flow.vm == .done("Ready"))
|
|
}
|
|
|
|
@Test func theRowsShowTheControllersProgress() {
|
|
let flow = makeFlow()
|
|
driver.state = .settingUp(.imageMissing)
|
|
driver.setupLog = "Step 3/9"
|
|
#expect(flow.vm == .running("Step 3/9", fraction: nil))
|
|
driver.state = .settingUp(.serviceStopped)
|
|
#expect(flow.runtime == .running("Starting the runtime…", fraction: nil))
|
|
}
|
|
|
|
@Test func anOpenBrowserCountsAsAReadyVM() async {
|
|
driver.state = .live(.peek)
|
|
driver.checkResults = [nil]
|
|
let flow = makeFlow()
|
|
await flow.refresh()
|
|
#expect(flow.vm == .done("Ready"))
|
|
#expect(flow.allRequiredDone)
|
|
}
|
|
|
|
@Test func anOpenBrowserIsReportedBeforeDownloadingAnUpdate() async {
|
|
driver.state = .live(.peek)
|
|
driver.runtimeStatuses = [.outdated("1.3.0")]
|
|
let flow = makeFlow()
|
|
await flow.setUp()
|
|
#expect(flow.runtime == .failed("Close the Knotchy browser first.", details: []))
|
|
#expect(installer.calls.isEmpty)
|
|
}
|
|
|
|
@Test func downloadProgressReadsInMegabytes() {
|
|
#expect(SetupFlow.downloadText(58_886_932, of: ContainerRuntime.pinned.size) == "Downloading… 59 of 118 MB")
|
|
}
|
|
}
|