diff --git a/.lighthouse/pipeline-rust-build.yaml b/.lighthouse/pipeline-rust-build.yaml index 036a1b79..624e21ab 100644 --- a/.lighthouse/pipeline-rust-build.yaml +++ b/.lighthouse/pipeline-rust-build.yaml @@ -137,7 +137,7 @@ spec: value: [] - name: docker-image value: "$(params.image-name):$(tasks.fetch-repository.results.commit)" - - name: run-image-all + - name: run-image-tracing-compare taskRef: name: run-docker-image workspaces: @@ -152,6 +152,26 @@ spec: value: ["--no-default-features", "--features", "tracing,compare"] - name: docker-image value: "$(params.image-name):$(tasks.fetch-repository.results.commit)" + - name: run-image-all + taskRef: + name: run-docker-image + workspaces: + - name: source + workspace: git-source + - name: cargo-cache + workspace: cargo-cache + runAfter: + - run-image-default + params: + - name: args + value: + [ + "--no-default-features", + "--features", + "tracing,compare,foreign_document_test", + ] + - name: docker-image + value: "$(params.image-name):$(tasks.fetch-repository.results.commit)" finally: - name: report-success when: diff --git a/Cargo.toml b/Cargo.toml index d7fed05a..fa76adfc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,14 @@ path = "src/lib.rs" path = "src/bin_compare.rs" required-features = ["compare"] +[[bin]] + # This bin exists for development purposes only. The real target of this crate is the library. + name = "foreign_document_test" + path = "src/bin_foreign_document_test.rs" + required-features = ["foreign_document_test"] + [dependencies] +futures = { version = "0.3.28", optional = true } nom = "7.1.1" opentelemetry = { version = "0.20.0", optional = true, default-features = false, features = ["trace", "rt-tokio"] } opentelemetry-otlp = { version = "0.13.0", optional = true } @@ -40,13 +47,15 @@ tokio = { version = "1.30.0", optional = true, default-features = false, feature tracing = { version = "0.1.37", optional = true } tracing-opentelemetry = { version = "0.20.0", optional = true } tracing-subscriber = { version = "0.3.17", optional = true, features = ["env-filter"] } +walkdir = { version = "2.3.3", optional = true } [build-dependencies] walkdir = "2.3.3" [features] default = [] -compare = [] +compare = ["tokio/process", "tokio/macros"] +foreign_document_test = ["compare", "dep:futures", "tokio/sync", "dep:walkdir", "tokio/process"] tracing = ["dep:opentelemetry", "dep:opentelemetry-otlp", "dep:opentelemetry-semantic-conventions", "dep:tokio", "dep:tracing", "dep:tracing-opentelemetry", "dep:tracing-subscriber"] # Optimized build for any sort of release. diff --git a/Makefile b/Makefile index a7412bc3..fdab0f9d 100644 --- a/Makefile +++ b/Makefile @@ -52,6 +52,7 @@ buildtest: > cargo build --no-default-features --features compare > cargo build --no-default-features --features tracing > cargo build --no-default-features --features compare,tracing +> cargo build --no-default-features --features compare,tracing,foreign_document_test .PHONY: foreign_document_test foreign_document_test: diff --git a/docker/organic_test/Dockerfile b/docker/organic_test/Dockerfile index 562d302e..0ae74eb7 100644 --- a/docker/organic_test/Dockerfile +++ b/docker/organic_test/Dockerfile @@ -102,6 +102,4 @@ COPY --from=foreign-document-gather /foreign_documents/doomemacs /foreign_docume COPY --from=foreign-document-gather /foreign_documents/worg /foreign_documents/worg COPY --from=build-org-mode /root/org-mode /foreign_documents/org-mode COPY --from=build-emacs /root/emacs /foreign_documents/emacs -COPY foreign_document_test_entrypoint.sh /entrypoint.sh -RUN chmod +x /entrypoint.sh -ENTRYPOINT ["/entrypoint.sh"] +ENTRYPOINT ["cargo", "run", "--bin", "foreign_document_test", "--features", "compare,foreign_document_test", "--profile", "release-lto"] diff --git a/docker/organic_test/foreign_document_test_entrypoint.sh b/docker/organic_test/foreign_document_test_entrypoint.sh deleted file mode 100644 index 3a51ce94..00000000 --- a/docker/organic_test/foreign_document_test_entrypoint.sh +++ /dev/null @@ -1,149 +0,0 @@ -#!/usr/bin/env bash -# -# Run the Organic compare script against a series of documents sourced from exterior places. -set -euo pipefail -IFS=$'\n\t' -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - -REALPATH=$(command -v uu-realpath || command -v realpath) - -function log { - (>&2 echo "${@}") -} - -function die { - local status_code="$1" - shift - (>&2 echo "${@}") - exit "$status_code" -} - -function main { - cargo build --no-default-features --features compare --profile release-lto - if [ "${CARGO_TARGET_DIR:-}" = "" ]; then - CARGO_TARGET_DIR=$(realpath target/) - fi - PARSE="${CARGO_TARGET_DIR}/release-lto/compare" - - local all_status=0 - set +e - - (run_compare_function "org-mode" compare_all_org_document "/foreign_documents/org-mode") - if [ "$?" -ne 0 ]; then all_status=1; fi - (run_compare_function "emacs" compare_all_org_document "/foreign_documents/emacs") - if [ "$?" -ne 0 ]; then all_status=1; fi - (run_compare_function "worg" compare_all_org_document "/foreign_documents/worg") - if [ "$?" -ne 0 ]; then all_status=1; fi - (run_compare_function "howard_abrams" compare_howard_abrams) - if [ "$?" -ne 0 ]; then all_status=1; fi - (run_compare_function "doomemacs" compare_all_org_document "/foreign_documents/doomemacs") - if [ "$?" -ne 0 ]; then all_status=1; fi - - set -e - if [ "$all_status" -ne 0 ]; then - red_text "Some tests failed." - else - green_text "All tests passed." - fi - return "$all_status" -} - -function green_text { - (IFS=' '; printf '\x1b[38;2;0;255;0m%s\x1b[0m' "${*}") -} - -function red_text { - (IFS=' '; printf '\x1b[38;2;255;0;0m%s\x1b[0m' "${*}") -} - -function yellow_text { - (IFS=' '; printf '\x1b[38;2;255;255;0m%s\x1b[0m' "${*}") -} - -function indent { - local depth="$1" - local scaled_depth=$((depth * 2)) - shift 1 - local prefix - prefix=$(printf -- "%${scaled_depth}s") - while read -r l; do - (IFS=' '; printf -- '%s%s\n' "$prefix" "$l") - done -} - -function run_compare_function { - local name="$1" - local stdoutput - shift 1 - set +e - stdoutput=$("${@}") - local status=$? - set -e - if [ "$status" -eq 0 ]; then - echo "$(green_text "GOOD") $name" - indent 1 <<<"$stdoutput" - else - echo "$(red_text "FAIL") $name" - indent 1 <<<"$stdoutput" - return 1 - fi -} - -function compare_all_org_document { - local root_dir="$1" - local target_document - local all_status=0 - while read target_document; do - local relative_path - relative_path=$($REALPATH --relative-to "$root_dir" "$target_document") - set +e - (run_compare "$relative_path" "$target_document") - if [ "$?" -ne 0 ]; then all_status=1; fi - set -e - done<<<"$(find "$root_dir" -type f -iname '*.org' | sort)" - return "$all_status" -} - -function run_compare { - local name="$1" - local target_document="$2" - set +e - ($PARSE "$target_document" &> /dev/null) - local status=$? - set -e - if [ "$status" -eq 0 ]; then - echo "$(green_text "GOOD") $name" - else - echo "$(red_text "FAIL") $name" - return 1 - fi -} - -function compare_howard_abrams { - local all_status=0 - set +e - - (run_compare_function "dot-files" compare_all_org_document "/foreign_documents/howardabrams/dot-files") - if [ "$?" -ne 0 ]; then all_status=1; fi - (run_compare_function "hamacs" compare_all_org_document "/foreign_documents/howardabrams/hamacs") - if [ "$?" -ne 0 ]; then all_status=1; fi - (run_compare_function "demo-it" compare_all_org_document "/foreign_documents/howardabrams/demo-it") - if [ "$?" -ne 0 ]; then all_status=1; fi - (run_compare_function "magit-demo" compare_all_org_document "/foreign_documents/howardabrams/magit-demo") - if [ "$?" -ne 0 ]; then all_status=1; fi - (run_compare_function "pdx-emacs-hackers" compare_all_org_document "/foreign_documents/howardabrams/pdx-emacs-hackers") - if [ "$?" -ne 0 ]; then all_status=1; fi - (run_compare_function "flora-simulator" compare_all_org_document "/foreign_documents/howardabrams/flora-simulator") - if [ "$?" -ne 0 ]; then all_status=1; fi - (run_compare_function "literate-devops-demo" compare_all_org_document "/foreign_documents/howardabrams/literate-devops-demo") - if [ "$?" -ne 0 ]; then all_status=1; fi - (run_compare_function "clojure-yesql-xp" compare_all_org_document "/foreign_documents/howardabrams/clojure-yesql-xp") - if [ "$?" -ne 0 ]; then all_status=1; fi - (run_compare_function "veep" compare_all_org_document "/foreign_documents/howardabrams/veep") - if [ "$?" -ne 0 ]; then all_status=1; fi - - set -e - return "$all_status" -} - -main "${@}" diff --git a/src/bin_compare.rs b/src/bin_compare.rs index b2012a6f..4c1216b0 100644 --- a/src/bin_compare.rs +++ b/src/bin_compare.rs @@ -14,7 +14,12 @@ mod init_tracing; #[cfg(not(feature = "tracing"))] fn main() -> Result<(), Box> { - main_body() + let rt = tokio::runtime::Runtime::new()?; + let result = rt.block_on(async { + let main_body_result = main_body().await; + main_body_result + }); + result } #[cfg(feature = "tracing")] @@ -30,14 +35,21 @@ fn main() -> Result<(), Box> { } #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] -fn main_body() -> Result<(), Box> { +async fn main_body() -> Result<(), Box> { let args = std::env::args().skip(1); if args.is_empty() { let org_contents = read_stdin_to_string()?; - run_anonymous_compare(org_contents) + if run_anonymous_compare(org_contents).await? { + } else { + Err("Diff results do not match.")?; + } + Ok(()) } else { for arg in args { - run_compare_on_file(arg)? + if run_compare_on_file(arg).await? { + } else { + Err("Diff results do not match.")?; + } } Ok(()) } diff --git a/src/bin_foreign_document_test.rs b/src/bin_foreign_document_test.rs new file mode 100644 index 00000000..ccd7e6c4 --- /dev/null +++ b/src/bin_foreign_document_test.rs @@ -0,0 +1,399 @@ +#![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()?; + let result = rt.block_on(async { + let main_body_result = main_body().await; + main_body_result + }); + result +} + +#[cfg(feature = "tracing")] +fn main() -> Result> { + let rt = tokio::runtime::Runtime::new()?; + let result = rt.block_on(async { + init_telemetry()?; + let main_body_result = main_body().await; + shutdown_telemetry()?; + main_body_result + }); + 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 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") + })); + let layer = layer.chain(compare_group("veep", || { + compare_all_org_document("/foreign_documents/howardabrams/veep") + })); + layer +} + +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, + file_path: PathBuf, + 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, + file_path: self.file_path, + 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 + } +} diff --git a/src/compare/compare.rs b/src/compare/compare.rs index b8b9c0f8..756dcb7b 100644 --- a/src/compare/compare.rs +++ b/src/compare/compare.rs @@ -12,39 +12,60 @@ use crate::context::LocalFileAccessInterface; use crate::parser::parse_file_with_settings; use crate::parser::parse_with_settings; -pub fn run_anonymous_compare>( +pub async fn run_anonymous_compare>( org_contents: P, -) -> Result<(), Box> { - run_anonymous_compare_with_settings(org_contents, &GlobalSettings::default()) +) -> Result> { + run_anonymous_compare_with_settings(org_contents, &GlobalSettings::default(), false).await } -pub fn run_compare_on_file>(org_path: P) -> Result<(), Box> { - run_compare_on_file_with_settings(org_path, &GlobalSettings::default()) +pub async fn run_compare_on_file>( + org_path: P, +) -> Result> { + run_compare_on_file_with_settings(org_path, &GlobalSettings::default(), false).await } -pub fn run_anonymous_compare_with_settings>( +pub async fn silent_anonymous_compare>( org_contents: P, - global_settings: &GlobalSettings, -) -> Result<(), Box> { +) -> Result> { + run_anonymous_compare_with_settings(org_contents, &GlobalSettings::default(), true).await +} + +pub async fn silent_compare_on_file>( + org_path: P, +) -> Result> { + run_compare_on_file_with_settings(org_path, &GlobalSettings::default(), true).await +} + +pub async fn run_anonymous_compare_with_settings<'g, 's, P: AsRef>( + org_contents: P, + global_settings: &GlobalSettings<'g, 's>, + silent: bool, +) -> Result> { // TODO: This is a work-around to pretend that dos line endings do not exist. It would be better to handle the difference in line endings. let org_contents = org_contents.as_ref().replace("\r\n", "\n"); let org_contents = org_contents.as_str(); - print_versions()?; + if !silent { + print_versions().await?; + } let rust_parsed = parse_with_settings(org_contents, global_settings)?; - let org_sexp = emacs_parse_anonymous_org_document(org_contents, global_settings)?; + let org_sexp = emacs_parse_anonymous_org_document(org_contents, global_settings).await?; let (_remaining, parsed_sexp) = sexp(org_sexp.as_str()).map_err(|e| e.to_string())?; - println!("{}\n\n\n", org_contents); - println!("{}", org_sexp); - println!("{:#?}", rust_parsed); + if !silent { + println!("{}\n\n\n", org_contents); + println!("{}", org_sexp); + println!("{:#?}", rust_parsed); + } // We do the diffing after printing out both parsed forms in case the diffing panics let diff_result = compare_document(&parsed_sexp, &rust_parsed)?; - diff_result.print(org_contents)?; + if !silent { + diff_result.print(org_contents)?; + } if diff_result.is_bad() { - Err("Diff results do not match.")?; - } else { + return Ok(false); + } else if !silent { println!( "{color}Entire document passes.{reset}", color = DiffResult::foreground_color(0, 255, 0), @@ -52,15 +73,18 @@ pub fn run_anonymous_compare_with_settings>( ); } - Ok(()) + Ok(true) } -pub fn run_compare_on_file_with_settings>( +pub async fn run_compare_on_file_with_settings<'g, 's, P: AsRef>( org_path: P, - global_settings: &GlobalSettings, -) -> Result<(), Box> { + global_settings: &GlobalSettings<'g, 's>, + silent: bool, +) -> Result> { let org_path = org_path.as_ref(); - print_versions()?; + if !silent { + print_versions().await?; + } let parent_directory = org_path .parent() .ok_or("Should be contained inside a directory.")?; @@ -77,20 +101,24 @@ pub fn run_compare_on_file_with_settings>( global_settings }; let rust_parsed = parse_file_with_settings(org_contents, &global_settings, Some(org_path))?; - let org_sexp = emacs_parse_file_org_document(org_path, &global_settings)?; + let org_sexp = emacs_parse_file_org_document(org_path, &global_settings).await?; let (_remaining, parsed_sexp) = sexp(org_sexp.as_str()).map_err(|e| e.to_string())?; - println!("{}\n\n\n", org_contents); - println!("{}", org_sexp); - println!("{:#?}", rust_parsed); + if !silent { + println!("{}\n\n\n", org_contents); + println!("{}", org_sexp); + println!("{:#?}", rust_parsed); + } // We do the diffing after printing out both parsed forms in case the diffing panics let diff_result = compare_document(&parsed_sexp, &rust_parsed)?; - diff_result.print(org_contents)?; + if !silent { + diff_result.print(org_contents)?; + } if diff_result.is_bad() { - Err("Diff results do not match.")?; - } else { + return Ok(false); + } else if !silent { println!( "{color}Entire document passes.{reset}", color = DiffResult::foreground_color(0, 255, 0), @@ -98,11 +126,14 @@ pub fn run_compare_on_file_with_settings>( ); } - Ok(()) + Ok(true) } -fn print_versions() -> Result<(), Box> { - eprintln!("Using emacs version: {}", get_emacs_version()?.trim()); - eprintln!("Using org-mode version: {}", get_org_mode_version()?.trim()); +async fn print_versions() -> Result<(), Box> { + eprintln!("Using emacs version: {}", get_emacs_version().await?.trim()); + eprintln!( + "Using org-mode version: {}", + get_org_mode_version().await?.trim() + ); Ok(()) } diff --git a/src/compare/mod.rs b/src/compare/mod.rs index 278146b6..8b658262 100644 --- a/src/compare/mod.rs +++ b/src/compare/mod.rs @@ -10,3 +10,5 @@ pub use compare::run_anonymous_compare; pub use compare::run_anonymous_compare_with_settings; pub use compare::run_compare_on_file; pub use compare::run_compare_on_file_with_settings; +pub use compare::silent_anonymous_compare; +pub use compare::silent_compare_on_file; diff --git a/src/compare/parse.rs b/src/compare/parse.rs index 10cb01dd..8b28fc3e 100644 --- a/src/compare/parse.rs +++ b/src/compare/parse.rs @@ -1,5 +1,6 @@ use std::path::Path; -use std::process::Command; + +use tokio::process::Command; use crate::context::HeadlineLevelFilter; use crate::settings::GlobalSettings; @@ -25,9 +26,9 @@ fn global_settings_elisp(global_settings: &GlobalSettings) -> String { ret } -pub(crate) fn emacs_parse_anonymous_org_document( +pub(crate) async fn emacs_parse_anonymous_org_document<'g, 's, C>( file_contents: C, - global_settings: &GlobalSettings, + global_settings: &GlobalSettings<'g, 's>, ) -> Result> where C: AsRef, @@ -54,7 +55,7 @@ where .arg("--batch") .arg("--eval") .arg(elisp_script); - let out = cmd.output()?; + let out = cmd.output().await?; let status = out.status.exit_ok(); if status.is_err() { eprintln!( @@ -69,9 +70,9 @@ where Ok(String::from_utf8(org_sexp)?) } -pub(crate) fn emacs_parse_file_org_document

