Skip to content

Commit

Permalink
Merge pull request #7 from IntrepidPursuits/coverage
Browse files Browse the repository at this point in the history
Coverage
  • Loading branch information
Patrick Butkiewicz authored Nov 14, 2016
2 parents c5afc0b + 509c92d commit 1af38a0
Show file tree
Hide file tree
Showing 15 changed files with 447 additions and 25 deletions.
73 changes: 73 additions & 0 deletions JenkinsTests/CoberturaCoverageTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//
// CoberturaCoverageTests.swift
// Jenkins
//
// Created by Patrick Butkiewicz on 11/13/16.
//
//

import Foundation
import XCTest
@testable import Jenkins

class CoberturaCoverageTests: XCTestCase {

func testCoberturaCodeCoverageDepth2() {

guard let path = Bundle(for: type(of: self)).path(forResource: "CoberturaCoverageReportDepth2", ofType: "json") else {
return XCTFail("Missing Coverage Report JSON")
}

guard
let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .alwaysMapped),
let json: JSON = try! JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as? JSON
else {
return XCTFail("Failed mapping Coverage Report JSON")
}

let c = CoberturaCodeCoverageReport(json: json["results"] as! JSON)

let validChildren = 0
XCTAssert(c.childReports.count == validChildren, "Report has \(c.childReports.count) children, but should have \(validChildren)")

let validName = "Cobertura Coverage Report"
XCTAssert(c.name.compare(validName) == ComparisonResult.orderedSame, "Coverage report name is '\(c.name)' but should be \(validName)")

let validCovElements = 5
XCTAssert(c.coverageElements.count == validCovElements, "Report has \(c.coverageElements.count) coverage elements but should have \(validCovElements)")

let validLineRatio = 0.2
let actualLineRatio = c.ratio(of: CoberturaCodeCoverageElementType.Lines)
XCTAssertEqualWithAccuracy(actualLineRatio, validLineRatio, accuracy: 0.05)
}

func testCoberturaCodeCoverageDepth3() {
guard let path = Bundle(for: type(of: self)).path(forResource: "CoberturaCoverageReportDepth3", ofType: "json") else {
return XCTFail("Missing Coverage Report JSON")
}

guard
let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .alwaysMapped),
let json: JSON = try! JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as? JSON
else {
return XCTFail("Failed mapping Coverage Report JSON")
}

let c = CoberturaCodeCoverageReport(json: json["results"] as! JSON)

let validChildren = 27
XCTAssert(c.childReports.count == validChildren, "Report has \(c.childReports.count) children, but should have \(validChildren)")

let validName = "Cobertura Coverage Report"
XCTAssert(c.name.compare(validName) == ComparisonResult.orderedSame, "Coverage report name is '\(c.name)' but should be \(validName)")

let validCovElements = 5
XCTAssert(c.coverageElements.count == validCovElements, "Report has \(c.coverageElements.count) coverage elements but should have \(validCovElements)")

let validLineRatio = 0.2
let actualLineRatio = c.ratio(of: CoberturaCodeCoverageElementType.Lines)
XCTAssertEqualWithAccuracy(actualLineRatio, validLineRatio, accuracy: 0.05)
}

}

