Repository: cpisciotta/xcbeautify
Pull request #242 (2024-01-17 10:36:06)

Teamcity renderer
Added renderer for TeamCity CI. It improves showing warnings and errors.

The main reason is showing developers compilation errors on build page instead of searching error from the very long output log.

I’ve copied the TerminalRenderer, because this renderer is good and shows information very well. 
Just added additional strings to logs according to the TeamCity documentation to show compilation errors and highlight warnings

Before: 
![image](https://github.com/cpisciotta/xcbeautify/assets/1080044/13a5ffa9-7419-4a1f-875a-24a624919e81)

After:
![image](https://github.com/cpisciotta/xcbeautify/assets/1080044/4b4e6420-6c3b-4800-8ab5-b900477fd75a)


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/cpisciotta/xcbeautify/pull/242?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Charles+Pisciotta) Report
Attention: `228 lines` in your changes are missing coverage. Please review.
> Comparison is base [(`5df94fd`)](https://app.codecov.io/gh/cpisciotta/xcbeautify/commit/5df94fdea5d71c0f51fabda2ee4931a82f85dd6b?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Charles+Pisciotta) 85.83% compared to head [(`c4f12b8`)](https://app.codecov.io/gh/cpisciotta/xcbeautify/pull/242?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Charles+Pisciotta) 69.43%.

| [Files](https://app.codecov.io/gh/cpisciotta/xcbeautify/pull/242?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Charles+Pisciotta) | Patch % | Lines |
|---|---|---|
| [...ces/XcbeautifyLib/Renderers/TeamCityRenderer.swift](https://app.codecov.io/gh/cpisciotta/xcbeautify/pull/242?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Charles+Pisciotta#diff-U291cmNlcy9YY2JlYXV0aWZ5TGliL1JlbmRlcmVycy9UZWFtQ2l0eVJlbmRlcmVyLnN3aWZ0) | 0.00% | [216 Missing :warning: ](https://app.codecov.io/gh/cpisciotta/xcbeautify/pull/242?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Charles+Pisciotta) |
| [...ources/XcbeautifyLib/String+TeamCityEscaping.swift](https://app.codecov.io/gh/cpisciotta/xcbeautify/pull/242?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Charles+Pisciotta#diff-U291cmNlcy9YY2JlYXV0aWZ5TGliL1N0cmluZytUZWFtQ2l0eUVzY2FwaW5nLnN3aWZ0) | 0.00% | [11 Missing :warning: ](https://app.codecov.io/gh/cpisciotta/xcbeautify/pull/242?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Charles+Pisciotta) |
| [Sources/XcbeautifyLib/Parser.swift](https://app.codecov.io/gh/cpisciotta/xcbeautify/pull/242?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Charles+Pisciotta#diff-U291cmNlcy9YY2JlYXV0aWZ5TGliL1BhcnNlci5zd2lmdA==) | 50.00% | [1 Missing :warning: ](https://app.codecov.io/gh/cpisciotta/xcbeautify/pull/242?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Charles+Pisciotta) |

<details><summary>Additional details and impacted files</summary>


```diff
@@             Coverage Diff             @@
##             main     #242       +/-   ##
===========================================
- Coverage   85.83%   69.43%   -16.40%     
===========================================
  Files          13       15        +2     
  Lines        1842     2071      +229     
===========================================
- Hits         1581     1438      -143     
- Misses        261      633      +372     
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/cpisciotta/xcbeautify/pull/242?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Charles+Pisciotta).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Charles+Pisciotta).


Patch:
diff --git a/README.md b/README.md
index 42a66a9..211a9b4 100644
--- a/README.md
+++ b/README.md
@@ -111,6 +111,14 @@ swift test [flags] 2>&1 | xcbeautify
 set -o pipefail && xcodebuild [flags] | xcbeautify --renderer github-actions
 ```
 
+### TeamCity
+
+`xcbeautify` features an integrated TeamCity renderer that harnesses [service messages](https://www.jetbrains.com/help/teamcity/service-messages.html) to highlight warnings, errors, and results directly within the TeamCity user interface. To utilize this function, simply run `xcbeautify` and add the `--renderer teamcity` flag during execution:
+
+```
+set -o pipefail && xcodebuild [flags] | xcbeautify --renderer teamcity
+```
+
 ## Development
 
 Generate Xcode project:
diff --git a/Sources/XcbeautifyLib/Constants.swift b/Sources/XcbeautifyLib/Constants.swift
index 298c559..3fb9bb7 100644
--- a/Sources/XcbeautifyLib/Constants.swift
+++ b/Sources/XcbeautifyLib/Constants.swift
@@ -39,4 +39,7 @@ public enum Renderer: String {
 
     /// Formats output suitable for GitHub Actions annotations. Maps to `GitHubRenderer`.
     case gitHubActions = "github-actions"
+
+    /// Formats output suitable for TeamCity service messages. Maps to `TeamCityRenderer`.
+    case teamcity
 }
diff --git a/Sources/XcbeautifyLib/Parser.swift b/Sources/XcbeautifyLib/Parser.swift
index d197572..5402cc4 100644
--- a/Sources/XcbeautifyLib/Parser.swift
+++ b/Sources/XcbeautifyLib/Parser.swift
@@ -117,6 +117,8 @@ package class Parser {
             self.renderer = TerminalRenderer(colored: colored, additionalLines: additionalLines)
         case .gitHubActions:
             self.renderer = GitHubActionsRenderer(additionalLines: additionalLines)
+        case .teamcity:
+            self.renderer = TeamCityRenderer(colored: colored, additionalLines: additionalLines)
         }
 
         self.preserveUnbeautifiedLines = preserveUnbeautifiedLines
diff --git a/Sources/XcbeautifyLib/Renderers/TeamCityRenderer.swift b/Sources/XcbeautifyLib/Renderers/TeamCityRenderer.swift
new file mode 100644
index 0000000..03de035
--- /dev/null
+++ b/Sources/XcbeautifyLib/Renderers/TeamCityRenderer.swift
@@ -0,0 +1,165 @@
+import Colorizer
+import Foundation
+
+struct TeamCityRenderer: OutputRendering {
+    let colored: Bool
+    let additionalLines: () -> String?
+
+    init(colored: Bool, additionalLines: @escaping () -> String?) {
+        self.colored = colored
+        self.additionalLines = additionalLines
+    }
+
+    private func outputTeamCityProblem(text: String, filePath: String) -> String {
+        """
+        ##teamcity[buildProblem description='\(filePath)']
+        \(text)
+        """
+    }
+
+    private func outputTeamCityError(text: String, details: String) -> String {
+        """
+        ##teamcity[message text='\(text)' errorDetails='\(details.teamCityEscaped())' status='ERROR']
+        \(text)
+        """
+    }
+
+    private func outputTeamCityWarning(text: String, details: String) -> String {
+        """
+        ##teamcity[message text='\([text, details.teamCityEscaped()].joined(separator: "|n"))' status='WARNING']
+        \(text)
+        """
+    }
+
+    func formatError(group: ErrorCaptureGroup) -> String {
+        let errorMessage = group.wholeError
+        let outputString = colored ? Symbol.error + " " + errorMessage.f.Red : Symbol.asciiError + " " + errorMessage
+
+        return outputTeamCityError(text: "Build error", details: outputString)
+    }
+
+    func formatCompileError(group: CompileErrorCaptureGroup) -> String {
+        let filePath = group.filePath
+        let reason = group.reason
+
+        // Read 2 additional lines to get the error line and cursor position
+        let line: String = additionalLines() ?? ""
+        let cursor: String = additionalLines() ?? ""
+        let outputString = colored ?
+            """
+            \(Symbol.error) \(filePath): \(reason.f.Red)
+            \(line)
+            \(cursor.f.Cyan)
+            """
+            :
+            """
+            \(Symbol.asciiError) \(filePath): \(reason)
+            \(line)
+            \(cursor)
+            """
+
+        return outputTeamCityProblem(text: outputString, filePath: filePath)
+    }
+
+    func formatFileMissingError(group: FileMissingErrorCaptureGroup) -> String {
+        let reason = group.reason
+        let filePath = group.filePath
+        return outputTeamCityError(
+            text: "File missing error",
+            details: colored ? "\(Symbol.error) \(filePath): \(reason.f.Red)" : "\(Symbol.asciiError) \(filePath): \(reason)"
+        )
+    }
+
+    func formatWarning(group: GenericWarningCaptureGroup) -> String {
+        let warningMessage = group.wholeWarning
+        return outputTeamCityWarning(
+            text: "Xcodebuild warning",
+            details: colored ? Symbol.warning + " " + warningMessage.f.Yellow : Symbol.asciiWarning + " " + warningMessage
+        )
+    }
+
+    func formatUndefinedSymbolLocation(group: UndefinedSymbolLocationCaptureGroup) -> String {
+        outputTeamCityWarning(
+            text: "Undefined symbol location",
+            details: colored ? Symbol.warning + " " + group.wholeWarning.f.Yellow : Symbol.asciiWarning + " " + group.wholeWarning
+        )
+    }
+
+    func formatCompileWarning(group: CompileWarningCaptureGroup) -> String {
+        let filePath = group.filePath
+        let reason = group.reason
+
+        // Read 2 additional lines to get the warning line and cursor position
+        let line: String = additionalLines() ?? ""
+        let cursor: String = additionalLines() ?? ""
+        let outputString = colored ?
+            """
+            \(Symbol.warning)  \(filePath): \(reason.f.Yellow)
+            \(line)
+            \(cursor.f.Green)
+            """
+            :
+            """
+            \(Symbol.asciiWarning)  \(filePath): \(reason)
+            \(line)
+            \(cursor)
+            """
+
+        return outputTeamCityWarning(text: "Compile warning", details: outputString)
+    }
+
+    func formatLdWarning(group: LDWarningCaptureGroup) -> String {
+        let prefix = group.ldPrefix
+        let message = group.warningMessage
+        return outputTeamCityWarning(
+            text: "Linker warning",
+            details: colored ? "\(Symbol.warning) \(prefix.f.Yellow)\(message.f.Yellow)" : "\(Symbol.asciiWarning) \(prefix)\(message)"
+        )
+    }
+
+    func formatLinkerUndefinedSymbolsError(group: LinkerUndefinedSymbolsCaptureGroup) -> String {
+        let reason = group.reason
+        return outputTeamCityWarning(
+            text: "Linker error. Undefined symbols error",
+            details: colored ? "\(Symbol.error) \(reason.f.Red)" : "\(Symbol.asciiError) \(reason)"
+        )
+    }
+
+    // TODO: Print file path
+    func formatLinkerDuplicateSymbolsError(group: LinkerDuplicateSymbolsCaptureGroup) -> String {
+        let reason = group.reason
+        return outputTeamCityError(
+            text: "Linker error. Duplicated symbols",
+            details: colored ? "\(Symbol.error) \(reason.f.Red)" : "\(Symbol.asciiError) \(reason)"
+        )
+    }
+
+    func formatWillNotBeCodesignWarning(group: WillNotBeCodeSignedCaptureGroup) -> String {
+        let warningMessage = group.wholeWarning
+        return outputTeamCityWarning(
+            text: "Codesign error",
+            details: colored ? Symbol.warning + " " + warningMessage.f.Yellow : Symbol.asciiWarning + " " + warningMessage
+        )
+    }
+
+    func formatDuplicateLocalizedStringKey(group: DuplicateLocalizedStringKeyCaptureGroup) -> String {
+        let message = group.warningMessage
+        return outputTeamCityWarning(
+            text: "Duplicated localized string key",
+            details: colored ? Symbol.warning + " " + message.f.Yellow : Symbol.asciiWarning + " " + message
+        )
+    }
+}
+
+private extension String {
+    func teamCityEscaped() -> String {
+        // According to the documentation: https://www.jetbrains.com/help/teamcity/service-messages.html#Escaped+Values
+        replacingOccurrences(of: "|", with: "||")
+            .replacingOccurrences(of: "'", with: "|'")
+            .replacingOccurrences(of: "\n", with: "|n")
+            .replacingOccurrences(of: "\r", with: "|r")
+            .replacingOccurrences(of: "\\u{", with: "|u{") // Assuming the unicode format in Swift is \\u{NNNN}
+            .replacingOccurrences(of: "[", with: "|[")
+            .replacingOccurrences(of: "]", with: "|]")
+    }
+}
diff --git a/Sources/xcbeautify/Xcbeautify.swift b/Sources/xcbeautify/Xcbeautify.swift
index e08eae1..bae2867 100644
--- a/Sources/xcbeautify/Xcbeautify.swift
+++ b/Sources/xcbeautify/Xcbeautify.swift
@@ -23,8 +23,20 @@ struct Xcbeautify: ParsableCommand {
     @Flag(name: .long, help: "Disable the colored output")
     var disableColoredOutput = (ProcessInfo.processInfo.environment["NO_COLOR"] != nil)
 
-    @Option(help: "Specify a renderer to format raw xcodebuild output ( options: terminal | github-actions ).")
-    var renderer: Renderer = ProcessInfo.processInfo.environment["GITHUB_ACTIONS"] == "true" ? .gitHubActions : .terminal
+    // swiftformat:disable redundantReturn
+
+    @Option(help: "Specify a renderer to format raw xcodebuild output ( options: terminal | github-actions | teamcity ).")
+    var renderer: Renderer = {
+        if ProcessInfo.processInfo.environment["GITHUB_ACTIONS"] == "true" {
+            return .gitHubActions
+        } else if ProcessInfo.processInfo.environment["TEAMCITY_VERSION"] != nil {
+            return .teamcity
+        } else {
+            return .terminal
+        }
+    }()
+
+    // swiftformat:enable redundantReturn
 
     @Option(help: "Generate the specified reports")
     var report: [Report] = []


Test patch:
diff --git a/Tests/XcbeautifyLibTests/RendererTests/TeamCityRendererTests.swift b/Tests/XcbeautifyLibTests/RendererTests/TeamCityRendererTests.swift
new file mode 100644
index 0000000..6a276aa
--- /dev/null
+++ b/Tests/XcbeautifyLibTests/RendererTests/TeamCityRendererTests.swift
@@ -0,0 +1,676 @@
+import XCTest
+@testable import XcbeautifyLib
+
+final class TeamCityRendererTests: XCTestCase {
+    var parser: Parser!
+
+    override func setUpWithError() throws {
+        try super.setUpWithError()
+        parser = Parser(colored: false, renderer: .teamcity, additionalLines: { nil })
+    }
+
+    private func noColoredFormatted(_ string: String) -> String? {
+        parser.parse(line: string)
+    }
+
+    func testAggregateTarget() {
+        let formatted = noColoredFormatted("=== BUILD AGGREGATE TARGET Be Aggro OF PROJECT AggregateExample WITH CONFIGURATION Debug ===")
+        XCTAssertEqual(formatted, "Aggregate target Be Aggro of project AggregateExample with configuration Debug")
+    }
+
+    func testAnalyze() {
+        let formatted = noColoredFormatted("AnalyzeShallow /Users/admin/CocoaLumberjack/Classes/DDTTYLogger.m normal x86_64 (in target: CocoaLumberjack-Static)")
+        XCTAssertEqual(formatted, "[CocoaLumberjack-Static] Analyzing DDTTYLogger.m")
+    }
+
+    func testAnalyzeTarget() {
+        let formatted = noColoredFormatted("=== ANALYZE TARGET The Spacer OF PROJECT Pods WITH THE DEFAULT CONFIGURATION Debug ===")
+        XCTAssertEqual(formatted, "Analyze target The Spacer of project Pods with configuration Debug")
+    }
+
+    func testBuildTarget() {
+        let formatted = noColoredFormatted("=== BUILD TARGET The Spacer OF PROJECT Pods WITH THE DEFAULT CONFIGURATION Debug ===")
+        XCTAssertEqual(formatted, "Build target The Spacer of project Pods with configuration Debug")
+    }
+
+    func testCheckDependenciesErrors() { }
+
+    func testCheckDependencies() {
+        let command = "Check Dependencies"
+        let formatted = noColoredFormatted(command)
+        XCTAssertEqual(formatted, command)
+    }
+
+    func testClangError() {
+        let formatted = noColoredFormatted("clang: error: linker command failed with exit code 1 (use -v to see invocation)")
+        XCTAssertEqual(
+            formatted,
+            "##teamcity[message text=\'Build error\' errorDetails=\'|[x|] clang: error: linker command failed with exit code 1 (use -v to see invocation)\' status=\'ERROR\']\nBuild error"
+        )
+    }
+
+    func testCleanRemove() {
+        let formatted = noColoredFormatted("Clean.Remove clean /Users/admin/Library/Developer/Xcode/DerivedData/MyLibrary-abcd/Build/Intermediates/MyLibrary.build/Debug-iphonesimulator/MyLibraryTests.build")
+        XCTAssertEqual(formatted, "Cleaning MyLibraryTests.build")
+    }
+
+    func testCleanTarget() {
+        let formatted = noColoredFormatted("=== ANALYZE TARGET The Spacer OF PROJECT Pods WITH THE DEFAULT CONFIGURATION Debug ===")
+        XCTAssertEqual(formatted, "Analyze target The Spacer of project Pods with configuration Debug")
+    }
+
+    func testCodesignFramework() {
+        let formatted = noColoredFormatted("CodeSign build/Release/MyFramework.framework/Versions/A")
+        XCTAssertEqual(formatted, "Signing build/Release/MyFramework.framework")
+    }
+
+    func testCodesign() {
+        let formatted = noColoredFormatted("CodeSign build/Release/MyApp.app")
+        XCTAssertEqual(formatted, "Signing MyApp.app")
+    }
+
+    func testMultipleCodesigns() {
+        let formattedApp = noColoredFormatted("CodeSign build/Release/MyApp.app (in target 'X' from project 'Y' at path 'Z')")
+        let formattedFramework = noColoredFormatted("CodeSign build/Release/MyFramework.framework/Versions/A (in target 'X' from project 'Y')")
+        XCTAssertEqual(formattedApp, "Signing MyApp.app")
+        XCTAssertEqual(formattedFramework, "Signing build/Release/MyFramework.framework")
+    }
+
+    func testCompileCommand() { }
+
+    func testCompileError() {
+        let inputError = "/path/file.swift:64:69: error: cannot find 'input' in scope"
+        let outputError = "##teamcity[buildProblem description=\'/path/file.swift:64:69\']\n[x] /path/file.swift:64:69: cannot find \'input\' in scope\n\n"
+        XCTAssertEqual(noColoredFormatted(inputError), outputError)
+
+        let inputFatal = "/path/file.swift:64:69: fatal error: cannot find 'input' in scope"
+        let outputFatal = "##teamcity[buildProblem description=\'/path/file.swift:64:69\']\n[x] /path/file.swift:64:69: cannot find \'input\' in scope\n\n"
+        XCTAssertEqual(noColoredFormatted(inputFatal), outputFatal)
+    }
+
+    func testCompile() {
+        #if os(macOS)
+        // Xcode 10 and before
+        let input1 = "CompileSwift normal x86_64 /Users/admin/dev/Swifttrain/xcbeautify/Sources/xcbeautify/setup.swift (in target: xcbeautify)"
+        // Xcode 11+'s output
+        let input2 = "CompileSwift normal x86_64 /Users/admin/dev/Swifttrain/xcbeautify/Sources/xcbeautify/setup.swift (in target 'xcbeautify' from project 'xcbeautify')"
+        let output = "[xcbeautify] Compiling setup.swift"
+        XCTAssertEqual(noColoredFormatted(input1), output)
+        XCTAssertEqual(noColoredFormatted(input2), output)
+        #endif
+    }
+
+    func testSwiftCompile_arm64() {
+        let input = "SwiftCompile normal arm64 /path/to/File.swift (in target 'Target' from project 'Project')"
+        let output = "[Target] Compiling File.swift"
+        XCTAssertEqual(noColoredFormatted(input), output)
+    }
+
+    func testSwiftCompile_x86_64() {
+        let input = "SwiftCompile normal x86_64 /Backyard-Birds/Build/Intermediates.noindex/BackyardBirdsData.build/Debug/BackyardBirdsData.build/DerivedSources/resource_bundle_accessor.swift (in target 'BackyardBirdsData' from project 'BackyardBirdsData')"
+        let output = "[BackyardBirdsData] Compiling resource_bundle_accessor.swift"
+        XCTAssertEqual(noColoredFormatted(input), output)
+    }
+
+    func testSwiftCompiling() {
+        let input = #"SwiftCompile normal x86_64 Compiling\ BackyardBirdsDataContainer.swift,\ ColorData.swift,\ DataGeneration.swift,\ DataGenerationOptions.swift /Backyard-Birds/BackyardBirdsData/General/BackyardBirdsDataContainer.swift /Backyard-Birds/BackyardBirdsData/General/ColorData.swift /Backyard-Birds/BackyardBirdsData/General/DataGeneration.swift /Backyard-Birds/BackyardBirdsData/General/DataGenerationOptions.swift (in target 'BackyardBirdsData' from project 'BackyardBirdsData')"#
+        XCTAssertNil(noColoredFormatted(input))
+    }
+
+    func testCompileStoryboard() {
+        let formatted = noColoredFormatted("CompileStoryboard /Users/admin/MyApp/MyApp/Main.storyboard (in target: MyApp)")
+        XCTAssertEqual(formatted, "[MyApp] Compiling Main.storyboard")
+    }
+
+    func testCompileWarning() {
+        let input = "/path/file.swift:64:69: warning: 'flatMap' is deprecated: Please use compactMap(_:) for the case where closure returns an optional value"
+        let output = "##teamcity[message text=\'Compile warning|n|[!|]  /path/file.swift:64:69: |\'flatMap|\' is deprecated: Please use compactMap(_:) for the case where closure returns an optional value|n|n\' status=\'WARNING\']\nCompile warning"
+        XCTAssertEqual(noColoredFormatted(input), output)
+    }
+
+    func testCompileXib() {
+        let input = "CompileXIB /path/file.xib (in target 'MyApp' from project 'MyProject')"
+        let output = "[MyApp] Compiling file.xib"
+        XCTAssertEqual(noColoredFormatted(input), output)
+    }
+
+    func testCopyHeader() {
+        let input = "CpHeader /path/to/destination/file.h /path/file.h (in target 'MyApp' from project 'MyProject')"
+        let output = "[MyApp] Copying file.h"
+        XCTAssertEqual(noColoredFormatted(input), output)
+    }
+
+    func testCopyPlist() {
+        let input = "CopyPlistFile /path/to/destination/file.plist /path/file.plist (in target 'MyApp' from project 'MyProject')"
+        let output = "[MyApp] Copying file.plist"
+        XCTAssertEqual(noColoredFormatted(input), output)
+    }
+
+    func testCopyStrings() {
+        let input = "CopyStringsFile /path/to/destination/file.strings /path/file.strings (in target 'MyApp' from project 'MyProject')"
+        let output = "[MyApp] Copying file.strings"
+        XCTAssertEqual(noColoredFormatted(input), output)
+    }
+
+    func testCpresource() {
+        let input = "CpResource /path/to/destination/file.ttf /path/file.ttf (in target 'MyApp' from project 'MyProject')"
+        let output = "[MyApp] Copying file.ttf"
+        XCTAssertEqual(noColoredFormatted(input), output)
+    }
+
+    func testCursor() { }
+
+    func testExecutedWithoutSkipped() throws {
+        let input1 = "Test Suite 'All tests' failed at 2022-01-15 21:31:49.073."
+        let input2 = "Executed 3 tests, with 2 failures (1 unexpected) in 0.112 (0.112) seconds"
+
+        let input3 = "Test Suite 'All tests' passed at 2022-01-15 21:33:49.073."
+        let input4 = "Executed 1 test, with 1 failure (1 unexpected) in 0.200 (0.200) seconds"
+
+        // First test plan
+        XCTAssertNil(parser.summary)
+        XCTAssertFalse(parser.needToRecordSummary)
+        let formatted1 = noColoredFormatted(input1)
+        #if os(macOS)
+        // FIXME: Failing on Linux
+        XCTAssertTrue(parser.needToRecordSummary)
+        #endif
+        let formatted2 = noColoredFormatted(input2)
+        XCTAssertFalse(parser.needToRecordSummary)
+        XCTAssertNil(formatted1)
+        XCTAssertNil(formatted2)
+
+        #if os(macOS)
+        // FIXME: Failing on Linux
+        var summary = try XCTUnwrap(parser.summary)
+
+        XCTAssertEqual(summary.testsCount, 3)
+        XCTAssertEqual(summary.failuresCount, 2)
+        XCTAssertEqual(summary.unexpectedCount, 1)
+        XCTAssertEqual(summary.skippedCount, 0)
+        XCTAssertEqual(summary.time, 0.112)
+
+        // Second test plan
+        XCTAssertNotNil(parser.summary)
+        XCTAssertFalse(parser.needToRecordSummary)
+        let formatted3 = noColoredFormatted(input3)
+        XCTAssertTrue(parser.needToRecordSummary)
+        let formatted4 = noColoredFormatted(input4)
+        XCTAssertFalse(parser.needToRecordSummary)
+        XCTAssertNil(formatted3)
+        XCTAssertNil(formatted4)
+
+        summary = try XCTUnwrap(parser.summary)
+
+        XCTAssertEqual(summary.testsCount, 4)
+        XCTAssertEqual(summary.failuresCount, 3)
+        XCTAssertEqual(summary.unexpectedCount, 2)
+        XCTAssertEqual(summary.skippedCount, 0)
+        XCTAssertEqual(summary.time, 0.312)
+        #endif
+    }
+
+    #if os(macOS)
+    func testExecutedWithSkipped() {
+        let input1 = "Test Suite 'All tests' failed at 2022-01-15 21:31:49.073."
+        let input2 = "Executed 56 tests, with 3 test skipped and 2 failures (1 unexpected) in 1.029 (1.029) seconds"
+
+        let input3 = "Test Suite 'All tests' passed at 2022-01-15 21:33:49.073."
+        let input4 = "Executed 1 test, with 1 test skipped and 1 failure (1 unexpected) in 3.000 (3.000) seconds"
+
+        // First test plan
+        XCTAssertNil(parser.summary)
+        XCTAssertFalse(parser.needToRecordSummary)
+        let formatted1 = noColoredFormatted(input1)
+        XCTAssertTrue(parser.needToRecordSummary)
+        let formatted2 = noColoredFormatted(input2)
+        XCTAssertFalse(parser.needToRecordSummary)
+        XCTAssertNil(formatted1)
+        XCTAssertNil(formatted2)
+        XCTAssertNotNil(parser.summary)
+
+        XCTAssertEqual(parser.summary?.testsCount, 56)
+        XCTAssertEqual(parser.summary?.failuresCount, 2)
+        XCTAssertEqual(parser.summary?.unexpectedCount, 1)
+        XCTAssertEqual(parser.summary?.skippedCount, 3)
+        XCTAssertEqual(parser.summary?.time, 1.029)
+
+        // Second test plan
+        XCTAssertNotNil(parser.summary)
+        XCTAssertFalse(parser.needToRecordSummary)
+        let formatted3 = noColoredFormatted(input3)
+        XCTAssertTrue(parser.needToRecordSummary)
+        let formatted4 = noColoredFormatted(input4)
+        XCTAssertFalse(parser.needToRecordSummary)
+        XCTAssertNil(formatted3)
+        XCTAssertNil(formatted4)
+        XCTAssertNotNil(parser.summary)
+
+        XCTAssertEqual(parser.summary?.testsCount, 57)
+        XCTAssertEqual(parser.summary?.failuresCount, 3)
+        XCTAssertEqual(parser.summary?.unexpectedCount, 2)
+        XCTAssertEqual(parser.summary?.skippedCount, 4)
+        XCTAssertEqual(parser.summary?.time, 4.029)
+    }
+    #endif
+
+    func testFailingTest() { }
+
+    func testFatalError() {
+        let input = "fatal error: malformed or corrupted AST file: 'could not find file '/path/file.h' referenced by AST file' note: after modifying system headers, please delete the module cache at '/path/DerivedData/ModuleCache/M5WJ0FYE7N06'"
+        let output = "##teamcity[message text=\'Build error\' errorDetails=\'|[x|] fatal error: malformed or corrupted AST file: |\'could not find file |\'/path/file.h|\' referenced by AST file|\' note: after modifying system headers, please delete the module cache at |\'/path/DerivedData/ModuleCache/M5WJ0FYE7N06|\'\' status=\'ERROR\']\nBuild error"
+        XCTAssertEqual(noColoredFormatted(input), output)
+    }
+
+    func testFileMissingError() {
+        let input = "<unknown>:0: error: no such file or directory: '/path/file.swift'"
+        let output = "##teamcity[message text=\'File missing error\' errorDetails=\'|[x|] /path/file.swift: error: no such file or directory:\' status=\'ERROR\']\nFile missing error"
+        XCTAssertEqual(noColoredFormatted(input), output)
+    }
+
+    func testGenerateCoverageData() {
+        let formatted = noColoredFormatted("Generating coverage data...")
+        XCTAssertEqual(formatted, "Generating code coverage data...")
+    }
+
+    func testGeneratedCoverageReport() {
+        let formatted = noColoredFormatted("Generated coverage report: /path/to/code coverage.xccovreport")
+        XCTAssertEqual(formatted, "Generated code coverage report: /path/to/code coverage.xccovreport")
+    }
+
+    func testGenerateDsym() {
+        let input = "GenerateDSYMFile /path/file.dSYM /path/to/file (in target 'MyApp' from project 'MyProject')"
+        let output = "[MyApp] Generating file.dSYM"
+        XCTAssertEqual(noColoredFormatted(input), output)
+    }
+
+    func testGenericWarning() {
+        let input = "warning: some warning here 123"
+        let output = "##teamcity[message text=\'Xcodebuild warning|n|[!|] some warning here 123\' status=\'WARNING\']\nXcodebuild warning"
+        XCTAssertEqual(noColoredFormatted(input), output)
+    }
+
+    func testLdError() {
+        let inputLdLibraryError = "ld: library not found for -lPods-Yammer"
+        XCTAssertEqual(noColoredFormatted(inputLdLibraryError), "##teamcity[message text=\'Build error\' errorDetails=\'|[x|] ld: library not found for -lPods-Yammer\' status=\'ERROR\']\nBuild error")
+
+        let inputLdSymbolsError = "ld: symbol(s) not found for architecture x86_64"
+        XCTAssertEqual(noColoredFormatted(inputLdSymbolsError), "##teamcity[message text=\'Build error\' errorDetails=\'|[x|] ld: symbol(s) not found for architecture x86_64\' status=\'ERROR\']\nBuild error")
+    }
+
+    func testLdWarning() {
+        let input = "ld: warning: embedded dylibs/frameworks only run on iOS 8 or later"
+        let output = "##teamcity[message text=\'Linker warning|n|[!|] ld: embedded dylibs/frameworks only run on iOS 8 or later\' status=\'WARNING\']\nLinker warning"
+        XCTAssertEqual(noColoredFormatted(input), output)
+    }
+
+    func testLibtool() { }
+
+    func testLinkerDuplicateSymbolsLocation() { }
+
+    func testLinkerDuplicateSymbols() { }
+
+    func testLinkerUndefinedSymbolLocation() { }
+
+    func testLinkerUndefinedSymbols() { }
+
+    func testLinking() {
+        #if os(macOS)
+        let formatted = noColoredFormatted("Ld /Users/admin/Library/Developer/Xcode/DerivedData/xcbeautify-abcd/Build/Products/Debug/xcbeautify normal x86_64 (in target: xcbeautify)")
+        XCTAssertEqual(formatted, "[xcbeautify] Linking xcbeautify")
+
+        let formatted2 = noColoredFormatted("Ld /Users/admin/Library/Developer/Xcode/DerivedData/MyApp-abcd/Build/Intermediates.noindex/ArchiveIntermediates/MyApp/IntermediateBuildFilesPath/MyApp.build/Release-iphoneos/MyApp.build/Objects-normal/armv7/My\\ App normal armv7 (in target: MyApp)")
+        XCTAssertEqual(formatted2, "[MyApp] Linking My\\ App")
+        #endif
+    }
+
+    func testModuleIncludesError() { }
+
+    func testNoCertificate() { }
+
+    #if os(macOS)
+    func testTestCaseWithSpacesPassed() {
+        let formatted = noColoredFormatted("Test Case '-[MyProject.MyTestSuite some component, when the disk is full, will display an error]' passed (0.005 seconds).")
+        XCTAssertEqual(formatted, "    ✔ some component, when the disk is full, will display an error (0.005 seconds)")
+    }
+    #endif
+
+    #if os(macOS)
+    func testTestCaseWithSpacesFailed() {
+        let formatted = noColoredFormatted("/Users/jsmith/MyProject/Example.swift:12: error: -[MyProject.MyTestSuite one, when added to two, produces three] : expected to equal <3>, got <4>")
+        XCTAssertEqual(formatted, "    ✖ one, when added to two, produces three, expected to equal <3>, got <4>")
+    }
+    #endif
+
+    func testParallelTestCaseFailed() {
+        let formatted = noColoredFormatted("Test case 'XcbeautifyLibTests.testBuildTarget()' failed on 'xctest (49438)' (0.131 seconds)")
+        XCTAssertEqual(formatted, "    ✖ testBuildTarget on 'xctest (49438)' (0.131 seconds)")
+    }
+
+    func testParallelTestCasePassed() {
+        let formatted = noColoredFormatted("Test case 'XcbeautifyLibTests.testBuildTarget()' passed on 'xctest (49438)' (0.131 seconds)")
+        XCTAssertEqual(formatted, "    ✔ testBuildTarget on 'xctest (49438)' (0.131 seconds)")
+    }
+
+    func testConcurrentDestinationTestSuiteStarted() {
+        let formatted = noColoredFormatted("Test suite 'XcbeautifyLibTests (iOS).xctest' started on 'iPhone X'")
+        XCTAssertEqual(formatted, "Test Suite XcbeautifyLibTests (iOS).xctest started on 'iPhone X'")
+    }
+
+    func testConcurrentDestinationTestCaseFailed() {
+        let formatted = noColoredFormatted("Test case 'XcbeautifyLibTests.testBuildTarget()' failed on 'iPhone X' (77.158 seconds)")
+        XCTAssertEqual(formatted, "    ✖ testBuildTarget on 'iPhone X' (77.158 seconds)")
+    }
+
+    func testConcurrentDestinationTestCasePassed() {
+        let formatted = noColoredFormatted("Test case 'XcbeautifyLibTests.testBuildTarget()' passed on 'iPhone X' (77.158 seconds)")
+        XCTAssertEqual(formatted, "    ✔ testBuildTarget on 'iPhone X' (77.158 seconds)")
+    }
+
+    func testParallelTestCaseAppKitPassed() {
+        let formatted = noColoredFormatted("Test case '-[XcbeautifyLibTests.XcbeautifyLibTests testBuildTarget]' passed on 'xctest (49438)' (0.131 seconds).")
+        XCTAssertEqual(formatted, "    ✔ testBuildTarget (0.131) seconds)")
+    }
+
+    func testParallelTestCaseAppKitWithSpacesPassed() {
+        let formatted = noColoredFormatted("Test case '-[XcbeautifyLibTests.XcbeautifyLibTests test build target]' passed on 'xctest (49438)' (0.131 seconds).")
+        XCTAssertEqual(formatted, "    ✔ test build target (0.131) seconds)")
+    }
+
+    func testParallelTestingStarted() {
+        let formatted = noColoredFormatted("Testing started on 'iPhone X'")
+        XCTAssertEqual(formatted, "Testing started on 'iPhone X'")
+    }
+
+    func testParallelTestingPassed() {
+        let formatted = noColoredFormatted("Testing passed on 'iPhone X'")
+        XCTAssertEqual(formatted, "Testing passed on 'iPhone X'")
+    }
+
+    func testParallelTestingFailed() {
+        let formatted = noColoredFormatted("Testing failed on 'iPhone X'")
+        XCTAssertEqual(formatted, "Testing failed on 'iPhone X'")
+    }
+
+    func testPbxcp() {
+        let formatted = noColoredFormatted("PBXCp /Users/admin/CocoaLumberjack/Classes/Extensions/DDDispatchQueueLogFormatter.h /Users/admin/Library/Developer/Xcode/DerivedData/Lumberjack-abcd/Build/Products/Release/include/CocoaLumberjack/DDDispatchQueueLogFormatter.h (in target: CocoaLumberjack-Static)")
+        XCTAssertEqual(formatted, "[CocoaLumberjack-Static] Copying DDDispatchQueueLogFormatter.h")
+    }
+
+    func testPhaseScriptExecution() {
+        let input1 = "PhaseScriptExecution [CP]\\ Check\\ Pods\\ Manifest.lock /Users/admin/Library/Developer/Xcode/DerivedData/App-abcd/Build/Intermediates.noindex/ArchiveIntermediates/App/IntermediateBuildFilesPath/App.build/Release-iphoneos/App.build/Script-53BECF2B2F2E203E928C31AE.sh (in target: App)"
+        let input2 = "PhaseScriptExecution [CP]\\ Check\\ Pods\\ Manifest.lock /Users/admin/Library/Developer/Xcode/DerivedData/App-abcd/Build/Intermediates.noindex/ArchiveIntermediates/App/IntermediateBuildFilesPath/App.build/Release-iphoneos/App.build/Script-53BECF2B2F2E203E928C31AE.sh (in target 'App' from project 'App')"
+        XCTAssertEqual(noColoredFormatted(input1), "[App] Running script [CP] Check Pods Manifest.lock")
+        XCTAssertEqual(noColoredFormatted(input2), "[App] Running script [CP] Check Pods Manifest.lock")
+    }
+
+    func testPhaseSuccess() {
+        let formatted = noColoredFormatted("** CLEAN SUCCEEDED ** [0.085 sec]")
+        XCTAssertEqual(formatted, "Clean Succeeded")
+    }
+
+    func testPodsError() {
+        let input = "error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation."
+        let output = "##teamcity[message text=\'Build error\' errorDetails=\'|[x|] error: The sandbox is not in sync with the Podfile.lock. Run |\'pod install|\' or update your CocoaPods installation.\' status=\'ERROR\']\nBuild error"
+        XCTAssertEqual(noColoredFormatted(input), output)
+    }
+
+    func testPreprocess() {
+        let input = "Preprocess /Example/Example/Something.m normal arm64 (in target 'SomeTarget' from project 'SomeProject')"
+        let output = "[SomeTarget] Preprocess Something.m"
+        XCTAssertEqual(noColoredFormatted(input), output)
+    }
+
+    func testProcessInfoPlist() {
+        let formatted = noColoredFormatted("ProcessInfoPlistFile /Users/admin/Library/Developer/Xcode/DerivedData/xcbeautify-abcd/Build/Products/Debug/Guaka.framework/Versions/A/Resources/Info.plist /Users/admin/xcbeautify/xcbeautify.xcodeproj/Guaka_Info.plist")
+        XCTAssertEqual(formatted, "Processing Guaka_Info.plist")
+    }
+
+    func testProcessPchCommand() {
+        let formatted = noColoredFormatted("/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang -x c++-header -target x86_64-apple-macos10.13 -c /path/to/my.pch -o /path/to/output/AcVDiff_Prefix.pch.gch")
+        XCTAssertEqual(formatted, "Preprocessing /path/to/my.pch")
+    }
+
+    func testProcessPchCommandArbitraryExtension() {
+        let formatted = noColoredFormatted(#"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang -x c++-header -target x86_64-apple-macos12.3 -c /path/with\ space/cmake_pch.hxx -o /path/with\ space/build/SharedPrecompiledHeaders/SharedPrecompiledHeaders/2304651503107189736/cmake_pch.hxx.gch --serialize-diagnostics /path/with\ space/build/SharedPrecompiledHeaders/SharedPrecompiledHeaders/2304651503107189736/cmake_pch.hxx.dia"#)
+        XCTAssertEqual(formatted, #"Preprocessing /path/with\ space/cmake_pch.hxx"#)
+    }
+
+    func testProcessPch() {
+        let formatted = noColoredFormatted("ProcessPCH /Users/admin/Library/Developer/Xcode/DerivedData/Lumberjack-abcd/Build/Intermediates.noindex/PrecompiledHeaders/SharedPrecompiledHeaders/5872309797734264511/CocoaLumberjack-Prefix.pch.gch /Users/admin/CocoaLumberjack/Framework/Lumberjack/CocoaLumberjack-Prefix.pch normal x86_64 objective-c com.apple.compilers.llvm.clang.1_0.analyzer (in target: CocoaLumberjack)")
+        XCTAssertEqual(formatted, "[CocoaLumberjack] Processing CocoaLumberjack-Prefix.pch")
+    }
+
+    func testProcessPchArbitraryExtension() {
+        let formatted = noColoredFormatted(#"ProcessPCH++ /Users/admin/src/Test\ Folder/_builds/SharedPrecompiledHeaders/SharedPrecompiledHeaders/2304651503107189736/cmake_pch.hxx.gch /Users/admin/src/Test\ Folder/_builds/CMakeFiles/foo.dir/Debug/cmake_pch.hxx normal x86_64 c++ com.apple.compilers.llvm.clang.1_0.compiler (in target 'foo' from project 'foo')"#)
+        XCTAssertEqual(formatted, "[foo] Processing cmake_pch.hxx")
+    }
+
+    func testProcessPchPlusPlus() {
+        let formatted = noColoredFormatted("ProcessPCH++ /Users/admin/Library/Developer/Xcode/DerivedData/Lumberjack-abcd/Build/Intermediates.noindex/PrecompiledHeaders/SharedPrecompiledHeaders/5872309797734264511/CocoaLumberjack-Prefix.pch.gch /Users/admin/CocoaLumberjack/Framework/Lumberjack/CocoaLumberjack-Prefix.pch normal x86_64 objective-c com.apple.compilers.llvm.clang.1_0.analyzer (in target: CocoaLumberjack)")
+        XCTAssertEqual(formatted, "[CocoaLumberjack] Processing CocoaLumberjack-Prefix.pch")
+    }
+
+    func testProvisioningProfileRequired() {
+        let input = #"MyProject requires a provisioning profile. Select a provisioning profile for the "Debug" build configuration in the project editor."#
+        let output = "##teamcity[message text=\'Build error\' errorDetails=\'|[x|] MyProject requires a provisioning profile. Select a provisioning profile for the \"Debug\" build configuration in the project editor.\' status=\'ERROR\']\nBuild error"
+        XCTAssertEqual(noColoredFormatted(input), output)
+        XCTAssertEqual(parser.outputType, .error)
+    }
+
+    func testRestartingTests() {
+        let formatted = noColoredFormatted("Restarting after unexpected exit, crash, or test timeout in HomePresenterTest.testIsCellPresented(); summary will include totals from previous launches.")
+        XCTAssertEqual(formatted, "    ✖ Restarting after unexpected exit, crash, or test timeout in HomePresenterTest.testIsCellPresented(); summary will include totals from previous launches.")
+    }
+
+    func testShellCommand() {
+        let formatted = noColoredFormatted("    cd /foo/bar/baz")
+        XCTAssertNil(formatted)
+        XCTAssertEqual(parser.outputType, .task)
+    }
+
+    func testSymbolReferencedFrom() {
+        let formatted = noColoredFormatted("  \"NetworkBusiness.ImageDownloadManager.saveImage(image: __C.UIImage, needWatermark: Swift.Bool, params: [Swift.String : Any], downloadHandler: (Swift.Bool) -> ()?) -> ()\", referenced from:")
+        XCTAssertEqual(formatted, "[x]   \"NetworkBusiness.ImageDownloadManager.saveImage(image: __C.UIImage, needWatermark: Swift.Bool, params: [Swift.String : Any], downloadHandler: (Swift.Bool) -> ()?) -> ()\", referenced from:")
+        XCTAssertEqual(parser.outputType, .error)
+    }
+
+    func testUndefinedSymbolLocation() {
+        let formatted = noColoredFormatted("      MediaBrowser.ChatGalleryViewController.downloadImage() -> () in MediaBrowser(ChatGalleryViewController.o)")
+        XCTAssertEqual(formatted, "##teamcity[message text=\'Undefined symbol location|n|[!|]       MediaBrowser.ChatGalleryViewController.downloadImage() -> () in MediaBrowser(ChatGalleryViewController.o)\' status=\'WARNING\']\nUndefined symbol location")
+        XCTAssertEqual(parser.outputType, .warning)
+    }
+
+    func testTestCaseMeasured() {
+        #if os(macOS)
+        let formatted = noColoredFormatted(#"/Users/cyberbeni/Desktop/framework/TypedNotificationCenter/<compiler-generated>:54: Test Case '-[TypedNotificationCenterPerformanceTests.BridgedNotificationTests test_subscribing_2senders_notificationName]' measured [High Water Mark For Heap Allocations, KB] average: 5407.634, relative standard deviation: 45.772%, values: [9341.718750, 3779.468750, 3779.468750, 9630.344727, 3779.468750, 3779.468750, 3895.093750, 3779.468750, 8532.372070, 3779.468750], performanceMetricID:com.apple.XCTPerformanceMetric_HighWaterMarkForHeapAllocations, baselineName: "", baselineAverage: , polarity: prefers smaller, maxPercentRegression: 10.000%, maxPercentRelativeStandardDeviation: 10.000%, maxRegression: 1.000, maxStandardDeviation: 1.000"#)
+        XCTAssertEqual(formatted, #"    ◷ test_subscribing_2senders_notificationName measured (5407.634 KB ±45.772% -- High Water Mark For Heap Allocations)"#)
+        #endif
+    }
+
+    func testTestCasePassed() {
+        #if os(macOS)
+        let formatted = noColoredFormatted("Test Case '-[XcbeautifyLibTests.XcbeautifyLibTests testBuildTarget]' passed (0.131 seconds).")
+        XCTAssertEqual(formatted, "    ✔ testBuildTarget (0.131 seconds)")
+        #endif
+    }
+
+    func testTestCasePending() { }
+
+    func testTestCaseStarted() { }
+
+    func testTestSuiteStart() { }
+
+    func testTestSuiteStarted() { }
+
+    #if os(macOS)
+    func testTestSuiteAllTestsPassed() {
+        let input = "Test Suite 'All tests' passed at 2022-01-15 21:31:49.073."
+
+        XCTAssertFalse(parser.needToRecordSummary)
+        let formatted = noColoredFormatted(input)
+        XCTAssertNil(formatted)
+        XCTAssertTrue(parser.needToRecordSummary)
+    }
+    #endif
+
+    #if os(macOS)
+    func testTestSuiteAllTestsFailed() {
+        let input = "Test Suite 'All tests' failed at 2022-01-15 21:31:49.073."
+
+        XCTAssertFalse(parser.needToRecordSummary)
+        let formatted = noColoredFormatted(input)
+        XCTAssertNil(formatted)
+        XCTAssertTrue(parser.needToRecordSummary)
+    }
+    #endif
+
+    func testTestsRunCompletion() { }
+
+    func testTiffutil() {
+        let input = "TiffUtil file.tiff"
+        XCTAssertNil(noColoredFormatted(input))
+        XCTAssertEqual(parser.outputType, .task)
+    }
+
+    func testTouch() {
+        let formatted = noColoredFormatted("Touch /Users/admin/Library/Developer/Xcode/DerivedData/xcbeautify-dgnqmpfertotpceazwfhtfwtuuwt/Build/Products/Debug/XcbeautifyLib.framework (in target: XcbeautifyLib)")
+        XCTAssertEqual(formatted, "[XcbeautifyLib] Touching XcbeautifyLib.framework")
+    }
+
+    func testUiFailingTest() {
+        let formatted = noColoredFormatted("    t =    10.13s Assertion Failure: <unknown>:0: App crashed in <external symbol>")
+        XCTAssertEqual(formatted, "    ✖ <unknown>:0, App crashed in <external symbol>")
+    }
+
+    func testWillNotBeCodeSigned() {
+        let input = "FrameworkName will not be code signed because its settings don't specify a development team."
+        let output = "##teamcity[message text=\'Codesign error|n|[!|] FrameworkName will not be code signed because its settings don|\'t specify a development team.\' status=\'WARNING\']\nCodesign error"
+        XCTAssertEqual(noColoredFormatted(input), output)
+    }
+
+    func testWriteAuxiliaryFileGeneric() {
+        let input = #"WriteAuxiliaryFile /path/to/some/auxiliary/file.extension (in target 'Target' from project 'Project')"#
+        let output = "[Target] Write Auxiliary File file.extension"
+        XCTAssertEqual(noColoredFormatted(input), output)
+    }
+
+    func testWriteAuxiliaryFileBackyardBirds() {
+        let input = #"WriteAuxiliaryFile /Backyard-Birds/Build/Intermediates.noindex/LayeredArtworkLibrary.build/Debug/LayeredArtworkLibrary_LayeredArtworkLibrary.build/empty-LayeredArtworkLibrary_LayeredArtworkLibrary.plist (in target 'LayeredArtworkLibrary_LayeredArtworkLibrary' from project 'LayeredArtworkLibrary')"#
+        let output = "[LayeredArtworkLibrary_LayeredArtworkLibrary] Write Auxiliary File empty-LayeredArtworkLibrary_LayeredArtworkLibrary.plist"
+        XCTAssertEqual(noColoredFormatted(input), output)
+    }
+
+    func testWriteFile() {
+        let input = "write-file /path/file.SwiftFileList"
+        XCTAssertNil(noColoredFormatted(input))
+        XCTAssertEqual(parser.outputType, .task)
+    }
+
+    func testPackageFetching() {
+        let input1 = "Fetching from https://github.com/cpisciotta/xcbeautify"
+        let output1 = "Fetching https://github.com/cpisciotta/xcbeautify"
+        let formatted1 = noColoredFormatted(input1)
+        XCTAssertEqual(formatted1, output1)
+
+        let input2 = "Fetching from https://github.com/cpisciotta/xcbeautify (cached)"
+        let output2 = "Fetching https://github.com/cpisciotta/xcbeautify (cached)"
+        let formatted2 = noColoredFormatted(input2)
+        XCTAssertEqual(formatted2, output2)
+
+        let input3 = "Fetching from https://github.com/cpisciotta/xcbeautify.git"
+        let output3 = "Fetching https://github.com/cpisciotta/xcbeautify.git"
+        let formatted3 = noColoredFormatted(input3)
+        XCTAssertEqual(formatted3, output3)
+    }
+
+    func testPackageUpdating() {
+        let input1 = "Updating from https://github.com/cpisciotta/xcbeautify"
+        let output1 = "Updating https://github.com/cpisciotta/xcbeautify"
+        let formatted1 = noColoredFormatted(input1)
+        XCTAssertEqual(formatted1, output1)
+
+        let input2 = "Updating from https://github.com/cpisciotta/xcbeautify (cached)"
+        let output2 = "Updating https://github.com/cpisciotta/xcbeautify (cached)"
+        let formatted2 = noColoredFormatted(input2)
+        XCTAssertEqual(formatted2, output2)
+
+        let input3 = "Updating from https://github.com/cpisciotta/xcbeautify.git"
+        let output3 = "Updating https://github.com/cpisciotta/xcbeautify.git"
+        let formatted3 = noColoredFormatted(input3)
+        XCTAssertEqual(formatted3, output3)
+    }
+
+    func testPackageCheckingOut() {
+        let input1 = "Cloning local copy of package 'xcbeautify'"
+        let formatted1 = noColoredFormatted(input1)
+        XCTAssertNil(formatted1)
+
+        let input2 = "Checking out x.y.z of package 'xcbeautify'"
+        let output2 = "Checking out 'xcbeautify' @ x.y.z"
+        let formatted2 = noColoredFormatted(input2)
+        XCTAssertEqual(formatted2, output2)
+    }
+
+    func testPackageGraphResolved() {
+        // Start
+        let start = noColoredFormatted("Resolve Package Graph")
+        XCTAssertEqual(start, "Resolving Package Graph")
+
+        // Ended
+        let ended = noColoredFormatted("Resolved source packages:")
+        XCTAssertEqual(ended, "Resolved source packages")
+
+        // Package
+        let package = noColoredFormatted("  StrasbourgParkAPI: https://github.com/yageek/StrasbourgParkAPI.git @ 3.0.2")
+        XCTAssertEqual(package, "StrasbourgParkAPI - https://github.com/yageek/StrasbourgParkAPI.git @ 3.0.2")
+    }
+
+    func testXcodebuildError() {
+        let formatted = noColoredFormatted("xcodebuild: error: Existing file at -resultBundlePath \"/output/file.xcresult\"")
+        XCTAssertEqual(formatted, "##teamcity[message text=\'Build error\' errorDetails=\'|[x|] xcodebuild: error: Existing file at -resultBundlePath \"/output/file.xcresult\"\' status=\'ERROR\']\nBuild error")
+    }
+
+    func testXcodeprojError() {
+        // Given
+        let errorText = #"/path/to/project.xcodeproj: error: No signing certificate "iOS Distribution" found: No "iOS Distribution" signing certificate matching team ID "xxxxx" with a private key was found. (in target 'target' from project 'project')"#
+        let expectedFormatted = "##teamcity[buildProblem description=\'/path/to/project.xcodeproj\']\n[x] /path/to/project.xcodeproj: No signing certificate \"iOS Distribution\" found: No \"iOS Distribution\" signing certificate matching team ID \"xxxxx\" with a private key was found. (in target \'target\' from project \'project\')\n\n"
+
+        // When
+        let actualFormatted = noColoredFormatted(errorText)
+
+        // Then
+        XCTAssertEqual(actualFormatted, expectedFormatted)
+        XCTAssertEqual(parser.outputType, .error)
+    }
+
+    func testXcodeprojWarning() {
+        // Given
+        let errorText = #"/Users/xxxxx/Example/Pods/Pods.xcodeproj: warning: The iOS deployment target 'IPHONEOS_DEPLOYMENT_TARGET' is set to 9.0, but the range of supported deployment target versions is 11.0 to 16.0.99. (in target 'XXPay' from project 'Pods')"#
+        let expectedFormatted = "##teamcity[message text=\'Compile warning|n|[!|]  /Users/xxxxx/Example/Pods/Pods.xcodeproj: The iOS deployment target |\'IPHONEOS_DEPLOYMENT_TARGET|\' is set to 9.0, but the range of supported deployment target versions is 11.0 to 16.0.99. (in target |\'XXPay|\' from project |\'Pods|\')|n|n\' status=\'WARNING\']\nCompile warning"
+
+        // When
+        let actualFormatted = noColoredFormatted(errorText)
+
+        // Then
+        XCTAssertEqual(actualFormatted, expectedFormatted)
+        XCTAssertEqual(parser.outputType, .warning)
+    }
+
+    func testDuplicateLocalizedStringKey() {
+        let formatted = noColoredFormatted(#"2022-12-07 16:26:40 --- WARNING: Key "duplicate" used with multiple values. Value "First" kept. Value "Second" ignored."#)
+        XCTAssertEqual(formatted, "##teamcity[message text=\'Duplicated localized string key|n|[!|] Key \"duplicate\" used with multiple values. Value \"First\" kept. Value \"Second\" ignored.\' status=\'WARNING\']\nDuplicated localized string key")
+        XCTAssertEqual(parser.outputType, .warning)
+    }
+
+    func testCommandProduceError() {
+        let error = """
+        error: Multiple commands produce '/Users/admin/teamcity/work/8906de356cda3a27/build/fastlane/Build/Products/Debug-iphonesimulator/some.app/PrivacyInfo.xcprivacy'
+            note: Target 'some-app' (project 'Some App') has copy command from '/Users/admin/teamcity/work/8906de356cda3a27/Resources/Privacy/PrivacyInfo.xcprivacy' to '/Users/admin/teamcity/work/8906de356cda3a27/build/fastlane/Build/Products/Debug-iphonesimulator/some.app/PrivacyInfo.xcprivacy'
+            note: That command depends on command in Target 'some-app' (project 'Some App'): script phase “[CP] Copy Pods Resources”
+        """
+        let actual = noColoredFormatted(error)
+
+        XCTAssertEqual(actual, "##teamcity[message text=\'Build error\' errorDetails=\'|[x|] error: Multiple commands produce |\'/Users/admin/teamcity/work/8906de356cda3a27/build/fastlane/Build/Products/Debug-iphonesimulator/some.app/PrivacyInfo.xcprivacy|\'\' status=\'ERROR\']\nBuild error")
+    }
+}