Skip to content

Commit 133a610

Browse files
committed
Reapply "Allow opting into Swift Testing entrypoints for macOS test bundles when building with the SwiftPM CLI" (#727)
This reverts commit d91213d.
1 parent e7e99d5 commit 133a610

File tree

6 files changed

+147
-55
lines changed

6 files changed

+147
-55
lines changed

Sources/SWBCore/Settings/BuiltinMacros.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -726,6 +726,7 @@ public final class BuiltinMacros {
726726
public static let GENERATE_RESOURCE_ACCESSORS = BuiltinMacros.declareBooleanMacro("GENERATE_RESOURCE_ACCESSORS")
727727
public static let GENERATE_TEST_ENTRY_POINT = BuiltinMacros.declareBooleanMacro("GENERATE_TEST_ENTRY_POINT")
728728
public static let GENERATED_TEST_ENTRY_POINT_PATH = BuiltinMacros.declarePathMacro("GENERATED_TEST_ENTRY_POINT_PATH")
729+
public static let GENERATED_TEST_ENTRY_POINT_INCLUDE_DISCOVERED_TESTS = BuiltinMacros.declareBooleanMacro("GENERATED_TEST_ENTRY_POINT_INCLUDE_DISCOVERED_TESTS")
729730
public static let GENERATE_TEXT_BASED_STUBS = BuiltinMacros.declareBooleanMacro("GENERATE_TEXT_BASED_STUBS")
730731
public static let GENERATE_INTERMEDIATE_TEXT_BASED_STUBS = BuiltinMacros.declareBooleanMacro("GENERATE_INTERMEDIATE_TEXT_BASED_STUBS")
731732
public static let GLOBAL_API_NOTES_PATH = BuiltinMacros.declareStringMacro("GLOBAL_API_NOTES_PATH")
@@ -1792,6 +1793,7 @@ public final class BuiltinMacros {
17921793
GENERATE_RESOURCE_ACCESSORS,
17931794
GENERATE_TEST_ENTRY_POINT,
17941795
GENERATED_TEST_ENTRY_POINT_PATH,
1796+
GENERATED_TEST_ENTRY_POINT_INCLUDE_DISCOVERED_TESTS,
17951797
GENERATE_TEXT_BASED_STUBS,
17961798
GENERATE_INTERMEDIATE_TEXT_BASED_STUBS,
17971799
GID,

Sources/SWBUniversalPlatform/Specs/ProductTypes.xcspec

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,9 @@
335335

336336
PROVISIONING_PROFILE_SUPPORTED = YES;
337337
PROVISIONING_PROFILE_REQUIRED = NO;
338+
339+
GENERATE_TEST_ENTRY_POINT = "$(GENERATE_TEST_ENTRYPOINTS_FOR_BUNDLES)";
340+
GENERATED_TEST_ENTRY_POINT_PATH = "$(DERIVED_SOURCES_DIR)/test_entry_point.swift";
338341
};
339342
PackageTypes = (
340343
com.apple.package-type.bundle.unit-test
@@ -353,6 +356,7 @@
353356
ENABLE_TESTING_SEARCH_PATHS = YES;
354357
GENERATE_TEST_ENTRY_POINT = YES;
355358
GENERATED_TEST_ENTRY_POINT_PATH = "$(DERIVED_SOURCES_DIR)/test_entry_point.swift";
359+
GENERATED_TEST_ENTRY_POINT_INCLUDE_DISCOVERED_TESTS = YES;
356360
};
357361
PackageTypes = (
358362
com.apple.package-type.mach-o-executable

Sources/SWBUniversalPlatform/TestEntryPointGenerationTaskAction.swift

Lines changed: 55 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -25,24 +25,30 @@ class TestEntryPointGenerationTaskAction: TaskAction {
2525
let options = try Options.parse(Array(task.commandLineAsStrings.dropFirst()))
2626

2727
var tests: [IndexStore.TestCaseClass] = []
28-
var objects: [Path] = []
29-
for linkerFilelist in options.linkerFilelist {
30-
let filelistContents = String(String(decoding: try executionDelegate.fs.read(linkerFilelist), as: UTF8.self))
31-
let entries = filelistContents.split(separator: "\n", omittingEmptySubsequences: true).map { Path($0) }.map {
32-
for indexUnitBasePath in options.indexUnitBasePath {
33-
if let remappedPath = generateIndexOutputPath(from: $0, basePath: indexUnitBasePath) {
34-
return remappedPath
28+
if options.discoverTests {
29+
var objects: [Path] = []
30+
for linkerFilelist in options.linkerFilelist {
31+
let filelistContents = String(String(decoding: try executionDelegate.fs.read(linkerFilelist), as: UTF8.self))
32+
let entries = filelistContents.split(separator: "\n", omittingEmptySubsequences: true).map { Path($0) }.map {
33+
for indexUnitBasePath in options.indexUnitBasePath {
34+
if let remappedPath = generateIndexOutputPath(from: $0, basePath: indexUnitBasePath) {
35+
return remappedPath
36+
}
3537
}
38+
return $0
3639
}
37-
return $0
40+
objects.append(contentsOf: entries)
41+
}
42+
guard let indexStoreLibraryPath = options.indexStoreLibraryPath else {
43+
outputDelegate.emitError("Test discovery was requested, but failed to lookup index store library in toolchain")
44+
return .failed
45+
}
46+
let indexStoreAPI = try IndexStoreAPI(dylib: indexStoreLibraryPath)
47+
for indexStore in options.indexStore {
48+
let store = try IndexStore.open(store: indexStore, api: indexStoreAPI)
49+
let testInfo = try store.listTests(in: objects)
50+
tests.append(contentsOf: testInfo)
3851
}
39-
objects.append(contentsOf: entries)
40-
}
41-
let indexStoreAPI = try IndexStoreAPI(dylib: options.indexStoreLibraryPath)
42-
for indexStore in options.indexStore {
43-
let store = try IndexStore.open(store: indexStore, api: indexStoreAPI)
44-
let testInfo = try store.listTests(in: objects)
45-
tests.append(contentsOf: testInfo)
4652
}
4753

4854
try executionDelegate.fs.write(options.output, contents: ByteString(encodingAsUTF8: """
@@ -52,8 +58,8 @@ class TestEntryPointGenerationTaskAction: TaskAction {
5258
5359
\(testObservationFragment)
5460
55-
import XCTest
56-
\(discoveredTestsFragment(tests: tests))
61+
public import XCTest
62+
\(discoveredTestsFragment(tests: tests, options: options))
5763
5864
@main
5965
@available(macOS 10.15, iOS 11, watchOS 4, tvOS 11, visionOS 1, *)
@@ -94,16 +100,7 @@ class TestEntryPointGenerationTaskAction: TaskAction {
94100
}
95101
}
96102
#endif
97-
if testingLibrary == "xctest" {
98-
#if !os(Windows) && \(options.enableExperimentalTestOutput)
99-
_ = Self.testOutputPath().map { SwiftPMXCTestObserver(testOutputPath: testOutputPath) }
100-
#endif
101-
#if os(WASI)
102-
await XCTMain(__allDiscoveredTests()) as Never
103-
#else
104-
XCTMain(__allDiscoveredTests()) as Never
105-
#endif
106-
}
103+
\(xctestFragment(enableExperimentalTestOutput: options.enableExperimentalTestOutput, disable: !options.discoverTests))
107104
}
108105
#else
109106
static func main() async {
@@ -113,16 +110,7 @@ class TestEntryPointGenerationTaskAction: TaskAction {
113110
await Testing.__swiftPMEntryPoint() as Never
114111
}
115112
#endif
116-
if testingLibrary == "xctest" {
117-
#if !os(Windows) && \(options.enableExperimentalTestOutput)
118-
_ = Self.testOutputPath().map { SwiftPMXCTestObserver(testOutputPath: testOutputPath) }
119-
#endif
120-
#if os(WASI)
121-
await XCTMain(__allDiscoveredTests()) as Never
122-
#else
123-
XCTMain(__allDiscoveredTests()) as Never
124-
#endif
125-
}
113+
\(xctestFragment(enableExperimentalTestOutput: options.enableExperimentalTestOutput, disable: !options.discoverTests))
126114
}
127115
#endif
128116
}
@@ -137,14 +125,18 @@ class TestEntryPointGenerationTaskAction: TaskAction {
137125

138126
private struct Options: ParsableArguments {
139127
@Option var output: Path
140-
@Option var indexStoreLibraryPath: Path
141-
@Option var linkerFilelist: [Path]
142-
@Option var indexStore: [Path]
143-
@Option var indexUnitBasePath: [Path]
128+
@Option var indexStoreLibraryPath: Path? = nil
129+
@Option() var linkerFilelist: [Path] = []
130+
@Option var indexStore: [Path] = []
131+
@Option var indexUnitBasePath: [Path] = []
144132
@Flag var enableExperimentalTestOutput: Bool = false
133+
@Flag var discoverTests: Bool = false
145134
}
146135

147-
private func discoveredTestsFragment(tests: [IndexStore.TestCaseClass]) -> String {
136+
private func discoveredTestsFragment(tests: [IndexStore.TestCaseClass], options: Options) -> String {
137+
guard options.discoverTests else {
138+
return ""
139+
}
148140
var fragment = ""
149141
for moduleName in Set(tests.map { $0.module }).sorted() {
150142
fragment += "@testable import \(moduleName)\n"
@@ -174,11 +166,29 @@ class TestEntryPointGenerationTaskAction: TaskAction {
174166
return fragment
175167
}
176168

169+
private func xctestFragment(enableExperimentalTestOutput: Bool, disable: Bool) -> String {
170+
guard !disable else {
171+
return ""
172+
}
173+
return """
174+
if testingLibrary == "xctest" {
175+
#if !os(Windows) && \(enableExperimentalTestOutput)
176+
_ = Self.testOutputPath().map { SwiftPMXCTestObserver(testOutputPath: testOutputPath) }
177+
#endif
178+
#if os(WASI)
179+
await XCTMain(__allDiscoveredTests()) as Never
180+
#else
181+
XCTMain(__allDiscoveredTests()) as Never
182+
#endif
183+
}
184+
"""
185+
}
186+
177187
private var testObservationFragment: String =
178188
"""
179189
#if !os(Windows) // Test observation is not supported on Windows
180-
import Foundation
181-
import XCTest
190+
public import Foundation
191+
public import XCTest
182192
183193
public final class SwiftPMXCTestObserver: NSObject {
184194
let testOutputPath: String
@@ -562,7 +572,7 @@ class TestEntryPointGenerationTaskAction: TaskAction {
562572
}
563573
}
564574
565-
import XCTest
575+
public import XCTest
566576
567577
#if canImport(Darwin) // XCTAttachment is unavailable in swift-corelibs-xctest.
568578
extension TestAttachment {

Sources/SWBUniversalPlatform/TestEntryPointGenerationTool.swift

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,13 @@ final class TestEntryPointGenerationToolSpec: GenericCommandLineToolSpec, SpecId
1919

2020
override func commandLineFromTemplate(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate, optionContext: (any DiscoveredCommandLineToolSpecInfo)?, specialArgs: [String] = [], lookup: ((MacroDeclaration) -> MacroExpression?)? = nil) async -> [CommandLineArgument] {
2121
var args = await super.commandLineFromTemplate(cbc, delegate, optionContext: optionContext, specialArgs: specialArgs, lookup: lookup)
22-
for (toolchainPath, toolchainLibrarySearchPath) in cbc.producer.toolchains.map({ ($0.path, $0.librarySearchPaths) }) {
23-
if let path = toolchainLibrarySearchPath.findLibrary(operatingSystem: cbc.producer.hostOperatingSystem, basename: "IndexStore") {
24-
args.append(contentsOf: ["--index-store-library-path", .path(path)])
22+
if cbc.scope.evaluate(BuiltinMacros.GENERATED_TEST_ENTRY_POINT_INCLUDE_DISCOVERED_TESTS) {
23+
args.append("--discover-tests")
24+
for toolchainLibrarySearchPath in cbc.producer.toolchains.map({ $0.librarySearchPaths }) {
25+
if let path = toolchainLibrarySearchPath.findLibrary(operatingSystem: cbc.producer.hostOperatingSystem, basename: "IndexStore") {
26+
args.append(contentsOf: ["--index-store-library-path", .path(path)])
27+
break
28+
}
2529
}
2630
for input in cbc.inputs {
2731
if input.fileType.conformsTo(identifier: "text") {
@@ -43,12 +47,14 @@ final class TestEntryPointGenerationToolSpec: GenericCommandLineToolSpec, SpecId
4347
public func constructTasks(_ cbc: CommandBuildContext, _ delegate: any TaskGenerationDelegate, indexStorePaths: [Path], indexUnitBasePaths: [Path]) async {
4448
var commandLine = await commandLineFromTemplate(cbc, delegate, optionContext: nil)
4549

46-
for indexStorePath in indexStorePaths {
47-
commandLine.append(contentsOf: ["--index-store", .path(indexStorePath)])
48-
}
49-
50-
for basePath in indexUnitBasePaths {
51-
commandLine.append(contentsOf: ["--index-unit-base-path", .path(basePath)])
50+
if cbc.scope.evaluate(BuiltinMacros.GENERATED_TEST_ENTRY_POINT_INCLUDE_DISCOVERED_TESTS) {
51+
for indexStorePath in indexStorePaths {
52+
commandLine.append(contentsOf: ["--index-store", .path(indexStorePath)])
53+
}
54+
55+
for basePath in indexUnitBasePaths {
56+
commandLine.append(contentsOf: ["--index-unit-base-path", .path(basePath)])
57+
}
5258
}
5359

5460
delegate.createTask(

Tests/SWBBuildSystemTests/BuildOperationTests.swift

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,76 @@ fileprivate struct BuildOperationTests: CoreBasedTests {
382382
}
383383
}
384384

385+
@Test(.requireSDKs(.macOS))
386+
func unitTestWithGeneratedEntryPointViaMacOSOverride() async throws {
387+
try await withTemporaryDirectory(removeTreeOnDeinit: false) { (tmpDir: Path) in
388+
let testProject = try await TestProject(
389+
"TestProject",
390+
sourceRoot: tmpDir,
391+
groupTree: TestGroup(
392+
"SomeFiles",
393+
children: [
394+
TestFile("test.swift"),
395+
]),
396+
buildConfigurations: [
397+
TestBuildConfiguration("Debug", buildSettings: [
398+
"ARCHS": "$(ARCHS_STANDARD)",
399+
"CODE_SIGNING_ALLOWED": "NO",
400+
"PRODUCT_NAME": "$(TARGET_NAME)",
401+
"SDKROOT": "$(HOST_PLATFORM)",
402+
"SUPPORTED_PLATFORMS": "$(HOST_PLATFORM)",
403+
"SWIFT_VERSION": swiftVersion,
404+
"INDEX_DATA_STORE_DIR": "\(tmpDir.join("index").str)",
405+
"LINKER_DRIVER": "swiftc"
406+
])
407+
],
408+
targets: [
409+
TestStandardTarget(
410+
"MyTests",
411+
type: .unitTest,
412+
buildConfigurations: [
413+
TestBuildConfiguration("Debug", buildSettings: [
414+
"GENERATE_TEST_ENTRYPOINTS_FOR_BUNDLES": "YES"
415+
])
416+
],
417+
buildPhases: [
418+
TestSourcesBuildPhase(["test.swift"]),
419+
],
420+
),
421+
])
422+
let core = try await getCore()
423+
let tester = try await BuildOperationTester(core, testProject, simulated: false)
424+
try localFS.createDirectory(tmpDir.join("index"))
425+
let projectDir = tester.workspace.projects[0].sourceRoot
426+
427+
try await tester.fs.writeFileContents(projectDir.join("test.swift")) { stream in
428+
stream <<< """
429+
import Testing
430+
import XCTest
431+
@Suite struct MySuite {
432+
@Test func myTest() {
433+
#expect(42 == 42)
434+
}
435+
}
436+
437+
final class MYXCTests: XCTestCase {
438+
func testFoo() {
439+
XCTAssertTrue(true)
440+
}
441+
}
442+
"""
443+
}
444+
445+
let destination: RunDestinationInfo = .host
446+
try await tester.checkBuild(runDestination: destination, persistent: true) { results in
447+
results.checkNoErrors()
448+
results.checkTask(.matchRuleType("GenerateTestEntryPoint")) { task in
449+
task.checkCommandLineMatches(["builtin-generateTestEntryPoint", "--output", .suffix("test_entry_point.swift")])
450+
}
451+
}
452+
}
453+
}
454+
385455
@Test(.requireSDKs(.host), .skipHostOS(.macOS), .skipHostOS(.windows, "cannot find testing library"))
386456
func unitTestWithGeneratedEntryPoint() async throws {
387457
try await withTemporaryDirectory(removeTreeOnDeinit: false) { (tmpDir: Path) in

Tests/SWBTaskConstructionTests/UnitTestTaskConstructionTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ fileprivate struct UnitTestTaskConstructionTests: CoreBasedTests {
366366
await tester.checkBuild(runDestination: .linux, fs: fs) { results in
367367
results.checkTarget("UnitTestRunner") { target in
368368
results.checkTask(.matchTarget(target), .matchRuleType("GenerateTestEntryPoint")) { task in
369-
task.checkCommandLineMatches([.suffix("builtin-generateTestEntryPoint"), "--output", .suffix("test_entry_point.swift"), "--index-store-library-path", .suffix("libIndexStore.so"), "--linker-filelist", .suffix("UnitTestTarget.LinkFileList"), "--index-store", "/index", "--index-unit-base-path", "/tmp/Test/aProject/build"])
369+
task.checkCommandLineMatches([.suffix("builtin-generateTestEntryPoint"), "--output", .suffix("test_entry_point.swift"), "--discover-tests", "--index-store-library-path", .suffix("libIndexStore.so"), "--linker-filelist", .suffix("UnitTestTarget.LinkFileList"), "--index-store", "/index", "--index-unit-base-path", "/tmp/Test/aProject/build"])
370370
task.checkInputs([
371371
.pathPattern(.suffix("UnitTestTarget.LinkFileList")),
372372
.pathPattern(.suffix("UnitTestTarget.so")),

0 commit comments

Comments
 (0)