mirror of
https://github.com/denoland/deno.git
synced 2024-11-27 16:10:57 -05:00
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.
This commit is contained in:
parent
ada8f58c7a
commit
3360b8ee37
2 changed files with 328 additions and 202 deletions
|
@ -2,12 +2,16 @@
|
||||||
|
|
||||||
use deno_core::futures;
|
use deno_core::futures;
|
||||||
use deno_core::futures::prelude::*;
|
use deno_core::futures::prelude::*;
|
||||||
|
use deno_core::futures::stream::SplitSink;
|
||||||
use deno_core::serde_json;
|
use deno_core::serde_json;
|
||||||
use deno_core::url;
|
use deno_core::url;
|
||||||
use deno_runtime::deno_fetch::reqwest;
|
use deno_runtime::deno_fetch::reqwest;
|
||||||
use deno_runtime::deno_websocket::tokio_tungstenite;
|
use deno_runtime::deno_websocket::tokio_tungstenite;
|
||||||
|
use deno_runtime::deno_websocket::tokio_tungstenite::tungstenite;
|
||||||
use std::io::BufRead;
|
use std::io::BufRead;
|
||||||
|
use std::pin::Pin;
|
||||||
use test_util as util;
|
use test_util as util;
|
||||||
|
use tokio::net::TcpStream;
|
||||||
|
|
||||||
fn inspect_flag_with_unique_port(flag_prefix: &str) -> String {
|
fn inspect_flag_with_unique_port(flag_prefix: &str) -> String {
|
||||||
use std::sync::atomic::{AtomicU16, Ordering};
|
use std::sync::atomic::{AtomicU16, Ordering};
|
||||||
|
@ -28,6 +32,71 @@ fn extract_ws_url_from_stderr(
|
||||||
url::Url::parse(ws_url).unwrap()
|
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]
|
#[tokio::test]
|
||||||
async fn inspector_connect() {
|
async fn inspector_connect() {
|
||||||
let script = util::testdata_path().join("inspector1.js");
|
let script = util::testdata_path().join("inspector1.js");
|
||||||
|
@ -54,14 +123,6 @@ async fn inspector_connect() {
|
||||||
child.wait().unwrap();
|
child.wait().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
enum TestStep {
|
|
||||||
StdOut(&'static str),
|
|
||||||
StdErr(&'static str),
|
|
||||||
WsRecv(&'static str),
|
|
||||||
WsSend(&'static str),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn inspector_break_on_first_line() {
|
async fn inspector_break_on_first_line() {
|
||||||
let script = util::testdata_path().join("inspector2.js");
|
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.
|
assert_eq!(response.status(), 101); // Switching protocols.
|
||||||
|
|
||||||
let (mut socket_tx, socket_rx) = socket.split();
|
let (mut socket_tx, socket_rx) = socket.split();
|
||||||
let mut socket_rx =
|
let mut socket_rx = socket_rx
|
||||||
socket_rx.map(|msg| msg.unwrap().to_string()).filter(|msg| {
|
.map(|msg| msg.unwrap().to_string())
|
||||||
|
.filter(|msg| {
|
||||||
let pass = !msg.starts_with(r#"{"method":"Debugger.scriptParsed","#);
|
let pass = !msg.starts_with(r#"{"method":"Debugger.scriptParsed","#);
|
||||||
futures::future::ready(pass)
|
futures::future::ready(pass)
|
||||||
});
|
})
|
||||||
|
.boxed_local();
|
||||||
|
|
||||||
let stdout = child.stdout.as_mut().unwrap();
|
let stdout = child.stdout.as_mut().unwrap();
|
||||||
let mut stdout_lines =
|
let mut stdout_lines =
|
||||||
std::io::BufReader::new(stdout).lines().map(|r| r.unwrap());
|
std::io::BufReader::new(stdout).lines().map(|r| r.unwrap());
|
||||||
|
|
||||||
use TestStep::*;
|
assert_stderr_for_inspect_brk(&mut stderr_lines);
|
||||||
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":"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 {
|
assert_inspector_messages(
|
||||||
match step {
|
&mut socket_tx,
|
||||||
StdErr(s) => assert_eq!(&stderr_lines.next().unwrap(), s),
|
&[
|
||||||
StdOut(s) => assert_eq!(&stdout_lines.next().unwrap(), s),
|
r#"{"id":1,"method":"Runtime.enable"}"#,
|
||||||
WsRecv(s) => assert!(socket_rx.next().await.unwrap().starts_with(s)),
|
r#"{"id":2,"method":"Debugger.enable"}"#,
|
||||||
WsSend(s) => socket_tx.send(s.into()).await.unwrap(),
|
],
|
||||||
}
|
&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.kill().unwrap();
|
||||||
child.wait().unwrap();
|
child.wait().unwrap();
|
||||||
|
@ -266,63 +348,84 @@ async fn inspector_does_not_hang() {
|
||||||
assert_eq!(response.status(), 101); // Switching protocols.
|
assert_eq!(response.status(), 101); // Switching protocols.
|
||||||
|
|
||||||
let (mut socket_tx, socket_rx) = socket.split();
|
let (mut socket_tx, socket_rx) = socket.split();
|
||||||
let mut socket_rx =
|
let mut socket_rx = socket_rx
|
||||||
socket_rx.map(|msg| msg.unwrap().to_string()).filter(|msg| {
|
.map(|msg| msg.unwrap().to_string())
|
||||||
|
.filter(|msg| {
|
||||||
let pass = !msg.starts_with(r#"{"method":"Debugger.scriptParsed","#);
|
let pass = !msg.starts_with(r#"{"method":"Debugger.scriptParsed","#);
|
||||||
futures::future::ready(pass)
|
futures::future::ready(pass)
|
||||||
});
|
})
|
||||||
|
.boxed_local();
|
||||||
|
|
||||||
let stdout = child.stdout.as_mut().unwrap();
|
let stdout = child.stdout.as_mut().unwrap();
|
||||||
let mut stdout_lines =
|
let mut stdout_lines =
|
||||||
std::io::BufReader::new(stdout).lines().map(|r| r.unwrap());
|
std::io::BufReader::new(stdout).lines().map(|r| r.unwrap());
|
||||||
|
|
||||||
use TestStep::*;
|
assert_stderr_for_inspect_brk(&mut stderr_lines);
|
||||||
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 {
|
assert_inspector_messages(
|
||||||
match step {
|
&mut socket_tx,
|
||||||
StdErr(s) => assert_eq!(&stderr_lines.next().unwrap(), s),
|
&[
|
||||||
WsRecv(s) => assert!(socket_rx.next().await.unwrap().starts_with(s)),
|
r#"{"id":1,"method":"Runtime.enable"}"#,
|
||||||
WsSend(s) => socket_tx.send(s.into()).await.unwrap(),
|
r#"{"id":2,"method":"Debugger.enable"}"#,
|
||||||
_ => unreachable!(),
|
],
|
||||||
}
|
&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 {
|
for i in 0..128u32 {
|
||||||
let request_id = i + 10;
|
let request_id = i + 10;
|
||||||
// Expect the number {i} on stdout.
|
// Expect the number {i} on stdout.
|
||||||
let s = i.to_string();
|
let s = i.to_string();
|
||||||
assert_eq!(stdout_lines.next().unwrap(), s);
|
assert_eq!(stdout_lines.next().unwrap(), s);
|
||||||
// Expect console.log
|
|
||||||
let s = r#"{"method":"Runtime.consoleAPICalled","#;
|
assert_inspector_messages(
|
||||||
assert!(socket_rx.next().await.unwrap().starts_with(s));
|
&mut socket_tx,
|
||||||
// Expect hitting the `debugger` statement.
|
&[],
|
||||||
let s = r#"{"method":"Debugger.paused","#;
|
&mut socket_rx,
|
||||||
assert!(socket_rx.next().await.unwrap().starts_with(s));
|
&[],
|
||||||
// Send the 'Debugger.resume' request.
|
&[
|
||||||
let s = format!(r#"{{"id":{},"method":"Debugger.resume"}}"#, request_id);
|
r#"{"method":"Runtime.consoleAPICalled","#,
|
||||||
socket_tx.send(s.into()).await.unwrap();
|
r#"{"method":"Debugger.paused","#,
|
||||||
// Expect confirmation of the 'Debugger.resume' request.
|
],
|
||||||
let s = format!(r#"{{"id":{},"result":{{}}}}"#, request_id);
|
)
|
||||||
assert_eq!(socket_rx.next().await.unwrap(), s);
|
.await;
|
||||||
let s = r#"{"method":"Debugger.resumed","params":{}}"#;
|
|
||||||
assert_eq!(socket_rx.next().await.unwrap(), s);
|
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.
|
// 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.
|
assert_eq!(response.status(), 101); // Switching protocols.
|
||||||
|
|
||||||
let (mut socket_tx, socket_rx) = socket.split();
|
let (mut socket_tx, socket_rx) = socket.split();
|
||||||
let mut socket_rx =
|
let mut socket_rx = socket_rx
|
||||||
socket_rx.map(|msg| msg.unwrap().to_string()).filter(|msg| {
|
.map(|msg| msg.unwrap().to_string())
|
||||||
|
.filter(|msg| {
|
||||||
let pass = !msg.starts_with(r#"{"method":"Debugger.scriptParsed","#);
|
let pass = !msg.starts_with(r#"{"method":"Debugger.scriptParsed","#);
|
||||||
futures::future::ready(pass)
|
futures::future::ready(pass)
|
||||||
});
|
})
|
||||||
|
.boxed_local();
|
||||||
|
|
||||||
let stdin = child.stdin.take().unwrap();
|
let stdin = child.stdin.take().unwrap();
|
||||||
|
|
||||||
|
@ -400,43 +505,60 @@ async fn inspector_runtime_evaluate_does_not_crash() {
|
||||||
.map(|r| r.unwrap())
|
.map(|r| r.unwrap())
|
||||||
.filter(|s| !s.starts_with("Deno "));
|
.filter(|s| !s.starts_with("Deno "));
|
||||||
|
|
||||||
use TestStep::*;
|
assert_stderr_for_inspect(&mut stderr_lines);
|
||||||
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(
|
|
||||||
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 {
|
assert_inspector_messages(
|
||||||
match step {
|
&mut socket_tx,
|
||||||
StdOut(s) => assert_eq!(&stdout_lines.next().unwrap(), s),
|
&[
|
||||||
StdErr(s) => assert_eq!(&stderr_lines.next().unwrap(), s),
|
r#"{"id":1,"method":"Runtime.enable"}"#,
|
||||||
WsRecv(s) => assert!(socket_rx.next().await.unwrap().starts_with(s)),
|
r#"{"id":2,"method":"Debugger.enable"}"#,
|
||||||
WsSend(s) => socket_tx.send(s.into()).await.unwrap(),
|
],
|
||||||
}
|
&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 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);
|
drop(stdin);
|
||||||
child.wait().unwrap();
|
child.wait().unwrap();
|
||||||
|
@ -549,53 +671,76 @@ async fn inspector_break_on_first_line_in_test() {
|
||||||
assert_eq!(response.status(), 101); // Switching protocols.
|
assert_eq!(response.status(), 101); // Switching protocols.
|
||||||
|
|
||||||
let (mut socket_tx, socket_rx) = socket.split();
|
let (mut socket_tx, socket_rx) = socket.split();
|
||||||
let mut socket_rx =
|
let mut socket_rx = socket_rx
|
||||||
socket_rx.map(|msg| msg.unwrap().to_string()).filter(|msg| {
|
.map(|msg| msg.unwrap().to_string())
|
||||||
|
.filter(|msg| {
|
||||||
let pass = !msg.starts_with(r#"{"method":"Debugger.scriptParsed","#);
|
let pass = !msg.starts_with(r#"{"method":"Debugger.scriptParsed","#);
|
||||||
futures::future::ready(pass)
|
futures::future::ready(pass)
|
||||||
});
|
})
|
||||||
|
.boxed_local();
|
||||||
|
|
||||||
let stdout = child.stdout.as_mut().unwrap();
|
let stdout = child.stdout.as_mut().unwrap();
|
||||||
let mut stdout_lines =
|
let mut stdout_lines =
|
||||||
std::io::BufReader::new(stdout).lines().map(|r| r.unwrap());
|
std::io::BufReader::new(stdout).lines().map(|r| r.unwrap());
|
||||||
|
|
||||||
use TestStep::*;
|
assert_stderr_for_inspect_brk(&mut stderr_lines);
|
||||||
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":"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 {
|
assert_inspector_messages(
|
||||||
match step {
|
&mut socket_tx,
|
||||||
StdOut(s) => assert!(
|
&[
|
||||||
&stdout_lines.next().unwrap().contains(s),
|
r#"{"id":1,"method":"Runtime.enable"}"#,
|
||||||
"Doesn't contain {}",
|
r#"{"id":2,"method":"Debugger.enable"}"#,
|
||||||
s
|
],
|
||||||
),
|
&mut socket_rx,
|
||||||
StdErr(s) => assert_eq!(&stderr_lines.next().unwrap(), s),
|
&[
|
||||||
WsRecv(s) => assert!(socket_rx.next().await.unwrap().starts_with(s)),
|
r#"{"id":1,"result":{}}"#,
|
||||||
WsSend(s) => socket_tx.send(s.into()).await.unwrap(),
|
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!(&stdout_lines
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.starts_with("running 1 test from"));
|
||||||
|
assert!(&stdout_lines
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.contains("test has finished running"));
|
||||||
|
|
||||||
child.kill().unwrap();
|
child.kill().unwrap();
|
||||||
child.wait().unwrap();
|
child.wait().unwrap();
|
||||||
|
|
|
@ -27,7 +27,6 @@ use std::cell::BorrowMutError;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::ffi::c_void;
|
use std::ffi::c_void;
|
||||||
use std::mem::replace;
|
|
||||||
use std::mem::take;
|
use std::mem::take;
|
||||||
use std::mem::MaybeUninit;
|
use std::mem::MaybeUninit;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
@ -127,7 +126,7 @@ impl v8::inspector::V8InspectorClientImpl for JsRuntimeInspector {
|
||||||
|
|
||||||
fn run_if_waiting_for_debugger(&mut self, context_group_id: i32) {
|
fn run_if_waiting_for_debugger(&mut self, context_group_id: i32) {
|
||||||
assert_eq!(context_group_id, JsRuntimeInspector::CONTEXT_GROUP_ID);
|
assert_eq!(context_group_id, JsRuntimeInspector::CONTEXT_GROUP_ID);
|
||||||
self.flags.borrow_mut().session_handshake_done = true;
|
self.flags.borrow_mut().waiting_for_session = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -226,34 +225,20 @@ impl JsRuntimeInspector {
|
||||||
loop {
|
loop {
|
||||||
loop {
|
loop {
|
||||||
// Do one "handshake" with a newly connected session at a time.
|
// Do one "handshake" with a newly connected session at a time.
|
||||||
if let Some(session) = &mut sessions.handshake {
|
if let Some(mut session) = sessions.handshake.take() {
|
||||||
let poll_result = session.poll_unpin(cx);
|
if session.poll_unpin(cx).is_pending() {
|
||||||
let handshake_done =
|
sessions.established.push(session);
|
||||||
replace(&mut self.flags.borrow_mut().session_handshake_done, false);
|
continue;
|
||||||
match poll_result {
|
}
|
||||||
Poll::Pending if handshake_done => {
|
|
||||||
let session = sessions.handshake.take().unwrap();
|
|
||||||
sessions.established.push(session);
|
|
||||||
take(&mut self.flags.borrow_mut().waiting_for_session);
|
|
||||||
}
|
|
||||||
Poll::Ready(_) => sessions.handshake = None,
|
|
||||||
Poll::Pending => break,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Accept new connections.
|
// Accept new connections.
|
||||||
match sessions.session_rx.poll_next_unpin(cx) {
|
let poll_result = sessions.session_rx.poll_next_unpin(cx);
|
||||||
Poll::Ready(Some(session_proxy)) => {
|
if let Poll::Ready(Some(session_proxy)) = poll_result {
|
||||||
let session = InspectorSession::new(
|
let session =
|
||||||
sessions.v8_inspector.clone(),
|
InspectorSession::new(sessions.v8_inspector.clone(), session_proxy);
|
||||||
session_proxy,
|
let prev = sessions.handshake.replace(session);
|
||||||
);
|
assert!(prev.is_none());
|
||||||
let prev = sessions.handshake.replace(session);
|
|
||||||
assert!(prev.is_none());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Poll::Ready(None) => {}
|
|
||||||
Poll::Pending => {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Poll established sessions.
|
// Poll established sessions.
|
||||||
|
@ -264,9 +249,8 @@ impl JsRuntimeInspector {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let should_block = sessions.handshake.is_some()
|
let should_block =
|
||||||
|| self.flags.borrow().on_pause
|
self.flags.borrow().on_pause || self.flags.borrow().waiting_for_session;
|
||||||
|| self.flags.borrow().waiting_for_session;
|
|
||||||
|
|
||||||
let new_state = self.waker.update(|w| {
|
let new_state = self.waker.update(|w| {
|
||||||
match w.poll_state {
|
match w.poll_state {
|
||||||
|
@ -311,8 +295,11 @@ impl JsRuntimeInspector {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This function blocks the thread until at least one inspector client has
|
/// This function blocks the thread until at least one inspector client has
|
||||||
/// established a websocket connection and successfully completed the
|
/// established a websocket connection.
|
||||||
/// handshake. After that, it instructs V8 to pause at the next statement.
|
///
|
||||||
|
/// After that, it instructs V8 to pause at the next statement.
|
||||||
|
/// Frontend must send "Runtime.runIfWaitingForDebugger" message to resume
|
||||||
|
/// execution.
|
||||||
pub fn wait_for_session_and_break_on_next_statement(&mut self) {
|
pub fn wait_for_session_and_break_on_next_statement(&mut self) {
|
||||||
loop {
|
loop {
|
||||||
match self.sessions.get_mut().established.iter_mut().next() {
|
match self.sessions.get_mut().established.iter_mut().next() {
|
||||||
|
@ -326,10 +313,6 @@ impl JsRuntimeInspector {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Obtain a sender for proxy channels.
|
/// Obtain a sender for proxy channels.
|
||||||
///
|
|
||||||
/// After a proxy is sent inspector will wait for a "handshake".
|
|
||||||
/// Frontend must send "Runtime.runIfWaitingForDebugger" message to
|
|
||||||
/// complete the handshake.
|
|
||||||
pub fn get_session_sender(&self) -> UnboundedSender<InspectorSessionProxy> {
|
pub fn get_session_sender(&self) -> UnboundedSender<InspectorSessionProxy> {
|
||||||
self.new_session_tx.clone()
|
self.new_session_tx.clone()
|
||||||
}
|
}
|
||||||
|
@ -362,8 +345,7 @@ impl JsRuntimeInspector {
|
||||||
};
|
};
|
||||||
|
|
||||||
// InspectorSessions for a local session is added directly to the "established"
|
// InspectorSessions for a local session is added directly to the "established"
|
||||||
// sessions, so it doesn't need to go through the session sender and handshake
|
// sessions, so it doesn't need to go through the session sender.
|
||||||
// phase.
|
|
||||||
let inspector_session =
|
let inspector_session =
|
||||||
InspectorSession::new(self.v8_inspector.clone(), proxy);
|
InspectorSession::new(self.v8_inspector.clone(), proxy);
|
||||||
self
|
self
|
||||||
|
@ -380,7 +362,6 @@ impl JsRuntimeInspector {
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct InspectorFlags {
|
struct InspectorFlags {
|
||||||
waiting_for_session: bool,
|
waiting_for_session: bool,
|
||||||
session_handshake_done: bool,
|
|
||||||
on_pause: bool,
|
on_pause: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue