diff --git a/.gitignore b/.gitignore index c12003316..054d96f75 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ static/styles/vendor.css static/styles/app.css static/styles/fonts.css static/styles/noscript.css +src/snapshots diff --git a/Cargo.lock b/Cargo.lock index ea6a0b3f0..220c38a64 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -203,6 +203,7 @@ dependencies = [ "color-eyre", "comrak", "eyre", + "insta", "lazy_static", "rayon", "regex", @@ -441,6 +442,18 @@ dependencies = [ "xdg", ] +[[package]] +name = "console" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "windows-sys 0.59.0", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -605,6 +618,12 @@ version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7914353092ddf589ad78f25c5c1c21b7f80b0ff8621e7c814c3485b5306da9d" +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + [[package]] name = "encoding_rs" version = "0.8.35" @@ -1160,6 +1179,22 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "insta" +version = "1.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50259abbaa67d11d2bcafc7ba1d094ed7a0c70e3ce893f0d0997f73558cb3084" +dependencies = [ + "console", + "globset", + "linked-hash-map", + "once_cell", + "pin-project", + "regex", + "similar", + "walkdir", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -1997,6 +2032,12 @@ dependencies = [ "libc", ] +[[package]] +name = "similar" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" + [[package]] name = "siphasher" version = "1.0.1" diff --git a/Cargo.toml b/Cargo.toml index 1b47803c0..c7467bfe9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,3 +21,6 @@ tera = "=1.20.0" [workspace] members = ["serve"] + +[dev-dependencies] +insta = { version = "1.42.0", features = ["filters", "glob"] } diff --git a/README.md b/README.md index e3c493b88..79032d87f 100644 --- a/README.md +++ b/README.md @@ -54,3 +54,28 @@ author: Blog post author (or on behalf of which team) release: true (to be only used for official posts about Rust releases announcements) --- ``` + +### Snapshot testing + +If you're making changes to how the site is generated, you may want to check the impact your changes have on the output. +For this purpose, there is a setup to do snapshot testing over the entire output directory. +It's not run in CI, because the number of snapshots is too large. +But you can run these tests locally as needed. + +- Make sure you have [cargo-insta](https://insta.rs/docs/quickstart/) installed. + +- Generate the good snapshots to compare against, usually based off the master branch: + ```sh + cargo insta test --accept --include-ignored + ``` + Consider making a commit with these snapshots, so you can always check the diff of your changes with git: + ```sh + git add --force src/snapshots # snapshots are ignored by default + git commit --message "WIP add good snapshots" + ``` + Since we can't merge the snapshots to main, don't forget to drop this commit when opening a pull request. + +- Compare the output of the branch you're working on with the good snapshots: + ```sh + cargo insta test --review --include-ignored + ``` diff --git a/src/lib.rs b/src/lib.rs index 01ceaf8dc..cc360dd2c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -302,3 +302,40 @@ pub fn main() -> eyre::Result<()> { Ok(()) } + +#[test] +fn snapshot() { + main().unwrap(); + let timestamped_files = ["releases.json", "feed.xml"]; + let inexplicably_non_deterministic_files = ["images/2023-08-rust-survey-2022/experiences.png"]; + insta::glob!("..", "site/**/*", |path| { + if path.is_dir() { + return; + } + let path = path.display().to_string(); + if timestamped_files + .into_iter() + .chain(inexplicably_non_deterministic_files) + .any(|f| path.ends_with(f)) + { + // Skip troublesome files, e.g. they might contain timestamps. + // If possible, they are tested separately below. + return; + } + let content = fs::read(path).unwrap(); + // insta can't deal with non-utf8 strings? + let content = String::from_utf8_lossy(&content).into_owned(); + insta::assert_snapshot!(content); + }); + + // test files with timestamps filtered + insta::with_settings!({filters => vec![ + (r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\+\d{2}:\d{2}", "(filtered timestamp)"), + ]}, { + for file in timestamped_files { + let content = fs::read(format!("site/{file}")).unwrap(); + let content = String::from_utf8_lossy(&content).into_owned(); + insta::assert_snapshot!(content); + } + }); +}