From fcf5f9b596b2edcebfadf30746920fd68bb08ab8 Mon Sep 17 00:00:00 2001 From: Mike Gerasimenko Date: Sun, 2 Nov 2025 16:44:57 +0100 Subject: [PATCH] Support multiple test plans --- Package.swift | 3 +- .../SelectiveTestingPlugin.swift | 23 ++-- README.md | 22 +++- .../DependencyGraph.swift | 17 +-- Sources/SelectiveTestingCore/Config.swift | 13 +++ .../SelectiveTestingTool.swift | 35 ++++-- Sources/Workspace/WorkspaceInfo.swift | 24 ++++- .../SelectiveTesting.swift | 6 +- .../ExampleProject/ExampleProject2.xctestplan | 64 +++++++++++ .../IntegrationTestTool.swift | 12 ++- .../SelectiveTestingConfigTests.swift | 101 +++++++++++++++++- selective-testing-config-example.yml | 8 ++ 12 files changed, 291 insertions(+), 37 deletions(-) create mode 100644 Tests/SelectiveTestingTests/ExampleProject/ExampleProject2.xctestplan diff --git a/Package.swift b/Package.swift index 7447ec4..9c4474b 100644 --- a/Package.swift +++ b/Package.swift @@ -29,7 +29,8 @@ let targets: [PackageDescription.Target] = [ "Git", "PathKit", "Rainbow", - "Yams"]), + "Yams", + .product(name: "ArgumentParser", package: "swift-argument-parser")]), .target(name: "DependencyCalculator", dependencies: ["Workspace", "PathKit", "SelectiveTestLogger", "Git"]), .target(name: "TestConfigurator", diff --git a/Plugins/SelectiveTestingPlugin/SelectiveTestingPlugin.swift b/Plugins/SelectiveTestingPlugin/SelectiveTestingPlugin.swift index 2689daf..e2fc66a 100644 --- a/Plugins/SelectiveTestingPlugin/SelectiveTestingPlugin.swift +++ b/Plugins/SelectiveTestingPlugin/SelectiveTestingPlugin.swift @@ -48,13 +48,22 @@ struct SelectiveTestingPlugin: CommandPlugin { toolArguments.remove(at: indexOfTarget) } - if !toolArguments.contains(where: { $0 == "--test-plan" }), - let testPlan = context.xcodeProject.filePaths.first(where: { - $0.extension == "xctestplan" - }) - { - print("Using \(testPlan.string) test plan") - toolArguments.append(contentsOf: ["--test-plan", testPlan.string]) + if !toolArguments.contains(where: { $0 == "--test-plan" }) { + let testPlans = context.xcodeProject.filePaths.filter { + $0.extension == "xctestplan" + } + + if !testPlans.isEmpty { + if testPlans.count == 1 { + print("Using \(testPlans[0].string) test plan") + } else { + print("Using \(testPlans.count) test plans") + } + + for testPlan in testPlans { + toolArguments.append(contentsOf: ["--test-plan", testPlan.string]) + } + } } try run(tool.path.string, arguments: toolArguments) diff --git a/README.md b/README.md index ce02e96..08ae96e 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,14 @@ NB: This command assumes you have [jq](https://jqlang.github.io/jq/) tool instal Alternatively, you can use CLI to achieve the same result: 1. Run `mint run mikeger/XcodeSelectiveTesting@0.12.7 YourWorkspace.xcworkspace --test-plan YourTestPlan.xctestplan` -2. Run tests normally, XcodeSelectiveTesting would modify your test plan according to the local changes +2. Run tests normally, XcodeSelectiveTesting would modify your test plan according to the local changes + +To process multiple test plans, specify the `--test-plan` option multiple times: +```bash +mint run mikeger/XcodeSelectiveTesting@0.12.7 YourWorkspace.xcworkspace \ + --test-plan TestPlan1.xctestplan \ + --test-plan TestPlan2.xctestplan +``` ### Use case: Xcode-based project, execute tests on the CI, no test plan @@ -99,6 +106,14 @@ Alternatively, you can use CLI to achieve the same result: 2. Add a CI step before you execute your tests: `mint run mikeger/XcodeSelectiveTesting@0.12.7 YourWorkspace.xcworkspace --test-plan YourTestPlan.xctestplan --base-branch $PR_BASE_BRANCH` 3. Execute your tests +To process multiple test plans on CI: +```bash +mint run mikeger/XcodeSelectiveTesting@0.12.7 YourWorkspace.xcworkspace \ + --test-plan TestPlan1.xctestplan \ + --test-plan TestPlan2.xctestplan \ + --base-branch $PR_BASE_BRANCH +``` + ### Use case: GitHub Actions, other cases when the git repo is not in the shape to provide the changeset out of the box 1. Add code to install the tool @@ -145,7 +160,7 @@ This is the hardest part: dealing with obscure Xcode formats. But if we get that - `--help`: Display all command line options - `--base-branch`: Branch to compare against to find the relevant changes. If emitted, a local changeset is used (development mode). -- `--test-plan`: Path to the test plan. If not given, tool would try to infer the path. +- `--test-plan`: Path to the test plan. If not given, tool would try to infer the path. Can be specified multiple times to process multiple test plans. - `--json`: Provide output in JSON format (STDOUT). - `--dependency-graph`: Opens Safari with a dependency graph visualization. Attention: if you don't trust Javascript ecosystem prefer using `--dot` option. More info [here](https://github.com/mikeger/XcodeSelectiveTesting/wiki/How-to-visualize-your-dependency-structure). - `--dot`: Output dependency graph in Dot (Graphviz) format. To be used with Graphviz: `brew install graphviz`, then `xcode-selective-test --dot | dot -Tsvg > output.svg && open output.svg` @@ -160,7 +175,8 @@ It is possible to define the configuration in a separate file. The tool would lo Options available are (see `selective-testing-config-example.yml` for an example): - `basePath`: Relative or absolute path to the project. If set, the command line option can be emitted. -- `testPlan`: Relative or absolute path to the test plan to configure. +- `testPlan`: Relative or absolute path to the test plan to configure. For backwards compatibility. +- `testPlans`: Array of relative or absolute paths to test plans to configure. Use this to process multiple test plans. - `exclude`: List of relative paths to exclude when looking for Swift packages. - `extra/dependencies`: Options allowing to hint tool about dependencies between targets or packages. - `extra/targetsFiles`: Options allowing to hint tool about the files affecting targets or packages. diff --git a/Sources/DependencyCalculator/DependencyGraph.swift b/Sources/DependencyCalculator/DependencyGraph.swift index 64d5758..5a0adfb 100644 --- a/Sources/DependencyCalculator/DependencyGraph.swift +++ b/Sources/DependencyCalculator/DependencyGraph.swift @@ -106,7 +106,7 @@ extension WorkspaceInfo { var resultDependencies = packageWorkspaceInfo.dependencyStructure var files = packageWorkspaceInfo.files var folders = packageWorkspaceInfo.folders - var candidateTestPlan = packageWorkspaceInfo.candidateTestPlan + var candidateTestPlans = packageWorkspaceInfo.candidateTestPlans let allProjects: [(XcodeProj, Path)] var workspaceDefinitionPath: Path? = nil @@ -144,15 +144,13 @@ extension WorkspaceInfo { files = files.merging(with: newFiles) folders = folders.merging(with: newDependencies.folders) - if candidateTestPlan == nil { - candidateTestPlan = newDependencies.candidateTestPlan - } + candidateTestPlans.append(contentsOf: newDependencies.candidateTestPlans) } let workspaceInfo = WorkspaceInfo(files: files, folders: folders, dependencyStructure: resultDependencies, - candidateTestPlan: candidateTestPlan) + candidateTestPlans: candidateTestPlans) if let config { // Process additional config return processAdditional(config: config, workspaceInfo: workspaceInfo) @@ -295,7 +293,7 @@ extension WorkspaceInfo { var dependsOn: [TargetIdentity: Set] = [:] var files: [TargetIdentity: Set] = [:] var folders: [Path: TargetIdentity] = [:] - var candidateTestPlan: Path? = nil + var candidateTestPlans: [Path] = [] var packagesByName: [String: PackageTargetMetadata] = packages.toDictionary(path: \.name) let targetsByName = project.pbxproj.nativeTargets.toDictionary(path: \.name) @@ -385,14 +383,17 @@ extension WorkspaceInfo { // Find existing test plans project.sharedData?.schemes.forEach { scheme in scheme.testAction?.testPlans?.forEach { plan in - candidateTestPlan = path.parent() + plan.reference.replacingOccurrences(of: "container:", with: "") + let testPlanPath = path.parent() + plan.reference.replacingOccurrences(of: "container:", with: "") + if !candidateTestPlans.contains(testPlanPath) { + candidateTestPlans.append(testPlanPath) + } } } return WorkspaceInfo(files: files, folders: folders, dependencyStructure: DependencyGraph(dependsOn: dependsOn), - candidateTestPlan: candidateTestPlan?.string) + candidateTestPlans: candidateTestPlans.map { $0.string }) } private static func isSwiftVersion6Plus() throws -> Bool { diff --git a/Sources/SelectiveTestingCore/Config.swift b/Sources/SelectiveTestingCore/Config.swift index 1198e3b..ed58e93 100644 --- a/Sources/SelectiveTestingCore/Config.swift +++ b/Sources/SelectiveTestingCore/Config.swift @@ -9,11 +9,24 @@ import Yams struct Config: Codable { let basePath: String? let testPlan: String? + let testPlans: [String]? let exclude: [String]? let extra: WorkspaceInfo.AdditionalConfig? static let defaultConfigName = ".xcode-selective-testing.yml" + + /// Returns all test plans, merging singular `testPlan` and plural `testPlans` + var allTestPlans: [String] { + var plans: [String] = [] + if let testPlan = testPlan { + plans.append(testPlan) + } + if let testPlans = testPlans { + plans.append(contentsOf: testPlans) + } + return plans + } } extension Config { diff --git a/Sources/SelectiveTestingCore/SelectiveTestingTool.swift b/Sources/SelectiveTestingCore/SelectiveTestingTool.swift index 9bef42a..fb5e735 100644 --- a/Sources/SelectiveTestingCore/SelectiveTestingTool.swift +++ b/Sources/SelectiveTestingCore/SelectiveTestingTool.swift @@ -21,12 +21,12 @@ public final class SelectiveTestingTool { private let dryRun: Bool private let dot: Bool private let verbose: Bool - private let testPlan: String? + private let testPlans: [String] private let config: Config? public init(baseBranch: String?, basePath: String?, - testPlan: String?, + testPlans: [String], changedFiles: [String], printJSON: Bool = false, renderDependencyGraph: Bool = false, @@ -57,7 +57,11 @@ public final class SelectiveTestingTool { self.dot = dot self.dryRun = dryRun self.verbose = verbose - self.testPlan = testPlan ?? config?.testPlan + + // Merge CLI test plans with config test plans + var allTestPlans = config?.allTestPlans ?? [] + allTestPlans.append(contentsOf: testPlans) + self.testPlans = allTestPlans } public func run() async throws -> Set { @@ -130,13 +134,26 @@ public final class SelectiveTestingTool { } } - if !dryRun, let testPlan { + if !dryRun { // 4. Configure workspace to test given targets - try enableTests(at: Path(testPlan), - targetsToTest: affectedTargets) - } else if !dryRun, let testPlan = workspaceInfo.candidateTestPlan { - try enableTests(at: Path(testPlan), - targetsToTest: affectedTargets) + let plansToUpdate = testPlans.isEmpty ? workspaceInfo.candidateTestPlans : testPlans + + if !plansToUpdate.isEmpty { + for testPlan in plansToUpdate { + try enableTests(at: Path(testPlan), + targetsToTest: affectedTargets) + } + } else if !printJSON { + if affectedTargets.isEmpty { + if verbose { Logger.message("No targets affected") } + } else { + if verbose { Logger.message("Targets to test:") } + + for target in affectedTargets { + Logger.message(target.description) + } + } + } } else if !printJSON { if affectedTargets.isEmpty { if verbose { Logger.message("No targets affected") } diff --git a/Sources/Workspace/WorkspaceInfo.swift b/Sources/Workspace/WorkspaceInfo.swift index 15c917c..c557e94 100644 --- a/Sources/Workspace/WorkspaceInfo.swift +++ b/Sources/Workspace/WorkspaceInfo.swift @@ -28,7 +28,12 @@ public struct WorkspaceInfo { public let targetsForFiles: [Path: Set] public let folders: [Path: TargetIdentity] public let dependencyStructure: DependencyGraph - public var candidateTestPlan: String? + public var candidateTestPlans: [String] + + /// Backwards compatibility: returns the first candidate test plan + public var candidateTestPlan: String? { + candidateTestPlans.first + } public init(files: [TargetIdentity: Set], folders: [Path: TargetIdentity], @@ -39,18 +44,31 @@ public struct WorkspaceInfo { targetsForFiles = WorkspaceInfo.targets(for: files) self.folders = folders self.dependencyStructure = dependencyStructure - self.candidateTestPlan = candidateTestPlan + self.candidateTestPlans = candidateTestPlan.map { [$0] } ?? [] + } + + public init(files: [TargetIdentity: Set], + folders: [Path: TargetIdentity], + dependencyStructure: DependencyGraph, + candidateTestPlans: [String]) + { + self.files = files + targetsForFiles = WorkspaceInfo.targets(for: files) + self.folders = folders + self.dependencyStructure = dependencyStructure + self.candidateTestPlans = candidateTestPlans } public func merging(with other: WorkspaceInfo) -> WorkspaceInfo { let newFiles = files.merging(with: other.files) let newFolders = folders.merging(with: other.folders) let dependencyStructure = dependencyStructure.merging(with: other.dependencyStructure) + let mergedTestPlans = candidateTestPlans + other.candidateTestPlans return WorkspaceInfo(files: newFiles, folders: newFolders, dependencyStructure: dependencyStructure, - candidateTestPlan: candidateTestPlan ?? other.candidateTestPlan) + candidateTestPlans: mergedTestPlans) } static func targets(for targetsToFiles: [TargetIdentity: Set]) -> [Path: Set] { diff --git a/Sources/xcode-selective-test/SelectiveTesting.swift b/Sources/xcode-selective-test/SelectiveTesting.swift index 1fe36ac..54c7ffb 100644 --- a/Sources/xcode-selective-test/SelectiveTesting.swift +++ b/Sources/xcode-selective-test/SelectiveTesting.swift @@ -19,8 +19,8 @@ struct SelectiveTesting: AsyncParsableCommand { @Option(name: .long, help: "Name of the base branch") var baseBranch: String? - @Option(name: .long, help: "Test plan to modify") - var testPlan: String? + @Option(name: .long, parsing: .upToNextOption, help: "Test plan(s) to modify. Can be specified multiple times.") + var testPlan: [String] = [] @Flag(name: .long, help: "Output in JSON format") var JSON: Bool = false @@ -43,7 +43,7 @@ struct SelectiveTesting: AsyncParsableCommand { mutating func run() async throws { let tool = try SelectiveTestingTool(baseBranch: baseBranch, basePath: basePath, - testPlan: testPlan, + testPlans: testPlan, changedFiles: changedFiles, printJSON: JSON, renderDependencyGraph: dependencyGraph, diff --git a/Tests/SelectiveTestingTests/ExampleProject/ExampleProject2.xctestplan b/Tests/SelectiveTestingTests/ExampleProject/ExampleProject2.xctestplan new file mode 100644 index 0000000..e6133f3 --- /dev/null +++ b/Tests/SelectiveTestingTests/ExampleProject/ExampleProject2.xctestplan @@ -0,0 +1,64 @@ +{ + "configurations" : [ + { + "id" : "B9E0CAB3-55AE-4CC1-82BC-0598F7105368", + "name" : "Test Scheme Action", + "options" : { + + } + } + ], + "defaultOptions" : { + "codeCoverage" : false, + "targetForVariableExpansion" : { + "containerPath" : "container:ExampleProject.xcodeproj", + "identifier" : "276DB5BA29B144C900E5C615", + "name" : "ExampleProject" + } + }, + "testTargets" : [ + { + "target" : { + "containerPath" : "container:ExampleLibrary\/ExampleLibrary.xcodeproj", + "identifier" : "27F467CF29B1453600A93E94", + "name" : "ExampleLibraryTests" + } + }, + { + "target" : { + "containerPath" : "container:ExamplePackage", + "identifier" : "ExamplePackageTests", + "name" : "ExamplePackageTests" + } + }, + { + "target" : { + "containerPath" : "container:ExampleProject.xcodeproj", + "identifier" : "276DB5CA29B144CA00E5C615", + "name" : "ExampleProjectTests" + } + }, + { + "target" : { + "containerPath" : "container:ExampleProject.xcodeproj", + "identifier" : "276DB5D429B144CA00E5C615", + "name" : "ExampleProjectUITests" + } + }, + { + "target" : { + "containerPath" : "container:ExampleProject.xcodeproj", + "identifier" : "27F467ED29B1457600A93E94", + "name" : "ExmapleTargetLibraryTests" + } + }, + { + "target" : { + "containerPath" : "container:ExamplePackage", + "identifier" : "Subtests", + "name" : "Subtests" + } + } + ], + "version" : 1 +} diff --git a/Tests/SelectiveTestingTests/IntegrationTestTool.swift b/Tests/SelectiveTestingTests/IntegrationTestTool.swift index 792217c..f788306 100644 --- a/Tests/SelectiveTestingTests/IntegrationTestTool.swift +++ b/Tests/SelectiveTestingTests/IntegrationTestTool.swift @@ -83,9 +83,17 @@ final class IntegrationTestTool { try configText.write(toFile: path.string, atomically: true, encoding: .utf8) } + let testPlans: [String] + if let testPlan { + testPlans = [testPlan] + } + else { + testPlans = [] + } + return try SelectiveTestingTool(baseBranch: "main", basePath: basePath?.string, - testPlan: testPlan, + testPlans: testPlans, changedFiles: changedFiles, renderDependencyGraph: false, turbo: turbo, @@ -95,7 +103,7 @@ final class IntegrationTestTool { func createSUT() throws -> SelectiveTestingTool { return try SelectiveTestingTool(baseBranch: "main", basePath: (projectPath + "ExampleWorkspace.xcworkspace").string, - testPlan: "ExampleProject.xctestplan", + testPlans: ["ExampleProject.xctestplan"], changedFiles: [], renderDependencyGraph: false, verbose: true) diff --git a/Tests/SelectiveTestingTests/SelectiveTestingConfigTests.swift b/Tests/SelectiveTestingTests/SelectiveTestingConfigTests.swift index 391b8d8..c765497 100644 --- a/Tests/SelectiveTestingTests/SelectiveTestingConfigTests.swift +++ b/Tests/SelectiveTestingTests/SelectiveTestingConfigTests.swift @@ -27,6 +27,7 @@ final class SelectiveTestingConfigTests: XCTestCase { // given let tool = try testTool.createSUT(config: Config(basePath: (testTool.projectPath + "ExampleWorkspace.xcworkspace").string, testPlan: nil, + testPlans: nil, exclude: nil, extra: nil)) // when @@ -45,6 +46,7 @@ final class SelectiveTestingConfigTests: XCTestCase { // given let tool = try testTool.createSUT(config: Config(basePath: (testTool.projectPath + "ExampleWorkspace.xcworkspace").string, testPlan: "ExampleProject.xctestplan", + testPlans: nil, exclude: nil, extra: nil)) // when @@ -67,6 +69,7 @@ final class SelectiveTestingConfigTests: XCTestCase { // given let tool = try testTool.createSUT(config: Config(basePath: (testTool.projectPath + "ExampleWorkspace.xcworkspace").string, testPlan: "ExampleProject.xctestplan", + testPlans: nil, exclude: nil, extra: nil)) // when @@ -85,6 +88,7 @@ final class SelectiveTestingConfigTests: XCTestCase { // given let tool = try testTool.createSUT(config: Config(basePath: (testTool.projectPath + "ExampleWorkspace.xcworkspace").string, testPlan: "ExampleProject.xctestplan", + testPlans: nil, exclude: nil, extra: nil)) // when @@ -105,6 +109,7 @@ final class SelectiveTestingConfigTests: XCTestCase { dependencies: ["ExampleProject:ExmapleTargetLibrary": ["ExampleSubpackage:ExampleSubpackage"]]) let fullConfig = Config(basePath: (testTool.projectPath + "ExampleWorkspace.xcworkspace").string, testPlan: nil, + testPlans: nil, exclude: nil, extra: additionalConfig) let tool = try testTool.createSUT(config: fullConfig) @@ -123,6 +128,7 @@ final class SelectiveTestingConfigTests: XCTestCase { dependencies: [:]) let fullConfig = Config(basePath: (testTool.projectPath + "ExampleWorkspace.xcworkspace").string, testPlan: nil, + testPlans: nil, exclude: nil, extra: additionalConfig) let tool = try testTool.createSUT(config: fullConfig) @@ -139,6 +145,7 @@ final class SelectiveTestingConfigTests: XCTestCase { // given let tool = try testTool.createSUT(config: Config(basePath: (testTool.projectPath + "ExampleWorkspace.xcworkspace").string, testPlan: "ExampleProject.xctestplan", + testPlans: nil, exclude: ["ExamplePackage"], extra: nil)) // when @@ -167,7 +174,7 @@ final class SelectiveTestingConfigTests: XCTestCase { // given let tool = try SelectiveTestingTool(baseBranch: "main", basePath: (testTool.projectPath + "ExampleWorkspace.xcworkspace").string, - testPlan: "ExampleProject.xctestplan", + testPlans: ["ExampleProject.xctestplan"], changedFiles: [], renderDependencyGraph: false, dryRun: true, @@ -180,4 +187,96 @@ final class SelectiveTestingConfigTests: XCTestCase { let _ = try await tool.run() try testTool.checkTestPlanUnmodified(at: testTool.projectPath + "ExampleProject.xctestplan") } + + func testMultipleTestPlansViaCLI() async throws { + // given + let tool = try SelectiveTestingTool(baseBranch: "main", + basePath: (testTool.projectPath + "ExampleWorkspace.xcworkspace").string, + testPlans: ["ExampleProject.xctestplan", "ExampleProject2.xctestplan"], + changedFiles: [], + verbose: true) + + // when + try testTool.changeFile(at: testTool.projectPath + "ExampleLibrary/ExampleLibrary/ExampleLibrary.swift") + + // then + let result = try await tool.run() + XCTAssertEqual(result, Set([testTool.mainProjectMainTarget, + testTool.mainProjectTests, + testTool.mainProjectUITests, + testTool.exampleLibrary, + testTool.exampleLibraryTests])) + + // Verify both test plans were updated + try testTool.validateTestPlan(testPlanPath: testTool.projectPath + "ExampleProject.xctestplan", + expected: Set([testTool.mainProjectTests, + testTool.mainProjectUITests, + testTool.exampleLibraryTests])) + try testTool.validateTestPlan(testPlanPath: testTool.projectPath + "ExampleProject2.xctestplan", + expected: Set([testTool.mainProjectTests, + testTool.mainProjectUITests, + testTool.exampleLibraryTests])) + } + + func testMultipleTestPlansViaConfig() async throws { + // given + let tool = try testTool.createSUT(config: Config(basePath: (testTool.projectPath + "ExampleWorkspace.xcworkspace").string, + testPlan: nil, + testPlans: ["ExampleProject.xctestplan", "ExampleProject2.xctestplan"], + exclude: nil, + extra: nil)) + + // when + try testTool.changeFile(at: testTool.projectPath + "ExampleLibrary/ExampleLibrary/ExampleLibrary.swift") + + // then + let result = try await tool.run() + XCTAssertEqual(result, Set([testTool.mainProjectMainTarget, + testTool.mainProjectTests, + testTool.mainProjectUITests, + testTool.exampleLibrary, + testTool.exampleLibraryTests])) + + // Verify both test plans were updated + try testTool.validateTestPlan(testPlanPath: testTool.projectPath + "ExampleProject.xctestplan", + expected: Set([testTool.mainProjectTests, + testTool.mainProjectUITests, + testTool.exampleLibraryTests])) + try testTool.validateTestPlan(testPlanPath: testTool.projectPath + "ExampleProject2.xctestplan", + expected: Set([testTool.mainProjectTests, + testTool.mainProjectUITests, + testTool.exampleLibraryTests])) + } + + func testMultipleTestPlansMixedCliAndConfig() async throws { + // given - config has one test plan, CLI adds another + let tool = try testTool.createSUT( + config: Config(basePath: (testTool.projectPath + "ExampleWorkspace.xcworkspace").string, + testPlan: "ExampleProject.xctestplan", + testPlans: nil, + exclude: nil, + extra: nil), + testPlan: "ExampleProject2.xctestplan") + + // when + try testTool.changeFile(at: testTool.projectPath + "ExampleLibrary/ExampleLibrary/ExampleLibrary.swift") + + // then + let result = try await tool.run() + XCTAssertEqual(result, Set([testTool.mainProjectMainTarget, + testTool.mainProjectTests, + testTool.mainProjectUITests, + testTool.exampleLibrary, + testTool.exampleLibraryTests])) + + // Verify both test plans were updated + try testTool.validateTestPlan(testPlanPath: testTool.projectPath + "ExampleProject.xctestplan", + expected: Set([testTool.mainProjectTests, + testTool.mainProjectUITests, + testTool.exampleLibraryTests])) + try testTool.validateTestPlan(testPlanPath: testTool.projectPath + "ExampleProject2.xctestplan", + expected: Set([testTool.mainProjectTests, + testTool.mainProjectUITests, + testTool.exampleLibraryTests])) + } } diff --git a/selective-testing-config-example.yml b/selective-testing-config-example.yml index 8b190a2..87cbbaa 100644 --- a/selective-testing-config-example.yml +++ b/selective-testing-config-example.yml @@ -1,5 +1,13 @@ basePath: Workspace.xcworkspace + +# Single test plan (for backwards compatibility) testPlan: TestPlan.xctestplan + +# Multiple test plans (recommended for processing multiple test plans) +# testPlans: +# - TestPlan1.xctestplan +# - TestPlan2.xctestplan + exclude: - Fixtures extra: