Merge branch 'foreign_document_test'
This commit is contained in:
		
						commit
						8ac8f9fe6e
					
				| @ -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: | ||||
|  | ||||
							
								
								
									
										11
									
								
								Cargo.toml
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								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. | ||||
|  | ||||
							
								
								
									
										1
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								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: | ||||
|  | ||||
| @ -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"] | ||||
|  | ||||
| @ -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 "${@}" | ||||
| @ -14,7 +14,12 @@ mod init_tracing; | ||||
| 
 | ||||
| #[cfg(not(feature = "tracing"))] | ||||
| fn main() -> Result<(), Box<dyn std::error::Error>> { | ||||
|     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<dyn std::error::Error>> { | ||||
| } | ||||
| 
 | ||||
| #[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] | ||||
| fn main_body() -> Result<(), Box<dyn std::error::Error>> { | ||||
| async fn main_body() -> Result<(), Box<dyn std::error::Error>> { | ||||
|     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(()) | ||||
|     } | ||||
|  | ||||
							
								
								
									
										399
									
								
								src/bin_foreign_document_test.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										399
									
								
								src/bin_foreign_document_test.rs
									
									
									
									
									
										Normal file
									
								
							| @ -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<ExitCode, Box<dyn std::error::Error>> { | ||||
|     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<ExitCode, Box<dyn std::error::Error>> { | ||||
|     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<ExitCode, Box<dyn std::error::Error>> { | ||||
|     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<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") | ||||
|     })); | ||||
|     let layer = layer.chain(compare_group("veep", || { | ||||
|         compare_all_org_document("/foreign_documents/howardabrams/veep") | ||||
|     })); | ||||
|     layer | ||||
| } | ||||
| 
 | ||||
| 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())); | ||||
|     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<TestConfig>, | ||||
| } | ||||
| 
 | ||||
| #[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<TestResult>, | ||||
| } | ||||
| 
 | ||||
