diff --git a/src/ragger/conftest/base_conftest.py b/src/ragger/conftest/base_conftest.py index 7acee6f8..8d8ae67c 100644 --- a/src/ragger/conftest/base_conftest.py +++ b/src/ragger/conftest/base_conftest.py @@ -6,6 +6,7 @@ from pathlib import Path from typing import Generator, List, Optional from unittest.mock import MagicMock +import warnings from ragger.backend import BackendInterface, SpeculosBackend, LedgerCommBackend, LedgerWalletBackend from ragger.firmware import Firmware @@ -33,8 +34,13 @@ def pytest_addoption(parser): parser.addoption("--golden_run", action="store_true", default=False, - help="Do not compare the snapshots during testing, but instead save the live " - "ones. Will only work with 'speculos' as the backend") + help="Force saving the live snapshots. " + "Will only work with 'speculos' as the backend") + parser.addoption("--rm", + action="store_true", + default=False, + help="Force to remove the already existing snapshots before running test. " + "Only valid when `golden_run` is provided") parser.addoption("--pki_prod", action="store_true", default=False, @@ -77,6 +83,20 @@ def golden_run(pytestconfig): return pytestconfig.getoption("golden_run") +@pytest.fixture(scope="session") +def rm_snap(pytestconfig): + if not pytestconfig.getoption("rm"): + return False + # If rm_snap is True, it means we want to remove existing snapshots + # before taking new ones. This is only relevant if golden_run is True. + if not pytestconfig.getoption("golden_run"): + # warnings.warn("--rm option is ignored when golden_run is not set.") + warnings.warn_explicit("--rm option is ignored when golden_run is not set.", UserWarning, + __file__, 0) + return False + return True + + @pytest.fixture(scope=conf.OPTIONAL.BACKEND_SCOPE) def log_apdu_file(request, pytestconfig, full_test_name: str): filename = pytestconfig.getoption("log_apdu_file") @@ -360,15 +380,15 @@ def backend(skip_tests_for_unsupported_devices, root_pytest_dir: Path, backend_n @pytest.fixture(scope=conf.OPTIONAL.BACKEND_SCOPE) -def navigator(backend: BackendInterface, device: Device, golden_run: bool, display: bool, - navigation: bool): +def navigator(backend: BackendInterface, device: Device, golden_run: bool, rm_snap: bool, + display: bool, navigation: bool): if not navigation: return MagicMock() if device.is_nano: - return NanoNavigator(backend, device, golden_run) + return NanoNavigator(backend, device, golden_run, rm_snap) else: - return TouchNavigator(backend, device, golden_run) + return TouchNavigator(backend, device, golden_run, rm_snap) @pytest.fixture(scope="function") diff --git a/src/ragger/navigator/nano_navigator.py b/src/ragger/navigator/nano_navigator.py index 605282d1..5983b076 100644 --- a/src/ragger/navigator/nano_navigator.py +++ b/src/ragger/navigator/nano_navigator.py @@ -23,7 +23,11 @@ class NanoNavigator(Navigator): - def __init__(self, backend: BackendInterface, device: Device, golden_run: bool = False): + def __init__(self, + backend: BackendInterface, + device: Device, + golden_run: bool = False, + rm_snap: bool = False): if device.touchable: raise ValueError(f"'{self.__class__.__name__}' does not work on touchable devices") callbacks: Dict[BaseNavInsID, Callable] = { @@ -36,4 +40,4 @@ def __init__(self, backend: BackendInterface, device: Device, golden_run: bool = NavInsID.LEFT_CLICK: backend.left_click, NavInsID.BOTH_CLICK: backend.both_click } - super().__init__(backend, device, callbacks, golden_run) + super().__init__(backend, device, callbacks, golden_run, rm_snap) diff --git a/src/ragger/navigator/navigator.py b/src/ragger/navigator/navigator.py index f841e175..c235b81f 100644 --- a/src/ragger/navigator/navigator.py +++ b/src/ragger/navigator/navigator.py @@ -39,7 +39,8 @@ def __init__(self, backend: BackendInterface, device: Device, callbacks: Dict[BaseNavInsID, Callable], - golden_run: bool = False): + golden_run: bool = False, + rm_snap: bool = False): """Initializes the Backend :param backend: Which Backend will be managed @@ -50,11 +51,14 @@ def __init__(self, :type callbacks: Device :param golden_run: Allows to generate golden snapshots :type golden_run: bool + :param rm_snap: Starts by erasing to existing golden snapshots + :type rm_snap: bool """ self._backend = backend self._device = device self._callbacks = callbacks self._golden_run = golden_run + self._rm_snap = rm_snap def _get_snaps_dir_path(self, path: Path, test_case_name: Union[Path, str], is_golden: bool) -> Path: @@ -72,6 +76,12 @@ def _check_snaps_dir_path(self, path: Path, test_case_name: Union[Path, str], dir_path.mkdir(parents=True) else: raise ValueError(f"Golden snapshots directory ({dir_path}) does not exist.") + elif self._rm_snap and isinstance(self._backend, SpeculosBackend): + # Remove all files in the directory if rm_snap is True + self._backend.logger.info(f"Removing snapshot from {dir_path.name}") + for file in dir_path.iterdir(): + if file.is_file(): + file.unlink() return dir_path def _init_snaps_temp_dir(self, diff --git a/src/ragger/navigator/touch_navigator.py b/src/ragger/navigator/touch_navigator.py index c0f4a20c..a99de90d 100644 --- a/src/ragger/navigator/touch_navigator.py +++ b/src/ragger/navigator/touch_navigator.py @@ -25,7 +25,11 @@ class TouchNavigator(Navigator): - def __init__(self, backend: BackendInterface, device: Device, golden_run: bool = False): + def __init__(self, + backend: BackendInterface, + device: Device, + golden_run: bool = False, + rm_snap: bool = False): if not device.touchable: raise ValueError(f"'{self.__class__.__name__}' only works with touchable devices") screen = FullScreen(backend, device) @@ -85,4 +89,4 @@ def __init__(self, backend: BackendInterface, device: Device, golden_run: bool = NavInsID.USE_CASE_ADDRESS_CONFIRMATION_CONFIRM: screen.address_confirmation.confirm, NavInsID.USE_CASE_ADDRESS_CONFIRMATION_CANCEL: screen.address_confirmation.cancel, } - super().__init__(backend, device, callbacks, golden_run) + super().__init__(backend, device, callbacks, golden_run, rm_snap)