use std::collections::HashMap;
use std::env;
use std::ffi::OsStr;
use std::fs;
use std::path::Path;
use anyhow::Context as _;
use anyhow::bail;
use futures::future::BoxFuture;
use libtest_mimic::Trial;
use pretty_assertions::StrComparison;
use wdl_analysis::Config as AnalysisConfig;
use wdl_engine::config::BackendConfig;
use wdl_engine::config::Config as EngineConfig;
const DOCKER_ONLY_TESTS: &[&str] = &[
"container-fallback",
"url-symlink",
];
#[derive(Debug, Clone, Default, serde::Deserialize, serde::Serialize)]
pub struct TestConfig {
pub analysis: AnalysisConfig,
pub engine: EngineConfig,
}
pub fn find_tests(
run_test: fn(&Path, TestConfig) -> BoxFuture<'_, Result<(), anyhow::Error>>,
base_dir: &Path,
runtime: &tokio::runtime::Handle,
) -> Result<Vec<Trial>, anyhow::Error> {
let mut tests = vec![];
for entry in base_dir.read_dir().unwrap() {
let entry = entry.expect("failed to read directory");
let path = entry.path();
if !path.is_dir() {
continue;
}
let test_name_base = path
.file_stem()
.map(std::ffi::OsStr::to_string_lossy)
.unwrap()
.into_owned();
for (config_name, config) in resolve_configs(&path)
.with_context(|| format!("getting configs for {test_name_base}"))?
{
let test_runtime = runtime.clone();
let test_path = path.clone();
tests.push(Trial::test(
format!("{test_name_base}_{config_name}"),
move || {
Ok(test_runtime
.block_on(run_test(test_path.as_path(), config))
.map_err(|e| format!("{e:?}"))?)
},
));
}
}
Ok(tests)
}
pub fn resolve_configs(path: &Path) -> Result<HashMap<String, TestConfig>, anyhow::Error> {
let mut base_configs = base_configs()?;
let config_override_path = path.join("config-override.toml");
if config_override_path.exists() {
for config in base_configs.values_mut() {
let combined = config::Config::builder()
.add_source(config::Config::try_from(config).unwrap())
.add_source(config::File::from(config_override_path.as_path()).required(true))
.build()?
.try_deserialize()?;
*config = combined;
}
}
if let Some(test) = path.file_name().and_then(OsStr::to_str)
&& DOCKER_ONLY_TESTS.contains(&test)
{
base_configs.remove("local");
}
Ok(base_configs)
}
pub fn base_configs() -> Result<HashMap<String, TestConfig>, anyhow::Error> {
if let Some(env_config) = env::var_os("SPROCKET_TEST_ENGINE_CONFIG") {
let engine = toml::from_str(&std::fs::read_to_string(env_config)?)?;
let config = TestConfig {
engine,
..TestConfig::default()
};
return Ok(HashMap::from([("env_config".to_string(), config)]));
}
#[allow(unused_mut)]
let mut configs = HashMap::from([(
"local".to_string(),
TestConfig {
engine: EngineConfig {
backends: [(
"default".to_string(),
BackendConfig::Local(Default::default()),
)]
.into(),
..Default::default()
},
..TestConfig::default()
},
)]);
#[cfg(not(docker_tests_disabled))]
configs.insert(
"docker".to_string(),
TestConfig {
engine: EngineConfig {
backends: [(
"default".to_string(),
BackendConfig::Docker(Default::default()),
)]
.into(),
..Default::default()
},
..TestConfig::default()
},
);
Ok(configs)
}
pub fn strip_paths(root: &Path, s: &str) -> String {
#[cfg(windows)]
{
let mut pattern = root.to_str().expect("path is not UTF-8").to_string();
if !pattern.ends_with('\\') {
pattern.push('\\');
}
let s = s.replace(&pattern, "");
let pattern = pattern.replace('\\', "\\\\");
s.replace(&pattern, "")
}
#[cfg(unix)]
{
let mut pattern = root.to_str().expect("path is not UTF-8").to_string();
if !pattern.ends_with('/') {
pattern.push('/');
}
s.replace(&pattern, "")
}
}
pub fn normalize(s: &str) -> String {
s.replace("\\\\", "/")
.replace("\\", "/")
.replace("\r\n", "\n")
}
pub fn compare_result(path: &Path, result: &str) -> Result<(), anyhow::Error> {
let result = normalize(result);
if env::var_os("BLESS").is_some() {
fs::write(path, &result).with_context(|| {
format!(
"failed to write result file `{path}`",
path = path.display()
)
})?;
return Ok(());
}
let expected = fs::read_to_string(path)
.with_context(|| {
format!(
"failed to read result file `{path}`: expected contents to be `{result}`",
path = path.display()
)
})?
.replace("\r\n", "\n");
if expected != result {
bail!(
"result from `{path}` is not as expected:\n{diff}",
path = path.display(),
diff = StrComparison::new(&expected, &result),
);
}
Ok(())
}