/src/wasmtime/cranelift/filetests/src/test_wasm.rs
Line | Count | Source (jump to first uncovered line) |
1 | | //! Test runner for `.wat` files to exercise CLIF-to-Wasm translations. |
2 | | |
3 | | mod config; |
4 | | mod env; |
5 | | |
6 | | use anyhow::{bail, ensure, Context, Result}; |
7 | | use config::TestConfig; |
8 | | use env::ModuleEnv; |
9 | | use similar::TextDiff; |
10 | | use std::{fmt::Write, path::Path}; |
11 | | |
12 | | /// Run one `.wat` test. |
13 | 0 | pub fn run(path: &Path, wat: &str) -> Result<()> { |
14 | 0 | debug_assert_eq!(path.extension().unwrap_or_default(), "wat"); |
15 | | |
16 | | // The test config source is the leading lines of the WAT file that are |
17 | | // prefixed with `;;!`. |
18 | 0 | let config_lines: Vec<_> = wat |
19 | 0 | .lines() |
20 | 0 | .take_while(|l| l.starts_with(";;!")) |
21 | 0 | .map(|l| &l[3..]) |
22 | 0 | .collect(); |
23 | 0 | let config_text = config_lines.join("\n"); |
24 | | |
25 | 0 | let config: TestConfig = |
26 | 0 | toml::from_str(&config_text).context("failed to parse the test configuration")?; |
27 | 0 | log::debug!("Wasm test config = {config:#?}"); |
28 | | |
29 | 0 | config |
30 | 0 | .validate() |
31 | 0 | .context("test configuration is malformed")?; |
32 | | |
33 | 0 | let parsed = cranelift_reader::parse_sets_and_triple(&config.settings, &config.target) |
34 | 0 | .context("invalid ISA target or Cranelift settings")?; |
35 | 0 | let flags_or_isa = parsed.as_fisa(); |
36 | 0 | ensure!( |
37 | 0 | flags_or_isa.isa.is_some(), |
38 | 0 | "Running `.wat` tests requires specifying an ISA" |
39 | | ); |
40 | 0 | let isa = flags_or_isa.isa.unwrap(); |
41 | 0 |
|
42 | 0 | let mut env = ModuleEnv::new(isa, config.clone()); |
43 | | |
44 | 0 | let wasm = wat::parse_str(wat).context("failed to parse the test WAT")?; |
45 | 0 | let mut validator = wasmparser::Validator::new_with_features( |
46 | 0 | cranelift_wasm::ModuleEnvironment::wasm_features(&env), |
47 | 0 | ); |
48 | 0 | validator |
49 | 0 | .validate_all(&wasm) |
50 | 0 | .context("test WAT failed to validate")?; |
51 | | |
52 | 0 | cranelift_wasm::translate_module(&wasm, &mut env) |
53 | 0 | .context("failed to translate the test case into CLIF")?; |
54 | | |
55 | 0 | let mut actual = String::new(); |
56 | 0 | for (_index, func) in env.inner.info.function_bodies.iter() { |
57 | 0 | if config.compile { |
58 | 0 | let mut ctx = cranelift_codegen::Context::for_function(func.clone()); |
59 | 0 | ctx.set_disasm(true); |
60 | 0 | let code = ctx |
61 | 0 | .compile(isa, &mut Default::default()) |
62 | 0 | .map_err(|e| crate::pretty_anyhow_error(&e.func, e.inner))?; |
63 | 0 | writeln!(&mut actual, "function {}:", func.name).unwrap(); |
64 | 0 | writeln!(&mut actual, "{}", code.vcode.as_ref().unwrap()).unwrap(); |
65 | 0 | } else if config.optimize { |
66 | 0 | let mut ctx = cranelift_codegen::Context::for_function(func.clone()); |
67 | 0 | ctx.optimize(isa) |
68 | 0 | .map_err(|e| crate::pretty_anyhow_error(&ctx.func, e))?; |
69 | 0 | writeln!(&mut actual, "{}", ctx.func.display()).unwrap(); |
70 | 0 | } else { |
71 | 0 | writeln!(&mut actual, "{}", func.display()).unwrap(); |
72 | 0 | } |
73 | | } |
74 | 0 | let actual = actual.trim(); |
75 | 0 | log::debug!("=== actual ===\n{actual}"); |
76 | | |
77 | | // The test's expectation is the final comment. |
78 | 0 | let mut expected_lines: Vec<_> = wat |
79 | 0 | .lines() |
80 | 0 | .rev() |
81 | 0 | .take_while(|l| l.starts_with(";;")) |
82 | 0 | .map(|l| { |
83 | 0 | if l.starts_with(";; ") { |
84 | 0 | &l[3..] |
85 | | } else { |
86 | 0 | &l[2..] |
87 | | } |
88 | 0 | }) |
89 | 0 | .collect(); |
90 | 0 | expected_lines.reverse(); |
91 | 0 | let expected = expected_lines.join("\n"); |
92 | 0 | let expected = expected.trim(); |
93 | 0 | log::debug!("=== expected ===\n{expected}"); |
94 | | |
95 | 0 | if actual == expected { |
96 | 0 | return Ok(()); |
97 | 0 | } |
98 | 0 |
|
99 | 0 | if std::env::var("CRANELIFT_TEST_BLESS").unwrap_or_default() == "1" { |
100 | 0 | let old_expectation_line_count = wat |
101 | 0 | .lines() |
102 | 0 | .rev() |
103 | 0 | .take_while(|l| l.starts_with(";;")) |
104 | 0 | .count(); |
105 | 0 | let old_wat_line_count = wat.lines().count(); |
106 | 0 | let new_wat_lines: Vec<_> = wat |
107 | 0 | .lines() |
108 | 0 | .take(old_wat_line_count - old_expectation_line_count) |
109 | 0 | .map(|l| l.to_string()) |
110 | 0 | .chain(actual.lines().map(|l| { |
111 | 0 | if l.is_empty() { |
112 | 0 | ";;".to_string() |
113 | | } else { |
114 | 0 | format!(";; {l}") |
115 | | } |
116 | 0 | })) |
117 | 0 | .collect(); |
118 | 0 | let mut new_wat = new_wat_lines.join("\n"); |
119 | 0 | new_wat.push('\n'); |
120 | 0 | std::fs::write(path, new_wat) |
121 | 0 | .with_context(|| format!("failed to write file: {}", path.display()))?; |
122 | 0 | return Ok(()); |
123 | 0 | } |
124 | 0 |
|
125 | 0 | bail!( |
126 | 0 | "Did not get the expected CLIF translation:\n\n\ |
127 | 0 | {}\n\n\ |
128 | 0 | Note: You can re-run with the `CRANELIFT_TEST_BLESS=1` environment\n\ |
129 | 0 | variable set to update test expectations.", |
130 | 0 | TextDiff::from_lines(expected, actual) |
131 | 0 | .unified_diff() |
132 | 0 | .header("expected", "actual") |
133 | 0 | ) |
134 | 0 | } |