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