use mdns_sd::{DaemonStatus, HostnameResolutionEvent, ServiceDaemon, ServiceEvent, ServiceInfo};
use std::collections::HashSet;
use std::thread::sleep;
use std::time::{Duration, SystemTime};
use test_log::test;
#[test]
fn test_shutdown_unregisters_services() {
let d = ServiceDaemon::new().expect("Failed to create daemon");
let ty_domain = "_shutdown-test1._udp.local.";
let now = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap();
let instance_name = format!("shutdown-test-{}", now.as_micros());
let my_service = ServiceInfo::new(
ty_domain,
&instance_name,
"shutdown-host.local.",
"",
5300,
None,
)
.expect("valid service info")
.enable_addr_auto();
let fullname = my_service.get_fullname().to_string();
d.register(my_service).expect("Failed to register service");
sleep(Duration::from_millis(500));
let d2 = ServiceDaemon::new().expect("Failed to create daemon");
let browse_chan = d2.browse(ty_domain).unwrap();
let mut found = false;
let timeout = Duration::from_secs(2);
let timer = std::time::Instant::now() + timeout;
while std::time::Instant::now() < timer {
if let Ok(ServiceEvent::ServiceResolved(info)) =
browse_chan.recv_timeout(Duration::from_millis(100))
{
if info.get_fullname() == fullname {
found = true;
break;
}
}
}
assert!(found, "Service should be discovered before shutdown");
let shutdown_receiver = d.shutdown().unwrap();
let status = shutdown_receiver.recv().unwrap();
assert!(matches!(status, DaemonStatus::Shutdown));
sleep(Duration::from_millis(500));
let mut removed = false;
let timer = std::time::Instant::now() + Duration::from_secs(2);
while std::time::Instant::now() < timer {
if let Ok(ServiceEvent::ServiceRemoved(_, removed_fullname)) =
browse_chan.recv_timeout(Duration::from_millis(100))
{
if removed_fullname == fullname {
removed = true;
break;
}
}
}
assert!(removed, "Service should be removed after shutdown");
d2.shutdown().unwrap();
}
#[test]
fn test_shutdown_stops_browse() {
let d = ServiceDaemon::new().expect("Failed to create daemon");
let ty_domain = "_shutdown-browse-test._udp.local.";
let browse_chan = d.browse(ty_domain).unwrap();
sleep(Duration::from_millis(100));
let shutdown_receiver = d.shutdown().unwrap();
let status = shutdown_receiver.recv().unwrap();
assert!(matches!(status, DaemonStatus::Shutdown));
let mut search_stopped = false;
let timeout = Duration::from_secs(2);
let timer = std::time::Instant::now() + timeout;
while std::time::Instant::now() < timer {
match browse_chan.recv_timeout(Duration::from_millis(100)) {
Ok(ServiceEvent::SearchStopped(stopped_ty)) => {
if stopped_ty == ty_domain {
search_stopped = true;
break;
}
}
Ok(_) => continue,
Err(_) => break,
}
}
assert!(
search_stopped,
"Browse should be stopped with SearchStopped event"
);
}
#[test]
fn test_shutdown_stops_hostname_resolution() {
let d = ServiceDaemon::new().expect("Failed to create daemon");
let hostname = "test-shutdown-host.local.";
let resolve_chan = d.resolve_hostname(hostname, None).unwrap();
sleep(Duration::from_millis(100));
let shutdown_receiver = d.shutdown().unwrap();
let status = shutdown_receiver.recv().unwrap();
assert!(matches!(status, DaemonStatus::Shutdown));
let mut search_stopped = false;
let timeout = Duration::from_secs(2);
let timer = std::time::Instant::now() + timeout;
while std::time::Instant::now() < timer {
match resolve_chan.recv_timeout(Duration::from_millis(100)) {
Ok(HostnameResolutionEvent::SearchStopped(stopped_hostname)) => {
if stopped_hostname.to_lowercase() == hostname.to_lowercase() {
search_stopped = true;
break;
}
}
Ok(_) => continue,
Err(_) => break,
}
}
assert!(
search_stopped,
"Hostname resolution should be stopped with SearchStopped event"
);
}
#[test]
fn test_shutdown_notifies_monitors() {
let d = ServiceDaemon::new().expect("Failed to create daemon");
let _monitor_chan = d.monitor().unwrap();
let ty_domain = "_shutdown-monitor-test._udp.local.";
let now = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap();
let instance_name = format!("monitor-test-{}", now.as_micros());
let my_service = ServiceInfo::new(
ty_domain,
&instance_name,
"monitor-host.local.",
"",
5301,
None,
)
.expect("valid service info")
.enable_addr_auto();
d.register(my_service).expect("Failed to register service");
sleep(Duration::from_millis(300));
let shutdown_receiver = d.shutdown().unwrap();
let status = shutdown_receiver.recv().unwrap();
assert!(matches!(status, DaemonStatus::Shutdown));
sleep(Duration::from_millis(300));
}
#[test]
fn test_shutdown_multiple_services() {
let d = ServiceDaemon::new().expect("Failed to create daemon");
let ty_domain = "_shutdown-multi-test._udp.local.";
let now = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_micros();
let mut fullnames = Vec::new();
for i in 0..3 {
let instance_name = format!("multi-test-{}-{}", now, i);
let my_service = ServiceInfo::new(
ty_domain,
&instance_name,
&format!("multi-host-{}.local.", i),
"",
5302 + i,
None,
)
.expect("valid service info")
.enable_addr_auto();
fullnames.push(my_service.get_fullname().to_string());
d.register(my_service).expect("Failed to register service");
}
sleep(Duration::from_millis(500));
let d2 = ServiceDaemon::new().expect("Failed to create daemon");
let browse_chan = d2.browse(ty_domain).unwrap();
let mut found_services = HashSet::new();
let timeout = Duration::from_secs(3);
let timer = std::time::Instant::now() + timeout;
while std::time::Instant::now() < timer && found_services.len() < fullnames.len() {
if let Ok(ServiceEvent::ServiceResolved(info)) =
browse_chan.recv_timeout(Duration::from_millis(100))
{
let fullname = info.get_fullname().to_string();
if fullnames.contains(&fullname) {
found_services.insert(fullname);
}
}
}
println!("Found {} services before shutdown", found_services.len());
let shutdown_receiver = d.shutdown().unwrap();
let status = shutdown_receiver.recv().unwrap();
assert!(matches!(status, DaemonStatus::Shutdown));
sleep(Duration::from_millis(500));
let mut removed_services = HashSet::new();
let timer = std::time::Instant::now() + Duration::from_secs(3);
while std::time::Instant::now() < timer && removed_services.len() < found_services.len() {
if let Ok(ServiceEvent::ServiceRemoved(_, removed_fullname)) =
browse_chan.recv_timeout(Duration::from_millis(100))
{
if fullnames.contains(&removed_fullname) {
removed_services.insert(removed_fullname);
}
}
}
println!("Removed {} services after shutdown", removed_services.len());
assert_eq!(
removed_services.len(),
found_services.len(),
"All discovered services should be removed after shutdown"
);
d2.shutdown().unwrap();
}
#[test]
fn test_operations_fail_after_shutdown() {
let d = ServiceDaemon::new().expect("Failed to create daemon");
let shutdown_receiver = d.shutdown().unwrap();
let status = shutdown_receiver.recv().unwrap();
assert!(matches!(status, DaemonStatus::Shutdown));
let ty_domain = "_post-shutdown-test._udp.local.";
let my_service = ServiceInfo::new(ty_domain, "test", "test.local.", "", 5303, None).unwrap();
let result = d.register(my_service);
assert!(result.is_err(), "Register should fail after shutdown");
let result = d.browse(ty_domain);
assert!(result.is_err(), "Browse should fail after shutdown");
let result = d.resolve_hostname("test.local.", None);
assert!(
result.is_err(),
"Resolve hostname should fail after shutdown"
);
let status_receiver = d.status().unwrap();
let status = status_receiver.recv().unwrap();
assert!(matches!(status, DaemonStatus::Shutdown));
}
#[test]
fn test_shutdown_idempotent() {
let d = ServiceDaemon::new().expect("Failed to create daemon");
let shutdown_receiver1 = d.shutdown().unwrap();
let status1 = shutdown_receiver1.recv().unwrap();
assert!(matches!(status1, DaemonStatus::Shutdown));
let result = d.shutdown();
if let Ok(shutdown_receiver2) = result {
let status2 = shutdown_receiver2.recv().unwrap();
assert!(matches!(status2, DaemonStatus::Shutdown));
}
}
#[test]
fn test_shutdown_concurrent_operations() {
let d = ServiceDaemon::new().expect("Failed to create daemon");
let d_clone = d.clone();
let handle = std::thread::spawn(move || {
let browse_chan = d_clone.browse("_concurrent-test._udp.local.").unwrap();
loop {
match browse_chan.recv_timeout(Duration::from_secs(5)) {
Ok(ServiceEvent::SearchStopped(_)) => break,
Ok(_) => continue,
Err(_) => break,
}
}
});
sleep(Duration::from_millis(100));
let shutdown_receiver = d.shutdown().unwrap();
let status = shutdown_receiver.recv().unwrap();
assert!(matches!(status, DaemonStatus::Shutdown));
handle.join().expect("Browse thread should complete");
}