Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Sources/Swiftly/Install.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,10 @@ struct Install: SwiftlyCommand {
}

mutating func run(_ ctx: SwiftlyCoreContext) async throws {
try await validateSwiftly(ctx)
let versionUpdateReminder = try await validateSwiftly(ctx)
defer {
versionUpdateReminder()
}

var config = try await Config.load(ctx)

Expand Down
6 changes: 5 additions & 1 deletion Sources/Swiftly/List.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@ struct List: SwiftlyCommand {
}

mutating func run(_ ctx: SwiftlyCoreContext) async throws {
try await validateSwiftly(ctx)
let versionUpdateReminder = try await validateSwiftly(ctx)
defer {
versionUpdateReminder()
}

let selector = try self.toolchainSelector.map { input in
try ToolchainSelector(parsing: input)
}
Expand Down
6 changes: 5 additions & 1 deletion Sources/Swiftly/ListAvailable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,11 @@ struct ListAvailable: SwiftlyCommand {
}

mutating func run(_ ctx: SwiftlyCoreContext) async throws {
try await validateSwiftly(ctx)
let versionUpdateReminder = try await validateSwiftly(ctx)
defer {
versionUpdateReminder()
}

let selector = try self.toolchainSelector.map { input in
try ToolchainSelector(parsing: input)
}
Expand Down
5 changes: 4 additions & 1 deletion Sources/Swiftly/Run.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ struct Run: SwiftlyCommand {
}

mutating func run(_ ctx: SwiftlyCoreContext) async throws {
try await validateSwiftly(ctx)
let versionUpdateReminder = try await validateSwiftly(ctx)
defer {
versionUpdateReminder()
}

// Handle the specific case where help is requested of the run subcommand
if command == ["--help"] {
Expand Down
2 changes: 1 addition & 1 deletion Sources/Swiftly/SelfUpdate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ struct SelfUpdate: SwiftlyCommand {
}

mutating func run(_ ctx: SwiftlyCoreContext) async throws {
try await validateSwiftly(ctx)
let _ = try await validateSwiftly(ctx)

let swiftlyBin = Swiftly.currentPlatform.swiftlyBinDir(ctx) / "swiftly"
guard try await fs.exists(atPath: swiftlyBin) else {
Expand Down
24 changes: 23 additions & 1 deletion Sources/Swiftly/Swiftly.swift
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ extension Data {
}

extension SwiftlyCommand {
public mutating func validateSwiftly(_ ctx: SwiftlyCoreContext) async throws {
public mutating func validateSwiftly(_ ctx: SwiftlyCoreContext) async throws -> () -> Void {
for requiredDir in Swiftly.requiredDirectories(ctx) {
guard try await fs.exists(atPath: requiredDir) else {
do {
Expand All @@ -107,5 +107,27 @@ extension SwiftlyCommand {

// Verify that the configuration exists and can be loaded
_ = try await Config.load(ctx)

let shouldUpdateSwiftly: Bool
if let swiftlyRelease = try? await ctx.httpClient.getCurrentSwiftlyRelease() {
shouldUpdateSwiftly = try swiftlyRelease.swiftlyVersion > SwiftlyCore.version
} else {
shouldUpdateSwiftly = false
}

return {
if shouldUpdateSwiftly {
let updateMessage = """
-----------------------------
A new release of swiftly is available.
Please run `swiftly self-update` to update.
-----------------------------\n
"""

if let data = updateMessage.data(using: .utf8) {
FileHandle.standardError.write(data)
}
}
}
}
}
6 changes: 5 additions & 1 deletion Sources/Swiftly/Uninstall.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,11 @@ struct Uninstall: SwiftlyCommand {
}

mutating func run(_ ctx: SwiftlyCoreContext) async throws {
try await validateSwiftly(ctx)
let versionUpdateReminder = try await validateSwiftly(ctx)
defer {
versionUpdateReminder()
}

let startingConfig = try await Config.load(ctx)

let toolchains: [ToolchainVersion]
Expand Down
6 changes: 5 additions & 1 deletion Sources/Swiftly/Update.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,11 @@ struct Update: SwiftlyCommand {
}

public mutating func run(_ ctx: SwiftlyCoreContext) async throws {
try await validateSwiftly(ctx)
let versionUpdateReminder = try await validateSwiftly(ctx)
defer {
versionUpdateReminder()
}

var config = try await Config.load(ctx)

guard let parameters = try await self.resolveUpdateParameters(ctx, &config) else {
Expand Down
6 changes: 5 additions & 1 deletion Sources/Swiftly/Use.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,11 @@ struct Use: SwiftlyCommand {
}

mutating func run(_ ctx: SwiftlyCoreContext) async throws {
try await validateSwiftly(ctx)
let versionUpdateReminder = try await validateSwiftly(ctx)
defer {
versionUpdateReminder()
}

var config = try await Config.load(ctx)

// This is the bare use command where we print the selected toolchain version (or the path to it)
Expand Down
32 changes: 19 additions & 13 deletions Tests/SwiftlyTests/ListTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,21 @@ import Testing
.oldReleaseSnapshot,
]

private static let swiftlyVersion = SwiftlyVersion(major: SwiftlyCore.version.major, minor: 0, patch: 0)

/// Constructs a mock home directory with the toolchains listed above installed and runs the provided closure within
/// the context of that home.
func runListTest(f: () async throws -> Void) async throws {
try await SwiftlyTests.withTestHome(name: Self.homeName) {
for toolchain in Set<ToolchainVersion>.allToolchains() {
try await SwiftlyTests.installMockedToolchain(toolchain: toolchain)
}
try await SwiftlyTests.withMockedSwiftlyVersion(latestSwiftlyVersion: Self.swiftlyVersion) {
for toolchain in Set<ToolchainVersion>.allToolchains() {
try await SwiftlyTests.installMockedToolchain(toolchain: toolchain)
}

try await SwiftlyTests.runCommand(Use.self, ["use", "latest"])
try await SwiftlyTests.runCommand(Use.self, ["use", "latest"])

try await f()
try await f()
}
}
}

Expand Down Expand Up @@ -155,16 +159,18 @@ import Testing

/// Tests that `list` properly handles the case where no toolchains have been installed yet.
@Test(.testHome(Self.homeName)) func listEmpty() async throws {
var toolchains = try await self.runList(selector: nil)
#expect(toolchains == [])
try await SwiftlyTests.withMockedSwiftlyVersion(latestSwiftlyVersion: Self.swiftlyVersion) {
var toolchains = try await self.runList(selector: nil)
#expect(toolchains == [])

toolchains = try await self.runList(selector: "5")
#expect(toolchains == [])
toolchains = try await self.runList(selector: "5")
#expect(toolchains == [])

toolchains = try await self.runList(selector: "main-snapshot")
#expect(toolchains == [])
toolchains = try await self.runList(selector: "main-snapshot")
#expect(toolchains == [])

toolchains = try await self.runList(selector: "5.7-snapshot")
#expect(toolchains == [])
toolchains = try await self.runList(selector: "5.7-snapshot")
#expect(toolchains == [])
}
}
}
5 changes: 3 additions & 2 deletions Tests/SwiftlyTests/PlatformTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import Testing
return (mockedToolchainFile, version, tmpDir)
}

@Test(.testHome()) func install() async throws {
@Test(.testHome(), .mockedSwiftlyVersion()) func install() async throws {
// GIVEN: a toolchain has been downloaded
var (mockedToolchainFile, version, tmpDir) = try await self.mockToolchainDownload(version: "5.7.1")
var cleanup = [tmpDir]
Expand Down Expand Up @@ -53,7 +53,7 @@ import Testing
#expect(2 == toolchains.count)
}

@Test(.testHome()) func uninstall() async throws {
@Test(.testHome(), .mockedSwiftlyVersion()) func uninstall() async throws {
// GIVEN: toolchains have been downloaded, and installed
var (mockedToolchainFile, version, tmpDir) = try await self.mockToolchainDownload(version: "5.8.0")
var cleanup = [tmpDir]
Expand Down Expand Up @@ -89,6 +89,7 @@ import Testing

#if os(macOS) || os(Linux)
@Test(
.mockedSwiftlyVersion(),
.mockHomeToolchains(),
arguments: [
"/a/b/c:SWIFTLY_BIN_DIR:/d/e/f",
Expand Down
4 changes: 2 additions & 2 deletions Tests/SwiftlyTests/RunTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Testing
static let homeName = "runTests"

/// Tests that the `run` command can switch between installed toolchains.
@Test(.mockHomeToolchains()) func runSelection() async throws {
@Test(.mockedSwiftlyVersion(), .mockHomeToolchains()) func runSelection() async throws {
// GIVEN: a set of installed toolchains
// WHEN: invoking the run command with a selector argument for that toolchain
var output = try await SwiftlyTests.runWithMockedIO(Run.self, ["run", "swift", "--version", "+\(ToolchainVersion.newStable.name)"])
Expand All @@ -35,7 +35,7 @@ import Testing
}

/// Tests the `run` command verifying that the environment is as expected
@Test(.mockHomeToolchains()) func runEnvironment() async throws {
@Test(.mockedSwiftlyVersion(), .mockHomeToolchains()) func runEnvironment() async throws {
// The toolchains directory should be the fist entry on the path
let output = try await SwiftlyTests.runWithMockedIO(Run.self, ["run", try await Swiftly.currentPlatform.getShell(), "-c", "echo $PATH"])
#expect(output.count == 1)
Expand Down
17 changes: 17 additions & 0 deletions Tests/SwiftlyTests/SwiftlyTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,23 @@ extension Trait where Self == TestHomeTrait {
static func testHome(_ name: String = "testHome") -> Self { Self(name) }
}

// extension Trait for mockedSwiftlyVersion
struct MockedSwiftlyVersionTrait: TestTrait, TestScoping {
var name: String = "testHome"

init(_ name: String) { self.name = name }

func provideScope(for _: Test, testCase _: Test.Case?, performing function: @Sendable () async throws -> Void) async throws {
try await SwiftlyTests.withMockedSwiftlyVersion(latestSwiftlyVersion: SwiftlyVersion(major: SwiftlyCore.version.major, minor: 0, patch: 0)) {
try await function()
}
}
}

extension Trait where Self == MockedSwiftlyVersionTrait {
static func mockedSwiftlyVersion(_ name: String = "testHome") -> Self { Self(name) }
}

struct MockHomeToolchainsTrait: TestTrait, TestScoping {
var name: String = "testHome"
var toolchains: Set<ToolchainVersion> = .allToolchains()
Expand Down
22 changes: 11 additions & 11 deletions Tests/SwiftlyTests/UninstallTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Testing
static let homeName = "uninstallTests"

/// Tests that `swiftly uninstall` successfully handles being invoked when no toolchains have been installed yet.
@Test(.mockHomeToolchains(Self.homeName, toolchains: [])) func uninstallNoInstalledToolchains() async throws {
@Test(.mockHomeToolchains(Self.homeName, toolchains: []), .mockedSwiftlyVersion()) func uninstallNoInstalledToolchains() async throws {
_ = try await SwiftlyTests.runWithMockedIO(Uninstall.self, ["uninstall", "1.2.3"], input: ["y"])

try await SwiftlyTests.validateInstalledToolchains(
Expand All @@ -17,7 +17,7 @@ import Testing
}

/// Tests that `swiftly uninstall latest` successfully uninstalls the latest stable release of Swift.
@Test func uninstallLatest() async throws {
@Test(.mockedSwiftlyVersion()) func uninstallLatest() async throws {
let toolchains = Set<ToolchainVersion>.allToolchains().filter { $0.asStableRelease != nil }
try await SwiftlyTests.withMockedHome(homeName: Self.homeName, toolchains: toolchains) {
var installed = toolchains
Expand All @@ -38,7 +38,7 @@ import Testing
}

/// Tests that a fully-qualified stable release version can be supplied to `swiftly uninstall`.
@Test(.mockHomeToolchains(Self.homeName)) func uninstallStableRelease() async throws {
@Test(.mockedSwiftlyVersion(), .mockHomeToolchains(Self.homeName)) func uninstallStableRelease() async throws {
var installed: Set<ToolchainVersion> = .allToolchains()

for toolchain in Set<ToolchainVersion>.allToolchains().filter({ $0.isStableRelease() }) {
Expand All @@ -60,7 +60,7 @@ import Testing
}

/// Tests that a fully-qualified snapshot version can be supplied to `swiftly uninstall`.
@Test(.mockHomeToolchains(Self.homeName)) func uninstallSnapshot() async throws {
@Test(.mockedSwiftlyVersion(), .mockHomeToolchains(Self.homeName)) func uninstallSnapshot() async throws {
var installed: Set<ToolchainVersion> = .allToolchains()

for toolchain in Set<ToolchainVersion>.allToolchains().filter({ $0.isSnapshot() }) {
Expand All @@ -82,7 +82,7 @@ import Testing
}

/// Tests that multiple toolchains can be installed at once.
@Test func bulkUninstall() async throws {
@Test(.mockedSwiftlyVersion()) func bulkUninstall() async throws {
let toolchains = Set(
[
"main-snapshot-2022-01-03",
Expand Down Expand Up @@ -158,7 +158,7 @@ import Testing
}

/// Tests that uninstalling the toolchain that is currently "in use" has the expected behavior.
@Test func uninstallInUse() async throws {
@Test(.mockedSwiftlyVersion()) func uninstallInUse() async throws {
let toolchains: Set<ToolchainVersion> = [
.oldStable,
.oldStableNewPatch,
Expand Down Expand Up @@ -224,7 +224,7 @@ import Testing
}

/// Tests that uninstalling the last toolchain is handled properly and cleans up any symlinks.
@Test(.mockHomeToolchains(Self.homeName, toolchains: [.oldStable])) func uninstallLastToolchain() async throws {
@Test(.mockedSwiftlyVersion(), .mockHomeToolchains(Self.homeName, toolchains: [.oldStable])) func uninstallLastToolchain() async throws {
_ = try await SwiftlyTests.runWithMockedIO(Uninstall.self, ["uninstall", ToolchainVersion.oldStable.name], input: ["y"])
let config = try await Config.load()
#expect(config.inUse == nil)
Expand All @@ -237,7 +237,7 @@ import Testing
}

/// Tests that aborting an uninstall works correctly.
@Test(.mockHomeToolchains(Self.homeName, toolchains: .allToolchains(), inUse: .oldStable)) func uninstallAbort() async throws {
@Test(.mockedSwiftlyVersion(), .mockHomeToolchains(Self.homeName, toolchains: .allToolchains(), inUse: .oldStable)) func uninstallAbort() async throws {
let preConfig = try await Config.load()
_ = try await SwiftlyTests.runWithMockedIO(Uninstall.self, ["uninstall", ToolchainVersion.oldStable.name], input: ["n"])
try await SwiftlyTests.validateInstalledToolchains(
Expand All @@ -250,7 +250,7 @@ import Testing
}

/// Tests that providing the `-y` argument skips the confirmation prompt.
@Test(.mockHomeToolchains(Self.homeName, toolchains: [.oldStable, .newStable])) func uninstallAssumeYes() async throws {
@Test(.mockedSwiftlyVersion(), .mockHomeToolchains(Self.homeName, toolchains: [.oldStable, .newStable])) func uninstallAssumeYes() async throws {
try await SwiftlyTests.runCommand(Uninstall.self, ["uninstall", "-y", ToolchainVersion.oldStable.name])
try await SwiftlyTests.validateInstalledToolchains(
[.newStable],
Expand All @@ -259,7 +259,7 @@ import Testing
}

/// Tests that providing "all" as an argument to uninstall will uninstall all toolchains.
@Test(.mockHomeToolchains(Self.homeName, toolchains: [.oldStable, .newStable, .newMainSnapshot, .oldReleaseSnapshot])) func uninstallAll() async throws {
@Test(.mockedSwiftlyVersion(), .mockHomeToolchains(Self.homeName, toolchains: [.oldStable, .newStable, .newMainSnapshot, .oldReleaseSnapshot])) func uninstallAll() async throws {
try await SwiftlyTests.runCommand(Uninstall.self, ["uninstall", "-y", "all"])
try await SwiftlyTests.validateInstalledToolchains(
[],
Expand All @@ -268,7 +268,7 @@ import Testing
}

/// Tests that uninstalling a toolchain that is the global default, but is not in the list of installed toolchains.
@Test(.mockHomeToolchains(Self.homeName, toolchains: [.oldStable, .newStable, .newMainSnapshot, .oldReleaseSnapshot])) func uninstallNotInstalled() async throws {
@Test(.mockedSwiftlyVersion(), .mockHomeToolchains(Self.homeName, toolchains: [.oldStable, .newStable, .newMainSnapshot, .oldReleaseSnapshot])) func uninstallNotInstalled() async throws {
var config = try await Config.load()
config.inUse = .newMainSnapshot
config.installedToolchains.remove(.newMainSnapshot)
Expand Down
Loading