Skip to content

Commit 658a7f7

Browse files
committed
[v0.0.8] 2025-09-05
🚨++ New Plug-in Integrated ++ 🚨 - Stomatal Conductance plug-in integrated with PyHelios - Made some updates to testing infrastructure to avoid pytest state contamination
1 parent fbb3bc0 commit 658a7f7

20 files changed

Lines changed: 3130 additions & 16 deletions

β€Žbuild_scripts/build_helios.pyβ€Ž

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1412,7 +1412,7 @@ def get_default_plugins() -> List[str]:
14121412
List of default plugins
14131413
"""
14141414
# Return the plugins that are actually integrated into PyHelios
1415-
integrated_plugins = ["visualizer", "weberpenntree", "radiation", "energybalance", "solarposition"]
1415+
integrated_plugins = ["visualizer", "weberpenntree", "radiation", "energybalance", "solarposition", "stomatalconductance"]
14161416

14171417
# Filter by platform compatibility
14181418
default_plugins = []

β€Ždocs/CHANGELOG.mdβ€Ž

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
# [v0.0.8] 2025-09-05
4+
5+
🚨++ New Plug-in Integrated ++ 🚨
6+
- Stomatal Conductance plug-in integrated with PyHelios
7+
8+
- Made some updates to testing infrastructure to avoid pytest state contamination
9+
310
# [v0.0.7] 2025-09-04
411

512
- Updated Helios native C++ library to v1.3.47

β€Ždocs/plugin_integration_guide.mdβ€Ž

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1843,6 +1843,105 @@ ls tests/test_*yourplugin*
18431843
mv tests/test_your_plugin.py tests/test_yourplugin.py
18441844
```
18451845

1846+
#### Pytest Test Isolation Issues (State Contamination)
1847+
1848+
**CRITICAL PROBLEM**: A persistent issue where tests pass individually but fail when run as part of the full test suite, affecting multiple plugins (energybalance, radiation, stomatalconductance).
1849+
1850+
**Symptoms**:
1851+
- Tests pass when run with `pytest tests/test_yourplugin.py -v`
1852+
- Same tests fail when run with `pytest` (full suite)
1853+
- Import-related failures with class identity mismatches
1854+
- Error messages like `AssertionError: False = issubclass(...)`
1855+
1856+
**Root Causes Identified**:
1857+
1. **Global Plugin Registry State**: Plugin registry singleton persists contaminated state across test modules
1858+
2. **Import Path Inconsistencies**: Different import paths for error classes cause class identity issues
1859+
3. **Context Validation Issues**: `isinstance()` checks fail due to class identity problems during module reloading
1860+
1861+
**PERMANENT SOLUTION IMPLEMENTED** (v0.0.7+):
1862+
1863+
**1. Enhanced Test Fixture Architecture** (`conftest.py` - already fixed):
1864+
```python
1865+
@pytest.fixture(scope="module", autouse=True)
1866+
def reset_plugin_state():
1867+
"""Reset plugin registry state between test modules to prevent contamination."""
1868+
# Reset at the start of each test module
1869+
_reset_plugin_registry_if_available()
1870+
yield
1871+
# Reset at the end of each test module
1872+
_reset_plugin_registry_if_available()
1873+
1874+
def _reset_plugin_registry_if_available():
1875+
"""Reset plugin registry to prevent test contamination."""
1876+
try:
1877+
from pyhelios.plugins.registry import reset_plugin_registry
1878+
reset_plugin_registry()
1879+
except ImportError:
1880+
pass
1881+
```
1882+
1883+
**2. Import Path Standardization** (REQUIRED for new plugins):
1884+
```python
1885+
# βœ… CORRECT - Import from main pyhelios module
1886+
from pyhelios import HeliosError, YourPluginError
1887+
1888+
# ❌ WRONG - Direct import from exceptions module causes contamination
1889+
from pyhelios.exceptions import HeliosError
1890+
```
1891+
1892+
**3. Robust Parameter Validation** (for Context-related issues):
1893+
```python
1894+
# Use duck typing to handle class identity issues during test runs
1895+
if not (hasattr(context, '__class__') and
1896+
(isinstance(context, Context) or
1897+
context.__class__.__name__ == 'Context')):
1898+
raise TypeError(f"Requires a Context instance, got {type(context).__name__}")
1899+
```
1900+
1901+
**4. Enhanced Error Class Registration** (add to `pyhelios/__init__.py`):
1902+
```python
1903+
# Ensure ALL plugin error classes are available from main module
1904+
try:
1905+
from .YourPlugin import YourPlugin, YourPluginError
1906+
except (AttributeError, ImportError):
1907+
YourPlugin = None
1908+
YourPluginError = None
1909+
```
1910+
1911+
**PREVENTION CHECKLIST** for new plugin integrations:
1912+
- [ ] Import ALL error classes from main `pyhelios` module, not submodules
1913+
- [ ] Add plugin error classes to main module imports in `__init__.py`
1914+
- [ ] Use duck typing for Context validation (see pattern above)
1915+
- [ ] Test both individual plugin tests AND full test suite
1916+
- [ ] Verify no failing tests when plugin unavailable (proper skipping)
1917+
1918+
**Debugging Commands**:
1919+
```bash
1920+
# Test individual plugin tests
1921+
pytest tests/test_yourplugin.py -v
1922+
1923+
# Test with other plugin tests to check for contamination
1924+
pytest tests/test_yourplugin.py tests/test_energybalance.py tests/test_radiation_model.py -v
1925+
1926+
# Run full test suite to verify no contamination
1927+
pytest --tb=short -x --maxfail=3
1928+
1929+
# Check for import consistency issues
1930+
python -c "
1931+
from pyhelios import YourPluginError, HeliosError
1932+
print('YourPluginError module:', YourPluginError.__module__)
1933+
print('HeliosError module:', HeliosError.__module__)
1934+
print('Inheritance check:', issubclass(YourPluginError, HeliosError))
1935+
"
1936+
```
1937+
1938+
**Resolution Verification**:
1939+
After implementing the fix, expect:
1940+
- βœ… All tests pass individually: `pytest tests/test_yourplugin.py -v`
1941+
- βœ… All tests pass in full suite: `pytest --tb=short`
1942+
- βœ… Zero test failures due to state contamination
1943+
- βœ… Proper test skipping when plugins unavailable
1944+
18461945
**Debug Plugin Detection**:
18471946
```python
18481947
# Test the plugin detection logic

β€Ždocs/plugin_radiation.mdβ€Ž

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ The radiation plugin requires:
1616
## Basic Usage
1717

1818
```python
19-
from pyhelios import Context, RadiationModel, RadiationModelError
19+
from pyhelios import Context, RadiationModel
2020
from pyhelios.types import *
2121

2222
# Create context with geometry
@@ -370,9 +370,7 @@ try:
370370
# Access band-specific data stored by radiation model
371371
for band in ["PAR", "NIR", "SW"]:
372372
print(f"Band {band} results available in primitive data: radiation_flux_{band}")
373-
374-
except RadiationModelError as e:
375-
print(f"Radiation simulation failed: {e}")
373+
376374
```
377375

378376
### Data Storage

0 commit comments

Comments
Β (0)