Repository: clap-rs/clap
Pull request #3667 (2022-04-30 01:28:47)

fix(help): Make help output more consistent
This started off with working towards one problem but I ended up getting side tracked when I noticed some of these bugs in the help output
- We inconsistently generate the usage string for help
- We showed a bad error for `foo help sub doesnt_exist`

As part of this, we moved the help-subcommand to work more like how the parser traversed `Command`s.


Patch:
diff --git a/Cargo.toml b/Cargo.toml
index 4fe3edbd..8f3dd23f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -142,6 +142,7 @@ rustversion = "1"
 # Cutting out `filesystem` feature
 trycmd = { version = "0.13", default-features = false, features = ["color-auto", "diff", "examples"] }
 humantime = "2"
+snapbox = "0.2.9"
 
 [[example]]
 name = "demo"
diff --git a/src/build/command.rs b/src/build/command.rs
index 5e273a0c..d9697af5 100644
--- a/src/build/command.rs
+++ b/src/build/command.rs
@@ -644,13 +644,13 @@ impl<'help> App<'help> {
                     // Stop borrowing command so we can get another mut ref to it.
                     let command = command.to_owned();
                     debug!(
-                        "App::try_get_matches_from_mut: Parsed command {} from argv",
+                        "Command::try_get_matches_from_mut: Parsed command {} from argv",
                         command
                     );
 
-                    debug!("App::try_get_matches_from_mut: Reinserting command into arguments so subcommand parser matches it");
+                    debug!("Command::try_get_matches_from_mut: Reinserting command into arguments so subcommand parser matches it");
                     raw_args.insert(&cursor, &[&command]);
-                    debug!("App::try_get_matches_from_mut: Clearing name and bin_name so that displayed command name starts with applet name");
+                    debug!("Command::try_get_matches_from_mut: Clearing name and bin_name so that displayed command name starts with applet name");
                     self.name.clear();
                     self.bin_name = None;
                     return self._do_parse(&mut raw_args, cursor);
@@ -695,7 +695,7 @@ impl<'help> App<'help> {
     /// ```
     /// [`io::stdout()`]: std::io::stdout()
     pub fn print_help(&mut self) -> io::Result<()> {
-        self._build();
+        self._build_self();
         let color = self.get_color();
 
         let mut c = Colorizer::new(Stream::Stdout, color);
@@ -720,7 +720,7 @@ impl<'help> App<'help> {
     /// [`-h` (short)]: Arg::help()
     /// [`--help` (long)]: Arg::long_help()
     pub fn print_long_help(&mut self) -> io::Result<()> {
-        self._build();
+        self._build_self();
         let color = self.get_color();
 
         let mut c = Colorizer::new(Stream::Stdout, color);
@@ -746,7 +746,7 @@ impl<'help> App<'help> {
     /// [`-h` (short)]: Arg::help()
     /// [`--help` (long)]: Arg::long_help()
     pub fn write_help<W: io::Write>(&mut self, w: &mut W) -> io::Result<()> {
-        self._build();
+        self._build_self();
 
         let usage = Usage::new(self);
         Help::new(HelpWriter::Normal(w), self, &usage, false).write_help()?;
@@ -770,7 +770,7 @@ impl<'help> App<'help> {
     /// [`-h` (short)]: Arg::help()
     /// [`--help` (long)]: Arg::long_help()
     pub fn write_long_help<W: io::Write>(&mut self, w: &mut W) -> io::Result<()> {
-        self._build();
+        self._build_self();
 
         let usage = Usage::new(self);
         Help::new(HelpWriter::Normal(w), self, &usage, true).write_help()?;
@@ -838,7 +838,7 @@ impl<'help> App<'help> {
     pub fn render_usage(&mut self) -> String {
         // If there are global arguments, or settings we need to propagate them down to subcommands
         // before parsing incase we run into a subcommand
-        self._build();
+        self._build_self();
 
         Usage::new(self).create_usage_with_title(&[])
     }
@@ -3299,7 +3299,7 @@ impl<'help> App<'help> {
     /// Should we color the output?
     #[inline(never)]
     pub fn get_color(&self) -> ColorChoice {
-        debug!("App::color: Color setting...");
+        debug!("Command::color: Color setting...");
 
         if cfg!(feature = "color") {
             #[allow(deprecated)]
@@ -3447,7 +3447,7 @@ impl<'help> App<'help> {
                 .iter()
                 .map(|id| {
                     self.args.args().find(|arg| arg.id == *id).expect(
-                        "App::get_arg_conflicts_with: \
+                        "Command::get_arg_conflicts_with: \
                     The passed arg conflicts with an arg unknown to the cmd",
                     )
                 })
@@ -3478,7 +3478,7 @@ impl<'help> App<'help> {
                     )
                     .find(|arg| arg.id == *id)
                     .expect(
-                        "App::get_arg_conflicts_with: \
+                        "Command::get_arg_conflicts_with: \
                     The passed arg conflicts with an arg unknown to the cmd",
                     )
             })
@@ -3959,11 +3959,11 @@ impl<'help> App<'help> {
         raw_args: &mut clap_lex::RawArgs,
         args_cursor: clap_lex::ArgCursor,
     ) -> ClapResult<ArgMatches> {
-        debug!("App::_do_parse");
+        debug!("Command::_do_parse");
 
         // If there are global arguments, or settings we need to propagate them down to subcommands
         // before parsing in case we run into a subcommand
-        self._build();
+        self._build_self();
 
         let mut matcher = ArgMatcher::new(self);
 
@@ -3971,7 +3971,7 @@ impl<'help> App<'help> {
         let mut parser = Parser::new(self);
         if let Err(error) = parser.get_matches_with(&mut matcher, raw_args, args_cursor) {
             if self.is_set(AppSettings::IgnoreErrors) {
-                debug!("App::_do_parse: ignoring error: {}", error);
+                debug!("Command::_do_parse: ignoring error: {}", error);
             } else {
                 return Err(error);
             }
@@ -3991,16 +3991,84 @@ impl<'help> App<'help> {
         self.build();
     }
 
+    #[doc(hidden)]
+    #[deprecated(since = "3.1.10", note = "Replaced with `Command::build`")]
+    pub fn _build(&mut self) {
+        self._build_self()
+    }
+
+    #[doc(hidden)]
+    #[deprecated(since = "3.1.13", note = "Replaced with `Command::build`")]
+    pub fn _build_bin_names(&mut self) {
+        self._build_bin_names_internal();
+    }
+
     /// Prepare for introspecting on all included [`Command`]s
     ///
     /// Call this on the top-level [`Command`] when done building and before reading state for
     /// cases like completions, custom help output, etc.
     pub fn build(&mut self) {
-        self._build();
+        self._build_self();
         for subcmd in self.get_subcommands_mut() {
-            subcmd._build();
+            subcmd._build_self();
+        }
+        self._build_bin_names_internal();
+    }
+
+    pub(crate) fn _build_self(&mut self) {
+        debug!("Command::_build");
+        if !self.settings.is_set(AppSettings::Built) {
+            // Make sure all the globally set flags apply to us as well
+            self.settings = self.settings | self.g_settings;
+
+            self._propagate();
+            self._check_help_and_version();
+            self._propagate_global_args();
+            self._derive_display_order();
+
+            let mut pos_counter = 1;
+            let self_override = self.is_set(AppSettings::AllArgsOverrideSelf);
+            let hide_pv = self.is_set(AppSettings::HidePossibleValues);
+            for a in self.args.args_mut() {
+                // Fill in the groups
+                for g in &a.groups {
+                    if let Some(ag) = self.groups.iter_mut().find(|grp| grp.id == *g) {
+                        ag.args.push(a.id.clone());
+                    } else {
+                        let mut ag = ArgGroup::with_id(g.clone());
+                        ag.args.push(a.id.clone());
+                        self.groups.push(ag);
+                    }
+                }
+
+                // Figure out implied settings
+                if a.is_last_set() {
+                    // if an arg has `Last` set, we need to imply DontCollapseArgsInUsage so that args
+                    // in the usage string don't get confused or left out.
+                    self.settings.set(AppSettings::DontCollapseArgsInUsage);
+                }
+                if hide_pv && a.is_takes_value_set() {
+                    a.settings.set(ArgSettings::HidePossibleValues);
+                }
+                if self_override {
+                    let self_id = a.id.clone();
+                    a.overrides.push(self_id);
+                }
+                a._build();
+                if a.is_positional() && a.index.is_none() {
+                    a.index = Some(pos_counter);
+                    pos_counter += 1;
+                }
+            }
+
+            self.args._build();
+
+            #[cfg(debug_assertions)]
+            assert_app(self);
+            self.settings.set(AppSettings::Built);
+        } else {
+            debug!("Command::_build: already built");
         }
-        self._build_bin_names();
     }
 
     pub(crate) fn _build_subcommand(&mut self, name: &str) -> Option<&mut Self> {
@@ -4034,12 +4102,12 @@ impl<'help> App<'help> {
             sc_names = format!("{{{}}}", sc_names);
         }
 
-        sc.usage_name = Some(
-            self.bin_name
-                .as_ref()
-                .map(|bin_name| format!("{}{}{}", bin_name, mid_string, sc_names))
-                .unwrap_or(sc_names),
-        );
+        let usage_name = self
+            .bin_name
+            .as_ref()
+            .map(|bin_name| format!("{}{}{}", bin_name, mid_string, sc_names))
+            .unwrap_or(sc_names);
+        sc.usage_name = Some(usage_name);
 
         // bin_name should be parent's bin_name + [<reqs>] + the sc's name separated by
         // a space
@@ -4051,70 +4119,86 @@ impl<'help> App<'help> {
         ));
 
         // Ensure all args are built and ready to parse
-        sc._build();
+        sc._build_self();
 
         Some(sc)
     }
 
-    #[doc(hidden)]
-    #[deprecated(since = "3.1.10", note = "Replaced with `Command::build`")]
-    pub fn _build(&mut self) {
-        self._build_self()
-    }
+    fn _build_bin_names_internal(&mut self) {
+        debug!("Command::_build_bin_names");
 
-    pub(crate) fn _build_self(&mut self) {
-        debug!("App::_build");
-        if !self.settings.is_set(AppSettings::Built) {
-            // Make sure all the globally set flags apply to us as well
-            self.settings = self.settings | self.g_settings;
+        if !self.is_set(AppSettings::BinNameBuilt) {
+            let mut mid_string = String::from(" ");
+            if !self.is_subcommand_negates_reqs_set() {
+                let reqs = Usage::new(self).get_required_usage_from(&[], None, true); // maybe Some(m)
 
-            self._propagate();
-            self._check_help_and_version();
-            self._propagate_global_args();
-            self._derive_display_order();
+                for s in &reqs {
+                    mid_string.push_str(s);
+                    mid_string.push(' ');
+                }
+            }
 
-            let mut pos_counter = 1;
-            let self_override = self.is_set(AppSettings::AllArgsOverrideSelf);
-            let hide_pv = self.is_set(AppSettings::HidePossibleValues);
-            for a in self.args.args_mut() {
-                // Fill in the groups
-                for g in &a.groups {
-                    if let Some(ag) = self.groups.iter_mut().find(|grp| grp.id == *g) {
-                        ag.args.push(a.id.clone());
-                    } else {
-                        let mut ag = ArgGroup::with_id(g.clone());
-                        ag.args.push(a.id.clone());
-                        self.groups.push(ag);
+            for mut sc in &mut self.subcommands {
+                debug!("Command::_build_bin_names:iter: bin_name set...");
+
+                if sc.usage_name.is_none() {
+                    use std::fmt::Write;
+                    // Display subcommand name, short and long in usage
+                    let mut sc_names = sc.name.clone();
+                    let mut flag_subcmd = false;
+                    if let Some(l) = sc.long_flag {
+                        write!(sc_names, "|--{}", l).unwrap();
+                        flag_subcmd = true;
+                    }
+                    if let Some(s) = sc.short_flag {
+                        write!(sc_names, "|-{}", s).unwrap();
+                        flag_subcmd = true;
                     }
-                }
 
-                // Figure out implied settings
-                if a.is_last_set() {
-                    // if an arg has `Last` set, we need to imply DontCollapseArgsInUsage so that args
-                    // in the usage string don't get confused or left out.
-                    self.settings.set(AppSettings::DontCollapseArgsInUsage);
-                }
-                if hide_pv && a.is_takes_value_set() {
-                    a.settings.set(ArgSettings::HidePossibleValues);
-                }
-                if self_override {
-                    let self_id = a.id.clone();
-                    a.overrides.push(self_id);
+                    if flag_subcmd {
+                        sc_names = format!("{{{}}}", sc_names);
+                    }
+
+                    let usage_name = format!(
+                        "{}{}{}",
+                        self.bin_name.as_ref().unwrap_or(&self.name),
+                        mid_string,
+                        sc_names
+                    );
+                    debug!(
+                        "Command::_build_bin_names:iter: Setting usage_name of {} to {:?}",
+                        sc.name, usage_name
+                    );
+                    sc.usage_name = Some(usage_name);
+                } else {
+                    debug!(
+                        "Command::_build_bin_names::iter: Using existing usage_name of {} ({:?})",
+                        sc.name, sc.usage_name
+                    );
                 }
-                a._build();
-                if a.is_positional() && a.index.is_none() {
-                    a.index = Some(pos_counter);
-                    pos_counter += 1;
+
+                if sc.bin_name.is_none() {
+                    let bin_name = format!(
+                        "{} {}",
+                        self.bin_name.as_ref().unwrap_or(&self.name),
+                        &*sc.name
+                    );
+                    debug!(
+                        "Command::_build_bin_names:iter: Setting bin_name of {} to {:?}",
+                        sc.name, bin_name
+                    );
+                    sc.bin_name = Some(bin_name);
+                } else {
+                    debug!(
+                        "Command::_build_bin_names::iter: Using existing bin_name of {} ({:?})",
+                        sc.name, sc.bin_name
+                    );
                 }
+                sc._build_bin_names_internal();
             }
-
-            self.args._build();
-
-            #[cfg(debug_assertions)]
-            assert_app(self);
-            self.settings.set(AppSettings::Built);
+            self.set(AppSettings::BinNameBuilt);
         } else {
-            debug!("App::_build: already built");
+            debug!("Command::_build_bin_names: already built");
         }
     }
 
@@ -4158,7 +4242,7 @@ impl<'help> App<'help> {
 
     /// Propagate global args
     pub(crate) fn _propagate_global_args(&mut self) {
-        debug!("App::_propagate_global_args:{}", self.name);
+        debug!("Command::_propagate_global_args:{}", self.name);
 
         for sc in &mut self.subcommands {
             for a in self.args.args().filter(|a| a.is_global_set()) {
@@ -4192,7 +4276,7 @@ impl<'help> App<'help> {
 
     /// Propagate settings
     pub(crate) fn _propagate(&mut self) {
-        debug!("App::_propagate:{}", self.name);
+        debug!("Command::_propagate:{}", self.name);
         let mut subcommands = std::mem::take(&mut self.subcommands);
         for sc in &mut subcommands {
             self._propagate_subcommand(sc);
@@ -4222,7 +4306,7 @@ impl<'help> App<'help> {
 
     #[allow(clippy::blocks_in_if_conditions)]
     pub(crate) fn _check_help_and_version(&mut self) {
-        debug!("App::_check_help_and_version: {}", self.name);
+        debug!("Command::_check_help_and_version: {}", self.name);
 
         if self.is_set(AppSettings::DisableHelpFlag)
             || self.args.args().any(|x| {
@@ -4234,7 +4318,7 @@ impl<'help> App<'help> {
                 .iter()
                 .any(|sc| sc.long_flag == Some("help"))
         {
-            debug!("App::_check_help_and_version: Removing generated help");
+            debug!("Command::_check_help_and_version: Removing generated help");
 
             let generated_help_pos = self
                 .args
@@ -4277,7 +4361,7 @@ To change `help`s short, call `cmd.arg(Arg::new(\"help\")...)`.",
                     .expect(INTERNAL_ERROR_MSG);
                 help.short = Some('h');
             } else {
-                debug!("App::_check_help_and_version: Removing `-h` from help");
+                debug!("Command::_check_help_and_version: Removing `-h` from help");
             }
         }
 
@@ -4298,7 +4382,7 @@ To change `help`s short, call `cmd.arg(Arg::new(\"help\")...)`.",
                 .iter()
                 .any(|sc| sc.long_flag == Some("version"))
         {
-            debug!("App::_check_help_and_version: Removing generated version");
+            debug!("Command::_check_help_and_version: Removing generated version");
 
             // This is the check mentioned above that only checks for Generated, not
             // GeneratedMutated args by design.
@@ -4339,7 +4423,7 @@ To change `help`s short, call `cmd.arg(Arg::new(\"help\")...)`.",
             && self.has_subcommands()
             && !self.subcommands.iter().any(|s| s.id == Id::help_hash())
         {
-            debug!("App::_check_help_and_version: Building help subcommand");
+            debug!("Command::_check_help_and_version: Building help subcommand");
             let mut help_subcmd = App::new("help")
                 .about("Print this message or the help of the given subcommand(s)")
                 .arg(
@@ -4365,7 +4449,7 @@ To change `help`s short, call `cmd.arg(Arg::new(\"help\")...)`.",
     }
 
     pub(crate) fn _derive_display_order(&mut self) {
-        debug!("App::_derive_display_order:{}", self.name);
+        debug!("Command::_derive_display_order:{}", self.name);
 
         if self.settings.is_set(AppSettings::DeriveDisplayOrder) {
             for a in self
@@ -4385,45 +4469,8 @@ To change `help`s short, call `cmd.arg(Arg::new(\"help\")...)`.",
         }
     }
 
-    // used in clap_complete (https://github.com/clap-rs/clap_complete)
-    #[doc(hidden)]
-    pub fn _build_bin_names(&mut self) {
-        debug!("App::_build_bin_names");
-
-        if !self.is_set(AppSettings::BinNameBuilt) {
-            for mut sc in &mut self.subcommands {
-                debug!("App::_build_bin_names:iter: bin_name set...");
-
-                if sc.bin_name.is_none() {
-                    debug!("No");
-                    let bin_name = format!(
-                        "{}{}{}",
-                        self.bin_name.as_ref().unwrap_or(&self.name),
-                        if self.bin_name.is_some() { " " } else { "" },
-                        &*sc.name
-                    );
-                    debug!(
-                        "App::_build_bin_names:iter: Setting bin_name of {} to {}",
-                        self.name, bin_name
-                    );
-                    sc.bin_name = Some(bin_name);
-                } else {
-                    debug!("yes ({:?})", sc.bin_name);
-                }
-                debug!(
-                    "App::_build_bin_names:iter: Calling build_bin_names from...{}",
-                    sc.name
-                );
-                sc._build_bin_names();
-            }
-            self.set(AppSettings::BinNameBuilt);
-        } else {
-            debug!("App::_build_bin_names: already built");
-        }
-    }
-
     pub(crate) fn _render_version(&self, use_long: bool) -> String {
-        debug!("App::_render_version");
+        debug!("Command::_render_version");
 
         let ver = if use_long {
             self.long_version.or(self.version).unwrap_or("")
@@ -4560,7 +4607,7 @@ impl<'help> App<'help> {
 
     /// Iterate through the groups this arg is member of.
     pub(crate) fn groups_for_arg<'a>(&'a self, arg: &Id) -> impl Iterator<Item = Id> + 'a {
-        debug!("App::groups_for_arg: id={:?}", arg);
+        debug!("Command::groups_for_arg: id={:?}", arg);
         let arg = arg.clone();
         self.groups
             .iter()
@@ -4600,7 +4647,7 @@ impl<'help> App<'help> {
     }
 
     pub(crate) fn unroll_args_in_group(&self, group: &Id) -> Vec<Id> {
-        debug!("App::unroll_args_in_group: group={:?}", group);
+        debug!("Command::unroll_args_in_group: group={:?}", group);
         let mut g_vec = vec![group];
         let mut args = vec![];
 
@@ -4613,13 +4660,13 @@ impl<'help> App<'help> {
                 .args
                 .iter()
             {
-                debug!("App::unroll_args_in_group:iter: entity={:?}", n);
+                debug!("Command::unroll_args_in_group:iter: entity={:?}", n);
                 if !args.contains(n) {
                     if self.find(n).is_some() {
-                        debug!("App::unroll_args_in_group:iter: this is an arg");
+                        debug!("Command::unroll_args_in_group:iter: this is an arg");
                         args.push(n.clone())
                     } else {
-                        debug!("App::unroll_args_in_group:iter: this is a group");
+                        debug!("Command::unroll_args_in_group:iter: this is a group");
                         g_vec.push(n);
                     }
                 }
diff --git a/src/error/mod.rs b/src/error/mod.rs
index d4fe8a32..3e486101 100644
--- a/src/error/mod.rs
+++ b/src/error/mod.rs
@@ -77,7 +77,7 @@ impl Error {
     /// Format the existing message with the Command's context
     #[must_use]
     pub fn format(mut self, cmd: &mut Command) -> Self {
-        cmd._build();
+        cmd._build_self();
         let usage = cmd.render_usage();
         if let Some(message) = self.inner.message.as_mut() {
             message.format(cmd, usage);
diff --git a/src/parse/parser.rs b/src/parse/parser.rs
index 7c62b2a3..b5b69666 100644
--- a/src/parse/parser.rs
+++ b/src/parse/parser.rs
@@ -613,42 +613,29 @@ impl<'help, 'cmd> Parser<'help, 'cmd> {
     ) -> ClapResult<ParseResult> {
         debug!("Parser::parse_help_subcommand");
 
-        let mut bin_name = self
-            .cmd
-            .get_bin_name()
-            .unwrap_or_else(|| self.cmd.get_name())
-            .to_owned();
-
-        let mut sc = {
-            let mut sc = self.cmd.clone();
+        let mut cmd = self.cmd.clone();
+        let sc = {
+            let mut sc = &mut cmd;
 
             for cmd in cmds {
-                sc = if let Some(c) = sc.find_subcommand(cmd) {
-                    c
-                } else if let Some(c) = sc.find_subcommand(&cmd.to_string_lossy()) {
-                    c
+                sc = if let Some(sc_name) =
+                    sc.find_subcommand(cmd).map(|sc| sc.get_name().to_owned())
+                {
+                    sc._build_subcommand(&sc_name).unwrap()
                 } else {
                     return Err(ClapError::unrecognized_subcommand(
-                        self.cmd,
+                        sc,
                         cmd.to_string_lossy().into_owned(),
-                        self.cmd
-                            .get_bin_name()
-                            .unwrap_or_else(|| self.cmd.get_name())
+                        sc.get_bin_name()
+                            .unwrap_or_else(|| sc.get_name())
                             .to_owned(),
                     ));
-                }
-                .clone();
-
-                sc._build_self();
-                bin_name.push(' ');
-                bin_name.push_str(sc.get_name());
+                };
             }
 
             sc
         };
-        sc = sc.bin_name(bin_name);
-
-        let parser = Parser::new(&mut sc);
+        let parser = Parser::new(sc);
 
         Err(parser.help_err(true, Stream::Stdout))
     }


Test patch:
diff --git a/tests/builder/app_settings.rs b/tests/builder/app_settings.rs
index cdcdd804..3013406b 100644
--- a/tests/builder/app_settings.rs
+++ b/tests/builder/app_settings.rs
@@ -250,12 +250,12 @@ fn arg_required_else_help_error_message() {
                 .short('i')
                 .long("info"),
         );
-    assert!(utils::compare_output(
+    utils::assert_output(
         cmd,
         "test",
         ARG_REQUIRED_ELSE_HELP,
-        true // Unlike normal displaying of help, we should provide a fatal exit code
-    ));
+        true, // Unlike normal displaying of help, we should provide a fatal exit code
+    );
 }
 
 #[test]
@@ -281,12 +281,12 @@ fn subcommand_required_else_help_error_message() {
         .setting(AppSettings::SubcommandRequiredElseHelp)
         .version("1.0")
         .subcommand(Command::new("info").arg(Arg::new("filename")));
-    assert!(utils::compare_output(
+    utils::assert_output(
         cmd,
         "test",
         SUBCOMMAND_REQUIRED_ELSE_HELP,
-        true // Unlike normal displaying of help, we should provide a fatal exit code
-    ));
+        true, // Unlike normal displaying of help, we should provide a fatal exit code
+    );
 }
 
 #[cfg(not(feature = "suggestions"))]
@@ -419,12 +419,7 @@ fn skip_possible_values() {
             arg!([arg1] "some pos arg").possible_values(["three", "four"]),
         ]);
 
-    assert!(utils::compare_output(
-        cmd,
-        "test --help",
-        SKIP_POS_VALS,
-        false
-    ));
+    utils::assert_output(cmd, "test --help", SKIP_POS_VALS, false);
 }
 
 #[test]
@@ -624,12 +619,7 @@ fn dont_collapse_args() {
             Arg::new("arg2").help("some"),
             Arg::new("arg3").help("some"),
         ]);
-    assert!(utils::compare_output(
-        cmd,
-        "clap-test --help",
-        DONT_COLLAPSE_ARGS,
-        false
-    ));
+    utils::assert_output(cmd, "clap-test --help", DONT_COLLAPSE_ARGS, false);
 }
 
 #[test]
@@ -643,12 +633,7 @@ fn require_eq() {
             .value_name("FILE")
             .help("some"),
     );
-    assert!(utils::compare_output(
-        cmd,
-        "clap-test --help",
-        REQUIRE_EQUALS,
-        false
-    ));
+    utils::assert_output(cmd, "clap-test --help", REQUIRE_EQUALS, false);
 }
 
 #[test]
@@ -863,12 +848,7 @@ fn issue_1093_allow_ext_sc() {
     let cmd = Command::new("clap-test")
         .version("v1.4.8")
         .allow_external_subcommands(true);
-    assert!(utils::compare_output(
-        cmd,
-        "clap-test --help",
-        ALLOW_EXT_SC,
-        false
-    ));
+    utils::assert_output(cmd, "clap-test --help", ALLOW_EXT_SC, false);
 }
 
 #[test]
diff --git a/tests/builder/arg_aliases.rs b/tests/builder/arg_aliases.rs
index f8401674..e0df5825 100644
--- a/tests/builder/arg_aliases.rs
+++ b/tests/builder/arg_aliases.rs
@@ -168,12 +168,7 @@ fn invisible_arg_aliases_help_output() {
             )
             .arg(arg!(-f - -flag).aliases(&["unseeable", "flg1", "anyway"])),
     );
-    assert!(utils::compare_output(
-        cmd,
-        "ct test --help",
-        SC_INVISIBLE_ALIAS_HELP,
-        false
-    ));
+    utils::assert_output(cmd, "ct test --help", SC_INVISIBLE_ALIAS_HELP, false);
 }
 
 #[test]
@@ -197,10 +192,5 @@ fn visible_arg_aliases_help_output() {
                     .visible_aliases(&["v_flg", "flag2", "flg3"]),
             ),
     );
-    assert!(utils::compare_output(
-        cmd,
-        "ct test --help",
-        SC_VISIBLE_ALIAS_HELP,
-        false
-    ));
+    utils::assert_output(cmd, "ct test --help", SC_VISIBLE_ALIAS_HELP, false);
 }
diff --git a/tests/builder/arg_aliases_short.rs b/tests/builder/arg_aliases_short.rs
index 5069bcb7..d939c49d 100644
--- a/tests/builder/arg_aliases_short.rs
+++ b/tests/builder/arg_aliases_short.rs
@@ -164,12 +164,7 @@ fn invisible_short_arg_aliases_help_output() {
             )
             .arg(arg!(-f - -flag).short_aliases(&['x', 'y', 'z'])),
     );
-    assert!(utils::compare_output(
-        cmd,
-        "ct test --help",
-        SC_INVISIBLE_ALIAS_HELP,
-        false
-    ));
+    utils::assert_output(cmd, "ct test --help", SC_INVISIBLE_ALIAS_HELP, false);
 }
 
 #[test]
@@ -194,10 +189,5 @@ fn visible_short_arg_aliases_help_output() {
                     .visible_short_aliases(&['a', 'b', '🦆']),
             ),
     );
-    assert!(utils::compare_output(
-        cmd,
-        "ct test --help",
-        SC_VISIBLE_ALIAS_HELP,
-        false
-    ));
+    utils::assert_output(cmd, "ct test --help", SC_VISIBLE_ALIAS_HELP, false);
 }
diff --git a/tests/builder/conflicts.rs b/tests/builder/conflicts.rs
index 8126c93b..f1fb64f1 100644
--- a/tests/builder/conflicts.rs
+++ b/tests/builder/conflicts.rs
@@ -248,42 +248,42 @@ fn required_group_conflicts_with_arg() {
 
 #[test]
 fn conflict_output() {
-    assert!(utils::compare_output(
+    utils::assert_output(
         utils::complex_app(),
         "clap-test val1 fa --flag --long-option-2 val2 -F",
         CONFLICT_ERR,
         true,
-    ));
+    );
 }
 
 #[test]
 fn conflict_output_rev() {
-    assert!(utils::compare_output(
+    utils::assert_output(
         utils::complex_app(),
         "clap-test val1 fa -F --long-option-2 val2 --flag",
         CONFLICT_ERR_REV,
         true,
-    ));
+    );
 }
 
 #[test]
 fn conflict_output_with_required() {
-    assert!(utils::compare_output(
+    utils::assert_output(
         utils::complex_app(),
         "clap-test val1 --flag --long-option-2 val2 -F",
         CONFLICT_ERR,
         true,
-    ));
+    );
 }
 
 #[test]
 fn conflict_output_rev_with_required() {
-    assert!(utils::compare_output(
+    utils::assert_output(
         utils::complex_app(),
         "clap-test val1 -F --long-option-2 val2 --flag",
         CONFLICT_ERR_REV,
         true,
-    ));
+    );
 }
 
 #[test]
@@ -304,12 +304,12 @@ fn conflict_output_three_conflicting() {
                 .long("three")
                 .conflicts_with_all(&["one", "two"]),
         );
-    assert!(utils::compare_output(
+    utils::assert_output(
         cmd,
         "three_conflicting_arguments --one --two --three",
         CONFLICT_ERR_THREE,
         true,
-    ));
+    );
 }
 
 #[test]
diff --git a/tests/builder/default_vals.rs b/tests/builder/default_vals.rs
index 640a525e..c86ea38c 100644
--- a/tests/builder/default_vals.rs
+++ b/tests/builder/default_vals.rs
@@ -577,7 +577,7 @@ fn default_vals_donnot_show_in_smart_usage() {
         )
         .arg(Arg::new("input").required(true));
 
-    assert!(utils::compare_output(
+    utils::assert_output(
         cmd,
         "bug",
         "error: The following required arguments were not provided:
@@ -589,7 +589,7 @@ USAGE:
 For more information try --help
 ",
         true,
-    ));
+    );
 }
 
 #[test]
diff --git a/tests/builder/derive_order.rs b/tests/builder/derive_order.rs
index 9e5608e8..a257b77c 100644
--- a/tests/builder/derive_order.rs
+++ b/tests/builder/derive_order.rs
@@ -99,12 +99,7 @@ fn no_derive_order() {
             .help("second option"),
     ]);
 
-    assert!(utils::compare_output(
-        cmd,
-        "test --help",
-        NO_DERIVE_ORDER,
-        false
-    ));
+    utils::assert_output(cmd, "test --help", NO_DERIVE_ORDER, false);
 }
 
 #[test]
@@ -125,12 +120,7 @@ fn derive_order() {
                 .help("second option"),
         ]);
 
-    assert!(utils::compare_output(
-        cmd,
-        "test --help",
-        UNIFIED_HELP_AND_DERIVE,
-        false
-    ));
+    utils::assert_output(cmd, "test --help", UNIFIED_HELP_AND_DERIVE, false);
 }
 
 #[test]
@@ -169,7 +159,7 @@ OPTIONS:
                 .help("first option"),
         );
 
-    assert!(utils::compare_output(cmd, "test --help", HELP, false));
+    utils::assert_output(cmd, "test --help", HELP, false);
 }
 
 #[test]
@@ -207,7 +197,7 @@ OPTIONS:
                 .help("second option"),
         );
 
-    assert!(utils::compare_output(cmd, "test --help", HELP, false));
+    utils::assert_output(cmd, "test --help", HELP, false);
 }
 
 #[test]
@@ -229,12 +219,7 @@ fn derive_order_subcommand_propagate() {
             ]),
         );
 
-    assert!(utils::compare_output(
-        cmd,
-        "test sub --help",
-        UNIFIED_DERIVE_SC_PROP,
-        false
-    ));
+    utils::assert_output(cmd, "test sub --help", UNIFIED_DERIVE_SC_PROP, false);
 }
 
 #[test]
@@ -259,12 +244,12 @@ fn derive_order_subcommand_propagate_with_explicit_display_order() {
             ]),
         );
 
-    assert!(utils::compare_output(
+    utils::assert_output(
         cmd,
         "test sub --help",
         UNIFIED_DERIVE_SC_PROP_EXPLICIT_ORDER,
-        false
-    ));
+        false,
+    );
 }
 
 #[test]
@@ -281,12 +266,7 @@ fn prefer_user_help_with_derive_order() {
             Arg::new("flag_a").long("flag_a").help("second flag"),
         ]);
 
-    assert!(utils::compare_output(
-        cmd,
-        "test --help",
-        PREFER_USER_HELP_DERIVE_ORDER,
-        false
-    ));
+    utils::assert_output(cmd, "test --help", PREFER_USER_HELP_DERIVE_ORDER, false);
 }
 
 #[test]
@@ -304,10 +284,10 @@ fn prefer_user_help_in_subcommand_with_derive_order() {
             ]),
         );
 
-    assert!(utils::compare_output(
+    utils::assert_output(
         cmd,
         "test sub --help",
         PREFER_USER_HELP_SUBCMD_DERIVE_ORDER,
-        false
-    ));
+        false,
+    );
 }
diff --git a/tests/builder/display_order.rs b/tests/builder/display_order.rs
index 1c97ba2e..68b4c3d3 100644
--- a/tests/builder/display_order.rs
+++ b/tests/builder/display_order.rs
@@ -6,7 +6,7 @@ use clap::Command;
 fn very_large_display_order() {
     let cmd = Command::new("test").subcommand(Command::new("sub").display_order(usize::MAX));
 
-    assert!(utils::compare_output(
+    utils::assert_output(
         cmd,
         "test --help",
         "test 
@@ -21,6 +21,6 @@ SUBCOMMANDS:
     help    Print this message or the help of the given subcommand(s)
     sub     
 ",
-        false
-    ));
+        false,
+    );
 }
diff --git a/tests/builder/empty_values.rs b/tests/builder/empty_values.rs
index 5d57f160..c537f0c3 100644
--- a/tests/builder/empty_values.rs
+++ b/tests/builder/empty_values.rs
@@ -124,10 +124,5 @@ USAGE:
 For more information try --help
 ";
 
-    assert!(utils::compare_output(
-        cmd,
-        "config --config",
-        NO_EUQALS_ERROR,
-        true
-    ));
+    utils::assert_output(cmd, "config --config", NO_EUQALS_ERROR, true);
 }
diff --git a/tests/builder/error.rs b/tests/builder/error.rs
index 3e4f923f..15cdefd1 100644
--- a/tests/builder/error.rs
+++ b/tests/builder/error.rs
@@ -2,12 +2,7 @@ use crate::utils;
 
 use clap::{arg, error::ErrorKind, Arg, Command, Error};
 
-fn compare_error(
-    err: Error,
-    expected_kind: ErrorKind,
-    expected_output: &str,
-    stderr: bool,
-) -> bool {
+fn assert_error(err: Error, expected_kind: ErrorKind, expected_output: &str, stderr: bool) {
     let actual_output = err.to_string();
     assert_eq!(
         stderr,
@@ -17,7 +12,7 @@ fn compare_error(
         err.use_stderr()
     );
     assert_eq!(expected_kind, err.kind());
-    utils::compare(expected_output, actual_output)
+    utils::assert_eq(expected_output, actual_output)
 }
 
 #[test]
@@ -55,7 +50,7 @@ For more information try --help
     let mut cmd = cmd;
     let expected_kind = ErrorKind::InvalidValue;
     let err = cmd.error(expected_kind, "Failed for mysterious reasons");
-    assert!(compare_error(err, expected_kind, MESSAGE, true));
+    assert_error(err, expected_kind, MESSAGE, true);
 }
 
 #[test]
diff --git a/tests/builder/flag_subcommands.rs b/tests/builder/flag_subcommands.rs
index c5c24983..6c434148 100644
--- a/tests/builder/flag_subcommands.rs
+++ b/tests/builder/flag_subcommands.rs
@@ -503,12 +503,7 @@ fn flag_subcommand_long_short_normal_usage_string() {
                         .multiple_values(true),
                 ),
         );
-    assert!(utils::compare_output(
-        cmd,
-        "pacman -Qh",
-        FLAG_SUBCOMMAND_HELP,
-        false
-    ));
+    utils::assert_output(cmd, "pacman -Qh", FLAG_SUBCOMMAND_HELP, false);
 }
 
 static FLAG_SUBCOMMAND_NO_SHORT_HELP: &str = "pacman-query 
@@ -556,12 +551,12 @@ fn flag_subcommand_long_normal_usage_string() {
                         .multiple_values(true),
                 ),
         );
-    assert!(utils::compare_output(
+    utils::assert_output(
         cmd,
         "pacman query --help",
         FLAG_SUBCOMMAND_NO_SHORT_HELP,
-        false
-    ));
+        false,
+    );
 }
 
 static FLAG_SUBCOMMAND_NO_LONG_HELP: &str = "pacman-query 
@@ -609,10 +604,10 @@ fn flag_subcommand_short_normal_usage_string() {
                         .multiple_values(true),
                 ),
         );
-    assert!(utils::compare_output(
+    utils::assert_output(
         cmd,
         "pacman query --help",
         FLAG_SUBCOMMAND_NO_LONG_HELP,
-        false
-    ));
+        false,
+    );
 }
diff --git a/tests/builder/flags.rs b/tests/builder/flags.rs
index 3d782ff3..343cc3a9 100644
--- a/tests/builder/flags.rs
+++ b/tests/builder/flags.rs
@@ -160,12 +160,7 @@ fn issue_1284_argument_in_flag_style() {
         .unwrap();
     assert_eq!(m.value_of("filename"), Some("--a-flag"));
 
-    assert!(utils::compare_output(
-        cmd,
-        "mycat --another-flag",
-        USE_FLAG_AS_ARGUMENT,
-        true
-    ));
+    utils::assert_output(cmd, "mycat --another-flag", USE_FLAG_AS_ARGUMENT, true);
 }
 
 #[test]
@@ -182,10 +177,5 @@ For more information try --help
 ";
     let cmd = Command::new("test").arg(Arg::new("arg").takes_value(true).required(true));
 
-    assert!(utils::compare_output(
-        cmd,
-        "test -----",
-        MULTIPLE_DASHES,
-        true
-    ));
+    utils::assert_output(cmd, "test -----", MULTIPLE_DASHES, true);
 }
diff --git a/tests/builder/groups.rs b/tests/builder/groups.rs
index 76d5761a..e9f0c561 100644
--- a/tests/builder/groups.rs
+++ b/tests/builder/groups.rs
@@ -178,12 +178,7 @@ fn req_group_usage_string() {
                 .required(true),
         );
 
-    assert!(utils::compare_output(
-        cmd,
-        "clap-test",
-        REQ_GROUP_USAGE,
-        true
-    ));
+    utils::assert_output(cmd, "clap-test", REQ_GROUP_USAGE, true);
 }
 
 #[test]
