Repository: cargo-generate/cargo-generate (rust)

Issue:
`cargo test` shortcut for template authors
As a **template author**
I want to **have an easy way to test my template code**
So that **I save time to manually generate a project, run then cargo test in it and delete the generated project again**

I imagine something like
```sh
cd my-wonderful-template
cargo generate --test

## will run cargo generate as a template user would do, but just into a temp folder
## maybe with default variables to not run interactively?
## will run cargo test in the final generated project
## will delete the temp folder
```


Pull request description:
Support `--test` for running tests on the expanded template
This is an attempt at supporting `--test` for running `cargo --test` inside the temporary folder where the template is expanded.

The implementation is now 100% in line with the original feature request.

* Anything after the `--test` on the command line is forwarded to the `cargo test` command.
* If needed, the user can specify a `CARGO_GENERATE_TEST_CMD` environment variable, whose contents will be used instead of `cargo test`, thereby making the command work for non Rust templates.
* User does not have to specify a project-name, as one will be generated.
* `--test` can not be used with `--git`, `--path` or `--favorite`. If will *always* use `--path .` internally.
* If a favorite/subfolder is specified, it will be interpreted as a subfolder of $CWD.

![t-rec_2](https://user-images.githubusercontent.com/7338549/179186076-adbcef30-b0d8-4655-ba0b-b874d3750003.gif)

Fixes #455 

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 501a3ca..797273a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -135,6 +135,7 @@ dependencies = [
  "liquid-core",
  "liquid-derive",
  "liquid-lib",
+ "names",
  "openssl",
  "paste",
  "path-absolutize",
@@ -202,9 +203,9 @@ dependencies = [
 
 [[package]]
 name = "clap_lex"
-version = "0.2.2"
+version = "0.2.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5538cd660450ebeb4234cfecf8f2284b844ffc4c50531e66d584ad5b91293613"
+checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
 dependencies = [
  "os_str_bytes",
 ]
@@ -226,9 +227,9 @@ dependencies = [
 
 [[package]]
 name = "crossbeam-channel"
-version = "0.5.4"
+version = "0.5.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53"
+checksum = "4c02a4d71819009c192cf4872265391563fd6a84c81ff2c0f2a7026ca4c1d85c"
 dependencies = [
  "cfg-if",
  "crossbeam-utils",
@@ -247,26 +248,26 @@ dependencies = [
 
 [[package]]
 name = "crossbeam-epoch"
-version = "0.9.8"
+version = "0.9.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c"
+checksum = "07db9d94cbd326813772c968ccd25999e5f8ae22f4f8d1b11effa37ef6ce281d"
 dependencies = [
  "autocfg",
  "cfg-if",
  "crossbeam-utils",
- "lazy_static",
  "memoffset",
+ "once_cell",
  "scopeguard",
 ]
 
 [[package]]
 name = "crossbeam-utils"
-version = "0.8.8"
+version = "0.8.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38"
+checksum = "7d82ee10ce34d7bc12c2122495e7593a9c41347ecdd64185af4ecf72cb1a7f83"
 dependencies = [
  "cfg-if",
- "lazy_static",
+ "once_cell",
 ]
 
 [[package]]
@@ -276,7 +277,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "3495912c9c1ccf2e18976439f4443f3fee0fd61f424ff99fde6a66b15ecb448f"
 dependencies = [
  "cfg-if",
- "hashbrown 0.12.1",
+ "hashbrown",
  "lock_api",
  "parking_lot_core",
 ]
@@ -335,9 +336,9 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
 
 [[package]]
 name = "either"
-version = "1.6.1"
+version = "1.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
+checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be"
 
 [[package]]
 name = "encode_unicode"
@@ -623,15 +624,9 @@ dependencies = [
 
 [[package]]
 name = "hashbrown"
-version = "0.11.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
-
-[[package]]
-name = "hashbrown"
-version = "0.12.1"
+version = "0.12.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3"
+checksum = "607c8a29735385251a339424dd462993c0fed8fa09d378f259377df08c126022"
 
 [[package]]
 name = "heck"
@@ -694,12 +689,12 @@ dependencies = [
 
 [[package]]
 name = "indexmap"
-version = "1.8.2"
+version = "1.9.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e6012d540c5baa3589337a98ce73408de9b5a25ec9fc2c6fd6be8f0d39e0ca5a"
+checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
 dependencies = [
  "autocfg",
- "hashbrown 0.11.2",
+ "hashbrown",
 ]
 
 [[package]]
@@ -911,9 +906,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
 
 [[package]]
 name = "memmap2"
-version = "0.5.4"
+version = "0.5.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d5172b50c23043ff43dd53e51392f36519d9b35a8f3a410d30ece5d1aedd58ae"
+checksum = "3a79b39c93a7a5a27eeaf9a23b5ff43f1b9e0ad6b1cdd441140ae53c35613fc7"
 dependencies = [
  "libc",
 ]
@@ -933,6 +928,15 @@ version = "0.2.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
 
+[[package]]
+name = "names"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7bddcd3bf5144b6392de80e04c347cd7fab2508f6df16a85fc496ecd5cec39bc"
+dependencies = [
+ "rand",
+]
+
 [[package]]
 name = "nom"
 version = "7.1.1"
@@ -985,9 +989,9 @@ checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
 
 [[package]]
 name = "once_cell"
-version = "1.12.0"
+version = "1.13.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225"
+checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
 
 [[package]]
 name = "opaque-debug"
@@ -1148,6 +1152,12 @@ version = "0.3.25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"
 
+[[package]]
+name = "ppv-lite86"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
+
 [[package]]
 name = "predicates"
 version = "2.1.1"
@@ -1210,9 +1220,9 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
 
 [[package]]
 name = "proc-macro2"
-version = "1.0.39"
+version = "1.0.40"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f"
+checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7"
 dependencies = [
  "unicode-ident",
 ]
@@ -1249,13 +1259,43 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
 
 [[package]]
 name = "quote"
-version = "1.0.18"
+version = "1.0.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
+checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804"
 dependencies = [
  "proc-macro2",
 ]
 
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
+dependencies = [
+ "getrandom",
+]
+
 [[package]]
 name = "rayon"
 version = "1.5.3"
@@ -1464,9 +1504,9 @@ dependencies = [
 
 [[package]]
 name = "smallvec"
-version = "1.8.0"
+version = "1.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
+checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1"
 
 [[package]]
 name = "smartstring"
@@ -1487,9 +1527,9 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
 
 [[package]]
 name = "syn"
-version = "1.0.96"
+version = "1.0.98"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf"
+checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1611,9 +1651,9 @@ checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
 
 [[package]]
 name = "ucd-trie"
-version = "0.1.3"
+version = "0.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
+checksum = "89570599c4fe5585de2b388aab47e99f7fa4e9238a1399f707a02e356058141c"
 
 [[package]]
 name = "unicode-bidi"
@@ -1635,9 +1675,9 @@ checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c"
 
 [[package]]
 name = "unicode-normalization"
-version = "0.1.19"
+version = "0.1.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9"
+checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6"
 dependencies = [
  "tinyvec",
 ]
@@ -1823,6 +1863,6 @@ checksum = "f4dd6dc7df2d84cf7b33822ed5b86318fb1781948e9663bacd047fc9dd52259d"
 
 [[package]]
 name = "zeroize"
-version = "1.5.5"
+version = "1.5.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94693807d016b2f2d2e14420eb3bfcca689311ff775dcf113d74ea624b7cdf07"
+checksum = "20b578acffd8516a6c3f2a1bdefc1ec37e547bb4e0fb8b6b01a4cafc886b4442"
diff --git a/Cargo.toml b/Cargo.toml
index be65870..1757cfa 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -30,6 +30,7 @@ rhai = "1.8"
 path-absolutize = "3.0"
 git-config = "0.5.0"
 paste = "1.0"
+names = { version = "0.14", default-features = false }
 
 # liquid
 liquid = "0.26"
diff --git a/src/args.rs b/src/args.rs
index 7940d7d..42fbdd4 100644
--- a/src/args.rs
+++ b/src/args.rs
@@ -43,7 +43,8 @@ pub struct GenerateArgs {
             "define",
             "init",
             "template-values-file",
-            "ssh-identity"
+            "ssh-identity",
+            "test",
         ])
     )]
     pub list_favorites: bool,
@@ -118,6 +119,10 @@ pub struct GenerateArgs {
     /// Allow the template to overwrite existing files in the destination.
     #[clap(short, long, action)]
     pub overwrite: bool,
+
+    /// All args after "--" on the command line.
+    #[clap(skip)]
+    pub other_args: Option<Vec<String>>,
 }
 
 #[derive(Debug, Clone, Args)]
@@ -131,6 +136,12 @@ pub struct TemplatePath {
     #[clap()]
     pub subfolder: Option<String>,
 
+    /// Expand $CWD as a template, then run `cargo test` on the expansion (set $CARGO_GENERATE_TEST_CMD to override test command).
+    ///
+    /// Any arguments given after the `--test` argument, will be used as arguments for the test command.
+    #[clap(long, action, group("SpecificPath"))]
+    pub test: bool,
+
     /// Git repository to clone template from. Can be a URL (like
     /// `https://github.com/rust-cli/cli-template`), a path (relative or absolute), or an
     /// `owner/repo` abbreviated GitHub URL (like `rust-cli/cli-template`).
diff --git a/src/lib.rs b/src/lib.rs
index 08a928a..7384bb5 100755
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -53,7 +53,6 @@ use hooks::execute_hooks;
 use ignore_me::remove_dir_files;
 use interactive::prompt_and_check_variable;
 use project_variables::{StringEntry, TemplateSlots, VarInfo};
-use std::ffi::OsString;
 use std::{
     borrow::Borrow,
     collections::HashMap,
@@ -147,6 +146,28 @@ fn internal_generate(mut args: GenerateArgs) -> Result<PathBuf> {
         &args,
     )?;
 
+    if args.template_path.test {
+        test_expanded_template(&template_dir, &args)
+    } else {
+        copy_expanded_template(
+            template_dir,
+            project_dir,
+            user_parsed_input,
+            config,
+            args,
+            branch,
+        )
+    }
+}
+
+fn copy_expanded_template(
+    template_dir: PathBuf,
+    project_dir: PathBuf,
+    user_parsed_input: UserParsedInput,
+    config: Config,
+    args: GenerateArgs,
+    branch: String,
+) -> Result<PathBuf> {
     println!(
         "{} {} `{}`{}",
         emoji::WRENCH,
@@ -155,7 +176,6 @@ fn internal_generate(mut args: GenerateArgs) -> Result<PathBuf> {
         style("...").bold()
     );
     copy_dir_all(&template_dir, &project_dir, user_parsed_input.overwrite())?;
-
     let vcs = config
         .template
         .and_then(|t| t.vcs)
@@ -164,7 +184,6 @@ fn internal_generate(mut args: GenerateArgs) -> Result<PathBuf> {
         info!("{}", style("Initializing a fresh Git repository").bold());
         vcs.initialize(&project_dir, branch, args.force_git_init)?;
     }
-
     println!(
         "{} {} {} {}",
         emoji::SPARKLE,
@@ -172,10 +191,43 @@ fn internal_generate(mut args: GenerateArgs) -> Result<PathBuf> {
         style("New project created").bold(),
         style(&project_dir.display()).underlined()
     );
-
     Ok(project_dir)
 }
 
+fn test_expanded_template(template_dir: &PathBuf, args: &GenerateArgs) -> Result<PathBuf> {
+    println!(
+        "{} {}{}{}",
+        emoji::WRENCH,
+        style("Running \"").bold(),
+        style("cargo test"),
+        style("\" ...").bold(),
+    );
+    std::env::set_current_dir(template_dir)?;
+    let (cmd, cmd_args) = std::env::var("CARGO_GENERATE_TEST_CMD")
+        .map(|env_test_cmd| {
+            let mut split_cmd_args = env_test_cmd.split_whitespace().map(str::to_string);
+            (
+                split_cmd_args.next().unwrap(),
+                split_cmd_args.collect::<Vec<String>>(),
+            )
+        })
+        .unwrap_or_else(|_| (String::from("cargo"), vec![String::from("test")]));
+    std::process::Command::new(cmd)
+        .args(cmd_args)
+        .args(
+            args.other_args
+                .as_ref()
+                .cloned()
+                .unwrap_or_default()
+                .into_iter(),
+        )
+        .spawn()?
+        .wait()?
+        .success()
+        .then(PathBuf::new)
+        .ok_or_else(|| anyhow!("{} Testing failed", emoji::ERROR))
+}
+
 fn prepare_local_template(
     source_template: &UserParsedInput,
 ) -> Result<(TempDir, PathBuf, String), anyhow::Error> {
@@ -407,14 +459,13 @@ pub(crate) fn copy_dir_all(
     }
     fn copy_all(src: impl AsRef<Path>, dst: impl AsRef<Path>, overwrite: bool) -> Result<()> {
         fs::create_dir_all(&dst)?;
-        let git_file_name: OsString = ".git".into();
         for src_entry in fs::read_dir(src)? {
             let src_entry = src_entry?;
             let filename = src_entry.file_name().to_string_lossy().to_string();
             let entry_type = src_entry.file_type()?;
             if entry_type.is_dir() {
                 let dst_path = dst.as_ref().join(filename);
-                if git_file_name == src_entry.file_name() {
+                if ".git" == src_entry.file_name() {
                     continue;
                 }
                 copy_dir_all(src_entry.path(), dst_path, overwrite)?;
diff --git a/src/main.rs b/src/main.rs
index ed19ef3..789bab2 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,9 +1,11 @@
+use std::path::PathBuf;
+
 use anyhow::Result;
 use cargo_generate::{generate, list_favorites, Cli};
 use clap::Parser;
 
 fn main() -> Result<()> {
-    let Cli::Generate(args) = Cli::parse();
+    let args = resolve_args();
 
     if args.list_favorites {
         list_favorites(&args)?;
@@ -13,3 +15,32 @@ fn main() -> Result<()> {
 
     Ok(())
 }
+
+fn resolve_args() -> cargo_generate::GenerateArgs {
+    let (args, other_args): (Vec<_>, Vec<_>) = {
+        let mut before_other_args = true;
+        std::env::args().partition(|a| {
+            if before_other_args && a == "--test" {
+                before_other_args = false;
+                true
+            } else {
+                before_other_args
+            }
+        })
+    };
+    let Cli::Generate(mut args) = Cli::parse_from(args);
+    args.other_args = Some(other_args);
+    if args.template_path.test {
+        args.template_path.path = Some(
+            args.template_path
+                .auto_path
+                .take()
+                .map(|sub| PathBuf::from(".").join(sub).display().to_string())
+                .unwrap_or_else(|| String::from(".")),
+        );
+        if args.name.is_none() {
+            args.name = names::Generator::default().next();
+        }
+    }
+    args
+}


Test patch:
diff --git a/tests/integration/library.rs b/tests/integration/library.rs
index 39e2bd2..8eff4ea 100644
--- a/tests/integration/library.rs
+++ b/tests/integration/library.rs
@@ -18,6 +18,7 @@ fn it_allows_generate_call_with_public_args_and_returns_the_generated_path() {
             path: None,
             favorite: None,
             subfolder: None,
+            test: false,
         },
         name: Some(String::from("foobar_project")),
         force: true,
@@ -36,6 +37,7 @@ fn it_allows_generate_call_with_public_args_and_returns_the_generated_path() {
         force_git_init: false,
         allow_commands: false,
         overwrite: false,
+        other_args: None,
     };
 
     assert_eq!(