Skip to content

Commit

Permalink
Add Windows Support (#1088)
Browse files Browse the repository at this point in the history
* Successfully compile and test on Windows

* Add Windows CI job

* Provide divergent implementations for stop

We need to preserve the CFRunLoopStop calls for platforms which can use CoreFoundation.

* Add back the runLoopMode selection code

* Restore missing code block
  • Loading branch information
brianmichel authored Sep 27, 2023
1 parent 6106e7b commit 67aca90
Show file tree
Hide file tree
Showing 8 changed files with 66 additions and 9 deletions.
13 changes: 13 additions & 0 deletions .github/workflows/ci-swiftpm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 4 additions & 0 deletions Sources/Nimble/Matchers/PostNotification.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<Out>(
_ predicate: Predicate<[Notification]>,
Expand Down
3 changes: 3 additions & 0 deletions Sources/Nimble/Utils/AsyncAwait.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#if !os(WASI)

#if canImport(CoreFoundation)
import CoreFoundation
#endif

import Dispatch
import Foundation

Expand Down
2 changes: 2 additions & 0 deletions Sources/Nimble/Utils/AsyncTimerSequence.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#if !os(WASI)

#if canImport(CoreFoundation)
import CoreFoundation
#endif
import Dispatch
import Foundation

Expand Down
33 changes: 33 additions & 0 deletions Sources/Nimble/Utils/PollAwait.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#if !os(WASI)

#if canImport(CoreFoundation)
import CoreFoundation
#endif
import Dispatch
import Foundation

Expand Down Expand Up @@ -192,6 +194,7 @@ internal class AwaitPromiseBuilder<T> {
let timedOutSem = DispatchSemaphore(value: 0)
let semTimedOutOrBlocked = DispatchSemaphore(value: 0)
semTimedOutOrBlocked.signal()
#if canImport(CoreFoundation)
let runLoop = CFRunLoopGetMain()
#if canImport(Darwin)
let runLoopMode = CFRunLoopMode.defaultMode.rawValue
Expand All @@ -209,12 +212,30 @@ internal class AwaitPromiseBuilder<T> {
}
// 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 {
timedOutSem.signal()
semTimedOutOrBlocked.signal()
if self.promise.resolveResult(.timedOut) {
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
}
}
}
Expand Down Expand Up @@ -302,7 +323,11 @@ internal class Awaiter {
if completionCount < 2 {
func completeBlock() {
if promise.resolveResult(.completed(result)) {
#if canImport(CoreFoundation)
CFRunLoopStop(CFRunLoopGetMain())
#else
RunLoop.main._stop()
#endif
}
}

Expand Down Expand Up @@ -340,12 +365,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
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion Tests/NimbleTests/AsyncAwaitTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 8 additions & 8 deletions Tests/NimbleTests/Matchers/ThrowAssertionTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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())
Expand All @@ -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())
}
Expand All @@ -59,21 +59,21 @@ 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())
}
#endif
}

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
}
Expand Down
2 changes: 2 additions & 0 deletions Tests/NimbleTests/PollingTest.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
#if !os(WASI)

import Dispatch
#if canImport(CoreFoundation)
import CoreFoundation
#endif
import Foundation
import XCTest
import Nimble
Expand Down

0 comments on commit 67aca90

Please sign in to comment.