-
Notifications
You must be signed in to change notification settings - Fork 263
Add support for WASI platform #478
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
Changes from 1 commit
e7ea525
ae2cb67
e428651
99a5c70
c63a84f
9decbc4
2a46822
da82bb9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
// This source file is part of the Swift.org open source project | ||
// | ||
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors | ||
// Licensed under Apache License v2.0 with Runtime Library Exception | ||
// | ||
// See http://swift.org/LICENSE.txt for license information | ||
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors | ||
// | ||
// | ||
// NoThreadDispatchShims.swift | ||
// | ||
|
||
// This file is a shim for platforms that don't have libdispatch and do assume a single-threaded environment. | ||
|
||
// NOTE: We can't use use `#if canImport(Dispatch)` because Dispatch Clang module is placed directly in the resource | ||
// directory, and not split into target-specific directories. This means that the module is always available, even on | ||
// platforms that don't have libdispatch. Thus, we need to check for the actual platform. | ||
#if os(WASI) | ||
|
||
/// No-op shim function | ||
func dispatchPrecondition(condition: DispatchPredicate) {} | ||
|
||
struct DispatchPredicate { | ||
static func onQueue<X>(_: X) -> Self { | ||
return DispatchPredicate() | ||
} | ||
|
||
static func notOnQueue<X>(_: X) -> Self { | ||
return DispatchPredicate() | ||
} | ||
} | ||
|
||
extension XCTWaiter { | ||
/// Single-threaded queue without any actual queueing | ||
struct DispatchQueue { | ||
init(label: String) {} | ||
|
||
func sync<T>(_ body: () -> T) -> T { | ||
body() | ||
} | ||
func async(_ body: @escaping () -> Void) { | ||
body() | ||
} | ||
} | ||
} | ||
|
||
#endif |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -117,7 +117,9 @@ open class XCTWaiter { | |
private var state = State.ready | ||
internal var timeout: TimeInterval = 0 | ||
internal var waitSourceLocation: SourceLocation? | ||
#if !USE_SWIFT_CONCURRENCY_WAITER | ||
private weak var manager: WaiterManager<XCTWaiter>? | ||
#endif | ||
private var runLoop: RunLoop? | ||
|
||
private weak var _delegate: XCTWaiterDelegate? | ||
|
@@ -187,9 +189,16 @@ open class XCTWaiter { | |
/// these environments. To ensure compatibility of tests between | ||
/// swift-corelibs-xctest and Apple XCTest, it is not recommended to pass | ||
/// explicit values for `file` and `line`. | ||
#if USE_SWIFT_CONCURRENCY_WAITER | ||
@available(*, unavailable, message: "Expectation-based waiting is not available when using the Swift concurrency waiter.") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm a little bit confused about the messaging of the Is there anything left that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Sorry for your confusion, the option conceptually means that no thread blocking should happen with this mode.
I used "expectation" word because
There are no functionalities There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you think it would be workable to adjust the scope and semantics of this conditional a little bit, perhaps calling it I do see that usage of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That totally makes sense to me. I managed to guard out There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we introduce a second queue, we should be very wary of deadlocks, data races, etc. where the current code assumes a single global lock. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right, I used the new queue in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed, considering the fine-grained scoping of the locking provided by the queue for these, I think we should be ok in that regard. |
||
#else | ||
@available(*, noasync, message: "Use await fulfillment(of:timeout:enforceOrder:) instead.") | ||
#endif | ||
@discardableResult | ||
open func wait(for expectations: [XCTestExpectation], timeout: TimeInterval, enforceOrder: Bool = false, file: StaticString = #file, line: Int = #line) -> Result { | ||
#if USE_SWIFT_CONCURRENCY_WAITER | ||
fatalError("This method is not available when using the Swift concurrency waiter.") | ||
#else | ||
precondition(Set(expectations).count == expectations.count, "API violation - each expectation can appear only once in the 'expectations' parameter.") | ||
|
||
self.timeout = timeout | ||
|
@@ -251,6 +260,7 @@ open class XCTWaiter { | |
} | ||
|
||
return result | ||
#endif | ||
} | ||
|
||
/// Wait on an array of expectations for up to the specified timeout, and optionally specify whether they | ||
|
@@ -276,9 +286,16 @@ open class XCTWaiter { | |
/// these environments. To ensure compatibility of tests between | ||
/// swift-corelibs-xctest and Apple XCTest, it is not recommended to pass | ||
/// explicit values for `file` and `line`. | ||
#if USE_SWIFT_CONCURRENCY_WAITER | ||
@available(*, unavailable, message: "Expectation-based waiting is not available when using the Swift concurrency waiter.") | ||
#else | ||
@available(macOS 12.0, *) | ||
#endif | ||
@discardableResult | ||
open func fulfillment(of expectations: [XCTestExpectation], timeout: TimeInterval, enforceOrder: Bool = false, file: StaticString = #file, line: Int = #line) async -> Result { | ||
#if USE_SWIFT_CONCURRENCY_WAITER | ||
fatalError("This method is not available when using the Swift concurrency waiter.") | ||
#else | ||
return await withCheckedContinuation { continuation in | ||
// This function operates by blocking a background thread instead of one owned by libdispatch or by the | ||
// Swift runtime (as used by Swift concurrency.) To ensure we use a thread owned by neither subsystem, use | ||
|
@@ -288,6 +305,7 @@ open class XCTWaiter { | |
continuation.resume(returning: result) | ||
} | ||
} | ||
#endif | ||
} | ||
|
||
/// Convenience API to create an XCTWaiter which then waits on an array of expectations for up to the specified timeout, and optionally specify whether they | ||
|
@@ -306,9 +324,17 @@ open class XCTWaiter { | |
/// expectations are not fulfilled before the given timeout. Default is the line | ||
/// number of the call to this method in the calling file. It is rare to | ||
/// provide this parameter when calling this method. | ||
#if USE_SWIFT_CONCURRENCY_WAITER | ||
@available(*, unavailable, message: "Expectation-based waiting is not available when using the Swift concurrency waiter.") | ||
#else | ||
@available(*, noasync, message: "Use await fulfillment(of:timeout:enforceOrder:) instead.") | ||
#endif | ||
open class func wait(for expectations: [XCTestExpectation], timeout: TimeInterval, enforceOrder: Bool = false, file: StaticString = #file, line: Int = #line) -> Result { | ||
#if USE_SWIFT_CONCURRENCY_WAITER | ||
fatalError("This method is not available when using the Swift concurrency waiter.") | ||
#else | ||
return XCTWaiter().wait(for: expectations, timeout: timeout, enforceOrder: enforceOrder, file: file, line: line) | ||
#endif | ||
} | ||
|
||
/// Convenience API to create an XCTWaiter which then waits on an array of expectations for up to the specified timeout, and optionally specify whether they | ||
|
@@ -327,9 +353,17 @@ open class XCTWaiter { | |
/// expectations are not fulfilled before the given timeout. Default is the line | ||
/// number of the call to this method in the calling file. It is rare to | ||
/// provide this parameter when calling this method. | ||
#if USE_SWIFT_CONCURRENCY_WAITER | ||
@available(*, unavailable, message: "Expectation-based waiting is not available when using the Swift concurrency waiter.") | ||
#else | ||
@available(macOS 12.0, *) | ||
#endif | ||
open class func fulfillment(of expectations: [XCTestExpectation], timeout: TimeInterval, enforceOrder: Bool = false, file: StaticString = #file, line: Int = #line) async -> Result { | ||
#if USE_SWIFT_CONCURRENCY_WAITER | ||
fatalError("This method is not available when using the Swift concurrency waiter.") | ||
#else | ||
return await XCTWaiter().fulfillment(of: expectations, timeout: timeout, enforceOrder: enforceOrder, file: file, line: line) | ||
#endif | ||
} | ||
|
||
deinit { | ||
|
@@ -338,6 +372,7 @@ open class XCTWaiter { | |
} | ||
} | ||
|
||
#if !USE_SWIFT_CONCURRENCY_WAITER | ||
private func queue_configureExpectations(_ expectations: [XCTestExpectation]) { | ||
dispatchPrecondition(condition: .onQueue(XCTWaiter.subsystemQueue)) | ||
|
||
|
@@ -413,9 +448,11 @@ open class XCTWaiter { | |
queue_validateExpectationFulfillment(dueToTimeout: false) | ||
} | ||
} | ||
#endif | ||
|
||
} | ||
|
||
#if !USE_SWIFT_CONCURRENCY_WAITER | ||
private extension XCTWaiter { | ||
func primitiveWait(using runLoop: RunLoop, duration timeout: TimeInterval) { | ||
// The contract for `primitiveWait(for:)` explicitly allows waiting for a shorter period than requested | ||
|
@@ -436,6 +473,7 @@ private extension XCTWaiter { | |
#endif | ||
} | ||
} | ||
#endif | ||
|
||
extension XCTWaiter: Equatable { | ||
public static func == (lhs: XCTWaiter, rhs: XCTWaiter) -> Bool { | ||
|
@@ -453,6 +491,7 @@ extension XCTWaiter: CustomStringConvertible { | |
} | ||
} | ||
|
||
#if !USE_SWIFT_CONCURRENCY_WAITER | ||
extension XCTWaiter: ManageableWaiter { | ||
var isFinished: Bool { | ||
return XCTWaiter.subsystemQueue.sync { | ||
|
@@ -479,3 +518,5 @@ extension XCTWaiter: ManageableWaiter { | |
} | ||
} | ||
} | ||
|
||
#endif |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this option work on other platforms besides WASM/WASI? I wonder if that would be a path to improving test coverage for this new code, by running the existing lit-based tests on Darwin or Linux, with this flag turned on?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it works on other platforms, but the test suite doesn't work as is now because the
XCTMain
is async with the configuration. So we need to adjust those tests, but it's not too hard. Do you want to include those adjustments in this PR?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool. It's fine for that to come afterwards, but I would like to see it happen!