1use anyhow::{anyhow, bail, Context, Result};
2use clap::Parser;
3use rayon::prelude::*;
4use std::borrow::Cow;
5use std::collections::{HashMap, HashSet};
6use std::fmt;
7use std::fs;
8use std::io::Write;
9use std::path::{Path, PathBuf};
10use std::process::{Command, Stdio};
11use std::sync::Arc;
12use wasm_encoder::{Encode, Section};
13use wit_component::{ComponentEncoder, StringEncoding};
14
15mod c;
16mod config;
17mod csharp;
18mod custom;
19mod moonbit;
20mod runner;
21mod rust;
22mod wat;
23
24#[derive(Default, Debug, Clone, Parser)]
66pub struct Opts {
67 test: Vec<PathBuf>,
69
70 #[clap(long, value_name = "PATH")]
72 artifacts: PathBuf,
73
74 #[clap(short, long, value_name = "REGEX")]
78 filter: Option<regex::Regex>,
79
80 #[clap(long, default_value = "wasmtime")]
82 runner: std::ffi::OsString,
83
84 #[clap(flatten)]
85 rust: rust::RustOpts,
86
87 #[clap(flatten)]
88 c: c::COpts,
89
90 #[clap(flatten)]
91 custom: custom::CustomOpts,
92
93 #[clap(short, long)]
99 inherit_stderr: bool,
100
101 #[clap(short, long, required = true, value_delimiter = ',')]
105 languages: Vec<String>,
106}
107
108impl Opts {
109 pub fn run(&self, wit_bindgen: &Path) -> Result<()> {
110 Runner {
111 opts: self,
112 rust_state: None,
113 wit_bindgen,
114 test_runner: runner::TestRunner::new(&self.runner)?,
115 }
116 .run()
117 }
118}
119
120struct Test {
122 name: String,
126
127 path: PathBuf,
129
130 config: config::WitConfig,
132
133 kind: TestKind,
134}
135
136enum TestKind {
137 Runtime(Vec<Component>),
138 Codegen(PathBuf),
139}
140
141struct Component {
143 name: String,
147
148 path: PathBuf,
150
151 kind: Kind,
153
154 language: Language,
156
157 bindgen: Bindgen,
160
161 contents: String,
163
164 lang_config: Option<HashMap<String, toml::Value>>,
166}
167
168#[derive(Clone)]
169struct Bindgen {
170 args: Vec<String>,
173 wit_path: PathBuf,
176 world: String,
179 wit_config: config::WitConfig,
181}
182
183#[derive(Debug, PartialEq, Copy, Clone)]
184enum Kind {
185 Runner,
186 Test,
187}
188
189#[derive(Clone, Debug, PartialEq, Eq, Hash)]
190enum Language {
191 Rust,
192 C,
193 Cpp,
194 Wat,
195 Csharp,
196 MoonBit,
197 Custom(custom::Language),
198}
199
200struct Compile<'a> {
203 component: &'a Component,
204 bindings_dir: &'a Path,
205 artifacts_dir: &'a Path,
206 output: &'a Path,
207}
208
209struct Verify<'a> {
212 wit_test: &'a Path,
213 bindings_dir: &'a Path,
214 artifacts_dir: &'a Path,
215 args: &'a [String],
216 world: &'a str,
217}
218
219struct Runner<'a> {
221 opts: &'a Opts,
222 rust_state: Option<rust::State>,
223 wit_bindgen: &'a Path,
224 test_runner: runner::TestRunner,
225}
226
227impl Runner<'_> {
228 fn run(&mut self) -> Result<()> {
230 let mut tests = HashMap::new();
232 for test in self.opts.test.iter() {
233 self.discover_tests(&mut tests, test)
234 .with_context(|| format!("failed to discover tests in {test:?}"))?;
235 }
236 if tests.is_empty() {
237 bail!(
238 "no `test.wit` files found were found in {:?}",
239 self.opts.test,
240 );
241 }
242
243 self.prepare_languages(&tests)?;
244 self.run_codegen_tests(&tests)?;
245 self.run_runtime_tests(&tests)?;
246
247 println!("PASSED");
248
249 Ok(())
250 }
251
252 fn discover_tests(&self, tests: &mut HashMap<String, Test>, path: &Path) -> Result<()> {
254 if path.is_file() {
255 if path.extension().and_then(|s| s.to_str()) == Some("wit") {
256 let config =
257 fs::read_to_string(path).with_context(|| format!("failed to read {path:?}"))?;
258 let config = config::parse_test_config::<config::WitConfig>(&config, "//@")
259 .with_context(|| format!("failed to parse test config from {path:?}"))?;
260 return self.insert_test(&path, config, TestKind::Codegen(path.to_owned()), tests);
261 }
262
263 return Ok(());
264 }
265
266 let runtime_candidate = path.join("test.wit");
267 if runtime_candidate.is_file() {
268 let (config, components) = self
269 .load_runtime_test(&runtime_candidate, path)
270 .with_context(|| format!("failed to load test in {path:?}"))?;
271 return self.insert_test(path, config, TestKind::Runtime(components), tests);
272 }
273
274 let codegen_candidate = path.join("wit");
275 if codegen_candidate.is_dir() {
276 return self.insert_test(
277 path,
278 Default::default(),
279 TestKind::Codegen(codegen_candidate),
280 tests,
281 );
282 }
283
284 for entry in path.read_dir().context("failed to read test directory")? {
285 let entry = entry.context("failed to read test directory entry")?;
286 let path = entry.path();
287
288 self.discover_tests(tests, &path)?;
289 }
290
291 Ok(())
292 }
293
294 fn insert_test(
295 &self,
296 path: &Path,
297 config: config::WitConfig,
298 kind: TestKind,
299 tests: &mut HashMap<String, Test>,
300 ) -> Result<()> {
301 let test_name = path
302 .file_name()
303 .and_then(|s| s.to_str())
304 .context("non-utf-8 filename")?;
305 let prev = tests.insert(
306 test_name.to_string(),
307 Test {
308 name: test_name.to_string(),
309 path: path.to_path_buf(),
310 config,
311 kind,
312 },
313 );
314 if prev.is_some() {
315 bail!("duplicate test name `{test_name}` found");
316 }
317 Ok(())
318 }
319
320 fn load_runtime_test(
324 &self,
325 wit: &Path,
326 dir: &Path,
327 ) -> Result<(config::WitConfig, Vec<Component>)> {
328 let mut resolve = wit_parser::Resolve::default();
329
330 let wit_path = if dir.join("deps").exists() { dir } else { wit };
331 let (pkg, _files) = resolve.push_path(wit_path).context(format!(
332 "failed to load `test.wit` in test directory: {:?}",
333 &wit
334 ))?;
335 let resolve = Arc::new(resolve);
336
337 let wit_contents = std::fs::read_to_string(wit)?;
338 let wit_config: config::WitConfig = config::parse_test_config(&wit_contents, "//@")
339 .context("failed to parse WIT test config")?;
340
341 let mut worlds = Vec::new();
342
343 let mut push_world = |kind: Kind, name: &str| -> Result<()> {
344 let world = resolve.select_world(pkg, Some(name)).with_context(|| {
345 format!("failed to find expected `{name}` world to generate bindings")
346 })?;
347 worlds.push((world, kind));
348 Ok(())
349 };
350 push_world(Kind::Runner, wit_config.runner_world())?;
351 for world in wit_config.dependency_worlds() {
352 push_world(Kind::Test, &world)?;
353 }
354
355 let mut components = Vec::new();
356 let mut any_runner = false;
357 let mut any_test = false;
358
359 for entry in dir.read_dir().context("failed to read test directory")? {
360 let entry = entry.context("failed to read test directory entry")?;
361 let path = entry.path();
362
363 let Some(name) = path.file_name().and_then(|s| s.to_str()) else {
364 continue;
365 };
366 if name == "test.wit" {
367 continue;
368 }
369
370 let Some((world, kind)) = worlds
371 .iter()
372 .find(|(world, _kind)| name.starts_with(&resolve.worlds[*world].name))
373 else {
374 log::debug!("skipping file {name:?}");
375 continue;
376 };
377 match kind {
378 Kind::Runner => any_runner = true,
379 Kind::Test => any_test = true,
380 }
381 let bindgen = Bindgen {
382 args: Vec::new(),
383 wit_config: wit_config.clone(),
384 world: resolve.worlds[*world].name.clone(),
385 wit_path: wit_path.to_path_buf(),
386 };
387 let component = self
388 .parse_component(&path, *kind, bindgen)
389 .with_context(|| format!("failed to parse component source file {path:?}"))?;
390 components.push(component);
391 }
392
393 if !any_runner {
394 bail!("no runner files found in test directory");
395 }
396 if !any_test {
397 bail!("no test files found in test directory");
398 }
399
400 Ok((wit_config, components))
401 }
402
403 fn parse_component(&self, path: &Path, kind: Kind, mut bindgen: Bindgen) -> Result<Component> {
406 let extension = path
407 .extension()
408 .and_then(|s| s.to_str())
409 .context("non-utf-8 path extension")?;
410
411 let language = match extension {
412 "rs" => Language::Rust,
413 "c" => Language::C,
414 "cpp" => Language::Cpp,
415 "wat" => Language::Wat,
416 "cs" => Language::Csharp,
417 "mbt" => Language::MoonBit,
418 other => Language::Custom(custom::Language::lookup(self, other)?),
419 };
420
421 let contents = fs::read_to_string(&path)?;
422 let config = match language.obj().comment_prefix_for_test_config() {
423 Some(comment) => {
424 config::parse_test_config::<config::RuntimeTestConfig>(&contents, comment)?
425 }
426 None => Default::default(),
427 };
428 assert!(bindgen.args.is_empty());
429 bindgen.args = config.args.into();
430
431 Ok(Component {
432 name: path.file_stem().unwrap().to_str().unwrap().to_string(),
433 path: path.to_path_buf(),
434 language,
435 bindgen,
436 kind,
437 contents,
438 lang_config: config.lang,
439 })
440 }
441
442 fn prepare_languages(&mut self, tests: &HashMap<String, Test>) -> Result<()> {
445 let all_languages = self.all_languages();
446
447 let mut prepared = HashSet::new();
448 let mut prepare = |lang: &Language| -> Result<()> {
449 if !self.include_language(lang) || !prepared.insert(lang.clone()) {
450 return Ok(());
451 }
452 lang.obj()
453 .prepare(self)
454 .with_context(|| format!("failed to prepare language {lang}"))
455 };
456
457 for test in tests.values() {
458 match &test.kind {
459 TestKind::Runtime(c) => {
460 for component in c {
461 prepare(&component.language)?
462 }
463 }
464 TestKind::Codegen(_) => {
465 for lang in all_languages.iter() {
466 prepare(lang)?;
467 }
468 }
469 }
470 }
471
472 Ok(())
473 }
474
475 fn all_languages(&self) -> Vec<Language> {
476 let mut languages = Language::ALL.to_vec();
477 for (ext, _) in self.opts.custom.custom.iter() {
478 languages.push(Language::Custom(
479 custom::Language::lookup(self, ext).unwrap(),
480 ));
481 }
482 languages
483 }
484
485 fn run_codegen_tests(&mut self, tests: &HashMap<String, Test>) -> Result<()> {
487 let mut codegen_tests = Vec::new();
488 let languages = self.all_languages();
489 for (name, config, test) in tests.iter().filter_map(|(name, t)| match &t.kind {
490 TestKind::Runtime(_) => None,
491 TestKind::Codegen(p) => Some((name, &t.config, p)),
492 }) {
493 if let Some(filter) = &self.opts.filter {
494 if !filter.is_match(name) {
495 continue;
496 }
497 }
498 for language in languages.iter() {
499 if *language == Language::Cpp {
502 continue;
503 }
504
505 if !self.include_language(&language) {
508 continue;
509 }
510
511 let mut args = Vec::new();
512 for arg in language.obj().default_bindgen_args_for_codegen() {
513 args.push(arg.to_string());
514 }
515
516 codegen_tests.push((
517 language.clone(),
518 test,
519 name.to_string(),
520 args.clone(),
521 config.clone(),
522 ));
523
524 for (args_kind, new_args) in language.obj().codegen_test_variants() {
525 let mut args = args.clone();
526 for arg in new_args.iter() {
527 args.push(arg.to_string());
528 }
529 codegen_tests.push((
530 language.clone(),
531 test,
532 format!("{name}-{args_kind}"),
533 args,
534 config.clone(),
535 ));
536 }
537 }
538 }
539
540 if codegen_tests.is_empty() {
541 return Ok(());
542 }
543
544 println!("Running {} codegen tests:", codegen_tests.len());
545
546 let results = codegen_tests
547 .par_iter()
548 .map(|(language, test, args_kind, args, config)| {
549 let should_fail = language.obj().should_fail_verify(args_kind, config, args);
550 let result = self
551 .codegen_test(language, test, &args_kind, args, config)
552 .with_context(|| {
553 format!("failed to codegen test for `{language}` over {test:?}")
554 });
555 self.update_status(&result, should_fail);
556 (result, should_fail, language, test, args_kind)
557 })
558 .collect::<Vec<_>>();
559
560 println!("");
561
562 self.render_errors(results.into_iter().map(
563 |(result, should_fail, language, test, args_kind)| {
564 StepResult::new(test.to_str().unwrap(), result)
565 .should_fail(should_fail)
566 .metadata("language", language)
567 .metadata("variant", args_kind)
568 },
569 ));
570
571 Ok(())
572 }
573
574 fn codegen_test(
580 &self,
581 language: &Language,
582 test: &Path,
583 args_kind: &str,
584 args: &[String],
585 config: &config::WitConfig,
586 ) -> Result<()> {
587 let mut resolve = wit_parser::Resolve::default();
588 let (pkg, _) = resolve.push_path(test).context("failed to load WIT")?;
589 let world = resolve
590 .select_world(pkg, None)
591 .or_else(|err| resolve.select_world(pkg, Some("imports")).map_err(|_| err))
592 .context("failed to select a world for bindings generation")?;
593 let world = resolve.worlds[world].name.clone();
594
595 let artifacts_dir = std::env::current_dir()?
596 .join(&self.opts.artifacts)
597 .join("codegen")
598 .join(language.to_string())
599 .join(args_kind);
600 let _ = fs::remove_dir_all(&artifacts_dir);
601 let bindings_dir = artifacts_dir.join("bindings");
602 let bindgen = Bindgen {
603 args: args.to_vec(),
604 wit_path: test.to_path_buf(),
605 world: world.clone(),
606 wit_config: config.clone(),
607 };
608 language
609 .obj()
610 .generate_bindings(self, &bindgen, &bindings_dir)
611 .context("failed to generate bindings")?;
612
613 language
614 .obj()
615 .verify(
616 self,
617 &Verify {
618 world: &world,
619 artifacts_dir: &artifacts_dir,
620 bindings_dir: &bindings_dir,
621 wit_test: test,
622 args: &bindgen.args,
623 },
624 )
625 .context("failed to verify generated bindings")?;
626
627 Ok(())
628 }
629
630 fn run_runtime_tests(&mut self, tests: &HashMap<String, Test>) -> Result<()> {
632 let components = tests
633 .values()
634 .filter(|t| match &self.opts.filter {
635 Some(filter) => filter.is_match(&t.name),
636 None => true,
637 })
638 .filter_map(|t| match &t.kind {
639 TestKind::Runtime(c) => Some(c.iter().map(move |c| (t, c))),
640 TestKind::Codegen(_) => None,
641 })
642 .flat_map(|i| i)
643 .filter(|(_test, component)| self.include_language(&component.language))
646 .collect::<Vec<_>>();
647
648 println!("Compiling {} components:", components.len());
649
650 let compile_results = components
653 .par_iter()
654 .map(|(test, component)| {
655 let path = self
656 .compile_component(test, component)
657 .with_context(|| format!("failed to compile component {:?}", component.path));
658 self.update_status(&path, false);
659 (test, component, path)
660 })
661 .collect::<Vec<_>>();
662 println!("");
663
664 let mut compilations = Vec::new();
665 self.render_errors(
666 compile_results
667 .into_iter()
668 .map(|(test, component, result)| match result {
669 Ok(path) => {
670 compilations.push((test, component, path));
671 StepResult::new("", Ok(()))
672 }
673 Err(e) => StepResult::new(&test.name, Err(e))
674 .metadata("component", &component.name)
675 .metadata("path", component.path.display()),
676 }),
677 );
678
679 let mut compiled_components = HashMap::new();
684 for (test, component, path) in compilations {
685 let list = compiled_components.entry(&test.name).or_insert(Vec::new());
686 list.push((*component, path));
687 }
688
689 let mut to_run = Vec::new();
690 for (test, components) in compiled_components.iter() {
691 for a in components.iter().filter(|(c, _)| c.kind == Kind::Runner) {
692 self.push_tests(&tests[test.as_str()], components, a, &mut to_run)?;
693 }
694 }
695
696 println!("Running {} runtime tests:", to_run.len());
697
698 let results = to_run
699 .par_iter()
700 .map(|(case_name, (runner, runner_path), test_components)| {
701 let case = &tests[*case_name];
702 let result = self
703 .runtime_test(case, runner, runner_path, test_components)
704 .with_context(|| format!("failed to run `{}`", case.name));
705 self.update_status(&result, false);
706 (result, case_name, runner, runner_path, test_components)
707 })
708 .collect::<Vec<_>>();
709
710 println!("");
711
712 self.render_errors(results.into_iter().map(
713 |(result, case_name, runner, runner_path, test_components)| {
714 let mut result = StepResult::new(case_name, result)
715 .metadata("runner", runner.path.display())
716 .metadata("compiled runner", runner_path.display());
717 for (test, path) in test_components {
718 result = result
719 .metadata("test", test.path.display())
720 .metadata("compiled test", path.display());
721 }
722 result
723 },
724 ));
725
726 Ok(())
727 }
728
729 fn push_tests<'a>(
732 &self,
733 test: &'a Test,
734 components: &'a [(&'a Component, PathBuf)],
735 runner: &'a (&'a Component, PathBuf),
736 to_run: &mut Vec<(
737 &'a str,
738 (&'a Component, &'a Path),
739 Vec<(&'a Component, &'a Path)>,
740 )>,
741 ) -> Result<()> {
742 fn push<'a>(
750 worlds: &[String],
751 components: &'a [(&'a Component, PathBuf)],
752 test: &mut Vec<(&'a Component, &'a Path)>,
753 commit: &mut dyn FnMut(Vec<(&'a Component, &'a Path)>),
754 ) -> Result<()> {
755 match worlds.split_first() {
756 Some((world, rest)) => {
757 let mut any = false;
758 for (component, path) in components {
759 if component.bindgen.world == *world {
760 any = true;
761 test.push((component, path));
762 push(rest, components, test, commit)?;
763 test.pop();
764 }
765 }
766 if !any {
767 bail!("no components found for `{world}`");
768 }
769 }
770
771 None => commit(test.clone()),
773 }
774 Ok(())
775 }
776
777 push(
778 &test.config.dependency_worlds(),
779 components,
780 &mut Vec::new(),
781 &mut |test_components| {
782 to_run.push((&test.name, (runner.0, &runner.1), test_components));
783 },
784 )
785 }
786
787 fn compile_component(&self, test: &Test, component: &Component) -> Result<PathBuf> {
792 let root_dir = std::env::current_dir()?
793 .join(&self.opts.artifacts)
794 .join(&test.name);
795 let artifacts_dir = root_dir.join(format!("{}-{}", component.name, component.language));
796 let _ = fs::remove_dir_all(&artifacts_dir);
797 let bindings_dir = artifacts_dir.join("bindings");
798 let output = root_dir.join(format!("{}-{}.wasm", component.name, component.language));
799 component
800 .language
801 .obj()
802 .generate_bindings(self, &component.bindgen, &bindings_dir)?;
803 let result = Compile {
804 component,
805 bindings_dir: &bindings_dir,
806 artifacts_dir: &artifacts_dir,
807 output: &output,
808 };
809 component.language.obj().compile(self, &result)?;
810
811 let wasm = fs::read(&output)
813 .with_context(|| format!("failed to read output wasm file {output:?}"))?;
814 if !wasmparser::Parser::is_component(&wasm) {
815 bail!("output file {output:?} is not a component");
816 }
817 wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all())
818 .validate_all(&wasm)
819 .with_context(|| format!("compiler produced invalid wasm file {output:?}"))?;
820
821 Ok(output)
822 }
823
824 fn runtime_test(
829 &self,
830 case: &Test,
831 runner: &Component,
832 runner_wasm: &Path,
833 test_components: &[(&Component, &Path)],
834 ) -> Result<()> {
835 let composed = if case.config.wac.is_none() && test_components.len() == 1 {
841 self.compose_wasm_with_wasm_compose(runner_wasm, test_components)?
842 } else {
843 self.compose_wasm_with_wac(case, runner, runner_wasm, test_components)?
844 };
845
846 let dst = runner_wasm.parent().unwrap();
847 let mut filename = format!(
848 "composed-{}",
849 runner.path.file_name().unwrap().to_str().unwrap(),
850 );
851 for (test, _) in test_components {
852 filename.push_str("-");
853 filename.push_str(test.path.file_name().unwrap().to_str().unwrap());
854 }
855 filename.push_str(".wasm");
856 let composed_wasm = dst.join(filename);
857 write_if_different(&composed_wasm, &composed)?;
858
859 self.run_command(self.test_runner.command().arg(&composed_wasm))?;
860 Ok(())
861 }
862
863 fn compose_wasm_with_wasm_compose(
864 &self,
865 runner_wasm: &Path,
866 test_components: &[(&Component, &Path)],
867 ) -> Result<Vec<u8>> {
868 assert!(test_components.len() == 1);
869 let test_wasm = test_components[0].1;
870 let mut config = wasm_compose::config::Config::default();
871 config.definitions = vec![test_wasm.to_path_buf()];
872 wasm_compose::composer::ComponentComposer::new(runner_wasm, &config)
873 .compose()
874 .with_context(|| format!("failed to compose {runner_wasm:?} with {test_wasm:?}"))
875 }
876
877 fn compose_wasm_with_wac(
878 &self,
879 case: &Test,
880 runner: &Component,
881 runner_wasm: &Path,
882 test_components: &[(&Component, &Path)],
883 ) -> Result<Vec<u8>> {
884 let document = match &case.config.wac {
885 Some(path) => {
886 let wac_config = case.path.join(path);
887 fs::read_to_string(&wac_config)
888 .with_context(|| format!("failed to read {wac_config:?}"))?
889 }
890 None => {
893 let mut script = String::from("package example:composition;\n");
894 let mut args = Vec::new();
895 for (component, _path) in test_components {
896 let world = &component.bindgen.world;
897 args.push(format!("...{world}"));
898 script.push_str(&format!("let {world} = new test:{world} {{ ... }};\n"));
899 }
900 args.push("...".to_string());
901 let runner = &runner.bindgen.world;
902 script.push_str(&format!(
903 "let runner = new test:{runner} {{ {} }};\n\
904 export runner...;",
905 args.join(", ")
906 ));
907
908 script
909 }
910 };
911
912 let components_as_packages = test_components
915 .iter()
916 .map(|(component, path)| {
917 Ok((format!("test:{}", component.bindgen.world), fs::read(path)?))
918 })
919 .collect::<Result<Vec<_>>>()?;
920
921 let runner_name = format!("test:{}", runner.bindgen.world);
922 let mut packages = indexmap::IndexMap::new();
923 packages.insert(
924 wac_types::BorrowedPackageKey {
925 name: &runner_name,
926 version: None,
927 },
928 fs::read(runner_wasm)?,
929 );
930 for (name, contents) in components_as_packages.iter() {
931 packages.insert(
932 wac_types::BorrowedPackageKey {
933 name,
934 version: None,
935 },
936 contents.clone(),
937 );
938 }
939
940 let document =
942 wac_parser::Document::parse(&document).context("failed to parse wac script")?;
943 document
944 .resolve(packages)
945 .context("failed to run `wac` resolve")?
946 .encode(wac_graph::EncodeOptions {
947 define_components: true,
948 validate: false,
949 processor: None,
950 })
951 .context("failed to encode `wac` result")
952 }
953
954 fn run_command(&self, cmd: &mut Command) -> Result<()> {
957 if self.opts.inherit_stderr {
958 cmd.stderr(Stdio::inherit());
959 }
960 let output = cmd
961 .output()
962 .with_context(|| format!("failed to spawn {cmd:?}"))?;
963 if output.status.success() {
964 return Ok(());
965 }
966
967 let mut error = format!(
968 "\
969command execution failed
970command: {cmd:?}
971status: {}",
972 output.status,
973 );
974
975 if !output.stdout.is_empty() {
976 error.push_str(&format!(
977 "\nstdout:\n {}",
978 String::from_utf8_lossy(&output.stdout).replace("\n", "\n ")
979 ));
980 }
981 if !output.stderr.is_empty() {
982 error.push_str(&format!(
983 "\nstderr:\n {}",
984 String::from_utf8_lossy(&output.stderr).replace("\n", "\n ")
985 ));
986 }
987
988 bail!("{error}")
989 }
990
991 fn convert_p1_to_component(&self, p1: &Path, compile: &Compile<'_>) -> Result<()> {
996 let mut resolve = wit_parser::Resolve::default();
997 let (pkg, _) = resolve
998 .push_path(&compile.component.bindgen.wit_path)
999 .context("failed to load WIT")?;
1000 let world = resolve.select_world(pkg, Some(&compile.component.bindgen.world))?;
1001 let mut module = fs::read(&p1).context("failed to read wasm file")?;
1002 let encoded = wit_component::metadata::encode(&resolve, world, StringEncoding::UTF8, None)?;
1003
1004 let section = wasm_encoder::CustomSection {
1005 name: Cow::Borrowed("component-type"),
1006 data: Cow::Borrowed(&encoded),
1007 };
1008 module.push(section.id());
1009 section.encode(&mut module);
1010
1011 let wasi_adapter = match compile.component.kind {
1012 Kind::Runner => {
1013 wasi_preview1_component_adapter_provider::WASI_SNAPSHOT_PREVIEW1_COMMAND_ADAPTER
1014 }
1015 Kind::Test => {
1016 wasi_preview1_component_adapter_provider::WASI_SNAPSHOT_PREVIEW1_REACTOR_ADAPTER
1017 }
1018 };
1019
1020 let component = ComponentEncoder::default()
1021 .module(module.as_slice())
1022 .context("failed to load custom sections from input module")?
1023 .validate(true)
1024 .adapter("wasi_snapshot_preview1", wasi_adapter)
1025 .context("failed to load wasip1 adapter")?
1026 .encode()
1027 .context("failed to convert to a component")?;
1028 write_if_different(compile.output, component)?;
1029 Ok(())
1030 }
1031
1032 fn update_status<T>(&self, result: &Result<T>, should_fail: bool) {
1034 if result.is_ok() == !should_fail {
1035 print!(".");
1036 } else {
1037 print!("F");
1038 }
1039 let _ = std::io::stdout().flush();
1040 }
1041
1042 fn include_language(&self, language: &Language) -> bool {
1044 self.opts
1045 .languages
1046 .iter()
1047 .any(|l| l == language.obj().display())
1048 }
1049
1050 fn render_errors<'a>(&self, results: impl Iterator<Item = StepResult<'a>>) {
1051 let mut failures = 0;
1052 for result in results {
1053 let err = match (result.result, result.should_fail) {
1054 (Ok(()), false) | (Err(_), true) => continue,
1055 (Err(e), false) => e,
1056 (Ok(()), true) => anyhow!("test should have failed, but passed"),
1057 };
1058 failures += 1;
1059
1060 println!("------ Failure: {} --------", result.name);
1061 for (k, v) in result.metadata {
1062 println!(" {k}: {v}");
1063 }
1064 println!(" error: {}", format!("{err:?}").replace("\n", "\n "));
1065 }
1066
1067 if failures > 0 {
1068 println!("{failures} tests FAILED");
1069 std::process::exit(1);
1070 }
1071 }
1072}
1073
1074struct StepResult<'a> {
1075 result: Result<()>,
1076 should_fail: bool,
1077 name: &'a str,
1078 metadata: Vec<(&'a str, String)>,
1079}
1080
1081impl<'a> StepResult<'a> {
1082 fn new(name: &'a str, result: Result<()>) -> StepResult<'a> {
1083 StepResult {
1084 name,
1085 result,
1086 should_fail: false,
1087 metadata: Vec::new(),
1088 }
1089 }
1090
1091 fn should_fail(mut self, fail: bool) -> Self {
1092 self.should_fail = fail;
1093 self
1094 }
1095
1096 fn metadata(mut self, name: &'a str, value: impl fmt::Display) -> Self {
1097 self.metadata.push((name, value.to_string()));
1098 self
1099 }
1100}
1101
1102trait LanguageMethods {
1105 fn display(&self) -> &str;
1107
1108 fn comment_prefix_for_test_config(&self) -> Option<&str>;
1114
1115 fn codegen_test_variants(&self) -> &[(&str, &[&str])] {
1124 &[]
1125 }
1126
1127 fn prepare(&self, runner: &mut Runner<'_>) -> Result<()>;
1130
1131 fn generate_bindings(&self, runner: &Runner<'_>, bindgen: &Bindgen, dir: &Path) -> Result<()> {
1135 let name = match self.bindgen_name() {
1136 Some(name) => name,
1137 None => return Ok(()),
1138 };
1139 let mut cmd = Command::new(runner.wit_bindgen);
1140 cmd.arg(name)
1141 .arg(&bindgen.wit_path)
1142 .arg("--world")
1143 .arg(format!("%{}", bindgen.world))
1144 .arg("--out-dir")
1145 .arg(dir);
1146
1147 match bindgen.wit_config.default_bindgen_args {
1148 Some(true) | None => {
1149 for arg in self.default_bindgen_args() {
1150 cmd.arg(arg);
1151 }
1152 }
1153 Some(false) => {}
1154 }
1155
1156 for arg in bindgen.args.iter() {
1157 cmd.arg(arg);
1158 }
1159
1160 runner.run_command(&mut cmd)
1161 }
1162
1163 fn default_bindgen_args(&self) -> &[&str] {
1168 &[]
1169 }
1170
1171 fn default_bindgen_args_for_codegen(&self) -> &[&str] {
1174 &[]
1175 }
1176
1177 fn bindgen_name(&self) -> Option<&str> {
1184 Some(self.display())
1185 }
1186
1187 fn compile(&self, runner: &Runner<'_>, compile: &Compile) -> Result<()>;
1189
1190 fn should_fail_verify(&self, name: &str, config: &config::WitConfig, args: &[String]) -> bool;
1193
1194 fn verify(&self, runner: &Runner<'_>, verify: &Verify) -> Result<()>;
1197}
1198
1199impl Language {
1200 const ALL: &[Language] = &[
1201 Language::Rust,
1202 Language::C,
1203 Language::Cpp,
1204 Language::Wat,
1205 Language::Csharp,
1206 Language::MoonBit,
1207 ];
1208
1209 fn obj(&self) -> &dyn LanguageMethods {
1210 match self {
1211 Language::Rust => &rust::Rust,
1212 Language::C => &c::C,
1213 Language::Cpp => &c::Cpp,
1214 Language::Wat => &wat::Wat,
1215 Language::Csharp => &csharp::Csharp,
1216 Language::MoonBit => &moonbit::MoonBit,
1217 Language::Custom(custom) => custom,
1218 }
1219 }
1220}
1221
1222impl fmt::Display for Language {
1223 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1224 self.obj().display().fmt(f)
1225 }
1226}
1227
1228impl fmt::Display for Kind {
1229 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1230 match self {
1231 Kind::Runner => "runner".fmt(f),
1232 Kind::Test => "test".fmt(f),
1233 }
1234 }
1235}
1236
1237fn write_if_different(path: &Path, contents: impl AsRef<[u8]>) -> Result<bool> {
1240 let contents = contents.as_ref();
1241 if let Ok(prev) = fs::read(path) {
1242 if prev == contents {
1243 return Ok(false);
1244 }
1245 }
1246
1247 if let Some(parent) = path.parent() {
1248 fs::create_dir_all(parent)
1249 .with_context(|| format!("failed to create directory {parent:?}"))?;
1250 }
1251 fs::write(path, contents).with_context(|| format!("failed to write {path:?}"))?;
1252 Ok(true)
1253}
1254
1255impl Component {
1256 fn deserialize_lang_config<T>(&self) -> Result<T>
1262 where
1263 T: Default + serde::de::DeserializeOwned,
1264 {
1265 if self.lang_config.is_none() {
1268 return Ok(T::default());
1269 }
1270
1271 let config = config::parse_test_config::<config::RuntimeTestConfig<T>>(
1276 &self.contents,
1277 self.language
1278 .obj()
1279 .comment_prefix_for_test_config()
1280 .unwrap(),
1281 )?;
1282 Ok(config.lang.unwrap())
1283 }
1284}