whitaker_common/test_support/
ui.rs1use crate::test_support::copy_fixture;
8use camino::Utf8Path;
9use glob::glob;
10use std::fs;
11use std::io;
12use std::path::{Path, PathBuf};
13use tempfile::{TempDir, tempdir};
14
15pub struct FixtureEnvironment {
17 _tempdir: TempDir,
18 workdir: PathBuf,
19 config: Option<String>,
20}
21
22impl FixtureEnvironment {
23 pub fn workdir(&self) -> &Path {
25 &self.workdir
26 }
27
28 pub fn take_config(&mut self) -> Option<String> {
30 self.config.take()
31 }
32}
33
34pub fn discover_fixtures(directory: &Utf8Path) -> io::Result<Vec<PathBuf>> {
36 let pattern = directory.join("*.rs").to_string();
37 let walker = glob(&pattern).map_err(|error| io::Error::other(error.to_string()))?;
38 let mut fixtures = Vec::new();
39
40 for entry in walker {
41 let path = entry.map_err(|error| io::Error::other(error.to_string()))?;
42 if path.is_file() {
43 fixtures.push(path);
44 }
45 }
46
47 Ok(fixtures)
48}
49
50pub fn run_fixtures_with<F>(
52 crate_name: &str,
53 directory: &Utf8Path,
54 mut runner: F,
55) -> Result<(), String>
56where
57 F: FnMut(&str, &Utf8Path, &Path) -> Result<(), String>,
58{
59 let mut fixtures = discover_fixtures(directory).map_err(|error| error.to_string())?;
60 fixtures.sort();
61
62 for source in fixtures {
63 runner(crate_name, directory, &source)?;
64 }
65
66 Ok(())
67}
68
69pub fn prepare_fixture(directory: &Utf8Path, source: &Path) -> io::Result<FixtureEnvironment> {
71 let tempdir = tempdir()?;
72 copy_fixture(directory.as_std_path(), source, tempdir.path())?;
73 let config = resolve_fixture_config(directory, source)?;
74 Ok(FixtureEnvironment {
75 workdir: tempdir.path().to_path_buf(),
76 _tempdir: tempdir,
77 config,
78 })
79}
80
81pub fn run_test_runner<F>(fixture_name: &str, runner: F) -> Result<(), String>
83where
84 F: FnOnce(),
85{
86 std::panic::catch_unwind(std::panic::AssertUnwindSafe(runner)).map_err(|payload| match payload
87 .downcast::<String>(
88 ) {
89 Ok(message) => format!("{fixture_name}: {message}"),
90 Err(payload) => match payload.downcast::<&'static str>() {
91 Ok(message) => format!("{fixture_name}: {message}"),
92 Err(_) => format!("{fixture_name}: dylint UI tests panicked without a message"),
93 },
94 })
95}
96
97pub fn resolve_fixture_config(directory: &Utf8Path, source: &Path) -> io::Result<Option<String>> {
99 if let Some(config) = read_fixture_config(source)? {
100 Ok(Some(config))
101 } else {
102 read_directory_config(directory)
103 }
104}
105
106pub fn read_fixture_config(source: &Path) -> io::Result<Option<String>> {
108 let stem = source
109 .file_stem()
110 .and_then(|value| value.to_str())
111 .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "fixture missing name"))?;
112 let config_path = source.with_file_name(format!("{stem}.dylint.toml"));
113
114 if config_path.exists() {
115 fs::read_to_string(config_path).map(Some)
116 } else {
117 Ok(None)
118 }
119}
120
121pub fn read_directory_config(directory: &Utf8Path) -> io::Result<Option<String>> {
123 let path = directory.as_std_path().join("dylint.toml");
124 if path.exists() {
125 fs::read_to_string(path).map(Some)
126 } else {
127 Ok(None)
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use super::*;
134 use camino::Utf8PathBuf;
135 use std::fs;
136
137 fn utf8_path(buf: &Path) -> Utf8PathBuf {
138 Utf8PathBuf::from_path_buf(buf.to_path_buf()).expect("utf8 path")
139 }
140
141 #[test]
142 fn run_fixtures_sorts_and_runs_all_cases() {
143 let dir = tempdir().expect("fixture directory");
144 fs::write(dir.path().join("b.rs"), "fn main() {}").expect("write first fixture");
145 fs::write(dir.path().join("a.rs"), "fn main() {}").expect("write second fixture");
146 let directory = utf8_path(dir.path());
147 let mut visited = Vec::new();
148
149 run_fixtures_with("crate", &directory, |_, _, source| {
150 let name = source
151 .file_name()
152 .and_then(|value| value.to_str())
153 .ok_or_else(|| "utf8 file name".to_string())?
154 .to_owned();
155 visited.push(name);
156 Ok(())
157 })
158 .expect("fixtures run");
159
160 assert_eq!(visited, vec!["a.rs".to_string(), "b.rs".to_string()]);
161 }
162
163 #[test]
164 fn discover_fixtures_filters_rust_files() {
165 let dir = tempdir().expect("fixture directory");
166 fs::write(dir.path().join("first.rs"), "").expect("first fixture");
167 fs::write(dir.path().join("second.txt"), "").expect("second fixture");
168 let directory = utf8_path(dir.path());
169
170 let mut fixtures = discover_fixtures(&directory).expect("discover fixtures");
171 fixtures.sort();
172
173 assert_eq!(fixtures.len(), 1);
174 assert!(fixtures[0].ends_with("first.rs"));
175 }
176
177 #[test]
178 fn discover_fixtures_returns_empty_directory() {
179 let dir = tempdir().expect("fixture directory");
180 let directory = utf8_path(dir.path());
181
182 let fixtures = discover_fixtures(&directory).expect("discover fixtures");
183
184 assert!(fixtures.is_empty());
185 }
186
187 #[test]
188 fn read_fixture_config_loads_optional_file() {
189 let dir = tempdir().expect("fixture directory");
190 let fixture = dir.path().join("case.rs");
191 fs::write(&fixture, "").expect("fixture file");
192 let config = dir.path().join("case.dylint.toml");
193 fs::write(&config, "key = 1").expect("config file");
194
195 let contents = read_fixture_config(&fixture).expect("config contents");
196 assert_eq!(contents.as_deref(), Some("key = 1"));
197 }
198
199 #[test]
200 fn read_directory_config_loads_global_file() {
201 let dir = tempdir().expect("fixture directory");
202 let directory = utf8_path(dir.path());
203 fs::write(directory.as_std_path().join("dylint.toml"), "max_lines = 5")
204 .expect("global config");
205
206 let contents = read_directory_config(&directory).expect("config contents");
207 assert_eq!(contents.as_deref(), Some("max_lines = 5"));
208 }
209
210 #[test]
211 fn resolve_fixture_config_prefers_fixture_specific_file() {
212 let dir = tempdir().expect("fixture directory");
213 let directory = utf8_path(dir.path());
214 let fixture = directory.as_std_path().join("case.rs");
215 fs::write(&fixture, "").expect("fixture");
216 fs::write(
217 directory.as_std_path().join("case.dylint.toml"),
218 "fixture = true",
219 )
220 .expect("fixture config");
221 fs::write(directory.as_std_path().join("dylint.toml"), "global = true")
222 .expect("global config");
223
224 let contents = resolve_fixture_config(&directory, &fixture).expect("config contents");
225 assert_eq!(contents.as_deref(), Some("fixture = true"));
226 }
227}