mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-06-12 20:41:24 +00:00
Support asynchronous tests (#600)
* Tweak the implementation of heap closures This commit updates the implementation of the `Closure` type to internally store an `Rc` and be suitable for dropping a `Closure` during the execution of the closure. This is currently needed for promises but may be generally useful as well! * Support asynchronous tests This commit adds support for executing tests asynchronously. This is modeled by tests returning a `Future` instead of simply executing inline, and is signified with `#[wasm_bindgen_test(async)]`. Support for this is added through a new `wasm-bindgen-futures` crate which is a binding between the `futures` crate and JS `Promise` objects. Lots more details can be found in the details of the commit, but one of the end results is that the `web-sys` tests are now entirely contained in the same test suite and don't need `npm install` to be run to execute them! * Review tweaks * Add some bindings for `Function.call` to `js_sys` Name them `call0`, `call1`, `call2`, ... for the number of arguments being passed. * Use oneshots channels with `JsFuture` It did indeed clean up the implementation!
This commit is contained in:
@ -214,7 +214,6 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
||||
builder.rust_argument("this.a");
|
||||
}
|
||||
builder
|
||||
.rust_argument("this.b")
|
||||
.process(&closure.function)?
|
||||
.finish("function", "this.f")
|
||||
};
|
||||
@ -225,18 +224,16 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
||||
let reset_idx = format!(
|
||||
"\
|
||||
let cb{0} = {js};\n\
|
||||
cb{0}.f = wasm.__wbg_function_table.get(getGlobalArgument({f}));\n\
|
||||
cb{0}.a = getGlobalArgument({a});\n\
|
||||
cb{0}.b = getGlobalArgument({b});\n\
|
||||
cb{0}.f = wasm.__wbg_function_table.get(getGlobalArgument({c}));\n\
|
||||
let real = cb{0}.bind(cb{0});\n\
|
||||
real.original = cb{0};\n\
|
||||
idx{0} = getUint32Memory()[{0} / 4] = addHeapObject(real);\n\
|
||||
",
|
||||
abi,
|
||||
js = js,
|
||||
f = self.global_idx(),
|
||||
a = self.global_idx(),
|
||||
b = self.global_idx(),
|
||||
c = self.global_idx(),
|
||||
);
|
||||
self.prelude(&format!(
|
||||
"\
|
||||
|
@ -20,17 +20,18 @@
|
||||
}
|
||||
};
|
||||
console.log = function() {
|
||||
if (window.global_cx)
|
||||
window.global_cx.console_log(orig_console_log, arguments);
|
||||
if (window.console_log_redirect)
|
||||
window.console_log_redirect(orig_console_log, arguments);
|
||||
else
|
||||
orig_console_log.apply(this, arguments);
|
||||
};
|
||||
console.error = function() {
|
||||
if (window.global_cx)
|
||||
window.global_cx.console_error(orig_console_error, arguments);
|
||||
if (window.console_error_redirect)
|
||||
window.console_error_redirect(orig_console_error, arguments);
|
||||
else
|
||||
orig_console_error.apply(this, arguments);
|
||||
};
|
||||
window.__wbg_test_invoke = f => f();
|
||||
</script>
|
||||
<script src='run.js' type=module></script>
|
||||
</body>
|
||||
|
@ -8,17 +8,19 @@
|
||||
const orig_console_log = console.log;
|
||||
const orig_console_error = console.error;
|
||||
console.log = function() {
|
||||
if (window.global_cx)
|
||||
window.global_cx.console_log(orig_console_log, arguments);
|
||||
if (window.console_log_redirect)
|
||||
window.console_log_redirect(orig_console_log, arguments);
|
||||
else
|
||||
orig_console_log.apply(this, arguments);
|
||||
};
|
||||
console.error = function() {
|
||||
if (window.global_cx)
|
||||
window.global_cx.console_error(orig_console_error, arguments);
|
||||
if (window.console_error_redirect)
|
||||
window.console_error_redirect(orig_console_error, arguments);
|
||||
else
|
||||
orig_console_error.apply(this, arguments);
|
||||
};
|
||||
|
||||
window.__wbg_test_invoke = f => f();
|
||||
</script>
|
||||
<script src='run.js' type=module></script>
|
||||
</body>
|
||||
|
@ -12,33 +12,38 @@ pub fn execute(module: &str, tmpdir: &Path, args: &[OsString], tests: &[String])
|
||||
let mut js_to_execute = format!(r#"
|
||||
const {{ exit }} = require('process');
|
||||
|
||||
let cx = null;
|
||||
let console_log_redirect = null;
|
||||
let console_error_redirect = null;
|
||||
|
||||
// override `console.log` and `console.error` before we import tests to
|
||||
// ensure they're bound correctly in wasm. This'll allow us to intercept
|
||||
// all these calls and capture the output of tests
|
||||
const prev_log = console.log;
|
||||
console.log = function() {{
|
||||
if (cx === null) {{
|
||||
if (console_log_redirect === null) {{
|
||||
prev_log.apply(null, arguments);
|
||||
}} else {{
|
||||
cx.console_log(prev_log, arguments);
|
||||
console_log_redirect(prev_log, arguments);
|
||||
}}
|
||||
}};
|
||||
const prev_error = console.error;
|
||||
console.error = function() {{
|
||||
if (cx === null) {{
|
||||
if (console_error_redirect === null) {{
|
||||
prev_error.apply(null, arguments);
|
||||
}} else {{
|
||||
cx.console_error(prev_error, arguments);
|
||||
console_error_redirect(prev_error, arguments);
|
||||
}}
|
||||
}};
|
||||
|
||||
function main(tests) {{
|
||||
global.__wbg_test_invoke = f => f();
|
||||
|
||||
async function main(tests) {{
|
||||
const support = require("./{0}");
|
||||
const wasm = require("./{0}_bg");
|
||||
|
||||
cx = new support.Context();
|
||||
console_log_redirect = support.__wbgtest_console_log;
|
||||
console_error_redirect = support.__wbgtest_console_error;
|
||||
|
||||
// Forward runtime arguments. These arguments are also arguments to the
|
||||
// `wasm-bindgen-test-runner` which forwards them to node which we
|
||||
@ -46,7 +51,8 @@ pub fn execute(module: &str, tmpdir: &Path, args: &[OsString], tests: &[String])
|
||||
// filters for now.
|
||||
cx.args(process.argv.slice(2));
|
||||
|
||||
if (!cx.run(tests.map(n => wasm[n])))
|
||||
const ok = await cx.run(tests.map(n => wasm[n]));
|
||||
if (!ok)
|
||||
exit(1);
|
||||
}}
|
||||
|
||||
@ -64,6 +70,10 @@ pub fn execute(module: &str, tmpdir: &Path, args: &[OsString], tests: &[String])
|
||||
// And as a final addendum, exit with a nonzero code if any tests fail.
|
||||
js_to_execute.push_str("
|
||||
main(tests)
|
||||
.catch(e => {
|
||||
console.error(e);
|
||||
exit(1);
|
||||
});
|
||||
");
|
||||
|
||||
let js_path = tmpdir.join("run.js");
|
||||
|
@ -16,7 +16,7 @@ pub fn spawn(
|
||||
tests: &[String],
|
||||
) -> Result<Server<impl Fn(&Request) -> Response + Send + Sync>, Error> {
|
||||
let mut js_to_execute = format!(r#"
|
||||
import {{ Context }} from './{0}';
|
||||
import {{ Context, __wbgtest_console_log, __wbgtest_console_error }} from './{0}';
|
||||
import * as wasm from './{0}_bg';
|
||||
|
||||
// Now that we've gotten to the point where JS is executing, update our
|
||||
@ -30,7 +30,8 @@ pub fn spawn(
|
||||
await wasm.booted;
|
||||
|
||||
const cx = Context.new();
|
||||
window.global_cx = cx;
|
||||
window.console_log_redirect = __wbgtest_console_log;
|
||||
window.console_error_redirect = __wbgtest_console_error;
|
||||
|
||||
// Forward runtime arguments. These arguments are also arguments to the
|
||||
// `wasm-bindgen-test-runner` which forwards them to node which we
|
||||
@ -38,7 +39,7 @@ pub fn spawn(
|
||||
// filters for now.
|
||||
cx.args({1:?});
|
||||
|
||||
cx.run(test.map(s => wasm[s]));
|
||||
await cx.run(test.map(s => wasm[s]));
|
||||
}}
|
||||
|
||||
const tests = [];
|
||||
|
9
crates/futures/Cargo.toml
Normal file
9
crates/futures/Cargo.toml
Normal file
@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.2.15"
|
||||
authors = ["The wasm-bindgen Developers"]
|
||||
|
||||
[dependencies]
|
||||
futures = "0.1.20"
|
||||
js-sys = { path = "../js-sys", version = '0.2.0' }
|
||||
wasm-bindgen = { path = "../..", version = '0.2.15' }
|
12
crates/futures/README.md
Normal file
12
crates/futures/README.md
Normal file
@ -0,0 +1,12 @@
|
||||
# wasm-bindgen-futures
|
||||
|
||||
[Documention][documentation]
|
||||
|
||||
This is an experimental crate (aka just written) which is targeted at bridging
|
||||
a Rust `Future` and a JS `Promise`. Internally it contains two conversions, one
|
||||
from a JS `Promise` to a Rust `Future`, and another from a Rust `Future` to a
|
||||
JS `Promise`.
|
||||
|
||||
See the [documentation] for more info.
|
||||
|
||||
[documentation]: https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen_futures/
|
278
crates/futures/src/lib.rs
Normal file
278
crates/futures/src/lib.rs
Normal file
@ -0,0 +1,278 @@
|
||||
//! A JS `Promise` to Rust `Future` bridge
|
||||
//!
|
||||
//! This crate provides a bridge for working with JS `Promise` types as a Rust
|
||||
//! `Future`, and similarly contains utilities to turn a rust `Future` into a JS
|
||||
//! `Promise`. This can be useful when working with asynchronous or otherwise
|
||||
//! blocking work in Rust (wasm), and provides the ability to interoperate with
|
||||
//! JS events and JS I/O primitives.
|
||||
//!
|
||||
//! There are two main interfaces in this crate currently:
|
||||
//!
|
||||
//! * `JsFuture` - a type that is constructed with a `Promise` and can then be
|
||||
//! used as a `Future<Item = JsValue, Error = JsValue>`. This Rust future will
|
||||
//! resolve or reject with the value coming out of the `Promise`.
|
||||
//! * `future_to_promise` - converts a Rust `Future<Item = JsValue, Error =
|
||||
//! JsValue>` into a JS `Promise`. The future's result will translate to
|
||||
//! either a rejected or resolved `Promise` in JS.
|
||||
//!
|
||||
//! These two types should provide enough of a bridge to interoperate the two
|
||||
//! systems and make sure that Rust/JS can work together with asynchronous and
|
||||
//! I/O work.
|
||||
|
||||
#![deny(missing_docs)]
|
||||
#![feature(use_extern_macros)]
|
||||
|
||||
extern crate futures;
|
||||
extern crate wasm_bindgen;
|
||||
extern crate js_sys;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::cell::{RefCell, Cell};
|
||||
|
||||
use futures::executor::{self, Spawn, Notify};
|
||||
use futures::prelude::*;
|
||||
use futures::sync::oneshot;
|
||||
use js_sys::{Function, Promise};
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
/// A Rust `Future` backed by a JS `Promise`.
|
||||
///
|
||||
/// This type is constructed with a JS `Promise` object and translates it to a
|
||||
/// Rust `Future`. This type implements the `Future` trait from the `futures`
|
||||
/// crate and will either succeed or fail depending on what happens with the JS
|
||||
/// `Promise`.
|
||||
///
|
||||
/// Currently this type is constructed with `JsFuture::from`.
|
||||
pub struct JsFuture {
|
||||
resolved: oneshot::Receiver<JsValue>,
|
||||
rejected: oneshot::Receiver<JsValue>,
|
||||
callbacks: Option<(Closure<FnMut(JsValue)>, Closure<FnMut(JsValue)>)>,
|
||||
}
|
||||
|
||||
impl From<Promise> for JsFuture {
|
||||
fn from(js: Promise) -> JsFuture {
|
||||
// Use the `then` method to schedule two callbacks, one for the
|
||||
// resolved value and one for the rejected value. These two callbacks
|
||||
// will be connected to oneshot channels which feed back into our
|
||||
// future.
|
||||
//
|
||||
// This may not be the speediest option today but it should work!
|
||||
let (tx1, rx1) = oneshot::channel();
|
||||
let (tx2, rx2) = oneshot::channel();
|
||||
let mut tx1 = Some(tx1);
|
||||
let resolve = Closure::wrap(Box::new(move |val| {
|
||||
drop(tx1.take().unwrap().send(val));
|
||||
}) as Box<FnMut(_)>);
|
||||
let mut tx2 = Some(tx2);
|
||||
let reject = Closure::wrap(Box::new(move |val| {
|
||||
drop(tx2.take().unwrap().send(val));
|
||||
}) as Box<FnMut(_)>);
|
||||
|
||||
js.then2(&resolve, &reject);
|
||||
|
||||
JsFuture {
|
||||
resolved: rx1,
|
||||
rejected: rx2,
|
||||
callbacks: Some((resolve, reject)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for JsFuture {
|
||||
type Item = JsValue;
|
||||
type Error = JsValue;
|
||||
|
||||
fn poll(&mut self) -> Poll<JsValue, JsValue> {
|
||||
// Test if either our resolved or rejected side is finished yet. Note
|
||||
// that they will return errors if they're disconnected which can't
|
||||
// happen until we drop the `callbacks` field, which doesn't happen
|
||||
// till we're done, so we dont need to handle that.
|
||||
if let Ok(Async::Ready(val)) = self.resolved.poll() {
|
||||
drop(self.callbacks.take());
|
||||
return Ok(val.into())
|
||||
}
|
||||
if let Ok(Async::Ready(val)) = self.rejected.poll() {
|
||||
drop(self.callbacks.take());
|
||||
return Err(val)
|
||||
}
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts a Rust `Future` into a JS `Promise`.
|
||||
///
|
||||
/// This function will take any future in Rust and schedule it to be executed,
|
||||
/// returning a JS `Promise` which can then be passed back to JS to get plumbed
|
||||
/// into the rest of a system.
|
||||
///
|
||||
/// The `future` provided must adhere to `'static` because it'll be scheduled
|
||||
/// to run in the background and cannot contain any stack references. The
|
||||
/// returned `Promise` will be resolved or rejected when the future completes,
|
||||
/// depending on whether it finishes with `Ok` or `Err`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Note that in wasm panics are currently translated to aborts, but "abort" in
|
||||
/// this case means that a JS exception is thrown. The wasm module is still
|
||||
/// usable (likely erroneously) after Rust panics.
|
||||
///
|
||||
/// If the `future` provided panics then the returned `Promise` **will not
|
||||
/// resolve**. Instead it will be a leaked promise. This is an unfortunate
|
||||
/// limitation of wasm currently that's hoped to be fixed one day!
|
||||
pub fn future_to_promise<F>(future: F) -> Promise
|
||||
where F: Future<Item = JsValue, Error = JsValue> + 'static,
|
||||
{
|
||||
_future_to_promise(Box::new(future))
|
||||
}
|
||||
|
||||
// Implementation of actually transforming a future into a JS `Promise`.
|
||||
//
|
||||
// The only primitive we have to work with here is `Promise::new`, which gives
|
||||
// us two callbacks that we can use to either reject or resolve the promise.
|
||||
// It's our job to ensure that one of those callbacks is called at the
|
||||
// appropriate time.
|
||||
//
|
||||
// Now we know that JS (in general) can't block and is largely
|
||||
// notification/callback driven. That means that our future must either have
|
||||
// synchronous computational work to do, or it's "scheduled a notification" to
|
||||
// happen. These notifications are likely callbacks to get executed when things
|
||||
// finish (like a different promise or something like `setTimeout`). The general
|
||||
// idea here is thus to do as much synchronous work as we can and then otherwise
|
||||
// translate notifications of a future's task into "let's poll the future!"
|
||||
//
|
||||
// This isn't necessarily the greatest future executor in the world, but it
|
||||
// should get the job done for now hopefully.
|
||||
fn _future_to_promise(future: Box<Future<Item = JsValue, Error = JsValue>>) -> Promise {
|
||||
let mut future = Some(executor::spawn(future));
|
||||
return Promise::new(&mut |resolve, reject| {
|
||||
Package::poll(&Arc::new(Package {
|
||||
spawn: RefCell::new(future.take().unwrap()),
|
||||
resolve,
|
||||
reject,
|
||||
notified: Cell::new(State::Notified),
|
||||
}));
|
||||
});
|
||||
|
||||
struct Package {
|
||||
// Our "spawned future". This'll have everything we need to poll the
|
||||
// future and continue to move it forward.
|
||||
spawn: RefCell<Spawn<Box<Future<Item = JsValue, Error = JsValue>>>>,
|
||||
|
||||
// The current state of this future, expressed in an enum below. This
|
||||
// indicates whether we're currently polling the future, received a
|
||||
// notification and need to keep polling, or if we're waiting for a
|
||||
// notification to come in (and no one is polling).
|
||||
notified: Cell<State>,
|
||||
|
||||
// Our two callbacks connected to the `Promise` that we returned to JS.
|
||||
// We'll be invoking one of these at the end.
|
||||
resolve: Function,
|
||||
reject: Function,
|
||||
}
|
||||
|
||||
// The possible states our `Package` (future) can be in, tracked internally
|
||||
// and used to guide what happens when polling a future.
|
||||
enum State {
|
||||
// This future is currently and actively being polled. Attempting to
|
||||
// access the future will result in a runtime panic and is considered a
|
||||
// bug.
|
||||
Polling,
|
||||
|
||||
// This future has been notified, while it was being polled. This marker
|
||||
// is used in the `Notify` implementation below, and indicates that a
|
||||
// notification was received that the future is ready to make progress.
|
||||
// If seen, however, it probably means that the future is also currently
|
||||
// being polled.
|
||||
Notified,
|
||||
|
||||
// The future is blocked, waiting for something to happen. Stored here
|
||||
// is a self-reference to the future itself so we can pull it out in
|
||||
// `Notify` and continue polling.
|
||||
//
|
||||
// Note that the self-reference here is an Arc-cycle that will leak
|
||||
// memory unless the future completes, but currently that should be ok
|
||||
// as we'll have to stick around anyway while the future is executing!
|
||||
//
|
||||
// This state is removed as soon as a notification comes in, so the leak
|
||||
// should only be "temporary"
|
||||
Waiting(Arc<Package>),
|
||||
}
|
||||
|
||||
// No shared memory right now, wasm is single threaded, no need to worry
|
||||
// about this!
|
||||
unsafe impl Send for Package {}
|
||||
unsafe impl Sync for Package {}
|
||||
|
||||
impl Package {
|
||||
// Move the future contained in `me` as far forward as we can. This will
|
||||
// do as much synchronous work as possible to complete the future,
|
||||
// ensuring that when it blocks we're scheduled to get notified via some
|
||||
// callback somewhere at some point (vague, right?)
|
||||
//
|
||||
// TODO: this probably shouldn't do as much synchronous work as possible
|
||||
// as it can starve other computations. Rather it should instead
|
||||
// yield every so often with something like `setTimeout` with the
|
||||
// timeout set to zero.
|
||||
fn poll(me: &Arc<Package>) {
|
||||
loop {
|
||||
match me.notified.replace(State::Polling) {
|
||||
// We received a notification while previously polling, or
|
||||
// this is the initial poll. We've got work to do below!
|
||||
State::Notified => {}
|
||||
|
||||
// We've gone through this loop once and no notification was
|
||||
// received while we were executing work. That means we got
|
||||
// `NotReady` below and we're scheduled to receive a
|
||||
// notification. Block ourselves and wait for later.
|
||||
//
|
||||
// When the notification comes in it'll notify our task, see
|
||||
// our `Waiting` state, and resume the polling process
|
||||
State::Polling => {
|
||||
me.notified.set(State::Waiting(me.clone()));
|
||||
break
|
||||
}
|
||||
|
||||
State::Waiting(_) => panic!("shouldn't see waiting state!"),
|
||||
}
|
||||
|
||||
let (val, f) = match me.spawn.borrow_mut().poll_future_notify(me, 0) {
|
||||
// If the future is ready, immediately call the
|
||||
// resolve/reject callback and then return as we're done.
|
||||
Ok(Async::Ready(value)) => (value, &me.resolve),
|
||||
Err(value) => (value, &me.reject),
|
||||
|
||||
// Otherwise keep going in our loop, if we weren't notified
|
||||
// we'll break out and start waiting.
|
||||
Ok(Async::NotReady) => continue,
|
||||
};
|
||||
|
||||
drop(f.call1(&JsValue::undefined(), &val));
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Notify for Package {
|
||||
fn notify(&self, _id: usize) {
|
||||
match self.notified.replace(State::Notified) {
|
||||
// we need to schedule polling to resume, so we do so
|
||||
// immediately for now
|
||||
State::Waiting(me) => Package::poll(&me),
|
||||
|
||||
// we were already notified, and were just notified again;
|
||||
// having now coalesced the notifications we return as it's
|
||||
// still someone else's job to process this
|
||||
State::Notified => {}
|
||||
|
||||
// the future was previously being polled, and we've just
|
||||
// switched it to the "you're notified" state. We don't have
|
||||
// access to the future as it's being polled, so the future
|
||||
// polling process later sees this notification and will
|
||||
// continue polling. For us, though, there's nothing else to do,
|
||||
// so we bail out.
|
||||
// later see
|
||||
State::Polling => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -740,6 +740,34 @@ extern "C" {
|
||||
#[wasm_bindgen(method, catch)]
|
||||
pub fn apply(this: &Function, context: &JsValue, args: &Array) -> Result<JsValue, JsValue>;
|
||||
|
||||
/// The `call()` method calls a function with a given this value and
|
||||
/// arguments provided individually.
|
||||
///
|
||||
/// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call
|
||||
#[wasm_bindgen(method, catch, js_name = call)]
|
||||
pub fn call0(this: &Function, context: &JsValue) -> Result<JsValue, JsValue>;
|
||||
|
||||
/// The `call()` method calls a function with a given this value and
|
||||
/// arguments provided individually.
|
||||
///
|
||||
/// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call
|
||||
#[wasm_bindgen(method, catch, js_name = call)]
|
||||
pub fn call1(this: &Function, context: &JsValue, arg1: &JsValue) -> Result<JsValue, JsValue>;
|
||||
|
||||
/// The `call()` method calls a function with a given this value and
|
||||
/// arguments provided individually.
|
||||
///
|
||||
/// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call
|
||||
#[wasm_bindgen(method, catch, js_name = call)]
|
||||
pub fn call2(this: &Function, context: &JsValue, arg1: &JsValue, arg2: &JsValue) -> Result<JsValue, JsValue>;
|
||||
|
||||
/// The `call()` method calls a function with a given this value and
|
||||
/// arguments provided individually.
|
||||
///
|
||||
/// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call
|
||||
#[wasm_bindgen(method, catch, js_name = call)]
|
||||
pub fn call3(this: &Function, context: &JsValue, arg1: &JsValue, arg2: &JsValue, arg3: &JsValue) -> Result<JsValue, JsValue>;
|
||||
|
||||
/// The bind() method creates a new function that, when called, has its this keyword set to the provided value,
|
||||
/// with a given sequence of arguments preceding any provided when the new function is called.
|
||||
///
|
||||
@ -3009,3 +3037,101 @@ extern "C" {
|
||||
#[wasm_bindgen(static_method_of = Intl, js_name = getCanonicalLocales)]
|
||||
pub fn get_canonical_locales(s: &JsValue) -> Array;
|
||||
}
|
||||
|
||||
// Promise
|
||||
#[wasm_bindgen]
|
||||
extern {
|
||||
/// The `Promise` object represents the eventual completion (or failure) of
|
||||
/// an asynchronous operation, and its resulting value.
|
||||
///
|
||||
/// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
|
||||
pub type Promise;
|
||||
|
||||
/// Creates a new `Promise` with the provided executor `cb`
|
||||
///
|
||||
/// The `cb` is a function that is passed with the arguments `resolve` and
|
||||
/// `reject`. The `cb` function is executed immediately by the `Promise`
|
||||
/// implementation, passing `resolve` and `reject` functions (the executor
|
||||
/// is called before the `Promise` constructor even returns the created
|
||||
/// object). The `resolve` and `reject` functions, when called, resolve or
|
||||
/// reject the promise, respectively. The executor normally initiates
|
||||
/// some asynchronous work, and then, once that completes, either calls
|
||||
/// the `resolve` function to resolve the promise or else rejects it if an
|
||||
/// error occurred.
|
||||
///
|
||||
/// If an error is thrown in the executor function, the promise is rejected.
|
||||
/// The return value of the executor is ignored.
|
||||
///
|
||||
/// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
|
||||
#[wasm_bindgen(constructor)]
|
||||
pub fn new(cb: &mut FnMut(Function, Function)) -> Promise;
|
||||
|
||||
/// The `Promise.all(iterable)` method returns a single `Promise` that
|
||||
/// resolves when all of the promises in the iterable argument have resolved
|
||||
/// or when the iterable argument contains no promises. It rejects with the
|
||||
/// reason of the first promise that rejects.
|
||||
///
|
||||
/// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
|
||||
#[wasm_bindgen(static_method_of = Promise)]
|
||||
pub fn all(obj: JsValue) -> Promise;
|
||||
|
||||
/// The `Promise.race(iterable)` method returns a promise that resolves or
|
||||
/// rejects as soon as one of the promises in the iterable resolves or
|
||||
/// rejects, with the value or reason from that promise.
|
||||
///
|
||||
/// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race
|
||||
#[wasm_bindgen(static_method_of = Promise)]
|
||||
pub fn race(obj: JsValue) -> Promise;
|
||||
|
||||
/// The `Promise.reject(reason)` method returns a `Promise` object that is
|
||||
/// rejected with the given reason.
|
||||
///
|
||||
/// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/reject
|
||||
#[wasm_bindgen(static_method_of = Promise)]
|
||||
pub fn reject(obj: JsValue) -> Promise;
|
||||
|
||||
/// The `Promise.resolve(value)` method returns a `Promise` object that is
|
||||
/// resolved with the given value. If the value is a promise, that promise
|
||||
/// is returned; if the value is a thenable (i.e. has a "then" method), the
|
||||
/// returned promise will "follow" that thenable, adopting its eventual
|
||||
/// state; otherwise the returned promise will be fulfilled with the value.
|
||||
///
|
||||
/// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/resolve
|
||||
#[wasm_bindgen(static_method_of = Promise)]
|
||||
pub fn resolve(obj: JsValue) -> Promise;
|
||||
|
||||
/// The `catch()` method returns a `Promise` and deals with rejected cases
|
||||
/// only. It behaves the same as calling `Promise.prototype.then(undefined,
|
||||
/// onRejected)` (in fact, calling `obj.catch(onRejected)` internally calls
|
||||
/// `obj.then(undefined, onRejected)`).
|
||||
///
|
||||
/// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch
|
||||
#[wasm_bindgen(method)]
|
||||
pub fn catch(this: &Promise, cb: &Closure<FnMut(JsValue)>) -> Promise;
|
||||
|
||||
/// The `then()` method returns a `Promise`. It takes up to two arguments:
|
||||
/// callback functions for the success and failure cases of the `Promise`.
|
||||
///
|
||||
/// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then
|
||||
#[wasm_bindgen(method)]
|
||||
pub fn then(this: &Promise, cb: &Closure<FnMut(JsValue)>) -> Promise;
|
||||
|
||||
/// Same as `then`, only with both arguments provided.
|
||||
#[wasm_bindgen(method, js_name = then)]
|
||||
pub fn then2(this: &Promise,
|
||||
resolve: &Closure<FnMut(JsValue)>,
|
||||
reject: &Closure<FnMut(JsValue)>) -> Promise;
|
||||
|
||||
/// The `finally()` method returns a `Promise`. When the promise is settled,
|
||||
/// whether fulfilled or rejected, the specified callback function is
|
||||
/// executed. This provides a way for code that must be executed once the
|
||||
/// `Promise` has been dealt with to be run whether the promise was
|
||||
/// fulfilled successfully or rejected.
|
||||
///
|
||||
/// This lets you avoid duplicating code in both the promise's `then()` and
|
||||
/// `catch()` handlers.
|
||||
///
|
||||
/// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/finally
|
||||
#[wasm_bindgen(method)]
|
||||
pub fn finally(this: &Promise, cb: &Closure<FnMut()>) -> Promise;
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
use wasm_bindgen::JsValue;
|
||||
use wasm_bindgen_test::*;
|
||||
use js_sys::*;
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
pub const SCHEMA_VERSION: &str = "6";
|
||||
pub const SCHEMA_VERSION: &str = "7";
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ProgramOnlySchema {
|
||||
|
@ -16,8 +16,18 @@ pub fn wasm_bindgen_test(
|
||||
attr: proc_macro::TokenStream,
|
||||
body: proc_macro::TokenStream,
|
||||
) -> proc_macro::TokenStream {
|
||||
if attr.into_iter().next().is_some() {
|
||||
panic!("this attribute currently takes no arguments");
|
||||
let mut attr = attr.into_iter();
|
||||
let mut async = false;
|
||||
while let Some(token) = attr.next() {
|
||||
match &token {
|
||||
proc_macro::TokenTree::Ident(i) if i.to_string() == "async" => async = true,
|
||||
_ => panic!("malformed `#[wasm_bindgen_test]` attribute"),
|
||||
}
|
||||
match &attr.next() {
|
||||
Some(proc_macro::TokenTree::Punct(op)) if op.as_char() == ',' => {}
|
||||
Some(_) => panic!("malformed `#[wasm_bindgen_test]` attribute"),
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
|
||||
let mut body = TokenStream::from(body).into_iter();
|
||||
@ -32,6 +42,12 @@ pub fn wasm_bindgen_test(
|
||||
|
||||
let mut tokens = Vec::<TokenTree>::new();
|
||||
|
||||
let test_body = if async {
|
||||
quote! { cx.execute_async(test_name, #ident); }
|
||||
} else {
|
||||
quote! { cx.execute_sync(test_name, #ident); }
|
||||
};
|
||||
|
||||
// We generate a `#[no_mangle]` with a known prefix so the test harness can
|
||||
// later slurp up all of these functions and pass them as arguments to the
|
||||
// main test harness. This is the entry point for all tests.
|
||||
@ -41,7 +57,9 @@ pub fn wasm_bindgen_test(
|
||||
#[no_mangle]
|
||||
pub extern fn #name(cx: *const ::wasm_bindgen_test::__rt::Context) {
|
||||
unsafe {
|
||||
(*cx).execute(concat!(module_path!(), "::", stringify!(#ident)), #ident);
|
||||
let cx = &*cx;
|
||||
let test_name = concat!(module_path!(), "::", stringify!(#ident));
|
||||
#test_body
|
||||
}
|
||||
}
|
||||
}).into_iter());
|
||||
|
@ -7,10 +7,13 @@ license = "MIT/Apache-2.0"
|
||||
repository = "https://github.com/rustwasm/wasm-bindgen"
|
||||
|
||||
[dependencies]
|
||||
wasm-bindgen-test-macro = { path = '../test-macro', version = '=0.2.15' }
|
||||
wasm-bindgen = { path = '../..', version = '0.2.15' }
|
||||
js-sys = { path = '../js-sys', version = '0.2.0' }
|
||||
console_error_panic_hook = '0.1'
|
||||
futures = "0.1"
|
||||
js-sys = { path = '../js-sys', version = '0.2.0' }
|
||||
scoped-tls = "0.1"
|
||||
wasm-bindgen = { path = '../..', version = '0.2.15' }
|
||||
wasm-bindgen-futures = { path = '../futures', version = '0.2.15' }
|
||||
wasm-bindgen-test-macro = { path = '../test-macro', version = '=0.2.15' }
|
||||
|
||||
[lib]
|
||||
test = false
|
||||
|
@ -100,6 +100,24 @@ ton of documentation just yet, but a taste of how it works is:
|
||||
And that's it! You've now got a test harness executing native wasm code inside
|
||||
of Node.js and you can use `cargo test` as you normally would for workflows.
|
||||
|
||||
## Asynchronous Tests
|
||||
|
||||
Not all tests can execute immediately and some may need to do "blocking" work
|
||||
like fetching resources and/or other bits and pieces. To accommodate this
|
||||
asynchronous tests are also supported through the `futures` crate:
|
||||
|
||||
```rust
|
||||
#[wasm_bindgen_test(async)]
|
||||
fn my_test() -> impl Future<Item = (), Error = JsValue> {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
The test will pass if the future resolves without panicking or returning an
|
||||
error, and otherwise the test will fail.
|
||||
|
||||
This support is currently powered by the `wasm-bindgen-futures` crate.
|
||||
|
||||
## Components
|
||||
|
||||
The test harness is made of three separate components, but you typically don't
|
||||
@ -165,8 +183,5 @@ Things that'd be awesome to support in the future:
|
||||
|
||||
* Arguments to `wasm-bindgen-test-runner` which are the same as `wasm-bindgen`,
|
||||
for example `--debug` to affect the generated output.
|
||||
* Built-in webserver to `wasm-bindgen-test-runner`. This would be handy for
|
||||
running tests in a browser while developing.
|
||||
* Headless browser testing to allow for testing in a browser on CI.
|
||||
* Running each test in its own wasm instance to avoid poisoning the environment
|
||||
on panic
|
||||
|
16
crates/test/sample/Cargo.toml
Normal file
16
crates/test/sample/Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "sample"
|
||||
version = "0.1.0"
|
||||
authors = ["The wasm-bindgen Authors"]
|
||||
|
||||
[lib]
|
||||
test = false
|
||||
|
||||
[dependencies]
|
||||
futures = "0.1"
|
||||
js-sys = { path = '../../js-sys' }
|
||||
wasm-bindgen = { path = '../../..' }
|
||||
wasm-bindgen-futures = { path = '../../futures' }
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = { path = '..' }
|
4
crates/test/sample/README.md
Normal file
4
crates/test/sample/README.md
Normal file
@ -0,0 +1,4 @@
|
||||
# Sample test crate
|
||||
|
||||
This is a dummy crate used to test changes to the `wasm-bindgen-test` crate,
|
||||
this'll never be published nor tested on CI, it's intended for human inspection.
|
64
crates/test/sample/src/lib.rs
Normal file
64
crates/test/sample/src/lib.rs
Normal file
@ -0,0 +1,64 @@
|
||||
#![feature(use_extern_macros)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate futures;
|
||||
extern crate js_sys;
|
||||
extern crate wasm_bindgen;
|
||||
extern crate wasm_bindgen_futures;
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use futures::prelude::*;
|
||||
use js_sys::Promise;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
|
||||
pub struct Timeout {
|
||||
id: u32,
|
||||
inner: JsFuture,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern {
|
||||
#[wasm_bindgen(js_name = setTimeout)]
|
||||
fn set_timeout(closure: JsValue, millis: f64) -> u32;
|
||||
|
||||
#[wasm_bindgen(js_name = clearTimeout)]
|
||||
fn clear_timeout(id: u32);
|
||||
}
|
||||
|
||||
impl Timeout {
|
||||
pub fn new(dur: Duration) -> Timeout {
|
||||
let millis = dur.as_secs()
|
||||
.checked_mul(1000)
|
||||
.unwrap()
|
||||
.checked_add(dur.subsec_millis() as u64)
|
||||
.unwrap() as f64; // TODO: checked cast
|
||||
|
||||
let mut id = None;
|
||||
let promise = Promise::new(&mut |resolve, _reject| {
|
||||
id = Some(set_timeout(resolve.into(), millis));
|
||||
});
|
||||
|
||||
Timeout {
|
||||
id: id.unwrap(),
|
||||
inner: JsFuture::from(promise),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for Timeout {
|
||||
type Item = ();
|
||||
type Error = JsValue;
|
||||
|
||||
fn poll(&mut self) -> Poll<(), JsValue> {
|
||||
let _obj = try_ready!(self.inner.poll());
|
||||
Ok(().into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Timeout {
|
||||
fn drop(&mut self) {
|
||||
clear_timeout(self.id);
|
||||
}
|
||||
}
|
10
crates/test/sample/tests/browser.rs
Normal file
10
crates/test/sample/tests/browser.rs
Normal file
@ -0,0 +1,10 @@
|
||||
#![feature(use_extern_macros)]
|
||||
|
||||
extern crate futures;
|
||||
extern crate sample;
|
||||
extern crate wasm_bindgen;
|
||||
extern crate wasm_bindgen_test;
|
||||
|
||||
wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
|
||||
|
||||
pub mod common;
|
45
crates/test/sample/tests/common/mod.rs
Normal file
45
crates/test/sample/tests/common/mod.rs
Normal file
@ -0,0 +1,45 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use futures::prelude::*;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_test::*;
|
||||
use sample::Timeout;
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn pass() {
|
||||
console_log!("DO NOT SEE ME");
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test(async)]
|
||||
fn pass_after_2s() -> impl Future<Item = (), Error = JsValue> {
|
||||
console_log!("immediate log");
|
||||
Timeout::new(Duration::new(1, 0))
|
||||
.and_then(|()| {
|
||||
console_log!("log after 1s");
|
||||
Timeout::new(Duration::new(1, 0)).map(|()| {
|
||||
console_log!("log at end");
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn fail() {
|
||||
console_log!("helpful messsage, please see me");
|
||||
panic!("this is a failing test");
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test(async)]
|
||||
fn fail_after_3s() -> impl Future<Item = (), Error = JsValue> {
|
||||
console_log!("immediate log");
|
||||
Timeout::new(Duration::new(1, 0))
|
||||
.and_then(|()| {
|
||||
console_log!("log after 1s");
|
||||
Timeout::new(Duration::new(1, 0)).and_then(|()| {
|
||||
console_log!("log after 2s");
|
||||
Timeout::new(Duration::new(1, 0)).map(|()| {
|
||||
panic!("end");
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
8
crates/test/sample/tests/node.rs
Normal file
8
crates/test/sample/tests/node.rs
Normal file
@ -0,0 +1,8 @@
|
||||
#![feature(use_extern_macros)]
|
||||
|
||||
extern crate futures;
|
||||
extern crate sample;
|
||||
extern crate wasm_bindgen;
|
||||
extern crate wasm_bindgen_test;
|
||||
|
||||
pub mod common;
|
@ -5,10 +5,14 @@
|
||||
#![feature(use_extern_macros)]
|
||||
#![deny(missing_docs)]
|
||||
|
||||
extern crate wasm_bindgen_test_macro;
|
||||
extern crate wasm_bindgen;
|
||||
extern crate js_sys;
|
||||
extern crate console_error_panic_hook;
|
||||
extern crate futures;
|
||||
extern crate js_sys;
|
||||
#[macro_use]
|
||||
extern crate scoped_tls;
|
||||
extern crate wasm_bindgen;
|
||||
extern crate wasm_bindgen_futures;
|
||||
extern crate wasm_bindgen_test_macro;
|
||||
|
||||
pub use wasm_bindgen_test_macro::wasm_bindgen_test;
|
||||
|
||||
|
@ -52,32 +52,14 @@ impl super::Formatter for Browser {
|
||||
self.pre.set_inner_html(&html);
|
||||
}
|
||||
|
||||
fn log_start(&self, name: &str) {
|
||||
let data = format!("test {} ... ", name);
|
||||
let mut html = self.pre.inner_html();
|
||||
html.push_str(&data);
|
||||
self.pre.set_inner_html(&html);
|
||||
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 log_success(&self) {
|
||||
let mut html = self.pre.inner_html();
|
||||
html.push_str("ok\n");
|
||||
self.pre.set_inner_html(&html);
|
||||
}
|
||||
|
||||
fn log_ignored(&self) {
|
||||
let mut html = self.pre.inner_html();
|
||||
html.push_str("ignored\n");
|
||||
self.pre.set_inner_html(&html);
|
||||
}
|
||||
|
||||
fn log_failure(&self, err: JsValue) -> String {
|
||||
let mut html = self.pre.inner_html();
|
||||
html.push_str("FAIL\n");
|
||||
self.pre.set_inner_html(&html);
|
||||
|
||||
fn stringify_error(&self, err: &JsValue) -> String {
|
||||
// TODO: this should be a checked cast to `Error`
|
||||
let err = Error::from(err);
|
||||
let err = Error::from(err.clone());
|
||||
let name = String::from(err.name());
|
||||
let message = String::from(err.message());
|
||||
let err = BrowserError::from(JsValue::from(err));
|
||||
|
@ -1,7 +1,7 @@
|
||||
//! Runtime detection of whether we're in node.js or a browser.
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
use js_sys::{Array, Function};
|
||||
use js_sys::Function;
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern {
|
||||
@ -54,7 +54,7 @@ pub fn is_browser() -> bool {
|
||||
//
|
||||
// Whew!
|
||||
let this = Function::new_no_args("return this")
|
||||
.apply(&JsValue::undefined(), &Array::new())
|
||||
.call0(&JsValue::undefined())
|
||||
.unwrap();
|
||||
assert!(this != JsValue::undefined());
|
||||
This::from(this).self_() != JsValue::undefined()
|
||||
|
@ -61,21 +61,21 @@
|
||||
// This is used for test filters today.
|
||||
//
|
||||
// * The `Context::run` function is called. Again, the generated JS has gathered
|
||||
// all wasm tests to be executed into a list, and it's passed in here. Again,
|
||||
// it's very important that these functions are JS values, not function
|
||||
// pointers in Rust.
|
||||
// all wasm tests to be executed into a list, and it's passed in here.
|
||||
//
|
||||
// * Next, `Context::run` will proceed to execute all of the functions. When a
|
||||
// function is executed we're invoking a JS function, which means we're
|
||||
// allowed to catch exceptions. This is how we handle failing tests without
|
||||
// aborting the entire process.
|
||||
// * Next, `Context::run` returns a `Promise` representing the eventual
|
||||
// execution of all the tests. The Rust `Future` that's returned will work
|
||||
// with the tests to ensure that everything's executed by the time the
|
||||
// `Promise` resolves.
|
||||
//
|
||||
// * When a test executes, it's executing an entry point generated by
|
||||
// `#[wasm_bindgen_test]`. The test informs the `Context` of its name and
|
||||
// other metadata, and then `Context::execute` actually invokes the tests
|
||||
// itself (which currently is a unit function).
|
||||
// other metadata, and then `Context::execute_*` function creates a future
|
||||
// representing the execution of the test. This feeds back into the future
|
||||
// returned by `Context::run` to finish the test suite.
|
||||
//
|
||||
// * Finally, after all tests are run, the `Context` prints out all the results.
|
||||
// * Finally, after all tests are run, the `Context`'s future resolves, prints
|
||||
// out all the result, and finishes in JS.
|
||||
//
|
||||
// ## Other various notes
|
||||
//
|
||||
@ -87,13 +87,25 @@
|
||||
// Overall this is all somewhat in flux as it's pretty new, and feedback is
|
||||
// always of course welcome!
|
||||
|
||||
|
||||
use std::cell::{RefCell, Cell};
|
||||
use std::fmt;
|
||||
use std::mem;
|
||||
use std::rc::Rc;
|
||||
|
||||
use console_error_panic_hook;
|
||||
use js_sys::{Array, Function};
|
||||
use futures::future;
|
||||
use futures::prelude::*;
|
||||
use js_sys::{Array, Function, Promise};
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::future_to_promise;
|
||||
|
||||
// Maximum number of tests to execute concurrently. Eventually this should be a
|
||||
// configuration option specified at runtime or at compile time rather than
|
||||
// baked in here.
|
||||
//
|
||||
// Currently the default is 1 because the DOM has a lot of shared state, and
|
||||
// conccurrently doing things by default would likely end up in a bad situation.
|
||||
const CONCURRENCY: usize = 1;
|
||||
|
||||
pub mod node;
|
||||
pub mod browser;
|
||||
@ -105,15 +117,15 @@ pub mod detect;
|
||||
/// drive test execution.
|
||||
#[wasm_bindgen]
|
||||
pub struct Context {
|
||||
state: Rc<State>,
|
||||
}
|
||||
|
||||
struct State {
|
||||
/// An optional filter used to restrict which tests are actually executed
|
||||
/// and which are ignored. This is passed via the `args` function which
|
||||
/// comes from the command line of `wasm-bindgen-test-runner`. Currently
|
||||
/// this is the only "CLI option"
|
||||
filter: Option<String>,
|
||||
|
||||
/// The current test that is executing. If `None` no test is executing, if
|
||||
/// `Some` it's the name of the tests.
|
||||
current_test: RefCell<Option<String>>,
|
||||
filter: RefCell<Option<String>>,
|
||||
|
||||
/// Counter of the number of tests that have succeeded.
|
||||
succeeded: Cell<usize>,
|
||||
@ -121,38 +133,59 @@ pub struct Context {
|
||||
/// Counter of the number of tests that have been ignored
|
||||
ignored: Cell<usize>,
|
||||
|
||||
/// A list of all tests which have failed. The first element of this pair is
|
||||
/// the name of the test that failed, and the second is all logging
|
||||
/// information (formatted) associated with the failure.
|
||||
failures: RefCell<Vec<(String, String)>>,
|
||||
/// A list of all tests which have failed.
|
||||
///
|
||||
/// Each test listed here is paired with a `JsValue` that represents the
|
||||
/// exception thrown which caused the test to fail.
|
||||
failures: RefCell<Vec<(Test, JsValue)>>,
|
||||
|
||||
/// Sink for `console.log` invocations when a test is running. This is
|
||||
/// filled in by the `Context::console_log` function below while a test is
|
||||
/// executing (aka while `current_test` above is `Some`).
|
||||
current_log: RefCell<String>,
|
||||
current_error: RefCell<String>,
|
||||
/// Remaining tests to execute, when empty we're just waiting on the
|
||||
/// `Running` tests to finish.
|
||||
remaining: RefCell<Vec<Test>>,
|
||||
|
||||
/// Flag set as a test executes if it was actually ignored.
|
||||
ignore_this_test: Cell<bool>,
|
||||
/// List of currently executing tests. These tests all involve some level
|
||||
/// of asynchronous work, so they're sitting on the running list.
|
||||
running: RefCell<Vec<Test>>,
|
||||
|
||||
/// How to actually format output, either node.js or browser-specific
|
||||
/// implementation.
|
||||
formatter: Box<Formatter>,
|
||||
}
|
||||
|
||||
/// Representation of one test that needs to be executed.
|
||||
///
|
||||
/// Tests are all represented as futures, and tests perform no work until their
|
||||
/// future is polled.
|
||||
struct Test {
|
||||
name: String,
|
||||
future: Box<Future<Item = (), Error = JsValue>>,
|
||||
output: Rc<RefCell<Output>>,
|
||||
}
|
||||
|
||||
/// Captured output of each test.
|
||||
#[derive(Default)]
|
||||
struct Output {
|
||||
log: String,
|
||||
error: String,
|
||||
}
|
||||
|
||||
trait Formatter {
|
||||
/// Writes a line of output, typically status information.
|
||||
fn writeln(&self, line: &str);
|
||||
fn log_start(&self, name: &str);
|
||||
fn log_success(&self);
|
||||
fn log_ignored(&self);
|
||||
fn log_failure(&self, err: JsValue) -> String;
|
||||
|
||||
/// Log the result of a test, either passing or failing.
|
||||
fn log_test(&self, name: &str, result: &Result<(), JsValue>);
|
||||
|
||||
/// Convert a thrown value into a string, using platform-specific apis
|
||||
/// perhaps to turn the error into a string.
|
||||
fn stringify_error(&self, val: &JsValue) -> String;
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern {
|
||||
#[wasm_bindgen(js_namespace = console, js_name = log)]
|
||||
#[doc(hidden)]
|
||||
pub fn console_log(s: &str);
|
||||
pub fn js_console_log(s: &str);
|
||||
|
||||
// General-purpose conversion into a `String`.
|
||||
#[wasm_bindgen(js_name = String)]
|
||||
@ -161,11 +194,10 @@ extern {
|
||||
|
||||
/// Internal implementation detail of the `console_log!` macro.
|
||||
pub fn log(args: &fmt::Arguments) {
|
||||
console_log(&args.to_string());
|
||||
js_console_log(&args.to_string());
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
|
||||
impl Context {
|
||||
/// Creates a new context ready to run tests.
|
||||
///
|
||||
@ -181,15 +213,15 @@ impl Context {
|
||||
None => Box::new(browser::Browser::new()),
|
||||
};
|
||||
Context {
|
||||
filter: None,
|
||||
current_test: RefCell::new(None),
|
||||
succeeded: Cell::new(0),
|
||||
ignored: Cell::new(0),
|
||||
failures: RefCell::new(Vec::new()),
|
||||
current_log: RefCell::new(String::new()),
|
||||
current_error: RefCell::new(String::new()),
|
||||
ignore_this_test: Cell::new(false),
|
||||
formatter,
|
||||
state: Rc::new(State {
|
||||
filter: Default::default(),
|
||||
failures: Default::default(),
|
||||
ignored: Default::default(),
|
||||
remaining: Default::default(),
|
||||
running: Default::default(),
|
||||
succeeded: Default::default(),
|
||||
formatter,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@ -204,107 +236,232 @@ impl Context {
|
||||
// argument as a test filter.
|
||||
//
|
||||
// Everything else is rejected.
|
||||
let mut filter = self.state.filter.borrow_mut();
|
||||
for arg in args {
|
||||
let arg = arg.as_string().unwrap();
|
||||
if arg.starts_with("-") {
|
||||
panic!("flag {} not supported", arg);
|
||||
} else if self.filter.is_some() {
|
||||
} else if filter.is_some() {
|
||||
panic!("more than one filter argument cannot be passed");
|
||||
}
|
||||
self.filter = Some(arg);
|
||||
*filter = Some(arg);
|
||||
}
|
||||
}
|
||||
|
||||
/// Executes a list of tests, returning whether any of them failed.
|
||||
/// Executes a list of tests, returning a promise representing their
|
||||
/// eventual completion.
|
||||
///
|
||||
/// This is the main entry point for executing tests. All the tests passed
|
||||
/// in are the JS `Function` object that was plucked off the
|
||||
/// `WebAssembly.Instance` exports list. This allows us to invoke it but
|
||||
/// still catch JS exceptions.
|
||||
pub fn run(&self, tests: Vec<JsValue>) -> bool {
|
||||
let this = JsValue::null();
|
||||
|
||||
// Each entry point has one argument, a raw pointer to this `Context`,
|
||||
// so build up that array we'll be passing all the functions.
|
||||
let args = Array::new();
|
||||
args.push(&JsValue::from(self as *const Context as u32));
|
||||
|
||||
/// `WebAssembly.Instance` exports list.
|
||||
///
|
||||
/// The promise returned resolves to either `true` if all tests passed or
|
||||
/// `false` if at least one test failed.
|
||||
pub fn run(&self, tests: Vec<JsValue>) -> Promise {
|
||||
let noun = if tests.len() == 1 { "test" } else { "tests" };
|
||||
self.formatter.writeln(&format!("running {} {}", tests.len(), noun));
|
||||
self.formatter.writeln("");
|
||||
self.state.formatter.writeln(&format!("running {} {}", tests.len(), noun));
|
||||
self.state.formatter.writeln("");
|
||||
|
||||
// Execute all our test functions through their wasm shims (unclear how
|
||||
// to pass native function pointers around here). Each test will
|
||||
// execute one of the `execute_*` tests below which will push a
|
||||
// future onto our `remaining` list, which we'll process later.
|
||||
let cx_arg = (self as *const Context as u32).into();
|
||||
for test in tests {
|
||||
self.ignore_this_test.set(false);
|
||||
|
||||
// Use `Function.apply` to catch any exceptions and otherwise invoke
|
||||
// the test.
|
||||
let test = Function::from(test);
|
||||
match test.apply(&this, &args) {
|
||||
Ok(_) => {
|
||||
if self.ignore_this_test.get() {
|
||||
self.log_ignore()
|
||||
} else {
|
||||
self.log_success()
|
||||
}
|
||||
match Function::from(test).call1(&JsValue::null(), &cx_arg) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
panic!("exception thrown while creating a test: {}",
|
||||
self.state.formatter.stringify_error(&e));
|
||||
}
|
||||
Err(e) => self.log_failure(e),
|
||||
}
|
||||
drop(self.current_test.borrow_mut().take());
|
||||
*self.current_log.borrow_mut() = String::new();
|
||||
*self.current_error.borrow_mut() = String::new();
|
||||
}
|
||||
self.log_results();
|
||||
self.failures.borrow().len() == 0
|
||||
|
||||
// Now that we've collected all our tests we wrap everything up in a
|
||||
// future to actually do all the processing, and pass it out to JS as a
|
||||
// `Promise`.
|
||||
let future = ExecuteTests(self.state.clone()).map(JsValue::from)
|
||||
.map_err(|e| match e {});
|
||||
future_to_promise(future)
|
||||
}
|
||||
}
|
||||
|
||||
scoped_thread_local!(static CURRENT_OUTPUT: RefCell<Output>);
|
||||
|
||||
/// Handler for `console.log` invocations.
|
||||
///
|
||||
/// If a test is currently running it takes the `args` array and stringifies
|
||||
/// it and appends it to the current output of the test. Otherwise it passes
|
||||
/// the arguments to the original `console.log` function, psased as
|
||||
/// `original`.
|
||||
//
|
||||
// TODO: how worth is it to actually capture the output here? Due to the nature
|
||||
// of futures/js we can't guarantee that all output is captured because JS code
|
||||
// could just be executing in the void and we wouldn't know which test to
|
||||
// attach it to. The main `test` crate in the rust repo also has issues about
|
||||
// how not all output is captured, causing some inconsistencies sometimes.
|
||||
#[wasm_bindgen]
|
||||
pub fn __wbgtest_console_log(original: &Function, args: &Array) {
|
||||
record(original, args, |output| &mut output.log)
|
||||
}
|
||||
|
||||
/// Handler for `console.error` invocations.
|
||||
///
|
||||
/// Works the same as `console_log` above.
|
||||
#[wasm_bindgen]
|
||||
pub fn __wbgtest_console_error(original: &Function, args: &Array) {
|
||||
record(original, args, |output| &mut output.error)
|
||||
}
|
||||
|
||||
fn record(orig: &Function, args: &Array, dst: impl FnOnce(&mut Output) -> &mut String) {
|
||||
if !CURRENT_OUTPUT.is_set() {
|
||||
drop(orig.apply(&JsValue::null(), args));
|
||||
return
|
||||
}
|
||||
|
||||
fn log_start(&self, test: &str) {
|
||||
let mut current_test = self.current_test.borrow_mut();
|
||||
assert!(current_test.is_none());
|
||||
*current_test = Some(test.to_string());
|
||||
self.formatter.log_start(test);
|
||||
CURRENT_OUTPUT.with(|output| {
|
||||
let mut out = output.borrow_mut();
|
||||
let dst = dst(&mut out);
|
||||
args.for_each(&mut |val, idx, _array| {
|
||||
if idx != 0 {
|
||||
dst.push_str(" ");
|
||||
}
|
||||
dst.push_str(&stringify(&val));
|
||||
});
|
||||
dst.push_str("\n");
|
||||
});
|
||||
}
|
||||
|
||||
impl Context {
|
||||
/// Entry point for a synchronous test in wasm. The `#[wasm_bindgen_test]`
|
||||
/// macro generates invocations of this method.
|
||||
pub fn execute_sync(&self, name: &str, f: impl FnOnce() + 'static) {
|
||||
self.execute(name, future::lazy(|| Ok(f())));
|
||||
}
|
||||
|
||||
fn log_success(&self) {
|
||||
self.formatter.log_success();
|
||||
self.succeeded.set(self.succeeded.get() + 1);
|
||||
/// Entry point for an asynchronous in wasm. The
|
||||
/// `#[wasm_bindgen_test(async)]` macro generates invocations of this
|
||||
/// method.
|
||||
pub fn execute_async<F>(&self, name: &str, f: impl FnOnce() -> F + 'static)
|
||||
where F: Future<Item = (), Error = JsValue> + 'static,
|
||||
{
|
||||
self.execute(name, future::lazy(f))
|
||||
}
|
||||
|
||||
fn log_ignore(&self) {
|
||||
self.formatter.log_ignored();
|
||||
self.ignored.set(self.ignored.get() + 1);
|
||||
}
|
||||
|
||||
fn log_failure(&self, err: JsValue) {
|
||||
let name = self.current_test.borrow().as_ref().unwrap().clone();
|
||||
let log = mem::replace(&mut *self.current_log.borrow_mut(), String::new());
|
||||
let error = mem::replace(&mut *self.current_error.borrow_mut(), String::new());
|
||||
let mut msg = String::new();
|
||||
if log.len() > 0 {
|
||||
msg.push_str("log output:\n");
|
||||
msg.push_str(&tab(&log));
|
||||
msg.push_str("\n");
|
||||
fn execute(
|
||||
&self,
|
||||
name: &str,
|
||||
test: impl Future<Item = (), Error = JsValue> + 'static,
|
||||
) {
|
||||
// If our test is filtered out, record that it was filtered and move
|
||||
// on, nothing to do here.
|
||||
let filter = self.state.filter.borrow();
|
||||
if let Some(filter) = &*filter {
|
||||
if !name.contains(filter) {
|
||||
let ignored = self.state.ignored.get();
|
||||
self.state.ignored.set(ignored + 1);
|
||||
return
|
||||
}
|
||||
}
|
||||
if error.len() > 0 {
|
||||
msg.push_str("error output:\n");
|
||||
msg.push_str(&tab(&error));
|
||||
msg.push_str("\n");
|
||||
|
||||
// Looks like we've got a test that needs to be executed! Push it onto
|
||||
// the list of remaining tests.
|
||||
let output = Rc::new(RefCell::new(Output::default()));
|
||||
let future = TestFuture {
|
||||
output: output.clone(),
|
||||
test,
|
||||
};
|
||||
self.state.remaining.borrow_mut().push(Test {
|
||||
name: name.to_string(),
|
||||
future: Box::new(future),
|
||||
output,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
struct ExecuteTests(Rc<State>);
|
||||
|
||||
enum Never {}
|
||||
|
||||
impl Future for ExecuteTests {
|
||||
type Item = bool;
|
||||
type Error = Never;
|
||||
|
||||
fn poll(&mut self) -> Poll<bool, Never> {
|
||||
let mut running = self.0.running.borrow_mut();
|
||||
let mut remaining = self.0.remaining.borrow_mut();
|
||||
|
||||
// First up, try to make progress on all active tests. Remove any
|
||||
// finished tests.
|
||||
for i in (0..running.len()).rev() {
|
||||
let result = match running[i].future.poll() {
|
||||
Ok(Async::Ready(_jsavl)) => Ok(()),
|
||||
Ok(Async::NotReady) => continue,
|
||||
Err(e) => Err(e),
|
||||
};
|
||||
let test = running.remove(i);
|
||||
self.0.log_test_result(test, result);
|
||||
}
|
||||
|
||||
// Next up, try to schedule as many tests as we can. Once we get a test
|
||||
// we `poll` it once to ensure we'll receive notifications. We only
|
||||
// want to schedule up to a maximum amount of work though, so this may
|
||||
// not schedule all tests.
|
||||
while running.len() < CONCURRENCY {
|
||||
let mut test = match remaining.pop() {
|
||||
Some(test) => test,
|
||||
None => break,
|
||||
};
|
||||
let result = match test.future.poll() {
|
||||
Ok(Async::Ready(())) => Ok(()),
|
||||
Ok(Async::NotReady) => {
|
||||
running.push(test);
|
||||
continue
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
};
|
||||
self.0.log_test_result(test, result);
|
||||
}
|
||||
|
||||
// Tests are still executing, we're registered to get a notification,
|
||||
// keep going.
|
||||
if running.len() != 0 {
|
||||
return Ok(Async::NotReady)
|
||||
}
|
||||
|
||||
// If there are no tests running then we must have finished everything,
|
||||
// so we shouldn't have any more remaining tests either.
|
||||
assert_eq!(remaining.len(), 0);
|
||||
|
||||
self.0.print_results();
|
||||
let all_passed = self.0.failures.borrow().len() == 0;
|
||||
Ok(Async::Ready(all_passed))
|
||||
}
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn log_test_result(&self, test: Test, result: Result<(), JsValue>) {
|
||||
// Print out information about the test passing or failing
|
||||
self.formatter.log_test(&test.name, &result);
|
||||
|
||||
// Save off the test for later processing when we print the final
|
||||
// results.
|
||||
match result {
|
||||
Ok(()) => self.succeeded.set(self.succeeded.get() + 1),
|
||||
Err(e) => self.failures.borrow_mut().push((test, e)),
|
||||
}
|
||||
msg.push_str("JS exception that was thrown:\n");
|
||||
msg.push_str(&tab(&self.formatter.log_failure(err)));
|
||||
self.failures.borrow_mut().push((name, msg));
|
||||
}
|
||||
|
||||
fn log_results(&self) {
|
||||
fn print_results(&self) {
|
||||
let failures = self.failures.borrow();
|
||||
if failures.len() > 0 {
|
||||
self.formatter.writeln("\nfailures:\n");
|
||||
for (test, logs) in failures.iter() {
|
||||
let msg = format!("---- {} output ----\n{}", test, tab(logs));
|
||||
self.formatter.writeln(&msg);
|
||||
for (test, error) in failures.iter() {
|
||||
self.print_failure(test, error);
|
||||
}
|
||||
self.formatter.writeln("failures:\n");
|
||||
for (test, _) in failures.iter() {
|
||||
self.formatter.writeln(&format!(" {}", test));
|
||||
self.formatter.writeln(&format!(" {}", test.name));
|
||||
}
|
||||
}
|
||||
self.formatter.writeln("");
|
||||
@ -320,51 +477,77 @@ impl Context {
|
||||
));
|
||||
}
|
||||
|
||||
/// Handler for `console.log` invocations.
|
||||
///
|
||||
/// If a test is currently running it takes the `args` array and stringifies
|
||||
/// it and appends it to the current output of the test. Otherwise it passes
|
||||
/// the arguments to the original `console.log` function, psased as
|
||||
/// `original`.
|
||||
pub fn console_log(&self, original: &Function, args: &Array) {
|
||||
self.log(original, args, &self.current_log)
|
||||
}
|
||||
|
||||
/// Handler for `console.error` invocations.
|
||||
///
|
||||
/// Works the same as `console_log` above.
|
||||
pub fn console_error(&self, original: &Function, args: &Array) {
|
||||
self.log(original, args, &self.current_error)
|
||||
}
|
||||
|
||||
fn log(&self, orig: &Function, args: &Array, dst: &RefCell<String>) {
|
||||
if self.current_test.borrow().is_none() {
|
||||
drop(orig.apply(&JsValue::null(), args));
|
||||
return
|
||||
fn print_failure(&self, test: &Test, error: &JsValue) {
|
||||
let mut logs = String::new();
|
||||
let output = test.output.borrow();
|
||||
if output.log.len() > 0 {
|
||||
logs.push_str("log output:\n");
|
||||
logs.push_str(&tab(&output.log));
|
||||
logs.push_str("\n");
|
||||
}
|
||||
let mut log = dst.borrow_mut();
|
||||
args.for_each(&mut |val, idx, _array| {
|
||||
if idx != 0 {
|
||||
log.push_str(" ");
|
||||
}
|
||||
log.push_str(&stringify(&val));
|
||||
});
|
||||
log.push_str("\n");
|
||||
if output.error.len() > 0 {
|
||||
logs.push_str("error output:\n");
|
||||
logs.push_str(&tab(&output.error));
|
||||
logs.push_str("\n");
|
||||
}
|
||||
logs.push_str("JS exception that was thrown:\n");
|
||||
let error_string = self.formatter.stringify_error(error);
|
||||
logs.push_str(&tab(&error_string));
|
||||
|
||||
let msg = format!("---- {} output ----\n{}", test.name, tab(&logs));
|
||||
self.formatter.writeln(&msg);
|
||||
}
|
||||
}
|
||||
|
||||
impl Context {
|
||||
/// Entry point for a test in wasm. The `#[wasm_bindgen_test]` macro
|
||||
/// generates invocations of this method.
|
||||
pub fn execute(&self, name: &str, f: impl FnOnce()) {
|
||||
self.log_start(name);
|
||||
if let Some(filter) = &self.filter {
|
||||
if !name.contains(filter) {
|
||||
self.ignore_this_test.set(true);
|
||||
return
|
||||
}
|
||||
}
|
||||
f();
|
||||
/// A wrapper future around each test
|
||||
///
|
||||
/// This future is what's actually executed for each test and is what's stored
|
||||
/// inside of a `Test`. This wrapper future performs two critical functions:
|
||||
///
|
||||
/// * First, every time when polled, it configures the `CURRENT_OUTPUT` tls
|
||||
/// variable to capture output for the current test. That way at least when
|
||||
/// we've got Rust code running we'll be able to capture output.
|
||||
///
|
||||
/// * Next, this "catches panics". Right now all wasm code is configured as
|
||||
/// panic=abort, but it's more like an exception in JS. It's pretty sketchy
|
||||
/// to actually continue executing Rust code after an "abort", but we don't
|
||||
/// have much of a choice for now.
|
||||
///
|
||||
/// Panics are caught here by using a shim function that is annotated with
|
||||
/// `catch` so we can capture JS exceptions (which Rust panics become). This
|
||||
/// way if any Rust code along the execution of a test panics we'll hopefully
|
||||
/// capture it.
|
||||
///
|
||||
/// Note that both of the above aspects of this future are really just best
|
||||
/// effort. This is all a bit of a hack right now when it comes down to it and
|
||||
/// it definitely won't work in some situations. Hopefully as those situations
|
||||
/// arise though we can handle them!
|
||||
///
|
||||
/// The good news is that everything should work flawlessly in the case where
|
||||
/// tests have no output and execute successfully. And everyone always writes
|
||||
/// perfect code on the first try, right? *sobs*
|
||||
struct TestFuture<F> {
|
||||
output: Rc<RefCell<Output>>,
|
||||
test: F,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern {
|
||||
#[wasm_bindgen(catch)]
|
||||
fn __wbg_test_invoke(f: &mut FnMut()) -> Result<(), JsValue>;
|
||||
}
|
||||
|
||||
impl<F: Future<Error = JsValue>> Future for TestFuture<F> {
|
||||
type Item = F::Item;
|
||||
type Error = F::Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<F::Item, F::Error> {
|
||||
let test = &mut self.test;
|
||||
let mut future_output = None;
|
||||
CURRENT_OUTPUT.set(&self.output, || {
|
||||
__wbg_test_invoke(&mut || future_output = Some(test.poll()))
|
||||
})?;
|
||||
future_output.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,22 +4,13 @@
|
||||
//! for node itself.
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
use js_sys::eval;
|
||||
|
||||
/// Implementation of the `Formatter` trait for node.js
|
||||
pub struct Node {
|
||||
/// Handle to node's imported `fs` module, imported dynamically because we
|
||||
/// can't statically do it as it doesn't work in a browser.
|
||||
fs: NodeFs,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern {
|
||||
// Type declarations for the `writeSync` function in node
|
||||
type NodeFs;
|
||||
#[wasm_bindgen(method, js_name = writeSync, structural)]
|
||||
fn write_sync(this: &NodeFs, fd: i32, data: &[u8]);
|
||||
|
||||
// Not using `js_sys::Error` because node's errors specifically have a
|
||||
// `stack` attribute.
|
||||
type NodeError;
|
||||
@ -34,35 +25,22 @@ impl Node {
|
||||
if super::detect::is_browser() {
|
||||
return None
|
||||
}
|
||||
|
||||
// Use `eval` for now as a quick fix around static imports not working
|
||||
// for dual browser/node support.
|
||||
let import = eval("require(\"fs\")").unwrap();
|
||||
Some(Node { fs: import.into() })
|
||||
Some(Node { })
|
||||
}
|
||||
}
|
||||
|
||||
impl super::Formatter for Node {
|
||||
fn writeln(&self, line: &str) {
|
||||
super::console_log(line);
|
||||
super::js_console_log(line);
|
||||
}
|
||||
|
||||
fn log_start(&self, name: &str) {
|
||||
let data = format!("test {} ... ", name);
|
||||
self.fs.write_sync(2, data.as_bytes());
|
||||
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 log_success(&self) {
|
||||
self.fs.write_sync(2, b"ok\n");
|
||||
}
|
||||
|
||||
fn log_ignored(&self) {
|
||||
self.fs.write_sync(2, b"ignored\n");
|
||||
}
|
||||
|
||||
fn log_failure(&self, err: JsValue) -> String {
|
||||
self.fs.write_sync(2, b"ignored\n");
|
||||
fn stringify_error(&self, err: &JsValue) -> String {
|
||||
// TODO: should do a checked cast to `NodeError`
|
||||
NodeError::from(err).stack()
|
||||
NodeError::from(err.clone()).stack()
|
||||
}
|
||||
}
|
||||
|
@ -15,11 +15,10 @@ wasm-bindgen-webidl = { path = "../webidl", version = "=0.2.15" }
|
||||
sourcefile = "0.1"
|
||||
|
||||
[dependencies]
|
||||
wasm-bindgen = { path = "../..", version = "=0.2.15" }
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
|
||||
wasm-bindgen-test-project-builder = { path = '../test-project-builder', version = '=0.2.15' }
|
||||
wasm-bindgen = { path = "../..", version = "0.2.15" }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
|
||||
wasm-bindgen-test = { path = '../test', version = '=0.2.15' }
|
||||
futures = "0.1"
|
||||
js-sys = { path = '../js-sys', version = '0.2.0' }
|
||||
wasm-bindgen-test = { path = '../test', version = '0.2.15' }
|
||||
wasm-bindgen-futures = { path = '../futures', version = '0.2.15' }
|
||||
|
@ -1,52 +0,0 @@
|
||||
use super::websys_project;
|
||||
|
||||
#[test]
|
||||
fn event() {
|
||||
websys_project()
|
||||
.file(
|
||||
"src/lib.rs",
|
||||
r#"
|
||||
#![feature(use_extern_macros)]
|
||||
extern crate wasm_bindgen;
|
||||
use wasm_bindgen::prelude::*;
|
||||
extern crate web_sys;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn test_event(event: &web_sys::Event) {
|
||||
// These should match `new Event`.
|
||||
assert!(event.bubbles());
|
||||
assert!(event.cancelable());
|
||||
assert!(event.composed());
|
||||
|
||||
// The default behavior not initially prevented, but after
|
||||
// we call `prevent_default` it better be.
|
||||
assert!(!event.default_prevented());
|
||||
event.prevent_default();
|
||||
assert!(event.default_prevented());
|
||||
}
|
||||
"#,
|
||||
)
|
||||
.file(
|
||||
"test.js",
|
||||
r#"
|
||||
import * as assert from "assert";
|
||||
import * as wasm from "./out";
|
||||
|
||||
export async function test() {
|
||||
await new Promise(resolve => {
|
||||
window.addEventListener("test-event", e => {
|
||||
wasm.test_event(e);
|
||||
resolve();
|
||||
});
|
||||
|
||||
window.dispatchEvent(new Event("test-event", {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
composed: true,
|
||||
}));
|
||||
});
|
||||
}
|
||||
"#,
|
||||
)
|
||||
.test();
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
#![cfg(not(target_arch = "wasm32"))]
|
||||
|
||||
extern crate wasm_bindgen_test_project_builder as project_builder;
|
||||
use project_builder::{project, Project};
|
||||
|
||||
mod event;
|
||||
|
||||
fn websys_project() -> Project {
|
||||
project()
|
||||
.add_local_dependency("web-sys", env!("CARGO_MANIFEST_DIR"))
|
||||
.headless(true)
|
||||
.clone()
|
||||
}
|
10
crates/web-sys/tests/wasm/event.js
Normal file
10
crates/web-sys/tests/wasm/event.js
Normal file
@ -0,0 +1,10 @@
|
||||
export function new_event() {
|
||||
return new Promise(resolve => {
|
||||
window.addEventListener("test-event", resolve);
|
||||
window.dispatchEvent(new Event("test-event", {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
composed: true,
|
||||
}));
|
||||
});
|
||||
}
|
29
crates/web-sys/tests/wasm/event.rs
Normal file
29
crates/web-sys/tests/wasm/event.rs
Normal file
@ -0,0 +1,29 @@
|
||||
use futures::future::Future;
|
||||
use js_sys::Promise;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen_futures::JsFuture;
|
||||
use wasm_bindgen_test::*;
|
||||
use web_sys::Event;
|
||||
|
||||
#[wasm_bindgen(module = "./tests/wasm/event.js")]
|
||||
extern {
|
||||
fn new_event() -> Promise;
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test(async)]
|
||||
fn event() -> impl Future<Item = (), Error = JsValue> {
|
||||
JsFuture::from(new_event())
|
||||
.map(Event::from)
|
||||
.map(|event| {
|
||||
// These should match `new Event`.
|
||||
assert!(event.bubbles());
|
||||
assert!(event.cancelable());
|
||||
assert!(event.composed());
|
||||
|
||||
// The default behavior not initially prevented, but after
|
||||
// we call `prevent_default` it better be.
|
||||
assert!(!event.default_prevented());
|
||||
event.prevent_default();
|
||||
assert!(event.default_prevented());
|
||||
})
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
#![feature(use_extern_macros)]
|
||||
#![cfg(target_arch = "wasm32")]
|
||||
|
||||
extern crate futures;
|
||||
extern crate js_sys;
|
||||
extern crate wasm_bindgen;
|
||||
extern crate wasm_bindgen_futures;
|
||||
extern crate wasm_bindgen_test;
|
||||
extern crate web_sys;
|
||||
|
||||
@ -14,6 +16,7 @@ pub mod br_element;
|
||||
pub mod button_element;
|
||||
pub mod div_element;
|
||||
pub mod element;
|
||||
pub mod event;
|
||||
pub mod head_element;
|
||||
pub mod heading_element;
|
||||
pub mod headers;
|
||||
|
Reference in New Issue
Block a user