Use PyInstaller for freeze test env

cx_freeze doesn't seem to be very well supported in Python 3.5.

Using pyinstaller instead and rename environment to "freeze" which
is a more generic term for freezing python code into standalone
executables.

Fix #1769
This commit is contained in:
Bruno Oliveira 2016-07-26 21:29:07 -03:00
parent d911bfcb8a
commit ed36d627e4
14 changed files with 70 additions and 139 deletions

View File

@ -25,7 +25,7 @@ env:
- TESTENV=py35-trial
- TESTENV=py27-nobyte
- TESTENV=doctesting
- TESTENV=py27-cxfreeze
- TESTENV=freeze
script: tox --recreate -e $TESTENV

View File

@ -100,7 +100,8 @@ def pytest_namespace():
def freeze_includes():
"""
Returns a list of module names used by py.test that should be
included by cx_freeze.
included by cx_freeze/pyinstaller to generate a standalone
pytest executable.
"""
result = list(_iter_all_modules(py))
result += list(_iter_all_modules(_pytest))

View File

@ -10,7 +10,7 @@ environment:
# builds timing out in AppVeyor
- TOXENV: "linting,py26,py27,py33,py34,py35,pypy"
- TOXENV: "py27-pexpect,py27-xdist,py27-trial,py35-pexpect,py35-xdist,py35-trial"
- TOXENV: "py27-nobyte,doctesting,py27-cxfreeze"
- TOXENV: "py27-nobyte,doctesting,freeze"
install:
- echo Installed Pythons

View File

@ -699,36 +699,33 @@ and run it::
You'll see that the fixture finalizers could use the precise reporting
information.
Integrating pytest runner and cx_freeze
-----------------------------------------------------------
Integrating pytest runner and PyInstaller
-----------------------------------------
If you freeze your application using a tool like
`cx_freeze <https://cx-freeze.readthedocs.io>`_ in order to distribute it
to your end-users, it is a good idea to also package your test runner and run
your tests using the frozen application.
`PyInstaller <https://pyinstaller.readthedocs.io>`_
in order to distribute it to your end-users, it is a good idea to also package
your test runner and run your tests using the frozen application. This way packaging
errors such as dependencies not being included into the executable can be detected early
while also allowing you to send test files to users so they can run them in their
machines, which can be invaluable to obtain more information about a hard to reproduce bug.
This way packaging errors such as dependencies not being
included into the executable can be detected early while also allowing you to
send test files to users so they can run them in their machines, which can be
invaluable to obtain more information about a hard to reproduce bug.
Unfortunately ``cx_freeze`` can't discover them
Unfortunately ``PyInstaller`` can't discover them
automatically because of ``pytest``'s use of dynamic module loading, so you
must declare them explicitly by using ``pytest.freeze_includes()``::
must declare them explicitly by using ``pytest.freeze_includes()`` and an
auxiliary script:
# contents of setup.py
from cx_Freeze import setup, Executable
.. code-block:: python
# contents of create_executable.py
import pytest
import subprocess
setup(
name="app_main",
executables=[Executable("app_main.py")],
options={"build_exe":
{
'includes': pytest.freeze_includes()}
},
# ... other options
)
hidden = []
for x in pytest.freeze_includes():
hidden.extend(['--hidden-import', x])
args = ['pyinstaller'] + hidden + ['runtests_script.py']
subprocess.check_call(' '.join(args), shell=True)
If you don't want to ship a different executable just in order to run your tests,
you can make your program check for a certain flag and pass control

View File

