1use std::path::PathBuf;
4
5use alpm_buildinfo::cli::ValidateArgs;
6use anyhow::{Context, Result};
7use colored::Colorize;
8use log::{debug, info};
9use rayon::iter::{IntoParallelIterator, ParallelIterator};
10
11use crate::{cli::TestFileType, sync::PackageRepositories, ui::get_progress_bar};
12
13static PKGSRC_DIR: &str = "pkgsrc";
14static AUR_DIR: &str = "aur";
15static PACKAGES_DIR: &str = "packages";
16static DATABASES_DIR: &str = "databases";
17
18#[derive(Clone, Debug)]
20pub struct TestRunner {
21 pub test_data_dir: PathBuf,
23 pub file_type: TestFileType,
25 pub repositories: Vec<PackageRepositories>,
27}
28
29impl TestRunner {
30 pub fn run_tests(&self) -> Result<()> {
33 let test_files = self.find_files_of_type().context(format!(
34 "Failed to detect files for type {}",
35 self.file_type
36 ))?;
37 info!(
38 "Found {} {} files for testing",
39 test_files.len(),
40 self.file_type
41 );
42
43 let progress_bar = get_progress_bar(test_files.len() as u64);
44
45 let asserts: Vec<(PathBuf, Result<()>)> = test_files
47 .into_par_iter()
48 .map(|file| {
49 let result = match self.file_type {
50 TestFileType::BuildInfo => alpm_buildinfo::commands::validate(ValidateArgs {
51 file: Some(file.clone()),
52 schema: None,
53 })
54 .map_err(|err| err.into()),
55 TestFileType::SrcInfo => alpm_srcinfo::commands::validate(Some(&file), None)
56 .map_err(|err| err.into()),
57 TestFileType::MTree => {
58 alpm_mtree::commands::validate(Some(&file), None).map_err(|err| err.into())
59 }
60 TestFileType::PackageInfo => {
61 alpm_pkginfo::commands::validate(Some(file.clone()), None)
62 .map_err(|err| err.into())
63 }
64 TestFileType::RemoteDesc => unimplemented!(),
65 TestFileType::RemoteFiles => unimplemented!(),
66 TestFileType::LocalDesc => unimplemented!(),
67 TestFileType::LocalFiles => unimplemented!(),
68 };
69
70 progress_bar.inc(1);
71 (file, result)
72 })
73 .collect();
74
75 progress_bar.finish_with_message("Validation run finished.");
77
78 let failures: Vec<(PathBuf, anyhow::Error)> = asserts
80 .into_iter()
81 .filter_map(|(path, result)| {
82 if let Err(err) = result {
83 Some((path, err))
84 } else {
85 None
86 }
87 })
88 .collect();
89
90 if !failures.is_empty() {
91 for (index, failure) in failures.into_iter().enumerate() {
92 let index = format!("[{index}]").bold().red();
93 info!(
94 "{index} {} with error:\n {}\n",
95 failure.0.to_string_lossy().bold(),
96 failure.1
97 );
98 }
99 }
100
101 Ok(())
102 }
103
104 pub fn find_files_of_type(&self) -> Result<Vec<PathBuf>> {
108 let mut files = Vec::new();
109
110 let type_folders = match self.file_type {
112 TestFileType::BuildInfo | TestFileType::PackageInfo | TestFileType::MTree => self
115 .repositories
116 .iter()
117 .map(|repo| self.test_data_dir.join(PACKAGES_DIR).join(repo.to_string()))
118 .collect(),
119 TestFileType::SrcInfo => vec![
120 self.test_data_dir.join(PKGSRC_DIR),
121 self.test_data_dir.join(AUR_DIR),
122 ],
123 TestFileType::RemoteDesc | TestFileType::RemoteFiles => self
126 .repositories
127 .iter()
128 .map(|repo| {
129 self.test_data_dir
130 .join(DATABASES_DIR)
131 .join(repo.to_string())
132 })
133 .collect(),
134 TestFileType::LocalDesc | TestFileType::LocalFiles => {
135 unimplemented!();
136 }
137 };
138 for folder in type_folders {
139 debug!("Looking for files in {folder:?}");
140 if !folder.exists() {
141 info!("The directory {folder:?} doesn't exist, skipping.");
142 continue;
143 }
144 for pkg_folder in
148 std::fs::read_dir(&folder).context(format!("Failed to read folder {folder:?}"))?
149 {
150 let pkg_folder = pkg_folder?;
151 let file_path = pkg_folder.path().join(self.file_type.to_string());
152 if file_path.exists() {
153 files.push(file_path);
154 }
155 }
156 }
157
158 Ok(files)
159 }
160}
161
162#[cfg(test)]
163mod tests {
164 use std::{
165 collections::HashSet,
166 fs::{OpenOptions, create_dir},
167 };
168
169 use rstest::rstest;
170 use strum::IntoEnumIterator;
171
172 use super::*;
173
174 const PKG_NAMES: &[&str] = &[
175 "pipewire-alsa-1:1.0.7-1-x86_64",
176 "xorg-xvinfo-1.1.5-1-x86_64",
177 "acl-2.3.2-1-x86_64",
178 "archlinux-keyring-20240520-1-any",
179 ];
180
181 #[rstest]
185 #[case(TestFileType::BuildInfo)]
186 #[case(TestFileType::PackageInfo)]
187 #[case(TestFileType::MTree)]
188 fn test_find_files_for_packages(#[case] file_type: TestFileType) -> Result<()> {
189 let tmp_dir = tempfile::tempdir()?;
191 let packages_dir = tmp_dir.path().join(PACKAGES_DIR);
192 create_dir(&packages_dir)?;
193
194 let mut expected_files = HashSet::new();
196
197 for (index, repo) in PackageRepositories::iter().enumerate() {
199 let repo_dir = packages_dir.join(repo.to_string());
201 create_dir(&repo_dir)?;
202
203 let pkg = PKG_NAMES[index];
205 let pkg_dir = repo_dir.join(pkg);
206 create_dir(&pkg_dir)?;
207
208 let file_path = pkg_dir.join(file_type.to_string());
210 OpenOptions::new()
211 .create(true)
212 .write(true)
213 .truncate(true)
214 .open(&file_path)?;
215 expected_files.insert(file_path);
216 }
217
218 let runner = TestRunner {
220 test_data_dir: tmp_dir.path().to_owned(),
221 file_type,
222 repositories: PackageRepositories::iter().collect(),
223 };
224 let found_files = HashSet::from_iter(runner.find_files_of_type()?.into_iter());
225
226 assert_eq!(
227 found_files, expected_files,
228 "Expected that all created package files are also found."
229 );
230
231 Ok(())
232 }
233
234 #[rstest]
238 #[case(TestFileType::RemoteFiles)]
239 #[case(TestFileType::RemoteDesc)]
240 fn test_find_files_for_databases(#[case] file_type: TestFileType) -> Result<()> {
241 let tmp_dir = tempfile::tempdir()?;
243 let databases_dir = tmp_dir.path().join(DATABASES_DIR);
244 create_dir(&databases_dir)?;
245
246 let mut expected_files = HashSet::new();
248
249 for (index, repo) in PackageRepositories::iter().enumerate() {
251 let repo_dir = databases_dir.join(repo.to_string());
253 create_dir(&repo_dir)?;
254
255 let pkg = PKG_NAMES[index];
257 let pkg_dir = repo_dir.join(pkg);
258 create_dir(&pkg_dir)?;
259
260 let file_path = pkg_dir.join(file_type.to_string());
262 OpenOptions::new()
263 .create(true)
264 .write(true)
265 .truncate(true)
266 .open(&file_path)?;
267 expected_files.insert(file_path);
268 }
269
270 let runner = TestRunner {
272 test_data_dir: tmp_dir.path().to_owned(),
273 file_type,
274 repositories: PackageRepositories::iter().collect(),
275 };
276 let found_files = HashSet::from_iter(runner.find_files_of_type()?.into_iter());
277
278 assert_eq!(
279 found_files, expected_files,
280 "Expected that all created databases files are also found."
281 );
282
283 Ok(())
284 }
285
286 #[rstest]
290 #[case(TestFileType::SrcInfo)]
291 fn test_find_files_for_pkgsrc(#[case] file_type: TestFileType) -> Result<()> {
292 let tmp_dir = tempfile::tempdir()?;
294 let pkgsrc_dir = tmp_dir.path().join(PKGSRC_DIR);
295 create_dir(&pkgsrc_dir)?;
296
297 let mut expected_files = HashSet::new();
299
300 for pkg in PKG_NAMES {
303 let pkg_dir = pkgsrc_dir.join(pkg);
304 create_dir(&pkg_dir)?;
305
306 let file_path = pkg_dir.join(file_type.to_string());
308 OpenOptions::new()
309 .create(true)
310 .write(true)
311 .truncate(true)
312 .open(&file_path)?;
313 expected_files.insert(file_path);
314 }
315
316 let runner = TestRunner {
318 test_data_dir: tmp_dir.path().to_owned(),
319 file_type,
320 repositories: PackageRepositories::iter().collect(),
321 };
322 let found_files = HashSet::from_iter(runner.find_files_of_type()?.into_iter());
323
324 assert_eq!(
325 found_files, expected_files,
326 "Expected that all created pkgsrc files are also found."
327 );
328
329 Ok(())
330 }
331}