wit_bindgen_test/
lib.rs

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