run_make_support/assertion_helpers/
mod.rs

1//! Collection of assertions and assertion-related helpers.
2
3#[cfg(test)]
4mod tests;
5
6use std::panic;
7use std::path::Path;
8
9use crate::{fs, regex};
10
11/// Assert that `actual` is equal to `expected`.
12#[track_caller]
13pub fn assert_equals<A: AsRef<str>, E: AsRef<str>>(actual: A, expected: E) {
14    let actual = actual.as_ref();
15    let expected = expected.as_ref();
16
17    if actual != expected {
18        eprintln!("=== ACTUAL TEXT ===");
19        eprintln!("{}", actual);
20        eprintln!("=== EXPECTED ===");
21        eprintln!("{}", expected);
22        panic!("expected text does not match actual text");
23    }
24}
25
26struct SearchDetails<'assertion_name, 'haystack, 'needle> {
27    assertion_name: &'assertion_name str,
28    haystack: &'haystack str,
29    needle: &'needle str,
30}
31
32impl<'assertion_name, 'haystack, 'needle> SearchDetails<'assertion_name, 'haystack, 'needle> {
33    fn dump(&self) {
34        eprintln!("{}:", self.assertion_name);
35        eprintln!("=== HAYSTACK ===");
36        eprintln!("{}", self.haystack);
37        eprintln!("=== NEEDLE ===");
38        eprintln!("{}", self.needle);
39    }
40}
41
42/// Assert that `haystack` contains `needle`.
43#[track_caller]
44pub fn assert_contains<H: AsRef<str>, N: AsRef<str>>(haystack: H, needle: N) {
45    let haystack = haystack.as_ref();
46    let needle = needle.as_ref();
47    if !haystack.contains(needle) {
48        SearchDetails { assertion_name: "assert_contains", haystack, needle }.dump();
49        panic!("needle was not found in haystack");
50    }
51}
52
53/// Assert that `haystack` does not contain `needle`.
54#[track_caller]
55pub fn assert_not_contains<H: AsRef<str>, N: AsRef<str>>(haystack: H, needle: N) {
56    let haystack = haystack.as_ref();
57    let needle = needle.as_ref();
58    if haystack.contains(needle) {
59        SearchDetails { assertion_name: "assert_not_contains", haystack, needle }.dump();
60        panic!("needle was unexpectedly found in haystack");
61    }
62}
63
64/// Assert that `haystack` contains the regex `needle`.
65#[track_caller]
66pub fn assert_contains_regex<H: AsRef<str>, N: AsRef<str>>(haystack: H, needle: N) {
67    let haystack = haystack.as_ref();
68    let needle = needle.as_ref();
69    let re = regex::Regex::new(needle).unwrap();
70    if !re.is_match(haystack) {
71        SearchDetails { assertion_name: "assert_contains_regex", haystack, needle }.dump();
72        panic!("regex was not found in haystack");
73    }
74}
75
76/// Assert that `haystack` does not contain the regex `needle`.
77#[track_caller]
78pub fn assert_not_contains_regex<H: AsRef<str>, N: AsRef<str>>(haystack: H, needle: N) {
79    let haystack = haystack.as_ref();
80    let needle = needle.as_ref();
81    let re = regex::Regex::new(needle).unwrap();
82    if re.is_match(haystack) {
83        SearchDetails { assertion_name: "assert_not_contains_regex", haystack, needle }.dump();
84        panic!("regex was unexpectedly found in haystack");
85    }
86}
87
88/// Assert that `haystack` contains regex `needle` an `expected_count` number of times.
89#[track_caller]
90pub fn assert_count_is<H: AsRef<str>, N: AsRef<str>>(
91    expected_count: usize,
92    haystack: H,
93    needle: N,
94) {
95    let haystack = haystack.as_ref();
96    let needle = needle.as_ref();
97
98    let actual_count = haystack.matches(needle).count();
99    if expected_count != actual_count {
100        let count_fmt = format!(
101            "assert_count_is (expected_count = {expected_count}, actual_count = {actual_count})"
102        );
103        SearchDetails { assertion_name: &count_fmt, haystack, needle }.dump();
104        panic!(
105            "regex did not appear {expected_count} times in haystack (expected_count = \
106            {expected_count}, actual_count = {actual_count})"
107        );
108    }
109}
110
111/// Assert that all files in `dir1` exist and have the same content in `dir2`
112// FIXME(#135037): not robust against symlinks, lacks sanity test coverage.
113pub fn assert_dirs_are_equal(dir1: impl AsRef<Path>, dir2: impl AsRef<Path>) {
114    let dir2 = dir2.as_ref();
115    fs::read_dir_entries(dir1, |entry_path| {
116        let entry_name = entry_path.file_name().unwrap();
117        if entry_path.is_dir() {
118            assert_dirs_are_equal(&entry_path, &dir2.join(entry_name));
119        } else {
120            let path2 = dir2.join(entry_name);
121            let file1 = fs::read(&entry_path);
122            let file2 = fs::read(&path2);
123
124            // We don't use `assert_eq!` because they are `Vec<u8>`, so not great for display.
125            // Why not using String? Because there might be minified files or even potentially
126            // binary ones, so that would display useless output.
127            assert!(
128                file1 == file2,
129                "`{}` and `{}` have different content",
130                entry_path.display(),
131                path2.display(),
132            );
133        }
134    });
135}