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