( +pub(crate) async fn emacs_parse_file_org_document<'g, 's, P>( file_path: P, - global_settings: &GlobalSettings, + global_settings: &GlobalSettings<'g, 's>, ) -> Result> where P: AsRef, @@ -106,7 +107,7 @@ where .arg("--batch") .arg("--eval") .arg(elisp_script); - let out = cmd.output()?; + let out = cmd.output().await?; let status = out.status.exit_ok(); if status.is_err() { eprintln!( @@ -143,7 +144,7 @@ where output } -pub fn get_emacs_version() -> Result> { +pub async fn get_emacs_version() -> Result> { let elisp_script = r#"(progn (message "%s" (version)) )"#; @@ -156,12 +157,12 @@ pub fn get_emacs_version() -> Result> { .arg("--eval") .arg(elisp_script); - let out = cmd.output()?; + let out = cmd.output().await?; out.status.exit_ok()?; Ok(String::from_utf8(out.stderr)?) } -pub fn get_org_mode_version() -> Result> { +pub async fn get_org_mode_version() -> Result> { let elisp_script = r#"(progn (org-mode) (message "%s" (org-version nil t nil)) @@ -175,7 +176,7 @@ pub fn get_org_mode_version() -> Result> { .arg("--eval") .arg(elisp_script); - let out = cmd.output()?; + let out = cmd.output().await?; out.status.exit_ok()?; Ok(String::from_utf8(out.stderr)?) } diff --git a/src/context/file_access_interface.rs b/src/context/file_access_interface.rs index d54c7cfd..d269edff 100644 --- a/src/context/file_access_interface.rs +++ b/src/context/file_access_interface.rs @@ -1,6 +1,12 @@ use std::fmt::Debug; use std::path::PathBuf; +#[cfg(any(feature = "compare", feature = "foreign_document_test"))] +pub trait FileAccessInterface: Sync + Debug { + fn read_file(&self, path: &str) -> Result; +} + +#[cfg(not(any(feature = "compare", feature = "foreign_document_test")))] pub trait FileAccessInterface: Debug { fn read_file(&self, path: &str) -> Result; } diff --git a/tests/test_template b/tests/test_template index 108ac0c3..1f824063 100644 --- a/tests/test_template +++ b/tests/test_template @@ -1,17 +1,17 @@ // TODO: Investigate writing a proc macro to make specifying these combinations easier. For example, currently I am only setting 1 setting per test to keep the repetition reasonable when I should be mixing all the different combinations. {expect_fail} -#[test] -fn autogen_default_{name}() -> Result<(), Box> {{ +#[tokio::test] +async fn autogen_default_{name}() -> Result<(), Box> {{ let org_path = "{path}"; let org_contents = std::fs::read_to_string(org_path).expect("Read org file."); - organic::compare::run_anonymous_compare(org_contents.as_str())?; + organic::compare::run_anonymous_compare(org_contents.as_str()).await?; Ok(()) }} {expect_fail} -#[test] -fn autogen_la_{name}() -> Result<(), Box> {{ +#[tokio::test] +async fn autogen_la_{name}() -> Result<(), Box> {{ let org_path = "{path}"; let org_contents = std::fs::read_to_string(org_path).expect("Read org file."); let global_settings = {{ @@ -19,13 +19,13 @@ fn autogen_la_{name}() -> Result<(), Box> {{ global_settings.list_allow_alphabetical = true; global_settings }}; - organic::compare::run_anonymous_compare_with_settings(org_contents.as_str(), &global_settings)?; + organic::compare::run_anonymous_compare_with_settings(org_contents.as_str(), &global_settings, false).await?; Ok(()) }} {expect_fail} -#[test] -fn autogen_t1_{name}() -> Result<(), Box> {{ +#[tokio::test] +async fn autogen_t1_{name}() -> Result<(), Box> {{ let org_path = "{path}"; let org_contents = std::fs::read_to_string(org_path).expect("Read org file."); let global_settings = {{ @@ -33,13 +33,13 @@ fn autogen_t1_{name}() -> Result<(), Box> {{ global_settings.tab_width = 1; global_settings }}; - organic::compare::run_anonymous_compare_with_settings(org_contents.as_str(), &global_settings)?; + organic::compare::run_anonymous_compare_with_settings(org_contents.as_str(), &global_settings, false).await?; Ok(()) }} {expect_fail} -#[test] -fn autogen_t16_{name}() -> Result<(), Box> {{ +#[tokio::test] +async fn autogen_t16_{name}() -> Result<(), Box> {{ let org_path = "{path}"; let org_contents = std::fs::read_to_string(org_path).expect("Read org file."); let global_settings = {{ @@ -47,13 +47,13 @@ fn autogen_t16_{name}() -> Result<(), Box> {{ global_settings.tab_width = 16; global_settings }}; - organic::compare::run_anonymous_compare_with_settings(org_contents.as_str(), &global_settings)?; + organic::compare::run_anonymous_compare_with_settings(org_contents.as_str(), &global_settings, false).await?; Ok(()) }} {expect_fail} -#[test] -fn autogen_odd_{name}() -> Result<(), Box> {{ +#[tokio::test] +async fn autogen_odd_{name}() -> Result<(), Box> {{ let org_path = "{path}"; let org_contents = std::fs::read_to_string(org_path).expect("Read org file."); let global_settings = {{ @@ -61,6 +61,6 @@ fn autogen_odd_{name}() -> Result<(), Box> {{ global_settings.odd_levels_only = organic::settings::HeadlineLevelFilter::Odd; global_settings }}; - organic::compare::run_anonymous_compare_with_settings(org_contents.as_str(), &global_settings)?; + organic::compare::run_anonymous_compare_with_settings(org_contents.as_str(), &global_settings, false).await?; Ok(()) }}