diff --git a/Cargo.lock b/Cargo.lock index 9795b5255..75ab4c2ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -185,21 +185,6 @@ dependencies = [ "syn 2.0.87", ] -[[package]] -name = "bit-set" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" -dependencies = [ - "bit-vec", -] - -[[package]] -name = "bit-vec" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" - [[package]] name = "bitflags" version = "1.3.2" @@ -1557,7 +1542,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", - "libm", ] [[package]] @@ -1821,32 +1805,6 @@ dependencies = [ "hex", ] -[[package]] -name = "proptest" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" -dependencies = [ - "bit-set", - "bit-vec", - "bitflags 2.6.0", - "lazy_static", - "num-traits", - "rand", - "rand_chacha", - "rand_xorshift", - "regex-syntax", - "rusty-fork", - "tempfile", - "unarray", -] - -[[package]] -name = "quick-error" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" - [[package]] name = "quick-error" version = "2.0.1" @@ -1907,15 +1865,6 @@ dependencies = [ "rand_core", ] -[[package]] -name = "rand_xorshift" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" -dependencies = [ - "rand_core", -] - [[package]] name = "rayon" version = "1.10.0" @@ -2084,18 +2033,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rusty-fork" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" -dependencies = [ - "fnv", - "quick-error 1.2.3", - "tempfile", - "wait-timeout", -] - [[package]] name = "same-file" version = "1.0.6" @@ -2482,12 +2419,6 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" -[[package]] -name = "unarray" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" - [[package]] name = "unicode-ident" version = "1.0.13" @@ -2574,7 +2505,6 @@ name = "uu_base32" version = "0.0.28" dependencies = [ "clap", - "proptest", "uucore", ] @@ -2686,7 +2616,7 @@ dependencies = [ "filetime", "indicatif", "libc", - "quick-error 2.0.1", + "quick-error", "selinux", "uucore", "walkdir", @@ -3134,7 +3064,7 @@ dependencies = [ "chrono", "clap", "itertools", - "quick-error 2.0.1", + "quick-error", "regex", "uucore", ] @@ -3631,15 +3561,6 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "wait-timeout" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" -dependencies = [ - "libc", -] - [[package]] name = "walkdir" version = "2.5.0" diff --git a/src/uu/base32/Cargo.toml b/src/uu/base32/Cargo.toml index ffcd4796c..26ab2bc6f 100644 --- a/src/uu/base32/Cargo.toml +++ b/src/uu/base32/Cargo.toml @@ -1,5 +1,3 @@ -# spell-checker:ignore proptest - [package] name = "uu_base32" version = "0.0.28" @@ -22,9 +20,6 @@ path = "src/base32.rs" clap = { workspace = true } uucore = { workspace = true, features = ["encoding"] } -[dev-dependencies] -proptest = "1.5.0" - [[bin]] name = "base32" path = "src/main.rs" diff --git a/src/uu/base32/src/base32.rs b/src/uu/base32/src/base32.rs index 46a0361ea..e14e83921 100644 --- a/src/uu/base32/src/base32.rs +++ b/src/uu/base32/src/base32.rs @@ -5,6 +5,7 @@ pub mod base_common; +use base_common::ReadSeek; use clap::Command; use uucore::{encoding::Format, error::UResult, help_about, help_usage}; @@ -17,7 +18,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { let config = base_common::parse_base_cmd_args(args, ABOUT, USAGE)?; - let mut input = base_common::get_input(&config)?; + let mut input: Box = base_common::get_input(&config)?; base_common::handle_input(&mut input, format, config) } diff --git a/src/uu/base32/src/base_common.rs b/src/uu/base32/src/base_common.rs index 130fe8626..84a461963 100644 --- a/src/uu/base32/src/base_common.rs +++ b/src/uu/base32/src/base_common.rs @@ -3,15 +3,15 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore hexupper lsbf msbf unpadded +// spell-checker:ignore hexupper lsbf msbf unpadded nopad aGVsbG8sIHdvcmxkIQ use clap::{crate_version, Arg, ArgAction, Command}; use std::fs::File; -use std::io::{self, ErrorKind, Read}; +use std::io::{self, ErrorKind, Read, Seek, SeekFrom}; use std::path::{Path, PathBuf}; use uucore::display::Quotable; use uucore::encoding::{ - for_base_common::{BASE32, BASE32HEX, BASE64, BASE64URL, HEXUPPER}, + for_base_common::{BASE32, BASE32HEX, BASE64, BASE64URL, BASE64_NOPAD, HEXUPPER}, Format, Z85Wrapper, BASE2LSBF, BASE2MSBF, }; use uucore::encoding::{EncodingWrapper, SupportsFastDecodeAndEncode}; @@ -143,25 +143,50 @@ pub fn base_app(about: &'static str, usage: &str) -> Command { ) } -pub fn get_input(config: &Config) -> UResult> { +/// A trait alias for types that implement both `Read` and `Seek`. +pub trait ReadSeek: Read + Seek {} + +/// Automatically implement the `ReadSeek` trait for any type that implements both `Read` and `Seek`. +impl ReadSeek for T {} + +pub fn get_input(config: &Config) -> UResult> { match &config.to_read { Some(path_buf) => { // Do not buffer input, because buffering is handled by `fast_decode` and `fast_encode` let file = File::open(path_buf).map_err_context(|| path_buf.maybe_quote().to_string())?; - Ok(Box::new(file)) } None => { - let stdin_lock = io::stdin().lock(); - - Ok(Box::new(stdin_lock)) + let mut buffer = Vec::new(); + io::stdin().read_to_end(&mut buffer)?; + Ok(Box::new(io::Cursor::new(buffer))) } } } -pub fn handle_input(input: &mut dyn Read, format: Format, config: Config) -> UResult<()> { - let supports_fast_decode_and_encode = get_supports_fast_decode_and_encode(format); +/// Determines if the input buffer ends with padding ('=') after trimming trailing whitespace. +fn has_padding(input: &mut R) -> UResult { + let mut buf = Vec::new(); + input + .read_to_end(&mut buf) + .map_err(|err| USimpleError::new(1, format_read_error(err.kind())))?; + + // Reverse iterator and skip trailing whitespace without extra collections + let has_padding = buf + .iter() + .rfind(|&&byte| !byte.is_ascii_whitespace()) + .is_some_and(|&byte| byte == b'='); + + input.seek(SeekFrom::Start(0))?; + Ok(has_padding) +} + +pub fn handle_input(input: &mut R, format: Format, config: Config) -> UResult<()> { + let has_padding = has_padding(input)?; + + let supports_fast_decode_and_encode = + get_supports_fast_decode_and_encode(format, config.decode, has_padding); let supports_fast_decode_and_encode_ref = supports_fast_decode_and_encode.as_ref(); @@ -184,7 +209,11 @@ pub fn handle_input(input: &mut dyn Read, format: Format, config: Config) -> URe } } -pub fn get_supports_fast_decode_and_encode(format: Format) -> Box { +pub fn get_supports_fast_decode_and_encode( + format: Format, + decode: bool, + has_padding: bool, +) -> Box { const BASE16_VALID_DECODING_MULTIPLE: usize = 2; const BASE2_VALID_DECODING_MULTIPLE: usize = 8; const BASE32_VALID_DECODING_MULTIPLE: usize = 8; @@ -231,13 +260,24 @@ pub fn get_supports_fast_decode_and_encode(format: Format) -> Box { + let alphabet: &[u8] = if has_padding { + &b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/="[..] + } else { + &b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/"[..] + }; + let wrapper = if decode && !has_padding { + BASE64_NOPAD + } else { + BASE64 + }; + Box::from(EncodingWrapper::new( + wrapper, + BASE64_VALID_DECODING_MULTIPLE, + BASE64_UNPADDED_MULTIPLE, + alphabet, + )) + } Format::Base64Url => Box::from(EncodingWrapper::new( BASE64URL, BASE64_VALID_DECODING_MULTIPLE, @@ -316,6 +356,7 @@ pub mod fast_encode { encoded_buffer: &mut VecDeque, output: &mut dyn Write, is_cleanup: bool, + empty_wrap: bool, ) -> io::Result<()> { // TODO // `encoded_buffer` only has to be a VecDeque if line wrapping is enabled @@ -324,7 +365,9 @@ pub mod fast_encode { output.write_all(encoded_buffer.make_contiguous())?; if is_cleanup { - output.write_all(b"\n")?; + if !empty_wrap { + output.write_all(b"\n")?; + } } else { encoded_buffer.clear(); } @@ -381,12 +424,13 @@ pub mod fast_encode { encoded_buffer: &mut VecDeque, output: &mut dyn Write, is_cleanup: bool, + empty_wrap: bool, ) -> io::Result<()> { // Write all data in `encoded_buffer` to `output` if let &mut Some(ref mut li) = line_wrapping { write_with_line_breaks(li, encoded_buffer, output, is_cleanup)?; } else { - write_without_line_breaks(encoded_buffer, output, is_cleanup)?; + write_without_line_breaks(encoded_buffer, output, is_cleanup, empty_wrap)?; } Ok(()) @@ -473,9 +517,14 @@ pub mod fast_encode { )?; assert!(leftover_buffer.len() < encode_in_chunks_of_size); - // Write all data in `encoded_buffer` to `output` - write_to_output(&mut line_wrapping, &mut encoded_buffer, output, false)?; + write_to_output( + &mut line_wrapping, + &mut encoded_buffer, + output, + false, + wrap == Some(0), + )?; } Err(er) => { let kind = er.kind(); @@ -499,7 +548,13 @@ pub mod fast_encode { // Write all data in `encoded_buffer` to output // `is_cleanup` triggers special cleanup-only logic - write_to_output(&mut line_wrapping, &mut encoded_buffer, output, true)?; + write_to_output( + &mut line_wrapping, + &mut encoded_buffer, + output, + true, + wrap == Some(0), + )?; } Ok(()) @@ -759,3 +814,33 @@ fn format_read_error(kind: ErrorKind) -> String { format!("read error: {kind_string_capitalized}") } + +#[cfg(test)] +mod tests { + use super::*; + use std::io::Cursor; + + #[test] + fn test_has_padding() { + let test_cases = vec![ + ("aGVsbG8sIHdvcmxkIQ==", true), + ("aGVsbG8sIHdvcmxkIQ== ", true), + ("aGVsbG8sIHdvcmxkIQ==\n", true), + ("aGVsbG8sIHdvcmxkIQ== \n", true), + ("aGVsbG8sIHdvcmxkIQ=", true), + ("aGVsbG8sIHdvcmxkIQ= ", true), + ("aGVsbG8sIHdvcmxkIQ \n", false), + ("aGVsbG8sIHdvcmxkIQ", false), + ]; + + for (input, expected) in test_cases { + let mut cursor = Cursor::new(input.as_bytes()); + assert_eq!( + has_padding(&mut cursor).unwrap(), + expected, + "Failed for input: '{}'", + input + ); + } + } +} diff --git a/src/uu/base32/tests/property_tests.rs b/src/uu/base32/tests/property_tests.rs deleted file mode 100644 index 0f2393c42..000000000 --- a/src/uu/base32/tests/property_tests.rs +++ /dev/null @@ -1,430 +0,0 @@ -// spell-checker:ignore lsbf msbf proptest - -use proptest::{prelude::TestCaseError, prop_assert, prop_assert_eq, test_runner::TestRunner}; -use std::io::Cursor; -use uu_base32::base_common::{fast_decode, fast_encode, get_supports_fast_decode_and_encode}; -use uucore::encoding::{Format, SupportsFastDecodeAndEncode}; - -const CASES: u32 = { - #[cfg(debug_assertions)] - { - 32 - } - - #[cfg(not(debug_assertions))] - { - 128 - } -}; - -const NORMAL_INPUT_SIZE_LIMIT: usize = { - #[cfg(debug_assertions)] - { - // 256 kibibytes - 256 * 1024 - } - - #[cfg(not(debug_assertions))] - { - // 4 mebibytes - 4 * 1024 * 1024 - } -}; - -const LARGE_INPUT_SIZE_LIMIT: usize = 4 * NORMAL_INPUT_SIZE_LIMIT; - -// Note that `TestRunner`s cannot be reused -fn get_test_runner() -> TestRunner { - TestRunner::new(proptest::test_runner::Config { - cases: CASES, - failure_persistence: None, - - ..proptest::test_runner::Config::default() - }) -} - -fn generic_round_trip(format: Format) { - let supports_fast_decode_and_encode = get_supports_fast_decode_and_encode(format); - - let supports_fast_decode_and_encode_ref = supports_fast_decode_and_encode.as_ref(); - - // Make sure empty inputs round trip - { - get_test_runner() - .run( - &( - proptest::bool::ANY, - proptest::bool::ANY, - proptest::option::of(0_usize..512_usize), - ), - |(ignore_garbage, line_wrap_zero, line_wrap)| { - configurable_round_trip( - format, - supports_fast_decode_and_encode_ref, - ignore_garbage, - line_wrap_zero, - line_wrap, - // Do not add garbage - Vec::<(usize, u8)>::new(), - // Empty input - Vec::::new(), - ) - }, - ) - .unwrap(); - } - - // Unusually large line wrapping settings - { - get_test_runner() - .run( - &( - proptest::bool::ANY, - proptest::bool::ANY, - proptest::option::of(512_usize..65_535_usize), - proptest::collection::vec(proptest::num::u8::ANY, 0..NORMAL_INPUT_SIZE_LIMIT), - ), - |(ignore_garbage, line_wrap_zero, line_wrap, input)| { - configurable_round_trip( - format, - supports_fast_decode_and_encode_ref, - ignore_garbage, - line_wrap_zero, - line_wrap, - // Do not add garbage - Vec::<(usize, u8)>::new(), - input, - ) - }, - ) - .unwrap(); - } - - // Spend more time on sane line wrapping settings - { - get_test_runner() - .run( - &( - proptest::bool::ANY, - proptest::bool::ANY, - proptest::option::of(0_usize..512_usize), - proptest::collection::vec(proptest::num::u8::ANY, 0..NORMAL_INPUT_SIZE_LIMIT), - ), - |(ignore_garbage, line_wrap_zero, line_wrap, input)| { - configurable_round_trip( - format, - supports_fast_decode_and_encode_ref, - ignore_garbage, - line_wrap_zero, - line_wrap, - // Do not add garbage - Vec::<(usize, u8)>::new(), - input, - ) - }, - ) - .unwrap(); - } - - // Test with garbage data - { - get_test_runner() - .run( - &( - proptest::bool::ANY, - proptest::bool::ANY, - proptest::option::of(0_usize..512_usize), - // Garbage data to insert - proptest::collection::vec( - ( - // Random index - proptest::num::usize::ANY, - // In all of the encodings being tested, non-ASCII bytes are garbage - 128_u8..=u8::MAX, - ), - 0..4_096, - ), - proptest::collection::vec(proptest::num::u8::ANY, 0..NORMAL_INPUT_SIZE_LIMIT), - ), - |(ignore_garbage, line_wrap_zero, line_wrap, garbage_data, input)| { - configurable_round_trip( - format, - supports_fast_decode_and_encode_ref, - ignore_garbage, - line_wrap_zero, - line_wrap, - garbage_data, - input, - ) - }, - ) - .unwrap(); - } - - // Test small inputs - { - get_test_runner() - .run( - &( - proptest::bool::ANY, - proptest::bool::ANY, - proptest::option::of(0_usize..512_usize), - proptest::collection::vec(proptest::num::u8::ANY, 0..1_024), - ), - |(ignore_garbage, line_wrap_zero, line_wrap, input)| { - configurable_round_trip( - format, - supports_fast_decode_and_encode_ref, - ignore_garbage, - line_wrap_zero, - line_wrap, - // Do not add garbage - Vec::<(usize, u8)>::new(), - input, - ) - }, - ) - .unwrap(); - } - - // Test small inputs with garbage data - { - get_test_runner() - .run( - &( - proptest::bool::ANY, - proptest::bool::ANY, - proptest::option::of(0_usize..512_usize), - // Garbage data to insert - proptest::collection::vec( - ( - // Random index - proptest::num::usize::ANY, - // In all of the encodings being tested, non-ASCII bytes are garbage - 128_u8..=u8::MAX, - ), - 0..1_024, - ), - proptest::collection::vec(proptest::num::u8::ANY, 0..1_024), - ), - |(ignore_garbage, line_wrap_zero, line_wrap, garbage_data, input)| { - configurable_round_trip( - format, - supports_fast_decode_and_encode_ref, - ignore_garbage, - line_wrap_zero, - line_wrap, - garbage_data, - input, - ) - }, - ) - .unwrap(); - } - - // Test large inputs - { - get_test_runner() - .run( - &( - proptest::bool::ANY, - proptest::bool::ANY, - proptest::option::of(0_usize..512_usize), - proptest::collection::vec(proptest::num::u8::ANY, 0..LARGE_INPUT_SIZE_LIMIT), - ), - |(ignore_garbage, line_wrap_zero, line_wrap, input)| { - configurable_round_trip( - format, - supports_fast_decode_and_encode_ref, - ignore_garbage, - line_wrap_zero, - line_wrap, - // Do not add garbage - Vec::<(usize, u8)>::new(), - input, - ) - }, - ) - .unwrap(); - } -} - -fn configurable_round_trip( - format: Format, - supports_fast_decode_and_encode: &dyn SupportsFastDecodeAndEncode, - ignore_garbage: bool, - line_wrap_zero: bool, - line_wrap: Option, - garbage_data: Vec<(usize, u8)>, - mut input: Vec, -) -> Result<(), TestCaseError> { - // Z85 only accepts inputs with lengths divisible by 4 - if let Format::Z85 = format { - // Reduce length of "input" until it is divisible by 4 - input.truncate((input.len() / 4) * 4); - - assert!((input.len() % 4) == 0); - } - - let line_wrap_to_use = if line_wrap_zero { Some(0) } else { line_wrap }; - - let input_len = input.len(); - - let garbage_data_len = garbage_data.len(); - - let garbage_data_is_empty = garbage_data_len == 0; - - let (input, encoded) = { - let mut output = Vec::with_capacity(input_len * 8); - - let mut cursor = Cursor::new(input); - - fast_encode::fast_encode( - &mut cursor, - &mut output, - supports_fast_decode_and_encode, - line_wrap_to_use, - ) - .unwrap(); - - (cursor.into_inner(), output) - }; - - let encoded_or_encoded_with_garbage = if garbage_data_is_empty { - encoded - } else { - let encoded_len = encoded.len(); - - let encoded_highest_index = match encoded_len.checked_sub(1) { - Some(0) | None => None, - Some(x) => Some(x), - }; - - let mut garbage_data_indexed = vec![Option::::None; encoded_len]; - - let mut encoded_with_garbage = Vec::::with_capacity(encoded_len + garbage_data_len); - - for (index, garbage_byte) in garbage_data { - if let Some(x) = encoded_highest_index { - let index_to_use = index % x; - - garbage_data_indexed[index_to_use] = Some(garbage_byte); - } else { - encoded_with_garbage.push(garbage_byte); - } - } - - for (index, encoded_byte) in encoded.into_iter().enumerate() { - encoded_with_garbage.push(encoded_byte); - - if let Some(garbage_byte) = garbage_data_indexed[index] { - encoded_with_garbage.push(garbage_byte); - } - } - - encoded_with_garbage - }; - - match line_wrap_to_use { - Some(0) => { - let line_endings_count = encoded_or_encoded_with_garbage - .iter() - .filter(|byte| **byte == b'\n') - .count(); - - // If line wrapping is disabled, there should only be one '\n' character (at the very end of the output) - prop_assert_eq!(line_endings_count, 1); - } - _ => { - // TODO - // Validate other line wrapping settings - } - } - - let decoded_or_error = { - let mut output = Vec::with_capacity(input_len); - - let mut cursor = Cursor::new(encoded_or_encoded_with_garbage); - - match fast_decode::fast_decode( - &mut cursor, - &mut output, - supports_fast_decode_and_encode, - ignore_garbage, - ) { - Ok(()) => Ok(output), - Err(er) => Err(er), - } - }; - - let made_round_trip = match decoded_or_error { - Ok(ve) => input.as_slice() == ve.as_slice(), - Err(_) => false, - }; - - let result_was_correct = if garbage_data_is_empty || ignore_garbage { - // If there was no garbage data added, or if "ignore_garbage" was enabled, expect the round trip to succeed - made_round_trip - } else { - // If garbage data was added, and "ignore_garbage" was disabled, expect the round trip to fail - - !made_round_trip - }; - - if !result_was_correct { - eprintln!( - "\ -(configurable_round_trip) FAILURE -format: {format:?} -ignore_garbage: {ignore_garbage} -line_wrap_to_use: {line_wrap_to_use:?} -garbage_data_len: {garbage_data_len} -input_len: {input_len} -", - ); - } - - prop_assert!(result_was_correct); - - Ok(()) -} - -#[test] -fn base16_round_trip() { - generic_round_trip(Format::Base16); -} - -#[test] -fn base2lsbf_round_trip() { - generic_round_trip(Format::Base2Lsbf); -} - -#[test] -fn base2msbf_round_trip() { - generic_round_trip(Format::Base2Msbf); -} - -#[test] -fn base32_round_trip() { - generic_round_trip(Format::Base32); -} - -#[test] -fn base32hex_round_trip() { - generic_round_trip(Format::Base32Hex); -} - -#[test] -fn base64_round_trip() { - generic_round_trip(Format::Base64); -} - -#[test] -fn base64url_round_trip() { - generic_round_trip(Format::Base64Url); -} - -#[test] -fn z85_round_trip() { - generic_round_trip(Format::Z85); -} diff --git a/tests/by-util/test_base64.rs b/tests/by-util/test_base64.rs index f07da925f..29b9edf02 100644 --- a/tests/by-util/test_base64.rs +++ b/tests/by-util/test_base64.rs @@ -40,6 +40,28 @@ fn test_encode_repeat_flags_later_wrap_15() { .stdout_only("aGVsbG8sIHdvcmx\nkIQ==\n"); // spell-checker:disable-line } +#[test] +fn test_decode_short() { + let input = "aQ"; + new_ucmd!() + .args(&["--decode"]) + .pipe_in(input) + .succeeds() + .stdout_only("i"); +} + +#[test] +fn test_multi_lines() { + let input = ["aQ\n\n\n", "a\nQ==\n\n\n"]; + for i in input { + new_ucmd!() + .args(&["--decode"]) + .pipe_in(i) + .succeeds() + .stdout_only("i"); + } +} + #[test] fn test_base64_encode_file() { new_ucmd!() @@ -105,6 +127,17 @@ fn test_wrap() { // spell-checker:disable-next-line .stdout_only("VGhlIHF1aWNrIGJyb3du\nIGZveCBqdW1wcyBvdmVy\nIHRoZSBsYXp5IGRvZy4=\n"); } + let input = "hello, world"; + new_ucmd!() + .args(&["--wrap", "0"]) + .pipe_in(input) + .succeeds() + .stdout_only("aGVsbG8sIHdvcmxk"); // spell-checker:disable-line + new_ucmd!() + .args(&["--wrap", "30"]) + .pipe_in(input) + .succeeds() + .stdout_only("aGVsbG8sIHdvcmxk\n"); // spell-checker:disable-line } #[test]