mirror of
https://github.com/pdxlocations/contact.git
synced 2026-03-28 17:12:35 +01:00
Merge pull request #259 from pdxlocations:fix-shutdown
Fix interface shutdown handling
This commit is contained in:
@@ -6,19 +6,26 @@ import sys
|
|||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
import contact.ui.default_config as config
|
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.colors import setup_colors
|
||||||
from contact.ui.splash import draw_splash
|
|
||||||
from contact.ui.control_ui import set_region, settings_menu
|
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.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
|
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:
|
def main(stdscr: curses.window) -> None:
|
||||||
output_capture = io.StringIO()
|
output_capture = io.StringIO()
|
||||||
|
interface = None
|
||||||
try:
|
try:
|
||||||
with contextlib.redirect_stdout(output_capture), contextlib.redirect_stderr(output_capture):
|
with contextlib.redirect_stdout(output_capture), contextlib.redirect_stderr(output_capture):
|
||||||
setup_colors()
|
setup_colors()
|
||||||
@@ -39,7 +46,7 @@ def main(stdscr: curses.window) -> None:
|
|||||||
)
|
)
|
||||||
if confirmation == "Yes":
|
if confirmation == "Yes":
|
||||||
set_region(interface)
|
set_region(interface)
|
||||||
interface.close()
|
close_interface(interface)
|
||||||
draw_splash(stdscr)
|
draw_splash(stdscr)
|
||||||
interface = reconnect_interface(args)
|
interface = reconnect_interface(args)
|
||||||
stdscr.clear()
|
stdscr.clear()
|
||||||
@@ -52,6 +59,8 @@ def main(stdscr: curses.window) -> None:
|
|||||||
logging.error("Traceback: %s", traceback.format_exc())
|
logging.error("Traceback: %s", traceback.format_exc())
|
||||||
logging.error("Console output before crash:\n%s", console_output)
|
logging.error("Console output before crash:\n%s", console_output)
|
||||||
raise
|
raise
|
||||||
|
finally:
|
||||||
|
close_interface(interface)
|
||||||
|
|
||||||
|
|
||||||
def ensure_min_rows(stdscr: curses.window, min_rows: int = 11) -> None:
|
def ensure_min_rows(stdscr: curses.window, min_rows: int = 11) -> None:
|
||||||
|
|||||||
75
tests/test_settings.py
Normal file
75
tests/test_settings.py
Normal 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()
|
||||||
Reference in New Issue
Block a user