@@ -199,12 +194,12 @@ fn req_group_with_conflict_usage_string() {
                 .required(true),
         );
 
-    assert!(utils::compare_output(
+    utils::assert_output(
         cmd,
         "clap-test --delete base",
         REQ_GROUP_CONFLICT_USAGE,
-        true
-    ));
+        true,
+    );
 }
 
 #[test]
@@ -219,12 +214,12 @@ fn req_group_with_conflict_usage_string_only_options() {
                 .args(&["all", "delete"])
                 .required(true),
         );
-    assert!(utils::compare_output(
+    utils::assert_output(
         cmd,
         "clap-test --delete --all",
         REQ_GROUP_CONFLICT_ONLY_OPTIONS,
-        true
-    ));
+        true,
+    );
 }
 
 #[test]
@@ -286,12 +281,7 @@ OPTIONS:
     let cmd = Command::new("prog")
         .arg(Arg::new("a").value_name("A"))
         .group(ArgGroup::new("group").arg("a").required(true));
-    assert!(utils::compare_output(
-        cmd,
-        "prog --help",
-        GROUP_USAGE_USE_VAL_NAME,
-        false,
-    ));
+    utils::assert_output(cmd, "prog --help", GROUP_USAGE_USE_VAL_NAME, false);
 }
 
 #[test]
