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