From 8f3f014880e17c4ac2a6bf267253397682d9c866 Mon Sep 17 00:00:00 2001 From: Elvis Shi <3942121+el-hoshino@users.noreply.github.com> Date: Thu, 3 Mar 2022 15:49:10 +0900 Subject: [PATCH 1/5] Move IssueComments in Kantoku out to an individual file --- .../Kantoku+IssueComments.swift | 67 ++++++++++++++++++ Sources/DangerSwiftKantoku/Kantoku.swift | 69 +++---------------- .../XCResultParsingConfiguration.swift | 2 +- 3 files changed, 79 insertions(+), 59 deletions(-) create mode 100644 Sources/DangerSwiftKantoku/Kantoku+IssueComments.swift diff --git a/Sources/DangerSwiftKantoku/Kantoku+IssueComments.swift b/Sources/DangerSwiftKantoku/Kantoku+IssueComments.swift new file mode 100644 index 0000000..c70a63c --- /dev/null +++ b/Sources/DangerSwiftKantoku/Kantoku+IssueComments.swift @@ -0,0 +1,67 @@ +// +// File.swift +// +// +// Created by 史 翔新 on 2022/03/03. +// + +import Foundation + +extension Kantoku { + + enum CommentLevel { + case comment + case warning + case failure + } + + private func post(as level: CommentLevel) -> (_ message: String) -> Void { + + switch level { + case .comment: + return comment(_:) + + case .warning: + return warn(_:) + + case .failure: + return fail(_:) + } + + } + + private func post(as level: CommentLevel) -> (_ message: String, _ filePath: String, _ lineNumber: Int) -> Void { + + switch level { + case .comment: + return comment(_:to:at:) + + case .warning: + return warn(_:to:at:) + + case .failure: + return fail(_:to:at:) + } + + } + + func post(_ summaries: [PostableIssueSummary], as level: CommentLevel) { + + for summary in summaries { + let message = summary.issueMessage + let filePath = summary.documentLocation?.relativePath(against: workingDirectoryPath) + + if let filePath = filePath { + let lineNumber = filePath.queries?.endingLineNumber + // Line numbers in XCResult starts from `0`, while on web pages like GitHub starts from `1` + post(as: level)(message, filePath.filePath, lineNumber.map({ $0 + 1 }) ?? 0) + + } else { + post(as: level)(message) + } + + } + + } + +} diff --git a/Sources/DangerSwiftKantoku/Kantoku.swift b/Sources/DangerSwiftKantoku/Kantoku.swift index bf425b1..397e2ef 100644 --- a/Sources/DangerSwiftKantoku/Kantoku.swift +++ b/Sources/DangerSwiftKantoku/Kantoku.swift @@ -71,67 +71,12 @@ extension Kantoku { extension Kantoku { - private enum CommentLevel { - case comment - case warning - case failure - } - - private func post(as level: CommentLevel) -> (_ message: String) -> Void { - - switch level { - case .comment: - return comment(_:) - - case .warning: - return warn(_:) - - case .failure: - return fail(_:) - } - - } - - private func post(as level: CommentLevel) -> (_ message: String, _ filePath: String, _ lineNumber: Int) -> Void { - - switch level { - case .comment: - return comment(_:to:at:) - - case .warning: - return warn(_:to:at:) - - case .failure: - return fail(_:to:at:) - } - - } - - private func post(_ summaries: [PostableIssueSummary], as level: CommentLevel) { - - for summary in summaries { - let message = summary.issueMessage - let filePath = summary.documentLocation?.relativePath(against: workingDirectoryPath) - - if let filePath = filePath { - let lineNumber = filePath.queries?.endingLineNumber - // Line numbers in XCResult starts from `0`, while on web pages like GitHub starts from `1` - post(as: level)(message, filePath.filePath, lineNumber.map({ $0 + 1 }) ?? 0) - - } else { - post(as: level)(message) - } - - } + private func postIssuesIfNeeded(from resultFile: XCResultFile, configuration: XCResultParsingConfiguration) { - } - - public func parseXCResultFile(at filePath: String, configuration: XCResultParsingConfiguration) { - - let resultFile = XCResultFile(url: .init(fileURLWithPath: filePath)) if configuration.needsIssues { + guard let issues = resultFile.getInvocationRecord()?.issues else { - warn("Failed to get invocation record from \(filePath)") + warn("Failed to get invocation record from \(resultFile.url.absoluteString)") return } @@ -155,4 +100,12 @@ extension Kantoku { } + public func parseXCResultFile(at filePath: String, configuration: XCResultParsingConfiguration) { + + let resultFile = XCResultFile(url: .init(fileURLWithPath: filePath)) + + postIssuesIfNeeded(from: resultFile, configuration: configuration) + + } + } diff --git a/Sources/DangerSwiftKantoku/XCResultParsingConfiguration.swift b/Sources/DangerSwiftKantoku/XCResultParsingConfiguration.swift index 0bbf434..c74a6c0 100644 --- a/Sources/DangerSwiftKantoku/XCResultParsingConfiguration.swift +++ b/Sources/DangerSwiftKantoku/XCResultParsingConfiguration.swift @@ -38,7 +38,7 @@ extension XCResultParsingConfiguration { extension XCResultParsingConfiguration { - public var needsIssues: Bool { + var needsIssues: Bool { parseBuildWarnings || parseBuildErrors || parseAnalyzerWarnings || parseTestFailures } From dba6d5e1b44ba48b7e577832b4d8fc63c8f72e89 Mon Sep 17 00:00:00 2001 From: Elvis Shi <3942121+el-hoshino@users.noreply.github.com> Date: Thu, 3 Mar 2022 15:50:18 +0900 Subject: [PATCH 2/5] Add new configurations for CodeCoverage --- .../XCResultParsingConfiguration.swift | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Sources/DangerSwiftKantoku/XCResultParsingConfiguration.swift b/Sources/DangerSwiftKantoku/XCResultParsingConfiguration.swift index c74a6c0..927a8a4 100644 --- a/Sources/DangerSwiftKantoku/XCResultParsingConfiguration.swift +++ b/Sources/DangerSwiftKantoku/XCResultParsingConfiguration.swift @@ -9,21 +9,38 @@ import Foundation public struct XCResultParsingConfiguration { + public enum CodeCoverageRequirement { + public struct CoverageThreshold { + public var acceptable: Double + public var recommended: Double + public init(acceptable: Double, recommended: Double) { + self.acceptable = acceptable + self.recommended = recommended + } + } + case none + case required(CoverageThreshold) + } + public var parseBuildWarnings: Bool public var parseBuildErrors: Bool public var parseAnalyzerWarnings: Bool public var parseTestFailures: Bool + public var codeCoverageRequirement: CodeCoverageRequirement + public init( parseBuildWarnings: Bool = true, parseBuildErrors: Bool = true, parseAnalyzerWarnings: Bool = true, - parseTestFailures: Bool = true + parseTestFailures: Bool = true, + codeCoverageRequirement: CodeCoverageRequirement = .required(.init(acceptable: 0, recommended: 0.6)) ) { self.parseBuildWarnings = parseBuildWarnings self.parseBuildErrors = parseBuildErrors self.parseAnalyzerWarnings = parseAnalyzerWarnings self.parseTestFailures = parseTestFailures + self.codeCoverageRequirement = codeCoverageRequirement } } From 6758424e5f68b025f97f1b7b0b68b3d367743f14 Mon Sep 17 00:00:00 2001 From: Elvis Shi <3942121+el-hoshino@users.noreply.github.com> Date: Thu, 3 Mar 2022 15:51:54 +0900 Subject: [PATCH 3/5] Implement CodeCoverage report --- .../DangerSwiftKantoku/Kantoku+Coverage.swift | 76 +++++++++++++++++++ Sources/DangerSwiftKantoku/Kantoku.swift | 46 +++++++++++ 2 files changed, 122 insertions(+) create mode 100644 Sources/DangerSwiftKantoku/Kantoku+Coverage.swift diff --git a/Sources/DangerSwiftKantoku/Kantoku+Coverage.swift b/Sources/DangerSwiftKantoku/Kantoku+Coverage.swift new file mode 100644 index 0000000..1557d7c --- /dev/null +++ b/Sources/DangerSwiftKantoku/Kantoku+Coverage.swift @@ -0,0 +1,76 @@ +// +// Kantoku+Coverage.swift +// +// +// Created by 史 翔新 on 2022/03/03. +// + +import Foundation +import XCResultKit + +extension Kantoku { + + var coverageNumberFormatter: NumberFormatter { + + let formatter = NumberFormatter() + formatter.numberStyle = .percent + formatter.maximumFractionDigits = 2 + return formatter + + } + +} + +extension Kantoku { + + enum CoverageAcceptance { + case good + case acceptable + case reject + } + + private struct CoverageCommentItem { + var title: String + var coverage: Double + var acceptance: CoverageAcceptance + } + + func post(_ coverage: CodeCoverage, as acceptanceDecision: (Double) -> CoverageAcceptance) { + + let formatter = coverageNumberFormatter + let title = "Overall" + let overallCoverage = coverage.lineCoverage + let overallAcceptance = acceptanceDecision(overallCoverage) + let coverageText = formatter.string(from: overallCoverage as NSNumber) ?? { + fail("Failed to extract overall coverage from \(overallCoverage)") + return "NaN" + }() + + let markdownLines: [String] = [ + "| | Coverage | Acceptance |", + "|:---:|:---:|:---:|", + "| \(title) | \(coverageText) | \(overallAcceptance.markdownDescription) |", + // TODO: Coverage of diff files + ] + markdown(markdownLines.joined(separator: "\n")) + + } + +} + +extension Kantoku.CoverageAcceptance { + + var markdownDescription: String { + switch self { + case .good: + return ":white_flower:" + + case .acceptable: + return ":thinking:" + + case .reject: + return ":no_good:" + } + } + +} diff --git a/Sources/DangerSwiftKantoku/Kantoku.swift b/Sources/DangerSwiftKantoku/Kantoku.swift index 397e2ef..4ff4ed9 100644 --- a/Sources/DangerSwiftKantoku/Kantoku.swift +++ b/Sources/DangerSwiftKantoku/Kantoku.swift @@ -12,6 +12,8 @@ public struct Kantoku { let workingDirectoryPath: String + private let markdownCommentExecutor: (_ comment: String) -> Void + private let inlineCommentExecutor: (_ comment: String, _ filePath: String, _ lineNumber: Int) -> Void private let normalCommentExecutor: (_ comment: String) -> Void @@ -23,6 +25,7 @@ public struct Kantoku { init( workingDirectoryPath: String, + markdownCommentExecutor: @escaping (_ comment: String) -> Void, inlineCommentExecutor: @escaping (_ comment: String, _ filePath: String, _ lineNumber: Int) -> Void, normalCommentExecutor: @escaping (_ comment: String) -> Void, inlineWarningExecutor: @escaping (_ comment: String, _ filePath: String, _ lineNumber: Int) -> Void, @@ -31,6 +34,7 @@ public struct Kantoku { normalFailureExecutor: @escaping (_ comment: String) -> Void ) { self.workingDirectoryPath = workingDirectoryPath + self.markdownCommentExecutor = markdownCommentExecutor self.inlineCommentExecutor = inlineCommentExecutor self.normalCommentExecutor = normalCommentExecutor self.inlineWarningExecutor = inlineWarningExecutor @@ -43,6 +47,10 @@ public struct Kantoku { extension Kantoku { + func markdown(_ comment: String) { + markdownCommentExecutor(comment) + } + func comment(_ comment: String, to filePath: String, at lineNumber: Int) { inlineCommentExecutor(comment, filePath, lineNumber) } @@ -100,12 +108,50 @@ extension Kantoku { } + private func postCoverageIfNeeded(from resultFile: XCResultFile, configuration: XCResultParsingConfiguration) { + + if let coverageAcceptanceDecision = configuration.codeCoverageRequirement.acceptanceDecision { + + guard let coverage = resultFile.getCodeCoverage() else { + warn("Failed to get coverage from \(resultFile.url.absoluteString)") + return + } + + post(coverage, as: coverageAcceptanceDecision) + + } + + } + public func parseXCResultFile(at filePath: String, configuration: XCResultParsingConfiguration) { let resultFile = XCResultFile(url: .init(fileURLWithPath: filePath)) postIssuesIfNeeded(from: resultFile, configuration: configuration) + postCoverageIfNeeded(from: resultFile, configuration: configuration) } } + +extension XCResultParsingConfiguration.CodeCoverageRequirement { + + var acceptanceDecision: ((Double) -> Kantoku.CoverageAcceptance)? { + switch self { + case .none: + return nil + + case .required(let threshold): + return { coverage in + if coverage >= threshold.recommended { + return .good + } else if coverage >= threshold.acceptable { + return .acceptable + } else { + return .reject + } + } + } + } + +} From 71beeb2d22c439ce330edeabd88b84d3e3843e7a Mon Sep 17 00:00:00 2001 From: Elvis Shi <3942121+el-hoshino@users.noreply.github.com> Date: Thu, 3 Mar 2022 15:52:12 +0900 Subject: [PATCH 4/5] Apply DangerDSL to Kantoku --- Sources/DangerSwiftKantoku/DangerDSL+.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/DangerSwiftKantoku/DangerDSL+.swift b/Sources/DangerSwiftKantoku/DangerDSL+.swift index 5405e1a..719443b 100644 --- a/Sources/DangerSwiftKantoku/DangerDSL+.swift +++ b/Sources/DangerSwiftKantoku/DangerDSL+.swift @@ -12,6 +12,7 @@ extension DangerDSL { public var kantoku: Kantoku { .init( workingDirectoryPath: utils.exec("pwd"), + markdownCommentExecutor: { markdown($0) }, inlineCommentExecutor: { message(message: $0, file: $1, line: $2) }, normalCommentExecutor: { message($0) }, inlineWarningExecutor: { warn(message: $0, file: $1, line: $2) }, From 792ada85cf3af91d1b6a5203088bf02f3dde00ac Mon Sep 17 00:00:00 2001 From: Elvis Shi <3942121+el-hoshino@users.noreply.github.com> Date: Thu, 3 Mar 2022 16:58:47 +0900 Subject: [PATCH 5/5] Add warnings and failures depending on overall coverage --- Sources/DangerSwiftKantoku/Kantoku+Coverage.swift | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Sources/DangerSwiftKantoku/Kantoku+Coverage.swift b/Sources/DangerSwiftKantoku/Kantoku+Coverage.swift index 1557d7c..130abff 100644 --- a/Sources/DangerSwiftKantoku/Kantoku+Coverage.swift +++ b/Sources/DangerSwiftKantoku/Kantoku+Coverage.swift @@ -54,6 +54,17 @@ extension Kantoku { ] markdown(markdownLines.joined(separator: "\n")) + switch overallAcceptance { + case .good: + break + + case .acceptable: + warn("Overall coverage is \(overallCoverage)") + + case .reject: + fail("Overall coverage is \(overallCoverage), which is not enough") + } + } }