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:
parent
d911bfcb8a
commit
ed36d627e4
|
@ -25,7 +25,7 @@ env:
|
||||||
- TESTENV=py35-trial
|
- TESTENV=py35-trial
|
||||||
- TESTENV=py27-nobyte
|
- TESTENV=py27-nobyte
|
||||||
- TESTENV=doctesting
|
- TESTENV=doctesting
|
||||||
- TESTENV=py27-cxfreeze
|
- TESTENV=freeze
|
||||||
|
|
||||||
script: tox --recreate -e $TESTENV
|
script: tox --recreate -e $TESTENV
|
||||||
|
|
||||||
|
|
|
@ -100,7 +100,8 @@ def pytest_namespace():
|
||||||
def freeze_includes():
|
def freeze_includes():
|
||||||
"""
|
"""
|
||||||
Returns a list of module names used by py.test that should be
|
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(py))
|
||||||
result += list(_iter_all_modules(_pytest))
|
result += list(_iter_all_modules(_pytest))
|
||||||
|
|
|
@ -10,7 +10,7 @@ environment:
|
||||||
# builds timing out in AppVeyor
|
# builds timing out in AppVeyor
|
||||||
- TOXENV: "linting,py26,py27,py33,py34,py35,pypy"
|
- TOXENV: "linting,py26,py27,py33,py34,py35,pypy"
|
||||||
- TOXENV: "py27-pexpect,py27-xdist,py27-trial,py35-pexpect,py35-xdist,py35-trial"
|
- 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:
|
install:
|
||||||
- echo Installed Pythons
|
- echo Installed Pythons
|
||||||
|
|
|
@ -699,36 +699,33 @@ and run it::
|
||||||
You'll see that the fixture finalizers could use the precise reporting
|
You'll see that the fixture finalizers could use the precise reporting
|
||||||
information.
|
information.
|
||||||
|
|
||||||
Integrating pytest runner and cx_freeze
|
Integrating pytest runner and PyInstaller
|
||||||
-----------------------------------------------------------
|
-----------------------------------------
|
||||||
|
|
||||||
If you freeze your application using a tool like
|
If you freeze your application using a tool like
|
||||||
`cx_freeze <https://cx-freeze.readthedocs.io>`_ in order to distribute it
|
`PyInstaller <https://pyinstaller.readthedocs.io>`_
|
||||||
to your end-users, it is a good idea to also package your test runner and run
|
in order to distribute it to your end-users, it is a good idea to also package
|
||||||
your tests using the frozen application.
|
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
|
Unfortunately ``PyInstaller`` can't discover them
|
||||||
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
|
|
||||||
automatically because of ``pytest``'s use of dynamic module loading, so you
|
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
|
.. code-block:: python
|
||||||
from cx_Freeze import setup, Executable
|
|
||||||
|
# contents of create_executable.py
|
||||||
import pytest
|
import pytest
|
||||||
|
import subprocess
|
||||||
|
|
||||||
setup(
|
hidden = []
|
||||||
name="app_main",
|
for x in pytest.freeze_includes():
|
||||||
executables=[Executable("app_main.py")],
|
hidden.extend(['--hidden-import', x])
|
||||||
options={"build_exe":
|
args = ['pyinstaller'] + hidden + ['runtests_script.py']
|
||||||
{
|
subprocess.check_call(' '.join(args), shell=True)
|
||||||
'includes': pytest.freeze_includes()}
|
|
||||||
},
|
|
||||||
# ... other options
|
|
||||||
)
|
|
||||||
|
|
||||||
If you don't want to ship a different executable just in order to run your tests,
|
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
|
you can make your program check for a certain flag and pass control
|
||||||
|
|
|
@ -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)
|
|
|
@ -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()}},
|
|
||||||
)
|
|
||||||
|
|
|
@ -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))
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
*.spec
|
|
@ -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)
|
||||||
|
|
|
@ -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
11
tox.ini
|
@ -5,7 +5,7 @@ distshare={homedir}/.tox/distshare
|
||||||
envlist=
|
envlist=
|
||||||
linting,py26,py27,py33,py34,py35,pypy,
|
linting,py26,py27,py33,py34,py35,pypy,
|
||||||
{py27,py35}-{pexpect,xdist,trial},
|
{py27,py35}-{pexpect,xdist,trial},
|
||||||
py27-nobyte,doctesting,py27-cxfreeze
|
py27-nobyte,doctesting,freeze
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
commands= py.test --lsof -rfsxX {posargs:testing}
|
commands= py.test --lsof -rfsxX {posargs:testing}
|
||||||
|
@ -124,12 +124,11 @@ changedir=testing
|
||||||
commands=
|
commands=
|
||||||
{envpython} {envbindir}/py.test-jython -rfsxX {posargs}
|
{envpython} {envbindir}/py.test-jython -rfsxX {posargs}
|
||||||
|
|
||||||
[testenv:py27-cxfreeze]
|
[testenv:freeze]
|
||||||
changedir=testing/cx_freeze
|
changedir=testing/freeze
|
||||||
platform=linux|darwin
|
deps=pyinstaller
|
||||||
commands=
|
commands=
|
||||||
{envpython} install_cx_freeze.py
|
{envpython} create_executable.py
|
||||||
{envpython} runtests_setup.py build --build-exe build
|
|
||||||
{envpython} tox_run.py
|
{envpython} tox_run.py
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue