Skip to main content

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