""" Pytest compatibility layer for unittest. Provides pytest-style decorators and fixtures using unittest. """ import unittest import functools import threading import time from pathlib import Path from unittest.mock import MagicMock, patch, mock_open from datetime import datetime from typing import Generator # Global fixture registry _fixtures = {} def fixture(scope="function"): """Decorator to mark a function as a fixture.""" def decorator(func): func._is_fixture = True func._scope = scope _fixtures[func.__name__] = func return func return decorator def pytestmark(*marks): """Decorator to apply pytest markers (no-op for unittest).""" def decorator(cls): return cls return decorator # Marker decorators class Markers: @staticmethod def unit(func): """Mark as unit test.""" func._marker = 'unit' return func @staticmethod def integration(func): """Mark as integration test.""" func._marker = 'integration' return func @staticmethod def ui(func): """Mark as UI test.""" func._marker = 'ui' return func @staticmethod def performance(func): """Mark as performance test.""" func._marker = 'performance' return func @staticmethod def slow(func): """Mark as slow test.""" func._marker = 'slow' return func @staticmethod def requires_qt(func): """Mark as requiring Qt.""" func._marker = 'requires_qt' return func @staticmethod def requires_network(func): """Mark as requiring network.""" func._marker = 'requires_network' return func # Create pytest.mark-style interface class PytestMark: unit = Markers.unit integration = Markers.integration ui = Markers.ui performance = Markers.performance slow = Markers.slow requires_qt = Markers.requires_qt requires_network = Markers.requires_network # Test result storage for compatibility class TestGlobals: last_result = None def raises(expected_exception, match=None): """Context manager for expecting exceptions (pytest.raises style).""" class RaisesContext: def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): if exc_type is None: raise AssertionError(f"Expected {expected_exception.__name__} but no exception was raised") if not issubclass(exc_type, expected_exception): return False # Re-raise if match and not match in str(exc_val): raise AssertionError(f"Exception message did not match '{match}': {exc_val}") return True # Suppress exception return RaisesContext() # Approximate matcher for floating point class Approx: def __init__(self, value, rel=None, abs=None): self.value = value self.rel = rel or 1e-6 self.abs = abs or 1e-12 def __eq__(self, other): if not isinstance(other, (int, float)): return False tolerance = max(self.abs, self.rel * abs(self.value)) return abs(self.value - other) <= tolerance def __repr__(self): return f"Approx({self.value})" def approx(value, rel=None, abs=None): """Create an approximate matcher for floating point comparison.""" return Approx(value, rel, abs)