From 91293557ec057071f2cda13a858746c86adc3d18 Mon Sep 17 00:00:00 2001 From: Brandon Evans Date: Sat, 23 Jan 2021 11:35:24 -0700 Subject: [PATCH 1/2] Catch and reword xip "not enough free space" error --- Xcodes/Backend/AppState+Install.swift | 16 ++++++++++-- XcodesTests/AppStateTests.swift | 35 +++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/Xcodes/Backend/AppState+Install.swift b/Xcodes/Backend/AppState+Install.swift index 2d07bb2..215b445 100644 --- a/Xcodes/Backend/AppState+Install.swift +++ b/Xcodes/Backend/AppState+Install.swift @@ -206,10 +206,15 @@ extension AppState { return Current.shell.unxip(source) .catch { error -> AnyPublisher in - if let executionError = error as? ProcessExecutionError, - executionError.standardError.contains("damaged and can’t be expanded") { + if let executionError = error as? ProcessExecutionError { + if executionError.standardError.contains("damaged and can’t be expanded") { return Fail(error: InstallationError.damagedXIP(url: source)) .eraseToAnyPublisher() + } else if executionError.standardError.contains("can’t be expanded because the selected volume doesn’t have enough free space.") { + return Fail(error: InstallationError.notEnoughFreeSpaceToExpandArchive(archivePath: Path(url: source)!, + version: availableXcode.version)) + .eraseToAnyPublisher() + } } return Fail(error: error) .eraseToAnyPublisher() @@ -365,6 +370,7 @@ extension AppState { public enum InstallationError: LocalizedError, Equatable { case damagedXIP(url: URL) + case notEnoughFreeSpaceToExpandArchive(archivePath: Path, version: Version) case failedToMoveXcodeToApplications case failedSecurityAssessment(xcode: InstalledXcode, output: String) case codesignVerifyFailed(output: String) @@ -383,6 +389,12 @@ public enum InstallationError: LocalizedError, Equatable { switch self { case .damagedXIP(let url): return "The archive \"\(url.lastPathComponent)\" is damaged and can't be expanded." + case let .notEnoughFreeSpaceToExpandArchive(archivePath, version): + return """ + The archive “\(archivePath.basename())” can’t be expanded because the current volume doesn’t have enough free space. + + Make more space available to expand the archive and then install Xcode \(version.appleDescription) again to start installation from where it left off. + """ case .failedToMoveXcodeToApplications: return "Failed to move Xcode to the /Applications directory." case .failedSecurityAssessment(let xcode, let output): diff --git a/XcodesTests/AppStateTests.swift b/XcodesTests/AppStateTests.swift index b5f7dc3..294bb12 100644 --- a/XcodesTests/AppStateTests.swift +++ b/XcodesTests/AppStateTests.swift @@ -280,4 +280,39 @@ class AppStateTests: XCTestCase { ) } + func test_Install_NotEnoughFreeSpace() throws { + Current.shell.unxip = { _ in + Fail(error: ProcessExecutionError( + process: Process(), + standardOutput: "xip: signing certificate was \"Development Update\" (validation not attempted)", + standardError: "xip: error: The archive “Xcode-12.4.0-Release.Candidate+12D4e.xip” can’t be expanded because the selected volume doesn’t have enough free space." + )) + .eraseToAnyPublisher() + } + let archiveURL = URL(fileURLWithPath: "/Users/user/Library/Application Support/Xcode-0.0.0.xip") + + let recorder = subject.unarchiveAndMoveXIP( + availableXcode: AvailableXcode( + version: Version("0.0.0")!, + url: URL(string: "https://developer.apple.com")!, + filename: "Xcode-0.0.0.xip", + releaseDate: nil + ), + at: archiveURL, + to: URL(string: "/Applications/Xcode-0.0.0.app")! + ).record() + + let completion = try wait(for: recorder.completion, timeout: 1, description: "Completion") + + if case let .failure(error as InstallationError) = completion { + XCTAssertEqual( + error, + InstallationError.notEnoughFreeSpaceToExpandArchive(archivePath: Path(url: archiveURL)!, + version: Version("0.0.0")!) + ) + } + else { + XCTFail() + } + } } From 42bfbdb50d934139f514356c5c3fa9c366049993 Mon Sep 17 00:00:00 2001 From: Brandon Evans Date: Sat, 23 Jan 2021 13:28:54 -0700 Subject: [PATCH 2/2] Add AppStateTests.swift to XcodesTests target --- Xcodes.xcodeproj/project.pbxproj | 2 ++ XcodesTests/AppStateTests.swift | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Xcodes.xcodeproj/project.pbxproj b/Xcodes.xcodeproj/project.pbxproj index e968bdb..7d23bd9 100644 --- a/Xcodes.xcodeproj/project.pbxproj +++ b/Xcodes.xcodeproj/project.pbxproj @@ -51,6 +51,7 @@ CAA858C425A2BE4E00ACF8C0 /* Downloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAA858C325A2BE4E00ACF8C0 /* Downloader.swift */; }; CAA858CD25A3D8BC00ACF8C0 /* ErrorHandling in Frameworks */ = {isa = PBXBuildFile; productRef = CAA858CC25A3D8BC00ACF8C0 /* ErrorHandling */; }; CAA858DB25A3E11F00ACF8C0 /* aria2-release-1.35.0.tar.gz in Resources */ = {isa = PBXBuildFile; fileRef = CAA858DA25A3E11F00ACF8C0 /* aria2-release-1.35.0.tar.gz */; }; + CAB3AB0E25BCA6C200BF1B04 /* AppStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAD2E7B72449575100113D76 /* AppStateTests.swift */; }; CABFA9BB2592EEEA00380FEE /* DateFormatter+.swift in Sources */ = {isa = PBXBuildFile; fileRef = CABFA9BA2592EEEA00380FEE /* DateFormatter+.swift */; }; CABFA9BD2592EEEA00380FEE /* Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = CABFA9A92592EEE900380FEE /* Environment.swift */; }; CABFA9BF2592EEEA00380FEE /* URLSession+DownloadTaskPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = CABFA9B32592EEEA00380FEE /* URLSession+DownloadTaskPublisher.swift */; }; @@ -808,6 +809,7 @@ CAC281E7259FA45A00B8AB0B /* Environment+Mock.swift in Sources */, CAC281E2259FA44600B8AB0B /* Bundle+XcodesTests.swift in Sources */, CA2518EC25A7FF2B00F08414 /* AppStateUpdateTests.swift in Sources */, + CAB3AB0E25BCA6C200BF1B04 /* AppStateTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/XcodesTests/AppStateTests.swift b/XcodesTests/AppStateTests.swift index 294bb12..11fef6a 100644 --- a/XcodesTests/AppStateTests.swift +++ b/XcodesTests/AppStateTests.swift @@ -167,7 +167,7 @@ class AppStateTests: XCTestCase { [.installing(.trashingArchive), .notInstalled, .notInstalled], [.installing(.checkingSecurity), .notInstalled, .notInstalled], [.installing(.finishing), .notInstalled, .notInstalled], - [.installed, .notInstalled, .notInstalled] + [.installed(Path("/Applications/Xcode-0.0.0.app")!), .notInstalled, .notInstalled] ] ) } @@ -275,7 +275,7 @@ class AppStateTests: XCTestCase { [.installing(.trashingArchive), .notInstalled, .notInstalled], [.installing(.checkingSecurity), .notInstalled, .notInstalled], [.installing(.finishing), .notInstalled, .notInstalled], - [.installed, .notInstalled, .notInstalled] + [.installed(Path("/Applications/Xcode-0.0.0.app")!), .notInstalled, .notInstalled] ] ) }