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