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

https://gitlab.com/frewsxcv/cargo · Rust · 157 lines · 133 code · 9 blank · 15 comment · 5 complexity · 073164f9e03cbbebe413959142d820fc MD5 · raw file

  1. use crate::compare::{assert_match_exact, find_json_mismatch};
  2. use crate::registry::{self, alt_api_path};
  3. use flate2::read::GzDecoder;
  4. use std::collections::{HashMap, HashSet};
  5. use std::fs::File;
  6. use std::io::{self, prelude::*, SeekFrom};
  7. use std::path::{Path, PathBuf};
  8. use tar::Archive;
  9. fn read_le_u32<R>(mut reader: R) -> io::Result<u32>
  10. where
  11. R: Read,
  12. {
  13. let mut buf = [0; 4];
  14. reader.read_exact(&mut buf)?;
  15. Ok(u32::from_le_bytes(buf))
  16. }
  17. /// Checks the result of a crate publish.
  18. pub fn validate_upload(expected_json: &str, expected_crate_name: &str, expected_files: &[&str]) {
  19. let new_path = registry::api_path().join("api/v1/crates/new");
  20. _validate_upload(
  21. &new_path,
  22. expected_json,
  23. expected_crate_name,
  24. expected_files,
  25. &[],
  26. );
  27. }
  28. /// Checks the result of a crate publish, along with the contents of the files.
  29. pub fn validate_upload_with_contents(
  30. expected_json: &str,
  31. expected_crate_name: &str,
  32. expected_files: &[&str],
  33. expected_contents: &[(&str, &str)],
  34. ) {
  35. let new_path = registry::api_path().join("api/v1/crates/new");
  36. _validate_upload(
  37. &new_path,
  38. expected_json,
  39. expected_crate_name,
  40. expected_files,
  41. expected_contents,
  42. );
  43. }
  44. /// Checks the result of a crate publish to an alternative registry.
  45. pub fn validate_alt_upload(
  46. expected_json: &str,
  47. expected_crate_name: &str,
  48. expected_files: &[&str],
  49. ) {
  50. let new_path = alt_api_path().join("api/v1/crates/new");
  51. _validate_upload(
  52. &new_path,
  53. expected_json,
  54. expected_crate_name,
  55. expected_files,
  56. &[],
  57. );
  58. }
  59. fn _validate_upload(
  60. new_path: &Path,
  61. expected_json: &str,
  62. expected_crate_name: &str,
  63. expected_files: &[&str],
  64. expected_contents: &[(&str, &str)],
  65. ) {
  66. let mut f = File::open(new_path).unwrap();
  67. // 32-bit little-endian integer of length of JSON data.
  68. let json_sz = read_le_u32(&mut f).expect("read json length");
  69. let mut json_bytes = vec![0; json_sz as usize];
  70. f.read_exact(&mut json_bytes).expect("read JSON data");
  71. let actual_json = serde_json::from_slice(&json_bytes).expect("uploaded JSON should be valid");
  72. let expected_json = serde_json::from_str(expected_json).expect("expected JSON does not parse");
  73. if let Err(e) = find_json_mismatch(&expected_json, &actual_json, None) {
  74. panic!("{}", e);
  75. }
  76. // 32-bit little-endian integer of length of crate file.
  77. let crate_sz = read_le_u32(&mut f).expect("read crate length");
  78. let mut krate_bytes = vec![0; crate_sz as usize];
  79. f.read_exact(&mut krate_bytes).expect("read crate data");
  80. // Check at end.
  81. let current = f.seek(SeekFrom::Current(0)).unwrap();
  82. assert_eq!(f.seek(SeekFrom::End(0)).unwrap(), current);
  83. // Verify the tarball.
  84. validate_crate_contents(
  85. &krate_bytes[..],
  86. expected_crate_name,
  87. expected_files,
  88. expected_contents,
  89. );
  90. }
  91. /// Checks the contents of a `.crate` file.
  92. ///
  93. /// - `expected_crate_name` should be something like `foo-0.0.1.crate`.
  94. /// - `expected_files` should be a complete list of files in the crate
  95. /// (relative to expected_crate_name).
  96. /// - `expected_contents` should be a list of `(file_name, contents)` tuples
  97. /// to validate the contents of the given file. Only the listed files will
  98. /// be checked (others will be ignored).
  99. pub fn validate_crate_contents(
  100. reader: impl Read,
  101. expected_crate_name: &str,
  102. expected_files: &[&str],
  103. expected_contents: &[(&str, &str)],
  104. ) {
  105. let mut rdr = GzDecoder::new(reader);
  106. assert_eq!(
  107. rdr.header().unwrap().filename().unwrap(),
  108. expected_crate_name.as_bytes()
  109. );
  110. let mut contents = Vec::new();
  111. rdr.read_to_end(&mut contents).unwrap();
  112. let mut ar = Archive::new(&contents[..]);
  113. let files: HashMap<PathBuf, String> = ar
  114. .entries()
  115. .unwrap()
  116. .map(|entry| {
  117. let mut entry = entry.unwrap();
  118. let name = entry.path().unwrap().into_owned();
  119. let mut contents = String::new();
  120. entry.read_to_string(&mut contents).unwrap();
  121. (name, contents)
  122. })
  123. .collect();
  124. assert!(expected_crate_name.ends_with(".crate"));
  125. let base_crate_name = Path::new(&expected_crate_name[..expected_crate_name.len() - 6]);
  126. let actual_files: HashSet<PathBuf> = files.keys().cloned().collect();
  127. let expected_files: HashSet<PathBuf> = expected_files
  128. .iter()
  129. .map(|name| base_crate_name.join(name))
  130. .collect();
  131. let missing: Vec<&PathBuf> = expected_files.difference(&actual_files).collect();
  132. let extra: Vec<&PathBuf> = actual_files.difference(&expected_files).collect();
  133. if !missing.is_empty() || !extra.is_empty() {
  134. panic!(
  135. "uploaded archive does not match.\nMissing: {:?}\nExtra: {:?}\n",
  136. missing, extra
  137. );
  138. }
  139. if !expected_contents.is_empty() {
  140. for (e_file_name, e_file_contents) in expected_contents {
  141. let full_e_name = base_crate_name.join(e_file_name);
  142. let actual_contents = files
  143. .get(&full_e_name)
  144. .unwrap_or_else(|| panic!("file `{}` missing in archive", e_file_name));
  145. assert_match_exact(e_file_contents, actual_contents);
  146. }
  147. }
  148. }