From 53700fccdd3ab2e3f9f72148c8cca512a5478c67 Mon Sep 17 00:00:00 2001 From: Dorian Peron Date: Tue, 6 Jan 2026 20:28:05 +0100 Subject: [PATCH 01/10] checksum: Rework the OutputFormat decision --- src/uu/cksum/src/cksum.rs | 18 ++-- src/uu/hashsum/src/hashsum.rs | 21 +--- .../src/lib/features/checksum/compute.rs | 98 +++++++++++++------ src/uucore/src/lib/features/checksum/mod.rs | 2 + 4 files changed, 81 insertions(+), 58 deletions(-) diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index 7d3228407ef..2032d0662bb 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -9,7 +9,7 @@ use clap::builder::ValueParser; use clap::{Arg, ArgAction, Command}; use std::ffi::{OsStr, OsString}; use uucore::checksum::compute::{ - ChecksumComputeOptions, figure_out_output_format, perform_checksum_computation, + ChecksumComputeOptions, OutputFormat, perform_checksum_computation, }; use uucore::checksum::validate::{ ChecksumValidateOptions, ChecksumVerbose, perform_checksum_validation, @@ -208,18 +208,20 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let (tag, binary) = handle_tag_text_binary_flags(std::env::args_os())?; + let output_format = OutputFormat::from_cksum( + algo_kind, + tag, + binary, + matches.get_flag(options::RAW), + matches.get_flag(options::BASE64), + ); + let algo = SizedAlgoKind::from_unsized(algo_kind, length)?; let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO)); let opts = ChecksumComputeOptions { algo_kind: algo, - output_format: figure_out_output_format( - algo, - tag, - binary, - matches.get_flag(options::RAW), - matches.get_flag(options::BASE64), - ), + output_format, line_ending, }; diff --git a/src/uu/hashsum/src/hashsum.rs b/src/uu/hashsum/src/hashsum.rs index 1bad3635558..6c401041ab4 100644 --- a/src/uu/hashsum/src/hashsum.rs +++ b/src/uu/hashsum/src/hashsum.rs @@ -13,7 +13,7 @@ use clap::builder::ValueParser; use clap::{Arg, ArgAction, ArgMatches, Command}; use uucore::checksum::compute::{ - ChecksumComputeOptions, figure_out_output_format, perform_checksum_computation, + ChecksumComputeOptions, OutputFormat, perform_checksum_computation, }; use uucore::checksum::validate::{ ChecksumValidateOptions, ChecksumVerbose, perform_checksum_validation, @@ -121,9 +121,6 @@ pub fn uumain(mut args: impl uucore::Args) -> UResult<()> { let args = iter::once(program.clone()).chain(args); - // Default binary in Windows, text mode otherwise - let binary_flag_default = cfg!(windows); - let (command, is_hashsum_bin) = uu_app(&binary_name); // FIXME: this should use try_get_matches_from() and crash!(), but at the moment that just @@ -148,13 +145,6 @@ pub fn uumain(mut args: impl uucore::Args) -> UResult<()> { (AlgoKind::from_bin_name(&binary_name)?, length) }; - let binary = if matches.get_flag("binary") { - true - } else if matches.get_flag("text") { - false - } else { - binary_flag_default - }; let check = matches.get_flag("check"); let check_flag = |flag| match (check, matches.get_flag(flag)) { @@ -204,16 +194,11 @@ pub fn uumain(mut args: impl uucore::Args) -> UResult<()> { let algo = SizedAlgoKind::from_unsized(algo_kind, length)?; let line_ending = LineEnding::from_zero_flag(matches.get_flag("zero")); + let output_format = OutputFormat::from_standalone(std::env::args_os())?; let opts = ChecksumComputeOptions { algo_kind: algo, - output_format: figure_out_output_format( - algo, - matches.get_flag(options::TAG), - binary, - /* raw */ false, - /* base64: */ false, - ), + output_format, line_ending, }; diff --git a/src/uucore/src/lib/features/checksum/compute.rs b/src/uucore/src/lib/features/checksum/compute.rs index c08765af40e..c5b0cf6e4b6 100644 --- a/src/uucore/src/lib/features/checksum/compute.rs +++ b/src/uucore/src/lib/features/checksum/compute.rs @@ -5,12 +5,12 @@ // spell-checker:ignore bitlen -use std::ffi::OsStr; +use std::ffi::{OsStr, OsString}; use std::fs::File; use std::io::{self, BufReader, Read, Write}; use std::path::Path; -use crate::checksum::{ChecksumError, SizedAlgoKind, digest_reader, escape_filename}; +use crate::checksum::{AlgoKind, ChecksumError, SizedAlgoKind, digest_reader, escape_filename}; use crate::error::{FromIo, UResult, USimpleError}; use crate::line_ending::LineEnding; use crate::sum::DigestOutput; @@ -103,42 +103,76 @@ impl OutputFormat { fn is_raw(&self) -> bool { *self == Self::Raw } -} -/// Use already-processed arguments to decide the output format. -pub fn figure_out_output_format( - algo: SizedAlgoKind, - tag: bool, - binary: bool, - raw: bool, - base64: bool, -) -> OutputFormat { - // Raw output format takes precedence over anything else. - if raw { - return OutputFormat::Raw; - } + /// Find the correct output format for cksum. + pub fn from_cksum(algo: AlgoKind, tag: bool, binary: bool, raw: bool, base64: bool) -> Self { + // Raw output format takes precedence over anything else. + if raw { + return Self::Raw; + } + + // Then, if the algo is legacy, takes precedence over the rest + if algo.is_legacy() { + return Self::Legacy; + } - // Then, if the algo is legacy, takes precedence over the rest - if algo.is_legacy() { - return OutputFormat::Legacy; + let digest_format = if base64 { + DigestFormat::Base64 + } else { + DigestFormat::Hexadecimal + }; + + // After that, decide between tagged and untagged output + if tag { + Self::Tagged(digest_format) + } else { + let reading_mode = if binary { + ReadingMode::Binary + } else { + ReadingMode::Text + }; + Self::Untagged(digest_format, reading_mode) + } } - let digest_format = if base64 { - DigestFormat::Base64 - } else { - DigestFormat::Hexadecimal - }; + /// Find the correct output format for a standalone checksum util (b2sum, + /// md5sum, etc) + /// + /// Since standalone utils can't use the Raw or Legacy output format, it is + /// decided only using the --tag, --binary and --text arguments. + pub fn from_standalone(args: impl Iterator) -> UResult { + let mut text = true; + let mut tag = false; + + for arg in args { + if arg == "--" { + break; + } else if arg == "--tag" { + tag = true; + text = false; + } else if arg == "--binary" || arg == "-b" { + text = false; + } else if arg == "--text" || arg == "-t" { + // Finding a `--text` after `--tag` is an error. + if tag { + return Err(ChecksumError::TextAfterTag.into()); + } + text = true; + } + } - // After that, decide between tagged and untagged output - if tag { - OutputFormat::Tagged(digest_format) - } else { - let reading_mode = if binary { - ReadingMode::Binary + if tag { + Ok(Self::Tagged(DigestFormat::Hexadecimal)) } else { - ReadingMode::Text - }; - OutputFormat::Untagged(digest_format, reading_mode) + Ok(Self::Untagged( + DigestFormat::Hexadecimal, + if text { + ReadingMode::Text + } else { + ReadingMode::Binary + }, + )) + } } } diff --git a/src/uucore/src/lib/features/checksum/mod.rs b/src/uucore/src/lib/features/checksum/mod.rs index 2f3d28b4121..7ae4c775be6 100644 --- a/src/uucore/src/lib/features/checksum/mod.rs +++ b/src/uucore/src/lib/features/checksum/mod.rs @@ -397,6 +397,8 @@ pub enum ChecksumError { BinaryTextConflict, #[error("--text mode is only supported with --untagged")] TextWithoutUntagged, + #[error("--tag does not support --text mode")] + TextAfterTag, #[error("--check is not supported with --algorithm={{bsd,sysv,crc,crc32b}}")] AlgorithmNotSupportedWithCheck, #[error("You cannot combine multiple hash algorithms!")] From 320c80b196d2516ed1ed67c3d66e5690ff2e2941 Mon Sep 17 00:00:00 2001 From: Dorian Peron Date: Tue, 6 Jan 2026 20:07:40 +0100 Subject: [PATCH 02/10] introduce checksum_common --- Cargo.lock | 11 ++ Cargo.toml | 1 + fuzz/Cargo.lock | 10 ++ src/uu/checksum_common/Cargo.toml | 35 ++++ src/uu/checksum_common/LICENSE | 1 + src/uu/checksum_common/locales/en-US.ftl | 19 ++ src/uu/checksum_common/locales/fr-FR.ftl | 19 ++ src/uu/checksum_common/src/cli.rs | 215 +++++++++++++++++++++++ src/uu/checksum_common/src/lib.rs | 207 ++++++++++++++++++++++ 9 files changed, 518 insertions(+) create mode 100644 src/uu/checksum_common/Cargo.toml create mode 120000 src/uu/checksum_common/LICENSE create mode 100644 src/uu/checksum_common/locales/en-US.ftl create mode 100644 src/uu/checksum_common/locales/fr-FR.ftl create mode 100644 src/uu/checksum_common/src/cli.rs create mode 100644 src/uu/checksum_common/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index ef717e28156..e763db08a57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3239,6 +3239,17 @@ dependencies = [ "uucore", ] +[[package]] +name = "uu_checksum_common" +version = "0.6.0" +dependencies = [ + "clap", + "codspeed-divan-compat", + "fluent", + "tempfile", + "uucore", +] + [[package]] name = "uu_chgrp" version = "0.6.0" diff --git a/Cargo.toml b/Cargo.toml index 398c6001716..b30b0672ce1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -407,6 +407,7 @@ uucore = { version = "0.6.0", package = "uucore", path = "src/uucore" } uucore_procs = { version = "0.6.0", package = "uucore_procs", path = "src/uucore_procs" } uu_ls = { version = "0.6.0", path = "src/uu/ls" } uu_base32 = { version = "0.6.0", path = "src/uu/base32" } +uu_checksum_common = { version = "0.6.0", path = "src/uu/checksum_common" } uutests = { version = "0.6.0", package = "uutests", path = "tests/uutests" } [dependencies] diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 4094c806bf4..62bd4b15749 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -1692,12 +1692,22 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uu_checksum_common" +version = "0.6.0" +dependencies = [ + "clap", + "fluent", + "uucore", +] + [[package]] name = "uu_cksum" version = "0.6.0" dependencies = [ "clap", "fluent", + "uu_checksum_common", "uucore", ] diff --git a/src/uu/checksum_common/Cargo.toml b/src/uu/checksum_common/Cargo.toml new file mode 100644 index 00000000000..079a46b4649 --- /dev/null +++ b/src/uu/checksum_common/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "uu_checksum_common" +description = "Base for checksum utils" +version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +keywords.workspace = true +categories.workspace = true +edition.workspace = true +readme.workspace = true + +[lints] +workspace = true + +[lib] +path = "src/lib.rs" + +[dependencies] +clap = { workspace = true } +uucore = { workspace = true, features = [ + "checksum", + "encoding", + "sum", + "hardware", +] } +fluent = { workspace = true } + +[dev-dependencies] +divan = { workspace = true } +tempfile = { workspace = true } + +# [[bench]] +# name = "b2sum_bench" +# harness = false diff --git a/src/uu/checksum_common/LICENSE b/src/uu/checksum_common/LICENSE new file mode 120000 index 00000000000..5853aaea53b --- /dev/null +++ b/src/uu/checksum_common/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/checksum_common/locales/en-US.ftl b/src/uu/checksum_common/locales/en-US.ftl new file mode 100644 index 00000000000..0dddfb19c65 --- /dev/null +++ b/src/uu/checksum_common/locales/en-US.ftl @@ -0,0 +1,19 @@ +ck-common-after-help = With no FILE or when FILE is -, read standard input + +# checksum argument help messages +ck-common-help-algorithm = select the digest type to use. See DIGEST below +ck-common-help-untagged = create a reversed style checksum, without digest type +ck-common-help-tag-default = create a BSD style checksum (default) +ck-common-help-tag = create a BSD style checksum +ck-common-help-text = read in text mode (default) +ck-common-help-length = digest length in bits; must not exceed the max size and must be a multiple of 8 for blake2b; must be 224, 256, 384, or 512 for sha2 or sha3 +ck-common-help-check = read checksums from the FILEs and check them +ck-common-help-base64 = emit base64-encoded digests, not hexadecimal +ck-common-help-raw = emit a raw binary digest, not hexadecimal +ck-common-help-zero = end each output line with NUL, not newline, and disable file name escaping +ck-common-help-strict = exit non-zero for improperly formatted checksum lines +ck-common-help-warn = warn about improperly formatted checksum lines +ck-common-help-status = don't output anything, status code shows success +ck-common-help-quiet = don't print OK for each successfully verified file +ck-common-help-ignore-missing = don't fail or report status for missing files +ck-common-help-debug = print CPU hardware capability detection info used by cksum diff --git a/src/uu/checksum_common/locales/fr-FR.ftl b/src/uu/checksum_common/locales/fr-FR.ftl new file mode 100644 index 00000000000..0b22519cad4 --- /dev/null +++ b/src/uu/checksum_common/locales/fr-FR.ftl @@ -0,0 +1,19 @@ +ck-common-after-help = Sans FICHIER ou quand FICHER est -, lit l'entrée standard + +# Messages d'aide d'arguments checksum +ck-common-help-algorithm = sélectionner le type de condensé à utiliser. Voir DIGEST ci-dessous +ck-common-help-untagged = créer une somme de contrôle de style inversé, sans type de condensé +ck-common-help-tag-default = créer une somme de contrôle de style BSD (par défaut) +ck-common-help-tag = créer une somme de contrôle de style BSD +ck-common-help-text = lire en mode texte (par défaut) +ck-common-help-length = longueur du condensé en bits ; ne doit pas dépasser le maximum pour l'algorithme blake2 et doit être un multiple de 8 +ck-common-help-raw = émettre un condensé binaire brut, pas hexadécimal +ck-common-help-strict = sortir avec un code non-zéro pour les lignes de somme de contrôle mal formatées +ck-common-help-check = lire les sommes de hachage des FICHIERs et les vérifier +ck-common-help-base64 = émettre un condensé base64, pas hexadécimal +ck-common-help-warn = avertir des lignes de somme de contrôle mal formatées +ck-common-help-status = ne rien afficher, le code de statut indique le succès +ck-common-help-quiet = ne pas afficher OK pour chaque fichier vérifié avec succès +ck-common-help-ignore-missing = ne pas échouer ou signaler le statut pour les fichiers manquants +ck-common-help-zero = terminer chaque ligne de sortie avec NUL, pas un saut de ligne, et désactiver l'échappement des noms de fichiers +ck-common-help-debug = afficher les informations de débogage sur la détection de la prise en charge matérielle du processeur diff --git a/src/uu/checksum_common/src/cli.rs b/src/uu/checksum_common/src/cli.rs new file mode 100644 index 00000000000..a5e979e3057 --- /dev/null +++ b/src/uu/checksum_common/src/cli.rs @@ -0,0 +1,215 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +use clap::{Arg, ArgAction, Command}; +use uucore::{checksum::SUPPORTED_ALGORITHMS, translate}; + +/// List of all options that can be encountered in checksum utils +pub mod options { + // cksum-specific + pub const ALGORITHM: &str = "algorithm"; + pub const DEBUG: &str = "debug"; + + // positional arg + pub const FILE: &str = "file"; + + pub const UNTAGGED: &str = "untagged"; + pub const TAG: &str = "tag"; + pub const LENGTH: &str = "length"; + pub const RAW: &str = "raw"; + pub const BASE64: &str = "base64"; + pub const CHECK: &str = "check"; + pub const TEXT: &str = "text"; + pub const BINARY: &str = "binary"; + pub const ZERO: &str = "zero"; + + // check-specific + pub const STRICT: &str = "strict"; + pub const STATUS: &str = "status"; + pub const WARN: &str = "warn"; + pub const IGNORE_MISSING: &str = "ignore-missing"; + pub const QUIET: &str = "quiet"; +} + +/// `ChecksumCommand` is a convenience trait to more easily declare checksum +/// CLI interfaces with +pub trait ChecksumCommand { + fn with_algo(self) -> Self; + + fn with_length(self) -> Self; + + fn with_check_and_opts(self) -> Self; + + fn with_binary(self) -> Self; + + fn with_text(self, is_default: bool) -> Self; + + fn with_tag(self, is_default: bool) -> Self; + + fn with_untagged(self) -> Self; + + fn with_raw(self) -> Self; + + fn with_base64(self) -> Self; + + fn with_zero(self) -> Self; + + fn with_debug(self) -> Self; +} + +impl ChecksumCommand for Command { + fn with_algo(self) -> Self { + self.arg( + Arg::new(options::ALGORITHM) + .long(options::ALGORITHM) + .short('a') + .help(translate!("ck-common-help-algorithm")) + .value_name("ALGORITHM") + .value_parser(SUPPORTED_ALGORITHMS), + ) + } + + fn with_length(self) -> Self { + self.arg( + Arg::new(options::LENGTH) + .long(options::LENGTH) + .short('l') + .help(translate!("ck-common-help-length")) + .action(ArgAction::Set), + ) + } + + fn with_check_and_opts(self) -> Self { + self.arg( + Arg::new(options::CHECK) + .short('c') + .long(options::CHECK) + .help(translate!("ck-common-help-check")) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::WARN) + .short('w') + .long("warn") + .help(translate!("ck-common-help-warn")) + .action(ArgAction::SetTrue) + .overrides_with_all([options::STATUS, options::QUIET]), + ) + .arg( + Arg::new(options::STATUS) + .long("status") + .help(translate!("ck-common-help-status")) + .action(ArgAction::SetTrue) + .overrides_with_all([options::WARN, options::QUIET]), + ) + .arg( + Arg::new(options::QUIET) + .long(options::QUIET) + .help(translate!("ck-common-help-quiet")) + .action(ArgAction::SetTrue) + .overrides_with_all([options::STATUS, options::WARN]), + ) + .arg( + Arg::new(options::IGNORE_MISSING) + .long(options::IGNORE_MISSING) + .help(translate!("ck-common-help-ignore-missing")) + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new(options::STRICT) + .long(options::STRICT) + .help(translate!("ck-common-help-strict")) + .action(ArgAction::SetTrue), + ) + } + + fn with_binary(self) -> Self { + self.arg( + Arg::new(options::BINARY) + .long(options::BINARY) + .short('b') + .hide(true) + .action(ArgAction::SetTrue), + ) + } + + fn with_text(self, is_default: bool) -> Self { + let mut arg = Arg::new(options::TEXT) + .long(options::TEXT) + .short('t') + .action(ArgAction::SetTrue); + + arg = if is_default { + arg.help(translate!("ck-common-help-text")) + } else { + arg.hide(true) + }; + + self.arg(arg) + } + + fn with_tag(self, default: bool) -> Self { + let mut arg = Arg::new(options::TAG) + .long(options::TAG) + .action(ArgAction::SetTrue); + + arg = if default { + arg.help(translate!("ck-common-help-tag-default")) + } else { + arg.help(translate!("ck-common-help-tag")) + }; + + self.arg(arg) + } + + fn with_untagged(self) -> Self { + self.arg( + Arg::new(options::UNTAGGED) + .long(options::UNTAGGED) + .help(translate!("ck-common-help-untagged")) + .action(ArgAction::SetTrue), + ) + } + + fn with_raw(self) -> Self { + self.arg( + Arg::new(options::RAW) + .long(options::RAW) + .help(translate!("ck-common-help-raw")) + .action(ArgAction::SetTrue), + ) + } + + fn with_base64(self) -> Self { + self.arg( + Arg::new(options::BASE64) + .long(options::BASE64) + .help(translate!("ck-common-help-base64")) + .action(ArgAction::SetTrue) + // Even though this could easily just override an earlier '--raw', + // GNU cksum does not permit these flags to be combined: + .conflicts_with(options::RAW), + ) + } + + fn with_zero(self) -> Self { + self.arg( + Arg::new(options::ZERO) + .long(options::ZERO) + .short('z') + .help(translate!("ck-common-help-zero")) + .action(ArgAction::SetTrue), + ) + } + + fn with_debug(self) -> Self { + self.arg( + Arg::new(options::DEBUG) + .long(options::DEBUG) + .help(translate!("ck-common-help-debug")) + .action(ArgAction::SetTrue), + ) + } +} diff --git a/src/uu/checksum_common/src/lib.rs b/src/uu/checksum_common/src/lib.rs new file mode 100644 index 00000000000..1d5a2726520 --- /dev/null +++ b/src/uu/checksum_common/src/lib.rs @@ -0,0 +1,207 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +// spell-checker:ignore (ToDO) algo + +use std::ffi::OsString; + +use clap::builder::ValueParser; +use clap::{Arg, ArgAction, ArgMatches, Command, ValueHint}; + +use uucore::checksum::compute::{ + ChecksumComputeOptions, OutputFormat, perform_checksum_computation, +}; +use uucore::checksum::validate::{self, ChecksumValidateOptions, ChecksumVerbose}; +use uucore::checksum::{AlgoKind, ChecksumError, SizedAlgoKind}; +use uucore::error::UResult; +use uucore::line_ending::LineEnding; +use uucore::{crate_version, format_usage, localized_help_template, util_name}; + +mod cli; +pub use cli::ChecksumCommand; +pub use cli::options; + +/// Expands to generate the right `uumain` and `uu_app` functions +/// for standalone checksum binaries. +/// +/// Example: +/// ``` +/// use uu_checksum_common::declare_standalone; +/// use uucore::checksum::AlgoKind; +/// +/// declare_standalone!("sha512sum", AlgoKind::Sha512); +/// ``` +#[macro_export] +macro_rules! declare_standalone { + ($bin:literal, $kind:expr) => { + #[::uucore::main] + pub fn uumain(args: impl ::uucore::Args) -> ::uucore::error::UResult<()> { + ::uu_checksum_common::standalone_main($kind, uu_app(), args) + } + + #[inline] + pub fn uu_app() -> ::clap::Command { + ::uu_checksum_common::standalone_checksum_app( + ::uucore::translate!(concat!($bin, "-about")), + ::uucore::translate!(concat!($bin, "-usage")), + ) + } + }; +} + +/// Entrypoint for standalone checksums accepting the `--length` argument +/// +/// Note: Ideally, we wouldn't require a `cmd` to be passed to the function, +/// but for localization purposes, the standalone binaries must declare their +/// command (with about and usage) themselves, otherwise calling --help from +/// the multicall binary results in an unformatted output. +pub fn standalone_with_length_main( + algo: AlgoKind, + cmd: Command, + args: impl uucore::Args, + validate_len: fn(&str) -> UResult>, +) -> UResult<()> { + let matches = uucore::clap_localization::handle_clap_result(cmd, args)?; + let algo = Some(algo); + + let length = matches + .get_one::(options::LENGTH) + .map(String::as_str) + .map(validate_len) + .transpose()? + .flatten(); + + let format = OutputFormat::from_standalone(std::env::args_os()); + + checksum_main(algo, length, matches, format?) +} + +/// Entrypoint for standalone checksums *NOT* accepting the `--length` argument +pub fn standalone_main(algo: AlgoKind, cmd: Command, args: impl uucore::Args) -> UResult<()> { + let matches = uucore::clap_localization::handle_clap_result(cmd, args)?; + let algo = Some(algo); + + let format = OutputFormat::from_standalone(std::env::args_os()); + + checksum_main(algo, None, matches, format?) +} + +/// Base command processing for all the checksum executables. +pub fn default_checksum_app(about: String, usage: String) -> Command { + Command::new(util_name()) + .version(crate_version!()) + .help_template(localized_help_template(util_name())) + .about(about) + .override_usage(format_usage(&usage)) + .infer_long_args(true) + .args_override_self(true) + .arg( + Arg::new(options::FILE) + .hide(true) + .action(ArgAction::Append) + .value_parser(ValueParser::os_string()) + .default_value("-") + .hide_default_value(true) + .value_hint(ValueHint::FilePath), + ) +} + +/// Command processing for standalone checksums accepting the `--length` +/// argument +pub fn standalone_checksum_app_with_length(about: String, usage: String) -> Command { + default_checksum_app(about, usage) + .with_binary() + .with_check_and_opts() + .with_length() + .with_tag(false) + .with_text(true) + .with_zero() +} + +/// Command processing for standalone checksums *NOT* accepting the `--length` +/// argument +pub fn standalone_checksum_app(about: String, usage: String) -> Command { + default_checksum_app(about, usage) + .with_binary() + .with_check_and_opts() + .with_tag(false) + .with_text(true) + .with_zero() +} + +/// This is the common entrypoint to all checksum utils. Performs some +/// validation on arguments and proceeds in computing or checking mode. +pub fn checksum_main( + algo: Option, + length: Option, + matches: ArgMatches, + output_format: OutputFormat, +) -> UResult<()> { + let check = matches.get_flag("check"); + + let check_flag = |flag| match (check, matches.get_flag(flag)) { + (_, false) => Ok(false), + (true, true) => Ok(true), + (false, true) => Err(ChecksumError::CheckOnlyFlag(flag.into())), + }; + + // Each of the following flags are only expected in --check mode. + // If we encounter them otherwise, end with an error. + let ignore_missing = check_flag("ignore-missing")?; + let warn = check_flag("warn")?; + let quiet = check_flag("quiet")?; + let strict = check_flag("strict")?; + let status = check_flag("status")?; + + // clap provides the default value -. So we unwrap() safety. + let files = matches + .get_many::(options::FILE) + .unwrap() + .map(|s| s.as_os_str()); + + if check { + // cksum does not support '--check'ing legacy algorithms + if algo.is_some_and(AlgoKind::is_legacy) { + return Err(ChecksumError::AlgorithmNotSupportedWithCheck.into()); + } + + let text_flag = matches.get_flag(options::TEXT); + let binary_flag = matches.get_flag(options::BINARY); + let tag = matches.get_flag(options::TAG); + + if tag || binary_flag || text_flag { + return Err(ChecksumError::BinaryTextConflict.into()); + } + + // Execute the checksum validation based on the presence of files or the use of stdin + + let verbose = ChecksumVerbose::new(status, quiet, warn); + let opts = ChecksumValidateOptions { + ignore_missing, + strict, + verbose, + }; + + return validate::perform_checksum_validation(files, algo, length, opts); + } + + // Not --check + + // Set the default algorithm to CRC when not '--check'ing. + let algo_kind = algo.unwrap_or(AlgoKind::Crc); + + let algo = SizedAlgoKind::from_unsized(algo_kind, length)?; + let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO)); + + let opts = ChecksumComputeOptions { + algo_kind: algo, + output_format, + line_ending, + }; + + perform_checksum_computation(opts, files)?; + + Ok(()) +} From 52ef36d7adf7ae1a855a43316be6f70a13278580 Mon Sep 17 00:00:00 2001 From: Dorian Peron Date: Tue, 6 Jan 2026 20:55:58 +0100 Subject: [PATCH 03/10] cksum: transition to checksum_common --- Cargo.lock | 1 + src/uu/cksum/Cargo.toml | 1 + src/uu/cksum/locales/en-US.ftl | 16 -- src/uu/cksum/locales/fr-FR.ftl | 16 -- src/uu/cksum/src/cksum.rs | 263 ++++-------------------------- src/uucore/src/lib/mods/locale.rs | 5 + 6 files changed, 35 insertions(+), 267 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e763db08a57..15df13ee79a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3295,6 +3295,7 @@ dependencies = [ "clap", "codspeed-divan-compat", "fluent", + "uu_checksum_common", "uucore", ] diff --git a/src/uu/cksum/Cargo.toml b/src/uu/cksum/Cargo.toml index 1c343181ca4..5f509e31335 100644 --- a/src/uu/cksum/Cargo.toml +++ b/src/uu/cksum/Cargo.toml @@ -25,6 +25,7 @@ uucore = { workspace = true, features = [ "sum", "hardware", ] } +uu_checksum_common = { workspace = true } fluent = { workspace = true } [dev-dependencies] diff --git a/src/uu/cksum/locales/en-US.ftl b/src/uu/cksum/locales/en-US.ftl index 834cd77b0ef..aece6fc5b72 100644 --- a/src/uu/cksum/locales/en-US.ftl +++ b/src/uu/cksum/locales/en-US.ftl @@ -12,19 +12,3 @@ cksum-after-help = DIGEST determines the digest algorithm and default output for - sha3: (only available through cksum) - blake2b: (equivalent to b2sum) - sm3: (only available through cksum) - -# Help messages -cksum-help-algorithm = select the digest type to use. See DIGEST below -cksum-help-untagged = create a reversed style checksum, without digest type -cksum-help-tag = create a BSD style checksum, undo --untagged (default) -cksum-help-length = digest length in bits; must not exceed the max for the blake2 algorithm and must be a multiple of 8 -cksum-help-raw = emit a raw binary digest, not hexadecimal -cksum-help-strict = exit non-zero for improperly formatted checksum lines -cksum-help-check = read hashsums from the FILEs and check them -cksum-help-base64 = emit a base64 digest, not hexadecimal -cksum-help-warn = warn about improperly formatted checksum lines -cksum-help-status = don't output anything, status code shows success -cksum-help-quiet = don't print OK for each successfully verified file -cksum-help-ignore-missing = don't fail or report status for missing files -cksum-help-zero = end each output line with NUL, not newline, and disable file name escaping -cksum-help-debug = print CPU hardware capability detection info used by cksum diff --git a/src/uu/cksum/locales/fr-FR.ftl b/src/uu/cksum/locales/fr-FR.ftl index 01136f606f9..bbc12e59cde 100644 --- a/src/uu/cksum/locales/fr-FR.ftl +++ b/src/uu/cksum/locales/fr-FR.ftl @@ -12,19 +12,3 @@ cksum-after-help = DIGEST détermine l'algorithme de condensé et le format de s - sha3 : (disponible uniquement via cksum) - blake2b : (équivalent à b2sum) - sm3 : (disponible uniquement via cksum) - -# Messages d'aide -cksum-help-algorithm = sélectionner le type de condensé à utiliser. Voir DIGEST ci-dessous -cksum-help-untagged = créer une somme de contrôle de style inversé, sans type de condensé -cksum-help-tag = créer une somme de contrôle de style BSD, annuler --untagged (par défaut) -cksum-help-length = longueur du condensé en bits ; ne doit pas dépasser le maximum pour l'algorithme blake2 et doit être un multiple de 8 -cksum-help-raw = émettre un condensé binaire brut, pas hexadécimal -cksum-help-strict = sortir avec un code non-zéro pour les lignes de somme de contrôle mal formatées -cksum-help-check = lire les sommes de hachage des FICHIERs et les vérifier -cksum-help-base64 = émettre un condensé base64, pas hexadécimal -cksum-help-warn = avertir des lignes de somme de contrôle mal formatées -cksum-help-status = ne rien afficher, le code de statut indique le succès -cksum-help-quiet = ne pas afficher OK pour chaque fichier vérifié avec succès -cksum-help-ignore-missing = ne pas échouer ou signaler le statut pour les fichiers manquants -cksum-help-zero = terminer chaque ligne de sortie avec NUL, pas un saut de ligne, et désactiver l'échappement des noms de fichiers -cksum-help-debug = afficher les informations de débogage sur la détection de la prise en charge matérielle du processeur diff --git a/src/uu/cksum/src/cksum.rs b/src/uu/cksum/src/cksum.rs index 2032d0662bb..0f9fdee5fa6 100644 --- a/src/uu/cksum/src/cksum.rs +++ b/src/uu/cksum/src/cksum.rs @@ -5,23 +5,18 @@ // spell-checker:ignore (ToDO) fname, algo, bitlen -use clap::builder::ValueParser; -use clap::{Arg, ArgAction, Command}; -use std::ffi::{OsStr, OsString}; -use uucore::checksum::compute::{ - ChecksumComputeOptions, OutputFormat, perform_checksum_computation, -}; -use uucore::checksum::validate::{ - ChecksumValidateOptions, ChecksumVerbose, perform_checksum_validation, -}; +use std::ffi::OsStr; + +use clap::Command; +use uu_checksum_common::{ChecksumCommand, checksum_main, default_checksum_app, options}; + +use uucore::checksum::compute::OutputFormat; use uucore::checksum::{ - AlgoKind, ChecksumError, SUPPORTED_ALGORITHMS, SizedAlgoKind, calculate_blake2b_length_str, - sanitize_sha2_sha3_length_str, + AlgoKind, ChecksumError, calculate_blake2b_length_str, sanitize_sha2_sha3_length_str, }; use uucore::error::UResult; use uucore::hardware::{HasHardwareFeatures as _, SimdPolicy}; -use uucore::line_ending::LineEnding; -use uucore::{format_usage, show_error, translate}; +use uucore::{show_error, translate}; /// Print CPU hardware capability detection information to stderr /// This matches GNU cksum's --debug behavior @@ -47,26 +42,6 @@ fn print_cpu_debug_info() { } } -mod options { - pub const ALGORITHM: &str = "algorithm"; - pub const FILE: &str = "file"; - pub const UNTAGGED: &str = "untagged"; - pub const TAG: &str = "tag"; - pub const LENGTH: &str = "length"; - pub const RAW: &str = "raw"; - pub const BASE64: &str = "base64"; - pub const CHECK: &str = "check"; - pub const STRICT: &str = "strict"; - pub const TEXT: &str = "text"; - pub const BINARY: &str = "binary"; - pub const STATUS: &str = "status"; - pub const WARN: &str = "warn"; - pub const IGNORE_MISSING: &str = "ignore-missing"; - pub const QUIET: &str = "quiet"; - pub const ZERO: &str = "zero"; - pub const DEBUG: &str = "debug"; -} - /// cksum has a bunch of legacy behavior. We handle this in this function to /// make sure they are self contained and "easier" to understand. /// @@ -137,22 +112,6 @@ fn maybe_sanitize_length( pub fn uumain(args: impl uucore::Args) -> UResult<()> { let matches = uucore::clap_localization::handle_clap_result(uu_app(), args)?; - let check = matches.get_flag(options::CHECK); - - let check_flag = |flag| match (check, matches.get_flag(flag)) { - (_, false) => Ok(false), - (true, true) => Ok(true), - (false, true) => Err(ChecksumError::CheckOnlyFlag(flag.into())), - }; - - // Each of the following flags are only expected in --check mode. - // If we encounter them otherwise, end with an error. - let ignore_missing = check_flag(options::IGNORE_MISSING)?; - let warn = check_flag(options::WARN)?; - let quiet = check_flag(options::QUIET)?; - let strict = check_flag(options::STRICT)?; - let status = check_flag(options::STATUS)?; - let algo_cli = matches .get_one::(options::ALGORITHM) .map(AlgoKind::from_cksum) @@ -164,202 +123,36 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let length = maybe_sanitize_length(algo_cli, input_length)?; - // clap provides the default value -. So we unwrap() safety. - let files = matches - .get_many::(options::FILE) - .unwrap() - .map(|s| s.as_os_str()); - - if check { - // cksum does not support '--check'ing legacy algorithms - if algo_cli.is_some_and(AlgoKind::is_legacy) { - return Err(ChecksumError::AlgorithmNotSupportedWithCheck.into()); - } - - let text_flag = matches.get_flag(options::TEXT); - let binary_flag = matches.get_flag(options::BINARY); - let tag = matches.get_flag(options::TAG); - - if tag || binary_flag || text_flag { - return Err(ChecksumError::BinaryTextConflict.into()); - } - - // Execute the checksum validation based on the presence of files or the use of stdin - - let verbose = ChecksumVerbose::new(status, quiet, warn); - let opts = ChecksumValidateOptions { - ignore_missing, - strict, - verbose, - }; - - return perform_checksum_validation(files, algo_cli, length, opts); - } - - // Not --check - - // Print hardware debug info if requested - if matches.get_flag(options::DEBUG) { - print_cpu_debug_info(); - } - - // Set the default algorithm to CRC when not '--check'ing. - let algo_kind = algo_cli.unwrap_or(AlgoKind::Crc); - let (tag, binary) = handle_tag_text_binary_flags(std::env::args_os())?; let output_format = OutputFormat::from_cksum( - algo_kind, + algo_cli.unwrap_or(AlgoKind::Crc), tag, binary, - matches.get_flag(options::RAW), - matches.get_flag(options::BASE64), + /* raw */ matches.get_flag(options::RAW), + /* base64 */ matches.get_flag(options::BASE64), ); - let algo = SizedAlgoKind::from_unsized(algo_kind, length)?; - let line_ending = LineEnding::from_zero_flag(matches.get_flag(options::ZERO)); - - let opts = ChecksumComputeOptions { - algo_kind: algo, - output_format, - line_ending, - }; - - perform_checksum_computation(opts, files)?; + // Print hardware debug info if requested + if matches.get_flag(options::DEBUG) { + print_cpu_debug_info(); + } - Ok(()) + checksum_main(algo_cli, length, matches, output_format) } pub fn uu_app() -> Command { - Command::new(uucore::util_name()) - .version(uucore::crate_version!()) - .help_template(uucore::localized_help_template(uucore::util_name())) - .about(translate!("cksum-about")) - .override_usage(format_usage(&translate!("cksum-usage"))) - .infer_long_args(true) - .args_override_self(true) - .arg( - Arg::new(options::FILE) - .hide(true) - .action(ArgAction::Append) - .value_parser(ValueParser::os_string()) - .default_value("-") - .hide_default_value(true) - .value_hint(clap::ValueHint::FilePath), - ) - .arg( - Arg::new(options::ALGORITHM) - .long(options::ALGORITHM) - .short('a') - .help(translate!("cksum-help-algorithm")) - .value_name("ALGORITHM") - .value_parser(SUPPORTED_ALGORITHMS), - ) - .arg( - Arg::new(options::UNTAGGED) - .long(options::UNTAGGED) - .help(translate!("cksum-help-untagged")) - .action(ArgAction::SetTrue) - .overrides_with(options::TAG), - ) - .arg( - Arg::new(options::TAG) - .long(options::TAG) - .help(translate!("cksum-help-tag")) - .action(ArgAction::SetTrue) - .overrides_with(options::UNTAGGED), - ) - .arg( - Arg::new(options::LENGTH) - .long(options::LENGTH) - .short('l') - .help(translate!("cksum-help-length")) - .action(ArgAction::Set), - ) - .arg( - Arg::new(options::RAW) - .long(options::RAW) - .help(translate!("cksum-help-raw")) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::STRICT) - .long(options::STRICT) - .help(translate!("cksum-help-strict")) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::CHECK) - .short('c') - .long(options::CHECK) - .help(translate!("cksum-help-check")) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::BASE64) - .long(options::BASE64) - .help(translate!("cksum-help-base64")) - .action(ArgAction::SetTrue) - // Even though this could easily just override an earlier '--raw', - // GNU cksum does not permit these flags to be combined: - .conflicts_with(options::RAW), - ) - .arg( - Arg::new(options::TEXT) - .long(options::TEXT) - .short('t') - .hide(true) - .overrides_with(options::BINARY) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::BINARY) - .long(options::BINARY) - .short('b') - .hide(true) - .overrides_with(options::TEXT) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::WARN) - .short('w') - .long("warn") - .help(translate!("cksum-help-warn")) - .action(ArgAction::SetTrue) - .overrides_with_all([options::STATUS, options::QUIET]), - ) - .arg( - Arg::new(options::STATUS) - .long("status") - .help(translate!("cksum-help-status")) - .action(ArgAction::SetTrue) - .overrides_with_all([options::WARN, options::QUIET]), - ) - .arg( - Arg::new(options::QUIET) - .long(options::QUIET) - .help(translate!("cksum-help-quiet")) - .action(ArgAction::SetTrue) - .overrides_with_all([options::WARN, options::STATUS]), - ) - .arg( - Arg::new(options::IGNORE_MISSING) - .long(options::IGNORE_MISSING) - .help(translate!("cksum-help-ignore-missing")) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::ZERO) - .long(options::ZERO) - .short('z') - .help(translate!("cksum-help-zero")) - .action(ArgAction::SetTrue), - ) - .arg( - Arg::new(options::DEBUG) - .long(options::DEBUG) - .help(translate!("cksum-help-debug")) - .action(ArgAction::SetTrue), - ) + default_checksum_app(translate!("cksum-about"), translate!("cksum-usage")) + .with_algo() + .with_untagged() + .with_tag(true) + .with_length() + .with_raw() + .with_check_and_opts() + .with_base64() + .with_text(false) + .with_binary() + .with_zero() + .with_debug() .after_help(translate!("cksum-after-help")) } diff --git a/src/uucore/src/lib/mods/locale.rs b/src/uucore/src/lib/mods/locale.rs index ec9a78b433c..8425d1e1b8b 100644 --- a/src/uucore/src/lib/mods/locale.rs +++ b/src/uucore/src/lib/mods/locale.rs @@ -156,6 +156,11 @@ fn create_bundle( // Then, try to load utility-specific strings from the utility's locale directory try_add_resource_from(get_locales_dir(util_name).ok()); + // checksum binaries also require fluent files from the checksum_common crate + if ["cksum"].contains(&util_name) { + try_add_resource_from(get_locales_dir("checksum_common").ok()); + } + // If we have at least one resource, return the bundle if bundle.has_message("common-error") || bundle.has_message(&format!("{util_name}-about")) { Ok(bundle) From a968342d426383b304d7887251648648d1647134 Mon Sep 17 00:00:00 2001 From: Dorian Peron Date: Tue, 6 Jan 2026 23:39:27 +0100 Subject: [PATCH 04/10] md5sum: introduce standalone binary --- Cargo.lock | 13 + Cargo.toml | 2 + GNUmakefile | 1 - build.rs | 1 - src/common/validation.rs | 5 +- src/uu/md5sum/Cargo.toml | 42 + src/uu/md5sum/LICENSE | 1 + src/uu/md5sum/locales/en-US.ftl | 2 + src/uu/md5sum/locales/fr-FR.ftl | 2 + src/uu/md5sum/src/main.rs | 1 + src/uu/md5sum/src/md5sum.rs | 1 + src/uucore/src/lib/lib.rs | 4 +- src/uucore/src/lib/mods/locale.rs | 2 +- tests/by-util/test_hashsum.rs | 52 +- tests/by-util/test_md5sum.rs | 812 ++++++++++++++++++ tests/fixtures/md5sum/input.txt | 1 + .../{hashsum => md5sum}/md5.checkfile | 0 .../fixtures/{hashsum => md5sum}/md5.expected | 0 tests/tests.rs | 4 + 19 files changed, 923 insertions(+), 23 deletions(-) create mode 100644 src/uu/md5sum/Cargo.toml create mode 120000 src/uu/md5sum/LICENSE create mode 100644 src/uu/md5sum/locales/en-US.ftl create mode 100644 src/uu/md5sum/locales/fr-FR.ftl create mode 100644 src/uu/md5sum/src/main.rs create mode 100644 src/uu/md5sum/src/md5sum.rs create mode 100644 tests/by-util/test_md5sum.rs create mode 100644 tests/fixtures/md5sum/input.txt rename tests/fixtures/{hashsum => md5sum}/md5.checkfile (100%) rename tests/fixtures/{hashsum => md5sum}/md5.expected (100%) diff --git a/Cargo.lock b/Cargo.lock index 15df13ee79a..49d209ab0a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -599,6 +599,7 @@ dependencies = [ "uu_ln", "uu_logname", "uu_ls", + "uu_md5sum", "uu_mkdir", "uu_mkfifo", "uu_mknod", @@ -3675,6 +3676,18 @@ dependencies = [ "uutils_term_grid", ] +[[package]] +name = "uu_md5sum" +version = "0.6.0" +dependencies = [ + "clap", + "codspeed-divan-compat", + "fluent", + "tempfile", + "uu_checksum_common", + "uucore", +] + [[package]] name = "uu_mkdir" version = "0.6.0" diff --git a/Cargo.toml b/Cargo.toml index b30b0672ce1..f6354a64440 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,6 +86,7 @@ feat_common_core = [ "basenc", "cat", "cksum", + "md5sum", "comm", "cp", "csplit", @@ -437,6 +438,7 @@ chmod = { optional = true, version = "0.6.0", package = "uu_chmod", path = "src/ chown = { optional = true, version = "0.6.0", package = "uu_chown", path = "src/uu/chown" } chroot = { optional = true, version = "0.6.0", package = "uu_chroot", path = "src/uu/chroot" } cksum = { optional = true, version = "0.6.0", package = "uu_cksum", path = "src/uu/cksum" } +md5sum = { optional = true, version = "0.6.0", package = "uu_md5sum", path = "src/uu/md5sum" } comm = { optional = true, version = "0.6.0", package = "uu_comm", path = "src/uu/comm" } cp = { optional = true, version = "0.6.0", package = "uu_cp", path = "src/uu/cp" } csplit = { optional = true, version = "0.6.0", package = "uu_csplit", path = "src/uu/csplit" } diff --git a/GNUmakefile b/GNUmakefile index af2fde7acce..243e038c433 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -97,7 +97,6 @@ SELINUX_PROGS := \ HASHSUM_PROGS := \ b2sum \ - md5sum \ sha1sum \ sha224sum \ sha256sum \ diff --git a/build.rs b/build.rs index aabd968329b..989f7630d2b 100644 --- a/build.rs +++ b/build.rs @@ -91,7 +91,6 @@ pub fn main() { phf_map.entry(krate, format!("({krate}::uumain, {krate}::uu_app_custom)")); let map_value = format!("({krate}::uumain, {krate}::uu_app_common)"); - phf_map.entry("md5sum", map_value.clone()); phf_map.entry("sha1sum", map_value.clone()); phf_map.entry("sha224sum", map_value.clone()); phf_map.entry("sha256sum", map_value.clone()); diff --git a/src/common/validation.rs b/src/common/validation.rs index f3923adb872..d332d304f39 100644 --- a/src/common/validation.rs +++ b/src/common/validation.rs @@ -51,9 +51,7 @@ fn get_canonical_util_name(util_name: &str) -> &str { "[" => "test", // hashsum aliases - all these hash commands are aliases for hashsum - "md5sum" | "sha1sum" | "sha224sum" | "sha256sum" | "sha384sum" | "sha512sum" | "b2sum" => { - "hashsum" - } + "sha1sum" | "sha224sum" | "sha256sum" | "sha384sum" | "sha512sum" | "b2sum" => "hashsum", "dir" => "ls", // dir is an alias for ls @@ -85,7 +83,6 @@ mod tests { fn test_get_canonical_util_name() { // Test a few key aliases assert_eq!(get_canonical_util_name("["), "test"); - assert_eq!(get_canonical_util_name("md5sum"), "hashsum"); assert_eq!(get_canonical_util_name("dir"), "ls"); // Test passthrough case diff --git a/src/uu/md5sum/Cargo.toml b/src/uu/md5sum/Cargo.toml new file mode 100644 index 00000000000..70ecfe0cdc3 --- /dev/null +++ b/src/uu/md5sum/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "uu_md5sum" +description = "md5sum ~ (uutils) Print or check the MD5 checksums" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/md5sum" +version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +keywords.workspace = true +categories.workspace = true +edition.workspace = true +readme.workspace = true + +[lints] +workspace = true + +[lib] +path = "src/md5sum.rs" + +[dependencies] +clap = { workspace = true } +uu_checksum_common = { workspace = true } +uucore = { workspace = true, features = [ + "checksum", + "encoding", + "sum", + "hardware", +] } +fluent = { workspace = true } + +[dev-dependencies] +divan = { workspace = true } +tempfile = { workspace = true } +uucore = { workspace = true, features = ["benchmark"] } + +[[bin]] +name = "md5sum" +path = "src/main.rs" + +# [[bench]] +# name = "b2sum_bench" +# harness = false diff --git a/src/uu/md5sum/LICENSE b/src/uu/md5sum/LICENSE new file mode 120000 index 00000000000..5853aaea53b --- /dev/null +++ b/src/uu/md5sum/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/md5sum/locales/en-US.ftl b/src/uu/md5sum/locales/en-US.ftl new file mode 100644 index 00000000000..9712ff7c66d --- /dev/null +++ b/src/uu/md5sum/locales/en-US.ftl @@ -0,0 +1,2 @@ +md5sum-about = Print or check the MD5 checksums +md5sum-usage = md5sum [OPTIONS] [FILE]... diff --git a/src/uu/md5sum/locales/fr-FR.ftl b/src/uu/md5sum/locales/fr-FR.ftl new file mode 100644 index 00000000000..8da43df3665 --- /dev/null +++ b/src/uu/md5sum/locales/fr-FR.ftl @@ -0,0 +1,2 @@ +md5sum-about = Afficher le MD5 et la taille de chaque fichier +md5sum-usage = md5sum [OPTION]... [FICHIER]... diff --git a/src/uu/md5sum/src/main.rs b/src/uu/md5sum/src/main.rs new file mode 100644 index 00000000000..d5509656f93 --- /dev/null +++ b/src/uu/md5sum/src/main.rs @@ -0,0 +1 @@ +uucore::bin!(uu_md5sum); diff --git a/src/uu/md5sum/src/md5sum.rs b/src/uu/md5sum/src/md5sum.rs new file mode 100644 index 00000000000..c9366eb4ba9 --- /dev/null +++ b/src/uu/md5sum/src/md5sum.rs @@ -0,0 +1 @@ +uu_checksum_common::declare_standalone!("md5sum", uucore::checksum::AlgoKind::Md5); diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 03ae3d95576..effefbc0646 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -173,9 +173,7 @@ pub fn get_canonical_util_name(util_name: &str) -> &str { "[" => "test", // hashsum aliases - all these hash commands are aliases for hashsum - "md5sum" | "sha1sum" | "sha224sum" | "sha256sum" | "sha384sum" | "sha512sum" | "b2sum" => { - "hashsum" - } + "sha1sum" | "sha224sum" | "sha256sum" | "sha384sum" | "sha512sum" | "b2sum" => "hashsum", "dir" => "ls", // dir is an alias for ls diff --git a/src/uucore/src/lib/mods/locale.rs b/src/uucore/src/lib/mods/locale.rs index 8425d1e1b8b..19b9be9f10e 100644 --- a/src/uucore/src/lib/mods/locale.rs +++ b/src/uucore/src/lib/mods/locale.rs @@ -157,7 +157,7 @@ fn create_bundle( try_add_resource_from(get_locales_dir(util_name).ok()); // checksum binaries also require fluent files from the checksum_common crate - if ["cksum"].contains(&util_name) { + if ["cksum", "md5sum"].contains(&util_name) { try_add_resource_from(get_locales_dir("checksum_common").ok()); } diff --git a/tests/by-util/test_hashsum.rs b/tests/by-util/test_hashsum.rs index 2f1719b0eca..2022f81b8c0 100644 --- a/tests/by-util/test_hashsum.rs +++ b/tests/by-util/test_hashsum.rs @@ -201,7 +201,6 @@ macro_rules! test_digest_with_len { }; } -test_digest! {md5, md5} test_digest! {sha1, sha1} test_digest! {b3sum, b3sum} test_digest! {shake128, shake128} @@ -237,6 +236,7 @@ fn test_check_sha1() { .stderr_is(""); } +#[ignore = "moved to standalone"] #[test] fn test_check_md5_ignore_missing() { let scene = TestScenario::new(util_name!()); @@ -428,6 +428,7 @@ fn test_check_file_not_found_warning() { // Asterisk `*` is a reserved paths character on win32, nor the path can end with a whitespace. // ref: https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions +#[ignore = "moved to standalone"] #[test] fn test_check_md5sum() { let scene = TestScenario::new(util_name!()); @@ -478,6 +479,7 @@ fn test_check_md5sum() { } // GNU also supports one line sep +#[ignore = "moved to standalone"] #[test] fn test_check_md5sum_only_one_space() { let scene = TestScenario::new(util_name!()); @@ -501,6 +503,7 @@ fn test_check_md5sum_only_one_space() { .stdout_only("a: OK\n b: OK\nc: OK\n"); } +#[ignore = "moved to standalone"] #[test] fn test_check_md5sum_reverse_bsd() { let scene = TestScenario::new(util_name!()); @@ -550,6 +553,7 @@ fn test_check_md5sum_reverse_bsd() { } } +#[ignore = "moved to standalone"] #[test] fn test_check_md5sum_mixed_format() { let scene = TestScenario::new(util_name!()); @@ -622,6 +626,7 @@ fn test_tag() { ); } +#[ignore = "moved to standalone"] #[test] #[cfg(not(windows))] fn test_with_escape_filename() { @@ -637,6 +642,7 @@ fn test_with_escape_filename() { assert!(stdout.trim().ends_with("a\\nb")); } +#[ignore = "moved to standalone"] #[test] #[cfg(not(windows))] fn test_with_escape_filename_zero_text() { @@ -657,6 +663,7 @@ fn test_with_escape_filename_zero_text() { assert!(stdout.contains("a\nb")); } +#[ignore = "moved to standalone"] #[test] fn test_check_empty_line() { let scene = TestScenario::new(util_name!()); @@ -675,6 +682,7 @@ fn test_check_empty_line() { .stderr_contains("WARNING: 1 line is improperly formatted"); } +#[ignore = "moved to standalone"] #[test] #[cfg(not(windows))] fn test_check_with_escape_filename() { @@ -699,6 +707,7 @@ fn test_check_with_escape_filename() { result.stdout_is("\\a\\nb: OK\n"); } +#[ignore = "moved to standalone"] #[test] fn test_check_strict_error() { let scene = TestScenario::new(util_name!()); @@ -718,6 +727,7 @@ fn test_check_strict_error() { .stderr_contains("WARNING: 3 lines are improperly formatted"); } +#[ignore = "moved to standalone"] #[test] fn test_check_warn() { let scene = TestScenario::new(util_name!()); @@ -746,6 +756,7 @@ fn test_check_warn() { .fails(); } +#[ignore = "moved to standalone"] #[test] fn test_check_status() { let scene = TestScenario::new(util_name!()); @@ -762,6 +773,7 @@ fn test_check_status() { .no_output(); } +#[ignore = "moved to standalone"] #[test] fn test_check_status_code() { let scene = TestScenario::new(util_name!()); @@ -779,6 +791,7 @@ fn test_check_status_code() { .stdout_is(""); } +#[ignore = "moved to standalone"] #[test] fn test_sha1_with_md5sum_should_fail() { let scene = TestScenario::new(util_name!()); @@ -795,6 +808,7 @@ fn test_sha1_with_md5sum_should_fail() { .stderr_does_not_contain("WARNING: 1 line is improperly formatted"); } +#[ignore = "moved to standalone"] #[test] // Disabled on Windows because of the "*" #[cfg(not(windows))] @@ -834,6 +848,7 @@ fn test_check_one_two_space_star() { .stdout_is("*empty: OK\n"); } +#[ignore = "moved to standalone"] #[test] // Disabled on Windows because of the "*" #[cfg(not(windows))] @@ -876,6 +891,7 @@ fn test_check_space_star_or_not() { .stderr_contains("WARNING: 1 line is improperly formatted"); } +#[ignore = "moved to standalone"] #[test] fn test_check_no_backslash_no_space() { let scene = TestScenario::new(util_name!()); @@ -891,6 +907,7 @@ fn test_check_no_backslash_no_space() { .stdout_is("f: OK\n"); } +#[ignore = "moved to standalone"] #[test] fn test_incomplete_format() { let scene = TestScenario::new(util_name!()); @@ -906,6 +923,7 @@ fn test_incomplete_format() { .stderr_contains("no properly formatted checksum lines found"); } +#[ignore = "moved to standalone"] #[test] fn test_start_error() { let scene = TestScenario::new(util_name!()); @@ -923,6 +941,7 @@ fn test_start_error() { .stderr_contains("WARNING: 1 line is improperly formatted"); } +#[ignore = "moved to standalone"] #[test] fn test_check_check_ignore_no_file() { let scene = TestScenario::new(util_name!()); @@ -939,6 +958,7 @@ fn test_check_check_ignore_no_file() { .stderr_contains("in.md5: no file was verified"); } +#[ignore = "moved to standalone"] #[test] fn test_check_directory_error() { let scene = TestScenario::new(util_name!()); @@ -958,6 +978,7 @@ fn test_check_directory_error() { .stderr_contains(err_msg); } +#[ignore = "moved to standalone"] #[test] #[cfg(not(windows))] fn test_continue_after_directory_error() { @@ -990,6 +1011,7 @@ fn test_continue_after_directory_error() { .stderr_is(err_msg); } +#[ignore = "moved to standalone"] #[test] fn test_check_quiet() { let scene = TestScenario::new(util_name!()); @@ -1030,6 +1052,7 @@ fn test_check_quiet() { .stderr_contains("md5sum: the --strict option is meaningful only when verifying checksums"); } +#[ignore = "moved to standalone"] #[test] fn test_star_to_start() { let scene = TestScenario::new(util_name!()); @@ -1081,6 +1104,7 @@ fn test_check_b2sum_strict_check() { .stdout_only(&output); } +#[ignore = "moved to standalone"] #[test] fn test_check_md5_comment_line() { // A comment in a checksum file shall be discarded unnoticed. @@ -1106,6 +1130,7 @@ fn test_check_md5_comment_line() { .no_stderr(); } +#[ignore = "moved to standalone"] #[test] fn test_check_md5_comment_only() { // A file only filled with comments is equivalent to an empty file, @@ -1125,6 +1150,7 @@ fn test_check_md5_comment_only() { .stderr_contains("no properly formatted checksum lines found"); } +#[ignore = "moved to standalone"] #[test] fn test_check_md5_comment_leading_space() { // A file only filled with comments is equivalent to an empty file, @@ -1198,12 +1224,12 @@ fn test_help_shows_correct_utility_name() { let scene = TestScenario::new(util_name!()); // Test md5sum - scene - .ccmd("md5sum") - .arg("--help") - .succeeds() - .stdout_contains("Usage: md5sum") - .stdout_does_not_contain("Usage: hashsum"); + // scene + // .ccmd("md5sum") + // .arg("--help") + // .succeeds() + // .stdout_contains("Usage: md5sum") + // .stdout_does_not_contain("Usage: hashsum"); // Test sha256sum scene @@ -1214,12 +1240,12 @@ fn test_help_shows_correct_utility_name() { .stdout_does_not_contain("Usage: hashsum"); // Test b2sum - scene - .ccmd("b2sum") - .arg("--help") - .succeeds() - .stdout_contains("Usage: b2sum") - .stdout_does_not_contain("Usage: hashsum"); + // scene + // .ccmd("b2sum") + // .arg("--help") + // .succeeds() + // .stdout_contains("Usage: b2sum") + // .stdout_does_not_contain("Usage: hashsum"); // Test that generic hashsum still shows the correct usage scene diff --git a/tests/by-util/test_md5sum.rs b/tests/by-util/test_md5sum.rs new file mode 100644 index 00000000000..6ccf173cf15 --- /dev/null +++ b/tests/by-util/test_md5sum.rs @@ -0,0 +1,812 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +use uutests::new_ucmd; +use uutests::util::TestScenario; +use uutests::util_name; +// spell-checker:ignore checkfile, testf, ntestf +macro_rules! get_hash( + ($str:expr) => ( + $str.split(' ').collect::>()[0] + ); +); + +macro_rules! test_digest { + ($id:ident) => { + mod $id { + use uutests::util::*; + use uutests::util_name; + static EXPECTED_FILE: &'static str = concat!(stringify!($id), ".expected"); + static CHECK_FILE: &'static str = concat!(stringify!($id), ".checkfile"); + static INPUT_FILE: &'static str = "input.txt"; + + #[test] + fn test_single_file() { + let ts = TestScenario::new(util_name!()); + assert_eq!( + ts.fixtures.read(EXPECTED_FILE), + get_hash!( + ts.ucmd() + .arg(INPUT_FILE) + .succeeds() + .no_stderr() + .stdout_str() + ) + ); + } + + #[test] + fn test_stdin() { + let ts = TestScenario::new(util_name!()); + assert_eq!( + ts.fixtures.read(EXPECTED_FILE), + get_hash!( + ts.ucmd() + .pipe_in_fixture(INPUT_FILE) + .succeeds() + .no_stderr() + .stdout_str() + ) + ); + } + + #[test] + fn test_check() { + let ts = TestScenario::new(util_name!()); + println!("File content='{}'", ts.fixtures.read(INPUT_FILE)); + println!("Check file='{}'", ts.fixtures.read(CHECK_FILE)); + + ts.ucmd() + .args(&["--check", CHECK_FILE]) + .succeeds() + .no_stderr() + .stdout_is("input.txt: OK\n"); + } + + #[test] + fn test_zero() { + let ts = TestScenario::new(util_name!()); + assert_eq!( + ts.fixtures.read(EXPECTED_FILE), + get_hash!( + ts.ucmd() + .arg("--zero") + .arg(INPUT_FILE) + .succeeds() + .no_stderr() + .stdout_str() + ) + ); + } + + #[test] + fn test_missing_file() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + + at.write("a", "file1\n"); + at.write("c", "file3\n"); + + ts.ucmd() + .args(&["a", "b", "c"]) + .fails() + .stdout_contains("a\n") + .stdout_contains("c\n") + .stderr_contains("b: No such file or directory"); + } + } + }; +} + +test_digest! {md5} + +#[test] +fn test_check_md5_ignore_missing() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.write("testf", "foobar\n"); + at.write( + "testf.sha1", + "14758f1afd44c09b7992073ccf00b43d testf\n14758f1afd44c09b7992073ccf00b43d testf2\n", + ); + scene + .ccmd("md5sum") + .arg("-c") + .arg(at.subdir.join("testf.sha1")) + .fails() + .stdout_contains("testf2: FAILED open or read"); + + scene + .ccmd("md5sum") + .arg("-c") + .arg("--ignore-missing") + .arg(at.subdir.join("testf.sha1")) + .succeeds() + .stdout_is("testf: OK\n") + .stderr_is(""); + + scene + .ccmd("md5sum") + .arg("--ignore-missing") + .arg(at.subdir.join("testf.sha1")) + .fails() + .stderr_contains( + "md5sum: the --ignore-missing option is meaningful only when verifying checksums", + ); +} + +// Asterisk `*` is a reserved paths character on win32, nor the path can end with a whitespace. +// ref: https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions +#[test] +fn test_check_md5sum() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + #[cfg(not(windows))] + { + for f in &["a", " b", "*c", "dd", " "] { + at.write(f, &format!("{f}\n")); + } + at.write( + "check.md5sum", + "60b725f10c9c85c70d97880dfe8191b3 a\n\ + bf35d7536c785cf06730d5a40301eba2 b\n\ + f5b61709718c1ecf8db1aea8547d4698 *c\n\ + b064a020db8018f18ff5ae367d01b212 dd\n\ + d784fa8b6d98d27699781bd9a7cf19f0 ", + ); + scene + .ccmd("md5sum") + .arg("--strict") + .arg("-c") + .arg("check.md5sum") + .succeeds() + .stdout_is("a: OK\n b: OK\n*c: OK\ndd: OK\n : OK\n") + .stderr_is(""); + } + #[cfg(windows)] + { + for f in &["a", " b", "dd"] { + at.write(f, &format!("{f}\n")); + } + at.write( + "check.md5sum", + "60b725f10c9c85c70d97880dfe8191b3 a\n\ + bf35d7536c785cf06730d5a40301eba2 b\n\ + b064a020db8018f18ff5ae367d01b212 dd", + ); + scene + .ccmd("md5sum") + .arg("--strict") + .arg("-c") + .arg("check.md5sum") + .succeeds() + .stdout_is("a: OK\n b: OK\ndd: OK\n") + .stderr_is(""); + } +} + +// GNU also supports one line sep +#[test] +fn test_check_md5sum_only_one_space() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + for f in ["a", " b", "c"] { + at.write(f, &format!("{f}\n")); + } + at.write( + "check.md5sum", + "60b725f10c9c85c70d97880dfe8191b3 a\n\ + bf35d7536c785cf06730d5a40301eba2 b\n\ + 2cd6ee2c70b0bde53fbe6cac3c8b8bb1 c\n", + ); + scene + .ccmd("md5sum") + .arg("--strict") + .arg("-c") + .arg("check.md5sum") + .succeeds() + .stdout_only("a: OK\n b: OK\nc: OK\n"); +} + +#[test] +fn test_check_md5sum_reverse_bsd() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + #[cfg(not(windows))] + { + for f in &["a", " b", "*c", "dd", " "] { + at.write(f, &format!("{f}\n")); + } + at.write( + "check.md5sum", + "60b725f10c9c85c70d97880dfe8191b3 a\n\ + bf35d7536c785cf06730d5a40301eba2 b\n\ + f5b61709718c1ecf8db1aea8547d4698 *c\n\ + b064a020db8018f18ff5ae367d01b212 dd\n\ + d784fa8b6d98d27699781bd9a7cf19f0 ", + ); + scene + .ccmd("md5sum") + .arg("--strict") + .arg("-c") + .arg("check.md5sum") + .succeeds() + .stdout_is("a: OK\n b: OK\n*c: OK\ndd: OK\n : OK\n") + .stderr_is(""); + } + #[cfg(windows)] + { + for f in &["a", " b", "dd"] { + at.write(f, &format!("{f}\n")); + } + at.write( + "check.md5sum", + "60b725f10c9c85c70d97880dfe8191b3 a\n\ + bf35d7536c785cf06730d5a40301eba2 b\n\ + b064a020db8018f18ff5ae367d01b212 dd", + ); + scene + .ccmd("md5sum") + .arg("--strict") + .arg("-c") + .arg("check.md5sum") + .succeeds() + .stdout_is("a: OK\n b: OK\ndd: OK\n") + .stderr_is(""); + } +} + +#[test] +fn test_check_md5sum_mixed_format() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + #[cfg(not(windows))] + { + for f in &[" b", "*c", "dd", " "] { + at.write(f, &format!("{f}\n")); + } + at.write( + "check.md5sum", + "bf35d7536c785cf06730d5a40301eba2 b\n\ + f5b61709718c1ecf8db1aea8547d4698 *c\n\ + b064a020db8018f18ff5ae367d01b212 dd\n\ + d784fa8b6d98d27699781bd9a7cf19f0 ", + ); + } + #[cfg(windows)] + { + for f in &[" b", "dd"] { + at.write(f, &format!("{f}\n")); + } + at.write( + "check.md5sum", + "bf35d7536c785cf06730d5a40301eba2 b\n\ + b064a020db8018f18ff5ae367d01b212 dd", + ); + } + scene + .ccmd("md5sum") + .arg("--strict") + .arg("-c") + .arg("check.md5sum") + .fails_with_code(1); +} + +#[test] +fn test_invalid_arg() { + new_ucmd!().arg("--definitely-invalid").fails_with_code(1); +} + +#[test] +fn test_conflicting_arg() { + new_ucmd!().arg("--tag").arg("--check").fails_with_code(1); + new_ucmd!().arg("--tag").arg("--text").fails_with_code(1); +} + +#[test] +#[cfg_attr(windows, ignore = "Disabled on windows")] +fn test_with_escape_filename() { + let scene = TestScenario::new(util_name!()); + + let at = &scene.fixtures; + let filename = "a\nb"; + at.touch(filename); + let result = scene.ccmd("md5sum").arg("--text").arg(filename).succeeds(); + let stdout = result.stdout_str(); + println!("stdout {stdout}"); + assert!(stdout.starts_with('\\')); + assert!(stdout.trim().ends_with("a\\nb")); +} + +#[test] +#[cfg_attr(windows, ignore = "Disabled on windows")] +fn test_with_escape_filename_zero_text() { + let scene = TestScenario::new(util_name!()); + + let at = &scene.fixtures; + let filename = "a\nb"; + at.touch(filename); + let result = scene + .ccmd("md5sum") + .arg("--text") + .arg("--zero") + .arg(filename) + .succeeds(); + let stdout = result.stdout_str(); + println!("stdout {stdout}"); + assert!(!stdout.starts_with('\\')); + assert!(stdout.contains("a\nb")); +} + +#[test] +fn test_check_empty_line() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.touch("f"); + at.write( + "in.md5", + "d41d8cd98f00b204e9800998ecf8427e f\n\nd41d8cd98f00b204e9800998ecf8427e f\ninvalid\n\n", + ); + scene + .ccmd("md5sum") + .arg("--check") + .arg(at.subdir.join("in.md5")) + .succeeds() + .stderr_contains("WARNING: 1 line is improperly formatted"); +} + +#[test] +#[cfg_attr(windows, ignore = "Disabled on windows")] +fn test_check_with_escape_filename() { + let scene = TestScenario::new(util_name!()); + + let at = &scene.fixtures; + + let filename = "a\nb"; + at.touch(filename); + let result = scene.ccmd("md5sum").arg("--tag").arg(filename).succeeds(); + let stdout = result.stdout_str(); + println!("stdout {stdout}"); + assert!(stdout.starts_with("\\MD5")); + assert!(stdout.contains("a\\nb")); + at.write("check.md5", stdout); + let result = scene + .ccmd("md5sum") + .arg("--strict") + .arg("-c") + .arg("check.md5") + .succeeds(); + result.stdout_is("\\a\\nb: OK\n"); +} + +#[test] +fn test_check_strict_error() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.touch("f"); + at.write( + "in.md5", + "ERR\nERR\nd41d8cd98f00b204e9800998ecf8427e f\nERR\n", + ); + scene + .ccmd("md5sum") + .arg("--check") + .arg("--strict") + .arg(at.subdir.join("in.md5")) + .fails() + .stderr_contains("WARNING: 3 lines are improperly formatted"); +} + +#[test] +fn test_check_warn() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.touch("f"); + at.write( + "in.md5", + "d41d8cd98f00b204e9800998ecf8427e f\nd41d8cd98f00b204e9800998ecf8427e f\ninvalid\n", + ); + scene + .ccmd("md5sum") + .arg("--check") + .arg("--warn") + .arg(at.subdir.join("in.md5")) + .succeeds() + .stderr_contains("in.md5: 3: improperly formatted MD5 checksum line") + .stderr_contains("WARNING: 1 line is improperly formatted"); + + // with strict, we should fail the execution + scene + .ccmd("md5sum") + .arg("--check") + .arg("--strict") + .arg(at.subdir.join("in.md5")) + .fails(); +} + +#[test] +fn test_check_status() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.touch("f"); + at.write("in.md5", "MD5(f)= d41d8cd98f00b204e9800998ecf8427f\n"); + scene + .ccmd("md5sum") + .arg("--check") + .arg("--status") + .arg(at.subdir.join("in.md5")) + .fails() + .no_output(); +} + +#[test] +fn test_check_status_code() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.touch("f"); + at.write("in.md5", "d41d8cd98f00b204e9800998ecf8427f f\n"); + scene + .ccmd("md5sum") + .arg("--check") + .arg("--status") + .arg(at.subdir.join("in.md5")) + .fails() + .stderr_is("") + .stdout_is(""); +} + +#[test] +fn test_sha1_with_md5sum_should_fail() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.touch("f"); + at.write("f.sha1", "SHA1 (f) = d41d8cd98f00b204e9800998ecf8427e\n"); + scene + .ccmd("md5sum") + .arg("--check") + .arg(at.subdir.join("f.sha1")) + .fails() + .stderr_contains("f.sha1: no properly formatted checksum lines found") + .stderr_does_not_contain("WARNING: 1 line is improperly formatted"); +} + +#[test] +// Disabled on Windows because of the "*" +#[cfg_attr(windows, ignore = "Disabled on windows")] +fn test_check_one_two_space_star() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.touch("empty"); + + // with one space, the "*" is removed + at.write("in.md5", "d41d8cd98f00b204e9800998ecf8427e *empty\n"); + + scene + .ccmd("md5sum") + .arg("--check") + .arg(at.subdir.join("in.md5")) + .succeeds() + .stdout_is("empty: OK\n"); + + // with two spaces, the "*" is not removed + at.write("in.md5", "d41d8cd98f00b204e9800998ecf8427e *empty\n"); + // First should fail as *empty doesn't exit + scene + .ccmd("md5sum") + .arg("--check") + .arg(at.subdir.join("in.md5")) + .fails() + .stdout_is("*empty: FAILED open or read\n"); + + at.touch("*empty"); + // Should pass as we have the file + scene + .ccmd("md5sum") + .arg("--check") + .arg(at.subdir.join("in.md5")) + .succeeds() + .stdout_is("*empty: OK\n"); +} + +#[test] +// Disabled on Windows because of the "*" +#[cfg_attr(windows, ignore = "Disabled on windows")] +fn test_check_space_star_or_not() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.touch("a"); + at.touch("*c"); + + // with one space, the "*" is removed + at.write( + "in.md5", + "d41d8cd98f00b204e9800998ecf8427e *c\n + d41d8cd98f00b204e9800998ecf8427e a\n", + ); + + scene + .ccmd("md5sum") + .arg("--check") + .arg(at.subdir.join("in.md5")) + .fails() + .stdout_contains("c: FAILED") + .stdout_does_not_contain("a: FAILED") + .stderr_contains("WARNING: 1 line is improperly formatted"); + + at.write( + "in.md5", + "d41d8cd98f00b204e9800998ecf8427e a\n + d41d8cd98f00b204e9800998ecf8427e *c\n", + ); + + // First should fail as *empty doesn't exit + scene + .ccmd("md5sum") + .arg("--check") + .arg(at.subdir.join("in.md5")) + .succeeds() + .stdout_contains("a: OK") + .stderr_contains("WARNING: 1 line is improperly formatted"); +} + +#[test] +fn test_check_no_backslash_no_space() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.touch("f"); + at.write("in.md5", "MD5(f)= d41d8cd98f00b204e9800998ecf8427e\n"); + scene + .ccmd("md5sum") + .arg("--check") + .arg(at.subdir.join("in.md5")) + .succeeds() + .stdout_is("f: OK\n"); +} + +#[test] +fn test_incomplete_format() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.touch("f"); + at.write("in.md5", "MD5 (\n"); + scene + .ccmd("md5sum") + .arg("--check") + .arg(at.subdir.join("in.md5")) + .fails() + .stderr_contains("no properly formatted checksum lines found"); +} + +#[test] +fn test_start_error() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.touch("f"); + at.write("in.md5", "ERR\nd41d8cd98f00b204e9800998ecf8427e f\n"); + scene + .ccmd("md5sum") + .arg("--check") + .arg("--strict") + .arg(at.subdir.join("in.md5")) + .fails() + .stdout_is("f: OK\n") + .stderr_contains("WARNING: 1 line is improperly formatted"); +} + +#[test] +fn test_check_check_ignore_no_file() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.touch("f"); + at.write("in.md5", "d41d8cd98f00b204e9800998ecf8427f missing\n"); + scene + .ccmd("md5sum") + .arg("--check") + .arg("--ignore-missing") + .arg(at.subdir.join("in.md5")) + .fails() + .stderr_contains("in.md5: no file was verified"); +} + +#[test] +fn test_check_directory_error() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.mkdir("d"); + at.write("in.md5", "d41d8cd98f00b204e9800998ecf8427f d\n"); + #[cfg(not(windows))] + let err_msg = "md5sum: d: Is a directory\n"; + #[cfg(windows)] + let err_msg = "md5sum: d: Permission denied\n"; + scene + .ccmd("md5sum") + .arg("--check") + .arg(at.subdir.join("in.md5")) + .fails() + .stderr_contains(err_msg); +} + +#[test] +#[cfg(not(windows))] +fn test_continue_after_directory_error() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.mkdir("d"); + at.touch("file"); + at.touch("no_read_perms"); + at.set_mode("no_read_perms", 200); + + let (out, err_msg) = ( + "d41d8cd98f00b204e9800998ecf8427e file\n", + [ + "md5sum: d: Is a directory", + "md5sum: dne: No such file or directory", + "md5sum: no_read_perms: Permission denied\n", + ] + .join("\n"), + ); + + scene + .ccmd("md5sum") + .arg("d") + .arg("dne") + .arg("no_read_perms") + .arg("file") + .fails() + .stdout_is(out) + .stderr_is(err_msg); +} + +#[test] +fn test_check_quiet() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.touch("f"); + at.write("in.md5", "d41d8cd98f00b204e9800998ecf8427e f\n"); + scene + .ccmd("md5sum") + .arg("--quiet") + .arg("--check") + .arg(at.subdir.join("in.md5")) + .succeeds() + .no_output(); + + // incorrect md5 + at.write("in.md5", "d41d8cd98f00b204e9800998ecf8427f f\n"); + scene + .ccmd("md5sum") + .arg("--quiet") + .arg("--check") + .arg(at.subdir.join("in.md5")) + .fails() + .stdout_contains("f: FAILED") + .stderr_contains("WARNING: 1 computed checksum did NOT match"); + + scene + .ccmd("md5sum") + .arg("--quiet") + .arg(at.subdir.join("in.md5")) + .fails() + .stderr_contains("md5sum: the --quiet option is meaningful only when verifying checksums"); + scene + .ccmd("md5sum") + .arg("--strict") + .arg(at.subdir.join("in.md5")) + .fails() + .stderr_contains("md5sum: the --strict option is meaningful only when verifying checksums"); +} + +#[test] +fn test_star_to_start() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.touch("f"); + at.write("in.md5", "d41d8cd98f00b204e9800998ecf8427e *f\n"); + scene + .ccmd("md5sum") + .arg("--check") + .arg(at.subdir.join("in.md5")) + .succeeds() + .stdout_only("f: OK\n"); +} + +#[test] +fn test_check_md5_comment_line() { + // A comment in a checksum file shall be discarded unnoticed. + + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.write("foo", "foo-content\n"); + at.write( + "MD5SUM", + "\ + # This is a comment\n\ + 8411029f3f5b781026a93db636aca721 foo\n\ + # next comment is empty\n#", + ); + + scene + .ccmd("md5sum") + .arg("--check") + .arg("MD5SUM") + .succeeds() + .stdout_contains("foo: OK") + .no_stderr(); +} + +#[test] +fn test_check_md5_comment_only() { + // A file only filled with comments is equivalent to an empty file, + // and therefore produces an error. + + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.write("foo", "foo-content\n"); + at.write("MD5SUM", "# This is a comment\n"); + + scene + .ccmd("md5sum") + .arg("--check") + .arg("MD5SUM") + .fails() + .stderr_contains("no properly formatted checksum lines found"); +} + +#[test] +fn test_check_md5_comment_leading_space() { + // A file only filled with comments is equivalent to an empty file, + // and therefore produces an error. + + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.write("foo", "foo-content\n"); + at.write( + "MD5SUM", + " # This is a comment\n\ + 8411029f3f5b781026a93db636aca721 foo\n", + ); + + scene + .ccmd("md5sum") + .arg("--check") + .arg("MD5SUM") + .succeeds() + .stdout_contains("foo: OK") + .stderr_contains("WARNING: 1 line is improperly formatted"); +} + +#[test] +fn test_help_shows_correct_utility_name() { + // Test md5sum + new_ucmd!() + .arg("--help") + .succeeds() + .stdout_contains("Usage: md5sum") + .stdout_does_not_contain("Usage: hashsum"); +} diff --git a/tests/fixtures/md5sum/input.txt b/tests/fixtures/md5sum/input.txt new file mode 100644 index 00000000000..8c01d89ae06 --- /dev/null +++ b/tests/fixtures/md5sum/input.txt @@ -0,0 +1 @@ +hello, world \ No newline at end of file diff --git a/tests/fixtures/hashsum/md5.checkfile b/tests/fixtures/md5sum/md5.checkfile similarity index 100% rename from tests/fixtures/hashsum/md5.checkfile rename to tests/fixtures/md5sum/md5.checkfile diff --git a/tests/fixtures/hashsum/md5.expected b/tests/fixtures/md5sum/md5.expected similarity index 100% rename from tests/fixtures/hashsum/md5.expected rename to tests/fixtures/md5sum/md5.expected diff --git a/tests/tests.rs b/tests/tests.rs index 9ffdfd4a312..1b2a2131b20 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -68,6 +68,10 @@ mod test_cksum; #[path = "by-util/test_comm.rs"] mod test_comm; +#[cfg(feature = "md5sum")] +#[path = "by-util/test_md5sum.rs"] +mod test_md5sum; + #[cfg(feature = "cp")] #[path = "by-util/test_cp.rs"] mod test_cp; From d22c7e14ece8791dec463e32d39d333e8d882a3d Mon Sep 17 00:00:00 2001 From: Dorian Peron Date: Fri, 9 Jan 2026 15:43:12 +0100 Subject: [PATCH 05/10] b2sum: introduce standalone binary --- Cargo.lock | 13 + Cargo.toml | 2 + GNUmakefile | 1 - build.rs | 1 - src/common/validation.rs | 2 +- src/uu/b2sum/Cargo.toml | 38 +++ src/uu/b2sum/LICENSE | 1 + src/uu/b2sum/locales/en-US.ftl | 2 + src/uu/b2sum/locales/fr-FR.ftl | 2 + src/uu/b2sum/src/b2sum.rs | 29 ++ src/uu/b2sum/src/main.rs | 1 + src/uucore/src/lib/lib.rs | 2 +- src/uucore/src/lib/mods/locale.rs | 2 +- tests/by-util/test_b2sum.rs | 299 ++++++++++++++++++ tests/by-util/test_hashsum.rs | 11 +- .../{hashsum => b2sum}/b2sum.checkfile | 0 .../{hashsum => b2sum}/b2sum.expected | 0 tests/fixtures/b2sum/input.txt | 1 + tests/tests.rs | 4 + 19 files changed, 405 insertions(+), 6 deletions(-) create mode 100644 src/uu/b2sum/Cargo.toml create mode 120000 src/uu/b2sum/LICENSE create mode 100644 src/uu/b2sum/locales/en-US.ftl create mode 100644 src/uu/b2sum/locales/fr-FR.ftl create mode 100644 src/uu/b2sum/src/b2sum.rs create mode 100644 src/uu/b2sum/src/main.rs create mode 100644 tests/by-util/test_b2sum.rs rename tests/fixtures/{hashsum => b2sum}/b2sum.checkfile (100%) rename tests/fixtures/{hashsum => b2sum}/b2sum.expected (100%) create mode 100644 tests/fixtures/b2sum/input.txt diff --git a/Cargo.lock b/Cargo.lock index 49d209ab0a1..a1b35d5850a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -556,6 +556,7 @@ dependencies = [ "unicode-width 0.2.2", "unindent", "uu_arch", + "uu_b2sum", "uu_base32", "uu_base64", "uu_basename", @@ -3172,6 +3173,18 @@ dependencies = [ "uucore", ] +[[package]] +name = "uu_b2sum" +version = "0.6.0" +dependencies = [ + "clap", + "codspeed-divan-compat", + "fluent", + "tempfile", + "uu_checksum_common", + "uucore", +] + [[package]] name = "uu_base32" version = "0.6.0" diff --git a/Cargo.toml b/Cargo.toml index f6354a64440..429959b284a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,6 +86,7 @@ feat_common_core = [ "basenc", "cat", "cksum", + "b2sum", "md5sum", "comm", "cp", @@ -438,6 +439,7 @@ chmod = { optional = true, version = "0.6.0", package = "uu_chmod", path = "src/ chown = { optional = true, version = "0.6.0", package = "uu_chown", path = "src/uu/chown" } chroot = { optional = true, version = "0.6.0", package = "uu_chroot", path = "src/uu/chroot" } cksum = { optional = true, version = "0.6.0", package = "uu_cksum", path = "src/uu/cksum" } +b2sum = { optional = true, version = "0.6.0", package = "uu_b2sum", path = "src/uu/b2sum" } md5sum = { optional = true, version = "0.6.0", package = "uu_md5sum", path = "src/uu/md5sum" } comm = { optional = true, version = "0.6.0", package = "uu_comm", path = "src/uu/comm" } cp = { optional = true, version = "0.6.0", package = "uu_cp", path = "src/uu/cp" } diff --git a/GNUmakefile b/GNUmakefile index 243e038c433..b28ef8903c9 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -96,7 +96,6 @@ SELINUX_PROGS := \ runcon HASHSUM_PROGS := \ - b2sum \ sha1sum \ sha224sum \ sha256sum \ diff --git a/build.rs b/build.rs index 989f7630d2b..39a26a807f1 100644 --- a/build.rs +++ b/build.rs @@ -96,7 +96,6 @@ pub fn main() { phf_map.entry("sha256sum", map_value.clone()); phf_map.entry("sha384sum", map_value.clone()); phf_map.entry("sha512sum", map_value.clone()); - phf_map.entry("b2sum", map_value.clone()); } _ => { phf_map.entry(krate, map_value.clone()); diff --git a/src/common/validation.rs b/src/common/validation.rs index d332d304f39..773505bedc0 100644 --- a/src/common/validation.rs +++ b/src/common/validation.rs @@ -51,7 +51,7 @@ fn get_canonical_util_name(util_name: &str) -> &str { "[" => "test", // hashsum aliases - all these hash commands are aliases for hashsum - "sha1sum" | "sha224sum" | "sha256sum" | "sha384sum" | "sha512sum" | "b2sum" => "hashsum", + "sha1sum" | "sha224sum" | "sha256sum" | "sha384sum" | "sha512sum" => "hashsum", "dir" => "ls", // dir is an alias for ls diff --git a/src/uu/b2sum/Cargo.toml b/src/uu/b2sum/Cargo.toml new file mode 100644 index 00000000000..61b0b702aef --- /dev/null +++ b/src/uu/b2sum/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "uu_b2sum" +description = "b2sum ~ (uutils) Print or check the BLAKE2b checksums" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/b2sum" +version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +keywords.workspace = true +categories.workspace = true +edition.workspace = true +readme.workspace = true + +[lints] +workspace = true + +[lib] +path = "src/b2sum.rs" + +[dependencies] +clap = { workspace = true } +uu_checksum_common = { workspace = true } +uucore = { workspace = true, features = [ + "checksum", + "encoding", + "sum", + "hardware", +] } +fluent = { workspace = true } + +[dev-dependencies] +divan = { workspace = true } +tempfile = { workspace = true } +uucore = { workspace = true, features = ["benchmark"] } + +[[bin]] +name = "b2sum" +path = "src/main.rs" diff --git a/src/uu/b2sum/LICENSE b/src/uu/b2sum/LICENSE new file mode 120000 index 00000000000..5853aaea53b --- /dev/null +++ b/src/uu/b2sum/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/b2sum/locales/en-US.ftl b/src/uu/b2sum/locales/en-US.ftl new file mode 100644 index 00000000000..a5ab9ea7ebf --- /dev/null +++ b/src/uu/b2sum/locales/en-US.ftl @@ -0,0 +1,2 @@ +b2sum-about = Print or check the BLAKE2b checksums +b2sum-usage = b2sum [OPTIONS] [FILE]... diff --git a/src/uu/b2sum/locales/fr-FR.ftl b/src/uu/b2sum/locales/fr-FR.ftl new file mode 100644 index 00000000000..7cb93e5d8d9 --- /dev/null +++ b/src/uu/b2sum/locales/fr-FR.ftl @@ -0,0 +1,2 @@ +b2sum-about = Afficher le BLAKE2b et la taille de chaque fichier +b2sum-usage = b2sum [OPTION]... [FICHIER]... diff --git a/src/uu/b2sum/src/b2sum.rs b/src/uu/b2sum/src/b2sum.rs new file mode 100644 index 00000000000..502bd8b53be --- /dev/null +++ b/src/uu/b2sum/src/b2sum.rs @@ -0,0 +1,29 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +// spell-checker:ignore (ToDO) algo + +use clap::Command; + +use uu_checksum_common::{standalone_checksum_app_with_length, standalone_with_length_main}; + +use uucore::checksum::{AlgoKind, calculate_blake2b_length_str}; +use uucore::error::UResult; +use uucore::translate; + +#[uucore::main] +pub fn uumain(args: impl uucore::Args) -> UResult<()> { + standalone_with_length_main( + AlgoKind::Blake2b, + uu_app(), + args, + calculate_blake2b_length_str, + ) +} + +#[inline] +pub fn uu_app() -> Command { + standalone_checksum_app_with_length(translate!("b2sum-about"), translate!("b2sum-usage")) +} diff --git a/src/uu/b2sum/src/main.rs b/src/uu/b2sum/src/main.rs new file mode 100644 index 00000000000..422fa2fe709 --- /dev/null +++ b/src/uu/b2sum/src/main.rs @@ -0,0 +1 @@ +uucore::bin!(uu_b2sum); diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index effefbc0646..d793867c5a8 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -173,7 +173,7 @@ pub fn get_canonical_util_name(util_name: &str) -> &str { "[" => "test", // hashsum aliases - all these hash commands are aliases for hashsum - "sha1sum" | "sha224sum" | "sha256sum" | "sha384sum" | "sha512sum" | "b2sum" => "hashsum", + "sha1sum" | "sha224sum" | "sha256sum" | "sha384sum" | "sha512sum" => "hashsum", "dir" => "ls", // dir is an alias for ls diff --git a/src/uucore/src/lib/mods/locale.rs b/src/uucore/src/lib/mods/locale.rs index 19b9be9f10e..6036501f4be 100644 --- a/src/uucore/src/lib/mods/locale.rs +++ b/src/uucore/src/lib/mods/locale.rs @@ -157,7 +157,7 @@ fn create_bundle( try_add_resource_from(get_locales_dir(util_name).ok()); // checksum binaries also require fluent files from the checksum_common crate - if ["cksum", "md5sum"].contains(&util_name) { + if ["cksum", "b2sum", "md5sum"].contains(&util_name) { try_add_resource_from(get_locales_dir("checksum_common").ok()); } diff --git a/tests/by-util/test_b2sum.rs b/tests/by-util/test_b2sum.rs new file mode 100644 index 00000000000..30e2c46ca35 --- /dev/null +++ b/tests/by-util/test_b2sum.rs @@ -0,0 +1,299 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +use rstest::rstest; + +use uutests::new_ucmd; +use uutests::util::TestScenario; +use uutests::util_name; +// spell-checker:ignore checkfile, testf, ntestf +macro_rules! get_hash( + ($str:expr) => ( + $str.split(' ').collect::>()[0] + ); +); + +macro_rules! test_digest_with_len { + ($id:ident, $size:expr) => { + mod $id { + use uutests::util::*; + use uutests::util_name; + static LENGTH_ARG: &'static str = concat!("--length=", stringify!($size)); + static EXPECTED_FILE: &'static str = concat!(stringify!($id), ".expected"); + static CHECK_FILE: &'static str = concat!(stringify!($id), ".checkfile"); + static INPUT_FILE: &'static str = "input.txt"; + + #[test] + fn test_single_file() { + let ts = TestScenario::new(util_name!()); + assert_eq!( + ts.fixtures.read(EXPECTED_FILE), + get_hash!( + ts.ucmd() + .arg(LENGTH_ARG) + .arg(INPUT_FILE) + .succeeds() + .no_stderr() + .stdout_str() + ) + ); + } + + #[test] + fn test_stdin() { + let ts = TestScenario::new(util_name!()); + assert_eq!( + ts.fixtures.read(EXPECTED_FILE), + get_hash!( + ts.ucmd() + .arg(LENGTH_ARG) + .pipe_in_fixture(INPUT_FILE) + .succeeds() + .no_stderr() + .stdout_str() + ) + ); + } + + #[test] + fn test_check() { + let ts = TestScenario::new(util_name!()); + println!("File content='{}'", ts.fixtures.read(INPUT_FILE)); + println!("Check file='{}'", ts.fixtures.read(CHECK_FILE)); + + ts.ucmd() + .args(&[LENGTH_ARG, "--check", CHECK_FILE]) + .succeeds() + .no_stderr() + .stdout_is("input.txt: OK\n"); + } + + #[test] + fn test_zero() { + let ts = TestScenario::new(util_name!()); + assert_eq!( + ts.fixtures.read(EXPECTED_FILE), + get_hash!( + ts.ucmd() + .arg(LENGTH_ARG) + .arg("--zero") + .arg(INPUT_FILE) + .succeeds() + .no_stderr() + .stdout_str() + ) + ); + } + + #[test] + fn test_missing_file() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + + at.write("a", "file1\n"); + at.write("c", "file3\n"); + + ts.ucmd() + .args(&[LENGTH_ARG, "a", "b", "c"]) + .fails() + .stdout_contains("a\n") + .stdout_contains("c\n") + .stderr_contains("b: No such file or directory"); + } + } + }; +} + +test_digest_with_len! {b2sum, 512} + +#[test] +fn test_check_b2sum_length_option_0() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.write("testf", "foobar\n"); + at.write("testf.b2sum", "9e2bf63e933e610efee4a8d6cd4a9387e80860edee97e27db3b37a828d226ab1eb92a9cdd8ca9ca67a753edaf8bd89a0558496f67a30af6f766943839acf0110 testf\n"); + + scene + .ccmd("b2sum") + .arg("--length=0") + .arg("-c") + .arg(at.subdir.join("testf.b2sum")) + .succeeds() + .stdout_only("testf: OK\n"); +} + +#[test] +fn test_check_b2sum_length_duplicate() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.write("testf", "foobar\n"); + + scene + .ccmd("b2sum") + .arg("--length=123") + .arg("--length=128") + .arg("testf") + .succeeds() + .stdout_contains("d6d45901dec53e65d2b55fb6e2ab67b0"); +} + +#[test] +fn test_check_b2sum_length_option_8() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.write("testf", "foobar\n"); + at.write("testf.b2sum", "6a testf\n"); + + scene + .ccmd("b2sum") + .arg("--length=8") + .arg("-c") + .arg(at.subdir.join("testf.b2sum")) + .succeeds() + .stdout_only("testf: OK\n"); +} + +#[test] +fn test_invalid_b2sum_length_option_not_multiple_of_8() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.write("testf", "foobar\n"); + + scene + .ccmd("b2sum") + .arg("--length=9") + .arg(at.subdir.join("testf")) + .fails_with_code(1) + .stderr_contains("b2sum: invalid length: '9'") + .stderr_contains("b2sum: length is not a multiple of 8"); +} + +#[rstest] +#[case("513")] +#[case("1024")] +#[case("18446744073709552000")] +fn test_invalid_b2sum_length_option_too_large(#[case] len: &str) { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.write("testf", "foobar\n"); + + scene + .ccmd("b2sum") + .arg("--length") + .arg(len) + .arg(at.subdir.join("testf")) + .fails_with_code(1) + .no_stdout() + .stderr_contains(format!("b2sum: invalid length: '{len}'")) + .stderr_contains("b2sum: maximum digest length for 'BLAKE2b' is 512 bits"); +} + +#[test] +fn test_check_b2sum_tag_output() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.touch("f"); + + scene + .ccmd("b2sum") + .arg("--length=0") + .arg("--tag") + .arg("f") + .succeeds() + .stdout_only("BLAKE2b (f) = 786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce\n"); + + scene + .ccmd("b2sum") + .arg("--length=128") + .arg("--tag") + .arg("f") + .succeeds() + .stdout_only("BLAKE2b-128 (f) = cae66941d9efbd404e4d88758ea67670\n"); +} + +#[test] +fn test_check_b2sum_verify() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.write("a", "a\n"); + + scene + .ccmd("b2sum") + .arg("--tag") + .arg("a") + .succeeds() + .stdout_only("BLAKE2b (a) = bedfbb90d858c2d67b7ee8f7523be3d3b54004ef9e4f02f2ad79a1d05bfdfe49b81e3c92ebf99b504102b6bf003fa342587f5b3124c205f55204e8c4b4ce7d7c\n"); + + scene + .ccmd("b2sum") + .arg("--tag") + .arg("-l") + .arg("128") + .arg("a") + .succeeds() + .stdout_only("BLAKE2b-128 (a) = b93e0fc7bb21633c08bba07c5e71dc00\n"); +} + +#[test] +fn test_invalid_arg() { + new_ucmd!().arg("--definitely-invalid").fails_with_code(1); +} + +#[test] +fn test_check_b2sum_strict_check() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + at.touch("f"); + + let checksums = [ + "2e f\n", + "e4a6a0577479b2b4 f\n", + "cae66941d9efbd404e4d88758ea67670 f\n", + "246c0442cd564aced8145b8b60f1370aa7 f\n", + "0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8 f\n", + "4ded8c5fc8b12f3273f877ca585a44ad6503249a2b345d6d9c0e67d85bcb700db4178c0303e93b8f4ad758b8e2c9fd8b3d0c28e585f1928334bb77d36782e8 f\n", + "786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce f\n", + ]; + + at.write("ck", &checksums.join("")); + + let output = "f: OK\n".to_string().repeat(checksums.len()); + + scene + .ccmd("b2sum") + .arg("-c") + .arg(at.subdir.join("ck")) + .succeeds() + .stdout_only(&output); + + scene + .ccmd("b2sum") + .arg("--strict") + .arg("-c") + .arg(at.subdir.join("ck")) + .succeeds() + .stdout_only(&output); +} + +#[test] +fn test_help_shows_correct_utility_name() { + // Test that help output shows the actual utility name instead of "hashsum" + let scene = TestScenario::new(util_name!()); + + // Test b2sum + scene + .ccmd("b2sum") + .arg("--help") + .succeeds() + .stdout_contains("Usage: b2sum") + .stdout_does_not_contain("Usage: hashsum"); +} diff --git a/tests/by-util/test_hashsum.rs b/tests/by-util/test_hashsum.rs index 2022f81b8c0..26125dc5e1e 100644 --- a/tests/by-util/test_hashsum.rs +++ b/tests/by-util/test_hashsum.rs @@ -214,7 +214,6 @@ test_digest_with_len! {sha3_224, sha3, 224} test_digest_with_len! {sha3_256, sha3, 256} test_digest_with_len! {sha3_384, sha3, 384} test_digest_with_len! {sha3_512, sha3, 512} -test_digest_with_len! {b2sum, b2sum, 512} #[test] fn test_check_sha1() { @@ -271,6 +270,7 @@ fn test_check_md5_ignore_missing() { .stderr_contains("the --ignore-missing option is meaningful only when verifying checksums"); } +#[ignore = "moved to standalone"] #[test] fn test_check_b2sum_length_option_0() { let scene = TestScenario::new(util_name!()); @@ -288,6 +288,7 @@ fn test_check_b2sum_length_option_0() { .stdout_only("testf: OK\n"); } +#[ignore = "moved to standalone"] #[test] fn test_check_b2sum_length_duplicate() { let scene = TestScenario::new(util_name!()); @@ -304,6 +305,7 @@ fn test_check_b2sum_length_duplicate() { .stdout_contains("d6d45901dec53e65d2b55fb6e2ab67b0"); } +#[ignore = "moved to standalone"] #[test] fn test_check_b2sum_length_option_8() { let scene = TestScenario::new(util_name!()); @@ -321,6 +323,7 @@ fn test_check_b2sum_length_option_8() { .stdout_only("testf: OK\n"); } +#[ignore = "moved to standalone"] #[test] fn test_invalid_b2sum_length_option_not_multiple_of_8() { let scene = TestScenario::new(util_name!()); @@ -338,8 +341,11 @@ fn test_invalid_b2sum_length_option_not_multiple_of_8() { } #[rstest] +#[ignore = "moved to standalone"] #[case("513")] +#[ignore = "moved to standalone"] #[case("1024")] +#[ignore = "moved to standalone"] #[case("18446744073709552000")] fn test_invalid_b2sum_length_option_too_large(#[case] len: &str) { let scene = TestScenario::new(util_name!()); @@ -358,6 +364,7 @@ fn test_invalid_b2sum_length_option_too_large(#[case] len: &str) { .stderr_contains("b2sum: maximum digest length for 'BLAKE2b' is 512 bits"); } +#[ignore = "moved to standalone"] #[test] fn test_check_b2sum_tag_output() { let scene = TestScenario::new(util_name!()); @@ -382,6 +389,7 @@ fn test_check_b2sum_tag_output() { .stdout_only("BLAKE2b-128 (f) = cae66941d9efbd404e4d88758ea67670\n"); } +#[ignore = "moved to standalone"] #[test] fn test_check_b2sum_verify() { let scene = TestScenario::new(util_name!()); @@ -1068,6 +1076,7 @@ fn test_star_to_start() { .stdout_only("f: OK\n"); } +#[ignore = "moved to standalone"] #[test] fn test_check_b2sum_strict_check() { let scene = TestScenario::new(util_name!()); diff --git a/tests/fixtures/hashsum/b2sum.checkfile b/tests/fixtures/b2sum/b2sum.checkfile similarity index 100% rename from tests/fixtures/hashsum/b2sum.checkfile rename to tests/fixtures/b2sum/b2sum.checkfile diff --git a/tests/fixtures/hashsum/b2sum.expected b/tests/fixtures/b2sum/b2sum.expected similarity index 100% rename from tests/fixtures/hashsum/b2sum.expected rename to tests/fixtures/b2sum/b2sum.expected diff --git a/tests/fixtures/b2sum/input.txt b/tests/fixtures/b2sum/input.txt new file mode 100644 index 00000000000..8c01d89ae06 --- /dev/null +++ b/tests/fixtures/b2sum/input.txt @@ -0,0 +1 @@ +hello, world \ No newline at end of file diff --git a/tests/tests.rs b/tests/tests.rs index 1b2a2131b20..78225ac97f3 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -68,6 +68,10 @@ mod test_cksum; #[path = "by-util/test_comm.rs"] mod test_comm; +#[cfg(feature = "b2sum")] +#[path = "by-util/test_b2sum.rs"] +mod test_b2sum; + #[cfg(feature = "md5sum")] #[path = "by-util/test_md5sum.rs"] mod test_md5sum; From 6aa5b1fe66841c337060fccb48e9c87299ca4450 Mon Sep 17 00:00:00 2001 From: Dorian Peron Date: Fri, 9 Jan 2026 16:40:17 +0100 Subject: [PATCH 06/10] sha1sum: introduce standalone binary --- Cargo.lock | 13 ++ Cargo.toml | 2 + GNUmakefile | 1 - build.rs | 1 - src/common/validation.rs | 2 +- src/uu/sha1sum/Cargo.toml | 38 ++++ src/uu/sha1sum/LICENSE | 1 + src/uu/sha1sum/locales/en-US.ftl | 2 + src/uu/sha1sum/locales/fr-FR.ftl | 2 + src/uu/sha1sum/src/main.rs | 1 + src/uu/sha1sum/src/sha1sum.rs | 1 + src/uucore/src/lib/lib.rs | 2 +- src/uucore/src/lib/mods/locale.rs | 2 +- tests/by-util/test_hashsum.rs | 3 +- tests/by-util/test_sha1sum.rs | 165 ++++++++++++++++++ tests/fixtures/sha1sum/input.txt | 1 + .../{hashsum => sha1sum}/sha1.checkfile | 0 .../{hashsum => sha1sum}/sha1.expected | 0 tests/tests.rs | 4 + 19 files changed, 235 insertions(+), 6 deletions(-) create mode 100644 src/uu/sha1sum/Cargo.toml create mode 120000 src/uu/sha1sum/LICENSE create mode 100644 src/uu/sha1sum/locales/en-US.ftl create mode 100644 src/uu/sha1sum/locales/fr-FR.ftl create mode 100644 src/uu/sha1sum/src/main.rs create mode 100644 src/uu/sha1sum/src/sha1sum.rs create mode 100644 tests/by-util/test_sha1sum.rs create mode 100644 tests/fixtures/sha1sum/input.txt rename tests/fixtures/{hashsum => sha1sum}/sha1.checkfile (100%) rename tests/fixtures/{hashsum => sha1sum}/sha1.expected (100%) diff --git a/Cargo.lock b/Cargo.lock index a1b35d5850a..8269a9be2ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -627,6 +627,7 @@ dependencies = [ "uu_rmdir", "uu_runcon", "uu_seq", + "uu_sha1sum", "uu_shred", "uu_shuf", "uu_sleep", @@ -3984,6 +3985,18 @@ dependencies = [ "uucore", ] +[[package]] +name = "uu_sha1sum" +version = "0.6.0" +dependencies = [ + "clap", + "codspeed-divan-compat", + "fluent", + "tempfile", + "uu_checksum_common", + "uucore", +] + [[package]] name = "uu_shred" version = "0.6.0" diff --git a/Cargo.toml b/Cargo.toml index 429959b284a..d2e72e7ac1b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -88,6 +88,7 @@ feat_common_core = [ "cksum", "b2sum", "md5sum", + "sha1sum", "comm", "cp", "csplit", @@ -441,6 +442,7 @@ chroot = { optional = true, version = "0.6.0", package = "uu_chroot", path = "sr cksum = { optional = true, version = "0.6.0", package = "uu_cksum", path = "src/uu/cksum" } b2sum = { optional = true, version = "0.6.0", package = "uu_b2sum", path = "src/uu/b2sum" } md5sum = { optional = true, version = "0.6.0", package = "uu_md5sum", path = "src/uu/md5sum" } +sha1sum = { optional = true, version = "0.6.0", package = "uu_sha1sum", path = "src/uu/sha1sum" } comm = { optional = true, version = "0.6.0", package = "uu_comm", path = "src/uu/comm" } cp = { optional = true, version = "0.6.0", package = "uu_cp", path = "src/uu/cp" } csplit = { optional = true, version = "0.6.0", package = "uu_csplit", path = "src/uu/csplit" } diff --git a/GNUmakefile b/GNUmakefile index b28ef8903c9..053441c74f7 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -96,7 +96,6 @@ SELINUX_PROGS := \ runcon HASHSUM_PROGS := \ - sha1sum \ sha224sum \ sha256sum \ sha384sum \ diff --git a/build.rs b/build.rs index 39a26a807f1..6b0e177fd87 100644 --- a/build.rs +++ b/build.rs @@ -91,7 +91,6 @@ pub fn main() { phf_map.entry(krate, format!("({krate}::uumain, {krate}::uu_app_custom)")); let map_value = format!("({krate}::uumain, {krate}::uu_app_common)"); - phf_map.entry("sha1sum", map_value.clone()); phf_map.entry("sha224sum", map_value.clone()); phf_map.entry("sha256sum", map_value.clone()); phf_map.entry("sha384sum", map_value.clone()); diff --git a/src/common/validation.rs b/src/common/validation.rs index 773505bedc0..ffadfd9178c 100644 --- a/src/common/validation.rs +++ b/src/common/validation.rs @@ -51,7 +51,7 @@ fn get_canonical_util_name(util_name: &str) -> &str { "[" => "test", // hashsum aliases - all these hash commands are aliases for hashsum - "sha1sum" | "sha224sum" | "sha256sum" | "sha384sum" | "sha512sum" => "hashsum", + "sha224sum" | "sha256sum" | "sha384sum" | "sha512sum" => "hashsum", "dir" => "ls", // dir is an alias for ls diff --git a/src/uu/sha1sum/Cargo.toml b/src/uu/sha1sum/Cargo.toml new file mode 100644 index 00000000000..001bddd69e3 --- /dev/null +++ b/src/uu/sha1sum/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "uu_sha1sum" +description = "sha1sum ~ (uutils) Print or check the SHA1 checksums" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/sha1sum" +version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +keywords.workspace = true +categories.workspace = true +edition.workspace = true +readme.workspace = true + +[lints] +workspace = true + +[lib] +path = "src/sha1sum.rs" + +[dependencies] +clap = { workspace = true } +uu_checksum_common = { workspace = true } +uucore = { workspace = true, features = [ + "checksum", + "encoding", + "sum", + "hardware", +] } +fluent = { workspace = true } + +[dev-dependencies] +divan = { workspace = true } +tempfile = { workspace = true } +uucore = { workspace = true, features = ["benchmark"] } + +[[bin]] +name = "sha1sum" +path = "src/main.rs" diff --git a/src/uu/sha1sum/LICENSE b/src/uu/sha1sum/LICENSE new file mode 120000 index 00000000000..5853aaea53b --- /dev/null +++ b/src/uu/sha1sum/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/sha1sum/locales/en-US.ftl b/src/uu/sha1sum/locales/en-US.ftl new file mode 100644 index 00000000000..378b8f8d456 --- /dev/null +++ b/src/uu/sha1sum/locales/en-US.ftl @@ -0,0 +1,2 @@ +sha1sum-about = Print or check the SHA1 checksums +sha1sum-usage = sha1sum [OPTIONS] [FILE]... diff --git a/src/uu/sha1sum/locales/fr-FR.ftl b/src/uu/sha1sum/locales/fr-FR.ftl new file mode 100644 index 00000000000..865bd8071b4 --- /dev/null +++ b/src/uu/sha1sum/locales/fr-FR.ftl @@ -0,0 +1,2 @@ +sha1sum-about = Afficher le SHA1 et la taille de chaque fichier +sha1sum-usage = sha1sum [OPTION]... [FICHIER]... diff --git a/src/uu/sha1sum/src/main.rs b/src/uu/sha1sum/src/main.rs new file mode 100644 index 00000000000..18d80cfdeac --- /dev/null +++ b/src/uu/sha1sum/src/main.rs @@ -0,0 +1 @@ +uucore::bin!(uu_sha1sum); diff --git a/src/uu/sha1sum/src/sha1sum.rs b/src/uu/sha1sum/src/sha1sum.rs new file mode 100644 index 00000000000..e715c79661e --- /dev/null +++ b/src/uu/sha1sum/src/sha1sum.rs @@ -0,0 +1 @@ +uu_checksum_common::declare_standalone!("sha1sum", uucore::checksum::AlgoKind::Sha1); diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index d793867c5a8..203a7ebb26a 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -173,7 +173,7 @@ pub fn get_canonical_util_name(util_name: &str) -> &str { "[" => "test", // hashsum aliases - all these hash commands are aliases for hashsum - "sha1sum" | "sha224sum" | "sha256sum" | "sha384sum" | "sha512sum" => "hashsum", + "sha224sum" | "sha256sum" | "sha384sum" | "sha512sum" => "hashsum", "dir" => "ls", // dir is an alias for ls diff --git a/src/uucore/src/lib/mods/locale.rs b/src/uucore/src/lib/mods/locale.rs index 6036501f4be..2b740eff892 100644 --- a/src/uucore/src/lib/mods/locale.rs +++ b/src/uucore/src/lib/mods/locale.rs @@ -157,7 +157,7 @@ fn create_bundle( try_add_resource_from(get_locales_dir(util_name).ok()); // checksum binaries also require fluent files from the checksum_common crate - if ["cksum", "b2sum", "md5sum"].contains(&util_name) { + if ["cksum", "b2sum", "md5sum", "sha1sum"].contains(&util_name) { try_add_resource_from(get_locales_dir("checksum_common").ok()); } diff --git a/tests/by-util/test_hashsum.rs b/tests/by-util/test_hashsum.rs index 26125dc5e1e..646724d66fc 100644 --- a/tests/by-util/test_hashsum.rs +++ b/tests/by-util/test_hashsum.rs @@ -201,7 +201,6 @@ macro_rules! test_digest_with_len { }; } -test_digest! {sha1, sha1} test_digest! {b3sum, b3sum} test_digest! {shake128, shake128} test_digest! {shake256, shake256} @@ -215,6 +214,7 @@ test_digest_with_len! {sha3_256, sha3, 256} test_digest_with_len! {sha3_384, sha3, 384} test_digest_with_len! {sha3_512, sha3, 512} +#[ignore = "moved to standalone"] #[test] fn test_check_sha1() { // To make sure that #3815 doesn't happen again @@ -414,6 +414,7 @@ fn test_check_b2sum_verify() { .stdout_only("BLAKE2b-128 (a) = b93e0fc7bb21633c08bba07c5e71dc00\n"); } +#[ignore = "moved to standalone"] #[test] fn test_check_file_not_found_warning() { let scene = TestScenario::new(util_name!()); diff --git a/tests/by-util/test_sha1sum.rs b/tests/by-util/test_sha1sum.rs new file mode 100644 index 00000000000..d0e7f6f3d71 --- /dev/null +++ b/tests/by-util/test_sha1sum.rs @@ -0,0 +1,165 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +use uutests::new_ucmd; +use uutests::util::TestScenario; +use uutests::util_name; +// spell-checker:ignore checkfile, testf, ntestf +macro_rules! get_hash( + ($str:expr) => ( + $str.split(' ').collect::>()[0] + ); +); + +macro_rules! test_digest { + ($id:ident) => { + mod $id { + use uutests::util::*; + use uutests::util_name; + static EXPECTED_FILE: &'static str = concat!(stringify!($id), ".expected"); + static CHECK_FILE: &'static str = concat!(stringify!($id), ".checkfile"); + static INPUT_FILE: &'static str = "input.txt"; + + #[test] + fn test_single_file() { + let ts = TestScenario::new(util_name!()); + assert_eq!( + ts.fixtures.read(EXPECTED_FILE), + get_hash!( + ts.ucmd() + .arg(INPUT_FILE) + .succeeds() + .no_stderr() + .stdout_str() + ) + ); + } + + #[test] + fn test_stdin() { + let ts = TestScenario::new(util_name!()); + assert_eq!( + ts.fixtures.read(EXPECTED_FILE), + get_hash!( + ts.ucmd() + .pipe_in_fixture(INPUT_FILE) + .succeeds() + .no_stderr() + .stdout_str() + ) + ); + } + + #[test] + fn test_check() { + let ts = TestScenario::new(util_name!()); + println!("File content='{}'", ts.fixtures.read(INPUT_FILE)); + println!("Check file='{}'", ts.fixtures.read(CHECK_FILE)); + + ts.ucmd() + .args(&["--check", CHECK_FILE]) + .succeeds() + .no_stderr() + .stdout_is("input.txt: OK\n"); + } + + #[test] + fn test_zero() { + let ts = TestScenario::new(util_name!()); + assert_eq!( + ts.fixtures.read(EXPECTED_FILE), + get_hash!( + ts.ucmd() + .arg("--zero") + .arg(INPUT_FILE) + .succeeds() + .no_stderr() + .stdout_str() + ) + ); + } + + #[test] + fn test_missing_file() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + + at.write("a", "file1\n"); + at.write("c", "file3\n"); + + ts.ucmd() + .args(&["a", "b", "c"]) + .fails() + .stdout_contains("a\n") + .stdout_contains("c\n") + .stderr_contains("b: No such file or directory"); + } + } + }; +} + +test_digest! {sha1} + +#[test] +fn test_check_sha1() { + // To make sure that #3815 doesn't happen again + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.write("testf", "foobar\n"); + at.write( + "testf.sha1", + "988881adc9fc3655077dc2d4d757d480b5ea0e11 testf\n", + ); + scene + .ccmd("sha1sum") + .arg("-c") + .arg(at.subdir.join("testf.sha1")) + .succeeds() + .stdout_is("testf: OK\n") + .stderr_is(""); +} + +#[test] +fn test_check_file_not_found_warning() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.write("testf", "foobar\n"); + at.write( + "testf.sha1", + "988881adc9fc3655077dc2d4d757d480b5ea0e11 testf\n", + ); + at.remove("testf"); + scene + .ccmd("sha1sum") + .arg("-c") + .arg(at.subdir.join("testf.sha1")) + .fails() + .stdout_is("testf: FAILED open or read\n") + .stderr_is("sha1sum: testf: No such file or directory\nsha1sum: WARNING: 1 listed file could not be read\n"); +} + +#[test] +fn test_invalid_arg() { + new_ucmd!().arg("--definitely-invalid").fails_with_code(1); +} + +#[test] +fn test_conflicting_arg() { + new_ucmd!().arg("--tag").arg("--check").fails_with_code(1); + new_ucmd!().arg("--tag").arg("--text").fails_with_code(1); +} + +#[test] +fn test_help_shows_correct_utility_name() { + // Test that help output shows the actual utility name instead of "hashsum" + + new_ucmd!() + .arg("--help") + .succeeds() + .stdout_contains("Usage: sha1sum") + .stdout_does_not_contain("Usage: hashsum"); +} diff --git a/tests/fixtures/sha1sum/input.txt b/tests/fixtures/sha1sum/input.txt new file mode 100644 index 00000000000..8c01d89ae06 --- /dev/null +++ b/tests/fixtures/sha1sum/input.txt @@ -0,0 +1 @@ +hello, world \ No newline at end of file diff --git a/tests/fixtures/hashsum/sha1.checkfile b/tests/fixtures/sha1sum/sha1.checkfile similarity index 100% rename from tests/fixtures/hashsum/sha1.checkfile rename to tests/fixtures/sha1sum/sha1.checkfile diff --git a/tests/fixtures/hashsum/sha1.expected b/tests/fixtures/sha1sum/sha1.expected similarity index 100% rename from tests/fixtures/hashsum/sha1.expected rename to tests/fixtures/sha1sum/sha1.expected diff --git a/tests/tests.rs b/tests/tests.rs index 78225ac97f3..76deb8c7dc2 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -76,6 +76,10 @@ mod test_b2sum; #[path = "by-util/test_md5sum.rs"] mod test_md5sum; +#[cfg(feature = "sha1sum")] +#[path = "by-util/test_sha1sum.rs"] +mod test_sha1sum; + #[cfg(feature = "cp")] #[path = "by-util/test_cp.rs"] mod test_cp; From e5f72019d24b737d4745029cded664647c0825da Mon Sep 17 00:00:00 2001 From: Dorian Peron Date: Fri, 9 Jan 2026 17:03:13 +0100 Subject: [PATCH 07/10] sha224sum: introduce standalone binary --- Cargo.lock | 13 ++ Cargo.toml | 2 + GNUmakefile | 1 - build.rs | 1 - src/common/validation.rs | 2 +- src/uu/sha224sum/Cargo.toml | 38 ++++++ src/uu/sha224sum/LICENSE | 1 + src/uu/sha224sum/locales/en-US.ftl | 2 + src/uu/sha224sum/locales/fr-FR.ftl | 2 + src/uu/sha224sum/src/main.rs | 1 + src/uu/sha224sum/src/sha224sum.rs | 1 + src/uucore/src/lib/lib.rs | 2 +- src/uucore/src/lib/mods/locale.rs | 2 +- tests/by-util/test_hashsum.rs | 1 - tests/by-util/test_sha224sum.rs | 120 ++++++++++++++++++ tests/fixtures/sha224sum/input.txt | 1 + .../{hashsum => sha224sum}/sha224.checkfile | 0 .../{hashsum => sha224sum}/sha224.expected | 0 tests/tests.rs | 4 + 19 files changed, 188 insertions(+), 6 deletions(-) create mode 100644 src/uu/sha224sum/Cargo.toml create mode 120000 src/uu/sha224sum/LICENSE create mode 100644 src/uu/sha224sum/locales/en-US.ftl create mode 100644 src/uu/sha224sum/locales/fr-FR.ftl create mode 100644 src/uu/sha224sum/src/main.rs create mode 100644 src/uu/sha224sum/src/sha224sum.rs create mode 100644 tests/by-util/test_sha224sum.rs create mode 100644 tests/fixtures/sha224sum/input.txt rename tests/fixtures/{hashsum => sha224sum}/sha224.checkfile (100%) rename tests/fixtures/{hashsum => sha224sum}/sha224.expected (100%) diff --git a/Cargo.lock b/Cargo.lock index 8269a9be2ad..8232e2e27a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -628,6 +628,7 @@ dependencies = [ "uu_runcon", "uu_seq", "uu_sha1sum", + "uu_sha224sum", "uu_shred", "uu_shuf", "uu_sleep", @@ -3997,6 +3998,18 @@ dependencies = [ "uucore", ] +[[package]] +name = "uu_sha224sum" +version = "0.6.0" +dependencies = [ + "clap", + "codspeed-divan-compat", + "fluent", + "tempfile", + "uu_checksum_common", + "uucore", +] + [[package]] name = "uu_shred" version = "0.6.0" diff --git a/Cargo.toml b/Cargo.toml index d2e72e7ac1b..789586c3d83 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -89,6 +89,7 @@ feat_common_core = [ "b2sum", "md5sum", "sha1sum", + "sha224sum", "comm", "cp", "csplit", @@ -443,6 +444,7 @@ cksum = { optional = true, version = "0.6.0", package = "uu_cksum", path = "src/ b2sum = { optional = true, version = "0.6.0", package = "uu_b2sum", path = "src/uu/b2sum" } md5sum = { optional = true, version = "0.6.0", package = "uu_md5sum", path = "src/uu/md5sum" } sha1sum = { optional = true, version = "0.6.0", package = "uu_sha1sum", path = "src/uu/sha1sum" } +sha224sum = { optional = true, version = "0.6.0", package = "uu_sha224sum", path = "src/uu/sha224sum" } comm = { optional = true, version = "0.6.0", package = "uu_comm", path = "src/uu/comm" } cp = { optional = true, version = "0.6.0", package = "uu_cp", path = "src/uu/cp" } csplit = { optional = true, version = "0.6.0", package = "uu_csplit", path = "src/uu/csplit" } diff --git a/GNUmakefile b/GNUmakefile index 053441c74f7..f713e63906c 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -96,7 +96,6 @@ SELINUX_PROGS := \ runcon HASHSUM_PROGS := \ - sha224sum \ sha256sum \ sha384sum \ sha512sum diff --git a/build.rs b/build.rs index 6b0e177fd87..49d8c675cd8 100644 --- a/build.rs +++ b/build.rs @@ -91,7 +91,6 @@ pub fn main() { phf_map.entry(krate, format!("({krate}::uumain, {krate}::uu_app_custom)")); let map_value = format!("({krate}::uumain, {krate}::uu_app_common)"); - phf_map.entry("sha224sum", map_value.clone()); phf_map.entry("sha256sum", map_value.clone()); phf_map.entry("sha384sum", map_value.clone()); phf_map.entry("sha512sum", map_value.clone()); diff --git a/src/common/validation.rs b/src/common/validation.rs index ffadfd9178c..c5aaac3d0a6 100644 --- a/src/common/validation.rs +++ b/src/common/validation.rs @@ -51,7 +51,7 @@ fn get_canonical_util_name(util_name: &str) -> &str { "[" => "test", // hashsum aliases - all these hash commands are aliases for hashsum - "sha224sum" | "sha256sum" | "sha384sum" | "sha512sum" => "hashsum", + "sha256sum" | "sha384sum" | "sha512sum" => "hashsum", "dir" => "ls", // dir is an alias for ls diff --git a/src/uu/sha224sum/Cargo.toml b/src/uu/sha224sum/Cargo.toml new file mode 100644 index 00000000000..25086ee4248 --- /dev/null +++ b/src/uu/sha224sum/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "uu_sha224sum" +description = "sha224sum ~ (uutils) Print or check the SHA224 checksums" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/sha224sum" +version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +keywords.workspace = true +categories.workspace = true +edition.workspace = true +readme.workspace = true + +[lints] +workspace = true + +[lib] +path = "src/sha224sum.rs" + +[dependencies] +clap = { workspace = true } +uu_checksum_common = { workspace = true } +uucore = { workspace = true, features = [ + "checksum", + "encoding", + "sum", + "hardware", +] } +fluent = { workspace = true } + +[dev-dependencies] +divan = { workspace = true } +tempfile = { workspace = true } +uucore = { workspace = true, features = ["benchmark"] } + +[[bin]] +name = "sha224sum" +path = "src/main.rs" diff --git a/src/uu/sha224sum/LICENSE b/src/uu/sha224sum/LICENSE new file mode 120000 index 00000000000..5853aaea53b --- /dev/null +++ b/src/uu/sha224sum/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/sha224sum/locales/en-US.ftl b/src/uu/sha224sum/locales/en-US.ftl new file mode 100644 index 00000000000..00f852b7126 --- /dev/null +++ b/src/uu/sha224sum/locales/en-US.ftl @@ -0,0 +1,2 @@ +sha224sum-about = Print or check the SHA224 checksums +sha224sum-usage = sha224sum [OPTIONS] [FILE]... diff --git a/src/uu/sha224sum/locales/fr-FR.ftl b/src/uu/sha224sum/locales/fr-FR.ftl new file mode 100644 index 00000000000..dbd90e9f398 --- /dev/null +++ b/src/uu/sha224sum/locales/fr-FR.ftl @@ -0,0 +1,2 @@ +sha224sum-about = Afficher le SHA224 et la taille de chaque fichier +sha224sum-usage = sha224sum [OPTION]... [FICHIER]... diff --git a/src/uu/sha224sum/src/main.rs b/src/uu/sha224sum/src/main.rs new file mode 100644 index 00000000000..974671331cd --- /dev/null +++ b/src/uu/sha224sum/src/main.rs @@ -0,0 +1 @@ +uucore::bin!(uu_sha224sum); diff --git a/src/uu/sha224sum/src/sha224sum.rs b/src/uu/sha224sum/src/sha224sum.rs new file mode 100644 index 00000000000..3491046756a --- /dev/null +++ b/src/uu/sha224sum/src/sha224sum.rs @@ -0,0 +1 @@ +uu_checksum_common::declare_standalone!("sha224sum", uucore::checksum::AlgoKind::Sha224); diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 203a7ebb26a..b013e9128a9 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -173,7 +173,7 @@ pub fn get_canonical_util_name(util_name: &str) -> &str { "[" => "test", // hashsum aliases - all these hash commands are aliases for hashsum - "sha224sum" | "sha256sum" | "sha384sum" | "sha512sum" => "hashsum", + "sha256sum" | "sha384sum" | "sha512sum" => "hashsum", "dir" => "ls", // dir is an alias for ls diff --git a/src/uucore/src/lib/mods/locale.rs b/src/uucore/src/lib/mods/locale.rs index 2b740eff892..c69a5452dfa 100644 --- a/src/uucore/src/lib/mods/locale.rs +++ b/src/uucore/src/lib/mods/locale.rs @@ -157,7 +157,7 @@ fn create_bundle( try_add_resource_from(get_locales_dir(util_name).ok()); // checksum binaries also require fluent files from the checksum_common crate - if ["cksum", "b2sum", "md5sum", "sha1sum"].contains(&util_name) { + if ["cksum", "b2sum", "md5sum", "sha1sum", "sha224sum"].contains(&util_name) { try_add_resource_from(get_locales_dir("checksum_common").ok()); } diff --git a/tests/by-util/test_hashsum.rs b/tests/by-util/test_hashsum.rs index 646724d66fc..ea044f6abc9 100644 --- a/tests/by-util/test_hashsum.rs +++ b/tests/by-util/test_hashsum.rs @@ -205,7 +205,6 @@ test_digest! {b3sum, b3sum} test_digest! {shake128, shake128} test_digest! {shake256, shake256} -test_digest_with_len! {sha224, sha224, 224} test_digest_with_len! {sha256, sha256, 256} test_digest_with_len! {sha384, sha384, 384} test_digest_with_len! {sha512, sha512, 512} diff --git a/tests/by-util/test_sha224sum.rs b/tests/by-util/test_sha224sum.rs new file mode 100644 index 00000000000..e2b7129b8a5 --- /dev/null +++ b/tests/by-util/test_sha224sum.rs @@ -0,0 +1,120 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +use uutests::new_ucmd; +// spell-checker:ignore checkfile, testf, ntestf +macro_rules! get_hash( + ($str:expr) => ( + $str.split(' ').collect::>()[0] + ); +); + +macro_rules! test_digest { + ($id:ident) => { + mod $id { + use uutests::util::*; + use uutests::util_name; + static EXPECTED_FILE: &'static str = concat!(stringify!($id), ".expected"); + static CHECK_FILE: &'static str = concat!(stringify!($id), ".checkfile"); + static INPUT_FILE: &'static str = "input.txt"; + + #[test] + fn test_single_file() { + let ts = TestScenario::new(util_name!()); + assert_eq!( + ts.fixtures.read(EXPECTED_FILE), + get_hash!( + ts.ucmd() + .arg(INPUT_FILE) + .succeeds() + .no_stderr() + .stdout_str() + ) + ); + } + + #[test] + fn test_stdin() { + let ts = TestScenario::new(util_name!()); + assert_eq!( + ts.fixtures.read(EXPECTED_FILE), + get_hash!( + ts.ucmd() + .pipe_in_fixture(INPUT_FILE) + .succeeds() + .no_stderr() + .stdout_str() + ) + ); + } + + #[test] + fn test_check() { + let ts = TestScenario::new(util_name!()); + println!("File content='{}'", ts.fixtures.read(INPUT_FILE)); + println!("Check file='{}'", ts.fixtures.read(CHECK_FILE)); + + ts.ucmd() + .args(&["--check", CHECK_FILE]) + .succeeds() + .no_stderr() + .stdout_is("input.txt: OK\n"); + } + + #[test] + fn test_zero() { + let ts = TestScenario::new(util_name!()); + assert_eq!( + ts.fixtures.read(EXPECTED_FILE), + get_hash!( + ts.ucmd() + .arg("--zero") + .arg(INPUT_FILE) + .succeeds() + .no_stderr() + .stdout_str() + ) + ); + } + + #[test] + fn test_missing_file() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + + at.write("a", "file1\n"); + at.write("c", "file3\n"); + + ts.ucmd() + .args(&["a", "b", "c"]) + .fails() + .stdout_contains("a\n") + .stdout_contains("c\n") + .stderr_contains("b: No such file or directory"); + } + } + }; +} +test_digest! {sha224} + +#[test] +fn test_invalid_arg() { + new_ucmd!().arg("--definitely-invalid").fails_with_code(1); +} + +#[test] +fn test_conflicting_arg() { + new_ucmd!().arg("--tag").arg("--check").fails_with_code(1); +} + +#[test] +fn test_help_shows_correct_utility_name() { + // Test that help output shows the actual utility name instead of "hashsum" + new_ucmd!() + .arg("--help") + .succeeds() + .stdout_contains("Usage: sha224sum") + .stdout_does_not_contain("Usage: hashsum"); +} diff --git a/tests/fixtures/sha224sum/input.txt b/tests/fixtures/sha224sum/input.txt new file mode 100644 index 00000000000..8c01d89ae06 --- /dev/null +++ b/tests/fixtures/sha224sum/input.txt @@ -0,0 +1 @@ +hello, world \ No newline at end of file diff --git a/tests/fixtures/hashsum/sha224.checkfile b/tests/fixtures/sha224sum/sha224.checkfile similarity index 100% rename from tests/fixtures/hashsum/sha224.checkfile rename to tests/fixtures/sha224sum/sha224.checkfile diff --git a/tests/fixtures/hashsum/sha224.expected b/tests/fixtures/sha224sum/sha224.expected similarity index 100% rename from tests/fixtures/hashsum/sha224.expected rename to tests/fixtures/sha224sum/sha224.expected diff --git a/tests/tests.rs b/tests/tests.rs index 76deb8c7dc2..b51ed1c5b0e 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -80,6 +80,10 @@ mod test_md5sum; #[path = "by-util/test_sha1sum.rs"] mod test_sha1sum; +#[cfg(feature = "sha224sum")] +#[path = "by-util/test_sha224sum.rs"] +mod test_sha224sum; + #[cfg(feature = "cp")] #[path = "by-util/test_cp.rs"] mod test_cp; From 928ca15251557d4fd6fc16db209f65f44eb0f018 Mon Sep 17 00:00:00 2001 From: Dorian Peron Date: Fri, 9 Jan 2026 17:18:39 +0100 Subject: [PATCH 08/10] sha256sum: introduce standalone binary --- Cargo.lock | 13 ++ Cargo.toml | 2 + GNUmakefile | 1 - build.rs | 1 - src/common/validation.rs | 2 +- src/uu/sha256sum/Cargo.toml | 38 ++++ src/uu/sha256sum/LICENSE | 1 + src/uu/sha256sum/locales/en-US.ftl | 2 + src/uu/sha256sum/locales/fr-FR.ftl | 2 + src/uu/sha256sum/src/main.rs | 1 + src/uu/sha256sum/src/sha256sum.rs | 1 + src/uucore/src/lib/lib.rs | 2 +- src/uucore/src/lib/mods/locale.rs | 11 +- tests/by-util/test_hashsum.rs | 18 +- tests/by-util/test_sha256sum.rs | 181 ++++++++++++++++++ tests/fixtures/sha256sum/binary.png | Bin 0 -> 8055 bytes .../binary.sha256.checkfile | 0 .../binary.sha256.expected | 0 tests/fixtures/sha256sum/input.txt | 1 + .../{hashsum => sha256sum}/sha256.checkfile | 0 .../{hashsum => sha256sum}/sha256.expected | 0 tests/tests.rs | 4 + 22 files changed, 268 insertions(+), 13 deletions(-) create mode 100644 src/uu/sha256sum/Cargo.toml create mode 120000 src/uu/sha256sum/LICENSE create mode 100644 src/uu/sha256sum/locales/en-US.ftl create mode 100644 src/uu/sha256sum/locales/fr-FR.ftl create mode 100644 src/uu/sha256sum/src/main.rs create mode 100644 src/uu/sha256sum/src/sha256sum.rs create mode 100644 tests/by-util/test_sha256sum.rs create mode 100644 tests/fixtures/sha256sum/binary.png rename tests/fixtures/{hashsum => sha256sum}/binary.sha256.checkfile (100%) rename tests/fixtures/{hashsum => sha256sum}/binary.sha256.expected (100%) create mode 100644 tests/fixtures/sha256sum/input.txt rename tests/fixtures/{hashsum => sha256sum}/sha256.checkfile (100%) rename tests/fixtures/{hashsum => sha256sum}/sha256.expected (100%) diff --git a/Cargo.lock b/Cargo.lock index 8232e2e27a2..aa8dcdf2746 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -629,6 +629,7 @@ dependencies = [ "uu_seq", "uu_sha1sum", "uu_sha224sum", + "uu_sha256sum", "uu_shred", "uu_shuf", "uu_sleep", @@ -4010,6 +4011,18 @@ dependencies = [ "uucore", ] +[[package]] +name = "uu_sha256sum" +version = "0.6.0" +dependencies = [ + "clap", + "codspeed-divan-compat", + "fluent", + "tempfile", + "uu_checksum_common", + "uucore", +] + [[package]] name = "uu_shred" version = "0.6.0" diff --git a/Cargo.toml b/Cargo.toml index 789586c3d83..801f679794e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -90,6 +90,7 @@ feat_common_core = [ "md5sum", "sha1sum", "sha224sum", + "sha256sum", "comm", "cp", "csplit", @@ -445,6 +446,7 @@ b2sum = { optional = true, version = "0.6.0", package = "uu_b2sum", path = "src/ md5sum = { optional = true, version = "0.6.0", package = "uu_md5sum", path = "src/uu/md5sum" } sha1sum = { optional = true, version = "0.6.0", package = "uu_sha1sum", path = "src/uu/sha1sum" } sha224sum = { optional = true, version = "0.6.0", package = "uu_sha224sum", path = "src/uu/sha224sum" } +sha256sum = { optional = true, version = "0.6.0", package = "uu_sha256sum", path = "src/uu/sha256sum" } comm = { optional = true, version = "0.6.0", package = "uu_comm", path = "src/uu/comm" } cp = { optional = true, version = "0.6.0", package = "uu_cp", path = "src/uu/cp" } csplit = { optional = true, version = "0.6.0", package = "uu_csplit", path = "src/uu/csplit" } diff --git a/GNUmakefile b/GNUmakefile index f713e63906c..998968d0928 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -96,7 +96,6 @@ SELINUX_PROGS := \ runcon HASHSUM_PROGS := \ - sha256sum \ sha384sum \ sha512sum diff --git a/build.rs b/build.rs index 49d8c675cd8..c206e892f56 100644 --- a/build.rs +++ b/build.rs @@ -91,7 +91,6 @@ pub fn main() { phf_map.entry(krate, format!("({krate}::uumain, {krate}::uu_app_custom)")); let map_value = format!("({krate}::uumain, {krate}::uu_app_common)"); - phf_map.entry("sha256sum", map_value.clone()); phf_map.entry("sha384sum", map_value.clone()); phf_map.entry("sha512sum", map_value.clone()); } diff --git a/src/common/validation.rs b/src/common/validation.rs index c5aaac3d0a6..aa01b467919 100644 --- a/src/common/validation.rs +++ b/src/common/validation.rs @@ -51,7 +51,7 @@ fn get_canonical_util_name(util_name: &str) -> &str { "[" => "test", // hashsum aliases - all these hash commands are aliases for hashsum - "sha256sum" | "sha384sum" | "sha512sum" => "hashsum", + "sha384sum" | "sha512sum" => "hashsum", "dir" => "ls", // dir is an alias for ls diff --git a/src/uu/sha256sum/Cargo.toml b/src/uu/sha256sum/Cargo.toml new file mode 100644 index 00000000000..2ca6204c041 --- /dev/null +++ b/src/uu/sha256sum/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "uu_sha256sum" +description = "sha256sum ~ (uutils) Print or check the SHA256 checksums" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/sha256sum" +version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +keywords.workspace = true +categories.workspace = true +edition.workspace = true +readme.workspace = true + +[lints] +workspace = true + +[lib] +path = "src/sha256sum.rs" + +[dependencies] +clap = { workspace = true } +uu_checksum_common = { workspace = true } +uucore = { workspace = true, features = [ + "checksum", + "encoding", + "sum", + "hardware", +] } +fluent = { workspace = true } + +[dev-dependencies] +divan = { workspace = true } +tempfile = { workspace = true } +uucore = { workspace = true, features = ["benchmark"] } + +[[bin]] +name = "sha256sum" +path = "src/main.rs" diff --git a/src/uu/sha256sum/LICENSE b/src/uu/sha256sum/LICENSE new file mode 120000 index 00000000000..5853aaea53b --- /dev/null +++ b/src/uu/sha256sum/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/sha256sum/locales/en-US.ftl b/src/uu/sha256sum/locales/en-US.ftl new file mode 100644 index 00000000000..60a0b4a3f33 --- /dev/null +++ b/src/uu/sha256sum/locales/en-US.ftl @@ -0,0 +1,2 @@ +sha256sum-about = Print or check the SHA256 checksums +sha256sum-usage = sha256sum [OPTIONS] [FILE]... diff --git a/src/uu/sha256sum/locales/fr-FR.ftl b/src/uu/sha256sum/locales/fr-FR.ftl new file mode 100644 index 00000000000..baaa2f83bd7 --- /dev/null +++ b/src/uu/sha256sum/locales/fr-FR.ftl @@ -0,0 +1,2 @@ +sha256sum-about = Afficher le SHA256 et la taille de chaque fichier +sha256sum-usage = sha256sum [OPTION]... [FICHIER]... diff --git a/src/uu/sha256sum/src/main.rs b/src/uu/sha256sum/src/main.rs new file mode 100644 index 00000000000..323cd315df1 --- /dev/null +++ b/src/uu/sha256sum/src/main.rs @@ -0,0 +1 @@ +uucore::bin!(uu_sha256sum); diff --git a/src/uu/sha256sum/src/sha256sum.rs b/src/uu/sha256sum/src/sha256sum.rs new file mode 100644 index 00000000000..ab47a23df44 --- /dev/null +++ b/src/uu/sha256sum/src/sha256sum.rs @@ -0,0 +1 @@ +uu_checksum_common::declare_standalone!("sha256sum", uucore::checksum::AlgoKind::Sha256); diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index b013e9128a9..3e6003f311e 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -173,7 +173,7 @@ pub fn get_canonical_util_name(util_name: &str) -> &str { "[" => "test", // hashsum aliases - all these hash commands are aliases for hashsum - "sha256sum" | "sha384sum" | "sha512sum" => "hashsum", + "sha384sum" | "sha512sum" => "hashsum", "dir" => "ls", // dir is an alias for ls diff --git a/src/uucore/src/lib/mods/locale.rs b/src/uucore/src/lib/mods/locale.rs index c69a5452dfa..da5a996d577 100644 --- a/src/uucore/src/lib/mods/locale.rs +++ b/src/uucore/src/lib/mods/locale.rs @@ -157,7 +157,16 @@ fn create_bundle( try_add_resource_from(get_locales_dir(util_name).ok()); // checksum binaries also require fluent files from the checksum_common crate - if ["cksum", "b2sum", "md5sum", "sha1sum", "sha224sum"].contains(&util_name) { + if [ + "cksum", + "b2sum", + "md5sum", + "sha1sum", + "sha224sum", + "sha256sum", + ] + .contains(&util_name) + { try_add_resource_from(get_locales_dir("checksum_common").ok()); } diff --git a/tests/by-util/test_hashsum.rs b/tests/by-util/test_hashsum.rs index ea044f6abc9..ff6d92f2d89 100644 --- a/tests/by-util/test_hashsum.rs +++ b/tests/by-util/test_hashsum.rs @@ -205,7 +205,6 @@ test_digest! {b3sum, b3sum} test_digest! {shake128, shake128} test_digest! {shake256, shake256} -test_digest_with_len! {sha256, sha256, 256} test_digest_with_len! {sha384, sha384, 384} test_digest_with_len! {sha512, sha512, 512} test_digest_with_len! {sha3_224, sha3, 224} @@ -618,6 +617,7 @@ fn test_conflicting_arg() { .fails_with_code(1); } +#[ignore = "moved to standalone"] #[test] fn test_tag() { let scene = TestScenario::new(util_name!()); @@ -1184,6 +1184,7 @@ fn test_check_md5_comment_leading_space() { .stderr_contains("WARNING: 1 line is improperly formatted"); } +#[ignore = "moved to standalone"] #[test] fn test_sha256_binary() { let ts = TestScenario::new(util_name!()); @@ -1200,6 +1201,7 @@ fn test_sha256_binary() { ); } +#[ignore = "moved to standalone"] #[test] fn test_sha256_stdin_binary() { let ts = TestScenario::new(util_name!()); @@ -1217,8 +1219,8 @@ fn test_sha256_stdin_binary() { } // This test is currently disabled on windows +#[ignore = "moved to standalone"] #[test] -#[cfg_attr(windows, ignore = "Discussion is in #9168")] fn test_check_sha256_binary() { new_ucmd!() .args(&["--sha256", "--check", "binary.sha256.checkfile"]) @@ -1241,12 +1243,12 @@ fn test_help_shows_correct_utility_name() { // .stdout_does_not_contain("Usage: hashsum"); // Test sha256sum - scene - .ccmd("sha256sum") - .arg("--help") - .succeeds() - .stdout_contains("Usage: sha256sum") - .stdout_does_not_contain("Usage: hashsum"); + // scene + // .ccmd("sha256sum") + // .arg("--help") + // .succeeds() + // .stdout_contains("Usage: sha256sum") + // .stdout_does_not_contain("Usage: hashsum"); // Test b2sum // scene diff --git a/tests/by-util/test_sha256sum.rs b/tests/by-util/test_sha256sum.rs new file mode 100644 index 00000000000..b3b538384b1 --- /dev/null +++ b/tests/by-util/test_sha256sum.rs @@ -0,0 +1,181 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +use uutests::new_ucmd; +use uutests::util::TestScenario; +use uutests::util_name; +// spell-checker:ignore checkfile, testf, ntestf +macro_rules! get_hash( + ($str:expr) => ( + $str.split(' ').collect::>()[0] + ); +); + +macro_rules! test_digest { + ($id:ident) => { + mod $id { + use uutests::util::*; + use uutests::util_name; + static EXPECTED_FILE: &'static str = concat!(stringify!($id), ".expected"); + static CHECK_FILE: &'static str = concat!(stringify!($id), ".checkfile"); + static INPUT_FILE: &'static str = "input.txt"; + + #[test] + fn test_single_file() { + let ts = TestScenario::new(util_name!()); + assert_eq!( + ts.fixtures.read(EXPECTED_FILE), + get_hash!( + ts.ucmd() + .arg(INPUT_FILE) + .succeeds() + .no_stderr() + .stdout_str() + ) + ); + } + + #[test] + fn test_stdin() { + let ts = TestScenario::new(util_name!()); + assert_eq!( + ts.fixtures.read(EXPECTED_FILE), + get_hash!( + ts.ucmd() + .pipe_in_fixture(INPUT_FILE) + .succeeds() + .no_stderr() + .stdout_str() + ) + ); + } + + #[test] + fn test_check() { + let ts = TestScenario::new(util_name!()); + println!("File content='{}'", ts.fixtures.read(INPUT_FILE)); + println!("Check file='{}'", ts.fixtures.read(CHECK_FILE)); + + ts.ucmd() + .args(&["--check", CHECK_FILE]) + .succeeds() + .no_stderr() + .stdout_is("input.txt: OK\n"); + } + + #[test] + fn test_zero() { + let ts = TestScenario::new(util_name!()); + assert_eq!( + ts.fixtures.read(EXPECTED_FILE), + get_hash!( + ts.ucmd() + .arg("--zero") + .arg(INPUT_FILE) + .succeeds() + .no_stderr() + .stdout_str() + ) + ); + } + + #[test] + fn test_missing_file() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + + at.write("a", "file1\n"); + at.write("c", "file3\n"); + + ts.ucmd() + .args(&["a", "b", "c"]) + .fails() + .stdout_contains("a\n") + .stdout_contains("c\n") + .stderr_contains("b: No such file or directory"); + } + } + }; +} + +test_digest! {sha256} + +#[test] +fn test_invalid_arg() { + new_ucmd!().arg("--definitely-invalid").fails_with_code(1); +} + +#[test] +fn test_conflicting_arg() { + new_ucmd!().arg("--tag").arg("--check").fails_with_code(1); + new_ucmd!().arg("--tag").arg("--text").fails_with_code(1); +} + +#[test] +fn test_tag() { + let scene = TestScenario::new(util_name!()); + let at = &scene.fixtures; + + at.write("foobar", "foo bar\n"); + scene + .ccmd("sha256sum") + .arg("--tag") + .arg("foobar") + .succeeds() + .stdout_is( + "SHA256 (foobar) = 1f2ec52b774368781bed1d1fb140a92e0eb6348090619c9291f9a5a3c8e8d151\n", + ); +} + +#[test] +fn test_sha256_binary() { + let ts = TestScenario::new(util_name!()); + assert_eq!( + ts.fixtures.read("binary.sha256.expected"), + get_hash!( + ts.ucmd() + .arg("binary.png") + .succeeds() + .no_stderr() + .stdout_str() + ) + ); +} + +#[test] +fn test_sha256_stdin_binary() { + let ts = TestScenario::new(util_name!()); + assert_eq!( + ts.fixtures.read("binary.sha256.expected"), + get_hash!( + ts.ucmd() + .pipe_in_fixture("binary.png") + .succeeds() + .no_stderr() + .stdout_str() + ) + ); +} + +// This test is currently disabled on windows +#[test] +#[cfg_attr(windows, ignore = "Discussion is in #9168")] +fn test_check_sha256_binary() { + new_ucmd!() + .args(&["--check", "binary.sha256.checkfile"]) + .succeeds() + .no_stderr() + .stdout_is("binary.png: OK\n"); +} + +#[test] +fn test_help_shows_correct_utility_name() { + // Test that help output shows the actual utility name instead of "hashsum" + new_ucmd!() + .arg("--help") + .succeeds() + .stdout_contains("Usage: sha256sum") + .stdout_does_not_contain("Usage: hashsum"); +} diff --git a/tests/fixtures/sha256sum/binary.png b/tests/fixtures/sha256sum/binary.png new file mode 100644 index 0000000000000000000000000000000000000000..6c4161338f200299744af6dff35884ac5c94516b GIT binary patch literal 8055 zcmV--ABf8e>JhZmS~7IuY$40C}`|m zj2cjbH3|elN(2S5018`>vdgmU?!3>y_xitF=gyonbI#1%yXW&f=dtXa-<&yfeskKd z6v2=bN&p30P6v42q2?9fOh~4 z$oPM~0n`9^FMzxhfP(-W37`vrRRHP($T1i~(Sr(+{^~o^qS63zF36CPS=Iq)7C_#` zS#s_PAj4n?%^fbC`YQl~C1f~Lt7SP9#3?UJOtmZk;{be@C1VSKfdQYukxBD*}`u9_jwn4Y5pEdg+nz}e7?k{#Rm z`cDFI8i4b-GbO7WPT36LY5?Z|7z^N^0c1?@dA8;Nt_M(;S>)4+{^W73 zqdc1ecJL&WZ#;I`y7+84JLO4D@5GQG*86kjyeBj3Fzcu%OP3MBMCV73kh~JqZLVhOM8JyANdG6%o^VBN4 zI8&_`I8u6@=eU&talb@^KK`75&YAD!r*Xf;DDgX?O)AX+bd@maNOQ2J`ZOi{ECjM? z1E71pT8`(Vf{X^Rk=C;-0Sp(sE=7|JLpZt%{;nA+@nfO=+@O|%T?!#W-c;9daV3SMs%L?b5#c%k1Upi{Y(WrELqS_10o zR-DoL^8nZN*W5>xl5dOj$^h4N(NGT+JSF(MmjXVwZdw578Q{7t8tS5rrl256Wxn9= zHhawb^O2gkD^W`0cacYIdmcOo{Kno))d$p zOo&uQRF4LBl=B&e(+|-Dsi?{~0B+6^oK3{Rs+Ml99W$=$BN6o^+X1R2fMaR45_j8V zmjwdvcOb2dw4wKf!Q2y2~oo?>9UZ_M84( zAt-kXV_S)S09F7P=5z0Mj7IW*8O5bG0qr-LqiYd2AI8$n`4s@GJpx+z`O_B+JtZ5| zQJRkdRLA^c=L0w);I#A-4fXL;4mX_9<(dR2N;$2uWvQb%(e~>ka zPUXm?hymg@Cg8oKOFb9%O^PK6X!vCuy5k6`DLRtq9yxN+o7>k(a2I$chUbr+$6{{u z8{%#N2GRDaV`BmWP4oaTFXneOCgwJXh2O>0*spR|Dk+Sk2V~}yvz{@4WwKT2#G(uE z@)+LN2JX5wIer>(LvP_rPAdCwx_648ML`@`jL#e8dwW0bhmMQR5iQi6Y04}O+oMgD z7PJxb3v{ij+#UA=$H~VLH1LgaKV*T@Eutycxp6;eTCd#urjT*VDRf8 z34uh*vvC?Ks=RRtZI!@Ic@c$i(bvg5ANytn>*z=ZyWaE$cHv>UdpW?Hz7+~xE7 z?OdMYghs^((t;yK5>7gw18^!E(3e@G_j=N@te>l_z_jHRS4~@9ag$1!qP%rf|Bb5i zcTD}PeC)$3DxVenxzh*!17Kg3bqL5LS}un|h0ASeLX_i1W=axL4ucl9GSA~1aaSb6 zk0=x>6yzHYt#NW4oq`PU^BI6q2~d)yDuk#|5NFPJy2~OwDy~R~*@$6h#?xvc4z_SD z8X8qmP2T0MRMcEaUj{?+SWVyfIChk7RyC_p8TRLKg|bAL#D*{=n_mN%9pU}HV9*C8 zs8o2wqY&j7eKqI(&_v65y!pG0)Or&Y%PRjDAGX%=8Blb9vX;@Y??XFSJnCggc8BeDh=t*(y3Op4fk#}F@n=9(l* zI&jV6iHL$k9hP&US(rCD8UfrD!+m{y=tUG{x|+HObAQD5GbDrQ5pT$>0dSw+k3u_M49&AU*A@rT3}B+qb)pSt$ZyW{`TIfugBVJ57^60J zkqjxcjR~8i32Mzdo4(CKJvIes-f2q4}CnQ(v8gHK9Wk3NhKpV zUM9HMK8<0BqzPm0-o@wd4a_3-md9^C0q}c|zjvo|jiE4k)Wh)gJsQbYjl z2Cacp*#%XQjsiD*1HgO%S+jh+%gnh*VKT~RJO5!pIU))&K4_jpSp_NLu=Wjrwm!cV zH8yPYF)ulo!>OULINRs%qWx#CfYVhseuPKMI#P+4yYB_yNNW5@0mVqOUZNg;iEi#? zcvF$xFGKR#TfjwDbf>hQc|&&h`Q3g1{>j}iXLj>C|LJ)juMy-hYTZ&=TAm=G9N;ue zl%76+@60%_hQi=pfxaZ_lvgYn1ECjya~MOb3cx*H374`Jqz8c8sT8w9rbs(L@OLRz z*pH;-3^#m{U&)`jS(O1Hq*Q`GZ zgG#|jCWq5sBiA`t3jh}?QjkoJ%lwZ_1qrkb=EJU3l1p6Dw4Kdl^!p3Snxmn9hUnP{ z;3a|Ir5M%b4Q>==vI_$VPGPoNmF%PknNzH{>1EYT!wqkz4cBLU@E(#2tM|DBVJHkP zqRCMYf!_|{n)p*XRJEtVG}lzOe`NkgrhRD8kQgU1-CCmy(G(;KRx>`*ATG`} zM|599{8aE*;8;$(qM9)@(hUBhTp7!}_M=zPlJ-G;_1jJ(&D!^6WtV8apCOsl(Z^#f zxBurwaKABr2|q;+Y%T?GtA+%fYXSU))44MPdMO$Tla&C5(@6DY4f!aYzIG~r*Zh7c zVyw6cz~!{RR~7N_IeHXE83)ObUu>aM-V2;vJVhhNuvAl?(ArkU+{jeHa3=nSmF+4Jk*eAB!r;p7bpb%OV9b3{9J` zGmjDg7l9E6l;jrB1JKt8AS~S0irUE-yRn_6|oIe(C8Be zLz*z*a%K1~eY1HCq7?2Bvr;ZwQ7(M*9Dy)o-XeoxKh05fvn|LsII)MMU{lIHSppou~Lz%0g zT0w>UB5)fFeyE84S2-#UJU;>imOamQ;sLopqg^Ntq05`T$F@(-jWcHL* zcmPImT5l+9{8Inc~{grF6GP9bGT^5_y=1)~{?$~7pRTZ4|HJ=UHYD&w$^Cb{xhmP#vu zG%9`gWl?S|ZCO^mYMBAi*>4VFh|?dKv81y8{UWBd(n?A12g%RCnLw4T&Oa-ZB&JL&J zPLCiB)=s0swh2daL)A1$Tlp4dR zqSweI@dP8WKg6UZow%@8xlS1#_jd2~JN~g&sZa~MzkXyd-8+FlVB$WVHJn>n3*J^Q zR2J1co^Jp+fZN8G0C>eKUYpFm`BVmVI26F`3M$%y`48hp`9i(8S+nC+%k-mqtF3$y zYojp<$&OSh$VIf`7?sPt)(zQ%C7WSUE>$#|ToZdJAlq7@T4c($RJaC6VZm0v4Vp^L zR4!8vXFWGeft-2F2_pTj!<@XT__{P#l({M}3)gQ}zEHP;VH5h4Mi#+k*2w+(!Ln2v zfmT2GX_f9&%zjK!WV&-l&xnaEY~2m$YyNUD6pEmvMbM z{z6Bz!D%ZhK2j}Hl2aARN->00pbKT_{J*=SO1YRrYUJ#A82dpDqKk^`;u(WVT4_5M z8P}(@M^UOs%ORS*V__Q?M{p}vB?IWj(N^Xn83l2^Qg3TAYHR^utOmJ4G`EZH&}2E# zd-6d&uGJtH6MC1mozSP#`y)ymG4U`?%1N@zPpGc+dR+c<$DTEQF#Cvf!*m?xwM1 zq*OOwcUGIaPIDqLqO`VeW1f0O&rY>x^z4+jDz4$Qx9yG&)6mV1Hb_cE`*t-iM|r~x zCPu6rU@ja_1rb?;MC}jY%n_fDBYe)W(<>JP_*cZYZAsUHK{V|?h#w`L3sqMv%f5Wd z{4F|M8~kuS)EC5X-(`M35K)l3H2H&g@nZlJBEIK#7JXZLe;oIXAN$?AzFZc^)T$Lj z?nLcJ^G^VO_xq8Eg1o`6*|`9oig4<4bsVEe#ey$hj$ssuovEKy-1*FZV+j&BHX?)7 zI!>qV0dO2`0DU921nq$BxCh_e<)4Qr^Z7af@4N!2>nS>(?K@HK!{Ea`t=Q>gK{{Wr%*Y-SklVR&vec4KT{l}dIe9~Bb{nt``O4| zGXZN<)c1`7+r|Y)%vh@AgkO>pzS9m0WnKv2FAC*tN5=>5ZyVIiT2o2_dc95f6MZ9?UoL)p~tZ*9_+up z*!rt0V!HVx0DU#c9m(SXrKp}_bp-H<#y#yV08RujLzDcRTr{M0zY{xmp90H@)rUSL zjq|IjZ}@J`*VpTC>hYl&H*c=gm?B~k4_cL3BCA%A2e|8`lQm4l7$l%sSDsk6`$9$DRg6*Bl(6+ico(GRgP^lmT04$6s zfy}3jh?g0WB%MLG2fY(}xcqx&VCh;a$t^q#6=HD5HZ3OaH{f&>ZMqDW6;(jxFf;&| zHG4z()t7%zIa}hoHUQ4y2QM@GQawXHeZ}&n%fD~Pk~M}w2&0Hw{G`fhzDfmAeg5Ic zha9elabw`XfD6AGUb{r*qVc`TO2+pqdrEO)N%h$F`6Fg5yF0*@w*j~;;9bAu$LBrv zv3ml_2|^vcER~o?sZtPUez}-apM0d83q0v)@3#&u8+BlZvU8a0E(s|e99Fk)SM&Jf zk7^!xct=nQSq=d(Q(rdQIqCb{n@Fbvyjz zq7F8$9x-F-Z6W0>j3}d%P7YQP+)#yrw57d^>V55~xtB^3igCR<^|Dg-$nm{94TEJ5 zW$rs&>oR8@HNP=s|ND_;mG4+q;~P_#SJi}+xogD9lyosk#gt!#f^_G?EZ=!8XmBwv zUOB#W_cnE%#{!K<1J(!yfhpDi_M5h4y;$CM=}Q{SL=I9ERyJ-PRI#I~LP3gX9{K^c=xPI z$MdV7VaLvuUmI_u>CX^u%=}98JGGgzU=h#3wtD=ok#Q7sSc!z972^#J=9x$jy3e-F zQQ65F8gaHq?!)11V6W?Sm_Xzkclxx^1AMR9IHTVg+zC|qPAdTX#N~d?xbE%3b>2P< z!q78fP^=(Po~`|WIESGI5tl~^ zCUbc8VjFW#}!E&31MogFEa@_M&!RzP6 znN&1JP7!RM1v%QMIW4+%z1LVG%S0a9lZ(ACbY(~4SMURN^eQH!-ygtG2}40g#tYoq zeAC54u268+TmJ*#=d?f&wL0Fy*tU!Kb??fpU7h|wzvqXw;MqgtS4Ky1=F^wwvGb4{ z9cjoHciYO8SfX4;;Xj^DHBYqtzUCY0rMQ4!s*%iJ7d55KvwaCOZO!68Kjn+d2a4%H zbOg)r7@fK*IThQ+t^UE>A@kc&ZBoXh*X8(1)N@)hhM8|Ou>^LYqAc@iQk~@WiZg|} zl^>h9+bdn%3=i8gKjmBxt80~EN`;AI4U)d^cs}$7juaoW_(d3B9Y?HQ52{)3g&gS? zGd5U7gdH545TT#&aA{inCoF}ou)HKl|ek)+C=-Vw=PHD9 zr@%XM+WHljYwuIk2O=XM=j5jX7{@5&OZi0;iMX+zr`Y*mrjlg77WhpSN0S4Yc(p5i zbf!j7KSKznC*TX5??qP@<3y5g+q_}A&k7flBPA;yJ!YO-tmZw zf}9w4r3@y Date: Fri, 9 Jan 2026 17:44:43 +0100 Subject: [PATCH 09/10] sha384sum: introduce standalone binary --- Cargo.lock | 13 ++ Cargo.toml | 2 + GNUmakefile | 1 - build.rs | 1 - src/common/validation.rs | 2 +- src/uu/sha384sum/Cargo.toml | 38 ++++++ src/uu/sha384sum/LICENSE | 1 + src/uu/sha384sum/locales/en-US.ftl | 2 + src/uu/sha384sum/locales/fr-FR.ftl | 2 + src/uu/sha384sum/src/main.rs | 1 + src/uu/sha384sum/src/sha384sum.rs | 1 + src/uucore/src/lib/lib.rs | 2 +- src/uucore/src/lib/mods/locale.rs | 1 + tests/by-util/test_hashsum.rs | 1 - tests/by-util/test_sha384sum.rs | 122 ++++++++++++++++++ tests/fixtures/sha384sum/input.txt | 1 + .../{hashsum => sha384sum}/sha384.checkfile | 0 .../{hashsum => sha384sum}/sha384.expected | 0 tests/tests.rs | 4 + 19 files changed, 190 insertions(+), 5 deletions(-) create mode 100644 src/uu/sha384sum/Cargo.toml create mode 120000 src/uu/sha384sum/LICENSE create mode 100644 src/uu/sha384sum/locales/en-US.ftl create mode 100644 src/uu/sha384sum/locales/fr-FR.ftl create mode 100644 src/uu/sha384sum/src/main.rs create mode 100644 src/uu/sha384sum/src/sha384sum.rs create mode 100644 tests/by-util/test_sha384sum.rs create mode 100644 tests/fixtures/sha384sum/input.txt rename tests/fixtures/{hashsum => sha384sum}/sha384.checkfile (100%) rename tests/fixtures/{hashsum => sha384sum}/sha384.expected (100%) diff --git a/Cargo.lock b/Cargo.lock index aa8dcdf2746..9db65e310ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -630,6 +630,7 @@ dependencies = [ "uu_sha1sum", "uu_sha224sum", "uu_sha256sum", + "uu_sha384sum", "uu_shred", "uu_shuf", "uu_sleep", @@ -4023,6 +4024,18 @@ dependencies = [ "uucore", ] +[[package]] +name = "uu_sha384sum" +version = "0.6.0" +dependencies = [ + "clap", + "codspeed-divan-compat", + "fluent", + "tempfile", + "uu_checksum_common", + "uucore", +] + [[package]] name = "uu_shred" version = "0.6.0" diff --git a/Cargo.toml b/Cargo.toml index 801f679794e..a24e87060c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,6 +91,7 @@ feat_common_core = [ "sha1sum", "sha224sum", "sha256sum", + "sha384sum", "comm", "cp", "csplit", @@ -447,6 +448,7 @@ md5sum = { optional = true, version = "0.6.0", package = "uu_md5sum", path = "sr sha1sum = { optional = true, version = "0.6.0", package = "uu_sha1sum", path = "src/uu/sha1sum" } sha224sum = { optional = true, version = "0.6.0", package = "uu_sha224sum", path = "src/uu/sha224sum" } sha256sum = { optional = true, version = "0.6.0", package = "uu_sha256sum", path = "src/uu/sha256sum" } +sha384sum = { optional = true, version = "0.6.0", package = "uu_sha384sum", path = "src/uu/sha384sum" } comm = { optional = true, version = "0.6.0", package = "uu_comm", path = "src/uu/comm" } cp = { optional = true, version = "0.6.0", package = "uu_cp", path = "src/uu/cp" } csplit = { optional = true, version = "0.6.0", package = "uu_csplit", path = "src/uu/csplit" } diff --git a/GNUmakefile b/GNUmakefile index 998968d0928..7805ff9d6d3 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -96,7 +96,6 @@ SELINUX_PROGS := \ runcon HASHSUM_PROGS := \ - sha384sum \ sha512sum $(info Detected OS = $(OS)) diff --git a/build.rs b/build.rs index c206e892f56..8a5e0ec5ca8 100644 --- a/build.rs +++ b/build.rs @@ -91,7 +91,6 @@ pub fn main() { phf_map.entry(krate, format!("({krate}::uumain, {krate}::uu_app_custom)")); let map_value = format!("({krate}::uumain, {krate}::uu_app_common)"); - phf_map.entry("sha384sum", map_value.clone()); phf_map.entry("sha512sum", map_value.clone()); } _ => { diff --git a/src/common/validation.rs b/src/common/validation.rs index aa01b467919..5c90de222b9 100644 --- a/src/common/validation.rs +++ b/src/common/validation.rs @@ -51,7 +51,7 @@ fn get_canonical_util_name(util_name: &str) -> &str { "[" => "test", // hashsum aliases - all these hash commands are aliases for hashsum - "sha384sum" | "sha512sum" => "hashsum", + "sha512sum" => "hashsum", "dir" => "ls", // dir is an alias for ls diff --git a/src/uu/sha384sum/Cargo.toml b/src/uu/sha384sum/Cargo.toml new file mode 100644 index 00000000000..2fb9ca0375b --- /dev/null +++ b/src/uu/sha384sum/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "uu_sha384sum" +description = "sha384sum ~ (uutils) Print or check the SHA384 checksums" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/sha384sum" +version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +keywords.workspace = true +categories.workspace = true +edition.workspace = true +readme.workspace = true + +[lints] +workspace = true + +[lib] +path = "src/sha384sum.rs" + +[dependencies] +clap = { workspace = true } +uu_checksum_common = { workspace = true } +uucore = { workspace = true, features = [ + "checksum", + "encoding", + "sum", + "hardware", +] } +fluent = { workspace = true } + +[dev-dependencies] +divan = { workspace = true } +tempfile = { workspace = true } +uucore = { workspace = true, features = ["benchmark"] } + +[[bin]] +name = "sha384sum" +path = "src/main.rs" diff --git a/src/uu/sha384sum/LICENSE b/src/uu/sha384sum/LICENSE new file mode 120000 index 00000000000..5853aaea53b --- /dev/null +++ b/src/uu/sha384sum/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/sha384sum/locales/en-US.ftl b/src/uu/sha384sum/locales/en-US.ftl new file mode 100644 index 00000000000..e10a99c1eb3 --- /dev/null +++ b/src/uu/sha384sum/locales/en-US.ftl @@ -0,0 +1,2 @@ +sha384sum-about = Print or check the SHA384 checksums +sha384sum-usage = sha384sum [OPTIONS] [FILE]... diff --git a/src/uu/sha384sum/locales/fr-FR.ftl b/src/uu/sha384sum/locales/fr-FR.ftl new file mode 100644 index 00000000000..f751315eceb --- /dev/null +++ b/src/uu/sha384sum/locales/fr-FR.ftl @@ -0,0 +1,2 @@ +sha1sum-about = Afficher le SHA384 et la taille de chaque fichier +sha1sum-usage = sha384sum [OPTION]... [FICHIER]... diff --git a/src/uu/sha384sum/src/main.rs b/src/uu/sha384sum/src/main.rs new file mode 100644 index 00000000000..c87f32e286e --- /dev/null +++ b/src/uu/sha384sum/src/main.rs @@ -0,0 +1 @@ +uucore::bin!(uu_sha384sum); diff --git a/src/uu/sha384sum/src/sha384sum.rs b/src/uu/sha384sum/src/sha384sum.rs new file mode 100644 index 00000000000..818478e294d --- /dev/null +++ b/src/uu/sha384sum/src/sha384sum.rs @@ -0,0 +1 @@ +uu_checksum_common::declare_standalone!("sha384sum", uucore::checksum::AlgoKind::Sha384); diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 3e6003f311e..75b00426b85 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -173,7 +173,7 @@ pub fn get_canonical_util_name(util_name: &str) -> &str { "[" => "test", // hashsum aliases - all these hash commands are aliases for hashsum - "sha384sum" | "sha512sum" => "hashsum", + "sha512sum" => "hashsum", "dir" => "ls", // dir is an alias for ls diff --git a/src/uucore/src/lib/mods/locale.rs b/src/uucore/src/lib/mods/locale.rs index da5a996d577..a2994b6e38b 100644 --- a/src/uucore/src/lib/mods/locale.rs +++ b/src/uucore/src/lib/mods/locale.rs @@ -164,6 +164,7 @@ fn create_bundle( "sha1sum", "sha224sum", "sha256sum", + "sha384sum", ] .contains(&util_name) { diff --git a/tests/by-util/test_hashsum.rs b/tests/by-util/test_hashsum.rs index ff6d92f2d89..0e23ecd599f 100644 --- a/tests/by-util/test_hashsum.rs +++ b/tests/by-util/test_hashsum.rs @@ -205,7 +205,6 @@ test_digest! {b3sum, b3sum} test_digest! {shake128, shake128} test_digest! {shake256, shake256} -test_digest_with_len! {sha384, sha384, 384} test_digest_with_len! {sha512, sha512, 512} test_digest_with_len! {sha3_224, sha3, 224} test_digest_with_len! {sha3_256, sha3, 256} diff --git a/tests/by-util/test_sha384sum.rs b/tests/by-util/test_sha384sum.rs new file mode 100644 index 00000000000..9dbc730801c --- /dev/null +++ b/tests/by-util/test_sha384sum.rs @@ -0,0 +1,122 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +use uutests::new_ucmd; +// spell-checker:ignore checkfile, testf, ntestf +macro_rules! get_hash( + ($str:expr) => ( + $str.split(' ').collect::>()[0] + ); +); + +macro_rules! test_digest { + ($id:ident) => { + mod $id { + use uutests::util::*; + use uutests::util_name; + static EXPECTED_FILE: &'static str = concat!(stringify!($id), ".expected"); + static CHECK_FILE: &'static str = concat!(stringify!($id), ".checkfile"); + static INPUT_FILE: &'static str = "input.txt"; + + #[test] + fn test_single_file() { + let ts = TestScenario::new(util_name!()); + assert_eq!( + ts.fixtures.read(EXPECTED_FILE), + get_hash!( + ts.ucmd() + .arg(INPUT_FILE) + .succeeds() + .no_stderr() + .stdout_str() + ) + ); + } + + #[test] + fn test_stdin() { + let ts = TestScenario::new(util_name!()); + assert_eq!( + ts.fixtures.read(EXPECTED_FILE), + get_hash!( + ts.ucmd() + .pipe_in_fixture(INPUT_FILE) + .succeeds() + .no_stderr() + .stdout_str() + ) + ); + } + + #[test] + fn test_check() { + let ts = TestScenario::new(util_name!()); + println!("File content='{}'", ts.fixtures.read(INPUT_FILE)); + println!("Check file='{}'", ts.fixtures.read(CHECK_FILE)); + + ts.ucmd() + .args(&["--check", CHECK_FILE]) + .succeeds() + .no_stderr() + .stdout_is("input.txt: OK\n"); + } + + #[test] + fn test_zero() { + let ts = TestScenario::new(util_name!()); + assert_eq!( + ts.fixtures.read(EXPECTED_FILE), + get_hash!( + ts.ucmd() + .arg("--zero") + .arg(INPUT_FILE) + .succeeds() + .no_stderr() + .stdout_str() + ) + ); + } + + #[test] + fn test_missing_file() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + + at.write("a", "file1\n"); + at.write("c", "file3\n"); + + ts.ucmd() + .args(&["a", "b", "c"]) + .fails() + .stdout_contains("a\n") + .stdout_contains("c\n") + .stderr_contains("b: No such file or directory"); + } + } + }; +} + +test_digest! {sha384} + +#[test] +fn test_invalid_arg() { + new_ucmd!().arg("--definitely-invalid").fails_with_code(1); +} + +#[test] +fn test_conflicting_arg() { + new_ucmd!().arg("--tag").arg("--check").fails_with_code(1); + new_ucmd!().arg("--tag").arg("--text").fails_with_code(1); +} + +#[test] +fn test_help_shows_correct_utility_name() { + // Test that help output shows the actual utility name instead of "hashsum" + new_ucmd!() + .arg("--help") + .succeeds() + .stdout_contains("Usage: sha384sum") + .stdout_does_not_contain("Usage: hashsum"); +} diff --git a/tests/fixtures/sha384sum/input.txt b/tests/fixtures/sha384sum/input.txt new file mode 100644 index 00000000000..8c01d89ae06 --- /dev/null +++ b/tests/fixtures/sha384sum/input.txt @@ -0,0 +1 @@ +hello, world \ No newline at end of file diff --git a/tests/fixtures/hashsum/sha384.checkfile b/tests/fixtures/sha384sum/sha384.checkfile similarity index 100% rename from tests/fixtures/hashsum/sha384.checkfile rename to tests/fixtures/sha384sum/sha384.checkfile diff --git a/tests/fixtures/hashsum/sha384.expected b/tests/fixtures/sha384sum/sha384.expected similarity index 100% rename from tests/fixtures/hashsum/sha384.expected rename to tests/fixtures/sha384sum/sha384.expected diff --git a/tests/tests.rs b/tests/tests.rs index 26cf7b7aad0..0cc604994cd 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -88,6 +88,10 @@ mod test_sha224sum; #[path = "by-util/test_sha256sum.rs"] mod test_sha256sum; +#[cfg(feature = "sha384sum")] +#[path = "by-util/test_sha384sum.rs"] +mod test_sha384sum; + #[cfg(feature = "cp")] #[path = "by-util/test_cp.rs"] mod test_cp; From 70c37a72adddc25880e30ab70ed0583071b4d5e9 Mon Sep 17 00:00:00 2001 From: Dorian Peron Date: Fri, 9 Jan 2026 17:58:36 +0100 Subject: [PATCH 10/10] sha512sum: introduce standalone binary --- Cargo.lock | 13 +++ Cargo.toml | 2 + GNUmakefile | 3 - build.rs | 3 - src/common/validation.rs | 3 - src/uu/sha512sum/Cargo.toml | 38 +++++++ src/uu/sha512sum/LICENSE | 1 + src/uu/sha512sum/locales/en-US.ftl | 2 + src/uu/sha512sum/locales/fr-FR.ftl | 2 + src/uu/sha512sum/src/main.rs | 1 + src/uu/sha512sum/src/sha512sum.rs | 1 + src/uucore/src/lib/lib.rs | 3 - src/uucore/src/lib/mods/locale.rs | 1 + tests/by-util/test_hashsum.rs | 1 - tests/by-util/test_sha512sum.rs | 122 ++++++++++++++++++++++ tests/fixtures/sha512sum/input.txt | 1 + tests/fixtures/sha512sum/sha512.checkfile | 1 + tests/fixtures/sha512sum/sha512.expected | 1 + tests/tests.rs | 4 + 19 files changed, 190 insertions(+), 13 deletions(-) create mode 100644 src/uu/sha512sum/Cargo.toml create mode 120000 src/uu/sha512sum/LICENSE create mode 100644 src/uu/sha512sum/locales/en-US.ftl create mode 100644 src/uu/sha512sum/locales/fr-FR.ftl create mode 100644 src/uu/sha512sum/src/main.rs create mode 100644 src/uu/sha512sum/src/sha512sum.rs create mode 100644 tests/by-util/test_sha512sum.rs create mode 100644 tests/fixtures/sha512sum/input.txt create mode 100644 tests/fixtures/sha512sum/sha512.checkfile create mode 100644 tests/fixtures/sha512sum/sha512.expected diff --git a/Cargo.lock b/Cargo.lock index 9db65e310ca..17c05da50f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -631,6 +631,7 @@ dependencies = [ "uu_sha224sum", "uu_sha256sum", "uu_sha384sum", + "uu_sha512sum", "uu_shred", "uu_shuf", "uu_sleep", @@ -4036,6 +4037,18 @@ dependencies = [ "uucore", ] +[[package]] +name = "uu_sha512sum" +version = "0.6.0" +dependencies = [ + "clap", + "codspeed-divan-compat", + "fluent", + "tempfile", + "uu_checksum_common", + "uucore", +] + [[package]] name = "uu_shred" version = "0.6.0" diff --git a/Cargo.toml b/Cargo.toml index a24e87060c1..12e062f8f12 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -92,6 +92,7 @@ feat_common_core = [ "sha224sum", "sha256sum", "sha384sum", + "sha512sum", "comm", "cp", "csplit", @@ -449,6 +450,7 @@ sha1sum = { optional = true, version = "0.6.0", package = "uu_sha1sum", path = " sha224sum = { optional = true, version = "0.6.0", package = "uu_sha224sum", path = "src/uu/sha224sum" } sha256sum = { optional = true, version = "0.6.0", package = "uu_sha256sum", path = "src/uu/sha256sum" } sha384sum = { optional = true, version = "0.6.0", package = "uu_sha384sum", path = "src/uu/sha384sum" } +sha512sum = { optional = true, version = "0.6.0", package = "uu_sha512sum", path = "src/uu/sha512sum" } comm = { optional = true, version = "0.6.0", package = "uu_comm", path = "src/uu/comm" } cp = { optional = true, version = "0.6.0", package = "uu_cp", path = "src/uu/cp" } csplit = { optional = true, version = "0.6.0", package = "uu_csplit", path = "src/uu/csplit" } diff --git a/GNUmakefile b/GNUmakefile index 7805ff9d6d3..b1c8ff2dce1 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -95,9 +95,6 @@ SELINUX_PROGS := \ chcon \ runcon -HASHSUM_PROGS := \ - sha512sum - $(info Detected OS = $(OS)) ifeq (,$(findstring MINGW,$(OS))) diff --git a/build.rs b/build.rs index 8a5e0ec5ca8..a7eb9031225 100644 --- a/build.rs +++ b/build.rs @@ -89,9 +89,6 @@ pub fn main() { } "hashsum" => { phf_map.entry(krate, format!("({krate}::uumain, {krate}::uu_app_custom)")); - - let map_value = format!("({krate}::uumain, {krate}::uu_app_common)"); - phf_map.entry("sha512sum", map_value.clone()); } _ => { phf_map.entry(krate, map_value.clone()); diff --git a/src/common/validation.rs b/src/common/validation.rs index 5c90de222b9..d723ca9262a 100644 --- a/src/common/validation.rs +++ b/src/common/validation.rs @@ -50,9 +50,6 @@ fn get_canonical_util_name(util_name: &str) -> &str { // uu_test aliases - '[' is an alias for test "[" => "test", - // hashsum aliases - all these hash commands are aliases for hashsum - "sha512sum" => "hashsum", - "dir" => "ls", // dir is an alias for ls // Default case - return the util name as is diff --git a/src/uu/sha512sum/Cargo.toml b/src/uu/sha512sum/Cargo.toml new file mode 100644 index 00000000000..0cea1453b07 --- /dev/null +++ b/src/uu/sha512sum/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "uu_sha512sum" +description = "sha512sum ~ (uutils) Print or check the SHA512 checksums" +repository = "https://github.com/uutils/coreutils/tree/main/src/uu/sha512sum" +version.workspace = true +authors.workspace = true +license.workspace = true +homepage.workspace = true +keywords.workspace = true +categories.workspace = true +edition.workspace = true +readme.workspace = true + +[lints] +workspace = true + +[lib] +path = "src/sha512sum.rs" + +[dependencies] +clap = { workspace = true } +uu_checksum_common = { workspace = true } +uucore = { workspace = true, features = [ + "checksum", + "encoding", + "sum", + "hardware", +] } +fluent = { workspace = true } + +[dev-dependencies] +divan = { workspace = true } +tempfile = { workspace = true } +uucore = { workspace = true, features = ["benchmark"] } + +[[bin]] +name = "sha512sum" +path = "src/main.rs" diff --git a/src/uu/sha512sum/LICENSE b/src/uu/sha512sum/LICENSE new file mode 120000 index 00000000000..5853aaea53b --- /dev/null +++ b/src/uu/sha512sum/LICENSE @@ -0,0 +1 @@ +../../../LICENSE \ No newline at end of file diff --git a/src/uu/sha512sum/locales/en-US.ftl b/src/uu/sha512sum/locales/en-US.ftl new file mode 100644 index 00000000000..395a9007771 --- /dev/null +++ b/src/uu/sha512sum/locales/en-US.ftl @@ -0,0 +1,2 @@ +sha512sum-about = Print or check the SHA512 checksums +sha512sum-usage = sha512sum [OPTIONS] [FILE]... diff --git a/src/uu/sha512sum/locales/fr-FR.ftl b/src/uu/sha512sum/locales/fr-FR.ftl new file mode 100644 index 00000000000..59abcc2f9bc --- /dev/null +++ b/src/uu/sha512sum/locales/fr-FR.ftl @@ -0,0 +1,2 @@ +sha512sum-about = Afficher le SHA512 et la taille de chaque fichier +sha512sum-usage = sha512sum [OPTION]... [FICHIER]... diff --git a/src/uu/sha512sum/src/main.rs b/src/uu/sha512sum/src/main.rs new file mode 100644 index 00000000000..64a6ecea622 --- /dev/null +++ b/src/uu/sha512sum/src/main.rs @@ -0,0 +1 @@ +uucore::bin!(uu_sha512sum); diff --git a/src/uu/sha512sum/src/sha512sum.rs b/src/uu/sha512sum/src/sha512sum.rs new file mode 100644 index 00000000000..125d263f0e2 --- /dev/null +++ b/src/uu/sha512sum/src/sha512sum.rs @@ -0,0 +1 @@ +uu_checksum_common::declare_standalone!("sha512sum", uucore::checksum::AlgoKind::Sha512); diff --git a/src/uucore/src/lib/lib.rs b/src/uucore/src/lib/lib.rs index 75b00426b85..228ca3eded6 100644 --- a/src/uucore/src/lib/lib.rs +++ b/src/uucore/src/lib/lib.rs @@ -172,9 +172,6 @@ pub fn get_canonical_util_name(util_name: &str) -> &str { // uu_test aliases - '[' is an alias for test "[" => "test", - // hashsum aliases - all these hash commands are aliases for hashsum - "sha512sum" => "hashsum", - "dir" => "ls", // dir is an alias for ls // Default case - return the util name as is diff --git a/src/uucore/src/lib/mods/locale.rs b/src/uucore/src/lib/mods/locale.rs index a2994b6e38b..a6dad4c6285 100644 --- a/src/uucore/src/lib/mods/locale.rs +++ b/src/uucore/src/lib/mods/locale.rs @@ -165,6 +165,7 @@ fn create_bundle( "sha224sum", "sha256sum", "sha384sum", + "sha512sum", ] .contains(&util_name) { diff --git a/tests/by-util/test_hashsum.rs b/tests/by-util/test_hashsum.rs index 0e23ecd599f..c139469d66c 100644 --- a/tests/by-util/test_hashsum.rs +++ b/tests/by-util/test_hashsum.rs @@ -205,7 +205,6 @@ test_digest! {b3sum, b3sum} test_digest! {shake128, shake128} test_digest! {shake256, shake256} -test_digest_with_len! {sha512, sha512, 512} test_digest_with_len! {sha3_224, sha3, 224} test_digest_with_len! {sha3_256, sha3, 256} test_digest_with_len! {sha3_384, sha3, 384} diff --git a/tests/by-util/test_sha512sum.rs b/tests/by-util/test_sha512sum.rs new file mode 100644 index 00000000000..5e01ad32a18 --- /dev/null +++ b/tests/by-util/test_sha512sum.rs @@ -0,0 +1,122 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +use uutests::new_ucmd; +// spell-checker:ignore checkfile, testf, ntestf +macro_rules! get_hash( + ($str:expr) => ( + $str.split(' ').collect::>()[0] + ); +); + +macro_rules! test_digest { + ($id:ident) => { + mod $id { + use uutests::util::*; + use uutests::util_name; + static EXPECTED_FILE: &'static str = concat!(stringify!($id), ".expected"); + static CHECK_FILE: &'static str = concat!(stringify!($id), ".checkfile"); + static INPUT_FILE: &'static str = "input.txt"; + + #[test] + fn test_single_file() { + let ts = TestScenario::new(util_name!()); + assert_eq!( + ts.fixtures.read(EXPECTED_FILE), + get_hash!( + ts.ucmd() + .arg(INPUT_FILE) + .succeeds() + .no_stderr() + .stdout_str() + ) + ); + } + + #[test] + fn test_stdin() { + let ts = TestScenario::new(util_name!()); + assert_eq!( + ts.fixtures.read(EXPECTED_FILE), + get_hash!( + ts.ucmd() + .pipe_in_fixture(INPUT_FILE) + .succeeds() + .no_stderr() + .stdout_str() + ) + ); + } + + #[test] + fn test_check() { + let ts = TestScenario::new(util_name!()); + println!("File content='{}'", ts.fixtures.read(INPUT_FILE)); + println!("Check file='{}'", ts.fixtures.read(CHECK_FILE)); + + ts.ucmd() + .args(&["--check", CHECK_FILE]) + .succeeds() + .no_stderr() + .stdout_is("input.txt: OK\n"); + } + + #[test] + fn test_zero() { + let ts = TestScenario::new(util_name!()); + assert_eq!( + ts.fixtures.read(EXPECTED_FILE), + get_hash!( + ts.ucmd() + .arg("--zero") + .arg(INPUT_FILE) + .succeeds() + .no_stderr() + .stdout_str() + ) + ); + } + + #[test] + fn test_missing_file() { + let ts = TestScenario::new(util_name!()); + let at = &ts.fixtures; + + at.write("a", "file1\n"); + at.write("c", "file3\n"); + + ts.ucmd() + .args(&["a", "b", "c"]) + .fails() + .stdout_contains("a\n") + .stdout_contains("c\n") + .stderr_contains("b: No such file or directory"); + } + } + }; +} + +test_digest! {sha512} + +#[test] +fn test_invalid_arg() { + new_ucmd!().arg("--definitely-invalid").fails_with_code(1); +} + +#[test] +fn test_conflicting_arg() { + new_ucmd!().arg("--tag").arg("--check").fails_with_code(1); + new_ucmd!().arg("--tag").arg("--text").fails_with_code(1); +} + +#[test] +fn test_help_shows_correct_utility_name() { + // Test that help output shows the actual utility name instead of "hashsum" + new_ucmd!() + .arg("--help") + .succeeds() + .stdout_contains("Usage: sha512sum") + .stdout_does_not_contain("Usage: hashsum"); +} diff --git a/tests/fixtures/sha512sum/input.txt b/tests/fixtures/sha512sum/input.txt new file mode 100644 index 00000000000..8c01d89ae06 --- /dev/null +++ b/tests/fixtures/sha512sum/input.txt @@ -0,0 +1 @@ +hello, world \ No newline at end of file diff --git a/tests/fixtures/sha512sum/sha512.checkfile b/tests/fixtures/sha512sum/sha512.checkfile new file mode 100644 index 00000000000..41a55cabbb5 --- /dev/null +++ b/tests/fixtures/sha512sum/sha512.checkfile @@ -0,0 +1 @@ +8710339dcb6814d0d9d2290ef422285c9322b7163951f9a0ca8f883d3305286f44139aa374848e4174f5aada663027e4548637b6d19894aec4fb6c46a139fbf9 input.txt diff --git a/tests/fixtures/sha512sum/sha512.expected b/tests/fixtures/sha512sum/sha512.expected new file mode 100644 index 00000000000..fd817368620 --- /dev/null +++ b/tests/fixtures/sha512sum/sha512.expected @@ -0,0 +1 @@ +8710339dcb6814d0d9d2290ef422285c9322b7163951f9a0ca8f883d3305286f44139aa374848e4174f5aada663027e4548637b6d19894aec4fb6c46a139fbf9 \ No newline at end of file diff --git a/tests/tests.rs b/tests/tests.rs index 0cc604994cd..d2ecbca10f1 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -92,6 +92,10 @@ mod test_sha256sum; #[path = "by-util/test_sha384sum.rs"] mod test_sha384sum; +#[cfg(feature = "sha512sum")] +#[path = "by-util/test_sha512sum.rs"] +mod test_sha512sum; + #[cfg(feature = "cp")] #[path = "by-util/test_cp.rs"] mod test_cp;