mirror of
https://git.quad4.io/RNS-Things/MeshChatX.git
synced 2026-03-31 06:05:49 +00:00
- 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.
189 lines
6.4 KiB
Python
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)
|