From 10ee2acdd26a7f1298c6f6d6b7af9b469fe29b87 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 4 May 2024 14:41:41 +0200 Subject: Merging upstream version 1.70.0+dfsg2. Signed-off-by: Daniel Baumann --- src/tools/cargo/tests/testsuite/publish.rs | 2951 ++++++++++++++++++++++++++++ 1 file changed, 2951 insertions(+) create mode 100644 src/tools/cargo/tests/testsuite/publish.rs (limited to 'src/tools/cargo/tests/testsuite/publish.rs') diff --git a/src/tools/cargo/tests/testsuite/publish.rs b/src/tools/cargo/tests/testsuite/publish.rs new file mode 100644 index 000000000..00a79fe73 --- /dev/null +++ b/src/tools/cargo/tests/testsuite/publish.rs @@ -0,0 +1,2951 @@ +//! Tests for the `cargo publish` command. + +use cargo_test_support::git::{self, repo}; +use cargo_test_support::paths; +use cargo_test_support::registry::{self, Package, RegistryBuilder, Response}; +use cargo_test_support::{basic_manifest, no_such_file_err_msg, project, publish}; +use std::fs; +use std::sync::{Arc, Mutex}; + +const CLEAN_FOO_JSON: &str = r#" + { + "authors": [], + "badges": {}, + "categories": [], + "deps": [], + "description": "foo", + "documentation": "foo", + "features": {}, + "homepage": "foo", + "keywords": [], + "license": "MIT", + "license_file": null, + "links": null, + "name": "foo", + "readme": null, + "readme_file": null, + "repository": "foo", + "vers": "0.0.1" + } +"#; + +fn validate_upload_foo() { + publish::validate_upload( + r#" + { + "authors": [], + "badges": {}, + "categories": [], + "deps": [], + "description": "foo", + "documentation": null, + "features": {}, + "homepage": null, + "keywords": [], + "license": "MIT", + "license_file": null, + "links": null, + "name": "foo", + "readme": null, + "readme_file": null, + "repository": null, + "vers": "0.0.1" + } + "#, + "foo-0.0.1.crate", + &["Cargo.lock", "Cargo.toml", "Cargo.toml.orig", "src/main.rs"], + ); +} + +fn validate_upload_li() { + publish::validate_upload( + r#" + { + "authors": [], + "badges": {}, + "categories": [], + "deps": [], + "description": "li", + "documentation": null, + "features": {}, + "homepage": null, + "keywords": [], + "license": "MIT", + "license_file": null, + "links": null, + "name": "li", + "readme": null, + "readme_file": null, + "repository": null, + "vers": "0.0.1" + } + "#, + "li-0.0.1.crate", + &["Cargo.lock", "Cargo.toml", "Cargo.toml.orig", "src/main.rs"], + ); +} + +#[cargo_test] +fn simple() { + let registry = RegistryBuilder::new().http_api().http_index().build(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("publish --no-verify") + .replace_crates_io(registry.index_url()) + .with_stderr( + "\ +[UPDATING] crates.io index +[WARNING] manifest has no documentation, [..] +See [..] +[PACKAGING] foo v0.0.1 ([CWD]) +[PACKAGED] [..] files, [..] ([..] compressed) +[UPLOADING] foo v0.0.1 ([CWD]) +[UPLOADED] foo v0.0.1 to registry `crates-io` +note: Waiting for `foo v0.0.1` to be available at registry `crates-io`. +You may press ctrl-c to skip waiting; the crate should be available shortly. +[PUBLISHED] foo v0.0.1 at registry `crates-io` +", + ) + .run(); + + validate_upload_foo(); +} + +// Check that the `token` key works at the root instead of under a +// `[registry]` table. +#[cargo_test] +fn simple_publish_with_http() { + let _reg = registry::RegistryBuilder::new() + .http_api() + .token(registry::Token::Plaintext("sekrit".to_string())) + .build(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("publish --no-verify --token sekrit --registry dummy-registry") + .with_stderr( + "\ +[UPDATING] `dummy-registry` index +[WARNING] manifest has no documentation, [..] +See [..] +[PACKAGING] foo v0.0.1 ([CWD]) +[PACKAGED] [..] files, [..] ([..] compressed) +[UPLOADING] foo v0.0.1 ([CWD]) +[UPLOADED] foo v0.0.1 to registry `dummy-registry` +note: Waiting for `foo v0.0.1` to be available at registry `dummy-registry`. +You may press ctrl-c to skip waiting; the crate should be available shortly. +[PUBLISHED] foo v0.0.1 at registry `dummy-registry` +", + ) + .run(); +} + +#[cargo_test] +fn simple_publish_with_asymmetric() { + let _reg = registry::RegistryBuilder::new() + .http_api() + .http_index() + .alternative_named("dummy-registry") + .token(registry::Token::rfc_key()) + .build(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("publish --no-verify -Zregistry-auth --registry dummy-registry") + .masquerade_as_nightly_cargo(&["registry-auth"]) + .with_stderr( + "\ +[UPDATING] `dummy-registry` index +[WARNING] manifest has no documentation, [..] +See [..] +[PACKAGING] foo v0.0.1 ([CWD]) +[PACKAGED] [..] files, [..] ([..] compressed) +[UPLOADING] foo v0.0.1 ([CWD]) +[UPLOADED] foo v0.0.1 to registry `dummy-registry` +note: Waiting for `foo v0.0.1` to be available at registry `dummy-registry`. +You may press ctrl-c to skip waiting; the crate should be available shortly. +[PUBLISHED] foo v0.0.1 at registry `dummy-registry` +", + ) + .run(); +} + +#[cargo_test] +fn old_token_location() { + // `publish` generally requires a remote registry + let registry = registry::RegistryBuilder::new().http_api().build(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + let credentials = paths::home().join(".cargo/credentials.toml"); + fs::remove_file(&credentials).unwrap(); + + // Verify can't publish without a token. + p.cargo("publish --no-verify") + .replace_crates_io(registry.index_url()) + .with_status(101) + .with_stderr_contains( + "[ERROR] no token found, \ + please run `cargo login`", + ) + .run(); + + fs::write(&credentials, format!(r#"token = "{}""#, registry.token())).unwrap(); + + p.cargo("publish --no-verify") + .replace_crates_io(registry.index_url()) + .with_stderr( + "\ +[UPDATING] crates.io index +[WARNING] manifest has no documentation, [..] +See [..] +[PACKAGING] foo v0.0.1 ([CWD]) +[PACKAGED] [..] files, [..] ([..] compressed) +[UPLOADING] foo v0.0.1 ([CWD]) +[UPLOADED] foo v0.0.1 to registry `crates-io` +note: Waiting [..] +You may press ctrl-c [..] +[PUBLISHED] foo v0.0.1 [..] +", + ) + .run(); + + // Skip `validate_upload_foo` as we just cared we got far enough for verify the token behavior. + // Other tests will verify the endpoint gets the right payload. +} + +#[cargo_test] +fn simple_with_index() { + // `publish` generally requires a remote registry + let registry = registry::RegistryBuilder::new().http_api().build(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("publish --no-verify") + .arg("--token") + .arg(registry.token()) + .arg("--index") + .arg(registry.index_url().as_str()) + .with_stderr( + "\ +[..] +[..] +[..] +[..] +[..] +[UPLOADING] foo v0.0.1 ([CWD]) +[UPLOADED] foo v0.0.1 to registry `[ROOT]/registry` +note: Waiting [..] +You may press ctrl-c [..] +[PUBLISHED] foo v0.0.1 [..] +", + ) + .run(); + + // Skip `validate_upload_foo` as we just cared we got far enough for verify the VCS behavior. + // Other tests will verify the endpoint gets the right payload. +} + +#[cargo_test] +fn git_deps() { + // Use local registry for faster test times since no publish will occur + let registry = 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("publish -v --no-verify") + .replace_crates_io(registry.index_url()) + .with_status(101) + .with_stderr( + "\ +[UPDATING] [..] index +[ERROR] all dependencies must have a version specified when publishing. +dependency `foo` does not specify a version +Note: The published dependency will use the version from crates.io, +the `git` specification will be removed from the dependency declaration. +", + ) + .run(); +} + +#[cargo_test] +fn path_dependency_no_version() { + // Use local registry for faster test times since no publish will occur + let registry = registry::init(); + + 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.0.1")) + .file("bar/src/lib.rs", "") + .build(); + + p.cargo("publish") + .replace_crates_io(registry.index_url()) + .with_status(101) + .with_stderr( + "\ +[UPDATING] [..] index +[ERROR] all dependencies must have a version specified when publishing. +dependency `bar` does not specify a version +Note: The published dependency will use the version from crates.io, +the `path` specification will be removed from the dependency declaration. +", + ) + .run(); +} + +#[cargo_test] +fn unpublishable_crate() { + // Use local registry for faster test times since no publish will occur + let registry = registry::init(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + publish = false + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("publish --index") + .arg(registry.index_url().as_str()) + .with_status(101) + .with_stderr( + "\ +[ERROR] `foo` cannot be published. +`package.publish` is set to `false` or an empty list in Cargo.toml and prevents publishing. +", + ) + .run(); +} + +#[cargo_test] +fn dont_publish_dirty() { + // Use local registry for faster test times since no publish will occur + let registry = registry::init(); + + let p = project().file("bar", "").build(); + + let _ = git::repo(&paths::root().join("foo")) + .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", "fn main() {}") + .build(); + + p.cargo("publish") + .replace_crates_io(registry.index_url()) + .with_status(101) + .with_stderr( + "\ +[UPDATING] crates.io index +error: 1 files in the working directory contain changes that were not yet \ +committed into git: + +bar + +to proceed despite this and include the uncommitted changes, pass the `--allow-dirty` flag +", + ) + .run(); +} + +#[cargo_test] +fn publish_clean() { + // `publish` generally requires a remote registry + let registry = registry::RegistryBuilder::new().http_api().build(); + + let p = project().build(); + + let _ = repo(&paths::root().join("foo")) + .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", "fn main() {}") + .build(); + + p.cargo("publish") + .replace_crates_io(registry.index_url()) + .with_stderr( + "\ +[..] +[..] +[VERIFYING] foo v0.0.1 ([CWD]) +[..] +[..] +[..] +[UPLOADING] foo v0.0.1 ([CWD]) +[UPLOADED] foo v0.0.1 to registry `crates-io` +note: Waiting [..] +You may press ctrl-c to skip waiting; the crate should be available shortly. +[PUBLISHED] foo v0.0.1 [..] +", + ) + .run(); + + // Skip `validate_upload_foo_clean` as we just cared we got far enough for verify the VCS behavior. + // Other tests will verify the endpoint gets the right payload. +} + +#[cargo_test] +fn publish_in_sub_repo() { + // `publish` generally requires a remote registry + let registry = registry::RegistryBuilder::new().http_api().build(); + + let p = project().no_manifest().file("baz", "").build(); + + let _ = repo(&paths::root().join("foo")) + .file( + "bar/Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + documentation = "foo" + homepage = "foo" + repository = "foo" + "#, + ) + .file("bar/src/main.rs", "fn main() {}") + .build(); + + p.cargo("publish") + .replace_crates_io(registry.index_url()) + .cwd("bar") + .with_stderr( + "\ +[..] +[..] +[VERIFYING] foo v0.0.1 ([CWD]) +[..] +[..] +[..] +[UPLOADING] foo v0.0.1 ([CWD]) +[UPLOADED] foo v0.0.1 to registry `crates-io` +note: Waiting [..] +You may press ctrl-c [..] +[PUBLISHED] foo v0.0.1 [..] +", + ) + .run(); + + // Skip `validate_upload_foo_clean` as we just cared we got far enough for verify the VCS behavior. + // Other tests will verify the endpoint gets the right payload. +} + +#[cargo_test] +fn publish_when_ignored() { + // `publish` generally requires a remote registry + let registry = registry::RegistryBuilder::new().http_api().build(); + + let p = project().file("baz", "").build(); + + let _ = repo(&paths::root().join("foo")) + .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", "fn main() {}") + .file(".gitignore", "baz") + .build(); + + p.cargo("publish") + .replace_crates_io(registry.index_url()) + .with_stderr( + "\ +[..] +[..] +[VERIFYING] foo v0.0.1 ([CWD]) +[..] +[..] +[..] +[UPLOADING] foo v0.0.1 ([CWD]) +[UPLOADED] foo v0.0.1 to registry `crates-io` +note: Waiting [..] +You may press ctrl-c [..] +[PUBLISHED] foo v0.0.1 [..] +", + ) + .run(); + + // Skip `validate_upload` as we just cared we got far enough for verify the VCS behavior. + // Other tests will verify the endpoint gets the right payload. +} + +#[cargo_test] +fn ignore_when_crate_ignored() { + // `publish` generally requires a remote registry + let registry = registry::RegistryBuilder::new().http_api().build(); + + let p = project().no_manifest().file("bar/baz", "").build(); + + let _ = repo(&paths::root().join("foo")) + .file(".gitignore", "bar") + .nocommit_file( + "bar/Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + documentation = "foo" + homepage = "foo" + repository = "foo" + "#, + ) + .nocommit_file("bar/src/main.rs", "fn main() {}"); + p.cargo("publish") + .replace_crates_io(registry.index_url()) + .cwd("bar") + .with_stderr( + "\ +[..] +[..] +[VERIFYING] foo v0.0.1 ([CWD]) +[..] +[..] +[..] +[UPLOADING] foo v0.0.1 ([CWD]) +[UPLOADED] foo v0.0.1 to registry `crates-io` +note: Waiting [..] +You may press ctrl-c [..] +[PUBLISHED] foo v0.0.1 [..] +", + ) + .run(); + + // Skip `validate_upload` as we just cared we got far enough for verify the VCS behavior. + // Other tests will verify the endpoint gets the right payload. +} + +#[cargo_test] +fn new_crate_rejected() { + // Use local registry for faster test times since no publish will occur + let registry = registry::init(); + + let p = project().file("baz", "").build(); + + let _ = repo(&paths::root().join("foo")) + .nocommit_file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + documentation = "foo" + homepage = "foo" + repository = "foo" + "#, + ) + .nocommit_file("src/main.rs", "fn main() {}"); + p.cargo("publish") + .replace_crates_io(registry.index_url()) + .with_status(101) + .with_stderr_contains( + "[ERROR] 3 files in the working directory contain \ + changes that were not yet committed into git:", + ) + .run(); +} + +#[cargo_test] +fn dry_run() { + // Use local registry for faster test times since no publish will occur + let registry = registry::init(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("publish --dry-run --index") + .arg(registry.index_url().as_str()) + .with_stderr( + "\ +[UPDATING] `[..]` index +[WARNING] manifest has no documentation, [..] +See [..] +[PACKAGING] foo v0.0.1 ([CWD]) +[VERIFYING] foo v0.0.1 ([CWD]) +[COMPILING] foo v0.0.1 [..] +[FINISHED] dev [unoptimized + debuginfo] target(s) in [..] +[PACKAGED] [..] files, [..] ([..] compressed) +[UPLOADING] foo v0.0.1 ([CWD]) +[WARNING] aborting upload due to dry run +", + ) + .run(); + + // Ensure the API request wasn't actually made + assert!(registry::api_path().join("api/v1/crates").exists()); + assert!(!registry::api_path().join("api/v1/crates/new").exists()); +} + +#[cargo_test] +fn registry_not_in_publish_list() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + publish = [ + "test" + ] + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("publish") + .arg("--registry") + .arg("alternative") + .with_status(101) + .with_stderr( + "\ +[ERROR] `foo` cannot be published. +The registry `alternative` is not listed in the `package.publish` value in Cargo.toml. +", + ) + .run(); +} + +#[cargo_test] +fn publish_empty_list() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + publish = [] + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("publish --registry alternative") + .with_status(101) + .with_stderr( + "\ +[ERROR] `foo` cannot be published. +`package.publish` is set to `false` or an empty list in Cargo.toml and prevents publishing. +", + ) + .run(); +} + +#[cargo_test] +fn publish_allowed_registry() { + let _registry = RegistryBuilder::new() + .http_api() + .http_index() + .alternative() + .build(); + + let p = project().build(); + + let _ = repo(&paths::root().join("foo")) + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + documentation = "foo" + homepage = "foo" + repository = "foo" + publish = ["alternative"] + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("publish --registry alternative") + .with_stderr( + "\ +[..] +[..] +[VERIFYING] foo v0.0.1 ([CWD]) +[..] +[..] +[..] +[UPLOADING] foo v0.0.1 ([CWD]) +[UPLOADED] foo v0.0.1 to registry `alternative` +note: Waiting for `foo v0.0.1` to be available at registry `alternative`. +You may press ctrl-c [..] +[PUBLISHED] foo v0.0.1 at registry `alternative` +", + ) + .run(); + + publish::validate_alt_upload( + CLEAN_FOO_JSON, + "foo-0.0.1.crate", + &[ + "Cargo.lock", + "Cargo.toml", + "Cargo.toml.orig", + "src/main.rs", + ".cargo_vcs_info.json", + ], + ); +} + +#[cargo_test] +fn publish_implicitly_to_only_allowed_registry() { + let _registry = RegistryBuilder::new() + .http_api() + .http_index() + .alternative() + .build(); + + let p = project().build(); + + let _ = repo(&paths::root().join("foo")) + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + documentation = "foo" + homepage = "foo" + repository = "foo" + publish = ["alternative"] + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("publish") + .with_stderr( + "\ +[NOTE] Found `alternative` as only allowed registry. Publishing to it automatically. +[UPDATING] `alternative` index +[..] +[VERIFYING] foo v0.0.1 ([CWD]) +[..] +[..] +[..] +[UPLOADING] foo v0.0.1 ([CWD]) +[UPLOADED] foo v0.0.1 to registry `alternative` +note: Waiting [..] +You may press ctrl-c [..] +[PUBLISHED] foo v0.0.1 [..] +", + ) + .run(); + + publish::validate_alt_upload( + CLEAN_FOO_JSON, + "foo-0.0.1.crate", + &[ + "Cargo.lock", + "Cargo.toml", + "Cargo.toml.orig", + "src/main.rs", + ".cargo_vcs_info.json", + ], + ); +} + +#[cargo_test] +fn publish_fail_with_no_registry_specified() { + let p = project().build(); + + let _ = repo(&paths::root().join("foo")) + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + documentation = "foo" + homepage = "foo" + repository = "foo" + publish = ["alternative", "test"] + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("publish") + .with_status(101) + .with_stderr( + "\ +[ERROR] `foo` cannot be published. +The registry `crates-io` is not listed in the `package.publish` value in Cargo.toml. +", + ) + .run(); +} + +#[cargo_test] +fn block_publish_no_registry() { + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + publish = [] + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("publish --registry alternative") + .with_status(101) + .with_stderr( + "\ +[ERROR] `foo` cannot be published. +`package.publish` is set to `false` or an empty list in Cargo.toml and prevents publishing. +", + ) + .run(); +} + +// Explicitly setting `crates-io` in the publish list. +#[cargo_test] +fn publish_with_crates_io_explicit() { + // `publish` generally requires a remote registry + let registry = registry::RegistryBuilder::new().http_api().build(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + publish = ["crates-io"] + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("publish --registry alternative") + .with_status(101) + .with_stderr( + "\ +[ERROR] `foo` cannot be published. +The registry `alternative` is not listed in the `package.publish` value in Cargo.toml. +", + ) + .run(); + + p.cargo("publish") + .replace_crates_io(registry.index_url()) + .with_stderr( + "\ +[UPDATING] [..] +[WARNING] [..] +[..] +[PACKAGING] [..] +[VERIFYING] foo v0.0.1 ([CWD]) +[..] +[..] +[..] +[UPLOADING] foo v0.0.1 ([CWD]) +[UPLOADED] foo v0.0.1 to registry `crates-io` +note: Waiting [..] +You may press ctrl-c [..] +[PUBLISHED] foo v0.0.1 [..] +", + ) + .run(); +} + +#[cargo_test] +fn publish_with_select_features() { + // `publish` generally requires a remote registry + let registry = registry::RegistryBuilder::new().http_api().build(); + + 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("publish --features required") + .replace_crates_io(registry.index_url()) + .with_stderr( + "\ +[..] +[..] +[..] +[..] +[VERIFYING] foo v0.0.1 ([CWD]) +[..] +[..] +[..] +[UPLOADING] foo v0.0.1 ([CWD]) +[UPLOADED] foo v0.0.1 to registry `crates-io` +note: Waiting [..] +You may press ctrl-c [..] +[PUBLISHED] foo v0.0.1 [..] +", + ) + .run(); +} + +#[cargo_test] +fn publish_with_all_features() { + // `publish` generally requires a remote registry + let registry = registry::RegistryBuilder::new().http_api().build(); + + 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("publish --all-features") + .replace_crates_io(registry.index_url()) + .with_stderr( + "\ +[..] +[..] +[..] +[..] +[VERIFYING] foo v0.0.1 ([CWD]) +[..] +[..] +[..] +[UPLOADING] foo v0.0.1 ([CWD]) +[UPLOADED] foo v0.0.1 to registry `crates-io` +note: Waiting [..] +You may press ctrl-c [..] +[PUBLISHED] foo v0.0.1 [..] +", + ) + .run(); +} + +#[cargo_test] +fn publish_with_no_default_features() { + // Use local registry for faster test times since no publish will occur + let registry = registry::init(); + + 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("publish --no-default-features") + .replace_crates_io(registry.index_url()) + .with_status(101) + .with_stderr_contains("error: This crate requires `required` feature!") + .run(); +} + +#[cargo_test] +fn publish_with_patch() { + let registry = RegistryBuilder::new().http_api().http_index().build(); + Package::new("bar", "1.0.0").publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + [dependencies] + bar = "1.0" + [patch.crates-io] + bar = { path = "bar" } + "#, + ) + .file( + "src/main.rs", + "extern crate bar; + fn main() { + bar::newfunc(); + }", + ) + .file("bar/Cargo.toml", &basic_manifest("bar", "1.0.0")) + .file("bar/src/lib.rs", "pub fn newfunc() {}") + .build(); + + // Check that it works with the patched crate. + p.cargo("build").run(); + + // Check that verify fails with patched crate which has new functionality. + p.cargo("publish") + .replace_crates_io(registry.index_url()) + .with_status(101) + .with_stderr_contains("[..]newfunc[..]") + .run(); + + // Remove the usage of new functionality and try again. + p.change_file("src/main.rs", "extern crate bar; pub fn main() {}"); + + p.cargo("publish") + .replace_crates_io(registry.index_url()) + .with_stderr( + "\ +[..] +[..] +[..] +[..] +[UPDATING] crates.io index +[VERIFYING] foo v0.0.1 ([CWD]) +[..] +[..] +[..] +[..] +[UPLOADING] foo v0.0.1 ([CWD]) +[UPLOADED] foo v0.0.1 to registry `crates-io` +note: Waiting [..] +You may press ctrl-c [..] +[PUBLISHED] foo v0.0.1 [..] +", + ) + .run(); + + publish::validate_upload( + r#" + { + "authors": [], + "badges": {}, + "categories": [], + "deps": [ + { + "default_features": true, + "features": [], + "kind": "normal", + "name": "bar", + "optional": false, + "target": null, + "version_req": "^1.0" + } + ], + "description": "foo", + "documentation": null, + "features": {}, + "homepage": null, + "keywords": [], + "license": "MIT", + "license_file": null, + "links": null, + "name": "foo", + "readme": null, + "readme_file": null, + "repository": null, + "vers": "0.0.1" + } + "#, + "foo-0.0.1.crate", + &["Cargo.lock", "Cargo.toml", "Cargo.toml.orig", "src/main.rs"], + ); +} + +#[cargo_test] +fn publish_checks_for_token_before_verify() { + let registry = registry::RegistryBuilder::new() + .no_configure_token() + .build(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + // Assert upload token error before the package is verified + p.cargo("publish") + .replace_crates_io(registry.index_url()) + .with_status(101) + .with_stderr_contains("[ERROR] no token found, please run `cargo login`") + .with_stderr_does_not_contain("[VERIFYING] foo v0.0.1 ([CWD])") + .run(); + + // Assert package verified successfully on dry run + p.cargo("publish --dry-run") + .replace_crates_io(registry.index_url()) + .with_stderr( + "\ +[..] +[..] +[..] +[..] +[VERIFYING] foo v0.0.1 ([CWD]) +[..] +[..] +[..] +[UPLOADING] foo v0.0.1 [..] +[WARNING] aborting upload due to dry run +", + ) + .run(); +} + +#[cargo_test] +fn publish_with_bad_source() { + let p = project() + .file( + ".cargo/config", + r#" + [source.crates-io] + replace-with = 'local-registry' + + [source.local-registry] + local-registry = 'registry' + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("publish") + .with_status(101) + .with_stderr( + "\ +[ERROR] crates-io is replaced with non-remote-registry source registry `[..]/foo/registry`; +include `--registry crates-io` to use crates.io +", + ) + .run(); + + p.change_file( + ".cargo/config", + r#" + [source.crates-io] + replace-with = "vendored-sources" + + [source.vendored-sources] + directory = "vendor" + "#, + ); + + p.cargo("publish") + .with_status(101) + .with_stderr( + "\ +[ERROR] crates-io is replaced with non-remote-registry source dir [..]/foo/vendor; +include `--registry crates-io` to use crates.io +", + ) + .run(); +} + +// A dependency with both `git` and `version`. +#[cargo_test] +fn publish_git_with_version() { + let registry = RegistryBuilder::new().http_api().http_index().build(); + + Package::new("dep1", "1.0.1") + .file("src/lib.rs", "pub fn f() -> i32 {1}") + .publish(); + + let git_project = git::new("dep1", |project| { + project + .file("Cargo.toml", &basic_manifest("dep1", "1.0.0")) + .file("src/lib.rs", "pub fn f() -> i32 {2}") + }); + + let p = project() + .file( + "Cargo.toml", + &format!( + r#" + [package] + name = "foo" + version = "0.1.0" + authors = [] + edition = "2018" + license = "MIT" + description = "foo" + + [dependencies] + dep1 = {{version = "1.0", git="{}"}} + "#, + git_project.url() + ), + ) + .file( + "src/main.rs", + r#" + pub fn main() { + println!("{}", dep1::f()); + } + "#, + ) + .build(); + + p.cargo("run").with_stdout("2").run(); + + p.cargo("publish --no-verify") + .replace_crates_io(registry.index_url()) + .with_stderr( + "\ +[..] +[..] +[..] +[..] +[..] +[..] +[UPLOADING] foo v0.1.0 ([CWD]) +[UPLOADED] foo v0.1.0 to registry `crates-io` +note: Waiting [..] +You may press ctrl-c [..] +[PUBLISHED] foo v0.1.0 [..] +", + ) + .run(); + + publish::validate_upload_with_contents( + r#" + { + "authors": [], + "badges": {}, + "categories": [], + "deps": [ + { + "default_features": true, + "features": [], + "kind": "normal", + "name": "dep1", + "optional": false, + "target": null, + "version_req": "^1.0" + } + ], + "description": "foo", + "documentation": null, + "features": {}, + "homepage": null, + "keywords": [], + "license": "MIT", + "license_file": null, + "links": null, + "name": "foo", + "readme": null, + "readme_file": null, + "repository": null, + "vers": "0.1.0" + } + "#, + "foo-0.1.0.crate", + &["Cargo.lock", "Cargo.toml", "Cargo.toml.orig", "src/main.rs"], + &[ + ( + "Cargo.toml", + // Check that only `version` is included in Cargo.toml. + &format!( + "{}\n\ + [package]\n\ + edition = \"2018\"\n\ + name = \"foo\"\n\ + version = \"0.1.0\"\n\ + authors = []\n\ + description = \"foo\"\n\ + license = \"MIT\"\n\ + \n\ + [dependencies.dep1]\n\ + version = \"1.0\"\n\ + ", + cargo::core::package::MANIFEST_PREAMBLE + ), + ), + ( + "Cargo.lock", + // The important check here is that it is 1.0.1 in the registry. + "# This file is automatically @generated by Cargo.\n\ + # It is not intended for manual editing.\n\ + version = 3\n\ + \n\ + [[package]]\n\ + name = \"dep1\"\n\ + version = \"1.0.1\"\n\ + source = \"registry+https://github.com/rust-lang/crates.io-index\"\n\ + checksum = \"[..]\"\n\ + \n\ + [[package]]\n\ + name = \"foo\"\n\ + version = \"0.1.0\"\n\ + dependencies = [\n\ + \x20\"dep1\",\n\ + ]\n\ + ", + ), + ], + ); +} + +#[cargo_test] +fn publish_dev_dep_no_version() { + let registry = RegistryBuilder::new().http_api().http_index().build(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + authors = [] + license = "MIT" + description = "foo" + documentation = "foo" + homepage = "foo" + repository = "foo" + + [dev-dependencies] + bar = { path = "bar" } + "#, + ) + .file("src/lib.rs", "") + .file("bar/Cargo.toml", &basic_manifest("bar", "0.0.1")) + .file("bar/src/lib.rs", "") + .build(); + + p.cargo("publish --no-verify") + .replace_crates_io(registry.index_url()) + .with_stderr( + "\ +[UPDATING] [..] +[PACKAGING] foo v0.1.0 [..] +[PACKAGED] [..] files, [..] ([..] compressed) +[UPLOADING] foo v0.1.0 [..] +[UPLOADED] foo v0.1.0 [..] +note: Waiting [..] +You may press ctrl-c [..] +[PUBLISHED] foo v0.1.0 [..] +", + ) + .run(); + + publish::validate_upload_with_contents( + r#" + { + "authors": [], + "badges": {}, + "categories": [], + "deps": [], + "description": "foo", + "documentation": "foo", + "features": {}, + "homepage": "foo", + "keywords": [], + "license": "MIT", + "license_file": null, + "links": null, + "name": "foo", + "readme": null, + "readme_file": null, + "repository": "foo", + "vers": "0.1.0" + } + "#, + "foo-0.1.0.crate", + &["Cargo.toml", "Cargo.toml.orig", "src/lib.rs"], + &[( + "Cargo.toml", + &format!( + r#"{} +[package] +name = "foo" +version = "0.1.0" +authors = [] +description = "foo" +homepage = "foo" +documentation = "foo" +license = "MIT" +repository = "foo" + +[dev-dependencies] +"#, + cargo::core::package::MANIFEST_PREAMBLE + ), + )], + ); +} + +#[cargo_test] +fn credentials_ambiguous_filename() { + // `publish` generally requires a remote registry + let registry = registry::RegistryBuilder::new().http_api().build(); + + // Make token in `credentials.toml` incorrect to ensure it is not read. + let credentials_toml = paths::home().join(".cargo/credentials.toml"); + fs::write(credentials_toml, r#"token = "wrong-token""#).unwrap(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("publish --no-verify") + .replace_crates_io(registry.index_url()) + .with_status(101) + .with_stderr_contains("[..]Unauthorized message from server[..]") + .run(); + + // Favor `credentials` if exists. + let credentials = paths::home().join(".cargo/credentials"); + fs::write(credentials, r#"token = "sekrit""#).unwrap(); + + p.cargo("publish --no-verify") + .replace_crates_io(registry.index_url()) + .with_stderr( + "\ +[..] +[WARNING] Both `[..]/credentials` and `[..]/credentials.toml` exist. Using `[..]/credentials` +[..] +[..] +[..] +[..] +[UPLOADING] foo v0.0.1 [..] +[UPLOADED] foo v0.0.1 [..] +note: Waiting [..] +You may press ctrl-c [..] +[PUBLISHED] foo v0.0.1 [..] +", + ) + .run(); +} + +// --index will not load registry.token to avoid possibly leaking +// crates.io token to another server. +#[cargo_test] +fn index_requires_token() { + // Use local registry for faster test times since no publish will occur + let registry = registry::init(); + + let credentials = paths::home().join(".cargo/credentials.toml"); + fs::remove_file(&credentials).unwrap(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("publish --no-verify --index") + .arg(registry.index_url().as_str()) + .with_status(101) + .with_stderr( + "\ +[ERROR] command-line argument --index requires --token to be specified +", + ) + .run(); +} + +// publish with source replacement without --registry +#[cargo_test] +fn cratesio_source_replacement() { + registry::init(); + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("publish --no-verify") + .with_status(101) + .with_stderr( + "\ +[ERROR] crates-io is replaced with remote registry dummy-registry; +include `--registry dummy-registry` or `--registry crates-io` +", + ) + .run(); +} + +#[cargo_test] +fn publish_with_missing_readme() { + // Use local registry for faster test times since no publish will occur + let registry = registry::init(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + authors = [] + license = "MIT" + description = "foo" + homepage = "https://example.com/" + readme = "foo.md" + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("publish --no-verify") + .replace_crates_io(registry.index_url()) + .with_status(101) + .with_stderr(&format!( + "\ +[UPDATING] [..] +[PACKAGING] foo v0.1.0 [..] +[PACKAGED] [..] files, [..] ([..] compressed) +[UPLOADING] foo v0.1.0 [..] +[ERROR] failed to read `readme` file for package `foo v0.1.0 ([ROOT]/foo)` + +Caused by: + failed to read `[ROOT]/foo/foo.md` + +Caused by: + {} +", + no_such_file_err_msg() + )) + .run(); +} + +// Registry returns an API error. +#[cargo_test] +fn api_error_json() { + let _registry = registry::RegistryBuilder::new() + .alternative() + .http_api() + .add_responder("/api/v1/crates/new", |_, _| Response { + body: br#"{"errors": [{"detail": "you must be logged in"}]}"#.to_vec(), + code: 403, + headers: vec![], + }) + .build(); + + 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/lib.rs", "") + .build(); + + p.cargo("publish --no-verify --registry alternative") + .with_status(101) + .with_stderr( + "\ +[UPDATING] [..] +[PACKAGING] foo v0.0.1 [..] +[PACKAGED] [..] files, [..] ([..] compressed) +[UPLOADING] foo v0.0.1 [..] +[ERROR] failed to publish to registry at http://127.0.0.1:[..]/ + +Caused by: + the remote server responded with an error (status 403 Forbidden): you must be logged in +", + ) + .run(); +} + +// Registry returns an API error with a 200 status code. +#[cargo_test] +fn api_error_200() { + let _registry = registry::RegistryBuilder::new() + .alternative() + .http_api() + .add_responder("/api/v1/crates/new", |_, _| Response { + body: br#"{"errors": [{"detail": "max upload size is 123"}]}"#.to_vec(), + code: 200, + headers: vec![], + }) + .build(); + + 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/lib.rs", "") + .build(); + + p.cargo("publish --no-verify --registry alternative") + .with_status(101) + .with_stderr( + "\ +[UPDATING] [..] +[PACKAGING] foo v0.0.1 [..] +[PACKAGED] [..] files, [..] ([..] compressed) +[UPLOADING] foo v0.0.1 [..] +[ERROR] failed to publish to registry at http://127.0.0.1:[..]/ + +Caused by: + the remote server responded with an error: max upload size is 123 +", + ) + .run(); +} + +// Registry returns an error code without a JSON message. +#[cargo_test] +fn api_error_code() { + let _registry = registry::RegistryBuilder::new() + .alternative() + .http_api() + .add_responder("/api/v1/crates/new", |_, _| Response { + body: br#"go away"#.to_vec(), + code: 400, + headers: vec![], + }) + .build(); + + 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/lib.rs", "") + .build(); + + p.cargo("publish --no-verify --registry alternative") + .with_status(101) + .with_stderr( + "\ +[UPDATING] [..] +[PACKAGING] foo v0.0.1 [..] +[PACKAGED] [..] files, [..] ([..] compressed) +[UPLOADING] foo v0.0.1 [..] +[ERROR] failed to publish to registry at http://127.0.0.1:[..]/ + +Caused by: + failed to get a 200 OK response, got 400 + headers: + HTTP/1.1 400 + Content-Length: 7 + + body: + go away +", + ) + .run(); +} + +// Registry has a network error. +#[cargo_test] +fn api_curl_error() { + let _registry = registry::RegistryBuilder::new() + .alternative() + .http_api() + .add_responder("/api/v1/crates/new", |_, _| { + panic!("broke"); + }) + .build(); + 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/lib.rs", "") + .build(); + + // This doesn't check for the exact text of the error in the remote + // possibility that cargo is linked with a weird version of libcurl, or + // curl changes the text of the message. Currently the message 52 + // (CURLE_GOT_NOTHING) is: + // Server returned nothing (no headers, no data) (Empty reply from server) + p.cargo("publish --no-verify --registry alternative") + .with_status(101) + .with_stderr( + "\ +[UPDATING] [..] +[PACKAGING] foo v0.0.1 [..] +[PACKAGED] [..] files, [..] ([..] compressed) +[UPLOADING] foo v0.0.1 [..] +[ERROR] failed to publish to registry at http://127.0.0.1:[..]/ + +Caused by: + [52] [..] +", + ) + .run(); +} + +// Registry returns an invalid response. +#[cargo_test] +fn api_other_error() { + let _registry = registry::RegistryBuilder::new() + .alternative() + .http_api() + .add_responder("/api/v1/crates/new", |_, _| Response { + body: b"\xff".to_vec(), + code: 200, + headers: vec![], + }) + .build(); + + 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/lib.rs", "") + .build(); + + p.cargo("publish --no-verify --registry alternative") + .with_status(101) + .with_stderr( + "\ +[UPDATING] [..] +[PACKAGING] foo v0.0.1 [..] +[PACKAGED] [..] files, [..] ([..] compressed) +[UPLOADING] foo v0.0.1 [..] +[ERROR] failed to publish to registry at http://127.0.0.1:[..]/ + +Caused by: + invalid response from server + +Caused by: + response body was not valid utf-8 +", + ) + .run(); +} + +#[cargo_test] +fn in_package_workspace() { + let registry = RegistryBuilder::new().http_api().http_index().build(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + edition = "2021" + [workspace] + members = ["li"] + "#, + ) + .file("src/main.rs", "fn main() {}") + .file( + "li/Cargo.toml", + r#" + [package] + name = "li" + version = "0.0.1" + description = "li" + license = "MIT" + "#, + ) + .file("li/src/main.rs", "fn main() {}") + .build(); + + p.cargo("publish -p li --no-verify") + .replace_crates_io(registry.index_url()) + .with_stderr( + "\ +[UPDATING] [..] +[WARNING] manifest has no documentation, homepage or repository. +See [..] +[PACKAGING] li v0.0.1 ([CWD]/li) +[PACKAGED] [..] files, [..] ([..] compressed) +[UPLOADING] li v0.0.1 ([CWD]/li) +[UPLOADED] li v0.0.1 to registry `crates-io` +note: Waiting [..] +You may press ctrl-c [..] +[PUBLISHED] li v0.0.1 [..] +", + ) + .run(); + + validate_upload_li(); +} + +#[cargo_test] +fn with_duplicate_spec_in_members() { + // Use local registry for faster test times since no publish will occur + let registry = registry::init(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + [workspace] + resolver = "2" + members = ["li","bar"] + default-members = ["li","bar"] + "#, + ) + .file("src/main.rs", "fn main() {}") + .file( + "li/Cargo.toml", + r#" + [package] + name = "li" + version = "0.0.1" + description = "li" + license = "MIT" + "#, + ) + .file("li/src/main.rs", "fn main() {}") + .file( + "bar/Cargo.toml", + r#" + [package] + name = "bar" + version = "0.0.1" + description = "bar" + license = "MIT" + "#, + ) + .file("bar/src/main.rs", "fn main() {}") + .build(); + + p.cargo("publish --no-verify") + .replace_crates_io(registry.index_url()) + .with_status(101) + .with_stderr( + "error: the `-p` argument must be specified to select a single package to publish", + ) + .run(); +} + +#[cargo_test] +fn in_package_workspace_with_members_with_features_old() { + let registry = RegistryBuilder::new().http_api().http_index().build(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + [workspace] + members = ["li"] + "#, + ) + .file("src/main.rs", "fn main() {}") + .file( + "li/Cargo.toml", + r#" + [package] + name = "li" + version = "0.0.1" + description = "li" + license = "MIT" + "#, + ) + .file("li/src/main.rs", "fn main() {}") + .build(); + + p.cargo("publish -p li --no-verify") + .replace_crates_io(registry.index_url()) + .with_stderr( + "\ +[UPDATING] [..] +[WARNING] manifest has no documentation, homepage or repository. +See [..] +[PACKAGING] li v0.0.1 ([CWD]/li) +[PACKAGED] [..] files, [..] ([..] compressed) +[UPLOADING] li v0.0.1 ([CWD]/li) +[UPLOADED] li v0.0.1 to registry `crates-io` +note: Waiting [..] +You may press ctrl-c [..] +[PUBLISHED] li v0.0.1 [..] +", + ) + .run(); + + validate_upload_li(); +} + +#[cargo_test] +fn in_virtual_workspace() { + // Use local registry for faster test times since no publish will occur + let registry = registry::init(); + + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = ["foo"] + "#, + ) + .file( + "foo/Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + "#, + ) + .file("foo/src/main.rs", "fn main() {}") + .build(); + + p.cargo("publish --no-verify") + .replace_crates_io(registry.index_url()) + .with_status(101) + .with_stderr( + "error: the `-p` argument must be specified in the root of a virtual workspace", + ) + .run(); +} + +#[cargo_test] +fn in_virtual_workspace_with_p() { + // `publish` generally requires a remote registry + let registry = registry::RegistryBuilder::new().http_api().build(); + + let p = project() + .file( + "Cargo.toml", + r#" + [workspace] + members = ["foo","li"] + "#, + ) + .file( + "foo/Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + "#, + ) + .file("foo/src/main.rs", "fn main() {}") + .file( + "li/Cargo.toml", + r#" + [package] + name = "li" + version = "0.0.1" + description = "li" + license = "MIT" + "#, + ) + .file("li/src/main.rs", "fn main() {}") + .build(); + + p.cargo("publish -p li --no-verify") + .replace_crates_io(registry.index_url()) + .with_stderr( + "\ +[UPDATING] [..] +[WARNING] manifest has no documentation, homepage or repository. +See [..] +[PACKAGING] li v0.0.1 ([CWD]/li) +[PACKAGED] [..] files, [..] ([..] compressed) +[UPLOADING] li v0.0.1 ([CWD]/li) +[UPLOADED] li v0.0.1 to registry `crates-io` +note: Waiting [..] +You may press ctrl-c [..] +[PUBLISHED] li v0.0.1 [..] +", + ) + .run(); +} + +#[cargo_test] +fn in_package_workspace_not_found() { + // Use local registry for faster test times since no publish will occur + let registry = registry::init(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + edition = "2021" + [workspace] + "#, + ) + .file("src/main.rs", "fn main() {}") + .file( + "li/Cargo.toml", + r#" + [package] + name = "li" + version = "0.0.1" + edition = "2021" + authors = [] + license = "MIT" + description = "li" + "#, + ) + .file("li/src/main.rs", "fn main() {}") + .build(); + + p.cargo("publish -p li --no-verify") + .replace_crates_io(registry.index_url()) + .with_status(101) + .with_stderr( + "\ +error: package ID specification `li` did not match any packages + +Did you mean `foo`? +", + ) + .run(); +} + +#[cargo_test] +fn in_package_workspace_found_multiple() { + // Use local registry for faster test times since no publish will occur + let registry = registry::init(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + edition = "2021" + [workspace] + members = ["li","lii"] + "#, + ) + .file("src/main.rs", "fn main() {}") + .file( + "li/Cargo.toml", + r#" + [package] + name = "li" + version = "0.0.1" + edition = "2021" + authors = [] + license = "MIT" + description = "li" + "#, + ) + .file("li/src/main.rs", "fn main() {}") + .file( + "lii/Cargo.toml", + r#" + [package] + name = "lii" + version = "0.0.1" + edition = "2021" + authors = [] + license = "MIT" + description = "lii" + "#, + ) + .file("lii/src/main.rs", "fn main() {}") + .build(); + + p.cargo("publish -p li* --no-verify") + .replace_crates_io(registry.index_url()) + .with_status(101) + .with_stderr( + "\ +error: the `-p` argument must be specified to select a single package to publish +", + ) + .run(); +} + +#[cargo_test] +// https://github.com/rust-lang/cargo/issues/10536 +fn publish_path_dependency_without_workspace() { + // Use local registry for faster test times since no publish will occur + let registry = registry::init(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.1.0" + edition = "2021" + [dependencies.bar] + path = "bar" + "#, + ) + .file("src/main.rs", "fn main() {}") + .file( + "bar/Cargo.toml", + r#" + [package] + name = "bar" + version = "0.0.1" + edition = "2021" + authors = [] + license = "MIT" + description = "bar" + "#, + ) + .file("bar/src/main.rs", "fn main() {}") + .build(); + + p.cargo("publish -p bar --no-verify") + .replace_crates_io(registry.index_url()) + .with_status(101) + .with_stderr( + "\ +error: package ID specification `bar` did not match any packages + +Did you mean `foo`? +", + ) + .run(); +} + +#[cargo_test] +fn http_api_not_noop() { + let registry = registry::RegistryBuilder::new().http_api().build(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("publish") + .replace_crates_io(registry.index_url()) + .with_stderr( + "\ +[..] +[..] +[..] +[..] +[VERIFYING] foo v0.0.1 ([CWD]) +[..] +[..] +[..] +[UPLOADING] foo v0.0.1 ([CWD]) +[UPLOADED] foo v0.0.1 to registry `crates-io` +note: Waiting [..] +You may press ctrl-c [..] +[PUBLISHED] foo v0.0.1 [..] +", + ) + .run(); + + let p = project() + .file( + "Cargo.toml", + r#" + [project] + name = "bar" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + + [dependencies] + foo = "0.0.1" + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("build").run(); +} + +#[cargo_test] +fn wait_for_first_publish() { + // Counter for number of tries before the package is "published" + let arc: Arc> = Arc::new(Mutex::new(0)); + let arc2 = arc.clone(); + + // Registry returns an invalid response. + let registry = registry::RegistryBuilder::new() + .http_index() + .http_api() + .add_responder("/index/de/la/delay", move |req, server| { + let mut lock = arc.lock().unwrap(); + *lock += 1; + if *lock <= 1 { + server.not_found(req) + } else { + server.index(req) + } + }) + .build(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "delay" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("publish --no-verify") + .replace_crates_io(registry.index_url()) + .with_status(0) + .with_stderr( + "\ +[UPDATING] crates.io index +[WARNING] manifest has no documentation, [..] +See [..] +[PACKAGING] delay v0.0.1 ([CWD]) +[PACKAGED] [..] files, [..] ([..] compressed) +[UPLOADING] delay v0.0.1 ([CWD]) +[UPLOADED] delay v0.0.1 to registry `crates-io` +note: Waiting for `delay v0.0.1` to be available at registry `crates-io`. +You may press ctrl-c to skip waiting; the crate should be available shortly. +[PUBLISHED] delay v0.0.1 at registry `crates-io` +", + ) + .run(); + + // Verify the responder has been pinged + let lock = arc2.lock().unwrap(); + assert_eq!(*lock, 2); + drop(lock); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + [dependencies] + delay = "0.0.1" + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("build").with_status(0).run(); +} + +/// A separate test is needed for package names with - or _ as they hit +/// the responder twice per cargo invocation. If that ever gets changed +/// this test will need to be changed accordingly. +#[cargo_test] +fn wait_for_first_publish_underscore() { + // Counter for number of tries before the package is "published" + let arc: Arc> = Arc::new(Mutex::new(0)); + let arc2 = arc.clone(); + let misses = Arc::new(Mutex::new(Vec::new())); + let misses2 = misses.clone(); + + // Registry returns an invalid response. + let registry = registry::RegistryBuilder::new() + .http_index() + .http_api() + .add_responder("/index/de/la/delay_with_underscore", move |req, server| { + let mut lock = arc.lock().unwrap(); + *lock += 1; + if *lock <= 1 { + server.not_found(req) + } else { + server.index(req) + } + }) + .not_found_handler(move |req, _| { + misses.lock().unwrap().push(req.url.to_string()); + Response { + body: b"not found".to_vec(), + code: 404, + headers: vec![], + } + }) + .build(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "delay_with_underscore" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("publish --no-verify") + .replace_crates_io(registry.index_url()) + .with_status(0) + .with_stderr( + "\ +[UPDATING] crates.io index +[WARNING] manifest has no documentation, [..] +See [..] +[PACKAGING] delay_with_underscore v0.0.1 ([CWD]) +[PACKAGED] [..] files, [..] ([..] compressed) +[UPLOADING] delay_with_underscore v0.0.1 ([CWD]) +[UPLOADED] delay_with_underscore v0.0.1 to registry `crates-io` +note: Waiting for `delay_with_underscore v0.0.1` to be available at registry `crates-io`. +You may press ctrl-c to skip waiting; the crate should be available shortly. +[PUBLISHED] delay_with_underscore v0.0.1 at registry `crates-io` +", + ) + .run(); + + // Verify the repsponder has been pinged + let lock = arc2.lock().unwrap(); + assert_eq!(*lock, 2); + drop(lock); + { + let misses = misses2.lock().unwrap(); + assert!( + misses.len() == 1, + "should only have 1 not found URL; instead found {misses:?}" + ); + } + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + [dependencies] + delay_with_underscore = "0.0.1" + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("build").with_status(0).run(); +} + +#[cargo_test] +fn wait_for_subsequent_publish() { + // Counter for number of tries before the package is "published" + let arc: Arc> = Arc::new(Mutex::new(0)); + let arc2 = arc.clone(); + let publish_req = Arc::new(Mutex::new(None)); + let publish_req2 = publish_req.clone(); + + let registry = registry::RegistryBuilder::new() + .http_index() + .http_api() + .add_responder("/api/v1/crates/new", move |req, server| { + // Capture the publish request, but defer publishing + *publish_req.lock().unwrap() = Some(req.clone()); + server.ok(req) + }) + .add_responder("/index/de/la/delay", move |req, server| { + let mut lock = arc.lock().unwrap(); + *lock += 1; + if *lock == 3 { + // Run the publish on the 3rd attempt + let rep = server + .check_authorized_publish(&publish_req2.lock().unwrap().as_ref().unwrap()); + assert_eq!(rep.code, 200); + } + server.index(req) + }) + .build(); + + // Publish an earlier version + Package::new("delay", "0.0.1") + .file("src/lib.rs", "") + .publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "delay" + version = "0.0.2" + authors = [] + license = "MIT" + description = "foo" + + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("publish --no-verify") + .replace_crates_io(registry.index_url()) + .with_status(0) + .with_stderr( + "\ +[UPDATING] crates.io index +[WARNING] manifest has no documentation, [..] +See [..] +[PACKAGING] delay v0.0.2 ([CWD]) +[PACKAGED] [..] files, [..] ([..] compressed) +[UPLOADING] delay v0.0.2 ([CWD]) +[UPLOADED] delay v0.0.2 to registry `crates-io` +note: Waiting for `delay v0.0.2` to be available at registry `crates-io`. +You may press ctrl-c to skip waiting; the crate should be available shortly. +[PUBLISHED] delay v0.0.2 at registry `crates-io` +", + ) + .run(); + + // Verify the responder has been pinged + let lock = arc2.lock().unwrap(); + assert_eq!(*lock, 3); + drop(lock); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + [dependencies] + delay = "0.0.2" + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("check").with_status(0).run(); +} + +#[cargo_test] +fn skip_wait_for_publish() { + // Intentionally using local registry so the crate never makes it to the index + let registry = registry::init(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + "#, + ) + .file("src/main.rs", "fn main() {}") + .file( + ".cargo/config", + " + [publish] + timeout = 0 + ", + ) + .build(); + + p.cargo("publish --no-verify -Zpublish-timeout") + .replace_crates_io(registry.index_url()) + .masquerade_as_nightly_cargo(&["publish-timeout"]) + .with_stderr( + "\ +[UPDATING] crates.io index +[WARNING] manifest has no documentation, [..] +See [..] +[PACKAGING] foo v0.0.1 ([CWD]) +[PACKAGED] [..] files, [..] ([..] compressed) +[UPLOADING] foo v0.0.1 ([CWD]) +", + ) + .run(); +} + +#[cargo_test] +fn timeout_waiting_for_publish() { + // Publish doesn't happen within the timeout window. + let registry = registry::RegistryBuilder::new() + .http_api() + .delayed_index_update(20) + .build(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "delay" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + "#, + ) + .file("src/lib.rs", "") + .file( + ".cargo/config.toml", + r#" + [publish] + timeout = 2 + "#, + ) + .build(); + + p.cargo("publish --no-verify -Zpublish-timeout") + .replace_crates_io(registry.index_url()) + .masquerade_as_nightly_cargo(&["publish-timeout"]) + .with_status(0) + .with_stderr( + "\ +[UPDATING] crates.io index +[WARNING] manifest has no documentation, [..] +See [..] +[PACKAGING] delay v0.0.1 ([CWD]) +[PACKAGED] [..] files, [..] ([..] compressed) +[UPLOADING] delay v0.0.1 ([CWD]) +[UPLOADED] delay v0.0.1 to registry `crates-io` +note: Waiting for `delay v0.0.1` to be available at registry `crates-io`. +You may press ctrl-c to skip waiting; the crate should be available shortly. +warning: timed out waiting for `delay v0.0.1` to be available in registry `crates-io` +note: The registry may have a backlog that is delaying making the crate available. The crate should be available soon. +", + ) + .run(); +} + +#[cargo_test] +fn wait_for_git_publish() { + // Slow publish to an index with a git index. + let registry = registry::RegistryBuilder::new() + .http_api() + .delayed_index_update(5) + .build(); + + // Publish an earlier version + Package::new("delay", "0.0.1") + .file("src/lib.rs", "") + .publish(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "delay" + version = "0.0.2" + authors = [] + license = "MIT" + description = "foo" + "#, + ) + .file("src/lib.rs", "") + .build(); + + p.cargo("publish --no-verify") + .replace_crates_io(registry.index_url()) + .with_status(0) + .with_stderr( + "\ +[UPDATING] crates.io index +[WARNING] manifest has no documentation, [..] +See [..] +[PACKAGING] delay v0.0.2 ([CWD]) +[PACKAGED] [..] files, [..] ([..] compressed) +[UPLOADING] delay v0.0.2 ([CWD]) +[UPLOADED] delay v0.0.2 to registry `crates-io` +note: Waiting for `delay v0.0.2` to be available at registry `crates-io`. +You may press ctrl-c to skip waiting; the crate should be available shortly. +[PUBLISHED] delay v0.0.2 at registry `crates-io` +", + ) + .run(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + [dependencies] + delay = "0.0.2" + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("check").with_status(0).run(); +} + +#[cargo_test] +fn invalid_token() { + // Checks publish behavior with an invalid token. + let registry = RegistryBuilder::new().http_api().http_index().build(); + + let p = project() + .file( + "Cargo.toml", + r#" + [package] + name = "foo" + version = "0.0.1" + authors = [] + license = "MIT" + description = "foo" + documentation = "foo" + "#, + ) + .file("src/main.rs", "fn main() {}") + .build(); + + p.cargo("publish --no-verify") + .replace_crates_io(registry.index_url()) + .env("CARGO_REGISTRY_TOKEN", "\x16") + .with_stderr( + "\ +[UPDATING] crates.io index +[PACKAGING] foo v0.0.1 ([ROOT]/foo) +[PACKAGED] 4 files, [..] +[UPLOADING] foo v0.0.1 ([ROOT]/foo) +error: failed to publish to registry at http://127.0.0.1:[..]/ + +Caused by: + token contains invalid characters. + Only printable ISO-8859-1 characters are allowed as it is sent in a HTTPS header. +", + ) + .with_status(101) + .run(); +} -- cgit v1.2.3