use std::io::{BufRead, BufReader, Cursor, Write}; use std::net::TcpStream; use std::process::{Command, Stdio}; use std::time::Duration; use runa_dap::framing::{read_frame, write_frame}; use serde_json::{Value, json}; fn run(input: &[u8], args: &[&str]) -> std::process::Output { let mut child = Command::new(env!("CARGO_BIN_EXE_runa-dap")) .args(args) .env_remove("RUNA_CONNECT") .env_remove("RUNA_DAP_LISTEN") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn() .unwrap(); child.stdin.take().unwrap().write_all(input).unwrap(); child.wait_with_output().unwrap() } fn messages(output: &[u8]) -> Vec { let mut reader = Cursor::new(output); let mut messages = Vec::new(); while let Some(message) = read_frame(&mut reader).unwrap() { messages.push(message); } messages } #[test] fn executable_exchanges_standard_frames_and_disconnects_without_a_server() { let mut input = Vec::new(); write_frame( &mut input, &json!({"seq": 1, "type": "request", "command": "initialize"}), ) .unwrap(); write_frame( &mut input, &json!({"seq": 2, "type": "request", "command": "launch"}), ) .unwrap(); write_frame( &mut input, &json!({"seq": 3, "type": "request", "command": "disconnect"}), ) .unwrap(); let output = run(&input, &[]); assert!(output.status.success(), "{:?}", output.stderr); let messages = messages(&output.stdout); assert_eq!( messages[0]["body"]["supportsConfigurationDoneRequest"], true ); assert_eq!(messages[1]["success"], false); assert_eq!(messages[2]["success"], true); assert_eq!(messages[3]["event"], "terminated"); for (index, message) in messages.iter().enumerate() { assert_eq!(message["seq"], index + 1); } } #[test] fn executable_reports_bad_framing_on_protocol_and_stderr() { let output = run(b"Content-Length: 1048577\r\n\r\n", &[]); assert!(!output.status.success()); assert!(!output.stderr.is_empty()); let messages = messages(&output.stdout); assert_eq!(messages[0]["event"], "output"); assert!( messages[0]["body"]["output"] .as_str() .unwrap() .contains("Invalid DAP framing") ); assert_eq!(messages[1]["event"], "terminated"); } #[test] fn cli_documents_connect_and_rejects_nonloopback_addresses() { let help = run(&[], &["--help"]); assert!(help.status.success()); let help = String::from_utf8(help.stdout).unwrap(); assert!(help.contains("RUNA_CONNECT")); assert!(help.contains("127.0.0.1:3737")); assert!(help.contains("--listen")); assert!(help.contains("RUNA_DAP_LISTEN")); for address in ["0.0.0.0:3737", "192.0.2.1:3737", "invalid"] { let output = run(&[], &["--connect", address]); assert!(!output.status.success()); assert!(!output.stderr.is_empty()); } for address in ["0.0.0.0:0", "[::]:0", "192.0.2.1:3737", "invalid"] { let output = run(&[], &["--listen", address]); assert!(!output.status.success()); assert!(!output.stderr.is_empty()); } } #[test] fn tcp_listener_accepts_framed_sessions_and_survives_invalid_framing() { let mut child = Command::new(env!("CARGO_BIN_EXE_runa-dap")) .args(["--listen", "127.0.0.1:0"]) .env_remove("RUNA_CONNECT") .env_remove("RUNA_DAP_LISTEN") .stdin(Stdio::null()) .stdout(Stdio::null()) .stderr(Stdio::piped()) .spawn() .unwrap(); let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { let mut line = String::new(); let mut diagnostics = BufReader::new(child.stderr.take().unwrap()); diagnostics.read_line(&mut line).unwrap(); let address = line.trim().strip_prefix("runa-dap: listening on ").unwrap(); for malformed in [true, false, false] { let mut socket = TcpStream::connect(address).unwrap(); socket .set_read_timeout(Some(Duration::from_secs(5))) .unwrap(); if malformed { socket .write_all(b"Content-Length: 1048577\r\n\r\n") .unwrap(); let mut reader = BufReader::new(socket); assert_eq!(read_frame(&mut reader).unwrap().unwrap()["event"], "output"); assert_eq!( read_frame(&mut reader).unwrap().unwrap()["event"], "terminated" ); } else { write_frame( &mut socket, &json!({ "seq": 1, "type": "request", "command": "initialize" }), ) .unwrap(); let mut reader = BufReader::new(socket.try_clone().unwrap()); assert_eq!(read_frame(&mut reader).unwrap().unwrap()["success"], true); write_frame( &mut socket, &json!({ "seq": 2, "type": "request", "command": "disconnect" }), ) .unwrap(); assert_eq!(read_frame(&mut reader).unwrap().unwrap()["success"], true); assert_eq!( read_frame(&mut reader).unwrap().unwrap()["event"], "terminated" ); } } })); child.kill().unwrap(); child.wait().unwrap(); result.unwrap(); }