diff --git a/Sources/TuistCore/Utils/EnvironmentController.swift b/Sources/TuistCore/Utils/EnvironmentController.swift index a78624319a9..36539a06378 100644 --- a/Sources/TuistCore/Utils/EnvironmentController.swift +++ b/Sources/TuistCore/Utils/EnvironmentController.swift @@ -1,6 +1,5 @@ import Basic import Foundation -import TuistCore /// Protocol that defines the interface of a local environment controller. /// It manages the local directory where tuistenv stores the tuist versions and user settings. diff --git a/Sources/TuistEnvKit/Commands/BundleCommand.swift b/Sources/TuistEnvKit/Commands/BundleCommand.swift index 4be520b16a4..970632a2ecc 100644 --- a/Sources/TuistEnvKit/Commands/BundleCommand.swift +++ b/Sources/TuistEnvKit/Commands/BundleCommand.swift @@ -81,7 +81,7 @@ final class BundleCommand: Command { // Installing if !fileHandler.exists(versionPath) { printer.print("Version \(version) not available locally. Installing...") - try installer.install(version: version) + try installer.install(version: version, force: false) } // Copying diff --git a/Sources/TuistEnvKit/Commands/CommandRunner.swift b/Sources/TuistEnvKit/Commands/CommandRunner.swift index 828406adc08..d74e6f64c34 100644 --- a/Sources/TuistEnvKit/Commands/CommandRunner.swift +++ b/Sources/TuistEnvKit/Commands/CommandRunner.swift @@ -93,7 +93,7 @@ class CommandRunner: CommandRunning { if let highgestVersion = versionsController.semverVersions().last?.description { version = highgestVersion } else { - try updater.update() + try updater.update(force: false) guard let highgestVersion = versionsController.semverVersions().last?.description else { throw CommandRunnerError.versionNotFound } @@ -107,7 +107,7 @@ class CommandRunner: CommandRunning { func runVersion(_ version: String) throws { if !versionsController.versions().contains(where: { $0.description == version }) { printer.print("Version \(version) not found locally. Installing...") - try installer.install(version: version) + try installer.install(version: version, force: false) } let path = versionsController.path(version: version) diff --git a/Sources/TuistEnvKit/Commands/InstallCommand.swift b/Sources/TuistEnvKit/Commands/InstallCommand.swift index c48b7704751..3ec2b63a803 100644 --- a/Sources/TuistEnvKit/Commands/InstallCommand.swift +++ b/Sources/TuistEnvKit/Commands/InstallCommand.swift @@ -2,19 +2,33 @@ import Foundation import TuistCore import Utility +/// Command that installs new versions of Tuist in the system. final class InstallCommand: Command { // MARK: - Command + /// Command name. static var command: String = "install" + + /// Command description. static var overview: String = "Installs a version of tuist" // MARK: - Attributes + /// Controller to manage system versions. private let versionsController: VersionsControlling + + /// Printer instance to output messages to the user. private let printer: Printing + + /// Installer instance to run the installation. private let installer: Installing + + /// Version argument to specify the version that will be installed. let versionArgument: PositionalArgument + /// Force argument (-f). When passed, it re-installs the version compiling it from the source. + let forceArgument: OptionArgument + // MARK: - Init convenience init(parser: ArgumentParser) { @@ -37,15 +51,24 @@ final class InstallCommand: Command { kind: String.self, optional: false, usage: "The version of tuist to be installed") + forceArgument = subParser.add(option: "--force", + shortName: "-f", + kind: Bool.self, + usage: "Re-installs the version compiling it from the source", completion: nil) } + /// Runs the install command. + /// + /// - Parameter result: Result obtained from parsing the CLI arguments. + /// - Throws: An error if the installation process fails. func run(with result: ArgumentParser.Result) throws { + let force = result.get(forceArgument) ?? false let version = result.get(versionArgument)! let versions = versionsController.versions().map({ $0.description }) if versions.contains(version) { printer.print(warning: "Version \(version) already installed, skipping") return } - try installer.install(version: version) + try installer.install(version: version, force: force) } } diff --git a/Sources/TuistEnvKit/Commands/UpdateCommand.swift b/Sources/TuistEnvKit/Commands/UpdateCommand.swift index 59274f9319f..f4895227350 100644 --- a/Sources/TuistEnvKit/Commands/UpdateCommand.swift +++ b/Sources/TuistEnvKit/Commands/UpdateCommand.swift @@ -2,39 +2,63 @@ import Foundation import TuistCore import Utility +/// Command that updates the version of Tuist in the environment. final class UpdateCommand: Command { // MARK: - Command + /// Name of the command. static var command: String = "update" + + /// Description of the command. static var overview: String = "Installs the latest version if it's not already installed" // MARK: - Attributes - private let versionsController: VersionsControlling + /// Updater instance that runs the update. private let updater: Updating + + /// Printer instance to output updates during the process. private let printer: Printing + /// Force argument (-f). When passed, it re-installs the latest version compiling it from the source. + let forceArgument: OptionArgument + // MARK: - Init + /// Initializes the update command. + /// + /// - Parameter parser: Argument parser where the command should be registered. convenience init(parser: ArgumentParser) { self.init(parser: parser, - versionsController: VersionsController(), updater: Updater(), printer: Printer()) } + /// Initializes the update command. + /// + /// - Parameters: + /// - parser: Argument parser where the command should be registered. + /// - updater: Updater instance that runs the update. + /// - printer: Printer instance to output updates during the process. init(parser: ArgumentParser, - versionsController: VersionsControlling, updater: Updating, - printer: Printer) { - parser.add(subparser: UpdateCommand.command, overview: UpdateCommand.overview) - self.versionsController = versionsController + printer: Printing) { + let subParser = parser.add(subparser: UpdateCommand.command, overview: UpdateCommand.overview) self.printer = printer self.updater = updater + forceArgument = subParser.add(option: "--force", + shortName: "-f", + kind: Bool.self, + usage: "Re-installs the latest version compiling it from the source", completion: nil) } - func run(with _: ArgumentParser.Result) throws { + /// Runs the update command. + /// + /// - Parameter result: Result obtained from parsing the CLI arguments. + /// - Throws: An error if the update process fails. + func run(with result: ArgumentParser.Result) throws { + let force = result.get(forceArgument) ?? false printer.print(section: "Checking for updates...") - try updater.update() + try updater.update(force: force) } } diff --git a/Sources/TuistEnvKit/Installer/Installer.swift b/Sources/TuistEnvKit/Installer/Installer.swift index 18718232da9..21c41602102 100644 --- a/Sources/TuistEnvKit/Installer/Installer.swift +++ b/Sources/TuistEnvKit/Installer/Installer.swift @@ -2,10 +2,21 @@ import Basic import Foundation import TuistCore +/// Protocol that defines the interface of an instance that can install versions of Tuist. protocol Installing: AnyObject { - func install(version: String) throws + /// It installs a version of Tuist in the local environment. + /// + /// - Parameters: + /// - version: Version to be installed. + /// - force: When true, it ignores the Swift version and compiles it from the source. + /// - Throws: An error if the installation fails. + func install(version: String, force: Bool) throws } +/// Error thrown by the installer. +/// +/// - versionNotFound: When the specified version cannot be found. +/// - incompatibleSwiftVersion: When the environment Swift version is incompatible with the Swift version Tuist has been compiled with. enum InstallerError: FatalError, Equatable { case versionNotFound(String) case incompatibleSwiftVersion(local: String, expected: String) @@ -38,6 +49,7 @@ enum InstallerError: FatalError, Equatable { } } +/// Class that manages the installation of Tuist versions. final class Installer: Installing { // MARK: - Attributes @@ -66,12 +78,20 @@ final class Installer: Installing { // MARK: - Installing - func install(version: String) throws { + func install(version: String, force: Bool) throws { let temporaryDirectory = try TemporaryDirectory(removeTreeOnDeinit: true) - try install(version: version, temporaryDirectory: temporaryDirectory) + try install(version: version, temporaryDirectory: temporaryDirectory, force: force) } - func install(version: String, temporaryDirectory: TemporaryDirectory) throws { + func install(version: String, temporaryDirectory: TemporaryDirectory, force: Bool = false) throws { + // We ignore the Swift version and install from the soruce code + if force { + printer.print("Forcing the installation of \(version) from the source code") + try installFromSource(version: version, + temporaryDirectory: temporaryDirectory) + return + } + try verifySwiftVersion(version: version) var bundleURL: URL? @@ -184,10 +204,11 @@ final class Installer: Installing { verbose: false, environment: System.userEnvironment).throwIfError() - // Copying files - if !fileHandler.exists(installationDirectory) { - try system.capture("/bin/mkdir", arguments: installationDirectory.asString, verbose: false, environment: nil).throwIfError() + if fileHandler.exists(installationDirectory) { + try fileHandler.delete(installationDirectory) } + try fileHandler.createFolder(installationDirectory) + try buildCopier.copy(from: buildDirectory, to: installationDirectory) diff --git a/Sources/TuistEnvKit/Updater/Updater.swift b/Sources/TuistEnvKit/Updater/Updater.swift index d5785f6ba4d..74c7e0be344 100644 --- a/Sources/TuistEnvKit/Updater/Updater.swift +++ b/Sources/TuistEnvKit/Updater/Updater.swift @@ -2,7 +2,7 @@ import Foundation import TuistCore protocol Updating: AnyObject { - func update() throws + func update(force: Bool) throws } final class Updater: Updating { @@ -27,24 +27,27 @@ final class Updater: Updating { // MARK: - Internal - func update() throws { + func update(force: Bool) throws { let releases = try githubClient.releases() guard let highestRemoteVersion = releases.map({ $0.version }).sorted().last else { - print("No remote versions found") + printer.print("No remote versions found") return } - if let highestLocalVersion = versionsController.semverVersions().sorted().last { + if force { + printer.print("Forcing the update of version \(highestRemoteVersion)") + try installer.install(version: highestRemoteVersion.description, force: true) + } else if let highestLocalVersion = versionsController.semverVersions().sorted().last { if highestRemoteVersion <= highestLocalVersion { printer.print("There are no updates available") } else { printer.print("Installing new version available \(highestRemoteVersion)") - try installer.install(version: highestRemoteVersion.description) + try installer.install(version: highestRemoteVersion.description, force: false) } } else { printer.print("No local versions available. Installing the latest version \(highestRemoteVersion)") - try installer.install(version: highestRemoteVersion.description) + try installer.install(version: highestRemoteVersion.description, force: false) } } } diff --git a/Tests/TuistEnvKitTests/Commands/BundleCommandTests.swift b/Tests/TuistEnvKitTests/Commands/BundleCommandTests.swift index 31c8fefc041..9a45f7faac1 100644 --- a/Tests/TuistEnvKitTests/Commands/BundleCommandTests.swift +++ b/Tests/TuistEnvKitTests/Commands/BundleCommandTests.swift @@ -68,8 +68,8 @@ final class BundleCommandTests: XCTestCase { let tuistVersionPath = fileHandler.currentPath.appending(component: Constants.versionFileName) try "3.2.1".write(to: tuistVersionPath.url, atomically: true, encoding: .utf8) - installer.installStub = { versionToInstall in - let versionPath = self.versionsController.path(version: versionToInstall) + installer.installStub = { version, _ in + let versionPath = self.versionsController.path(version: version) try self.fileHandler.createFolder(versionPath) try Data().write(to: versionPath.appending(component: "test").url) } @@ -102,8 +102,8 @@ final class BundleCommandTests: XCTestCase { try "3.2.1".write(to: tuistVersionPath.url, atomically: true, encoding: .utf8) - installer.installStub = { versionToInstall in - let versionPath = self.versionsController.path(version: versionToInstall) + installer.installStub = { version, _ in + let versionPath = self.versionsController.path(version: version) try self.fileHandler.createFolder(versionPath) try Data().write(to: versionPath.appending(component: "test").url) } diff --git a/Tests/TuistEnvKitTests/Commands/CommandRunnerTests.swift b/Tests/TuistEnvKitTests/Commands/CommandRunnerTests.swift index d64ad174b99..79cf655085d 100644 --- a/Tests/TuistEnvKitTests/Commands/CommandRunnerTests.swift +++ b/Tests/TuistEnvKitTests/Commands/CommandRunnerTests.swift @@ -84,8 +84,8 @@ final class CommandRunnerTests: XCTestCase { versionResolver.resolveStub = { _ in ResolvedVersion.versionFile(self.fileHandler.currentPath, "3.2.1") } - var installedVersion: String? - installer.installStub = { installedVersion = $0 } + var installArgs: [(version: String, force: Bool)] = [] + installer.installStub = { version, force in installArgs.append((version: version, force: force)) } system.stub(args: [binaryPath.asString, "--help"], stderror: nil, @@ -97,7 +97,8 @@ final class CommandRunnerTests: XCTestCase { XCTAssertEqual(printer.printArgs.count, 2) XCTAssertEqual(printer.printArgs.first, "Using version 3.2.1 defined at \(fileHandler.currentPath.asString)") XCTAssertEqual(printer.printArgs.last, "Version 3.2.1 not found locally. Installing...") - XCTAssertEqual(installedVersion, "3.2.1") + XCTAssertEqual(installArgs.count, 1) + XCTAssertEqual(installArgs.first?.version, "3.2.1") } func test_when_version_file_and_install_fails() throws { @@ -106,7 +107,7 @@ final class CommandRunnerTests: XCTestCase { versionResolver.resolveStub = { _ in ResolvedVersion.versionFile(self.fileHandler.currentPath, "3.2.1") } let error = NSError.test() - installer.installStub = { _ in throw error } + installer.installStub = { _, _ in throw error } XCTAssertThrowsError(try subject.run()) { XCTAssertEqual($0 as NSError, error) @@ -159,7 +160,7 @@ final class CommandRunnerTests: XCTestCase { versionResolver.resolveStub = { _ in ResolvedVersion.undefined } versionsController.semverVersionsStub = [] - updater.updateStub = { + updater.updateStub = { _ in self.versionsController.semverVersionsStub = [Version(string: "3.2.1")!] } @@ -182,7 +183,7 @@ final class CommandRunnerTests: XCTestCase { versionsController.semverVersionsStub = [] let error = NSError.test() - updater.updateStub = { + updater.updateStub = { _ in throw error } diff --git a/Tests/TuistEnvKitTests/Commands/InstallCommandTests.swift b/Tests/TuistEnvKitTests/Commands/InstallCommandTests.swift index 6c22e2b5b7e..c3f3be6992e 100644 --- a/Tests/TuistEnvKitTests/Commands/InstallCommandTests.swift +++ b/Tests/TuistEnvKitTests/Commands/InstallCommandTests.swift @@ -54,11 +54,28 @@ final class InstallCommandTests: XCTestCase { versionsController.versionsStub = [] - var installedVersion: String? - installer.installStub = { installedVersion = $0 } + var installArgs: [(version: String, force: Bool)] = [] + installer.installStub = { version, force in installArgs.append((version: version, force: force)) } try subject.run(with: result) - XCTAssertEqual(installedVersion, "3.2.1") + XCTAssertEqual(installArgs.count, 1) + XCTAssertEqual(installArgs.first?.version, "3.2.1") + XCTAssertEqual(installArgs.first?.force, false) + } + + func test_run_when_force() throws { + let result = try parser.parse(["install", "3.2.1", "-f"]) + + versionsController.versionsStub = [] + + var installArgs: [(version: String, force: Bool)] = [] + installer.installStub = { version, force in installArgs.append((version: version, force: force)) } + + try subject.run(with: result) + + XCTAssertEqual(installArgs.count, 1) + XCTAssertEqual(installArgs.first?.version, "3.2.1") + XCTAssertEqual(installArgs.first?.force, true) } } diff --git a/Tests/TuistEnvKitTests/Commands/UpdateCommandTests.swift b/Tests/TuistEnvKitTests/Commands/UpdateCommandTests.swift index 0edb42c6495..f89d01cd1fa 100644 --- a/Tests/TuistEnvKitTests/Commands/UpdateCommandTests.swift +++ b/Tests/TuistEnvKitTests/Commands/UpdateCommandTests.swift @@ -1,9 +1,26 @@ import Foundation -@testable import TuistEnvKit -import Utility import XCTest +@testable import TuistCoreTesting +@testable import TuistEnvKit +@testable import Utility + final class UpdateCommandTests: XCTestCase { + var parser: ArgumentParser! + var subject: UpdateCommand! + var updater: MockUpdater! + var printer: MockPrinter! + + override func setUp() { + super.setUp() + parser = ArgumentParser(usage: "test", overview: "overview") + updater = MockUpdater() + printer = MockPrinter() + subject = UpdateCommand(parser: parser, + updater: updater, + printer: printer) + } + func test_command() { XCTAssertEqual(UpdateCommand.command, "update") } @@ -11,4 +28,24 @@ final class UpdateCommandTests: XCTestCase { func test_overview() { XCTAssertEqual(UpdateCommand.overview, "Installs the latest version if it's not already installed") } + + func test_init_registers_the_command() { + XCTAssertEqual(parser.subparsers.count, 1) + XCTAssertEqual(parser.subparsers.first?.key, UpdateCommand.command) + XCTAssertEqual(parser.subparsers.first?.value.overview, UpdateCommand.overview) + } + + func test_run() throws { + let result = try parser.parse(["update", "-f"]) + + var updateCalls: [Bool] = [] + updater.updateStub = { force in + updateCalls.append(force) + } + + try subject.run(with: result) + + XCTAssertEqual(printer.printSectionArgs, ["Checking for updates..."]) + XCTAssertEqual(updateCalls, [true]) + } } diff --git a/Tests/TuistEnvKitTests/Installer/InstallerTests.swift b/Tests/TuistEnvKitTests/Installer/InstallerTests.swift index 1bf5a310c29..fe80f3c3aee 100644 --- a/Tests/TuistEnvKitTests/Installer/InstallerTests.swift +++ b/Tests/TuistEnvKitTests/Installer/InstallerTests.swift @@ -180,9 +180,10 @@ final class InstallerTests: XCTestCase { func test_install_when_no_bundled_release() throws { let version = "3.2.1" let temporaryDirectory = try TemporaryDirectory(removeTreeOnDeinit: true) + let installationDirectory = fileHandler.currentPath.appending(component: "3.2.1") versionsController.installStub = { _, closure in - try closure(self.fileHandler.currentPath) + try closure(installationDirectory) } system.stub(args: [ @@ -223,13 +224,6 @@ final class InstallerTests: XCTestCase { stderror: nil, stdout: nil, exitstatus: 0) - system.stub(args: [ - "/bin/mkdir", - fileHandler.currentPath.asString, - ], - stderror: nil, - stdout: nil, - exitstatus: 0) try subject.install(version: version, temporaryDirectory: temporaryDirectory) @@ -241,7 +235,68 @@ final class InstallerTests: XCTestCase { XCTAssertEqual(printer.printArgs[1], "Building using Swift (it might take a while)") XCTAssertEqual(printer.printArgs[2], "Version 3.2.1 installed") - let tuistVersionPath = fileHandler.currentPath.appending(component: Constants.versionFileName) + let tuistVersionPath = installationDirectory.appending(component: Constants.versionFileName) + XCTAssertTrue(fileHandler.exists(tuistVersionPath)) + } + + func test_install_when_force() throws { + let version = "3.2.1" + let temporaryDirectory = try TemporaryDirectory(removeTreeOnDeinit: true) + let installationDirectory = fileHandler.currentPath.appending(component: "3.2.1") + + versionsController.installStub = { _, closure in + try closure(installationDirectory) + } + + system.stub(args: [ + "/usr/bin/env", "git", + "clone", Constants.gitRepositoryURL, + temporaryDirectory.path.asString, + ], + stderror: nil, + stdout: nil, + exitstatus: 0) + system.stub(args: [ + "/usr/bin/env", "git", "-C", temporaryDirectory.path.asString, + "checkout", version, + ], + stderror: nil, + stdout: nil, + exitstatus: 0) + system.stub(args: ["/usr/bin/xcrun", "-f", "swift"], + stderror: nil, + stdout: "/path/to/swift", + exitstatus: 0) + system.stub(args: [ + "/path/to/swift", "build", + "--product", "tuist", + "--package-path", temporaryDirectory.path.asString, + "--configuration", "release", + "-Xswiftc", "-static-stdlib", + ], + stderror: nil, + stdout: nil, + exitstatus: 0) + system.stub(args: [ + "/path/to/swift", "build", + "--product", "ProjectDescription", + "--package-path", temporaryDirectory.path.asString, + "--configuration", "release", + ], + stderror: nil, + stdout: nil, + exitstatus: 0) + + try subject.install(version: version, temporaryDirectory: temporaryDirectory, force: true) + + XCTAssertEqual(printer.printArgs.count, 4) + + XCTAssertEqual(printer.printArgs[0], "Forcing the installation of 3.2.1 from the source code") + XCTAssertEqual(printer.printArgs[1], "Pulling source code") + XCTAssertEqual(printer.printArgs[2], "Building using Swift (it might take a while)") + XCTAssertEqual(printer.printArgs[3], "Version 3.2.1 installed") + + let tuistVersionPath = installationDirectory.appending(component: Constants.versionFileName) XCTAssertTrue(fileHandler.exists(tuistVersionPath)) } diff --git a/Tests/TuistEnvKitTests/Installer/Mocks/MockInstaller.swift b/Tests/TuistEnvKitTests/Installer/Mocks/MockInstaller.swift index 9dfbe270d2b..19b4a93185d 100644 --- a/Tests/TuistEnvKitTests/Installer/Mocks/MockInstaller.swift +++ b/Tests/TuistEnvKitTests/Installer/Mocks/MockInstaller.swift @@ -3,10 +3,10 @@ import Foundation final class MockInstaller: Installing { var installCallCount: UInt = 0 - var installStub: ((String) throws -> Void)? + var installStub: ((String, Bool) throws -> Void)? - func install(version: String) throws { + func install(version: String, force: Bool) throws { installCallCount += 1 - try installStub?(version) + try installStub?(version, force) } } diff --git a/Tests/TuistEnvKitTests/Updater/Mocks/MockUpdater.swift b/Tests/TuistEnvKitTests/Updater/Mocks/MockUpdater.swift index 5069fd16c6a..3127ce78e78 100644 --- a/Tests/TuistEnvKitTests/Updater/Mocks/MockUpdater.swift +++ b/Tests/TuistEnvKitTests/Updater/Mocks/MockUpdater.swift @@ -3,10 +3,10 @@ import Foundation final class MockUpdater: Updating { var updateCallCount: UInt = 0 - var updateStub: (() throws -> Void)? + var updateStub: ((Bool) throws -> Void)? - func update() throws { + func update(force: Bool) throws { updateCallCount += 1 - try updateStub?() + try updateStub?(force) } } diff --git a/Tests/TuistEnvKitTests/Updater/UpdaterTests.swift b/Tests/TuistEnvKitTests/Updater/UpdaterTests.swift index 4a5282bf89c..11fa9db6e08 100644 --- a/Tests/TuistEnvKitTests/Updater/UpdaterTests.swift +++ b/Tests/TuistEnvKitTests/Updater/UpdaterTests.swift @@ -1,10 +1,80 @@ import Foundation -@testable import TuistEnvKit import XCTest +@testable import TuistCoreTesting +@testable import TuistEnvKit + final class UpdaterTests: XCTestCase { var githubClient: MockGitHubClient! var versionsController: MockVersionsController! var installer: MockInstaller! + var printer: MockPrinter! var subject: Updater! + + override func setUp() { + githubClient = MockGitHubClient() + versionsController = try! MockVersionsController() + installer = MockInstaller() + printer = MockPrinter() + subject = Updater(githubClient: githubClient, + versionsController: versionsController, + installer: installer, + printer: printer) + } + + func test_update_when_no_remote_releases() throws { + githubClient.releasesStub = { [] } + try subject.update(force: false) + XCTAssertEqual(printer.printArgs, ["No remote versions found"]) + } + + func test_update_when_force() throws { + githubClient.releasesStub = { [Release.test(version: "3.2.1")] } + var installArgs: [(version: String, force: Bool)] = [] + installer.installStub = { version, force in installArgs.append((version: version, force: force)) } + + try subject.update(force: true) + + XCTAssertEqual(printer.printArgs, ["Forcing the update of version 3.2.1"]) + XCTAssertEqual(installArgs.count, 1) + XCTAssertEqual(installArgs.first?.version, "3.2.1") + XCTAssertEqual(installArgs.first?.force, true) + } + + func test_update_when_there_are_no_updates() throws { + versionsController.semverVersionsStub = ["3.2.1"] + githubClient.releasesStub = { [Release.test(version: "3.2.1")] } + + try subject.update(force: false) + + XCTAssertEqual(printer.printArgs, ["There are no updates available"]) + } + + func test_update_when_there_are_updates() throws { + versionsController.semverVersionsStub = ["3.1.1"] + githubClient.releasesStub = { [Release.test(version: "3.2.1")] } + var installArgs: [(version: String, force: Bool)] = [] + installer.installStub = { version, force in installArgs.append((version: version, force: force)) } + + try subject.update(force: false) + + XCTAssertEqual(printer.printArgs, ["Installing new version available 3.2.1"]) + XCTAssertEqual(installArgs.count, 1) + XCTAssertEqual(installArgs.first?.version, "3.2.1") + XCTAssertEqual(installArgs.first?.force, false) + } + + func test_update_when_no_local_versions_available() throws { + versionsController.semverVersionsStub = [] + githubClient.releasesStub = { [Release.test(version: "3.2.1")] } + var installArgs: [(version: String, force: Bool)] = [] + installer.installStub = { version, force in installArgs.append((version: version, force: force)) } + + try subject.update(force: false) + + XCTAssertEqual(printer.printArgs, ["No local versions available. Installing the latest version 3.2.1"]) + XCTAssertEqual(installArgs.count, 1) + XCTAssertEqual(installArgs.first?.version, "3.2.1") + XCTAssertEqual(installArgs.first?.force, false) + } }