diff --git a/testing/cffi0/test_zintegration.py b/testing/cffi0/test_zintegration.py index 20436913..38d911b6 100644 --- a/testing/cffi0/test_zintegration.py +++ b/testing/cffi0/test_zintegration.py @@ -4,72 +4,20 @@ import textwrap from pathlib import Path from testing.udir import udir +from testing.support import create_bridged_venv import pytest if sys.platform == 'win32': pytestmark = pytest.mark.skip('snippets do not run on win32') -if sys.version_info < (2, 7): - pytestmark = pytest.mark.skip( - 'fails e.g. on a Debian/Ubuntu which patches virtualenv' - ' in a non-2.6-friendly way') +try: + import setuptools +except ImportError: + pytestmark = pytest.mark.skip('the snippets need setuptools at build time') def create_venv(name): tmpdir = udir / name - try: - # FUTURE: we should probably update this to use venv for at least more modern Pythons, and - # install setuptools/pip/etc explicitly for the tests that require them (as venv has stopped including - # setuptools by default for newer versions). - subprocess.check_call(['virtualenv', - #'--never-download', <= could be added, but causes failures - # in random cases on random machines - '-p', os.path.abspath(sys.executable), - str(tmpdir)]) - - # Newer venv/virtualenv no longer include setuptools by default, which - # breaks a number of these tests; ensure they're always present - subprocess.check_call([ - str(tmpdir / 'bin/python'), - '-m', - 'pip', - 'install', - 'setuptools', - '--upgrade' - ]) - - except OSError as e: - pytest.skip("Cannot execute virtualenv: %s" % (e,)) - - site_packages = None - for dirpath, dirnames, filenames in os.walk(tmpdir): - if Path(dirpath).name == 'site-packages': - site_packages = dirpath - break - paths = "" - if site_packages: - try: - from cffi import _pycparser - modules = ('cffi', '_cffi_backend') - except ImportError: - modules = ('cffi', '_cffi_backend', 'pycparser') - try: - import ply - except ImportError: - pass - else: - modules += ('ply',) # needed for older versions of pycparser - paths = [] - for module in modules: - target = __import__(module, None, None, []) - if not hasattr(target, '__file__'): # for _cffi_backend on pypy - continue - src = os.path.abspath(target.__file__) - for end in ['__init__.pyc', '__init__.pyo', '__init__.py']: - if src.lower().endswith(end): - src = src[:-len(end)-1] - break - paths.append(os.path.dirname(src)) - paths = os.pathsep.join(paths) - return tmpdir, paths + create_bridged_venv(tmpdir) + return tmpdir, '' SNIPPET_DIR = Path(__file__).absolute().parent / 'snippets' diff --git a/testing/cffi1/test_cffi_gen_src_meson.py b/testing/cffi1/test_cffi_gen_src_meson.py index e3408cf5..ece55c1c 100644 --- a/testing/cffi1/test_cffi_gen_src_meson.py +++ b/testing/cffi1/test_cffi_gen_src_meson.py @@ -1,10 +1,11 @@ """End-to-end test: build a self-contained CFFI extension with meson-python. The test provisions a fresh nested venv under ``tmp_path`` using the -stdlib :mod:`venv` module, installs ``cffi`` (from the current source -tree) and ``meson-python`` into it, installs one of the small example -projects that live under ``testing/cffi1/cffi_gen_src_examples/``, and then -imports the built extension to confirm it works. +stdlib :mod:`venv` module, bridged so that it can import this +environment's ``cffi`` and ``meson-python`` (see +``testing.support.create_bridged_venv``), installs one of the small +example projects that live under ``testing/cffi1/cffi_gen_src_examples/``, +and then imports the built extension to confirm it works. """ @@ -19,6 +20,8 @@ import cffi +from testing.support import create_bridged_venv + pytestmark = [ pytest.mark.thread_unsafe(reason="spawns subprocesses, slow"), ] @@ -28,11 +31,15 @@ except ImportError: pytest.skip("Test requires meson-python", allow_module_level=True) +# meson needs the ninja binary at build time; it is not a Python-level +# dependency of meson-python. +if not (shutil.which("ninja") or shutil.which("ninja-build")): + pytest.skip("Test requires ninja", allow_module_level=True) + HERE = Path(__file__).resolve().parent EXAMPLE_PROJECT = HERE / "cffi_gen_src_examples" / "exec_python_example" EXAMPLE_PROJECT2 = HERE / "cffi_gen_src_examples" / "read_sources_example" -CFFI_DIR = HERE.parent.parent def _venv_python(venv_dir): @@ -44,39 +51,21 @@ def _venv_python(venv_dir): @pytest.mark.parametrize("project", [EXAMPLE_PROJECT, EXAMPLE_PROJECT2]) def test_meson_python_build(tmp_path, project): venv_dir = tmp_path / "venv" - subprocess.check_call([sys.executable, "-m", "venv", str(venv_dir)]) + create_bridged_venv(venv_dir) venv_python = _venv_python(venv_dir) assert venv_python.exists(), venv_python - # Upgrade pip so --no-build-isolation behaves consistently with recent - # resolver behaviour on older base images. - subprocess.check_call([ - str(venv_python), "-m", "pip", "install", "--upgrade", "pip", - ]) - - # Install build-time deps into the nested venv. - subprocess.check_call([ - str(venv_python), "-m", "pip", "install", "meson-python", CFFI_DIR - ]) - # Copy the example project so nothing is written back into the # source tree project_dir = tmp_path / "project" shutil.copytree(project, project_dir) - # The example meson.build files locate the codegen tool with - # find_program('cffi-gen-src'), which searches PATH. pip only puts - # an environment's scripts directory on PATH for isolated builds, - # so with --no-build-isolation the nested venv's script must be - # made findable by hand. - env = os.environ.copy() - env["PATH"] = str(venv_python.parent) + os.pathsep + env.get("PATH", "") - - # --no-build-isolation to ensure the test runs against the CFFI build we want to test + # --no-build-isolation so the build uses the bridged environment; + # --no-index/--no-deps so pip cannot touch the network. proc = subprocess.run([ str(venv_python), "-m", "pip", "install", "-v", - "--no-build-isolation", str(project_dir), - ], env=env, capture_output=True, text=True) + "--no-index", "--no-deps", "--no-build-isolation", str(project_dir), + ], capture_output=True, text=True) assert proc.returncode == 0, proc.stdout + proc.stderr # Confirm the built extension imports and behaves as expected. diff --git a/testing/support.py b/testing/support.py index 4f43c2f8..45aa03fb 100644 --- a/testing/support.py +++ b/testing/support.py @@ -1,4 +1,7 @@ import sys, os +import site, subprocess +from pathlib import Path +import pytest from cffi._imp_emulation import load_dynamic if sys.version_info < (3,): @@ -134,3 +137,28 @@ def typeof_disabled(*args, **kwds): extra_compile_args.append('-Wno-error=sign-conversion') del platform_tags + + +def create_bridged_venv(tmpdir): + """Create a venv at *tmpdir* that can import this environment's + packages, so tests can build and install projects into it without + downloading anything. + """ + try: + subprocess.check_call([sys.executable, '-m', 'venv', str(tmpdir)]) + except OSError as e: + pytest.skip("Cannot execute venv: %s" % (e,)) + + site_packages = None + for dirpath, dirnames, filenames in os.walk(tmpdir): + if Path(dirpath).name == 'site-packages': + site_packages = dirpath + break + if site_packages: + site_dirs = list(site.getsitepackages()) + user_site = site.getusersitepackages() + if user_site: + site_dirs.append(user_site) + pth = Path(site_packages) / '_outer_test_env.pth' + pth.write_text(''.join( + 'import site; site.addsitedir(%r)\n' % (d,) for d in site_dirs))