diff --git a/tests/builder/help.rs b/tests/builder/help.rs
index 48eb1cf2..f2db0c08 100644
--- a/tests/builder/help.rs
+++ b/tests/builder/help.rs
@@ -655,6 +655,41 @@ fn help_subcommand() {
     assert_eq!(m.unwrap_err().kind(), ErrorKind::DisplayHelp);
 }
 
+#[test]
+fn help_multi_subcommand_error() {
+    let cmd = Command::new("ctest").subcommand(
+        Command::new("subcmd").subcommand(
+            Command::new("multi")
+                .about("tests subcommands")
+                .author("Kevin K. <kbknapp@gmail.com>")
+                .version("0.1")
+                .arg(arg!(
+                    -f --flag                    "tests flags"
+                ))
+                .arg(
+                    arg!(
+                        -o --option <scoption>    "tests options"
+                    )
+                    .required(false)
+                    .multiple_values(true)
+                    .multiple_occurrences(true),
+                ),
+        ),
+    );
+    let err = cmd
+        .try_get_matches_from(["ctest", "help", "subcmd", "multi", "foo"])
+        .unwrap_err();
+
+    static EXPECTED: &str = "error: The subcommand 'foo' wasn't recognized
+
+USAGE:
+    ctest subcmd multi <subcommands>
+
+For more information try --help
+";
+    utils::assert_eq(EXPECTED, err.to_string());
+}
+
 #[test]
 fn req_last_arg_usage() {
     let cmd = Command::new("example")
@@ -672,12 +707,7 @@ fn req_last_arg_usage() {
                 .required(true)
                 .last(true),
         );
-    assert!(utils::compare_output(
-        cmd,
-        "example --help",
-        LAST_ARG_REQ_MULT,
-        false
-    ));
+    utils::assert_output(cmd, "example --help", LAST_ARG_REQ_MULT, false);
 }
 
 #[test]
