""" sn_basis/test/run_tests.py Zentraler Test-Runner für sn_basis. Wrapper-konform, QGIS-unabhängig, CI- und IDE-fähig. """ import unittest import datetime import inspect import os import sys from pathlib import Path # --------------------------------------------------------- # Pre-Bootstrap: Plugin-Root in sys.path eintragen # --------------------------------------------------------- THIS_FILE = Path(__file__).resolve() PLUGIN_ROOT = THIS_FILE.parents[2] if str(PLUGIN_ROOT) not in sys.path: sys.path.insert(0, str(PLUGIN_ROOT)) from sn_basis.functions import ( get_plugin_root, add_to_sys_path, ) # --------------------------------------------------------- # Bootstrap: Plugin-Root in sys.path eintragen # --------------------------------------------------------- def bootstrap(): """ Simuliert das QGIS-Plugin-Startverhalten: stellt sicher, dass sn_basis importierbar ist. """ plugin_root = get_plugin_root() add_to_sys_path(plugin_root) bootstrap() # --------------------------------------------------------- # Farben # --------------------------------------------------------- RED = "\033[91m" YELLOW = "\033[93m" GREEN = "\033[92m" CYAN = "\033[96m" MAGENTA = "\033[95m" RESET = "\033[0m" GLOBAL_TEST_COUNTER = 0 # --------------------------------------------------------- # Farbige TestResult-Klasse # --------------------------------------------------------- class ColoredTestResult(unittest.TextTestResult): _last_test_class: type | None = None def startTest(self, test): global GLOBAL_TEST_COUNTER GLOBAL_TEST_COUNTER += 1 self.stream.write(f"{CYAN}[Test {GLOBAL_TEST_COUNTER}]{RESET}\n") super().startTest(test) def startTestClass(self, test): cls = test.__class__ file = inspect.getfile(cls) filename = os.path.basename(file) self.stream.write( f"\n{MAGENTA}{'=' * 70}\n" f"Starte Testklasse: {filename} → {cls.__name__}\n" f"{'=' * 70}{RESET}\n" ) def addError(self, test, err): super().addError(test, err) self.stream.write(f"{RED}ERROR{RESET}\n") def addFailure(self, test, err): super().addFailure(test, err) self.stream.write(f"{RED}FAILURE{RESET}\n") def addSkip(self, test, reason): super().addSkip(test, reason) self.stream.write(f"{YELLOW}SKIPPED{RESET}: {reason}\n") def addSuccess(self, test): super().addSuccess(test) self.stream.write(f"{GREEN}OK{RESET}\n") # --------------------------------------------------------- # Farbiger TestRunner # --------------------------------------------------------- class ColoredTestRunner(unittest.TextTestRunner): def _makeResult(self): result = ColoredTestResult( self.stream, self.descriptions, self.verbosity, ) original_start_test = result.startTest def patched_start_test(test): if not hasattr(result, "_last_test_class") or \ result._last_test_class != test.__class__: result.startTestClass(test) result._last_test_class = test.__class__ original_start_test(test) result.startTest = patched_start_test return result # --------------------------------------------------------- # Testlauf starten # --------------------------------------------------------- def main(): print("\n" + "=" * 70) print( f"{CYAN}Testlauf gestartet am: " f"{datetime.datetime.now():%Y-%m-%d %H:%M:%S}{RESET}" ) print("=" * 70 + "\n") loader = unittest.TestLoader() suite = loader.discover( start_dir=os.path.dirname(__file__), pattern="test_*.py" ) runner = ColoredTestRunner(verbosity=2) result = runner.run(suite) # Exit-Code für CI / Skripte return 0 if result.wasSuccessful() else 1 if __name__ == "__main__": raise SystemExit(main())