diff options
Diffstat (limited to 'test')
-rw-r--r-- | test/roles/acme.missing_deps/requirements.yml | 2 | ||||
-rw-r--r-- | test/test_config.py | 21 | ||||
-rw-r--r-- | test/test_runtime.py | 191 | ||||
-rw-r--r-- | test/test_schema.py | 13 | ||||
-rw-r--r-- | test/test_types.py | 9 | ||||
-rw-r--r-- | test/test_version.py | 13 |
6 files changed, 195 insertions, 54 deletions
diff --git a/test/roles/acme.missing_deps/requirements.yml b/test/roles/acme.missing_deps/requirements.yml index 53c5937..58d68f1 100644 --- a/test/roles/acme.missing_deps/requirements.yml +++ b/test/roles/acme.missing_deps/requirements.yml @@ -1,2 +1,4 @@ collections: - foo.bar # collection that does not exist, so we can test offline mode +roles: + - this_role_does_not_exist # and also role that does not exist diff --git a/test/test_config.py b/test/test_config.py index ebdde00..bedb2d4 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -7,7 +7,12 @@ import pytest from _pytest.monkeypatch import MonkeyPatch from packaging.version import Version -from ansible_compat.config import AnsibleConfig, ansible_version, parse_ansible_version +from ansible_compat.config import ( + AnsibleConfig, + ansible_collections_path, + ansible_version, + parse_ansible_version, +) from ansible_compat.errors import InvalidPrerequisiteError, MissingAnsibleError @@ -85,3 +90,17 @@ def test_ansible_version() -> None: def test_ansible_version_arg() -> None: """Validate ansible_version behavior.""" assert ansible_version("2.0") >= Version("1.0") + + +@pytest.mark.parametrize( + "var", + ("", "ANSIBLE_COLLECTIONS_PATH", "ANSIBLE_COLLECTIONS_PATHS"), + ids=["blank", "singular", "plural"], +) +def test_ansible_collections_path_env(var: str, monkeypatch: MonkeyPatch) -> None: + """Test that ansible_collections_path returns the appropriate env var.""" + # Set the variable + if var: + monkeypatch.setenv(var, "") + + assert ansible_collections_path() == (var or "ANSIBLE_COLLECTIONS_PATH") 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: diff --git a/test/test_schema.py b/test/test_schema.py index 10c1a9a..91616a9 100644 --- a/test/test_schema.py +++ b/test/test_schema.py @@ -72,3 +72,16 @@ def test_schema(index: int) -> None: def test_json_path() -> None: """Test json_path function.""" assert json_path(["a", 1, "b"]) == "$.a[1].b" + + +def test_validate_invalid_schema() -> None: + """Test validate function error handling.""" + schema = "[]" + data = json_from_asset("assets/validate0_data.json") + errors = validate(schema, data) + + assert len(errors) == 1 + assert ( + errors[0].to_friendly() + == "In 'schema sanity check': Invalid schema, must be a mapping." + ) diff --git a/test/test_types.py b/test/test_types.py new file mode 100644 index 0000000..6702b48 --- /dev/null +++ b/test/test_types.py @@ -0,0 +1,9 @@ +"""Tests for types module.""" + +import ansible_compat.types + + +def test_types() -> None: + """Tests that JSON types are exported.""" + assert ansible_compat.types.JSON + assert ansible_compat.types.JSON_ro diff --git a/test/test_version.py b/test/test_version.py new file mode 100644 index 0000000..b5d26ab --- /dev/null +++ b/test/test_version.py @@ -0,0 +1,13 @@ +"""Tests for _version module.""" + + +def test_version_module() -> None: + """Tests that _version exports are present.""" + # import kept here to allow mypy/pylint to run when module is not installed + # and the generated _version.py is missing. + # pylint: disable=no-name-in-module,no-member + import ansible_compat._version # type: ignore[import-not-found,unused-ignore] + + assert ansible_compat._version.__version__ + assert ansible_compat._version.__version_tuple__ + assert ansible_compat._version.version |