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

swift 6.0.1 crash when running tests swift testing with @MainActor annotation only on linux #919

Open
doozMen opened this issue Oct 17, 2024 · 2 comments

Comments

@doozMen
Copy link
Contributor

doozMen commented Oct 17, 2024

Describe the bug
Only happens on linux

  1. clone https://github.com/doozMen/swift-testing-main-bug
  2. run unit tests with visual studio in debug

You will see it crashes. The docs state that Thread.isMainThread is not available. Weirdly it is on macOS just not on linux.

To Reproduce
run the test project on linux and maybe add code

    #expect(Thread.isMainThread == true)

This will work on mac and not on linux

Expected behavior

Not creating deadlock because of doing dispach on main in sync from main

Screenshots
Screenshot 2024-10-17 at 16 16 31

Environment

  • swift-snapshot-testing version [1.17..5]
  • Swift [6.0.1]
  • OS: aarch64-unknown-linux-gnu

Additional context
Maybe also a bug in swift but not sure as when you the check is unavailable in async context according to the docs. So you should not rely on it?

Maybe have a asser option that is marked with @mainactor might be better?

@stephencelis
Copy link
Member

@doozMen Thanks for the report. We're running SnapshotTesting on Linux for pointfree.co, but are on a particular branch that uses this logic to dispatch to the main queue instead:

static func registerIfNeeded() {
DispatchQueue.mainSync {
if !registered {
registered = true
XCTestObservationCenter.shared.addTestObserver(CleanCounterBetweenTestCases())
}
}
}

Would you be open to exploring if this fix would address the problem for you when applied to the main branch?

@peterkovacs
Copy link
Contributor

peterkovacs commented Jan 23, 2025

This issue is also affecting me in a very similar way. My project is using swift 6, and swift-testing exclusively. All my suites are marked as @MainActor and I get the following crash at run-time:

*** Signal 5: Backtracing from 0xffffa84b3e98... done ***

*** Program crashed: System trap at 0x0000ffffa84b3e98 ***

Thread 0 crashed:

 0                  0x0000ffffa84b3e98 __DISPATCH_WAIT_FOR_QUEUE__ + 352 in libdispatch.so
 1 [ra]             0x0000ffffa84b38d4 _dispatch_sync_f_slow + 163 in libdispatch.so
 2 [ra]             0x0000ffffa844eb6c DispatchQueue.sync(execute:) + 147 in libswiftDispatch.so
 3 [ra]             0x0000aaaab6175a04 static CleanCounterBetweenTestCases.registerIfNeeded() + 135 in SwiftTUIPackageTests.xctest at /app/.build/checkouts/swift-snapshot-testing/Sources/SnapshotTesting/AssertSnapshot.swift:484:26
 4 [ra]             0x0000aaaab617a7d0 verifySnapshot<A, B>(of:as:named:record:snapshotDirectory:timeout:fileID:file:testName:line:column:) + 367 in SwiftTUIPackageTests.xctest at /app/.build/checkouts/swift-snapshot-testing/Sources/SnapshotTesting/AssertSnapshot.swift:288:32
 5 [ra]             0x0000aaaab617a508 assertSnapshot<A, B>(of:as:named:record:timeout:fileID:file:testName:line:column:) + 399 in SwiftTUIPackageTests.xctest at /app/.build/checkouts/swift-snapshot-testing/Sources/SnapshotTesting/AssertSnapshot.swift:114:17
 6 [ra]             0x0000aaaab6330f54 PaddingTests.testPaddingEdges(edges:) + 839 in SwiftTUIPackageTests.xctest at /app/Tests/SwiftTUITests/PaddingTests.swift:71:9
 7 [async]          0x0000aaaab63309a4 static PaddingTests.$s13SwiftTUITests12PaddingTestsV04testC5Edges4TestfMp_44functestPaddingEdges_edges_Edges_asyncthrowsfMu0_@Sendable (edges:) in SwiftTUIPackageTests.xctest at /tmp/swift-generated-sources/@__swiftmacro_13SwiftTUITests12PaddingTestsV04testC5Edges4TestfMp_.swift:4
 8 [async]          0x0000aaaab63316a4 implicit closure #1 in static PaddingTests.$s13SwiftTUITests12PaddingTestsV04testC5Edges4TestfMp_74__🟠$test_container__function__functestPaddingEdges_edges_Edges_asyncthowsfMu_.__tests.getter in SwiftTUIPackageTests.xctest at /tmp/swift-generated-sources/@__swiftmacro_13SwiftTUITests12PaddingTestsV04testC5Edges4TestfMp_.swift:35
 9 [async]          0x0000ffffa746bd34 closure #1 in closure #2 in Test.Case.Generator.init<>(arguments:parameters:testFunction:) in libTesting.so
