Files
MeshChatX/tests/backend/test_lifecycle.py
Sudo-Ivan 1eeeb1cb4e Improve crash recovery diagnostics and analysis
- Expanded the crash recovery module to include advanced diagnostic metrics such as system entropy, KL-Divergence, and manifold curvature.
- Improved root cause analysis by implementing a probabilistic approach with detailed suggestions for various failure scenarios.
- Added comprehensive tests to validate heuristic analysis, entropy calculations, and the robustness of the crash recovery logic.
- Updated existing tests to ensure accurate diagnosis and reporting of system states during exceptions.
2026-01-14 18:32:58 -06:00

189 lines
6.4 KiB
Python

import gc
import os
import shutil
import sqlite3
import tempfile
from unittest.mock import MagicMock, patch
import pytest
import RNS
from hypothesis import given, settings
from hypothesis import strategies as st
from meshchatx.src.backend.database.provider import DatabaseProvider
from meshchatx.src.backend.identity_context import IdentityContext
from meshchatx.src.backend.web_audio_bridge import WebAudioBridge
def test_database_provider_disposal():
"""Test that DatabaseProvider correctly closes connections."""
db_path = os.path.join(tempfile.gettempdir(), "test_disposal.db")
if os.path.exists(db_path):
os.remove(db_path)
# Ensure any existing singleton is cleared
DatabaseProvider._instance = None
try:
provider = DatabaseProvider.get_instance(db_path)
conn = provider.connection
assert isinstance(conn, sqlite3.Connection)
# Test close()
provider.close()
with pytest.raises(sqlite3.ProgrammingError, match="closed database"):
conn.execute("SELECT 1")
# Re-open
conn2 = provider.connection
assert conn2 is not conn
# Test close_all()
provider.close_all()
with pytest.raises(sqlite3.ProgrammingError, match="closed database"):
conn2.execute("SELECT 1")
finally:
if os.path.exists(db_path):
try:
os.remove(db_path)
except Exception:
pass
DatabaseProvider._instance = None
def test_web_audio_bridge_disposal():
"""Test that WebAudioBridge correctly manages clients and cleanup."""
mock_tele_mgr = MagicMock()
mock_config_mgr = MagicMock()
bridge = WebAudioBridge(mock_tele_mgr, mock_config_mgr)
mock_client = MagicMock()
mock_tele = MagicMock()
mock_tele.active_call = True
mock_tele_mgr.telephone = mock_tele
with (
patch("meshchatx.src.backend.web_audio_bridge.WebAudioSource"),
patch("meshchatx.src.backend.web_audio_bridge.WebAudioSink"),
patch("meshchatx.src.backend.web_audio_bridge.Tee"),
patch("meshchatx.src.backend.web_audio_bridge.Pipeline"),
):
bridge.attach_client(mock_client)
assert mock_client in bridge.clients
bridge.on_call_ended()
assert bridge.tx_source is None
assert bridge.rx_sink is None
assert len(bridge.clients) == 0
def test_identity_context_teardown_completeness():
"""Verify that teardown cleans up all major components."""
mock_identity = MagicMock(spec=RNS.Identity)
mock_identity.hash = b"test_hash_32_bytes_long_01234567"
mock_identity.get_private_key.return_value = b"mock_pk"
mock_app = MagicMock()
mock_app.storage_dir = tempfile.mkdtemp()
with (
patch("meshchatx.src.backend.identity_context.Database"),
patch("meshchatx.src.backend.identity_context.ConfigManager"),
patch("meshchatx.src.backend.identity_context.create_lxmf_router"),
patch("meshchatx.src.backend.identity_context.IntegrityManager"),
patch(
"meshchatx.src.backend.identity_context.AutoPropagationManager",
),
):
context = IdentityContext(mock_identity, mock_app)
context.start_background_threads = MagicMock()
context.register_announce_handlers = MagicMock()
context.setup()
# Capture instances
db_instance = context.database
auto_prop_instance = context.auto_propagation_manager
context.teardown()
# Verify component cleanup
db_instance._checkpoint_and_close.assert_called()
auto_prop_instance.stop.assert_called()
assert context.running is False
def test_identity_context_memory_leak():
"""Verify that IdentityContext can be garbage collected after teardown."""
mock_identity = MagicMock(spec=RNS.Identity)
mock_identity.hash = b"leak_test_hash_32_bytes_long_012"
mock_identity.get_private_key.return_value = b"mock_pk"
mock_app = MagicMock()
mock_app.storage_dir = tempfile.mkdtemp()
import weakref
# We use a list to store the ref so we can access it after the function scope
leak_ref = []
def run_lifecycle():
with (
patch("meshchatx.src.backend.identity_context.Database"),
patch("meshchatx.src.backend.identity_context.ConfigManager"),
patch("meshchatx.src.backend.identity_context.create_lxmf_router"),
patch("meshchatx.src.backend.identity_context.IntegrityManager"),
patch("RNS.Transport"),
):
context = IdentityContext(mock_identity, mock_app)
context.start_background_threads = MagicMock()
context.register_announce_handlers = MagicMock()
context.setup()
context.teardown()
leak_ref.append(weakref.ref(context))
# End of with block and function scope should clear 'context'
run_lifecycle()
# Break any potential cycles in the app mock which might have captured the context
mock_app.reset_mock()
# Multiple collection rounds
for _ in range(5):
gc.collect()
# Check if it was collected
assert leak_ref[0]() is None, "IdentityContext was not garbage collected"
@settings(deadline=None)
@given(st.integers(min_value=1, max_value=3))
def test_identity_context_repeated_lifecycle(n):
"""Fuzz the lifecycle by repeating setup/teardown multiple times with new instances."""
mock_identity = MagicMock(spec=RNS.Identity)
mock_identity.hash = b"fuzz_hash_32_bytes_long_01234567"
mock_identity.get_private_key.return_value = b"mock_pk"
mock_app = MagicMock()
mock_app.storage_dir = tempfile.mkdtemp()
with (
patch("meshchatx.src.backend.identity_context.Database"),
patch("meshchatx.src.backend.identity_context.ConfigManager"),
patch("meshchatx.src.backend.identity_context.create_lxmf_router"),
patch("meshchatx.src.backend.identity_context.IntegrityManager"),
patch("RNS.Transport"),
):
for _ in range(n):
context = IdentityContext(mock_identity, mock_app)
context.start_background_threads = MagicMock()
context.register_announce_handlers = MagicMock()
context.setup()
assert context.running is True
context.teardown()
assert context.running is False
if os.path.exists(mock_app.storage_dir):
shutil.rmtree(mock_app.storage_dir)