Repository: cpisciotta/xcbeautify (swift)

Issue:
[New Output] Support "Testing started" Output
### What's the uncaptured output?

```
Testing started
```

### Suggested Regex

```regex
^Testing started$
```

### Suggested Output

_No response_

### Additional Context

_No response_

Pull request:
Adresses #206.

### Changes

Add a capture group for the "Testing started" output.

### Testing

- Added new unit tests

Patch:
diff --git a/Sources/XcbeautifyLib/CaptureGroups.swift b/Sources/XcbeautifyLib/CaptureGroups.swift
index 272d1951..d97bbcc4 100644
--- a/Sources/XcbeautifyLib/CaptureGroups.swift
+++ b/Sources/XcbeautifyLib/CaptureGroups.swift
@@ -1816,6 +1816,24 @@ struct SwiftDriverJobDiscoveryEmittingModuleCaptureGroup: CaptureGroup {
     init?(groups: [String]) { }
 }
 
+/// This output is printed when running
+/// `xcodebuild test -scheme xcbeautify-Package -destination 'platform=macOS,arch=arm64'`.
+struct TestingStartedCaptureGroup: CaptureGroup {
+    static let outputType: OutputType = .test
+
+    /// Regular expression captured groups:
+    /// $1 = whole message
+    static let regex = Regex(pattern: "^(Testing started.*)$")
+
+    let wholeMessage: String
+
+    init?(groups: [String]) {
+        assert(groups.count >= 1)
+        guard let wholeMessage = groups[safe: 0] else { return nil }
+        self.wholeMessage = wholeMessage
+    }
+}
+
 struct SwiftDriverJobDiscoveryCompilingCaptureGroup: CaptureGroup {
     static let outputType: OutputType = .task
 
diff --git a/Sources/XcbeautifyLib/Formatter.swift b/Sources/XcbeautifyLib/Formatter.swift
index 3938e41e..2ee1a431 100644
--- a/Sources/XcbeautifyLib/Formatter.swift
+++ b/Sources/XcbeautifyLib/Formatter.swift
@@ -210,6 +210,8 @@ package struct Formatter {
             return renderer.formatSwiftDriverJobDiscoveryEmittingModule(group: group)
         case let group as SwiftDriverJobDiscoveryCompilingCaptureGroup:
             return renderer.formatSwiftDriverJobDiscoveryCompiling(group: group)
+        case let group as TestingStartedCaptureGroup:
+            return renderer.formatTestingStarted(group: group)
         default:
             assertionFailure()
             return nil
diff --git a/Sources/XcbeautifyLib/Parser.swift b/Sources/XcbeautifyLib/Parser.swift
index a1bb1f07..d108b54a 100644
--- a/Sources/XcbeautifyLib/Parser.swift
+++ b/Sources/XcbeautifyLib/Parser.swift
@@ -96,6 +96,7 @@ package final class Parser {
         ExecutedWithSkippedCaptureGroup.self,
         TestSuiteAllTestsPassedCaptureGroup.self,
         TestSuiteAllTestsFailedCaptureGroup.self,
+        TestingStartedCaptureGroup.self,
     ]
 
     // MARK: - Init
diff --git a/Sources/XcbeautifyLib/Renderers/OutputRendering.swift b/Sources/XcbeautifyLib/Renderers/OutputRendering.swift
index 3611594d..70ea8a88 100644
--- a/Sources/XcbeautifyLib/Renderers/OutputRendering.swift
+++ b/Sources/XcbeautifyLib/Renderers/OutputRendering.swift
@@ -83,6 +83,7 @@ protocol OutputRendering {
     func formatWriteAuxiliaryFile(group: WriteAuxiliaryFileCaptureGroup) -> String?
     func formatWriteFile(group: WriteFileCaptureGroup) -> String?
     func formatSwiftDriverJobDiscoveryEmittingModule(group: SwiftDriverJobDiscoveryEmittingModuleCaptureGroup) -> String?
+    func formatTestingStarted(group: TestingStartedCaptureGroup) -> String
     func formatSwiftDriverJobDiscoveryCompiling(group: SwiftDriverJobDiscoveryCompilingCaptureGroup) -> String?
 }
 
@@ -560,4 +561,8 @@ extension OutputRendering {
     func formatParallelTestingFailed(group: ParallelTestingFailedCaptureGroup) -> String {
         colored ? group.wholeError.s.Bold.f.Red : group.wholeError
     }
+
+    func formatTestingStarted(group: TestingStartedCaptureGroup) -> String {
+        colored ? group.wholeMessage.s.Bold.f.Cyan : group.wholeMessage
+    }
 }


Test patch:
diff --git a/Tests/XcbeautifyLibTests/ParserTests.swift b/Tests/XcbeautifyLibTests/ParserTests.swift
index de59aa96..d66fee72 100644
--- a/Tests/XcbeautifyLibTests/ParserTests.swift
+++ b/Tests/XcbeautifyLibTests/ParserTests.swift
@@ -75,4 +75,15 @@ final class ParserTests: XCTestCase {
         XCTAssertEqual(captureGroup.target, "BackyardBirdsData_BackyardBirdsData")
         XCTAssertEqual(captureGroup.project, "Backyard Birds Data")
     }
+
+    func testMatchTestingStarted() throws {
+        let input = "Testing started"
+        let captureGroup = try XCTUnwrap(parser.parse(line: input) as? TestingStartedCaptureGroup)
+        XCTAssertEqual(captureGroup.wholeMessage, "Testing started")
+    }
+
+    func testNotMatchTestingStarted() throws {
+        let input = #"2024-08-18 18:17:52.619 xcodebuild[9799:394817] [MT] IDETestOperationsObserverDebug: 21.975 elapsed -- Testing started completed."#
+        XCTAssertNil(parser.parse(line: input))
+    }
 }
diff --git a/Tests/XcbeautifyLibTests/RendererTests/GitHubActionsRendererTests.swift b/Tests/XcbeautifyLibTests/RendererTests/GitHubActionsRendererTests.swift
index a16c540e..07829c88 100644
--- a/Tests/XcbeautifyLibTests/RendererTests/GitHubActionsRendererTests.swift
+++ b/Tests/XcbeautifyLibTests/RendererTests/GitHubActionsRendererTests.swift
@@ -602,4 +602,9 @@ final class GitHubActionsRendererTests: XCTestCase {
         let formatted = logFormatted(#"2022-12-07 16:26:40 --- WARNING: Key "duplicate" used with multiple values. Value "First" kept. Value "Second" ignored."#)
         XCTAssertEqual(formatted, #"::warning ::Key "duplicate" used with multiple values. Value "First" kept. Value "Second" ignored."#)
     }
+
+    func testTestingStarted() {
+        let formatted = logFormatted(#"Testing started"#)
+        XCTAssertEqual(formatted, #"Testing started"#)
+    }
 }
diff --git a/Tests/XcbeautifyLibTests/RendererTests/TeamCityRendererTests.swift b/Tests/XcbeautifyLibTests/RendererTests/TeamCityRendererTests.swift
index c48588e7..028dbe14 100644
--- a/Tests/XcbeautifyLibTests/RendererTests/TeamCityRendererTests.swift
+++ b/Tests/XcbeautifyLibTests/RendererTests/TeamCityRendererTests.swift
@@ -623,4 +623,9 @@ final class TeamCityRendererTests: XCTestCase {
 
         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")
     }
+
+    func testTestingStarted() {
+        let formatted = noColoredFormatted(#"Testing started"#)
+        XCTAssertEqual(formatted, #"Testing started"#)
+    }
 }
diff --git a/Tests/XcbeautifyLibTests/RendererTests/TerminalRendererTests.swift b/Tests/XcbeautifyLibTests/RendererTests/TerminalRendererTests.swift
index bc3ec2d9..914d70d9 100644
--- a/Tests/XcbeautifyLibTests/RendererTests/TerminalRendererTests.swift
+++ b/Tests/XcbeautifyLibTests/RendererTests/TerminalRendererTests.swift
@@ -629,4 +629,9 @@ final class TerminalRendererTests: XCTestCase {
         let formatted = noColoredFormatted(#"2022-12-07 16:26:40 --- WARNING: Key "duplicate" used with multiple values. Value "First" kept. Value "Second" ignored."#)
         XCTAssertEqual(formatted, #"[!] Key "duplicate" used with multiple values. Value "First" kept. Value "Second" ignored."#)
     }
+
+    func testTestingStarted() {
+        let formatted = noColoredFormatted(#"Testing started"#)
+        XCTAssertEqual(formatted, #"Testing started"#)
+    }
 }