Skip to content

Commit 8f0bf20

Browse files
authored
Migrate swiftly to swift testing (#304)
Streamline passing of test contexts to common Swiftly API Remove extraneous HTTP requests during most testing Fully sandbox and mock GPG key import and verification so that it never pollutes the user's configuration. Introduce test scoping traits to remove many of the with... closures needed for mocking Move the toolchain constants into ToolchainVersion and Set for shorter lists and static references Introduce test parameters and test scoping traits to reduce nesting within test cases Increase HTTP timeouts to help with timeouts in CI Improve test temporary file cleanup Run HTTPClientTests serially to avoid network contention and add retries. Create OpenAPI client in a single place for better consistency and remove unused Response
1 parent 97774fd commit 8f0bf20

30 files changed

+1675
-1831
lines changed

Sources/LinuxPlatform/Linux.swift

Lines changed: 49 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import Foundation
22
import SwiftlyCore
33

4-
var swiftGPGKeysRefreshed = false
5-
64
/// `Platform` implementation for Linux systems.
75
/// This implementation can be reused for any supported Linux platform.
86
/// TODO: replace dummy implementations
@@ -29,25 +27,21 @@ public struct Linux: Platform {
2927
}
3028
}
3129

32-
public var swiftlyBinDir: URL {
33-
SwiftlyCore.mockedHomeDir.map { $0.appendingPathComponent("bin", isDirectory: true) }
30+
public func swiftlyBinDir(_ ctx: SwiftlyCoreContext) -> URL {
31+
ctx.mockedHomeDir.map { $0.appendingPathComponent("bin", isDirectory: true) }
3432
?? ProcessInfo.processInfo.environment["SWIFTLY_BIN_DIR"].map { URL(fileURLWithPath: $0) }
3533
?? FileManager.default.homeDirectoryForCurrentUser
3634
.appendingPathComponent(".local/share/swiftly/bin", isDirectory: true)
3735
}
3836

39-
public var swiftlyToolchainsDir: URL {
40-
self.swiftlyHomeDir.appendingPathComponent("toolchains", isDirectory: true)
37+
public func swiftlyToolchainsDir(_ ctx: SwiftlyCoreContext) -> URL {
38+
self.swiftlyHomeDir(ctx).appendingPathComponent("toolchains", isDirectory: true)
4139
}
4240

4341
public var toolchainFileExtension: String {
4442
"tar.gz"
4543
}
4644

47-
public func isSystemDependencyPresent(_: SystemDependency) -> Bool {
48-
true
49-
}
50-
5145
private static let skipVerificationMessage: String = "To skip signature verification, specify the --no-verify flag."
5246

5347
public func verifySwiftlySystemPrerequisites() throws {
@@ -73,7 +67,7 @@ public struct Linux: Platform {
7367
}
7468
}
7569

76-
public func verifySystemPrerequisitesForInstall(httpClient: SwiftlyHTTPClient, platformName: String, version _: ToolchainVersion, requireSignatureValidation: Bool) async throws -> String? {
70+
public func verifySystemPrerequisitesForInstall(_ ctx: SwiftlyCoreContext, platformName: String, version _: ToolchainVersion, requireSignatureValidation: Bool) async throws -> String? {
7771
// TODO: these are hard-coded until we have a place to query for these based on the toolchain version
7872
// These lists were copied from the dockerfile sources here: https://github.com/apple/swift-docker/tree/ea035798755cce4ec41e0c6dbdd320904cef0421/5.10
7973
let packages: [String] = switch platformName {
@@ -264,22 +258,21 @@ public struct Linux: Platform {
264258
throw SwiftlyError(message: msg)
265259
}
266260

267-
// Import the latest swift keys, but only once per session, which will help with the performance in tests
268-
if !swiftGPGKeysRefreshed {
269-
let tmpFile = self.getTempFilePath()
270-
let _ = FileManager.default.createFile(atPath: tmpFile.path, contents: nil, attributes: [.posixPermissions: 0o600])
271-
defer {
272-
try? FileManager.default.removeItem(at: tmpFile)
273-
}
261+
let tmpFile = self.getTempFilePath()
262+
let _ = FileManager.default.createFile(atPath: tmpFile.path, contents: nil, attributes: [.posixPermissions: 0o600])
263+
defer {
264+
try? FileManager.default.removeItem(at: tmpFile)
265+
}
274266

275-
guard let url = URL(string: "https://www.swift.org/keys/all-keys.asc") else {
276-
throw SwiftlyError(message: "malformed URL to the swift gpg keys")
277-
}
267+
guard let url = URL(string: "https://www.swift.org/keys/all-keys.asc") else {
268+
throw SwiftlyError(message: "malformed URL to the swift gpg keys")
269+
}
278270

279-
try await httpClient.downloadFile(url: url, to: tmpFile)
271+
try await ctx.httpClient.downloadFile(url: url, to: tmpFile)
272+
if let mockedHomeDir = ctx.mockedHomeDir {
273+
try self.runProgram("gpg", "--import", tmpFile.path, quiet: true, env: ["GNUPGHOME": mockedHomeDir.appendingPathComponent(".gnupg").path])
274+
} else {
280275
try self.runProgram("gpg", "--import", tmpFile.path, quiet: true)
281-
282-
swiftGPGKeysRefreshed = true
283276
}
284277
}
285278

@@ -330,17 +323,17 @@ public struct Linux: Platform {
330323
}
331324
}
332325

333-
public func install(from tmpFile: URL, version: ToolchainVersion, verbose: Bool) throws {
326+
public func install(_ ctx: SwiftlyCoreContext, from tmpFile: URL, version: ToolchainVersion, verbose: Bool) throws {
334327
guard tmpFile.fileExists() else {
335328
throw SwiftlyError(message: "\(tmpFile) doesn't exist")
336329
}
337330

338-
if !self.swiftlyToolchainsDir.fileExists() {
339-
try FileManager.default.createDirectory(at: self.swiftlyToolchainsDir, withIntermediateDirectories: false)
331+
if !self.swiftlyToolchainsDir(ctx).fileExists() {
332+
try FileManager.default.createDirectory(at: self.swiftlyToolchainsDir(ctx), withIntermediateDirectories: false)
340333
}
341334

342-
SwiftlyCore.print("Extracting toolchain...")
343-
let toolchainDir = self.swiftlyToolchainsDir.appendingPathComponent(version.name)
335+
ctx.print("Extracting toolchain...")
336+
let toolchainDir = self.swiftlyToolchainsDir(ctx).appendingPathComponent(version.name)
344337

345338
if toolchainDir.fileExists() {
346339
try FileManager.default.removeItem(at: toolchainDir)
@@ -354,23 +347,26 @@ public struct Linux: Platform {
354347
let destination = toolchainDir.appendingPathComponent(String(relativePath))
355348

356349
if verbose {
357-
SwiftlyCore.print("\(destination.path)")
350+
ctx.print("\(destination.path)")
358351
}
359352

360353
// prepend /path/to/swiftlyHomeDir/toolchains/<toolchain> to each file name
361354
return destination
362355
}
363356
}
364357

365-
public func extractSwiftlyAndInstall(from archive: URL) throws {
358+
public func extractSwiftlyAndInstall(_ ctx: SwiftlyCoreContext, from archive: URL) throws {
366359
guard archive.fileExists() else {
367360
throw SwiftlyError(message: "\(archive) doesn't exist")
368361
}
369362

370363
let tmpDir = self.getTempFilePath()
364+
defer {
365+
try? FileManager.default.removeItem(at: tmpDir)
366+
}
371367
try FileManager.default.createDirectory(atPath: tmpDir.path, withIntermediateDirectories: true)
372368

373-
SwiftlyCore.print("Extracting new swiftly...")
369+
ctx.print("Extracting new swiftly...")
374370
try extractArchive(atPath: archive) { name in
375371
// Extract to the temporary directory
376372
tmpDir.appendingPathComponent(String(name))
@@ -379,8 +375,8 @@ public struct Linux: Platform {
379375
try self.runProgram(tmpDir.appendingPathComponent("swiftly").path, "init")
380376
}
381377

382-
public func uninstall(_ toolchain: ToolchainVersion, verbose _: Bool) throws {
383-
let toolchainDir = self.swiftlyToolchainsDir.appendingPathComponent(toolchain.name)
378+
public func uninstall(_ ctx: SwiftlyCoreContext, _ toolchain: ToolchainVersion, verbose _: Bool) throws {
379+
let toolchainDir = self.swiftlyToolchainsDir(ctx).appendingPathComponent(toolchain.name)
384380
try FileManager.default.removeItem(at: toolchainDir)
385381
}
386382

@@ -394,9 +390,9 @@ public struct Linux: Platform {
394390
FileManager.default.temporaryDirectory.appendingPathComponent("swiftly-\(UUID())")
395391
}
396392

397-
public func verifySignature(httpClient: SwiftlyHTTPClient, archiveDownloadURL: URL, archive: URL, verbose: Bool) async throws {
393+
public func verifySignature(_ ctx: SwiftlyCoreContext, archiveDownloadURL: URL, archive: URL, verbose: Bool) async throws {
398394
if verbose {
399-
SwiftlyCore.print("Downloading toolchain signature...")
395+
ctx.print("Downloading toolchain signature...")
400396
}
401397

402398
let sigFile = self.getTempFilePath()
@@ -405,20 +401,24 @@ public struct Linux: Platform {
405401
try? FileManager.default.removeItem(at: sigFile)
406402
}
407403

408-
try await httpClient.downloadFile(
404+
try await ctx.httpClient.downloadFile(
409405
url: archiveDownloadURL.appendingPathExtension("sig"),
410406
to: sigFile
411407
)
412408

413-
SwiftlyCore.print("Verifying toolchain signature...")
409+
ctx.print("Verifying toolchain signature...")
414410
do {
415-
try self.runProgram("gpg", "--verify", sigFile.path, archive.path, quiet: !verbose)
411+
if let mockedHomeDir = ctx.mockedHomeDir {
412+
try self.runProgram("gpg", "--verify", sigFile.path, archive.path, quiet: false, env: ["GNUPGHOME": mockedHomeDir.appendingPathComponent(".gnupg").path])
413+
} else {
414+
try self.runProgram("gpg", "--verify", sigFile.path, archive.path, quiet: !verbose)
415+
}
416416
} catch {
417417
throw SwiftlyError(message: "Signature verification failed: \(error).")
418418
}
419419
}
420420

421-
private func manualSelectPlatform(_ platformPretty: String?) async -> PlatformDefinition {
421+
private func manualSelectPlatform(_ ctx: SwiftlyCoreContext, _ platformPretty: String?) async -> PlatformDefinition {
422422
if let platformPretty {
423423
print("\(platformPretty) is not an officially supported platform, but the toolchains for another platform may still work on it.")
424424
} else {
@@ -434,7 +434,7 @@ public struct Linux: Platform {
434434
\(selections)
435435
""")
436436

437-
let choice = SwiftlyCore.readLine(prompt: "Pick one of the available selections [0-\(self.linuxPlatforms.count)] ") ?? "0"
437+
let choice = ctx.readLine(prompt: "Pick one of the available selections [0-\(self.linuxPlatforms.count)] ") ?? "0"
438438

439439
guard let choiceNum = Int(choice) else {
440440
fatalError("Installation canceled")
@@ -447,7 +447,7 @@ public struct Linux: Platform {
447447
return self.linuxPlatforms[choiceNum - 1]
448448
}
449449

450-
public func detectPlatform(disableConfirmation: Bool, platform: String?) async throws -> PlatformDefinition {
450+
public func detectPlatform(_ ctx: SwiftlyCoreContext, disableConfirmation: Bool, platform: String?) async throws -> PlatformDefinition {
451451
// We've been given a hint to use
452452
if let platform {
453453
guard let pd = linuxPlatforms.first(where: { $0.nameFull == platform }) else {
@@ -475,7 +475,7 @@ public struct Linux: Platform {
475475
} else {
476476
print(message)
477477
}
478-
return await self.manualSelectPlatform(platformPretty)
478+
return await self.manualSelectPlatform(ctx, platformPretty)
479479
}
480480

481481
let releaseInfo = try String(contentsOfFile: releaseFile, encoding: .utf8)
@@ -502,7 +502,7 @@ public struct Linux: Platform {
502502
} else {
503503
print(message)
504504
}
505-
return await self.manualSelectPlatform(platformPretty)
505+
return await self.manualSelectPlatform(ctx, platformPretty)
506506
}
507507

508508
if (id + (idlike ?? "")).contains("amzn") {
@@ -513,7 +513,7 @@ public struct Linux: Platform {
513513
} else {
514514
print(message)
515515
}
516-
return await self.manualSelectPlatform(platformPretty)
516+
return await self.manualSelectPlatform(ctx, platformPretty)
517517
}
518518

519519
return .amazonlinux2
@@ -525,7 +525,7 @@ public struct Linux: Platform {
525525
} else {
526526
print(message)
527527
}
528-
return await self.manualSelectPlatform(platformPretty)
528+
return await self.manualSelectPlatform(ctx, platformPretty)
529529
}
530530

531531
return .rhel9
@@ -539,7 +539,7 @@ public struct Linux: Platform {
539539
} else {
540540
print(message)
541541
}
542-
return await self.manualSelectPlatform(platformPretty)
542+
return await self.manualSelectPlatform(ctx, platformPretty)
543543
}
544544

545545
public func getShell() async throws -> String {
@@ -559,8 +559,8 @@ public struct Linux: Platform {
559559
return "/bin/bash"
560560
}
561561

562-
public func findToolchainLocation(_ toolchain: ToolchainVersion) -> URL {
563-
self.swiftlyToolchainsDir.appendingPathComponent("\(toolchain.name)")
562+
public func findToolchainLocation(_ ctx: SwiftlyCoreContext, _ toolchain: ToolchainVersion) -> URL {
563+
self.swiftlyToolchainsDir(ctx).appendingPathComponent("\(toolchain.name)")
564564
}
565565

566566
public static let currentPlatform: any Platform = Linux()

0 commit comments

Comments
 (0)