use std::env;
use std::path::Path;
use std::path::absolute;
use anyhow::Context;
use anyhow::Result;
use anyhow::bail;
use common::TestConfig;
use common::compare_result;
use common::find_tests;
use common::strip_paths;
use futures::FutureExt as _;
use futures::future::BoxFuture;
use serde_json::to_string_pretty;
use tempfile::TempDir;
use tracing::info;
use tracing::level_filters::LevelFilter;
use wdl_analysis::Analyzer;
use wdl_ast::Diagnostic;
use wdl_ast::Severity;
use wdl_engine::EvaluationError;
use wdl_engine::Events;
use wdl_engine::Inputs;
use wdl_engine::v1::Evaluator;
mod common;
fn run_test(test: &Path, config: TestConfig) -> BoxFuture<'_, Result<()>> {
async move {
let analyzer = Analyzer::new(config.analysis, |(), _, _, _| async {});
analyzer
.add_directory(test)
.await
.context("adding directory")?;
let results = analyzer.analyze(()).await.context("running analysis")?;
let source_path = test.join("source.wdl");
let Some(result) = results
.iter()
.find(|r| Some(r.document().path().as_ref()) == source_path.to_str())
else {
bail!("`source.wdl` was not found in the analysis results");
};
if let Some(e) = result.error() {
bail!("parsing failed: {e:#}");
}
if result.document().has_errors() {
let errors: Vec<_> = result
.document()
.diagnostics()
.filter(|d| d.severity() == Severity::Error)
.collect();
bail!(
"test WDL contains {} error(s):\n{}",
errors.len(),
errors
.iter()
.map(|d| format!(" - {:?}", d))
.collect::<Vec<_>>()
.join("\n")
);
}
let path = result.document().path();
let diagnostics = match result.error() {
Some(e) => vec![Diagnostic::error(format!("failed to read `{path}`: {e:#}"))],
None => result.document().diagnostics().cloned().collect(),
};
if let Some(diagnostic) = diagnostics.iter().find(|d| d.severity() == Severity::Error) {
bail!(EvaluationError::new(result.document().clone(), diagnostic.clone()).to_string());
}
let mut inputs = match Inputs::parse(result.document(), test.join("inputs.json"))? {
Some((_, Inputs::Task(_))) => {
bail!("`inputs.json` contains inputs for a task, not a workflow")
}
Some((_, Inputs::Workflow(inputs))) => inputs,
None => Default::default(),
};
let test_dir = absolute(test).expect("failed to get absolute directory");
let test_dir_path = test_dir.as_path().into();
let workflow = result
.document()
.workflow()
.context("document does not contain a workflow")?;
inputs
.join_paths(workflow, |_| Ok(std::slice::from_ref(&test_dir_path)))
.await?;
let mut dir = TempDir::new_in(env!("CARGO_TARGET_TMPDIR"))
.context("failed to create temporary directory")?;
if env::var_os("SPROCKET_TEST_KEEP_TMPDIRS").is_some() {
dir.disable_cleanup(true);
info!(dir = %dir.path().display(), "test temp dir created (will be kept)");
} else {
info!(dir = %dir.path().display(), "test temp dir created");
}
let evaluator = Evaluator::new(
dir.path(),
config.engine.into(),
Default::default(),
Events::disabled(),
)
.await?;
match evaluator
.evaluate_workflow(result.document(), inputs.clone(), &dir)
.await
{
Ok(outputs) => {
let outputs = outputs.with_name(workflow.name());
let outputs = to_string_pretty(&outputs).context("failed to serialize outputs")?;
let outputs = strip_paths(dir.path(), &outputs);
let outputs = strip_paths(&test_dir, &outputs);
compare_result(&test.join("outputs.json"), &outputs)?;
}
Err(e) => {
let error = e.to_string();
let error = strip_paths(dir.path(), &error);
let error = strip_paths(&test_dir, &error);
compare_result(&test.join("error.txt"), &error)?;
}
}
Ok(())
}
.boxed()
}
fn main() -> Result<()> {
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::builder()
.with_default_directive(LevelFilter::OFF.into())
.from_env_lossy(),
)
.init();
let args = libtest_mimic::Arguments::from_args();
let runtime = tokio::runtime::Runtime::new()?;
let tests = find_tests(
run_test,
&Path::new("tests").join("workflows"),
runtime.handle(),
)?;
libtest_mimic::run(&args, tests).exit();
}