diff options
author | Matt Mastracci <matthew@mastracci.com> | 2024-02-10 13:22:13 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-02-10 20:22:13 +0000 |
commit | f5e46c9bf2f50d66a953fa133161fc829cecff06 (patch) | |
tree | 8faf2f5831c1c7b11d842cd9908d141082c869a5 /cli/tests/integration/inspector_tests.rs | |
parent | d2477f780630a812bfd65e3987b70c0d309385bb (diff) |
chore: move cli/tests/ -> tests/ (#22369)
This looks like a massive PR, but it's only a move from cli/tests ->
tests, and updates of relative paths for files.
This is the first step towards aggregate all of the integration test
files under tests/, which will lead to a set of integration tests that
can run without the CLI binary being built.
While we could leave these tests under `cli`, it would require us to
keep a more complex directory structure for the various test runners. In
addition, we have a lot of complexity to ignore various test files in
the `cli` project itself (cargo publish exclusion rules, autotests =
false, etc).
And finally, the `tests/` folder will eventually house the `test_ffi`,
`test_napi` and other testing code, reducing the size of the root repo
directory.
For easier review, the extremely large and noisy "move" is in the first
commit (with no changes -- just a move), while the remainder of the
changes to actual files is in the second commit.
Diffstat (limited to 'cli/tests/integration/inspector_tests.rs')
-rw-r--r-- | cli/tests/integration/inspector_tests.rs | 1440 |
1 files changed, 0 insertions, 1440 deletions
diff --git a/cli/tests/integration/inspector_tests.rs b/cli/tests/integration/inspector_tests.rs deleted file mode 100644 index bbe70ae5e..000000000 --- a/cli/tests/integration/inspector_tests.rs +++ /dev/null @@ -1,1440 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -use bytes::Bytes; -use deno_core::anyhow::anyhow; -use deno_core::error::AnyError; -use deno_core::serde_json; -use deno_core::serde_json::json; -use deno_core::url; -use deno_fetch::reqwest; -use fastwebsockets::FragmentCollector; -use fastwebsockets::Frame; -use fastwebsockets::WebSocket; -use hyper::body::Incoming; -use hyper::upgrade::Upgraded; -use hyper::Request; -use hyper::Response; -use hyper_util::rt::TokioIo; -use std::io::BufRead; -use std::time::Duration; -use test_util as util; -use tokio::net::TcpStream; -use tokio::time::timeout; -use url::Url; -use util::assert_starts_with; -use util::DenoChild; -use util::TestContextBuilder; - -struct SpawnExecutor; - -impl<Fut> hyper::rt::Executor<Fut> for SpawnExecutor -where - Fut: std::future::Future + Send + 'static, - Fut::Output: Send + 'static, -{ - fn execute(&self, fut: Fut) { - deno_core::unsync::spawn(fut); - } -} - -async fn connect_to_ws( - uri: Url, -) -> (WebSocket<TokioIo<Upgraded>>, Response<Incoming>) { - let domain = &uri.host().unwrap().to_string(); - let port = &uri.port().unwrap_or(match uri.scheme() { - "wss" | "https" => 443, - _ => 80, - }); - let addr = format!("{domain}:{port}"); - - let stream = TcpStream::connect(addr).await.unwrap(); - - let host = uri.host_str().unwrap(); - - let req = Request::builder() - .method("GET") - .uri(uri.path()) - .header("Host", host) - .header(hyper::header::UPGRADE, "websocket") - .header(hyper::header::CONNECTION, "Upgrade") - .header( - "Sec-WebSocket-Key", - fastwebsockets::handshake::generate_key(), - ) - .header("Sec-WebSocket-Version", "13") - .body(http_body_util::Empty::<Bytes>::new()) - .unwrap(); - - fastwebsockets::handshake::client(&SpawnExecutor, req, stream) - .await - .unwrap() -} - -struct InspectorTester { - socket: FragmentCollector<TokioIo<Upgraded>>, - notification_filter: Box<dyn FnMut(&str) -> bool + 'static>, - child: DenoChild, - stderr_lines: Box<dyn Iterator<Item = String>>, - stdout_lines: Box<dyn Iterator<Item = String>>, -} - -impl Drop for InspectorTester { - fn drop(&mut self) { - _ = self.child.kill(); - } -} - -fn ignore_script_parsed(msg: &str) -> bool { - !msg.starts_with(r#"{"method":"Debugger.scriptParsed","#) -} - -impl InspectorTester { - async fn create<F>(mut child: DenoChild, notification_filter: F) -> Self - where - F: FnMut(&str) -> bool + 'static, - { - let stdout = child.stdout.take().unwrap(); - let stdout_lines = - std::io::BufReader::new(stdout).lines().map(|r| r.unwrap()); - - let stderr = child.stderr.take().unwrap(); - let mut stderr_lines = - std::io::BufReader::new(stderr).lines().map(|r| r.unwrap()); - - let uri = extract_ws_url_from_stderr(&mut stderr_lines); - - let (socket, response) = connect_to_ws(uri).await; - - assert_eq!(response.status(), 101); // Switching protocols. - - Self { - socket: FragmentCollector::new(socket), - notification_filter: Box::new(notification_filter), - child, - stderr_lines: Box::new(stderr_lines), - stdout_lines: Box::new(stdout_lines), - } - } - - async fn send_many(&mut self, messages: &[serde_json::Value]) { - // TODO(bartlomieju): add graceful error handling - for msg in messages { - let result = self - .socket - .write_frame(Frame::text(msg.to_string().into_bytes().into())) - .await - .map_err(|e| anyhow!(e)); - self.handle_error(result); - } - } - - async fn send(&mut self, message: serde_json::Value) { - self.send_many(&[message]).await; - } - - fn handle_error<T>(&mut self, result: Result<T, AnyError>) -> T { - match result { - Ok(result) => result, - Err(err) => { - let mut stdout = vec![]; - for line in self.stdout_lines.by_ref() { - stdout.push(line); - } - let mut stderr = vec![]; - for line in self.stderr_lines.by_ref() { - stderr.push(line); - } - let stdout = stdout.join("\n"); - let stderr = stderr.join("\n"); - self.child.kill().unwrap(); - - panic!( - "Inspector test failed with error: {err:?}.\nstdout:\n{stdout}\nstderr:\n{stderr}" - ); - } - } - } - - async fn recv(&mut self) -> String { - loop { - // In the rare case this locks up, don't wait longer than one minute - let result = timeout(Duration::from_secs(60), self.socket.read_frame()) - .await - .expect("recv() timeout") - .map_err(|e| anyhow!(e)); - let message = - String::from_utf8(self.handle_error(result).payload.to_vec()).unwrap(); - if (self.notification_filter)(&message) { - return message; - } - } - } - - async fn recv_as_json(&mut self) -> serde_json::Value { - let msg = self.recv().await; - serde_json::from_str(&msg).unwrap() - } - - async fn assert_received_messages( - &mut self, - responses: &[&str], - notifications: &[&str], - ) { - let expected_messages = responses.len() + notifications.len(); - let mut responses_idx = 0; - let mut notifications_idx = 0; - - for _ in 0..expected_messages { - let msg = self.recv().await; - - if msg.starts_with(r#"{"id":"#) { - assert!( - msg.starts_with(responses[responses_idx]), - "Doesn't start with {}, instead received {}", - responses[responses_idx], - msg - ); - responses_idx += 1; - } else { - assert!( - msg.starts_with(notifications[notifications_idx]), - "Doesn't start with {}, instead received {}", - notifications[notifications_idx], - msg - ); - notifications_idx += 1; - } - } - } - - fn stderr_line(&mut self) -> String { - self.stderr_lines.next().unwrap() - } - - fn stdout_line(&mut self) -> String { - self.stdout_lines.next().unwrap() - } - - fn assert_stderr_for_inspect(&mut self) { - assert_stderr( - &mut self.stderr_lines, - &["Visit chrome://inspect to connect to the debugger."], - ); - } - - fn assert_stderr_for_inspect_brk(&mut self) { - assert_stderr( - &mut self.stderr_lines, - &[ - "Visit chrome://inspect to connect to the debugger.", - "Deno is waiting for debugger to connect.", - ], - ); - } -} - -fn assert_stderr( - stderr_lines: &mut impl std::iter::Iterator<Item = String>, - expected_lines: &[&str], -) { - let mut expected_index = 0; - - loop { - let line = skip_check_line(stderr_lines); - - assert_eq!(line, expected_lines[expected_index]); - expected_index += 1; - - if expected_index >= expected_lines.len() { - break; - } - } -} - -fn inspect_flag_with_unique_port(flag_prefix: &str) -> String { - use std::sync::atomic::AtomicU16; - use std::sync::atomic::Ordering; - static PORT: AtomicU16 = AtomicU16::new(9229); - let port = PORT.fetch_add(1, Ordering::Relaxed); - format!("{flag_prefix}=127.0.0.1:{port}") -} - -fn extract_ws_url_from_stderr( - stderr_lines: &mut impl std::iter::Iterator<Item = String>, -) -> url::Url { - let stderr_first_line = skip_check_line(stderr_lines); - assert_starts_with!(&stderr_first_line, "Debugger listening on "); - let v: Vec<_> = stderr_first_line.match_indices("ws:").collect(); - assert_eq!(v.len(), 1); - let ws_url_index = v[0].0; - let ws_url = &stderr_first_line[ws_url_index..]; - url::Url::parse(ws_url).unwrap() -} - -fn skip_check_line( - stderr_lines: &mut impl std::iter::Iterator<Item = String>, -) -> String { - loop { - let mut line = stderr_lines.next().unwrap(); - line = util::strip_ansi_codes(&line).to_string(); - - if line.starts_with("Check") || line.starts_with("Download") { - continue; - } - - return line; - } -} - -#[tokio::test] -async fn inspector_connect() { - let script = util::testdata_path().join("inspector/inspector1.js"); - let mut child = util::deno_cmd() - .arg("run") - .arg(inspect_flag_with_unique_port("--inspect")) - .arg(script) - .stderr_piped() - .spawn() - .unwrap(); - - let stderr = child.stderr.as_mut().unwrap(); - let mut stderr_lines = - std::io::BufReader::new(stderr).lines().map(|r| r.unwrap()); - let ws_url = extract_ws_url_from_stderr(&mut stderr_lines); - - let (_socket, response) = connect_to_ws(ws_url).await; - assert_eq!("101 Switching Protocols", response.status().to_string()); - child.kill().unwrap(); - child.wait().unwrap(); -} - -#[tokio::test] -async fn inspector_break_on_first_line() { - let script = util::testdata_path().join("inspector/inspector2.js"); - let child = util::deno_cmd() - .arg("run") - .arg(inspect_flag_with_unique_port("--inspect-brk")) - .arg(script) - .piped_output() - .spawn() - .unwrap(); - - let mut tester = InspectorTester::create(child, ignore_script_parsed).await; - - tester.assert_stderr_for_inspect_brk(); - - tester - .send_many(&[ - json!({"id":1,"method":"Runtime.enable"}), - json!({"id":2,"method":"Debugger.enable"}), - ]) - .await; - tester.assert_received_messages( - &[ - r#"{"id":1,"result":{}}"#, - r#"{"id":2,"result":{"debuggerId":"#, - ], - &[ - r#"{"method":"Runtime.executionContextCreated","params":{"context":{"id":1,"#, - ], - ) - .await; - - tester - .send(json!({"id":3,"method":"Runtime.runIfWaitingForDebugger"})) - .await; - tester - .assert_received_messages( - &[r#"{"id":3,"result":{}}"#], - &[r#"{"method":"Debugger.paused","#], - ) - .await; - - tester - .send(json!({ - "id":4, - "method":"Runtime.evaluate", - "params":{ - "expression":"Deno[Deno.internal].core.print(\"hello from the inspector\\n\")", - "contextId":1, - "includeCommandLineAPI":true, - "silent":false, - "returnByValue":true - } - })) - .await; - tester - .assert_received_messages( - &[r#"{"id":4,"result":{"result":{"type":"object","subtype":"null","value":null}}}"#], - &[], - ) - .await; - - assert_eq!( - &tester.stdout_lines.next().unwrap(), - "hello from the inspector" - ); - - tester - .send(json!({"id":5,"method":"Debugger.resume"})) - .await; - tester - .assert_received_messages(&[r#"{"id":5,"result":{}}"#], &[]) - .await; - - assert_eq!( - &tester.stdout_lines.next().unwrap(), - "hello from the script" - ); - - tester.child.kill().unwrap(); - tester.child.wait().unwrap(); -} - -#[tokio::test] -async fn inspector_pause() { - let script = util::testdata_path().join("inspector/inspector1.js"); - let child = util::deno_cmd() - .arg("run") - .arg(inspect_flag_with_unique_port("--inspect")) - .arg(script) - .piped_output() - .spawn() - .unwrap(); - - let mut tester = InspectorTester::create(child, ignore_script_parsed).await; - - tester - .send(json!({"id":6,"method":"Debugger.enable"})) - .await; - tester - .assert_received_messages(&[r#"{"id":6,"result":{"debuggerId":"#], &[]) - .await; - - tester - .send(json!({"id":31,"method":"Debugger.pause"})) - .await; - - tester - .assert_received_messages(&[r#"{"id":31,"result":{}}"#], &[]) - .await; - - tester.child.kill().unwrap(); -} - -#[tokio::test] -async fn inspector_port_collision() { - // Skip this test on WSL, which allows multiple processes to listen on the - // same port, rather than making `bind()` fail with `EADDRINUSE`. We also - // skip this test on Windows because it will occasionally flake, possibly - // due to a similar issue. - if (cfg!(target_os = "linux") - && std::env::var_os("WSL_DISTRO_NAME").is_some()) - || cfg!(windows) - { - return; - } - - let script = util::testdata_path().join("inspector/inspector1.js"); - let inspect_flag = inspect_flag_with_unique_port("--inspect"); - - let mut child1 = util::deno_cmd() - .arg("run") - .arg(&inspect_flag) - .arg(script.clone()) - .stderr_piped() - .spawn() - .unwrap(); - - let stderr_1 = child1.stderr.as_mut().unwrap(); - let mut stderr_1_lines = std::io::BufReader::new(stderr_1) - .lines() - .map(|r| r.unwrap()); - let _ = extract_ws_url_from_stderr(&mut stderr_1_lines); - - let mut child2 = util::deno_cmd() - .arg("run") - .arg(&inspect_flag) - .arg(script) - .stderr_piped() - .spawn() - .unwrap(); - - let stderr_2 = child2.stderr.as_mut().unwrap(); - let stderr_2_error_message = std::io::BufReader::new(stderr_2) - .lines() - .map(|r| r.unwrap()) - .inspect(|line| assert!(!line.contains("Debugger listening"))) - .find(|line| line.contains("Cannot start inspector server")); - assert!(stderr_2_error_message.is_some()); - - child1.kill().unwrap(); - child1.wait().unwrap(); - child2.wait().unwrap(); -} - -#[tokio::test] -async fn inspector_does_not_hang() { - let script = util::testdata_path().join("inspector/inspector3.js"); - let child = util::deno_cmd() - .arg("run") - .arg(inspect_flag_with_unique_port("--inspect-brk")) - .env("NO_COLOR", "1") - .arg(script) - .piped_output() - .spawn() - .unwrap(); - - let mut tester = InspectorTester::create(child, ignore_script_parsed).await; - - tester.assert_stderr_for_inspect_brk(); - - tester - .send_many(&[ - json!({"id":1,"method":"Runtime.enable"}), - json!({"id":2,"method":"Debugger.enable"}), - ]) - .await; - tester.assert_received_messages( - &[ - r#"{"id":1,"result":{}}"#, - r#"{"id":2,"result":{"debuggerId":"# - ], - &[ - r#"{"method":"Runtime.executionContextCreated","params":{"context":{"id":1,"# - ], - ) - .await; - - tester - .send(json!({"id":3,"method":"Runtime.runIfWaitingForDebugger"})) - .await; - tester - .assert_received_messages( - &[r#"{"id":3,"result":{}}"#], - &[r#"{"method":"Debugger.paused","#], - ) - .await; - - tester - .send(json!({"id":4,"method":"Debugger.resume"})) - .await; - tester - .assert_received_messages( - &[r#"{"id":4,"result":{}}"#], - &[r#"{"method":"Debugger.resumed","params":{}}"#], - ) - .await; - - for i in 0..128u32 { - let request_id = i + 10; - // Expect the number {i} on stdout. - let s = i.to_string(); - assert_eq!(tester.stdout_lines.next().unwrap(), s); - - tester - .assert_received_messages( - &[], - &[ - r#"{"method":"Runtime.consoleAPICalled","#, - r#"{"method":"Debugger.paused","#, - ], - ) - .await; - - tester - .send(json!({"id":request_id,"method":"Debugger.resume"})) - .await; - tester - .assert_received_messages( - &[&format!(r#"{{"id":{request_id},"result":{{}}}}"#)], - &[r#"{"method":"Debugger.resumed","params":{}}"#], - ) - .await; - } - - // Check that we can gracefully close the websocket connection. - tester - .socket - .write_frame(Frame::close_raw(vec![].into())) - .await - .unwrap(); - - assert_eq!(&tester.stdout_lines.next().unwrap(), "done"); - assert!(tester.child.wait().unwrap().success()); -} - -#[tokio::test] -async fn inspector_without_brk_runs_code() { - let script = util::testdata_path().join("inspector/inspector4.js"); - let mut child = util::deno_cmd() - .arg("run") - .arg(inspect_flag_with_unique_port("--inspect")) - .arg(script) - .piped_output() - .spawn() - .unwrap(); - - let stderr = child.stderr.as_mut().unwrap(); - let mut stderr_lines = - std::io::BufReader::new(stderr).lines().map(|r| r.unwrap()); - let _ = extract_ws_url_from_stderr(&mut stderr_lines); - - // Check that inspector actually runs code without waiting for inspector - // connection. - let stdout = child.stdout.as_mut().unwrap(); - let mut stdout_lines = - std::io::BufReader::new(stdout).lines().map(|r| r.unwrap()); - let stdout_first_line = stdout_lines.next().unwrap(); - assert_eq!(stdout_first_line, "hello"); - - child.kill().unwrap(); - child.wait().unwrap(); -} - -#[tokio::test] -async fn inspector_runtime_evaluate_does_not_crash() { - let child = util::deno_cmd() - .arg("repl") - .arg("--allow-read") - .arg(inspect_flag_with_unique_port("--inspect")) - .stdin(std::process::Stdio::piped()) - .piped_output() - .spawn() - .unwrap(); - - let mut tester = InspectorTester::create(child, ignore_script_parsed).await; - - let stdin = tester.child.stdin.take().unwrap(); - - tester.assert_stderr_for_inspect(); - assert_starts_with!(&tester.stdout_line(), "Deno"); - assert_eq!( - &tester.stdout_line(), - "exit using ctrl+d, ctrl+c, or close()" - ); - assert_eq!(&tester.stderr_line(), "Debugger session started."); - - tester - .send_many(&[ - json!({"id":1,"method":"Runtime.enable"}), - json!({"id":2,"method":"Debugger.enable"}), - ]) - .await; - tester.assert_received_messages( - &[ - r#"{"id":1,"result":{}}"#, - r#"{"id":2,"result":{"debuggerId":"#, - ], - &[ - r#"{"method":"Runtime.executionContextCreated","params":{"context":{"id":1,"#, - ], - ) - .await; - - tester - .send(json!({ - "id":3, - "method":"Runtime.compileScript", - "params":{ - "expression":"Deno.cwd()", - "sourceURL":"", - "persistScript":false, - "executionContextId":1 - } - })) - .await; - tester - .assert_received_messages(&[r#"{"id":3,"result":{}}"#], &[]) - .await; - tester - .send(json!({ - "id":4, - "method":"Runtime.evaluate", - "params":{ - "expression":"Deno.cwd()", - "objectGroup":"console", - "includeCommandLineAPI":true, - "silent":false, - "contextId":1, - "returnByValue":true, - "generatePreview":true, - "userGesture":true, - "awaitPromise":false, - "replMode":true - } - })) - .await; - tester - .assert_received_messages( - &[r#"{"id":4,"result":{"result":{"type":"string","value":""#], - &[], - ) - .await; - tester - .send(json!({ - "id":5, - "method":"Runtime.evaluate", - "params":{ - "expression":"console.error('done');", - "objectGroup":"console", - "includeCommandLineAPI":true, - "silent":false, - "contextId":1, - "returnByValue":true, - "generatePreview":true, - "userGesture":true, - "awaitPromise":false, - "replMode":true - } - })) - .await; - tester - .assert_received_messages( - &[r#"{"id":5,"result":{"result":{"type":"undefined"}}}"#], - &[r#"{"method":"Runtime.consoleAPICalled"#], - ) - .await; - assert_eq!(&tester.stderr_line(), "done"); - drop(stdin); - tester.child.wait().unwrap(); -} - -#[tokio::test] -async fn inspector_json() { - let script = util::testdata_path().join("inspector/inspector1.js"); - let mut child = util::deno_cmd() - .arg("run") - .arg(inspect_flag_with_unique_port("--inspect")) - .arg(script) - .stderr_piped() - .spawn() - .unwrap(); - - let stderr = child.stderr.as_mut().unwrap(); - let mut stderr_lines = - std::io::BufReader::new(stderr).lines().map(|r| r.unwrap()); - let ws_url = extract_ws_url_from_stderr(&mut stderr_lines); - let mut url = ws_url.clone(); - let _ = url.set_scheme("http"); - url.set_path("/json"); - let client = reqwest::Client::new(); - - // Ensure that the webSocketDebuggerUrl matches the host header - for (host, expected) in [ - (None, ws_url.as_str()), - (Some("some.random.host"), "ws://some.random.host/"), - (Some("some.random.host:1234"), "ws://some.random.host:1234/"), - (Some("[::1]:1234"), "ws://[::1]:1234/"), - ] { - let mut req = reqwest::Request::new(reqwest::Method::GET, url.clone()); - if let Some(host) = host { - req.headers_mut().insert( - reqwest::header::HOST, - reqwest::header::HeaderValue::from_static(host), - ); - } - let resp = client.execute(req).await.unwrap(); - assert_eq!(resp.status(), reqwest::StatusCode::OK); - let endpoint_list: Vec<deno_core::serde_json::Value> = - serde_json::from_str(&resp.text().await.unwrap()).unwrap(); - let matching_endpoint = endpoint_list.iter().find(|e| { - e["webSocketDebuggerUrl"] - .as_str() - .unwrap() - .contains(expected) - }); - assert!(matching_endpoint.is_some()); - } - - child.kill().unwrap(); -} - -#[tokio::test] -async fn inspector_json_list() { - let script = util::testdata_path().join("inspector/inspector1.js"); - let mut child = util::deno_cmd() - .arg("run") - .arg(inspect_flag_with_unique_port("--inspect")) - .arg(script) - .stderr_piped() - .spawn() - .unwrap(); - - let stderr = child.stderr.as_mut().unwrap(); - let mut stderr_lines = - std::io::BufReader::new(stderr).lines().map(|r| r.unwrap()); - let ws_url = extract_ws_url_from_stderr(&mut stderr_lines); - let mut url = ws_url.clone(); - let _ = url.set_scheme("http"); - url.set_path("/json/list"); - let resp = reqwest::get(url).await.unwrap(); - assert_eq!(resp.status(), reqwest::StatusCode::OK); - let endpoint_list: Vec<deno_core::serde_json::Value> = - serde_json::from_str(&resp.text().await.unwrap()).unwrap(); - let matching_endpoint = endpoint_list - .iter() - .find(|e| e["webSocketDebuggerUrl"] == ws_url.as_str()); - assert!(matching_endpoint.is_some()); - child.kill().unwrap(); -} - -#[tokio::test] -async fn inspector_connect_non_ws() { - // https://github.com/denoland/deno/issues/11449 - // Verify we don't panic if non-WS connection is being established - let script = util::testdata_path().join("inspector/inspector1.js"); - let mut child = util::deno_cmd() - .arg("run") - .arg(inspect_flag_with_unique_port("--inspect")) - .arg(script) - .stderr_piped() - .spawn() - .unwrap(); - - let stderr = child.stderr.as_mut().unwrap(); - let mut stderr_lines = - std::io::BufReader::new(stderr).lines().map(|r| r.unwrap()); - let mut ws_url = extract_ws_url_from_stderr(&mut stderr_lines); - // Change scheme to URL and try send a request. We're not interested - // in the request result, just that the process doesn't panic. - ws_url.set_scheme("http").unwrap(); - let resp = reqwest::get(ws_url).await.unwrap(); - assert_eq!("400 Bad Request", resp.status().to_string()); - child.kill().unwrap(); - child.wait().unwrap(); -} - -#[tokio::test] -async fn inspector_break_on_first_line_in_test() { - let script = util::testdata_path().join("inspector/inspector_test.js"); - let child = util::deno_cmd() - .arg("test") - .arg("--quiet") - .arg(inspect_flag_with_unique_port("--inspect-brk")) - .arg(script) - .env("NO_COLOR", "1") - .piped_output() - .spawn() - .unwrap(); - - let mut tester = InspectorTester::create(child, ignore_script_parsed).await; - - tester.assert_stderr_for_inspect_brk(); - - tester - .send_many(&[ - json!({"id":1,"method":"Runtime.enable"}), - json!({"id":2,"method":"Debugger.enable"}), - ]) - .await; - tester.assert_received_messages( - &[ - r#"{"id":1,"result":{}}"#, - r#"{"id":2,"result":{"debuggerId":"#, - ], - &[ - r#"{"method":"Runtime.executionContextCreated","params":{"context":{"id":1,"#, - ], - ) - .await; - - tester - .send(json!({"id":3,"method":"Runtime.runIfWaitingForDebugger"})) - .await; - tester - .assert_received_messages( - &[r#"{"id":3,"result":{}}"#], - &[r#"{"method":"Debugger.paused","#], - ) - .await; - - tester - .send(json!({ - "id":4, - "method":"Runtime.evaluate", - "params":{ - "expression":"1 + 1", - "contextId":1, - "includeCommandLineAPI":true, - "silent":false, - "returnByValue":true - } - })) - .await; - tester.assert_received_messages( - &[r#"{"id":4,"result":{"result":{"type":"number","value":2,"description":"2"}}}"#], - &[], - ) - .await; - - tester - .send(json!({"id":5,"method":"Debugger.resume"})) - .await; - tester - .assert_received_messages(&[r#"{"id":5,"result":{}}"#], &[]) - .await; - - assert_starts_with!(&tester.stdout_line(), "running 1 test from"); - let line = tester.stdout_line(); - assert!( - &line.contains("basic test ... ok"), - "Missing content: {line}" - ); - - tester.child.kill().unwrap(); - tester.child.wait().unwrap(); -} - -#[tokio::test] -async fn inspector_with_ts_files() { - let script = util::testdata_path().join("inspector/test.ts"); - let child = util::deno_cmd() - .arg("run") - .arg("--check") - .arg(inspect_flag_with_unique_port("--inspect-brk")) - .arg(script) - .piped_output() - .spawn() - .unwrap(); - - fn notification_filter(msg: &str) -> bool { - (msg.starts_with(r#"{"method":"Debugger.scriptParsed","#) - && msg.contains("testdata/inspector")) - || !msg.starts_with(r#"{"method":"Debugger.scriptParsed","#) - } - - let mut tester = InspectorTester::create(child, notification_filter).await; - - tester.assert_stderr_for_inspect_brk(); - - tester - .send_many(&[ - json!({"id":1,"method":"Runtime.enable"}), - json!({"id":2,"method":"Debugger.enable"}), - ]) - .await; - tester.assert_received_messages( - &[ - r#"{"id":1,"result":{}}"#, - ], - &[ - r#"{"method":"Runtime.executionContextCreated","params":{"context":{"id":1,"#, - ], - ) - .await; - - // receive messages with sources from this test - let script1 = tester.recv().await; - assert!(script1.contains("testdata/inspector/test.ts")); - let script1_id = { - let v: serde_json::Value = serde_json::from_str(&script1).unwrap(); - v["params"]["scriptId"].as_str().unwrap().to_string() - }; - let script2 = tester.recv().await; - assert!(script2.contains("testdata/inspector/foo.ts")); - let script2_id = { - let v: serde_json::Value = serde_json::from_str(&script2).unwrap(); - v["params"]["scriptId"].as_str().unwrap().to_string() - }; - let script3 = tester.recv().await; - assert!(script3.contains("testdata/inspector/bar.js")); - let script3_id = { - let v: serde_json::Value = serde_json::from_str(&script3).unwrap(); - v["params"]["scriptId"].as_str().unwrap().to_string() - }; - - tester - .assert_received_messages(&[r#"{"id":2,"result":{"debuggerId":"#], &[]) - .await; - - tester - .send(json!({"id":3,"method":"Runtime.runIfWaitingForDebugger"})) - .await; - tester - .assert_received_messages( - &[r#"{"id":3,"result":{}}"#], - &[r#"{"method":"Debugger.paused","#], - ) - .await; - - tester.send_many( - &[ - json!({"id":4,"method":"Debugger.getScriptSource","params":{"scriptId":script1_id.as_str()}}), - json!({"id":5,"method":"Debugger.getScriptSource","params":{"scriptId":script2_id.as_str()}}), - json!({"id":6,"method":"Debugger.getScriptSource","params":{"scriptId":script3_id.as_str()}}), - ]) - .await; - tester.assert_received_messages( - &[ - r#"{"id":4,"result":{"scriptSource":"import { foo } from \"./foo.ts\";\nimport { bar } from \"./bar.js\";\nconsole.log(foo());\nconsole.log(bar());\n//# sourceMappingURL=data:application/json;base64,"#, - r#"{"id":5,"result":{"scriptSource":"class Foo {\n hello() {\n return \"hello\";\n }\n}\nexport function foo() {\n const f = new Foo();\n return f.hello();\n}\n//# sourceMappingURL=data:application/json;base64,"#, - r#"{"id":6,"result":{"scriptSource":"export function bar() {\n return \"world\";\n}\n"#, - ], - &[], - ) - .await; - - tester - .send(json!({"id":7,"method":"Debugger.resume"})) - .await; - tester - .assert_received_messages(&[r#"{"id":7,"result":{}}"#], &[]) - .await; - - assert_eq!(&tester.stdout_line(), "hello"); - assert_eq!(&tester.stdout_line(), "world"); - - tester.assert_received_messages( - &[], - &[ - r#"{"method":"Debugger.resumed","params":{}}"#, - r#"{"method":"Runtime.consoleAPICalled","#, - r#"{"method":"Runtime.consoleAPICalled","#, - r#"{"method":"Runtime.executionContextDestroyed","params":{"executionContextId":1"#, - ], - ) - .await; - - assert_eq!( - &tester.stdout_line(), - "Program finished. Waiting for inspector to disconnect to exit the process..." - ); - - tester.child.kill().unwrap(); - tester.child.wait().unwrap(); -} - -#[tokio::test] -async fn inspector_memory() { - let script = util::testdata_path().join("inspector/memory.js"); - let child = util::deno_cmd() - .arg("run") - .arg(inspect_flag_with_unique_port("--inspect-brk")) - .arg(script) - .piped_output() - .spawn() - .unwrap(); - - let mut tester = InspectorTester::create(child, ignore_script_parsed).await; - - tester.assert_stderr_for_inspect_brk(); - - tester - .send_many(&[ - json!({"id":1,"method":"Runtime.enable"}), - json!({"id":2,"method":"Debugger.enable"}), - ]) - .await; - tester.assert_received_messages( - &[ - r#"{"id":1,"result":{}}"#, - r#"{"id":2,"result":{"debuggerId":"#, - ], - &[ - r#"{"method":"Runtime.executionContextCreated","params":{"context":{"id":1,"#, - ], - ) - .await; - - tester - .send_many(&[ - json!({"id":3,"method":"Runtime.runIfWaitingForDebugger"}), - json!({"id":4,"method":"HeapProfiler.enable"}), - ]) - .await; - tester - .assert_received_messages( - &[r#"{"id":3,"result":{}}"#, r#"{"id":4,"result":{}}"#], - &[r#"{"method":"Debugger.paused","#], - ) - .await; - - tester - .send(json!({"id":5,"method":"Runtime.getHeapUsage", "params": {}})) - .await; - - let json_msg = tester.recv_as_json().await; - assert_eq!(json_msg["id"].as_i64().unwrap(), 5); - let result = &json_msg["result"]; - assert!( - result["usedSize"].as_i64().unwrap() - <= result["totalSize"].as_i64().unwrap() - ); - - tester - .send(json!({ - "id":6, - "method":"HeapProfiler.takeHeapSnapshot", - "params": { - "reportProgress": true, - "treatGlobalObjectsAsRoots": true, - "captureNumberValue": false - } - })) - .await; - - let mut progress_report_completed = false; - loop { - let msg = tester.recv().await; - - // TODO(bartlomieju): can be abstracted - if !progress_report_completed - && msg.starts_with( - r#"{"method":"HeapProfiler.reportHeapSnapshotProgress","params""#, - ) - { - let json_msg: serde_json::Value = serde_json::from_str(&msg).unwrap(); - if let Some(finished) = json_msg["params"].get("finished") { - progress_report_completed = finished.as_bool().unwrap(); - } - continue; - } - - if msg.starts_with(r#"{"method":"HeapProfiler.reportHeapSnapshotProgress","params":{"done":"#,) { - continue; - } - - if msg.starts_with(r#"{"id":6,"result":{}}"#) { - assert!(progress_report_completed); - break; - } - } - - tester.child.kill().unwrap(); - tester.child.wait().unwrap(); -} - -#[tokio::test] -async fn inspector_profile() { - let script = util::testdata_path().join("inspector/memory.js"); - let child = util::deno_cmd() - .arg("run") - .arg(inspect_flag_with_unique_port("--inspect-brk")) - .arg(script) - .piped_output() - .spawn() - .unwrap(); - - let mut tester = InspectorTester::create(child, ignore_script_parsed).await; - - tester.assert_stderr_for_inspect_brk(); - - tester - .send_many(&[ - json!({"id":1,"method":"Runtime.enable"}), - json!({"id":2,"method":"Debugger.enable"}), - ]) - .await; - tester.assert_received_messages( - &[ - r#"{"id":1,"result":{}}"#, - r#"{"id":2,"result":{"debuggerId":"#, - ], - &[ - r#"{"method":"Runtime.executionContextCreated","params":{"context":{"id":1,"#, - ], - ) - .await; - - tester - .send_many(&[ - json!({"id":3,"method":"Runtime.runIfWaitingForDebugger"}), - json!({"id":4,"method":"Profiler.enable"}), - ]) - .await; - tester - .assert_received_messages( - &[r#"{"id":3,"result":{}}"#, r#"{"id":4,"result":{}}"#], - &[r#"{"method":"Debugger.paused","#], - ) - .await; - - tester.send_many( - &[ - json!({"id":5,"method":"Profiler.setSamplingInterval","params":{"interval": 100}}), - json!({"id":6,"method":"Profiler.start","params":{}}), - ], - ).await; - tester - .assert_received_messages( - &[r#"{"id":5,"result":{}}"#, r#"{"id":6,"result":{}}"#], - &[], - ) - .await; - - tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; - - tester - .send(json!({"id":7,"method":"Profiler.stop", "params": {}})) - .await; - let json_msg = tester.recv_as_json().await; - assert_eq!(json_msg["id"].as_i64().unwrap(), 7); - let result = &json_msg["result"]; - let profile = &result["profile"]; - assert!( - profile["startTime"].as_i64().unwrap() - < profile["endTime"].as_i64().unwrap() - ); - profile["samples"].as_array().unwrap(); - profile["nodes"].as_array().unwrap(); - - tester.child.kill().unwrap(); - tester.child.wait().unwrap(); -} - -// TODO(bartlomieju): this test became flaky on CI after wiring up "ext/node" -// compatibility layer. Can't reproduce this problem locally for either Mac M1 -// or Linux. Ignoring for now to unblock further integration of "ext/node". -#[ignore] -#[tokio::test] -async fn inspector_break_on_first_line_npm_esm() { - let context = TestContextBuilder::for_npm().build(); - let child = context - .new_command() - .args_vec([ - "run", - "--quiet", - &inspect_flag_with_unique_port("--inspect-brk"), - "npm:@denotest/bin/cli-esm", - "this", - "is", - "a", - "test", - ]) - .spawn_with_piped_output(); - - let mut tester = InspectorTester::create(child, ignore_script_parsed).await; - - tester.assert_stderr_for_inspect_brk(); - - tester - .send_many(&[ - json!({"id":1,"method":"Runtime.enable"}), - json!({"id":2,"method":"Debugger.enable"}), - ]) - .await; - tester.assert_received_messages( - &[ - r#"{"id":1,"result":{}}"#, - r#"{"id":2,"result":{"debuggerId":"#, - ], - &[ - r#"{"method":"Runtime.executionContextCreated","params":{"context":{"id":1,"#, - ], - ) - .await; - - tester - .send(json!({"id":3,"method":"Runtime.runIfWaitingForDebugger"})) - .await; - tester - .assert_received_messages( - &[r#"{"id":3,"result":{}}"#], - &[r#"{"method":"Debugger.paused","#], - ) - .await; - - tester - .send(json!({"id":4,"method":"Debugger.resume"})) - .await; - tester - .assert_received_messages(&[r#"{"id":4,"result":{}}"#], &[]) - .await; - - assert_eq!(&tester.stdout_line(), "this"); - assert_eq!(&tester.stdout_line(), "is"); - assert_eq!(&tester.stdout_line(), "a"); - assert_eq!(&tester.stdout_line(), "test"); - - tester.child.kill().unwrap(); - tester.child.wait().unwrap(); -} - -// TODO(bartlomieju): this test became flaky on CI after wiring up "ext/node" -// compatibility layer. Can't reproduce this problem locally for either Mac M1 -// or Linux. Ignoring for now to unblock further integration of "ext/node". -#[ignore] -#[tokio::test] -async fn inspector_break_on_first_line_npm_cjs() { - let context = TestContextBuilder::for_npm().build(); - let child = context - .new_command() - .args_vec([ - "run", - "--quiet", - &inspect_flag_with_unique_port("--inspect-brk"), - "npm:@denotest/bin/cli-cjs", - "this", - "is", - "a", - "test", - ]) - .spawn_with_piped_output(); - - let mut tester = InspectorTester::create(child, ignore_script_parsed).await; - - tester.assert_stderr_for_inspect_brk(); - - tester - .send_many(&[ - json!({"id":1,"method":"Runtime.enable"}), - json!({"id":2,"method":"Debugger.enable"}), - ]) - .await; - tester.assert_received_messages( - &[ - r#"{"id":1,"result":{}}"#, - r#"{"id":2,"result":{"debuggerId":"#, - ], - &[ - r#"{"method":"Runtime.executionContextCreated","params":{"context":{"id":1,"#, - ], - ) - .await; - - tester - .send(json!({"id":3,"method":"Runtime.runIfWaitingForDebugger"})) - .await; - tester - .assert_received_messages( - &[r#"{"id":3,"result":{}}"#], - &[r#"{"method":"Debugger.paused","#], - ) - .await; - - tester - .send(json!({"id":4,"method":"Debugger.resume"})) - .await; - tester - .assert_received_messages(&[r#"{"id":4,"result":{}}"#], &[]) - .await; - - assert_eq!(&tester.stdout_line(), "this"); - assert_eq!(&tester.stdout_line(), "is"); - assert_eq!(&tester.stdout_line(), "a"); - assert_eq!(&tester.stdout_line(), "test"); - - tester.child.kill().unwrap(); - tester.child.wait().unwrap(); -} - -// TODO(bartlomieju): this test became flaky on CI after wiring up "ext/node" -// compatibility layer. Can't reproduce this problem locally for either Mac M1 -// or Linux. Ignoring for now to unblock further integration of "ext/node". -#[ignore] -#[tokio::test] -async fn inspector_error_with_npm_import() { - let script = util::testdata_path().join("inspector/error_with_npm_import.js"); - let context = TestContextBuilder::for_npm().build(); - let child = context - .new_command() - .args_vec([ - "run", - "--quiet", - "-A", - &inspect_flag_with_unique_port("--inspect-brk"), - &script.to_string_lossy(), - ]) - .spawn_with_piped_output(); - - let mut tester = InspectorTester::create(child, ignore_script_parsed).await; - - tester.assert_stderr_for_inspect_brk(); - - tester - .send_many(&[ - json!({"id":1,"method":"Runtime.enable"}), - json!({"id":2,"method":"Debugger.enable"}), - ]) - .await; - tester.assert_received_messages( - &[ - r#"{"id":1,"result":{}}"#, - r#"{"id":2,"result":{"debuggerId":"#, - ], - &[ - r#"{"method":"Runtime.executionContextCreated","params":{"context":{"id":1,"#, - ], - ) - .await; - - tester - .send(json!({"id":3,"method":"Runtime.runIfWaitingForDebugger"})) - .await; - tester - .assert_received_messages( - &[r#"{"id":3,"result":{}}"#], - &[r#"{"method":"Debugger.paused","#], - ) - .await; - - tester - .send(json!({"id":4,"method":"Debugger.resume"})) - .await; - tester - .assert_received_messages( - &[r#"{"id":4,"result":{}}"#], - &[r#"{"method":"Runtime.exceptionThrown","#], - ) - .await; - assert_eq!(&tester.stderr_line(), "Debugger session started."); - assert_eq!(&tester.stderr_line(), "error: Uncaught Error: boom!"); - - assert_eq!(tester.child.wait().unwrap().code(), Some(1)); -} - -#[tokio::test] -async fn inspector_wait() { - let script = util::testdata_path().join("inspector/inspect_wait.js"); - let test_context = TestContextBuilder::new().use_temp_cwd().build(); - let temp_dir = test_context.temp_dir(); - - let child = test_context - .new_command() - .args_vec([ - "run", - "--quiet", - "-A", - &inspect_flag_with_unique_port("--inspect-wait"), - &script.to_string_lossy(), - ]) - .spawn_with_piped_output(); - - tokio::time::sleep(tokio::time::Duration::from_millis(300)).await; - assert!(!temp_dir.path().join("hello.txt").exists()); - - let mut tester = InspectorTester::create(child, ignore_script_parsed).await; - - tester.assert_stderr_for_inspect_brk(); - tester - .send_many(&[ - json!({"id":1,"method":"Runtime.enable"}), - json!({"id":2,"method":"Debugger.enable"}), - ]) - .await; - tester.assert_received_messages( - &[ - r#"{"id":1,"result":{}}"#, - r#"{"id":2,"result":{"debuggerId":"#, - ], - &[ - r#"{"method":"Runtime.executionContextCreated","params":{"context":{"id":1,"#, - ], - ) - .await; - // TODO(bartlomieju): ideally this shouldn't be needed, but currently there's - // no way to express that in inspector code. Most clients always send this - // message anyway. - tester - .send(json!({"id":3,"method":"Runtime.runIfWaitingForDebugger"})) - .await; - tester - .assert_received_messages(&[r#"{"id":3,"result":{}}"#], &[]) - .await; - assert_eq!(&tester.stderr_line(), "Debugger session started."); - tokio::time::sleep(tokio::time::Duration::from_millis(300)).await; - assert_eq!(&tester.stderr_line(), "did run"); - assert!(temp_dir.path().join("hello.txt").exists()); - tester.child.kill().unwrap(); -} |