diff --git a/_pytest/cx_freeze_support.py b/_pytest/cx_freeze_support.py index 84b2a11ee..fbe438bf0 100644 --- a/_pytest/cx_freeze_support.py +++ b/_pytest/cx_freeze_support.py @@ -1,52 +1,56 @@ +""" +Package to support embedding pytest runner into executable files. +.. note:: Since we are imported into pytest namespace, we use local imports to + be as cheap as possible. +""" def includes(): - return [ - '_pytest.assertion.newinterpret', - '_pytest.assertion.oldinterpret', - '_pytest.assertion.reinterpret', - '_pytest.assertion.rewrite', - '_pytest.assertion.util', + """ + Returns a list of module names used by py.test that should be + included by cx_freeze. + """ + import py + import _pytest - '_pytest._argcomplete', - '_pytest.doctest', - '_pytest.pdb', - '_pytest.unittest', - '_pytest.capture', - '_pytest.config', - '_pytest.core', - '_pytest.genscript', - '_pytest.helpconfig', - '_pytest.hookspec', - '_pytest.junitxml', - '_pytest.main', - '_pytest.mark', - '_pytest.monkeypatch', - '_pytest.nose', - '_pytest.pastebin', - '_pytest.pytester', - '_pytest.python', - '_pytest.recwarn', - '_pytest.resultlog', - '_pytest.runner', - '_pytest.skipping', - '_pytest.standalonetemplate', - '_pytest.terminal', - '_pytest.tmpdir', + result = list(_iter_all_modules(py)) + result += list(_iter_all_modules(_pytest)) - 'py._builtin', - 'py._path.local', - 'py._io.capture', - 'py._io.saferepr', - 'py._iniconfig', - 'py._io.terminalwriter', - 'py._xmlgen', - 'py._error', - 'py._std', - - # builtin files imported by pytest using py.std implicit mechanism + # builtin files imported by pytest using py.std implicit mechanism; + # should be removed if https://bitbucket.org/hpk42/pytest/pull-request/185 + # gets merged + result += [ 'argparse', 'shlex', 'warnings', 'types', - ] \ No newline at end of file + ] + return result + + +def _iter_all_modules(package, prefix=''): + """ + Iterates over the names of all modules that can be found in the given + package, recursively. + + Example: + _iter_all_modules(_pytest) -> + ['_pytest.assertion.newinterpret', + '_pytest.capture', + '_pytest.core', + ... + ] + """ + import pkgutil + import os + + if type(package) is not str: + path, prefix = package.__path__[0], package.__name__ + '.' + else: + path = package + for _, name, is_package in pkgutil.iter_modules([path]): + if is_package: + for m in _iter_all_modules(os.path.join(path, name), prefix=name + '.'): + yield prefix + m + else: + yield prefix + name diff --git a/doc/en/example/simple.txt b/doc/en/example/simple.txt index 6b6163d58..caaf1fb56 100644 --- a/doc/en/example/simple.txt +++ b/doc/en/example/simple.txt @@ -694,33 +694,26 @@ 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 embedding the ``pytest`` runner into a frozen executable using -``cx_freeze`` is not as straightforward as one would like, -because ``pytest`` makes heavy use of dynamic module loading which -``cx_freeze`` can't resolve by itself. - -To solve this, you have to manually include ``pytest`` and ``py`` -modules by using the ``build_exe`` option in your ``setup.py`` script, like this:: +Unfortunately ``cx_freeze`` can't discover them +automatically because of ``pytest``'s use of dynamic module loading, so you +must declare them explicitly by using ``pytest.cx_freeze_support.includes()``:: # contents of setup.py from cx_Freeze import setup, Executable + import pytest - includes = [ - '_pytest.doctest', - '_pytest.unittest', - # ... lots more - ] setup( name="runtests", - options={"build_exe": {'includes': includes}}, + options={"build_exe": + { + 'includes': pytest.cx_freeze_support.includes()} + }, # ... other options ) -(For the complete list, check out the modules under ``_pytest`` in your -site-packages). - -With that, you can make your program check for a certain flag and pass control -over to ``pytest``:: +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 +over to ``pytest`` instead. For example:: # contents of app_main.py import sys @@ -734,6 +727,6 @@ over to ``pytest``:: ... This makes it convenient to execute your tests from within your frozen -application, using standard ``py.test`` command-line:: +application, using standard ``py.test`` command-line options:: $ ./app_main --pytest --verbose --tb=long --junit-xml=results.xml test-suite/ \ No newline at end of file diff --git a/testing/cx_freeze/run.py b/testing/cx_freeze/run.py deleted file mode 100644 index d52906df3..000000000 --- a/testing/cx_freeze/run.py +++ /dev/null @@ -1,7 +0,0 @@ -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)) \ No newline at end of file diff --git a/testing/cx_freeze/runtests_script.py b/testing/cx_freeze/runtests_script.py index d3e32ec82..f2b032d76 100644 --- a/testing/cx_freeze/runtests_script.py +++ b/testing/cx_freeze/runtests_script.py @@ -1,7 +1,6 @@ """ -Simple script that actually executes py.test runner when passed "--pytest" as -first argument; in this case, all other arguments are forwarded to pytest's -main(). +This is the script that is actually frozen into an executable: simply executes +py.test main(). """ if __name__ == '__main__': diff --git a/testing/cx_freeze/runtests_setup.py b/testing/cx_freeze/runtests_setup.py index 3d15a5a82..56b39205d 100644 --- a/testing/cx_freeze/runtests_setup.py +++ b/testing/cx_freeze/runtests_setup.py @@ -1,6 +1,9 @@ +""" +Sample setup.py script that generates an executable with pytest runner embedded. +""" from cx_Freeze import setup, Executable - import pytest + setup( name="runtests", version="0.1", diff --git a/testing/cx_freeze/tox_run.py b/testing/cx_freeze/tox_run.py new file mode 100644 index 000000000..95ac1b858 --- /dev/null +++ b/testing/cx_freeze/tox_run.py @@ -0,0 +1,14 @@ +""" +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). +""" +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)) \ No newline at end of file diff --git a/tox.ini b/tox.ini index d0ae9c9cc..d1214f5c2 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] distshare={homedir}/.tox/distshare -envlist=flakes,py26,py27,py34,pypy,py27-pexpect,py33-pexpect,py27-nobyte,py32,py33,py27-xdist,py33-xdist,py27-trial,py33-trial,doctesting +envlist=flakes,py26,py27,py34,pypy,py27-pexpect,py33-pexpect,py27-nobyte,py32,py33,py27-xdist,py33-xdist,py27-trial,py33-trial,doctesting,py27-cxfreeze [testenv] changedir=testing @@ -129,7 +129,7 @@ changedir=testing/cx_freeze basepython=python2.7 commands= {envpython} runtests_setup.py build --build-exe build - {envpython} run.py + {envpython} tox_run.py [pytest] minversion=2.0