//! Integration tests for the `portunlock` command. use assert_cmd::Command; use predicates::prelude::*; use std::io::{BufRead, BufReader}; use std::process::{Child, Command as StdCommand, Stdio}; fn cargo_command() -> Command { Command::cargo_bin("portunlock").expect("binary") } struct Holder { child: Child, tcp_port: u16, udp_port: u16, } impl Drop for Holder { fn drop(&mut self) { let _ = self.child.kill(); let _ = self.child.wait(); } } fn spawn_holder() -> Holder { let script = "\ $ErrorActionPreference='Stop'; \ $tcp=[System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Loopback,0); \ $tcp.Start(); \ $udp=[System.Net.Sockets.UdpClient]::new(0); \ $tcpPort=([System.Net.IPEndPoint]$tcp.LocalEndpoint).Port; \ $udpPort=([System.Net.IPEndPoint]$udp.Client.LocalEndPoint).Port; \ [Console]::Out.WriteLine(\"$tcpPort,$udpPort\"); \ [Console]::Out.Flush(); \ try { Start-Sleep -Seconds 30 } finally { $tcp.Stop(); $udp.Close() }"; let mut child = StdCommand::new("pwsh") .args(["-NoProfile", "-Command", script]) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn() .expect("spawn holder"); let stdout = child.stdout.take().expect("holder stdout"); let mut reader = BufReader::new(stdout); let mut line = String::new(); let read = reader.read_line(&mut line).expect("holder port line"); if read == 0 { let status = child.wait().expect("holder exit status"); panic!("holder exited before reporting ports: {status}"); } let (tcp, udp) = line.trim().split_once(',').expect("tcp,udp port line"); Holder { child, tcp_port: tcp.parse().expect("tcp port"), udp_port: udp.parse().expect("udp port"), } } #[test] fn reports_port_owners_and_non_force_free_reports_remaining_tcp_owner() { let holder = spawn_holder(); let tcp_port = holder.tcp_port; let udp_port = holder.udp_port; let mut who = cargo_command(); who.args(["who", "--json", "--protocol", "any"]) .arg(tcp_port.to_string()) .arg(udp_port.to_string()) .assert() .success() .stdout(predicate::str::contains(format!("\"port\":{tcp_port}"))) .stdout(predicate::str::contains(format!("\"port\":{udp_port}"))) .stdout(predicate::str::contains("\"protocol\":\"tcp\"")) .stdout(predicate::str::contains("\"protocol\":\"udp\"")); let mut free = cargo_command(); free.args(["free", "--json", "--protocol", "tcp"]) .arg(tcp_port.to_string()) .assert() .code(3) .stdout(predicate::str::contains(format!("\"port\":{tcp_port}"))) .stdout(predicate::str::contains("\"ok\":false")) .stdout(predicate::str::contains("owners remain")); } #[test] fn help_includes_port_examples() { let mut command = cargo_command(); command .arg("--help") .assert() .success() .stdout(predicate::str::contains("--protocol")) .stdout(predicate::str::contains("portunlock who")) .stdout(predicate::str::contains("ConvertFrom-Json")); }