diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a9c63c31..bc86573f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -87,3 +87,30 @@ jobs: with: files: info.lcov token: ${{ secrets.CODECOV_TOKEN }} + + end-to-end: + name: End-to-End Test (${{ matrix.xcode_version }}) + runs-on: macos-latest + strategy: + fail-fast: false + matrix: + include: + - xcode_version: "15.3" + uncaptured_output: 255 + - xcode_version: "15.4" + uncaptured_output: 255 + - xcode_version: "16.0" + uncaptured_output: 255 + - xcode_version: "16.1" + uncaptured_output: 255 + env: + BUILD_LOG: ${{ matrix.xcode_version }}.log + steps: + - uses: actions/checkout@v4 + - uses: actions/checkout@v4 + with: + repository: "cpisciotta/GitHub-Issues-Browser" + path: "Demo" + - run: swift build + - run: set -euo pipefail && xcodebuild clean test -project 'Demo/GHIssues.xcodeproj' -scheme GHIssues -destination 'platform=iOS Simulator,name=iPhone 15 Pro' -derivedDataPath DerivedData CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGN_ENTITLEMENTS="" CODE_SIGNING_ALLOWED=NO ONLY_ACTIVE_ARCH=NO > ${{ env.BUILD_LOG }} + - run: ./.build/debug/ParsingCheck --file-path ${{ env.BUILD_LOG }} --uncaptured-output ${{ matrix.uncaptured_output }} diff --git a/Package.swift b/Package.swift index 4ccb4ae9..451a3e4d 100644 --- a/Package.swift +++ b/Package.swift @@ -36,6 +36,13 @@ let package = Package( "XMLCoder", ] ), + .executableTarget( + name: "ParsingCheck", + dependencies: [ + "XcbeautifyLib", + .product(name: "ArgumentParser", package: "swift-argument-parser"), + ] + ), .testTarget( name: "XcbeautifyLibTests", dependencies: ["XcbeautifyLib"], diff --git a/Sources/ParsingCheck/ParsingCheck.swift b/Sources/ParsingCheck/ParsingCheck.swift new file mode 100644 index 00000000..30596f86 --- /dev/null +++ b/Sources/ParsingCheck/ParsingCheck.swift @@ -0,0 +1,48 @@ +import ArgumentParser +import Darwin +import Foundation +import XcbeautifyLib + +@main +struct ParsingCheck: ParsableCommand { + + @Option + var filePath: String + + @Option + var uncapturedOutput: Int + + enum ParsingCheckError: Error { + case dataReadError + case noData + case regression(Int) + } + + func run() throws { + guard let data = FileManager.default.contents(atPath: filePath) else { + throw ParsingCheckError.noData + } + + guard !data.isEmpty else { + throw ParsingCheckError.noData + } + + var buildLog: [String] = String(decoding: data, as: UTF8.self) + .components(separatedBy: .newlines) + + let parser = Parser() + + var uncapturedOutput = 0 + + while !buildLog.isEmpty { + let line = buildLog.removeFirst() + if !line.isEmpty, parser.parse(line: line) == nil { + uncapturedOutput += 1 + } + } + + guard self.uncapturedOutput == uncapturedOutput else { + throw ParsingCheckError.regression(uncapturedOutput) + } + } +}