@@ -719,12 +749,7 @@ fn args_with_last_usage() {
                 .last(true)
                 .value_name("ARGS"),
         );
-    assert!(utils::compare_output(
-        cmd,
-        "flamegraph --help",
-        LAST_ARG_USAGE,
-        false
-    ));
+    utils::assert_output(cmd, "flamegraph --help", LAST_ARG_USAGE, false);
 }
 
 #[test]
@@ -753,12 +778,7 @@ fn subcommand_help_rev() {
 
 #[test]
 fn complex_help_output() {
-    assert!(utils::compare_output(
-        utils::complex_app(),
-        "clap-test --help",
-        HELP,
-        false
-    ));
+    utils::assert_output(utils::complex_app(), "clap-test --help", HELP, false);
 }
 
 #[test]
@@ -768,18 +788,8 @@ fn after_and_before_help_output() {
         .about("tests clap library")
         .before_help("some text that comes before the help")
         .after_help("some text that comes after the help");
-    assert!(utils::compare_output(
-        cmd.clone(),
-        "clap-test -h",
-        AFTER_HELP,
-        false
-    ));
-    assert!(utils::compare_output(
-        cmd,
-        "clap-test --help",
-        AFTER_HELP,
-        false
-    ));
+    utils::assert_output(cmd.clone(), "clap-test -h", AFTER_HELP, false);
+    utils::assert_output(cmd, "clap-test --help", AFTER_HELP, false);
 }
 
 #[test]
@@ -791,18 +801,8 @@ fn after_and_before_long_help_output() {
         .after_help("some text that comes after the help")
         .before_long_help("some longer text that comes before the help")
         .after_long_help("some longer text that comes after the help");
-    assert!(utils::compare_output(
-        cmd.clone(),
-        "clap-test --help",
-        AFTER_LONG_HELP,
-        false
-    ));
-    assert!(utils::compare_output(
-        cmd,
-        "clap-test -h",
-        AFTER_HELP,
-        false
-    ));
+    utils::assert_output(cmd.clone(), "clap-test --help", AFTER_LONG_HELP, false);
+    utils::assert_output(cmd, "clap-test -h", AFTER_HELP, false);
 }
 
 #[test]
@@ -826,12 +826,7 @@ fn multi_level_sc_help() {
                 ),
         ),
     );
-    assert!(utils::compare_output(
-        cmd,
-        "ctest help subcmd multi",
-        MULTI_SC_HELP,
-        false
-    ));
+    utils::assert_output(cmd, "ctest help subcmd multi", MULTI_SC_HELP, false);
 }
 
 #[test]
@@ -839,23 +834,13 @@ fn no_wrap_help() {
     let cmd = Command::new("ctest")
         .term_width(0)
         .override_help(MULTI_SC_HELP);
-    assert!(utils::compare_output(
-        cmd,
-        "ctest --help",
-        &format!("{}\n", MULTI_SC_HELP),
-        false
-    ));
+    utils::assert_output(cmd, "ctest --help", &format!("{}\n", MULTI_SC_HELP), false);
 }
 
 #[test]
 fn no_wrap_default_help() {
     let cmd = Command::new("ctest").version("1.0").term_width(0);
-    assert!(utils::compare_output(
-        cmd,
-        "ctest --help",
-        DEFAULT_HELP,
-        false
-    ));
+    utils::assert_output(cmd, "ctest --help", DEFAULT_HELP, false);
 }
 
 #[test]
@@ -907,12 +892,7 @@ OPTIONS:
                 .long("no-git-push")
                 .help("Do not push generated commit and tags to git remote"),
         );
-    assert!(utils::compare_output(
-        cmd,
-        "test --help",
-        WRAPPED_HELP,
-        false
-    ));
+    utils::assert_output(cmd, "test --help", WRAPPED_HELP, false);
 }
 
 #[test]
@@ -956,12 +936,7 @@ OPTIONS:
                 .long("no-git-push")
                 .help("Do not push generated commit and tags to git remote"),
         );
-    assert!(utils::compare_output(
-        cmd,
-        "test --help",
-        UNWRAPPED_HELP,
-        false
-    ));
+    utils::assert_output(cmd, "test --help", UNWRAPPED_HELP, false);
 }
 
 #[test]
@@ -1039,23 +1014,13 @@ OPTIONS:
                 )
                 .possible_value(PossibleValue::new("second").help("short help")),
         );
-    assert!(utils::compare_output(
-        cmd,
-        "test --help",
-        WRAPPED_HELP,
-        false
-    ));
+    utils::assert_output(cmd, "test --help", WRAPPED_HELP, false);
 }
 
 #[test]
 fn complex_subcommand_help_output() {
     let a = utils::complex_app();
-    assert!(utils::compare_output(
-        a,
-        "clap-test subcmd --help",
-        SC_HELP,
-        false
-    ));
+    utils::assert_output(a, "clap-test subcmd --help", SC_HELP, false);
 }
 
 #[test]
@@ -1075,12 +1040,7 @@ fn issue_626_unicode_cutoff() {
             )
             .takes_value(true),
     );
-    assert!(utils::compare_output(
-        cmd,
-        "ctest --help",
-        ISSUE_626_CUTOFF,
-        false
-    ));
+    utils::assert_output(cmd, "ctest --help", ISSUE_626_CUTOFF, false);
 }
 
 #[test]
@@ -1106,12 +1066,7 @@ fn hide_possible_vals() {
                 .help("A coffeehouse, coffee shop, or café.")
                 .takes_value(true),
         );
-    assert!(utils::compare_output(
-        cmd,
-        "ctest --help",
-        HIDE_POS_VALS,
-        false
-    ));
+    utils::assert_output(cmd, "ctest --help", HIDE_POS_VALS, false);
 }
 
 #[test]
@@ -1136,12 +1091,7 @@ fn hide_single_possible_val() {
                 .help("A coffeehouse, coffee shop, or café.")
                 .takes_value(true),
         );
-    assert!(utils::compare_output(
-        cmd,
-        "ctest --help",
-        HIDE_POS_VALS,
-        false
-    ));
+    utils::assert_output(cmd, "ctest --help", HIDE_POS_VALS, false);
 }
 
 #[test]
@@ -1202,12 +1152,7 @@ OPTIONS:
                 .help("A coffeehouse, coffee shop, or café.")
                 .takes_value(true),
         );
-    assert!(utils::compare_output(
-        app,
-        "ctest --help",
-        POS_VALS_HELP,
-        false
-    ));
+    utils::assert_output(app, "ctest --help", POS_VALS_HELP, false);
 }
 
 #[test]
@@ -1223,12 +1168,7 @@ fn issue_626_panic() {
            d'Afrique et d'Asie, dans des plantations qui sont cultivées pour les marchés d'exportation. \
            Le café est souvent une contribution majeure aux exportations des régions productrices.")
            .takes_value(true));
-    assert!(utils::compare_output(
-        cmd,
-        "ctest --help",
-        ISSUE_626_PANIC,
-        false
-    ));
+    utils::assert_output(cmd, "ctest --help", ISSUE_626_PANIC, false);
 }
 
 #[test]
@@ -1252,12 +1192,7 @@ fn issue_626_variable_panic() {
 #[test]
 fn final_word_wrapping() {
     let cmd = Command::new("ctest").version("0.1").term_width(24);
-    assert!(utils::compare_output(
-        cmd,
-        "ctest --help",
-        FINAL_WORD_WRAPPING,
-        false
-    ));
+    utils::assert_output(cmd, "ctest --help", FINAL_WORD_WRAPPING, false);
 }
 
 #[test]
@@ -1270,12 +1205,7 @@ fn wrapping_newline_chars() {
              l, long           Copy-friendly, 14 characters, contains symbols.\n\
              m, med, medium    Copy-friendly, 8 characters, contains symbols.\n",
         ));
-    assert!(utils::compare_output(
-        cmd,
-        "ctest --help",
-        WRAPPING_NEWLINE_CHARS,
-        false
-    ));
+    utils::assert_output(cmd, "ctest --help", WRAPPING_NEWLINE_CHARS, false);
 }
 
 #[test]
@@ -1288,12 +1218,7 @@ fn wrapping_newline_variables() {
              l, long           Copy-friendly, 14 characters, contains symbols.{n}\
              m, med, medium    Copy-friendly, 8 characters, contains symbols.{n}",
         ));
-    assert!(utils::compare_output(
-        cmd,
-        "ctest --help",
-        WRAPPING_NEWLINE_CHARS,
-        false
-    ));
+    utils::assert_output(cmd, "ctest --help", WRAPPING_NEWLINE_CHARS, false);
 }
 
 #[test]
@@ -1303,12 +1228,7 @@ fn old_newline_chars() {
             .short('m')
             .help("Some help with some wrapping\n(Defaults to something)"),
     );
-    assert!(utils::compare_output(
-        cmd,
-        "ctest --help",
-        OLD_NEWLINE_CHARS,
-        false
-    ));
+    utils::assert_output(cmd, "ctest --help", OLD_NEWLINE_CHARS, false);
 }
 
 #[test]
@@ -1318,12 +1238,7 @@ fn old_newline_variables() {
             .short('m')
             .help("Some help with some wrapping{n}(Defaults to something)"),
     );
-    assert!(utils::compare_output(
-        cmd,
-        "ctest --help",
-        OLD_NEWLINE_CHARS,
-        false
-    ));
+    utils::assert_output(cmd, "ctest --help", OLD_NEWLINE_CHARS, false);
 }
 
 #[test]
@@ -1340,12 +1255,7 @@ fn issue_688_hide_pos_vals() {
 				.long("filter")
 				.possible_values(filter_values)
 				.takes_value(true));
-    assert!(utils::compare_output(
-        app1,
-        "ctest --help",
-        ISSUE_688,
-        false
-    ));
+    utils::assert_output(app1, "ctest --help", ISSUE_688, false);
 
     let app2 = Command::new("ctest")
             .version("0.1")
@@ -1356,12 +1266,7 @@ fn issue_688_hide_pos_vals() {
 				.long("filter")
 				.possible_values(filter_values)
 				.takes_value(true));
-    assert!(utils::compare_output(
-        app2,
-        "ctest --help",
-        ISSUE_688,
-        false
-    ));
+    utils::assert_output(app2, "ctest --help", ISSUE_688, false);
 
     let app3 = Command::new("ctest")
             .version("0.1")
@@ -1371,12 +1276,7 @@ fn issue_688_hide_pos_vals() {
                 images. The default is Linear (Bilinear). [possible values: Nearest, Linear, Cubic, Gaussian, Lanczos3]")
 				.long("filter")
 				.takes_value(true));
-    assert!(utils::compare_output(
-        app3,
-        "ctest --help",
-        ISSUE_688,
-        false
-    ));
+    utils::assert_output(app3, "ctest --help", ISSUE_688, false);
 }
 
 #[test]
@@ -1414,7 +1314,7 @@ fn issue_702_multiple_values() {
                 .multiple_values(true)
                 .takes_value(true),
         );
-    assert!(utils::compare_output(cmd, "myapp --help", ISSUE_702, false));
+    utils::assert_output(cmd, "myapp --help", ISSUE_702, false);
 }
 
 #[test]
@@ -1427,12 +1327,7 @@ fn long_about() {
             "something really really long, with\nmultiple lines of text\nthat should be displayed",
         )
         .arg(Arg::new("arg1").help("some option"));
-    assert!(utils::compare_output(
-        cmd,
-        "myapp --help",
-        LONG_ABOUT,
-        false
-    ));
+    utils::assert_output(cmd, "myapp --help", LONG_ABOUT, false);
 }
 
 #[test]
