Skip to content

Commit c317a82

Browse files
committed
tests: rework the test for freetype-py
Rework the test for `freetype-py` to accommodate scenarios where the `freetype` shared library is not originally bundled with the package. In such cases, `freetype-py` uses `ctypes.util.find_library()` to discover and load system copy of the `freetype` shared library. This is also picked up by PyInstaller's `ctypes` analysis; the said shared library is collected into top-level application directory, and successfully loaded at run-time. But as far as the test goes, on Linux (and other POSIX systems except for macOS), we cannot use the `freetype.FT_Library_filename` variable to verify that the bundled copy from top-level application is used, because `ctypes.util.find_library()` does not resolve full paths. Therefore, we need the test to use `psutil` to dump the list of loaded shared libraries once `freetype` is imported. On the other hand, the required `psutil.memory_maps()` function is unavailable on macOS. But thankfully, `ctypes.util.find_library()` on macOS does resolve full library paths, so the original test based on `freetype.FT_Library_filename` should work with both PyPI wheel (package-bundled shared library) and Homebrew-installed `freetype-py` (shared library collected from Homebrew library directory). So in the end, we need two test variants; the original one for macOS and a new `psutil`-based one for all other POSIX platforms. On Windows, we can use either variant, as `psutil.memory_maps()` is available and `ctypes.util.find_library()` resolves full library paths.
1 parent 3c5a4bf commit c317a82

1 file changed

Lines changed: 61 additions & 21 deletions

File tree

tests/test_libraries.py

Lines changed: 61 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
get_module_attribute,
2121
is_module_satisfies,
2222
)
23-
from PyInstaller.utils.tests import importorskip, requires, xfail
23+
from PyInstaller.utils.tests import importorskip, importable, requires, xfail
2424

2525

2626
@importorskip('fiona')
@@ -1930,31 +1930,71 @@ def test_z3c_rml_rml2pdf(pyi_builder):
19301930
""")
19311931

19321932

1933+
# Ensure that the freetype shared library is bundled with the frozen application, and that it is being used (as opposed
1934+
# to the system copy).
19331935
@importorskip('freetype')
1934-
def test_pyi_freetype(pyi_builder):
1935-
pyi_builder.test_source("""
1936-
import sys
1937-
import pathlib
1936+
def test_freetype(pyi_builder):
1937+
if (is_darwin or is_win) or not importable('psutil'):
1938+
# This is the original variant of the test, which we need to keep around due to the fact that on macOS, `psutil`
1939+
# does not provide the `memory_maps()` function, so we cannot use it to inspect loaded shared libraries.
1940+
# Therefore, we inspect the library path as indicated by the `freetype.FT_Library_filename` variable; even if
1941+
# `ctypes.util.find_library()` fallback were used by `freetype` to load the shared library (for example, when
1942+
# using Homebrew-installed `freetype-py` instead of PyPI wheels), on macOS, that function resolves full library
1943+
# path (in contrast to other POSIX platforms, where it resolves only the basename).
1944+
#
1945+
# On Windows, we can use either variant of the test, as `psutil.memory_maps()` is available, and
1946+
# `ctypes.util.find_library()` resolves full library paths.
1947+
pyi_builder.test_source("""
1948+
import sys
1949+
import pathlib
19381950
1939-
import freetype
1951+
import freetype
19401952
1941-
# Ensure that the freetype shared library is bundled with the frozen application; otherwise, freetype might be
1942-
# using system-wide library.
1953+
# First, check that freetype.FT_Library_filename is an absolute path; otherwise, it is likely using
1954+
# basename-only ctypes fallback.
1955+
ft_library_file = pathlib.Path(freetype.FT_Library_filename)
1956+
print(f"FT library file (original): {ft_library_file}", file=sys.stderr)
1957+
assert ft_library_file.is_absolute(), "FT library file is not an absolute path!"
19431958
1944-
# First, check that freetype.FT_Library_filename is an absolute path; otherwise, it is likely using
1945-
# basename-only ctypes fallback.
1946-
ft_library_file = pathlib.Path(freetype.FT_Library_filename)
1947-
print(f"FT library file (original): {ft_library_file}", file=sys.stderr)
1948-
assert ft_library_file.is_absolute(), "FT library file is not an absolute path!"
1959+
# Check that fully-resolved freetype.FT_Library_filename is anchored in fully-resolved frozen application
1960+
# directory.
1961+
app_dir = pathlib.Path(__file__).resolve().parent
1962+
print(f"Application directory: {app_dir}", file=sys.stderr)
1963+
ft_library_path = pathlib.Path(ft_library_file).resolve()
1964+
print(f"FT library file (resolved): {ft_library_path}", file=sys.stderr)
1965+
assert app_dir in ft_library_path.parents, "FT library is not bundled with frozen application!"
1966+
""")
1967+
else:
1968+
# POSIX variant of the test that uses `psutil` for inspecting loaded shared libraries.
1969+
pyi_builder.test_source("""
1970+
import os
1971+
import pathlib
1972+
import sys
19491973
1950-
# Check that fully-resolved freetype.FT_Library_filename is anchored in fully-resolved frozen application
1951-
# directory.
1952-
app_dir = pathlib.Path(__file__).resolve().parent
1953-
print(f"Application directory: {app_dir}", file=sys.stderr)
1954-
ft_library_path = pathlib.Path(ft_library_file).resolve()
1955-
print(f"FT library file (resolved): {ft_library_path}", file=sys.stderr)
1956-
assert app_dir in ft_library_path.parents, "FT library is not bundled with frozen application!"
1957-
""")
1974+
import psutil
1975+
1976+
proc = psutil.Process(os.getpid())
1977+
1978+
libs_before = [pathlib.Path(lib.path) for lib in proc.memory_maps()]
1979+
1980+
print("Importing freetype...", file=sys.stderr)
1981+
import freetype
1982+
1983+
libs_after = [pathlib.Path(lib.path) for lib in proc.memory_maps()]
1984+
1985+
libs_new = sorted(set(libs_after) - set(libs_before))
1986+
print("Shared libraries loaded during freetype import:", file=sys.stderr)
1987+
for lib in libs_new:
1988+
print(f" * {lib}", file=sys.stderr)
1989+
1990+
libs_freetype = [lib for lib in libs_new if 'freetype' in lib.name]
1991+
assert len(libs_freetype) >= 1, "No library with 'freetype' in name found!"
1992+
1993+
app_dir = pathlib.Path(__file__).parent.resolve()
1994+
for lib_path in libs_freetype:
1995+
lib_path = lib_path.resolve()
1996+
assert app_dir in lib_path.parents, f"{lib_path} is not rooted in top-level application directory!"
1997+
""")
19581998

19591999

19602000
@importorskip('vaderSentiment')

0 commit comments

Comments
 (0)