Skip to content

Commit b14e431

Browse files
authored
Apply toolset's testRunner property in swift test (#8254)
### Motivation: Per [SE-0378 toolsets can specify an optional `testRunner` property](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0387-cross-compilation-destinations.md#toolsetjson-files), which so far had no effect. We should respect this property in `swift test` invocations when any toolsets or Swift SDKs are selected. This property specifies a path to a test harness that has implementation-specific knowledge of interpreting produced test bundles. This allows testing cross-compiled products in an environment other than the host, e.g. a container runtime when cross-compiled to Linux, qemu or a firmware flasher and serial port setup script with Swift Embedded for microcontrollers, or in a Wasm runtime for a WASI binary. ### Modifications: Updated SwiftRunCommand to check for the debugger property in toolsets or Swift SDKs when such are specified. Its value is then used to launch a freshly built binary. Changed test bundle extension from `.wasm` to `.xctest` used for other platforms to reduce special casing in corresponding code paths. Without this change, initial implementation crashed here in the `bundlePath` property, triggering `fatalError`, as lookup logic always assumes `.xctest` extension: https://github.com/swiftlang/swift-package-manager/blob/6aa009fd30992c3cba9af3fcb439ef57b61b75da/Sources/SPMBuildCore/BuiltTestProduct.swift#L35 ### Result: Cross-compiled tests can be directly launched with a `swift test` invocation.
1 parent 88e2af7 commit b14e431

File tree

9 files changed

+56
-24
lines changed

9 files changed

+56
-24
lines changed

Fixtures/Miscellaneous/EchoExecutable/Package.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@ let package = Package(
77
.executable(name: "secho", targets: ["secho"])
88
],
99
targets: [
10-
.target(name: "secho", dependencies: [])
11-
])
10+
.target(name: "secho", dependencies: []),
11+
.testTarget(name: "TestSuite")
12+
])
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import XCTest
2+
3+
final class TestCase: XCTestCase {
4+
func testFoo() {
5+
XCTAssertTrue(true)
6+
}
7+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
#!/bin/sh
22

3+
echo sentinel
34
echo "$@"
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"debugger": { "path": "echo.sh" },
3+
"testRunner": { "path": "echo.sh" },
34
"schemaVersion" : "1.0",
45
"rootPath" : "."
56
}

