diff --git a/xcresultparser/XCResultFormatter.swift b/xcresultparser/XCResultFormatter.swift index 7108ec4..659e876 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 coverageTargets: Set private var numFormatter: NumberFormatter = { let numFormatter = NumberFormatter() @@ -32,7 +33,8 @@ struct XCResultFormatter { // MARK: - Initializer init?(with url: URL, - formatter: XCResultFormatting + formatter: XCResultFormatting, + coverageTargets: [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.coverageTargets = codeCoverage?.targets(filteredBy: coverageTargets) ?? [] //if let logsId = invocationRecord?.actions.last?.actionResult.logRef?.id { // let testLogs = resultFile.getLogs(id: logsId) @@ -202,8 +205,13 @@ struct XCResultFormatter { guard let codeCoverage = codeCoverage else { return lines } + var executableLines: Int = 0 + var coveredLines: Int = 0 for target in codeCoverage.targets { + guard coverageTargets.contains(target.name) else { 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,10 +265,32 @@ 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 } } +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 2aef790..0408bf5 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: [.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 + @Flag(name: .shortAndLong, help: "Quiet. Don't print status output.") var quiet: Int @@ -63,14 +69,18 @@ struct xcresultparser: ParsableCommand { private func outputDescription() throws { guard let resultParser = XCResultFormatter( - with: URL(fileURLWithPath: xcresultFile), - formatter: outputFormatter) else { + with: URL(fileURLWithPath: xcresultFile), + formatter: outputFormatter, + coverageTargets: coverageTargets + ) else { throw ParseError.argumentError } writeToStdOutLn(resultParser.documentPrefix(title: "XCResults")) - writeToStdOutLn(resultParser.summary) - writeToStdOutLn(resultParser.divider) - writeToStdOutLn(resultParser.testDetails) + if noTestResult == 0 { + writeToStdOutLn(resultParser.summary) + writeToStdOutLn(resultParser.divider) + writeToStdOutLn(resultParser.testDetails) + } if coverage == 1 { writeToStdOutLn(resultParser.coverageDetails) }