diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml
index 9603568a..1a47b46d 100644
--- a/crates/cli/Cargo.toml
+++ b/crates/cli/Cargo.toml
@@ -15,8 +15,9 @@ information see https://github.com/alexcrichton/wasm-bindgen.
[dependencies]
docopt = "1.0"
failure = "0.1"
+parity-wasm = "0.31"
+rouille = { version = "2.1.0", default-features = false }
serde = "1.0"
serde_derive = "1.0"
wasm-bindgen-cli-support = { path = "../cli-support", version = "=0.2.15" }
wasm-bindgen-shared = { path = "../shared", version = "=0.2.15" }
-parity-wasm = "0.31"
diff --git a/crates/cli/src/bin/wasm-bindgen-test-runner/index.html b/crates/cli/src/bin/wasm-bindgen-test-runner/index.html
new file mode 100644
index 00000000..88fad944
--- /dev/null
+++ b/crates/cli/src/bin/wasm-bindgen-test-runner/index.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+ Loading scripts...
+
+
+
+
diff --git a/crates/cli/src/bin/wasm-bindgen-test-runner.rs b/crates/cli/src/bin/wasm-bindgen-test-runner/main.rs
similarity index 50%
rename from crates/cli/src/bin/wasm-bindgen-test-runner.rs
rename to crates/cli/src/bin/wasm-bindgen-test-runner/main.rs
index aa908d31..c316f475 100644
--- a/crates/cli/src/bin/wasm-bindgen-test-runner.rs
+++ b/crates/cli/src/bin/wasm-bindgen-test-runner/main.rs
@@ -1,17 +1,22 @@
#[macro_use]
extern crate failure;
-extern crate wasm_bindgen_cli_support;
extern crate parity_wasm;
+extern crate rouille;
+extern crate wasm_bindgen_cli_support;
use std::env;
use std::fs;
+use std::io::{self, Write};
use std::path::PathBuf;
-use std::process::{self, Command};
+use std::process;
use failure::{ResultExt, Error};
use parity_wasm::elements::{Module, Deserialize};
use wasm_bindgen_cli_support::Bindgen;
+mod node;
+mod server;
+
fn main() {
let err = match rmain() {
Ok(()) => return,
@@ -53,52 +58,6 @@ fn rmain() -> Result<(), Error> {
.and_then(|s| s.to_str())
.ok_or_else(|| format_err!("invalid filename passed in"))?;
- let mut js_to_execute = format!(r#"
- const {{ exit }} = require('process');
-
- let cx = 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) {{
- prev_log.apply(null, arguments);
- }} else {{
- cx.console_log(prev_log, arguments);
- }}
- }};
- const prev_error = console.error;
- console.error = function() {{
- if (cx === null) {{
- prev_error.apply(null, arguments);
- }} else {{
- cx.console_error(prev_error, arguments);
- }}
- }};
-
- const support = require("./{0}");
- const wasm = require("./{0}_bg");
-
- // Hack for now to support 0 tests in a binary. This should be done
- // better...
- if (support.Context === undefined)
- process.exit(0);
-
- cx = new support.Context();
-
- // Forward runtime arguments. These arguments are also arguments to the
- // `wasm-bindgen-test-runner` which forwards them to node which we
- // forward to the test harness. this is basically only used for test
- // filters for now.
- cx.args(process.argv.slice(2));
-
- const tests = [];
- "#,
- module
- );
-
// Collect all tests that the test harness is supposed to run. We assume
// that any exported function with the prefix `__wbg_test` is a test we need
// to execute.
@@ -110,53 +69,41 @@ fn rmain() -> Result<(), Error> {
.context("failed to read wasm file")?;
let wasm = Module::deserialize(&mut &wasm[..])
.context("failed to deserialize wasm module")?;
+ let mut tests = Vec::new();
if let Some(exports) = wasm.export_section() {
for export in exports.entries() {
if !export.field().starts_with("__wbg_test") {
continue
}
- js_to_execute.push_str(&format!("tests.push(wasm.{})\n", export.field()));
+ tests.push(export.field().to_string());
}
}
+ if tests.len() == 0 {
+ println!("no tests to run!");
+ return Ok(())
+ }
- // And as a final addendum, exit with a nonzero code if any tests fail.
- js_to_execute.push_str("if (!cx.run(tests)) exit(1);\n");
+ let node = true;
+
+ print!("Executing bindgen ...\r");
+ io::stdout().flush()?;
// For now unconditionally generate wasm-bindgen code tailored for node.js,
// but eventually we'll want more options here for browsers!
let mut b = Bindgen::new();
b.debug(true)
- .nodejs(true)
- .input_module(module, wasm, |m| parity_wasm::serialize(m).unwrap())
+ .nodejs(node)
+ .input_module(module, wasm, |w| parity_wasm::serialize(w).unwrap())
.keep_debug(false)
.generate(&tmpdir)
.context("executing `wasm-bindgen` over the wasm file")?;
- let js_path = tmpdir.join("run.js");
- fs::write(&js_path, js_to_execute)
- .context("failed to write JS file")?;
+ print!(" \r");
+ io::stdout().flush()?;
- // Last but not least, execute `node`! Add an entry to `NODE_PATH` for the
- // project root to hopefully pick up `node_modules` and other local imports.
- let path = env::var_os("NODE_PATH").unwrap_or_default();
- let mut paths = env::split_paths(&path).collect::>();
- paths.push(env::current_dir().unwrap());
- exec(
- Command::new("node")
- .env("NODE_PATH", env::join_paths(&paths).unwrap())
- .arg(&js_path)
- .args(args)
- )
-}
+ if node {
+ return node::execute(&module, &tmpdir, &args.collect::>(), &tests)
+ }
-#[cfg(unix)]
-fn exec(cmd: &mut Command) -> Result<(), Error> {
- use std::os::unix::prelude::*;
- Err(Error::from(cmd.exec()).context("failed to execute `node`").into())
-}
-
-#[cfg(windows)]
-fn exec(cmd: &mut Command) -> Result<(), Error> {
- let status = cmd.status()?;
- process::exit(status.code().unwrap_or(3));
+ server::spawn(&module, &tmpdir, &args.collect::>(), &tests)
}
diff --git a/crates/cli/src/bin/wasm-bindgen-test-runner/node.rs b/crates/cli/src/bin/wasm-bindgen-test-runner/node.rs
new file mode 100644
index 00000000..75c01d65
--- /dev/null
+++ b/crates/cli/src/bin/wasm-bindgen-test-runner/node.rs
@@ -0,0 +1,100 @@
+use std::env;
+use std::ffi::OsString;
+use std::fs;
+use std::path::Path;
+use std::process::Command;
+
+use failure::{ResultExt, Error};
+
+pub fn execute(module: &str, tmpdir: &Path, args: &[OsString], tests: &[String])
+ -> Result<(), Error>
+{
+ let mut js_to_execute = format!(r#"
+ const {{ exit }} = require('process');
+
+ let cx = 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) {{
+ prev_log.apply(null, arguments);
+ }} else {{
+ cx.console_log(prev_log, arguments);
+ }}
+ }};
+ const prev_error = console.error;
+ console.error = function() {{
+ if (cx === null) {{
+ prev_error.apply(null, arguments);
+ }} else {{
+ cx.console_error(prev_error, arguments);
+ }}
+ }};
+
+ function main(tests) {{
+ const support = require("./{0}");
+ const wasm = require("./{0}_bg");
+
+ // Hack for now to support 0 tests in a binary. This should be done
+ // better...
+ if (support.Context === undefined)
+ process.exit(0);
+
+ cx = new support.Context();
+
+ // Forward runtime arguments. These arguments are also arguments to the
+ // `wasm-bindgen-test-runner` which forwards them to node which we
+ // forward to the test harness. this is basically only used for test
+ // filters for now.
+ cx.args(process.argv.slice(2));
+
+ if (!cx.run(tests.map(n => wasm[n])))
+ exit(1);
+ }}
+
+ const tests = [];
+ "#,
+ module
+ );
+
+ // Note that we're collecting *JS objects* that represent the functions to
+ // execute, and then those objects are passed into wasm for it to execute
+ // when it sees fit.
+ for test in tests {
+ js_to_execute.push_str(&format!("tests.push('{}')\n", test));
+ }
+
+ // And as a final addendum, exit with a nonzero code if any tests fail.
+ js_to_execute.push_str("
+ main(tests)
+ ");
+
+ let js_path = tmpdir.join("run.js");
+ fs::write(&js_path, js_to_execute)
+ .context("failed to write JS file")?;
+
+ let path = env::var("NODE_PATH").unwrap_or_default();
+ let mut path = env::split_paths(&path).collect::>();
+ path.push(env::current_dir().unwrap());
+ exec(
+ Command::new("node")
+ .env("NODE_PATH", env::join_paths(&path).unwrap())
+ .arg(&js_path)
+ .args(args)
+ )
+}
+
+#[cfg(unix)]
+fn exec(cmd: &mut Command) -> Result<(), Error> {
+ use std::os::unix::prelude::*;
+ Err(Error::from(cmd.exec()).context("failed to execute `node`").into())
+}
+
+#[cfg(windows)]
+fn exec(cmd: &mut Command) -> Result<(), Error> {
+ let status = cmd.status()?;
+ process::exit(status.code().unwrap_or(3));
+}
diff --git a/crates/cli/src/bin/wasm-bindgen-test-runner/server.rs b/crates/cli/src/bin/wasm-bindgen-test-runner/server.rs
new file mode 100644
index 00000000..419a3f7a
--- /dev/null
+++ b/crates/cli/src/bin/wasm-bindgen-test-runner/server.rs
@@ -0,0 +1,107 @@
+use std::ffi::OsString;
+use std::path::Path;
+use std::fs;
+
+use failure::{ResultExt, Error};
+use rouille::{self, Response, Request};
+use wasm_bindgen_cli_support::wasm2es6js::Config;
+
+pub fn spawn(module: &str, tmpdir: &Path, args: &[OsString], tests: &[String])
+ -> Result<(), Error>
+{
+ let mut js_to_execute = format!(r#"
+ import {{ Context }} from './{0}';
+ import * as wasm from './{0}_bg';
+
+ document.getElementById('output').innerHTML = "Loading wasm module...";
+
+ async function main(test) {{
+ await wasm.booted;
+ const cx = Context.new();
+ window.global_cx = cx;
+
+ // Forward runtime arguments. These arguments are also arguments to the
+ // `wasm-bindgen-test-runner` which forwards them to node which we
+ // forward to the test harness. this is basically only used for test
+ // filters for now.
+ cx.args({1:?});
+
+ cx.run(test.map(s => wasm[s]));
+ }}
+
+ const tests = [];
+ "#,
+ module, args,
+ );
+ for test in tests {
+ js_to_execute.push_str(&format!("tests.push('{}');\n", test));
+ }
+ js_to_execute.push_str("main(tests);\n");
+
+ let js_path = tmpdir.join("run.js");
+ fs::write(&js_path, js_to_execute)
+ .context("failed to write JS file")?;
+
+ // No browser today supports a wasm file as ES modules natively, so we need
+ // to shim it. Use `wasm2es6js` here to fetch an appropriate URL and look
+ // like an ES module with the wasm module under the hood.
+ let wasm_name = format!("{}_bg.wasm", module);
+ let wasm = fs::read(tmpdir.join(&wasm_name))?;
+ let output = Config::new()
+ .fetch(Some(format!("/{}", wasm_name)))
+ .generate(&wasm)?;
+ let js = output.js()?;
+ fs::write(tmpdir.join(format!("{}_bg.js", module)), js)
+ .context("failed to write JS file")?;
+
+ // For now, always run forever on this port. We may update this later!
+ println!("Listening on port 8000");
+ let tmpdir = tmpdir.to_path_buf();
+ rouille::start_server("localhost:8000", move |request| {
+ // The root path gets our canned `index.html`
+ if request.url() == "/" {
+ return Response::from_data("text/html", include_str!("index.html"));
+ }
+
+ // Otherwise we need to find the asset here. It may either be in our
+ // temporary directory (generated files) or in the main directory
+ // (relative import paths to JS). Try to find both locations.
+ let mut response = try_asset(&request, &tmpdir);
+ if !response.is_success() {
+ response = try_asset(&request, ".".as_ref());
+ }
+ // Make sure browsers don't cache anything (Chrome appeared to with this
+ // header?)
+ response.headers.retain(|(k, _)| k != "Cache-Control");
+ return response
+ });
+
+ fn try_asset(request: &Request, dir: &Path) -> Response {
+ let response = rouille::match_assets(request, dir);
+ if response.is_success() {
+ return response
+ }
+
+ // When a browser is doing ES imports it's using the directives we
+ // write in the code that *don't* have file extensions (aka we say `from
+ // 'foo'` instead of `from 'foo.js'`. Fixup those paths here to see if a
+ // `js` file exists.
+ if let Some(part) = request.url().split('/').last() {
+ if !part.contains(".") {
+ let new_request = Request::fake_http(
+ request.method(),
+ format!("{}.js", request.url()),
+ request.headers()
+ .map(|(a, b)| (a.to_string(), b.to_string()))
+ .collect(),
+ Vec::new(),
+ );
+ let response = rouille::match_assets(&new_request, dir);
+ if response.is_success() {
+ return response
+ }
+ }
+ }
+ response
+ }
+}