From 99422d54bac392de6d96c5b7d90dafd41457d5ac Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Thu, 2 Jul 2026 23:55:58 -0700 Subject: [PATCH 01/10] Add `IssueReporting` trait --- Package.swift | 12 +- Sources/Sharing/Internal/Reference.swift | 1 - .../Internal/SharedChangeTracker.swift | 1 - Sources/Sharing/SharedContinuations.swift | 1 - .../Sharing/SharedKeys/AppStorageKey.swift | 1 - Sources/Sharing/SharedReader.swift | 1 - .../Traits/IdentifiedCollections.swift | 1 - Sources/Sharing/Traits/IssueReporting.swift | 143 ++++++++++++++++++ 8 files changed, 154 insertions(+), 7 deletions(-) create mode 100644 Sources/Sharing/Traits/IssueReporting.swift diff --git a/Package.swift b/Package.swift index c2b8cc73..3e9dff87 100644 --- a/Package.swift +++ b/Package.swift @@ -30,6 +30,10 @@ let package = Package( name: "IdentifiedCollections", description: "Derive Shared elements from Shared collections using IdentifiedCollections" ), + .trait( + name: "IssueReporting", + description: "Improve test coverage by raising runtime warnings as test failures" + ), ], dependencies: [ .package(url: "https://github.com/pointfreeco/combine-schedulers", from: "1.0.0"), @@ -71,7 +75,13 @@ let package = Package( traits: ["IdentifiedCollections"] ) ), - .product(name: "IssueReporting", package: "xctest-dynamic-overlay"), + .product( + name: "IssueReporting", + package: "xctest-dynamic-overlay", + condition: .when( + traits: ["IssueReporting"] + ) + ), .product(name: "PerceptionCore", package: "swift-perception"), ], resources: [ diff --git a/Sources/Sharing/Internal/Reference.swift b/Sources/Sharing/Internal/Reference.swift index 2fb56d1a..234fd4e8 100644 --- a/Sources/Sharing/Internal/Reference.swift +++ b/Sources/Sharing/Internal/Reference.swift @@ -1,6 +1,5 @@ import Dependencies import Foundation -import IssueReporting public import PerceptionCore #if canImport(Combine) diff --git a/Sources/Sharing/Internal/SharedChangeTracker.swift b/Sources/Sharing/Internal/SharedChangeTracker.swift index f88e06a8..88fedb91 100644 --- a/Sources/Sharing/Internal/SharedChangeTracker.swift +++ b/Sources/Sharing/Internal/SharedChangeTracker.swift @@ -1,6 +1,5 @@ public import Dependencies import Foundation -public import IssueReporting @_spi(SharedChangeTracking) public struct SharedChangeTracker: Hashable, Sendable { diff --git a/Sources/Sharing/SharedContinuations.swift b/Sources/Sharing/SharedContinuations.swift index dbf1bc2d..6c015393 100644 --- a/Sources/Sharing/SharedContinuations.swift +++ b/Sources/Sharing/SharedContinuations.swift @@ -1,5 +1,4 @@ import Foundation -import IssueReporting /// A mechanism to communicate with a shared key's external system, synchronously or asynchronously. /// diff --git a/Sources/Sharing/SharedKeys/AppStorageKey.swift b/Sources/Sharing/SharedKeys/AppStorageKey.swift index 23f815e3..7b7e6fa5 100644 --- a/Sources/Sharing/SharedKeys/AppStorageKey.swift +++ b/Sources/Sharing/SharedKeys/AppStorageKey.swift @@ -2,7 +2,6 @@ import ConcurrencyExtras public import Dependencies @preconcurrency public import Foundation - import IssueReporting #if canImport(AppKit) import AppKit diff --git a/Sources/Sharing/SharedReader.swift b/Sources/Sharing/SharedReader.swift index 1446474b..ccce0339 100644 --- a/Sources/Sharing/SharedReader.swift +++ b/Sources/Sharing/SharedReader.swift @@ -1,5 +1,4 @@ import Foundation -import IssueReporting public import Observation public import PerceptionCore diff --git a/Sources/Sharing/Traits/IdentifiedCollections.swift b/Sources/Sharing/Traits/IdentifiedCollections.swift index 5c1aa939..a23c7676 100644 --- a/Sources/Sharing/Traits/IdentifiedCollections.swift +++ b/Sources/Sharing/Traits/IdentifiedCollections.swift @@ -1,6 +1,5 @@ #if IdentifiedCollections public import IdentifiedCollections - import IssueReporting extension RangeReplaceableCollection { /// Creates an collection of shared elements from a shared collection. diff --git a/Sources/Sharing/Traits/IssueReporting.swift b/Sources/Sharing/Traits/IssueReporting.swift new file mode 100644 index 00000000..c8dc4e24 --- /dev/null +++ b/Sources/Sharing/Traits/IssueReporting.swift @@ -0,0 +1,143 @@ +#if IssueReporting + import IssueReporting + + @_transparent + public func reportIssue( + _ message: @autoclosure () -> String? = nil, + fileID: StaticString = #fileID, + filePath: StaticString = #filePath, + line: UInt = #line, + column: UInt = #column + ) { + IssueReporting.reportIssue( + message(), + fileID: fileID, + filePath: filePath, + line: line, + column: column + ) + } + + @_transparent + func reportIssue( + _ error: any Error, + _ message: @autoclosure () -> String? = nil, + fileID: StaticString = #fileID, + filePath: StaticString = #filePath, + line: UInt = #line, + column: UInt = #column + ) { + IssueReporting.reportIssue( + error, + message(), + fileID: fileID, + filePath: filePath, + line: line, + column: column + ) + } +#else + public import Foundation + + #if canImport(os) + public import os + #endif + + @_transparent + public func reportIssue( + _ message: @autoclosure () -> String? = nil, + fileID: StaticString = #fileID, + filePath: StaticString = #filePath, + line: UInt = #line, + column: UInt = #column + ) { + runtimeWarn( + message(), + fileID: fileID, + line: line, + ) + } + + @_transparent + func reportIssue( + _ error: any Error, + _ message: @autoclosure () -> String? = nil, + fileID: StaticString = #fileID, + filePath: StaticString = #filePath, + line: UInt = #line, + column: UInt = #column + ) { + runtimeWarn( + "Caught error: \(error)\(message().map { ": \($0)" } ?? "")", + fileID: fileID, + line: line, + ) + } + + @_transparent + @inlinable + func runtimeWarn( + _ message: @autoclosure () -> String?, + fileID: StaticString, + line: UInt + ) { + #if canImport(os) + guard ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] != "1" + else { + print("🟣 \(fileID):\(line): \(message() ?? "")") + return + } + let moduleName = String( + Substring("\(fileID)".utf8.prefix(while: { $0 != UTF8.CodeUnit(ascii: "/") })) + ) + var message = message() ?? "" + if message.isEmpty { + message = "Issue reported" + } + os_log( + .fault, + dso: dso, + log: OSLog(subsystem: "com.apple.runtime-issues", category: moduleName), + "%@", + "\(isTesting ? "\(fileID):\(line): " : "")\(message)" + ) + #else + fputs("\(message)\n", stderr) + printError("\(fileID):\(line): \(message() ?? "")") + #endif + } + + @usableFromInline nonisolated(unsafe) let dso: UnsafeRawPointer = { + let count = _dyld_image_count() + for i in 0.. Date: Fri, 3 Jul 2026 10:38:37 -0700 Subject: [PATCH 02/10] fix --- Sources/Sharing/Traits/IssueReporting.swift | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/Sources/Sharing/Traits/IssueReporting.swift b/Sources/Sharing/Traits/IssueReporting.swift index c8dc4e24..5f1bb95e 100644 --- a/Sources/Sharing/Traits/IssueReporting.swift +++ b/Sources/Sharing/Traits/IssueReporting.swift @@ -37,13 +37,14 @@ ) } #else - public import Foundation - #if canImport(os) public import os + public import Foundation #endif - @_transparent + #if canImport(os) + @_transparent + #endif public func reportIssue( _ message: @autoclosure () -> String? = nil, fileID: StaticString = #fileID, @@ -58,7 +59,9 @@ ) } - @_transparent + #if canImport(os) + @_transparent + #endif func reportIssue( _ error: any Error, _ message: @autoclosure () -> String? = nil, @@ -74,8 +77,10 @@ ) } - @_transparent - @inlinable + #if canImport(os) + @_transparent + @inlinable + #endif func runtimeWarn( _ message: @autoclosure () -> String?, fileID: StaticString, From 214699f9db55977357ac4404249b19b92def78de Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Fri, 3 Jul 2026 10:39:31 -0700 Subject: [PATCH 03/10] wip --- Sources/Sharing/Traits/IssueReporting.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/Sharing/Traits/IssueReporting.swift b/Sources/Sharing/Traits/IssueReporting.swift index 5f1bb95e..85fe22fb 100644 --- a/Sources/Sharing/Traits/IssueReporting.swift +++ b/Sources/Sharing/Traits/IssueReporting.swift @@ -40,6 +40,8 @@ #if canImport(os) public import os public import Foundation + #else + import Foundation #endif #if canImport(os) @@ -108,7 +110,6 @@ ) #else fputs("\(message)\n", stderr) - printError("\(fileID):\(line): \(message() ?? "")") #endif } From 844d1b8802a4c7b8f5c47e0b87820386264dea13 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Fri, 3 Jul 2026 10:47:20 -0700 Subject: [PATCH 04/10] wip --- Sources/Sharing/Traits/IssueReporting.swift | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/Sources/Sharing/Traits/IssueReporting.swift b/Sources/Sharing/Traits/IssueReporting.swift index 85fe22fb..a089d809 100644 --- a/Sources/Sharing/Traits/IssueReporting.swift +++ b/Sources/Sharing/Traits/IssueReporting.swift @@ -114,17 +114,19 @@ } @usableFromInline nonisolated(unsafe) let dso: UnsafeRawPointer = { - let count = _dyld_image_count() - for i in 0.. Date: Fri, 3 Jul 2026 11:25:31 -0700 Subject: [PATCH 05/10] wip --- Sources/Sharing/Traits/IssueReporting.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/Sharing/Traits/IssueReporting.swift b/Sources/Sharing/Traits/IssueReporting.swift index a089d809..1632bae9 100644 --- a/Sources/Sharing/Traits/IssueReporting.swift +++ b/Sources/Sharing/Traits/IssueReporting.swift @@ -109,6 +109,10 @@ "\(isTesting ? "\(fileID):\(line): " : "")\(message)" ) #else + var message = message() ?? "" + if message.isEmpty { + message = "Issue reported" + } fputs("\(message)\n", stderr) #endif } From 5bf07c557d5199accec33d2616c211730d98bddd Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Fri, 3 Jul 2026 11:26:41 -0700 Subject: [PATCH 06/10] wip --- Sources/Sharing/Traits/IssueReporting.swift | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/Sources/Sharing/Traits/IssueReporting.swift b/Sources/Sharing/Traits/IssueReporting.swift index 1632bae9..0f7fb9c7 100644 --- a/Sources/Sharing/Traits/IssueReporting.swift +++ b/Sources/Sharing/Traits/IssueReporting.swift @@ -88,19 +88,19 @@ fileID: StaticString, line: UInt ) { + var message = message() ?? "" + if message.isEmpty { + message = "Issue reported" + } #if canImport(os) guard ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] != "1" else { - print("🟣 \(fileID):\(line): \(message() ?? "")") + print("🟣 \(fileID):\(line): \(message)") return } let moduleName = String( Substring("\(fileID)".utf8.prefix(while: { $0 != UTF8.CodeUnit(ascii: "/") })) ) - var message = message() ?? "" - if message.isEmpty { - message = "Issue reported" - } os_log( .fault, dso: dso, @@ -109,10 +109,6 @@ "\(isTesting ? "\(fileID):\(line): " : "")\(message)" ) #else - var message = message() ?? "" - if message.isEmpty { - message = "Issue reported" - } fputs("\(message)\n", stderr) #endif } From 5ced5610ba8dc349fc376e3947a8d4b14c75570b Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Fri, 3 Jul 2026 11:34:18 -0700 Subject: [PATCH 07/10] wip --- Sources/Sharing/Traits/IssueReporting.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Sharing/Traits/IssueReporting.swift b/Sources/Sharing/Traits/IssueReporting.swift index 0f7fb9c7..f6ae004b 100644 --- a/Sources/Sharing/Traits/IssueReporting.swift +++ b/Sources/Sharing/Traits/IssueReporting.swift @@ -41,7 +41,7 @@ public import os public import Foundation #else - import Foundation + @preconcurrency import Foundation #endif #if canImport(os) From f6b061e3dc367e0546c368f66a1fd031b4535ee3 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Fri, 3 Jul 2026 13:54:39 -0700 Subject: [PATCH 08/10] wip --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6bc36fbf..58a590aa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: matrix: xcode: ['26.5'] config: ['debug', 'release'] - traits: ['', 'CasePaths', 'CustomDump', 'IdentifiedCollections'] + traits: ['', 'CasePaths', 'CustomDump', 'IdentifiedCollections', 'IssueReporting'] runs-on: macos-26 steps: - uses: actions/checkout@v4 @@ -35,7 +35,7 @@ jobs: matrix: swift: - '6.3' - traits: ['', 'CasePaths', 'CustomDump', 'IdentifiedCollections'] + traits: ['', 'CasePaths', 'CustomDump', 'IdentifiedCollections', 'IssueReporting'] runs-on: ubuntu-latest container: swift:${{ matrix.swift }} steps: From 978ba6be442dc0540bb164007a360505f49452fa Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Fri, 3 Jul 2026 13:59:03 -0700 Subject: [PATCH 09/10] fix --- Sources/Sharing/Traits/IssueReporting.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/Sharing/Traits/IssueReporting.swift b/Sources/Sharing/Traits/IssueReporting.swift index f6ae004b..6781810c 100644 --- a/Sources/Sharing/Traits/IssueReporting.swift +++ b/Sources/Sharing/Traits/IssueReporting.swift @@ -1,8 +1,8 @@ #if IssueReporting - import IssueReporting + public import IssueReporting @_transparent - public func reportIssue( + func reportIssue( _ message: @autoclosure () -> String? = nil, fileID: StaticString = #fileID, filePath: StaticString = #filePath, From 4f4510162cdd778f0bb8e1a6d2e323f31e13f687 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Fri, 3 Jul 2026 14:07:08 -0700 Subject: [PATCH 10/10] wip --- Package.swift | 1 + Package@swift-5.9.swift | 1 + Package@swift-6.0.swift | 1 + Tests/SharingTests/AppStorageTests.swift | 134 +++++++++--------- Tests/SharingTests/CasePathsTraitTests.swift | 38 ++--- Tests/SharingTests/ContinuationTests.swift | 102 ++++++------- .../SharedChangeTrackerTests.swift | 2 +- 7 files changed, 144 insertions(+), 135 deletions(-) diff --git a/Package.swift b/Package.swift index 3e9dff87..4032ffce 100644 --- a/Package.swift +++ b/Package.swift @@ -126,6 +126,7 @@ package.traits.insert( : [ "CustomDump", "IdentifiedCollections", + "IssueReporting", ] ) ) diff --git a/Package@swift-5.9.swift b/Package@swift-5.9.swift index e8139b5c..aa2b4fb0 100644 --- a/Package@swift-5.9.swift +++ b/Package@swift-5.9.swift @@ -58,5 +58,6 @@ for target in package.targets { target.swiftSettings?.append(contentsOf: [ .define("CustomDump"), .define("IdentifiedCollections"), + .define("IssueReporting"), ]) } diff --git a/Package@swift-6.0.swift b/Package@swift-6.0.swift index a2c79b0c..c25bad0e 100644 --- a/Package@swift-6.0.swift +++ b/Package@swift-6.0.swift @@ -69,6 +69,7 @@ for target in package.targets { target.swiftSettings?.append(contentsOf: [ .define("CustomDump"), .define("IdentifiedCollections"), + .define("IssueReporting"), .enableUpcomingFeature("ExistentialAny"), .enableUpcomingFeature("ImmutableWeakCaptures"), .enableUpcomingFeature("InferIsolatedConformances"), diff --git a/Tests/SharingTests/AppStorageTests.swift b/Tests/SharingTests/AppStorageTests.swift index 005f9f21..40ee2e1e 100644 --- a/Tests/SharingTests/AppStorageTests.swift +++ b/Tests/SharingTests/AppStorageTests.swift @@ -139,79 +139,81 @@ #expect(store.value(forKey: "bool") == nil) } - @Test func invalidKeyWarning() async { - await withKnownIssue { - @Shared(.appStorage("co.pointfree.isEnabled")) var isEnabled = false - store.set(true, forKey: "co.pointfree.isEnabled") - await MainActor.run { - #expect(isEnabled) - } - } matching: { - $0.description.hasSuffix( - """ - A Shared app storage key ("co.pointfree.isEnabled") contains an invalid character \ - (".") for key-value observation. External updates will be \ - observed less efficiently and accurately via notification center, instead. - - Please reformat this key by removing invalid characters in order to ensure efficient, \ - cross-process observation. - - If you cannot control the format of this key and would like to silence this warning, \ - override the '\\.appStorageKeyFormatWarningEnabled' dependency at the entry point of \ - your application. For example: - - import Dependencies - - @main - struct MyApp: App { - init() { - prepareDependencies { - $0.appStorageKeyFormatWarningEnabled = false + #if IssueReporting + @Test func invalidKeyWarning() async { + await withKnownIssue { + @Shared(.appStorage("co.pointfree.isEnabled")) var isEnabled = false + store.set(true, forKey: "co.pointfree.isEnabled") + await MainActor.run { + #expect(isEnabled) + } + } matching: { + $0.description.hasSuffix( + """ + A Shared app storage key ("co.pointfree.isEnabled") contains an invalid character \ + (".") for key-value observation. External updates will be \ + observed less efficiently and accurately via notification center, instead. + + Please reformat this key by removing invalid characters in order to ensure efficient, \ + cross-process observation. + + If you cannot control the format of this key and would like to silence this warning, \ + override the '\\.appStorageKeyFormatWarningEnabled' dependency at the entry point of \ + your application. For example: + + import Dependencies + + @main + struct MyApp: App { + init() { + prepareDependencies { + $0.appStorageKeyFormatWarningEnabled = false + } + // ... } // ... } - // ... - } - """ - ) - } - - await withKnownIssue { - @Shared(.appStorage("@count")) var count = 0 - store.set(42, forKey: "@count") - await MainActor.run { - #expect(count == 42) + """ + ) } - } matching: { - $0.description.hasSuffix( - """ - A Shared app storage key ("@count") contains an invalid character \ - ("@") for key-value observation. External updates will be \ - observed less efficiently and accurately via notification center, instead. - - Please reformat this key by removing invalid characters in order to ensure efficient, \ - cross-process observation. - - If you cannot control the format of this key and would like to silence this warning, \ - override the '\\.appStorageKeyFormatWarningEnabled' dependency at the entry point of \ - your application. For example: - - import Dependencies - - @main - struct MyApp: App { - init() { - prepareDependencies { - $0.appStorageKeyFormatWarningEnabled = false + + await withKnownIssue { + @Shared(.appStorage("@count")) var count = 0 + store.set(42, forKey: "@count") + await MainActor.run { + #expect(count == 42) + } + } matching: { + $0.description.hasSuffix( + """ + A Shared app storage key ("@count") contains an invalid character \ + ("@") for key-value observation. External updates will be \ + observed less efficiently and accurately via notification center, instead. + + Please reformat this key by removing invalid characters in order to ensure efficient, \ + cross-process observation. + + If you cannot control the format of this key and would like to silence this warning, \ + override the '\\.appStorageKeyFormatWarningEnabled' dependency at the entry point of \ + your application. For example: + + import Dependencies + + @main + struct MyApp: App { + init() { + prepareDependencies { + $0.appStorageKeyFormatWarningEnabled = false + } + // ... } // ... } - // ... - } - """ - ) + """ + ) + } } - } + #endif @Test func invalidKeyWarningSuppression() async { withDependencies { @@ -250,7 +252,7 @@ } } - #if DEBUG + #if DEBUG && IssueReporting @Test func suiteWarning() { let suiteName = NSTemporaryDirectory() + "suite-warning" @Shared(.appStorage("count", store: UserDefaults(suiteName: suiteName)!)) var count = 0 diff --git a/Tests/SharingTests/CasePathsTraitTests.swift b/Tests/SharingTests/CasePathsTraitTests.swift index 1153d503..0c9ab887 100644 --- a/Tests/SharingTests/CasePathsTraitTests.swift +++ b/Tests/SharingTests/CasePathsTraitTests.swift @@ -89,26 +89,28 @@ } } - @Test func tracking() throws { - @Shared(value: Route.detail(0)) var model - let _detail: Shared? = $model.detail - let detail = try #require(_detail) - - withKnownIssue { - do { - let tracker = SharedChangeTracker() - tracker.track { - detail.withLock { $0 += 1 } + #if IssueReporting + @Test func tracking() throws { + @Shared(value: Route.detail(0)) var model + let _detail: Shared? = $model.detail + let detail = try #require(_detail) + + withKnownIssue { + do { + let tracker = SharedChangeTracker() + tracker.track { + detail.withLock { $0 += 1 } + } } + } matching: { + print("# " + $0.description) + return $0.description.hasSuffix( + """ + Tracked unasserted changes to 'Shared(value: SharingTests.CasePathsTraitTests.Route.detail(1))': SharingTests.CasePathsTraitTests.Route.detail(0) → SharingTests.CasePathsTraitTests.Route.detail(1) + """ + ) } - } matching: { - print("# " + $0.description) - return $0.description.hasSuffix( - """ - Tracked unasserted changes to 'Shared(value: SharingTests.CasePathsTraitTests.Route.detail(1))': SharingTests.CasePathsTraitTests.Route.detail(0) → SharingTests.CasePathsTraitTests.Route.detail(1) - """ - ) } - } + #endif } #endif diff --git a/Tests/SharingTests/ContinuationTests.swift b/Tests/SharingTests/ContinuationTests.swift index a6c4a77d..2ec3e8af 100644 --- a/Tests/SharingTests/ContinuationTests.swift +++ b/Tests/SharingTests/ContinuationTests.swift @@ -1,59 +1,61 @@ -import Sharing -import Testing +#if IssueReporting + import Sharing + import Testing -@Suite struct ContinuationTests { - @Test func dontResumeLoadContinuation() async throws { - try await withKnownIssue { - @SharedReader(DontResumeLoadContinuationKey()) var value = 0 - #expect($value.isLoading == false) - try await $value.load() - #expect($value.isLoading == false) - } matching: { issue in - issue.description.hasSuffix( - """ - 'DontResumeLoadContinuationKey()' leaked its continuation without one of its resume \ - methods being invoked. This will cause tasks waiting on it to resume immediately. - """ - ) + @Suite struct ContinuationTests { + @Test func dontResumeLoadContinuation() async throws { + try await withKnownIssue { + @SharedReader(DontResumeLoadContinuationKey()) var value = 0 + #expect($value.isLoading == false) + try await $value.load() + #expect($value.isLoading == false) + } matching: { issue in + issue.description.hasSuffix( + """ + 'DontResumeLoadContinuationKey()' leaked its continuation without one of its resume \ + methods being invoked. This will cause tasks waiting on it to resume immediately. + """ + ) + } } - } - @Test func dontResumeSaveContinuation() async throws { - @Shared(DontResumeSaveContinuationKey()) var value = 0 - try await withKnownIssue { - try await $value.save() - } matching: { issue in - issue.description.hasSuffix( - """ - 'DontResumeSaveContinuationKey()' leaked its continuation without one of its resume \ - methods being invoked. This will cause tasks waiting on it to resume immediately. - """ - ) + @Test func dontResumeSaveContinuation() async throws { + @Shared(DontResumeSaveContinuationKey()) var value = 0 + try await withKnownIssue { + try await $value.save() + } matching: { issue in + issue.description.hasSuffix( + """ + 'DontResumeSaveContinuationKey()' leaked its continuation without one of its resume \ + methods being invoked. This will cause tasks waiting on it to resume immediately. + """ + ) + } } } -} -private struct DontResumeLoadContinuationKey: SharedReaderKey { - var id: some Hashable { 0 } - func load(context: LoadContext, continuation: LoadContinuation) {} - func subscribe( - context: LoadContext, - subscriber: SharedSubscriber - ) -> SharedSubscription { - SharedSubscription {} + private struct DontResumeLoadContinuationKey: SharedReaderKey { + var id: some Hashable { 0 } + func load(context: LoadContext, continuation: LoadContinuation) {} + func subscribe( + context: LoadContext, + subscriber: SharedSubscriber + ) -> SharedSubscription { + SharedSubscription {} + } } -} -private struct DontResumeSaveContinuationKey: SharedKey { - var id: some Hashable { 0 } - func load(context: LoadContext, continuation: LoadContinuation) { - continuation.resume(returning: 42) - } - func subscribe( - context: LoadContext, - subscriber: SharedSubscriber - ) -> SharedSubscription { - SharedSubscription {} + private struct DontResumeSaveContinuationKey: SharedKey { + var id: some Hashable { 0 } + func load(context: LoadContext, continuation: LoadContinuation) { + continuation.resume(returning: 42) + } + func subscribe( + context: LoadContext, + subscriber: SharedSubscriber + ) -> SharedSubscription { + SharedSubscription {} + } + func save(_ value: Int, context: SaveContext, continuation: SaveContinuation) {} } - func save(_ value: Int, context: SaveContext, continuation: SaveContinuation) {} -} +#endif diff --git a/Tests/SharingTests/SharedChangeTrackerTests.swift b/Tests/SharingTests/SharedChangeTrackerTests.swift index d55fcf7c..f2e5ed51 100644 --- a/Tests/SharingTests/SharedChangeTrackerTests.swift +++ b/Tests/SharingTests/SharedChangeTrackerTests.swift @@ -1,4 +1,4 @@ -#if CustomDump +#if CustomDump && IssueReporting import Combine import CustomDump import PerceptionCore