diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index e0c79338..6a2194bf 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -38,8 +38,8 @@ jobs: pip install mypy - name: Install Delocate dependencies. run: | - pip install pytest - pip install -e . + pip install --requirement test-requirements.txt + pip install --editable . - name: MyPy uses: liskin/gh-problem-matcher-wrap@v2 with: diff --git a/delocate/tests/scriptrunner.py b/delocate/tests/scriptrunner.py deleted file mode 100644 index 92db11be..00000000 --- a/delocate/tests/scriptrunner.py +++ /dev/null @@ -1,167 +0,0 @@ -""" Module to help tests check script output - -Provides class to be instantiated in tests that check scripts. Usually works -something like this in a test module:: - - from .scriptrunner import ScriptRunner - runner = ScriptRunner() - -Then, in the tests, something like:: - - code, stdout, stderr = runner.run_command(['my-script', my_arg]) - assert_equal(code, 0) - assert_equal(stdout, b'This script ran OK') -""" -import os -import sys -from os.path import dirname, isdir, isfile -from os.path import join as pjoin -from os.path import pathsep, realpath -from subprocess import PIPE, Popen - - -def _get_package(): - """Workaround for missing ``__package__`` in Python 3.2""" - if "__package__" in globals() and __package__ is not None: - return __package__ - return __name__.split(".", 1)[0] - - -# Same as __package__ for Python 2.6, 2.7 and >= 3.3 -MY_PACKAGE = _get_package() - - -def local_script_dir(script_sdir): - """Get local script directory if running in development dir, else None""" - # Check for presence of scripts in development directory. ``realpath`` - # allows for the situation where the development directory has been linked - # into the path. - package_path = dirname(__import__(MY_PACKAGE).__file__) - above_us = realpath(pjoin(package_path, "..")) - devel_script_dir = pjoin(above_us, script_sdir) - if isfile(pjoin(above_us, "setup.py")) and isdir(devel_script_dir): - return devel_script_dir - return None - - -def local_module_dir(module_name): - """Get local module directory if running in development dir, else None""" - mod = __import__(module_name) - containing_path = dirname(dirname(realpath(mod.__file__))) - if containing_path == realpath(os.getcwd()): - return containing_path - return None - - -class ScriptRunner(object): - """Class to run scripts and return output - - Finds local scripts and local modules if running in the development - directory, otherwise finds system scripts and modules. - """ - - def __init__( - self, - script_sdir="scripts", - module_sdir=MY_PACKAGE, - debug_print_var=None, - output_processor=lambda x: x, - ): - """Init ScriptRunner instance - - Parameters - ---------- - script_sdir : str, optional - Name of subdirectory in top-level directory (directory containing - setup.py), to find scripts in development tree. Typically - 'scripts', but might be 'bin'. - module_sdir : str, optional - Name of subdirectory in top-level directory (directory containing - setup.py), to find main package directory. - debug_print_vsr : str, optional - Name of environment variable that indicates whether to do debug - printing or no. - output_processor : callable - Callable to run on the stdout, stderr outputs before returning - them. Use this to convert bytes to unicode, strip whitespace, etc. - """ - self.local_script_dir = local_script_dir(script_sdir) - self.local_module_dir = local_module_dir(module_sdir) - if debug_print_var is None: - debug_print_var = "{0}_DEBUG_PRINT".format(module_sdir.upper()) - self.debug_print = os.environ.get(debug_print_var, False) - self.output_processor = output_processor - - def run_command(self, cmd, check_code=True): - """Run command sequence `cmd` returning exit code, stdout, stderr - - Parameters - ---------- - cmd : str or sequence - string with command name or sequence of strings defining command - check_code : {True, False}, optional - If True, raise error for non-zero return code - - Returns - ------- - returncode : int - return code from execution of `cmd` - stdout : bytes (python 3) or str (python 2) - stdout from `cmd` - stderr : bytes (python 3) or str (python 2) - stderr from `cmd` - """ - if isinstance(cmd, str): - cmd = [cmd] - else: - cmd = list(cmd) - if self.local_script_dir is not None: - # Windows can't run script files without extensions natively - # so we need to run local scripts (no extensions) via the - # Python interpreter. On Unix, we might have the wrong incantation - # for the Python interpreter in the hash bang first line in the - # source file. So, either way, run the script through the - # Python interpreter - cmd = [sys.executable, pjoin(self.local_script_dir, cmd[0])] + cmd[ - 1: - ] - elif os.name == "nt": - # Need .bat file extension for windows - cmd[0] += ".bat" - if os.name == "nt": - # Quote any arguments with spaces. The quotes delimit the arguments - # on Windows, and the arguments might be files paths with spaces. - # On Unix the list elements are each separate arguments. - cmd = ['"{0}"'.format(c) if " " in c else c for c in cmd] - if self.debug_print: - print("Running command '%s'" % cmd) - env = os.environ - if self.local_module_dir is not None: - # module likely comes from the current working directory. - # We might need that directory on the path if we're running - # the scripts from a temporary directory - env = env.copy() - pypath = env.get("PYTHONPATH", None) - if pypath is None: - env["PYTHONPATH"] = self.local_module_dir - else: - env["PYTHONPATH"] = self.local_module_dir + pathsep + pypath - proc = Popen(cmd, stdout=PIPE, stderr=PIPE, env=env) - stdout, stderr = proc.communicate() - if proc.poll() is None: - proc.terminate() - if check_code and proc.returncode != 0: - raise RuntimeError( - """Command "{0}" failed with - stdout - ------ - {1} - stderr - ------ - {2} - """.format( - cmd, stdout, stderr - ) - ) - opp = self.output_processor - return proc.returncode, opp(stdout), opp(stderr) diff --git a/delocate/tests/test_scripts.py b/delocate/tests/test_scripts.py index ab2d66c3..a99008a9 100644 --- a/delocate/tests/test_scripts.py +++ b/delocate/tests/test_scripts.py @@ -6,24 +6,24 @@ the top-level folder ``scripts``. Otherwise try and get the scripts from the path """ -from __future__ import absolute_import, division, print_function +from __future__ import annotations import os import shutil import subprocess import sys -from os.path import abspath, basename, dirname, exists, isfile +from os.path import basename, exists from os.path import join as pjoin from os.path import realpath, splitext +from pathlib import Path from typing import Text import pytest +from pytest_console_scripts import ScriptRunner from ..tmpdirs import InGivenDirectory, InTemporaryDirectory from ..tools import dir2zip, set_install_name, zip2dir from ..wheeltools import InWheel -from .pytest_tools import assert_equal, assert_false, assert_raises, assert_true -from .scriptrunner import ScriptRunner from .test_delocating import _copy_to, _make_bare_depends, _make_libtree from .test_fuse import assert_same_tree from .test_install_names import EXT_LIBS @@ -45,16 +45,16 @@ assert_winfo_similar, ) +DATA_PATH = (Path(__file__).parent / "data").resolve(strict=True) -def _proc_lines(in_str): - """Decode `in_string` to str, split lines, strip whitespace - Remove any empty lines. +def _proc_lines(in_str: str) -> list[str]: + """Return input split across lines, striping whitespace, without blanks. Parameters ---------- - in_str : bytes - Input bytes for splitting, stripping + in_str : str + Input for splitting, stripping Returns ------- @@ -62,20 +62,14 @@ def _proc_lines(in_str): List of line ``str`` where each line has been stripped of leading and trailing whitespace and empty lines have been removed. """ - lines = in_str.decode("latin1").splitlines() + lines = in_str.splitlines() return [line.strip() for line in lines if line.strip() != ""] -lines_runner = ScriptRunner(output_processor=_proc_lines) -run_command = lines_runner.run_command -bytes_runner = ScriptRunner() - - -DATA_PATH = abspath(pjoin(dirname(__file__), "data")) - - -@pytest.mark.xfail(sys.platform != "darwin", reason="Needs macOS linkage.") -def test_listdeps(plat_wheel: PlatWheel) -> None: +@pytest.mark.xfail( # type: ignore[misc] + sys.platform != "darwin", reason="Needs macOS linkage." +) +def test_listdeps(plat_wheel: PlatWheel, script_runner: ScriptRunner) -> None: # smokey tests of list dependencies command local_libs = { "liba.dylib", @@ -85,75 +79,83 @@ def test_listdeps(plat_wheel: PlatWheel) -> None: } # single path, with libs with InGivenDirectory(DATA_PATH): - code, stdout, stderr = run_command(["delocate-listdeps", DATA_PATH]) - assert set(stdout) == local_libs - assert code == 0 + result = script_runner.run( + "delocate-listdeps", str(DATA_PATH), check=True + ) + assert set(_proc_lines(result.stdout)) == local_libs # single path, no libs with InTemporaryDirectory(): zip2dir(PURE_WHEEL, "pure") - code, stdout, stderr = run_command(["delocate-listdeps", "pure"]) - assert set(stdout) == set() - assert code == 0 + result = script_runner.run(["delocate-listdeps", "pure"], check=True) + assert result.stdout.strip() == "" + # Multiple paths one with libs zip2dir(plat_wheel.whl, "plat") - code, stdout, stderr = run_command( - ["delocate-listdeps", "pure", "plat"] + result = script_runner.run( + ["delocate-listdeps", "pure", "plat"], check=True ) - assert stdout == ["pure:", "plat:", plat_wheel.stray_lib] - assert code == 0 + assert _proc_lines(result.stdout) == [ + "pure:", + "plat:", + plat_wheel.stray_lib, + ] + # With -d flag, get list of dependending modules - code, stdout, stderr = run_command( - ["delocate-listdeps", "-d", "pure", "plat"] + result = script_runner.run( + ["delocate-listdeps", "-d", "pure", "plat"], check=True ) - assert stdout == [ + assert _proc_lines(result.stdout) == [ "pure:", "plat:", plat_wheel.stray_lib + ":", - pjoin("plat", "fakepkg1", "subpkg", "module2.abi3.so"), + str(Path("plat", "fakepkg1", "subpkg", "module2.abi3.so")), ] - assert code == 0 # With --all flag, get all dependencies with InGivenDirectory(DATA_PATH): - code, stdout, stderr = run_command( - ["delocate-listdeps", "--all", DATA_PATH] + result = script_runner.run( + ["delocate-listdeps", "--all", DATA_PATH], check=True ) rp_ext_libs = set(realpath(L) for L in EXT_LIBS) - assert set(stdout) == local_libs | rp_ext_libs - assert code == 0 + assert set(_proc_lines(result.stdout)) == local_libs | rp_ext_libs + # Works on wheels as well - code, stdout, stderr = run_command(["delocate-listdeps", PURE_WHEEL]) - assert set(stdout) == set() - code, stdout, stderr = run_command( - ["delocate-listdeps", PURE_WHEEL, plat_wheel.whl] + result = script_runner.run(["delocate-listdeps", PURE_WHEEL], check=True) + assert result.stdout.strip() == "" + result = script_runner.run( + ["delocate-listdeps", PURE_WHEEL, plat_wheel.whl], check=True ) - assert stdout == [ + assert _proc_lines(result.stdout) == [ PURE_WHEEL + ":", plat_wheel.whl + ":", plat_wheel.stray_lib, ] + # -d flag (is also --dependency flag) m2 = pjoin("fakepkg1", "subpkg", "module2.abi3.so") - code, stdout, stderr = run_command( - ["delocate-listdeps", "--depending", PURE_WHEEL, plat_wheel.whl] + result = script_runner.run( + ["delocate-listdeps", "--depending", PURE_WHEEL, plat_wheel.whl], + check=True, ) - assert stdout == [ + assert _proc_lines(result.stdout) == [ PURE_WHEEL + ":", plat_wheel.whl + ":", plat_wheel.stray_lib + ":", m2, ] + # Can be used with --all - code, stdout, stderr = run_command( + result = script_runner.run( [ "delocate-listdeps", "--all", "--depending", PURE_WHEEL, plat_wheel.whl, - ] + ], + check=True, ) - assert stdout == [ + assert _proc_lines(result.stdout) == [ PURE_WHEEL + ":", plat_wheel.whl + ":", plat_wheel.stray_lib + ":", @@ -164,8 +166,10 @@ def test_listdeps(plat_wheel: PlatWheel) -> None: ] -@pytest.mark.xfail(sys.platform != "darwin", reason="Runs macOS executable.") -def test_path() -> None: +@pytest.mark.xfail( # type: ignore[misc] + sys.platform != "darwin", reason="Runs macOS executable." +) +def test_path(script_runner: ScriptRunner) -> None: # Test path cleaning with InTemporaryDirectory(): # Make a tree; use realpath for OSX /private/var - /var @@ -182,89 +186,97 @@ def test_path() -> None: subprocess.run([stest_lib], check=True) set_install_name(slibc, EXT_LIBS[0], fake_lib) # Check it fixes up correctly - code, stdout, stderr = run_command( - ["delocate-path", "subtree", "subtree2", "-L", "deplibs"] + script_runner.run( + ["delocate-path", "subtree", "subtree2", "-L", "deplibs"], + check=True, ) - assert len(os.listdir(pjoin("subtree", "deplibs"))) == 0 + assert len(os.listdir(Path("subtree", "deplibs"))) == 0 # Check fake libary gets copied and delocated - out_path = pjoin("subtree2", "deplibs") + out_path = Path("subtree2", "deplibs") assert os.listdir(out_path) == ["libfake.dylib"] -@pytest.mark.xfail(sys.platform != "darwin", reason="Needs macOS linkage.") -def test_path_dylibs(): +@pytest.mark.xfail( # type: ignore[misc] + sys.platform != "darwin", reason="Needs macOS linkage." +) +def test_path_dylibs(script_runner: ScriptRunner) -> None: # Test delocate-path with and without dylib extensions with InTemporaryDirectory(): # With 'dylibs-only' - does not inspect non-dylib files liba, bare_b = _make_bare_depends() - out_dypath = pjoin("subtree", "deplibs") - code, stdout, stderr = run_command( - ["delocate-path", "subtree", "-L", "deplibs", "-d"] + out_dypath = Path("subtree", "deplibs") + script_runner.run( + ["delocate-path", "subtree", "-L", "deplibs", "-d"], check=True ) - assert_equal(len(os.listdir(out_dypath)), 0) - code, stdout, stderr = run_command( - ["delocate-path", "subtree", "-L", "deplibs", "--dylibs-only"] + assert len(os.listdir(out_dypath)) == 0 + script_runner.run( + ["delocate-path", "subtree", "-L", "deplibs", "--dylibs-only"], + check=True, ) - assert_equal(len(os.listdir(pjoin("subtree", "deplibs"))), 0) + assert len(os.listdir(Path("subtree", "deplibs"))) == 0 # Default - does inspect non-dylib files - code, stdout, stderr = run_command( - ["delocate-path", "subtree", "-L", "deplibs"] + script_runner.run( + ["delocate-path", "subtree", "-L", "deplibs"], check=True ) - assert_equal(os.listdir(out_dypath), ["liba.dylib"]) + assert os.listdir(out_dypath) == ["liba.dylib"] -def _check_wheel(wheel_fname, lib_sdir): - wheel_fname = abspath(wheel_fname) +def _check_wheel(wheel_fname: str | Path, lib_sdir: str | Path) -> None: + wheel_fname = Path(wheel_fname).resolve(strict=True) with InTemporaryDirectory(): - zip2dir(wheel_fname, "plat_pkg") - dylibs = pjoin("plat_pkg", "fakepkg1", lib_sdir) - assert_true(exists(dylibs)) - assert_equal(os.listdir(dylibs), ["libextfunc.dylib"]) + zip2dir(str(wheel_fname), "plat_pkg") + dylibs = Path("plat_pkg", "fakepkg1", lib_sdir) + assert dylibs.exists() + assert os.listdir(dylibs) == ["libextfunc.dylib"] -@pytest.mark.xfail(sys.platform != "darwin", reason="Needs macOS linkage.") -def test_wheel(): +@pytest.mark.xfail( # type: ignore[misc] + sys.platform != "darwin", reason="Needs macOS linkage." +) +def test_wheel(script_runner: ScriptRunner) -> None: # Some tests for wheel fixing with InTemporaryDirectory() as tmpdir: # Default in-place fix fixed_wheel, stray_lib = _fixed_wheel(tmpdir) - code, stdout, stderr = run_command(["delocate-wheel", fixed_wheel]) + script_runner.run(["delocate-wheel", fixed_wheel], check=True) _check_wheel(fixed_wheel, ".dylibs") # Make another copy to test another output directory fixed_wheel, stray_lib = _fixed_wheel(tmpdir) - code, stdout, stderr = run_command( - ["delocate-wheel", "-L", "dynlibs_dir", fixed_wheel] + script_runner.run( + ["delocate-wheel", "-L", "dynlibs_dir", fixed_wheel], check=True ) _check_wheel(fixed_wheel, "dynlibs_dir") # Another output directory fixed_wheel, stray_lib = _fixed_wheel(tmpdir) - code, stdout, stderr = run_command( - ["delocate-wheel", "-w", "fixed", fixed_wheel] + script_runner.run( + ["delocate-wheel", "-w", "fixed", fixed_wheel], check=True ) - _check_wheel(pjoin("fixed", basename(fixed_wheel)), ".dylibs") + _check_wheel(Path("fixed", basename(fixed_wheel)), ".dylibs") # More than one wheel shutil.copy2(fixed_wheel, "wheel_copy.ext") - code, stdout, stderr = run_command( - ["delocate-wheel", "-w", "fixed2", fixed_wheel, "wheel_copy.ext"] - ) - assert_equal( - stdout, - ["Fixing: " + name for name in (fixed_wheel, "wheel_copy.ext")], + result = script_runner.run( + ["delocate-wheel", "-w", "fixed2", fixed_wheel, "wheel_copy.ext"], + check=True, ) - _check_wheel(pjoin("fixed2", basename(fixed_wheel)), ".dylibs") - _check_wheel(pjoin("fixed2", "wheel_copy.ext"), ".dylibs") + assert _proc_lines(result.stdout) == [ + "Fixing: " + name for name in (fixed_wheel, "wheel_copy.ext") + ] + _check_wheel(Path("fixed2", basename(fixed_wheel)), ".dylibs") + _check_wheel(Path("fixed2", "wheel_copy.ext"), ".dylibs") + # Verbose - single wheel - code, stdout, stderr = run_command( - ["delocate-wheel", "-w", "fixed3", fixed_wheel, "-v"] + result = script_runner.run( + ["delocate-wheel", "-w", "fixed3", fixed_wheel, "-v"], check=True ) - _check_wheel(pjoin("fixed3", basename(fixed_wheel)), ".dylibs") + _check_wheel(Path("fixed3", basename(fixed_wheel)), ".dylibs") wheel_lines1 = [ "Fixing: " + fixed_wheel, "Copied to package .dylibs directory:", stray_lib, ] - assert_equal(stdout, wheel_lines1) - code, stdout, stderr = run_command( + assert _proc_lines(result.stdout) == wheel_lines1 + + result = script_runner.run( [ "delocate-wheel", "-v", @@ -272,18 +284,21 @@ def test_wheel(): "fixed4", fixed_wheel, "wheel_copy.ext", - ] + ], + check=True, ) wheel_lines2 = [ "Fixing: wheel_copy.ext", "Copied to package .dylibs directory:", stray_lib, ] - assert_equal(stdout, wheel_lines1 + wheel_lines2) + assert _proc_lines(result.stdout) == wheel_lines1 + wheel_lines2 -@pytest.mark.xfail(sys.platform != "darwin", reason="Needs macOS linkage.") -def test_fix_wheel_dylibs(): +@pytest.mark.xfail( # type: ignore[misc] + sys.platform != "darwin", reason="Needs macOS linkage." +) +def test_fix_wheel_dylibs(script_runner: ScriptRunner) -> None: # Check default and non-default search for dynamic libraries with InTemporaryDirectory() as tmpdir: # Default in-place fix @@ -291,26 +306,24 @@ def test_fix_wheel_dylibs(): _rename_module(fixed_wheel, "module.other", "test.whl") shutil.copyfile("test.whl", "test2.whl") # Default is to look in all files and therefore fix - code, stdout, stderr = run_command(["delocate-wheel", "test.whl"]) + script_runner.run(["delocate-wheel", "test.whl"], check=True) _check_wheel("test.whl", ".dylibs") # Can turn this off to only look in dynamic lib exts - code, stdout, stderr = run_command( - ["delocate-wheel", "test2.whl", "-d"] - ) + script_runner.run(["delocate-wheel", "test2.whl", "-d"], check=True) with InWheel("test2.whl"): # No fix - assert_false(exists(pjoin("fakepkg1", ".dylibs"))) + assert not Path("fakepkg1", ".dylibs").exists() -@pytest.mark.xfail(sys.platform != "darwin", reason="Needs macOS linkage.") -def test_fix_wheel_archs() -> None: +@pytest.mark.xfail( # type: ignore[misc] + sys.platform != "darwin", reason="Needs macOS linkage." +) +def test_fix_wheel_archs(script_runner: ScriptRunner) -> None: # Some tests for wheel fixing with InTemporaryDirectory() as tmpdir: # Test check of architectures fixed_wheel, stray_lib = _fixed_wheel(tmpdir) # Fixed wheel, architectures are OK - code, stdout, stderr = run_command( - ["delocate-wheel", fixed_wheel, "-k"] - ) + script_runner.run(["delocate-wheel", fixed_wheel, "-k"], check=True) _check_wheel(fixed_wheel, ".dylibs") # Broken with one architecture removed archs = set(("x86_64", "arm64")) @@ -327,133 +340,142 @@ def _fix_break_fix(arch: Text) -> None: for arch in archs: # Not checked _fix_break(arch) - code, stdout, stderr = run_command(["delocate-wheel", fixed_wheel]) + script_runner.run(["delocate-wheel", fixed_wheel], check=True) _check_wheel(fixed_wheel, ".dylibs") # Checked _fix_break(arch) - code, stdout, stderr = bytes_runner.run_command( - ["delocate-wheel", fixed_wheel, "--check-archs"], - check_code=False, + result = script_runner.run( + ["delocate-wheel", fixed_wheel, "--check-archs"] ) - assert_false(code == 0) - stderr_unicode = stderr.decode("latin1").strip() - assert stderr_unicode.startswith("Traceback") + assert result.returncode != 0 + assert result.stderr.startswith("Traceback") assert ( "DelocationError: Some missing architectures in wheel" - in stderr_unicode + in result.stderr ) - assert_equal(stdout.strip(), b"") + assert result.stdout.strip() == "" # Checked, verbose _fix_break(arch) - code, stdout, stderr = bytes_runner.run_command( - ["delocate-wheel", fixed_wheel, "--check-archs", "-v"], - check_code=False, + result = script_runner.run( + ["delocate-wheel", fixed_wheel, "--check-archs", "-v"] ) - assert_false(code == 0) - stderr = stderr.decode("latin1").strip() - assert "Traceback" in stderr - assert stderr.endswith( + assert result.returncode != 0 + assert "Traceback" in result.stderr + assert result.stderr.endswith( "DelocationError: Some missing architectures in wheel" f"\n{'fakepkg1/subpkg/module2.abi3.so'}" f" needs arch {archs.difference([arch]).pop()}" - f" missing from {stray_lib}" + f" missing from {stray_lib}\n" ) - stdout_unicode = stdout.decode("latin1").strip() - assert stdout_unicode == f"Fixing: {fixed_wheel}" + assert result.stdout == f"Fixing: {fixed_wheel}\n" # Require particular architectures both_archs = "arm64,x86_64" for ok in ("universal2", "arm64", "x86_64", both_archs): _fixed_wheel(tmpdir) - code, stdout, stderr = run_command( - ["delocate-wheel", fixed_wheel, "--require-archs=" + ok] + script_runner.run( + ["delocate-wheel", fixed_wheel, "--require-archs=" + ok], + check=True, ) for arch in archs: other_arch = archs.difference([arch]).pop() for not_ok in ("intel", both_archs, other_arch): _fix_break_fix(arch) - code, stdout, stderr = run_command( + result = script_runner.run( [ "delocate-wheel", fixed_wheel, "--require-archs=" + not_ok, ], - check_code=False, ) - assert_false(code == 0) + assert result.returncode != 0 -@pytest.mark.xfail(sys.platform == "win32", reason="Can't run scripts.") -def test_fuse_wheels(): +@pytest.mark.xfail( # type: ignore[misc] + sys.platform == "win32", reason="Can't run scripts." +) +def test_fuse_wheels(script_runner: ScriptRunner) -> None: # Some tests for wheel fusing with InTemporaryDirectory(): zip2dir(PLAT_WHEEL, "to_wheel") zip2dir(PLAT_WHEEL, "from_wheel") dir2zip("to_wheel", "to_wheel.whl") dir2zip("from_wheel", "from_wheel.whl") - code, stdout, stderr = run_command( - ["delocate-fuse", "to_wheel.whl", "from_wheel.whl"] + script_runner.run( + ["delocate-fuse", "to_wheel.whl", "from_wheel.whl"], check=True ) - assert_equal(code, 0) zip2dir("to_wheel.whl", "to_wheel_fused") assert_same_tree("to_wheel_fused", "from_wheel") # Test output argument os.mkdir("wheels") - code, stdout, stderr = run_command( - ["delocate-fuse", "to_wheel.whl", "from_wheel.whl", "-w", "wheels"] + script_runner.run( + ["delocate-fuse", "to_wheel.whl", "from_wheel.whl", "-w", "wheels"], + check=True, ) zip2dir(pjoin("wheels", "to_wheel.whl"), "to_wheel_refused") assert_same_tree("to_wheel_refused", "from_wheel") -@pytest.mark.xfail(sys.platform == "win32", reason="Can't run scripts.") -def test_patch_wheel(): +@pytest.mark.xfail( # type: ignore[misc] + sys.platform == "win32", reason="Can't run scripts." +) +def test_patch_wheel(script_runner: ScriptRunner) -> None: # Some tests for patching wheel with InTemporaryDirectory(): shutil.copyfile(PURE_WHEEL, "example.whl") # Default is to overwrite input - code, stdout, stderr = run_command( - ["delocate-patch", "example.whl", WHEEL_PATCH] + script_runner.run( + ["delocate-patch", "example.whl", WHEEL_PATCH], check=True ) zip2dir("example.whl", "wheel1") - with open(pjoin("wheel1", "fakepkg2", "__init__.py"), "rt") as fobj: - assert_equal(fobj.read(), 'print("Am in init")\n') + assert ( + Path("wheel1", "fakepkg2", "__init__.py").read_text() + == 'print("Am in init")\n' + ) # Pass output directory shutil.copyfile(PURE_WHEEL, "example.whl") - code, stdout, stderr = run_command( - ["delocate-patch", "example.whl", WHEEL_PATCH, "-w", "wheels"] + script_runner.run( + ["delocate-patch", "example.whl", WHEEL_PATCH, "-w", "wheels"], + check=True, ) zip2dir(pjoin("wheels", "example.whl"), "wheel2") - with open(pjoin("wheel2", "fakepkg2", "__init__.py"), "rt") as fobj: - assert_equal(fobj.read(), 'print("Am in init")\n') + assert ( + Path("wheel2", "fakepkg2", "__init__.py").read_text() + == 'print("Am in init")\n' + ) # Bad patch fails shutil.copyfile(PURE_WHEEL, "example.whl") - assert_raises( - RuntimeError, - run_command, - ["delocate-patch", "example.whl", WHEEL_PATCH_BAD], + result = script_runner.run( + ["delocate-patch", "example.whl", WHEEL_PATCH_BAD] ) + assert result.returncode != 0 -@pytest.mark.xfail(sys.platform == "win32", reason="Can't run scripts.") -def test_add_platforms() -> None: +@pytest.mark.xfail( # type: ignore[misc] + sys.platform == "win32", reason="Can't run scripts." +) +def test_add_platforms(script_runner: ScriptRunner) -> None: # Check adding platform to wheel name and tag section assert_winfo_similar(PLAT_WHEEL, EXP_ITEMS, drop_version=False) with InTemporaryDirectory() as tmpdir: # First wheel needs proper wheel filename for later unpack test out_fname = basename(PURE_WHEEL) # Need to specify at least one platform - with pytest.raises(RuntimeError): - run_command(["delocate-addplat", PURE_WHEEL, "-w", tmpdir]) - plat_args = ["-p", EXTRA_PLATS[0], "--plat-tag", EXTRA_PLATS[1]] + with pytest.raises(subprocess.CalledProcessError): + script_runner.run( + ["delocate-addplat", PURE_WHEEL, "-w", tmpdir], check=True + ) + plat_args = ("-p", EXTRA_PLATS[0], "--plat-tag", EXTRA_PLATS[1]) # Can't add platforms to a pure wheel - with pytest.raises(RuntimeError): - run_command( - ["delocate-addplat", PURE_WHEEL, "-w", tmpdir] + plat_args + with pytest.raises(subprocess.CalledProcessError): + script_runner.run( + ["delocate-addplat", PURE_WHEEL, "-w", tmpdir, *plat_args], + check=True, ) assert not exists(out_fname) # Error raised (as above) unless ``--skip-error`` flag set - code, stdout, stderr = run_command( - ["delocate-addplat", PURE_WHEEL, "-w", tmpdir, "-k"] + plat_args + script_runner.run( + ["delocate-addplat", PURE_WHEEL, "-w", tmpdir, "-k", *plat_args], + check=True, ) # Still doesn't do anything though assert not exists(out_fname) @@ -461,23 +483,27 @@ def test_add_platforms() -> None: out_fname = ".".join( (splitext(basename(PLAT_WHEEL))[0],) + EXTRA_PLATS + ("whl",) ) - code, stdout, stderr = run_command( - ["delocate-addplat", PLAT_WHEEL, "-w", tmpdir] + plat_args + script_runner.run( + ["delocate-addplat", PLAT_WHEEL, "-w", tmpdir, *plat_args], + check=True, ) - assert isfile(out_fname) + assert Path(out_fname).is_file() assert_winfo_similar(out_fname, EXTRA_EXPS) - # If wheel exists (as it does) then raise error - with pytest.raises(RuntimeError): - run_command( - ["delocate-addplat", PLAT_WHEEL, "-w", tmpdir] + plat_args + # If wheel exists (as it does) then fail + with pytest.raises(subprocess.CalledProcessError): + script_runner.run( + ["delocate-addplat", PLAT_WHEEL, "-w", tmpdir, *plat_args], + check=True, ) # Unless clobber is set - code, stdout, stderr = run_command( - ["delocate-addplat", PLAT_WHEEL, "-c", "-w", tmpdir] + plat_args + script_runner.run( + ["delocate-addplat", PLAT_WHEEL, "-c", "-w", tmpdir, *plat_args], + check=True, ) # Can also specify platform tags via --osx-ver flags - code, stdout, stderr = run_command( - ["delocate-addplat", PLAT_WHEEL, "-c", "-w", tmpdir, "-x", "10_9"] + script_runner.run( + ["delocate-addplat", PLAT_WHEEL, "-c", "-w", tmpdir, "-x", "10_9"], + check=True, ) assert_winfo_similar(out_fname, EXTRA_EXPS) # Can mix plat_tag and osx_ver @@ -491,7 +517,7 @@ def test_add_platforms() -> None: extra_big_exp = EXTRA_EXPS + [ ("Tag", "{pyver}-{abi}-" + plat) for plat in extra_extra ] - code, stdout, stderr = run_command( + script_runner.run( [ "delocate-addplat", PLAT_WHEEL, @@ -501,8 +527,9 @@ def test_add_platforms() -> None: "10_12", "-d", "universal2", - ] - + plat_args + *plat_args, + ], + check=True, ) assert_winfo_similar(out_big_fname, extra_big_exp) # Default is to write into directory of wheel @@ -510,14 +537,14 @@ def test_add_platforms() -> None: shutil.copy2(PLAT_WHEEL, "wheels") local_plat = pjoin("wheels", basename(PLAT_WHEEL)) local_out = pjoin("wheels", out_fname) - code, stdout, stderr = run_command( - ["delocate-addplat", local_plat] + plat_args + script_runner.run( + ["delocate-addplat", local_plat, *plat_args], check=True ) assert exists(local_out) # With rm_orig flag, delete original unmodified wheel os.unlink(local_out) - code, stdout, stderr = run_command( - ["delocate-addplat", "-r", local_plat] + plat_args + script_runner.run( + ["delocate-addplat", "-r", local_plat, *plat_args], check=True ) assert not exists(local_plat) assert exists(local_out) @@ -526,22 +553,24 @@ def test_add_platforms() -> None: # If platforms already present, don't write more res = sorted(os.listdir("wheels")) assert_winfo_similar(local_out, EXTRA_EXPS) - code, stdout, stderr = run_command( - ["delocate-addplat", local_out, "--clobber"] + plat_args + script_runner.run( + ["delocate-addplat", local_out, "--clobber", *plat_args], check=True ) assert sorted(os.listdir("wheels")) == res assert_winfo_similar(local_out, EXTRA_EXPS) # The wheel doesn't get deleted output name same as input, as here - code, stdout, stderr = run_command( - ["delocate-addplat", local_out, "-r", "--clobber"] + plat_args + script_runner.run( + ["delocate-addplat", local_out, "-r", "--clobber", *plat_args], + check=True, ) assert sorted(os.listdir("wheels")) == res # But adds WHEEL tags if missing, even if file name is OK shutil.copy2(local_plat, local_out) with pytest.raises(AssertionError): assert_winfo_similar(local_out, EXTRA_EXPS) - code, stdout, stderr = run_command( - ["delocate-addplat", local_out, "--clobber"] + plat_args + script_runner.run( + ["delocate-addplat", local_out, "--clobber", *plat_args], check=True ) assert sorted(os.listdir("wheels")) == res assert_winfo_similar(local_out, EXTRA_EXPS) + assert_winfo_similar(local_out, EXTRA_EXPS) diff --git a/delocate/tests/test_wheelies.py b/delocate/tests/test_wheelies.py index fabe189d..8477dabd 100644 --- a/delocate/tests/test_wheelies.py +++ b/delocate/tests/test_wheelies.py @@ -1,4 +1,5 @@ """ Direct tests of fixes to wheels """ +from __future__ import annotations import os import shutil @@ -10,6 +11,7 @@ from os.path import abspath, basename, exists, isdir from os.path import join as pjoin from os.path import realpath +from pathlib import Path from subprocess import check_call from typing import NamedTuple @@ -83,7 +85,7 @@ def test_fix_pure_python(): assert_false(exists(pjoin("pure_pkg", "fakepkg2", ".dylibs"))) -def _fixed_wheel(out_path): +def _fixed_wheel(out_path: str | Path) -> tuple[str, str]: wheel_base = basename(PLAT_WHEEL) with InGivenDirectory(out_path): zip2dir(PLAT_WHEEL, "_plat_pkg") @@ -99,12 +101,13 @@ def _fixed_wheel(out_path): return pjoin(out_path, wheel_base), stray_lib -def _rename_module(in_wheel, mod_fname, out_wheel): - # Rename module with library dependency in wheel - with InWheel(in_wheel, out_wheel): - mod_dir = pjoin("fakepkg1", "subpkg") - os.rename(pjoin(mod_dir, "module2.abi3.so"), pjoin(mod_dir, mod_fname)) - return out_wheel +def _rename_module( + in_wheel: str | Path, mod_fname: str | Path, out_wheel: str | Path +) -> None: + """Rename a module with library dependency in a wheel.""" + with InWheel(str(in_wheel), str(out_wheel)): + mod_dir = Path("fakepkg1", "subpkg") + os.rename(Path(mod_dir, "module2.abi3.so"), Path(mod_dir, mod_fname)) @pytest.mark.xfail(sys.platform != "darwin", reason="otool") @@ -219,13 +222,14 @@ def func(fn): ) -def _thin_lib(stray_lib, arch): +def _thin_lib(stray_lib: str | Path, arch: str) -> None: + stray_lib = str(stray_lib) check_call(["lipo", "-thin", arch, stray_lib, "-output", stray_lib]) -def _thin_mod(wheel, arch): +def _thin_mod(wheel: str | Path, arch: str) -> None: with InWheel(wheel, wheel): - mod_fname = pjoin("fakepkg1", "subpkg", "module2.abi3.so") + mod_fname = str(Path("fakepkg1", "subpkg", "module2.abi3.so")) check_call(["lipo", "-thin", arch, mod_fname, "-output", mod_fname]) diff --git a/pyproject.toml b/pyproject.toml index 363312b8..77a0d9b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ line_length = 80 [tool.pytest.ini_options] minversion = "6.0" -required_plugins = ["pytest-cov"] +required_plugins = ["pytest-console-scripts>=1.4.0", "pytest-cov"] testpaths = ["delocate/"] addopts = ["--doctest-modules", "--cov=delocate", "--cov-config=.coveragerc"] log_file_level = "DEBUG" diff --git a/test-requirements.txt b/test-requirements.txt index 88252fda..56f63600 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,4 +1,5 @@ # Requirements for tests wheel pytest +pytest-console-scripts~=1.4 pytest-cov