Skip to content

Commit 31285d0

Browse files
committed
Add wasm32 tests and CI coverage (including wasm32-unknown-unknown)
1 parent 712fe33 commit 31285d0

7 files changed

Lines changed: 285 additions & 24 deletions

File tree

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
name: Setup Wasm dependencies
2+
description: Install Rust, WASI SDK, and Wasmtime on x86_64 Linux
3+
inputs:
4+
target:
5+
description: Rust target (wasm32-unknown-unknown/wasm32-wasipx)
6+
required: true
7+
c_target:
8+
description: C target for wasm32-unknown-unknown (wasm32-wasip1 or wasm32-wasip2)
9+
default: wasm32-wasip1
10+
toolchain:
11+
description: Rust toolchain
12+
default: stable
13+
wasi_sdk:
14+
description: WASI SDK version
15+
default: '34'
16+
wasmtime:
17+
description: Wasmtime version for WASI targets
18+
default: v50.0.0
19+
20+
runs:
21+
using: composite
22+
steps:
23+
- uses: dtolnay/rust-toolchain@stable
24+
with:
25+
toolchain: ${{ inputs.toolchain }}
26+
target: ${{ inputs.target }}
27+
- name: Install WASI SDK
28+
shell: bash
29+
working-directory: ${{ runner.tool_cache }}
30+
env:
31+
WASI_SDK_VERSION: ${{ inputs.wasi_sdk }}
32+
run: |
33+
archive="wasi-sdk-$WASI_SDK_VERSION.0-x86_64-linux"
34+
curl -fLO "https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-$WASI_SDK_VERSION/$archive.tar.gz"
35+
tar xf "$archive.tar.gz"
36+
echo "WASI_SDK_PATH=$PWD/$archive" >> "$GITHUB_ENV"
37+
- name: Install Wasmtime
38+
shell: bash
39+
working-directory: ${{ runner.tool_cache }}
40+
env:
41+
WASMTIME_VERSION: ${{ inputs.wasmtime }}
42+
run: |
43+
archive="wasmtime-$WASMTIME_VERSION-x86_64-linux"
44+
curl -fLO "https://github.com/bytecodealliance/wasmtime/releases/download/$WASMTIME_VERSION/$archive.tar.xz"
45+
tar xf "$archive.tar.xz"
46+
echo "$PWD/$archive" >> "$GITHUB_PATH"
47+
- name: Configure Wasm build
48+
shell: bash
49+
env:
50+
WASM_TARGET: ${{ inputs.target }}
51+
WASM_C_TARGET: ${{ inputs.c_target }}
52+
run: |
53+
source "$GITHUB_WORKSPACE/tests/wasm/setup.sh" "$WASM_TARGET" "$WASM_C_TARGET"
54+
printf '%s\n' "${wasm_env[@]}" >> "$GITHUB_ENV"

‎.github/workflows/main.yml‎

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -240,37 +240,47 @@ jobs:
240240
cargo test --tests --features "${{ matrix.lua }},vendored"
241241
cargo test --tests --features "${{ matrix.lua }},vendored,async,serde,macros,anyhow,userdata-wrappers"
242242
243-
test_wasm32_wasip2:
244-
name: Test on wasm32-wasip2
243+
test_wasm32_unknown:
244+
name: Test on wasm32-unknown-unknown (${{ matrix.c_target }})
245245
runs-on: ubuntu-latest
246+
timeout-minutes: 15
246247
needs: build
247248
strategy:
248249
matrix:
249-
lua: [lua55, lua54, lua53, lua52, lua51]
250+
lua: [lua55, lua54, lua53, lua52, lua51, luau]
251+
c_target: [wasm32-wasip1, wasm32-wasip2]
250252
steps:
251253
- uses: actions/checkout@main
252-
- uses: dtolnay/rust-toolchain@stable
254+
- uses: ./.github/actions/setup-wasm
253255
with:
254-
toolchain: nightly-2025-10-02
255-
target: wasm32-wasip2
256-
- name: Install wasi-sdk/Wasmtime
257-
working-directory: ${{ runner.tool_cache }}
258-
run: |
259-
wasi_sdk=29
260-
wasmtime=v40.0.1
256+
target: wasm32-unknown-unknown
257+
c_target: ${{ matrix.c_target }}
258+
- name: Run Wasm tests
259+
run: bash tests/wasm/run.sh "${{ matrix.lua }}" "${{ matrix.c_target }}"
261260

