Prepare test harness for Orca 50 rebase
This commit is contained in:
+10
-5
@@ -6,15 +6,15 @@ project('cthulhu',
|
||||
python = import('python')
|
||||
i18n = import('i18n')
|
||||
|
||||
python_minimum_version = '3.9'
|
||||
python_minimum_version = '3.10'
|
||||
python3 = python.find_installation('python3', required: true)
|
||||
if not python3.language_version().version_compare(f'>= @python_minimum_version@')
|
||||
error(f'Python @python_minimum_version@ or newer is required.')
|
||||
endif
|
||||
|
||||
# Hard dependencies (checked via pkg-config)
|
||||
dependency('atspi-2', version: '>= 2.52.0')
|
||||
dependency('atk-bridge-2.0', version: '>= 2.26.0')
|
||||
dependency('atspi-2', version: '>= 2.56.0')
|
||||
dependency('atk-bridge-2.0', version: '>= 2.56.0')
|
||||
dependency('pygobject-3.0', version: '>= 3.18')
|
||||
|
||||
# Hard Python module dependencies
|
||||
@@ -33,14 +33,19 @@ if not pluggy_result.found()
|
||||
error('pluggy module is required')
|
||||
endif
|
||||
|
||||
dasbus_result = python.find_installation('python3', modules:['dasbus'], required: false)
|
||||
if not dasbus_result.found()
|
||||
error('dasbus is required for D-Bus remote controller interface')
|
||||
endif
|
||||
|
||||
# End users might not have the Gtk development libraries installed, making pkg-config fail.
|
||||
# Therefore, check this dependency via python.
|
||||
gtk_major_version = '3'
|
||||
gtk_minor_version = '0'
|
||||
gtk_minor_version = '24'
|
||||
gtk_command = ' '.join([
|
||||
'import gi; gi.require_version("Gtk", "3.0"); from gi.repository import Gtk;',
|
||||
'print(f"{Gtk.get_major_version()}.{Gtk.get_minor_version()}.{Gtk.get_micro_version()}");',
|
||||
f'failed = Gtk.get_major_version() != @gtk_major_version@;',
|
||||
f'failed = Gtk.get_major_version() != @gtk_major_version@ or Gtk.get_minor_version() < @gtk_minor_version@;',
|
||||
'exit(failed)'
|
||||
])
|
||||
gtk_test = run_command(python3, '-c', gtk_command, check: false)
|
||||
|
||||
+2
-1
@@ -7,12 +7,13 @@ name = "cthulhu"
|
||||
dynamic = ["version"]
|
||||
description = "Fork of the Orca screen reader"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.9"
|
||||
requires-python = ">=3.10"
|
||||
license = { text = "LGPL-2.1-or-later" }
|
||||
dependencies = [
|
||||
"pygobject>=3.18",
|
||||
"pluggy",
|
||||
"tomlkit",
|
||||
"dasbus",
|
||||
"brlapi; extra == 'braille'",
|
||||
"python-speechd; extra == 'speech'",
|
||||
"piper-tts; extra == 'piper'",
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import importlib.util
|
||||
import os
|
||||
import sys
|
||||
import types
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
os.environ["GSETTINGS_BACKEND"] = "memory"
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
SRC_ROOT = REPO_ROOT / "src"
|
||||
@@ -63,3 +67,27 @@ for generated_module in ("cthulhu_i18n", "cthulhu_platform"):
|
||||
loaded_module = _load_generated_module(generated_module)
|
||||
sys.modules[f"cthulhu.{generated_module}"] = loaded_module
|
||||
setattr(cthulhu, generated_module, loaded_module)
|
||||
|
||||
|
||||
from cthulhu_test_fixtures import test_context # noqa: E402,F401
|
||||
|
||||
|
||||
def clean_all_cthulhu_modules() -> None:
|
||||
modules_to_remove = [
|
||||
module_name
|
||||
for module_name in sys.modules
|
||||
if module_name.startswith("cthulhu.")
|
||||
and module_name not in {"cthulhu.cthulhu_i18n", "cthulhu.cthulhu_platform"}
|
||||
]
|
||||
for module_name in modules_to_remove:
|
||||
sys.modules.pop(module_name, None)
|
||||
|
||||
|
||||
def pytest_configure(config: pytest.Config) -> None:
|
||||
config.addinivalue_line("markers", "unit: marks tests as unit tests")
|
||||
|
||||
|
||||
def pytest_runtest_setup(item: pytest.Item) -> None:
|
||||
clean_all_cthulhu_modules()
|
||||
if str(SRC_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(SRC_ROOT))
|
||||
|
||||
@@ -0,0 +1,306 @@
|
||||
# Orca Test Context - Test Isolation Framework
|
||||
#
|
||||
# Copyright 2025 Igalia, S.L.
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2.1 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the
|
||||
# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
|
||||
# Boston MA 02110-1301 USA.
|
||||
|
||||
"""Test isolation framework for Cthulhu screen reader tests."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import gi
|
||||
|
||||
gi.require_version("Atspi", "2.0")
|
||||
gi.require_version("Gio", "2.0")
|
||||
from gi.repository import Atspi, Gio, GLib # pylint: disable=wrong-import-position
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
|
||||
class CthulhuTestContext:
|
||||
"""Test isolation framework for Cthulhu tests."""
|
||||
|
||||
def __init__(self, mocker: MockerFixture, monkeypatch: MonkeyPatch):
|
||||
"""Initialize the test context."""
|
||||
|
||||
self.mocker: MockerFixture = mocker
|
||||
self.monkeypatch: MonkeyPatch = monkeypatch
|
||||
self.patches: dict[str, Any] = {}
|
||||
self.mocks: dict[str, MagicMock] = {}
|
||||
|
||||
def patch(self, target: str, **kwargs) -> MagicMock:
|
||||
"""Convenience method for creating patches."""
|
||||
|
||||
return self.mocker.patch(target, **kwargs)
|
||||
|
||||
def patch_object(self, target: object, attribute: str, **kwargs) -> MagicMock:
|
||||
"""Convenience method for patching object attributes."""
|
||||
|
||||
return self.mocker.patch.object(target, attribute, **kwargs)
|
||||
|
||||
def Mock(self, **kwargs) -> MagicMock: # pylint: disable=invalid-name
|
||||
"""Convenience method for creating Mock objects."""
|
||||
|
||||
return self.mocker.Mock(**kwargs)
|
||||
|
||||
def patch_env(
|
||||
self,
|
||||
env_vars: dict[str, str],
|
||||
remove_vars: list[str] | None = None,
|
||||
) -> MagicMock | None:
|
||||
"""Convenience method for patching environment variables."""
|
||||
|
||||
if remove_vars:
|
||||
for var in remove_vars:
|
||||
if var in os.environ:
|
||||
del os.environ[var]
|
||||
|
||||
if env_vars:
|
||||
return self.mocker.patch.dict(os.environ, env_vars)
|
||||
return None
|
||||
|
||||
def patch_module(self, module_name: str, mock_module: Any) -> MagicMock:
|
||||
"""Convenience method for patching sys.modules entries."""
|
||||
|
||||
return self.mocker.patch.dict(sys.modules, {module_name: mock_module})
|
||||
|
||||
def patch_modules(self, modules: dict[str, Any]) -> MagicMock:
|
||||
"""Convenience method for patching multiple sys.modules entries."""
|
||||
|
||||
return self.mocker.patch.dict(sys.modules, modules)
|
||||
|
||||
def _setup_required_imports(self) -> None:
|
||||
"""Sets up commonly required module imports that real modules need."""
|
||||
|
||||
required_modules = [
|
||||
"cthulhu.cthulhu_i18n",
|
||||
"cthulhu.cmdnames",
|
||||
"cthulhu.input_event",
|
||||
"cthulhu.keybindings",
|
||||
"cthulhu.messages",
|
||||
"cthulhu.text_attribute_names",
|
||||
]
|
||||
|
||||
for module_name in required_modules:
|
||||
if module_name not in self.mocks:
|
||||
mock_module = self.mocker.patch(module_name, create=True)
|
||||
if module_name == "cthulhu.cthulhu_i18n":
|
||||
mock_module._ = lambda x: x
|
||||
self.mocks[module_name] = mock_module
|
||||
|
||||
def _setup_essential_modules(self, module_names: list[str]) -> dict[str, MagicMock]:
|
||||
"""Returns dictionary mapping module names to mock objects."""
|
||||
|
||||
essential_modules = {}
|
||||
for module_name in module_names:
|
||||
mock_module = self.mocker.Mock()
|
||||
self.patch_module(module_name, mock_module)
|
||||
essential_modules[module_name] = mock_module
|
||||
return essential_modules
|
||||
|
||||
def setup_shared_dependencies(
|
||||
self,
|
||||
additional_modules: list[str] | None = None,
|
||||
) -> dict[str, MagicMock]:
|
||||
"""Returns common/shared dependencies used across most Orca test modules."""
|
||||
|
||||
core_modules = [
|
||||
"cthulhu.debug",
|
||||
"cthulhu.messages",
|
||||
"cthulhu.input_event",
|
||||
"cthulhu.keybindings",
|
||||
"cthulhu.cmdnames",
|
||||
"cthulhu.ax_object",
|
||||
"cthulhu.dbus_service",
|
||||
"cthulhu.script_manager",
|
||||
"cthulhu.cthulhu_i18n",
|
||||
"cthulhu.guilabels",
|
||||
"cthulhu.text_attribute_names",
|
||||
"cthulhu.focus_manager",
|
||||
"cthulhu.braille",
|
||||
"cthulhu.cthulhu_platform",
|
||||
]
|
||||
|
||||
if additional_modules:
|
||||
core_modules.extend(additional_modules)
|
||||
|
||||
essential_modules = self._setup_essential_modules(core_modules)
|
||||
self.configure_shared_module_behaviors(essential_modules)
|
||||
return essential_modules
|
||||
|
||||
# pylint: disable-next=too-many-locals, too-many-statements, too-many-branches
|
||||
def configure_shared_module_behaviors(self, essential_modules: dict[str, MagicMock]) -> None:
|
||||
"""Configure standard behaviors for shared modules to reduce duplication."""
|
||||
|
||||
if "cthulhu.cthulhu_i18n" in essential_modules:
|
||||
i18n_mock = essential_modules["cthulhu.cthulhu_i18n"]
|
||||
i18n_mock._ = lambda x: x
|
||||
i18n_mock.C_ = lambda c, x: x
|
||||
i18n_mock.ngettext = lambda s, p, n: s if n == 1 else p
|
||||
|
||||
if "cthulhu.debug" in essential_modules:
|
||||
debug_mock = essential_modules["cthulhu.debug"]
|
||||
debug_mock.LEVEL_INFO = 800
|
||||
debug_mock.LEVEL_SEVERE = 1000
|
||||
debug_mock.print_message = self.mocker.Mock()
|
||||
debug_mock.print_tokens = self.mocker.Mock()
|
||||
debug_mock.println = self.mocker.Mock()
|
||||
|
||||
if "cthulhu.keybindings" in essential_modules:
|
||||
keybindings_mock = essential_modules["cthulhu.keybindings"]
|
||||
bindings_instance = self.mocker.Mock()
|
||||
bindings_instance.is_empty = self.mocker.Mock(return_value=True)
|
||||
bindings_instance.add = self.mocker.Mock()
|
||||
keybindings_mock.KeyBindings = self.mocker.Mock(return_value=bindings_instance)
|
||||
keybindings_mock.KeyBinding = self.mocker.Mock(return_value=self.mocker.Mock())
|
||||
keybindings_mock.DEFAULT_MODIFIER_MASK = 1
|
||||
keybindings_mock.CTHULHU_SHIFT_MODIFIER_MASK = 2
|
||||
|
||||
if "cthulhu.focus_manager" in essential_modules:
|
||||
focus_manager_mock = essential_modules["cthulhu.focus_manager"]
|
||||
manager_instance = self.mocker.Mock()
|
||||
manager_instance.get_locus_of_focus = self.mocker.Mock(return_value=None)
|
||||
manager_instance.set_locus_of_focus = self.mocker.Mock()
|
||||
manager_instance.in_say_all = self.mocker.Mock(return_value=False)
|
||||
manager_instance.is_in_preferences_window = self.mocker.Mock(return_value=False)
|
||||
manager_instance.get_active_mode_and_object_of_interest = self.mocker.Mock(
|
||||
return_value=(None, None),
|
||||
)
|
||||
focus_manager_mock.get_manager = self.mocker.Mock(return_value=manager_instance)
|
||||
focus_manager_mock.OBJECT_NAVIGATOR = "object-navigator"
|
||||
essential_modules["focus_manager_instance"] = manager_instance
|
||||
|
||||
if "cthulhu.dbus_service" in essential_modules:
|
||||
dbus_service_mock = essential_modules["cthulhu.dbus_service"]
|
||||
controller_mock = self.mocker.Mock()
|
||||
controller_mock.register_decorated_module = self.mocker.Mock()
|
||||
dbus_service_mock.get_remote_controller = self.mocker.Mock(return_value=controller_mock)
|
||||
dbus_service_mock.command = lambda func: func
|
||||
dbus_service_mock.getter = lambda func: func
|
||||
dbus_service_mock.setter = lambda func: func
|
||||
dbus_service_mock.parameterized_command = lambda func: func
|
||||
|
||||
if "cthulhu.script_manager" in essential_modules:
|
||||
script_manager_mock = essential_modules["cthulhu.script_manager"]
|
||||
manager_instance = self.mocker.Mock()
|
||||
script_instance = self.mocker.Mock()
|
||||
script_instance.present_message = self.mocker.Mock()
|
||||
script_instance.present_object = self.mocker.Mock()
|
||||
script_instance.speak_message = self.mocker.Mock()
|
||||
script_instance.update_braille = self.mocker.Mock()
|
||||
speech_gen = self.mocker.Mock()
|
||||
braille_gen = self.mocker.Mock()
|
||||
script_instance.get_speech_generator = self.mocker.Mock(return_value=speech_gen)
|
||||
script_instance.get_braille_generator = self.mocker.Mock(return_value=braille_gen)
|
||||
manager_instance.get_active_script = self.mocker.Mock(return_value=script_instance)
|
||||
manager_instance.get_script = self.mocker.Mock(return_value=script_instance)
|
||||
manager_instance.get_manager = self.mocker.Mock(return_value=manager_instance)
|
||||
script_manager_mock.get_manager = self.mocker.Mock(return_value=manager_instance)
|
||||
|
||||
if "cthulhu.ax_object" in essential_modules:
|
||||
ax_object_mock = essential_modules["cthulhu.ax_object"]
|
||||
ax_object_class_mock = self.mocker.Mock()
|
||||
ax_object_class_mock.is_valid = self.mocker.Mock(return_value=True)
|
||||
ax_object_class_mock.is_dead = self.mocker.Mock(return_value=False)
|
||||
ax_object_class_mock.get_name = self.mocker.Mock(return_value="")
|
||||
ax_object_class_mock.get_role = self.mocker.Mock(return_value=Atspi.Role.PANEL)
|
||||
ax_object_class_mock.get_parent = self.mocker.Mock(return_value=None)
|
||||
ax_object_class_mock.find_ancestor = self.mocker.Mock(return_value=None)
|
||||
ax_object_class_mock.clear_cache = self.mocker.Mock()
|
||||
ax_object_mock.AXObject = ax_object_class_mock
|
||||
|
||||
if "cthulhu.ax_utilities" in essential_modules:
|
||||
ax_utilities_mock = essential_modules["cthulhu.ax_utilities"]
|
||||
ax_utilities_class_mock = self.mocker.Mock()
|
||||
ax_utilities_class_mock.is_focused = self.mocker.Mock(return_value=True)
|
||||
ax_utilities_class_mock.is_table_cell_or_header = self.mocker.Mock(return_value=False)
|
||||
ax_utilities_class_mock.is_list_item = self.mocker.Mock(return_value=False)
|
||||
ax_utilities_class_mock.is_layout_only = self.mocker.Mock(return_value=False)
|
||||
ax_utilities_class_mock.get_status_bar = self.mocker.Mock(return_value=None)
|
||||
ax_utilities_class_mock.get_info_bar = self.mocker.Mock(return_value=None)
|
||||
ax_utilities_class_mock.is_showing = self.mocker.Mock(return_value=True)
|
||||
ax_utilities_class_mock.is_visible = self.mocker.Mock(return_value=True)
|
||||
ax_utilities_class_mock.is_sensitive = self.mocker.Mock(return_value=True)
|
||||
ax_utilities_mock.AXUtilities = ax_utilities_class_mock
|
||||
|
||||
if "cthulhu.input_event" in essential_modules:
|
||||
input_event_mock = essential_modules["cthulhu.input_event"]
|
||||
input_event_mock.KEY_PRESSED_EVENT = "key-pressed"
|
||||
input_event_mock.KEY_RELEASED_EVENT = "key-released"
|
||||
input_event_mock.MOUSE_BUTTON_CLICKED_EVENT = "mouse-clicked"
|
||||
|
||||
if "cthulhu.cthulhu_platform" in essential_modules:
|
||||
cthulhu_platform_mock = essential_modules["cthulhu.cthulhu_platform"]
|
||||
cthulhu_platform_mock.tablesdir = "/usr/share/liblouis/tables"
|
||||
|
||||
if "cthulhu.braille_presenter" in essential_modules:
|
||||
braille_presenter_mock = essential_modules["cthulhu.braille_presenter"]
|
||||
presenter_instance = self.mocker.Mock()
|
||||
presenter_instance.use_braille = self.mocker.Mock(return_value=False)
|
||||
presenter_instance.get_braille_is_enabled = self.mocker.Mock(return_value=False)
|
||||
presenter_instance.get_flash_messages_are_enabled = self.mocker.Mock(return_value=False)
|
||||
presenter_instance.get_flash_messages_are_detailed = self.mocker.Mock(
|
||||
return_value=False,
|
||||
)
|
||||
presenter_instance.get_flashtime_from_settings = self.mocker.Mock(return_value=5000)
|
||||
braille_presenter_mock.get_presenter = self.mocker.Mock(return_value=presenter_instance)
|
||||
|
||||
if "cthulhu.presentation_manager" in essential_modules:
|
||||
presentation_manager_mock = essential_modules["cthulhu.presentation_manager"]
|
||||
manager_instance = self.mocker.Mock()
|
||||
manager_instance.interrupt_presentation = self.mocker.Mock()
|
||||
manager_instance.present_message = self.mocker.Mock()
|
||||
manager_instance.speak_message = self.mocker.Mock()
|
||||
manager_instance.speak_character = self.mocker.Mock()
|
||||
manager_instance.present_braille_message = self.mocker.Mock()
|
||||
manager_instance.spell_item = self.mocker.Mock()
|
||||
manager_instance.spell_phonetically = self.mocker.Mock()
|
||||
manager_instance.play_sound = self.mocker.Mock()
|
||||
manager_instance.speak_contents = self.mocker.Mock()
|
||||
manager_instance.display_contents = self.mocker.Mock()
|
||||
presentation_manager_mock.get_manager = self.mocker.Mock(return_value=manager_instance)
|
||||
|
||||
if "gi.repository" in essential_modules:
|
||||
gi_repo_mock = essential_modules["gi.repository"]
|
||||
gi_repo_mock.Gio = Gio
|
||||
gi_repo_mock.GLib = GLib
|
||||
|
||||
def get_mock(self, name: str) -> MagicMock | None:
|
||||
"""Returns mock object if it exists, None otherwise."""
|
||||
|
||||
return self.mocks.get(name)
|
||||
|
||||
def __enter__(self) -> CthulhuTestContext: # noqa: PYI034
|
||||
"""Enter the test context."""
|
||||
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
|
||||
"""Exit the test context and clean up all patches."""
|
||||
|
||||
for patch_obj in self.patches.values():
|
||||
patch_obj.stop()
|
||||
|
||||
self.patches.clear()
|
||||
self.mocks.clear()
|
||||
@@ -0,0 +1,85 @@
|
||||
# Orca Test Fixtures - Pytest Integration
|
||||
#
|
||||
# Copyright 2025 Igalia, S.L.
|
||||
# Author: Joanmarie Diggs <jdiggs@igalia.com>
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU Lesser General Public
|
||||
# License as published by the Free Software Foundation; either
|
||||
# version 2.1 of the License, or (at your option) any later version.
|
||||
#
|
||||
# This library is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this library; if not, write to the
|
||||
# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
|
||||
# Boston MA 02110-1301 USA.
|
||||
|
||||
"""Pytest fixtures for Cthulhu screen reader tests.
|
||||
|
||||
This module provides pytest fixtures that integrate the CthulhuTestContext
|
||||
with pytest's fixture system, providing clean, isolated test environments.
|
||||
|
||||
The fixtures are designed to be simple to use while providing complete
|
||||
test isolation and preventing cross-test contamination.
|
||||
|
||||
Usage:
|
||||
def test_presenter(orca_test):
|
||||
cthulhu_test.setup_where_am_i_presenter_dependencies()
|
||||
from cthulhu.where_am_i_presenter import WhereAmIPresenter
|
||||
|
||||
presenter = WhereAmIPresenter()
|
||||
result = presenter.some_method()
|
||||
assert result is True
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import pytest
|
||||
|
||||
from cthulhu_test_context import CthulhuTestContext
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Generator
|
||||
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_context(mocker: MockerFixture, monkeypatch) -> Generator[CthulhuTestContext, None, None]:
|
||||
"""Provides clean, isolated Orca test environment.
|
||||
|
||||
This is the primary fixture for Cthulhu tests. It provides a complete
|
||||
test isolation context that prevents cross-test contamination.
|
||||
|
||||
Usage:
|
||||
class TestMyModule:
|
||||
def _setup_dependencies(self, cthulhu_test):
|
||||
# Set up only what your module needs
|
||||
return cthulhu_test.setup_shared_dependencies(["cthulhu.debug"])
|
||||
|
||||
def test_my_functionality(self, cthulhu_test):
|
||||
self._setup_dependencies(orca_test)
|
||||
from cthulhu.my_module import MyClass
|
||||
|
||||
# Your test code here
|
||||
instance = MyClass()
|
||||
result = instance.some_method()
|
||||
|
||||
assert result is True
|
||||
|
||||
Args:
|
||||
mocker: pytest-mock mocker fixture (automatically injected)
|
||||
monkeypatch: pytest monkeypatch fixture (automatically injected)
|
||||
|
||||
Yields:
|
||||
CthulhuTestContext instance with clean isolation
|
||||
"""
|
||||
|
||||
with CthulhuTestContext(mocker, monkeypatch) as context:
|
||||
yield context
|
||||
Reference in New Issue
Block a user