From f72b207dc4f330114688861e0ea3922b2480b4b1 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Tue, 27 Sep 2022 05:26:54 +0000 Subject: [PATCH 1/9] Add JavaScriptEventLoopTestSupport module to install executor --- Package.swift | 8 ++++++++ .../JavaScriptEventLoopTestSupport.swift | 9 +++++++++ .../_CJavaScriptEventLoopTestSupport.c | 19 +++++++++++++++++++ .../include/dummy.h | 0 4 files changed, 36 insertions(+) create mode 100644 Sources/JavaScriptEventLoopTestSupport/JavaScriptEventLoopTestSupport.swift create mode 100644 Sources/_CJavaScriptEventLoopTestSupport/_CJavaScriptEventLoopTestSupport.c create mode 100644 Sources/_CJavaScriptEventLoopTestSupport/include/dummy.h diff --git a/Package.swift b/Package.swift index d278e5ab9..5902bc9b5 100644 --- a/Package.swift +++ b/Package.swift @@ -26,5 +26,13 @@ let package = Package( dependencies: ["JavaScriptKit", "_CJavaScriptEventLoop"] ), .target(name: "_CJavaScriptEventLoop"), + .target( + name: "JavaScriptEventLoopTestSupport", + dependencies: [ + "_CJavaScriptEventLoopTestSupport", + .product(name: "JavaScriptEventLoop", package: "JavaScriptKit"), + ] + ), + .target(name: "_CJavaScriptEventLoopTestSupport"), ] ) diff --git a/Sources/JavaScriptEventLoopTestSupport/JavaScriptEventLoopTestSupport.swift b/Sources/JavaScriptEventLoopTestSupport/JavaScriptEventLoopTestSupport.swift new file mode 100644 index 000000000..368639304 --- /dev/null +++ b/Sources/JavaScriptEventLoopTestSupport/JavaScriptEventLoopTestSupport.swift @@ -0,0 +1,9 @@ +// This module just expose 'JavaScriptEventLoop.installGlobalExecutor' to C ABI +// See _CJavaScriptEventLoopTestSupport.c for why this is needed + +import JavaScriptEventLoop + +@_cdecl("swift_javascriptkit_activate_js_executor_impl") +func swift_javascriptkit_activate_js_executor_impl() { + JavaScriptEventLoop.installGlobalExecutor() +} diff --git a/Sources/_CJavaScriptEventLoopTestSupport/_CJavaScriptEventLoopTestSupport.c b/Sources/_CJavaScriptEventLoopTestSupport/_CJavaScriptEventLoopTestSupport.c new file mode 100644 index 000000000..7dfdbe2e8 --- /dev/null +++ b/Sources/_CJavaScriptEventLoopTestSupport/_CJavaScriptEventLoopTestSupport.c @@ -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(); +} + diff --git a/Sources/_CJavaScriptEventLoopTestSupport/include/dummy.h b/Sources/_CJavaScriptEventLoopTestSupport/include/dummy.h new file mode 100644 index 000000000..e69de29bb From 6fea6e5d18ae8e62f0576c19d60079c44f30eee7 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Fri, 30 Sep 2022 12:47:20 +0000 Subject: [PATCH 2/9] Add short document about JavaScriptEventLoopTestSupport --- README.md | 17 ++++++++++++++++ .../JavaScriptEventLoopTestSupport.swift | 20 +++++++++++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 42af64320..0c2c6988c 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/Sources/JavaScriptEventLoopTestSupport/JavaScriptEventLoopTestSupport.swift b/Sources/JavaScriptEventLoopTestSupport/JavaScriptEventLoopTestSupport.swift index 368639304..d62979dd9 100644 --- a/Sources/JavaScriptEventLoopTestSupport/JavaScriptEventLoopTestSupport.swift +++ b/Sources/JavaScriptEventLoopTestSupport/JavaScriptEventLoopTestSupport.swift @@ -1,8 +1,24 @@ -// This module just expose 'JavaScriptEventLoop.installGlobalExecutor' to C ABI -// See _CJavaScriptEventLoopTestSupport.c for why this is needed +/// 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 @_cdecl("swift_javascriptkit_activate_js_executor_impl") func swift_javascriptkit_activate_js_executor_impl() { JavaScriptEventLoop.installGlobalExecutor() From d82c70f3592b4813eae90dd4833d8dcf231c8b02 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Fri, 30 Sep 2022 12:50:21 +0000 Subject: [PATCH 3/9] Publish JavaScriptEventLoopTestSupport as a product --- Package.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Package.swift b/Package.swift index 5902bc9b5..b0b0520de 100644 --- a/Package.swift +++ b/Package.swift @@ -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( From eb9d9f29fa92d277f4db065730a78b85804368d2 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Fri, 30 Sep 2022 13:38:11 +0000 Subject: [PATCH 4/9] Add arg0 to wasi arguments because XCTest reads process arguments and it expects at least having one argument --- IntegrationTests/lib.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/IntegrationTests/lib.js b/IntegrationTests/lib.js index a0af77527..347348fb9 100644 --- a/IntegrationTests/lib.js +++ b/IntegrationTests/lib.js @@ -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 @@ -27,7 +27,7 @@ const WASI = { return originalWriteSync(fd, buffer, offset, length, position); }; const wasi = new WasmerWASI({ - args: [], + args: [programName], env: {}, bindings: { ...WasmerWASI.defaultBindings, @@ -44,9 +44,9 @@ const WASI = { } } }, - MicroWASI: () => { + MicroWASI: ({ programName }) => { const wasi = new MicroWASI({ - args: [], + args: [programName], env: {}, features: [useAll()], }) @@ -59,9 +59,9 @@ const WASI = { } } }, - Node: () => { + Node: ({ programName }) => { const wasi = new NodeWASI({ - args: [], + args: [programName], env: {}, returnOnExit: true, }) @@ -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, { From 28bb7f2bfed89c781d267b289575a9ce31f52d80 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Fri, 30 Sep 2022 13:38:48 +0000 Subject: [PATCH 5/9] Exit process when proc_exit is called on Node.js WASI because Node.js's WASI implementatin does't supprot proc_exit in async reactor model. See https://github.com/nodejs/node/blob/2a4452a53af65a13db4efae474162a7dcfd38dd5/lib/wasi.js#L121 --- IntegrationTests/lib.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IntegrationTests/lib.js b/IntegrationTests/lib.js index 347348fb9..e708816cb 100644 --- a/IntegrationTests/lib.js +++ b/IntegrationTests/lib.js @@ -63,7 +63,7 @@ const WASI = { const wasi = new NodeWASI({ args: [programName], env: {}, - returnOnExit: true, + returnOnExit: false, }) return { From 08c36d2601b9e21da028115cfffe060d8b406d53 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Fri, 30 Sep 2022 13:40:33 +0000 Subject: [PATCH 6/9] Add unit test target for JavaScriptEventLoopTestSupport --- Package.swift | 9 ++++++++- .../JavaScriptEventLoopTestSupportTests.swift | 15 +++++++++++++++ scripts/test-harness.js | 10 ++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 Tests/JavaScriptEventLoopTestSupportTests/JavaScriptEventLoopTestSupportTests.swift create mode 100644 scripts/test-harness.js diff --git a/Package.swift b/Package.swift index b0b0520de..c8f55dd0b 100644 --- a/Package.swift +++ b/Package.swift @@ -31,9 +31,16 @@ let package = Package( name: "JavaScriptEventLoopTestSupport", dependencies: [ "_CJavaScriptEventLoopTestSupport", - .product(name: "JavaScriptEventLoop", package: "JavaScriptKit"), + "JavaScriptEventLoop", ] ), .target(name: "_CJavaScriptEventLoopTestSupport"), + .testTarget( + name: "JavaScriptEventLoopTestSupportTests", + dependencies: [ + "JavaScriptKit", + "JavaScriptEventLoopTestSupport" + ] + ), ] ) diff --git a/Tests/JavaScriptEventLoopTestSupportTests/JavaScriptEventLoopTestSupportTests.swift b/Tests/JavaScriptEventLoopTestSupportTests/JavaScriptEventLoopTestSupportTests.swift new file mode 100644 index 000000000..cca303a09 --- /dev/null +++ b/Tests/JavaScriptEventLoopTestSupportTests/JavaScriptEventLoopTestSupportTests.swift @@ -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 + } + ) + } + } +} diff --git a/scripts/test-harness.js b/scripts/test-harness.js new file mode 100644 index 000000000..39a7dbe9a --- /dev/null +++ b/scripts/test-harness.js @@ -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); From 0c39b24267aa3fa2095cc9e9c34e0ff0e6cd9ca5 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Fri, 30 Sep 2022 13:44:28 +0000 Subject: [PATCH 7/9] Add unit test job in Makefile --- Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Makefile b/Makefile index bd93f2e60..58ccccdd5 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,10 @@ build: .PHONY: test test: + @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 + @echo Running integration tests cd IntegrationTests && \ CONFIGURATION=debug make test && \ CONFIGURATION=debug SWIFT_BUILD_FLAGS="-Xswiftc -DJAVASCRIPTKIT_WITHOUT_WEAKREFS" make test && \ From 3e94df2cead0ebfa335246414a9a3652dfb4cd49 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Fri, 30 Sep 2022 13:48:02 +0000 Subject: [PATCH 8/9] Add API guard directives for native build --- .../JavaScriptEventLoopTestSupport.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Sources/JavaScriptEventLoopTestSupport/JavaScriptEventLoopTestSupport.swift b/Sources/JavaScriptEventLoopTestSupport/JavaScriptEventLoopTestSupport.swift index d62979dd9..9922de945 100644 --- a/Sources/JavaScriptEventLoopTestSupport/JavaScriptEventLoopTestSupport.swift +++ b/Sources/JavaScriptEventLoopTestSupport/JavaScriptEventLoopTestSupport.swift @@ -19,7 +19,13 @@ 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 From b34c15f8e630dd7e85f8a073fcc05305c6efcd36 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Fri, 30 Sep 2022 14:01:07 +0000 Subject: [PATCH 9/9] Run async unit tests only with 5.7 toolchain --- .github/workflows/test.yml | 15 ++++++++++----- Makefile | 9 ++++++--- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 96c36e547..e6b0db563 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -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 diff --git a/Makefile b/Makefile index 58ccccdd5..7b8736221 100644 --- a/Makefile +++ b/Makefile @@ -12,9 +12,6 @@ build: .PHONY: test test: - @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 @echo Running integration tests cd IntegrationTests && \ CONFIGURATION=debug make test && \ @@ -22,6 +19,12 @@ 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