diff options
Diffstat (limited to '')
-rw-r--r-- | test/test_runtime.py | 191 |
1 files changed, 138 insertions, 53 deletions
diff --git a/test/test_runtime.py b/test/test_runtime.py index 0823f60..ebf99c9 100644 --- a/test/test_runtime.py +++ b/test/test_runtime.py @@ -1,6 +1,6 @@ """Tests for Runtime class.""" -# pylint: disable=protected-access +# pylint: disable=protected-access,too-many-lines from __future__ import annotations import logging @@ -15,7 +15,6 @@ from typing import TYPE_CHECKING, Any import pytest from packaging.version import Version -from ansible_compat.config import ansible_version from ansible_compat.constants import INVALID_PREREQUISITES_RC from ansible_compat.errors import ( AnsibleCommandError, @@ -182,10 +181,13 @@ def test_runtime_install_role( runtime.cache_dir = tmp_dir -def test_prepare_environment_with_collections(tmp_path: pathlib.Path) -> None: +def test_prepare_environment_with_collections(runtime_tmp: Runtime) -> None: """Check that collections are correctly installed.""" - runtime = Runtime(isolated=True, project_dir=tmp_path) - runtime.prepare_environment(required_collections={"community.molecule": "0.1.0"}) + runtime_tmp.prepare_environment( + required_collections={"community.molecule": "0.1.0"}, + install_local=True, + ) + assert "community.molecule" in runtime_tmp.collections def test_runtime_install_requirements_missing_file() -> None: @@ -438,19 +440,25 @@ def test_require_collection_invalid_collections_path(runtime: Runtime) -> None: runtime.require_collection("community.molecule") -def test_require_collection_preexisting_broken(tmp_path: pathlib.Path) -> None: +def test_require_collection_preexisting_broken(runtime_tmp: Runtime) -> None: """Check that require_collection raise with broken pre-existing collection.""" - runtime = Runtime(isolated=True, project_dir=tmp_path) - dest_path: str = runtime.config.collections_paths[0] + dest_path: str = runtime_tmp.config.collections_paths[0] dest = pathlib.Path(dest_path) / "ansible_collections" / "foo" / "bar" dest.mkdir(parents=True, exist_ok=True) with pytest.raises(InvalidPrerequisiteError, match="missing MANIFEST.json"): - runtime.require_collection("foo.bar") + runtime_tmp.require_collection("foo.bar") -def test_require_collection(runtime_tmp: Runtime) -> None: - """Check that require collection successful install case.""" - runtime_tmp.require_collection("community.molecule", "0.1.0") +def test_require_collection_install(runtime_tmp: Runtime) -> None: + """Check that require collection successful install case, including upgrade path.""" + runtime_tmp.install_collection("ansible.posix:==1.5.2") + runtime_tmp.load_collections() + collection = runtime_tmp.collections["ansible.posix"] + assert collection.version == "1.5.2" + runtime_tmp.require_collection(name="ansible.posix", version="1.5.4", install=True) + runtime_tmp.load_collections() + collection = runtime_tmp.collections["ansible.posix"] + assert collection.version == "1.5.4" @pytest.mark.parametrize( @@ -532,11 +540,14 @@ def test_install_galaxy_role_unlink( caplog: pytest.LogCaptureFixture, ) -> None: """Test ability to unlink incorrect symlinked roles.""" - runtime_tmp = Runtime(verbosity=1) + runtime_tmp = Runtime(verbosity=1, isolated=True) runtime_tmp.prepare_environment() + assert runtime_tmp.cache_dir is not None pathlib.Path(f"{runtime_tmp.cache_dir}/roles").mkdir(parents=True, exist_ok=True) - pathlib.Path(f"{runtime_tmp.cache_dir}/roles/acme.get_rich").symlink_to("/dev/null") - pathlib.Path(f"{runtime_tmp.project_dir}/meta").mkdir() + roledir = pathlib.Path(f"{runtime_tmp.cache_dir}/roles/acme.get_rich") + if not roledir.exists(): + roledir.symlink_to("/dev/null") + pathlib.Path(f"{runtime_tmp.project_dir}/meta").mkdir(exist_ok=True) pathlib.Path(f"{runtime_tmp.project_dir}/meta/main.yml").write_text( """galaxy_info: role_name: get_rich @@ -546,6 +557,7 @@ def test_install_galaxy_role_unlink( ) runtime_tmp._install_galaxy_role(runtime_tmp.project_dir) assert "symlink to current repository" in caplog.text + pathlib.Path(f"{runtime_tmp.project_dir}/meta/main.yml").unlink() def test_install_galaxy_role_bad_namespace(runtime_tmp: Runtime) -> None: @@ -563,6 +575,18 @@ def test_install_galaxy_role_bad_namespace(runtime_tmp: Runtime) -> None: runtime_tmp._install_galaxy_role(runtime_tmp.project_dir, role_name_check=1) +def test_install_galaxy_role_no_meta(runtime_tmp: Runtime) -> None: + """Check install role with missing meta/main.yml.""" + # This should fail because meta/main.yml is missing + with pytest.raises( + FileNotFoundError, + match=f"No such file or directory: '{runtime_tmp.project_dir.absolute()}/meta/main.yaml'", + ): + runtime_tmp._install_galaxy_role(runtime_tmp.project_dir) + # But ignore_errors will return without doing anything + runtime_tmp._install_galaxy_role(runtime_tmp.project_dir, ignore_errors=True) + + @pytest.mark.parametrize( "galaxy_info", ( @@ -737,11 +761,83 @@ def test_install_collection_from_disk_fail() -> None: ) -def test_prepare_environment_offline_role() -> None: +def test_load_collections_failure(mocker: MockerFixture) -> None: + """Tests for ansible-galaxy erroring.""" + mocker.patch( + "ansible_compat.runtime.Runtime.run", + return_value=CompletedProcess( + ["x"], + returncode=1, + stdout="There was an error", + stderr="This is the error", + ), + autospec=True, + ) + runtime = Runtime() + with pytest.raises(RuntimeError, match="Unable to list collections: "): + runtime.load_collections() + + +@pytest.mark.parametrize( + "value", + ("[]", '{"path": "bad data"}', '{"path": {"ansible.posix": 123}}'), + ids=["list", "malformed_collection", "bad_collection_data"], +) +def test_load_collections_garbage(value: str, mocker: MockerFixture) -> None: + """Tests for ansible-galaxy returning bad data.""" + mocker.patch( + "ansible_compat.runtime.Runtime.run", + return_value=CompletedProcess( + ["x"], + returncode=0, + stdout=value, + stderr="", + ), + autospec=True, + ) + runtime = Runtime() + with pytest.raises(TypeError, match="Unexpected collection data, "): + runtime.load_collections() + + +@pytest.mark.parametrize( + "value", + ("", '{"path": {123: 456}}'), + ids=["nothing", "bad_collection_name"], +) +def test_load_collections_invalid_json(value: str, mocker: MockerFixture) -> None: + """Tests for ansible-galaxy returning bad data.""" + mocker.patch( + "ansible_compat.runtime.Runtime.run", + return_value=CompletedProcess( + ["x"], + returncode=0, + stdout=value, + stderr="", + ), + autospec=True, + ) + runtime = Runtime() + with pytest.raises( + RuntimeError, + match=f"Unable to parse galaxy output as JSON: {value}", + ): + runtime.load_collections() + + +def test_prepare_environment_offline_role(caplog: pytest.LogCaptureFixture) -> None: """Ensure that we can make use of offline roles.""" with cwd(Path("test/roles/acme.missing_deps")): runtime = Runtime(isolated=True) runtime.prepare_environment(install_local=True, offline=True) + assert ( + "Skipped installing old role dependencies due to running in offline mode." + in caplog.text + ) + assert ( + "Skipped installing collection dependencies due to running in offline mode." + in caplog.text + ) def test_runtime_run(runtime: Runtime) -> None: @@ -785,35 +881,18 @@ def test_runtime_plugins(runtime: Runtime) -> None: assert isinstance(runtime.plugins.role, dict) assert "become" in runtime.plugins.keyword - if ansible_version() < Version("2.14.0"): - assert "sudo" in runtime.plugins.become - assert "memory" in runtime.plugins.cache - assert "default" in runtime.plugins.callback - assert "local" in runtime.plugins.connection - assert "ini" in runtime.plugins.inventory - assert "env" in runtime.plugins.lookup - assert "sh" in runtime.plugins.shell - assert "host_group_vars" in runtime.plugins.vars - assert "file" in runtime.plugins.module - assert "free" in runtime.plugins.strategy - # ansible-doc below 2.14 does not support listing 'test' and 'filter' types: - with pytest.raises(RuntimeError): - assert "is_abs" in runtime.plugins.test - with pytest.raises(RuntimeError): - assert "bool" in runtime.plugins.filter - else: - assert "ansible.builtin.sudo" in runtime.plugins.become - assert "ansible.builtin.memory" in runtime.plugins.cache - assert "ansible.builtin.default" in runtime.plugins.callback - assert "ansible.builtin.local" in runtime.plugins.connection - assert "ansible.builtin.ini" in runtime.plugins.inventory - assert "ansible.builtin.env" in runtime.plugins.lookup - assert "ansible.builtin.sh" in runtime.plugins.shell - assert "ansible.builtin.host_group_vars" in runtime.plugins.vars - assert "ansible.builtin.file" in runtime.plugins.module - assert "ansible.builtin.free" in runtime.plugins.strategy - assert "ansible.builtin.is_abs" in runtime.plugins.test - assert "ansible.builtin.bool" in runtime.plugins.filter + assert "ansible.builtin.sudo" in runtime.plugins.become + assert "ansible.builtin.memory" in runtime.plugins.cache + assert "ansible.builtin.default" in runtime.plugins.callback + assert "ansible.builtin.local" in runtime.plugins.connection + assert "ansible.builtin.ini" in runtime.plugins.inventory + assert "ansible.builtin.env" in runtime.plugins.lookup + assert "ansible.builtin.sh" in runtime.plugins.shell + assert "ansible.builtin.host_group_vars" in runtime.plugins.vars + assert "ansible.builtin.file" in runtime.plugins.module + assert "ansible.builtin.free" in runtime.plugins.strategy + assert "ansible.builtin.is_abs" in runtime.plugins.test + assert "ansible.builtin.bool" in runtime.plugins.filter @pytest.mark.parametrize( @@ -866,11 +945,20 @@ def test_is_url(name: str, result: bool) -> None: assert is_url(name) == result -def test_prepare_environment_repair_broken_symlink( +@pytest.mark.parametrize( + ("dest", "message"), + ( + ("/invalid/destination", "Collection is symlinked, but not pointing to"), + (Path.cwd(), "Found symlinked collection, skipping its installation."), + ), + ids=["broken", "valid"], +) +def test_prepare_environment_symlink( + dest: str | Path, + message: str, caplog: pytest.LogCaptureFixture, ) -> None: - """Ensure we can deal with broken symlinks in collections.""" - caplog.set_level(logging.INFO) + """Ensure avalid symlinks to collections are properly detected.""" project_dir = Path(__file__).parent / "collections" / "acme.minimal" runtime = Runtime(isolated=True, project_dir=project_dir) assert runtime.cache_dir @@ -879,12 +967,9 @@ def test_prepare_environment_repair_broken_symlink( goodies = acme / "minimal" rmtree(goodies, ignore_errors=True) goodies.unlink(missing_ok=True) - goodies.symlink_to("/invalid/destination") + goodies.symlink_to(dest) runtime.prepare_environment(install_local=True) - assert any( - msg.startswith("Collection is symlinked, but not pointing to") - for msg in caplog.messages - ) + assert message in caplog.text def test_get_galaxy_role_name_invalid() -> None: |