134 lines
3.4 KiB
Python
134 lines
3.4 KiB
Python
"""
|
|
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)
|