diff options
author | Bartek IwaĆczuk <biwanczuk@gmail.com> | 2021-12-30 16:47:58 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-12-30 16:47:58 +0100 |
commit | 1adf8ee54529b4e754d4f0513cc7763b3db54199 (patch) | |
tree | 5dc02ac462ecfcbc2f6c6ca7fc9f675706867e4d /cli/tests/integration/inspector_tests.rs | |
parent | de9778949b8eb6eedaf490488ed2a11fa304d9fb (diff) |
fix(core): inspector works if no "Runtime.runIfWaitingForDebugger" message is sent (#13191)
This commit changes flow in inspector code to no longer require
"Runtime.runIfWaitingForDebugger" message to complete a handshake.
Even though clients like Chrome DevTools always send this message on startup,
it is against the protocol to require this message to start an inspector
session.
Instead "Runtime.runIfWaitingForDebugger" is required only when running with
"--inspect-brk" flag, which matches behavior of Node.js.
Diffstat (limited to 'cli/tests/integration/inspector_tests.rs')
-rw-r--r-- | cli/tests/integration/inspector_tests.rs | 469 |
1 files changed, 307 insertions, 162 deletions
diff --git a/cli/tests/integration/inspector_tests.rs b/cli/tests/integration/inspector_tests.rs index f5365ac99..f6b95c1b4 100644 --- a/cli/tests/integration/inspector_tests.rs +++ b/cli/tests/integration/inspector_tests.rs @@ -2,12 +2,16 @@ 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; fn inspect_flag_with_unique_port(flag_prefix: &str) -> String { use std::sync::atomic::{AtomicU16, Ordering}; @@ -28,6 +32,71 @@ fn extract_ws_url_from_stderr( url::Url::parse(ws_url).unwrap() } +fn assert_stderr_for_inspect( + stderr_lines: &mut impl std::iter::Iterator<Item = String>, +) { + assert_eq!( + &stderr_lines.next().unwrap(), + "Visit chrome://inspect to connect to the debugger." + ); +} + +fn assert_stderr_for_inspect_brk( + stderr_lines: &mut impl std::iter::Iterator<Item = String>, +) { + assert_eq!( + &stderr_lines.next().unwrap(), + "Visit chrome://inspect to connect to the debugger." + ); + assert_eq!( + &stderr_lines.next().unwrap(), + "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("inspector1.js"); @@ -54,14 +123,6 @@ async fn inspector_connect() { child.wait().unwrap(); } -#[derive(Debug)] -enum TestStep { - StdOut(&'static str), - StdErr(&'static str), - WsRecv(&'static str), - WsSend(&'static str), -} - #[tokio::test] async fn inspector_break_on_first_line() { let script = util::testdata_path().join("inspector2.js"); @@ -85,48 +146,69 @@ async fn inspector_break_on_first_line() { 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 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()); - use TestStep::*; - let test_steps = vec![ - StdErr("Visit chrome://inspect to connect to the debugger."), - StdErr("Deno is waiting for debugger to connect."), - WsSend(r#"{"id":1,"method":"Runtime.enable"}"#), - WsSend(r#"{"id":2,"method":"Debugger.enable"}"#), - WsRecv( + 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,"#, - ), - WsRecv(r#"{"id":1,"result":{}}"#), - WsRecv(r#"{"id":2,"result":{"debuggerId":"#), - WsSend(r#"{"id":3,"method":"Runtime.runIfWaitingForDebugger"}"#), - WsRecv(r#"{"id":3,"result":{}}"#), - WsRecv(r#"{"method":"Debugger.paused","#), - WsSend( + ], + ) + .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}}"#, - ), - WsRecv(r#"{"id":4,"result":{"result":{"type":"undefined"}}}"#), - StdOut("hello from the inspector"), - WsSend(r#"{"id":5,"method":"Debugger.resume"}"#), - WsRecv(r#"{"id":5,"result":{}}"#), - StdOut("hello from the script"), - ]; - - for step in test_steps { - match step { - StdErr(s) => assert_eq!(&stderr_lines.next().unwrap(), s), - StdOut(s) => assert_eq!(&stdout_lines.next().unwrap(), s), - WsRecv(s) => assert!(socket_rx.next().await.unwrap().starts_with(s)), - WsSend(s) => socket_tx.send(s.into()).await.unwrap(), - } - } + ], + &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(); @@ -266,63 +348,84 @@ async fn inspector_does_not_hang() { 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 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()); - use TestStep::*; - let test_steps = vec![ - StdErr("Visit chrome://inspect to connect to the debugger."), - StdErr("Deno is waiting for debugger to connect."), - WsSend(r#"{"id":1,"method":"Runtime.enable"}"#), - WsSend(r#"{"id":2,"method":"Debugger.enable"}"#), - WsRecv( - r#"{"method":"Runtime.executionContextCreated","params":{"context":{"id":1,"#, - ), - WsRecv(r#"{"id":1,"result":{}}"#), - WsRecv(r#"{"id":2,"result":{"debuggerId":"#), - WsSend(r#"{"id":3,"method":"Runtime.runIfWaitingForDebugger"}"#), - WsRecv(r#"{"id":3,"result":{}}"#), - WsRecv(r#"{"method":"Debugger.paused","#), - WsSend(r#"{"id":4,"method":"Debugger.resume"}"#), - WsRecv(r#"{"id":4,"result":{}}"#), - WsRecv(r#"{"method":"Debugger.resumed","params":{}}"#), - ]; - - for step in test_steps { - match step { - StdErr(s) => assert_eq!(&stderr_lines.next().unwrap(), s), - WsRecv(s) => assert!(socket_rx.next().await.unwrap().starts_with(s)), - WsSend(s) => socket_tx.send(s.into()).await.unwrap(), - _ => unreachable!(), - } - } + 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); - // Expect console.log - let s = r#"{"method":"Runtime.consoleAPICalled","#; - assert!(socket_rx.next().await.unwrap().starts_with(s)); - // Expect hitting the `debugger` statement. - let s = r#"{"method":"Debugger.paused","#; - assert!(socket_rx.next().await.unwrap().starts_with(s)); - // Send the 'Debugger.resume' request. - let s = format!(r#"{{"id":{},"method":"Debugger.resume"}}"#, request_id); - socket_tx.send(s.into()).await.unwrap(); - // Expect confirmation of the 'Debugger.resume' request. - let s = format!(r#"{{"id":{},"result":{{}}}}"#, request_id); - assert_eq!(socket_rx.next().await.unwrap(), s); - let s = r#"{"method":"Debugger.resumed","params":{}}"#; - assert_eq!(socket_rx.next().await.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. @@ -386,11 +489,13 @@ async fn inspector_runtime_evaluate_does_not_crash() { 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 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(); @@ -400,43 +505,60 @@ async fn inspector_runtime_evaluate_does_not_crash() { .map(|r| r.unwrap()) .filter(|s| !s.starts_with("Deno ")); - use TestStep::*; - let test_steps = vec![ - StdErr("Visit chrome://inspect to connect to the debugger."), - WsSend(r#"{"id":1,"method":"Runtime.enable"}"#), - WsSend(r#"{"id":2,"method":"Debugger.enable"}"#), - WsRecv( + 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,"#, - ), - WsRecv(r#"{"id":1,"result":{}}"#), - WsRecv(r#"{"id":2,"result":{"debuggerId":"#), - WsSend(r#"{"id":3,"method":"Runtime.runIfWaitingForDebugger"}"#), - WsRecv(r#"{"id":3,"result":{}}"#), - StdOut("exit using ctrl+d or close()"), - WsSend( - r#"{"id":4,"method":"Runtime.compileScript","params":{"expression":"Deno.cwd()","sourceURL":"","persistScript":false,"executionContextId":1}}"#, - ), - WsRecv(r#"{"id":4,"result":{}}"#), - WsSend( - r#"{"id":5,"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}}"#, - ), - WsRecv(r#"{"id":5,"result":{"result":{"type":"string","value":""#), - WsSend( - r#"{"id":6,"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}}"#, - ), - WsRecv(r#"{"method":"Runtime.consoleAPICalled"#), - WsRecv(r#"{"id":6,"result":{"result":{"type":"undefined"}}}"#), - StdErr("done"), - ]; - - for step in test_steps { - match step { - StdOut(s) => assert_eq!(&stdout_lines.next().unwrap(), s), - StdErr(s) => assert_eq!(&stderr_lines.next().unwrap(), s), - WsRecv(s) => assert!(socket_rx.next().await.unwrap().starts_with(s)), - WsSend(s) => socket_tx.send(s.into()).await.unwrap(), - } - } + ], + ) + .await; + + assert_eq!( + &stdout_lines.next().unwrap(), + "exit using ctrl+d 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(); @@ -549,53 +671,76 @@ async fn inspector_break_on_first_line_in_test() { 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 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()); - use TestStep::*; - let test_steps = vec![ - StdErr("Visit chrome://inspect to connect to the debugger."), - StdErr("Deno is waiting for debugger to connect."), - WsSend(r#"{"id":1,"method":"Runtime.enable"}"#), - WsSend(r#"{"id":2,"method":"Debugger.enable"}"#), - WsRecv( + 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,"#, - ), - WsRecv(r#"{"id":1,"result":{}}"#), - WsRecv(r#"{"id":2,"result":{"debuggerId":"#), - WsSend(r#"{"id":3,"method":"Runtime.runIfWaitingForDebugger"}"#), - WsRecv(r#"{"id":3,"result":{}}"#), - WsRecv(r#"{"method":"Debugger.paused","#), - WsSend( + ], + ) + .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}}"#, - ), - WsRecv(r#"{"id":4,"result":{"result":{"type":"undefined"}}}"#), - StdOut("hello from the inspector"), - WsSend(r#"{"id":5,"method":"Debugger.resume"}"#), - WsRecv(r#"{"id":5,"result":{}}"#), - StdOut("running 1 test from"), - StdOut("test has finished running"), - ]; - - for step in test_steps { - match step { - StdOut(s) => assert!( - &stdout_lines.next().unwrap().contains(s), - "Doesn't contain {}", - s - ), - StdErr(s) => assert_eq!(&stderr_lines.next().unwrap(), s), - WsRecv(s) => assert!(socket_rx.next().await.unwrap().starts_with(s)), - WsSend(s) => socket_tx.send(s.into()).await.unwrap(), - } - } + ], + &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!(&stdout_lines + .next() + .unwrap() + .starts_with("running 1 test from")); + assert!(&stdout_lines + .next() + .unwrap() + .contains("test has finished running")); child.kill().unwrap(); child.wait().unwrap(); |