diff --git a/apps/vectorcraft/tests/automation_args.rs b/apps/vectorcraft/tests/automation_args.rs new file mode 100644 index 00000000..2086b030 --- /dev/null +++ b/apps/vectorcraft/tests/automation_args.rs @@ -0,0 +1,70 @@ +// Integration tests may panic on failure (AGENTS.md › Robustness). +#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)] + +use std::process::{Command, Stdio}; +use std::time::{Duration, Instant}; + +const BIN: &str = env!("CARGO_BIN_EXE_vectorcraft"); + +/// Invalid roots must exit before desktop initialization on every platform, including a CI +/// machine with no display. A regression must fail rather than hang with a window open. +fn rejects(args: &[&str], message: &str) { + let mut child = Command::new(BIN) + .args(args) + .env_remove("VECTORCRAFT_CONTROL_PORT") + .env_remove("VECTORCRAFT_AUTOMATION_READ_ROOT") + .env_remove("VECTORCRAFT_AUTOMATION_WRITE_ROOT") + .env("VECTORCRAFT_NO_PREFS", "1") + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let deadline = Instant::now() + Duration::from_secs(5); + let timed_out = loop { + if child.try_wait().unwrap().is_some() { + break false; + } + if Instant::now() >= deadline { + child.kill().unwrap(); + break true; + } + std::thread::sleep(Duration::from_millis(10)); + }; + let out = child.wait_with_output().unwrap(); + assert!(!timed_out, "{args:?}: desktop started or did not exit promptly"); + assert!(!out.status.success(), "{args:?}"); + assert!(out.stdout.is_empty(), "{args:?}: {:?}", out.stdout); + // Exact stderr excludes unrelated startup warnings or display errors that could otherwise + // make a malformed invocation fail for an unrelated reason. + assert_eq!(String::from_utf8(out.stderr).unwrap(), format!("vectorcraft: {message}\n"), "{args:?}"); +} + +#[test] +fn missing_empty_or_option_operands_fail_before_startup() { + for flag in ["--automation-read-root", "--automation-write-root"] { + rejects(&["--control", "7979", flag], &format!("{flag} needs a folder")); + rejects(&[flag], &format!("{flag} needs a folder")); + rejects(&["--control", "7979", &format!("{flag}=")], &format!("{flag} needs a non-empty folder")); + rejects(&[flag, ""], &format!("{flag} needs a non-empty folder")); + for next in ["--version", "--control", "--automation-write-root", "-folder"] { + rejects(&[flag, next], &format!("{flag} needs a folder; use {flag}=