From 800172fbe713670edab4d8c486b2e1e67d3a1038 Mon Sep 17 00:00:00 2001 From: Vitaly _Vi Shukela Date: Sat, 23 Jan 2016 15:48:59 +0300 Subject: [PATCH] Implement "cargo init" When non-existing directory is provided as argument, it works just like "cargo new". When existing directory is used, it may also create template source file like "cargo new" or may find and use existing source code for Cargo.toml. Squashed commit of the following: cargo init: Supply USER envvar for one test cargo init: Other message when Cargo.toml already exists cargo init: Resolve conflict after with #2257 fix minor issues cargo new/init: Simplify error handling code in entry points cargo new/init: Better message for invalid characters in name cargo init: fix minor issues in test cargo init: Avoid excessive builds in the test cargo init: minor fixes cargo init: Skip no_filename test on Windows cargo init: Implement better error message for bin name clash cargo init: minor fixes cargo init: handle "/" path cargo init: Actualise cargo new: Fix upper case error message in test cargo init: Remove paths::{file,directory}_already_exists fix uppper-case error messages cargo init: Fix minor issues per diff comments cargo init: Change binary handling cargo init: Move multiple lib error detection away from mk cargo init: Support optional path argument cargo init: Fix minor issues per Github comments cargo init: Fix complaint from tests/check-style.sh cargo init: Handle projects with multiple mains cargo init: Major refactor, multi-target projects cargo init: Add Cargo.lock unconditionally cargo init: Fix complains from tests/check-style.sh cargo init: Tests for handling VCS cargo init: Handle VCS cargo init: work in progress cargo init: Deduplicate some things between new and init cargo init: Auto-detection of --bin cargo init: work in progress... cargo init: Fix tests and allow explicit --vcs cargo init: intermediate refactor cargo init: First sketch of implementation cargo init: Preliminary test cargo init: first stub See https://github.com/vi/cargo/tree/cargo_init_unsquashed for individual commits --- src/bin/cargo.rs | 1 + src/bin/init.rs | 53 ++++++ src/cargo/ops/cargo_new.rs | 342 ++++++++++++++++++++++++++++++++----- src/cargo/ops/mod.rs | 2 +- src/cargo/util/paths.rs | 16 ++ tests/test_cargo_init.rs | 315 ++++++++++++++++++++++++++++++++++ tests/test_cargo_new.rs | 4 +- tests/tests.rs | 1 + 8 files changed, 688 insertions(+), 46 deletions(-) create mode 100644 src/bin/init.rs create mode 100644 tests/test_cargo_init.rs diff --git a/src/bin/cargo.rs b/src/bin/cargo.rs index 72db84d251d..62556279d59 100644 --- a/src/bin/cargo.rs +++ b/src/bin/cargo.rs @@ -69,6 +69,7 @@ macro_rules! each_subcommand{ $mac!(generate_lockfile); $mac!(git_checkout); $mac!(help); + $mac!(init); $mac!(install); $mac!(locate_project); $mac!(login); diff --git a/src/bin/init.rs b/src/bin/init.rs new file mode 100644 index 00000000000..c5e4cb66d47 --- /dev/null +++ b/src/bin/init.rs @@ -0,0 +1,53 @@ +use std::env; + +use cargo::ops; +use cargo::util::{CliResult, Config}; + +#[derive(RustcDecodable)] +struct Options { + flag_verbose: bool, + flag_quiet: bool, + flag_color: Option, + flag_bin: bool, + arg_path: Option, + flag_name: Option, + flag_vcs: Option, +} + +pub const USAGE: &'static str = " +Create a new cargo package in current directory + +Usage: + cargo init [options] [] + cargo init -h | --help + +Options: + -h, --help Print this message + --vcs VCS Initialize a new repository for the given version + control system (git or hg) or do not initialize any version + control at all (none) overriding a global configuration. + --bin Use a binary instead of a library template + --name NAME Set the resulting package name + -v, --verbose Use verbose output + -q, --quiet No output printed to stdout + --color WHEN Coloring: auto, always, never +"; + +pub fn execute(options: Options, config: &Config) -> CliResult> { + debug!("executing; cmd=cargo-init; args={:?}", env::args().collect::>()); + try!(config.shell().set_verbosity(options.flag_verbose, options.flag_quiet)); + try!(config.shell().set_color_config(options.flag_color.as_ref().map(|s| &s[..]))); + + let Options { flag_bin, arg_path, flag_name, flag_vcs, .. } = options; + + let opts = ops::NewOptions { + version_control: flag_vcs, + bin: flag_bin, + path: &arg_path.unwrap_or(format!(".")), + name: flag_name.as_ref().map(|s| s.as_ref()), + }; + + try!(ops::init(opts, config)); + Ok(None) +} + diff --git a/src/cargo/ops/cargo_new.rs b/src/cargo/ops/cargo_new.rs index 18cfeb88ff2..a855403d1ee 100644 --- a/src/cargo/ops/cargo_new.rs +++ b/src/cargo/ops/cargo_new.rs @@ -2,6 +2,7 @@ use std::env; use std::fs; use std::io::prelude::*; use std::path::Path; +use std::collections::BTreeMap; use rustc_serialize::{Decodable, Decoder}; @@ -24,6 +25,19 @@ pub struct NewOptions<'a> { pub name: Option<&'a str>, } +struct SourceFileInformation { + relative_path: String, + target_name: String, + bin: bool, +} + +struct MkOptions<'a> { + version_control: Option, + path: &'a Path, + name: &'a str, + source_files: Vec, +} + impl Decodable for VersionControl { fn decode(d: &mut D) -> Result { Ok(match &try!(d.read_str())[..] { @@ -44,38 +58,234 @@ struct CargoNewConfig { version_control: Option, } -pub fn new(opts: NewOptions, config: &Config) -> CargoResult<()> { - let path = config.cwd().join(opts.path); - if fs::metadata(&path).is_ok() { - bail!("destination `{}` already exists", path.display()) +fn get_name<'a>(path: &'a Path, opts: &'a NewOptions, config: &Config) -> CargoResult<&'a str> { + if let Some(name) = opts.name { + return Ok(name); } - let name = match opts.name { - Some(name) => name, - None => { - let dir_name = try!(path.file_name().and_then(|s| s.to_str()).chain_error(|| { - human(&format!("cannot create a project with a non-unicode name: {:?}", - path.file_name().unwrap())) - })); - if opts.bin { - dir_name - } else { - let new_name = strip_rust_affixes(dir_name); - if new_name != dir_name { - let message = format!( - "note: package will be named `{}`; use --name to override", - new_name); - try!(config.shell().say(&message, BLACK)); - } - new_name - } + + if path.file_name().is_none() { + bail!("cannot auto-detect project name from path {:?} ; use --name to override", + path.as_os_str()); + } + + let dir_name = try!(path.file_name().and_then(|s| s.to_str()).chain_error(|| { + human(&format!("cannot create a project with a non-unicode name: {:?}", + path.file_name().unwrap())) + })); + + if opts.bin { + Ok(dir_name) + } else { + let new_name = strip_rust_affixes(dir_name); + if new_name != dir_name { + let message = format!( + "note: package will be named `{}`; use --name to override", + new_name); + try!(config.shell().say(&message, BLACK)); } - }; + Ok(new_name) + } +} + +fn check_name(name: &str) -> CargoResult<()> { for c in name.chars() { if c.is_alphanumeric() { continue } if c == '_' || c == '-' { continue } - bail!("Invalid character `{}` in crate name: `{}`", c, name) + bail!("Invalid character `{}` in crate name: `{}`\n\ + use --name to override crate name", + c, name) + } + Ok(()) +} + +fn detect_source_paths_and_types(project_path : &Path, + project_name: &str, + detected_files: &mut Vec, + ) -> CargoResult<()> { + let path = project_path; + let name = project_name; + + enum H { + Bin, + Lib, + Detect, + } + + struct Test { + proposed_path: String, + handling: H, + } + + let tests = vec![ + Test { proposed_path: format!("src/main.rs"), handling: H::Bin }, + Test { proposed_path: format!("main.rs"), handling: H::Bin }, + Test { proposed_path: format!("src/{}.rs", name), handling: H::Detect }, + Test { proposed_path: format!("{}.rs", name), handling: H::Detect }, + Test { proposed_path: format!("src/lib.rs"), handling: H::Lib }, + Test { proposed_path: format!("lib.rs"), handling: H::Lib }, + ]; + + for i in tests { + let pp = i.proposed_path; + + // path/pp does not exist or is not a file + if !fs::metadata(&path.join(&pp)).map(|x| x.is_file()).unwrap_or(false) { + continue; + } + + let sfi = match i.handling { + H::Bin => { + SourceFileInformation { + relative_path: pp, + target_name: project_name.to_string(), + bin: true + } + } + H::Lib => { + SourceFileInformation { + relative_path: pp, + target_name: project_name.to_string(), + bin: false + } + } + H::Detect => { + let content = try!(paths::read(&path.join(pp.clone()))); + let isbin = content.contains("fn main"); + SourceFileInformation { + relative_path: pp, + target_name: project_name.to_string(), + bin: isbin + } + } + }; + detected_files.push(sfi); + } + + // Check for duplicate lib attempt + + let mut previous_lib_relpath : Option<&str> = None; + let mut duplicates_checker : BTreeMap<&str, &SourceFileInformation> = BTreeMap::new(); + + for i in detected_files { + if i.bin { + if let Some(x) = BTreeMap::get::(&duplicates_checker, i.target_name.as_ref()) { + bail!("\ +multiple possible binary sources found: + {} + {} +cannot automatically generate Cargo.toml as the main target would be ambiguous", + &x.relative_path, &i.relative_path); + } + duplicates_checker.insert(i.target_name.as_ref(), i); + } else { + if let Some(plp) = previous_lib_relpath { + return Err(human(format!("cannot have a project with \ + multiple libraries, \ + found both `{}` and `{}`", + plp, i.relative_path))); + } + previous_lib_relpath = Some(&i.relative_path); + } + } + + Ok(()) +} + +fn plan_new_source_file(bin: bool, project_name: String) -> SourceFileInformation { + if bin { + SourceFileInformation { + relative_path: "src/main.rs".to_string(), + target_name: project_name, + bin: true, + } + } else { + SourceFileInformation { + relative_path: "src/lib.rs".to_string(), + target_name: project_name, + bin: false, + } } - mk(config, &path, name, &opts).chain_error(|| { +} + +pub fn new(opts: NewOptions, config: &Config) -> CargoResult<()> { + let path = config.cwd().join(opts.path); + if fs::metadata(&path).is_ok() { + bail!("destination `{}` already exists", + path.display()) + } + + let name = try!(get_name(&path, &opts, config)); + try!(check_name(name)); + + let mkopts = MkOptions { + version_control: opts.version_control, + path: &path, + name: name, + source_files: vec![plan_new_source_file(opts.bin, name.to_string())], + }; + + mk(config, &mkopts).chain_error(|| { + human(format!("Failed to create project `{}` at `{}`", + name, path.display())) + }) +} + +pub fn init(opts: NewOptions, config: &Config) -> CargoResult<()> { + let path = config.cwd().join(opts.path); + + let cargotoml_path = path.join("Cargo.toml"); + if fs::metadata(&cargotoml_path).is_ok() { + bail!("`cargo init` cannot be run on existing Cargo projects") + } + + let name = try!(get_name(&path, &opts, config)); + try!(check_name(name)); + + let mut src_paths_types = vec![]; + + try!(detect_source_paths_and_types(&path, name, &mut src_paths_types)); + + if src_paths_types.len() == 0 { + src_paths_types.push(plan_new_source_file(opts.bin, name.to_string())); + } else { + // --bin option may be ignored if lib.rs or src/lib.rs present + // Maybe when doing `cargo init --bin` inside a library project stub, + // user may mean "initialize for library, but also add binary target" + } + + let mut version_control = opts.version_control; + + if version_control == None { + let mut num_detected_vsces = 0; + + if fs::metadata(&path.join(".git")).is_ok() { + version_control = Some(VersionControl::Git); + num_detected_vsces += 1; + } + + if fs::metadata(&path.join(".hg")).is_ok() { + version_control = Some(VersionControl::Hg); + num_detected_vsces += 1; + } + + // if none exists, maybe create git, like in `cargo new` + + if num_detected_vsces > 1 { + bail!("both .git and .hg directories found \ + and the ignore file can't be \ + filled in as a result, \ + specify --vcs to override detection"); + } + } + + let mkopts = MkOptions { + version_control: version_control, + path: &path, + name: name, + source_files: src_paths_types, + }; + + mk(config, &mkopts).chain_error(|| { human(format!("Failed to create project `{}` at `{}`", name, path.display())) }) @@ -99,14 +309,13 @@ fn existing_vcs_repo(path: &Path, cwd: &Path) -> bool { GitRepo::discover(path, cwd).is_ok() || HgRepo::discover(path, cwd).is_ok() } -fn mk(config: &Config, path: &Path, name: &str, - opts: &NewOptions) -> CargoResult<()> { +fn mk(config: &Config, opts: &MkOptions) -> CargoResult<()> { + let path = opts.path; + let name = opts.name; let cfg = try!(global_config(config)); let mut ignore = "target\n".to_string(); let in_existing_vcs_repo = existing_vcs_repo(path.parent().unwrap(), config.cwd()); - if !opts.bin { - ignore.push_str("Cargo.lock\n"); - } + ignore.push_str("Cargo.lock\n"); let vcs = match (opts.version_control, cfg.version_control, in_existing_vcs_repo) { (None, None, false) => VersionControl::Git, @@ -117,15 +326,19 @@ fn mk(config: &Config, path: &Path, name: &str, match vcs { VersionControl::Git => { - try!(GitRepo::init(path, config.cwd())); - try!(paths::write(&path.join(".gitignore"), ignore.as_bytes())); + if !fs::metadata(&path.join(".git")).is_ok() { + try!(GitRepo::init(path, config.cwd())); + } + try!(paths::append(&path.join(".gitignore"), ignore.as_bytes())); }, VersionControl::Hg => { - try!(HgRepo::init(path, config.cwd())); - try!(paths::write(&path.join(".hgignore"), ignore.as_bytes())); + if !fs::metadata(&path.join(".hg")).is_ok() { + try!(HgRepo::init(path, config.cwd())); + } + try!(paths::append(&path.join(".hgignore"), ignore.as_bytes())); }, VersionControl::NoVcs => { - try!(fs::create_dir(path)); + try!(fs::create_dir_all(path)); }, }; @@ -139,6 +352,32 @@ fn mk(config: &Config, path: &Path, name: &str, (Some(name), None, _, None) | (None, None, name, None) => name, }; + + let mut cargotoml_path_specifier = String::new(); + + // Calculare what [lib] and [[bin]]s do we need to append to Cargo.toml + + for i in &opts.source_files { + if i.bin { + if i.relative_path != "src/main.rs" { + cargotoml_path_specifier.push_str(&format!(r#" +[[bin]] +name = "{}" +path = {} +"#, i.target_name, toml::Value::String(i.relative_path.clone()))); + } + } else { + if i.relative_path != "src/lib.rs" { + cargotoml_path_specifier.push_str(&format!(r#" +[lib] +name = "{}" +path = {} +"#, i.target_name, toml::Value::String(i.relative_path.clone()))); + } + } + } + + // Create Cargo.toml file with necessary [lib] and [[bin]] sections, if needed try!(paths::write(&path.join("Cargo.toml"), format!( r#"[package] @@ -147,25 +386,40 @@ version = "0.1.0" authors = [{}] [dependencies] -"#, name, toml::Value::String(author)).as_bytes())); +{}"#, name, toml::Value::String(author), cargotoml_path_specifier).as_bytes())); - try!(fs::create_dir(&path.join("src"))); - if opts.bin { - try!(paths::write(&path.join("src/main.rs"), b"\ + // Create all specified source files + // (with respective parent directories) + // if they are don't exist + + for i in &opts.source_files { + let path_of_source_file = path.join(i.relative_path.clone()); + + if let Some(src_dir) = path_of_source_file.parent() { + try!(fs::create_dir_all(src_dir)); + } + + let default_file_content : &[u8] = if i.bin { + b"\ fn main() { println!(\"Hello, world!\"); } -")); - } else { - try!(paths::write(&path.join("src/lib.rs"), b"\ +" + } else { + b"\ #[cfg(test)] mod test { #[test] fn it_works() { } } -")); +" + }; + + if !fs::metadata(&path_of_source_file).map(|x| x.is_file()).unwrap_or(false) { + return paths::write(&path_of_source_file, default_file_content) + } } Ok(()) diff --git a/src/cargo/ops/mod.rs b/src/cargo/ops/mod.rs index c8924833aa9..806d3921a6e 100644 --- a/src/cargo/ops/mod.rs +++ b/src/cargo/ops/mod.rs @@ -8,7 +8,7 @@ pub use self::cargo_rustc::{BuildOutput, BuildConfig, TargetConfig}; pub use self::cargo_rustc::{CommandType, CommandPrototype, ExecEngine, ProcessEngine}; pub use self::cargo_run::run; pub use self::cargo_install::{install, install_list, uninstall}; -pub use self::cargo_new::{new, NewOptions, VersionControl}; +pub use self::cargo_new::{new, init, NewOptions, VersionControl}; pub use self::cargo_doc::{doc, DocOptions}; pub use self::cargo_generate_lockfile::{generate_lockfile}; pub use self::cargo_generate_lockfile::{update_lockfile}; diff --git a/src/cargo/util/paths.rs b/src/cargo/util/paths.rs index 1f08ea6dd7d..2e9397df6c4 100644 --- a/src/cargo/util/paths.rs +++ b/src/cargo/util/paths.rs @@ -1,6 +1,7 @@ use std::env; use std::ffi::{OsStr, OsString}; use std::fs::File; +use std::fs::OpenOptions; use std::io::prelude::*; use std::path::{Path, PathBuf, Component}; @@ -87,6 +88,21 @@ pub fn write(path: &Path, contents: &[u8]) -> CargoResult<()> { }) } +pub fn append(path: &Path, contents: &[u8]) -> CargoResult<()> { + (|| -> CargoResult<()> { + let mut f = try!(OpenOptions::new() + .write(true) + .append(true) + .create(true) + .open(path)); + + try!(f.write_all(contents)); + Ok(()) + }).chain_error(|| { + internal(format!("failed to write `{}`", path.display())) + }) +} + #[cfg(unix)] pub fn path2bytes(path: &Path) -> CargoResult<&[u8]> { use std::os::unix::prelude::*; diff --git a/tests/test_cargo_init.rs b/tests/test_cargo_init.rs new file mode 100644 index 00000000000..f36ea06e9a9 --- /dev/null +++ b/tests/test_cargo_init.rs @@ -0,0 +1,315 @@ +use std::fs::{self, File}; +use std::io::prelude::*; +use std::env; +use tempdir::TempDir; +use support::{execs, paths, cargo_dir}; +use hamcrest::{assert_that, existing_file, existing_dir, is_not}; + +use cargo::util::{process, ProcessBuilder}; + +fn setup() { +} + +fn cargo_process(s: &str) -> ProcessBuilder { + let mut p = process(&cargo_dir().join("cargo")); + p.arg(s).cwd(&paths::root()).env("HOME", &paths::home()); + return p; +} + +test!(simple_lib { + assert_that(cargo_process("init").arg("--vcs").arg("none") + .env("USER", "foo"), + execs().with_status(0)); + + assert_that(&paths::root().join("Cargo.toml"), existing_file()); + assert_that(&paths::root().join("src/lib.rs"), existing_file()); + assert_that(&paths::root().join(".gitignore"), is_not(existing_file())); + + assert_that(cargo_process("build"), + execs().with_status(0)); +}); + +test!(simple_bin { + let path = paths::root().join("foo"); + fs::create_dir(&path).unwrap(); + assert_that(cargo_process("init").arg("--bin").arg("--vcs").arg("none") + .env("USER", "foo").cwd(&path), + execs().with_status(0)); + + assert_that(&paths::root().join("foo/Cargo.toml"), existing_file()); + assert_that(&paths::root().join("foo/src/main.rs"), existing_file()); + + assert_that(cargo_process("build").cwd(&path), + execs().with_status(0)); + assert_that(&paths::root().join(&format!("foo/target/debug/foo{}", + env::consts::EXE_SUFFIX)), + existing_file()); +}); + +fn bin_already_exists(explicit: bool, rellocation: &str) { + let path = paths::root().join("foo"); + fs::create_dir_all(&path.join("src")).unwrap(); + + let sourcefile_path = path.join(rellocation); + + let content = br#" + fn main() { + println!("Hello, world 2!"); + } + "#; + + File::create(&sourcefile_path).unwrap().write_all(content).unwrap(); + + if explicit { + assert_that(cargo_process("init").arg("--bin").arg("--vcs").arg("none") + .env("USER", "foo").cwd(&path), + execs().with_status(0)); + } else { + assert_that(cargo_process("init").arg("--vcs").arg("none") + .env("USER", "foo").cwd(&path), + execs().with_status(0)); + } + + assert_that(&paths::root().join("foo/Cargo.toml"), existing_file()); + assert_that(&paths::root().join("foo/src/lib.rs"), is_not(existing_file())); + + // Check that our file is not overwritten + let mut new_content = Vec::new(); + File::open(&sourcefile_path).unwrap().read_to_end(&mut new_content).unwrap(); + assert_eq!(Vec::from(content as &[u8]), new_content); +} + +test!(bin_already_exists_explicit { + bin_already_exists(true, "src/main.rs") +}); + +test!(bin_already_exists_implicit { + bin_already_exists(false, "src/main.rs") +}); + +test!(bin_already_exists_explicit_nosrc { + bin_already_exists(true, "main.rs") +}); + +test!(bin_already_exists_implicit_nosrc { + bin_already_exists(false, "main.rs") +}); + +test!(bin_already_exists_implicit_namenosrc { + bin_already_exists(false, "foo.rs") +}); + +test!(bin_already_exists_implicit_namesrc { + bin_already_exists(false, "src/foo.rs") +}); + +test!(confused_by_multiple_lib_files { + let path = paths::root().join("foo"); + fs::create_dir_all(&path.join("src")).unwrap(); + + let sourcefile_path1 = path.join("src/lib.rs"); + + File::create(&sourcefile_path1).unwrap().write_all(br#" + fn qqq () { + println!("Hello, world 2!"); + } + "#).unwrap(); + + let sourcefile_path2 = path.join("lib.rs"); + + File::create(&sourcefile_path2).unwrap().write_all(br#" + fn qqq () { + println!("Hello, world 3!"); + } + "#).unwrap(); + + assert_that(cargo_process("init").arg("--vcs").arg("none") + .env("USER", "foo").cwd(&path), + execs().with_status(101).with_stderr("\ +cannot have a project with multiple libraries, found both `src/lib.rs` and `lib.rs` +")); + + assert_that(&paths::root().join("foo/Cargo.toml"), is_not(existing_file())); +}); + + +test!(multibin_project_name_clash { + let path = paths::root().join("foo"); + fs::create_dir(&path).unwrap(); + + let sourcefile_path1 = path.join("foo.rs"); + + File::create(&sourcefile_path1).unwrap().write_all(br#" + fn main () { + println!("Hello, world 2!"); + } + "#).unwrap(); + + let sourcefile_path2 = path.join("main.rs"); + + File::create(&sourcefile_path2).unwrap().write_all(br#" + fn main () { + println!("Hello, world 3!"); + } + "#).unwrap(); + + assert_that(cargo_process("init").arg("--vcs").arg("none") + .env("USER", "foo").cwd(&path), + execs().with_status(101).with_stderr("\ +multiple possible binary sources found: + main.rs + foo.rs +cannot automatically generate Cargo.toml as the main target would be ambiguous +")); + + assert_that(&paths::root().join("foo/Cargo.toml"), is_not(existing_file())); +}); + +fn lib_already_exists(rellocation: &str) { + let path = paths::root().join("foo"); + fs::create_dir_all(&path.join("src")).unwrap(); + + let sourcefile_path = path.join(rellocation); + + let content = br#" + pub fn qqq() {} + "#; + + File::create(&sourcefile_path).unwrap().write_all(content).unwrap(); + + assert_that(cargo_process("init").arg("--vcs").arg("none") + .env("USER", "foo").cwd(&path), + execs().with_status(0)); + + assert_that(&paths::root().join("foo/Cargo.toml"), existing_file()); + assert_that(&paths::root().join("foo/src/main.rs"), is_not(existing_file())); + + // Check that our file is not overwritten + let mut new_content = Vec::new(); + File::open(&sourcefile_path).unwrap().read_to_end(&mut new_content).unwrap(); + assert_eq!(Vec::from(content as &[u8]), new_content); +} + +test!(lib_already_exists_src { + lib_already_exists("src/lib.rs") +}); + +test!(lib_already_exists_nosrc { + lib_already_exists("lib.rs") +}); + +test!(simple_git { + assert_that(cargo_process("init").arg("--vcs").arg("git") + .env("USER", "foo"), + execs().with_status(0)); + + assert_that(&paths::root().join("Cargo.toml"), existing_file()); + assert_that(&paths::root().join("src/lib.rs"), existing_file()); + assert_that(&paths::root().join(".git"), existing_dir()); + assert_that(&paths::root().join(".gitignore"), existing_file()); +}); + +test!(auto_git { + let td = TempDir::new("cargo").unwrap(); + let foo = &td.path().join("foo"); + fs::create_dir_all(&foo).unwrap(); + assert_that(cargo_process("init").cwd(foo.clone()) + .env("USER", "foo"), + execs().with_status(0)); + + assert_that(&foo.join("Cargo.toml"), existing_file()); + assert_that(&foo.join("src/lib.rs"), existing_file()); + assert_that(&foo.join(".git"), existing_dir()); + assert_that(&foo.join(".gitignore"), existing_file()); +}); + +test!(invalid_dir_name { + let foo = &paths::root().join("foo.bar"); + fs::create_dir_all(&foo).unwrap(); + assert_that(cargo_process("init").cwd(foo.clone()) + .env("USER", "foo"), + execs().with_status(101).with_stderr("\ +Invalid character `.` in crate name: `foo.bar` +use --name to override crate name +")); + + assert_that(&foo.join("Cargo.toml"), is_not(existing_file())); +}); + +test!(git_autodetect { + fs::create_dir(&paths::root().join(".git")).unwrap(); + + assert_that(cargo_process("init") + .env("USER", "foo"), + execs().with_status(0)); + + + assert_that(&paths::root().join("Cargo.toml"), existing_file()); + assert_that(&paths::root().join("src/lib.rs"), existing_file()); + assert_that(&paths::root().join(".git"), existing_dir()); + assert_that(&paths::root().join(".gitignore"), existing_file()); +}); + + +test!(mercurial_autodetect { + fs::create_dir(&paths::root().join(".hg")).unwrap(); + + assert_that(cargo_process("init") + .env("USER", "foo"), + execs().with_status(0)); + + + assert_that(&paths::root().join("Cargo.toml"), existing_file()); + assert_that(&paths::root().join("src/lib.rs"), existing_file()); + assert_that(&paths::root().join(".git"), is_not(existing_dir())); + assert_that(&paths::root().join(".hgignore"), existing_file()); +}); + +test!(gitignore_appended_not_replaced { + fs::create_dir(&paths::root().join(".git")).unwrap(); + + File::create(&paths::root().join(".gitignore")).unwrap().write_all(b"qqqqqq\n").unwrap(); + + assert_that(cargo_process("init") + .env("USER", "foo"), + execs().with_status(0)); + + + assert_that(&paths::root().join("Cargo.toml"), existing_file()); + assert_that(&paths::root().join("src/lib.rs"), existing_file()); + assert_that(&paths::root().join(".git"), existing_dir()); + assert_that(&paths::root().join(".gitignore"), existing_file()); + + let mut contents = String::new(); + File::open(&paths::root().join(".gitignore")).unwrap().read_to_string(&mut contents).unwrap(); + assert!(contents.contains(r#"qqqqqq"#)); +}); + +test!(with_argument { + assert_that(cargo_process("init").arg("foo").arg("--vcs").arg("none") + .env("USER", "foo"), + execs().with_status(0)); + assert_that(&paths::root().join("foo/Cargo.toml"), existing_file()); +}); + + +test!(unknown_flags { + assert_that(cargo_process("init").arg("foo").arg("--flag"), + execs().with_status(1) + .with_stderr("\ +Unknown flag: '--flag' + +Usage: + cargo init [options] [] + cargo init -h | --help +")); +}); + +#[cfg(not(windows))] +test!(no_filename { + assert_that(cargo_process("init").arg("/"), + execs().with_status(101) + .with_stderr("\ +cannot auto-detect project name from path \"/\" ; use --name to override +")); +}); diff --git a/tests/test_cargo_new.rs b/tests/test_cargo_new.rs index 8c61d05ae27..63868112fcd 100644 --- a/tests/test_cargo_new.rs +++ b/tests/test_cargo_new.rs @@ -94,7 +94,9 @@ test!(existing { test!(invalid_characters { assert_that(cargo_process("new").arg("foo.rs"), execs().with_status(101) - .with_stderr("Invalid character `.` in crate name: `foo.rs`")); + .with_stderr("\ +Invalid character `.` in crate name: `foo.rs` +use --name to override crate name")); }); test!(rust_prefix_stripped { diff --git a/tests/tests.rs b/tests/tests.rs index 761fee61bb0..a435f91b860 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -49,6 +49,7 @@ mod test_cargo_features; mod test_cargo_fetch; mod test_cargo_freshness; mod test_cargo_generate_lockfile; +mod test_cargo_init; mod test_cargo_install; mod test_cargo_new; mod test_cargo_package;