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