diff options
Diffstat (limited to 'tests/testsuite/package.rs')
-rw-r--r-- | tests/testsuite/package.rs | 2764 |
1 files changed, 2764 insertions, 0 deletions
diff --git a/tests/testsuite/package.rs b/tests/testsuite/package.rs new file mode 100644 index 0000000..14bac66 --- /dev/null +++ b/tests/testsuite/package.rs @@ -0,0 +1,2764 @@ +//! Tests for the `cargo package` command. + +use cargo_test_support::paths::CargoPathExt; +use cargo_test_support::publish::validate_crate_contents; +use cargo_test_support::registry::{self, Package}; +use cargo_test_support::{ + basic_manifest, cargo_process, git, path2url, paths, project, symlink_supported, t, +}; +use flate2::read::GzDecoder; +use std::fs::{self, read_to_string, File}; +use std::path::Path; +use tar::Archive; + +#[cargo_test] +fn simple() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + exclude = ["*.txt"] + license = "MIT" + description = "foo" + "#, + ) + .file("src/main.rs", r#"fn main() { println!("hello"); }"#) + .file("src/bar.txt", "") // should be ignored when packaging + .build(); + + p.cargo("package") + .with_stderr( + "\ +[WARNING] manifest has no documentation[..] +See [..] +[PACKAGING] foo v0.0.1 ([CWD]) +[VERIFYING] foo v0.0.1 ([CWD]) +[COMPILING] foo v0.0.1 ([CWD][..]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[PACKAGED] 4 files, [..] ([..] compressed) +", + ) + .run(); + assert!(p.root().join("target/package/foo-0.0.1.crate").is_file()); + p.cargo("package -l") + .with_stdout( + "\ +Cargo.lock +Cargo.toml +Cargo.toml.orig +src/main.rs +", + ) + .run(); + p.cargo("package").with_stdout("").run(); + + let f = File::open(&p.root().join("target/package/foo-0.0.1.crate")).unwrap(); + validate_crate_contents( + f, + "foo-0.0.1.crate", + &["Cargo.lock", "Cargo.toml", "Cargo.toml.orig", "src/main.rs"], + &[], + ); +} + +#[cargo_test] +fn metadata_warning() { + let p = project().file("src/main.rs", "fn main() {}").build(); + p.cargo("package") + .with_stderr( + "\ +warning: manifest has no description, license, license-file, documentation, \ +homepage or repository. +See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info. +[PACKAGING] foo v0.0.1 ([CWD]) +[VERIFYING] foo v0.0.1 ([CWD]) +[COMPILING] foo v0.0.1 ([CWD][..]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[PACKAGED] [..] files, [..] ([..] compressed) +", + ) + .run(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + p.cargo("package") + .with_stderr( + "\ +warning: manifest has no description, documentation, homepage or repository. +See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info. +[PACKAGING] foo v0.0.1 ([CWD]) +[VERIFYING] foo v0.0.1 ([CWD]) +[COMPILING] foo v0.0.1 ([CWD][..]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[PACKAGED] [..] files, [..] ([..] compressed) +", + ) + .run(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + repository = "bar" + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + p.cargo("package") + .with_stderr( + "\ +[PACKAGING] foo v0.0.1 ([CWD]) +[VERIFYING] foo v0.0.1 ([CWD]) +[COMPILING] foo v0.0.1 ([CWD][..]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[PACKAGED] [..] files, [..] ([..] compressed) +", + ) + .run(); +} + +#[cargo_test] +fn package_verbose() { + let root = paths::root().join("all"); + let repo = git::repo(&root) + .file("Cargo.toml", &basic_manifest("foo", "0.0.1")) + .file("src/main.rs", "fn main() {}") + .file("a/a/Cargo.toml", &basic_manifest("a", "0.0.1")) + .file("a/a/src/lib.rs", "") + .build(); + cargo_process("build").cwd(repo.root()).run(); + + println!("package main repo"); + cargo_process("package -v --no-verify") + .cwd(repo.root()) + .with_stderr( + "\ +[WARNING] manifest has no description[..] +See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info. +[PACKAGING] foo v0.0.1 ([..]) +[ARCHIVING] .cargo_vcs_info.json +[ARCHIVING] Cargo.lock +[ARCHIVING] Cargo.toml +[ARCHIVING] Cargo.toml.orig +[ARCHIVING] src/main.rs +[PACKAGED] 5 files, [..] ([..] compressed) +", + ) + .run(); + + let f = File::open(&repo.root().join("target/package/foo-0.0.1.crate")).unwrap(); + let vcs_contents = format!( + r#"{{ + "git": {{ + "sha1": "{}" + }}, + "path_in_vcs": "" +}} +"#, + repo.revparse_head() + ); + validate_crate_contents( + f, + "foo-0.0.1.crate", + &[ + "Cargo.lock", + "Cargo.toml", + "Cargo.toml.orig", + "src/main.rs", + ".cargo_vcs_info.json", + ], + &[(".cargo_vcs_info.json", &vcs_contents)], + ); + + println!("package sub-repo"); + cargo_process("package -v --no-verify") + .cwd(repo.root().join("a/a")) + .with_stderr( + "\ +[WARNING] manifest has no description[..] +See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info. +[PACKAGING] a v0.0.1 ([..]) +[ARCHIVING] .cargo_vcs_info.json +[ARCHIVING] Cargo.toml +[ARCHIVING] Cargo.toml.orig +[ARCHIVING] src/lib.rs +[PACKAGED] 4 files, [..] ([..] compressed) +", + ) + .run(); + + let f = File::open(&repo.root().join("a/a/target/package/a-0.0.1.crate")).unwrap(); + let vcs_contents = format!( + r#"{{ + "git": {{ + "sha1": "{}" + }}, + "path_in_vcs": "a/a" +}} +"#, + repo.revparse_head() + ); + validate_crate_contents( + f, + "a-0.0.1.crate", + &[ + "Cargo.toml", + "Cargo.toml.orig", + "src/lib.rs", + ".cargo_vcs_info.json", + ], + &[(".cargo_vcs_info.json", &vcs_contents)], + ); +} + +#[cargo_test] +fn package_verification() { + let p = project().file("src/main.rs", "fn main() {}").build(); + p.cargo("build").run(); + p.cargo("package") + .with_stderr( + "\ +[WARNING] manifest has no description[..] +See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info. +[PACKAGING] foo v0.0.1 ([CWD]) +[VERIFYING] foo v0.0.1 ([CWD]) +[COMPILING] foo v0.0.1 ([CWD][..]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[PACKAGED] [..] files, [..] ([..] compressed) +", + ) + .run(); +} + +#[cargo_test] +fn vcs_file_collision() { + let p = project().build(); + let _ = git::repo(&paths::root().join("foo")) + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + description = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + documentation = "foo" + homepage = "foo" + repository = "foo" + exclude = ["*.no-existe"] + "#, + ) + .file( + "src/main.rs", + r#" + fn main() {} + "#, + ) + .file(".cargo_vcs_info.json", "foo") + .build(); + p.cargo("package") + .arg("--no-verify") + .with_status(101) + .with_stderr( + "\ +[ERROR] invalid inclusion of reserved file name .cargo_vcs_info.json \ +in package source +", + ) + .run(); +} + +#[cargo_test] +fn orig_file_collision() { + let p = project().build(); + let _ = git::repo(&paths::root().join("foo")) + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + description = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + documentation = "foo" + homepage = "foo" + repository = "foo" + exclude = ["*.no-existe"] + "#, + ) + .file( + "src/main.rs", + r#" + fn main() {} + "#, + ) + .file("Cargo.toml.orig", "oops") + .build(); + p.cargo("package") + .arg("--no-verify") + .with_status(101) + .with_stderr( + "\ +[ERROR] invalid inclusion of reserved file name Cargo.toml.orig \ +in package source +", + ) + .run(); +} + +#[cargo_test] +fn path_dependency_no_version() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + + [dependencies.bar] + path = "bar" + "#, + ) + .file("src/main.rs", "fn main() {}") + .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) + .file("bar/src/lib.rs", "") + .build(); + + p.cargo("package") + .with_status(101) + .with_stderr( + "\ +[WARNING] manifest has no documentation, homepage or repository. +See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info. +[ERROR] all dependencies must have a version specified when packaging. +dependency `bar` does not specify a version\n\ +Note: The packaged dependency will use the version from crates.io, +the `path` specification will be removed from the dependency declaration. +", + ) + .run(); +} + +#[cargo_test] +fn git_dependency_no_version() { + registry::init(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + + [dependencies.foo] + git = "git://path/to/nowhere" + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("package") + .with_status(101) + .with_stderr( + "\ +[WARNING] manifest has no documentation, homepage or repository. +See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info. +[ERROR] all dependencies must have a version specified when packaging. +dependency `foo` does not specify a version +Note: The packaged dependency will use the version from crates.io, +the `git` specification will be removed from the dependency declaration. +", + ) + .run(); +} + +#[cargo_test] +fn exclude() { + let root = paths::root().join("exclude"); + let repo = git::repo(&root) + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + exclude = [ + "*.txt", + # file in root + "file_root_1", # NO_CHANGE (ignored) + "/file_root_2", # CHANGING (packaged -> ignored) + "file_root_3/", # NO_CHANGE (packaged) + "file_root_4/*", # NO_CHANGE (packaged) + "file_root_5/**", # NO_CHANGE (packaged) + # file in sub-dir + "file_deep_1", # CHANGING (packaged -> ignored) + "/file_deep_2", # NO_CHANGE (packaged) + "file_deep_3/", # NO_CHANGE (packaged) + "file_deep_4/*", # NO_CHANGE (packaged) + "file_deep_5/**", # NO_CHANGE (packaged) + # dir in root + "dir_root_1", # CHANGING (packaged -> ignored) + "/dir_root_2", # CHANGING (packaged -> ignored) + "dir_root_3/", # CHANGING (packaged -> ignored) + "dir_root_4/*", # NO_CHANGE (ignored) + "dir_root_5/**", # NO_CHANGE (ignored) + # dir in sub-dir + "dir_deep_1", # CHANGING (packaged -> ignored) + "/dir_deep_2", # NO_CHANGE + "dir_deep_3/", # CHANGING (packaged -> ignored) + "dir_deep_4/*", # CHANGING (packaged -> ignored) + "dir_deep_5/**", # CHANGING (packaged -> ignored) + ] + "#, + ) + .file("src/main.rs", r#"fn main() { println!("hello"); }"#) + .file("bar.txt", "") + .file("src/bar.txt", "") + // File in root. + .file("file_root_1", "") + .file("file_root_2", "") + .file("file_root_3", "") + .file("file_root_4", "") + .file("file_root_5", "") + // File in sub-dir. + .file("some_dir/file_deep_1", "") + .file("some_dir/file_deep_2", "") + .file("some_dir/file_deep_3", "") + .file("some_dir/file_deep_4", "") + .file("some_dir/file_deep_5", "") + // Dir in root. + .file("dir_root_1/some_dir/file", "") + .file("dir_root_2/some_dir/file", "") + .file("dir_root_3/some_dir/file", "") + .file("dir_root_4/some_dir/file", "") + .file("dir_root_5/some_dir/file", "") + // Dir in sub-dir. + .file("some_dir/dir_deep_1/some_dir/file", "") + .file("some_dir/dir_deep_2/some_dir/file", "") + .file("some_dir/dir_deep_3/some_dir/file", "") + .file("some_dir/dir_deep_4/some_dir/file", "") + .file("some_dir/dir_deep_5/some_dir/file", "") + .build(); + + cargo_process("package --no-verify -v") + .cwd(repo.root()) + .with_stdout("") + .with_stderr( + "\ +[WARNING] manifest has no description[..] +See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info. +[PACKAGING] foo v0.0.1 ([..]) +[ARCHIVING] .cargo_vcs_info.json +[ARCHIVING] Cargo.lock +[ARCHIVING] Cargo.toml +[ARCHIVING] Cargo.toml.orig +[ARCHIVING] file_root_3 +[ARCHIVING] file_root_4 +[ARCHIVING] file_root_5 +[ARCHIVING] some_dir/dir_deep_2/some_dir/file +[ARCHIVING] some_dir/dir_deep_4/some_dir/file +[ARCHIVING] some_dir/dir_deep_5/some_dir/file +[ARCHIVING] some_dir/file_deep_2 +[ARCHIVING] some_dir/file_deep_3 +[ARCHIVING] some_dir/file_deep_4 +[ARCHIVING] some_dir/file_deep_5 +[ARCHIVING] src/main.rs +[PACKAGED] 15 files, [..] ([..] compressed) +", + ) + .run(); + + assert!(repo.root().join("target/package/foo-0.0.1.crate").is_file()); + + cargo_process("package -l") + .cwd(repo.root()) + .with_stdout( + "\ +.cargo_vcs_info.json +Cargo.lock +Cargo.toml +Cargo.toml.orig +file_root_3 +file_root_4 +file_root_5 +some_dir/dir_deep_2/some_dir/file +some_dir/dir_deep_4/some_dir/file +some_dir/dir_deep_5/some_dir/file +some_dir/file_deep_2 +some_dir/file_deep_3 +some_dir/file_deep_4 +some_dir/file_deep_5 +src/main.rs +", + ) + .run(); +} + +#[cargo_test] +fn include() { + let root = paths::root().join("include"); + let repo = git::repo(&root) + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + exclude = ["*.txt"] + include = ["foo.txt", "**/*.rs", "Cargo.toml", ".dotfile"] + "#, + ) + .file("foo.txt", "") + .file("src/main.rs", r#"fn main() { println!("hello"); }"#) + .file(".dotfile", "") + // Should be ignored when packaging. + .file("src/bar.txt", "") + .build(); + + cargo_process("package --no-verify -v") + .cwd(repo.root()) + .with_stderr( + "\ +[WARNING] manifest has no description[..] +See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info. +[WARNING] both package.include and package.exclude are specified; the exclude list will be ignored +[PACKAGING] foo v0.0.1 ([..]) +[ARCHIVING] .cargo_vcs_info.json +[ARCHIVING] .dotfile +[ARCHIVING] Cargo.lock +[ARCHIVING] Cargo.toml +[ARCHIVING] Cargo.toml.orig +[ARCHIVING] foo.txt +[ARCHIVING] src/main.rs +[PACKAGED] 7 files, [..] ([..] compressed) +", + ) + .run(); +} + +#[cargo_test] +fn package_lib_with_bin() { + let p = project() + .file("src/main.rs", "extern crate foo; fn main() {}") + .file("src/lib.rs", "") + .build(); + + p.cargo("package -v").run(); +} + +#[cargo_test] +fn package_git_submodule() { + let project = git::new("foo", |project| { + project + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = ["foo@example.com"] + license = "MIT" + description = "foo" + repository = "foo" + "#, + ) + .file("src/lib.rs", "pub fn foo() {}") + }); + let library = git::new("bar", |library| { + library.no_manifest().file("Makefile", "all:") + }); + + let repository = git2::Repository::open(&project.root()).unwrap(); + let url = path2url(library.root()).to_string(); + git::add_submodule(&repository, &url, Path::new("bar")); + git::commit(&repository); + + let repository = git2::Repository::open(&project.root().join("bar")).unwrap(); + repository + .reset( + &repository.revparse_single("HEAD").unwrap(), + git2::ResetType::Hard, + None, + ) + .unwrap(); + + project + .cargo("package --no-verify -v") + .with_stderr_contains("[ARCHIVING] bar/Makefile") + .run(); +} + +#[cargo_test] +/// Tests if a symlink to a git submodule is properly handled. +/// +/// This test requires you to be able to make symlinks. +/// For windows, this may require you to enable developer mode. +fn package_symlink_to_submodule() { + #[cfg(unix)] + use std::os::unix::fs::symlink; + #[cfg(windows)] + use std::os::windows::fs::symlink_dir as symlink; + + if !symlink_supported() { + return; + } + + let project = git::new("foo", |project| { + project.file("src/lib.rs", "pub fn foo() {}") + }); + + let library = git::new("submodule", |library| { + library.no_manifest().file("Makefile", "all:") + }); + + let repository = git2::Repository::open(&project.root()).unwrap(); + let url = path2url(library.root()).to_string(); + git::add_submodule(&repository, &url, Path::new("submodule")); + t!(symlink( + &project.root().join("submodule"), + &project.root().join("submodule-link") + )); + git::add(&repository); + git::commit(&repository); + + let repository = git2::Repository::open(&project.root().join("submodule")).unwrap(); + repository + .reset( + &repository.revparse_single("HEAD").unwrap(), + git2::ResetType::Hard, + None, + ) + .unwrap(); + + project + .cargo("package --no-verify -v") + .with_stderr_contains("[ARCHIVING] submodule/Makefile") + .run(); +} + +#[cargo_test] +fn no_duplicates_from_modified_tracked_files() { + let p = git::new("all", |p| p.file("src/main.rs", "fn main() {}")); + p.change_file("src/main.rs", r#"fn main() { println!("A change!"); }"#); + p.cargo("build").run(); + p.cargo("package --list --allow-dirty") + .with_stdout( + "\ +Cargo.lock +Cargo.toml +Cargo.toml.orig +src/main.rs +", + ) + .run(); +} + +#[cargo_test] +fn ignore_nested() { + let cargo_toml = r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + "#; + let main_rs = r#" + fn main() { println!("hello"); } + "#; + let p = project() + .file("Cargo.toml", cargo_toml) + .file("src/main.rs", main_rs) + // If a project happens to contain a copy of itself, we should + // ignore it. + .file("a_dir/foo/Cargo.toml", cargo_toml) + .file("a_dir/foo/src/main.rs", main_rs) + .build(); + + p.cargo("package") + .with_stderr( + "\ +[WARNING] manifest has no documentation[..] +See https://doc.rust-lang.org/cargo/reference/manifest.html#package-metadata for more info. +[PACKAGING] foo v0.0.1 ([CWD]) +[VERIFYING] foo v0.0.1 ([CWD]) +[COMPILING] foo v0.0.1 ([CWD][..]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[PACKAGED] 4 files, [..] ([..] compressed) +", + ) + .run(); + assert!(p.root().join("target/package/foo-0.0.1.crate").is_file()); + p.cargo("package -l") + .with_stdout( + "\ +Cargo.lock +Cargo.toml +Cargo.toml.orig +src/main.rs +", + ) + .run(); + p.cargo("package").with_stdout("").run(); + + let f = File::open(&p.root().join("target/package/foo-0.0.1.crate")).unwrap(); + validate_crate_contents( + f, + "foo-0.0.1.crate", + &["Cargo.lock", "Cargo.toml", "Cargo.toml.orig", "src/main.rs"], + &[], + ); +} + +// Windows doesn't allow these characters in filenames. +#[cfg(unix)] +#[cargo_test] +fn package_weird_characters() { + let p = project() + .file("src/main.rs", r#"fn main() { println!("hello"); }"#) + .file("src/:foo", "") + .build(); + + p.cargo("package") + .with_status(101) + .with_stderr( + "\ +warning: [..] +See [..] +[ERROR] cannot package a filename with a special character `:`: src/:foo +", + ) + .run(); +} + +#[cargo_test] +fn repackage_on_source_change() { + let p = project() + .file("src/main.rs", r#"fn main() { println!("hello"); }"#) + .build(); + + p.cargo("package").run(); + + // Add another source file + p.change_file("src/foo.rs", r#"fn main() { println!("foo"); }"#); + + // Check that cargo rebuilds the tarball + p.cargo("package") + .with_stderr( + "\ +[WARNING] [..] +See [..] +[PACKAGING] foo v0.0.1 ([CWD]) +[VERIFYING] foo v0.0.1 ([CWD]) +[COMPILING] foo v0.0.1 ([CWD][..]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[PACKAGED] 5 files, [..] ([..] compressed) +", + ) + .run(); + + // Check that the tarball contains the added file + let f = File::open(&p.root().join("target/package/foo-0.0.1.crate")).unwrap(); + validate_crate_contents( + f, + "foo-0.0.1.crate", + &[ + "Cargo.lock", + "Cargo.toml", + "Cargo.toml.orig", + "src/main.rs", + "src/foo.rs", + ], + &[], + ); +} + +#[cargo_test] +/// Tests if a broken symlink is properly handled when packaging. +/// +/// This test requires you to be able to make symlinks. +/// For windows, this may require you to enable developer mode. +fn broken_symlink() { + #[cfg(unix)] + use std::os::unix::fs::symlink; + #[cfg(windows)] + use std::os::windows::fs::symlink_dir as symlink; + + if !symlink_supported() { + return; + } + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = 'foo' + documentation = 'foo' + homepage = 'foo' + repository = 'foo' + "#, + ) + .file("src/main.rs", r#"fn main() { println!("hello"); }"#) + .build(); + t!(symlink("nowhere", &p.root().join("src/foo.rs"))); + + p.cargo("package -v") + .with_status(101) + .with_stderr_contains( + "\ +[ERROR] failed to prepare local package for uploading + +Caused by: + failed to open for archiving: `[..]foo.rs` + +Caused by: + [..] +", + ) + .run(); +} + +#[cargo_test] +/// Tests if a broken but excluded symlink is ignored. +/// See issue rust-lang/cargo#10917 +/// +/// This test requires you to be able to make symlinks. +/// For windows, this may require you to enable developer mode. +fn broken_but_excluded_symlink() { + #[cfg(unix)] + use std::os::unix::fs::symlink; + #[cfg(windows)] + use std::os::windows::fs::symlink_dir as symlink; + + if !symlink_supported() { + return; + } + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = 'foo' + documentation = 'foo' + homepage = 'foo' + repository = 'foo' + exclude = ["src/foo.rs"] + "#, + ) + .file("src/main.rs", r#"fn main() { println!("hello"); }"#) + .build(); + t!(symlink("nowhere", &p.root().join("src/foo.rs"))); + + p.cargo("package -v --list") + // `src/foo.rs` is excluded. + .with_stdout( + "\ +Cargo.lock +Cargo.toml +Cargo.toml.orig +src/main.rs +", + ) + .run(); +} + +#[cargo_test] +#[cfg(not(windows))] // https://github.com/libgit2/libgit2/issues/6250 +/// Test that /dir and /dir/ matches symlinks to directories. +fn gitignore_symlink_dir() { + if !symlink_supported() { + return; + } + + let (p, _repo) = git::new_repo("foo", |p| { + p.file("src/main.rs", r#"fn main() { println!("hello"); }"#) + .symlink_dir("src", "src1") + .symlink_dir("src", "src2") + .symlink_dir("src", "src3") + .symlink_dir("src", "src4") + .file(".gitignore", "/src1\n/src2/\nsrc3\nsrc4/") + }); + + p.cargo("package -l --no-metadata") + .with_stderr("") + .with_stdout( + "\ +.cargo_vcs_info.json +.gitignore +Cargo.lock +Cargo.toml +Cargo.toml.orig +src/main.rs +", + ) + .run(); +} + +#[cargo_test] +#[cfg(not(windows))] // https://github.com/libgit2/libgit2/issues/6250 +/// Test that /dir and /dir/ matches symlinks to directories in dirty working directory. +fn gitignore_symlink_dir_dirty() { + if !symlink_supported() { + return; + } + + let (p, _repo) = git::new_repo("foo", |p| { + p.file("src/main.rs", r#"fn main() { println!("hello"); }"#) + .file(".gitignore", "/src1\n/src2/\nsrc3\nsrc4/") + }); + + p.symlink("src", "src1"); + p.symlink("src", "src2"); + p.symlink("src", "src3"); + p.symlink("src", "src4"); + + p.cargo("package -l --no-metadata") + .with_stderr("") + .with_stdout( + "\ +.cargo_vcs_info.json +.gitignore +Cargo.lock +Cargo.toml +Cargo.toml.orig +src/main.rs +", + ) + .run(); + + p.cargo("package -l --no-metadata --allow-dirty") + .with_stderr("") + .with_stdout( + "\ +.gitignore +Cargo.lock +Cargo.toml +Cargo.toml.orig +src/main.rs +", + ) + .run(); +} + +#[cargo_test] +/// Tests if a symlink to a directory is properly included. +/// +/// This test requires you to be able to make symlinks. +/// For windows, this may require you to enable developer mode. +fn package_symlink_to_dir() { + if !symlink_supported() { + return; + } + + project() + .file("src/main.rs", r#"fn main() { println!("hello"); }"#) + .file("bla/Makefile", "all:") + .symlink_dir("bla", "foo") + .build() + .cargo("package -v") + .with_stderr_contains("[ARCHIVING] foo/Makefile") + .run(); +} + +#[cargo_test] +/// Tests if a symlink to ancestor causes filesystem loop error. +/// +/// This test requires you to be able to make symlinks. +/// For windows, this may require you to enable developer mode. +fn filesystem_loop() { + if !symlink_supported() { + return; + } + + project() + .file("src/main.rs", r#"fn main() { println!("hello"); }"#) + .symlink_dir("a/b", "a/b/c/d/foo") + .build() + .cargo("package -v") + .with_stderr_contains( + "[WARNING] File system loop found: [..]/a/b/c/d/foo points to an ancestor [..]/a/b", + ) + .run(); +} + +#[cargo_test] +fn do_not_package_if_repository_is_dirty() { + let p = project().build(); + + // Create a Git repository containing a minimal Rust project. + let _ = git::repo(&paths::root().join("foo")) + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + license = "MIT" + description = "foo" + documentation = "foo" + homepage = "foo" + repository = "foo" + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + // Modify Cargo.toml without committing the change. + p.change_file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + license = "MIT" + description = "foo" + documentation = "foo" + homepage = "foo" + repository = "foo" + # change + "#, + ); + + p.cargo("package") + .with_status(101) + .with_stderr( + "\ +error: 1 files in the working directory contain changes that were not yet \ +committed into git: + +Cargo.toml + +to proceed despite this and include the uncommitted changes, pass the `--allow-dirty` flag +", + ) + .run(); +} + +#[cargo_test] +fn dirty_ignored() { + // Cargo warns about an ignored file that will be published. + let (p, repo) = git::new_repo("foo", |p| { + p.file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + description = "foo" + license = "foo" + documentation = "foo" + include = ["src", "build"] + "#, + ) + .file("src/lib.rs", "") + .file(".gitignore", "build") + }); + // Example of adding a file that is confusingly ignored by an overzealous + // gitignore rule. + p.change_file("src/build/mod.rs", ""); + p.cargo("package --list") + .with_status(101) + .with_stderr( + "\ +error: 1 files in the working directory contain changes that were not yet committed into git: + +src/build/mod.rs + +to proceed despite this and include the uncommitted changes, pass the `--allow-dirty` flag +", + ) + .run(); + // Add the ignored file and make sure it is included. + let mut index = t!(repo.index()); + t!(index.add_path(Path::new("src/build/mod.rs"))); + t!(index.write()); + git::commit(&repo); + p.cargo("package --list") + .with_stderr("") + .with_stdout( + "\ +.cargo_vcs_info.json +Cargo.toml +Cargo.toml.orig +src/build/mod.rs +src/lib.rs +", + ) + .run(); +} + +#[cargo_test] +fn generated_manifest() { + let registry = registry::alt_init(); + Package::new("abc", "1.0.0").publish(); + Package::new("def", "1.0.0").alternative(true).publish(); + Package::new("ghi", "1.0.0").publish(); + Package::new("bar", "0.1.0").publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + exclude = ["*.txt"] + license = "MIT" + description = "foo" + + [package.metadata] + foo = 'bar' + + [workspace] + + [dependencies] + bar = { path = "bar", version = "0.1" } + def = { version = "1.0", registry = "alternative" } + ghi = "1.0" + abc = "1.0" + "#, + ) + .file("src/main.rs", "") + .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) + .file("bar/src/lib.rs", "") + .build(); + + p.cargo("package --no-verify").run(); + + let f = File::open(&p.root().join("target/package/foo-0.0.1.crate")).unwrap(); + let rewritten_toml = format!( + r#"{} +[package] +name = "foo" +version = "0.0.1" +authors = [] +exclude = ["*.txt"] +description = "foo" +license = "MIT" + +[package.metadata] +foo = "bar" + +[dependencies.abc] +version = "1.0" + +[dependencies.bar] +version = "0.1" + +[dependencies.def] +version = "1.0" +registry-index = "{}" + +[dependencies.ghi] +version = "1.0" +"#, + cargo::core::package::MANIFEST_PREAMBLE, + registry.index_url() + ); + + validate_crate_contents( + f, + "foo-0.0.1.crate", + &["Cargo.lock", "Cargo.toml", "Cargo.toml.orig", "src/main.rs"], + &[("Cargo.toml", &rewritten_toml)], + ); +} + +#[cargo_test] +fn ignore_workspace_specifier() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + + authors = [] + + [workspace] + + [dependencies] + bar = { path = "bar", version = "0.1" } + "#, + ) + .file("src/main.rs", "") + .file( + "bar/Cargo.toml", + r#" + [package] + name = "bar" + version = "0.1.0" + authors = [] + workspace = ".." + "#, + ) + .file("bar/src/lib.rs", "") + .build(); + + p.cargo("package --no-verify").cwd("bar").run(); + + let f = File::open(&p.root().join("target/package/bar-0.1.0.crate")).unwrap(); + let rewritten_toml = format!( + r#"{} +[package] +name = "bar" +version = "0.1.0" +authors = [] +"#, + cargo::core::package::MANIFEST_PREAMBLE + ); + validate_crate_contents( + f, + "bar-0.1.0.crate", + &["Cargo.toml", "Cargo.toml.orig", "src/lib.rs"], + &[("Cargo.toml", &rewritten_toml)], + ); +} + +#[cargo_test] +fn package_two_kinds_of_deps() { + Package::new("other", "1.0.0").publish(); + Package::new("other1", "1.0.0").publish(); + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + + [dependencies] + other = "1.0" + other1 = { version = "1.0" } + "#, + ) + .file("src/main.rs", "") + .build(); + + p.cargo("package --no-verify").run(); +} + +#[cargo_test] +fn test_edition() { + let p = project() + .file( + "Cargo.toml", + r#" + cargo-features = ["edition"] + [package] + name = "foo" + version = "0.0.1" + authors = [] + edition = "2018" + "#, + ) + .file("src/lib.rs", r#" "#) + .build(); + + p.cargo("check -v") + .with_stderr_contains( + "\ +[CHECKING] foo v0.0.1 ([..]) +[RUNNING] `rustc [..]--edition=2018 [..] +", + ) + .run(); +} + +#[cargo_test] +fn edition_with_metadata() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + edition = "2018" + + [package.metadata.docs.rs] + features = ["foobar"] + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("package").run(); +} + +#[cargo_test] +fn test_edition_malformed() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + edition = "chicken" + "#, + ) + .file("src/lib.rs", r#" "#) + .build(); + + p.cargo("check -v") + .with_status(101) + .with_stderr( + "\ +error: failed to parse manifest at `[..]` + +Caused by: + failed to parse the `edition` key + +Caused by: + supported edition values are `2015`, `2018`, or `2021`, but `chicken` is unknown +" + .to_string(), + ) + .run(); +} + +#[cargo_test] +fn test_edition_from_the_future() { + let p = project() + .file( + "Cargo.toml", + r#"[package] + edition = "2038" + name = "foo" + version = "99.99.99" + authors = [] + "#, + ) + .file("src/main.rs", r#""#) + .build(); + + p.cargo("check") + .with_status(101) + .with_stderr( + "\ +error: failed to parse manifest at `[..]` + +Caused by: + failed to parse the `edition` key + +Caused by: + this version of Cargo is older than the `2038` edition, and only supports `2015`, `2018`, and `2021` editions. +" + .to_string(), + ) + .run(); +} + +#[cargo_test] +fn do_not_package_if_src_was_modified() { + let p = project() + .file("src/main.rs", r#"fn main() { println!("hello"); }"#) + .file("dir/foo.txt", "") + .file("bar.txt", "") + .file( + "build.rs", + r#" + use std::fs; + + fn main() { + fs::write("src/generated.txt", + "Hello, world of generated files." + ).expect("failed to create file"); + fs::remove_file("dir/foo.txt").expect("failed to remove file"); + fs::remove_dir("dir").expect("failed to remove dir"); + fs::write("bar.txt", "updated content").expect("failed to update"); + fs::create_dir("new-dir").expect("failed to create dir"); + } + "#, + ) + .build(); + + p.cargo("package") + .with_status(101) + .with_stderr_contains( + "\ +error: failed to verify package tarball + +Caused by: + Source directory was modified by build.rs during cargo publish. \ + Build scripts should not modify anything outside of OUT_DIR. + Changed: [CWD]/target/package/foo-0.0.1/bar.txt + Added: [CWD]/target/package/foo-0.0.1/new-dir + <tab>[CWD]/target/package/foo-0.0.1/src/generated.txt + Removed: [CWD]/target/package/foo-0.0.1/dir + <tab>[CWD]/target/package/foo-0.0.1/dir/foo.txt + + To proceed despite this, pass the `--no-verify` flag.", + ) + .run(); + + p.cargo("package --no-verify").run(); +} + +#[cargo_test] +fn package_with_select_features() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + + [features] + required = [] + optional = [] + "#, + ) + .file( + "src/main.rs", + "#[cfg(not(feature = \"required\"))] + compile_error!(\"This crate requires `required` feature!\"); + fn main() {}", + ) + .build(); + + p.cargo("package --features required").run(); +} + +#[cargo_test] +fn package_with_all_features() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + + [features] + required = [] + optional = [] + "#, + ) + .file( + "src/main.rs", + "#[cfg(not(feature = \"required\"))] + compile_error!(\"This crate requires `required` feature!\"); + fn main() {}", + ) + .build(); + + p.cargo("package --all-features").run(); +} + +#[cargo_test] +fn package_no_default_features() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + + [features] + default = ["required"] + required = [] + "#, + ) + .file( + "src/main.rs", + "#[cfg(not(feature = \"required\"))] + compile_error!(\"This crate requires `required` feature!\"); + fn main() {}", + ) + .build(); + + p.cargo("package --no-default-features") + .with_stderr_contains("error: This crate requires `required` feature!") + .with_status(101) + .run(); +} + +#[cargo_test] +fn include_cargo_toml_implicit() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + include = ["src/lib.rs"] + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("package --list") + .with_stdout("Cargo.toml\nCargo.toml.orig\nsrc/lib.rs\n") + .run(); +} + +fn include_exclude_test(include: &str, exclude: &str, files: &[&str], expected: &str) { + let mut pb = project().file( + "Cargo.toml", + &format!( + r#" + [package] + name = "foo" + version = "0.1.0" + authors = [] + license = "MIT" + description = "foo" + documentation = "foo" + homepage = "foo" + repository = "foo" + include = {} + exclude = {} + "#, + include, exclude + ), + ); + for file in files { + pb = pb.file(file, ""); + } + let p = pb.build(); + + p.cargo("package --list") + .with_stderr("") + .with_stdout(expected) + .run(); + p.root().rm_rf(); +} + +#[cargo_test] +fn package_include_ignore_only() { + // Test with a gitignore pattern that fails to parse with glob. + // This is a somewhat nonsense pattern, but is an example of something git + // allows and glob does not. + assert!(glob::Pattern::new("src/abc**").is_err()); + + include_exclude_test( + r#"["Cargo.toml", "src/abc**", "src/lib.rs"]"#, + "[]", + &["src/lib.rs", "src/abc1.rs", "src/abc2.rs", "src/abc/mod.rs"], + "Cargo.toml\n\ + Cargo.toml.orig\n\ + src/abc/mod.rs\n\ + src/abc1.rs\n\ + src/abc2.rs\n\ + src/lib.rs\n\ + ", + ) +} + +#[cargo_test] +fn gitignore_patterns() { + include_exclude_test( + r#"["Cargo.toml", "foo"]"#, // include + "[]", + &["src/lib.rs", "foo", "a/foo", "a/b/foo", "x/foo/y", "bar"], + "Cargo.toml\n\ + Cargo.toml.orig\n\ + a/b/foo\n\ + a/foo\n\ + foo\n\ + x/foo/y\n\ + ", + ); + + include_exclude_test( + r#"["Cargo.toml", "/foo"]"#, // include + "[]", + &["src/lib.rs", "foo", "a/foo", "a/b/foo", "x/foo/y", "bar"], + "Cargo.toml\n\ + Cargo.toml.orig\n\ + foo\n\ + ", + ); + + include_exclude_test( + "[]", + r#"["foo/"]"#, // exclude + &["src/lib.rs", "foo", "a/foo", "x/foo/y", "bar"], + "Cargo.toml\n\ + Cargo.toml.orig\n\ + a/foo\n\ + bar\n\ + foo\n\ + src/lib.rs\n\ + ", + ); + + include_exclude_test( + "[]", + r#"["*.txt", "[ab]", "[x-z]"]"#, // exclude + &[ + "src/lib.rs", + "foo.txt", + "bar/foo.txt", + "other", + "a", + "b", + "c", + "x", + "y", + "z", + ], + "Cargo.toml\n\ + Cargo.toml.orig\n\ + c\n\ + other\n\ + src/lib.rs\n\ + ", + ); + + include_exclude_test( + r#"["Cargo.toml", "**/foo/bar"]"#, // include + "[]", + &["src/lib.rs", "a/foo/bar", "foo", "bar"], + "Cargo.toml\n\ + Cargo.toml.orig\n\ + a/foo/bar\n\ + ", + ); + + include_exclude_test( + r#"["Cargo.toml", "foo/**"]"#, // include + "[]", + &["src/lib.rs", "a/foo/bar", "foo/x/y/z"], + "Cargo.toml\n\ + Cargo.toml.orig\n\ + foo/x/y/z\n\ + ", + ); + + include_exclude_test( + r#"["Cargo.toml", "a/**/b"]"#, // include + "[]", + &["src/lib.rs", "a/b", "a/x/b", "a/x/y/b"], + "Cargo.toml\n\ + Cargo.toml.orig\n\ + a/b\n\ + a/x/b\n\ + a/x/y/b\n\ + ", + ); +} + +#[cargo_test] +fn gitignore_negate() { + include_exclude_test( + r#"["Cargo.toml", "*.rs", "!foo.rs", "\\!important"]"#, // include + "[]", + &["src/lib.rs", "foo.rs", "!important"], + "!important\n\ + Cargo.toml\n\ + Cargo.toml.orig\n\ + src/lib.rs\n\ + ", + ); + + // NOTE: This is unusual compared to git. Git treats `src/` as a + // short-circuit which means rules like `!src/foo.rs` would never run. + // However, because Cargo only works by iterating over *files*, it doesn't + // short-circuit. + include_exclude_test( + r#"["Cargo.toml", "src/", "!src/foo.rs"]"#, // include + "[]", + &["src/lib.rs", "src/foo.rs"], + "Cargo.toml\n\ + Cargo.toml.orig\n\ + src/lib.rs\n\ + ", + ); + + include_exclude_test( + r#"["Cargo.toml", "src/*.rs", "!foo.rs"]"#, // include + "[]", + &["src/lib.rs", "foo.rs", "src/foo.rs", "src/bar/foo.rs"], + "Cargo.toml\n\ + Cargo.toml.orig\n\ + src/lib.rs\n\ + ", + ); + + include_exclude_test( + "[]", + r#"["*.rs", "!foo.rs", "\\!important"]"#, // exclude + &["src/lib.rs", "foo.rs", "!important"], + "Cargo.toml\n\ + Cargo.toml.orig\n\ + foo.rs\n\ + ", + ); +} + +#[cargo_test] +fn exclude_dot_files_and_directories_by_default() { + include_exclude_test( + "[]", + "[]", + &["src/lib.rs", ".dotfile", ".dotdir/file"], + "Cargo.toml\n\ + Cargo.toml.orig\n\ + src/lib.rs\n\ + ", + ); + + include_exclude_test( + r#"["Cargo.toml", "src/lib.rs", ".dotfile", ".dotdir/file"]"#, + "[]", + &["src/lib.rs", ".dotfile", ".dotdir/file"], + ".dotdir/file\n\ + .dotfile\n\ + Cargo.toml\n\ + Cargo.toml.orig\n\ + src/lib.rs\n\ + ", + ); +} + +#[cargo_test] +fn invalid_license_file_path() { + // Test warning when license-file points to a non-existent file. + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "1.0.0" + license-file = "does-not-exist" + description = "foo" + homepage = "foo" + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("package --no-verify") + .with_stderr( + "\ +[WARNING] license-file `does-not-exist` does not appear to exist (relative to `[..]/foo`). +Please update the license-file setting in the manifest at `[..]/foo/Cargo.toml` +This may become a hard error in the future. +[PACKAGING] foo v1.0.0 ([..]/foo) +[PACKAGED] [..] files, [..] ([..] compressed) +", + ) + .run(); +} + +#[cargo_test] +fn license_file_implicit_include() { + // license-file should be automatically included even if not listed. + let p = git::new("foo", |p| { + p.file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "1.0.0" + license-file = "subdir/LICENSE" + description = "foo" + homepage = "foo" + include = ["src"] + "#, + ) + .file("src/lib.rs", "") + .file("subdir/LICENSE", "license text") + }); + + p.cargo("package --list") + .with_stdout( + "\ +.cargo_vcs_info.json +Cargo.toml +Cargo.toml.orig +src/lib.rs +subdir/LICENSE +", + ) + .with_stderr("") + .run(); + + p.cargo("package --no-verify -v") + .with_stderr( + "\ +[PACKAGING] foo v1.0.0 [..] +[ARCHIVING] .cargo_vcs_info.json +[ARCHIVING] Cargo.toml +[ARCHIVING] Cargo.toml.orig +[ARCHIVING] src/lib.rs +[ARCHIVING] subdir/LICENSE +[PACKAGED] 5 files, [..] ([..] compressed) +", + ) + .run(); + let f = File::open(&p.root().join("target/package/foo-1.0.0.crate")).unwrap(); + validate_crate_contents( + f, + "foo-1.0.0.crate", + &[ + ".cargo_vcs_info.json", + "Cargo.toml", + "Cargo.toml.orig", + "subdir/LICENSE", + "src/lib.rs", + ], + &[("subdir/LICENSE", "license text")], + ); +} + +#[cargo_test] +fn relative_license_included() { + // license-file path outside of package will copy into root. + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "1.0.0" + license-file = "../LICENSE" + description = "foo" + homepage = "foo" + "#, + ) + .file("src/lib.rs", "") + .file("../LICENSE", "license text") + .build(); + + p.cargo("package --list") + .with_stdout( + "\ +Cargo.toml +Cargo.toml.orig +LICENSE +src/lib.rs +", + ) + .with_stderr("") + .run(); + + p.cargo("package") + .with_stderr( + "\ +[PACKAGING] foo v1.0.0 [..] +[VERIFYING] foo v1.0.0 [..] +[COMPILING] foo v1.0.0 [..] +[FINISHED] [..] +[PACKAGED] 4 files, [..] ([..] compressed) +", + ) + .run(); + let f = File::open(&p.root().join("target/package/foo-1.0.0.crate")).unwrap(); + validate_crate_contents( + f, + "foo-1.0.0.crate", + &["Cargo.toml", "Cargo.toml.orig", "LICENSE", "src/lib.rs"], + &[("LICENSE", "license text")], + ); + let manifest = + std::fs::read_to_string(p.root().join("target/package/foo-1.0.0/Cargo.toml")).unwrap(); + assert!(manifest.contains("license-file = \"LICENSE\"")); + let orig = + std::fs::read_to_string(p.root().join("target/package/foo-1.0.0/Cargo.toml.orig")).unwrap(); + assert!(orig.contains("license-file = \"../LICENSE\"")); +} + +#[cargo_test] +fn relative_license_include_collision() { + // Can't copy a relative license-file if there is a file with that name already. + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "1.0.0" + license-file = "../LICENSE" + description = "foo" + homepage = "foo" + "#, + ) + .file("src/lib.rs", "") + .file("../LICENSE", "outer license") + .file("LICENSE", "inner license") + .build(); + + p.cargo("package --list") + .with_stdout( + "\ +Cargo.toml +Cargo.toml.orig +LICENSE +src/lib.rs +", + ) + .with_stderr("[WARNING] license-file `../LICENSE` appears to be [..]") + .run(); + + p.cargo("package") + .with_stderr( + "\ +[WARNING] license-file `../LICENSE` appears to be [..] +[PACKAGING] foo v1.0.0 [..] +[VERIFYING] foo v1.0.0 [..] +[COMPILING] foo v1.0.0 [..] +[FINISHED] [..] +[PACKAGED] 4 files, [..] ([..] compressed) +", + ) + .run(); + let f = File::open(&p.root().join("target/package/foo-1.0.0.crate")).unwrap(); + validate_crate_contents( + f, + "foo-1.0.0.crate", + &["Cargo.toml", "Cargo.toml.orig", "LICENSE", "src/lib.rs"], + &[("LICENSE", "inner license")], + ); + let manifest = read_to_string(p.root().join("target/package/foo-1.0.0/Cargo.toml")).unwrap(); + assert!(manifest.contains("license-file = \"LICENSE\"")); + let orig = read_to_string(p.root().join("target/package/foo-1.0.0/Cargo.toml.orig")).unwrap(); + assert!(orig.contains("license-file = \"../LICENSE\"")); +} + +#[cargo_test] +#[cfg(not(windows))] // Don't want to create invalid files on Windows. +fn package_restricted_windows() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + license = "MIT" + description = "foo" + homepage = "foo" + "#, + ) + .file("src/lib.rs", "pub mod con;\npub mod aux;") + .file("src/con.rs", "pub fn f() {}") + .file("src/aux/mod.rs", "pub fn f() {}") + .build(); + + p.cargo("package") + // use unordered here because the order of the warning is different on each platform. + .with_stderr_unordered( + "\ +[WARNING] file src/aux/mod.rs is a reserved Windows filename, it will not work on Windows platforms +[WARNING] file src/con.rs is a reserved Windows filename, it will not work on Windows platforms +[PACKAGING] foo [..] +[VERIFYING] foo [..] +[COMPILING] foo [..] +[PACKAGED] [..] files, [..] ([..] compressed) +[FINISHED] [..] +", + ) + .run(); +} + +#[cargo_test] +fn finds_git_in_parent() { + // Test where `Cargo.toml` is not in the root of the git repo. + let repo_path = paths::root().join("repo"); + fs::create_dir(&repo_path).unwrap(); + let p = project() + .at("repo/foo") + .file("Cargo.toml", &basic_manifest("foo", "0.1.0")) + .file("src/lib.rs", "") + .build(); + let repo = git::init(&repo_path); + git::add(&repo); + git::commit(&repo); + p.change_file("ignoreme", ""); + p.change_file("ignoreme2", ""); + p.cargo("package --list --allow-dirty") + .with_stdout( + "\ +Cargo.toml +Cargo.toml.orig +ignoreme +ignoreme2 +src/lib.rs +", + ) + .run(); + + p.change_file(".gitignore", "ignoreme"); + p.cargo("package --list --allow-dirty") + .with_stdout( + "\ +.gitignore +Cargo.toml +Cargo.toml.orig +ignoreme2 +src/lib.rs +", + ) + .run(); + + fs::write(repo_path.join(".gitignore"), "ignoreme2").unwrap(); + p.cargo("package --list --allow-dirty") + .with_stdout( + "\ +.gitignore +Cargo.toml +Cargo.toml.orig +src/lib.rs +", + ) + .run(); +} + +#[cargo_test] +#[cfg(windows)] +fn reserved_windows_name() { + // If we are running on a version of Windows that allows these reserved filenames, + // skip this test. + if paths::windows_reserved_names_are_allowed() { + return; + } + + Package::new("bar", "1.0.0") + .file("src/lib.rs", "pub mod aux;") + .file("src/aux.rs", "") + .publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + + [dependencies] + bar = "1.0.0" + "#, + ) + .file("src/main.rs", "extern crate bar;\nfn main() { }") + .build(); + p.cargo("package") + .with_status(101) + .with_stderr_contains( + "\ +error: failed to verify package tarball + +Caused by: + failed to download replaced source registry `[..]` + +Caused by: + failed to unpack package `[..] `[..]`)` + +Caused by: + failed to unpack entry at `[..]aux.rs` + +Caused by: + `[..]aux.rs` appears to contain a reserved Windows path, it cannot be extracted on Windows + +Caused by: + failed to unpack `[..]aux.rs` + +Caused by: + failed to unpack `[..]aux.rs` into `[..]aux.rs`", + ) + .run(); +} + +#[cargo_test] +fn list_with_path_and_lock() { + // Allow --list even for something that isn't packageable. + + // Init an empty registry because a versionless path dep will search for + // the package on crates.io. + registry::init(); + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + license = "MIT" + description = "foo" + homepage = "foo" + + [dependencies] + bar = {path="bar"} + "#, + ) + .file("src/main.rs", "fn main() {}") + .file("bar/Cargo.toml", &basic_manifest("bar", "0.1.0")) + .file("bar/src/lib.rs", "") + .build(); + + p.cargo("package --list") + .with_stdout( + "\ +Cargo.lock +Cargo.toml +Cargo.toml.orig +src/main.rs +", + ) + .run(); + + p.cargo("package") + .with_status(101) + .with_stderr( + "\ +[ERROR] all dependencies must have a version specified when packaging. +dependency `bar` does not specify a version +Note: The packaged dependency will use the version from crates.io, +the `path` specification will be removed from the dependency declaration. +", + ) + .run(); +} + +#[cargo_test] +fn long_file_names() { + // Filenames over 100 characters require a GNU extension tarfile. + // See #8453. + + registry::init(); + let long_name = concat!( + "012345678901234567890123456789012345678901234567890123456789", + "012345678901234567890123456789012345678901234567890123456789", + "012345678901234567890123456789012345678901234567890123456789" + ); + if cfg!(windows) { + // Long paths on Windows require a special registry entry that is + // disabled by default (even on Windows 10). + // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file + // If the directory where Cargo runs happens to be more than 80 characters + // long, then it will bump into this limit. + // + // First create a directory to account for various paths Cargo will + // be using in the target directory (such as "target/package/foo-0.1.0"). + let test_path = paths::root().join("test-dir-probe-long-path-support"); + test_path.mkdir_p(); + let test_path = test_path.join(long_name); + if let Err(e) = File::create(&test_path) { + // write to stderr directly to avoid output from being captured + // and always display text, even without --nocapture + use std::io::Write; + writeln!( + std::io::stderr(), + "\nSkipping long_file_names test, this OS or filesystem does not \ + appear to support long file paths: {:?}\n{:?}", + e, + test_path + ) + .unwrap(); + return; + } + } + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + license = "MIT" + description = "foo" + homepage = "foo" + + [dependencies] + "#, + ) + .file(long_name, "something") + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("package").run(); + p.cargo("package --list") + .with_stdout(&format!( + "\ +{} +Cargo.lock +Cargo.toml +Cargo.toml.orig +src/main.rs +", + long_name + )) + .run(); +} + +#[cargo_test] +fn reproducible_output() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + exclude = ["*.txt"] + license = "MIT" + description = "foo" + "#, + ) + .file("src/main.rs", r#"fn main() { println!("hello"); }"#) + .build(); + + p.cargo("package").run(); + assert!(p.root().join("target/package/foo-0.0.1.crate").is_file()); + + let f = File::open(&p.root().join("target/package/foo-0.0.1.crate")).unwrap(); + let decoder = GzDecoder::new(f); + let mut archive = Archive::new(decoder); + for ent in archive.entries().unwrap() { + let ent = ent.unwrap(); + println!("checking {:?}", ent.path()); + let header = ent.header(); + assert_eq!(header.mode().unwrap(), 0o644); + assert!(header.mtime().unwrap() != 0); + assert_eq!(header.username().unwrap().unwrap(), ""); + assert_eq!(header.groupname().unwrap().unwrap(), ""); + } +} + +#[cargo_test] +fn package_with_resolver_and_metadata() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + resolver = '2' + + [package.metadata.docs.rs] + all-features = true + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("package").run(); +} + +#[cargo_test] +fn deleted_git_working_tree() { + // When deleting a file, but not staged, cargo should ignore the file. + let (p, repo) = git::new_repo("foo", |p| { + p.file("src/lib.rs", "").file("src/main.rs", "fn main() {}") + }); + p.root().join("src/lib.rs").rm_rf(); + p.cargo("package --allow-dirty --list") + .with_stdout( + "\ +Cargo.lock +Cargo.toml +Cargo.toml.orig +src/main.rs +", + ) + .run(); + p.cargo("package --allow-dirty").run(); + let mut index = t!(repo.index()); + t!(index.remove(Path::new("src/lib.rs"), 0)); + t!(index.write()); + p.cargo("package --allow-dirty --list") + .with_stdout( + "\ +Cargo.lock +Cargo.toml +Cargo.toml.orig +src/main.rs +", + ) + .run(); + p.cargo("package --allow-dirty").run(); +} + +#[cargo_test] +fn in_workspace() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + + [workspace] + members = ["bar"] + "#, + ) + .file("src/main.rs", "fn main() {}") + .file( + "bar/Cargo.toml", + r#" + [package] + name = "bar" + version = "0.0.1" + authors = [] + license = "MIT" + description = "bar" + workspace = ".." + "#, + ) + .file("bar/src/main.rs", "fn main() {}") + .build(); + + p.cargo("package --workspace") + .with_stderr( + "\ +[WARNING] manifest has no documentation, [..] +See [..] +[PACKAGING] bar v0.0.1 ([CWD]/bar) +[VERIFYING] bar v0.0.1 ([CWD]/bar) +[COMPILING] bar v0.0.1 ([CWD][..]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[PACKAGED] [..] files, [..] ([..] compressed) +[WARNING] manifest has no documentation, [..] +See [..] +[PACKAGING] foo v0.0.1 ([CWD]) +[VERIFYING] foo v0.0.1 ([CWD]) +[COMPILING] foo v0.0.1 ([CWD][..]) +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[PACKAGED] [..] files, [..] ([..] compressed) +", + ) + .run(); + + assert!(p.root().join("target/package/foo-0.0.1.crate").is_file()); + assert!(p.root().join("target/package/bar-0.0.1.crate").is_file()); +} + +#[cargo_test] +fn workspace_overrides_resolver() { + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = ["bar", "baz"] + "#, + ) + .file( + "bar/Cargo.toml", + r#" + [package] + name = "bar" + version = "0.1.0" + edition = "2021" + "#, + ) + .file("bar/src/lib.rs", "") + .file( + "baz/Cargo.toml", + r#" + [package] + name = "baz" + version = "0.1.0" + edition = "2015" + "#, + ) + .file("baz/src/lib.rs", "") + .build(); + + p.cargo("package --no-verify -p bar -p baz").run(); + + let f = File::open(&p.root().join("target/package/bar-0.1.0.crate")).unwrap(); + let rewritten_toml = format!( + r#"{} +[package] +edition = "2021" +name = "bar" +version = "0.1.0" +resolver = "1" +"#, + cargo::core::package::MANIFEST_PREAMBLE + ); + validate_crate_contents( + f, + "bar-0.1.0.crate", + &["Cargo.toml", "Cargo.toml.orig", "src/lib.rs"], + &[("Cargo.toml", &rewritten_toml)], + ); + + // When the crate has the same implicit resolver as the workspace it is not overridden + let f = File::open(&p.root().join("target/package/baz-0.1.0.crate")).unwrap(); + let rewritten_toml = format!( + r#"{} +[package] +edition = "2015" +name = "baz" +version = "0.1.0" +"#, + cargo::core::package::MANIFEST_PREAMBLE + ); + validate_crate_contents( + f, + "baz-0.1.0.crate", + &["Cargo.toml", "Cargo.toml.orig", "src/lib.rs"], + &[("Cargo.toml", &rewritten_toml)], + ); +} + +fn verify_packaged_status_line( + output: std::process::Output, + num_files: usize, + uncompressed_size: u64, + compressed_size: u64, +) { + use cargo::util::human_readable_bytes; + + let stderr = String::from_utf8(output.stderr).unwrap(); + let mut packaged_lines = stderr + .lines() + .filter(|line| line.trim().starts_with("Packaged")); + let packaged_line = packaged_lines + .next() + .expect("`Packaged` status line should appear in stderr"); + assert!( + packaged_lines.next().is_none(), + "Only one `Packaged` status line should appear in stderr" + ); + let size_info = packaged_line.trim().trim_start_matches("Packaged").trim(); + let uncompressed = human_readable_bytes(uncompressed_size); + let compressed = human_readable_bytes(compressed_size); + let expected = format!( + "{} files, {:.1}{} ({:.1}{} compressed)", + num_files, uncompressed.0, uncompressed.1, compressed.0, compressed.1 + ); + assert_eq!(size_info, expected); +} + +#[cargo_test] +fn basic_filesizes() { + let cargo_toml_orig_contents = r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + exclude = ["*.txt"] + license = "MIT" + description = "foo" + "#; + let main_rs_contents = r#"fn main() { println!("🦀"); }"#; + let cargo_toml_contents = format!( + r#"{} +[package] +name = "foo" +version = "0.0.1" +authors = [] +exclude = ["*.txt"] +description = "foo" +license = "MIT" +"#, + cargo::core::package::MANIFEST_PREAMBLE + ); + let cargo_lock_contents = r#"# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "foo" +version = "0.0.1" +"#; + let p = project() + .file("Cargo.toml", cargo_toml_orig_contents) + .file("src/main.rs", main_rs_contents) + .file("src/bar.txt", "Ignored text file contents") // should be ignored when packaging + .build(); + + let uncompressed_size = (cargo_toml_orig_contents.len() + + main_rs_contents.len() + + cargo_toml_contents.len() + + cargo_lock_contents.len()) as u64; + let output = p.cargo("package").exec_with_output().unwrap(); + + assert!(p.root().join("target/package/foo-0.0.1.crate").is_file()); + p.cargo("package -l") + .with_stdout( + "\ +Cargo.lock +Cargo.toml +Cargo.toml.orig +src/main.rs +", + ) + .run(); + p.cargo("package").with_stdout("").run(); + + let f = File::open(&p.root().join("target/package/foo-0.0.1.crate")).unwrap(); + let compressed_size = f.metadata().unwrap().len(); + verify_packaged_status_line(output, 4, uncompressed_size, compressed_size); + validate_crate_contents( + f, + "foo-0.0.1.crate", + &["Cargo.lock", "Cargo.toml", "Cargo.toml.orig", "src/main.rs"], + &[ + ("Cargo.lock", cargo_lock_contents), + ("Cargo.toml", &cargo_toml_contents), + ("Cargo.toml.orig", cargo_toml_orig_contents), + ("src/main.rs", main_rs_contents), + ], + ); +} + +#[cargo_test] +fn larger_filesizes() { + let cargo_toml_orig_contents = r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + "#; + let lots_of_crabs = std::iter::repeat("🦀").take(1337).collect::<String>(); + let main_rs_contents = format!(r#"fn main() {{ println!("{}"); }}"#, lots_of_crabs); + let bar_txt_contents = "This file is relatively uncompressible, to increase the compressed + package size beyond 1KiB. + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt + ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation + ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in + reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur + sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est + laborum."; + let cargo_toml_contents = format!( + r#"{} +[package] +name = "foo" +version = "0.0.1" +authors = [] +description = "foo" +license = "MIT" +"#, + cargo::core::package::MANIFEST_PREAMBLE + ); + let cargo_lock_contents = r#"# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "foo" +version = "0.0.1" +"#; + let p = project() + .file("Cargo.toml", cargo_toml_orig_contents) + .file("src/main.rs", &main_rs_contents) + .file("src/bar.txt", bar_txt_contents) + .build(); + + let uncompressed_size = (cargo_toml_orig_contents.len() + + main_rs_contents.len() + + cargo_toml_contents.len() + + cargo_lock_contents.len() + + bar_txt_contents.len()) as u64; + + let output = p.cargo("package").exec_with_output().unwrap(); + assert!(p.root().join("target/package/foo-0.0.1.crate").is_file()); + p.cargo("package -l") + .with_stdout( + "\ +Cargo.lock +Cargo.toml +Cargo.toml.orig +src/bar.txt +src/main.rs +", + ) + .run(); + p.cargo("package").with_stdout("").run(); + + let f = File::open(&p.root().join("target/package/foo-0.0.1.crate")).unwrap(); + let compressed_size = f.metadata().unwrap().len(); + verify_packaged_status_line(output, 5, uncompressed_size, compressed_size); + validate_crate_contents( + f, + "foo-0.0.1.crate", + &[ + "Cargo.lock", + "Cargo.toml", + "Cargo.toml.orig", + "src/bar.txt", + "src/main.rs", + ], + &[ + ("Cargo.lock", cargo_lock_contents), + ("Cargo.toml", &cargo_toml_contents), + ("Cargo.toml.orig", cargo_toml_orig_contents), + ("src/bar.txt", bar_txt_contents), + ("src/main.rs", &main_rs_contents), + ], + ); +} + +#[cargo_test] +fn symlink_filesizes() { + if !symlink_supported() { + return; + } + + let cargo_toml_orig_contents = r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + "#; + let lots_of_crabs = std::iter::repeat("🦀").take(1337).collect::<String>(); + let main_rs_contents = format!(r#"fn main() {{ println!("{}"); }}"#, lots_of_crabs); + let bar_txt_contents = "This file is relatively uncompressible, to increase the compressed + package size beyond 1KiB. + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt + ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation + ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in + reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur + sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est + laborum."; + let cargo_toml_contents = format!( + r#"{} +[package] +name = "foo" +version = "0.0.1" +authors = [] +description = "foo" +license = "MIT" +"#, + cargo::core::package::MANIFEST_PREAMBLE + ); + let cargo_lock_contents = r#"# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "foo" +version = "0.0.1" +"#; + + let p = project() + .file("Cargo.toml", cargo_toml_orig_contents) + .file("src/main.rs", &main_rs_contents) + .file("bla/bar.txt", bar_txt_contents) + .symlink("src/main.rs", "src/main.rs.bak") + .symlink_dir("bla", "foo") + .build(); + + let uncompressed_size = (cargo_toml_orig_contents.len() + + main_rs_contents.len() * 2 + + cargo_toml_contents.len() + + cargo_lock_contents.len() + + bar_txt_contents.len() * 2) as u64; + + let output = p.cargo("package").exec_with_output().unwrap(); + assert!(p.root().join("target/package/foo-0.0.1.crate").is_file()); + p.cargo("package -l") + .with_stdout( + "\ +Cargo.lock +Cargo.toml +Cargo.toml.orig +bla/bar.txt +foo/bar.txt +src/main.rs +src/main.rs.bak +", + ) + .run(); + p.cargo("package").with_stdout("").run(); + + let f = File::open(&p.root().join("target/package/foo-0.0.1.crate")).unwrap(); + let compressed_size = f.metadata().unwrap().len(); + verify_packaged_status_line(output, 7, uncompressed_size, compressed_size); + validate_crate_contents( + f, + "foo-0.0.1.crate", + &[ + "Cargo.lock", + "Cargo.toml", + "Cargo.toml.orig", + "bla/bar.txt", + "foo/bar.txt", + "src/main.rs", + "src/main.rs.bak", + ], + &[ + ("Cargo.lock", cargo_lock_contents), + ("Cargo.toml", &cargo_toml_contents), + ("Cargo.toml.orig", cargo_toml_orig_contents), + ("bla/bar.txt", bar_txt_contents), + ("foo/bar.txt", bar_txt_contents), + ("src/main.rs", &main_rs_contents), + ("src/main.rs.bak", &main_rs_contents), + ], + ); +} |