Skip to content

Commit 3fb4d81

Browse files
authored
Integration with swift-testing (#7047)
1 parent 0849e9d commit 3fb4d81

File tree

9 files changed

+456
-238
lines changed

9 files changed

+456
-238
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// swift-tools-version: 5.10
2+
import PackageDescription
3+
4+
let package = Package(
5+
name: "SwiftTesting",
6+
platforms: [
7+
.macOS(.v13), .iOS(.v16), .watchOS(.v9), .tvOS(.v16), .visionOS(.v1)
8+
],
9+
dependencies: [
10+
.package(url: "https://github.com/apple/swift-testing.git", branch: "main"),
11+
],
12+
targets: [
13+
.testTarget(
14+
name: "SwiftTestingTests",
15+
dependencies: [.product(name: "Testing", package: "swift-testing"),]
16+
),
17+
]
18+
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import Testing
2+
@Test("SOME TEST FUNCTION") func someTestFunction() {}

Sources/Build/BuildOperationBuildSystemDelegateHandler.swift

Lines changed: 99 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -109,73 +109,74 @@ final class TestDiscoveryCommand: CustomLLBuildCommand, TestBuildCommand {
109109
}
110110

111111
private func execute(fileSystem: Basics.FileSystem, tool: TestDiscoveryTool) throws {
112-
let index = self.context.buildParameters.indexStore
113-
let api = try self.context.indexStoreAPI.get()
114-
let store = try IndexStore.open(store: TSCAbsolutePath(index), api: api)
115-
116-
// FIXME: We can speed this up by having one llbuild command per object file.
117-
let tests = try store.listTests(in: tool.inputs.map { try TSCAbsolutePath(AbsolutePath(validating: $0.name)) })
118-
119112
let outputs = tool.outputs.compactMap { try? AbsolutePath(validating: $0.name) }
120-
let testsByModule = Dictionary(grouping: tests, by: { $0.module.spm_mangledToC99ExtendedIdentifier() })
121-
122-
func isMainFile(_ path: AbsolutePath) -> Bool {
123-
path.basename == TestDiscoveryTool.mainFileName
124-
}
125113

126-
var maybeMainFile: AbsolutePath?
127-
// Write one file for each test module.
128-
//
129-
// We could write everything in one file but that can easily run into type conflicts due
130-
// in complex packages with large number of test targets.
131-
for file in outputs {
132-
if maybeMainFile == nil && isMainFile(file) {
133-
maybeMainFile = file
134-
continue
114+
switch self.context.buildParameters.testingParameters.library {
115+
case .swiftTesting:
116+
for file in outputs {
117+
try fileSystem.writeIfChanged(path: file, string: "")
135118
}
119+
case .xctest:
120+
let index = self.context.buildParameters.indexStore
121+
let api = try self.context.indexStoreAPI.get()
122+
let store = try IndexStore.open(store: TSCAbsolutePath(index), api: api)
136123

137-
// FIXME: This is relying on implementation detail of the output but passing the
138-
// the context all the way through is not worth it right now.
139-
let module = file.basenameWithoutExt.spm_mangledToC99ExtendedIdentifier()
124+
// FIXME: We can speed this up by having one llbuild command per object file.
125+
let tests = try store.listTests(in: tool.inputs.map { try TSCAbsolutePath(AbsolutePath(validating: $0.name)) })
140126

141-
guard let tests = testsByModule[module] else {
142-
// This module has no tests so just write an empty file for it.
143-
try fileSystem.writeFileContents(file, bytes: "")
144-
continue
127+
let testsByModule = Dictionary(grouping: tests, by: { $0.module.spm_mangledToC99ExtendedIdentifier() })
128+
129+
// Find the main file path.
130+
guard let mainFile = outputs.first(where: { path in
131+
path.basename == TestDiscoveryTool.mainFileName
132+
}) else {
133+
throw InternalError("main output (\(TestDiscoveryTool.mainFileName)) not found")
145134
}
146-
try write(
147-
tests: tests,
148-
forModule: module,
149-
fileSystem: fileSystem,
150-
path: file
151-
)
152-
}
153135

154-
guard let mainFile = maybeMainFile else {
155-
throw InternalError("main output (\(TestDiscoveryTool.mainFileName)) not found")
156-
}
136+
// Write one file for each test module.
137+
//
138+
// We could write everything in one file but that can easily run into type conflicts due
139+
// in complex packages with large number of test targets.
140+
for file in outputs where file != mainFile {
141+
// FIXME: This is relying on implementation detail of the output but passing the
142+
// the context all the way through is not worth it right now.
143+
let module = file.basenameWithoutExt.spm_mangledToC99ExtendedIdentifier()
144+
145+
guard let tests = testsByModule[module] else {
146+
// This module has no tests so just write an empty file for it.
147+
try fileSystem.writeFileContents(file, bytes: "")
148+
continue
149+
}
150+
try write(
151+
tests: tests,
152+
forModule: module,
153+
fileSystem: fileSystem,
154+
path: file
155+
)
156+
}
157157

158-
let testsKeyword = tests.isEmpty ? "let" : "var"
158+
let testsKeyword = tests.isEmpty ? "let" : "var"
159159

160-
// Write the main file.
161-
let stream = try LocalFileOutputByteStream(mainFile)
160+
// Write the main file.
161+
let stream = try LocalFileOutputByteStream(mainFile)
162162

163-
stream.send(
164-
#"""
165-
import XCTest
163+
stream.send(
164+
#"""
165+
import XCTest
166166
167-
@available(*, deprecated, message: "Not actually deprecated. Marked as deprecated to allow inclusion of deprecated tests (which test deprecated functionality) without warnings")
168-
public func __allDiscoveredTests() -> [XCTestCaseEntry] {
169-
\#(testsKeyword) tests = [XCTestCaseEntry]()
167+
@available(*, deprecated, message: "Not actually deprecated. Marked as deprecated to allow inclusion of deprecated tests (which test deprecated functionality) without warnings")
168+
public func __allDiscoveredTests() -> [XCTestCaseEntry] {
169+
\#(testsKeyword) tests = [XCTestCaseEntry]()
170170
171-
\#(testsByModule.keys.map { "tests += __\($0)__allTests()" }.joined(separator: "\n "))
171+
\#(testsByModule.keys.map { "tests += __\($0)__allTests()" }.joined(separator: "\n "))
172172

173-
return tests
174-
}
175-
"""#
176-
)
173+
return tests
174+
}
175+
"""#
176+
)
177177

178-
stream.flush()
178+
stream.flush()
179+
}
179180
}
180181

181182
override func execute(
@@ -201,10 +202,6 @@ final class TestDiscoveryCommand: CustomLLBuildCommand, TestBuildCommand {
201202

202203
final class TestEntryPointCommand: CustomLLBuildCommand, TestBuildCommand {
203204
private func execute(fileSystem: Basics.FileSystem, tool: TestEntryPointTool) throws {
204-
// Find the inputs, which are the names of the test discovery module(s)
205-
let inputs = tool.inputs.compactMap { try? AbsolutePath(validating: $0.name) }
206-
let discoveryModuleNames = inputs.map(\.basenameWithoutExt)
207-
208205
let outputs = tool.outputs.compactMap { try? AbsolutePath(validating: $0.name) }
209206

210207
// Find the main output file
@@ -214,34 +211,57 @@ final class TestEntryPointCommand: CustomLLBuildCommand, TestBuildCommand {
214211
throw InternalError("main file output (\(TestEntryPointTool.mainFileName)) not found")
215212
}
216213

217-
let testObservabilitySetup: String
218-
if self.context.buildParameters.testingParameters.experimentalTestOutput
219-
&& self.context.buildParameters.targetTriple.supportsTestSummary {
220-
testObservabilitySetup = "_ = SwiftPMXCTestObserver()\n"
221-
} else {
222-
testObservabilitySetup = ""
223-
}
224-
225214
// Write the main file.
226215
let stream = try LocalFileOutputByteStream(mainFile)
227216

228-
stream.send(
229-
#"""
230-
\#(generateTestObservationCode(buildParameters: self.context.buildParameters))
231-
232-
import XCTest
233-
\#(discoveryModuleNames.map { "import \($0)" }.joined(separator: "\n"))
217+
switch self.context.buildParameters.testingParameters.library {
218+
case .swiftTesting:
219+
stream.send(
220+
#"""
221+
#if canImport(Testing)
222+
@_spi(SwiftPackageManagerSupport) import Testing
223+
#endif
234224
235-
@main
236-
@available(*, deprecated, message: "Not actually deprecated. Marked as deprecated to allow inclusion of deprecated tests (which test deprecated functionality) without warnings")
237-
struct Runner {
238-
static func main() {
239-
\#(testObservabilitySetup)
240-
XCTMain(__allDiscoveredTests())
225+
@main struct Runner {
226+
static func main() async {
227+
#if canImport(Testing)
228+
await Testing.swiftPMEntryPoint() as Never
229+
#endif
230+
}
241231
}
232+
"""#
233+
)
234+
case .xctest:
235+
// Find the inputs, which are the names of the test discovery module(s)
236+
let inputs = tool.inputs.compactMap { try? AbsolutePath(validating: $0.name) }
237+
let discoveryModuleNames = inputs.map(\.basenameWithoutExt)
238+
239+
let testObservabilitySetup: String
240+
if self.context.buildParameters.testingParameters.experimentalTestOutput
241+
&& self.context.buildParameters.targetTriple.supportsTestSummary {
242+
testObservabilitySetup = "_ = SwiftPMXCTestObserver()\n"
243+
} else {
244+
testObservabilitySetup = ""
242245
}
243-
"""#
244-
)
246+
247+
stream.send(
248+
#"""
249+
\#(generateTestObservationCode(buildParameters: self.context.buildParameters))
250+
251+
import XCTest
252+
\#(discoveryModuleNames.map { "import \($0)" }.joined(separator: "\n"))
253+
254+
@main
255+
@available(*, deprecated, message: "Not actually deprecated. Marked as deprecated to allow inclusion of deprecated tests (which test deprecated functionality) without warnings")
256+
struct Runner {
257+
static func main() {
258+
\#(testObservabilitySetup)
259+
XCTMain(__allDiscoveredTests()) as Never
260+
}
261+
}
262+
"""#
263+
)
264+
}
245265

246266
stream.flush()
247267
}

0 commit comments

Comments
 (0)