251 lines
9.2 KiB
Rust
251 lines
9.2 KiB
Rust
#![cfg(unix)]
|
|
use makepad_agents::{protocol::SessionLocation, server, terminal::HostedTerminal};
|
|
use makepad_strict_json::{self as json, Value};
|
|
use std::{
|
|
fs,
|
|
io::{Read, Write},
|
|
os::unix::net::UnixStream,
|
|
path::PathBuf,
|
|
process::Command,
|
|
time::{Duration, Instant},
|
|
};
|
|
|
|
struct Session {
|
|
root: PathBuf,
|
|
state: PathBuf,
|
|
}
|
|
impl Drop for Session {
|
|
fn drop(&mut self) {
|
|
let _ = server::stop(&self.state, "name-test");
|
|
let _ = fs::remove_dir_all(&self.root);
|
|
}
|
|
}
|
|
fn send(stream: &mut UnixStream, kind: u8, payload: &[u8]) {
|
|
let mut frame = vec![kind];
|
|
frame.extend_from_slice(&(payload.len() as u32).to_be_bytes());
|
|
frame.extend_from_slice(payload);
|
|
stream.write_all(&frame).unwrap();
|
|
}
|
|
fn observe_title(stream: &mut UnixStream, terminal: &mut HostedTerminal, expected: &str) {
|
|
let deadline = Instant::now() + Duration::from_secs(5);
|
|
while terminal.title() != expected {
|
|
assert!(
|
|
Instant::now() < deadline,
|
|
"attached client did not receive {expected}"
|
|
);
|
|
let mut header = [0; 5];
|
|
stream.read_exact(&mut header).unwrap();
|
|
let length = u32::from_be_bytes(header[1..].try_into().unwrap()) as usize;
|
|
assert!(length <= 1024 * 1024);
|
|
let mut bytes = vec![0; length];
|
|
stream.read_exact(&mut bytes).unwrap();
|
|
assert_ne!(header[0], 5, "{}", String::from_utf8_lossy(&bytes));
|
|
if matches!(header[0], 2 | 3) {
|
|
terminal.process(&bytes);
|
|
}
|
|
}
|
|
}
|
|
#[test]
|
|
fn osc_and_current_session_cli_names_reach_list_and_attached_client() {
|
|
let root = std::env::temp_dir().join(format!(
|
|
"screen-names-{}-{}",
|
|
std::process::id(),
|
|
makepad_agents::protocol::random_token().unwrap()
|
|
));
|
|
fs::create_dir(&root).unwrap();
|
|
let root = root.canonicalize().unwrap();
|
|
let session = Session {
|
|
state: root.join("sessions"),
|
|
root,
|
|
};
|
|
let binary = env!("CARGO_BIN_EXE_agents");
|
|
let output = Command::new(binary)
|
|
.args(["start", "--state-dir"])
|
|
.arg(&session.state)
|
|
.args(["--session", "name-test", "--cwd"])
|
|
.arg(&session.root)
|
|
.args(["--", "/bin/sh"])
|
|
.env("SCREEN_TEST_BINARY", binary)
|
|
.output()
|
|
.unwrap();
|
|
assert!(
|
|
output.status.success(),
|
|
"{}; host log: {}",
|
|
String::from_utf8_lossy(&output.stderr),
|
|
fs::read_to_string(session.state.join("name-test.log")).unwrap_or_default()
|
|
);
|
|
let location = SessionLocation::open(&session.state, "name-test", false).unwrap();
|
|
let mut stream = UnixStream::connect(&location.socket_path).unwrap();
|
|
stream
|
|
.set_read_timeout(Some(Duration::from_secs(5)))
|
|
.unwrap();
|
|
send(
|
|
&mut stream,
|
|
1,
|
|
br#"{"version":1,"session_id":"name-test","cols":80,"rows":24,"read_only":false}"#,
|
|
);
|
|
let mut attached = HostedTerminal::new(80, 24, 100);
|
|
for (command, expected) in [
|
|
("printf '\\033]0;osc-zero\\007'\n", "osc-zero"),
|
|
("printf '\\033]2;osc-two\\033\\\\'\n", "osc-two"),
|
|
("\"$SCREEN_TEST_BINARY\" name cli-name\n", "cli-name"),
|
|
(
|
|
"\"$SCREEN_TEST_BINARY\" agents name wrapper-name\n",
|
|
"wrapper-name",
|
|
),
|
|
] {
|
|
send(&mut stream, 6, command.as_bytes());
|
|
observe_title(&mut stream, &mut attached, expected);
|
|
let output = Command::new(binary)
|
|
.args(["list", "--state-dir"])
|
|
.arg(&session.state)
|
|
.output()
|
|
.unwrap();
|
|
assert!(output.status.success());
|
|
let inventory = json::parse(&output.stdout).unwrap();
|
|
let row = &inventory.as_arr().unwrap()[0];
|
|
assert_eq!(row.get("title").and_then(Value::as_str), Some(expected));
|
|
assert_eq!(
|
|
row.get("session_id").and_then(Value::as_str),
|
|
Some("name-test")
|
|
);
|
|
assert_eq!(
|
|
row.get("cwd").and_then(Value::as_str),
|
|
session.root.to_str()
|
|
);
|
|
assert!(row.get("started_at_ms").and_then(Value::as_u64).unwrap() > 0);
|
|
let stored = json::parse(&fs::read(&location.metadata_path).unwrap()).unwrap();
|
|
assert_eq!(stored.get("title").and_then(Value::as_str), Some(expected));
|
|
}
|
|
drop(stream);
|
|
let stopped = server::stop(&session.state, "name-test").unwrap();
|
|
assert_eq!(
|
|
stopped.get("title").and_then(Value::as_str),
|
|
Some("wrapper-name")
|
|
);
|
|
assert_eq!(stopped.get("running").and_then(Value::as_bool), Some(false));
|
|
}
|
|
|
|
#[test]
|
|
fn shell_started_session_is_listed_with_osc_title_in_workspace_scope() {
|
|
let root = std::env::temp_dir().join(format!(
|
|
"screen-inventory-{}-{}",
|
|
std::process::id(),
|
|
makepad_agents::protocol::random_token().unwrap()
|
|
));
|
|
fs::create_dir(&root).unwrap();
|
|
let root = root.canonicalize().unwrap();
|
|
let session = Session {
|
|
state: root.join("local/agent_state/studio/iteration-verification/state/agent_sessions"),
|
|
root,
|
|
};
|
|
makepad_agents::protocol::private_directory(&session.state, true).unwrap();
|
|
let binary = env!("CARGO_BIN_EXE_agents");
|
|
// A shell starts the session before any Studio/client attaches. Omit cwd
|
|
// and state flags, as in tools/agents start --session NAME -- COMMAND.
|
|
let output = Command::new(binary)
|
|
.current_dir(&session.root)
|
|
.env_remove("MAKEPAD_AGENTS_STATE_DIR")
|
|
.env_remove("MAKEPAD_AGENTS_SESSION")
|
|
.args([
|
|
"start",
|
|
"--session",
|
|
"name-test",
|
|
"--",
|
|
"/bin/sh",
|
|
"-c",
|
|
"printf '\\033]0;hello-agent\\007'; exec sleep 600",
|
|
])
|
|
.output()
|
|
.unwrap();
|
|
assert!(
|
|
output.status.success(),
|
|
"{}; host log: {}",
|
|
String::from_utf8_lossy(&output.stderr),
|
|
fs::read_to_string(session.state.join("name-test.log")).unwrap_or_default()
|
|
);
|
|
for scope in ["workspace", "explicit", "inherited"] {
|
|
let deadline = Instant::now() + Duration::from_secs(5);
|
|
loop {
|
|
let mut list = Command::new(binary);
|
|
list.arg("list")
|
|
.current_dir(&session.root)
|
|
.env_remove("MAKEPAD_AGENTS_STATE_DIR");
|
|
match scope {
|
|
"explicit" => {
|
|
// Explicit scope wins over an unrelated inherited scope.
|
|
list.arg("--state-dir")
|
|
.arg(&session.state)
|
|
.env("MAKEPAD_AGENTS_STATE_DIR", session.root.join("empty"));
|
|
}
|
|
"inherited" => {
|
|
// Outside the workspace, the inherited scope still wins.
|
|
list.current_dir(&session.state)
|
|
.env("MAKEPAD_AGENTS_STATE_DIR", &session.state);
|
|
}
|
|
_ => {}
|
|
}
|
|
let output = list.output().unwrap();
|
|
assert!(
|
|
output.status.success(),
|
|
"{scope}: {}",
|
|
String::from_utf8_lossy(&output.stderr)
|
|
);
|
|
let inventory = json::parse(&output.stdout).unwrap();
|
|
let rows = inventory.as_arr().unwrap();
|
|
assert_eq!(rows.len(), 1, "{scope}: {inventory:?}");
|
|
let row = &rows[0];
|
|
assert_eq!(
|
|
row.get("session_id").and_then(Value::as_str),
|
|
Some("name-test")
|
|
);
|
|
assert_eq!(
|
|
row.get("state_dir").and_then(Value::as_str),
|
|
session.state.to_str()
|
|
);
|
|
assert_eq!(
|
|
row.get("cwd").and_then(Value::as_str),
|
|
session.root.to_str()
|
|
);
|
|
assert_eq!(row.get("running").and_then(Value::as_bool), Some(true));
|
|
assert_eq!(row.get("clients").and_then(Value::as_u64), Some(0));
|
|
if row.get("title").and_then(Value::as_str) == Some("hello-agent") {
|
|
break;
|
|
}
|
|
assert!(
|
|
Instant::now() < deadline,
|
|
"{scope}: OSC title missing: {row:?}"
|
|
);
|
|
std::thread::sleep(Duration::from_millis(20));
|
|
}
|
|
}
|
|
let stopped = server::stop(&session.state, "name-test").unwrap();
|
|
assert_eq!(stopped.get("running").and_then(Value::as_bool), Some(false));
|
|
assert!(server::list(&session.state)
|
|
.unwrap()
|
|
.as_arr()
|
|
.unwrap()
|
|
.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn title_changes_project_to_existing_and_new_clients() {
|
|
use makepad_agents::snapshot::Projection;
|
|
let mut source = HostedTerminal::new(80, 24, 100);
|
|
let mut client = HostedTerminal::new(80, 24, 100);
|
|
let mut projection = Projection::default();
|
|
for (sequence, title) in [
|
|
(b"\x1b]0;zero\x07".as_slice(), "zero"),
|
|
(b"\x1b]2;two\x1b\\".as_slice(), "two"),
|
|
] {
|
|
source.process(sequence);
|
|
client.process(&source.render(&mut projection, 80, 24));
|
|
assert_eq!(client.title(), title);
|
|
}
|
|
source.set_title("same name");
|
|
client.process(&source.render(&mut projection, 80, 24));
|
|
assert_eq!(client.title(), "same name");
|
|
let mut new_client = HostedTerminal::new(100, 30, 100);
|
|
new_client.process(&source.render(&mut Projection::default(), 100, 30));
|
|
assert_eq!(new_client.title(), "same name");
|
|
}
|