diff --git a/Sources/SwiftDependencyUpdaterLibrary/Dependency.swift b/Sources/SwiftDependencyUpdaterLibrary/Dependency.swift index 9413890..cec9b6b 100644 --- a/Sources/SwiftDependencyUpdaterLibrary/Dependency.swift +++ b/Sources/SwiftDependencyUpdaterLibrary/Dependency.swift @@ -34,7 +34,7 @@ struct Dependency { if packageDescription.dependencies.isEmpty { return [] } - let resolvedPackage = try ResolvedPackage.loadResolvedPackage(from: folder) + let resolvedPackage = try ResolvedPackage.resolveAndLoadResolvedPackage(from: folder) let swiftPackageUpdates = try SwiftPackageUpdate.checkUpdates(in: folder) return try mergeDependencies(packageDescription: packageDescription, resolvedPackage: resolvedPackage, swiftPackageUpdates: swiftPackageUpdates) } diff --git a/Sources/SwiftDependencyUpdaterLibrary/PackageDescription.swift b/Sources/SwiftDependencyUpdaterLibrary/PackageDescription.swift index b9d0d2f..d3cfb81 100644 --- a/Sources/SwiftDependencyUpdaterLibrary/PackageDescription.swift +++ b/Sources/SwiftDependencyUpdaterLibrary/PackageDescription.swift @@ -30,6 +30,27 @@ private struct PackageDependencyV55: PackageDependency { let url: URL } +private struct PackageDependencyV56: PackageDependency { + + enum CodingKeys: String, CodingKey { + case name = "identity" + case requirement + case location + } + + let name: String + let requirement: DependencyRequirement + let location: PackageLocation + + var url: URL { + location.remote.first! + } +} + +private struct PackageLocation: Decodable { + let remote: [URL] +} + enum DependencyRequirement: Decodable, Equatable { case exact(version: Version) @@ -116,21 +137,39 @@ struct PackageDescriptionV55: PackageDescription { } +struct PackageDescriptionV56: PackageDescription { + + enum CodingKeys: String, CodingKey { + case dependencyMap = "dependencies" + } + + private let dependencyMap: [[String: [PackageDependencyV56]]] + var dependencies: [PackageDependency] { + dependencyMap.flatMap { $0.values.flatMap { $0 } } + } + +} + enum PackageDescriptionFactory { static func loadPackageDescription(from folder: URL) throws -> PackageDescription { let json = try readPackageDescription(from: folder) let data = json.data(using: .utf8)! let decoder = JSONDecoder() do { - let packageDescription = try decoder.decode(PackageDescriptionV55.self, from: data) + let packageDescription = try decoder.decode(PackageDescriptionV56.self, from: data) return packageDescription } catch { do { - let packageDescription = try decoder.decode(PackageDescriptionV54.self, from: data) + let packageDescription = try decoder.decode(PackageDescriptionV55.self, from: data) return packageDescription } catch { + do { + let packageDescription = try decoder.decode(PackageDescriptionV54.self, from: data) + return packageDescription + } catch { + throw PackageDescriptionError.parsingFailed(String(describing: error), json) + } } - throw PackageDescriptionError.parsingFailed(String(describing: error), json) } } diff --git a/Sources/SwiftDependencyUpdaterLibrary/ResolvedPackage.swift b/Sources/SwiftDependencyUpdaterLibrary/ResolvedPackage.swift index 3f45612..b5dca93 100644 --- a/Sources/SwiftDependencyUpdaterLibrary/ResolvedPackage.swift +++ b/Sources/SwiftDependencyUpdaterLibrary/ResolvedPackage.swift @@ -63,13 +63,17 @@ struct ResolvedPackage: Decodable { let dependencies: [ResolvedDependency] - static func loadResolvedPackage(from folder: URL) throws -> ResolvedPackage { + static func resolveAndLoadResolvedPackage(from folder: URL) throws -> ResolvedPackage { do { try shellOut(to: "swift", arguments: ["package", "resolve", "--package-path", "\"\(folder.path)\"" ]) } catch { let error = error as! ShellOutError // swiftlint:disable:this force_cast throw ResolvedPackageError.resolvingFailed(error.message) } + return try loadResolvedPackage(from: folder) + } + + static func loadResolvedPackage(from folder: URL) throws -> ResolvedPackage { let data = try readResolvedPackageData(from: folder) let decoder = JSONDecoder() do { diff --git a/Sources/SwiftDependencyUpdaterLibrary/SwiftPackageUpdate.swift b/Sources/SwiftDependencyUpdaterLibrary/SwiftPackageUpdate.swift index 4870fcc..32e1da1 100644 --- a/Sources/SwiftDependencyUpdaterLibrary/SwiftPackageUpdate.swift +++ b/Sources/SwiftDependencyUpdaterLibrary/SwiftPackageUpdate.swift @@ -32,7 +32,7 @@ struct SwiftPackageUpdate { private static func readUpdates(in folder: URL) throws -> String { do { - let output = try shellOut(to: "swift", arguments: ["package", "update", "--dry-run", "--package-path", "\"\(folder.path)\"" ]) + let output = try shellOut(to: "swift", arguments: ["package", "--package-path", "\"\(folder.path)\"", "update", "--dry-run" ]) return output } catch { let error = error as! ShellOutError // swiftlint:disable:this force_cast diff --git a/Tests/SwiftDependencyUpdaterLibraryTests/Extensions/XCTestCase.swift b/Tests/SwiftDependencyUpdaterLibraryTests/Extensions/XCTestCase.swift index 19fdf0b..b757a93 100644 --- a/Tests/SwiftDependencyUpdaterLibraryTests/Extensions/XCTestCase.swift +++ b/Tests/SwiftDependencyUpdaterLibraryTests/Extensions/XCTestCase.swift @@ -74,4 +74,19 @@ extension XCTestCase { XCTAssertEqual(error, expectedError, file: file, line: line) } + func assert(_ expression: @autoclosure () throws -> T, throws expectedErrors: [E], in file: StaticString = #file, line: UInt = #line) { + var caughtError: Error? + + XCTAssertThrowsError(try expression(), file: file, line: line) { + caughtError = $0 + } + + guard let error = caughtError as? E else { + XCTFail("Unexpected error type, got \(type(of: caughtError!)) instead of \(E.self)", file: file, line: line) + return + } + + XCTAssert(expectedErrors.contains(error), "Received \(error) instead of expected error", file: file, line: line) + } + } diff --git a/Tests/SwiftDependencyUpdaterLibraryTests/PackageDescriptionTest.swift b/Tests/SwiftDependencyUpdaterLibraryTests/PackageDescriptionTest.swift index 2b73991..6b9b88a 100644 --- a/Tests/SwiftDependencyUpdaterLibraryTests/PackageDescriptionTest.swift +++ b/Tests/SwiftDependencyUpdaterLibraryTests/PackageDescriptionTest.swift @@ -6,9 +6,13 @@ class PackageDescriptionTest: XCTestCase { func testEmptyFolder() { let folder = emptyFolderURL() + assert( try PackageDescriptionFactory.loadPackageDescription(from: folder), - throws: PackageDescriptionError.loadingFailed("error: root manifest not found") + throws: [ + PackageDescriptionError.loadingFailed("error: root manifest not found"), + PackageDescriptionError.loadingFailed("error: Could not find Package.swift in this directory or any of its parent directories.") + ] ) } @@ -26,8 +30,12 @@ class PackageDescriptionTest: XCTestCase { XCTFail("Unexpected error, got \(type(of: caughtError!)) \(caughtError!)) instead of PackageDescriptionError.loadingFailed") return } + let errors = [ + "Missing or empty JSON output from manifest compilation", + "\(folder.path): error: malformed" + ] - XCTAssert(description.contains("\(folder.path): error: malformed")) + XCTAssert(errors.contains { description.contains($0) }, "Received \(description) instead of expected error") } func testParsing() { diff --git a/Tests/SwiftDependencyUpdaterLibraryTests/ResolvedPackageTests.swift b/Tests/SwiftDependencyUpdaterLibraryTests/ResolvedPackageTests.swift index 3ed7b2c..73a826c 100644 --- a/Tests/SwiftDependencyUpdaterLibraryTests/ResolvedPackageTests.swift +++ b/Tests/SwiftDependencyUpdaterLibraryTests/ResolvedPackageTests.swift @@ -4,23 +4,64 @@ import XCTest class ResolvedPackageTests: XCTestCase { + func testEmptyFolderResolve() { + let folder = emptyFolderURL() + + assert( + try ResolvedPackage.resolveAndLoadResolvedPackage(from: folder), + throws: [ + ResolvedPackageError.resolvingFailed("error: root manifest not found"), + ResolvedPackageError.resolvingFailed("error: Could not find Package.swift in this directory or any of its parent directories.") + ] + ) + } + func testEmptyFolder() { let folder = emptyFolderURL() + assert( try ResolvedPackage.loadResolvedPackage(from: folder), - throws: ResolvedPackageError.resolvingFailed("error: root manifest not found") + throws: [ + ResolvedPackageError.readingFailed("The file “Package.resolved” couldn’t be opened because there is no such file."), + ResolvedPackageError.readingFailed("The operation could not be completed. No such file or directory") + ] ) } + func testInvalidFileResolve() { + let folder = emptyFolderURL() + let resolvedFile = temporaryFileURL(in: folder, name: "Package.resolved") + createFile(at: resolvedFile, content: "\n") + let packageFile = temporaryFileURL(in: folder, name: "Package.swift") + createFile(at: packageFile, content: TestUtils.emptyPackageSwiftFileContent) + + XCTAssertThrowsError(try ResolvedPackage.resolveAndLoadResolvedPackage(from: folder)) { + guard let error = $0 as? ResolvedPackageError else { + XCTFail("Unexpected error type, got \(type(of: $0)) instead of \(ResolvedPackageError.self)") + return + } + if case let .resolvingFailed(errorMessage) = error { + XCTAssert(errorMessage.contains("error: Package.resolved file is corrupted or malformed; fix or delete the file to continue"), + "Received error message \(errorMessage) does not contain expected error") + } else { + XCTFail("Received errors is not of type resolvingFailed: \(error)") + } + } + } + func testInvalidFile() { let folder = emptyFolderURL() let resolvedFile = temporaryFileURL(in: folder, name: "Package.resolved") createFile(at: resolvedFile, content: "\n") let packageFile = temporaryFileURL(in: folder, name: "Package.swift") createFile(at: packageFile, content: TestUtils.emptyPackageSwiftFileContent) + assert( try ResolvedPackage.loadResolvedPackage(from: folder), - throws: ResolvedPackageError.resolvingFailed("error: Package.resolved file is corrupted or malformed; fix or delete the file to continue: malformed") + throws: [ + ResolvedPackageError.parsingFailed("The data couldn’t be read because it isn’t in the correct format.", "\n"), + ResolvedPackageError.parsingFailed("The operation could not be completed. The data isn’t in the correct format.", "\n") + ] ) } diff --git a/Tests/SwiftDependencyUpdaterLibraryTests/SubCommands/GitHubCommandTests.swift b/Tests/SwiftDependencyUpdaterLibraryTests/SubCommands/GitHubCommandTests.swift index 8b262a4..d184406 100644 --- a/Tests/SwiftDependencyUpdaterLibraryTests/SubCommands/GitHubCommandTests.swift +++ b/Tests/SwiftDependencyUpdaterLibraryTests/SubCommands/GitHubCommandTests.swift @@ -110,7 +110,11 @@ class GitHubCommandTests: XCTestCase { let result = outputFromExecutionWith(arguments: ["github", url.path]) XCTAssertEqual(result.exitCode, 1) XCTAssertEqual(result.errorOutput, "") - XCTAssertEqual(result.output, "Could not get package data, swift package dump-package failed: error: root manifest not found") + let errors = [ + "Could not get package data, swift package dump-package failed: error: Could not find Package.swift in this directory or any of its parent directories.", + "Could not get package data, swift package dump-package failed: error: root manifest not found" + ] + XCTAssert(errors.contains(result.output), "Received \(result.output) instead of expected error") } func testInvalidPackage() { diff --git a/Tests/SwiftDependencyUpdaterLibraryTests/SubCommands/ListCommandTests.swift b/Tests/SwiftDependencyUpdaterLibraryTests/SubCommands/ListCommandTests.swift index d3f4007..c641246 100644 --- a/Tests/SwiftDependencyUpdaterLibraryTests/SubCommands/ListCommandTests.swift +++ b/Tests/SwiftDependencyUpdaterLibraryTests/SubCommands/ListCommandTests.swift @@ -16,7 +16,11 @@ class ListCommandTests: XCTestCase { let result = outputFromExecutionWith(arguments: ["list", url.path]) XCTAssertEqual(result.exitCode, 1) XCTAssertEqual(result.errorOutput, "") - XCTAssertEqual(result.output, "Could not get package data, swift package dump-package failed: error: root manifest not found") + let errors = [ + "Could not get package data, swift package dump-package failed: error: Could not find Package.swift in this directory or any of its parent directories.", + "Could not get package data, swift package dump-package failed: error: root manifest not found" + ] + XCTAssert(errors.contains(result.output), "Received \(result.output) instead of expected error") } func testInvalidPackage() { diff --git a/Tests/SwiftDependencyUpdaterLibraryTests/SubCommands/UpdateCommandTests.swift b/Tests/SwiftDependencyUpdaterLibraryTests/SubCommands/UpdateCommandTests.swift index 7339de9..44383d4 100644 --- a/Tests/SwiftDependencyUpdaterLibraryTests/SubCommands/UpdateCommandTests.swift +++ b/Tests/SwiftDependencyUpdaterLibraryTests/SubCommands/UpdateCommandTests.swift @@ -16,7 +16,11 @@ class UpdateCommandTests: XCTestCase { let result = outputFromExecutionWith(arguments: ["update", url.path]) XCTAssertEqual(result.exitCode, 1) XCTAssertEqual(result.errorOutput, "") - XCTAssertEqual(result.output, "Could not get package data, swift package dump-package failed: error: root manifest not found") + let errors = [ + "Could not get package data, swift package dump-package failed: error: root manifest not found", + "Could not get package data, swift package dump-package failed: error: Could not find Package.swift in this directory or any of its parent directories." + ] + XCTAssert(errors.contains(result.output), "Received \(result.output) instead of expected error") } func testInvalidPackage() { diff --git a/Tests/SwiftDependencyUpdaterLibraryTests/SwiftPackageUpdateTests.swift b/Tests/SwiftDependencyUpdaterLibraryTests/SwiftPackageUpdateTests.swift index 235e411..39054e2 100644 --- a/Tests/SwiftDependencyUpdaterLibraryTests/SwiftPackageUpdateTests.swift +++ b/Tests/SwiftDependencyUpdaterLibraryTests/SwiftPackageUpdateTests.swift @@ -32,7 +32,10 @@ class SwiftPackageUpdateTests: XCTestCase { let folder = emptyFolderURL() assert( try SwiftPackageUpdate.checkUpdates(in: folder), - throws: SwiftPackageUpdateError.loadingFailed("error: root manifest not found") + throws: [ + SwiftPackageUpdateError.loadingFailed("error: root manifest not found"), + SwiftPackageUpdateError.loadingFailed("error: Could not find Package.swift in this directory or any of its parent directories.") + ] ) }