diff --git a/.github/codecov.yml b/.github/codecov.yml
index db921cb1..3dfc9be6 100644
--- a/.github/codecov.yml
+++ b/.github/codecov.yml
@@ -2,5 +2,5 @@ coverage:
status:
project:
default:
- target: 100%
- threshold: 15%
+ target: 95%
+ threshold: 5%
diff --git a/README.md b/README.md
index 42d6d587..658dd9bb 100644
--- a/README.md
+++ b/README.md
@@ -12,8 +12,9 @@ without typing any command in terminal for [flash the firmware onto the device](
## Installing
-There are pre-built
-[releases](https://github.com/selfcustody/krux-installer/releases) for:
+[](https://github.com/selfcustody/krux-installer/releases)
+
+Available for:
* Linux:
* Debian-like;
@@ -23,7 +24,7 @@ There are pre-built
* intel processors;
* arm64 processors (M1/M2/M3).
-To build it from the source, please follow the steps below:
+## Build from source
* [System setup](/#system-setup)
* [Linux](/#linux)
diff --git a/e2e/test_000_base_screen.py b/e2e/test_000_base_screen.py
index 04036149..3164f8c9 100644
--- a/e2e/test_000_base_screen.py
+++ b/e2e/test_000_base_screen.py
@@ -1,4 +1,5 @@
import os
+import sys
from pathlib import Path
from unittest.mock import patch, call, MagicMock
from kivy.base import EventLoop, EventLoopBase
@@ -93,12 +94,21 @@ def test_static_open_settings(self, mock_get_ruunning_app):
BaseScreen.open_settings()
mock_get_ruunning_app.return_value.open_settings.assert_called_once()
+ @patch.object(EventLoopBase, "ensure_window", lambda x: None)
+ @patch("src.app.screens.base_screen.App.get_running_app")
+ def test_static_quit_app(self, mock_get_ruunning_app):
+ mock_get_ruunning_app.return_value = MagicMock()
+ mock_get_ruunning_app.return_value.stop = MagicMock()
+
+ BaseScreen.quit_app()
+ mock_get_ruunning_app.return_value.stop.assert_called_once()
+
@patch.object(EventLoopBase, "ensure_window", lambda x: None)
@patch("sys.platform", "linux")
@patch(
"src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
)
- def test_init_linux(self, mock_get_locale):
+ def test_init_linux_no_frozen(self, mock_get_locale):
screen = BaseScreen(wid="mock", name="Mock")
self.render(screen)
@@ -112,6 +122,31 @@ def test_init_linux(self, mock_get_locale):
mock_get_locale.assert_called_once()
+ @patch.object(EventLoopBase, "ensure_window", lambda x: None)
+ @patch("sys.platform", "linux")
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
+ )
+ def test_init_linux_frozen(self, mock_get_locale):
+ with patch.dict(
+ sys.__dict__, {"_MEIPASS": os.path.join("mock", "path"), "frozen": True}
+ ):
+ screen = BaseScreen(wid="mock", name="Mock")
+ self.render(screen)
+
+ # get your Window instance safely
+ EventLoop.ensure_window()
+ window = EventLoop.window
+
+ # your asserts
+ self.assertEqual(
+ screen.logo_img, os.path.join("mock", "path", "assets", "logo.png")
+ )
+ self.assertEqual(window.children[0], screen)
+ self.assertEqual(window.children[0].height, window.height)
+
+ mock_get_locale.assert_called_once()
+
@patch.object(EventLoopBase, "ensure_window", lambda x: None)
@patch("src.app.screens.base_screen.BaseScreen.get_locale")
def test_init_win32(self, mock_get_locale):
@@ -401,6 +436,40 @@ def test_update_screen_locale(self, mock_get_locale):
self.assertEqual(screen.locale, "mocked")
mock_get_locale.assert_called_once()
+ @patch.object(EventLoopBase, "ensure_window", lambda x: None)
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
+ )
+ @patch("src.app.screens.base_screen.BaseScreen.redirect_exception")
+ def test_fail_update_screen_locale(self, mock_redirect_exception, mock_get_locale):
+ screen = BaseScreen(wid="mock", name="Mock")
+ screen.make_grid(wid="mock_grid", rows=1)
+ screen.make_button(
+ row=0,
+ wid="mock_button",
+ root_widget="mock_grid",
+ text="Mocked button",
+ font_factor=32,
+ halign=None,
+ on_press=MagicMock(),
+ on_release=MagicMock(),
+ on_ref_press=MagicMock(),
+ )
+ setattr(screen, "update", MagicMock())
+ self.render(screen)
+ self.assertEqual(screen.locale, "en_US.UTF-8")
+
+ screen.update_screen(
+ name="MockedScreen",
+ key="locale",
+ value=None,
+ allowed_screens=("MockedScreen",),
+ on_update=MagicMock(),
+ )
+
+ mock_get_locale.assert_called_once()
+ mock_redirect_exception.assert_called_once()
+
@patch.object(EventLoopBase, "ensure_window", lambda x: None)
@patch("src.app.screens.base_screen.BaseScreen.get_locale")
@patch("src.app.screens.base_screen.Color")
diff --git a/e2e/test_001_greetings_screen.py b/e2e/test_001_greetings_screen.py
index 82647de0..9c9137af 100644
--- a/e2e/test_001_greetings_screen.py
+++ b/e2e/test_001_greetings_screen.py
@@ -7,6 +7,9 @@
from kivy.tests.common import GraphicUnitTest
from src.app.screens.greetings_screen import GreetingsScreen
+# to be used in mocking grp
+import src.app.screens.greetings_screen
+
class TestAboutScreen(GraphicUnitTest):
@@ -317,40 +320,50 @@ def test_fail_get_os_dialout_group_file_not_found(
mock_redirect_exception.assert_called()
@mark.skipif(
- sys.platform in ("win32", "darwin"),
- reason="does not run on windows or macos",
+ sys.platform in ("win32"),
+ reason="does not run on windows",
)
+ @patch("sys.platform", "linux") # Patch platform to Linux
@patch.object(EventLoopBase, "ensure_window", lambda x: None)
@patch(
"src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
)
- @patch("src.app.screens.greetings_screen.grp")
- def test_is_user_not_in_dialout(self, mock_grp, mock_get_locale):
+ def test_is_user_not_in_dialout(self, mock_get_locale):
+ # Create a mock grp module
+ mock_grp = MagicMock()
mock_grp.getgrall.return_value = [
MagicMock(gr_name="dialout", gr_passwd="x", gr_gid=1234, gr_mem=["brltty"])
]
- # mock_grp.getgrall.return_value[0].__getitem__ = MagicMock(
- # return_value=['dialout', 'x', 1234, ['brltty']]
- # )
+
+ # Temporarily add the mock grp to greetings_screen's global namespace
+ setattr(src.app.screens.greetings_screen, "grp", mock_grp)
+
+ # Initialize the screen and call the method to test
screen = GreetingsScreen()
is_in_dialout = screen.is_user_in_dialout_group(
user="mockuser", group="dialout"
)
- self.assertEqual(is_in_dialout, False)
+ # Assertions
+ self.assertEqual(is_in_dialout, False)
mock_get_locale.assert_called()
mock_grp.getgrall.assert_called()
+ # Clean up by removing the mock from the module's namespace
+ delattr(src.app.screens.greetings_screen, "grp")
+
@mark.skipif(
- sys.platform in ("win32", "darwin"),
- reason="does not run on windows or macos",
+ sys.platform in ("win32"),
+ reason="does not run on windows",
)
+ @patch("sys.platform", "linux") # Patch platform to Linux
@patch.object(EventLoopBase, "ensure_window", lambda x: None)
@patch(
"src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
)
- @patch("src.app.screens.greetings_screen.grp")
- def test_is_user_in_dialout(self, mock_grp, mock_get_locale):
+ def test_is_user_in_dialout(self, mock_get_locale):
+ # Create a mock grp module
+ mock_grp = MagicMock()
mock_grp.getgrall.return_value = [
MagicMock(
gr_name="dialout",
@@ -359,6 +372,11 @@ def test_is_user_in_dialout(self, mock_grp, mock_get_locale):
gr_mem=["brltty", "mockuser"],
)
]
+
+ # Temporarily add the mock grp to greetings_screen's global namespace
+ setattr(src.app.screens.greetings_screen, "grp", mock_grp)
+
+ # Initialize the screen and call the method to test
screen = GreetingsScreen()
is_in_dialout = screen.is_user_in_dialout_group(
user="mockuser", group="dialout"
@@ -459,9 +477,10 @@ def test_pass_check_dialout_permission_not_linux(
mock_schedule_once.assert_called()
@mark.skipif(
- sys.platform in ("win32", "darwin"),
+ sys.platform in ("win32"),
reason="does not run on windows or darwin",
)
+ @patch("sys.platform", "linux")
@patch.object(EventLoopBase, "ensure_window", lambda x: None)
@patch("src.app.screens.greetings_screen.os.environ.get", return_value="mockuser")
@patch(
@@ -501,9 +520,10 @@ def test_check_dialout_permission_not_in_dialout(
mock_in_dialout.assert_called()
@mark.skipif(
- sys.platform in ("win32", "darwin"),
+ sys.platform in ("win32"),
reason="does not run on windows or darwin",
)
+ @patch("sys.platform", "linux")
@patch.object(EventLoopBase, "ensure_window", lambda x: None)
@patch("src.app.screens.greetings_screen.os.environ.get", return_value="mockuser")
@patch(
diff --git a/e2e/test_002_ask_permission_dialout_screen.py b/e2e/test_002_ask_permission_dialout_screen.py
index 870b6bb7..525908da 100644
--- a/e2e/test_002_ask_permission_dialout_screen.py
+++ b/e2e/test_002_ask_permission_dialout_screen.py
@@ -11,7 +11,7 @@
# WARNING: Do not run these tests on windows
# they will break because it do not have the builtin 'grp' module
@mark.skipif(
- sys.platform in ("win32", "darwin"),
+ sys.platform in ("win32"),
reason="does not run on windows or macos",
)
class TestAskPermissionDialoutScreen(GraphicUnitTest):
@@ -29,11 +29,17 @@ def setUpClass(cls):
def teardown_class(cls):
EventLoop.exit()
+ @patch("sys.platform", "linux")
@patch.object(EventLoopBase, "ensure_window", lambda x: None)
@patch(
"src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
)
- def test_render_label(self, mock_get_locale):
+ @patch(
+ "src.app.screens.ask_permission_dialout_screen.open",
+ new_callable=mock_open,
+ read_data="ID_LIKE=debian",
+ )
+ def test_render_label(self, open_mock, mock_get_locale):
screen = AskPermissionDialoutScreen()
screen.manager = MagicMock()
screen.manager.get_screen = MagicMock()
@@ -46,12 +52,19 @@ def test_render_label(self, mock_get_locale):
# patch assertions
mock_get_locale.assert_called_once()
+ open_mock.assert_called_once_with("/etc/os-release", mode="r", encoding="utf-8")
@patch.object(EventLoopBase, "ensure_window", lambda x: None)
+ @patch("sys.platform", "linux")
@patch(
"src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
)
- def test_on_update_user(self, mock_get_locale):
+ @patch(
+ "src.app.screens.ask_permission_dialout_screen.open",
+ new_callable=mock_open,
+ read_data="ID_LIKE=debian",
+ )
+ def test_on_update_user(self, open_mock, mock_get_locale):
screen = AskPermissionDialoutScreen()
screen.manager = MagicMock()
screen.manager.get_screen = MagicMock()
@@ -65,12 +78,19 @@ def test_on_update_user(self, mock_get_locale):
# patch assertions
mock_get_locale.assert_called_once()
+ open_mock.assert_called_once_with("/etc/os-release", mode="r", encoding="utf-8")
+ @patch("sys.platform", "linux")
@patch.object(EventLoopBase, "ensure_window", lambda x: None)
@patch(
"src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
)
- def test_on_update_distro(self, mock_get_locale):
+ @patch(
+ "src.app.screens.ask_permission_dialout_screen.open",
+ new_callable=mock_open,
+ read_data="ID_LIKE=debian",
+ )
+ def test_on_update_distro(self, open_mock, mock_get_locale):
screen = AskPermissionDialoutScreen()
screen.manager = MagicMock()
screen.manager.get_screen = MagicMock()
@@ -84,7 +104,9 @@ def test_on_update_distro(self, mock_get_locale):
# patch assertions
mock_get_locale.assert_called_once()
+ open_mock.assert_called_once_with("/etc/os-release", mode="r", encoding="utf-8")
+ @patch("sys.platform", "linux")
@patch.object(EventLoopBase, "ensure_window", lambda x: None)
@patch(
"src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
@@ -92,7 +114,12 @@ def test_on_update_distro(self, mock_get_locale):
@patch(
"src.app.screens.ask_permission_dialout_screen.AskPermissionDialoutScreen.show_warning"
)
- def test_on_update_screen(self, mock_show_warning, mock_get_locale):
+ @patch(
+ "src.app.screens.ask_permission_dialout_screen.open",
+ new_callable=mock_open,
+ read_data="ID_LIKE=debian",
+ )
+ def test_on_update_screen(self, open_mock, mock_show_warning, mock_get_locale):
screen = AskPermissionDialoutScreen()
screen.manager = MagicMock()
screen.manager.get_screen = MagicMock()
@@ -106,12 +133,19 @@ def test_on_update_screen(self, mock_show_warning, mock_get_locale):
# patch assertions
mock_get_locale.assert_called_once()
mock_show_warning.assert_called_once()
+ open_mock.assert_called_once_with("/etc/os-release", mode="r", encoding="utf-8")
+ @patch("sys.platform", "linux")
@patch.object(EventLoopBase, "ensure_window", lambda x: None)
@patch(
"src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
)
- def test_on_update_group(self, mock_get_locale):
+ @patch(
+ "src.app.screens.ask_permission_dialout_screen.open",
+ new_callable=mock_open,
+ read_data="ID_LIKE=debian",
+ )
+ def test_on_update_group(self, open_mock, mock_get_locale):
screen = AskPermissionDialoutScreen()
screen.manager = MagicMock()
screen.manager.get_screen = MagicMock()
@@ -125,6 +159,7 @@ def test_on_update_group(self, mock_get_locale):
# patch assertions
mock_get_locale.assert_called_once()
+ open_mock.assert_called_once_with("/etc/os-release", mode="r", encoding="utf-8")
@patch.object(EventLoopBase, "ensure_window", lambda x: None)
@patch(
@@ -181,8 +216,8 @@ def test_show_warning(self, mock_manager, open_mock, mock_get_locale):
mock_get_locale.assert_called_once()
open_mock.assert_called()
- @patch.object(EventLoopBase, "ensure_window", lambda x: None)
@patch("sys.platform", "linux")
+ @patch.object(EventLoopBase, "ensure_window", lambda x: None)
@patch(
"src.app.screens.ask_permission_dialout_screen.open",
new_callable=mock_open,
@@ -221,6 +256,53 @@ def test_press_allow_on_debian(self, mock_exec, mock_get_locale, open_mock):
callback=on_permission_created,
)
+ @patch("sys.platform", "linux")
+ @patch.object(EventLoopBase, "ensure_window", lambda x: None)
+ @patch(
+ "src.app.screens.ask_permission_dialout_screen.open",
+ new_callable=mock_open,
+ read_data="ID_LIKE=debian",
+ )
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
+ )
+ @patch(
+ "src.app.screens.ask_permission_dialout_screen.SudoerLinux.exec",
+ side_effect=Exception(),
+ )
+ @patch("src.app.screens.base_screen.BaseScreen.redirect_exception")
+ def test_fail_press_allow_on_debian(
+ self, mock_redirect_exception, mock_exec, mock_get_locale, open_mock
+ ):
+ screen = AskPermissionDialoutScreen()
+ screen.manager = MagicMock()
+ screen.manager.get_screen = MagicMock()
+ screen.bin = "mock"
+ screen.bin_args = ["-a", "-G"]
+ screen.group = "mockedgroup"
+ screen.user = "mockeduser"
+
+ self.render(screen)
+
+ # get your Window instance safely
+ EventLoop.ensure_window()
+ action = getattr(screen, f"on_ref_press_{screen.id}_label")
+ action("Allow")
+
+ # patch assertions
+ on_permission_created = getattr(
+ AskPermissionDialoutScreen, "on_permission_created"
+ )
+
+ open_mock.assert_called_once()
+ mock_get_locale.assert_called_once()
+ mock_exec.assert_called_once_with(
+ cmd=["/usr/sbin/usermod", "-a", "-G", "mockedgroup", "mockeduser"],
+ env={},
+ callback=on_permission_created,
+ )
+ mock_redirect_exception.assert_called()
+
@patch.object(EventLoopBase, "ensure_window", lambda x: None)
@patch("sys.platform", "linux")
@patch(
@@ -381,13 +463,18 @@ def test_press_allow_on_alpine(self, mock_exec, mock_get_locale, open_mock):
callback=on_permission_created,
)
- @patch.object(EventLoopBase, "ensure_window", lambda x: None)
@patch("sys.platform", "linux")
+ @patch.object(EventLoopBase, "ensure_window", lambda x: None)
@patch(
"src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
)
+ @patch(
+ "src.app.screens.ask_permission_dialout_screen.open",
+ new_callable=mock_open,
+ read_data="ID_LIKE=debian",
+ )
@patch("src.app.screens.base_screen.BaseScreen.quit_app")
- def test_press_deny(self, mock_quit_app, mock_get_locale):
+ def test_press_deny(self, mock_quit_app, open_mock, mock_get_locale):
screen = AskPermissionDialoutScreen()
screen.manager = MagicMock()
screen.manager.get_screen = MagicMock()
@@ -406,13 +493,19 @@ def test_press_deny(self, mock_quit_app, mock_get_locale):
mock_get_locale.assert_called_once()
mock_quit_app.assert_called_once()
+ open_mock.assert_called_once_with("/etc/os-release", mode="r", encoding="utf-8")
- @patch.object(EventLoopBase, "ensure_window", lambda x: None)
@patch("sys.platform", "linux")
+ @patch.object(EventLoopBase, "ensure_window", lambda x: None)
@patch(
"src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
)
- def test_on_permission_created(self, mock_get_locale):
+ @patch(
+ "src.app.screens.ask_permission_dialout_screen.open",
+ new_callable=mock_open,
+ read_data="ID_LIKE=debian",
+ )
+ def test_on_permission_created(self, open_mock, mock_get_locale):
screen = AskPermissionDialoutScreen()
screen.manager = MagicMock()
screen.manager.get_screen = MagicMock()
@@ -443,3 +536,4 @@ def test_on_permission_created(self, mock_get_locale):
# patch assertions
mock_get_locale.assert_called_once()
+ open_mock.assert_called_once_with("/etc/os-release", mode="r", encoding="utf-8")
diff --git a/e2e/test_003_main_screen.py b/e2e/test_003_main_screen.py
index 465d016e..3859f0a2 100644
--- a/e2e/test_003_main_screen.py
+++ b/e2e/test_003_main_screen.py
@@ -220,6 +220,22 @@ def test_update_version(self, mock_get_locale):
mock_get_locale.assert_has_calls(calls)
+ @patch.object(EventLoopBase, "ensure_window", lambda x: None)
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
+ )
+ @patch("src.app.screens.base_screen.BaseScreen.redirect_exception")
+ def test_fail_update_version(self, mock_redirect_exception, mock_get_locale):
+ screen = MainScreen()
+ self.render(screen)
+
+ # get your Window instance safely
+ EventLoop.ensure_window()
+
+ screen.update(name="SelectVersionScreen", key="version")
+ mock_redirect_exception.assert_called()
+ mock_get_locale.assert_called()
+
@patch.object(EventLoopBase, "ensure_window", lambda x: None)
@patch(
"src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
@@ -257,6 +273,22 @@ def test_update_device(self, mock_get_locale):
mock_get_locale.assert_any_call()
+ @patch.object(EventLoopBase, "ensure_window", lambda x: None)
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
+ )
+ @patch("src.app.screens.base_screen.BaseScreen.redirect_exception")
+ def test_fail_update_device(self, mock_redirect_exception, mock_get_locale):
+ screen = MainScreen()
+ self.render(screen)
+
+ # get your Window instance safely
+ EventLoop.ensure_window()
+
+ screen.update(name="SelectVersionScreen", key="device")
+ mock_redirect_exception.assert_called()
+ mock_get_locale.assert_called()
+
@patch.object(EventLoopBase, "ensure_window", lambda x: None)
@patch(
"src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
@@ -527,12 +559,10 @@ def test_on_press_can_flash_or_wipe(self, mock_get_locale, mock_set_background):
"src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
)
@patch("src.app.screens.base_screen.BaseScreen.get_destdir_assets")
- @patch("src.app.screens.main_screen.re.findall", side_effect=[True])
@patch("src.app.screens.main_screen.os.path.isfile", side_effect=[False])
def test_on_release_flash_to_download_stable_zip_screen(
self,
mock_isfile,
- mock_findall,
mock_get_destdir_assets,
mock_get_locale,
mock_manager,
@@ -561,10 +591,43 @@ def test_on_release_flash_to_download_stable_zip_screen(
mock_set_screen.assert_called_once_with(
name="DownloadStableZipScreen", direction="left"
)
- mock_findall.assert_called_once_with(r"^v\d+\.\d+\.\d$", "v24.03.0")
pattern = re.compile(r".*v24\.03\.0\.zip")
self.assertTrue(pattern.match(mock_isfile.call_args[0][0]))
+ @patch.object(EventLoopBase, "ensure_window", lambda x: None)
+ @patch("src.app.screens.main_screen.MainScreen.set_background")
+ @patch("src.app.screens.main_screen.MainScreen.manager")
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
+ )
+ @patch("src.app.screens.base_screen.BaseScreen.redirect_exception")
+ def test_fail_on_release_flash_to_download_stable_zip_screen(
+ self,
+ mock_redirect_exception,
+ mock_get_locale,
+ mock_manager,
+ mock_set_background,
+ ):
+ mock_manager.get_screen = MagicMock()
+
+ screen = MainScreen()
+ screen.version = "mocked"
+ self.render(screen)
+
+ # get your Window instance safely
+ EventLoop.ensure_window()
+ window = EventLoop.window
+ grid = window.children[0].children[0]
+ button = grid.children[3]
+
+ screen.update(name="SelectVersionScreen", key="device", value="m5stickv")
+ action = getattr(screen.__class__, f"on_release_{button.id}")
+ action(button)
+
+ mock_get_locale.assert_any_call()
+ mock_set_background.assert_called_once_with(wid="main_flash", rgba=(0, 0, 0, 1))
+ mock_redirect_exception.assert_called()
+
@patch.object(EventLoopBase, "ensure_window", lambda x: None)
@patch("src.app.screens.main_screen.MainScreen.set_background")
@patch("src.app.screens.main_screen.MainScreen.set_screen")
@@ -573,12 +636,10 @@ def test_on_release_flash_to_download_stable_zip_screen(
"src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
)
@patch("src.app.screens.base_screen.BaseScreen.get_destdir_assets")
- @patch("src.app.screens.main_screen.re.findall", side_effect=[True])
@patch("src.app.screens.main_screen.os.path.isfile", side_effect=[True])
def test_on_release_flash_to_warning_already_downloaded_zip_screen(
self,
mock_isfile,
- mock_findall,
mock_get_destdir_assets,
mock_get_locale,
mock_manager,
@@ -607,7 +668,6 @@ def test_on_release_flash_to_warning_already_downloaded_zip_screen(
mock_set_screen.assert_called_once_with(
name="WarningAlreadyDownloadedScreen", direction="left"
)
- mock_findall.assert_called_once_with(r"^v\d+\.\d+\.\d$", "v24.03.0")
pattern = re.compile(r".*v24\.03\.0\.zip")
self.assertTrue(pattern.match(mock_isfile.call_args[0][0]))
@@ -618,10 +678,8 @@ def test_on_release_flash_to_warning_already_downloaded_zip_screen(
@patch(
"src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
)
- @patch("src.app.screens.main_screen.re.findall", side_effect=[False, True])
def test_on_release_flash_to_download_beta_screen(
self,
- mock_findall,
mock_get_locale,
mock_manager,
mock_set_screen,
@@ -644,12 +702,6 @@ def test_on_release_flash_to_download_beta_screen(
action(button)
mock_get_locale.assert_any_call()
- mock_findall.assert_has_calls(
- [
- call("^v\\d+\\.\\d+\\.\\d$", "odudex/krux_binaries"),
- call(r"^odudex/krux_binaries", "odudex/krux_binaries"),
- ]
- )
mock_set_background.assert_called_once_with(wid="main_flash", rgba=(0, 0, 0, 1))
mock_set_screen.assert_called_once_with(
name="DownloadBetaScreen", direction="left"
diff --git a/e2e/test_008_about_screen.py b/e2e/test_008_about_screen.py
index e3393dcb..9bb3a677 100644
--- a/e2e/test_008_about_screen.py
+++ b/e2e/test_008_about_screen.py
@@ -43,7 +43,7 @@ def test_render_main_screen(self, mock_get_locale):
text = "".join(
[
- "[ref=SourceCode][b]v0.0.20-beta[/b][/ref]",
+ "[ref=SourceCode][b]v0.0.20[/b][/ref]",
"\n",
"\n",
"follow us on X: ",
@@ -81,7 +81,7 @@ def test_update_locale(self, mock_get_locale):
text = "".join(
[
- "[ref=SourceCode][b]v0.0.20-beta[/b][/ref]",
+ "[ref=SourceCode][b]v0.0.20[/b][/ref]",
"\n",
"\n",
"siga-nos no X: ",
diff --git a/e2e/test_016_unzip_stable_screen.py b/e2e/test_016_unzip_stable_screen.py
index a96a8d60..39bd9968 100644
--- a/e2e/test_016_unzip_stable_screen.py
+++ b/e2e/test_016_unzip_stable_screen.py
@@ -245,24 +245,22 @@ def test_update_airgap_button(self, mock_get_locale, mock_get_destdir_assets):
screen.update(name="VerifyStableZipScreen", key="version", value="v0.0.1")
screen.update(name="VerifyStableZipScreen", key="airgap-button")
- # p = os.path.join("mock", "krux-v0.0.1", "maixpy_mock", "firmware.bin")
- # text = "".join(
- # [
- # "[color=#333333]",
- # "Air-gapped update with (soon)",
- # "[/color]",
- # "\n",
- # "[color=#333333]",
- # p,
- # "[/color]",
- # ]
- # )
+ p = os.path.join("mock", "krux-v0.0.1", "maixpy_mock", "firmware.bin")
+ text = "".join(
+ [
+ "Air-gapped update with",
+ "\n",
+ "[color=#efcc00]",
+ p,
+ "[/color]",
+ ]
+ )
# default assertions
- # button = screen.ids[f"{screen.id}_airgap_button"]
- # self.assertEqual(screen.ids[f"{screen.id}_airgap_button"].text, text)
- # self.assertTrue(hasattr(screen.__class__, f"on_press_{button.id}"))
- # self.assertTrue(hasattr(screen.__class__, f"on_release_{button.id}"))
+ button = screen.ids[f"{screen.id}_airgap_button"]
+ self.assertEqual(screen.ids[f"{screen.id}_airgap_button"].text, text)
+ self.assertTrue(hasattr(screen.__class__, f"on_press_{button.id}"))
+ self.assertTrue(hasattr(screen.__class__, f"on_release_{button.id}"))
# patch assertions
mock_get_destdir_assets.assert_any_call()
@@ -335,30 +333,28 @@ def test_on_press_airgap_button(
screen.update(name="VerifyStableZipScreen", key="device", value="mock")
screen.update(name="VerifyStableZipScreen", key="version", value="v0.0.1")
screen.update(name="VerifyStableZipScreen", key="airgap-button")
- # button = screen.ids[f"{screen.id}_airgap_button"]
- # action = getattr(screen.__class__, f"on_press_{button.id}")
- # action(button)
-
- # p = os.path.join("mock", "krux-v0.0.1", "maixpy_mock", "firmware.bin")
- # text = "".join(
- # [
- # "[color=#333333]",
- # "Air-gapped update with (soon)",
- # "[/color]",
- # "\n",
- # "[color=#333333]",
- # p,
- # "[/color]",
- # ]
- # )
+ button = screen.ids[f"{screen.id}_airgap_button"]
+ action = getattr(screen.__class__, f"on_press_{button.id}")
+ action(button)
+
+ p = os.path.join("mock", "krux-v0.0.1", "maixpy_mock", "firmware.bin")
+ text = "".join(
+ [
+ "Extracting",
+ "\n",
+ "[color=#efcc00]",
+ p,
+ "[/color]",
+ ]
+ )
# default assertions
- # self.assertEqual(button.text, text)
+ self.assertEqual(button.text, text)
# patch assertions
mock_get_destdir_assets.assert_called_once()
mock_get_locale.assert_called()
- mock_set_background.assert_not_called()
+ mock_set_background.assert_called()
@patch.object(EventLoopBase, "ensure_window", lambda x: None)
@patch(
@@ -436,19 +432,19 @@ def test_on_release_flash_button(
"src.app.screens.base_screen.BaseScreen.get_destdir_assets", return_value="mock"
)
@patch("src.app.screens.unzip_stable_screen.UnzipStableScreen.set_background")
- # @patch("src.app.screens.unzip_stable_screen.FirmwareUnzip")
+ @patch("src.app.screens.unzip_stable_screen.FirmwareUnzip")
@patch("src.app.screens.unzip_stable_screen.UnzipStableScreen.manager")
@patch("src.app.screens.unzip_stable_screen.time.sleep")
def test_on_release_airgapped_button(
self,
mock_sleep,
mock_manager,
- # mock_firmware_unzip,
+ mock_firmware_unzip,
mock_set_background,
mock_get_destdir_assets,
mock_get_locale,
):
- # mock_firmware_unzip.load = MagicMock()
+ mock_firmware_unzip.load = MagicMock()
mock_manager.get_screen = MagicMock()
screen = UnzipStableScreen()
@@ -463,13 +459,13 @@ def test_on_release_airgapped_button(
screen.update(name="VerifyStableZipScreen", key="airgap-button")
button = screen.ids[f"{screen.id}_airgap_button"]
- # action = getattr(screen.__class__, f"on_release_{button.id}")
- # action(button)
+ action = getattr(screen.__class__, f"on_release_{button.id}")
+ action(button)
p = os.path.join("mock", "krux-v0.0.1", "maixpy_mock", "firmware.bin")
text = "".join(
[
- "Air-gapped update with",
+ "Extracted",
"\n",
"[color=#efcc00]",
p,
@@ -483,7 +479,7 @@ def test_on_release_airgapped_button(
# patch assertions
mock_get_destdir_assets.assert_called_once()
mock_get_locale.assert_called()
- # mock_firmware_unzip.assert_not_called()
- mock_set_background.assert_not_called()
- mock_manager.get_screen.assert_not_called()
- mock_sleep.assert_not_called()
+ mock_firmware_unzip.assert_called()
+ mock_set_background.assert_called()
+ mock_manager.get_screen.assert_called()
+ mock_sleep.assert_called()
diff --git a/e2e/test_019_config_krux_installer.py b/e2e/test_019_config_krux_installer.py
index 04b243cd..0f98dd2d 100644
--- a/e2e/test_019_config_krux_installer.py
+++ b/e2e/test_019_config_krux_installer.py
@@ -13,6 +13,65 @@ class TestConfigKruxInstaller(GraphicUnitTest):
def teardown_class(cls):
EventLoop.exit()
+ @patch("src.app.config_krux_installer.LabelBase.register")
+ @patch(
+ "src.app.config_krux_installer.os.path.abspath",
+ side_effect=[
+ os.path.join("mock", "path", "assets"),
+ os.path.join("mock", "path", "i18n"),
+ ],
+ )
+ def test_init(self, mock_abspath, mock_register):
+ with patch.dict(sys.__dict__, {"frozen": False}):
+ ConfigKruxInstaller()
+ mock_register.assert_called_once_with(
+ "Roboto",
+ os.path.join(
+ "mock", "path", "assets", "NotoSansCJK_CY_JP_SC_KR_VI_Krux.ttf"
+ ),
+ )
+ mock_abspath.assert_called()
+
+ @patch("src.app.config_krux_installer.kv_resources.resource_add_path")
+ @patch("src.app.config_krux_installer.LabelBase.register")
+ def test_init_frozen_branch(self, mock_register, mock_resource_add_path):
+ with patch.dict(
+ sys.__dict__, {"_MEIPASS": os.path.join("mocked", "path"), "frozen": True}
+ ):
+ installer = ConfigKruxInstaller()
+ mock_resource_add_path.assert_called_with(os.path.join("mocked", "path"))
+ mock_register.assert_called_once_with(
+ "Roboto",
+ os.path.join(
+ "mocked", "path", "assets", "NotoSansCJK_CY_JP_SC_KR_VI_Krux.ttf"
+ ),
+ )
+ self.assertEqual(
+ installer.assets_path, os.path.join("mocked", "path", "assets")
+ )
+ self.assertEqual(
+ installer.i18n_path, os.path.join("mocked", "path", "src", "i18n")
+ )
+
+ @patch("sys.platform", "linux")
+ def test_make_lang_code_posix(self):
+ lang = ConfigKruxInstaller.make_lang_code(lang="en_US")
+ self.assertEqual(lang, "en_US.UTF-8")
+
+ @patch("sys.platform", "win32")
+ def test_make_lang_code_windows(self):
+ lang = ConfigKruxInstaller.make_lang_code(lang="en_US")
+ self.assertEqual(lang, "en_US")
+
+ @patch("sys.platform", "mockos")
+ def test_fail_make_lang_code(self):
+ with self.assertRaises(OSError) as exc:
+ ConfigKruxInstaller.make_lang_code(lang="en_US")
+ self.assertEqual(
+ str(exc.exception),
+ "Couldn't possible to setup locale: OS 'mockos' not implemented",
+ )
+
@patch.dict(os.environ, {"LANG": "en-US.UTF-8"}, clear=True)
@patch("sys.platform", "linux")
def test_get_system_lang_linux(self):
@@ -357,30 +416,139 @@ def test_get_application_config(
)
mock_get_application_config.assert_called_once_with("mockfile")
- @patch("src.app.config_krux_installer.ConfigKruxInstaller.create_app_dir")
- @patch("src.app.config_krux_installer.ConfigKruxInstaller.get_system_lang")
- def test_build_config(self, mock_get_system_lang, mock_create_app_dir):
- mock_create_app_dir.return_value = "mockdir"
+ @patch("sys.platform", "linux")
+ @patch(
+ "src.app.config_krux_installer.ConfigKruxInstaller.create_app_dir",
+ return_value="mockdir",
+ )
+ @patch(
+ "src.app.config_krux_installer.ConfigKruxInstaller.get_system_lang",
+ return_value="en_US.UTF-8",
+ )
+ @patch("src.app.config_krux_installer.os.path.isfile", return_value=True)
+ def test_build_config_posix_no_default_lang(
+ self, mock_isfile, mock_get_system_lang, mock_create_app_dir
+ ):
config = MagicMock()
config.setdefaults = MagicMock()
- mock_get_system_lang.return_value = "en_US.UTF-8"
app = ConfigKruxInstaller()
+ app.i18n_path = os.path.join("mock", "i18n")
app.build_config(config)
# patch assertions
mock_create_app_dir.assert_called_once_with(name="local")
+ mock_get_system_lang.assert_called_once()
+ mock_isfile.assert_called_once_with(
+ os.path.join("mock", "i18n", "en_US.UTF-8.json")
+ )
+ config.setdefaults.assert_has_calls(
+ [
+ call("destdir", {"assets": "mockdir"}),
+ call("flash", {"baudrate": 1500000}),
+ call("locale", {"lang": "en_US.UTF-8"}),
+ ]
+ )
+
+ @patch("sys.platform", "linux")
+ @patch(
+ "src.app.config_krux_installer.ConfigKruxInstaller.create_app_dir",
+ return_value="mockdir",
+ )
+ @patch(
+ "src.app.config_krux_installer.ConfigKruxInstaller.get_system_lang",
+ return_value="mo_CK.UTF-8",
+ )
+ @patch("src.app.config_krux_installer.os.path.isfile", return_value=False)
+ def test_build_config_posix_default_lang(
+ self, mock_isfile, mock_get_system_lang, mock_create_app_dir
+ ):
+ config = MagicMock()
+ config.setdefaults = MagicMock()
- if sys.platform in ("linux", "darwin"):
- lang = "en_US.UTF-8"
- else:
- lang = "en_US"
+ app = ConfigKruxInstaller()
+ app.i18n_path = os.path.join("mock", "i18n")
+ app.build_config(config)
+ # patch assertions
+ mock_create_app_dir.assert_called_once_with(name="local")
+ mock_get_system_lang.assert_called_once()
+ mock_isfile.assert_called_once_with(
+ os.path.join("mock", "i18n", "mo_CK.UTF-8.json")
+ )
+ config.setdefaults.assert_has_calls(
+ [
+ call("destdir", {"assets": "mockdir"}),
+ call("flash", {"baudrate": 1500000}),
+ call("locale", {"lang": "en_US.UTF-8"}),
+ ]
+ )
+
+ @patch("sys.platform", "win32")
+ @patch(
+ "src.app.config_krux_installer.ConfigKruxInstaller.create_app_dir",
+ return_value="mockdir",
+ )
+ @patch(
+ "src.app.config_krux_installer.ConfigKruxInstaller.get_system_lang",
+ return_value="en_US",
+ )
+ @patch("src.app.config_krux_installer.os.path.isfile", return_value=True)
+ def test_build_config_windows_default_lang(
+ self, mock_isfile, mock_get_system_lang, mock_create_app_dir
+ ):
+ config = MagicMock()
+ config.setdefaults = MagicMock()
+
+ app = ConfigKruxInstaller()
+ app.i18n_path = os.path.join("mock", "i18n")
+ app.build_config(config)
+
+ # patch assertions
+ mock_create_app_dir.assert_called_once_with(name="local")
+ mock_get_system_lang.assert_called_once()
+ mock_isfile.assert_called_once_with(
+ os.path.join("mock", "i18n", "en_US.UTF-8.json")
+ )
+ config.setdefaults.assert_has_calls(
+ [
+ call("destdir", {"assets": "mockdir"}),
+ call("flash", {"baudrate": 1500000}),
+ call("locale", {"lang": "en_US"}),
+ ]
+ )
+
+ @patch("sys.platform", "win32")
+ @patch(
+ "src.app.config_krux_installer.ConfigKruxInstaller.create_app_dir",
+ return_value="mockdir",
+ )
+ @patch(
+ "src.app.config_krux_installer.ConfigKruxInstaller.get_system_lang",
+ return_value="mo_CK",
+ )
+ @patch("src.app.config_krux_installer.os.path.isfile", return_value=False)
+ def test_build_config_windows_no_default_lang(
+ self, mock_isfile, mock_get_system_lang, mock_create_app_dir
+ ):
+ config = MagicMock()
+ config.setdefaults = MagicMock()
+
+ app = ConfigKruxInstaller()
+ app.i18n_path = os.path.join("mock", "i18n")
+ app.build_config(config)
+
+ # patch assertions
+ mock_create_app_dir.assert_called_once_with(name="local")
+ mock_get_system_lang.assert_called_once()
+ mock_isfile.assert_called_once_with(
+ os.path.join("mock", "i18n", "mo_CK.UTF-8.json")
+ )
config.setdefaults.assert_has_calls(
[
call("destdir", {"assets": "mockdir"}),
call("flash", {"baudrate": 1500000}),
- call("locale", {"lang": lang}),
+ call("locale", {"lang": "en_US"}),
]
)
diff --git a/e2e/test_020_app_init.py b/e2e/test_020_app_init.py
index 8771fef1..179a1724 100644
--- a/e2e/test_020_app_init.py
+++ b/e2e/test_020_app_init.py
@@ -1,6 +1,6 @@
import os
import sys
-from unittest.mock import patch
+from unittest.mock import patch, mock_open, MagicMock
from pytest import mark
from kivy.base import EventLoop, EventLoopBase
from kivy.tests.common import GraphicUnitTest
@@ -32,18 +32,19 @@ def test_init(self):
self.assertIsInstance(app.screen_manager, ScreenManager)
@mark.skipif(
- sys.platform in ("win32", "darwin"),
- reason="does not run on windows or macos",
+ sys.platform in ("win32"),
+ reason="does not run on windows",
)
@patch.object(EventLoopBase, "ensure_window", lambda x: None)
@patch("sys.platform", "linux")
+ @patch("builtins.open", new_callable=mock_open, read_data="ID_LIKE=debian")
@patch(
"src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
)
@patch(
"src.app.screens.base_screen.BaseScreen.get_destdir_assets", return_value="mock"
)
- def test_build_linux(self, mock_get_destdir_assets, mock_get_locale):
+ def test_build_debian(self, mock_get_destdir_assets, mock_get_locale, open_mock):
app = KruxInstallerApp()
app.build()
@@ -72,9 +73,307 @@ def test_build_linux(self, mock_get_destdir_assets, mock_get_locale):
for screen in screens:
self.assertFalse(app.screen_manager.get_screen(screen) is None)
+ open_mock.assert_called_once_with("/etc/os-release", mode="r", encoding="utf-8")
mock_get_destdir_assets.assert_called_once()
mock_get_locale.assert_called()
+ @mark.skipif(
+ sys.platform in ("win32"),
+ reason="does not run on windows",
+ )
+ @patch.object(EventLoopBase, "ensure_window", lambda x: None)
+ @patch("sys.platform", "linux")
+ @patch("builtins.open", new_callable=mock_open, read_data="ID_LIKE=rhel")
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
+ )
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_destdir_assets", return_value="mock"
+ )
+ def test_build_rhel(self, mock_get_destdir_assets, mock_get_locale, open_mock):
+ app = KruxInstallerApp()
+ app.build()
+
+ screens = (
+ "GreetingsScreen",
+ "AskPermissionDialoutScreen",
+ "MainScreen",
+ "SelectDeviceScreen",
+ "SelectVersionScreen",
+ "SelectOldVersionScreen",
+ "WarningBetaScreen",
+ "AboutScreen",
+ "DownloadStableZipScreen",
+ "DownloadStableZipSha256Screen",
+ "DownloadStableZipSigScreen",
+ "DownloadSelfcustodyPemScreen",
+ "VerifyStableZipScreen",
+ "UnzipStableScreen",
+ "DownloadBetaScreen",
+ "WarningAlreadyDownloadedScreen",
+ "WarningWipeScreen",
+ "FlashScreen",
+ "WipeScreen",
+ "ErrorScreen",
+ )
+ for screen in screens:
+ self.assertFalse(app.screen_manager.get_screen(screen) is None)
+
+ open_mock.assert_called_once_with("/etc/os-release", mode="r", encoding="utf-8")
+ mock_get_destdir_assets.assert_called_once()
+ mock_get_locale.assert_called()
+
+ @mark.skipif(
+ sys.platform in ("win32"),
+ reason="does not run on windows",
+ )
+ @patch.object(EventLoopBase, "ensure_window", lambda x: None)
+ @patch("sys.platform", "linux")
+ @patch("builtins.open", new_callable=mock_open, read_data="ID_LIKE=suse")
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
+ )
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_destdir_assets", return_value="mock"
+ )
+ def test_build_suse(self, mock_get_destdir_assets, mock_get_locale, open_mock):
+ app = KruxInstallerApp()
+ app.build()
+
+ screens = (
+ "GreetingsScreen",
+ "AskPermissionDialoutScreen",
+ "MainScreen",
+ "SelectDeviceScreen",
+ "SelectVersionScreen",
+ "SelectOldVersionScreen",
+ "WarningBetaScreen",
+ "AboutScreen",
+ "DownloadStableZipScreen",
+ "DownloadStableZipSha256Screen",
+ "DownloadStableZipSigScreen",
+ "DownloadSelfcustodyPemScreen",
+ "VerifyStableZipScreen",
+ "UnzipStableScreen",
+ "DownloadBetaScreen",
+ "WarningAlreadyDownloadedScreen",
+ "WarningWipeScreen",
+ "FlashScreen",
+ "WipeScreen",
+ "ErrorScreen",
+ )
+ for screen in screens:
+ self.assertFalse(app.screen_manager.get_screen(screen) is None)
+
+ open_mock.assert_called_once_with("/etc/os-release", mode="r", encoding="utf-8")
+ mock_get_destdir_assets.assert_called_once()
+ mock_get_locale.assert_called()
+
+ @mark.skipif(
+ sys.platform in ("win32"),
+ reason="does not run on windows",
+ )
+ @patch.object(EventLoopBase, "ensure_window", lambda x: None)
+ @patch("sys.platform", "linux")
+ @patch("builtins.open", new_callable=mock_open, read_data="ID=arch")
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
+ )
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_destdir_assets", return_value="mock"
+ )
+ def test_build_arch(self, mock_get_destdir_assets, mock_get_locale, open_mock):
+ app = KruxInstallerApp()
+ app.build()
+
+ screens = (
+ "GreetingsScreen",
+ "AskPermissionDialoutScreen",
+ "MainScreen",
+ "SelectDeviceScreen",
+ "SelectVersionScreen",
+ "SelectOldVersionScreen",
+ "WarningBetaScreen",
+ "AboutScreen",
+ "DownloadStableZipScreen",
+ "DownloadStableZipSha256Screen",
+ "DownloadStableZipSigScreen",
+ "DownloadSelfcustodyPemScreen",
+ "VerifyStableZipScreen",
+ "UnzipStableScreen",
+ "DownloadBetaScreen",
+ "WarningAlreadyDownloadedScreen",
+ "WarningWipeScreen",
+ "FlashScreen",
+ "WipeScreen",
+ "ErrorScreen",
+ )
+ for screen in screens:
+ self.assertFalse(app.screen_manager.get_screen(screen) is None)
+
+ open_mock.assert_called()
+ mock_get_destdir_assets.assert_called_once()
+ mock_get_locale.assert_called()
+
+ @mark.skipif(
+ sys.platform in ("win32"),
+ reason="does not run on windows",
+ )
+ @patch.object(EventLoopBase, "ensure_window", lambda x: None)
+ @patch("sys.platform", "linux")
+ @patch("builtins.open", new_callable=mock_open, read_data="ID=alpine")
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
+ )
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_destdir_assets", return_value="mock"
+ )
+ def test_build_alpine(self, mock_get_destdir_assets, mock_get_locale, open_mock):
+ app = KruxInstallerApp()
+ app.build()
+
+ screens = (
+ "GreetingsScreen",
+ "AskPermissionDialoutScreen",
+ "MainScreen",
+ "SelectDeviceScreen",
+ "SelectVersionScreen",
+ "SelectOldVersionScreen",
+ "WarningBetaScreen",
+ "AboutScreen",
+ "DownloadStableZipScreen",
+ "DownloadStableZipSha256Screen",
+ "DownloadStableZipSigScreen",
+ "DownloadSelfcustodyPemScreen",
+ "VerifyStableZipScreen",
+ "UnzipStableScreen",
+ "DownloadBetaScreen",
+ "WarningAlreadyDownloadedScreen",
+ "WarningWipeScreen",
+ "FlashScreen",
+ "WipeScreen",
+ "ErrorScreen",
+ )
+ for screen in screens:
+ self.assertFalse(app.screen_manager.get_screen(screen) is None)
+
+ open_mock.assert_called()
+ mock_get_destdir_assets.assert_called_once()
+ mock_get_locale.assert_called()
+
+ @mark.skipif(
+ sys.platform in ("win32"),
+ reason="does not run on windows",
+ )
+ @patch.object(EventLoopBase, "ensure_window", lambda x: None)
+ @patch("sys.platform", "linux")
+ @patch(
+ "builtins.open",
+ new_callable=mock_open,
+ read_data="ID=mockos\nPRETTY_NAME=MockOS",
+ )
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
+ )
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_destdir_assets", return_value="mock"
+ )
+ @patch("src.app.screens.base_screen.BaseScreen.redirect_exception")
+ def test_fail_build_unrecognized(
+ self,
+ mock_redirect_exception,
+ mock_get_destdir_assets,
+ mock_get_locale,
+ open_mock,
+ ):
+ app = KruxInstallerApp()
+ app.build()
+
+ screens = (
+ "GreetingsScreen",
+ "AskPermissionDialoutScreen",
+ "MainScreen",
+ "SelectDeviceScreen",
+ "SelectVersionScreen",
+ "SelectOldVersionScreen",
+ "WarningBetaScreen",
+ "AboutScreen",
+ "DownloadStableZipScreen",
+ "DownloadStableZipSha256Screen",
+ "DownloadStableZipSigScreen",
+ "DownloadSelfcustodyPemScreen",
+ "VerifyStableZipScreen",
+ "UnzipStableScreen",
+ "DownloadBetaScreen",
+ "WarningAlreadyDownloadedScreen",
+ "WarningWipeScreen",
+ "FlashScreen",
+ "WipeScreen",
+ "ErrorScreen",
+ )
+ for screen in screens:
+ self.assertFalse(app.screen_manager.get_screen(screen) is None)
+
+ open_mock.assert_called()
+ mock_get_destdir_assets.assert_called_once()
+ mock_get_locale.assert_called()
+ mock_redirect_exception.assert_called()
+
+ @mark.skipif(
+ sys.platform in ("win32"),
+ reason="does not run on windows",
+ )
+ @patch.object(EventLoopBase, "ensure_window", lambda x: None)
+ @patch("sys.platform", "linux")
+ @patch("builtins.open", new_callable=mock_open)
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
+ )
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_destdir_assets", return_value="mock"
+ )
+ @patch("src.app.screens.base_screen.BaseScreen.redirect_exception")
+ def test_fail_build_linux_filenotfound(
+ self,
+ mock_redirect_exception,
+ mock_get_destdir_assets,
+ mock_get_locale,
+ open_mock,
+ ):
+ open_mock.return_value.__enter__.side_effect = [FileNotFoundError, MagicMock()]
+ app = KruxInstallerApp()
+ app.build()
+
+ screens = (
+ "GreetingsScreen",
+ "AskPermissionDialoutScreen",
+ "MainScreen",
+ "SelectDeviceScreen",
+ "SelectVersionScreen",
+ "SelectOldVersionScreen",
+ "WarningBetaScreen",
+ "AboutScreen",
+ "DownloadStableZipScreen",
+ "DownloadStableZipSha256Screen",
+ "DownloadStableZipSigScreen",
+ "DownloadSelfcustodyPemScreen",
+ "VerifyStableZipScreen",
+ "UnzipStableScreen",
+ "DownloadBetaScreen",
+ "WarningAlreadyDownloadedScreen",
+ "WarningWipeScreen",
+ "FlashScreen",
+ "WipeScreen",
+ "ErrorScreen",
+ )
+ for screen in screens:
+ self.assertFalse(app.screen_manager.get_screen(screen) is None)
+
+ open_mock.assert_called()
+ mock_get_destdir_assets.assert_called_once()
+ mock_get_locale.assert_called()
+ mock_redirect_exception.assert_called()
+
@patch("sys.platform", "win32")
@patch(
"src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
diff --git a/e2e/test_022_flash_screen.py b/e2e/test_022_flash_screen.py
index 25f01523..07a12070 100644
--- a/e2e/test_022_flash_screen.py
+++ b/e2e/test_022_flash_screen.py
@@ -1,3 +1,4 @@
+import threading
from unittest.mock import patch, MagicMock, call
from kivy.base import EventLoop, EventLoopBase
from kivy.tests.common import GraphicUnitTest
@@ -140,6 +141,32 @@ def test_on_pre_enter(self, mock_get_locale):
# patch assertions
mock_get_locale.assert_called()
+ @patch.object(EventLoopBase, "ensure_window", lambda x: None)
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
+ )
+ def test_greeting_fail_on_data_mock(self, mock_get_locale):
+ screen = FlashScreen()
+ screen.flasher.ktool.kill = MagicMock()
+ screen.flasher.ktool.checkKillExit = MagicMock()
+
+ screen.output = []
+ screen.on_pre_enter()
+ self.render(screen)
+
+ # get your Window instance safely
+ EventLoop.ensure_window()
+
+ on_data = getattr(FlashScreen, "on_data")
+ on_data("Greeting fail: mock")
+
+ self.assertEqual(screen.fail_msg, "Greeting fail: mock")
+
+ # patch assertions
+ mock_get_locale.assert_called()
+ screen.flasher.ktool.kill.assert_called()
+ screen.flasher.ktool.checkKillExit.assert_called()
+
@patch.object(EventLoopBase, "ensure_window", lambda x: None)
@patch(
"src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
@@ -389,3 +416,209 @@ def test_on_enter(self, mock_flasher, mock_thread, mock_partial, mock_get_locale
any_order=True,
)
mock_thread.assert_called_once_with(name=screen.name, target=mock_partial())
+
+ @patch.object(EventLoopBase, "ensure_window", lambda x: None)
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
+ )
+ @patch("src.app.screens.flash_screen.partial")
+ @patch("src.app.screens.flash_screen.threading.Thread")
+ @patch("src.utils.flasher.Flasher")
+ @patch("src.app.screens.base_screen.BaseScreen.redirect_exception")
+ def test_on_enter_fail_stopiteration(
+ self,
+ mock_redirect_exception,
+ mock_flasher,
+ mock_thread,
+ mock_partial,
+ mock_get_locale,
+ ):
+ mock_flasher.__class__.print_callback = MagicMock()
+
+ screen = FlashScreen()
+ screen.flasher = MagicMock()
+ screen.flasher.ktool = MagicMock()
+ screen.flasher.flash = MagicMock()
+ setattr(FlashScreen, "on_done", MagicMock())
+ setattr(FlashScreen, "on_data", MagicMock())
+ setattr(FlashScreen, "on_process", MagicMock())
+
+ # Define a custom excepthook
+ def mock_excepthook(args):
+ exc_type, exc_value, exc_traceback, thread = args.sequence
+ self.assertTrue(issubclass(exc_type, Exception))
+ self.assertEqual(str(exc_value), "StopIteration mocked")
+ self.assertEqual(exc_traceback, None)
+ self.assertTrue(thread is mock_thread)
+
+ # Patch threading.excepthook with the custom hook
+ with patch("threading.excepthook", mock_excepthook):
+ # Call the on_enter method
+ screen.on_enter()
+
+ # Simulate the exception using ExceptHookArgs
+ exc_args = threading.ExceptHookArgs(
+ sequence=(
+ Exception,
+ Exception("StopIteration mocked"),
+ None,
+ mock_thread,
+ )
+ )
+ threading.excepthook(exc_args)
+
+ # patch assertions
+ mock_get_locale.assert_called()
+ mock_partial.assert_called()
+ mock_thread.assert_called_once_with(name=screen.name, target=mock_partial())
+ mock_redirect_exception.assert_called()
+
+ @patch.object(EventLoopBase, "ensure_window", lambda x: None)
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
+ )
+ @patch("src.app.screens.flash_screen.partial")
+ @patch("src.app.screens.flash_screen.threading.Thread")
+ @patch("src.utils.flasher.Flasher")
+ @patch("src.app.screens.base_screen.BaseScreen.redirect_exception")
+ def test_on_enter_fail_cancel(
+ self,
+ mock_redirect_exception,
+ mock_flasher,
+ mock_thread,
+ mock_partial,
+ mock_get_locale,
+ ):
+ mock_flasher.__class__.print_callback = MagicMock()
+
+ screen = FlashScreen()
+ screen.flasher = MagicMock()
+ screen.flasher.ktool = MagicMock()
+ screen.flasher.flash = MagicMock()
+ setattr(FlashScreen, "on_done", MagicMock())
+ setattr(FlashScreen, "on_data", MagicMock())
+ setattr(FlashScreen, "on_process", MagicMock())
+
+ # Define a custom excepthook
+ def mock_excepthook(args):
+ exc_type, exc_value, exc_traceback, thread = args.sequence
+ self.assertTrue(issubclass(exc_type, Exception))
+ self.assertEqual(str(exc_value), "Cancel mocked")
+ self.assertEqual(exc_traceback, None)
+ self.assertTrue(thread is mock_thread)
+
+ # Patch threading.excepthook with the custom hook
+ with patch("threading.excepthook", mock_excepthook):
+ # Call the on_enter method
+ screen.on_enter()
+
+ # Simulate the exception using ExceptHookArgs
+ exc_args = threading.ExceptHookArgs(
+ sequence=(Exception, Exception("Cancel mocked"), None, mock_thread)
+ )
+ threading.excepthook(exc_args)
+
+ # patch assertions
+ mock_get_locale.assert_called()
+ mock_partial.assert_called()
+ mock_thread.assert_called_once_with(name=screen.name, target=mock_partial())
+ mock_redirect_exception.assert_called()
+
+ @patch.object(EventLoopBase, "ensure_window", lambda x: None)
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
+ )
+ @patch("src.app.screens.flash_screen.partial")
+ @patch("src.app.screens.flash_screen.threading.Thread")
+ @patch("src.utils.flasher.Flasher")
+ @patch("src.app.screens.base_screen.BaseScreen.redirect_exception")
+ def test_on_enter_fail_unknow(
+ self,
+ mock_redirect_exception,
+ mock_flasher,
+ mock_thread,
+ mock_partial,
+ mock_get_locale,
+ ):
+ mock_flasher.__class__.print_callback = MagicMock()
+
+ screen = FlashScreen()
+ screen.flasher = MagicMock()
+ screen.flasher.ktool = MagicMock()
+ screen.flasher.flash = MagicMock()
+ setattr(FlashScreen, "on_done", MagicMock())
+ setattr(FlashScreen, "on_data", MagicMock())
+ setattr(FlashScreen, "on_process", MagicMock())
+
+ # Define a custom excepthook
+ def mock_excepthook(args):
+ exc_type, exc_value, exc_traceback, thread = args.sequence
+ self.assertTrue(issubclass(exc_type, Exception))
+ self.assertEqual(str(exc_value), "Unknow mocked")
+ self.assertEqual(exc_traceback, None)
+ self.assertTrue(thread is mock_thread)
+
+ # Patch threading.excepthook with the custom hook
+ with patch("threading.excepthook", mock_excepthook):
+ # Call the on_enter method
+ screen.on_enter()
+
+ # Simulate the exception using ExceptHookArgs
+ exc_args = threading.ExceptHookArgs(
+ sequence=(Exception, Exception("Unknow mocked"), None, mock_thread)
+ )
+ threading.excepthook(exc_args)
+
+ # patch assertions
+ mock_get_locale.assert_called()
+ mock_partial.assert_called()
+ mock_thread.assert_called_once_with(name=screen.name, target=mock_partial())
+ mock_redirect_exception.assert_called()
+
+ @patch.object(EventLoopBase, "ensure_window", lambda x: None)
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
+ )
+ @patch("src.app.screens.base_screen.BaseScreen.set_screen")
+ def test_on_ref_press_back_after_done(self, mock_set_screen, mock_get_locale):
+ screen = FlashScreen()
+ screen.output = []
+ screen.on_pre_enter()
+ self.render(screen)
+
+ # get your Window instance safely
+ EventLoop.ensure_window()
+
+ on_done = getattr(FlashScreen, "on_done")
+ on_ref_press = getattr(FlashScreen, "on_ref_press_flash_screen_info")
+
+ on_done(0)
+ on_ref_press(screen.ids["flash_screen_info"], "Back")
+
+ # patch assertions
+ mock_get_locale.assert_any_call()
+ mock_set_screen.assert_called()
+
+ @patch.object(EventLoopBase, "ensure_window", lambda x: None)
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
+ )
+ @patch("src.app.screens.base_screen.BaseScreen.quit_app")
+ def test_on_ref_press_quit_after_done(self, mock_quit_app, mock_get_locale):
+ screen = FlashScreen()
+ screen.output = []
+ screen.on_pre_enter()
+ self.render(screen)
+
+ # get your Window instance safely
+ EventLoop.ensure_window()
+
+ on_done = getattr(FlashScreen, "on_done")
+ on_ref_press = getattr(FlashScreen, "on_ref_press_flash_screen_info")
+
+ on_done(0)
+ on_ref_press(screen.ids["flash_screen_info"], "Quit")
+
+ # patch assertions
+ mock_get_locale.assert_any_call()
+ mock_quit_app.assert_called()
diff --git a/e2e/test_023_wipe_screen.py b/e2e/test_023_wipe_screen.py
index 972a174d..4619a9ee 100644
--- a/e2e/test_023_wipe_screen.py
+++ b/e2e/test_023_wipe_screen.py
@@ -1,4 +1,5 @@
import os
+import threading
from unittest.mock import patch, MagicMock, call
from kivy.base import EventLoop, EventLoopBase
from kivy.tests.common import GraphicUnitTest
@@ -52,34 +53,23 @@ def test_init(self, mock_schedule_once, mock_partial, mock_get_locale):
)
mock_schedule_once.assert_has_calls([call(mock_partial(), 0)], any_order=True)
- @patch.object(EventLoopBase, "ensure_window", lambda x: None)
@patch(
"src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
)
@patch("src.app.screens.base_screen.BaseScreen.redirect_exception")
def test_fail_update_wrong_name(self, mock_redirect_exception, mock_get_locale):
screen = WipeScreen()
- self.render(screen)
-
- # get your Window instance safely
- EventLoop.ensure_window()
-
screen.update(name="MockScreen")
# patch assertions
mock_get_locale.assert_called_once()
mock_redirect_exception.assert_called_once()
- @patch.object(EventLoopBase, "ensure_window", lambda x: None)
@patch(
"src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
)
def test_update_locale(self, mock_get_locale):
screen = WipeScreen()
- self.render(screen)
-
- # get your Window instance safely
- EventLoop.ensure_window()
screen.update(name=screen.name, key="locale", value="en_US.UTF-8")
self.assertEqual(screen.locale, "en_US.UTF-8")
@@ -87,16 +77,11 @@ def test_update_locale(self, mock_get_locale):
# patch assertions
mock_get_locale.assert_called_once()
- @patch.object(EventLoopBase, "ensure_window", lambda x: None)
@patch(
"src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
)
def test_update_device(self, mock_get_locale):
screen = WipeScreen()
- self.render(screen)
-
- # get your Window instance safely
- EventLoop.ensure_window()
screen.update(name=screen.name, key="device", value="amigo")
self.assertEqual(screen.device, "amigo")
@@ -104,16 +89,13 @@ def test_update_device(self, mock_get_locale):
# patch assertions
mock_get_locale.asset_called_once()
- @patch.object(EventLoopBase, "ensure_window", lambda x: None)
@patch(
"src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
)
def test_update_wiper(self, mock_get_locale):
screen = WipeScreen()
- self.render(screen)
# get your Window instance safely
- EventLoop.ensure_window()
screen.update(name=screen.name, key="wiper", value=1500000)
self.assertEqual(screen.wiper.baudrate, 1500000)
@@ -142,18 +124,34 @@ def test_on_pre_enter(self, mock_get_locale):
# patch assertions
mock_get_locale.assert_any_call()
- @patch.object(EventLoopBase, "ensure_window", lambda x: None)
@patch(
"src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
)
- def test_on_data(self, mock_get_locale):
+ def test_greeting_fail_on_data_mock(self, mock_get_locale):
screen = WipeScreen()
+ screen.wiper = MagicMock()
+ screen.wiper.ktool.kill = MagicMock()
+ screen.wiper.ktool.checkKillExit = MagicMock()
screen.output = []
screen.on_pre_enter()
- self.render(screen)
- # get your Window instance safely
- EventLoop.ensure_window()
+ on_data = getattr(WipeScreen, "on_data")
+ on_data("Greeting fail: mock")
+
+ self.assertEqual(screen.fail_msg, "Greeting fail: mock")
+
+ # patch assertions
+ mock_get_locale.assert_called()
+ screen.wiper.ktool.kill.assert_called()
+ screen.wiper.ktool.checkKillExit.assert_called()
+
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
+ )
+ def test_on_data(self, mock_get_locale):
+ screen = WipeScreen()
+ screen.output = []
+ screen.on_pre_enter()
on_data = getattr(WipeScreen, "on_data")
on_data("[color=#00ff00] INFO [/color] mock")
@@ -162,7 +160,6 @@ def test_on_data(self, mock_get_locale):
# patch assertions
mock_get_locale.assert_any_call()
- @patch.object(EventLoopBase, "ensure_window", lambda x: None)
@patch(
"src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
)
@@ -170,11 +167,6 @@ def test_on_print_callback_pop_ouput(self, mock_get_locale):
screen = WipeScreen()
screen.output = []
screen.on_pre_enter()
- self.render(screen)
-
- # get your Window instance safely
- EventLoop.ensure_window()
-
on_data = getattr(WipeScreen, "on_data")
for i in range(4):
@@ -185,7 +177,6 @@ def test_on_print_callback_pop_ouput(self, mock_get_locale):
# patch assertions
mock_get_locale.assert_any_call()
- @patch.object(EventLoopBase, "ensure_window", lambda x: None)
@patch(
"src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
)
@@ -194,10 +185,6 @@ def test_on_data_erased(self, mock_done, mock_get_locale):
screen = WipeScreen()
screen.output = []
screen.on_pre_enter()
- self.render(screen)
-
- # get your Window instance safely
- EventLoop.ensure_window()
on_data = getattr(WipeScreen, "on_data")
on_data("[color=#00ff00] INFO [/color] SPI Flash erased.")
@@ -225,6 +212,8 @@ def test_on_done(self, mock_get_locale):
[
"[b]DONE![/b]",
"\n",
+ "disconnect and reconnect device before flash again",
+ "\n",
"[color=#00FF00]",
"[ref=Back][u]Back[/u][/ref]",
"[/color]",
@@ -243,7 +232,6 @@ def test_on_done(self, mock_get_locale):
# patch assertions
mock_get_locale.assert_any_call()
- @patch.object(EventLoopBase, "ensure_window", lambda x: None)
@patch(
"src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
)
@@ -277,3 +265,209 @@ def test_on_enter(self, mock_flasher, mock_thread, mock_partial, mock_get_locale
any_order=True,
)
mock_thread.assert_called_once_with(name=screen.name, target=mock_partial())
+
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
+ )
+ @patch("src.app.screens.wipe_screen.partial")
+ @patch("src.app.screens.wipe_screen.threading.Thread")
+ @patch("src.utils.flasher.Flasher")
+ @patch("src.app.screens.base_screen.BaseScreen.redirect_exception")
+ def test_on_enter_fail_stopiteration(
+ self,
+ mock_redirect_exception,
+ mock_flasher,
+ mock_thread,
+ mock_partial,
+ mock_get_locale,
+ ):
+ mock_flasher.__class__.print_callback = MagicMock()
+
+ screen = WipeScreen()
+ screen.wiper = MagicMock()
+ screen.wiper.ktool = MagicMock()
+ screen.wiper.flash = MagicMock()
+ setattr(WipeScreen, "on_done", MagicMock())
+ setattr(WipeScreen, "on_data", MagicMock())
+ setattr(WipeScreen, "on_process", MagicMock())
+
+ # Define a custom excepthook
+ def mock_excepthook(args):
+ exc_type, exc_value, exc_traceback, thread = args.sequence
+ self.assertTrue(issubclass(exc_type, Exception))
+ self.assertEqual(str(exc_value), "StopIteration mocked")
+ self.assertEqual(exc_traceback, None)
+ self.assertTrue(thread is mock_thread)
+
+ # Patch threading.excepthook with the custom hook
+ with patch("threading.excepthook", mock_excepthook):
+ # Call the on_enter method
+ screen.on_pre_enter()
+ screen.on_enter()
+
+ # Simulate the exception using ExceptHookArgs
+ exc_args = threading.ExceptHookArgs(
+ sequence=(
+ Exception,
+ Exception("StopIteration mocked"),
+ None,
+ mock_thread,
+ )
+ )
+ threading.excepthook(exc_args)
+
+ # patch assertions
+ mock_get_locale.assert_called()
+ mock_partial.assert_called()
+ mock_thread.assert_called_once_with(name=screen.name, target=mock_partial())
+ mock_redirect_exception.assert_called()
+
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
+ )
+ @patch("src.app.screens.wipe_screen.partial")
+ @patch("src.app.screens.wipe_screen.threading.Thread")
+ @patch("src.utils.flasher.Flasher")
+ @patch("src.app.screens.base_screen.BaseScreen.redirect_exception")
+ def test_on_enter_fail_cancel(
+ self,
+ mock_redirect_exception,
+ mock_flasher,
+ mock_thread,
+ mock_partial,
+ mock_get_locale,
+ ):
+ mock_flasher.__class__.print_callback = MagicMock()
+
+ screen = WipeScreen()
+ screen.wiper = MagicMock()
+ screen.wiper.ktool = MagicMock()
+ screen.wiper.flash = MagicMock()
+ setattr(WipeScreen, "on_done", MagicMock())
+ setattr(WipeScreen, "on_data", MagicMock())
+ setattr(WipeScreen, "on_process", MagicMock())
+
+ # Define a custom excepthook
+ def mock_excepthook(args):
+ exc_type, exc_value, exc_traceback, thread = args.sequence
+ self.assertTrue(issubclass(exc_type, Exception))
+ self.assertEqual(str(exc_value), "Cancel mocked")
+ self.assertEqual(exc_traceback, None)
+ self.assertTrue(thread is mock_thread)
+
+ # Patch threading.excepthook with the custom hook
+ with patch("threading.excepthook", mock_excepthook):
+ # Call the on_enter method
+ screen.on_pre_enter()
+ screen.on_enter()
+
+ # Simulate the exception using ExceptHookArgs
+ exc_args = threading.ExceptHookArgs(
+ sequence=(
+ Exception,
+ Exception("Cancel mocked"),
+ None,
+ mock_thread,
+ )
+ )
+ threading.excepthook(exc_args)
+
+ # patch assertions
+ mock_get_locale.assert_called()
+ mock_partial.assert_called()
+ mock_thread.assert_called_once_with(name=screen.name, target=mock_partial())
+ mock_redirect_exception.assert_called()
+
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
+ )
+ @patch("src.app.screens.wipe_screen.partial")
+ @patch("src.app.screens.wipe_screen.threading.Thread")
+ @patch("src.utils.flasher.Flasher")
+ @patch("src.app.screens.base_screen.BaseScreen.redirect_exception")
+ def test_on_enter_fail_unknow(
+ self,
+ mock_redirect_exception,
+ mock_flasher,
+ mock_thread,
+ mock_partial,
+ mock_get_locale,
+ ):
+ mock_flasher.__class__.print_callback = MagicMock()
+
+ screen = WipeScreen()
+ screen.wiper = MagicMock()
+ screen.wiper.ktool = MagicMock()
+ screen.wiper.flash = MagicMock()
+ setattr(WipeScreen, "on_done", MagicMock())
+ setattr(WipeScreen, "on_data", MagicMock())
+ setattr(WipeScreen, "on_process", MagicMock())
+
+ # Define a custom excepthook
+ def mock_excepthook(args):
+ exc_type, exc_value, exc_traceback, thread = args.sequence
+ self.assertTrue(issubclass(exc_type, Exception))
+ self.assertEqual(str(exc_value), "Unknow mocked")
+ self.assertEqual(exc_traceback, None)
+ self.assertTrue(thread is mock_thread)
+
+ # Patch threading.excepthook with the custom hook
+ with patch("threading.excepthook", mock_excepthook):
+ # Call the on_enter method
+ screen.on_pre_enter()
+ screen.on_enter()
+
+ # Simulate the exception using ExceptHookArgs
+ exc_args = threading.ExceptHookArgs(
+ sequence=(
+ Exception,
+ Exception("Unknow mocked"),
+ None,
+ mock_thread,
+ )
+ )
+ threading.excepthook(exc_args)
+
+ # patch assertions
+ mock_get_locale.assert_called()
+ mock_partial.assert_called()
+ mock_thread.assert_called_once_with(name=screen.name, target=mock_partial())
+ mock_redirect_exception.assert_called()
+
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
+ )
+ @patch("src.app.screens.base_screen.BaseScreen.set_screen")
+ def test_on_ref_press_back_after_done(self, mock_set_screen, mock_get_locale):
+ screen = WipeScreen()
+ screen.output = []
+ screen.on_pre_enter()
+
+ on_done = getattr(WipeScreen, "on_done")
+ on_ref_press = getattr(WipeScreen, "on_ref_press_wipe_screen_info")
+
+ on_done(0)
+ on_ref_press(screen.ids["wipe_screen_info"], "Back")
+
+ # patch assertions
+ mock_get_locale.assert_any_call()
+ mock_set_screen.assert_called()
+
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
+ )
+ @patch("src.app.screens.base_screen.BaseScreen.quit_app")
+ def test_on_ref_press_quit_after_done(self, mock_quit_app, mock_get_locale):
+ screen = WipeScreen()
+ screen.output = []
+ screen.on_pre_enter()
+
+ on_done = getattr(WipeScreen, "on_done")
+ on_ref_press = getattr(WipeScreen, "on_ref_press_wipe_screen_info")
+
+ on_done(0)
+ on_ref_press(screen.ids["wipe_screen_info"], "Quit")
+
+ # patch assertions
+ mock_get_locale.assert_any_call()
+ mock_quit_app.assert_called()
diff --git a/e2e/test_024_warning_wipe_screen.py b/e2e/test_024_warning_wipe_screen.py
index 011b96cb..8e0d2870 100644
--- a/e2e/test_024_warning_wipe_screen.py
+++ b/e2e/test_024_warning_wipe_screen.py
@@ -1,6 +1,6 @@
import os
from unittest.mock import patch, MagicMock, call
-from kivy.base import EventLoop, EventLoopBase
+from kivy.base import EventLoop
from kivy.tests.common import GraphicUnitTest
from kivy.core.text import LabelBase, DEFAULT_FONT
from src.app.screens.warning_wipe_screen import WarningWipeScreen
@@ -21,7 +21,6 @@ def setUpClass(cls):
def teardown_class(cls):
EventLoop.exit()
- @patch.object(EventLoopBase, "ensure_window", lambda x: None)
@patch(
"src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
)
@@ -29,20 +28,13 @@ def teardown_class(cls):
@patch("src.app.screens.warning_wipe_screen.Clock.schedule_once")
def test_init(self, mock_schedule_once, mock_partial, mock_get_locale):
screen = WarningWipeScreen()
- self.render(screen)
-
- # get your Window instance safely
- EventLoop.ensure_window()
- window = EventLoop.window
- grid = window.children[0].children[0]
- image = grid.children[1]
- label = grid.children[0]
# default assertions
- self.assertEqual(grid.id, "warning_wipe_screen_grid")
+ grid = screen.ids[f"{screen.id}_grid"]
+ self.assertTrue("warning_wipe_screen_grid" in screen.ids)
self.assertEqual(len(grid.children), 2)
- self.assertEqual(image.id, "warning_wipe_screen_warn")
- self.assertEqual(label.id, "warning_wipe_screen_label")
+ self.assertTrue("warning_wipe_screen_warn" in screen.ids)
+ self.assertTrue("warning_wipe_screen_label" in screen.ids)
# patch assertions
mock_get_locale.assert_called_once()
@@ -51,19 +43,12 @@ def test_init(self, mock_schedule_once, mock_partial, mock_get_locale):
)
mock_schedule_once.assert_has_calls([call(mock_partial(), 0)], any_order=True)
- @patch.object(EventLoopBase, "ensure_window", lambda x: None)
@patch(
"src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
)
def test_make_label_text(self, mock_get_locale):
screen = WarningWipeScreen()
- self.render(screen)
-
- # get your Window instance safely
- EventLoop.ensure_window()
- window = EventLoop.window
- grid = window.children[0].children[0]
- label = grid.children[0]
+ label = screen.ids[f"{screen.id}_label"]
screen.update(name=screen.name, key="locale", value="en_US.UTF-8")
text = "".join(
@@ -93,7 +78,6 @@ def test_make_label_text(self, mock_get_locale):
# patch assertions
mock_get_locale.assert_called_once()
- @patch.object(EventLoopBase, "ensure_window", lambda x: None)
@patch(
"src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
)
@@ -103,10 +87,8 @@ def test_make_label_text(self, mock_get_locale):
)
def test_update_locale(self, mock_make_label_text, mock_get_locale):
screen = WarningWipeScreen()
- self.render(screen)
# get your Window instance safely
- EventLoop.ensure_window()
screen.update(name=screen.name, key="locale", value="en_US.UTF8")
self.assertEqual(screen.locale, "en_US.UTF8")
@@ -115,7 +97,6 @@ def test_update_locale(self, mock_make_label_text, mock_get_locale):
mock_get_locale.assert_called_once()
mock_make_label_text.assert_any_call()
- @patch.object(EventLoopBase, "ensure_window", lambda x: None)
@patch(
"src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
)
@@ -125,17 +106,12 @@ def test_update_locale(self, mock_make_label_text, mock_get_locale):
)
def test_on_enter(self, mock_make_label_text, mock_get_locale):
screen = WarningWipeScreen()
- self.render(screen)
screen.on_enter()
- # get your Window instance safely
- EventLoop.ensure_window()
-
# patch assertions
mock_get_locale.assert_any_call()
mock_make_label_text.assert_called_once()
- @patch.object(EventLoopBase, "ensure_window", lambda x: None)
@patch(
"src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
)
@@ -149,17 +125,10 @@ def test_on_ref_press_proceed(
screen.manager = MagicMock()
screen.manager.get_screen = MagicMock()
- self.render(screen)
-
- # get your Window instance safely
- EventLoop.ensure_window()
- window = EventLoop.window
- grid = window.children[0].children[0]
- label = grid.children[0]
button = screen.ids[f"{screen.id}_label"]
action = getattr(screen.__class__, f"on_ref_press_{button.id}")
- action(label, "WipeScreen")
+ action(button, "WipeScreen")
mock_get_locale.assert_any_call()
screen.manager.get_screen.assert_has_calls(
@@ -187,7 +156,6 @@ def test_on_ref_press_proceed(
[call(mock_partial(), 0), call(mock_partial(), 0)], any_order=True
)
- @patch.object(EventLoopBase, "ensure_window", lambda x: None)
@patch(
"src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
)
@@ -197,16 +165,10 @@ def test_on_ref_press_deny(self, mock_set_screen, mock_get_locale):
screen.manager = MagicMock()
screen.manager.get_screen = MagicMock()
- self.render(screen)
-
# get your Window instance safely
- EventLoop.ensure_window()
- window = EventLoop.window
- grid = window.children[0].children[0]
- label = grid.children[0]
button = screen.ids[f"{screen.id}_label"]
action = getattr(screen.__class__, f"on_ref_press_{button.id}")
- action(label, "MainScreen")
+ action(button, "MainScreen")
mock_get_locale.assert_any_call()
mock_set_screen.assert_called_once_with(name="MainScreen", direction="right")
diff --git a/e2e/test_025_error_screen.py b/e2e/test_025_error_screen.py
index 29a76cd8..bac00ded 100644
--- a/e2e/test_025_error_screen.py
+++ b/e2e/test_025_error_screen.py
@@ -1,6 +1,6 @@
import os
from unittest.mock import patch, MagicMock
-from kivy.base import EventLoop, EventLoopBase
+from kivy.base import EventLoop
from kivy.tests.common import GraphicUnitTest
from kivy.core.text import LabelBase, DEFAULT_FONT
from src.app.screens.error_screen import ErrorScreen
@@ -21,29 +21,19 @@ def setUpClass(cls):
def teardown_class(cls):
EventLoop.exit()
- @patch.object(EventLoopBase, "ensure_window", lambda x: None)
@patch(
"src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
)
def test_init(self, mock_get_locale):
screen = ErrorScreen()
- self.render(screen)
-
- # get your Window instance safely
- EventLoop.ensure_window()
- window = EventLoop.window
- grid = window.children[0].children[0]
- label = grid.children[0]
# default assertions
- self.assertEqual(grid.id, "error_screen_grid")
- self.assertEqual(len(grid.children), 1)
- self.assertEqual(label.id, "error_screen_label")
+ self.assertTrue("error_screen_grid" in screen.ids)
+ self.assertTrue("error_screen_label" in screen.ids)
# patch assertions
mock_get_locale.assert_called_once()
- @patch.object(EventLoopBase, "ensure_window", lambda x: None)
@patch(
"src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
)
@@ -51,10 +41,8 @@ def test_make_label_text(self, mock_get_locale):
screen = ErrorScreen()
screen.manager = MagicMock()
screen.manager.screen_names = ["KruxInstallerApp", screen.name]
- self.render(screen)
# get your Window instance safely
- EventLoop.ensure_window()
label = screen.ids[f"{screen.id}_label"]
error = RuntimeError("Error: mocked error: at test")
@@ -93,32 +81,22 @@ def test_make_label_text(self, mock_get_locale):
# patch assertions
mock_get_locale.assert_called_once()
- @patch.object(EventLoopBase, "ensure_window", lambda x: None)
@patch(
"src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
)
@patch("src.app.screens.error_screen.ErrorScreen.set_screen")
def test_on_ref_press_back(self, mock_set_screen, mock_get_locale):
screen = ErrorScreen()
-
- self.render(screen)
-
- # get your Window instance safely
- EventLoop.ensure_window()
- window = EventLoop.window
- grid = window.children[0].children[0]
- label = grid.children[0]
button = screen.ids[f"{screen.id}_label"]
action = getattr(ErrorScreen, f"on_ref_press_{button.id}")
- action(label, "Back")
+ action(button, "Back")
mock_get_locale.assert_any_call()
mock_set_screen.assert_called_once_with(
name="GreetingsScreen", direction="right"
)
- @patch.object(EventLoopBase, "ensure_window", lambda x: None)
@patch(
"src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
)
@@ -128,22 +106,14 @@ def test_on_ref_press_quit(self, mock_quit_app, mock_get_locale):
screen.manager = MagicMock()
screen.manager.get_screen = MagicMock()
- self.render(screen)
-
- # get your Window instance safely
- EventLoop.ensure_window()
- window = EventLoop.window
- grid = window.children[0].children[0]
- label = grid.children[0]
button = screen.ids[f"{screen.id}_label"]
action = getattr(ErrorScreen, f"on_ref_press_{button.id}")
- action(label, "Quit")
+ action(button, "Quit")
mock_get_locale.assert_any_call()
mock_quit_app.assert_called_once()
- @patch.object(EventLoopBase, "ensure_window", lambda x: None)
@patch(
"src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
)
@@ -153,17 +123,10 @@ def test_on_ref_press_report(self, mock_web_open, mock_get_locale):
screen.manager = MagicMock()
screen.manager.get_screen = MagicMock()
- self.render(screen)
-
- # get your Window instance safely
- EventLoop.ensure_window()
- window = EventLoop.window
- grid = window.children[0].children[0]
- label = grid.children[0]
button = screen.ids[f"{screen.id}_label"]
action = getattr(ErrorScreen, f"on_ref_press_{button.id}")
- action(label, "ReportIssue")
+ action(button, "ReportIssue")
mock_get_locale.assert_any_call()
mock_web_open.assert_called_once_with(
diff --git a/e2e/test_026_warning_before_airgap_update_screen.py b/e2e/test_026_warning_before_airgap_update_screen.py
new file mode 100644
index 00000000..61241283
--- /dev/null
+++ b/e2e/test_026_warning_before_airgap_update_screen.py
@@ -0,0 +1,297 @@
+import os
+from unittest.mock import patch, MagicMock
+from kivy.base import EventLoop, EventLoopBase
+from kivy.tests.common import GraphicUnitTest
+from kivy.core.text import LabelBase, DEFAULT_FONT
+from src.app.screens.warning_before_airgap_update_screen import (
+ WarningBeforeAirgapUpdateScreen,
+)
+
+
+class TestWarningBeforeAirgapUpdateScreen(GraphicUnitTest):
+
+ @classmethod
+ def setUpClass(cls):
+ cwd_path = os.path.dirname(__file__)
+ rel_assets_path = os.path.join(cwd_path, "..", "assets")
+ assets_path = os.path.abspath(rel_assets_path)
+ font_name = "NotoSansCJK_CY_JP_SC_KR_VI_Krux.ttf"
+ noto_sans_path = os.path.join(assets_path, font_name)
+ LabelBase.register(DEFAULT_FONT, noto_sans_path)
+
+ @classmethod
+ def teardown_class(cls):
+ EventLoop.exit()
+
+ @patch.object(EventLoopBase, "ensure_window", lambda x: None)
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
+ )
+ def test_render_main_screen(self, mock_get_locale):
+ screen = WarningBeforeAirgapUpdateScreen()
+ self.render(screen)
+
+ # get your Window instance safely
+ EventLoop.ensure_window()
+ window = EventLoop.window
+ grid = window.children[0].children[0]
+ warn = grid.children[1]
+ button = grid.children[0]
+
+ self.assertEqual(window.children[0], screen)
+ self.assertEqual(screen.name, "WarningBeforeAirgapUpdateScreen")
+ self.assertEqual(screen.id, "warning_before_airgap_update_screen")
+ self.assertEqual(grid.id, "warning_before_airgap_update_screen_grid")
+ self.assertEqual(warn.id, "warning_before_airgap_update_screen_warn")
+ self.assertEqual(button.id, "warning_before_airgap_update_screen_label")
+
+ text = "".join(
+ [
+ "[color=#efcc00]Before proceeding with the air-gapped update:[/color]",
+ "\n",
+ "* Insert a FAT32 formatted SDCard into your computer",
+ "\n",
+ "* On the next screen, choose the drive to copy firmware",
+ "\n",
+ "\n",
+ "[color=#ff0000][ref=MainScreen]Back[/ref][/color] [color=#00ff00][ref=AirgapUpdateScreen]Proceed[/ref][/color]",
+ ]
+ )
+
+ self.assertEqual(button.text, text)
+ mock_get_locale.assert_any_call()
+
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
+ )
+ @patch("src.app.screens.base_screen.BaseScreen.set_screen")
+ def test_on_ref_press_back(self, mock_set_screen, mock_get_locale):
+ screen = WarningBeforeAirgapUpdateScreen()
+
+ action = getattr(
+ screen, "on_ref_press_warning_before_airgap_update_screen_label"
+ )
+ action("MainScreen")
+
+ mock_set_screen.assert_called_once_with(name="MainScreen", direction="left")
+ mock_get_locale.assert_any_call()
+
+ @patch.object(EventLoopBase, "ensure_window", lambda x: None)
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
+ )
+ def test_on_update(self, mock_get_locale):
+ screen = WarningBeforeAirgapUpdateScreen()
+ self.render(screen)
+
+ # get your Window instance safely
+ EventLoop.ensure_window()
+
+ screen.update(name=screen.name, key="locale", value="en_US.UTF-8")
+
+ mock_get_locale.assert_any_call()
+
+ @patch("sys.platform", "linux")
+ # @patch.object(EventLoopBase, "ensure_window", lambda x: None)
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
+ )
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.on_get_removable_drives_linux",
+ return_value=[],
+ )
+ def test_on_ref_press_no_drives_found_linux(
+ self, mock_on_get_removable_drives_linux, mock_get_locale
+ ):
+ screen = WarningBeforeAirgapUpdateScreen()
+ screen.manager = MagicMock()
+ screen.manager.get_screen = MagicMock()
+ # self.render(screen)
+
+ # get your Window instance safely
+ # EventLoop.ensure_window()
+
+ action = getattr(
+ screen, "on_ref_press_warning_before_airgap_update_screen_label"
+ )
+ action("AirgapUpdateScreen")
+
+ mock_get_locale.assert_any_call()
+ mock_on_get_removable_drives_linux.assert_called_once()
+
+ @patch("sys.platform", "darwin")
+ @patch.object(EventLoopBase, "ensure_window", lambda x: None)
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
+ )
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.on_get_removable_drives_macos",
+ return_value=[],
+ )
+ def test_on_ref_press_no_drives_found_darwin(
+ self, on_get_removable_drives_macos, mock_get_locale
+ ):
+ screen = WarningBeforeAirgapUpdateScreen()
+ screen.manager = MagicMock()
+ screen.manager.get_screen = MagicMock()
+ self.render(screen)
+
+ # get your Window instance safely
+ EventLoop.ensure_window()
+
+ action = getattr(
+ screen, "on_ref_press_warning_before_airgap_update_screen_label"
+ )
+ action("AirgapUpdateScreen")
+
+ mock_get_locale.assert_any_call()
+ on_get_removable_drives_macos.assert_called_once()
+
+ @patch("sys.platform", "win32")
+ @patch.object(EventLoopBase, "ensure_window", lambda x: None)
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
+ )
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.on_get_removable_drives_windows",
+ return_value=[],
+ )
+ def test_on_ref_press_no_drives_found_windows(
+ self, on_get_removable_drives_windows, mock_get_locale
+ ):
+ screen = WarningBeforeAirgapUpdateScreen()
+ screen.manager = MagicMock()
+ screen.manager.get_screen = MagicMock()
+ self.render(screen)
+
+ # get your Window instance safely
+ EventLoop.ensure_window()
+
+ action = getattr(
+ screen, "on_ref_press_warning_before_airgap_update_screen_label"
+ )
+ action("AirgapUpdateScreen")
+
+ mock_get_locale.assert_any_call()
+ on_get_removable_drives_windows.assert_called_once()
+
+ @patch("sys.platform", "linux")
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
+ )
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.on_get_removable_drives_linux",
+ return_value=["/media/mock"],
+ )
+ @patch("src.app.screens.warning_before_airgap_update_screen.partial")
+ @patch("src.app.screens.warning_before_airgap_update_screen.Clock.schedule_once")
+ @patch("src.app.screens.base_screen.BaseScreen.set_screen")
+ def test_on_ref_press_drives_found_linux(
+ self,
+ mock_set_screen,
+ mock_schedule_once,
+ mock_partial,
+ mock_on_get_removable_drives_linux,
+ mock_get_locale,
+ ):
+ screen = WarningBeforeAirgapUpdateScreen()
+ screen.manager = MagicMock()
+ screen.manager.get_screen = MagicMock()
+
+ action = getattr(
+ screen, "on_ref_press_warning_before_airgap_update_screen_label"
+ )
+ action("AirgapUpdateScreen")
+
+ mock_get_locale.assert_any_call()
+ mock_on_get_removable_drives_linux.assert_called_once()
+ screen.manager.get_screen.assert_called_once_with("AirgapUpdateScreen")
+ mock_partial.assert_called()
+ mock_schedule_once.assert_called()
+ mock_set_screen.assert_called_once_with(
+ name="AirgapUpdateScreen", direction="right"
+ )
+
+ @patch("sys.platform", "darwin")
+ @patch.object(EventLoopBase, "ensure_window", lambda x: None)
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
+ )
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.on_get_removable_drives_macos",
+ return_value=["/Volumes/mock"],
+ )
+ @patch("src.app.screens.warning_before_airgap_update_screen.partial")
+ @patch("src.app.screens.warning_before_airgap_update_screen.Clock.schedule_once")
+ @patch("src.app.screens.base_screen.BaseScreen.set_screen")
+ def test_on_ref_press_drives_found_macos(
+ self,
+ mock_set_screen,
+ mock_schedule_once,
+ mock_partial,
+ mock_on_get_removable_drives_macos,
+ mock_get_locale,
+ ):
+ screen = WarningBeforeAirgapUpdateScreen()
+ screen.manager = MagicMock()
+ screen.manager.get_screen = MagicMock()
+ self.render(screen)
+
+ # get your Window instance safely
+ EventLoop.ensure_window()
+
+ action = getattr(
+ screen, "on_ref_press_warning_before_airgap_update_screen_label"
+ )
+ action("AirgapUpdateScreen")
+
+ mock_get_locale.assert_any_call()
+ mock_on_get_removable_drives_macos.assert_called_once()
+ screen.manager.get_screen.assert_called_once_with("AirgapUpdateScreen")
+ mock_partial.assert_called()
+ mock_schedule_once.assert_called()
+ mock_set_screen.assert_called_once_with(
+ name="AirgapUpdateScreen", direction="right"
+ )
+
+ @patch("sys.platform", "win32")
+ @patch.object(EventLoopBase, "ensure_window", lambda x: None)
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
+ )
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.on_get_removable_drives_windows",
+ return_value=["D:\\"],
+ )
+ @patch("src.app.screens.warning_before_airgap_update_screen.partial")
+ @patch("src.app.screens.warning_before_airgap_update_screen.Clock.schedule_once")
+ @patch("src.app.screens.base_screen.BaseScreen.set_screen")
+ def test_on_ref_press_drives_found_windows(
+ self,
+ mock_set_screen,
+ mock_schedule_once,
+ mock_partial,
+ mock_on_get_removable_drives_windows,
+ mock_get_locale,
+ ):
+ screen = WarningBeforeAirgapUpdateScreen()
+ screen.manager = MagicMock()
+ screen.manager.get_screen = MagicMock()
+ self.render(screen)
+
+ # get your Window instance safely
+ EventLoop.ensure_window()
+
+ action = getattr(
+ screen, "on_ref_press_warning_before_airgap_update_screen_label"
+ )
+ action("AirgapUpdateScreen")
+
+ mock_get_locale.assert_any_call()
+ mock_on_get_removable_drives_windows.assert_called_once()
+ screen.manager.get_screen.assert_called_once_with("AirgapUpdateScreen")
+ mock_partial.assert_called()
+ mock_schedule_once.assert_called()
+ mock_set_screen.assert_called_once_with(
+ name="AirgapUpdateScreen", direction="right"
+ )
diff --git a/e2e/test_027_airgap_update_screen.py b/e2e/test_027_airgap_update_screen.py
new file mode 100644
index 00000000..7564af60
--- /dev/null
+++ b/e2e/test_027_airgap_update_screen.py
@@ -0,0 +1,176 @@
+import os
+from unittest.mock import patch, MagicMock, mock_open
+from kivy.base import EventLoop, EventLoopBase
+from kivy.tests.common import GraphicUnitTest
+from kivy.core.text import LabelBase, DEFAULT_FONT
+from src.app.screens.airgap_update_screen import (
+ AirgapUpdateScreen,
+)
+
+# pylint: disable=line-too-long
+MOCK_SIG = b"0E\x02!\x00\xed\xfb\xb2\x99\x06\x99\x97fDQ\x0f%\xdf=\xe7^h\xd1\xb6n\x16\x9cBm\xc4\xcc\xbbb:P\xb5#\x02 f\xee\xf8\x95\xfd'sqH\x9eO\xa3x\xb6>\xdc\x83\x96\xd1\xf7\x92\xcf&W\xf4n\xc0\xd3\xc8\xfe\xd3\xfd"
+
+
+class TestAirgapUpdateScreen(GraphicUnitTest):
+
+ @classmethod
+ def setUpClass(cls):
+ cwd_path = os.path.dirname(__file__)
+ rel_assets_path = os.path.join(cwd_path, "..", "assets")
+ assets_path = os.path.abspath(rel_assets_path)
+ font_name = "NotoSansCJK_CY_JP_SC_KR_VI_Krux.ttf"
+ noto_sans_path = os.path.join(assets_path, font_name)
+ LabelBase.register(DEFAULT_FONT, noto_sans_path)
+
+ @classmethod
+ def teardown_class(cls):
+ EventLoop.exit()
+
+ @patch.object(EventLoopBase, "ensure_window", lambda x: None)
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
+ )
+ def test_init(self, mock_get_locale):
+ screen = AirgapUpdateScreen()
+ self.render(screen)
+
+ self.assertEqual(screen.firmware_bin, "")
+ self.assertEqual(screen.firmware_sig, "")
+
+ mock_get_locale.assert_any_call()
+
+ @patch.object(EventLoopBase, "ensure_window", lambda x: None)
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
+ )
+ def test_update_firmware_bin(self, mock_get_locale):
+ screen = AirgapUpdateScreen()
+ screen.update(
+ name=screen.name, key="binary", value=os.path.join("mock", "firmware.bin")
+ )
+
+ self.assertEqual(screen.firmware_bin, os.path.join("mock", "firmware.bin"))
+ mock_get_locale.assert_any_call()
+
+ @patch.object(EventLoopBase, "ensure_window", lambda x: None)
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
+ )
+ def test_update_firmware_sig(self, mock_get_locale):
+ screen = AirgapUpdateScreen()
+ screen.update(
+ name=screen.name,
+ key="signature",
+ value=os.path.join("mock", "firmware.bin.sig"),
+ )
+
+ self.assertEqual(screen.firmware_sig, os.path.join("mock", "firmware.bin.sig"))
+ mock_get_locale.assert_any_call()
+
+ @patch.object(EventLoopBase, "ensure_window", lambda x: None)
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
+ )
+ def test_update_drives(self, mock_get_locale):
+ screen = AirgapUpdateScreen()
+ screen.update(name=screen.name, key="drives", value=[os.path.join("mock", "0")])
+
+ self.assertTrue(f"{screen.id}_grid" in screen.ids)
+ self.assertTrue(f"{screen.id}_button_0" in screen.ids)
+
+ button_0_text = screen.ids[f"{screen.id}_button_0"].text
+ self.assertEqual(
+ button_0_text,
+ "".join(
+ [
+ "Select",
+ "\n",
+ f"[color=#efcc00]{os.path.join("mock", "0")}[/color]",
+ "\n",
+ "to copy firmware",
+ ]
+ ),
+ )
+ mock_get_locale.assert_any_call()
+
+ @patch.object(EventLoopBase, "ensure_window", lambda x: None)
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
+ )
+ def test_on_leave(self, mock_get_locale):
+ screen = AirgapUpdateScreen()
+ screen.update(name=screen.name, key="drives", value=[os.path.join("mock", "0")])
+
+ self.assertTrue(f"{screen.id}_grid" in screen.ids)
+ self.assertTrue(f"{screen.id}_button_0" in screen.ids)
+
+ # now clean
+ screen.on_leave()
+ self.assertTrue(f"{screen.id}_grid" not in screen.ids)
+
+ mock_get_locale.assert_any_call()
+
+ @patch.object(EventLoopBase, "ensure_window", lambda x: None)
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
+ )
+ @patch("src.app.screens.base_screen.BaseScreen.set_background")
+ def test_on_press_button(self, mock_set_background, mock_get_locale):
+ screen = AirgapUpdateScreen()
+ screen.update(name=screen.name, key="drives", value=[os.path.join("mock", "0")])
+
+ action = getattr(AirgapUpdateScreen, f"on_press_{screen.id}_button_0")
+ action(screen.ids[f"{screen.id}_button_0"])
+
+ mock_get_locale.assert_any_call()
+ mock_set_background.assert_called_once_with(
+ wid=f"{screen.id}_button_0", rgba=(0.25, 0.25, 0.25, 1)
+ )
+
+ @patch.object(EventLoopBase, "ensure_window", lambda x: None)
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
+ )
+ @patch("src.app.screens.airgap_update_screen.Clock.schedule_once")
+ @patch("src.app.screens.airgap_update_screen.partial")
+ @patch("src.app.screens.base_screen.BaseScreen.set_background")
+ @patch("src.app.screens.base_screen.BaseScreen.set_screen")
+ @patch("src.app.screens.airgap_update_screen.shutil.copyfile")
+ @patch("src.utils.verifyer.sha256_verifyer.os.path.exists", return_value=True)
+ @patch(
+ "src.utils.verifyer.sha256_verifyer.open",
+ new_callable=mock_open,
+ read_data=MOCK_SIG,
+ )
+ def test_on_release_button(
+ self,
+ open_mock,
+ mock_exists,
+ mock_copyfile,
+ mock_set_screen,
+ mock_set_background,
+ mock_partial,
+ mock_schedule_once,
+ mock_get_locale,
+ ):
+ screen = AirgapUpdateScreen()
+ screen.manager = MagicMock()
+ screen.manager.get_screen = MagicMock()
+
+ screen.update(name=screen.name, key="drives", value=[os.path.join("mock", "0")])
+
+ action = getattr(AirgapUpdateScreen, f"on_release_{screen.id}_button_0")
+ action(screen.ids[f"{screen.id}_button_0"])
+
+ mock_get_locale.assert_any_call()
+ mock_partial.assert_called()
+ mock_schedule_once.assert_called()
+ mock_set_background.assert_called_once_with(
+ wid=f"{screen.id}_button_0", rgba=(0, 0, 0, 1)
+ )
+ mock_set_screen.assert_called_once_with(
+ name="WarningAfterAirgapUpdateScreen", direction="left"
+ )
+ mock_copyfile.assert_called()
+ mock_exists.assert_called()
+ open_mock.assert_called()
diff --git a/e2e/test_028_warning_after_airgap_update_screen.py b/e2e/test_028_warning_after_airgap_update_screen.py
new file mode 100644
index 00000000..059a9388
--- /dev/null
+++ b/e2e/test_028_warning_after_airgap_update_screen.py
@@ -0,0 +1,125 @@
+import os
+from unittest.mock import patch
+from kivy.base import EventLoop, EventLoopBase
+from kivy.tests.common import GraphicUnitTest
+from kivy.core.text import LabelBase, DEFAULT_FONT
+from src.app.screens.warning_after_airgap_update_screen import (
+ WarningAfterAirgapUpdateScreen,
+)
+
+
+class TestWarningAfterAirgapUpdateScreen(GraphicUnitTest):
+
+ @classmethod
+ def setUpClass(cls):
+ cwd_path = os.path.dirname(__file__)
+ rel_assets_path = os.path.join(cwd_path, "..", "assets")
+ assets_path = os.path.abspath(rel_assets_path)
+ font_name = "NotoSansCJK_CY_JP_SC_KR_VI_Krux.ttf"
+ noto_sans_path = os.path.join(assets_path, font_name)
+ LabelBase.register(DEFAULT_FONT, noto_sans_path)
+
+ @classmethod
+ def teardown_class(cls):
+ EventLoop.exit()
+
+ @patch.object(EventLoopBase, "ensure_window", lambda x: None)
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
+ )
+ def test_render_main_screen(self, mock_get_locale):
+ screen = WarningAfterAirgapUpdateScreen()
+ self.render(screen)
+
+ # get your Window instance safely
+ EventLoop.ensure_window()
+ window = EventLoop.window
+
+ self.assertEqual(window.children[0], screen)
+ self.assertEqual(screen.name, "WarningAfterAirgapUpdateScreen")
+ self.assertEqual(screen.id, "warning_after_airgap_update_screen")
+ self.assertTrue("warning_after_airgap_update_screen_grid" in screen.ids)
+ self.assertTrue("warning_after_airgap_update_screen_subgrid" in screen.ids)
+ self.assertTrue("warning_after_airgap_update_screen_done" in screen.ids)
+ self.assertTrue("warning_after_airgap_update_screen_menu" in screen.ids)
+ self.assertTrue("warning_after_airgap_update_screen_label" in screen.ids)
+
+ mock_get_locale.assert_called()
+
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
+ )
+ @patch(
+ "src.app.screens.warning_after_airgap_update_screen.WarningAfterAirgapUpdateScreen.quit_app"
+ )
+ def test_on_ref_press_quit(self, mock_quit_app, mock_get_locale):
+ screen = WarningAfterAirgapUpdateScreen()
+
+ action = getattr(screen, "on_ref_press_warning_after_airgap_update_screen_menu")
+ action("Quit")
+
+ mock_quit_app.assert_called_once()
+ mock_get_locale.assert_any_call()
+
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
+ )
+ @patch("src.app.screens.base_screen.BaseScreen.set_screen")
+ def test_on_ref_press_back(self, mock_set_screen, mock_get_locale):
+ screen = WarningAfterAirgapUpdateScreen()
+
+ action = getattr(screen, "on_ref_press_warning_after_airgap_update_screen_menu")
+ action("MainScreen")
+
+ mock_set_screen.assert_called_once_with(name="MainScreen", direction="right")
+ mock_get_locale.assert_any_call()
+
+ @patch.object(EventLoopBase, "ensure_window", lambda x: None)
+ @patch(
+ "src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US.UTF-8"
+ )
+ def test_on_update_locale(self, mock_get_locale):
+ screen = WarningAfterAirgapUpdateScreen()
+ self.render(screen)
+
+ # get your Window instance safely
+ EventLoop.ensure_window()
+
+ screen.update(name=screen.name, key="label")
+ screen.update(name=screen.name, key="sdcard", value=os.path.join("tmp", "mock"))
+ screen.update(name=screen.name, key="hash", value="abcdef01234567890a")
+ screen.update(name=screen.name, key="locale", value="en_US.UTF-8")
+
+ text_menu = "".join(
+ [
+ ".bin and .sig have been copied to",
+ "\n",
+ f"[color=#efcc00]{os.path.join('tmp', 'mock')}[/color].",
+ "\n",
+ "\n",
+ "[color=#ff0000]",
+ "[u][ref=Quit]Quit[/ref][/u]",
+ "[/color]",
+ " ",
+ "[color=#00ff00]",
+ "[u][ref=MainScreen]Back[/ref][/u]",
+ "[/color]",
+ ]
+ )
+
+ text_label = "".join(
+ [
+ "* Insert the SDcard into your device and reboot it to update.",
+ "\n",
+ "\n",
+ "* You should see this computed hash on device screen:",
+ "\n",
+ "\n",
+ "[color=#efcc00]ab cd ef 01 23 45 67 89 0a",
+ "[/color]",
+ ]
+ )
+
+ self.assertEqual(screen.ids[f"{screen.id}_menu"].text, text_menu)
+ self.assertEqual(screen.ids[f"{screen.id}_label"].text, text_label)
+ mock_get_locale.assert_any_call()
diff --git a/e2e_drives/__init__.py b/e2e_drives/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/e2e_drives/test_000_base_screen_linux_logical_drives.py b/e2e_drives/test_000_base_screen_linux_logical_drives.py
new file mode 100644
index 00000000..dd08b39d
--- /dev/null
+++ b/e2e_drives/test_000_base_screen_linux_logical_drives.py
@@ -0,0 +1,59 @@
+import sys
+import importlib
+import unittest
+from unittest.mock import MagicMock, patch
+from src.app.screens.base_screen import BaseScreen
+
+MOCK_DISKS_LINUX = """
+NAME="sda" TYPE="disk" RM="0" MOUNTPOINT=""
+NAME="sda1" TYPE="part" RM="0" MOUNTPOINT="/boot"
+NAME="sda2" TYPE="part" RM="0" MOUNTPOINT="/"
+NAME="sda3" TYPE="part" RM="0" MOUNTPOINT="[SWAP]"
+NAME="sdb" TYPE="disk" RM="1" MOUNTPOINT=""
+NAME="sdb1" TYPE="part" RM="1" MOUNTPOINT="/media/mock/USB1"
+NAME="sdc" TYPE="disk" RM="0" MOUNTPOINT=""
+NAME="sdc1" TYPE="part" RM="0" MOUNTPOINT="/mnt/data"
+NAME="sdd" TYPE="disk" RM="1" MOUNTPOINT=""
+NAME="sdd1" TYPE="part" RM="1" MOUNTPOINT="/media/mock/USB2"
+"""
+
+
+class TestBaseScreenLinuxDrives(unittest.TestCase):
+ def setUp(self):
+ self.platform_patch = patch("sys.platform", "linux")
+ self.platform_patch.start()
+
+ mock_stdout = MagicMock(stdout=MOCK_DISKS_LINUX)
+
+ mock_run = MagicMock()
+ mock_run.return_value = mock_stdout
+
+ self.mock_subprocess = MagicMock()
+ self.mock_subprocess.run = mock_run
+
+ with patch.dict("sys.modules", {"subprocess": self.mock_subprocess}):
+ # Reload the base_screen module to apply the patch
+ importlib.reload(sys.modules["src.app.screens.base_screen"])
+
+ def tearDown(self):
+ self.platform_patch.stop()
+
+ @patch("src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US")
+ def test_on_get_removable_drives_windows(self, mock_get_locale):
+ screen = BaseScreen(wid="mock", name="Mock")
+ screen.screen_manager = MagicMock()
+ screen.screen_manager.get_screen = MagicMock()
+
+ disks = screen.on_get_removable_drives_linux()
+
+ self.assertEqual(len(disks), 2)
+ self.assertEqual(disks[0], "/media/mock/USB1")
+ self.assertEqual(disks[1], "/media/mock/USB2")
+
+ mock_get_locale.assert_called_once()
+ self.mock_subprocess.run.assert_called_once_with(
+ ["lsblk", "-P", "-o", "NAME,TYPE,RM,MOUNTPOINT"],
+ capture_output=True,
+ text=True,
+ check=True,
+ )
diff --git a/e2e_drives/test_001_base_screen_windows_logical_drives.py b/e2e_drives/test_001_base_screen_windows_logical_drives.py
new file mode 100644
index 00000000..ab294b43
--- /dev/null
+++ b/e2e_drives/test_001_base_screen_windows_logical_drives.py
@@ -0,0 +1,38 @@
+import sys
+import importlib
+import unittest
+from unittest.mock import MagicMock, patch
+from src.app.screens.base_screen import BaseScreen
+
+
+class TestBaseScreenWindowsDrives(unittest.TestCase):
+ def setUp(self):
+ # Patch `sys.platform` to simulate a Windows environment
+ self.platform_patch = patch("sys.platform", "win32")
+ self.platform_patch.start()
+ self.mock_win32file = MagicMock()
+
+ # Mock bitmask for drives D and E
+ self.mock_win32file.GetLogicalDrives.return_value = 0b011000
+ self.mock_win32file.DRIVE_REMOVABLE = 2 # Mock constant for removable drives
+ self.mock_win32file.GetDriveType.side_effect = lambda drive: (
+ self.mock_win32file.DRIVE_REMOVABLE if drive in ["D:\\", "E:\\"] else 3
+ )
+
+ with patch.dict("sys.modules", {"win32file": self.mock_win32file}):
+ # Reload the base_screen module to apply the patch
+ importlib.reload(sys.modules["src.app.screens.base_screen"])
+
+ def tearDown(self):
+ self.platform_patch.stop()
+
+ @patch("src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US")
+ def test_on_get_removable_drives_windows(self, mock_get_locale):
+ screen = BaseScreen(wid="mock", name="Mock")
+ screen.screen_manager = MagicMock()
+ screen.screen_manager.get_screen = MagicMock()
+ screen.on_get_removable_drives_windows()
+ self.mock_win32file.GetLogicalDrives.assert_called_once()
+ self.mock_win32file.GetDriveType.assert_any_call("D:\\")
+ self.mock_win32file.GetDriveType.assert_any_call("E:\\")
+ mock_get_locale.assert_called()
diff --git a/e2e_drives/test_002_base_screen_macos_logical_drives.py b/e2e_drives/test_002_base_screen_macos_logical_drives.py
new file mode 100644
index 00000000..3e845f50
--- /dev/null
+++ b/e2e_drives/test_002_base_screen_macos_logical_drives.py
@@ -0,0 +1,111 @@
+import sys
+import importlib
+import unittest
+from unittest.mock import MagicMock, patch
+from src.app.screens.base_screen import BaseScreen
+
+MOCK_DISKS_MAC = """
+ Device Identifier: disk0
+ Device Node: /dev/disk0
+ Whole: Yes
+ Part of Whole: disk0
+ Device / Media Name: Apple SSD
+ Volume Name: Macintosh HD
+ Mounted: Yes
+ Mount Point: /
+ File System: APFS
+ Content (IOContent): Apple_APFS
+ Device Block Size: 512 Bytes
+ Disk Size: 500.3 GB (500279395328 Bytes)
+ Read-Only Media: No
+ Removable Media: No
+ Solid State: Yes
+ Virtual: No
+ Ejectable: No
+
+ Device Identifier: disk0s1
+ Device Node: /dev/disk0s1
+ Whole: No
+ Part of Whole: disk0
+ Volume Name: EFI
+ Mounted: No
+ File System: MS-DOS (FAT32)
+ Content (IOContent): EFI
+ Device Block Size: 512 Bytes
+ Disk Size: 209.7 MB (209715200 Bytes)
+ Read-Only Media: No
+ Removable Media: No
+
+ Device Identifier: disk0s2
+ Device Node: /dev/disk0s2
+ Whole: No
+ Part of Whole: disk0
+ Volume Name: Macintosh HD - Data
+ Mounted: Yes
+ Mount Point: /System/Volumes/Data
+ File System: APFS
+ Content (IOContent): Apple_APFS
+ Device Block Size: 512 Bytes
+ Disk Size: 500.1 GB (500000000000 Bytes)
+ Read-Only Media: No
+ Removable Media: No
+
+ Device Identifier: disk1
+ Device Node: /dev/disk1
+ Device Location: External
+ Whole: Yes
+ Part of Whole: disk1
+ Device / Media Name: External USB Drive
+ Volume Name: Backup Drive
+ Mounted: Yes
+ Mount Point: /Volumes/Backup Drive
+ File System: FAT32
+ File System Personality MS-DOS FAT32
+ Content (IOContent): Apple_HFS
+ Device Block Size: 4096 Bytes
+ Disk Size: 2.0 TB (2000000000000 Bytes)
+ Read-Only Media: No
+ Removable Media: Yes
+ Solid State: No
+ Virtual: No
+ Ejectable: Yes
+"""
+
+
+class TestBaseScreenLinuxDrives(unittest.TestCase):
+ def setUp(self):
+ self.platform_patch = patch("sys.platform", "darwin")
+ self.platform_patch.start()
+
+ mock_stdout = MagicMock(stdout=MOCK_DISKS_MAC)
+
+ mock_run = MagicMock()
+ mock_run.return_value = mock_stdout
+
+ self.mock_subprocess = MagicMock()
+ self.mock_subprocess.run = mock_run
+
+ with patch.dict("sys.modules", {"subprocess": self.mock_subprocess}):
+ # Reload the base_screen module to apply the patch
+ importlib.reload(sys.modules["src.app.screens.base_screen"])
+
+ def tearDown(self):
+ self.platform_patch.stop()
+
+ @patch("src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US")
+ def test_on_get_removable_drives_mac(self, mock_get_locale):
+ screen = BaseScreen(wid="mock", name="Mock")
+ screen.screen_manager = MagicMock()
+ screen.screen_manager.get_screen = MagicMock()
+
+ disks = screen.on_get_removable_drives_macos()
+
+ self.assertEqual(len(disks), 1)
+
+ mock_get_locale.assert_called_once()
+ self.mock_subprocess.run.assert_called_once_with(
+ ["diskutil", "info", "-all"],
+ capture_output=True,
+ text=True,
+ check=True,
+ )
diff --git a/e2e_drives/test_003_fail_base_screen_linux_logical_drives.py b/e2e_drives/test_003_fail_base_screen_linux_logical_drives.py
new file mode 100644
index 00000000..79e8d9a8
--- /dev/null
+++ b/e2e_drives/test_003_fail_base_screen_linux_logical_drives.py
@@ -0,0 +1,64 @@
+import sys
+import importlib
+import unittest
+from unittest.mock import MagicMock, patch
+from src.app.screens.base_screen import BaseScreen
+
+MOCK_DISKS_LINUX = """
+NAME="sda" TYPE="disk" RM="0" MOUNTPOINT=""
+NAME="sda1" TYPE="part" RM="0" MOUNTPOINT="/boot"
+NAME="sda2" TYPE="part" RM="0" MOUNTPOINT="/"
+NAME="sda3" TYPE="part" RM="0" MOUNTPOINT="[SWAP]"
+NAME="sdb" TYPE="disk" RM="1" MOUNTPOINT=""
+NAME="sdb1" TYPE="part" RM="1" MOUNTPOINT="/media/mock/USB1"
+NAME="sdc" TYPE="disk" RM="0" MOUNTPOINT=""
+NAME="sdc1" TYPE="part" RM="0" MOUNTPOINT="/mnt/data"
+NAME="sdd" TYPE="disk" RM="1" MOUNTPOINT=""
+NAME="sdd1" TYPE="part" RM="1" MOUNTPOINT="/media/mock/USB2"
+"""
+
+
+# Mock named exception for this test
+class CalledProcessError(Exception):
+
+ def __init__(self, cmd, returncode):
+ super().__init__(cmd)
+ self.returncode = returncode
+
+
+class TestBaseScreenLinuxDrives(unittest.TestCase):
+ def setUp(self):
+ self.platform_patch = patch("sys.platform", "linux")
+ self.platform_patch.start()
+
+ mock_run = MagicMock()
+ mock_run.side_effect = CalledProcessError(cmd="mock", returncode=1)
+
+ self.mock_subprocess = MagicMock()
+ self.mock_subprocess.run = mock_run
+ self.mock_subprocess.CalledProcessError = CalledProcessError
+
+ with patch.dict("sys.modules", {"subprocess": self.mock_subprocess}):
+ # Reload the base_screen module to apply the patch
+ importlib.reload(sys.modules["src.app.screens.base_screen"])
+
+ def tearDown(self):
+ self.platform_patch.stop()
+
+ @patch("src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US")
+ def test_on_get_removable_drives_windows(self, mock_get_locale):
+ screen = BaseScreen(wid="mock", name="Mock")
+ screen.manager = MagicMock()
+ screen.manager.get_screen = MagicMock()
+ screen.redirect_exception = MagicMock()
+
+ screen.on_get_removable_drives_linux()
+
+ mock_get_locale.assert_called_once()
+ self.mock_subprocess.run.assert_called_once_with(
+ ["lsblk", "-P", "-o", "NAME,TYPE,RM,MOUNTPOINT"],
+ capture_output=True,
+ text=True,
+ check=True,
+ )
+ screen.redirect_exception.assert_called()
diff --git a/e2e_drives/test_004_fail_base_screen_windows_logical_drives.py b/e2e_drives/test_004_fail_base_screen_windows_logical_drives.py
new file mode 100644
index 00000000..15dd9f0b
--- /dev/null
+++ b/e2e_drives/test_004_fail_base_screen_windows_logical_drives.py
@@ -0,0 +1,32 @@
+import sys
+import importlib
+import unittest
+from unittest.mock import MagicMock, patch
+from src.app.screens.base_screen import BaseScreen
+
+
+class TestFailBaseScreenWindowsDrives(unittest.TestCase):
+
+ def setUp(self):
+ self.platform_patch = patch("sys.platform", "win32")
+ self.platform_patch.start()
+ self.mock_win32file = MagicMock()
+ self.mock_win32file.GetLogicalDrives.side_effect = Exception("mock")
+
+ with patch.dict("sys.modules", {"win32file": self.mock_win32file}):
+ # Reload the base_screen module to apply the patch
+ importlib.reload(sys.modules["src.app.screens.base_screen"])
+
+ def tearDown(self):
+ self.platform_patch.stop()
+
+ @patch("src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US")
+ def test_fail_on_get_removable_drives_windows(self, mock_get_locale):
+ screen = BaseScreen(wid="mock", name="Mock")
+ screen.manager = MagicMock()
+ screen.manager.get_screen = MagicMock()
+ screen.redirect_exception = MagicMock()
+ screen.on_get_removable_drives_windows()
+ self.mock_win32file.GetLogicalDrives.assert_called_once()
+ screen.redirect_exception.assert_called()
+ mock_get_locale.assert_called_once()
diff --git a/e2e_drives/test_005_fail_base_screen_macos_logical_drives.py b/e2e_drives/test_005_fail_base_screen_macos_logical_drives.py
new file mode 100644
index 00000000..ff31f637
--- /dev/null
+++ b/e2e_drives/test_005_fail_base_screen_macos_logical_drives.py
@@ -0,0 +1,51 @@
+import sys
+import importlib
+import unittest
+from unittest.mock import MagicMock, patch
+from src.app.screens.base_screen import BaseScreen
+
+
+# Mock named exception for this test
+class CalledProcessError(Exception):
+
+ def __init__(self, cmd, returncode):
+ super().__init__(cmd)
+ self.returncode = returncode
+
+
+class TestFailBaseScreenMacDrives(unittest.TestCase):
+ def setUp(self):
+ self.platform_patch = patch("sys.platform", "darwin")
+ self.platform_patch.start()
+
+ mock_run = MagicMock()
+ mock_run.side_effect = CalledProcessError(cmd="mock", returncode=1)
+
+ self.mock_subprocess = MagicMock()
+ self.mock_subprocess.run = mock_run
+ self.mock_subprocess.CalledProcessError = CalledProcessError
+
+ with patch.dict("sys.modules", {"subprocess": self.mock_subprocess}):
+ # Reload the base_screen module to apply the patch
+ importlib.reload(sys.modules["src.app.screens.base_screen"])
+
+ def tearDown(self):
+ self.platform_patch.stop()
+
+ @patch("src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US")
+ def test_on_get_removable_drives_mac(self, mock_get_locale):
+ screen = BaseScreen(wid="mock", name="Mock")
+ screen.manager = MagicMock()
+ screen.manager.get_screen = MagicMock()
+ screen.redirect_exception = MagicMock()
+
+ screen.on_get_removable_drives_macos()
+
+ mock_get_locale.assert_called_once()
+ self.mock_subprocess.run.assert_called_once_with(
+ ["diskutil", "info", "-all"],
+ capture_output=True,
+ text=True,
+ check=True,
+ )
+ screen.redirect_exception.assert_called()
diff --git a/e2e_drives/test_006_fail_unknow_base_screen_linux_logical_drives.py b/e2e_drives/test_006_fail_unknow_base_screen_linux_logical_drives.py
new file mode 100644
index 00000000..3ed1356c
--- /dev/null
+++ b/e2e_drives/test_006_fail_unknow_base_screen_linux_logical_drives.py
@@ -0,0 +1,51 @@
+import sys
+import importlib
+import unittest
+from unittest.mock import MagicMock, patch
+from src.app.screens.base_screen import BaseScreen
+
+
+# Mock named exception for this test
+class CalledProcessError(Exception):
+
+ def __init__(self, cmd, returncode):
+ super().__init__(cmd)
+ self.returncode = returncode
+
+
+class TestBaseScreenLinuxDrives(unittest.TestCase):
+ def setUp(self):
+ self.platform_patch = patch("sys.platform", "linux")
+ self.platform_patch.start()
+
+ mock_run = MagicMock()
+ mock_run.side_effect = Exception("mock")
+
+ self.mock_subprocess = MagicMock()
+ self.mock_subprocess.run = mock_run
+ self.mock_subprocess.CalledProcessError = CalledProcessError
+
+ with patch.dict("sys.modules", {"subprocess": self.mock_subprocess}):
+ # Reload the base_screen module to apply the patch
+ importlib.reload(sys.modules["src.app.screens.base_screen"])
+
+ def tearDown(self):
+ self.platform_patch.stop()
+
+ @patch("src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US")
+ def test_on_get_removable_drives_windows(self, mock_get_locale):
+ screen = BaseScreen(wid="mock", name="Mock")
+ screen.manager = MagicMock()
+ screen.manager.get_screen = MagicMock()
+ screen.redirect_exception = MagicMock()
+
+ screen.on_get_removable_drives_linux()
+
+ mock_get_locale.assert_called_once()
+ self.mock_subprocess.run.assert_called_once_with(
+ ["lsblk", "-P", "-o", "NAME,TYPE,RM,MOUNTPOINT"],
+ capture_output=True,
+ text=True,
+ check=True,
+ )
+ screen.redirect_exception.assert_called()
diff --git a/e2e_drives/test_007_fail_unknow_base_screen_macos_logical_drives.py b/e2e_drives/test_007_fail_unknow_base_screen_macos_logical_drives.py
new file mode 100644
index 00000000..52763eca
--- /dev/null
+++ b/e2e_drives/test_007_fail_unknow_base_screen_macos_logical_drives.py
@@ -0,0 +1,51 @@
+import sys
+import importlib
+import unittest
+from unittest.mock import MagicMock, patch
+from src.app.screens.base_screen import BaseScreen
+
+
+# Mock named exception for this test
+class CalledProcessError(Exception):
+
+ def __init__(self, cmd, returncode):
+ super().__init__(cmd)
+ self.returncode = returncode
+
+
+class TestFailBaseScreenMacDrives(unittest.TestCase):
+ def setUp(self):
+ self.platform_patch = patch("sys.platform", "darwin")
+ self.platform_patch.start()
+
+ mock_run = MagicMock()
+ mock_run.side_effect = Exception("mock")
+
+ self.mock_subprocess = MagicMock()
+ self.mock_subprocess.run = mock_run
+ self.mock_subprocess.CalledProcessError = CalledProcessError
+
+ with patch.dict("sys.modules", {"subprocess": self.mock_subprocess}):
+ # Reload the base_screen module to apply the patch
+ importlib.reload(sys.modules["src.app.screens.base_screen"])
+
+ def tearDown(self):
+ self.platform_patch.stop()
+
+ @patch("src.app.screens.base_screen.BaseScreen.get_locale", return_value="en_US")
+ def test_on_get_removable_drives_mac(self, mock_get_locale):
+ screen = BaseScreen(wid="mock", name="Mock")
+ screen.manager = MagicMock()
+ screen.manager.get_screen = MagicMock()
+ screen.redirect_exception = MagicMock()
+
+ screen.on_get_removable_drives_macos()
+
+ mock_get_locale.assert_called_once()
+ self.mock_subprocess.run.assert_called_once_with(
+ ["diskutil", "info", "-all"],
+ capture_output=True,
+ text=True,
+ check=True,
+ )
+ screen.redirect_exception.assert_called()
diff --git a/img/badge_github.png b/img/badge_github.png
new file mode 100644
index 00000000..326d2547
Binary files /dev/null and b/img/badge_github.png differ
diff --git a/poetry.lock b/poetry.lock
index 6ae5f6b7..cfa18de6 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -710,7 +710,6 @@ files = [
{file = "kivy_deps.angle-0.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:574381d4e66f3198bc48aa10f238e7a3816ad56b80ec939f5d56fb33a378d0b1"},
{file = "kivy_deps.angle-0.4.0-cp312-cp312-win32.whl", hash = "sha256:4fa7a6366899fba13f7624baf4645787165f45731db08d14557da29c12ee48f0"},
{file = "kivy_deps.angle-0.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:668e670d4afd2551af0af2c627ceb0feac884bd799fb6a3dff78fdbfa2ea0451"},
- {file = "kivy_deps.angle-0.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:9afbf702f8bb9a993c48f39c018ca3b4d2ec381a5d3f82fe65bdaa6af0bba29b"},
{file = "kivy_deps.angle-0.4.0-cp37-cp37m-win32.whl", hash = "sha256:24cfc0076d558080a00c443c7117311b4a977c1916fe297232eff1fd6f62651e"},
{file = "kivy_deps.angle-0.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:48592ac6f7c183c5cd10d9ebe43d4148d0b2b9e400a2b0bcb5d21014cc929ce2"},
{file = "kivy_deps.angle-0.4.0-cp38-cp38-win32.whl", hash = "sha256:1bbacf20bf6bd6ee965388f95d937c8fba2c54916fb44faa166c2ba58276753c"},
@@ -732,7 +731,6 @@ files = [
{file = "kivy_deps.glew-0.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:22e155ec59ce717387f5d8804811206d200a023ba3d0bc9bbf1393ee28d0053e"},
{file = "kivy_deps.glew-0.3.1-cp312-cp312-win32.whl", hash = "sha256:b64ee4e445a04bc7c848c0261a6045fc2f0944cc05d7f953e3860b49f2703424"},
{file = "kivy_deps.glew-0.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:3acbbd30da05fc10c185b5d4bb75fbbc882a6ef2192963050c1c94d60a6e795a"},
- {file = "kivy_deps.glew-0.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:f4aa8322078359862ccd9e16e5cea61976d75fb43125d87922e20c916fa31a11"},
{file = "kivy_deps.glew-0.3.1-cp37-cp37m-win32.whl", hash = "sha256:5bf6a63fe9cc4fe7bbf280ec267ec8c47914020a1175fb22152525ff1837b436"},
{file = "kivy_deps.glew-0.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:d64a8625799fab7a7efeb3661ef8779a7f9c6d80da53eed87a956320f55530fa"},
{file = "kivy_deps.glew-0.3.1-cp38-cp38-win32.whl", hash = "sha256:00f4ae0a4682d951266458ddb639451edb24baa54a35215dce889209daf19a06"},
diff --git a/pyproject.toml b/pyproject.toml
index a35ef8c1..ac8a535c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -3,7 +3,7 @@ prefer-active-python = true
[tool.poetry]
name = "krux-installer"
-version = "0.0.20-beta"
+version = "0.0.20"
description = "A GUI based application to flash Krux firmware on K210 based devices"
authors = [
"qlrd "
@@ -40,22 +40,26 @@ cli = "python src/krux-installer.py"
format-src= "black ./src"
format-tests= "black ./tests"
format-e2e= "black ./e2e"
+format-drives= "black ./e2e_drives"
format-installer = "black ./krux-installer.py"
-format = ["format-src", "format-tests", "format-e2e", "format-installer"]
+format = ["format-src", "format-tests", "format-e2e", "format-drives", "format-installer"]
test-unit = "pytest --cache-clear --cov=src/utils/constants --cov=src/utils/info --cov=src/utils/selector --cov=src/utils/downloader --cov=src/utils/trigger --cov=src/utils/flasher --cov=src/utils/unzip --cov=src/utils/signer --cov=src/utils/verifyer --cov=src/i18n --cov-branch --cov-report html ./tests"
test-e2e = "pytest --cov-append --cov=src/app --cov-branch --cov-report html ./e2e"
-test = ["test-unit", "test-e2e"]
+test-drives = "pytest --cov-append --cov=src/app --cov-branch --cov-report html ./e2e_drives"
+test = ["test-unit", "test-e2e", "test-drives"]
coverage-unit = "pytest --cache-clear --cov=src/utils/constants --cov=src/utils/info --cov=src/utils/selector --cov=src/utils/downloader --cov=src/utils/trigger --cov=src/utils/flasher --cov=src/utils/unzip --cov=src/utils/signer --cov=src/utils/verifyer --cov=src/i18n --cov-branch --cov-report xml ./tests"
coverage-e2e = "pytest --cov-append --cov=src/app --cov-branch --cov-report xml ./e2e"
-coverage = ["coverage-unit", "coverage-e2e"]
+coverage-drives = "pytest --cov-append --cov=src/app --cov-branch --cov-report xml ./e2e_drives"
+coverage = ["coverage-unit", "coverage-e2e", "coverage-drives"]
lint.sequence = [
{ cmd = "jsonlint src/i18n/*.json"},
{ cmd = "pylint --rcfile=.pylint/src ./src" },
{ cmd = "pylint --rcfile=.pylint/tests ./tests"},
- { cmd = "pylint --rcfile=.pylint/tests ./e2e"}
+ { cmd = "pylint --rcfile=.pylint/tests ./e2e"},
+ { cmd = "pylint --rcfile=.pylint/tests ./e2e_drives"}
]
build-linux.sequence = [
diff --git a/src/app/config_krux_installer.py b/src/app/config_krux_installer.py
index 66695d33..9d44cf3b 100644
--- a/src/app/config_krux_installer.py
+++ b/src/app/config_krux_installer.py
@@ -77,7 +77,7 @@ def make_lang_code(lang: str) -> str:
return lang
raise OSError(
- f"Couldn 't possible to setup locale: OS '{sys.platform}' not implemented"
+ f"Couldn't possible to setup locale: OS '{sys.platform}' not implemented"
)
@staticmethod
diff --git a/src/app/screens/airgap_update_screen.py b/src/app/screens/airgap_update_screen.py
index 0c4a9aca..16e86bc5 100644
--- a/src/app/screens/airgap_update_screen.py
+++ b/src/app/screens/airgap_update_screen.py
@@ -47,40 +47,55 @@ def on_press(instance):
self.debug(f"Calling {instance.id}::on_press")
self.set_background(wid=instance.id, rgba=(0.25, 0.25, 0.25, 1))
+ setattr(AirgapUpdateScreen, f"on_press_{self.id}_button_{row}", on_press)
+
def on_release(instance):
- new_firmware_bin = os.path.join(drive, "firmware.bin")
- new_firmware_sig = os.path.join(drive, "firmware.bin.sig")
- shutil.copyfile(self.firmware_bin, new_firmware_bin)
- shutil.copyfile(self.firmware_sig, new_firmware_sig)
-
- # After copy, make sha256 hash to show
- sha256 = Sha256Verifyer(filename=new_firmware_bin)
- sha256.load()
-
- # Now update the next screen
- warn_screen = self.manager.get_screen("WarningAfterAirgapUpdateScreen")
-
- fns = [
- partial(
- warn_screen.update,
- name=self.name,
- key="sdcard",
- value=drive,
- ),
- partial(
- warn_screen.update,
- name=self.name,
- key="hash",
- value=sha256.data.split(" ", maxsplit=1)[0],
- ),
- partial(warn_screen.update, name=self.name, key="label"),
- ]
-
- for fn in fns:
- Clock.schedule_once(fn, 0)
-
- self.set_background(wid=instance.id, rgba=(0, 0, 0, 1))
- self.set_screen(name="WarningAfterAirgapUpdateScreen", direction="left")
+ new_firmware_bin = os.path.normpath(os.path.join(drive, "firmware.bin"))
+ new_firmware_sig = os.path.normpath(os.path.join(drive, "firmware.bin.sig"))
+ try:
+ shutil.copyfile(self.firmware_bin, new_firmware_bin)
+ shutil.copyfile(self.firmware_sig, new_firmware_sig)
+
+ # After copy, make sha256 hash to show
+ sha256 = Sha256Verifyer(filename=new_firmware_bin)
+ sha256.load()
+
+ # Now update the next screen
+ warn_screen = self.manager.get_screen("WarningAfterAirgapUpdateScreen")
+
+ fns = [
+ partial(
+ warn_screen.update,
+ name=self.name,
+ key="sdcard",
+ value=drive,
+ ),
+ partial(
+ warn_screen.update,
+ name=self.name,
+ key="hash",
+ value=sha256.data.split(" ", maxsplit=1)[0],
+ ),
+ partial(warn_screen.update, name=self.name, key="label"),
+ ]
+
+ for fn in fns:
+ Clock.schedule_once(fn, 0)
+
+ self.set_background(wid=instance.id, rgba=(0, 0, 0, 1))
+ self.set_screen(name="WarningAfterAirgapUpdateScreen", direction="left")
+
+ except shutil.Error as err_exc:
+ self.redirect_exception(exception=err_exc)
+
+ except shutil.ExecError as exec_exc:
+ self.redirect_exception(exception=exec_exc)
+
+ # pylint: disable=broad-exception-caught
+ except Exception as exc:
+ self.redirect_exception(exception=exc)
+
+ setattr(AirgapUpdateScreen, f"on_release_{self.id}_button_{row}", on_release)
select = self.translate("Select")
to_copy = self.translate("to copy firmware")
diff --git a/src/app/screens/ask_permission_dialout_screen.py b/src/app/screens/ask_permission_dialout_screen.py
index d5e1a4c1..b79c9be1 100644
--- a/src/app/screens/ask_permission_dialout_screen.py
+++ b/src/app/screens/ask_permission_dialout_screen.py
@@ -86,7 +86,6 @@ def on_ref_press(*args):
# pylint: disable=broad-exception-caught
except Exception as err:
- self.error(str(err))
self.redirect_exception(exception=err)
if args[1] == "Deny":
@@ -137,6 +136,11 @@ def detect_usermod_bin(self):
elif "ID_LIKE" in os_data and "suse" in os_data["ID_LIKE"]:
bin_path = "/usr/sbin/usermod"
+ # Check for Fedora, to fix issue #115
+ # see https://github.com/selfcustody/krux-installer/issues/115
+ elif "ID" in os_data and "fedora" in os_data["ID"]:
+ bin_path = "/usr/sbin/usermod"
+
# Arch, Manjaro, Slackware, Gentoo
elif os_data.get("ID") in ("arch", "manjaro", "slackware", "gentoo"):
bin_path = "/usr/bin/usermod"
diff --git a/src/app/screens/base_flash_screen.py b/src/app/screens/base_flash_screen.py
index 86c21187..2f1f0c7e 100644
--- a/src/app/screens/base_flash_screen.py
+++ b/src/app/screens/base_flash_screen.py
@@ -140,10 +140,22 @@ def on_done(dt):
back = self.translate("Back")
_quit = self.translate("Quit")
+ # If the done step occurs in FlashScreen,
+ # just put a line break
+ # It it occurs in WipeScreen,
+ # add a message to remove device and re-plug
+ # it again before flash
+ below_done = "\n"
+ if self.name == "WipeScreen":
+ below_done += self.translate(
+ "disconnect and reconnect device before flash again"
+ )
+ below_done += "\n"
+
self.ids[f"{self.id}_progress"].text = "".join(
[
f"[b]{done}![/b]",
- "\n",
+ below_done,
"[color=#00FF00]",
f"[ref=Back][u]{back}[/u][/ref]",
"[/color]",
diff --git a/src/app/screens/base_screen.py b/src/app/screens/base_screen.py
index 06de292e..343c7d4e 100644
--- a/src/app/screens/base_screen.py
+++ b/src/app/screens/base_screen.py
@@ -294,6 +294,11 @@ def on_get_removable_drives_linux(self) -> typing.List[str]:
exc = RuntimeError(f"Error detecting removable drives:\n{e}")
self.redirect_exception(exception=exc)
+ # pylint: disable=broad-exception-caught
+ except Exception as e:
+ exc = RuntimeError(f"Unknow error while detecting removable drives:\n{e}")
+ self.redirect_exception(exception=exc)
+
return drive_list
def on_get_removable_drives_macos(self) -> typing.List[str]:
@@ -351,6 +356,11 @@ def on_get_removable_drives_macos(self) -> typing.List[str]:
exc = RuntimeError(f"Error detecting removable drives:\n{e}")
self.redirect_exception(exception=exc)
+ # pylint: disable=broad-exception-caught
+ except Exception as e:
+ exc = RuntimeError(f"Unknow error while detecting removable drives:\n{e}")
+ self.redirect_exception(exception=exc)
+
return drive_list
def on_get_removable_drives_windows(self) -> typing.List[str]:
@@ -391,6 +401,7 @@ def on_get_removable_drives_windows(self) -> typing.List[str]:
def redirect_exception(self, exception: Exception):
"""Get an exception and prepare a ErrorScreen rendering"""
+ print(exception)
screen = self.manager.get_screen("ErrorScreen")
fns = [
partial(screen.update, name=self.name, key="canvas"),
diff --git a/src/app/screens/flash_screen.py b/src/app/screens/flash_screen.py
index 24fb2bb1..a2450951 100644
--- a/src/app/screens/flash_screen.py
+++ b/src/app/screens/flash_screen.py
@@ -24,7 +24,6 @@
import threading
import traceback
from functools import partial
-from kivy.app import App
from kivy.clock import Clock
from src.app.screens.base_flash_screen import BaseFlashScreen
from src.utils.flasher import Flasher
@@ -39,6 +38,7 @@ def __init__(self, **kwargs):
self.flashing_msg = self.translate("Flashing")
self.at_msg = self.translate("at")
self.flasher = Flasher()
+ self.fail_msg = ""
fn = partial(self.update, name=self.name, key="canvas")
Clock.schedule_once(fn, 0)
@@ -71,6 +71,11 @@ def on_data(*args, **kwargs):
self.output.append("*")
self.output.append("")
+ elif "Greeting fail" in text:
+ self.fail_msg = text
+ self.flasher.ktool.kill()
+ self.flasher.ktool.checkKillExit()
+
if len(self.output) > 10:
del self.output[:1]
@@ -118,16 +123,16 @@ def on_pre_enter(self, *args):
self.build_on_process()
self.build_on_done()
+ wid = f"{self.id}_info"
+
def on_ref_press(*args):
if args[1] == "Back":
self.set_screen(name="MainScreen", direction="right")
- elif args[1] == "Quit":
- App.get_running_app().stop()
+ if args[1] == "Quit":
+ self.quit_app()
- else:
- exc = RuntimeError(f"Invalid ref: {args[1]}")
- self.redirect_exception(exception=exc)
+ setattr(FlashScreen, f"on_ref_press_{wid}", on_ref_press)
self.make_subgrid(
wid=f"{self.id}_subgrid", rows=2, root_widget=f"{self.id}_grid"
@@ -157,7 +162,7 @@ def on_ref_press(*args):
self.make_button(
row=2,
- wid=f"{self.id}_info",
+ wid=wid,
text="",
font_factor=72,
root_widget=f"{self.id}_grid",
@@ -186,11 +191,33 @@ def hook(err):
err.exc_type, err.exc_value, err.exc_traceback
)
msg = "".join(trace[-2:])
- self.error(msg)
- self.redirect_exception(exception=RuntimeError(f"Flash failed: {msg}"))
+ general_msg = "".join(
+ [
+ "Ensure that you have selected the correct device ",
+ "and that your computer has successfully detected it.",
+ ]
+ )
+
+ if "StopIteration" in msg:
+ self.fail_msg = msg
+ self.fail_msg += f"\n\n{general_msg}"
+ not_conn_fail = RuntimeError(f"Flash failed:\n{self.fail_msg}\n")
+ self.redirect_exception(exception=not_conn_fail)
+
+ elif "Cancel" in msg:
+ self.fail_msg = f"{self.fail_msg}\n\n{general_msg}"
+ greeting_fail = RuntimeError(f"Flash failed:\n{self.fail_msg}\n")
+ self.redirect_exception(exception=greeting_fail)
+
+ else:
+ self.fail_msg = msg
+ any_fail = RuntimeError(f"Flash failed:\n{self.fail_msg}\n")
+ self.redirect_exception(exception=any_fail)
+
+ setattr(FlashScreen, "on_except_hook", hook)
# hook what happened
- threading.excepthook = hook
+ threading.excepthook = getattr(FlashScreen, "on_except_hook")
# start thread
self.thread.start()
diff --git a/src/app/screens/greetings_screen.py b/src/app/screens/greetings_screen.py
index 890af219..2c0aaeca 100644
--- a/src/app/screens/greetings_screen.py
+++ b/src/app/screens/greetings_screen.py
@@ -112,7 +112,7 @@ def get_os_dialout_group(self):
"dialout",
) # Pop!_OS will fall under this
- # Check for Red Hat-based systems (Fedora, CentOS, Rocky Linux, etc.)
+ # Check for Red Hat-based systems (CentOS, Rocky Linux, etc.)
if "ID_LIKE" in os_data and "rhel" in os_data["ID_LIKE"]:
detected = (
os_data["ID_LIKE"],
@@ -126,6 +126,14 @@ def get_os_dialout_group(self):
"dialout",
) # SUSE systems also often use `dialout`
+ # Check for Fedora, to fix issue #115
+ # see https://github.com/selfcustody/krux-installer/issues/115
+ elif "ID" in os_data and "fedora" in os_data["ID"]:
+ detected = (
+ os_data["ID"],
+ "dialout",
+ ) # FEDORA systems also often use `dialout`
+
# Arch, Manjaro, Slackware, Gentoo
if os_data.get("ID") in ("arch", "manjaro", "slackware", "gentoo"):
detected = (os_data["ID"], "uucp")
diff --git a/src/app/screens/main_screen.py b/src/app/screens/main_screen.py
index c0b16403..b24431cd 100644
--- a/src/app/screens/main_screen.py
+++ b/src/app/screens/main_screen.py
@@ -23,6 +23,7 @@
"""
import os
import re
+import typing
from functools import partial
from kivy.clock import Clock
from src.utils.selector import VALID_DEVICES
@@ -190,6 +191,71 @@ def on_release_select_device(instance):
)
self.ids[wid].size_hint = (1, 1)
+ def on_check_any_official_release(
+ self, partial_list: typing.List[typing.Callable]
+ ) -> str:
+ """Check if any official release file exists"""
+ resources = MainScreen.get_destdir_assets()
+ zipfile = os.path.join(resources, f"krux-{self.version}.zip")
+ to_screen = None
+
+ if os.path.isfile(zipfile):
+ to_screen = "WarningAlreadyDownloadedScreen"
+ else:
+ to_screen = "DownloadStableZipScreen"
+
+ screen = self.manager.get_screen(to_screen)
+ partial_list.append(
+ partial(
+ screen.update,
+ name=self.name,
+ key="canvas",
+ )
+ )
+ partial_list.append(
+ partial(
+ screen.update,
+ name=self.name,
+ key="version",
+ value=self.version,
+ )
+ )
+
+ return to_screen
+
+ def on_check_any_beta_release(
+ self, partial_list: typing.List[typing.Callable]
+ ) -> str:
+ """Check if release is beta"""
+ to_screen = "DownloadBetaScreen"
+ screen = self.manager.get_screen(to_screen)
+ partial_list.append(
+ partial(
+ screen.update,
+ name=self.name,
+ key="canvas",
+ )
+ )
+ partial_list.append(
+ partial(
+ screen.update,
+ name=self.name,
+ key="firmware",
+ value="kboot.kfpkg",
+ )
+ )
+ partial_list.append(
+ partial(
+ screen.update,
+ name=self.name,
+ key="device",
+ value=self.device,
+ )
+ )
+
+ partial_list.append(partial(screen.update, name=self.name, key="downloader"))
+ return to_screen
+
def build_flash_button(self):
"""Create staticmethods using instance variables to control the flash button"""
wid = "main_flash"
@@ -211,75 +277,33 @@ def on_release_flash(instance):
# partials are functions that call `update`
# method in screen before go to them
- partials = []
-
- # Check if any official release file exists
- if re.findall(r"^v\d+\.\d+\.\d$", self.version):
- resources = MainScreen.get_destdir_assets()
- zipfile = os.path.join(resources, f"krux-{self.version}.zip")
-
- if os.path.isfile(zipfile):
- to_screen = "WarningAlreadyDownloadedScreen"
- else:
- to_screen = "DownloadStableZipScreen"
-
- screen = self.manager.get_screen(to_screen)
- partials.append(
- partial(
- screen.update,
- name=self.name,
- key="canvas",
- )
- )
- partials.append(
- partial(
- screen.update,
- name=self.name,
- key="version",
- value=self.version,
- )
- )
+ partial_list = []
+ err = None
- # check if release is beta
- elif re.findall("^odudex/krux_binaries", self.version):
- to_screen = "DownloadBetaScreen"
- screen = self.manager.get_screen(to_screen)
- partials.append(
- partial(
- screen.update,
- name=self.name,
- key="canvas",
- )
- )
- partials.append(
- partial(
- screen.update,
- name=self.name,
- key="firmware",
- value="kboot.kfpkg",
- )
- )
- partials.append(
- partial(
- screen.update,
- name=self.name,
- key="device",
- value=self.device,
- )
+ if re.match(r"^v\d+\.\d+\.\d$", self.version):
+ to_screen = self.on_check_any_official_release(
+ partial_list=partial_list
)
- partials.append(
- partial(screen.update, name=self.name, key="downloader")
+
+ elif re.match("^odudex/krux_binaries", self.version):
+ to_screen = self.on_check_any_beta_release(
+ partial_list=partial_list
)
+ else:
+ err = RuntimeError(f"version '{self.version}' not supported")
+ self.redirect_exception(exception=err)
+ return
+
# Execute the partials
- for fn in partials:
+ for fn in partial_list:
Clock.schedule_once(fn, 0)
# Goto the selected screen
self.set_screen(name=to_screen, direction="left")
- setattr(MainScreen, "on_press_flash", on_press_flash)
- setattr(MainScreen, "on_release_flash", on_release_flash)
+ setattr(MainScreen, f"on_press_{wid}", on_press_flash)
+ setattr(MainScreen, f"on_release_{wid}", on_release_flash)
flash_msg = self.translate("Flash")
self.make_button(
@@ -289,8 +313,8 @@ def on_release_flash(instance):
text=f"[color=#333333]{flash_msg}[/color]",
font_factor=28,
halign=None,
- on_press=getattr(MainScreen, "on_press_flash"),
- on_release=getattr(MainScreen, "on_release_flash"),
+ on_press=getattr(MainScreen, f"on_press_{wid}"),
+ on_release=getattr(MainScreen, f"on_release_{wid}"),
on_ref_press=None,
)
@@ -450,7 +474,6 @@ def on_update():
self.update_version(value)
else:
error = RuntimeError(f"Invalid value for key '{key}': {value}")
- self.error(str(error))
self.redirect_exception(exception=error)
if key == "device":
@@ -458,7 +481,6 @@ def on_update():
self.update_device(value)
else:
error = RuntimeError(f"Invalid value for key '{key}': {value}")
- self.error(str(error))
self.redirect_exception(exception=error)
if key == "flash":
diff --git a/src/app/screens/unzip_stable_screen.py b/src/app/screens/unzip_stable_screen.py
index 293d076c..52972647 100644
--- a/src/app/screens/unzip_stable_screen.py
+++ b/src/app/screens/unzip_stable_screen.py
@@ -177,6 +177,8 @@ def build_extract_to_airgap_button(self):
extract_msg = self.translate("Unziping")
extracted_msg = self.translate("Unziped")
+ wid = f"{self.id}_airgap_button"
+
def on_press(instance):
self.debug(f"Calling Button::{instance.id}::on_press")
file_path = os.path.join(rel_path, "firmware.bin")
@@ -185,6 +187,8 @@ def on_press(instance):
)
self.set_background(wid=instance.id, rgba=(0.25, 0.25, 0.25, 1))
+ setattr(UnzipStableScreen, f"on_press_{wid}", on_press)
+
def on_release(instance):
self.debug(f"Calling Button::{instance.id}::on_release")
bin_path = os.path.join(base_path, "firmware.bin")
@@ -223,10 +227,12 @@ def on_release(instance):
time.sleep(2.1)
self.set_screen(name="WarningBeforeAirgapUpdateScreen", direction="left")
+ setattr(UnzipStableScreen, f"on_release_{wid}", on_release)
+
p = os.path.join(rel_path, "firmware.bin")
self.make_button(
row=1,
- wid=f"{self.id}_airgap_button",
+ wid=wid,
root_widget=f"{self.id}_grid",
text="".join([airgap_msg, "\n", "[color=#efcc00]", p, "[/color]"]),
font_factor=42,
diff --git a/src/app/screens/warning_after_airgap_update_screen.py b/src/app/screens/warning_after_airgap_update_screen.py
index 09877e9d..62c89240 100644
--- a/src/app/screens/warning_after_airgap_update_screen.py
+++ b/src/app/screens/warning_after_airgap_update_screen.py
@@ -22,7 +22,6 @@
about_screen.py
"""
from functools import partial
-from kivy.app import App
from kivy.clock import Clock
from src.app.screens.base_screen import BaseScreen
@@ -60,7 +59,11 @@ def on_ref_press(*args):
self.set_screen(name="MainScreen", direction="right")
if args[1] == "Quit":
- App.get_running_app().stop()
+ self.quit_app()
+
+ setattr(
+ WarningAfterAirgapUpdateScreen, f"on_ref_press_{self.id}_menu", on_ref_press
+ )
self.make_image(
wid=f"{self.id}_done",
diff --git a/src/app/screens/warning_before_airgap_update_screen.py b/src/app/screens/warning_before_airgap_update_screen.py
index 559f281a..bdff28e3 100644
--- a/src/app/screens/warning_before_airgap_update_screen.py
+++ b/src/app/screens/warning_before_airgap_update_screen.py
@@ -70,12 +70,23 @@ def on_ref_press(*args):
if sys.platform == "win32":
drive_list = self.on_get_removable_drives_windows()
- screen = self.manager.get_screen("AirgapUpdateScreen")
- fn = partial(
- screen.update, name=self.name, key="drives", value=drive_list
- )
- Clock.schedule_once(fn, 0)
- self.set_screen(name="AirgapUpdateScreen", direction="right")
+ if len(drive_list) == 0:
+ exc = RuntimeError("No removable drives found")
+ self.redirect_exception(exception=exc)
+
+ else:
+ screen = self.manager.get_screen("AirgapUpdateScreen")
+ fn = partial(
+ screen.update, name=self.name, key="drives", value=drive_list
+ )
+ Clock.schedule_once(fn, 0)
+ self.set_screen(name="AirgapUpdateScreen", direction="right")
+
+ setattr(
+ WarningBeforeAirgapUpdateScreen,
+ f"on_ref_press_{self.id}_label",
+ on_ref_press,
+ )
self.make_button(
row=0,
@@ -86,7 +97,9 @@ def on_ref_press(*args):
root_widget=f"{self.id}_grid",
on_press=None,
on_release=None,
- on_ref_press=on_ref_press,
+ on_ref_press=getattr(
+ WarningBeforeAirgapUpdateScreen, f"on_ref_press_{self.id}_label"
+ ),
)
self.ids[f"{self.id}_label"].halign = "justify"
diff --git a/src/app/screens/warning_wipe_screen.py b/src/app/screens/warning_wipe_screen.py
index 7ac0c54a..165c14e6 100644
--- a/src/app/screens/warning_wipe_screen.py
+++ b/src/app/screens/warning_wipe_screen.py
@@ -68,7 +68,6 @@ def on_ref_press(*args):
)
for fn in partials:
- print(fn)
Clock.schedule_once(fn, 0)
self.set_screen(name=args[1], direction="left")
diff --git a/src/app/screens/wipe_screen.py b/src/app/screens/wipe_screen.py
index 9bffd270..dca1ab77 100644
--- a/src/app/screens/wipe_screen.py
+++ b/src/app/screens/wipe_screen.py
@@ -25,7 +25,6 @@
import traceback
from functools import partial
from kivy.clock import Clock
-from kivy.app import App
from src.utils.flasher.wiper import Wiper
from src.app.screens.base_flash_screen import BaseFlashScreen
@@ -40,6 +39,7 @@ def __init__(self, **kwargs):
self.success = False
self.progress = ""
self.device = None
+ self.fail_msg = ""
fn = partial(self.update, name=self.name, key="canvas")
Clock.schedule_once(fn, 0)
@@ -74,6 +74,11 @@ def on_data(*args, **kwargs):
if len(self.output) > 10:
del self.output[:1]
+ if "Greeting fail" in text:
+ self.fail_msg = text
+ self.wiper.ktool.kill()
+ self.wiper.ktool.checkKillExit()
+
if "SPI Flash erased." in text:
self.is_done = True
# pylint: disable=not-callable
@@ -90,18 +95,16 @@ def on_pre_enter(self, *args):
self.build_on_data()
self.build_on_done()
+ wid = f"{self.id}_info"
+
def on_ref_press(*args):
if args[1] == "Back":
self.set_screen(name="MainScreen", direction="right")
- elif args[1] == "Quit":
- App.get_running_app().stop()
+ if args[1] == "Quit":
+ self.quit_app()
- else:
- msg = f"Invalid ref: {args[1]}"
- exc = RuntimeError(msg)
- self.error(msg)
- self.redirect_exception(exception=exc)
+ setattr(WipeScreen, f"on_ref_press_{wid}", on_ref_press)
self.make_subgrid(
wid=f"{self.id}_subgrid", rows=3, root_widget=f"{self.id}_grid"
@@ -158,11 +161,34 @@ def hook(err):
err.exc_type, err.exc_value, err.exc_traceback
)
msg = "".join(trace[-2:])
+ general_msg = "".join(
+ [
+ "Ensure that you have selected the correct device ",
+ "and that your computer has successfully detected it.",
+ ]
+ )
+
self.error(msg)
- self.redirect_exception(exception=RuntimeError(f"Wipe failed: {msg}"))
+ if "StopIteration" in msg:
+ self.fail_msg = msg
+ self.fail_msg += f"\n\n{general_msg}"
+ not_conn_fail = RuntimeError(f"Wipe failed:\n{self.fail_msg}\n")
+ self.redirect_exception(exception=not_conn_fail)
+
+ elif "Cancel" in msg:
+ self.fail_msg = f"{self.fail_msg}\n\n{general_msg}"
+ greeting_fail = RuntimeError(f"Wipe failed:\n{self.fail_msg}\n")
+ self.redirect_exception(exception=greeting_fail)
+
+ else:
+ self.fail_msg = msg
+ any_fail = RuntimeError(f"Wipe failed:\n{self.fail_msg}\n")
+ self.redirect_exception(exception=any_fail)
+
+ setattr(WipeScreen, "on_except_hook", hook)
# hook what happened
- threading.excepthook = hook
+ threading.excepthook = getattr(WipeScreen, "on_except_hook")
self.thread.start()
def update(self, *args, **kwargs):
diff --git a/src/i18n/af_ZA.UTF-8.json b/src/i18n/af_ZA.UTF-8.json
index 6ec9b199..d7dba786 100644
--- a/src/i18n/af_ZA.UTF-8.json
+++ b/src/i18n/af_ZA.UTF-8.json
@@ -117,7 +117,8 @@
"PLEASE DO NOT UNPLUG YOUR DEVICE": "MOET ASSEBLIEF NIE U TOESTEL ONTKOPPEL NIE",
"DONE": "GEDOEN",
"Back": "Terug",
- "Quit": "Sluit"
+ "Quit": "Sluit",
+ "disconnect and reconnect device before flash again": "ontkoppel en koppel die toestel weer voordat jy weer flits"
},
"warning_wipe_screen": {
"You are about to initiate a FULL WIPE of this device": "U is op die punt om 'n VOLLEDIGE WIPE van hierdie toestel te begin",
diff --git a/src/i18n/de_DE.UTF-8.json b/src/i18n/de_DE.UTF-8.json
index fb553e5d..49663c1d 100644
--- a/src/i18n/de_DE.UTF-8.json
+++ b/src/i18n/de_DE.UTF-8.json
@@ -114,7 +114,8 @@
"PLEASE DO NOT UNPLUG YOUR DEVICE": "BITTE NICHT DAS GERÄT TRENNEN",
"DONE": "FERTIG",
"Back": "Zurück",
- "Quit": "Beenden"
+ "Quit": "Beenden",
+ "disconnect and reconnect device before flash again": "Gerät trennen und erneut verbinden, bevor erneut geflasht wird"
},
"warning_wipe_screen": {
"You are about to initiate a FULL WIPE of this device": "Sie sind dabei, dieses Gerät KOMPLETT ZU LÖSCHEN",
diff --git a/src/i18n/en_US.UTF-8.json b/src/i18n/en_US.UTF-8.json
index 35b9fab6..032bf853 100644
--- a/src/i18n/en_US.UTF-8.json
+++ b/src/i18n/en_US.UTF-8.json
@@ -114,7 +114,8 @@
"PLEASE DO NOT UNPLUG YOUR DEVICE": "PLEASE DO NOT UNPLUG YOUR DEVICE",
"DONE": "DONE",
"Back": "Back",
- "Quit": "Quit"
+ "Quit": "Quit",
+ "disconnect and reconnect device before flash again": "disconnect and reconnect device before flash again"
},
"warning_wipe_screen": {
"You are about to initiate a FULL WIPE of this device": "You are about to initiate a FULL WIPE of this device",
diff --git a/src/i18n/es_ES.UTF-8.json b/src/i18n/es_ES.UTF-8.json
index c30d57c3..d32777ca 100644
--- a/src/i18n/es_ES.UTF-8.json
+++ b/src/i18n/es_ES.UTF-8.json
@@ -118,7 +118,8 @@
"PLEASE DO NOT UNPLUG YOUR DEVICE": "POR FAVOR, NO DESCONECTE SU DISPOSITIVO",
"DONE": "HECHO",
"Back": "Volve",
- "Quit": "Salir"
+ "Quit": "Salir",
+ "disconnect and reconnect device before flash again": "desconecta y vuelve a conectar el dispositivo antes de volver a flashear"
},
"warning_wipe_screen": {
"You are about to initiate a FULL WIPE of this device": "Está a punto de iniciar un BORRADO COMPLETO de este dispositivo",
diff --git a/src/i18n/fr_FR.UTF-8.json b/src/i18n/fr_FR.UTF-8.json
index 8aed1130..5cb89cbb 100644
--- a/src/i18n/fr_FR.UTF-8.json
+++ b/src/i18n/fr_FR.UTF-8.json
@@ -118,7 +118,8 @@
"PLEASE DO NOT UNPLUG YOUR DEVICE": "VEUILLEZ NE PAS DÉBRANCHER VOTRE APPAREIL",
"DONE": "FAIT",
"Back": "Retour",
- "Quit": "Fermer"
+ "Quit": "Fermer",
+ "disconnect and reconnect device before flash again": "déconnectez et reconnectez l'appareil avant de flasher à nouveau"
},
"warning_wipe_screen": {
"You are about to initiate a FULL WIPE of this device": "Vous êtes sur le point de lancer un EFFACEMENT COMPLET de cet appareil",
diff --git a/src/i18n/it_IT.UTF-8.json b/src/i18n/it_IT.UTF-8.json
index 88440484..2de0b354 100644
--- a/src/i18n/it_IT.UTF-8.json
+++ b/src/i18n/it_IT.UTF-8.json
@@ -118,7 +118,8 @@
"PLEASE DO NOT UNPLUG YOUR DEVICE": "SI PREGA DI NON SCOLLEGARE IL DISPOSITIVO",
"DONE": "FATTO",
"Back": "Indietro",
- "Quit": "Dimettersi"
+ "Quit": "Dimettersi",
+ "disconnect and reconnect device before flash again": "disconnetti e riconnetti il dispositivo prima di flashare di nuovo"
},
"warning_wipe_screen": {
"You are about to initiate a FULL WIPE of this device": "Stai per avviare una CANCELLAZIONE COMPLETA di questo dispositivo",
diff --git a/src/i18n/ja_JP.UTF-8.json b/src/i18n/ja_JP.UTF-8.json
index e001f912..829aef62 100644
--- a/src/i18n/ja_JP.UTF-8.json
+++ b/src/i18n/ja_JP.UTF-8.json
@@ -118,7 +118,8 @@
"PLEASE DO NOT UNPLUG YOUR DEVICE": "デバイスの電源を切らないでください。",
"DONE": "完了",
"Back": "戻る",
- "Quit": "終了"
+ "Quit": "終了",
+ "disconnect and reconnect device before flash again": "再フラッシュする前にデバイスを切断して再接続してください"
},
"warning_wipe_screen": {
"You are about to initiate a FULL WIPE of this device": "このデバイスの完全消去を開始しようとしています。",
diff --git a/src/i18n/ko_KR.UTF-8.json b/src/i18n/ko_KR.UTF-8.json
index f51fcb80..d6659d86 100644
--- a/src/i18n/ko_KR.UTF-8.json
+++ b/src/i18n/ko_KR.UTF-8.json
@@ -118,7 +118,8 @@
"PLEASE DO NOT UNPLUG YOUR DEVICE": "장치의 플러그를 뽑지 마십시오.",
"DONE": "수행",
"Back": "뒤로",
- "Quit": "사임하다"
+ "Quit": "사임하다",
+ "disconnect and reconnect device before flash again": "다시 플래시하기 전에 장치를 연결 해제하고 다시 연결하세요"
},
"warning_wipe_screen": {
"You are about to initiate a FULL WIPE of this device": "이 장치의 전체 지우기를 시작하려고 합니다.",
diff --git a/src/i18n/nl_NL.UTF-8.json b/src/i18n/nl_NL.UTF-8.json
index c3d6e65f..3b5ce88b 100644
--- a/src/i18n/nl_NL.UTF-8.json
+++ b/src/i18n/nl_NL.UTF-8.json
@@ -116,7 +116,8 @@
"PLEASE DO NOT UNPLUG YOUR DEVICE": "KOPPEL HET APPARAAT NIET LOS",
"DONE": "KLAAR",
"Back": "Terug",
- "Quit": "Stoppen"
+ "Quit": "Stoppen",
+ "disconnect and reconnect device before flash again": "koppel het apparaat los en sluit het\nopnieuw aan voordat je opnieuw flasht"
},
"warning_wipe_screen": {
"You are about to initiate a FULL WIPE of this device": "Je staat op het punt om te starten met het VOLLEDIGE WISSEN van dit apparaat",
diff --git a/src/i18n/pt_BR.UTF-8.json b/src/i18n/pt_BR.UTF-8.json
index aebe3db9..30d9a9f8 100644
--- a/src/i18n/pt_BR.UTF-8.json
+++ b/src/i18n/pt_BR.UTF-8.json
@@ -118,7 +118,8 @@
"PLEASE DO NOT UNPLUG YOUR DEVICE": "POR FAVOR, NÃO DESCONECTE SEU DISPOSITIVO",
"DONE": "FEITO",
"Back": "Voltar",
- "Quit": "Sair"
+ "Quit": "Sair",
+ "disconnect and reconnect device before flash again": "desconecte e reconecte o dispositivo antes de realizar o flash novamente"
},
"warning_wipe_screen": {
"You are about to initiate a FULL WIPE of this device": "Você está prestes a iniciar uma limpeza completa deste dispositivo",
diff --git a/src/i18n/ru_RU.UTF-8.json b/src/i18n/ru_RU.UTF-8.json
index fe611206..78f3939e 100644
--- a/src/i18n/ru_RU.UTF-8.json
+++ b/src/i18n/ru_RU.UTF-8.json
@@ -118,7 +118,8 @@
"PLEASE DO NOT UNPLUG YOUR DEVICE": "ПОЖАЛУЙСТА, НЕ ОТКЛЮЧАЙТЕ УСТРОЙСТВО ОТ СЕТИ",
"DONE": "ДОГОВОРИЛИСЬ",
"Back": "Назад",
- "Quit": "Покидать"
+ "Quit": "Покидать",
+ "disconnect and reconnect device before flash again": "отсоедините и снова подключите\nустройство перед повторной прошивкой"
},
"warning_wipe_screen": {
"You are about to initiate a FULL WIPE of this device": "Вы собираетесь инициировать ПОЛНУЮ ОЧИСТКУ этого устройства",
diff --git a/src/i18n/zh_CN.UTF-8.json b/src/i18n/zh_CN.UTF-8.json
index 20b66ddd..527cb177 100644
--- a/src/i18n/zh_CN.UTF-8.json
+++ b/src/i18n/zh_CN.UTF-8.json
@@ -118,7 +118,8 @@
"PLEASE DO NOT UNPLUG YOUR DEVICE": "请不要拔下您的设备",
"DONE": "完成",
"Back": "返回",
- "Quit": "退出"
+ "Quit": "退出",
+ "disconnect and reconnect device before flash again": "在重新刷机之前,请断开并重新连接设备"
},
"warning_wipe_screen": {
"You are about to initiate a FULL WIPE of this device": "您的操作将会完全擦除此设备",
diff --git a/src/utils/constants/__init__.py b/src/utils/constants/__init__.py
index 4a0d4cac..4169c22d 100644
--- a/src/utils/constants/__init__.py
+++ b/src/utils/constants/__init__.py
@@ -33,6 +33,7 @@
ROOT_DIRNAME = os.path.abspath(os.path.dirname(__file__))
VALID_DEVICES_VERSIONS = {
+ "v24.11.0": ["m5stickv", "amigo", "dock", "bit", "yahboom", "cube", "wonder_mv"],
"v24.09.1": ["m5stickv", "amigo", "dock", "bit", "yahboom", "cube", "wonder_mv"],
"v24.09.0": ["m5stickv", "amigo", "dock", "bit", "yahboom", "cube", "wonder_mv"],
"v24.07.0": ["m5stickv", "amigo", "dock", "bit", "yahboom", "cube"],
@@ -62,7 +63,7 @@ def _open_pyproject() -> dict[str, Any]:
like name, version and description
"""
if sys.version_info.minor <= 10:
- # pylint: disable=import-outside-toplevel
+ # pylint: disable=import-outside-toplevel,import-error
from tomli import loads as load_toml
if sys.version_info.minor > 10:
# pylint: disable=import-outside-toplevel,import-error
diff --git a/src/utils/flasher/base_flasher.py b/src/utils/flasher/base_flasher.py
index e53c96be..fa6ebdc7 100644
--- a/src/utils/flasher/base_flasher.py
+++ b/src/utils/flasher/base_flasher.py
@@ -55,6 +55,7 @@ class BaseFlasher(Trigger):
def __init__(self):
super().__init__()
self.ktool = KTool()
+ self.stop_thread = False
@property
def firmware(self) -> str:
diff --git a/src/utils/flasher/flasher.py b/src/utils/flasher/flasher.py
index 1d6ab1f3..96507c34 100644
--- a/src/utils/flasher/flasher.py
+++ b/src/utils/flasher/flasher.py
@@ -69,11 +69,11 @@ def flash(self, callback: typing.Callable):
callback=callback,
)
+ except StopIteration as stop_exc:
+ self.ktool.__class__.log(str(stop_exc))
+
# pylint: disable=broad-exception-caught
except Exception as exc:
- self.ktool.__class__.log(f"{str(exc)} for {self.port}")
- self.ktool.__class__.log("")
-
try:
newport = next(self._available_ports_generator)
if self.is_port_working(newport.device):
@@ -93,6 +93,9 @@ def flash(self, callback: typing.Callable):
except StopIteration as stop_exc:
self.ktool.__class__.log(str(stop_exc))
+ except Exception as gen_exc:
+ self.ktool.__class__.log(str(gen_exc))
+
else:
exc = RuntimeError(f"Port {self.port} not working")
self.ktool.__class__.log(str(exc))
diff --git a/src/utils/flasher/wiper.py b/src/utils/flasher/wiper.py
index 4fd84532..f6df3c24 100644
--- a/src/utils/flasher/wiper.py
+++ b/src/utils/flasher/wiper.py
@@ -47,11 +47,11 @@ def wipe(self, device: str):
self.ktool.process()
+ except StopIteration as stop_exc:
+ self.ktool.__class__.log(str(stop_exc))
+
# pylint: disable=broad-exception-caught
except Exception as exc:
- self.ktool.__class__.log(f"{str(exc)} for {self.port}")
- self.ktool.__class__.log("")
-
try:
newport = next(self._available_ports_generator)
if self.is_port_working(newport.device):
@@ -73,6 +73,9 @@ def wipe(self, device: str):
except StopIteration as stop_exc:
self.ktool.__class__.log(str(stop_exc))
+ except Exception as gen_exc:
+ self.ktool.__class__.log(str(gen_exc))
+
else:
exc = RuntimeError(f"Port {self.port} not working")
self.ktool.__class__.log(str(exc))
diff --git a/tests/test_000_constants.py b/tests/test_000_constants.py
index de93c93a..6acb3ec8 100644
--- a/tests/test_000_constants.py
+++ b/tests/test_000_constants.py
@@ -1,14 +1,20 @@
import os
+
+# import sys
from unittest import TestCase
-from unittest.mock import mock_open, patch
+from unittest.mock import mock_open, patch, MagicMock
from src.utils.constants import _open_pyproject, get_name, get_version, get_description
-PYPROJECT_STR = """
-[tool.poetry]
+PYPROJECT_STR = """[tool.poetry]
name = "test"
version = "0.0.1"
-description = "Hello World!"
-"""
+description = \"Hello World!\""""
+
+MOCK_TOML_DATA = {
+ "tool": {
+ "poetry": {"name": "test", "version": "0.0.1", "description": "Hello World!"}
+ }
+}
class TestConstants(TestCase):
@@ -19,26 +25,19 @@ def test_open_pyproject_with_py_minor_version_10(
self, open_mock, mock_version_info
):
mock_version_info.minor = 9
-
- rootdirname = os.path.abspath(os.path.dirname(__file__))
- pyproject_filename = os.path.abspath(
- os.path.join(rootdirname, "..", "pyproject.toml")
- )
-
- data = _open_pyproject()
- open_mock.assert_called_once_with(pyproject_filename, "r", encoding="utf8")
- self.assertEqual(
- data,
- {
- "tool": {
- "poetry": {
- "name": "test",
- "version": "0.0.1",
- "description": "Hello World!",
- }
- }
- },
- )
+ mock_tomli = MagicMock()
+ mock_tomli.loads.return_value = MOCK_TOML_DATA
+
+ with patch.dict("sys.modules", {"tomli": mock_tomli}):
+ rootdirname = os.path.abspath(os.path.dirname(__file__))
+ pyproject_filename = os.path.abspath(
+ os.path.join(rootdirname, "..", "pyproject.toml")
+ )
+
+ data = _open_pyproject()
+ open_mock.assert_called_once_with(pyproject_filename, "r", encoding="utf8")
+ mock_tomli.loads.assert_called_once_with(PYPROJECT_STR.strip())
+ self.assertEqual(data, MOCK_TOML_DATA)
@patch("builtins.open", new_callable=mock_open, read_data=PYPROJECT_STR)
def test_open_pyproject(self, open_mock):
diff --git a/tests/test_025_flasher.py b/tests/test_025_flasher.py
index 7334ef02..b3319ca4 100644
--- a/tests/test_025_flasher.py
+++ b/tests/test_025_flasher.py
@@ -199,13 +199,7 @@ def test_fail_flash_after_first_greeting_fail_port_not_working(
),
]
)
- mock_ktool_log.assert_has_calls(
- [
- call("Greeting fail: mock test for mocked"),
- call(""),
- call("Port mocked_next not working"),
- ]
- )
+ mock_ktool_log.assert_has_calls([call("Port mocked_next not working")])
@patch("os.path.exists", return_value=True)
@patch("src.utils.flasher.base_flasher.list_ports", new_callable=MockListPortsGrep)
@@ -228,7 +222,9 @@ def test_fail_flash_after_first_greeting_fail_stop_iteration(
mock_process.side_effect = [mock_exception, True]
mock_next.side_effect = [MagicMock(device="mocked")]
- mock_list_ports.grep.return_value.__next__.side_effect = [StopIteration()]
+ mock_list_ports.grep.return_value.__next__.side_effect = [
+ StopIteration("mocked stop")
+ ]
callback = MagicMock()
f = Flasher()
@@ -257,6 +253,4 @@ def test_fail_flash_after_first_greeting_fail_stop_iteration(
),
]
)
- mock_ktool_log.assert_has_calls(
- [call("Greeting fail: mock test for mocked"), call("")]
- )
+ mock_ktool_log.assert_has_calls([call("mocked stop")])
diff --git a/tests/test_026_wiper.py b/tests/test_026_wiper.py
index 992e8578..23184b9e 100644
--- a/tests/test_026_wiper.py
+++ b/tests/test_026_wiper.py
@@ -141,13 +141,7 @@ def test_fail_wipe_after_first_greeting_fail_port_not_working(
]
)
mock_process.assert_called_once()
- mock_ktool_log.assert_has_calls(
- [
- call("Greeting fail: mock test for mocked"),
- call(""),
- call("Port mocked_next not working"),
- ]
- )
+ mock_ktool_log.assert_has_calls([call("Port mocked_next not working")])
@patch("src.utils.flasher.base_flasher.list_ports", new_callable=MockListPortsGrep)
@patch("src.utils.flasher.base_flasher.next")
@@ -165,7 +159,9 @@ def test_fail_wipe_after_first_greeting_fail_stop_iteration(
mock_exception = Exception("Greeting fail: mock test")
mock_process.side_effect = [mock_exception, True]
mock_next.side_effect = [MagicMock(device="mocked")]
- mock_list_ports.grep.return_value.__next__.side_effect = [StopIteration()]
+ mock_list_ports.grep.return_value.__next__.side_effect = [
+ StopIteration("mocked stop")
+ ]
f = Wiper()
f.baudrate = 1500000
@@ -180,6 +176,4 @@ def test_fail_wipe_after_first_greeting_fail_stop_iteration(
]
)
# mock_process.assert_called_once()
- mock_ktool_log.assert_has_calls(
- [call("Greeting fail: mock test for mocked"), call("")]
- )
+ mock_ktool_log.assert_has_calls([call("mocked stop")])