Add RegistryBuilder to help initializing test registries.

The intent here is to make it more flexible to create different registry
setups, and to reuse code a little more easily.
This commit is contained in:
Eric Huss 2021-01-28 11:54:27 -08:00
parent e099df243b
commit 340656e29d
12 changed files with 252 additions and 119 deletions

View file

@ -5,9 +5,12 @@ use cargo::util::Sha256;
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;
@ -70,6 +73,183 @@ pub fn generate_alt_dl_url(name: &str) -> String {
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
@ -162,70 +342,28 @@ pub struct Dependency {
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"
"#
));
// 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(),
);
RegistryBuilder::new().build();
}
/// 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(&registry_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();

View file

@ -8,6 +8,7 @@ use std::fs;
#[cargo_test]
fn depend_on_alt_registry() {
registry::alt_init();
let p = project()
.file(
"Cargo.toml",
@ -57,6 +58,7 @@ fn depend_on_alt_registry() {
#[cargo_test]
fn depend_on_alt_registry_depends_on_same_registry_no_index() {
registry::alt_init();
let p = project()
.file(
"Cargo.toml",
@ -99,6 +101,7 @@ fn depend_on_alt_registry_depends_on_same_registry_no_index() {
#[cargo_test]
fn depend_on_alt_registry_depends_on_same_registry() {
registry::alt_init();
let p = project()
.file(
"Cargo.toml",
@ -141,6 +144,7 @@ fn depend_on_alt_registry_depends_on_same_registry() {
#[cargo_test]
fn depend_on_alt_registry_depends_on_crates_io() {
registry::alt_init();
let p = project()
.file(
"Cargo.toml",
@ -185,7 +189,7 @@ fn depend_on_alt_registry_depends_on_crates_io() {
#[cargo_test]
fn registry_and_path_dep_works() {
registry::init();
registry::alt_init();
let p = project()
.file(
@ -219,7 +223,7 @@ fn registry_and_path_dep_works() {
#[cargo_test]
fn registry_incompatible_with_git() {
registry::init();
registry::alt_init();
let p = project()
.file(
@ -249,6 +253,7 @@ fn registry_incompatible_with_git() {
#[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()
@ -307,6 +312,7 @@ fn cannot_publish_to_crates_io_with_registry_dependency() {
#[cargo_test]
fn publish_with_registry_dependency() {
registry::alt_init();
let p = project()
.file(
"Cargo.toml",
@ -370,6 +376,7 @@ fn publish_with_registry_dependency() {
#[cargo_test]
fn alt_registry_and_crates_io_deps() {
registry::alt_init();
let p = project()
.file(
"Cargo.toml",
@ -415,7 +422,7 @@ fn alt_registry_and_crates_io_deps() {
#[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();
@ -432,6 +439,7 @@ fn block_publish_due_to_no_token() {
#[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
@ -472,6 +480,7 @@ fn publish_to_alt_registry() {
#[cargo_test]
fn publish_with_crates_io_dep() {
registry::alt_init();
let p = project()
.file(
"Cargo.toml",
@ -537,7 +546,7 @@ fn publish_with_crates_io_dep() {
#[cargo_test]
fn passwords_in_registries_index_url_forbidden() {
registry::init();
registry::alt_init();
let config = paths::home().join(".cargo/config");
@ -567,6 +576,7 @@ Caused by:
#[cargo_test]
fn patch_alt_reg() {
registry::alt_init();
Package::new("bar", "0.1.0").publish();
let p = project()
.file(
@ -656,6 +666,7 @@ Caused by:
#[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();
@ -739,6 +750,7 @@ fn no_api() {
#[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",
@ -1033,6 +1045,7 @@ fn alt_reg_metadata() {
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",
@ -1188,6 +1201,7 @@ fn unknown_registry() {
#[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(
@ -1237,6 +1251,7 @@ fn registries_index_relative_url() {
#[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(

View file

@ -1,12 +1,8 @@
//! 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('\\', "\\\\")
@ -14,9 +10,10 @@ fn toml_bin(proj: &Project, name: &str) -> String {
#[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(
@ -64,8 +61,10 @@ fn gated() {
#[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",
@ -138,38 +137,16 @@ Only one of these values may be set, remove one or the other to proceed.
/// 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()
@ -206,7 +183,7 @@ fn get_token_test() -> (Project, thread::JoinHandle<()>) {
)
.file("src/lib.rs", "")
.build();
(p, t)
(p, server)
}
#[cargo_test]
@ -231,10 +208,7 @@ fn publish() {
#[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#"
@ -327,10 +301,7 @@ fn login() {
#[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")
@ -418,9 +389,7 @@ fn owner() {
#[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#"
@ -448,8 +417,10 @@ Caused by:
#[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"))

View file

@ -9,7 +9,7 @@ use std::sync::atomic::{AtomicUsize, Ordering};
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,
};
@ -549,6 +549,7 @@ fn upgrade_git() {
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"); }"#)

View file

@ -145,7 +145,7 @@ fn new_credentials_is_used_instead_old() {
#[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();

View file

@ -78,5 +78,6 @@ fn default_registry() {
#[cargo_test]
fn other_registry() {
registry::alt_init();
simple_logout_test(Some("alternative"), "--registry alternative");
}

View file

@ -818,6 +818,7 @@ to proceed despite this and include the uncommitted changes, pass the `--allow-d
#[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();

View file

@ -2,7 +2,7 @@
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;
@ -1543,6 +1543,7 @@ fn update_unused_new_version() {
#[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();
@ -1866,6 +1867,7 @@ fn patched_dep_new_version() {
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();
@ -1931,6 +1933,7 @@ fn patch_update_doesnt_update_other_sources() {
#[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();

View file

@ -677,7 +677,7 @@ The registry `alternative` is not listed in the `publish` value in Cargo.toml.
#[cargo_test]
fn publish_allowed_registry() {
registry::init();
registry::alt_init();
let p = project().build();
@ -717,7 +717,7 @@ fn publish_allowed_registry() {
#[cargo_test]
fn publish_implicitly_to_only_allowed_registry() {
registry::init();
registry::alt_init();
let p = project().build();

View file

@ -2,7 +2,7 @@
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]
@ -66,6 +66,7 @@ fn rename_with_different_names() {
#[cargo_test]
fn lots_of_names() {
registry::alt_init();
Package::new("foo", "0.1.0")
.file("src/lib.rs", "pub fn foo1() {}")
.publish();

View file

@ -1,6 +1,6 @@
//! 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 {
@ -215,6 +215,7 @@ fn alt_registry() {
// --extern-html-root-url is unstable
return;
}
registry::alt_init();
Package::new("bar", "1.0.0")
.alternative(true)
.file(

View file

@ -7,7 +7,7 @@
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]
@ -594,6 +594,7 @@ fn ignore_hidden() {
#[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| {