Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 262d096a authored by Martin Geisler's avatar Martin Geisler
Browse files

pdl: enable snapshot testing via ‘cargo test’

This adds a minimal infrastructure needed to support snapshot testing
with Cargo. In short, we read a snapshot file with known-good content
and compare this to the generated output. If they differ, we
optionally override the snapshot with the generated output. This will
allow us to easily update the output as we evolve the code generator.

This the strategy described in go/pdl-rust-backend-testing-strategy.

Test: atest pdl_inline_tests && cargo test
Change-Id: I82078f77168ee7141de71ff57935c1fb12d09364
parent 2acc00a7
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -47,6 +47,10 @@ rust_test_host {
    data: [
        ":rustfmt",
        ":rustfmt.toml",
        "test/generated/preamble.rs",
        "test/generated/packet_decl_empty.rs",
        "test/generated/packet_decl_simple_little_endian.rs",
        "test/generated/packet_decl_simple_big_endian.rs",
    ],
}

+11 −9
Original line number Diff line number Diff line
@@ -556,7 +556,7 @@ mod tests {
    use super::*;
    use crate::ast;
    use crate::parser::parse_inline;
    use crate::test_utils::{assert_eq_with_diff, rustfmt};
    use crate::test_utils::{assert_snapshot_eq, rustfmt};

    /// Parse a string fragment as a PDL file.
    ///
@@ -571,8 +571,7 @@ mod tests {
    #[test]
    fn test_generate_preamble() {
        let actual_code = generate_preamble(Path::new("some/path/foo.pdl")).unwrap();
        let expected_code = include_str!("../test/generated/preamble.rs");
        assert_eq_with_diff(&rustfmt(expected_code), &rustfmt(&actual_code));
        assert_snapshot_eq("test/generated/preamble.rs", &rustfmt(&actual_code));
    }

    #[test]
@@ -587,8 +586,7 @@ mod tests {
        let children = HashMap::new();
        let decl = &grammar.declarations[0];
        let actual_code = generate_decl(&grammar, &packets, &children, decl).unwrap();
        let expected_code = include_str!("../test/generated/packet_decl_empty.rs");
        assert_eq_with_diff(&rustfmt(expected_code), &rustfmt(&actual_code));
        assert_snapshot_eq("test/generated/packet_decl_empty.rs", &rustfmt(&actual_code));
    }

    #[test]
@@ -607,8 +605,10 @@ mod tests {
        let children = HashMap::new();
        let decl = &grammar.declarations[0];
        let actual_code = generate_decl(&grammar, &packets, &children, decl).unwrap();
        let expected_code = include_str!("../test/generated/packet_decl_simple_little_endian.rs");
        assert_eq_with_diff(&rustfmt(expected_code), &rustfmt(&actual_code));
        assert_snapshot_eq(
            "test/generated/packet_decl_simple_little_endian.rs",
            &rustfmt(&actual_code),
        );
    }

    #[test]
@@ -627,7 +627,9 @@ mod tests {
        let children = HashMap::new();
        let decl = &grammar.declarations[0];
        let actual_code = generate_decl(&grammar, &packets, &children, decl).unwrap();
        let expected_code = include_str!("../test/generated/packet_decl_simple_big_endian.rs");
        assert_eq_with_diff(&rustfmt(expected_code), &rustfmt(&actual_code));
        assert_snapshot_eq(
            "test/generated/packet_decl_simple_big_endian.rs",
            &rustfmt(&actual_code),
        );
    }
}
+51 −0
Original line number Diff line number Diff line
@@ -5,7 +5,9 @@
// rest of the `pdl` crate. To make this work, avoid `use crate::`
// statements below.

use std::fs;
use std::io::Write;
use std::path::Path;
use std::process::{Command, Stdio};
use tempfile::NamedTempFile;

@@ -95,3 +97,52 @@ pub fn diff(left: &str, right: &str) -> String {
pub fn assert_eq_with_diff(left: &str, right: &str) {
    assert!(left == right, "texts did not match, diff:\n{}\n", diff(left, right));
}

/// Compare a string with a snapshot file.
///
/// The `snapshot_path` is relative to the current working directory
/// of the test binary. This depends on how you execute the tests:
///
/// * When using `atest`: The current working directory is a random
///   temporary directory. You need to ensure that the snapshot file
///   is installed into this directory. You do this by adding the
///   snapshot to the `data` attribute of your test rule
///
/// * When using Cargo: The current working directory is set to
///   `CARGO_MANIFEST_DIR`, which is where the `Cargo.toml` file is
///   found.
///
/// If you run the test with Cargo and the `UPDATE_SNAPSHOTS`
/// environment variable is set, then the `actual_content` will be
/// written to `snapshot_path`. Otherwise the content is compared and
/// a panic is triggered if they differ.
#[track_caller]
pub fn assert_snapshot_eq<P: AsRef<Path>>(snapshot_path: P, actual_content: &str) {
    let snapshot = snapshot_path.as_ref();
    let snapshot_content = fs::read(&snapshot).unwrap_or_else(|err| {
        panic!("Could not read snapshot from {}: {}", snapshot.display(), err)
    });
    let snapshot_content = String::from_utf8(snapshot_content).expect("Snapshot was not UTF-8");

    // Normal comparison if UPDATE_SNAPSHOTS is unset.
    if std::env::var("UPDATE_SNAPSHOTS").is_err() {
        return assert_eq_with_diff(&snapshot_content, actual_content);
    }

    // Bail out if we are not using Cargo.
    if std::env::var("CARGO_MANIFEST_DIR").is_err() {
        panic!("Please unset UPDATE_SNAPSHOTS if you are not using Cargo");
    }

    if actual_content != snapshot_content {
        eprintln!(
            "Updating snapshot {}: {} -> {} bytes",
            snapshot.display(),
            snapshot_content.len(),
            actual_content.len()
        );
        fs::write(&snapshot_path, actual_content).unwrap_or_else(|err| {
            panic!("Could not write snapshot to {}: {}", snapshot.display(), err)
        });
    }
}
+4 −1
Original line number Diff line number Diff line
@@ -4,9 +4,12 @@ use std::process::Command;
// The integration test in this file is not part of the pdl crate, and
// so we cannot directly depend on anything from pdl. However, we can
// include the test_utils.rs file directly.
//
// The module is public to avoid an "function is never used" error,
// which is triggered because we don't use all test_utils functions.

#[path = "../src/test_utils.rs"]
mod test_utils;
pub mod test_utils;
use test_utils::{assert_eq_with_diff, find_binary, rustfmt};

fn strip_blank_lines(text: &str) -> String {