diff --git a/Package.swift b/Package.swift index 9c4474b..7d53159 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.6 +// swift-tools-version: 6.0 import PackageDescription diff --git a/Plugins/SelectiveTestingPlugin/SelectiveTestingPlugin.swift b/Plugins/SelectiveTestingPlugin/SelectiveTestingPlugin.swift index e2fc66a..7f0dc29 100644 --- a/Plugins/SelectiveTestingPlugin/SelectiveTestingPlugin.swift +++ b/Plugins/SelectiveTestingPlugin/SelectiveTestingPlugin.swift @@ -7,11 +7,10 @@ import PackagePlugin @main struct SelectiveTestingPlugin: CommandPlugin { - private func run(_ executable: String, arguments: [String] = []) throws { - let executableURL = URL(fileURLWithPath: executable) + private func run(_ executable: URL, arguments: [String] = []) throws { let process = Process() - process.executableURL = executableURL + process.executableURL = executable process.arguments = arguments try process.run() @@ -24,10 +23,10 @@ struct SelectiveTestingPlugin: CommandPlugin { } func performCommand(context: PluginContext, arguments: [String]) async throws { - FileManager().changeCurrentDirectoryPath(context.package.directory.string) + FileManager.default.changeCurrentDirectoryPath(context.package.directoryURL.path) let tool = try context.tool(named: "xcode-selective-test") - try run(tool.path.string, arguments: arguments) + try run(tool.url, arguments: arguments) } } @@ -36,7 +35,7 @@ struct SelectiveTestingPlugin: CommandPlugin { extension SelectiveTestingPlugin: XcodeCommandPlugin { func performCommand(context: XcodePluginContext, arguments: [String]) throws { - FileManager().changeCurrentDirectoryPath(context.xcodeProject.directory.string) + FileManager.default.changeCurrentDirectoryPath(context.xcodeProject.directoryURL.path) let tool = try context.tool(named: "xcode-selective-test") @@ -66,7 +65,7 @@ struct SelectiveTestingPlugin: CommandPlugin { } } - try run(tool.path.string, arguments: toolArguments) + try run(tool.url, arguments: toolArguments) } } #endif diff --git a/Sources/DependencyCalculator/DependencyGraph.swift b/Sources/DependencyCalculator/DependencyGraph.swift index 5a0adfb..5e60bbe 100644 --- a/Sources/DependencyCalculator/DependencyGraph.swift +++ b/Sources/DependencyCalculator/DependencyGraph.swift @@ -152,15 +152,22 @@ extension WorkspaceInfo { dependencyStructure: resultDependencies, candidateTestPlans: candidateTestPlans) if let config { + let additionalBasePath: Path + if path.extension == "xcworkspace" || path.extension == "xcodeproj" { + additionalBasePath = path.parent() + } else { + additionalBasePath = path + } // Process additional config - return processAdditional(config: config, workspaceInfo: workspaceInfo) + return processAdditional(config: config, workspaceInfo: workspaceInfo, basePath: additionalBasePath) } else { return workspaceInfo } } static func processAdditional(config: WorkspaceInfo.AdditionalConfig, - workspaceInfo: WorkspaceInfo) -> WorkspaceInfo + workspaceInfo: WorkspaceInfo, + basePath: Path) -> WorkspaceInfo { var files = workspaceInfo.files var folders = workspaceInfo.folders @@ -191,7 +198,7 @@ extension WorkspaceInfo { } for filePath in filesToAdd { - let path = Path(filePath).absolute() + let path = (basePath + filePath).absolute() guard path.exists else { Logger.error("Config: Path \(path) does not exist") diff --git a/Sources/DependencyCalculator/PackageMetadata.swift b/Sources/DependencyCalculator/PackageMetadata.swift index 3e21461..9b69f47 100644 --- a/Sources/DependencyCalculator/PackageMetadata.swift +++ b/Sources/DependencyCalculator/PackageMetadata.swift @@ -25,7 +25,7 @@ struct PackageTargetMetadata { flags.append("--ignore-lock") } - let manifest = try Shell.execOrFail("cd \(path) && swift package dump-package \(flags.joined(separator: " "))") + let manifest = try Shell.execOrFail("(cd \(path) && swift package dump-package \(flags.joined(separator: " ")))") .trimmingCharacters(in: .newlines) guard let manifestData = manifest.data(using: .utf8), let manifestJson = try JSONSerialization.jsonObject(with: manifestData, options: []) as? [String: Any], diff --git a/Sources/Git/Git+Changeset.swift b/Sources/Git/Git+Changeset.swift index 4856df3..6263721 100644 --- a/Sources/Git/Git+Changeset.swift +++ b/Sources/Git/Git+Changeset.swift @@ -11,7 +11,7 @@ public extension Git { func changeset(baseBranch: String, verbose: Bool = false) throws -> Set { let gitRoot = try repoRoot() - var currentBranch = try Shell.execOrFail("cd \(gitRoot) && git branch --show-current").trimmingCharacters(in: .newlines) + var currentBranch = try Shell.execOrFail("(cd \(gitRoot) && git branch --show-current)").trimmingCharacters(in: .newlines) if verbose { Logger.message("Current branch: \(currentBranch)") Logger.message("Base branch: \(baseBranch)") @@ -23,7 +23,7 @@ public extension Git { currentBranch = "HEAD" } - let changes = try Shell.execOrFail("cd \(gitRoot) && git diff '\(baseBranch)'..'\(currentBranch)' --name-only") + let changes = try Shell.execOrFail("(cd \(gitRoot) && git diff '\(baseBranch)'..'\(currentBranch)' --name-only)") let changesTrimmed = changes.trimmingCharacters(in: .whitespacesAndNewlines) guard !changesTrimmed.isEmpty else { @@ -36,7 +36,7 @@ public extension Git { func localChangeset() throws -> Set { let gitRoot = try repoRoot() - let changes = try Shell.execOrFail("cd \(gitRoot) && git diff HEAD --name-only") + let changes = try Shell.execOrFail("(cd \(gitRoot) && git diff HEAD --name-only)") let changesTrimmed = changes.trimmingCharacters(in: .whitespacesAndNewlines) diff --git a/Sources/Git/Git.swift b/Sources/Git/Git.swift index 11a4a94..e8894c1 100644 --- a/Sources/Git/Git.swift +++ b/Sources/Git/Git.swift @@ -14,7 +14,7 @@ public struct Git { } public func repoRoot() throws -> Path { - let gitPath = try Shell.execOrFail("cd \(path) && git rev-parse --show-toplevel").trimmingCharacters(in: .newlines) + let gitPath = try Shell.execOrFail("(cd \(path) && git rev-parse --show-toplevel)").trimmingCharacters(in: .newlines) return Path(gitPath).absolute() } @@ -22,7 +22,7 @@ public struct Git { public func find(pattern: String) throws -> Set { let gitRoot = try repoRoot() - let result = try Shell.exec("cd \(gitRoot) && git ls-files | grep \(pattern)").0.trimmingCharacters(in: .newlines) + let result = try Shell.exec("(cd \(gitRoot) && git ls-files | grep \(pattern))").0.trimmingCharacters(in: .newlines) guard !result.isEmpty else { return Set() diff --git a/Sources/SelectiveTestLogger/Logger.swift b/Sources/SelectiveTestLogger/Logger.swift index cb88b01..2dba74e 100644 --- a/Sources/SelectiveTestLogger/Logger.swift +++ b/Sources/SelectiveTestLogger/Logger.swift @@ -9,18 +9,21 @@ public struct StandardErrorOutputStream: TextOutputStream { public mutating func write(_ string: String) { fputs(string, stderr) } } -public var errStream = StandardErrorOutputStream() - public enum Logger { + private static func write(_ message: String) { + var stream = StandardErrorOutputStream() + print(message, to: &stream) + } + public static func message(_ message: String) { - print(message, to: &errStream) + write(message) } public static func warning(_ message: String) { - print("[WARN]: \(message)".yellow, to: &errStream) + write("[WARN]: \(message)".yellow) } public static func error(_ message: String) { - print("[ERROR]: \(message)".red, to: &errStream) + write("[ERROR]: \(message)".red) } } diff --git a/Sources/SelectiveTestingCore/SelectiveTestingTool.swift b/Sources/SelectiveTestingCore/SelectiveTestingTool.swift index fb5e735..10708a7 100644 --- a/Sources/SelectiveTestingCore/SelectiveTestingTool.swift +++ b/Sources/SelectiveTestingCore/SelectiveTestingTool.swift @@ -35,21 +35,40 @@ public final class SelectiveTestingTool { dryRun: Bool = false, verbose: Bool = false) throws { - if let configData = try? (Path.current + Config.defaultConfigName).read(), - let config = try Config.load(from: configData) - { - self.config = config + let suppliedBasePath = basePath.map { Path($0) } + var configCandidates: [Path] = [] + if let suppliedBasePath { + let baseDirectory: Path + if let ext = suppliedBasePath.extension, + ext == "xcworkspace" || ext == "xcodeproj" { + baseDirectory = suppliedBasePath.parent() + } else if suppliedBasePath.isDirectory { + baseDirectory = suppliedBasePath + } else { + baseDirectory = suppliedBasePath.parent() + } + configCandidates.append(baseDirectory + Config.defaultConfigName) + } + configCandidates.append(Path.current + Config.defaultConfigName) + + if let configPath = configCandidates.first(where: { $0.exists }), + let configData = try? configPath.read(), + let loadedConfig = try Config.load(from: configData) { + self.config = loadedConfig + if verbose { + Logger.message("Loaded config from \(configPath)") + } } else { config = nil } - let finalBasePath = basePath ?? + let finalBasePath = Path(basePath ?? config?.basePath ?? Path().glob("*.xcworkspace").first?.string ?? - Path().glob("*.xcodeproj").first?.string ?? "." + Path().glob("*.xcodeproj").first?.string ?? ".") self.baseBranch = baseBranch - self.basePath = Path(finalBasePath) + self.basePath = finalBasePath self.changedFiles = changedFiles self.printJSON = printJSON self.renderDependencyGraph = renderDependencyGraph @@ -59,12 +78,21 @@ public final class SelectiveTestingTool { self.verbose = verbose // Merge CLI test plans with config test plans - var allTestPlans = config?.allTestPlans ?? [] + var allTestPlans: [String] = config?.allTestPlans ?? [] allTestPlans.append(contentsOf: testPlans) self.testPlans = allTestPlans } public func run() async throws -> Set { + let workingDirectory: Path + if let ext = basePath.extension, + ext == "xcworkspace" || ext == "xcodeproj" { + workingDirectory = basePath.parent() + } else if basePath.isDirectory { + workingDirectory = basePath + } else { + workingDirectory = basePath.parent() + } // 1. Identify changed files let changeset: Set @@ -136,7 +164,13 @@ public final class SelectiveTestingTool { if !dryRun { // 4. Configure workspace to test given targets - let plansToUpdate = testPlans.isEmpty ? workspaceInfo.candidateTestPlans : testPlans + let plansToUpdate = testPlans.isEmpty ? + workspaceInfo.candidateTestPlans : + testPlans.map { plan in + let planPath = Path(plan) + let resolved = planPath.isAbsolute ? planPath : workingDirectory + planPath + return resolved.absolute().string + } if !plansToUpdate.isEmpty { for testPlan in plansToUpdate { diff --git a/Sources/xcode-selective-test/SelectiveTesting.swift b/Sources/xcode-selective-test/SelectiveTesting.swift index 54c7ffb..9605ca6 100644 --- a/Sources/xcode-selective-test/SelectiveTesting.swift +++ b/Sources/xcode-selective-test/SelectiveTesting.swift @@ -2,7 +2,7 @@ // Created by Mike Gerasymenko // -import ArgumentParser +@preconcurrency import ArgumentParser import SelectiveTestingCore import SelectiveTestLogger diff --git a/Tests/DependencyCalculatorTests/DependencyCalculatorTests.swift b/Tests/DependencyCalculatorTests/DependencyCalculatorTests.swift index 2cf2920..0aa7733 100644 --- a/Tests/DependencyCalculatorTests/DependencyCalculatorTests.swift +++ b/Tests/DependencyCalculatorTests/DependencyCalculatorTests.swift @@ -6,10 +6,11 @@ import Foundation import PathKit import SelectiveTestingCore +import Testing import Workspace -import XCTest -final class DependencyCalculatorTests: XCTestCase { +@Suite +struct DependencyCalculatorTests { func depStructure() -> (DependencyGraph, TargetIdentity, TargetIdentity, TargetIdentity, TargetIdentity, TargetIdentity, TargetIdentity) { let mainApp = TargetIdentity.project(path: "/folder/Project.xcodepoj", targetName: "MainApp", testTarget: false) let mainAppTests = TargetIdentity.project(path: "/folder/Project.xcodepoj", targetName: "MainAppTests", testTarget: true) @@ -35,8 +36,8 @@ final class DependencyCalculatorTests: XCTestCase { return (depsGraph, mainApp, module, submodule, mainAppTests, moduleTests, submoduleTests) } - func testGraphIntegrity_submodule() async throws { - // given + @Test + func graphIntegrity_submodule() async throws { let (depsGraph, mainApp, module, submodule, mainAppTests, moduleTests, submoduleTests) = depStructure() let files = Set([Path("/folder/submodule/file.swift")]) @@ -45,16 +46,14 @@ final class DependencyCalculatorTests: XCTestCase { folders: [:], dependencyStructure: depsGraph, candidateTestPlan: nil) - // when let affected = graph.affectedTargets(changedFiles: files) - // then - XCTAssertEqual(affected, Set([mainApp, mainAppTests, module, moduleTests, submodule, submoduleTests])) + #expect(affected == Set([mainApp, mainAppTests, module, moduleTests, submodule, submoduleTests])) } - func testGraphIntegrity_mainApp() async throws { - // given + @Test + func graphIntegrity_mainApp() async throws { let (depsGraph, mainApp, _, _, mainAppTests, _, _) = depStructure() let files = Set([Path("/folder/submodule/file.swift")]) @@ -63,16 +62,14 @@ final class DependencyCalculatorTests: XCTestCase { folders: [:], dependencyStructure: depsGraph, candidateTestPlan: nil) - // when let affected = graph.affectedTargets(changedFiles: files) - // then - XCTAssertEqual(affected, Set([mainApp, mainAppTests])) + #expect(affected == Set([mainApp, mainAppTests])) } - func testGraphIntegrity_module() async throws { - // given + @Test + func graphIntegrity_module() async throws { let (depsGraph, mainApp, module, _, mainAppTests, moduleTests, _) = depStructure() let files = Set([Path("/folder/submodule/file.swift")]) @@ -81,11 +78,9 @@ final class DependencyCalculatorTests: XCTestCase { folders: [:], dependencyStructure: depsGraph, candidateTestPlan: nil) - // when let affected = graph.affectedTargets(changedFiles: files) - // then - XCTAssertEqual(affected, Set([module, moduleTests, mainApp, mainAppTests])) + #expect(affected == Set([module, moduleTests, mainApp, mainAppTests])) } } diff --git a/Tests/DependencyCalculatorTests/PackageMetadataTests.swift b/Tests/DependencyCalculatorTests/PackageMetadataTests.swift index dc6fdd6..f4eac3b 100644 --- a/Tests/DependencyCalculatorTests/PackageMetadataTests.swift +++ b/Tests/DependencyCalculatorTests/PackageMetadataTests.swift @@ -5,26 +5,26 @@ @testable import DependencyCalculator import Foundation import PathKit +import Testing @testable import Workspace -import XCTest -final class PackageMetadataTests: XCTestCase { - func testPackageMetadataParsing_Simple() throws { - // given +@Suite +struct PackageMetadataTests { + @Test + func packageMetadataParsing_Simple() throws { guard let exampleInBundle = Bundle.module.path(forResource: "ExamplePackages", ofType: "") else { fatalError("Missing ExamplePackages in TestBundle") } - // when + let basePath = Path(exampleInBundle) + "Simple" let metadata = try PackageTargetMetadata.parse(at: basePath) - // then - XCTAssertEqual(metadata.count, 2) + #expect(metadata.count == 2) let first = metadata[0] - XCTAssertEqual(first.name, "ExampleSubpackage") - XCTAssertEqual(first.path, basePath) - XCTAssertEqual(first.dependsOn.count, 0) - XCTAssertEqual(first.affectedBy, Set([ + #expect(first.name == "ExampleSubpackage") + #expect(first.path == basePath) + #expect(first.dependsOn.isEmpty) + #expect(first.affectedBy == Set([ basePath + "Package.swift", basePath + "Package.resolved", basePath + "Sources" + "ExampleSubpackage", @@ -32,81 +32,79 @@ final class PackageMetadataTests: XCTestCase { ])) let second = metadata[1] - XCTAssertEqual(second.name, "ExampleSubpackageTests") - XCTAssertEqual(second.path, basePath) - XCTAssertEqual(second.dependsOn.count, 1) - XCTAssertEqual(second.affectedBy, Set([ + #expect(second.name == "ExampleSubpackageTests") + #expect(second.path == basePath) + #expect(second.dependsOn.count == 1) + #expect(second.affectedBy == Set([ basePath + "Package.swift", basePath + "Package.resolved", basePath + "Tests" + "ExampleSubpackageTests" ])) - let identity = try XCTUnwrap(second.dependsOn.first) + let identity = try #require(second.dependsOn.first) - XCTAssertEqual(identity.type, .package) - XCTAssertEqual(identity.path, basePath) - XCTAssertEqual(identity.name, "ExampleSubpackage") - XCTAssertFalse(identity.isTestTarget) + #expect(identity.type == .package) + #expect(identity.path == basePath) + #expect(identity.name == "ExampleSubpackage") + #expect(!identity.isTestTarget) } - func testPackageMetadataParsing_ExamplePacakge() throws { - // given + @Test + func packageMetadataParsing_ExamplePackage() throws { guard let exampleInBundle = Bundle.module.path(forResource: "ExamplePackages", ofType: "") else { fatalError("Missing ExamplePackages in TestBundle") } - // when + let basePath = Path(exampleInBundle) + "CrossDependency" let metadata = try PackageTargetMetadata.parse(at: basePath) - // then - XCTAssertEqual(metadata.count, 10) + #expect(metadata.count == 10) let first = metadata[0] - XCTAssertEqual(first.name, "SelectiveTesting") - XCTAssertEqual(first.path, basePath) - XCTAssertEqual(first.dependsOn, Set([TargetIdentity.package(path: basePath, targetName: "SelectiveTestingCore", testTarget: false)])) - XCTAssertEqual(first.affectedBy, Set([ + #expect(first.name == "SelectiveTesting") + #expect(first.path == basePath) + #expect(first.dependsOn == Set([TargetIdentity.package(path: basePath, targetName: "SelectiveTestingCore", testTarget: false)])) + #expect(first.affectedBy == Set([ basePath + "Package.swift", basePath + "Package.resolved", basePath + "Sources" + "SelectiveTesting" ])) let second = metadata[1] - XCTAssertEqual(second.name, "SelectiveTestingCore") - XCTAssertEqual(second.path, basePath) - XCTAssertEqual(second.dependsOn.count, 6) - XCTAssertEqual(second.affectedBy, Set([ + #expect(second.name == "SelectiveTestingCore") + #expect(second.path == basePath) + #expect(second.dependsOn.count == 6) + #expect(second.affectedBy == Set([ basePath + "Package.swift", basePath + "Package.resolved", basePath + "Sources" + "SelectiveTestingCore" ])) } - - func testPackageAndWorkspace() async throws { - // given + + @Test + func packageAndWorkspace() throws { guard let exampleInBundle = Bundle.module.path(forResource: "ExamplePackages", ofType: "") else { fatalError("Missing ExamplePackages in TestBundle") } - // when + let basePath = Path(exampleInBundle) + "PackageAndWorkspace" let metadata = try PackageTargetMetadata.parse(at: basePath) - // then - XCTAssertEqual(metadata.count, 2) + #expect(metadata.count == 2) let first = metadata[0] - XCTAssertEqual(first.name, "APackage") - XCTAssertEqual(first.path, basePath) - XCTAssertEqual(first.dependsOn, Set([])) - XCTAssertEqual(first.affectedBy, Set([ + #expect(first.name == "APackage") + #expect(first.path == basePath) + #expect(first.dependsOn.isEmpty) + #expect(first.affectedBy == Set([ basePath + "Package.swift", basePath + "Package.resolved", basePath + "Sources" + "APackage" ])) let second = metadata[1] - XCTAssertEqual(second.name, "APackageTests") - XCTAssertEqual(second.path, basePath) - XCTAssertEqual(second.dependsOn.count, 1) - XCTAssertEqual(second.affectedBy, Set([ + #expect(second.name == "APackageTests") + #expect(second.path == basePath) + #expect(second.dependsOn.count == 1) + #expect(second.affectedBy == Set([ basePath + "Package.swift", basePath + "Package.resolved", basePath + "Tests" + "APackageTests" diff --git a/Tests/SelectiveTestingTests/IntegrationTestTool.swift b/Tests/SelectiveTestingTests/IntegrationTestTool.swift index f788306..e36b8bc 100644 --- a/Tests/SelectiveTestingTests/IntegrationTestTool.swift +++ b/Tests/SelectiveTestingTests/IntegrationTestTool.swift @@ -6,19 +6,24 @@ import Foundation import PathKit @testable import SelectiveTestingCore import SelectiveTestShell +import Testing import TestConfigurator import Workspace -import XCTest -final class IntegrationTestTool { +struct IntegrationTestTool { var projectPath: Path = "" - func setUp(subfolder: Bool = false) throws { + private func runInProject(_ command: String) throws { + try Shell.execOrFail("(cd \"\(projectPath.string)\" && \(command))") + } + + init(subfolder: Bool = false) throws { let tmpPath = Path.temporary.absolute() + let uniqueFolder = "ExampleProject-\(UUID().uuidString)" guard let exampleInBundle = Bundle.module.path(forResource: "ExampleProject", ofType: "") else { fatalError("Missing ExampleProject in TestBundle") } - projectPath = tmpPath + "ExampleProject" + projectPath = tmpPath + uniqueFolder try? FileManager.default.removeItem(atPath: projectPath.string) if subfolder { let finalPath = (projectPath + "Subfolder").string @@ -28,23 +33,22 @@ final class IntegrationTestTool { 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") - try Shell.execOrFail("git checkout -b main") - try Shell.execOrFail("git add .") - try Shell.execOrFail("git commit -m \"Base\"") - try Shell.execOrFail("git checkout -b feature") + try runInProject("git init") + try runInProject("git config commit.gpgsign false") + try runInProject("git checkout -b main") + try runInProject("git add .") + try runInProject("git commit -m \"Base\"") + try runInProject("git checkout -b feature") } 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() + static func withTestTool(subfolder: Bool = false, closure: (IntegrationTestTool) async throws -> Void) async throws { + let tool = try IntegrationTestTool(subfolder: subfolder) + defer { try? tool.tearDown() } + try await closure(tool) } func changeFile(at path: Path) throws { @@ -53,22 +57,22 @@ final class IntegrationTestTool { try handle.write(contentsOf: "\n \n".data(using: .utf8)!) try handle.close() - try Shell.execOrFail("git add .") - try Shell.execOrFail("git commit -m \"Change\"") + try runInProject("git add .") + try runInProject("git commit -m \"Change\"") } func addFile(at path: Path) throws { FileManager().createFile(atPath: path.string, contents: "\n \n".data(using: .utf8)!) - try Shell.execOrFail("git add .") - try Shell.execOrFail("git commit -m \"Change\"") + try runInProject("git add .") + try runInProject("git commit -m \"Change\"") } func removeFile(at path: Path) throws { try path.delete() - try Shell.execOrFail("git add .") - try Shell.execOrFail("git commit -m \"Change\"") + try runInProject("git add .") + try runInProject("git commit -m \"Change\"") } func createSUT(config: Config? = nil, @@ -79,7 +83,7 @@ final class IntegrationTestTool { { if let config { let configText = try config.save() - let path = Path.current + Config.defaultConfigName + let path = projectPath + Config.defaultConfigName try configText.write(toFile: path.string, atomically: true, encoding: .utf8) } @@ -90,9 +94,27 @@ final class IntegrationTestTool { else { testPlans = [] } + + let resolvedBasePath: String? + if let basePath { + if basePath.isAbsolute { + resolvedBasePath = basePath.string + } else { + resolvedBasePath = (projectPath + basePath).string + } + } else if let configBasePath = config?.basePath { + let configPath = Path(configBasePath) + if configPath.isAbsolute { + resolvedBasePath = configPath.string + } else { + resolvedBasePath = (projectPath + configPath).string + } + } else { + resolvedBasePath = projectPath.string + } return try SelectiveTestingTool(baseBranch: "main", - basePath: basePath?.string, + basePath: resolvedBasePath, testPlans: testPlans, changedFiles: changedFiles, renderDependencyGraph: false, @@ -116,8 +138,8 @@ final class IntegrationTestTool { let container = Path(target.target.containerPath.replacingOccurrences(of: "container:", with: "")) let name = target.target.name - guard target.enabled ?? true else { - XCTFail("Unexpected \(target.target.name): disabled targets must be removed") + guard target.enabled != false else { + Issue.record("Unexpected \(target.target.name): disabled targets must be removed") return nil } @@ -128,7 +150,7 @@ final class IntegrationTestTool { } } - XCTAssertEqual(Set(testPlanTargets), expected) + #expect(Set(testPlanTargets) == expected) } func checkTestPlanUnmodified(at newPath: Path) throws { @@ -139,19 +161,19 @@ final class IntegrationTestTool { let originalContents = try String(contentsOfFile: orignialTestPlanPath.string) let newContents = try String(contentsOfFile: newPath.string) - XCTAssertEqual(originalContents, newContents) + #expect(originalContents == newContents) } - lazy var mainProjectMainTarget = TargetIdentity.project(path: projectPath + "ExampleProject.xcodeproj", targetName: "ExampleProject", testTarget: false) - lazy var mainProjectTests = TargetIdentity.project(path: projectPath + "ExampleProject.xcodeproj", targetName: "ExampleProjectTests", testTarget: true) - lazy var mainProjectLibrary = TargetIdentity.project(path: projectPath + "ExampleProject.xcodeproj", targetName: "ExmapleTargetLibrary", testTarget: false) - lazy var mainProjectLibraryTests = TargetIdentity.project(path: projectPath + "ExampleProject.xcodeproj", targetName: "ExmapleTargetLibraryTests", testTarget: true) - lazy var mainProjectUITests = TargetIdentity.project(path: projectPath + "ExampleProject.xcodeproj", targetName: "ExampleProjectUITests", testTarget: true) - lazy var exampleLibrary = TargetIdentity.project(path: projectPath + "ExampleLibrary/ExampleLibrary.xcodeproj", targetName: "ExampleLibrary", testTarget: false) - lazy var exampleLibraryTests = TargetIdentity.project(path: projectPath + "ExampleLibrary/ExampleLibrary.xcodeproj", targetName: "ExampleLibraryTests", testTarget: true) - lazy var exampleLibraryInGroup = TargetIdentity.project(path: projectPath + "Group/ExampleProjectInGroup.xcodeproj", targetName: "ExampleProjectInGroup", testTarget: false) - lazy var package = TargetIdentity.package(path: projectPath + "ExamplePackage", targetName: "ExamplePackage", testTarget: false) - lazy var packageTests = TargetIdentity.package(path: projectPath + "ExamplePackage", targetName: "ExamplePackageTests", testTarget: true) - lazy var subtests = TargetIdentity.package(path: projectPath + "ExamplePackage", targetName: "Subtests", testTarget: true) - lazy var binary = TargetIdentity.package(path: projectPath + "ExamplePackage", targetName: "BinaryTarget", testTarget: false) + func mainProjectMainTarget() -> TargetIdentity { TargetIdentity.project(path: projectPath + "ExampleProject.xcodeproj", targetName: "ExampleProject", testTarget: false) } + func mainProjectTests() -> TargetIdentity { TargetIdentity.project(path: projectPath + "ExampleProject.xcodeproj", targetName: "ExampleProjectTests", testTarget: true) } + func mainProjectLibrary() -> TargetIdentity { TargetIdentity.project(path: projectPath + "ExampleProject.xcodeproj", targetName: "ExmapleTargetLibrary", testTarget: false) } + func mainProjectLibraryTests() -> TargetIdentity { TargetIdentity.project(path: projectPath + "ExampleProject.xcodeproj", targetName: "ExmapleTargetLibraryTests", testTarget: true) } + func mainProjectUITests() -> TargetIdentity { TargetIdentity.project(path: projectPath + "ExampleProject.xcodeproj", targetName: "ExampleProjectUITests", testTarget: true) } + func exampleLibrary() -> TargetIdentity { TargetIdentity.project(path: projectPath + "ExampleLibrary/ExampleLibrary.xcodeproj", targetName: "ExampleLibrary", testTarget: false) } + func exampleLibraryTests() -> TargetIdentity { TargetIdentity.project(path: projectPath + "ExampleLibrary/ExampleLibrary.xcodeproj", targetName: "ExampleLibraryTests", testTarget: true) } + func exampleLibraryInGroup() -> TargetIdentity { TargetIdentity.project(path: projectPath + "Group/ExampleProjectInGroup.xcodeproj", targetName: "ExampleProjectInGroup", testTarget: false) } + func package() -> TargetIdentity { TargetIdentity.package(path: projectPath + "ExamplePackage", targetName: "ExamplePackage", testTarget: false) } + func packageTests() -> TargetIdentity { TargetIdentity.package(path: projectPath + "ExamplePackage", targetName: "ExamplePackageTests", testTarget: true) } + func subtests() -> TargetIdentity { TargetIdentity.package(path: projectPath + "ExamplePackage", targetName: "Subtests", testTarget: true) } + func binary() -> TargetIdentity { TargetIdentity.package(path: projectPath + "ExamplePackage", targetName: "BinaryTarget", testTarget: false) } } diff --git a/Tests/SelectiveTestingTests/SelectiveTestingConfigTests.swift b/Tests/SelectiveTestingTests/SelectiveTestingConfigTests.swift index c765497..79f70b4 100644 --- a/Tests/SelectiveTestingTests/SelectiveTestingConfigTests.swift +++ b/Tests/SelectiveTestingTests/SelectiveTestingConfigTests.swift @@ -5,106 +5,105 @@ import PathKit @testable import SelectiveTestingCore import SelectiveTestShell +import Testing import Workspace -import XCTest -final class SelectiveTestingConfigTests: XCTestCase { - let testTool = IntegrationTestTool() +@Suite +struct SelectiveTestingConfigTests { + @Test + func configWorkspacePath() async throws { + let testTool = try IntegrationTestTool() + defer { try? testTool.tearDown() } - 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 testConfigWorkspacePath() async throws { - // given let tool = try testTool.createSUT(config: Config(basePath: (testTool.projectPath + "ExampleWorkspace.xcworkspace").string, testPlan: nil, testPlans: nil, 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])) + #expect(result == Set([testTool.mainProjectMainTarget(), + testTool.mainProjectTests(), + testTool.mainProjectUITests(), + testTool.exampleLibrary(), + testTool.exampleLibraryTests()])) } - func testConfigTestplanPath() async throws { - // given + @Test + func configTestplanPath() async throws { + let testTool = try IntegrationTestTool() + defer { try? testTool.tearDown() } + let tool = try testTool.createSUT(config: Config(basePath: (testTool.projectPath + "ExampleWorkspace.xcworkspace").string, testPlan: "ExampleProject.xctestplan", testPlans: nil, 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])) + #expect(result == Set([testTool.mainProjectMainTarget(), + testTool.mainProjectTests(), + testTool.mainProjectUITests(), + testTool.exampleLibrary(), + testTool.exampleLibraryTests()])) try testTool.validateTestPlan(testPlanPath: testTool.projectPath + "ExampleProject.xctestplan", - expected: Set([testTool.mainProjectTests, - testTool.mainProjectUITests, - testTool.exampleLibraryTests])) + expected: Set([testTool.mainProjectTests(), + testTool.mainProjectUITests(), + testTool.exampleLibraryTests()])) } - func testConfigTestplanPath_packageChanged() async throws { - // given + @Test + func configTestplanPath_packageChanged() async throws { + let testTool = try IntegrationTestTool() + + defer { try? testTool.tearDown() } + let tool = try testTool.createSUT(config: Config(basePath: (testTool.projectPath + "ExampleWorkspace.xcworkspace").string, testPlan: "ExampleProject.xctestplan", testPlans: nil, exclude: nil, extra: nil)) - // when + try testTool.changeFile(at: testTool.projectPath + "ExamplePackage/Package.swift") - // then - let _ = try await tool.run() + _ = try await tool.run() try testTool.validateTestPlan(testPlanPath: testTool.projectPath + "ExampleProject.xctestplan", - expected: Set([testTool.mainProjectTests, - testTool.mainProjectUITests, - testTool.packageTests, - testTool.subtests])) + expected: Set([testTool.mainProjectTests(), + testTool.mainProjectUITests(), + testTool.packageTests(), + testTool.subtests()])) } - - func testConfigTestplanPath_packageResolvedChanged() async throws { - // given + + @Test + func configTestplanPath_packageResolvedChanged() async throws { + let testTool = try IntegrationTestTool() + defer { try? testTool.tearDown() } + let tool = try testTool.createSUT(config: Config(basePath: (testTool.projectPath + "ExampleWorkspace.xcworkspace").string, testPlan: "ExampleProject.xctestplan", testPlans: nil, exclude: nil, extra: nil)) - // when + try testTool.addFile(at: testTool.projectPath + "ExamplePackage/Package.resolved") - // then - let _ = try await tool.run() + _ = try await tool.run() try testTool.validateTestPlan(testPlanPath: testTool.projectPath + "ExampleProject.xctestplan", - expected: Set([testTool.mainProjectTests, - testTool.mainProjectUITests, - testTool.packageTests, - testTool.subtests])) + expected: Set([testTool.mainProjectTests(), + testTool.mainProjectUITests(), + testTool.packageTests(), + testTool.subtests()])) } - func testAdditionalDependency() async throws { - // given + @Test + func additionalDependency() async throws { + let testTool = try IntegrationTestTool() + defer { try? testTool.tearDown() } + let additionalConfig = WorkspaceInfo.AdditionalConfig(targetsFiles: [:], dependencies: ["ExampleProject:ExmapleTargetLibrary": ["ExampleSubpackage:ExampleSubpackage"]]) let fullConfig = Config(basePath: (testTool.projectPath + "ExampleWorkspace.xcworkspace").string, @@ -113,17 +112,19 @@ final class SelectiveTestingConfigTests: XCTestCase { exclude: nil, extra: additionalConfig) let tool = try testTool.createSUT(config: fullConfig) - // when + try testTool.changeFile(at: testTool.projectPath + "ExampleSubpackage/Package.swift") - // then let result = try await tool.run() - XCTAssertTrue(result.contains(testTool.mainProjectLibrary)) - XCTAssertTrue(result.contains(testTool.mainProjectLibraryTests)) + #expect(result.contains(testTool.mainProjectLibrary())) + #expect(result.contains(testTool.mainProjectLibraryTests())) } - func testAdditionalFiles() async throws { - // given + @Test + func additionalFiles() async throws { + let testTool = try IntegrationTestTool() + defer { try? testTool.tearDown() } + let additionalConfig = WorkspaceInfo.AdditionalConfig(targetsFiles: ["ExampleProject:ExmapleTargetLibrary": ["ExmapleTargetLibrary/SomeFile.swift"]], dependencies: [:]) let fullConfig = Config(basePath: (testTool.projectPath + "ExampleWorkspace.xcworkspace").string, @@ -132,46 +133,51 @@ final class SelectiveTestingConfigTests: XCTestCase { exclude: nil, extra: additionalConfig) let tool = try testTool.createSUT(config: fullConfig) - // when + try testTool.addFile(at: testTool.projectPath + "ExmapleTargetLibrary/SomeFile.swift") - // then let result = try await tool.run() - XCTAssertTrue(result.contains(testTool.mainProjectLibrary)) - XCTAssertTrue(result.contains(testTool.mainProjectLibraryTests)) + #expect(result.contains(testTool.mainProjectLibrary())) + #expect(result.contains(testTool.mainProjectLibraryTests())) } - func testExclude() async throws { - // given + @Test + func exclude() async throws { + let testTool = try IntegrationTestTool() + defer { try? testTool.tearDown() } + let tool = try testTool.createSUT(config: Config(basePath: (testTool.projectPath + "ExampleWorkspace.xcworkspace").string, testPlan: "ExampleProject.xctestplan", testPlans: nil, exclude: ["ExamplePackage"], extra: nil)) - // when + try testTool.changeFile(at: testTool.projectPath + "ExamplePackage/Package.swift") - // then - let _ = try await tool.run() + _ = try await tool.run() try testTool.validateTestPlan(testPlanPath: testTool.projectPath + "ExampleProject.xctestplan", expected: Set([])) } - func testPackageChangeInDifferentNamedPackage() async throws { - // given + @Test + func packageChangeInDifferentNamedPackage() async throws { + let testTool = try IntegrationTestTool() + defer { try? testTool.tearDown() } + let tool = try testTool.createSUT() - // when try testTool.changeFile(at: testTool.projectPath + "ExamplePackage/Tests/Subtests/Test.swift") - // then - let _ = try await tool.run() + _ = try await tool.run() try testTool.validateTestPlan(testPlanPath: testTool.projectPath + "ExampleProject.xctestplan", - expected: Set([testTool.subtests])) + expected: Set([testTool.subtests()])) } - - func testDryRun() async throws { - // given + + @Test + func dryRun() async throws { + let testTool = try IntegrationTestTool() + defer { try? testTool.tearDown() } + let tool = try SelectiveTestingTool(baseBranch: "main", basePath: (testTool.projectPath + "ExampleWorkspace.xcworkspace").string, testPlans: ["ExampleProject.xctestplan"], @@ -180,76 +186,77 @@ final class SelectiveTestingConfigTests: XCTestCase { dryRun: true, verbose: true) - // when try testTool.changeFile(at: testTool.projectPath + "ExamplePackage/Tests/Subtests/Test.swift") - // then - let _ = try await tool.run() + _ = try await tool.run() try testTool.checkTestPlanUnmodified(at: testTool.projectPath + "ExampleProject.xctestplan") } - func testMultipleTestPlansViaCLI() async throws { - // given + @Test + func multipleTestPlansViaCLI() async throws { + let testTool = try IntegrationTestTool() + defer { try? testTool.tearDown() } + 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])) + #expect(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])) + expected: Set([testTool.mainProjectTests(), + testTool.mainProjectUITests(), + testTool.exampleLibraryTests()])) try testTool.validateTestPlan(testPlanPath: testTool.projectPath + "ExampleProject2.xctestplan", - expected: Set([testTool.mainProjectTests, - testTool.mainProjectUITests, - testTool.exampleLibraryTests])) + expected: Set([testTool.mainProjectTests(), + testTool.mainProjectUITests(), + testTool.exampleLibraryTests()])) } - func testMultipleTestPlansViaConfig() async throws { - // given + @Test + func multipleTestPlansViaConfig() async throws { + let testTool = try IntegrationTestTool() + defer { try? testTool.tearDown() } + 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])) + #expect(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])) + expected: Set([testTool.mainProjectTests(), + testTool.mainProjectUITests(), + testTool.exampleLibraryTests()])) try testTool.validateTestPlan(testPlanPath: testTool.projectPath + "ExampleProject2.xctestplan", - expected: Set([testTool.mainProjectTests, - testTool.mainProjectUITests, - testTool.exampleLibraryTests])) + expected: Set([testTool.mainProjectTests(), + testTool.mainProjectUITests(), + testTool.exampleLibraryTests()])) } - func testMultipleTestPlansMixedCliAndConfig() async throws { - // given - config has one test plan, CLI adds another + @Test + func multipleTestPlansMixedCliAndConfig() async throws { + let testTool = try IntegrationTestTool() + defer { try? testTool.tearDown() } + let tool = try testTool.createSUT( config: Config(basePath: (testTool.projectPath + "ExampleWorkspace.xcworkspace").string, testPlan: "ExampleProject.xctestplan", @@ -258,25 +265,22 @@ final class SelectiveTestingConfigTests: XCTestCase { 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])) + #expect(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])) + expected: Set([testTool.mainProjectTests(), + testTool.mainProjectUITests(), + testTool.exampleLibraryTests()])) try testTool.validateTestPlan(testPlanPath: testTool.projectPath + "ExampleProject2.xctestplan", - expected: Set([testTool.mainProjectTests, - testTool.mainProjectUITests, - testTool.exampleLibraryTests])) + expected: Set([testTool.mainProjectTests(), + testTool.mainProjectUITests(), + testTool.exampleLibraryTests()])) } } diff --git a/Tests/SelectiveTestingTests/SelectiveTestingPackagesTests.swift b/Tests/SelectiveTestingTests/SelectiveTestingPackagesTests.swift index 985664e..cd439c6 100644 --- a/Tests/SelectiveTestingTests/SelectiveTestingPackagesTests.swift +++ b/Tests/SelectiveTestingTests/SelectiveTestingPackagesTests.swift @@ -5,98 +5,89 @@ import PathKit @testable import SelectiveTestingCore import SelectiveTestShell +import Testing import Workspace -import XCTest -final class SelectiveTestingPackagesTests: XCTestCase { - let testTool = IntegrationTestTool() +@Suite +struct SelectiveTestingPackagesTests { + @Test + func projectLoading_changePackage() async throws { + let testTool = try IntegrationTestTool() + defer { try? testTool.tearDown() } - 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_changePackage() async throws { - // given let tool = try testTool.createSUT() - // when try testTool.changeFile(at: testTool.projectPath + "ExamplePackage/Sources/ExamplePackage/ExamplePackage.swift") - // then let result = try await tool.run() - XCTAssertEqual(result, Set([testTool.mainProjectMainTarget, - testTool.mainProjectTests, - testTool.mainProjectUITests, - testTool.package, - testTool.packageTests, - testTool.subtests])) + #expect(result == Set([testTool.mainProjectMainTarget(), + testTool.mainProjectTests(), + testTool.mainProjectUITests(), + testTool.package(), + testTool.packageTests(), + testTool.subtests()])) } - func testProjectLoading_changePackageDefintion() async throws { - // given + @Test + func projectLoading_changePackageDefinition() async throws { + let testTool = try IntegrationTestTool() + defer { try? testTool.tearDown() } + let tool = try testTool.createSUT() - // when try testTool.changeFile(at: testTool.projectPath + "ExamplePackage/Package.swift") - // then let result = try await tool.run() - XCTAssertEqual(result, Set([testTool.mainProjectMainTarget, - testTool.mainProjectTests, - testTool.mainProjectUITests, - testTool.package, - testTool.packageTests, - testTool.subtests, - testTool.binary])) + #expect(result == Set([testTool.mainProjectMainTarget(), + testTool.mainProjectTests(), + testTool.mainProjectUITests(), + testTool.package(), + testTool.packageTests(), + testTool.subtests(), + testTool.binary()])) } - func testProjectLoading_packageAddFile() async throws { - // given + @Test + func projectLoading_packageAddFile() async throws { + let testTool = try IntegrationTestTool() + defer { try? testTool.tearDown() } + let tool = try testTool.createSUT() - // when try testTool.addFile(at: testTool.projectPath + "ExamplePackage/Sources/ExamplePackage/ExamplePackageFile.swift") - // then let result = try await tool.run() - XCTAssertEqual(result, Set([testTool.mainProjectMainTarget, - testTool.mainProjectTests, - testTool.mainProjectUITests, - testTool.package, - testTool.packageTests, - testTool.subtests])) + #expect(result == Set([testTool.mainProjectMainTarget(), + testTool.mainProjectTests(), + testTool.mainProjectUITests(), + testTool.package(), + testTool.packageTests(), + testTool.subtests()])) } - func testProjectLoading_packageRemoveFile() async throws { - // given + @Test + func projectLoading_packageRemoveFile() async throws { + let testTool = try IntegrationTestTool() + defer { try? testTool.tearDown() } + let tool = try testTool.createSUT() - // when try testTool.removeFile(at: testTool.projectPath + "ExamplePackage/Sources/ExamplePackage/ExamplePackage.swift") - // then let result = try await tool.run() - XCTAssertEqual(result, Set([testTool.mainProjectMainTarget, - testTool.mainProjectTests, - testTool.mainProjectUITests, - testTool.package, - testTool.packageTests, - testTool.subtests])) + #expect(result == Set([testTool.mainProjectMainTarget(), + testTool.mainProjectTests(), + testTool.mainProjectUITests(), + testTool.package(), + testTool.packageTests(), + testTool.subtests()])) } - func testBinaryTargetChange() async throws { - // given - let tool = try testTool.createSUT() + @Test + func binaryTargetChange() async throws { + let testTool = try IntegrationTestTool() + defer { try? testTool.tearDown() } - // when + let tool = try testTool.createSUT() try testTool.changeFile(at: testTool.projectPath + "ExamplePackage/Binary.xcframework/Info.plist") - // then let result = try await tool.run() - XCTAssertEqual(result, Set([testTool.binary])) + #expect(result == Set([testTool.binary()])) } } diff --git a/Tests/SelectiveTestingTests/SelectiveTestingPerformanceTests.swift b/Tests/SelectiveTestingTests/SelectiveTestingPerformanceTests.swift index e08c3f6..fc2663d 100644 --- a/Tests/SelectiveTestingTests/SelectiveTestingPerformanceTests.swift +++ b/Tests/SelectiveTestingTests/SelectiveTestingPerformanceTests.swift @@ -5,45 +5,25 @@ import Foundation import PathKit @testable import SelectiveTestingCore -import XCTest - -final class SelectiveTestingPerformanceTests: 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 testPerformance() async throws { - measure { - let expecation = expectation(description: "Job is done") - Task { - // 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, - ])) - expecation.fulfill() - } - - wait(for: [expecation], timeout: 2000) - } +import Testing + +@Suite +struct SelectiveTestingPerformanceTests { + @Test + func performance() async throws { + let testTool = try IntegrationTestTool() + defer { try? testTool.tearDown() } + + let tool = try testTool.createSUT() + try testTool.changeFile(at: testTool.projectPath + "ExampleProject.xcodeproj/project.pbxproj") + + let result = try await tool.run() + #expect(result == Set([ + testTool.mainProjectMainTarget(), + testTool.mainProjectTests(), + testTool.mainProjectUITests(), + testTool.mainProjectLibrary(), + testTool.mainProjectLibraryTests(), + ])) } } diff --git a/Tests/SelectiveTestingTests/SelectiveTestingProjectTests.swift b/Tests/SelectiveTestingTests/SelectiveTestingProjectTests.swift index c67481b..a51b995 100644 --- a/Tests/SelectiveTestingTests/SelectiveTestingProjectTests.swift +++ b/Tests/SelectiveTestingTests/SelectiveTestingProjectTests.swift @@ -5,133 +5,128 @@ import PathKit @testable import SelectiveTestingCore import SelectiveTestShell +import Testing import Workspace -import XCTest -final class SelectiveTestingProjectTests: XCTestCase { - let testTool = IntegrationTestTool() +@Suite +struct SelectiveTestingProjectTests { + @Test + func projectAlone() async throws { + let testTool = try IntegrationTestTool() + defer { try? testTool.tearDown() } - 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 testProjectAlone() async throws { - // given let tool = try testTool.createSUT(config: nil, basePath: "ExampleProject.xcodeproj") - // 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, + #expect(result == Set([ + testTool.mainProjectMainTarget(), + testTool.mainProjectTests(), + testTool.mainProjectUITests(), + testTool.mainProjectLibrary(), + testTool.mainProjectLibraryTests(), ])) } - func testProjectDeepGroupPathChange_turbo() async throws { - // given + @Test + func projectDeepGroupPathChange_turbo() async throws { + let testTool = try IntegrationTestTool() + defer { try? testTool.tearDown() } + let tool = try testTool.createSUT(config: nil, basePath: "ExampleProject.xcodeproj", turbo: true) - // when try testTool.changeFile(at: testTool.projectPath + "ExampleProject/DeepGroup/Path/GroupContentView.swift") - // then let result = try await tool.run() - XCTAssertEqual(result, Set([ - testTool.mainProjectMainTarget + #expect(result == Set([ + testTool.mainProjectMainTarget() ])) } - func testProjectDeepGroupPathChange() async throws { - // given + @Test + func projectDeepGroupPathChange() async throws { + let testTool = try IntegrationTestTool() + defer { try? testTool.tearDown() } + let tool = try testTool.createSUT(config: nil, basePath: "ExampleProject.xcodeproj") - // when try testTool.changeFile(at: testTool.projectPath + "ExampleProject/DeepGroup/Path/GroupContentView.swift") - // then let result = try await tool.run() - XCTAssertEqual(result, Set([ - testTool.mainProjectMainTarget, - testTool.mainProjectTests, - testTool.mainProjectUITests, + #expect(result == Set([ + testTool.mainProjectMainTarget(), + testTool.mainProjectTests(), + testTool.mainProjectUITests(), ])) } - func testProjectDeepFolderPathChange_turbo() async throws { - // given + @Test + func projectDeepFolderPathChange_turbo() async throws { + let testTool = try IntegrationTestTool() + defer { try? testTool.tearDown() } + let tool = try testTool.createSUT(config: nil, basePath: "ExampleProject.xcodeproj", turbo: true) - // when try testTool.changeFile(at: testTool.projectPath + "ExampleProject/DeepFolder/Path/FolderContentView.swift") - // then let result = try await tool.run() - XCTAssertEqual(result, Set([ - testTool.mainProjectMainTarget + #expect(result == Set([ + testTool.mainProjectMainTarget() ])) } - func testProjectDeepFolderPathChange() async throws { - // given + @Test + func projectDeepFolderPathChange() async throws { + let testTool = try IntegrationTestTool() + defer { try? testTool.tearDown() } + let tool = try testTool.createSUT(config: nil, basePath: "ExampleProject.xcodeproj") - // when try testTool.changeFile(at: testTool.projectPath + "ExampleProject/DeepFolder/Path/FolderContentView.swift") - // then let result = try await tool.run() - XCTAssertEqual(result, Set([ - testTool.mainProjectMainTarget, - testTool.mainProjectTests, - testTool.mainProjectUITests, + #expect(result == Set([ + testTool.mainProjectMainTarget(), + testTool.mainProjectTests(), + testTool.mainProjectUITests(), ])) } - func testProjectLocalizedPathChange() async throws { - // given + @Test + func projectLocalizedPathChange() async throws { + let testTool = try IntegrationTestTool() + defer { try? testTool.tearDown() } + let tool = try testTool.createSUT(config: nil, basePath: "ExampleProject.xcodeproj") - // when try testTool.changeFile(at: testTool.projectPath + "ExampleProject/Base.lproj/Example.xib") - // then let result = try await tool.run() - XCTAssertEqual(result, Set([ - testTool.mainProjectMainTarget, - testTool.mainProjectTests, - testTool.mainProjectUITests, + #expect(result == Set([ + testTool.mainProjectMainTarget(), + testTool.mainProjectTests(), + testTool.mainProjectUITests(), ])) } - - func testPassingChangedFiles() async throws { - // given & when + + @Test + func passingChangedFiles() async throws { + let testTool = try IntegrationTestTool() + defer { try? testTool.tearDown() } + let changedPath = testTool.projectPath + "ExampleProject/Base.lproj/Example.xib" let tool = try testTool.createSUT(config: nil, basePath: "ExampleProject.xcodeproj", changedFiles: [changedPath.string]) - // then let result = try await tool.run() - XCTAssertEqual(result, Set([ - testTool.mainProjectMainTarget, - testTool.mainProjectTests, - testTool.mainProjectUITests, + #expect(result == Set([ + testTool.mainProjectMainTarget(), + testTool.mainProjectTests(), + testTool.mainProjectUITests(), ])) } } diff --git a/Tests/SelectiveTestingTests/SelectiveTestingWorkspaceTests.swift b/Tests/SelectiveTestingTests/SelectiveTestingWorkspaceTests.swift index 4f15488..43c51b8 100644 --- a/Tests/SelectiveTestingTests/SelectiveTestingWorkspaceTests.swift +++ b/Tests/SelectiveTestingTests/SelectiveTestingWorkspaceTests.swift @@ -5,142 +5,125 @@ import PathKit @testable import SelectiveTestingCore import SelectiveTestShell +import Testing import Workspace -import XCTest -final class SelectiveTestingWorksapceTests: XCTestCase { - let testTool = IntegrationTestTool() - - func testProjectLoading_empty() async throws { - try await testTool.withTestTool { - // given +@Suite +struct SelectiveTestingWorkspaceTests { + @Test + func projectLoading_empty() async throws { + try await IntegrationTestTool.withTestTool { testTool in let tool = try testTool.createSUT() - // when let result = try await tool.run() - // then - XCTAssertEqual(result, Set()) + #expect(result == Set()) } } - func testProjectLoading_changeLibrary() async throws { - try await testTool.withTestTool { - // given + @Test + func projectLoading_changeLibrary() async throws { + try await IntegrationTestTool.withTestTool { testTool in 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])) + #expect(result == Set([testTool.mainProjectMainTarget(), + testTool.mainProjectTests(), + testTool.mainProjectUITests(), + testTool.exampleLibrary(), + testTool.exampleLibraryTests()])) } } - func testProjectLoading_changeAsset() async throws { - try await testTool.withTestTool { - // given + @Test + func projectLoading_changeAsset() async throws { + try await IntegrationTestTool.withTestTool { testTool in 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])) + #expect(result == Set([testTool.mainProjectMainTarget(), + testTool.mainProjectTests(), + testTool.mainProjectUITests()])) } } - func testProjectLoading_testPlanChange() async throws { - try await testTool.withTestTool { - // given + @Test + func projectLoading_testPlanChange() async throws { + try await IntegrationTestTool.withTestTool { testTool in let tool = try testTool.createSUT() - // when try testTool.changeFile(at: testTool.projectPath + "ExampleProject.xctestplan") - - // then + let result = try await tool.run() - XCTAssertEqual(result, Set()) + #expect(result == Set()) } } - func testProjectLoading_testWorkspaceFileChange() async throws { - try await testTool.withTestTool { - // given + @Test + func projectLoading_testWorkspaceFileChange() async throws { + try await IntegrationTestTool.withTestTool { testTool in 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, + #expect(result == Set([ + testTool.mainProjectMainTarget(), + testTool.mainProjectTests(), + testTool.mainProjectUITests(), + testTool.mainProjectLibrary(), + testTool.mainProjectLibraryTests(), + testTool.exampleLibraryTests(), + testTool.exampleLibrary(), + testTool.exampleLibraryInGroup() ])) } } - func testProjectLoading_testProjectFileChange() async throws { - try await testTool.withTestTool { - // given - let tool = try testTool.createSUT() - // when + @Test + func projectLoading_testProjectFileChange() async throws { + try await IntegrationTestTool.withTestTool { testTool in try testTool.changeFile(at: testTool.projectPath + "ExampleProject.xcodeproj/project.pbxproj") - - // then + let tool = try testTool.createSUT() let result = try await tool.run() - XCTAssertEqual(result, Set([ - testTool.mainProjectMainTarget, - testTool.mainProjectTests, - testTool.mainProjectUITests, - testTool.mainProjectLibrary, - testTool.mainProjectLibraryTests, + #expect(result == Set([ + testTool.mainProjectMainTarget(), + testTool.mainProjectTests(), + testTool.mainProjectUITests(), + testTool.mainProjectLibrary(), + testTool.mainProjectLibraryTests(), ])) } } - func testInferTestPlan() async throws { - try await testTool.withTestTool { - // given + @Test + func inferTestPlan() async throws { + try await IntegrationTestTool.withTestTool { testTool in 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 await tool.run() try testTool.validateTestPlan(testPlanPath: testTool.projectPath + "ExampleProject.xctestplan", - expected: Set([testTool.mainProjectTests, - testTool.mainProjectUITests, - testTool.exampleLibraryTests])) + expected: Set([testTool.mainProjectTests(), + testTool.mainProjectUITests(), + testTool.exampleLibraryTests()])) } } - - func testInferTestPlanInSubfolder() async throws { - try await testTool.withTestTool(subfolder: true) { - // given + + @Test + func inferTestPlanInSubfolder() async throws { + try await IntegrationTestTool.withTestTool(subfolder: true) { testTool in 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 await tool.run() try testTool.validateTestPlan(testPlanPath: testTool.projectPath + "Subfolder/ExampleProject.xctestplan", - expected: Set([testTool.mainProjectTests, - testTool.mainProjectUITests, - testTool.exampleLibraryTests])) + expected: Set([testTool.mainProjectTests(), + testTool.mainProjectUITests(), + testTool.exampleLibraryTests()])) } } } diff --git a/Tests/SelectiveTestingTests/TestPlanCodableTests.swift b/Tests/SelectiveTestingTests/TestPlanCodableTests.swift index 495ffc5..4d959c2 100644 --- a/Tests/SelectiveTestingTests/TestPlanCodableTests.swift +++ b/Tests/SelectiveTestingTests/TestPlanCodableTests.swift @@ -1,8 +1,9 @@ +import Foundation @testable import TestConfigurator -import XCTest +import Testing -class SkippedTestsTests: XCTestCase { - // Sample JSON for skippedTests as an array of strings +@Suite +struct SkippedTestsTests { let jsonWithArray = """ { "skippedTests": [ @@ -12,7 +13,6 @@ class SkippedTestsTests: XCTestCase { } """.data(using: .utf8)! - // Sample JSON for skippedTests as a dictionary let jsonWithDictionary = """ { "skippedTests": { @@ -25,33 +25,36 @@ class SkippedTestsTests: XCTestCase { } """.data(using: .utf8)! - func testDecodeSkippedTestsAsArray() throws { + @Test + func decodeSkippedTestsAsArray() throws { let decoder = JSONDecoder() let container = try decoder.decode(SkippedTestsContainer.self, from: jsonWithArray) if case let .array(skippedTests) = container.skippedTests { - XCTAssertEqual(skippedTests, [ + #expect(skippedTests == [ "DigitalRewardsServiceTests", "LoyaltyCreditCardRewardsPointViewModelTests" ]) } else { - XCTFail("Expected skippedTests to be an array") + Issue.record("Expected skippedTests to be an array") } } - func testDecodeSkippedTestsAsDictionary() throws { + @Test + func decodeSkippedTestsAsDictionary() throws { let decoder = JSONDecoder() let container = try decoder.decode(SkippedTestsContainer.self, from: jsonWithDictionary) if case let .dictionary(suites) = container.skippedTests { - XCTAssertEqual(suites.suites.count, 1) - XCTAssertEqual(suites.suites[0].name, "SparksMissionOfferVisibleTrackingEventTests") + #expect(suites.suites.count == 1) + #expect(suites.suites[0].name == "SparksMissionOfferVisibleTrackingEventTests") } else { - XCTFail("Expected skippedTests to be a dictionary") + Issue.record("Expected skippedTests to be a dictionary") } } - func testEncodeSkippedTestsAsArray() throws { + @Test + func encodeSkippedTestsAsArray() throws { let container = SkippedTestsContainer( skippedTests: .array([ "DigitalRewardsServiceTests", @@ -64,12 +67,13 @@ class SkippedTestsTests: XCTestCase { let encodedData = try encoder.encode(container) let encodedString = String(data: encodedData, encoding: .utf8) - XCTAssertNotNil(encodedString) - XCTAssertTrue(encodedString!.contains("\"skippedTests\" : [")) - XCTAssertTrue(encodedString!.contains("\"DigitalRewardsServiceTests\"")) + #expect(encodedString != nil) + #expect(encodedString?.contains("\"skippedTests\" : [") == true) + #expect(encodedString?.contains("\"DigitalRewardsServiceTests\"") == true) } - func testEncodeSkippedTestsAsDictionary() throws { + @Test + func encodeSkippedTestsAsDictionary() throws { let container = SkippedTestsContainer( skippedTests: .dictionary( Tests.Suites(suites: [ @@ -83,14 +87,13 @@ class SkippedTestsTests: XCTestCase { let encodedData = try encoder.encode(container) let encodedString = String(data: encodedData, encoding: .utf8) - XCTAssertNotNil(encodedString) - XCTAssertTrue(encodedString!.contains("\"skippedTests\" : {")) - XCTAssertTrue(encodedString!.contains("\"suites\" : [")) - XCTAssertTrue(encodedString!.contains("\"name\" : \"SparksMissionOfferVisibleTrackingEventTests\"")) + #expect(encodedString != nil) + #expect(encodedString?.contains("\"skippedTests\" : {") == true) + #expect(encodedString?.contains("\"suites\" : [") == true) + #expect(encodedString?.contains("\"name\" : \"SparksMissionOfferVisibleTrackingEventTests\"") == true) } } -// Container to isolate the "skippedTests" field for testing struct SkippedTestsContainer: Codable { let skippedTests: Tests }