diff --git a/Cargo.lock b/Cargo.lock index a646b96..308cc5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + [[package]] name = "anstream" version = "0.6.7" @@ -50,6 +59,18 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "clap" version = "4.4.16" @@ -102,6 +123,107 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-timer" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "hashbrown" version = "0.14.3" @@ -130,6 +252,24 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "proc-macro2" version = "1.0.76" @@ -148,12 +288,91 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "regex" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "relative-path" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e898588f33fdd5b9420719948f9f2a32c922a246964576f71ba7f24f80610fbc" + +[[package]] +name = "rstest" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97eeab2f3c0a199bc4be135c36c924b6590b88c377d416494288c14f2db30199" +dependencies = [ + "futures", + "futures-timer", + "rstest_macros", + "rustc_version", +] + +[[package]] +name = "rstest_macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d428f8247852f894ee1be110b375111b586d4fa431f6c46e64ba5a0dcccbe605" +dependencies = [ + "cfg-if", + "glob", + "proc-macro2", + "quote", + "regex", + "relative-path", + "rustc_version", + "syn", + "unicode-ident", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + [[package]] name = "ryu" version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +[[package]] +name = "semver" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" + [[package]] name = "serde" version = "1.0.195" @@ -187,6 +406,15 @@ dependencies = [ "unsafe-libyaml", ] +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + [[package]] name = "strsim" version = "0.10.0" @@ -209,6 +437,7 @@ name = "tester" version = "0.1.0" dependencies = [ "clap", + "rstest", "serde", "serde_yaml", ] diff --git a/Cargo.toml b/Cargo.toml index b78d78d..79402b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,5 +7,6 @@ edition = "2021" [dependencies] clap = { version = "4.4.16", features = ["derive"] } +rstest = "0.18.2" serde = { version = "1.0.195", features = ["derive"] } serde_yaml = "0.9.30" diff --git a/README.md b/README.md index 680488e..b72023f 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ This is a (mostly) drop-in replacement for the previous tester written for CMPUT 415. Most of the options are specified in the [config file](#configuration-file), so the cli is minimal. We do specify some options: -``` rust +``` Usage: tester [OPTIONS] --config-file Commands: @@ -14,11 +14,11 @@ Commands: Options: -v, --verbosity... Sets the verbosity of the tester + -t, --threads Set the number of threads (only valid in run and grade mode) -c, --config-file Set the config file -g, --grading-conf Set the grading config file -h, --help Print help -V, --version Print version - ``` the verbosity defines the level of logging, you can play around with it (specify `-v[v]+` for more verbose logging). diff --git a/src/main.rs b/src/main.rs index d3e298f..dfeffe3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,8 +16,7 @@ fn main() { let threads = get_threads(cli.threads); let config = util::config::parse_config(cli.config_file); - //parse the config file - // let config = parse_config(cli.config_file); + assert!(threads > 0); match &cli.command { Command::Validate => validate::validate(verbosity, config), diff --git a/src/util.rs b/src/util.rs index ef68c36..8354b60 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1 +1,2 @@ pub mod config; +pub mod logger; diff --git a/src/util/config.rs b/src/util/config.rs index 1aa5457..c47742b 100644 --- a/src/util/config.rs +++ b/src/util/config.rs @@ -5,39 +5,39 @@ use serde_yaml::{self}; #[derive(Debug, Deserialize)] pub struct Config { - tested_executables: Vec, + pub tested_executables: Vec, - input_path: PathBuf, - output_path: PathBuf, - in_stream_path: PathBuf, + pub input_path: PathBuf, + pub output_path: PathBuf, + pub in_stream_path: PathBuf, - runtimes: Option>, + pub runtimes: Option>, - toolchains: Vec, + pub toolchains: Vec, } #[derive(Debug, Deserialize)] pub struct Team { - name: String, - executable: PathBuf, + pub name: String, + pub executable: PathBuf, } #[derive(Debug, Deserialize)] pub struct Toolchain { - name: String, - steps: Vec,} + pub name: String, + pub steps: Vec,} #[derive(Debug, Deserialize)] pub struct Step { - name: String, + pub name: String, - executable_path: Option, // if None then we use the current executable path - arguments: Vec, // special string $INPUT corresponds to previous step output - output: String, // the output file name + pub executable_path: Option, // if None then we use the current executable path + pub arguments: Vec, // special string $INPUT corresponds to previous step output + pub output: String, // the output file name - uses_runtime: Option, - uses_in_stream: Option, - allow_error: Option + pub uses_runtime: Option, + pub uses_in_stream: Option, + pub allow_error: Option } pub fn parse_config(path: PathBuf) -> Config { diff --git a/src/util/logger.rs b/src/util/logger.rs new file mode 100644 index 0000000..f89070d --- /dev/null +++ b/src/util/logger.rs @@ -0,0 +1,22 @@ +// prints output to the stderr if the verbosity is +// greater than or equal to the logging level of the +// notification. +#[macro_export] +macro_rules! log { + // NOTE: this uses a variadic argument system + // copied from the std::fmt eprintln! macro + // see the appropriate documentation + ($l:expr, $v:expr, $($arg:tt)*) => {{ + if $v >= $l { + eprintln!($($arg)*); + } + }}; +} + +#[cfg(test)] +mod tests { + #[test] + fn test_name() { + log!(1, 1, "this is a {}", "test"); + } +} diff --git a/src/util/test/parse_config/config.yaml b/src/util/test/parse_config/config.yaml index e166161..a9a36c7 100644 --- a/src/util/test/parse_config/config.yaml +++ b/src/util/test/parse_config/config.yaml @@ -1,6 +1,6 @@ -input_path: "/path/to/dir" -output_path: "/path/to/out/dir" -in_stream_path: "/path/to/in/stream" +input_path: "/test/in" +output_path: "/test/out" +in_stream_path: "/test/instream" tested_executables: - name: "team1" diff --git a/src/validate.rs b/src/validate.rs index fb88886..2594df4 100644 --- a/src/validate.rs +++ b/src/validate.rs @@ -1,11 +1,377 @@ +use std::{collections::VecDeque, fs}; +use std::path::{PathBuf, Component}; + use crate::util; +use crate::log; pub fn validate(verbosity: u8, config: util::config::Config) { println!("Validating"); + + let in_suffix = "in"; //TODO make this configurable? + let out_suffix = "out"; + log!(1, verbosity, "LOG 1: Input suffix: {} \nOutput suffix: {}", in_suffix, out_suffix); + // get list of all file names in input/output + log!(1, verbosity, "LOG 1:Reading the list of input and output files"); + let in_files: Vec = get_dir_files(&config.input_path, verbosity).expect("Failed to read the input directory contents"); + let out_files: Vec = get_dir_files(&config.output_path, verbosity).expect("Failed to read the output directory contents"); + + // check the files for valid suffixes + log!(1, verbosity, "LOG 1:Collecting files with invalid suffixes"); + let invalid_suffix_in: Vec = collect_invalid(&in_files, in_suffix, verbosity); + let invalid_suffix_out: Vec = collect_invalid(&out_files, out_suffix, verbosity); + + // print the delinquints + if invalid_suffix_in.len() > 0 { + print_invalid_suffixes(invalid_suffix_in, in_suffix); + } + + if invalid_suffix_out.len() > 0 { + print_invalid_suffixes(invalid_suffix_out, out_suffix); + } + + // make sure each has a match + log!(1, verbosity, "LOG 1:Checking if valid mappings exist between input and output files"); + let in_to_out: (Vec<(PathBuf, PathBuf)>, Vec) = map_files(in_files, out_files, &config, verbosity); + log!(1, verbosity, "LOG 1:Verifying file matches"); + let invalid: Vec<(PathBuf, PathBuf)> = check_mappings(in_to_out.0, in_suffix, out_suffix, verbosity); + + print_unmached(in_to_out.1); + print_invalid_pairs(invalid, in_suffix, out_suffix); +} + +/** + * @brief Maps input files to output files based on their stem + * + * @param in_files: The test input files + * @param out_files: The test output files + * + * @return A tuple of two elements: + * - A vector containing the file mappings from input to output (if found) + * - A vector containing paths for which a match was not found +*/ +fn map_files(in_files: Vec, out_files: Vec, config: &util::config::Config, verbosity: u8) -> (Vec<(PathBuf, PathBuf)>, Vec) { + let mut res: (Vec<(PathBuf, PathBuf)>, Vec) = (vec![], vec![]); + + // get the prefixes + let mut in_prefix = vec![]; + for pre in config.input_path.components() { + in_prefix.push(pre); + } + log!(2, verbosity, "LOG 2: Input prefixes found: {:?}", in_prefix); + + let mut out_prefix = vec![]; + for pre in config.output_path.components() { + out_prefix.push(pre); + } + log!(2, verbosity, "LOG 2: Output prefixes found: {:?}", out_prefix); + + // check if the files have the same paths relative to in/out directory + for ifile in &in_files { + let mut found = false; + + // get the components of the path + let mut in_comp: VecDeque = VecDeque::from(vec![]); + for tmp in ifile.components() { + in_comp.push_back(tmp); + } + in_comp.pop_back(); + + // check if the files share common ancestors except for the first path + for ofile in &out_files { + + // get the components of the path + let mut out_comp: VecDeque = VecDeque::from(vec![]); + for tmp in ofile.components() { + out_comp.push_back(tmp); + } + out_comp.pop_back(); + // println!("{:?}", ofile); + // println!("{:?}", out_comp); + + log!(2, verbosity, "LOG 2: Checking mapping of {} to {}", + &ifile.to_str().expect("failed to parse string"), + &ofile.to_str().expect("failed to parse string")); + // check if the file names are the same + if ofile.file_stem() == ifile.file_stem() { + // check that their paths are the same relative to the input/output directory + for comp in &in_prefix { if &comp == &in_comp.front().expect("Failed to extract") { in_comp.pop_front(); } } + for comp in &out_prefix { if &comp == &out_comp.front().expect("Failed to extract") { out_comp.pop_front(); } } + // println!("{:?}\n{:?}\n{:?}", in_prefix, in_comp,out_comp); + + // if the files match, then we add them to the mapping + if out_comp == in_comp { + log!(1, verbosity, "LOG 1: Adding the mapping between {} and {} to the list of file mappings.", + ifile.to_str().expect("failed to parse string"), + ofile.to_str().expect("failed to parse string")); + res.0.push((ifile.clone(), ofile.clone())); + found = true; + } + } + } + + // if we fail to find a match, we add the file to the rest + if !found { + log!(1, verbosity, "LOG 1: Adding {} to the list of unmapped files", ifile.to_str().expect("failed to parse string")); + res.1.push(ifile.clone()); } + } + + let mut cl: Vec = Vec::from(out_files.clone()); + let mut r: Vec = vec![]; + for tup in &res.0 { + r.push(tup.1.clone()); + } + + cl.retain(|x| !r.contains(&x)); + log!(1, verbosity, "LOG 1: Adding the following files to the list of unmapped files:\n{:?}", cl); + res.1.append(&mut cl); + // println!("{:?}", res); + return res; +} + +/** + * @brief Checks that a given mapping is valid, containing the correct suffixes + * + * @param mappings between two files + * @param the expected in_suffix + * @param the expected out_suffix + * + * @return A vector of tuples with (input, output) PathBufs +*/ +fn check_mappings(mappings: Vec<(PathBuf, PathBuf)>, in_suffix: &str, out_suffix: &str, verbosity: u8) -> Vec<(PathBuf, PathBuf)> { + let mut res: Vec<(PathBuf, PathBuf)> = vec![]; + + for tup in &mappings { + log!(2, verbosity, "LOG 2: Verifying the mapping {:?}", tup); + if !(tup.0.as_path().extension().unwrap() == in_suffix) || !(tup.1.as_path().extension().unwrap() == out_suffix) { + res.push(tup.clone()); + log!(2, verbosity, "LOG 2: Added {:?} to the list of invalid mappings", tup); + } + } + // println!("{:?}", res); + return res; +} + +/** + * @brief Collects paths with invalid suffixes + * + * @param files: A vector of PathBuf containing file names + * @param suf: The expected suffix for the files + * + * @return A vector of paths with invalid suffixes +*/ +fn collect_invalid(files: &Vec, suf: &str, verbosity: u8) -> Vec { + let mut res: Vec = vec![]; + + for file in files { + log!(2, verbosity, "LOG 2: Checking the validity of {} against the suffix {}:\n$${}$$\n$${}$$", + file.to_str().expect("failed to parse string"), + suf, + file.extension().expect("Failed to extract suffix").to_str().expect("Failed to parse string"), + suf); + + let extension = file.extension().expect("Invalid file name for validation"); + if extension.to_str() != Some(suf) { + res.push(file.clone()); + log!(2, verbosity, "LOG 2: Added {} to invalid files", file.to_str().expect("failed to parse string")); + } + } + // println!("{:?}", res); + return res +} +/** + * @brief Get all file names in a directory + * + * @param path: the path to the target directory + * + * @returns Result with a Vector of PathBuf or error +*/ +fn get_dir_files(path: &PathBuf, verbosity: u8) -> Result, &'static str> { + let mut res: Vec = vec![]; + + log!(2, verbosity, "LOG 2: Checking the directory {} for files", path.to_str().expect("failed to parse string")); + // get the readout of the path for error handling + let entries = match fs::read_dir(path) { + Ok(x) => x, + Err(..) => return Err("Failed to read from the given directory"), + }; + + for entry in entries { + let p = entry.expect("Bad element: validate::get_dir_files(..)"); + if p.path().is_dir() { + (); + } else { + log!(2, verbosity, "LOG 2: Found file {}", stringify!(p)); + res.push(p.path()); + } + } + + return Ok(res); +} + +/** + * @brief Print utility for the invalid paths + * + * @param files: A vector containing the invalid path names + * @param exp: The expected suffix +*/ +fn print_invalid_suffixes(files: Vec, exp: &str) { + println!("Invalid suffixes detected 😱:" ); + println!("Current => Suggested"); + for file in files { + let initial = match file.to_str() { + Some(x) => x, + _ => panic!("Failed to load the file"), + }; + + println!("{} => {}.{}", + initial.to_string(), + file.file_stem().expect("failed to extract") + .to_str().expect("failed to transform"), + exp); + } +} + +/** + * @brief Utility function to pring unmatched files + * + * @param unmatched: Unmatched files +*/ +fn print_unmached(unmatched: Vec) { + println!("We were unable to find matches for the following files:"); + + for file in unmatched { + println!("{}", file.to_str().expect("Failed to read file name")); + } +} + +/** + * @brief Utility function to print invalid pairs and suggest a fix + * + * @param invalid: A vector of invalid pairs +*/ +fn print_invalid_pairs(invalid: Vec<(PathBuf, PathBuf)>, in_suffix: &str, out_suffix: &str) { + println!("The following files have problematic mappings, please validate their suffixes and paths:"); + + for pair in invalid { + println!("({} {}): $${}$$ => $${}$$ should be $${}$$ => $${}$$", + pair.0.to_str().expect("Failed to read file name"), + pair.1.to_str().expect("Failed to read file name"), + pair.0.extension().expect("Failed to read file name").to_str().expect("Failed to convert"), + pair.1.extension().expect("Failed to read file name").to_str().expect("Failed to convert"), + in_suffix, + out_suffix, + ); + } } #[cfg(test)] mod tests { + use rstest::{fixture, rstest}; + use super::*; + + #[fixture] + fn files() -> Vec { + let valid_path1 = PathBuf::from("path/file.suf"); + let valid_path2 = PathBuf::from("path/file_stupid.suf"); + let valid_path3 = PathBuf::from("path/file/dead.suf"); + let invalid_path1 = PathBuf::from("path/file.sub"); + let invalid_path2 = PathBuf::from("path/file_stupid.sur"); + let invalid_path3 = PathBuf::from("path/file.bub"); + + let files: Vec = vec![valid_path1.clone(), valid_path2.clone(), valid_path3.clone(), + invalid_path1.clone(), invalid_path2.clone(), invalid_path3.clone()]; + + return files; + } + + #[fixture] + fn in_files() -> Vec { + let invalid_map11 = PathBuf::from("/test/in/inv/path/file.in "); + let invalid_map12 = PathBuf::from("/test/in/inv/path/file_stupid.in"); + let invalid_map13 = PathBuf::from("/test/in/inv/path/dead.inn"); + + let valid_map11 = PathBuf::from("/test/in/val/path/file.in"); + let valid_map12 = PathBuf::from("/test/in/val/path/file_stupid.in"); + let valid_map13 = PathBuf::from("/test/in/val/path/file/dead.in"); + + let in_files = vec![invalid_map11, invalid_map12, invalid_map13, + valid_map11, valid_map12, valid_map13]; + + return in_files; + } + + #[fixture] + fn out_files() -> Vec { + let invalid_map21 = PathBuf::from("/test/out/inv/path/file.out"); + let invalid_map22 = PathBuf::from("/test/out/inv/path/file_stupid.out "); + let invalid_map23 = PathBuf::from("/test/out/inv/path/file/dead.outt"); + + let valid_map21 = PathBuf::from("/test/out/val/path/file.out"); + let valid_map22 = PathBuf::from("/test/out/val/path/file_stupid.out"); + let valid_map23 = PathBuf::from("/test/out/val/path/file/dead.out"); + + let out_files = vec![invalid_map21, invalid_map22, invalid_map23, + valid_map21, valid_map22, valid_map23]; + + return out_files; + } + + #[fixture] + fn config() -> util::config::Config { + return util::config::parse_config(PathBuf::from("src/util/test/parse_config/config.yaml")); + } + + #[rstest] + fn test_collect_invalid(files: Vec) { + let suffix = "suf"; + let invalid = collect_invalid(&files, suffix, 2); + + println!("{:?}", invalid); + + // visual testing for the print formatting + print_invalid_suffixes(invalid.clone(), suffix); + + assert!(!invalid.contains(&files[0])); + assert!(!invalid.contains(&files[1])); + assert!(!invalid.contains(&files[2])); + + assert!(invalid.contains(&files[3])); + assert!(invalid.contains(&files[4])); + assert!(invalid.contains(&files[5])); + } + + #[rstest] + fn test_map(in_files: Vec, out_files: Vec, config: util::config::Config) { + let mappings: (Vec<(PathBuf, PathBuf)>, Vec) = map_files(in_files.clone(), out_files.clone(), &config, 2); + + // print_unmached(mappings.1); + let test = (in_files[0].clone(), out_files[0].clone()); + assert!(&mappings.0.contains(&test), "The mapping does not contain {:?}", test); + assert!(&mappings.0.contains(&(in_files[1].clone(), out_files[1].clone()))); + assert!(!&mappings.0.contains(&(in_files[2].clone(), out_files[2].clone()))); + assert!(&mappings.0.contains(&(in_files[3].clone(), out_files[3].clone()))); + assert!(&mappings.0.contains(&(in_files[4].clone(), out_files[4].clone()))); + assert!(&mappings.0.contains(&(in_files[5].clone(), out_files[5].clone()))); + + assert!(&mappings.1.contains(&in_files[2])); + assert!(&mappings.1.contains(&out_files[2])); + } + + #[rstest] + fn test_check_valid_mappings(in_files: Vec, out_files: Vec, config: util::config::Config) { + let in_suffix = "in"; + let out_suffix = "out"; + let invalid: Vec<(PathBuf, PathBuf)> = check_mappings(map_files(in_files.clone(), out_files.clone(), &config, 2).0, in_suffix, out_suffix, 2); + + print_invalid_pairs(invalid.clone(), in_suffix, out_suffix); + assert!(&invalid.contains(&(in_files[0].clone(), out_files[0].clone()))); + assert!(&invalid.contains(&(in_files[1].clone(), out_files[1].clone()))); + assert!(!&invalid.contains(&(in_files[2].clone(), out_files[2].clone()))); + + assert!(!&invalid.contains(&(in_files[3].clone(), out_files[3].clone()))); + assert!(!&invalid.contains(&(in_files[4].clone(), out_files[4].clone()))); + assert!(!&invalid.contains(&(in_files[5].clone(), out_files[5].clone()))); + } }