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