try fix shutdown

This commit is contained in:
pdxlocations
2026-03-21 21:13:53 -07:00
parent b4b084b627
commit 480c32ba56
2 changed files with 90 additions and 6 deletions

View File

@@ -6,19 +6,26 @@ import sys
import traceback
import contact.ui.default_config as config
from contact.utilities.input_handlers import get_list_input
from contact.utilities.i18n import t
from contact.ui.dialog import dialog
from contact.utilities.i18n import t
from contact.ui.colors import setup_colors
from contact.ui.splash import draw_splash
from contact.ui.control_ui import set_region, settings_menu
from contact.ui.dialog import dialog
from contact.ui.splash import draw_splash
from contact.utilities.arg_parser import setup_parser
from contact.utilities.i18n import t
from contact.utilities.input_handlers import get_list_input
from contact.utilities.interfaces import initialize_interface, reconnect_interface
def close_interface(interface: object) -> None:
if interface is None:
return
with contextlib.suppress(Exception):
interface.close()
def main(stdscr: curses.window) -> None:
output_capture = io.StringIO()
interface = None
try:
with contextlib.redirect_stdout(output_capture), contextlib.redirect_stderr(output_capture):
setup_colors()
@@ -39,7 +46,7 @@ def main(stdscr: curses.window) -> None:
)
if confirmation == "Yes":
set_region(interface)
interface.close()
close_interface(interface)
draw_splash(stdscr)
interface = reconnect_interface(args)
stdscr.clear()
@@ -52,6 +59,8 @@ def main(stdscr: curses.window) -> None:
logging.error("Traceback: %s", traceback.format_exc())
logging.error("Console output before crash:\n%s", console_output)
raise
finally:
close_interface(interface)
def ensure_min_rows(stdscr: curses.window, min_rows: int = 11) -> None:

75
tests/test_settings.py Normal file
View File

@@ -0,0 +1,75 @@
from argparse import Namespace
from types import SimpleNamespace
import unittest
from unittest import mock
import contact.settings as settings
class SettingsRuntimeTests(unittest.TestCase):
def test_main_closes_interface_after_normal_settings_exit(self) -> None:
stdscr = mock.Mock()
args = Namespace()
interface = mock.Mock()
interface.localNode = SimpleNamespace(localConfig=SimpleNamespace(lora=SimpleNamespace(region=1)))
with mock.patch.object(settings, "setup_colors"):
with mock.patch.object(settings, "ensure_min_rows"):
with mock.patch.object(settings, "draw_splash"):
with mock.patch.object(settings.curses, "curs_set"):
with mock.patch.object(settings, "setup_parser") as setup_parser:
with mock.patch.object(settings, "initialize_interface", return_value=interface):
with mock.patch.object(settings, "settings_menu") as settings_menu:
setup_parser.return_value.parse_args.return_value = args
settings.main(stdscr)
settings_menu.assert_called_once_with(stdscr, interface)
interface.close.assert_called_once_with()
def test_main_closes_reconnected_interface_after_region_reset(self) -> None:
stdscr = mock.Mock()
args = Namespace()
old_interface = mock.Mock()
old_interface.localNode = SimpleNamespace(localConfig=SimpleNamespace(lora=SimpleNamespace(region=0)))
new_interface = mock.Mock()
new_interface.localNode = SimpleNamespace(localConfig=SimpleNamespace(lora=SimpleNamespace(region=1)))
with mock.patch.object(settings, "setup_colors"):
with mock.patch.object(settings, "ensure_min_rows"):
with mock.patch.object(settings, "draw_splash"):
with mock.patch.object(settings.curses, "curs_set"):
with mock.patch.object(settings, "setup_parser") as setup_parser:
with mock.patch.object(settings, "initialize_interface", return_value=old_interface):
with mock.patch.object(settings, "get_list_input", return_value="Yes"):
with mock.patch.object(settings, "set_region") as set_region:
with mock.patch.object(
settings, "reconnect_interface", return_value=new_interface
) as reconnect_interface:
with mock.patch.object(settings, "settings_menu") as settings_menu:
setup_parser.return_value.parse_args.return_value = args
settings.main(stdscr)
set_region.assert_called_once_with(old_interface)
reconnect_interface.assert_called_once_with(args)
settings_menu.assert_called_once_with(stdscr, new_interface)
old_interface.close.assert_called_once_with()
new_interface.close.assert_called_once_with()
def test_main_closes_interface_when_settings_menu_raises(self) -> None:
stdscr = mock.Mock()
args = Namespace()
interface = mock.Mock()
interface.localNode = SimpleNamespace(localConfig=SimpleNamespace(lora=SimpleNamespace(region=1)))
with mock.patch.object(settings, "setup_colors"):
with mock.patch.object(settings, "ensure_min_rows"):
with mock.patch.object(settings, "draw_splash"):
with mock.patch.object(settings.curses, "curs_set"):
with mock.patch.object(settings, "setup_parser") as setup_parser:
with mock.patch.object(settings, "initialize_interface", return_value=interface):
with mock.patch.object(settings, "settings_menu", side_effect=RuntimeError("boom")):
setup_parser.return_value.parse_args.return_value = args
with self.assertRaises(RuntimeError):
settings.main(stdscr)
interface.close.assert_called_once_with()