mod utils;
use std::process::Command as SystemCommand;
use std::time::{Duration, Instant};
use utils::get_available_port;
fn start_container(port: u16) -> String {
let output = SystemCommand::new("docker")
.args([
"run",
"-d",
"--rm",
"-p",
&format!("{}:80", port),
"nginx:alpine",
])
.output()
.expect("Failed to start container");
assert!(
output.status.success(),
"docker run failed: {}",
String::from_utf8_lossy(&output.stderr)
);
String::from_utf8(output.stdout)
.expect("Invalid container ID")
.trim()
.to_string()
}
fn is_container_running(container_id: &str) -> bool {
let output = SystemCommand::new("docker")
.args(["inspect", "-f", "{{.State.Running}}", container_id])
.output();
match output {
Ok(o) => String::from_utf8_lossy(&o.stdout).trim() == "true",
Err(_) => false,
}
}
fn wait_for_container(container_id: &str, port: u16, timeout: Duration) -> bool {
let start = Instant::now();
while start.elapsed() < timeout {
if is_container_running(container_id)
&& std::net::TcpStream::connect(format!("127.0.0.1:{}", port)).is_ok()
{
return true;
}
std::thread::sleep(Duration::from_millis(100));
}
false
}
fn remove_container(container_id: &str) {
let _ = SystemCommand::new("docker")
.args(["rm", "-f", container_id])
.output();
}
#[test]
#[ignore]
fn test_container_runtime_is_present() {
let output = SystemCommand::new("docker")
.args(["version"])
.output()
.expect("Container runtime not found");
assert!(
output.status.success(),
"Container runtime is not running: {}",
String::from_utf8_lossy(&output.stderr)
);
}
#[test]
#[ignore]
fn test_kill_container_mode_container() {
let port = get_available_port();
let container_id = start_container(port);
assert!(
wait_for_container(&container_id, port, Duration::from_secs(15)),
"Container did not become ready on port {}",
port
);
let mut cmd = assert_cmd::cargo_bin_cmd!("killport");
let output = cmd
.args([&port.to_string(), "--mode", "container"])
.assert()
.success();
let stdout = String::from_utf8_lossy(&output.get_output().stdout);
assert!(
stdout.contains("Successfully killed"),
"Expected kill message, got: {}",
stdout
);
assert!(
stdout.contains("container"),
"Expected 'container' in output, got: {}",
stdout
);
std::thread::sleep(Duration::from_secs(1));
assert!(
!is_container_running(&container_id),
"Container should be stopped after kill"
);
remove_container(&container_id);
}
#[test]
#[ignore]
fn test_kill_container_mode_auto() {
let port = get_available_port();
let container_id = start_container(port);
assert!(
wait_for_container(&container_id, port, Duration::from_secs(15)),
"Container did not become ready on port {}",
port
);
let mut cmd = assert_cmd::cargo_bin_cmd!("killport");
let output = cmd.args([&port.to_string()]).assert().success();
let stdout = String::from_utf8_lossy(&output.get_output().stdout);
assert!(
stdout.contains("Successfully killed"),
"Expected kill message, got: {}",
stdout
);
std::thread::sleep(Duration::from_secs(1));
assert!(
!is_container_running(&container_id),
"Container should be stopped after kill"
);
remove_container(&container_id);
}
#[test]
#[ignore]
fn test_dry_run_container_still_alive() {
let port = get_available_port();
let container_id = start_container(port);
assert!(
wait_for_container(&container_id, port, Duration::from_secs(15)),
"Container did not become ready on port {}",
port
);
let mut cmd = assert_cmd::cargo_bin_cmd!("killport");
let output = cmd
.args([&port.to_string(), "--mode", "container", "--dry-run"])
.assert()
.success();
let stdout = String::from_utf8_lossy(&output.get_output().stdout);
assert!(
stdout.contains("Would kill"),
"Dry run should say 'Would kill', got: {}",
stdout
);
assert!(
is_container_running(&container_id),
"Container should still be alive after dry run"
);
remove_container(&container_id);
}
#[test]
#[ignore]
fn test_no_container_on_port() {
let port = get_available_port();
let mut cmd = assert_cmd::cargo_bin_cmd!("killport");
cmd.args([&port.to_string(), "--mode", "container"])
.assert()
.code(2)
.stdout(format!("No container found using port {}\n", port));
}
#[test]
#[ignore]
fn test_container_mode_ignores_native_process() {
let port = get_available_port();
let tempdir = tempfile::tempdir().unwrap();
let mut child = utils::start_tcp_listener(tempdir.path(), port);
let pid = child.id();
let mut cmd = assert_cmd::cargo_bin_cmd!("killport");
let output = cmd
.args([&port.to_string(), "--mode", "container"])
.assert()
.code(2);
let stdout = String::from_utf8_lossy(&output.get_output().stdout);
assert!(
stdout.contains("No container found"),
"Expected 'No container found', got: {}",
stdout
);
assert!(
utils::is_process_alive(pid),
"Native process should not be killed in container mode"
);
let _ = child.kill();
let _ = child.wait();
}
#[cfg(unix)]
#[test]
#[ignore]
fn test_kill_container_with_signal() {
let port = get_available_port();
let container_id = start_container(port);
assert!(
wait_for_container(&container_id, port, Duration::from_secs(15)),
"Container did not become ready on port {}",
port
);
let mut cmd = assert_cmd::cargo_bin_cmd!("killport");
let output = cmd
.args([&port.to_string(), "--mode", "container", "-s", "sigkill"])
.assert()
.success();
let stdout = String::from_utf8_lossy(&output.get_output().stdout);
assert!(
stdout.contains("Successfully killed"),
"Expected kill message, got: {}",
stdout
);
std::thread::sleep(Duration::from_secs(1));
assert!(
!is_container_running(&container_id),
"Container should be stopped after SIGKILL"
);
remove_container(&container_id);
}
#[test]
#[ignore]
fn test_auto_mode_port_forwarder_not_in_output() {
let port = get_available_port();
let container_id = start_container(port);
assert!(
wait_for_container(&container_id, port, Duration::from_secs(15)),
"Container did not become ready on port {}",
port
);
let mut cmd = assert_cmd::cargo_bin_cmd!("killport");
let output = cmd.args([&port.to_string()]).assert().success();
let stdout = String::from_utf8_lossy(&output.get_output().stdout);
assert!(
!stdout.contains("docker-proxy"),
"docker-proxy should be filtered out, got: {}",
stdout
);
remove_container(&container_id);
}