1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
|
"""Test the scan path functionality of the runtime."""
import json
import os
import subprocess
import sys
import textwrap
from pathlib import Path
import pytest
from _pytest.monkeypatch import MonkeyPatch
from ansible_compat.runtime import Runtime
from .conftest import VirtualEnvironment
V2_COLLECTION_TARBALL = Path("examples/reqs_v2/community-molecule-0.1.0.tar.gz")
V2_COLLECTION_NAMESPACE = "community"
V2_COLLECTION_NAME = "molecule"
V2_COLLECTION_VERSION = "0.1.0"
V2_COLLECTION_FULL_NAME = f"{V2_COLLECTION_NAMESPACE}.{V2_COLLECTION_NAME}"
@pytest.mark.skipif(
sys.version_info >= (3, 14),
reason="Python 3.14 requires unreleased ansible-core which will fail this test.",
)
@pytest.mark.parametrize(
("scan", "raises_not_found"),
(
pytest.param(False, True, id="disabled"),
pytest.param(True, False, id="enabled"),
),
ids=str,
)
def test_scan_sys_path(
venv_module: VirtualEnvironment,
monkeypatch: MonkeyPatch,
tmp_path: Path,
scan: bool,
raises_not_found: bool,
) -> None:
"""Confirm sys path is scanned for collections.
Args:
venv_module: Fixture for a virtual environment
monkeypatch: Fixture for monkeypatching
tmp_path: Fixture for a temporary directory
scan: Whether to scan the sys path
raises_not_found: Whether the collection is expected to be found
"""
# Isolated the test from the others, so ansible will not find collections
# that might be installed by other tests.
monkeypatch.setenv("VIRTUAL_ENV", venv_module.project.as_posix())
monkeypatch.setenv("ANSIBLE_HOME", tmp_path.as_posix())
# Set the sys scan path environment variable
monkeypatch.setenv("ANSIBLE_COLLECTIONS_SCAN_SYS_PATH", str(scan))
# Set the ansible collections paths to avoid bleed from other tests
monkeypatch.setenv("ANSIBLE_COLLECTIONS_PATH", str(tmp_path))
runtime_tmp = Runtime(project_dir=tmp_path, isolated=True)
first_site_package_dir = venv_module.site_package_dirs()[0]
installed_to = (
first_site_package_dir
/ "ansible_collections"
/ V2_COLLECTION_NAMESPACE
/ V2_COLLECTION_NAME
)
if not installed_to.exists():
# Install the collection into the venv site packages directory, force
# as of yet this test is not isolated from the rest of the system
runtime_tmp.install_collection(
collection=V2_COLLECTION_TARBALL,
destination=first_site_package_dir,
force=True,
)
# Confirm the collection is installed
assert installed_to.exists()
script = textwrap.dedent(
f"""
import json;
from ansible_compat.runtime import Runtime;
r = Runtime();
fv, cp = r.require_collection(name="{V2_COLLECTION_FULL_NAME}", version="{V2_COLLECTION_VERSION}", install=False);
print(json.dumps({{"found_version": str(fv), "collection_path": str(cp)}}));
""",
)
proc = venv_module.python_script_run(script)
if raises_not_found:
assert proc.returncode != 0, (proc.stdout, proc.stderr)
assert "InvalidPrerequisiteError" in proc.stderr
assert "'community.molecule' not found" in proc.stderr
else:
assert proc.returncode == 0, (proc.stdout, proc.stderr)
result = json.loads(proc.stdout)
assert result["found_version"] == V2_COLLECTION_VERSION
assert result["collection_path"] == str(installed_to)
runtime_tmp.clean()
def test_ro_venv() -> None:
"""Tests behavior when the virtual environment is read-only.
See Related https://github.com/ansible/ansible-compat/pull/470
"""
tox_work_dir = os.environ.get("TOX_WORK_DIR", ".tox")
venv_path = f"{tox_work_dir}/ro"
commands = [
f"mkdir -p {venv_path}",
f"chmod -R a+w {venv_path}",
f"uv venv --no-project --clear {venv_path}",
f"VIRTUAL_ENV={venv_path} uv pip install -q -e .",
f"chmod -R a-w {venv_path}",
f"{venv_path}/bin/python -c \"from ansible_compat.runtime import Runtime; r = Runtime(); r.install_collection('ansible.posix:>=2.0.0')\"",
f"chmod -R a+w {venv_path}",
f"rm -rf {venv_path}",
]
for cmd in commands:
result = subprocess.run( # noqa: S602
cmd,
check=False,
shell=True,
text=True,
capture_output=True,
)
assert result.returncode == 0, (
f"Got {result.returncode} running {cmd}\n\tstderr: {result.stderr}\n\tstdout: {result.stdout}"
)
|