From f56f001b806ddccfc3ef60bd7e87a1891ff79ea7 Mon Sep 17 00:00:00 2001 From: Abbas Mousavi Date: Wed, 30 Oct 2019 23:58:21 +0000 Subject: [PATCH 1/5] Adds codeCoverageTargets to TestAction --- Sources/ProjectDescription/Scheme.swift | 5 +++++ .../Generator/SchemesGenerator.swift | 15 +++++++++++++++ Sources/TuistGenerator/Models/Scheme.swift | 4 ++++ .../TuistKit/Generator/GeneratorModelLoader.swift | 2 ++ 4 files changed, 26 insertions(+) diff --git a/Sources/ProjectDescription/Scheme.swift b/Sources/ProjectDescription/Scheme.swift index cde2c8bd3c2..c2f956bc0a5 100644 --- a/Sources/ProjectDescription/Scheme.swift +++ b/Sources/ProjectDescription/Scheme.swift @@ -72,6 +72,7 @@ public struct TestAction: Equatable, Codable { public let arguments: Arguments? public let configurationName: String public let coverage: Bool + public let codeCoverageTargets: [String] public let preActions: [ExecutionAction] public let postActions: [ExecutionAction] @@ -79,6 +80,7 @@ public struct TestAction: Equatable, Codable { arguments: Arguments? = nil, configurationName: String, coverage: Bool = false, + codeCoverageTargets: [String] = [], preActions: [ExecutionAction] = [], postActions: [ExecutionAction] = []) { self.targets = targets @@ -87,18 +89,21 @@ public struct TestAction: Equatable, Codable { self.coverage = coverage self.preActions = preActions self.postActions = postActions + self.codeCoverageTargets = codeCoverageTargets } public init(targets: [String], arguments: Arguments? = nil, config: PresetBuildConfiguration = .debug, coverage: Bool = false, + codeCoverageTargets: [String] = [], preActions: [ExecutionAction] = [], postActions: [ExecutionAction] = []) { self.init(targets: targets, arguments: arguments, configurationName: config.name, coverage: coverage, + codeCoverageTargets: codeCoverageTargets, preActions: preActions, postActions: postActions) } diff --git a/Sources/TuistGenerator/Generator/SchemesGenerator.swift b/Sources/TuistGenerator/Generator/SchemesGenerator.swift index 1e99f0631e2..6f035276cb8 100644 --- a/Sources/TuistGenerator/Generator/SchemesGenerator.swift +++ b/Sources/TuistGenerator/Generator/SchemesGenerator.swift @@ -161,6 +161,18 @@ final class SchemesGenerator: SchemesGenerating { var testables: [XCScheme.TestableReference] = [] var preActions: [XCScheme.ExecutionAction] = [] var postActions: [XCScheme.ExecutionAction] = [] + var codeCoverageTargets: [XCScheme.BuildableReference] = [] + + testAction.codeCoverageTargets.forEach { name in + + guard let target = project.targets.first(where: { $0.name == name }) else { return } + guard let pbxTarget = generatedProject.targets[name] else { return } + + let reference = self.targetBuildableReference(target: target, + pbxTarget: pbxTarget, + projectName: generatedProject.name) + codeCoverageTargets.append(reference) + } testAction.targets.forEach { name in guard let target = project.targets.first(where: { $0.name == name }), target.product.testsBundle else { return } @@ -189,6 +201,7 @@ final class SchemesGenerator: SchemesGenerating { args = XCScheme.CommandLineArguments(arguments: commandlineArgruments(arguments.launch)) environments = environmentVariables(arguments.environment) } + let onlyGenerateCoverageForSpecifiedTargets = codeCoverageTargets.count > 0 ? true : nil let shouldUseLaunchSchemeArgsEnv: Bool = args == nil && environments == nil @@ -199,6 +212,8 @@ final class SchemesGenerator: SchemesGenerating { postActions: postActions, shouldUseLaunchSchemeArgsEnv: shouldUseLaunchSchemeArgsEnv, codeCoverageEnabled: testAction.coverage, + codeCoverageTargets: codeCoverageTargets, + onlyGenerateCoverageForSpecifiedTargets: onlyGenerateCoverageForSpecifiedTargets, commandlineArguments: args, environmentVariables: environments) } diff --git a/Sources/TuistGenerator/Models/Scheme.swift b/Sources/TuistGenerator/Models/Scheme.swift index c3d88cb5138..0f74cfc4dfb 100644 --- a/Sources/TuistGenerator/Models/Scheme.swift +++ b/Sources/TuistGenerator/Models/Scheme.swift @@ -114,6 +114,7 @@ public class TestAction: Equatable { public let arguments: Arguments? public let configurationName: String public let coverage: Bool + public let codeCoverageTargets: [String] public let preActions: [ExecutionAction] public let postActions: [ExecutionAction] @@ -123,6 +124,7 @@ public class TestAction: Equatable { arguments: Arguments? = nil, configurationName: String, coverage: Bool = false, + codeCoverageTargets: [String] = [], preActions: [ExecutionAction] = [], postActions: [ExecutionAction] = []) { self.targets = targets @@ -131,6 +133,7 @@ public class TestAction: Equatable { self.coverage = coverage self.preActions = preActions self.postActions = postActions + self.codeCoverageTargets = codeCoverageTargets } // MARK: - Equatable @@ -140,6 +143,7 @@ public class TestAction: Equatable { lhs.arguments == rhs.arguments && lhs.configurationName == rhs.configurationName && lhs.coverage == rhs.coverage && + lhs.codeCoverageTargets == rhs.codeCoverageTargets && lhs.preActions == rhs.preActions && lhs.postActions == rhs.postActions } diff --git a/Sources/TuistKit/Generator/GeneratorModelLoader.swift b/Sources/TuistKit/Generator/GeneratorModelLoader.swift index 95ce9c1693a..f4f5d41f19d 100644 --- a/Sources/TuistKit/Generator/GeneratorModelLoader.swift +++ b/Sources/TuistKit/Generator/GeneratorModelLoader.swift @@ -634,6 +634,7 @@ extension TuistGenerator.TestAction { let arguments = manifest.arguments.map { TuistGenerator.Arguments.from(manifest: $0) } let configurationName = manifest.configurationName let coverage = manifest.coverage + let codeCoverageTargets = manifest.codeCoverageTargets let preActions = manifest.preActions.map { TuistGenerator.ExecutionAction.from(manifest: $0) } let postActions = manifest.postActions.map { TuistGenerator.ExecutionAction.from(manifest: $0) } @@ -641,6 +642,7 @@ extension TuistGenerator.TestAction { arguments: arguments, configurationName: configurationName, coverage: coverage, + codeCoverageTargets: codeCoverageTargets, preActions: preActions, postActions: postActions) } From 24a1492629edd969d208d25c218afbf35ea2f8e1 Mon Sep 17 00:00:00 2001 From: Abbas Mousavi Date: Thu, 31 Oct 2019 00:40:55 +0000 Subject: [PATCH 2/5] Adds documents --- CHANGELOG.md | 1 + docs/usage/2-projectswift.mdx | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 906b3c90f21..f0cbf01dcfc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Please, check out guidelines: https://keepachangelog.com/en/1.0.0/ - **Breaking** Allow specifying a deployment target within project manifests https://github.com/tuist/tuist/pull/541 by @mollyIV - Add support for sticker pack extension & app extension products https://github.com/tuist/tuist/pull/489 by @Rag0n - Utility to locate the root directory of a project https://github.com/tuist/tuist/pull/622/checks?check_run_id=284958709 by @pepibumur. +- Adds `codeCoverageTargets` to `TestAction` to make XCode gather coverage info only for that targets https://github.com/tuist/tuist/pull/619/checks?check_run_id=282561179 by @abbasmousavi ### Changed diff --git a/docs/usage/2-projectswift.mdx b/docs/usage/2-projectswift.mdx index 18d945312ab..60968abbb8d 100644 --- a/docs/usage/2-projectswift.mdx +++ b/docs/usage/2-projectswift.mdx @@ -797,6 +797,14 @@ Alternatively, when leveraging custom configurations, the configuration name can optional: true, default: 'false', }, + { + name: 'codeCoverageTargets', + description: + 'A list of targets you want to gather the test coverage data for them, which are defined in the project.', + type: '[String]', + optional: true, + default: '[]', + }, { name: 'Pre-actions', description: From 87863fb8d78bdde0f7cab34815720e0438223a73 Mon Sep 17 00:00:00 2001 From: Abbas Mousavi Date: Fri, 1 Nov 2019 23:53:55 +0000 Subject: [PATCH 3/5] extracts the testCoverageTargetReferences method --- .../Generator/SchemesGenerator.swift | 40 +++++++++++++------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/Sources/TuistGenerator/Generator/SchemesGenerator.swift b/Sources/TuistGenerator/Generator/SchemesGenerator.swift index 6f035276cb8..ba31b4a5f07 100644 --- a/Sources/TuistGenerator/Generator/SchemesGenerator.swift +++ b/Sources/TuistGenerator/Generator/SchemesGenerator.swift @@ -145,6 +145,31 @@ final class SchemesGenerator: SchemesGenerating { macroExpansion: nil, testables: testables) } + + /// Generates the array of BuildableReference for targets that the + /// coverage report should be generated for them. + /// + /// - Parameters: + /// - testAction: test actions. + /// - project: Project manifest. + /// - generatedProject: Generated Xcode project. + /// - Returns: Array of buildable references. + private func testCoverageTargetReferences(testAction: TestAction, project: Project, generatedProject: GeneratedProject) -> [XCScheme.BuildableReference] { + + var codeCoverageTargets: [XCScheme.BuildableReference] = [] + testAction.codeCoverageTargets.forEach { name in + + guard let target = project.targets.first(where: { $0.name == name }) else { return } + guard let pbxTarget = generatedProject.targets[name] else { return } + + let reference = self.targetBuildableReference(target: target, + pbxTarget: pbxTarget, + projectName: generatedProject.name) + codeCoverageTargets.append(reference) + } + + return codeCoverageTargets + } /// Generates the scheme test action. /// @@ -161,18 +186,6 @@ final class SchemesGenerator: SchemesGenerating { var testables: [XCScheme.TestableReference] = [] var preActions: [XCScheme.ExecutionAction] = [] var postActions: [XCScheme.ExecutionAction] = [] - var codeCoverageTargets: [XCScheme.BuildableReference] = [] - - testAction.codeCoverageTargets.forEach { name in - - guard let target = project.targets.first(where: { $0.name == name }) else { return } - guard let pbxTarget = generatedProject.targets[name] else { return } - - let reference = self.targetBuildableReference(target: target, - pbxTarget: pbxTarget, - projectName: generatedProject.name) - codeCoverageTargets.append(reference) - } testAction.targets.forEach { name in guard let target = project.targets.first(where: { $0.name == name }), target.product.testsBundle else { return } @@ -201,6 +214,9 @@ final class SchemesGenerator: SchemesGenerating { args = XCScheme.CommandLineArguments(arguments: commandlineArgruments(arguments.launch)) environments = environmentVariables(arguments.environment) } + + let codeCoverageTargets = self.testCoverageTargetReferences(testAction: testAction, project: project, generatedProject: generatedProject) + let onlyGenerateCoverageForSpecifiedTargets = codeCoverageTargets.count > 0 ? true : nil let shouldUseLaunchSchemeArgsEnv: Bool = args == nil && environments == nil From cd3c61431625092f8d8fa7bddc72990995bbf609 Mon Sep 17 00:00:00 2001 From: Abbas Mousavi Date: Sat, 2 Nov 2019 01:45:28 +0000 Subject: [PATCH 4/5] Adds a test --- .../Generator/SchemesGeneratorTests.swift | 24 +++++++++++++++++++ .../Models/TestData/Scheme+TestData.swift | 2 ++ 2 files changed, 26 insertions(+) diff --git a/Tests/TuistGeneratorTests/Generator/SchemesGeneratorTests.swift b/Tests/TuistGeneratorTests/Generator/SchemesGeneratorTests.swift index 09ea42f6478..94ed120a1ee 100644 --- a/Tests/TuistGeneratorTests/Generator/SchemesGeneratorTests.swift +++ b/Tests/TuistGeneratorTests/Generator/SchemesGeneratorTests.swift @@ -155,6 +155,30 @@ final class SchemeGeneratorTests: XCTestCase { XCTAssertEqual(postBuildableReference?.blueprintName, "AppTests") XCTAssertEqual(postBuildableReference?.buildableIdentifier, "primary") } + + func test_schemeTestAction_with_codeCoverageTargets() { + + let target = Target.test(name: "App", product: .app) + let testTarget = Target.test(name: "AppTests", product: .unitTests) + + let testAction = TestAction.test(targets: ["AppTests"], coverage: true, codeCoverageTargets: ["App"]) + let buildAction = BuildAction.test(targets: ["App"]) + + let scheme = Scheme.test(name: "AppTests", shared: true, buildAction: buildAction, testAction: testAction) + let project = Project.test(targets: [target,testTarget]) + + let pbxTarget = PBXNativeTarget(name: "App", productType: .application) + let pbxTestTarget = PBXNativeTarget(name: "AppTests", productType: .unitTestBundle) + let generatedProject = GeneratedProject.test(targets: ["AppTests": pbxTestTarget, "App": pbxTarget]) + + let got = subject.schemeTestAction(scheme: scheme, project: project, generatedProject: generatedProject) + + let codeCoverageTargetsBuildableReference = got?.codeCoverageTargets + + XCTAssertEqual(got?.onlyGenerateCoverageForSpecifiedTargets, true) + XCTAssertEqual(codeCoverageTargetsBuildableReference?.count, 1) + XCTAssertEqual(codeCoverageTargetsBuildableReference?.first?.buildableName, "App.app") + } func test_schemeBuildAction() { let target = Target.test(name: "App", product: .app) diff --git a/Tests/TuistGeneratorTests/Models/TestData/Scheme+TestData.swift b/Tests/TuistGeneratorTests/Models/TestData/Scheme+TestData.swift index 24f6bc6ab91..4766d7a9680 100644 --- a/Tests/TuistGeneratorTests/Models/TestData/Scheme+TestData.swift +++ b/Tests/TuistGeneratorTests/Models/TestData/Scheme+TestData.swift @@ -25,12 +25,14 @@ extension TestAction { arguments: Arguments? = Arguments.test(), configurationName: String = BuildConfiguration.debug.name, coverage: Bool = false, + codeCoverageTargets: [String] = [], preActions: [ExecutionAction] = [], postActions: [ExecutionAction] = []) -> TestAction { return TestAction(targets: targets, arguments: arguments, configurationName: configurationName, coverage: coverage, + codeCoverageTargets: codeCoverageTargets, preActions: preActions, postActions: postActions) } From 1f5df61c5b24b8ae247ced29dbd25f517c7d7a26 Mon Sep 17 00:00:00 2001 From: Abbas Mousavi Date: Sat, 2 Nov 2019 02:25:21 +0000 Subject: [PATCH 5/5] adds lint --- .../TuistGenerator/Linter/SchemeLinter.swift | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/Sources/TuistGenerator/Linter/SchemeLinter.swift b/Sources/TuistGenerator/Linter/SchemeLinter.swift index 006f2792810..2a4f70c1e55 100644 --- a/Sources/TuistGenerator/Linter/SchemeLinter.swift +++ b/Sources/TuistGenerator/Linter/SchemeLinter.swift @@ -9,6 +9,7 @@ class SchemeLinter: SchemeLinting { func lint(project: Project) -> [LintingIssue] { var issues = [LintingIssue]() issues.append(contentsOf: lintReferencedBuildConfigurations(schemes: project.schemes, settings: project.settings)) + issues.append(contentsOf: lintCodeCoverageTargets(schemes: project.schemes, targets: project.targets)) return issues } } @@ -32,6 +33,15 @@ private extension SchemeLinter { } } + if let testAction = scheme.testAction { + if !buildConfigurationNames.contains(testAction.configurationName) { + issues.append( + missingBuildConfigurationIssue(buildConfigurationName: testAction.configurationName, + actionDescription: "the scheme's test action") + ) + } + } + if let testAction = scheme.testAction { if !buildConfigurationNames.contains(testAction.configurationName) { issues.append( @@ -48,4 +58,26 @@ private extension SchemeLinter { let reason = "The build configuration '\(buildConfigurationName)' specified in \(actionDescription) isn't defined in the project." return LintingIssue(reason: reason, severity: .error) } + + func lintCodeCoverageTargets(schemes: [Scheme], targets: [Target]) -> [LintingIssue] { + + let targetNames = targets.map{$0.name} + var issues: [LintingIssue] = [] + + for scheme in schemes { + for target in scheme.testAction?.codeCoverageTargets ?? [] { + if !targetNames.contains(target) { + issues.append(missingCodeCoverageTargetIssue(missingTargetName: target, schemaName: scheme.name)) + } + } + } + + return issues + } + + func missingCodeCoverageTargetIssue(missingTargetName: String, schemaName: String) -> LintingIssue { + let reason = "The target '\(missingTargetName)' specified in \(schemaName) code coverage targets list isn't defined in the project." + return LintingIssue(reason: reason, severity: .error) + } + }