|
1 | 1 | //! A test suite to test the `wasm-tools` CLI itself. |
2 | 2 | //! |
3 | | -//! This test suite will look for `*.wat` files in the `tests/cli/**` directory, |
4 | | -//! recursively. Each wat file must have a directive of the form: |
5 | | -//! |
6 | | -//! ;; RUN: ... |
7 | | -//! |
8 | | -//! where `...` is a space-separate set of command to pass to the `wasm-tools` |
9 | | -//! CLI. The `%` argument is replaced with the path to the current file. For |
10 | | -//! example: |
11 | | -//! |
12 | | -//! ;; RUN: dump % |
13 | | -//! |
14 | | -//! would execute `wasm-tools dump the-current-file.wat`. The `cli` directory |
15 | | -//! additionally contains `*.stdout` and `*.stderr` files to assert the output |
16 | | -//! of the subcommand. Files are not present if the stdout/stderr are empty. |
17 | | -//! |
18 | | -//! This also supports a limited form of piping along the lines of: |
19 | | -//! |
20 | | -//! ;; RUN: strip % | objdump |
21 | | -//! |
22 | | -//! where a `|` will execute the first subcommand and pipe its stdout into the |
23 | | -//! stdin of the next command. |
24 | | -//! |
25 | | -//! Use `BLESS=1` in the environment to auto-update expectation files. Be sure |
26 | | -//! to look at the diff! |
| 3 | +//! This test suite will look for `*.wat` and `*.wit` files in the |
| 4 | +//! `tests/cli/**` directory, recursively. For more information about supported |
| 5 | +//! directives and features of this test suite see the `tests/cli/readme.wat` |
| 6 | +//! file which has an explanatory comment at the top for what's going on. |
27 | 7 |
|
28 | | -use anyhow::{anyhow, bail, Context, Result}; |
| 8 | +use anyhow::{bail, Context, Result}; |
| 9 | +use indexmap::IndexMap; |
29 | 10 | use libtest_mimic::{Arguments, Trial}; |
30 | 11 | use pretty_assertions::StrComparison; |
31 | 12 | use std::env; |
@@ -59,54 +40,96 @@ fn main() { |
59 | 40 | libtest_mimic::run(&args, trials).exit(); |
60 | 41 | } |
61 | 42 |
|
62 | | -fn wasm_tools_exe() -> Command { |
63 | | - Command::new(env!("CARGO_BIN_EXE_wasm-tools")) |
64 | | -} |
65 | | - |
66 | 43 | fn run_test(test: &Path, bless: bool) -> Result<()> { |
67 | 44 | let contents = std::fs::read_to_string(test)?; |
68 | | - let (line, should_fail) = contents |
| 45 | + |
| 46 | + let mut directives = contents |
69 | 47 | .lines() |
70 | | - .filter_map(|l| { |
71 | | - let run = l.strip_prefix(";; RUN: ").or(l.strip_prefix("// RUN: ")); |
72 | | - let fail = l.strip_prefix(";; FAIL: ").or(l.strip_prefix("// FAIL: ")); |
73 | | - run.map(|l| (l, false)).or(fail.map(|l| (l, true))) |
74 | | - }) |
75 | | - .next() |
76 | | - .ok_or_else(|| anyhow!("no line found with `;; RUN: ` directive"))?; |
77 | | - |
78 | | - let mut cmd = wasm_tools_exe(); |
79 | | - let mut stdin = None; |
80 | | - let tempdir = TempDir::new()?; |
81 | | - for arg in line.split_whitespace() { |
82 | | - let arg = arg.replace("%tmpdir", tempdir.path().to_str().unwrap()); |
83 | | - if arg == "|" { |
84 | | - let output = execute(&mut cmd, stdin.as_deref(), false)?; |
85 | | - stdin = Some(output.stdout); |
86 | | - cmd = wasm_tools_exe(); |
87 | | - } else if arg == "%" { |
88 | | - cmd.arg(test); |
89 | | - } else { |
90 | | - cmd.arg(arg); |
| 48 | + .enumerate() |
| 49 | + .filter(|(_, l)| !l.is_empty()) |
| 50 | + .filter_map(|(i, l)| { |
| 51 | + l.strip_prefix("// ") |
| 52 | + .or(l.strip_prefix(";; ")) |
| 53 | + .map(|l| (i + 1, l)) |
| 54 | + }); |
| 55 | + |
| 56 | + let mut commands = IndexMap::new(); |
| 57 | + |
| 58 | + while let Some((i, line)) = directives.next() { |
| 59 | + let run = line.strip_prefix("RUN"); |
| 60 | + let fail = line.strip_prefix("FAIL"); |
| 61 | + let (directive, should_fail) = match run.map(|l| (l, false)).or(fail.map(|l| (l, true))) { |
| 62 | + Some(pair) => pair, |
| 63 | + None => continue, |
| 64 | + }; |
| 65 | + let (cmd, name) = match directive.strip_prefix("[") { |
| 66 | + Some(prefix) => match prefix.find("]:") { |
| 67 | + Some(i) => (&prefix[i + 2..], &prefix[..i]), |
| 68 | + None => bail!("line {i}: failed to find `]:` after `[`"), |
| 69 | + }, |
| 70 | + None => match directive.strip_prefix(":") { |
| 71 | + Some(cmd) => (cmd, ""), |
| 72 | + None => bail!("line {i}: failed to find `:` after `RUN` or `FAIL`"), |
| 73 | + }, |
| 74 | + }; |
| 75 | + let mut cmd = cmd.to_string(); |
| 76 | + while cmd.ends_with("\\") { |
| 77 | + cmd.pop(); |
| 78 | + match directives.next() { |
| 79 | + Some((_, line)) => cmd.push_str(line), |
| 80 | + None => bail!("line {i}: directive ends in `\\` but nothing on next line"), |
| 81 | + } |
| 82 | + } |
| 83 | + |
| 84 | + match commands.insert(name, (cmd, should_fail)) { |
| 85 | + Some(_) => bail!("line {i}: duplicate directive named {name:?}"), |
| 86 | + None => {} |
91 | 87 | } |
92 | 88 | } |
93 | 89 |
|
94 | | - let output = execute(&mut cmd, stdin.as_deref(), should_fail)?; |
95 | | - let extension = test.extension().unwrap().to_str().unwrap(); |
96 | | - assert_output( |
97 | | - bless, |
98 | | - &output.stdout, |
99 | | - &test.with_extension(&format!("{extension}.stdout")), |
100 | | - &tempdir, |
101 | | - ) |
102 | | - .context("failed to check stdout expectation (auto-update with BLESS=1)")?; |
103 | | - assert_output( |
104 | | - bless, |
105 | | - &output.stderr, |
106 | | - &test.with_extension(&format!("{extension}.stderr")), |
107 | | - &tempdir, |
108 | | - ) |
109 | | - .context("failed to check stderr expectation (auto-update with BLESS=1)")?; |
| 90 | + if commands.is_empty() { |
| 91 | + bail!("failed to find `// RUN: ...` or `// FAIL: ...` at the top of this file"); |
| 92 | + } |
| 93 | + let exe = Path::new(env!("CARGO_BIN_EXE_wasm-tools")); |
| 94 | + let tempdir = TempDir::new_in(exe.parent().unwrap())?; |
| 95 | + for (name, (line, should_fail)) in commands { |
| 96 | + let mut cmd = Command::new(exe); |
| 97 | + let mut stdin = None; |
| 98 | + for arg in line.split_whitespace() { |
| 99 | + let arg = arg.replace("%tmpdir", tempdir.path().to_str().unwrap()); |
| 100 | + if arg == "|" { |
| 101 | + let output = execute(&mut cmd, stdin.as_deref(), false)?; |
| 102 | + stdin = Some(output.stdout); |
| 103 | + cmd = Command::new(exe); |
| 104 | + } else if arg == "%" { |
| 105 | + cmd.arg(test); |
| 106 | + } else { |
| 107 | + cmd.arg(arg); |
| 108 | + } |
| 109 | + } |
| 110 | + |
| 111 | + let output = execute(&mut cmd, stdin.as_deref(), should_fail)?; |
| 112 | + let extension = test.extension().unwrap().to_str().unwrap(); |
| 113 | + let extension = if name.is_empty() { |
| 114 | + extension.to_string() |
| 115 | + } else { |
| 116 | + format!("{extension}.{name}") |
| 117 | + }; |
| 118 | + assert_output( |
| 119 | + bless, |
| 120 | + &output.stdout, |
| 121 | + &test.with_extension(&format!("{extension}.stdout")), |
| 122 | + &tempdir, |
| 123 | + ) |
| 124 | + .context("failed to check stdout expectation (auto-update with BLESS=1)")?; |
| 125 | + assert_output( |
| 126 | + bless, |
| 127 | + &output.stderr, |
| 128 | + &test.with_extension(&format!("{extension}.stderr")), |
| 129 | + &tempdir, |
| 130 | + ) |
| 131 | + .context("failed to check stderr expectation (auto-update with BLESS=1)")?; |
| 132 | + } |
110 | 133 | Ok(()) |
111 | 134 | } |
112 | 135 |
|
|
0 commit comments