|
7 | 7 | //! compares the result with the corresponding `.fixed.rs` file. If they don't
|
8 | 8 | //! match, then the test fails.
|
9 | 9 | //!
|
10 |
| -//! There are several debugging environment variables for this test that you can set: |
| 10 | +//! The files ending in `.nightly.rs` will run only on the nightly toolchain |
11 | 11 | //!
|
12 |
| -//! - `RUST_LOG=parse_and_replace=debug`: Print debug information. |
13 |
| -//! - `RUSTFIX_TEST_BLESS=test-name.rs`: When given the name of a test, this |
14 |
| -//! will overwrite the `.json` and `.fixed.rs` files with the expected |
15 |
| -//! values. This can be used when adding a new test. |
16 |
| -//! - `RUSTFIX_TEST_RECORD_JSON=1`: Records the JSON output to |
17 |
| -//! `*.recorded.json` files. You can then move that to `.json` or whatever |
18 |
| -//! you need. |
19 |
| -//! - `RUSTFIX_TEST_RECORD_FIXED_RUST=1`: Records the fixed result to |
20 |
| -//! `*.recorded.rs` files. You can then move that to `.rs` or whatever you |
21 |
| -//! need. |
| 12 | +//! To override snapshots, run `SNAPSHOTS=overwrite cargo test`. |
| 13 | +//! See [`snapbox::assert::Action`] for different actions. |
22 | 14 |
|
23 | 15 | #![allow(clippy::disallowed_methods, clippy::print_stdout, clippy::print_stderr)]
|
24 | 16 |
|
25 |
| -use anyhow::{anyhow, ensure, Context, Error}; |
| 17 | +use anyhow::{anyhow, Context, Error}; |
26 | 18 | use rustfix::apply_suggestions;
|
| 19 | +use serde_json::Value; |
| 20 | +use snapbox::data::DataFormat; |
| 21 | +use snapbox::{Assert, Data}; |
27 | 22 | use std::collections::HashSet;
|
28 | 23 | use std::env;
|
29 | 24 | use std::ffi::OsString;
|
30 | 25 | use std::fs;
|
31 |
| -use std::path::{Path, PathBuf}; |
| 26 | +use std::path::Path; |
32 | 27 | use std::process::{Command, Output};
|
33 | 28 | use tempfile::tempdir;
|
34 |
| -use tracing::{debug, info, warn}; |
35 | 29 |
|
36 | 30 | mod fixmode {
|
37 | 31 | pub const EVERYTHING: &str = "yolo";
|
38 | 32 | }
|
39 | 33 |
|
40 |
| -mod settings { |
41 |
| - // can be set as env var to debug |
42 |
| - pub const CHECK_JSON: &str = "RUSTFIX_TEST_CHECK_JSON"; |
43 |
| - pub const RECORD_JSON: &str = "RUSTFIX_TEST_RECORD_JSON"; |
44 |
| - pub const RECORD_FIXED_RUST: &str = "RUSTFIX_TEST_RECORD_FIXED_RUST"; |
45 |
| - pub const BLESS: &str = "RUSTFIX_TEST_BLESS"; |
46 |
| -} |
47 |
| - |
48 | 34 | static mut VERSION: (u32, bool) = (0, false);
|
49 | 35 |
|
50 | 36 | // Temporarily copy from `cargo_test_macro::version`.
|
@@ -108,170 +94,98 @@ fn compiles_without_errors(file: &Path) -> Result<(), Error> {
|
108 | 94 |
|
109 | 95 | match res.status.code() {
|
110 | 96 | Some(0) => Ok(()),
|
111 |
| - _ => { |
112 |
| - info!( |
113 |
| - "file {:?} failed to compile:\n{}", |
114 |
| - file, |
115 |
| - String::from_utf8(res.stderr)? |
116 |
| - ); |
117 |
| - Err(anyhow!( |
118 |
| - "failed with status {:?} (`env RUST_LOG=parse_and_replace=info` for more info)", |
119 |
| - res.status.code(), |
120 |
| - )) |
121 |
| - } |
122 |
| - } |
123 |
| -} |
124 |
| - |
125 |
| -fn diff(expected: &str, actual: &str) -> String { |
126 |
| - use similar::{ChangeTag, TextDiff}; |
127 |
| - use std::fmt::Write; |
128 |
| - |
129 |
| - let mut res = String::new(); |
130 |
| - let diff = TextDiff::from_lines(expected.trim(), actual.trim()); |
131 |
| - |
132 |
| - let mut different = false; |
133 |
| - for op in diff.ops() { |
134 |
| - for change in diff.iter_changes(op) { |
135 |
| - let prefix = match change.tag() { |
136 |
| - ChangeTag::Equal => continue, |
137 |
| - ChangeTag::Insert => "+", |
138 |
| - ChangeTag::Delete => "-", |
139 |
| - }; |
140 |
| - if !different { |
141 |
| - writeln!(&mut res, "differences found (+ == actual, - == expected):").unwrap(); |
142 |
| - different = true; |
143 |
| - } |
144 |
| - write!(&mut res, "{} {}", prefix, change.value()).unwrap(); |
145 |
| - } |
146 |
| - } |
147 |
| - if different { |
148 |
| - write!(&mut res, "").unwrap(); |
| 97 | + _ => Err(anyhow!( |
| 98 | + "file {:?} failed compile with status {:?}:\n {}", |
| 99 | + file, |
| 100 | + res.status.code(), |
| 101 | + String::from_utf8(res.stderr)? |
| 102 | + )), |
149 | 103 | }
|
150 |
| - |
151 |
| - res |
152 | 104 | }
|
153 | 105 |
|
154 |
| -fn test_rustfix_with_file<P: AsRef<Path>>(file: P, mode: &str) -> Result<(), Error> { |
| 106 | +fn test_rustfix_with_file<P: AsRef<Path>>(file: P, mode: &str) { |
155 | 107 | let file: &Path = file.as_ref();
|
156 | 108 | let json_file = file.with_extension("json");
|
157 |
| - let fixed_file = file.with_extension("fixed.rs"); |
| 109 | + let expected_fixed_file = file.with_extension("fixed.rs"); |
158 | 110 |
|
159 | 111 | let filter_suggestions = if mode == fixmode::EVERYTHING {
|
160 | 112 | rustfix::Filter::Everything
|
161 | 113 | } else {
|
162 | 114 | rustfix::Filter::MachineApplicableOnly
|
163 | 115 | };
|
164 | 116 |
|
165 |
| - debug!("next up: {:?}", file); |
166 |
| - let code = fs::read_to_string(file)?; |
167 |
| - let errors = compile_and_get_json_errors(file) |
168 |
| - .with_context(|| format!("could not compile {}", file.display()))?; |
169 |
| - let suggestions = |
170 |
| - rustfix::get_suggestions_from_json(&errors, &HashSet::new(), filter_suggestions) |
171 |
| - .context("could not load suggestions")?; |
172 |
| - |
173 |
| - if std::env::var(settings::RECORD_JSON).is_ok() { |
174 |
| - fs::write(file.with_extension("recorded.json"), &errors)?; |
175 |
| - } |
| 117 | + let code = fs::read_to_string(file).unwrap(); |
176 | 118 |
|
177 |
| - if std::env::var(settings::CHECK_JSON).is_ok() { |
178 |
| - let expected_json = fs::read_to_string(&json_file) |
179 |
| - .with_context(|| format!("could not load json fixtures for {}", file.display()))?; |
180 |
| - let expected_suggestions = |
181 |
| - rustfix::get_suggestions_from_json(&expected_json, &HashSet::new(), filter_suggestions) |
182 |
| - .context("could not load expected suggestions")?; |
| 119 | + let json = compile_and_get_json_errors(file) |
| 120 | + .with_context(|| format!("could not compile {}", file.display())) |
| 121 | + .unwrap(); |
183 | 122 |
|
184 |
| - ensure!( |
185 |
| - expected_suggestions == suggestions, |
186 |
| - "got unexpected suggestions from clippy:\n{}", |
187 |
| - diff( |
188 |
| - &format!("{:?}", expected_suggestions), |
189 |
| - &format!("{:?}", suggestions) |
190 |
| - ) |
191 |
| - ); |
192 |
| - } |
| 123 | + let suggestions = |
| 124 | + rustfix::get_suggestions_from_json(&json, &HashSet::new(), filter_suggestions) |
| 125 | + .context("could not load suggestions") |
| 126 | + .unwrap(); |
193 | 127 |
|
194 | 128 | let fixed = apply_suggestions(&code, &suggestions)
|
195 |
| - .with_context(|| format!("could not apply suggestions to {}", file.display()))? |
| 129 | + .with_context(|| format!("could not apply suggestions to {}", file.display())) |
| 130 | + .unwrap() |
196 | 131 | .replace('\r', "");
|
197 | 132 |
|
198 |
| - if std::env::var(settings::RECORD_FIXED_RUST).is_ok() { |
199 |
| - fs::write(file.with_extension("recorded.rs"), &fixed)?; |
200 |
| - } |
201 |
| - |
202 |
| - if let Some(bless_name) = std::env::var_os(settings::BLESS) { |
203 |
| - if bless_name == file.file_name().unwrap() { |
204 |
| - std::fs::write(&json_file, &errors)?; |
205 |
| - std::fs::write(&fixed_file, &fixed)?; |
206 |
| - } |
207 |
| - } |
208 |
| - |
209 |
| - let expected_fixed = fs::read_to_string(&fixed_file) |
210 |
| - .with_context(|| format!("could read fixed file for {}", file.display()))? |
211 |
| - .replace('\r', ""); |
212 |
| - ensure!( |
213 |
| - fixed.trim() == expected_fixed.trim(), |
214 |
| - "file {} doesn't look fixed:\n{}", |
215 |
| - file.display(), |
216 |
| - diff(fixed.trim(), expected_fixed.trim()) |
| 133 | + let assert = Assert::new().action_env(snapbox::assert::DEFAULT_ACTION_ENV); |
| 134 | + let (actual_fix, expected_fix) = assert.normalize( |
| 135 | + Data::text(&fixed), |
| 136 | + Data::read_from(expected_fixed_file.as_path(), Some(DataFormat::Text)), |
217 | 137 | );
|
218 | 138 |
|
219 |
| - compiles_without_errors(&fixed_file)?; |
220 |
| - |
221 |
| - Ok(()) |
222 |
| -} |
| 139 | + if actual_fix != expected_fix { |
| 140 | + let fixed_assert = assert.try_eq(Some(&"Current Fix"), actual_fix, expected_fix); |
| 141 | + assert!(fixed_assert.is_ok(), "{}", fixed_assert.err().unwrap()); |
| 142 | + |
| 143 | + let expected_json = Data::read_from(json_file.as_path(), Some(DataFormat::Text)); |
| 144 | + |
| 145 | + let pretty_json = json |
| 146 | + .split("\n") |
| 147 | + .filter(|j| !j.is_empty()) |
| 148 | + .map(|j| { |
| 149 | + serde_json::to_string_pretty(&serde_json::from_str::<Value>(j).unwrap()).unwrap() |
| 150 | + }) |
| 151 | + .collect::<Vec<String>>() |
| 152 | + .join("\n"); |
| 153 | + |
| 154 | + let json_assert = assert.try_eq( |
| 155 | + Some(&"Compiler Error"), |
| 156 | + Data::text(pretty_json), |
| 157 | + expected_json, |
| 158 | + ); |
| 159 | + assert!(json_assert.is_ok(), "{}", json_assert.err().unwrap()); |
| 160 | + } |
223 | 161 |
|
224 |
| -fn get_fixture_files(p: &str) -> Result<Vec<PathBuf>, Error> { |
225 |
| - Ok(fs::read_dir(p)? |
226 |
| - .map(|e| e.unwrap().path()) |
227 |
| - .filter(|p| p.is_file()) |
228 |
| - .filter(|p| { |
229 |
| - let x = p.to_string_lossy(); |
230 |
| - x.ends_with(".rs") && !x.ends_with(".fixed.rs") && !x.ends_with(".recorded.rs") |
231 |
| - }) |
232 |
| - .collect()) |
| 162 | + compiles_without_errors(&expected_fixed_file).unwrap(); |
233 | 163 | }
|
234 | 164 |
|
235 |
| -fn assert_fixtures(dir: &str, mode: &str) { |
236 |
| - let files = get_fixture_files(dir) |
237 |
| - .with_context(|| format!("couldn't load dir `{dir}`")) |
238 |
| - .unwrap(); |
239 |
| - let mut failures = 0; |
240 |
| - |
241 |
| - let is_not_nightly = !version().1; |
242 |
| - |
243 |
| - for file in &files { |
244 |
| - if file |
245 |
| - .file_stem() |
246 |
| - .unwrap() |
247 |
| - .to_str() |
248 |
| - .unwrap() |
249 |
| - .ends_with(".nightly") |
250 |
| - && is_not_nightly |
251 |
| - { |
252 |
| - info!("skipped: {file:?}"); |
253 |
| - continue; |
254 |
| - } |
255 |
| - if let Err(err) = test_rustfix_with_file(file, mode) { |
256 |
| - println!("failed: {}", file.display()); |
257 |
| - warn!("{:?}", err); |
258 |
| - failures += 1; |
| 165 | +macro_rules! run_test { |
| 166 | + ($name:ident, $file:expr) => { |
| 167 | + #[test] |
| 168 | + #[allow(non_snake_case)] |
| 169 | + fn $name() { |
| 170 | + let (_, nightly) = version(); |
| 171 | + if !$file.ends_with(".nightly.rs") || nightly { |
| 172 | + let file = Path::new(concat!("./tests/everything/", $file)); |
| 173 | + assert!(file.is_file(), "could not load {}", $file); |
| 174 | + test_rustfix_with_file(file, fixmode::EVERYTHING); |
| 175 | + } |
259 | 176 | }
|
260 |
| - info!("passed: {:?}", file); |
261 |
| - } |
262 |
| - |
263 |
| - if failures > 0 { |
264 |
| - panic!( |
265 |
| - "{} out of {} fixture asserts failed\n\ |
266 |
| - (run with `env RUST_LOG=parse_and_replace=info` to get more details)", |
267 |
| - failures, |
268 |
| - files.len(), |
269 |
| - ); |
270 |
| - } |
| 177 | + }; |
271 | 178 | }
|
272 | 179 |
|
273 |
| -#[test] |
274 |
| -fn everything() { |
275 |
| - tracing_subscriber::fmt::init(); |
276 |
| - assert_fixtures("./tests/everything", fixmode::EVERYTHING); |
| 180 | +run_test! { |
| 181 | + closure_immutable_outer_variable, |
| 182 | + "closure-immutable-outer-variable.rs" |
277 | 183 | }
|
| 184 | +run_test! {dedup_suggestions, "dedup-suggestions.rs"} |
| 185 | +run_test! {E0178, "E0178.rs"} |
| 186 | +run_test! {handle_insert_only, "handle-insert-only.rs"} |
| 187 | +run_test! {lt_generic_comp, "lt-generic-comp.rs"} |
| 188 | +run_test! {multiple_solutions, "multiple-solutions.rs"} |
| 189 | +run_test! {replace_only_one_char, "replace-only-one-char.rs"} |
| 190 | +run_test! {str_lit_type_mismatch, "str-lit-type-mismatch.rs"} |
| 191 | +run_test! {use_insert, "use-insert.rs"} |
0 commit comments