diff --git a/.config/dictionary.txt b/.config/dictionary.txt index 73dc524..eab5381 100644 --- a/.config/dictionary.txt +++ b/.config/dictionary.txt @@ -10,6 +10,7 @@ cpython delenv devel endgroup +envdir envlist envtmpdir fileh diff --git a/tests/fixtures/unit/test_plugin/galaxy.yml b/tests/fixtures/unit/test_plugin/galaxy.yml new file mode 100644 index 0000000..e69de29 diff --git a/tests/integration/test_basic.py b/tests/integration/test_basic.py index df96f84..4fb252f 100644 --- a/tests/integration/test_basic.py +++ b/tests/integration/test_basic.py @@ -3,8 +3,6 @@ from __future__ import annotations import json -import os -import runpy import subprocess from typing import TYPE_CHECKING @@ -25,15 +23,14 @@ def test_ansible_environments(module_fixture_dir: Path, tox_bin: Path) -> None: module_fixture_dir: pytest fixture to get the fixtures directory tox_bin: pytest fixture to get the tox binary """ + cmd = (tox_bin, "-l", "--ansible", "--conf", f"{module_fixture_dir}/tox-ansible.ini") try: - proc = subprocess.run( - f"{tox_bin} -l --ansible --conf {module_fixture_dir}/tox-ansible.ini", + proc = subprocess.run( # noqa: S603 + cmd, capture_output=True, cwd=str(module_fixture_dir), text=True, check=True, - shell=True, - env=os.environ, ) except subprocess.CalledProcessError as exc: print(exc.stdout) @@ -61,14 +58,13 @@ def test_gh_matrix( monkeypatch.delenv("GITHUB_ACTIONS", raising=False) monkeypatch.delenv("GITHUB_OUTPUT", raising=False) - proc = subprocess.run( - f"{tox_bin} --ansible --gh-matrix --root {module_fixture_dir} --conf tox-ansible.ini", + cmd = (tox_bin, "--ansible", "--gh-matrix", "--conf", f"{module_fixture_dir}/tox-ansible.ini") + proc = subprocess.run( # noqa: S603 + cmd, capture_output=True, cwd=str(module_fixture_dir), text=True, check=True, - shell=True, - env=os.environ, ) structured = json.loads(proc.stdout) assert isinstance(structured, list) @@ -113,14 +109,13 @@ def test_no_ansible_flag(module_fixture_dir: Path, tox_bin: Path) -> None: tox_bin: pytest fixture to get the tox binary """ - proc = subprocess.run( - f"{tox_bin} --root {module_fixture_dir} --conf tox-ansible.ini", + cmd = (tox_bin, "--root", str(module_fixture_dir), "--conf", "tox-ansible.ini") + proc = subprocess.run( # noqa: S603 + cmd, capture_output=True, cwd=str(module_fixture_dir), text=True, check=True, - shell=True, - env=os.environ, ) assert "py: OK" in proc.stdout @@ -133,15 +128,22 @@ def test_no_ansible_flag_gh(module_fixture_dir: Path, tox_bin: Path) -> None: tox_bin: pytest fixture to get the tox binary """ + cmd = ( + tox_bin, + "--gh-matrix", + "--root", + str(module_fixture_dir), + "--conf", + "tox-ansible.ini", + ) + with pytest.raises(subprocess.CalledProcessError) as exc: - subprocess.run( - f"{tox_bin} --gh-matrix --root {module_fixture_dir} --conf tox-ansible.ini", + subprocess.run( # noqa: S603 + cmd, capture_output=True, cwd=str(module_fixture_dir), text=True, check=True, - shell=True, - env=os.environ, ) assert "The --gh-matrix option requires --ansible" in exc.value.stdout @@ -157,59 +159,23 @@ def test_tox_ini_msg( tox_bin: pytest fixture to get the tox binary """ + cmd = (tox_bin, "--ansible", "--root", str(module_fixture_dir), "-e", "non-existent") with pytest.raises(subprocess.CalledProcessError) as exc: - subprocess.run( - f"{tox_bin} --ansible --root {module_fixture_dir} -e non-existent", + subprocess.run( # noqa: S603 + cmd, capture_output=True, cwd=str(module_fixture_dir), text=True, check=True, - shell=True, - env=os.environ, ) expected = "Using a default tox.ini file with tox-ansible plugin is not recommended" assert expected in exc.value.stdout -def test_tox_invalid( - module_fixture_dir: Path, - tox_bin: Path, - monkeypatch: pytest.MonkeyPatch, - capsys: pytest.CaptureFixture[str], -) -> None: - """Test an invalid environment is ignored. The plugin should not raise an error. - - Args: - module_fixture_dir: pytest fixture to get the fixtures directory - tox_bin: pytest fixture to get the tox binary - monkeypatch: pytest fixture to patch modules - capsys: pytest fixture to capture stdout/stderr - """ - monkeypatch.setattr("tox_ansible.plugin.ENV_LIST", "insanity-py3.13-devel") - - monkeypatch.setattr( - "sys.argv", - [ - str(tox_bin), - "config", - "--ansible", - "--root", - str(module_fixture_dir), - "--conf", - "tox-ansible.ini", - ], - ) - with pytest.raises(SystemExit, match="0"): - runpy.run_module("tox", run_name="__main__") - captured = capsys.readouterr() - assert "Unable to find galaxy.yml file" not in captured.out - - def test_setting_matrix_scope( module_fixture_dir: Path, tox_bin: Path, monkeypatch: pytest.MonkeyPatch, - capsys: pytest.CaptureFixture[str], ) -> None: """Test setting the matrix scope to a specific section. @@ -217,27 +183,28 @@ def test_setting_matrix_scope( module_fixture_dir: pytest fixture to get the fixtures directory tox_bin: pytest fixture to get the tox binary monkeypatch: pytest fixture to patch modules - capsys: pytest fixture to capture stdout/stderr """ monkeypatch.delenv("GITHUB_ACTIONS", raising=False) monkeypatch.delenv("GITHUB_OUTPUT", raising=False) monkeypatch.chdir(module_fixture_dir) - monkeypatch.setattr( - "sys.argv", - [ - str(tox_bin), - "--ansible", - "--gh-matrix", - "--matrix-scope", - "integration", - "--conf", - "tox-ansible.ini", - ], + + cmd = ( + tox_bin, + "--ansible", + "--gh-matrix", + "--matrix-scope", + "integration", + "--conf", + "tox-ansible.ini", ) - with pytest.raises(SystemExit, match="0"): - runpy.run_module("tox", run_name="__main__") - captured = capsys.readouterr() - structured = json.loads(captured.out) + proc = subprocess.run( # noqa: S603 + cmd, + capture_output=True, + cwd=str(module_fixture_dir), + text=True, + check=False, + ) + structured = json.loads(proc.stdout) assert isinstance(structured, list) assert all(entry["name"].startswith("integration") for entry in structured) @@ -246,7 +213,6 @@ def test_action_not_output( module_fixture_dir: Path, tox_bin: Path, monkeypatch: pytest.MonkeyPatch, - capsys: pytest.CaptureFixture[str], ) -> None: """Test for exit when action is set but not output. @@ -254,23 +220,18 @@ def test_action_not_output( module_fixture_dir: pytest fixture to get the fixtures directory tox_bin: pytest fixture to get the tox binary monkeypatch: pytest fixture to patch modules - capsys: pytest fixture to capture """ monkeypatch.setenv("GITHUB_ACTIONS", "true") monkeypatch.delenv("GITHUB_OUTPUT", raising=False) monkeypatch.chdir(module_fixture_dir) - monkeypatch.setattr( - "sys.argv", - [ - str(tox_bin), - "--ansible", - "--gh-matrix", - "--conf", - "tox-ansible.ini", - ], - ) - with pytest.raises(SystemExit, match="1"): - runpy.run_module("tox", run_name="__main__") - captured = capsys.readouterr() - assert "GITHUB_OUTPUT environment variable not set" in captured.out + cmd = (tox_bin, "--ansible", "--gh-matrix", "--conf", "tox-ansible.ini") + + proc = subprocess.run( # noqa: S603 + cmd, + capture_output=True, + cwd=str(module_fixture_dir), + text=True, + check=False, + ) + assert "GITHUB_OUTPUT environment variable not set" in proc.stdout diff --git a/tests/unit/test_plugin.py b/tests/unit/test_plugin.py index 015b8f6..86897bf 100644 --- a/tests/unit/test_plugin.py +++ b/tests/unit/test_plugin.py @@ -1,18 +1,31 @@ """Unit plugin tests.""" +import io import json import re from pathlib import Path import pytest +import yaml +from tox.config.cli.parse import Options from tox.config.cli.parser import Parsed +from tox.config.loader.memory import MemoryLoader from tox.config.main import Config from tox.config.source import discover_source from tox.config.types import EnvList +from tox.report import ToxHandler +from tox.session.state import State -from tox_ansible.plugin import conf_commands_pre, generate_gh_matrix +from tox_ansible.plugin import ( + conf_commands, + conf_commands_pre, + conf_deps, + generate_gh_matrix, + get_collection_name, + tox_add_env_config, +) def test_commands_pre(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: @@ -96,3 +109,347 @@ def test_gen_version_matrix(python: str, tmp_path: Path, monkeypatch: pytest.Mon "name": f"integration-{python}-2.15", "python": "3.13", } + + +def test_gen_version_matrix_with_nl(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """Test the version matrix generation when it contains a newline. + + Args: + tmp_path: Pytest fixture. + monkeypatch: Pytest fixture. + """ + environment_list = EnvList(envs=["integration-py3.13-2.18"]) + monkeypatch.setenv("GITHUB_ACTIONS", "true") + gh_output = tmp_path / "matrix.json" + monkeypatch.setenv("GITHUB_OUTPUT", str(gh_output)) + + json_dumps = json.dumps + + def json_dumps_mock(result: list[dict[str, str]]) -> str: + """Mock json.dumps. + + Args: + result: Result. + + Returns: + str: JSON string. + """ + return json_dumps(result, indent=4) + + monkeypatch.setattr("json.dumps", json_dumps_mock) + + generate_gh_matrix(environment_list, "all") + result = gh_output.read_text() + assert result.startswith("envlist< None: + """Test the collection name retrieval when the file is missing. + + Args: + tmp_path: Pytest fixture. + caplog: Pytest fixture for log capture + """ + with pytest.raises(SystemExit, match="1"): + get_collection_name(tmp_path / "galaxy.yml") + logs = caplog.text + assert "Unable to find galaxy.yml" in logs + + +def test_get_collection_name_broken( + tmp_path: Path, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test the collection name retrieval when the file is missing. + + Args: + tmp_path: Pytest fixture. + caplog: Pytest fixture for log capture + """ + galaxy_file = tmp_path / "galaxy.yml" + contents = {"namespace": "test", "no_name": "test"} + galaxy_file.write_text(yaml.dump(contents)) + with pytest.raises(SystemExit, match="1"): + get_collection_name(tmp_path / "galaxy.yml") + logs = caplog.text + assert "Unable to find 'name' in galaxy.yml" in logs + + +def test_get_collection_name_success( + tmp_path: Path, +) -> None: + """Test the collection name retrieval when the file is missing. + + Args: + tmp_path: Pytest fixture. + """ + galaxy_file = tmp_path / "galaxy.yml" + contents = {"namespace": "test", "name": "test"} + galaxy_file.write_text(yaml.dump(contents)) + name, namespace = get_collection_name(tmp_path / "galaxy.yml") + assert name == "test" + assert namespace == "test" + + +def test_conf_commands_unit(tmp_path: Path) -> None: + """Test the conf_commands function. + + Args: + tmp_path: Pytest fixture. + """ + ini_file = tmp_path / "tox.ini" + ini_file.touch() + source = discover_source(ini_file, None) + + conf = Config.make( + Parsed(work_dir=tmp_path, override=[], config_file=ini_file, root_dir=tmp_path), + pos_args=[], + source=source, + ).get_env("unit-py3.13-2.18") + + result = conf_commands( + env_conf=conf, + c_name="test", + c_namespace="test", + test_type="unit", + pos_args=None, + ) + assert len(result) == 1 + assert result[0] == "python3 -m pytest --ansible-unit-inject-only ./tests/unit" + + +def test_conf_commands_sanity(tmp_path: Path) -> None: + """Test the conf_commands function. + + Args: + tmp_path: Pytest fixture. + """ + ini_file = tmp_path / "tox.ini" + ini_file.touch() + source = discover_source(ini_file, None) + + conf = Config.make( + Parsed(work_dir=tmp_path, override=[], config_file=ini_file, root_dir=tmp_path), + pos_args=[], + source=source, + ).get_env("sanity-py3.13-2.18") + + conf.add_config( + keys=["env_tmp_dir", "envtmpdir"], + of_type=Path, + default=tmp_path, + desc="", + ) + + result = conf_commands( + env_conf=conf, + c_name="test", + c_namespace="test", + test_type="sanity", + pos_args=None, + ) + assert len(result) == 1 + assert "ansible-test sanity" in result[0] + + +def test_conf_commands_integration(tmp_path: Path) -> None: + """Test the conf_commands function. + + Args: + tmp_path: Pytest fixture. + """ + ini_file = tmp_path / "tox.ini" + ini_file.touch() + source = discover_source(ini_file, None) + + conf = Config.make( + Parsed(work_dir=tmp_path, override=[], config_file=ini_file, root_dir=tmp_path), + pos_args=[], + source=source, + ).get_env("integration-py3.13-2.18") + + result = conf_commands( + env_conf=conf, + c_name="test", + c_namespace="test", + test_type="integration", + pos_args=None, + ) + assert len(result) == 1 + assert result[0] == "python3 -m pytest --ansible-unit-inject-only ./tests/integration" + + +def test_conf_commands_invalid(tmp_path: Path, caplog: pytest.LogCaptureFixture) -> None: + """Test the conf_commands function. + + Args: + tmp_path: Pytest fixture. + caplog: Pytest fixture for log capture + """ + ini_file = tmp_path / "tox.ini" + ini_file.touch() + source = discover_source(ini_file, None) + + conf = Config.make( + Parsed(work_dir=tmp_path, override=[], config_file=ini_file, root_dir=tmp_path), + pos_args=[], + source=source, + ).get_env("invalid-py3.13-2.18") + + with pytest.raises(SystemExit, match="1"): + conf_commands( + env_conf=conf, + c_name="test", + c_namespace="test", + test_type="invalid", + pos_args=None, + ) + + logs = caplog.text + assert "Unknown test type" in logs + + +def test_conf_deps(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """Test the conf_commands function. + + Args: + tmp_path: Pytest fixture. + monkeypatch: Pytest fixture for patching. + """ + ini_file = tmp_path / "tox.ini" + ini_file.touch() + source = discover_source(ini_file, None) + + (tmp_path / "test-requirements.txt").write_text("test-requirement") + (tmp_path / "requirements.txt").write_text("requirement") + (tmp_path / "requirements-test.txt").write_text("requirement-test") + monkeypatch.setattr("tox_ansible.plugin.TOX_WORK_DIR", tmp_path) + + conf = Config.make( + Parsed(work_dir=tmp_path, override=[], config_file=ini_file, root_dir=tmp_path), + pos_args=[], + source=source, + ).get_env("unit-py3.13-2.18") + + result = conf_deps(env_conf=conf, test_type="unit") + assert "test-requirement" in result + assert "requirement" in result + assert "requirement-test" in result + + +def test_tox_add_env_config_valid(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """Test the tox_add_env_config function. + + Args: + tmp_path: Pytest fixture for temporary directory. + monkeypatch: Pytest fixture for patching. + """ + ini_file = tmp_path / "tox.ini" + ini_file.touch() + (tmp_path / "galaxy.yml").write_text("namespace: test\nname: test") + monkeypatch.chdir(tmp_path) + source = discover_source(ini_file, None) + parsed = Parsed( + work_dir=tmp_path, + override=[], + config_file=ini_file, + root_dir=tmp_path, + ansible=True, + ) + + env_conf = Config.make( + parsed=parsed, + pos_args=[], + source=source, + ).get_env("unit-py3.13-2.18") + + env_conf.add_config( + keys=["env_tmp_dir", "envtmpdir"], + of_type=Path, + default=tmp_path, + desc="", + ) + + env_conf.add_config( + keys=["env_dir", "envdir"], + of_type=Path, + default=tmp_path, + desc="", + ) + + output = io.BytesIO() + wrapper = io.TextIOWrapper( + buffer=output, + encoding="utf-8", + line_buffering=True, + ) + + state = State( + options=Options( + parsed=parsed, + pos_args="", + source=source, + cmd_handlers={}, + log_handler=ToxHandler(level=0, is_colored=False, out_err=(wrapper, wrapper)), + ), + args=[], + ) + + tox_add_env_config(env_conf, state) + + assert isinstance(env_conf.loaders[0], MemoryLoader) + assert ( + env_conf.loaders[0].raw["description"] + == "Unit tests using ansible-core 2.18 and python 3.13" + ) + + +def test_tox_add_env_config_invalid(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """Test the tox_add_env_config function. + + Args: + tmp_path: Pytest fixture for temporary directory. + monkeypatch: Pytest fixture for patching. + """ + ini_file = tmp_path / "tox.ini" + ini_file.touch() + (tmp_path / "galaxy.yml").write_text("namespace: test\nname: test") + monkeypatch.chdir(tmp_path) + source = discover_source(ini_file, None) + parsed = Parsed( + work_dir=tmp_path, + override=[], + config_file=ini_file, + root_dir=tmp_path, + ansible=True, + ) + + env_conf = Config.make( + parsed=parsed, + pos_args=[], + source=source, + ).get_env("insanity-py3.13-2.18") + + output = io.BytesIO() + wrapper = io.TextIOWrapper( + buffer=output, + encoding="utf-8", + line_buffering=True, + ) + + state = State( + options=Options( + parsed=parsed, + pos_args="", + source=source, + cmd_handlers={}, + log_handler=ToxHandler(level=0, is_colored=False, out_err=(wrapper, wrapper)), + ), + args=[], + ) + + tox_add_env_config(env_conf, state) + assert not env_conf.loaders