262-
curl -LO https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-$wasi_sdk/wasi-sdk-$wasi_sdk.0-x86_64-linux.tar.gz
263-
tar xf wasi-sdk-$wasi_sdk.0-x86_64-linux.tar.gz
264-
WASI_SDK_PATH=`pwd`/wasi-sdk-$wasi_sdk.0-x86_64-linux
265-
echo "WASI_SDK_PATH=$WASI_SDK_PATH" >> $GITHUB_ENV
266-
echo "CC_wasm32_wasip2=$WASI_SDK_PATH/bin/clang" >> $GITHUB_ENV
267-
echo "CARGO_TARGET_WASM32_WASIP2_LINKER=$WASI_SDK_PATH/bin/clang" >> $GITHUB_ENV
268-
echo "CARGO_TARGET_WASM32_WASIP2_RUSTFLAGS=-Clink-arg=-Wl,--export=cabi_realloc" >> $GITHUB_ENV
269-
270-
curl -LO https://github.com/bytecodealliance/wasmtime/releases/download/$wasmtime/wasmtime-$wasmtime-x86_64-linux.tar.xz
271-
tar xf wasmtime-$wasmtime-x86_64-linux.tar.xz
272-
echo "CARGO_TARGET_WASM32_WASIP2_RUNNER=`pwd`/wasmtime-$wasmtime-x86_64-linux/wasmtime -W exceptions" >> $GITHUB_ENV
273-
- name: Run ${{ matrix.lua }} tests
261+
test_wasm32_wasi:
262+
name: Test on ${{ matrix.target }}
263+
runs-on: ubuntu-latest
264+
timeout-minutes: 15
265+
needs: build
266+
strategy:
267+
matrix:
268+
lua: [lua55, lua54, lua53, lua52, lua51, luau]
269+
target: [wasm32-wasip1, wasm32-wasip2, wasm32-wasip3]
270+
include:
271+
# Stable Rust does not yet distribute the WASIp3 standard library.
272+
- target: wasm32-wasip3
273+
toolchain: nightly
274+
steps:
275+
- uses: actions/checkout@main
276+
- uses: ./.github/actions/setup-wasm
277+
with:
278+
target: ${{ matrix.target }}
279+
toolchain: ${{ matrix.toolchain || 'stable' }}
280+
- name: Run Wasm tests
281+
run: cargo test --manifest-path tests/wasm/Cargo.toml --target ${{ matrix.target }} --lib --features "${{ matrix.lua }}"
282+
- name: Run full ${{ matrix.lua }} tests
283+
if: ${{ matrix.target == 'wasm32-wasip2' && matrix.lua != 'luau' }}
274284
run: |
275285
cargo test --target wasm32-wasip2 --tests --features "${{ matrix.lua }},vendored"
276286
cargo test --target wasm32-wasip2 --tests --features "${{ matrix.lua }},vendored,serde,macros,anyhow,userdata-wrappers"
@@ -283,7 +293,9 @@ jobs:
283293
- uses: dtolnay/rust-toolchain@nightly
284294
with:
285295
components: rustfmt
286-
- run: cargo fmt -- --check
296+
- run: |
297+
cargo fmt -- --check
298+
cargo fmt --manifest-path tests/wasm/Cargo.toml -- --check
287299
288300
clippy:
289301
name: Clippy

‎tests/wasm/Cargo.toml‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[package]
2+
name = "mlua-wasm-tests"
3+
version = "0.0.0"
4+
edition = "2024"
5+
publish = false
6+
7+
[workspace]
8+
9+
[lib]
10+
crate-type = ["cdylib"]
11+
12+
[features]
13+
luau = ["mlua/luau"]
14+
lua55 = ["mlua/lua55", "mlua/vendored"]
15+
lua54 = ["mlua/lua54", "mlua/vendored"]
16+
lua53 = ["mlua/lua53", "mlua/vendored"]
17+
lua52 = ["mlua/lua52", "mlua/vendored"]
18+
lua51 = ["mlua/lua51", "mlua/vendored"]
19+
20+
[dependencies]
21+
mlua = { path = "../.." }

‎tests/wasm/main.c‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
extern void run_tests(void);
2+
3+
int main(void) {
4+
run_tests();
5+
return 0;
6+
}

