diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d887cc699b7..9cdc7359f32 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -72,6 +72,7 @@ jobs: - run: cargo clippy --no-deps --all-features -p wasm-bindgen-macro-support -- -D warnings - run: cargo clippy --no-deps --all-features -p wasm-bindgen-multi-value-xform -- -D warnings - run: cargo clippy --no-deps --all-features -p wasm-bindgen-shared -- -D warnings + - run: cargo clippy --no-deps --all-features -p wasm-bindgen-test-shared -- -D warnings - run: cargo clippy --no-deps --all-features --target wasm32-unknown-unknown -p wasm-bindgen-test -- -D warnings - run: cargo clippy --no-deps --all-features -p wasm-bindgen-test-macro -- -D warnings - run: cargo clippy --no-deps --all-features -p wasm-bindgen-threads-xform -- -D warnings @@ -275,6 +276,7 @@ jobs: - run: cargo test -p wasm-bindgen-wasm-interpreter - run: cargo test -p wasm-bindgen-futures - run: cargo test -p wasm-bindgen-shared + - run: cargo test -p wasm-bindgen-test-shared test_with_geckodriver: strategy: diff --git a/CHANGELOG.md b/CHANGELOG.md index f00eaaa3cb8..86c510beb34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,9 @@ * `console.*()` calls in tests are now always intercepted by default. To show them use `--nocapture`. When shown they are always printed in-place instead of after test results, analogous to `cargo test`. [#4356](https://github.com/rustwasm/wasm-bindgen/pull/4356) +* Replace `WASM_BINDGEN_UNSTABLE_TEST_PROFRAW_OUT` and `WASM_BINDGEN_UNSTABLE_TEST_PROFRAW_PREFIX` with parsing `LLVM_PROFILE_FILE` analogous to Rust test coverage. + [#4367](https://github.com/rustwasm/wasm-bindgen/pull/4367) + ### Fixed - Fixed using [JavaScript keyword](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#keywords) as identifiers not being handled correctly. diff --git a/Cargo.toml b/Cargo.toml index 57536ab1ce9..6823126426e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -135,6 +135,7 @@ resolver = "2" [patch.crates-io] js-sys = { path = 'crates/js-sys' } +minicov = { git = "https://github.com/daxpedda/minicov", branch = "module-signature" } wasm-bindgen = { path = '.' } wasm-bindgen-futures = { path = 'crates/futures' } web-sys = { path = 'crates/web-sys' } diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 870ca4ef325..9999c931ee3 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -35,6 +35,7 @@ ureq = { version = "2.7", default-features = false, features = ["brotli", "gzip" walrus = "0.23" wasm-bindgen-cli-support = { path = "../cli-support", version = "=0.2.99" } wasm-bindgen-shared = { path = "../shared", version = "=0.2.99" } +wasm-bindgen-test-shared = { path = "../test-shared", version = "=0.2.99" } [dev-dependencies] assert_cmd = "2" diff --git a/crates/cli/src/bin/wasm-bindgen-test-runner/main.rs b/crates/cli/src/bin/wasm-bindgen-test-runner/main.rs index a16e860fb12..d1dfdafce0b 100644 --- a/crates/cli/src/bin/wasm-bindgen-test-runner/main.rs +++ b/crates/cli/src/bin/wasm-bindgen-test-runner/main.rs @@ -16,7 +16,6 @@ use clap::Parser; use clap::ValueEnum; use std::env; use std::fs; -use std::path::Path; use std::path::PathBuf; use std::thread; use wasm_bindgen_cli_support::Bindgen; @@ -102,12 +101,6 @@ fn main() -> anyhow::Result<()> { let shell = shell::Shell::new(); - let file_name = cli - .file - .file_name() - .map(Path::new) - .context("file to test is not a valid file, can't extract file name")?; - // 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. @@ -276,8 +269,6 @@ fn main() -> anyhow::Result<()> { b.split_linked_modules(true); } - let coverage = coverage_args(file_name); - b.debug(debug) .input_module(module, wasm) .keep_debug(false) @@ -288,7 +279,7 @@ fn main() -> anyhow::Result<()> { match test_mode { TestMode::Node { no_modules } => { - node::execute(module, tmpdir.path(), cli, &tests, !no_modules, coverage)? + node::execute(module, tmpdir.path(), cli, &tests, !no_modules)? } TestMode::Deno => deno::execute(module, tmpdir.path(), cli, &tests)?, TestMode::Browser { .. } @@ -310,7 +301,6 @@ fn main() -> anyhow::Result<()> { &tests, test_mode, std::env::var("WASM_BINDGEN_TEST_NO_ORIGIN_ISOLATION").is_err(), - coverage, ) .context("failed to spawn server")?; let addr = srv.server_addr(); @@ -379,28 +369,6 @@ impl TestMode { } } -fn coverage_args(file_name: &Path) -> PathBuf { - fn generated(file_name: &Path, prefix: &str) -> String { - let res = format!("{prefix}{}.profraw", file_name.display()); - res - } - - let prefix = env::var_os("WASM_BINDGEN_UNSTABLE_TEST_PROFRAW_PREFIX") - .map(|s| s.to_str().unwrap().to_string()) - .unwrap_or_default(); - - match env::var_os("WASM_BINDGEN_UNSTABLE_TEST_PROFRAW_OUT") { - Some(s) => { - let mut buf = PathBuf::from(s); - if buf.is_dir() { - buf.push(generated(file_name, &prefix)); - } - buf - } - None => PathBuf::from(generated(file_name, &prefix)), - } -} - /// Possible values for the `--format` option. #[derive(Debug, Clone, Copy, ValueEnum)] enum FormatSetting { diff --git a/crates/cli/src/bin/wasm-bindgen-test-runner/node.rs b/crates/cli/src/bin/wasm-bindgen-test-runner/node.rs index 68249ad7da9..1282176a909 100644 --- a/crates/cli/src/bin/wasm-bindgen-test-runner/node.rs +++ b/crates/cli/src/bin/wasm-bindgen-test-runner/node.rs @@ -1,6 +1,7 @@ use std::env; use std::fs; -use std::path::{Path, PathBuf}; +use std::path::Path; +use std::process; use std::process::Command; use anyhow::{Context, Error}; @@ -49,8 +50,18 @@ pub fn execute( cli: Cli, tests: &[String], module_format: bool, - coverage: PathBuf, ) -> Result<(), Error> { + let coverage_env = if let Ok(env) = env::var("LLVM_PROFILE_FILE") { + &format!("\"{env}\"") + } else { + "undefined" + }; + let coverage_pid = process::id(); + let coverage_temp_dir = env::temp_dir() + .to_str() + .map(String::from) + .context("failed to parse path to temporary directory")?; + let mut js_to_execute = format!( r#" {exit}; @@ -68,8 +79,10 @@ pub fn execute( const ok = await cx.run(tests.map(n => wasm.__wasm[n])); const coverage = wasm.__wbgtest_cov_dump(); - if (coverage !== undefined) - await fs.writeFile('{coverage}', coverage); + if (coverage !== undefined) {{ + const path = wasm.__wbgtest_coverage_path({coverage_env}, {coverage_pid}, {coverage_temp_dir:?}, wasm.__wbgtest_module_signature()); + await fs.writeFile(path, coverage); + }} if (!ok) exit(1); @@ -92,7 +105,6 @@ pub fn execute( } else { r"import fs from 'node:fs/promises'".to_string() }, - coverage = coverage.display(), nocapture = cli.nocapture.clone(), console_override = SHARED_SETUP, args = cli.into_args(), diff --git a/crates/cli/src/bin/wasm-bindgen-test-runner/server.rs b/crates/cli/src/bin/wasm-bindgen-test-runner/server.rs index 88999940e52..ff1ec9e9a6e 100644 --- a/crates/cli/src/bin/wasm-bindgen-test-runner/server.rs +++ b/crates/cli/src/bin/wasm-bindgen-test-runner/server.rs @@ -1,8 +1,8 @@ use std::borrow::Cow; -use std::fs; use std::io::{Read, Write}; use std::net::SocketAddr; -use std::path::{Path, PathBuf}; +use std::path::Path; +use std::{env, fs, process}; use anyhow::{anyhow, Context, Error}; use rouille::{Request, Response, Server}; @@ -18,14 +18,14 @@ pub(crate) fn spawn( tests: &[String], test_mode: TestMode, isolate_origin: bool, - coverage: PathBuf, ) -> Result Response + Send + Sync>, Error> { let mut js_to_execute = String::new(); let cov_import = if test_mode.no_modules() { - "let __wbgtest_cov_dump = wasm_bindgen.__wbgtest_cov_dump;" + "let __wbgtest_cov_dump = wasm_bindgen.__wbgtest_cov_dump;\n\ + let __wbgtest_module_signature = wasm_bindgen.__wbgtest_module_signature;" } else { - "__wbgtest_cov_dump," + "__wbgtest_cov_dump,__wbgtest_module_signature," }; let cov_dump = r#" // Dump the coverage data collected during the tests @@ -34,6 +34,9 @@ pub(crate) fn spawn( if (coverage !== undefined) { await fetch("/__wasm_bindgen/coverage", { method: "POST", + headers: { + "Module-Signature": __wbgtest_module_signature(), + }, body: coverage }); } @@ -326,7 +329,13 @@ pub(crate) fn spawn( return response; } else if request.url() == "/__wasm_bindgen/coverage" { - return if let Err(e) = handle_coverage_dump(&coverage, request) { + let module_signature = request + .header("Module-Signature") + .expect("sent coverage data without module signature") + .parse() + .expect("sent invalid module signature"); + + return if let Err(e) = handle_coverage_dump(module_signature, request) { let s: &str = &format!("Failed to dump coverage: {e}"); log::error!("{s}"); let mut ret = Response::text(s); @@ -386,9 +395,17 @@ pub(crate) fn spawn( } } -fn handle_coverage_dump(profraw_path: &Path, request: &Request) -> anyhow::Result<()> { +fn handle_coverage_dump(module_signature: u64, request: &Request) -> anyhow::Result<()> { // This is run after all tests are done and dumps the data received in the request // into a single profraw file + let profraw_path = wasm_bindgen_test_shared::coverage_path( + env::var("LLVM_PROFILE_FILE").ok().as_deref(), + process::id(), + env::temp_dir() + .to_str() + .context("failed to parse path to temporary directory")?, + module_signature, + ); let mut profraw = std::fs::File::create(profraw_path)?; let mut data = Vec::new(); if let Some(mut r_data) = request.data() { diff --git a/crates/test-shared/Cargo.toml b/crates/test-shared/Cargo.toml new file mode 100644 index 00000000000..5197fc6ff7f --- /dev/null +++ b/crates/test-shared/Cargo.toml @@ -0,0 +1,18 @@ +[package] +authors = ["The wasm-bindgen Developers"] +description = """ +Shared support between wasm-bindgen-test and wasm-bindgen-test-runner, an internal +dependency. +""" +documentation = "https://docs.rs/wasm-bindgen-test-shared" +edition = "2021" +homepage = "https://rustwasm.github.io/wasm-bindgen/" +include = ["/LICENSE-*", "/src"] +license = "MIT OR Apache-2.0" +name = "wasm-bindgen-test-shared" +repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/test-shared" +rust-version = "1.57" +version = "0.2.99" + +[lints] +workspace = true diff --git a/crates/test-shared/LICENSE-APACHE b/crates/test-shared/LICENSE-APACHE new file mode 120000 index 00000000000..1cd601d0a3a --- /dev/null +++ b/crates/test-shared/LICENSE-APACHE @@ -0,0 +1 @@ +../../LICENSE-APACHE \ No newline at end of file diff --git a/crates/test-shared/LICENSE-MIT b/crates/test-shared/LICENSE-MIT new file mode 120000 index 00000000000..b2cfbdc7b0b --- /dev/null +++ b/crates/test-shared/LICENSE-MIT @@ -0,0 +1 @@ +../../LICENSE-MIT \ No newline at end of file diff --git a/crates/test-shared/src/lib.rs b/crates/test-shared/src/lib.rs new file mode 100644 index 00000000000..a1423287e99 --- /dev/null +++ b/crates/test-shared/src/lib.rs @@ -0,0 +1,80 @@ +#![doc(html_root_url = "https://docs.rs/wasm-bindgen-test-shared/0.2")] +#![no_std] + +extern crate alloc; + +use alloc::string::{String, ToString}; + +pub fn coverage_path(env: Option<&str>, pid: u32, tmpdir: &str, module_signature: u64) -> String { + let env = env.unwrap_or("default_%m_%p.profraw"); + + let mut path = String::new(); + let mut chars = env.chars().enumerate().peekable(); + + while let Some((index, char)) = chars.next() { + if char != '%' { + path.push(char); + continue; + } + + if chars.next_if(|(_, c)| *c == 'p').is_some() { + path.push_str(&pid.to_string()) + } else if chars.next_if(|(_, c)| *c == 'h').is_some() { + path.push_str("wbgt") + } else if chars.next_if(|(_, c)| *c == 't').is_some() { + path.push_str(tmpdir) + } else { + let mut last_index = index; + + loop { + if let Some((index, _)) = chars.next_if(|(_, c)| c.is_ascii_digit()) { + last_index = index; + } else if chars.next_if(|(_, c)| *c == 'm').is_some() { + path.push_str(&module_signature.to_string()); + path.push_str("_0"); + break; + } else { + path.push_str(&env[index..=last_index]); + break; + } + } + } + } + + path +} + +#[test] +fn test() { + fn asssert<'a>(env: impl Into>, result: &str) { + assert_eq!(coverage_path(env.into(), 123, "tmp", 456), result); + } + + asssert(None, "default_456_0_123.profraw"); + asssert("", ""); + asssert("%p", "123"); + asssert("%h", "wbgt"); + asssert("%t", "tmp"); + asssert("%m", "456_0"); + asssert("%0123456789m", "456_0"); + asssert("%", "%"); + asssert("%%", "%%"); + asssert("%a", "%a"); + asssert("%0123456789", "%0123456789"); + asssert("%0123456789p", "%0123456789p"); + asssert("%%p", "%123"); + asssert("%%%p", "%%123"); + asssert("%a%p", "%a123"); + asssert("%0123456789%p", "%0123456789123"); + asssert("%p%", "123%"); + asssert("%p%%", "123%%"); + asssert("%p%a", "123%a"); + asssert("%p%0123456789", "123%0123456789"); + asssert("%p%0123456789p", "123%0123456789p"); + asssert("%m%a", "456_0%a"); + asssert("%m%0123456789", "456_0%0123456789"); + asssert("%m%0123456789p", "456_0%0123456789p"); + asssert("%0123456789m%a", "456_0%a"); + asssert("%0123456789m%0123456789", "456_0%0123456789"); + asssert("%0123456789m%0123456789p", "456_0%0123456789p"); +} diff --git a/crates/test/Cargo.toml b/crates/test/Cargo.toml index 004e990cac7..fb5f3e8ff47 100644 --- a/crates/test/Cargo.toml +++ b/crates/test/Cargo.toml @@ -19,6 +19,7 @@ js-sys = { path = '../js-sys', version = '=0.3.76', default-features = false } wasm-bindgen = { path = '../..', version = '=0.2.99', default-features = false } wasm-bindgen-futures = { path = '../futures', version = '=0.4.49', default-features = false } wasm-bindgen-test-macro = { path = '../test-macro', version = '=0.3.49' } +wasm-bindgen-test-shared = { path = "../test-shared", version = "=0.2.99" } [target.'cfg(all(target_arch = "wasm32", wasm_bindgen_unstable_test_coverage))'.dependencies] minicov = "0.3" diff --git a/crates/test/src/coverage.rs b/crates/test/src/coverage.rs index 2fe38443ccf..dc24c41375c 100644 --- a/crates/test/src/coverage.rs +++ b/crates/test/src/coverage.rs @@ -23,3 +23,15 @@ pub fn __wbgtest_cov_dump() -> Option> { pub fn __wbgtest_cov_dump() -> Option> { None } + +#[cfg(wasm_bindgen_unstable_test_coverage)] +#[wasm_bindgen] +pub fn __wbgtest_module_signature() -> Option { + Some(minicov::module_signature()) +} + +#[cfg(not(wasm_bindgen_unstable_test_coverage))] +#[wasm_bindgen] +pub fn __wbgtest_module_signature() -> Option { + None +} diff --git a/crates/test/src/rt/node.rs b/crates/test/src/rt/node.rs index 40e5276b3dd..bad6990b6ac 100644 --- a/crates/test/src/rt/node.rs +++ b/crates/test/src/rt/node.rs @@ -44,3 +44,14 @@ impl super::Formatter for Node { NodeError::from(err.clone()).stack() } } + +/// Path to use for coverage data. +#[wasm_bindgen] +pub fn __wbgtest_coverage_path(env: Option, pid: u32, temp_dir: &str, module_signature: u64) -> String { + wasm_bindgen_test_shared::coverage_path( + env.as_deref(), + pid, + temp_dir, + module_signature, + ) +} diff --git a/guide/src/wasm-bindgen-test/coverage.md b/guide/src/wasm-bindgen-test/coverage.md index 4bd3e6722ce..6672cca85d3 100644 --- a/guide/src/wasm-bindgen-test/coverage.md +++ b/guide/src/wasm-bindgen-test/coverage.md @@ -21,12 +21,9 @@ Due to the current limitation of `llvm-cov`, we can't collect profiling symbols ### Arguments to the test runner -The following environment variables can be used to control the coverage output when [executing the test runner][1]: +Like with Rust test coverage, you can use the [`LLVM_PROFILE_FILE`][1] environment variable to specify a path for the generated `.profraw` files. -- `WASM_BINDGEN_UNSTABLE_TEST_PROFRAW_OUT` to control the file name of the profraw or the directory in which it is placed. It might be necessary to provide the full path if e.g. running tests in a workspace. -- `WASM_BINDGEN_UNSTABLE_TEST_PROFRAW_PREFIX` to add a custom prefix to the profraw files. This can be useful if you're running the tests automatically in succession. - -[1]: usage.html#appendix-using-wasm-bindgen-test-without-wasm-pack +[1]: https://releases.llvm.org/19.1.0/tools/clang/docs/SourceBasedCodeCoverage.html#running-the-instrumented-program ### Target features diff --git a/publish.rs b/publish.rs index e36f3ff2a43..a955a1343c8 100644 --- a/publish.rs +++ b/publish.rs @@ -23,6 +23,7 @@ const CRATES_TO_PUBLISH: &[&str] = &[ "wasm-bindgen-macro-support", "wasm-bindgen-macro", "wasm-bindgen-test-macro", + "wasm-bindgen-test-shared", "wasm-bindgen-test", "wasm-bindgen-wasm-interpreter", "wasm-bindgen-wasm-conventions",