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