Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Add JSON option and rework runtime so that you always get the desired…
… output (table, minimum, json) regardless of success or failure
  • Loading branch information
mattpolzin committed Jan 9, 2021
1 parent d01fdc4 commit c0914f1
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 39 deletions.
Binary file not shown.
11 changes: 5 additions & 6 deletions README.md
Expand Up @@ -11,21 +11,20 @@ The library has a pretty small and straight forward interface. I have not had ti
### Tool

```
swift-test-codecov <codecov-filepath> [--metric <metric>] [--minimum <minimum-coverage>] [--table] [--sort <sort>] [--dependencies]
USAGE: swift-test-codecov <codecov-filepath> [--metric <metric>] [--minimum <minimum-coverage>] [--print-format <print-format>] [--sort <sort>] [--dependencies]
ARGUMENTS:
<codecov-filepath> the location of the JSON file output by `swift test --enable-code-coverage`.
You will find this in the build directory.
For example, if you've just performed a debug build, the file will be located at
`./.build/debug/codecov/<package-name>.json`.
For example, if you've just performed a debug build, the file will be located at `./.build/debug/codecov/<package-name>.json`.
OPTIONS:
-m, --metric <metric> The metric over which to aggregate. One of lines, functions, instantiations (default: lines)
-v, --minimum <minimum-coverage>
The minimum coverage allowed. A value between 0 and 100. Coverage below the minimum will result in
exit code 1. (default: 0)
-t, --table Prints an ascii table of coverage numbers.
The minimum coverage allowed. A value between 0 and 100. Coverage below the minimum will result in exit code 1. (default: 0)
-p, --print-format <print-format>
Set the print format. One of minimal, table, json (default: minimal)
-s, --sort <sort> Set the sort order for the coverage table. One of filename, +cov, -cov (default: filename)
-d, --dependencies Include dependencies in code coverage calculation.
-h, --help Show help information.
Expand Down
53 changes: 53 additions & 0 deletions Sources/SwiftTestCodecovLib/Aggregate.swift
@@ -0,0 +1,53 @@
//
// Aggregate.swift
//
//
// Created by Mathew Polzin on 1/8/21.
//

import Foundation

public func isDependencyPath(_ path: String) -> Bool {
return path.contains(".build/")
}

public struct Aggregate: Encodable {
/// The coverage data per-file.
public let coveragePerFile: [String: CodeCov.File.Coverage]
/// The total number of whatever aggregate property is chosen
///
/// For example, the number of lines (in total, not with coverage).
public let totalCount: Int
/// Overall coverage -- a number between 0.0 and 1.0
public let overallCoverage: Double

/// Overall coverage -- a number between 0.0 and 100.0
public var overallCoveragePercent: Double {
overallCoverage * 100
}

public var formattedOverallCoveragePercent: String {
"\(String(format: "%.2f", overallCoveragePercent))%"
}

public init(
coverage: CodeCov,
property: CodeCov.AggregateProperty,
includeDependencies: Bool
) {
coveragePerFile = coverage
.fileCoverages(for: property)
.filter { filename, _ in
includeDependencies ? true : !isDependencyPath(filename)
}

let total = coveragePerFile.reduce(0) { tot, next in
tot + next.value.count
}
totalCount = total

overallCoverage = coveragePerFile.reduce(0.0) { avg, next in
avg + Double(next.value.covered) / Double(total)
}
}
}
2 changes: 1 addition & 1 deletion Sources/SwiftTestCodecovLib/CodeCov.swift
Expand Up @@ -45,7 +45,7 @@ public struct CodeCov: Decodable {
}
}

public struct Coverage: Decodable {
public struct Coverage: Codable {
public let count: Int
public let covered: Int
public let percent: Double
Expand Down
90 changes: 58 additions & 32 deletions Sources/swift-test-codecov/main.swift
Expand Up @@ -12,7 +12,14 @@ You will find this in the build directory.
For example, if you've just performed a debug build, the file will be located at `./.build/debug/codecov/<package-name>.json`.
"""

/// How to sort the coverage table results.
/// How to display the results.
enum PrintFormat: String, CaseIterable, ExpressibleByArgument {
case minimal
case table
case json
}

/// How to sort the coverage table results (if `PrintFormat` is `.table`).
enum SortOrder: String, CaseIterable, ExpressibleByArgument {
case filename
case coverageAsc = "+cov"
Expand All @@ -26,6 +33,9 @@ struct StatsCommand: ParsableCommand {
discussion: "Ingest Code Coverage Metrics provided by `swift test --enable-code-coverage` and provide some high level analysis."
)

static let jsonDecoder = JSONDecoder()
static let jsonEncoder = JSONEncoder()

@Argument(
help: ArgumentHelp(
"the location of the JSON file output by `swift test --enable-code-coverage`.",
Expand All @@ -51,11 +61,13 @@ struct StatsCommand: ParsableCommand {
)
var minimumCoverage: Int = 0

@Flag(
name: [.customLong("table"), .customShort("t")],
help: ArgumentHelp("Prints an ascii table of coverage numbers.")
@Option(
name: [.long, .short],
parsing: .unconditional,
help: ArgumentHelp("Set the print format. One of "
+ PrintFormat.allCases.map { $0.rawValue }.joined(separator: ", "))
)
var printTable: Bool = false
var printFormat: PrintFormat = .minimal

@Option(
name: [.long, .short],
Expand All @@ -78,53 +90,62 @@ struct StatsCommand: ParsableCommand {
}

func run() throws {
let jsonDecoder = JSONDecoder()

let aggProperty: CodeCov.AggregateProperty = metric
let minimumCov = minimumCoverage

let data = try! Data(contentsOf: URL(fileURLWithPath: codecovFile))

let codeCoverage = try! jsonDecoder.decode(CodeCov.self, from: data)

func isDependencyPath(_ path: String) -> Bool {
return path.contains(".build/")
}
let codeCoverage = try! Self.jsonDecoder.decode(CodeCov.self, from: data)

let coveragePerFile = codeCoverage
.fileCoverages(for: aggProperty)
.filter { filename, _ in
includeDependencies ? true : !isDependencyPath(filename)
}
let aggregateCoverage = Aggregate(
coverage: codeCoverage,
property: aggProperty,
includeDependencies: includeDependencies
)

let totalCountOfProperty = coveragePerFile.reduce(0) { tot, next in
tot + next.value.count
}
let passed = aggregateCoverage.overallCoveragePercent > Double(minimumCov)

let overallCoverage = coveragePerFile.reduce(0.0) { avg, next in
avg + Double(next.value.covered) / Double(totalCountOfProperty)
if !passed && printFormat == .table {
// we don't print the error message out for the minimal or JSON formats.
print("")
print("!! The overall coverage did not meet the minimum threshold of \(minimumCov)%")
}

let overallCoveragePercent = overallCoverage * 100
printResults(aggregateCoverage)

let formattedOverallPercent = "\(String(format: "%.2f", overallCoveragePercent))%"

guard overallCoveragePercent > Double(minimumCov) else {
print("The overall coverage (\(formattedOverallPercent)) did not meet the minimum threshold of \(minimumCov)%")
guard passed else {
throw ExitCode.failure
}
}
}

guard printTable else {
print(formattedOverallPercent)
return
extension StatsCommand {

func printResults(_ aggregateCoverage: Aggregate) {
switch printFormat {
case .minimal:
printMinimal(aggregateCoverage)
case .table:
printTable(aggregateCoverage)
case .json:
printJson(aggregateCoverage)
}
}

print("Overall Coverage: \(formattedOverallPercent)")
func printMinimal(_ aggregateCoverage: Aggregate) {
print(aggregateCoverage.formattedOverallCoveragePercent)
}

func printTable(_ aggregateCoverage: Aggregate) {

print("")
print("Overall Coverage: \(aggregateCoverage.formattedOverallCoveragePercent)")
print("")

typealias CoverageTriple = (dependency: Bool, filename: String, coverage: Double)

let fileCoverages: [CoverageTriple] = coveragePerFile.map {
let fileCoverages: [CoverageTriple] = aggregateCoverage.coveragePerFile.map {
(
dependency: isDependencyPath($0.key),
filename: URL(fileURLWithPath: $0.key).lastPathComponent,
Expand Down Expand Up @@ -152,6 +173,7 @@ struct StatsCommand: ParsableCommand {
sourceCoverages.append(fileCoverage)
}
}
let blankTriple: CoverageTriple = (false, "", -1)
let dividerTriple: CoverageTriple = (false, "=-=-=-=-=-=-=-=-=", -1)

let table = TextTable<CoverageTriple> {
Expand All @@ -164,7 +186,11 @@ struct StatsCommand: ParsableCommand {
].compactMap { $0 }
}

table.print(sourceCoverages + [dividerTriple] + testCoverages, style: Simple.self)
table.print(sourceCoverages + [blankTriple, dividerTriple, blankTriple] + testCoverages, style: Simple.self)
}

func printJson(_ aggregateCoverage: Aggregate) {
print(String(data: try! Self.jsonEncoder.encode(aggregateCoverage), encoding: .utf8)!)
}
}

Expand Down

0 comments on commit c0914f1

Please sign in to comment.