Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test with CheckedContinuation loses testing context and crashes #804

Closed
bernoulliconvergenator opened this issue Nov 4, 2024 · 1 comment
Labels
bug Something isn't working

Comments

@bernoulliconvergenator
Copy link

bernoulliconvergenator commented Nov 4, 2024

Description

Consider an Observable model that interacts with an actor model:

actor ActorModel {
   func doMyThing() -> Int {
      return 1
   }
}

@Observable final class ObservableModel: @unchecked Sendable {
   private(set) var actorValue: Int?
   private let actorModel = ActorModel()

   @MainActor func buttonPressed() {
      actorValue = nil // reset

      Task.detached {
         let newActorValue = await self.actorModel.doMyThing()
         #expect(newActorValue == 1)
         Task { @MainActor [weak self] in
            self?.actorValue = 2 // *** !! OOPS ***
         }
      }
   }
}

To test that buttonPressed() resets actorValue and then asynchronously assigns actorValue after calling actorModel.doMyThing(), we can construct a test with observation tracking inside a checked continuation:

@Test @MainActor func shouldFail() async throws {
   let observableModel = ObservableModel()
   try #require(observableModel.actorValue == nil)

   try await withCheckedThrowingContinuation { (cont: CheckedContinuation<Void, Error>) -> Void in
      @Sendable func observeActorValue(count: Int) {
         withObservationTracking {
            print("observing observableModel.actorValue=\(String(describing: observableModel.actorValue)) count=\(count)")
         } onChange: {
            Task { @MainActor in
               print("onChange observableModel.actorValue=\(String(describing: observableModel.actorValue))")
               switch count {
               case 0:
                  #expect(observableModel.actorValue == nil)
               case 1:
                  #expect(observableModel.actorValue == 1)
                  cont.resume()
               default:
                  Issue.record("recursed on observeActorModel (\(count) times, expected once")
               }
               observeActorValue(count: count + 1) // recurse
            }
         }
      }
      observeActorValue(count: 0)

      observableModel.buttonPressed()

      do {
         try #require(observableModel.actorValue == nil)
      } catch {
         cont.resume(throwing: error)
      }
   }
}

Because of the OOPS, the #expect in case 1: fails. But we also get a crash:
image

◇ Test shouldFail() started.
observing observableModel.actorValue=nil count=0
onChange observableModel.actorValue=nil
observing observableModel.actorValue=nil count=1
onChange observableModel.actorValue=Optional(2)
✘ Test «unknown» recorded an issue at CheckedContinuationMultipleEventsShouldFail.swift:24:19: Expectation failed: (observableModel.actorValue → 2) == 1
XCTest/HarnessEventHandler.swift:168: Fatal error: Issue recorded event did not contain a test

The test passes if we set the correct value in buttonPressed() (ie no OOPS), but are we certain the passing #expect is realized by the testing context?

Expected behavior

The test should record an issue and not crash.

Actual behavior

The test crashed.

Steps to reproduce

BugSwiftTestingCheckedContinuation.zip

swift-testing version/commit hash

Version 16.1 (16B40)

Swift & OS version (output of swift --version ; uname -a)

swift-driver version: 1.115 Apple Swift version 6.0.2 (swiftlang-6.0.2.1.2 clang-1600.0.26.4)
Target: arm64-apple-macosx15.0
Darwin BernouliConvergenator.local 24.1.0 Darwin Kernel Version 24.1.0: Thu Oct 10 21:00:32 PDT 2024; root:xnu-11215.41.3~2/RELEASE_ARM64_T6030 arm64
@bernoulliconvergenator bernoulliconvergenator added the bug Something isn't working label Nov 4, 2024
@grynspan
Copy link
Contributor

grynspan commented Nov 4, 2024

Duplicate of #480

@grynspan grynspan marked this as a duplicate of #480 Nov 4, 2024
@grynspan grynspan closed this as not planned Won't fix, can't repro, duplicate, stale Nov 4, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants