use std::process as path_std_process;
use tau_proto::CborValue;
use super::*;
use crate::tools as path_crate_tools;
fn ls_args(path: &std::path::Path) -> CborValue {
CborValue::Map(vec![(
CborValue::Text("path".to_owned()),
CborValue::Text(path.display().to_string()),
)])
}
#[test]
fn remove_file_failure_injection_is_path_and_instance_scoped() {
let tempdir = tempfile::TempDir::new().expect("tempdir");
let injected_path = tempdir.path().join("injected.txt");
let other_path = tempdir.path().join("other.txt");
std::fs::write(&injected_path, "injected\n").expect("write injected file");
std::fs::write(&other_path, "other\n").expect("write other file");
let mut injected_world = ShellWorld::real();
injected_world.fail_next_remove_file_for(injected_path.clone());
let mut other_world = ShellWorld::real();
other_world
.remove_file(&injected_path)
.expect("other world should not inherit the injected failure");
std::fs::write(&injected_path, "injected\n").expect("restore injected file");
injected_world
.remove_file(&other_path)
.expect("other path should not use the injected failure");
let error = injected_world
.remove_file(&injected_path)
.expect_err("configured path should fail before removal");
assert_eq!(error.kind(), io::ErrorKind::PermissionDenied);
assert_eq!(
std::fs::read_to_string(&injected_path).expect("read injected file"),
"injected\n"
);
injected_world
.remove_file(&injected_path)
.expect("configured failure should apply only once");
}
#[cfg(unix)]
#[test]
fn read_file_limited_rejects_fifo_without_blocking() {
let tempdir = tempfile::TempDir::new().expect("tempdir");
let fifo_path = tempdir.path().join("pipe");
let status = path_std_process::Command::new("mkfifo")
.arg(&fifo_path)
.status()
.expect("run mkfifo");
assert!(status.success(), "mkfifo failed");
let error = read_file_limited_real(&fifo_path, MAX_SAFE_FILE_READ_BYTES)
.expect_err("fifo should be rejected without blocking");
assert_eq!(error.kind(), io::ErrorKind::InvalidInput);
assert!(error.to_string().contains("not a regular file"));
}
#[cfg(unix)]
#[test]
fn atomic_write_preserves_final_symlink() {
use std::os::unix::fs::symlink;
let tempdir = tempfile::TempDir::new().expect("tempdir");
let target = tempdir.path().join("target.txt");
let link = tempdir.path().join("link.txt");
std::fs::write(&target, "old\n").expect("write target");
symlink("target.txt", &link).expect("symlink");
atomic_write_file(&link, b"new\n").expect("atomic write");
assert_eq!(
std::fs::read_to_string(&target).expect("read target"),
"new\n"
);
assert!(
std::fs::symlink_metadata(&link)
.expect("link metadata")
.file_type()
.is_symlink()
);
}
#[cfg(unix)]
#[test]
fn atomic_write_follows_chained_final_symlink() {
use std::os::unix::fs::symlink;
let tempdir = tempfile::TempDir::new().expect("tempdir");
let target = tempdir.path().join("target.txt");
let link2 = tempdir.path().join("link2.txt");
let link1 = tempdir.path().join("link1.txt");
std::fs::write(&target, "old\n").expect("write target");
symlink("target.txt", &link2).expect("link2");
symlink("link2.txt", &link1).expect("link1");
atomic_write_file(&link1, b"new\n").expect("atomic write");
assert_eq!(
std::fs::read_to_string(&target).expect("read target"),
"new\n"
);
assert!(
std::fs::symlink_metadata(&link1)
.expect("link1 metadata")
.file_type()
.is_symlink()
);
assert!(
std::fs::symlink_metadata(&link2)
.expect("link2 metadata")
.file_type()
.is_symlink()
);
}
#[test]
fn atomic_write_temp_collision_preserves_existing_temp() {
let tempdir = tempfile::TempDir::new().expect("tempdir");
let target = tempdir.path().join("target.txt");
let colliding_temp = tempdir.path().join(".target.txt.tmp-collision");
std::fs::write(&target, "old\n").expect("write target");
std::fs::write(&colliding_temp, "someone else\n").expect("write temp");
let err = atomic_write_file_to_temp(&target, &colliding_temp, b"new\n")
.expect_err("temp collision should fail");
assert_eq!(err.kind(), io::ErrorKind::AlreadyExists);
assert_eq!(
std::fs::read_to_string(&target).expect("read target"),
"old\n"
);
assert_eq!(
std::fs::read_to_string(&colliding_temp).expect("read temp"),
"someone else\n"
);
}
#[test]
fn ls_vcr_records_world_ops_and_replays_through_tool_logic() {
let real_dir = tempfile::TempDir::new().expect("real dir");
std::fs::write(real_dir.path().join("beta"), "b").expect("write beta");
std::fs::create_dir(real_dir.path().join("alpha")).expect("create alpha");
let cassette_dir = tempfile::TempDir::new().expect("cassette dir");
let args = ls_args(real_dir.path());
let mut recording = ShellWorld::for_tool(
"ls",
"call_ls",
&args,
Some(tau_vcr::VcrConfig::new(
tau_vcr::VcrMode::RecordIfMissing,
cassette_dir.path(),
)),
)
.expect("recording world");
let recorded = path_crate_tools::ls::run_ls(&args, &mut recording).expect("recorded ls");
recording.finish().expect("record cassette");
std::fs::remove_file(real_dir.path().join("beta")).expect("remove live file");
std::fs::remove_dir(real_dir.path().join("alpha")).expect("remove live dir");
let mut replay = ShellWorld::for_tool(
"ls",
"call_ls",
&args,
Some(tau_vcr::VcrConfig::new(
tau_vcr::VcrMode::ReplayOnly,
cassette_dir.path(),
)),
)
.expect("replay world");
let replayed = path_crate_tools::ls::run_ls(&args, &mut replay).expect("replayed ls");
replay.finish().expect("consume replay ops");
assert_eq!(replayed.result, recorded.result);
let cassette =
std::fs::read_to_string(cassette_dir.path().join("call_ls.yaml")).expect("read cassette");
assert!(cassette.contains("op: is_dir"));
assert!(cassette.contains("op: read_dir"));
assert!(cassette.contains("name: alpha"));
assert!(!cassette.contains("kind: utf8"));
assert!(!cassette.contains("value: alpha"));
assert!(!cassette.contains("1 alpha/"));
}
#[test]
fn read_vcr_replays_file_bytes_through_read_logic() {
let real_dir = tempfile::TempDir::new().expect("real dir");
let file = real_dir.path().join("file.txt");
std::fs::write(&file, b"alpha\n\xFFbeta").expect("write file");
let cassette_dir = tempfile::TempDir::new().expect("cassette dir");
let args = CborValue::Map(vec![
(
CborValue::Text("path".to_owned()),
CborValue::Text(file.display().to_string()),
),
(
CborValue::Text("start_line".to_owned()),
CborValue::Integer(2.into()),
),
]);
let mut recording = ShellWorld::for_tool(
"read",
"call_read",
&args,
Some(tau_vcr::VcrConfig::new(
tau_vcr::VcrMode::RecordIfMissing,
cassette_dir.path(),
)),
)
.expect("recording world");
let recorded = path_crate_tools::read::read_file(&args, &mut recording).expect("recorded read");
recording.finish().expect("record cassette");
std::fs::write(&file, b"changed").expect("change live file");
let mut replay = ShellWorld::for_tool(
"read",
"call_read",
&args,
Some(tau_vcr::VcrConfig::new(
tau_vcr::VcrMode::ReplayOnly,
cassette_dir.path(),
)),
)
.expect("replay world");
let replayed = path_crate_tools::read::read_file(&args, &mut replay).expect("replayed read");
replay.finish().expect("consume replay ops");
assert_eq!(replayed.result, recorded.result);
let cassette =
std::fs::read_to_string(cassette_dir.path().join("call_read.yaml")).expect("read cassette");
assert!(cassette.contains("op: read_file"));
assert!(cassette.contains("\\uDCFFbeta"));
assert!(!cassette.contains("- 255"));
}
#[test]
fn edit_vcr_replay_asserts_write_without_mutating_live_file() {
let real_dir = tempfile::TempDir::new().expect("real dir");
let file = real_dir.path().join("file.txt");
std::fs::write(&file, b"one\ntwo\n").expect("write file");
let cassette_dir = tempfile::TempDir::new().expect("cassette dir");
let args = CborValue::Map(vec![
(
CborValue::Text("path".to_owned()),
CborValue::Text(file.display().to_string()),
),
(
CborValue::Text("edits".to_owned()),
CborValue::Array(vec![CborValue::Map(vec![
(
CborValue::Text("start_line".to_owned()),
CborValue::Integer(2.into()),
),
(
CborValue::Text("end_line_exclusive".to_owned()),
CborValue::Integer(3.into()),
),
(
CborValue::Text("newText".to_owned()),
CborValue::Text("TWO\n".to_owned()),
),
(
CborValue::Text("context_line".to_owned()),
CborValue::Text("two".to_owned()),
),
])]),
),
]);
let mut recording = ShellWorld::for_tool(
"edit",
"call_edit",
&args,
Some(tau_vcr::VcrConfig::new(
tau_vcr::VcrMode::RecordIfMissing,
cassette_dir.path(),
)),
)
.expect("recording world");
let recorded = path_crate_tools::edit::edit_file(&args, &mut recording).expect("recorded edit");
recording.finish().expect("record cassette");
assert_eq!(
std::fs::read(&file).expect("read recorded file"),
b"one\nTWO\n"
);
std::fs::write(&file, b"live should not change\n").expect("change live file");
let mut replay = ShellWorld::for_tool(
"edit",
"call_edit",
&args,
Some(tau_vcr::VcrConfig::new(
tau_vcr::VcrMode::ReplayOnly,
cassette_dir.path(),
)),
)
.expect("replay world");
let replayed = path_crate_tools::edit::edit_file(&args, &mut replay).expect("replayed edit");
replay.finish().expect("consume replay ops");
assert_eq!(replayed.result, recorded.result);
assert_eq!(
std::fs::read(&file).expect("read live file"),
b"live should not change\n"
);
let cassette =
std::fs::read_to_string(cassette_dir.path().join("call_edit.yaml")).expect("read cassette");
assert!(cassette.contains("op: read_file"));
assert!(cassette.contains("op: path_exists"));
assert!(cassette.contains("op: write_file"));
}
#[test]
fn apply_patch_vcr_replay_asserts_move_write_and_remove_without_mutating_live_files() {
let real_dir = tempfile::TempDir::new().expect("real dir");
let source = real_dir.path().join("source.txt");
let dest = real_dir.path().join("dest.txt");
std::fs::write(&source, "one\ntwo\n").expect("write source");
let cassette_dir = tempfile::TempDir::new().expect("cassette dir");
let args = CborValue::Text(format!(
"*** Begin Patch\n*** Update File: {}\n*** Move to: {}\n@@\n one\n-two\n+TWO\n*** End Patch",
source.display(),
dest.display()
));
let mut recording = ShellWorld::for_tool(
"apply_patch",
"call_patch",
&args,
Some(tau_vcr::VcrConfig::new(
tau_vcr::VcrMode::RecordIfMissing,
cassette_dir.path(),
)),
)
.expect("recording world");
let recorded = path_crate_tools::apply_patch::apply_patch(&args, &mut recording)
.expect("recorded apply_patch");
recording.finish().expect("record cassette");
assert!(!source.exists());
assert_eq!(
std::fs::read_to_string(&dest).expect("read dest"),
"one\nTWO\n"
);
std::fs::write(&source, "live source\n").expect("restore live source");
std::fs::write(&dest, "live dest\n").expect("change live dest");
let mut replay = ShellWorld::for_tool(
"apply_patch",
"call_patch",
&args,
Some(tau_vcr::VcrConfig::new(
tau_vcr::VcrMode::ReplayOnly,
cassette_dir.path(),
)),
)
.expect("replay world");
let replayed = path_crate_tools::apply_patch::apply_patch(&args, &mut replay)
.expect("replayed apply_patch");
replay.finish().expect("consume replay ops");
assert_eq!(replayed.result, recorded.result);
assert_eq!(
std::fs::read_to_string(&source).expect("read source"),
"live source\n"
);
assert_eq!(
std::fs::read_to_string(&dest).expect("read dest"),
"live dest\n"
);
let cassette = std::fs::read_to_string(cassette_dir.path().join("call_patch.yaml"))
.expect("read cassette");
assert!(cassette.contains("op: read_file"));
assert!(cassette.contains("op: create_dir_all"));
assert!(cassette.contains("op: write_file"));
assert!(cassette.contains("op: is_dir"));
assert!(cassette.contains("op: remove_file"));
}
#[test]
fn apply_patch_vcr_relative_paths_do_not_record_cwd_absolute_paths() {
let cwd = std::env::current_dir().expect("current dir");
let real_dir = tempfile::Builder::new()
.prefix("world-relative-")
.tempdir_in(&cwd)
.expect("real dir under cwd");
let source = real_dir.path().join("source.txt");
let dest = real_dir.path().join("dest.txt");
std::fs::write(&source, "one\ntwo\n").expect("write source");
let source_rel = source.strip_prefix(&cwd).expect("relative source");
let dest_rel = dest.strip_prefix(&cwd).expect("relative dest");
let cassette_dir = tempfile::TempDir::new().expect("cassette dir");
let args = CborValue::Text(format!(
"*** Begin Patch\n*** Update File: {}\n*** Move to: {}\n@@\n one\n-two\n+TWO\n*** End Patch",
source_rel.display(),
dest_rel.display()
));
let mut recording = ShellWorld::for_tool(
"apply_patch",
"call_relative_patch",
&args,
Some(tau_vcr::VcrConfig::new(
tau_vcr::VcrMode::RecordIfMissing,
cassette_dir.path(),
)),
)
.expect("recording world");
path_crate_tools::apply_patch::apply_patch(&args, &mut recording)
.expect("recorded apply_patch");
recording.finish().expect("record cassette");
let cassette = std::fs::read_to_string(cassette_dir.path().join("call_relative_patch.yaml"))
.expect("read cassette");
assert!(cassette.contains(&format!("path: {}", source_rel.display())));
assert!(cassette.contains(&format!("path: {}", dest_rel.display())));
assert!(!cassette.contains(cwd.to_str().expect("utf8 cwd")));
}