mirror of
https://github.com/rustwasm/wasm-bindgen.git
synced 2025-01-05 19:53:55 +03:00
Worker tests (#3351)
* Running tests on workers without displaying errors * Initializing the wasm bindgen only in worker * Adding tests to run in workers * Format * Adding webdriver.json file * Removed memory module from worker tests * Keeping original JsValue without overwriting * Updated the documentation
This commit is contained in:
parent
153a6aa9c7
commit
0753bec4c6
1
.gitignore
vendored
1
.gitignore
vendored
@ -9,4 +9,3 @@ yarn.lock
|
||||
/publish
|
||||
/publish.exe
|
||||
.vscode
|
||||
webdriver.json
|
||||
|
@ -28,8 +28,8 @@ mod shell;
|
||||
enum TestMode {
|
||||
Node,
|
||||
Deno,
|
||||
Browser,
|
||||
NoModule,
|
||||
Browser { no_modules: bool },
|
||||
Worker { no_modules: bool },
|
||||
}
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
@ -100,13 +100,12 @@ fn main() -> anyhow::Result<()> {
|
||||
|
||||
let custom_section = wasm.customs.remove_raw("__wasm_bindgen_test_unstable");
|
||||
let test_mode = match custom_section {
|
||||
Some(section) if section.data.contains(&0x01) => {
|
||||
if std::env::var("WASM_BINDGEN_USE_NO_MODULE").is_ok() {
|
||||
TestMode::NoModule
|
||||
} else {
|
||||
TestMode::Browser
|
||||
}
|
||||
}
|
||||
Some(section) if section.data.contains(&0x01) => TestMode::Browser {
|
||||
no_modules: std::env::var("WASM_BINDGEN_USE_NO_MODULE").is_ok(),
|
||||
},
|
||||
Some(section) if section.data.contains(&0x10) => TestMode::Worker {
|
||||
no_modules: std::env::var("WASM_BINDGEN_USE_NO_MODULE").is_ok(),
|
||||
},
|
||||
Some(_) => bail!("invalid __wasm_bingen_test_unstable value"),
|
||||
None if std::env::var("WASM_BINDGEN_USE_DENO").is_ok() => TestMode::Deno,
|
||||
None => TestMode::Node,
|
||||
@ -163,8 +162,12 @@ integration test.\
|
||||
match test_mode {
|
||||
TestMode::Node => b.nodejs(true)?,
|
||||
TestMode::Deno => b.deno(true)?,
|
||||
TestMode::Browser => b.web(true)?,
|
||||
TestMode::NoModule => b.no_modules(true)?,
|
||||
TestMode::Browser { no_modules: false } | TestMode::Worker { no_modules: false } => {
|
||||
b.web(true)?
|
||||
}
|
||||
TestMode::Browser { no_modules: true } | TestMode::Worker { no_modules: true } => {
|
||||
b.no_modules(true)?
|
||||
}
|
||||
};
|
||||
|
||||
if std::env::var("WASM_BINDGEN_SPLIT_LINKED_MODULES").is_ok() {
|
||||
@ -184,7 +187,7 @@ integration test.\
|
||||
match test_mode {
|
||||
TestMode::Node => node::execute(&module, &tmpdir, &args, &tests)?,
|
||||
TestMode::Deno => deno::execute(&module, &tmpdir, &args, &tests)?,
|
||||
TestMode::Browser | TestMode::NoModule => {
|
||||
TestMode::Browser { no_modules } | TestMode::Worker { no_modules } => {
|
||||
let srv = server::spawn(
|
||||
&if headless {
|
||||
"127.0.0.1:0".parse().unwrap()
|
||||
@ -198,7 +201,8 @@ integration test.\
|
||||
&tmpdir,
|
||||
&args,
|
||||
&tests,
|
||||
matches!(test_mode, TestMode::NoModule),
|
||||
no_modules,
|
||||
matches!(test_mode, TestMode::Worker { no_modules: _ }),
|
||||
)
|
||||
.context("failed to spawn server")?;
|
||||
let addr = srv.server_addr();
|
||||
|
@ -15,8 +15,11 @@ pub fn spawn(
|
||||
args: &[OsString],
|
||||
tests: &[String],
|
||||
no_module: bool,
|
||||
worker: bool,
|
||||
) -> Result<Server<impl Fn(&Request) -> Response + Send + Sync>, Error> {
|
||||
let mut js_to_execute = if no_module {
|
||||
let mut js_to_execute = String::new();
|
||||
|
||||
let wbg_import_script = if no_module {
|
||||
String::from(
|
||||
r#"
|
||||
let Context = wasm_bindgen.WasmBindgenTestContext;
|
||||
@ -45,36 +48,140 @@ pub fn spawn(
|
||||
)
|
||||
};
|
||||
|
||||
js_to_execute.push_str(&format!(
|
||||
r#"
|
||||
// Now that we've gotten to the point where JS is executing, update our
|
||||
// status text as at this point we should be asynchronously fetching the
|
||||
// wasm module.
|
||||
document.getElementById('output').textContent = "Loading wasm module...";
|
||||
if worker {
|
||||
let mut worker_script = if no_module {
|
||||
format!(r#"importScripts("{0}.js");"#, module)
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
async function main(test) {{
|
||||
const wasm = await init('./{0}_bg.wasm');
|
||||
worker_script.push_str(&wbg_import_script);
|
||||
|
||||
const cx = new Context();
|
||||
window.on_console_debug = __wbgtest_console_debug;
|
||||
window.on_console_log = __wbgtest_console_log;
|
||||
window.on_console_info = __wbgtest_console_info;
|
||||
window.on_console_warn = __wbgtest_console_warn;
|
||||
window.on_console_error = __wbgtest_console_error;
|
||||
worker_script.push_str(&format!(
|
||||
r#"
|
||||
const wrap = method => {{
|
||||
const on_method = `on_console_${{method}}`;
|
||||
self.console[method] = function (...args) {{
|
||||
if (self[on_method]) {{
|
||||
self[on_method](args);
|
||||
}}
|
||||
postMessage(["__wbgtest_" + method, args]);
|
||||
}};
|
||||
}};
|
||||
|
||||
// Forward runtime arguments. These arguments are also arguments to the
|
||||
// `wasm-bindgen-test-runner` which forwards them to node which we
|
||||
// forward to the test harness. this is basically only used for test
|
||||
// filters for now.
|
||||
cx.args({1:?});
|
||||
self.__wbg_test_invoke = f => f();
|
||||
self.__wbg_test_output = "";
|
||||
self.__wbg_test_output_writeln = function (line) {{
|
||||
self.__wbg_test_output += line + "\n";
|
||||
postMessage(["__wbgtest_output", self.__wbg_test_output]);
|
||||
}}
|
||||
|
||||
await cx.run(test.map(s => wasm[s]));
|
||||
}}
|
||||
wrap("debug");
|
||||
wrap("log");
|
||||
wrap("info");
|
||||
wrap("warn");
|
||||
wrap("error");
|
||||
|
||||
const tests = [];
|
||||
"#,
|
||||
module, args,
|
||||
));
|
||||
async function run_in_worker(tests) {{
|
||||
const wasm = await init("./{0}_bg.wasm");
|
||||
const t = self;
|
||||
const cx = new Context();
|
||||
|
||||
self.on_console_debug = __wbgtest_console_debug;
|
||||
self.on_console_log = __wbgtest_console_log;
|
||||
self.on_console_info = __wbgtest_console_info;
|
||||
self.on_console_warn = __wbgtest_console_warn;
|
||||
self.on_console_error = __wbgtest_console_error;
|
||||
|
||||
cx.args({1:?});
|
||||
await cx.run(tests.map(s => wasm[s]));
|
||||
}}
|
||||
|
||||
onmessage = function(e) {{
|
||||
let tests = e.data;
|
||||
run_in_worker(tests);
|
||||
}}
|
||||
"#,
|
||||
module, args,
|
||||
));
|
||||
|
||||
let worker_js_path = tmpdir.join("worker.js");
|
||||
fs::write(&worker_js_path, worker_script).context("failed to write JS file")?;
|
||||
|
||||
js_to_execute.push_str(&format!(
|
||||
r#"
|
||||
// Now that we've gotten to the point where JS is executing, update our
|
||||
// status text as at this point we should be asynchronously fetching the
|
||||
// wasm module.
|
||||
document.getElementById('output').textContent = "Loading wasm module...";
|
||||
const worker = new Worker("worker.js", {{type: "{}"}});
|
||||
|
||||
worker.addEventListener("message", function(e) {{
|
||||
// Checking the whether the message is from wasm_bindgen_test
|
||||
if(
|
||||
e.data &&
|
||||
Array.isArray(e.data) &&
|
||||
e.data[0] &&
|
||||
typeof e.data[0] == "string" &&
|
||||
e.data[0].slice(0,10)=="__wbgtest_"
|
||||
) {{
|
||||
const method = e.data[0].slice(10);
|
||||
const args = e.data.slice(1);
|
||||
|
||||
if (
|
||||
method == "log" || method == "error" ||
|
||||
method == "warn" || method == "info" ||
|
||||
method == "debug"
|
||||
) {{
|
||||
console[method].apply(undefined, args[0]);
|
||||
}} else if (method == "output") {{
|
||||
document.getElementById("output").textContent = args[0];
|
||||
}}
|
||||
}}
|
||||
}});
|
||||
|
||||
async function main(test) {{
|
||||
worker.postMessage(test)
|
||||
}}
|
||||
|
||||
const tests = [];
|
||||
"#,
|
||||
if no_module { "classic" } else { "module" }
|
||||
));
|
||||
} else {
|
||||
js_to_execute.push_str(&wbg_import_script);
|
||||
|
||||
js_to_execute.push_str(&format!(
|
||||
r#"
|
||||
// Now that we've gotten to the point where JS is executing, update our
|
||||
// status text as at this point we should be asynchronously fetching the
|
||||
// wasm module.
|
||||
document.getElementById('output').textContent = "Loading wasm module...";
|
||||
|
||||
async function main(test) {{
|
||||
const wasm = await init('./{0}_bg.wasm');
|
||||
|
||||
const cx = new Context();
|
||||
window.on_console_debug = __wbgtest_console_debug;
|
||||
window.on_console_log = __wbgtest_console_log;
|
||||
window.on_console_info = __wbgtest_console_info;
|
||||
window.on_console_warn = __wbgtest_console_warn;
|
||||
window.on_console_error = __wbgtest_console_error;
|
||||
|
||||
// Forward runtime arguments. These arguments are also arguments to the
|
||||
// `wasm-bindgen-test-runner` which forwards them to node which we
|
||||
// forward to the test harness. this is basically only used for test
|
||||
// filters for now.
|
||||
cx.args({1:?});
|
||||
|
||||
await cx.run(test.map(s => wasm[s]));
|
||||
}}
|
||||
|
||||
const tests = [];
|
||||
"#,
|
||||
module, args,
|
||||
));
|
||||
}
|
||||
for test in tests {
|
||||
js_to_execute.push_str(&format!("tests.push('{}');\n", test));
|
||||
}
|
||||
|
@ -35,6 +35,8 @@ macro_rules! console_log {
|
||||
///
|
||||
/// * `run_in_browser` - requires that this test is run in a browser rather than
|
||||
/// node.js, which is the default for executing tests.
|
||||
/// * `run_in_worker` - requires that this test is run in a web worker rather than
|
||||
/// node.js, which is the default for executing tests.
|
||||
///
|
||||
/// This macro may be invoked at most one time per test suite (an entire binary
|
||||
/// like `tests/foo.rs`, not per module)
|
||||
@ -46,6 +48,12 @@ macro_rules! wasm_bindgen_test_configure {
|
||||
pub static __WBG_TEST_RUN_IN_BROWSER: [u8; 1] = [0x01];
|
||||
$crate::wasm_bindgen_test_configure!($($others)*);
|
||||
);
|
||||
(run_in_worker $($others:tt)*) => (
|
||||
#[link_section = "__wasm_bindgen_test_unstable"]
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub static __WBG_TEST_RUN_IN_WORKER: [u8; 1] = [0x10];
|
||||
$crate::wasm_bindgen_test_configure!($($others)*);
|
||||
);
|
||||
() => ()
|
||||
}
|
||||
|
||||
|
@ -6,19 +6,37 @@ use wasm_bindgen::prelude::*;
|
||||
extern "C" {
|
||||
type This;
|
||||
#[wasm_bindgen(method, getter, structural, js_name = self)]
|
||||
fn self_(me: &This) -> JsValue;
|
||||
fn self_(me: &This) -> Option<Scope>;
|
||||
|
||||
type Scope;
|
||||
#[wasm_bindgen(method, getter, structural)]
|
||||
fn constructor(me: &Scope) -> Constructor;
|
||||
|
||||
type Constructor;
|
||||
#[wasm_bindgen(method, getter, structural)]
|
||||
fn name(me: &Constructor) -> String;
|
||||
}
|
||||
|
||||
/// Returns whether it's likely we're executing in a browser environment, as
|
||||
/// opposed to node.js.
|
||||
// If this function is inlined then there's no other functions in this module
|
||||
// (which becomes an object file) to actually pull in the custom section listed
|
||||
// above. Force this to never be inlined so if this module is needed its forced
|
||||
// to pull in the descriptor section from `#[wasm_bindgen]` above.
|
||||
#[inline(never)]
|
||||
pub fn is_browser() -> bool {
|
||||
// Test whether we're in a browser by seeing if the `self` property is
|
||||
// defined on the global object, which should in turn only be true in
|
||||
// browsers.
|
||||
js_sys::global().unchecked_into::<This>().self_() != JsValue::undefined()
|
||||
/// Detecting the current JS scope
|
||||
pub fn detect() -> Runtime {
|
||||
// Test whether we're in a browser/worker by seeing if the `self` property is
|
||||
// defined on the global object and it is not equal to a WorkerScope, which should in turn
|
||||
// only be true in browsers.
|
||||
match js_sys::global().unchecked_into::<This>().self_() {
|
||||
Some(scope) => match scope.constructor().name().as_str() {
|
||||
"DedicatedWorkerGlobalScope" | "SharedWorkerGlobalScope" => Runtime::Worker,
|
||||
_ => Runtime::Browser,
|
||||
},
|
||||
None => Runtime::Node,
|
||||
}
|
||||
}
|
||||
|
||||
/// Current runtime environment
|
||||
pub enum Runtime {
|
||||
/// Current scope is a browser scope
|
||||
Browser,
|
||||
/// Current scope is a node scope
|
||||
Node,
|
||||
/// Current scope is a worker scope
|
||||
Worker,
|
||||
}
|
||||
|
@ -109,6 +109,7 @@ const CONCURRENCY: usize = 1;
|
||||
pub mod browser;
|
||||
pub mod detect;
|
||||
pub mod node;
|
||||
pub mod worker;
|
||||
|
||||
/// Runtime test harness support instantiated in JS.
|
||||
///
|
||||
@ -237,10 +238,12 @@ impl Context {
|
||||
}));
|
||||
});
|
||||
|
||||
let formatter = match node::Node::new() {
|
||||
Some(node) => Box::new(node) as Box<dyn Formatter>,
|
||||
None => Box::new(browser::Browser::new()),
|
||||
let formatter = match detect::detect() {
|
||||
detect::Runtime::Browser => Box::new(browser::Browser::new()) as Box<dyn Formatter>,
|
||||
detect::Runtime::Node => Box::new(node::Node::new()) as Box<dyn Formatter>,
|
||||
detect::Runtime::Worker => Box::new(worker::Worker::new()) as Box<dyn Formatter>,
|
||||
};
|
||||
|
||||
Context {
|
||||
state: Rc::new(State {
|
||||
filter: Default::default(),
|
||||
|
@ -18,13 +18,9 @@ extern "C" {
|
||||
}
|
||||
|
||||
impl Node {
|
||||
/// Attempts to create a new formatter for node.js, returning `None` if this
|
||||
/// is executing in a browser and Node won't work.
|
||||
pub fn new() -> Option<Node> {
|
||||
if super::detect::is_browser() {
|
||||
return None;
|
||||
}
|
||||
Some(Node {})
|
||||
/// Attempts to create a new formatter for node.js
|
||||
pub fn new() -> Node {
|
||||
Node {}
|
||||
}
|
||||
}
|
||||
|
||||
|
66
crates/test/src/rt/worker.rs
Normal file
66
crates/test/src/rt/worker.rs
Normal file
@ -0,0 +1,66 @@
|
||||
//! Support for printing status information of a test suite in a browser.
|
||||
//!
|
||||
//! Currently this is quite simple, rendering the same as the console tests in
|
||||
//! node.js. Output here is rendered in a `pre`, however.
|
||||
|
||||
use js_sys::Error;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
/// Implementation of `Formatter` for browsers.
|
||||
///
|
||||
/// Routes all output to a `pre` on the page currently. Eventually this probably
|
||||
/// wants to be a pretty table with colors and folding and whatnot.
|
||||
pub struct Worker {}
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
type WorkerError;
|
||||
#[wasm_bindgen(method, getter, structural)]
|
||||
fn stack(this: &WorkerError) -> JsValue;
|
||||
|
||||
#[wasm_bindgen(js_name = "__wbg_test_output_writeln")]
|
||||
fn write_output_line(data: JsValue);
|
||||
}
|
||||
|
||||
impl Worker {
|
||||
/// Attempts to create a new formatter for web worker
|
||||
pub fn new() -> Worker {
|
||||
Worker {}
|
||||
}
|
||||
}
|
||||
|
||||
impl super::Formatter for Worker {
|
||||
fn writeln(&self, line: &str) {
|
||||
write_output_line(JsValue::from(String::from(line)));
|
||||
}
|
||||
|
||||
fn log_test(&self, name: &str, result: &Result<(), JsValue>) {
|
||||
let s = if result.is_ok() { "ok" } else { "FAIL" };
|
||||
self.writeln(&format!("test {} ... {}", name, s));
|
||||
}
|
||||
|
||||
fn stringify_error(&self, err: &JsValue) -> String {
|
||||
// TODO: this should be a checked cast to `Error`
|
||||
let error = Error::from(err.clone());
|
||||
let name = String::from(error.name());
|
||||
let message = String::from(error.message());
|
||||
let err = WorkerError::from(err.clone());
|
||||
let stack = err.stack();
|
||||
|
||||
let header = format!("{}: {}", name, message);
|
||||
let stack = match stack.as_string() {
|
||||
Some(stack) => stack,
|
||||
None => return header,
|
||||
};
|
||||
|
||||
// If the `stack` variable contains the name/message already, this is
|
||||
// probably a chome-like error which is already rendered well, so just
|
||||
// return this info
|
||||
if stack.contains(&header) {
|
||||
return stack;
|
||||
}
|
||||
|
||||
// Fallback to make sure we don't lose any info
|
||||
format!("{}\n{}", header, stack)
|
||||
}
|
||||
}
|
@ -10,6 +10,16 @@ use wasm_bindgen_test::wasm_bindgen_test_configure;
|
||||
wasm_bindgen_test_configure!(run_in_browser);
|
||||
```
|
||||
|
||||
Or if you need to run your tests inside a web worker, you can also
|
||||
configured it using the `wasm_bindgen_test_configure` macro as following
|
||||
snippet.
|
||||
|
||||
```rust
|
||||
use wasm_bindgen_test::wasm_bindgen_test_configure;
|
||||
|
||||
wasm_bindgen_test_configure!(run_in_worker);
|
||||
```
|
||||
|
||||
Note that although a particular test crate must target either headless browsers
|
||||
or Node.js, you can have test suites for both Node.js and browsers for your
|
||||
project by using multiple test crates. For example:
|
||||
@ -18,6 +28,7 @@ project by using multiple test crates. For example:
|
||||
$MY_CRATE/
|
||||
`-- tests
|
||||
|-- node.rs # The tests in this suite use the default Node.js.
|
||||
|-- worker.rs # The tests in this suite are configured for workers.
|
||||
`-- web.rs # The tests in this suite are configured for browsers.
|
||||
```
|
||||
|
||||
@ -75,6 +86,12 @@ Full list supported capabilities can be found:
|
||||
|
||||
Note that the `headless` argument is always enabled for both browsers.
|
||||
|
||||
You have to enable the special preference `dom.workers.modules.enabled` for
|
||||
firefox when running the tests in Web Workers without using
|
||||
`WASM_BINDGEN_USE_NO_MODULE` variable. Because firefox supported
|
||||
ECMAScript modules in last release (2023-03-14) behind a special
|
||||
preference.
|
||||
|
||||
### Debugging Headless Browser Tests
|
||||
|
||||
Omitting the `--headless` flag will disable headless mode, and allow you to
|
||||
|
18
tests/worker/main.rs
Normal file
18
tests/worker/main.rs
Normal file
@ -0,0 +1,18 @@
|
||||
#![cfg(target_arch = "wasm32")]
|
||||
|
||||
extern crate js_sys;
|
||||
extern crate wasm_bindgen;
|
||||
extern crate wasm_bindgen_test;
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure};
|
||||
|
||||
wasm_bindgen_test_configure!(run_in_worker);
|
||||
|
||||
pub mod modules;
|
||||
|
||||
// should not be executed
|
||||
#[wasm_bindgen(start)]
|
||||
fn start() {
|
||||
panic!();
|
||||
}
|
3
tests/worker/modules.js
Normal file
3
tests/worker/modules.js
Normal file
@ -0,0 +1,3 @@
|
||||
export function get_five() {
|
||||
return 5;
|
||||
}
|
12
tests/worker/modules.rs
Normal file
12
tests/worker/modules.rs
Normal file
@ -0,0 +1,12 @@
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_test::*;
|
||||
|
||||
#[wasm_bindgen(raw_module = "./tests/worker/modules.js")]
|
||||
extern "C" {
|
||||
fn get_five() -> u32;
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn test_get_five() {
|
||||
assert_eq!(get_five(), 5);
|
||||
}
|
8
webdriver.json
Normal file
8
webdriver.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"moz:firefoxOptions": {
|
||||
"prefs": {
|
||||
"dom.workers.modules.enabled": true
|
||||
},
|
||||
"args": []
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user