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 custom;
18mod runner;
19mod rust;
20mod wat;
21
22#[derive(Default, Debug, Clone, Parser)]
64pub struct Opts {
65 test: Vec<PathBuf>,
67
68 #[clap(long, value_name = "PATH")]
70 artifacts: PathBuf,
71
72 #[clap(short, long, value_name = "REGEX")]
76 filter: Option<regex::Regex>,
77
78 #[clap(long, default_value = "wasmtime")]
80 runner: std::ffi::OsString,
81
82 #[clap(flatten)]
83 rust: rust::RustOpts,
84
85 #[clap(flatten)]
86 c: c::COpts,
87
88 #[clap(flatten)]
89 custom: custom::CustomOpts,
90
91 #[clap(short, long)]
97 inherit_stderr: bool,
98
99 #[clap(short, long)]
104 languages: Vec<String>,
105}
106
107impl Opts {
108 pub fn run(&self, wit_bindgen: &Path) -> Result<()> {
109 Runner {
110 opts: self,
111 rust_state: None,
112 wit_bindgen,
113 test_runner: runner::TestRunner::new(&self.runner)?,
114 }
115 .run()
116 }
117}
118
119struct Test {
121 name: String,
125
126 kind: TestKind,
127}
128
129enum TestKind {
130 Runtime(Vec<Component>),
131 Codegen(PathBuf),
132}
133
134struct Component {
136 name: String,
140
141 path: PathBuf,
143
144 kind: Kind,
146
147 language: Language,
149
150 bindgen: Bindgen,
153}
154
155#[derive(Clone)]
156struct Bindgen {
157 args: Vec<String>,
160 wit_path: PathBuf,
163 world: String,
166 wit_config: config::WitConfig,
168}
169
170#[derive(PartialEq)]
171enum Kind {
172 Runner,
173 Test,
174}
175
176#[derive(Clone, Debug, PartialEq, Eq, Hash)]
177enum Language {
178 Rust,
179 C,
180 Cpp,
181 Wat,
182 Custom(custom::Language),
183}
184
185struct Compile<'a> {
188 component: &'a Component,
189 bindings_dir: &'a Path,
190 artifacts_dir: &'a Path,
191 output: &'a Path,
192}
193
194struct Verify<'a> {
197 wit_test: &'a Path,
198 bindings_dir: &'a Path,
199 artifacts_dir: &'a Path,
200 args: &'a [String],
201 world: &'a str,
202}
203
204struct Runner<'a> {
206 opts: &'a Opts,
207 rust_state: Option<rust::State>,
208 wit_bindgen: &'a Path,
209 test_runner: runner::TestRunner,
210}
211
212impl Runner<'_> {
213 fn run(&mut self) -> Result<()> {
215 let mut tests = HashMap::new();
217 for test in self.opts.test.iter() {
218 self.discover_tests(&mut tests, test)
219 .with_context(|| format!("failed to discover tests in {test:?}"))?;
220 }
221 if tests.is_empty() {
222 bail!(
223 "no `test.wit` files found were found in {:?}",
224 self.opts.test,
225 );
226 }
227
228 self.prepare_languages(&tests)?;
229 self.run_codegen_tests(&tests)?;
230 self.run_runtime_tests(&tests)?;
231
232 println!("PASSED");
233
234 Ok(())
235 }
236
237 fn discover_tests(&self, tests: &mut HashMap<String, Test>, path: &Path) -> Result<()> {
239 if path.is_file() {
240 if path.extension().and_then(|s| s.to_str()) == Some("wit") {
241 return self.insert_test(&path, TestKind::Codegen(path.to_owned()), tests);
242 }
243
244 return Ok(());
245 }
246
247 let runtime_candidate = path.join("test.wit");
248 if runtime_candidate.is_file() {
249 let components = self
250 .load_test(&runtime_candidate, path)
251 .with_context(|| format!("failed to load test in {path:?}"))?;
252 return self.insert_test(path, TestKind::Runtime(components), tests);
253 }
254
255 let codegen_candidate = path.join("wit");
256 if codegen_candidate.is_dir() {
257 return self.insert_test(path, TestKind::Codegen(codegen_candidate), tests);
258 }
259
260 for entry in path.read_dir().context("failed to read test directory")? {
261 let entry = entry.context("failed to read test directory entry")?;
262 let path = entry.path();
263
264 self.discover_tests(tests, &path)?;
265 }
266
267 Ok(())
268 }
269
270 fn insert_test(
271 &self,
272 path: &Path,
273 kind: TestKind,
274 tests: &mut HashMap<String, Test>,
275 ) -> Result<()> {
276 let test_name = path
277 .file_name()
278 .and_then(|s| s.to_str())
279 .context("non-utf-8 filename")?;
280 let prev = tests.insert(
281 test_name.to_string(),
282 Test {
283 name: test_name.to_string(),
284 kind,
285 },
286 );
287 if prev.is_some() {
288 bail!("duplicate test name `{test_name}` found");
289 }
290 Ok(())
291 }
292
293 fn load_test(&self, wit: &Path, dir: &Path) -> Result<Vec<Component>> {
297 let mut resolve = wit_parser::Resolve::default();
298 let pkg = resolve
299 .push_file(&wit)
300 .context("failed to load `test.wit` in test directory")?;
301 let resolve = Arc::new(resolve);
302 resolve
303 .select_world(pkg, Some("runner"))
304 .context("failed to find expected `runner` world to generate bindings")?;
305 resolve
306 .select_world(pkg, Some("test"))
307 .context("failed to find expected `test` world to generate bindings")?;
308
309 let wit_contents = std::fs::read_to_string(wit)?;
310 let wit_config: config::WitConfig = config::parse_test_config(&wit_contents, "//@")
311 .context("failed to parse WIT test config")?;
312
313 let mut components = Vec::new();
314 let mut any_runner = false;
315 let mut any_test = false;
316
317 for entry in dir.read_dir().context("failed to read test directory")? {
318 let entry = entry.context("failed to read test directory entry")?;
319 let path = entry.path();
320
321 let Some(name) = path.file_name().and_then(|s| s.to_str()) else {
322 continue;
323 };
324 let kind = if name.starts_with("runner") {
325 any_runner = true;
326 Kind::Runner
327 } else if name != "test.wit" && name.starts_with("test") {
328 any_test = true;
329 Kind::Test
330 } else {
331 log::debug!("skipping file {name:?}");
332 continue;
333 };
334
335 let bindgen = Bindgen {
336 args: Vec::new(),
337 wit_config: wit_config.clone(),
338 world: kind.to_string(),
339 wit_path: wit.to_path_buf(),
340 };
341
342 let component = self
343 .parse_component(&path, kind, bindgen)
344 .with_context(|| format!("failed to parse component source file {path:?}"))?;
345 components.push(component);
346 }
347
348 if !any_runner {
349 bail!("no `runner*` test files found in test directory");
350 }
351 if !any_test {
352 bail!("no `test*` test files found in test directory");
353 }
354
355 Ok(components)
356 }
357
358 fn parse_component(&self, path: &Path, kind: Kind, mut bindgen: Bindgen) -> Result<Component> {
361 let extension = path
362 .extension()
363 .and_then(|s| s.to_str())
364 .context("non-utf-8 path extension")?;
365
366 let language = match extension {
367 "rs" => Language::Rust,
368 "c" => Language::C,
369 "cpp" => Language::Cpp,
370 "wat" => Language::Wat,
371 other => Language::Custom(custom::Language::lookup(self, other)?),
372 };
373
374 let contents = fs::read_to_string(&path)?;
375 let config = match language.obj().comment_prefix_for_test_config() {
376 Some(comment) => {
377 config::parse_test_config::<config::RuntimeTestConfig>(&contents, comment)?
378 }
379 None => Default::default(),
380 };
381 assert!(bindgen.args.is_empty());
382 bindgen.args = config.args.into();
383
384 Ok(Component {
385 name: path.file_stem().unwrap().to_str().unwrap().to_string(),
386 path: path.to_path_buf(),
387 language,
388 bindgen,
389 kind,
390 })
391 }
392
393 fn prepare_languages(&mut self, tests: &HashMap<String, Test>) -> Result<()> {
396 let all_languages = self.all_languages();
397
398 let mut prepared = HashSet::new();
399 let mut prepare = |lang: &Language| -> Result<()> {
400 if !self.include_language(lang) || !prepared.insert(lang.clone()) {
401 return Ok(());
402 }
403 lang.obj()
404 .prepare(self)
405 .with_context(|| format!("failed to prepare language {lang}"))
406 };
407
408 for test in tests.values() {
409 match &test.kind {
410 TestKind::Runtime(c) => {
411 for component in c {
412 prepare(&component.language)?
413 }
414 }
415 TestKind::Codegen(_) => {
416 for lang in all_languages.iter() {
417 prepare(lang)?;
418 }
419 }
420 }
421 }
422
423 Ok(())
424 }
425
426 fn all_languages(&self) -> Vec<Language> {
427 let mut languages = Language::ALL.to_vec();
428 for (ext, _) in self.opts.custom.custom.iter() {
429 languages.push(Language::Custom(
430 custom::Language::lookup(self, ext).unwrap(),
431 ));
432 }
433 languages
434 }
435
436 fn run_codegen_tests(&mut self, tests: &HashMap<String, Test>) -> Result<()> {
438 let mut codegen_tests = Vec::new();
439 let languages = self.all_languages();
440 for (name, test) in tests.iter().filter_map(|(name, t)| match &t.kind {
441 TestKind::Runtime(_) => None,
442 TestKind::Codegen(p) => Some((name, p)),
443 }) {
444 let config = match fs::read_to_string(test) {
445 Ok(wit) => config::parse_test_config::<config::WitConfig>(&wit, "//@")
446 .with_context(|| format!("failed to parse test config from {test:?}"))?,
447 Err(_) => Default::default(),
448 };
449 for language in languages.iter() {
450 if *language == Language::Cpp {
453 continue;
454 }
455
456 if !self.include_language(&language) {
459 continue;
460 }
461
462 codegen_tests.push((
463 language.clone(),
464 test,
465 name.to_string(),
466 Vec::new(),
467 config.clone(),
468 ));
469
470 for (args_kind, args) in language.obj().codegen_test_variants() {
471 codegen_tests.push((
472 language.clone(),
473 test,
474 format!("{name}-{args_kind}"),
475 args.iter().map(|s| s.to_string()).collect::<Vec<_>>(),
476 config.clone(),
477 ));
478 }
479 }
480 }
481
482 if codegen_tests.is_empty() {
483 return Ok(());
484 }
485
486 println!("Running {} codegen tests:", codegen_tests.len());
487
488 let results = codegen_tests
489 .par_iter()
490 .map(|(language, test, args_kind, args, config)| {
491 let should_fail = language.obj().should_fail_verify(args_kind, config, args);
492 let result = self
493 .codegen_test(language, test, &args_kind, args, config)
494 .with_context(|| {
495 format!("failed to codegen test for `{language}` over {test:?}")
496 });
497 self.update_status(&result, should_fail);
498 (result, should_fail, language, test, args_kind)
499 })
500 .collect::<Vec<_>>();
501
502 println!("");
503
504 self.render_errors(results.into_iter().map(
505 |(result, should_fail, language, test, args_kind)| {
506 StepResult::new(test.to_str().unwrap(), result)
507 .should_fail(should_fail)
508 .metadata("language", language)
509 .metadata("variant", args_kind)
510 },
511 ));
512
513 Ok(())
514 }
515
516 fn codegen_test(
522 &self,
523 language: &Language,
524 test: &Path,
525 args_kind: &str,
526 args: &[String],
527 config: &config::WitConfig,
528 ) -> Result<()> {
529 let mut resolve = wit_parser::Resolve::default();
530 let (pkg, _) = resolve.push_path(test).context("failed to load WIT")?;
531 let world = resolve
532 .select_world(pkg, None)
533 .or_else(|err| resolve.select_world(pkg, Some("imports")).map_err(|_| err))
534 .context("failed to select a world for bindings generation")?;
535 let world = resolve.worlds[world].name.clone();
536
537 let artifacts_dir = std::env::current_dir()?
538 .join(&self.opts.artifacts)
539 .join("codegen")
540 .join(language.to_string())
541 .join(args_kind);
542 let bindings_dir = artifacts_dir.join("bindings");
543 let bindgen = Bindgen {
544 args: args.to_vec(),
545 wit_path: test.to_path_buf(),
546 world: world.clone(),
547 wit_config: config.clone(),
548 };
549 language
550 .obj()
551 .generate_bindings(self, &bindgen, &bindings_dir)
552 .context("failed to generate bindings")?;
553
554 language
555 .obj()
556 .verify(
557 self,
558 &Verify {
559 world: &world,
560 artifacts_dir: &artifacts_dir,
561 bindings_dir: &bindings_dir,
562 wit_test: test,
563 args: &bindgen.args,
564 },
565 )
566 .context("failed to verify generated bindings")?;
567
568 Ok(())
569 }
570
571 fn run_runtime_tests(&mut self, tests: &HashMap<String, Test>) -> Result<()> {
573 let components = tests
574 .values()
575 .filter(|t| match &self.opts.filter {
576 Some(filter) => filter.is_match(&t.name),
577 None => true,
578 })
579 .filter_map(|t| match &t.kind {
580 TestKind::Runtime(c) => Some(c.iter().map(move |c| (t, c))),
581 TestKind::Codegen(_) => None,
582 })
583 .flat_map(|i| i)
584 .filter(|(_test, component)| self.include_language(&component.language))
587 .collect::<Vec<_>>();
588
589 println!("Compiling {} components:", components.len());
590
591 let compile_results = components
594 .par_iter()
595 .map(|(test, component)| {
596 let path = self
597 .compile_component(test, component)
598 .with_context(|| format!("failed to compile component {:?}", component.path));
599 self.update_status(&path, false);
600 (test, component, path)
601 })
602 .collect::<Vec<_>>();
603 println!("");
604
605 let mut compilations = Vec::new();
606 self.render_errors(
607 compile_results
608 .into_iter()
609 .map(|(test, component, result)| match result {
610 Ok(path) => {
611 compilations.push((test, component, path));
612 StepResult::new("", Ok(()))
613 }
614 Err(e) => StepResult::new(&test.name, Err(e))
615 .metadata("component", &component.name)
616 .metadata("path", component.path.display()),
617 }),
618 );
619
620 let mut compiled_components = HashMap::new();
625 for (test, component, path) in compilations {
626 let list = compiled_components.entry(&test.name).or_insert(Vec::new());
627 list.push((component, path));
628 }
629
630 let mut to_run = Vec::new();
631 for (test, components) in compiled_components.iter() {
632 for a in components.iter().filter(|(c, _)| c.kind == Kind::Runner) {
633 for b in components.iter().filter(|(c, _)| c.kind == Kind::Test) {
634 to_run.push((test, a, b));
635 }
636 }
637 }
638
639 println!("Running {} runtime tests:", to_run.len());
640
641 let results = to_run
642 .par_iter()
643 .map(|(case_name, (runner, runner_path), (test, test_path))| {
644 let case = &tests[case_name.as_str()];
645 let result = self
646 .runtime_test(case, runner, runner_path, test, test_path)
647 .with_context(|| {
648 format!(
649 "failed to run `{}` with runner `{}` and test `{}`",
650 case.name, runner.language, test.language,
651 )
652 });
653 self.update_status(&result, false);
654 (result, case_name, runner, runner_path, test, test_path)
655 })
656 .collect::<Vec<_>>();
657
658 println!("");
659
660 self.render_errors(results.into_iter().map(
661 |(result, case_name, runner, runner_path, test, test_path)| {
662 StepResult::new(case_name, result)
663 .metadata("runner", runner.path.display())
664 .metadata("test", test.path.display())
665 .metadata("compiled runner", runner_path.display())
666 .metadata("compiled test", test_path.display())
667 },
668 ));
669
670 Ok(())
671 }
672
673 fn compile_component(&self, test: &Test, component: &Component) -> Result<PathBuf> {
678 let root_dir = std::env::current_dir()?
679 .join(&self.opts.artifacts)
680 .join(&test.name);
681 let artifacts_dir = root_dir.join(format!("{}-{}", component.name, component.language));
682 let bindings_dir = artifacts_dir.join("bindings");
683 let output = root_dir.join(format!("{}-{}.wasm", component.name, component.language));
684 component
685 .language
686 .obj()
687 .generate_bindings(self, &component.bindgen, &bindings_dir)?;
688 let result = Compile {
689 component,
690 bindings_dir: &bindings_dir,
691 artifacts_dir: &artifacts_dir,
692 output: &output,
693 };
694 component.language.obj().compile(self, &result)?;
695
696 let wasm = fs::read(&output)
698 .with_context(|| format!("failed to read output wasm file {output:?}"))?;
699 if !wasmparser::Parser::is_component(&wasm) {
700 bail!("output file {output:?} is not a component");
701 }
702 wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all())
703 .validate_all(&wasm)
704 .with_context(|| format!("compiler produced invalid wasm file {output:?}"))?;
705
706 Ok(output)
707 }
708
709 fn runtime_test(
714 &self,
715 case: &Test,
716 runner: &Component,
717 runner_wasm: &Path,
718 test: &Component,
719 test_wasm: &Path,
720 ) -> Result<()> {
721 let mut config = wasm_compose::config::Config::default();
722 config.definitions = vec![test_wasm.to_path_buf()];
723 let composed = wasm_compose::composer::ComponentComposer::new(runner_wasm, &config)
724 .compose()
725 .with_context(|| format!("failed to compose {runner_wasm:?} with {test_wasm:?}"))?;
726 let dst = runner_wasm.parent().unwrap();
727 let composed_wasm = dst.join(format!(
728 "{}-composed-{}-{}-{}-{}.wasm",
729 case.name, runner.name, runner.language, test.name, test.language
730 ));
731 write_if_different(&composed_wasm, &composed)?;
732
733 self.run_command(self.test_runner.command().arg(&composed_wasm))?;
734 Ok(())
735 }
736
737 fn run_command(&self, cmd: &mut Command) -> Result<()> {
740 if self.opts.inherit_stderr {
741 cmd.stderr(Stdio::inherit());
742 }
743 let output = cmd
744 .output()
745 .with_context(|| format!("failed to spawn {cmd:?}"))?;
746 if output.status.success() {
747 return Ok(());
748 }
749
750 let mut error = format!(
751 "\
752command execution failed
753command: {cmd:?}
754status: {}",
755 output.status,
756 );
757
758 if !output.stdout.is_empty() {
759 error.push_str(&format!(
760 "\nstdout:\n {}",
761 String::from_utf8_lossy(&output.stdout).replace("\n", "\n ")
762 ));
763 }
764 if !output.stderr.is_empty() {
765 error.push_str(&format!(
766 "\nstderr:\n {}",
767 String::from_utf8_lossy(&output.stderr).replace("\n", "\n ")
768 ));
769 }
770
771 bail!("{error}")
772 }
773
774 fn convert_p1_to_component(&self, p1: &Path, compile: &Compile<'_>) -> Result<()> {
779 let mut resolve = wit_parser::Resolve::default();
780 let (pkg, _) = resolve.push_path(&compile.component.bindgen.wit_path)?;
781 let world = resolve.select_world(pkg, Some(&compile.component.kind.to_string()))?;
782 let mut module = fs::read(&p1).context("failed to read wasm file")?;
783 let encoded = wit_component::metadata::encode(&resolve, world, StringEncoding::UTF8, None)?;
784
785 let section = wasm_encoder::CustomSection {
786 name: Cow::Borrowed("component-type"),
787 data: Cow::Borrowed(&encoded),
788 };
789 module.push(section.id());
790 section.encode(&mut module);
791
792 let wasi_adapter = match compile.component.kind {
793 Kind::Runner => {
794 wasi_preview1_component_adapter_provider::WASI_SNAPSHOT_PREVIEW1_COMMAND_ADAPTER
795 }
796 Kind::Test => {
797 wasi_preview1_component_adapter_provider::WASI_SNAPSHOT_PREVIEW1_REACTOR_ADAPTER
798 }
799 };
800
801 let component = ComponentEncoder::default()
802 .module(module.as_slice())
803 .context("failed to load custom sections from input module")?
804 .validate(true)
805 .adapter("wasi_snapshot_preview1", wasi_adapter)
806 .context("failed to load wasip1 adapter")?
807 .encode()
808 .context("failed to convert to a component")?;
809 write_if_different(compile.output, component)?;
810 Ok(())
811 }
812
813 fn update_status<T>(&self, result: &Result<T>, should_fail: bool) {
815 if result.is_ok() == !should_fail {
816 print!(".");
817 } else {
818 print!("F");
819 }
820 let _ = std::io::stdout().flush();
821 }
822
823 fn include_language(&self, language: &Language) -> bool {
825 let lang = language.obj().display();
826 let mut any_positive = false;
827 let mut any_negative = false;
828 for opt in self.opts.languages.iter() {
829 for name in opt.split(',') {
830 if let Some(suffix) = name.strip_prefix('-') {
831 any_negative = true;
832 if suffix == lang {
835 return false;
836 }
837 } else {
838 any_positive = true;
839 if name == lang {
841 return true;
842 }
843 }
844 }
845 }
846
847 if self.opts.languages.is_empty() {
849 return true;
850 }
851
852 if any_positive {
855 return false;
856 }
857
858 assert!(any_negative);
861 true
862 }
863
864 fn render_errors<'a>(&self, results: impl Iterator<Item = StepResult<'a>>) {
865 let mut failures = 0;
866 for result in results {
867 let err = match (result.result, result.should_fail) {
868 (Ok(()), false) | (Err(_), true) => continue,
869 (Err(e), false) => e,
870 (Ok(()), true) => anyhow!("test should have failed, but passed"),
871 };
872 failures += 1;
873
874 println!("------ Failure: {} --------", result.name);
875 for (k, v) in result.metadata {
876 println!(" {k}: {v}");
877 }
878 println!(" error: {}", format!("{err:?}").replace("\n", "\n "));
879 }
880
881 if failures > 0 {
882 println!("{failures} tests FAILED");
883 std::process::exit(1);
884 }
885 }
886}
887
888struct StepResult<'a> {
889 result: Result<()>,
890 should_fail: bool,
891 name: &'a str,
892 metadata: Vec<(&'a str, String)>,
893}
894
895impl<'a> StepResult<'a> {
896 fn new(name: &'a str, result: Result<()>) -> StepResult<'a> {
897 StepResult {
898 name,
899 result,
900 should_fail: false,
901 metadata: Vec::new(),
902 }
903 }
904
905 fn should_fail(mut self, fail: bool) -> Self {
906 self.should_fail = fail;
907 self
908 }
909
910 fn metadata(mut self, name: &'a str, value: impl fmt::Display) -> Self {
911 self.metadata.push((name, value.to_string()));
912 self
913 }
914}
915
916trait LanguageMethods {
919 fn display(&self) -> &str;
921
922 fn comment_prefix_for_test_config(&self) -> Option<&str>;
928
929 fn codegen_test_variants(&self) -> &[(&str, &[&str])] {
938 &[]
939 }
940
941 fn prepare(&self, runner: &mut Runner<'_>) -> Result<()>;
944
945 fn generate_bindings(&self, runner: &Runner<'_>, bindgen: &Bindgen, dir: &Path) -> Result<()> {
949 let name = match self.bindgen_name() {
950 Some(name) => name,
951 None => return Ok(()),
952 };
953 let mut cmd = Command::new(runner.wit_bindgen);
954 cmd.arg(name)
955 .arg(&bindgen.wit_path)
956 .arg("--world")
957 .arg(format!("%{}", bindgen.world))
958 .arg("--out-dir")
959 .arg(dir);
960
961 match bindgen.wit_config.default_bindgen_args {
962 Some(true) | None => {
963 for arg in self.default_bindgen_args() {
964 cmd.arg(arg);
965 }
966 }
967 Some(false) => {}
968 }
969
970 for arg in bindgen.args.iter() {
971 cmd.arg(arg);
972 }
973
974 runner.run_command(&mut cmd)
975 }
976
977 fn default_bindgen_args(&self) -> &[&str] {
982 &[]
983 }
984
985 fn bindgen_name(&self) -> Option<&str> {
992 Some(self.display())
993 }
994
995 fn compile(&self, runner: &Runner<'_>, compile: &Compile) -> Result<()>;
997
998 fn should_fail_verify(&self, name: &str, config: &config::WitConfig, args: &[String]) -> bool;
1001
1002 fn verify(&self, runner: &Runner<'_>, verify: &Verify) -> Result<()>;
1005}
1006
1007impl Language {
1008 const ALL: &[Language] = &[Language::Rust, Language::C, Language::Cpp, Language::Wat];
1009
1010 fn obj(&self) -> &dyn LanguageMethods {
1011 match self {
1012 Language::Rust => &rust::Rust,
1013 Language::C => &c::C,
1014 Language::Cpp => &c::Cpp,
1015 Language::Wat => &wat::Wat,
1016 Language::Custom(custom) => custom,
1017 }
1018 }
1019}
1020
1021impl fmt::Display for Language {
1022 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1023 self.obj().display().fmt(f)
1024 }
1025}
1026
1027impl fmt::Display for Kind {
1028 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1029 match self {
1030 Kind::Runner => "runner".fmt(f),
1031 Kind::Test => "test".fmt(f),
1032 }
1033 }
1034}
1035
1036fn write_if_different(path: &Path, contents: impl AsRef<[u8]>) -> Result<bool> {
1039 let contents = contents.as_ref();
1040 if let Ok(prev) = fs::read(path) {
1041 if prev == contents {
1042 return Ok(false);
1043 }
1044 }
1045
1046 if let Some(parent) = path.parent() {
1047 fs::create_dir_all(parent)
1048 .with_context(|| format!("failed to create directory {parent:?}"))?;
1049 }
1050 fs::write(path, contents).with_context(|| format!("failed to write {path:?}"))?;
1051 Ok(true)
1052}