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:
Matt Clay 2019-09-09 18:32:29 -07:00 committed by GitHub
parent e3ea89801b
commit c77ab11051
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 56 additions and 23 deletions

View file

@ -0,0 +1,2 @@
bugfixes:
- ansible-test now properly handles creation of Python execv wrappers when the selected interpreter is a script

View file

@ -0,0 +1,2 @@
bugfixes:
- ansible-test now properly installs requirements for multiple Python versions when running sanity tests

View file

@ -0,0 +1,2 @@
bugfixes:
- ansible-test now properly activates virtual environments created using the --venv option

View file

@ -0,0 +1,2 @@
bugfixes:
- ansible-test now properly registers its own code in a virtual environment when running from an install

View file

@ -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):

View file

@ -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):

View file

@ -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()):