1pub(crate) mod manifest;
3#[allow(clippy::module_inception)]
4pub(crate) mod package;
5pub(crate) mod strictness;
6pub(crate) mod volume;
7
8pub use self::{
9 manifest::ManifestError,
10 package::{
11 Package, WalkBuilderFactory, WasmerPackageError, include_everything_walker,
12 wasmer_ignore_walker,
13 },
14 strictness::Strictness,
15 volume::{WasmerPackageVolume, fs::*, in_memory::*},
16};
17
18#[cfg(test)]
19mod tests {
20 use sha2::Digest;
21 use shared_buffer::OwnedBuffer;
22 use tempfile::TempDir;
23
24 use webc::{
25 metadata::annotations::FileSystemMapping,
26 migration::{are_semantically_equivalent, v2_to_v3, v3_to_v2},
27 };
28
29 use crate::{package::Package, utils::from_bytes};
30
31 #[test]
32 fn migration_roundtrip() {
33 let temp = TempDir::new().unwrap();
34 let wasmer_toml = r#"
35 [package]
36 name = "some/package"
37 version = "0.0.0"
38 description = "Test package"
39 [fs]
40 "/first" = "first"
41 second = "nested/dir"
42 "second/child" = "third"
43 empty = "empty"
44 "#;
45 let manifest = temp.path().join("wasmer.toml");
46 std::fs::write(&manifest, wasmer_toml).unwrap();
47 let first = temp.path().join("first");
62 std::fs::create_dir_all(&first).unwrap();
63 std::fs::write(first.join("file.txt"), "File").unwrap();
64 let second = temp.path().join("nested").join("dir");
66 std::fs::create_dir_all(&second).unwrap();
67 std::fs::write(second.join("README.md"), "please").unwrap();
68 let another_dir = temp.path().join("nested").join("dir").join("another-dir");
69 std::fs::create_dir_all(&another_dir).unwrap();
70 std::fs::write(another_dir.join("empty.txt"), "").unwrap();
71 let third = temp.path().join("third");
73 std::fs::create_dir_all(&third).unwrap();
74 std::fs::write(third.join("file.txt"), "Hello, World!").unwrap();
75 let empty_dir = temp.path().join("empty");
77 std::fs::create_dir_all(empty_dir).unwrap();
78
79 let package = Package::from_manifest(manifest).unwrap();
80
81 let webc = package.serialize().unwrap();
82
83 let webc_v2 = v3_to_v2(webc.clone()).unwrap();
84
85 are_semantically_equivalent(webc_v2.clone(), webc.into()).unwrap();
86
87 let container = from_bytes(webc_v2.clone().into_bytes()).unwrap();
88 let manifest = container.manifest();
89 let fs_table = manifest.filesystem().unwrap().unwrap();
90 assert_eq!(
91 fs_table,
92 [
93 FileSystemMapping {
94 from: None,
95 volume_name: "atom".to_string(),
96 host_path: Some("/first".to_string()),
97 mount_path: "/first".to_string(),
98 },
99 FileSystemMapping {
100 from: None,
101 volume_name: "atom".to_string(),
102 host_path: Some("/nested/dir".to_string()),
103 mount_path: "/second".to_string(),
104 },
105 FileSystemMapping {
106 from: None,
107 volume_name: "atom".to_string(),
108 host_path: Some("/third".to_string()),
109 mount_path: "/second/child".to_string(),
110 },
111 FileSystemMapping {
112 from: None,
113 volume_name: "atom".to_string(),
114 host_path: Some("/empty".to_string()),
115 mount_path: "/empty".to_string(),
116 },
117 ]
118 );
119
120 let atom_volume = container.get_volume("atom").unwrap();
121 assert_eq!(
122 atom_volume.read_file("/first/file.txt").unwrap(),
123 (OwnedBuffer::from(b"File".as_slice()), None)
124 );
125 assert_eq!(
126 atom_volume.read_file("/nested/dir/README.md").unwrap(),
127 (OwnedBuffer::from(b"please".as_slice()), None),
128 );
129 assert_eq!(
130 atom_volume
131 .read_file("/nested/dir/another-dir/empty.txt")
132 .unwrap(),
133 (OwnedBuffer::from(b"".as_slice()), None)
134 );
135 assert_eq!(
136 atom_volume.read_file("/third/file.txt").unwrap(),
137 (OwnedBuffer::from(b"Hello, World!".as_slice()), None)
138 );
139 assert_eq!(
140 atom_volume.read_dir("/empty").unwrap().len(),
141 0,
142 "Directories should be included, even if empty"
143 );
144
145 let webc_v3 = v2_to_v3(webc_v2.clone()).unwrap();
147
148 are_semantically_equivalent(webc_v2, webc_v3.clone()).unwrap();
149
150 let container = from_bytes(webc_v3.into_bytes()).unwrap();
151 let manifest = container.manifest();
152 let fs_table = manifest.filesystem().unwrap().unwrap();
153 assert_eq!(
154 fs_table,
155 [
156 FileSystemMapping {
157 from: None,
158 volume_name: "/first".to_string(),
159 host_path: None,
160 mount_path: "/first".to_string(),
161 },
162 FileSystemMapping {
163 from: None,
164 volume_name: "/nested/dir".to_string(),
165 host_path: None,
166 mount_path: "/second".to_string(),
167 },
168 FileSystemMapping {
169 from: None,
170 volume_name: "/third".to_string(),
171 host_path: None,
172 mount_path: "/second/child".to_string(),
173 },
174 FileSystemMapping {
175 from: None,
176 volume_name: "/empty".to_string(),
177 host_path: None,
178 mount_path: "/empty".to_string(),
179 },
180 ]
181 );
182
183 let first_file_hash: [u8; 32] = sha2::Sha256::digest(b"File").into();
184 let readme_hash: [u8; 32] = sha2::Sha256::digest(b"please").into();
185 let empty_hash: [u8; 32] = sha2::Sha256::digest(b"").into();
186 let third_file_hash: [u8; 32] = sha2::Sha256::digest(b"Hello, World!").into();
187
188 let first_volume = container.get_volume("/first").unwrap();
189 assert_eq!(
190 first_volume.read_file("/file.txt").unwrap(),
191 (b"File".as_slice().into(), Some(first_file_hash)),
192 );
193
194 let nested_dir_volume = container.get_volume("/nested/dir").unwrap();
195 assert_eq!(
196 nested_dir_volume.read_file("README.md").unwrap(),
197 (b"please".as_slice().into(), Some(readme_hash)),
198 );
199 assert_eq!(
200 nested_dir_volume
201 .read_file("/another-dir/empty.txt")
202 .unwrap(),
203 (b"".as_slice().into(), Some(empty_hash))
204 );
205
206 let third_volume = container.get_volume("/third").unwrap();
207 assert_eq!(
208 third_volume.read_file("/file.txt").unwrap(),
209 (b"Hello, World!".as_slice().into(), Some(third_file_hash))
210 );
211
212 let empty_volume = container.get_volume("/empty").unwrap();
213 assert_eq!(
214 empty_volume.read_dir("/").unwrap().len(),
215 0,
216 "Directories should be included, even if empty"
217 );
218 }
219
220 #[test]
221 fn fs_entry_is_not_required_for_migration() {
222 let temp = TempDir::new().unwrap();
223 let wasmer_toml = r#"
224 [package]
225 name = "some/package"
226 version = "0.0.0"
227 description = "Test package"
228 "#;
229 let manifest = temp.path().join("wasmer.toml");
230 std::fs::write(&manifest, wasmer_toml).unwrap();
231 let package = Package::from_manifest(manifest).unwrap();
232
233 let webc = package.serialize().unwrap();
234
235 let webc_v2 = v3_to_v2(webc).unwrap();
236 let container = from_bytes(webc_v2.clone().into_bytes()).unwrap();
237 let manifest = container.manifest();
238 assert!(manifest.filesystem().unwrap().is_none());
239
240 let webc_v3 = v2_to_v3(webc_v2).unwrap();
242 let container = from_bytes(webc_v3.into_bytes()).unwrap();
243 let manifest = container.manifest();
244 assert!(manifest.filesystem().unwrap().is_none());
245 }
246
247 #[test]
248 fn container_unpacks_atoms() {
249 let temp = TempDir::new().unwrap();
250 let wasmer_toml = r#"
251 [package]
252 name = "some/package"
253 version = "0.0.0"
254 description = "Test package"
255 [[module]]
256 name = "foo"
257 source = "foo.wasm"
258 abi = "wasi"
259 [fs]
260 "/bar" = "bar"
261 "#;
262
263 let manifest = temp.path().join("wasmer.toml");
264 std::fs::write(&manifest, wasmer_toml).unwrap();
265
266 let atom_path = temp.path().join("foo.wasm");
267 std::fs::write(&atom_path, b"").unwrap();
268
269 let bar = temp.path().join("bar");
270 std::fs::create_dir(&bar).unwrap();
271
272 let webc = Package::from_manifest(&manifest)
273 .unwrap()
274 .serialize()
275 .unwrap();
276 let container = from_bytes(webc).unwrap();
277
278 let out_dir = temp.path().join("out");
279 container.unpack(&out_dir, false).unwrap();
280
281 let expected_entries = [
282 "bar", "metadata", "foo", "manifest.json",
286 ];
287 let entries = std::fs::read_dir(&out_dir)
288 .unwrap()
289 .map(|e| e.unwrap())
290 .collect::<Vec<_>>();
291
292 assert_eq!(expected_entries.len(), entries.len());
293 assert!(expected_entries.iter().all(|e| {
294 entries
295 .iter()
296 .any(|entry| entry.file_name().as_os_str() == *e)
297 }))
298 }
299}