From af233d8cac3d1b6e77b8957185af95e9a6c85e41 Mon Sep 17 00:00:00 2001 From: Brian Michel Date: Fri, 22 Sep 2023 20:04:07 -0400 Subject: [PATCH 1/5] Successfully compile and test on Windows --- .../Nimble/Matchers/PostNotification.swift | 4 +++ Sources/Nimble/Utils/AsyncAwait.swift | 3 ++ Sources/Nimble/Utils/AsyncTimerSequence.swift | 2 ++ Sources/Nimble/Utils/PollAwait.swift | 31 ++++++------------- Tests/NimbleTests/AsyncAwaitTest.swift | 2 +- .../Matchers/ThrowAssertionTest.swift | 16 +++++----- Tests/NimbleTests/PollingTest.swift | 4 +++ 7 files changed, 31 insertions(+), 31 deletions(-) diff --git a/Sources/Nimble/Matchers/PostNotification.swift b/Sources/Nimble/Matchers/PostNotification.swift index 6f604ae8c..c848b968a 100644 --- a/Sources/Nimble/Matchers/PostNotification.swift +++ b/Sources/Nimble/Matchers/PostNotification.swift @@ -43,7 +43,11 @@ internal class NotificationCollector { } } +#if !os(Windows) private let mainThread = pthread_self() +#else +private let mainThread = Thread.mainThread +#endif private func _postNotifications( _ predicate: Predicate<[Notification]>, diff --git a/Sources/Nimble/Utils/AsyncAwait.swift b/Sources/Nimble/Utils/AsyncAwait.swift index 12547249f..a21dfd2cc 100644 --- a/Sources/Nimble/Utils/AsyncAwait.swift +++ b/Sources/Nimble/Utils/AsyncAwait.swift @@ -1,6 +1,9 @@ #if !os(WASI) +#if canImport(CoreFoundation) import CoreFoundation +#endif + import Dispatch import Foundation diff --git a/Sources/Nimble/Utils/AsyncTimerSequence.swift b/Sources/Nimble/Utils/AsyncTimerSequence.swift index 6bd46b83c..83c2485f9 100644 --- a/Sources/Nimble/Utils/AsyncTimerSequence.swift +++ b/Sources/Nimble/Utils/AsyncTimerSequence.swift @@ -1,6 +1,8 @@ #if !os(WASI) +#if canImport(CoreFoundation) import CoreFoundation +#endif import Dispatch import Foundation diff --git a/Sources/Nimble/Utils/PollAwait.swift b/Sources/Nimble/Utils/PollAwait.swift index 177bd094b..21b51bc5f 100644 --- a/Sources/Nimble/Utils/PollAwait.swift +++ b/Sources/Nimble/Utils/PollAwait.swift @@ -1,6 +1,8 @@ #if !os(WASI) +#if canImport(CoreFoundation) import CoreFoundation +#endif import Dispatch import Foundation @@ -192,31 +194,16 @@ internal class AwaitPromiseBuilder { let timedOutSem = DispatchSemaphore(value: 0) let semTimedOutOrBlocked = DispatchSemaphore(value: 0) semTimedOutOrBlocked.signal() - let runLoop = CFRunLoopGetMain() - #if canImport(Darwin) - let runLoopMode = CFRunLoopMode.defaultMode.rawValue - #else - let runLoopMode = kCFRunLoopDefaultMode - #endif - CFRunLoopPerformBlock(runLoop, runLoopMode) { + let runLoop = RunLoop.main + runLoop.perform(inModes: [.default], block: { if semTimedOutOrBlocked.wait(timeout: .now()) == .success { timedOutSem.signal() semTimedOutOrBlocked.signal() if self.promise.resolveResult(.timedOut) { - CFRunLoopStop(CFRunLoopGetMain()) + runLoop._stop() } } - } - // potentially interrupt blocking code on run loop to let timeout code run - CFRunLoopStop(runLoop) - let now = DispatchTime.now() + forcefullyAbortTimeout.dispatchTimeInterval - let didNotTimeOut = timedOutSem.wait(timeout: now) != .success - let timeoutWasNotTriggered = semTimedOutOrBlocked.wait(timeout: .now()) == .success - if didNotTimeOut && timeoutWasNotTriggered { - if self.promise.resolveResult(.blockedRunLoop) { - CFRunLoopStop(CFRunLoopGetMain()) - } - } + }) } return self } @@ -302,7 +289,7 @@ internal class Awaiter { if completionCount < 2 { func completeBlock() { if promise.resolveResult(.completed(result)) { - CFRunLoopStop(CFRunLoopGetMain()) + RunLoop.main._stop() } } @@ -340,12 +327,12 @@ internal class Awaiter { do { if let result = try closure() { if promise.resolveResult(.completed(result)) { - CFRunLoopStop(CFRunLoopGetCurrent()) + RunLoop.current._stop() } } } catch let error { if promise.resolveResult(.errorThrown(error)) { - CFRunLoopStop(CFRunLoopGetCurrent()) + RunLoop.current._stop() } } } diff --git a/Tests/NimbleTests/AsyncAwaitTest.swift b/Tests/NimbleTests/AsyncAwaitTest.swift index 3c13dea76..ff80b8df0 100644 --- a/Tests/NimbleTests/AsyncAwaitTest.swift +++ b/Tests/NimbleTests/AsyncAwaitTest.swift @@ -85,7 +85,7 @@ final class AsyncAwaitTest: XCTestCase { // swiftlint:disable:this type_body_len @MainActor func testToEventuallyOnMain() async { await expect(1).toEventually(equal(1), timeout: .seconds(300)) - await expect { usleep(10); return 1 }.toEventually(equal(1)) + await expect { try? await Task.sleep(nanoseconds: 10_000); return 1 }.toEventually(equal(1)) } @MainActor diff --git a/Tests/NimbleTests/Matchers/ThrowAssertionTest.swift b/Tests/NimbleTests/Matchers/ThrowAssertionTest.swift index 916104e80..8d547d128 100644 --- a/Tests/NimbleTests/Matchers/ThrowAssertionTest.swift +++ b/Tests/NimbleTests/Matchers/ThrowAssertionTest.swift @@ -9,19 +9,19 @@ private let error: Error = NSError(domain: "test", code: 0, userInfo: nil) final class ThrowAssertionTest: XCTestCase { func testPositiveMatch() { - #if arch(x86_64) || arch(arm64) + #if (arch(x86_64) || arch(arm64)) && !os(Windows) expect { () -> Void in fatalError() }.to(throwAssertion()) #endif } func testErrorThrown() { - #if arch(x86_64) || arch(arm64) + #if (arch(x86_64) || arch(arm64)) && !os(Windows) expect { throw error }.toNot(throwAssertion()) #endif } func testPostAssertionCodeNotRun() { - #if arch(x86_64) || arch(arm64) + #if (arch(x86_64) || arch(arm64)) && !os(Windows) var reachedPoint1 = false var reachedPoint2 = false @@ -37,7 +37,7 @@ final class ThrowAssertionTest: XCTestCase { } func testNegativeMatch() { - #if arch(x86_64) || arch(arm64) + #if (arch(x86_64) || arch(arm64)) && !os(Windows) var reachedPoint1 = false expect { reachedPoint1 = true }.toNot(throwAssertion()) @@ -47,7 +47,7 @@ final class ThrowAssertionTest: XCTestCase { } func testPositiveMessage() { - #if arch(x86_64) || arch(arm64) + #if (arch(x86_64) || arch(arm64)) && !os(Windows) failsWithErrorMessage("expected to throw an assertion") { expect { () -> Void? in return }.to(throwAssertion()) } @@ -59,7 +59,7 @@ final class ThrowAssertionTest: XCTestCase { } func testNegativeMessage() { - #if arch(x86_64) || arch(arm64) + #if (arch(x86_64) || arch(arm64)) && !os(Windows) failsWithErrorMessage("expected to not throw an assertion") { expect { () -> Void in fatalError() }.toNot(throwAssertion()) } @@ -67,13 +67,13 @@ final class ThrowAssertionTest: XCTestCase { } func testNonVoidClosure() { - #if arch(x86_64) || arch(arm64) + #if (arch(x86_64) || arch(arm64)) && !os(Windows) expect { () -> Int in fatalError() }.to(throwAssertion()) #endif } func testChainOnThrowAssertion() { - #if arch(x86_64) || arch(arm64) + #if (arch(x86_64) || arch(arm64)) && !os(Windows) expect { () -> Int in return 5 }.toNot(throwAssertion()).to(equal(5)) #endif } diff --git a/Tests/NimbleTests/PollingTest.swift b/Tests/NimbleTests/PollingTest.swift index 2abe01402..ebaa1b6ea 100644 --- a/Tests/NimbleTests/PollingTest.swift +++ b/Tests/NimbleTests/PollingTest.swift @@ -1,7 +1,9 @@ #if !os(WASI) import Dispatch +#if canImport(CoreFoundation) import CoreFoundation +#endif import Foundation import XCTest import Nimble @@ -93,11 +95,13 @@ final class PollingTest: XCTestCase { } } + #if !os(Windows) func testWaitUntilTimesOutIfNotCalled() { failsWithErrorMessage("Waited more than 1.0 second") { waitUntil(timeout: .seconds(1)) { _ in return } } } + #endif func testWaitUntilTimesOutWhenExceedingItsTime() { var waiting = true From cca18600accd6d5569770512510733a1107fd630 Mon Sep 17 00:00:00 2001 From: Brian Michel Date: Fri, 22 Sep 2023 20:07:12 -0400 Subject: [PATCH 2/5] Add Windows CI job --- .github/workflows/ci-swiftpm.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/workflows/ci-swiftpm.yml b/.github/workflows/ci-swiftpm.yml index e15c91aa1..174f2fd50 100644 --- a/.github/workflows/ci-swiftpm.yml +++ b/.github/workflows/ci-swiftpm.yml @@ -50,3 +50,16 @@ jobs: - uses: actions/checkout@v4 - run: swift build -Xswiftc -suppress-warnings - run: swift test -Xswiftc -suppress-warnings --enable-test-discovery + + swiftpm_windows: + name: SwiftPM, Windows + runs-on: windows-latest + steps: + - uses: actions/checkout@v3 + - name: Install Swift + uses: compnerd/gha-setup-swift@main + with: + branch: swift-5.9-release + tag: 5.9-RELEASE + - name: Test Windows + run: swift test -Xswiftc -suppress-warnings From f0f85084706b27efae9b7239e59dcc582479f87c Mon Sep 17 00:00:00 2001 From: Brian Michel Date: Sat, 23 Sep 2023 09:01:32 -0400 Subject: [PATCH 3/5] Provide divergent implementations for stop We need to preserve the CFRunLoopStop calls for platforms which can use CoreFoundation. --- Sources/Nimble/Utils/PollAwait.swift | 29 ++++++++++++++++++++++++++++ Tests/NimbleTests/PollingTest.swift | 2 -- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/Sources/Nimble/Utils/PollAwait.swift b/Sources/Nimble/Utils/PollAwait.swift index 21b51bc5f..725cd8c6e 100644 --- a/Sources/Nimble/Utils/PollAwait.swift +++ b/Sources/Nimble/Utils/PollAwait.swift @@ -194,6 +194,20 @@ internal class AwaitPromiseBuilder { let timedOutSem = DispatchSemaphore(value: 0) let semTimedOutOrBlocked = DispatchSemaphore(value: 0) semTimedOutOrBlocked.signal() + #if canImport(CoreFoundation) + let runLoop = CFRunLoopGetMain() + CFRunLoopPerformBlock(runLoop, runLoopMode) { + if semTimedOutOrBlocked.wait(timeout: .now()) == .success { + timedOutSem.signal() + semTimedOutOrBlocked.signal() + if self.promise.resolveResult(.timedOut) { + CFRunLoopStop(CFRunLoopGetMain()) + } + } + } + // potentially interrupt blocking code on run loop to let timeout code run + CFRunLoopStop(runLoop) + #else let runLoop = RunLoop.main runLoop.perform(inModes: [.default], block: { if semTimedOutOrBlocked.wait(timeout: .now()) == .success { @@ -204,6 +218,9 @@ internal class AwaitPromiseBuilder { } } }) + // potentially interrupt blocking code on run loop to let timeout code run + runLoop._stop() + #endif } return self } @@ -289,7 +306,11 @@ internal class Awaiter { if completionCount < 2 { func completeBlock() { if promise.resolveResult(.completed(result)) { + #if canImport(CoreFoundation) + CFRunLoopStop(CFRunLoopGetMain()) + #else RunLoop.main._stop() + #endif } } @@ -327,12 +348,20 @@ internal class Awaiter { do { if let result = try closure() { if promise.resolveResult(.completed(result)) { + #if canImport(CoreFoundation) + CFRunLoopStop(CFRunLoopGetCurrent()) + #else RunLoop.current._stop() + #endif } } } catch let error { if promise.resolveResult(.errorThrown(error)) { + #if canImport(CoreFoundation) + CFRunLoopStop(CFRunLoopGetCurrent()) + #else RunLoop.current._stop() + #endif } } } diff --git a/Tests/NimbleTests/PollingTest.swift b/Tests/NimbleTests/PollingTest.swift index ebaa1b6ea..0b7a7c394 100644 --- a/Tests/NimbleTests/PollingTest.swift +++ b/Tests/NimbleTests/PollingTest.swift @@ -95,13 +95,11 @@ final class PollingTest: XCTestCase { } } - #if !os(Windows) func testWaitUntilTimesOutIfNotCalled() { failsWithErrorMessage("Waited more than 1.0 second") { waitUntil(timeout: .seconds(1)) { _ in return } } } - #endif func testWaitUntilTimesOutWhenExceedingItsTime() { var waiting = true From 83431ee3d2519d51aa8f7541c2f0cf8aad56449b Mon Sep 17 00:00:00 2001 From: Brian Michel Date: Sat, 23 Sep 2023 09:04:28 -0400 Subject: [PATCH 4/5] Add back the runLoopMode selection code --- Sources/Nimble/Utils/PollAwait.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Sources/Nimble/Utils/PollAwait.swift b/Sources/Nimble/Utils/PollAwait.swift index 725cd8c6e..3f57f090e 100644 --- a/Sources/Nimble/Utils/PollAwait.swift +++ b/Sources/Nimble/Utils/PollAwait.swift @@ -195,7 +195,12 @@ internal class AwaitPromiseBuilder { let semTimedOutOrBlocked = DispatchSemaphore(value: 0) semTimedOutOrBlocked.signal() #if canImport(CoreFoundation) - let runLoop = CFRunLoopGetMain() + let runLoop = CFRunLoopGetMain() + #if canImport(Darwin) + let runLoopMode = CFRunLoopMode.defaultMode.rawValue + #else + let runLoopMode = kCFRunLoopDefaultMode + #endif CFRunLoopPerformBlock(runLoop, runLoopMode) { if semTimedOutOrBlocked.wait(timeout: .now()) == .success { timedOutSem.signal() From 7b4e1efee67c9dfa37cd21e9529c65611706062a Mon Sep 17 00:00:00 2001 From: Brian Michel Date: Sat, 23 Sep 2023 09:20:03 -0400 Subject: [PATCH 5/5] Restore missing code block --- Sources/Nimble/Utils/PollAwait.swift | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Sources/Nimble/Utils/PollAwait.swift b/Sources/Nimble/Utils/PollAwait.swift index 3f57f090e..161e9552a 100644 --- a/Sources/Nimble/Utils/PollAwait.swift +++ b/Sources/Nimble/Utils/PollAwait.swift @@ -219,13 +219,25 @@ internal class AwaitPromiseBuilder { timedOutSem.signal() semTimedOutOrBlocked.signal() if self.promise.resolveResult(.timedOut) { - runLoop._stop() + RunLoop.main._stop() } } }) // potentially interrupt blocking code on run loop to let timeout code run runLoop._stop() #endif + let now = DispatchTime.now() + forcefullyAbortTimeout.dispatchTimeInterval + let didNotTimeOut = timedOutSem.wait(timeout: now) != .success + let timeoutWasNotTriggered = semTimedOutOrBlocked.wait(timeout: .now()) == .success + if didNotTimeOut && timeoutWasNotTriggered { + if self.promise.resolveResult(.blockedRunLoop) { + #if canImport(CoreFoundation) + CFRunLoopStop(CFRunLoopGetMain()) + #else + RunLoop.main._stop() + #endif + } + } } return self }