Skip to content

Commit 367e989

Browse files
authored
Next generation: tests on Linux, strict concurrency, swift testing, swift logging (#79)
* Re-enable tests on Linux * Use ubuntu-latest * Update Swift setup action in GitHub workflow * Migrate to Swift Testing * Move to SwiftLogger * Enable strict concurrency * Branch out test setups * Use macOS 26 image * Run CI on push * Xcode 26 * Bring back given/when/then * Cleanup
1 parent 85b71b8 commit 367e989

26 files changed

Lines changed: 717 additions & 549 deletions

.github/workflows/test.yml

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ on:
44
push:
55
branches: [ "main" ]
66
pull_request:
7-
types: [opened, reopened]
7+
types: [opened, reopened, synchronize]
88

99
jobs:
10-
test:
10+
test-16-2:
1111
runs-on: macos-latest
1212
steps:
1313
- name: Set Xcode version
@@ -16,12 +16,20 @@ jobs:
1616
- name: Run tests
1717
run: swift test -v
1818

19-
test-linux:
20-
if: false
21-
runs-on: ubuntu-22.04
19+
test-26:
20+
runs-on: macos-26
21+
steps:
22+
- name: Set Xcode version
23+
run: sudo xcode-select -s /Applications/Xcode_26.0.app
24+
- uses: actions/checkout@v3
25+
- name: Run tests
26+
run: swift test -v
27+
28+
test-linux-6-1:
29+
runs-on: ubuntu-latest
2230
steps:
2331
- uses: actions/checkout@v3
24-
- uses: swift-actions/setup-swift@v2
32+
- uses: SwiftyLab/setup-swift@latest
2533
with:
2634
swift-version: "6.1.0"
2735
- name: Get swift version
@@ -30,3 +38,17 @@ jobs:
3038
run: git config --global user.email "test@example.com" && git config --global user.name "Test User"
3139
- name: Run tests
3240
run: swift test -v
41+
42+
test-linux-6-2:
43+
runs-on: ubuntu-latest
44+
steps:
45+
- uses: actions/checkout@v3
46+
- uses: SwiftyLab/setup-swift@latest
47+
with:
48+
swift-version: "6.2.0"
49+
- name: Get swift version
50+
run: swift --version
51+
- name: Prepare Git
52+
run: git config --global user.email "test@example.com" && git config --global user.name "Test User"
53+
- name: Run tests
54+
run: swift test -v

Package.resolved

Lines changed: 11 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// swift-tools-version: 5.6
1+
// swift-tools-version: 6.0
22

33
import PackageDescription
44

@@ -17,40 +17,59 @@ let products: [PackageDescription.Product] = [
1717
)
1818
]
1919

20+
let flags: [PackageDescription.SwiftSetting] = [.enableExperimentalFeature("StrictConcurrency")]
21+
2022
let targets: [PackageDescription.Target] = [
2123
.executableTarget(
2224
name: "xcode-selective-test",
2325
dependencies: ["SelectiveTestingCore",
24-
.product(name: "ArgumentParser", package: "swift-argument-parser")]
26+
.product(name: "ArgumentParser", package: "swift-argument-parser")],
27+
swiftSettings: flags
2528
),
2629
.target(name: "SelectiveTestingCore",
2730
dependencies: ["DependencyCalculator",
2831
"TestConfigurator",
2932
"Git",
3033
"PathKit",
31-
"Rainbow",
3234
"Yams",
33-
.product(name: "ArgumentParser", package: "swift-argument-parser")]),
35+
.product(name: "ArgumentParser", package: "swift-argument-parser")],
36+
swiftSettings: flags
37+
),
3438
.target(name: "DependencyCalculator",
35-
dependencies: ["Workspace", "PathKit", "SelectiveTestLogger", "Git"]),
39+
dependencies: ["Workspace", "PathKit", "Git", .product(name: "Logging", package: "swift-log")],
40+
swiftSettings: flags
41+
),
3642
.target(name: "TestConfigurator",
37-
dependencies: ["Workspace", "PathKit", "SelectiveTestLogger"]),
43+
dependencies: [
44+
"Workspace",
45+
"PathKit",
46+
.product(name: "Logging", package: "swift-log"),
47+
.product(name: "ArgumentParser", package: "swift-argument-parser")
48+
],
49+
swiftSettings: flags
50+
),
3851
.target(name: "Workspace",
39-
dependencies: ["XcodeProj", "SelectiveTestLogger"]),
52+
dependencies: ["XcodeProj", .product(name: "Logging", package: "swift-log")],
53+
swiftSettings: flags
54+
),
4055
.target(name: "Git",
41-
dependencies: ["SelectiveTestShell", "SelectiveTestLogger", "PathKit"]),
42-
.target(name: "SelectiveTestLogger",
43-
dependencies: ["Rainbow"]),
44-
.target(name: "SelectiveTestShell"),
56+
dependencies: ["SelectiveTestShell", "PathKit", .product(name: "Logging", package: "swift-log")],
57+
swiftSettings: flags
58+
),
59+
.target(name: "SelectiveTestShell",
60+
swiftSettings: flags
61+
),
4562
.testTarget(
4663
name: "SelectiveTestingTests",
4764
dependencies: ["xcode-selective-test", "PathKit"],
48-
resources: [.copy("ExampleProject")]
65+
resources: [.copy("ExampleProject")],
66+
swiftSettings: flags
4967
),
5068
.testTarget(
5169
name: "DependencyCalculatorTests",
5270
dependencies: ["DependencyCalculator", "Workspace", "PathKit", "SelectiveTestingCore"],
53-
resources: [.copy("ExamplePackages")]
71+
resources: [.copy("ExamplePackages")],
72+
swiftSettings: flags
5473
),
5574
.plugin(
5675
name: "SelectiveTestingPlugin",
@@ -64,7 +83,7 @@ let targets: [PackageDescription.Target] = [
6483
]
6584
),
6685
dependencies: ["xcode-selective-test"]
67-
),
86+
)
6887
]
6988

