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