1use shuyi_core::ContentHash;
4
5use super::Bundle;
6use crate::fnv::Hasher;
7use crate::prelude::*;
8
9impl Bundle {
10 pub fn fingerprint(&self) -> ContentHash {
28 let mut hash = Hasher::new();
29 for layer in self.shipped().iter().rev() {
30 if let Some(content) = layer.stands_for {
31 hash = Hasher(content.0);
32 continue;
33 }
34 let mut paths = Vec::new();
35 layer.files.list("", true, &mut paths);
36 paths.sort_unstable();
37 for path in paths {
38 hash.sized(path.as_bytes());
39 hash.sized(layer.files.get(path).unwrap_or_default());
40 }
41 }
42 ContentHash(hash.0)
43 }
44}
45
46#[cfg(test)]
47pub(crate) mod tests {
48 use alloc::collections::BTreeMap;
49
50 use super::super::{Layer, story_only};
51 use super::*;
52
53 static BASE: &[(&str, &[u8])] = &[
54 ("data/a.ron", b"base a"),
55 ("data/b.ron", b"base b"),
56 ("top.txt", b"top"),
57 ];
58
59 static OVER: &[(&str, &[u8])] = &[("data/a.ron", b"over a"), ("new.txt", b"new")];
60
61 fn layer(files: &[(&str, &[u8])]) -> Layer {
63 let files = files
64 .iter()
65 .map(|(path, bytes)| ((*path).to_owned(), bytes.to_vec()))
66 .collect::<BTreeMap<_, _>>();
67 Layer::owned("layer".to_owned(), files).unwrap()
68 }
69
70 fn of(files: &[(&str, &[u8])]) -> ContentHash {
72 Bundle::new(vec![layer(files)]).fingerprint()
73 }
74
75 pub(crate) fn fixed() -> Bundle {
80 Bundle::new(vec![Layer::table("over", OVER), Layer::table("base", BASE)])
81 }
82
83 #[test]
84 fn no_layer_is_the_offset_basis() {
85 assert_eq!(
87 Bundle::new(Vec::new()).fingerprint(),
88 ContentHash(0xcbf2_9ce4_8422_2325)
89 );
90 assert_eq!(of(&[]), ContentHash(0xcbf2_9ce4_8422_2325));
91 }
92
93 #[test]
94 fn a_byte_a_name_or_a_file_changed_changes_it() {
95 let base = of(BASE);
96 assert_eq!(of(BASE), base);
97 let byte: &[(&str, &[u8])] = &[
98 ("data/a.ron", b"base A"),
99 ("data/b.ron", b"base b"),
100 ("top.txt", b"top"),
101 ];
102 let name: &[(&str, &[u8])] = &[
103 ("data/a.ron", b"base a"),
104 ("data/c.ron", b"base b"),
105 ("top.txt", b"top"),
106 ];
107 let added: &[(&str, &[u8])] = &[
108 ("data/a.ron", b"base a"),
109 ("data/b.ron", b"base b"),
110 ("data/c.ron", b""),
111 ("top.txt", b"top"),
112 ];
113 let moved: &[(&str, &[u8])] = &[
115 ("data/a.ro", b"nbase a"),
116 ("data/b.ron", b"base b"),
117 ("top.txt", b"top"),
118 ];
119 for changed in [byte, name, added, moved] {
120 assert_ne!(of(changed), base, "{changed:?}");
121 }
122 }
123
124 #[test]
125 fn the_layers_count_base_first_and_the_test_layers_never() {
126 let base = Layer::table("base", BASE);
127 let over = Layer::table("over", OVER);
128 let both = Bundle::new(vec![over, base]).fingerprint();
129 assert_ne!(both, Bundle::new(vec![base, over]).fingerprint());
130 assert_ne!(both, of(BASE));
131 let tests = Bundle::new(vec![over, base, Layer::table("tests", BASE).test()]);
133 assert_eq!(tests.fingerprint(), both);
134 assert_eq!(story_only(|| tests.fingerprint()), both);
135 let changed = Bundle::new(vec![over, base, Layer::table("tests", OVER).test()]);
136 assert_eq!(changed.fingerprint(), both);
137 let private = Bundle::new(vec![over.private(), base]).fingerprint();
139 assert_eq!(private, both);
140 assert_eq!(
142 Bundle::new(vec![layer(OVER), layer(BASE)]).fingerprint(),
143 both
144 );
145 }
146
147 #[test]
148 fn a_layer_standing_for_a_bundle_takes_its_fingerprint_and_hides_those_under_it() {
149 let base = Layer::table("base", BASE);
150 let over = Layer::table("over", OVER);
151 let loose = Bundle::new(vec![base]).fingerprint();
152 let pack = Layer::table("pack", OVER).standing_for(loose);
154 assert_eq!(Bundle::new(vec![pack]).fingerprint(), loose);
155 assert_eq!(
157 Bundle::new(vec![over, pack]).fingerprint(),
158 Bundle::new(vec![over, base]).fingerprint()
159 );
160 assert_eq!(
162 Bundle::new(vec![pack, over]).fingerprint(),
163 Bundle::new(vec![pack]).fingerprint()
164 );
165 let test = Layer::table("tests", OVER)
167 .standing_for(ContentHash(1))
168 .test();
169 assert_eq!(Bundle::new(vec![base, test]).fingerprint(), loose);
170 }
171
172 proptest::proptest! {
173 #[test]
176 fn one_byte_changed_anywhere_changes_it(
177 layers in proptest::collection::vec(
178 proptest::collection::btree_map(
179 "[ab]{1,2}(/[ab]{1,2}){0,2}",
180 proptest::collection::vec(proptest::num::u8::ANY, 1..20),
181 1..6,
182 ),
183 1..4,
184 ),
185 which in proptest::num::usize::ANY,
186 at in proptest::num::usize::ANY,
187 flip in 1..=u8::MAX,
188 ) {
189 let make = |layers: &[BTreeMap<String, Vec<u8>>]| {
190 let layers = layers
191 .iter()
192 .enumerate()
193 .map(|(i, files)| Layer::owned(format!("layer{i}"), files.clone()).unwrap())
194 .collect();
195 Bundle::new(layers).fingerprint()
196 };
197 let before = make(&layers);
198 let mut changed = layers.clone();
199 let files: Vec<(usize, String)> = changed
200 .iter()
201 .enumerate()
202 .flat_map(|(i, files)| files.keys().map(move |path| (i, path.clone())))
203 .collect();
204 let (layer, path) = &files[which % files.len()];
205 let bytes = changed[*layer].get_mut(path).unwrap();
206 let len = bytes.len();
207 bytes[at % len] ^= flip;
208 proptest::prop_assert_ne!(make(&changed), before);
209 }
210 }
211}