diff --git a/JenkinsTests/CoberturaCoverageTests.swift b/JenkinsTests/CoberturaCoverageTests.swift new file mode 100644 index 0000000..dacbcc9 --- /dev/null +++ b/JenkinsTests/CoberturaCoverageTests.swift @@ -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) + } + +} + diff --git a/JenkinsTests/Info.plist b/JenkinsTests/Info.plist new file mode 100644 index 0000000..6c6c23c --- /dev/null +++ b/JenkinsTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/JenkinsTests/JSON/CoberturaCoverageReportDepth2.json b/JenkinsTests/JSON/CoberturaCoverageReportDepth2.json new file mode 100644 index 0000000..a4032cc --- /dev/null +++ b/JenkinsTests/JSON/CoberturaCoverageReportDepth2.json @@ -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"}} diff --git a/JenkinsTests/JSON/CoberturaCoverageReportDepth3.json b/JenkinsTests/JSON/CoberturaCoverageReportDepth3.json new file mode 100644 index 0000000..ff85a9a --- /dev/null +++ b/JenkinsTests/JSON/CoberturaCoverageReportDepth3.json @@ -0,0 +1,2 @@ + +{"_class":"hudson.plugins.cobertura.targets.CoverageResult","results":{"children":[{"children":[{"children":[{}],"elements":[{},{},{}],"name":"Pods/Crashlytics/iOS/Crashlytics.framework/Headers/CLSLogging.h"}],"elements":[{"denominator":1.0,"name":"Files","numerator":0.0,"ratio":0.0},{"denominator":1.0,"name":"Classes","numerator":0.0,"ratio":0.0},{"denominator":1.0,"name":"Lines","numerator":0.0,"ratio":0.0},{"denominator":0.0,"name":"Conditionals","numerator":0.0,"ratio":100.0}],"name":"Pods.Crashlytics.iOS.Crashlytics.framework.Headers"},{"children":[{"children":[{}],"elements":[{},{},{}],"name":"Analytics.swift"}],"elements":[{"denominator":1.0,"name":"Files","numerator":1.0,"ratio":100.0},{"denominator":1.0,"name":"Classes","numerator":1.0,"ratio":100.0},{"denominator":215.0,"name":"Lines","numerator":64.0,"ratio":29.767443},{"denominator":0.0,"name":"Conditionals","numerator":0.0,"ratio":100.0}],"name":"bose-connect-ios.Analytics"},{"children":[{"children":[{}],"elements":[{},{},{}],"name":"AppDelegate.swift"},{"children":[{}],"elements":[{},{},{}],"name":"CarouselViewController.swift"},{"children":[{}],"elements":[{},{},{}],"name":"MainCarouselViewController.swift"},{"children":[{}],"elements":[{},{},{}],"name":"MonetNavigationController.swift"},{"children":[{}],"elements":[{},{},{}],"name":"Router.swift"}],"elements":[{"denominator":5.0,"name":"Files","numerator":4.0,"ratio":80.0},{"denominator":5.0,"name":"Classes","numerator":4.0,"ratio":80.0},{"denominator":918.0,"name":"Lines","numerator":383.0,"ratio":41.721134},{"denominator":0.0,"name":"Conditionals","numerator":0.0,"ratio":100.0}],"name":"bose-connect-ios.Controllers"},{"children":[{"children":[{}],"elements":[{},{},{}],"name":"AboutViewController.swift"},{"children":[{}],"elements":[{},{},{}],"name":"TextFilledViewController.swift"}],"elements":[{"denominator":2.0,"name":"Files","numerator":0.0,"ratio":0.0},{"denominator":2.0,"name":"Classes","numerator":0.0,"ratio":0.0},{"denominator":273.0,"name":"Lines","numerator":0.0,"ratio":0.0},{"denominator":0.0,"name":"Conditionals","numerator":0.0,"ratio":100.0}],"name":"bose-connect-ios.Controllers.About"},{"children":[{"children":[{}],"elements":[{},{},{}],"name":"CarouselHelpViewController.swift"},{"children":[{}],"elements":[{},{},{}],"name":"ConnectedDeviceViewController.swift"},{"children":[{}],"elements":[{},{},{}],"name":"ConnectingDeviceViewController.swift"},{"children":[{}],"elements":[{},{},{}],"name":"SearchingViewController.swift"}],"elements":[{"denominator":4.0,"name":"Files","numerator":2.0,"ratio":50.0},{"denominator":4.0,"name":"Classes","numerator":2.0,"ratio":50.0},{"denominator":977.0,"name":"Lines","numerator":39.0,"ratio":3.9918118},{"denominator":0.0,"name":"Conditionals","numerator":0.0,"ratio":100.0}],"name":"bose-connect-ios.Controllers.CarouselSubControllers"},{"children":[{"children":[{}],"elements":[{},{},{}],"name":"FirmwareUpdateAnimationCircle.swift"},{"children":[{}],"elements":[{},{},{}],"name":"FirmwareUpdateController.swift"},{"children":[{}],"elements":[{},{},{}],"name":"ForemanInstructionsViewController.swift"}],"elements":[{"denominator":3.0,"name":"Files","numerator":0.0,"ratio":0.0},{"denominator":3.0,"name":"Classes","numerator":0.0,"ratio":0.0},{"denominator":319.0,"name":"Lines","numerator":0.0,"ratio":0.0},{"denominator":0.0,"name":"Conditionals","numerator":0.0,"ratio":100.0}],"name":"bose-connect-ios.Controllers.FirmwareUpdate"},{"children":[{"children":[{}],"elements":[{},{},{}],"name":"FirmwareUpdateSuccessOverlay.swift"}],"elements":[{"denominator":1.0,"name":"Files","numerator":0.0,"ratio":0.0},{"denominator":1.0,"name":"Classes","numerator":0.0,"ratio":0.0},{"denominator":22.0,"name":"Lines","numerator":0.0,"ratio":0.0},{"denominator":0.0,"name":"Conditionals","numerator":0.0,"ratio":100.0}],"name":"bose-connect-ios.Controllers.FirmwareUpdate.UpdateSuccessOverlay"},{"children":[{"children":[{}],"elements":[{},{},{}],"name":"FullScreenAlertController.swift"}],"elements":[{"denominator":1.0,"name":"Files","numerator":0.0,"ratio":0.0},{"denominator":1.0,"name":"Classes","numerator":0.0,"ratio":0.0},{"denominator":67.0,"name":"Lines","numerator":0.0,"ratio":0.0},{"denominator":0.0,"name":"Conditionals","numerator":0.0,"ratio":100.0}],"name":"bose-connect-ios.Controllers.FullScreenAlert"},{"children":[{"children":[{}],"elements":[{},{},{}],"name":"HeartRateInformationViewController.swift"}],"elements":[{"denominator":1.0,"name":"Files","numerator":0.0,"ratio":0.0},{"denominator":1.0,"name":"Classes","numerator":0.0,"ratio":0.0},{"denominator":222.0,"name":"Lines","numerator":0.0,"ratio":0.0},{"denominator":0.0,"name":"Conditionals","numerator":0.0,"ratio":100.0}],"name":"bose-connect-ios.Controllers.HeartRate"},{"children":[{"children":[{}],"elements":[{},{},{}],"name":"HelpWebviewController.swift"}],"elements":[{"denominator":1.0,"name":"Files","numerator":0.0,"ratio":0.0},{"denominator":1.0,"name":"Classes","numerator":0.0,"ratio":0.0},{"denominator":50.0,"name":"Lines","numerator":0.0,"ratio":0.0},{"denominator":0.0,"name":"Conditionals","numerator":0.0,"ratio":100.0}],"name":"bose-connect-ios.Controllers.Help"},{"children":[{"children":[{}],"elements":[{},{},{}],"name":"MusicShareCarouselViewController.swift"},{"children":[{}],"elements":[{},{},{}],"name":"MusicShareConnectingDeviceViewController.swift"},{"children":[{}],"elements":[{},{},{}],"name":"MusicShareHistoryViewController.swift"},{"children":[{}],"elements":[{},{},{}],"name":"MusicShareOnboardingViewController.swift"}],"elements":[{"denominator":4.0,"name":"Files","numerator":0.0,"ratio":0.0},{"denominator":4.0,"name":"Classes","numerator":0.0,"ratio":0.0},{"denominator":698.0,"name":"Lines","numerator":0.0,"ratio":0.0},{"denominator":0.0,"name":"Conditionals","numerator":0.0,"ratio":100.0}],"name":"bose-connect-ios.Controllers.MusicShare"},{"children":[{"children":[{}],"elements":[{},{},{}],"name":"NowPlayingBar.swift"},{"children":[{}],"elements":[{},{},{}],"name":"NowPlayingModel.swift"},{"children":[{}],"elements":[{},{},{}],"name":"NowPlayingViewController.swift"},{"children":[{}],"elements":[{},{},{}],"name":"PlayPauseButton.swift"}],"elements":[{"denominator":4.0,"name":"Files","numerator":4.0,"ratio":100.0},{"denominator":4.0,"name":"Classes","numerator":4.0,"ratio":100.0},{"denominator":901.0,"name":"Lines","numerator":405.0,"ratio":44.950054},{"denominator":0.0,"name":"Conditionals","numerator":0.0,"ratio":100.0}],"name":"bose-connect-ios.Controllers.NowPlaying"},{"children":[{"children":[{}],"elements":[{},{},{}],"name":"PairingModeViewController.swift"}],"elements":[{"denominator":1.0,"name":"Files","numerator":0.0,"ratio":0.0},{"denominator":1.0,"name":"Classes","numerator":0.0,"ratio":0.0},{"denominator":111.0,"name":"Lines","numerator":0.0,"ratio":0.0},{"denominator":0.0,"name":"Conditionals","numerator":0.0,"ratio":100.0}],"name":"bose-connect-ios.Controllers.PairingMode"},{"children":[{"children":[{}],"elements":[{},{},{}],"name":"ManageDevicesViewController.swift"},{"children":[{}],"elements":[{},{},{}],"name":"ProductDetailsViewController.swift"},{"children":[{}],"elements":[{},{},{}],"name":"RenameHeadsetViewController.swift"},{"children":[{}],"elements":[{},{},{}],"name":"SettingsBaseController.swift"},{"children":[{}],"elements":[{},{},{}],"name":"SettingsViewController.swift"},{"children":[{}],"elements":[{},{},{}],"name":"SleepTimerViewController.swift"},{"children":[{}],"elements":[{},{},{}],"name":"VoiceLanguageViewController.swift"}],"elements":[{"denominator":7.0,"name":"Files","numerator":0.0,"ratio":0.0},{"denominator":7.0,"name":"Classes","numerator":0.0,"ratio":0.0},{"denominator":1163.0,"name":"Lines","numerator":0.0,"ratio":0.0},{"denominator":0.0,"name":"Conditionals","numerator":0.0,"ratio":100.0}],"name":"bose-connect-ios.Controllers.Settings"},{"children":[{"children":[{}],"elements":[{},{},{}],"name":"VideoCompletionController.swift"},{"children":[{}],"elements":[{},{},{}],"name":"VideoPlayerController.swift"}],"elements":[{"denominator":2.0,"name":"Files","numerator":2.0,"ratio":100.0},{"denominator":2.0,"name":"Classes","numerator":2.0,"ratio":100.0},{"denominator":425.0,"name":"Lines","numerator":19.0,"ratio":4.470588},{"denominator":0.0,"name":"Conditionals","numerator":0.0,"ratio":100.0}],"name":"bose-connect-ios.Controllers.VideoPlayer"},{"children":[{"children":[{}],"elements":[{},{},{}],"name":"AllUpdatesReceiverType.swift"},{"children":[{}],"elements":[{},{},{}],"name":"AnyTrue.swift"},{"children":[{}],"elements":[{},{},{}],"name":"Array+Extensions.swift"},{"children":[{}],"elements":[{},{},{}],"name":"BMAP+Extensions.swift"},{"children":[{}],"elements":[{},{},{}],"name":"Eat.swift"},{"children":[{}],"elements":[{},{},{}],"name":"Event.swift"},{"children":[{}],"elements":[{},{},{}],"name":"Geometry+Extensions.swift"},{"children":[{}],"elements":[{},{},{}],"name":"Log.swift"},{"children":[{}],"elements":[{},{},{}],"name":"NSBundle+Extensions.swift"},{"children":[{}],"elements":[{},{},{}],"name":"NSDate+Extensions.swift"},{"children":[{}],"elements":[{},{},{}],"name":"NSMutableAttributedString+Extensions.swift"},{"children":[{}],"elements":[{},{},{}],"name":"String+Extensions.swift"},{"children":[{}],"elements":[{},{},{}],"name":"Theme.swift"},{"children":[{}],"elements":[{},{},{}],"name":"UIButton+Extensions.swift"},{"children":[{}],"elements":[{},{},{}],"name":"UILabel+Extensions.swift"},{"children":[{}],"elements":[{},{},{}],"name":"UILocalNotification+Extensions.swift"},{"children":[{}],"elements":[{},{},{}],"name":"UINavigationController+Extensions.swift"},{"children":[{}],"elements":[{},{},{}],"name":"UITableView+Extensions.swift"},{"children":[{}],"elements":[{},{},{}],"name":"UIView+Extensions.swift"},{"children":[{}],"elements":[{},{},{}],"name":"UIViewController+Extensions.swift"}],"elements":[{"denominator":20.0,"name":"Files","numerator":11.0,"ratio":55.0},{"denominator":20.0,"name":"Classes","numerator":11.0,"ratio":55.0},{"denominator":991.0,"name":"Lines","numerator":463.0,"ratio":46.720486},{"denominator":0.0,"name":"Conditionals","numerator":0.0,"ratio":100.0}],"name":"bose-connect-ios.Extensions"},{"children":[{"children":[{}],"elements":[{},{},{}],"name":"Crashlytics+Extensions.swift"},{"children":[{}],"elements":[{},{},{}],"name":"CrashlyticsBridge.m"}],"elements":[{"denominator":2.0,"name":"Files","numerator":1.0,"ratio":50.0},{"denominator":2.0,"name":"Classes","numerator":1.0,"ratio":50.0},{"denominator":32.0,"name":"Lines","numerator":5.0,"ratio":15.625},{"denominator":0.0,"name":"Conditionals","numerator":0.0,"ratio":100.0}],"name":"bose-connect-ios.Extensions.CrashlyticsLog"},{"children":[{"children":[{}],"elements":[{},{},{}],"name":"Mocks.swift"}],"elements":[{"denominator":1.0,"name":"Files","numerator":1.0,"ratio":100.0},{"denominator":1.0,"name":"Classes","numerator":1.0,"ratio":100.0},{"denominator":180.0,"name":"Lines","numerator":61.0,"ratio":33.88889},{"denominator":0.0,"name":"Conditionals","numerator":0.0,"ratio":100.0}],"name":"bose-connect-ios.Mocks"},{"children":[{"children":[{}],"elements":[{},{},{}],"name":"Debug.swift"},{"children":[{}],"elements":[{},{},{}],"name":"NameGenerator.swift"}],"elements":[{"denominator":2.0,"name":"Files","numerator":2.0,"ratio":100.0},{"denominator":2.0,"name":"Classes","numerator":2.0,"ratio":100.0},{"denominator":47.0,"name":"Lines","numerator":39.0,"ratio":82.97872},{"denominator":0.0,"name":"Conditionals","numerator":0.0,"ratio":100.0}],"name":"bose-connect-ios.Models"},{"children":[{"children":[{}],"elements":[{},{},{}],"name":"LastConnectedProduct.swift"},{"children":[{}],"elements":[{},{},{}],"name":"NotificationPreferences.swift"},{"children":[{}],"elements":[{},{},{}],"name":"PreviouslyMusicShared.swift"},{"children":[{}],"elements":[{},{},{}],"name":"PreviouslyPairedBoseDevices.swift"}],"elements":[{"denominator":4.0,"name":"Files","numerator":3.0,"ratio":75.0},{"denominator":4.0,"name":"Classes","numerator":3.0,"ratio":75.0},{"denominator":182.0,"name":"Lines","numerator":163.0,"ratio":89.56044},{"denominator":0.0,"name":"Conditionals","numerator":0.0,"ratio":100.0}],"name":"bose-connect-ios.Models.UserDefaults"},{"children":[{"children":[{}],"elements":[{},{},{}],"name":"LevelChangeThrottler.swift"}],"elements":[{"denominator":1.0,"name":"Files","numerator":1.0,"ratio":100.0},{"denominator":1.0,"name":"Classes","numerator":1.0,"ratio":100.0},{"denominator":24.0,"name":"Lines","numerator":4.0,"ratio":16.666666},{"denominator":0.0,"name":"Conditionals","numerator":0.0,"ratio":100.0}],"name":"bose-connect-ios.Utilities"},{"children":[{"children":[{}],"elements":[{},{},{}],"name":"HeartRateInformationViewModel.swift"},{"children":[{}],"elements":[{},{},{}],"name":"MusicShareHistoryViewModel.swift"}],"elements":[{"denominator":2.0,"name":"Files","numerator":2.0,"ratio":100.0},{"denominator":2.0,"name":"Classes","numerator":2.0,"ratio":100.0},{"denominator":45.0,"name":"Lines","numerator":33.0,"ratio":73.333336},{"denominator":0.0,"name":"Conditionals","numerator":0.0,"ratio":100.0}],"name":"bose-connect-ios.ViewModels"},{"children":[{"children":[{}],"elements":[{},{},{}],"name":"BoseDeviceViewModel.swift"},{"children":[{}],"elements":[{},{},{}],"name":"CarouselViewModel.swift"},{"children":[{}],"elements":[{},{},{}],"name":"MusicShareBoseDeviceViewModel.swift"},{"children":[{}],"elements":[{},{},{}],"name":"MusicShareCarouselViewModel.swift"},{"children":[{}],"elements":[{},{},{}],"name":"MusicShareOnboardingViewModel.swift"}],"elements":[{"denominator":5.0,"name":"Files","numerator":5.0,"ratio":100.0},{"denominator":5.0,"name":"Classes","numerator":5.0,"ratio":100.0},{"denominator":1246.0,"name":"Lines","numerator":506.0,"ratio":40.60995},{"denominator":0.0,"name":"Conditionals","numerator":0.0,"ratio":100.0}],"name":"bose-connect-ios.ViewModels.Carousel"},{"children":[{"children":[{}],"elements":[{},{},{}],"name":"AboutTableViewCell.swift"},{"children":[{}],"elements":[{},{},{}],"name":"BatteryView.swift"},{"children":[{}],"elements":[{},{},{}],"name":"BoseBMAPVolumeSlider.swift"},{"children":[{}],"elements":[{},{},{}],"name":"BoseNativeVolumeSlider.swift"},{"children":[{}],"elements":[{},{},{}],"name":"ButtonFooter.swift"},{"children":[{}],"elements":[{},{},{}],"name":"ConnectingArrowsView.swift"},{"children":[{}],"elements":[{},{},{}],"name":"DeviceControlsView.swift"},{"children":[{}],"elements":[{},{},{}],"name":"EAPInstructionView.swift"},{"children":[{}],"elements":[{},{},{}],"name":"FirmwareStatusHeaderView.swift"},{"children":[{}],"elements":[{},{},{}],"name":"FirmwareTransferProgressView.swift"},{"children":[{}],"elements":[{},{},{}],"name":"IndependentButton.swift"},{"children":[{}],"elements":[{},{},{}],"name":"IndependentSwitch.swift"},{"children":[{}],"elements":[{},{},{}],"name":"MarginLabel.swift"},{"children":[{}],"elements":[{},{},{}],"name":"MusicShareHistoryTableViewCell.swift"},{"children":[{}],"elements":[{},{},{}],"name":"PagedCarouselView.swift"},{"children":[{}],"elements":[{},{},{}],"name":"PulsingView.swift"},{"children":[{}],"elements":[{},{},{}],"name":"RadioButton.swift"},{"children":[{}],"elements":[{},{},{}],"name":"RemoveButton.swift"},{"children":[{}],"elements":[{},{},{}],"name":"RoundedSolidButton.swift"},{"children":[{}],"elements":[{},{},{}],"name":"SettingsTableViewCell.swift"},{"children":[{}],"elements":[{},{},{}],"name":"SlideUpOverlay.swift"},{"children":[{}],"elements":[{},{},{}],"name":"TappablePuppetMusicShareDeviceView.swift"},{"children":[{}],"elements":[{},{},{}],"name":"Tooltip.swift"},{"children":[{}],"elements":[{},{},{}],"name":"VerticalPanGestureRecognizer.swift"}],"elements":[{"denominator":24.0,"name":"Files","numerator":4.0,"ratio":16.666666},{"denominator":24.0,"name":"Classes","numerator":4.0,"ratio":16.666666},{"denominator":1306.0,"name":"Lines","numerator":148.0,"ratio":11.332313},{"denominator":0.0,"name":"Conditionals","numerator":0.0,"ratio":100.0}],"name":"bose-connect-ios.Views"},{"children":[{"children":[{}],"elements":[{},{},{}],"name":"CNCSliderDeviceContentView.swift"},{"children":[{}],"elements":[{},{},{}],"name":"CNCTooltipView.swift"},{"children":[{}],"elements":[{},{},{}],"name":"CNCViewController.swift"},{"children":[{}],"elements":[{},{},{}],"name":"CircularSlider.swift"},{"children":[{}],"elements":[{},{},{}],"name":"CircularSliderView.swift"}],"elements":[{"denominator":5.0,"name":"Files","numerator":0.0,"ratio":0.0},{"denominator":5.0,"name":"Classes","numerator":0.0,"ratio":0.0},{"denominator":624.0,"name":"Lines","numerator":0.0,"ratio":0.0},{"denominator":0.0,"name":"Conditionals","numerator":0.0,"ratio":100.0}],"name":"bose-connect-ios.Views.CNC"},{"children":[{"children":[{}],"elements":[{},{},{}],"name":"EarbudInstructionCell.swift"},{"children":[{}],"elements":[{},{},{}],"name":"HeartRateAcquiredView.swift"},{"children":[{}],"elements":[{},{},{}],"name":"HeartRateEarbudView.swift"},{"children":[{}],"elements":[{},{},{}],"name":"HeartRateViewController.swift"},{"children":[{}],"elements":[{},{},{}],"name":"VectorLoadingView.swift"}],"elements":[{"denominator":5.0,"name":"Files","numerator":0.0,"ratio":0.0},{"denominator":5.0,"name":"Classes","numerator":0.0,"ratio":0.0},{"denominator":354.0,"name":"Lines","numerator":0.0,"ratio":0.0},{"denominator":0.0,"name":"Conditionals","numerator":0.0,"ratio":100.0}],"name":"bose-connect-ios.Views.HeartRate"},{"children":[{"children":[{}],"elements":[{},{},{}],"name":"MusicShareOboardingOverviewContentView.swift"},{"children":[{}],"elements":[{},{},{}],"name":"MusicShareOnboardGetStartedContentView.swift"}],"elements":[{"denominator":2.0,"name":"Files","numerator":1.0,"ratio":50.0},{"denominator":2.0,"name":"Classes","numerator":1.0,"ratio":50.0},{"denominator":88.0,"name":"Lines","numerator":30.0,"ratio":34.090908},{"denominator":0.0,"name":"Conditionals","numerator":0.0,"ratio":100.0}],"name":"bose-connect-ios.Views.MusicShareOnboarding"}],"elements":[{"denominator":27.0,"name":"Packages","numerator":15.0,"ratio":55.555557},{"denominator":111.0,"name":"Files","numerator":44.0,"ratio":39.63964},{"denominator":111.0,"name":"Classes","numerator":44.0,"ratio":39.63964},{"denominator":11481.0,"name":"Lines","numerator":2362.0,"ratio":20.57312},{"denominator":0.0,"name":"Conditionals","numerator":0.0,"ratio":100.0}],"name":"Cobertura Coverage Report"}} diff --git a/JenkinsTests/JSON/JacocoCoverageReportDepth.json b/JenkinsTests/JSON/JacocoCoverageReportDepth.json new file mode 100644 index 0000000..716b2d6 --- /dev/null +++ b/JenkinsTests/JSON/JacocoCoverageReportDepth.json @@ -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"}} diff --git a/JenkinsTests/JacocoCoverageTests.swift b/JenkinsTests/JacocoCoverageTests.swift new file mode 100644 index 0000000..f03ac44 --- /dev/null +++ b/JenkinsTests/JacocoCoverageTests.swift @@ -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) + } +} diff --git a/JenkinsTests/JenkinsTests.swift b/JenkinsTests/JenkinsTests.swift new file mode 100644 index 0000000..bc19845 --- /dev/null +++ b/JenkinsTests/JenkinsTests.swift @@ -0,0 +1,13 @@ +// +// JenkinsTests.swift +// JenkinsTests +// +// Created by Patrick Butkiewicz on 11/13/16. +// +// + +import XCTest + +class JenkinsTests: XCTestCase { + +} diff --git a/README.md b/README.md index e5f8c83..10ec900 100644 --- a/README.md +++ b/README.md @@ -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) @@ -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 diff --git a/Sources/APIClient.swift b/Sources/APIClient.swift index a0232d8..1dc74c4 100644 --- a/Sources/APIClient.swift +++ b/Sources/APIClient.swift @@ -8,7 +8,6 @@ import Foundation -typealias JSON = [String: AnyObject] private let ApiClientTimeout: TimeInterval = 30 internal enum APIError: Error { diff --git a/Sources/CoberturaCoverage.swift b/Sources/CoberturaCoverage.swift new file mode 100644 index 0000000..f2414c4 --- /dev/null +++ b/Sources/CoberturaCoverage.swift @@ -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) + } +} diff --git a/Sources/Coverage.swift b/Sources/Coverage.swift new file mode 100644 index 0000000..2ed73e7 --- /dev/null +++ b/Sources/Coverage.swift @@ -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))) + } +} diff --git a/Sources/JSON.swift b/Sources/JSON.swift new file mode 100644 index 0000000..1fdacaf --- /dev/null +++ b/Sources/JSON.swift @@ -0,0 +1,2 @@ +import Foundation +typealias JSON = [String: AnyObject] diff --git a/Sources/JacacoCoverage.swift b/Sources/JacacoCoverage.swift new file mode 100644 index 0000000..b818672 --- /dev/null +++ b/Sources/JacacoCoverage.swift @@ -0,0 +1,107 @@ +// +// JacacoCoverage.swift +// Jenkins +// +// Created by Patrick Butkiewicz on 11/13/16. +// +// + +import Foundation + +public enum JacocoCodeCoverageElementType: CoverageElementType { + case BranchCoverage + case ClassCoverage + case ComplexityCoverage + case InstructionCoverage + case LineCoverage + case MethodCoverage + case Unknown + + init(_ rawValue: String) { + switch rawValue { + case "branchCoverage": self = .BranchCoverage + case "classCoverage": self = .ClassCoverage + case "complexityScore": self = .ComplexityCoverage + case "instructionCoverage": self = .InstructionCoverage + case "lineCoverage": self = .LineCoverage + case "methodCoverage": self = .MethodCoverage + default: self = .Unknown + } + } +} + +public struct JacocoCodeCoverageElement: CoverageElement { + public var elementType: CoverageElementType + public var covered: Int + public var total: Int +} + +public struct JacocoCodeCoverageReport { + private(set) var name: String + private(set) var coverageElements: [JacocoCodeCoverageElement] + + init(json: JSON) { + let elementCoveredKey = "covered" + let elementTotalKey = "total" + + name = json["_class"] as? String ?? "Jacoco Report" + var elements: [JacocoCodeCoverageElement] = [] + + for (key, coverage) in json { + let elemType = JacocoCodeCoverageElementType(key) + if elemType != .Unknown { + let covered = coverage[elementCoveredKey] as? Int ?? 0 + let total = coverage[elementTotalKey] as? Int ?? 0 + let elem = JacocoCodeCoverageElement(elementType: elemType, covered: covered, total: total) + elements.append(elem) + } + } + + self.coverageElements = elements + } + + public func ratio(of element: CoverageElementType) -> Double { + if let e = element as? JacocoCodeCoverageElementType { + return coverageElements.filter({$0.elementType as? JacocoCodeCoverageElementType == e}).first?.ratio() ?? 0 + } + return 0 + } +} + +/* + * Jenkins Cobertura Extension + */ + +extension Jenkins { + public func jacocoCoverage(_ job: String, + build: Int = 0, + handler: @escaping (_ coverageReport: JacocoCodeCoverageReport?) -> Void) + { + let buildPath = (build == 0) ? "lastSuccessfulBuild" : String(build) + + guard let url: URL = URL(string: jobURL)? + .appendingPathComponent(job) + .appendingPathComponent(buildPath) + .appendingPathComponent("jacoco") + .appendingPathComponent("api") + .appendingPathComponent("json") else { + return handler(nil) + } + + client?.get(path: url) { response, error in + guard let json = response as? JSON else { + return handler(nil) + } + + handler(JacocoCodeCoverageReport(json: json)) + } + } + + public func jacocoCoverage(_ job: Job, + build: Int = 0, + depth: Int = 0, + handler: @escaping (_ coverageReport: JacocoCodeCoverageReport?) -> Void) + { + jacocoCoverage(job.name, build: build, handler: handler) + } +} diff --git a/Tests/Jenkins/JenkinsTests.swift b/Tests/Jenkins/JenkinsTests.swift deleted file mode 100644 index 2326ff4..0000000 --- a/Tests/Jenkins/JenkinsTests.swift +++ /dev/null @@ -1,17 +0,0 @@ -import XCTest -@testable import Jenkins - -class JenkinsTests: XCTestCase { - func testExample() { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct results. - XCTAssertEqual(Jenkins().text, "Hello, World!") - } - - - static var allTests : [(String, (JenkinsTests) -> () throws -> Void)] { - return [ - ("testExample", testExample), - ] - } -} diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift deleted file mode 100644 index 27aa45f..0000000 --- a/Tests/LinuxMain.swift +++ /dev/null @@ -1,6 +0,0 @@ -import XCTest -@testable import JenkinsTestSuite - -XCTMain([ - testCase(JenkinsTests.allTests), -])