wasm_bindgen_test/rt/mod.rs
1//! Internal-only runtime module used for the `wasm_bindgen_test` crate.
2//!
3//! No API contained in this module will respect semver, these should all be
4//! considered private APIs.
5
6// # Architecture of `wasm_bindgen_test`
7//
8// This module can seem a bit funky, but it's intended to be the runtime support
9// of the `#[wasm_bindgen_test]` macro and be amenable to executing Wasm test
10// suites. The general idea is that for a Wasm test binary there will be a set
11// of functions tagged `#[wasm_bindgen_test]`. It's the job of the runtime
12// support to execute all of these functions, collecting and collating the
13// results.
14//
15// This runtime support works in tandem with the `wasm-bindgen-test-runner`
16// binary as part of the `wasm-bindgen-cli` package.
17//
18// ## High Level Overview
19//
20// Here's a rough and (semi) high level overview of what happens when this crate
21// runs.
22//
23// * First, the user runs `cargo test --target wasm32-unknown-unknown`
24//
25// * Cargo then compiles all the test suites (aka `tests/*.rs`) as Wasm binaries
26// (the `bin` crate type). These binaries all have entry points that are
27// `main` functions, but it's actually not used. The binaries are also
28// compiled with `--test`, which means they're linked to the standard `test`
29// crate, but this crate doesn't work on Wasm and so we bypass it entirely.
30//
31// * Instead of using `#[test]`, which doesn't work, users wrote tests with
32// `#[wasm_bindgen_test]`. This macro expands to a bunch of `#[no_mangle]`
33// functions with known names (currently named `__wbg_test_*`).
34//
35// * Next up, Cargo was configured via its test runner support to execute the
36// `wasm-bindgen-test-runner` binary. Instead of what Cargo normally does,
37// executing `target/wasm32-unknown-unknown/debug/deps/foo-xxxxx.wasm` (which
38// will fail as we can't actually execute was binaries), Cargo will execute
39// `wasm-bindgen-test-runner target/.../foo-xxxxx.wasm`.
40//
41// * The `wasm-bindgen-test-runner` binary takes over. It runs `wasm-bindgen`
42// over the binary, generating JS bindings and such. It also figures out if
43// we're running in node.js or a browser.
44//
45// * The `wasm-bindgen-test-runner` binary generates a JS entry point. This
46// entry point creates a `Context` below. The runner binary also parses the
47// Wasm file and finds all functions that are named `__wbg_test_*`. The
48// generate file gathers up all these functions into an array and then passes
49// them to `Context` below. Note that these functions are passed as *JS
50// values*.
51//
52// * Somehow, the runner then executes the JS file. This may be with node.js, it
53// may serve up files in a server and wait for the user, or it serves up files
54// in a server and starts headless testing.
55//
56// * Testing starts, it loads all the modules using either ES imports or Node
57// `require` statements. Everything is loaded in JS now.
58//
59// * A `Context` is created. The `Context` is forwarded the CLI arguments of the
60// original `wasm-bindgen-test-runner` in an environment specific fashion.
61// This is used for test filters today.
62//
63// * The `Context::run` function is called. Again, the generated JS has gathered
64// all Wasm tests to be executed into a list, and it's passed in here.
65//
66// * Next, `Context::run` returns a `Promise` representing the eventual
67// execution of all the tests. The Rust `Future` that's returned will work
68// with the tests to ensure that everything's executed by the time the
69// `Promise` resolves.
70//
71// * When a test executes, it's executing an entry point generated by
72// `#[wasm_bindgen_test]`. The test informs the `Context` of its name and
73// other metadata, and then `Context::execute_*` function creates a future
74// representing the execution of the test. This feeds back into the future
75// returned by `Context::run` to finish the test suite.
76//
77// * Finally, after all tests are run, the `Context`'s future resolves, prints
78// out all the result, and finishes in JS.
79//
80// ## Other various notes
81//
82// Phew, that was a lot! Some other various bits and pieces you may want to be
83// aware of are throughout the code. These include things like how printing
84// results is different in node vs a browser, or how we even detect if we're in
85// node or a browser.
86//
87// Overall this is all somewhat in flux as it's pretty new, and feedback is
88// always of course welcome!
89
90use alloc::borrow::ToOwned;
91use alloc::boxed::Box;
92use alloc::format;
93use alloc::rc::Rc;
94use alloc::string::{String, ToString};
95use alloc::vec::Vec;
96use core::cell::{Cell, RefCell};
97use core::fmt::{self, Display};
98use core::future::Future;
99use core::panic::AssertUnwindSafe;
100use core::pin::Pin;
101use core::task::{self, Poll};
102use js_sys::{Array, Function, Number, Promise, Undefined};
103pub use wasm_bindgen;
104
105use wasm_bindgen::prelude::*;
106use wasm_bindgen_futures::future_to_promise;
107
108// Maximum number of tests to execute concurrently. Eventually this should be a
109// configuration option specified at runtime or at compile time rather than
110// baked in here.
111//
112// Currently the default is 1 because the DOM has a lot of shared state, and
113// conccurrently doing things by default would likely end up in a bad situation.
114const CONCURRENCY: usize = 1;
115
116pub mod browser;
117
118/// A modified `criterion.rs`, retaining only the basic benchmark capabilities.
119#[cfg_attr(wasm_bindgen_unstable_test_coverage, coverage(off))]
120pub mod criterion;
121pub mod detect;
122pub mod node;
123mod scoped_tls;
124/// Directly depending on wasm-bindgen-test-based libraries should be avoided,
125/// as it creates a circular dependency that breaks their usage within `wasm-bindgen-test`.
126///
127/// Let's copy web-time.
128#[cfg_attr(wasm_bindgen_unstable_test_coverage, coverage(off))]
129pub(crate) mod web_time;
130pub mod worker;
131
132/// Runtime test harness support instantiated in JS.
133///
134/// The node.js entry script instantiates a `Context` here which is used to
135/// drive test execution.
136#[wasm_bindgen(js_name = WasmBindgenTestContext)]
137pub struct Context {
138 state: Rc<State>,
139}
140
141struct State {
142 /// In Benchmark
143 is_bench: bool,
144
145 /// Include ignored tests.
146 include_ignored: Cell<bool>,
147
148 /// Counter of the number of tests that have succeeded.
149 succeeded_count: Cell<usize>,
150
151 /// Number of tests that have been filtered.
152 filtered_count: Cell<usize>,
153
154 /// Number of tests that have been ignored.
155 ignored_count: Cell<usize>,
156
157 /// A list of all tests which have failed.
158 ///
159 /// Each test listed here is paired with a `JsValue` that represents the
160 /// exception thrown which caused the test to fail.
161 failures: RefCell<Vec<(Test, Failure)>>,
162
163 /// Remaining tests to execute, when empty we're just waiting on the
164 /// `Running` tests to finish.
165 remaining: RefCell<Vec<Test>>,
166
167 /// List of currently executing tests. These tests all involve some level
168 /// of asynchronous work, so they're sitting on the running list.
169 running: RefCell<Vec<Test>>,
170
171 /// How to actually format output, either node.js or browser-specific
172 /// implementation.
173 formatter: Box<dyn Formatter>,
174
175 /// Timing the total duration.
176 timer: Option<Timer>,
177}
178
179/// Failure reasons.
180enum Failure {
181 /// Normal failing test.
182 Error(JsValue),
183 /// A test that `should_panic` but didn't.
184 ShouldPanic,
185 /// A test that `should_panic` with a specific message,
186 /// but panicked with a different message.
187 ShouldPanicExpected,
188}
189
190/// Representation of one test that needs to be executed.
191///
192/// Tests are all represented as futures, and tests perform no work until their
193/// future is polled.
194struct Test {
195 name: String,
196 future: Pin<Box<dyn Future<Output = Result<(), JsValue>>>>,
197 output: Rc<RefCell<Output>>,
198 should_panic: Option<Option<&'static str>>,
199}
200
201/// Captured output of each test.
202#[derive(Default)]
203struct Output {
204 debug: String,
205 log: String,
206 info: String,
207 warn: String,
208 error: String,
209 panic: String,
210 should_panic: bool,
211}
212
213enum TestResult {
214 Ok,
215 Err(JsValue),
216 Ignored(Option<String>),
217}
218
219impl From<Result<(), JsValue>> for TestResult {
220 fn from(value: Result<(), JsValue>) -> Self {
221 match value {
222 Ok(()) => Self::Ok,
223 Err(err) => Self::Err(err),
224 }
225 }
226}
227
228impl Display for TestResult {
229 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
230 match self {
231 TestResult::Ok => write!(f, "ok"),
232 TestResult::Err(_) => write!(f, "FAIL"),
233 TestResult::Ignored(None) => write!(f, "ignored"),
234 TestResult::Ignored(Some(reason)) => write!(f, "ignored, {reason}"),
235 }
236 }
237}
238
239trait Formatter {
240 /// Writes a line of output, typically status information.
241 fn writeln(&self, line: &str);
242
243 /// Log the result of a test, either passing or failing.
244 fn log_test(&self, is_bench: bool, name: &str, result: &TestResult) {
245 if !is_bench {
246 self.writeln(&format!("test {name} ... {result}"));
247 }
248 }
249
250 /// Convert a thrown value into a string, using platform-specific apis
251 /// perhaps to turn the error into a string.
252 fn stringify_error(&self, val: &JsValue) -> String;
253}
254
255#[wasm_bindgen]
256extern "C" {
257 #[wasm_bindgen(js_namespace = console, js_name = log)]
258 #[doc(hidden)]
259 pub fn js_console_log(s: &str);
260
261 #[wasm_bindgen(js_namespace = console, js_name = error)]
262 #[doc(hidden)]
263 pub fn js_console_error(s: &str);
264
265 // General-purpose conversion into a `String`.
266 #[wasm_bindgen(js_name = String)]
267 fn stringify(val: &JsValue) -> String;
268
269 type Global;
270
271 #[wasm_bindgen(method, getter)]
272 fn performance(this: &Global) -> JsValue;
273
274 /// Type for the [`Performance` object](https://developer.mozilla.org/en-US/docs/Web/API/Performance).
275 type Performance;
276
277 /// Binding to [`Performance.now()`](https://developer.mozilla.org/en-US/docs/Web/API/Performance/now).
278 #[wasm_bindgen(method)]
279 fn now(this: &Performance) -> f64;
280}
281
282/// Internal implementation detail of the `console_log!` macro.
283pub fn console_log(args: &fmt::Arguments) {
284 js_console_log(&args.to_string());
285}
286
287/// Internal implementation detail of the `console_error!` macro.
288pub fn console_error(args: &fmt::Arguments) {
289 js_console_error(&args.to_string());
290}
291
292#[wasm_bindgen(js_class = WasmBindgenTestContext)]
293impl Context {
294 /// Creates a new context ready to run tests.
295 ///
296 /// A `Context` is the main structure through which test execution is
297 /// coordinated, and this will collect output and results for all executed
298 /// tests.
299 #[wasm_bindgen(constructor)]
300 pub fn new(is_bench: bool) -> Context {
301 fn panic_handling(mut message: String) {
302 let should_panic = if !CURRENT_OUTPUT.is_set() {
303 false
304 } else {
305 CURRENT_OUTPUT.with(|output| {
306 let mut output = output.borrow_mut();
307 output.panic.push_str(&message);
308 output.should_panic
309 })
310 };
311
312 // See https://github.com/rustwasm/console_error_panic_hook/blob/4dc30a5448ed3ffcfb961b1ad54d000cca881b84/src/lib.rs#L83-L123.
313 if !should_panic {
314 #[wasm_bindgen]
315 extern "C" {
316 type Error;
317
318 #[wasm_bindgen(constructor)]
319 fn new() -> Error;
320
321 #[wasm_bindgen(method, getter)]
322 fn stack(error: &Error) -> String;
323 }
324
325 message.push_str("\n\nStack:\n\n");
326 let e = Error::new();
327 message.push_str(&e.stack());
328
329 message.push_str("\n\n");
330
331 js_console_error(&message);
332 }
333 }
334 #[cfg(feature = "std")]
335 static SET_HOOK: std::sync::Once = std::sync::Once::new();
336 #[cfg(feature = "std")]
337 SET_HOOK.call_once(|| {
338 std::panic::set_hook(Box::new(|panic_info| {
339 panic_handling(panic_info.to_string());
340 }));
341 });
342 #[cfg(not(feature = "std"))]
343 #[panic_handler]
344 fn panic_handler(panic_info: &core::panic::PanicInfo<'_>) -> ! {
345 panic_handling(panic_info.to_string());
346 unreachable!();
347 }
348
349 let formatter = match detect::detect() {
350 detect::Runtime::Browser => Box::new(browser::Browser::new()) as Box<dyn Formatter>,
351 detect::Runtime::Node => Box::new(node::Node::new()) as Box<dyn Formatter>,
352 detect::Runtime::Worker => Box::new(worker::Worker::new()) as Box<dyn Formatter>,
353 };
354
355 let timer = Timer::new();
356
357 Context {
358 state: Rc::new(State {
359 is_bench,
360 include_ignored: Default::default(),
361 failures: Default::default(),
362 succeeded_count: Default::default(),
363 filtered_count: Default::default(),
364 ignored_count: Default::default(),
365 remaining: Default::default(),
366 running: Default::default(),
367 formatter,
368 timer,
369 }),
370 }
371 }
372
373 /// Handle `--include-ignored` flag.
374 pub fn include_ignored(&mut self, include_ignored: bool) {
375 self.state.include_ignored.set(include_ignored);
376 }
377
378 /// Handle filter argument.
379 pub fn filtered_count(&mut self, filtered: usize) {
380 self.state.filtered_count.set(filtered);
381 }
382
383 /// Executes a list of tests, returning a promise representing their
384 /// eventual completion.
385 ///
386 /// This is the main entry point for executing tests. All the tests passed
387 /// in are the JS `Function` object that was plucked off the
388 /// `WebAssembly.Instance` exports list.
389 ///
390 /// The promise returned resolves to either `true` if all tests passed or
391 /// `false` if at least one test failed.
392 pub fn run(&self, tests: Vec<JsValue>) -> Promise {
393 if !self.state.is_bench {
394 let noun = if tests.len() == 1 { "test" } else { "tests" };
395 self.state
396 .formatter
397 .writeln(&format!("running {} {noun}", tests.len()));
398 }
399
400 // Execute all our test functions through their Wasm shims (unclear how
401 // to pass native function pointers around here). Each test will
402 // execute one of the `execute_*` tests below which will push a
403 // future onto our `remaining` list, which we'll process later.
404 let cx_arg = Number::from(self as *const Context as u32);
405 for test in tests {
406 match test
407 .unchecked_into::<Function<fn(Number) -> Undefined>>()
408 .call(&JsValue::null(), (&cx_arg,))
409 {
410 Ok(_) => {}
411 Err(e) => {
412 panic!(
413 "exception thrown while creating a test: {}",
414 self.state.formatter.stringify_error(&e)
415 );
416 }
417 }
418 }
419
420 // Now that we've collected all our tests we wrap everything up in a
421 // future to actually do all the processing, and pass it out to JS as a
422 // `Promise`.
423 let state = AssertUnwindSafe(self.state.clone());
424 future_to_promise(async {
425 let passed = ExecuteTests(state).await;
426 Ok(JsValue::from(passed))
427 })
428 }
429}
430
431crate::scoped_thread_local!(static CURRENT_OUTPUT: RefCell<Output>);
432
433/// Handler for `console.log` invocations.
434///
435/// If a test is currently running it takes the `args` array and stringifies
436/// it and appends it to the current output of the test. Otherwise it passes
437/// the arguments to the original `console.log` function, psased as
438/// `original`.
439//
440// TODO: how worth is it to actually capture the output here? Due to the nature
441// of futures/js we can't guarantee that all output is captured because JS code
442// could just be executing in the void and we wouldn't know which test to
443// attach it to. The main `test` crate in the rust repo also has issues about
444// how not all output is captured, causing some inconsistencies sometimes.
445#[wasm_bindgen]
446pub fn __wbgtest_console_log(args: &Array) {
447 record(args, |output| &mut output.log)
448}
449
450/// Handler for `console.debug` invocations. See above.
451#[wasm_bindgen]
452pub fn __wbgtest_console_debug(args: &Array) {
453 record(args, |output| &mut output.debug)
454}
455
456/// Handler for `console.info` invocations. See above.
457#[wasm_bindgen]
458pub fn __wbgtest_console_info(args: &Array) {
459 record(args, |output| &mut output.info)
460}
461
462/// Handler for `console.warn` invocations. See above.
463#[wasm_bindgen]
464pub fn __wbgtest_console_warn(args: &Array) {
465 record(args, |output| &mut output.warn)
466}
467
468/// Handler for `console.error` invocations. See above.
469#[wasm_bindgen]
470pub fn __wbgtest_console_error(args: &Array) {
471 record(args, |output| &mut output.error)
472}
473
474fn record(args: &Array, dst: impl FnOnce(&mut Output) -> &mut String) {
475 if !CURRENT_OUTPUT.is_set() {
476 return;
477 }
478
479 CURRENT_OUTPUT.with(|output| {
480 let mut out = output.borrow_mut();
481 let dst = dst(&mut out);
482 args.for_each(&mut |val, idx, _array| {
483 if idx != 0 {
484 dst.push(' ');
485 }
486 dst.push_str(&stringify(&val));
487 });
488 dst.push('\n');
489 });
490}
491
492/// Similar to [`std::process::Termination`], but for wasm-bindgen tests.
493pub trait Termination {
494 /// Convert this into a JS result.
495 fn into_js_result(self) -> Result<(), JsValue>;
496}
497
498impl Termination for () {
499 fn into_js_result(self) -> Result<(), JsValue> {
500 Ok(())
501 }
502}
503
504impl<E: core::fmt::Debug> Termination for Result<(), E> {
505 fn into_js_result(self) -> Result<(), JsValue> {
506 self.map_err(|e| JsError::new(&format!("{e:?}")).into())
507 }
508}
509
510impl Context {
511 /// Entry point for a synchronous test in wasm. The `#[wasm_bindgen_test]`
512 /// macro generates invocations of this method.
513 pub fn execute_sync<T: Termination>(
514 &self,
515 name: &str,
516 f: impl 'static + FnOnce() -> T,
517 should_panic: Option<Option<&'static str>>,
518 ignore: Option<Option<&'static str>>,
519 ) {
520 self.execute(name, async { f().into_js_result() }, should_panic, ignore);
521 }
522
523 /// Entry point for an asynchronous in wasm. The
524 /// `#[wasm_bindgen_test(async)]` macro generates invocations of this
525 /// method.
526 pub fn execute_async<F>(
527 &self,
528 name: &str,
529 f: impl FnOnce() -> F + 'static,
530 should_panic: Option<Option<&'static str>>,
531 ignore: Option<Option<&'static str>>,
532 ) where
533 F: Future + 'static,
534 F::Output: Termination,
535 {
536 self.execute(
537 name,
538 async { f().await.into_js_result() },
539 should_panic,
540 ignore,
541 )
542 }
543
544 fn execute(
545 &self,
546 name: &str,
547 test: impl Future<Output = Result<(), JsValue>> + 'static,
548 should_panic: Option<Option<&'static str>>,
549 ignore: Option<Option<&'static str>>,
550 ) {
551 // Remove the crate name to mimic libtest more closely.
552 // This also removes our `__wbgt_` or `__wbgb_` prefix and the `ignored` and `should_panic` modifiers.
553 let name = name.split_once("::").unwrap().1;
554
555 if let Some(ignore) = ignore {
556 if !self.state.include_ignored.get() {
557 self.state.formatter.log_test(
558 self.state.is_bench,
559 name,
560 &TestResult::Ignored(ignore.map(str::to_owned)),
561 );
562 let ignored = self.state.ignored_count.get();
563 self.state.ignored_count.set(ignored + 1);
564 return;
565 }
566 }
567
568 // Looks like we've got a test that needs to be executed! Push it onto
569 // the list of remaining tests.
570 let output = Output {
571 should_panic: should_panic.is_some(),
572 ..Default::default()
573 };
574 let output = Rc::new(RefCell::new(output));
575 let future = TestFuture {
576 output: output.clone(),
577 test,
578 };
579 self.state.remaining.borrow_mut().push(Test {
580 name: name.to_string(),
581 future: Pin::from(Box::new(future)),
582 output,
583 should_panic,
584 });
585 }
586}
587
588struct ExecuteTests(AssertUnwindSafe<Rc<State>>);
589
590impl Future for ExecuteTests {
591 type Output = bool;
592
593 fn poll(self: Pin<&mut Self>, cx: &mut task::Context) -> Poll<bool> {
594 let mut running = self.0.running.borrow_mut();
595 let mut remaining = self.0.remaining.borrow_mut();
596
597 // First up, try to make progress on all active tests. Remove any
598 // finished tests.
599 for i in (0..running.len()).rev() {
600 let result = match running[i].future.as_mut().poll(cx) {
601 Poll::Ready(result) => result,
602 Poll::Pending => continue,
603 };
604 let test = running.remove(i);
605 self.0.log_test_result(test, result.into());
606 }
607
608 // Next up, try to schedule as many tests as we can. Once we get a test
609 // we `poll` it once to ensure we'll receive notifications. We only
610 // want to schedule up to a maximum amount of work though, so this may
611 // not schedule all tests.
612 while running.len() < CONCURRENCY {
613 let mut test = match remaining.pop() {
614 Some(test) => test,
615 None => break,
616 };
617 // Output test invocation log for debugging failures with --nocapture
618 console_log!("Invoking test: {}", test.name);
619 let result = match test.future.as_mut().poll(cx) {
620 Poll::Ready(result) => result,
621 Poll::Pending => {
622 running.push(test);
623 continue;
624 }
625 };
626 self.0.log_test_result(test, result.into());
627 }
628
629 // Tests are still executing, we're registered to get a notification,
630 // keep going.
631 if !running.is_empty() {
632 return Poll::Pending;
633 }
634
635 // If there are no tests running then we must have finished everything,
636 // so we shouldn't have any more remaining tests either.
637 assert_eq!(remaining.len(), 0);
638
639 self.0.print_results();
640 let all_passed = self.0.failures.borrow().is_empty();
641 Poll::Ready(all_passed)
642 }
643}
644
645impl State {
646 fn log_test_result(&self, test: Test, result: TestResult) {
647 // Save off the test for later processing when we print the final
648 // results.
649 if let Some(should_panic) = test.should_panic {
650 if let TestResult::Err(_e) = result {
651 if let Some(expected) = should_panic {
652 if !test.output.borrow().panic.contains(expected) {
653 self.formatter.log_test(
654 self.is_bench,
655 &test.name,
656 &TestResult::Err(JsValue::NULL),
657 );
658 self.failures
659 .borrow_mut()
660 .push((test, Failure::ShouldPanicExpected));
661 return;
662 }
663 }
664
665 self.formatter
666 .log_test(self.is_bench, &test.name, &TestResult::Ok);
667 self.succeeded_count.set(self.succeeded_count.get() + 1);
668 } else {
669 self.formatter
670 .log_test(self.is_bench, &test.name, &TestResult::Err(JsValue::NULL));
671 self.failures
672 .borrow_mut()
673 .push((test, Failure::ShouldPanic));
674 }
675 } else {
676 self.formatter.log_test(self.is_bench, &test.name, &result);
677
678 match result {
679 TestResult::Ok => self.succeeded_count.set(self.succeeded_count.get() + 1),
680 TestResult::Err(e) => self.failures.borrow_mut().push((test, Failure::Error(e))),
681 _ => (),
682 }
683 }
684 }
685
686 fn print_results(&self) {
687 let failures = self.failures.borrow();
688 if !failures.is_empty() {
689 self.formatter.writeln("\nfailures:\n");
690 for (test, failure) in failures.iter() {
691 self.print_failure(test, failure);
692 }
693 self.formatter.writeln("failures:\n");
694 for (test, _) in failures.iter() {
695 self.formatter.writeln(&format!(" {}", test.name));
696 }
697 }
698 let finished_in = if let Some(timer) = &self.timer {
699 format!("; finished in {:.2?}s", timer.elapsed())
700 } else {
701 String::new()
702 };
703 self.formatter.writeln("");
704 self.formatter.writeln(&format!(
705 "test result: {}. \
706 {} passed; \
707 {} failed; \
708 {} ignored; \
709 {} filtered out\
710 {finished_in}\n",
711 if failures.is_empty() { "ok" } else { "FAILED" },
712 self.succeeded_count.get(),
713 failures.len(),
714 self.ignored_count.get(),
715 self.filtered_count.get()
716 ));
717 }
718
719 fn accumulate_console_output(&self, logs: &mut String, which: &str, output: &str) {
720 if output.is_empty() {
721 return;
722 }
723 logs.push_str(which);
724 logs.push_str(" output:\n");
725 logs.push_str(&tab(output));
726 logs.push('\n');
727 }
728
729 fn print_failure(&self, test: &Test, failure: &Failure) {
730 let mut logs = String::new();
731 let output = test.output.borrow();
732
733 match failure {
734 Failure::ShouldPanic => {
735 logs.push_str(&format!(
736 "note: {} did not panic as expected\n\n",
737 test.name
738 ));
739 }
740 Failure::ShouldPanicExpected => {
741 logs.push_str("note: panic did not contain expected string\n");
742 logs.push_str(&format!(" panic message: `\"{}\"`,\n", output.panic));
743 logs.push_str(&format!(
744 " expected substring: `\"{}\"`\n\n",
745 test.should_panic.unwrap().unwrap()
746 ));
747 }
748 _ => (),
749 }
750
751 self.accumulate_console_output(&mut logs, "debug", &output.debug);
752 self.accumulate_console_output(&mut logs, "log", &output.log);
753 self.accumulate_console_output(&mut logs, "info", &output.info);
754 self.accumulate_console_output(&mut logs, "warn", &output.warn);
755 self.accumulate_console_output(&mut logs, "error", &output.error);
756
757 if let Failure::Error(error) = failure {
758 logs.push_str("JS exception that was thrown:\n");
759 let error_string = self.formatter.stringify_error(error);
760 logs.push_str(&tab(&error_string));
761 }
762
763 let msg = format!("---- {} output ----\n{}", test.name, tab(&logs));
764 self.formatter.writeln(&msg);
765 }
766}
767
768/// A wrapper future around each test
769///
770/// This future is what's actually executed for each test and is what's stored
771/// inside of a `Test`. This wrapper future performs two critical functions:
772///
773/// * First, every time when polled, it configures the `CURRENT_OUTPUT` tls
774/// variable to capture output for the current test. That way at least when
775/// we've got Rust code running we'll be able to capture output.
776///
777/// * Next, this "catches panics". Right now all Wasm code is configured as
778/// panic=abort, but it's more like an exception in JS. It's pretty sketchy
779/// to actually continue executing Rust code after an "abort", but we don't
780/// have much of a choice for now.
781///
782/// Panics are caught here by using a shim function that is annotated with
783/// `catch` so we can capture JS exceptions (which Rust panics become). This
784/// way if any Rust code along the execution of a test panics we'll hopefully
785/// capture it.
786///
787/// Note that both of the above aspects of this future are really just best
788/// effort. This is all a bit of a hack right now when it comes down to it and
789/// it definitely won't work in some situations. Hopefully as those situations
790/// arise though we can handle them!
791///
792/// The good news is that everything should work flawlessly in the case where
793/// tests have no output and execute successfully. And everyone always writes
794/// perfect code on the first try, right? *sobs*
795struct TestFuture<F> {
796 output: Rc<RefCell<Output>>,
797 test: F,
798}
799
800#[wasm_bindgen]
801extern "C" {
802 #[wasm_bindgen(catch)]
803 fn __wbg_test_invoke(f: &ScopedClosure<dyn FnMut()>) -> Result<(), JsValue>;
804}
805
806impl<F: Future<Output = Result<(), JsValue>>> Future for TestFuture<F> {
807 type Output = F::Output;
808
809 fn poll(self: Pin<&mut Self>, cx: &mut task::Context) -> Poll<Self::Output> {
810 let output = self.output.clone();
811 // Use `new_unchecked` here to project our own pin, and we never
812 // move `test` so this should be safe
813 let test: Pin<&mut F> = unsafe { Pin::map_unchecked_mut(self, |me| &mut me.test) };
814 let mut future_output = None;
815 let result = CURRENT_OUTPUT.set(&output, || {
816 let mut test = Some(test);
817 let mut func = AssertUnwindSafe(|| {
818 let test = test.take().unwrap_throw();
819 future_output = Some(test.poll(cx))
820 });
821 let closure = ScopedClosure::borrow_mut(&mut func);
822 __wbg_test_invoke(&closure)
823 });
824 match (result, future_output) {
825 (_, Some(Poll::Ready(result))) => Poll::Ready(result),
826 (_, Some(Poll::Pending)) => Poll::Pending,
827 (Err(e), _) => Poll::Ready(Err(e)),
828 (Ok(_), None) => wasm_bindgen::throw_str("invalid poll state"),
829 }
830 }
831}
832
833fn tab(s: &str) -> String {
834 let mut result = String::new();
835 for line in s.lines() {
836 result.push_str(" ");
837 result.push_str(line);
838 result.push('\n');
839 }
840 result
841}
842
843struct Timer {
844 performance: Performance,
845 started: f64,
846}
847
848impl Timer {
849 fn new() -> Option<Self> {
850 let global: Global = js_sys::global().unchecked_into();
851 let performance = global.performance();
852 (!performance.is_undefined()).then(|| {
853 let performance: Performance = performance.unchecked_into();
854 let started = performance.now();
855 Self {
856 performance,
857 started,
858 }
859 })
860 }
861
862 fn elapsed(&self) -> f64 {
863 (self.performance.now() - self.started) / 1000.
864 }
865}