Skip to main content

wit_bindgen_test/
lib.rs

1use anyhow::{Context, Result, bail};
2use clap::Parser;
3use libtest_mimic::Trial;
4use std::borrow::Cow;
5use std::collections::{HashMap, HashSet};
6use std::fmt;
7use std::fs;
8use std::mem;
9use std::path::{Path, PathBuf};
10use std::process::{Command, Stdio};
11use std::sync::{Arc, Mutex};
12use wasm_encoder::{Encode, Section};
13use wit_component::{ComponentEncoder, StringEncoding};
14
15mod c;
16mod config;
17mod cpp;
18mod csharp;
19mod custom;
20mod d;
21mod go;
22mod moonbit;
23mod runner;
24mod rust;
25mod wat;
26
27/// Tool to run tests that exercise the `wit-bindgen` bindings generator.
28///
29/// This tool is used to (a) generate bindings for a target language, (b)
30/// compile the bindings and source code to a wasm component, (c) compose a
31/// "runner" and a "test" component together, and (d) execute this component to
32/// ensure that it passes. This process is guided by filesystem structure which
33/// must adhere to some conventions.
34///
35/// * Tests are located in any directory that contains a `test.wit` description
36///   of the WIT being tested. The `<TEST>` argument to this command is walked
37///   recursively to find `test.wit` files.
38///
39/// * The `test.wit` file must have a `runner` world and a `test` world. The
40///   "runner" should import interfaces that are exported by "test".
41///
42/// * Adjacent to `test.wit` should be a number of `runner*.*` files. There is
43///   one runner per source language, for example `runner.rs` and `runner.c`.
44///   These are source files for the `runner` world. Source files can start with
45///   `//@ ...` comments to deserialize into `config::RuntimeTestConfig`,
46///   currently that supports:
47///
48///   ```text
49///   //@ args = ['--arguments', 'to', '--the', 'bindings', '--generator']
50///   ```
51///
52///   or
53///
54///   ```text
55///   //@ args = '--arguments to --the bindings --generator'
56///   ```
57///
58/// * Adjacent to `test.wit` should also be a number of `test*.*` files. Like
59///   runners there is one per source language. Note that you can have multiple
60///   implementations of tests in the same language too, for example
61///   `test-foo.rs` and `test-bar.rs`. All tests must export the same `test`
62///   world from `test.wit`, however.
63///
64/// This tool will discover `test.wit` files, discover runners/tests, and then
65/// compile everything and run the combinatorial matrix of runners against
66/// tests. It's expected that each `runner.*` and `test.*` perform the same
67/// functionality and only differ in source language.
68#[derive(Default, Debug, Clone, Parser)]
69pub struct Opts {
70    /// Directory containing the test being run or all tests being run.
71    test: Vec<PathBuf>,
72
73    /// Path to where binary artifacts for tests are stored.
74    #[clap(long, value_name = "PATH")]
75    artifacts: PathBuf,
76
77    /// Optional filter to use on test names to only run some tests.
78    ///
79    /// This is a regular expression defined by the `regex` Rust crate.
80    #[clap(short, long, value_name = "REGEX")]
81    filter: Option<regex::Regex>,
82
83    /// The executable or script used to execute a fully composed test case.
84    #[clap(long, default_value = "wasmtime")]
85    runner: std::ffi::OsString,
86
87    #[clap(flatten)]
88    rust: rust::RustOpts,
89
90    #[clap(flatten)]
91    c: c::COpts,
92
93    #[clap(flatten)]
94    custom: custom::CustomOpts,
95
96    /// Whether or not the calling process's stderr is inherited into child
97    /// processes.
98    ///
99    /// This helps preserving color in compiler error messages but can also
100    /// jumble up output if there are multiple errors.
101    #[clap(short, long)]
102    inherit_stderr: bool,
103
104    /// Configuration of which languages are tested.
105    ///
106    /// Passing `--lang rust` will only test Rust for example.
107    #[clap(short, long, required = true, value_delimiter = ',')]
108    languages: Vec<String>,
109
110    /// Less output per test
111    #[clap(short, long, conflicts_with = "format")]
112    quiet: bool,
113
114    /// Number of threads used for parallel testing.
115    #[clap(long, value_name = "N")]
116    test_threads: Option<usize>,
117
118    /// Only run tests with this exact name.
119    #[clap(long)]
120    exact: bool,
121
122    /// A list of filters. Tests whose names contain parts of any of these
123    /// filters are skipped.
124    #[clap(long, value_name = "FILTER")]
125    skip: Vec<String>,
126
127    /// Specifies whether or not to color the output.
128    #[clap(long, value_name = "auto|always|never")]
129    color: Option<libtest_mimic::ColorSetting>,
130
131    /// Specifies the format of the output.
132    #[clap(long, value_name = "pretty|terse")]
133    format: Option<libtest_mimic::FormatSetting>,
134}
135
136impl Opts {
137    pub fn run(&self, wit_bindgen: &Path) -> Result<()> {
138        Runner {
139            opts: self.clone(),
140            rust_state: None,
141            go_state: None,
142            wit_bindgen: wit_bindgen.to_path_buf(),
143            test_runner: runner::TestRunner::new(&self.runner)?,
144        }
145        .run()
146    }
147}
148
149/// Helper structure representing a discovered `test.wit` file.
150#[derive(Clone)]
151struct Test {
152    /// The name of this test, unique amongst all tests.
153    ///
154    /// Inferred from the directory name.
155    name: String,
156
157    /// Path to the root of this test.
158    path: PathBuf,
159
160    /// Configuration for this test, specified in the WIT file.
161    config: config::WitConfig,
162
163    kind: TestKind,
164}
165
166#[derive(Clone)]
167enum TestKind {
168    Runtime(Vec<Component>),
169    Codegen(PathBuf),
170}
171
172/// Helper structure representing a single component found in a test directory.
173#[derive(Clone)]
174struct Component {
175    /// The name of this component, inferred from the file stem.
176    ///
177    /// May be shared across different languages.
178    name: String,
179
180    /// The path to the source file for this component.
181    path: PathBuf,
182
183    /// Whether or not this component is a "runner" or a "test"
184    kind: Kind,
185
186    /// The detected language for this component.
187    language: Language,
188
189    /// The WIT world that's being used with this component, loaded from
190    /// `test.wit`.
191    bindgen: Bindgen,
192
193    /// The contents of the test file itself.
194    contents: String,
195
196    /// The contents of the test file itself.
197    lang_config: Option<HashMap<String, toml::Value>>,
198
199    /// Runtime flags to wasmtime.
200    wasmtime_flags: config::StringList,
201}
202
203#[derive(Clone)]
204struct Bindgen {
205    /// The arguments to the bindings generator that this component will be
206    /// using.
207    args: Vec<String>,
208    /// The path to the `*.wit` file or files that are having bindings
209    /// generated.
210    wit_path: PathBuf,
211    /// The name of the world within `wit_path` that's having bindings generated
212    /// for it.
213    world: String,
214    /// Configuration found in `wit_path`
215    wit_config: config::WitConfig,
216}
217
218#[derive(Debug, PartialEq, Copy, Clone)]
219enum Kind {
220    Runner,
221    Test,
222}
223
224#[derive(Clone, Debug, PartialEq, Eq, Hash)]
225enum Language {
226    Rust,
227    C,
228    Cpp,
229    Wat,
230    Csharp,
231    MoonBit,
232    Go,
233    D,
234    Custom(custom::Language),
235}
236
237/// Helper structure to package up arguments when sent to language-specific
238/// compilation backends for `LanguageMethods::compile`
239struct Compile<'a> {
240    component: &'a Component,
241    bindings_dir: &'a Path,
242    artifacts_dir: &'a Path,
243    output: &'a Path,
244}
245
246/// Helper structure to package up arguments when sent to language-specific
247/// compilation backends for `LanguageMethods::verify`
248struct Verify<'a> {
249    wit_test: &'a Path,
250    bindings_dir: &'a Path,
251    artifacts_dir: &'a Path,
252    args: &'a [String],
253    world: &'a str,
254}
255
256/// Helper structure to package up runtime state associated with executing tests.
257struct Runner {
258    opts: Opts,
259    rust_state: Option<rust::State>,
260    go_state: Option<go::State>,
261    wit_bindgen: PathBuf,
262    test_runner: runner::TestRunner,
263}
264
265impl Runner {
266    /// Executes all tests.
267    fn run(mut self) -> Result<()> {
268        // First step, discover all tests in the specified test directory.
269        let mut tests = HashMap::new();
270        for test in self.opts.test.iter() {
271            self.discover_tests(&mut tests, test)
272                .with_context(|| format!("failed to discover tests in {test:?}"))?;
273        }
274        if tests.is_empty() {
275            bail!(
276                "no `test.wit` files found were found in {:?}",
277                self.opts.test,
278            );
279        }
280
281        self.prepare_languages(&tests)?;
282        let me = Arc::new(self);
283        me.run_codegen_tests(&tests)?;
284        me.run_runtime_tests(&tests)?;
285
286        Ok(())
287    }
288
289    /// Walks over `dir`, recursively, inserting located cases into `tests`.
290    fn discover_tests(&self, tests: &mut HashMap<String, Test>, path: &Path) -> Result<()> {
291        if path.is_file() {
292            if path.extension().and_then(|s| s.to_str()) == Some("wit") {
293                let config =
294                    fs::read_to_string(path).with_context(|| format!("failed to read {path:?}"))?;
295                let config = config::parse_test_config::<config::WitConfig>(&config, "//@")
296                    .with_context(|| format!("failed to parse test config from {path:?}"))?;
297                return self.insert_test(&path, config, TestKind::Codegen(path.to_owned()), tests);
298            }
299
300            return Ok(());
301        }
302
303        let runtime_candidate = path.join("test.wit");
304        if runtime_candidate.is_file() {
305            let (config, components) = self
306                .load_runtime_test(&runtime_candidate, path)
307                .with_context(|| format!("failed to load test in {path:?}"))?;
308            return self.insert_test(path, config, TestKind::Runtime(components), tests);
309        }
310
311        let codegen_candidate = path.join("wit");
312        if codegen_candidate.is_dir() {
313            return self.insert_test(
314                path,
315                Default::default(),
316                TestKind::Codegen(codegen_candidate),
317                tests,
318            );
319        }
320
321        for entry in path.read_dir().context("failed to read test directory")? {
322            let entry = entry.context("failed to read test directory entry")?;
323            let path = entry.path();
324
325            self.discover_tests(tests, &path)?;
326        }
327
328        Ok(())
329    }
330
331    fn insert_test(
332        &self,
333        path: &Path,
334        config: config::WitConfig,
335        kind: TestKind,
336        tests: &mut HashMap<String, Test>,
337    ) -> Result<()> {
338        let test_name = path
339            .file_name()
340            .and_then(|s| s.to_str())
341            .context("non-utf-8 filename")?;
342        let prev = tests.insert(
343            test_name.to_string(),
344            Test {
345                name: test_name.to_string(),
346                path: path.to_path_buf(),
347                config,
348                kind,
349            },
350        );
351        if prev.is_some() {
352            bail!("duplicate test name `{test_name}` found");
353        }
354        Ok(())
355    }
356
357    /// Loads a test from `dir` using the `wit` file in the directory specified.
358    ///
359    /// Returns a list of components that were found within this directory.
360    fn load_runtime_test(
361        &self,
362        wit: &Path,
363        dir: &Path,
364    ) -> Result<(config::WitConfig, Vec<Component>)> {
365        let mut resolve = wit_parser::Resolve::default();
366
367        let wit_path = if dir.join("deps").exists() { dir } else { wit };
368        let (pkg, _files) = resolve.push_path(wit_path).context(format!(
369            "failed to load `test.wit` in test directory: {:?}",
370            &wit
371        ))?;
372        let resolve = Arc::new(resolve);
373
374        let wit_contents = std::fs::read_to_string(wit)?;
375        let wit_config: config::WitConfig = config::parse_test_config(&wit_contents, "//@")
376            .context("failed to parse WIT test config")?;
377
378        let mut worlds = Vec::new();
379
380        let mut push_world = |kind: Kind, name: &str| -> Result<()> {
381            let world = resolve.select_world(&[pkg], Some(name)).with_context(|| {
382                format!("failed to find expected `{name}` world to generate bindings")
383            })?;
384            worlds.push((world, kind));
385            Ok(())
386        };
387        push_world(Kind::Runner, wit_config.runner_world())?;
388        for world in wit_config.dependency_worlds() {
389            push_world(Kind::Test, &world)?;
390        }
391
392        let mut components = Vec::new();
393        let mut any_runner = false;
394        let mut any_test = false;
395
396        for entry in dir.read_dir().context("failed to read test directory")? {
397            let entry = entry.context("failed to read test directory entry")?;
398            let path = entry.path();
399
400            let Some(name) = path.file_name().and_then(|s| s.to_str()) else {
401                continue;
402            };
403            if name == "test.wit" {
404                continue;
405            }
406
407            let Some((world, kind)) = worlds
408                .iter()
409                .find(|(world, _kind)| name.starts_with(&resolve.worlds[*world].name))
410            else {
411                log::debug!("skipping file {name:?}");
412                continue;
413            };
414            match kind {
415                Kind::Runner => any_runner = true,
416                Kind::Test => any_test = true,
417            }
418            let bindgen = Bindgen {
419                args: Vec::new(),
420                wit_config: wit_config.clone(),
421                world: resolve.worlds[*world].name.clone(),
422                wit_path: wit_path.to_path_buf(),
423            };
424            let component = self
425                .parse_component(&path, *kind, bindgen)
426                .with_context(|| format!("failed to parse component source file {path:?}"))?;
427            components.push(component);
428        }
429
430        if !any_runner {
431            bail!("no runner files found in test directory");
432        }
433        if !any_test {
434            bail!("no test files found in test directory");
435        }
436
437        Ok((wit_config, components))
438    }
439
440    /// Parsers the component located at `path` and creates all information
441    /// necessary for a `Component` return value.
442    fn parse_component(&self, path: &Path, kind: Kind, mut bindgen: Bindgen) -> Result<Component> {
443        let extension = path
444            .extension()
445            .and_then(|s| s.to_str())
446            .context("non-utf-8 path extension")?;
447
448        let language = match extension {
449            "rs" => Language::Rust,
450            "c" => Language::C,
451            "cpp" => Language::Cpp,
452            "wat" => Language::Wat,
453            "cs" => Language::Csharp,
454            "mbt" => Language::MoonBit,
455            "go" => Language::Go,
456            "d" => Language::D,
457            other => Language::Custom(custom::Language::lookup(self, other)?),
458        };
459
460        let contents = fs::read_to_string(&path)?;
461        let config = match language.obj().comment_prefix_for_test_config() {
462            Some(comment) => {
463                config::parse_test_config::<config::RuntimeTestConfig>(&contents, comment)?
464            }
465            None => Default::default(),
466        };
467        assert!(bindgen.args.is_empty());
468        bindgen.args = config.args.into();
469
470        Ok(Component {
471            name: path.file_stem().unwrap().to_str().unwrap().to_string(),
472            path: path.to_path_buf(),
473            language,
474            bindgen,
475            kind,
476            contents,
477            lang_config: config.lang,
478            wasmtime_flags: config.wasmtime_flags,
479        })
480    }
481
482    /// Prepares all languages in use in `test` as part of a one-time
483    /// initialization step.
484    fn prepare_languages(&mut self, tests: &HashMap<String, Test>) -> Result<()> {
485        let all_languages = self.all_languages();
486
487        let mut prepared = HashSet::new();
488        let mut prepare = |lang: &Language| -> Result<()> {
489            if !self.include_language(lang) || !prepared.insert(lang.clone()) {
490                return Ok(());
491            }
492            lang.obj()
493                .prepare(self)
494                .with_context(|| format!("failed to prepare language {lang}"))
495        };
496
497        for test in tests.values() {
498            match &test.kind {
499                TestKind::Runtime(c) => {
500                    for component in c {
501                        prepare(&component.language)?
502                    }
503                }
504                TestKind::Codegen(_) => {
505                    for lang in all_languages.iter() {
506                        prepare(lang)?;
507                    }
508                }
509            }
510        }
511
512        Ok(())
513    }
514
515    fn all_languages(&self) -> Vec<Language> {
516        let mut languages = Language::ALL.to_vec();
517        for (ext, _) in self.opts.custom.custom.iter() {
518            languages.push(Language::Custom(
519                custom::Language::lookup(self, ext).unwrap(),
520            ));
521        }
522        languages
523    }
524
525    /// Executes all tests that are `TestKind::Codegen`.
526    fn run_codegen_tests(self: &Arc<Self>, tests: &HashMap<String, Test>) -> Result<()> {
527        let mut codegen_tests = Vec::new();
528        let languages = self.all_languages();
529        for (name, config, test) in tests.iter().filter_map(|(name, t)| match &t.kind {
530            TestKind::Runtime(_) => None,
531            TestKind::Codegen(p) => Some((name, &t.config, p)),
532        }) {
533            if let Some(filter) = &self.opts.filter {
534                if !filter.is_match(name) {
535                    continue;
536                }
537            }
538            for language in languages.iter() {
539                // If the CLI arguments filter out this language, then discard
540                // the test case.
541                if !self.include_language(&language) {
542                    continue;
543                }
544
545                let mut args = Vec::new();
546                for arg in language.obj().default_bindgen_args_for_codegen() {
547                    args.push(arg.to_string());
548                }
549
550                codegen_tests.push((
551                    language.clone(),
552                    test.to_owned(),
553                    name.to_string(),
554                    args.clone(),
555                    config.clone(),
556                ));
557
558                for (args_kind, new_args) in language.obj().codegen_test_variants() {
559                    let mut args = args.clone();
560                    for arg in new_args.iter() {
561                        args.push(arg.to_string());
562                    }
563                    codegen_tests.push((
564                        language.clone(),
565                        test.clone(),
566                        format!("{name}-{args_kind}"),
567                        args,
568                        config.clone(),
569                    ));
570                }
571            }
572        }
573
574        if codegen_tests.is_empty() {
575            return Ok(());
576        }
577
578        println!("=== Running codegen tests ===");
579        self.run_tests(
580            codegen_tests
581                .into_iter()
582                .map(|(language, test, args_kind, args, config)| {
583                    let me = self.clone();
584                    let should_fail = language
585                        .obj()
586                        .should_fail_verify(self, &args_kind, &config, &args);
587
588                    let name = format!("{language} {args_kind} {test:?}");
589                    Trial::test(&name, move || {
590                        let result = me
591                            .codegen_test(&language, &test, &args_kind, &args, &config)
592                            .with_context(|| {
593                                format!("failed to codegen test for `{language}` over {test:?}")
594                            });
595
596                        me.render_error(
597                            StepResult::new(result)
598                                .should_fail(should_fail)
599                                .metadata("language", language)
600                                .metadata("variant", args_kind),
601                        )
602                    })
603                })
604                .collect::<Vec<_>>(),
605        );
606
607        Ok(())
608    }
609
610    fn run_tests(&self, trials: Vec<Trial>) {
611        let args = libtest_mimic::Arguments {
612            skip: self.opts.skip.clone(),
613            quiet: self.opts.quiet,
614            format: self.opts.format,
615            color: self.opts.color,
616            test_threads: self.opts.test_threads,
617            exact: self.opts.exact,
618            ..Default::default()
619        };
620        libtest_mimic::run(&args, trials).exit_if_failed();
621    }
622
623    /// Runs a single codegen test.
624    ///
625    /// This will generate bindings for `test` in the `language` specified. The
626    /// test name is mangled by `args_kind` and the `args` are arguments to pass
627    /// to the bindings generator.
628    fn codegen_test(
629        &self,
630        language: &Language,
631        test: &Path,
632        args_kind: &str,
633        args: &[String],
634        config: &config::WitConfig,
635    ) -> Result<()> {
636        let mut resolve = wit_parser::Resolve::default();
637        let (pkg, _) = resolve.push_path(test).context("failed to load WIT")?;
638        let world = resolve
639            .select_world(&[pkg], None)
640            .or_else(|err| {
641                resolve
642                    .select_world(&[pkg], Some("imports"))
643                    .map_err(|_| err)
644            })
645            .context("failed to select a world for bindings generation")?;
646        let world = resolve.worlds[world].name.clone();
647
648        let artifacts_dir = std::env::current_dir()?
649            .join(&self.opts.artifacts)
650            .join("codegen")
651            .join(language.to_string())
652            .join(args_kind);
653        let _ = fs::remove_dir_all(&artifacts_dir);
654        let bindings_dir = artifacts_dir.join("bindings");
655        let bindgen = Bindgen {
656            args: args.to_vec(),
657            wit_path: test.to_path_buf(),
658            world: world.clone(),
659            wit_config: config.clone(),
660        };
661        language
662            .obj()
663            .generate_bindings(self, &bindgen, &bindings_dir)
664            .context("failed to generate bindings")?;
665
666        language
667            .obj()
668            .verify(
669                self,
670                &Verify {
671                    world: &world,
672                    artifacts_dir: &artifacts_dir,
673                    bindings_dir: &bindings_dir,
674                    wit_test: test,
675                    args: &bindgen.args,
676                },
677            )
678            .context("failed to verify generated bindings")?;
679
680        Ok(())
681    }
682
683    /// Execute all `TestKind::Runtime` tests
684    fn run_runtime_tests(self: &Arc<Self>, tests: &HashMap<String, Test>) -> Result<()> {
685        let components = tests
686            .values()
687            .filter(|t| match &self.opts.filter {
688                Some(filter) => filter.is_match(&t.name),
689                None => true,
690            })
691            .filter_map(|t| match &t.kind {
692                TestKind::Runtime(c) => Some(c.iter().map(move |c| (t, c))),
693                TestKind::Codegen(_) => None,
694            })
695            .flat_map(|i| i)
696            // Discard components that are unrelated to the languages being
697            // tested.
698            .filter(|(_test, component)| self.include_language(&component.language))
699            .collect::<Vec<_>>();
700
701        println!("=== Compiling components ===");
702        let compilations = Arc::new(Mutex::new(Vec::new()));
703        self.run_tests(
704            components
705                .into_iter()
706                .map(|(test, component)| {
707                    let me = self.clone();
708                    let compilations = compilations.clone();
709                    let test = test.clone();
710                    let component = component.clone();
711                    let should_fail = component.language.obj().should_fail_compile(
712                        self,
713                        &component.path,
714                        &component.bindgen.wit_config,
715                    );
716                    Trial::test(&component.path.display().to_string(), move || {
717                        let result = me.compile_component(&test, &component).with_context(|| {
718                            format!("failed to compile component {:?}", component.path)
719                        });
720                        match result {
721                            Ok(path) if !should_fail => {
722                                compilations.lock().unwrap().push((test, component, path));
723                                Ok(())
724                            }
725                            other => me.render_error(
726                                StepResult::new(other.map(|_| ()))
727                                    .should_fail(should_fail)
728                                    .metadata("component", &component.name)
729                                    .metadata("path", component.path.display()),
730                            ),
731                        }
732                    })
733                })
734                .collect(),
735        );
736        let compilations = mem::take(&mut *compilations.lock().unwrap());
737
738        // Next, massage the data a bit. Create a map of all tests to where
739        // their components are located. Then perform a product of runners/tests
740        // to generate a list of test cases. Finally actually execute the test
741        // cases.
742        let mut compiled_components = HashMap::new();
743        for (test, component, path) in compilations {
744            let list = compiled_components.entry(test.name).or_insert(Vec::new());
745            list.push((component, path));
746        }
747
748        let mut to_run = Vec::new();
749        for (test, components) in compiled_components.iter() {
750            for a in components.iter().filter(|(c, _)| c.kind == Kind::Runner) {
751                self.push_tests(&tests[test.as_str()], components, a, &mut to_run)
752                    .with_context(|| format!("failed to make test for `{test}`"))?;
753            }
754        }
755
756        println!("=== Running runtime tests ===");
757
758        self.run_tests(
759            to_run
760                .into_iter()
761                .map(|(case_name, (runner, runner_path), test_components)| {
762                    let me = self.clone();
763                    let mut name = format!("{case_name}");
764                    for component in [&runner]
765                        .into_iter()
766                        .chain(test_components.iter().map(|p| &p.0))
767                    {
768                        name.push_str(&format!(
769                            " | {}",
770                            component.path.file_name().unwrap().to_str().unwrap()
771                        ));
772                    }
773                    let case_name = case_name.to_string();
774                    let runner = runner.clone();
775                    let runner_path = runner_path.to_path_buf();
776                    let case = tests[case_name.as_str()].clone();
777                    Trial::test(&name, move || {
778                        let result = me
779                            .runtime_test(&case, &runner, &runner_path, &test_components)
780                            .with_context(|| format!("failed to run `{}`", case.name));
781                        me.render_error(
782                            StepResult::new(result)
783                                .metadata("runner", runner.path.display())
784                                .metadata("compiled runner", runner_path.display()),
785                        )
786                    })
787                })
788                .collect(),
789        );
790
791        Ok(())
792    }
793
794    /// For the `test` provided, and the selected `runner`, determines all
795    /// permutations of tests from `components` and pushes them on to `to_run`.
796    fn push_tests(
797        &self,
798        test: &Test,
799        components: &[(Component, PathBuf)],
800        runner: &(Component, PathBuf),
801        to_run: &mut Vec<(String, (Component, PathBuf), Vec<(Component, PathBuf)>)>,
802    ) -> Result<()> {
803        /// Recursive function which walks over `worlds`, the list of worlds
804        /// that `test` expects, one by one. For each world it finds a matching
805        /// component in `components` and then recurses for the next item in the
806        /// `worlds` list.
807        ///
808        /// Once `worlds` is empty the `test` list, a temporary vector, is
809        /// cloned and pushed into `commit`.
810        fn push(
811            worlds: &[String],
812            components: &[(Component, PathBuf)],
813            test: &mut Vec<(Component, PathBuf)>,
814            commit: &mut dyn FnMut(Vec<(Component, PathBuf)>),
815        ) -> Result<()> {
816            match worlds.split_first() {
817                Some((world, rest)) => {
818                    let mut any = false;
819                    for (component, path) in components {
820                        if component.bindgen.world == *world {
821                            any = true;
822                            test.push((component.clone(), path.clone()));
823                            push(rest, components, test, commit)?;
824                            test.pop();
825                        }
826                    }
827                    if !any {
828                        bail!("no components found for `{world}`");
829                    }
830                }
831
832                // No more `worlds`? Then `test` is our set of test components.
833                None => commit(test.clone()),
834            }
835            Ok(())
836        }
837
838        push(
839            &test.config.dependency_worlds(),
840            components,
841            &mut Vec::new(),
842            &mut |test_components| {
843                to_run.push((
844                    test.name.clone(),
845                    (runner.0.clone(), runner.1.clone()),
846                    test_components,
847                ));
848            },
849        )
850    }
851
852    /// Compiles the `component` specified to wasm for the `test` given.
853    ///
854    /// This will generate bindings for `component` and then perform
855    /// language-specific compilation to convert the files into a component.
856    fn compile_component(&self, test: &Test, component: &Component) -> Result<PathBuf> {
857        let root_dir = std::env::current_dir()?
858            .join(&self.opts.artifacts)
859            .join(&test.name);
860        let artifacts_dir = root_dir.join(format!("{}-{}", component.name, component.language));
861        let _ = fs::remove_dir_all(&artifacts_dir);
862        let bindings_dir = artifacts_dir.join("bindings");
863        let output = root_dir.join(format!("{}-{}.wasm", component.name, component.language));
864        component
865            .language
866            .obj()
867            .generate_bindings(self, &component.bindgen, &bindings_dir)?;
868        let result = Compile {
869            component,
870            bindings_dir: &bindings_dir,
871            artifacts_dir: &artifacts_dir,
872            output: &output,
873        };
874        component.language.obj().compile(self, &result)?;
875
876        // Double-check the output is indeed a component and it's indeed valid.
877        let wasm = fs::read(&output)
878            .with_context(|| format!("failed to read output wasm file {output:?}"))?;
879        if !wasmparser::Parser::is_component(&wasm) {
880            bail!("output file {output:?} is not a component");
881        }
882        wasmparser::Validator::new_with_features(wasmparser::WasmFeatures::all())
883            .validate_all(&wasm)
884            .with_context(|| {
885                format!(
886                    "compiler produced invalid wasm file {output:?} for component {}",
887                    component.name
888                )
889            })?;
890
891        Ok(output)
892    }
893
894    /// Executes a single test case.
895    ///
896    /// Composes `runner_wasm` with the components in `test_components` and then
897    /// executes it with the runner specified in CLI flags.
898    fn runtime_test(
899        &self,
900        case: &Test,
901        runner: &Component,
902        runner_wasm: &Path,
903        test_components: &[(Component, PathBuf)],
904    ) -> Result<()> {
905        // If possible use `wasm-compose` to compose the test together. This is
906        // only possible when customization isn't used though. This is also only
907        // done for async tests at this time to ensure that there's a version of
908        // composition that's done which is at the same version as wasmparser
909        // and friends.
910        let composed = if case.config.wac.is_none() {
911            self.compose_wasm_with_wasm_compose(runner_wasm, test_components)?
912        } else {
913            self.compose_wasm_with_wac(case, runner, runner_wasm, test_components)?
914        };
915
916        let dst = runner_wasm.parent().unwrap();
917        let mut filename = format!(
918            "composed-{}",
919            runner.path.file_name().unwrap().to_str().unwrap(),
920        );
921        for (test, _) in test_components {
922            filename.push_str("-");
923            filename.push_str(test.path.file_name().unwrap().to_str().unwrap());
924        }
925        filename.push_str(".wasm");
926        let composed_wasm = dst.join(filename);
927        write_if_different(&composed_wasm, &composed)?;
928
929        let mut cmd = self.test_runner.command();
930        for component in [runner]
931            .into_iter()
932            .chain(test_components.iter().map(|(c, _)| c))
933        {
934            for flag in Vec::from(component.wasmtime_flags.clone()) {
935                cmd.arg(flag);
936            }
937        }
938        cmd.arg(&composed_wasm);
939        self.run_command(&mut cmd)?;
940        Ok(())
941    }
942
943    fn compose_wasm_with_wasm_compose(
944        &self,
945        runner_wasm: &Path,
946        test_components: &[(Component, PathBuf)],
947    ) -> Result<Vec<u8>> {
948        assert!(test_components.len() > 0);
949        let mut last_bytes = None;
950        let mut path: PathBuf;
951        for (i, (_component, component_path)) in test_components.iter().enumerate() {
952            let main = match last_bytes.take() {
953                Some(bytes) => {
954                    path = runner_wasm.with_extension(&format!("composition{i}.wasm"));
955                    std::fs::write(&path, &bytes)
956                        .with_context(|| format!("failed to write temporary file {path:?}"))?;
957                    path.as_path()
958                }
959                None => runner_wasm,
960            };
961
962            let mut config = wasm_compose::config::Config::default();
963            config.definitions = vec![component_path.to_path_buf()];
964            last_bytes = Some(
965                wasm_compose::composer::ComponentComposer::new(main, &config)
966                    .compose()
967                    .with_context(|| {
968                        format!("failed to compose {main:?} with {component_path:?}")
969                    })?,
970            );
971        }
972
973        Ok(last_bytes.unwrap())
974    }
975
976    fn compose_wasm_with_wac(
977        &self,
978        case: &Test,
979        runner: &Component,
980        runner_wasm: &Path,
981        test_components: &[(Component, PathBuf)],
982    ) -> Result<Vec<u8>> {
983        let document = match &case.config.wac {
984            Some(path) => {
985                let wac_config = case.path.join(path);
986                fs::read_to_string(&wac_config)
987                    .with_context(|| format!("failed to read {wac_config:?}"))?
988            }
989            // Default wac script is to just make `test_components` available
990            // to the `runner`.
991            None => {
992                let mut script = String::from("package example:composition;\n");
993                let mut args = Vec::new();
994                for (component, _path) in test_components {
995                    let world = &component.bindgen.world;
996                    args.push(format!("...{world}"));
997                    script.push_str(&format!("let {world} = new test:{world} {{ ... }};\n"));
998                }
999                args.push("...".to_string());
1000                let runner = &runner.bindgen.world;
1001                script.push_str(&format!(
1002                    "let runner = new test:{runner} {{ {} }};\n\
1003                     export runner...;",
1004                    args.join(", ")
1005                ));
1006
1007                script
1008            }
1009        };
1010
1011        // Get allocations for `test:{world}` rooted on the stack as
1012        // `BorrowedPackageKey` below requires `&str`.
1013        let components_as_packages = test_components
1014            .iter()
1015            .map(|(component, path)| {
1016                Ok((format!("test:{}", component.bindgen.world), fs::read(path)?))
1017            })
1018            .collect::<Result<Vec<_>>>()?;
1019
1020        let runner_name = format!("test:{}", runner.bindgen.world);
1021        let mut packages = indexmap::IndexMap::new();
1022        packages.insert(
1023            wac_types::BorrowedPackageKey {
1024                name: &runner_name,
1025                version: None,
1026            },
1027            fs::read(runner_wasm)?,
1028        );
1029        for (name, contents) in components_as_packages.iter() {
1030            packages.insert(
1031                wac_types::BorrowedPackageKey {
1032                    name,
1033                    version: None,
1034                },
1035                contents.clone(),
1036            );
1037        }
1038
1039        // TODO: should figure out how to render these errors better.
1040        let document =
1041            wac_parser::Document::parse(&document).context("failed to parse wac script")?;
1042        document
1043            .resolve(packages)
1044            .context("failed to run `wac` resolve")?
1045            .encode(wac_graph::EncodeOptions {
1046                define_components: true,
1047                validate: false,
1048                processor: None,
1049            })
1050            .context("failed to encode `wac` result")
1051    }
1052
1053    /// Helper to execute an external process and generate a helpful error
1054    /// message on failure.
1055    fn run_command(&self, cmd: &mut Command) -> Result<String> {
1056        if self.opts.inherit_stderr {
1057            cmd.stderr(Stdio::inherit());
1058        }
1059        let output = cmd
1060            .output()
1061            .with_context(|| format!("failed to spawn {cmd:?}"))?;
1062        if output.status.success() {
1063            return Ok(String::from_utf8_lossy(&output.stdout).into());
1064        }
1065
1066        let mut error = format!(
1067            "\
1068command execution failed
1069command: {cmd:?}
1070status: {}",
1071            output.status,
1072        );
1073
1074        if !output.stdout.is_empty() {
1075            error.push_str(&format!(
1076                "\nstdout:\n  {}",
1077                String::from_utf8_lossy(&output.stdout).replace("\n", "\n  ")
1078            ));
1079        }
1080        if !output.stderr.is_empty() {
1081            error.push_str(&format!(
1082                "\nstderr:\n  {}",
1083                String::from_utf8_lossy(&output.stderr).replace("\n", "\n  ")
1084            ));
1085        }
1086
1087        bail!("{error}")
1088    }
1089
1090    /// Converts the WASIp1 module at `p1` to a component using the information
1091    /// stored within `compile`.
1092    ///
1093    /// Stores the output at `compile.output`.
1094    fn convert_p1_to_component(&self, p1: &Path, compile: &Compile<'_>) -> Result<()> {
1095        let mut resolve = wit_parser::Resolve::default();
1096        let (pkg, _) = resolve
1097            .push_path(&compile.component.bindgen.wit_path)
1098            .context("failed to load WIT")?;
1099        let world = resolve.select_world(&[pkg], Some(&compile.component.bindgen.world))?;
1100        let mut module = fs::read(&p1).context("failed to read wasm file")?;
1101
1102        if !has_component_type_sections(&module) {
1103            let encoded =
1104                wit_component::metadata::encode(&resolve, world, StringEncoding::UTF8, None)?;
1105            let section = wasm_encoder::CustomSection {
1106                name: Cow::Borrowed("component-type"),
1107                data: Cow::Borrowed(&encoded),
1108            };
1109            module.push(section.id());
1110            section.encode(&mut module);
1111        }
1112
1113        let wasi_adapter =
1114            wasi_preview1_component_adapter_provider::WASI_SNAPSHOT_PREVIEW1_REACTOR_ADAPTER;
1115
1116        let component = ComponentEncoder::default()
1117            .module(module.as_slice())
1118            .context("failed to load custom sections from input module")?
1119            .validate(true)
1120            .adapter("wasi_snapshot_preview1", wasi_adapter)
1121            .context("failed to load wasip1 adapter")?
1122            .encode()
1123            .context("failed to convert to a component")?;
1124        write_if_different(compile.output, component)?;
1125        Ok(())
1126    }
1127
1128    /// Returns whether `languages` is included in this testing session.
1129    fn include_language(&self, language: &Language) -> bool {
1130        self.opts
1131            .languages
1132            .iter()
1133            .any(|l| l == language.obj().display())
1134    }
1135
1136    fn render_error(&self, result: StepResult<'_>) -> Result<(), libtest_mimic::Failed> {
1137        let err = match (result.result, result.should_fail) {
1138            (Ok(()), false) | (Err(_), true) => return Ok(()),
1139            (Err(e), false) => e,
1140            (Ok(()), true) => return Err("test should have failed, but passed".into()),
1141        };
1142
1143        let mut s = String::new();
1144        for (k, v) in result.metadata {
1145            s.push_str(&format!("  {k}: {v}\n"));
1146        }
1147        s.push_str(&format!(
1148            "  error: {}",
1149            format!("{err:?}").replace("\n", "\n  ")
1150        ));
1151        Err(s.into())
1152    }
1153}
1154
1155fn has_component_type_sections(wasm: &[u8]) -> bool {
1156    for payload in wasmparser::Parser::new(0).parse_all(wasm) {
1157        match payload {
1158            Ok(wasmparser::Payload::CustomSection(s)) if s.name().starts_with("component-type") => {
1159                return true;
1160            }
1161            _ => {}
1162        }
1163    }
1164    false
1165}
1166
1167struct StepResult<'a> {
1168    result: Result<()>,
1169    should_fail: bool,
1170    metadata: Vec<(&'a str, String)>,
1171}
1172
1173impl<'a> StepResult<'a> {
1174    fn new(result: Result<()>) -> StepResult<'a> {
1175        StepResult {
1176            result,
1177            should_fail: false,
1178            metadata: Vec::new(),
1179        }
1180    }
1181
1182    fn should_fail(mut self, fail: bool) -> Self {
1183        self.should_fail = fail;
1184        self
1185    }
1186
1187    fn metadata(mut self, name: &'a str, value: impl fmt::Display) -> Self {
1188        self.metadata.push((name, value.to_string()));
1189        self
1190    }
1191}
1192
1193/// Helper trait for each language to implement which encapsulates
1194/// language-specific logic.
1195trait LanguageMethods {
1196    /// Display name for this language, used in filenames.
1197    fn display(&self) -> &str;
1198
1199    /// Returns the prefix that this language uses to annotate configuration in
1200    /// the top of source files.
1201    ///
1202    /// This should be the language's line-comment syntax followed by `@`, e.g.
1203    /// `//@` for Rust or `;;@` for WebAssembly Text.
1204    fn comment_prefix_for_test_config(&self) -> Option<&str>;
1205
1206    /// Returns the extra permutations, if any, of arguments to use with codegen
1207    /// tests.
1208    ///
1209    /// This is used to run all codegen tests with a variety of bindings
1210    /// generator options. The first element in the tuple is a descriptive
1211    /// string that should be unique (used in file names) and the second elemtn
1212    /// is the list of arguments for that variant to pass to the bindings
1213    /// generator.
1214    fn codegen_test_variants(&self) -> &[(&str, &[&str])] {
1215        &[]
1216    }
1217
1218    /// Performs any one-time preparation necessary for this language, such as
1219    /// downloading or caching dependencies.
1220    fn prepare(&self, runner: &mut Runner) -> Result<()>;
1221
1222    /// Add some files to the generated directory _before_ calling bindgen
1223    fn generate_bindings_prepare(
1224        &self,
1225        _runner: &Runner,
1226        _bindgen: &Bindgen,
1227        _dir: &Path,
1228    ) -> Result<()> {
1229        Ok(())
1230    }
1231
1232    /// Generates bindings for `component` into `dir`.
1233    ///
1234    /// Runs `wit-bindgen` in aa subprocess to catch failures such as panics.
1235    fn generate_bindings(&self, runner: &Runner, bindgen: &Bindgen, dir: &Path) -> Result<()> {
1236        let name = match self.bindgen_name() {
1237            Some(name) => name,
1238            None => return Ok(()),
1239        };
1240        self.generate_bindings_prepare(runner, bindgen, dir)?;
1241        let mut cmd = Command::new(&runner.wit_bindgen);
1242        cmd.arg(name)
1243            .arg(&bindgen.wit_path)
1244            .arg("--world")
1245            .arg(format!("%{}", bindgen.world))
1246            .arg("--out-dir")
1247            .arg(dir);
1248
1249        match bindgen.wit_config.default_bindgen_args {
1250            Some(true) | None => {
1251                for arg in self.default_bindgen_args() {
1252                    cmd.arg(arg);
1253                }
1254            }
1255            Some(false) => {}
1256        }
1257
1258        for arg in bindgen.args.iter() {
1259            cmd.arg(arg);
1260        }
1261
1262        runner.run_command(&mut cmd)?;
1263        Ok(())
1264    }
1265
1266    /// Returns the default set of arguments that will be passed to
1267    /// `wit-bindgen`.
1268    ///
1269    /// Defaults to empty, but each language can override it.
1270    fn default_bindgen_args(&self) -> &[&str] {
1271        &[]
1272    }
1273
1274    /// Same as `default_bindgen_args` but specifically applied during codegen
1275    /// tests, such as generating stub impls by default.
1276    fn default_bindgen_args_for_codegen(&self) -> &[&str] {
1277        &[]
1278    }
1279
1280    /// Returns the name of this bindings generator when passed to
1281    /// `wit-bindgen`.
1282    ///
1283    /// By default this is `Some(self.display())`, but it can be overridden if
1284    /// necessary. Returning `None` here means that no bindings generator is
1285    /// supported.
1286    fn bindgen_name(&self) -> Option<&str> {
1287        Some(self.display())
1288    }
1289
1290    /// Performs compilation as specified by `compile`.
1291    fn compile(&self, runner: &Runner, compile: &Compile) -> Result<()>;
1292
1293    /// Returns whether this language is supposed to fail this codegen tests
1294    /// given the `config` and `args` for the test.
1295    fn should_fail_verify(
1296        &self,
1297        runner: &Runner,
1298        name: &str,
1299        config: &config::WitConfig,
1300        args: &[String],
1301    ) -> bool;
1302
1303    /// Returns whether this language is expected to fail to compile the
1304    /// runtime test component described by `config`.
1305    fn should_fail_compile(
1306        &self,
1307        runner: &Runner,
1308        path: &Path,
1309        config: &config::WitConfig,
1310    ) -> bool {
1311        let _ = (runner, path, config);
1312        false
1313    }
1314
1315    /// Performs a "check" or a verify that the generated bindings described by
1316    /// `Verify` are indeed valid.
1317    fn verify(&self, runner: &Runner, verify: &Verify) -> Result<()>;
1318}
1319
1320impl Language {
1321    const ALL: &[Language] = &[
1322        Language::Rust,
1323        Language::C,
1324        Language::Cpp,
1325        Language::Wat,
1326        Language::Csharp,
1327        Language::MoonBit,
1328        Language::Go,
1329        Language::D,
1330    ];
1331
1332    fn obj(&self) -> &dyn LanguageMethods {
1333        match self {
1334            Language::Rust => &rust::Rust,
1335            Language::C => &c::C,
1336            Language::Cpp => &cpp::Cpp,
1337            Language::Wat => &wat::Wat,
1338            Language::Csharp => &csharp::Csharp,
1339            Language::MoonBit => &moonbit::MoonBit,
1340            Language::Go => &go::Go,
1341            Language::D => &d::D,
1342            Language::Custom(custom) => custom,
1343        }
1344    }
1345}
1346
1347impl fmt::Display for Language {
1348    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1349        self.obj().display().fmt(f)
1350    }
1351}
1352
1353impl fmt::Display for Kind {
1354    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1355        match self {
1356            Kind::Runner => "runner".fmt(f),
1357            Kind::Test => "test".fmt(f),
1358        }
1359    }
1360}
1361
1362/// Returns `true` if the file was written, or `false` if the file is the same
1363/// as it was already on disk.
1364fn write_if_different(path: &Path, contents: impl AsRef<[u8]>) -> Result<bool> {
1365    let contents = contents.as_ref();
1366    if let Ok(prev) = fs::read(path) {
1367        if prev == contents {
1368            return Ok(false);
1369        }
1370    }
1371
1372    if let Some(parent) = path.parent() {
1373        fs::create_dir_all(parent)
1374            .with_context(|| format!("failed to create directory {parent:?}"))?;
1375    }
1376    fs::write(path, contents).with_context(|| format!("failed to write {path:?}"))?;
1377    Ok(true)
1378}
1379
1380impl Component {
1381    /// Helper to convert `RuntimeTestConfig` to a `RuntimeTestConfig<T>` and
1382    /// then extract the `T`.
1383    ///
1384    /// This is called from within each language's implementation with a
1385    /// specific `T` necessary for that language.
1386    fn deserialize_lang_config<T>(&self) -> Result<T>
1387    where
1388        T: Default + serde::de::DeserializeOwned,
1389    {
1390        // If this test has no language-specific configuration then return this
1391        // language's default configuration.
1392        if self.lang_config.is_none() {
1393            return Ok(T::default());
1394        }
1395
1396        // Otherwise re-parse the TOML at the top of the file but this time
1397        // with the specific `T` that we're interested in. This is expected
1398        // to then produce a value in the `lang` field since
1399        // `self.lang_config.is_some()` is true.
1400        let config = config::parse_test_config::<config::RuntimeTestConfig<T>>(
1401            &self.contents,
1402            self.language
1403                .obj()
1404                .comment_prefix_for_test_config()
1405                .unwrap(),
1406        )?;
1407        Ok(config.lang.unwrap())
1408    }
1409}