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
2 changes: 2 additions & 0 deletions Sources/SWBCore/Settings/BuiltinMacros.swift
Original file line number Diff line number Diff line change
Expand Up @@ -726,6 +726,7 @@ public final class BuiltinMacros {
public static let GENERATE_RESOURCE_ACCESSORS = BuiltinMacros.declareBooleanMacro("GENERATE_RESOURCE_ACCESSORS")
public static let GENERATE_TEST_ENTRY_POINT = BuiltinMacros.declareBooleanMacro("GENERATE_TEST_ENTRY_POINT")
public static let GENERATED_TEST_ENTRY_POINT_PATH = BuiltinMacros.declarePathMacro("GENERATED_TEST_ENTRY_POINT_PATH")
public static let GENERATED_TEST_ENTRY_POINT_INCLUDE_DISCOVERED_TESTS = BuiltinMacros.declareBooleanMacro("GENERATED_TEST_ENTRY_POINT_INCLUDE_DISCOVERED_TESTS")
public static let GENERATE_TEXT_BASED_STUBS = BuiltinMacros.declareBooleanMacro("GENERATE_TEXT_BASED_STUBS")
public static let GENERATE_INTERMEDIATE_TEXT_BASED_STUBS = BuiltinMacros.declareBooleanMacro("GENERATE_INTERMEDIATE_TEXT_BASED_STUBS")
public static let GLOBAL_API_NOTES_PATH = BuiltinMacros.declareStringMacro("GLOBAL_API_NOTES_PATH")
Expand Down Expand Up @@ -1792,6 +1793,7 @@ public final class BuiltinMacros {
GENERATE_RESOURCE_ACCESSORS,
GENERATE_TEST_ENTRY_POINT,
GENERATED_TEST_ENTRY_POINT_PATH,
GENERATED_TEST_ENTRY_POINT_INCLUDE_DISCOVERED_TESTS,
GENERATE_TEXT_BASED_STUBS,
GENERATE_INTERMEDIATE_TEXT_BASED_STUBS,
GID,
Expand Down
4 changes: 4 additions & 0 deletions Sources/SWBUniversalPlatform/Specs/ProductTypes.xcspec
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,9 @@

PROVISIONING_PROFILE_SUPPORTED = YES;
PROVISIONING_PROFILE_REQUIRED = NO;

GENERATE_TEST_ENTRY_POINT = "$(GENERATE_TEST_ENTRYPOINTS_FOR_BUNDLES)";
GENERATED_TEST_ENTRY_POINT_PATH = "$(DERIVED_SOURCES_DIR)/test_entry_point.swift";
};
PackageTypes = (
com.apple.package-type.bundle.unit-test
Expand All @@ -353,6 +356,7 @@
ENABLE_TESTING_SEARCH_PATHS = YES;
GENERATE_TEST_ENTRY_POINT = YES;
GENERATED_TEST_ENTRY_POINT_PATH = "$(DERIVED_SOURCES_DIR)/test_entry_point.swift";
GENERATED_TEST_ENTRY_POINT_INCLUDE_DISCOVERED_TESTS = YES;
};
PackageTypes = (
com.apple.package-type.mach-o-executable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,24 +25,30 @@ class TestEntryPointGenerationTaskAction: TaskAction {
let options = try Options.parse(Array(task.commandLineAsStrings.dropFirst()))

var tests: [IndexStore.TestCaseClass] = []
var objects: [Path] = []
for linkerFilelist in options.linkerFilelist {
let filelistContents = String(String(decoding: try executionDelegate.fs.read(linkerFilelist), as: UTF8.self))
let entries = filelistContents.split(separator: "\n", omittingEmptySubsequences: true).map { Path($0) }.map {
for indexUnitBasePath in options.indexUnitBasePath {
if let remappedPath = generateIndexOutputPath(from: $0, basePath: indexUnitBasePath) {
return remappedPath
if options.discoverTests {
var objects: [Path] = []
for linkerFilelist in options.linkerFilelist {
let filelistContents = String(String(decoding: try executionDelegate.fs.read(linkerFilelist), as: UTF8.self))
let entries = filelistContents.split(separator: "\n", omittingEmptySubsequences: true).map { Path($0) }.map {
for indexUnitBasePath in options.indexUnitBasePath {
if let remappedPath = generateIndexOutputPath(from: $0, basePath: indexUnitBasePath) {
return remappedPath
}
}
return $0
}
return $0
objects.append(contentsOf: entries)
}
guard let indexStoreLibraryPath = options.indexStoreLibraryPath else {
outputDelegate.emitError("Test discovery was requested, but failed to lookup index store library in toolchain")
return .failed
}
let indexStoreAPI = try IndexStoreAPI(dylib: indexStoreLibraryPath)
for indexStore in options.indexStore {
let store = try IndexStore.open(store: indexStore, api: indexStoreAPI)
let testInfo = try store.listTests(in: objects)
tests.append(contentsOf: testInfo)
}
objects.append(contentsOf: entries)
}
let indexStoreAPI = try IndexStoreAPI(dylib: options.indexStoreLibraryPath)
for indexStore in options.indexStore {
let store = try IndexStore.open(store: indexStore, api: indexStoreAPI)
let testInfo = try store.listTests(in: objects)
tests.append(contentsOf: testInfo)
}

try executionDelegate.fs.write(options.output, contents: ByteString(encodingAsUTF8: """
Expand All @@ -52,8 +58,8 @@ class TestEntryPointGenerationTaskAction: TaskAction {

\(testObservationFragment)

import XCTest
\(discoveredTestsFragment(tests: tests))
public import XCTest
\(discoveredTestsFragment(tests: tests, options: options))

@main
@available(macOS 10.15, iOS 11, watchOS 4, tvOS 11, visionOS 1, *)
Expand Down Expand Up @@ -94,16 +100,7 @@ class TestEntryPointGenerationTaskAction: TaskAction {
}
}
#endif
if testingLibrary == "xctest" {
#if !os(Windows) && \(options.enableExperimentalTestOutput)
_ = Self.testOutputPath().map { SwiftPMXCTestObserver(testOutputPath: testOutputPath) }
#endif
#if os(WASI)
await XCTMain(__allDiscoveredTests()) as Never
#else
XCTMain(__allDiscoveredTests()) as Never
#endif
}
\(xctestFragment(enableExperimentalTestOutput: options.enableExperimentalTestOutput, disable: !options.discoverTests))
}
#else
static func main() async {
Expand All @@ -113,16 +110,7 @@ class TestEntryPointGenerationTaskAction: TaskAction {
await Testing.__swiftPMEntryPoint() as Never
}
#endif
if testingLibrary == "xctest" {
#if !os(Windows) && \(options.enableExperimentalTestOutput)
_ = Self.testOutputPath().map { SwiftPMXCTestObserver(testOutputPath: testOutputPath) }
#endif
#if os(WASI)
await XCTMain(__allDiscoveredTests()) as Never
#else
XCTMain(__allDiscoveredTests()) as Never
#endif
}
\(xctestFragment(enableExperimentalTestOutput: options.enableExperimentalTestOutput, disable: !options.discoverTests))
}
#endif
}
Expand All @@ -137,14 +125,18 @@ class TestEntryPointGenerationTaskAction: TaskAction {

private struct Options: ParsableArguments {
@Option var output: Path
@Option var indexStoreLibraryPath: Path
@Option var linkerFilelist: [Path]
@Option var indexStore: [Path]
@Option var indexUnitBasePath: [Path]
@Option var indexStoreLibraryPath: Path? = nil
@Option() var linkerFilelist: [Path] = []
@Option var indexStore: [Path] = []
@Option var indexUnitBasePath: [Path] = []
@Flag var enableExperimentalTestOutput: Bool = false
@Flag var discoverTests: Bool = false
}

private func discoveredTestsFragment(tests: [IndexStore.TestCaseClass]) -> String {
private func discoveredTestsFragment(tests: [IndexStore.TestCaseClass], options: Options) -> String {
guard options.discoverTests else {
return ""
}
var fragment = ""
for moduleName in Set(tests.map { $0.module }).sorted() {
fragment += "@testable import \(moduleName)\n"
Expand Down Expand Up @@ -174,11 +166,29 @@ class TestEntryPointGenerationTaskAction: TaskAction {
return fragment
}

private func xctestFragment(enableExperimentalTestOutput: Bool, disable: Bool) -> String {
guard !disable else {
return ""
}
return """
if testingLibrary == "xctest" {
#if !os(Windows) && \(enableExperimentalTestOutput)
_ = Self.testOutputPath().map { SwiftPMXCTestObserver(testOutputPath: testOutputPath) }
#endif
#if os(WASI)
await XCTMain(__allDiscoveredTests()) as Never
#else
XCTMain(__allDiscoveredTests()) as Never
#endif
}
"""
}

private var testObservationFragment: String =
"""
#if !os(Windows) // Test observation is not supported on Windows
import Foundation
import XCTest
public import Foundation
public import XCTest

public final class SwiftPMXCTestObserver: NSObject {
let testOutputPath: String
Expand Down Expand Up @@ -562,7 +572,7 @@ class TestEntryPointGenerationTaskAction: TaskAction {
}
}

import XCTest
public import XCTest

#if canImport(Darwin) // XCTAttachment is unavailable in swift-corelibs-xctest.
extension TestAttachment {
Expand Down
24 changes: 15 additions & 9 deletions Sources/SWBUniversalPlatform/TestEntryPointGenerationTool.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,13 @@ final class TestEntryPointGenerationToolSpec: GenericCommandLineToolSpec, SpecId

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

for indexStorePath in indexStorePaths {
commandLine.append(contentsOf: ["--index-store", .path(indexStorePath)])
}

for basePath in indexUnitBasePaths {
commandLine.append(contentsOf: ["--index-unit-base-path", .path(basePath)])
if cbc.scope.evaluate(BuiltinMacros.GENERATED_TEST_ENTRY_POINT_INCLUDE_DISCOVERED_TESTS) {
for indexStorePath in indexStorePaths {
commandLine.append(contentsOf: ["--index-store", .path(indexStorePath)])
}

for basePath in indexUnitBasePaths {
commandLine.append(contentsOf: ["--index-unit-base-path", .path(basePath)])
}
}

delegate.createTask(
Expand Down
70 changes: 70 additions & 0 deletions Tests/SWBBuildSystemTests/BuildOperationTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,76 @@ fileprivate struct BuildOperationTests: CoreBasedTests {
}
}

@Test(.requireSDKs(.macOS))
func unitTestWithGeneratedEntryPointViaMacOSOverride() async throws {
try await withTemporaryDirectory(removeTreeOnDeinit: false) { (tmpDir: Path) in
let testProject = try await TestProject(
"TestProject",
sourceRoot: tmpDir,
groupTree: TestGroup(
"SomeFiles",
children: [
TestFile("test.swift"),
]),
buildConfigurations: [
TestBuildConfiguration("Debug", buildSettings: [
"ARCHS": "$(ARCHS_STANDARD)",
"CODE_SIGNING_ALLOWED": "NO",
"PRODUCT_NAME": "$(TARGET_NAME)",
"SDKROOT": "$(HOST_PLATFORM)",
"SUPPORTED_PLATFORMS": "$(HOST_PLATFORM)",
"SWIFT_VERSION": swiftVersion,
"INDEX_DATA_STORE_DIR": "\(tmpDir.join("index").str)",
"LINKER_DRIVER": "swiftc"
])
],
targets: [
TestStandardTarget(
"MyTests",
type: .unitTest,
buildConfigurations: [
TestBuildConfiguration("Debug", buildSettings: [
"GENERATE_TEST_ENTRYPOINTS_FOR_BUNDLES": "YES"
])
],
buildPhases: [
TestSourcesBuildPhase(["test.swift"]),
],
),
])
let core = try await getCore()
let tester = try await BuildOperationTester(core, testProject, simulated: false)
try localFS.createDirectory(tmpDir.join("index"))
let projectDir = tester.workspace.projects[0].sourceRoot

try await tester.fs.writeFileContents(projectDir.join("test.swift")) { stream in
stream <<< """
import Testing
import XCTest
@Suite struct MySuite {
@Test func myTest() {
#expect(42 == 42)
}
}

final class MYXCTests: XCTestCase {
func testFoo() {
XCTAssertTrue(true)
}
}
"""
}

let destination: RunDestinationInfo = .host
try await tester.checkBuild(runDestination: destination, persistent: true) { results in
results.checkNoErrors()
results.checkTask(.matchRuleType("GenerateTestEntryPoint")) { task in
task.checkCommandLineMatches(["builtin-generateTestEntryPoint", "--output", .suffix("test_entry_point.swift")])
}
}
}
}

@Test(.requireSDKs(.host), .skipHostOS(.macOS), .skipHostOS(.windows, "cannot find testing library"))
func unitTestWithGeneratedEntryPoint() async throws {
try await withTemporaryDirectory(removeTreeOnDeinit: false) { (tmpDir: Path) in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ fileprivate struct UnitTestTaskConstructionTests: CoreBasedTests {
await tester.checkBuild(runDestination: .linux, fs: fs) { results in
results.checkTarget("UnitTestRunner") { target in
results.checkTask(.matchTarget(target), .matchRuleType("GenerateTestEntryPoint")) { task in
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"])
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"])
task.checkInputs([
.pathPattern(.suffix("UnitTestTarget.LinkFileList")),
.pathPattern(.suffix("UnitTestTarget.so")),
Expand Down