#![allow(dead_code)]
pub mod fixture_server;
use std::path::{Path, PathBuf};
use std::process::{Child, Command, Stdio};
use std::time::{Duration, Instant};
use fixture_server::FixtureServer;
const VS_BIN: &str = env!("CARGO_BIN_EXE_vs");
#[derive(Copy, Clone, Debug)]
pub enum Backend {
Mac,
Linux,
Windows,
}
impl Backend {
pub fn available_on_current_platform(self) -> bool {
match self {
Self::Mac => cfg!(target_os = "macos"),
Self::Linux => cfg!(target_os = "linux"),
Self::Windows => cfg!(target_os = "windows"),
}
}
pub fn name(self) -> &'static str {
match self {
Self::Mac => "mac",
Self::Linux => "linux",
Self::Windows => "windows",
}
}
}
pub fn each_available_backend() -> impl Iterator<Item = Backend> {
[Backend::Mac, Backend::Linux, Backend::Windows]
.into_iter()
.filter(|b| b.available_on_current_platform())
}
pub struct DaemonGuard {
child: Child,
home: PathBuf,
}
impl DaemonGuard {
pub fn home(&self) -> &Path {
&self.home
}
}
impl Drop for DaemonGuard {
fn drop(&mut self) {
let _ = self.child.kill();
let _ = self.child.wait();
}
}
pub fn spawn_daemon(home: &Path) -> DaemonGuard {
spawn_daemon_with_env(home, &[])
}
pub fn spawn_daemon_with_env(home: &Path, env: &[(&str, &str)]) -> DaemonGuard {
let log = home.join("daemon.log");
let log_file = std::fs::File::create(&log).expect("create daemon log");
let log_clone = log_file.try_clone().expect("clone daemon log fd");
let mut cmd = Command::new(VS_BIN);
cmd.env("RUST_BACKTRACE", "1")
.arg(format!("--home={}", home.display()))
.arg("serve")
.stdin(Stdio::null())
.stdout(Stdio::from(log_file))
.stderr(Stdio::from(log_clone));
for (k, v) in env {
cmd.env(k, v);
}
let child = cmd.spawn().expect("spawn vs serve");
let mut guard = DaemonGuard {
child,
home: home.to_path_buf(),
};
let socket = home.join("daemon.sock");
let deadline = Instant::now() + Duration::from_secs(10);
while Instant::now() < deadline {
if vs_daemon::transport::is_listening(&socket) {
return guard;
}
std::thread::sleep(Duration::from_millis(50));
}
let _ = guard.child.kill();
let _ = guard.child.wait();
panic!(
"daemon socket {} did not appear within 10s",
socket.display()
);
}
pub struct CliOut {
pub code: i32,
pub stdout: String,
pub stderr: String,
}
pub fn vs(home: &Path, args: &[&str]) -> CliOut {
let out = Command::new(VS_BIN)
.arg(format!("--home={}", home.display()))
.arg("--no-spawn")
.args(args)
.output()
.expect("run vs");
let mut stderr = String::from_utf8_lossy(&out.stderr).into_owned();
let code = out.status.code().unwrap_or(-1);
if code != 0 && stderr.contains("daemon closed connection") {
let log_path = home.join("daemon.log");
if let Ok(log) = std::fs::read_to_string(&log_path) {
if !log.trim().is_empty() {
stderr.push_str("\n--- daemon.log ---\n");
stderr.push_str(&log);
}
}
}
CliOut {
code,
stdout: String::from_utf8_lossy(&out.stdout).into_owned(),
stderr,
}
}
pub fn token_of(out: &CliOut) -> String {
for line in out.stdout.lines() {
if let Some(rest) = line.strip_prefix('@') {
return rest
.split_whitespace()
.next()
.expect("@-line without token")
.to_string();
}
}
panic!(
"no success envelope in stdout (code={}):\nstdout:\n{}\nstderr:\n{}",
out.code, out.stdout, out.stderr
);
}
pub fn body_first(out: &CliOut) -> String {
let mut lines = out.stdout.lines();
let _envelope = lines.next();
match lines.next() {
Some(line) => line.to_string(),
None => panic!("empty body in:\n{}", out.stdout),
}
}
pub fn body_rest(out: &CliOut) -> String {
let mut lines = out.stdout.lines();
let _envelope = lines.next();
lines.collect::<Vec<_>>().join("\n")
}
pub fn assert_ok(label: &str, out: &CliOut) {
assert!(
out.code == 0 || out.code == 2,
"{label}: exit={} stdout={:?} stderr={:?}",
out.code,
out.stdout,
out.stderr
);
}
pub fn assert_err(out: &CliOut) -> String {
for line in out.stdout.lines() {
if let Some(rest) = line.strip_prefix("! ") {
return rest
.split_whitespace()
.next()
.expect("! line without code")
.to_string();
}
}
panic!(
"expected error envelope (code={}) — stdout:\n{}\nstderr:\n{}",
out.code, out.stdout, out.stderr
);
}
pub struct TestContext {
pub server: FixtureServer,
pub home: tempfile::TempDir,
pub daemon: DaemonGuard,
}
impl TestContext {
pub fn start() -> Self {
Self::start_with_env(&[])
}
pub fn start_with_env(env: &[(&str, &str)]) -> Self {
let server = FixtureServer::start();
let home = tempfile::tempdir().unwrap();
let key_path = home.path().join("key");
let key = vs_store::MasterKey::generate().expect("generate master key");
key.write_to_file(&key_path).expect("write master key");
let daemon = spawn_daemon_with_env(home.path(), env);
Self {
server,
home,
daemon,
}
}
pub fn home_path(&self) -> &Path {
self.home.path()
}
pub fn url(&self, path: &str) -> String {
self.server.url(path)
}
pub fn vs(&self, args: &[&str]) -> CliOut {
vs(self.home.path(), args)
}
}