summaryrefslogtreecommitdiffstats
path: root/tests/units/cli/exec
diff options
context:
space:
mode:
Diffstat (limited to 'tests/units/cli/exec')
-rw-r--r--tests/units/cli/exec/__init__.py3
-rw-r--r--tests/units/cli/exec/test__init__.py30
-rw-r--r--tests/units/cli/exec/test_commands.py125
-rw-r--r--tests/units/cli/exec/test_utils.py134
4 files changed, 292 insertions, 0 deletions
diff --git a/tests/units/cli/exec/__init__.py b/tests/units/cli/exec/__init__.py
new file mode 100644
index 0000000..e772bee
--- /dev/null
+++ b/tests/units/cli/exec/__init__.py
@@ -0,0 +1,3 @@
+# Copyright (c) 2023-2024 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the LICENSE file.
diff --git a/tests/units/cli/exec/test__init__.py b/tests/units/cli/exec/test__init__.py
new file mode 100644
index 0000000..f8ad365
--- /dev/null
+++ b/tests/units/cli/exec/test__init__.py
@@ -0,0 +1,30 @@
+# Copyright (c) 2023-2024 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the LICENSE file.
+"""
+Tests for anta.cli.exec
+"""
+from __future__ import annotations
+
+from click.testing import CliRunner
+
+from anta.cli import anta
+from anta.cli.utils import ExitCode
+
+
+def test_anta_exec(click_runner: CliRunner) -> None:
+ """
+ Test anta exec
+ """
+ result = click_runner.invoke(anta, ["exec"])
+ assert result.exit_code == ExitCode.OK
+ assert "Usage: anta exec" in result.output
+
+
+def test_anta_exec_help(click_runner: CliRunner) -> None:
+ """
+ Test anta exec --help
+ """
+ result = click_runner.invoke(anta, ["exec", "--help"])
+ assert result.exit_code == ExitCode.OK
+ assert "Usage: anta exec" in result.output
diff --git a/tests/units/cli/exec/test_commands.py b/tests/units/cli/exec/test_commands.py
new file mode 100644
index 0000000..f96d7f6
--- /dev/null
+++ b/tests/units/cli/exec/test_commands.py
@@ -0,0 +1,125 @@
+# Copyright (c) 2023-2024 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the LICENSE file.
+"""
+Tests for anta.cli.exec.commands
+"""
+
+from __future__ import annotations
+
+from pathlib import Path
+from typing import TYPE_CHECKING
+
+import pytest
+
+from anta.cli import anta
+from anta.cli.exec.commands import clear_counters, collect_tech_support, snapshot
+from anta.cli.utils import ExitCode
+
+if TYPE_CHECKING:
+ from click.testing import CliRunner
+
+
+def test_clear_counters_help(click_runner: CliRunner) -> None:
+ """
+ Test `anta exec clear-counters --help`
+ """
+ result = click_runner.invoke(clear_counters, ["--help"])
+ assert result.exit_code == 0
+ assert "Usage" in result.output
+
+
+def test_snapshot_help(click_runner: CliRunner) -> None:
+ """
+ Test `anta exec snapshot --help`
+ """
+ result = click_runner.invoke(snapshot, ["--help"])
+ assert result.exit_code == 0
+ assert "Usage" in result.output
+
+
+def test_collect_tech_support_help(click_runner: CliRunner) -> None:
+ """
+ Test `anta exec collect-tech-support --help`
+ """
+ result = click_runner.invoke(collect_tech_support, ["--help"])
+ assert result.exit_code == 0
+ assert "Usage" in result.output
+
+
+@pytest.mark.parametrize(
+ "tags",
+ [
+ pytest.param(None, id="no tags"),
+ pytest.param("leaf,spine", id="with tags"),
+ ],
+)
+def test_clear_counters(click_runner: CliRunner, tags: str | None) -> None:
+ """
+ Test `anta exec clear-counters`
+ """
+ cli_args = ["exec", "clear-counters"]
+ if tags is not None:
+ cli_args.extend(["--tags", tags])
+ result = click_runner.invoke(anta, cli_args)
+ assert result.exit_code == ExitCode.OK
+
+
+COMMAND_LIST_PATH_FILE = Path(__file__).parent.parent.parent.parent / "data" / "test_snapshot_commands.yml"
+
+
+@pytest.mark.parametrize(
+ "commands_path, tags",
+ [
+ pytest.param(None, None, id="missing command list"),
+ pytest.param(Path("/I/do/not/exist"), None, id="wrong path for command_list"),
+ pytest.param(COMMAND_LIST_PATH_FILE, None, id="command-list only"),
+ pytest.param(COMMAND_LIST_PATH_FILE, "leaf,spine", id="with tags"),
+ ],
+)
+def test_snapshot(tmp_path: Path, click_runner: CliRunner, commands_path: Path | None, tags: str | None) -> None:
+ """
+ Test `anta exec snapshot`
+ """
+ cli_args = ["exec", "snapshot", "--output", str(tmp_path)]
+ # Need to mock datetetime
+ if commands_path is not None:
+ cli_args.extend(["--commands-list", str(commands_path)])
+ if tags is not None:
+ cli_args.extend(["--tags", tags])
+ result = click_runner.invoke(anta, cli_args)
+ # Failure scenarios
+ if commands_path is None:
+ assert result.exit_code == ExitCode.USAGE_ERROR
+ return
+ if not Path.exists(Path(commands_path)):
+ assert result.exit_code == ExitCode.USAGE_ERROR
+ return
+ assert result.exit_code == ExitCode.OK
+
+
+@pytest.mark.parametrize(
+ "output, latest, configure, tags",
+ [
+ pytest.param(None, None, False, None, id="no params"),
+ pytest.param("/tmp/dummy", None, False, None, id="with output"),
+ pytest.param(None, 1, False, None, id="only last show tech"),
+ pytest.param(None, None, True, None, id="configure"),
+ pytest.param(None, None, False, "leaf,spine", id="with tags"),
+ ],
+)
+def test_collect_tech_support(click_runner: CliRunner, output: str | None, latest: str | None, configure: bool | None, tags: str | None) -> None:
+ """
+ Test `anta exec collect-tech-support`
+ """
+ cli_args = ["exec", "collect-tech-support"]
+ if output is not None:
+ cli_args.extend(["--output", output])
+ if latest is not None:
+ cli_args.extend(["--latest", latest])
+ if configure is True:
+ cli_args.extend(["--configure"])
+ if tags is not None:
+ cli_args.extend(["--tags", tags])
+ result = click_runner.invoke(anta, cli_args)
+ assert result.exit_code == ExitCode.OK
diff --git a/tests/units/cli/exec/test_utils.py b/tests/units/cli/exec/test_utils.py
new file mode 100644
index 0000000..6df1c86
--- /dev/null
+++ b/tests/units/cli/exec/test_utils.py
@@ -0,0 +1,134 @@
+# Copyright (c) 2023-2024 Arista Networks, Inc.
+# Use of this source code is governed by the Apache License 2.0
+# that can be found in the LICENSE file.
+"""
+Tests for anta.cli.exec.utils
+"""
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any
+from unittest.mock import call, patch
+
+import pytest
+
+from anta.cli.exec.utils import clear_counters_utils # , collect_commands, collect_scheduled_show_tech
+from anta.device import AntaDevice
+from anta.inventory import AntaInventory
+from anta.models import AntaCommand
+
+if TYPE_CHECKING:
+ from pytest import LogCaptureFixture
+
+
+# TODO complete test cases
+@pytest.mark.asyncio
+@pytest.mark.parametrize(
+ "inventory_state, per_device_command_output, tags",
+ [
+ pytest.param(
+ {"dummy": {"is_online": False}, "dummy2": {"is_online": False}, "dummy3": {"is_online": False}},
+ {},
+ None,
+ id="no_connected_device",
+ ),
+ pytest.param(
+ {"dummy": {"is_online": True, "hw_model": "cEOSLab"}, "dummy2": {"is_online": True, "hw_model": "vEOS-lab"}, "dummy3": {"is_online": False}},
+ {},
+ None,
+ id="cEOSLab and vEOS-lab devices",
+ ),
+ pytest.param(
+ {"dummy": {"is_online": True}, "dummy2": {"is_online": True}, "dummy3": {"is_online": False}},
+ {"dummy": None}, # None means the command failed to collect
+ None,
+ id="device with error",
+ ),
+ pytest.param(
+ {"dummy": {"is_online": True}, "dummy2": {"is_online": True}, "dummy3": {"is_online": True}},
+ {},
+ ["spine"],
+ id="tags",
+ ),
+ ],
+)
+async def test_clear_counters_utils(
+ caplog: LogCaptureFixture,
+ test_inventory: AntaInventory,
+ inventory_state: dict[str, Any],
+ per_device_command_output: dict[str, Any],
+ tags: list[str] | None,
+) -> None:
+ """
+ Test anta.cli.exec.utils.clear_counters_utils
+ """
+
+ async def mock_connect_inventory() -> None:
+ """
+ mocking connect_inventory coroutine
+ """
+ for name, device in test_inventory.items():
+ device.is_online = inventory_state[name].get("is_online", True)
+ device.established = inventory_state[name].get("established", device.is_online)
+ device.hw_model = inventory_state[name].get("hw_model", "dummy")
+
+ async def dummy_collect(self: AntaDevice, command: AntaCommand) -> None:
+ """
+ mocking collect coroutine
+ """
+ command.output = per_device_command_output.get(self.name, "")
+
+ # Need to patch the child device class
+ with patch("anta.device.AsyncEOSDevice.collect", side_effect=dummy_collect, autospec=True) as mocked_collect, patch(
+ "anta.inventory.AntaInventory.connect_inventory",
+ side_effect=mock_connect_inventory,
+ ) as mocked_connect_inventory:
+ print(mocked_collect)
+ mocked_collect.side_effect = dummy_collect
+ await clear_counters_utils(test_inventory, tags=tags)
+
+ mocked_connect_inventory.assert_awaited_once()
+ devices_established = list(test_inventory.get_inventory(established_only=True, tags=tags).values())
+ if devices_established:
+ # Building the list of calls
+ calls = []
+ for device in devices_established:
+ calls.append(
+ call(
+ device,
+ **{
+ "command": AntaCommand(
+ command="clear counters",
+ version="latest",
+ revision=None,
+ ofmt="json",
+ output=per_device_command_output.get(device.name, ""),
+ errors=[],
+ )
+ },
+ )
+ )
+ if device.hw_model not in ["cEOSLab", "vEOS-lab"]:
+ calls.append(
+ call(
+ device,
+ **{
+ "command": AntaCommand(
+ command="clear hardware counter drop",
+ version="latest",
+ revision=None,
+ ofmt="json",
+ output=per_device_command_output.get(device.name, ""),
+ )
+ },
+ )
+ )
+ mocked_collect.assert_has_awaits(calls)
+ # Check error
+ for key, value in per_device_command_output.items():
+ if value is None:
+ # means some command failed to collect
+ assert "ERROR" in caplog.text
+ assert f"Could not clear counters on device {key}: []" in caplog.text
+ else:
+ mocked_collect.assert_not_awaited()