[go: up one dir, main page]

uucore/
lib.rs

1// This file is part of the uutils coreutils package.
2//
3// For the full copyright and license information, please view the LICENSE
4// file that was distributed with this source code.
5//! library ~ (core/bundler file)
6// #![deny(missing_docs)] //TODO: enable this
7//
8// spell-checker:ignore sigaction SIGBUS SIGSEGV extendedbigdecimal myutil logind
9
10// * feature-gated external crates (re-shared as public internal modules)
11#[cfg(feature = "libc")]
12pub extern crate libc;
13#[cfg(all(feature = "windows-sys", target_os = "windows"))]
14pub extern crate windows_sys;
15
16//## internal modules
17
18mod features; // feature-gated code modules
19mod macros; // crate macros (macro_rules-type; exported to `crate::...`)
20mod mods; // core cross-platform modules
21
22pub use uucore_procs::*;
23
24// * cross-platform modules
25pub use crate::mods::clap_localization;
26pub use crate::mods::display;
27pub use crate::mods::error;
28#[cfg(feature = "fs")]
29pub use crate::mods::io;
30pub use crate::mods::line_ending;
31pub use crate::mods::locale;
32pub use crate::mods::os;
33pub use crate::mods::panic;
34pub use crate::mods::posix;
35
36// * feature-gated modules
37#[cfg(feature = "backup-control")]
38pub use crate::features::backup_control;
39#[cfg(feature = "benchmark")]
40pub use crate::features::benchmark;
41#[cfg(feature = "buf-copy")]
42pub use crate::features::buf_copy;
43#[cfg(feature = "checksum")]
44pub use crate::features::checksum;
45#[cfg(feature = "colors")]
46pub use crate::features::colors;
47#[cfg(feature = "encoding")]
48pub use crate::features::encoding;
49#[cfg(feature = "extendedbigdecimal")]
50pub use crate::features::extendedbigdecimal;
51#[cfg(feature = "fast-inc")]
52pub use crate::features::fast_inc;
53#[cfg(feature = "format")]
54pub use crate::features::format;
55#[cfg(feature = "fs")]
56pub use crate::features::fs;
57#[cfg(feature = "i18n-common")]
58pub use crate::features::i18n;
59#[cfg(feature = "lines")]
60pub use crate::features::lines;
61#[cfg(feature = "parser")]
62pub use crate::features::parser;
63#[cfg(feature = "quoting-style")]
64pub use crate::features::quoting_style;
65#[cfg(feature = "ranges")]
66pub use crate::features::ranges;
67#[cfg(feature = "ringbuffer")]
68pub use crate::features::ringbuffer;
69#[cfg(feature = "sum")]
70pub use crate::features::sum;
71#[cfg(feature = "feat_systemd_logind")]
72pub use crate::features::systemd_logind;
73#[cfg(feature = "time")]
74pub use crate::features::time;
75#[cfg(feature = "update-control")]
76pub use crate::features::update_control;
77#[cfg(feature = "uptime")]
78pub use crate::features::uptime;
79#[cfg(feature = "version-cmp")]
80pub use crate::features::version_cmp;
81
82// * (platform-specific) feature-gated modules
83// ** non-windows (i.e. Unix + Fuchsia)
84#[cfg(all(not(windows), feature = "mode"))]
85pub use crate::features::mode;
86// ** unix-only
87#[cfg(all(unix, feature = "entries"))]
88pub use crate::features::entries;
89#[cfg(all(unix, feature = "perms"))]
90pub use crate::features::perms;
91#[cfg(all(unix, any(feature = "pipes", feature = "buf-copy")))]
92pub use crate::features::pipes;
93#[cfg(all(unix, feature = "process"))]
94pub use crate::features::process;
95#[cfg(target_os = "linux")]
96pub use crate::features::safe_traversal;
97#[cfg(all(unix, not(target_os = "fuchsia"), feature = "signals"))]
98pub use crate::features::signals;
99#[cfg(all(
100    unix,
101    not(target_os = "android"),
102    not(target_os = "fuchsia"),
103    not(target_os = "openbsd"),
104    not(target_os = "redox"),
105    feature = "utmpx"
106))]
107pub use crate::features::utmpx;
108// ** windows-only
109#[cfg(all(windows, feature = "wide"))]
110pub use crate::features::wide;
111
112#[cfg(feature = "fsext")]
113pub use crate::features::fsext;
114
115#[cfg(all(unix, feature = "fsxattr"))]
116pub use crate::features::fsxattr;
117
118#[cfg(all(target_os = "linux", feature = "selinux"))]
119pub use crate::features::selinux;
120
121//## core functions
122
123#[cfg(unix)]
124use nix::errno::Errno;
125#[cfg(unix)]
126use nix::sys::signal::{
127    SaFlags, SigAction, SigHandler::SigDfl, SigSet, Signal::SIGBUS, Signal::SIGSEGV, sigaction,
128};
129use std::borrow::Cow;
130use std::ffi::{OsStr, OsString};
131use std::io::{BufRead, BufReader};
132use std::iter;
133#[cfg(unix)]
134use std::os::unix::ffi::{OsStrExt, OsStringExt};
135use std::str;
136use std::str::Utf8Chunk;
137use std::sync::{LazyLock, atomic::Ordering};
138
139/// Disables the custom signal handlers installed by Rust for stack-overflow handling. With those custom signal handlers processes ignore the first SIGBUS and SIGSEGV signal they receive.
140/// See <https://github.com/rust-lang/rust/blob/8ac1525e091d3db28e67adcbbd6db1e1deaa37fb/src/libstd/sys/unix/stack_overflow.rs#L71-L92> for details.
141#[cfg(unix)]
142pub fn disable_rust_signal_handlers() -> Result<(), Errno> {
143    unsafe {
144        sigaction(
145            SIGSEGV,
146            &SigAction::new(SigDfl, SaFlags::empty(), SigSet::all()),
147        )
148    }?;
149    unsafe {
150        sigaction(
151            SIGBUS,
152            &SigAction::new(SigDfl, SaFlags::empty(), SigSet::all()),
153        )
154    }?;
155    Ok(())
156}
157
158pub fn get_canonical_util_name(util_name: &str) -> &str {
159    // remove the "uu_" prefix
160    let util_name = &util_name[3..];
161    match util_name {
162        // uu_test aliases - '[' is an alias for test
163        "[" => "test",
164
165        // hashsum aliases - all these hash commands are aliases for hashsum
166        "md5sum" | "sha1sum" | "sha224sum" | "sha256sum" | "sha384sum" | "sha512sum"
167        | "sha3sum" | "sha3-224sum" | "sha3-256sum" | "sha3-384sum" | "sha3-512sum"
168        | "shake128sum" | "shake256sum" | "b2sum" | "b3sum" => "hashsum",
169
170        "dir" => "ls", // dir is an alias for ls
171
172        // Default case - return the util name as is
173        _ => util_name,
174    }
175}
176
177/// Execute utility code for `util`.
178///
179/// This macro expands to a main function that invokes the `uumain` function in `util`
180/// Exits with code returned by `uumain`.
181#[macro_export]
182macro_rules! bin {
183    ($util:ident) => {
184        pub fn main() {
185            use std::io::Write;
186            use uucore::locale;
187            // suppress extraneous error output for SIGPIPE failures/panics
188            uucore::panic::mute_sigpipe_panic();
189            locale::setup_localization(uucore::get_canonical_util_name(stringify!($util)))
190                .unwrap_or_else(|err| {
191                    match err {
192                        uucore::locale::LocalizationError::ParseResource {
193                            error: err_msg,
194                            snippet,
195                        } => eprintln!("Localization parse error at {snippet}: {err_msg:?}"),
196                        other => eprintln!("Could not init the localization system: {other}"),
197                    }
198                    std::process::exit(99)
199                });
200
201            // execute utility code
202            let code = $util::uumain(uucore::args_os());
203            // (defensively) flush stdout for utility prior to exit; see <https://github.com/rust-lang/rust/issues/23818>
204            if let Err(e) = std::io::stdout().flush() {
205                eprintln!("Error flushing stdout: {e}");
206            }
207
208            std::process::exit(code);
209        }
210    };
211}
212
213/// Generate the version string for clap.
214///
215/// The generated string has the format `(<project name>) <version>`, for
216/// example: "(uutils coreutils) 0.30.0". clap will then prefix it with the util name.
217#[macro_export]
218macro_rules! crate_version {
219    () => {
220        concat!("(uutils coreutils) ", env!("CARGO_PKG_VERSION"))
221    };
222}
223
224/// Generate the usage string for clap.
225///
226/// This function does two things. It indents all but the first line to align
227/// the lines because clap adds "Usage: " to the first line. And it replaces
228/// all occurrences of `{}` with the execution phrase and returns the resulting
229/// `String`. It does **not** support more advanced formatting features such
230/// as `{0}`.
231pub fn format_usage(s: &str) -> String {
232    let s = s.replace('\n', &format!("\n{}", " ".repeat(7)));
233    s.replace("{}", crate::execution_phrase())
234}
235
236/// Creates a localized help template for clap commands.
237///
238/// This function returns a help template that uses the localized
239/// "Usage:" label from the translation files. This ensures consistent
240/// localization across all utilities.
241///
242/// Note: We avoid using clap's `{usage-heading}` placeholder because it is
243/// hardcoded to "Usage:" and cannot be localized. Instead, we manually
244/// construct the usage line with the localized label.
245///
246/// # Parameters
247/// - `util_name`: The name of the utility (for localization setup)
248///
249/// # Example
250/// ```no_run
251/// use clap::Command;
252/// use uucore::localized_help_template;
253///
254/// let app = Command::new("myutil")
255///     .help_template(localized_help_template("myutil"));
256/// ```
257pub fn localized_help_template(util_name: &str) -> clap::builder::StyledStr {
258    use std::io::IsTerminal;
259
260    // Determine if colors should be enabled - same logic as configure_localized_command
261    let colors_enabled = if std::env::var("NO_COLOR").is_ok() {
262        false
263    } else if std::env::var("CLICOLOR_FORCE").is_ok() || std::env::var("FORCE_COLOR").is_ok() {
264        true
265    } else {
266        IsTerminal::is_terminal(&std::io::stdout())
267            && std::env::var("TERM").unwrap_or_default() != "dumb"
268    };
269
270    localized_help_template_with_colors(util_name, colors_enabled)
271}
272
273/// Create a localized help template with explicit color control
274/// This ensures color detection consistency between clap and our template
275pub fn localized_help_template_with_colors(
276    util_name: &str,
277    colors_enabled: bool,
278) -> clap::builder::StyledStr {
279    use std::fmt::Write;
280
281    // Ensure localization is initialized for this utility
282    let _ = crate::locale::setup_localization(util_name);
283
284    // Get the localized "Usage" label
285    let usage_label = crate::locale::translate!("common-usage");
286
287    // Create a styled template
288    let mut template = clap::builder::StyledStr::new();
289
290    // Add the basic template parts
291    writeln!(template, "{{before-help}}{{about-with-newline}}").unwrap();
292
293    // Add styled usage header (bold + underline like clap's default)
294    if colors_enabled {
295        write!(
296            template,
297            "\x1b[1m\x1b[4m{usage_label}:\x1b[0m {{usage}}\n\n"
298        )
299        .unwrap();
300    } else {
301        write!(template, "{usage_label}: {{usage}}\n\n").unwrap();
302    }
303
304    // Add the rest
305    write!(template, "{{all-args}}{{after-help}}").unwrap();
306
307    template
308}
309
310/// Used to check if the utility is the second argument.
311/// Used to check if we were called as a multicall binary (`coreutils <utility>`)
312pub fn get_utility_is_second_arg() -> bool {
313    crate::macros::UTILITY_IS_SECOND_ARG.load(Ordering::SeqCst)
314}
315
316/// Change the value of `UTILITY_IS_SECOND_ARG` to true
317/// Used to specify that the utility is the second argument.
318pub fn set_utility_is_second_arg() {
319    crate::macros::UTILITY_IS_SECOND_ARG.store(true, Ordering::SeqCst);
320}
321
322// args_os() can be expensive to call, it copies all of argv before iterating.
323// So if we want only the first arg or so it's overkill. We cache it.
324static ARGV: LazyLock<Vec<OsString>> = LazyLock::new(|| wild::args_os().collect());
325
326static UTIL_NAME: LazyLock<String> = LazyLock::new(|| {
327    let base_index = usize::from(get_utility_is_second_arg());
328    let is_man = usize::from(ARGV[base_index].eq("manpage"));
329    let argv_index = base_index + is_man;
330
331    // Strip directory path to show only utility name
332    // (e.g., "mkdir" instead of "./target/debug/mkdir")
333    // in version output, error messages, and other user-facing output
334    std::path::Path::new(&ARGV[argv_index])
335        .file_name()
336        .unwrap_or(&ARGV[argv_index])
337        .to_string_lossy()
338        .into_owned()
339});
340
341/// Derive the utility name.
342pub fn util_name() -> &'static str {
343    &UTIL_NAME
344}
345
346static EXECUTION_PHRASE: LazyLock<String> = LazyLock::new(|| {
347    if get_utility_is_second_arg() {
348        ARGV.iter()
349            .take(2)
350            .map(|os_str| os_str.to_string_lossy().into_owned())
351            .collect::<Vec<_>>()
352            .join(" ")
353    } else {
354        ARGV[0].to_string_lossy().into_owned()
355    }
356});
357
358/// Derive the complete execution phrase for "usage".
359pub fn execution_phrase() -> &'static str {
360    &EXECUTION_PHRASE
361}
362
363/// Args contains arguments passed to the utility.
364/// It is a trait that extends `Iterator<Item = OsString>`.
365/// It provides utility functions to collect the arguments into a `Vec<String>`.
366/// The collected `Vec<String>` can be lossy or ignore invalid encoding.
367pub trait Args: Iterator<Item = OsString> + Sized {
368    /// Collects the iterator into a `Vec<String>`, lossily converting the `OsString`s to `Strings`.
369    fn collect_lossy(self) -> Vec<String> {
370        self.map(|s| s.to_string_lossy().into_owned()).collect()
371    }
372
373    /// Collects the iterator into a `Vec<String>`, removing any elements that contain invalid encoding.
374    fn collect_ignore(self) -> Vec<String> {
375        self.filter_map(|s| s.into_string().ok()).collect()
376    }
377}
378
379impl<T: Iterator<Item = OsString> + Sized> Args for T {}
380
381/// Returns an iterator over the command line arguments as `OsString`s.
382/// args_os() can be expensive to call
383pub fn args_os() -> impl Iterator<Item = OsString> {
384    ARGV.iter().cloned()
385}
386
387/// Returns an iterator over the command line arguments as `OsString`s, filtering out empty arguments.
388/// This is useful for handling cases where extra whitespace or empty arguments are present.
389/// args_os_filtered() can be expensive to call
390pub fn args_os_filtered() -> impl Iterator<Item = OsString> {
391    ARGV.iter().filter(|arg| !arg.is_empty()).cloned()
392}
393
394/// Read a line from stdin and check whether the first character is `'y'` or `'Y'`
395pub fn read_yes() -> bool {
396    let mut s = String::new();
397    match std::io::stdin().read_line(&mut s) {
398        Ok(_) => matches!(s.chars().next(), Some('y' | 'Y')),
399        _ => false,
400    }
401}
402
403#[derive(Debug)]
404pub struct NonUtf8OsStrError {
405    input_lossy_string: String,
406}
407
408impl std::fmt::Display for NonUtf8OsStrError {
409    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
410        use os_display::Quotable;
411        let quoted = self.input_lossy_string.quote();
412        f.write_fmt(format_args!(
413            "invalid UTF-8 input {quoted} encountered when converting to bytes on a platform that doesn't expose byte arguments",
414        ))
415    }
416}
417
418impl std::error::Error for NonUtf8OsStrError {}
419impl error::UError for NonUtf8OsStrError {}
420
421/// Converts an `OsStr` to a UTF-8 `&[u8]`.
422///
423/// This always succeeds on unix platforms,
424/// and fails on other platforms if the string can't be coerced to UTF-8.
425pub fn os_str_as_bytes(os_string: &OsStr) -> Result<&[u8], NonUtf8OsStrError> {
426    #[cfg(unix)]
427    return Ok(os_string.as_bytes());
428
429    #[cfg(not(unix))]
430    os_string
431        .to_str()
432        .ok_or_else(|| NonUtf8OsStrError {
433            input_lossy_string: os_string.to_string_lossy().into_owned(),
434        })
435        .map(|s| s.as_bytes())
436}
437
438/// Performs a potentially lossy conversion from `OsStr` to UTF-8 bytes.
439///
440/// This is always lossless on unix platforms,
441/// and wraps [`OsStr::to_string_lossy`] on non-unix platforms.
442pub fn os_str_as_bytes_lossy(os_string: &OsStr) -> Cow<'_, [u8]> {
443    #[cfg(unix)]
444    return Cow::from(os_string.as_bytes());
445
446    #[cfg(not(unix))]
447    match os_string.to_string_lossy() {
448        Cow::Borrowed(slice) => Cow::from(slice.as_bytes()),
449        Cow::Owned(owned) => Cow::from(owned.into_bytes()),
450    }
451}
452
453/// Converts a `&[u8]` to an `&OsStr`,
454/// or parses it as UTF-8 into an [`OsString`] on non-unix platforms.
455///
456/// This always succeeds on unix platforms,
457/// and fails on other platforms if the bytes can't be parsed as UTF-8.
458pub fn os_str_from_bytes(bytes: &[u8]) -> mods::error::UResult<Cow<'_, OsStr>> {
459    #[cfg(unix)]
460    return Ok(Cow::Borrowed(OsStr::from_bytes(bytes)));
461
462    #[cfg(not(unix))]
463    Ok(Cow::Owned(OsString::from(str::from_utf8(bytes).map_err(
464        |_| mods::error::UUsageError::new(1, "Unable to transform bytes into OsStr"),
465    )?)))
466}
467
468/// Converts a `Vec<u8>` into an `OsString`, parsing as UTF-8 on non-unix platforms.
469///
470/// This always succeeds on unix platforms,
471/// and fails on other platforms if the bytes can't be parsed as UTF-8.
472pub fn os_string_from_vec(vec: Vec<u8>) -> mods::error::UResult<OsString> {
473    #[cfg(unix)]
474    return Ok(OsString::from_vec(vec));
475
476    #[cfg(not(unix))]
477    Ok(OsString::from(String::from_utf8(vec).map_err(|_| {
478        mods::error::UUsageError::new(1, "invalid UTF-8 was detected in one or more arguments")
479    })?))
480}
481
482/// Converts an `OsString` into a `Vec<u8>`, parsing as UTF-8 on non-unix platforms.
483///
484/// This always succeeds on unix platforms,
485/// and fails on other platforms if the bytes can't be parsed as UTF-8.
486pub fn os_string_to_vec(s: OsString) -> mods::error::UResult<Vec<u8>> {
487    #[cfg(unix)]
488    let v = s.into_vec();
489    #[cfg(not(unix))]
490    let v = s
491        .into_string()
492        .map_err(|_| {
493            mods::error::UUsageError::new(1, "invalid UTF-8 was detected in one or more arguments")
494        })?
495        .into();
496
497    Ok(v)
498}
499
500/// Equivalent to `std::BufRead::lines` which outputs each line as a `Vec<u8>`,
501/// which avoids panicking on non UTF-8 input.
502pub fn read_byte_lines<R: std::io::Read>(
503    mut buf_reader: BufReader<R>,
504) -> impl Iterator<Item = Vec<u8>> {
505    iter::from_fn(move || {
506        let mut buf = Vec::with_capacity(256);
507        let size = buf_reader.read_until(b'\n', &mut buf).ok()?;
508
509        if size == 0 {
510            return None;
511        }
512
513        // Trim (\r)\n
514        if buf.ends_with(b"\n") {
515            buf.pop();
516            if buf.ends_with(b"\r") {
517                buf.pop();
518            }
519        }
520
521        Some(buf)
522    })
523}
524
525/// Equivalent to `std::BufRead::lines` which outputs each line as an `OsString`
526/// This won't panic on non UTF-8 characters on Unix,
527/// but it still will on Windows.
528pub fn read_os_string_lines<R: std::io::Read>(
529    buf_reader: BufReader<R>,
530) -> impl Iterator<Item = OsString> {
531    read_byte_lines(buf_reader).map(|byte_line| os_string_from_vec(byte_line).expect("UTF-8 error"))
532}
533
534/// Prompt the user with a formatted string and returns `true` if they reply `'y'` or `'Y'`
535///
536/// This macro functions accepts the same syntax as `format!`. The prompt is written to
537/// `stderr`. A space is also printed at the end for nice spacing between the prompt and
538/// the user input. Any input starting with `'y'` or `'Y'` is interpreted as `yes`.
539///
540/// # Examples
541/// ```
542/// use uucore::prompt_yes;
543/// let file = "foo.rs";
544/// prompt_yes!("Do you want to delete '{file}'?");
545/// ```
546/// will print something like below to `stderr` (with `util_name` substituted by the actual
547/// util name) and will wait for user input.
548/// ```txt
549/// util_name: Do you want to delete 'foo.rs'?
550/// ```
551#[macro_export]
552macro_rules! prompt_yes(
553    ($($args:tt)+) => ({
554        use std::io::Write;
555        eprint!("{}: ", uucore::util_name());
556        eprint!($($args)+);
557        eprint!(" ");
558        let res = std::io::stderr().flush().map_err(|err| {
559            $crate::error::USimpleError::new(1, err.to_string())
560        });
561        uucore::show_if_err!(res);
562        uucore::read_yes()
563    })
564);
565
566/// Represent either a character or a byte.
567/// Used to iterate on partially valid UTF-8 data
568#[derive(Debug, Clone, Copy, PartialEq, Eq)]
569pub enum CharByte {
570    Char(char),
571    Byte(u8),
572}
573
574impl From<char> for CharByte {
575    fn from(value: char) -> Self {
576        Self::Char(value)
577    }
578}
579
580impl From<u8> for CharByte {
581    fn from(value: u8) -> Self {
582        Self::Byte(value)
583    }
584}
585
586impl From<&u8> for CharByte {
587    fn from(value: &u8) -> Self {
588        Self::Byte(*value)
589    }
590}
591
592struct Utf8ChunkIterator<'a> {
593    iter: Box<dyn Iterator<Item = CharByte> + 'a>,
594}
595
596impl Iterator for Utf8ChunkIterator<'_> {
597    type Item = CharByte;
598
599    fn next(&mut self) -> Option<Self::Item> {
600        self.iter.next()
601    }
602}
603
604impl<'a> From<Utf8Chunk<'a>> for Utf8ChunkIterator<'a> {
605    fn from(chk: Utf8Chunk<'a>) -> Self {
606        Self {
607            iter: Box::new(
608                chk.valid()
609                    .chars()
610                    .map(CharByte::from)
611                    .chain(chk.invalid().iter().map(CharByte::from)),
612            ),
613        }
614    }
615}
616
617/// Iterates on the valid and invalid parts of a byte sequence with regard to
618/// the UTF-8 encoding.
619pub struct CharByteIterator<'a> {
620    iter: Box<dyn Iterator<Item = CharByte> + 'a>,
621}
622
623impl<'a> CharByteIterator<'a> {
624    /// Make a `CharByteIterator` from a byte slice.
625    /// [`CharByteIterator`]
626    pub fn new(input: &'a [u8]) -> Self {
627        Self {
628            iter: Box::new(input.utf8_chunks().flat_map(Utf8ChunkIterator::from)),
629        }
630    }
631}
632
633impl Iterator for CharByteIterator<'_> {
634    type Item = CharByte;
635
636    fn next(&mut self) -> Option<Self::Item> {
637        self.iter.next()
638    }
639}
640
641pub trait IntoCharByteIterator<'a> {
642    fn iter_char_bytes(self) -> CharByteIterator<'a>;
643}
644
645impl<'a> IntoCharByteIterator<'a> for &'a [u8] {
646    fn iter_char_bytes(self) -> CharByteIterator<'a> {
647        CharByteIterator::new(self)
648    }
649}
650
651#[cfg(test)]
652mod tests {
653    use super::*;
654    use std::ffi::OsStr;
655
656    fn make_os_vec(os_str: &OsStr) -> Vec<OsString> {
657        vec![
658            OsString::from("test"),
659            OsString::from("สวัสดี"), // spell-checker:disable-line
660            os_str.to_os_string(),
661        ]
662    }
663
664    #[cfg(any(unix, target_os = "redox"))]
665    fn test_invalid_utf8_args_lossy(os_str: &OsStr) {
666        // assert our string is invalid utf8
667        assert!(os_str.to_os_string().into_string().is_err());
668        let test_vec = make_os_vec(os_str);
669        let collected_to_str = test_vec.clone().into_iter().collect_lossy();
670        // conservation of length - when accepting lossy conversion no arguments may be dropped
671        assert_eq!(collected_to_str.len(), test_vec.len());
672        // first indices identical
673        for index in 0..2 {
674            assert_eq!(collected_to_str[index], test_vec[index].to_str().unwrap());
675        }
676        // lossy conversion for string with illegal encoding is done
677        assert_eq!(
678            *collected_to_str[2],
679            os_str.to_os_string().to_string_lossy()
680        );
681    }
682
683    #[cfg(any(unix, target_os = "redox"))]
684    fn test_invalid_utf8_args_ignore(os_str: &OsStr) {
685        // assert our string is invalid utf8
686        assert!(os_str.to_os_string().into_string().is_err());
687        let test_vec = make_os_vec(os_str);
688        let collected_to_str = test_vec.clone().into_iter().collect_ignore();
689        // assert that the broken entry is filtered out
690        assert_eq!(collected_to_str.len(), test_vec.len() - 1);
691        // assert that the unbroken indices are converted as expected
692        for index in 0..2 {
693            assert_eq!(
694                collected_to_str.get(index).unwrap(),
695                test_vec.get(index).unwrap().to_str().unwrap()
696            );
697        }
698    }
699
700    #[test]
701    fn valid_utf8_encoding_args() {
702        // create a vector containing only correct encoding
703        let test_vec = make_os_vec(&OsString::from("test2"));
704        // expect complete conversion without losses, even when lossy conversion is accepted
705        let _ = test_vec.into_iter().collect_lossy();
706    }
707
708    #[cfg(any(unix, target_os = "redox"))]
709    #[test]
710    fn invalid_utf8_args_unix() {
711        use std::os::unix::ffi::OsStrExt;
712
713        let source = [0x66, 0x6f, 0x80, 0x6f];
714        let os_str = OsStr::from_bytes(&source[..]);
715        test_invalid_utf8_args_lossy(os_str);
716        test_invalid_utf8_args_ignore(os_str);
717    }
718
719    #[test]
720    fn test_format_usage() {
721        assert_eq!(format_usage("expr EXPRESSION"), "expr EXPRESSION");
722        assert_eq!(
723            format_usage("expr EXPRESSION\nexpr OPTION"),
724            "expr EXPRESSION\n       expr OPTION"
725        );
726    }
727}