"""
ABOUTME: Pytest configuration and shared fixtures for testing
ABOUTME: Provides path setup, common fixtures, and test utilities
"""
import sys
from pathlib import Path
from typing import Any, Dict, Generator
import pytest
import logging
PROJECT_ROOT = Path(__file__).parent.parent
SRC_PATH = PROJECT_ROOT / "src"
MODULES_PATH = SRC_PATH / "modules"
paths_to_add = [str(SRC_PATH), str(MODULES_PATH)]
for path in paths_to_add:
if path not in sys.path:
sys.path.insert(0, path)
def is_windows() -> bool:
"""Check if running on Windows."""
return sys.platform.startswith('win')
def is_orcaflex_available() -> bool:
"""Check if OrcaFlex is available."""
try:
import OrcFxAPI
return True
except ImportError:
return False
skip_if_not_windows = pytest.mark.skipif(
not is_windows(),
reason="Windows-only test"
)
skip_if_no_orcaflex = pytest.mark.skipif(
not is_orcaflex_available(),
reason="OrcaFlex not available"
)
@pytest.fixture(scope="session")
def configure_logging():
"""Configure logging for test session."""
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
return logging.getLogger("test")
@pytest.fixture(scope="session")
def project_root() -> Path:
"""Return project root directory."""
return PROJECT_ROOT
@pytest.fixture(scope="session")
def data_dir(project_root) -> Path:
"""Return data directory path."""
return project_root / "data"
@pytest.fixture(scope="session")
def raw_data_dir(data_dir) -> Path:
"""Return raw data directory path."""
return data_dir / "raw"
@pytest.fixture(scope="session")
def test_data_dir() -> Path:
"""Return test fixtures directory."""
return Path(__file__).parent / "fixtures"
@pytest.fixture
def temp_output_dir(tmp_path) -> Path:
"""Return temporary output directory for test."""
output_dir = tmp_path / "output"
output_dir.mkdir(exist_ok=True)
return output_dir
@pytest.fixture
def sample_dict() -> Dict[str, Any]:
"""Provide sample dictionary data."""
return {
"name": "test_item",
"value": 42,
"enabled": True,
"items": [1, 2, 3, 4, 5],
"metadata": {
"created": "2026-01-14",
"author": "tester"
}
}
@pytest.fixture
def sample_list() -> list:
"""Provide sample list data."""
return [
{"id": 1, "value": 10},
{"id": 2, "value": 20},
{"id": 3, "value": 30},
]
@pytest.fixture
def sample_dataframe():
"""Provide sample pandas DataFrame."""
import pandas as pd
return pd.DataFrame({
"id": [1, 2, 3, 4, 5],
"name": ["a", "b", "c", "d", "e"],
"value": [10.0, 20.0, 30.0, 40.0, 50.0],
"category": ["x", "y", "x", "y", "x"]
})
@pytest.fixture
def sample_time_series():
"""Provide sample time series DataFrame."""
import pandas as pd
import numpy as np
dates = pd.date_range("2026-01-01", periods=100, freq="D")
return pd.DataFrame({
"date": dates,
"value": np.random.randn(100).cumsum() + 100,
"volume": np.random.randint(100, 1000, 100)
})
@pytest.fixture
def temp_config(tmp_path) -> Path:
"""Create temporary YAML configuration file."""
config_file = tmp_path / "config.yaml"
config_file.write_text("""
metadata:
name: test-config
version: 1.0.0
settings:
debug: true
log_level: DEBUG
output_dir: ./output
input:
source: data/input.csv
encoding: utf-8
output:
format: html
path: reports/output.html
""")
return config_file
@pytest.fixture
def temp_csv(tmp_path, sample_dataframe) -> Path:
"""Create temporary CSV file with sample data."""
csv_path = tmp_path / "test_data.csv"
sample_dataframe.to_csv(csv_path, index=False)
return csv_path
@pytest.fixture
def mock_api_response() -> Dict[str, Any]:
"""Provide mock API response."""
return {
"status": "success",
"data": [
{"id": 1, "result": "ok"},
{"id": 2, "result": "ok"},
],
"metadata": {
"total": 2,
"page": 1
}
}
@pytest.fixture
def mock_error_response() -> Dict[str, Any]:
"""Provide mock error response."""
return {
"status": "error",
"error": {
"code": "INVALID_INPUT",
"message": "Input validation failed"
}
}
@pytest.fixture(autouse=True)
def cleanup_after_test():
"""Cleanup after each test."""
yield
@pytest.fixture(scope="session", autouse=True)
def cleanup_after_session():
"""Cleanup after test session."""
yield
def assert_dataframe_equal(df1, df2, **kwargs):
"""Assert two DataFrames are equal with helpful error messages."""
import pandas as pd
pd.testing.assert_frame_equal(df1, df2, **kwargs)
def assert_dict_subset(subset: Dict, superset: Dict):
"""Assert subset dict is contained in superset."""
for key, value in subset.items():
assert key in superset, f"Key '{key}' not found"
assert superset[key] == value, f"Value mismatch for '{key}'"
@pytest.fixture
def df_equal():
"""Provide DataFrame equality assertion."""
return assert_dataframe_equal
@pytest.fixture
def dict_subset():
"""Provide dict subset assertion."""
return assert_dict_subset
"""
ABOUTME: Example test file demonstrating pytest patterns
ABOUTME: Shows usage of fixtures and markers
"""
import pytest
class TestExampleUnit:
"""Unit tests for example module."""
@pytest.mark.unit
def test_basic_functionality(self, sample_dict):
"""Test basic functionality with sample data."""
assert sample_dict["name"] == "test_item"
assert sample_dict["value"] == 42
@pytest.mark.unit
def test_list_processing(self, sample_list):
"""Test list processing."""
assert len(sample_list) == 3
total = sum(item["value"] for item in sample_list)
assert total == 60
@pytest.mark.unit
def test_dataframe_operations(self, sample_dataframe):
"""Test DataFrame operations."""
assert len(sample_dataframe) == 5
assert "value" in sample_dataframe.columns
assert sample_dataframe["value"].sum() == 150.0
class TestExampleIntegration:
"""Integration tests demonstrating external resource usage."""
@pytest.mark.integration
def test_config_loading(self, temp_config):
"""Test loading configuration file."""
import yaml
with open(temp_config) as f:
config = yaml.safe_load(f)
assert config["metadata"]["name"] == "test-config"
assert config["settings"]["debug"] is True
@pytest.mark.integration
def test_csv_processing(self, temp_csv, sample_dataframe):
"""Test CSV file processing."""
import pandas as pd
loaded = pd.read_csv(temp_csv)
assert len(loaded) == len(sample_dataframe)
@pytest.mark.integration
@pytest.mark.slow
def test_time_series_analysis(self, sample_time_series):
"""Test time series analysis (may be slow)."""
assert len(sample_time_series) == 100
assert sample_time_series["date"].dtype == "datetime64[ns]"
class TestExampleMarkers:
"""Tests demonstrating various markers."""
@pytest.mark.unit
def test_fast_operation(self):
"""Fast unit test."""
result = 1 + 1
assert result == 2
@pytest.mark.slow
def test_slow_operation(self):
"""Slow test (marked for selective execution)."""
import time
time.sleep(0.1)
assert True
@pytest.mark.skip(reason="Not implemented yet")
def test_future_feature(self):
"""Test for future feature."""
pass
@pytest.mark.xfail(reason="Known bug, tracked in issue #123")
def test_known_bug(self):
"""Test for known bug."""
assert False
@pytest.mark.parametrize("input_val,expected", [
(1, 2),
(2, 4),
(3, 6),
(0, 0),
(-1, -2),
])
def test_parametrized_doubling(input_val, expected):
"""Parametrized test for doubling function."""
result = input_val * 2
assert result == expected