// what a command reports and how it exits, including when the write fails. mod support; use std::fs; use support::Case; #[test] fn findings_are_reported_through_the_exit_code() { let case = Case::new("findings"); case.write("loose.txt", b"x\n"); case.ahab(&["link", "check"]).ok(); case.ahab(&["link", "check", "--exit-code"]).code_is(4); } #[test] fn an_argument_error_exits_two() { let case = Case::new("usage"); case.ahab(&["link", "restore"]).code_is(2); case.ahab(&["link", "restore", "--all", "some/path"]) .code_is(2); case.ahab(&["postgres", "dump", "-F", "directory", "-z", "d"]) .code_is(2); case.ahab(&["nonsense"]).code_is(2); } #[test] fn a_write_that_cannot_be_delivered_is_a_failure_rather_than_a_success() { let case = Case::new("devfull"); for n in 0..200 { case.write(&format!("file-{n}.txt"), b"x\n"); } // /dev/full accepts the open and fails the write. exiting 0 there would // report a listing that was never delivered let Ok(full) = fs::File::create("/dev/full") else { eprintln!("skipped: this system has no /dev/full to fail a write against"); return; }; let out = std::process::Command::new(env!("CARGO_BIN_EXE_ahab")) .current_dir(&case.repo) .env("XDG_DATA_HOME", case.repo.join("../xdg")) .args(["link", "check", "--exit-code", "--porcelain"]) .stdout(full) .output() .expect("running ahab"); assert_eq!(out.status.code(), Some(1), "{:?}", out.status); assert!( String::from_utf8_lossy(&out.stderr).contains("writing to stdout failed"), "{}", String::from_utf8_lossy(&out.stderr) ); } #[test] fn every_named_path_is_moved_even_when_the_reader_leaves() { let case = Case::new("partial"); for name in ["a.env", "b.env", "c.env"] { case.write(name, b"x\n"); } case.gitignore("a.env\nb.env\nc.env\n"); // a closed stdout must stop the writing, not the moving: the command had // planned to move all three and reported success for doing so let run = case.ahab(&["link", "add", "a.env", "b.env", "c.env"]); run.ok(); for name in ["a.env", "b.env", "c.env"] { assert!(case.is_symlink(name), "{name} was left behind"); } } #[test] fn quiet_keeps_the_reason_a_path_failed() { let case = Case::new("quiet"); case.write("ok.env", b"x\n"); case.gitignore("ok.env\n"); let run = case.ahab(&["-q", "link", "add", "ok.env", "missing.env"]); run.failed() .says("1 of 2 paths failed") // the line saying *which* and *why* is a result, not progress .says("missing.env"); } #[test] fn a_dry_run_changes_nothing_on_disk() { let case = Case::new("dryrun"); case.write(".env", b"SECRET=1\n"); case.gitignore(".env\n"); case.ahab(&["--dry-run", "link", "add", ".env"]).ok(); assert!(!case.is_symlink(".env")); assert!(!case.store.join(".env").exists()); }