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