Sources/Commands/SwiftTestCommand.swift

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -948,23 +948,31 @@ final class TestRunner {
948948
/// Constructs arguments to execute XCTest.
949949
private func args(forTestAt testPath: AbsolutePath) throws -> [String] {
950950
var args: [String] = []
951+
952+
if let runner = self.toolchain.swiftSDK.toolset.knownTools[.testRunner], let runnerPath = runner.path {
953+
args.append(runnerPath.pathString)
954+
args.append(contentsOf: runner.extraCLIOptions)
955+
args.append(testPath.relative(to: localFileSystem.currentWorkingDirectory!).pathString)
956+
args.append(contentsOf: self.additionalArguments)
957+
} else {
951958
#if os(macOS)
952-
switch library {
953-
case .xctest:
954-
guard let xctestPath = self.toolchain.xctestPath else {
955-
throw TestError.xcodeNotInstalled
959+
switch library {
960+
case .xctest:
961+
guard let xctestPath = self.toolchain.xctestPath else {
962+
throw TestError.xcodeNotInstalled
963+
}
964+
args += [xctestPath.pathString]
965+
case .swiftTesting:
966+
let helper = try self.toolchain.getSwiftTestingHelper()
967+
args += [helper.pathString, "--test-bundle-path", testPath.pathString]
956968
}
957-
args += [xctestPath.pathString]
958-
case .swiftTesting:
959-
let helper = try self.toolchain.getSwiftTestingHelper()
960-
args += [helper.pathString, "--test-bundle-path", testPath.pathString]
969+
args += self.additionalArguments
970+
args += [testPath.pathString]
971+
#else
972+
args += [testPath.pathString]
973+
args += self.additionalArguments
974+
#endif
961975
}
962-
args += additionalArguments
963-
args += [testPath.pathString]
964-
#else
965-
args += [testPath.pathString]
966-
args += additionalArguments
967-
#endif
968976

969977
if library == .swiftTesting {
970978
// HACK: tell the test bundle/executable that we want to run Swift Testing, not XCTest.

Sources/SPMBuildCore/BuildParameters/BuildParameters.swift

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -302,9 +302,6 @@ public struct BuildParameters: Encodable {
302302
case .library(.automatic), .plugin:
303303
fatalError()
304304
case .test:
305-
guard !self.triple.isWasm else {
306-
return try RelativePath(validating: "\(product.name).wasm")
307-
}
308305
let base = "\(product.name).xctest"
309306
if self.triple.isDarwin() {
310307
return try RelativePath(validating: "\(base)/Contents/MacOS/\(product.name)")

Tests/BuildTests/CrossCompilationBuildPlanTests.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ final class CrossCompilationBuildPlanTests: XCTestCase {
210210
[
211211
result.plan.destinationBuildParameters.toolchain.swiftCompilerPath.pathString,
212212
"-L", buildPath.pathString,
213-
"-o", buildPath.appending(components: "PkgPackageTests.wasm").pathString,
213+
"-o", buildPath.appending(components: "PkgPackageTests.xctest").pathString,
214214
"-module-name", "PkgPackageTests",
215215
"-emit-executable",
216216
"@\(buildPath.appending(components: "PkgPackageTests.product", "Objects.LinkFileList"))",
@@ -220,7 +220,7 @@ final class CrossCompilationBuildPlanTests: XCTestCase {
220220
)
221221

222222
let testPathExtension = try testBuildDescription.binaryPath.extension
223-
XCTAssertEqual(testPathExtension, "wasm")
223+
XCTAssertEqual(testPathExtension, "xctest")
224224
}
225225

226226
func testMacros() async throws {

Tests/CommandsTests/RunCommandTests.swift

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,8 @@ final class RunCommandTests: CommandsTestCase {
4848
["--toolset", "\(fixturePath)/toolset.json"], packagePath: fixturePath)
4949

5050
// We only expect tool's output on the stdout stream.
51-
XCTAssertMatch(stdout, .contains("""
52-
\(fixturePath)/.build
53-
"""))
51+
XCTAssertMatch(stdout, .contains("\(fixturePath)/.build"))
52+
XCTAssertMatch(stdout, .contains("sentinel"))
5453

5554
// swift-build-tool output should go to stderr.
5655
XCTAssertMatch(stderr, .regex("Compiling"))

Tests/CommandsTests/TestCommandTests.swift

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,24 @@ final class TestCommandTests: CommandsTestCase {
3636
XCTAssert(stdout.contains("Swift Package Manager"), "got stdout:\n" + stdout)
3737
}
3838

39+
// `runner.sh` script from the toolset won't work on Windows
40+
#if !os(Windows)
41+
func testToolsetRunner() async throws {
42+
try await fixture(name: "Miscellaneous/EchoExecutable") { fixturePath in
43+
let (stdout, stderr) = try await SwiftPM.Test.execute(
44+
["--toolset", "\(fixturePath)/toolset.json"], packagePath: fixturePath)
45+
46+
// We only expect tool's output on the stdout stream.
47+
XCTAssertMatch(stdout, .contains("sentinel"))
48+
XCTAssertMatch(stdout, .contains("\(fixturePath)"))
49+
50+
// swift-build-tool output should go to stderr.
51+
XCTAssertMatch(stderr, .regex("Compiling"))
52+
XCTAssertMatch(stderr, .contains("Linking"))
53+
}
54+
}
55+
#endif
56+
3957
func testNumWorkersParallelRequirement() async throws {
4058
#if !os(macOS)
4159
// Running swift-test fixtures on linux is not yet possible.

0 commit comments

Comments
 (0)