7089
let package = Package(
@@ -77,8 +96,8 @@ let package = Package(
7796
.package(url: "https://github.com/tuist/XcodeProj.git", .upToNextMajor(from: "9.0.2")),
7897
.package(url: "https://github.com/apple/swift-argument-parser.git", .upToNextMajor(from: "1.2.0")),
7998
.package(url: "https://github.com/kylef/PathKit.git", .upToNextMinor(from: "1.0.0")),
80-
.package(url: "https://github.com/onevcat/Rainbow", .upToNextMajor(from: "4.0.0")),
8199
.package(url: "https://github.com/jpsim/Yams.git", from: "5.0.5"),
100+
.package(url: "https://github.com/apple/swift-log", from: "1.6.0")
82101
],
83102
targets: targets
84103
)

Plugins/SelectiveTestingPlugin/SelectiveTestingPlugin.swift

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,10 @@ import PackagePlugin
77

88
@main
99
struct SelectiveTestingPlugin: CommandPlugin {
10-
private func run(_ executable: String, arguments: [String] = []) throws {
11-
let executableURL = URL(fileURLWithPath: executable)
10+
private func run(_ executable: URL, arguments: [String] = []) throws {
1211

1312
let process = Process()
14-
process.executableURL = executableURL
13+
process.executableURL = executable
1514
process.arguments = arguments
1615

1716
try process.run()
@@ -24,10 +23,10 @@ struct SelectiveTestingPlugin: CommandPlugin {
2423
}
2524

2625
func performCommand(context: PluginContext, arguments: [String]) async throws {
27-
FileManager().changeCurrentDirectoryPath(context.package.directory.string)
26+
FileManager.default.changeCurrentDirectoryPath(context.package.directoryURL.path)
2827
let tool = try context.tool(named: "xcode-selective-test")
2928

30-
try run(tool.path.string, arguments: arguments)
29+
try run(tool.url, arguments: arguments)
3130
}
3231
}
3332

@@ -36,7 +35,7 @@ struct SelectiveTestingPlugin: CommandPlugin {
3635

3736
extension SelectiveTestingPlugin: XcodeCommandPlugin {
3837
func performCommand(context: XcodePluginContext, arguments: [String]) throws {
39-
FileManager().changeCurrentDirectoryPath(context.xcodeProject.directory.string)
38+
FileManager.default.changeCurrentDirectoryPath(context.xcodeProject.directoryURL.path)
4039

4140
let tool = try context.tool(named: "xcode-selective-test")
4241

@@ -49,24 +48,28 @@ struct SelectiveTestingPlugin: CommandPlugin {
4948
}
5049

5150
if !toolArguments.contains(where: { $0 == "--test-plan" }) {
52-
let testPlans = context.xcodeProject.filePaths.filter {
53-
$0.extension == "xctestplan"
51+
let allFiles = context.xcodeProject.targets.reduce([]) { partialResult, target in
52+
partialResult + target.inputFiles
53+
}
54+
55+
let testPlans = allFiles.filter {
56+
$0.url.pathExtension == "xctestplan"
5457
}
5558

5659
if !testPlans.isEmpty {
5760
if testPlans.count == 1 {
58-
print("Using \(testPlans[0].string) test plan")
61+
print("Using \(testPlans[0].url.path()) test plan")
5962
} else {
6063
print("Using \(testPlans.count) test plans")
6164
}
6265

6366
for testPlan in testPlans {
64-
toolArguments.append(contentsOf: ["--test-plan", testPlan.string])
67+
toolArguments.append(contentsOf: ["--test-plan", testPlan.url.path()])
6568
}
6669
}
6770
}
6871

69-
try run(tool.path.string, arguments: toolArguments)
72+
try run(tool.url, arguments: toolArguments)
7073
}
7174
}
7275
#endif

Sources/DependencyCalculator/ConcurrentMap.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import Foundation
66

7-
final class ThreadSafe<A> {
7+
final class ThreadSafe<A: Sendable>: @unchecked Sendable {
88
private var _value: A
99
private let queue = DispatchQueue(label: "ThreadSafe")
1010
init(_ value: A) {
@@ -22,8 +22,8 @@ final class ThreadSafe<A> {
2222
}
2323
}
2424

25-
extension Array {
26-
func concurrentMap<B>(_ transform: @escaping (Element) -> B) -> [B] {
25+
extension Array where Element: Sendable {
26+
func concurrentMap<B: Sendable>(_ transform: @escaping @Sendable (Element) -> B) -> [B] {
2727
let result = ThreadSafe([B?](repeating: nil, count: count))
2828
DispatchQueue.concurrentPerform(iterations: count) { idx in
2929
let element = self[idx]

Sources/DependencyCalculator/DependencyCalculator.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import Foundation
66
import PathKit
7-
import SelectiveTestLogger
7+
import Logging
88
import Workspace
99

1010
public extension WorkspaceInfo {
@@ -18,7 +18,7 @@ public extension WorkspaceInfo {
1818
} else if let targetFromFolder = targetForFolder(path) {
1919
result.insert(targetFromFolder)
2020
} else {
21-
Logger.message("Changed file at \(path) appears not to belong to any target")
21+
logger.info("Changed file at \(path) appears not to belong to any target")
2222
}
2323
}
2424
if incldueIndirectlyAffected {

0 commit comments

Comments
 (0)