From e505855b66a95898b5a943a8e982decf9c809a6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20Gro=C3=9F?= Date: Thu, 2 Apr 2026 21:38:06 +0200 Subject: [PATCH 1/2] Introduce `--rules-dir` resolution and add comprehensive CLI tests * Added `resolve_rules_dir` function to handle `--rules-dir` resolution. * Updated logging to display the resolved rules directory. * Added tests ensuring correct behavior for explicit `--rules-dir` usage, default rules-dir fallback, and invalid rules-dir handling. --- src/bin/mathml2text.rs | 9 +++- tests/mathml2text.rs | 97 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 tests/mathml2text.rs diff --git a/src/bin/mathml2text.rs b/src/bin/mathml2text.rs index ff05b5219..3b55225c3 100644 --- a/src/bin/mathml2text.rs +++ b/src/bin/mathml2text.rs @@ -18,6 +18,10 @@ fn get_rules_dir() -> String { return rules_path.as_os_str().to_str().unwrap().to_string(); } +fn resolve_rules_dir(rules_dir: Option) -> PathBuf { + return rules_dir.unwrap_or_else(|| PathBuf::from(get_rules_dir())); +} + #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] enum OutputType { Text, @@ -60,14 +64,15 @@ fn main() -> Result<()> { "#.to_string() }; - if let Err(e) = set_rules_dir(get_rules_dir()) { + let rules_dir = resolve_rules_dir(cli.rules_dir); + if let Err(e) = set_rules_dir(rules_dir.to_string_lossy()) { panic!("Error: exiting -- {}", errors_to_string(&e)); } debug!("Languages: {}", libmathcat::interface::get_supported_languages()?.join(", ")); #[cfg(feature = "include-zip")] info!("***********include-zip is present**********"); - info!("Version = '{}' using Rules dir {}", get_version(), get_rules_dir()); + info!("Version = '{}' using Rules dir {}", get_version(), rules_dir.display()); set_preference("Language", cli.language)?; set_preference("DecimalSeparator", "Auto").unwrap(); diff --git a/tests/mathml2text.rs b/tests/mathml2text.rs new file mode 100644 index 000000000..80193a8ba --- /dev/null +++ b/tests/mathml2text.rs @@ -0,0 +1,97 @@ +mod common; + +use std::path::PathBuf; +use std::process::Command; +use std::time::{SystemTime, UNIX_EPOCH}; + +fn mathml2text_command() -> Command { Command::new(env!("CARGO_BIN_EXE_mathml2text")) } + +fn rules_dir() -> String { common::abs_rules_dir_path() } + +fn unique_temp_file(name: &str) -> PathBuf { + let timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("system time should be after UNIX_EPOCH") + .as_nanos(); + std::env::temp_dir().join(format!("mathml2text-{name}-{timestamp}.mml")) +} + +/// Verifies that `mathml2text` uses an explicitly provided `--rules-dir`. +/// This protects the CLI override path instead of silently falling back to the default rules location. +#[test] +fn accepts_explicit_rules_dir() { + let input_file = unique_temp_file("explicit-rules"); + std::fs::write( + &input_file, + "4", + ) + .expect("should write temp input file"); + + let output = mathml2text_command() + .args([ + "--rules-dir", + rules_dir().as_str(), + ]) + .arg(&input_file) + .output() + .expect("mathml2text should run"); + + let _ = std::fs::remove_file(&input_file); + + assert!(output.status.success(), "stderr: {}", String::from_utf8_lossy(&output.stderr)); + assert_eq!("4\n", String::from_utf8_lossy(&output.stdout)); +} + +/// Verifies that the positional input-file argument still works on its own. +/// This keeps the default CLI file-input path covered while testing the rules-dir changes separately. +#[test] +fn still_accepts_input_file() { + let input_file = unique_temp_file("input-file"); + std::fs::write( + &input_file, + "2", + ) + .expect("should write temp input file"); + + let output = mathml2text_command() + .arg(&input_file) + .output() + .expect("mathml2text should run"); + + let _ = std::fs::remove_file(&input_file); + + assert!(output.status.success(), "stderr: {}", String::from_utf8_lossy(&output.stderr)); + assert_eq!("2\n", String::from_utf8_lossy(&output.stdout)); +} + +/// Verifies that an invalid explicit `--rules-dir` causes the CLI to fail. +/// This protects against regressions where the flag is parsed but then ignored at runtime. +#[test] +fn rejects_invalid_explicit_rules_dir() { + let missing_rules_dir = std::env::temp_dir().join(format!( + "mathml2text-missing-rules-{}", + SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("system time should be after UNIX_EPOCH") + .as_nanos() + )); + let input_file = unique_temp_file("invalid-rules"); + std::fs::write( + &input_file, + "5", + ) + .expect("should write temp input file"); + + let output = mathml2text_command() + .args([ + "--rules-dir", + missing_rules_dir.to_str().expect("temp path should be valid UTF-8"), + ]) + .arg(&input_file) + .output() + .expect("mathml2text should run"); + + let _ = std::fs::remove_file(&input_file); + + assert!(!output.status.success(), "stdout: {}", String::from_utf8_lossy(&output.stdout)); +} From 447db8e4baf5d3f2e1a2f74efbea8cdea02697cb Mon Sep 17 00:00:00 2001 From: mgros Date: Thu, 4 Jun 2026 23:25:09 +0200 Subject: [PATCH 2/2] add proper fixture for mathml2text tests --- tests/mathml2text.rs | 125 +++++++++++++++++-------------------------- 1 file changed, 49 insertions(+), 76 deletions(-) diff --git a/tests/mathml2text.rs b/tests/mathml2text.rs index 80193a8ba..2f9f418d3 100644 --- a/tests/mathml2text.rs +++ b/tests/mathml2text.rs @@ -1,97 +1,70 @@ mod common; +use std::ffi::OsStr; use std::path::PathBuf; -use std::process::Command; +use std::process::{Command, Output}; use std::time::{SystemTime, UNIX_EPOCH}; -fn mathml2text_command() -> Command { Command::new(env!("CARGO_BIN_EXE_mathml2text")) } +struct Mathml2TextFixture { + input_file: PathBuf, +} -fn rules_dir() -> String { common::abs_rules_dir_path() } +impl Mathml2TextFixture { + fn new(name: &str, mathml: &str) -> Self { + let timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("system time should be after UNIX_EPOCH") + .as_nanos(); + let input_file = std::env::temp_dir().join(format!("mathml2text-{name}-{timestamp}.mml")); + std::fs::write(&input_file, mathml).expect("should write temp input file"); + Self { input_file } + } + + fn run_with_rules_dir(&self, rules_dir: impl AsRef) -> Output { + Command::new(env!("CARGO_BIN_EXE_mathml2text")) + .arg("--rules-dir") + .arg(rules_dir) + .arg(&self.input_file) + .output() + .expect("mathml2text should run") + } + + fn missing_rules_dir(&self) -> PathBuf { + self.input_file.with_extension("missing-rules") + } +} -fn unique_temp_file(name: &str) -> PathBuf { - let timestamp = SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("system time should be after UNIX_EPOCH") - .as_nanos(); - std::env::temp_dir().join(format!("mathml2text-{name}-{timestamp}.mml")) +impl Drop for Mathml2TextFixture { + fn drop(&mut self) { + let _ = std::fs::remove_file(&self.input_file); + } } /// Verifies that `mathml2text` uses an explicitly provided `--rules-dir`. /// This protects the CLI override path instead of silently falling back to the default rules location. #[test] fn accepts_explicit_rules_dir() { - let input_file = unique_temp_file("explicit-rules"); - std::fs::write( - &input_file, - "4", - ) - .expect("should write temp input file"); - - let output = mathml2text_command() - .args([ - "--rules-dir", - rules_dir().as_str(), - ]) - .arg(&input_file) - .output() - .expect("mathml2text should run"); - - let _ = std::fs::remove_file(&input_file); - - assert!(output.status.success(), "stderr: {}", String::from_utf8_lossy(&output.stderr)); + let fixture = Mathml2TextFixture::new("explicit-rules", "4"); + let output = fixture.run_with_rules_dir(common::abs_rules_dir_path()); + + assert!( + output.status.success(), + "stderr: {}", + String::from_utf8_lossy(&output.stderr) + ); assert_eq!("4\n", String::from_utf8_lossy(&output.stdout)); } -/// Verifies that the positional input-file argument still works on its own. -/// This keeps the default CLI file-input path covered while testing the rules-dir changes separately. -#[test] -fn still_accepts_input_file() { - let input_file = unique_temp_file("input-file"); - std::fs::write( - &input_file, - "2", - ) - .expect("should write temp input file"); - - let output = mathml2text_command() - .arg(&input_file) - .output() - .expect("mathml2text should run"); - - let _ = std::fs::remove_file(&input_file); - - assert!(output.status.success(), "stderr: {}", String::from_utf8_lossy(&output.stderr)); - assert_eq!("2\n", String::from_utf8_lossy(&output.stdout)); -} - /// Verifies that an invalid explicit `--rules-dir` causes the CLI to fail. /// This protects against regressions where the flag is parsed but then ignored at runtime. #[test] fn rejects_invalid_explicit_rules_dir() { - let missing_rules_dir = std::env::temp_dir().join(format!( - "mathml2text-missing-rules-{}", - SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("system time should be after UNIX_EPOCH") - .as_nanos() - )); - let input_file = unique_temp_file("invalid-rules"); - std::fs::write( - &input_file, - "5", - ) - .expect("should write temp input file"); - - let output = mathml2text_command() - .args([ - "--rules-dir", - missing_rules_dir.to_str().expect("temp path should be valid UTF-8"), - ]) - .arg(&input_file) - .output() - .expect("mathml2text should run"); - - let _ = std::fs::remove_file(&input_file); - - assert!(!output.status.success(), "stdout: {}", String::from_utf8_lossy(&output.stdout)); + let fixture = Mathml2TextFixture::new("invalid-rules", "5"); + let output = fixture.run_with_rules_dir(fixture.missing_rules_dir()); + + assert!( + !output.status.success(), + "stdout: {}", + String::from_utf8_lossy(&output.stdout) + ); }