organic/src/bin_foreign_document_test.rs

398 lines
12 KiB
Rust
Raw Normal View History

#![feature(round_char_boundary)]
#![feature(exact_size_is_empty)]
2023-10-08 11:54:21 +00:00
use std::path::Path;
2023-10-11 23:41:32 +00:00
use std::path::PathBuf;
2023-10-14 21:38:53 +00:00
use std::process::ExitCode;
2023-10-11 23:47:30 +00:00
use futures::future::BoxFuture;
use futures::future::FutureExt;
use organic::compare::silent_compare_on_file;
2023-10-13 15:41:49 +00:00
use tokio::sync::Semaphore;
2023-10-13 16:07:52 +00:00
use tokio::task::JoinError;
use walkdir::WalkDir;
#[cfg(feature = "tracing")]
use crate::init_tracing::init_telemetry;
#[cfg(feature = "tracing")]
use crate::init_tracing::shutdown_telemetry;
#[cfg(feature = "tracing")]
mod init_tracing;
#[cfg(not(feature = "tracing"))]
2023-10-14 21:38:53 +00:00
fn main() -> Result<ExitCode, Box<dyn std::error::Error>> {
let rt = tokio::runtime::Runtime::new()?;
2023-10-16 23:12:25 +00:00
rt.block_on(async {
2023-10-07 05:13:26 +00:00
let main_body_result = main_body().await;
main_body_result
2023-10-16 23:12:25 +00:00
})
}
#[cfg(feature = "tracing")]
2023-10-14 21:38:53 +00:00
fn main() -> Result<ExitCode, Box<dyn std::error::Error>> {
let rt = tokio::runtime::Runtime::new()?;
2023-10-16 23:12:25 +00:00
rt.block_on(async {
init_telemetry()?;
2023-10-07 05:13:26 +00:00
let main_body_result = main_body().await;
shutdown_telemetry()?;
main_body_result
2023-10-16 23:12:25 +00:00
})
}
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))]
2023-10-14 21:38:53 +00:00
async fn main_body() -> Result<ExitCode, Box<dyn std::error::Error>> {
2023-10-13 17:20:21 +00:00
let layer = compare_group("org-mode", || {
compare_all_org_document("/foreign_documents/org-mode")
2023-10-08 21:17:32 +00:00
});
2023-10-13 17:20:21 +00:00
let layer = layer.chain(compare_group("emacs", || {
compare_all_org_document("/foreign_documents/emacs")
}));
let layer = layer.chain(compare_group("worg", || {
compare_all_org_document("/foreign_documents/worg")
}));
let layer = layer.chain(compare_group("howard_abrams", compare_howard_abrams));
let layer = layer.chain(compare_group("doomemacs", || {
compare_all_org_document("/foreign_documents/doomemacs")
}));
let layer = layer.chain(compare_group("literate_build_emacs", || {
compare_all_org_document("/foreign_documents/literate_build_emacs")
}));
2023-10-13 17:20:21 +00:00
let running_tests: Vec<_> = layer.map(|c| tokio::spawn(c.run_test())).collect();
2023-10-14 21:38:53 +00:00
let mut any_failed = false;
2023-10-13 17:20:21 +00:00
for test in running_tests.into_iter() {
let test_result = test.await??;
2023-10-14 21:38:53 +00:00
if test_result.is_immediately_bad() || test_result.has_bad_children() {
any_failed = true;
}
test_result.print();
2023-10-13 17:20:21 +00:00
}
2023-10-14 21:38:53 +00:00
if any_failed {
println!(
"{color}Some tests failed.{reset}",
color = TestResult::foreground_color(255, 0, 0),
reset = TestResult::reset_color(),
);
Ok(ExitCode::FAILURE)
} else {
println!(
"{color}All tests passed.{reset}",
color = TestResult::foreground_color(0, 255, 0),
reset = TestResult::reset_color(),
);
Ok(ExitCode::SUCCESS)
}
}
2023-10-07 05:13:26 +00:00
fn compare_howard_abrams() -> impl Iterator<Item = TestConfig> {
let layer = compare_group("dot-files", || {
compare_all_org_document("/foreign_documents/howardabrams/dot-files")
});
let layer = layer.chain(compare_group("hamacs", || {
compare_all_org_document("/foreign_documents/howardabrams/hamacs")
}));
let layer = layer.chain(compare_group("demo-it", || {
compare_all_org_document("/foreign_documents/howardabrams/demo-it")
}));
let layer = layer.chain(compare_group("magit-demo", || {
compare_all_org_document("/foreign_documents/howardabrams/magit-demo")
}));
let layer = layer.chain(compare_group("pdx-emacs-hackers", || {
compare_all_org_document("/foreign_documents/howardabrams/pdx-emacs-hackers")
}));
let layer = layer.chain(compare_group("flora-simulator", || {
compare_all_org_document("/foreign_documents/howardabrams/flora-simulator")
}));
let layer = layer.chain(compare_group("literate-devops-demo", || {
compare_all_org_document("/foreign_documents/howardabrams/literate-devops-demo")
}));
let layer = layer.chain(compare_group("clojure-yesql-xp", || {
compare_all_org_document("/foreign_documents/howardabrams/clojure-yesql-xp")
}));
2023-10-16 23:12:25 +00:00
layer.chain(compare_group("veep", || {
compare_all_org_document("/foreign_documents/howardabrams/veep")
2023-10-16 23:12:25 +00:00
}))
}
2023-10-13 17:20:21 +00:00
fn compare_group<N: Into<String>, F: Fn() -> I, I: Iterator<Item = TestConfig>>(
name: N,
inner: F,
) -> impl Iterator<Item = TestConfig> {
std::iter::once(TestConfig::TestLayer(TestLayer {
name: name.into(),
children: inner().collect(),
}))
}
fn compare_all_org_document<P: AsRef<Path>>(root_dir: P) -> impl Iterator<Item = TestConfig> {
let root_dir = root_dir.as_ref();
let mut test_files = WalkDir::new(root_dir)
.into_iter()
.filter(|e| match e {
Ok(dir_entry) => {
dir_entry.file_type().is_file()
&& Path::new(dir_entry.file_name())
.extension()
.map(|ext| ext.to_ascii_lowercase() == "org")
.unwrap_or(false)
}
Err(_) => true,
})
.collect::<Result<Vec<_>, _>>()
.unwrap();
test_files.sort_by_cached_key(|test_file| PathBuf::from(test_file.path()));
2023-10-14 19:07:17 +00:00
let test_configs: Vec<_> = test_files
.into_iter()
.map(|test_file| {
let name = test_file
.path()
.strip_prefix(root_dir)
.expect("Result is from walkdir so it must be below the root directory.")
.as_os_str()
.to_string_lossy()
.into_owned();
TestConfig::SingleFile(SingleFile {
name,
file_path: test_file.into_path(),
})
})
2023-10-14 19:07:17 +00:00
.collect();
test_configs.into_iter()
}
2023-10-13 15:41:49 +00:00
static TEST_PERMITS: Semaphore = Semaphore::const_new(8);
2023-10-07 05:13:26 +00:00
#[derive(Debug)]
2023-10-11 23:41:32 +00:00
enum TestConfig {
TestLayer(TestLayer),
SingleFile(SingleFile),
2023-10-07 05:13:26 +00:00
}
#[derive(Debug)]
2023-10-11 23:41:32 +00:00
struct TestLayer {
name: String,
children: Vec<TestConfig>,
2023-10-07 05:13:26 +00:00
}
#[derive(Debug)]
2023-10-11 23:41:32 +00:00
struct SingleFile {
name: String,
2023-10-11 23:41:32 +00:00
file_path: PathBuf,
2023-10-08 11:54:21 +00:00
}
#[derive(Debug)]
2023-10-11 23:41:32 +00:00
enum TestResult {
ResultLayer(ResultLayer),
SingleFileResult(SingleFileResult),
2023-10-08 11:54:21 +00:00
}
#[derive(Debug)]
2023-10-11 23:41:32 +00:00
struct ResultLayer {
name: String,
children: Vec<TestResult>,
2023-10-08 11:54:21 +00:00
}
#[derive(Debug)]
2023-10-11 23:41:32 +00:00
struct SingleFileResult {
name: String,
2023-10-08 21:17:32 +00:00
status: TestStatus,
2023-10-07 05:13:26 +00:00
}
#[derive(Debug)]
2023-10-08 21:17:32 +00:00
pub(crate) enum TestStatus {
2023-10-13 16:14:46 +00:00
Pass,
Fail,
2023-10-08 21:17:32 +00:00
}
2023-10-11 23:41:32 +00:00
impl TestConfig {
2023-10-13 16:07:52 +00:00
fn run_test(self) -> BoxFuture<'static, Result<TestResult, JoinError>> {
2023-10-11 23:47:30 +00:00
async move {
match self {
2023-10-13 16:07:52 +00:00
TestConfig::TestLayer(test) => Ok(TestResult::ResultLayer(test.run_test().await?)),
TestConfig::SingleFile(test) => {
Ok(TestResult::SingleFileResult(test.run_test().await?))
}
2023-10-11 23:47:30 +00:00
}
2023-10-08 21:17:32 +00:00
}
2023-10-11 23:47:30 +00:00
.boxed()
2023-10-08 21:17:32 +00:00
}
2023-10-07 05:13:26 +00:00
}
2023-10-08 11:54:21 +00:00
2023-10-11 23:41:32 +00:00
impl SingleFile {
2023-10-13 16:07:52 +00:00
async fn run_test(self) -> Result<SingleFileResult, JoinError> {
2023-10-13 15:41:49 +00:00
let _permit = TEST_PERMITS.acquire().await.unwrap();
2023-10-14 21:48:38 +00:00
let result = silent_compare_on_file(&self.file_path).await;
2023-10-13 16:07:52 +00:00
Ok(SingleFileResult {
name: self.name,
status: if let Ok(true) = result {
2023-10-13 16:14:46 +00:00
TestStatus::Pass
2023-10-08 21:17:32 +00:00
} else {
2023-10-13 16:14:46 +00:00
TestStatus::Fail
2023-10-08 21:17:32 +00:00
},
2023-10-13 16:07:52 +00:00
})
2023-10-08 21:17:32 +00:00
}
}
2023-10-11 23:41:32 +00:00
impl TestLayer {
2023-10-13 16:07:52 +00:00
async fn run_test(self) -> Result<ResultLayer, JoinError> {
let running_children: Vec<_> = self
.children
.into_iter()
.map(|c| tokio::spawn(c.run_test()))
.collect();
let mut children = Vec::with_capacity(running_children.len());
for c in running_children {
children.push(c.await??);
2023-10-11 23:41:32 +00:00
}
2023-10-13 16:07:52 +00:00
Ok(ResultLayer {
2023-10-11 23:41:32 +00:00
name: self.name,
children,
2023-10-13 16:07:52 +00:00
})
2023-10-08 11:54:21 +00:00
}
}
impl TestResult {
pub fn print(&self) {
self.print_indented(0);
}
fn print_indented(&self, indentation: usize) {
match self {
TestResult::ResultLayer(result) => result.print_indented(indentation),
TestResult::SingleFileResult(result) => result.print_indented(indentation),
}
}
fn has_bad_children(&self) -> bool {
match self {
TestResult::ResultLayer(result) => result.has_bad_children(),
TestResult::SingleFileResult(result) => result.has_bad_children(),
}
}
fn is_immediately_bad(&self) -> bool {
match self {
TestResult::ResultLayer(result) => result.is_immediately_bad(),
TestResult::SingleFileResult(result) => result.is_immediately_bad(),
}
}
pub(crate) fn foreground_color(red: u8, green: u8, blue: u8) -> String {
if TestResult::should_use_color() {
format!(
"\x1b[38;2;{red};{green};{blue}m",
red = red,
green = green,
blue = blue
)
} else {
String::new()
}
}
#[allow(dead_code)]
pub(crate) fn background_color(red: u8, green: u8, blue: u8) -> String {
if TestResult::should_use_color() {
format!(
"\x1b[48;2;{red};{green};{blue}m",
red = red,
green = green,
blue = blue
)
} else {
String::new()
}
}
pub(crate) fn reset_color() -> &'static str {
if TestResult::should_use_color() {
"\x1b[0m"
} else {
""
}
}
fn should_use_color() -> bool {
!std::env::var("NO_COLOR").is_ok_and(|val| !val.is_empty())
}
}
impl SingleFileResult {
fn print_indented(&self, indentation: usize) {
match self.status {
TestStatus::Pass => {
println!(
"{indentation}{color}PASS{reset} {name}",
indentation = " ".repeat(indentation),
color = TestResult::foreground_color(0, 255, 0),
reset = TestResult::reset_color(),
name = self.name
);
}
TestStatus::Fail => {
println!(
"{indentation}{color}FAIL{reset} {name}",
indentation = " ".repeat(indentation),
color = TestResult::foreground_color(255, 0, 0),
reset = TestResult::reset_color(),
name = self.name
);
}
}
}
fn has_bad_children(&self) -> bool {
false
}
fn is_immediately_bad(&self) -> bool {
match self.status {
TestStatus::Pass => false,
TestStatus::Fail => true,
}
}
}
impl ResultLayer {
fn print_indented(&self, indentation: usize) {
if self.is_immediately_bad() {
println!(
"{indentation}{color}FAIL{reset} {name}",
indentation = " ".repeat(indentation),
color = TestResult::foreground_color(255, 0, 0),
reset = TestResult::reset_color(),
name = self.name
);
} else if self.has_bad_children() {
println!(
"{indentation}{color}BADCHILD{reset} {name}",
indentation = " ".repeat(indentation),
color = TestResult::foreground_color(255, 255, 0),
reset = TestResult::reset_color(),
name = self.name
);
} else {
println!(
"{indentation}{color}PASS{reset} {name}",
indentation = " ".repeat(indentation),
color = TestResult::foreground_color(0, 255, 0),
reset = TestResult::reset_color(),
name = self.name
);
}
self.children
.iter()
.for_each(|result| result.print_indented(indentation + 1));
}
fn has_bad_children(&self) -> bool {
self.children
.iter()
.any(|result| result.is_immediately_bad() || result.has_bad_children())
}
fn is_immediately_bad(&self) -> bool {
false
}
}