|
| 1 | +//! Collection of assertions and assertion-related helpers. |
| 2 | +
|
| 3 | +use std::panic; |
| 4 | +use std::path::{Path, PathBuf}; |
| 5 | + |
| 6 | +use crate::fs_helpers; |
| 7 | +use crate::fs_wrapper; |
| 8 | +use crate::path_helpers::cwd; |
| 9 | + |
| 10 | +/// Browse the directory `path` non-recursively and return all files which respect the parameters |
| 11 | +/// outlined by `closure`. |
| 12 | +#[track_caller] |
| 13 | +pub fn shallow_find_files<P: AsRef<Path>, F: Fn(&PathBuf) -> bool>( |
| 14 | + path: P, |
| 15 | + filter: F, |
| 16 | +) -> Vec<PathBuf> { |
| 17 | + let mut matching_files = Vec::new(); |
| 18 | + for entry in fs_wrapper::read_dir(path) { |
| 19 | + let entry = entry.expect("failed to read directory entry."); |
| 20 | + let path = entry.path(); |
| 21 | + |
| 22 | + if path.is_file() && filter(&path) { |
| 23 | + matching_files.push(path); |
| 24 | + } |
| 25 | + } |
| 26 | + matching_files |
| 27 | +} |
| 28 | + |
| 29 | +/// Returns true if the filename at `path` starts with `prefix`. |
| 30 | +pub fn has_prefix<P: AsRef<Path>>(path: P, prefix: &str) -> bool { |
| 31 | + path.as_ref().file_name().is_some_and(|name| name.to_str().unwrap().starts_with(prefix)) |
| 32 | +} |
| 33 | + |
| 34 | +/// Returns true if the filename at `path` has the extension `extension`. |
| 35 | +pub fn has_extension<P: AsRef<Path>>(path: P, extension: &str) -> bool { |
| 36 | + path.as_ref().extension().is_some_and(|ext| ext == extension) |
| 37 | +} |
| 38 | + |
| 39 | +/// Returns true if the filename at `path` does not contain `expected`. |
| 40 | +pub fn not_contains<P: AsRef<Path>>(path: P, expected: &str) -> bool { |
| 41 | + !path.as_ref().file_name().is_some_and(|name| name.to_str().unwrap().contains(expected)) |
| 42 | +} |
| 43 | + |
| 44 | +/// Returns true if the filename at `path` is not in `expected`. |
| 45 | +pub fn filename_not_in_denylist<P: AsRef<Path>, V: AsRef<[String]>>(path: P, expected: V) -> bool { |
| 46 | + let expected = expected.as_ref(); |
| 47 | + path.as_ref() |
| 48 | + .file_name() |
| 49 | + .is_some_and(|name| !expected.contains(&name.to_str().unwrap().to_owned())) |
| 50 | +} |
| 51 | + |
| 52 | +/// Returns true if the filename at `path` ends with `suffix`. |
| 53 | +pub fn has_suffix<P: AsRef<Path>>(path: P, suffix: &str) -> bool { |
| 54 | + path.as_ref().file_name().is_some_and(|name| name.to_str().unwrap().ends_with(suffix)) |
| 55 | +} |
| 56 | + |
| 57 | +/// Gathers all files in the current working directory that have the extension `ext`, and counts |
| 58 | +/// the number of lines within that contain a match with the regex pattern `re`. |
| 59 | +pub fn count_regex_matches_in_files_with_extension(re: ®ex::Regex, ext: &str) -> usize { |
| 60 | + let fetched_files = shallow_find_files(cwd(), |path| has_extension(path, ext)); |
| 61 | + |
| 62 | + let mut count = 0; |
| 63 | + for file in fetched_files { |
| 64 | + let content = fs_wrapper::read_to_string(file); |
| 65 | + count += content.lines().filter(|line| re.is_match(&line)).count(); |
| 66 | + } |
| 67 | + |
| 68 | + count |
| 69 | +} |
| 70 | + |
| 71 | +/// Read the contents of a file that cannot simply be read by |
| 72 | +/// [`read_to_string`][crate::fs_wrapper::read_to_string], due to invalid UTF-8 data, then assert |
| 73 | +/// that it contains `expected`. |
| 74 | +#[track_caller] |
| 75 | +pub fn invalid_utf8_contains<P: AsRef<Path>, S: AsRef<str>>(path: P, expected: S) { |
| 76 | + let buffer = fs_wrapper::read(path.as_ref()); |
| 77 | + let expected = expected.as_ref(); |
| 78 | + if !String::from_utf8_lossy(&buffer).contains(expected) { |
| 79 | + eprintln!("=== FILE CONTENTS (LOSSY) ==="); |
| 80 | + eprintln!("{}", String::from_utf8_lossy(&buffer)); |
| 81 | + eprintln!("=== SPECIFIED TEXT ==="); |
| 82 | + eprintln!("{}", expected); |
| 83 | + panic!("specified text was not found in file"); |
| 84 | + } |
| 85 | +} |
| 86 | + |
| 87 | +/// Read the contents of a file that cannot simply be read by |
| 88 | +/// [`read_to_string`][crate::fs_wrapper::read_to_string], due to invalid UTF-8 data, then assert |
| 89 | +/// that it does not contain `expected`. |
| 90 | +#[track_caller] |
| 91 | +pub fn invalid_utf8_not_contains<P: AsRef<Path>, S: AsRef<str>>(path: P, expected: S) { |
| 92 | + let buffer = fs_wrapper::read(path.as_ref()); |
| 93 | + let expected = expected.as_ref(); |
| 94 | + if String::from_utf8_lossy(&buffer).contains(expected) { |
| 95 | + eprintln!("=== FILE CONTENTS (LOSSY) ==="); |
| 96 | + eprintln!("{}", String::from_utf8_lossy(&buffer)); |
| 97 | + eprintln!("=== SPECIFIED TEXT ==="); |
| 98 | + eprintln!("{}", expected); |
| 99 | + panic!("specified text was unexpectedly found in file"); |
| 100 | + } |
| 101 | +} |
| 102 | + |
| 103 | +/// Assert that `actual` is equal to `expected`. |
| 104 | +#[track_caller] |
| 105 | +pub fn assert_equals<A: AsRef<str>, E: AsRef<str>>(actual: A, expected: E) { |
| 106 | + let actual = actual.as_ref(); |
| 107 | + let expected = expected.as_ref(); |
| 108 | + if actual != expected { |
| 109 | + eprintln!("=== ACTUAL TEXT ==="); |
| 110 | + eprintln!("{}", actual); |
| 111 | + eprintln!("=== EXPECTED ==="); |
| 112 | + eprintln!("{}", expected); |
| 113 | + panic!("expected text was not found in actual text"); |
| 114 | + } |
| 115 | +} |
| 116 | + |
| 117 | +/// Assert that `haystack` contains `needle`. |
| 118 | +#[track_caller] |
| 119 | +pub fn assert_contains<H: AsRef<str>, N: AsRef<str>>(haystack: H, needle: N) { |
| 120 | + let haystack = haystack.as_ref(); |
| 121 | + let needle = needle.as_ref(); |
| 122 | + if !haystack.contains(needle) { |
| 123 | + eprintln!("=== HAYSTACK ==="); |
| 124 | + eprintln!("{}", haystack); |
| 125 | + eprintln!("=== NEEDLE ==="); |
| 126 | + eprintln!("{}", needle); |
| 127 | + panic!("needle was not found in haystack"); |
| 128 | + } |
| 129 | +} |
| 130 | + |
| 131 | +/// Assert that `haystack` does not contain `needle`. |
| 132 | +#[track_caller] |
| 133 | +pub fn assert_not_contains<H: AsRef<str>, N: AsRef<str>>(haystack: H, needle: N) { |
| 134 | + let haystack = haystack.as_ref(); |
| 135 | + let needle = needle.as_ref(); |
| 136 | + if haystack.contains(needle) { |
| 137 | + eprintln!("=== HAYSTACK ==="); |
| 138 | + eprintln!("{}", haystack); |
| 139 | + eprintln!("=== NEEDLE ==="); |
| 140 | + eprintln!("{}", needle); |
| 141 | + panic!("needle was unexpectedly found in haystack"); |
| 142 | + } |
| 143 | +} |
| 144 | + |
| 145 | +/// Assert that all files in `dir1` exist and have the same content in `dir2` |
| 146 | +pub fn assert_recursive_eq(dir1: impl AsRef<Path>, dir2: impl AsRef<Path>) { |
| 147 | + let dir2 = dir2.as_ref(); |
| 148 | + fs_helpers::read_dir(dir1, |entry_path| { |
| 149 | + let entry_name = entry_path.file_name().unwrap(); |
| 150 | + if entry_path.is_dir() { |
| 151 | + assert_recursive_eq(&entry_path, &dir2.join(entry_name)); |
| 152 | + } else { |
| 153 | + let path2 = dir2.join(entry_name); |
| 154 | + let file1 = fs_wrapper::read(&entry_path); |
| 155 | + let file2 = fs_wrapper::read(&path2); |
| 156 | + |
| 157 | + // We don't use `assert_eq!` because they are `Vec<u8>`, so not great for display. |
| 158 | + // Why not using String? Because there might be minified files or even potentially |
| 159 | + // binary ones, so that would display useless output. |
| 160 | + assert!( |
| 161 | + file1 == file2, |
| 162 | + "`{}` and `{}` have different content", |
| 163 | + entry_path.display(), |
| 164 | + path2.display(), |
| 165 | + ); |
| 166 | + } |
| 167 | + }); |
| 168 | +} |
0 commit comments