Skip to content

Commit

Permalink
Merge pull request #2 from yumemi-inc/feature/test-coverage
Browse files Browse the repository at this point in the history
Feature/test coverage
  • Loading branch information
Elvis Shi authored Mar 18, 2022
2 parents 9c8c1ab + 792ada8 commit 7322cb8
Show file tree
Hide file tree
Showing 5 changed files with 231 additions and 60 deletions.
1 change: 1 addition & 0 deletions Sources/DangerSwiftKantoku/DangerDSL+.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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) },
Expand Down
87 changes: 87 additions & 0 deletions Sources/DangerSwiftKantoku/Kantoku+Coverage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
//
// 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"))

switch overallAcceptance {
case .good:
break

case .acceptable:
warn("Overall coverage is \(overallCoverage)")

case .reject:
fail("Overall coverage is \(overallCoverage), which is not enough")
}

}

}

extension Kantoku.CoverageAcceptance {

var markdownDescription: String {
switch self {
case .good:
return ":white_flower:"

case .acceptable:
return ":thinking:"

case .reject:
return ":no_good:"
}
}

}
67 changes: 67 additions & 0 deletions Sources/DangerSwiftKantoku/Kantoku+IssueComments.swift
Original file line number Diff line number Diff line change
@@ -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)
}

}

}

}
115 changes: 57 additions & 58 deletions Sources/DangerSwiftKantoku/Kantoku.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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,
Expand All @@ -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
Expand All @@ -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)
}
Expand Down Expand Up @@ -71,67 +79,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)
}

}

}

public func parseXCResultFile(at filePath: String, configuration: XCResultParsingConfiguration) {
private func postIssuesIfNeeded(from resultFile: XCResultFile, 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
}

Expand All @@ -155,4 +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
}
}
}
}

}
21 changes: 19 additions & 2 deletions Sources/DangerSwiftKantoku/XCResultParsingConfiguration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

}
Expand All @@ -38,7 +55,7 @@ extension XCResultParsingConfiguration {

extension XCResultParsingConfiguration {

public var needsIssues: Bool {
var needsIssues: Bool {
parseBuildWarnings || parseBuildErrors || parseAnalyzerWarnings || parseTestFailures
}

Expand Down

0 comments on commit 7322cb8

Please sign in to comment.