1use crate::compare::InMemoryDir;
61use crate::registry::{self, alt_api_path, FeatureMap};
62use flate2::read::GzDecoder;
63use snapbox::prelude::*;
64use std::collections::HashSet;
65use std::fs;
66use std::fs::File;
67use std::io::{self, prelude::*, SeekFrom};
68use std::path::Path;
69use tar::Archive;
70
71fn read_le_u32<R>(mut reader: R) -> io::Result<u32>
72where
73 R: Read,
74{
75 let mut buf = [0; 4];
76 reader.read_exact(&mut buf)?;
77 Ok(u32::from_le_bytes(buf))
78}
79
80#[track_caller]
82pub fn validate_upload(expected_json: &str, expected_crate_name: &str, expected_files: &[&str]) {
83 let new_path = registry::api_path().join("api/v1/crates/new");
84 _validate_upload(
85 &new_path,
86 expected_json,
87 expected_crate_name,
88 expected_files,
89 (),
90 );
91}
92
93#[track_caller]
95pub fn validate_upload_with_contents(
96 expected_json: &str,
97 expected_crate_name: &str,
98 expected_files: &[&str],
99 expected_contents: impl Into<InMemoryDir>,
100) {
101 let new_path = registry::api_path().join("api/v1/crates/new");
102 _validate_upload(
103 &new_path,
104 expected_json,
105 expected_crate_name,
106 expected_files,
107 expected_contents,
108 );
109}
110
111#[track_caller]
113pub fn validate_alt_upload(
114 expected_json: &str,
115 expected_crate_name: &str,
116 expected_files: &[&str],
117) {
118 let new_path = alt_api_path().join("api/v1/crates/new");
119 _validate_upload(
120 &new_path,
121 expected_json,
122 expected_crate_name,
123 expected_files,
124 (),
125 );
126}
127
128#[track_caller]
129fn _validate_upload(
130 new_path: &Path,
131 expected_json: &str,
132 expected_crate_name: &str,
133 expected_files: &[&str],
134 expected_contents: impl Into<InMemoryDir>,
135) {
136 let (actual_json, krate_bytes) = read_new_post(new_path);
137
138 snapbox::assert_data_eq!(actual_json, expected_json.is_json());
139
140 validate_crate_contents(
142 &krate_bytes[..],
143 expected_crate_name,
144 expected_files,
145 expected_contents,
146 );
147}
148
149#[track_caller]
150fn read_new_post(new_path: &Path) -> (Vec<u8>, Vec<u8>) {
151 let mut f = File::open(new_path).unwrap();
152
153 let json_sz = read_le_u32(&mut f).expect("read json length");
155 let mut json_bytes = vec![0; json_sz as usize];
156 f.read_exact(&mut json_bytes).expect("read JSON data");
157
158 let crate_sz = read_le_u32(&mut f).expect("read crate length");
160 let mut krate_bytes = vec![0; crate_sz as usize];
161 f.read_exact(&mut krate_bytes).expect("read crate data");
162
163 let current = f.seek(SeekFrom::Current(0)).unwrap();
165 assert_eq!(f.seek(SeekFrom::End(0)).unwrap(), current);
166
167 (json_bytes, krate_bytes)
168}
169
170#[track_caller]
179pub fn validate_crate_contents(
180 reader: impl Read,
181 expected_crate_name: &str,
182 expected_files: &[&str],
183 expected_contents: impl Into<InMemoryDir>,
184) {
185 let expected_contents = expected_contents.into();
186 validate_crate_contents_(
187 reader,
188 expected_crate_name,
189 expected_files,
190 expected_contents,
191 )
192}
193
194#[track_caller]
195fn validate_crate_contents_(
196 reader: impl Read,
197 expected_crate_name: &str,
198 expected_files: &[&str],
199 expected_contents: InMemoryDir,
200) {
201 let mut rdr = GzDecoder::new(reader);
202 snapbox::assert_data_eq!(rdr.header().unwrap().filename().unwrap(), {
203 let expected: snapbox::Data = expected_crate_name.into();
204 expected.raw()
205 });
206
207 let mut contents = Vec::new();
208 rdr.read_to_end(&mut contents).unwrap();
209 let mut ar = Archive::new(&contents[..]);
210 let base_crate_name = Path::new(
211 expected_crate_name
212 .strip_suffix(".crate")
213 .expect("must end with .crate"),
214 );
215 let actual_contents: InMemoryDir = ar
216 .entries()
217 .unwrap()
218 .map(|entry| {
219 let mut entry = entry.unwrap();
220 let name = entry
221 .path()
222 .unwrap()
223 .strip_prefix(base_crate_name)
224 .unwrap()
225 .to_owned();
226 let mut contents = String::new();
227 entry.read_to_string(&mut contents).unwrap();
228 (name, contents)
229 })
230 .collect();
231 let actual_files: HashSet<&Path> = actual_contents.paths().collect();
232 let expected_files: HashSet<&Path> =
233 expected_files.iter().map(|name| Path::new(name)).collect();
234 let missing: Vec<&&Path> = expected_files.difference(&actual_files).collect();
235 let extra: Vec<&&Path> = actual_files.difference(&expected_files).collect();
236 if !missing.is_empty() || !extra.is_empty() {
237 panic!(
238 "uploaded archive does not match.\nMissing: {:?}\nExtra: {:?}\n",
239 missing, extra
240 );
241 }
242 actual_contents.assert_contains(&expected_contents);
243}
244
245pub(crate) fn create_index_line(
246 name: serde_json::Value,
247 vers: &str,
248 deps: Vec<serde_json::Value>,
249 cksum: &str,
250 features: crate::registry::FeatureMap,
251 yanked: bool,
252 links: Option<String>,
253 rust_version: Option<&str>,
254 v: Option<u32>,
255) -> String {
256 let (features, features2) = split_index_features(features.clone());
258 let mut json = serde_json::json!({
259 "name": name,
260 "vers": vers,
261 "deps": deps,
262 "cksum": cksum,
263 "features": features,
264 "yanked": yanked,
265 "links": links,
266 });
267 if let Some(f2) = &features2 {
268 json["features2"] = serde_json::json!(f2);
269 json["v"] = serde_json::json!(2);
270 }
271 if let Some(v) = v {
272 json["v"] = serde_json::json!(v);
273 }
274 if let Some(rust_version) = rust_version {
275 json["rust_version"] = serde_json::json!(rust_version);
276 }
277
278 json.to_string()
279}
280
281pub(crate) fn write_to_index(registry_path: &Path, name: &str, line: String, local: bool) {
282 let file = cargo_util::registry::make_dep_path(name, false);
283
284 let dst = if local {
286 registry_path.join("index").join(&file)
287 } else {
288 registry_path.join(&file)
289 };
290 let prev = fs::read_to_string(&dst).unwrap_or_default();
291 t!(fs::create_dir_all(dst.parent().unwrap()));
292 t!(fs::write(&dst, prev + &line[..] + "\n"));
293
294 if !local {
296 let repo = t!(git2::Repository::open(®istry_path));
297 let mut index = t!(repo.index());
298 t!(index.add_path(Path::new(&file)));
299 t!(index.write());
300 let id = t!(index.write_tree());
301
302 let tree = t!(repo.find_tree(id));
304 let sig = t!(repo.signature());
305 let parent = t!(repo.refname_to_id("refs/heads/master"));
306 let parent = t!(repo.find_commit(parent));
307 t!(repo.commit(
308 Some("HEAD"),
309 &sig,
310 &sig,
311 "Another commit",
312 &tree,
313 &[&parent]
314 ));
315 }
316}
317
318fn split_index_features(mut features: FeatureMap) -> (FeatureMap, Option<FeatureMap>) {
319 let mut features2 = FeatureMap::new();
320 for (feat, values) in features.iter_mut() {
321 if values
322 .iter()
323 .any(|value| value.starts_with("dep:") || value.contains("?/"))
324 {
325 let new_values = values.drain(..).collect();
326 features2.insert(feat.clone(), new_values);
327 }
328 }
329 if features2.is_empty() {
330 (features, None)
331 } else {
332 (features, Some(features2))
333 }
334}