wasm-bindgen-test-0.3.37/Cargo.toml0000644000000022120000000000100124510ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" rust-version = "1.56" name = "wasm-bindgen-test" version = "0.3.37" authors = ["The wasm-bindgen Developers"] description = "Internal testing crate for wasm-bindgen" readme = "README.md" license = "MIT/Apache-2.0" repository = "https://github.com/rustwasm/wasm-bindgen" resolver = "2" [lib] test = false [dependencies.console_error_panic_hook] version = "0.1" [dependencies.gg-alloc] version = "1.0" optional = true [dependencies.js-sys] version = "0.3.64" [dependencies.scoped-tls] version = "1.0" [dependencies.wasm-bindgen] version = "0.2.87" [dependencies.wasm-bindgen-futures] version = "0.4.37" [dependencies.wasm-bindgen-test-macro] version = "=0.3.37" wasm-bindgen-test-0.3.37/Cargo.toml.orig000064400000000000000000000012231046102023000161330ustar 00000000000000[package] name = "wasm-bindgen-test" version = "0.3.37" authors = ["The wasm-bindgen Developers"] description = "Internal testing crate for wasm-bindgen" license = "MIT/Apache-2.0" repository = "https://github.com/rustwasm/wasm-bindgen" edition = "2018" rust-version = "1.56" [dependencies] console_error_panic_hook = '0.1' js-sys = { path = '../js-sys', version = '0.3.64' } scoped-tls = "1.0" wasm-bindgen = { path = '../..', version = '0.2.87' } wasm-bindgen-futures = { path = '../futures', version = '0.4.37' } wasm-bindgen-test-macro = { path = '../test-macro', version = '=0.3.37' } gg-alloc = { version = "1.0", optional = true } [lib] test = false wasm-bindgen-test-0.3.37/LICENSE-APACHE000064400000000000000000000251371046102023000152020ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. wasm-bindgen-test-0.3.37/LICENSE-MIT000064400000000000000000000020411046102023000146770ustar 00000000000000Copyright (c) 2014 Alex Crichton Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. wasm-bindgen-test-0.3.37/README.md000064400000000000000000000056551046102023000145400ustar 00000000000000# `wasm-bindgen-test` [**Read the "Testing with `wasm-bindgen-test`" section of the guide!**](https://rustwasm.github.io/wasm-bindgen/wasm-bindgen-test/index.html) ## Components The test harness is made of three separate components, but you typically don't have to worry about most of them. They're documented here for documentation purposes! ### `wasm-bindgen-test-macro` This crate, living at `crates/test-macro`, is a procedural macro that defines the `#[wasm_bindgen_test]` macro. **The normal `#[test]` cannot be used and will not work.** Eventually it's intended that the `#[wasm_bindgen_test]` attribute could gain arguments like "run in a browser" or something like a minimum Node version. For now though the macro is pretty simple and reexported from the next crate, `wasm-bindgen-test`. ### `wasm-bindgen-test` This is the runtime support needed to execute tests. This is basically the same thing as the `test` crate in the Rust repository, and one day it will likely use the `test` crate itself! For now though it's a minimal reimplementation that provides the support for: * Printing what test cases are running * Collecting `console.log` and `console.error` output of each test case for printing later * Rendering the failure output of each test case * Catching JS exceptions so tests can continue to run after a test fails * Driving execution of all tests This is the crate which you actually link to in your wasm test and through which you import the `#[wasm_bindgen_test]` macro. Otherwise this crate provides a `console_log!` macro that's a utility like `println!` only using `console.log`. This crate may grow more functionality in the future, but for now it's somewhat bare bones! ### `wasm-bindgen-test-runner` This is where the secret sauce comes into play. We configured Cargo to execute this binary *instead* of directly executing the `*.wasm` file (which Cargo would otherwise try to do). This means that whenever a test is executed it executes this binary with the wasm file as an argument, allowing it to take full control over the test process! The test runner is currently pretty simple, executing a few steps: * First, it runs the equivalent of `wasm-bindgen`. This'll generate wasm-bindgen output in a temporary directory. * Next, it generates a small shim JS file which imports these wasm-bindgen-generated files and executes the test harness. * Finally, it executes `node` over the generated JS file, executing all of your tests. In essence what happens is that this test runner automatically executes `wasm-bindgen` and then uses Node to actually execute the wasm file, meaning that your wasm code currently runs in a Node environment. ## Future Work 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. * Running each test in its own wasm instance to avoid poisoning the environment on panic wasm-bindgen-test-0.3.37/src/lib.rs000064400000000000000000000040661046102023000151570ustar 00000000000000//! Runtime support for the `#[wasm_bindgen_test]` attribute //! //! More documentation can be found in the README for this crate! #![deny(missing_docs)] pub use wasm_bindgen_test_macro::wasm_bindgen_test; // Custom allocator that only returns pointers in the 2GB-4GB range // To ensure we actually support more than 2GB of memory #[cfg(all(test, feature = "gg-alloc"))] #[global_allocator] static A: gg_alloc::GgAlloc = gg_alloc::GgAlloc::new(std::alloc::System); /// Helper macro which acts like `println!` only routes to `console.log` /// instead. #[macro_export] macro_rules! console_log { ($($arg:tt)*) => ( $crate::__rt::log(&format_args!($($arg)*)) ) } /// A macro used to configured how this test is executed by the /// `wasm-bindgen-test-runner` harness. /// /// This macro is invoked as: /// /// ```ignore /// wasm_bindgen_test_configure!(foo bar baz); /// ``` /// /// where all of `foo`, `bar`, and `baz`, would be recognized options to this /// macro. The currently known options to this macro are: /// /// * `run_in_browser` - requires that this test is run in a browser rather than /// node.js, which is the default for executing tests. /// * `run_in_worker` - requires that this test is run in a web worker rather than /// node.js, which is the default for executing tests. /// /// This macro may be invoked at most one time per test suite (an entire binary /// like `tests/foo.rs`, not per module) #[macro_export] macro_rules! wasm_bindgen_test_configure { (run_in_browser $($others:tt)*) => ( #[link_section = "__wasm_bindgen_test_unstable"] #[cfg(target_arch = "wasm32")] pub static __WBG_TEST_RUN_IN_BROWSER: [u8; 1] = [0x01]; $crate::wasm_bindgen_test_configure!($($others)*); ); (run_in_worker $($others:tt)*) => ( #[link_section = "__wasm_bindgen_test_unstable"] #[cfg(target_arch = "wasm32")] pub static __WBG_TEST_RUN_IN_WORKER: [u8; 1] = [0x10]; $crate::wasm_bindgen_test_configure!($($others)*); ); () => () } #[path = "rt/mod.rs"] pub mod __rt; wasm-bindgen-test-0.3.37/src/rt/browser.rs000064400000000000000000000051661046102023000165230ustar 00000000000000//! Support for printing status information of a test suite in a browser. //! //! Currently this is quite simple, rendering the same as the console tests in //! node.js. Output here is rendered in a `pre`, however. use js_sys::Error; use wasm_bindgen::prelude::*; /// Implementation of `Formatter` for browsers. /// /// Routes all output to a `pre` on the page currently. Eventually this probably /// wants to be a pretty table with colors and folding and whatnot. pub struct Browser { pre: Element, } #[wasm_bindgen] extern "C" { type HTMLDocument; static document: HTMLDocument; #[wasm_bindgen(method, structural)] fn getElementById(this: &HTMLDocument, id: &str) -> Element; type Element; #[wasm_bindgen(method, getter = textContent, structural)] fn text_content(this: &Element) -> String; #[wasm_bindgen(method, setter = textContent, structural)] fn set_text_content(this: &Element, text: &str); type BrowserError; #[wasm_bindgen(method, getter, structural)] fn stack(this: &BrowserError) -> JsValue; } impl Browser { /// Creates a new instance of `Browser`, assuming that its APIs will work /// (requires `Node::new()` to have return `None` first). pub fn new() -> Browser { let pre = document.getElementById("output"); pre.set_text_content(""); Browser { pre } } } impl super::Formatter for Browser { fn writeln(&self, line: &str) { let mut html = self.pre.text_content(); html.extend(line.chars().chain(Some('\n'))); self.pre.set_text_content(&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 stringify_error(&self, err: &JsValue) -> String { // TODO: this should be a checked cast to `Error` 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)); let stack = err.stack(); let header = format!("{}: {}", name, message); let stack = match stack.as_string() { Some(stack) => stack, None => return header, }; // If the `stack` variable contains the name/message already, this is // probably a chome-like error which is already rendered well, so just // return this info if stack.contains(&header) { return stack; } // Fallback to make sure we don't lose any info format!("{}\n{}", header, stack) } } wasm-bindgen-test-0.3.37/src/rt/detect.rs000064400000000000000000000023521046102023000163020ustar 00000000000000//! Runtime detection of whether we're in node.js or a browser. use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { type This; #[wasm_bindgen(method, getter, structural, js_name = self)] fn self_(me: &This) -> Option; type Scope; #[wasm_bindgen(method, getter, structural)] fn constructor(me: &Scope) -> Constructor; type Constructor; #[wasm_bindgen(method, getter, structural)] fn name(me: &Constructor) -> String; } /// Detecting the current JS scope pub fn detect() -> Runtime { // Test whether we're in a browser/worker by seeing if the `self` property is // defined on the global object and it is not equal to a WorkerScope, which should in turn // only be true in browsers. match js_sys::global().unchecked_into::().self_() { Some(scope) => match scope.constructor().name().as_str() { "DedicatedWorkerGlobalScope" | "SharedWorkerGlobalScope" => Runtime::Worker, _ => Runtime::Browser, }, None => Runtime::Node, } } /// Current runtime environment pub enum Runtime { /// Current scope is a browser scope Browser, /// Current scope is a node scope Node, /// Current scope is a worker scope Worker, } wasm-bindgen-test-0.3.37/src/rt/mod.rs000064400000000000000000000624641046102023000156230ustar 00000000000000//! Internal-only runtime module used for the `wasm_bindgen_test` crate. //! //! No API contained in this module will respect semver, these should all be //! considered private APIs. // # Architecture of `wasm_bindgen_test` // // This module can seem a bit funky, but it's intended to be the runtime support // of the `#[wasm_bindgen_test]` macro and be amenable to executing wasm test // suites. The general idea is that for a wasm test binary there will be a set // of functions tagged `#[wasm_bindgen_test]`. It's the job of the runtime // support to execute all of these functions, collecting and collating the // results. // // This runtime support works in tandem with the `wasm-bindgen-test-runner` // binary as part of the `wasm-bindgen-cli` package. // // ## High Level Overview // // Here's a rough and (semi) high level overview of what happens when this crate // runs. // // * First, the user runs `cargo test --target wasm32-unknown-unknown` // // * Cargo then compiles all the test suites (aka `tests/*.rs`) as wasm binaries // (the `bin` crate type). These binaries all have entry points that are // `main` functions, but it's actually not used. The binaries are also // compiled with `--test`, which means they're linked to the standard `test` // crate, but this crate doesn't work on wasm and so we bypass it entirely. // // * Instead of using `#[test]`, which doesn't work, users wrote tests with // `#[wasm_bindgen_test]`. This macro expands to a bunch of `#[no_mangle]` // functions with known names (currently named `__wbg_test_*`). // // * Next up, Cargo was configured via its test runner support to execute the // `wasm-bindgen-test-runner` binary. Instead of what Cargo normally does, // executing `target/wasm32-unknown-unknown/debug/deps/foo-xxxxx.wasm` (which // will fail as we can't actually execute was binaries), Cargo will execute // `wasm-bindgen-test-runner target/.../foo-xxxxx.wasm`. // // * The `wasm-bindgen-test-runner` binary takes over. It runs `wasm-bindgen` // over the binary, generating JS bindings and such. It also figures out if // we're running in node.js or a browser. // // * The `wasm-bindgen-test-runner` binary generates a JS entry point. This // entry point creates a `Context` below. The runner binary also parses the // wasm file and finds all functions that are named `__wbg_test_*`. The // generate file gathers up all these functions into an array and then passes // them to `Context` below. Note that these functions are passed as *JS // values*. // // * Somehow, the runner then executes the JS file. This may be with node.js, it // may serve up files in a server and wait for the user, or it serves up files // in a server and starts headless testing. // // * Testing starts, it loads all the modules using either ES imports or Node // `require` statements. Everything is loaded in JS now. // // * A `Context` is created. The `Context` is forwarded the CLI arguments of the // original `wasm-bindgen-test-runner` in an environment specific fashion. // 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. // // * 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_*` 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`'s future resolves, prints // out all the result, and finishes in JS. // // ## Other various notes // // Phew, that was a lot! Some other various bits and pieces you may want to be // aware of are throughout the code. These include things like how printing // results is different in node vs a browser, or how we even detect if we're in // node or a browser. // // Overall this is all somewhat in flux as it's pretty new, and feedback is // always of course welcome! use js_sys::{Array, Function, Promise}; use std::cell::{Cell, RefCell}; use std::fmt; use std::future::Future; use std::pin::Pin; use std::rc::Rc; use std::sync::Once; use std::task::{self, Poll}; 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 browser; pub mod detect; pub mod node; pub mod worker; /// Runtime test harness support instantiated in JS. /// /// The node.js entry script instantiates a `Context` here which is used to /// drive test execution. #[wasm_bindgen(js_name = WasmBindgenTestContext)] pub struct Context { state: Rc, } 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: RefCell>, /// Counter of the number of tests that have succeeded. succeeded: Cell, /// Counter of the number of tests that have been ignored ignored: Cell, /// 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>, /// Remaining tests to execute, when empty we're just waiting on the /// `Running` tests to finish. remaining: RefCell>, /// List of currently executing tests. These tests all involve some level /// of asynchronous work, so they're sitting on the running list. running: RefCell>, /// How to actually format output, either node.js or browser-specific /// implementation. formatter: Box, } /// Failure reasons. enum Failure { /// Normal failing test. Error(JsValue), /// A test that `should_panic` but didn't. ShouldPanic, /// A test that `should_panic` with a specific message, /// but panicked with a different message. ShouldPanicExpected, } /// 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: Pin>>>, output: Rc>, should_panic: Option>, } /// Captured output of each test. #[derive(Default)] struct Output { debug: String, log: String, info: String, warn: String, error: String, panic: String, should_panic: bool, } trait Formatter { /// Writes a line of output, typically status information. fn writeln(&self, line: &str); /// 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 "C" { #[wasm_bindgen(js_namespace = console, js_name = log)] #[doc(hidden)] pub fn js_console_log(s: &str); // General-purpose conversion into a `String`. #[wasm_bindgen(js_name = String)] fn stringify(val: &JsValue) -> String; } /// Internal implementation detail of the `console_log!` macro. pub fn log(args: &fmt::Arguments) { js_console_log(&args.to_string()); } #[wasm_bindgen(js_class = WasmBindgenTestContext)] impl Context { /// Creates a new context ready to run tests. /// /// A `Context` is the main structure through which test execution is /// coordinated, and this will collect output and results for all executed /// tests. #[wasm_bindgen(constructor)] pub fn new() -> Context { static SET_HOOK: Once = Once::new(); SET_HOOK.call_once(|| { std::panic::set_hook(Box::new(|panic_info| { let should_panic = CURRENT_OUTPUT.with(|output| { let mut output = output.borrow_mut(); output.panic.push_str(&panic_info.to_string()); output.should_panic }); if !should_panic { console_error_panic_hook::hook(panic_info); } })); }); let formatter = match detect::detect() { detect::Runtime::Browser => Box::new(browser::Browser::new()) as Box, detect::Runtime::Node => Box::new(node::Node::new()) as Box, detect::Runtime::Worker => Box::new(worker::Worker::new()) as Box, }; Context { state: Rc::new(State { filter: Default::default(), failures: Default::default(), ignored: Default::default(), remaining: Default::default(), running: Default::default(), succeeded: Default::default(), formatter, }), } } /// Inform this context about runtime arguments passed to the test /// harness. /// /// Eventually this will be used to support flags, but for now it's just /// used to support test filters. pub fn args(&mut self, args: Vec) { // Here we want to reject all flags like `--foo` or `-f` as we don't // support anything, and also we only support at most one non-flag // 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 filter.is_some() { panic!("more than one filter argument cannot be passed"); } *filter = Some(arg); } } /// 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. /// /// 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) -> Promise { let noun = if tests.len() == 1 { "test" } else { "tests" }; 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 { 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) ); } } } // 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 state = self.state.clone(); future_to_promise(async { let passed = ExecuteTests(state).await; Ok(JsValue::from(passed)) }) } } scoped_tls::scoped_thread_local!(static CURRENT_OUTPUT: RefCell); /// 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(args: &Array) { record(args, |output| &mut output.log) } /// Handler for `console.debug` invocations. See above. #[wasm_bindgen] pub fn __wbgtest_console_debug(args: &Array) { record(args, |output| &mut output.debug) } /// Handler for `console.info` invocations. See above. #[wasm_bindgen] pub fn __wbgtest_console_info(args: &Array) { record(args, |output| &mut output.info) } /// Handler for `console.warn` invocations. See above. #[wasm_bindgen] pub fn __wbgtest_console_warn(args: &Array) { record(args, |output| &mut output.warn) } /// Handler for `console.error` invocations. See above. #[wasm_bindgen] pub fn __wbgtest_console_error(args: &Array) { record(args, |output| &mut output.error) } fn record(args: &Array, dst: impl FnOnce(&mut Output) -> &mut String) { if !CURRENT_OUTPUT.is_set() { return; } 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(' '); } dst.push_str(&stringify(&val)); }); dst.push('\n'); }); } /// Similar to [`std::process::Termination`], but for wasm-bindgen tests. pub trait Termination { /// Convert this into a JS result. fn into_js_result(self) -> Result<(), JsValue>; } impl Termination for () { fn into_js_result(self) -> Result<(), JsValue> { Ok(()) } } impl Termination for Result<(), E> { fn into_js_result(self) -> Result<(), JsValue> { self.map_err(|e| JsError::new(&format!("{:?}", e)).into()) } } 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 'static + FnOnce() -> T, should_panic: Option>, ) { self.execute(name, async { f().into_js_result() }, should_panic); } /// Entry point for an asynchronous in wasm. The /// `#[wasm_bindgen_test(async)]` macro generates invocations of this /// method. pub fn execute_async( &self, name: &str, f: impl FnOnce() -> F + 'static, should_panic: Option>, ) where F: Future + 'static, F::Output: Termination, { self.execute(name, async { f().await.into_js_result() }, should_panic) } fn execute( &self, name: &str, test: impl Future> + 'static, should_panic: Option>, ) { // 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; } } // Looks like we've got a test that needs to be executed! Push it onto // the list of remaining tests. let output = Output { should_panic: should_panic.is_some(), ..Default::default() }; let output = Rc::new(RefCell::new(output)); let future = TestFuture { output: output.clone(), test, }; self.state.remaining.borrow_mut().push(Test { name: name.to_string(), future: Pin::from(Box::new(future)), output, should_panic, }); } } struct ExecuteTests(Rc); impl Future for ExecuteTests { type Output = bool; fn poll(self: Pin<&mut Self>, cx: &mut task::Context) -> Poll { 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.as_mut().poll(cx) { Poll::Ready(result) => result, Poll::Pending => continue, }; 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.as_mut().poll(cx) { Poll::Ready(result) => result, Poll::Pending => { running.push(test); continue; } }; 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 Poll::Pending; } // 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; Poll::Ready(all_passed) } } impl State { fn log_test_result(&self, test: Test, result: Result<(), JsValue>) { // Save off the test for later processing when we print the final // results. if let Some(should_panic) = test.should_panic { if let Err(_e) = result { if let Some(expected) = should_panic { if !test.output.borrow().panic.contains(expected) { self.formatter.log_test(&test.name, &Err(JsValue::NULL)); self.failures .borrow_mut() .push((test, Failure::ShouldPanicExpected)); return; } } self.formatter.log_test(&test.name, &Ok(())); self.succeeded.set(self.succeeded.get() + 1); } else { self.formatter.log_test(&test.name, &Err(JsValue::NULL)); self.failures .borrow_mut() .push((test, Failure::ShouldPanic)); } } else { self.formatter.log_test(&test.name, &result); match result { Ok(()) => self.succeeded.set(self.succeeded.get() + 1), Err(e) => self.failures.borrow_mut().push((test, Failure::Error(e))), } } } fn print_results(&self) { let failures = self.failures.borrow(); if failures.len() > 0 { self.formatter.writeln("\nfailures:\n"); for (test, failure) in failures.iter() { self.print_failure(test, failure); } self.formatter.writeln("failures:\n"); for (test, _) in failures.iter() { self.formatter.writeln(&format!(" {}", test.name)); } } self.formatter.writeln(""); self.formatter.writeln(&format!( "test result: {}. \ {} passed; \ {} failed; \ {} ignored\n", if failures.len() == 0 { "ok" } else { "FAILED" }, self.succeeded.get(), failures.len(), self.ignored.get(), )); } fn accumulate_console_output(&self, logs: &mut String, which: &str, output: &str) { if output.is_empty() { return; } logs.push_str(which); logs.push_str(" output:\n"); logs.push_str(&tab(output)); logs.push('\n'); } fn print_failure(&self, test: &Test, failure: &Failure) { let mut logs = String::new(); let output = test.output.borrow(); match failure { Failure::ShouldPanic => { logs.push_str(&format!( "note: {} did not panic as expected\n\n", test.name )); } Failure::ShouldPanicExpected => { logs.push_str("note: panic did not contain expected string\n"); logs.push_str(&format!(" panic message: `\"{}\"`,\n", output.panic)); logs.push_str(&format!( " expected substring: `\"{}\"`\n\n", test.should_panic.unwrap().unwrap() )); } _ => (), } self.accumulate_console_output(&mut logs, "debug", &output.debug); self.accumulate_console_output(&mut logs, "log", &output.log); self.accumulate_console_output(&mut logs, "info", &output.info); self.accumulate_console_output(&mut logs, "warn", &output.warn); self.accumulate_console_output(&mut logs, "error", &output.error); if let Failure::Error(error) = failure { 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); } } /// 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 { output: Rc>, test: F, } #[wasm_bindgen] extern "C" { #[wasm_bindgen(catch)] fn __wbg_test_invoke(f: &mut dyn FnMut()) -> Result<(), JsValue>; } impl>> Future for TestFuture { type Output = F::Output; fn poll(self: Pin<&mut Self>, cx: &mut task::Context) -> Poll { let output = self.output.clone(); // Use `new_unchecked` here to project our own pin, and we never // move `test` so this should be safe let test = unsafe { Pin::map_unchecked_mut(self, |me| &mut me.test) }; let mut future_output = None; let result = CURRENT_OUTPUT.set(&output, || { let mut test = Some(test); __wbg_test_invoke(&mut || { let test = test.take().unwrap_throw(); future_output = Some(test.poll(cx)) }) }); match (result, future_output) { (_, Some(Poll::Ready(result))) => Poll::Ready(result), (_, Some(Poll::Pending)) => Poll::Pending, (Err(e), _) => Poll::Ready(Err(e)), (Ok(_), None) => wasm_bindgen::throw_str("invalid poll state"), } } } fn tab(s: &str) -> String { let mut result = String::new(); for line in s.lines() { result.push_str(" "); result.push_str(line); result.push('\n'); } result } wasm-bindgen-test-0.3.37/src/rt/node.rs000064400000000000000000000021611046102023000157550ustar 00000000000000//! Support for printing status information of a test suite in node.js //! //! This currently uses the same output as `libtest`, only reimplemented here //! for node itself. use wasm_bindgen::prelude::*; /// Implementation of the `Formatter` trait for node.js pub struct Node {} #[wasm_bindgen] extern "C" { // Not using `js_sys::Error` because node's errors specifically have a // `stack` attribute. type NodeError; #[wasm_bindgen(method, getter, js_class = "Error", structural)] fn stack(this: &NodeError) -> String; } impl Node { /// Attempts to create a new formatter for node.js pub fn new() -> Node { Node {} } } impl super::Formatter for Node { fn writeln(&self, line: &str) { super::js_console_log(line); } fn log_test(&self, name: &str, result: &Result<(), JsValue>) { let s = if result.is_ok() { "ok" } else { "FAIL" }; self.writeln(&format!("test {} ... {}", name, s)); } fn stringify_error(&self, err: &JsValue) -> String { // TODO: should do a checked cast to `NodeError` NodeError::from(err.clone()).stack() } } wasm-bindgen-test-0.3.37/src/rt/worker.rs000064400000000000000000000040511046102023000163410ustar 00000000000000//! Support for printing status information of a test suite in a browser. //! //! Currently this is quite simple, rendering the same as the console tests in //! node.js. Output here is rendered in a `pre`, however. use js_sys::Error; use wasm_bindgen::prelude::*; /// Implementation of `Formatter` for browsers. /// /// Routes all output to a `pre` on the page currently. Eventually this probably /// wants to be a pretty table with colors and folding and whatnot. pub struct Worker {} #[wasm_bindgen] extern "C" { type WorkerError; #[wasm_bindgen(method, getter, structural)] fn stack(this: &WorkerError) -> JsValue; #[wasm_bindgen(js_name = "__wbg_test_output_writeln")] fn write_output_line(data: JsValue); } impl Worker { /// Attempts to create a new formatter for web worker pub fn new() -> Worker { Worker {} } } impl super::Formatter for Worker { fn writeln(&self, line: &str) { write_output_line(JsValue::from(String::from(line))); } fn log_test(&self, name: &str, result: &Result<(), JsValue>) { let s = if result.is_ok() { "ok" } else { "FAIL" }; self.writeln(&format!("test {} ... {}", name, s)); } fn stringify_error(&self, err: &JsValue) -> String { // TODO: this should be a checked cast to `Error` let error = Error::from(err.clone()); let name = String::from(error.name()); let message = String::from(error.message()); let err = WorkerError::from(err.clone()); let stack = err.stack(); let header = format!("{}: {}", name, message); let stack = match stack.as_string() { Some(stack) => stack, None => return header, }; // If the `stack` variable contains the name/message already, this is // probably a chome-like error which is already rendered well, so just // return this info if stack.contains(&header) { return stack; } // Fallback to make sure we don't lose any info format!("{}\n{}", header, stack) } }