use super::logger::null_logger; use std::env; use std::fs; use std::mem::ManuallyDrop; use std::path::{Path, PathBuf}; use std::process::{Command, Stdio}; use std::sync::{MutexGuard, Once, ONCE_INIT}; use std::thread; use tempfile::TempDir; use wasm_pack; use wasm_pack::binaries::Cache; /// A test fixture in a temporary directory. pub struct Fixture { // NB: we wrap the fixture's tempdir in a `ManuallyDrop` so that if a test // fails, its directory isn't deleted, and we have a chance to manually // inspect its state and figure out what is going on. pub dir: ManuallyDrop, pub path: PathBuf, } impl Fixture { /// Create a new test fixture in a temporary directory. pub fn new() -> Fixture { // Make sure that all fixtures end up sharing a target dir, and we don't // recompile wasm-bindgen and friends many times over. static SET_TARGET_DIR: Once = ONCE_INIT; let target_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("target"); SET_TARGET_DIR.call_once(|| { env::set_var("CARGO_TARGET_DIR", &target_dir); }); let root = target_dir.join("t"); fs::create_dir_all(&root).unwrap(); let dir = TempDir::new_in(&root).unwrap(); let path = dir.path().join("wasm-pack"); eprintln!("Created fixture at {}", path.display()); Fixture { dir: ManuallyDrop::new(dir), path, } } /// Create a file within this fixture. /// /// `path` should be a relative path to the file (relative within this /// fixture's path). /// /// The `contents` are written to the file. pub fn file, C: AsRef<[u8]>>(&self, path: P, contents: C) -> &Self { assert!(path.as_ref().is_relative()); let path = self.path.join(path); if let Some(parent) = path.parent() { fs::create_dir_all(parent).unwrap(); } fs::write(path, contents).unwrap(); self } /// Add a generic `README.md` file to the fixture. pub fn readme(&self) -> &Self { self.file( "README.md", r#" # Fixture! > an example rust -> wasm project "#, ) } /// Add a `Cargo.toml` with a correctly configured `wasm-bindgen` /// dependency, `wasm-bindgen-test` dev-dependency, and `crate-type = /// ["cdylib"]`. /// /// `name` is the crate's name. pub fn cargo_toml(&self, name: &str) -> &Self { self.file( "Cargo.toml", &format!( r#" [package] authors = ["The wasm-pack developers"] description = "so awesome rust+wasm package" license = "WTFPL" name = "{}" repository = "https://github.com/rustwasm/wasm-pack.git" version = "0.1.0" [lib] crate-type = ["cdylib"] [dependencies] wasm-bindgen = "=0.2.21" [dev-dependencies] wasm-bindgen-test = "=0.2.21" "#, name ), ) } /// Add a `src/lib.rs` file that contains a "hello world" program. pub fn hello_world_src_lib(&self) -> &Self { self.file( "src/lib.rs", r#" extern crate wasm_bindgen; use wasm_bindgen::prelude::*; // Import the `window.alert` function from the Web. #[wasm_bindgen] extern { fn alert(s: &str); } // Export a `greet` function from Rust to JavaScript, that alerts a // hello message. #[wasm_bindgen] pub fn greet(name: &str) { alert(&format!("Hello, {}!", name)); } "#, ) } /// Install a local wasm-bindgen for this fixture. /// /// Takes care not to re-install for every fixture, but only the one time /// for the whole test suite. pub fn install_local_wasm_bindgen(&self) -> PathBuf { static INSTALL_WASM_BINDGEN: Once = ONCE_INIT; let cache = self.cache(); let version = "0.2.21"; let log = &null_logger(); let download = || { if let Ok(download) = wasm_pack::bindgen::download_prebuilt_wasm_bindgen(&cache, version, true) { return Ok(download); } wasm_pack::bindgen::cargo_install_wasm_bindgen(log, &cache, version, true) }; // Only one thread can perform the actual download, and then afterwards // everything will hit the cache so we can run the same path. INSTALL_WASM_BINDGEN.call_once(|| { download().unwrap(); }); download().unwrap().binary("wasm-bindgen") } /// Download `geckodriver` and return its path. /// /// Takes care to ensure that only one `geckodriver` is downloaded for the whole /// test suite. pub fn install_local_geckodriver(&self) -> PathBuf { static FETCH_GECKODRIVER: Once = ONCE_INIT; let cache = self.cache(); // like above for synchronization FETCH_GECKODRIVER.call_once(|| { wasm_pack::test::webdriver::install_geckodriver(&cache, true).unwrap(); }); wasm_pack::test::webdriver::install_geckodriver(&cache, true).unwrap() } /// Download `chromedriver` and return its path. /// /// Takes care to ensure that only one `chromedriver` is downloaded for the whole /// test suite. pub fn install_local_chromedriver(&self) -> PathBuf { static FETCH_CHROMEDRIVER: Once = ONCE_INIT; let cache = self.cache(); // like above for synchronization FETCH_CHROMEDRIVER.call_once(|| { wasm_pack::test::webdriver::install_chromedriver(&cache, true).unwrap(); }); wasm_pack::test::webdriver::install_chromedriver(&cache, true).unwrap() } pub fn cache(&self) -> Cache { let target_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("target"); Cache::at(&target_dir.join("test_cache")) } /// The `step_install_wasm_bindgen` and `step_run_wasm_bindgen` steps only /// occur after the `step_build_wasm` step. In order to read the lockfile /// in the test fixture's temporary directory, we should first build the /// crate, targeting `wasm32-unknown-unknown`. pub fn cargo_check(&self) -> &Self { Command::new("cargo") .current_dir(&self.path) .arg("check") .arg("--target") .arg("wasm32-unknown-unknown") .stdout(Stdio::null()) .stderr(Stdio::null()) .status() .unwrap(); self } pub fn run(&self, cmd: wasm_pack::command::Command) -> Result<(), failure::Error> { let logger = wasm_pack::logger::new(&cmd, 3)?; match cmd { wasm_pack::command::Command::Test(cmd) => { let _lock = self.lock(); let mut test = wasm_pack::command::test::Test::try_from_opts(cmd)?; test.set_cache(self.cache()); test.run(&logger) } wasm_pack::command::Command::Build(cmd) => { let mut build = wasm_pack::command::build::Build::try_from_opts(cmd)?; build.set_cache(self.cache()); build.run(&logger) } _ => unreachable!(), } } pub fn lock(&self) -> MutexGuard<'static, ()> { use std::sync::Mutex; lazy_static! { static ref ONE_TEST_AT_A_TIME: Mutex<()> = Mutex::new(()); } ONE_TEST_AT_A_TIME.lock().unwrap_or_else(|e| e.into_inner()) } } impl Drop for Fixture { fn drop(&mut self) { if !thread::panicking() { unsafe { ManuallyDrop::drop(&mut self.dir) } } } } pub fn bad_cargo_toml() -> Fixture { let fixture = Fixture::new(); fixture.readme().hello_world_src_lib().file( "Cargo.toml", r#" [package] name = "bad-cargo-toml" version = "0.1.0" authors = ["The wasm-pack developers"] [lib] crate-type = ["foo"] [dependencies] # Note: no wasm-bindgen dependency! "#, ); fixture } pub fn js_hello_world() -> Fixture { let fixture = Fixture::new(); fixture .readme() .cargo_toml("js-hello-world") .hello_world_src_lib(); fixture } pub fn no_cdylib() -> Fixture { let fixture = Fixture::new(); fixture.readme().hello_world_src_lib().file( "Cargo.toml", r#" [package] authors = ["The wasm-pack developers"] description = "so awesome rust+wasm package" license = "WTFPL" name = "foo" repository = "https://github.com/rustwasm/wasm-pack.git" version = "0.1.0" # [lib] # crate-type = ["cdylib"] [dependencies] wasm-bindgen = "=0.2.21" [dev-dependencies] wasm-bindgen-test = "=0.2.21" "#, ); fixture } pub fn not_a_crate() -> Fixture { let fixture = Fixture::new(); fixture.file("README.md", "This is not a Rust crate!"); fixture } pub fn serde_feature() -> Fixture { let fixture = Fixture::new(); fixture.readme().hello_world_src_lib().file( "Cargo.toml", r#" [package] name = "serde-serialize" version = "0.1.0" authors = ["The wasm-pack developers"] [lib] crate-type = ["cdylib"] [dependencies.wasm-bindgen] version = "^0.2" features = ["serde-serialize"] "#, ); fixture } pub fn wbg_test_diff_versions() -> Fixture { let fixture = Fixture::new(); fixture .readme() .file( "Cargo.toml", r#" [package] name = "wbg-test-diff-versions" version = "0.1.0" authors = ["The wasm-pack developers"] [lib] crate-type = ["cdylib", "rlib"] [dependencies] # We depend on wasm-bindgen 0.2.21 wasm-bindgen = "=0.2.21" [dev-dependencies] # And we depend on wasm-bindgen-test 0.2.19. This should still # work, and we should end up with `wasm-bindgen` at 0.2.21 and # wasm-bindgen-test at 0.2.19, and everything should still work. wasm-bindgen-test = "0.2.19" "#, ) .file( "src/lib.rs", r#" extern crate wasm_bindgen; use wasm_bindgen::prelude::*; #[wasm_bindgen] pub fn one() -> u32 { 1 } "#, ) .file( "tests/node.rs", r#" extern crate wbg_test_diff_versions; extern crate wasm_bindgen_test; use wasm_bindgen_test::*; #[wasm_bindgen_test] fn pass() { assert_eq!(wbg_test_diff_versions::one(), 1); } "#, ); fixture } pub fn wbg_test_browser() -> Fixture { let fixture = Fixture::new(); fixture .readme() .cargo_toml("wbg-test-browser") .hello_world_src_lib() .file( "tests/browser.rs", r#" extern crate wasm_bindgen_test; use wasm_bindgen_test::*; wasm_bindgen_test_configure!(run_in_browser); #[wasm_bindgen_test] fn pass() { assert_eq!(1, 1); } "#, ); fixture } pub fn wbg_test_fail() -> Fixture { let fixture = Fixture::new(); fixture .readme() .cargo_toml("wbg-test-fail") .hello_world_src_lib() .file( "tests/node.rs", r#" extern crate wasm_bindgen_test; use wasm_bindgen_test::*; #[wasm_bindgen_test] fn pass() { assert_eq!(1, 2); } "#, ); fixture } pub fn wbg_test_node() -> Fixture { let fixture = Fixture::new(); fixture .readme() .cargo_toml("wbg-test-node") .hello_world_src_lib() .file( "tests/node.rs", r#" extern crate wasm_bindgen_test; use wasm_bindgen_test::*; #[wasm_bindgen_test] fn pass() { assert_eq!(1, 1); } "#, ); fixture } pub fn transitive_dependencies() -> Fixture { fn project_main_fixture(fixture: &mut Fixture) { fixture.file(PathBuf::from("main/README"), "# Main Fixture\n"); fixture.file( PathBuf::from("main/Cargo.toml"), r#" [package] authors = ["The wasm-pack developers"] description = "so awesome rust+wasm package" license = "WTFPL" name = "main_project" repository = "https://github.com/rustwasm/wasm-pack.git" version = "0.1.0" [lib] crate-type = ["cdylib"] [dependencies] wasm-bindgen = "=0.2.21" project_a = { path = "../project_a" } project_b = { path = "../project_b" } [dev-dependencies] wasm-bindgen-test = "=0.2.21" "#, ); fixture.file( PathBuf::from("main/src/lib.rs"), r#" extern crate wasm_bindgen; use wasm_bindgen::prelude::*; // Import the `window.alert` function from the Web. #[wasm_bindgen] extern { fn alert(s: &str); } // Export a `greet` function from Rust to JavaScript, that alerts a // hello message. #[wasm_bindgen] pub fn greet(name: &str) { alert(&format!("Hello, {}!", name)); } "#, ); } fn project_a_fixture(fixture: &mut Fixture) { fixture.file( PathBuf::from("project_a/README"), "# Project Alpha Fixture\n", ); fixture.file( PathBuf::from("project_a/Cargo.toml"), r#" [package] authors = ["The wasm-pack developers"] description = "so awesome rust+wasm package" license = "WTFPL" name = "project_a" repository = "https://github.com/rustwasm/wasm-pack.git" version = "0.1.0" [lib] crate-type = ["cdylib"] [dependencies] wasm-bindgen = "=0.2.21" project_b = { path = "../project_b" } [dev-dependencies] wasm-bindgen-test = "=0.2.21" "#, ); fixture.file( PathBuf::from("project_a/src/lib.rs"), r#" extern crate wasm_bindgen; // extern crate project_b; use wasm_bindgen::prelude::*; // Import the `window.alert` function from the Web. #[wasm_bindgen] extern { fn alert(s: &str); } // Export a `greet` function from Rust to JavaScript, that alerts a // hello message. #[wasm_bindgen] pub fn greet(name: &str) { alert(&format!("Hello, {}!", name)); } "#, ); } fn project_b_fixture(fixture: &mut Fixture) { fixture.file( PathBuf::from("project_b/README"), "# Project Beta Fixture\n", ); fixture.file( PathBuf::from("project_b/Cargo.toml"), r#" [package] authors = ["The wasm-pack developers"] description = "so awesome rust+wasm package" license = "WTFPL" name = "project_b" repository = "https://github.com/rustwasm/wasm-pack.git" version = "0.1.0" [lib] crate-type = ["cdylib"] [dependencies] wasm-bindgen = "=0.2.21" [dev-dependencies] wasm-bindgen-test = "=0.2.21" "#, ); fixture.file( PathBuf::from("project_b/src/lib.rs"), r#" extern crate wasm_bindgen; use wasm_bindgen::prelude::*; // Import the `window.alert` function from the Web. #[wasm_bindgen] extern { fn alert(s: &str); } // Export a `greet` function from Rust to JavaScript, that alerts a // hello message. #[wasm_bindgen] pub fn greet(name: &str) { alert(&format!("Hello, {}!", name)); } "#, ); } let mut fixture = Fixture::new(); project_b_fixture(&mut fixture); project_a_fixture(&mut fixture); project_main_fixture(&mut fixture); fixture }