Files
radar_system/python_app/tests/test_file_io.py
T
2026-10-05 16:46:28 +03:00

45 lines
2.0 KiB
Python

"""Settings commits retain valid files on failure and concurrent writes."""
from concurrent.futures import ThreadPoolExecutor
import json
from pathlib import Path
import tempfile
import unittest
from unittest import mock
from python_app.file_io import atomic_write_json
from python_app.orchestration.gui_session_state import GuiSessionStateStore
class AtomicJsonTest(unittest.TestCase):
def setUp(self) -> None:
directory = tempfile.TemporaryDirectory()
self.addCleanup(directory.cleanup)
self.path = Path(directory.name) / "settings.json"
atomic_write_json(self.path, {"old": True})
def test_failed_sync_or_commit_preserves_previous_file(self) -> None:
for operation in ('fsync', 'replace'):
with self.subTest(operation=operation), \
mock.patch(f'python_app.file_io.os.{operation}', side_effect=OSError('disk failure')), \
self.assertRaises(OSError):
atomic_write_json(self.path, {'new': True}, durable=True)
self.assertEqual(json.loads(self.path.read_text()), {'old': True})
self.assertEqual(list(self.path.parent.glob('*.tmp')), [])
def test_nonfinite_json_cannot_overwrite_settings(self) -> None:
with self.assertRaises(ValueError):
atomic_write_json(self.path, {'bad': float('nan')})
self.assertEqual(json.loads(self.path.read_text()), {'old': True})
def test_concurrent_writers_publish_complete_json_and_leave_no_temp_files(self) -> None:
payloads = [{'index': i, 'values': [i] * 1000} for i in range(32)]
with ThreadPoolExecutor(max_workers=8) as executor:
list(executor.map(lambda payload: atomic_write_json(self.path, payload), payloads))
self.assertIn(json.loads(self.path.read_text()), payloads)
self.assertEqual(list(self.path.parent.glob('*.tmp')), [])
def test_invalid_utf8_session_cache_uses_defaults(self) -> None:
self.path.write_bytes(b'\xff')
self.assertEqual(GuiSessionStateStore(self.path).load().last_profile_path, '')