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 annotated_atoms = BTreeMap::new();
473
474 for (atom_name, (a, b)) in atoms {
475 let annotations =
476 if let Some(module) = manifest.modules.iter().find(|v| v.name == atom_name) {
477 module.annotations.as_ref()
478 } else {
479 None
480 };
481
482 annotated_atoms.insert(atom_name, (a, b, annotations));
483 }
484
485 let (mut manifest, atoms) =
486 super::manifest::in_memory_wasmer_manifest_to_webc(&manifest, &annotated_atoms)?;
487
488 if let Some(entry) = manifest.package.get_mut(Wapm::KEY) {
489 let mut wapm: Wapm = entry.deserialized()?;
490
491 wapm.name.take();
492 wapm.version.take();
493 wapm.description.take();
494
495 *entry = ciborium::value::Value::serialized(&wapm)?;
496 };
497
498 Ok(Package {
499 base_dir: BaseDir::Path(Path::new("/").to_path_buf()),
500 manifest,
501 atoms,
502 strictness,
503 volumes,
504 })
505 }
506
507 fn load(
508 wasmer_toml: WasmerManifest,
509 base_dir: impl Into<BaseDir>,
510 strictness: Strictness,
511 walker_factory: WalkBuilderFactory,
512 ) -> Result<Self, WasmerPackageError> {
513 let base_dir = base_dir.into();
514
515 if strictness.is_strict() {
516 wasmer_toml.validate()?;
517 }
518
519 let (mut manifest, atoms) =
520 wasmer_manifest_to_webc(&wasmer_toml, base_dir.path(), strictness)?;
521
522 if let Some(entry) = manifest.package.get_mut(Wapm::KEY) {
524 let mut wapm: Wapm = entry.deserialized()?;
525
526 wapm.name.take();
527 wapm.version.take();
528 wapm.description.take();
529
530 *entry = ciborium::value::Value::serialized(&wapm)?;
531 };
532
533 let base_dir_path = base_dir.path().to_path_buf();
535 let metadata_volume = FsVolume::new_metadata(&wasmer_toml, base_dir_path.clone())?;
537 let mut volumes: BTreeMap<String, Arc<dyn WasmerPackageVolume + Send + Sync + 'static>> = {
539 let old = FsVolume::new_assets(&wasmer_toml, &base_dir_path, walker_factory)?;
540 let mut new = BTreeMap::new();
541
542 for (k, v) in old.into_iter() {
543 new.insert(k, Arc::new(v) as _);
544 }
545
546 new
547 };
548 volumes.insert(
549 metadata_volume.name().to_string(),
550 Arc::new(metadata_volume),
551 );
552
553 Ok(Package {
554 base_dir,
555 manifest,
556 atoms,
557 strictness,
558 volumes,
559 })
560 }
561
562 pub fn webc_hash(&self) -> Option<[u8; 32]> {
564 None
565 }
566
567 pub fn manifest(&self) -> &WebcManifest {
569 &self.manifest
570 }
571
572 pub fn atoms(&self) -> &BTreeMap<String, OwnedBuffer> {
574 &self.atoms
575 }
576
577 pub fn volumes(
579 &self,
580 ) -> impl Iterator<Item = &Arc<dyn WasmerPackageVolume + Sync + Send + 'static>> {
581 self.volumes.values()
582 }
583
584 pub fn serialize(&self) -> Result<Bytes, Error> {
587 let mut w = Writer::new(ChecksumAlgorithm::Sha256)
588 .write_manifest(self.manifest())?
589 .write_atoms(self.atom_entries()?)?;
590
591 for (name, volume) in &self.volumes {
592 w.write_volume(name.as_str(), volume.as_directory_tree(self.strictness)?)?;
593 }
594
595 let serialized = w.finish(webc::v3::SignatureAlgorithm::None)?;
596
597 Ok(serialized)
598 }
599
600 fn atom_entries(&self) -> Result<BTreeMap<PathSegment, FileEntry<'_>>, Error> {
601 self.atoms()
602 .iter()
603 .map(|(key, value)| {
604 let filename = PathSegment::parse(key)
605 .with_context(|| format!("\"{key}\" isn't a valid atom name"))?;
606 Ok((filename, FileEntry::borrowed(value, Timestamps::default())))
608 })
609 .collect()
610 }
611
612 pub(crate) fn get_volume(
613 &self,
614 name: &str,
615 ) -> Option<Arc<dyn WasmerPackageVolume + Sync + Send + 'static>> {
616 self.volumes.get(name).cloned()
617 }
618
619 pub(crate) fn volume_names(&self) -> Vec<Cow<'_, str>> {
620 self.volumes
621 .keys()
622 .map(|name| Cow::Borrowed(name.as_str()))
623 .collect()
624 }
625}
626
627impl AbstractWebc for Package {
628 fn version(&self) -> Version {
629 Version::V3
630 }
631
632 fn manifest(&self) -> &WebcManifest {
633 self.manifest()
634 }
635
636 fn atom_names(&self) -> Vec<Cow<'_, str>> {
637 self.atoms()
638 .keys()
639 .map(|s| Cow::Borrowed(s.as_str()))
640 .collect()
641 }
642
643 fn get_atom(&self, name: &str) -> Option<OwnedBuffer> {
644 self.atoms().get(name).cloned()
645 }
646
647 fn get_webc_hash(&self) -> Option<[u8; 32]> {
648 self.webc_hash()
649 }
650
651 fn get_atoms_hash(&self) -> Option<[u8; 32]> {
652 None
653 }
654
655 fn volume_names(&self) -> Vec<Cow<'_, str>> {
656 self.volume_names()
657 }
658
659 fn get_volume(&self, name: &str) -> Option<Volume> {
660 self.get_volume(name).map(|v| {
661 let a: Arc<dyn AbstractVolume + Send + Sync + 'static> = v.as_volume();
662
663 Volume::from(a)
664 })
665 }
666}
667
668impl From<Package> for Container {
669 fn from(value: Package) -> Self {
670 Container::new(value)
671 }
672}
673
674const IS_WASI: bool = cfg!(all(target_family = "wasm", target_os = "wasi"));
675
676fn tempdir() -> Result<TempDir, std::io::Error> {
684 if !IS_WASI {
685 return TempDir::new();
687 }
688
689 let temp_dir: PathBuf = std::env::var("TMPDIR")
692 .unwrap_or_else(|_| "/tmp".to_string())
693 .into();
694
695 if temp_dir.exists() {
696 TempDir::new_in(temp_dir)
697 } else {
698 if let Ok(current_exe) = std::env::current_exe() {
704 if let Some(parent) = current_exe.parent() {
705 if let Ok(temp) = TempDir::new_in(parent) {
706 return Ok(temp);
707 }
708 }
709 }
710
711 std::fs::create_dir_all(&temp_dir)?;
713 TempDir::new_in(temp_dir)
714 }
715}
716
717fn unpack_archive(
724 mut archive: Archive<impl std::io::Read>,
725 dest: &Path,
726) -> Result<(), std::io::Error> {
727 cfg_if::cfg_if! {
728 if #[cfg(all(target_family = "wasm", target_os = "wasi"))]
729 {
730 for entry in archive.entries()? {
733 let mut entry = entry?;
734 let item_path = entry.path()?;
735 let full_path = resolve_archive_path(dest, &item_path);
736
737 match entry.header().entry_type() {
738 tar::EntryType::Directory => {
739 std::fs::create_dir_all(&full_path)?;
740 }
741 tar::EntryType::Regular => {
742 if let Some(parent) = full_path.parent() {
743 std::fs::create_dir_all(parent)?;
744 }
745 let mut f = File::create(&full_path)?;
746 std::io::copy(&mut entry, &mut f)?;
747
748 let mtime = entry.header().mtime().unwrap_or_default();
749 if let Err(e) = set_timestamp(full_path.as_path(), mtime) {
750 println!("WARN: {e:?}");
751 }
752 }
753 _ => {}
754 }
755 }
756 Ok(())
757
758 } else {
759 archive.unpack(dest)
760 }
761 }
762}
763
764#[cfg(all(target_family = "wasm", target_os = "wasi"))]
765fn set_timestamp(path: &Path, timestamp: u64) -> Result<(), anyhow::Error> {
766 let fd = unsafe {
767 libc::open(
768 path.as_os_str().as_encoded_bytes().as_ptr() as _,
769 libc::O_RDONLY,
770 )
771 };
772
773 if fd < 0 {
774 anyhow::bail!(format!("failed to open: {}", path.display()));
775 }
776
777 let timespec = [
778 libc::timespec {
780 tv_sec: unsafe { libc::time(std::ptr::null_mut()) }, tv_nsec: 0,
782 },
783 libc::timespec {
785 tv_sec: timestamp as i64,
786 tv_nsec: 0,
787 },
788 ];
789
790 let res = unsafe { libc::futimens(fd, timespec.as_ptr() as _) };
791
792 if res < 0 {
793 anyhow::bail!("failed to set timestamp for: {}", path.display());
794 }
795
796 Ok(())
797}
798
799#[cfg(all(target_family = "wasm", target_os = "wasi"))]
800fn resolve_archive_path(base_dir: &Path, path: &Path) -> PathBuf {
801 let mut buffer = base_dir.to_path_buf();
802
803 for component in path.components() {
804 match component {
805 std::path::Component::Prefix(_)
806 | std::path::Component::RootDir
807 | std::path::Component::CurDir => continue,
808 std::path::Component::ParentDir => {
809 buffer.pop();
810 }
811 std::path::Component::Normal(segment) => {
812 buffer.push(segment);
813 }
814 }
815 }
816
817 buffer
818}
819
820fn read_manifest(base_dir: &Path) -> Result<(PathBuf, WasmerManifest), WasmerPackageError> {
821 for path in ["wasmer.toml", "wapm.toml"] {
822 let path = base_dir.join(path);
823
824 match std::fs::read_to_string(&path) {
825 Ok(s) => {
826 let toml_file = toml::from_str(&s).map_err({
827 let path = path.clone();
828 |error| WasmerPackageError::TomlDeserialize { path, error }
829 })?;
830
831 return Ok((path, toml_file));
832 }
833 Err(e) if e.kind() == std::io::ErrorKind::NotFound => continue,
834 Err(error) => {
835 return Err(WasmerPackageError::FileRead { path, error });
836 }
837 }
838 }
839
840 Err(WasmerPackageError::MissingManifest)
841}
842
843#[derive(Debug)]
844enum BaseDir {
845 Path(PathBuf),
847 Temp(TempDir),
849}
850
851impl BaseDir {
852 fn path(&self) -> &Path {
853 match self {
854 BaseDir::Path(p) => p.as_path(),
855 BaseDir::Temp(t) => t.path(),
856 }
857 }
858}
859
860impl From<TempDir> for BaseDir {
861 fn from(v: TempDir) -> Self {
862 Self::Temp(v)
863 }
864}
865
866impl From<PathBuf> for BaseDir {
867 fn from(v: PathBuf) -> Self {
868 Self::Path(v)
869 }
870}
871
872#[cfg(test)]
873mod tests {
874 use std::{
875 collections::BTreeMap,
876 fs::File,
877 path::{Path, PathBuf},
878 str::FromStr,
879 time::SystemTime,
880 };
881
882 use flate2::{Compression, write::GzEncoder};
883 use sha2::Digest;
884 use shared_buffer::OwnedBuffer;
885 use tempfile::TempDir;
886 use webc::{
887 PathSegment, PathSegments,
888 metadata::{
889 Binding, BindingsExtended, WaiBindings, WitBindings,
890 annotations::{FileSystemMapping, VolumeSpecificPath},
891 },
892 };
893
894 use crate::{package::*, utils::from_bytes};
895
896 #[test]
897 fn nonexistent_files() {
898 let temp = TempDir::new().unwrap();
899
900 assert!(Package::from_manifest(temp.path().join("nonexistent.toml")).is_err());
901 assert!(Package::from_tarball_file(temp.path().join("nonexistent.tar.gz")).is_err());
902 }
903
904 #[test]
905 fn load_a_tarball() {
906 let coreutils = Path::new(env!("CARGO_MANIFEST_DIR"))
907 .join("..")
908 .join("..")
909 .join("tests")
910 .join("old-tar-gz")
911 .join("coreutils-1.0.11.tar.gz");
912 assert!(coreutils.exists());
913
914 let package = Package::from_tarball_file(coreutils).unwrap();
915
916 let wapm = package.manifest().wapm().unwrap().unwrap();
917 assert!(wapm.name.is_none());
918 assert!(wapm.version.is_none());
919 assert!(wapm.description.is_none());
920 }
921
922 #[test]
923 fn tarball_with_no_manifest() {
924 let temp = TempDir::new().unwrap();
925 let empty_tarball = temp.path().join("empty.tar.gz");
926 let mut f = File::create(&empty_tarball).unwrap();
927 tar::Builder::new(GzEncoder::new(&mut f, Compression::fast()))
928 .finish()
929 .unwrap();
930
931 assert!(Package::from_tarball_file(&empty_tarball).is_err());
932 }
933
934 #[test]
935 fn empty_package_on_disk() {
936 let temp = TempDir::new().unwrap();
937 let manifest = temp.path().join("wasmer.toml");
938 std::fs::write(
939 &manifest,
940 r#"
941 [package]
942 name = "some/package"
943 version = "0.0.0"
944 description = "A dummy package"
945 "#,
946 )
947 .unwrap();
948
949 let package = Package::from_manifest(&manifest).unwrap();
950
951 let wapm = package.manifest().wapm().unwrap().unwrap();
952 assert!(wapm.name.is_none());
953 assert!(wapm.version.is_none());
954 assert!(wapm.description.is_none());
955 }
956
957 #[test]
958 fn load_old_cowsay() {
959 let tarball = Path::new(env!("CARGO_MANIFEST_DIR"))
960 .join("..")
961 .join("..")
962 .join("tests")
963 .join("old-tar-gz")
964 .join("cowsay-0.3.0.tar.gz");
965
966 let pkg = Package::from_tarball_file(tarball).unwrap();
967
968 insta::assert_yaml_snapshot!(pkg.manifest());
969 assert_eq!(
970 pkg.manifest.commands.keys().collect::<Vec<_>>(),
971 ["cowsay", "cowthink"],
972 );
973 }
974
975 #[test]
976 fn serialize_package_with_non_existent_fs() {
977 let temp = TempDir::new().unwrap();
978 let wasmer_toml = r#"
979 [package]
980 name = "some/package"
981 version = "0.0.0"
982 description = "Test package"
983
984 [fs]
985 "/first" = "./first"
986 "#;
987 let manifest = temp.path().join("wasmer.toml");
988
989 std::fs::write(&manifest, wasmer_toml).unwrap();
990
991 let error = Package::from_manifest(manifest).unwrap_err();
992
993 match error {
994 WasmerPackageError::PathNotExists { path } => {
995 assert_eq!(path, PathBuf::from_str("./first").unwrap());
996 }
997 e => panic!("unexpected error: {e:?}"),
998 }
999 }
1000
1001 #[test]
1002 fn serialize_package_with_bundled_directories() {
1003 let temp = TempDir::new().unwrap();
1004 let wasmer_toml = r#"
1005 [package]
1006 name = "some/package"
1007 version = "0.0.0"
1008 description = "Test package"
1009
1010 [fs]
1011 "/first" = "first"
1012 second = "nested/dir"
1013 "second/child" = "third"
1014 empty = "empty"
1015 "#;
1016 let manifest = temp.path().join("wasmer.toml");
1017 std::fs::write(&manifest, wasmer_toml).unwrap();
1018 let first = temp.path().join("first");
1036 std::fs::create_dir_all(&first).unwrap();
1037 std::fs::write(first.join("file.txt"), "File").unwrap();
1038 let second = temp.path().join("nested").join("dir");
1040 std::fs::create_dir_all(&second).unwrap();
1041 std::fs::write(second.join(".wasmerignore"), "ignore_me").unwrap();
1042 std::fs::write(second.join(".hidden"), "something something").unwrap();
1043 std::fs::write(second.join("ignore_me"), "something something").unwrap();
1044 std::fs::write(second.join("README.md"), "please").unwrap();
1045 let another_dir = temp.path().join("nested").join("dir").join("another-dir");
1046 std::fs::create_dir_all(&another_dir).unwrap();
1047 std::fs::write(another_dir.join("empty.txt"), "").unwrap();
1048 let third = temp.path().join("third");
1050 std::fs::create_dir_all(&third).unwrap();
1051 std::fs::write(third.join("file.txt"), "Hello, World!").unwrap();
1052 let empty_dir = temp.path().join("empty");
1054 std::fs::create_dir_all(empty_dir).unwrap();
1055
1056 let package = Package::from_manifest(manifest).unwrap();
1057
1058 let webc = package.serialize().unwrap();
1059 let webc = from_bytes(webc).unwrap();
1060 let manifest = webc.manifest();
1061 let wapm_metadata = manifest.wapm().unwrap().unwrap();
1062 assert!(wapm_metadata.name.is_none());
1063 assert!(wapm_metadata.version.is_none());
1064 assert!(wapm_metadata.description.is_none());
1065 let fs_table = manifest.filesystem().unwrap().unwrap();
1066 assert_eq!(
1067 fs_table,
1068 [
1069 FileSystemMapping {
1070 from: None,
1071 volume_name: "/first".to_string(),
1072 host_path: None,
1073 mount_path: "/first".to_string(),
1074 },
1075 FileSystemMapping {
1076 from: None,
1077 volume_name: "/nested/dir".to_string(),
1078 host_path: None,
1079 mount_path: "/second".to_string(),
1080 },
1081 FileSystemMapping {
1082 from: None,
1083 volume_name: "/third".to_string(),
1084 host_path: None,
1085 mount_path: "/second/child".to_string(),
1086 },
1087 FileSystemMapping {
1088 from: None,
1089 volume_name: "/empty".to_string(),
1090 host_path: None,
1091 mount_path: "/empty".to_string(),
1092 },
1093 ]
1094 );
1095
1096 let first_file_hash: [u8; 32] = sha2::Sha256::digest(b"File").into();
1097 let readme_hash: [u8; 32] = sha2::Sha256::digest(b"please").into();
1098 let empty_hash: [u8; 32] = sha2::Sha256::digest(b"").into();
1099 let third_file_hash: [u8; 32] = sha2::Sha256::digest(b"Hello, World!").into();
1100
1101 let first_volume = webc.get_volume("/first").unwrap();
1102 assert_eq!(
1103 first_volume.read_file("/file.txt").unwrap(),
1104 (b"File".as_slice().into(), Some(first_file_hash)),
1105 );
1106
1107 let nested_dir_volume = webc.get_volume("/nested/dir").unwrap();
1108 assert_eq!(
1109 nested_dir_volume.read_file("README.md").unwrap(),
1110 (b"please".as_slice().into(), Some(readme_hash)),
1111 );
1112 assert!(nested_dir_volume.read_file(".wasmerignore").is_some());
1113 assert!(nested_dir_volume.read_file(".hidden").is_some());
1114 assert!(nested_dir_volume.read_file("ignore_me").is_none());
1115 assert_eq!(
1116 nested_dir_volume
1117 .read_file("/another-dir/empty.txt")
1118 .unwrap(),
1119 (b"".as_slice().into(), Some(empty_hash))
1120 );
1121
1122 let third_volume = webc.get_volume("/third").unwrap();
1123 assert_eq!(
1124 third_volume.read_file("/file.txt").unwrap(),
1125 (b"Hello, World!".as_slice().into(), Some(third_file_hash))
1126 );
1127
1128 let empty_volume = webc.get_volume("/empty").unwrap();
1129 assert_eq!(
1130 empty_volume.read_dir("/").unwrap().len(),
1131 0,
1132 "Directories should be included, even if empty"
1133 );
1134 }
1135
1136 #[test]
1137 fn serialize_package_with_metadata_files() {
1138 let temp = TempDir::new().unwrap();
1139 let wasmer_toml = r#"
1140 [package]
1141 name = "some/package"
1142 version = "0.0.0"
1143 description = "Test package"
1144 readme = "README.md"
1145 license-file = "LICENSE"
1146 "#;
1147 let manifest = temp.path().join("wasmer.toml");
1148 std::fs::write(&manifest, wasmer_toml).unwrap();
1149 std::fs::write(temp.path().join("README.md"), "readme").unwrap();
1150 std::fs::write(temp.path().join("LICENSE"), "license").unwrap();
1151
1152 let serialized = Package::from_manifest(manifest)
1153 .unwrap()
1154 .serialize()
1155 .unwrap();
1156
1157 let webc = from_bytes(serialized).unwrap();
1158 let metadata_volume = webc.get_volume("metadata").unwrap();
1159
1160 let readme_hash: [u8; 32] = sha2::Sha256::digest(b"readme").into();
1161 let license_hash: [u8; 32] = sha2::Sha256::digest(b"license").into();
1162
1163 assert_eq!(
1164 metadata_volume.read_file("/README.md").unwrap(),
1165 (b"readme".as_slice().into(), Some(readme_hash))
1166 );
1167 assert_eq!(
1168 metadata_volume.read_file("/LICENSE").unwrap(),
1169 (b"license".as_slice().into(), Some(license_hash))
1170 );
1171 }
1172
1173 #[test]
1174 fn load_package_with_wit_bindings() {
1175 let temp = TempDir::new().unwrap();
1176 let wasmer_toml = r#"
1177 [package]
1178 name = "some/package"
1179 version = "0.0.0"
1180 description = ""
1181
1182 [[module]]
1183 name = "my-lib"
1184 source = "./my-lib.wasm"
1185 abi = "none"
1186 bindings = { wit-bindgen = "0.1.0", wit-exports = "./file.wit" }
1187 "#;
1188 std::fs::write(temp.path().join("wasmer.toml"), wasmer_toml).unwrap();
1189 std::fs::write(temp.path().join("file.wit"), "file").unwrap();
1190 std::fs::write(temp.path().join("my-lib.wasm"), b"\0asm...").unwrap();
1191
1192 let package = Package::from_manifest(temp.path().join("wasmer.toml"))
1193 .unwrap()
1194 .serialize()
1195 .unwrap();
1196 let webc = from_bytes(package).unwrap();
1197
1198 assert_eq!(
1199 webc.manifest().bindings,
1200 vec![Binding {
1201 name: "library-bindings".to_string(),
1202 kind: "wit@0.1.0".to_string(),
1203 annotations: ciborium::value::Value::serialized(&BindingsExtended::Wit(
1204 WitBindings {
1205 exports: "metadata://file.wit".to_string(),
1206 module: "my-lib".to_string(),
1207 }
1208 ))
1209 .unwrap(),
1210 }]
1211 );
1212 let metadata_volume = webc.get_volume("metadata").unwrap();
1213 let file_hash: [u8; 32] = sha2::Sha256::digest(b"file").into();
1214 assert_eq!(
1215 metadata_volume.read_file("/file.wit").unwrap(),
1216 (b"file".as_slice().into(), Some(file_hash))
1217 );
1218 insta::with_settings! {
1219 { description => wasmer_toml },
1220 { insta::assert_yaml_snapshot!(webc.manifest()); }
1221 }
1222 }
1223
1224 #[test]
1225 fn load_package_with_wai_bindings() {
1226 let temp = TempDir::new().unwrap();
1227 let wasmer_toml = r#"
1228 [package]
1229 name = "some/package"
1230 version = "0.0.0"
1231 description = ""
1232
1233 [[module]]
1234 name = "my-lib"
1235 source = "./my-lib.wasm"
1236 abi = "none"
1237 bindings = { wai-version = "0.2.0", exports = "./file.wai", imports = ["a.wai", "b.wai"] }
1238 "#;
1239 std::fs::write(temp.path().join("wasmer.toml"), wasmer_toml).unwrap();
1240 std::fs::write(temp.path().join("file.wai"), "file").unwrap();
1241 std::fs::write(temp.path().join("a.wai"), "a").unwrap();
1242 std::fs::write(temp.path().join("b.wai"), "b").unwrap();
1243 std::fs::write(temp.path().join("my-lib.wasm"), b"\0asm...").unwrap();
1244
1245 let package = Package::from_manifest(temp.path().join("wasmer.toml"))
1246 .unwrap()
1247 .serialize()
1248 .unwrap();
1249 let webc = from_bytes(package).unwrap();
1250
1251 assert_eq!(
1252 webc.manifest().bindings,
1253 vec![Binding {
1254 name: "library-bindings".to_string(),
1255 kind: "wai@0.2.0".to_string(),
1256 annotations: ciborium::value::Value::serialized(&BindingsExtended::Wai(
1257 WaiBindings {
1258 exports: Some("metadata://file.wai".to_string()),
1259 module: "my-lib".to_string(),
1260 imports: vec![
1261 "metadata://a.wai".to_string(),
1262 "metadata://b.wai".to_string(),
1263 ]
1264 }
1265 ))
1266 .unwrap(),
1267 }]
1268 );
1269 let metadata_volume = webc.get_volume("metadata").unwrap();
1270
1271 let file_hash: [u8; 32] = sha2::Sha256::digest(b"file").into();
1272 let a_hash: [u8; 32] = sha2::Sha256::digest(b"a").into();
1273 let b_hash: [u8; 32] = sha2::Sha256::digest(b"b").into();
1274
1275 assert_eq!(
1276 metadata_volume.read_file("/file.wai").unwrap(),
1277 (b"file".as_slice().into(), Some(file_hash))
1278 );
1279 assert_eq!(
1280 metadata_volume.read_file("/a.wai").unwrap(),
1281 (b"a".as_slice().into(), Some(a_hash))
1282 );
1283 assert_eq!(
1284 metadata_volume.read_file("/b.wai").unwrap(),
1285 (b"b".as_slice().into(), Some(b_hash))
1286 );
1287 insta::with_settings! {
1288 { description => wasmer_toml },
1289 { insta::assert_yaml_snapshot!(webc.manifest()); }
1290 }
1291 }
1292
1293 #[test]
1295 fn absolute_paths_in_wasmer_toml_issue_105() {
1296 let temp = TempDir::new().unwrap();
1297 let base_dir = temp.path().canonicalize().unwrap();
1298 let sep = std::path::MAIN_SEPARATOR;
1299 let wasmer_toml = format!(
1300 r#"
1301 [package]
1302 name = 'some/package'
1303 version = '0.0.0'
1304 description = 'Test package'
1305 readme = '{BASE_DIR}{sep}README.md'
1306 license-file = '{BASE_DIR}{sep}LICENSE'
1307
1308 [[module]]
1309 name = 'first'
1310 source = '{BASE_DIR}{sep}target{sep}debug{sep}package.wasm'
1311 bindings = {{ wai-version = '0.2.0', exports = '{BASE_DIR}{sep}bindings{sep}file.wai', imports = ['{BASE_DIR}{sep}bindings{sep}a.wai'] }}
1312 "#,
1313 BASE_DIR = base_dir.display(),
1314 );
1315 let manifest = temp.path().join("wasmer.toml");
1316 std::fs::write(&manifest, &wasmer_toml).unwrap();
1317 std::fs::write(temp.path().join("README.md"), "readme").unwrap();
1318 std::fs::write(temp.path().join("LICENSE"), "license").unwrap();
1319 let bindings = temp.path().join("bindings");
1320 std::fs::create_dir_all(&bindings).unwrap();
1321 std::fs::write(bindings.join("file.wai"), "file.wai").unwrap();
1322 std::fs::write(bindings.join("a.wai"), "a.wai").unwrap();
1323 let target = temp.path().join("target").join("debug");
1324 std::fs::create_dir_all(&target).unwrap();
1325 std::fs::write(target.join("package.wasm"), b"\0asm...").unwrap();
1326
1327 let serialized = Package::from_manifest(manifest)
1328 .unwrap()
1329 .serialize()
1330 .unwrap();
1331
1332 let webc = from_bytes(serialized).unwrap();
1333 let manifest = webc.manifest();
1334 let wapm = manifest.wapm().unwrap().unwrap();
1335
1336 let lookup = |item: VolumeSpecificPath| {
1338 let volume = webc.get_volume(&item.volume).unwrap();
1339 let (contents, _) = volume.read_file(&item.path).unwrap();
1340 String::from_utf8(contents.into()).unwrap()
1341 };
1342 assert_eq!(lookup(wapm.license_file.unwrap()), "license");
1343 assert_eq!(lookup(wapm.readme.unwrap()), "readme");
1344
1345 let lookup = |item: &str| {
1348 let (volume, path) = item.split_once(":/").unwrap();
1349 let volume = webc.get_volume(volume).unwrap();
1350 let (content, _) = volume.read_file(path).unwrap();
1351 String::from_utf8(content.into()).unwrap()
1352 };
1353 let bindings = manifest.bindings[0].get_wai_bindings().unwrap();
1354 assert_eq!(lookup(&bindings.imports[0]), "a.wai");
1355 assert_eq!(lookup(bindings.exports.unwrap().as_str()), "file.wai");
1356
1357 let mut settings = insta::Settings::clone_current();
1359 let base_dir = base_dir.display().to_string();
1360 settings.set_description(wasmer_toml.replace(&base_dir, "[BASE_DIR]"));
1361 let filter = regex::escape(&base_dir);
1362 settings.add_filter(&filter, "[BASE_DIR]");
1363 settings.bind(|| {
1364 insta::assert_yaml_snapshot!(webc.manifest());
1365 });
1366 }
1367
1368 #[test]
1369 fn serializing_will_skip_missing_metadata_by_default() {
1370 let temp = TempDir::new().unwrap();
1371 let wasmer_toml = r#"
1372 [package]
1373 name = 'some/package'
1374 version = '0.0.0'
1375 description = 'Test package'
1376 readme = '/this/does/not/exist/README.md'
1377 license-file = 'LICENSE.wtf'
1378 "#;
1379 let manifest = temp.path().join("wasmer.toml");
1380 std::fs::write(&manifest, wasmer_toml).unwrap();
1381 let pkg = Package::from_manifest(manifest).unwrap();
1382
1383 let serialized = pkg.serialize().unwrap();
1384
1385 let webc = from_bytes(serialized).unwrap();
1386 let manifest = webc.manifest();
1387 let wapm = manifest.wapm().unwrap().unwrap();
1388 assert!(wapm.license_file.is_none());
1390 assert!(wapm.readme.is_none());
1391
1392 let pkg = Package {
1394 strictness: Strictness::Strict,
1395 ..pkg
1396 };
1397 assert!(pkg.serialize().is_err());
1398 }
1399
1400 #[test]
1401 fn serialize_package_without_local_base_fs_paths() {
1402 let temp = TempDir::new().unwrap();
1403 let wasmer_toml = r#"
1404 [package]
1405 name = "some/package"
1406 version = "0.0.0"
1407 description = "Test package"
1408 readme = 'README.md'
1409 license-file = 'LICENSE'
1410
1411 [fs]
1412 "/path_in_wasix" = "local-dir/dir1"
1413 "#;
1414 let manifest = temp.path().join("wasmer.toml");
1415 std::fs::write(&manifest, wasmer_toml).unwrap();
1416
1417 std::fs::write(temp.path().join("README.md"), "readme").unwrap();
1418 std::fs::write(temp.path().join("LICENSE"), "license").unwrap();
1419
1420 let dir1 = temp.path().join("local-dir").join("dir1");
1427 std::fs::create_dir_all(&dir1).unwrap();
1428
1429 let a = dir1.join("a");
1430 let b = dir1.join("b");
1431
1432 std::fs::write(a, "a").unwrap();
1433 std::fs::write(b, "b").unwrap();
1434
1435 let package = Package::from_manifest(manifest).unwrap();
1436
1437 let webc = package.serialize().unwrap();
1438 let webc = from_bytes(webc).unwrap();
1439 let manifest = webc.manifest();
1440 let wapm_metadata = manifest.wapm().unwrap().unwrap();
1441
1442 assert!(wapm_metadata.name.is_none());
1443 assert!(wapm_metadata.version.is_none());
1444 assert!(wapm_metadata.description.is_none());
1445
1446 let fs_table = manifest.filesystem().unwrap().unwrap();
1447 assert_eq!(
1448 fs_table,
1449 [FileSystemMapping {
1450 from: None,
1451 volume_name: "/local-dir/dir1".to_string(),
1452 host_path: None,
1453 mount_path: "/path_in_wasix".to_string(),
1454 },]
1455 );
1456
1457 let readme_hash: [u8; 32] = sha2::Sha256::digest(b"readme").into();
1458 let license_hash: [u8; 32] = sha2::Sha256::digest(b"license").into();
1459
1460 let a_hash: [u8; 32] = sha2::Sha256::digest(b"a").into();
1461 let b_hash: [u8; 32] = sha2::Sha256::digest(b"b").into();
1462
1463 let dir1_volume = webc.get_volume("/local-dir/dir1").unwrap();
1464 let meta_volume = webc.get_volume("metadata").unwrap();
1465
1466 assert_eq!(
1467 meta_volume.read_file("LICENSE").unwrap(),
1468 (b"license".as_slice().into(), Some(license_hash)),
1469 );
1470 assert_eq!(
1471 meta_volume.read_file("README.md").unwrap(),
1472 (b"readme".as_slice().into(), Some(readme_hash)),
1473 );
1474 assert_eq!(
1475 dir1_volume.read_file("a").unwrap(),
1476 (b"a".as_slice().into(), Some(a_hash))
1477 );
1478 assert_eq!(
1479 dir1_volume.read_file("b").unwrap(),
1480 (b"b".as_slice().into(), Some(b_hash))
1481 );
1482 }
1483
1484 #[test]
1485 fn serialize_package_with_nested_fs_entries_without_local_base_fs_paths() {
1486 let temp = TempDir::new().unwrap();
1487 let wasmer_toml = r#"
1488 [package]
1489 name = "some/package"
1490 version = "0.0.0"
1491 description = "Test package"
1492 readme = 'README.md'
1493 license-file = 'LICENSE'
1494
1495 [fs]
1496 "/path_in_wasix" = "local-dir/dir1"
1497 "#;
1498 let manifest = temp.path().join("wasmer.toml");
1499 std::fs::write(&manifest, wasmer_toml).unwrap();
1500
1501 std::fs::write(temp.path().join("README.md"), "readme").unwrap();
1502 std::fs::write(temp.path().join("LICENSE"), "license").unwrap();
1503
1504 let local_dir = temp.path().join("local-dir");
1512 std::fs::create_dir_all(&local_dir).unwrap();
1513
1514 let dir1 = local_dir.join("dir1");
1515 std::fs::create_dir_all(&dir1).unwrap();
1516
1517 let dir2 = dir1.join("dir2");
1518 std::fs::create_dir_all(&dir2).unwrap();
1519
1520 let a = dir2.join("a");
1521 let b = dir1.join("b");
1522
1523 std::fs::write(a, "a").unwrap();
1524 std::fs::write(b, "b").unwrap();
1525
1526 let package = Package::from_manifest(manifest).unwrap();
1527
1528 let webc = package.serialize().unwrap();
1529 let webc = from_bytes(webc).unwrap();
1530 let manifest = webc.manifest();
1531 let wapm_metadata = manifest.wapm().unwrap().unwrap();
1532
1533 assert!(wapm_metadata.name.is_none());
1534 assert!(wapm_metadata.version.is_none());
1535 assert!(wapm_metadata.description.is_none());
1536
1537 let fs_table = manifest.filesystem().unwrap().unwrap();
1538 assert_eq!(
1539 fs_table,
1540 [FileSystemMapping {
1541 from: None,
1542 volume_name: "/local-dir/dir1".to_string(),
1543 host_path: None,
1544 mount_path: "/path_in_wasix".to_string(),
1545 },]
1546 );
1547
1548 let readme_hash: [u8; 32] = sha2::Sha256::digest(b"readme").into();
1549 let license_hash: [u8; 32] = sha2::Sha256::digest(b"license").into();
1550
1551 let a_hash: [u8; 32] = sha2::Sha256::digest(b"a").into();
1552 let dir2_hash: [u8; 32] = sha2::Sha256::digest(a_hash).into();
1553 let b_hash: [u8; 32] = sha2::Sha256::digest(b"b").into();
1554
1555 let dir1_volume = webc.get_volume("/local-dir/dir1").unwrap();
1556 let meta_volume = webc.get_volume("metadata").unwrap();
1557
1558 assert_eq!(
1559 meta_volume.read_file("LICENSE").unwrap(),
1560 (b"license".as_slice().into(), Some(license_hash)),
1561 );
1562 assert_eq!(
1563 meta_volume.read_file("README.md").unwrap(),
1564 (b"readme".as_slice().into(), Some(readme_hash)),
1565 );
1566 assert_eq!(
1567 dir1_volume
1568 .read_dir("/")
1569 .unwrap()
1570 .into_iter()
1571 .map(|(p, h, _)| (p, h))
1572 .collect::<Vec<_>>(),
1573 vec![
1574 (PathSegment::parse("b").unwrap(), Some(b_hash)),
1575 (PathSegment::parse("dir2").unwrap(), Some(dir2_hash))
1576 ]
1577 );
1578 assert_eq!(
1579 dir1_volume
1580 .read_dir("/dir2")
1581 .unwrap()
1582 .into_iter()
1583 .map(|(p, h, _)| (p, h))
1584 .collect::<Vec<_>>(),
1585 vec![(PathSegment::parse("a").unwrap(), Some(a_hash))]
1586 );
1587 assert_eq!(
1588 dir1_volume.read_file("/dir2/a").unwrap(),
1589 (b"a".as_slice().into(), Some(a_hash))
1590 );
1591 assert_eq!(
1592 dir1_volume.read_file("/b").unwrap(),
1593 (b"b".as_slice().into(), Some(b_hash))
1594 );
1595 }
1596
1597 #[test]
1598 fn serialize_package_mapped_to_same_dir_without_local_base_fs_paths() {
1599 let temp = TempDir::new().unwrap();
1600 let wasmer_toml = r#"
1601 [package]
1602 name = "some/package"
1603 version = "0.0.0"
1604 description = "Test package"
1605 readme = 'README.md'
1606 license-file = 'LICENSE'
1607
1608 [fs]
1609 "/dir1" = "local-dir1/dir"
1610 "/dir2" = "local-dir2/dir"
1611 "#;
1612 let manifest = temp.path().join("wasmer.toml");
1613 std::fs::write(&manifest, wasmer_toml).unwrap();
1614
1615 std::fs::write(temp.path().join("README.md"), "readme").unwrap();
1616 std::fs::write(temp.path().join("LICENSE"), "license").unwrap();
1617
1618 let dir1 = temp.path().join("local-dir1").join("dir");
1625 std::fs::create_dir_all(&dir1).unwrap();
1626 let dir2 = temp.path().join("local-dir2").join("dir");
1627 std::fs::create_dir_all(&dir2).unwrap();
1628
1629 let package = Package::from_manifest(manifest).unwrap();
1630
1631 let webc = package.serialize().unwrap();
1632 let webc = from_bytes(webc).unwrap();
1633 let manifest = webc.manifest();
1634 let wapm_metadata = manifest.wapm().unwrap().unwrap();
1635
1636 assert!(wapm_metadata.name.is_none());
1637 assert!(wapm_metadata.version.is_none());
1638 assert!(wapm_metadata.description.is_none());
1639
1640 let fs_table = manifest.filesystem().unwrap().unwrap();
1641 assert_eq!(
1642 fs_table,
1643 [
1644 FileSystemMapping {
1645 from: None,
1646 volume_name: "/local-dir1/dir".to_string(),
1647 host_path: None,
1648 mount_path: "/dir1".to_string(),
1649 },
1650 FileSystemMapping {
1651 from: None,
1652 volume_name: "/local-dir2/dir".to_string(),
1653 host_path: None,
1654 mount_path: "/dir2".to_string(),
1655 },
1656 ]
1657 );
1658
1659 let readme_hash: [u8; 32] = sha2::Sha256::digest(b"readme").into();
1660 let license_hash: [u8; 32] = sha2::Sha256::digest(b"license").into();
1661
1662 let dir1_volume = webc.get_volume("/local-dir1/dir").unwrap();
1663 let dir2_volume = webc.get_volume("/local-dir2/dir").unwrap();
1664 let meta_volume = webc.get_volume("metadata").unwrap();
1665
1666 assert_eq!(
1667 meta_volume.read_file("LICENSE").unwrap(),
1668 (b"license".as_slice().into(), Some(license_hash)),
1669 );
1670 assert_eq!(
1671 meta_volume.read_file("README.md").unwrap(),
1672 (b"readme".as_slice().into(), Some(readme_hash)),
1673 );
1674 assert!(dir1_volume.read_dir("/").unwrap().is_empty());
1675 assert!(dir2_volume.read_dir("/").unwrap().is_empty());
1676 }
1677
1678 #[test]
1679 fn metadata_only_contains_relevant_files() {
1680 let temp = TempDir::new().unwrap();
1681 let wasmer_toml = r#"
1682 [package]
1683 name = "some/package"
1684 version = "0.0.0"
1685 description = ""
1686 license-file = "./path/to/LICENSE"
1687 readme = "README.md"
1688
1689 [[module]]
1690 name = "asdf"
1691 source = "asdf.wasm"
1692 abi = "none"
1693 bindings = { wai-version = "0.2.0", exports = "asdf.wai", imports = ["browser.wai"] }
1694 "#;
1695
1696 let manifest = temp.path().join("wasmer.toml");
1697 std::fs::write(&manifest, wasmer_toml).unwrap();
1698
1699 let license_dir = temp.path().join("path").join("to");
1700 std::fs::create_dir_all(&license_dir).unwrap();
1701 std::fs::write(license_dir.join("LICENSE"), "license").unwrap();
1702 std::fs::write(temp.path().join("README.md"), "readme").unwrap();
1703 std::fs::write(temp.path().join("asdf.wasm"), b"\0asm...").unwrap();
1704 std::fs::write(temp.path().join("asdf.wai"), "exports").unwrap();
1705 std::fs::write(temp.path().join("browser.wai"), "imports").unwrap();
1706 std::fs::write(temp.path().join("unwanted_file.txt"), "unwanted_file").unwrap();
1707
1708 let package = Package::from_manifest(manifest).unwrap();
1709
1710 let contents: Vec<_> = package
1711 .get_volume("metadata")
1712 .unwrap()
1713 .read_dir(&PathSegments::ROOT)
1714 .unwrap()
1715 .into_iter()
1716 .map(|(path, _, _)| path)
1717 .collect();
1718
1719 assert_eq!(
1720 contents,
1721 vec![
1722 PathSegment::parse("README.md").unwrap(),
1723 PathSegment::parse("asdf.wai").unwrap(),
1724 PathSegment::parse("browser.wai").unwrap(),
1725 PathSegment::parse("path").unwrap(),
1726 ]
1727 );
1728 }
1729
1730 #[test]
1731 fn create_from_in_memory() -> anyhow::Result<()> {
1732 let wasmer_toml = r#"
1733 [dependencies]
1734 "wasmer/python" = "3.12.9+build.9"
1735
1736
1737 [[command]]
1738 module = "wasmer/python:python"
1739 name = "hello"
1740 runner = "wasi"
1741
1742 [command.annotations.wasi]
1743 main-args = [ "-c", "import os; print([f for f in os.walk('/public')]); " ]
1744
1745 [fs]
1746 "/public" = "public"
1747 "#;
1748
1749 let manifest = toml::from_str(wasmer_toml)?;
1750
1751 let file_modified = SystemTime::now();
1752 let file_data = String::from("Hello, world!").as_bytes().to_vec();
1753
1754 let file = MemoryFile {
1755 modified: file_modified,
1756 data: file_data,
1757 };
1758
1759 let mut nodes = BTreeMap::new();
1760 nodes.insert(String::from("hello.txt"), MemoryNode::File(file));
1761
1762 let dir_modified = SystemTime::now();
1763 let dir = MemoryDir {
1764 modified: dir_modified,
1765 nodes,
1766 };
1767
1768 let volume = MemoryVolume { node: dir };
1769 let mut volumes = BTreeMap::new();
1770
1771 volumes.insert("public".to_string(), volume);
1772
1773 let atoms = BTreeMap::new();
1774 let package = super::Package::from_in_memory(
1775 manifest,
1776 volumes,
1777 atoms,
1778 MemoryVolume {
1779 node: MemoryDir {
1780 modified: SystemTime::now(),
1781 nodes: BTreeMap::new(),
1782 },
1783 },
1784 Strictness::Strict,
1785 )?;
1786
1787 _ = package.serialize()?;
1788
1789 Ok(())
1790 }
1791
1792 #[test]
1793 fn compare_fs_mem_manifest() -> anyhow::Result<()> {
1794 let wasmer_toml = r#"
1795 [package]
1796 name = "test"
1797 version = "0.0.0"
1798 description = "asdf"
1799 "#;
1800
1801 let temp = TempDir::new()?;
1802 let manifest_path = temp.path().join("wasmer.toml");
1803 std::fs::write(&manifest_path, wasmer_toml).unwrap();
1804
1805 let fs_package = super::Package::from_manifest(manifest_path)?;
1806
1807 let manifest = toml::from_str(wasmer_toml)?;
1808 let memory_package = super::Package::from_in_memory(
1809 manifest,
1810 Default::default(),
1811 Default::default(),
1812 MemoryVolume {
1813 node: MemoryDir {
1814 modified: SystemTime::UNIX_EPOCH,
1815 nodes: BTreeMap::new(),
1816 },
1817 },
1818 Strictness::Lossy,
1819 )?;
1820
1821 assert_eq!(memory_package.serialize()?, fs_package.serialize()?);
1822
1823 Ok(())
1824 }
1825
1826 #[test]
1827 fn compare_fs_mem_manifest_and_atoms() -> anyhow::Result<()> {
1828 let wasmer_toml = r#"
1829 [package]
1830 name = "test"
1831 version = "0.0.0"
1832 description = "asdf"
1833
1834 [[module]]
1835 name = "foo"
1836 source = "foo.wasm"
1837 abi = "wasi"
1838 "#;
1839
1840 let temp = TempDir::new()?;
1841 let manifest_path = temp.path().join("wasmer.toml");
1842 std::fs::write(&manifest_path, wasmer_toml).unwrap();
1843
1844 let atom_path = temp.path().join("foo.wasm");
1845 std::fs::write(&atom_path, b"").unwrap();
1846
1847 let fs_package = super::Package::from_manifest(manifest_path)?;
1848
1849 let manifest = toml::from_str(wasmer_toml)?;
1850 let mut atoms = BTreeMap::new();
1851 atoms.insert("foo".to_owned(), (None, OwnedBuffer::new()));
1852 let memory_package = super::Package::from_in_memory(
1853 manifest,
1854 Default::default(),
1855 atoms,
1856 MemoryVolume {
1857 node: MemoryDir {
1858 modified: SystemTime::UNIX_EPOCH,
1859 nodes: BTreeMap::new(),
1860 },
1861 },
1862 Strictness::Lossy,
1863 )?;
1864
1865 assert_eq!(memory_package.serialize()?, fs_package.serialize()?);
1866
1867 Ok(())
1868 }
1869
1870 #[test]
1871 fn compare_fs_mem_volume() -> anyhow::Result<()> {
1872 let wasmer_toml = r#"
1873 [package]
1874 name = "test"
1875 version = "0.0.0"
1876 description = "asdf"
1877
1878 [[module]]
1879 name = "foo"
1880 source = "foo.wasm"
1881 abi = "wasi"
1882
1883 [fs]
1884 "/bar" = "bar"
1885 "#;
1886
1887 let temp = TempDir::new()?;
1888 let manifest_path = temp.path().join("wasmer.toml");
1889 std::fs::write(&manifest_path, wasmer_toml).unwrap();
1890
1891 let atom_path = temp.path().join("foo.wasm");
1892 std::fs::write(&atom_path, b"").unwrap();
1893
1894 let bar = temp.path().join("bar");
1895 std::fs::create_dir(&bar).unwrap();
1896
1897 let baz = bar.join("baz");
1898 std::fs::write(&baz, b"abc")?;
1899
1900 let baz_metadata = std::fs::metadata(&baz)?;
1901
1902 let fs_package = super::Package::from_manifest(manifest_path)?;
1903
1904 let manifest = toml::from_str(wasmer_toml)?;
1905
1906 let mut atoms = BTreeMap::new();
1907 atoms.insert("foo".to_owned(), (None, OwnedBuffer::new()));
1908
1909 let mut volumes = BTreeMap::new();
1910 volumes.insert(
1911 "/bar".to_owned(),
1912 MemoryVolume {
1913 node: MemoryDir {
1914 modified: SystemTime::UNIX_EPOCH,
1915 nodes: {
1916 let mut children = BTreeMap::new();
1917
1918 children.insert(
1919 "baz".to_owned(),
1920 MemoryNode::File(MemoryFile {
1921 modified: baz_metadata.modified()?,
1922 data: b"abc".to_vec(),
1923 }),
1924 );
1925
1926 children
1927 },
1928 },
1929 },
1930 );
1931 let memory_package = super::Package::from_in_memory(
1932 manifest,
1933 volumes,
1934 atoms,
1935 MemoryVolume {
1936 node: MemoryDir {
1937 modified: SystemTime::UNIX_EPOCH,
1938 nodes: Default::default(),
1939 },
1940 },
1941 Strictness::Lossy,
1942 )?;
1943
1944 assert_eq!(memory_package.serialize()?, fs_package.serialize()?);
1945
1946 Ok(())
1947 }
1948
1949 #[test]
1950 fn compare_fs_mem_bindings() -> anyhow::Result<()> {
1951 let temp = TempDir::new().unwrap();
1952
1953 let wasmer_toml = r#"
1954 [package]
1955 name = "some/package"
1956 version = "0.0.0"
1957 description = ""
1958 license-file = "LICENSE"
1959 readme = "README.md"
1960
1961 [[module]]
1962 name = "asdf"
1963 source = "asdf.wasm"
1964 abi = "none"
1965 bindings = { wai-version = "0.2.0", exports = "asdf.wai", imports = ["browser.wai"] }
1966
1967 [fs]
1968 "/dir1" = "local-dir1/dir"
1969 "/dir2" = "local-dir2/dir"
1970 "#;
1971
1972 let manifest = temp.path().join("wasmer.toml");
1973 std::fs::write(&manifest, wasmer_toml).unwrap();
1974
1975 std::fs::write(temp.path().join("LICENSE"), "license").unwrap();
1976 std::fs::write(temp.path().join("README.md"), "readme").unwrap();
1977 std::fs::write(temp.path().join("asdf.wasm"), b"\0asm...").unwrap();
1978 std::fs::write(temp.path().join("asdf.wai"), "exports").unwrap();
1979 std::fs::write(temp.path().join("browser.wai"), "imports").unwrap();
1980
1981 let dir1 = temp.path().join("local-dir1").join("dir");
1988 std::fs::create_dir_all(&dir1).unwrap();
1989 let dir2 = temp.path().join("local-dir2").join("dir");
1990 std::fs::create_dir_all(&dir2).unwrap();
1991
1992 let fs_package = super::Package::from_manifest(manifest)?;
1993
1994 let manifest = toml::from_str(wasmer_toml)?;
1995
1996 let mut atoms = BTreeMap::new();
1997 atoms.insert(
1998 "asdf".to_owned(),
1999 (None, OwnedBuffer::from_static(b"\0asm...")),
2000 );
2001
2002 let mut volumes = BTreeMap::new();
2003 volumes.insert(
2004 "/local-dir1/dir".to_owned(),
2005 MemoryVolume {
2006 node: MemoryDir {
2007 modified: SystemTime::UNIX_EPOCH,
2008 nodes: Default::default(),
2009 },
2010 },
2011 );
2012 volumes.insert(
2013 "/local-dir2/dir".to_owned(),
2014 MemoryVolume {
2015 node: MemoryDir {
2016 modified: SystemTime::UNIX_EPOCH,
2017 nodes: Default::default(),
2018 },
2019 },
2020 );
2021
2022 let memory_package = super::Package::from_in_memory(
2023 manifest,
2024 volumes,
2025 atoms,
2026 MemoryVolume {
2027 node: MemoryDir {
2028 modified: SystemTime::UNIX_EPOCH,
2029 nodes: {
2030 let mut children = BTreeMap::new();
2031
2032 children.insert(
2033 "README.md".to_owned(),
2034 MemoryNode::File(MemoryFile {
2035 modified: temp.path().join("README.md").metadata()?.modified()?,
2036 data: b"readme".to_vec(),
2037 }),
2038 );
2039
2040 children.insert(
2041 "LICENSE".to_owned(),
2042 MemoryNode::File(MemoryFile {
2043 modified: temp.path().join("LICENSE").metadata()?.modified()?,
2044 data: b"license".to_vec(),
2045 }),
2046 );
2047
2048 children.insert(
2049 "asdf.wai".to_owned(),
2050 MemoryNode::File(MemoryFile {
2051 modified: temp.path().join("asdf.wai").metadata()?.modified()?,
2052 data: b"exports".to_vec(),
2053 }),
2054 );
2055
2056 children.insert(
2057 "browser.wai".to_owned(),
2058 MemoryNode::File(MemoryFile {
2059 modified: temp.path().join("browser.wai").metadata()?.modified()?,
2060 data: b"imports".to_vec(),
2061 }),
2062 );
2063
2064 children
2065 },
2066 },
2067 },
2068 Strictness::Lossy,
2069 )?;
2070
2071 let memory_package = memory_package.serialize()?;
2072 let fs_package = fs_package.serialize()?;
2073
2074 assert_eq!(memory_package, fs_package);
2075
2076 Ok(())
2077 }
2078}