1use crate::prelude::*;
25use crate::{Engine, ModuleVersionStrategy, Precompiled};
26use core::fmt;
27use core::str::FromStr;
28use object::endian::Endianness;
29#[cfg(any(feature = "cranelift", feature = "winch"))]
30use object::write::{Object, StandardSegment};
31use object::{FileFlags, Object as _, ObjectSection, read::elf::ElfFile64};
32use serde_derive::{Deserialize, Serialize};
33use wasmtime_environ::obj;
34use wasmtime_environ::{FlagValue, ObjectKind, Tunables};
35
36const VERSION: u8 = 0;
37
38pub fn check_compatible(engine: &Engine, mmap: &[u8], expected: ObjectKind) -> Result<()> {
47 let obj = ElfFile64::<Endianness>::parse(mmap)
61 .map_err(obj::ObjectCrateErrorWrapper)
62 .context("failed to parse precompiled artifact as an ELF")?;
63 let expected_e_flags = match expected {
64 ObjectKind::Module => obj::EF_WASMTIME_MODULE,
65 ObjectKind::Component => obj::EF_WASMTIME_COMPONENT,
66 };
67 match obj.flags() {
68 FileFlags::Elf {
69 os_abi: obj::ELFOSABI_WASMTIME,
70 abi_version: 0,
71 e_flags,
72 } if e_flags & expected_e_flags == expected_e_flags => {}
73 _ => bail!("incompatible object file format"),
74 }
75
76 let data = obj
77 .section_by_name(obj::ELF_WASM_ENGINE)
78 .ok_or_else(|| format_err!("failed to find section `{}`", obj::ELF_WASM_ENGINE))?
79 .data()
80 .map_err(obj::ObjectCrateErrorWrapper)?;
81 let (first, data) = data
82 .split_first()
83 .ok_or_else(|| format_err!("invalid engine section"))?;
84 if *first != VERSION {
85 bail!("mismatched version in engine section");
86 }
87 let (len, data) = data
88 .split_first()
89 .ok_or_else(|| format_err!("invalid engine section"))?;
90 let len = usize::from(*len);
91 let (version, data) = if data.len() < len + 1 {
92 bail!("engine section too small")
93 } else {
94 data.split_at(len)
95 };
96
97 match &engine.config().module_version {
98 ModuleVersionStrategy::WasmtimeVersion => {
99 let version = core::str::from_utf8(version)?;
100 if version != env!("CARGO_PKG_VERSION_MAJOR") {
101 bail!("Module was compiled with incompatible Wasmtime version '{version}'");
102 }
103 }
104 ModuleVersionStrategy::Custom(v) => {
105 let version = core::str::from_utf8(&version)?;
106 if version != v {
107 bail!("Module was compiled with incompatible version '{version}'");
108 }
109 }
110 ModuleVersionStrategy::None => { }
111 }
112 postcard::from_bytes::<Metadata<'_>>(data)?.check_compatible(engine)
113}
114
115#[cfg(any(feature = "cranelift", feature = "winch"))]
116pub fn append_compiler_info(engine: &Engine, obj: &mut Object<'_>, metadata: &Metadata<'_>) {
117 let section = obj.add_section(
118 obj.segment_name(StandardSegment::Data).to_vec(),
119 obj::ELF_WASM_ENGINE.as_bytes().to_vec(),
120 object::SectionKind::ReadOnlyData,
121 );
122 let mut data = Vec::new();
123 data.push(VERSION);
124 let version = match &engine.config().module_version {
125 ModuleVersionStrategy::WasmtimeVersion => env!("CARGO_PKG_VERSION_MAJOR"),
126 ModuleVersionStrategy::Custom(c) => c,
127 ModuleVersionStrategy::None => "",
128 };
129 assert!(
131 version.len() < 256,
132 "package version must be less than 256 bytes"
133 );
134 data.push(version.len() as u8);
135 data.extend_from_slice(version.as_bytes());
136 data.extend(postcard::to_allocvec(metadata).unwrap());
137 obj.set_section_data(section, data, 1);
138}
139
140fn detect_precompiled<'data, R: object::ReadRef<'data>>(
141 obj: ElfFile64<'data, Endianness, R>,
142) -> Option<Precompiled> {
143 match obj.flags() {
144 FileFlags::Elf {
145 os_abi: obj::ELFOSABI_WASMTIME,
146 abi_version: 0,
147 e_flags,
148 } if e_flags & obj::EF_WASMTIME_MODULE != 0 => Some(Precompiled::Module),
149 FileFlags::Elf {
150 os_abi: obj::ELFOSABI_WASMTIME,
151 abi_version: 0,
152 e_flags,
153 } if e_flags & obj::EF_WASMTIME_COMPONENT != 0 => Some(Precompiled::Component),
154 _ => None,
155 }
156}
157
158pub fn detect_precompiled_bytes(bytes: &[u8]) -> Option<Precompiled> {
159 detect_precompiled(ElfFile64::parse(bytes).ok()?)
160}
161
162#[cfg(feature = "std")]
163pub fn detect_precompiled_file(path: impl AsRef<std::path::Path>) -> Result<Option<Precompiled>> {
164 let read_cache = object::ReadCache::new(std::fs::File::open(path)?);
165 let obj = ElfFile64::parse(&read_cache)?;
166 Ok(detect_precompiled(obj))
167}
168
169#[derive(Serialize, Deserialize)]
170pub struct Metadata<'a> {
171 target: String,
172 #[serde(borrow)]
173 shared_flags: Vec<(&'a str, FlagValue<'a>)>,
174 #[serde(borrow)]
175 isa_flags: Vec<(&'a str, FlagValue<'a>)>,
176 tunables: Tunables,
177 features: u64,
178}
179
180impl Metadata<'_> {
181 #[cfg(any(feature = "cranelift", feature = "winch"))]
182 pub fn new(engine: &Engine) -> Result<Metadata<'static>> {
183 let compiler = engine.try_compiler()?;
184 Ok(Metadata {
185 target: compiler.triple().to_string(),
186 shared_flags: compiler.flags(),
187 isa_flags: compiler.isa_flags(),
188 tunables: engine.tunables().clone(),
189 features: engine.features().bits(),
190 })
191 }
192
193 fn check_compatible(mut self, engine: &Engine) -> Result<()> {
194 self.check_triple(engine)?;
195 self.check_shared_flags(engine)?;
196 self.check_isa_flags(engine)?;
197 self.check_tunables(&engine.tunables())?;
198 self.check_features(&engine.features())?;
199 Ok(())
200 }
201
202 fn check_triple(&self, engine: &Engine) -> Result<()> {
203 let engine_target = engine.target();
204 let module_target =
205 target_lexicon::Triple::from_str(&self.target).map_err(|e| format_err!(e))?;
206
207 if module_target.architecture != engine_target.architecture {
208 bail!(
209 "Module was compiled for architecture '{}'",
210 module_target.architecture
211 );
212 }
213
214 if module_target.operating_system != engine_target.operating_system {
215 bail!(
216 "Module was compiled for operating system '{}'",
217 module_target.operating_system
218 );
219 }
220
221 Ok(())
222 }
223
224 fn check_shared_flags(&mut self, engine: &Engine) -> Result<()> {
225 for (name, val) in self.shared_flags.iter() {
226 engine
227 .check_compatible_with_shared_flag(name, val)
228 .map_err(|s| crate::Error::msg(s))
229 .context("compilation settings of module incompatible with native host")?;
230 }
231 Ok(())
232 }
233
234 fn check_isa_flags(&mut self, engine: &Engine) -> Result<()> {
235 for (name, val) in self.isa_flags.iter() {
236 engine
237 .check_compatible_with_isa_flag(name, val)
238 .map_err(|s| crate::Error::msg(s))
239 .context("compilation settings of module incompatible with native host")?;
240 }
241 Ok(())
242 }
243
244 fn check_int<T: Eq + fmt::Display>(found: T, expected: T, feature: &str) -> Result<()> {
245 if found == expected {
246 return Ok(());
247 }
248
249 bail!(
250 "Module was compiled with a {feature} of '{found}' but '{expected}' is expected for the host"
251 );
252 }
253
254 fn check_bool(found: bool, expected: bool, feature: impl fmt::Display) -> Result<()> {
255 if found == expected {
256 return Ok(());
257 }
258
259 bail!(
260 "Module was compiled {} {} but it {} enabled for the host",
261 if found { "with" } else { "without" },
262 feature,
263 if expected { "is" } else { "is not" }
264 );
265 }
266
267 fn check_tunables(&mut self, other: &Tunables) -> Result<()> {
268 let Tunables {
269 collector,
270 memory_reservation,
271 memory_guard_size,
272 debug_native,
273 debug_guest,
274 parse_wasm_debuginfo,
275 consume_fuel,
276 epoch_interruption,
277 memory_may_move,
278 guard_before_linear_memory,
279 table_lazy_init,
280 relaxed_simd_deterministic,
281 winch_callable,
282 signals_based_traps,
283 memory_init_cow,
284 inlining,
285 inlining_intra_module,
286 inlining_small_callee_size,
287 inlining_sum_size_threshold,
288
289 memory_reservation_for_growth: _,
291
292 generate_address_map: _,
297
298 debug_adapter_modules: _,
300 } = self.tunables;
301
302 Self::check_collector(collector, other.collector)?;
303 Self::check_int(
304 memory_reservation,
305 other.memory_reservation,
306 "memory reservation",
307 )?;
308 Self::check_int(
309 memory_guard_size,
310 other.memory_guard_size,
311 "memory guard size",
312 )?;
313 Self::check_bool(
314 debug_native,
315 other.debug_native,
316 "native debug information support",
317 )?;
318 Self::check_bool(debug_guest, other.debug_guest, "guest debug")?;
319 Self::check_bool(
320 parse_wasm_debuginfo,
321 other.parse_wasm_debuginfo,
322 "WebAssembly backtrace support",
323 )?;
324 Self::check_bool(consume_fuel, other.consume_fuel, "fuel support")?;
325 Self::check_bool(
326 epoch_interruption,
327 other.epoch_interruption,
328 "epoch interruption",
329 )?;
330 Self::check_bool(memory_may_move, other.memory_may_move, "memory may move")?;
331 Self::check_bool(
332 guard_before_linear_memory,
333 other.guard_before_linear_memory,
334 "guard before linear memory",
335 )?;
336 Self::check_bool(table_lazy_init, other.table_lazy_init, "table lazy init")?;
337 Self::check_bool(
338 relaxed_simd_deterministic,
339 other.relaxed_simd_deterministic,
340 "relaxed simd deterministic semantics",
341 )?;
342 Self::check_bool(
343 winch_callable,
344 other.winch_callable,
345 "Winch calling convention",
346 )?;
347 Self::check_bool(
348 signals_based_traps,
349 other.signals_based_traps,
350 "Signals-based traps",
351 )?;
352 Self::check_bool(
353 memory_init_cow,
354 other.memory_init_cow,
355 "memory initialization with CoW",
356 )?;
357 Self::check_bool(inlining, other.inlining, "function inlining")?;
358 Self::check_int(
359 inlining_small_callee_size,
360 other.inlining_small_callee_size,
361 "function inlining small-callee size",
362 )?;
363 Self::check_int(
364 inlining_sum_size_threshold,
365 other.inlining_sum_size_threshold,
366 "function inlining sum-size threshold",
367 )?;
368 Self::check_intra_module_inlining(inlining_intra_module, other.inlining_intra_module)?;
369
370 Ok(())
371 }
372
373 fn check_features(&mut self, other: &wasmparser::WasmFeatures) -> Result<()> {
374 let module_features = wasmparser::WasmFeatures::from_bits_truncate(self.features);
375 let missing_features = (*other & module_features) ^ module_features;
376 for (name, _) in missing_features.iter_names() {
377 let name = name.to_ascii_lowercase();
378 bail!(
379 "Module was compiled with support for WebAssembly feature \
380 `{name}` but it is not enabled for the host",
381 );
382 }
383 Ok(())
384 }
385
386 fn check_collector(
387 module: Option<wasmtime_environ::Collector>,
388 host: Option<wasmtime_environ::Collector>,
389 ) -> Result<()> {
390 match (module, host) {
391 (None, _) => Ok(()),
394 (Some(module), Some(host)) if module == host => Ok(()),
395
396 (Some(_), None) => {
397 bail!("module was compiled with GC however GC is disabled in the host")
398 }
399
400 (Some(module), Some(host)) => {
401 bail!(
402 "module was compiled for the {module} collector but \
403 the host is configured to use the {host} collector",
404 )
405 }
406 }
407 }
408
409 fn check_intra_module_inlining(
410 module: wasmtime_environ::IntraModuleInlining,
411 host: wasmtime_environ::IntraModuleInlining,
412 ) -> Result<()> {
413 if module == host {
414 return Ok(());
415 }
416
417 let desc = |cfg| match cfg {
418 wasmtime_environ::IntraModuleInlining::No => "without intra-module inlining",
419 wasmtime_environ::IntraModuleInlining::Yes => "with intra-module inlining",
420 wasmtime_environ::IntraModuleInlining::WhenUsingGc => {
421 "with intra-module inlining only when using GC"
422 }
423 };
424
425 let module = desc(module);
426 let host = desc(host);
427
428 bail!("module was compiled {module} however the host is configured {host}")
429 }
430}
431
432#[cfg(test)]
433mod test {
434 use super::*;
435 use crate::{Cache, Config, Module, OptLevel};
436 use std::{
437 collections::hash_map::DefaultHasher,
438 hash::{Hash, Hasher},
439 };
440 use tempfile::TempDir;
441
442 #[test]
443 fn test_architecture_mismatch() -> Result<()> {
444 let engine = Engine::default();
445 let mut metadata = Metadata::new(&engine)?;
446 metadata.target = "unknown-generic-linux".to_string();
447
448 match metadata.check_compatible(&engine) {
449 Ok(_) => unreachable!(),
450 Err(e) => assert_eq!(
451 e.to_string(),
452 "Module was compiled for architecture 'unknown'",
453 ),
454 }
455
456 Ok(())
457 }
458
459 #[test]
461 #[cfg(all(target_arch = "x86_64", not(miri)))]
462 fn test_os_mismatch() -> Result<()> {
463 let engine = Engine::default();
464 let mut metadata = Metadata::new(&engine)?;
465
466 metadata.target = format!(
467 "{}-generic-unknown",
468 target_lexicon::Triple::host().architecture
469 );
470
471 match metadata.check_compatible(&engine) {
472 Ok(_) => unreachable!(),
473 Err(e) => assert_eq!(
474 e.to_string(),
475 "Module was compiled for operating system 'unknown'",
476 ),
477 }
478
479 Ok(())
480 }
481
482 fn assert_contains(error: &Error, msg: &str) {
483 let msg = msg.trim();
484 if error.chain().any(|e| e.to_string().contains(msg)) {
485 return;
486 }
487
488 panic!("failed to find:\n\n'''{msg}\n'''\n\nwithin error message:\n\n'''{error:?}'''")
489 }
490
491 #[test]
492 fn test_cranelift_flags_mismatch() -> Result<()> {
493 let engine = Engine::default();
494 let mut metadata = Metadata::new(&engine)?;
495
496 metadata
497 .shared_flags
498 .push(("preserve_frame_pointers", FlagValue::Bool(false)));
499
500 match metadata.check_compatible(&engine) {
501 Ok(_) => unreachable!(),
502 Err(e) => {
503 assert_contains(
504 &e,
505 "compilation settings of module incompatible with native host",
506 );
507 assert_contains(
508 &e,
509 "setting \"preserve_frame_pointers\" is configured to Bool(false) which is not supported",
510 );
511 }
512 }
513
514 Ok(())
515 }
516
517 #[test]
518 fn test_isa_flags_mismatch() -> Result<()> {
519 let engine = Engine::default();
520 let mut metadata = Metadata::new(&engine)?;
521
522 metadata
523 .isa_flags
524 .push(("not_a_flag", FlagValue::Bool(true)));
525
526 match metadata.check_compatible(&engine) {
527 Ok(_) => unreachable!(),
528 Err(e) => {
529 assert_contains(
530 &e,
531 "compilation settings of module incompatible with native host",
532 );
533 assert_contains(
534 &e,
535 "don't know how to test for target-specific flag \"not_a_flag\" at runtime",
536 );
537 }
538 }
539
540 Ok(())
541 }
542
543 #[test]
544 #[cfg_attr(miri, ignore)]
545 #[cfg(target_pointer_width = "64")] fn test_tunables_int_mismatch() -> Result<()> {
547 let engine = Engine::default();
548 let mut metadata = Metadata::new(&engine)?;
549
550 metadata.tunables.memory_guard_size = 0;
551
552 match metadata.check_compatible(&engine) {
553 Ok(_) => unreachable!(),
554 Err(e) => assert_eq!(
555 e.to_string(),
556 "Module was compiled with a memory guard size of '0' but '33554432' is expected for the host"
557 ),
558 }
559
560 Ok(())
561 }
562
563 #[test]
564 fn test_tunables_bool_mismatch() -> Result<()> {
565 let mut config = Config::new();
566 config.epoch_interruption(true);
567
568 let engine = Engine::new(&config)?;
569 let mut metadata = Metadata::new(&engine)?;
570 metadata.tunables.epoch_interruption = false;
571
572 match metadata.check_compatible(&engine) {
573 Ok(_) => unreachable!(),
574 Err(e) => assert_eq!(
575 e.to_string(),
576 "Module was compiled without epoch interruption but it is enabled for the host"
577 ),
578 }
579
580 let mut config = Config::new();
581 config.epoch_interruption(false);
582
583 let engine = Engine::new(&config)?;
584 let mut metadata = Metadata::new(&engine)?;
585 metadata.tunables.epoch_interruption = true;
586
587 match metadata.check_compatible(&engine) {
588 Ok(_) => unreachable!(),
589 Err(e) => assert_eq!(
590 e.to_string(),
591 "Module was compiled with epoch interruption but it is not enabled for the host"
592 ),
593 }
594
595 Ok(())
596 }
597
598 #[test]
600 #[cfg(all(target_arch = "x86_64", not(miri)))]
601 fn test_feature_mismatch() -> Result<()> {
602 let mut config = Config::new();
603 config.wasm_threads(true);
604
605 let engine = Engine::new(&config)?;
606 let mut metadata = Metadata::new(&engine)?;
607 metadata.features &= !wasmparser::WasmFeatures::THREADS.bits();
608
609 metadata.check_compatible(&engine)?;
612
613 let mut config = Config::new();
614 config.wasm_threads(false);
615
616 let engine = Engine::new(&config)?;
617 let mut metadata = Metadata::new(&engine)?;
618 metadata.features |= wasmparser::WasmFeatures::THREADS.bits();
619
620 match metadata.check_compatible(&engine) {
621 Ok(_) => unreachable!(),
622 Err(e) => assert_eq!(
623 e.to_string(),
624 "Module was compiled with support for WebAssembly feature \
625 `threads` but it is not enabled for the host"
626 ),
627 }
628
629 Ok(())
630 }
631
632 #[test]
633 fn engine_weak_upgrades() {
634 let engine = Engine::default();
635 let weak = engine.weak();
636 weak.upgrade()
637 .expect("engine is still alive, so weak reference can upgrade");
638 drop(engine);
639 assert!(
640 weak.upgrade().is_none(),
641 "engine was dropped, so weak reference cannot upgrade"
642 );
643 }
644
645 #[test]
646 #[cfg_attr(miri, ignore)]
647 fn cache_accounts_for_opt_level() -> Result<()> {
648 let _ = env_logger::try_init();
649
650 let td = TempDir::new()?;
651 let config_path = td.path().join("config.toml");
652 std::fs::write(
653 &config_path,
654 &format!(
655 "
656 [cache]
657 directory = '{}'
658 ",
659 td.path().join("cache").display()
660 ),
661 )?;
662 let mut cfg = Config::new();
663 cfg.cranelift_opt_level(OptLevel::None)
664 .cache(Some(Cache::from_file(Some(&config_path))?));
665 let engine = Engine::new(&cfg)?;
666 Module::new(&engine, "(module (func))")?;
667 let cache_config = engine
668 .config()
669 .cache
670 .as_ref()
671 .expect("Missing cache config");
672 assert_eq!(cache_config.cache_hits(), 0);
673 assert_eq!(cache_config.cache_misses(), 1);
674 Module::new(&engine, "(module (func))")?;
675 assert_eq!(cache_config.cache_hits(), 1);
676 assert_eq!(cache_config.cache_misses(), 1);
677
678 let mut cfg = Config::new();
679 cfg.cranelift_opt_level(OptLevel::Speed)
680 .cache(Some(Cache::from_file(Some(&config_path))?));
681 let engine = Engine::new(&cfg)?;
682 let cache_config = engine
683 .config()
684 .cache
685 .as_ref()
686 .expect("Missing cache config");
687 Module::new(&engine, "(module (func))")?;
688 assert_eq!(cache_config.cache_hits(), 0);
689 assert_eq!(cache_config.cache_misses(), 1);
690 Module::new(&engine, "(module (func))")?;
691 assert_eq!(cache_config.cache_hits(), 1);
692 assert_eq!(cache_config.cache_misses(), 1);
693
694 let mut cfg = Config::new();
695 cfg.cranelift_opt_level(OptLevel::SpeedAndSize)
696 .cache(Some(Cache::from_file(Some(&config_path))?));
697 let engine = Engine::new(&cfg)?;
698 let cache_config = engine
699 .config()
700 .cache
701 .as_ref()
702 .expect("Missing cache config");
703 Module::new(&engine, "(module (func))")?;
704 assert_eq!(cache_config.cache_hits(), 0);
705 assert_eq!(cache_config.cache_misses(), 1);
706 Module::new(&engine, "(module (func))")?;
707 assert_eq!(cache_config.cache_hits(), 1);
708 assert_eq!(cache_config.cache_misses(), 1);
709
710 let mut cfg = Config::new();
711 cfg.debug_info(true)
712 .cache(Some(Cache::from_file(Some(&config_path))?));
713 let engine = Engine::new(&cfg)?;
714 let cache_config = engine
715 .config()
716 .cache
717 .as_ref()
718 .expect("Missing cache config");
719 Module::new(&engine, "(module (func))")?;
720 assert_eq!(cache_config.cache_hits(), 0);
721 assert_eq!(cache_config.cache_misses(), 1);
722 Module::new(&engine, "(module (func))")?;
723 assert_eq!(cache_config.cache_hits(), 1);
724 assert_eq!(cache_config.cache_misses(), 1);
725
726 Ok(())
727 }
728
729 #[test]
730 fn precompile_compatibility_key_accounts_for_opt_level() {
731 fn hash_for_config(cfg: &Config) -> u64 {
732 let engine = Engine::new(cfg).expect("Config should be valid");
733 let mut hasher = DefaultHasher::new();
734 engine.precompile_compatibility_hash().hash(&mut hasher);
735 hasher.finish()
736 }
737 let mut cfg = Config::new();
738 cfg.cranelift_opt_level(OptLevel::None);
739 let opt_none_hash = hash_for_config(&cfg);
740 cfg.cranelift_opt_level(OptLevel::Speed);
741 let opt_speed_hash = hash_for_config(&cfg);
742 assert_ne!(opt_none_hash, opt_speed_hash)
743 }
744
745 #[test]
746 fn precompile_compatibility_key_accounts_for_module_version_strategy() -> Result<()> {
747 fn hash_for_config(cfg: &Config) -> u64 {
748 let engine = Engine::new(cfg).expect("Config should be valid");
749 let mut hasher = DefaultHasher::new();
750 engine.precompile_compatibility_hash().hash(&mut hasher);
751 hasher.finish()
752 }
753 let mut cfg_custom_version = Config::new();
754 cfg_custom_version.module_version(ModuleVersionStrategy::Custom("1.0.1111".to_string()))?;
755 let custom_version_hash = hash_for_config(&cfg_custom_version);
756
757 let mut cfg_default_version = Config::new();
758 cfg_default_version.module_version(ModuleVersionStrategy::WasmtimeVersion)?;
759 let default_version_hash = hash_for_config(&cfg_default_version);
760
761 let mut cfg_none_version = Config::new();
762 cfg_none_version.module_version(ModuleVersionStrategy::None)?;
763 let none_version_hash = hash_for_config(&cfg_none_version);
764
765 assert_ne!(custom_version_hash, default_version_hash);
766 assert_ne!(custom_version_hash, none_version_hash);
767 assert_ne!(default_version_hash, none_version_hash);
768
769 Ok(())
770 }
771
772 #[test]
773 #[cfg_attr(miri, ignore)]
774 #[cfg(feature = "component-model")]
775 fn components_are_cached() -> Result<()> {
776 use crate::component::Component;
777
778 let td = TempDir::new()?;
779 let config_path = td.path().join("config.toml");
780 std::fs::write(
781 &config_path,
782 &format!(
783 "
784 [cache]
785 directory = '{}'
786 ",
787 td.path().join("cache").display()
788 ),
789 )?;
790 let mut cfg = Config::new();
791 cfg.cache(Some(Cache::from_file(Some(&config_path))?));
792 let engine = Engine::new(&cfg)?;
793 let cache_config = engine
794 .config()
795 .cache
796 .as_ref()
797 .expect("Missing cache config");
798 Component::new(&engine, "(component (core module (func)))")?;
799 assert_eq!(cache_config.cache_hits(), 0);
800 assert_eq!(cache_config.cache_misses(), 1);
801 Component::new(&engine, "(component (core module (func)))")?;
802 assert_eq!(cache_config.cache_hits(), 1);
803 assert_eq!(cache_config.cache_misses(), 1);
804
805 Ok(())
806 }
807}