#![allow(missing_docs)]
#![allow(deprecated)]
use assert_cmd::Command;
use std::fs;
use std::thread;
use std::time::Duration;
use tempfile::TempDir;
#[allow(unused_imports)]
use predicates::prelude::*;
fn ruchy_cmd() -> Command {
assert_cmd::cargo::cargo_bin_cmd!("ruchy")
}
#[test]
fn test_serve_help_shows_watch_flags() {
ruchy_cmd()
.arg("serve")
.arg("--help")
.assert()
.success()
.stdout(predicates::str::contains("--watch"))
.stdout(predicates::str::contains("--debounce"))
.stdout(predicates::str::contains("--pid-file"))
.stdout(predicates::str::contains("--watch-wasm"));
}
#[test]
fn test_serve_with_watch_starts_successfully() {
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("index.html");
fs::write(&test_file, "<h1>Test</h1>").unwrap();
let binary_path = assert_cmd::cargo::cargo_bin("ruchy");
let mut child = std::process::Command::new(binary_path)
.arg("serve")
.arg(temp_dir.path())
.arg("--watch")
.arg("--port")
.arg("9999")
.arg("--debounce")
.arg("100")
.spawn()
.expect("Failed to start server");
thread::sleep(Duration::from_secs(2));
assert!(
child.try_wait().unwrap().is_none(),
"Server should still be running"
);
child.kill().expect("Failed to kill server");
}
#[test]
fn test_pid_file_creation_and_cleanup() {
use ruchy::server::PidFile;
let temp_dir = TempDir::new().unwrap();
let pid_path = temp_dir.path().join("test.pid");
{
let _pid_file = PidFile::new(pid_path.clone()).unwrap();
assert!(pid_path.exists(), "PID file should be created");
let contents = fs::read_to_string(&pid_path).unwrap();
let pid: u32 = contents.trim().parse().unwrap();
assert_eq!(pid, std::process::id(), "PID should match current process");
}
assert!(!pid_path.exists(), "PID file should be removed after drop");
}
#[test]
fn test_pid_file_replaces_stale_entry() {
use ruchy::server::PidFile;
let temp_dir = TempDir::new().unwrap();
let pid_path = temp_dir.path().join("test.pid");
fs::write(&pid_path, "999999").unwrap();
let _pid_file = PidFile::new(pid_path.clone()).unwrap();
let contents = fs::read_to_string(&pid_path).unwrap();
let pid: u32 = contents.trim().parse().unwrap();
assert_eq!(
pid,
std::process::id(),
"Stale PID should be replaced with current process ID"
);
}
#[test]
#[cfg(unix)]
fn test_graceful_shutdown_on_sigterm() {
use nix::sys::signal::{kill, Signal};
use nix::unistd::Pid;
let temp_dir = TempDir::new().unwrap();
let test_file = temp_dir.path().join("index.html");
fs::write(&test_file, "<h1>Test</h1>").unwrap();
let pid_path = temp_dir.path().join("server.pid");
let binary_path = assert_cmd::cargo::cargo_bin("ruchy");
let mut child = std::process::Command::new(binary_path)
.arg("serve")
.arg(temp_dir.path())
.arg("--port")
.arg("9998")
.arg("--pid-file")
.arg(&pid_path)
.spawn()
.expect("Failed to start server");
thread::sleep(Duration::from_secs(2));
assert!(pid_path.exists(), "PID file should be created");
#[allow(clippy::cast_possible_wrap)]
let server_pid = child.id() as i32;
kill(Pid::from_raw(server_pid), Signal::SIGTERM).expect("Failed to send SIGTERM");
for _ in 0..50 {
if child.try_wait().unwrap().is_some() {
break;
}
thread::sleep(Duration::from_millis(100));
}
let status = child.try_wait().unwrap();
assert!(
status.is_some(),
"Server should have shut down gracefully on SIGTERM"
);
thread::sleep(Duration::from_millis(100)); assert!(
!pid_path.exists(),
"PID file should be removed on graceful shutdown"
);
}
#[test]
fn test_debounce_parameter_validation() {
let _temp_dir = TempDir::new().unwrap();
ruchy_cmd().arg("serve").arg("--help").assert().success();
}
#[test]
fn test_colored_output_in_startup_banner() {
let temp_dir = TempDir::new().unwrap();
let _output = ruchy_cmd()
.arg("serve")
.arg(temp_dir.path())
.arg("--port")
.arg("0") .timeout(Duration::from_secs(2))
.ok();
}
#[cfg(test)]
mod property_tests {
use super::*;
use proptest::prelude::*;
use ruchy::server::PidFile;
proptest! {
#[test]
fn prop_pid_file_always_contains_valid_pid(seed in any::<u32>()) {
let temp_dir = TempDir::new().unwrap();
let pid_path = temp_dir.path().join(format!("test_{seed}.pid"));
let _pid_file = PidFile::new(pid_path.clone()).unwrap();
prop_assert!(pid_path.exists());
let contents = fs::read_to_string(&pid_path).unwrap();
let pid: u32 = contents.parse().unwrap();
prop_assert_eq!(pid, std::process::id());
}
#[test]
fn prop_debounce_values_accepted(_debounce_ms in 0u64..10000u64) {
let _temp_dir = TempDir::new().unwrap();
let result = ruchy_cmd()
.arg("serve")
.arg("--help")
.assert()
.success();
prop_assert!(result.get_output().status.success());
}
}
}