Skip to content

Commit 00095ed

Browse files
grynspanbnbarham
authored andcommitted
Detect if XCTest is installed on Darwin (#7805)
This PR checks if XCTest is available before invoking XCTest-based tests on Darwin. There are three possible outcomes: 1. If XCTest is available, we will run XCTest-based tests (as we have historically.) 2. If XCTest is not available and the user explicitly passed `--enable-xctest`, we will attempt to run XCTest-based tests, but in general this code path will continue to fail as `swift test` has historically done when XCTest is not available. 3. If XCTest is not available and the user did not pass `--enable-xctest`, we skip running any XCTest logic. On Linux/Windows/etc., XCTest is always present via swift-corelibs-xctest and so this change has no practical effect there. On Darwin, XCTest may be missing if the user has installed the Xcode Command Line Tools, but not the full Xcode IDE. XCTest is not included with the Xcode Command Line Tools package. The purpose of this change is to allow running `swift test` when XCTest is unavailable but Swift Testing _is_ available.
1 parent 4fe1d63 commit 00095ed

File tree

3 files changed

+34
-29
lines changed

3 files changed

+34
-29
lines changed

Sources/Commands/PackageCommands/Init.swift

+4-4
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,12 @@ extension SwiftPackageCommand {
6060
// For macros this is reversed, since we don't support testing
6161
// macros with Swift Testing yet.
6262
var supportedTestingLibraries = Set<BuildParameters.Testing.Library>()
63-
if testLibraryOptions.isExplicitlyEnabled(.xctest) ||
64-
(initMode == .macro && testLibraryOptions.isEnabled(.xctest)) {
63+
if testLibraryOptions.isExplicitlyEnabled(.xctest, swiftCommandState: swiftCommandState) ||
64+
(initMode == .macro && testLibraryOptions.isEnabled(.xctest, swiftCommandState: swiftCommandState)) {
6565
supportedTestingLibraries.insert(.xctest)
6666
}
67-
if testLibraryOptions.isExplicitlyEnabled(.swiftTesting) ||
68-
(initMode != .macro && testLibraryOptions.isEnabled(.swiftTesting)) {
67+
if testLibraryOptions.isExplicitlyEnabled(.swiftTesting, swiftCommandState: swiftCommandState) ||
68+
(initMode != .macro && testLibraryOptions.isEnabled(.swiftTesting, swiftCommandState: swiftCommandState)) {
6969
supportedTestingLibraries.insert(.swiftTesting)
7070
}
7171

Sources/Commands/SwiftTestCommand.swift

+15-13
Original file line numberDiff line numberDiff line change
@@ -254,8 +254,10 @@ public struct SwiftTestCommand: AsyncSwiftCommand {
254254
var results = [TestRunner.Result]()
255255

256256
// Run XCTest.
257-
if options.testLibraryOptions.isEnabled(.xctest) {
258-
// validate XCTest available on darwin based systems
257+
if options.testLibraryOptions.isEnabled(.xctest, swiftCommandState: swiftCommandState) {
258+
// Validate XCTest is available on Darwin-based systems. If it's not available and we're hitting this code
259+
// path, that means the developer must have explicitly passed --enable-xctest (or the toolchain is
260+
// corrupt, I suppose.)
259261
let toolchain = try swiftCommandState.getTargetToolchain()
260262
if case let .unsupported(reason) = try swiftCommandState.getHostToolchain().swiftSDK.xctestSupport {
261263
if let reason {
@@ -276,7 +278,7 @@ public struct SwiftTestCommand: AsyncSwiftCommand {
276278
swiftCommandState: swiftCommandState,
277279
library: .xctest
278280
)
279-
if result == .success, let testCount, testCount == 0 {
281+
if result == .success, testCount == 0 {
280282
results.append(.noMatchingTests)
281283
} else {
282284
results.append(result)
@@ -322,9 +324,9 @@ public struct SwiftTestCommand: AsyncSwiftCommand {
322324
}
323325

324326
// Run Swift Testing (parallel or not, it has a single entry point.)
325-
if options.testLibraryOptions.isEnabled(.swiftTesting) {
327+
if options.testLibraryOptions.isEnabled(.swiftTesting, swiftCommandState: swiftCommandState) {
326328
lazy var testEntryPointPath = testProducts.lazy.compactMap(\.testEntryPointPath).first
327-
if options.testLibraryOptions.isExplicitlyEnabled(.swiftTesting) || testEntryPointPath == nil {
329+
if options.testLibraryOptions.isExplicitlyEnabled(.swiftTesting, swiftCommandState: swiftCommandState) || testEntryPointPath == nil {
328330
results.append(
329331
try await runTestProducts(
330332
testProducts,
@@ -410,7 +412,7 @@ public struct SwiftTestCommand: AsyncSwiftCommand {
410412
public func run(_ swiftCommandState: SwiftCommandState) async throws {
411413
do {
412414
// Validate commands arguments
413-
try self.validateArguments(observabilityScope: swiftCommandState.observabilityScope)
415+
try self.validateArguments(swiftCommandState: swiftCommandState)
414416
} catch {
415417
swiftCommandState.observabilityScope.emit(error)
416418
throw ExitCode.failure
@@ -464,7 +466,7 @@ public struct SwiftTestCommand: AsyncSwiftCommand {
464466
}
465467
additionalArguments += commandLineArguments
466468

467-
if var xunitPath = options.xUnitOutput, options.testLibraryOptions.isEnabled(.xctest) {
469+
if var xunitPath = options.xUnitOutput, options.testLibraryOptions.isEnabled(.xctest, swiftCommandState: swiftCommandState) {
468470
// We are running Swift Testing, XCTest is also running in this session, and an xUnit path
469471
// was specified. Make sure we don't stomp on XCTest's XML output by having Swift Testing
470472
// write to a different path.
@@ -631,7 +633,7 @@ public struct SwiftTestCommand: AsyncSwiftCommand {
631633
/// Private function that validates the commands arguments
632634
///
633635
/// - Throws: if a command argument is invalid
634-
private func validateArguments(observabilityScope: ObservabilityScope) throws {
636+
private func validateArguments(swiftCommandState: SwiftCommandState) throws {
635637
// Validation for --num-workers.
636638
if let workers = options.numberOfWorkers {
637639

@@ -646,13 +648,13 @@ public struct SwiftTestCommand: AsyncSwiftCommand {
646648
throw StringError("'--num-workers' must be greater than zero")
647649
}
648650

649-
guard options.testLibraryOptions.isEnabled(.xctest) else {
651+
guard options.testLibraryOptions.isEnabled(.xctest, swiftCommandState: swiftCommandState) else {
650652
throw StringError("'--num-workers' is only supported when testing with XCTest")
651653
}
652654
}
653655

654656
if options._deprecated_shouldListTests {
655-
observabilityScope.emit(warning: "'--list-tests' option is deprecated; use 'swift test list' instead")
657+
swiftCommandState.observabilityScope.emit(warning: "'--list-tests' option is deprecated; use 'swift test list' instead")
656658
}
657659
}
658660

@@ -733,7 +735,7 @@ extension SwiftTestCommand {
733735
library: .swiftTesting
734736
)
735737

736-
if testLibraryOptions.isEnabled(.xctest) {
738+
if testLibraryOptions.isEnabled(.xctest, swiftCommandState: swiftCommandState) {
737739
let testSuites = try TestingSupport.getTestSuites(
738740
in: testProducts,
739741
swiftCommandState: swiftCommandState,
@@ -749,9 +751,9 @@ extension SwiftTestCommand {
749751
}
750752
}
751753

752-
if testLibraryOptions.isEnabled(.swiftTesting) {
754+
if testLibraryOptions.isEnabled(.swiftTesting, swiftCommandState: swiftCommandState) {
753755
lazy var testEntryPointPath = testProducts.lazy.compactMap(\.testEntryPointPath).first
754-
if testLibraryOptions.isExplicitlyEnabled(.swiftTesting) || testEntryPointPath == nil {
756+
if testLibraryOptions.isExplicitlyEnabled(.swiftTesting, swiftCommandState: swiftCommandState) || testEntryPointPath == nil {
755757
let additionalArguments = ["--list-tests"] + CommandLine.arguments.dropFirst()
756758
let runner = TestRunner(
757759
bundlePaths: testProducts.map(\.binaryPath),

Sources/CoreCommands/Options.swift

+15-12
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import struct PackageModel.EnabledSanitizers
2525
import struct PackageModel.PackageIdentity
2626
import class PackageModel.Manifest
2727
import enum PackageModel.Sanitizer
28+
@_spi(SwiftPMInternal) import struct PackageModel.SwiftSDK
2829

2930
import struct SPMBuildCore.BuildParameters
3031

@@ -596,28 +597,30 @@ public struct TestLibraryOptions: ParsableArguments {
596597
help: .private)
597598
public var explicitlyEnableExperimentalSwiftTestingLibrarySupport: Bool?
598599

599-
private func isEnabled(_ library: BuildParameters.Testing.Library, `default`: Bool) -> Bool {
600+
private func isEnabled(_ library: BuildParameters.Testing.Library, `default`: Bool, swiftCommandState: SwiftCommandState) -> Bool {
600601
switch library {
601602
case .xctest:
602-
explicitlyEnableXCTestSupport ?? `default`
603+
if let explicitlyEnableXCTestSupport {
604+
return explicitlyEnableXCTestSupport
605+
}
606+
if let toolchain = try? swiftCommandState.getHostToolchain(),
607+
toolchain.swiftSDK.xctestSupport == .supported {
608+
return `default`
609+
}
610+
return false
603611
case .swiftTesting:
604-
explicitlyEnableSwiftTestingLibrarySupport ?? explicitlyEnableExperimentalSwiftTestingLibrarySupport ?? `default`
612+
return explicitlyEnableSwiftTestingLibrarySupport ?? explicitlyEnableExperimentalSwiftTestingLibrarySupport ?? `default`
605613
}
606614
}
607615

608616
/// Test whether or not a given library is enabled.
609-
public func isEnabled(_ library: BuildParameters.Testing.Library) -> Bool {
610-
isEnabled(library, default: true)
617+
public func isEnabled(_ library: BuildParameters.Testing.Library, swiftCommandState: SwiftCommandState) -> Bool {
618+
isEnabled(library, default: true, swiftCommandState: swiftCommandState)
611619
}
612620

613621
/// Test whether or not a given library was explicitly enabled by the developer.
614-
public func isExplicitlyEnabled(_ library: BuildParameters.Testing.Library) -> Bool {
615-
isEnabled(library, default: false)
616-
}
617-
618-
/// The list of enabled testing libraries.
619-
public var enabledTestingLibraries: [BuildParameters.Testing.Library] {
620-
[.xctest, .swiftTesting].filter(isEnabled)
622+
public func isExplicitlyEnabled(_ library: BuildParameters.Testing.Library, swiftCommandState: SwiftCommandState) -> Bool {
623+
isEnabled(library, default: false, swiftCommandState: swiftCommandState)
621624
}
622625
}
623626

0 commit comments

Comments
 (0)