diff --git a/pyproject.toml b/pyproject.toml index 17ea371c7..c35e973d7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,6 +70,7 @@ python_version = "3.10" # strict = true color_output = true error_summary = true +# check_untyped_defs = true # disallow_untyped_calls = true # disallow_untyped_defs = true disallow_any_generics = true diff --git a/src/ansible_navigator/actions/_actions.py b/src/ansible_navigator/actions/_actions.py index 89ab32c5e..766a49455 100644 --- a/src/ansible_navigator/actions/_actions.py +++ b/src/ansible_navigator/actions/_actions.py @@ -129,7 +129,7 @@ def names_factory(package: str) -> Callable[..., Any]: return functools.partial(names, package) -def run_interactive(package: str, action: str, *args: Any, **_kwargs: Any) -> Any: +def run_interactive(package: str, action: str, *args: Any, **_kwargs: dict[str, Any]) -> Any: """Call the given action's ``run()`` method. :param package: The name of the package @@ -172,7 +172,7 @@ def run_interactive_factory(package: str) -> Callable[..., Any]: return functools.partial(run_interactive, package) -def run_stdout(package: str, action: str, *args: Any, **_kwargs: Any) -> RunStdoutReturn: +def run_stdout(package: str, action: str, *args: Any, **_kwargs: dict[str, Any]) -> RunStdoutReturn: """Call the given action's ``run_stdout()`` method. :param package: The name of the package diff --git a/src/ansible_navigator/actions/inventory.py b/src/ansible_navigator/actions/inventory.py index 51151434f..54883af31 100644 --- a/src/ansible_navigator/actions/inventory.py +++ b/src/ansible_navigator/actions/inventory.py @@ -173,7 +173,7 @@ def _set_inventories_mtime(self) -> None: else: self._inventories_mtime = None - def update(self): + def update(self) -> None: """Request calling app update, inventory update checked in ``run()``.""" self._calling_app.update() diff --git a/src/ansible_navigator/actions/run.py b/src/ansible_navigator/actions/run.py index 4a528b50d..01a1c8247 100644 --- a/src/ansible_navigator/actions/run.py +++ b/src/ansible_navigator/actions/run.py @@ -230,7 +230,7 @@ def __init__(self, args: ApplicationConfiguration): """Task name storage from playbook_on_start using the task uuid as the key""" @property - def mode(self): + def mode(self) -> str: """Determine the mode and if playbook artifact creation is enabled. If so, run in interactive mode, but print stdout. diff --git a/src/ansible_navigator/command_runner/command_runner.py b/src/ansible_navigator/command_runner/command_runner.py index db9746667..5a009f02d 100644 --- a/src/ansible_navigator/command_runner/command_runner.py +++ b/src/ansible_navigator/command_runner/command_runner.py @@ -94,10 +94,10 @@ def worker( class CommandRunner: """Functionality for running commands.""" - def __init__(self): + def __init__(self) -> None: """Initialize the command runner.""" - self._completed_queue: Queue | None = None - self._pending_queue: Queue | None = None + self._completed_queue: Queue[Any] | None = None + self._pending_queue: Queue[Command | None] | None = None @staticmethod def run_single_process(commands: list[Command]): @@ -134,7 +134,7 @@ def run_multi_process(self, commands: list[Command]) -> list[Command]: results.append(self._completed_queue.get()) return results - def start_workers(self, jobs): + def start_workers(self, jobs: list[Command]) -> None: """Start the workers. :param jobs: List of commands to be run @@ -148,6 +148,8 @@ def start_workers(self, jobs): ) processes.append(proc) proc.start() + if not self._pending_queue: + raise RuntimeError for job in jobs: self._pending_queue.put(job) for _proc in range(worker_count): diff --git a/src/ansible_navigator/data/image_introspect.py b/src/ansible_navigator/data/image_introspect.py index 293f80e78..43b17ab28 100644 --- a/src/ansible_navigator/data/image_introspect.py +++ b/src/ansible_navigator/data/image_introspect.py @@ -75,12 +75,12 @@ class CommandRunner: Run commands using single or multiple processes. """ - def __init__(self): + def __init__(self) -> None: """Initialize the command runner.""" self._completed_queue: Queue[Any] | None = None self._pending_queue: Queue[Any] | None = None - def run_multi_thread(self, command_classes): + def run_multi_thread(self, command_classes: list[CmdParser]) -> list[CmdParser]: """Run commands with multiple threads. Workers are started to read from pending queue. @@ -94,17 +94,16 @@ def run_multi_thread(self, command_classes): self._completed_queue = Queue() if self._pending_queue is None: self._pending_queue = Queue() - results = {} all_commands = tuple( command for command_class in command_classes for command in command_class.commands ) self.start_workers(all_commands) - results = [] + results: list[CmdParser] = [] while len(results) != len(all_commands): results.append(self._completed_queue.get()) return results - def start_workers(self, jobs): + def start_workers(self, jobs: tuple[Command, ...]) -> None: """Start workers and submit jobs to pending queue. :param jobs: The jobs to be run @@ -118,6 +117,8 @@ def start_workers(self, jobs): ) processes.append(proc) proc.start() + if not self._pending_queue: + raise RuntimeError for job in jobs: self._pending_queue.put(job) for _proc in range(worker_count): @@ -129,6 +130,11 @@ def start_workers(self, jobs): class CmdParser: """A base class for command parsers with common parsing functions.""" + @property + def commands(self) -> list[Command]: + """List of commands to be executed.""" + return [] + @staticmethod def _strip(value: str) -> str: """Remove quotes, leading and trailing whitespace. @@ -387,7 +393,7 @@ def main(serialize: bool = True) -> dict[str, JSONTypes] | None: response["environment_variables"] = {"details": dict(os.environ)} try: command_runner = CommandRunner() - commands = [ + commands: list[CmdParser] = [ AnsibleCollections(), AnsibleVersion(), OsRelease(), diff --git a/src/ansible_navigator/diagnostics.py b/src/ansible_navigator/diagnostics.py index 06eeaecfd..d29205382 100644 --- a/src/ansible_navigator/diagnostics.py +++ b/src/ansible_navigator/diagnostics.py @@ -44,7 +44,7 @@ class Collector: name: str - def start(self, color: bool): + def start(self, color: bool) -> None: """Output start information to the console. :param color: Whether to color the message @@ -94,20 +94,20 @@ class Diagnostics: settings_file: dict[str, JSONTypes] -def register(collector: Collector): +def register(collector: Collector) -> Callable[..., Any]: """Register a collector. :param collector: The collector to register :returns: The decorator """ - def decorator(func): + def decorator(func: Callable[..., Any]) -> Callable[..., Any]: """Add the dunder collector to the func. :param func: The function to decorate :returns: The decorated function """ - func.__collector__ = collector + func.__collector__ = collector # type: ignore[attr-defined] return func return decorator @@ -128,14 +128,14 @@ def __init__(self, errors: dict[str, JSONTypes]) -> None: self.errors = errors -def diagnostic_runner(func) -> Callable[..., Any]: +def diagnostic_runner(func: Callable[..., Any]) -> Callable[..., Any]: """Wrap and run a collector. :param func: The function to wrap :returns: The decorator """ - def wrapper(*args, **kwargs): + def wrapper(*args: Any, **kwargs: dict[str, Any]) -> Callable[..., Any]: """Wrap and run the collector. :param args: The positional arguments @@ -145,7 +145,7 @@ def wrapper(*args, **kwargs): global DIAGNOSTIC_FAILURES start = datetime.now(timezone.utc) color = args[0].color - collector = func.__collector__ + collector = func.__collector__ # type: ignore[attr-defined] collector.start(color=color) try: result = func(*args, **kwargs) diff --git a/src/ansible_navigator/ui_framework/form.py b/src/ansible_navigator/ui_framework/form.py index 5375a835f..e50e571ef 100644 --- a/src/ansible_navigator/ui_framework/form.py +++ b/src/ansible_navigator/ui_framework/form.py @@ -92,7 +92,7 @@ class FormPresenter(CursesWindow): """Present the form to the user.""" # pylint: disable=too-many-instance-attributes - def __init__(self, form, screen, ui_config): + def __init__(self, form, screen, ui_config) -> None: """Initialize the form presenter. :param form: The form to present to the user diff --git a/src/ansible_navigator/utils/version_migration/settings_file.py b/src/ansible_navigator/utils/version_migration/settings_file.py index d72e777f7..6d7d50f7b 100644 --- a/src/ansible_navigator/utils/version_migration/settings_file.py +++ b/src/ansible_navigator/utils/version_migration/settings_file.py @@ -21,7 +21,7 @@ class SettingsFile(Migration): name = "Settings file migration base class" - def __init__(self): + def __init__(self) -> None: """Initialize the settings file migration.""" super().__init__() self.content: dict[Any, Any] = {} diff --git a/tests/conftest.py b/tests/conftest.py index b17a81cde..7e32db12e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -321,7 +321,7 @@ def pytest_sessionstart(session: pytest.Session): USER_ENVIRONMENT = {} -def pytest_configure(config: pytest.Config): +def pytest_configure(config: pytest.Config) -> None: """Attempt to save a contributor some troubleshooting. :param config: The pytest config object @@ -359,7 +359,7 @@ def pytest_configure(config: pytest.Config): pytest.exit("Please install tmux before testing.") -def pytest_unconfigure(config: pytest.Config): +def pytest_unconfigure(config: pytest.Config) -> None: """Restore the environment variables that start with ANSIBLE_. :param config: The pytest config object diff --git a/tests/fixtures/common/collections/ansible_collections/company_name/coll_1/plugins/filter/coll_1.py b/tests/fixtures/common/collections/ansible_collections/company_name/coll_1/plugins/filter/coll_1.py index 5800ad003..a802472b7 100644 --- a/tests/fixtures/common/collections/ansible_collections/company_name/coll_1/plugins/filter/coll_1.py +++ b/tests/fixtures/common/collections/ansible_collections/company_name/coll_1/plugins/filter/coll_1.py @@ -1,7 +1,9 @@ """An ansible test filter plugin.""" +from typing import Any -def filter_1(): + +def filter_1() -> None: """Convert strings to Candlepin labels.""" return @@ -10,7 +12,7 @@ def filter_1(): class FilterModule: """Coll_1 filter.""" - def filters(self): + def filters(self) -> dict[str, Any]: """Convert an arbitrary string to a valid Candlepin label. :returns: converted Candlepin label diff --git a/tests/integration/actions/builder/base.py b/tests/integration/actions/builder/base.py index aace5281f..b25234fec 100644 --- a/tests/integration/actions/builder/base.py +++ b/tests/integration/actions/builder/base.py @@ -41,7 +41,7 @@ def fixture_tmux_session(self, request): with TmuxSession(**params) as tmux_session: yield tmux_session - def test(self, request, tmux_session, step): + def test(self, request: pytest.FixtureRequest, tmux_session: TmuxSession, step): """Run the tests for ``builder``, mode and ``ee`` set in child class. :param request: A fixture providing details about the test caller diff --git a/tests/integration/actions/config/base.py b/tests/integration/actions/config/base.py index 028146e45..84f3b23e3 100644 --- a/tests/integration/actions/config/base.py +++ b/tests/integration/actions/config/base.py @@ -62,7 +62,7 @@ def fixture_tmux_session(self, request): with TmuxSession(**params) as tmux_session: yield tmux_session - def test(self, request, tmux_session, step): + def test(self, request: pytest.FixtureRequest, tmux_session: TmuxSession, step): # pylint: disable=too-many-locals """Run the tests for ``config``, mode and ``ee`` set in child class. diff --git a/tests/integration/actions/doc/base.py b/tests/integration/actions/doc/base.py index 9f9e9bed5..fd38bccad 100644 --- a/tests/integration/actions/doc/base.py +++ b/tests/integration/actions/doc/base.py @@ -42,13 +42,13 @@ def fixture_tmux_doc_session(request): def test( self, - request, - tmux_doc_session, - index, - user_input, - comment, - testname, - expected_in_output, + request: pytest.FixtureRequest, + tmux_doc_session: TmuxSession, + index: int, + user_input: str, + comment: str, + testname: str, + expected_in_output: list[str] | None, ): # pylint: disable=too-many-arguments # pylint: disable=too-many-locals diff --git a/tests/integration/actions/images/base.py b/tests/integration/actions/images/base.py index 406abc74d..72cf18185 100644 --- a/tests/integration/actions/images/base.py +++ b/tests/integration/actions/images/base.py @@ -74,7 +74,7 @@ def fixture_tmux_session(request): with TmuxSession(**params) as tmux_session: yield tmux_session - def test(self, request, tmux_session, step): + def test(self, request: pytest.FixtureRequest, tmux_session: TmuxSession, step): """Run the tests for images, mode and ``ee`` set in child class. :param request: A fixture providing details about the test caller diff --git a/tests/integration/actions/inventory/base.py b/tests/integration/actions/inventory/base.py index dae93a367..4e3a7c894 100644 --- a/tests/integration/actions/inventory/base.py +++ b/tests/integration/actions/inventory/base.py @@ -77,7 +77,7 @@ def fixture_tmux_session(request): with TmuxSession(**params) as tmux_session: yield tmux_session - def test(self, request, tmux_session, step): + def test(self, request: pytest.FixtureRequest, tmux_session: TmuxSession, step): """Run the tests for inventory, mode and ``ee`` set in child class. :param request: A fixture providing details about the test caller diff --git a/tests/integration/actions/replay/base.py b/tests/integration/actions/replay/base.py index 1b05ef77f..35f133c74 100644 --- a/tests/integration/actions/replay/base.py +++ b/tests/integration/actions/replay/base.py @@ -41,7 +41,15 @@ def fixture_tmux_session(request): with TmuxSession(**params) as tmux_session: yield tmux_session - def test(self, request, tmux_session, index, user_input, comment, search_within_response): + def test( + self, + request: pytest.FixtureRequest, + tmux_session: TmuxSession, + index, + user_input, + comment, + search_within_response, + ): # pylint: disable=too-many-arguments """Run the tests for replay, mode and ``ee`` set in child class. diff --git a/tests/integration/actions/run/base.py b/tests/integration/actions/run/base.py index ddc7a6212..46acb9387 100644 --- a/tests/integration/actions/run/base.py +++ b/tests/integration/actions/run/base.py @@ -66,7 +66,7 @@ def fixture_tmux_session(request): with TmuxSession(**params) as tmux_session: yield tmux_session - def test(self, request, tmux_session, step): + def test(self, request: pytest.FixtureRequest, tmux_session: TmuxSession, step): """Run the tests for run, mode and ``ee`` set in child class. :param request: A fixture providing details about the test caller diff --git a/tests/integration/actions/settings/base.py b/tests/integration/actions/settings/base.py index e01bf9a06..73efa9bd9 100644 --- a/tests/integration/actions/settings/base.py +++ b/tests/integration/actions/settings/base.py @@ -63,7 +63,7 @@ def fixture_tmux_session(self, request): with TmuxSession(**params) as tmux_session: yield tmux_session - def test(self, request, tmux_session, step): + def test(self, request: pytest.FixtureRequest, tmux_session: TmuxSession, step): # pylint: disable=too-many-locals """Run the tests for ``settings``, mode and ``ee`` set in child class. diff --git a/tests/integration/actions/stdout/base.py b/tests/integration/actions/stdout/base.py index 48e9e5f88..7500a56ca 100644 --- a/tests/integration/actions/stdout/base.py +++ b/tests/integration/actions/stdout/base.py @@ -41,7 +41,15 @@ def fixture_tmux_session(request): with TmuxSession(**params) as tmux_session: yield tmux_session - def test(self, request, tmux_session, index, user_input, comment, search_within_response): + def test( + self, + request: pytest.FixtureRequest, + tmux_session: TmuxSession, + index, + user_input, + comment, + search_within_response, + ): # pylint:disable=too-many-arguments """Run the tests for stdout, mode and EE set in child class. diff --git a/tests/integration/actions/templar/base.py b/tests/integration/actions/templar/base.py index 721ceb79d..9656bf1f7 100644 --- a/tests/integration/actions/templar/base.py +++ b/tests/integration/actions/templar/base.py @@ -83,7 +83,7 @@ def fixture_tmux_session(request): with TmuxSession(**params) as tmux_session: yield tmux_session - def test(self, request, tmux_session, step): + def test(self, request: pytest.FixtureRequest, tmux_session: TmuxSession, step): """Test interactive and ``stdout`` mode ``config``. :param request: A fixture providing details about the test caller diff --git a/tests/integration/test_execution_environment.py b/tests/integration/test_execution_environment.py index 07bdcc993..bbab558d6 100644 --- a/tests/integration/test_execution_environment.py +++ b/tests/integration/test_execution_environment.py @@ -64,7 +64,7 @@ def run_test( cli_entry: str, config_fixture: str, expected: dict[str, str], - ): + ) -> None: # pylint: disable=too-many-arguments """Confirm execution of ``cli.main()`` produces the desired results. diff --git a/tests/integration/test_execution_environment_image.py b/tests/integration/test_execution_environment_image.py index e24243020..62dbc067d 100644 --- a/tests/integration/test_execution_environment_image.py +++ b/tests/integration/test_execution_environment_image.py @@ -76,7 +76,7 @@ def run_test( cli_entry: str, config_fixture: str, expected: dict[str, str], - ): + ) -> None: # pylint: disable=too-many-arguments """Confirm execution of ``cli.main()`` produces the desired results. diff --git a/tests/integration/test_pass_environment_variable.py b/tests/integration/test_pass_environment_variable.py index ae8296ae7..2ff7a9764 100644 --- a/tests/integration/test_pass_environment_variable.py +++ b/tests/integration/test_pass_environment_variable.py @@ -80,7 +80,7 @@ def run_test( cli_entry: str, config_fixture: str, expected: dict[str, str], - ): + ) -> None: # pylint: disable=too-many-arguments """Confirm execution of ``cli.main()`` produces the desired results. diff --git a/tests/integration/test_set_environment_variable.py b/tests/integration/test_set_environment_variable.py index c3e1edd4c..3224f435d 100644 --- a/tests/integration/test_set_environment_variable.py +++ b/tests/integration/test_set_environment_variable.py @@ -80,7 +80,7 @@ def run_test( cli_entry: str, config_fixture: str, expected: dict[str, str], - ): + ) -> None: # pylint: disable=too-many-arguments """Confirm execution of ``cli.main()`` produces the desired results. diff --git a/tests/unit/actions/collections/test_collection_doc_cache_path.py b/tests/unit/actions/collections/test_collection_doc_cache_path.py index 660aaedbd..b7d519c8d 100644 --- a/tests/unit/actions/collections/test_collection_doc_cache_path.py +++ b/tests/unit/actions/collections/test_collection_doc_cache_path.py @@ -7,6 +7,8 @@ import pytest +from pytest_mock import MockerFixture + from ansible_navigator import cli @@ -18,32 +20,23 @@ class TstData(NamedTuple): DOC_CACHE_PATHS = ( - TstData(description="cwd", path="./cache.db"), - TstData(description="tmp dir", path="../cache.db"), + pytest.param(TstData(description="cwd", path="./cache.db"), id="cwd"), + pytest.param(TstData(description="tmp dir", path="../cache.db"), id="tmp-dir"), ) -def _id_description(value): - """Generate id for a test. - - :param value: Test identifier - :returns: Test ID descriptor - """ - return value.description - - class DuplicateMountError(RuntimeError): """An exception specific to the duplicate mount test for collections.""" -@pytest.mark.parametrize("doc_cache_path", DOC_CACHE_PATHS, ids=_id_description) +@pytest.mark.parametrize("doc_cache_path", DOC_CACHE_PATHS) @pytest.mark.usefixtures("patch_curses") def test_for_duplicates_sources( - doc_cache_path, + doc_cache_path: TstData, monkeypatch: pytest.MonkeyPatch, tmp_path: Path, - mocker, -): + mocker: MockerFixture, +) -> None: """Ensure duplicate volume mounts are not passed to runner. :param doc_cache_path: The test data diff --git a/tests/unit/actions/run/test_artifact.py b/tests/unit/actions/run/test_artifact.py index e42635b63..1a5b4bcaf 100644 --- a/tests/unit/actions/run/test_artifact.py +++ b/tests/unit/actions/run/test_artifact.py @@ -9,6 +9,7 @@ from copy import deepcopy from dataclasses import dataclass from re import Pattern +from typing import Any import pytest @@ -22,7 +23,7 @@ from tests.defaults import id_func -def make_dirs(*_args, **_kwargs): +def make_dirs(*_args: Any, **_kwargs: dict[str, Any]) -> bool: """Mock make_dirs. :param _args: The positional arguments @@ -32,7 +33,7 @@ def make_dirs(*_args, **_kwargs): return True -def get_status(*_args, **_kwargs): +def get_status(*_args: Any, **_kwargs: dict[str, Any]) -> tuple[str, int]: """Mock run.get_status. :param _args: The positional arguments @@ -58,7 +59,7 @@ class Scenario(BaseScenario): playbook_artifact_enable: bool = True time_zone: str = "UTC" - def __post_init__(self): + def __post_init__(self) -> None: """Ensure one match is set. :raises ValueError: When neither is set @@ -67,7 +68,7 @@ def __post_init__(self): msg = "re_match or starts_with required" raise ValueError(msg) - def __str__(self): + def __str__(self) -> str: """Provide the test id. :returns: The test id @@ -162,7 +163,7 @@ def test_artifact_path( mocker: MockerFixture, caplog: pytest.LogCaptureFixture, data: Scenario, -): +) -> None: """Test the building of the artifact filename given a filename or playbook. :param monkeypatch: The monkeypatch fixture @@ -214,7 +215,7 @@ def test_artifact_path( mocked_write.assert_not_called() -def test_artifact_contents(monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture): +def test_artifact_contents(monkeypatch: pytest.MonkeyPatch, mocker: MockerFixture) -> None: """Test the artifact contents for settings information. :param monkeypatch: The monkeypatch fixture diff --git a/tests/unit/actions/run/test_runner_async.py b/tests/unit/actions/run/test_runner_async.py index d68bfc808..b777fbf56 100644 --- a/tests/unit/actions/run/test_runner_async.py +++ b/tests/unit/actions/run/test_runner_async.py @@ -43,7 +43,7 @@ class Scenario(BaseScenario): write_job_events: bool expected: dict[str, Any] - def __str__(self): + def __str__(self) -> str: """Provide the test id. :returns: The test id @@ -104,7 +104,7 @@ def __str__(self): @pytest.mark.parametrize("data", test_data, ids=id_func) -def test_runner_args(mocker: MockerFixture, data: Scenario): +def test_runner_args(mocker: MockerFixture, data: Scenario) -> None: """Test the arguments passed to runner API. :param mocker: The mocker fixture diff --git a/tests/unit/actions/settings/test_settings.py b/tests/unit/actions/settings/test_settings.py index 6bfa5967e..f47dd1952 100644 --- a/tests/unit/actions/settings/test_settings.py +++ b/tests/unit/actions/settings/test_settings.py @@ -31,7 +31,7 @@ class ColorMenuTestEntry(BaseScenario): default: bool """Is the current value equal to the default""" - def __str__(self): + def __str__(self) -> str: """Provide a string representation. :returns: The string representation of self @@ -56,7 +56,7 @@ def __str__(self): @pytest.mark.parametrize(argnames="data", argvalues=ColorMenuTestEntries, ids=id_func) -def test_color_menu(data: ColorMenuTestEntry): +def test_color_menu(data: ColorMenuTestEntry) -> None: """Test color menu for a val set to the default. :param data: A test entry @@ -91,7 +91,7 @@ class ContentHeadingEntry(BaseScenario): """The content""" @property - def heading(self): + def heading(self) -> str: """Create the expected heading for this content. :returns: The expected heading @@ -99,7 +99,7 @@ def heading(self): heading = CONTENT_HEADING_DEFAULT if self.content.default else CONTENT_HEADING_NOT_DEFAULT return heading.format(**asdict(self.content)) - def __str__(self): + def __str__(self) -> str: """Provide a string representation. :returns: The string representation of self @@ -150,7 +150,7 @@ def __str__(self): @pytest.mark.parametrize(argnames="data", argvalues=ContentHeadingEntries, ids=id_func) -def test_content_heading(data: ContentHeadingEntry): +def test_content_heading(data: ContentHeadingEntry) -> None: """Test menu generation. :param data: The test data diff --git a/tests/unit/actions/test_config.py b/tests/unit/actions/test_config.py index 4d42b0231..91ff561bd 100644 --- a/tests/unit/actions/test_config.py +++ b/tests/unit/actions/test_config.py @@ -8,19 +8,19 @@ from ansible_navigator.ui_framework.curses_defs import CursesLinePart -def test_config_color_menu_true(): +def test_config_color_menu_true() -> None: """Test color menu for a val set to the default.""" entry = {"default": True} assert color_menu(0, "", entry) == (2, 0) -def test_config_color_menu_false(): +def test_config_color_menu_false() -> None: """Test color menu for a val not set to default.""" entry = {"default": False} assert color_menu(0, "", entry) == (3, 0) -def test_config_content_heading_true(): +def test_config_content_heading_true() -> None: """Test menu generation for a defaulted value.""" curses.initscr() curses.start_color() @@ -33,6 +33,7 @@ def test_config_content_heading_true(): "name": "Test option", } heading = content_heading(obj, line_length) + assert heading assert len(heading) == 1 assert len(heading[0]) == 1 assert isinstance(heading[0][0], CursesLinePart) diff --git a/tests/unit/actions/test_exec.py b/tests/unit/actions/test_exec.py index 05c5c95e7..1d7694625 100644 --- a/tests/unit/actions/test_exec.py +++ b/tests/unit/actions/test_exec.py @@ -21,66 +21,69 @@ class CommandTestData(NamedTuple): result_params: list[str] -def id_from_data(test_value): - """Return the name from the test data object. - - :param test_value: The value from which the test id will be extracted - :returns: The test id - """ - return f" {test_value.name} " - - command_test_data = [ - CommandTestData( - name="With shell simple", - command="echo foo", - use_shell=True, - default_exec_command=False, - result_command="/bin/bash", - result_params=["-c", "echo foo"], + pytest.param( + CommandTestData( + name="With shell simple", + command="echo foo", + use_shell=True, + default_exec_command=False, + result_command="/bin/bash", + result_params=["-c", "echo foo"], + ), + id="0", ), - CommandTestData( - name="Without shell simple", - command="echo foo", - use_shell=False, - default_exec_command=False, - result_command="echo", - result_params=["foo"], + pytest.param( + CommandTestData( + name="Without shell simple", + command="echo foo", + use_shell=False, + default_exec_command=False, + result_command="echo", + result_params=["foo"], + ), + id="1", ), - CommandTestData( - name="With shell complex", - command="ansible-vault encrypt_string --vault-password-file" - " a_password_file 'foobar' --name 'the_secret'", - use_shell=True, - default_exec_command=False, - result_command="/bin/bash", - result_params=[ - "-c", - "ansible-vault encrypt_string --vault-password-file" + pytest.param( + CommandTestData( + name="With shell complex", + command="ansible-vault encrypt_string --vault-password-file" " a_password_file 'foobar' --name 'the_secret'", - ], + use_shell=True, + default_exec_command=False, + result_command="/bin/bash", + result_params=[ + "-c", + "ansible-vault encrypt_string --vault-password-file" + " a_password_file 'foobar' --name 'the_secret'", + ], + ), + id="2", ), - CommandTestData( - name="Without shell complex", - command="ansible-vault encrypt_string --vault-password-file" - " a_password_file 'foobar' --name 'the secret'", - use_shell=False, - default_exec_command=False, - result_command="ansible-vault", - result_params=[ - "encrypt_string", - "--vault-password-file", - "a_password_file", - "foobar", - "--name", - "the secret", - ], + pytest.param( + CommandTestData( + name="Without shell complex", + command="ansible-vault encrypt_string --vault-password-file" + " a_password_file 'foobar' --name 'the secret'", + use_shell=False, + default_exec_command=False, + result_command="ansible-vault", + result_params=[ + "encrypt_string", + "--vault-password-file", + "a_password_file", + "foobar", + "--name", + "the secret", + ], + ), + id="3", ), ] -@pytest.mark.parametrize("cmd_test_data", command_test_data, ids=id_from_data) -def test_command_generation(cmd_test_data: CommandTestData): +@pytest.mark.parametrize("cmd_test_data", command_test_data) +def test_command_generation(cmd_test_data: CommandTestData) -> None: """Test the generation of the command and params. :param cmd_test_data: The test data diff --git a/tests/unit/actions/test_inventory.py b/tests/unit/actions/test_inventory.py index 017dc1d44..cd50ca480 100644 --- a/tests/unit/actions/test_inventory.py +++ b/tests/unit/actions/test_inventory.py @@ -8,7 +8,7 @@ from ansible_navigator.ui_framework.curses_defs import CursesLinePart -def test_color_menu_true(): +def test_color_menu_true() -> None: """Test color menu for a val set to the default.""" assert color_menu(0, "__name", {}) == (10, 0) assert color_menu(0, "__taxonomy", {}) == (11, 0) @@ -18,7 +18,7 @@ def test_color_menu_true(): assert color_menu(0, "", {"__name": True}) == (14, 0) -def test_content_heading_true(): +def test_content_heading_true() -> None: """Test menu generation for a defaulted value.""" curses.initscr() curses.start_color() @@ -33,6 +33,7 @@ def test_content_heading_true(): "ansible_platform": ansible_platform, } heading = content_heading(obj, line_length) + assert heading is not None assert len(heading) == 1 assert len(heading[0]) == 1 assert isinstance(heading[0][0], CursesLinePart) diff --git a/tests/unit/configuration_subsystem/test_internals.py b/tests/unit/configuration_subsystem/test_internals.py index 18a2fb562..7090b66eb 100644 --- a/tests/unit/configuration_subsystem/test_internals.py +++ b/tests/unit/configuration_subsystem/test_internals.py @@ -4,6 +4,8 @@ from copy import deepcopy +import pytest + from ansible_navigator.configuration_subsystem import Constants from ansible_navigator.configuration_subsystem import NavigatorConfiguration from ansible_navigator.initialization import parse_and_update @@ -11,7 +13,7 @@ from .defaults import TEST_FIXTURE_DIR -def test_settings_file_path_file_none(): +def test_settings_file_path_file_none() -> None: """Confirm a settings file path is not stored in the internals when not present.""" args = deepcopy(NavigatorConfiguration) args.internals.initializing = True @@ -20,7 +22,7 @@ def test_settings_file_path_file_none(): assert args.internals.settings_source == Constants.NONE -def test_settings_file_path_file_system(monkeypatch): +def test_settings_file_path_file_system(monkeypatch: pytest.MonkeyPatch) -> None: """Confirm a settings file path is stored in the internals when searched. :param monkeypatch: Fixture providing these helper methods for safely patching and mocking @@ -32,7 +34,7 @@ def test_settings_file_path_file_system(monkeypatch): args.internals.initializing = True args.application_version = "test" - def getcwd(): + def getcwd() -> str: return TEST_FIXTURE_DIR monkeypatch.setattr(os, "getcwd", getcwd) @@ -41,7 +43,7 @@ def getcwd(): assert args.internals.settings_source == Constants.SEARCH_PATH -def test_settings_file_path_environment_variable(monkeypatch): +def test_settings_file_path_environment_variable(monkeypatch: pytest.MonkeyPatch) -> None: """Confirm a settings file path is stored in the internals when set via environment variable. :param monkeypatch: Fixture providing these helper methods for safely patching and mocking diff --git a/tests/unit/configuration_subsystem/test_settings_effective.py b/tests/unit/configuration_subsystem/test_settings_effective.py index f8b8e653b..b19829c87 100644 --- a/tests/unit/configuration_subsystem/test_settings_effective.py +++ b/tests/unit/configuration_subsystem/test_settings_effective.py @@ -22,7 +22,7 @@ from ansible_navigator.utils.json_schema import validate -def test_settings_defaults(schema_dict: SettingsSchemaType): +def test_settings_defaults(schema_dict: SettingsSchemaType) -> None: """Check the settings file used as a sample against the schema. :param schema_dict: The json schema as a dictionary @@ -38,7 +38,7 @@ def test_settings_defaults(schema_dict: SettingsSchemaType): def test_settings_env_var_to_full( settings_env_var_to_full: tuple[Path, SettingsFileType], -): +) -> None: """Confirm the fixture writes the file and environment variable. :param settings_env_var_to_full: The env var and file writing fixture @@ -55,7 +55,7 @@ def test_settings_env_var_to_full( assert settings.internals.settings_source == Constants.ENVIRONMENT_VARIABLE -def test_settings_cli(): +def test_settings_cli() -> None: """Test the round trip generation of effective settings given some cli parameters.""" settings = deepcopy(NavigatorConfiguration) settings.internals.initializing = True @@ -66,6 +66,7 @@ def test_settings_cli(): effective = to_effective(settings) root = effective["ansible-navigator"] + assert isinstance(root, dict) assert root["app"] == "images" assert root["logging"]["append"] is False assert root["logging"]["level"] == "debug" diff --git a/tests/unit/configuration_subsystem/test_settings_sources.py b/tests/unit/configuration_subsystem/test_settings_sources.py index c22f211ce..4ad4fd9a0 100644 --- a/tests/unit/configuration_subsystem/test_settings_sources.py +++ b/tests/unit/configuration_subsystem/test_settings_sources.py @@ -1,6 +1,7 @@ """Test the ability to produce a dictionary of effective sources.""" from copy import deepcopy +from pathlib import Path import pytest @@ -8,10 +9,11 @@ from ansible_navigator.configuration_subsystem import Constants as C from ansible_navigator.configuration_subsystem import NavigatorConfiguration from ansible_navigator.configuration_subsystem import to_sources +from ansible_navigator.configuration_subsystem.definitions import SettingsFileType from ansible_navigator.initialization import parse_and_update -def test_defaults(): +def test_defaults() -> None: """Check the settings file used as a sample against the schema.""" settings = deepcopy(NavigatorConfiguration) settings.internals.initializing = True @@ -25,7 +27,7 @@ def test_defaults(): ) -def test_cli(): +def test_cli() -> None: """Test the source of effective settings given some cli parameters.""" settings = deepcopy(NavigatorConfiguration) settings.internals.initializing = True @@ -44,7 +46,7 @@ def test_cli(): ) -def test_env(monkeypatch: pytest.MonkeyPatch): +def test_env(monkeypatch: pytest.MonkeyPatch) -> None: """Test the source of effective settings given some environment variables. :param monkeypatch: The pytest monkeypatch fixture @@ -71,7 +73,7 @@ def test_env(monkeypatch: pytest.MonkeyPatch): ) -def test_full(settings_env_var_to_full): +def test_full(settings_env_var_to_full: tuple[Path, SettingsFileType]) -> None: """Test the source of effective settings given a full config. :param settings_env_var_to_full: The pytest fixture to provide a full config diff --git a/tests/unit/image_manager/test_image_puller.py b/tests/unit/image_manager/test_image_puller.py index 1ee96773e..49a55aaf0 100644 --- a/tests/unit/image_manager/test_image_puller.py +++ b/tests/unit/image_manager/test_image_puller.py @@ -20,26 +20,19 @@ class TstPullPolicy(NamedTuple): pull_required: bool -def id_from_data(value): - """Return the name from the test data object. - - :param value: Test object - :returns: Test object name - """ - return f" {value.pull_policy} " - - # Note, these tests assume our default is not a :latest data_do_have = [ - TstPullPolicy(pull_policy="always", pull_required=True), - TstPullPolicy(pull_policy="missing", pull_required=False), - TstPullPolicy(pull_policy="never", pull_required=False), - TstPullPolicy(pull_policy="tag", pull_required=False), + pytest.param(TstPullPolicy(pull_policy="always", pull_required=True), id="always"), + pytest.param(TstPullPolicy(pull_policy="missing", pull_required=False), id="missing"), + pytest.param(TstPullPolicy(pull_policy="never", pull_required=False), id="never"), + pytest.param(TstPullPolicy(pull_policy="tag", pull_required=False), id="tag"), ] -@pytest.mark.parametrize("data", data_do_have, ids=id_from_data) -def test_do_have(valid_container_engine: str, default_ee_image_name: str, data: TstPullPolicy): +@pytest.mark.parametrize("data", data_do_have) +def test_do_have( + valid_container_engine: str, default_ee_image_name: str, data: TstPullPolicy +) -> None: """Test using an image local. :param valid_container_engine: Container engine identifier @@ -58,14 +51,14 @@ def test_do_have(valid_container_engine: str, default_ee_image_name: str, data: # Note, these tests assume the image is a :latest data_do_have_but_latest = [ - TstPullPolicy(pull_policy="always", pull_required=True), - TstPullPolicy(pull_policy="missing", pull_required=False), - TstPullPolicy(pull_policy="never", pull_required=False), - TstPullPolicy(pull_policy="tag", pull_required=True), + pytest.param(TstPullPolicy(pull_policy="always", pull_required=True), id="always"), + pytest.param(TstPullPolicy(pull_policy="missing", pull_required=False), id="missing"), + pytest.param(TstPullPolicy(pull_policy="never", pull_required=False), id="never"), + pytest.param(TstPullPolicy(pull_policy="tag", pull_required=True), id="tag"), ] -@pytest.mark.parametrize("data", data_do_have_but_latest, ids=id_from_data) +@pytest.mark.parametrize("data", data_do_have_but_latest) def test_do_have_but_latest( valid_container_engine: str, small_image_name: str, data: TstPullPolicy ): @@ -86,15 +79,15 @@ def test_do_have_but_latest( data_missing_locally = [ - TstPullPolicy(pull_policy="always", pull_required=True), - TstPullPolicy(pull_policy="missing", pull_required=True), - TstPullPolicy(pull_policy="never", pull_required=False), - TstPullPolicy(pull_policy="tag", pull_required=True), + pytest.param(TstPullPolicy(pull_policy="always", pull_required=True), id="always"), + pytest.param(TstPullPolicy(pull_policy="missing", pull_required=True), id="missing"), + pytest.param(TstPullPolicy(pull_policy="never", pull_required=False), id="never"), + pytest.param(TstPullPolicy(pull_policy="tag", pull_required=True), id="tag"), ] -@pytest.mark.parametrize("data", data_missing_locally, ids=id_from_data) -def test_missing_locally(valid_container_engine, data): +@pytest.mark.parametrize("data", data_missing_locally) +def test_missing_locally(valid_container_engine: str, data: TstPullPolicy) -> None: """Test using an image not local. :param valid_container_engine: Container engine identifier diff --git a/tests/unit/logger/test_append.py b/tests/unit/logger/test_append.py index 8e7b243b4..9cbb9856d 100644 --- a/tests/unit/logger/test_append.py +++ b/tests/unit/logger/test_append.py @@ -4,6 +4,7 @@ from dataclasses import dataclass from pathlib import Path +from typing import Any import pytest @@ -21,7 +22,7 @@ class Scenario(BaseScenario): repeat: int = 5 session_count: int = 1 - def __str__(self): + def __str__(self) -> str: """Provide the test id. :returns: The test id @@ -53,7 +54,7 @@ def args(self, log_file: Path) -> list[str]: @pytest.mark.parametrize("data", test_data, ids=id_func) -def test_log_append(data: Scenario, monkeypatch: pytest.MonkeyPatch, tmp_path: Path): +def test_log_append(data: Scenario, monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: """Start with the CLI, create log messages and count. :param data: The test data @@ -61,7 +62,7 @@ def test_log_append(data: Scenario, monkeypatch: pytest.MonkeyPatch, tmp_path: P :param tmp_path: A temporary file path """ - def return_none(*_args, **_kwargs) -> None: + def return_none(*_args: Any, **_kwargs: dict[str, Any]) -> None: """Take no action, return none. :param _args: Arguments diff --git a/tests/unit/logger/test_time_zone.py b/tests/unit/logger/test_time_zone.py index c2052bed0..82977add7 100644 --- a/tests/unit/logger/test_time_zone.py +++ b/tests/unit/logger/test_time_zone.py @@ -7,12 +7,12 @@ from dataclasses import dataclass from pathlib import Path from re import Pattern +from typing import Any import pytest from ansible_navigator import cli from tests.defaults import BaseScenario -from tests.defaults import id_func @dataclass @@ -24,7 +24,7 @@ class Scenario(BaseScenario): time_zone: str | None = None will_exit: bool = False - def __str__(self): + def __str__(self) -> str: """Provide the test id. :returns: The test id @@ -44,24 +44,38 @@ def args(self, log_file: Path) -> list[str]: test_data = ( - Scenario(name="0", re_match=re.compile(r"^.*\+00:00$")), - Scenario(name="1", re_match=re.compile(r"^.*-0[78]:00$"), time_zone="America/Los_Angeles"), - Scenario(name="2", re_match=re.compile(r"^.*\+09:00$"), time_zone="Japan"), - Scenario(name="3", re_match=re.compile(r"^.*[+-][01][0-9]:[0-5][0-9]$"), time_zone="local"), - Scenario( - name="4", re_match=re.compile(r"^.*\+00:00$"), time_zone="does_not_exist", will_exit=True + pytest.param(Scenario(name="0", re_match=re.compile(r"^.*\+00:00$")), id="0"), + pytest.param( + Scenario(name="1", re_match=re.compile(r"^.*-0[78]:00$"), time_zone="America/Los_Angeles"), + id="1", + ), + pytest.param( + Scenario(name="2", re_match=re.compile(r"^.*\+09:00$"), time_zone="Japan"), id="2" + ), + pytest.param( + Scenario(name="3", re_match=re.compile(r"^.*[+-][01][0-9]:[0-5][0-9]$"), time_zone="local"), + id="3", + ), + pytest.param( + Scenario( + name="4", + re_match=re.compile(r"^.*\+00:00$"), + time_zone="does_not_exist", + will_exit=True, + ), + id="4", ), ) @pytest.mark.flaky(reruns=2) -@pytest.mark.parametrize("data", test_data, ids=id_func) +@pytest.mark.parametrize("data", test_data) def test_tz_support( data: Scenario, caplog: pytest.LogCaptureFixture, monkeypatch: pytest.MonkeyPatch, tmp_path: Path, -): +) -> None: """Start with the CLI, create log messages and match the time zone. :param caplog: The log capture fixture @@ -70,7 +84,7 @@ def test_tz_support( :param tmp_path: A temporary file path """ - def return_none(*_args, **_kwargs) -> None: + def return_none(*_args: Any, **_kwargs: dict[str, Any]) -> None: """Take no action, return none. :param _args: Arguments diff --git a/tests/unit/test_cli.py b/tests/unit/test_cli.py index bfdfbdf47..761f3ce06 100644 --- a/tests/unit/test_cli.py +++ b/tests/unit/test_cli.py @@ -4,6 +4,8 @@ from copy import deepcopy from pathlib import Path +from typing import Any +from typing import Literal from typing import NamedTuple # pylint: disable=preferred-module # FIXME: remove once migrated per GH-872 @@ -104,7 +106,22 @@ ), ) @patch("shutil.which", return_value="/path/to/container_engine") -def test_update_args_general(_mf1, monkeypatch, given, argname, expected): +def test_update_args_general( + _mf1: Any, + monkeypatch: pytest.MonkeyPatch, + given: list[str], + argname: Literal[ + "plugin_type", + "plugin_name", + "execution_environment_image", + "log_level", + "editor_command", + "inventory", + "playbook", + "cmdline", + ], + expected: list[str], +) -> None: """Test the parse and update function. :param monkeypatch: The monkeypatch fixture @@ -123,7 +140,7 @@ def test_update_args_general(_mf1, monkeypatch, given, argname, expected): @patch("shutil.which", return_value="/path/to/container_engine") -def test_editor_command_default(_mf1, monkeypatch): +def test_editor_command_default(_mf1: Any, monkeypatch: pytest.MonkeyPatch) -> None: """Test editor with default. :param monkeypatch: The monkeypatch fixture @@ -140,18 +157,6 @@ def test_editor_command_default(_mf1, monkeypatch): assert args.editor_command == "vi +{line_number} {filename}" -def id_for_hint_test(value): - """Generate an id for the hint test. - - The spaces here help with zsh - https://github.com/microsoft/vscode-python/issues/10398 - - :param value: Test identifier - :returns: Test descriptor - """ - return f" {value.command} " - - class TstHint(NamedTuple): """Obj for hint test data.""" @@ -194,7 +199,12 @@ class TstHint(NamedTuple): @pytest.mark.parametrize("data", tst_hint_data) -def test_hints(monkeypatch, locked_directory, valid_container_engine, data): +def test_hints( + monkeypatch: pytest.MonkeyPatch, + locked_directory: str, + valid_container_engine: str, + data: TstHint, +) -> None: """Test the hints don't generate a traceback. :param monkeypatch: The monkeypatch fixture @@ -214,13 +224,13 @@ def test_hints(monkeypatch, locked_directory, valid_container_engine, data): if data.set_ce: params += ["--ce", valid_container_engine] - _messages, exit_msgs = parse_and_update(params=params, args=args) + _messages, exit_msgs_obj = parse_and_update(params=params, args=args) expected = f"{data.prefix} {data.expected}" - exit_msgs = [exit_msg.message for exit_msg in exit_msgs] + exit_msgs = [exit_msg.message for exit_msg in exit_msgs_obj] assert any(expected in exit_msg for exit_msg in exit_msgs), (expected, exit_msgs) -def test_no_term(monkeypatch): +def test_no_term(monkeypatch: pytest.MonkeyPatch) -> None: """Test for err and hint w/o TERM. :param monkeypatch: The monkeypatch fixture @@ -228,9 +238,9 @@ def test_no_term(monkeypatch): monkeypatch.delenv("TERM") args = deepcopy(NavigatorConfiguration) args.internals.initializing = True - params = [] - _messages, exit_msgs = parse_and_update(params=params, args=args) - exit_msgs = [exit_msg.message for exit_msg in exit_msgs] + params: list[str] = [] + _messages, exit_msgs_obj = parse_and_update(params=params, args=args) + exit_msgs = [exit_msg.message for exit_msg in exit_msgs_obj] expected = "TERM environment variable must be set" assert any(expected in exit_msg for exit_msg in exit_msgs), (expected, exit_msgs) expected = "Try again after setting the TERM environment variable" @@ -241,7 +251,7 @@ def test_for_version_logged( monkeypatch: pytest.MonkeyPatch, caplog: pytest.LogCaptureFixture, tmp_path: Path, -): +) -> None: """Ensure the version is captured in the log. :param monkeypatch: The monkey patch fixture diff --git a/tests/unit/test_image_introspection.py b/tests/unit/test_image_introspection.py index 8351420ad..fbf25ffdc 100644 --- a/tests/unit/test_image_introspection.py +++ b/tests/unit/test_image_introspection.py @@ -1,6 +1,10 @@ # cspell:ignore buildvm """Unit tests for image introspection.""" import importlib +import types + +from importlib.machinery import ModuleSpec +from typing import Any import pytest @@ -48,7 +52,7 @@ @pytest.fixture(scope="module", name="imported_ii") -def image_introspection(): +def image_introspection() -> types.ModuleType: """Import the image introspection script using the share directory. :returns: Image introspect module @@ -57,12 +61,15 @@ def image_introspection(): cache_dir = generate_cache_path(app_name=APP_NAME) full_path = f"{cache_dir}/image_introspect.py" spec = importlib.util.spec_from_file_location("module", full_path) + assert isinstance(spec, ModuleSpec) module = importlib.util.module_from_spec(spec) + assert isinstance(module, types.ModuleType) + assert spec.loader is not None spec.loader.exec_module(module) return module -def test_system_packages_parse_one(imported_ii): +def test_system_packages_parse_one(imported_ii: Any) -> None: """Test parsing one package. :param imported_ii: Image introspection @@ -79,7 +86,7 @@ def test_system_packages_parse_one(imported_ii): assert "version: version_string" in command.details[0]["description"] -def test_system_packages_parse_many(imported_ii): +def test_system_packages_parse_many(imported_ii: Any) -> None: """Test parsing many packages. :param imported_ii: Image introspection