#![feature(round_char_boundary)] #![feature(exact_size_is_empty)] use std::path::Path; use std::path::PathBuf; use std::process::ExitCode; use futures::future::BoxFuture; use futures::future::FutureExt; use organic::compare::silent_compare_on_file; use tokio::sync::Semaphore; 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"))] fn main() -> Result> { let rt = tokio::runtime::Runtime::new()?; rt.block_on(async { let main_body_result = main_body().await; main_body_result }) } #[cfg(feature = "tracing")] fn main() -> Result> { let rt = tokio::runtime::Runtime::new()?; rt.block_on(async { init_telemetry()?; let main_body_result = main_body().await; shutdown_telemetry()?; main_body_result }) } #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] async fn main_body() -> Result> { let layer = compare_group("org-mode", || { compare_all_org_document("/foreign_documents/org-mode") }); 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") })); let running_tests: Vec<_> = layer.map(|c| tokio::spawn(c.run_test())).collect(); let mut any_failed = false; for test in running_tests.into_iter() { let test_result = test.await??; if test_result.is_immediately_bad() || test_result.has_bad_children() { any_failed = true; } test_result.print(); } 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) } } fn compare_howard_abrams() -> impl Iterator { 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") })); layer.chain(compare_group("veep", || { compare_all_org_document("/foreign_documents/howardabrams/veep") })) } fn compare_group, F: Fn() -> I, I: Iterator>( name: N, inner: F, ) -> impl Iterator { std::iter::once(TestConfig::TestLayer(TestLayer { name: name.into(), children: inner().collect(), })) } fn compare_all_org_document>(root_dir: P) -> impl Iterator { 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::, _>>() .unwrap(); test_files.sort_by_cached_key(|test_file| PathBuf::from(test_file.path())); 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(), }) }) .collect(); test_configs.into_iter() } static TEST_PERMITS: Semaphore = Semaphore::const_new(8); #[derive(Debug)] enum TestConfig { TestLayer(TestLayer), SingleFile(SingleFile), } #[derive(Debug)] struct TestLayer { name: String, children: Vec, } #[derive(Debug)] struct SingleFile { name: String, file_path: PathBuf, } #[derive(Debug)] enum TestResult { ResultLayer(ResultLayer), SingleFileResult(SingleFileResult), } #[derive(Debug)] struct ResultLayer { name: String, children: Vec, } #[derive(Debug)] struct SingleFileResult { name: String, status: TestStatus, } #[derive(Debug)] pub(crate) enum TestStatus { Pass, Fail, } impl TestConfig { fn run_test(self) -> BoxFuture<'static, Result> { async move { match self { TestConfig::TestLayer(test) => Ok(TestResult::ResultLayer(test.run_test().await?)), TestConfig::SingleFile(test) => { Ok(TestResult::SingleFileResult(test.run_test().await?)) } } } .boxed() } } impl SingleFile { async fn run_test(self) -> Result { let _permit = TEST_PERMITS.acquire().await.unwrap(); let result = silent_compare_on_file(&self.file_path).await; Ok(SingleFileResult { name: self.name, status: if let Ok(true) = result { TestStatus::Pass } else { TestStatus::Fail }, }) } } impl TestLayer { async fn run_test(self) -> Result { 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??); } Ok(ResultLayer { name: self.name, children, }) } } 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 } }