Skip to content

Commit 8d063a4

Browse files
authored
Fix example tests (#3614)
* Fix example tests I went to enable testing for the `synchronous-instantiation` example now that Firefox supports module workers, but then found that the `wasm-audio-worklet` example was failing because the test server doesn't set the headers needed to enable `SharedArrayBuffer`. It turns out that CI wasn't failing because it's been broken this whole whole time: it specifies the path to the built examples as simply `exbuild`, which doesn't work because the tests are run with their working directory set to `crates/example-tests`, not the root of the repo. This means that any requests that the examples try to make will 404. So this PR specifies it as an absolute path instead. At the moment, Firefox doesn't directly indicate any kind of error when navigation fails, which meant that the tests would just silently fail without actually testing anything. According to the spec, `browsingContext.navigate` is supposed to wait for the navigation to complete, and result in an error if something goes wrong; but I think Firefox is behind, because it seems to instead immediately return. To work around this, I've made it so that the tests manually wait for the `network.responseCompleted` event to check if fetching the page suceeded, and so this shouldn't happen again. I've left the actual fix for the `wasm-audio-worklet` example commented out to make sure that CI actually catches the issue now; that's why this PR is a draft. * properly interpolate repo root * use correct variable It looks like `env` is specifically for variables that you set manually, not arbitrary environment variables. * Fix wasm_audio_worklet * tweak doc comment
1 parent 26e8377 commit 8d063a4

File tree

4 files changed

+142
-78
lines changed

4 files changed

+142
-78
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -323,7 +323,7 @@ jobs:
323323
- run: rustup update --no-self-update stable && rustup default stable
324324
- run: cargo test -p example-tests
325325
env:
326-
EXBUILD: exbuild
326+
EXBUILD: ${{ github.workspace }}/exbuild
327327

328328
build_benchmarks:
329329
runs-on: ubuntu-latest

crates/example-tests/Cargo.toml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@ edition = "2018"
66
rust-version = "1.56"
77

88
[dependencies]
9-
anyhow = "1.0.58"
10-
futures-util = { version = "0.3.21", features = ["sink"] }
11-
hyper = { version = "0.14.20", features = ["server", "tcp", "http1"] }
9+
anyhow = "1.0.75"
10+
futures-util = { version = "0.3.28", features = ["sink"] }
11+
http = "0.2.9"
12+
hyper = { version = "0.14.27", features = ["server", "tcp", "http1"] }
1213
mozprofile = "0.8.0"
1314
mozrunner = "0.14.0"
1415
serde = { version = "1.0", features = ["derive"] }
1516
serde_json = "1.0"
16-
tokio = { version = "1.20.0", features = ["macros", "time"] }
17+
tokio = { version = "1.29.1", features = ["macros", "time"] }
1718
tokio-tungstenite = "0.17.2"
1819
tower = { version = "0.4.13", features = ["make"] }
19-
tower-http = { version = "0.3.4", features = ["fs"] }
20+
tower-http = { version = "0.3.5", features = ["fs", "util", "set-header"] }

crates/example-tests/src/lib.rs

Lines changed: 135 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use std::{env, str};
88

99
use anyhow::{bail, Context};
1010
use futures_util::{future, SinkExt, StreamExt};
11+
use http::{HeaderName, HeaderValue};
1112
use mozprofile::profile::Profile;
1213
use mozrunner::firefox_default_path;
1314
use mozrunner::runner::{FirefoxProcess, FirefoxRunner, Runner, RunnerProcess};
@@ -20,7 +21,9 @@ use tokio::time::timeout;
2021
use tokio_tungstenite::tungstenite::{self, Message};
2122
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
2223
use tower::make::Shared;
24+
use tower::ServiceBuilder;
2325
use tower_http::services::ServeDir;
26+
use tower_http::ServiceBuilderExt;
2427

2528
/// A command sent from the client to the server.
2629
#[derive(Serialize)]
@@ -241,6 +244,73 @@ impl WebDriver {
241244
}
242245
}
243246

