Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions Fixtures/Miscellaneous/TestDiscovery/SwiftTesting/Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// swift-tools-version: 5.10
import PackageDescription

let package = Package(
name: "SwiftTesting",
platforms: [
.macOS(.v13), .iOS(.v16), .watchOS(.v9), .tvOS(.v16), .visionOS(.v1)
],
dependencies: [
.package(url: "https://github.com/apple/swift-testing.git", branch: "main"),
],
targets: [
.testTarget(
name: "SwiftTestingTests",
dependencies: [.product(name: "Testing", package: "swift-testing"),]
),
]
)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import Testing
@Test("SOME TEST FUNCTION") func someTestFunction() {}
178 changes: 99 additions & 79 deletions Sources/Build/BuildOperationBuildSystemDelegateHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -109,73 +109,74 @@ final class TestDiscoveryCommand: CustomLLBuildCommand, TestBuildCommand {
}

private func execute(fileSystem: Basics.FileSystem, tool: TestDiscoveryTool) throws {
let index = self.context.buildParameters.indexStore
let api = try self.context.indexStoreAPI.get()
let store = try IndexStore.open(store: TSCAbsolutePath(index), api: api)

// FIXME: We can speed this up by having one llbuild command per object file.
let tests = try store.listTests(in: tool.inputs.map { try TSCAbsolutePath(AbsolutePath(validating: $0.name)) })

let outputs = tool.outputs.compactMap { try? AbsolutePath(validating: $0.name) }
let testsByModule = Dictionary(grouping: tests, by: { $0.module.spm_mangledToC99ExtendedIdentifier() })

func isMainFile(_ path: AbsolutePath) -> Bool {
path.basename == TestDiscoveryTool.mainFileName
}

var maybeMainFile: AbsolutePath?
// Write one file for each test module.
//
// We could write everything in one file but that can easily run into type conflicts due
// in complex packages with large number of test targets.
for file in outputs {
if maybeMainFile == nil && isMainFile(file) {
maybeMainFile = file
continue
switch self.context.buildParameters.testingParameters.library {
case .swiftTesting:
for file in outputs {
try fileSystem.writeIfChanged(path: file, string: "")
}
case .xctest:
let index = self.context.buildParameters.indexStore
let api = try self.context.indexStoreAPI.get()
let store = try IndexStore.open(store: TSCAbsolutePath(index), api: api)

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

guard let tests = testsByModule[module] else {
// This module has no tests so just write an empty file for it.
try fileSystem.writeFileContents(file, bytes: "")
continue
let testsByModule = Dictionary(grouping: tests, by: { $0.module.spm_mangledToC99ExtendedIdentifier() })

// Find the main file path.
guard let mainFile = outputs.first(where: { path in
path.basename == TestDiscoveryTool.mainFileName
}) else {
throw InternalError("main output (\(TestDiscoveryTool.mainFileName)) not found")
}
try write(
tests: tests,
forModule: module,
fileSystem: fileSystem,
path: file
)
}

guard let mainFile = maybeMainFile else {
throw InternalError("main output (\(TestDiscoveryTool.mainFileName)) not found")
}
// Write one file for each test module.
//
// We could write everything in one file but that can easily run into type conflicts due
// in complex packages with large number of test targets.
for file in outputs where file != mainFile {
// FIXME: This is relying on implementation detail of the output but passing the
// the context all the way through is not worth it right now.
let module = file.basenameWithoutExt.spm_mangledToC99ExtendedIdentifier()

guard let tests = testsByModule[module] else {
// This module has no tests so just write an empty file for it.
try fileSystem.writeFileContents(file, bytes: "")
continue
}
try write(
tests: tests,
forModule: module,
fileSystem: fileSystem,
path: file
)
}

let testsKeyword = tests.isEmpty ? "let" : "var"
let testsKeyword = tests.isEmpty ? "let" : "var"

// Write the main file.
let stream = try LocalFileOutputByteStream(mainFile)
// Write the main file.
let stream = try LocalFileOutputByteStream(mainFile)

stream.send(
#"""
import XCTest
stream.send(
#"""
import XCTest

@available(*, deprecated, message: "Not actually deprecated. Marked as deprecated to allow inclusion of deprecated tests (which test deprecated functionality) without warnings")
public func __allDiscoveredTests() -> [XCTestCaseEntry] {
\#(testsKeyword) tests = [XCTestCaseEntry]()
@available(*, deprecated, message: "Not actually deprecated. Marked as deprecated to allow inclusion of deprecated tests (which test deprecated functionality) without warnings")
public func __allDiscoveredTests() -> [XCTestCaseEntry] {
\#(testsKeyword) tests = [XCTestCaseEntry]()

\#(testsByModule.keys.map { "tests += __\($0)__allTests()" }.joined(separator: "\n "))
\#(testsByModule.keys.map { "tests += __\($0)__allTests()" }.joined(separator: "\n "))

return tests
}
"""#
)
return tests
}
"""#
)

stream.flush()
stream.flush()
}
}

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

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

let outputs = tool.outputs.compactMap { try? AbsolutePath(validating: $0.name) }

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

let testObservabilitySetup: String
if self.context.buildParameters.testingParameters.experimentalTestOutput
&& self.context.buildParameters.targetTriple.supportsTestSummary {
testObservabilitySetup = "_ = SwiftPMXCTestObserver()\n"
} else {
testObservabilitySetup = ""
}

// Write the main file.
let stream = try LocalFileOutputByteStream(mainFile)

stream.send(
#"""
\#(generateTestObservationCode(buildParameters: self.context.buildParameters))

import XCTest
\#(discoveryModuleNames.map { "import \($0)" }.joined(separator: "\n"))
switch self.context.buildParameters.testingParameters.library {
case .swiftTesting:
stream.send(
#"""
#if canImport(Testing)
@_spi(SwiftPackageManagerSupport) import Testing
#endif

@main
@available(*, deprecated, message: "Not actually deprecated. Marked as deprecated to allow inclusion of deprecated tests (which test deprecated functionality) without warnings")
struct Runner {
static func main() {
\#(testObservabilitySetup)
XCTMain(__allDiscoveredTests())
@main struct Runner {
static func main() async {
#if canImport(Testing)
await Testing.swiftPMEntryPoint() as Never
#endif
}
}
"""#
)
case .xctest:
// Find the inputs, which are the names of the test discovery module(s)
let inputs = tool.inputs.compactMap { try? AbsolutePath(validating: $0.name) }
let discoveryModuleNames = inputs.map(\.basenameWithoutExt)

let testObservabilitySetup: String
if self.context.buildParameters.testingParameters.experimentalTestOutput
&& self.context.buildParameters.targetTriple.supportsTestSummary {
testObservabilitySetup = "_ = SwiftPMXCTestObserver()\n"
} else {
testObservabilitySetup = ""
}
"""#
)

stream.send(
#"""
\#(generateTestObservationCode(buildParameters: self.context.buildParameters))

import XCTest
\#(discoveryModuleNames.map { "import \($0)" }.joined(separator: "\n"))

@main
@available(*, deprecated, message: "Not actually deprecated. Marked as deprecated to allow inclusion of deprecated tests (which test deprecated functionality) without warnings")
struct Runner {
static func main() {
\#(testObservabilitySetup)
XCTMain(__allDiscoveredTests()) as Never
}
}
"""#
)
}

stream.flush()
}
Expand Down
Loading