Merge branch 'foreign_document_test'
Some checks failed
rustfmt Build rustfmt has succeeded
rust-build Build rust-build has failed
rust-foreign-document-test Build rust-foreign-document-test has failed
rust-test Build rust-test has succeeded

This commit is contained in:
Tom Alexander 2023-10-14 18:53:52 -04:00
commit 8ac8f9fe6e
Signed by: talexander
GPG Key ID: D3A179C9A53C0EDE
12 changed files with 546 additions and 216 deletions

View File

@ -137,7 +137,7 @@ spec:
value: [] value: []
- name: docker-image - name: docker-image
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)" value: "$(params.image-name):$(tasks.fetch-repository.results.commit)"
- name: run-image-all - name: run-image-tracing-compare
taskRef: taskRef:
name: run-docker-image name: run-docker-image
workspaces: workspaces:
@ -152,6 +152,26 @@ spec:
value: ["--no-default-features", "--features", "tracing,compare"] value: ["--no-default-features", "--features", "tracing,compare"]
- name: docker-image - name: docker-image
value: "$(params.image-name):$(tasks.fetch-repository.results.commit)" 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: finally:
- name: report-success - name: report-success
when: when:

View File

@ -31,7 +31,14 @@ path = "src/lib.rs"
path = "src/bin_compare.rs" path = "src/bin_compare.rs"
required-features = ["compare"] 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] [dependencies]
futures = { version = "0.3.28", optional = true }
nom = "7.1.1" nom = "7.1.1"
opentelemetry = { version = "0.20.0", optional = true, default-features = false, features = ["trace", "rt-tokio"] } opentelemetry = { version = "0.20.0", optional = true, default-features = false, features = ["trace", "rt-tokio"] }
opentelemetry-otlp = { version = "0.13.0", optional = true } 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 = { version = "0.1.37", optional = true }
tracing-opentelemetry = { version = "0.20.0", optional = true } tracing-opentelemetry = { version = "0.20.0", optional = true }
tracing-subscriber = { version = "0.3.17", optional = true, features = ["env-filter"] } tracing-subscriber = { version = "0.3.17", optional = true, features = ["env-filter"] }
walkdir = { version = "2.3.3", optional = true }
[build-dependencies] [build-dependencies]
walkdir = "2.3.3" walkdir = "2.3.3"
[features] [features]
default = [] 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"] 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. # Optimized build for any sort of release.

View File

@ -52,6 +52,7 @@ buildtest:
> cargo build --no-default-features --features compare > cargo build --no-default-features --features compare
> cargo build --no-default-features --features tracing > cargo build --no-default-features --features tracing
> cargo build --no-default-features --features compare,tracing > cargo build --no-default-features --features compare,tracing
> cargo build --no-default-features --features compare,tracing,foreign_document_test
.PHONY: foreign_document_test .PHONY: foreign_document_test
foreign_document_test: foreign_document_test:

View File

@ -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=foreign-document-gather /foreign_documents/worg /foreign_documents/worg
COPY --from=build-org-mode /root/org-mode /foreign_documents/org-mode COPY --from=build-org-mode /root/org-mode /foreign_documents/org-mode
COPY --from=build-emacs /root/emacs /foreign_documents/emacs COPY --from=build-emacs /root/emacs /foreign_documents/emacs
COPY foreign_document_test_entrypoint.sh /entrypoint.sh ENTRYPOINT ["cargo", "run", "--bin", "foreign_document_test", "--features", "compare,foreign_document_test", "--profile", "release-lto"]
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

View File

@ -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 "${@}"

View File

@ -14,7 +14,12 @@ mod init_tracing;
#[cfg(not(feature = "tracing"))] #[cfg(not(feature = "tracing"))]
fn main() -> Result<(), Box<dyn std::error::Error>> { 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")] #[cfg(feature = "tracing")]
@ -30,14 +35,21 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
} }
#[cfg_attr(feature = "tracing", tracing::instrument(ret, level = "debug"))] #[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); let args = std::env::args().skip(1);
if args.is_empty() { if args.is_empty() {
let org_contents = read_stdin_to_string()?; 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 { } else {
for arg in args { for arg in args {
run_compare_on_file(arg)? if run_compare_on_file(arg).await? {
} else {
Err("Diff results do not match.")?;
}
} }
Ok(()) Ok(())
} }

View 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
}
}

View File