@ -1,64 +0,0 @@
"""
Installs cx_freeze from source, but first patching
setup.py as described here:
http://stackoverflow.com/questions/25107697/compiling-cx-freeze-under-ubuntu
"""
import glob
import tarfile
import os
import sys
import platform
import py
if __name__ == '__main__':
if 'ubuntu' not in platform.version().lower():
print('Not Ubuntu, installing using pip. (platform.version() is %r)' %
platform.version())
res = os.system('pip install cx_freeze')
if res != 0:
sys.exit(res)
sys.exit(0)
rootdir = py.path.local.make_numbered_dir(prefix='cx_freeze')
res = os.system('pip install --download %s --no-use-wheel '
'cx_freeze' % rootdir)
if res != 0:
sys.exit(res)
packages = glob.glob('%s/*.tar.gz' % rootdir)
assert len(packages) == 1
tar_filename = packages[0]
tar_file = tarfile.open(tar_filename)
try:
tar_file.extractall(path=str(rootdir))
finally:
tar_file.close()
basename = os.path.basename(tar_filename).replace('.tar.gz', '')
setup_py_filename = '%s/%s/setup.py' % (rootdir, basename)
with open(setup_py_filename) as f:
lines = f.readlines()
line_to_patch = 'if not vars.get("Py_ENABLE_SHARED", 0):'
for index, line in enumerate(lines):
if line_to_patch in line:
indent = line[:line.index(line_to_patch)]
lines[index] = indent + 'if True:\n'
print('Patched line %d' % (index + 1))
break
else:
sys.exit('Could not find line in setup.py to patch!')
with open(setup_py_filename, 'w') as f:
f.writelines(lines)
os.chdir('%s/%s' % (rootdir, basename))
res = os.system('python setup.py install')
if res != 0:
sys.exit(res)
sys.exit(0)

View File

@ -1,15 +0,0 @@
"""
Sample setup.py script that generates an executable with pytest runner embedded.
"""
if __name__ == '__main__':
from cx_Freeze import setup, Executable
import pytest
setup(
name="runtests",
version="0.1",
description="example of how embedding py.test into an executable using cx_freeze",
executables=[Executable("runtests_script.py")],
options={"build_exe": {'includes': pytest.freeze_includes()}},
)

View File

@ -1,15 +0,0 @@
"""
Called by tox.ini: uses the generated executable to run the tests in ./tests/
directory.
.. note:: somehow calling "build/runtests_script" directly from tox doesn't
seem to work (at least on Windows).
"""
if __name__ == '__main__':
import os
import sys
executable = os.path.join(os.getcwd(), 'build', 'runtests_script')
if sys.platform.startswith('win'):
executable += '.exe'
sys.exit(os.system('%s tests' % executable))

3
testing/freeze/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
build/
dist/
*.spec

View File

@ -0,0 +1,13 @@
"""
Generates an executable with pytest runner embedded using PyInstaller.
"""
if __name__ == '__main__':
import pytest
import subprocess
hidden = []
for x in pytest.freeze_includes():
hidden.extend(['--hidden-import', x])
args = ['pyinstaller', '--noconfirm'] + hidden + ['runtests_script.py']
subprocess.check_call(' '.join(args), shell=True)

View File

@ -1,9 +1,9 @@
"""
This is the script that is actually frozen into an executable: simply executes
py.test main().
"""
if __name__ == '__main__':
import sys
import pytest
"""
This is the script that is actually frozen into an executable: simply executes
py.test main().
"""
if __name__ == '__main__':
import sys
import pytest
sys.exit(pytest.main())

View File

@ -1,6 +1,6 @@
def test_upper():
assert 'foo'.upper() == 'FOO'
def test_lower():
def test_upper():
assert 'foo'.upper() == 'FOO'
def test_lower():
assert 'FOO'.lower() == 'foo'

12
testing/freeze/tox_run.py Normal file
View File

@ -0,0 +1,12 @@
"""
Called by tox.ini: uses the generated executable to run the tests in ./tests/
directory.
"""
if __name__ == '__main__':
import os
import sys
executable = os.path.join(os.getcwd(), 'dist', 'runtests_script', 'runtests_script')
if sys.platform.startswith('win'):
executable += '.exe'
sys.exit(os.system('%s tests' % executable))

11
tox.ini
View File

@ -5,7 +5,7 @@ distshare={homedir}/.tox/distshare
envlist=
linting,py26,py27,py33,py34,py35,pypy,
{py27,py35}-{pexpect,xdist,trial},
py27-nobyte,doctesting,py27-cxfreeze
py27-nobyte,doctesting,freeze
[testenv]
commands= py.test --lsof -rfsxX {posargs:testing}
@ -124,12 +124,11 @@ changedir=testing
commands=
{envpython} {envbindir}/py.test-jython -rfsxX {posargs}
[testenv:py27-cxfreeze]
changedir=testing/cx_freeze
platform=linux|darwin
[testenv:freeze]
changedir=testing/freeze
deps=pyinstaller
commands=
{envpython} install_cx_freeze.py
{envpython} runtests_setup.py build --build-exe build
{envpython} create_executable.py
{envpython} tox_run.py