22 changes: 22 additions & 0 deletions JenkinsTests/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
</dict>
</plist>
1 change: 1 addition & 0 deletions JenkinsTests/JSON/CoberturaCoverageReportDepth2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"_class":"hudson.plugins.cobertura.targets.CoverageResult","results":{"children":[{"children":[{}],"elements":[{},{},{},{}],"name":"Pods.Crashlytics.iOS.Crashlytics.framework.Headers"},{"children":[{}],"elements":[{},{},{},{}],"name":"bose-connect-ios.Analytics"},{"children":[{},{},{},{},{}],"elements":[{},{},{},{}],"name":"bose-connect-ios.Controllers"},{"children":[{},{}],"elements":[{},{},{},{}],"name":"bose-connect-ios.Controllers.About"},{"children":[{},{},{},{}],"elements":[{},{},{},{}],"name":"bose-connect-ios.Controllers.CarouselSubControllers"},{"children":[{},{},{}],"elements":[{},{},{},{}],"name":"bose-connect-ios.Controllers.FirmwareUpdate"},{"children":[{}],"elements":[{},{},{},{}],"name":"bose-connect-ios.Controllers.FirmwareUpdate.UpdateSuccessOverlay"},{"children":[{}],"elements":[{},{},{},{}],"name":"bose-connect-ios.Controllers.FullScreenAlert"},{"children":[{}],"elements":[{},{},{},{}],"name":"bose-connect-ios.Controllers.HeartRate"},{"children":[{}],"elements":[{},{},{},{}],"name":"bose-connect-ios.Controllers.Help"},{"children":[{},{},{},{}],"elements":[{},{},{},{}],"name":"bose-connect-ios.Controllers.MusicShare"},{"children":[{},{},{},{}],"elements":[{},{},{},{}],"name":"bose-connect-ios.Controllers.NowPlaying"},{"children":[{}],"elements":[{},{},{},{}],"name":"bose-connect-ios.Controllers.PairingMode"},{"children":[{},{},{},{},{},{},{}],"elements":[{},{},{},{}],"name":"bose-connect-ios.Controllers.Settings"},{"children":[{},{}],"elements":[{},{},{},{}],"name":"bose-connect-ios.Controllers.VideoPlayer"},{"children":[{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}],"elements":[{},{},{},{}],"name":"bose-connect-ios.Extensions"},{"children":[{},{}],"elements":[{},{},{},{}],"name":"bose-connect-ios.Extensions.CrashlyticsLog"},{"children":[{}],"elements":[{},{},{},{}],"name":"bose-connect-ios.Mocks"},{"children":[{},{}],"elements":[{},{},{},{}],"name":"bose-connect-ios.Models"},{"children":[{},{},{},{}],"elements":[{},{},{},{}],"name":"bose-connect-ios.Models.UserDefaults"},{"children":[{}],"elements":[{},{},{},{}],"name":"bose-connect-ios.Utilities"},{"children":[{},{}],"elements":[{},{},{},{}],"name":"bose-connect-ios.ViewModels"},{"children":[{},{},{},{},{}],"elements":[{},{},{},{}],"name":"bose-connect-ios.ViewModels.Carousel"},{"children":[{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}],"elements":[{},{},{},{}],"name":"bose-connect-ios.Views"},{"children":[{},{},{},{},{}],"elements":[{},{},{},{}],"name":"bose-connect-ios.Views.CNC"},{"children":[{},{},{},{},{}],"elements":[{},{},{},{}],"name":"bose-connect-ios.Views.HeartRate"},{"children":[{},{}],"elements":[{},{},{},{}],"name":"bose-connect-ios.Views.MusicShareOnboarding"}],"elements":[{"denominator":10.0,"name":"Packages","numerator":2.0,"ratio":20.0},{"denominator":20.0,"name":"Files","numerator":4.0,"ratio":20.0},{"denominator":20.0,"name":"Classes","numerator":5.0,"ratio":25.0},{"denominator":5.0,"name":"Lines","numerator":1.0,"ratio":20.0},{"denominator":0.0,"name":"Conditionals","numerator":0.0,"ratio":100.0}],"name":"Cobertura Coverage Report"}}
2 changes: 2 additions & 0 deletions JenkinsTests/JSON/CoberturaCoverageReportDepth3.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions JenkinsTests/JSON/JacocoCoverageReportDepth.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"_class":"hudson.plugins.jacoco.report.CoverageReport","branchCoverage":{"covered":731,"missed":454,"percentage":62,"percentageFloat":61.687763,"total":1185},"classCoverage":{"covered":61,"missed":23,"percentage":73,"percentageFloat":72.61904,"total":84},"complexityScore":{"covered":854,"missed":578,"percentage":60,"percentageFloat":59.63687,"total":1432},"instructionCoverage":{"covered":8244,"missed":2896,"percentage":74,"percentageFloat":74.003586,"total":11140},"lineCoverage":{"covered":2107,"missed":764,"percentage":73,"percentageFloat":73.38907,"total":2871},"methodCoverage":{"covered":567,"missed":247,"percentage":70,"percentageFloat":69.65602,"total":814},"previousResult":{"_class":"hudson.plugins.jacoco.report.CoverageReport"}}
53 changes: 53 additions & 0 deletions JenkinsTests/JacocoCoverageTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
//
// JacocoCoverageTests.swift
// Jenkins
//
// Created by Patrick Butkiewicz on 11/13/16.
//
//

