diff --git a/Sources/Testing/Running/Runner.Plan.swift b/Sources/Testing/Running/Runner.Plan.swift index c89fdecb5..db731d48c 100644 --- a/Sources/Testing/Running/Runner.Plan.swift +++ b/Sources/Testing/Running/Runner.Plan.swift @@ -93,6 +93,11 @@ extension Runner { stepGraph.compactMap(\.value).sorted { $0.test.sourceLocation < $1.test.sourceLocation } } + /// The tests this runner plan contains. + public var tests: some Collection { + steps.lazy.map(\.test) + } + /// Initialize an instance of this type with the specified graph of test /// plan steps. /// diff --git a/Sources/Testing/Running/Runner.swift b/Sources/Testing/Running/Runner.swift index bd1167b8e..6b06728ae 100644 --- a/Sources/Testing/Running/Runner.swift +++ b/Sources/Testing/Running/Runner.swift @@ -15,7 +15,7 @@ public struct Runner: Sendable { public var plan: Plan /// The set of tests this runner will run. - public var tests: [Test] { plan.steps.map(\.test) } + public var tests: [Test] { .init(plan.tests) } /// The runner's configuration. public var configuration: Configuration diff --git a/Sources/Testing/Test.ID.swift b/Sources/Testing/Test.ID.swift index 6759e2c3a..6f8ee60c2 100644 --- a/Sources/Testing/Test.ID.swift +++ b/Sources/Testing/Test.ID.swift @@ -10,7 +10,15 @@ extension Test: Identifiable { public struct ID: Sendable, Equatable, Hashable { - /// The name of the module containing the corresponding test. + /// The name of the module in which this test is defined. + /// + /// This may be different than the name of the module this test's containing + /// suite type is declared in. For example, if the test is defined in an + /// extension of a type declared in an imported module, the value of this + /// property on the ID of the containing suite will be the name of the + /// imported module, but the value of this property for the ID of the test + /// within that extension will be the name of the module which declares the + /// extension. public var moduleName: String /// The fully qualified name components (other than the module name) used to @@ -123,6 +131,8 @@ extension Test: Identifiable { var result = containingTypeInfo.map(ID.init) ?? ID(moduleName: sourceLocation.moduleName, nameComponents: [], sourceLocation: nil) + result.moduleName = sourceLocation.moduleName + if !isSuite { result.nameComponents.append(name) result.sourceLocation = sourceLocation diff --git a/Tests/TestingTests/Test.IDTests.swift b/Tests/TestingTests/Test.IDTests.swift new file mode 100644 index 000000000..2ae79119a --- /dev/null +++ b/Tests/TestingTests/Test.IDTests.swift @@ -0,0 +1,46 @@ +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2025 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for Swift project authors +// + +@testable @_spi(ForToolsIntegrationOnly) import Testing + +@Suite("Test.ID Tests") +struct Test_IDTests { + @Test func topmostSuiteInCurrentModule() async throws { + let plan = await Runner.Plan(selecting: SomeSuite.self) + + let suiteID = try #require(plan.tests.first { $0.name == "SomeSuite" }?.id) + #expect(suiteID.moduleName == .currentModuleName()) + + let functionID = try #require(plan.tests.first { $0.name == "example()" }?.id) + #expect(functionID.moduleName == .currentModuleName()) + } + + @Test func topmostSuiteInDifferentModule() async throws { + let plan = await Runner.Plan(selecting: String.AnotherSuite.self) + + let suiteID = try #require(plan.tests.first { $0.name == "AnotherSuite" }?.id) + #expect(suiteID.moduleName == .currentModuleName()) + + let functionID = try #require(plan.tests.first { $0.name == "example()" }?.id) + #expect(functionID.moduleName == .currentModuleName()) + } +} + +// MARK: - Fixtures + +@Suite(.hidden) struct SomeSuite { + @Test func example() {} +} + +extension String { + @Suite(.hidden) struct AnotherSuite { + @Test func example() {} + } +} diff --git a/Tests/TestingTests/TestSupport/TestingAdditions.swift b/Tests/TestingTests/TestSupport/TestingAdditions.swift index 4648f96af..3af51e93a 100644 --- a/Tests/TestingTests/TestSupport/TestingAdditions.swift +++ b/Tests/TestingTests/TestSupport/TestingAdditions.swift @@ -66,12 +66,16 @@ func testFunction(named name: String, in containingType: Any.Type) async -> Test /// /// - Parameters: /// - containingType: The type containing the tests that should be run. +/// - fileID: The `#fileID` string whose module should be used to locate +/// the test suite to run. If `nil`, the module which declares +/// `containingType` is used. The default value is the file ID of the file +/// in which this method is called. /// - configuration: The configuration to use for running. /// /// Any tests defined within `containingType` are also run. If no test is found /// representing that type, nothing is run. -func runTest(for containingType: Any.Type, configuration: Configuration = .init()) async { - let plan = await Runner.Plan(selecting: containingType, configuration: configuration) +func runTest(for containingType: Any.Type, inModuleOf fileID: String? = #fileID, configuration: Configuration = .init()) async { + let plan = await Runner.Plan(selecting: containingType, inModuleOf: fileID, configuration: configuration) let runner = Runner(plan: plan, configuration: configuration) await runner.run() } @@ -123,11 +127,20 @@ extension Runner.Plan { /// /// - Parameters: /// - containingType: The suite type this plan should select. + /// - fileID: The `#fileID` string whose module should be used to locate + /// the test suite to select. If `nil`, the module which declares + /// `containingType` is used. The default value is the file ID of the file + /// in which this initializer is called. /// - configuration: The configuration to use for planning. - init(selecting containingType: Any.Type, configuration: Configuration = .init()) async { + init(selecting containingType: Any.Type, inModuleOf fileID: String? = #fileID, configuration: Configuration = .init()) async { + var testID = Test.ID(type: containingType) + + if let fileID { + testID.moduleName = String(fileID[.. String { + String(fileID[..