Skip to content

Commit c094a02

Browse files
committed
feat(test): Snapshot .crate validation
1 parent b107422 commit c094a02

11 files changed

+612
-277
lines changed

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

Lines changed: 124 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,11 @@ use crate::paths;
4646
use crate::{diff, rustc_host};
4747
use anyhow::{bail, Context, Result};
4848
use serde_json::Value;
49+
use snapbox::Data;
50+
use snapbox::IntoData;
4951
use std::fmt;
5052
use std::path::Path;
53+
use std::path::PathBuf;
5154
use std::str;
5255
use url::Url;
5356

@@ -461,14 +464,6 @@ pub(crate) fn match_exact(
461464
);
462465
}
463466

464-
/// Convenience wrapper around [`match_exact`] which will panic on error.
465-
#[track_caller]
466-
pub(crate) fn assert_match_exact(expected: &str, actual: &str) {
467-
if let Err(e) = match_exact(expected, actual, "", "", None) {
468-
crate::panic_error("", e);
469-
}
470-
}
471-
472467
/// Checks that the given string contains the given lines, ignoring the order
473468
/// of the lines.
474469
///
@@ -860,6 +855,127 @@ impl fmt::Debug for WildStr<'_> {
860855
}
861856
}
862857

858+
pub struct InMemoryDir {
859+
files: Vec<(PathBuf, Data)>,
860+
}
861+
862+
impl InMemoryDir {
863+
pub fn paths(&self) -> impl Iterator<Item = &Path> {
864+
self.files.iter().map(|(p, _)| p.as_path())
865+
}
866+
867+
#[track_caller]
868+
pub fn assert_contains(&self, expected: &Self) {
869+
let assert = assert_e2e();
870+
for (path, expected_data) in &expected.files {
871+
let actual_data = self
872+
.files
873+
.iter()
874+
.find_map(|(p, d)| (path == p).then(|| d.clone()))
875+
.unwrap_or_else(|| Data::new());
876+
assert.eq(actual_data, expected_data);
877+
}
878+
}
879+
}
880+
881+
impl<P, D> FromIterator<(P, D)> for InMemoryDir
882+
where
883+
P: Into<std::path::PathBuf>,
884+
D: IntoData,
885+
{
886+
fn from_iter<I: IntoIterator<Item = (P, D)>>(files: I) -> Self {
887+
let files = files
888+
.into_iter()
889+
.map(|(p, d)| (p.into(), d.into_data()))
890+
.collect();
891+
Self { files }
892+
}
893+
}
894+
895+
impl<const N: usize, P, D> From<[(P, D); N]> for InMemoryDir
896+
where
897+
P: Into<PathBuf>,
898+
D: IntoData,
899+
{
900+
fn from(files: [(P, D); N]) -> Self {
901+
let files = files
902+
.into_iter()
903+
.map(|(p, d)| (p.into(), d.into_data()))
904+
.collect();
905+
Self { files }
906+
}
907+
}
908+
909+
impl<P, D> From<std::collections::HashMap<P, D>> for InMemoryDir
910+
where
911+
P: Into<PathBuf>,
912+
D: IntoData,
913+
{
914+
fn from(files: std::collections::HashMap<P, D>) -> Self {
915+
let files = files
916+
.into_iter()
917+
.map(|(p, d)| (p.into(), d.into_data()))
918+
.collect();
919+
Self { files }
920+
}
921+
}
922+
923+
impl<P, D> From<std::collections::BTreeMap<P, D>> for InMemoryDir
924+
where
925+
P: Into<PathBuf>,
926+
D: IntoData,
927+
{
928+
fn from(files: std::collections::BTreeMap<P, D>) -> Self {
929+
let files = files
930+
.into_iter()
931+
.map(|(p, d)| (p.into(), d.into_data()))
932+
.collect();
933+
Self { files }
934+
}
935+
}
936+
937+
impl From<()> for InMemoryDir {
938+
fn from(_files: ()) -> Self {
939+
let files = Vec::new();
940+
Self { files }
941+
}
942+
}
943+
944+
macro_rules! impl_from_tuple_for_dirsnapshot {
945+
($($var:ident $path:ident $data:ident),+) => {
946+
impl<$($path: Into<PathBuf>, $data: IntoData),+> From<($(($path, $data)),+ ,)> for InMemoryDir {
947+
fn from(files: ($(($path, $data)),+,)) -> Self {
948+
let ($($var),+ ,) = files;
949+
let files = [$(($var.0.into(), $var.1.into_data())),+];
950+
files.into()
951+
}
952+
}
953+
};
954+
}
955+
956+
macro_rules! impl_from_tuples_for_dirsnapshot {
957+
($var1:ident $path1:ident $data1:ident, $($var:ident $path:ident $data:ident),+) => {
958+
impl_from_tuples_for_dirsnapshot!(__impl $var1 $path1 $data1; $($var $path $data),+);
959+
};
960+
(__impl $($var:ident $path:ident $data:ident),+; $var1:ident $path1:ident $data1:ident $(,$var2:ident $path2:ident $data2:ident)*) => {
961+
impl_from_tuple_for_dirsnapshot!($($var $path $data),+);
962+
impl_from_tuples_for_dirsnapshot!(__impl $($var $path $data),+, $var1 $path1 $data1; $($var2 $path2 $data2),*);
963+
};
964+
(__impl $($var:ident $path:ident $data:ident),+;) => {
965+
impl_from_tuple_for_dirsnapshot!($($var $path $data),+);
966+
}
967+
}
968+
969+
impl_from_tuples_for_dirsnapshot!(
970+
s1 P1 D1,
971+
s2 P2 D2,
972+
s3 P3 D3,
973+
s4 P4 D4,
974+
s5 P5 D5,
975+
s6 P6 D6,
976+
s7 P7 D7
977+
);
978+
863979
#[cfg(test)]
864980
mod test {
865981
use snapbox::assert_data_eq;

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

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,14 @@
5757
//! );
5858
//! ```
5959
60-
use crate::compare::{assert_match_exact, find_json_mismatch};
60+
use crate::compare::{find_json_mismatch, InMemoryDir};
6161
use crate::registry::{self, alt_api_path, FeatureMap};
6262
use flate2::read::GzDecoder;
63-
use std::collections::{HashMap, HashSet};
63+
use std::collections::HashSet;
6464
use std::fs;
6565
use std::fs::File;
6666
use std::io::{self, prelude::*, SeekFrom};
67-
use std::path::{Path, PathBuf};
67+
use std::path::Path;
6868
use tar::Archive;
6969

7070
fn read_le_u32<R>(mut reader: R) -> io::Result<u32>
@@ -84,7 +84,7 @@ pub fn validate_upload(expected_json: &str, expected_crate_name: &str, expected_
8484
expected_json,
8585
expected_crate_name,
8686
expected_files,
87-
&[],
87+
(),
8888
);
8989
}
9090