@@ -1455,7 +1350,7 @@ fn issue_760() {
                 .long("opt")
                 .takes_value(true),
         );
-    assert!(utils::compare_output(cmd, "ctest --help", ISSUE_760, false));
+    utils::assert_output(cmd, "ctest --help", ISSUE_760, false);
 }
 
 #[test]
@@ -1468,7 +1363,7 @@ fn issue_1571() {
             .takes_value(true)
             .multiple_values(true),
     );
-    assert!(utils::compare_output(
+    utils::assert_output(
         cmd,
         "hello --help",
         "hello 
@@ -1480,8 +1375,8 @@ OPTIONS:
     -h, --help              Print help information
     -p, --package <name>    
 ",
-        false
-    ));
+        false,
+    );
 }
 
 #[test]
@@ -1493,12 +1388,7 @@ fn ripgrep_usage() {
     rg [OPTIONS] --type-list",
     );
 
-    assert!(utils::compare_output(
-        cmd,
-        "rg --help",
-        RIPGREP_USAGE,
-        false
-    ));
+    utils::assert_output(cmd, "rg --help", RIPGREP_USAGE, false);
 }
 
 #[test]
@@ -1522,12 +1412,7 @@ OPTIONS:
 {options}",
         );
 
-    assert!(utils::compare_output(
-        cmd,
-        "rg --help",
-        RIPGREP_USAGE,
-        false
-    ));
+    utils::assert_output(cmd, "rg --help", RIPGREP_USAGE, false);
 }
 
 #[test]
@@ -1538,12 +1423,7 @@ fn sc_negates_reqs() {
         .arg(arg!(-o --opt <FILE> "tests options"))
         .arg(Arg::new("PATH").help("help"))
         .subcommand(Command::new("test"));
-    assert!(utils::compare_output(
-        cmd,
-        "prog --help",
-        SC_NEGATES_REQS,
-        false
-    ));
+    utils::assert_output(cmd, "prog --help", SC_NEGATES_REQS, false);
 }
 
 #[test]
@@ -1553,12 +1433,7 @@ fn hide_args() {
         .arg(arg!(-f --flag "testing flags"))
         .arg(arg!(-o --opt <FILE> "tests options").required(false))
         .arg(Arg::new("pos").hide(true));
-    assert!(utils::compare_output(
-        cmd,
-        "prog --help",
-        HIDDEN_ARGS,
-        false
-    ));
+    utils::assert_output(cmd, "prog --help", HIDDEN_ARGS, false);
 }
 
 #[test]
@@ -1570,12 +1445,7 @@ fn args_negate_sc() {
         .arg(arg!(-o --opt <FILE> "tests options").required(false))
         .arg(Arg::new("PATH").help("help"))
         .subcommand(Command::new("test"));
-    assert!(utils::compare_output(
-        cmd,
-        "prog --help",
-        ARGS_NEGATE_SC,
-        false
-    ));
+    utils::assert_output(cmd, "prog --help", ARGS_NEGATE_SC, false);
 }
 
 #[test]
@@ -1586,12 +1456,7 @@ fn issue_1046_hide_scs() {
         .arg(arg!(-o --opt <FILE> "tests options").required(false))
         .arg(Arg::new("PATH").help("some"))
         .subcommand(Command::new("test").hide(true));
-    assert!(utils::compare_output(
-        cmd,
-        "prog --help",
-        ISSUE_1046_HIDDEN_SCS,
-        false
-    ));
+    utils::assert_output(cmd, "prog --help", ISSUE_1046_HIDDEN_SCS, false);
 }
 
 #[test]
@@ -1601,7 +1466,7 @@ fn issue_777_wrap_all_things() {
         .author("Some Very Long Name and crazy long email <email@server.com>")
         .about("Show how the about text is not wrapped")
         .term_width(35);
-    assert!(utils::compare_output(cmd, "ctest --help", ISSUE_777, false));
+    utils::assert_output(cmd, "ctest --help", ISSUE_777, false);
 }
 
 static OVERRIDE_HELP_SHORT: &str = "test 0.1
@@ -1620,18 +1485,8 @@ fn override_help_short() {
         .version("0.1")
         .mut_arg("help", |h| h.short('H'));
 
-    assert!(utils::compare_output(
-        cmd.clone(),
-        "test --help",
-        OVERRIDE_HELP_SHORT,
-        false
-    ));
-    assert!(utils::compare_output(
-        cmd,
-        "test -H",
-        OVERRIDE_HELP_SHORT,
-        false
-    ));
+    utils::assert_output(cmd.clone(), "test --help", OVERRIDE_HELP_SHORT, false);
+    utils::assert_output(cmd, "test -H", OVERRIDE_HELP_SHORT, false);
 }
 
 static OVERRIDE_HELP_LONG: &str = "test 0.1
@@ -1650,18 +1505,8 @@ fn override_help_long() {
         .version("0.1")
         .mut_arg("help", |h| h.long("hell"));
 
-    assert!(utils::compare_output(
-        cmd.clone(),
-        "test --hell",
-        OVERRIDE_HELP_LONG,
-        false
-    ));
-    assert!(utils::compare_output(
-        cmd,
-        "test -h",
-        OVERRIDE_HELP_LONG,
-        false
-    ));
+    utils::assert_output(cmd.clone(), "test --hell", OVERRIDE_HELP_LONG, false);
+    utils::assert_output(cmd, "test -h", OVERRIDE_HELP_LONG, false);
 }
 
 static OVERRIDE_HELP_ABOUT: &str = "test 0.1
@@ -1680,30 +1525,15 @@ fn override_help_about() {
         .version("0.1")
         .mut_arg("help", |h| h.help("Print help information"));
 
-    assert!(utils::compare_output(
-        cmd.clone(),
-        "test --help",
-        OVERRIDE_HELP_ABOUT,
-        false
-    ));
-    assert!(utils::compare_output(
-        cmd,
-        "test -h",
-        OVERRIDE_HELP_ABOUT,
-        false
-    ));
+    utils::assert_output(cmd.clone(), "test --help", OVERRIDE_HELP_ABOUT, false);
+    utils::assert_output(cmd, "test -h", OVERRIDE_HELP_ABOUT, false);
 }
 
 #[test]
 fn arg_short_conflict_with_help() {
     let cmd = Command::new("conflict").arg(Arg::new("home").short('h'));
 
-    assert!(utils::compare_output(
-        cmd,
-        "conflict --help",
-        HELP_CONFLICT,
-        false
-    ));
+    utils::assert_output(cmd, "conflict --help", HELP_CONFLICT, false);
 }
 
 #[cfg(debug_assertions)]
@@ -1729,7 +1559,7 @@ fn last_arg_mult_usage() {
                 .last(true)
                 .help("some"),
         );
-    assert!(utils::compare_output(cmd, "last --help", LAST_ARG, false));
+    utils::assert_output(cmd, "last --help", LAST_ARG, false);
 }
 
 #[test]
@@ -1746,12 +1576,7 @@ fn last_arg_mult_usage_req() {
                 .required(true)
                 .help("some"),
         );
-    assert!(utils::compare_output(
-        cmd,
-        "last --help",
-        LAST_ARG_REQ,
-        false
-    ));
+    utils::assert_output(cmd, "last --help", LAST_ARG_REQ, false);
 }
 
 #[test]
@@ -1770,12 +1595,7 @@ fn last_arg_mult_usage_req_with_sc() {
                 .help("some"),
         )
         .subcommand(Command::new("test").about("some"));
-    assert!(utils::compare_output(
-        cmd,
-        "last --help",
-        LAST_ARG_REQ_SC,
-        false
-    ));
+    utils::assert_output(cmd, "last --help", LAST_ARG_REQ_SC, false);
 }
 
 #[test]
@@ -1793,12 +1613,7 @@ fn last_arg_mult_usage_with_sc() {
                 .help("some"),
         )
         .subcommand(Command::new("test").about("some"));
-    assert!(utils::compare_output(
-        cmd,
-        "last --help",
-        LAST_ARG_SC,
-        false
-    ));
+    utils::assert_output(cmd, "last --help", LAST_ARG_SC, false);
 }
 
 #[test]
@@ -1810,12 +1625,7 @@ fn hide_default_val() {
             .default_value("default-argument")
             .hide_default_value(true),
     );
-    assert!(utils::compare_output(
-        app1,
-        "default --help",
-        HIDE_DEFAULT_VAL,
-        false
-    ));
+    utils::assert_output(app1, "default --help", HIDE_DEFAULT_VAL, false);
 
     let app2 = Command::new("default").version("0.1").term_width(120).arg(
         Arg::new("argument")
@@ -1823,12 +1633,7 @@ fn hide_default_val() {
             .long("arg")
             .default_value("default-argument"),
     );
-    assert!(utils::compare_output(
-        app2,
-        "default --help",
-        HIDE_DEFAULT_VAL,
-        false
-    ));
+    utils::assert_output(app2, "default --help", HIDE_DEFAULT_VAL, false);
 }
 
 #[test]
@@ -1840,12 +1645,7 @@ fn escaped_whitespace_values() {
             .default_value("\n")
             .possible_values(["normal", " ", "\n", "\t", "other"]),
     );
-    assert!(utils::compare_output(
-        app1,
-        "default --help",
-        ESCAPED_DEFAULT_VAL,
-        false
-    ));
+    utils::assert_output(app1, "default --help", ESCAPED_DEFAULT_VAL, false);
 }
 
 fn issue_1112_setup() -> Command<'static> {
@@ -1913,12 +1713,7 @@ fn issue_1052_require_delim_help() {
                 .value_delimiter(':'),
         );
 
-    assert!(utils::compare_output(
-        cmd,
-        "test --help",
-        REQUIRE_DELIM_HELP,
-        false
-    ));
+    utils::assert_output(cmd, "test --help", REQUIRE_DELIM_HELP, false);
 }
 
 #[test]
@@ -1945,12 +1740,7 @@ fn custom_headers_headers() {
         )
         .args(&[Arg::new("port").long("port")]);
 
-    assert!(utils::compare_output(
-        cmd,
-        "test --help",
-        CUSTOM_HELP_SECTION,
-        false
-    ));
+    utils::assert_output(cmd, "test --help", CUSTOM_HELP_SECTION, false);
 }
 
 static MULTIPLE_CUSTOM_HELP_SECTIONS: &str = "blorp 1.4
@@ -2031,12 +1821,7 @@ fn multiple_custom_help_headers() {
                 .takes_value(true),
         );
 
-    assert!(utils::compare_output(
-        cmd,
-        "test --help",
-        MULTIPLE_CUSTOM_HELP_SECTIONS,
-        false
-    ));
+    utils::assert_output(cmd, "test --help", MULTIPLE_CUSTOM_HELP_SECTIONS, false);
 }
 
 static CUSTOM_HELP_SECTION_HIDDEN_ARGS: &str = "blorp 1.4
@@ -2089,12 +1874,7 @@ fn custom_help_headers_hide_args() {
                 .hide_short_help(true),
         );
 
-    assert!(utils::compare_output(
-        cmd,
-        "test -h",
-        CUSTOM_HELP_SECTION_HIDDEN_ARGS,
-        false
-    ));
+    utils::assert_output(cmd, "test -h", CUSTOM_HELP_SECTION_HIDDEN_ARGS, false);
 }
 
 static ISSUE_897: &str = "ctest-foo 0.1
@@ -2119,12 +1899,7 @@ fn show_long_about_issue_897() {
             .about("About foo")
             .long_about("Long about foo"),
     );
-    assert!(utils::compare_output(
-        cmd,
-        "ctest foo --help",
-        ISSUE_897,
-        false
-    ));
+    utils::assert_output(cmd, "ctest foo --help", ISSUE_897, false);
 }
 
 static ISSUE_897_SHORT: &str = "ctest-foo 0.1
@@ -2146,12 +1921,7 @@ fn show_short_about_issue_897() {
             .about("About foo")
             .long_about("Long about foo"),
     );
-    assert!(utils::compare_output(
-        cmd,
-        "ctest foo -h",
-        ISSUE_897_SHORT,
-        false
-    ));
+    utils::assert_output(cmd, "ctest foo -h", ISSUE_897_SHORT, false);
 }
 
 #[test]
@@ -2171,7 +1941,7 @@ fn issue_1364_no_short_options() {
                 .multiple_values(true),
         );
 
-    assert!(utils::compare_output(cmd, "demo -h", ISSUE_1364, false));
+    utils::assert_output(cmd, "demo -h", ISSUE_1364, false);
 }
 
 #[rustfmt::skip]
@@ -2185,7 +1955,7 @@ fn issue_1487() {
         .group(ArgGroup::new("group1")
             .args(&["arg1", "arg2"])
             .required(true));
-    assert!(utils::compare_output(cmd, "ctest -h", ISSUE_1487, false));
+    utils::assert_output(cmd, "ctest -h", ISSUE_1487, false);
 }
 
 #[cfg(debug_assertions)]
@@ -2292,7 +2062,7 @@ with only valid keys and may not contain other nonsense
 that cannot be read by this program. Obviously I'm going on
 and on, so I'll stop now.",
     ));
-    assert!(utils::compare_output(cmd, "prog --help", ISSUE_1642, false));
+    utils::assert_output(cmd, "prog --help", ISSUE_1642, false);
 }
 
 const AFTER_HELP_NO_ARGS: &str = "myapp 1.0
@@ -2339,12 +2109,7 @@ fn help_subcmd_help() {
         .mut_arg("help", |h| h.help("Print custom help text"))
         .subcommand(Command::new("subcmd").subcommand(Command::new("multi").version("1.0")));
 
-    assert!(utils::compare_output(
-        cmd.clone(),
-        "myapp help help",
-        HELP_SUBCMD_HELP,
-        false
-    ));
+    utils::assert_output(cmd.clone(), "myapp help help", HELP_SUBCMD_HELP, false);
 }
 
 static SUBCMD_HELP_SUBCMD_HELP: &str = "myapp-subcmd-help 
