Skip to content

Commit 97774fd

Browse files
authored
Move E2E testing into GH action for Linux (#296)
* Use debug for the test-swiftly tool to speed up compilation of it * Matrix the shells for the test release checks, including fish and zsh
1 parent 351a278 commit 97774fd

File tree

5 files changed

+175
-70
lines changed

5 files changed

+175
-70
lines changed

.github/workflows/pull_request.yml

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,49 @@ jobs:
5050
- name: Prepare the action
5151
run: ./scripts/prep-gh-action.sh --install-swiftly
5252
- name: Build Artifact
53-
run: swift run build-swiftly-release --use-rhel-ubi9 --skip "999.0.0"
53+
run: swift run build-swiftly-release --test --skip "999.0.0"
5454
- name: Upload Artifact
5555
uses: actions/upload-artifact@v4
5656
with:
5757
name: swiftly-release-x86_64
5858
path: .build/release/swiftly-*.tar.gz
5959
if-no-files-found: error
6060
retention-days: 1
61+
- name: Upload Tests
62+
uses: actions/upload-artifact@v4
63+
with:
64+
name: swiftly-tests-x86_64
65+
path: .build/debug/test-swiftly-linux-x86_64.tar.gz
66+
if-no-files-found: error
67+
retention-days: 1
68+
69+
releasetest:
70+
name: Test Release / ${{matrix.shell.pkg}}
71+
needs: releasebuildcheck
72+
runs-on: ubuntu-latest
73+
strategy:
74+
fail-fast: false
75+
matrix:
76+
shell: [
77+
{"pkg": "bash", "bin": "/bin/bash"},
78+
{"pkg": "zsh", "bin": "/bin/zsh"},
79+
{"pkg": "fish", "bin": "/bin/fish"}
80+
]
81+
container:
82+
image: "ubuntu:24.04"
83+
steps:
84+
- name: Prepare System
85+
run: apt-get update && DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt-get -y install ca-certificates gpg tzdata ${{matrix.shell.pkg}} && chsh -s ${{matrix.shell.bin}}
86+
- name: Download Release
87+
uses: actions/download-artifact@v4
88+
with:
89+
name: swiftly-release-x86_64
90+
- name: Download Tests
91+
uses: actions/download-artifact@v4
92+
with:
93+
name: swiftly-tests-x86_64
94+
- name: Extract and Run Workflow Tests
95+
run: cp swiftly-*.tar.gz /root/swiftly.tar.gz && cp test-swiftly-*.tar.gz /root && cd /root && tar zxf test-swiftly-*.tar.gz && ./test-swiftly -y ./swiftly.tar.gz
6196

6297
formatcheck:
6398
name: Format Check

Package.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ let package = Package(
1212
name: "swiftly",
1313
targets: ["Swiftly"]
1414
),
15+
.executable(
16+
name: "test-swiftly",
17+
targets: ["TestSwiftly"]
18+
),
1519
],
1620
dependencies: [
1721
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.3.0"),
@@ -36,6 +40,15 @@ let package = Package(
3640
.product(name: "SwiftToolsSupport-auto", package: "swift-tools-support-core"),
3741
]
3842
),
43+
.executableTarget(
44+
name: "TestSwiftly",
45+
dependencies: [
46+
.product(name: "ArgumentParser", package: "swift-argument-parser"),
47+
.target(name: "SwiftlyCore"),
48+
.target(name: "LinuxPlatform", condition: .when(platforms: [.linux])),
49+
.target(name: "MacOSPlatform", condition: .when(platforms: [.macOS])),
50+
]
51+
),
3952
.target(
4053
name: "SwiftlyCore",
4154
dependencies: [
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import ArgumentParser
2+
import Foundation
3+
import SwiftlyCore
4+
5+
#if os(Linux)
6+
import LinuxPlatform
7+
#elseif os(macOS)
8+
import MacOSPlatform
9+
#endif
10+
11+
#if os(Linux)
12+
let currentPlatform: Platform = Linux.currentPlatform
13+
#elseif os(macOS)
14+
let currentPlatform: Platform = MacOS.currentPlatform
15+
#else
16+
#error("Unsupported platform")
17+
#endif
18+
19+
@main
20+
struct TestSwiftly: AsyncParsableCommand {
21+
@Flag(name: [.customShort("y"), .long], help: "Disable confirmation prompts by assuming 'yes'")
22+
var assumeYes: Bool = false
23+
24+
@Argument var swiftlyArchive: String? = nil
25+
26+
mutating func run() async throws {
27+
if !self.assumeYes {
28+
print("WARNING: This test will mutate your system to test the swiftly installation end-to-end. Please run this on a fresh system and try again with '--assume-yes'.")
29+
Foundation.exit(2)
30+
}
31+
32+
guard let swiftlyArchive = self.swiftlyArchive else {
33+
print("ERROR: You must provide a swiftly archive path for the test.")
34+
Foundation.exit(2)
35+
}
36+
37+
print("Extracting swiftly release")
38+
#if os(Linux)
39+
try currentPlatform.runProgram("tar", "-zxvf", swiftlyArchive, quiet: false)
40+
#elseif os(macOS)
41+
try currentPlatform.runProgram("installer", "-pkg", swiftlyArchive, "-target", "CurrentUserHomeDirectory", quiet: false)
42+
#endif
43+
44+
print("Running 'swiftly init --assume-yes --verbose' to install swiftly and the latest toolchain")
45+
46+
#if os(Linux)
47+
let extractedSwiftly = "./swiftly"
48+
#elseif os(macOS)
49+
let extractedSwiftly = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent(".swiftly/bin/swiftly").path
50+
#endif
51+
52+
try currentPlatform.runProgram(extractedSwiftly, "init", "--assume-yes", "--skip-install", quiet: false)
53+
54+
let shell = try await currentPlatform.getShell()
55+
56+
var env = ProcessInfo.processInfo.environment
57+
58+
// Setting this environment helps to ensure that the profile gets sourced with bash, even if it is not in an interactive shell
59+
if shell.hasSuffix("bash") {
60+
env["BASH_ENV"] = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent(".profile").path
61+
} else if shell.hasSuffix("zsh") {
62+
env["ZDOTDIR"] = FileManager.default.homeDirectoryForCurrentUser.path
63+
} else if shell.hasSuffix("fish") {
64+
env["XDG_CONFIG_HOME"] = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent(".config").path
65+
}
66+
67+
try currentPlatform.runProgram(shell, "-l", "-c", "swiftly install --assume-yes latest --post-install-file=./post-install.sh", quiet: false, env: env)
68+
69+
var swiftReady = false
70+
71+
if NSUserName() == "root" && FileManager.default.fileExists(atPath: "./post-install.sh") {
72+
try currentPlatform.runProgram(shell, "./post-install.sh", quiet: false)
73+
swiftReady = true
74+
} else if FileManager.default.fileExists(atPath: "./post-install.sh") {
75+
print("WARNING: not running as root, so skipping the post installation steps and final swift verification.")
76+
} else {
77+
swiftReady = true
78+
}
79+
80+
if swiftReady {
81+
try currentPlatform.runProgram(shell, "-l", "-c", "swift --version", quiet: false, env: env)
82+
}
83+
}
84+
}

Tests/SwiftlyTests/E2ETests.swift

Lines changed: 0 additions & 65 deletions
This file was deleted.

Tools/build-swiftly-release/BuildSwiftlyRelease.swift

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,9 @@ struct BuildSwiftlyRelease: AsyncParsableCommand {
164164
var useRhelUbi9: Bool = false
165165
#endif
166166

167+
@Flag(help: "Produce a swiftly-test.tar.gz that has a standalone test suite to test the released bundle.")
168+
var test: Bool = false
169+
167170
@Argument(help: "Version of swiftly to build the release.")
168171
var version: String
169172

@@ -365,7 +368,6 @@ struct BuildSwiftlyRelease: AsyncParsableCommand {
365368
FileManager.default.changeCurrentDirectoryPath(cwd)
366369

367370
try runProgram(swift, "build", "--swift-sdk", "\(arch)-swift-linux-musl", "--product=swiftly", "--pkg-config-path=\(pkgConfigPath)/lib/pkgconfig", "--static-swift-stdlib", "--configuration=release")
368-
try runProgram(swift, "sdk", "remove", sdkName)
369371

370372
let releaseDir = cwd + "/.build/release"
371373

@@ -383,6 +385,23 @@ struct BuildSwiftlyRelease: AsyncParsableCommand {
383385
try runProgram(tar, "--directory=\(releaseDir)", "-czf", releaseArchive, "swiftly", "LICENSE.txt")
384386

385387
print(releaseArchive)
388+
389+
if self.test {
390+
let debugDir = cwd + "/.build/debug"
391+
392+
#if arch(arm64)
393+
let testArchive = "\(debugDir)/test-swiftly-linux-aarch64.tar.gz"
394+
#else
395+
let testArchive = "\(debugDir)/test-swiftly-linux-x86_64.tar.gz"
396+
#endif
397+
398+
try runProgram(swift, "build", "--swift-sdk", "\(arch)-swift-linux-musl", "--product=test-swiftly", "--pkg-config-path=\(pkgConfigPath)/lib/pkgconfig", "--static-swift-stdlib", "--configuration=debug")
399+
try runProgram(tar, "--directory=\(debugDir)", "-czf", testArchive, "test-swiftly")
400+
401+
print(testArchive)
402+
}
403+
404+
try runProgram(swift, "sdk", "remove", sdkName)
386405
}
387406

388407
func buildMacOSRelease(cert: String?, identifier: String) async throws {
@@ -397,6 +416,8 @@ struct BuildSwiftlyRelease: AsyncParsableCommand {
397416
let pkgbuild = try await self.assertTool("pkgbuild", message: "In order to make pkg installers there needs to be the `pkgbuild` tool that is installed on macOS.")
398417
let strip = try await self.assertTool("strip", message: "In order to strip binaries there needs to be the `strip` tool that is installed on macOS.")
399418

419+
let tar = try await self.assertTool("tar", message: "In order to produce archives there needs to be the `tar` tool that is installed on macOS.")
420+
400421
try runProgram(swift, "package", "clean")
401422

402423
for arch in ["x86_64", "arm64"] {
@@ -415,7 +436,8 @@ struct BuildSwiftlyRelease: AsyncParsableCommand {
415436

416437
let cwd = FileManager.default.currentDirectoryPath
417438

418-
let pkgFile = URL(fileURLWithPath: cwd + "/.build/release/swiftly-\(self.version).pkg")
439+
let releaseDir = URL(fileURLWithPath: cwd + "/.build/release")
440+
let pkgFile = releaseDir.appendingPathComponent("/swiftly-\(self.version).pkg")
419441

420442
if let cert {
421443
try runProgram(
@@ -450,8 +472,8 @@ struct BuildSwiftlyRelease: AsyncParsableCommand {
450472
// Re-configure the pkg to prefer installs into the current user's home directory with the help of productbuild.
451473
// Note that command-line installs can override this preference, but the GUI install will limit the choices.
452474

453-
let pkgFileReconfigured = URL(fileURLWithPath: cwd + "/.build/release/swiftly-\(self.version)-reconfigured.pkg")
454-
let distFile = URL(fileURLWithPath: cwd + "/.build/release/distribution.plist")
475+
let pkgFileReconfigured = releaseDir.appendingPathComponent("swiftly-\(self.version)-reconfigured.pkg")
476+
let distFile = releaseDir.appendingPathComponent("distribution.plist")
455477

456478
try runProgram("productbuild", "--synthesize", "--package", pkgFile.path, distFile.path)
457479

@@ -466,5 +488,21 @@ struct BuildSwiftlyRelease: AsyncParsableCommand {
466488
}
467489
try FileManager.default.removeItem(at: pkgFile)
468490
try FileManager.default.copyItem(atPath: pkgFileReconfigured.path, toPath: pkgFile.path)
491+
492+
print(pkgFile.path)
493+
494+
if self.test {
495+
for arch in ["x86_64", "arm64"] {
496+
try runProgram(swift, "build", "--product=test-swiftly", "--configuration=debug", "--arch=\(arch)")
497+
try runProgram(strip, ".build/\(arch)-apple-macosx/release/swiftly")
498+
}
499+
500+
let testArchive = releaseDir.appendingPathComponent("test-swiftly-macos.tar.gz")
501+
502+
try runProgram(lipo, ".build/x86_64-apple-macosx/debug/test-swiftly", ".build/arm64-apple-macosx/debug/test-swiftly", "-create", "-o", "\(swiftlyBinDir)/swiftly")
503+
try runProgram(tar, "--directory=.build/x86_64-apple-macosx/debug", "-czf", testArchive.path, "test-swiftly")
504+
505+
print(testArchive.path)
506+
}
469507
}
470508
}

0 commit comments

Comments
 (0)