use std::{
ffi::OsString,
fs::File,
io::{BufRead, BufReader, Write},
path::{Path, PathBuf},
};
pub const FEATURE_PRETTY_ERRORS: bool = cfg!(feature = "pretty_errors");
pub const FEATURE_MODULE_DISAMBIGUATION: bool = cfg!(feature = "module_disambiguation");
pub const NR_FEATURES: usize =
0 + FEATURE_PRETTY_ERRORS as usize + FEATURE_MODULE_DISAMBIGUATION as usize;
const FEATURES: [&'static str; NR_FEATURES] = get_features();
const fn get_features() -> [&'static str; NR_FEATURES]
{
#[allow(unused_mut)]
let mut features: [&'static str; NR_FEATURES] = [""; NR_FEATURES];
#[cfg(feature = "pretty_errors")]
{
features[0] = "pretty_errors";
}
#[cfg(feature = "module_disambiguation")]
{
features[FEATURE_PRETTY_ERRORS as usize] = "module_disambiguation";
}
features
}
pub struct ExpansionTester<'a>
{
dir: &'a str,
testing_dir: &'a str,
source_dirs: Vec<(&'a str, Vec<Box<dyn Fn(&Path, &dyn AsRef<Path>)>>)>,
error_tests: bool,
}
impl<'a> ExpansionTester<'a>
{
pub fn new(home_dir: &'a str, testing_dir: &'a str) -> Self
{
Self {
dir: home_dir,
testing_dir,
source_dirs: Vec::new(),
error_tests: false,
}
}
pub fn new_errors(home_dir: &'a str, testing_dir: &'a str) -> Self
{
Self {
dir: home_dir,
testing_dir,
source_dirs: Vec::new(),
error_tests: true,
}
}
pub fn add_source_dir(
&mut self,
dir: &'a str,
actions: Vec<Box<dyn Fn(&Path, &dyn AsRef<Path>)>>,
)
{
self.source_dirs.push((dir, actions));
}
pub fn execute_tests(&self)
{
let testing_dir = self.dir.to_owned() + "/" + self.testing_dir;
let _ = std::fs::remove_dir_all(&testing_dir);
std::fs::create_dir_all(&testing_dir).unwrap();
for (source_dir, actions) in self.source_dirs.iter()
{
let source_dir_path = self.dir.to_owned() + "/" + source_dir;
if let Ok(files) = std::fs::read_dir(&source_dir_path)
{
for file in files
{
if let Ok(file) = file
{
for action in actions.iter()
{
action(&file.path(), &testing_dir);
}
}
else
{
panic!("Error accessing source file: {:?}", file)
}
}
}
}
let mut args = Vec::new();
let mut features = String::new();
args.push("--no-default-features");
if NR_FEATURES > 0
{
args.push("--features");
for f in FEATURES.iter()
{
features.push_str(f);
features.push(',');
}
args.push(features.as_str());
}
if self.error_tests
{
duplicate_macrotest::expand_without_refresh_args_fail(
testing_dir + "/*.rs",
args.as_slice(),
);
}
else
{
duplicate_macrotest::expand_without_refresh_args(
testing_dir + "/*.rs",
args.as_slice(),
);
}
}
pub fn copy_with_prefix(prefix: &str) -> Box<dyn Fn(&Path, &dyn AsRef<Path>)>
{
Self::copy_with_prefix_postfix(prefix, "")
}
pub fn copy_with_prefix_postfix(
prefix: &str,
postfix: &str,
) -> Box<dyn Fn(&Path, &dyn AsRef<Path>)>
{
let prefix = OsString::from(prefix);
let postfix = OsString::from(postfix);
Box::new(move |file, destination| {
let mut destination_file = destination.as_ref().to_path_buf();
let mut file_name = prefix.clone();
file_name.push(file.file_name().unwrap());
file_name.push(postfix.clone());
destination_file.push(file_name);
std::fs::copy(file, &destination_file).unwrap();
})
}
pub fn copy() -> Box<dyn Fn(&Path, &dyn AsRef<Path>)>
{
Self::copy_with_prefix("")
}
pub fn duplicate_for_inline() -> Box<dyn Fn(&Path, &dyn AsRef<Path>)>
{
Self::duplicate_for_inline_with_prefix("")
}
pub fn duplicate_for_inline_with_prefix(
prefix: &str,
) -> Box<dyn '_ + Fn(&Path, &dyn AsRef<Path>)>
{
Box::new(move |file, destination| {
let mut inline_file_name = OsString::from("inline_");
inline_file_name.push(prefix);
inline_file_name.push(file.file_name().unwrap());
let mut new_file_name = OsString::from(prefix);
new_file_name.push(file.file_name().unwrap());
let mut dest_file_path = destination.as_ref().to_path_buf();
let mut dest_inline_file_path = destination.as_ref().to_path_buf();
dest_file_path.push(new_file_name);
dest_inline_file_path.push(inline_file_name);
let mut dest_file = File::create(dest_file_path).unwrap();
let mut dest_inline_file = File::create(dest_inline_file_path).unwrap();
for line in BufReader::new(File::open(file).unwrap()).lines()
{
let line = line.unwrap();
let line = line.trim();
match line
{
"#[duplicate_item(" =>
{
dest_file.write_all("#[duplicate_item(".as_bytes()).unwrap();
dest_inline_file
.write_all("duplicate!{[".as_bytes())
.unwrap();
},
"#[substitute_item(" =>
{
dest_file
.write_all("#[substitute_item(".as_bytes())
.unwrap();
dest_inline_file
.write_all("substitute!{[".as_bytes())
.unwrap();
},
")]//duplicate_end" =>
{
dest_file.write_all(")]".as_bytes()).unwrap();
dest_inline_file.write_all("]".as_bytes()).unwrap();
},
"//item_end" =>
{
dest_inline_file.write_all("}".as_bytes()).unwrap();
},
_ =>
{
dest_file.write_all(line.as_bytes()).unwrap();
dest_inline_file.write_all(line.as_bytes()).unwrap();
},
}
dest_file.write_all("\n".as_bytes()).unwrap();
dest_inline_file.write_all("\n".as_bytes()).unwrap();
}
})
}
pub fn run_default_test_setup(home_dir: &str, test_subdir: &str)
{
Self::run_default_test_setup_errors(home_dir, test_subdir, false)
}
pub fn run_default_test_setup_errors(home_dir: &str, test_subdir: &str, test_errors: bool)
{
let mut test = if test_errors
{
ExpansionTester::new_errors(home_dir, test_subdir)
}
else
{
ExpansionTester::new(home_dir, test_subdir)
};
test.add_source_dir("from", vec![ExpansionTester::duplicate_for_inline()]);
test.add_source_dir(
"expected",
vec![
ExpansionTester::copy(),
ExpansionTester::copy_with_prefix("inline_"),
],
);
test.add_source_dir(
"expected_both",
vec![
ExpansionTester::copy_with_prefix("inline_short_"),
ExpansionTester::copy_with_prefix("inline_verbose_"),
ExpansionTester::copy_with_prefix("short_"),
ExpansionTester::copy_with_prefix("verbose_"),
],
);
test.execute_tests();
}
}
pub fn run_basic_expansion_error_tests(path: &str)
{
let mut tester = ExpansionTester::new_errors(path, "testing_basic");
tester.add_source_dir(
"basic",
vec![
ExpansionTester::copy_with_prefix_postfix("basic_", ".expanded.rs"),
ExpansionTester::copy_with_prefix_postfix("inline_basic_", ".expanded.rs"),
],
);
tester.add_source_dir(
"source",
vec![ExpansionTester::duplicate_for_inline_with_prefix("basic_")],
);
tester.execute_tests();
}
#[cfg_attr(not(feature = "pretty_errors"), allow(dead_code))]
pub fn get_source(prefix: &str) -> Box<dyn '_ + Fn(&Path, &dyn AsRef<Path>)>
{
Box::new(move |file, destination| {
let mut source_file_name = std::ffi::OsString::from(file.file_name().unwrap());
source_file_name.push(".rs");
let mut source_file_path = PathBuf::from(file.parent().unwrap().parent().unwrap());
source_file_path.push("source");
source_file_path.push(source_file_name);
assert!(
source_file_path.exists(),
"Missing file: {:?}",
source_file_path.as_os_str()
);
ExpansionTester::duplicate_for_inline_with_prefix(prefix)(&source_file_path, destination);
})
}
#[cfg_attr(not(feature = "pretty_errors"), allow(unused_variables))]
pub fn run_error_highlight_tests(path: &str)
{
#[cfg(feature = "pretty_errors")]
{
let mut tester = ExpansionTester::new_errors(path, "testing_highlight");
tester.add_source_dir(
"highlight",
vec![
ExpansionTester::copy_with_prefix_postfix("highlight_", ".expanded.rs"),
ExpansionTester::copy_with_prefix_postfix("inline_highlight_", ".expanded.rs"),
get_source("highlight_"),
],
);
tester.execute_tests();
}
}
#[cfg_attr(not(feature = "pretty_errors"), allow(unused_variables))]
pub fn run_error_hint_tests(path: &str)
{
#[cfg(feature = "pretty_errors")]
{
let mut tester = ExpansionTester::new_errors(path, "testing_hint");
tester.add_source_dir(
"hint",
vec![
ExpansionTester::copy_with_prefix_postfix("hint_", ".expanded.rs"),
ExpansionTester::copy_with_prefix_postfix("inline_hint_", ".expanded.rs"),
get_source("hint_"),
],
);
tester.execute_tests();
}
}