mod common;
use common::*;
use predicates::prelude::*;
#[test]
fn test_compress_basic_file() {
let dir = test_dir();
let test_data = b"Hello, world! This is a test file for compression. ".repeat(20);
let input = create_test_file(dir.path(), "test.txt", &test_data);
let output = dir.path().join("test.txt.crush");
crush_cmd()
.arg("compress")
.arg(&input)
.assert()
.success()
.stdout(predicate::str::contains("Compressed"));
assert_file_exists(&output);
assert_compressed(&input, &output);
}
#[test]
fn test_compress_file_not_found() {
crush_cmd()
.arg("compress")
.arg("nonexistent.txt")
.assert()
.failure()
.stderr(
predicate::str::contains("not found")
.or(predicate::str::contains("No such file"))
.or(predicate::str::contains("does not exist")),
);
}
#[test]
fn test_compress_output_exists() {
let dir = test_dir();
let input = create_test_file(dir.path(), "test.txt", b"Test data");
let _output = dir.path().join("test.txt.crush");
create_test_file(dir.path(), "test.txt.crush", b"existing data");
crush_cmd()
.arg("compress")
.arg(&input)
.assert()
.failure()
.stderr(
predicate::str::contains("already exists").or(predicate::str::contains("File exists")),
);
}
#[test]
fn test_compress_force_overwrite() {
let dir = test_dir();
let input = create_test_file(dir.path(), "test.txt", b"New test data for compression");
let output = dir.path().join("test.txt.crush");
let old_content = b"old compressed data";
create_test_file(dir.path(), "test.txt.crush", old_content);
crush_cmd()
.arg("compress")
.arg("--force")
.arg(&input)
.assert()
.success();
assert_file_exists(&output);
let new_content = read_file(&output);
assert_ne!(
new_content, old_content,
"File should have been overwritten"
);
}
#[test]
fn test_compress_keep_input() {
let dir = test_dir();
let input = create_test_file(dir.path(), "test.txt", b"Data to compress");
let output = dir.path().join("test.txt.crush");
crush_cmd().arg("compress").arg(&input).assert().success();
assert_file_exists(&input);
assert_file_exists(&output);
}
#[test]
#[cfg(windows)]
fn test_compress_preserves_mtime_windows() {
let dir = test_dir();
let input = create_test_file(dir.path(), "test.txt", b"mtime test data");
let output = dir.path().join("test.txt.crush");
let restored_path = dir.path().join("restored.txt");
let original_mtime = filetime::FileTime::from_unix_time(1_500_000_000, 0); filetime::set_file_mtime(&input, original_mtime).unwrap();
crush_cmd().arg("compress").arg(&input).assert().success();
crush_cmd()
.arg("decompress")
.arg(&output)
.arg("-o")
.arg(&restored_path)
.assert()
.success();
let restored_mtime = filetime::FileTime::from_last_modification_time(
&std::fs::metadata(&restored_path).unwrap(),
);
assert_eq!(
original_mtime, restored_mtime,
"Modification time should be preserved after roundtrip"
);
}
#[test]
#[cfg(target_os = "linux")]
fn test_compress_preserves_mtime_linux() {
let dir = test_dir();
let input = create_test_file(dir.path(), "test.txt", b"mtime test data for Linux");
let output = dir.path().join("test.txt.crush");
let restored_path = dir.path().join("restored.txt");
let original_mtime = filetime::FileTime::from_unix_time(1_600_000_000, 0);
filetime::set_file_mtime(&input, original_mtime).unwrap();
crush_cmd().arg("compress").arg(&input).assert().success();
crush_cmd()
.arg("decompress")
.arg(&output)
.arg("-o")
.arg(&restored_path)
.assert()
.success();
let restored_mtime = filetime::FileTime::from_last_modification_time(
&std::fs::metadata(&restored_path).unwrap(),
);
assert_eq!(
original_mtime, restored_mtime,
"Modification time should be preserved on Linux"
);
}
#[test]
#[cfg(target_os = "macos")]
fn test_compress_preserves_mtime_macos() {
let dir = test_dir();
let input = create_test_file(dir.path(), "test.txt", b"mtime test data for macOS");
let output = dir.path().join("test.txt.crush");
let restored_path = dir.path().join("restored.txt");
let original_mtime = filetime::FileTime::from_unix_time(1_600_000_000, 0);
filetime::set_file_mtime(&input, original_mtime).unwrap();
crush_cmd().arg("compress").arg(&input).assert().success();
crush_cmd()
.arg("decompress")
.arg(&output)
.arg("-o")
.arg(&restored_path)
.assert()
.success();
let restored_mtime = filetime::FileTime::from_last_modification_time(
&std::fs::metadata(&restored_path).unwrap(),
);
assert_eq!(
original_mtime, restored_mtime,
"Modification time should be preserved on macOS"
);
}
#[test]
#[cfg(unix)]
fn test_compress_preserves_unix_permissions() {
use std::os::unix::fs::PermissionsExt;
let dir = test_dir();
let input = create_test_file(dir.path(), "test.txt", b"permissions test data");
let output = dir.path().join("test.txt.crush");
let restored_path = dir.path().join("restored.txt");
let original_perms = std::fs::Permissions::from_mode(0o755);
std::fs::set_permissions(&input, original_perms.clone()).unwrap();
crush_cmd().arg("compress").arg(&input).assert().success();
std::fs::remove_file(&input).unwrap();
crush_cmd()
.arg("decompress")
.arg(&output)
.arg("-o")
.arg(&restored_path)
.assert()
.success();
let restored_perms = std::fs::metadata(&restored_path).unwrap().permissions();
assert_eq!(
original_perms.mode() & 0o777,
restored_perms.mode() & 0o777,
"Unix permissions should be preserved (expected: {:o}, got: {:o})",
original_perms.mode() & 0o777,
restored_perms.mode() & 0o777
);
}
#[test]
fn test_compress_large_file_progress() {
let dir = test_dir();
let large_data = vec![0u8; 2 * 1024 * 1024]; let input = create_test_file(dir.path(), "large.bin", &large_data);
let output = dir.path().join("large.bin.crush");
crush_cmd().arg("compress").arg(&input).assert().success();
assert_file_exists(&output);
}
#[test]
fn test_compress_displays_statistics() {
let dir = test_dir();
let test_data = b"statistics test data";
let input = create_test_file(dir.path(), "test.txt", test_data);
let _output = dir.path().join("test.txt.crush");
let assert = crush_cmd().arg("compress").arg(&input).assert().success();
let stdout = String::from_utf8(assert.get_output().stdout.clone()).unwrap();
assert!(
stdout.contains("Compressed"),
"Should show 'Compressed' message"
);
assert!(stdout.contains("test.txt"), "Should show input filename");
assert!(
stdout.contains("test.txt.crush"),
"Should show output filename"
);
assert!(stdout.contains("MB/s"), "Should show throughput");
assert!(
stdout.contains("smaller") || stdout.contains("larger") || stdout.contains("same size"),
"Should show size comparison"
);
}
#[test]
fn test_compress_verbose_plugin_selection() {
let dir = test_dir();
let test_data = b"verbose test data for plugin selection";
let input = create_test_file(dir.path(), "test.txt", test_data);
let assert = crush_cmd()
.arg("-v")
.arg("compress")
.arg(&input)
.assert()
.success();
let stderr = String::from_utf8(assert.get_output().stderr.clone()).unwrap();
assert!(
stderr.contains("plugin") || stderr.contains("deflate") || stderr.contains("selected"),
"Verbose output should show plugin selection information"
);
}
#[test]
fn test_compress_verbose_performance_metrics() {
let dir = test_dir();
let test_data = b"verbose test data for performance metrics";
let input = create_test_file(dir.path(), "test.txt", test_data);
let assert = crush_cmd()
.arg("-v")
.arg("compress")
.arg(&input)
.assert()
.success();
let stderr = String::from_utf8(assert.get_output().stderr.clone()).unwrap();
assert!(
stderr.contains("throughput") || stderr.contains("MB/s") || stderr.contains("performance"),
"Verbose output should show performance metrics"
);
}
#[test]
fn test_compress_verbose_debug_level() {
let dir = test_dir();
let test_data = b"debug level verbose test data";
let input = create_test_file(dir.path(), "test.txt", test_data);
let assert = crush_cmd()
.arg("-v")
.arg("compress")
.arg(&input)
.assert()
.success();
let stderr = String::from_utf8(assert.get_output().stderr.clone()).unwrap();
assert!(
!stderr.is_empty(),
"Debug level (-v) should produce diagnostic output"
);
}
#[test]
fn test_compress_verbose_trace_level() {
let dir = test_dir();
let test_data = b"trace level verbose test data";
let input = create_test_file(dir.path(), "test.txt", test_data);
let assert = crush_cmd()
.arg("-vv")
.arg("compress")
.arg(&input)
.assert()
.success();
let stderr = String::from_utf8(assert.get_output().stderr.clone()).unwrap();
assert!(
!stderr.is_empty(),
"Trace level (-vv) should produce detailed diagnostic output"
);
assert!(
stderr.len() > 20,
"Trace level output should be reasonably detailed"
);
}
#[test]
fn test_compress_interrupt_cleanup() {
use std::process::{Command, Stdio};
use std::thread;
use std::time::Duration;
let dir = test_dir();
let large_data = vec![0u8; 50 * 1024 * 1024]; let input = create_test_file(dir.path(), "interrupt_test.bin", &large_data);
let _output = dir.path().join("interrupt_test.bin.crush");
#[allow(deprecated)]
let crush_path = assert_cmd::cargo::cargo_bin("crush");
let mut child = Command::new(&crush_path)
.arg("compress")
.arg(&input)
.current_dir(dir.path())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn()
.expect("Failed to start compress process");
thread::sleep(Duration::from_millis(5));
child.kill().expect("Failed to kill process");
let status = child.wait().expect("Failed to wait for process");
assert!(
!status.success(),
"Process should not exit successfully after being killed"
);
}