From f3ed0e427db424b33b70c0ef56a67b6f8a7f642a Mon Sep 17 00:00:00 2001 From: clubby789 Date: Fri, 17 Jul 2026 15:43:50 +0100 Subject: [PATCH 1/2] tests: Implement snapshot test suites for formatting errors --- Cargo.lock | 67 +++++++++++ Cargo.toml | 1 + src/test/mod.rs | 40 +++++++ tests/rustfmt/main.rs | 109 +++++++++++++++++- tests/warning/snapshots/deprecated_skip.snap | 10 ++ .../warning/snapshots/invalid_attribute.snap | 10 ++ tests/warning/snapshots/line_overflow.snap | 11 ++ tests/warning/snapshots/lost_comment.snap | 11 ++ tests/warning/snapshots/multiple_errors.snap | 38 ++++++ .../snapshots/trailing_whitespace.snap | 12 ++ tests/warning/source/deprecated_skip.rs | 2 + tests/warning/source/invalid_attribute.rs | 2 + tests/warning/source/line_overflow.rs | 1 + tests/warning/source/lost_comment.rs | 1 + tests/warning/source/multiple_errors.rs | 12 ++ tests/warning/source/trailing_whitespace.rs | 2 + 16 files changed, 327 insertions(+), 2 deletions(-) create mode 100644 tests/warning/snapshots/deprecated_skip.snap create mode 100644 tests/warning/snapshots/invalid_attribute.snap create mode 100644 tests/warning/snapshots/line_overflow.snap create mode 100644 tests/warning/snapshots/lost_comment.snap create mode 100644 tests/warning/snapshots/multiple_errors.snap create mode 100644 tests/warning/snapshots/trailing_whitespace.snap create mode 100644 tests/warning/source/deprecated_skip.rs create mode 100644 tests/warning/source/invalid_attribute.rs create mode 100644 tests/warning/source/line_overflow.rs create mode 100644 tests/warning/source/lost_comment.rs create mode 100644 tests/warning/source/multiple_errors.rs create mode 100644 tests/warning/source/trailing_whitespace.rs diff --git a/Cargo.lock b/Cargo.lock index 343f5cdf885..5a654ff7880 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -193,6 +193,17 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "console" +version = "0.16.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fe5f465a4f6fee88fad41b85d990f84c835335e85b5d9e6e63e0d06d28cba7c" +dependencies = [ + "encode_unicode", + "libc", + "windows-sys", +] + [[package]] name = "crossbeam-utils" version = "0.8.8" @@ -236,6 +247,12 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + [[package]] name = "equivalent" version = "1.0.2" @@ -349,6 +366,20 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "insta" +version = "1.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f0f8fee8c926415c58d6ae43a08523a26faccb2323f5e6b644fe7dd4ef6b82" +dependencies = [ + "console", + "once_cell", + "regex", + "similar", + "strip-ansi-escapes", + "tempfile", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -546,6 +577,7 @@ dependencies = [ "dirs", "getopts", "ignore", + "insta", "itertools", "regex", "rustfmt-config_proc_macro", @@ -656,12 +688,27 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "similar" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" + [[package]] name = "smallvec" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +[[package]] +name = "strip-ansi-escapes" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ff8ef943b384c414f54aefa961dd2bd853add74ec75e7ac74cf91dba62bcfa" +dependencies = [ + "vte", +] + [[package]] name = "strsim" version = "0.11.1" @@ -881,6 +928,26 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vte" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5022b5fbf9407086c180e9557be968742d839e68346af7792b8592489732197" +dependencies = [ + "utf8parse", + "vte_generate_state_changes", +] + +[[package]] +name = "vte_generate_state_changes" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e369bee1b05d510a7b4ed645f5faa90619e05437111783ea5848f28d97d3c2e" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "walkdir" version = "2.3.2" diff --git a/Cargo.toml b/Cargo.toml index 84ad1f24ea8..3f4d38b3cc7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,6 +63,7 @@ semver = "1.0.21" [dev-dependencies] tempfile = "3.23.0" +insta = { version = "1.48.0", features = ["filters"] } # Rustc dependencies are loaded from the sysroot, Cargo doesn't know about them. diff --git a/src/test/mod.rs b/src/test/mod.rs index 291ac8fa078..6089c9a08ea 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -249,6 +249,46 @@ fn system_tests() { }); } +// Check formatting-specific warning/error emissions against snapshots. +#[test] +fn warning_tests() { + init_log(); + let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR")); + let manifest_dir_filter = regex::escape(manifest_dir.to_string_lossy().as_ref()); + let files = get_test_files(Path::new("tests/warning/source"), true); + let mut config = Config::default(); + config.set().error_on_line_overflow(true); + config.set().error_on_unformatted(true); + + for file in &files { + let snapshot_name = file.file_stem().unwrap().to_str().unwrap(); + let (parsing_errors, _, report) = format_file(file, config.clone()); + assert!(!parsing_errors, "{} failed to parse", file.display()); + assert!( + report.has_warnings(), + "{} did not emit a warning or error", + file.display() + ); + let warning = FormatReportFormatterBuilder::new(&report) + .build() + .to_string(); + + insta::with_settings!({ + snapshot_path => manifest_dir.join("tests/warning/snapshots"), + prepend_module_to_snapshot => false, + omit_expression => true, + filters => vec![ + (manifest_dir_filter.as_str(), "$$DIR"), + (r"\r\n", "\n"), + (r"\\", "/"), + ], + strip_ansi_escape_codes => true, + }, { + insta::assert_snapshot!(snapshot_name, warning); + }); + } +} + // Do the same for tests/coverage-source directory. // The only difference is the coverage mode. #[test] diff --git a/tests/rustfmt/main.rs b/tests/rustfmt/main.rs index 4008c8d1435..9a46544b367 100644 --- a/tests/rustfmt/main.rs +++ b/tests/rustfmt/main.rs @@ -5,7 +5,7 @@ use std::fs::{File, remove_file}; use std::path::Path; use std::process::Command; -use rustfmt_config_proc_macro::{nightly_only_test, rustfmt_only_ci_test}; +use rustfmt_config_proc_macro::{nightly_only_test, rustfmt_only_ci_test, stable_only_test}; /// Run the rustfmt executable with environment vars set and return its output. fn rustfmt_with_extra( @@ -117,11 +117,116 @@ fn inline_config() { ); } +#[stable_only_test] #[test] fn rustfmt_usage_text() { let args = ["--help"]; let (stdout, _) = rustfmt(&args); - assert!(stdout.contains("Format Rust code\n\nusage: rustfmt [options] ...")); + insta::assert_snapshot!(stdout, @" + Format Rust code + + usage: rustfmt [options] ... + + Options: + --check Run in 'check' mode. Exits with 0 if input is + formatted correctly. Exits with 1 and prints a diff if + formatting is required. + --emit [files|stdout] + What data to emit and how + --backup Backup any modified files. + --config-path [Path for the configuration file] + Recursively searches the given path for the + rustfmt.toml config file. If not found reverts to the + input file path + --edition [2015|2018|2021|2024] + Rust edition to use + --style-edition [2015|2018|2021|2024] + The edition of the Style Guide (unstable). + --color [always|never|auto] + Use colored output (if supported) + --print-config [default|minimal|current] PATH + Dumps a default or minimal config to PATH. A minimal + config is the subset of the current config file used + for formatting the current program. `current` writes + to stdout current config as if formatting the file at + PATH. + -l, --files-with-diff + Prints the names of mismatched files that were + formatted. Prints the names of files that would be + formatted when used with `--check` mode. + --config [key1=val1,key2=val2...] + Set options from command line. These settings take + priority over .rustfmt.toml + --style-edition [2015|2018|2021|2024] + The edition of the Style Guide. + -v, --verbose Print verbose output + -q, --quiet Print less output + -V, --version Show version information + -h, --help [=TOPIC] Show this message or help about a specific topic: + `config` + "); +} + +#[nightly_only_test] +#[test] +fn rustfmt_nightly_usage_text() { + let args = ["--help"]; + let (stdout, _) = rustfmt(&args); + insta::assert_snapshot!(stdout, @" + Format Rust code + + usage: rustfmt [options] ... + + Options: + --check Run in 'check' mode. Exits with 0 if input is + formatted correctly. Exits with 1 and prints a diff if + formatting is required. + --emit [files|stdout|coverage|checkstyle|json] + What data to emit and how + --backup Backup any modified files. + --config-path [Path for the configuration file] + Recursively searches the given path for the + rustfmt.toml config file. If not found reverts to the + input file path + --edition [2015|2018|2021|2024] + Rust edition to use + --style-edition [2015|2018|2021|2024] + The edition of the Style Guide (unstable). + --color [always|never|auto] + Use colored output (if supported) + --print-config [default|minimal|current] PATH + Dumps a default or minimal config to PATH. A minimal + config is the subset of the current config file used + for formatting the current program. `current` writes + to stdout current config as if formatting the file at + PATH. + -l, --files-with-diff + Prints the names of mismatched files that were + formatted. Prints the names of files that would be + formatted when used with `--check` mode. + --config [key1=val1,key2=val2...] + Set options from command line. These settings take + priority over .rustfmt.toml + --style-edition [2015|2018|2021|2024] + The edition of the Style Guide. + --unstable-features + Enables unstable features. Only available on nightly + channel. + --file-lines JSON + Format specified line ranges. Run with + `--help=file-lines` for more detail (unstable). + --error-on-unformatted + Error if unable to get comments or string literals + within max_width, or they are left with trailing + whitespaces (unstable). + --skip-children + Don't reformat child modules (unstable). + -v, --verbose Print verbose output + -q, --quiet Print less output + -V, --version Show version information + -h, --help [=TOPIC] Show this message or help about a specific topic: + `config` or `file-lines` + "); } #[test] diff --git a/tests/warning/snapshots/deprecated_skip.snap b/tests/warning/snapshots/deprecated_skip.snap new file mode 100644 index 00000000000..bdba05a85a1 --- /dev/null +++ b/tests/warning/snapshots/deprecated_skip.snap @@ -0,0 +1,10 @@ +--- +source: src/test/mod.rs +--- +warning: `rustfmt_skip` is deprecated; use `rustfmt::skip` + --> tests/warning/source/deprecated_skip.rs:1 + | +1 | #[rustfmt_skip] + | + +warning: rustfmt has failed to format. See previous 1 errors. diff --git a/tests/warning/snapshots/invalid_attribute.snap b/tests/warning/snapshots/invalid_attribute.snap new file mode 100644 index 00000000000..5168fd54054 --- /dev/null +++ b/tests/warning/snapshots/invalid_attribute.snap @@ -0,0 +1,10 @@ +--- +source: src/test/mod.rs +--- +error: invalid attribute + --> tests/warning/source/invalid_attribute.rs:1 + | +1 | #[rustfmt::invalid] + | + +warning: rustfmt has failed to format. See previous 1 errors. diff --git a/tests/warning/snapshots/line_overflow.snap b/tests/warning/snapshots/line_overflow.snap new file mode 100644 index 00000000000..beb0257c4aa --- /dev/null +++ b/tests/warning/snapshots/line_overflow.snap @@ -0,0 +1,11 @@ +--- +source: src/test/mod.rs +--- +error[internal]: line formatted, but exceeded maximum width (maximum: 100 (see `max_width` option), found: 109) + --> tests/warning/source/line_overflow.rs:1:1:101 + | +1 | fn this_function_name_is_intentionally_long_enough_to_exceed_the_default_one_hundred_character_maximum_width( + | ^^^^^^^^^ + | + +warning: rustfmt has failed to format. See previous 1 errors. diff --git a/tests/warning/snapshots/lost_comment.snap b/tests/warning/snapshots/lost_comment.snap new file mode 100644 index 00000000000..1983f12d73a --- /dev/null +++ b/tests/warning/snapshots/lost_comment.snap @@ -0,0 +1,11 @@ +--- +source: src/test/mod.rs +--- +error[internal]: not formatted because a comment would be lost + --> tests/warning/source/lost_comment.rs:1 + | +1 | fn main() { let _ = 1 /* This comment cannot be retained by the expression formatter. */ + 2; } + | + = note: set `error_on_unformatted = false` to suppress the warning against comments or string literals + +warning: rustfmt has failed to format. See previous 1 errors. diff --git a/tests/warning/snapshots/multiple_errors.snap b/tests/warning/snapshots/multiple_errors.snap new file mode 100644 index 00000000000..42935056081 --- /dev/null +++ b/tests/warning/snapshots/multiple_errors.snap @@ -0,0 +1,38 @@ +--- +source: src/test/mod.rs +--- +warning: `rustfmt_skip` is deprecated; use `rustfmt::skip` + --> tests/warning/source/multiple_errors.rs:1 + | +1 | #[rustfmt_skip] + | + +error: invalid attribute + --> tests/warning/source/multiple_errors.rs:4 + | +4 | #[rustfmt::invalid] + | + +error[internal]: not formatted because a comment would be lost + --> tests/warning/source/multiple_errors.rs:9 + | +9 | fn lost_comment() { let _ = 1 /* This comment cannot be retained by the expression formatter. */ + 2; } + | + = note: set `error_on_unformatted = false` to suppress the warning against comments or string literals + +error[internal]: line formatted, but exceeded maximum width (maximum: 100 (see `max_width` option), found: 109) + --> tests/warning/source/multiple_errors.rs:7:7:101 + | +7 | fn this_function_name_is_intentionally_long_enough_to_exceed_the_default_one_hundred_character_maximum_width( + | ^^^^^^^^^ + | + +error[internal]: left behind trailing whitespace + --> tests/warning/source/multiple_errors.rs:15:15:46 + | +15 | /// This doc comment has trailing whitespace. + | ^^^ + | + = note: set `error_on_unformatted = false` to suppress the warning against comments or string literals + +warning: rustfmt has failed to format. See previous 5 errors. diff --git a/tests/warning/snapshots/trailing_whitespace.snap b/tests/warning/snapshots/trailing_whitespace.snap new file mode 100644 index 00000000000..13ca45ac2cd --- /dev/null +++ b/tests/warning/snapshots/trailing_whitespace.snap @@ -0,0 +1,12 @@ +--- +source: src/test/mod.rs +--- +error[internal]: left behind trailing whitespace + --> tests/warning/source/trailing_whitespace.rs:1:1:46 + | +1 | /// This doc comment has trailing whitespace. + | ^^^ + | + = note: set `error_on_unformatted = false` to suppress the warning against comments or string literals + +warning: rustfmt has failed to format. See previous 1 errors. diff --git a/tests/warning/source/deprecated_skip.rs b/tests/warning/source/deprecated_skip.rs new file mode 100644 index 00000000000..6578f13bc7f --- /dev/null +++ b/tests/warning/source/deprecated_skip.rs @@ -0,0 +1,2 @@ +#[rustfmt_skip] +fn main() {} diff --git a/tests/warning/source/invalid_attribute.rs b/tests/warning/source/invalid_attribute.rs new file mode 100644 index 00000000000..f3957f4c042 --- /dev/null +++ b/tests/warning/source/invalid_attribute.rs @@ -0,0 +1,2 @@ +#[rustfmt::invalid] +fn main() {} diff --git a/tests/warning/source/line_overflow.rs b/tests/warning/source/line_overflow.rs new file mode 100644 index 00000000000..1ce77151c95 --- /dev/null +++ b/tests/warning/source/line_overflow.rs @@ -0,0 +1 @@ +fn this_function_name_is_intentionally_long_enough_to_exceed_the_default_one_hundred_character_maximum_width() {} diff --git a/tests/warning/source/lost_comment.rs b/tests/warning/source/lost_comment.rs new file mode 100644 index 00000000000..d075256c927 --- /dev/null +++ b/tests/warning/source/lost_comment.rs @@ -0,0 +1 @@ +fn main() { let _ = 1 /* This comment cannot be retained by the expression formatter. */ + 2; } diff --git a/tests/warning/source/multiple_errors.rs b/tests/warning/source/multiple_errors.rs new file mode 100644 index 00000000000..02e660d3cb8 --- /dev/null +++ b/tests/warning/source/multiple_errors.rs @@ -0,0 +1,12 @@ +#[rustfmt_skip] +fn deprecated_skip() {} + +#[rustfmt::invalid] +fn invalid_attribute() {} + +fn this_function_name_is_intentionally_long_enough_to_exceed_the_default_one_hundred_character_maximum_width() {} + +fn lost_comment() { let _ = 1 /* This comment cannot be retained by the expression formatter. */ + 2; } + +/// This doc comment has trailing whitespace. +fn trailing_whitespace() {} diff --git a/tests/warning/source/trailing_whitespace.rs b/tests/warning/source/trailing_whitespace.rs new file mode 100644 index 00000000000..bfa58ac2887 --- /dev/null +++ b/tests/warning/source/trailing_whitespace.rs @@ -0,0 +1,2 @@ +/// This doc comment has trailing whitespace. +fn main() {} From ea5d670ac5e59222bb33c5915ca7608ead2c21ed Mon Sep 17 00:00:00 2001 From: clubby789 Date: Wed, 22 Jul 2026 02:59:23 +0100 Subject: [PATCH 2/2] doc: Add instructions for updating snapshot tests --- Contributing.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Contributing.md b/Contributing.md index 996a30c3a5b..7b298052ab2 100644 --- a/Contributing.md +++ b/Contributing.md @@ -63,6 +63,11 @@ would need a configuration file named `test-indent.toml` in that directory. As a example, the `issue-1111.rs` test file is configured by the file `./tests/config/issue-1111.toml`. +### Updating snapshots + +Some tests that test rustfmt-specific output (e.g. `--help` output and formatting-specific errors) use [insta](https://insta.rs/) to snapshot their output. +To update these tests, install [`cargo-insta`](https://insta.rs/docs/cli/) and run `cargo insta test --review`. + ## Debugging Some `rewrite_*` methods use the `debug!` macro for printing useful information.