diff --git a/changelogs/fragments/ansible-test-egg-info-handling.yml b/changelogs/fragments/ansible-test-egg-info-handling.yml new file mode 100644 index 00000000000..c30ebd5de08 --- /dev/null +++ b/changelogs/fragments/ansible-test-egg-info-handling.yml @@ -0,0 +1,11 @@ +bugfixes: + - ansible-test - Running tests using an installed version of ``ansible-test`` against one Python version from another no longer fails + due to a missing ``egg-info`` directory. + This could occur when testing plugins which import ``pkg_resources``. + - ansible-test - Running tests using an installed version of ``ansible-test`` no longer generates an error attempting to create an ``egg-info`` directory + when an existing one is not found in the expected location. + This could occur if the existing ``egg-info`` directory included a Python version specifier in the name. +minor_changes: + - ansible-test - Generation of an ``egg-info`` directory, if needed, is now done after installing test dependencies and before running tests. + When running from an installed version of ``ansible-test`` a temporary directory is used to avoid permissions issues. + Previously it was done before installing test dependencies and adjacent to the installed directory. diff --git a/test/lib/ansible_test/_internal/ansible_util.py b/test/lib/ansible_test/_internal/ansible_util.py index 68298d92a12..339eff6908f 100644 --- a/test/lib/ansible_test/_internal/ansible_util.py +++ b/test/lib/ansible_test/_internal/ansible_util.py @@ -11,6 +11,10 @@ from .constants import ( SOFT_RLIMIT_NOFILE, ) +from .io import ( + write_text_file, +) + from .util import ( common_environment, display, @@ -20,6 +24,7 @@ from .util import ( ANSIBLE_TEST_DATA_ROOT, ANSIBLE_BIN_PATH, ANSIBLE_SOURCE_ROOT, + get_ansible_version, ) from .util_common import ( @@ -75,7 +80,7 @@ def ansible_environment(args, color=True, ansible_config=None): ANSIBLE_LIBRARY='/dev/null', ANSIBLE_DEVEL_WARNING='false', # Don't show warnings that CI is running devel ANSIBLE_CONTROLLER_PYTHON_WARNING='false', # Don't show warnings in CI for old controller Python - PYTHONPATH=get_ansible_python_path(), + PYTHONPATH=get_ansible_python_path(args), PAGER='/bin/cat', PATH=path, # give TQM worker processes time to report code coverage results @@ -171,28 +176,59 @@ def configure_plugin_paths(args): # type: (CommonConfig) -> t.Dict[str, str] return env -def get_ansible_python_path(): # type: () -> str +def get_ansible_python_path(args): # type: (CommonConfig) -> str """ Return a directory usable for PYTHONPATH, containing only the ansible package. If a temporary directory is required, it will be cached for the lifetime of the process and cleaned up at exit. """ - if ANSIBLE_SOURCE_ROOT: - # when running from source there is no need for a temporary directory to isolate the ansible package - return os.path.dirname(ANSIBLE_LIB_ROOT) - try: return get_ansible_python_path.python_path except AttributeError: pass - python_path = create_temp_dir(prefix='ansible-test-') - get_ansible_python_path.python_path = python_path + if ANSIBLE_SOURCE_ROOT: + # when running from source there is no need for a temporary directory to isolate the ansible package + python_path = os.path.dirname(ANSIBLE_LIB_ROOT) + else: + # when not running from source the installed directory is unsafe to add to PYTHONPATH + # doing so would expose many unwanted packages on sys.path + # instead a temporary directory is created which contains only ansible using a symlink + python_path = create_temp_dir(prefix='ansible-test-') + + os.symlink(ANSIBLE_LIB_ROOT, os.path.join(python_path, 'ansible')) + + if not args.explain: + generate_egg_info(python_path) - os.symlink(ANSIBLE_LIB_ROOT, os.path.join(python_path, 'ansible')) + get_ansible_python_path.python_path = python_path return python_path +def generate_egg_info(path): # type: (str) -> None + """Generate an egg-info in the specified base directory.""" + # minimal PKG-INFO stub following the format defined in PEP 241 + # required for older setuptools versions to avoid a traceback when importing pkg_resources from packages like cryptography + # newer setuptools versions are happy with an empty directory + # including a stub here means we don't need to locate the existing file or have setup.py generate it when running from source + pkg_info = ''' +Metadata-Version: 1.0 +Name: ansible +Version: %s +Platform: UNKNOWN +Summary: Radically simple IT automation +Author-email: info@ansible.com +License: GPLv3+ +''' % get_ansible_version() + + pkg_info_path = os.path.join(path, 'ansible_core.egg-info', 'PKG-INFO') + + if os.path.exists(pkg_info_path): + return + + write_text_file(pkg_info_path, pkg_info.lstrip(), create_directories=True) + + def check_pyyaml(args, version, required=True, quiet=False): """ :type args: EnvironmentConfig diff --git a/test/lib/ansible_test/_internal/executor.py b/test/lib/ansible_test/_internal/executor.py index 39c77017031..421354e24fc 100644 --- a/test/lib/ansible_test/_internal/executor.py +++ b/test/lib/ansible_test/_internal/executor.py @@ -298,8 +298,6 @@ def install_command_requirements(args, python_version=None, context=None, enable if args.raw: return - generate_egg_info(args) - if not args.requirements: return @@ -434,46 +432,6 @@ def pip_list(args, pip): return stdout -def generate_egg_info(args): - """ - :type args: EnvironmentConfig - """ - if args.explain: - return - - ansible_version = get_ansible_version() - - # inclusion of the version number in the path is optional - # see: https://setuptools.readthedocs.io/en/latest/formats.html#filename-embedded-metadata - egg_info_path = ANSIBLE_LIB_ROOT + '_core-%s.egg-info' % ansible_version - - if os.path.exists(egg_info_path): - return - - egg_info_path = ANSIBLE_LIB_ROOT + '_core.egg-info' - - if os.path.exists(egg_info_path): - return - - # minimal PKG-INFO stub following the format defined in PEP 241 - # required for older setuptools versions to avoid a traceback when importing pkg_resources from packages like cryptography - # newer setuptools versions are happy with an empty directory - # including a stub here means we don't need to locate the existing file or have setup.py generate it when running from source - pkg_info = ''' -Metadata-Version: 1.0 -Name: ansible -Version: %s -Platform: UNKNOWN -Summary: Radically simple IT automation -Author-email: info@ansible.com -License: GPLv3+ -''' % get_ansible_version() - - pkg_info_path = os.path.join(egg_info_path, 'PKG-INFO') - - write_text_file(pkg_info_path, pkg_info.lstrip(), create_directories=True) - - def generate_pip_install(pip, command, packages=None, constraints=None, use_constraints=True, context=None): """ :type pip: list[str]