‎tests/wasm/run.sh‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
lua="${1:-luau}"
5+
c_target="${2:-wasm32-wasip1}"
6+
if [[ $# -gt 2 || ! "$lua" =~ ^(luau|lua5[1-5])$ || ! "$c_target" =~ ^wasm32-wasip[12]$ ]]; then
7+
echo "Usage: $0 [lua55|lua54|lua53|lua52|lua51|luau] [wasm32-wasip1|wasm32-wasip2]" >&2
8+
exit 2
9+
fi
10+
11+
cd "$(dirname "$0")/../.."
12+
source tests/wasm/setup.sh wasm32-unknown-unknown "$c_target"
13+
export CARGO_TARGET_DIR="$PWD/target/wasm-tests"
14+
cargo_args=(--manifest-path tests/wasm/Cargo.toml --target wasm32-unknown-unknown --features "$lua")
15+
wasm_output="$CARGO_TARGET_DIR/wasm32-unknown-unknown/debug/mlua_wasm_tests.wasm"
16+
if [[ "$c_target" == wasm32-wasip1 ]]; then
17+
cargo build "${cargo_args[@]}"
18+
"${wasm_runner[@]}" --invoke run_tests "$wasm_output"
19+
else
20+
# The SDK links the bare Rust static library into a WASIp2 command component.
21+
wasm_output="${wasm_output%.wasm}.component.wasm"
22+
cargo rustc "${cargo_args[@]}" --lib --crate-type staticlib
23+
"$WASI_SDK_PATH/bin/clang" --target="$c_target" tests/wasm/main.c \
24+
"$CARGO_TARGET_DIR/wasm32-unknown-unknown/debug/libmlua_wasm_tests.a" \
25+
-L"$wasm_lib/eh" -lc++ -lsetjmp -o "$wasm_output"
26+
"${wasm_runner[@]}" "$wasm_output"
27+
fi

‎tests/wasm/setup.sh‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#!/usr/bin/env bash
2+
3+
# Source with a Rust target and optional C target for wasm32-unknown-unknown.
4+
: "${WASI_SDK_PATH:?Set WASI_SDK_PATH}"
5+
wasm_target="${1:?Pass a Rust target}"
6+
wasm_c_target="$wasm_target"
7+
if [[ "$wasm_target" == wasm32-unknown-unknown ]]; then
8+
wasm_c_target="${2:-wasm32-wasip1}"
9+
fi
10+
wasm_cc_target="${wasm_target//-/_}"
11+
wasm_cargo_target="CARGO_TARGET_$(printf '%s' "$wasm_cc_target" | tr '[:lower:]' '[:upper:]')"
12+
wasm_lib="$WASI_SDK_PATH/share/wasi-sysroot/lib/$wasm_c_target"
13+
wasm_rustflags="-Lnative=$wasm_lib/eh -Lnative=$wasm_lib -lstatic=c++abi -lstatic=unwind -lstatic=wasi-emulated-process-clocks"
14+
# Match the SDK's standard Wasm exception ABI.
15+
# https://github.com/WebAssembly/wasi-sdk/blob/wasi-sdk-34/CppExceptions.md
16+
wasm_cxxflags="-fexceptions -fwasm-exceptions -mllvm -wasm-use-legacy-eh=false"
17+
wasm_runner=(wasmtime -W exceptions)
18+
wasm_env=(
19+
"CC_$wasm_cc_target=$WASI_SDK_PATH/bin/clang"
20+
"CXX_$wasm_cc_target=$WASI_SDK_PATH/bin/clang++"
21+
"AR_$wasm_cc_target=$WASI_SDK_PATH/bin/llvm-ar"
22+
"CXXSTDLIB_$wasm_cc_target=c++"
23+
)
24+
if [[ "$wasm_target" == wasm32-unknown-unknown ]]; then
25+
wasm_env+=("CFLAGS_$wasm_cc_target=--target=$wasm_c_target")
26+
wasm_cxxflags="--target=$wasm_c_target $wasm_cxxflags"
27+
if [[ "$wasm_c_target" == wasm32-wasip1 ]]; then
28+
wasm_rustflags+=" -lstatic=c"
29+
fi
30+
else
31+
wasm_env+=("${wasm_cargo_target}_LINKER=$WASI_SDK_PATH/bin/clang")
32+
wasm_rustflags+=" -Clink-self-contained=no"
33+
if [[ "$wasm_target" != wasm32-wasip1 ]]; then
34+
wasm_rustflags+=" -Clink-arg=-Wl,--export=cabi_realloc"
35+
fi
36+
fi
37+
if [[ "$wasm_target" == wasm32-wasip3 ]]; then
38+
wasm_runner+=(-S p3)
39+
fi
40+
wasm_env+=(
41+
"LUAU_CXXFLAGS=$wasm_cxxflags"
42+
"${wasm_cargo_target}_RUSTFLAGS=$wasm_rustflags"
43+
"${wasm_cargo_target}_RUNNER=${wasm_runner[*]}"
44+
)
45+
export "${wasm_env[@]}"

‎tests/wasm/src/lib.rs‎

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
use mlua::{Error, Lua};
2+
3+
#[cfg(target_os = "unknown")]
4+
mod allocator {
5+
use std::alloc::{GlobalAlloc, Layout};
6+
use std::ffi::c_void;
7+
8+
// Rust and WASI libc must share an allocator
9+
struct WasiAllocator;
10+
11+
unsafe extern "C" {
12+
fn posix_memalign(ptr: *mut *mut c_void, align: usize, size: usize) -> i32;
13+
fn free(ptr: *mut c_void);
14+
}
15+
16+
unsafe impl GlobalAlloc for WasiAllocator {
17+
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
18+
let mut ptr = std::ptr::null_mut();
19+
if unsafe { posix_memalign(&mut ptr, layout.align().max(size_of::<usize>()), layout.size()) } != 0
20+
{
21+
return std::ptr::null_mut();
22+
}
23+
ptr.cast()
24+
}
25+
26+
unsafe fn dealloc(&self, ptr: *mut u8, _: Layout) {
27+
unsafe { free(ptr.cast()) };
28+
}
29+
}
30+
31+
#[global_allocator]
32+
static ALLOCATOR: WasiAllocator = WasiAllocator;
33+
}
34+
35+
#[cfg(target_os = "unknown")]
36+
#[unsafe(no_mangle)]
37+
pub extern "C" fn run_tests() {
38+
test_eval();
39+
test_errors();
40+
test_callbacks();
41+
test_coroutines();
42+
}
43+
44+
#[cfg_attr(target_os = "wasi", test)]
45+
fn test_eval() {
46+
let lua = Lua::new();
47+
assert_eq!(lua.load("return 6 * 7").eval::<i32>().unwrap(), 42);
48+
}
49+
50+
#[cfg_attr(target_os = "wasi", test)]
51+
fn test_errors() {
52+
let lua = Lua::new();
53+
assert!(matches!(
54+
lua.load("local =").exec(),
55+
Err(Error::SyntaxError { .. })
56+
));
57+
assert!(matches!(
58+
lua.load("error('test error')").exec(),
59+
Err(Error::RuntimeError(_))
60+
));
61+
assert!(
62+
!lua.load("return pcall(function() error('test error') end)")
63+
.eval::<bool>()
64+
.unwrap()
65+
);
66+
assert_eq!(lua.load("return 42").eval::<i32>().unwrap(), 42);
67+
}
68+
69+
#[cfg_attr(target_os = "wasi", test)]
70+
fn test_callbacks() {
71+
let lua = Lua::new();
72+
let double = lua.create_function(|_, n: i32| Ok(n * 2)).unwrap();
73+
lua.globals().set("double", double).unwrap();
74+
assert_eq!(lua.load("return double(21)").eval::<i32>().unwrap(), 42);
75+
let fail = lua
76+
.create_function(|_, ()| Err::<(), _>(Error::runtime("callback error")))
77+
.unwrap();
78+
lua.globals().set("fail", fail).unwrap();
79+
assert!(matches!(
80+
lua.load("fail()").exec(),
81+
Err(Error::CallbackError { .. })
82+
));
83+
assert!(!lua.load("return pcall(fail)").eval::<bool>().unwrap());
84+
}
85+
86+
#[cfg_attr(target_os = "wasi", test)]
87+
fn test_coroutines() {
88+
let lua = Lua::new();
89+
let function = lua
90+
.load("local n = ...; coroutine.yield(n); return n * 2")
91+
.into_function()
92+
.unwrap();
93+
let thread = lua.create_thread(function).unwrap();
94+
assert_eq!(thread.resume::<i32>(21).unwrap(), 21);
95+
assert_eq!(thread.resume::<i32>(()).unwrap(), 42);
96+
}

0 commit comments

Comments
 (0)