chore(release): prepare public source release
This commit is contained in:
@@ -0,0 +1,364 @@
|
||||
//! CLI integration tests for the `mhash` binary.
|
||||
|
||||
use std::fs;
|
||||
|
||||
use assert_cmd::Command;
|
||||
use predicates::prelude::*;
|
||||
use serde_json::Value;
|
||||
use tempfile::tempdir;
|
||||
|
||||
fn mhash_command() -> assert_cmd::Command {
|
||||
Command::cargo_bin("mhash").unwrap_or_else(|error| {
|
||||
panic!("failed to locate mhash binary: {error}");
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_algorithms_includes_openhashtab_matrix() {
|
||||
let mut command = mhash_command();
|
||||
command.arg("list-algorithms").arg("--json");
|
||||
command
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("parallelhash256-528"))
|
||||
.stdout(predicate::str::contains("kangarootwelve-264"))
|
||||
.stdout(predicate::str::contains("streebog-512"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hashes_file_as_json() {
|
||||
let directory = tempdir().unwrap_or_else(|error| panic!("tempdir failed: {error}"));
|
||||
let file = directory.path().join("sample.txt");
|
||||
fs::write(&file, b"abc").unwrap_or_else(|error| panic!("fixture write failed: {error}"));
|
||||
|
||||
let mut command = mhash_command();
|
||||
command
|
||||
.arg("--algorithm")
|
||||
.arg("sha256,blake3-256")
|
||||
.arg("--json")
|
||||
.arg(&file);
|
||||
command
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains(
|
||||
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
|
||||
))
|
||||
.stdout(predicate::str::contains("blake3-256"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hashes_file_as_jsonl() {
|
||||
let directory = tempdir().unwrap_or_else(|error| panic!("tempdir failed: {error}"));
|
||||
let file = directory.path().join("sample.txt");
|
||||
fs::write(&file, b"abc").unwrap_or_else(|error| panic!("fixture write failed: {error}"));
|
||||
|
||||
let mut command = mhash_command();
|
||||
command
|
||||
.arg("--algorithm")
|
||||
.arg("sha256,md5")
|
||||
.arg("--format")
|
||||
.arg("jsonl")
|
||||
.arg(&file);
|
||||
command
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("\"algorithm\":\"md5\""))
|
||||
.stdout(predicate::str::contains("\"algorithm\":\"sha256\""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recursive_ignore_and_sum_format_hash_only_included_files() {
|
||||
let directory = tempdir().unwrap_or_else(|error| panic!("tempdir failed: {error}"));
|
||||
let keep = directory.path().join("keep.txt");
|
||||
let nested = directory.path().join("nested");
|
||||
let ignored = nested.join("ignored.skip");
|
||||
fs::create_dir(&nested).unwrap_or_else(|error| panic!("fixture dir failed: {error}"));
|
||||
fs::write(&keep, b"abc").unwrap_or_else(|error| panic!("fixture write failed: {error}"));
|
||||
fs::write(&ignored, b"ignored").unwrap_or_else(|error| panic!("fixture write failed: {error}"));
|
||||
|
||||
let mut command = mhash_command();
|
||||
command
|
||||
.arg("--algorithm")
|
||||
.arg("sha256")
|
||||
.arg("--recursive")
|
||||
.arg("--ignore")
|
||||
.arg("*.skip")
|
||||
.arg("--format")
|
||||
.arg("sum")
|
||||
.arg(directory.path());
|
||||
command
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains(
|
||||
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad",
|
||||
))
|
||||
.stdout(predicate::str::contains("keep.txt"))
|
||||
.stdout(predicate::str::contains("ignored.skip").not());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn uppercase_sum_output_uses_uppercase_hex() {
|
||||
let directory = tempdir().unwrap_or_else(|error| panic!("tempdir failed: {error}"));
|
||||
let file = directory.path().join("sample.txt");
|
||||
fs::write(&file, b"abc").unwrap_or_else(|error| panic!("fixture write failed: {error}"));
|
||||
|
||||
let mut command = mhash_command();
|
||||
command
|
||||
.arg("--algorithm")
|
||||
.arg("sha256")
|
||||
.arg("--uppercase")
|
||||
.arg("--format")
|
||||
.arg("sum")
|
||||
.arg(&file);
|
||||
command.assert().success().stdout(predicate::str::contains(
|
||||
"BA7816BF8F01CFEA414140DE5DAE2223B00361A396177A9CB410FF61F20015AD",
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stdin_lines_input_hashes_existing_paths() {
|
||||
let directory = tempdir().unwrap_or_else(|error| panic!("tempdir failed: {error}"));
|
||||
let file = directory.path().join("sample.txt");
|
||||
fs::write(&file, b"abc").unwrap_or_else(|error| panic!("fixture write failed: {error}"));
|
||||
|
||||
let mut command = mhash_command();
|
||||
command
|
||||
.arg("--input-format")
|
||||
.arg("lines")
|
||||
.arg("--algorithm")
|
||||
.arg("md5")
|
||||
.arg("--format")
|
||||
.arg("jsonl")
|
||||
.write_stdin(format!("{}\n", file.display()));
|
||||
command.assert().success().stdout(predicate::str::contains(
|
||||
"\"digest\":\"900150983cd24fb0d6963f7d28e17f72\"",
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stdin_jsonl_input_hashes_existing_paths() {
|
||||
let directory = tempdir().unwrap_or_else(|error| panic!("tempdir failed: {error}"));
|
||||
let file = directory.path().join("sample.txt");
|
||||
fs::write(&file, b"abc").unwrap_or_else(|error| panic!("fixture write failed: {error}"));
|
||||
let input = format!(
|
||||
"{{\"path\":\"{}\"}}\n",
|
||||
file.display().to_string().replace('\\', "\\\\")
|
||||
);
|
||||
|
||||
let mut command = mhash_command();
|
||||
command
|
||||
.arg("--input-format")
|
||||
.arg("jsonl")
|
||||
.arg("--algorithm")
|
||||
.arg("sha1")
|
||||
.arg("--format")
|
||||
.arg("jsonl")
|
||||
.write_stdin(input);
|
||||
command.assert().success().stdout(predicate::str::contains(
|
||||
"\"digest\":\"a9993e364706816aba3e25717850c26c9cd0d89d\"",
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn help_and_json_hash_output_do_not_expose_backend_controls() {
|
||||
let directory = tempdir().unwrap_or_else(|error| panic!("tempdir failed: {error}"));
|
||||
let file = directory.path().join("sample.txt");
|
||||
fs::write(&file, b"abc").unwrap_or_else(|error| panic!("fixture write failed: {error}"));
|
||||
|
||||
mhash_command()
|
||||
.arg("--help")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("--backend").not());
|
||||
|
||||
let mut command = mhash_command();
|
||||
let output = command
|
||||
.arg("--algorithm")
|
||||
.arg("sha256")
|
||||
.arg("--io")
|
||||
.arg("read")
|
||||
.arg("--threads")
|
||||
.arg("2")
|
||||
.arg("--json")
|
||||
.arg(&file)
|
||||
.assert()
|
||||
.success()
|
||||
.get_output()
|
||||
.stdout
|
||||
.clone();
|
||||
let records: Vec<Value> = serde_json::from_slice(&output)
|
||||
.unwrap_or_else(|error| panic!("json parse failed: {error}"));
|
||||
|
||||
assert_eq!(records.len(), 1);
|
||||
assert!(records[0].get("requested_backend").is_none());
|
||||
assert!(records[0].get("backend").is_none());
|
||||
assert!(records[0].get("io").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn backend_option_is_not_public_cli() {
|
||||
let mut command = mhash_command();
|
||||
command
|
||||
.arg("--backend")
|
||||
.arg("simd")
|
||||
.arg("README.md")
|
||||
.assert()
|
||||
.code(2)
|
||||
.stderr(predicate::str::contains("unsupported argument"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bench_accepts_threads_and_io_options_as_json_smoke() {
|
||||
let directory = tempdir().unwrap_or_else(|error| panic!("tempdir failed: {error}"));
|
||||
let file = directory.path().join("sample.txt");
|
||||
fs::write(&file, b"abc").unwrap_or_else(|error| panic!("fixture write failed: {error}"));
|
||||
|
||||
let mut command = mhash_command();
|
||||
let output = command
|
||||
.arg("bench")
|
||||
.arg("--algorithm")
|
||||
.arg("sha256")
|
||||
.arg("--threads")
|
||||
.arg("2")
|
||||
.arg("--io")
|
||||
.arg("read")
|
||||
.arg("--format")
|
||||
.arg("json")
|
||||
.arg(&file)
|
||||
.assert()
|
||||
.success()
|
||||
.get_output()
|
||||
.stdout
|
||||
.clone();
|
||||
let records: Vec<Value> = serde_json::from_slice(&output)
|
||||
.unwrap_or_else(|error| panic!("json parse failed: {error}"));
|
||||
|
||||
assert_eq!(records.len(), 1);
|
||||
assert_eq!(records[0]["algorithm"], "sha256");
|
||||
assert_eq!(records[0]["bytes"], 3);
|
||||
assert!(records[0]["elapsed_ms"].as_f64().is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bench_end_to_end_reports_repeat_warmup_and_mode() {
|
||||
let directory = tempdir().unwrap_or_else(|error| panic!("tempdir failed: {error}"));
|
||||
let file = directory.path().join("sample.txt");
|
||||
fs::write(&file, b"abcdef").unwrap_or_else(|error| panic!("fixture write failed: {error}"));
|
||||
|
||||
let mut command = mhash_command();
|
||||
let output = command
|
||||
.arg("bench")
|
||||
.arg("--algorithm")
|
||||
.arg("sha256")
|
||||
.arg("--repeat")
|
||||
.arg("2")
|
||||
.arg("--warmup")
|
||||
.arg("1")
|
||||
.arg("--bench-mode")
|
||||
.arg("e2e")
|
||||
.arg("--io")
|
||||
.arg("read")
|
||||
.arg("--format")
|
||||
.arg("json")
|
||||
.arg(&file)
|
||||
.assert()
|
||||
.success()
|
||||
.get_output()
|
||||
.stdout
|
||||
.clone();
|
||||
let records: Vec<Value> = serde_json::from_slice(&output)
|
||||
.unwrap_or_else(|error| panic!("json parse failed: {error}"));
|
||||
|
||||
assert_eq!(records.len(), 1);
|
||||
assert_eq!(records[0]["algorithm"], "sha256");
|
||||
assert_eq!(records[0]["bytes"], 6);
|
||||
assert_eq!(records[0]["repeat"], 2);
|
||||
assert_eq!(records[0]["warmup"], 1);
|
||||
assert_eq!(records[0]["bench_mode"], "end-to-end");
|
||||
assert!(records[0]["elapsed_ms"].as_f64().is_some());
|
||||
assert!(records[0]["throughput_mib_s"].as_f64().is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verifies_generated_manifest() {
|
||||
let directory = tempdir().unwrap_or_else(|error| panic!("tempdir failed: {error}"));
|
||||
let file = directory.path().join("sample.txt");
|
||||
let manifest = directory.path().join("checksums.jsonl");
|
||||
fs::write(&file, b"abc").unwrap_or_else(|error| panic!("fixture write failed: {error}"));
|
||||
|
||||
let mut hash_command = mhash_command();
|
||||
hash_command
|
||||
.arg("--algorithm")
|
||||
.arg("sha256")
|
||||
.arg("--manifest")
|
||||
.arg(&manifest)
|
||||
.arg(&file);
|
||||
hash_command.assert().success();
|
||||
|
||||
let mut verify_command = mhash_command();
|
||||
verify_command.arg("verify").arg(&manifest).arg("--json");
|
||||
verify_command
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("\"ok\":true"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verifies_multi_algorithm_manifest_for_one_path() {
|
||||
let directory = tempdir().unwrap_or_else(|error| panic!("tempdir failed: {error}"));
|
||||
let file = directory.path().join("sample.txt");
|
||||
let manifest = directory.path().join("checksums.jsonl");
|
||||
fs::write(&file, b"abc").unwrap_or_else(|error| panic!("fixture write failed: {error}"));
|
||||
|
||||
let mut hash_command = mhash_command();
|
||||
hash_command
|
||||
.arg("--algorithm")
|
||||
.arg("sha256,md5,xxh3-128")
|
||||
.arg("--manifest")
|
||||
.arg(&manifest)
|
||||
.arg(&file);
|
||||
hash_command.assert().success();
|
||||
|
||||
let mut verify_command = mhash_command();
|
||||
let output = verify_command
|
||||
.arg("verify")
|
||||
.arg(&manifest)
|
||||
.arg("--json")
|
||||
.assert()
|
||||
.success()
|
||||
.get_output()
|
||||
.stdout
|
||||
.clone();
|
||||
let records: Vec<Value> = serde_json::from_slice(&output)
|
||||
.unwrap_or_else(|error| panic!("json parse failed: {error}"));
|
||||
|
||||
assert_eq!(records.len(), 3);
|
||||
assert!(records.iter().all(|record| record["ok"] == true));
|
||||
assert_eq!(records[0]["algorithm"], "md5");
|
||||
assert_eq!(records[1]["algorithm"], "sha256");
|
||||
assert_eq!(records[2]["algorithm"], "xxh3-128");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mercury_output_json_is_honored() {
|
||||
let directory = tempdir().unwrap_or_else(|error| panic!("tempdir failed: {error}"));
|
||||
let file = directory.path().join("sample.txt");
|
||||
fs::write(&file, b"abc").unwrap_or_else(|error| panic!("fixture write failed: {error}"));
|
||||
|
||||
let mut command = mhash_command();
|
||||
command.env("MERCURY_OUTPUT", "json").arg(&file);
|
||||
command
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::starts_with("[{"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_algorithm_exits_usage_error() {
|
||||
let mut command = mhash_command();
|
||||
command.arg("--algorithm").arg("sha999").arg("README.md");
|
||||
command
|
||||
.assert()
|
||||
.code(2)
|
||||
.stderr(predicate::str::contains("unsupported algorithm"));
|
||||
}
|
||||
Reference in New Issue
Block a user