From 837be67dcd283db3257c2c5f59bcceddd04867ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20K=C3=B6tte?= Date: Sat, 22 May 2021 23:12:11 -0700 Subject: [PATCH 1/4] Add update command --- .../Dependency.swift | 4 + .../Extensions/String.swift | 11 +++ .../ResolvedPackage.swift | 8 ++ .../SubCommands/UpdateCommand.swift | 42 ++++++++++ .../SwiftDependencyUpdater.swift | 4 +- .../SwiftPackage.swift | 84 +++++++++++++++++++ .../Update.swift | 25 ++++++ 7 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 Sources/SwiftDependencyUpdaterLibrary/SubCommands/UpdateCommand.swift create mode 100644 Sources/SwiftDependencyUpdaterLibrary/SwiftPackage.swift diff --git a/Sources/SwiftDependencyUpdaterLibrary/Dependency.swift b/Sources/SwiftDependencyUpdaterLibrary/Dependency.swift index da12db5..749be20 100644 --- a/Sources/SwiftDependencyUpdaterLibrary/Dependency.swift +++ b/Sources/SwiftDependencyUpdaterLibrary/Dependency.swift @@ -44,6 +44,10 @@ struct Dependency { update: update) } } + + func update(in folder: URL) throws { + try update?.execute(for: self, in: folder) + } } extension Dependency: CustomStringConvertible { diff --git a/Sources/SwiftDependencyUpdaterLibrary/Extensions/String.swift b/Sources/SwiftDependencyUpdaterLibrary/Extensions/String.swift index b924095..bf004b4 100644 --- a/Sources/SwiftDependencyUpdaterLibrary/Extensions/String.swift +++ b/Sources/SwiftDependencyUpdaterLibrary/Extensions/String.swift @@ -14,4 +14,15 @@ extension String { } } + func matchingStringsWithRange(regex: NSRegularExpression) -> [[(string: String, range: NSRange)?]] { + let nsString = self as NSString + let results = regex.matches(in: self, options: [], range: NSRange(self.startIndex..., in: self)) + return results.map { result in + (0.. Bool { + guard case let .withChangingRequirements(updatedVersion) = update else { + throw SwiftPackageError.invalidUpdate(dependency.name, update) + } + + var string = try read() + let nsString = string as NSString + + // swiftlint:disable:next line_length + let versionRegExString = "(\\.upToNextMajor\\s*\\(\\s*from\\s*:\\s*\"([0-9]*\\.[0-9]*\\.[0-9]*)\"\\s*\\))|(\\.upToNextMinor\\s*\\(\\s*from\\s*:\\s*\"([0-9]*\\.[0-9]*\\.[0-9]*)\"\\s*\\))|(\\.exact\\s*\\(\\s*\"([0-9]*\\.[0-9]*\\.[0-9]*)\"\\s*\\))|(from\\s*:\\s*\\s*\"([0-9]*\\.[0-9]*\\.[0-9]*)\")|(\\s*\"[0-9]*\\.[0-9]*\\.[0-9]*\"\\.\\.\\.\"([0-9]*\\.[0-9]*\\.[0-9]*)\")|(\\s*\"[0-9]*\\.[0-9]*\\.[0-9]*\"\\.\\.<\"([0-9]*\\.[0-9]*\\.[0-9]*)\")" + // swiftlint:disable:next line_length + let regex = try NSRegularExpression(pattern: "dependencies\\s*:\\s*\\[\\s*[^\\]]*\\.package\\s*\\(\\s*url\\s*:\\s*\"\(NSRegularExpression.escapedPattern(for: dependency.url.absoluteString))\"\\s*,\\s*(\(versionRegExString))", options: [.anchorsMatchLines]) + + let results = string.matchingStringsWithRange(regex: regex) + guard results.count == 1, let matches = results[safe: 0] else { + throw SwiftPackageError.resultCountMismatch(dependency.name, results.count) + } + + var packageUpdate = false + if matches[2] != nil, let version = matches[3] { + string = nsString.replacingCharacters(in: version.range, with: "\(updatedVersion)") + } else if matches[4] != nil, let version = matches[5] { + string = nsString.replacingCharacters(in: version.range, with: "\(updatedVersion)") + } else if matches[6] != nil, let version = matches[7] { + string = nsString.replacingCharacters(in: version.range, with: "\(updatedVersion)") + } else if matches[8] != nil, let version = matches[9] { + string = nsString.replacingCharacters(in: version.range, with: "\(updatedVersion)") + } else if matches[10] != nil, let version = matches[11] { + string = nsString.replacingCharacters(in: version.range, with: "\(updatedVersion)") + packageUpdate = true + } else if matches[12] != nil, let version = matches[13] { + var newVersion = updatedVersion + newVersion.patch += 1 + string = nsString.replacingCharacters(in: version.range, with: "\(newVersion)") + packageUpdate = true + } else { + throw SwiftPackageError.noResultMatch(dependency.name, matches.map { $0?.string }) + } + + try write(string) + return packageUpdate + } + + private func read() throws -> String { + try String(contentsOf: url, encoding: .utf8) + } + + private func write(_ string: String) throws { + try string.write(to: url, atomically: true, encoding: .utf8) + } + +} + +extension SwiftPackageError: LocalizedError { + public var errorDescription: String? { + switch self { + case let .invalidUpdate(name, update): + return "Invalid update for \(name): \(update)" + case let .resultCountMismatch(name, count): + return "Finding version requirement in Package.swift failed for \(name): Got \(count) instead of 1 result" + case let .noResultMatch(name, results): + return "Finding version requirement in Package.swift failed for \(name). Findings: \(results)" + } + } +} diff --git a/Sources/SwiftDependencyUpdaterLibrary/Update.swift b/Sources/SwiftDependencyUpdaterLibrary/Update.swift index 333d227..e10faea 100644 --- a/Sources/SwiftDependencyUpdaterLibrary/Update.swift +++ b/Sources/SwiftDependencyUpdaterLibrary/Update.swift @@ -1,5 +1,6 @@ import Foundation import Releases +import ShellOut enum UpdateError: Error, Equatable { case resolvedVersionNotFound(String, Version, Version) @@ -38,6 +39,30 @@ enum Update: Equatable { return nil } } + + func execute(for dependency: Dependency, in folder: URL) throws { + switch self { + case let .withChangingRequirements(version): + print("Updating \(dependency.name): \(dependency.resolvedVersion.versionNumberOrRevision) -> \(version)".bold) + let swiftPackage = SwiftPackage(in: folder) + let packageUpdate = try swiftPackage.performUpdate(self, of: dependency) + print("Updated Package.swift".green) + if packageUpdate { + try shellOut(to: "swift", arguments: ["package", "update", dependency.name, "--package-path", "\"\(folder.path)\"" ]) + print("Resolved to new version".green) + } else { + try shellOut(to: "swift", arguments: ["package", "update", "resolve", "--package-path", "\"\(folder.path)\"" ]) + print("Resolved Version".green) + } + case let .withoutChangingRequirements(version): + print("Updating \(dependency.name): \(dependency.resolvedVersion.versionNumberOrRevision) -> \(version)".bold) + try shellOut(to: "swift", arguments: ["package", "update", dependency.name, "--package-path", "\"\(folder.path)\"" ]) + print("Resolved to new version".green) + default: + // Do nothing + break + } + } } extension Update: CustomStringConvertible { From 81cc9df2e92b3c8df9be6874d958da30cc57931b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20K=C3=B6tte?= Date: Sat, 22 May 2021 23:13:59 -0700 Subject: [PATCH 2/4] Add documentation for update command --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 9ceb702..5524205 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,10 @@ $ swift run swift-dependency-updater ### Locally +#### Update dependencies: + +`swift-dependency-updater [update] [] [--keep-requirements]` + #### List all dependencies and possible updates: `swift-dependency-updater list [] [--exclude-indirect] [--updates-only]` From 02ab7f64d0acf64570075132bcfe6899aeafc158 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20K=C3=B6tte?= Date: Sun, 23 May 2021 00:17:11 -0700 Subject: [PATCH 3/4] Add tests for update command code --- .../SwiftPackage.swift | 26 ++- .../ExtensionsTests/StringTests.swift | 29 ++- .../ResolvedPackageTests.swift | 13 +- .../SubCommands/UpdateCommandTests.swift | 70 +++++++ .../SwiftPackageTests.swift | 179 ++++++++++++++++++ .../TestUtils.swift | 9 + 6 files changed, 317 insertions(+), 9 deletions(-) create mode 100644 Tests/SwiftDependencyUpdaterLibraryTests/SubCommands/UpdateCommandTests.swift create mode 100644 Tests/SwiftDependencyUpdaterLibraryTests/SwiftPackageTests.swift diff --git a/Sources/SwiftDependencyUpdaterLibrary/SwiftPackage.swift b/Sources/SwiftDependencyUpdaterLibrary/SwiftPackage.swift index b06d216..8e4ed4c 100644 --- a/Sources/SwiftDependencyUpdaterLibrary/SwiftPackage.swift +++ b/Sources/SwiftDependencyUpdaterLibrary/SwiftPackage.swift @@ -5,6 +5,8 @@ enum SwiftPackageError: Error, Equatable { case invalidUpdate(String, Update) case resultCountMismatch(String, Int) case noResultMatch(String, [String?]) + case readFailed(String) + case writeFailed(String) } struct SwiftPackage { @@ -18,7 +20,7 @@ struct SwiftPackage { } func performUpdate(_ update: Update, of dependency: Dependency) throws -> Bool { - guard case let .withChangingRequirements(updatedVersion) = update else { + guard case var .withChangingRequirements(updatedVersion) = update else { throw SwiftPackageError.invalidUpdate(dependency.name, update) } @@ -48,9 +50,8 @@ struct SwiftPackage { string = nsString.replacingCharacters(in: version.range, with: "\(updatedVersion)") packageUpdate = true } else if matches[12] != nil, let version = matches[13] { - var newVersion = updatedVersion - newVersion.patch += 1 - string = nsString.replacingCharacters(in: version.range, with: "\(newVersion)") + updatedVersion.patch += 1 + string = nsString.replacingCharacters(in: version.range, with: "\(updatedVersion)") packageUpdate = true } else { throw SwiftPackageError.noResultMatch(dependency.name, matches.map { $0?.string }) @@ -61,11 +62,20 @@ struct SwiftPackage { } private func read() throws -> String { - try String(contentsOf: url, encoding: .utf8) + do { + return try String(contentsOf: url, encoding: .utf8) + } catch { + throw SwiftPackageError.readFailed(error.localizedDescription) + } + } private func write(_ string: String) throws { - try string.write(to: url, atomically: true, encoding: .utf8) + do { + try string.write(to: url, atomically: true, encoding: .utf8) + } catch { + throw SwiftPackageError.writeFailed(error.localizedDescription) + } } } @@ -79,6 +89,10 @@ extension SwiftPackageError: LocalizedError { return "Finding version requirement in Package.swift failed for \(name): Got \(count) instead of 1 result" case let .noResultMatch(name, results): return "Finding version requirement in Package.swift failed for \(name). Findings: \(results)" + case let .readFailed(error): + return "Failed to read Package.swift file: \(error)" + case let .writeFailed(error): + return "Failed to write Package.swift file: \(error)" } } } diff --git a/Tests/SwiftDependencyUpdaterLibraryTests/ExtensionsTests/StringTests.swift b/Tests/SwiftDependencyUpdaterLibraryTests/ExtensionsTests/StringTests.swift index c7247f5..cea973a 100644 --- a/Tests/SwiftDependencyUpdaterLibraryTests/ExtensionsTests/StringTests.swift +++ b/Tests/SwiftDependencyUpdaterLibraryTests/ExtensionsTests/StringTests.swift @@ -4,7 +4,6 @@ import XCTest class StringTests: XCTestCase { func testMatchingStrings_multipleGroups() { - // swiftlint:disable:next force_try let regex = try! NSRegularExpression(pattern: "^\\s+([^\\s]+:[^\\s]+)\\s+(-?[0-9]+(.[0-9]+)?)\\s+([^\\s]+)\\s*(;.*)?$", options: []) let results = " Assets:Checking 1.00 EUR".matchingStrings(regex: regex) XCTAssertEqual(results.count, 1) @@ -12,7 +11,6 @@ class StringTests: XCTestCase { } func testMatchingStrings_multipleResults() { - // swiftlint:disable:next force_try let regex = try! NSRegularExpression(pattern: "\\d\\D\\d", options: []) let results = "0a01b1".matchingStrings(regex: regex) XCTAssertEqual(results.count, 2) @@ -20,4 +18,31 @@ class StringTests: XCTestCase { XCTAssertEqual(results[1], ["1b1"]) } + func testMatchingStringsWithRange_multipleGroups() { + let regex = try! NSRegularExpression(pattern: "^\\s+([^\\s]+:[^\\s]+)\\s+(-?[0-9]+(.[0-9]+)?)\\s+([^\\s]+)\\s*(;.*)?$", options: []) + let results = " Assets:Checking 1.00 EUR".matchingStringsWithRange(regex: regex) + XCTAssertEqual(results.count, 1) + XCTAssertEqual(results[0][0]?.string, " Assets:Checking 1.00 EUR") + XCTAssertEqual(results[0][1]?.string, "Assets:Checking") + XCTAssertEqual(results[0][2]?.string, "1.00") + XCTAssertEqual(results[0][3]?.string, ".00") + XCTAssertEqual(results[0][4]?.string, "EUR") + XCTAssertNil(results[0][5]) + XCTAssertEqual(results[0][0]?.range, NSRange(location: 0, length: 26)) + XCTAssertEqual(results[0][1]?.range, NSRange(location: 2, length: 15)) + XCTAssertEqual(results[0][2]?.range, NSRange(location: 18, length: 4)) + XCTAssertEqual(results[0][3]?.range, NSRange(location: 19, length: 3)) + XCTAssertEqual(results[0][4]?.range, NSRange(location: 23, length: 3)) + } + + func testMatchingStringsWithRange_multipleResults() { + let regex = try! NSRegularExpression(pattern: "\\d\\D\\d", options: []) + let results = "0a01b1".matchingStringsWithRange(regex: regex) + XCTAssertEqual(results.count, 2) + XCTAssertEqual(results[0][0]?.string, "0a0") + XCTAssertEqual(results[1][0]?.string, "1b1") + XCTAssertEqual(results[0][0]?.range, NSRange(location: 0, length: 3)) + XCTAssertEqual(results[1][0]?.range, NSRange(location: 3, length: 3)) + } + } diff --git a/Tests/SwiftDependencyUpdaterLibraryTests/ResolvedPackageTests.swift b/Tests/SwiftDependencyUpdaterLibraryTests/ResolvedPackageTests.swift index c7abda0..8045ee9 100644 --- a/Tests/SwiftDependencyUpdaterLibraryTests/ResolvedPackageTests.swift +++ b/Tests/SwiftDependencyUpdaterLibraryTests/ResolvedPackageTests.swift @@ -37,7 +37,6 @@ class ResolvedPackageTests: XCTestCase { } func testParsing() { - let folder = emptyFolderURL() let file = temporaryFileURL(in: folder, name: "Package.resolved") createFile(at: file, content: TestUtils.packageResolvedFileContent) @@ -83,6 +82,18 @@ class ResolvedPackageTests: XCTestCase { XCTAssertEqual("\(version)", "0.0.0 (abc, branch: main)") } + func testVersionNumberOrRevision() { + let decoder = JSONDecoder() + + var data = "{\"revision\": \"abc\", \"branch\": null, \"version\": null}".data(using: .utf8)! + var version = try! decoder.decode(ResolvedVersion.self, from: data) + XCTAssertEqual("\(version.versionNumberOrRevision)", "abc") + + data = "{\"revision\": \"abc\", \"branch\": \"main\", \"version\": \"1.2.3\"}".data(using: .utf8)! + version = try! decoder.decode(ResolvedVersion.self, from: data) + XCTAssertEqual("\(version.versionNumberOrRevision)", "1.2.3") + } + func testResolvedPackageErrorString() { XCTAssertEqual("\(ResolvedPackageError.readingFailed("abc").localizedDescription)", "Could not read Package.resolved file: abc") XCTAssertEqual("\(ResolvedPackageError.parsingFailed("abc", "def").localizedDescription)", "Could not parse package data: abc\n\nPackage Data: def") diff --git a/Tests/SwiftDependencyUpdaterLibraryTests/SubCommands/UpdateCommandTests.swift b/Tests/SwiftDependencyUpdaterLibraryTests/SubCommands/UpdateCommandTests.swift new file mode 100644 index 0000000..6c48e78 --- /dev/null +++ b/Tests/SwiftDependencyUpdaterLibraryTests/SubCommands/UpdateCommandTests.swift @@ -0,0 +1,70 @@ +@testable import SwiftDependencyUpdaterLibrary +import XCTest + +class UpdateCommandTests: XCTestCase { + + func testFileInsteadOfFolder() { + let url = emptyFileURL() + let result = outputFromExecutionWith(arguments: ["update", url.path]) + XCTAssertEqual(result.exitCode, 1) + XCTAssertEqual(result.errorOutput, "") + XCTAssertEqual(result.output, "Folder argument must be a directory.") + } + + func testEmptyFolder() { + let url = emptyFolderURL() + 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") + } + + func testInvalidPackage() { + let folder = emptyFolderURL() + let packageSwift = temporaryFileURL(in: folder, name: "Package.swift") + createFile(at: packageSwift, content: TestUtils.emptyPackageSwiftFileContent) + let packageResolved = temporaryFileURL(in: folder, name: "Package.resolved") + createFile(at: packageResolved, content: TestUtils.emptyPackageResolvedFileContent) + let result = outputFromExecutionWith(arguments: ["update", folder.path]) + XCTAssertEqual(result.exitCode, 1) + XCTAssertEqual(result.errorOutput, "") + XCTAssert(result.output.contains("Could not get package data, swift package dump-package failed")) + } + + func testNoDependencies() { + let folder = createEmptySwiftPackage() + let result = outputFromExecutionWith(arguments: ["update", folder.path]) + XCTAssertEqual(result.exitCode, 0) + XCTAssertEqual(result.errorOutput, "") + XCTAssertEqual(result.output, "Everything is already up-to-date!") + } + + func testDefaultCommand() { + let folder = createEmptySwiftPackage() + let result = outputFromExecutionWith(arguments: [folder.path]) + XCTAssertEqual(result.exitCode, 0) + XCTAssertEqual(result.errorOutput, "") + XCTAssertEqual(result.output, "Everything is already up-to-date!") + } + + func testNoDependenciesKeepRequirements() { + let folder = createEmptySwiftPackage() + let result = outputFromExecutionWith(arguments: ["update", folder.path, "--keep-requirements"]) + XCTAssertEqual(result.exitCode, 0) + XCTAssertEqual(result.errorOutput, "") + XCTAssertEqual(result.output, "Everything is already up-to-date!") + } + + func createEmptySwiftPackage() -> URL { + let folder = emptyFolderURL() + let packageSwift = temporaryFileURL(in: folder, name: "Package.swift") + createFile(at: packageSwift, content: TestUtils.emptyPackageSwiftFileContent) + let packageResolved = temporaryFileURL(in: folder, name: "Package.resolved") + createFile(at: packageResolved, content: TestUtils.emptyPackageResolvedFileContent) + let sourceFile = temporaryFileURL(in: folder.appendingPathComponent("Sources/Name"), name: "Name.swift") + createFile(at: sourceFile, content: "") + + return folder + } + +} diff --git a/Tests/SwiftDependencyUpdaterLibraryTests/SwiftPackageTests.swift b/Tests/SwiftDependencyUpdaterLibraryTests/SwiftPackageTests.swift new file mode 100644 index 0000000..a69c92a --- /dev/null +++ b/Tests/SwiftDependencyUpdaterLibraryTests/SwiftPackageTests.swift @@ -0,0 +1,179 @@ +import Rainbow +import Releases +@testable import SwiftDependencyUpdaterLibrary +import XCTest + +class SwiftPackageTests: XCTestCase { + + func testEmptyFolder() { + let folder = emptyFolderURL() + let swiftPackage = SwiftPackage(in: folder) + let update = Update.withChangingRequirements(try! Version(string: "2.1.2")) + let resolvedVersion = TestUtils.resolvedVersion("1.2.3") + let dependency = Dependency(name: "ABC", url: URL(string: "https://github.com/Name/abc.git")!, requirement: nil, resolvedVersion: resolvedVersion, update: update) + #if os(Linux) + assert( + try swiftPackage.performUpdate(update, of: dependency), + throws: SwiftPackageError.readFailed("The operation could not be completed. No such file or directory") + ) + #else + assert( + try swiftPackage.performUpdate(update, of: dependency), + throws: SwiftPackageError.readFailed("The file “Package.swift” couldn’t be opened because there is no such file.") + ) + #endif + } + + func testInvalidFile() { + let folder = emptyFolderURL() + let file = temporaryFileURL(in: folder, name: "Package.swift") + createFile(at: file, content: "\n") + let swiftPackage = SwiftPackage(in: folder) + let update = Update.withChangingRequirements(try! Version(string: "2.1.2")) + let resolvedVersion = TestUtils.resolvedVersion("1.2.3") + let dependency = Dependency(name: "ABC", url: URL(string: "https://github.com/Name/abc.git")!, requirement: nil, resolvedVersion: resolvedVersion, update: update) + assert( + try swiftPackage.performUpdate(update, of: dependency), + throws: SwiftPackageError.resultCountMismatch(dependency.name, 0) + ) + } + + func testInvalidUpdate() { + let folder = emptyFolderURL() + let swiftPackage = SwiftPackage(in: folder) + let update = Update.withoutChangingRequirements(try! Version(string: "1.2.4")) + let resolvedVersion = TestUtils.resolvedVersion("1.2.3") + let dependency = Dependency(name: "ABC", url: URL(string: "https://github.com/Name/abc.git")!, requirement: nil, resolvedVersion: resolvedVersion, update: update) + assert( + try swiftPackage.performUpdate(update, of: dependency), + throws: SwiftPackageError.invalidUpdate(dependency.name, update) + ) + + } + + func testUpdateUpToNextMinor() { + let (swiftPackage, file) = setUpSamplePackage() + let update = Update.withChangingRequirements(try! Version(string: "1.2.3")) + let resolvedVersion = TestUtils.resolvedVersion("0.3.3") + let dependency = Dependency(name: "a", url: URL(string: "https://github.com/a/a")!, requirement: nil, resolvedVersion: resolvedVersion, update: update) + + XCTAssertFalse(try swiftPackage.performUpdate(update, of: dependency)) + + XCTAssert(try! String(contentsOf: file, encoding: .utf8).contains(""" + .package( + url: "https://github.com/a/a", + .upToNextMinor(from: "1.2.3") + ), + """)) + } + + func testUpdateUpToNextMajor() { + let (swiftPackage, file) = setUpSamplePackage() + let update = Update.withChangingRequirements(try! Version(string: "3.0.3")) + let resolvedVersion = TestUtils.resolvedVersion("2.5.3") + let dependency = Dependency(name: "b", url: URL(string: "https://github.com/b/b.git")!, requirement: nil, resolvedVersion: resolvedVersion, update: update) + + XCTAssertFalse(try swiftPackage.performUpdate(update, of: dependency)) + + XCTAssert(try! String(contentsOf: file, encoding: .utf8).contains(""" + .package( + url: "https://github.com/b/b.git", + .upToNextMajor(from: "3.0.3") + ), + """)) + } + + func testUpdateExact() { + let (swiftPackage, file) = setUpSamplePackage() + let update = Update.withChangingRequirements(try! Version(string: "0.1.9")) + let resolvedVersion = TestUtils.resolvedVersion("0.1.8") + let dependency = Dependency(name: "c", url: URL(string: "https://github.com/c/c.git")!, requirement: nil, resolvedVersion: resolvedVersion, update: update) + + XCTAssertFalse(try swiftPackage.performUpdate(update, of: dependency)) + + XCTAssert(try! String(contentsOf: file, encoding: .utf8).contains(""" + .package( + url: "https://github.com/c/c.git", + .exact("0.1.9") + ), + """)) + } + + func testUpdateFrom() { + let (swiftPackage, file) = setUpSamplePackage() + let update = Update.withChangingRequirements(try! Version(string: "2.1.9")) + let resolvedVersion = TestUtils.resolvedVersion("1.3.8") + let dependency = Dependency(name: "f", url: URL(string: "https://github.com/f/f.git")!, requirement: nil, resolvedVersion: resolvedVersion, update: update) + + XCTAssertFalse(try swiftPackage.performUpdate(update, of: dependency)) + + XCTAssert(try! String(contentsOf: file, encoding: .utf8).contains(""" + .package( + url: "https://github.com/f/f.git", + from: "2.1.9" + ), + """)) + } + + func testUpdateRange() { + let (swiftPackage, file) = setUpSamplePackage() + let update = Update.withChangingRequirements(try! Version(string: "1.2.6")) + let resolvedVersion = TestUtils.resolvedVersion("1.2.5") + let dependency = Dependency(name: "g", url: URL(string: "https://github.com/g/g.git")!, requirement: nil, resolvedVersion: resolvedVersion, update: update) + + XCTAssertTrue(try swiftPackage.performUpdate(update, of: dependency)) + + XCTAssert(try! String(contentsOf: file, encoding: .utf8).contains(""" + .package( + url: "https://github.com/g/g.git", + "1.2.3"..<"1.2.7" + ), + """)) + } + + func testUpdateClosedRange() { + let (swiftPackage, file) = setUpSamplePackage() + let update = Update.withChangingRequirements(try! Version(string: "3.1.1")) + let resolvedVersion = TestUtils.resolvedVersion("2.2.6") + let dependency = Dependency(name: "h", url: URL(string: "https://github.com/h/h.git")!, requirement: nil, resolvedVersion: resolvedVersion, update: update) + + XCTAssertTrue(try swiftPackage.performUpdate(update, of: dependency)) + + XCTAssert(try! String(contentsOf: file, encoding: .utf8).contains(""" + .package( + url: "https://github.com/h/h.git", + "2.2.3"..."3.1.1" + ), + """)) + } + + func testSwiftPackageErrorString() { + let originalValue = Rainbow.enabled + Rainbow.enabled = false + + XCTAssertEqual( + "\(SwiftPackageError.invalidUpdate("abc", Update.withoutChangingRequirements(try! Version(string: "0.1.2"))).localizedDescription)", + "Invalid update for abc: 0.1.2 (Without changing requirements)" + ) + XCTAssertEqual( + "\(SwiftPackageError.resultCountMismatch("abc", 2).localizedDescription)", + "Finding version requirement in Package.swift failed for abc: Got 2 instead of 1 result" + ) + XCTAssertEqual( + "\(SwiftPackageError.noResultMatch("name", ["", "abc", nil]).localizedDescription)", + "Finding version requirement in Package.swift failed for name. Findings: \(["", "abc", nil])" + ) + XCTAssertEqual( "\(SwiftPackageError.readFailed("err").localizedDescription)", "Failed to read Package.swift file: err") + XCTAssertEqual( "\(SwiftPackageError.writeFailed("err").localizedDescription)", "Failed to write Package.swift file: err") + + Rainbow.enabled = originalValue + } + + func setUpSamplePackage() -> (SwiftPackage, URL) { + let folder = emptyFolderURL() + let file = temporaryFileURL(in: folder, name: "Package.swift") + createFile(at: file, content: TestUtils.packageSwiftFileContent) + return (SwiftPackage(in: folder), file) + } + +} diff --git a/Tests/SwiftDependencyUpdaterLibraryTests/TestUtils.swift b/Tests/SwiftDependencyUpdaterLibraryTests/TestUtils.swift index 5170d08..1098050 100644 --- a/Tests/SwiftDependencyUpdaterLibraryTests/TestUtils.swift +++ b/Tests/SwiftDependencyUpdaterLibraryTests/TestUtils.swift @@ -1,3 +1,6 @@ +import Foundation +@testable import SwiftDependencyUpdaterLibrary + enum TestUtils { static let emptyPackageResolvedFileContent = """ @@ -134,4 +137,10 @@ enum TestUtils { ) """ + static func resolvedVersion(_ version: String) -> ResolvedVersion { + let decoder = JSONDecoder() + let data = "{\"revision\": \"abc\", \"branch\": null, \"version\": \"\(version)\"}".data(using: .utf8)! + return try! decoder.decode(ResolvedVersion.self, from: data) + } + } From ea5783d769511a6d825a9c4817a2ef8a2a2698a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Steffen=20K=C3=B6tte?= Date: Sun, 23 May 2021 00:31:35 -0700 Subject: [PATCH 4/4] remove unnecessary empty line --- Sources/SwiftDependencyUpdaterLibrary/SwiftPackage.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/SwiftDependencyUpdaterLibrary/SwiftPackage.swift b/Sources/SwiftDependencyUpdaterLibrary/SwiftPackage.swift index 8e4ed4c..6531e6e 100644 --- a/Sources/SwiftDependencyUpdaterLibrary/SwiftPackage.swift +++ b/Sources/SwiftDependencyUpdaterLibrary/SwiftPackage.swift @@ -67,7 +67,6 @@ struct SwiftPackage { } catch { throw SwiftPackageError.readFailed(error.localizedDescription) } - } private func write(_ string: String) throws {