Skip to content

Add JavaScriptEventLoopTestSupport module to install executor #213

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

Merged
merged 9 commits into from
Sep 30, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,27 @@ jobs:
- { os: ubuntu-20.04, toolchain: wasm-5.7.1-RELEASE, wasi-backend: MicroWASI }

runs-on: ${{ matrix.entry.os }}
env:
JAVASCRIPTKIT_WASI_BACKEND: ${{ matrix.entry.wasi-backend }}
SWIFT_VERSION: ${{ matrix.entry.toolchain }}
steps:
- name: Checkout
uses: actions/checkout@master
with:
fetch-depth: 1
- name: Run Test
env:
JAVASCRIPTKIT_WASI_BACKEND: ${{ matrix.entry.wasi-backend }}
- name: Install swiftenv
run: |
git clone https://github.com/kylef/swiftenv.git ~/.swiftenv
export SWIFTENV_ROOT="$HOME/.swiftenv"
export PATH="$SWIFTENV_ROOT/bin:$PATH"
eval "$(swiftenv init -)"
SWIFT_VERSION=${{ matrix.entry.toolchain }} make bootstrap
echo $PATH >> $GITHUB_PATH
env >> $GITHUB_ENV
echo ${{ matrix.entry.toolchain }} > .swift-version
make test
- run: make bootstrap
- run: make test
- run: make unittest
if: ${{ startsWith(matrix.toolchain, 'wasm-5.7.') }}
- name: Check if SwiftPM resources are stale
run: |
make regenerate_swiftpm_resources
Expand Down
16 changes: 8 additions & 8 deletions IntegrationTests/lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const fs = require("fs");
const readFile = promisify(fs.readFile);

