Skip to content

Commit d7adaf0

Browse files
Merge pull request #6 from walkingeyerobot/yfyang
Add the necessary emscripten test mode.
2 parents b338824 + 948df4b commit d7adaf0

File tree

9 files changed

+233
-30
lines changed

9 files changed

+233
-30
lines changed

crates/cli-support/src/js/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2566,7 +2566,7 @@ __wbg_set_wasm(wasm);"
25662566
real.original = state;
25672567
CLOSURE_DTORS.register(real, state, state);
25682568
return real;
2569-
}},\n
2569+
}}
25702570
",
25712571
));
25722572
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
(function() {{
2+
var elem = document.querySelector('#output');
3+
window.extraLibraryFuncs = [];
4+
window.addToLibrary = function(LibraryWbg) {
5+
window.wasmExports = {__wbindgen_start:() => {}};
6+
window.cachedTextEncoder = {encodeInto:() => {}};
7+
window.Module = {};
8+
9+
try {
10+
LibraryWbg.$initBindgen();
11+
} catch (e) {
12+
elem.innerText = 'test setup failed: ' + e;
13+
}
14+
15+
function testExtraLibraryFuncs () {
16+
['$initBindgen', '$addOnInit', '$CLOSURE_DTORS', '$getStringFromWasm0'].forEach((value) => {
17+
if (!extraLibraryFuncs.includes(value)) {
18+
return { status: false, e: `test result: ${value} not found`};
19+
}
20+
});
21+
return {status: true, e: 'test result: ok'};
22+
}
23+
24+
function testLibraryWbg () {
25+
if (typeof Module.hello !== 'function') {
26+
return {status: false, e:'test result: hello() is not found'};
27+
}
28+
if (typeof Module.Interval !== 'function') {
29+
return {status: false, e:'test result: Interval is not found'};
30+
}
31+
32+
const keys = Object.keys(LibraryWbg);
33+
const testNames = ['clearInterval', 'setInterval', 'log'];
34+
35+
for (const name of testNames) {
36+
const regex = new RegExp(`^__wbg_${name}`);
37+
const res = keys.find(key => regex.test(key));
38+
if (!res) {
39+
return {status: false, e:`test result: ${name} not found`};
40+
}
41+
}
42+
return {status: true, e:'test result: ok'};
43+
}
44+
45+
const tests = [testExtraLibraryFuncs(), testLibraryWbg()];
46+
for (const res of tests) {
47+
if (!res.status) {
48+
elem.innerText = res.e;
49+
return;
50+
}
51+
}
52+
elem.innerText = 'test result: ok';
53+
54+
};
55+
}}());
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta content="text/html;charset=utf-8" http-equiv="Content-Type"/>
5+
</head>
6+
<body>
7+
<pre id="output">Loading scripts...</pre>
8+
<pre id="console_log"></pre>
9+
<pre id="console_info"></pre>
10+
<pre id="console_warn"></pre>
11+
<pre id="console_error"></pre>
12+
<!-- {IMPORT_SCRIPTS} -->
13+
</body>
14+
</html>

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

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,16 +111,27 @@ fn main() -> anyhow::Result<()> {
111111

112112
let shell = shell::Shell::new();
113113

114-
let file_name = cli
114+
let mut file_name = cli
115115
.file
116116
.file_name()
117117
.map(Path::new)
118118
.context("file to test is not a valid file, can't extract file name")?;
119119

120+
let mut file_name_buf= PathBuf::from(cli.file.clone());
121+
122+
// Repoint the file to be read from "name.js" to "name.wasm" in the case of emscripten.
123+
// Rustc generates a .js and a .wasm file when targeting emscripten. It lists the .js
124+
// file as the primary executor which is inconsitent with what is expected here.
125+
if file_name.extension().unwrap() == "js" {
126+
file_name_buf.pop();
127+
file_name_buf.push(file_name.file_stem().unwrap());
128+
file_name_buf.set_extension("wasm");
129+
file_name = Path::new(&file_name_buf);
130+
}
120131
// Collect all tests that the test harness is supposed to run. We assume
121132
// that any exported function with the prefix `__wbg_test` is a test we need
122133
// to execute.
123-
let wasm = fs::read(&cli.file).context("failed to read Wasm file")?;
134+
let wasm = fs::read(&file_name_buf).context("failed to read Wasm file")?;
124135
let mut wasm =
125136
walrus::Module::from_buffer(&wasm).context("failed to deserialize Wasm module")?;
126137
let mut tests = Tests::new();
@@ -203,6 +214,7 @@ fn main() -> anyhow::Result<()> {
203214
Some(section) if section.data.contains(&0x03) => TestMode::SharedWorker { no_modules },
204215
Some(section) if section.data.contains(&0x04) => TestMode::ServiceWorker { no_modules },
205216
Some(section) if section.data.contains(&0x05) => TestMode::Node { no_modules },
217+
Some(section) if section.data.contains(&0x06) => TestMode::Emscripten {},
206218
Some(_) => bail!("invalid __wasm_bingen_test_unstable value"),
207219
None => {
208220
let mut modes = Vec::new();
@@ -295,6 +307,9 @@ fn main() -> anyhow::Result<()> {
295307
} else {
296308
b.web(true)?
297309
}
310+
},
311+
TestMode::Emscripten {} => {
312+
b.emscripten(true)?
298313
}
299314
};
300315

@@ -316,6 +331,19 @@ fn main() -> anyhow::Result<()> {
316331
TestMode::Node { no_modules } => {
317332
node::execute(module, tmpdir.path(), cli, tests, !no_modules, coverage)?
318333
}
334+
TestMode::Emscripten => {
335+
let srv = server::spawn_emscripten(
336+
&"127.0.0.1:0".parse().unwrap(),
337+
tmpdir.path(),
338+
std::env::var("WASM_BINDGEN_TEST_NO_ORIGIN_ISOLATION").is_err()).context("failed to spawn server")?;
339+
let addr = srv.server_addr();
340+
println!(
341+
"Tests are now available at http://{}",
342+
addr
343+
);
344+
thread::spawn(|| srv.run());
345+
headless::run(&addr, &shell, driver_timeout, browser_timeout)?;
346+
}
319347
TestMode::Deno => deno::execute(module, tmpdir.path(), cli, tests)?,
320348
TestMode::Browser { .. }
321349
| TestMode::DedicatedWorker { .. }
@@ -372,6 +400,7 @@ enum TestMode {
372400
DedicatedWorker { no_modules: bool },
373401
SharedWorker { no_modules: bool },
374402
ServiceWorker { no_modules: bool },
403+
Emscripten,
375404
}
376405

377406
impl TestMode {
@@ -384,7 +413,7 @@ impl TestMode {
384413

385414
fn no_modules(self) -> bool {
386415
match self {
387-
Self::Deno => true,
416+
Self::Deno | Self::Emscripten => true,
388417
Self::Browser { no_modules }
389418
| Self::Node { no_modules }
390419
| Self::DedicatedWorker { no_modules }
@@ -401,6 +430,7 @@ impl TestMode {
401430
TestMode::DedicatedWorker { .. } => "WASM_BINDGEN_USE_DEDICATED_WORKER",
402431
TestMode::SharedWorker { .. } => "WASM_BINDGEN_USE_SHARED_WORKER",
403432
TestMode::ServiceWorker { .. } => "WASM_BINDGEN_USE_SERVICE_WORKER",
433+
TestMode::Emscripten { .. } => "WASM_BINDGEN_USE_EMSCRIPTEN",
404434
}
405435
}
406436
}

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

Lines changed: 63 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -353,37 +353,76 @@ pub(crate) fn spawn(
353353
response
354354
})
355355
.map_err(|e| anyhow!("{}", e))?;
356-
return Ok(srv);
356+
return Ok(srv);
357+
}
358+
359+
pub(crate) fn spawn_emscripten(
360+
addr: &SocketAddr,
361+
tmpdir: &Path,
362+
isolate_origin: bool,
363+
) -> Result<Server<impl Fn(&Request) -> Response + Send + Sync>, Error> {
364+
let js_path = tmpdir.join("run.js");
365+
fs::write(js_path, include_str!("emscripten_test.js")).context("failed to write JS file")?;
366+
let tmpdir = tmpdir.to_path_buf();
367+
let srv = Server::new(addr, move |request| {
368+
if request.url() == "/" {
369+
let s =
370+
include_str!("index-emscripten.html");
371+
let s =
372+
s.replace(
373+
"<!-- {IMPORT_SCRIPTS} -->",
374+
"<script src=\"run.js\"></script>\n <script src=\"library_bindgen.js\"></script>",
375+
);
376+
377+
let response = Response::from_data("text/html", s);
357378

358-
fn try_asset(request: &Request, dir: &Path) -> Response {
359-
let response = rouille::match_assets(request, dir);
360-
if response.is_success() {
361379
return response;
362380
}
363381

364-
// When a browser is doing ES imports it's using the directives we
365-
// write in the code that *don't* have file extensions (aka we say `from
366-
// 'foo'` instead of `from 'foo.js'`. Fixup those paths here to see if a
367-
// `js` file exists.
368-
if let Some(part) = request.url().split('/').last() {
369-
if !part.contains('.') {
370-
let new_request = Request::fake_http(
371-
request.method(),
372-
format!("{}.js", request.url()),
373-
request
374-
.headers()
375-
.map(|(a, b)| (a.to_string(), b.to_string()))
376-
.collect(),
377-
Vec::new(),
378-
);
379-
let response = rouille::match_assets(&new_request, dir);
380-
if response.is_success() {
381-
return response;
382-
}
383-
}
382+
let mut response = try_asset(request, &tmpdir);
383+
if !response.is_success() {
384+
response = try_asset(request, ".".as_ref());
385+
}
386+
// Make sure browsers don't cache anything (Chrome appeared to with this
387+
// header?)
388+
response.headers.retain(|(k, _)| k != "Cache-Control");
389+
if isolate_origin {
390+
set_isolate_origin_headers(&mut response)
384391
}
385392
response
393+
})
394+
.map_err(|e| anyhow!("{}", e))?;
395+
return Ok(srv);
396+
}
397+
398+
fn try_asset(request: &Request, dir: &Path) -> Response {
399+
let response = rouille::match_assets(request, dir);
400+
if response.is_success() {
401+
return response;
402+
}
403+
404+
// When a browser is doing ES imports it's using the directives we
405+
// write in the code that *don't* have file extensions (aka we say `from
406+
// 'foo'` instead of `from 'foo.js'`. Fixup those paths here to see if a
407+
// `js` file exists.
408+
if let Some(part) = request.url().split('/').last() {
409+
if !part.contains('.') {
410+
let new_request = Request::fake_http(
411+
request.method(),
412+
format!("{}.js", request.url()),
413+
request
414+
.headers()
415+
.map(|(a, b)| (a.to_string(), b.to_string()))
416+
.collect(),
417+
Vec::new(),
418+
);
419+
let response = rouille::match_assets(&new_request, dir);
420+
if response.is_success() {
421+
return response;
422+
}
423+
}
386424
}
425+
response
387426
}
388427

389428
fn handle_coverage_dump(profraw_path: &Path, request: &Request) -> anyhow::Result<()> {

crates/test/src/lib.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,14 @@ macro_rules! wasm_bindgen_test_configure {
106106
$crate::wasm_bindgen_test_configure!($($others)*);
107107
};
108108
);
109+
(run_in_emscripten $($others:tt)*) => (
110+
const _: () = {
111+
#[link_section = "__wasm_bindgen_test_unstable"]
112+
#[cfg(target_arch = "wasm32")]
113+
pub static __WBG_TEST_run_in_emscripten: [u8; 1] = [0x06];
114+
$crate::wasm_bindgen_test_configure!($($others)*);
115+
};
116+
);
109117
() => ()
110118
}
111119

tests/headless/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#![cfg(target_arch = "wasm32")]
1+
#![cfg(all(target_arch = "wasm32", target_os = "unknown"))]
22

33
extern crate wasm_bindgen;
44
extern crate wasm_bindgen_test;

tests/wasm/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#![cfg(target_arch = "wasm32")]
1+
#![cfg(all(target_arch = "wasm32", target_os = "unknown"))]
22
#![allow(renamed_and_removed_lints)] // clippy::drop_ref will be renamed to drop_ref
33
#![allow(clippy::drop_ref, clippy::drop_non_drop)]
44

tests/wasm32-emscripten/main.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
#![cfg(all(target_arch = "wasm32", target_os = "emscripten"))]
2+
3+
extern crate wasm_bindgen;
4+
extern crate wasm_bindgen_test;
5+
6+
use wasm_bindgen::prelude::*;
7+
use wasm_bindgen_test::*;
8+
9+
wasm_bindgen_test_configure!(run_in_emscripten);
10+
11+
#[wasm_bindgen]
12+
extern "C" {
13+
fn setInterval(closure: &Closure<dyn FnMut()>, millis: u32) -> f64;
14+
fn clearInterval(token: f64);
15+
16+
#[wasm_bindgen(js_namespace = console)]
17+
fn log(s: &str);
18+
}
19+
20+
#[wasm_bindgen]
21+
pub struct Interval {
22+
closure: Closure<dyn FnMut()>,
23+
token: f64,
24+
}
25+
26+
impl Interval {
27+
pub fn new<F: 'static>(millis: u32, f: F) -> Interval
28+
where
29+
F: FnMut()
30+
{
31+
// Construct a new closure.
32+
let closure = Closure::new(f);
33+
34+
// Pass the closure to JS, to run every n milliseconds.
35+
let token = setInterval(&closure, millis);
36+
37+
Interval { closure, token }
38+
}
39+
}
40+
41+
// When the Interval is destroyed, clear its `setInterval` timer.
42+
impl Drop for Interval {
43+
fn drop(&mut self) {
44+
clearInterval(self.token);
45+
}
46+
}
47+
48+
// Keep logging "hello" every second until the resulting `Interval` is dropped.
49+
#[wasm_bindgen]
50+
pub fn hello() -> Interval {
51+
Interval::new(1_000, || log("hello"))
52+
}
53+
54+
#[wasm_bindgen_test]
55+
fn hello_test() {
56+
hello();
57+
}

0 commit comments

Comments
 (0)