diff options
author | Bartek IwaĆczuk <biwanczuk@gmail.com> | 2022-11-26 02:29:56 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-11-26 02:29:56 +0100 |
commit | 7e0c55818721b78109e2bb58bf14f814036bf06d (patch) | |
tree | 8038d3eebdbe8b3162b792f623499e5a749fc2e8 /cli/tests/inspector_tests.rs | |
parent | 0c0af67f89b610eb61e4f66a5dbf665474aa9383 (diff) |
tests: move integration tests to separate modules (#16816)
Diffstat (limited to 'cli/tests/inspector_tests.rs')
-rw-r--r-- | cli/tests/inspector_tests.rs | 1123 |
1 files changed, 1123 insertions, 0 deletions
diff --git a/cli/tests/inspector_tests.rs b/cli/tests/inspector_tests.rs new file mode 100644 index 000000000..febff7c28 --- /dev/null +++ b/cli/tests/inspector_tests.rs @@ -0,0 +1,1123 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use deno_core::futures; +use deno_core::futures::prelude::*; +use deno_core::futures::stream::SplitSink; +use deno_core::serde_json; +use deno_core::url; +use deno_runtime::deno_fetch::reqwest; +use deno_runtime::deno_websocket::tokio_tungstenite; +use deno_runtime::deno_websocket::tokio_tungstenite::tungstenite; +use std::io::BufRead; +use std::pin::Pin; +use test_util as util; +use tokio::net::TcpStream; + +mod inspector { + use super::*; + + macro_rules! assert_starts_with { + ($string:expr, $($test:expr),+) => { + let string = $string; // This might be a function call or something + if !($(string.starts_with($test))||+) { + panic!("{:?} does not start with {:?}", string, [$($test),+]); + } + } +} + + fn inspect_flag_with_unique_port(flag_prefix: &str) -> String { + use std::sync::atomic::{AtomicU16, Ordering}; + static PORT: AtomicU16 = AtomicU16::new(9229); + let port = PORT.fetch_add(1, Ordering::Relaxed); + format!("{}=127.0.0.1:{}", flag_prefix, 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") { + continue; + } + + return line; + } + } + + 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 assert_stderr_for_inspect( + stderr_lines: &mut impl std::iter::Iterator<Item = String>, + ) { + assert_stderr( + stderr_lines, + &["Visit chrome://inspect to connect to the debugger."], + ); + } + + fn assert_stderr_for_inspect_brk( + stderr_lines: &mut impl std::iter::Iterator<Item = String>, + ) { + assert_stderr( + stderr_lines, + &[ + "Visit chrome://inspect to connect to the debugger.", + "Deno is waiting for debugger to connect.", + ], + ); + } + + async fn assert_inspector_messages( + socket_tx: &mut SplitSink< + tokio_tungstenite::WebSocketStream< + tokio_tungstenite::MaybeTlsStream<TcpStream>, + >, + tungstenite::Message, + >, + messages: &[&str], + socket_rx: &mut Pin<Box<dyn Stream<Item = String>>>, + responses: &[&str], + notifications: &[&str], + ) { + for msg in messages { + socket_tx.send(msg.to_string().into()).await.unwrap(); + } + + let expected_messages = responses.len() + notifications.len(); + let mut responses_idx = 0; + let mut notifications_idx = 0; + + for _ in 0..expected_messages { + let msg = socket_rx.next().await.unwrap(); + + 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; + } + } + } + + #[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(std::process::Stdio::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); + + // We use tokio_tungstenite as a websocket client because warp (which is + // a dependency of Deno) uses it. + let (_socket, response) = + tokio_tungstenite::connect_async(ws_url).await.unwrap(); + 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 mut child = util::deno_cmd() + .arg("run") + .arg(inspect_flag_with_unique_port("--inspect-brk")) + .arg(script) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::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) = + tokio_tungstenite::connect_async(ws_url).await.unwrap(); + assert_eq!(response.status(), 101); // Switching protocols. + + let (mut socket_tx, socket_rx) = socket.split(); + let mut socket_rx = socket_rx + .map(|msg| msg.unwrap().to_string()) + .filter(|msg| { + let pass = !msg.starts_with(r#"{"method":"Debugger.scriptParsed","#); + futures::future::ready(pass) + }) + .boxed_local(); + + let stdout = child.stdout.as_mut().unwrap(); + let mut stdout_lines = + std::io::BufReader::new(stdout).lines().map(|r| r.unwrap()); + + assert_stderr_for_inspect_brk(&mut stderr_lines); + + assert_inspector_messages( + &mut socket_tx, + &[ + r#"{"id":1,"method":"Runtime.enable"}"#, + r#"{"id":2,"method":"Debugger.enable"}"#, + ], + &mut socket_rx, + &[ + r#"{"id":1,"result":{}}"#, + r#"{"id":2,"result":{"debuggerId":"#, + ], + &[ + r#"{"method":"Runtime.executionContextCreated","params":{"context":{"id":1,"#, + ], + ) + .await; + + assert_inspector_messages( + &mut socket_tx, + &[r#"{"id":3,"method":"Runtime.runIfWaitingForDebugger"}"#], + &mut socket_rx, + &[r#"{"id":3,"result":{}}"#], + &[r#"{"method":"Debugger.paused","#], + ) + .await; + + assert_inspector_messages( + &mut socket_tx, + &[ + r#"{"id":4,"method":"Runtime.evaluate","params":{"expression":"Deno.core.print(\"hello from the inspector\\n\")","contextId":1,"includeCommandLineAPI":true,"silent":false,"returnByValue":true}}"#, + ], + &mut socket_rx, + &[r#"{"id":4,"result":{"result":{"type":"undefined"}}}"#], + &[], + ) + .await; + + assert_eq!(&stdout_lines.next().unwrap(), "hello from the inspector"); + + assert_inspector_messages( + &mut socket_tx, + &[r#"{"id":5,"method":"Debugger.resume"}"#], + &mut socket_rx, + &[r#"{"id":5,"result":{}}"#], + &[], + ) + .await; + + assert_eq!(&stdout_lines.next().unwrap(), "hello from the script"); + + child.kill().unwrap(); + child.wait().unwrap(); + } + + #[tokio::test] + async fn inspector_pause() { + 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(std::process::Stdio::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); + + // We use tokio_tungstenite as a websocket client because warp (which is + // a dependency of Deno) uses it. + let (mut socket, _) = + tokio_tungstenite::connect_async(ws_url).await.unwrap(); + + /// Returns the next websocket message as a string ignoring + /// Debugger.scriptParsed messages. + async fn ws_read_msg( + socket: &mut tokio_tungstenite::WebSocketStream< + tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>, + >, + ) -> String { + use deno_core::futures::stream::StreamExt; + while let Some(msg) = socket.next().await { + let msg = msg.unwrap().to_string(); + // FIXME(bartlomieju): fails because there's a file loaded + // called 150_errors.js + // assert!(!msg.contains("error")); + if !msg.contains("Debugger.scriptParsed") { + return msg; + } + } + unreachable!() + } + + socket + .send(r#"{"id":6,"method":"Debugger.enable"}"#.into()) + .await + .unwrap(); + + let msg = ws_read_msg(&mut socket).await; + println!("response msg 1 {}", msg); + assert_starts_with!(msg, r#"{"id":6,"result":{"debuggerId":"#); + + socket + .send(r#"{"id":31,"method":"Debugger.pause"}"#.into()) + .await + .unwrap(); + + let msg = ws_read_msg(&mut socket).await; + println!("response msg 2 {}", msg); + assert_eq!(msg, r#"{"id":31,"result":{}}"#); + + 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`. + if cfg!(target_os = "linux") + && std::env::var_os("WSL_DISTRO_NAME").is_some() + { + 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(std::process::Stdio::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(std::process::Stdio::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 mut child = util::deno_cmd() + .arg("run") + .arg(inspect_flag_with_unique_port("--inspect-brk")) + .env("NO_COLOR", "1") + .arg(script) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::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) = + tokio_tungstenite::connect_async(ws_url).await.unwrap(); + assert_eq!(response.status(), 101); // Switching protocols. + + let (mut socket_tx, socket_rx) = socket.split(); + let mut socket_rx = socket_rx + .map(|msg| msg.unwrap().to_string()) + .filter(|msg| { + let pass = !msg.starts_with(r#"{"method":"Debugger.scriptParsed","#); + futures::future::ready(pass) + }) + .boxed_local(); + + let stdout = child.stdout.as_mut().unwrap(); + let mut stdout_lines = + std::io::BufReader::new(stdout).lines().map(|r| r.unwrap()); + + assert_stderr_for_inspect_brk(&mut stderr_lines); + + assert_inspector_messages( + &mut socket_tx, + &[ + r#"{"id":1,"method":"Runtime.enable"}"#, + r#"{"id":2,"method":"Debugger.enable"}"#, + ], + &mut socket_rx, + &[ + r#"{"id":1,"result":{}}"#, + r#"{"id":2,"result":{"debuggerId":"# + ], + &[ + r#"{"method":"Runtime.executionContextCreated","params":{"context":{"id":1,"# + ], + ) + .await; + + assert_inspector_messages( + &mut socket_tx, + &[r#"{"id":3,"method":"Runtime.runIfWaitingForDebugger"}"#], + &mut socket_rx, + &[r#"{"id":3,"result":{}}"#], + &[r#"{"method":"Debugger.paused","#], + ) + .await; + + assert_inspector_messages( + &mut socket_tx, + &[r#"{"id":4,"method":"Debugger.resume"}"#], + &mut socket_rx, + &[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!(stdout_lines.next().unwrap(), s); + + assert_inspector_messages( + &mut socket_tx, + &[], + &mut socket_rx, + &[], + &[ + r#"{"method":"Runtime.consoleAPICalled","#, + r#"{"method":"Debugger.paused","#, + ], + ) + .await; + + assert_inspector_messages( + &mut socket_tx, + &[&format!( + r#"{{"id":{},"method":"Debugger.resume"}}"#, + request_id + )], + &mut socket_rx, + &[&format!(r#"{{"id":{},"result":{{}}}}"#, request_id)], + &[r#"{"method":"Debugger.resumed","params":{}}"#], + ) + .await; + } + + // Check that we can gracefully close the websocket connection. + socket_tx.close().await.unwrap(); + socket_rx.for_each(|_| async {}).await; + + assert_eq!(&stdout_lines.next().unwrap(), "done"); + assert!(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) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::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 _ = 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 mut child = util::deno_cmd() + .arg("repl") + .arg(inspect_flag_with_unique_port("--inspect")) + .stdin(std::process::Stdio::piped()) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap(); + + let stderr = child.stderr.as_mut().unwrap(); + let mut stderr_lines = std::io::BufReader::new(stderr) + .lines() + .map(|r| r.unwrap()) + .filter(|s| s.as_str() != "Debugger session started."); + let ws_url = extract_ws_url_from_stderr(&mut stderr_lines); + + let (socket, response) = + tokio_tungstenite::connect_async(ws_url).await.unwrap(); + assert_eq!(response.status(), 101); // Switching protocols. + + let (mut socket_tx, socket_rx) = socket.split(); + let mut socket_rx = socket_rx + .map(|msg| msg.unwrap().to_string()) + .filter(|msg| { + let pass = !msg.starts_with(r#"{"method":"Debugger.scriptParsed","#); + futures::future::ready(pass) + }) + .boxed_local(); + + let stdin = child.stdin.take().unwrap(); + + let stdout = child.stdout.as_mut().unwrap(); + let mut stdout_lines = std::io::BufReader::new(stdout) + .lines() + .map(|r| r.unwrap()) + .filter(|s| !s.starts_with("Deno ")); + + assert_stderr_for_inspect(&mut stderr_lines); + + assert_inspector_messages( + &mut socket_tx, + &[ + r#"{"id":1,"method":"Runtime.enable"}"#, + r#"{"id":2,"method":"Debugger.enable"}"#, + ], + &mut socket_rx, + &[ + r#"{"id":1,"result":{}}"#, + r#"{"id":2,"result":{"debuggerId":"#, + ], + &[ + r#"{"method":"Runtime.executionContextCreated","params":{"context":{"id":1,"#, + ], + ) + .await; + + assert_eq!( + &stdout_lines.next().unwrap(), + "exit using ctrl+d, ctrl+c, or close()" + ); + + assert_inspector_messages( + &mut socket_tx, + &[ + r#"{"id":3,"method":"Runtime.compileScript","params":{"expression":"Deno.cwd()","sourceURL":"","persistScript":false,"executionContextId":1}}"#, + ], + &mut socket_rx, + &[r#"{"id":3,"result":{}}"#], &[] + ).await; + + assert_inspector_messages( + &mut socket_tx, + &[ + r#"{"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}}"#, + ], + &mut socket_rx, + &[r#"{"id":4,"result":{"result":{"type":"string","value":""#], + &[], + ).await; + + assert_inspector_messages( + &mut socket_tx, + &[ + r#"{"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}}"#, + ], + &mut socket_rx, + &[r#"{"id":5,"result":{"result":{"type":"undefined"}}}"#], + &[r#"{"method":"Runtime.consoleAPICalled"#], + ).await; + + assert_eq!(&stderr_lines.next().unwrap(), "done"); + + drop(stdin); + 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(std::process::Stdio::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 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_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(std::process::Stdio::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(std::process::Stdio::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] + #[ignore] // https://github.com/denoland/deno/issues/13491 + async fn inspector_break_on_first_line_in_test() { + let script = util::testdata_path().join("inspector/inspector_test.js"); + let mut child = util::deno_cmd() + .arg("test") + .arg(inspect_flag_with_unique_port("--inspect-brk")) + .arg(script) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::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) = + tokio_tungstenite::connect_async(ws_url).await.unwrap(); + assert_eq!(response.status(), 101); // Switching protocols. + + let (mut socket_tx, socket_rx) = socket.split(); + let mut socket_rx = socket_rx + .map(|msg| msg.unwrap().to_string()) + .filter(|msg| { + let pass = !msg.starts_with(r#"{"method":"Debugger.scriptParsed","#); + futures::future::ready(pass) + }) + .boxed_local(); + + let stdout = child.stdout.as_mut().unwrap(); + let mut stdout_lines = + std::io::BufReader::new(stdout).lines().map(|r| r.unwrap()); + + assert_stderr_for_inspect_brk(&mut stderr_lines); + + assert_inspector_messages( + &mut socket_tx, + &[ + r#"{"id":1,"method":"Runtime.enable"}"#, + r#"{"id":2,"method":"Debugger.enable"}"#, + ], + &mut socket_rx, + &[ + r#"{"id":1,"result":{}}"#, + r#"{"id":2,"result":{"debuggerId":"#, + ], + &[ + r#"{"method":"Runtime.executionContextCreated","params":{"context":{"id":1,"#, + ], + ) + .await; + + assert_inspector_messages( + &mut socket_tx, + &[r#"{"id":3,"method":"Runtime.runIfWaitingForDebugger"}"#], + &mut socket_rx, + &[r#"{"id":3,"result":{}}"#], + &[r#"{"method":"Debugger.paused","#], + ) + .await; + + assert_inspector_messages( + &mut socket_tx, + &[ + r#"{"id":4,"method":"Runtime.evaluate","params":{"expression":"Deno.core.print(\"hello from the inspector\\n\")","contextId":1,"includeCommandLineAPI":true,"silent":false,"returnByValue":true}}"#, + ], + &mut socket_rx, + &[r#"{"id":4,"result":{"result":{"type":"undefined"}}}"#], + &[], + ) + .await; + + assert_eq!(&stdout_lines.next().unwrap(), "hello from the inspector"); + + assert_inspector_messages( + &mut socket_tx, + &[r#"{"id":5,"method":"Debugger.resume"}"#], + &mut socket_rx, + &[r#"{"id":5,"result":{}}"#], + &[], + ) + .await; + + assert_starts_with!(&stdout_lines.next().unwrap(), "running 1 test from"); + assert!(&stdout_lines + .next() + .unwrap() + .contains("test has finished running")); + + child.kill().unwrap(); + child.wait().unwrap(); + } + + #[tokio::test] + async fn inspector_with_ts_files() { + let script = util::testdata_path().join("inspector/test.ts"); + let mut child = util::deno_cmd() + .arg("run") + .arg("--check") + .arg(inspect_flag_with_unique_port("--inspect-brk")) + .arg(script) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::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) = + tokio_tungstenite::connect_async(ws_url).await.unwrap(); + assert_eq!(response.status(), 101); // Switching protocols. + + let (mut socket_tx, socket_rx) = socket.split(); + let mut socket_rx = socket_rx + .map(|msg| msg.unwrap().to_string()) + .filter(|msg| { + let pass = (msg.starts_with(r#"{"method":"Debugger.scriptParsed","#) + && msg.contains("testdata/inspector")) + || !msg.starts_with(r#"{"method":"Debugger.scriptParsed","#); + futures::future::ready(pass) + }) + .boxed_local(); + + let stdout = child.stdout.as_mut().unwrap(); + let mut stdout_lines = + std::io::BufReader::new(stdout).lines().map(|r| r.unwrap()); + + assert_stderr_for_inspect_brk(&mut stderr_lines); + + assert_inspector_messages( + &mut socket_tx, + &[ + r#"{"id":1,"method":"Runtime.enable"}"#, + r#"{"id":2,"method":"Debugger.enable"}"#, + ], + &mut socket_rx, + &[ + r#"{"id":1,"result":{}}"#, + ], + &[ + r#"{"method":"Runtime.executionContextCreated","params":{"context":{"id":1,"#, + ], + ) + .await; + + // receive messages with sources from this test + let script1 = socket_rx.next().await.unwrap(); + 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 = socket_rx.next().await.unwrap(); + 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 = socket_rx.next().await.unwrap(); + 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() + }; + + assert_inspector_messages( + &mut socket_tx, + &[], + &mut socket_rx, + &[r#"{"id":2,"result":{"debuggerId":"#], + &[], + ) + .await; + + assert_inspector_messages( + &mut socket_tx, + &[r#"{"id":3,"method":"Runtime.runIfWaitingForDebugger"}"#], + &mut socket_rx, + &[r#"{"id":3,"result":{}}"#], + &[r#"{"method":"Debugger.paused","#], + ) + .await; + + assert_inspector_messages( + &mut socket_tx, + &[ + &format!(r#"{{"id":4,"method":"Debugger.getScriptSource","params":{{"scriptId":"{}"}}}}"#, script1_id), + &format!(r#"{{"id":5,"method":"Debugger.getScriptSource","params":{{"scriptId":"{}"}}}}"#, script2_id), + &format!(r#"{{"id":6,"method":"Debugger.getScriptSource","params":{{"scriptId":"{}"}}}}"#, script3_id), + ], + &mut socket_rx, + &[ + 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; + + assert_inspector_messages( + &mut socket_tx, + &[r#"{"id":7,"method":"Debugger.resume"}"#], + &mut socket_rx, + &[r#"{"id":7,"result":{}}"#], + &[], + ) + .await; + + assert_eq!(&stdout_lines.next().unwrap(), "hello"); + assert_eq!(&stdout_lines.next().unwrap(), "world"); + + child.kill().unwrap(); + child.wait().unwrap(); + } + + #[tokio::test] + async fn inspector_memory() { + let script = util::testdata_path().join("inspector/memory.js"); + let mut child = util::deno_cmd() + .arg("run") + .arg(inspect_flag_with_unique_port("--inspect-brk")) + .arg(script) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::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) = + tokio_tungstenite::connect_async(ws_url).await.unwrap(); + assert_eq!(response.status(), 101); // Switching protocols. + + let (mut socket_tx, socket_rx) = socket.split(); + let mut socket_rx = socket_rx + .map(|msg| msg.unwrap().to_string()) + .filter(|msg| { + let pass = !msg.starts_with(r#"{"method":"Debugger.scriptParsed","#); + futures::future::ready(pass) + }) + .boxed_local(); + + assert_stderr_for_inspect_brk(&mut stderr_lines); + + assert_inspector_messages( + &mut socket_tx, + &[ + r#"{"id":1,"method":"Runtime.enable"}"#, + r#"{"id":2,"method":"Debugger.enable"}"#, + + ], + &mut socket_rx, + &[ + r#"{"id":1,"result":{}}"#, + r#"{"id":2,"result":{"debuggerId":"#, + ], + &[ + r#"{"method":"Runtime.executionContextCreated","params":{"context":{"id":1,"#, + ], + ) + .await; + + assert_inspector_messages( + &mut socket_tx, + &[ + r#"{"id":3,"method":"Runtime.runIfWaitingForDebugger"}"#, + r#"{"id":4,"method":"HeapProfiler.enable"}"#, + ], + &mut socket_rx, + &[r#"{"id":3,"result":{}}"#, r#"{"id":4,"result":{}}"#], + &[r#"{"method":"Debugger.paused","#], + ) + .await; + + socket_tx + .send( + r#"{"id":5,"method":"Runtime.getHeapUsage", "params": {}}"# + .to_string() + .into(), + ) + .await + .unwrap(); + let msg = socket_rx.next().await.unwrap(); + let json_msg: serde_json::Value = serde_json::from_str(&msg).unwrap(); + 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() + ); + + socket_tx.send( + r#"{"id":6,"method":"HeapProfiler.takeHeapSnapshot","params": {"reportProgress": true, "treatGlobalObjectsAsRoots": true, "captureNumberValue": false}}"# + .to_string().into() + ).await.unwrap(); + + let mut progress_report_completed = false; + loop { + let msg = socket_rx.next().await.unwrap(); + + 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; + } + } + + child.kill().unwrap(); + child.wait().unwrap(); + } + + #[tokio::test] + async fn inspector_profile() { + let script = util::testdata_path().join("inspector/memory.js"); + let mut child = util::deno_cmd() + .arg("run") + .arg(inspect_flag_with_unique_port("--inspect-brk")) + .arg(script) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::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) = + tokio_tungstenite::connect_async(ws_url).await.unwrap(); + assert_eq!(response.status(), 101); // Switching protocols. + + let (mut socket_tx, socket_rx) = socket.split(); + let mut socket_rx = socket_rx + .map(|msg| msg.unwrap().to_string()) + .filter(|msg| { + let pass = !msg.starts_with(r#"{"method":"Debugger.scriptParsed","#); + futures::future::ready(pass) + }) + .boxed_local(); + + assert_stderr_for_inspect_brk(&mut stderr_lines); + + assert_inspector_messages( + &mut socket_tx, + &[ + r#"{"id":1,"method":"Runtime.enable"}"#, + r#"{"id":2,"method":"Debugger.enable"}"#, + + ], + &mut socket_rx, + &[ + r#"{"id":1,"result":{}}"#, + r#"{"id":2,"result":{"debuggerId":"#, + ], + &[ + r#"{"method":"Runtime.executionContextCreated","params":{"context":{"id":1,"#, + ], + ) + .await; + + assert_inspector_messages( + &mut socket_tx, + &[ + r#"{"id":3,"method":"Runtime.runIfWaitingForDebugger"}"#, + r#"{"id":4,"method":"Profiler.enable"}"#, + ], + &mut socket_rx, + &[r#"{"id":3,"result":{}}"#, r#"{"id":4,"result":{}}"#], + &[r#"{"method":"Debugger.paused","#], + ) + .await; + + assert_inspector_messages( + &mut socket_tx, + &[ + r#"{"id":5,"method":"Profiler.setSamplingInterval","params":{"interval": 100}}"#, + r#"{"id":6,"method":"Profiler.start","params":{}}"#, + ], + &mut socket_rx, + &[r#"{"id":5,"result":{}}"#, r#"{"id":6,"result":{}}"#], + &[], + ) + .await; + + tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; + + socket_tx + .send( + r#"{"id":7,"method":"Profiler.stop", "params": {}}"#.to_string().into(), + ) + .await + .unwrap(); + let msg = socket_rx.next().await.unwrap(); + let json_msg: serde_json::Value = serde_json::from_str(&msg).unwrap(); + 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(); + + child.kill().unwrap(); + child.wait().unwrap(); + } +} |