247+
/// Handles a `log.entryAdded` event with the given parameters, and returns an
248+
/// error if the log entry is an error (or something else goes wrong).
249+
fn handle_log_event(params: Value) -> anyhow::Result<()> {
250+
#[derive(Deserialize)]
251+
#[serde(rename_all = "camelCase")]
252+
struct LogEntry {
253+
level: LogLevel,
254+
text: Option<String>,
255+
stack_trace: Option<StackTrace>,
256+
}
257+
258+
#[derive(Deserialize, Debug, PartialEq, Eq, Clone, Copy)]
259+
#[serde(rename_all = "lowercase")]
260+
enum LogLevel {
261+
Debug,
262+
Info,
263+
Warn,
264+
Error,
265+
}
266+
267+
#[derive(Deserialize)]
268+
#[serde(rename_all = "camelCase")]
269+
struct StackTrace {
270+
call_frames: Vec<StackFrame>,
271+
}
272+
273+
#[derive(Deserialize)]
274+
#[serde(rename_all = "camelCase")]
275+
struct StackFrame {
276+
column_number: i64,
277+
function_name: String,
278+
line_number: i64,
279+
url: String,
280+
}
281+
282+
impl Display for StackFrame {
283+
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
284+
write!(
285+
f,
286+
"{} (at {}:{}:{})",
287+
self.function_name, self.url, self.line_number, self.column_number
288+
)
289+
}
290+
}
291+
292+
let entry: LogEntry = serde_json::from_value(params).context("invalid log entry received")?;
293+
294+
if entry.level == LogLevel::Error {
295+
if let Some(text) = entry.text {
296+
let mut msg = format!("An error occurred: {text}");
297+
298+
if let Some(stack_trace) = entry.stack_trace {
299+
write!(msg, "\n\nStack trace:").unwrap();
300+
for frame in stack_trace.call_frames {
301+
write!(msg, "\n{frame}").unwrap();
302+
}
303+
}
304+
305+
bail!("{msg}")
306+
} else {
307+
bail!("An error occurred")
308+
}
309+
}
310+
311+
Ok(())
312+
}
313+
244314
/// Run a single example with the passed name, using the passed closure to
245315
/// build it if prebuilt examples weren't provided.
246316
pub async fn test_example(
@@ -256,8 +326,18 @@ pub async fn test_example(
256326
let mut driver = WebDriver::new().await?;
257327

258328
// Serve the path.
259-
let server = hyper::Server::try_bind(&"127.0.0.1:0".parse().unwrap())?
260-
.serve(Shared::new(ServeDir::new(path)));
329+
let service = ServiceBuilder::new()
330+
.override_response_header(
331+
HeaderName::from_static("cross-origin-opener-policy"),
332+
HeaderValue::from_static("same-origin"),
333+
)
334+
.override_response_header(
335+
HeaderName::from_static("cross-origin-embedder-policy"),
336+
HeaderValue::from_static("require-corp"),
337+
)
338+
.service(ServeDir::new(path));
339+
let server =
340+
hyper::Server::try_bind(&"127.0.0.1:0".parse().unwrap())?.serve(Shared::new(service));
261341

262342
let addr = server.local_addr();
263343

@@ -281,13 +361,18 @@ pub async fn test_example(
281361
.issue_cmd(
282362
"session.subscribe",
283363
json!({
284-
"events": ["log.entryAdded"],
364+
"events": ["log.entryAdded", "network.responseCompleted"],
285365
"contexts": [&context],
286366
}),
287367
)
288368
.await?;
289369

290-
let _: Value = driver
370+
#[derive(Deserialize)]
371+
struct BrowsingContextNavigateResult {
372+
navigation: Option<String>,
373+
}
374+
375+
let BrowsingContextNavigateResult { navigation } = driver
291376
.issue_cmd(
292377
"browsingContext.navigate",
293378
json!({
@@ -296,6 +381,51 @@ pub async fn test_example(
296381
}),
297382
)
298383
.await?;
384+
// Apparently this being null means that 'the navigation [was] canceled before
385+
// making progress'.
386+
// source: https://w3c.github.io/webdriver-bidi/#module-browsingContext
387+
let navigation = navigation.context("navigation canceled")?;
388+
389+
// Wait for the page to be fetched, so that we can check whether it succeeds.
390+
// Note: I'm pretty sure that `browsingContext.navigate` is supposed to report
391+
// an error anyway if this fails, but Firefox seems to be behind the spec here.
392+
loop {
393+
let event = driver
394+
.next_event()
395+
.await
396+
.context("websocket unexpectedly closed")?;
397+
match event.method.as_str() {
398+
"log.entryAdded" => handle_log_event(event.params)?,
399+
"network.responseCompleted" => {
400+
#[derive(Deserialize)]
401+
struct NetworkReponseCompletedParameters {
402+
navigation: Option<String>,
403+
response: NetworkResponseData,
404+
}
405+
406+
#[derive(Deserialize)]
407+
#[serde(rename_all = "camelCase")]
408+
struct NetworkResponseData {
409+
status: u64,
410+
status_text: String,
411+
}
412+
413+
let params: NetworkReponseCompletedParameters =
414+
serde_json::from_value(event.params)?;
415+
if params.navigation.as_ref() == Some(&navigation) {
416+
if !(200..300).contains(&params.response.status) {
417+
bail!(
418+
"fetching page failed ({} {})",
419+
params.response.status,
420+
params.response.status_text
421+
)
422+
}
423+
break;
424+
}
425+
}
426+
_ => {}
427+
}
428+
}
299429

300430
let start = Instant::now();
301431
// Wait 5 seconds for any errors to occur.
@@ -305,73 +435,7 @@ pub async fn test_example(
305435
Ok(event) => {
306436
let event = event?;
307437
if event.method == "log.entryAdded" {
308-
#[derive(Deserialize)]
309-
#[serde(rename_all = "camelCase")]
310-
struct LogEntry {
311-
level: LogLevel,
312-
// source: Source,
313-
text: Option<String>,
314-
// timestamp: i64,
315-
stack_trace: Option<StackTrace>,
316-
// kind: LogEntryKind,
317-
}
318-
319-
#[derive(Deserialize, Debug, PartialEq, Eq, Clone, Copy)]
320-
#[serde(rename_all = "lowercase")]
321-
enum LogLevel {
322-
Debug,
323-
Info,
324-
Warning,
325-
Error,
326-
}
327-
328-
#[derive(Deserialize)]
329-
#[serde(rename_all = "camelCase")]
330-
struct StackTrace {
331-
call_frames: Vec<StackFrame>,
332-
}
333-
334-
#[derive(Deserialize)]
335-
#[serde(rename_all = "camelCase")]
336-
struct StackFrame {
337-
column_number: i64,
338-
function_name: String,
339-
line_number: i64,
340-
url: String,
341-
}
342-
343-
impl Display for StackFrame {
344-
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
345-
write!(
346-
f,
347-
"{} (at {}:{}:{})",
348-
self.function_name,
349-
self.url,
350-
self.line_number,
351-
self.column_number
352-
)
353-
}
354-
}
355-
356-
let entry: LogEntry = serde_json::from_value(event.params)
357-
.context("invalid log entry received")?;
358-
359-
if entry.level == LogLevel::Error {
360-
if let Some(text) = entry.text {
361-
let mut msg = format!("An error occurred: {text}");
362-
363-
if let Some(stack_trace) = entry.stack_trace {
364-
write!(msg, "\n\nStack trace:").unwrap();
365-
for frame in stack_trace.call_frames {
366-
write!(msg, "\n{frame}").unwrap();
367-
}
368-
}
369-
370-
bail!("{msg}")
371-
} else {
372-
bail!("An error occurred")
373-
}
374-
}
438+
handle_log_event(event.params)?;
375439
}
376440
}
377441
Err(_) => break,

crates/example-tests/tests/shell.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ macro_rules! shell_tests {
3636
shell_tests! {
3737
#["RUSTUP_TOOLCHAIN" = "nightly"]
3838
raytrace_parallel = "raytrace-parallel",
39-
#[ignore = "This requires module workers, which Firefox doesn't support yet."]
4039
synchronous_instantiation = "synchronous-instantiation",
4140
wasm2js = "wasm2js",
4241
#["RUSTUP_TOOLCHAIN" = "nightly"]

0 commit comments

Comments
 (0)