aboutsummaryrefslogtreecommitdiff
path: root/tests/cli_wrapper.rs
diff options
context:
space:
mode:
authorFranz Bettag <franz@bett.ag>2026-04-28 19:11:11 +0200
committerGitHub <noreply@github.com>2026-04-28 20:11:11 +0300
commit5baf231e076ed96bb2aaba245eb53ad0e4501d76 (patch)
treea89d28ad26b26f8b8e519106dc304d4e54d3b39e /tests/cli_wrapper.rs
parent22d7bef5680a1d55c28ff6246518d96487e95d5e (diff)
Add named persistent sessions (#57)
* feat: add named persistent sessions * feat: add session management commands * fix: tighten named session stop and override precedence * fix: handle session help and stop test race * test: isolate default session env test --------- Co-authored-by: Ogulcan Celik <ogulcancelik@gmail.com>
Diffstat (limited to 'tests/cli_wrapper.rs')
-rw-r--r--tests/cli_wrapper.rs259
1 files changed, 259 insertions, 0 deletions
diff --git a/tests/cli_wrapper.rs b/tests/cli_wrapper.rs
index caad5b39..03bfaad4 100644
--- a/tests/cli_wrapper.rs
+++ b/tests/cli_wrapper.rs
@@ -29,6 +29,19 @@ struct SpawnedHerdr {
child: Box<dyn Child + Send + Sync>,
}
+struct SpawnedServerProcess {
+ child: std::process::Child,
+}
+
+impl Drop for SpawnedServerProcess {
+ fn drop(&mut self) {
+ let pid = self.child.id();
+ let _ = self.child.kill();
+ let _ = self.child.wait();
+ unregister_spawned_herdr_pid(Some(pid));
+ }
+}
+
impl Drop for SpawnedHerdr {
fn drop(&mut self) {
let pid = self.child.process_id();
@@ -71,6 +84,91 @@ fn spawn_herdr(config_home: &Path, runtime_dir: &Path, socket_path: &Path) -> Sp
spawn_herdr_with_path(config_home, runtime_dir, socket_path, None)
}
+fn app_dir_name() -> &'static str {
+ if cfg!(debug_assertions) {
+ "herdr-dev"
+ } else {
+ "herdr"
+ }
+}
+
+fn named_session_socket(config_home: &Path, session: &str) -> PathBuf {
+ config_home
+ .join(app_dir_name())
+ .join("sessions")
+ .join(session)
+ .join("herdr.sock")
+}
+
+fn spawn_named_server(
+ config_home: &Path,
+ runtime_dir: &Path,
+ session: &str,
+) -> SpawnedServerProcess {
+ fs::create_dir_all(config_home.join(app_dir_name())).unwrap();
+ fs::create_dir_all(runtime_dir).unwrap();
+ register_runtime_dir(runtime_dir);
+ fs::write(
+ config_home.join(app_dir_name()).join("config.toml"),
+ "onboarding = false\n",
+ )
+ .unwrap();
+
+ let mut command = Command::new(env!("CARGO_BIN_EXE_herdr"));
+ command
+ .args(["--session", session, "server"])
+ .env("XDG_CONFIG_HOME", config_home)
+ .env("XDG_RUNTIME_DIR", runtime_dir)
+ .env_remove("HERDR_SOCKET_PATH")
+ .env_remove("HERDR_CLIENT_SOCKET_PATH")
+ .env_remove("HERDR_ENV")
+ .stdin(std::process::Stdio::null())
+ .stdout(std::process::Stdio::null())
+ .stderr(std::process::Stdio::null());
+
+ let child = command.spawn().unwrap();
+ register_spawned_herdr_pid(Some(child.id()));
+ SpawnedServerProcess { child }
+}
+
+fn run_named_cli(config_home: &Path, runtime_dir: &Path, args: &[&str]) -> std::process::Output {
+ run_named_cli_with_socket_override(config_home, runtime_dir, args, None)
+}
+
+fn run_named_cli_with_socket_override(
+ config_home: &Path,
+ runtime_dir: &Path,
+ args: &[&str],
+ socket_override: Option<&Path>,
+) -> std::process::Output {
+ let mut command = Command::new(env!("CARGO_BIN_EXE_herdr"));
+ command
+ .args(args)
+ .env("XDG_CONFIG_HOME", config_home)
+ .env("XDG_RUNTIME_DIR", runtime_dir)
+ .env_remove("HERDR_CLIENT_SOCKET_PATH")
+ .env_remove("HERDR_ENV");
+ if let Some(socket_override) = socket_override {
+ command.env("HERDR_SOCKET_PATH", socket_override);
+ } else {
+ command.env_remove("HERDR_SOCKET_PATH");
+ }
+ command.output().unwrap()
+}
+
+fn run_named_cli_json(config_home: &Path, runtime_dir: &Path, args: &[&str]) -> serde_json::Value {
+ let output = run_named_cli(config_home, runtime_dir, args);
+ assert!(
+ output.status.success(),
+ "command failed: herdr {}\nstatus: {:?}\nstderr: {}\nstdout: {}",
+ args.join(" "),
+ output.status.code(),
+ String::from_utf8_lossy(&output.stderr),
+ String::from_utf8_lossy(&output.stdout)
+ );
+ serde_json::from_slice(&output.stdout).unwrap()
+}
+
fn spawn_herdr_with_path(
config_home: &Path,
runtime_dir: &Path,
@@ -472,6 +570,7 @@ fn help_commands_exit_successfully() {
&["tab", "-h"],
&["pane", "-h"],
&["wait", "-h"],
+ &["session", "-h"],
&["integration", "-h"],
];
@@ -512,6 +611,166 @@ fn removed_show_changelog_flag_fails_before_nested_guard() {
}
#[test]
+fn named_sessions_use_separate_servers_and_workspace_state() {
+ let base = unique_test_dir();
+ let config_home = base.join("config");
+ let runtime_dir = base.join("runtime");
+
+ let alpha = spawn_named_server(&config_home, &runtime_dir, "alpha");
+ let beta = spawn_named_server(&config_home, &runtime_dir, "beta");
+
+ wait_for_socket(
+ &named_session_socket(&config_home, "alpha"),
+ Duration::from_secs(5),
+ );
+ wait_for_socket(
+ &named_session_socket(&config_home, "beta"),
+ Duration::from_secs(5),
+ );
+
+ run_named_cli_json(
+ &config_home,
+ &runtime_dir,
+ &[
+ "--session",
+ "alpha",
+ "workspace",
+ "create",
+ "--label",
+ "alpha-ws",
+ "--no-focus",
+ ],
+ );
+ run_named_cli_json(
+ &config_home,
+ &runtime_dir,
+ &[
+ "--session",
+ "beta",
+ "workspace",
+ "create",
+ "--label",
+ "beta-ws",
+ "--no-focus",
+ ],
+ );
+
+ let alpha_list = run_named_cli_json(
+ &config_home,
+ &runtime_dir,
+ &["--session", "alpha", "workspace", "list"],
+ );
+ let beta_list = run_named_cli_json(
+ &config_home,
+ &runtime_dir,
+ &["--session", "beta", "workspace", "list"],
+ );
+
+ let alpha_labels: Vec<_> = alpha_list["result"]["workspaces"]
+ .as_array()
+ .unwrap()
+ .iter()
+ .map(|workspace| workspace["label"].as_str().unwrap())
+ .collect();
+ let beta_labels: Vec<_> = beta_list["result"]["workspaces"]
+ .as_array()
+ .unwrap()
+ .iter()
+ .map(|workspace| workspace["label"].as_str().unwrap())
+ .collect();
+
+ assert_eq!(alpha_labels, vec!["alpha-ws"]);
+ assert_eq!(beta_labels, vec!["beta-ws"]);
+
+ let beta_via_explicit_session = run_named_cli_with_socket_override(
+ &config_home,
+ &runtime_dir,
+ &["--session", "beta", "workspace", "list"],
+ Some(&named_session_socket(&config_home, "alpha")),
+ );
+ assert!(
+ beta_via_explicit_session.status.success(),
+ "stderr: {}",
+ String::from_utf8_lossy(&beta_via_explicit_session.stderr)
+ );
+ let beta_via_explicit_session: serde_json::Value =
+ serde_json::from_slice(&beta_via_explicit_session.stdout).unwrap();
+ let labels_via_explicit: Vec<_> = beta_via_explicit_session["result"]["workspaces"]
+ .as_array()
+ .unwrap()
+ .iter()
+ .map(|workspace| workspace["label"].as_str().unwrap())
+ .collect();
+ assert_eq!(labels_via_explicit, vec!["beta-ws"]);
+
+ let sessions = run_named_cli_json(&config_home, &runtime_dir, &["session", "list"]);
+ let sessions = sessions["sessions"].as_array().unwrap();
+ let default_session = sessions
+ .iter()
+ .find(|session| session["name"] == "default")
+ .unwrap();
+ let alpha_session = sessions
+ .iter()
+ .find(|session| session["name"] == "alpha")
+ .unwrap();
+ let beta_session = sessions
+ .iter()
+ .find(|session| session["name"] == "beta")
+ .unwrap();
+ assert_eq!(default_session["default"], true);
+ assert_eq!(default_session["running"], false);
+ assert_eq!(alpha_session["running"], true);
+ assert_eq!(beta_session["running"], true);
+ assert!(alpha_session["socket_path"]
+ .as_str()
+ .unwrap()
+ .ends_with("/sessions/alpha/herdr.sock"));
+ assert!(beta_session["session_dir"]
+ .as_str()
+ .unwrap()
+ .ends_with("/sessions/beta"));
+
+ let delete_running = run_named_cli(&config_home, &runtime_dir, &["session", "delete", "alpha"]);
+ assert_eq!(delete_running.status.code(), Some(1));
+ assert!(
+ String::from_utf8_lossy(&delete_running.stderr).contains("stop it before deleting"),
+ "stderr: {}",
+ String::from_utf8_lossy(&delete_running.stderr)
+ );
+
+ let delete_default = run_named_cli(
+ &config_home,
+ &runtime_dir,
+ &["session", "delete", "default"],
+ );
+ assert_eq!(delete_default.status.code(), Some(1));
+ assert!(
+ String::from_utf8_lossy(&delete_default.stderr).contains("default session"),
+ "stderr: {}",
+ String::from_utf8_lossy(&delete_default.stderr)
+ );
+
+ let stopped_alpha =
+ run_named_cli_json(&config_home, &runtime_dir, &["session", "stop", "alpha"]);
+ assert_eq!(stopped_alpha["stopped"], true);
+ assert_eq!(stopped_alpha["session"]["running"], false);
+
+ let deleted_alpha =
+ run_named_cli_json(&config_home, &runtime_dir, &["session", "delete", "alpha"]);
+ assert_eq!(deleted_alpha["deleted"], true);
+ assert!(!config_home
+ .join(app_dir_name())
+ .join("sessions")
+ .join("alpha")
+ .exists());
+
+ let _ = run_named_cli(&config_home, &runtime_dir, &["session", "stop", "beta"]);
+ drop(alpha);
+ drop(beta);
+ cleanup_test_base(&base);
+}
+
+#[test]
fn integration_commands_honor_socket_override_when_server_is_missing() {
let base = unique_test_dir();
let home_dir = base.join("home");