123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154 |
- #-----------------------------------------------------------------------------
- # Copyright (c) 2005-2021, PyInstaller Development Team.
- #
- # Distributed under the terms of the GNU General Public License (version 2
- # or later) with exception for distributing the bootloader.
- #
- # The full license is in the file COPYING.txt, distributed with this software.
- #
- # SPDX-License-Identifier: (GPL-2.0-or-later WITH Bootloader-exception)
- #-----------------------------------------------------------------------------
- """
- Decorators for skipping PyInstaller tests when specific requirements are not met.
- """
- import distutils.ccompiler
- import inspect
- import os
- import shutil
- import textwrap
- import pytest
- import sys
- from PyInstaller.compat import is_win
- # Wrap some pytest decorators to be consistent in tests.
- parametrize = pytest.mark.parametrize
- skipif = pytest.mark.skipif
- xfail = pytest.mark.xfail
- def _check_for_compiler():
- import tempfile
- # Change to some tempdir since cc.has_function() would compile into the current directory, leaving garbage.
- old_wd = os.getcwd()
- tmp = tempfile.mkdtemp()
- os.chdir(tmp)
- cc = distutils.ccompiler.new_compiler()
- if is_win:
- try:
- cc.initialize()
- has_compiler = True
- # This error is raised on Windows if a compiler can't be found.
- except distutils.errors.DistutilsPlatformError:
- has_compiler = False
- else:
- # The C standard library contains the ``clock`` function. Use that to determine if a compiler is installed. This
- # does not work on Windows::
- #
- # Users\bjones\AppData\Local\Temp\a.out.exe.manifest : general error
- # c1010070: Failed to load and parse the manifest. The system cannot
- # find the file specified.
- has_compiler = cc.has_function('clock', includes=['time.h'])
- os.chdir(old_wd)
- # TODO: Find a way to remove the generated clockXXXX.c file, too
- shutil.rmtree(tmp)
- return has_compiler
- # A decorator to skip tests if a C compiler is not detected.
- has_compiler = _check_for_compiler()
- skipif_no_compiler = skipif(not has_compiler, reason="Requires a C compiler")
- skip = pytest.mark.skip
- def importorskip(package: str):
- """
- Skip a decorated test if **package** is not importable.
- Arguments:
- package:
- The name of the module. May be anything that is allowed after the ``import`` keyword. e.g. 'numpy' or
- 'PIL.Image'.
- Returns:
- A pytest marker which either skips the test or does nothing.
- This function intentionally does not import the module. Doing so can lead to `sys.path` and `PATH` being
- polluted, which then breaks later builds.
- """
- if not importable(package):
- return pytest.mark.skip(f"Can't import '{package}'.")
- return pytest.mark.skipif(False, reason=f"Don't skip: '{package}' is importable.")
- def importable(package: str):
- from importlib.util import find_spec
- # The find_spec() function is used by the importlib machinery to locate a module to import. Using it finds the
- # module but does not run it. Unfortunately, it does import parent modules to check submodules.
- if "." in package:
- # Using subprocesses is slow. If the top level module doesn't exist then we can skip it.
- if not importable(package.split(".")[0]):
- return False
- # This is a submodule, import it in isolation.
- from subprocess import DEVNULL, run
- return run([sys.executable, "-c", "import " + package], stdout=DEVNULL, stderr=DEVNULL).returncode == 0
- return find_spec(package) is not None
- def requires(requirement: str):
- """
- Mark a test to be skipped if **requirement** is not satisfied.
- Args:
- requirement:
- A distribution name and optionally a version. See :func:`pkg_resources.require` which this argument is
- forwarded to.
- Returns:
- Either a skip marker or a dummy marker.
- This function intentionally does not import the module. Doing so can lead to `sys.path` and `PATH` being
- polluted, which then breaks later builds.
- """
- import pkg_resources
- try:
- pkg_resources.require(requirement)
- return pytest.mark.skipif(False, reason=f"Don't skip: '{requirement}' is satisfied.")
- except pkg_resources.ResolutionError:
- return pytest.mark.skip("Requires " + requirement)
- def gen_sourcefile(tmpdir, source, test_id=None):
- """
- Generate a source file for testing.
- The source will be written into a file named like the test-function. This file will then be passed to
- `test_script`. If you need other related file, e.g. as `.toc`-file for testing the content, put it at at the
- normal place. Just mind to take the basnename from the test-function's name.
- :param script: Source code to create executable from. This will be saved into a temporary file which is then
- passed on to `test_script`.
- :param test_id: Test-id for parametrized tests. If given, it will be appended to the script filename,
- separated by two underscores.
- Ensure that the caller of `test_source` is in a UTF-8 encoded file with the correct '# -*- coding: utf-8 -*-'
- marker.
- """
- testname = inspect.stack()[1][3]
- if test_id:
- # For parametrized test append the test-id.
- testname = testname + '__' + test_id
- # Periods are not allowed in Python module names.
- testname = testname.replace('.', '_')
- scriptfile = tmpdir / (testname + '.py')
- source = textwrap.dedent(source)
- with scriptfile.open('w', encoding='utf-8') as ofh:
- print('# -*- coding: utf-8 -*-', file=ofh)
- print(source, file=ofh)
- return scriptfile
|