From 7128151465bc5d546a43883976ab294e982fd27b Mon Sep 17 00:00:00 2001 From: Mike Gerasimenko Date: Fri, 17 Oct 2025 22:23:03 +0200 Subject: [PATCH] feat(65): Allow project in subfolder --- .../DependencyGraph.swift | 6 +- .../IntegrationTestTool.swift | 17 +- .../SelectiveTestingWorkspaceTests.swift | 202 ++++++++++-------- 3 files changed, 130 insertions(+), 95 deletions(-) diff --git a/Sources/DependencyCalculator/DependencyGraph.swift b/Sources/DependencyCalculator/DependencyGraph.swift index 97c5dad..64d5758 100644 --- a/Sources/DependencyCalculator/DependencyGraph.swift +++ b/Sources/DependencyCalculator/DependencyGraph.swift @@ -295,7 +295,7 @@ extension WorkspaceInfo { var dependsOn: [TargetIdentity: Set] = [:] var files: [TargetIdentity: Set] = [:] var folders: [Path: TargetIdentity] = [:] - var candidateTestPlan: String? = nil + var candidateTestPlan: Path? = nil var packagesByName: [String: PackageTargetMetadata] = packages.toDictionary(path: \.name) let targetsByName = project.pbxproj.nativeTargets.toDictionary(path: \.name) @@ -385,14 +385,14 @@ extension WorkspaceInfo { // Find existing test plans project.sharedData?.schemes.forEach { scheme in scheme.testAction?.testPlans?.forEach { plan in - candidateTestPlan = plan.reference.replacingOccurrences(of: "container:", with: "") + candidateTestPlan = path.parent() + plan.reference.replacingOccurrences(of: "container:", with: "") } } return WorkspaceInfo(files: files, folders: folders, dependencyStructure: DependencyGraph(dependsOn: dependsOn), - candidateTestPlan: candidateTestPlan) + candidateTestPlan: candidateTestPlan?.string) } private static func isSwiftVersion6Plus() throws -> Bool { diff --git a/Tests/SelectiveTestingTests/IntegrationTestTool.swift b/Tests/SelectiveTestingTests/IntegrationTestTool.swift index 4858ea9..792217c 100644 --- a/Tests/SelectiveTestingTests/IntegrationTestTool.swift +++ b/Tests/SelectiveTestingTests/IntegrationTestTool.swift @@ -13,14 +13,21 @@ import XCTest final class IntegrationTestTool { var projectPath: Path = "" - func setUp() throws { + func setUp(subfolder: Bool = false) throws { let tmpPath = Path.temporary.absolute() guard let exampleInBundle = Bundle.module.path(forResource: "ExampleProject", ofType: "") else { fatalError("Missing ExampleProject in TestBundle") } projectPath = tmpPath + "ExampleProject" try? FileManager.default.removeItem(atPath: projectPath.string) - try FileManager.default.copyItem(atPath: exampleInBundle, toPath: projectPath.string) + if subfolder { + let finalPath = (projectPath + "Subfolder").string + try FileManager.default.createDirectory(atPath: projectPath.string, withIntermediateDirectories: true) + try FileManager.default.copyItem(atPath: exampleInBundle, toPath: finalPath) + } + else { + try FileManager.default.copyItem(atPath: exampleInBundle, toPath: projectPath.string) + } FileManager.default.changeCurrentDirectoryPath(projectPath.string) try Shell.execOrFail("git init") try Shell.execOrFail("git config commit.gpgsign false") @@ -33,6 +40,12 @@ final class IntegrationTestTool { func tearDown() throws { try? FileManager.default.removeItem(atPath: projectPath.string) } + + func withTestTool(subfolder: Bool = false, closure: () async throws -> Void) async throws { + try setUp(subfolder: subfolder) + try await closure() + try tearDown() + } func changeFile(at path: Path) throws { let handle = FileHandle(forUpdatingAtPath: path.string)! diff --git a/Tests/SelectiveTestingTests/SelectiveTestingWorkspaceTests.swift b/Tests/SelectiveTestingTests/SelectiveTestingWorkspaceTests.swift index 4f7931b..4f15488 100644 --- a/Tests/SelectiveTestingTests/SelectiveTestingWorkspaceTests.swift +++ b/Tests/SelectiveTestingTests/SelectiveTestingWorkspaceTests.swift @@ -11,114 +11,136 @@ import XCTest final class SelectiveTestingWorksapceTests: XCTestCase { let testTool = IntegrationTestTool() - override func setUp() async throws { - try await super.setUp() - - try testTool.setUp() - } - - override func tearDown() async throws { - try await super.tearDown() - - try testTool.tearDown() - } - func testProjectLoading_empty() async throws { - // given - let tool = try testTool.createSUT() - // when - let result = try await tool.run() - // then - XCTAssertEqual(result, Set()) + try await testTool.withTestTool { + // given + let tool = try testTool.createSUT() + // when + let result = try await tool.run() + // then + XCTAssertEqual(result, Set()) + } } func testProjectLoading_changeLibrary() async throws { - // given - let tool = try testTool.createSUT() - // 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])) + try await testTool.withTestTool { + // given + let tool = try testTool.createSUT() + // 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])) + } } func testProjectLoading_changeAsset() async throws { - // given - let tool = try testTool.createSUT() - // when - try testTool.changeFile(at: testTool.projectPath + "ExampleProject/Assets.xcassets/Contents.json") - - // then - let result = try await tool.run() - XCTAssertEqual(result, Set([testTool.mainProjectMainTarget, - testTool.mainProjectTests, - testTool.mainProjectUITests])) + try await testTool.withTestTool { + // given + let tool = try testTool.createSUT() + // when + try testTool.changeFile(at: testTool.projectPath + "ExampleProject/Assets.xcassets/Contents.json") + + // then + let result = try await tool.run() + XCTAssertEqual(result, Set([testTool.mainProjectMainTarget, + testTool.mainProjectTests, + testTool.mainProjectUITests])) + } } func testProjectLoading_testPlanChange() async throws { - // given - let tool = try testTool.createSUT() - // when - try testTool.changeFile(at: testTool.projectPath + "ExampleProject.xctestplan") - - // then - let result = try await tool.run() - XCTAssertEqual(result, Set()) + try await testTool.withTestTool { + // given + let tool = try testTool.createSUT() + // when + try testTool.changeFile(at: testTool.projectPath + "ExampleProject.xctestplan") + + // then + let result = try await tool.run() + XCTAssertEqual(result, Set()) + } } func testProjectLoading_testWorkspaceFileChange() async throws { - // given - let tool = try testTool.createSUT() - // when - try testTool.changeFile(at: testTool.projectPath + "ExampleWorkspace.xcworkspace/contents.xcworkspacedata") - // then - let result = try await tool.run() - XCTAssertEqual(result, Set([ - testTool.mainProjectMainTarget, - testTool.mainProjectTests, - testTool.mainProjectUITests, - testTool.mainProjectLibrary, - testTool.mainProjectLibraryTests, - testTool.exampleLibraryTests, - testTool.exampleLibrary, - testTool.exampleLibraryInGroup, - ])) + try await testTool.withTestTool { + // given + let tool = try testTool.createSUT() + // when + try testTool.changeFile(at: testTool.projectPath + "ExampleWorkspace.xcworkspace/contents.xcworkspacedata") + // then + let result = try await tool.run() + XCTAssertEqual(result, Set([ + testTool.mainProjectMainTarget, + testTool.mainProjectTests, + testTool.mainProjectUITests, + testTool.mainProjectLibrary, + testTool.mainProjectLibraryTests, + testTool.exampleLibraryTests, + testTool.exampleLibrary, + testTool.exampleLibraryInGroup, + ])) + } } func testProjectLoading_testProjectFileChange() async throws { - // given - let tool = try testTool.createSUT() - // when - try testTool.changeFile(at: testTool.projectPath + "ExampleProject.xcodeproj/project.pbxproj") - - // then - let result = try await tool.run() - XCTAssertEqual(result, Set([ - testTool.mainProjectMainTarget, - testTool.mainProjectTests, - testTool.mainProjectUITests, - testTool.mainProjectLibrary, - testTool.mainProjectLibraryTests, - ])) + try await testTool.withTestTool { + // given + let tool = try testTool.createSUT() + // when + try testTool.changeFile(at: testTool.projectPath + "ExampleProject.xcodeproj/project.pbxproj") + + // then + let result = try await tool.run() + XCTAssertEqual(result, Set([ + testTool.mainProjectMainTarget, + testTool.mainProjectTests, + testTool.mainProjectUITests, + testTool.mainProjectLibrary, + testTool.mainProjectLibraryTests, + ])) + } } func testInferTestPlan() async throws { - // given - let tool = try testTool.createSUT(config: nil, - testPlan: nil) - // when - try testTool.changeFile(at: testTool.projectPath + "ExampleLibrary/ExampleLibrary/ExampleLibrary.swift") - - // then - let _ = try await tool.run() - try testTool.validateTestPlan(testPlanPath: testTool.projectPath + "ExampleProject.xctestplan", - expected: Set([testTool.mainProjectTests, - testTool.mainProjectUITests, - testTool.exampleLibraryTests])) + try await testTool.withTestTool { + // given + let tool = try testTool.createSUT(config: nil, + testPlan: nil) + // when + try testTool.changeFile(at: testTool.projectPath + "ExampleLibrary/ExampleLibrary/ExampleLibrary.swift") + + // then + let _ = try await tool.run() + try testTool.validateTestPlan(testPlanPath: testTool.projectPath + "ExampleProject.xctestplan", + expected: Set([testTool.mainProjectTests, + testTool.mainProjectUITests, + testTool.exampleLibraryTests])) + } + } + + func testInferTestPlanInSubfolder() async throws { + try await testTool.withTestTool(subfolder: true) { + // given + let tool = try testTool.createSUT( + config: nil, + basePath: testTool.projectPath + "Subfolder", + testPlan: nil) + + // when + try testTool.changeFile(at: testTool.projectPath + "Subfolder/ExampleLibrary/ExampleLibrary/ExampleLibrary.swift") + + // then + let _ = try await tool.run() + try testTool.validateTestPlan(testPlanPath: testTool.projectPath + "Subfolder/ExampleProject.xctestplan", + expected: Set([testTool.mainProjectTests, + testTool.mainProjectUITests, + testTool.exampleLibraryTests])) + } } }