//! Process-level checks for the NDI runtime boundary. //! //! These deliberately run the real binary in fresh processes so the loader's //! one-time cache cannot leak from one assertion into another. use std::path::PathBuf; use std::process::Command; use std::time::{SystemTime, UNIX_EPOCH}; fn binary() -> &'static str { env!("CARGO_BIN_EXE_video-output") } fn definitely_missing_runtime_dir() -> PathBuf { let nonce = SystemTime::now() .duration_since(UNIX_EPOCH) .expect("the system clock should be after 1970") .as_nanos(); std::env::temp_dir().join(format!( "video-output-missing-ndi-runtime-{}-{nonce}", std::process::id() )) } #[test] fn missing_ndi_runtime_is_clear_and_does_not_affect_non_ndi_commands() { let missing = definitely_missing_runtime_dir(); let ndi = Command::new(binary()) .args(["ndi-sources", "--timeout", "1"]) .env("NDI_RUNTIME_DIR_V6", &missing) .env_remove("JOURNAL_STREAM") .output() .expect("run video-output ndi-sources"); assert!(!ndi.status.success(), "NDI discovery must fail without its runtime"); let diagnostic = format!( "{}{}", String::from_utf8_lossy(&ndi.stdout), String::from_utf8_lossy(&ndi.stderr) ) .to_ascii_lowercase(); assert!( diagnostic.contains("could not load the ndi runtime"), "expected a clear NDI loader error, got: {diagnostic}" ); let version = Command::new(binary()) .arg("--version") .env("NDI_RUNTIME_DIR_V6", &missing) .env_remove("JOURNAL_STREAM") .output() .expect("run video-output --version"); assert!( version.status.success(), "a missing NDI runtime must not affect non-NDI commands: {}", String::from_utf8_lossy(&version.stderr) ); assert!(String::from_utf8_lossy(&version.stdout).contains("video-output")); } #[cfg(target_os = "linux")] #[test] fn elf_has_no_hard_dependency_on_libndi() { let output = match Command::new("readelf").args(["-d", binary()]).output() { Ok(output) => output, Err(e) if e.kind() == std::io::ErrorKind::NotFound => { eprintln!("skipping ELF dependency check: readelf is not installed"); return; } Err(e) => panic!("could not run readelf: {e}"), }; assert!( output.status.success(), "readelf failed: {}", String::from_utf8_lossy(&output.stderr) ); let dynamic = String::from_utf8_lossy(&output.stdout); let needed: Vec<&str> = dynamic.lines().filter(|line| line.contains("(NEEDED)")).collect(); assert!( needed.iter().all(|line| !line.to_ascii_lowercase().contains("libndi")), "binary acquired a hard NDI dependency:\n{}", needed.join("\n") ); }