Skip to content

Commit 537dc09

Browse files
committed
Auto merge of #9126 - ehuss:registry-builder, r=alexcrichton
Add RegistryBuilder for tests, and update crates-io error handling. This adds `RegistryBuilder` to the test suite to make it more flexible to create different registry setups, and to reuse code a little more easily. This also makes a small adjustment to the registry API to add a `ResponseError` type to make it easier to work with API errors. As part of this, some tests were added to validate the API behavior for response errors. There are only a few very small changes here: * Extra newlines are removed from the headers printed in the error message. * The UTF-8 error now also includes the text "invalid response from server". * The "file too large" crates.io publish error now displays the tarball size. (There is no test for this because it is only issued for talking to `crates.io`.) Split from #9111.
2 parents 398514a + 06b8d1c commit 537dc09

File tree

13 files changed

+525
-151
lines changed

13 files changed

+525
-151
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)