Repository: clap-rs/clap
Pull request #2104 (2020-08-23 19:49:35)

Basic centralized sanity test for all examples
This changeset was split off of #2084. It partially addresses #1652.

The sanity test currently just runs all examples with `--help`, asserting that the exit code is 0 and something is written to stdout. It could check other properties additionally/instead—let's follow up in comments.


Patch:
diff --git a/examples/03_args.rs b/examples/03_args.rs
index dca9d2d6..86cfa997 100644
--- a/examples/03_args.rs
+++ b/examples/03_args.rs
@@ -41,7 +41,6 @@ fn main() {
                 .long("config"),
             Arg::new("input")
                 .about("the input file to use")
-                .index(1)
                 .required(true),
         ])
         // *Note* the following two examples are convenience methods, if you wish
diff --git a/examples/05_flag_args.rs b/examples/05_flag_args.rs
index 6183a0df..28e771d3 100644
--- a/examples/05_flag_args.rs
+++ b/examples/05_flag_args.rs
@@ -29,9 +29,8 @@ fn main() {
                                            // also has a conflicts_with_all(Vec<&str>)
                                            // and an exclusive(true)
         )
-        // NOTE: In order to compile this example, comment out requires() and
-        // conflicts_with() because we have not defined an "output" or "config"
-        // argument.
+        .arg("-c, --config=[FILE] 'sets a custom config file'")
+        .arg("<output> 'sets an output file'")
         .get_matches();
 
     // We can find out whether or not awesome was used
diff --git a/examples/06_positional_args.rs b/examples/06_positional_args.rs
index 2a2d6a64..31d2d66f 100644
--- a/examples/06_positional_args.rs
+++ b/examples/06_positional_args.rs
@@ -33,8 +33,7 @@ fn main() {
         // requires "config"
         // Note, we also do not need to specify requires("input")
         // because requires lists are automatically two-way
-        // NOTE: In order to compile this example, comment out conflicts_with()
-        // because we have not defined an "output" argument.
+        .arg(Arg::new("output").about("the output file to use").index(3))
         .get_matches();
 
     // We can find out whether or not "input" or "config" were used
diff --git a/examples/07_option_args.rs b/examples/07_option_args.rs
index 2933b71b..1c5eb438 100644
--- a/examples/07_option_args.rs
+++ b/examples/07_option_args.rs
@@ -33,9 +33,8 @@ fn main() {
                                            // also has a conflicts_with_all(Vec<&str>)
                                            // and an exclusive(true)
         )
-        // NOTE: In order to compile this example, comment out conflicts_with()
-        // and requires() because we have not defined an "output" or "config"
-        // argument.
+        .arg("-c, --config=[FILE] 'the config file to use'")
+        .arg("<output> 'the output file to use'")
         .get_matches();
 
     // We can find out whether or not "input" was used
diff --git a/examples/14_groups.rs b/examples/14_groups.rs
index 4fbc727d..7341193d 100644
--- a/examples/14_groups.rs
+++ b/examples/14_groups.rs
@@ -34,7 +34,7 @@ fn main() {
         .group(
             ArgGroup::new("vers")
                 .required(true)
-                .args(&["ver", "major", "minor", "patch"]),
+                .args(&["set-ver", "major", "minor", "patch"]),
         )
         // Arguments can also be added to a group individually, these two arguments
         // are part of the "input" group which is not required


Test patch:
diff --git a/tests/examples.rs b/tests/examples.rs
new file mode 100644
index 00000000..2c080256
--- /dev/null
+++ b/tests/examples.rs
@@ -0,0 +1,53 @@
+#![cfg(not(tarpaulin))]
+
+use std::ffi::OsStr;
+use std::fs;
+use std::process::{Command, Output};
+
+fn run_example<S: AsRef<str>>(name: S, args: &[&str]) -> Output {
+    let mut all_args = vec![
+        "run",
+        "--example",
+        name.as_ref(),
+        "--features",
+        "yaml unstable",
+        "--",
+    ];
+    all_args.extend_from_slice(args);
+
+    Command::new(env!("CARGO"))
+        .args(all_args)
+        .output()
+        .expect("failed to run example")
+}
+
+#[test]
+fn examples_are_functional() {
+    let example_paths = fs::read_dir("examples")
+        .expect("couldn't read examples directory")
+        .map(|result| result.expect("couldn't get directory entry").path())
+        .filter(|path| path.is_file() && path.extension().and_then(OsStr::to_str) == Some("rs"));
+
+    let mut example_count = 0;
+    for path in example_paths {
+        example_count += 1;
+
+        let example_name = path
+            .file_stem()
+            .and_then(OsStr::to_str)
+            .expect("unable to determine example name");
+
+        let help_output = run_example(example_name, &["--help"]);
+        assert!(
+            help_output.status.success(),
+            "{} --help exited with nonzero",
+            example_name,
+        );
+        assert!(
+            !help_output.stdout.is_empty(),
+            "{} --help had no output",
+            example_name,
+        );
+    }
+    assert!(example_count > 0);
+}