import os
import sys
from unittest.mock import MagicMock
import pytest
# Mock Stata dependencies ONLY if they're not already available
# This allows tests that need real Stata to use it, while providing mocks for unit tests
def _setup_stata_mocks_if_needed():
"""Set up mock Stata modules only if real ones are not available."""
# Try to import the real modules first
stata_available = False
try:
import stata_setup
stata_available = True
except (ImportError, ModuleNotFoundError):
pass
# Only mock if Stata is not available
if not stata_available:
if 'sfi' not in sys.modules:
sys.modules['sfi'] = MagicMock()
if 'pystata' not in sys.modules:
sys.modules['pystata'] = MagicMock()
if 'stata_setup' not in sys.modules:
sys.modules['stata_setup'] = MagicMock()
# In conftest.py
@pytest.fixture(scope="session")
def stata_client():
"""Single StataClient shared across all test files."""
from mcp_stata.stata_client import StataClient
from mcp_stata import discovery
stata_path = os.environ.get("STATA_PATH")
if not stata_path or not os.path.exists(stata_path):
stata_path, _ = discovery.find_stata_path()
os.environ["STATA_PATH"] = stata_path
c = StataClient()
c.init()
return c
# Call this immediately to ensure mocks are available if needed
_setup_stata_mocks_if_needed()
@pytest.fixture(scope="session", autouse=True)
def mock_stata_modules():
"""Session-scoped fixture to ensure Stata modules are mocked if needed."""
_setup_stata_mocks_if_needed()
yield
# Note: We don't remove the mocks after tests because other tests might need them
def configure_stata_for_tests():
"""
Helper function to configure Stata for tests that need it.
Returns (stata_dir, stata_flavor) for stata_setup.config().
Raises exceptions if Stata is not found.
"""
import stata_setup
from mcp_stata.discovery import find_stata_path
stata_exec_path, stata_flavor = find_stata_path()
# stata_setup.config needs the directory, not the binary
bin_dir = os.path.dirname(stata_exec_path)
# For macOS .app bundles, use the parent directory of the .app
if '.app/Contents/MacOS/' in stata_exec_path:
# Go up from MacOS -> Contents -> .app -> parent directory
app_bundle = os.path.dirname(os.path.dirname(bin_dir))
stata_dir = os.path.dirname(app_bundle)
else:
stata_dir = bin_dir
return stata_dir, stata_flavor
@pytest.fixture
def client(stata_client):
"""
Function-scoped alias to the shared Stata client.
Tests that need additional isolation should clean state explicitly.
"""
return stata_client
# Work around Windows PermissionError when pytest tries to unlink the
# pytest-current symlink during temp directory cleanup. Pytest's cleanup
# lives in _pytest.pathlib.cleanup_dead_symlinks; wrap it to ignore
# PermissionError so test runs don't warn/fail on exit.
if os.name == "nt":
try:
import _pytest.pathlib as _pl # type: ignore
except ImportError:
_pl = None
if _pl is not None:
_orig_cleanup_dead_symlinks = getattr(_pl, "cleanup_dead_symlinks", None)
def _cleanup_dead_symlinks_safe(root):
if _orig_cleanup_dead_symlinks is None:
return
try:
_orig_cleanup_dead_symlinks(root)
except PermissionError:
# Ignore symlink removal failures (e.g., antivirus or handle held)
return
_pl.cleanup_dead_symlinks = _cleanup_dead_symlinks_safe # type: ignore