wit_bindgen_test/
lib.rs

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