-
Notifications
You must be signed in to change notification settings - Fork 654
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Run in an un-activated virtual environment, fixes #1507 #1866
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
"""Utilities supporting virtual environments.""" | ||
|
||
# We run various ansible command line tools (and detect the ansible version | ||
# by executing `ansible --version`). | ||
# The executable we want to run may not be in $PATH, especially if we | ||
# are running in a virtual environment that's not been activated. | ||
|
||
import os | ||
import pathlib | ||
import subprocess | ||
import sys | ||
from functools import lru_cache | ||
from typing import TYPE_CHECKING, Any, List | ||
|
||
if TYPE_CHECKING: | ||
# https://github.com/PyCQA/pylint/issues/3240 | ||
# pylint: disable=unsubscriptable-object | ||
CompletedProcess = subprocess.CompletedProcess[Any] | ||
else: | ||
CompletedProcess = subprocess.CompletedProcess | ||
|
||
|
||
def _normalize(path: str) -> pathlib.Path: | ||
# pathlib.Path takes care of MS Windows case-insensitivity | ||
return pathlib.Path(os.path.realpath(os.path.normpath(os.path.expanduser(path)))) | ||
|
||
|
||
def _get_search_path() -> List[pathlib.Path]: | ||
return [_normalize(path) for path in os.get_exec_path()] | ||
|
||
|
||
@lru_cache() | ||
def _get_venv_path() -> str: | ||
venv_dir = _normalize(os.path.dirname(sys.executable)) | ||
search_path = _get_search_path() | ||
if venv_dir in search_path or search_path and search_path[0] == pathlib.Path("."): | ||
return "" | ||
return str(venv_dir) | ||
|
||
|
||
def add_venv_path(cmd: str) -> str: | ||
"""Prepend the path to the venv, if any, to the supplied command.""" | ||
return os.path.join(_get_venv_path(), cmd) | ||
|
||
|
||
def run_ansible_version() -> CompletedProcess: | ||
"""Run `ansible --version` and return the result.""" | ||
return subprocess.run( | ||
args=[add_venv_path("ansible"), "--version"], | ||
stdout=subprocess.PIPE, | ||
stderr=subprocess.PIPE, | ||
universal_newlines=True, | ||
check=False, | ||
) |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,7 +1,9 @@ | ||||||
"""Tests related to prerun part of the linter.""" | ||||||
import os | ||||||
import re | ||||||
import subprocess | ||||||
from typing import List | ||||||
import sys | ||||||
from typing import Callable, List | ||||||
|
||||||
import pytest | ||||||
from _pytest.monkeypatch import MonkeyPatch | ||||||
|
@@ -11,35 +13,113 @@ | |||||
from ansiblelint.constants import INVALID_PREREQUISITES_RC | ||||||
from ansiblelint.testing import run_ansible_lint | ||||||
|
||||||
EXECDIR = os.path.dirname(sys.executable) | ||||||
|
||||||
Deactivator = Callable[[bool], str] | ||||||
DeactivateVenv = Callable[[], Deactivator] | ||||||
|
||||||
|
||||||
@pytest.fixture | ||||||
def deactivate_venv(monkeypatch: MonkeyPatch) -> Deactivator: | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like the use of Assuming you're worried/don't know how this works with |
||||||
"""Deliver a function to deactivate the current venv, if any, if requested, by removing it's bin dir from $PATH.""" | ||||||
|
||||||
def deactiveator(deactivate: bool) -> str: | ||||||
if deactivate: | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pro tip: this clause can be turned into a "guard expression" by flipping the check. Use |
||||||
search_path = os.get_exec_path() | ||||||
try: | ||||||
pos = search_path.index(EXECDIR) | ||||||
except ValueError: | ||||||
return "" | ||||||
del search_path[pos] | ||||||
monkeypatch.setenv("PATH", ";".join(search_path)) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe this meant to be
Suggested change
*nix separator is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @webknjaz And you know very well that ansible-lint does not run on Windows. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know but it's still good practice not to hardcode things regardless. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And your suggestion is misleading as A simple dot... totally different outcome. |
||||||
return EXECDIR | ||||||
return "" | ||||||
|
||||||
return deactiveator | ||||||
|
||||||
|
||||||
# https://github.com/box/flaky/issues/170 | ||||||
@flaky(max_runs=3) # type: ignore | ||||||
def test_prerun_reqs_v1() -> None: | ||||||
@pytest.mark.parametrize( | ||||||
("deactivate"), | ||||||
( | ||||||
(False), | ||||||
(True), | ||||||
), | ||||||
ids=("venv activated", "venv deactivated"), | ||||||
) | ||||||
def test_prerun_reqs_v1(deactivate: bool, deactivate_venv: Deactivator) -> None: | ||||||
"""Checks that the linter can auto-install requirements v1 when found.""" | ||||||
cwd = os.path.realpath( | ||||||
os.path.join( | ||||||
os.path.dirname(os.path.realpath(__file__)), "..", "examples", "reqs_v1" | ||||||
) | ||||||
) | ||||||
execdir = deactivate_venv(deactivate) | ||||||
result = run_ansible_lint("-v", ".", cwd=cwd) | ||||||
assert "Running ansible-galaxy role install" in result.stderr, result.stderr | ||||||
|
||||||
ansible_galaxy = os.path.join(execdir, "ansible-galaxy") | ||||||
assert f"Running {ansible_galaxy} role install" in result.stderr, result.stderr | ||||||
assert ( | ||||||
"Running ansible-galaxy collection install" not in result.stderr | ||||||
f"Running {ansible_galaxy} collection install" not in result.stderr | ||||||
), result.stderr | ||||||
assert result.returncode == 0, result | ||||||
|
||||||
|
||||||
@flaky(max_runs=3) # type: ignore | ||||||
def test_prerun_reqs_v2() -> None: | ||||||
@pytest.mark.parametrize( | ||||||
("executable"), # Run with `python -m` or the venv command: issue #1507 | ||||||
( | ||||||
(None), | ||||||
(os.path.join(os.path.dirname(sys.executable), "ansible-lint")), | ||||||
), | ||||||
ids=( | ||||||
"run as ansiblelint python module", | ||||||
"run via path to the virtualenv's ansible-lint command", | ||||||
), | ||||||
) | ||||||
@pytest.mark.parametrize( | ||||||
("deactivate"), | ||||||
( | ||||||
(False), | ||||||
(True), | ||||||
), | ||||||
ids=("venv activated", "venv deactivated"), | ||||||
) | ||||||
def test_prerun_reqs_v2( | ||||||
executable: str, deactivate: bool, deactivate_venv: Deactivator | ||||||
) -> None: | ||||||
"""Checks that the linter can auto-install requirements v2 when found.""" | ||||||
cwd = os.path.realpath( | ||||||
os.path.join( | ||||||
os.path.dirname(os.path.realpath(__file__)), "..", "examples", "reqs_v2" | ||||||
) | ||||||
) | ||||||
result = run_ansible_lint("-v", ".", cwd=cwd) | ||||||
assert "Running ansible-galaxy role install" in result.stderr, result.stderr | ||||||
assert "Running ansible-galaxy collection install" in result.stderr, result.stderr | ||||||
execdir = deactivate_venv(deactivate) | ||||||
result = run_ansible_lint("-v", ".", cwd=cwd, executable=executable) | ||||||
|
||||||
# These first 2 assertations are written to pass both with the test | ||||||
# as-it-is before the fix of issue #1507 and after the fix. | ||||||
# (See: https://github.com/ansible-community/ansible-lint/issues/1507) | ||||||
# But these assertations are written as much like the "before" version | ||||||
# of this test as possible, to demonstrate that the assertion | ||||||
# itself is not substantively changed by the fix. | ||||||
# When these asserations fail, without the #1507 patch, | ||||||
# it demonstrates that ansible-lint does not run in a virtual environment | ||||||
# unless that environment is activated. | ||||||
assert re.search( | ||||||
"Running [^ ]*ansible-galaxy role install", result.stderr | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not do a more precise comparison with
Suggested change
? |
||||||
), result.stderr | ||||||
assert re.search( | ||||||
"Running [^ ]*ansible-galaxy collection install", result.stderr | ||||||
), result.stderr | ||||||
|
||||||
# Commands are given with or without paths, as expected. | ||||||
ansible_galaxy = os.path.join(execdir, "ansible-galaxy") | ||||||
assert f"Running {ansible_galaxy} role install" in result.stderr, result.stderr | ||||||
assert ( | ||||||
f"Running {ansible_galaxy} collection install" in result.stderr | ||||||
), result.stderr | ||||||
assert result.returncode == 0, result | ||||||
|
||||||
|
||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will fix the failing docs build: