Skip to content

Commit

Permalink
[6.0] Add --enable-code-coverage to swift build (#7565)
Browse files Browse the repository at this point in the history
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 <m_desiatov@apple.com>
  • Loading branch information
grynspan and MaxDesiatov committed May 15, 2024
1 parent f177504 commit 165f35c
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 6 deletions.
39 changes: 33 additions & 6 deletions Sources/Commands/SwiftBuildCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ struct BuildCommandOptions: ParsableArguments {
@Flag(help: "Build both source and test targets")
var buildTests: Bool = false

/// Whether to enable code coverage.
@Flag(name: .customLong("code-coverage"),
inversion: .prefixedEnableDisable,
help: "Enable code coverage")
var enableCodeCoverage: Bool = false

/// If the binary output path should be printed.
@Flag(name: .customLong("show-bin-path"), help: "Print the binary output path")
var shouldPrintBinPath: Bool = false
Expand Down Expand Up @@ -148,9 +154,20 @@ package struct SwiftBuildCommand: AsyncSwiftCommand {
guard let subset = options.buildSubset(observabilityScope: swiftCommandState.observabilityScope) else {
throw ExitCode.failure
}

var productsBuildParameters = try swiftCommandState.productsBuildParameters
var toolsBuildParameters = try swiftCommandState.toolsBuildParameters

// Clean out the code coverage directory that may contain stale
// profraw files from a previous run of the code coverage tool.
if self.options.enableCodeCoverage {
try swiftCommandState.fileSystem.removeFileTree(swiftCommandState.productsBuildParameters.codeCovPath)
productsBuildParameters.testingParameters.enableCodeCoverage = true
toolsBuildParameters.testingParameters.enableCodeCoverage = true
}

if case .allIncludingTests = subset {
var buildParameters = try swiftCommandState.productsBuildParameters
for library in try options.testLibraryOptions.enabledTestingLibraries(swiftCommandState: swiftCommandState) {
func updateTestingParameters(of buildParameters: inout BuildParameters, library: BuildParameters.Testing.Library) {
buildParameters.testingParameters = .init(
configuration: buildParameters.configuration,
targetTriple: buildParameters.triple,
Expand All @@ -161,18 +178,28 @@ package struct SwiftBuildCommand: AsyncSwiftCommand {
testEntryPointPath: globalOptions.build.testEntryPointPath,
library: library
)
try build(swiftCommandState, subset: subset, buildParameters: buildParameters)
}
for library in try options.testLibraryOptions.enabledTestingLibraries(swiftCommandState: swiftCommandState) {
updateTestingParameters(of: &productsBuildParameters, library: library)
updateTestingParameters(of: &toolsBuildParameters, library: library)
try build(swiftCommandState, subset: subset, productsBuildParameters: productsBuildParameters, toolsBuildParameters: toolsBuildParameters)
}
} else {
try build(swiftCommandState, subset: subset)
try build(swiftCommandState, subset: subset, productsBuildParameters: productsBuildParameters, toolsBuildParameters: toolsBuildParameters)
}
}

private func build(_ swiftCommandState: SwiftCommandState, subset: BuildSubset, buildParameters: BuildParameters? = nil) throws {
private func build(
_ swiftCommandState: SwiftCommandState,
subset: BuildSubset,
productsBuildParameters: BuildParameters,
toolsBuildParameters: BuildParameters
) throws {
let buildSystem = try swiftCommandState.createBuildSystem(
explicitProduct: options.product,
shouldLinkStaticSwiftStdlib: options.shouldLinkStaticSwiftStdlib,
productsBuildParameters: buildParameters,
productsBuildParameters: productsBuildParameters,
toolsBuildParameters: toolsBuildParameters,
// command result output goes on stdout
// ie "swift build" should output to stdout
outputStream: TSCBasic.stdoutStream
Expand Down
17 changes: 17 additions & 0 deletions Tests/CommandsTests/BuildCommandTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -664,4 +664,21 @@ final class BuildCommandTests: CommandsTestCase {
}
}
#endif

func testCodeCoverage() throws {
// Test that no codecov directory is created if not specified when building.
try fixture(name: "Miscellaneous/TestDiscovery/Simple") { path in
let buildResult = try self.build(["--build-tests"], packagePath: path, cleanAfterward: false)
XCTAssertThrowsError(try SwiftPM.Test.execute(["--skip-build", "--enable-code-coverage"], packagePath: path))
}

// Test that enabling code coverage during building produces the expected folder.
try fixture(name: "Miscellaneous/TestDiscovery/Simple") { path in
let buildResult = try self.build(["--build-tests", "--enable-code-coverage"], packagePath: path, cleanAfterward: false)
try SwiftPM.Test.execute(["--skip-build", "--enable-code-coverage"], packagePath: path)
let codeCovPath = buildResult.binPath.appending("codecov")
let codeCovFiles = try localFileSystem.getDirectoryContents(codeCovPath)
XCTAssertGreaterThan(codeCovFiles.count, 0)
}
}
}

0 comments on commit 165f35c

Please sign in to comment.