From 5565c1f4ae2de7862b1fe44f218767187f6e274f Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 26 Jul 2016 20:11:49 -0300 Subject: [PATCH 1/8] Sort link refs in CHANGELOG --- CHANGELOG.rst | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 55dcc4093..5e179050d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -69,33 +69,33 @@ * -.. _#634: https://github.com/pytest-dev/pytest/issues/634 -.. _#717: https://github.com/pytest-dev/pytest/issues/717 +.. _#1210: https://github.com/pytest-dev/pytest/issues/1210 +.. _#1479: https://github.com/pytest-dev/pytest/issues/1479 +.. _#1503: https://github.com/pytest-dev/pytest/issues/1503 +.. _#1553: https://github.com/pytest-dev/pytest/issues/1553 .. _#1579: https://github.com/pytest-dev/pytest/issues/1579 .. _#1580: https://github.com/pytest-dev/pytest/pull/1580 -.. _#1605: https://github.com/pytest-dev/pytest/issues/1605 .. _#1597: https://github.com/pytest-dev/pytest/pull/1597 -.. _#460: https://github.com/pytest-dev/pytest/pull/460 -.. _#1553: https://github.com/pytest-dev/pytest/issues/1553 +.. _#1605: https://github.com/pytest-dev/pytest/issues/1605 .. _#1626: https://github.com/pytest-dev/pytest/pull/1626 -.. _#1503: https://github.com/pytest-dev/pytest/issues/1503 -.. _#1479: https://github.com/pytest-dev/pytest/issues/1479 +.. _#460: https://github.com/pytest-dev/pytest/pull/460 +.. _#634: https://github.com/pytest-dev/pytest/issues/634 +.. _#717: https://github.com/pytest-dev/pytest/issues/717 .. _#925: https://github.com/pytest-dev/pytest/issues/925 -.. _#1210: https://github.com/pytest-dev/pytest/issues/1210 -.. _@eolo999: https://github.com/eolo999 -.. _@blueyed: https://github.com/blueyed -.. _@graingert: https://github.com/graingert -.. _@taschini: https://github.com/taschini -.. _@nikratio: https://github.com/nikratio -.. _@RedBeardCode: https://github.com/RedBeardCode -.. _@Vogtinator: https://github.com/Vogtinator .. _@bagerard: https://github.com/bagerard +.. _@BeyondEvil: https://github.com/BeyondEvil +.. _@blueyed: https://github.com/blueyed .. _@davehunt: https://github.com/davehunt .. _@DRMacIver: https://github.com/DRMacIver -.. _@BeyondEvil: https://github.com/BeyondEvil +.. _@eolo999: https://github.com/eolo999 +.. _@graingert: https://github.com/graingert .. _@JonathonSonesen: https://github.com/JonathonSonesen +.. _@nikratio: https://github.com/nikratio +.. _@RedBeardCode: https://github.com/RedBeardCode .. _@Stranger6667: https://github.com/Stranger6667 +.. _@taschini: https://github.com/taschini +.. _@Vogtinator: https://github.com/Vogtinator 2.9.2 From 0b8a91b8584690fa7e0230f58ebe468c3b0782b8 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 26 Jul 2016 20:20:22 -0300 Subject: [PATCH 2/8] Fix pdf links in the documentation Fix #1436 --- doc/en/contents.rst | 2 +- doc/en/getting-started.rst | 2 +- doc/en/index.rst | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/en/contents.rst b/doc/en/contents.rst index 4e94f4ade..23721d445 100644 --- a/doc/en/contents.rst +++ b/doc/en/contents.rst @@ -3,7 +3,7 @@ Full pytest documentation =========================== -`Download latest version as PDF `_ +`Download latest version as PDF `_ .. `Download latest version as EPUB `_ diff --git a/doc/en/getting-started.rst b/doc/en/getting-started.rst index 0372f7f3b..d4e217f88 100644 --- a/doc/en/getting-started.rst +++ b/doc/en/getting-started.rst @@ -11,7 +11,7 @@ Installation and Getting Started `colorama (Windows) `_, `argparse (py26) `_. -**documentation as PDF**: `download latest `_ +**documentation as PDF**: `download latest `_ .. _`getstarted`: .. _installation: diff --git a/doc/en/index.rst b/doc/en/index.rst index 04b4512da..94fe90749 100644 --- a/doc/en/index.rst +++ b/doc/en/index.rst @@ -10,7 +10,7 @@ pytest: helps you write better programs - free and open source software, distributed under the terms of the :ref:`MIT license ` - **well tested** with more than a thousand tests against itself - **strict backward compatibility policy** for safe pytest upgrades - - :ref:`comprehensive online ` and `PDF documentation `_ + - :ref:`comprehensive online ` and `PDF documentation `_ - many :ref:`third party plugins ` and :ref:`builtin helpers `, - used in :ref:`many small and large projects and organisations ` - comes with many :ref:`tested examples ` From 9a686817196c40b85ff7690a975f5a917d4ecab3 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 26 Jul 2016 20:25:12 -0300 Subject: [PATCH 3/8] Point doc links in README to docs.pytest.org --- README.rst | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/README.rst b/README.rst index 68fc92211..bd4be0d3d 100644 --- a/README.rst +++ b/README.rst @@ -1,5 +1,5 @@ -.. image:: http://pytest.org/latest/_static/pytest1.png - :target: http://pytest.org +.. image:: http://docs.pytest.org/en/latest/_static/pytest1.png + :target: http://docs.pytest.org :align: center :alt: pytest @@ -51,33 +51,33 @@ To execute it:: test_sample.py:5: AssertionError ======= 1 failed in 0.12 seconds ======== -Due to ``py.test``'s detailed assertion introspection, only plain ``assert`` statements are used. See `getting-started `_ for more examples. +Due to ``py.test``'s detailed assertion introspection, only plain ``assert`` statements are used. See `getting-started `_ for more examples. Features -------- -- Detailed info on failing `assert statements `_ (no need to remember ``self.assert*`` names); +- Detailed info on failing `assert statements `_ (no need to remember ``self.assert*`` names); - `Auto-discovery - `_ + `_ of test modules and functions; -- `Modular fixtures `_ for +- `Modular fixtures `_ for managing small or parametrized long-lived test resources; -- Can run `unittest `_ (or trial), - `nose `_ test suites out of the box; +- Can run `unittest `_ (or trial), + `nose `_ test suites out of the box; - Python2.6+, Python3.2+, PyPy-2.3, Jython-2.5 (untested); -- Rich plugin architecture, with over 150+ `external plugins `_ and thriving community; +- Rich plugin architecture, with over 150+ `external plugins `_ and thriving community; Documentation ------------- -For full documentation, including installation, tutorials and PDF documents, please see http://pytest.org. +For full documentation, including installation, tutorials and PDF documents, please see http://docs.pytest.org. Bugs/Requests @@ -89,7 +89,7 @@ Please use the `GitHub issue tracker `_ page for fixes and enhancements of each version. +Consult the `Changelog `_ page for fixes and enhancements of each version. License From ed36d627e46622a17300fab567fc03332f6583c9 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 26 Jul 2016 21:29:07 -0300 Subject: [PATCH 4/8] 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 --- .travis.yml | 2 +- _pytest/genscript.py | 3 +- appveyor.yml | 2 +- doc/en/example/simple.rst | 43 ++++++------- testing/cx_freeze/install_cx_freeze.py | 64 ------------------- testing/cx_freeze/runtests_setup.py | 15 ----- testing/cx_freeze/tox_run.py | 15 ----- testing/freeze/.gitignore | 3 + testing/freeze/create_executable.py | 13 ++++ .../{cx_freeze => freeze}/runtests_script.py | 16 ++--- .../tests/test_doctest.txt | 0 .../tests/test_trivial.py | 10 +-- testing/freeze/tox_run.py | 12 ++++ tox.ini | 11 ++-- 14 files changed, 70 insertions(+), 139 deletions(-) delete mode 100644 testing/cx_freeze/install_cx_freeze.py delete mode 100644 testing/cx_freeze/runtests_setup.py delete mode 100644 testing/cx_freeze/tox_run.py create mode 100644 testing/freeze/.gitignore create mode 100644 testing/freeze/create_executable.py rename testing/{cx_freeze => freeze}/runtests_script.py (82%) rename testing/{cx_freeze => freeze}/tests/test_doctest.txt (100%) rename testing/{cx_freeze => freeze}/tests/test_trivial.py (65%) create mode 100644 testing/freeze/tox_run.py diff --git a/.travis.yml b/.travis.yml index 3a8f36e95..74483452c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -25,7 +25,7 @@ env: - TESTENV=py35-trial - TESTENV=py27-nobyte - TESTENV=doctesting - - TESTENV=py27-cxfreeze + - TESTENV=freeze script: tox --recreate -e $TESTENV diff --git a/_pytest/genscript.py b/_pytest/genscript.py index d2962d8fc..f070d8816 100755 --- a/_pytest/genscript.py +++ b/_pytest/genscript.py @@ -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)) diff --git a/appveyor.yml b/appveyor.yml index 2bd72db45..9c28366a0 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -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 diff --git a/doc/en/example/simple.rst b/doc/en/example/simple.rst index 1007ee1e1..76ee97ab3 100644 --- a/doc/en/example/simple.rst +++ b/doc/en/example/simple.rst @@ -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 `_ 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 `_ +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 diff --git a/testing/cx_freeze/install_cx_freeze.py b/testing/cx_freeze/install_cx_freeze.py deleted file mode 100644 index 83dce87aa..000000000 --- a/testing/cx_freeze/install_cx_freeze.py +++ /dev/null @@ -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) diff --git a/testing/cx_freeze/runtests_setup.py b/testing/cx_freeze/runtests_setup.py deleted file mode 100644 index 01e8a8a89..000000000 --- a/testing/cx_freeze/runtests_setup.py +++ /dev/null @@ -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()}}, - ) - diff --git a/testing/cx_freeze/tox_run.py b/testing/cx_freeze/tox_run.py deleted file mode 100644 index e8df2684b..000000000 --- a/testing/cx_freeze/tox_run.py +++ /dev/null @@ -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)) \ No newline at end of file diff --git a/testing/freeze/.gitignore b/testing/freeze/.gitignore new file mode 100644 index 000000000..490310b6c --- /dev/null +++ b/testing/freeze/.gitignore @@ -0,0 +1,3 @@ +build/ +dist/ +*.spec \ No newline at end of file diff --git a/testing/freeze/create_executable.py b/testing/freeze/create_executable.py new file mode 100644 index 000000000..8cf259c40 --- /dev/null +++ b/testing/freeze/create_executable.py @@ -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) + diff --git a/testing/cx_freeze/runtests_script.py b/testing/freeze/runtests_script.py similarity index 82% rename from testing/cx_freeze/runtests_script.py rename to testing/freeze/runtests_script.py index f2b032d76..cb961fc6c 100644 --- a/testing/cx_freeze/runtests_script.py +++ b/testing/freeze/runtests_script.py @@ -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()) \ No newline at end of file diff --git a/testing/cx_freeze/tests/test_doctest.txt b/testing/freeze/tests/test_doctest.txt similarity index 100% rename from testing/cx_freeze/tests/test_doctest.txt rename to testing/freeze/tests/test_doctest.txt diff --git a/testing/cx_freeze/tests/test_trivial.py b/testing/freeze/tests/test_trivial.py similarity index 65% rename from testing/cx_freeze/tests/test_trivial.py rename to testing/freeze/tests/test_trivial.py index d8a572baa..6cf6b05ad 100644 --- a/testing/cx_freeze/tests/test_trivial.py +++ b/testing/freeze/tests/test_trivial.py @@ -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' \ No newline at end of file diff --git a/testing/freeze/tox_run.py b/testing/freeze/tox_run.py new file mode 100644 index 000000000..5310ac1b7 --- /dev/null +++ b/testing/freeze/tox_run.py @@ -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)) \ No newline at end of file diff --git a/tox.ini b/tox.ini index 5fa9ef21e..b16a06887 100644 --- a/tox.ini +++ b/tox.ini @@ -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 From ae9d3bf886e25078bc399278f245e1740dbba986 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 27 Jul 2016 08:49:00 -0300 Subject: [PATCH 5/8] Freeze docs: PyInstaller hook and wording As discussed during the review, suggest in general to use PyInstaller and just mention pytest.freeze_includes() in less detail on how to actually use it, because it varies from tool to tool. --- doc/en/example/simple.rst | 38 +++++++++++++++----------------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/doc/en/example/simple.rst b/doc/en/example/simple.rst index 76ee97ab3..5275c52c8 100644 --- a/doc/en/example/simple.rst +++ b/doc/en/example/simple.rst @@ -699,8 +699,8 @@ and run it:: You'll see that the fixture finalizers could use the precise reporting information. -Integrating pytest runner and PyInstaller ------------------------------------------ +Freezing pytest +--------------- If you freeze your application using a tool like `PyInstaller `_ @@ -708,29 +708,21 @@ 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. +machines, which can be useful to obtain more information about a hard to reproduce bug. -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()`` and an -auxiliary script: +Fortunately recent ``PyInstaller`` releases already have a custom hook +for pytest, but if you are using another tool to freeze executables +such as ``cx_freeze`` or ``py2exe``, you can use ``pytest.freeze_includes()`` +to obtain the full list of internal pytest modules. How to configure the tools +to find the internal modules varies from tool to tool, however. + +Instead of freezing the pytest runner as a separate executable, you can make +your frozen program work as the pytest runner by some clever +argument handling during program startup. This allows you to +have a single executable, which is usually more convenient. .. code-block:: python - # contents of create_executable.py - import pytest - import subprocess - - 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 -over to ``pytest`` instead. For example:: - # contents of app_main.py import sys @@ -742,7 +734,7 @@ over to ``pytest`` instead. For example:: # by your argument-parsing library of choice as usual ... -This makes it convenient to execute your tests from within your frozen -application, using standard ``py.test`` command-line options:: +This allows you to execute tests using the frozen +application with standard ``py.test`` command-line options:: ./app_main --pytest --verbose --tb=long --junitxml=results.xml test-suite/ From a2891420de5a020fbcc09ee0af3124325334fc6d Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Mon, 20 Jun 2016 18:47:12 +0200 Subject: [PATCH 6/8] Fix determining rootdir from common_ancestor --- _pytest/config.py | 23 +++++++++++++++++---- testing/test_collection.py | 6 ++++-- testing/test_config.py | 42 +++++++++++++++++++++++++++++++++++--- 3 files changed, 62 insertions(+), 9 deletions(-) diff --git a/_pytest/config.py b/_pytest/config.py index 94de8a659..7345a8652 100644 --- a/_pytest/config.py +++ b/_pytest/config.py @@ -1095,6 +1095,8 @@ def get_common_ancestor(args): if str(arg)[0] == "-": continue p = py.path.local(arg) + if not p.exists(): + continue if common_ancestor is None: common_ancestor = p else: @@ -1108,21 +1110,28 @@ def get_common_ancestor(args): common_ancestor = shared if common_ancestor is None: common_ancestor = py.path.local() - elif not common_ancestor.isdir(): + elif common_ancestor.isfile(): common_ancestor = common_ancestor.dirpath() return common_ancestor +def get_dirs_from_args(args): + return [d for d in (py.path.local(x) for x in args + if not str(x).startswith("-")) + if d.exists()] + + def determine_setup(inifile, args): + dirs = get_dirs_from_args(args) if inifile: iniconfig = py.iniconfig.IniConfig(inifile) try: inicfg = iniconfig["pytest"] except KeyError: inicfg = None - rootdir = get_common_ancestor(args) + rootdir = get_common_ancestor(dirs) else: - ancestor = get_common_ancestor(args) + ancestor = get_common_ancestor(dirs) rootdir, inifile, inicfg = getcfg( [ancestor], ["pytest.ini", "tox.ini", "setup.cfg"]) if rootdir is None: @@ -1130,7 +1139,13 @@ def determine_setup(inifile, args): if rootdir.join("setup.py").exists(): break else: - rootdir = ancestor + rootdir, inifile, inicfg = getcfg( + dirs, ["pytest.ini", "tox.ini", "setup.cfg"]) + if rootdir is None: + rootdir = get_common_ancestor([py.path.local(), ancestor]) + is_fs_root = os.path.splitdrive(str(rootdir))[1] == os.sep + if is_fs_root: + rootdir = ancestor return rootdir, inifile, inicfg or {} diff --git a/testing/test_collection.py b/testing/test_collection.py index cd0a5cc19..b70dce367 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -490,7 +490,8 @@ class TestSession: class Test_getinitialnodes: def test_global_file(self, testdir, tmpdir): x = tmpdir.ensure("x.py") - config = testdir.parseconfigure(x) + with tmpdir.as_cwd(): + config = testdir.parseconfigure(x) col = testdir.getnode(config, x) assert isinstance(col, pytest.Module) assert col.name == 'x.py' @@ -504,7 +505,8 @@ class Test_getinitialnodes: subdir = tmpdir.join("subdir") x = subdir.ensure("x.py") subdir.ensure("__init__.py") - config = testdir.parseconfigure(x) + with subdir.as_cwd(): + config = testdir.parseconfigure(x) col = testdir.getnode(config, x) assert isinstance(col, pytest.Module) assert col.name == 'x.py' diff --git a/testing/test_config.py b/testing/test_config.py index 47c4a2adc..f1c762554 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -468,7 +468,8 @@ def test_consider_args_after_options_for_rootdir_and_inifile(testdir, args): args[i] = d1 elif arg == 'dir2': args[i] = d2 - result = testdir.runpytest(*args) + with root.as_cwd(): + result = testdir.runpytest(*args) result.stdout.fnmatch_lines(['*rootdir: *myroot, inifile: ']) @@ -547,10 +548,14 @@ class TestWarning: class TestRootdir: def test_simple_noini(self, tmpdir): assert get_common_ancestor([tmpdir]) == tmpdir - assert get_common_ancestor([tmpdir.mkdir("a"), tmpdir]) == tmpdir - assert get_common_ancestor([tmpdir, tmpdir.join("a")]) == tmpdir + a = tmpdir.mkdir("a") + assert get_common_ancestor([a, tmpdir]) == tmpdir + assert get_common_ancestor([tmpdir, a]) == tmpdir with tmpdir.as_cwd(): assert get_common_ancestor([]) == tmpdir + no_path = tmpdir.join('does-not-exist') + assert get_common_ancestor([no_path]) == tmpdir + assert get_common_ancestor([no_path.join('a')]) == tmpdir @pytest.mark.parametrize("name", "setup.cfg tox.ini pytest.ini".split()) def test_with_ini(self, tmpdir, name): @@ -595,3 +600,34 @@ class TestRootdir: inifile = tmpdir.ensure("pytest.ini") rootdir, inifile, inicfg = determine_setup(inifile, [tmpdir]) assert rootdir == tmpdir + + def test_with_arg_outside_cwd_without_inifile(self, tmpdir): + a = tmpdir.mkdir("a") + b = tmpdir.mkdir("b") + rootdir, inifile, inicfg = determine_setup(None, [a, b]) + assert rootdir == tmpdir + assert inifile is None + + def test_with_arg_outside_cwd_with_inifile(self, tmpdir): + a = tmpdir.mkdir("a") + b = tmpdir.mkdir("b") + inifile = a.ensure("pytest.ini") + rootdir, parsed_inifile, inicfg = determine_setup(None, [a, b]) + assert rootdir == a + assert inifile == parsed_inifile + + @pytest.mark.parametrize('dirs', ([], ['does-not-exist'], + ['a/does-not-exist'])) + def test_with_non_dir_arg(self, dirs, tmpdir): + with tmpdir.ensure(dir=True).as_cwd(): + rootdir, inifile, inicfg = determine_setup(None, dirs) + assert rootdir == tmpdir + assert inifile is None + + def test_with_existing_file_in_subdir(self, tmpdir): + a = tmpdir.mkdir("a") + a.ensure("exist") + with tmpdir.as_cwd(): + rootdir, inifile, inicfg = determine_setup(None, ['a/exist']) + assert rootdir == tmpdir + assert inifile is None From eb081352805d381454cdc9902ed66196ce9bd82b Mon Sep 17 00:00:00 2001 From: Dave Hunt Date: Tue, 21 Jun 2016 21:48:59 +0200 Subject: [PATCH 7/8] Update documentation to describe expected rootdir behaviour --- doc/en/customize.rst | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/doc/en/customize.rst b/doc/en/customize.rst index 34e319c24..a8e680967 100644 --- a/doc/en/customize.rst +++ b/doc/en/customize.rst @@ -29,25 +29,29 @@ project/testrun-specific information. Here is the algorithm which finds the rootdir from ``args``: -- determine the common ancestor directory for the specified ``args``. +- determine the common ancestor directory for the specified ``args`` that are + recognised as paths that exist in the file system. If no such paths are + found, the common ancestor directory is set to the current working directory. -- look for ``pytest.ini``, ``tox.ini`` and ``setup.cfg`` files in the - ancestor directory and upwards. If one is matched, it becomes the - ini-file and its directory becomes the rootdir. An existing - ``pytest.ini`` file will always be considered a match whereas - ``tox.ini`` and ``setup.cfg`` will only match if they contain - a ``[pytest]`` section. +- look for ``pytest.ini``, ``tox.ini`` and ``setup.cfg`` files in the ancestor + directory and upwards. If one is matched, it becomes the ini-file and its + directory becomes the rootdir. -- if no ini-file was found, look for ``setup.py`` upwards from - the common ancestor directory to determine the ``rootdir``. +- if no ini-file was found, look for ``setup.py`` upwards from the common + ancestor directory to determine the ``rootdir``. -- if no ini-file and no ``setup.py`` was found, use the already - determined common ancestor as root directory. This allows to - work with pytest in structures that are not part of a package - and don't have any particular ini-file configuration. +- if no ``setup.py`` was found, look for ``pytest.ini``, ``tox.ini`` and + ``setup.cfg`` in each of the specified ``args`` and upwards. If one is + matched, it becomes the ini-file and its directory becomes the rootdir. -Note that options from multiple ini-files candidates are never merged, -the first one wins (``pytest.ini`` always wins even if it does not +- if no ini-file was found, use the already determined common ancestor as root + directory. This allows to work with pytest in structures that are not part of + a package and don't have any particular ini-file configuration. + +Note that an existing ``pytest.ini`` file will always be considered a match, +whereas ``tox.ini`` and ``setup.cfg`` will only match if they contain a +``[pytest]`` section. Options from multiple ini-files candidates are never +merged - the first one wins (``pytest.ini`` always wins, even if it does not contain a ``[pytest]`` section). The ``config`` object will subsequently carry these attributes: From 21230aa017538dd4a508778f47b2e35cd96b7f78 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 6 Aug 2016 17:14:13 -0300 Subject: [PATCH 8/8] Add CHANGELOG entry --- CHANGELOG.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5e179050d..5cebf083d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -35,6 +35,10 @@ deprecated but still present. Thanks to `@RedBeardCode`_ and `@tomviner`_ for PR (`#1626`_). +* Refined logic for determining the ``rootdir``, considering only valid + paths which fixes a number of issues: `#1594`_, `#1435`_ and `#1471`_. + Thanks to `@blueyed`_ and `@davehunt`_ for the PR. + * Always include full assertion explanation. The previous behaviour was hiding sub-expressions that happened to be False, assuming this was redundant information. Thanks `@bagerard`_ for reporting (`#1503`_). Thanks to `@davehunt`_ and @@ -70,11 +74,14 @@ * .. _#1210: https://github.com/pytest-dev/pytest/issues/1210 +.. _#1435: https://github.com/pytest-dev/pytest/issues/1435 +.. _#1471: https://github.com/pytest-dev/pytest/issues/1471 .. _#1479: https://github.com/pytest-dev/pytest/issues/1479 .. _#1503: https://github.com/pytest-dev/pytest/issues/1503 .. _#1553: https://github.com/pytest-dev/pytest/issues/1553 .. _#1579: https://github.com/pytest-dev/pytest/issues/1579 .. _#1580: https://github.com/pytest-dev/pytest/pull/1580 +.. _#1594: https://github.com/pytest-dev/pytest/issues/1594 .. _#1597: https://github.com/pytest-dev/pytest/pull/1597 .. _#1605: https://github.com/pytest-dev/pytest/issues/1605 .. _#1626: https://github.com/pytest-dev/pytest/pull/1626