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