| 1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
|
| 7 |
|
| 8 |
|
| 9 |
|
| 10 |
|
| 11 |
|
| 12 |
|
| 13 |
|
| 14 |
|
| 15 |
|
| 16 |
|
| 17 |
|
| 18 |
use std::fs; |
| 19 |
use std::path::{Path, PathBuf}; |
| 20 |
|
| 21 |
|
| 22 |
|
| 23 |
|
| 24 |
|
| 25 |
|
| 26 |
|
| 27 |
|
| 28 |
|
| 29 |
|
| 30 |
|
| 31 |
|
| 32 |
|
| 33 |
|
| 34 |
|
| 35 |
|
| 36 |
|
| 37 |
|
| 38 |
|
| 39 |
|
| 40 |
|
| 41 |
|
| 42 |
|
| 43 |
|
| 44 |
|
| 45 |
|
| 46 |
|
| 47 |
|
| 48 |
|
| 49 |
|
| 50 |
|
| 51 |
|
| 52 |
|
| 53 |
|
| 54 |
|
| 55 |
const LOOSE_STATUS_HIGH_WATER: usize = 6; |
| 56 |
|
| 57 |
|
| 58 |
|
| 59 |
|
| 60 |
|
| 61 |
|
| 62 |
|
| 63 |
const TEST_PREFIX_HIGH_WATER: usize = 176; |
| 64 |
|
| 65 |
const WORKFLOWS_DIR: &str = "tests/workflows"; |
| 66 |
|
| 67 |
|
| 68 |
|
| 69 |
const TESTS_DIR: &str = "tests"; |
| 70 |
|
| 71 |
#[test] |
| 72 |
fn loose_status_assertions_do_not_increase() { |
| 73 |
let mut per_file: Vec<(String, usize)> = Vec::new(); |
| 74 |
for path in rs_files(Path::new(TESTS_DIR)) { |
| 75 |
if file_name(&path) == "test_hygiene.rs" { |
| 76 |
continue; |
| 77 |
} |
| 78 |
let text = fs::read_to_string(&path).expect("read test module"); |
| 79 |
let n = text.matches(".status.is_success()").count() |
| 80 |
+ text.matches(".status.is_client_error()").count(); |
| 81 |
if n > 0 { |
| 82 |
per_file.push((file_name(&path), n)); |
| 83 |
} |
| 84 |
} |
| 85 |
let total: usize = per_file.iter().map(|(_, n)| n).sum(); |
| 86 |
|
| 87 |
if total > LOOSE_STATUS_HIGH_WATER { |
| 88 |
per_file.sort_by_key(|(_, n)| std::cmp::Reverse(*n)); |
| 89 |
panic!( |
| 90 |
"loose status assertions rose from {LOOSE_STATUS_HIGH_WATER} to {total}.\n\ |
| 91 |
Assert the exact code the handler contracts: assert_eq!(resp.status, 403).\n\ |
| 92 |
Heaviest files: {:?}", |
| 93 |
&per_file[..per_file.len().min(5)], |
| 94 |
); |
| 95 |
} |
| 96 |
assert_eq!( |
| 97 |
total, LOOSE_STATUS_HIGH_WATER, |
| 98 |
"loose status assertions fell to {total}. Lower LOOSE_STATUS_HIGH_WATER to {total}.", |
| 99 |
); |
| 100 |
} |
| 101 |
|
| 102 |
#[test] |
| 103 |
fn prefixed_test_names_do_not_increase() { |
| 104 |
let mut found: Vec<String> = Vec::new(); |
| 105 |
for dir in ["src", "tests"] { |
| 106 |
for path in rs_files(Path::new(dir)) { |
| 107 |
let text = fs::read_to_string(&path).expect("read rust file"); |
| 108 |
for line in text.lines() { |
| 109 |
let line = line.trim_start(); |
| 110 |
if line.starts_with("fn test_") || line.starts_with("async fn test_") { |
| 111 |
found.push(format!("{}: {line}", path.display())); |
| 112 |
} |
| 113 |
} |
| 114 |
} |
| 115 |
} |
| 116 |
let total = found.len(); |
| 117 |
|
| 118 |
assert!( |
| 119 |
total <= TEST_PREFIX_HIGH_WATER, |
| 120 |
"`test_`-prefixed test names rose from {TEST_PREFIX_HIGH_WATER} to {total}.\n\ |
| 121 |
Name the behavior instead: `returns_none_when_absent`, not `test_lookup`.\n\ |
| 122 |
Examples: {:?}", |
| 123 |
&found[..found.len().min(5)], |
| 124 |
); |
| 125 |
assert_eq!( |
| 126 |
total, TEST_PREFIX_HIGH_WATER, |
| 127 |
"`test_` prefixes fell to {total}. Lower TEST_PREFIX_HIGH_WATER to {total}.", |
| 128 |
); |
| 129 |
} |
| 130 |
|
| 131 |
|
| 132 |
|
| 133 |
|
| 134 |
|
| 135 |
|
| 136 |
|
| 137 |
|
| 138 |
|
| 139 |
|
| 140 |
|
| 141 |
|
| 142 |
|
| 143 |
|
| 144 |
|
| 145 |
|
| 146 |
|
| 147 |
|
| 148 |
|
| 149 |
|
| 150 |
|
| 151 |
|
| 152 |
|
| 153 |
|
| 154 |
|
| 155 |
#[test] |
| 156 |
fn every_workflow_module_has_a_doc_header() { |
| 157 |
let missing: Vec<String> = rs_files(Path::new(WORKFLOWS_DIR)) |
| 158 |
.into_iter() |
| 159 |
.filter(|p| file_name(p) != "mod.rs") |
| 160 |
.filter(|p| { |
| 161 |
let text = fs::read_to_string(p).expect("read workflow module"); |
| 162 |
!text.lines().next().is_some_and(|l| l.starts_with("//!")) |
| 163 |
}) |
| 164 |
.map(|p| file_name(&p)) |
| 165 |
.collect(); |
| 166 |
|
| 167 |
assert!( |
| 168 |
missing.is_empty(), |
| 169 |
"workflow modules without a `//!` header: {missing:?}\n\ |
| 170 |
Say what surface the module covers and what would break if it were deleted.", |
| 171 |
); |
| 172 |
} |
| 173 |
|
| 174 |
|
| 175 |
|
| 176 |
|
| 177 |
#[test] |
| 178 |
fn every_ignored_test_states_a_reason() { |
| 179 |
let mut bare: Vec<String> = Vec::new(); |
| 180 |
for dir in ["src", "tests"] { |
| 181 |
for path in rs_files(Path::new(dir)) { |
| 182 |
let text = fs::read_to_string(&path).expect("read rust file"); |
| 183 |
for (i, line) in text.lines().enumerate() { |
| 184 |
if line.trim() == "#[ignore]" { |
| 185 |
bare.push(format!("{}:{}", path.display(), i + 1)); |
| 186 |
} |
| 187 |
} |
| 188 |
} |
| 189 |
} |
| 190 |
assert!( |
| 191 |
bare.is_empty(), |
| 192 |
"`#[ignore]` with no reason string: {bare:?}\n\ |
| 193 |
Write `#[ignore = \"why, and how to run it\"]`.", |
| 194 |
); |
| 195 |
} |
| 196 |
|
| 197 |
|
| 198 |
fn rs_files(dir: &Path) -> Vec<PathBuf> { |
| 199 |
let mut out = Vec::new(); |
| 200 |
let Ok(entries) = fs::read_dir(dir) else { |
| 201 |
return out; |
| 202 |
}; |
| 203 |
for entry in entries.flatten() { |
| 204 |
let path = entry.path(); |
| 205 |
if path.is_dir() { |
| 206 |
out.extend(rs_files(&path)); |
| 207 |
} else if path.extension().is_some_and(|e| e == "rs") { |
| 208 |
out.push(path); |
| 209 |
} |
| 210 |
} |
| 211 |
out.sort(); |
| 212 |
out |
| 213 |
} |
| 214 |
|
| 215 |
fn file_name(path: &Path) -> String { |
| 216 |
path.file_name() |
| 217 |
.expect("workflow path has a file name") |
| 218 |
.to_string_lossy() |
| 219 |
.into_owned() |
| 220 |
} |
| 221 |
|