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