import Foundation
import XCTest
@testable import Jenkins

class JacocoCoverageTests: XCTestCase {

func testJacocoCodeCoverageDepth2() {
guard let path = Bundle(for: type(of: self)).path(forResource: "JacocoCoverageReportDepth", ofType: "json") else {
return XCTFail("Missing Jacoco Coverage Report JSON")
}

guard
let data = try? Data(contentsOf: URL(fileURLWithPath: path), options: .alwaysMapped),
let json: JSON = try! JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments) as? JSON
else {
return XCTFail("Failed mapping Coverage Report JSON")
}

let coverage = JacocoCodeCoverageReport(json: json)

let validBranchRatio = 0.61
let actualBranchRatio = coverage.ratio(of: JacocoCodeCoverageElementType.BranchCoverage)
XCTAssertEqualWithAccuracy(actualBranchRatio, validBranchRatio, accuracy: 0.05)

let validClassRatio = 0.72
let actualClassRatio = coverage.ratio(of: JacocoCodeCoverageElementType.ClassCoverage)
XCTAssertEqualWithAccuracy(actualClassRatio, validClassRatio, accuracy: 0.05)

let validComplexityRatio = 0.59
let actualComplexityRatio = coverage.ratio(of: JacocoCodeCoverageElementType.ComplexityCoverage)
XCTAssertEqualWithAccuracy(actualComplexityRatio, validComplexityRatio, accuracy: 0.05)

let validInstructionRatio = 0.74
let actualInstructionRatio = coverage.ratio(of: JacocoCodeCoverageElementType.InstructionCoverage)
XCTAssertEqualWithAccuracy(actualInstructionRatio, validInstructionRatio, accuracy: 0.05)

let validLineRatio = 0.73
let actualLineRatio = coverage.ratio(of: JacocoCodeCoverageElementType.LineCoverage)
XCTAssertEqualWithAccuracy(actualLineRatio, validLineRatio, accuracy: 0.05)

let validMethodRatio = 0.69
let actualMethodRatio = coverage.ratio(of: JacocoCodeCoverageElementType.MethodCoverage)
XCTAssertEqualWithAccuracy(actualMethodRatio, validMethodRatio, accuracy: 0.05)
}
}
13 changes: 13 additions & 0 deletions JenkinsTests/JenkinsTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// JenkinsTests.swift
// JenkinsTests
//
// Created by Patrick Butkiewicz on 11/13/16.
//
//

import XCTest

