Fix issues with ansible-test --venv option. (#62033)
* Fix ansible-test venv activation. When using the ansible-test --venv option, an execv wrapper for each python interpreter is now used instead of a symbolic link. * Fix ansible-test execv wrapper generation. Use the currently running Python interpreter for the shebang in the execv wrapper instead of the selected interpreter. This allows the wrapper to work when the selected interpreter is a script instead of a binary. * Fix ansible-test sanity requirements install. When running sanity tests on multiple Python versions, install requirements for all versions used instead of only the default version. * Fix ansible-test --venv when installed. When running ansible-test from an install, the --venv delegation option needs to make sure the ansible-test code is available in the created virtual environment. Exposing system site packages does not work because the virtual environment may be for a different Python version than the one on which ansible-test is installed.
This commit is contained in:
parent
e3ea89801b
commit
c77ab11051
7 changed files with 56 additions and 23 deletions
|
@ -0,0 +1,2 @@
|
|||
bugfixes:
|
||||
- ansible-test now properly handles creation of Python execv wrappers when the selected interpreter is a script
|
|
@ -0,0 +1,2 @@
|
|||
bugfixes:
|
||||
- ansible-test now properly installs requirements for multiple Python versions when running sanity tests
|
2
changelogs/fragments/ansible-test-venv-activation.yml
Normal file
2
changelogs/fragments/ansible-test-venv-activation.yml
Normal file
|
@ -0,0 +1,2 @@
|
|||
bugfixes:
|
||||
- ansible-test now properly activates virtual environments created using the --venv option
|
2
changelogs/fragments/ansible-test-venv-pythonpath.yml
Normal file
2
changelogs/fragments/ansible-test-venv-pythonpath.yml
Normal file
|
@ -0,0 +1,2 @@
|
|||
bugfixes:
|
||||
- ansible-test now properly registers its own code in a virtual environment when running from an install
|
|
@ -48,12 +48,16 @@ from .util import (
|
|||
display,
|
||||
ANSIBLE_BIN_PATH,
|
||||
ANSIBLE_TEST_DATA_ROOT,
|
||||
ANSIBLE_LIB_ROOT,
|
||||
ANSIBLE_TEST_ROOT,
|
||||
tempdir,
|
||||
make_dirs,
|
||||
)
|
||||
|
||||
from .util_common import (
|
||||
run_command,
|
||||
ResultType,
|
||||
create_interpreter_wrapper,
|
||||
)
|
||||
|
||||
from .docker_util import (
|
||||
|
@ -244,7 +248,7 @@ def delegate_venv(args, # type: EnvironmentConfig
|
|||
|
||||
with tempdir() as inject_path:
|
||||
for version, path in venvs.items():
|
||||
os.symlink(os.path.join(path, 'bin', 'python'), os.path.join(inject_path, 'python%s' % version))
|
||||
create_interpreter_wrapper(os.path.join(path, 'bin', 'python'), os.path.join(inject_path, 'python%s' % version))
|
||||
|
||||
python_interpreter = os.path.join(inject_path, 'python%s' % args.python_version)
|
||||
|
||||
|
@ -255,11 +259,18 @@ def delegate_venv(args, # type: EnvironmentConfig
|
|||
cmd += ['--coverage-label', 'venv']
|
||||
|
||||
env = common_environment()
|
||||
env.update(
|
||||
PATH=inject_path + os.pathsep + env['PATH'],
|
||||
)
|
||||
|
||||
run_command(args, cmd, env=env)
|
||||
with tempdir() as library_path:
|
||||
# expose ansible and ansible_test to the virtual environment (only required when running from an install)
|
||||
os.symlink(ANSIBLE_LIB_ROOT, os.path.join(library_path, 'ansible'))
|
||||
os.symlink(ANSIBLE_TEST_ROOT, os.path.join(library_path, 'ansible_test'))
|
||||
|
||||
env.update(
|
||||
PATH=inject_path + os.pathsep + env['PATH'],
|
||||
PYTHONPATH=library_path,
|
||||
)
|
||||
|
||||
run_command(args, cmd, env=env)
|
||||
|
||||
|
||||
def delegate_docker(args, exclude, require, integration_targets):
|
||||
|
|
|
@ -89,8 +89,6 @@ def command_sanity(args):
|
|||
if args.delegate:
|
||||
raise Delegate(require=changes, exclude=args.exclude)
|
||||
|
||||
install_command_requirements(args)
|
||||
|
||||
tests = sanity_get_tests()
|
||||
|
||||
if args.test:
|
||||
|
@ -108,6 +106,8 @@ def command_sanity(args):
|
|||
total = 0
|
||||
failed = []
|
||||
|
||||
requirements_installed = set() # type: t.Set[str]
|
||||
|
||||
for test in tests:
|
||||
if args.list_tests:
|
||||
display.info(test.name)
|
||||
|
@ -180,6 +180,10 @@ def command_sanity(args):
|
|||
sanity_targets = SanityTargets(tuple(all_targets), tuple(usable_targets))
|
||||
|
||||
if usable_targets or test.no_targets:
|
||||
if version not in requirements_installed:
|
||||
requirements_installed.add(version)
|
||||
install_command_requirements(args, version)
|
||||
|
||||
if isinstance(test, SanityCodeSmellTest):
|
||||
result = test.test(args, sanity_targets, version)
|
||||
elif isinstance(test, SanityMultipleVersion):
|
||||
|
|
|
@ -7,6 +7,7 @@ import contextlib
|
|||
import json
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
import textwrap
|
||||
|
||||
|
@ -204,22 +205,7 @@ def get_python_path(args, interpreter):
|
|||
else:
|
||||
display.info('Injecting "%s" as a execv wrapper for the "%s" interpreter.' % (injected_interpreter, interpreter), verbosity=1)
|
||||
|
||||
code = textwrap.dedent('''
|
||||
#!%s
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from os import execv
|
||||
from sys import argv
|
||||
|
||||
python = '%s'
|
||||
|
||||
execv(python, [python] + argv[1:])
|
||||
''' % (interpreter, interpreter)).lstrip()
|
||||
|
||||
write_text_file(injected_interpreter, code)
|
||||
|
||||
os.chmod(injected_interpreter, MODE_FILE_EXECUTE)
|
||||
create_interpreter_wrapper(interpreter, injected_interpreter)
|
||||
|
||||
os.chmod(python_path, MODE_DIRECTORY)
|
||||
|
||||
|
@ -231,6 +217,30 @@ def get_python_path(args, interpreter):
|
|||
return python_path
|
||||
|
||||
|
||||
def create_interpreter_wrapper(interpreter, injected_interpreter): # type: (str, str) -> None
|
||||
"""Create a wrapper for the given Python interpreter at the specified path."""
|
||||
# sys.executable is used for the shebang to guarantee it is a binary instead of a script
|
||||
# injected_interpreter could be a script from the system or our own wrapper created for the --venv option
|
||||
shebang_interpreter = sys.executable
|
||||
|
||||
code = textwrap.dedent('''
|
||||
#!%s
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from os import execv
|
||||
from sys import argv
|
||||
|
||||
python = '%s'
|
||||
|
||||
execv(python, [python] + argv[1:])
|
||||
''' % (shebang_interpreter, interpreter)).lstrip()
|
||||
|
||||
write_text_file(injected_interpreter, code)
|
||||
|
||||
os.chmod(injected_interpreter, MODE_FILE_EXECUTE)
|
||||
|
||||
|
||||
def cleanup_python_paths():
|
||||
"""Clean up all temporary python directories."""
|
||||
for path in sorted(PYTHON_PATHS.values()):
|
||||
|
|
Loading…
Reference in a new issue