use flate2::write::GzEncoder;
use flate2::Compression;
use std::collections::HashMap;
+use std::fmt::Write as _;
use std::fs::{self, File};
-use std::io::prelude::*;
+use std::io::{BufRead, BufReader, Write};
+use std::net::TcpListener;
use std::path::{Path, PathBuf};
+use std::thread;
use tar::{Builder, Header};
use url::Url;
format!("{}/{{crate}}/{{version}}/{{crate}}-{{version}}.crate", base)
}
+/// A builder for initializing registries.
+pub struct RegistryBuilder {
+ /// If `true`, adds source replacement for crates.io to a registry on the filesystem.
+ replace_crates_io: bool,
+ /// If `true`, configures a registry named "alternative".
+ alternative: bool,
+ /// If set, sets the API url for the "alternative" registry.
+ /// This defaults to a directory on the filesystem.
+ alt_api_url: Option<String>,
+ /// If `true`, configures `.cargo/credentials` with some tokens.
+ add_tokens: bool,
+}
+
+impl RegistryBuilder {
+ pub fn new() -> RegistryBuilder {
+ RegistryBuilder {
+ replace_crates_io: true,
+ alternative: false,
+ alt_api_url: None,
+ add_tokens: true,
+ }
+ }
+
+ /// Sets whether or not to replace crates.io with a registry on the filesystem.
+ /// Default is `true`.
+ pub fn replace_crates_io(&mut self, replace: bool) -> &mut Self {
+ self.replace_crates_io = replace;
+ self
+ }
+
+ /// Sets whether or not to initialize an alternative registry named "alternative".
+ /// Default is `false`.
+ pub fn alternative(&mut self, alt: bool) -> &mut Self {
+ self.alternative = alt;
+ self
+ }
+
+ /// Sets the API url for the "alternative" registry.
+ /// Defaults to a path on the filesystem ([`alt_api_path`]).
+ pub fn alternative_api_url(&mut self, url: &str) -> &mut Self {
+ self.alternative = true;
+ self.alt_api_url = Some(url.to_string());
+ self
+ }
+
+ /// Sets whether or not to initialize `.cargo/credentials` with some tokens.
+ /// Defaults to `true`.
+ pub fn add_tokens(&mut self, add: bool) -> &mut Self {
+ self.add_tokens = add;
+ self
+ }
+
+ /// Initializes the registries.
+ pub fn build(&self) {
+ let config_path = paths::home().join(".cargo/config");
+ if config_path.exists() {
+ panic!(
+ "{} already exists, the registry may only be initialized once, \
+ and must be done before the config file is created",
+ config_path.display()
+ );
+ }
+ t!(fs::create_dir_all(config_path.parent().unwrap()));
+ let mut config = String::new();
+ if self.replace_crates_io {
+ write!(
+ &mut config,
+ "
+ [source.crates-io]
+ replace-with = 'dummy-registry'
+
+ [source.dummy-registry]
+ registry = '{}'
+ ",
+ registry_url()
+ )
+ .unwrap();
+ }
+ if self.alternative {
+ write!(
+ config,
+ "
+ [registries.alternative]
+ index = '{}'
+ ",
+ alt_registry_url()
+ )
+ .unwrap();
+ }
+ t!(fs::write(&config_path, config));
+
+ if self.add_tokens {
+ let credentials = paths::home().join(".cargo/credentials");
+ t!(fs::write(
+ &credentials,
+ r#"
+ [registry]
+ token = "api-token"
+
+ [registries.alternative]
+ token = "api-token"
+ "#
+ ));
+ }
+
+ if self.replace_crates_io {
+ init_registry(
+ registry_path(),
+ dl_url().into_string(),
+ api_url(),
+ api_path(),
+ );
+ }
+
+ if self.alternative {
+ init_registry(
+ alt_registry_path(),
+ alt_dl_url(),
+ self.alt_api_url
+ .as_ref()
+ .map_or_else(alt_api_url, |url| Url::parse(&url).expect("valid url")),
+ alt_api_path(),
+ );
+ }
+ }
+
+ /// Initializes the registries, and sets up an HTTP server for the
+ /// "alternative" registry.
+ ///
+ /// The given callback takes a `Vec` of headers when a request comes in.
+ /// The first entry should be the HTTP command, such as
+ /// `PUT /api/v1/crates/new HTTP/1.1`.
+ ///
+ /// The callback should return the HTTP code for the response, and the
+ /// response body.
+ ///
+ /// This method returns a `JoinHandle` which you should call
+ /// `.join().unwrap()` on before exiting the test.
+ pub fn build_api_server<'a>(
+ &mut self,
+ handler: &'static (dyn (Fn(Vec<String>) -> (u32, &'a dyn AsRef<[u8]>)) + Sync),
+ ) -> thread::JoinHandle<()> {
+ let server = TcpListener::bind("127.0.0.1:0").unwrap();
+ let addr = server.local_addr().unwrap();
+ let api_url = format!("http://{}", addr);
+
+ self.replace_crates_io(false)
+ .alternative_api_url(&api_url)
+ .build();
+
+ let t = thread::spawn(move || {
+ let mut conn = BufReader::new(server.accept().unwrap().0);
+ let headers: Vec<_> = (&mut conn)
+ .lines()
+ .map(|s| s.unwrap())
+ .take_while(|s| s.len() > 2)
+ .map(|s| s.trim().to_string())
+ .collect();
+ let (code, response) = handler(headers);
+ let response = response.as_ref();
+ let stream = conn.get_mut();
+ write!(
+ stream,
+ "HTTP/1.1 {}\r\n\
+ Content-Length: {}\r\n\
+ \r\n",
+ code,
+ response.len()
+ )
+ .unwrap();
+ stream.write_all(response).unwrap();
+ });
+
+ t
+ }
+}
+
/// A builder for creating a new package in a registry.
///
/// This uses "source replacement" using an automatically generated
optional: bool,
}
+/// Initializes the on-disk registry and sets up the config so that crates.io
+/// is replaced with the one on disk.
pub fn init() {
let config = paths::home().join(".cargo/config");
- t!(fs::create_dir_all(config.parent().unwrap()));
if config.exists() {
return;
}
- t!(fs::write(
- &config,
- format!(
- r#"
- [source.crates-io]
- registry = 'https://wut'
- replace-with = 'dummy-registry'
-
- [source.dummy-registry]
- registry = '{reg}'
-
- [registries.alternative]
- index = '{alt}'
- "#,
- reg = registry_url(),
- alt = alt_registry_url()
- )
- ));
- let credentials = paths::home().join(".cargo/credentials");
- t!(fs::write(
- &credentials,
- r#"
- [registry]
- token = "api-token"
-
- [registries.alternative]
- token = "api-token"
- "#
- ));
+ RegistryBuilder::new().build();
+}
- // Initialize a new registry.
- init_registry(
- registry_path(),
- dl_url().into_string(),
- api_url(),
- api_path(),
- );
-
- // Initialize an alternative registry.
- init_registry(
- alt_registry_path(),
- alt_dl_url(),
- alt_api_url(),
- alt_api_path(),
- );
+/// Variant of `init` that initializes the "alternative" registry.
+pub fn alt_init() {
+ RegistryBuilder::new().alternative(true).build();
}
+/// Creates a new on-disk registry.
pub fn init_registry(registry_path: PathBuf, dl_url: String, api_url: Url, api_path: PathBuf) {
// Initialize a new registry.
repo(®istry_path)
.file(
"config.json",
- &format!(
- r#"
- {{"dl":"{}","api":"{}"}}
- "#,
- dl_url, api_url
- ),
+ &format!(r#"{{"dl":"{}","api":"{}"}}"#, dl_url, api_url),
)
.build();
fs::create_dir_all(api_path.join("api/v1/crates")).unwrap();
#[cargo_test]
fn depend_on_alt_registry() {
+ registry::alt_init();
let p = project()
.file(
"Cargo.toml",
#[cargo_test]
fn depend_on_alt_registry_depends_on_same_registry_no_index() {
+ registry::alt_init();
let p = project()
.file(
"Cargo.toml",
#[cargo_test]
fn depend_on_alt_registry_depends_on_same_registry() {
+ registry::alt_init();
let p = project()
.file(
"Cargo.toml",
#[cargo_test]
fn depend_on_alt_registry_depends_on_crates_io() {
+ registry::alt_init();
let p = project()
.file(
"Cargo.toml",
#[cargo_test]
fn registry_and_path_dep_works() {
- registry::init();
+ registry::alt_init();
let p = project()
.file(
#[cargo_test]
fn registry_incompatible_with_git() {
- registry::init();
+ registry::alt_init();
let p = project()
.file(
#[cargo_test]
fn cannot_publish_to_crates_io_with_registry_dependency() {
+ registry::alt_init();
let fakeio_path = paths::root().join("fake.io");
let fakeio_url = fakeio_path.into_url().unwrap();
let p = project()
#[cargo_test]
fn publish_with_registry_dependency() {
+ registry::alt_init();
let p = project()
.file(
"Cargo.toml",
#[cargo_test]
fn alt_registry_and_crates_io_deps() {
+ registry::alt_init();
let p = project()
.file(
"Cargo.toml",
#[cargo_test]
fn block_publish_due_to_no_token() {
- registry::init();
+ registry::alt_init();
let p = project().file("src/lib.rs", "").build();
fs::remove_file(paths::home().join(".cargo/credentials")).unwrap();
#[cargo_test]
fn publish_to_alt_registry() {
+ registry::alt_init();
let p = project().file("src/main.rs", "fn main() {}").build();
// Setup the registry by publishing a package
#[cargo_test]
fn publish_with_crates_io_dep() {
+ registry::alt_init();
let p = project()
.file(
"Cargo.toml",
#[cargo_test]
fn passwords_in_registries_index_url_forbidden() {
- registry::init();
+ registry::alt_init();
let config = paths::home().join(".cargo/config");
#[cargo_test]
fn patch_alt_reg() {
+ registry::alt_init();
Package::new("bar", "0.1.0").publish();
let p = project()
.file(
#[cargo_test]
fn no_api() {
+ registry::alt_init();
Package::new("bar", "0.0.1").alternative(true).publish();
// Configure without `api`.
let repo = git2::Repository::open(registry::alt_registry_path()).unwrap();
#[cargo_test]
fn alt_reg_metadata() {
// Check for "registry" entries in `cargo metadata` with alternative registries.
+ registry::alt_init();
let p = project()
.file(
"Cargo.toml",
fn unknown_registry() {
// A known registry refers to an unknown registry.
// foo -> bar(crates.io) -> baz(alt)
+ registry::alt_init();
let p = project()
.file(
"Cargo.toml",
#[cargo_test]
fn registries_index_relative_url() {
+ registry::alt_init();
let config = paths::root().join(".cargo/config");
fs::create_dir_all(config.parent().unwrap()).unwrap();
fs::write(
#[cargo_test]
fn registries_index_relative_path_not_allowed() {
+ registry::alt_init();
let config = paths::root().join(".cargo/config");
fs::create_dir_all(config.parent().unwrap()).unwrap();
fs::write(
//! Tests for credential-process.
-use cargo_test_support::paths::CargoPathExt;
use cargo_test_support::{basic_manifest, cargo_process, paths, project, registry, Project};
use std::fs;
-use std::io::{BufRead, BufReader, Write};
-use std::net::TcpListener;
use std::thread;
-use url::Url;
fn toml_bin(proj: &Project, name: &str) -> String {
proj.bin(name).display().to_string().replace('\\', "\\\\")
#[cargo_test]
fn gated() {
- registry::init();
-
- paths::home().join(".cargo/credentials").rm_rf();
+ registry::RegistryBuilder::new()
+ .alternative(true)
+ .add_tokens(false)
+ .build();
let p = project()
.file(
#[cargo_test]
fn warn_both_token_and_process() {
// Specifying both credential-process and a token in config should issue a warning.
- registry::init();
- paths::home().join(".cargo/credentials").rm_rf();
+ registry::RegistryBuilder::new()
+ .alternative(true)
+ .add_tokens(false)
+ .build();
let p = project()
.file(
".cargo/config",
/// Returns a thread handle for the API server, the test should join it when
/// finished. Also returns the simple `foo` project to test against.
fn get_token_test() -> (Project, thread::JoinHandle<()>) {
- let server = TcpListener::bind("127.0.0.1:0").unwrap();
- let addr = server.local_addr().unwrap();
- let api_url = format!("http://{}", addr);
-
- registry::init_registry(
- registry::alt_registry_path(),
- registry::alt_dl_url(),
- Url::parse(&api_url).unwrap(),
- registry::alt_api_path(),
- );
-
// API server that checks that the token is included correctly.
- let t = thread::spawn(move || {
- let mut conn = BufReader::new(server.accept().unwrap().0);
- let headers: Vec<_> = (&mut conn)
- .lines()
- .map(|s| s.unwrap())
- .take_while(|s| s.len() > 2)
- .map(|s| s.trim().to_string())
- .collect();
- assert!(headers
- .iter()
- .any(|header| header == "Authorization: sekrit"));
- conn.get_mut()
- .write_all(
- b"HTTP/1.1 200\r\n\
- Content-Length: 33\r\n\
- \r\n\
- {\"ok\": true, \"msg\": \"completed!\"}\r\n",
- )
- .unwrap();
- });
+ let server = registry::RegistryBuilder::new()
+ .add_tokens(false)
+ .build_api_server(&|headers| {
+ assert!(headers
+ .iter()
+ .any(|header| header == "Authorization: sekrit"));
+
+ (200, &r#"{"ok": true, "msg": "completed!"}"#)
+ });
// The credential process to use.
let cred_proj = project()
)
.file("src/lib.rs", "")
.build();
- (p, t)
+ (p, server)
}
#[cargo_test]
#[cargo_test]
fn basic_unsupported() {
// Non-action commands don't support login/logout.
- registry::init();
- // If both `credential-process` and `token` are specified, it will ignore
- // `credential-process`, so remove the default tokens.
- paths::home().join(".cargo/credentials").rm_rf();
+ registry::RegistryBuilder::new().add_tokens(false).build();
cargo::util::paths::append(
&paths::home().join(".cargo/config"),
br#"
#[cargo_test]
fn logout() {
- registry::init();
- // If both `credential-process` and `token` are specified, it will ignore
- // `credential-process`, so remove the default tokens.
- paths::home().join(".cargo/credentials").rm_rf();
+ registry::RegistryBuilder::new().add_tokens(false).build();
// The credential process to use.
let cred_proj = project()
.at("cred_proj")
#[cargo_test]
fn libexec_path() {
// cargo: prefixed names use the sysroot
- registry::init();
-
- paths::home().join(".cargo/credentials").rm_rf();
+ registry::RegistryBuilder::new().add_tokens(false).build();
cargo::util::paths::append(
&paths::home().join(".cargo/config"),
br#"
#[cargo_test]
fn invalid_token_output() {
// Error when credential process does not output the expected format for a token.
- registry::init();
- paths::home().join(".cargo/credentials").rm_rf();
+ registry::RegistryBuilder::new()
+ .alternative(true)
+ .add_tokens(false)
+ .build();
let cred_proj = project()
.at("cred_proj")
.file("Cargo.toml", &basic_manifest("test-cred", "1.0.0"))
use cargo_test_support::install::{cargo_home, exe};
use cargo_test_support::paths::CargoPathExt;
-use cargo_test_support::registry::Package;
+use cargo_test_support::registry::{self, Package};
use cargo_test_support::{
basic_manifest, cargo_process, cross_compile, execs, git, process, project, Execs,
};
fn switch_sources() {
// Installing what appears to be the same thing, but from different
// sources should reinstall.
+ registry::alt_init();
pkg("foo", "1.0.0");
Package::new("foo", "1.0.0")
.file("src/main.rs", r#"fn main() { println!("alt"); }"#)
#[cargo_test]
fn registry_credentials() {
- registry::init();
+ registry::alt_init();
let config = paths::home().join(".cargo/config");
let mut f = OpenOptions::new().append(true).open(config).unwrap();
#[cargo_test]
fn other_registry() {
+ registry::alt_init();
simple_logout_test(Some("alternative"), "--registry alternative");
}
#[cargo_test]
fn generated_manifest() {
+ 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();
use cargo_test_support::git;
use cargo_test_support::paths;
-use cargo_test_support::registry::Package;
+use cargo_test_support::registry::{self, Package};
use cargo_test_support::{basic_manifest, project};
use std::fs;
#[cargo_test]
fn too_many_matches() {
// The patch locations has multiple versions that match.
+ registry::alt_init();
Package::new("bar", "0.1.0").publish();
Package::new("bar", "0.1.0").alternative(true).publish();
Package::new("bar", "0.1.1").alternative(true).publish();
fn patch_update_doesnt_update_other_sources() {
// Very extreme edge case, make sure a patch update doesn't update other
// sources.
+ registry::alt_init();
Package::new("bar", "0.1.0").publish();
Package::new("bar", "0.1.0").alternative(true).publish();
#[cargo_test]
fn can_update_with_alt_reg() {
// A patch to an alt reg can update.
+ registry::alt_init();
Package::new("bar", "0.1.0").publish();
Package::new("bar", "0.1.0").alternative(true).publish();
Package::new("bar", "0.1.1").alternative(true).publish();
#[cargo_test]
fn publish_allowed_registry() {
- registry::init();
+ registry::alt_init();
let p = project().build();
#[cargo_test]
fn publish_implicitly_to_only_allowed_registry() {
- registry::init();
+ registry::alt_init();
let p = project().build();
use cargo_test_support::git;
use cargo_test_support::paths;
-use cargo_test_support::registry::Package;
+use cargo_test_support::registry::{self, Package};
use cargo_test_support::{basic_manifest, project};
#[cargo_test]
#[cargo_test]
fn lots_of_names() {
+ registry::alt_init();
Package::new("foo", "0.1.0")
.file("src/lib.rs", "pub fn foo1() {}")
.publish();
//! Tests for the -Zrustdoc-map feature.
-use cargo_test_support::registry::Package;
+use cargo_test_support::registry::{self, Package};
use cargo_test_support::{is_nightly, paths, project, Project};
fn basic_project() -> Project {
// --extern-html-root-url is unstable
return;
}
+ registry::alt_init();
Package::new("bar", "1.0.0")
.alternative(true)
.file(
use std::fs;
use cargo_test_support::git;
-use cargo_test_support::registry::Package;
+use cargo_test_support::registry::{self, Package};
use cargo_test_support::{basic_lib_manifest, paths, project, Project};
#[cargo_test]
#[cargo_test]
fn config_instructions_works() {
// Check that the config instructions work for all dependency kinds.
+ registry::alt_init();
Package::new("dep", "0.1.0").publish();
Package::new("altdep", "0.1.0").alternative(true).publish();
let git_project = git::new("gitdep", |project| {