Skip to content

Commit 340656e

Browse files
committed
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.
1 parent e099df2 commit 340656e

File tree

12 files changed

+251
-118
lines changed

12 files changed

+251
-118
lines changed

crates/cargo-test-support/src/registry.rs

Lines changed: 190 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@ use cargo::util::Sha256;
55
use flate2::write::GzEncoder;
66
use flate2::Compression;
77
use std::collections::HashMap;
8+
use std::fmt::Write as _;
89
use std::fs::{self, File};
9-
use std::io::prelude::*;
10+
use std::io::{BufRead, BufReader, Write};
11+
use std::net::TcpListener;
1012
use std::path::{Path, PathBuf};
13+
use std::thread;
1114
use tar::{Builder, Header};
1215
use url::Url;
1316

@@ -70,6 +73,183 @@ pub fn generate_alt_dl_url(name: &str) -> String {
7073
format!("{}/{{crate}}/{{version}}/{{crate}}-{{version}}.crate", base)
7174
}
7275

76+
/// A builder for initializing registries.
77+
pub struct RegistryBuilder {
78+
/// If `true`, adds source replacement for crates.io to a registry on the filesystem.
79+
replace_crates_io: bool,
80+
/// If `true`, configures a registry named "alternative".
81+
alternative: bool,
82+
/// If set, sets the API url for the "alternative" registry.
83+
/// This defaults to a directory on the filesystem.
84+
alt_api_url: Option<String>,
85+
/// If `true`, configures `.cargo/credentials` with some tokens.
86+
add_tokens: bool,
87+
}
88+
89+
impl RegistryBuilder {
90+
pub fn new() -> RegistryBuilder {
91+
RegistryBuilder {
92+
replace_crates_io: true,
93+
alternative: false,
94+
alt_api_url: None,
95+
add_tokens: true,
96+
}
97+
}
98+
99+
/// Sets whether or not to replace crates.io with a registry on the filesystem.
100+
/// Default is `true`.
101+
pub fn replace_crates_io(&mut self, replace: bool) -> &mut Self {
102+
self.replace_crates_io = replace;
103+
self
104+
}
105+
106+
/// Sets whether or not to initialize an alternative registry named "alternative".
107+
/// Default is `false`.
108+
pub fn alternative(&mut self, alt: bool) -> &mut Self {
109+
self.alternative = alt;
110+
self
111+
}
112+
113+
/// Sets the API url for the "alternative" registry.
114+
/// Defaults to a path on the filesystem ([`alt_api_path`]).
115+
pub fn alternative_api_url(&mut self, url: &str) -> &mut Self {
116+
self.alternative = true;
117+
self.alt_api_url = Some(url.to_string());
118+
self
119+
}
120+
121+
/// Sets whether or not to initialize `.cargo/credentials` with some tokens.
122+
/// Defaults to `true`.
123+
pub fn add_tokens(&mut self, add: bool) -> &mut Self {
124+
self.add_tokens = add;
125+
self
126+
}
127+
128+
/// Initializes the registries.
129+
pub fn build(&self) {
130+
let config_path = paths::home().join(".cargo/config");
131+
if config_path.exists() {
132+
panic!(
133+
"{} already exists, the registry may only be initialized once, \
134+
and must be done before the config file is created",
135+
config_path.display()
136+
);
137+
}
138+
t!(fs::create_dir_all(config_path.parent().unwrap()));
139+
let mut config = String::new();
140+
if self.replace_crates_io {
141+
write!(
142+
&mut config,
143+
"
144+
[source.crates-io]
145+
replace-with = 'dummy-registry'
146+
147+
[source.dummy-registry]
148+
registry = '{}'
149+
",
150+
registry_url()
151+
)
152+
.unwrap();
153+
}
154+
if self.alternative {
155+
write!(
156+
config,
157+
"
158+
[registries.alternative]
159+
index = '{}'
160+
",
161+
alt_registry_url()
162+
)
163+
.unwrap();
164+
}
165+
t!(fs::write(&config_path, config));
166+
167+
if self.add_tokens {
168+
let credentials = paths::home().join(".cargo/credentials");
169+
t!(fs::write(
170+
&credentials,
171+
r#"
172+
[registry]
173+
token = "api-token"
174+
175+
[registries.alternative]
176+
token = "api-token"
177+
"#
178+
));
179+
}
180+
181+
if self.replace_crates_io {
182+
init_registry(
183+
registry_path(),
184+
dl_url().into_string(),
185+
api_url(),
186+
api_path(),
187+
);
188+
}
189+
190+
if self.alternative {
191+
init_registry(
192+
alt_registry_path(),
193+
alt_dl_url(),
194+
self.alt_api_url
195+
.as_ref()
196+
.map_or_else(alt_api_url, |url| Url::parse(&url).expect("valid url")),
197+
alt_api_path(),
198+
);
199+
}
200+
}
201+
202+
/// Initializes the registries, and sets up an HTTP server for the
203+
/// "alternative" registry.
204+
///
205+
/// The given callback takes a `Vec` of headers when a request comes in.
206+
/// The first entry should be the HTTP command, such as
207+
/// `PUT /api/v1/crates/new HTTP/1.1`.
208+
///
209+
/// The callback should return the HTTP code for the response, and the
210+
/// response body.
211+
///
212+
/// This method returns a `JoinHandle` which you should call
213+
/// `.join().unwrap()` on before exiting the test.
214+
pub fn build_api_server<'a>(
215+
&mut self,
216+
handler: &'static (dyn (Fn(Vec<String>) -> (u32, &'a dyn AsRef<[u8]>)) + Sync),
217+
) -> thread::JoinHandle<()> {
218+
let server = TcpListener::bind("127.0.0.1:0").unwrap();
219+
let addr = server.local_addr().unwrap();
220+
let api_url = format!("http://{}", addr);
221+
222+
self.replace_crates_io(false)
223+
.alternative_api_url(&api_url)
224+
.build();
225+
226+
let t = thread::spawn(move || {
227+
let mut conn = BufReader::new(server.accept().unwrap().0);
228+
let headers: Vec<_> = (&mut conn)
229+
.lines()
230+
.map(|s| s.unwrap())
231+
.take_while(|s| s.len() > 2)
232+
.map(|s| s.trim().to_string())
233+
.collect();
234+
let (code, response) = handler(headers);
235+
let response = response.as_ref();
236+
let stream = conn.get_mut();
237+
write!(
238+
stream,
239+
"HTTP/1.1 {}\r\n\
240+
Content-Length: {}\r\n\
241+
\r\n",
242+
code,
243+
response.len()
244+
)
245+
.unwrap();
246+
stream.write_all(response).unwrap();
247+
});
248+
249+
t
250+
}
251+
}
252+
73253
/// A builder for creating a new package in a registry.
74254
///
75255
/// This uses "source replacement" using an automatically generated
@@ -162,70 +342,28 @@ pub struct Dependency {
162342
optional: bool,
163343
}
164344

