Add GenerationOption to enable code coverage (#2020)
Resolves [no ticket] ### Short description 📝 Code coverage is not enabled when schemes are automatically generated. To enable it, one needs to define a scheme manually to enable `Coverage` and set `codeCoverageTargets`. This change introduces a new `generationOption` case: `enableCodeCoverage` that will enable code coverage for automatically generated schemes. ### Solution 📦 I started by attempting to update Project.swift with a manually-defined scheme to enable code coverage, but there are a lot of projects in our workspace and it seemed like a broader solution would be more useful than one that would require touching each project (and potentially ensuring the stand-in schemes are kept up-to-date). I started with my colleague @kalkwarf's PR #1782 and used that as a guide to make similar changes in what seemed like appropriate places including the generator code, tests, and documentation. This change doesn't introduce any breaking changes and hopefully avoids any unnecessary complexity, while making it possible to enable code coverage with one line: ``` let config = Config( generationOptions: [ // your options here .enableCodeCoverage, <-- this one :-) ] ) ``` ### Implementation 👩💻👨💻 - [x] Extend `GenerationOptions` enum with `enableCodeCoverage` case. - [x] Update `AutogeneratedSchemesProjectMapper` to initialize with `TuistCore.Config` . - [x] Check config to to enable code coverage and generate code coverage targets. - [x] Update `AutogeneratedSchemesProjectMapperTests` to verify new behavior. - [x] Checked that existing tests would fail if the they were run with the new option. - [x] Added new case to `docs/usage/config.mdx`. ### **One more thing™** ~This is a draft since I am not sure the test configuration this produces is correct, but I wanted to get eyes on the changes to the project in general.~ Looks like the coverage config is good to go! Thanks for reading!!
This commit is contained in:
commit
eead4911f2
|
@ -11,6 +11,7 @@ Please, check out guidelines: https://keepachangelog.com/en/1.0.0/
|
|||
- Synthesize accessors for stringsdict [#1993](https://github.com/tuist/tuist/pull/1993) by [@fortmarek](https://githubl.com/fortmarek)
|
||||
- Add support for `StencilSwiftKit`'s additional filters. [#1994](https://github.com/tuist/tuist/pull/1994) by [@svastven](https://github.com/svastven).
|
||||
- Add `migration list-targets` command to show all targets sorted by number of dependencies [#1732](https://github.com/tuist/tuist/pull/1732) of a given project by [@andreacipriani](https://github.com/andreacipriani).
|
||||
- Add `enableCodeCoverage` generation option to enable code coverage in automatically generated schemes [#ZZZZ](https://github.com/tuist/tuist/pull/ZZZZ) by [@frijole](https://github.com/frijole).)
|
||||
|
||||
### Fixed
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ public struct Config: Codable, Equatable {
|
|||
/// - disableAutogeneratedSchemes: When passed, Tuist generates the project only with custom specified schemes, autogenerated default schemes are skipped
|
||||
/// - disableSynthesizedResourceAccessors: When passed, Tuist does not synthesize resource accessors
|
||||
/// - disableShowEnvironmentVarsInScriptPhases: When passed, Tuist disables echoing the ENV in shell script build phases
|
||||
/// - enableCodeCoverage: When passed, Tuist will enable code coverage for autogenerated default schemes
|
||||
public enum GenerationOptions: Encodable, Decodable, Equatable {
|
||||
case xcodeProjectName(TemplateString)
|
||||
case organizationName(String)
|
||||
|
@ -19,6 +20,7 @@ public struct Config: Codable, Equatable {
|
|||
case disableAutogeneratedSchemes
|
||||
case disableSynthesizedResourceAccessors
|
||||
case disableShowEnvironmentVarsInScriptPhases
|
||||
case enableCodeCoverage
|
||||
}
|
||||
|
||||
/// Generation options.
|
||||
|
@ -55,6 +57,7 @@ extension Config.GenerationOptions {
|
|||
case disableAutogeneratedSchemes
|
||||
case disableSynthesizedResourceAccessors
|
||||
case disableShowEnvironmentVarsInScriptPhases
|
||||
case enableCodeCoverage
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
|
@ -90,6 +93,10 @@ extension Config.GenerationOptions {
|
|||
self = .disableShowEnvironmentVarsInScriptPhases
|
||||
return
|
||||
}
|
||||
if container.allKeys.contains(.enableCodeCoverage), try container.decode(Bool.self, forKey: .enableCodeCoverage) {
|
||||
self = .enableCodeCoverage
|
||||
return
|
||||
}
|
||||
|
||||
throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "Unknown enum case"))
|
||||
}
|
||||
|
@ -113,6 +120,8 @@ extension Config.GenerationOptions {
|
|||
try container.encode(true, forKey: .disableSynthesizedResourceAccessors)
|
||||
case .disableShowEnvironmentVarsInScriptPhases:
|
||||
try container.encode(true, forKey: .disableShowEnvironmentVarsInScriptPhases)
|
||||
case .enableCodeCoverage:
|
||||
try container.encode(true, forKey: .enableCodeCoverage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -136,6 +145,8 @@ public func == (lhs: TuistConfig.GenerationOptions, rhs: TuistConfig.GenerationO
|
|||
return true
|
||||
case (.disableShowEnvironmentVarsInScriptPhases, .disableShowEnvironmentVarsInScriptPhases):
|
||||
return true
|
||||
case (.enableCodeCoverage, .enableCodeCoverage):
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ public struct Config: Equatable, Hashable {
|
|||
case disableAutogeneratedSchemes
|
||||
case disableSynthesizedResourceAccessors
|
||||
case disableShowEnvironmentVarsInScriptPhases
|
||||
case enableCodeCoverage
|
||||
}
|
||||
|
||||
/// Generation options.
|
||||
|
|
|
@ -4,9 +4,13 @@ import TuistCore
|
|||
/// A project mapper that auto-generates schemes for each of the targets of the `Project`
|
||||
/// if the user hasn't already defined schemes for those.
|
||||
public final class AutogeneratedSchemesProjectMapper: ProjectMapping {
|
||||
private let enableCodeCoverage: Bool
|
||||
|
||||
// MARK: - Init
|
||||
|
||||
public init() {}
|
||||
public init(enableCodeCoverage: Bool) {
|
||||
self.enableCodeCoverage = enableCodeCoverage
|
||||
}
|
||||
|
||||
// MARK: - ProjectMapping
|
||||
|
||||
|
@ -17,6 +21,7 @@ public final class AutogeneratedSchemesProjectMapper: ProjectMapping {
|
|||
let autogeneratedSchemes = project.targets.compactMap { (target: Target) -> Scheme? in
|
||||
let scheme = self.createDefaultScheme(target: target,
|
||||
project: project,
|
||||
codeCoverage: enableCodeCoverage,
|
||||
buildConfiguration: project.defaultDebugBuildConfigurationName)
|
||||
// The user has already defined a scheme with that name.
|
||||
if schemeNames.contains(scheme.name) { return nil }
|
||||
|
@ -28,21 +33,23 @@ public final class AutogeneratedSchemesProjectMapper: ProjectMapping {
|
|||
|
||||
// MARK: - Private
|
||||
|
||||
private func createDefaultScheme(target: Target, project: Project, buildConfiguration: String) -> Scheme {
|
||||
private func createDefaultScheme(target: Target, project: Project, codeCoverage: Bool, buildConfiguration: String) -> Scheme {
|
||||
let targetReference = TargetReference(projectPath: project.path, name: target.name)
|
||||
|
||||
let buildTargets = buildableTargets(targetReference: targetReference, target: target, project: project)
|
||||
let testTargets = testableTargets(targetReference: targetReference, target: target, project: project)
|
||||
let executable = runnableExecutable(targetReference: targetReference, target: target, project: project)
|
||||
|
||||
let codeCoverageTargets = codeCoverage ? [targetReference] : []
|
||||
|
||||
return Scheme(name: target.name,
|
||||
shared: true,
|
||||
buildAction: BuildAction(targets: buildTargets),
|
||||
testAction: TestAction(targets: testTargets,
|
||||
arguments: nil,
|
||||
configurationName: buildConfiguration,
|
||||
coverage: false,
|
||||
codeCoverageTargets: [],
|
||||
coverage: enableCodeCoverage,
|
||||
codeCoverageTargets: codeCoverageTargets,
|
||||
preActions: [],
|
||||
postActions: [],
|
||||
diagnosticsOptions: Set()),
|
||||
|
|
|
@ -27,7 +27,7 @@ final class ProjectMapperProvider: ProjectMapperProviding {
|
|||
|
||||
// Auto-generation of schemes
|
||||
if !config.generationOptions.contains(.disableAutogeneratedSchemes) {
|
||||
mappers.append(AutogeneratedSchemesProjectMapper())
|
||||
mappers.append(AutogeneratedSchemesProjectMapper(enableCodeCoverage: config.generationOptions.contains(.enableCodeCoverage)))
|
||||
}
|
||||
|
||||
// Delete current derived
|
||||
|
|
|
@ -71,7 +71,7 @@ final class ProjectEditor: ProjectEditing {
|
|||
templatesDirectoryLocator: TemplatesDirectoryLocating = TemplatesDirectoryLocator(),
|
||||
projectMapper: ProjectMapping = SequentialProjectMapper(
|
||||
mappers: [
|
||||
AutogeneratedSchemesProjectMapper(),
|
||||
AutogeneratedSchemesProjectMapper(enableCodeCoverage: false),
|
||||
]
|
||||
),
|
||||
sideEffectDescriptorExecutor: SideEffectDescriptorExecuting = SideEffectDescriptorExecutor()
|
||||
|
|
|
@ -42,6 +42,8 @@ extension TuistCore.Config.GenerationOption {
|
|||
return .disableSynthesizedResourceAccessors
|
||||
case .disableShowEnvironmentVarsInScriptPhases:
|
||||
return .disableShowEnvironmentVarsInScriptPhases
|
||||
case .enableCodeCoverage:
|
||||
return .enableCodeCoverage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ final class ConfigTests: XCTestCase {
|
|||
.disableAutogeneratedSchemes,
|
||||
.disableSynthesizedResourceAccessors,
|
||||
.disableShowEnvironmentVarsInScriptPhases,
|
||||
.enableCodeCoverage,
|
||||
])
|
||||
|
||||
XCTAssertCodable(config)
|
||||
|
|
|
@ -12,7 +12,7 @@ final class AutogeneratedSchemesProjectMapperTests: TuistUnitTestCase {
|
|||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
subject = AutogeneratedSchemesProjectMapper()
|
||||
subject = AutogeneratedSchemesProjectMapper(enableCodeCoverage: false)
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
|
@ -142,13 +142,50 @@ final class AutogeneratedSchemesProjectMapperTests: TuistUnitTestCase {
|
|||
])
|
||||
}
|
||||
|
||||
func test_map_enables_test_coverage_on_generated_schemes() throws {
|
||||
// Given
|
||||
subject = AutogeneratedSchemesProjectMapper(enableCodeCoverage: true)
|
||||
|
||||
let targetA = Target.test(
|
||||
name: "A",
|
||||
dependencies: [
|
||||
.target(name: "B"),
|
||||
]
|
||||
)
|
||||
let targetATests = Target.test(
|
||||
name: "ATests",
|
||||
product: .unitTests,
|
||||
dependencies: [.target(name: "A")]
|
||||
)
|
||||
let projectPath = try temporaryPath()
|
||||
let project = Project.test(
|
||||
path: projectPath,
|
||||
targets: [
|
||||
targetA,
|
||||
targetATests,
|
||||
]
|
||||
)
|
||||
|
||||
// When
|
||||
let (got, sideEffects) = try subject.map(project: project)
|
||||
|
||||
// Then
|
||||
XCTAssertEmpty(sideEffects)
|
||||
XCTAssertEqual(got.schemes.count, 2)
|
||||
|
||||
// Then: A Tests
|
||||
let gotAScheme = got.schemes.first!
|
||||
XCTAssertTrue(gotAScheme.testAction?.coverage != nil)
|
||||
XCTAssertEqual(gotAScheme.testAction?.codeCoverageTargets.count, 1)
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
private func testScheme(
|
||||
target: Target,
|
||||
target: TuistCore.Target,
|
||||
projectPath: AbsolutePath,
|
||||
testTargetName: String
|
||||
) -> Scheme {
|
||||
) -> TuistCore.Scheme {
|
||||
Scheme(
|
||||
name: target.name,
|
||||
shared: true,
|
||||
|
|
|
@ -95,4 +95,22 @@ final class ProjectMapperProviderTests: TuistUnitTestCase {
|
|||
let sequentialProjectMapper = try XCTUnwrap(got as? SequentialProjectMapper)
|
||||
XCTAssertEqual(sequentialProjectMapper.mappers.filter { $0 is TargetProjectMapper }.count, 1)
|
||||
}
|
||||
|
||||
func test_mappers_does_enable_code_coverage() throws {
|
||||
// Given
|
||||
subject = ProjectMapperProvider(contentHasher: CacheContentHasher())
|
||||
|
||||
// When
|
||||
let got = subject.mapper(
|
||||
config: Config.test(
|
||||
generationOptions: [
|
||||
.enableCodeCoverage,
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
// Then
|
||||
let sequentialProjectMapper = try XCTUnwrap(got as? SequentialProjectMapper)
|
||||
XCTAssertEqual(sequentialProjectMapper.mappers.filter { $0 is AutogeneratedSchemesProjectMapper }.count, 1)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -125,6 +125,11 @@ Generation options allow customizing the generation of Xcode projects.
|
|||
description:
|
||||
'Do not automatically synthesize resource accessors (assets, localized strings, etc.)',
|
||||
},
|
||||
{
|
||||
case: '.enableCodeCoverage',
|
||||
description:
|
||||
'Enable code coverage for auto generated schemes.',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
|
|
Loading…
Reference in New Issue