From adadb009083f5c38d7f876a06e177a34e4e99fc2 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Wed, 29 Jan 2025 14:21:06 -0800 Subject: [PATCH] Add `withErrorReporting` (#143) * Add `withErrorReporting` It's common to write the following boilerplate when working with code that can throw errors: ```swift do { try work() } catch { reportIssue(error) } ``` Let's extract this pattern to a helper that can save at least some boilerplate: ```swift withErrorReporting { try work() } ``` Or even make it a 1-liner in some cases: ```swift withErrorReporting(catching: work) ``` * Bump Wasm CI * wip * add test * wip * Disable Wasm tests for now * wip --------- Co-authored-by: Brandon Williams --- .github/workflows/ci.yml | 30 +++--- .../Extensions/withErrorReporting.md | 7 ++ .../Documentation.docc/IssueReporting.md | 1 + Sources/IssueReporting/ErrorReporting.swift | 93 +++++++++++++++++++ .../WithErrorReportingTests.swift | 27 ++++++ 5 files changed, 140 insertions(+), 18 deletions(-) create mode 100644 Sources/IssueReporting/Documentation.docc/Extensions/withErrorReporting.md create mode 100644 Sources/IssueReporting/ErrorReporting.swift create mode 100644 Tests/IssueReportingTests/WithErrorReportingTests.swift diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9310e6b..0cf2de5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -95,28 +95,22 @@ jobs: run: make CONFIG=${{ matrix.config }} build-for-static-stdlib wasm: - name: SwiftWasm + name: Wasm runs-on: ubuntu-latest - strategy: - matrix: - toolchain: - - wasm-5.9.2-RELEASE - - wasm-5.10.0-RELEASE steps: - - name: Cache toolchains - uses: actions/cache@v3 - with: - path: ~/Library/Developer/Toolchains - key: ${{ matrix.toolchain }} - uses: actions/checkout@v4 - uses: bytecodealliance/actions/wasmtime/setup@v1 - - uses: swiftwasm/setup-swiftwasm@v1 - with: - swift-version: ${{ matrix.toolchain }} - - name: Build tests - run: swift build --triple wasm32-unknown-wasi --build-tests -Xlinker -z -Xlinker stack-size=$((1024 * 1024)) - - name: Run tests - run: wasmtime .build/debug/xctest-dynamic-overlayPackageTests.wasm + - name: Install Swift and Swift SDK for WebAssembly + run: | + PREFIX=/opt/swift + set -ex + curl -f -o /tmp/swift.tar.gz "https://download.swift.org/swift-6.0.2-release/ubuntu2204/swift-6.0.2-RELEASE/swift-6.0.2-RELEASE-ubuntu22.04.tar.gz" + sudo mkdir -p $PREFIX; sudo tar -xzf /tmp/swift.tar.gz -C $PREFIX --strip-component 1 + $PREFIX/usr/bin/swift sdk install https://github.com/swiftwasm/swift/releases/download/swift-wasm-6.0.2-RELEASE/swift-wasm-6.0.2-RELEASE-wasm32-unknown-wasi.artifactbundle.zip --checksum 6ffedb055cb9956395d9f435d03d53ebe9f6a8d45106b979d1b7f53358e1dcb4 + echo "$PREFIX/usr/bin" >> $GITHUB_PATH + + - name: Build + run: swift build --swift-sdk wasm32-unknown-wasi --build-tests -Xlinker -z -Xlinker stack-size=$((1024 * 1024)) windows: name: Windows diff --git a/Sources/IssueReporting/Documentation.docc/Extensions/withErrorReporting.md b/Sources/IssueReporting/Documentation.docc/Extensions/withErrorReporting.md new file mode 100644 index 0000000..d57673e --- /dev/null +++ b/Sources/IssueReporting/Documentation.docc/Extensions/withErrorReporting.md @@ -0,0 +1,7 @@ +# ``IssueReporting/withErrorReporting(_:to:fileID:filePath:line:column:catching:)-89omf`` + +## Topics + +### Overloads + +- ``withErrorReporting(_:to:fileID:filePath:line:column:catching:)-3dh1h`` diff --git a/Sources/IssueReporting/Documentation.docc/IssueReporting.md b/Sources/IssueReporting/Documentation.docc/IssueReporting.md index 4d4edd1..0882572 100644 --- a/Sources/IssueReporting/Documentation.docc/IssueReporting.md +++ b/Sources/IssueReporting/Documentation.docc/IssueReporting.md @@ -53,6 +53,7 @@ that ship in the same target as the library itself. - ``reportIssue(_:fileID:filePath:line:column:)`` - ``withExpectedIssue(_:isIntermittent:fileID:filePath:line:column:_:)-9pinm`` +- ``withErrorReporting(_:to:fileID:filePath:line:column:catching:)-89omf`` ### Issue reporters diff --git a/Sources/IssueReporting/ErrorReporting.swift b/Sources/IssueReporting/ErrorReporting.swift new file mode 100644 index 0000000..029ce9e --- /dev/null +++ b/Sources/IssueReporting/ErrorReporting.swift @@ -0,0 +1,93 @@ +/// Evaluates a throwing closure and automatically catches and reports any error thrown. +/// +/// - Parameters: +/// - message: A message describing the expectation. +/// - reporters: Issue reporters to notify during the operation. +/// - fileID: The source `#fileID` associated with the error reporting. +/// - filePath: The source `#filePath` associated with the error reporting. +/// - line: The source `#line` associated with the error reporting. +/// - column: The source `#column` associated with the error reporting. +/// - body: A synchronous operation. +/// - Returns: The optional result of the operation, or `nil` if an error was thrown. +@_transparent +public func withErrorReporting( + _ message: @autoclosure () -> String? = nil, + to reporters: [any IssueReporter]? = nil, + fileID: StaticString = #fileID, + filePath: StaticString = #filePath, + line: UInt = #line, + column: UInt = #column, + catching body: () throws -> R +) -> R? { + if let reporters { + return withIssueReporters(reporters) { + do { + return try body() + } catch { + reportIssue( + error, + message(), + fileID: fileID, + filePath: filePath, + line: line, + column: column + ) + return nil + } + } + } else { + do { + return try body() + } catch { + reportIssue( + error, + message(), + fileID: fileID, + filePath: filePath, + line: line, + column: column + ) + return nil + } + } +} + +/// Evaluates a throwing closure and automatically catches and reports any error thrown. +/// +/// - Parameters: +/// - message: A message describing the expectation. +/// - reporters: Issue reporters to notify during the operation. +/// - fileID: The source `#fileID` associated with the error reporting. +/// - filePath: The source `#filePath` associated with the error reporting. +/// - line: The source `#line` associated with the error reporting. +/// - column: The source `#column` associated with the error reporting. +/// - body: An asynchronous operation. +/// - Returns: The optional result of the operation, or `nil` if an error was thrown. +@_transparent +public func withErrorReporting( + _ message: @autoclosure () -> String? = nil, + to reporters: [any IssueReporter]? = nil, + fileID: StaticString = #fileID, + filePath: StaticString = #filePath, + line: UInt = #line, + column: UInt = #column, + catching body: () async throws -> R +) async -> R? { + if let reporters { + return await withIssueReporters(reporters) { + do { + return try await body() + } catch { + reportIssue(error, fileID: fileID, filePath: filePath, line: line, column: column) + return nil + } + } + } else { + do { + return try await body() + } catch { + reportIssue(error, fileID: fileID, filePath: filePath, line: line, column: column) + return nil + } + } +} diff --git a/Tests/IssueReportingTests/WithErrorReportingTests.swift b/Tests/IssueReportingTests/WithErrorReportingTests.swift new file mode 100644 index 0000000..0e875a6 --- /dev/null +++ b/Tests/IssueReportingTests/WithErrorReportingTests.swift @@ -0,0 +1,27 @@ +#if canImport(Testing) + import Testing + import IssueReporting + + @Suite + struct WithErrorReportingTests { + @Test func basics() { + withKnownIssue { + withErrorReporting { + throw SomeError() + } + } matching: { issue in + issue.description == "Caught error: SomeError()" + } + + withKnownIssue { + withErrorReporting("Failed") { + throw SomeError() + } + } matching: { issue in + issue.description == "Caught error: SomeError(): Failed" + } + } + } + + private struct SomeError: Error {} +#endif