use assert_cmd::Command;
use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};
pub fn assert_error_contains(output: &std::process::Output, error_substring: &str) {
assert!(
!output.status.success(),
"Expected command to fail but it succeeded"
);
let stderr = String::from_utf8_lossy(&output.stderr);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stderr.contains(error_substring) || stdout.contains(error_substring),
"Expected error containing '{error_substring}' but got:\nSTDERR: {stderr}\nSTDOUT: {stdout}"
);
}
#[cfg(unix)]
pub fn create_symlink(target: &Path, link: &Path) -> std::io::Result<()> {
std::os::unix::fs::symlink(target, link)
}
#[cfg(windows)]
pub fn create_symlink(target: &Path, link: &Path) -> std::io::Result<()> {
if target.is_dir() {
std::os::windows::fs::symlink_dir(target, link)
} else {
std::os::windows::fs::symlink_file(target, link)
}
}
pub fn create_circular_symlinks(temp_dir: &Path) -> std::io::Result<()> {
let link_a = temp_dir.join("link_a");
let link_b = temp_dir.join("link_b");
create_symlink(&link_b, &link_a)?;
create_symlink(&link_a, &link_b)?;
Ok(())
}
pub struct PathologicalFileBuilder {
content: Vec<u8>,
}
impl PathologicalFileBuilder {
pub fn new() -> Self {
Self {
content: Vec::new(),
}
}
pub fn with_null_bytes(mut self, count: usize) -> Self {
self.content.extend(vec![0u8; count]);
self
}
pub fn with_mixed_line_endings(mut self) -> Self {
self.content.extend_from_slice(b"line1\r\n");
self.content.extend_from_slice(b"line2\n");
self.content.extend_from_slice(b"line3\r");
self
}
pub fn with_utf8_bom(mut self) -> Self {
let mut new_content = vec![0xEF, 0xBB, 0xBF]; new_content.extend(self.content);
self.content = new_content;
self
}
pub fn with_long_line(mut self, length: usize) -> Self {
self.content.extend(vec![b'a'; length]);
self.content.push(b'\n');
self
}
pub fn with_only_whitespace(mut self) -> Self {
self.content.extend_from_slice(b"\n\t \r\n \n");
self
}
pub fn with_text(mut self, text: &str) -> Self {
self.content.extend_from_slice(text.as_bytes());
self
}
pub fn write_to_file(self, path: &Path) -> std::io::Result<()> {
let mut file = fs::File::create(path)?;
file.write_all(&self.content)?;
Ok(())
}
}
pub fn create_large_file(path: &Path, size_mb: usize) -> std::io::Result<()> {
let mut file = fs::File::create(path)?;
let pattern = b"This is a repeating pattern for the large file. ";
let chunk_size = pattern.len();
let total_bytes = size_mb * 1024 * 1024;
let iterations = total_bytes / chunk_size;
for _ in 0..iterations {
file.write_all(pattern)?;
}
Ok(())
}
#[cfg(unix)]
pub fn create_readonly_file(path: &Path, content: &str) -> std::io::Result<()> {
fs::write(path, content)?;
use std::os::unix::fs::PermissionsExt;
let permissions = fs::Permissions::from_mode(0o444); fs::set_permissions(path, permissions)?;
Ok(())
}
#[cfg(not(unix))]
pub fn create_readonly_file(path: &Path, content: &str) -> std::io::Result<()> {
fs::write(path, content)?;
let mut permissions = fs::metadata(path)?.permissions();
permissions.set_readonly(true);
fs::set_permissions(path, permissions)?;
Ok(())
}
pub fn create_deep_directory(base: &Path, depth: usize) -> std::io::Result<PathBuf> {
let mut current = base.to_path_buf();
for i in 0..depth {
current = current.join(format!("level_{i}"));
}
fs::create_dir_all(¤t)?;
Ok(current)
}
#[allow(dead_code)]
pub fn generate_long_path(base: &Path, target_length: usize) -> PathBuf {
let mut path = base.to_path_buf();
let segment = "very_long_directory_name_component_";
while path.to_string_lossy().len() < target_length {
path = path.join(segment);
}
path
}
pub fn run_context_creator(args: &[&str]) -> std::process::Output {
Command::cargo_bin("context-creator")
.unwrap()
.args(args)
.output()
.expect("Failed to execute context-creator")
}
pub fn create_file_with_special_name(
dir: &Path,
name: &str,
content: &str,
) -> std::io::Result<PathBuf> {
let path = dir.join(name);
fs::write(&path, content)?;
Ok(path)
}
#[allow(dead_code)]
pub struct PlatformTest;
#[allow(dead_code)]
impl PlatformTest {
#[cfg(unix)]
pub fn unix_only<F>(test_fn: F)
where
F: FnOnce(),
{
test_fn();
}
#[cfg(not(unix))]
pub fn unix_only<F>(_test_fn: F)
where
F: FnOnce(),
{
println!("Skipping Unix-only test on current platform");
}
#[cfg(windows)]
pub fn windows_only<F>(test_fn: F)
where
F: FnOnce(),
{
test_fn();
}
#[cfg(not(windows))]
pub fn windows_only<F>(_test_fn: F)
where
F: FnOnce(),
{
println!("Skipping Windows-only test on current platform");
}
}
pub fn assert_graceful_failure(output: &std::process::Output) {
assert!(!output.status.success(), "Expected command to fail");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
!stderr.contains("panic"),
"Tool panicked instead of failing gracefully: {stderr}"
);
assert!(
!stderr.contains("RUST_BACKTRACE"),
"Tool showed backtrace instead of user-friendly error: {stderr}"
);
}