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