@@ -2366,12 +2131,12 @@ fn subcmd_help_subcmd_help() {
         .mut_arg("help", |h| h.help("Print custom help text"))
         .subcommand(Command::new("subcmd").subcommand(Command::new("multi").version("1.0")));
 
-    assert!(utils::compare_output(
+    utils::assert_output(
         cmd.clone(),
         "myapp subcmd help help",
         SUBCMD_HELP_SUBCMD_HELP,
-        false
-    ));
+        false,
+    );
 }
 
 static HELP_ABOUT_MULTI_SC: &str = "myapp-subcmd-multi 1.0
@@ -2400,24 +2165,19 @@ fn help_about_multi_subcmd() {
         .mut_arg("help", |h| h.help("Print custom help text"))
         .subcommand(Command::new("subcmd").subcommand(Command::new("multi").version("1.0")));
 
-    assert!(utils::compare_output(
+    utils::assert_output(
         cmd.clone(),
         "myapp help subcmd multi",
         HELP_ABOUT_MULTI_SC,
-        false
-    ));
-    assert!(utils::compare_output(
+        false,
+    );
+    utils::assert_output(
         cmd.clone(),
         "myapp subcmd multi -h",
         HELP_ABOUT_MULTI_SC,
-        false
-    ));
-    assert!(utils::compare_output(
-        cmd,
-        "myapp subcmd multi --help",
-        HELP_ABOUT_MULTI_SC,
-        false
-    ));
+        false,
+    );
+    utils::assert_output(cmd, "myapp subcmd multi --help", HELP_ABOUT_MULTI_SC, false);
 }
 
 #[test]
@@ -2432,24 +2192,24 @@ fn help_about_multi_subcmd_override() {
             ),
         );
 
-    assert!(utils::compare_output(
+    utils::assert_output(
         cmd.clone(),
         "myapp help subcmd multi",
         HELP_ABOUT_MULTI_SC_OVERRIDE,
-        false
-    ));
-    assert!(utils::compare_output(
+        false,
+    );
+    utils::assert_output(
         cmd.clone(),
         "myapp subcmd multi -h",
         HELP_ABOUT_MULTI_SC_OVERRIDE,
-        false
-    ));
-    assert!(utils::compare_output(
+        false,
+    );
+    utils::assert_output(
         cmd,
         "myapp subcmd multi --help",
         HELP_ABOUT_MULTI_SC_OVERRIDE,
-        false
-    ));
+        false,
+    );
 }
 
 #[test]
@@ -2464,12 +2224,7 @@ fn option_usage_order() {
         Arg::new("x").short('x'),
     ]);
 
-    assert!(utils::compare_output(
-        cmd,
-        "order --help",
-        OPTION_USAGE_ORDER,
-        false
-    ));
+    utils::assert_output(cmd, "order --help", OPTION_USAGE_ORDER, false);
 }
 
 #[test]
@@ -2480,12 +2235,12 @@ fn prefer_about_over_long_about_in_subcommands_list() {
             .about("short about sub"),
     );
 
-    assert!(utils::compare_output(
+    utils::assert_output(
         cmd,
         "about-in-subcommands-list --help",
         ABOUT_IN_SUBCOMMANDS_LIST,
-        false
-    ));
+        false,
+    );
 }
 
 #[test]
@@ -2515,12 +2270,7 @@ OPTIONS:
         )
         .arg(Arg::new("pos2").takes_value(true));
 
-    assert!(utils::compare_output(
-        cmd,
-        "deno --help",
-        USAGE_WITH_GROUP,
-        false
-    ));
+    utils::assert_output(cmd, "deno --help", USAGE_WITH_GROUP, false);
 }
 
 static CUSTOM_HEADING_POS: &str = "test 1.4
@@ -2547,12 +2297,7 @@ fn custom_heading_pos() {
         .next_help_heading(Some("NETWORKING"))
         .arg(Arg::new("speed").help("How fast"));
 
-    assert!(utils::compare_output(
-        cmd,
-        "test --help",
-        CUSTOM_HEADING_POS,
-        false
-    ));
+    utils::assert_output(cmd, "test --help", CUSTOM_HEADING_POS, false);
 }
 
 static ONLY_CUSTOM_HEADING_OPTS_NO_ARGS: &str = "test 1.4
@@ -2573,12 +2318,7 @@ fn only_custom_heading_opts_no_args() {
         .next_help_heading(Some("NETWORKING"))
         .arg(arg!(-s --speed <SPEED> "How fast").required(false));
 
-    assert!(utils::compare_output(
-        cmd,
-        "test --help",
-        ONLY_CUSTOM_HEADING_OPTS_NO_ARGS,
-        false
-    ));
+    utils::assert_output(cmd, "test --help", ONLY_CUSTOM_HEADING_OPTS_NO_ARGS, false);
 }
 
 static ONLY_CUSTOM_HEADING_POS_NO_ARGS: &str = "test 1.4
@@ -2599,12 +2339,7 @@ fn only_custom_heading_pos_no_args() {
         .next_help_heading(Some("NETWORKING"))
         .arg(Arg::new("speed").help("How fast"));
 
-    assert!(utils::compare_output(
-        cmd,
-        "test --help",
-        ONLY_CUSTOM_HEADING_POS_NO_ARGS,
-        false
-    ));
+    utils::assert_output(cmd, "test --help", ONLY_CUSTOM_HEADING_POS_NO_ARGS, false);
 }
 
 #[test]
@@ -2617,7 +2352,7 @@ fn issue_2508_number_of_values_with_single_value_name() {
                 .number_of_values(2)
                 .value_name("ARG"),
         );
-    assert!(utils::compare_output(
+    utils::assert_output(
         cmd,
         "my_app --help",
         "my_app 
@@ -2630,8 +2365,8 @@ OPTIONS:
         --some_arg <some_arg> <some_arg>    
         --some_arg_issue <ARG> <ARG>        
 ",
-        false
-    ));
+        false,
+    );
 }
 
 #[test]
@@ -2640,7 +2375,7 @@ fn missing_positional_final_required() {
         .allow_missing_positional(true)
         .arg(Arg::new("arg1"))
         .arg(Arg::new("arg2").required(true));
-    assert!(utils::compare_output(
+    utils::assert_output(
         cmd,
         "test --help",
         "test 
@@ -2655,8 +2390,8 @@ ARGS:
 OPTIONS:
     -h, --help    Print help information
 ",
-        false
-    ));
+        false,
+    );
 }
 
 #[test]
@@ -2666,7 +2401,7 @@ fn missing_positional_final_multiple() {
         .arg(Arg::new("foo"))
         .arg(Arg::new("bar"))
         .arg(Arg::new("baz").takes_value(true).multiple_values(true));
-    assert!(utils::compare_output(
+    utils::assert_output(
         cmd,
         "test --help",
         "test 
@@ -2682,8 +2417,8 @@ ARGS:
 OPTIONS:
     -h, --help    Print help information
 ",
-        false
-    ));
+        false,
+    );
 }
 
 #[test]
@@ -2694,7 +2429,7 @@ fn positional_multiple_values_is_dotted() {
             .takes_value(true)
             .multiple_values(true),
     );
-    assert!(utils::compare_output(
+    utils::assert_output(
         cmd,
         "test --help",
         "test 
@@ -2708,8 +2443,8 @@ ARGS:
 OPTIONS:
     -h, --help    Print help information
 ",
-        false
-    ));
+        false,
+    );
 
     let cmd = Command::new("test").arg(
         Arg::new("foo")
@@ -2718,7 +2453,7 @@ OPTIONS:
             .value_name("BAR")
             .multiple_values(true),
     );
-    assert!(utils::compare_output(
+    utils::assert_output(
         cmd,
         "test --help",
         "test 
@@ -2732,8 +2467,8 @@ ARGS:
 OPTIONS:
     -h, --help    Print help information
 ",
-        false
-    ));
+        false,
+    );
 }
 
 #[test]
@@ -2744,7 +2479,7 @@ fn positional_multiple_occurrences_is_dotted() {
             .takes_value(true)
             .multiple_occurrences(true),
     );
-    assert!(utils::compare_output(
+    utils::assert_output(
         cmd,
         "test --help",
         "test 
@@ -2758,8 +2493,8 @@ ARGS:
 OPTIONS:
     -h, --help    Print help information
 ",
-        false
-    ));
+        false,
+    );
 
     let cmd = Command::new("test").arg(
         Arg::new("foo")
@@ -2768,7 +2503,7 @@ OPTIONS:
             .value_name("BAR")
             .multiple_occurrences(true),
     );
-    assert!(utils::compare_output(
+    utils::assert_output(
         cmd,
         "test --help",
         "test 
@@ -2782,8 +2517,8 @@ ARGS:
 OPTIONS:
     -h, --help    Print help information
 ",
-        false
-    ));
+        false,
+    );
 }
 
 #[test]
@@ -2851,7 +2586,7 @@ fn subcommand_help_doesnt_have_useless_help_flag() {
     // currently ignores the `--help` flag, the output shouldn't have it.
     let cmd = Command::new("test_app").subcommand(Command::new("test").about("Subcommand"));
 
-    assert!(utils::compare_output(
+    utils::assert_output(
         cmd,
         "example help help",
         "example-help 
@@ -2863,8 +2598,8 @@ USAGE:
 ARGS:
     <SUBCOMMAND>...    The subcommand whose help message to display
 ",
-        false
-    ));
+        false,
+    );
 }
 
 #[test]
@@ -2894,7 +2629,7 @@ fn dont_propagate_version_to_help_subcommand() {
         .propagate_version(true)
         .subcommand(clap::Command::new("subcommand"));
 
-    assert!(utils::compare_output(
+    utils::assert_output(
         cmd.clone(),
         "example help help",
         "example-help 
@@ -2906,8 +2641,8 @@ USAGE:
 ARGS:
     <SUBCOMMAND>...    The subcommand whose help message to display
 ",
-        false
-    ));
+        false,
+    );
 
     cmd.debug_assert();
 }
@@ -2925,3 +2660,80 @@ fn help_without_short() {
     let m = cmd.try_get_matches_from(["test", "-h", "0x100"]).unwrap();
     assert_eq!(m.value_of("hex"), Some("0x100"));
 }
+
+#[test]
+fn parent_cmd_req_in_usage_with_help_flag() {
+    static EXPECTED: &str = "parent-test 
+some
+
+USAGE:
+    parent <TARGET> <ARGS> test
+
+OPTIONS:
+    -h, --help    Print help information
+";
+    let cmd = Command::new("parent")
+        .version("0.1")
+        .arg(Arg::new("TARGET").required(true).help("some"))
+        .arg(
+            Arg::new("ARGS")
+                .takes_value(true)
+                .required(true)
+                .help("some"),
+        )
+        .subcommand(Command::new("test").about("some"));
+    utils::assert_output(cmd, "parent test --help", EXPECTED, false);
+}
+
+#[test]
+fn parent_cmd_req_in_usage_with_help_subcommand() {
+    static EXPECTED: &str = "parent-test 
+some
+
+USAGE:
+    parent <TARGET> <ARGS> test
+
+OPTIONS:
+    -h, --help    Print help information
+";
+    let cmd = Command::new("parent")
+        .version("0.1")
+        .arg(Arg::new("TARGET").required(true).help("some"))
+        .arg(
+            Arg::new("ARGS")
+                .takes_value(true)
+                .required(true)
+                .help("some"),
+        )
+        .subcommand(Command::new("test").about("some"));
+    utils::assert_output(cmd, "parent help test", EXPECTED, false);
+}
+
+#[test]
+fn parent_cmd_req_in_usage_with_render_help() {
+    static EXPECTED: &str = "parent-test 
+some
+
+USAGE:
+    parent <TARGET> <ARGS> test
+
+OPTIONS:
+    -h, --help    Print help information
+";
+    let mut cmd = Command::new("parent")
+        .version("0.1")
+        .arg(Arg::new("TARGET").required(true).help("some"))
+        .arg(
+            Arg::new("ARGS")
+                .takes_value(true)
+                .required(true)
+                .help("some"),
+        )
+        .subcommand(Command::new("test").about("some"));
+    cmd.build();
+    let subcmd = cmd.find_subcommand_mut("test").unwrap();
+
+    let mut buf = Vec::new();
+    subcmd.write_help(&mut buf).unwrap();
+    utils::assert_eq(EXPECTED, String::from_utf8(buf).unwrap());
+}
diff --git a/tests/builder/help_env.rs b/tests/builder/help_env.rs
index c18e3994..7aa5d815 100644
--- a/tests/builder/help_env.rs
+++ b/tests/builder/help_env.rs
@@ -109,7 +109,7 @@ fn hide_env() {
             .takes_value(true),
     );
 
-    assert!(utils::compare_output(cmd, "ctest --help", HIDE_ENV, false));
+    utils::assert_output(cmd, "ctest --help", HIDE_ENV, false);
 }
 
 #[test]
@@ -126,7 +126,7 @@ fn show_env() {
             .takes_value(true),
     );
 
-    assert!(utils::compare_output(cmd, "ctest --help", SHOW_ENV, false));
+    utils::assert_output(cmd, "ctest --help", SHOW_ENV, false);
 }
 
 #[test]
@@ -144,12 +144,7 @@ fn hide_env_vals() {
             .takes_value(true),
     );
 
-    assert!(utils::compare_output(
-        cmd,
-        "ctest --help",
-        HIDE_ENV_VALS,
-        false
-    ));
+    utils::assert_output(cmd, "ctest --help", HIDE_ENV_VALS, false);
 }
 
 #[test]