@@ -93,7 +93,7 @@ pub fn validate_upload_with_contents(
9393
expected_json: &str,
9494
expected_crate_name: &str,
9595
expected_files: &[&str],
96-
expected_contents: &[(&str, &str)],
96+
expected_contents: impl Into<InMemoryDir>,
9797
) {
9898
let new_path = registry::api_path().join("api/v1/crates/new");
9999
_validate_upload(
@@ -117,7 +117,7 @@ pub fn validate_alt_upload(
117117
expected_json,
118118
expected_crate_name,
119119
expected_files,
120-
&[],
120+
(),
121121
);
122122
}
123123

@@ -126,7 +126,7 @@ fn _validate_upload(
126126
expected_json: &str,
127127
expected_crate_name: &str,
128128
expected_files: &[&str],
129-
expected_contents: &[(&str, &str)],
129+
expected_contents: impl Into<InMemoryDir>,
130130
) {
131131
let (actual_json, krate_bytes) = read_new_post(new_path);
132132

@@ -177,7 +177,22 @@ pub fn validate_crate_contents(
177177
reader: impl Read,
178178
expected_crate_name: &str,
179179
expected_files: &[&str],
180-
expected_contents: &[(&str, &str)],
180+
expected_contents: impl Into<InMemoryDir>,
181+
) {
182+
let expected_contents = expected_contents.into();
183+
validate_crate_contents_(
184+
reader,
185+
expected_crate_name,
186+
expected_files,
187+
expected_contents,
188+
)
189+
}
190+
191+
fn validate_crate_contents_(
192+
reader: impl Read,
193+
expected_crate_name: &str,
194+
expected_files: &[&str],
195+
expected_contents: InMemoryDir,
181196
) {
182197
let mut rdr = GzDecoder::new(reader);
183198
assert_eq!(
@@ -192,7 +207,7 @@ pub fn validate_crate_contents(
192207
.strip_suffix(".crate")
193208
.expect("must end with .crate"),
194209
);
195-
let files: HashMap<PathBuf, String> = ar
210+
let actual_contents: InMemoryDir = ar
196211
.entries()
197212
.unwrap()
198213
.map(|entry| {
@@ -208,7 +223,7 @@ pub fn validate_crate_contents(
208223
(name, contents)
209224
})
210225
.collect();
211-
let actual_files: HashSet<&Path> = files.keys().map(|p| p.as_path()).collect();
226+
let actual_files: HashSet<&Path> = actual_contents.paths().collect();
212227
let expected_files: HashSet<&Path> =
213228
expected_files.iter().map(|name| Path::new(name)).collect();
214229
let missing: Vec<&&Path> = expected_files.difference(&actual_files).collect();
@@ -219,15 +234,7 @@ pub fn validate_crate_contents(
219234
missing, extra
220235
);
221236
}
222-
if !expected_contents.is_empty() {
223-
for (e_file_name, e_file_contents) in expected_contents {
224-
let e_file_name = Path::new(e_file_name);
225-
let actual_contents = files
226-
.get(e_file_name)
227-
.unwrap_or_else(|| panic!("file `{}` missing in archive", e_file_name.display()));
228-
assert_match_exact(e_file_contents, actual_contents);
229-
}
230-
}
237+
actual_contents.assert_contains(&expected_contents);
231238
}
232239

233240
pub(crate) fn create_index_line(

tests/testsuite/artifact_dep.rs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2260,10 +2260,20 @@ You may press ctrl-c to skip waiting; the crate should be available shortly.
22602260
"#,
22612261
"foo-0.1.0.crate",
22622262
&["Cargo.toml", "Cargo.toml.orig", "src/lib.rs"],
2263-
&[(
2263+
[(
22642264
"Cargo.toml",
2265-
&format!(
2266-
r#"{}
2265+
str![[r##"
2266+
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
2267+
#
2268+
# When uploading crates to the registry Cargo will automatically
2269+
# "normalize" Cargo.toml files for maximal compatibility
2270+
# with all versions of Cargo and also rewrite `path` dependencies
2271+
# to registry (e.g., crates.io) dependencies.
2272+
#
2273+
# If you are reading this file be aware that the original Cargo.toml
2274+
# will likely look very different (and much more reasonable).
2275+
# See Cargo.toml.orig for the original contents.
2276+
22672277
[package]
22682278
edition = "2015"
22692279
name = "foo"
@@ -2299,9 +2309,9 @@ artifact = [
22992309
"cdylib",
23002310
"staticlib",
23012311
]
2302-
target = "target""#,
2303-
cargo::core::manifest::MANIFEST_PREAMBLE
2304-
),
2312+
target = "target"
2313+
2314+
"##]],
23052315
)],
23062316
);
23072317
}

tests/testsuite/cross_publish.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ fn simple_cross_package() {
5858
f,
5959
"foo-0.0.0.crate",
6060
&["Cargo.lock", "Cargo.toml", "Cargo.toml.orig", "src/main.rs"],
61-
&[],
61+
(),
6262
);
6363
}
6464

tests/testsuite/features2.rs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1788,8 +1788,18 @@ fn package_includes_resolve_behavior() {
17881788

17891789
p.cargo("package").cwd("a").run();
17901790

1791-
let rewritten_toml = format!(
1792-
r#"{}
1791+
let rewritten_toml = str![[r##"
1792+
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
1793+
#
1794+
# When uploading crates to the registry Cargo will automatically
1795+
# "normalize" Cargo.toml files for maximal compatibility
1796+
# with all versions of Cargo and also rewrite `path` dependencies
1797+
# to registry (e.g., crates.io) dependencies.
1798+
#
1799+
# If you are reading this file be aware that the original Cargo.toml
1800+
# will likely look very different (and much more reasonable).
1801+
# See Cargo.toml.orig for the original contents.
1802+
17931803
[package]
17941804
edition = "2015"
17951805
name = "a"
@@ -1810,16 +1820,15 @@ resolver = "2"
18101820
[lib]
18111821
name = "a"
18121822
path = "src/lib.rs"
1813-
"#,
1814-
cargo::core::manifest::MANIFEST_PREAMBLE
1815-
);
1823+
1824+
"##]];
18161825

18171826
let f = File::open(&p.root().join("target/package/a-0.1.0.crate")).unwrap();
18181827
validate_crate_contents(
18191828
f,
18201829
"a-0.1.0.crate",
18211830
&["Cargo.toml", "Cargo.toml.orig", "src/lib.rs"],
1822-
&[("Cargo.toml", &rewritten_toml)],
1831+
[("Cargo.toml", rewritten_toml)],
18231832
);
18241833
}
18251834

0 commit comments

Comments
 (0)