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:
WhizSid 2023-04-26 19:39:15 +05:30 committed by GitHub
parent 153a6aa9c7
commit 0753bec4c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 322 additions and 63 deletions

1
.gitignore vendored
View File

@ -9,4 +9,3 @@ yarn.lock
/publish
/publish.exe
.vscode
webdriver.json

View File

@ -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();

View File

@ -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));
}

View File

@ -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)*);
);
() => ()
}

View File

@ -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,
}

View File

@ -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(),

View File

@ -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 {}
}
}

View 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)
}
}

View File

@ -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
View 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
View File

@ -0,0 +1,3 @@
export function get_five() {
return 5;
}

12
tests/worker/modules.rs Normal file
View 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
View File

@ -0,0 +1,8 @@
{
"moz:firefoxOptions": {
"prefs": {
"dom.workers.modules.enabled": true
},
"args": []
}
}