@@ -166,12 +161,7 @@ fn show_env_vals() {
             .takes_value(true),
     );
 
-    assert!(utils::compare_output(
-        cmd,
-        "ctest --help",
-        SHOW_ENV_VALS,
-        false
-    ));
+    utils::assert_output(cmd, "ctest --help", SHOW_ENV_VALS, false);
 }
 
 #[test]
@@ -187,12 +177,7 @@ fn hide_env_flag() {
             .help("A coffeehouse, coffee shop, or café."),
     );
 
-    assert!(utils::compare_output(
-        cmd,
-        "ctest --help",
-        HIDE_ENV_FLAG,
-        false
-    ));
+    utils::assert_output(cmd, "ctest --help", HIDE_ENV_FLAG, false);
 }
 
 #[test]
@@ -207,12 +192,7 @@ fn show_env_flag() {
             .help("A coffeehouse, coffee shop, or café."),
     );
 
-    assert!(utils::compare_output(
-        cmd,
-        "ctest --help",
-        SHOW_ENV_FLAG,
-        false
-    ));
+    utils::assert_output(cmd, "ctest --help", SHOW_ENV_FLAG, false);
 }
 
 #[test]
@@ -228,12 +208,7 @@ fn hide_env_vals_flag() {
             .help("A coffeehouse, coffee shop, or café."),
     );
 
-    assert!(utils::compare_output(
-        cmd,
-        "ctest --help",
-        HIDE_ENV_VALS_FLAG,
-        false
-    ));
+    utils::assert_output(cmd, "ctest --help", HIDE_ENV_VALS_FLAG, false);
 }
 
 #[test]
@@ -248,10 +223,5 @@ fn show_env_vals_flag() {
             .help("A coffeehouse, coffee shop, or café."),
     );
 
-    assert!(utils::compare_output(
-        cmd,
-        "ctest --help",
-        SHOW_ENV_VALS_FLAG,
-        false
-    ));
+    utils::assert_output(cmd, "ctest --help", SHOW_ENV_VALS_FLAG, false);
 }
diff --git a/tests/builder/hidden_args.rs b/tests/builder/hidden_args.rs
index 3827fe80..d2421d7c 100644
--- a/tests/builder/hidden_args.rs
+++ b/tests/builder/hidden_args.rs
@@ -28,12 +28,7 @@ fn hide_args() {
             arg!(--option <opt> "some option").required(false),
             Arg::new("DUMMY").hide(true),
         ]);
-    assert!(utils::compare_output(
-        cmd,
-        "test --help",
-        HIDDEN_ARGS,
-        false
-    ));
+    utils::assert_output(cmd, "test --help", HIDDEN_ARGS, false);
 }
 
 static HIDDEN_SHORT_ARGS: &str = "test 2.31.2
@@ -89,12 +84,7 @@ fn hide_short_args() {
                 .help("This text should be visible"),
         ]);
 
-    assert!(utils::compare_output(
-        cmd,
-        "test -h",
-        HIDDEN_SHORT_ARGS,
-        false
-    ));
+    utils::assert_output(cmd, "test -h", HIDDEN_SHORT_ARGS, false);
 }
 
 /// Ensure visible with opposite option
@@ -116,12 +106,7 @@ fn hide_short_args_long_help() {
                 .help("This text should be visible"),
         ]);
 
-    assert!(utils::compare_output(
-        cmd,
-        "test --help",
-        HIDDEN_SHORT_ARGS_LONG_HELP,
-        false
-    ));
+    utils::assert_output(cmd, "test --help", HIDDEN_SHORT_ARGS_LONG_HELP, false);
 }
 
 static HIDDEN_LONG_ARGS: &str = "test 2.31.2
@@ -160,12 +145,7 @@ fn hide_long_args() {
                 .help("This text should be visible"),
         ]);
 
-    assert!(utils::compare_output(
-        cmd,
-        "test --help",
-        HIDDEN_LONG_ARGS,
-        false
-    ));
+    utils::assert_output(cmd, "test --help", HIDDEN_LONG_ARGS, false);
 }
 
 static HIDDEN_LONG_ARGS_SHORT_HELP: &str = "test 2.31.2
@@ -200,12 +180,7 @@ fn hide_long_args_short_help() {
                 .help("This text should be visible"),
         ]);
 
-    assert!(utils::compare_output(
-        cmd,
-        "test -h",
-        HIDDEN_LONG_ARGS_SHORT_HELP,
-        false
-    ));
+    utils::assert_output(cmd, "test -h", HIDDEN_LONG_ARGS_SHORT_HELP, false);
 }
 
 static HIDDEN_POS_ARGS: &str = "test 1.4
@@ -228,12 +203,7 @@ fn hide_pos_args() {
         Arg::new("another").help("another pos"),
     ]);
 
-    assert!(utils::compare_output(
-        cmd,
-        "test --help",
-        HIDDEN_POS_ARGS,
-        false
-    ));
+    utils::assert_output(cmd, "test --help", HIDDEN_POS_ARGS, false);
 }
 
 static HIDDEN_SUBCMDS: &str = "test 1.4
@@ -252,12 +222,7 @@ fn hide_subcmds() {
         .version("1.4")
         .subcommand(Command::new("sub").hide(true));
 
-    assert!(utils::compare_output(
-        cmd,
-        "test --help",
-        HIDDEN_SUBCMDS,
-        false
-    ));
+    utils::assert_output(cmd, "test --help", HIDDEN_SUBCMDS, false);
 }
 
 static HIDDEN_OPT_ARGS_ONLY: &str = "test 1.4
@@ -281,12 +246,7 @@ fn hide_opt_args_only() {
                 .hide(true),
         );
 
-    assert!(utils::compare_output(
-        cmd,
-        "test --help",
-        HIDDEN_OPT_ARGS_ONLY,
-        false
-    ));
+    utils::assert_output(cmd, "test --help", HIDDEN_OPT_ARGS_ONLY, false);
 }
 
 static HIDDEN_POS_ARGS_ONLY: &str = "test 1.4
@@ -306,12 +266,7 @@ fn hide_pos_args_only() {
         .mut_arg("version", |a| a.hide(true))
         .args(&[Arg::new("pos").help("some pos").hide(true)]);
 
-    assert!(utils::compare_output(
-        cmd,
-        "test --help",
-        HIDDEN_POS_ARGS_ONLY,
-        false
-    ));
+    utils::assert_output(cmd, "test --help", HIDDEN_POS_ARGS_ONLY, false);
 }
 
 static HIDDEN_SUBCMDS_ONLY: &str = "test 1.4
@@ -331,10 +286,5 @@ fn hide_subcmds_only() {
         .mut_arg("version", |a| a.hide(true))
         .subcommand(Command::new("sub").hide(true));
 
-    assert!(utils::compare_output(
-        cmd,
-        "test --help",
-        HIDDEN_SUBCMDS_ONLY,
-        false
-    ));
+    utils::assert_output(cmd, "test --help", HIDDEN_SUBCMDS_ONLY, false);
 }
diff --git a/tests/builder/opts.rs b/tests/builder/opts.rs
index 5f8a63a2..f0b927ba 100644
--- a/tests/builder/opts.rs
+++ b/tests/builder/opts.rs
@@ -61,12 +61,7 @@ For more information try --help
             .takes_value(true)
             .long("config"),
     );
-    assert!(utils::compare_output(
-        cmd,
-        "prog --config file.conf",
-        NO_EQUALS,
-        true
-    ));
+    utils::assert_output(cmd, "prog --config file.conf", NO_EQUALS, true);
 }
 
 #[test]
@@ -427,12 +422,7 @@ fn leading_hyphen_with_only_pos_follows() {
 #[test]
 #[cfg(feature = "suggestions")]
 fn did_you_mean() {
-    assert!(utils::compare_output(
-        utils::complex_app(),
-        "clap-test --optio=foo",
-        DYM,
-        true
-    ));
+    utils::assert_output(utils::complex_app(), "clap-test --optio=foo", DYM, true);
 }
 
 #[test]
@@ -520,12 +510,12 @@ fn issue_1073_suboptimal_flag_suggestion() {
     let cmd = Command::new("ripgrep-616")
         .arg(Arg::new("files-with-matches").long("files-with-matches"))
         .arg(Arg::new("files-without-match").long("files-without-match"));
-    assert!(utils::compare_output(
+    utils::assert_output(
         cmd,
         "ripgrep-616 --files-without-matches",
         DYM_ISSUE_1073,
-        true
-    ));
+        true,
+    );
 }
 
 #[test]
diff --git a/tests/builder/possible_values.rs b/tests/builder/possible_values.rs
index 61f87ebd..58e6bd55 100644
--- a/tests/builder/possible_values.rs
+++ b/tests/builder/possible_values.rs
@@ -207,79 +207,79 @@ fn possible_values_of_option_multiple_fail() {
 
 #[test]
 fn possible_values_output() {
-    assert!(utils::compare_output(
+    utils::assert_output(
         Command::new("test").arg(Arg::new("option").short('O').possible_values([
             "slow",
             "fast",
-            "ludicrous speed"
+            "ludicrous speed",
         ])),
         "clap-test -O slo",
         PV_ERROR,
-        true
-    ));
+        true,
+    );
 }
 
 #[test]
 fn possible_values_alias_output() {
-    assert!(utils::compare_output(
+    utils::assert_output(
         Command::new("test").arg(
             Arg::new("option")
                 .short('O')
                 .possible_value("slow")
                 .possible_value(PossibleValue::new("fast").alias("fost"))
-                .possible_value(PossibleValue::new("ludicrous speed").aliases(["ls", "lcs"]))
+                .possible_value(PossibleValue::new("ludicrous speed").aliases(["ls", "lcs"])),
         ),
         "clap-test -O slo",
         PV_ERROR,
-        true
-    ));
+        true,
+    );
 }
 
 #[test]
 fn possible_values_hidden_output() {
-    assert!(utils::compare_output(
+    utils::assert_output(
         Command::new("test").arg(
             Arg::new("option")
                 .short('O')
                 .possible_values(["slow", "fast"])
                 .possible_value(PossibleValue::new("ludicrous speed"))
-                .possible_value(PossibleValue::new("forbidden speed").hide(true))
+                .possible_value(PossibleValue::new("forbidden speed").hide(true)),
         ),
         "clap-test -O slo",
         PV_ERROR,
-        true
-    ));
+        true,
+    );
 }
 
 #[test]
 fn escaped_possible_values_output() {
-    assert!(utils::compare_output(
+    utils::assert_output(
         Command::new("test").arg(Arg::new("option").short('O').possible_values([
             "slow",
             "fast",
-            "ludicrous speed"
+            "ludicrous speed",
         ])),
         "clap-test -O ludicrous",
         PV_ERROR_ESCAPED,
-        true
-    ));
+        true,
+    );
 }
 
 #[test]
 fn missing_possible_value_error() {
-    assert!(utils::compare_output(
+    utils::assert_output(
         Command::new("test").arg(
             Arg::new("option")
                 .short('O')
                 .possible_value("slow")
                 .possible_value(PossibleValue::new("fast").alias("fost"))
                 .possible_value(PossibleValue::new("ludicrous speed"))
-                .possible_value(PossibleValue::new("forbidden speed").hide(true))
+                .possible_value(PossibleValue::new("forbidden speed").hide(true)),
         ),
         "clap-test -O",
         MISSING_PV_ERROR,
-        true
-    ));
+        true,
+    );
 }
 
 static MISSING_PV_ERROR: &str =
diff --git a/tests/builder/require.rs b/tests/builder/require.rs
index e1cc956a..7caa4795 100644
--- a/tests/builder/require.rs
+++ b/tests/builder/require.rs
@@ -122,12 +122,7 @@ fn positional_required_with_requires() {
         .arg(Arg::new("opt"))
         .arg(Arg::new("bar"));
 
-    assert!(utils::compare_output(
-        cmd,
-        "clap-test",
-        POSITIONAL_REQ,
-        true
-    ));
+    utils::assert_output(cmd, "clap-test", POSITIONAL_REQ, true);
 }
 
 static POSITIONAL_REQ: &str = "error: The following required arguments were not provided:
@@ -147,12 +142,7 @@ fn positional_required_with_requires_if_no_value() {
         .arg(Arg::new("opt"))
         .arg(Arg::new("bar"));
 
-    assert!(utils::compare_output(
-        cmd,
-        "clap-test",
-        POSITIONAL_REQ_IF_NO_VAL,
-        true
-    ));
+    utils::assert_output(cmd, "clap-test", POSITIONAL_REQ_IF_NO_VAL, true);
 }
 
 static POSITIONAL_REQ_IF_NO_VAL: &str = "error: The following required arguments were not provided:
@@ -172,12 +162,7 @@ fn positional_required_with_requires_if_value() {
         .arg(Arg::new("opt"))
         .arg(Arg::new("bar"));
 
-    assert!(utils::compare_output(
-        cmd,
-        "clap-test val",
-        POSITIONAL_REQ_IF_VAL,
-        true
-    ));
+    utils::assert_output(cmd, "clap-test val", POSITIONAL_REQ_IF_VAL, true);
 }
 
 static POSITIONAL_REQ_IF_VAL: &str = "error: The following required arguments were not provided:
@@ -532,12 +517,7 @@ fn required_unless_any_err() {
 
 #[test]
 fn missing_required_output() {
-    assert!(utils::compare_output(
-        utils::complex_app(),
-        "clap-test -F",
-        MISSING_REQ,
-        true
-    ));
+    utils::assert_output(utils::complex_app(), "clap-test -F", MISSING_REQ, true);
 }
 
 // Conditional external requirements
@@ -794,12 +774,12 @@ fn list_correct_required_args() {
                 .long("output"),
         );
 
-    assert!(utils::compare_output(
+    utils::assert_output(
         cmd,
         "test --input somepath --target file",
         COND_REQ_IN_USAGE,
-        true
-    ));
+        true,
+    );
 }
 
 #[test]
@@ -828,12 +808,12 @@ fn required_if_val_present_fail_error_output() {
                 .long("output"),
         );
 
-    assert!(utils::compare_output(
+    utils::assert_output(
         cmd,
         "test --input somepath --target file",
         COND_REQ_IN_USAGE,
-        true
-    ));
+        true,
+    );
 }
 
 #[test]
@@ -928,12 +908,7 @@ fn require_eq() {
             .value_name("FILE")
             .help("some"),
     );
-    assert!(utils::compare_output(
-        cmd,
-        "clap-test",
-        REQUIRE_EQUALS,
-        true
-    ));
+    utils::assert_output(cmd, "clap-test", REQUIRE_EQUALS, true);
 }
 
 #[test]
