use std::env;
use std::ffi::OsStr;
use std::fs;
use std::path::Path;
use anyhow::Context as _;
use anyhow::bail;
use codespan_reporting::files::SimpleFile;
use codespan_reporting::term;
use codespan_reporting::term::Config;
use codespan_reporting::term::termcolor::Buffer;
use libtest_mimic::Trial;
use pretty_assertions::StrComparison;
use wdl_ast::Diagnostic;
use wdl_ast::Document;
use wdl_ast::Node;
use wdl_format::Config as FormatConfig;
use wdl_format::Formatter;
use wdl_format::NewlineStyle;
use wdl_format::element::FormatElement;
use wdl_format::element::node::AstNodeFormatExt;
fn normalize(s: &str) -> String {
s.replace("\r\n", "\n")
}
fn find_tests() -> Vec<Trial> {
Path::new("tests")
.join("format")
.read_dir()
.unwrap()
.filter_map(|entry| {
let entry = entry.expect("failed to read directory");
let path = entry.path();
if !path.is_dir() {
return None;
}
let test_name = path
.file_stem()
.map(OsStr::to_string_lossy)
.unwrap()
.into_owned();
Some(Trial::test(test_name, move || Ok(run_test(&path)?)))
})
.collect()
}
fn format_diagnostics(diagnostics: &[Diagnostic], path: &Path, source: &str) -> String {
let file = SimpleFile::new(path.as_os_str().to_str().unwrap(), source);
let mut buffer = Buffer::no_color();
for diagnostic in diagnostics {
term::emit_to_write_style(
&mut buffer,
&Config::default(),
&file,
&diagnostic.to_codespan(()),
)
.expect("should emit");
}
String::from_utf8(buffer.into_inner()).expect("should be UTF-8")
}
fn compare_result(
path: &Path,
result: &str,
allow_blessing: bool,
preserve_line_endings: bool,
) -> Result<(), anyhow::Error> {
let result = if preserve_line_endings {
result.to_string()
} else {
normalize(result)
};
if allow_blessing && env::var_os("BLESS").is_some() {
fs::write(path, &result).context("writing result file")?;
return Ok(());
}
let expected = if preserve_line_endings {
fs::read_to_string(path).context("reading result file")?
} else {
fs::read_to_string(path)
.context("reading result file")?
.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(())
}
fn prepare_document(source: &str, path: &Path) -> Result<FormatElement, anyhow::Error> {
let (document, diagnostics) = Document::parse(source, None);
if !diagnostics.is_empty() {
bail!(
"failed to parse `{path}` {e}",
path = path.display(),
e = format_diagnostics(&diagnostics, path, source)
);
};
Ok(Node::Ast(document.ast().into_v1().unwrap()).into_format_element())
}
fn format(config: FormatConfig, source: &str, path: &Path) -> Result<String, anyhow::Error> {
let document = prepare_document(source, path)?;
Formatter::new(config)
.format(&document)
.context("formatting document")
}
fn run_test_inner(
config: FormatConfig,
source: &str,
original_doc: &Path,
formatted_doc: &Path,
preserve_line_endings: bool,
) -> anyhow::Result<()> {
let formatted = format(config, source, original_doc)?;
compare_result(formatted_doc, &formatted, true, preserve_line_endings)?;
let twice_formatted = format(config, &formatted, formatted_doc)?;
compare_result(
formatted_doc,
&twice_formatted,
false,
preserve_line_endings,
)
.context("testing idempotency")?;
Ok(())
}
fn run_test(test: &Path) -> Result<(), anyhow::Error> {
let path = test.join("source.wdl");
let formatted_path = path.with_extension("formatted.wdl");
let source = std::fs::read_to_string(&path).context("reading source file")?;
let config_path = test.join("config.toml");
if config_path.exists() {
let content = std::fs::read_to_string(&config_path).context(format!(
"failed to read config at '{}'",
config_path.display()
))?;
let config: FormatConfig = toml::from_str(&content).context(format!(
"failed to parse config at '{}'",
config_path.display()
))?;
let preserve_line_endings = config.newline_style != NewlineStyle::Auto;
run_test_inner(
config,
&source,
&path,
&formatted_path,
preserve_line_endings,
)?;
run_test_inner(
FormatConfig::default(),
&source,
&path,
&path.with_extension("default.formatted.wdl"),
false,
)?;
} else {
run_test_inner(
FormatConfig::default(),
&source,
&path,
&formatted_path,
false,
)?;
}
Ok(())
}
fn main() {
let args = libtest_mimic::Arguments::from_args();
let tests = find_tests();
libtest_mimic::run(&args, tests).exit();
}