From 442c3754f8141d047b7d4c53f3aae02e10e79bb5 Mon Sep 17 00:00:00 2001 From: "Bradley A. Thornton" Date: Mon, 5 Aug 2024 15:27:25 -0700 Subject: [PATCH] Add some tests for installer --- .../subcommands/installer.py | 4 +- tests/unit/test_installer.py | 314 +++++++++++++++++- 2 files changed, 311 insertions(+), 7 deletions(-) diff --git a/src/ansible_dev_environment/subcommands/installer.py b/src/ansible_dev_environment/subcommands/installer.py index 82a7438..5c0c146 100644 --- a/src/ansible_dev_environment/subcommands/installer.py +++ b/src/ansible_dev_environment/subcommands/installer.py @@ -50,7 +50,9 @@ def __init__(self: Installer, config: Config, output: Output) -> None: def run(self: Installer) -> None: """Run the installer.""" - if self._config.args.collection_specifier and "," in self._config.args.collection_specifier: + if self._config.args.collection_specifier and any( + "," in s for s in self._config.args.collection_specifier + ): err = "Multiple optional dependencies are not supported at this time." self._output.critical(err) diff --git a/tests/unit/test_installer.py b/tests/unit/test_installer.py index d37ade7..902ff9e 100644 --- a/tests/unit/test_installer.py +++ b/tests/unit/test_installer.py @@ -4,9 +4,11 @@ from argparse import Namespace from pathlib import Path +from typing import Any import pytest +from ansible_dev_environment.arg_parser import parse from ansible_dev_environment.config import Config from ansible_dev_environment.output import Output from ansible_dev_environment.subcommands.installer import Installer @@ -158,16 +160,16 @@ def test_copy_using_ls(tmp_path: Path, output: Output) -> None: def test_no_adt_install( - tmpdir: Path, + tmp_path: Path, output: Output, ) -> None: """Test only core is installed. Args: - tmpdir: A temporary directory. + tmp_path: A temporary directory. output: The output fixture. """ - venv = tmpdir / "test_venv" + venv = tmp_path / "test_venv" args = Namespace( venv=venv, verbose=0, @@ -189,16 +191,16 @@ def test_no_adt_install( def test_adt_install( - tmpdir: Path, + tmp_path: Path, output: Output, ) -> None: """Test adt is installed. Args: - tmpdir: A temporary directory. + tmp_path: A temporary directory. output: The output fixture. """ - venv = tmpdir / "test_venv" + venv = tmp_path / "test_venv" args = Namespace( venv=venv, verbose=0, @@ -217,3 +219,303 @@ def test_adt_install( assert venv.exists() assert (venv / "bin" / "ansible").exists() assert (venv / "bin" / "adt").exists() + + +def test_multiple_specifiers( + tmp_path: Path, + output: Output, + monkeypatch: pytest.MonkeyPatch, + capsys: pytest.CaptureFixture[str], +) -> None: + """Test more than one collection specifier. + + Args: + tmp_path: A temporary directory. + output: The output fixture. + monkeypatch: The monkeypatch fixture. + capsys: The capsys fixture. + """ + command = ["ade", "install", "ansible.utils[dev,test]", "--venv", str(tmp_path / "venv")] + monkeypatch.setattr("sys.argv", command) + args = parse() + installer = Installer( + output=output, + config=Config(args=args, output=output, term_features=output.term_features), + ) + with pytest.raises(SystemExit): + installer.run() + captured = capsys.readouterr() + assert "Multiple optional dependencies are not supported at this time" in captured.err + + +def test_editable_not_local( + tmp_path: Path, + output: Output, + monkeypatch: pytest.MonkeyPatch, + capsys: pytest.CaptureFixture[str], +) -> None: + """Test editable with a non-local collection. + + Args: + tmp_path: A temporary directory. + output: The output fixture. + monkeypatch: The monkeypatch fixture. + capsys: The capsys fixture. + """ + command = ["ade", "install", "-e", "ansible.utils", "--venv", str(tmp_path / "venv")] + monkeypatch.setattr("sys.argv", command) + args = parse() + config = Config(args=args, output=output, term_features=output.term_features) + config.init() + installer = Installer( + output=output, + config=config, + ) + + def install_core(self: Installer) -> None: # noqa: ARG001 + """Don't install core. + + Args: + self: The installer instance. + """ + + monkeypatch.setattr(Installer, "_install_core", install_core) + with pytest.raises(SystemExit): + installer.run() + captured = capsys.readouterr() + assert "Editable installs are only supported for local collections" in captured.err + + +def test_core_installed(session_venv: Config) -> None: + """Test that core is installed only once. + + Args: + session_venv: The session_venv fixture. + """ + mtime_pre = session_venv.venv_bindir.joinpath("ansible").stat().st_mtime + installer = Installer(config=session_venv, output=session_venv._output) + installer._install_core() + mtime_post = session_venv.venv_bindir.joinpath("ansible").stat().st_mtime + assert mtime_pre == mtime_post + + +def test_core_install_fails( + session_venv: Config, + monkeypatch: pytest.MonkeyPatch, + capsys: pytest.CaptureFixture[str], +) -> None: + """Test a clean exit if the core install fails. + + Args: + session_venv: The session_venv fixture. + monkeypatch: The monkeypatch fixture. + capsys: The capsys fixture. + """ + orig_exists = Path.exists + + def exists(path: Path) -> bool: + """Selectively return False. + + Args: + path: A path to check + + Returns: + False if the path is "ansible", otherwise the original exists function result. + """ + if path.name == "ansible": + return False + return orig_exists(path) + + monkeypatch.setattr(Path, "exists", exists) + + def subprocess_run(*_args: Any, **_kwargs: Any) -> None: # noqa: ANN401 + """Raise an exception. + + Args: + *_args: Arguments + **_kwargs: Keyword arguments + + Raises: + subprocess.CalledProcessError: Always + + """ + raise subprocess.CalledProcessError(1, "ansible") + + monkeypatch.setattr( + "ansible_dev_environment.subcommands.installer.subprocess_run", + subprocess_run, + ) + installer = Installer(config=session_venv, output=session_venv._output) + + with pytest.raises(SystemExit): + installer._install_core() + + captured = capsys.readouterr() + assert "Failed to install ansible-core" in captured.err + + +def test_adt_installed(session_venv: Config) -> None: + """Test that adt is installed only once. + + Args: + session_venv: The session_venv fixture. + """ + orig_exists = Path.exists + + def exists(path: Path) -> bool: + """Selectively return False. + + Args: + path: A path to check + + Returns: + False if the path is "adt", otherwise the original exists function result. + """ + if path.name == "adt": + return True + return orig_exists(path) + + with pytest.MonkeyPatch.context() as m: + m.setattr(Path, "exists", exists) + installer = Installer(config=session_venv, output=session_venv._output) + installer._install_dev_tools() + + assert not session_venv.venv_bindir.joinpath("adt").exists() + + +def test_adt_install_fails( + session_venv: Config, + monkeypatch: pytest.MonkeyPatch, + capsys: pytest.CaptureFixture[str], +) -> None: + """Test a clean exit if the adt install fails. + + Args: + session_venv: The session_venv fixture. + monkeypatch: The monkeypatch fixture. + capsys: The capsys fixture. + """ + + def subprocess_run(*_args: Any, **_kwargs: Any) -> None: # noqa: ANN401 + """Raise an exception. + + Args: + *_args: Arguments + **_kwargs: Keyword arguments + + Raises: + subprocess.CalledProcessError: Always + + """ + raise subprocess.CalledProcessError(1, "adt") + + monkeypatch.setattr( + "ansible_dev_environment.subcommands.installer.subprocess_run", + subprocess_run, + ) + installer = Installer(config=session_venv, output=session_venv._output) + + with pytest.raises(SystemExit): + installer._install_dev_tools() + + captured = capsys.readouterr() + assert "Failed to install ansible-dev-tools" in captured.err + + +def test_reinstall( + function_venv: Config, + monkeypatch: pytest.MonkeyPatch, + capsys: pytest.CaptureFixture[str], +) -> None: + """Test that reinstalling works. + + Args: + function_venv: The function_venv fixture. + monkeypatch: The monkeypatch fixture. + capsys: The capsys fixture. + """ + command = [ + "ade", + "install", + "ansible.posix", + "--venv", + str(function_venv.venv), + "--ll", + "notset", + "-vvv", + ] + monkeypatch.setattr("sys.argv", command) + args = parse() + config = Config( + args=args, + output=function_venv._output, + term_features=function_venv._output.term_features, + ) + config.init() + pre_mtime = (function_venv.site_pkg_collections_path / "ansible" / "posix").stat().st_mtime + installer = Installer(config=config, output=function_venv._output) + installer.run() + post_mtime = (function_venv.site_pkg_collections_path / "ansible" / "posix").stat().st_mtime + assert post_mtime > pre_mtime + captured = capsys.readouterr() + assert "Removing installed " in captured.out + + +def test_install_fails( + function_venv: Config, + monkeypatch: pytest.MonkeyPatch, + capsys: pytest.CaptureFixture[str], +) -> None: + """Test for a clean exit if a collection install fails. + + Args: + function_venv: The function_venv fixture. + monkeypatch: The monkeypatch fixture. + capsys: The capsys fixture. + """ + command = [ + "ade", + "install", + "ansible.posix", + "--venv", + str(function_venv.venv), + "--ll", + "notset", + "-vvv", + ] + monkeypatch.setattr("sys.argv", command) + args = parse() + config = Config( + args=args, + output=function_venv._output, + term_features=function_venv._output.term_features, + ) + config.init() + + def subprocess_run(**kwargs: Any) -> subprocess.CompletedProcess[str]: # noqa: ANN401 + """Raise an exception. + + Args: + **kwargs: Keyword arguments + + Raises: + subprocess.CalledProcessError: if ansible posix is being installed + + Returns: + The completed process + + """ + if "install 'ansible.posix'" in kwargs["command"]: + raise subprocess.CalledProcessError(1, "ansible.posix") + return subprocess_run(**kwargs) + + monkeypatch.setattr( + "ansible_dev_environment.subcommands.installer.subprocess_run", + subprocess_run, + ) + + installer = Installer(config=config, output=function_venv._output) + with pytest.raises(SystemExit): + installer.run() + captured = capsys.readouterr() + assert "Failed to install collection" in captured.err