Skip to content

Commit ffb583a

Browse files
authored
Merge pull request #1773 from nicoddemus/fix-freeze
Use PyInstaller for freeze test env
2 parents 7b15206 + ae9d3bf commit ffb583a

13 files changed

+72
-149
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ env:
2525
- TESTENV=py35-trial
2626
- TESTENV=py27-nobyte
2727
- TESTENV=doctesting
28-
- TESTENV=py27-cxfreeze
28+
- TESTENV=freeze
2929

3030
script: tox --recreate -e $TESTENV
3131

_pytest/genscript.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,8 @@ def pytest_namespace():
100100
def freeze_includes():
101101
"""
102102
Returns a list of module names used by py.test that should be
103-
included by cx_freeze.
103+
included by cx_freeze/pyinstaller to generate a standalone
104+
pytest executable.
104105
"""
105106
result = list(_iter_all_modules(py))
106107
result += list(_iter_all_modules(_pytest))

appveyor.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ environment:
1010
# builds timing out in AppVeyor
1111
- TOXENV: "linting,py26,py27,py33,py34,py35,pypy"
1212
- TOXENV: "py27-pexpect,py27-xdist,py27-trial,py35-pexpect,py35-xdist,py35-trial"
13-
- TOXENV: "py27-nobyte,doctesting,py27-cxfreeze"
13+
- TOXENV: "py27-nobyte,doctesting,freeze"
1414

1515
install:
1616
- echo Installed Pythons

doc/en/example/simple.rst

Lines changed: 23 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -699,40 +699,29 @@ and run it::
699699
You'll see that the fixture finalizers could use the precise reporting
700700
information.
701701

702-
Integrating pytest runner and cx_freeze
703-
-----------------------------------------------------------
702+
Freezing pytest
703+
---------------
704704

705705
If you freeze your application using a tool like
706-
`cx_freeze <https://cx-freeze.readthedocs.io>`_ in order to distribute it
707-
to your end-users, it is a good idea to also package your test runner and run
708-
your tests using the frozen application.
709-
710-
This way packaging errors such as dependencies not being
711-
included into the executable can be detected early while also allowing you to
712-
send test files to users so they can run them in their machines, which can be
713-
invaluable to obtain more information about a hard to reproduce bug.
714-
715-
Unfortunately ``cx_freeze`` can't discover them
716-
automatically because of ``pytest``'s use of dynamic module loading, so you
717-
must declare them explicitly by using ``pytest.freeze_includes()``::
718-
719-
# contents of setup.py
720-
from cx_Freeze import setup, Executable
721-
import pytest
722-
723-
setup(
724-
name="app_main",
725-
executables=[Executable("app_main.py")],
726-
options={"build_exe":
727-
{
728-
'includes': pytest.freeze_includes()}
729-
},
730-
# ... other options
731-
)
732-
733-
If you don't want to ship a different executable just in order to run your tests,
734-
you can make your program check for a certain flag and pass control
735-
over to ``pytest`` instead. For example::
706+
`PyInstaller <https://pyinstaller.readthedocs.io>`_
707+
in order to distribute it to your end-users, it is a good idea to also package
708+
your test runner and run your tests using the frozen application. This way packaging
709+
errors such as dependencies not being included into the executable can be detected early
710+
while also allowing you to send test files to users so they can run them in their
711+
machines, which can be useful to obtain more information about a hard to reproduce bug.
712+
713+
Fortunately recent ``PyInstaller`` releases already have a custom hook
714+
for pytest, but if you are using another tool to freeze executables
715+
such as ``cx_freeze`` or ``py2exe``, you can use ``pytest.freeze_includes()``
716+
to obtain the full list of internal pytest modules. How to configure the tools
717+
to find the internal modules varies from tool to tool, however.
718+
719+
Instead of freezing the pytest runner as a separate executable, you can make
720+
your frozen program work as the pytest runner by some clever
721+
argument handling during program startup. This allows you to
722+
have a single executable, which is usually more convenient.
723+
724+
.. code-block:: python
736725
737726
# contents of app_main.py
738727
import sys
@@ -745,7 +734,7 @@ over to ``pytest`` instead. For example::
745734
# by your argument-parsing library of choice as usual
746735
...
747736
748-
This makes it convenient to execute your tests from within your frozen
749-
application, using standard ``py.test`` command-line options::
737+
This allows you to execute tests using the frozen
738+
application with standard ``py.test`` command-line options::
750739

