test(xy): integration test harness
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
#![allow(dead_code)] // not all helpers are used by every test file
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::process::Stdio;
|
||||
use std::time::Duration;
|
||||
use tokio::process::{Child, Command};
|
||||
use tempfile::TempDir;
|
||||
|
||||
pub struct Harness {
|
||||
pub tmp: TempDir,
|
||||
pub config_dir: PathBuf,
|
||||
pub state_dir: PathBuf,
|
||||
pub socket: PathBuf,
|
||||
pub daemon: Option<Child>,
|
||||
}
|
||||
|
||||
impl Harness {
|
||||
pub fn new() -> Self {
|
||||
let tmp = tempfile::tempdir().expect("tempdir");
|
||||
std::fs::create_dir_all(tmp.path().join("config")).unwrap();
|
||||
std::fs::create_dir_all(tmp.path().join("state")).unwrap();
|
||||
std::fs::create_dir_all(tmp.path().join("run")).unwrap();
|
||||
let config_dir = tmp.path().join("config/xy/servers");
|
||||
let state_dir = tmp.path().join("state/xy");
|
||||
std::fs::create_dir_all(&config_dir).unwrap();
|
||||
std::fs::create_dir_all(&state_dir).unwrap();
|
||||
let socket = tmp.path().join("run/xy.sock");
|
||||
Self { tmp, config_dir, state_dir, socket, daemon: None }
|
||||
}
|
||||
|
||||
pub fn write_server(&self, name: &str, command: &str, port: u16, restart_policy: &str) {
|
||||
let body = format!(
|
||||
"command \"{command}\"\nport {port}\nrestart {{ policy \"{restart_policy}\" backoff-initial \"10ms\" backoff-max \"50ms\" max-retries-per-minute 3 }}\nstop {{ grace \"500ms\" }}\n"
|
||||
);
|
||||
std::fs::write(self.config_dir.join(format!("{name}.kdl")), body).unwrap();
|
||||
}
|
||||
|
||||
pub async fn start_daemon(&mut self, xy_bin: &PathBuf) {
|
||||
let child = Command::new(xy_bin)
|
||||
.arg("daemon")
|
||||
.env("XDG_CONFIG_HOME", self.tmp.path().join("config"))
|
||||
.env("XDG_STATE_HOME", self.tmp.path().join("state"))
|
||||
.env("XDG_RUNTIME_DIR", self.tmp.path().join("run"))
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::inherit())
|
||||
.kill_on_drop(true)
|
||||
.spawn()
|
||||
.expect("spawn daemon");
|
||||
self.daemon = Some(child);
|
||||
let deadline = std::time::Instant::now() + Duration::from_secs(5);
|
||||
while !self.socket.exists() {
|
||||
if std::time::Instant::now() > deadline { panic!("daemon socket never appeared"); }
|
||||
tokio::time::sleep(Duration::from_millis(25)).await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run_cli(&self, xy_bin: &PathBuf, args: &[&str]) -> (i32, String, String) {
|
||||
let out = Command::new(xy_bin)
|
||||
.args(args)
|
||||
.env("XDG_CONFIG_HOME", self.tmp.path().join("config"))
|
||||
.env("XDG_STATE_HOME", self.tmp.path().join("state"))
|
||||
.env("XDG_RUNTIME_DIR", self.tmp.path().join("run"))
|
||||
.output().await.expect("run cli");
|
||||
let code = out.status.code().unwrap_or(-1);
|
||||
let stdout = String::from_utf8_lossy(&out.stdout).to_string();
|
||||
let stderr = String::from_utf8_lossy(&out.stderr).to_string();
|
||||
(code, stdout, stderr)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn xy_bin() -> PathBuf { artifact("xy") }
|
||||
pub fn sleep_server_bin() -> PathBuf { artifact("xy-test-sleep-server") }
|
||||
pub fn exit_failure_bin() -> PathBuf { artifact("xy-test-exit-failure") }
|
||||
|
||||
fn artifact(name: &str) -> PathBuf {
|
||||
let mut p = std::env::current_exe().unwrap();
|
||||
p.pop();
|
||||
if p.ends_with("deps") { p.pop(); }
|
||||
p.push(name);
|
||||
if !p.exists() { panic!("artifact `{}` not found at {}", name, p.display()); }
|
||||
p
|
||||
}
|
||||
Reference in New Issue
Block a user