# Critical Behavior Patterns - Testing Guide **Purpose:** This documentation lists essential behavior patterns that must be tested to ensure production-quality code and prevent resource leaks. **Last Updated:** 2025-11-22 **Test Coverage:** 41 tests implemented (100% of critical patterns) ## 🎯 Why Are These Tests Critical? Home Assistant integrations run **continuously** in the background. Resource leaks lead to: - **Memory Leaks**: RAM usage grows over days/weeks until HA becomes unstable - **Callback Leaks**: Listeners remain registered after entity removal → CPU load increases - **Timer Leaks**: Timers continue running after unload → unnecessary background tasks - **File Handle Leaks**: Storage files remain open → system resources exhausted ## ✅ Test Categories ### 1. Resource Cleanup (Memory Leak Prevention) **File:** `tests/test_resource_cleanup.py` #### 1.1 Listener Cleanup ✅ **What is tested:** - Time-sensitive listeners are correctly removed (`async_add_time_sensitive_listener()`) - Minute-update listeners are correctly removed (`async_add_minute_update_listener()`) - Lifecycle callbacks are correctly unregistered (`register_lifecycle_callback()`) - Sensor cleanup removes ALL registered listeners - Binary sensor cleanup removes ALL registered listeners **Why critical:** - Each registered listener holds references to Entity + Coordinator - Without cleanup: Entities are not freed by GC → Memory Leak - With 80+ sensors × 3 listener types = 240+ callbacks that must be cleanly removed **Code Locations:** - `coordinator/listeners.py` → `async_add_time_sensitive_listener()`, `async_add_minute_update_listener()` - `coordinator/core.py` → `register_lifecycle_callback()` - `sensor/core.py` → `async_will_remove_from_hass()` - `binary_sensor/core.py` → `async_will_remove_from_hass()` #### 1.2 Timer Cleanup ✅ **What is tested:** - Quarter-hour timer is cancelled and reference cleared - Minute timer is cancelled and reference cleared - Both timers are cancelled together - Cleanup works even when timers are `None` **Why critical:** - Uncancelled timers continue running after integration unload - HA's `async_track_utc_time_change()` creates persistent callbacks - Without cleanup: Timers keep firing → CPU load + unnecessary coordinator updates **Code Locations:** - `coordinator/listeners.py` → `cancel_timers()` - `coordinator/core.py` → `async_shutdown()` #### 1.3 Config Entry Cleanup ✅ **What is tested:** - Options update listener is registered via `async_on_unload()` - Cleanup function is correctly passed to `async_on_unload()` **Why critical:** - `entry.add_update_listener()` registers permanent callback - Without `async_on_unload()`: Listener remains active after reload → duplicate updates - Pattern: `entry.async_on_unload(entry.add_update_listener(handler))` **Code Locations:** - `coordinator/core.py` → `__init__()` (listener registration) - `__init__.py` → `async_unload_entry()` ### 2. Cache Invalidation ✅ **File:** `tests/test_resource_cleanup.py` #### 2.1 Config Cache Invalidation **What is tested:** - DataTransformer config cache is invalidated on options change - PeriodCalculator config + period cache is invalidated - Trend calculator cache is cleared on coordinator update **Why critical:** - Stale config → Sensors use old user settings - Stale period cache → Incorrect best/peak price periods - Stale trend cache → Outdated trend analysis **Code Locations:** - `coordinator/data_transformation.py` → `invalidate_config_cache()` - `coordinator/periods.py` → `invalidate_config_cache()` - `sensor/calculators/trend.py` → `clear_trend_cache()` ### 3. Storage Cleanup ✅ **File:** `tests/test_resource_cleanup.py` + `tests/test_coordinator_shutdown.py` #### 3.1 Persistent Storage Removal **What is tested:** - Storage file is deleted on config entry removal - Cache is saved on shutdown (no data loss) **Why critical:** - Without storage removal: Old files remain after uninstallation - Without cache save on shutdown: Data loss on HA restart - Storage path: `.storage/tibber_prices.{entry_id}` **Code Locations:** - `__init__.py` → `async_remove_entry()` - `coordinator/core.py` → `async_shutdown()` ### 4. Timer Scheduling ✅ **File:** `tests/test_timer_scheduling.py` **What is tested:** - Quarter-hour timer is registered with correct parameters - Minute timer is registered with correct parameters - Timers can be re-scheduled (override old timer) - Midnight turnover detection works correctly **Why critical:** - Wrong timer parameters → Entities update at wrong times - Without timer override on re-schedule → Multiple parallel timers → Performance problem ### 5. Sensor-to-Timer Assignment ✅ **File:** `tests/test_sensor_timer_assignment.py` **What is tested:** - All `TIME_SENSITIVE_ENTITY_KEYS` are valid entity keys - All `MINUTE_UPDATE_ENTITY_KEYS` are valid entity keys - Both lists are disjoint (no overlap) - Sensor and binary sensor platforms are checked **Why critical:** - Wrong timer assignment → Sensors update at wrong times - Overlap → Duplicate updates → Performance problem ## 🚨 Additional Analysis (Nice-to-Have Patterns) These patterns were analyzed and classified as **not critical**: ### 6. Async Task Management **Current Status:** Fire-and-forget pattern for short tasks - `sensor/core.py` → Chart data refresh (short-lived, max 1-2 seconds) - `coordinator/core.py` → Cache storage (short-lived, max 100ms) **Why no tests needed:** - No long-running tasks (all < 2 seconds) - HA's event loop handles short tasks automatically - Task exceptions are already logged **If needed:** `_chart_refresh_task` tracking + cancel in `async_will_remove_from_hass()` ### 7. API Session Cleanup **Current Status:** ✅ Correctly implemented - `async_get_clientsession(hass)` is used (shared session) - No new sessions are created - HA manages session lifecycle automatically **Code:** `api/client.py` + `__init__.py` ### 8. Translation Cache Memory **Current Status:** ✅ Bounded cache - Max ~5-10 languages × 5KB = 50KB total - Module-level cache without re-loading - Practically no memory issue **Code:** `const.py` → `_TRANSLATIONS_CACHE`, `_STANDARD_TRANSLATIONS_CACHE` ### 9. Coordinator Data Structure Integrity **Current Status:** Manually tested via `./scripts/develop` - Midnight turnover works correctly (observed over several days) - Missing keys are handled via `.get()` with defaults - 80+ sensors access `coordinator.data` without errors **Structure:** ```python coordinator.data = { "user_data": {...}, "priceInfo": [...], # Flat list of all enriched intervals "currency": "EUR" # Top-level for easy access } ``` ### 10. Service Response Memory **Current Status:** HA's response lifecycle - HA automatically frees service responses after return - ApexCharts ~20KB response is one-time per call - No response accumulation in integration code **Code:** `services/apexcharts.py` ## 📊 Test Coverage Status ### ✅ Implemented Tests (41 total) | Category | Status | Tests | File | Coverage | |----------|--------|-------|------|----------| | Listener Cleanup | ✅ | 5 | `test_resource_cleanup.py` | 100% | | Timer Cleanup | ✅ | 4 | `test_resource_cleanup.py` | 100% | | Config Entry Cleanup | ✅ | 1 | `test_resource_cleanup.py` | 100% | | Cache Invalidation | ✅ | 3 | `test_resource_cleanup.py` | 100% | | Storage Cleanup | ✅ | 1 | `test_resource_cleanup.py` | 100% | | Storage Persistence | ✅ | 2 | `test_coordinator_shutdown.py` | 100% | | Timer Scheduling | ✅ | 8 | `test_timer_scheduling.py` | 100% | | Sensor-Timer Assignment | ✅ | 17 | `test_sensor_timer_assignment.py` | 100% | | **TOTAL** | **✅** | **41** | | **100% (critical)** | ### 📋 Analyzed but Not Implemented (Nice-to-Have) | Category | Status | Rationale | |----------|--------|-----------| | Async Task Management | 📋 | Fire-and-forget pattern used (no long-running tasks) | | API Session Cleanup | ✅ | Pattern correct (`async_get_clientsession` used) | | Translation Cache | ✅ | Cache size bounded (~50KB max for 10 languages) | | Data Structure Integrity | 📋 | Would add test time without finding real issues | | Service Response Memory | 📋 | HA automatically frees service responses | **Legend:** - ✅ = Fully tested or pattern verified correct - 📋 = Analyzed, low priority for testing (no known issues) ## 🎯 Development Status ### ✅ All Critical Patterns Tested All essential memory leak prevention patterns are covered by 41 tests: - ✅ Listeners are correctly removed (no callback leaks) - ✅ Timers are cancelled (no background task leaks) - ✅ Config entry cleanup works (no dangling listeners) - ✅ Caches are invalidated (no stale data issues) - ✅ Storage is saved and cleaned up (no data loss) - ✅ Timer scheduling works correctly (no update issues) - ✅ Sensor-timer assignment is correct (no wrong updates) ### 📋 Nice-to-Have Tests (Optional) If problems arise in the future, these tests can be added: 1. **Async Task Management** - Pattern analyzed (fire-and-forget for short tasks) 2. **Data Structure Integrity** - Midnight rotation manually tested 3. **Service Response Memory** - HA's response lifecycle automatic **Conclusion:** The integration has production-quality test coverage for all critical resource leak patterns. ## 🔍 How to Run Tests ```bash # Run all resource cleanup tests (14 tests) ./scripts/test tests/test_resource_cleanup.py -v # Run all critical pattern tests (41 tests) ./scripts/test tests/test_resource_cleanup.py tests/test_coordinator_shutdown.py \ tests/test_timer_scheduling.py tests/test_sensor_timer_assignment.py -v # Run all tests with coverage ./scripts/test --cov=custom_components.tibber_prices --cov-report=html # Type checking and linting ./scripts/check # Manual memory leak test # 1. Start HA: ./scripts/develop # 2. Monitor RAM: watch -n 1 'ps aux | grep home-assistant' # 3. Reload integration multiple times (HA UI: Settings → Devices → Tibber Prices → Reload) # 4. RAM should stabilize (not grow continuously) ``` ## 📚 References - **Home Assistant Cleanup Patterns**: https://developers.home-assistant.io/docs/integration_setup_failures/#cleanup - **Async Best Practices**: https://developers.home-assistant.io/docs/asyncio_101/ - **Memory Profiling**: https://docs.python.org/3/library/tracemalloc.html