Skip to content

Commit cc032ce

Browse files
committed
On Darwin, check for presence of XCTest when swift test is called.
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 bc9a9e3 commit cc032ce

File tree

3 files changed

+32
-27
lines changed

3 files changed

+32
-27
lines changed

Sources/Commands/PackageCommands/Init.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,10 @@ extension SwiftPackageCommand {
6060
// but Swift Testing must remain off by default until it is present
6161
// in the Swift toolchain.
6262
var supportedTestingLibraries = Set<TestingLibrary>()
63-
if testLibraryOptions.isEnabled(.xctest) {
63+
if testLibraryOptions.isEnabled(.xctest, swiftCommandState: swiftCommandState) {
6464
supportedTestingLibraries.insert(.xctest)
6565
}
66-
if testLibraryOptions.isExplicitlyEnabled(.swiftTesting) {
66+
if testLibraryOptions.isExplicitlyEnabled(.swiftTesting, swiftCommandState: swiftCommandState) {
6767
supportedTestingLibraries.insert(.swiftTesting)
6868
}
6969

Sources/Commands/SwiftTestCommand.swift

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

258258
// Run XCTest.
259-
if options.testLibraryOptions.isEnabled(.xctest) {
260-
// validate XCTest available on darwin based systems
259+
if options.testLibraryOptions.isEnabled(.xctest, swiftCommandState: swiftCommandState) {
260+
// Validate XCTest available on Darwin-based systems. If it's not available and we're hitting this code
261+
// path, that means the developer must have explicitly passed --enable-xctest (or the toolchain is
262+
// corrupt, I suppose.)
261263
let toolchain = try swiftCommandState.getTargetToolchain()
262264
if case let .unsupported(reason) = try swiftCommandState.getHostToolchain().swiftSDK.xctestSupport {
263265
if let reason {
@@ -278,7 +280,7 @@ public struct SwiftTestCommand: AsyncSwiftCommand {
278280
swiftCommandState: swiftCommandState,
279281
library: .xctest
280282
)
281-
if result == .success, let testCount, testCount == 0 {
283+
if result == .success, testCount == 0 {
282284
results.append(.noMatchingTests)
283285
} else {
284286
results.append(result)
@@ -324,9 +326,9 @@ public struct SwiftTestCommand: AsyncSwiftCommand {
324326
}
325327

326328
// Run Swift Testing (parallel or not, it has a single entry point.)
327-
if options.testLibraryOptions.isEnabled(.swiftTesting) {
329+
if options.testLibraryOptions.isEnabled(.swiftTesting, swiftCommandState: swiftCommandState) {
328330
lazy var testEntryPointPath = testProducts.lazy.compactMap(\.testEntryPointPath).first
329-
if options.testLibraryOptions.isExplicitlyEnabled(.swiftTesting) || testEntryPointPath == nil {
331+
if options.testLibraryOptions.isExplicitlyEnabled(.swiftTesting, swiftCommandState: swiftCommandState) || testEntryPointPath == nil {
330332
results.append(
331333
try await runTestProducts(
332334
testProducts,
@@ -412,7 +414,7 @@ public struct SwiftTestCommand: AsyncSwiftCommand {
412414
public func run(_ swiftCommandState: SwiftCommandState) async throws {
413415
do {
414416
// Validate commands arguments
415-
try self.validateArguments(observabilityScope: swiftCommandState.observabilityScope)
417+
try self.validateArguments(swiftCommandState: swiftCommandState)
416418
} catch {
417419
swiftCommandState.observabilityScope.emit(error)
418420
throw ExitCode.failure
@@ -466,7 +468,7 @@ public struct SwiftTestCommand: AsyncSwiftCommand {
466468
}
467469
additionalArguments += commandLineArguments
468470

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

@@ -649,13 +651,13 @@ public struct SwiftTestCommand: AsyncSwiftCommand {
649651
throw StringError("'--num-workers' must be greater than zero")
650652
}
651653

652-
guard options.testLibraryOptions.isEnabled(.xctest) else {
654+
guard options.testLibraryOptions.isEnabled(.xctest, swiftCommandState: swiftCommandState) else {
653655
throw StringError("'--num-workers' is only supported when testing with XCTest")
654656
}
655657
}
656658

657659
if options._deprecated_shouldListTests {
658-
observabilityScope.emit(warning: "'--list-tests' option is deprecated; use 'swift test list' instead")
660+
swiftCommandState.observabilityScope.emit(warning: "'--list-tests' option is deprecated; use 'swift test list' instead")
659661
}
660662
}
661663

@@ -739,7 +741,7 @@ extension SwiftTestCommand {
739741
library: .swiftTesting
740742
)
741743

742-
if testLibraryOptions.isEnabled(.xctest) {
744+
if testLibraryOptions.isEnabled(.xctest, swiftCommandState: swiftCommandState) {
743745
let testSuites = try TestingSupport.getTestSuites(
744746
in: testProducts,
745747
swiftCommandState: swiftCommandState,
@@ -755,9 +757,9 @@ extension SwiftTestCommand {
755757
}
756758
}
757759

758-
if testLibraryOptions.isEnabled(.swiftTesting) {
760+
if testLibraryOptions.isEnabled(.swiftTesting, swiftCommandState: swiftCommandState) {
759761
lazy var testEntryPointPath = testProducts.lazy.compactMap(\.testEntryPointPath).first
760-
if testLibraryOptions.isExplicitlyEnabled(.swiftTesting) || testEntryPointPath == nil {
762+
if testLibraryOptions.isExplicitlyEnabled(.swiftTesting, swiftCommandState: swiftCommandState) || testEntryPointPath == nil {
761763
let additionalArguments = ["--list-tests"] + CommandLine.arguments.dropFirst()
762764
let runner = TestRunner(
763765
bundlePaths: testProducts.map(\.binaryPath),

Sources/CoreCommands/Options.swift

+15-12
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import struct PackageModel.EnabledSanitizers
2626
import struct PackageModel.PackageIdentity
2727
import class PackageModel.Manifest
2828
import enum PackageModel.Sanitizer
29+
@_spi(SwiftPMInternal) import struct PackageModel.SwiftSDK
2930

3031
import struct PackageGraph.TraitConfiguration
3132

@@ -598,28 +599,30 @@ public struct TestLibraryOptions: ParsableArguments {
598599
help: .private)
599600
public var explicitlyEnableExperimentalSwiftTestingLibrarySupport: Bool?
600601

601-
private func isEnabled(_ library: TestingLibrary, `default`: Bool) -> Bool {
602+
private func isEnabled(_ library: TestingLibrary, `default`: Bool, swiftCommandState: SwiftCommandState) -> Bool {
602603
switch library {
603604
case .xctest:
604-
explicitlyEnableXCTestSupport ?? `default`
605+
if let explicitlyEnableXCTestSupport {
606+
return explicitlyEnableXCTestSupport
607+
}
608+
if let toolchain = try? swiftCommandState.getHostToolchain(),
609+
toolchain.swiftSDK.xctestSupport == .supported {
610+
return `default`
611+
}
612+
return false
605613
case .swiftTesting:
606-
explicitlyEnableSwiftTestingLibrarySupport ?? explicitlyEnableExperimentalSwiftTestingLibrarySupport ?? `default`
614+
return explicitlyEnableSwiftTestingLibrarySupport ?? explicitlyEnableExperimentalSwiftTestingLibrarySupport ?? `default`
607615
}
608616
}
609617

610618
/// Test whether or not a given library is enabled.
611-
public func isEnabled(_ library: TestingLibrary) -> Bool {
612-
isEnabled(library, default: true)
619+
public func isEnabled(_ library: TestingLibrary, swiftCommandState: SwiftCommandState) -> Bool {
620+
isEnabled(library, default: true, swiftCommandState: swiftCommandState)
613621
}
614622

615623
/// Test whether or not a given library was explicitly enabled by the developer.
616-
public func isExplicitlyEnabled(_ library: TestingLibrary) -> Bool {
617-
isEnabled(library, default: false)
618-
}
619-
620-
/// The list of enabled testing libraries.
621-
public var enabledTestingLibraries: [TestingLibrary] {
622-
[.xctest, .swiftTesting].filter(isEnabled)
624+
public func isExplicitlyEnabled(_ library: TestingLibrary, swiftCommandState: SwiftCommandState) -> Bool {
625+
isEnabled(library, default: false, swiftCommandState: swiftCommandState)
623626
}
624627
}
625628

0 commit comments

Comments
 (0)