Skip to content

Commit

Permalink
tests: move compiled_dylib fixture to tests/functional/conftest.py
Browse files Browse the repository at this point in the history
If we want `compiled_dylib` to be session-scoped (to compile the
shared library for `ctypes` tests only once per session), we need
to move it from `PyInstaller/utils/conftest.py` into
`tests/functional/conftest.py`.

This is because its data directory needs to be resolved based on
the module's location (as ˙request.path` is not available in
session-scoped fixtures). And even if we were to use  test-scoped
fixture and infer location from `request.path`, the fixture would
be valid only for test files from `tests/functional` directory, so
it makes more sense to put it into its local `conftest.py` file.
  • Loading branch information
rokm committed Dec 25, 2024
1 parent 3137d00 commit 58100dd
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 72 deletions.
72 changes: 0 additions & 72 deletions PyInstaller/utils/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -568,78 +568,6 @@ def pyi_builder_spec(tmp_path, request, monkeypatch, pyi_modgraph):
shutil.rmtree(tmp_path, ignore_errors=True)


# A fixture that compiles a test shared library from `data/load_dll_using_ctypes/ctypes_dylib.c` in a sub-directory
# of the tmp_dir, and returns path to the compiled shared library.
@pytest.fixture(scope="session")
def compiled_dylib(tmp_path_factory):
# Copy the source to temporary directory.
orig_source_dir = _ROOT_DIR / 'tests' / 'functional' / 'data' / 'ctypes_dylib'
tmp_source_dir = tmp_path_factory.mktemp('compiled_ctypes_dylib')
shutil.copy2(orig_source_dir / 'ctypes_dylib.c', tmp_source_dir)

# Compile shared library using `distuils.ccompiler` module. The code is loosely based on implementation of the
# `distutils.command.build_ext` command module.
def _compile_dylib(source_dir):
# Until python 3.12, `distutils` was part of standard library. For newer python versions, `setuptools` provides
# its vendored copy. If neither are available, skip the test.
try:
import distutils.ccompiler
import distutils.sysconfig
except ImportError:
pytest.skip('distutils.ccompiler is not available')

# Set up compiler
compiler = distutils.ccompiler.new_compiler()
distutils.sysconfig.customize_compiler(compiler)
if hasattr(compiler, 'initialize'): # Applicable to MSVCCompiler on Windows.
compiler.initialize()

if is_win:
# With MinGW compiler, the `customize_compiler()` call ends up changing `compiler.shared_lib_extension` into
# ".pyd". Use ".dll" instead.
suffix = '.dll'
elif is_darwin:
# On macOS, `compiler.shared_lib_extension` is ".so", but ".dylib" is more appropriate.
suffix = '.dylib'
else:
suffix = compiler.shared_lib_extension

# Change the current working directory to the directory that contains source files. Ideally, we could pass the
# absolute path to sources to `compiler.compile()` and set its `output_dir` argument to the directory where
# object files should be generated. However, in this case, the object files are put under output directory
# *while retaining their original path component*. If `output_dir` is not specified, then the original absolute
# source file paths seem to be turned into relative ones (e.g, on Linux, the leading / is stripped away).
#
# NOTE: with python >= 3.11 we could use contextlib.chdir().
old_cwd = pathlib.Path.cwd()
os.chdir(source_dir)
try:
# Compile source .c file into object
sources = [
'ctypes_dylib.c',
]
objects = compiler.compile(sources)

# Link into shared library
output_filename = f'ctypes_dylib{suffix}'
compiler.link_shared_object(
objects,
output_filename,
target_lang='c',
export_symbols=['dummy'],
)
finally:
os.chdir(old_cwd) # Restore old working directory.

# Return path to compiled shared library
return source_dir / output_filename

try:
return _compile_dylib(tmp_source_dir)
except Exception as e:
pytest.skip(f"Could not compile test shared library: {e}")


@pytest.fixture
def pyi_windowed_builder(pyi_builder: AppBuilder):
"""A pyi_builder equivalent for testing --windowed applications."""
Expand Down
81 changes: 81 additions & 0 deletions tests/functional/conftest.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,83 @@
import pathlib

import pytest

# Bring all fixtures into this file.
from PyInstaller.utils.conftest import * # noqa: F401, F403


# A fixture that compiles a test shared library from `data/load_dll_using_ctypes/ctypes_dylib.c` in a sub-directory
# of the tmp_dir, and returns path to the compiled shared library.
#
# NOTE: for this fixture to be session-scoped, we need to define it here (as opposed to `PyInstaller.utils.confest`).
# This is because its data directory needs to be resolved based on this module's location. (And even if we were to use
# test-scoped fixture and infer location from `request.path`, the fixture would be valid only for test files from this
# directory).
@pytest.fixture(scope="session")
def compiled_dylib(tmp_path_factory):
# Copy the source to temporary directory.
orig_source_dir = pathlib.Path(__file__).parent / 'data' / 'ctypes_dylib'
tmp_source_dir = tmp_path_factory.mktemp('compiled_ctypes_dylib')
shutil.copy2(orig_source_dir / 'ctypes_dylib.c', tmp_source_dir)

# Compile shared library using `distuils.ccompiler` module. The code is loosely based on implementation of the
# `distutils.command.build_ext` command module.
def _compile_dylib(source_dir):
# Until python 3.12, `distutils` was part of standard library. For newer python versions, `setuptools` provides
# its vendored copy. If neither are available, skip the test.
try:
import distutils.ccompiler
import distutils.sysconfig
except ImportError:
pytest.skip('distutils.ccompiler is not available')

# Set up compiler
compiler = distutils.ccompiler.new_compiler()
distutils.sysconfig.customize_compiler(compiler)
if hasattr(compiler, 'initialize'): # Applicable to MSVCCompiler on Windows.
compiler.initialize()

if is_win:
# With MinGW compiler, the `customize_compiler()` call ends up changing `compiler.shared_lib_extension` into
# ".pyd". Use ".dll" instead.
suffix = '.dll'
elif is_darwin:
# On macOS, `compiler.shared_lib_extension` is ".so", but ".dylib" is more appropriate.
suffix = '.dylib'
else:
suffix = compiler.shared_lib_extension

# Change the current working directory to the directory that contains source files. Ideally, we could pass the
# absolute path to sources to `compiler.compile()` and set its `output_dir` argument to the directory where
# object files should be generated. However, in this case, the object files are put under output directory
# *while retaining their original path component*. If `output_dir` is not specified, then the original absolute
# source file paths seem to be turned into relative ones (e.g, on Linux, the leading / is stripped away).
#
# NOTE: with python >= 3.11 we could use contextlib.chdir().
old_cwd = pathlib.Path.cwd()
os.chdir(source_dir)
try:
# Compile source .c file into object
sources = [
'ctypes_dylib.c',
]
objects = compiler.compile(sources)

# Link into shared library
output_filename = f'ctypes_dylib{suffix}'
compiler.link_shared_object(
objects,
output_filename,
target_lang='c',
export_symbols=['dummy'],
)
finally:
os.chdir(old_cwd) # Restore old working directory.

# Return path to compiled shared library
return source_dir / output_filename

try:
return _compile_dylib(tmp_source_dir)
except Exception as e:
pytest.skip(f"Could not compile test shared library: {e}")

0 comments on commit 58100dd

Please sign in to comment.