10 [async] [thunk]  0x0000ffffa746ea14 partial apply for closure #1 in closure #2 in Test.Case.Generator.init<>(arguments:parameters:testFunction:) in libTesting.so
11 [async]          0x0000ffffa749e4f0 closure #1 in closure #1 in Runner._runTestCase(_:within:) in libTesting.so
12 [async] [thunk]  0x0000ffffa74a707c partial apply for closure #1 in closure #1 in Runner._runTestCase(_:within:) in libTesting.so
13 [async]          0x0000ffffa7466e98 specialized static Issue.withErrorRecording(at:configuration:isolation:_:) in libTesting.so
14 [async]          0x0000ffffa749de74 closure #1 in Runner._runTestCase(_:within:) in libTesting.so
15 [async] [thunk]  0x0000ffffa74a708c partial apply for closure #1 in Runner._runTestCase(_:within:) in libTesting.so
16 [async]          0x0000ffffa749760c specialized static Test.Case.withCurrent<A>(_:perform:) in libTesting.so
17 [async]          0x0000ffffa749da08 Runner._runTestCase(_:within:) in libTesting.so
18 [async]          0x0000ffffa749d5ac closure #2 in Runner._runTestCases<A>(_:within:) in libTesting.so
19 [async] [thunk]  0x0000ffffa74a7078 partial apply for closure #2 in Runner._runTestCases<A>(_:within:) in libTesting.so
20 [async]          0x0000ffffa74a32c4 specialized closure #1 in closure #1 in Runner._forEach<A, B>(in:for:_:) in libTesting.so
21 [async] [thunk]  0x0000ffffa74a7074 partial apply for specialized closure #1 in closure #1 in Runner._forEach<A, B>(in:for:_:) in libTesting.so
22 [async] [system] 0x0000ffffa87d6464 completeTaskWithClosure(swift::AsyncContext*, swift::SwiftError*) in libswift_Concurrency.so

However, when I apply the following diff (to main), as suggested by your comment @stephencelis, the tests pass:

diff --git a/Sources/SnapshotTesting/AssertSnapshot.swift b/Sources/SnapshotTesting/AssertSnapshot.swift
index 63be5bf..6e3ec58 100644
--- a/Sources/SnapshotTesting/AssertSnapshot.swift
+++ b/Sources/SnapshotTesting/AssertSnapshot.swift
@@ -516,18 +516,33 @@ func sanitizePathComponent(_ string: String) -> String {
   }
 #endif
 
+  extension DispatchQueue {
+  private static let key = DispatchSpecificKey<UInt8>()
+  private static let value: UInt8 = 0
+
+  fileprivate static func mainSync<R>(execute block: () -> R) -> R {
+    Self.main.setSpecific(key: key, value: value)
+    if getSpecific(key: key) == value {
+      return block()
+    } else {
+      return main.sync(execute: block)
+    }
+  }
+}
+
+
 // We need to clean counter between tests executions in order to support test-iterations.
 private class CleanCounterBetweenTestCases: NSObject, XCTestObservation {
   private static var registered = false
 
   static func registerIfNeeded() {
-    if Thread.isMainThread {
-      doRegisterIfNeeded()
-    } else {
-      DispatchQueue.main.sync {
-        doRegisterIfNeeded()
-      }
-    }
+         DispatchQueue.mainSync {
+     if !registered {
+       registered = true
+       XCTestObservationCenter.shared.addTestObserver(CleanCounterBetweenTestCases())
+     }
+   }
+
   }
 
   private static func doRegisterIfNeeded() {

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

3 participants