345+
/// Initializes the on-disk registry and sets up the config so that crates.io
346+
/// is replaced with the one on disk.
165347
pub fn init() {
166348
let config = paths::home().join(".cargo/config");
167-
t!(fs::create_dir_all(config.parent().unwrap()));
168349
if config.exists() {
169350
return;
170351
}
171-
t!(fs::write(
172-
&config,
173-
format!(
174-
r#"
175-
[source.crates-io]
176-
registry = 'https://wut'
177-
replace-with = 'dummy-registry'
178-
179-
[source.dummy-registry]
180-
registry = '{reg}'
181-
182-
[registries.alternative]
183-
index = '{alt}'
184-
"#,
185-
reg = registry_url(),
186-
alt = alt_registry_url()
187-
)
188-
));
189-
let credentials = paths::home().join(".cargo/credentials");
190-
t!(fs::write(
191-
&credentials,
192-
r#"
193-
[registry]
194-
token = "api-token"
195-
196-
[registries.alternative]
197-
token = "api-token"
198-
"#
199-
));
352+
RegistryBuilder::new().build();
353+
}
200354

201-
// Initialize a new registry.
202-
init_registry(
203-
registry_path(),
204-
dl_url().into_string(),
205-
api_url(),
206-
api_path(),
207-
);
208-
209-
// Initialize an alternative registry.
210-
init_registry(
211-
alt_registry_path(),
212-
alt_dl_url(),
213-
alt_api_url(),
214-
alt_api_path(),
215-
);
355+
/// Variant of `init` that initializes the "alternative" registry.
356+
pub fn alt_init() {
357+
RegistryBuilder::new().alternative(true).build();
216358
}
217359

360+
/// Creates a new on-disk registry.
218361
pub fn init_registry(registry_path: PathBuf, dl_url: String, api_url: Url, api_path: PathBuf) {
219362
// Initialize a new registry.
220363
repo(&registry_path)
221364
.file(
222365
"config.json",
223-
&format!(
224-
r#"
225-
{{"dl":"{}","api":"{}"}}
226-
"#,
227-
dl_url, api_url
228-
),
366+
&format!(r#"{{"dl":"{}","api":"{}"}}"#, dl_url, api_url),
229367
)
230368
.build();
231369
fs::create_dir_all(api_path.join("api/v1/crates")).unwrap();

0 commit comments

Comments
 (0)