diff --git a/.github/workflows/minimal-ci.yml b/.github/workflows/minimal-ci.yml index 3592f05da..85da6e168 100644 --- a/.github/workflows/minimal-ci.yml +++ b/.github/workflows/minimal-ci.yml @@ -128,6 +128,36 @@ jobs: run: cargo test $TEST_FEATURES + unit-test-web: + name: unit-test-web + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + + - name: "Patch Cargo.toml to use nightly extension API" + run: .github/other/patch-prebuilt.sh nightly + + - name: "Install Rust" + uses: ./.github/composite/rust + with: + rust: nightly + components: rust-src + + - name: "Install emscripten" + env: + EMSCRIPTEN_SRC_REF: 4.0.13 + EMSCRIPTEN_VERSION: 3.1.74 + run: | + git clone https://github.com/emscripten-core/emsdk.git --depth 1 --branch $EMSCRIPTEN_SRC_REF --single-branch + ./emsdk/emsdk install $EMSCRIPTEN_VERSION + ./emsdk/emsdk activate $EMSCRIPTEN_VERSION + source ./emsdk/emsdk_env.sh + echo PATH=$PATH >> $GITHUB_ENV + + - name: "Test (Wasm)" + run: ./check.sh testweb + + # For complex matrix workflow, see https://stackoverflow.com/a/65434401 godot-itest: name: godot-itest (${{ matrix.name }}) diff --git a/check.sh b/check.sh index eab598a04..89ffd0281 100755 --- a/check.sh +++ b/check.sh @@ -28,6 +28,7 @@ Commands: fmt format code, fail if bad test run unit tests (no Godot needed) itest run integration tests (from within Godot) + testweb run unit tests on web environment (requires node.js and emcc) clippy validate clippy lints klippy validate + fix clippy doc generate docs for 'godot' crate @@ -179,6 +180,38 @@ function cmd_itest() { run "$godotBin" $GODOT_ARGS --path itest/godot --headless -- "[${extraArgs[@]}]" } +function cmd_testweb() { + # Add more debug symbols to build + common_flags="-C link-args=-g" + + # Avoid problems with emcc potentially writing to read-only dir + cache_dir="$(realpath ./target)/emscripten_cache" + mkdir -p "${cache_dir}" + + echo "===============================" + echo "Initiating threaded Wasm tests." + echo "===============================" + + # For the runner env var: https://github.com/rust-lang/cargo/issues/7471 + # More memory (256 MiB) is needed for the parallel godot-cell tests which + # spawn 70 threads each. + CARGO_TARGET_WASM32_UNKNOWN_EMSCRIPTEN_RUNNER=node RUSTFLAGS="-C link-args=-pthread \ + -C target-feature=+atomics \ + -C link-args=-sINITIAL_MEMORY=268435456 \ + ${common_flags}" EM_CACHE="${cache_dir}" run cargo +nightly test \ + --features godot/experimental-wasm,godot/lazy-function-tables \ + -Zbuild-std --target wasm32-unknown-emscripten + + echo "===================================" + echo "Initiating non-threaded Wasm tests." + echo "===================================" + + CARGO_TARGET_WASM32_UNKNOWN_EMSCRIPTEN_RUNNER=node RUSTFLAGS="${common_flags}" \ + EM_CACHE="${cache_dir}" run cargo +nightly test \ + --features godot/experimental-wasm-nothreads,godot/experimental-wasm,godot/lazy-function-tables \ + -Zbuild-std --target wasm32-unknown-emscripten +} + function cmd_doc() { run cargo doc --lib -p godot --no-deps "${extraCargoArgs[@]}" } @@ -211,7 +244,7 @@ while [[ $# -gt 0 ]]; do --double) extraCargoArgs+=("--features" "godot/double-precision") ;; - fmt | test | itest | clippy | klippy | doc | dok) + fmt | test | itest | testweb | clippy | klippy | doc | dok) cmds+=("$arg") ;; -f | --filter) diff --git a/godot-cell/tests/mock/blocking.rs b/godot-cell/tests/mock/blocking.rs index 18b96db42..7402d0056 100644 --- a/godot-cell/tests/mock/blocking.rs +++ b/godot-cell/tests/mock/blocking.rs @@ -40,6 +40,10 @@ impl MyClass { /// /// This should not cause borrow failures and should not lead to deadlocks. #[test] +#[cfg_attr( + all(target_family = "wasm", not(target_feature = "atomics")), + ignore = "Threading not available" +)] fn calls_parallel() { use std::thread; @@ -72,6 +76,10 @@ fn calls_parallel() { /// Runs each method several times in a row. This should reduce the non-determinism that comes from /// scheduling of threads. #[test] +#[cfg_attr( + all(target_family = "wasm", not(target_feature = "atomics")), + ignore = "Threading not available" +)] fn calls_parallel_many_serial() { use std::thread; @@ -106,6 +114,10 @@ fn calls_parallel_many_serial() { /// Runs all the tests several times. This is different from [`calls_parallel_many_serial`] as that calls the /// methods like AAA...BBB...CCC..., whereas this interleaves the methods like ABC...ABC...ABC... #[test] +#[cfg_attr( + all(target_family = "wasm", not(target_feature = "atomics")), + ignore = "Threading not available" +)] fn calls_parallel_many_parallel() { use std::thread; @@ -142,6 +154,10 @@ fn calls_parallel_many_parallel() { /// a) Thread A holds mutable reference AND thread B holds no references. /// b) One or more threads hold shared references AND thread A holds no references #[test] +#[cfg_attr( + all(target_family = "wasm", not(target_feature = "atomics")), + ignore = "Threading not available" +)] fn non_blocking_reborrow() { use std::thread; let instance_id = MyClass::init(); @@ -172,6 +188,10 @@ fn non_blocking_reborrow() { /// This verifies that the thread which initialized the `GdCell` does not panic when it attempts to mutably borrow while there is already a /// shared borrow on an other thread. #[test] +#[cfg_attr( + all(target_family = "wasm", not(target_feature = "atomics")), + ignore = "Threading not available" +)] fn no_mut_panic_on_main() { use std::thread; let instance_id = MyClass::init(); diff --git a/godot-cell/tests/mock/panicking.rs b/godot-cell/tests/mock/panicking.rs index 7d279b80c..36fa88ea8 100644 --- a/godot-cell/tests/mock/panicking.rs +++ b/godot-cell/tests/mock/panicking.rs @@ -58,6 +58,10 @@ fn all_calls_work() { /// Run each method both from the main thread and a newly created thread. #[test] +#[cfg_attr( + all(target_family = "wasm", not(target_feature = "atomics")), + ignore = "Threading not available" +)] fn calls_different_thread() { use std::thread; @@ -87,6 +91,10 @@ fn calls_different_thread() { /// if the first call failed, so then we know the integer was incremented by 0. Otherwise, we at least know /// the range of values that it can be incremented by. #[test] +#[cfg_attr( + all(target_family = "wasm", not(target_feature = "atomics")), + ignore = "Threading not available" +)] fn calls_parallel() { use std::thread; @@ -121,6 +129,10 @@ fn calls_parallel() { /// Runs each method several times in a row. This should reduce the non-determinism that comes from /// scheduling of threads. #[test] +#[cfg_attr( + all(target_family = "wasm", not(target_feature = "atomics")), + ignore = "Threading not available" +)] fn calls_parallel_many_serial() { use std::thread; @@ -157,6 +169,10 @@ fn calls_parallel_many_serial() { /// Runs all the tests several times. This is different from [`calls_parallel_many_serial`] as that calls the /// methods like AAA...BBB...CCC..., whereas this interleaves the methods like ABC...ABC...ABC... #[test] +#[cfg_attr( + all(target_family = "wasm", not(target_feature = "atomics")), + ignore = "Threading not available" +)] fn calls_parallel_many_parallel() { use std::thread;