diff --git a/Sources/XCLogParser/logmanifest/LogManifest.swift b/Sources/XCLogParser/logmanifest/LogManifest.swift index eafff2e..fa4e7fb 100644 --- a/Sources/XCLogParser/logmanifest/LogManifest.swift +++ b/Sources/XCLogParser/logmanifest/LogManifest.swift @@ -21,7 +21,7 @@ import Foundation /// Parses a LogManifest.plist file. /// That file has a list of the existing Xcode Logs inside a Derived Data's project directory -public struct LogManifest { +public struct LogManifest: Sendable { public init() {} @@ -32,6 +32,7 @@ public struct LogManifest { return try parse(dictionary: logManifestDictionary, atPath: logManifestURL.path) } + // swiftlint:disable function_body_length public func parse(dictionary: NSDictionary, atPath path: String) throws -> [LogManifestEntry] { guard let logs = dictionary["logs"] as? [String: [String: Any]] else { throw LogError.invalidLogManifest("The file at \(path) is not a valid " + @@ -53,9 +54,28 @@ public struct LogManifest { } let startDate = Date(timeIntervalSinceReferenceDate: timeStartedRecording) let endDate = Date(timeIntervalSinceReferenceDate: timeStoppedRecording) - let timestampStart = Int(startDate.timeIntervalSince1970.rounded()) - let timestampEnd = Int(endDate.timeIntervalSince1970.rounded()) - + let timestampStart = startDate.timeIntervalSince1970 + let timestampEnd = endDate.timeIntervalSince1970 + + // Optionally extract statistics if available + let statistics: LogManifestEntryStatistics? = { + guard let primaryObservable = entry.value["primaryObservable"] as? [String: Any], + let totalNumberOfAnalyzerIssues = primaryObservable["totalNumberOfAnalyzerIssues"] as? Int, + let totalNumberOfErrors = primaryObservable["totalNumberOfErrors"] as? Int, + let totalNumberOfWarnings = primaryObservable["totalNumberOfWarnings"] as? Int, + let totalNumberOfTestFailures = primaryObservable["totalNumberOfTestFailures"] as? Int, + let highLevelStatus = primaryObservable["highLevelStatus"] as? String + else { + return nil + } + return LogManifestEntryStatistics( + totalNumberOfErrors: totalNumberOfErrors, + totalNumberOfAnalyzerIssues: totalNumberOfAnalyzerIssues, + highLevelStatus: highLevelStatus, + totalNumberOfTestFailures: totalNumberOfTestFailures, + totalNumberOfWarnings: totalNumberOfWarnings + ) + }() return LogManifestEntry(uniqueIdentifier: uniqueIdentifier, title: title, scheme: scheme, @@ -63,7 +83,9 @@ public struct LogManifest { timestampStart: timestampStart, timestampEnd: timestampEnd, duration: timestampEnd - timestampStart, - type: type) + type: type, + statistics: statistics + ) }.sorted(by: { lhs, rhs -> Bool in return lhs.timestampStart > rhs.timestampStart }) diff --git a/Sources/XCLogParser/logmanifest/LogManifestModel.swift b/Sources/XCLogParser/logmanifest/LogManifestModel.swift index 30194cf..c24f78d 100644 --- a/Sources/XCLogParser/logmanifest/LogManifestModel.swift +++ b/Sources/XCLogParser/logmanifest/LogManifestModel.swift @@ -19,7 +19,7 @@ import Foundation -public enum LogManifestEntryType: String, Encodable { +public enum LogManifestEntryType: String, Encodable, Sendable { case xcode case xcodebuild @@ -37,18 +37,28 @@ public enum LogManifestEntryType: String, Encodable { } } -public struct LogManifestEntry: Encodable { +public struct LogManifestEntry: Encodable, Sendable { public let uniqueIdentifier: String public let title: String public let scheme: String public let fileName: String - public let timestampStart: Int - public let timestampEnd: Int - public let duration: Int + public let timestampStart: TimeInterval + public let timestampEnd: TimeInterval + public let duration: Double public let type: LogManifestEntryType + public let statistics: LogManifestEntryStatistics? - public init(uniqueIdentifier: String, title: String, scheme: String, fileName: String, - timestampStart: Int, timestampEnd: Int, duration: Int, type: LogManifestEntryType) { + public init( + uniqueIdentifier: String, + title: String, + scheme: String, + fileName: String, + timestampStart: TimeInterval, + timestampEnd: TimeInterval, + duration: Double, + type: LogManifestEntryType, + statistics: LogManifestEntryStatistics? + ) { self.uniqueIdentifier = uniqueIdentifier self.title = title self.scheme = scheme @@ -57,6 +67,29 @@ public struct LogManifestEntry: Encodable { self.timestampEnd = timestampEnd self.duration = duration self.type = type + self.statistics = statistics } } + +public struct LogManifestEntryStatistics: Encodable, Sendable { + public let totalNumberOfErrors: Int + public let totalNumberOfAnalyzerIssues: Int + public let highLevelStatus: String + public let totalNumberOfTestFailures: Int + public let totalNumberOfWarnings: Int + + public init( + totalNumberOfErrors: Int, + totalNumberOfAnalyzerIssues: Int, + highLevelStatus: String, + totalNumberOfTestFailures: Int, + totalNumberOfWarnings: Int + ) { + self.totalNumberOfErrors = totalNumberOfErrors + self.totalNumberOfAnalyzerIssues = totalNumberOfAnalyzerIssues + self.highLevelStatus = highLevelStatus + self.totalNumberOfTestFailures = totalNumberOfTestFailures + self.totalNumberOfWarnings = totalNumberOfWarnings + } +} diff --git a/Tests/XCLogParserTests/LogManifestTests.swift b/Tests/XCLogParserTests/LogManifestTests.swift index bbccf04..710a55e 100644 --- a/Tests/XCLogParserTests/LogManifestTests.swift +++ b/Tests/XCLogParserTests/LogManifestTests.swift @@ -45,6 +45,14 @@ class LogManifestTests: XCTestCase { highLevelStatus W +totalNumberOfAnalyzerIssues +0 +totalNumberOfErrors +0 +totalNumberOfTestFailures +0 +totalNumberOfWarnings +2 schemeIdentifier-containerName MyApp @@ -95,7 +103,13 @@ class LogManifestTests: XCTestCase { "documentTypeString": "<nil>", "domainType": "Xcode.IDEActivityLogDomainType.BuildLog", "fileName": "599BC5A8-5E6A-4C16-A71E-A8D6301BAC07.xcactivitylog", - "highLevelStatus": "E", + "primaryObservable": [ + "highLevelStatus": "E", + "totalNumberOfErrors": 1, + "totalNumberOfAnalyzerIssues": 0, + "totalNumberOfTestFailures": 0, + "totalNumberOfWarnings": 2 + ], "schemeIdentifier-containerName": "MyApp project", "schemeIdentifier-schemeName": "MyApp", "schemeIdentifier-sharedScheme": 1, @@ -109,7 +123,13 @@ class LogManifestTests: XCTestCase { "documentTypeString": "<nil>", "domainType": "Xcode.IDEActivityLogDomainType.BuildLog", "fileName": "D1FEAFFA-2E88-4221-9CD2-AB607529381D.xcactivitylog", - "highLevelStatus": "E", + "primaryObservable": [ + "highLevelStatus": "E", + "totalNumberOfErrors": 1, + "totalNumberOfAnalyzerIssues": 0, + "totalNumberOfTestFailures": 0, + "totalNumberOfWarnings": 2 + ], "schemeIdentifier-containerName": "MyApp project", "schemeIdentifier-schemeName": "MyApp", "schemeIdentifier-sharedScheme": 1, @@ -130,11 +150,7 @@ class LogManifestTests: XCTestCase { let startDate = Date(timeIntervalSinceReferenceDate: firstStartedRecording) let endDate = Date(timeIntervalSinceReferenceDate: firstStoppedRecording) - let calendar = Calendar.current - guard let expectedDuration = calendar.dateComponents([.second], from: startDate, to: endDate).second else { - XCTFail("Error creating an expected duration field") - return - } + let expectedDuration = endDate.timeIntervalSince1970 - startDate.timeIntervalSince1970 XCTAssertEqual(expectedDuration, latestLog.duration) }