@ -12,39 +12,60 @@ use crate::context::LocalFileAccessInterface;
use crate::parser::parse_file_with_settings; use crate::parser::parse_file_with_settings;
use crate::parser::parse_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, org_contents: P,
) -> Result<(), Box<dyn std::error::Error>> { ) -> Result<bool, Box<dyn std::error::Error>> {
run_anonymous_compare_with_settings(org_contents, &GlobalSettings::default()) 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>> { pub async fn run_compare_on_file<P: AsRef<Path>>(
run_compare_on_file_with_settings(org_path, &GlobalSettings::default()) 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, org_contents: P,
global_settings: &GlobalSettings, ) -> Result<bool, Box<dyn std::error::Error>> {
) -> Result<(), 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. // 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_ref().replace("\r\n", "\n");
let org_contents = org_contents.as_str(); 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 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())?; let (_remaining, parsed_sexp) = sexp(org_sexp.as_str()).map_err(|e| e.to_string())?;
if !silent {
println!("{}\n\n\n", org_contents); println!("{}\n\n\n", org_contents);
println!("{}", org_sexp); println!("{}", org_sexp);
println!("{:#?}", rust_parsed); println!("{:#?}", rust_parsed);
}
// We do the diffing after printing out both parsed forms in case the diffing panics // We do the diffing after printing out both parsed forms in case the diffing panics
let diff_result = compare_document(&parsed_sexp, &rust_parsed)?; let diff_result = compare_document(&parsed_sexp, &rust_parsed)?;
if !silent {
diff_result.print(org_contents)?; diff_result.print(org_contents)?;
}
if diff_result.is_bad() { if diff_result.is_bad() {
Err("Diff results do not match.")?; return Ok(false);
} else { } else if !silent {
println!( println!(
"{color}Entire document passes.{reset}", "{color}Entire document passes.{reset}",
color = DiffResult::foreground_color(0, 255, 0), 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, org_path: P,
global_settings: &GlobalSettings, global_settings: &GlobalSettings<'g, 's>,
) -> Result<(), Box<dyn std::error::Error>> { silent: bool,
) -> Result<bool, Box<dyn std::error::Error>> {
let org_path = org_path.as_ref(); let org_path = org_path.as_ref();
print_versions()?; if !silent {
print_versions().await?;
}
let parent_directory = org_path let parent_directory = org_path
.parent() .parent()
.ok_or("Should be contained inside a directory.")?; .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 global_settings
}; };
let rust_parsed = parse_file_with_settings(org_contents, &global_settings, Some(org_path))?; 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())?; let (_remaining, parsed_sexp) = sexp(org_sexp.as_str()).map_err(|e| e.to_string())?;
if !silent {
println!("{}\n\n\n", org_contents); println!("{}\n\n\n", org_contents);
println!("{}", org_sexp); println!("{}", org_sexp);
println!("{:#?}", rust_parsed); println!("{:#?}", rust_parsed);
}
// We do the diffing after printing out both parsed forms in case the diffing panics // We do the diffing after printing out both parsed forms in case the diffing panics
let diff_result = compare_document(&parsed_sexp, &rust_parsed)?; let diff_result = compare_document(&parsed_sexp, &rust_parsed)?;
if !silent {
diff_result.print(org_contents)?; diff_result.print(org_contents)?;
}
if diff_result.is_bad() { if diff_result.is_bad() {
Err("Diff results do not match.")?; return Ok(false);
} else { } else if !silent {
println!( println!(
"{color}Entire document passes.{reset}", "{color}Entire document passes.{reset}",
color = DiffResult::foreground_color(0, 255, 0), 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>> { async fn print_versions() -> Result<(), Box<dyn std::error::Error>> {
eprintln!("Using emacs version: {}", get_emacs_version()?.trim()); eprintln!("Using emacs version: {}", get_emacs_version().await?.trim());
eprintln!("Using org-mode version: {}", get_org_mode_version()?.trim()); eprintln!(
"Using org-mode version: {}",
get_org_mode_version().await?.trim()
);
Ok(()) Ok(())
} }

View File

@ -10,3 +10,5 @@ pub use compare::run_anonymous_compare;
pub use compare::run_anonymous_compare_with_settings; pub use compare::run_anonymous_compare_with_settings;
pub use compare::run_compare_on_file; pub use compare::run_compare_on_file;
pub use compare::run_compare_on_file_with_settings; pub use compare::run_compare_on_file_with_settings;
pub use compare::silent_anonymous_compare;
pub use compare::silent_compare_on_file;

View File

@ -1,5 +1,6 @@
use std::path::Path; use std::path::Path;
use std::process::Command;
use tokio::process::Command;
use crate::context::HeadlineLevelFilter; use crate::context::HeadlineLevelFilter;
use crate::settings::GlobalSettings; use crate::settings::GlobalSettings;
@ -25,9 +26,9 @@ fn global_settings_elisp(global_settings: &GlobalSettings) -> String {
ret 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, file_contents: C,
global_settings: &GlobalSettings, global_settings: &GlobalSettings<'g, 's>,
) -> Result<String, Box<dyn std::error::Error>> ) -> Result<String, Box<dyn std::error::Error>>
where where
C: AsRef<str>, C: AsRef<str>,
@ -54,7 +55,7 @@ where
.arg("--batch") .arg("--batch")
.arg("--eval") .arg("--eval")
.arg(elisp_script); .arg(elisp_script);
let out = cmd.output()?; let out = cmd.output().await?;
let status = out.status.exit_ok(); let status = out.status.exit_ok();
if status.is_err() { if status.is_err() {
eprintln!( eprintln!(
@ -69,9 +70,9 @@ where
Ok(String::from_utf8(org_sexp)?) 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, file_path: P,
global_settings: &GlobalSettings, global_settings: &GlobalSettings<'g, 's>,
) -> Result<String, Box<dyn std::error::Error>> ) -> Result<String, Box<dyn std::error::Error>>
where where
P: AsRef<Path>, P: AsRef<Path>,
@ -106,7 +107,7 @@ where
.arg("--batch") .arg("--batch")
.arg("--eval") .arg("--eval")
.arg(elisp_script); .arg(elisp_script);
let out = cmd.output()?; let out = cmd.output().await?;
let status = out.status.exit_ok(); let status = out.status.exit_ok();
if status.is_err() { if status.is_err() {
eprintln!( eprintln!(
@ -143,7 +144,7 @@ where
output 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 let elisp_script = r#"(progn
(message "%s" (version)) (message "%s" (version))
)"#; )"#;
@ -156,12 +157,12 @@ pub fn get_emacs_version() -> Result<String, Box<dyn std::error::Error>> {
.arg("--eval") .arg("--eval")
.arg(elisp_script); .arg(elisp_script);
let out = cmd.output()?; let out = cmd.output().await?;
out.status.exit_ok()?; out.status.exit_ok()?;
Ok(String::from_utf8(out.stderr)?) 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 let elisp_script = r#"(progn
(org-mode) (org-mode)
(message "%s" (org-version nil t nil)) (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("--eval")
.arg(elisp_script); .arg(elisp_script);
let out = cmd.output()?; let out = cmd.output().await?;
out.status.exit_ok()?; out.status.exit_ok()?;
Ok(String::from_utf8(out.stderr)?) Ok(String::from_utf8(out.stderr)?)
} }

View File

@ -1,6 +1,12 @@
use std::fmt::Debug; use std::fmt::Debug;
use std::path::PathBuf; 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 { pub trait FileAccessInterface: Debug {
fn read_file(&self, path: &str) -> Result<String, std::io::Error>; fn read_file(&self, path: &str) -> Result<String, std::io::Error>;
} }

View File

@ -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. // 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} {expect_fail}
#[test] #[tokio::test]
fn autogen_default_{name}() -> Result<(), Box<dyn std::error::Error>> {{ async fn autogen_default_{name}() -> Result<(), Box<dyn std::error::Error>> {{
let org_path = "{path}"; let org_path = "{path}";
let org_contents = std::fs::read_to_string(org_path).expect("Read org file."); 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(()) Ok(())
}} }}
{expect_fail} {expect_fail}
#[test] #[tokio::test]
fn autogen_la_{name}() -> Result<(), Box<dyn std::error::Error>> {{ async fn autogen_la_{name}() -> Result<(), Box<dyn std::error::Error>> {{
let org_path = "{path}"; let org_path = "{path}";
let org_contents = std::fs::read_to_string(org_path).expect("Read org file."); let org_contents = std::fs::read_to_string(org_path).expect("Read org file.");
let global_settings = {{ 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.list_allow_alphabetical = true;
global_settings 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(()) Ok(())
}} }}
{expect_fail} {expect_fail}
#[test] #[tokio::test]
fn autogen_t1_{name}() -> Result<(), Box<dyn std::error::Error>> {{ async fn autogen_t1_{name}() -> Result<(), Box<dyn std::error::Error>> {{
let org_path = "{path}"; let org_path = "{path}";
let org_contents = std::fs::read_to_string(org_path).expect("Read org file."); let org_contents = std::fs::read_to_string(org_path).expect("Read org file.");
let global_settings = {{ let global_settings = {{
@ -33,13 +33,13 @@ fn autogen_t1_{name}() -> Result<(), Box<dyn std::error::Error>> {{
global_settings.tab_width = 1; global_settings.tab_width = 1;
global_settings 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(()) Ok(())
}} }}
{expect_fail} {expect_fail}
#[test] #[tokio::test]
fn autogen_t16_{name}() -> Result<(), Box<dyn std::error::Error>> {{ async fn autogen_t16_{name}() -> Result<(), Box<dyn std::error::Error>> {{
let org_path = "{path}"; let org_path = "{path}";
let org_contents = std::fs::read_to_string(org_path).expect("Read org file."); let org_contents = std::fs::read_to_string(org_path).expect("Read org file.");
let global_settings = {{ let global_settings = {{
@ -47,13 +47,13 @@ fn autogen_t16_{name}() -> Result<(), Box<dyn std::error::Error>> {{
global_settings.tab_width = 16; global_settings.tab_width = 16;
global_settings 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(()) Ok(())
}} }}
{expect_fail} {expect_fail}
#[test] #[tokio::test]
fn autogen_odd_{name}() -> Result<(), Box<dyn std::error::Error>> {{ async fn autogen_odd_{name}() -> Result<(), Box<dyn std::error::Error>> {{
let org_path = "{path}"; let org_path = "{path}";
let org_contents = std::fs::read_to_string(org_path).expect("Read org file."); let org_contents = std::fs::read_to_string(org_path).expect("Read org file.");
let global_settings = {{ 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.odd_levels_only = organic::settings::HeadlineLevelFilter::Odd;
global_settings 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(()) Ok(())
}} }}