""" Tests for the check-silent-exceptions pre-commit hook. Ensures the hook detects silent exception swallowing (except: pass) that masks bugs and violates the project's no-fallbacks principle. Exceptions should always be logged or re-raised. """ import sys from importlib import import_module from pathlib import Path HOOKS_DIR = Path(__file__).parent.parent.parent / ".pre-commit-hooks" sys.path.insert(0, str(HOOKS_DIR)) hook_module = import_module("check-silent-exceptions") check_file_fn = hook_module.check_file def _write_and_check(tmp_path, code: str, filename: str = "module.py"): p = tmp_path / filename p.write_text(code, encoding="utf-8") return check_file_fn(str(p)) class TestDetectsSilentExceptions: """Ensures bare except:pass and except Exception:pass are caught.""" def test_detects_except_exception_pass(self, tmp_path): code = """ try: do_work() except Exception: pass """ issues = _write_and_check(tmp_path, code) assert len(issues) >= 1 assert any("silent" in msg.lower() for _, msg in issues) def test_detects_bare_except_pass(self, tmp_path): code = """ try: do_work() except: pass """ issues = _write_and_check(tmp_path, code) assert len(issues) >= 1 def test_detects_except_with_as_pass(self, tmp_path): code = """ try: do_work() except Exception as e: pass """ issues = _write_and_check(tmp_path, code) assert len(issues) >= 1 class TestAllowsProperHandling: """Ensures exceptions with logging or re-raise are not flagged.""" def test_allows_logger_debug(self, tmp_path): code = """ try: do_work() except Exception: logger.debug("Expected failure") """ issues = _write_and_check(tmp_path, code) assert len(issues) == 0 def test_allows_logger_warning(self, tmp_path): code = """ try: do_work() except Exception: logger.warning("Something went wrong") """ issues = _write_and_check(tmp_path, code) assert len(issues) == 0 def test_allows_logger_exception(self, tmp_path): code = """ try: do_work() except Exception: logger.exception("Unexpected error") """ issues = _write_and_check(tmp_path, code) assert len(issues) == 0 def test_allows_reraise(self, tmp_path): code = """ try: do_work() except Exception: raise """ issues = _write_and_check(tmp_path, code) assert len(issues) == 0 def test_allows_noqa_comment(self, tmp_path): code = """ try: do_work() except Exception: # noqa: silent-exception pass """ issues = _write_and_check(tmp_path, code) assert len(issues) == 0 class TestAllowsSpecificExceptions: """Ensures catching specific exceptions (not Exception) is allowed.""" def test_allows_value_error_pass(self, tmp_path): code = """ try: int("abc") except ValueError: pass """ issues = _write_and_check(tmp_path, code) assert len(issues) == 0 def test_allows_type_error_pass(self, tmp_path): code = """ try: len(None) except TypeError: pass """ issues = _write_and_check(tmp_path, code) assert len(issues) == 0 def test_allows_key_error_pass(self, tmp_path): code = """ try: d = {} val = d["missing"] except KeyError: pass """ issues = _write_and_check(tmp_path, code) assert len(issues) == 0