const WASI = {
Wasmer: () => {
Wasmer: ({ programName }) => {
// Instantiate a new WASI Instance
const wasmFs = new WasmFs();
// Output stdout and stderr to console
Expand All @@ -27,7 +27,7 @@ const WASI = {
return originalWriteSync(fd, buffer, offset, length, position);
};
const wasi = new WasmerWASI({
args: [],
args: [programName],
env: {},
bindings: {
...WasmerWASI.defaultBindings,
Expand All @@ -44,9 +44,9 @@ const WASI = {
}
}
},
MicroWASI: () => {
MicroWASI: ({ programName }) => {
const wasi = new MicroWASI({
args: [],
args: [programName],
env: {},
features: [useAll()],
})
Expand All @@ -59,11 +59,11 @@ const WASI = {
}
}
},
Node: () => {
Node: ({ programName }) => {
const wasi = new NodeWASI({
args: [],
args: [programName],
env: {},
returnOnExit: true,
returnOnExit: false,
})

return {
Expand Down Expand Up @@ -91,7 +91,7 @@ const startWasiTask = async (wasmPath, wasiConstructor = selectWASIBackend()) =>
const swift = new SwiftRuntime();
// Fetch our Wasm File
const wasmBinary = await readFile(wasmPath);
const wasi = wasiConstructor();
const wasi = wasiConstructor({ programName: wasmPath });

// Instantiate the WebAssembly file
let { instance } = await WebAssembly.instantiate(wasmBinary, {
Expand Down
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,19 @@ build:

.PHONY: test
test:
@echo Running integration tests
cd IntegrationTests && \
CONFIGURATION=debug make test && \
CONFIGURATION=debug SWIFT_BUILD_FLAGS="-Xswiftc -DJAVASCRIPTKIT_WITHOUT_WEAKREFS" make test && \
CONFIGURATION=release make test && \
CONFIGURATION=release SWIFT_BUILD_FLAGS="-Xswiftc -DJAVASCRIPTKIT_WITHOUT_WEAKREFS" make test

.PHONY: unittest
unittest:
@echo Running unit tests
swift build --build-tests --triple wasm32-unknown-wasi -Xswiftc -Xclang-linker -Xswiftc -mexec-model=reactor -Xlinker --export=main
node --experimental-wasi-unstable-preview1 scripts/test-harness.js ./.build/wasm32-unknown-wasi/debug/JavaScriptKitPackageTests.wasm

.PHONY: benchmark_setup
benchmark_setup:
cd IntegrationTests && CONFIGURATION=release make benchmark_setup
Expand Down
16 changes: 16 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ let package = Package(
.library(name: "JavaScriptKit", targets: ["JavaScriptKit"]),
.library(name: "JavaScriptEventLoop", targets: ["JavaScriptEventLoop"]),
.library(name: "JavaScriptBigIntSupport", targets: ["JavaScriptBigIntSupport"]),
.library(name: "JavaScriptEventLoopTestSupport", targets: ["JavaScriptEventLoopTestSupport"]),
],
targets: [
.target(
Expand All @@ -26,5 +27,20 @@ let package = Package(
dependencies: ["JavaScriptKit", "_CJavaScriptEventLoop"]
),
.target(name: "_CJavaScriptEventLoop"),
.target(
name: "JavaScriptEventLoopTestSupport",
dependencies: [
"_CJavaScriptEventLoopTestSupport",
"JavaScriptEventLoop",
]
),
.target(name: "_CJavaScriptEventLoopTestSupport"),
.testTarget(
name: "JavaScriptEventLoopTestSupportTests",
dependencies: [
"JavaScriptKit",
"JavaScriptEventLoopTestSupport"
]
),
]
)
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,23 @@ asyncButtonElement.onclick = .object(JSClosure { _ in
_ = document.body.appendChild(asyncButtonElement)
```

### `JavaScriptEventLoop` activation in XCTest suites

If you need to execute Swift async functions that can be resumed by JS event loop in your XCTest suites, please add `JavaScriptEventLoopTestSupport` to your test target dependencies.

```diff
.testTarget(
name: "MyAppTests",
dependencies: [
"MyApp",
+ "JavaScriptEventLoopTestSupport",
]
)
```

Linking this module automatically activates JS event loop based global executor by calling `JavaScriptEventLoop.installGlobalExecutor()`


## Requirements

### For developers
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/// If you need to execute Swift async functions that can be resumed by JS
/// event loop in your XCTest suites, please add `JavaScriptEventLoopTestSupport`
/// to your test target dependencies.
///
/// ```diff
/// .testTarget(
/// name: "MyAppTests",
/// dependencies: [
/// "MyApp",
/// + "JavaScriptEventLoopTestSupport",
/// ]
/// )
/// ```
///
/// Linking this module automatically activates JS event loop based global
/// executor by calling `JavaScriptEventLoop.installGlobalExecutor()`

import JavaScriptEventLoop

// This module just expose 'JavaScriptEventLoop.installGlobalExecutor' to C ABI
// See _CJavaScriptEventLoopTestSupport.c for why this is needed

#if compiler(>=5.5)

@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
@_cdecl("swift_javascriptkit_activate_js_executor_impl")
func swift_javascriptkit_activate_js_executor_impl() {
JavaScriptEventLoop.installGlobalExecutor()
}

#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// This 'ctor' function is called at startup time of this program.
// It's invoked by '_start' of command-line or '_initialize' of reactor.
// This ctor activate the event loop based global executor automatically
// before running the test cases. For general applications, applications
// have to activate the event loop manually on their responsibility.
// However, XCTest framework doesn't provide a way to run arbitrary code
// before running all of the test suites. So, we have to do it here.
//
// See also: https://github.com/WebAssembly/WASI/blob/main/legacy/application-abi.md#current-unstable-abi

extern void swift_javascriptkit_activate_js_executor_impl(void);

// priority 0~100 is reserved by wasi-libc
// https://github.com/WebAssembly/wasi-libc/blob/30094b6ed05f19cee102115215863d185f2db4f0/libc-bottom-half/sources/environ.c#L20
__attribute__((constructor(/* priority */ 200)))
void swift_javascriptkit_activate_js_executor(void) {
swift_javascriptkit_activate_js_executor_impl();
}

Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import XCTest
import JavaScriptKit

final class JavaScriptEventLoopTestSupportTests: XCTestCase {
func testAwaitMicrotask() async {
let _: () = await withCheckedContinuation { cont in
JSObject.global.queueMicrotask.function!(
JSOneshotClosure { _ in
cont.resume(returning: ())
return .undefined
}
)
}
}
}
10 changes: 10 additions & 0 deletions scripts/test-harness.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Error.stackTraceLimit = Infinity;

const { startWasiTask, WASI } = require("../IntegrationTests/lib");

const handleExitOrError = (error) => {
console.log(error);
process.exit(1);
}

startWasiTask(process.argv[2]).catch(handleExitOrError);