From 577abfb7475a1fbdd137b7e111819c106975510b Mon Sep 17 00:00:00 2001 From: Kasper Weibel Nielsen-Refs Date: Wed, 22 Dec 2021 23:25:54 +0100 Subject: [PATCH 1/2] Add option to filter coverage on target name Add option to not show the test output Add total coverage breakdown in the output --- xcresultparser/XCResultFormatter.swift | 20 +++++++++++++++++++- xcresultparser/main.swift | 20 ++++++++++++++++---- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/xcresultparser/XCResultFormatter.swift b/xcresultparser/XCResultFormatter.swift index 7108ec4..0e8e4d3 100644 --- a/xcresultparser/XCResultFormatter.swift +++ b/xcresultparser/XCResultFormatter.swift @@ -16,6 +16,7 @@ struct XCResultFormatter { private let invocationRecord: ActionsInvocationRecord private let codeCoverage: CodeCoverage? private let outputFormatter: XCResultFormatting + private let targets: [String]? private var numFormatter: NumberFormatter = { let numFormatter = NumberFormatter() @@ -32,7 +33,8 @@ struct XCResultFormatter { // MARK: - Initializer init?(with url: URL, - formatter: XCResultFormatting + formatter: XCResultFormatting, + targets: [String]? ) { resultFile = XCResultFile(url: url) guard let record = resultFile.getInvocationRecord() else { @@ -41,6 +43,7 @@ struct XCResultFormatter { invocationRecord = record outputFormatter = formatter codeCoverage = resultFile.getCodeCoverage() + self.targets = targets //if let logsId = invocationRecord?.actions.last?.actionResult.logRef?.id { // let testLogs = resultFile.getLogs(id: logsId) @@ -202,8 +205,17 @@ struct XCResultFormatter { guard let codeCoverage = codeCoverage else { return lines } + var executableLines: Int = 0 + var coveredLines: Int = 0 for target in codeCoverage.targets { + // Clean up target.name. Split on '.' because the target.name is appended with .framework or .app + let targetName: String = String(target.name.split(separator: ".").first ?? "") + if let targets = targets, targets.count > 0, !targets.contains(targetName) { + continue + } let covPercent = percentFormatter.unwrappedString(for: (target.lineCoverage * 100)) + executableLines += target.executableLines + coveredLines += target.coveredLines lines.append( outputFormatter.codeCoverageTargetSummary( "\(target.name): \(covPercent)% (\(target.coveredLines)/\(target.executableLines))" @@ -257,6 +269,12 @@ struct XCResultFormatter { ) } } + // Append the total coverage below the header + guard executableLines > 0 else { return lines } + let fraction = Double(coveredLines) / Double(executableLines) + let covPercent: String = percentFormatter.unwrappedString(for: fraction * 100) + let line = outputFormatter.codeCoverageTargetSummary("Total coverage: \(covPercent)% (\(coveredLines)/\(executableLines))") + lines.insert(line, at: 1) return lines } } diff --git a/xcresultparser/main.swift b/xcresultparser/main.swift index 2aef790..f144a5d 100644 --- a/xcresultparser/main.swift +++ b/xcresultparser/main.swift @@ -21,9 +21,15 @@ struct xcresultparser: ParsableCommand { @Option(name: .shortAndLong, help: "The name of the project root. If present paths and urls are relative to the specified directory.") var projectRoot: String? + @Option(name: .shortAndLong, help: "Specify which targets to calculate coverage from") + var targets: [String] = [] + @Flag(name: .shortAndLong, help: "Whether to print coverage data.") var coverage: Int + @Flag(name: .shortAndLong, help: "Whether to print test results.") + var notestresult: Int + @Flag(name: .shortAndLong, help: "Quiet. Don't print status output.") var quiet: Int @@ -63,14 +69,20 @@ struct xcresultparser: ParsableCommand { private func outputDescription() throws { guard let resultParser = XCResultFormatter( - with: URL(fileURLWithPath: xcresultFile), - formatter: outputFormatter) else { + with: URL(fileURLWithPath: xcresultFile), + formatter: outputFormatter, + targets: targets + ) else { throw ParseError.argumentError } writeToStdOutLn(resultParser.documentPrefix(title: "XCResults")) - writeToStdOutLn(resultParser.summary) + if notestresult == 0 { + writeToStdOutLn(resultParser.summary) + } writeToStdOutLn(resultParser.divider) - writeToStdOutLn(resultParser.testDetails) + if notestresult == 0 { + writeToStdOutLn(resultParser.testDetails) + } if coverage == 1 { writeToStdOutLn(resultParser.coverageDetails) } From 2e29a684bf6fb52147440901a788250969aeeb22 Mon Sep 17 00:00:00 2001 From: Kasper Weibel Nielsen-Refs Date: Fri, 24 Dec 2021 00:09:23 +0100 Subject: [PATCH 2/2] * Use a Set for looking up target names. * Change CLI argument name * Extract target processing into an extension --- xcresultparser/XCResultFormatter.swift | 28 ++++++++++++++++++-------- xcresultparser/main.swift | 14 ++++++------- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/xcresultparser/XCResultFormatter.swift b/xcresultparser/XCResultFormatter.swift index 0e8e4d3..659e876 100644 --- a/xcresultparser/XCResultFormatter.swift +++ b/xcresultparser/XCResultFormatter.swift @@ -16,7 +16,7 @@ struct XCResultFormatter { private let invocationRecord: ActionsInvocationRecord private let codeCoverage: CodeCoverage? private let outputFormatter: XCResultFormatting - private let targets: [String]? + private let coverageTargets: Set private var numFormatter: NumberFormatter = { let numFormatter = NumberFormatter() @@ -34,7 +34,7 @@ struct XCResultFormatter { init?(with url: URL, formatter: XCResultFormatting, - targets: [String]? + coverageTargets: [String] = [] ) { resultFile = XCResultFile(url: url) guard let record = resultFile.getInvocationRecord() else { @@ -43,7 +43,7 @@ struct XCResultFormatter { invocationRecord = record outputFormatter = formatter codeCoverage = resultFile.getCodeCoverage() - self.targets = targets + self.coverageTargets = codeCoverage?.targets(filteredBy: coverageTargets) ?? [] //if let logsId = invocationRecord?.actions.last?.actionResult.logRef?.id { // let testLogs = resultFile.getLogs(id: logsId) @@ -208,11 +208,7 @@ struct XCResultFormatter { var executableLines: Int = 0 var coveredLines: Int = 0 for target in codeCoverage.targets { - // Clean up target.name. Split on '.' because the target.name is appended with .framework or .app - let targetName: String = String(target.name.split(separator: ".").first ?? "") - if let targets = targets, targets.count > 0, !targets.contains(targetName) { - continue - } + guard coverageTargets.contains(target.name) else { continue } let covPercent = percentFormatter.unwrappedString(for: (target.lineCoverage * 100)) executableLines += target.executableLines coveredLines += target.coveredLines @@ -279,6 +275,22 @@ struct XCResultFormatter { } } +private extension CodeCoverage { + func targets(filteredBy filter: [String]) -> Set { + let targetNames = targets.map { $0.name } + guard !filter.isEmpty else { + return Set(targetNames) + } + let filterSet = Set(filter) + let filtered = targetNames.filter { thisTarget in + // Clean up target.name. Split on '.' because the target.name is appended with .framework or .app + guard let stripped = thisTarget.split(separator: ".").first else { return true } + return filterSet.contains(String(stripped)) + } + return Set(filtered) + } +} + private extension NumberFormatter { func unwrappedString(for input: Double?) -> String { return string(for: input) ?? "" diff --git a/xcresultparser/main.swift b/xcresultparser/main.swift index f144a5d..0408bf5 100644 --- a/xcresultparser/main.swift +++ b/xcresultparser/main.swift @@ -21,14 +21,14 @@ struct xcresultparser: ParsableCommand { @Option(name: .shortAndLong, help: "The name of the project root. If present paths and urls are relative to the specified directory.") var projectRoot: String? - @Option(name: .shortAndLong, help: "Specify which targets to calculate coverage from") - var targets: [String] = [] + @Option(name: [.customShort("t"), .customLong("coverage-targets")], help: "Specify which targets to calculate coverage from") + var coverageTargets: [String] = [] @Flag(name: .shortAndLong, help: "Whether to print coverage data.") var coverage: Int @Flag(name: .shortAndLong, help: "Whether to print test results.") - var notestresult: Int + var noTestResult: Int @Flag(name: .shortAndLong, help: "Quiet. Don't print status output.") var quiet: Int @@ -71,16 +71,14 @@ struct xcresultparser: ParsableCommand { guard let resultParser = XCResultFormatter( with: URL(fileURLWithPath: xcresultFile), formatter: outputFormatter, - targets: targets + coverageTargets: coverageTargets ) else { throw ParseError.argumentError } writeToStdOutLn(resultParser.documentPrefix(title: "XCResults")) - if notestresult == 0 { + if noTestResult == 0 { writeToStdOutLn(resultParser.summary) - } - writeToStdOutLn(resultParser.divider) - if notestresult == 0 { + writeToStdOutLn(resultParser.divider) writeToStdOutLn(resultParser.testDetails) } if coverage == 1 {