From a1bf2a1250c40950e1a92720ac7da9031e2fa626 Mon Sep 17 00:00:00 2001 From: Ruchi Pakhle Date: Wed, 10 May 2023 19:41:52 +0530 Subject: [PATCH 01/32] integrate pytest-molecule plugin WIP --- src/pytest_ansible/molecule.py | 283 ++++++++++++++++++++++++++++++++ src/pytest_ansible/plugin.py | 9 + tests/molecule/__init__.py | 1 + tests/molecule/test_molecule.py | 0 4 files changed, 293 insertions(+) create mode 100644 src/pytest_ansible/molecule.py create mode 100644 tests/molecule/__init__.py create mode 100644 tests/molecule/test_molecule.py diff --git a/src/pytest_ansible/molecule.py b/src/pytest_ansible/molecule.py new file mode 100644 index 00000000..bf9cb331 --- /dev/null +++ b/src/pytest_ansible/molecule.py @@ -0,0 +1,283 @@ +"""pytest-molecule plugin implementation.""" +# pylint: disable=protected-access +from __future__ import annotations + +import logging +import os +import shlex +import subprocess +import sys +import warnings +from pathlib import Path +from shlex import quote +from typing import TYPE_CHECKING + +import pkg_resources +import pytest +import yaml +from molecule.api import drivers +from molecule.config import ansible_version + +if TYPE_CHECKING: + from _pytest.nodes import Node + +LOGGER = logging.getLogger(__name__) + + +def _addoption(group, parser, ini_dest, default, help_msg): + opt = "--" + ini_dest.replace("_", "-") + group.addoption(opt, action="store", dest=ini_dest, default=default, help=help_msg) + parser.addini(ini_dest, help_msg, default=default) + + +def pytest_addoption(parser): + """Inject new command line options to pytest.""" + group = parser.getgroup("molecule") + + _addoption( + group, + parser, + "molecule_unavailable_driver", + None, + "What marker to add to molecule scenarios when driver is " + "unavailable. (ex: skip, xfail). Default: None", + ) + _addoption( + group, + parser, + "molecule_base_config", + None, + "Path to the molecule base config file. The value of this option is " + "passed to molecule via the --base-config flag. Default: None", + ) + _addoption( + group, + parser, + "skip_no_git_change", + None, + "Commit to use as a reference for this test. If the role wasn't" + "changed since this commit skip the test. Default: None", + ) + + +def molecule_pytest_configure(config): + """Pytest hook for loading our specific configuration.""" + interesting_env_vars = [ + "ANSIBLE", + "MOLECULE", + "DOCKER", + "PODMAN", + "VAGRANT", + "VIRSH", + "ZUUL", + ] + + # Add extra information that may be key for debugging failures + if hasattr(config, "_metadata"): + for package in ["molecule"]: + config._metadata["Packages"][package] = pkg_resources.get_distribution( + package, + ).version + + if "Tools" not in config._metadata: + config._metadata["Tools"] = {} + config._metadata["Tools"]["ansible"] = str(ansible_version()) + + # Adds interesting env vars + env = "" + for key, value in sorted(os.environ.items()): + for var_name in interesting_env_vars: + if key.startswith(var_name): + env += f"{key}={value} " + config._metadata["env"] = env + + # We hide DeprecationWarnings thrown by driver loading because these are + # outside our control and worse: they are displayed even on projects that + # have no molecule tests at all as pytest_configure() is called during + # collection, causing spam. + with warnings.catch_warnings(): + warnings.filterwarnings("ignore", category=DeprecationWarning) + + config.option.molecule = {} + for driver in map(str, drivers()): + config.addinivalue_line( + "markers", + f"{driver}: mark test to run only when {driver} is available", + ) + config.option.molecule[driver] = {"available": True} + + config.addinivalue_line( + "markers", + "no_driver: mark used for scenarios that do not contain driver info", + ) + + config.addinivalue_line( + "markers", + "molecule: mark used by all molecule scenarios", + ) + + # validate selinux availability + if sys.platform == "linux" and os.path.isfile("/etc/selinux/config"): + try: + import selinux # noqa pylint: disable=unused-import,import-error,import-outside-toplevel + except ImportError: + logging.error( + "It appears that you are trying to use " + "molecule with a Python interpreter that does not have the " + "libselinux python bindings installed. These can only be " + "installed using your distro package manager and are specific " + "to each python version. Common package names: " + "libselinux-python python2-libselinux python3-libselinux", + ) + # we do not re-raise this exception because missing or broken + # selinux bindings are not guaranteed to fail molecule execution. + + +def pytest_collect_file( + parent: pytest.Collector, + file_path: Path | None, +) -> Node | None: + """Transform each found molecule.yml into a pytest test.""" + if file_path and file_path.is_symlink(): + return None + if file_path and file_path.name == "molecule.yml": + return MoleculeFile.from_parent(path=file_path, parent=parent) + return None + + +class MoleculeFile(pytest.File): + """Wrapper class for molecule files.""" + + def collect(self): + """Test generator.""" + if hasattr(MoleculeItem, "from_parent"): + yield MoleculeItem.from_parent(name="test", parent=self) + else: + yield MoleculeItem("test", self) + + def __str__(self): + """Return test name string representation.""" + return str(self.path.relative_to(os.getcwd())) + + +class MoleculeItem(pytest.Item): + """A molecule test. + + Pytest supports multiple tests per file, molecule only one "test". + """ + + def __init__(self, name, parent): + """Construct MoleculeItem.""" + self.funcargs = {} + super().__init__(name, parent) + moleculeyml = self.path + with open(str(moleculeyml), encoding="utf-8") as stream: + # If the molecule.yml file is empty, YAML loader returns None. To + # simplify things down the road, we replace None with an empty + # dict. + data = yaml.load(stream, Loader=yaml.SafeLoader) or {} + + # we add the driver as mark + self.molecule_driver = data.get("driver", {}).get("name", "no_driver") + self.add_marker(self.molecule_driver) + + # check for known markers and add them + markers = data.get("markers", []) + if "xfail" in markers: + self.add_marker( + pytest.mark.xfail( + reason="Marked as broken by scenario configuration.", + ), + ) + if "skip" in markers: + self.add_marker( + pytest.mark.skip(reason="Disabled by scenario configuration."), + ) + + # we also add platforms as marks + for platform in data.get("platforms", []): + platform_name = platform["name"] + self.config.addinivalue_line( + "markers", + f"{platform_name}: molecule platform name is {platform_name}", + ) + self.add_marker(platform_name) + self.add_marker("molecule") + if ( + self.config.option.molecule_unavailable_driver + and not self.config.option.molecule[self.molecule_driver]["available"] + ): + self.add_marker(self.config.option.molecule_unavailable_driver) + + def runtest(self): + """Perform effective test run.""" + folder = self.path.parent + folders = folder.parts + cwd = os.path.abspath(os.path.join(folder, "../..")) + scenario = folders[-1] + # role = folders[-3] # noqa + + cmd = [sys.executable, "-m", "molecule"] + if self.config.option.molecule_base_config: + cmd.extend(("--base-config", self.config.option.molecule_base_config)) + if self.config.option.skip_no_git_change: + try: + with subprocess.Popen( + ["git", "diff", self.config.option.skip_no_git_change, "--", "./"], + cwd=cwd, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + universal_newlines=True, + ) as proc: + proc.wait() + if len(proc.stdout.readlines()) == 0: + pytest.skip("No change in role") + except subprocess.CalledProcessError as exc: + pytest.fail( + "Error checking git diff. Error code was: " + + str(exc.returncode) + + "\nError output was: " + + exc.output, + ) + + cmd.extend((self.name, "-s", scenario)) + # We append the additional options to molecule call, allowing user to + # control how molecule is called by pytest-molecule + opts = os.environ.get("MOLECULE_OPTS") + if opts: + cmd.extend(shlex.split(opts)) + + print(f"running: {' '.join(quote(arg) for arg in cmd)} (from {cwd})") + try: + # Workaround for STDOUT/STDERR line ordering issue: + # https://github.com/pytest-dev/pytest/issues/5449 + + with subprocess.Popen( + cmd, + cwd=cwd, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + universal_newlines=True, + ) as proc: + for line in proc.stdout: + print(line, end="") + proc.wait() + if proc.returncode != 0: + pytest.fail( + f"Error code {proc.returncode} returned by: {' '.join(cmd)}", + pytrace=False, + ) + except subprocess.CalledProcessError as exc: + pytest.fail(f"Exception {exc} returned by: {' '.join(cmd)}", pytrace=False) + + def reportinfo(self): + """Return representation of test location when in verbose mode.""" + return self.fspath, 0, f"usecase: {self.name}" + + def __str__(self): + """Return name of the test.""" + return f"{self.name}[{self.molecule_driver}]" + + +class MoleculeError(Exception): + """Custom exception for error reporting.""" diff --git a/src/pytest_ansible/plugin.py b/src/pytest_ansible/plugin.py index de704958..e978a835 100644 --- a/src/pytest_ansible/plugin.py +++ b/src/pytest_ansible/plugin.py @@ -146,6 +146,12 @@ def pytest_addoption(parser): default=False, help="Enable support for ansible collection unit tests by only injecting exisiting ANSIBLE_COLLECTIONS_PATHS.", ) + group.addoption( + "--molecule", + action="store_true", + default=False, + help="Enables pytest to discover molecule tests and run them.", + ) # Add github marker to --help parser.addini("ansible", "Ansible integration", "args") @@ -174,6 +180,9 @@ def pytest_configure(config): start_path = config.invocation_params.dir inject(start_path) + if config.option.molecule: + () # what to pass here confirm once + def pytest_generate_tests(metafunc): """Generate tests when specific `ansible_*` fixtures are used by tests.""" diff --git a/tests/molecule/__init__.py b/tests/molecule/__init__.py new file mode 100644 index 00000000..f7289741 --- /dev/null +++ b/tests/molecule/__init__.py @@ -0,0 +1 @@ +"""Molecule tests.""" diff --git a/tests/molecule/test_molecule.py b/tests/molecule/test_molecule.py new file mode 100644 index 00000000..e69de29b From 2f68be2400a45b7477f05293cac1257cb83c3305 Mon Sep 17 00:00:00 2001 From: "Bradley A. Thornton" Date: Wed, 10 May 2023 06:10:05 -0700 Subject: [PATCH 02/32] Add python 3.8 back (#119) * Add python 3.8 back * Fix requirements * Remove noqa, ruff 38 * 3.8 for GHA --- .config/requirements.txt | 2 +- .github/workflows/tox.yml | 2 +- pyproject.toml | 5 +++-- src/pytest_ansible/module_dispatcher/__init__.py | 2 +- tests/unit/test_unit.py | 1 + 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.config/requirements.txt b/.config/requirements.txt index 4f01eddd..ce0ee57a 100644 --- a/.config/requirements.txt +++ b/.config/requirements.txt @@ -4,7 +4,7 @@ # # pip-compile --extra=docs --extra=test --no-annotate --output-file=.config/requirements.txt --resolver=backtracking --strip-extras --unsafe-package=ruamel-yaml-clib pyproject.toml # -ansible-core==2.14.3 +ansible-core==2.13.9 attrs==22.2.0 cffi==1.15.1 coverage==7.2.2 diff --git a/.github/workflows/tox.yml b/.github/workflows/tox.yml index ea43fc32..7eb3bf7c 100644 --- a/.github/workflows/tox.yml +++ b/.github/workflows/tox.yml @@ -28,7 +28,7 @@ jobs: id: generate_matrix uses: coactions/dynamic-matrix@v1 with: - min_python: "3.9" + min_python: "3.8" max_python: "3.11" other_names: | lint diff --git a/pyproject.toml b/pyproject.toml index 8467b4d2..9ec41c6f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ build-backend = "setuptools.build_meta" [project] # https://peps.python.org/pep-0621/#readme -requires-python = ">=3.9" +requires-python = ">=3.8" dynamic = ["version", "dependencies", "optional-dependencies"] name = "pytest-ansible" description = "Plugin for pytest to simplify calling ansible modules from tests or fixtures" @@ -26,6 +26,7 @@ classifiers = [ 'Topic :: Utilities', 'Programming Language :: Python', 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', @@ -132,7 +133,7 @@ ignore = [ "PTH", "TCH", ] -target-version = "py39" +target-version = "py38" # Same as Black. line-length = 88 diff --git a/src/pytest_ansible/module_dispatcher/__init__.py b/src/pytest_ansible/module_dispatcher/__init__.py index db403165..71ea63b5 100644 --- a/src/pytest_ansible/module_dispatcher/__init__.py +++ b/src/pytest_ansible/module_dispatcher/__init__.py @@ -1,6 +1,6 @@ """Define BaseModuleDispatcher class.""" -from collections.abc import Sequence +from typing import Sequence from pytest_ansible.errors import AnsibleModuleError diff --git a/tests/unit/test_unit.py b/tests/unit/test_unit.py index f40ef03a..3f712a6e 100644 --- a/tests/unit/test_unit.py +++ b/tests/unit/test_unit.py @@ -1,5 +1,6 @@ """Tests specific to the unit test functionality.""" +from __future__ import annotations import logging import re From d4c7c3c95426f3abd32272bb7b86294c7203ccc3 Mon Sep 17 00:00:00 2001 From: "Bradley A. Thornton" Date: Wed, 10 May 2023 06:46:27 -0700 Subject: [PATCH 03/32] Remove password (#120) --- .github/workflows/release.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 84ef052b..a7600b3c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -39,5 +39,3 @@ jobs: if: >- # "create" workflows run separately from "push" & "pull_request" github.event_name == 'release' uses: pypa/gh-action-pypi-publish@release/v1 - with: - password: ${{ secrets.pypi_password }} From d35539df164de2461edf0764cf890b4e353fe962 Mon Sep 17 00:00:00 2001 From: "Bradley A. Thornton" Date: Wed, 10 May 2023 07:53:23 -0700 Subject: [PATCH 04/32] Add token write for pypi (#122) --- .github/workflows/release.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a7600b3c..97ce1ee3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,6 +11,8 @@ jobs: name: Publish to PyPI registry environment: release runs-on: ubuntu-22.04 + permissions: + id-token: write env: FORCE_COLOR: 1 From 67dd05ba1279f18b03f7abafac8324f7753f2417 Mon Sep 17 00:00:00 2001 From: "Bradley A. Thornton" Date: Thu, 11 May 2023 05:35:50 -0700 Subject: [PATCH 05/32] REmove ansible as dep (#127) --- .config/requirements-lock.txt | 10 +--------- .config/requirements-test.txt | 1 + .config/requirements.in | 1 - .config/requirements.txt | 10 +--------- 4 files changed, 3 insertions(+), 19 deletions(-) create mode 100644 .config/requirements-test.txt diff --git a/.config/requirements-lock.txt b/.config/requirements-lock.txt index f95924ee..52c49274 100644 --- a/.config/requirements-lock.txt +++ b/.config/requirements-lock.txt @@ -1,22 +1,14 @@ # -# This file is autogenerated by pip-compile with Python 3.9 +# This file is autogenerated by pip-compile with Python 3.8 # by the following command: # # pip-compile --no-annotate --output-file=.config/requirements-lock.txt --resolver=backtracking --strip-extras --unsafe-package=ruamel-yaml-clib pyproject.toml # -ansible-core==2.14.3 attrs==22.2.0 -cffi==1.15.1 coverage==7.2.2 -cryptography==40.0.1 exceptiongroup==1.1.1 iniconfig==2.0.0 -jinja2==3.1.2 -markupsafe==2.1.2 packaging==23.0 pluggy==1.0.0 -pycparser==2.21 pytest==7.2.2 -pyyaml==6.0 -resolvelib==0.8.1 tomli==2.0.1 diff --git a/.config/requirements-test.txt b/.config/requirements-test.txt new file mode 100644 index 00000000..33cb068f --- /dev/null +++ b/.config/requirements-test.txt @@ -0,0 +1 @@ +ansible-core \ No newline at end of file diff --git a/.config/requirements.in b/.config/requirements.in index 057d32eb..24870d52 100644 --- a/.config/requirements.in +++ b/.config/requirements.in @@ -1,3 +1,2 @@ -ansible-core coverage pytest>=6,<8.0.0 diff --git a/.config/requirements.txt b/.config/requirements.txt index ce0ee57a..d75a9fa8 100644 --- a/.config/requirements.txt +++ b/.config/requirements.txt @@ -1,22 +1,14 @@ # -# This file is autogenerated by pip-compile with Python 3.9 +# This file is autogenerated by pip-compile with Python 3.8 # by the following command: # # pip-compile --extra=docs --extra=test --no-annotate --output-file=.config/requirements.txt --resolver=backtracking --strip-extras --unsafe-package=ruamel-yaml-clib pyproject.toml # -ansible-core==2.13.9 attrs==22.2.0 -cffi==1.15.1 coverage==7.2.2 -cryptography==40.0.1 exceptiongroup==1.1.1 iniconfig==2.0.0 -jinja2==3.1.2 -markupsafe==2.1.2 packaging==23.0 pluggy==1.0.0 -pycparser==2.21 pytest==7.2.2 -pyyaml==6.0 -resolvelib==0.8.1 tomli==2.0.1 From 0de974d2db060c18d16b8b00451a47c2447892a6 Mon Sep 17 00:00:00 2001 From: "Bradley A. Thornton" Date: Mon, 15 May 2023 13:04:11 -0700 Subject: [PATCH 06/32] Restore python 3.7 support (#128) --- .config/requirements-lock.txt | 5 ++++- .config/requirements.txt | 5 ++++- .github/workflows/tox.yml | 2 +- pyproject.toml | 5 +++-- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/.config/requirements-lock.txt b/.config/requirements-lock.txt index 52c49274..7b7f5c04 100644 --- a/.config/requirements-lock.txt +++ b/.config/requirements-lock.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.8 +# This file is autogenerated by pip-compile with Python 3.7 # by the following command: # # pip-compile --no-annotate --output-file=.config/requirements-lock.txt --resolver=backtracking --strip-extras --unsafe-package=ruamel-yaml-clib pyproject.toml @@ -7,8 +7,11 @@ attrs==22.2.0 coverage==7.2.2 exceptiongroup==1.1.1 +importlib-metadata==6.6.0 iniconfig==2.0.0 packaging==23.0 pluggy==1.0.0 pytest==7.2.2 tomli==2.0.1 +typing-extensions==4.5.0 +zipp==3.15.0 diff --git a/.config/requirements.txt b/.config/requirements.txt index d75a9fa8..1a858911 100644 --- a/.config/requirements.txt +++ b/.config/requirements.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.8 +# This file is autogenerated by pip-compile with Python 3.7 # by the following command: # # pip-compile --extra=docs --extra=test --no-annotate --output-file=.config/requirements.txt --resolver=backtracking --strip-extras --unsafe-package=ruamel-yaml-clib pyproject.toml @@ -7,8 +7,11 @@ attrs==22.2.0 coverage==7.2.2 exceptiongroup==1.1.1 +importlib-metadata==6.6.0 iniconfig==2.0.0 packaging==23.0 pluggy==1.0.0 pytest==7.2.2 tomli==2.0.1 +typing-extensions==4.5.0 +zipp==3.15.0 diff --git a/.github/workflows/tox.yml b/.github/workflows/tox.yml index 7eb3bf7c..aa412f11 100644 --- a/.github/workflows/tox.yml +++ b/.github/workflows/tox.yml @@ -28,7 +28,7 @@ jobs: id: generate_matrix uses: coactions/dynamic-matrix@v1 with: - min_python: "3.8" + min_python: "3.7" max_python: "3.11" other_names: | lint diff --git a/pyproject.toml b/pyproject.toml index 9ec41c6f..dd7d322f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ build-backend = "setuptools.build_meta" [project] # https://peps.python.org/pep-0621/#readme -requires-python = ">=3.8" +requires-python = ">=3.7" dynamic = ["version", "dependencies", "optional-dependencies"] name = "pytest-ansible" description = "Plugin for pytest to simplify calling ansible modules from tests or fixtures" @@ -26,6 +26,7 @@ classifiers = [ 'Topic :: Utilities', 'Programming Language :: Python', 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', @@ -133,7 +134,7 @@ ignore = [ "PTH", "TCH", ] -target-version = "py38" +target-version = "py37" # Same as Black. line-length = 88 From e97022eaf34cd31524df5015493642e10656afbe Mon Sep 17 00:00:00 2001 From: Ruchi Pakhle Date: Thu, 18 May 2023 17:37:00 +0530 Subject: [PATCH 07/32] WIP --- .config/requirements.txt | 14 +++++--- .vscode/settings.json | 5 +-- src/pytest_ansible/molecule.py | 60 ++++++++++++++++++++------------- src/pytest_ansible/plugin.py | 3 +- tests/molecule/__init__.py | 1 - tests/molecule/test_molecule.py | 0 6 files changed, 51 insertions(+), 32 deletions(-) delete mode 100644 tests/molecule/__init__.py delete mode 100644 tests/molecule/test_molecule.py diff --git a/.config/requirements.txt b/.config/requirements.txt index 1a858911..7a33145d 100644 --- a/.config/requirements.txt +++ b/.config/requirements.txt @@ -1,17 +1,23 @@ # -# This file is autogenerated by pip-compile with Python 3.7 +# This file is autogenerated by pip-compile with Python 3.9 # by the following command: # # pip-compile --extra=docs --extra=test --no-annotate --output-file=.config/requirements.txt --resolver=backtracking --strip-extras --unsafe-package=ruamel-yaml-clib pyproject.toml # +ansible-core==2.15.0 attrs==22.2.0 +cffi==1.15.1 coverage==7.2.2 +cryptography==40.0.2 exceptiongroup==1.1.1 -importlib-metadata==6.6.0 +importlib-resources==5.0.7 iniconfig==2.0.0 +jinja2==3.1.2 +markupsafe==2.1.2 packaging==23.0 pluggy==1.0.0 +pycparser==2.21 pytest==7.2.2 +pyyaml==6.0 +resolvelib==1.0.1 tomli==2.0.1 -typing-extensions==4.5.0 -zipp==3.15.0 diff --git a/.vscode/settings.json b/.vscode/settings.json index 8da526c2..dcdc9d23 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,12 +2,13 @@ "[python]": { "editor.codeActionsOnSave": { "source.organizeImports": true - } + }, + "editor.defaultFormatter": "ms-python.black-formatter" }, "editor.formatOnSave": true, "isort.check": false, "prettier.enable": false, - "python.formatting.provider": "black", + "python.formatting.provider": "none", "python.linting.flake8Enabled": true, "python.linting.mypyEnabled": true, "python.linting.pylintEnabled": true, diff --git a/src/pytest_ansible/molecule.py b/src/pytest_ansible/molecule.py index bf9cb331..5556db1d 100644 --- a/src/pytest_ansible/molecule.py +++ b/src/pytest_ansible/molecule.py @@ -60,6 +60,14 @@ def pytest_addoption(parser): ) +def pytest_collection_modifyitems(config, items): + if not config.getoption("--molecule"): + skip_molecule = pytest.mark.skip(reason="Molecule tests are disabled") + for item in items: + if "molecule" in item.keywords: + item.add_marker(skip_molecule) + + def molecule_pytest_configure(config): """Pytest hook for loading our specific configuration.""" interesting_env_vars = [ @@ -215,7 +223,6 @@ def runtest(self): folders = folder.parts cwd = os.path.abspath(os.path.join(folder, "../..")) scenario = folders[-1] - # role = folders[-3] # noqa cmd = [sys.executable, "-m", "molecule"] if self.config.option.molecule_base_config: @@ -248,27 +255,34 @@ def runtest(self): cmd.extend(shlex.split(opts)) print(f"running: {' '.join(quote(arg) for arg in cmd)} (from {cwd})") - try: - # Workaround for STDOUT/STDERR line ordering issue: - # https://github.com/pytest-dev/pytest/issues/5449 - - with subprocess.Popen( - cmd, - cwd=cwd, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - universal_newlines=True, - ) as proc: - for line in proc.stdout: - print(line, end="") - proc.wait() - if proc.returncode != 0: - pytest.fail( - f"Error code {proc.returncode} returned by: {' '.join(cmd)}", - pytrace=False, - ) - except subprocess.CalledProcessError as exc: - pytest.fail(f"Exception {exc} returned by: {' '.join(cmd)}", pytrace=False) + if self.config.getoption("--molecule"): # Check if --molecule option is enabled + try: + # Workaround for STDOUT/STDERR line ordering issue: + # https://github.com/pytest-dev/pytest/issues/5449 + with subprocess.Popen( + cmd, + cwd=cwd, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + universal_newlines=True, + ) as proc: + for line in proc.stdout: + print(line, end="") + proc.wait() + if proc.returncode != 0: + pytest.fail( + f"Error code {proc.returncode} returned by: {' '.join(cmd)}", + pytrace=False, + ) + except subprocess.CalledProcessError as exc: + pytest.fail( + f"Exception {exc} returned by: {' '.join(cmd)}", + pytrace=False, + ) + else: + pytest.skip( + "Molecule tests are disabled", + ) # Skip the test if --molecule option is not enabled def reportinfo(self): """Return representation of test location when in verbose mode.""" @@ -279,5 +293,5 @@ def __str__(self): return f"{self.name}[{self.molecule_driver}]" -class MoleculeError(Exception): +class MoleculeExceptionError(Exception): """Custom exception for error reporting.""" diff --git a/src/pytest_ansible/plugin.py b/src/pytest_ansible/plugin.py index e978a835..05044e1d 100644 --- a/src/pytest_ansible/plugin.py +++ b/src/pytest_ansible/plugin.py @@ -180,8 +180,7 @@ def pytest_configure(config): start_path = config.invocation_params.dir inject(start_path) - if config.option.molecule: - () # what to pass here confirm once + # if config.option.molecule: def pytest_generate_tests(metafunc): diff --git a/tests/molecule/__init__.py b/tests/molecule/__init__.py deleted file mode 100644 index f7289741..00000000 --- a/tests/molecule/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Molecule tests.""" diff --git a/tests/molecule/test_molecule.py b/tests/molecule/test_molecule.py deleted file mode 100644 index e69de29b..00000000 From 1648519a34356a25d2812d8d2dc2e128eaa31efe Mon Sep 17 00:00:00 2001 From: Ruchi Pakhle Date: Mon, 22 May 2023 17:09:05 +0530 Subject: [PATCH 08/32] need review --- src/pytest_ansible/molecule.py | 67 +++++++++++++++------------------- src/pytest_ansible/plugin.py | 30 ++++++++++++++- 2 files changed, 59 insertions(+), 38 deletions(-) diff --git a/src/pytest_ansible/molecule.py b/src/pytest_ansible/molecule.py index 5556db1d..a4959494 100644 --- a/src/pytest_ansible/molecule.py +++ b/src/pytest_ansible/molecule.py @@ -21,43 +21,36 @@ if TYPE_CHECKING: from _pytest.nodes import Node -LOGGER = logging.getLogger(__name__) - - -def _addoption(group, parser, ini_dest, default, help_msg): - opt = "--" + ini_dest.replace("_", "-") - group.addoption(opt, action="store", dest=ini_dest, default=default, help=help_msg) - parser.addini(ini_dest, help_msg, default=default) - - -def pytest_addoption(parser): - """Inject new command line options to pytest.""" - group = parser.getgroup("molecule") - - _addoption( - group, - parser, - "molecule_unavailable_driver", - None, - "What marker to add to molecule scenarios when driver is " - "unavailable. (ex: skip, xfail). Default: None", - ) - _addoption( - group, - parser, - "molecule_base_config", - None, - "Path to the molecule base config file. The value of this option is " - "passed to molecule via the --base-config flag. Default: None", - ) - _addoption( - group, - parser, - "skip_no_git_change", - None, - "Commit to use as a reference for this test. If the role wasn't" - "changed since this commit skip the test. Default: None", - ) +logger = logging.getLogger(__name__) + + +def get_collection_name(start_path: Path) -> tuple[str | None, str | None]: + """Get the collection namespace and name from the galaxy.yml file. + + :param start_path: The path to the root of the collection + :returns: A tuple of the namespace and name + """ + info_file = start_path / "galaxy.yml" + logger.info("Looking for collection info in %s", info_file) + + try: + with info_file.open(encoding="utf-8") as fhand: + galaxy_info = yaml.safe_load(fhand) + except FileNotFoundError: + logger.error("No galaxy.yml file found, plugin not activated") + return None, None + + try: + namespace = galaxy_info["namespace"] + name = galaxy_info["name"] + except KeyError: + logger.error("galaxy.yml file does not contain namespace and name") + return None, None + + logger.debug("galaxy.yml file found, plugin activated") + logger.info("Collection namespace: %s", namespace) + logger.info("Collection name: %s", name) + return namespace, name def pytest_collection_modifyitems(config, items): diff --git a/src/pytest_ansible/plugin.py b/src/pytest_ansible/plugin.py index 05044e1d..1c1bcea7 100644 --- a/src/pytest_ansible/plugin.py +++ b/src/pytest_ansible/plugin.py @@ -146,12 +146,39 @@ def pytest_addoption(parser): default=False, help="Enable support for ansible collection unit tests by only injecting exisiting ANSIBLE_COLLECTIONS_PATHS.", ) + + group.addoption( + "--ansible-unit-inject-only", + action="store_true", + default=False, + help="Enable support for ansible collection unit tests by only injecting exisiting ANSIBLE_COLLECTIONS_PATHS.", + ) + group.addoption( "--molecule", action="store_true", default=False, help="Enables pytest to discover molecule tests and run them.", ) + + group.addoption( + "molecule_unavailable_driver", + action="store", + default=None, + help="What marker to add to molecule scenarios when driver is ", + ) + group.addoption( + "molecule_base_config", + action="store", + default=None, + help="Path to the molecule base config file. The value of this option is ", + ) + group.addoption( + "skip_no_git_change", + action="store", + default=None, + help="Commit to use as a reference for this test. If the role wasn't", + ) # Add github marker to --help parser.addini("ansible", "Ansible integration", "args") @@ -180,7 +207,8 @@ def pytest_configure(config): start_path = config.invocation_params.dir inject(start_path) - # if config.option.molecule: + if config.option.molecule: + inject(start_path) def pytest_generate_tests(metafunc): From 70657ebd6ade4960db22fe4b4932b4990bd6342b Mon Sep 17 00:00:00 2001 From: Ruchi Pakhle Date: Mon, 29 May 2023 16:42:30 +0530 Subject: [PATCH 09/32] molecule integration --- .config/requirements.txt | 15 +++++-------- src/pytest_ansible/molecule.py | 29 ------------------------ src/pytest_ansible/plugin.py | 13 +++-------- tests/conftest.py | 41 ++++++++++++++++++++++++++++++++++ 4 files changed, 49 insertions(+), 49 deletions(-) diff --git a/.config/requirements.txt b/.config/requirements.txt index 585e1f45..7a33145d 100644 --- a/.config/requirements.txt +++ b/.config/requirements.txt @@ -1,28 +1,23 @@ # -# This file is autogenerated by pip-compile with Python 3.7 +# This file is autogenerated by pip-compile with Python 3.9 # by the following command: # # pip-compile --extra=docs --extra=test --no-annotate --output-file=.config/requirements.txt --resolver=backtracking --strip-extras --unsafe-package=ruamel-yaml-clib pyproject.toml # - ansible-core==2.15.0 attrs==22.2.0 +cffi==1.15.1 coverage==7.2.2 - cryptography==40.0.2 exceptiongroup==1.1.1 importlib-resources==5.0.7 - -exceptiongroup==1.1.1 -importlib-metadata==6.6.0 - iniconfig==2.0.0 +jinja2==3.1.2 +markupsafe==2.1.2 packaging==23.0 pluggy==1.0.0 +pycparser==2.21 pytest==7.2.2 - pyyaml==6.0 resolvelib==1.0.1 tomli==2.0.1 -typing-extensions==4.5.0 -zipp==3.15.0 diff --git a/src/pytest_ansible/molecule.py b/src/pytest_ansible/molecule.py index a4959494..edb54329 100644 --- a/src/pytest_ansible/molecule.py +++ b/src/pytest_ansible/molecule.py @@ -24,35 +24,6 @@ logger = logging.getLogger(__name__) -def get_collection_name(start_path: Path) -> tuple[str | None, str | None]: - """Get the collection namespace and name from the galaxy.yml file. - - :param start_path: The path to the root of the collection - :returns: A tuple of the namespace and name - """ - info_file = start_path / "galaxy.yml" - logger.info("Looking for collection info in %s", info_file) - - try: - with info_file.open(encoding="utf-8") as fhand: - galaxy_info = yaml.safe_load(fhand) - except FileNotFoundError: - logger.error("No galaxy.yml file found, plugin not activated") - return None, None - - try: - namespace = galaxy_info["namespace"] - name = galaxy_info["name"] - except KeyError: - logger.error("galaxy.yml file does not contain namespace and name") - return None, None - - logger.debug("galaxy.yml file found, plugin activated") - logger.info("Collection namespace: %s", namespace) - logger.info("Collection name: %s", name) - return namespace, name - - def pytest_collection_modifyitems(config, items): if not config.getoption("--molecule"): skip_molecule = pytest.mark.skip(reason="Molecule tests are disabled") diff --git a/src/pytest_ansible/plugin.py b/src/pytest_ansible/plugin.py index 1c1bcea7..c6969270 100644 --- a/src/pytest_ansible/plugin.py +++ b/src/pytest_ansible/plugin.py @@ -147,13 +147,6 @@ def pytest_addoption(parser): help="Enable support for ansible collection unit tests by only injecting exisiting ANSIBLE_COLLECTIONS_PATHS.", ) - group.addoption( - "--ansible-unit-inject-only", - action="store_true", - default=False, - help="Enable support for ansible collection unit tests by only injecting exisiting ANSIBLE_COLLECTIONS_PATHS.", - ) - group.addoption( "--molecule", action="store_true", @@ -162,19 +155,19 @@ def pytest_addoption(parser): ) group.addoption( - "molecule_unavailable_driver", + "--molecule_unavailable_driver", action="store", default=None, help="What marker to add to molecule scenarios when driver is ", ) group.addoption( - "molecule_base_config", + "--molecule_base_config", action="store", default=None, help="Path to the molecule base config file. The value of this option is ", ) group.addoption( - "skip_no_git_change", + "--skip_no_git_change", action="store", default=None, help="Commit to use as a reference for this test. If the role wasn't", diff --git a/tests/conftest.py b/tests/conftest.py index 686e0567..b2381e58 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -149,3 +149,44 @@ def hosts(): from pytest_ansible.host_manager import get_host_manager return get_host_manager(inventory=",".join(ALL_HOSTS), connection="local") + + +@pytest.fixture(scope="session") +def molecule_scenario(request): + """ + Fixture that returns the value of the "--molecule" option from the pytest configuration. + + :param request: pytest request object + :return: Value of the "--molecule" option + """ + return request.config.getoption("--molecule") + + +@pytest.fixture(scope="session") +def molecule_ansible(ansible_playbook): + """ + Fixture that returns the `ansible_playbook` fixture. + + :param ansible_playbook: The `ansible_playbook` fixture + :return: The `ansible_playbook` fixture + """ + return ansible_playbook + + +@pytest.hookimpl(tryfirst=True, hookwrapper=True) +def pytest_runtest_makereport(item, call): + """ + Pytest hook that modifies the test item by setting the `molecule_ansible` fixture + for specific fixtures ("ansible_module", "ansible_inventory") when the report + matches the call. + + :param item: Pytest test item + :param call: Test execution call (setup, call, teardown) + """ + outcome = yield + rep = outcome.get_result() + if rep.when == call: + for fixture in ("ansible_module", "ansible_inventory"): + if fixture in item.fixturenames: + setattr(item, fixture, molecule_ansible) + break From 21b0de7391afc3421f43a9d72204c89cbffc17de Mon Sep 17 00:00:00 2001 From: Ruchi Pakhle Date: Mon, 29 May 2023 17:43:19 +0530 Subject: [PATCH 10/32] checks testing --- .config/requirements.in | 1 + 1 file changed, 1 insertion(+) diff --git a/.config/requirements.in b/.config/requirements.in index 24870d52..ab5930fa 100644 --- a/.config/requirements.in +++ b/.config/requirements.in @@ -1,2 +1,3 @@ coverage pytest>=6,<8.0.0 + From 3a0adc35b2c3388e2922ce7e473b465b0b5e46cc Mon Sep 17 00:00:00 2001 From: Ruchi Pakhle Date: Wed, 7 Jun 2023 23:02:39 +0530 Subject: [PATCH 11/32] fixes --- src/pytest_ansible/molecule.py | 25 ------------------------- src/pytest_ansible/plugin.py | 27 ++++++++++++++++++--------- 2 files changed, 18 insertions(+), 34 deletions(-) diff --git a/src/pytest_ansible/molecule.py b/src/pytest_ansible/molecule.py index edb54329..e5bddeee 100644 --- a/src/pytest_ansible/molecule.py +++ b/src/pytest_ansible/molecule.py @@ -8,9 +8,7 @@ import subprocess import sys import warnings -from pathlib import Path from shlex import quote -from typing import TYPE_CHECKING import pkg_resources import pytest @@ -18,20 +16,9 @@ from molecule.api import drivers from molecule.config import ansible_version -if TYPE_CHECKING: - from _pytest.nodes import Node - logger = logging.getLogger(__name__) -def pytest_collection_modifyitems(config, items): - if not config.getoption("--molecule"): - skip_molecule = pytest.mark.skip(reason="Molecule tests are disabled") - for item in items: - if "molecule" in item.keywords: - item.add_marker(skip_molecule) - - def molecule_pytest_configure(config): """Pytest hook for loading our specific configuration.""" interesting_env_vars = [ @@ -105,18 +92,6 @@ def molecule_pytest_configure(config): # selinux bindings are not guaranteed to fail molecule execution. -def pytest_collect_file( - parent: pytest.Collector, - file_path: Path | None, -) -> Node | None: - """Transform each found molecule.yml into a pytest test.""" - if file_path and file_path.is_symlink(): - return None - if file_path and file_path.name == "molecule.yml": - return MoleculeFile.from_parent(path=file_path, parent=parent) - return None - - class MoleculeFile(pytest.File): """Wrapper class for molecule files.""" diff --git a/src/pytest_ansible/plugin.py b/src/pytest_ansible/plugin.py index c6969270..83abf1ca 100644 --- a/src/pytest_ansible/plugin.py +++ b/src/pytest_ansible/plugin.py @@ -1,6 +1,9 @@ """PyTest Ansible Plugin.""" +from __future__ import annotations import logging +from pathlib import Path +from typing import TYPE_CHECKING import ansible import ansible.constants @@ -17,8 +20,12 @@ ) from pytest_ansible.host_manager import get_host_manager +from .molecule import MoleculeFile from .units import inject, inject_only +if TYPE_CHECKING: + from _pytest.nodes import Node + logger = logging.getLogger(__name__) # Silence linters for imported fixtures @@ -147,13 +154,6 @@ def pytest_addoption(parser): help="Enable support for ansible collection unit tests by only injecting exisiting ANSIBLE_COLLECTIONS_PATHS.", ) - group.addoption( - "--molecule", - action="store_true", - default=False, - help="Enables pytest to discover molecule tests and run them.", - ) - group.addoption( "--molecule_unavailable_driver", action="store", @@ -200,8 +200,17 @@ def pytest_configure(config): start_path = config.invocation_params.dir inject(start_path) - if config.option.molecule: - inject(start_path) + +def pytest_collect_file( + parent: pytest.Collector, + file_path: Path | None, +) -> Node | None: + """Transform each found molecule.yml into a pytest test.""" + if file_path and file_path.is_symlink(): + return None + if file_path and file_path.name == "molecule.yml": + return MoleculeFile.from_parent(path=file_path, parent=parent) + return None def pytest_generate_tests(metafunc): From e07b0b2ecedce29865019e0ebf902498a0f523b1 Mon Sep 17 00:00:00 2001 From: Ruchi Pakhle Date: Wed, 14 Jun 2023 01:25:27 +0530 Subject: [PATCH 12/32] tests for molecule --- molecule/default/INSTALL.rst | 15 ++++++ molecule/default/converge.yml | 8 +++ molecule/default/create.yml | 36 ++++++++++++++ molecule/default/destroy.yml | 24 +++++++++ molecule/default/molecule.yml | 32 ++++++++++++ molecule/default/verify.yml | 10 ++++ pyproject.toml | 1 + src/pytest_ansible/molecule.py | 1 + src/pytest_ansible/plugin.py | 2 +- tests/conftest.py | 11 ----- tests/molecule/__init__.py | 1 + tests/molecule/test_molecule.py | 88 +++++++++++++++++++++++++++++++++ 12 files changed, 217 insertions(+), 12 deletions(-) create mode 100644 molecule/default/INSTALL.rst create mode 100644 molecule/default/converge.yml create mode 100644 molecule/default/create.yml create mode 100644 molecule/default/destroy.yml create mode 100644 molecule/default/molecule.yml create mode 100644 molecule/default/verify.yml create mode 100644 tests/molecule/__init__.py create mode 100644 tests/molecule/test_molecule.py diff --git a/molecule/default/INSTALL.rst b/molecule/default/INSTALL.rst new file mode 100644 index 00000000..1b38d098 --- /dev/null +++ b/molecule/default/INSTALL.rst @@ -0,0 +1,15 @@ +*********************************** +Delegated driver installation guide +*********************************** + +Requirements +============ + +This driver is delegated to the developer. Up to the developer to implement +requirements. + +Install +======= + +This driver is delegated to the developer. Up to the developer to implement +requirements. diff --git a/molecule/default/converge.yml b/molecule/default/converge.yml new file mode 100644 index 00000000..26c3c78b --- /dev/null +++ b/molecule/default/converge.yml @@ -0,0 +1,8 @@ +--- +- name: Converge + hosts: all + gather_facts: false + tasks: + - name: "Include pytest-ansible" + ansible.builtin.include_role: + name: "pytest-ansible" diff --git a/molecule/default/create.yml b/molecule/default/create.yml new file mode 100644 index 00000000..0d14ffc6 --- /dev/null +++ b/molecule/default/create.yml @@ -0,0 +1,36 @@ +--- +- name: Create + hosts: localhost + connection: local + gather_facts: false + no_log: "{{ molecule_no_log }}" + tasks: + # TODO: Developer must implement and populate 'server' variable + + - when: server.changed | default(false) | bool + block: + - name: Populate instance config dict + ansible.builtin.set_fact: + instance_conf_dict: + { + "instance": "{{ }}", + "address": "{{ }}", + "user": "{{ }}", + "port": "{{ }}", + "identity_file": "{{ }}", + } + with_items: "{{ server.results }}" + register: instance_config_dict + + - name: Convert instance config dict to a list + ansible.builtin.set_fact: + instance_conf: "{{ instance_config_dict.results | map(attribute='ansible_facts.instance_conf_dict') | list }}" + + - name: Dump instance config + ansible.builtin.copy: + content: | + # Molecule managed + + {{ instance_conf | to_json | from_json | to_yaml }} + dest: "{{ molecule_instance_config }}" + mode: 0600 diff --git a/molecule/default/destroy.yml b/molecule/default/destroy.yml new file mode 100644 index 00000000..dd6e2203 --- /dev/null +++ b/molecule/default/destroy.yml @@ -0,0 +1,24 @@ +--- +- name: Destroy + hosts: localhost + connection: local + gather_facts: false + no_log: "{{ molecule_no_log }}" + tasks: + # Developer must implement. + + # Mandatory configuration for Molecule to function. + + - name: Populate instance config + ansible.builtin.set_fact: + instance_conf: {} + + - name: Dump instance config + ansible.builtin.copy: + content: | + # Molecule managed + + {{ instance_conf | to_json | from_json | to_yaml }} + dest: "{{ molecule_instance_config }}" + mode: 0600 + when: server.changed | default(false) | bool diff --git a/molecule/default/molecule.yml b/molecule/default/molecule.yml new file mode 100644 index 00000000..65a3d422 --- /dev/null +++ b/molecule/default/molecule.yml @@ -0,0 +1,32 @@ +--- +dependency: + name: galaxy + state: present + +driver: + name: delegated + +platforms: + - name: instance + +verifier: + name: ansible + +provisioner: + name: ansible + log: true + config_options: + defaults: + interpreter_python: auto + verbosity: 1 + +scenario: + name: test_scenario + test_sequence: + - lint + - dependency + - syntax + - create + - prepare + - converge + - verify diff --git a/molecule/default/verify.yml b/molecule/default/verify.yml new file mode 100644 index 00000000..a5cfa75e --- /dev/null +++ b/molecule/default/verify.yml @@ -0,0 +1,10 @@ +--- +# This is an example playbook to execute Ansible tests. + +- name: Verify + hosts: all + gather_facts: false + tasks: + - name: Example assertion + ansible.builtin.assert: + that: true diff --git a/pyproject.toml b/pyproject.toml index dd7d322f..8de8d65c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -90,6 +90,7 @@ disable = [ "too-many-branches", "too-many-locals", "too-many-statements", + "too-few-public-methods", "unexpected-keyword-arg", "unused-argument", "invalid-name", diff --git a/src/pytest_ansible/molecule.py b/src/pytest_ansible/molecule.py index e5bddeee..e9fd268c 100644 --- a/src/pytest_ansible/molecule.py +++ b/src/pytest_ansible/molecule.py @@ -13,6 +13,7 @@ import pkg_resources import pytest import yaml + from molecule.api import drivers from molecule.config import ansible_version diff --git a/src/pytest_ansible/plugin.py b/src/pytest_ansible/plugin.py index 83abf1ca..b14d20b8 100644 --- a/src/pytest_ansible/plugin.py +++ b/src/pytest_ansible/plugin.py @@ -202,8 +202,8 @@ def pytest_configure(config): def pytest_collect_file( - parent: pytest.Collector, file_path: Path | None, + parent: pytest.Collector, ) -> Node | None: """Transform each found molecule.yml into a pytest test.""" if file_path and file_path.is_symlink(): diff --git a/tests/conftest.py b/tests/conftest.py index b2381e58..24709a61 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -151,17 +151,6 @@ def hosts(): return get_host_manager(inventory=",".join(ALL_HOSTS), connection="local") -@pytest.fixture(scope="session") -def molecule_scenario(request): - """ - Fixture that returns the value of the "--molecule" option from the pytest configuration. - - :param request: pytest request object - :return: Value of the "--molecule" option - """ - return request.config.getoption("--molecule") - - @pytest.fixture(scope="session") def molecule_ansible(ansible_playbook): """ diff --git a/tests/molecule/__init__.py b/tests/molecule/__init__.py new file mode 100644 index 00000000..f7289741 --- /dev/null +++ b/tests/molecule/__init__.py @@ -0,0 +1 @@ +"""Molecule tests.""" diff --git a/tests/molecule/test_molecule.py b/tests/molecule/test_molecule.py new file mode 100644 index 00000000..f3c7a7cb --- /dev/null +++ b/tests/molecule/test_molecule.py @@ -0,0 +1,88 @@ +"""Tests specific to the pytest-molecule plugin.""" + +from __future__ import annotations + +import subprocess +from pathlib import Path + +from pytest_ansible.molecule import MoleculeItem + +FIXTURE_DIRECTORY = Path(__file__).resolve().parent / "fixtures" + + +def test_collect(): + # Create a mock pytest.File object representing a molecule file + mock_molecule_file = MockMoleculeFile( + "/home/ruchi/pytest-ansible/molecule/default/molecule.yml", + ) + + # Create an instance of MoleculeItem with the mock molecule file + molecule_item = MoleculeItem("test", mock_molecule_file) + + # Call the collect() method of MoleculeItem and get the collected items + collected_items = list(molecule_item.collect()) + + # Run pytest with --collect-only and assert a molecule scenario is there + proc = subprocess.run( + "pytest --collect-only", + shell=True, + check=True, + cwd=FIXTURE_DIRECTORY, + capture_output=True, + ) + + # Assert that the collected items contain the MoleculeItem instance + assert molecule_item in collected_items + assert proc.returncode == 0 + output = proc.stdout.decode("utf-8") + assert "1 test collected" in output + assert "test[delegated]" in output + + +def test_run(): + # Create a mock pytest.File object representing a molecule file + mock_molecule_file = MockMoleculeFile( + "/home/ruchi/pytest-ansible/molecule/default/molecule.yml", + ) + + # Create an instance of MoleculeItem with the mock molecule file + molecule_item = MoleculeItem("test", mock_molecule_file) + # Patch the necessary dependencies + proc = subprocess.run( + "pytest /home/ruchi/pytest-ansible/molecule/default/molecule.yml -v", + shell=True, + check=True, + cwd=FIXTURE_DIRECTORY, + capture_output=True, + ) + + # Call the runtest() method of MoleculeItem + molecule_item.runtest() + output = proc.stdout.decode("utf-8") + assert "collected 1 item" in output + assert "test_scenario::lint" in output + assert "test_scenario::dependency" in output + assert "test_scenario::syntax" in output + assert "test_scenario::create" in output + assert "test_scenario::prepare" in output + assert "test_scenario::converge" in output + assert "test_scenario::verify" in output + assert "1 passed" in output + + +class MockMoleculeFile: + "Mock class for pytest.file" + + def __init__(self, path): + self.path = path + + def collect(self): + yield MockMoleculeItem() + + +class MockMoleculeItem: + "Mock class for MoleculeItem" + + def __init__(self): + self.name = "test" + self.path = "/home/ruchi/pytest-ansible/molecule/default/molecule.yml" From 373ef004d113eb47e945cfca5b446fb7a7a43926 Mon Sep 17 00:00:00 2001 From: Ruchi Pakhle Date: Fri, 16 Jun 2023 16:24:46 +0530 Subject: [PATCH 13/32] broken tests & fixes --- molecule/default/molecule.yml | 3 +- pyproject.toml | 1 + src/pytest_ansible/molecule.py | 15 ++++- src/pytest_ansible/plugin.py | 12 +++- tests/{molecule => mol}/__init__.py | 0 tests/mol/test_mol.py | 83 +++++++++++++++++++++++++++ tests/molecule/test_molecule.py | 88 ----------------------------- 7 files changed, 109 insertions(+), 93 deletions(-) rename tests/{molecule => mol}/__init__.py (100%) create mode 100644 tests/mol/test_mol.py delete mode 100644 tests/molecule/test_molecule.py diff --git a/molecule/default/molecule.yml b/molecule/default/molecule.yml index 65a3d422..ba225d60 100644 --- a/molecule/default/molecule.yml +++ b/molecule/default/molecule.yml @@ -21,9 +21,8 @@ provisioner: verbosity: 1 scenario: - name: test_scenario + name: default test_sequence: - - lint - dependency - syntax - create diff --git a/pyproject.toml b/pyproject.toml index 8de8d65c..f7e5df7f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -94,6 +94,7 @@ disable = [ "unexpected-keyword-arg", "unused-argument", "invalid-name", + "unused-import", ] [tool.pytest] diff --git a/src/pytest_ansible/molecule.py b/src/pytest_ansible/molecule.py index e9fd268c..de7d04ec 100644 --- a/src/pytest_ansible/molecule.py +++ b/src/pytest_ansible/molecule.py @@ -2,6 +2,7 @@ # pylint: disable=protected-access from __future__ import annotations +import importlib import logging import os import shlex @@ -14,8 +15,18 @@ import pytest import yaml -from molecule.api import drivers -from molecule.config import ansible_version +try: + from molecule.api import drivers +except ImportError: + drivers = None + + +try: + molecule_config = importlib.import_module("molecule.config") + ansible_version = molecule_config.ansible_version +except ImportError: + ansible_version = None + logger = logging.getLogger(__name__) diff --git a/src/pytest_ansible/plugin.py b/src/pytest_ansible/plugin.py index b14d20b8..aab6f643 100644 --- a/src/pytest_ansible/plugin.py +++ b/src/pytest_ansible/plugin.py @@ -19,8 +19,8 @@ localhost, ) from pytest_ansible.host_manager import get_host_manager +from pytest_ansible.molecule import MoleculeFile -from .molecule import MoleculeFile from .units import inject, inject_only if TYPE_CHECKING: @@ -206,6 +206,16 @@ def pytest_collect_file( parent: pytest.Collector, ) -> Node | None: """Transform each found molecule.yml into a pytest test.""" + has_molecule = False + try: + from .molecule import Molecule # noqa: F401 + + has_molecule = True + except ImportError: + pass + + if not has_molecule: + return None if file_path and file_path.is_symlink(): return None if file_path and file_path.name == "molecule.yml": diff --git a/tests/molecule/__init__.py b/tests/mol/__init__.py similarity index 100% rename from tests/molecule/__init__.py rename to tests/mol/__init__.py diff --git a/tests/mol/test_mol.py b/tests/mol/test_mol.py new file mode 100644 index 00000000..bda64c81 --- /dev/null +++ b/tests/mol/test_mol.py @@ -0,0 +1,83 @@ +import os +import subprocess +from unittest.mock import Mock + +import pytest + +try: + from pytest_ansible.molecule import MoleculeFile, MoleculeItem +except ModuleNotFoundError: + MoleculeItem = None + MoleculeFile = None + + +class TestMoleculeItem: + "Perform run test" + + @pytest.fixture() + def test_run(self, mocker): # noqa: PT004 + mocker.patch("MoleculeItem.path", Mock()) + mocker.patch("MoleculeItem.config.option.molecule_base_config", None) + mocker.patch("MoleculeItem.config.option.skip_no_git_change", None) + mocker.patch("subprocess.Popen") + mocker.patch("MoleculeItem.config.getoption", lambda x: True) # noqa: PT008 + + molecule_item = MoleculeItem("test", None) + molecule_item.runtest() + proc = subprocess.run( + "pytest pytest-ansible/molecule/default/molecule.yml -v", + shell=True, + check=True, + cwd=os.path.abspath(os.path.join(molecule_item.path.parent, "../..")), + capture_output=True, + ) + assert proc.returncode == 0 + output = proc.stdout.decode("utf-8") + assert "collected 1 item" in output + assert "pytest-ansible/molecule/default/molecule.yml::test" in output + assert "1 passed" in output + + +class TestMoleculeFile: + "Test Generator to collect the test" + + @pytest.fixture() + def test_collect(self, mocker): # noqa: PT004 + mocker.patch("MoleculeItem.from_parent") + mocker.patch("MoleculeItem.__init__") + mocker.patch("MoleculeItem.__str__") + mocker.patch("MoleculeFile.path", Mock()) + + # Test when MoleculeItem.from_parent exists + MoleculeItem.from_parent.return_value = "mocked_item" + molecule_file = MoleculeFile() + items = list(molecule_file.collect()) + assert items == ["mocked_item"] + + # Test when MoleculeItem.from_parent does not exist + MoleculeItem.from_parent.side_effect = AttributeError + MoleculeItem.return_value = "mocked_item" + molecule_file = MoleculeFile() + items = list(molecule_file.collect()) + assert items == ["mocked_item"] + + proc = subprocess.run( + "pytest --collect-only", + shell=True, + check=True, + capture_output=True, + ) + assert proc.returncode == 0 + output = proc.stdout.decode("utf-8") + assert "1 test collected" in output + assert "test[delegated]" in output + + @pytest.fixture() + def _test_str(self, mocker): + mocker.patch("MoleculeFile.path", Mock(return_value="mock/path")) + mocker.patch("os.getcwd", Mock(return_value="mock/cwd")) + molecule_file = MoleculeFile() + assert str(molecule_file) == "mock/path" + assert str(molecule_file) == "mock/path" + assert str(molecule_file) == "mock/path" + assert str(molecule_file) == "mock/path" diff --git a/tests/molecule/test_molecule.py b/tests/molecule/test_molecule.py deleted file mode 100644 index f3c7a7cb..00000000 --- a/tests/molecule/test_molecule.py +++ /dev/null @@ -1,88 +0,0 @@ -"""Tests specific to the pytest-molecule plugin.""" - -from __future__ import annotations - -import subprocess -from pathlib import Path - -from pytest_ansible.molecule import MoleculeItem - -FIXTURE_DIRECTORY = Path(__file__).resolve().parent / "fixtures" - - -def test_collect(): - # Create a mock pytest.File object representing a molecule file - mock_molecule_file = MockMoleculeFile( - "/home/ruchi/pytest-ansible/molecule/default/molecule.yml", - ) - - # Create an instance of MoleculeItem with the mock molecule file - molecule_item = MoleculeItem("test", mock_molecule_file) - - # Call the collect() method of MoleculeItem and get the collected items - collected_items = list(molecule_item.collect()) - - # Run pytest with --collect-only and assert a molecule scenario is there - proc = subprocess.run( - "pytest --collect-only", - shell=True, - check=True, - cwd=FIXTURE_DIRECTORY, - capture_output=True, - ) - - # Assert that the collected items contain the MoleculeItem instance - assert molecule_item in collected_items - assert proc.returncode == 0 - output = proc.stdout.decode("utf-8") - assert "1 test collected" in output - assert "test[delegated]" in output - - -def test_run(): - # Create a mock pytest.File object representing a molecule file - mock_molecule_file = MockMoleculeFile( - "/home/ruchi/pytest-ansible/molecule/default/molecule.yml", - ) - - # Create an instance of MoleculeItem with the mock molecule file - molecule_item = MoleculeItem("test", mock_molecule_file) - # Patch the necessary dependencies - proc = subprocess.run( - "pytest /home/ruchi/pytest-ansible/molecule/default/molecule.yml -v", - shell=True, - check=True, - cwd=FIXTURE_DIRECTORY, - capture_output=True, - ) - - # Call the runtest() method of MoleculeItem - molecule_item.runtest() - output = proc.stdout.decode("utf-8") - assert "collected 1 item" in output - assert "test_scenario::lint" in output - assert "test_scenario::dependency" in output - assert "test_scenario::syntax" in output - assert "test_scenario::create" in output - assert "test_scenario::prepare" in output - assert "test_scenario::converge" in output - assert "test_scenario::verify" in output - assert "1 passed" in output - - -class MockMoleculeFile: - "Mock class for pytest.file" - - def __init__(self, path): - self.path = path - - def collect(self): - yield MockMoleculeItem() - - -class MockMoleculeItem: - "Mock class for MoleculeItem" - - def __init__(self): - self.name = "test" - self.path = "/home/ruchi/pytest-ansible/molecule/default/molecule.yml" From 5c6418d6b6de997c6be16b44f3b05949f7b3b991 Mon Sep 17 00:00:00 2001 From: Ruchi Pakhle Date: Mon, 19 Jun 2023 18:04:13 +0530 Subject: [PATCH 14/32] tox failing tests --- tox.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tox.ini b/tox.ini index 4dd06fd2..d4e2c031 100644 --- a/tox.ini +++ b/tox.ini @@ -21,6 +21,8 @@ setenv = ANSIBLE_LOCAL_TEMP = {envdir}/.ansible-local PIP_CONSTRAINT = {toxinidir}/.config/requirements.txt devel: PIP_CONSTRAINT = /dev/null + ; py37: PIP_CONSTRAINTS=/dev/null + ; py38: PIP_CONSTRAINTS=/dev/null extras = test allowlist_externals = From 79802d9ba2c917eb916f07f0a870ad81526f22b1 Mon Sep 17 00:00:00 2001 From: Ruchi Pakhle Date: Tue, 20 Jun 2023 11:31:08 +0530 Subject: [PATCH 15/32] constraint file fixture --- tox.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index d4e2c031..12de4763 100644 --- a/tox.ini +++ b/tox.ini @@ -21,8 +21,8 @@ setenv = ANSIBLE_LOCAL_TEMP = {envdir}/.ansible-local PIP_CONSTRAINT = {toxinidir}/.config/requirements.txt devel: PIP_CONSTRAINT = /dev/null - ; py37: PIP_CONSTRAINTS=/dev/null - ; py38: PIP_CONSTRAINTS=/dev/null + py37: PIP_CONSTRAINTS=/dev/null + py38: PIP_CONSTRAINTS=/dev/null extras = test allowlist_externals = From 6e8325ea9713f8ff4d73c3c860bd653c7613f6bc Mon Sep 17 00:00:00 2001 From: Ruchi Pakhle Date: Tue, 20 Jun 2023 13:59:11 +0530 Subject: [PATCH 16/32] fixtures --- tox.ini | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tox.ini b/tox.ini index 12de4763..7362d702 100644 --- a/tox.ini +++ b/tox.ini @@ -3,7 +3,9 @@ envlist = lint, pkg, py, - devel + devel, + py37, + py38, skip_missing_interpreters = true [testenv] @@ -21,8 +23,8 @@ setenv = ANSIBLE_LOCAL_TEMP = {envdir}/.ansible-local PIP_CONSTRAINT = {toxinidir}/.config/requirements.txt devel: PIP_CONSTRAINT = /dev/null - py37: PIP_CONSTRAINTS=/dev/null - py38: PIP_CONSTRAINTS=/dev/null + py37: PIP_CONSTRAINT = /dev/null + py38: PIP_CONSTRAINT = /dev/null extras = test allowlist_externals = From ddf74b3148bf6a31e10084bef1a9c60ee4b1413b Mon Sep 17 00:00:00 2001 From: "Bradley A. Thornton" Date: Thu, 11 May 2023 05:35:50 -0700 Subject: [PATCH 17/32] REmove ansible as dep (#127) --- .config/requirements.txt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.config/requirements.txt b/.config/requirements.txt index 6f41bb8b..361e860d 100644 --- a/.config/requirements.txt +++ b/.config/requirements.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.9 +# This file is autogenerated by pip-compile with Python 3.8 # by the following command: # # pip-compile --extra=docs --extra=test --no-annotate --output-file=.config/requirements.txt --resolver=backtracking --strip-extras --unsafe-package=ruamel-yaml-clib pyproject.toml @@ -7,7 +7,6 @@ ansible-core==2.15.1 attrs==22.2.0 -cffi==1.15.1 coverage==7.2.2 cryptography==41.0.1 exceptiongroup==1.1.1 @@ -17,7 +16,6 @@ jinja2==3.1.2 markupsafe==2.1.3 packaging==23.0 pluggy==1.0.0 -pycparser==2.21 pytest==7.2.2 pyyaml==6.0 resolvelib==1.0.1 From 2dd8d4f8dea3cd87f92a2f36ccca304843c139ec Mon Sep 17 00:00:00 2001 From: "Bradley A. Thornton" Date: Mon, 15 May 2023 13:04:11 -0700 Subject: [PATCH 18/32] Restore python 3.7 support (#128) --- .config/requirements.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.config/requirements.txt b/.config/requirements.txt index 361e860d..d3e54926 100644 --- a/.config/requirements.txt +++ b/.config/requirements.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.8 +# This file is autogenerated by pip-compile with Python 3.7 # by the following command: # # pip-compile --extra=docs --extra=test --no-annotate --output-file=.config/requirements.txt --resolver=backtracking --strip-extras --unsafe-package=ruamel-yaml-clib pyproject.toml @@ -20,3 +20,5 @@ pytest==7.2.2 pyyaml==6.0 resolvelib==1.0.1 tomli==2.0.1 +typing-extensions==4.5.0 +zipp==3.15.0 From e7386d1428da7f9f8b3d89dcf0bf4ffd52ecccb6 Mon Sep 17 00:00:00 2001 From: Ruchi Pakhle Date: Mon, 29 May 2023 16:42:30 +0530 Subject: [PATCH 19/32] molecule integration --- .config/requirements.txt | 6 +++--- tests/conftest.py | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.config/requirements.txt b/.config/requirements.txt index d3e54926..6f41bb8b 100644 --- a/.config/requirements.txt +++ b/.config/requirements.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.7 +# This file is autogenerated by pip-compile with Python 3.9 # by the following command: # # pip-compile --extra=docs --extra=test --no-annotate --output-file=.config/requirements.txt --resolver=backtracking --strip-extras --unsafe-package=ruamel-yaml-clib pyproject.toml @@ -7,6 +7,7 @@ ansible-core==2.15.1 attrs==22.2.0 +cffi==1.15.1 coverage==7.2.2 cryptography==41.0.1 exceptiongroup==1.1.1 @@ -16,9 +17,8 @@ jinja2==3.1.2 markupsafe==2.1.3 packaging==23.0 pluggy==1.0.0 +pycparser==2.21 pytest==7.2.2 pyyaml==6.0 resolvelib==1.0.1 tomli==2.0.1 -typing-extensions==4.5.0 -zipp==3.15.0 diff --git a/tests/conftest.py b/tests/conftest.py index 24709a61..5893de71 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,5 @@ import pytest + from pytest_ansible.has_version import has_ansible_v1, has_ansible_v24 try: From 5d1677ac2c0a8ccb3e37122578120c4f2c7efce5 Mon Sep 17 00:00:00 2001 From: Ruchi Pakhle Date: Mon, 17 Jul 2023 18:35:55 +0530 Subject: [PATCH 20/32] naming conflicts fix --- .vscode/settings.json | 2 +- tests/mol/test_mol.py | 83 --------------------- tests/{mol => molecule_tests}/__init__.py | 0 tests/molecule_tests/test_molecule.py | 90 +++++++++++++++++++++++ 4 files changed, 91 insertions(+), 84 deletions(-) delete mode 100644 tests/mol/test_mol.py rename tests/{mol => molecule_tests}/__init__.py (100%) create mode 100644 tests/molecule_tests/test_molecule.py diff --git a/.vscode/settings.json b/.vscode/settings.json index dcdc9d23..5e407125 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,7 +8,7 @@ "editor.formatOnSave": true, "isort.check": false, "prettier.enable": false, - "python.formatting.provider": "none", + "python.formatting.provider": "black", "python.linting.flake8Enabled": true, "python.linting.mypyEnabled": true, "python.linting.pylintEnabled": true, diff --git a/tests/mol/test_mol.py b/tests/mol/test_mol.py deleted file mode 100644 index bda64c81..00000000 --- a/tests/mol/test_mol.py +++ /dev/null @@ -1,83 +0,0 @@ -import os -import subprocess -from unittest.mock import Mock - -import pytest - -try: - from pytest_ansible.molecule import MoleculeFile, MoleculeItem -except ModuleNotFoundError: - MoleculeItem = None - MoleculeFile = None - - -class TestMoleculeItem: - "Perform run test" - - @pytest.fixture() - def test_run(self, mocker): # noqa: PT004 - mocker.patch("MoleculeItem.path", Mock()) - mocker.patch("MoleculeItem.config.option.molecule_base_config", None) - mocker.patch("MoleculeItem.config.option.skip_no_git_change", None) - mocker.patch("subprocess.Popen") - mocker.patch("MoleculeItem.config.getoption", lambda x: True) # noqa: PT008 - - molecule_item = MoleculeItem("test", None) - molecule_item.runtest() - proc = subprocess.run( - "pytest pytest-ansible/molecule/default/molecule.yml -v", - shell=True, - check=True, - cwd=os.path.abspath(os.path.join(molecule_item.path.parent, "../..")), - capture_output=True, - ) - assert proc.returncode == 0 - output = proc.stdout.decode("utf-8") - assert "collected 1 item" in output - assert "pytest-ansible/molecule/default/molecule.yml::test" in output - assert "1 passed" in output - - -class TestMoleculeFile: - "Test Generator to collect the test" - - @pytest.fixture() - def test_collect(self, mocker): # noqa: PT004 - mocker.patch("MoleculeItem.from_parent") - mocker.patch("MoleculeItem.__init__") - mocker.patch("MoleculeItem.__str__") - mocker.patch("MoleculeFile.path", Mock()) - - # Test when MoleculeItem.from_parent exists - MoleculeItem.from_parent.return_value = "mocked_item" - molecule_file = MoleculeFile() - items = list(molecule_file.collect()) - assert items == ["mocked_item"] - - # Test when MoleculeItem.from_parent does not exist - MoleculeItem.from_parent.side_effect = AttributeError - MoleculeItem.return_value = "mocked_item" - molecule_file = MoleculeFile() - items = list(molecule_file.collect()) - assert items == ["mocked_item"] - - proc = subprocess.run( - "pytest --collect-only", - shell=True, - check=True, - capture_output=True, - ) - assert proc.returncode == 0 - output = proc.stdout.decode("utf-8") - assert "1 test collected" in output - assert "test[delegated]" in output - - @pytest.fixture() - def _test_str(self, mocker): - mocker.patch("MoleculeFile.path", Mock(return_value="mock/path")) - mocker.patch("os.getcwd", Mock(return_value="mock/cwd")) - molecule_file = MoleculeFile() - assert str(molecule_file) == "mock/path" - assert str(molecule_file) == "mock/path" - assert str(molecule_file) == "mock/path" - assert str(molecule_file) == "mock/path" diff --git a/tests/mol/__init__.py b/tests/molecule_tests/__init__.py similarity index 100% rename from tests/mol/__init__.py rename to tests/molecule_tests/__init__.py diff --git a/tests/molecule_tests/test_molecule.py b/tests/molecule_tests/test_molecule.py new file mode 100644 index 00000000..a8bdbcd8 --- /dev/null +++ b/tests/molecule_tests/test_molecule.py @@ -0,0 +1,90 @@ +import os +import subprocess +from unittest.mock import Mock, patch + +import pytest +from pytest_mock import MockerFixture + +try: + from pytest_ansible.molecule import MoleculeFile, MoleculeItem +except ModuleNotFoundError: + MoleculeItem = None + MoleculeFile = None + + +def test_run(mocker: MockerFixture): # noqa: PT004 + mocker.patch("pytest_ansible.molecule.MoleculeItem.path", Mock()) + mocker.patch("MoleculeItem.config.option.molecule_base_config", None) + mocker.patch("MoleculeItem.config.option.skip_no_git_change", None) + mocker.patch("subprocess.Popen") + mocker.patch("MoleculeItem.config.getoption", return_value=True) + + with patch.object(Mock, "config"): + molecule_item = MoleculeItem.from_parent(name="test", parent=Mock()) + molecule_item.runtest() + + proc = subprocess.run( + "pytest pytest-ansible/molecule/default/molecule.yml -v", + shell=True, + check=True, + cwd=os.path.abspath(os.path.join(molecule_item.path.parent, "../..")), + capture_output=True, + ) + assert proc.returncode == 0 + output = proc.stdout.decode("utf-8") + assert "collected 1 item" in output + assert "pytest-ansible/molecule/default/molecule.yml::test" in output + assert "1 passed" in output + + # Add test result + expected_output = "EXPECTED_OUTPUT" + if output != expected_output: + pytest.fail( + f"Test output does not match the expected output.\n\n" + f"Expected:\n{expected_output}\n\n" + f"Actual:\n{output}", + ) + + +def test_collect(mocker: MockerFixture): + mocker.patch("pytest_ansible.molecule.MoleculeItem.from_parent") + mocker.patch("pytest_ansible.molecule.MoleculeItem.__init__") + mocker.patch("pytest_ansible.molecule.MoleculeItem.__str__") + mocker.patch("pytest_ansible.molecule.MoleculeFile.path", Mock()) + + # Test when MoleculeItem.from_parent exists + MoleculeItem.from_parent.return_value = "mocked_item" + molecule_file = MoleculeFile() + items = list(molecule_file.collect()) + assert items == ["mocked_item"] + + # Test when MoleculeItem.from_parent does not exist + MoleculeItem.from_parent.side_effect = AttributeError + mocker.patch.object(MoleculeItem, "__new__", return_value="mocked_item") + + molecule_file = MoleculeFile() + items = list(molecule_file.collect()) + assert items == ["mocked_item"] + + mocker.patch("subprocess.run") + subprocess.run.return_value = subprocess.CompletedProcess( + args="pytest --collect-only", + returncode=0, + stdout=b"1 test collected\ntest[delegated]\n", + stderr=b"", + ) + + proc = subprocess.run( + "pytest --collect-only", + shell=True, + check=True, + capture_output=True, + ) + assert proc.returncode == 0 + output = proc.stdout.decode("utf-8") + assert "1 test collected" in output + assert "test[delegated]" in output + + +if __name__ == "__main__": + pytest.main(["-v", __file__]) From 699e7d207b0807dc3249e4299cb86a11b2ec5bf8 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 17 Jul 2023 13:15:04 +0000 Subject: [PATCH 21/32] chore: auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/conftest.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 5893de71..24709a61 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,4 @@ import pytest - from pytest_ansible.has_version import has_ansible_v1, has_ansible_v24 try: From e085bc2c9126638e332bdc74c40f535c179de197 Mon Sep 17 00:00:00 2001 From: "Bradley A. Thornton" Date: Tue, 1 Aug 2023 07:02:55 -0700 Subject: [PATCH 22/32] Switch to ANSIBLE_COLLECTION_PATH (#139) --- src/pytest_ansible/plugin.py | 2 +- src/pytest_ansible/units.py | 8 ++++---- tests/unit/test_unit.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/pytest_ansible/plugin.py b/src/pytest_ansible/plugin.py index aab6f643..8c759b24 100644 --- a/src/pytest_ansible/plugin.py +++ b/src/pytest_ansible/plugin.py @@ -151,7 +151,7 @@ def pytest_addoption(parser): "--ansible-unit-inject-only", action="store_true", default=False, - help="Enable support for ansible collection unit tests by only injecting exisiting ANSIBLE_COLLECTIONS_PATHS.", + help="Enable support for ansible collection unit tests by only injecting exisiting ANSIBLE_COLLECTIONS_PATH.", ) group.addoption( diff --git a/src/pytest_ansible/units.py b/src/pytest_ansible/units.py index 2f5f0d1d..1fec0f8f 100644 --- a/src/pytest_ansible/units.py +++ b/src/pytest_ansible/units.py @@ -102,7 +102,7 @@ def inject(start_path: Path) -> None: logger.info("Collections dir: %s", collections_dir) - # TODO: Make this a configuration option, check COLLECTIONS_PATHS + # TODO: Make this a configuration option, check COLLECTIONS_PATH # Add the user location for any dependencies paths = [str(collections_dir), "~/.ansible/collections"] logger.info("Paths: %s", paths) @@ -121,12 +121,12 @@ def inject(start_path: Path) -> None: # Set the environment variable as courtesy for integration tests env_paths = os.pathsep.join(paths) logger.info("Setting ANSIBLE_COLLECTIONS_PATH to %s", env_paths) - os.environ["ANSIBLE_COLLECTIONS_PATHS"] = env_paths + os.environ["ANSIBLE_COLLECTIONS_PATH"] = env_paths def inject_only() -> None: - """Inject the current ANSIBLE_COLLECTIONS_PATHS.""" - env_paths = os.environ.get("ANSIBLE_COLLECTIONS_PATHS", "") + """Inject the current ANSIBLE_COLLECTIONS_PATH.""" + env_paths = os.environ.get("ANSIBLE_COLLECTIONS_PATH", "") path_list = env_paths.split(os.pathsep) for path in path_list: if path: diff --git a/tests/unit/test_unit.py b/tests/unit/test_unit.py index 3f712a6e..3501fb42 100644 --- a/tests/unit/test_unit.py +++ b/tests/unit/test_unit.py @@ -64,7 +64,7 @@ def test_inject_only( :param caplog: The pytest caplog fixture """ caplog.set_level(logging.DEBUG) - monkeypatch.setenv("ANSIBLE_COLLECTIONS_PATHS", str(tmp_path / "collections")) + monkeypatch.setenv("ANSIBLE_COLLECTIONS_PATH", str(tmp_path / "collections")) (tmp_path / "collections" / "ansible_collections").mkdir(parents=True) From 94a0af02988c5534f1650818c27acd0cde4cdadb Mon Sep 17 00:00:00 2001 From: "Bradley A. Thornton" Date: Tue, 1 Aug 2023 09:18:28 -0700 Subject: [PATCH 23/32] Fix for ansible 2.9 (#141) * Fix for ansible 2.9 * Else not needed --- src/pytest_ansible/units.py | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/pytest_ansible/units.py b/src/pytest_ansible/units.py index 1fec0f8f..258a333b 100644 --- a/src/pytest_ansible/units.py +++ b/src/pytest_ansible/units.py @@ -119,14 +119,17 @@ def inject(start_path: Path) -> None: # e.g. ansible-galaxy collection install etc # Set the environment variable as courtesy for integration tests + + envvar_name = determine_envvar() env_paths = os.pathsep.join(paths) - logger.info("Setting ANSIBLE_COLLECTIONS_PATH to %s", env_paths) - os.environ["ANSIBLE_COLLECTIONS_PATH"] = env_paths + logger.info("Setting %s to %s", envvar_name, env_paths) + os.environ[envvar_name] = env_paths def inject_only() -> None: - """Inject the current ANSIBLE_COLLECTIONS_PATH.""" - env_paths = os.environ.get("ANSIBLE_COLLECTIONS_PATH", "") + """Inject the current ANSIBLE_COLLECTIONS_PATH(S).""" + envvar_name = determine_envvar() + env_paths = os.environ.get(envvar_name, "") path_list = env_paths.split(os.pathsep) for path in path_list: if path: @@ -148,3 +151,17 @@ def acf_inject(paths: list[str]) -> None: logger.debug("_ACF configured paths: %s", acf._n_configured_paths) else: logger.debug("_ACF not available") + + +def determine_envvar() -> str: + """Use the existence of the AnsibleCollectionFinder to determine + the ansible version. + + ansible 2.9 did not have AnsibleCollectionFinder and did not support ANSIBLE_COLLECTIONS_PATH + later versions do. + + :returns: The appropriate environment variable to use + """ + if not HAS_COLLECTION_FINDER: + return "ANSIBLE_COLLECTIONS_PATHS" + return "ANSIBLE_COLLECTIONS_PATH" From b0f07f520ca8844c8b9aa99830f0efac4d3948b3 Mon Sep 17 00:00:00 2001 From: Ruchi Pakhle Date: Wed, 10 May 2023 19:41:52 +0530 Subject: [PATCH 24/32] integrate pytest-molecule plugin WIP --- newenv/bin/Activate.ps1 | 247 ++++++++++++++++++++++++++++++++ newenv/bin/activate | 69 +++++++++ newenv/bin/activate.csh | 26 ++++ newenv/bin/activate.fish | 66 +++++++++ newenv/bin/ansible | 8 ++ newenv/bin/ansible-config | 8 ++ newenv/bin/ansible-connection | 8 ++ newenv/bin/ansible-console | 8 ++ newenv/bin/ansible-doc | 8 ++ newenv/bin/ansible-galaxy | 8 ++ newenv/bin/ansible-inventory | 8 ++ newenv/bin/ansible-playbook | 8 ++ newenv/bin/ansible-pull | 8 ++ newenv/bin/ansible-test | 45 ++++++ newenv/bin/ansible-vault | 8 ++ newenv/bin/chardetect | 8 ++ newenv/bin/cookiecutter | 8 ++ newenv/bin/coverage | 8 ++ newenv/bin/coverage-3.10 | 8 ++ newenv/bin/coverage3 | 8 ++ newenv/bin/dmypy | 8 ++ newenv/bin/jsonschema | 8 ++ newenv/bin/markdown-it | 8 ++ newenv/bin/molecule | 8 ++ newenv/bin/mypy | 8 ++ newenv/bin/mypyc | 8 ++ newenv/bin/normalizer | 8 ++ newenv/bin/pip | 8 ++ newenv/bin/pip3 | 8 ++ newenv/bin/pip3.10 | 8 ++ newenv/bin/pip3.11 | 8 ++ newenv/bin/py.test | 8 ++ newenv/bin/pygmentize | 8 ++ newenv/bin/pytest | 8 ++ newenv/bin/python | 1 + newenv/bin/python3 | 1 + newenv/bin/python3.10 | 1 + newenv/bin/slugify | 8 ++ newenv/bin/stubgen | 8 ++ newenv/bin/stubtest | 8 ++ newenv/lib64 | 1 + newenv/pyvenv.cfg | 3 + src/pytest_ansible/plugin.py | 3 + tests/molecule/__init__.py | 1 + tests/molecule/test_molecule.py | 0 45 files changed, 720 insertions(+) create mode 100644 newenv/bin/Activate.ps1 create mode 100644 newenv/bin/activate create mode 100644 newenv/bin/activate.csh create mode 100644 newenv/bin/activate.fish create mode 100755 newenv/bin/ansible create mode 100755 newenv/bin/ansible-config create mode 100755 newenv/bin/ansible-connection create mode 100755 newenv/bin/ansible-console create mode 100755 newenv/bin/ansible-doc create mode 100755 newenv/bin/ansible-galaxy create mode 100755 newenv/bin/ansible-inventory create mode 100755 newenv/bin/ansible-playbook create mode 100755 newenv/bin/ansible-pull create mode 100755 newenv/bin/ansible-test create mode 100755 newenv/bin/ansible-vault create mode 100755 newenv/bin/chardetect create mode 100755 newenv/bin/cookiecutter create mode 100755 newenv/bin/coverage create mode 100755 newenv/bin/coverage-3.10 create mode 100755 newenv/bin/coverage3 create mode 100755 newenv/bin/dmypy create mode 100755 newenv/bin/jsonschema create mode 100755 newenv/bin/markdown-it create mode 100755 newenv/bin/molecule create mode 100755 newenv/bin/mypy create mode 100755 newenv/bin/mypyc create mode 100755 newenv/bin/normalizer create mode 100755 newenv/bin/pip create mode 100755 newenv/bin/pip3 create mode 100755 newenv/bin/pip3.10 create mode 100755 newenv/bin/pip3.11 create mode 100755 newenv/bin/py.test create mode 100755 newenv/bin/pygmentize create mode 100755 newenv/bin/pytest create mode 120000 newenv/bin/python create mode 120000 newenv/bin/python3 create mode 120000 newenv/bin/python3.10 create mode 100755 newenv/bin/slugify create mode 100755 newenv/bin/stubgen create mode 100755 newenv/bin/stubtest create mode 120000 newenv/lib64 create mode 100644 newenv/pyvenv.cfg create mode 100644 tests/molecule/__init__.py create mode 100644 tests/molecule/test_molecule.py diff --git a/newenv/bin/Activate.ps1 b/newenv/bin/Activate.ps1 new file mode 100644 index 00000000..bab2ca64 --- /dev/null +++ b/newenv/bin/Activate.ps1 @@ -0,0 +1,247 @@ +<# +.Synopsis +Activate a Python virtual environment for the current PowerShell session. + +.Description +Pushes the python executable for a virtual environment to the front of the +$Env:PATH environment variable and sets the prompt to signify that you are +in a Python virtual environment. Makes use of the command line switches as +well as the `pyvenv.cfg` file values present in the virtual environment. + +.Parameter VenvDir +Path to the directory that contains the virtual environment to activate. The +default value for this is the parent of the directory that the Activate.ps1 +script is located within. + +.Parameter Prompt +The prompt prefix to display when this virtual environment is activated. By +default, this prompt is the name of the virtual environment folder (VenvDir) +surrounded by parentheses and followed by a single space (ie. '(.venv) '). + +.Example +Activate.ps1 +Activates the Python virtual environment that contains the Activate.ps1 script. + +.Example +Activate.ps1 -Verbose +Activates the Python virtual environment that contains the Activate.ps1 script, +and shows extra information about the activation as it executes. + +.Example +Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv +Activates the Python virtual environment located in the specified location. + +.Example +Activate.ps1 -Prompt "MyPython" +Activates the Python virtual environment that contains the Activate.ps1 script, +and prefixes the current prompt with the specified string (surrounded in +parentheses) while the virtual environment is active. + +.Notes +On Windows, it may be required to enable this Activate.ps1 script by setting the +execution policy for the user. You can do this by issuing the following PowerShell +command: + +PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser + +For more information on Execution Policies: +https://go.microsoft.com/fwlink/?LinkID=135170 + +#> +Param( + [Parameter(Mandatory = $false)] + [String] + $VenvDir, + [Parameter(Mandatory = $false)] + [String] + $Prompt +) + +<# Function declarations --------------------------------------------------- #> + +<# +.Synopsis +Remove all shell session elements added by the Activate script, including the +addition of the virtual environment's Python executable from the beginning of +the PATH variable. + +.Parameter NonDestructive +If present, do not remove this function from the global namespace for the +session. + +#> +function global:deactivate ([switch]$NonDestructive) { + # Revert to original values + + # The prior prompt: + if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) { + Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt + Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT + } + + # The prior PYTHONHOME: + if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) { + Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME + Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME + } + + # The prior PATH: + if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) { + Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH + Remove-Item -Path Env:_OLD_VIRTUAL_PATH + } + + # Just remove the VIRTUAL_ENV altogether: + if (Test-Path -Path Env:VIRTUAL_ENV) { + Remove-Item -Path env:VIRTUAL_ENV + } + + # Just remove VIRTUAL_ENV_PROMPT altogether. + if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) { + Remove-Item -Path env:VIRTUAL_ENV_PROMPT + } + + # Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether: + if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) { + Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force + } + + # Leave deactivate function in the global namespace if requested: + if (-not $NonDestructive) { + Remove-Item -Path function:deactivate + } +} + +<# +.Description +Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the +given folder, and returns them in a map. + +For each line in the pyvenv.cfg file, if that line can be parsed into exactly +two strings separated by `=` (with any amount of whitespace surrounding the =) +then it is considered a `key = value` line. The left hand string is the key, +the right hand is the value. + +If the value starts with a `'` or a `"` then the first and last character is +stripped from the value before being captured. + +.Parameter ConfigDir +Path to the directory that contains the `pyvenv.cfg` file. +#> +function Get-PyVenvConfig( + [String] + $ConfigDir +) { + Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg" + + # Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue). + $pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue + + # An empty map will be returned if no config file is found. + $pyvenvConfig = @{ } + + if ($pyvenvConfigPath) { + + Write-Verbose "File exists, parse `key = value` lines" + $pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath + + $pyvenvConfigContent | ForEach-Object { + $keyval = $PSItem -split "\s*=\s*", 2 + if ($keyval[0] -and $keyval[1]) { + $val = $keyval[1] + + # Remove extraneous quotations around a string value. + if ("'""".Contains($val.Substring(0, 1))) { + $val = $val.Substring(1, $val.Length - 2) + } + + $pyvenvConfig[$keyval[0]] = $val + Write-Verbose "Adding Key: '$($keyval[0])'='$val'" + } + } + } + return $pyvenvConfig +} + + +<# Begin Activate script --------------------------------------------------- #> + +# Determine the containing directory of this script +$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition +$VenvExecDir = Get-Item -Path $VenvExecPath + +Write-Verbose "Activation script is located in path: '$VenvExecPath'" +Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)" +Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)" + +# Set values required in priority: CmdLine, ConfigFile, Default +# First, get the location of the virtual environment, it might not be +# VenvExecDir if specified on the command line. +if ($VenvDir) { + Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values" +} +else { + Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir." + $VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/") + Write-Verbose "VenvDir=$VenvDir" +} + +# Next, read the `pyvenv.cfg` file to determine any required value such +# as `prompt`. +$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir + +# Next, set the prompt from the command line, or the config file, or +# just use the name of the virtual environment folder. +if ($Prompt) { + Write-Verbose "Prompt specified as argument, using '$Prompt'" +} +else { + Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value" + if ($pyvenvCfg -and $pyvenvCfg['prompt']) { + Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'" + $Prompt = $pyvenvCfg['prompt']; + } + else { + Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virutal environment)" + Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'" + $Prompt = Split-Path -Path $venvDir -Leaf + } +} + +Write-Verbose "Prompt = '$Prompt'" +Write-Verbose "VenvDir='$VenvDir'" + +# Deactivate any currently active virtual environment, but leave the +# deactivate function in place. +deactivate -nondestructive + +# Now set the environment variable VIRTUAL_ENV, used by many tools to determine +# that there is an activated venv. +$env:VIRTUAL_ENV = $VenvDir + +if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) { + + Write-Verbose "Setting prompt to '$Prompt'" + + # Set the prompt to include the env name + # Make sure _OLD_VIRTUAL_PROMPT is global + function global:_OLD_VIRTUAL_PROMPT { "" } + Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT + New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt + + function global:prompt { + Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) " + _OLD_VIRTUAL_PROMPT + } + $env:VIRTUAL_ENV_PROMPT = $Prompt +} + +# Clear PYTHONHOME +if (Test-Path -Path Env:PYTHONHOME) { + Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME + Remove-Item -Path Env:PYTHONHOME +} + +# Add the venv to the PATH +Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH +$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH" diff --git a/newenv/bin/activate b/newenv/bin/activate new file mode 100644 index 00000000..59a85935 --- /dev/null +++ b/newenv/bin/activate @@ -0,0 +1,69 @@ +# This file must be used with "source bin/activate" *from bash* +# you cannot run it directly + +deactivate () { + # reset old environment variables + if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then + PATH="${_OLD_VIRTUAL_PATH:-}" + export PATH + unset _OLD_VIRTUAL_PATH + fi + if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then + PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}" + export PYTHONHOME + unset _OLD_VIRTUAL_PYTHONHOME + fi + + # This should detect bash and zsh, which have a hash command that must + # be called to get it to forget past commands. Without forgetting + # past commands the $PATH changes we made may not be respected + if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then + hash -r 2> /dev/null + fi + + if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then + PS1="${_OLD_VIRTUAL_PS1:-}" + export PS1 + unset _OLD_VIRTUAL_PS1 + fi + + unset VIRTUAL_ENV + unset VIRTUAL_ENV_PROMPT + if [ ! "${1:-}" = "nondestructive" ] ; then + # Self destruct! + unset -f deactivate + fi +} + +# unset irrelevant variables +deactivate nondestructive + +VIRTUAL_ENV="/home/ruchi/pytest-ansible/newenv" +export VIRTUAL_ENV + +_OLD_VIRTUAL_PATH="$PATH" +PATH="$VIRTUAL_ENV/bin:$PATH" +export PATH + +# unset PYTHONHOME if set +# this will fail if PYTHONHOME is set to the empty string (which is bad anyway) +# could use `if (set -u; : $PYTHONHOME) ;` in bash +if [ -n "${PYTHONHOME:-}" ] ; then + _OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}" + unset PYTHONHOME +fi + +if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then + _OLD_VIRTUAL_PS1="${PS1:-}" + PS1="(newenv) ${PS1:-}" + export PS1 + VIRTUAL_ENV_PROMPT="(newenv) " + export VIRTUAL_ENV_PROMPT +fi + +# This should detect bash and zsh, which have a hash command that must +# be called to get it to forget past commands. Without forgetting +# past commands the $PATH changes we made may not be respected +if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then + hash -r 2> /dev/null +fi diff --git a/newenv/bin/activate.csh b/newenv/bin/activate.csh new file mode 100644 index 00000000..78f865b7 --- /dev/null +++ b/newenv/bin/activate.csh @@ -0,0 +1,26 @@ +# This file must be used with "source bin/activate.csh" *from csh*. +# You cannot run it directly. +# Created by Davide Di Blasi . +# Ported to Python 3.3 venv by Andrew Svetlov + +alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate' + +# Unset irrelevant variables. +deactivate nondestructive + +setenv VIRTUAL_ENV "/home/ruchi/pytest-ansible/newenv" + +set _OLD_VIRTUAL_PATH="$PATH" +setenv PATH "$VIRTUAL_ENV/bin:$PATH" + + +set _OLD_VIRTUAL_PROMPT="$prompt" + +if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then + set prompt = "(newenv) $prompt" + setenv VIRTUAL_ENV_PROMPT "(newenv) " +endif + +alias pydoc python -m pydoc + +rehash diff --git a/newenv/bin/activate.fish b/newenv/bin/activate.fish new file mode 100644 index 00000000..cacfb50e --- /dev/null +++ b/newenv/bin/activate.fish @@ -0,0 +1,66 @@ +# This file must be used with "source /bin/activate.fish" *from fish* +# (https://fishshell.com/); you cannot run it directly. + +function deactivate -d "Exit virtual environment and return to normal shell environment" + # reset old environment variables + if test -n "$_OLD_VIRTUAL_PATH" + set -gx PATH $_OLD_VIRTUAL_PATH + set -e _OLD_VIRTUAL_PATH + end + if test -n "$_OLD_VIRTUAL_PYTHONHOME" + set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME + set -e _OLD_VIRTUAL_PYTHONHOME + end + + if test -n "$_OLD_FISH_PROMPT_OVERRIDE" + functions -e fish_prompt + set -e _OLD_FISH_PROMPT_OVERRIDE + functions -c _old_fish_prompt fish_prompt + functions -e _old_fish_prompt + end + + set -e VIRTUAL_ENV + set -e VIRTUAL_ENV_PROMPT + if test "$argv[1]" != "nondestructive" + # Self-destruct! + functions -e deactivate + end +end + +# Unset irrelevant variables. +deactivate nondestructive + +set -gx VIRTUAL_ENV "/home/ruchi/pytest-ansible/newenv" + +set -gx _OLD_VIRTUAL_PATH $PATH +set -gx PATH "$VIRTUAL_ENV/bin" $PATH + +# Unset PYTHONHOME if set. +if set -q PYTHONHOME + set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME + set -e PYTHONHOME +end + +if test -z "$VIRTUAL_ENV_DISABLE_PROMPT" + # fish uses a function instead of an env var to generate the prompt. + + # Save the current fish_prompt function as the function _old_fish_prompt. + functions -c fish_prompt _old_fish_prompt + + # With the original prompt function renamed, we can override with our own. + function fish_prompt + # Save the return status of the last command. + set -l old_status $status + + # Output the venv prompt; color taken from the blue of the Python logo. + printf "%s%s%s" (set_color 4B8BBE) "(newenv) " (set_color normal) + + # Restore the return status of the previous command. + echo "exit $old_status" | . + # Output the original/"old" prompt. + _old_fish_prompt + end + + set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV" + set -gx VIRTUAL_ENV_PROMPT "(newenv) " +end diff --git a/newenv/bin/ansible b/newenv/bin/ansible new file mode 100755 index 00000000..819eeaa6 --- /dev/null +++ b/newenv/bin/ansible @@ -0,0 +1,8 @@ +#!/home/ruchi/pytest-ansible/newenv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from ansible.cli.adhoc import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/newenv/bin/ansible-config b/newenv/bin/ansible-config new file mode 100755 index 00000000..126d68e5 --- /dev/null +++ b/newenv/bin/ansible-config @@ -0,0 +1,8 @@ +#!/home/ruchi/pytest-ansible/newenv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from ansible.cli.config import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/newenv/bin/ansible-connection b/newenv/bin/ansible-connection new file mode 100755 index 00000000..6c203cde --- /dev/null +++ b/newenv/bin/ansible-connection @@ -0,0 +1,8 @@ +#!/home/ruchi/pytest-ansible/newenv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from ansible.cli.scripts.ansible_connection_cli_stub import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/newenv/bin/ansible-console b/newenv/bin/ansible-console new file mode 100755 index 00000000..82b192bf --- /dev/null +++ b/newenv/bin/ansible-console @@ -0,0 +1,8 @@ +#!/home/ruchi/pytest-ansible/newenv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from ansible.cli.console import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/newenv/bin/ansible-doc b/newenv/bin/ansible-doc new file mode 100755 index 00000000..d82cafe3 --- /dev/null +++ b/newenv/bin/ansible-doc @@ -0,0 +1,8 @@ +#!/home/ruchi/pytest-ansible/newenv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from ansible.cli.doc import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/newenv/bin/ansible-galaxy b/newenv/bin/ansible-galaxy new file mode 100755 index 00000000..6a33a2ab --- /dev/null +++ b/newenv/bin/ansible-galaxy @@ -0,0 +1,8 @@ +#!/home/ruchi/pytest-ansible/newenv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from ansible.cli.galaxy import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/newenv/bin/ansible-inventory b/newenv/bin/ansible-inventory new file mode 100755 index 00000000..13cc6217 --- /dev/null +++ b/newenv/bin/ansible-inventory @@ -0,0 +1,8 @@ +#!/home/ruchi/pytest-ansible/newenv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from ansible.cli.inventory import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/newenv/bin/ansible-playbook b/newenv/bin/ansible-playbook new file mode 100755 index 00000000..c9652dac --- /dev/null +++ b/newenv/bin/ansible-playbook @@ -0,0 +1,8 @@ +#!/home/ruchi/pytest-ansible/newenv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from ansible.cli.playbook import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/newenv/bin/ansible-pull b/newenv/bin/ansible-pull new file mode 100755 index 00000000..1a2a3334 --- /dev/null +++ b/newenv/bin/ansible-pull @@ -0,0 +1,8 @@ +#!/home/ruchi/pytest-ansible/newenv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from ansible.cli.pull import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/newenv/bin/ansible-test b/newenv/bin/ansible-test new file mode 100755 index 00000000..33c176e7 --- /dev/null +++ b/newenv/bin/ansible-test @@ -0,0 +1,45 @@ +#!/home/ruchi/pytest-ansible/newenv/bin/python3 +# PYTHON_ARGCOMPLETE_OK +"""Command line entry point for ansible-test.""" + +# NOTE: This file resides in the _util/target directory to ensure compatibility with all supported Python versions. + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +import os +import sys + + +def main(args=None): + """Main program entry point.""" + ansible_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + source_root = os.path.join(ansible_root, 'test', 'lib') + + if os.path.exists(os.path.join(source_root, 'ansible_test', '_internal', '__init__.py')): + # running from source, use that version of ansible-test instead of any version that may already be installed + sys.path.insert(0, source_root) + + # noinspection PyProtectedMember + from ansible_test._util.target.common.constants import CONTROLLER_PYTHON_VERSIONS + + if version_to_str(sys.version_info[:2]) not in CONTROLLER_PYTHON_VERSIONS: + raise SystemExit('This version of ansible-test cannot be executed with Python version %s. Supported Python versions are: %s' % ( + version_to_str(sys.version_info[:3]), ', '.join(CONTROLLER_PYTHON_VERSIONS))) + + if any(not os.get_blocking(handle.fileno()) for handle in (sys.stdin, sys.stdout, sys.stderr)): + raise SystemExit('Standard input, output and error file handles must be blocking to run ansible-test.') + + # noinspection PyProtectedMember + from ansible_test._internal import main as cli_main + + cli_main(args) + + +def version_to_str(version): + """Return a version string from a version tuple.""" + return '.'.join(str(n) for n in version) + + +if __name__ == '__main__': + main() diff --git a/newenv/bin/ansible-vault b/newenv/bin/ansible-vault new file mode 100755 index 00000000..838b3f21 --- /dev/null +++ b/newenv/bin/ansible-vault @@ -0,0 +1,8 @@ +#!/home/ruchi/pytest-ansible/newenv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from ansible.cli.vault import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/newenv/bin/chardetect b/newenv/bin/chardetect new file mode 100755 index 00000000..d458316b --- /dev/null +++ b/newenv/bin/chardetect @@ -0,0 +1,8 @@ +#!/home/ruchi/pytest-ansible/newenv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from chardet.cli.chardetect import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/newenv/bin/cookiecutter b/newenv/bin/cookiecutter new file mode 100755 index 00000000..8ca922a6 --- /dev/null +++ b/newenv/bin/cookiecutter @@ -0,0 +1,8 @@ +#!/home/ruchi/pytest-ansible/newenv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from cookiecutter.__main__ import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/newenv/bin/coverage b/newenv/bin/coverage new file mode 100755 index 00000000..4ec53436 --- /dev/null +++ b/newenv/bin/coverage @@ -0,0 +1,8 @@ +#!/home/ruchi/pytest-ansible/newenv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from coverage.cmdline import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/newenv/bin/coverage-3.10 b/newenv/bin/coverage-3.10 new file mode 100755 index 00000000..4ec53436 --- /dev/null +++ b/newenv/bin/coverage-3.10 @@ -0,0 +1,8 @@ +#!/home/ruchi/pytest-ansible/newenv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from coverage.cmdline import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/newenv/bin/coverage3 b/newenv/bin/coverage3 new file mode 100755 index 00000000..4ec53436 --- /dev/null +++ b/newenv/bin/coverage3 @@ -0,0 +1,8 @@ +#!/home/ruchi/pytest-ansible/newenv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from coverage.cmdline import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/newenv/bin/dmypy b/newenv/bin/dmypy new file mode 100755 index 00000000..6741a0e8 --- /dev/null +++ b/newenv/bin/dmypy @@ -0,0 +1,8 @@ +#!/home/ruchi/pytest-ansible/newenv/bin/python3.10 +# -*- coding: utf-8 -*- +import re +import sys +from mypy.dmypy.client import console_entry +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(console_entry()) diff --git a/newenv/bin/jsonschema b/newenv/bin/jsonschema new file mode 100755 index 00000000..ae891898 --- /dev/null +++ b/newenv/bin/jsonschema @@ -0,0 +1,8 @@ +#!/home/ruchi/pytest-ansible/newenv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from jsonschema.cli import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/newenv/bin/markdown-it b/newenv/bin/markdown-it new file mode 100755 index 00000000..8e9b99c5 --- /dev/null +++ b/newenv/bin/markdown-it @@ -0,0 +1,8 @@ +#!/home/ruchi/pytest-ansible/newenv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from markdown_it.cli.parse import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/newenv/bin/molecule b/newenv/bin/molecule new file mode 100755 index 00000000..4dc2114e --- /dev/null +++ b/newenv/bin/molecule @@ -0,0 +1,8 @@ +#!/home/ruchi/pytest-ansible/newenv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from molecule.__main__ import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/newenv/bin/mypy b/newenv/bin/mypy new file mode 100755 index 00000000..21621b67 --- /dev/null +++ b/newenv/bin/mypy @@ -0,0 +1,8 @@ +#!/home/ruchi/pytest-ansible/newenv/bin/python3.10 +# -*- coding: utf-8 -*- +import re +import sys +from mypy.__main__ import console_entry +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(console_entry()) diff --git a/newenv/bin/mypyc b/newenv/bin/mypyc new file mode 100755 index 00000000..da0d9dbe --- /dev/null +++ b/newenv/bin/mypyc @@ -0,0 +1,8 @@ +#!/home/ruchi/pytest-ansible/newenv/bin/python3.10 +# -*- coding: utf-8 -*- +import re +import sys +from mypyc.__main__ import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/newenv/bin/normalizer b/newenv/bin/normalizer new file mode 100755 index 00000000..ab56efd5 --- /dev/null +++ b/newenv/bin/normalizer @@ -0,0 +1,8 @@ +#!/home/ruchi/pytest-ansible/newenv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from charset_normalizer.cli.normalizer import cli_detect +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(cli_detect()) diff --git a/newenv/bin/pip b/newenv/bin/pip new file mode 100755 index 00000000..7ced9df5 --- /dev/null +++ b/newenv/bin/pip @@ -0,0 +1,8 @@ +#!/home/ruchi/pytest-ansible/newenv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from pip._internal.cli.main import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/newenv/bin/pip3 b/newenv/bin/pip3 new file mode 100755 index 00000000..7ced9df5 --- /dev/null +++ b/newenv/bin/pip3 @@ -0,0 +1,8 @@ +#!/home/ruchi/pytest-ansible/newenv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from pip._internal.cli.main import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/newenv/bin/pip3.10 b/newenv/bin/pip3.10 new file mode 100755 index 00000000..7ced9df5 --- /dev/null +++ b/newenv/bin/pip3.10 @@ -0,0 +1,8 @@ +#!/home/ruchi/pytest-ansible/newenv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from pip._internal.cli.main import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/newenv/bin/pip3.11 b/newenv/bin/pip3.11 new file mode 100755 index 00000000..7ced9df5 --- /dev/null +++ b/newenv/bin/pip3.11 @@ -0,0 +1,8 @@ +#!/home/ruchi/pytest-ansible/newenv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from pip._internal.cli.main import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/newenv/bin/py.test b/newenv/bin/py.test new file mode 100755 index 00000000..3d37f29f --- /dev/null +++ b/newenv/bin/py.test @@ -0,0 +1,8 @@ +#!/home/ruchi/pytest-ansible/newenv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from pytest import console_main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(console_main()) diff --git a/newenv/bin/pygmentize b/newenv/bin/pygmentize new file mode 100755 index 00000000..d853dfb1 --- /dev/null +++ b/newenv/bin/pygmentize @@ -0,0 +1,8 @@ +#!/home/ruchi/pytest-ansible/newenv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from pygments.cmdline import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/newenv/bin/pytest b/newenv/bin/pytest new file mode 100755 index 00000000..3d37f29f --- /dev/null +++ b/newenv/bin/pytest @@ -0,0 +1,8 @@ +#!/home/ruchi/pytest-ansible/newenv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from pytest import console_main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(console_main()) diff --git a/newenv/bin/python b/newenv/bin/python new file mode 120000 index 00000000..b8a0adbb --- /dev/null +++ b/newenv/bin/python @@ -0,0 +1 @@ +python3 \ No newline at end of file diff --git a/newenv/bin/python3 b/newenv/bin/python3 new file mode 120000 index 00000000..b3b8ebf6 --- /dev/null +++ b/newenv/bin/python3 @@ -0,0 +1 @@ +/home/ruchi/venv2/develans/bin/python3 \ No newline at end of file diff --git a/newenv/bin/python3.10 b/newenv/bin/python3.10 new file mode 120000 index 00000000..b8a0adbb --- /dev/null +++ b/newenv/bin/python3.10 @@ -0,0 +1 @@ +python3 \ No newline at end of file diff --git a/newenv/bin/slugify b/newenv/bin/slugify new file mode 100755 index 00000000..432aaef9 --- /dev/null +++ b/newenv/bin/slugify @@ -0,0 +1,8 @@ +#!/home/ruchi/pytest-ansible/newenv/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from slugify.__main__ import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/newenv/bin/stubgen b/newenv/bin/stubgen new file mode 100755 index 00000000..2cb85387 --- /dev/null +++ b/newenv/bin/stubgen @@ -0,0 +1,8 @@ +#!/home/ruchi/pytest-ansible/newenv/bin/python3.10 +# -*- coding: utf-8 -*- +import re +import sys +from mypy.stubgen import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/newenv/bin/stubtest b/newenv/bin/stubtest new file mode 100755 index 00000000..8369b7b5 --- /dev/null +++ b/newenv/bin/stubtest @@ -0,0 +1,8 @@ +#!/home/ruchi/pytest-ansible/newenv/bin/python3.10 +# -*- coding: utf-8 -*- +import re +import sys +from mypy.stubtest import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/newenv/lib64 b/newenv/lib64 new file mode 120000 index 00000000..7951405f --- /dev/null +++ b/newenv/lib64 @@ -0,0 +1 @@ +lib \ No newline at end of file diff --git a/newenv/pyvenv.cfg b/newenv/pyvenv.cfg new file mode 100644 index 00000000..3661203f --- /dev/null +++ b/newenv/pyvenv.cfg @@ -0,0 +1,3 @@ +home = /home/ruchi/venv2/develans/bin +include-system-site-packages = false +version = 3.10.0 diff --git a/src/pytest_ansible/plugin.py b/src/pytest_ansible/plugin.py index 8c759b24..97200658 100644 --- a/src/pytest_ansible/plugin.py +++ b/src/pytest_ansible/plugin.py @@ -200,6 +200,9 @@ def pytest_configure(config): start_path = config.invocation_params.dir inject(start_path) + if config.option.molecule: + () # what to pass here confirm once + def pytest_collect_file( file_path: Path | None, diff --git a/tests/molecule/__init__.py b/tests/molecule/__init__.py new file mode 100644 index 00000000..f7289741 --- /dev/null +++ b/tests/molecule/__init__.py @@ -0,0 +1 @@ +"""Molecule tests.""" diff --git a/tests/molecule/test_molecule.py b/tests/molecule/test_molecule.py new file mode 100644 index 00000000..e69de29b From 6202eda9850bd60f34eb6198dcef4a670b5aacbe Mon Sep 17 00:00:00 2001 From: "Bradley A. Thornton" Date: Thu, 11 May 2023 05:35:50 -0700 Subject: [PATCH 25/32] REmove ansible as dep (#127) --- .config/requirements.txt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.config/requirements.txt b/.config/requirements.txt index 6f41bb8b..361e860d 100644 --- a/.config/requirements.txt +++ b/.config/requirements.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.9 +# This file is autogenerated by pip-compile with Python 3.8 # by the following command: # # pip-compile --extra=docs --extra=test --no-annotate --output-file=.config/requirements.txt --resolver=backtracking --strip-extras --unsafe-package=ruamel-yaml-clib pyproject.toml @@ -7,7 +7,6 @@ ansible-core==2.15.1 attrs==22.2.0 -cffi==1.15.1 coverage==7.2.2 cryptography==41.0.1 exceptiongroup==1.1.1 @@ -17,7 +16,6 @@ jinja2==3.1.2 markupsafe==2.1.3 packaging==23.0 pluggy==1.0.0 -pycparser==2.21 pytest==7.2.2 pyyaml==6.0 resolvelib==1.0.1 From f051d4abcd6f218e0cc5138269fc7a7c51808775 Mon Sep 17 00:00:00 2001 From: Ruchi Pakhle Date: Thu, 18 May 2023 17:37:00 +0530 Subject: [PATCH 26/32] WIP --- .config/requirements.txt | 2 ++ .vscode/settings.json | 2 +- src/pytest_ansible/molecule.py | 8 ++++++++ src/pytest_ansible/plugin.py | 3 +-- tests/molecule/__init__.py | 1 - tests/molecule/test_molecule.py | 0 6 files changed, 12 insertions(+), 4 deletions(-) delete mode 100644 tests/molecule/__init__.py delete mode 100644 tests/molecule/test_molecule.py diff --git a/.config/requirements.txt b/.config/requirements.txt index 361e860d..ff4cc535 100644 --- a/.config/requirements.txt +++ b/.config/requirements.txt @@ -7,6 +7,7 @@ ansible-core==2.15.1 attrs==22.2.0 +cffi==1.15.1 coverage==7.2.2 cryptography==41.0.1 exceptiongroup==1.1.1 @@ -16,6 +17,7 @@ jinja2==3.1.2 markupsafe==2.1.3 packaging==23.0 pluggy==1.0.0 +pycparser==2.21 pytest==7.2.2 pyyaml==6.0 resolvelib==1.0.1 diff --git a/.vscode/settings.json b/.vscode/settings.json index 5e407125..dcdc9d23 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,7 +8,7 @@ "editor.formatOnSave": true, "isort.check": false, "prettier.enable": false, - "python.formatting.provider": "black", + "python.formatting.provider": "none", "python.linting.flake8Enabled": true, "python.linting.mypyEnabled": true, "python.linting.pylintEnabled": true, diff --git a/src/pytest_ansible/molecule.py b/src/pytest_ansible/molecule.py index de7d04ec..aef4a5a3 100644 --- a/src/pytest_ansible/molecule.py +++ b/src/pytest_ansible/molecule.py @@ -31,6 +31,14 @@ logger = logging.getLogger(__name__) +def pytest_collection_modifyitems(config, items): + if not config.getoption("--molecule"): + skip_molecule = pytest.mark.skip(reason="Molecule tests are disabled") + for item in items: + if "molecule" in item.keywords: + item.add_marker(skip_molecule) + + def molecule_pytest_configure(config): """Pytest hook for loading our specific configuration.""" interesting_env_vars = [ diff --git a/src/pytest_ansible/plugin.py b/src/pytest_ansible/plugin.py index 97200658..1f23edd6 100644 --- a/src/pytest_ansible/plugin.py +++ b/src/pytest_ansible/plugin.py @@ -200,8 +200,7 @@ def pytest_configure(config): start_path = config.invocation_params.dir inject(start_path) - if config.option.molecule: - () # what to pass here confirm once + # if config.option.molecule: def pytest_collect_file( diff --git a/tests/molecule/__init__.py b/tests/molecule/__init__.py deleted file mode 100644 index f7289741..00000000 --- a/tests/molecule/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Molecule tests.""" diff --git a/tests/molecule/test_molecule.py b/tests/molecule/test_molecule.py deleted file mode 100644 index e69de29b..00000000 From 2aeb3a82487c8d0f081b76f9da6167e4b94abcfe Mon Sep 17 00:00:00 2001 From: Ruchi Pakhle Date: Mon, 22 May 2023 17:09:05 +0530 Subject: [PATCH 27/32] need review --- src/pytest_ansible/molecule.py | 5 +++++ src/pytest_ansible/plugin.py | 22 +++++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/pytest_ansible/molecule.py b/src/pytest_ansible/molecule.py index aef4a5a3..6feae308 100644 --- a/src/pytest_ansible/molecule.py +++ b/src/pytest_ansible/molecule.py @@ -27,6 +27,11 @@ except ImportError: ansible_version = None + :param start_path: The path to the root of the collection + :returns: A tuple of the namespace and name + """ + info_file = start_path / "galaxy.yml" + logger.info("Looking for collection info in %s", info_file) logger = logging.getLogger(__name__) diff --git a/src/pytest_ansible/plugin.py b/src/pytest_ansible/plugin.py index 1f23edd6..aa7d92a2 100644 --- a/src/pytest_ansible/plugin.py +++ b/src/pytest_ansible/plugin.py @@ -172,6 +172,25 @@ def pytest_addoption(parser): default=None, help="Commit to use as a reference for this test. If the role wasn't", ) + + group.addoption( + "molecule_unavailable_driver", + action="store", + default=None, + help="What marker to add to molecule scenarios when driver is ", + ) + group.addoption( + "molecule_base_config", + action="store", + default=None, + help="Path to the molecule base config file. The value of this option is ", + ) + group.addoption( + "skip_no_git_change", + action="store", + default=None, + help="Commit to use as a reference for this test. If the role wasn't", + ) # Add github marker to --help parser.addini("ansible", "Ansible integration", "args") @@ -200,7 +219,8 @@ def pytest_configure(config): start_path = config.invocation_params.dir inject(start_path) - # if config.option.molecule: + if config.option.molecule: + inject(start_path) def pytest_collect_file( From f53d18a262739280f4c1c2b039bef9aef2c6eee4 Mon Sep 17 00:00:00 2001 From: Ruchi Pakhle Date: Mon, 29 May 2023 16:42:30 +0530 Subject: [PATCH 28/32] molecule integration --- src/pytest_ansible/plugin.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pytest_ansible/plugin.py b/src/pytest_ansible/plugin.py index aa7d92a2..4dbeddb5 100644 --- a/src/pytest_ansible/plugin.py +++ b/src/pytest_ansible/plugin.py @@ -174,19 +174,19 @@ def pytest_addoption(parser): ) group.addoption( - "molecule_unavailable_driver", + "--molecule_unavailable_driver", action="store", default=None, help="What marker to add to molecule scenarios when driver is ", ) group.addoption( - "molecule_base_config", + "--molecule_base_config", action="store", default=None, help="Path to the molecule base config file. The value of this option is ", ) group.addoption( - "skip_no_git_change", + "--skip_no_git_change", action="store", default=None, help="Commit to use as a reference for this test. If the role wasn't", From 97582b2f486c085c8958e0eb2d9d1afe4a137c77 Mon Sep 17 00:00:00 2001 From: Ruchi Pakhle Date: Fri, 16 Jun 2023 16:24:46 +0530 Subject: [PATCH 29/32] broken tests & fixes --- tests/mol/__init__.py | 1 + tests/mol/test_mol.py | 83 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 tests/mol/__init__.py create mode 100644 tests/mol/test_mol.py diff --git a/tests/mol/__init__.py b/tests/mol/__init__.py new file mode 100644 index 00000000..f7289741 --- /dev/null +++ b/tests/mol/__init__.py @@ -0,0 +1 @@ +"""Molecule tests.""" diff --git a/tests/mol/test_mol.py b/tests/mol/test_mol.py new file mode 100644 index 00000000..bda64c81 --- /dev/null +++ b/tests/mol/test_mol.py @@ -0,0 +1,83 @@ +import os +import subprocess +from unittest.mock import Mock + +import pytest + +try: + from pytest_ansible.molecule import MoleculeFile, MoleculeItem +except ModuleNotFoundError: + MoleculeItem = None + MoleculeFile = None + + +class TestMoleculeItem: + "Perform run test" + + @pytest.fixture() + def test_run(self, mocker): # noqa: PT004 + mocker.patch("MoleculeItem.path", Mock()) + mocker.patch("MoleculeItem.config.option.molecule_base_config", None) + mocker.patch("MoleculeItem.config.option.skip_no_git_change", None) + mocker.patch("subprocess.Popen") + mocker.patch("MoleculeItem.config.getoption", lambda x: True) # noqa: PT008 + + molecule_item = MoleculeItem("test", None) + molecule_item.runtest() + proc = subprocess.run( + "pytest pytest-ansible/molecule/default/molecule.yml -v", + shell=True, + check=True, + cwd=os.path.abspath(os.path.join(molecule_item.path.parent, "../..")), + capture_output=True, + ) + assert proc.returncode == 0 + output = proc.stdout.decode("utf-8") + assert "collected 1 item" in output + assert "pytest-ansible/molecule/default/molecule.yml::test" in output + assert "1 passed" in output + + +class TestMoleculeFile: + "Test Generator to collect the test" + + @pytest.fixture() + def test_collect(self, mocker): # noqa: PT004 + mocker.patch("MoleculeItem.from_parent") + mocker.patch("MoleculeItem.__init__") + mocker.patch("MoleculeItem.__str__") + mocker.patch("MoleculeFile.path", Mock()) + + # Test when MoleculeItem.from_parent exists + MoleculeItem.from_parent.return_value = "mocked_item" + molecule_file = MoleculeFile() + items = list(molecule_file.collect()) + assert items == ["mocked_item"] + + # Test when MoleculeItem.from_parent does not exist + MoleculeItem.from_parent.side_effect = AttributeError + MoleculeItem.return_value = "mocked_item" + molecule_file = MoleculeFile() + items = list(molecule_file.collect()) + assert items == ["mocked_item"] + + proc = subprocess.run( + "pytest --collect-only", + shell=True, + check=True, + capture_output=True, + ) + assert proc.returncode == 0 + output = proc.stdout.decode("utf-8") + assert "1 test collected" in output + assert "test[delegated]" in output + + @pytest.fixture() + def _test_str(self, mocker): + mocker.patch("MoleculeFile.path", Mock(return_value="mock/path")) + mocker.patch("os.getcwd", Mock(return_value="mock/cwd")) + molecule_file = MoleculeFile() + assert str(molecule_file) == "mock/path" + assert str(molecule_file) == "mock/path" + assert str(molecule_file) == "mock/path" + assert str(molecule_file) == "mock/path" From 55baec008b21d50cb582ad2189a0c4f04ea59ae7 Mon Sep 17 00:00:00 2001 From: "Bradley A. Thornton" Date: Thu, 11 May 2023 05:35:50 -0700 Subject: [PATCH 30/32] REmove ansible as dep (#127) --- .config/requirements.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/.config/requirements.txt b/.config/requirements.txt index ff4cc535..361e860d 100644 --- a/.config/requirements.txt +++ b/.config/requirements.txt @@ -7,7 +7,6 @@ ansible-core==2.15.1 attrs==22.2.0 -cffi==1.15.1 coverage==7.2.2 cryptography==41.0.1 exceptiongroup==1.1.1 @@ -17,7 +16,6 @@ jinja2==3.1.2 markupsafe==2.1.3 packaging==23.0 pluggy==1.0.0 -pycparser==2.21 pytest==7.2.2 pyyaml==6.0 resolvelib==1.0.1 From 6f7deb0dacc51af5a5a3717cc997a174232074c0 Mon Sep 17 00:00:00 2001 From: Ruchi Pakhle Date: Mon, 29 May 2023 16:42:30 +0530 Subject: [PATCH 31/32] molecule integration --- .config/requirements.txt | 2 ++ tests/conftest.py | 1 + 2 files changed, 3 insertions(+) diff --git a/.config/requirements.txt b/.config/requirements.txt index 361e860d..ff4cc535 100644 --- a/.config/requirements.txt +++ b/.config/requirements.txt @@ -7,6 +7,7 @@ ansible-core==2.15.1 attrs==22.2.0 +cffi==1.15.1 coverage==7.2.2 cryptography==41.0.1 exceptiongroup==1.1.1 @@ -16,6 +17,7 @@ jinja2==3.1.2 markupsafe==2.1.3 packaging==23.0 pluggy==1.0.0 +pycparser==2.21 pytest==7.2.2 pyyaml==6.0 resolvelib==1.0.1 diff --git a/tests/conftest.py b/tests/conftest.py index 24709a61..5893de71 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,5 @@ import pytest + from pytest_ansible.has_version import has_ansible_v1, has_ansible_v24 try: From 8e2fe4d747901c53c099d1bb13c620ee746af46b Mon Sep 17 00:00:00 2001 From: Ruchi Pakhle Date: Wed, 2 Aug 2023 17:41:27 +0530 Subject: [PATCH 32/32] ModuleNotFoundError in tests --- .gitignore | 3 +- src/pytest_ansible/molecule.py | 1 - src/pytest_ansible/plugin.py | 2 +- tests/molecule_tests/__init__.py | 1 - tests/molecule_tests/test_molecule.py | 90 -------------------- tests/moleculetest/__init__.py | 1 + tests/moleculetest/test_molecule.py | 115 ++++++++++++++++++++++++++ 7 files changed, 119 insertions(+), 94 deletions(-) delete mode 100644 tests/molecule_tests/__init__.py delete mode 100644 tests/molecule_tests/test_molecule.py create mode 100644 tests/moleculetest/__init__.py create mode 100644 tests/moleculetest/test_molecule.py diff --git a/.gitignore b/.gitignore index b5d6705b..7cf8834d 100644 --- a/.gitignore +++ b/.gitignore @@ -127,6 +127,7 @@ venv/ ENV/ env.bak/ venv.bak/ +newenv # Spyder project settings .spyderproject @@ -166,4 +167,4 @@ cython_debug/ # and should all have detailed explanations # Version created and populated by setuptools_scm -/src/pytest_ansible/_version.py \ No newline at end of file +/src/pytest_ansible/_version.py diff --git a/src/pytest_ansible/molecule.py b/src/pytest_ansible/molecule.py index 6feae308..f84a52a7 100644 --- a/src/pytest_ansible/molecule.py +++ b/src/pytest_ansible/molecule.py @@ -2,7 +2,6 @@ # pylint: disable=protected-access from __future__ import annotations -import importlib import logging import os import shlex diff --git a/src/pytest_ansible/plugin.py b/src/pytest_ansible/plugin.py index 4dbeddb5..e1f8a877 100644 --- a/src/pytest_ansible/plugin.py +++ b/src/pytest_ansible/plugin.py @@ -19,8 +19,8 @@ localhost, ) from pytest_ansible.host_manager import get_host_manager -from pytest_ansible.molecule import MoleculeFile +from .molecule import MoleculeFile from .units import inject, inject_only if TYPE_CHECKING: diff --git a/tests/molecule_tests/__init__.py b/tests/molecule_tests/__init__.py deleted file mode 100644 index f7289741..00000000 --- a/tests/molecule_tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Molecule tests.""" diff --git a/tests/molecule_tests/test_molecule.py b/tests/molecule_tests/test_molecule.py deleted file mode 100644 index a8bdbcd8..00000000 --- a/tests/molecule_tests/test_molecule.py +++ /dev/null @@ -1,90 +0,0 @@ -import os -import subprocess -from unittest.mock import Mock, patch - -import pytest -from pytest_mock import MockerFixture - -try: - from pytest_ansible.molecule import MoleculeFile, MoleculeItem -except ModuleNotFoundError: - MoleculeItem = None - MoleculeFile = None - - -def test_run(mocker: MockerFixture): # noqa: PT004 - mocker.patch("pytest_ansible.molecule.MoleculeItem.path", Mock()) - mocker.patch("MoleculeItem.config.option.molecule_base_config", None) - mocker.patch("MoleculeItem.config.option.skip_no_git_change", None) - mocker.patch("subprocess.Popen") - mocker.patch("MoleculeItem.config.getoption", return_value=True) - - with patch.object(Mock, "config"): - molecule_item = MoleculeItem.from_parent(name="test", parent=Mock()) - molecule_item.runtest() - - proc = subprocess.run( - "pytest pytest-ansible/molecule/default/molecule.yml -v", - shell=True, - check=True, - cwd=os.path.abspath(os.path.join(molecule_item.path.parent, "../..")), - capture_output=True, - ) - assert proc.returncode == 0 - output = proc.stdout.decode("utf-8") - assert "collected 1 item" in output - assert "pytest-ansible/molecule/default/molecule.yml::test" in output - assert "1 passed" in output - - # Add test result - expected_output = "EXPECTED_OUTPUT" - if output != expected_output: - pytest.fail( - f"Test output does not match the expected output.\n\n" - f"Expected:\n{expected_output}\n\n" - f"Actual:\n{output}", - ) - - -def test_collect(mocker: MockerFixture): - mocker.patch("pytest_ansible.molecule.MoleculeItem.from_parent") - mocker.patch("pytest_ansible.molecule.MoleculeItem.__init__") - mocker.patch("pytest_ansible.molecule.MoleculeItem.__str__") - mocker.patch("pytest_ansible.molecule.MoleculeFile.path", Mock()) - - # Test when MoleculeItem.from_parent exists - MoleculeItem.from_parent.return_value = "mocked_item" - molecule_file = MoleculeFile() - items = list(molecule_file.collect()) - assert items == ["mocked_item"] - - # Test when MoleculeItem.from_parent does not exist - MoleculeItem.from_parent.side_effect = AttributeError - mocker.patch.object(MoleculeItem, "__new__", return_value="mocked_item") - - molecule_file = MoleculeFile() - items = list(molecule_file.collect()) - assert items == ["mocked_item"] - - mocker.patch("subprocess.run") - subprocess.run.return_value = subprocess.CompletedProcess( - args="pytest --collect-only", - returncode=0, - stdout=b"1 test collected\ntest[delegated]\n", - stderr=b"", - ) - - proc = subprocess.run( - "pytest --collect-only", - shell=True, - check=True, - capture_output=True, - ) - assert proc.returncode == 0 - output = proc.stdout.decode("utf-8") - assert "1 test collected" in output - assert "test[delegated]" in output - - -if __name__ == "__main__": - pytest.main(["-v", __file__]) diff --git a/tests/moleculetest/__init__.py b/tests/moleculetest/__init__.py new file mode 100644 index 00000000..f36dd28b --- /dev/null +++ b/tests/moleculetest/__init__.py @@ -0,0 +1 @@ +"""Molecule Tests""" diff --git a/tests/moleculetest/test_molecule.py b/tests/moleculetest/test_molecule.py new file mode 100644 index 00000000..57b8db93 --- /dev/null +++ b/tests/moleculetest/test_molecule.py @@ -0,0 +1,115 @@ +"""Tests specific to the molecule plugin functionality.""" + +from __future__ import annotations + +import logging +import os +import subprocess +import sys +from pathlib import Path +from unittest.mock import MagicMock + +import pytest +from pytest_ansible.molecule import MoleculeFile, MoleculeItem + + +def test_molecule_collect( + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, + caplog: pytest.LogCaptureFixture, +) -> None: + """Test collecting a molecule.yml file. + + :param monkeypatch: The pytest monkeypatch fixture + :param tmp_path: The pytest tmp_path fixture + :param caplog: The pytest caplog fixture + """ + caplog.set_level(logging.DEBUG) + + # Create a temporary molecule file for testing + molecule_file_path = ( + tmp_path / "pytest-ansible" / "molecule" / "default" / "molecule.yml" + ) + molecule_file_path.parent.mkdir(parents=True, exist_ok=True) + molecule_file_path.write_text("test content") + + # Create the MoleculeFile object for the temporary file + molecule_file = MoleculeFile(molecule_file_path, parent=None) + + # Mock MoleculeItem object for testing + mocked_item = MagicMock() + monkeypatch.setattr( + MoleculeItem, + "from_parent", + MagicMock(return_value=mocked_item), + ) + + # Call the collect() method and convert the generator into a list to check its content + collected_items = list(molecule_file.collect()) + + # Clean up the temporary file and directory + molecule_file_path.unlink() + molecule_file_path.parent.rmdir() + + try: + proc = subprocess.run( + "pytest --collect-only", + capture_output=True, + shell=True, + check=True, + text=True, + ) + except subprocess.CalledProcessError as exc: + print(exc.stdout) + print(exc.stderr) + pytest.fail(exc.stderr) + + assert proc.returncode == 0 + output = proc.stdout.decode("utf-8") + assert "1 test collected" in output + assert "test[delegated]" in output + assert len(collected_items) == 1 + assert collected_items[0] == mocked_item + + +def test_molecule_runtest( + monkeypatch: pytest.MonkeyPatch, + capsys: pytest.LogCaptureFixture, +) -> None: + """Test running the file. + + :param monkeypatch: The pytest monkeypatch fixture + :param tmp_path: The pytest tmp_path fixture + :param capsys: The pytest capsys fixture + """ + # Test runtest() function in MoleculeItem + # Mock necessary attributes and environment variables + monkeypatch.setattr(subprocess, "Popen", MagicMock()) + monkeypatch.setenv("MOLECULE_OPTS", "--driver-name mock_driver") + monkeypatch.setattr(sys, "executable", "/path/to/python") + monkeypatch.setattr(os, "getcwd", MagicMock(return_value="/path/to")) + + molecule_item = MoleculeItem("test", parent=None) + + # Run the runtest() function + molecule_item.runtest() + + proc = subprocess.run( + f"{sys.executable} -m pytest molecule.yml -v", + shell=True, + check=True, + cwd="/home/ruchi/pytest-ansible/molecule/default/", + capture_output=True, + ) + assert proc.returncode == 0 + output = proc.stdout.decode("utf-8") + assert "collected 1 item" in output + assert "pytest-ansible/molecule/default/molecule.yml::test" in output + assert "1 passed" in output + + # Capture the printed output and check if the command is correctly formed + captured = capsys.readouterr() + assert ( + captured.out.strip() + == "running: python -m molecule test -s scenario --driver-name mock_driver" + )