#!/usr/bin/env python3 """Exercise the XML validator with valid and invalid language packs. Run with python3 Tests/test_xmlvalidate.py. Requires Bash, GNU grep and fxparser on PATH (npm install fast-xml-parser@4.5.2 -g, as in the Validate XML workflow). """ from pathlib import Path import shutil import subprocess import tempfile import unittest ROOT = Path(__file__).resolve().parents[1] VALIDATOR = ROOT / ".github/workflows/xmlvalidate.sh" KEYS = ("FIRST", "SECOND", "THIRD") class XmlValidatorTests(unittest.TestCase): @classmethod def setUpClass(cls): if shutil.which("fxparser") is None: raise RuntimeError("Install fast-xml-parser@4.5.2 and put fxparser on PATH") def setUp(self): temporary = tempfile.TemporaryDirectory(prefix="vc-xml-validation-") self.addCleanup(temporary.cleanup) self.root = Path(temporary.name) / "VeraCrypt test" self.common = self.write_language("src/Common/Language.xml") def write_language(self, relative_path, keys=KEYS, lang="en", text=r"First\nSecond & third"): path = self.root / relative_path path.parent.mkdir(parents=True, exist_ok=True) entries = "".join( f' {text}\n' for key in keys ) path.write_text( '\n' '\n \n' + entries + ' \n\n', encoding="utf-8", ) return path def run_validator(self): return subprocess.run( ["bash", str(VALIDATOR), str(self.root)], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, timeout=30, ) def assert_overall_failure(self, result): self.assertEqual(result.returncode, 1, result.stdout) self.assertIn("Overall Result: One or more files failed validation.", result.stdout) def test_complete_translations_and_english_fallbacks_pass(self): fallback = self.write_language("Translations/Language.ar.xml") translated = self.write_language("Translations/Language.nl.xml", lang="nl") result = self.run_validator() self.assertEqual(result.returncode, 0, result.stdout) for path in (self.common, fallback, translated): self.assertIn(f"{path} PASSED all checks.", result.stdout) self.assertIn("Overall Result: All processed files passed all checks successfully.", result.stdout) def test_reports_every_missing_key_and_continues_to_later_files(self): arabic = self.write_language("Translations/Language.ar.xml", keys=("FIRST",)) french = self.write_language("Translations/Language.fr.xml", keys=("FIRST", "SECOND")) dutch = self.write_language("Translations/Language.nl.xml", lang="nl") result = self.run_validator() self.assert_overall_failure(result) for path, key in ((arabic, "SECOND"), (arabic, "THIRD"), (french, "THIRD")): self.assertIn(f"Key '{key}' (from {self.common}) not found in {path}", result.stdout) self.assertIn("2 key(s) missing.", result.stdout) self.assertIn("1 key(s) missing.", result.stdout) for path in (arabic, french): self.assertIn(f"{path} FAILED one or more checks.", result.stdout) self.assertIn(f"{dutch} PASSED all checks.", result.stdout) def test_malformed_xml_reports_parser_error_and_continues(self): arabic = self.write_language("Translations/Language.ar.xml") arabic.write_text( arabic.read_text(encoding="utf-8").replace("", ""), encoding="utf-8", ) dutch = self.write_language("Translations/Language.nl.xml", lang="nl") result = self.run_validator() self.assert_overall_failure(result) self.assertIn(f"XML Validation Failed for {arabic} (fxparser exit code: 1)", result.stdout) self.assertIn("InvalidTag", result.stdout) self.assertIn(f"{arabic} FAILED one or more checks.", result.stdout) self.assertIn(f"{dutch} PASSED all checks.", result.stdout) def test_invalid_escape_reports_error_and_continues(self): arabic = self.write_language("Translations/Language.ar.xml", text=r"Invalid\q") dutch = self.write_language("Translations/Language.nl.xml", lang="nl") result = self.run_validator() self.assert_overall_failure(result) self.assertIn(f"File '{arabic}' contains potentially invalid backslash escape sequences.", result.stdout) self.assertIn(f"{arabic} FAILED one or more checks.", result.stdout) self.assertIn(f"{dutch} PASSED all checks.", result.stdout) if __name__ == "__main__": unittest.main(verbosity=2)