751740
./app_main --pytest --verbose --tb=long --junitxml=results.xml test-suite/

testing/cx_freeze/install_cx_freeze.py

Lines changed: 0 additions & 64 deletions
This file was deleted.

testing/cx_freeze/runtests_setup.py

Lines changed: 0 additions & 15 deletions
This file was deleted.

testing/freeze/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
build/
2+
dist/
3+
*.spec

testing/freeze/create_executable.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
"""
2+
Generates an executable with pytest runner embedded using PyInstaller.
3+
"""
4+
if __name__ == '__main__':
5+
import pytest
6+
import subprocess
7+
8+
hidden = []
9+
for x in pytest.freeze_includes():
10+
hidden.extend(['--hidden-import', x])
11+
args = ['pyinstaller', '--noconfirm'] + hidden + ['runtests_script.py']
12+
subprocess.check_call(' '.join(args), shell=True)
13+
Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
"""
2-
This is the script that is actually frozen into an executable: simply executes
3-
py.test main().
4-
"""
5-
6-
if __name__ == '__main__':
7-
import sys
8-
import pytest
1+
"""
2+
This is the script that is actually frozen into an executable: simply executes
3+
py.test main().
4+
"""
5+
6+
if __name__ == '__main__':
7+
import sys
8+
import pytest
99
sys.exit(pytest.main())
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
2-
def test_upper():
3-
assert 'foo'.upper() == 'FOO'
4-
5-
def test_lower():
1+
2+
def test_upper():
3+
assert 'foo'.upper() == 'FOO'
4+
5+
def test_lower():
66
assert 'FOO'.lower() == 'foo'
Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
1-
"""
2-
Called by tox.ini: uses the generated executable to run the tests in ./tests/
3-
directory.
4-
5-
.. note:: somehow calling "build/runtests_script" directly from tox doesn't
6-
seem to work (at least on Windows).
7-
"""
8-
if __name__ == '__main__':
9-
import os
10-
import sys
11-
12-
executable = os.path.join(os.getcwd(), 'build', 'runtests_script')
13-
if sys.platform.startswith('win'):
14-
executable += '.exe'
1+
"""
2+
Called by tox.ini: uses the generated executable to run the tests in ./tests/
3+
directory.
4+
"""
5+
if __name__ == '__main__':
6+
import os
7+
import sys
8+
9+
executable = os.path.join(os.getcwd(), 'dist', 'runtests_script', 'runtests_script')
10+
if sys.platform.startswith('win'):
11+
executable += '.exe'
1512
sys.exit(os.system('%s tests' % executable))

tox.ini

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ distshare={homedir}/.tox/distshare
55
envlist=
66
linting,py26,py27,py33,py34,py35,pypy,
77
{py27,py35}-{pexpect,xdist,trial},
8-
py27-nobyte,doctesting,py27-cxfreeze
8+
py27-nobyte,doctesting,freeze
99

1010
[testenv]
1111
commands= py.test --lsof -rfsxX {posargs:testing}
@@ -124,12 +124,11 @@ changedir=testing
124124
commands=
125125
{envpython} {envbindir}/py.test-jython -rfsxX {posargs}
126126

127-
[testenv:py27-cxfreeze]
128-
changedir=testing/cx_freeze
129-
platform=linux|darwin
127+
[testenv:freeze]
128+
changedir=testing/freeze
129+
deps=pyinstaller
130130
commands=
131-
{envpython} install_cx_freeze.py
132-
{envpython} runtests_setup.py build --build-exe build
131+
{envpython} create_executable.py
133132
{envpython} tox_run.py
134133

135134

0 commit comments

Comments
 (0)