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

Add support for WASI platform #478

Merged
merged 8 commits into from Mar 13, 2024

Conversation

kateinoigakukun
Copy link
Member

WASI does not have thread spawning method yet, but the existing implementation blocks threads to wait async test cases synchronously. This commit introduced a new waiter method for running async test cases in single-threaded WASI environments, enabled by USE_SWIFT_CONCURRENCY_WAITER flag.

With the new waiter, XCTMain is async runs the given test suites without blocking the thread by bypassing some synchronous public APIs like XCTest.perform and XCTest.run. This ignores those APIs even if they are overridden by user-defined subclasses, so it's not 100% compatible with the existing XCTest APIs. This is a trade-off to support async test execution in single-threaded environments, but it should be fine because the APIs are seldom overridden by user code.

WASI does not have thread spawning method yet, but the existing
implementation blocks threads to wait async test cases synchronously.
This commit introduced a new waiter method for running async test cases in
single-threaded WASI environments, enabled by USE_SWIFT_CONCURRENCY_WAITER
flag.

With the new waiter, `XCTMain` is async runs the given test suites
without blocking the thread by bypassing some synchronous public APIs
like `XCTest.perform` and `XCTest.run`. This ignores those APIs even if
they are overridden by user-defined subclasses, so it's not 100%
compatible with the existing XCTest APIs. This is a trade-off to
support async test execution in single-threaded environments, but it
should be fine because the APIs are seldom overridden by user code.
@kateinoigakukun
Copy link
Member Author

@swift-ci test

@MaxDesiatov
Copy link
Member

@swift-ci test macos

@MaxDesiatov MaxDesiatov added the WebAssembly Wasm and WASI support label Mar 11, 2024
@kateinoigakukun
Copy link
Member Author

@MaxDesiatov macOS build for corelibs-xctest has been broken for a while unfortunately...

@kateinoigakukun
Copy link
Member Author

@stmontgomery If we can get this merged before the next branch cut, it would be appreciated because this patch is the last difference from the fork. Most of the changes are guarded by a macro and should not affect other platforms. 🙇

@briancroom briancroom self-requested a review March 12, 2024 16:13
Copy link
Collaborator

@briancroom briancroom left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the PR and all the work on this, @kateinoigakukun! (And @MaxDesiatov as well, it sounds like.) I've left a number of questions and comments, some pretty important.

I wish there were a way to achieve this with less branching, but without reasync support in the compiler, I doubt we can do much better.

Besides the details of the code changes in the PR, I'm interested in learning more about the plans for ongoing support and maintenance of this compilation mode and platform. In particular, what kind of CI and testing is in place, or expected to come in the near future?

Sources/XCTest/Public/XCTestSuite.swift Show resolved Hide resolved
CMakeLists.txt Outdated
set(USE_SWIFT_CONCURRENCY_WAITER_default ON)
endif()

option(USE_SWIFT_CONCURRENCY_WAITER "Use Swift Concurrency-based waiter implementation" "${USE_SWIFT_CONCURRENCY_WAITER_default}")
Copy link
Collaborator

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?

Copy link
Member Author

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?

Copy link
Collaborator

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!

@@ -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.")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little bit confused about the messaging of the USE_SWIFT_CONCURRENCY_WAITER option, the continued availability of the XCTWaiter class, and these unavailability messages about "expectation-based waiting".

Is there anything left that XCTWaiter can do in this mode? Or should the entire class be removed / made unavailable?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little bit confused about the messaging of the USE_SWIFT_CONCURRENCY_WAITER option

Sorry for your confusion, the option conceptually means that no thread blocking should happen with this mode.

and these unavailability messages about "expectation-based waiting".

I used "expectation" word because XCTestExpectation-related APIs block thread. But "Blocking-wait is unavailable" might be more descriptive?

the continued availability of the XCTWaiter class
Is there anything left that XCTWaiter can do in this mode? Or should the entire class be removed / made unavailable?

There are no functionalities XCTWaiter can do with this mode, but I left the class itself available to avoid spreading #if around all XCTWaiter.subsystemQueue uses.

Copy link
Collaborator

Choose a reason for hiding this comment

