1use anyhow::{Context, Result, bail};
2use clap::Parser;
3use libtest_mimic::Trial;
4use std::borrow::Cow;
5use std::collections::{HashMap, HashSet};
6use std::fmt;
7use std::fs;
8use std::mem;
9use std::path::{Path, PathBuf};
10use std::process::{Command, Stdio};
11use std::sync::{Arc, Mutex};
12use wasm_encoder::{Encode, Section};
13use wit_component::{ComponentEncoder, StringEncoding};
14
15mod c;
16mod config;
17mod cpp;
18mod csharp;
19mod custom;
20mod d;
21mod go;
22mod moonbit;
23mod runner;
24mod rust;
25mod wat;
26
27#[derive(Default, Debug, Clone, Parser)]
69pub struct Opts {
70 test: Vec<PathBuf>,
72
73 #[clap(long, value_name = "PATH")]
75 artifacts: PathBuf,
76
77 #[clap(short, long, value_name = "REGEX")]
81 filter: Option<regex::Regex>,
82
83 #[clap(long, default_value = "wasmtime")]
85 runner: std::ffi::OsString,
86
87 #[clap(flatten)]
88 rust: rust::RustOpts,
89
90 #[clap(flatten)]
91 c: c::COpts,
92
93 #[clap(flatten)]
94 custom: custom::CustomOpts,
95
96 #[clap(short, long)]
102 inherit_stderr: bool,
103
104 #[clap(short, long, required = true, value_delimiter = ',')]
108 languages: Vec<String>,
109
110 #[clap(short, long, conflicts_with = "format")]
112 quiet: bool,
113
114 #[clap(long, value_name = "N")]
116 test_threads: Option<usize>,
117
118 #[clap(long)]
120 exact: bool,
121
122 #[clap(long, value_name = "FILTER")]
125 skip: Vec<String>,
126
127 #[clap(long, value_name = "auto|always|never")]
129 color: Option<libtest_mimic::ColorSetting>,
130
131 #[clap(long, value_name = "pretty|terse")]
133 format: Option<libtest_mimic::FormatSetting>,
134}
135
136impl Opts {
137 pub fn run(&self, wit_bindgen: &Path) -> Result<()> {
138 Runner {
139 opts: self.clone(),
140 rust_state: None,
141 go_state: None,
142 wit_bindgen: wit_bindgen.to_path_buf(),
143 test_runner: runner::TestRunner::new(&self.runner)?,
144 }
145 .run()
146 }
147}
148
149#[derive(Clone)]
151struct Test {
152 name: String,
156
157 path: PathBuf,
159
160 config: config::WitConfig,
162
163 kind: TestKind,
164}
165
166#[derive(Clone)]
167enum TestKind {
168 Runtime(Vec<Component>),
169 Codegen(PathBuf),
170}
171
172#[derive(Clone)]
174struct Component {
175 name: String,
179
180 path: PathBuf,
182
183 kind: Kind,
185
186 language: Language,
188
189 bindgen: Bindgen,
192
193 contents: String,
195
196 lang_config: Option<HashMap<String, toml::Value>>,
198
199 wasmtime_flags: config::StringList,
201}
202
203#[derive(Clone)]
204struct Bindgen {
205 args: Vec<String>,
208 wit_path: PathBuf,
211 world: String,
214 wit_config: config::WitConfig,
216}
217
218#[derive(Debug, PartialEq, Copy, Clone)]
219enum Kind {
220 Runner,
221 Test,
222}
223
224#[derive(Clone, Debug, PartialEq, Eq, Hash)]
225enum Language {
226 Rust,
227 C,
228 Cpp,
229 Wat,
230 Csharp,
231 MoonBit,
232 Go,
233 D,
234 Custom(custom::Language),
235}
236
237struct Compile<'a> {
240 component: &'a Component,
241 bindings_dir: &'a Path,
242 artifacts_dir: &'a Path,
243 output: &'a Path,
244}
245
246struct Verify<'a> {
249 wit_test: &'a Path,
250 bindings_dir: &'a Path,
251 artifacts_dir: &'a Path,
252 args: &'a [String],
253 world: &'a str,
254}
255
256struct Runner {
258 opts: Opts,
259 rust_state: Option<rust::State>,
260 go_state: Option<go::State>,
261 wit_bindgen: PathBuf,
262 test_runner: runner::TestRunner,
263}
264
265impl Runner {
266 fn run(mut self) -> Result<()> {
268 let mut tests = HashMap::new();
270 for test in self.opts.test.iter() {
271 self.discover_tests(&mut tests, test)
272 .with_context(|| format!("failed to discover tests in {test:?}"))?;
273 }
274 if tests.is_empty() {
275 bail!(
276 "no `test.wit` files found were found in {:?}",
277 self.opts.test,
278 );
279 }
280
281 self.prepare_languages(&tests)?;
282 let me = Arc::new(self);
283 me.run_codegen_tests(&tests)?;
284 me.run_runtime_tests(&tests)?;
285
286 Ok(())
287 }
288
289 fn discover_tests(&self, tests: &mut HashMap<String, Test>, path: &Path) -> Result<()> {
291 if path.is_file() {
292 if path.extension().and_then(|s| s.to_str()) == Some("wit") {
293 let config =
294 fs::read_to_string(path).with_context(|| format!("failed to read {path:?}"))?;
295 let config = config::parse_test_config::<config::WitConfig>(&config, "//@")
296 .with_context(|| format!("failed to parse test config from {path:?}"))?;
297 return self.insert_test(&path, config, TestKind::Codegen(path.to_owned()), tests);
298 }
299
300 return Ok(());
301 }
302
303 let runtime_candidate = path.join("test.wit");
304 if runtime_candidate.is_file() {
305 let (config, components) = self
306 .load_runtime_test(&runtime_candidate, path)
307 .with_context(|| format!("failed to load test in {path:?}"))?;
308 return self.insert_test(path, config, TestKind::Runtime(components), tests);
309 }
310
311 let codegen_candidate = path.join("wit");
312 if codegen_candidate.is_dir() {
313 return self.insert_test(
314 path,
315 Default::default(),
316 TestKind::Codegen(codegen_candidate),
317 tests,
318 );
319 }
320
321 for entry in path.read_dir().context("failed to read test directory")? {
322 let entry = entry.context("failed to read test directory entry")?;
323 let path = entry.path();
324
325 self.discover_tests(tests, &path)?;
326 }
327
328 Ok(())
329 }
330
331 fn insert_test(
332 &self,
333 path: &Path,
334 config: config::WitConfig,
335 kind: TestKind,
336 tests: &mut HashMap<String, Test>,
337 ) -> Result<()> {
338 let test_name = path
339 .file_name()
340 .and_then(|s| s.to_str())
341 .context("non-utf-8 filename")?;
342 let prev = tests.insert(
343 test_name.to_string(),
344 Test {
345 name: test_name.to_string(),
346 path: path.to_path_buf(),
347 config,
348 kind,
349 },
350 );
351 if prev.is_some() {
352 bail!("duplicate test name `{test_name}` found");
353 }
354 Ok(())
355 }
356
357 fn load_runtime_test(
361 &self,
362 wit: &Path,
363 dir: &Path,
364 ) -> Result<(config::WitConfig, Vec<Component>)> {
365 let mut resolve = wit_parser::Resolve::default();
366
367 let wit_path = if dir.join("deps").exists() { dir } else { wit };
368 let (pkg, _files) = resolve.push_path(wit_path).context(format!(
369 "failed to load `test.wit` in test directory: {:?}",
370 &wit
371 ))?;
372 let resolve = Arc::new(resolve);
373
374 let wit_contents = std::fs::read_to_string(wit)?;
375 let wit_config: config::WitConfig = config::parse_test_config(&wit_contents, "//@")
376 .context("failed to parse WIT test config")?;
377
378 let mut worlds = Vec::new();
379
380 let mut push_world = |kind: Kind, name: &str| -> Result<()> {
381 let world = resolve.select_world(&[pkg], Some(name)).with_context(|| {
382 format!("failed to find expected `{name}` world to generate bindings")
383 })?;
384 worlds.push((world, kind));
385 Ok(())
386 };
387 push_world(Kind::Runner, wit_config.runner_world())?;
388 for world in wit_config.dependency_worlds() {
389 push_world(Kind::Test, &world)?;
390 }
391
392 let mut components = Vec::new();
393 let mut any_runner = false;
394 let mut any_test = false;
395
396 for entry in dir.read_dir().context("failed to read test directory")? {
397 let entry = entry.context("failed to read test directory entry")?;
398 let path = entry.path();
399
400 let Some(name) = path.file_name().and_then(|s| s.to_str()) else {
401 continue;
402 };
403 if name == "test.wit" {
404 continue;
405 }
406
407 let Some((world, kind)) = worlds
408 .iter()
409 .find(|(world, _kind)| name.starts_with(&resolve.worlds[*world].name))
410 else {
411 log::debug!("skipping file {name:?}");
412 continue;
413 };
414 match kind {
415 Kind::Runner => any_runner = true,
416 Kind::Test => any_test = true,
417 }
418 let bindgen = Bindgen {
419 args: Vec::new(),
420 wit_config: wit_config.clone(),
421 world: resolve.worlds[*world].name.clone(),
422 wit_path: wit_path.to_path_buf(),
423 };
424 let component = self
425 .parse_component(&path, *kind, bindgen)
426 .with_context(|| format!("failed to parse component source file {path:?}"))?;
427 components.push(component);
428 }
429
430 if !any_runner {
431 bail!("no runner files found in test directory");
432 }
433 if !any_test {
434 bail!("no test files found in test directory");
435 }
436
437 Ok((wit_config, components))
438 }
439
440 fn parse_component(&self, path: &Path, kind: Kind, mut bindgen: Bindgen) -> Result<Component> {
443 let extension = path
444 .extension()
445 .and_then(|s| s.to_str())
446 .context("non-utf-8 path extension")?;
447
448 let language = match extension {
449 "rs" => Language::Rust,
450 "c" => Language::C,
451 "cpp" => Language::Cpp,
452 "wat" => Language::Wat,
453 "cs" => Language::Csharp,
454 "mbt" => Language::MoonBit,
455 "go" => Language::Go,
456 "d" => Language::D,
457 other => Language::Custom(custom::Language::lookup(self, other)?),
458 };
459
460 let contents = fs::read_to_string(&path)?;
461 let config = match language.obj().comment_prefix_for_test_config() {
462 Some(comment) => {
463 config::parse_test_config::<config::RuntimeTestConfig>(&contents, comment)?
464 }
465 None => Default::default(),
466 };
467 assert!(bindgen.args.is_empty());
468 bindgen.args = config.args.into();
469
470 Ok(Component {
471 name: path.file_stem().unwrap().to_str().unwrap().to_string(),
472 path: path.to_path_buf(),
473 language,
474 bindgen,
475 kind,
476 contents,
477 lang_config: config.lang,
478 wasmtime_flags: config.wasmtime_flags,
479 })
480 }
481
482 fn prepare_languages(&mut self, tests: &HashMap<String, Test>) -> Result<()> {
485 let all_languages = self.all_languages();
486
487 let mut prepared = HashSet::new();
488 let mut prepare = |lang: &Language| -> Result<()> {
489 if !self.include_language(lang) || !prepared.insert(lang.clone()) {
490 return Ok(());
491 }
492 lang.obj()
493 .prepare(self)
494 .with_context(|| format!("failed to prepare language {lang}"))
495 };
496
497 for test in tests.values() {
498 match &test.kind {
499 TestKind::Runtime(c) => {
500 for component in c {
501 prepare(&component.language)?
502 }
503 }
504 TestKind::Codegen(_) => {
505 for lang in all_languages.iter() {
506 prepare(lang)?;
507 }
508 }
509 }
510 }
511
512 Ok(())
513 }
514
515 fn all_languages(&self) -> Vec<Language> {
516 let mut languages = Language::ALL.to_vec();
517 for (ext, _) in self.opts.custom.custom.iter() {
518 languages.push(Language::Custom(
519 custom::Language::lookup(self, ext).unwrap(),
520 ));
521 }
522 languages
523 }
524
525 fn run_codegen_tests(self: &Arc<Self>, tests: &HashMap<String, Test>) -> Result<()> {
527 let mut codegen_tests = Vec::new();
528 let languages = self.all_languages();
529 for (name, config, test) in tests.iter().filter_map(|(name, t)| match &t.kind {
530 TestKind::Runtime(_) => None,
531 TestKind::Codegen(p) => Some((name, &t.config, p)),
532 }) {
533 if let Some(filter) = &self.opts.filter {
534 if !filter.is_match(name) {
535 continue;
536 }
537 }
538 for language in languages.iter() {
539 if !self.include_language(&language) {
542 continue;
543 }
544
545 let mut args = Vec::new();
546 for arg in language.obj().default_bindgen_args_for_codegen() {
547 args.push(arg.to_string());
548 }
549
550 codegen_tests.push((
551 language.clone(),
552 test.to_owned(),
553 name.to_string(),
554 args.clone(),
555 config.clone(),
556 ));
557
558 for (args_kind, new_args) in language.obj().codegen_test_variants() {
559 let mut args = args.clone();
560 for arg in new_args.iter() {
561 args.push(arg.to_string());
562 }
563 codegen_tests.push((
564 language.clone(),
565 test.clone(),
566 format!("{name}-{args_kind}"),
567 args,
568 config.clone(),
569 ));
570 }
571 }
572 }
573
574 if codegen_tests.is_empty() {
575 return Ok(());
576 }
577
578 println!("=== Running codegen tests ===");
579 self.run_tests(
580 codegen_tests
581 .into_iter()
582 .map(|(language, test, args_kind, args, config)| {
583 let me = self.clone();
584 let should_fail = language
585 .obj()
586 .should_fail_verify(self, &args_kind, &config, &args);
587
588 let name = format!("{language} {args_kind} {test:?}");
589 Trial::test(&name, move || {
590 let result = me
591 .codegen_test(&language, &test, &args_kind, &args, &config)
592 .with_context(|| {
593 format!("failed to codegen test for `{language}` over {test:?}")
594 });
595
596 me.render_error(
597 StepResult::new(result)
598 .should_fail(should_fail)
599 .metadata("language", language)
600 .metadata("variant", args_kind),
601 )
602 })
603 })
604 .collect::<Vec<_>>(),
605 );
606
607 Ok(())
608 }
609
610 fn run_tests(&self, trials: Vec<Trial>) {
611 let args = libtest_mimic::Arguments {
612 skip: self.opts.skip.clone(),
613 quiet: self.opts.quiet,
614 format: self.opts.format,
615 color: self.opts.color,
616 test_threads: self.opts.test_threads,
617 exact: self.opts.exact,
618 ..Default::default()
619 };
620 libtest_mimic::run(&args, trials).exit_if_failed();
621 }
622
623 fn codegen_test(
629 &self,
630 language: &Language,
631 test: &Path,
632 args_kind: &str,
633 args: &[String],
634 config: &config::WitConfig,
635 ) -> Result<()> {
636 let mut resolve = wit_parser::Resolve::default();
637 let (pkg, _) = resolve.push_path(test).context("failed to load WIT")?;
638 let world = resolve
639 .select_world(&[pkg], None)
640 .or_else(|err| {
641 resolve
642 .select_world(&[pkg], Some("imports"))
643 .map_err(|_| err)
644 })
645 .context("failed to select a world for bindings generation")?;
646 let world = resolve.worlds[world].name.clone();
647
648 let artifacts_dir = std::env::current_dir()?
649 .join(&self.opts.artifacts)
650 .join("codegen")
651 .join(language.to_string())
652 .join(args_kind);
653 let _ = fs::remove_dir_all(&artifacts_dir);
654 let bindings_dir = artifacts_dir.join("bindings");
655 let bindgen = Bindgen {
656 args: args.to_vec(),
657 wit_path: test.to_path_buf(),
658 world: world.clone(),
659 wit_config: config.clone(),
660 };
661 language
662 .obj()
663 .generate_bindings(self, &bindgen, &bindings_dir)
664 .context("failed to generate bindings")?;
665
666 language
667 .obj()
668 .verify(
669 self,
670 &Verify {
671 world: &world,
672 artifacts_dir: &artifacts_dir,
673 bindings_dir: &bindings_dir,
674 wit_test: test,
675 args: &bindgen.args,
676 },
677 )
678 .context("failed to verify generated bindings")?;
679
680 Ok(())
681 }
682
683 fn run_runtime_tests(self: &Arc<Self>, tests: &HashMap<String, Test>) -> Result<()> {
685 let components = tests
686 .values()
687 .filter(|t| match &self.opts.filter {
688 Some(filter) => filter.is_match(&t.name),
689 None => true,
690 })
691 .filter_map(|t| match &t.kind {
692 TestKind::Runtime(c) => Some(c.iter().map(move |c| (t, c))),
693 TestKind::Codegen(_) => None,
694 })
695 .flat_map(|i| i)
696 .filter(|(_test, component)| self.include_language(&component.language))
699 .collect::<Vec<_>>();
700
701 println!("=== Compiling components ===");
702 let compilations = Arc::new(Mutex::new(Vec::new()));
703 self.run_tests(
704 components
705 .into_iter()
706 .map(|(test, component)| {
707 let me = self.clone();
708 let compilations = compilations.clone();
709 let test = test.clone();
710 let component = component.clone();
711 let should_fail = component.language.obj().should_fail_compile(
712 self,
713 &component.path,
714 &component.bindgen.wit_config,
715 );
716 Trial::test(&component.path.display().to_string(), move || {
717 let result = me.compile_component(&test, &component).with_context(|| {
718 format!("failed to compile component {:?}", component.path)
719 });
720 match result {
721 Ok(path) if !should_fail => {
722 compilations.lock().unwrap().push((test, component, path));
723 Ok(())
724 }
725 other => me.render_error(
726 StepResult::new(other.map(|_| ()))
727 .should_fail(should_fail)
728 .metadata("component", &component.name)
729 .metadata("path", component.path.display()),
730 ),
731 }
732 })
733 })
734 .collect(),
735 );
736 let compilations = mem::take(&mut *compilations.lock().unwrap());
737
738 let mut compiled_components = HashMap::new();
743 for (test, component, path) in compilations {
744 let list = compiled_components.entry(test.name).or_insert(Vec::new());
745 list.push((component, path));
746 }
747
748 let mut to_run = Vec::new();
749 for (test, components) in compiled_components.iter() {
750 for a in components.iter().filter(|(c, _)| c.kind == Kind::Runner) {
751 self.push_tests(&tests[test.as_str()], components, a, &mut to_run)
752 .with_context(|| format!("failed to make test for `{test}`"))?;
753 }
754 }
755
756 println!("=== Running runtime tests ===");
757
758 self.run_tests(
759 to_run
760 .into_iter()
761 .map(|(case_name, (runner, runner_path), test_components)| {
762 let me = self.clone();
763 let mut name = format!("{case_name}");
764 for component in [&runner]
765 .into_iter()
766 .chain(test_components.iter().map(|p| &p.0))
767 {
768 name.push_str(&format!(
769 " | {}",
770 component.path.file_name().unwrap().to_str().unwrap()
771 ));
772 }
773 let case_name = case_name.to_string();
774 let runner = runner.clone();
775 let runner_path = runner_path.to_path_buf();
776 let case = tests[case_name.as_str()].clone();
777 Trial::test(&name, move || {
778 let result = me
779 .runtime_test(&case, &runner, &runner_path, &test_components)
780 .with_context(|| format!("failed to run `{}`", case.name));
781 me.render_error(
782 StepResult::new(result)
783 .metadata("runner", runner.path.display())
784 .metadata("compiled runner", runner_path.display()),
785 )
786 })
787 })
788 .collect(),
789 );
790
791 Ok(())
792 }
793
794 fn push_tests(
797 &self,
798 test: &Test,
799 components: &[(Component, PathBuf)],
800 runner: &(Component, PathBuf),
801 to_run: &mut Vec<(String, (Component, PathBuf), Vec<(Component, PathBuf)>)>,
802 ) -> Result<()> {
803 fn push(
811 worlds: &[String],
812 components: &[(Component, PathBuf)],
813 test: &mut Vec<(Component, PathBuf)>,
814 commit: &mut dyn FnMut(Vec<(Component, PathBuf)>),
815 ) -> Result<()> {
816 match worlds.split_first() {
817 Some((world, rest)) => {
818 let mut any = false;
819 for (component, path) in components {
820 if component.bindgen.world == *world {
821 any = true;
822 test.push((component.clone(), path.clone()));
823 push(rest, components, test, commit)?;
824 test.pop();
825 }
826 }
827 if !any {
828 bail!("no components found for `{world}`");
829 }
830 }
831
832 None => commit(test.clone()),
834 }
835 Ok(())
836 }
837
838 push(
839 &test.config.dependency_worlds(),
840 components,
841 &mut Vec::new(),
842 &mut |test_components| {
843 to_run.push((
844 test.name.clone(),
845 (runner.0.clone(), runner.1.clone()),
846 test_components,
847 ));
848 },
849 )
850 }
851
852 fn compile_component(&self, test: &Test, component: &Component) -> Result<PathBuf> {
857 let root_dir = std::env::current_dir()?
858 .join(&self.opts.artifacts)
859 .join(&test.name);
860 let artifacts_dir = root_dir.join(format!("{}-{}", component.name, component.language));
861 let _ = fs::remove_dir_all(&artifacts_dir);
862 let bindings_dir = artifacts_dir.join("bindings");
863 let output = root_dir.join(format!("{}-{}.wasm", component.name, component.language));
864 component
865 .language
866 .obj()
867 .generate_bindings(self, &component.bindgen, &bindings_dir)?;
868 let result = Compile {
869 component,
870 bindings_dir: &bindings_dir,
871 artifacts_dir: &artifacts_dir,
872 output: &output,
873 };
874 component.language.obj().compile(self, &result)?;
875
876 let wasm = fs::read(&output)
878 .with_context(|| format!("failed to read output wasm file {output:?}"))?;
879 if !wasmparser::Parser::is_component(&wasm) {
880 bail!("output file {output:?} is not a component");
881 }
882 wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all())
883 .validate_all(&wasm)
884 .with_context(|| {
885 format!(
886 "compiler produced invalid wasm file {output:?} for component {}",
887 component.name
888 )
889 })?;
890
891 Ok(output)
892 }
893
894 fn runtime_test(
899 &self,
900 case: &Test,
901 runner: &Component,
902 runner_wasm: &Path,
903 test_components: &[(Component, PathBuf)],
904 ) -> Result<()> {
905 let composed = if case.config.wac.is_none() {
911 self.compose_wasm_with_wasm_compose(runner_wasm, test_components)?
912 } else {
913 self.compose_wasm_with_wac(case, runner, runner_wasm, test_components)?
914 };
915
916 let dst = runner_wasm.parent().unwrap();
917 let mut filename = format!(
918 "composed-{}",
919 runner.path.file_name().unwrap().to_str().unwrap(),
920 );
921 for (test, _) in test_components {
922 filename.push_str("-");
923 filename.push_str(test.path.file_name().unwrap().to_str().unwrap());
924 }
925 filename.push_str(".wasm");
926 let composed_wasm = dst.join(filename);
927 write_if_different(&composed_wasm, &composed)?;
928
929 let mut cmd = self.test_runner.command();
930 for component in [runner]
931 .into_iter()
932 .chain(test_components.iter().map(|(c, _)| c))
933 {
934 for flag in Vec::from(component.wasmtime_flags.clone()) {
935 cmd.arg(flag);
936 }
937 }
938 cmd.arg(&composed_wasm);
939 self.run_command(&mut cmd)?;
940 Ok(())
941 }
942
943 fn compose_wasm_with_wasm_compose(
944 &self,
945 runner_wasm: &Path,
946 test_components: &[(Component, PathBuf)],
947 ) -> Result<Vec<u8>> {
948 assert!(test_components.len() > 0);
949 let mut last_bytes = None;
950 let mut path: PathBuf;
951 for (i, (_component, component_path)) in test_components.iter().enumerate() {
952 let main = match last_bytes.take() {
953 Some(bytes) => {
954 path = runner_wasm.with_extension(&format!("composition{i}.wasm"));
955 std::fs::write(&path, &bytes)
956 .with_context(|| format!("failed to write temporary file {path:?}"))?;
957 path.as_path()
958 }
959 None => runner_wasm,
960 };
961
962 let mut config = wasm_compose::config::Config::default();
963 config.definitions = vec![component_path.to_path_buf()];
964 last_bytes = Some(
965 wasm_compose::composer::ComponentComposer::new(main, &config)
966 .compose()
967 .with_context(|| {
968 format!("failed to compose {main:?} with {component_path:?}")
969 })?,
970 );
971 }
972
973 Ok(last_bytes.unwrap())
974 }
975
976 fn compose_wasm_with_wac(
977 &self,
978 case: &Test,
979 runner: &Component,
980 runner_wasm: &Path,
981 test_components: &[(Component, PathBuf)],
982 ) -> Result<Vec<u8>> {
983 let document = match &case.config.wac {
984 Some(path) => {
985 let wac_config = case.path.join(path);
986 fs::read_to_string(&wac_config)
987 .with_context(|| format!("failed to read {wac_config:?}"))?
988 }
989 None => {
992 let mut script = String::from("package example:composition;\n");
993 let mut args = Vec::new();
994 for (component, _path) in test_components {
995 let world = &component.bindgen.world;
996 args.push(format!("...{world}"));
997 script.push_str(&format!("let {world} = new test:{world} {{ ... }};\n"));
998 }
999 args.push("...".to_string());
1000 let runner = &runner.bindgen.world;
1001 script.push_str(&format!(
1002 "let runner = new test:{runner} {{ {} }};\n\
1003 export runner...;",
1004 args.join(", ")
1005 ));
1006
1007 script
1008 }
1009 };
1010
1011 let components_as_packages = test_components
1014 .iter()
1015 .map(|(component, path)| {
1016 Ok((format!("test:{}", component.bindgen.world), fs::read(path)?))
1017 })
1018 .collect::<Result<Vec<_>>>()?;
1019
1020 let runner_name = format!("test:{}", runner.bindgen.world);
1021 let mut packages = indexmap::IndexMap::new();
1022 packages.insert(
1023 wac_types::BorrowedPackageKey {
1024 name: &runner_name,
1025 version: None,
1026 },
1027 fs::read(runner_wasm)?,
1028 );
1029 for (name, contents) in components_as_packages.iter() {
1030 packages.insert(
1031 wac_types::BorrowedPackageKey {
1032 name,
1033 version: None,
1034 },
1035 contents.clone(),
1036 );
1037 }
1038
1039 let document =
1041 wac_parser::Document::parse(&document).context("failed to parse wac script")?;
1042 document
1043 .resolve(packages)
1044 .context("failed to run `wac` resolve")?
1045 .encode(wac_graph::EncodeOptions {
1046 define_components: true,
1047 validate: false,
1048 processor: None,
1049 })
1050 .context("failed to encode `wac` result")
1051 }
1052
1053 fn run_command(&self, cmd: &mut Command) -> Result<String> {
1056 if self.opts.inherit_stderr {
1057 cmd.stderr(Stdio::inherit());
1058 }
1059 let output = cmd
1060 .output()
1061 .with_context(|| format!("failed to spawn {cmd:?}"))?;
1062 if output.status.success() {
1063 return Ok(String::from_utf8_lossy(&output.stdout).into());
1064 }
1065
1066 let mut error = format!(
1067 "\
1068command execution failed
1069command: {cmd:?}
1070status: {}",
1071 output.status,
1072 );
1073
1074 if !output.stdout.is_empty() {
1075 error.push_str(&format!(
1076 "\nstdout:\n {}",
1077 String::from_utf8_lossy(&output.stdout).replace("\n", "\n ")
1078 ));
1079 }
1080 if !output.stderr.is_empty() {
1081 error.push_str(&format!(
1082 "\nstderr:\n {}",
1083 String::from_utf8_lossy(&output.stderr).replace("\n", "\n ")
1084 ));
1085 }
1086
1087 bail!("{error}")
1088 }
1089
1090 fn convert_p1_to_component(&self, p1: &Path, compile: &Compile<'_>) -> Result<()> {
1095 let mut resolve = wit_parser::Resolve::default();
1096 let (pkg, _) = resolve
1097 .push_path(&compile.component.bindgen.wit_path)
1098 .context("failed to load WIT")?;
1099 let world = resolve.select_world(&[pkg], Some(&compile.component.bindgen.world))?;
1100 let mut module = fs::read(&p1).context("failed to read wasm file")?;
1101
1102 if !has_component_type_sections(&module) {
1103 let encoded =
1104 wit_component::metadata::encode(&resolve, world, StringEncoding::UTF8, None)?;
1105 let section = wasm_encoder::CustomSection {
1106 name: Cow::Borrowed("component-type"),
1107 data: Cow::Borrowed(&encoded),
1108 };
1109 module.push(section.id());
1110 section.encode(&mut module);
1111 }
1112
1113 let wasi_adapter =
1114 wasi_preview1_component_adapter_provider::WASI_SNAPSHOT_PREVIEW1_REACTOR_ADAPTER;
1115
1116 let component = ComponentEncoder::default()
1117 .module(module.as_slice())
1118 .context("failed to load custom sections from input module")?
1119 .validate(true)
1120 .adapter("wasi_snapshot_preview1", wasi_adapter)
1121 .context("failed to load wasip1 adapter")?
1122 .encode()
1123 .context("failed to convert to a component")?;
1124 write_if_different(compile.output, component)?;
1125 Ok(())
1126 }
1127
1128 fn include_language(&self, language: &Language) -> bool {
1130 self.opts
1131 .languages
1132 .iter()
1133 .any(|l| l == language.obj().display())
1134 }
1135
1136 fn render_error(&self, result: StepResult<'_>) -> Result<(), libtest_mimic::Failed> {
1137 let err = match (result.result, result.should_fail) {
1138 (Ok(()), false) | (Err(_), true) => return Ok(()),
1139 (Err(e), false) => e,
1140 (Ok(()), true) => return Err("test should have failed, but passed".into()),
1141 };
1142
1143 let mut s = String::new();
1144 for (k, v) in result.metadata {
1145 s.push_str(&format!(" {k}: {v}\n"));
1146 }
1147 s.push_str(&format!(
1148 " error: {}",
1149 format!("{err:?}").replace("\n", "\n ")
1150 ));
1151 Err(s.into())
1152 }
1153}
1154
1155fn has_component_type_sections(wasm: &[u8]) -> bool {
1156 for payload in wasmparser::Parser::new(0).parse_all(wasm) {
1157 match payload {
1158 Ok(wasmparser::Payload::CustomSection(s)) if s.name().starts_with("component-type") => {
1159 return true;
1160 }
1161 _ => {}
1162 }
1163 }
1164 false
1165}
1166
1167struct StepResult<'a> {
1168 result: Result<()>,
1169 should_fail: bool,
1170 metadata: Vec<(&'a str, String)>,
1171}
1172
1173impl<'a> StepResult<'a> {
1174 fn new(result: Result<()>) -> StepResult<'a> {
1175 StepResult {
1176 result,
1177 should_fail: false,
1178 metadata: Vec::new(),
1179 }
1180 }
1181
1182 fn should_fail(mut self, fail: bool) -> Self {
1183 self.should_fail = fail;
1184 self
1185 }
1186
1187 fn metadata(mut self, name: &'a str, value: impl fmt::Display) -> Self {
1188 self.metadata.push((name, value.to_string()));
1189 self
1190 }
1191}
1192
1193trait LanguageMethods {
1196 fn display(&self) -> &str;
1198
1199 fn comment_prefix_for_test_config(&self) -> Option<&str>;
1205
1206 fn codegen_test_variants(&self) -> &[(&str, &[&str])] {
1215 &[]
1216 }
1217
1218 fn prepare(&self, runner: &mut Runner) -> Result<()>;
1221
1222 fn generate_bindings_prepare(
1224 &self,
1225 _runner: &Runner,
1226 _bindgen: &Bindgen,
1227 _dir: &Path,
1228 ) -> Result<()> {
1229 Ok(())
1230 }
1231
1232 fn generate_bindings(&self, runner: &Runner, bindgen: &Bindgen, dir: &Path) -> Result<()> {
1236 let name = match self.bindgen_name() {
1237 Some(name) => name,
1238 None => return Ok(()),
1239 };
1240 self.generate_bindings_prepare(runner, bindgen, dir)?;
1241 let mut cmd = Command::new(&runner.wit_bindgen);
1242 cmd.arg(name)
1243 .arg(&bindgen.wit_path)
1244 .arg("--world")
1245 .arg(format!("%{}", bindgen.world))
1246 .arg("--out-dir")
1247 .arg(dir);
1248
1249 match bindgen.wit_config.default_bindgen_args {
1250 Some(true) | None => {
1251 for arg in self.default_bindgen_args() {
1252 cmd.arg(arg);
1253 }
1254 }
1255 Some(false) => {}
1256 }
1257
1258 for arg in bindgen.args.iter() {
1259 cmd.arg(arg);
1260 }
1261
1262 runner.run_command(&mut cmd)?;
1263 Ok(())
1264 }
1265
1266 fn default_bindgen_args(&self) -> &[&str] {
1271 &[]
1272 }
1273
1274 fn default_bindgen_args_for_codegen(&self) -> &[&str] {
1277 &[]
1278 }
1279
1280 fn bindgen_name(&self) -> Option<&str> {
1287 Some(self.display())
1288 }
1289
1290 fn compile(&self, runner: &Runner, compile: &Compile) -> Result<()>;
1292
1293 fn should_fail_verify(
1296 &self,
1297 runner: &Runner,
1298 name: &str,
1299 config: &config::WitConfig,
1300 args: &[String],
1301 ) -> bool;
1302
1303 fn should_fail_compile(
1306 &self,
1307 runner: &Runner,
1308 path: &Path,
1309 config: &config::WitConfig,
1310 ) -> bool {
1311 let _ = (runner, path, config);
1312 false
1313 }
1314
1315 fn verify(&self, runner: &Runner, verify: &Verify) -> Result<()>;
1318}
1319
1320impl Language {
1321 const ALL: &[Language] = &[
1322 Language::Rust,
1323 Language::C,
1324 Language::Cpp,
1325 Language::Wat,
1326 Language::Csharp,
1327 Language::MoonBit,
1328 Language::Go,
1329 Language::D,
1330 ];
1331
1332 fn obj(&self) -> &dyn LanguageMethods {
1333 match self {
1334 Language::Rust => &rust::Rust,
1335 Language::C => &c::C,
1336 Language::Cpp => &cpp::Cpp,
1337 Language::Wat => &wat::Wat,
1338 Language::Csharp => &csharp::Csharp,
1339 Language::MoonBit => &moonbit::MoonBit,
1340 Language::Go => &go::Go,
1341 Language::D => &d::D,
1342 Language::Custom(custom) => custom,
1343 }
1344 }
1345}
1346
1347impl fmt::Display for Language {
1348 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1349 self.obj().display().fmt(f)
1350 }
1351}
1352
1353impl fmt::Display for Kind {
1354 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1355 match self {
1356 Kind::Runner => "runner".fmt(f),
1357 Kind::Test => "test".fmt(f),
1358 }
1359 }
1360}
1361
1362fn write_if_different(path: &Path, contents: impl AsRef<[u8]>) -> Result<bool> {
1365 let contents = contents.as_ref();
1366 if let Ok(prev) = fs::read(path) {
1367 if prev == contents {
1368 return Ok(false);
1369 }
1370 }
1371
1372 if let Some(parent) = path.parent() {
1373 fs::create_dir_all(parent)
1374 .with_context(|| format!("failed to create directory {parent:?}"))?;
1375 }
1376 fs::write(path, contents).with_context(|| format!("failed to write {path:?}"))?;
1377 Ok(true)
1378}
1379
1380impl Component {
1381 fn deserialize_lang_config<T>(&self) -> Result<T>
1387 where
1388 T: Default + serde::de::DeserializeOwned,
1389 {
1390 if self.lang_config.is_none() {
1393 return Ok(T::default());
1394 }
1395
1396 let config = config::parse_test_config::<config::RuntimeTestConfig<T>>(
1401 &self.contents,
1402 self.language
1403 .obj()
1404 .comment_prefix_for_test_config()
1405 .unwrap(),
1406 )?;
1407 Ok(config.lang.unwrap())
1408 }
1409}