wasmer_package/package/
mod.rs

1//! Load a Wasmer package from disk.
2pub(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        // Now we want to set up the following filesystem tree:
48        //
49        // - first/ ("/first")
50        //   - file.txt
51        // - nested/
52        //   - dir/ ("second")
53        //     - README.md
54        //     - another-dir/
55        //       - empty.txt
56        // - third/ ("second/child")
57        //   - file.txt
58        // - empty/ ("empty")
59        //
60        // The "/first" entry
61        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        // The "second" entry
65        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        // The "second/child" entry
72        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        // The "empty" entry
76        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        // Go back to v3
146        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        // Go back to v3
241        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",      // the volume
283            "metadata", // the metadata volume
284            "foo",      // the atom
285            "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}