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 concurrency_support,
289
290 memory_reservation_for_growth: _,
292
293 generate_address_map: _,
298
299 debug_adapter_modules: _,
301 } = self.tunables;
302
303 Self::check_collector(collector, other.collector)?;
304 Self::check_int(
305 memory_reservation,
306 other.memory_reservation,
307 "memory reservation",
308 )?;
309 Self::check_int(
310 memory_guard_size,
311 other.memory_guard_size,
312 "memory guard size",
313 )?;
314 Self::check_bool(
315 debug_native,
316 other.debug_native,
317 "native debug information support",
318 )?;
319 Self::check_bool(debug_guest, other.debug_guest, "guest debug")?;
320 Self::check_bool(
321 parse_wasm_debuginfo,
322 other.parse_wasm_debuginfo,
323 "WebAssembly backtrace support",
324 )?;
325 Self::check_bool(consume_fuel, other.consume_fuel, "fuel support")?;
326 Self::check_bool(
327 epoch_interruption,
328 other.epoch_interruption,
329 "epoch interruption",
330 )?;
331 Self::check_bool(memory_may_move, other.memory_may_move, "memory may move")?;
332 Self::check_bool(
333 guard_before_linear_memory,
334 other.guard_before_linear_memory,
335 "guard before linear memory",
336 )?;
337 Self::check_bool(table_lazy_init, other.table_lazy_init, "table lazy init")?;
338 Self::check_bool(
339 relaxed_simd_deterministic,
340 other.relaxed_simd_deterministic,
341 "relaxed simd deterministic semantics",
342 )?;
343 Self::check_bool(
344 winch_callable,
345 other.winch_callable,
346 "Winch calling convention",
347 )?;
348 Self::check_bool(
349 signals_based_traps,
350 other.signals_based_traps,
351 "Signals-based traps",
352 )?;
353 Self::check_bool(
354 memory_init_cow,
355 other.memory_init_cow,
356 "memory initialization with CoW",
357 )?;
358 Self::check_bool(inlining, other.inlining, "function inlining")?;
359 Self::check_int(
360 inlining_small_callee_size,
361 other.inlining_small_callee_size,
362 "function inlining small-callee size",
363 )?;
364 Self::check_int(
365 inlining_sum_size_threshold,
366 other.inlining_sum_size_threshold,
367 "function inlining sum-size threshold",
368 )?;
369 Self::check_bool(
370 concurrency_support,
371 other.concurrency_support,
372 "concurrency support",
373 )?;
374 Self::check_intra_module_inlining(inlining_intra_module, other.inlining_intra_module)?;
375
376 Ok(())
377 }
378
379 fn check_features(&mut self, other: &wasmparser::WasmFeatures) -> Result<()> {
380 let module_features = wasmparser::WasmFeatures::from_bits_truncate(self.features);
381 let missing_features = (*other & module_features) ^ module_features;
382 for (name, _) in missing_features.iter_names() {
383 let name = name.to_ascii_lowercase();
384 bail!(
385 "Module was compiled with support for WebAssembly feature \
386 `{name}` but it is not enabled for the host",
387 );
388 }
389 Ok(())
390 }
391
392 fn check_collector(
393 module: Option<wasmtime_environ::Collector>,
394 host: Option<wasmtime_environ::Collector>,
395 ) -> Result<()> {
396 match (module, host) {
397 (None, _) => Ok(()),
400 (Some(module), Some(host)) if module == host => Ok(()),
401
402 (Some(_), None) => {
403 bail!("module was compiled with GC however GC is disabled in the host")
404 }
405
406 (Some(module), Some(host)) => {
407 bail!(
408 "module was compiled for the {module} collector but \
409 the host is configured to use the {host} collector",
410 )
411 }
412 }
413 }
414
415 fn check_intra_module_inlining(
416 module: wasmtime_environ::IntraModuleInlining,
417 host: wasmtime_environ::IntraModuleInlining,
418 ) -> Result<()> {
419 if module == host {
420 return Ok(());
421 }
422
423 let desc = |cfg| match cfg {
424 wasmtime_environ::IntraModuleInlining::No => "without intra-module inlining",
425 wasmtime_environ::IntraModuleInlining::Yes => "with intra-module inlining",
426 wasmtime_environ::IntraModuleInlining::WhenUsingGc => {
427 "with intra-module inlining only when using GC"
428 }
429 };
430
431 let module = desc(module);
432 let host = desc(host);
433
434 bail!("module was compiled {module} however the host is configured {host}")
435 }
436}
437
438#[cfg(test)]
439mod test {
440 use super::*;
441 use crate::{Cache, Config, Module, OptLevel};
442 use std::{
443 collections::hash_map::DefaultHasher,
444 hash::{Hash, Hasher},
445 };
446 use tempfile::TempDir;
447
448 #[test]
449 fn test_architecture_mismatch() -> Result<()> {
450 let engine = Engine::default();
451 let mut metadata = Metadata::new(&engine)?;
452 metadata.target = "unknown-generic-linux".to_string();
453
454 match metadata.check_compatible(&engine) {
455 Ok(_) => unreachable!(),
456 Err(e) => assert_eq!(
457 e.to_string(),
458 "Module was compiled for architecture 'unknown'",
459 ),
460 }
461
462 Ok(())
463 }
464
465 #[test]
467 #[cfg(all(target_arch = "x86_64", not(miri)))]
468 fn test_os_mismatch() -> Result<()> {
469 let engine = Engine::default();
470 let mut metadata = Metadata::new(&engine)?;
471
472 metadata.target = format!(
473 "{}-generic-unknown",
474 target_lexicon::Triple::host().architecture
475 );
476
477 match metadata.check_compatible(&engine) {
478 Ok(_) => unreachable!(),
479 Err(e) => assert_eq!(
480 e.to_string(),
481 "Module was compiled for operating system 'unknown'",
482 ),
483 }
484
485 Ok(())
486 }
487
488 fn assert_contains(error: &Error, msg: &str) {
489 let msg = msg.trim();
490 if error.chain().any(|e| e.to_string().contains(msg)) {
491 return;
492 }
493
494 panic!("failed to find:\n\n'''{msg}\n'''\n\nwithin error message:\n\n'''{error:?}'''")
495 }
496
497 #[test]
498 fn test_cranelift_flags_mismatch() -> Result<()> {
499 let engine = Engine::default();
500 let mut metadata = Metadata::new(&engine)?;
501
502 metadata
503 .shared_flags
504 .push(("preserve_frame_pointers", FlagValue::Bool(false)));
505
506 match metadata.check_compatible(&engine) {
507 Ok(_) => unreachable!(),
508 Err(e) => {
509 assert_contains(
510 &e,
511 "compilation settings of module incompatible with native host",
512 );
513 assert_contains(
514 &e,
515 "setting \"preserve_frame_pointers\" is configured to Bool(false) which is not supported",
516 );
517 }
518 }
519
520 Ok(())
521 }
522
523 #[test]
524 fn test_isa_flags_mismatch() -> Result<()> {
525 let engine = Engine::default();
526 let mut metadata = Metadata::new(&engine)?;
527
528 metadata
529 .isa_flags
530 .push(("not_a_flag", FlagValue::Bool(true)));
531
532 match metadata.check_compatible(&engine) {
533 Ok(_) => unreachable!(),
534 Err(e) => {
535 assert_contains(
536 &e,
537 "compilation settings of module incompatible with native host",
538 );
539 assert_contains(
540 &e,
541 "don't know how to test for target-specific flag \"not_a_flag\" at runtime",
542 );
543 }
544 }
545
546 Ok(())
547 }
548
549 #[test]
550 #[cfg_attr(miri, ignore)]
551 #[cfg(target_pointer_width = "64")] fn test_tunables_int_mismatch() -> Result<()> {
553 let engine = Engine::default();
554 let mut metadata = Metadata::new(&engine)?;
555
556 metadata.tunables.memory_guard_size = 0;
557
558 match metadata.check_compatible(&engine) {
559 Ok(_) => unreachable!(),
560 Err(e) => assert_eq!(
561 e.to_string(),
562 "Module was compiled with a memory guard size of '0' but '33554432' is expected for the host"
563 ),
564 }
565
566 Ok(())
567 }
568
569 #[test]
570 fn test_tunables_bool_mismatch() -> Result<()> {
571 let mut config = Config::new();
572 config.epoch_interruption(true);
573
574 let engine = Engine::new(&config)?;
575 let mut metadata = Metadata::new(&engine)?;
576 metadata.tunables.epoch_interruption = false;
577
578 match metadata.check_compatible(&engine) {
579 Ok(_) => unreachable!(),
580 Err(e) => assert_eq!(
581 e.to_string(),
582 "Module was compiled without epoch interruption but it is enabled for the host"
583 ),
584 }
585
586 let mut config = Config::new();
587 config.epoch_interruption(false);
588
589 let engine = Engine::new(&config)?;
590 let mut metadata = Metadata::new(&engine)?;
591 metadata.tunables.epoch_interruption = true;
592
593 match metadata.check_compatible(&engine) {
594 Ok(_) => unreachable!(),
595 Err(e) => assert_eq!(
596 e.to_string(),
597 "Module was compiled with epoch interruption but it is not enabled for the host"
598 ),
599 }
600
601 Ok(())
602 }
603
604 #[test]
606 #[cfg(all(target_arch = "x86_64", not(miri)))]
607 fn test_feature_mismatch() -> Result<()> {
608 let mut config = Config::new();
609 config.wasm_threads(true);
610
611 let engine = Engine::new(&config)?;
612 let mut metadata = Metadata::new(&engine)?;
613 metadata.features &= !wasmparser::WasmFeatures::THREADS.bits();
614
615 metadata.check_compatible(&engine)?;
618
619 let mut config = Config::new();
620 config.wasm_threads(false);
621
622 let engine = Engine::new(&config)?;
623 let mut metadata = Metadata::new(&engine)?;
624 metadata.features |= wasmparser::WasmFeatures::THREADS.bits();
625
626 match metadata.check_compatible(&engine) {
627 Ok(_) => unreachable!(),
628 Err(e) => assert_eq!(
629 e.to_string(),
630 "Module was compiled with support for WebAssembly feature \
631 `threads` but it is not enabled for the host"
632 ),
633 }
634
635 Ok(())
636 }
637
638 #[test]
639 fn engine_weak_upgrades() {
640 let engine = Engine::default();
641 let weak = engine.weak();
642 weak.upgrade()
643 .expect("engine is still alive, so weak reference can upgrade");
644 drop(engine);
645 assert!(
646 weak.upgrade().is_none(),
647 "engine was dropped, so weak reference cannot upgrade"
648 );
649 }
650
651 #[test]
652 #[cfg_attr(miri, ignore)]
653 fn cache_accounts_for_opt_level() -> Result<()> {
654 let _ = env_logger::try_init();
655
656 let td = TempDir::new()?;
657 let config_path = td.path().join("config.toml");
658 std::fs::write(
659 &config_path,
660 &format!(
661 "
662 [cache]
663 directory = '{}'
664 ",
665 td.path().join("cache").display()
666 ),
667 )?;
668 let mut cfg = Config::new();
669 cfg.cranelift_opt_level(OptLevel::None)
670 .cache(Some(Cache::from_file(Some(&config_path))?));
671 let engine = Engine::new(&cfg)?;
672 Module::new(&engine, "(module (func))")?;
673 let cache_config = engine
674 .config()
675 .cache
676 .as_ref()
677 .expect("Missing cache config");
678 assert_eq!(cache_config.cache_hits(), 0);
679 assert_eq!(cache_config.cache_misses(), 1);
680 Module::new(&engine, "(module (func))")?;
681 assert_eq!(cache_config.cache_hits(), 1);
682 assert_eq!(cache_config.cache_misses(), 1);
683
684 let mut cfg = Config::new();
685 cfg.cranelift_opt_level(OptLevel::Speed)
686 .cache(Some(Cache::from_file(Some(&config_path))?));
687 let engine = Engine::new(&cfg)?;
688 let cache_config = engine
689 .config()
690 .cache
691 .as_ref()
692 .expect("Missing cache config");
693 Module::new(&engine, "(module (func))")?;
694 assert_eq!(cache_config.cache_hits(), 0);
695 assert_eq!(cache_config.cache_misses(), 1);
696 Module::new(&engine, "(module (func))")?;
697 assert_eq!(cache_config.cache_hits(), 1);
698 assert_eq!(cache_config.cache_misses(), 1);
699
700 let mut cfg = Config::new();
701 cfg.cranelift_opt_level(OptLevel::SpeedAndSize)
702 .cache(Some(Cache::from_file(Some(&config_path))?));
703 let engine = Engine::new(&cfg)?;
704 let cache_config = engine
705 .config()
706 .cache
707 .as_ref()
708 .expect("Missing cache config");
709 Module::new(&engine, "(module (func))")?;
710 assert_eq!(cache_config.cache_hits(), 0);
711 assert_eq!(cache_config.cache_misses(), 1);
712 Module::new(&engine, "(module (func))")?;
713 assert_eq!(cache_config.cache_hits(), 1);
714 assert_eq!(cache_config.cache_misses(), 1);
715
716 let mut cfg = Config::new();
717 cfg.debug_info(true)
718 .cache(Some(Cache::from_file(Some(&config_path))?));
719 let engine = Engine::new(&cfg)?;
720 let cache_config = engine
721 .config()
722 .cache
723 .as_ref()
724 .expect("Missing cache config");
725 Module::new(&engine, "(module (func))")?;
726 assert_eq!(cache_config.cache_hits(), 0);
727 assert_eq!(cache_config.cache_misses(), 1);
728 Module::new(&engine, "(module (func))")?;
729 assert_eq!(cache_config.cache_hits(), 1);
730 assert_eq!(cache_config.cache_misses(), 1);
731
732 Ok(())
733 }
734
735 #[test]
736 fn precompile_compatibility_key_accounts_for_opt_level() {
737 fn hash_for_config(cfg: &Config) -> u64 {
738 let engine = Engine::new(cfg).expect("Config should be valid");
739 let mut hasher = DefaultHasher::new();
740 engine.precompile_compatibility_hash().hash(&mut hasher);
741 hasher.finish()
742 }
743 let mut cfg = Config::new();
744 cfg.cranelift_opt_level(OptLevel::None);
745 let opt_none_hash = hash_for_config(&cfg);
746 cfg.cranelift_opt_level(OptLevel::Speed);
747 let opt_speed_hash = hash_for_config(&cfg);
748 assert_ne!(opt_none_hash, opt_speed_hash)
749 }
750
751 #[test]
752 fn precompile_compatibility_key_accounts_for_module_version_strategy() -> Result<()> {
753 fn hash_for_config(cfg: &Config) -> u64 {
754 let engine = Engine::new(cfg).expect("Config should be valid");
755 let mut hasher = DefaultHasher::new();
756 engine.precompile_compatibility_hash().hash(&mut hasher);
757 hasher.finish()
758 }
759 let mut cfg_custom_version = Config::new();
760 cfg_custom_version.module_version(ModuleVersionStrategy::Custom("1.0.1111".to_string()))?;
761 let custom_version_hash = hash_for_config(&cfg_custom_version);
762
763 let mut cfg_default_version = Config::new();
764 cfg_default_version.module_version(ModuleVersionStrategy::WasmtimeVersion)?;
765 let default_version_hash = hash_for_config(&cfg_default_version);
766
767 let mut cfg_none_version = Config::new();
768 cfg_none_version.module_version(ModuleVersionStrategy::None)?;
769 let none_version_hash = hash_for_config(&cfg_none_version);
770
771 assert_ne!(custom_version_hash, default_version_hash);
772 assert_ne!(custom_version_hash, none_version_hash);
773 assert_ne!(default_version_hash, none_version_hash);
774
775 Ok(())
776 }
777
778 #[test]
779 #[cfg_attr(miri, ignore)]
780 #[cfg(feature = "component-model")]
781 fn components_are_cached() -> Result<()> {
782 use crate::component::Component;
783
784 let td = TempDir::new()?;
785 let config_path = td.path().join("config.toml");
786 std::fs::write(
787 &config_path,
788 &format!(
789 "
790 [cache]
791 directory = '{}'
792 ",
793 td.path().join("cache").display()
794 ),
795 )?;
796 let mut cfg = Config::new();
797 cfg.cache(Some(Cache::from_file(Some(&config_path))?));
798 let engine = Engine::new(&cfg)?;
799 let cache_config = engine
800 .config()
801 .cache
802 .as_ref()
803 .expect("Missing cache config");
804 Component::new(&engine, "(component (core module (func)))")?;
805 assert_eq!(cache_config.cache_hits(), 0);
806 assert_eq!(cache_config.cache_misses(), 1);
807 Component::new(&engine, "(component (core module (func)))")?;
808 assert_eq!(cache_config.cache_hits(), 1);
809 assert_eq!(cache_config.cache_misses(), 1);
810
811 Ok(())
812 }
813}