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

Non inline tests not working for read based APIs #5

Open
jcavar opened this issue Mar 14, 2025 · 10 comments
Open

Non inline tests not working for read based APIs #5

jcavar opened this issue Mar 14, 2025 · 10 comments

Comments

@jcavar
Copy link

jcavar commented Mar 14, 2025

Currently, all GRDBSnapshotTesting tests pass, but none of them are using assertSnapshot inside of read closure.

I've added a test that crashes when run:

Image

I think this might be an issue with the upstream library (or XCTest, which requires attachments to be recorded on the main thread), but it might be worth revisiting this API so that the issue can be worked around.

@groue
Copy link
Owner

groue commented Mar 14, 2025

Thank you for the report @jcavar,

I can reproduce indeed. This crashes because the snapshot is created, from the main thread, but not from DispatchQueue.main, and this creates a deadlock in the snapshot testing library.

This deadlock was introduced in swift-snapshot-testing version 1.18.0, due to pointfreeco/swift-snapshot-testing#946.

// Crash
func test_issue_5() throws {
    let dbQueue = try DatabaseQueue()
    try dbQueue.read { db in
        assertSnapshot(of: "SELECT 'Hello, world!'", as: .dump(db))
    }
}

This is, strictly speaking, a breaking change in swift-snapshot-testing. I recommend that you open an issue in this repository.

You have two possible workarounds:

  1. Use a version of swift-snapshot-testing that did not introduce the breaking change. For example, version 1.17.7 does not crash.

  2. Avoid performing the snapshot from the main thread. For example, use await:

    // Pass
    @MainActor func test_issue_5() async throws {
        let dbQueue = try DatabaseQueue()
        try await dbQueue.read { db in
            assertSnapshot(of: "SELECT 'Hello, world!'", as: .dump(db))
        }
    }

@jcavar
Copy link
Author

jcavar commented Mar 16, 2025

Thank you for your response @groue. Which Xcode version are you running your passing example with?

Your example crashes for me with "Current context must not be nil" in Xcode 16.2.

Image

@groue
Copy link
Owner

groue commented Mar 16, 2025

OK, I just ran it in Xcode 16.2, and swift-snapshot-testing crashes on the first run (but the snapshot is created). The test passes on the second run. Sorry I did not spot this crash in my previous comment.

If this not acceptable, you can use the first workaround, which is using swift-snapshot-testing 1.17.7 (right before it has introduced the breaking change). Or use inline snapshots, that work too.

@jcavar
Copy link
Author

jcavar commented Mar 16, 2025

The test passes on the second run

This is true, but it also crashes if the test fails (e.g. change "SELECT 'Hello, world!" to "SELECT 'world!")

Or use inline snapshots, that work too.

I was considering this, but my output is quite large for inline snapshot tests

you can use the first workaround, which is using swift-snapshot-testing 1.17.7

We use snapshot tests quite heavily for other things, so I am worried to fix this dependency to an old version to workaround this.

Appreciate your support!

@groue
Copy link
Owner

groue commented Mar 16, 2025

Sorry I do not suggest anything better. We face a breaking change in a dependency. This should be notified to the maintainers of swift-snapshot-testing. If you don't know how to do it, I can help. But basically, it is opening an issue that describes what happens to your app, with a link to this issue, since it describes exactly how and when the breaking change happened.

@jcavar
Copy link
Author

jcavar commented Mar 17, 2025

No problem, I can open the issue in the original repository - I would just like to understand the scope of it first.

For me, even with 1.17.7 swift-snapshot-testing, async version crashes.

It seems to me that there are 2 issues here:

  1. Non async version doesn't work as it expects DispatchQueue.main
  2. async version doesn't work because XCTTest API is invoked from non main thread

Let me know your thoughts.

@groue
Copy link
Owner

groue commented Mar 17, 2025

For me as well there are two issues.

  • The regression (breaking change) that makes 1.18.0 unable to take snapshots from the main thread but not on the main DispatchQueue.
  • The crash in the async test. Maybe one should not take such snapshot, but I did not check the documentation very precisely, so I don't know if this is a bug in the SnapshotTesting library, or if such a test should not be written in the first place.

@jcavar
Copy link
Author

jcavar commented Mar 20, 2025

Thanks @groue!

Just to clarify this:

The regression (breaking change) that makes 1.18.0 unable to take snapshots from the main thread but not on the main DispatchQueue.

The issue is that:

  1. The test is run on the main thread
  2. GRDB calls read in a sync of a custom reader queue -> this blocks the main thread
  3. We call assertSnapshot inside of a closure (inside of sync of a custom reader queue)
  4. assertSnapshot calls DispatchQueue.main.sync, which causes a deadlock

Is my understanding correct?

@groue
Copy link
Owner

groue commented Mar 20, 2025

The test is run on the main thread

Correct 👍

GRDB calls read in a sync of a custom reader queue -> this blocks the main thread

The main thread is not blocked. You can run, on the main thread, some code inside a DispatchQueue which is not DispatchQueue.main:

// On the main thread
let queue = DispatchQueue(label: "test")
queue.sync {
    print("Hello")

    // Success: we're on the main thread
    assert(Thread.isMainThread)

    // Failure: we're not on the main DispatchQueue
    dispatchPrecondition(condition: .onQueue(.main))
}

We call assertSnapshot inside of a closure (inside of sync of a custom reader queue)

Yes 👍

assertSnapshot calls DispatchQueue.main.sync, which causes a deadlock

Correct 👍 This deadlock, in recent SDK releases, crashes instead of just dead-locking.

@jcavar
Copy link
Author

jcavar commented Mar 24, 2025

I understand, thank you.

Report created here:

pointfreeco/swift-snapshot-testing#961

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants