use std::path::PathBuf;
use std::process::Command;
use tempdir::TempDir;
use toml::{map::Map, Value};
use crate::{Error, Expander, ExpansionOutcome, Result, Test};
use crate::message::{message_different, message_expansion_error};
use std::env;
impl Expander {
pub fn expand(&mut self) {
let tests = expand_globs(&self.tests)
.into_iter()
.filter(|t| !t.test.path.to_string_lossy().ends_with(".expanded.rs"))
.collect::<Vec<_>>();
let len = tests.len();
println!("Running {} macro expansion tests", len);
let mut failures = 0;
for test in tests {
let file_stem = test
.test
.path
.file_stem()
.expect("no file stem")
.to_string_lossy();
match test.run() {
Ok(outcome) => match outcome {
ExpansionOutcome::Same => println!("{} - ok", file_stem),
ExpansionOutcome::Different(a, b) => {
message_different(&file_stem, &a, &b);
failures += 1;
}
ExpansionOutcome::New(_) => println!("{} - refreshed", file_stem),
ExpansionOutcome::ExpandError(msg) => {
message_expansion_error(msg);
failures += 1;
}
},
Err(e) => {
eprintln!("Error: {:#?}", e);
failures += 1;
}
}
}
println!("\n\n");
if failures > 0 {
panic!("{} or {} tests failed", failures, len);
}
}
}
struct ExpandedTest {
test: Test,
error: Option<Error>,
}
impl ExpandedTest {
pub fn run(&self) -> Result<ExpansionOutcome> {
let temp_crate = make_tmp_cargo_crate_for_src(&self.test.path)?;
let (success, output) = expand_crate(&temp_crate)?;
if !success {
return Ok(ExpansionOutcome::ExpandError(output))
}
let file_stem = self
.test
.path
.file_stem()
.expect("no file stem")
.to_string_lossy();
let mut expanded = self.test.path.clone();
expanded.pop();
let expanded = expanded.join(format!("{}.expanded.rs", file_stem));
if !expanded.exists() {
std::fs::write(expanded, &output)?;
std::fs::remove_dir_all(&temp_crate)?;
return Ok(ExpansionOutcome::New(output));
}
let expected_expansion = std::fs::read(expanded)?;
std::fs::remove_dir_all(&temp_crate)?;
Ok(if output == expected_expansion {
ExpansionOutcome::Same
} else {
ExpansionOutcome::Different(expected_expansion, output)
})
}
}
fn expand_globs(tests: &[Test]) -> Vec<ExpandedTest> {
fn glob(pattern: &str) -> Result<Vec<PathBuf>> {
let mut paths = glob::glob(pattern)?
.map(|entry| entry.map_err(Error::from))
.collect::<Result<Vec<PathBuf>>>()?;
paths.sort();
Ok(paths)
}
let mut vec = Vec::new();
for test in tests {
let mut expanded = ExpandedTest {
test: test.clone(),
error: None,
};
if let Some(utf8) = test.path.to_str() {
if utf8.contains('*') {
match glob(utf8) {
Ok(paths) => {
for path in paths {
vec.push(ExpandedTest {
test: Test {
path,
expected: expanded.test.expected,
},
error: None,
});
}
continue;
}
Err(error) => expanded.error = Some(error),
}
}
}
vec.push(expanded);
}
vec
}
#[allow(dead_code)]
fn fix_relative_paths_in_deps(
root_path: &PathBuf,
dd: &toml::value::Table,
deps: &mut toml::map::Map<String, toml::value::Value>,
) {
for (k, v) in dd {
let v = match v {
toml::value::Value::Table(map) => {
let mut new_map = map.clone();
if let Some(p) = new_map.get_mut("path") {
let path = PathBuf::from(p.as_str().expect("path as string"));
if path.is_relative() {
let new_path =
std::fs::canonicalize(root_path.join(path)).expect("canonicalize path");
*p = toml::value::Value::String(new_path.to_string_lossy().to_string());
}
};
toml::value::Value::Table(new_map)
}
_ => v.clone(),
};
deps.insert(k.clone(), v);
}
}
pub fn make_tmp_cargo_crate_for_src(src_path: &PathBuf) -> Result<PathBuf> {
let crate_name = env::var("CARGO_PKG_NAME").map_err(|_| Error::PkgName)?;
let source_dir = env::var_os("CARGO_MANIFEST_DIR")
.ok_or(Error::ManifestDirError)
.map(PathBuf::from)?;
let mut deps = toml::map::Map::new();
let mut map = toml::map::Map::new();
map.insert(
"path".to_string(),
toml::Value::String(source_dir.to_string_lossy().to_string()),
);
deps.insert(crate_name, toml::Value::Table(map));
let file_name = src_path
.file_name()
.expect("File name")
.to_string_lossy()
.replace(".", "-");
let temp_dir = TempDir::new("macrotest")?;
let dir_path = temp_dir.into_path();
let mut cargo_toml = Map::new();
let mut package = Map::new();
package.insert("name".into(), Value::String(file_name));
package.insert("version".into(), Value::String("0.1.0".into()));
cargo_toml.insert("package".into(), Value::Table(package));
cargo_toml.insert("dependencies".into(), Value::Table(deps.clone()));
let cargo_toml = toml::to_string_pretty(&cargo_toml)?;
std::fs::write(dir_path.join("Cargo.toml"), cargo_toml)?;
std::fs::create_dir(dir_path.join("src"))?;
std::fs::copy(src_path, dir_path.join("src").join("main.rs"))?;
Ok(dir_path)
}
pub fn expand_crate(path: &PathBuf) -> Result<(bool, Vec<u8>)> {
let cargo_expand = Command::new("cargo")
.arg("expand")
.current_dir(path)
.output()
.map_err(|e| Error::CargoExpandExecutionError(e.to_string()))?;
if !cargo_expand.status.success() {
return Ok((false, cargo_expand.stderr));
}
Ok((true, cargo_expand.stdout))
}