1use std::{
2 borrow::Cow,
3 collections::{BTreeMap, BTreeSet},
4 fmt::Debug,
5 fs::File,
6 io::{BufRead, BufReader},
7 path::{Path, PathBuf},
8 sync::Arc,
9};
10
11use anyhow::{Context, Error};
12use bytes::Bytes;
13use flate2::bufread::GzDecoder;
14use shared_buffer::OwnedBuffer;
15use tar::Archive;
16use tempfile::TempDir;
17use wasmer_config::package::Manifest as WasmerManifest;
18
19use webc::{
20 metadata::{annotations::Wapm, Manifest as WebcManifest},
21 v3::{
22 write::{FileEntry, Writer},
23 ChecksumAlgorithm, Timestamps,
24 },
25 AbstractVolume, AbstractWebc, Container, ContainerError, DetectError, PathSegment, Version,
26 Volume,
27};
28
29use super::{
30 manifest::wasmer_manifest_to_webc,
31 volume::{fs::FsVolume, WasmerPackageVolume},
32 ManifestError, MemoryVolume, Strictness,
33};
34
35#[derive(Debug, thiserror::Error)]
37#[allow(clippy::result_large_err)]
38#[non_exhaustive]
39pub enum WasmerPackageError {
40 #[error("Unable to create a temporary directory")]
42 TempDir(#[source] std::io::Error),
43 #[error("Unable to open \"{}\"", path.display())]
45 FileOpen {
46 path: PathBuf,
48 #[source]
50 error: std::io::Error,
51 },
52 #[error("Unable to read \"{}\"", path.display())]
54 FileRead {
55 path: PathBuf,
57 #[source]
59 error: std::io::Error,
60 },
61
62 #[error("IO Error: {0:?}")]
64 IoError(#[from] std::io::Error),
65
66 #[error("Malformed path format: {0:?}")]
68 MalformedPath(PathBuf),
69
70 #[error("Unable to extract the tarball")]
72 Tarball(#[source] std::io::Error),
73 #[error("Unable to deserialize \"{}\"", path.display())]
75 TomlDeserialize {
76 path: PathBuf,
78 #[source]
80 error: toml::de::Error,
81 },
82 #[error("Unable to deserialize \"{}\"", path.display())]
84 JsonDeserialize {
85 path: PathBuf,
87 #[source]
89 error: serde_json::Error,
90 },
91 #[error("Unable to find the \"wasmer.toml\"")]
93 MissingManifest,
94 #[error("Unable to get the absolute path for \"{}\"", path.display())]
96 Canonicalize {
97 path: PathBuf,
99 #[source]
101 error: std::io::Error,
102 },
103 #[error("Unable to load the \"wasmer.toml\" manifest")]
105 Manifest(#[from] ManifestError),
106 #[error("The manifest is invalid")]
108 Validation(#[from] wasmer_config::package::ValidationError),
109 #[error("Path: \"{}\" does not exist", path.display())]
111 PathNotExists {
112 path: PathBuf,
114 },
115 #[error("Volume creation failed: {0:?}")]
117 VolumeCreation(#[from] anyhow::Error),
118
119 #[error("serde error: {0:?}")]
121 SerdeError(#[from] ciborium::value::Error),
122
123 #[error("container error: {0:?}")]
125 ContainerError(#[from] ContainerError),
126
127 #[error("detect error: {0:?}")]
129 DetectError(#[from] DetectError),
130}
131
132#[derive(Debug)]
134pub struct Package {
135 #[allow(dead_code)]
138 base_dir: BaseDir,
139 manifest: WebcManifest,
140 atoms: BTreeMap<String, OwnedBuffer>,
141 strictness: Strictness,
142 volumes: BTreeMap<String, Arc<dyn WasmerPackageVolume + Send + Sync + 'static>>,
143}
144
145impl Package {
146 pub fn from_tarball_file(path: impl AsRef<Path>) -> Result<Self, WasmerPackageError> {
153 Package::from_tarball_file_with_strictness(path.as_ref(), Strictness::default())
154 }
155 pub fn from_tarball_file_with_strictness(
162 path: impl AsRef<Path>,
163 strictness: Strictness,
164 ) -> Result<Self, WasmerPackageError> {
165 let path = path.as_ref();
166 let f = File::open(path).map_err(|error| WasmerPackageError::FileOpen {
167 path: path.to_path_buf(),
168 error,
169 })?;
170
171 Package::from_tarball_with_strictness(BufReader::new(f), strictness)
172 }
173
174 pub fn from_tarball(tarball: impl BufRead) -> Result<Self, WasmerPackageError> {
176 Package::from_tarball_with_strictness(tarball, Strictness::default())
177 }
178
179 pub fn from_tarball_with_strictness(
181 tarball: impl BufRead,
182 strictness: Strictness,
183 ) -> Result<Self, WasmerPackageError> {
184 let tarball = GzDecoder::new(tarball);
185 let temp = tempdir().map_err(WasmerPackageError::TempDir)?;
186 let archive = Archive::new(tarball);
187 unpack_archive(archive, temp.path()).map_err(WasmerPackageError::Tarball)?;
188
189 let (_manifest_path, manifest) = read_manifest(temp.path())?;
190
191 Package::load(manifest, temp, strictness)
192 }
193
194 pub fn from_manifest(wasmer_toml: impl AsRef<Path>) -> Result<Self, WasmerPackageError> {
196 Package::from_manifest_with_strictness(wasmer_toml, Strictness::default())
197 }
198
199 pub fn from_manifest_with_strictness(
201 wasmer_toml: impl AsRef<Path>,
202 strictness: Strictness,
203 ) -> Result<Self, WasmerPackageError> {
204 let path = wasmer_toml.as_ref();
205 let path = path
206 .canonicalize()
207 .map_err(|error| WasmerPackageError::Canonicalize {
208 path: path.to_path_buf(),
209 error,
210 })?;
211
212 let wasmer_toml =
213 std::fs::read_to_string(&path).map_err(|error| WasmerPackageError::FileRead {
214 path: path.to_path_buf(),
215 error,
216 })?;
217 let wasmer_toml: WasmerManifest =
218 toml::from_str(&wasmer_toml).map_err(|error| WasmerPackageError::TomlDeserialize {
219 path: path.to_path_buf(),
220 error,
221 })?;
222
223 let base_dir = path
224 .parent()
225 .expect("Canonicalizing should always result in a file with a parent directory")
226 .to_path_buf();
227
228 for path in wasmer_toml.fs.values() {
229 if !base_dir.join(path).exists() {
230 return Err(WasmerPackageError::PathNotExists { path: path.clone() });
231 }
232 }
233
234 Package::load(wasmer_toml, base_dir, strictness)
235 }
236
237 pub fn from_json_manifest(manifest: PathBuf) -> Result<Self, WasmerPackageError> {
239 Self::from_json_manifest_with_strictness(manifest, Strictness::default())
240 }
241
242 pub fn from_json_manifest_with_strictness(
244 manifest: PathBuf,
245 strictness: Strictness,
246 ) -> Result<Self, WasmerPackageError> {
247 let base_dir = manifest
248 .parent()
249 .expect("Canonicalizing should always result in a file with a parent directory")
250 .to_path_buf();
251
252 let base_dir: BaseDir = base_dir.into();
253
254 let contents = std::fs::read(&manifest)?;
255 let manifest: WebcManifest =
256 serde_json::from_slice(&contents).map_err(|e| WasmerPackageError::JsonDeserialize {
257 path: manifest.clone(),
258 error: e,
259 })?;
260
261 let mut atoms = BTreeMap::<String, OwnedBuffer>::new();
262 for atom in manifest.atoms.keys() {
263 let path = base_dir.path().join(atom);
264
265 let contents = std::fs::read(&path)
266 .map_err(|e| WasmerPackageError::FileRead { path, error: e })?;
267
268 atoms.insert(atom.clone(), contents.into());
269 }
270
271 let mut volumes: BTreeMap<String, Arc<dyn WasmerPackageVolume + Send + Sync + 'static>> =
272 BTreeMap::new();
273 if let Some(fs_mappings) = manifest.filesystem()? {
274 for entry in fs_mappings.iter() {
275 let mut dirs = BTreeSet::new();
276 let path = entry.volume_name.strip_prefix('/').ok_or_else(|| {
277 WasmerPackageError::MalformedPath(PathBuf::from(&entry.volume_name))
278 })?;
279 let path = base_dir.path().join(path);
280 dirs.insert(path);
281
282 volumes.insert(
283 entry.volume_name.clone(),
284 Arc::new(FsVolume::new(
285 entry.volume_name.clone(),
286 base_dir.path().to_owned(),
287 BTreeSet::new(),
288 dirs,
289 )),
290 );
291 }
292 }
293
294 let mut files = BTreeSet::new();
295 for entry in std::fs::read_dir(base_dir.path().join(FsVolume::METADATA))? {
296 let entry = entry?;
297
298 files.insert(entry.path());
299 }
300
301 if let Some(wapm) = manifest.wapm().unwrap() {
302 if let Some(license_file) = wapm.license_file.as_ref() {
303 let path = license_file.path.strip_prefix('/').ok_or_else(|| {
304 WasmerPackageError::MalformedPath(PathBuf::from(&license_file.path))
305 })?;
306 let path = base_dir.path().join(FsVolume::METADATA).join(path);
307
308 files.insert(path);
309 }
310
311 if let Some(readme_file) = wapm.readme.as_ref() {
312 let path = readme_file.path.strip_prefix('/').ok_or_else(|| {
313 WasmerPackageError::MalformedPath(PathBuf::from(&readme_file.path))
314 })?;
315 let path = base_dir.path().join(FsVolume::METADATA).join(path);
316
317 files.insert(path);
318 }
319 }
320
321 volumes.insert(
322 FsVolume::METADATA.to_string(),
323 Arc::new(FsVolume::new_with_intermediate_dirs(
324 FsVolume::METADATA.to_string(),
325 base_dir.path().join(FsVolume::METADATA).to_owned(),
326 files,
327 BTreeSet::new(),
328 )),
329 );
330
331 Ok(Package {
332 base_dir,
333 manifest,
334 atoms,
335 strictness,
336 volumes,
337 })
338 }
339
340 pub fn from_in_memory(
342 manifest: WasmerManifest,
343 volumes: BTreeMap<String, MemoryVolume>,
344 atoms: BTreeMap<String, (Option<String>, OwnedBuffer)>,
345 metadata: MemoryVolume,
346 strictness: Strictness,
347 ) -> Result<Self, WasmerPackageError> {
348 let mut new_volumes = BTreeMap::new();
349
350 for (k, v) in volumes.into_iter() {
351 new_volumes.insert(k, Arc::new(v) as _);
352 }
353
354 new_volumes.insert(MemoryVolume::METADATA.to_string(), Arc::new(metadata) as _);
355
356 let volumes = new_volumes;
357
358 let (mut manifest, atoms) =
359 super::manifest::in_memory_wasmer_manifest_to_webc(&manifest, &atoms)?;
360
361 if let Some(entry) = manifest.package.get_mut(Wapm::KEY) {
362 let mut wapm: Wapm = entry.deserialized()?;
363
364 wapm.name.take();
365 wapm.version.take();
366 wapm.description.take();
367
368 *entry = ciborium::value::Value::serialized(&wapm)?;
369 };
370
371 Ok(Package {
372 base_dir: BaseDir::Path(Path::new("/").to_path_buf()),
373 manifest,
374 atoms,
375 strictness,
376 volumes,
377 })
378 }
379
380 fn load(
381 wasmer_toml: WasmerManifest,
382 base_dir: impl Into<BaseDir>,
383 strictness: Strictness,
384 ) -> Result<Self, WasmerPackageError> {
385 let base_dir = base_dir.into();
386
387 if strictness.is_strict() {
388 wasmer_toml.validate()?;
389 }
390
391 let (mut manifest, atoms) =
392 wasmer_manifest_to_webc(&wasmer_toml, base_dir.path(), strictness)?;
393
394 if let Some(entry) = manifest.package.get_mut(Wapm::KEY) {
396 let mut wapm: Wapm = entry.deserialized()?;
397
398 wapm.name.take();
399 wapm.version.take();
400 wapm.description.take();
401
402 *entry = ciborium::value::Value::serialized(&wapm)?;
403 };
404
405 let base_dir_path = base_dir.path().to_path_buf();
407 let metadata_volume = FsVolume::new_metadata(&wasmer_toml, base_dir_path.clone())?;
409 let mut volumes: BTreeMap<String, Arc<dyn WasmerPackageVolume + Send + Sync + 'static>> = {
411 let old = FsVolume::new_assets(&wasmer_toml, &base_dir_path)?;
412 let mut new = BTreeMap::new();
413
414 for (k, v) in old.into_iter() {
415 new.insert(k, Arc::new(v) as _);
416 }
417
418 new
419 };
420 volumes.insert(
421 metadata_volume.name().to_string(),
422 Arc::new(metadata_volume),
423 );
424
425 Ok(Package {
426 base_dir,
427 manifest,
428 atoms,
429 strictness,
430 volumes,
431 })
432 }
433
434 pub fn webc_hash(&self) -> Option<[u8; 32]> {
436 None
437 }
438
439 pub fn manifest(&self) -> &WebcManifest {
441 &self.manifest
442 }
443
444 pub fn atoms(&self) -> &BTreeMap<String, OwnedBuffer> {
446 &self.atoms
447 }
448
449 pub fn volumes(
451 &self,
452 ) -> impl Iterator<Item = &Arc<dyn WasmerPackageVolume + Sync + Send + 'static>> {
453 self.volumes.values()
454 }
455
456 pub fn serialize(&self) -> Result<Bytes, Error> {
459 let mut w = Writer::new(ChecksumAlgorithm::Sha256)
460 .write_manifest(self.manifest())?
461 .write_atoms(self.atom_entries()?)?;
462
463 for (name, volume) in &self.volumes {
464 w.write_volume(name.as_str(), volume.as_directory_tree(self.strictness)?)?;
465 }
466
467 let serialized = w.finish(webc::v3::SignatureAlgorithm::None)?;
468
469 Ok(serialized)
470 }
471
472 fn atom_entries(&self) -> Result<BTreeMap<PathSegment, FileEntry<'_>>, Error> {
473 self.atoms()
474 .iter()
475 .map(|(key, value)| {
476 let filename = PathSegment::parse(key)
477 .with_context(|| format!("\"{key}\" isn't a valid atom name"))?;
478 Ok((filename, FileEntry::borrowed(value, Timestamps::default())))
480 })
481 .collect()
482 }
483
484 pub(crate) fn get_volume(
485 &self,
486 name: &str,
487 ) -> Option<Arc<dyn WasmerPackageVolume + Sync + Send + 'static>> {
488 self.volumes.get(name).cloned()
489 }
490
491 pub(crate) fn volume_names(&self) -> Vec<Cow<'_, str>> {
492 self.volumes
493 .keys()
494 .map(|name| Cow::Borrowed(name.as_str()))
495 .collect()
496 }
497}
498
499impl AbstractWebc for Package {
500 fn version(&self) -> Version {
501 Version::V3
502 }
503
504 fn manifest(&self) -> &WebcManifest {
505 self.manifest()
506 }
507
508 fn atom_names(&self) -> Vec<Cow<'_, str>> {
509 self.atoms()
510 .keys()
511 .map(|s| Cow::Borrowed(s.as_str()))
512 .collect()
513 }
514
515 fn get_atom(&self, name: &str) -> Option<OwnedBuffer> {
516 self.atoms().get(name).cloned()
517 }
518
519 fn get_webc_hash(&self) -> Option<[u8; 32]> {
520 self.webc_hash()
521 }
522
523 fn get_atoms_hash(&self) -> Option<[u8; 32]> {
524 None
525 }
526
527 fn volume_names(&self) -> Vec<Cow<'_, str>> {
528 self.volume_names()
529 }
530
531 fn get_volume(&self, name: &str) -> Option<Volume> {
532 self.get_volume(name).map(|v| {
533 let a: Arc<dyn AbstractVolume + Send + Sync + 'static> = v.as_volume();
534
535 Volume::from(a)
536 })
537 }
538}
539
540impl From<Package> for Container {
541 fn from(value: Package) -> Self {
542 Container::new(value)
543 }
544}
545
546const IS_WASI: bool = cfg!(all(target_family = "wasm", target_os = "wasi"));
547
548fn tempdir() -> Result<TempDir, std::io::Error> {
556 if !IS_WASI {
557 return TempDir::new();
559 }
560
561 let temp_dir: PathBuf = std::env::var("TMPDIR")
564 .unwrap_or_else(|_| "/tmp".to_string())
565 .into();
566
567 if temp_dir.exists() {
568 TempDir::new_in(temp_dir)
569 } else {
570 if let Ok(current_exe) = std::env::current_exe() {
576 if let Some(parent) = current_exe.parent() {
577 if let Ok(temp) = TempDir::new_in(parent) {
578 return Ok(temp);
579 }
580 }
581 }
582
583 std::fs::create_dir_all(&temp_dir)?;
585 TempDir::new_in(temp_dir)
586 }
587}
588
589fn unpack_archive(
596 mut archive: Archive<impl std::io::Read>,
597 dest: &Path,
598) -> Result<(), std::io::Error> {
599 cfg_if::cfg_if! {
600 if #[cfg(all(target_family = "wasm", target_os = "wasi"))]
601 {
602 for entry in archive.entries()? {
605 let mut entry = entry?;
606 let item_path = entry.path()?;
607 let full_path = resolve_archive_path(dest, &item_path);
608
609 match entry.header().entry_type() {
610 tar::EntryType::Directory => {
611 std::fs::create_dir_all(&full_path)?;
612 }
613 tar::EntryType::Regular => {
614 if let Some(parent) = full_path.parent() {
615 std::fs::create_dir_all(parent)?;
616 }
617 let mut f = File::create(&full_path)?;
618 std::io::copy(&mut entry, &mut f)?;
619
620 let mtime = entry.header().mtime().unwrap_or_default();
621 if let Err(e) = set_timestamp(full_path.as_path(), mtime) {
622 println!("WARN: {e:?}");
623 }
624 }
625 _ => {}
626 }
627 }
628 Ok(())
629
630 } else {
631 archive.unpack(dest)
632 }
633 }
634}
635
636#[cfg(all(target_family = "wasm", target_os = "wasi"))]
637fn set_timestamp(path: &Path, timestamp: u64) -> Result<(), anyhow::Error> {
638 let fd = unsafe {
639 libc::open(
640 path.as_os_str().as_encoded_bytes().as_ptr() as _,
641 libc::O_RDONLY,
642 )
643 };
644
645 if fd < 0 {
646 anyhow::bail!(format!("failed to open: {}", path.display()));
647 }
648
649 let timespec = [
650 libc::timespec {
652 tv_sec: unsafe { libc::time(std::ptr::null_mut()) }, tv_nsec: 0,
654 },
655 libc::timespec {
657 tv_sec: timestamp as i64,
658 tv_nsec: 0,
659 },
660 ];
661
662 let res = unsafe { libc::futimens(fd, timespec.as_ptr() as _) };
663
664 if res < 0 {
665 anyhow::bail!("failed to set timestamp for: {}", path.display());
666 }
667
668 Ok(())
669}
670
671#[cfg(all(target_family = "wasm", target_os = "wasi"))]
672fn resolve_archive_path(base_dir: &Path, path: &Path) -> PathBuf {
673 let mut buffer = base_dir.to_path_buf();
674
675 for component in path.components() {
676 match component {
677 std::path::Component::Prefix(_)
678 | std::path::Component::RootDir
679 | std::path::Component::CurDir => continue,
680 std::path::Component::ParentDir => {
681 buffer.pop();
682 }
683 std::path::Component::Normal(segment) => {
684 buffer.push(segment);
685 }
686 }
687 }
688
689 buffer
690}
691
692fn read_manifest(base_dir: &Path) -> Result<(PathBuf, WasmerManifest), WasmerPackageError> {
693 for path in ["wasmer.toml", "wapm.toml"] {
694 let path = base_dir.join(path);
695
696 match std::fs::read_to_string(&path) {
697 Ok(s) => {
698 let toml_file = toml::from_str(&s).map_err({
699 let path = path.clone();
700 |error| WasmerPackageError::TomlDeserialize { path, error }
701 })?;
702
703 return Ok((path, toml_file));
704 }
705 Err(e) if e.kind() == std::io::ErrorKind::NotFound => continue,
706 Err(error) => {
707 return Err(WasmerPackageError::FileRead { path, error });
708 }
709 }
710 }
711
712 Err(WasmerPackageError::MissingManifest)
713}
714
715#[derive(Debug)]
716enum BaseDir {
717 Path(PathBuf),
719 Temp(TempDir),
721}
722
723impl BaseDir {
724 fn path(&self) -> &Path {
725 match self {
726 BaseDir::Path(p) => p.as_path(),
727 BaseDir::Temp(t) => t.path(),
728 }
729 }
730}
731
732impl From<TempDir> for BaseDir {
733 fn from(v: TempDir) -> Self {
734 Self::Temp(v)
735 }
736}
737
738impl From<PathBuf> for BaseDir {
739 fn from(v: PathBuf) -> Self {
740 Self::Path(v)
741 }
742}
743
744#[cfg(test)]
745mod tests {
746 use std::{
747 collections::BTreeMap,
748 fs::File,
749 path::{Path, PathBuf},
750 str::FromStr,
751 time::SystemTime,
752 };
753
754 use flate2::{write::GzEncoder, Compression};
755 use sha2::Digest;
756 use shared_buffer::OwnedBuffer;
757 use tempfile::TempDir;
758 use webc::{
759 metadata::{
760 annotations::{FileSystemMapping, VolumeSpecificPath},
761 Binding, BindingsExtended, WaiBindings, WitBindings,
762 },
763 PathSegment, PathSegments,
764 };
765
766 use crate::{package::*, utils::from_bytes};
767
768 #[test]
769 fn nonexistent_files() {
770 let temp = TempDir::new().unwrap();
771
772 assert!(Package::from_manifest(temp.path().join("nonexistent.toml")).is_err());
773 assert!(Package::from_tarball_file(temp.path().join("nonexistent.tar.gz")).is_err());
774 }
775
776 #[test]
777 fn load_a_tarball() {
778 let coreutils = Path::new(env!("CARGO_MANIFEST_DIR"))
779 .join("..")
780 .join("..")
781 .join("tests")
782 .join("old-tar-gz")
783 .join("coreutils-1.0.11.tar.gz");
784 assert!(coreutils.exists());
785
786 let package = Package::from_tarball_file(coreutils).unwrap();
787
788 let wapm = package.manifest().wapm().unwrap().unwrap();
789 assert!(wapm.name.is_none());
790 assert!(wapm.version.is_none());
791 assert!(wapm.description.is_none());
792 }
793
794 #[test]
795 fn tarball_with_no_manifest() {
796 let temp = TempDir::new().unwrap();
797 let empty_tarball = temp.path().join("empty.tar.gz");
798 let mut f = File::create(&empty_tarball).unwrap();
799 tar::Builder::new(GzEncoder::new(&mut f, Compression::fast()))
800 .finish()
801 .unwrap();
802
803 assert!(Package::from_tarball_file(&empty_tarball).is_err());
804 }
805
806 #[test]
807 fn empty_package_on_disk() {
808 let temp = TempDir::new().unwrap();
809 let manifest = temp.path().join("wasmer.toml");
810 std::fs::write(
811 &manifest,
812 r#"
813 [package]
814 name = "some/package"
815 version = "0.0.0"
816 description = "A dummy package"
817 "#,
818 )
819 .unwrap();
820
821 let package = Package::from_manifest(&manifest).unwrap();
822
823 let wapm = package.manifest().wapm().unwrap().unwrap();
824 assert!(wapm.name.is_none());
825 assert!(wapm.version.is_none());
826 assert!(wapm.description.is_none());
827 }
828
829 #[test]
830 fn load_old_cowsay() {
831 let tarball = Path::new(env!("CARGO_MANIFEST_DIR"))
832 .join("..")
833 .join("..")
834 .join("tests")
835 .join("old-tar-gz")
836 .join("cowsay-0.3.0.tar.gz");
837
838 let pkg = Package::from_tarball_file(tarball).unwrap();
839
840 insta::assert_yaml_snapshot!(pkg.manifest());
841 assert_eq!(
842 pkg.manifest.commands.keys().collect::<Vec<_>>(),
843 ["cowsay", "cowthink"],
844 );
845 }
846
847 #[test]
848 fn serialize_package_with_non_existent_fs() {
849 let temp = TempDir::new().unwrap();
850 let wasmer_toml = r#"
851 [package]
852 name = "some/package"
853 version = "0.0.0"
854 description = "Test package"
855
856 [fs]
857 "/first" = "./first"
858 "#;
859 let manifest = temp.path().join("wasmer.toml");
860
861 std::fs::write(&manifest, wasmer_toml).unwrap();
862
863 let error = Package::from_manifest(manifest).unwrap_err();
864
865 match error {
866 WasmerPackageError::PathNotExists { path } => {
867 assert_eq!(path, PathBuf::from_str("./first").unwrap());
868 }
869 e => panic!("unexpected error: {e:?}"),
870 }
871 }
872
873 #[test]
874 fn serialize_package_with_bundled_directories() {
875 let temp = TempDir::new().unwrap();
876 let wasmer_toml = r#"
877 [package]
878 name = "some/package"
879 version = "0.0.0"
880 description = "Test package"
881
882 [fs]
883 "/first" = "first"
884 second = "nested/dir"
885 "second/child" = "third"
886 empty = "empty"
887 "#;
888 let manifest = temp.path().join("wasmer.toml");
889 std::fs::write(&manifest, wasmer_toml).unwrap();
890 let first = temp.path().join("first");
908 std::fs::create_dir_all(&first).unwrap();
909 std::fs::write(first.join("file.txt"), "File").unwrap();
910 let second = temp.path().join("nested").join("dir");
912 std::fs::create_dir_all(&second).unwrap();
913 std::fs::write(second.join(".wasmerignore"), "ignore_me").unwrap();
914 std::fs::write(second.join(".hidden"), "something something").unwrap();
915 std::fs::write(second.join("ignore_me"), "something something").unwrap();
916 std::fs::write(second.join("README.md"), "please").unwrap();
917 let another_dir = temp.path().join("nested").join("dir").join("another-dir");
918 std::fs::create_dir_all(&another_dir).unwrap();
919 std::fs::write(another_dir.join("empty.txt"), "").unwrap();
920 let third = temp.path().join("third");
922 std::fs::create_dir_all(&third).unwrap();
923 std::fs::write(third.join("file.txt"), "Hello, World!").unwrap();
924 let empty_dir = temp.path().join("empty");
926 std::fs::create_dir_all(empty_dir).unwrap();
927
928 let package = Package::from_manifest(manifest).unwrap();
929
930 let webc = package.serialize().unwrap();
931 let webc = from_bytes(webc).unwrap();
932 let manifest = webc.manifest();
933 let wapm_metadata = manifest.wapm().unwrap().unwrap();
934 assert!(wapm_metadata.name.is_none());
935 assert!(wapm_metadata.version.is_none());
936 assert!(wapm_metadata.description.is_none());
937 let fs_table = manifest.filesystem().unwrap().unwrap();
938 assert_eq!(
939 fs_table,
940 [
941 FileSystemMapping {
942 from: None,
943 volume_name: "/first".to_string(),
944 host_path: None,
945 mount_path: "/first".to_string(),
946 },
947 FileSystemMapping {
948 from: None,
949 volume_name: "/nested/dir".to_string(),
950 host_path: None,
951 mount_path: "/second".to_string(),
952 },
953 FileSystemMapping {
954 from: None,
955 volume_name: "/third".to_string(),
956 host_path: None,
957 mount_path: "/second/child".to_string(),
958 },
959 FileSystemMapping {
960 from: None,
961 volume_name: "/empty".to_string(),
962 host_path: None,
963 mount_path: "/empty".to_string(),
964 },
965 ]
966 );
967
968 let first_file_hash: [u8; 32] = sha2::Sha256::digest(b"File").into();
969 let readme_hash: [u8; 32] = sha2::Sha256::digest(b"please").into();
970 let empty_hash: [u8; 32] = sha2::Sha256::digest(b"").into();
971 let third_file_hash: [u8; 32] = sha2::Sha256::digest(b"Hello, World!").into();
972
973 let first_volume = webc.get_volume("/first").unwrap();
974 assert_eq!(
975 first_volume.read_file("/file.txt").unwrap(),
976 (b"File".as_slice().into(), Some(first_file_hash)),
977 );
978
979 let nested_dir_volume = webc.get_volume("/nested/dir").unwrap();
980 assert_eq!(
981 nested_dir_volume.read_file("README.md").unwrap(),
982 (b"please".as_slice().into(), Some(readme_hash)),
983 );
984 assert!(nested_dir_volume.read_file(".wasmerignore").is_none());
985 assert!(nested_dir_volume.read_file(".hidden").is_none());
986 assert!(nested_dir_volume.read_file("ignore_me").is_none());
987 assert_eq!(
988 nested_dir_volume
989 .read_file("/another-dir/empty.txt")
990 .unwrap(),
991 (b"".as_slice().into(), Some(empty_hash))
992 );
993
994 let third_volume = webc.get_volume("/third").unwrap();
995 assert_eq!(
996 third_volume.read_file("/file.txt").unwrap(),
997 (b"Hello, World!".as_slice().into(), Some(third_file_hash))
998 );
999
1000 let empty_volume = webc.get_volume("/empty").unwrap();
1001 assert_eq!(
1002 empty_volume.read_dir("/").unwrap().len(),
1003 0,
1004 "Directories should be included, even if empty"
1005 );
1006 }
1007
1008 #[test]
1009 fn serialize_package_with_metadata_files() {
1010 let temp = TempDir::new().unwrap();
1011 let wasmer_toml = r#"
1012 [package]
1013 name = "some/package"
1014 version = "0.0.0"
1015 description = "Test package"
1016 readme = "README.md"
1017 license-file = "LICENSE"
1018 "#;
1019 let manifest = temp.path().join("wasmer.toml");
1020 std::fs::write(&manifest, wasmer_toml).unwrap();
1021 std::fs::write(temp.path().join("README.md"), "readme").unwrap();
1022 std::fs::write(temp.path().join("LICENSE"), "license").unwrap();
1023
1024 let serialized = Package::from_manifest(manifest)
1025 .unwrap()
1026 .serialize()
1027 .unwrap();
1028
1029 let webc = from_bytes(serialized).unwrap();
1030 let metadata_volume = webc.get_volume("metadata").unwrap();
1031
1032 let readme_hash: [u8; 32] = sha2::Sha256::digest(b"readme").into();
1033 let license_hash: [u8; 32] = sha2::Sha256::digest(b"license").into();
1034
1035 assert_eq!(
1036 metadata_volume.read_file("/README.md").unwrap(),
1037 (b"readme".as_slice().into(), Some(readme_hash))
1038 );
1039 assert_eq!(
1040 metadata_volume.read_file("/LICENSE").unwrap(),
1041 (b"license".as_slice().into(), Some(license_hash))
1042 );
1043 }
1044
1045 #[test]
1046 fn load_package_with_wit_bindings() {
1047 let temp = TempDir::new().unwrap();
1048 let wasmer_toml = r#"
1049 [package]
1050 name = "some/package"
1051 version = "0.0.0"
1052 description = ""
1053
1054 [[module]]
1055 name = "my-lib"
1056 source = "./my-lib.wasm"
1057 abi = "none"
1058 bindings = { wit-bindgen = "0.1.0", wit-exports = "./file.wit" }
1059 "#;
1060 std::fs::write(temp.path().join("wasmer.toml"), wasmer_toml).unwrap();
1061 std::fs::write(temp.path().join("file.wit"), "file").unwrap();
1062 std::fs::write(temp.path().join("my-lib.wasm"), b"\0asm...").unwrap();
1063
1064 let package = Package::from_manifest(temp.path().join("wasmer.toml"))
1065 .unwrap()
1066 .serialize()
1067 .unwrap();
1068 let webc = from_bytes(package).unwrap();
1069
1070 assert_eq!(
1071 webc.manifest().bindings,
1072 vec![Binding {
1073 name: "library-bindings".to_string(),
1074 kind: "wit@0.1.0".to_string(),
1075 annotations: ciborium::value::Value::serialized(&BindingsExtended::Wit(
1076 WitBindings {
1077 exports: "metadata://file.wit".to_string(),
1078 module: "my-lib".to_string(),
1079 }
1080 ))
1081 .unwrap(),
1082 }]
1083 );
1084 let metadata_volume = webc.get_volume("metadata").unwrap();
1085 let file_hash: [u8; 32] = sha2::Sha256::digest(b"file").into();
1086 assert_eq!(
1087 metadata_volume.read_file("/file.wit").unwrap(),
1088 (b"file".as_slice().into(), Some(file_hash))
1089 );
1090 insta::with_settings! {
1091 { description => wasmer_toml },
1092 { insta::assert_yaml_snapshot!(webc.manifest()); }
1093 }
1094 }
1095
1096 #[test]
1097 fn load_package_with_wai_bindings() {
1098 let temp = TempDir::new().unwrap();
1099 let wasmer_toml = r#"
1100 [package]
1101 name = "some/package"
1102 version = "0.0.0"
1103 description = ""
1104
1105 [[module]]
1106 name = "my-lib"
1107 source = "./my-lib.wasm"
1108 abi = "none"
1109 bindings = { wai-version = "0.2.0", exports = "./file.wai", imports = ["a.wai", "b.wai"] }
1110 "#;
1111 std::fs::write(temp.path().join("wasmer.toml"), wasmer_toml).unwrap();
1112 std::fs::write(temp.path().join("file.wai"), "file").unwrap();
1113 std::fs::write(temp.path().join("a.wai"), "a").unwrap();
1114 std::fs::write(temp.path().join("b.wai"), "b").unwrap();
1115 std::fs::write(temp.path().join("my-lib.wasm"), b"\0asm...").unwrap();
1116
1117 let package = Package::from_manifest(temp.path().join("wasmer.toml"))
1118 .unwrap()
1119 .serialize()
1120 .unwrap();
1121 let webc = from_bytes(package).unwrap();
1122
1123 assert_eq!(
1124 webc.manifest().bindings,
1125 vec![Binding {
1126 name: "library-bindings".to_string(),
1127 kind: "wai@0.2.0".to_string(),
1128 annotations: ciborium::value::Value::serialized(&BindingsExtended::Wai(
1129 WaiBindings {
1130 exports: Some("metadata://file.wai".to_string()),
1131 module: "my-lib".to_string(),
1132 imports: vec![
1133 "metadata://a.wai".to_string(),
1134 "metadata://b.wai".to_string(),
1135 ]
1136 }
1137 ))
1138 .unwrap(),
1139 }]
1140 );
1141 let metadata_volume = webc.get_volume("metadata").unwrap();
1142
1143 let file_hash: [u8; 32] = sha2::Sha256::digest(b"file").into();
1144 let a_hash: [u8; 32] = sha2::Sha256::digest(b"a").into();
1145 let b_hash: [u8; 32] = sha2::Sha256::digest(b"b").into();
1146
1147 assert_eq!(
1148 metadata_volume.read_file("/file.wai").unwrap(),
1149 (b"file".as_slice().into(), Some(file_hash))
1150 );
1151 assert_eq!(
1152 metadata_volume.read_file("/a.wai").unwrap(),
1153 (b"a".as_slice().into(), Some(a_hash))
1154 );
1155 assert_eq!(
1156 metadata_volume.read_file("/b.wai").unwrap(),
1157 (b"b".as_slice().into(), Some(b_hash))
1158 );
1159 insta::with_settings! {
1160 { description => wasmer_toml },
1161 { insta::assert_yaml_snapshot!(webc.manifest()); }
1162 }
1163 }
1164
1165 #[test]
1167 fn absolute_paths_in_wasmer_toml_issue_105() {
1168 let temp = TempDir::new().unwrap();
1169 let base_dir = temp.path().canonicalize().unwrap();
1170 let sep = std::path::MAIN_SEPARATOR;
1171 let wasmer_toml = format!(
1172 r#"
1173 [package]
1174 name = 'some/package'
1175 version = '0.0.0'
1176 description = 'Test package'
1177 readme = '{BASE_DIR}{sep}README.md'
1178 license-file = '{BASE_DIR}{sep}LICENSE'
1179
1180 [[module]]
1181 name = 'first'
1182 source = '{BASE_DIR}{sep}target{sep}debug{sep}package.wasm'
1183 bindings = {{ wai-version = '0.2.0', exports = '{BASE_DIR}{sep}bindings{sep}file.wai', imports = ['{BASE_DIR}{sep}bindings{sep}a.wai'] }}
1184 "#,
1185 BASE_DIR = base_dir.display(),
1186 );
1187 let manifest = temp.path().join("wasmer.toml");
1188 std::fs::write(&manifest, &wasmer_toml).unwrap();
1189 std::fs::write(temp.path().join("README.md"), "readme").unwrap();
1190 std::fs::write(temp.path().join("LICENSE"), "license").unwrap();
1191 let bindings = temp.path().join("bindings");
1192 std::fs::create_dir_all(&bindings).unwrap();
1193 std::fs::write(bindings.join("file.wai"), "file.wai").unwrap();
1194 std::fs::write(bindings.join("a.wai"), "a.wai").unwrap();
1195 let target = temp.path().join("target").join("debug");
1196 std::fs::create_dir_all(&target).unwrap();
1197 std::fs::write(target.join("package.wasm"), b"\0asm...").unwrap();
1198
1199 let serialized = Package::from_manifest(manifest)
1200 .unwrap()
1201 .serialize()
1202 .unwrap();
1203
1204 let webc = from_bytes(serialized).unwrap();
1205 let manifest = webc.manifest();
1206 let wapm = manifest.wapm().unwrap().unwrap();
1207
1208 let lookup = |item: VolumeSpecificPath| {
1210 let volume = webc.get_volume(&item.volume).unwrap();
1211 let (contents, _) = volume.read_file(&item.path).unwrap();
1212 String::from_utf8(contents.into()).unwrap()
1213 };
1214 assert_eq!(lookup(wapm.license_file.unwrap()), "license");
1215 assert_eq!(lookup(wapm.readme.unwrap()), "readme");
1216
1217 let lookup = |item: &str| {
1220 let (volume, path) = item.split_once(":/").unwrap();
1221 let volume = webc.get_volume(volume).unwrap();
1222 let (content, _) = volume.read_file(path).unwrap();
1223 String::from_utf8(content.into()).unwrap()
1224 };
1225 let bindings = manifest.bindings[0].get_wai_bindings().unwrap();
1226 assert_eq!(lookup(&bindings.imports[0]), "a.wai");
1227 assert_eq!(lookup(bindings.exports.unwrap().as_str()), "file.wai");
1228
1229 let mut settings = insta::Settings::clone_current();
1231 let base_dir = base_dir.display().to_string();
1232 settings.set_description(wasmer_toml.replace(&base_dir, "[BASE_DIR]"));
1233 let filter = regex::escape(&base_dir);
1234 settings.add_filter(&filter, "[BASE_DIR]");
1235 settings.bind(|| {
1236 insta::assert_yaml_snapshot!(webc.manifest());
1237 });
1238 }
1239
1240 #[test]
1241 fn serializing_will_skip_missing_metadata_by_default() {
1242 let temp = TempDir::new().unwrap();
1243 let wasmer_toml = r#"
1244 [package]
1245 name = 'some/package'
1246 version = '0.0.0'
1247 description = 'Test package'
1248 readme = '/this/does/not/exist/README.md'
1249 license-file = 'LICENSE.wtf'
1250 "#;
1251 let manifest = temp.path().join("wasmer.toml");
1252 std::fs::write(&manifest, wasmer_toml).unwrap();
1253 let pkg = Package::from_manifest(manifest).unwrap();
1254
1255 let serialized = pkg.serialize().unwrap();
1256
1257 let webc = from_bytes(serialized).unwrap();
1258 let manifest = webc.manifest();
1259 let wapm = manifest.wapm().unwrap().unwrap();
1260 assert!(wapm.license_file.is_none());
1262 assert!(wapm.readme.is_none());
1263
1264 let pkg = Package {
1266 strictness: Strictness::Strict,
1267 ..pkg
1268 };
1269 assert!(pkg.serialize().is_err());
1270 }
1271
1272 #[test]
1273 fn serialize_package_without_local_base_fs_paths() {
1274 let temp = TempDir::new().unwrap();
1275 let wasmer_toml = r#"
1276 [package]
1277 name = "some/package"
1278 version = "0.0.0"
1279 description = "Test package"
1280 readme = 'README.md'
1281 license-file = 'LICENSE'
1282
1283 [fs]
1284 "/path_in_wasix" = "local-dir/dir1"
1285 "#;
1286 let manifest = temp.path().join("wasmer.toml");
1287 std::fs::write(&manifest, wasmer_toml).unwrap();
1288
1289 std::fs::write(temp.path().join("README.md"), "readme").unwrap();
1290 std::fs::write(temp.path().join("LICENSE"), "license").unwrap();
1291
1292 let dir1 = temp.path().join("local-dir").join("dir1");
1299 std::fs::create_dir_all(&dir1).unwrap();
1300
1301 let a = dir1.join("a");
1302 let b = dir1.join("b");
1303
1304 std::fs::write(a, "a").unwrap();
1305 std::fs::write(b, "b").unwrap();
1306
1307 let package = Package::from_manifest(manifest).unwrap();
1308
1309 let webc = package.serialize().unwrap();
1310 let webc = from_bytes(webc).unwrap();
1311 let manifest = webc.manifest();
1312 let wapm_metadata = manifest.wapm().unwrap().unwrap();
1313
1314 assert!(wapm_metadata.name.is_none());
1315 assert!(wapm_metadata.version.is_none());
1316 assert!(wapm_metadata.description.is_none());
1317
1318 let fs_table = manifest.filesystem().unwrap().unwrap();
1319 assert_eq!(
1320 fs_table,
1321 [FileSystemMapping {
1322 from: None,
1323 volume_name: "/local-dir/dir1".to_string(),
1324 host_path: None,
1325 mount_path: "/path_in_wasix".to_string(),
1326 },]
1327 );
1328
1329 let readme_hash: [u8; 32] = sha2::Sha256::digest(b"readme").into();
1330 let license_hash: [u8; 32] = sha2::Sha256::digest(b"license").into();
1331
1332 let a_hash: [u8; 32] = sha2::Sha256::digest(b"a").into();
1333 let b_hash: [u8; 32] = sha2::Sha256::digest(b"b").into();
1334
1335 let dir1_volume = webc.get_volume("/local-dir/dir1").unwrap();
1336 let meta_volume = webc.get_volume("metadata").unwrap();
1337
1338 assert_eq!(
1339 meta_volume.read_file("LICENSE").unwrap(),
1340 (b"license".as_slice().into(), Some(license_hash)),
1341 );
1342 assert_eq!(
1343 meta_volume.read_file("README.md").unwrap(),
1344 (b"readme".as_slice().into(), Some(readme_hash)),
1345 );
1346 assert_eq!(
1347 dir1_volume.read_file("a").unwrap(),
1348 (b"a".as_slice().into(), Some(a_hash))
1349 );
1350 assert_eq!(
1351 dir1_volume.read_file("b").unwrap(),
1352 (b"b".as_slice().into(), Some(b_hash))
1353 );
1354 }
1355
1356 #[test]
1357 fn serialize_package_with_nested_fs_entries_without_local_base_fs_paths() {
1358 let temp = TempDir::new().unwrap();
1359 let wasmer_toml = r#"
1360 [package]
1361 name = "some/package"
1362 version = "0.0.0"
1363 description = "Test package"
1364 readme = 'README.md'
1365 license-file = 'LICENSE'
1366
1367 [fs]
1368 "/path_in_wasix" = "local-dir/dir1"
1369 "#;
1370 let manifest = temp.path().join("wasmer.toml");
1371 std::fs::write(&manifest, wasmer_toml).unwrap();
1372
1373 std::fs::write(temp.path().join("README.md"), "readme").unwrap();
1374 std::fs::write(temp.path().join("LICENSE"), "license").unwrap();
1375
1376 let local_dir = temp.path().join("local-dir");
1384 std::fs::create_dir_all(&local_dir).unwrap();
1385
1386 let dir1 = local_dir.join("dir1");
1387 std::fs::create_dir_all(&dir1).unwrap();
1388
1389 let dir2 = dir1.join("dir2");
1390 std::fs::create_dir_all(&dir2).unwrap();
1391
1392 let a = dir2.join("a");
1393 let b = dir1.join("b");
1394
1395 std::fs::write(a, "a").unwrap();
1396 std::fs::write(b, "b").unwrap();
1397
1398 let package = Package::from_manifest(manifest).unwrap();
1399
1400 let webc = package.serialize().unwrap();
1401 let webc = from_bytes(webc).unwrap();
1402 let manifest = webc.manifest();
1403 let wapm_metadata = manifest.wapm().unwrap().unwrap();
1404
1405 assert!(wapm_metadata.name.is_none());
1406 assert!(wapm_metadata.version.is_none());
1407 assert!(wapm_metadata.description.is_none());
1408
1409 let fs_table = manifest.filesystem().unwrap().unwrap();
1410 assert_eq!(
1411 fs_table,
1412 [FileSystemMapping {
1413 from: None,
1414 volume_name: "/local-dir/dir1".to_string(),
1415 host_path: None,
1416 mount_path: "/path_in_wasix".to_string(),
1417 },]
1418 );
1419
1420 let readme_hash: [u8; 32] = sha2::Sha256::digest(b"readme").into();
1421 let license_hash: [u8; 32] = sha2::Sha256::digest(b"license").into();
1422
1423 let a_hash: [u8; 32] = sha2::Sha256::digest(b"a").into();
1424 let dir2_hash: [u8; 32] = sha2::Sha256::digest(a_hash).into();
1425 let b_hash: [u8; 32] = sha2::Sha256::digest(b"b").into();
1426
1427 let dir1_volume = webc.get_volume("/local-dir/dir1").unwrap();
1428 let meta_volume = webc.get_volume("metadata").unwrap();
1429
1430 assert_eq!(
1431 meta_volume.read_file("LICENSE").unwrap(),
1432 (b"license".as_slice().into(), Some(license_hash)),
1433 );
1434 assert_eq!(
1435 meta_volume.read_file("README.md").unwrap(),
1436 (b"readme".as_slice().into(), Some(readme_hash)),
1437 );
1438 assert_eq!(
1439 dir1_volume
1440 .read_dir("/")
1441 .unwrap()
1442 .into_iter()
1443 .map(|(p, h, _)| (p, h))
1444 .collect::<Vec<_>>(),
1445 vec![
1446 (PathSegment::parse("b").unwrap(), Some(b_hash)),
1447 (PathSegment::parse("dir2").unwrap(), Some(dir2_hash))
1448 ]
1449 );
1450 assert_eq!(
1451 dir1_volume
1452 .read_dir("/dir2")
1453 .unwrap()
1454 .into_iter()
1455 .map(|(p, h, _)| (p, h))
1456 .collect::<Vec<_>>(),
1457 vec![(PathSegment::parse("a").unwrap(), Some(a_hash))]
1458 );
1459 assert_eq!(
1460 dir1_volume.read_file("/dir2/a").unwrap(),
1461 (b"a".as_slice().into(), Some(a_hash))
1462 );
1463 assert_eq!(
1464 dir1_volume.read_file("/b").unwrap(),
1465 (b"b".as_slice().into(), Some(b_hash))
1466 );
1467 }
1468
1469 #[test]
1470 fn serialize_package_mapped_to_same_dir_without_local_base_fs_paths() {
1471 let temp = TempDir::new().unwrap();
1472 let wasmer_toml = r#"
1473 [package]
1474 name = "some/package"
1475 version = "0.0.0"
1476 description = "Test package"
1477 readme = 'README.md'
1478 license-file = 'LICENSE'
1479
1480 [fs]
1481 "/dir1" = "local-dir1/dir"
1482 "/dir2" = "local-dir2/dir"
1483 "#;
1484 let manifest = temp.path().join("wasmer.toml");
1485 std::fs::write(&manifest, wasmer_toml).unwrap();
1486
1487 std::fs::write(temp.path().join("README.md"), "readme").unwrap();
1488 std::fs::write(temp.path().join("LICENSE"), "license").unwrap();
1489
1490 let dir1 = temp.path().join("local-dir1").join("dir");
1497 std::fs::create_dir_all(&dir1).unwrap();
1498 let dir2 = temp.path().join("local-dir2").join("dir");
1499 std::fs::create_dir_all(&dir2).unwrap();
1500
1501 let package = Package::from_manifest(manifest).unwrap();
1502
1503 let webc = package.serialize().unwrap();
1504 let webc = from_bytes(webc).unwrap();
1505 let manifest = webc.manifest();
1506 let wapm_metadata = manifest.wapm().unwrap().unwrap();
1507
1508 assert!(wapm_metadata.name.is_none());
1509 assert!(wapm_metadata.version.is_none());
1510 assert!(wapm_metadata.description.is_none());
1511
1512 let fs_table = manifest.filesystem().unwrap().unwrap();
1513 assert_eq!(
1514 fs_table,
1515 [
1516 FileSystemMapping {
1517 from: None,
1518 volume_name: "/local-dir1/dir".to_string(),
1519 host_path: None,
1520 mount_path: "/dir1".to_string(),
1521 },
1522 FileSystemMapping {
1523 from: None,
1524 volume_name: "/local-dir2/dir".to_string(),
1525 host_path: None,
1526 mount_path: "/dir2".to_string(),
1527 },
1528 ]
1529 );
1530
1531 let readme_hash: [u8; 32] = sha2::Sha256::digest(b"readme").into();
1532 let license_hash: [u8; 32] = sha2::Sha256::digest(b"license").into();
1533
1534 let dir1_volume = webc.get_volume("/local-dir1/dir").unwrap();
1535 let dir2_volume = webc.get_volume("/local-dir2/dir").unwrap();
1536 let meta_volume = webc.get_volume("metadata").unwrap();
1537
1538 assert_eq!(
1539 meta_volume.read_file("LICENSE").unwrap(),
1540 (b"license".as_slice().into(), Some(license_hash)),
1541 );
1542 assert_eq!(
1543 meta_volume.read_file("README.md").unwrap(),
1544 (b"readme".as_slice().into(), Some(readme_hash)),
1545 );
1546 assert!(dir1_volume.read_dir("/").unwrap().is_empty());
1547 assert!(dir2_volume.read_dir("/").unwrap().is_empty());
1548 }
1549
1550 #[test]
1551 fn metadata_only_contains_relevant_files() {
1552 let temp = TempDir::new().unwrap();
1553 let wasmer_toml = r#"
1554 [package]
1555 name = "some/package"
1556 version = "0.0.0"
1557 description = ""
1558 license-file = "./path/to/LICENSE"
1559 readme = "README.md"
1560
1561 [[module]]
1562 name = "asdf"
1563 source = "asdf.wasm"
1564 abi = "none"
1565 bindings = { wai-version = "0.2.0", exports = "asdf.wai", imports = ["browser.wai"] }
1566 "#;
1567
1568 let manifest = temp.path().join("wasmer.toml");
1569 std::fs::write(&manifest, wasmer_toml).unwrap();
1570
1571 let license_dir = temp.path().join("path").join("to");
1572 std::fs::create_dir_all(&license_dir).unwrap();
1573 std::fs::write(license_dir.join("LICENSE"), "license").unwrap();
1574 std::fs::write(temp.path().join("README.md"), "readme").unwrap();
1575 std::fs::write(temp.path().join("asdf.wasm"), b"\0asm...").unwrap();
1576 std::fs::write(temp.path().join("asdf.wai"), "exports").unwrap();
1577 std::fs::write(temp.path().join("browser.wai"), "imports").unwrap();
1578 std::fs::write(temp.path().join("unwanted_file.txt"), "unwanted_file").unwrap();
1579
1580 let package = Package::from_manifest(manifest).unwrap();
1581
1582 let contents: Vec<_> = package
1583 .get_volume("metadata")
1584 .unwrap()
1585 .read_dir(&PathSegments::ROOT)
1586 .unwrap()
1587 .into_iter()
1588 .map(|(path, _, _)| path)
1589 .collect();
1590
1591 assert_eq!(
1592 contents,
1593 vec![
1594 PathSegment::parse("README.md").unwrap(),
1595 PathSegment::parse("asdf.wai").unwrap(),
1596 PathSegment::parse("browser.wai").unwrap(),
1597 PathSegment::parse("path").unwrap(),
1598 ]
1599 );
1600 }
1601
1602 #[test]
1603 fn create_from_in_memory() -> anyhow::Result<()> {
1604 let wasmer_toml = r#"
1605 [dependencies]
1606 "wasmer/python" = "3.12.9+build.9"
1607
1608
1609 [[command]]
1610 module = "wasmer/python:python"
1611 name = "hello"
1612 runner = "wasi"
1613
1614 [command.annotations.wasi]
1615 main-args = [ "-c", "import os; print([f for f in os.walk('/public')]); " ]
1616
1617 [fs]
1618 "/public" = "public"
1619 "#;
1620
1621 let manifest = toml::from_str(wasmer_toml)?;
1622
1623 let file_modified = SystemTime::now();
1624 let file_data = String::from("Hello, world!").as_bytes().to_vec();
1625
1626 let file = MemoryFile {
1627 modified: file_modified,
1628 data: file_data,
1629 };
1630
1631 let mut nodes = BTreeMap::new();
1632 nodes.insert(String::from("hello.txt"), MemoryNode::File(file));
1633
1634 let dir_modified = SystemTime::now();
1635 let dir = MemoryDir {
1636 modified: dir_modified,
1637 nodes,
1638 };
1639
1640 let volume = MemoryVolume { node: dir };
1641 let mut volumes = BTreeMap::new();
1642
1643 volumes.insert("public".to_string(), volume);
1644
1645 let atoms = BTreeMap::new();
1646 let package = super::Package::from_in_memory(
1647 manifest,
1648 volumes,
1649 atoms,
1650 MemoryVolume {
1651 node: MemoryDir {
1652 modified: SystemTime::now(),
1653 nodes: BTreeMap::new(),
1654 },
1655 },
1656 Strictness::Strict,
1657 )?;
1658
1659 _ = package.serialize()?;
1660
1661 Ok(())
1662 }
1663
1664 #[test]
1665 fn compare_fs_mem_manifest() -> anyhow::Result<()> {
1666 let wasmer_toml = r#"
1667 [package]
1668 name = "test"
1669 version = "0.0.0"
1670 description = "asdf"
1671 "#;
1672
1673 let temp = TempDir::new()?;
1674 let manifest_path = temp.path().join("wasmer.toml");
1675 std::fs::write(&manifest_path, wasmer_toml).unwrap();
1676
1677 let fs_package = super::Package::from_manifest(manifest_path)?;
1678
1679 let manifest = toml::from_str(wasmer_toml)?;
1680 let memory_package = super::Package::from_in_memory(
1681 manifest,
1682 Default::default(),
1683 Default::default(),
1684 MemoryVolume {
1685 node: MemoryDir {
1686 modified: SystemTime::UNIX_EPOCH,
1687 nodes: BTreeMap::new(),
1688 },
1689 },
1690 Strictness::Lossy,
1691 )?;
1692
1693 assert_eq!(memory_package.serialize()?, fs_package.serialize()?);
1694
1695 Ok(())
1696 }
1697
1698 #[test]
1699 fn compare_fs_mem_manifest_and_atoms() -> anyhow::Result<()> {
1700 let wasmer_toml = r#"
1701 [package]
1702 name = "test"
1703 version = "0.0.0"
1704 description = "asdf"
1705
1706 [[module]]
1707 name = "foo"
1708 source = "foo.wasm"
1709 abi = "wasi"
1710 "#;
1711
1712 let temp = TempDir::new()?;
1713 let manifest_path = temp.path().join("wasmer.toml");
1714 std::fs::write(&manifest_path, wasmer_toml).unwrap();
1715
1716 let atom_path = temp.path().join("foo.wasm");
1717 std::fs::write(&atom_path, b"").unwrap();
1718
1719 let fs_package = super::Package::from_manifest(manifest_path)?;
1720
1721 let manifest = toml::from_str(wasmer_toml)?;
1722 let mut atoms = BTreeMap::new();
1723 atoms.insert("foo".to_owned(), (None, OwnedBuffer::new()));
1724 let memory_package = super::Package::from_in_memory(
1725 manifest,
1726 Default::default(),
1727 atoms,
1728 MemoryVolume {
1729 node: MemoryDir {
1730 modified: SystemTime::UNIX_EPOCH,
1731 nodes: BTreeMap::new(),
1732 },
1733 },
1734 Strictness::Lossy,
1735 )?;
1736
1737 assert_eq!(memory_package.serialize()?, fs_package.serialize()?);
1738
1739 Ok(())
1740 }
1741
1742 #[test]
1743 fn compare_fs_mem_volume() -> anyhow::Result<()> {
1744 let wasmer_toml = r#"
1745 [package]
1746 name = "test"
1747 version = "0.0.0"
1748 description = "asdf"
1749
1750 [[module]]
1751 name = "foo"
1752 source = "foo.wasm"
1753 abi = "wasi"
1754
1755 [fs]
1756 "/bar" = "bar"
1757 "#;
1758
1759 let temp = TempDir::new()?;
1760 let manifest_path = temp.path().join("wasmer.toml");
1761 std::fs::write(&manifest_path, wasmer_toml).unwrap();
1762
1763 let atom_path = temp.path().join("foo.wasm");
1764 std::fs::write(&atom_path, b"").unwrap();
1765
1766 let bar = temp.path().join("bar");
1767 std::fs::create_dir(&bar).unwrap();
1768
1769 let baz = bar.join("baz");
1770 std::fs::write(&baz, b"abc")?;
1771
1772 let baz_metadata = std::fs::metadata(&baz)?;
1773
1774 let fs_package = super::Package::from_manifest(manifest_path)?;
1775
1776 let manifest = toml::from_str(wasmer_toml)?;
1777
1778 let mut atoms = BTreeMap::new();
1779 atoms.insert("foo".to_owned(), (None, OwnedBuffer::new()));
1780
1781 let mut volumes = BTreeMap::new();
1782 volumes.insert(
1783 "/bar".to_owned(),
1784 MemoryVolume {
1785 node: MemoryDir {
1786 modified: SystemTime::UNIX_EPOCH,
1787 nodes: {
1788 let mut children = BTreeMap::new();
1789
1790 children.insert(
1791 "baz".to_owned(),
1792 MemoryNode::File(MemoryFile {
1793 modified: baz_metadata.modified()?,
1794 data: b"abc".to_vec(),
1795 }),
1796 );
1797
1798 children
1799 },
1800 },
1801 },
1802 );
1803 let memory_package = super::Package::from_in_memory(
1804 manifest,
1805 volumes,
1806 atoms,
1807 MemoryVolume {
1808 node: MemoryDir {
1809 modified: SystemTime::UNIX_EPOCH,
1810 nodes: Default::default(),
1811 },
1812 },
1813 Strictness::Lossy,
1814 )?;
1815
1816 assert_eq!(memory_package.serialize()?, fs_package.serialize()?);
1817
1818 Ok(())
1819 }
1820
1821 #[test]
1822 fn compare_fs_mem_bindings() -> anyhow::Result<()> {
1823 let temp = TempDir::new().unwrap();
1824
1825 let wasmer_toml = r#"
1826 [package]
1827 name = "some/package"
1828 version = "0.0.0"
1829 description = ""
1830 license-file = "LICENSE"
1831 readme = "README.md"
1832
1833 [[module]]
1834 name = "asdf"
1835 source = "asdf.wasm"
1836 abi = "none"
1837 bindings = { wai-version = "0.2.0", exports = "asdf.wai", imports = ["browser.wai"] }
1838
1839 [fs]
1840 "/dir1" = "local-dir1/dir"
1841 "/dir2" = "local-dir2/dir"
1842 "#;
1843
1844 let manifest = temp.path().join("wasmer.toml");
1845 std::fs::write(&manifest, wasmer_toml).unwrap();
1846
1847 std::fs::write(temp.path().join("LICENSE"), "license").unwrap();
1848 std::fs::write(temp.path().join("README.md"), "readme").unwrap();
1849 std::fs::write(temp.path().join("asdf.wasm"), b"\0asm...").unwrap();
1850 std::fs::write(temp.path().join("asdf.wai"), "exports").unwrap();
1851 std::fs::write(temp.path().join("browser.wai"), "imports").unwrap();
1852
1853 let dir1 = temp.path().join("local-dir1").join("dir");
1860 std::fs::create_dir_all(&dir1).unwrap();
1861 let dir2 = temp.path().join("local-dir2").join("dir");
1862 std::fs::create_dir_all(&dir2).unwrap();
1863
1864 let fs_package = super::Package::from_manifest(manifest)?;
1865
1866 let manifest = toml::from_str(wasmer_toml)?;
1867
1868 let mut atoms = BTreeMap::new();
1869 atoms.insert(
1870 "asdf".to_owned(),
1871 (None, OwnedBuffer::from_static(b"\0asm...")),
1872 );
1873
1874 let mut volumes = BTreeMap::new();
1875 volumes.insert(
1876 "/local-dir1/dir".to_owned(),
1877 MemoryVolume {
1878 node: MemoryDir {
1879 modified: SystemTime::UNIX_EPOCH,
1880 nodes: Default::default(),
1881 },
1882 },
1883 );
1884 volumes.insert(
1885 "/local-dir2/dir".to_owned(),
1886 MemoryVolume {
1887 node: MemoryDir {
1888 modified: SystemTime::UNIX_EPOCH,
1889 nodes: Default::default(),
1890 },
1891 },
1892 );
1893
1894 let memory_package = super::Package::from_in_memory(
1895 manifest,
1896 volumes,
1897 atoms,
1898 MemoryVolume {
1899 node: MemoryDir {
1900 modified: SystemTime::UNIX_EPOCH,
1901 nodes: {
1902 let mut children = BTreeMap::new();
1903
1904 children.insert(
1905 "README.md".to_owned(),
1906 MemoryNode::File(MemoryFile {
1907 modified: temp.path().join("README.md").metadata()?.modified()?,
1908 data: b"readme".to_vec(),
1909 }),
1910 );
1911
1912 children.insert(
1913 "LICENSE".to_owned(),
1914 MemoryNode::File(MemoryFile {
1915 modified: temp.path().join("LICENSE").metadata()?.modified()?,
1916 data: b"license".to_vec(),
1917 }),
1918 );
1919
1920 children.insert(
1921 "asdf.wai".to_owned(),
1922 MemoryNode::File(MemoryFile {
1923 modified: temp.path().join("asdf.wai").metadata()?.modified()?,
1924 data: b"exports".to_vec(),
1925 }),
1926 );
1927
1928 children.insert(
1929 "browser.wai".to_owned(),
1930 MemoryNode::File(MemoryFile {
1931 modified: temp.path().join("browser.wai").metadata()?.modified()?,
1932 data: b"imports".to_vec(),
1933 }),
1934 );
1935
1936 children
1937 },
1938 },
1939 },
1940 Strictness::Lossy,
1941 )?;
1942
1943 let memory_package = memory_package.serialize()?;
1944 let fs_package = fs_package.serialize()?;
1945
1946 assert_eq!(memory_package, fs_package);
1947
1948 Ok(())
1949 }
1950}