class JenkinsTests: XCTestCase {

}
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Jenkins-Swift
---
![Latest](http://img.shields.io/badge/Latest-0.0.3-brightgreen.svg)
![Latest](http://img.shields.io/badge/Latest-0.0.6-brightgreen.svg)
![Swift](http://img.shields.io/badge/swift-3.0-brightgreen.svg)
[![Build Status](https://travis-ci.org/IntrepidPursuits/Jenkins-swift.svg?branch=master)](https://travis-ci.org/IntrepidPursuits/Jenkins-swift)

Expand Down Expand Up @@ -198,6 +198,24 @@ Building a job requires using 1 of 2 methods, depending on whether or not your p
print("Building Job With Paramaters: \(parameters)")
}

___
#### Code Coverage

This client supports retrieving code coverage reports from Jacoco and Cobertura plugins.

jenkins.coberturaCoverage(project, handler: { report in
if let report = report {
print(report.ratio(of: CoberturaCodeCoverageElementType.Lines))
}
})

jenkins.jacocoCoverage(project, handler: { report in
if let report = report {
report.ratio(of: JacocoCodeCoverageElementType.LineCoverage)
}
})


___

## Contributing
Expand Down
1 change: 0 additions & 1 deletion Sources/APIClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

import Foundation

typealias JSON = [String: AnyObject]
private let ApiClientTimeout: TimeInterval = 30

internal enum APIError: Error {
Expand Down
116 changes: 116 additions & 0 deletions Sources/CoberturaCoverage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
//
// Coverage.swift
// Jenkins
//
// Created by Patrick Butkiewicz on 11/13/16.
//
//

import Foundation

public enum CoberturaCodeCoverageElementType: String, CoverageElementType {
case Classes = "Classes"
case Conditionals = "Conditionals"
case Files = "Files"
case Lines = "Lines"
case Packages = "Packages"
case Unknown = "Unknown"

init(_ rawValue: String) {
switch rawValue {
case "Classes": self = .Classes
case "Conditionals": self = .Conditionals
case "Files": self = .Files
case "Lines": self = .Lines
case "Packages": self = .Packages
default: self = .Unknown
}
}
}

public struct CoberturaCodeCoverageElement: CoverageElement {
public var elementType: CoverageElementType = CoberturaCodeCoverageElementType.Unknown
public var covered: Int = 0
public var total: Int = 0
}

public struct CoberturaCodeCoverageReport: CoverageReport {
private(set) var name: String
private(set) var childReports: [CoberturaCodeCoverageReport]
private(set) var coverageElements: [CoberturaCodeCoverageElement]

init(json: JSON) {
name = json["name"] as? String ?? ""

// for each child, init self
let childrenJSON: [JSON] = json["children"] as? [JSON] ?? []
childReports = childrenJSON
.filter({ child in
let elements = child["elements"] as? [JSON] ?? []
return elements.filter({ $0.count > 0 }).count > 0
})
.map({
CoberturaCodeCoverageReport(json: $0)
})

// for each element, init element
let elementJSON: [JSON] = json["elements"] as? [JSON] ?? []
coverageElements = elementJSON.map({ json in
let coverageElementName: String = json["name"] as? String ?? ""
let elementType = CoberturaCodeCoverageElementType(coverageElementName)
let covered = json["numerator"] as? Int ?? 0
let total = json["denominator"] as? Int ?? 0
return CoberturaCodeCoverageElement(elementType: elementType, covered: covered, total: total)
})
}

public func ratio(of element: CoverageElementType) -> Double {
if let e = element as? CoberturaCodeCoverageElementType {
return coverageElements.filter({
return ($0.elementType as? CoberturaCodeCoverageElementType) == e
}).first?.ratio() ?? 0
}
return 0
}
}

/*
* Jenkins Cobertura Extension
*/

extension Jenkins {
public func coberturaCoverage(_ job: String,
build: Int = 0,
depth: Int = 2,
handler: @escaping (_ coverageReport: CoberturaCodeCoverageReport?) -> Void)
{
let buildPath = (build == 0) ? "lastSuccessfulBuild" : String(build)

guard let url: URL = URL(string: jobURL)?
.appendingPathComponent(job)
.appendingPathComponent(buildPath)
.appendingPathComponent("cobertura")
.appendingPathComponent("api")
.appendingPathComponent("json") else {
return handler(nil)
}

let parameters: [String : AnyObject] = ["depth" : depth as AnyObject]
client?.get(path: url, params: parameters) { response, error in
guard let json = response as? JSON,
let results = json["results"] as? JSON else {
return handler(nil)
}

handler(CoberturaCodeCoverageReport(json: results))
}
}

public func coberturaCoverage(_ job: Job,
build: Int = 0,
depth: Int = 2,
handler: @escaping (_ coverageReport: CoberturaCodeCoverageReport?) -> Void)
{
coberturaCoverage(job.name, build: build, depth: depth, handler: handler)
}
}
38 changes: 38 additions & 0 deletions Sources/Coverage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//
// Coverage.swift
// Jenkins
//
// Created by Patrick Butkiewicz on 11/13/16.
//
//

import Foundation

public protocol CoverageReport {
func ratio(of element: CoverageElementType) -> Double
}

/*
* Coverage Element
*/

public protocol CoverageElementType {}

public protocol CoverageElement {
var elementType: CoverageElementType { get }
var covered: Int { get }
var total: Int { get }

func missed() -> Int
func ratio() -> Double
}

extension CoverageElement {
public func missed() -> Int {
return max(0, (total - covered))
}

public func ratio() -> Double {
return fmax(0, (Double(covered) / Double(total)))
}
}
2 changes: 2 additions & 0 deletions Sources/JSON.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import Foundation
typealias JSON = [String: AnyObject]
Loading

0 comments on commit 1af38a0

Please sign in to comment.