The 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 DISABLE_XCTWAITER or something along those lines, and have it guard the entirety of the XCTWaiter and XCTestExpectation classes, along with the everything in XCTestCase+Asynchronous.swift, and the expectation-related state tracking in XCTestCase.swift.

I do see that usage of the XCTWaiter.subsystemQueue has leaked out to non-waiter code in a few places (ThrownErrorWrapper and TeardownBlocksState). Those are using it for just basic locking purposes, and quite independently from the actual waiter subsystem. Perhaps we introduce a XCTestCase.subsystemQueue to use for those instead? Or even a different locking solution altogether? This would be a general-purpose change, not conditionalized by the new USE_SWIFT_CONCURRENCY_WAITER/DISABLE_XCTWAITER option.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That totally makes sense to me. I managed to guard out XCTWaiter and XCTestExpectation entirely, and it simplified a lot. A bonus here was I could remove DispatchShim :)

Copy link
Contributor

Choose a reason for hiding this comment

The 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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I used the new queue in TeardownBlocksState and ThrownErrorWrapper. As far as I checked, they are well independent from the other, and they won't acquire the other lock while acquiring itself lock. But I need more eyes here 🙏

Copy link
Collaborator

Choose a reason for hiding this comment

The 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.

Sources/XCTest/Public/XCAbstractTest.swift Show resolved Hide resolved
Sources/XCTest/Public/XCTestCase.swift Outdated Show resolved Hide resolved

do {
if skip == nil {
try testClosure(self)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's very important for behavioral consistency in this mode that when calling out to non-async user-provided code (test methods, and setUp and tearDown overrides) that those synchronous functions run on the main actor. I'm not certain, but I don't think that invariant is maintained by the current code. Can you comment on that?

(This was something that we struggled with a bit, and initially got wrong when we were working on support for async test methods a while back in #326)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can keep the consistency by adding @MainActor on this method.

@kateinoigakukun
Copy link
Member Author

I wish there were a way to achieve this with less branching, but without reasync support in the compiler, I doubt we can do much better.

Yeah, that's a very unfortunate situation to have many branching here due to function-colors. I agree that we can improve it with reasync support in the future.

Besides the details of the code changes in the PR, I'm interested in learning more about the plans for ongoing support and maintenance of this compilation mode and platform. In particular, what kind of CI and testing is in place, or expected to come in the near future?

Yes, at least I'm going to include XCTest and Foundation build in the WebAssembly build CI after this will get merged: https://ci.swift.org/job/oss-swift-pr-test-crosscompile-wasm-ubuntu-20_04/

Also, as you mentioned, we can build and run tests with this mode on other platforms.

Also this revealed teardown blocks were not being run in the mode, so
fix that as well.
To keep consistency with the regular mode.
To make the semantics of the option clearer.
The use of DispatchQueue in XCTest is now very limited, and it's only used
in a single place in XCTestCase.swift.
@kateinoigakukun
Copy link
Member Author

@swift-ci test

@kateinoigakukun
Copy link
Member Author

@swift-ci test

@briancroom
Copy link
Collaborator

Let’s take a good look at CI results, but the diff looks ok to me now! Thanks for discussing and the quick turnaround on the revisions.

@MaxDesiatov
Copy link
Member

@swift-ci test windows

@MaxDesiatov MaxDesiatov changed the title WebAssembly Support Add support for WASI platform Mar 13, 2024
@kateinoigakukun
Copy link
Member Author

@kateinoigakukun
Copy link
Member Author

Thanks a lot for your review @briancroom! I assume we need @shahmishal's help to get this merged by temporarily bypassing PR status check, right?

@shahmishal shahmishal merged commit e0c3868 into apple:main Mar 13, 2024
2 of 3 checks passed
@kkebo
Copy link

kkebo commented Mar 25, 2024

This change might have broken some tests especially if there are many test cases. (I'm sorry if I was wrong.)

@kateinoigakukun
Copy link
Member Author

@kkk669 Yes, the implementation is changed from the version used in the downstream, so some of large async test cases might not work. It will be mitigated after tail-call support.

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

Successfully merging this pull request may close these issues.

None yet

6 participants