@@ -958,12 +933,7 @@ fn require_eq_filtered() {
                 .value_name("FILE")
                 .help("some other arg"),
         );
-    assert!(utils::compare_output(
-        cmd,
-        "clap-test -f=blah",
-        REQUIRE_EQUALS_FILTERED,
-        true
-    ));
+    utils::assert_output(cmd, "clap-test -f=blah", REQUIRE_EQUALS_FILTERED, true);
 }
 
 #[test]
@@ -1005,12 +975,12 @@ fn require_eq_filtered_group() {
                 .args(&["g1", "g2"])
                 .required(true),
         );
-    assert!(utils::compare_output(
+    utils::assert_output(
         cmd,
         "clap-test -f=blah --g1=blah",
         REQUIRE_EQUALS_FILTERED_GROUP,
-        true
-    ));
+        true,
+    );
 }
 
 static ISSUE_1158: &str = "error: The following required arguments were not provided:
@@ -1084,19 +1054,14 @@ For more information try --help
                 .required_unless_present("c")
                 .conflicts_with("c"),
         );
-    assert!(utils::compare_output(
-        cmd,
-        "test --c asd",
-        MULTIPLE_REQUIRED_UNLESS_USAGE,
-        true
-    ));
+    utils::assert_output(cmd, "test --c asd", MULTIPLE_REQUIRED_UNLESS_USAGE, true);
 }
 
 #[test]
 fn issue_1158_conflicting_requirements() {
     let cmd = issue_1158_app();
 
-    assert!(utils::compare_output(cmd, "example id", ISSUE_1158, true));
+    utils::assert_output(cmd, "example id", ISSUE_1158, true);
 }
 
 #[test]
diff --git a/tests/builder/subcommands.rs b/tests/builder/subcommands.rs
index a4152858..90f815ea 100644
--- a/tests/builder/subcommands.rs
+++ b/tests/builder/subcommands.rs
@@ -174,12 +174,12 @@ fn subcommand_display_order() {
             .arg(Arg::new("roster").short('r')),
     ]);
 
-    assert!(utils::compare_output(
+    utils::assert_output(
         app_subcmd_alpha_order,
         "test --help",
         SUBCMD_ALPHA_ORDER,
         false,
-    ));
+    );
 
     let app_subcmd_decl_order = Command::new("test")
         .version("1")
@@ -193,12 +193,12 @@ fn subcommand_display_order() {
                 .arg(Arg::new("roster").short('r')),
         ]);
 
-    assert!(utils::compare_output(
+    utils::assert_output(
         app_subcmd_decl_order,
         "test --help",
         SUBCMD_DECL_ORDER,
         false,
-    ));
+    );
 }
 
 #[test]
@@ -223,7 +223,7 @@ fn multiple_aliases() {
 #[cfg(feature = "suggestions")]
 fn subcmd_did_you_mean_output() {
     let cmd = Command::new("dym").subcommand(Command::new("subcmd"));
-    assert!(utils::compare_output(cmd, "dym subcm", DYM_SUBCMD, true));
+    utils::assert_output(cmd, "dym subcm", DYM_SUBCMD, true);
 }
 
 #[test]
@@ -232,12 +232,7 @@ fn subcmd_did_you_mean_output_ambiguous() {
     let cmd = Command::new("dym")
         .subcommand(Command::new("test"))
         .subcommand(Command::new("temp"));
-    assert!(utils::compare_output(
-        cmd,
-        "dym te",
-        DYM_SUBCMD_AMBIGUOUS,
-        true
-    ));
+    utils::assert_output(cmd, "dym te", DYM_SUBCMD_AMBIGUOUS, true);
 }
 
 #[test]
@@ -260,12 +255,7 @@ For more information try --help
         Command::new("subcmd").arg(arg!(-s --subcmdarg <subcmdarg> "tests").required(false)),
     );
 
-    assert!(utils::compare_output(
-        cmd,
-        "dym --subcmarg subcmd",
-        EXPECTED,
-        true
-    ));
+    utils::assert_output(cmd, "dym --subcmarg subcmd", EXPECTED, true);
 }
 
 #[test]
@@ -286,12 +276,7 @@ For more information try --help
         Command::new("subcmd").arg(arg!(-s --subcmdarg <subcmdarg> "tests").required(false)),
     );
 
-    assert!(utils::compare_output(
-        cmd,
-        "dym --subcmarg foo",
-        EXPECTED,
-        true
-    ));
+    utils::assert_output(cmd, "dym --subcmarg foo", EXPECTED, true);
 }
 
 #[test]
@@ -312,12 +297,7 @@ fn visible_aliases_help_output() {
             .visible_alias("dongle")
             .visible_alias("done"),
     );
-    assert!(utils::compare_output(
-        cmd,
-        "clap-test --help",
-        VISIBLE_ALIAS_HELP,
-        false
-    ));
+    utils::assert_output(cmd, "clap-test --help", VISIBLE_ALIAS_HELP, false);
 }
 
 #[test]
@@ -325,12 +305,7 @@ fn invisible_aliases_help_output() {
     let cmd = Command::new("clap-test")
         .version("2.6")
         .subcommand(Command::new("test").about("Some help").alias("invisible"));
-    assert!(utils::compare_output(
-        cmd,
-        "clap-test --help",
-        INVISIBLE_ALIAS_HELP,
-        false
-    ));
+    utils::assert_output(cmd, "clap-test --help", INVISIBLE_ALIAS_HELP, false);
 }
 
 #[test]
@@ -449,12 +424,7 @@ fn subcommand_placeholder_test() {
 fn subcommand_used_after_double_dash() {
     let cmd = Command::new("cmd").subcommand(Command::new("subcmd"));
 
-    assert!(utils::compare_output(
-        cmd,
-        "cmd -- subcmd",
-        SUBCMD_AFTER_DOUBLE_DASH,
-        true
-    ));
+    utils::assert_output(cmd, "cmd -- subcmd", SUBCMD_AFTER_DOUBLE_DASH, true);
 }
 
 #[test]
@@ -510,7 +480,7 @@ fn subcommand_not_recognized() {
         .subcommand(Command::new("sub"))
         .disable_help_subcommand(true)
         .infer_subcommands(true);
-    assert!(utils::compare_output(
+    utils::assert_output(
         cmd,
         "fake help",
         "error: The subcommand 'help' wasn't recognized
@@ -520,8 +490,8 @@ USAGE:
 
 For more information try --help
 ",
-        true
-    ));
+        true,
+    );
 }
 
 #[cfg(feature = "unstable-multicall")]
diff --git a/tests/builder/template_help.rs b/tests/builder/template_help.rs
index ed5387e6..08940567 100644
--- a/tests/builder/template_help.rs
+++ b/tests/builder/template_help.rs
@@ -68,23 +68,13 @@ SUBCOMMANDS:
 #[test]
 fn with_template() {
     let cmd = get_app().help_template(EXAMPLE1_TMPL_S);
-    assert!(utils::compare_output(
-        cmd,
-        "MyApp --help",
-        SIMPLE_TEMPLATE,
-        false
-    ));
+    utils::assert_output(cmd, "MyApp --help", SIMPLE_TEMPLATE, false);
 }
 
 #[test]
 fn custom_template() {
     let cmd = get_app().help_template(EXAMPLE1_TMPS_F);
-    assert!(utils::compare_output(
-        cmd,
-        "MyApp --help",
-        CUSTOM_TEMPL_HELP,
-        false
-    ));
+    utils::assert_output(cmd, "MyApp --help", CUSTOM_TEMPL_HELP, false);
 }
 
 #[test]
@@ -94,7 +84,7 @@ fn template_empty() {
         .author("Kevin K. <kbknapp@gmail.com>")
         .about("Does awesome things")
         .help_template("");
-    assert!(utils::compare_output(cmd, "MyApp --help", "\n", false));
+    utils::assert_output(cmd, "MyApp --help", "\n", false);
 }
 
 #[test]
@@ -104,12 +94,7 @@ fn template_notag() {
         .author("Kevin K. <kbknapp@gmail.com>")
         .about("Does awesome things")
         .help_template("test no tag test");
-    assert!(utils::compare_output(
-        cmd,
-        "MyApp --help",
-        "test no tag test\n",
-        false
-    ));
+    utils::assert_output(cmd, "MyApp --help", "test no tag test\n", false);
 }
 
 #[test]
@@ -119,12 +104,7 @@ fn template_unknowntag() {
         .author("Kevin K. <kbknapp@gmail.com>")
         .about("Does awesome things")
         .help_template("test {unknown_tag} test");
-    assert!(utils::compare_output(
-        cmd,
-        "MyApp --help",
-        "test {unknown_tag} test\n",
-        false
-    ));
+    utils::assert_output(cmd, "MyApp --help", "test {unknown_tag} test\n", false);
 }
 
 #[test]
@@ -134,12 +114,12 @@ fn template_author_version() {
         .author("Kevin K. <kbknapp@gmail.com>")
         .about("Does awesome things")
         .help_template("{author}\n{version}\n{about}\n{bin}");
-    assert!(utils::compare_output(
+    utils::assert_output(
         cmd,
         "MyApp --help",
         "Kevin K. <kbknapp@gmail.com>\n1.0\nDoes awesome things\nMyApp\n",
-        false
-    ));
+        false,
+    );
 }
 
 // ----------
diff --git a/tests/builder/utils.rs b/tests/builder/utils.rs
index 1e146ea4..4385f796 100644
--- a/tests/builder/utils.rs
+++ b/tests/builder/utils.rs
@@ -7,57 +7,25 @@ use regex::Regex;
 
 use clap::{arg, Arg, ArgGroup, Command};
 
-pub fn compare<S, S2>(l: S, r: S2) -> bool
+#[track_caller]
+pub fn assert_eq<S, S2>(expected: S, actual: S2)
 where
     S: AsRef<str>,
     S2: AsRef<str>,
 {
-    let re = Regex::new("\x1b[^m]*m").unwrap();
-    // Strip out any mismatching \r character on windows that might sneak in on either side
-    let ls = l.as_ref().replace('\r', "");
-    let rs = r.as_ref().replace('\r', "");
-    let left_ = re.replace_all(&*ls, "");
-    let right = re.replace_all(&*rs, "");
-    let b = left_ == right;
-    let line_diff = left_
-        .lines()
-        .zip(right.lines())
-        .enumerate()
-        .filter_map(|(line, (left, right))| {
-            if left != right {
-                Some(format!("Line {}:\n{}\n{}", line, left, right))
-            } else {
-                None
-            }
-        })
-        .collect::<Vec<_>>()
-        .join("\n");
-    let line_count_diff = (left_.lines().count() as isize) - right.lines().count() as isize;
-    if !b {
-        dbg!(&left_);
-        dbg!(&right);
-        println!();
-        println!("--> left");
-        println!("{}", left_);
-        println!("--> right");
-        println!("{}", right);
-        println!("--> diff");
-        println!("{}", line_diff);
-        println!("--");
-        if line_count_diff != 0 {
-            println!("left line count - right line count = {}", line_count_diff);
-        }
-    }
-    b
+    let expected = expected.as_ref();
+    let actual = actual.as_ref();
+    snapbox::assert_eq(expected, actual);
 }
 
-pub fn compare_output(l: Command, args: &str, right: &str, stderr: bool) -> bool {
+#[track_caller]
+pub fn assert_output(l: Command, args: &str, expected: &str, stderr: bool) {
     let mut buf = Cursor::new(Vec::with_capacity(50));
     let res = l.try_get_matches_from(args.split(' ').collect::<Vec<_>>());
     let err = res.unwrap_err();
     write!(&mut buf, "{}", err).unwrap();
-    let content = buf.into_inner();
-    let left = String::from_utf8(content).unwrap();
+    let actual = buf.into_inner();
+    let actual = String::from_utf8(actual).unwrap();
     assert_eq!(
         stderr,
         err.use_stderr(),
@@ -65,7 +33,7 @@ pub fn compare_output(l: Command, args: &str, right: &str, stderr: bool) -> bool
         stderr,
         err.use_stderr()
     );
-    compare(left, right)
+    assert_eq(expected, actual)
 }
 
 // Legacy tests from the python script days
diff --git a/tests/builder/version.rs b/tests/builder/version.rs
index b534ae20..11703807 100644
--- a/tests/builder/version.rs
+++ b/tests/builder/version.rs
@@ -160,12 +160,7 @@ fn version_about_multi_subcmd() {
         .mut_arg("version", |a| a.help("Print custom version about text"))
         .propagate_version(true);
 
-    assert!(utils::compare_output(
-        cmd,
-        "foo bar baz -h",
-        VERSION_ABOUT_MULTI_SC,
-        false
-    ));
+    utils::assert_output(cmd, "foo bar baz -h", VERSION_ABOUT_MULTI_SC, false);
 }
 
 #[test]