diff --git a/src/uu/b2sum/src/b2sum.rs b/src/uu/b2sum/src/b2sum.rs index ddd5fe3e9e6..ee6bad0d488 100644 --- a/src/uu/b2sum/src/b2sum.rs +++ b/src/uu/b2sum/src/b2sum.rs @@ -9,13 +9,14 @@ use clap::Command; use uu_checksum_common::{standalone_checksum_app_with_length, standalone_with_length_main}; -use uucore::checksum::{AlgoKind, calculate_blake_length_str}; +use uucore::checksum::{AlgoKind, BlakeLength, validate_calculate_blake_length}; use uucore::error::UResult; use uucore::translate; #[uucore::main] pub fn uumain(args: impl uucore::Args) -> UResult<()> { - let calculate_blake2b_length = |s: &str| calculate_blake_length_str(AlgoKind::Blake2b, s); + let calculate_blake2b_length = + |s: &str| validate_calculate_blake_length(AlgoKind::Blake2b, BlakeLength::String(s)); standalone_with_length_main(AlgoKind::Blake2b, uu_app(), args, calculate_blake2b_length) } diff --git a/src/uu/checksum_common/src/lib.rs b/src/uu/checksum_common/src/lib.rs index f0d307870e7..56ded13f19e 100644 --- a/src/uu/checksum_common/src/lib.rs +++ b/src/uu/checksum_common/src/lib.rs @@ -63,7 +63,7 @@ pub fn standalone_with_length_main( algo: AlgoKind, cmd: Command, args: impl uucore::Args, - validate_len: fn(&str) -> UResult>, + validate_len: fn(&str) -> UResult, ) -> UResult<()> { let matches = uucore::clap_localization::handle_clap_result(cmd, args)?; let algo = Some(algo); @@ -72,8 +72,7 @@ pub fn standalone_with_length_main( .get_one::(options::LENGTH) .map(String::as_str) .map(validate_len) - .transpose()? - .flatten(); + .transpose()?; //todo: deduplicate matches.get_flag let text = !matches.get_flag(options::BINARY); diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index 19898def4f9..b0e13b3c405 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -12,7 +12,8 @@ use uu_checksum_common::{ChecksumCommand, checksum_main, default_checksum_app, o use uucore::checksum::compute::OutputFormat; use uucore::checksum::{ - AlgoKind, ChecksumError, calculate_blake_length_str, sanitize_sha2_sha3_length_str, + AlgoKind, BlakeLength, ChecksumError, sanitize_sha2_sha3_length_str, + validate_calculate_blake_length, }; use uucore::error::UResult; use uucore::hardware::{HasHardwareFeatures as _, SimdPolicy}; @@ -69,7 +70,7 @@ fn maybe_sanitize_length( // For BLAKE, if a length is provided, validate it. (Some(algo @ (AlgoKind::Blake2b | AlgoKind::Blake3)), Some(len)) => { - calculate_blake_length_str(algo, len) + validate_calculate_blake_length(algo, BlakeLength::String(len)).map(Some) } // For any other provided algorithm, check if length is 0. diff --git a/src/uucore/src/lib/features/checksum/mod.rs b/src/uucore/src/lib/features/checksum/mod.rs index 9433bbf3682..c0cad40de29 100644 --- a/src/uucore/src/lib/features/checksum/mod.rs +++ b/src/uucore/src/lib/features/checksum/mod.rs @@ -250,8 +250,8 @@ pub enum SizedAlgoKind { Sha2(ShaLength), Sha3(ShaLength), // Note: we store Blake*'s length as BYTES. - Blake2b(Option), - Blake3(Option), + Blake2b(usize), + Blake3(usize), // Shake* length are stored in bits. Shake128(Option), Shake256(Option), @@ -284,7 +284,8 @@ impl SizedAlgoKind { (ak::Sm3, _) => Ok(Self::Sm3), (ak::Sha1, _) => Ok(Self::Sha1), - (ak::Blake3, l) => Ok(Self::Blake3(l)), + (ak::Blake2b, l) => Ok(Self::Blake2b(l.unwrap_or(Blake2b::DEFAULT_BYTE_SIZE))), + (ak::Blake3, l) => Ok(Self::Blake3(l.unwrap_or(Blake3::DEFAULT_BYTE_SIZE))), (ak::Shake128, l) => Ok(Self::Shake128(l)), (ak::Shake256, l) => Ok(Self::Shake256(l)), (ak::Sha2, Some(l)) => Ok(Self::Sha2(ShaLength::try_from(l)?)), @@ -292,14 +293,6 @@ impl SizedAlgoKind { (algo @ (ak::Sha2 | ak::Sha3), None) => { Err(ChecksumError::LengthRequiredForSha(algo.to_lowercase().into()).into()) } - // [`calculate_blake2b_length`] expects a length in bits but we - // have a length in bytes. - (algo @ ak::Blake2b, Some(l)) => Ok(Self::Blake2b(calculate_blake_length_str( - algo, - &(8 * l).to_string(), - )?)), - (ak::Blake2b, None) => Ok(Self::Blake2b(None)), - (ak::Sha224, None) => Ok(Self::Sha2(ShaLength::Len224)), (ak::Sha256, None) => Ok(Self::Sha2(ShaLength::Len256)), (ak::Sha384, None) => Ok(Self::Sha2(ShaLength::Len384)), @@ -314,14 +307,9 @@ impl SizedAlgoKind { Self::Sha1 => "SHA1".into(), Self::Sha2(len) => format!("SHA{}", len.as_usize()), Self::Sha3(len) => format!("SHA3-{}", len.as_usize()), - Self::Blake2b(Some(byte_len)) => format!("BLAKE2b-{}", byte_len * 8), - Self::Blake2b(None) => "BLAKE2b".into(), - Self::Blake3(byte_len) => { - format!( - "BLAKE3-{}", - byte_len.unwrap_or(Blake3::DEFAULT_BYTE_SIZE) * 8 - ) - } + Self::Blake2b(Blake2b::DEFAULT_BYTE_SIZE) => "BLAKE2b".into(), + Self::Blake2b(byte_len) => format!("BLAKE2b-{}", byte_len * 8), + Self::Blake3(byte_len) => format!("BLAKE3-{}", byte_len * 8), Self::Shake128(opt_bit_len) => format!( "SHAKE128-{}", opt_bit_len.unwrap_or(Shake128::DEFAULT_BIT_SIZE) @@ -354,12 +342,8 @@ impl SizedAlgoKind { Self::Sha3(Len256) => Box::new(Sha3_256::default()), Self::Sha3(Len384) => Box::new(Sha3_384::default()), Self::Sha3(Len512) => Box::new(Sha3_512::default()), - Self::Blake2b(len_opt) => { - Box::new(len_opt.map(Blake2b::with_output_bytes).unwrap_or_default()) - } - Self::Blake3(len_opt) => { - Box::new(len_opt.map(Blake3::with_output_bytes).unwrap_or_default()) - } + Self::Blake2b(len) => Box::new(Blake2b::with_output_bytes(*len)), + Self::Blake3(len) => Box::new(Blake3::with_output_bytes(*len)), Self::Shake128(len_opt) => { Box::new(len_opt.map(Shake128::with_output_bits).unwrap_or_default()) } @@ -378,10 +362,10 @@ impl SizedAlgoKind { Self::Md5 => 128, Self::Sm3 => 512, Self::Sha1 => 160, - Self::Blake3(len) => len.unwrap_or(Blake3::DEFAULT_BYTE_SIZE) * 8, Self::Sha2(len) => len.as_usize(), Self::Sha3(len) => len.as_usize(), - Self::Blake2b(len) => len.unwrap_or(Blake2b::DEFAULT_BYTE_SIZE * 8), + Self::Blake2b(len) => len * 8, + Self::Blake3(len) => len * 8, Self::Shake128(len) => len.unwrap_or(Shake128::DEFAULT_BIT_SIZE), Self::Shake256(len) => len.unwrap_or(Shake256::DEFAULT_BIT_SIZE), } @@ -495,36 +479,65 @@ pub fn digest_reader( Ok((digest.result(), output_size)) } -/// Calculates the BYTE length of the digest. -pub fn calculate_blake_length_str(algo: AlgoKind, bit_length: &str) -> UResult> { +pub enum BlakeLength<'s> { + Int(usize), + String(&'s str), +} + +/// Expects a size in BITS, either as a string or int, and returns it as a BYTE +/// length. +/// +/// Note: when the input is a string, validation may print error messages. +/// Note: when the algo is Blake2b, values that are above 512 +/// (Blake2b::DEFAULT_BIT_SIZE) are errors. +pub fn validate_calculate_blake_length( + algo: AlgoKind, + bit_length: BlakeLength<'_>, +) -> UResult { debug_assert!(matches!(algo, AlgoKind::Blake2b | AlgoKind::Blake3)); + // TODO MSRV>=1.89 : Replace format! with format_args! to make string + // evaluation lazy. + #[allow(clippy::useless_format)] + let (disp, parsed, may_print_error) = match &bit_length { + BlakeLength::Int(i) => (format!("{}", *i), Ok(*i), false), + BlakeLength::String(s) => (format!("{}", *s), s.parse::(), true), + }; + + let print_error = || { + if may_print_error { + show_error!("{}", ChecksumError::InvalidLength(disp.clone())); + } + }; + // Blake2b's length is parsed in an u64. - match bit_length.parse::() { - Ok(0) => Ok(None), + match parsed { + Ok(0) => Ok(if algo == AlgoKind::Blake2b { + Blake2b::DEFAULT_BYTE_SIZE + } else { + Blake3::DEFAULT_BYTE_SIZE + }), // Error cases - Ok(n) if n > 512 && algo == AlgoKind::Blake2b => { - show_error!("{}", ChecksumError::InvalidLength(bit_length.into())); + Ok(n) if n > Blake2b::DEFAULT_BIT_SIZE && algo == AlgoKind::Blake2b => { + print_error(); Err(ChecksumError::LengthTooBigForBlake(algo.to_uppercase().into()).into()) } Err(e) if *e.kind() == IntErrorKind::PosOverflow => { - show_error!("{}", ChecksumError::InvalidLength(bit_length.into())); + print_error(); Err(ChecksumError::LengthTooBigForBlake(algo.to_uppercase().into()).into()) } - Err(_) => Err(ChecksumError::InvalidLength(bit_length.into()).into()), + Err(_) => Err(ChecksumError::InvalidLength(disp).into()), Ok(n) if n % 8 != 0 => { - show_error!("{}", ChecksumError::InvalidLength(bit_length.into())); + print_error(); Err(ChecksumError::LengthNotMultipleOf8.into()) } // Valid cases - // When length is 512, it is blake2b's default. So, don't show it - Ok(512) => Ok(None), // Divide by 8, as our blake2b implementation expects bytes instead of bits. - Ok(n) => Ok(Some(n / 8)), + Ok(n) => Ok(n / 8), } } @@ -644,18 +657,22 @@ mod tests { #[test] fn test_calculate_blake2b_length() { assert_eq!( - calculate_blake_length_str(AlgoKind::Blake2b, "0").unwrap(), - None + validate_calculate_blake_length(AlgoKind::Blake2b, BlakeLength::String("0")).unwrap(), + Blake2b::DEFAULT_BYTE_SIZE + ); + assert!( + validate_calculate_blake_length(AlgoKind::Blake2b, BlakeLength::String("10")).is_err() + ); + assert!( + validate_calculate_blake_length(AlgoKind::Blake2b, BlakeLength::String("520")).is_err() ); - assert!(calculate_blake_length_str(AlgoKind::Blake2b, "10").is_err()); - assert!(calculate_blake_length_str(AlgoKind::Blake2b, "520").is_err()); assert_eq!( - calculate_blake_length_str(AlgoKind::Blake2b, "512").unwrap(), - None + validate_calculate_blake_length(AlgoKind::Blake2b, BlakeLength::String("512")).unwrap(), + Blake2b::DEFAULT_BYTE_SIZE ); assert_eq!( - calculate_blake_length_str(AlgoKind::Blake2b, "256").unwrap(), - Some(32) + validate_calculate_blake_length(AlgoKind::Blake2b, BlakeLength::String("256")).unwrap(), + 32 ); } } diff --git a/src/uucore/src/lib/features/checksum/validate.rs b/src/uucore/src/lib/features/checksum/validate.rs index fcfba47e8c0..dc366930256 100644 --- a/src/uucore/src/lib/features/checksum/validate.rs +++ b/src/uucore/src/lib/features/checksum/validate.rs @@ -15,11 +15,12 @@ use std::io::{self, BufReader, Read, Write, stderr, stdin}; use os_display::Quotable; use crate::checksum::{ - AlgoKind, ChecksumError, ReadingMode, SizedAlgoKind, digest_reader, unescape_filename, + AlgoKind, BlakeLength, ChecksumError, ReadingMode, SizedAlgoKind, digest_reader, + unescape_filename, validate_calculate_blake_length, }; use crate::error::{FromIo, UError, UIoError, UResult, USimpleError}; use crate::quoting_style::{QuotingStyle, locale_aware_escape_name}; -use crate::sum::{self, DigestOutput}; +use crate::sum::{self, Blake2b, Blake3, DigestOutput}; use crate::{ os_str_as_bytes, os_str_from_bytes, read_os_string_lines, show, show_warning_caps, translate, }; @@ -637,7 +638,12 @@ fn identify_algo_name_and_length( let bytes = if let Some(bitlen) = line_info.algo_bit_len { match line_algo { - ak::Blake2b | ak::Blake3 if bitlen % 8 == 0 => Some(bitlen / 8), + algo @ (ak::Blake2b | ak::Blake3) => { + match validate_calculate_blake_length(algo, BlakeLength::Int(bitlen)) { + Ok(len) => Some(len), + Err(_) => return Err(LineCheckError::ImproperlyFormatted), + } + } ak::Sha2 | ak::Sha3 if [224, 256, 384, 512].contains(&bitlen) => Some(bitlen), ak::Shake128 | ak::Shake256 => Some(bitlen), // Either @@ -653,10 +659,10 @@ fn identify_algo_name_and_length( } } else if line_algo == ak::Blake2b { // Default length with BLAKE2b, - Some(64) + Some(Blake2b::DEFAULT_BYTE_SIZE) } else if line_algo == ak::Blake3 { // Default length with BLAKE3, - Some(32) + Some(Blake3::DEFAULT_BYTE_SIZE) } else { None }; diff --git a/src/uucore/src/lib/features/sum.rs b/src/uucore/src/lib/features/sum.rs index af122ced1a3..5c998cd0764 100644 --- a/src/uucore/src/lib/features/sum.rs +++ b/src/uucore/src/lib/features/sum.rs @@ -83,9 +83,15 @@ pub struct Blake2b { impl Blake2b { pub const DEFAULT_BYTE_SIZE: usize = 64; + pub const DEFAULT_BIT_SIZE: usize = Self::DEFAULT_BYTE_SIZE * 8; /// Return a new Blake2b instance with a custom output bytes length pub fn with_output_bytes(output_bytes: usize) -> Self { + debug_assert!( + output_bytes <= Self::DEFAULT_BYTE_SIZE, + "GNU doesn't accept BLAKE2b bigger than 64 bytes long" + ); + let mut params = blake2b_simd::Params::new(); params.hash_length(output_bytes); diff --git a/tests/by-util/test_cksum.rs b/tests/by-util/test_cksum.rs index 1ab5bb45285..16f18706f68 100644 --- a/tests/by-util/test_cksum.rs +++ b/tests/by-util/test_cksum.rs @@ -10,13 +10,37 @@ use rstest_reuse::{apply, template}; use uutests::at_and_ucmd; use uutests::new_ucmd; use uutests::util::TestScenario; +use uutests::util::UCommand; use uutests::util::log_info; use uutests::util_name; -const ALGOS: [&str; 11] = [ - "sysv", "bsd", "crc", "md5", "sha1", "sha224", "sha256", "sha384", "sha512", "blake2b", "sm3", -]; -const SHA_LENGTHS: [u32; 4] = [224, 256, 384, 512]; +#[template] +#[rstest] +#[case::sysv("sysv")] +#[case::bsd("bsd")] +#[case::crc("crc")] +#[case::md5("md5")] +#[case::sha1("sha1")] +#[case::sha224("sha224")] +#[case::sha256("sha256")] +#[case::sha384("sha384")] +#[case::sha512("sha512")] +#[case::blake2b("blake2b")] +#[case::blake3("blake3")] +#[case::sm3("sm3")] +fn test_all_algos(#[case] algo: &str) {} + +#[template] +#[rstest] +fn test_sha(#[values("sha2", "sha3")] algo: &str, #[values(224, 256, 384, 512)] len: u32) {} + +fn sha_fixture_name(algo: &str, len: u32, prefix: &str, suffix: &str) -> String { + // assume algo is always "sha2" or "sha3" + match algo { + "sha2" => format!("{prefix}sha{len}{suffix}"), + _ => format!("{prefix}sha3_{len}{suffix}"), + } +} #[test] fn test_invalid_arg() { @@ -170,43 +194,37 @@ fn test_tag_after_untagged() { .stdout_is_fixture("md5_single_file.expected"); } -#[test] -fn test_algorithm_single_file() { - for algo in ALGOS { - for option in ["-a", "--algorithm"] { - new_ucmd!() - .arg(format!("{option}={algo}")) - .arg("lorem_ipsum.txt") - .succeeds() - .stdout_is_fixture(format!("{algo}_single_file.expected")); - } +#[apply(test_all_algos)] +fn test_algorithm_single_file(#[case] algo: &str) { + for option in ["-a", "--algorithm"] { + new_ucmd!() + .arg(format!("{option}={algo}")) + .arg("lorem_ipsum.txt") + .succeeds() + .stdout_is_fixture(format!("{algo}_single_file.expected")); } } -#[test] -fn test_algorithm_multiple_files() { - for algo in ALGOS { - for option in ["-a", "--algorithm"] { - new_ucmd!() - .arg(format!("{option}={algo}")) - .arg("lorem_ipsum.txt") - .arg("alice_in_wonderland.txt") - .succeeds() - .stdout_is_fixture(format!("{algo}_multiple_files.expected")); - } +#[apply(test_all_algos)] +fn test_algorithm_multiple_files(#[case] algo: &str) { + for option in ["-a", "--algorithm"] { + new_ucmd!() + .arg(format!("{option}={algo}")) + .arg("lorem_ipsum.txt") + .arg("alice_in_wonderland.txt") + .succeeds() + .stdout_is_fixture(format!("{algo}_multiple_files.expected")); } } -#[test] -fn test_algorithm_stdin() { - for algo in ALGOS { - for option in ["-a", "--algorithm"] { - new_ucmd!() - .arg(format!("{option}={algo}")) - .pipe_in_fixture("lorem_ipsum.txt") - .succeeds() - .stdout_is_fixture(format!("{algo}_stdin.expected")); - } +#[apply(test_all_algos)] +fn test_algorithm_stdin(#[case] algo: &str) { + for option in ["-a", "--algorithm"] { + new_ucmd!() + .arg(format!("{option}={algo}")) + .pipe_in_fixture("lorem_ipsum.txt") + .succeeds() + .stdout_is_fixture(format!("{algo}_stdin.expected")); } } @@ -238,16 +256,14 @@ fn test_untagged_stdin() { .stdout_is_fixture("untagged/crc_stdin.expected"); } -#[test] -fn test_untagged_algorithm_single_file() { - for algo in ALGOS { - new_ucmd!() - .arg("--untagged") - .arg(format!("--algorithm={algo}")) - .arg("lorem_ipsum.txt") - .succeeds() - .stdout_is_fixture(format!("untagged/{algo}_single_file.expected")); - } +#[apply(test_all_algos)] +fn test_untagged_algorithm_single_file(#[case] algo: &str) { + new_ucmd!() + .arg("--untagged") + .arg(format!("--algorithm={algo}")) + .arg("lorem_ipsum.txt") + .succeeds() + .stdout_is_fixture(format!("untagged/{algo}_single_file.expected")); } #[test] @@ -272,29 +288,25 @@ fn test_untagged_algorithm_after_tag() { .stdout_is_fixture("untagged/md5_single_file.expected"); } -#[test] -fn test_untagged_algorithm_multiple_files() { - for algo in ALGOS { - new_ucmd!() - .arg("--untagged") - .arg(format!("--algorithm={algo}")) - .arg("lorem_ipsum.txt") - .arg("alice_in_wonderland.txt") - .succeeds() - .stdout_is_fixture(format!("untagged/{algo}_multiple_files.expected")); - } +#[apply(test_all_algos)] +fn test_untagged_algorithm_multiple_files(#[case] algo: &str) { + new_ucmd!() + .arg("--untagged") + .arg(format!("--algorithm={algo}")) + .arg("lorem_ipsum.txt") + .arg("alice_in_wonderland.txt") + .succeeds() + .stdout_is_fixture(format!("untagged/{algo}_multiple_files.expected")); } -#[test] -fn test_untagged_algorithm_stdin() { - for algo in ALGOS { - new_ucmd!() - .arg("--untagged") - .arg(format!("--algorithm={algo}")) - .pipe_in_fixture("lorem_ipsum.txt") - .succeeds() - .stdout_is_fixture(format!("untagged/{algo}_stdin.expected")); - } +#[apply(test_all_algos)] +fn test_untagged_algorithm_stdin(#[case] algo: &str) { + new_ucmd!() + .arg("--untagged") + .arg(format!("--algorithm={algo}")) + .pipe_in_fixture("lorem_ipsum.txt") + .succeeds() + .stdout_is_fixture(format!("untagged/{algo}_stdin.expected")); } #[test] @@ -374,131 +386,126 @@ fn test_sha_missing_length() { } } -#[test] -fn test_sha2_single_file() { - for l in SHA_LENGTHS { - new_ucmd!() - .arg("--algorithm=sha2") - .arg(format!("--length={l}")) - .arg("lorem_ipsum.txt") - .succeeds() - .stdout_is_fixture(format!("sha{l}_single_file.expected")); - } +fn sha_cmd(algo: &str, len: u32) -> UCommand { + let mut ucmd = new_ucmd!(); + ucmd.arg(format!("--algorithm={algo}")) + .arg(format!("--length={len}")); + ucmd } -#[test] -fn test_sha2_multiple_files() { - for l in SHA_LENGTHS { - new_ucmd!() - .arg("--algorithm=sha2") - .arg(format!("--length={l}")) - .arg("lorem_ipsum.txt") - .arg("alice_in_wonderland.txt") - .succeeds() - .stdout_is_fixture(format!("sha{l}_multiple_files.expected")); - } +#[apply(test_sha)] +fn test_sha_single_file(algo: &str, len: u32) { + sha_cmd(algo, len) + .arg("lorem_ipsum.txt") + .succeeds() + .stdout_is_fixture(sha_fixture_name(algo, len, "", "_single_file.expected")); } -#[test] -fn test_sha2_stdin() { - for l in SHA_LENGTHS { - new_ucmd!() - .arg("--algorithm=sha2") - .arg(format!("--length={l}")) - .pipe_in_fixture("lorem_ipsum.txt") - .succeeds() - .stdout_is_fixture(format!("sha{l}_stdin.expected")); - } +#[apply(test_sha)] +fn test_sha_multiple_files(algo: &str, len: u32) { + sha_cmd(algo, len) + .arg("lorem_ipsum.txt") + .arg("alice_in_wonderland.txt") + .succeeds() + .stdout_is_fixture(sha_fixture_name(algo, len, "", "_multiple_files.expected")); } -#[test] -fn test_untagged_sha2_single_file() { - for l in SHA_LENGTHS { - new_ucmd!() - .arg("--untagged") - .arg("--algorithm=sha2") - .arg(format!("--length={l}")) - .arg("lorem_ipsum.txt") - .succeeds() - .stdout_is_fixture(format!("untagged/sha{l}_single_file.expected")); - } +#[apply(test_sha)] +fn test_sha_stdin(algo: &str, len: u32) { + sha_cmd(algo, len) + .pipe_in_fixture("lorem_ipsum.txt") + .succeeds() + .stdout_is_fixture(sha_fixture_name(algo, len, "", "_stdin.expected")); } -#[test] -fn test_untagged_sha2_multiple_files() { - for l in SHA_LENGTHS { - new_ucmd!() - .arg("--untagged") - .arg("--algorithm=sha2") - .arg(format!("--length={l}")) - .arg("lorem_ipsum.txt") - .arg("alice_in_wonderland.txt") - .succeeds() - .stdout_is_fixture(format!("untagged/sha{l}_multiple_files.expected")); - } +#[apply(test_sha)] +fn test_untagged_sha_single_file(algo: &str, len: u32) { + sha_cmd(algo, len) + .arg("--untagged") + .arg("lorem_ipsum.txt") + .succeeds() + .stdout_is_fixture(sha_fixture_name( + algo, + len, + "untagged/", + "_single_file.expected", + )); } -#[test] -fn test_untagged_sha2_stdin() { - for l in SHA_LENGTHS { - new_ucmd!() - .arg("--untagged") - .arg("--algorithm=sha2") - .arg(format!("--length={l}")) - .pipe_in_fixture("lorem_ipsum.txt") - .succeeds() - .stdout_is_fixture(format!("untagged/sha{l}_stdin.expected")); - } +#[apply(test_sha)] +fn test_untagged_sha_multiple_files(algo: &str, len: u32) { + sha_cmd(algo, len) + .arg("--untagged") + .arg("lorem_ipsum.txt") + .arg("alice_in_wonderland.txt") + .succeeds() + .stdout_is_fixture(sha_fixture_name( + algo, + len, + "untagged/", + "_multiple_files.expected", + )); } -#[test] -fn test_check_tagged_sha2_single_file() { - for l in SHA_LENGTHS { - new_ucmd!() - .arg("--check") - .arg(format!("sha{l}_single_file.expected")) - .succeeds() - .stdout_is("lorem_ipsum.txt: OK\n"); - } +#[apply(test_sha)] +fn test_untagged_sha_stdin(algo: &str, len: u32) { + sha_cmd(algo, len) + .arg("--untagged") + .pipe_in_fixture("lorem_ipsum.txt") + .succeeds() + .stdout_is_fixture(sha_fixture_name(algo, len, "untagged/", "_stdin.expected")); } -#[test] -fn test_check_tagged_sha2_multiple_files() { - for l in SHA_LENGTHS { - new_ucmd!() - .arg("--check") - .arg(format!("sha{l}_multiple_files.expected")) - .succeeds() - .stdout_contains("lorem_ipsum.txt: OK\n") - .stdout_contains("alice_in_wonderland.txt: OK\n"); - } +#[apply(test_sha)] +fn test_check_tagged_sha_single_file(algo: &str, len: u32) { + new_ucmd!() + .arg("--check") + .arg(sha_fixture_name(algo, len, "", "_single_file.expected")) + .succeeds() + .stdout_is("lorem_ipsum.txt: OK\n"); } -// When checking sha2 in untagged mode, the length is automatically deduced -// from the length of the digest. -#[test] -fn test_check_untagged_sha2_single_file() { - for l in SHA_LENGTHS { - new_ucmd!() - .arg("--check") - .arg("--algorithm=sha2") - .arg(format!("untagged/sha{l}_single_file.expected")) - .succeeds() - .stdout_is("lorem_ipsum.txt: OK\n"); - } +#[apply(test_sha)] +fn test_check_tagged_sha_multiple_files(algo: &str, len: u32) { + new_ucmd!() + .arg("--check") + .arg(sha_fixture_name(algo, len, "", "_multiple_files.expected")) + .succeeds() + .stdout_contains("lorem_ipsum.txt: OK\n") + .stdout_contains("alice_in_wonderland.txt: OK\n"); } -#[test] -fn test_check_untagged_sha2_multiple_files() { - for l in SHA_LENGTHS { - new_ucmd!() - .arg("--check") - .arg("--algorithm=sha2") - .arg(format!("untagged/sha{l}_multiple_files.expected")) - .succeeds() - .stdout_contains("lorem_ipsum.txt: OK\n") - .stdout_contains("alice_in_wonderland.txt: OK\n"); - } +// When checking sha2/sha3 in untagged mode, the length is automatically +// deduced from the length of the digest. +#[apply(test_sha)] +fn test_check_untagged_sha_single_file(algo: &str, len: u32) { + new_ucmd!() + .arg("--check") + .arg(format!("--algorithm={algo}")) + .arg(sha_fixture_name( + algo, + len, + "untagged/", + "_single_file.expected", + )) + .succeeds() + .stdout_is("lorem_ipsum.txt: OK\n"); +} + +#[apply(test_sha)] +fn test_check_untagged_sha_multiple_files(algo: &str, len: u32) { + new_ucmd!() + .arg("--check") + .arg(format!("--algorithm={algo}")) + .arg(sha_fixture_name( + algo, + len, + "untagged/", + "_multiple_files.expected", + )) + .succeeds() + .stdout_contains("lorem_ipsum.txt: OK\n") + .stdout_contains("alice_in_wonderland.txt: OK\n"); } #[test] @@ -555,133 +562,6 @@ fn test_check_sha2_tagged_variant() { } } -#[test] -fn test_sha3_single_file() { - for l in SHA_LENGTHS { - new_ucmd!() - .arg("--algorithm=sha3") - .arg(format!("--length={l}")) - .arg("lorem_ipsum.txt") - .succeeds() - .stdout_is_fixture(format!("sha3_{l}_single_file.expected")); - } -} - -#[test] -fn test_sha3_multiple_files() { - for l in SHA_LENGTHS { - new_ucmd!() - .arg("--algorithm=sha3") - .arg(format!("--length={l}")) - .arg("lorem_ipsum.txt") - .arg("alice_in_wonderland.txt") - .succeeds() - .stdout_is_fixture(format!("sha3_{l}_multiple_files.expected")); - } -} - -#[test] -fn test_sha3_stdin() { - for l in SHA_LENGTHS { - new_ucmd!() - .arg("--algorithm=sha3") - .arg(format!("--length={l}")) - .pipe_in_fixture("lorem_ipsum.txt") - .succeeds() - .stdout_is_fixture(format!("sha3_{l}_stdin.expected")); - } -} - -#[test] -fn test_untagged_sha3_single_file() { - for l in SHA_LENGTHS { - new_ucmd!() - .arg("--untagged") - .arg("--algorithm=sha3") - .arg(format!("--length={l}")) - .arg("lorem_ipsum.txt") - .succeeds() - .stdout_is_fixture(format!("untagged/sha3_{l}_single_file.expected")); - } -} - -#[test] -fn test_untagged_sha3_multiple_files() { - for l in SHA_LENGTHS { - new_ucmd!() - .arg("--untagged") - .arg("--algorithm=sha3") - .arg(format!("--length={l}")) - .arg("lorem_ipsum.txt") - .arg("alice_in_wonderland.txt") - .succeeds() - .stdout_is_fixture(format!("untagged/sha3_{l}_multiple_files.expected")); - } -} - -#[test] -fn test_untagged_sha3_stdin() { - for l in SHA_LENGTHS { - new_ucmd!() - .arg("--untagged") - .arg("--algorithm=sha3") - .arg(format!("--length={l}")) - .pipe_in_fixture("lorem_ipsum.txt") - .succeeds() - .stdout_is_fixture(format!("untagged/sha3_{l}_stdin.expected")); - } -} - -#[test] -fn test_check_tagged_sha3_single_file() { - for l in SHA_LENGTHS { - new_ucmd!() - .arg("--check") - .arg(format!("sha3_{l}_single_file.expected")) - .succeeds() - .stdout_is("lorem_ipsum.txt: OK\n"); - } -} - -#[test] -fn test_check_tagged_sha3_multiple_files() { - for l in SHA_LENGTHS { - new_ucmd!() - .arg("--check") - .arg(format!("sha3_{l}_multiple_files.expected")) - .succeeds() - .stdout_contains("lorem_ipsum.txt: OK\n") - .stdout_contains("alice_in_wonderland.txt: OK\n"); - } -} - -// When checking sha3 in untagged mode, the length is automatically deduced -// from the length of the digest. -#[test] -fn test_check_untagged_sha3_single_file() { - for l in SHA_LENGTHS { - new_ucmd!() - .arg("--check") - .arg("--algorithm=sha3") - .arg(format!("untagged/sha3_{l}_single_file.expected")) - .succeeds() - .stdout_is("lorem_ipsum.txt: OK\n"); - } -} - -#[test] -fn test_check_untagged_sha3_multiple_files() { - for l in SHA_LENGTHS { - new_ucmd!() - .arg("--check") - .arg("--algorithm=sha3") - .arg(format!("untagged/sha3_{l}_multiple_files.expected")) - .succeeds() - .stdout_contains("lorem_ipsum.txt: OK\n") - .stdout_contains("alice_in_wonderland.txt: OK\n"); - } -} - #[test] fn test_check_algo() { for algo in ["bsd", "sysv", "crc", "crc32b"] { @@ -846,18 +726,17 @@ fn test_blake2b_length_invalid() { } } -#[test] -fn test_raw_single_file() { - for algo in ALGOS { - new_ucmd!() - .arg("--raw") - .arg("lorem_ipsum.txt") - .arg(format!("--algorithm={algo}")) - .succeeds() - .no_stderr() - .stdout_is_fixture_bytes(format!("raw/{algo}_single_file.expected")); - } +#[apply(test_all_algos)] +fn test_raw_single_file(#[case] algo: &str) { + new_ucmd!() + .arg("--raw") + .arg("lorem_ipsum.txt") + .arg(format!("--algorithm={algo}")) + .succeeds() + .no_stderr() + .stdout_is_fixture_bytes(format!("raw/{algo}_single_file.expected")); } + #[test] fn test_raw_multiple_files() { new_ucmd!() @@ -882,18 +761,17 @@ fn test_base64_raw_conflicts() { .stderr_contains("--raw"); } -#[test] -fn test_base64_single_file() { - for algo in ALGOS { - new_ucmd!() - .arg("--base64") - .arg("lorem_ipsum.txt") - .arg(format!("--algorithm={algo}")) - .succeeds() - .no_stderr() - .stdout_is_fixture_bytes(format!("base64/{algo}_single_file.expected")); - } +#[apply(test_all_algos)] +fn test_base64_single_file(#[case] algo: &str) { + new_ucmd!() + .arg("--base64") + .arg("lorem_ipsum.txt") + .arg(format!("--algorithm={algo}")) + .succeeds() + .no_stderr() + .stdout_is_fixture_bytes(format!("base64/{algo}_single_file.expected")); } + #[test] fn test_base64_multiple_files() { new_ucmd!() @@ -919,8 +797,8 @@ fn test_fail_on_folder() { .stderr_contains(format!("cksum: {folder_name}: Is a directory")); } -#[test] -fn test_all_algorithms_fail_on_folder() { +#[apply(test_all_algos)] +fn test_all_algorithms_fail_on_folder(#[case] algo: &str) { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -928,15 +806,13 @@ fn test_all_algorithms_fail_on_folder() { let folder_name = "a_folder"; at.mkdir(folder_name); - for algo in ALGOS { - scene - .ucmd() - .arg(format!("--algorithm={algo}")) - .arg(folder_name) - .fails() - .no_stdout() - .stderr_contains(format!("cksum: {folder_name}: Is a directory")); - } + scene + .ucmd() + .arg(format!("--algorithm={algo}")) + .arg(folder_name) + .fails() + .no_stdout() + .stderr_contains(format!("cksum: {folder_name}: Is a directory")); } #[cfg(unix)] @@ -971,7 +847,7 @@ fn test_dev_null() { #[cfg(unix)] #[test] -fn test_blake2b_512() { +fn test_blake2b_default_length() { let scene = TestScenario::new(util_name!()); let at = &scene.fixtures; @@ -2058,6 +1934,28 @@ fn test_check_blake_length_guess() { .arg(at.subdir.join("foo.sums")) .fails() .stderr_contains("foo.sums: no properly formatted checksum lines found"); + + // This is incorrect because the length hint provided doesn't match the + // checksum length. + let length_mismatch = "BLAKE2b-8 (foo.dat) = 171cdfdf84ed"; + at.write("foo.sums", length_mismatch); + scene + .ucmd() + .arg("--check") + .arg(at.subdir.join("foo.sums")) + .fails() + .stderr_contains("foo.sums: no properly formatted checksum lines found"); + + // In this case, validation should fail because even though the checksum + // length matches the hint, it is above BLAKE2b's max (512). + let too_long = "BLAKE2b-520 (/dev/null) = 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; + at.write("foo.sums", too_long); + scene + .ucmd() + .arg("--check") + .arg(at.subdir.join("foo.sums")) + .fails() + .stderr_contains("foo.sums: no properly formatted checksum lines found"); } #[test] diff --git a/tests/fixtures/cksum/base64/blake3_single_file.expected b/tests/fixtures/cksum/base64/blake3_single_file.expected new file mode 100644 index 00000000000..cce3661ff3d --- /dev/null +++ b/tests/fixtures/cksum/base64/blake3_single_file.expected @@ -0,0 +1 @@ +BLAKE3-256 (lorem_ipsum.txt) = /c9QNtxcUOUQabtbQFNF7VC4iiTaZsb4shtGR3z2mdk= diff --git a/tests/fixtures/cksum/blake3_multiple_files.expected b/tests/fixtures/cksum/blake3_multiple_files.expected new file mode 100644 index 00000000000..f9541ed6b2c --- /dev/null +++ b/tests/fixtures/cksum/blake3_multiple_files.expected @@ -0,0 +1,2 @@ +BLAKE3-256 (lorem_ipsum.txt) = fdcf5036dc5c50e51069bb5b405345ed50b88a24da66c6f8b21b46477cf699d9 +BLAKE3-256 (alice_in_wonderland.txt) = 93c026644dc6b34a13a2b6705485d06f32d712c76ebe5353e935155edeeb49d5 diff --git a/tests/fixtures/cksum/blake3_single_file.expected b/tests/fixtures/cksum/blake3_single_file.expected new file mode 100644 index 00000000000..cfc017b947c --- /dev/null +++ b/tests/fixtures/cksum/blake3_single_file.expected @@ -0,0 +1 @@ +BLAKE3-256 (lorem_ipsum.txt) = fdcf5036dc5c50e51069bb5b405345ed50b88a24da66c6f8b21b46477cf699d9 diff --git a/tests/fixtures/cksum/blake3_stdin.expected b/tests/fixtures/cksum/blake3_stdin.expected new file mode 100644 index 00000000000..fbb69fbc1e9 --- /dev/null +++ b/tests/fixtures/cksum/blake3_stdin.expected @@ -0,0 +1 @@ +BLAKE3-256 (-) = fdcf5036dc5c50e51069bb5b405345ed50b88a24da66c6f8b21b46477cf699d9 diff --git a/tests/fixtures/cksum/raw/blake3_single_file.expected b/tests/fixtures/cksum/raw/blake3_single_file.expected new file mode 100644 index 00000000000..c9e56f00552 --- /dev/null +++ b/tests/fixtures/cksum/raw/blake3_single_file.expected @@ -0,0 +1 @@ +ýÏP6Ü\Påi»[@SEíP¸Š$ÚfÆø²FG|ö™Ù \ No newline at end of file diff --git a/tests/fixtures/cksum/untagged/blake3_multiple_files.expected b/tests/fixtures/cksum/untagged/blake3_multiple_files.expected new file mode 100644 index 00000000000..29379596a67 --- /dev/null +++ b/tests/fixtures/cksum/untagged/blake3_multiple_files.expected @@ -0,0 +1,2 @@ +fdcf5036dc5c50e51069bb5b405345ed50b88a24da66c6f8b21b46477cf699d9 lorem_ipsum.txt +93c026644dc6b34a13a2b6705485d06f32d712c76ebe5353e935155edeeb49d5 alice_in_wonderland.txt diff --git a/tests/fixtures/cksum/untagged/blake3_single_file.expected b/tests/fixtures/cksum/untagged/blake3_single_file.expected new file mode 100644 index 00000000000..1fafca356f4 --- /dev/null +++ b/tests/fixtures/cksum/untagged/blake3_single_file.expected @@ -0,0 +1 @@ +fdcf5036dc5c50e51069bb5b405345ed50b88a24da66c6f8b21b46477cf699d9 lorem_ipsum.txt diff --git a/tests/fixtures/cksum/untagged/blake3_stdin.expected b/tests/fixtures/cksum/untagged/blake3_stdin.expected new file mode 100644 index 00000000000..4da4171eec6 --- /dev/null +++ b/tests/fixtures/cksum/untagged/blake3_stdin.expected @@ -0,0 +1 @@ +fdcf5036dc5c50e51069bb5b405345ed50b88a24da66c6f8b21b46477cf699d9 -