Skip to content

Commit 165f35c

Browse files
[6.0] Add --enable-code-coverage to swift build (#7565)
Explanation: Add `--enable-code-coverage` to `swift build`. This flag allows enabling code coverage when splitting a test run between `swift build` and `swift test --skip-build`. Scope: New option for the `swift build` command. Original PR: #7508 (partial), #7518 Risk: Low. Behaviour is identical for `swift build` without the new flag, and the new flag must be explicitly specified. Testing: Ran `swift build` unit tests at desk and in CI. Reviewer: @bnbarham, @MaxDesiatov, @stmontgomery --------- Co-authored-by: Max Desiatov <[email protected]>
1 parent f177504 commit 165f35c

File tree

2 files changed

+50
-6
lines changed

2 files changed

+50
-6
lines changed

Sources/Commands/SwiftBuildCommand.swift

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,12 @@ struct BuildCommandOptions: ParsableArguments {
7373
@Flag(help: "Build both source and test targets")
7474
var buildTests: Bool = false
7575

76+
/// Whether to enable code coverage.
77+
@Flag(name: .customLong("code-coverage"),
78+
inversion: .prefixedEnableDisable,
79+
help: "Enable code coverage")
80+
var enableCodeCoverage: Bool = false
81+
7682
/// If the binary output path should be printed.
7783
@Flag(name: .customLong("show-bin-path"), help: "Print the binary output path")
7884
var shouldPrintBinPath: Bool = false
@@ -148,9 +154,20 @@ package struct SwiftBuildCommand: AsyncSwiftCommand {
148154
guard let subset = options.buildSubset(observabilityScope: swiftCommandState.observabilityScope) else {
149155
throw ExitCode.failure
150156
}
157+
158+
var productsBuildParameters = try swiftCommandState.productsBuildParameters
159+
var toolsBuildParameters = try swiftCommandState.toolsBuildParameters
160+
161+
// Clean out the code coverage directory that may contain stale
162+
// profraw files from a previous run of the code coverage tool.
163+
if self.options.enableCodeCoverage {
164+
try swiftCommandState.fileSystem.removeFileTree(swiftCommandState.productsBuildParameters.codeCovPath)
165+
productsBuildParameters.testingParameters.enableCodeCoverage = true
166+
toolsBuildParameters.testingParameters.enableCodeCoverage = true
167+
}
168+
151169
if case .allIncludingTests = subset {
152-
var buildParameters = try swiftCommandState.productsBuildParameters
153-
for library in try options.testLibraryOptions.enabledTestingLibraries(swiftCommandState: swiftCommandState) {
170+
func updateTestingParameters(of buildParameters: inout BuildParameters, library: BuildParameters.Testing.Library) {
154171
buildParameters.testingParameters = .init(
155172
configuration: buildParameters.configuration,
156173
targetTriple: buildParameters.triple,
@@ -161,18 +178,28 @@ package struct SwiftBuildCommand: AsyncSwiftCommand {
161178
testEntryPointPath: globalOptions.build.testEntryPointPath,
162179
library: library
163180
)
164-
try build(swiftCommandState, subset: subset, buildParameters: buildParameters)
181+
}
182+
for library in try options.testLibraryOptions.enabledTestingLibraries(swiftCommandState: swiftCommandState) {
183+
updateTestingParameters(of: &productsBuildParameters, library: library)
184+
updateTestingParameters(of: &toolsBuildParameters, library: library)
185+
try build(swiftCommandState, subset: subset, productsBuildParameters: productsBuildParameters, toolsBuildParameters: toolsBuildParameters)
165186
}
166187
} else {
167-
try build(swiftCommandState, subset: subset)
188+
try build(swiftCommandState, subset: subset, productsBuildParameters: productsBuildParameters, toolsBuildParameters: toolsBuildParameters)
168189
}
169190
}
170191

171-
private func build(_ swiftCommandState: SwiftCommandState, subset: BuildSubset, buildParameters: BuildParameters? = nil) throws {
192+
private func build(
193+
_ swiftCommandState: SwiftCommandState,
194+
subset: BuildSubset,
195+
productsBuildParameters: BuildParameters,
196+
toolsBuildParameters: BuildParameters
197+
) throws {
172198
let buildSystem = try swiftCommandState.createBuildSystem(
173199
explicitProduct: options.product,
174200
shouldLinkStaticSwiftStdlib: options.shouldLinkStaticSwiftStdlib,
175-
productsBuildParameters: buildParameters,
201+
productsBuildParameters: productsBuildParameters,
202+
toolsBuildParameters: toolsBuildParameters,
176203
// command result output goes on stdout
177204
// ie "swift build" should output to stdout
178205
outputStream: TSCBasic.stdoutStream

Tests/CommandsTests/BuildCommandTests.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -664,4 +664,21 @@ final class BuildCommandTests: CommandsTestCase {
664664
}
665665
}
666666
#endif
667+
668+
func testCodeCoverage() throws {
669+
// Test that no codecov directory is created if not specified when building.
670+
try fixture(name: "Miscellaneous/TestDiscovery/Simple") { path in
671+
let buildResult = try self.build(["--build-tests"], packagePath: path, cleanAfterward: false)
672+
XCTAssertThrowsError(try SwiftPM.Test.execute(["--skip-build", "--enable-code-coverage"], packagePath: path))
673+
}
674+
675+
// Test that enabling code coverage during building produces the expected folder.
676+
try fixture(name: "Miscellaneous/TestDiscovery/Simple") { path in
677+
let buildResult = try self.build(["--build-tests", "--enable-code-coverage"], packagePath: path, cleanAfterward: false)
678+
try SwiftPM.Test.execute(["--skip-build", "--enable-code-coverage"], packagePath: path)
679+
let codeCovPath = buildResult.binPath.appending("codecov")
680+
let codeCovFiles = try localFileSystem.getDirectoryContents(codeCovPath)
681+
XCTAssertGreaterThan(codeCovFiles.count, 0)
682+
}
683+
}
667684
}

0 commit comments

Comments
 (0)