Skip to content

Commit eeb64bf

Browse files
author
Ari Breitkreuz
committed
Implement coverage output
1 parent c4b1585 commit eeb64bf

File tree

8 files changed

+234
-9
lines changed

8 files changed

+234
-9
lines changed

crates/cli/src/bin/wasm-bindgen-test-runner/deno.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
use std::ffi::OsString;
21
use std::fs;
32
use std::path::Path;
43
use std::process::Command;
4+
use std::{ffi::OsString, path::PathBuf};
55

66
use anyhow::{Context, Error};
77

@@ -12,12 +12,37 @@ pub fn execute(
1212
tmpdir: &Path,
1313
args: &[OsString],
1414
tests: &[String],
15+
coverage: Option<PathBuf>,
1516
) -> Result<(), Error> {
17+
let (cov_fn, cov_dump) = if let Some(profraw_path) = coverage {
18+
(
19+
format!(
20+
r#"
21+
async function dumpCoverage() {{
22+
const fs = require('node:fs');
23+
const data = wasm.__wbgtest_cov_dump();
24+
fs.writeFile("{}", data, err => {{
25+
if (err) {{
26+
console.error(err);
27+
}}
28+
}})
29+
}}
30+
"#,
31+
profraw_path.display()
32+
),
33+
"await dumpCoverage();",
34+
)
35+
} else {
36+
(String::new(), "")
37+
};
38+
1639
let mut js_to_execute = format!(
1740
r#"import * as wasm from "./{0}.js";
1841
1942
{console_override}
2043
44+
{cov_fn}
45+
2146
// global.__wbg_test_invoke = f => f();
2247
2348
// Forward runtime arguments. These arguments are also arguments to the
@@ -27,6 +52,7 @@ pub fn execute(
2752
cx.args(Deno.args.slice(1));
2853
2954
const ok = await cx.run(tests.map(n => wasm.__wasm[n]));
55+
{cov_dump}
3056
if (!ok) Deno.exit(1);
3157
3258
const tests = [];

crates/cli/src/bin/wasm-bindgen-test-runner/main.rs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use anyhow::{anyhow, bail, Context};
1515
use log::error;
1616
use std::env;
1717
use std::fs;
18+
use std::path::Path;
1819
use std::path::PathBuf;
1920
use std::thread;
2021
use wasm_bindgen_cli_support::Bindgen;
@@ -187,6 +188,8 @@ fn main() -> anyhow::Result<()> {
187188
b.split_linked_modules(true);
188189
}
189190

191+
let coverage = coverage_args(&tmpdir);
192+
190193
b.debug(debug)
191194
.input_module(module, wasm)
192195
.keep_debug(false)
@@ -198,8 +201,8 @@ fn main() -> anyhow::Result<()> {
198201
let args: Vec<_> = args.collect();
199202

200203
match test_mode {
201-
TestMode::Node => node::execute(module, &tmpdir, &args, &tests)?,
202-
TestMode::Deno => deno::execute(module, &tmpdir, &args, &tests)?,
204+
TestMode::Node => node::execute(module, &tmpdir, &args, &tests, coverage)?,
205+
TestMode::Deno => deno::execute(module, &tmpdir, &args, &tests, coverage)?,
203206
TestMode::Browser { no_modules } | TestMode::Worker { no_modules } => {
204207
let srv = server::spawn(
205208
&if headless {
@@ -216,6 +219,7 @@ fn main() -> anyhow::Result<()> {
216219
&tests,
217220
no_modules,
218221
matches!(test_mode, TestMode::Worker { no_modules: _ }),
222+
coverage,
219223
)
220224
.context("failed to spawn server")?;
221225
let addr = srv.server_addr();
@@ -242,3 +246,29 @@ fn main() -> anyhow::Result<()> {
242246
}
243247
Ok(())
244248
}
249+
250+
fn coverage_args(tmpdir: &Path) -> Option<PathBuf> {
251+
fn generated(tmpdir: &Path) -> String {
252+
format!(
253+
"{}.profraw",
254+
tmpdir.file_name().and_then(|s| s.to_str()).unwrap()
255+
)
256+
}
257+
258+
// Profraw path is ignored if coverage isn't enabled
259+
env::var_os("WASM_BINDGEN_TEST_COVERAGE")?;
260+
log::warn!("Coverage support is still considered highly experimental!");
261+
// TODO coverage link to wasm-bindgen book documenting correct usage
262+
263+
match env::var_os("WASM_BINDGEN_TEST_PROFRAW_OUT") {
264+
Some(s) => {
265+
let mut buf = PathBuf::from(s);
266+
if buf.is_dir() {
267+
buf.push(generated(tmpdir));
268+
}
269+
buf
270+
}
271+
None => PathBuf::from(generated(tmpdir)),
272+
}
273+
.into()
274+
}

crates/cli/src/bin/wasm-bindgen-test-runner/node.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
use std::env;
21
use std::ffi::OsString;
32
use std::fs;
43
use std::path::Path;
54
use std::process::Command;
5+
use std::{env, path::PathBuf};
66

77
use anyhow::{Context, Error};
88

@@ -43,7 +43,30 @@ pub fn execute(
4343
tmpdir: &Path,
4444
args: &[OsString],
4545
tests: &[String],
46+
coverage: Option<PathBuf>,
4647
) -> Result<(), Error> {
48+
let (cov_fn, cov_dump) = if let Some(profraw_path) = coverage {
49+
(
50+
format!(
51+
r#"
52+
async function dumpCoverage() {{
53+
const fs = require('node:fs');
54+
const data = wasm.__wbgtest_cov_dump();
55+
fs.writeFile("{}", data, err => {{
56+
if (err) {{
57+
console.error(err);
58+
}}
59+
}})
60+
}}
61+
"#,
62+
profraw_path.display()
63+
),
64+
"await dumpCoverage();",
65+
)
66+
} else {
67+
(String::new(), "")
68+
};
69+
4770
let mut js_to_execute = format!(
4871
r#"
4972
const {{ exit }} = require('process');
@@ -53,6 +76,8 @@ pub fn execute(
5376
5477
global.__wbg_test_invoke = f => f();
5578
79+
{cov_fn}
80+
5681
async function main(tests) {{
5782
// Forward runtime arguments. These arguments are also arguments to the
5883
// `wasm-bindgen-test-runner` which forwards them to node which we
@@ -61,6 +86,7 @@ pub fn execute(
6186
cx.args(process.argv.slice(2));
6287
6388
const ok = await cx.run(tests.map(n => wasm.__wasm[n]));
89+
{cov_dump}
6490
if (!ok)
6591
exit(1);
6692
}}

crates/cli/src/bin/wasm-bindgen-test-runner/server.rs

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
use std::borrow::Cow;
2-
use std::ffi::OsString;
32
use std::fs;
3+
use std::io::{Read, Write};
44
use std::net::SocketAddr;
55
use std::path::Path;
6+
use std::{ffi::OsString, path::PathBuf};
67

78
use anyhow::{anyhow, Context, Error};
89
use rouille::{Request, Response, Server};
@@ -16,9 +17,30 @@ pub fn spawn(
1617
tests: &[String],
1718
no_module: bool,
1819
worker: bool,
20+
coverage: Option<PathBuf>,
1921
) -> Result<Server<impl Fn(&Request) -> Response + Send + Sync>, Error> {
2022
let mut js_to_execute = String::new();
2123

24+
let (cov_import, cov_dump) = if coverage.is_some() {
25+
(
26+
if no_module {
27+
"let __wbgtest_cov_dump = wasm_bindgen.__wbgtest_cov_dump;"
28+
} else {
29+
"__wbgtest_cov_dump,"
30+
},
31+
r#"
32+
// Dump the coverage data collected during the tests
33+
const coverage = __wbgtest_cov_dump();
34+
await fetch("/__coverage/dump", {
35+
method: "POST",
36+
body: coverage
37+
});
38+
"#,
39+
)
40+
} else {
41+
("", "")
42+
};
43+
2244
let wbg_import_script = if no_module {
2345
String::from(
2446
r#"
@@ -28,6 +50,7 @@ pub fn spawn(
2850
let __wbgtest_console_info = wasm_bindgen.__wbgtest_console_info;
2951
let __wbgtest_console_warn = wasm_bindgen.__wbgtest_console_warn;
3052
let __wbgtest_console_error = wasm_bindgen.__wbgtest_console_error;
53+
{cov_import}
3154
let init = wasm_bindgen;
3255
"#,
3356
)
@@ -41,6 +64,7 @@ pub fn spawn(
4164
__wbgtest_console_info,
4265
__wbgtest_console_warn,
4366
__wbgtest_console_error,
67+
{cov_import}
4468
default as init,
4569
}} from './{}';
4670
"#,
@@ -95,6 +119,7 @@ pub fn spawn(
95119
96120
cx.args({1:?});
97121
await cx.run(tests.map(s => wasm[s]));
122+
{cov_dump}
98123
}}
99124
100125
onmessage = function(e) {{
@@ -175,6 +200,7 @@ pub fn spawn(
175200
cx.args({1:?});
176201
177202
await cx.run(test.map(s => wasm[s]));
203+
{cov_dump}
178204
}}
179205
180206
const tests = [];
@@ -218,6 +244,25 @@ pub fn spawn(
218244
)
219245
};
220246
return set_isolate_origin_headers(Response::from_data("text/html", s));
247+
} else if request.url() == "/__coverage/dump" {
248+
let profraw_path = coverage.as_ref().expect(
249+
"Received coverage dump request but server wasn't set up to accept coverage",
250+
);
251+
// This is run after all tests are done and dumps the data received in the request
252+
// into a single profraw file
253+
let mut profraw =
254+
std::fs::File::create(profraw_path).expect("Couldn't create .profraw for coverage");
255+
let mut data = Vec::new();
256+
request
257+
.data()
258+
.expect("Expected coverage data in body")
259+
.read_to_end(&mut data)
260+
.expect("Failed to read message body");
261+
262+
profraw
263+
.write_all(&data)
264+
.expect("Couldn't dump coverage data to profraw");
265+
return Response::text("Coverage dumped");
221266
}
222267

223268
// Otherwise we need to find the asset here. It may either be in our

crates/test/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ repository = "https://github.com/rustwasm/wasm-bindgen"
88
edition = "2018"
99
rust-version = "1.57"
1010

11+
[features]
12+
default = []
13+
experimental = ["coverage"]
14+
15+
coverage = ["minicov"]
16+
1117
[dependencies]
1218
console_error_panic_hook = '0.1'
1319
js-sys = { path = '../js-sys', version = '0.3.67' }
@@ -16,6 +22,7 @@ wasm-bindgen = { path = '../..', version = '0.2.90' }
1622
wasm-bindgen-futures = { path = '../futures', version = '0.4.40' }
1723
wasm-bindgen-test-macro = { path = '../test-macro', version = '=0.3.40' }
1824
gg-alloc = { version = "1.0", optional = true }
25+
minicov = { version = "0.3", optional = true }
1926

2027
[lib]
2128
test = false

crates/test/src/coverage.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
use wasm_bindgen::prelude::wasm_bindgen;
2+
3+
#[cfg(feature = "coverage")]
4+
#[wasm_bindgen]
5+
pub fn __wbgtest_cov_dump() -> Vec<u8> {
6+
let mut coverage = Vec::new();
7+
unsafe {
8+
minicov::capture_coverage(&mut coverage).unwrap();
9+
}
10+
if coverage.is_empty() {
11+
console_error!(
12+
"Empty coverage data received. Make sure you compile the tests with
13+
RUSTFLAGS=\"-Cinstrument-coverage -Zno-profile-runtime --emit=llvm-ir\"",
14+
);
15+
}
16+
coverage
17+
}
18+
19+
/// Called when setting WASM_BINDGEN_TEST_COVERAGE but coverage feature is disabled.
20+
/// Currently not being used because of issues in the interpreter regarding profiling
21+
/// information which cause an error before we get here.
22+
#[cfg(not(feature = "coverage"))]
23+
#[wasm_bindgen]
24+
pub fn __wbgtest_cov_dump() -> Vec<u8> {
25+
console_error!(
26+
"Coverage was supposed to be dumped, but the \"coverage\" feature is disabled in wasm-bindgen-test",
27+
);
28+
Vec::new()
29+
}

crates/test/src/lib.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,24 @@ macro_rules! console_log {
2121
)
2222
}
2323

24+
/// Helper macro which acts like `println!` only routes to `console.warn`
25+
/// instead.
26+
#[macro_export]
27+
macro_rules! console_warn {
28+
($($arg:tt)*) => (
29+
$crate::__rt::log_warn(&format_args!($($arg)*))
30+
)
31+
}
32+
33+
/// Helper macro which acts like `println!` only routes to `console.error`
34+
/// instead.
35+
#[macro_export]
36+
macro_rules! console_error {
37+
($($arg:tt)*) => (
38+
$crate::__rt::log_error(&format_args!($($arg)*))
39+
)
40+
}
41+
2442
/// A macro used to configured how this test is executed by the
2543
/// `wasm-bindgen-test-runner` harness.
2644
///
@@ -59,3 +77,5 @@ macro_rules! wasm_bindgen_test_configure {
5977

6078
#[path = "rt/mod.rs"]
6179
pub mod __rt;
80+
81+
mod coverage;

0 commit comments

Comments
 (0)