| #[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<TestResult, JoinError>> { | ||||
|         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<SingleFileResult, JoinError> { | ||||
|         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<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??); | ||||
|         } | ||||
|         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 | ||||
|     } | ||||
| } | ||||
| @ -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<P: AsRef<str>>( | ||||
| pub async fn run_anonymous_compare<P: AsRef<str>>( | ||||
|     org_contents: P, | ||||
| ) -> Result<(), Box<dyn std::error::Error>> { | ||||
|     run_anonymous_compare_with_settings(org_contents, &GlobalSettings::default()) | ||||
| ) -> Result<bool, Box<dyn std::error::Error>> { | ||||
|     run_anonymous_compare_with_settings(org_contents, &GlobalSettings::default(), false).await | ||||
| } | ||||
| 
 | ||||
| pub fn run_compare_on_file<P: AsRef<Path>>(org_path: P) -> Result<(), Box<dyn std::error::Error>> { | ||||
|     run_compare_on_file_with_settings(org_path, &GlobalSettings::default()) | ||||
| pub async fn run_compare_on_file<P: AsRef<Path>>( | ||||
|     org_path: P, | ||||
| ) -> Result<bool, Box<dyn std::error::Error>> { | ||||
|     run_compare_on_file_with_settings(org_path, &GlobalSettings::default(), false).await | ||||
| } | ||||
| 
 | ||||
| pub fn run_anonymous_compare_with_settings<P: AsRef<str>>( | ||||
| pub async fn silent_anonymous_compare<P: AsRef<str>>( | ||||
|     org_contents: P, | ||||
|     global_settings: &GlobalSettings, | ||||
| ) -> Result<(), Box<dyn std::error::Error>> { | ||||
| ) -> Result<bool, Box<dyn std::error::Error>> { | ||||
|     run_anonymous_compare_with_settings(org_contents, &GlobalSettings::default(), true).await | ||||
| } | ||||
| 
 | ||||
| pub async fn silent_compare_on_file<P: AsRef<Path>>( | ||||
|     org_path: P, | ||||
| ) -> Result<bool, Box<dyn std::error::Error>> { | ||||
|     run_compare_on_file_with_settings(org_path, &GlobalSettings::default(), true).await | ||||
| } | ||||
| 
 | ||||
| pub async fn run_anonymous_compare_with_settings<'g, 's, P: AsRef<str>>( | ||||
|     org_contents: P, | ||||
|     global_settings: &GlobalSettings<'g, 's>, | ||||
|     silent: bool, | ||||
| ) -> Result<bool, Box<dyn std::error::Error>> { | ||||
|     // 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<P: AsRef<str>>( | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     Ok(()) | ||||
|     Ok(true) | ||||
| } | ||||
| 
 | ||||
| pub fn run_compare_on_file_with_settings<P: AsRef<Path>>( | ||||
| pub async fn run_compare_on_file_with_settings<'g, 's, P: AsRef<Path>>( | ||||
|     org_path: P, | ||||
|     global_settings: &GlobalSettings, | ||||
| ) -> Result<(), Box<dyn std::error::Error>> { | ||||
|     global_settings: &GlobalSettings<'g, 's>, | ||||
|     silent: bool, | ||||
| ) -> Result<bool, Box<dyn std::error::Error>> { | ||||
|     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<P: AsRef<Path>>( | ||||
|         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<P: AsRef<Path>>( | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     Ok(()) | ||||
|     Ok(true) | ||||
| } | ||||
| 
 | ||||
| fn print_versions() -> Result<(), Box<dyn std::error::Error>> { | ||||
|     eprintln!("Using emacs version: {}", get_emacs_version()?.trim()); | ||||
|     eprintln!("Using org-mode version: {}", get_org_mode_version()?.trim()); | ||||
| async fn print_versions() -> Result<(), Box<dyn std::error::Error>> { | ||||
|     eprintln!("Using emacs version: {}", get_emacs_version().await?.trim()); | ||||
|     eprintln!( | ||||
|         "Using org-mode version: {}", | ||||
|         get_org_mode_version().await?.trim() | ||||
|     ); | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -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<C>( | ||||
| pub(crate) async fn emacs_parse_anonymous_org_document<'g, 's, C>( | ||||
|     file_contents: C, | ||||
|     global_settings: &GlobalSettings, | ||||
|     global_settings: &GlobalSettings<'g, 's>, | ||||
| ) -> Result<String, Box<dyn std::error::Error>> | ||||
| where | ||||
|     C: AsRef<str>, | ||||
| @ -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<P>( | ||||
| pub(crate) async fn emacs_parse_file_org_document<'g, 's, P>( | ||||
|     file_path: P, | ||||
|     global_settings: &GlobalSettings, | ||||
|     global_settings: &GlobalSettings<'g, 's>, | ||||
| ) -> Result<String, Box<dyn std::error::Error>> | ||||
| where | ||||
|     P: AsRef<Path>, | ||||
| @ -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<String, Box<dyn std::error::Error>> { | ||||
| pub async fn get_emacs_version() -> Result<String, Box<dyn std::error::Error>> { | ||||
|     let elisp_script = r#"(progn
 | ||||
|      (message "%s" (version)) | ||||
| )"#;
 | ||||
| @ -156,12 +157,12 @@ pub fn get_emacs_version() -> Result<String, Box<dyn std::error::Error>> { | ||||
|         .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<String, Box<dyn std::error::Error>> { | ||||
| pub async fn get_org_mode_version() -> Result<String, Box<dyn std::error::Error>> { | ||||
|     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<String, Box<dyn std::error::Error>> { | ||||
|         .arg("--eval") | ||||
|         .arg(elisp_script); | ||||
| 
 | ||||
|     let out = cmd.output()?; | ||||
|     let out = cmd.output().await?; | ||||
|     out.status.exit_ok()?; | ||||
|     Ok(String::from_utf8(out.stderr)?) | ||||
| } | ||||
|  | ||||
| @ -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<String, std::io::Error>; | ||||
| } | ||||
| 
 | ||||
| #[cfg(not(any(feature = "compare", feature = "foreign_document_test")))] | ||||
| pub trait FileAccessInterface: Debug { | ||||
|     fn read_file(&self, path: &str) -> Result<String, std::io::Error>; | ||||
| } | ||||
|  | ||||
| @ -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<dyn std::error::Error>> {{ | ||||
| #[tokio::test] | ||||
| async fn autogen_default_{name}() -> Result<(), Box<dyn std::error::Error>> {{ | ||||
|     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<dyn std::error::Error>> {{ | ||||
| #[tokio::test] | ||||
| async fn autogen_la_{name}() -> Result<(), Box<dyn std::error::Error>> {{ | ||||
|     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<dyn std::error::Error>> {{ | ||||
|         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<dyn std::error::Error>> {{ | ||||
| #[tokio::test] | ||||
| async fn autogen_t1_{name}() -> Result<(), Box<dyn std::error::Error>> {{ | ||||
|     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<dyn std::error::Error>> {{ | ||||
|         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<dyn std::error::Error>> {{ | ||||
| #[tokio::test] | ||||
| async fn autogen_t16_{name}() -> Result<(), Box<dyn std::error::Error>> {{ | ||||
|     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<dyn std::error::Error>> {{ | ||||
|         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<dyn std::error::Error>> {{ | ||||
| #[tokio::test] | ||||
| async fn autogen_odd_{name}() -> Result<(), Box<dyn std::error::Error>> {{ | ||||
|     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<dyn std::error::Error>> {{ | ||||
|         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(()) | ||||
| }} | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Tom Alexander
						Tom Alexander