From commits-noreply at bitbucket.org Fri May 1 01:28:25 2015 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 30 Apr 2015 23:28:25 -0000 Subject: [Pytest-commit] commit/pytest: 4 new changesets Message-ID: <20150430232825.25066.50042@app11.ash-private.bitbucket.org> 4 new commits in pytest: https://bitbucket.org/pytest-dev/pytest/commits/5d3bd2512a06/ Changeset: 5d3bd2512a06 Branch: pytest-2.7 User: flub Date: 2015-04-30 21:54:43+00:00 Summary: Prepare docs for bugfix release Affected #: 2 files diff -r 219483189eb71cca40376c15fb6ffebd492a3d12 -r 5d3bd2512a06a725a2067de19c2cf4f9402b8b4b doc/en/announce/index.txt --- a/doc/en/announce/index.txt +++ b/doc/en/announce/index.txt @@ -5,6 +5,8 @@ .. toctree:: :maxdepth: 2 + release-2.7.1 + release-2.7.0 release-2.6.3 release-2.6.2 release-2.6.1 diff -r 219483189eb71cca40376c15fb6ffebd492a3d12 -r 5d3bd2512a06a725a2067de19c2cf4f9402b8b4b doc/en/announce/release-2.7.1.txt --- /dev/null +++ b/doc/en/announce/release-2.7.1.txt @@ -0,0 +1,58 @@ +pytest-2.7.1: bug fixes +======================= + +pytest is a mature Python testing tool with more than a 1100 tests +against itself, passing on many different interpreters and platforms. +This release is supposed to be drop-in compatible to 2.7.0. + +See below for the changes and see docs at: + + http://pytest.org + +As usual, you can upgrade from pypi via:: + + pip install -U pytest + +Thanks to all who contributed to this release, among them: + + Bruno Oliveira + Holger Krekel + Ionel Maries Cristian + Floris Bruynooghe + +Happy testing, +The py.test Development Team + + +2.7.1 (compared to 2.7.0) +------------------------- + +- fix issue731: do not get confused by the braces which may be present + and unbalanced in an object's repr while collapsing False + explanations. Thanks Carl Meyer for the report and test case. + +- fix issue553: properly handling inspect.getsourcelines failures in + FixtureLookupError which would lead to to an internal error, + obfuscating the original problem. Thanks talljosh for initial + diagnose/patch and Bruno Oliveira for final patch. + +- fix issue660: properly report scope-mismatch-access errors + independently from ordering of fixture arguments. Also + avoid the pytest internal traceback which does not provide + information to the user. Thanks Holger Krekel. + +- streamlined and documented release process. Also all versions + (in setup.py and documentation generation) are now read + from _pytest/__init__.py. Thanks Holger Krekel. + +- fixed docs to remove the notion that yield-fixtures are experimental. + They are here to stay :) Thanks Bruno Oliveira. + +- Support building wheels by using environment markers for the + requirements. Thanks Ionel Maries Cristian. + +- fixed regression to 2.6.4 which surfaced e.g. in lost stdout capture printing + when tests raised SystemExit. Thanks Holger Krekel. + +- reintroduced _pytest fixture of the pytester plugin which is used + at least by pytest-xdist. https://bitbucket.org/pytest-dev/pytest/commits/6ec6b4170c89/ Changeset: 6ec6b4170c89 Branch: pytest-2.7 User: flub Date: 2015-04-30 21:55:01+00:00 Summary: Some small corrections to release process Affected #: 1 file diff -r 5d3bd2512a06a725a2067de19c2cf4f9402b8b4b -r 6ec6b4170c89088862a09a30d1c455502eb089e4 HOWTORELEASE.rst --- a/HOWTORELEASE.rst +++ b/HOWTORELEASE.rst @@ -2,11 +2,11 @@ How to release pytest (draft) -------------------------------------------- -1. bump version numbers in setup.py and pytest/__init__.py +1. bump version numbers in pytest/__init__.py (setup.py reads it) 2. check and finalize CHANGELOG -3. write doc/en/announce/pytest-VERSION.txt and include +3. write doc/en/announce/release-VERSION.txt and include it in doc/en/announce/index.txt 4. use devpi for uploading a release tarball to a staging area: https://bitbucket.org/pytest-dev/pytest/commits/3c21f64ac44a/ Changeset: 3c21f64ac44a Branch: pytest-2.7 User: flub Date: 2015-04-30 23:27:04+00:00 Summary: Some more tweaks of how to release Affected #: 1 file diff -r 6ec6b4170c89088862a09a30d1c455502eb089e4 -r 3c21f64ac44a43ad2b52211f152fa7f9e87c9de5 HOWTORELEASE.rst --- a/HOWTORELEASE.rst +++ b/HOWTORELEASE.rst @@ -15,7 +15,7 @@ 5. run from multiple machines: - ``devpi use https://devpi.net/USER/dev`` - - ``devpi test pytest-VERSION`` + - ``devpi test pytest==VERSION`` 6. check that tests pass for relevant combinations with ``devpi list pytest`` @@ -25,14 +25,15 @@ which is ok (tox does not support skipping on per-platform basis yet). -7. XXX "regen docs" (not easy to do currently as it requires - a development version of the regendoc tool from ronny) +7. Regenerate the docs using the toplevel makefile:: + make docs -8. go to "doc/en" and upload docs with "make install" - (the latter requires ssh-login permissions on pytest.org - because it uses rsync). Note that the "install" target of - doc/en/Makefile defines where the rsync goes to, typically - to the "latest" section of pytest.org. +8. Upload the docs using the toplevel makefile:: + make upload-docs + This requires ssh-login permission on pytest.org because it uses + rsync. + Note that the "install" target of doc/en/Makefile defines where the + rsync goes to, typically to the "latest" section of pytest.org. 9. publish to pypi "devpi push pytest-2.6.2 pypi:NAME" where NAME is the name of pypi.python.org as configured in your https://bitbucket.org/pytest-dev/pytest/commits/13b32b864565/ Changeset: 13b32b864565 Branch: pytest-2.7 User: flub Date: 2015-04-30 23:27:47+00:00 Summary: Use current regendocs location Affected #: 1 file diff -r 3c21f64ac44a43ad2b52211f152fa7f9e87c9de5 -r 13b32b864565690c56777a989558772303347e50 requirements-docs.txt --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,2 +1,2 @@ sphinx==1.2.3 -hg+ssh://hg at bitbucket.org/RonnyPfannschmidt/regendoc#egg=regendoc +hg+ssh://hg at bitbucket.org/pytest-dev/regendoc#egg=regendoc Repository URL: https://bitbucket.org/pytest-dev/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From builds at drone.io Fri May 1 01:38:47 2015 From: builds at drone.io (Drone.io Build) Date: Thu, 30 Apr 2015 23:38:47 +0000 Subject: [Pytest-commit] [FAIL] pytest - # 106 Message-ID: <20150430233847.124206.32716@drone.io> Build Failed Build : https://drone.io/bitbucket.org/pytest-dev/pytest/106 Project : https://drone.io/bitbucket.org/pytest-dev/pytest Repository : https://bitbucket.org/pytest-dev/pytest Version : 3976:13b32b864565 Author : Floris Bruynooghe Branch : pytest-2.7 Message: Use current regendocs location -------------- next part -------------- An HTML attachment was scrubbed... URL: From issues-reply at bitbucket.org Fri May 1 17:49:14 2015 From: issues-reply at bitbucket.org (Willem Mali) Date: Fri, 01 May 2015 15:49:14 -0000 Subject: [Pytest-commit] Issue #242: pip install --user tox doesn't add tox to path (hpk42/tox) Message-ID: <20150501154914.32476.47961@app08.ash-private.bitbucket.org> New issue 242: pip install --user tox doesn't add tox to path https://bitbucket.org/hpk42/tox/issue/242/pip-install-user-tox-doesnt-add-tox-to Willem Mali: After installing tox using the command: `pip install --user tox` It wasn't added to my path, making bash say it wasn't currently installed and could be installed from Ubuntu universe from the package `python-tox`, in response to the command `tox`. However, `python -m tox` did work. I'm running Lubuntu 14.04. This is possibly a bug of `pip`. From commits-noreply at bitbucket.org Sun May 3 13:08:33 2015 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sun, 03 May 2015 11:08:33 -0000 Subject: [Pytest-commit] commit/tox: hpk42: Merged in stevepiercy/tox/stevepiercy/grammar-fixes-1430426650165 (pull request #149) Message-ID: <20150503110833.18656.54204@app04.ash-private.bitbucket.org> 1 new commit in tox: https://bitbucket.org/hpk42/tox/commits/a0c25fab52d7/ Changeset: a0c25fab52d7 User: hpk42 Date: 2015-05-03 11:08:30+00:00 Summary: Merged in stevepiercy/tox/stevepiercy/grammar-fixes-1430426650165 (pull request #149) grammar fixes Affected #: 1 file diff -r 273d12589a2548f4a0603da9d82ade8a284d4196 -r a0c25fab52d76a62bafba137710ab8ec4e57c9e2 doc/example/general.txt --- a/doc/example/general.txt +++ b/doc/example/general.txt @@ -147,8 +147,8 @@ Avoiding expensive sdist ------------------------ -Some projects are large enough that running and sdist, followed by -an install everytime can be prohibitively costly. To solve this, +Some projects are large enough that running an sdist, followed by +an install every time can be prohibitively costly. To solve this, there are two different options you can add to the ``tox`` section. First, you can simply ask tox to please not make an sdist:: @@ -156,7 +156,7 @@ skipsdist=True If you do this, your local software package will not be installed into -the virtualenv. You should probably be ok with that, or take steps +the virtualenv. You should probably be okay with that, or take steps to deal with it in your commands section:: [testenv] @@ -164,7 +164,8 @@ python setup.py develop py.test -Running setup.py develop is a common enough model that it has its own option:: +Running ``setup.py develop`` is a common enough model that it has its own +option:: [testenv] usedevelop=True Repository URL: https://bitbucket.org/hpk42/tox/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Sun May 3 13:08:33 2015 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sun, 03 May 2015 11:08:33 -0000 Subject: [Pytest-commit] commit/tox: 2 new changesets Message-ID: <20150503110833.10858.77319@app10.ash-private.bitbucket.org> 2 new commits in tox: https://bitbucket.org/hpk42/tox/commits/3f0fdafd5327/ Changeset: 3f0fdafd5327 Branch: stevepiercy/grammar-fixes-1430426650165 User: stevepiercy Date: 2015-04-30 20:44:12+00:00 Summary: grammar fixes Affected #: 1 file diff -r 273d12589a2548f4a0603da9d82ade8a284d4196 -r 3f0fdafd5327139825b4a0b6602a0de4f1ac2333 doc/example/general.txt --- a/doc/example/general.txt +++ b/doc/example/general.txt @@ -147,8 +147,8 @@ Avoiding expensive sdist ------------------------ -Some projects are large enough that running and sdist, followed by -an install everytime can be prohibitively costly. To solve this, +Some projects are large enough that running an sdist, followed by +an install every time can be prohibitively costly. To solve this, there are two different options you can add to the ``tox`` section. First, you can simply ask tox to please not make an sdist:: @@ -156,7 +156,7 @@ skipsdist=True If you do this, your local software package will not be installed into -the virtualenv. You should probably be ok with that, or take steps +the virtualenv. You should probably be okay with that, or take steps to deal with it in your commands section:: [testenv] @@ -164,7 +164,8 @@ python setup.py develop py.test -Running setup.py develop is a common enough model that it has its own option:: +Running ``setup.py develop`` is a common enough model that it has its own +option:: [testenv] usedevelop=True https://bitbucket.org/hpk42/tox/commits/a0c25fab52d7/ Changeset: a0c25fab52d7 User: hpk42 Date: 2015-05-03 11:08:30+00:00 Summary: Merged in stevepiercy/tox/stevepiercy/grammar-fixes-1430426650165 (pull request #149) grammar fixes Affected #: 1 file diff -r 273d12589a2548f4a0603da9d82ade8a284d4196 -r a0c25fab52d76a62bafba137710ab8ec4e57c9e2 doc/example/general.txt --- a/doc/example/general.txt +++ b/doc/example/general.txt @@ -147,8 +147,8 @@ Avoiding expensive sdist ------------------------ -Some projects are large enough that running and sdist, followed by -an install everytime can be prohibitively costly. To solve this, +Some projects are large enough that running an sdist, followed by +an install every time can be prohibitively costly. To solve this, there are two different options you can add to the ``tox`` section. First, you can simply ask tox to please not make an sdist:: @@ -156,7 +156,7 @@ skipsdist=True If you do this, your local software package will not be installed into -the virtualenv. You should probably be ok with that, or take steps +the virtualenv. You should probably be okay with that, or take steps to deal with it in your commands section:: [testenv] @@ -164,7 +164,8 @@ python setup.py develop py.test -Running setup.py develop is a common enough model that it has its own option:: +Running ``setup.py develop`` is a common enough model that it has its own +option:: [testenv] usedevelop=True Repository URL: https://bitbucket.org/hpk42/tox/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Mon May 4 14:26:28 2015 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Mon, 04 May 2015 12:26:28 -0000 Subject: [Pytest-commit] commit/pytest: Eric Siegerman: Doc typo Message-ID: <20150504122628.25646.2405@app08.ash-private.bitbucket.org> 1 new commit in pytest: https://bitbucket.org/pytest-dev/pytest/commits/c490ad387968/ Changeset: c490ad387968 Branch: pytest-2.7 User: Eric Siegerman Date: 2015-05-01 15:18:17+00:00 Summary: Doc typo Affected #: 1 file diff -r 13b32b864565690c56777a989558772303347e50 -r c490ad387968c6c4691d369213241b9038203da1 doc/en/example/markers.txt --- a/doc/en/example/markers.txt +++ b/doc/en/example/markers.txt @@ -55,8 +55,8 @@ ================= 1 tests deselected by "-m 'not webtest'" ================= ================== 3 passed, 1 deselected in 0.01 seconds ================== -Selecing tests based on their node ID -------------------------------------- +Selecting tests based on their node ID +-------------------------------------- You can provide one or more :ref:`node IDs ` as positional arguments to select only specified tests. This makes it easy to select Repository URL: https://bitbucket.org/pytest-dev/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Mon May 4 14:28:37 2015 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Mon, 04 May 2015 12:28:37 -0000 Subject: [Pytest-commit] commit/pytest: 2 new changesets Message-ID: <20150504122837.5482.8321@app02.ash-private.bitbucket.org> 2 new commits in pytest: https://bitbucket.org/pytest-dev/pytest/commits/aaca1518a32a/ Changeset: aaca1518a32a Branch: famousgarkin/monkeypatchchdir-docstring-punctuation-1430453576841 User: famousgarkin Date: 2015-05-01 04:13:06+00:00 Summary: monkeypatch.chdir docstring punctuation Affected #: 1 file diff -r ce106625d6b53f0ff6e9dce8a1cf0c761341b2f4 -r aaca1518a32ad565e4f59d85defbfc8fa396125c _pytest/monkeypatch.py --- a/_pytest/monkeypatch.py +++ b/_pytest/monkeypatch.py @@ -177,8 +177,8 @@ sys.path.insert(0, str(path)) def chdir(self, path): - """ Change the current working directory to the specified path - path can be a string or a py.path.local object + """ Change the current working directory to the specified path. + Path can be a string or a py.path.local object. """ if self._cwd is None: self._cwd = os.getcwd() https://bitbucket.org/pytest-dev/pytest/commits/f78c26828b71/ Changeset: f78c26828b71 User: hpk42 Date: 2015-05-04 12:28:32+00:00 Summary: Merged in famousgarkin/pytest/famousgarkin/monkeypatchchdir-docstring-punctuation-1430453576841 (pull request #288) monkeypatch.chdir docstring punctuation Affected #: 1 file diff -r ce106625d6b53f0ff6e9dce8a1cf0c761341b2f4 -r f78c26828b715a99d7373c6e4b4618215872d403 _pytest/monkeypatch.py --- a/_pytest/monkeypatch.py +++ b/_pytest/monkeypatch.py @@ -177,8 +177,8 @@ sys.path.insert(0, str(path)) def chdir(self, path): - """ Change the current working directory to the specified path - path can be a string or a py.path.local object + """ Change the current working directory to the specified path. + Path can be a string or a py.path.local object. """ if self._cwd is None: self._cwd = os.getcwd() Repository URL: https://bitbucket.org/pytest-dev/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Mon May 4 14:28:37 2015 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Mon, 04 May 2015 12:28:37 -0000 Subject: [Pytest-commit] commit/pytest: hpk42: Merged in famousgarkin/pytest/famousgarkin/monkeypatchchdir-docstring-punctuation-1430453576841 (pull request #288) Message-ID: <20150504122837.27681.21597@app12.ash-private.bitbucket.org> 1 new commit in pytest: https://bitbucket.org/pytest-dev/pytest/commits/f78c26828b71/ Changeset: f78c26828b71 User: hpk42 Date: 2015-05-04 12:28:32+00:00 Summary: Merged in famousgarkin/pytest/famousgarkin/monkeypatchchdir-docstring-punctuation-1430453576841 (pull request #288) monkeypatch.chdir docstring punctuation Affected #: 1 file diff -r ce106625d6b53f0ff6e9dce8a1cf0c761341b2f4 -r f78c26828b715a99d7373c6e4b4618215872d403 _pytest/monkeypatch.py --- a/_pytest/monkeypatch.py +++ b/_pytest/monkeypatch.py @@ -177,8 +177,8 @@ sys.path.insert(0, str(path)) def chdir(self, path): - """ Change the current working directory to the specified path - path can be a string or a py.path.local object + """ Change the current working directory to the specified path. + Path can be a string or a py.path.local object. """ if self._cwd is None: self._cwd = os.getcwd() Repository URL: https://bitbucket.org/pytest-dev/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From builds at drone.io Mon May 4 14:36:49 2015 From: builds at drone.io (Drone.io Build) Date: Mon, 04 May 2015 12:36:49 +0000 Subject: [Pytest-commit] [FAIL] pytest - # 107 Message-ID: <20150504123649.11924.98318@drone.io> Build Failed Build : https://drone.io/bitbucket.org/pytest-dev/pytest/107 Project : https://drone.io/bitbucket.org/pytest-dev/pytest Repository : https://bitbucket.org/pytest-dev/pytest Version : 3977:c490ad387968 Author : Eric Siegerman Branch : pytest-2.7 Message: Doc typo -------------- next part -------------- An HTML attachment was scrubbed... URL: From builds at drone.io Mon May 4 14:37:56 2015 From: builds at drone.io (Drone.io Build) Date: Mon, 04 May 2015 12:37:56 +0000 Subject: [Pytest-commit] [FAIL] pytest - # 108 Message-ID: <20150504123756.84504.56963@drone.io> Build Failed Build : https://drone.io/bitbucket.org/pytest-dev/pytest/108 Project : https://drone.io/bitbucket.org/pytest-dev/pytest Repository : https://bitbucket.org/pytest-dev/pytest Version : 4225:f78c26828b71 Author : holger krekel Branch : default Message: Merged in famousgarkin/pytest/famousgarkin/monkeypatchchdir-docstring-punctuation-1430453576841 (pull request #288) -------------- next part -------------- An HTML attachment was scrubbed... URL: From commits-noreply at bitbucket.org Mon May 4 14:40:49 2015 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Mon, 04 May 2015 12:40:49 -0000 Subject: [Pytest-commit] commit/tox: hpk42: Merged in stevepiercy/tox (pull request #150) Message-ID: <20150504124049.8358.73275@app05.ash-private.bitbucket.org> 1 new commit in tox: https://bitbucket.org/hpk42/tox/commits/868a79b11ff5/ Changeset: 868a79b11ff5 User: hpk42 Date: 2015-05-04 12:40:47+00:00 Summary: Merged in stevepiercy/tox (pull request #150) grammar fix Affected #: 1 file diff -r a0c25fab52d76a62bafba137710ab8ec4e57c9e2 -r 868a79b11ff5fc09b4d13ab7641fb47c77780a91 doc/example/jenkins.txt --- a/doc/example/jenkins.txt +++ b/doc/example/jenkins.txt @@ -148,7 +148,7 @@ Now everything proceeds as :ref:`artifacts` shows it. So if you are using defaults you can re-use and debug exactly the -same ``tox.ini`` file and make use of automatical sharing of +same ``tox.ini`` file and make use of automatic sharing of your artifacts between runs or Jenkins jobs. .. include:: ../links.txt Repository URL: https://bitbucket.org/hpk42/tox/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Mon May 4 14:40:49 2015 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Mon, 04 May 2015 12:40:49 -0000 Subject: [Pytest-commit] commit/tox: 2 new changesets Message-ID: <20150504124049.17529.54839@app04.ash-private.bitbucket.org> 2 new commits in tox: https://bitbucket.org/hpk42/tox/commits/c51e16686f21/ Changeset: c51e16686f21 User: stevepiercy Date: 2015-04-30 21:11:53+00:00 Summary: grammar fix Affected #: 1 file diff -r 273d12589a2548f4a0603da9d82ade8a284d4196 -r c51e16686f21ff29938f3801ffcbb9ed43607977 doc/example/jenkins.txt --- a/doc/example/jenkins.txt +++ b/doc/example/jenkins.txt @@ -148,7 +148,7 @@ Now everything proceeds as :ref:`artifacts` shows it. So if you are using defaults you can re-use and debug exactly the -same ``tox.ini`` file and make use of automatical sharing of +same ``tox.ini`` file and make use of automatic sharing of your artifacts between runs or Jenkins jobs. .. include:: ../links.txt https://bitbucket.org/hpk42/tox/commits/868a79b11ff5/ Changeset: 868a79b11ff5 User: hpk42 Date: 2015-05-04 12:40:47+00:00 Summary: Merged in stevepiercy/tox (pull request #150) grammar fix Affected #: 1 file diff -r a0c25fab52d76a62bafba137710ab8ec4e57c9e2 -r 868a79b11ff5fc09b4d13ab7641fb47c77780a91 doc/example/jenkins.txt --- a/doc/example/jenkins.txt +++ b/doc/example/jenkins.txt @@ -148,7 +148,7 @@ Now everything proceeds as :ref:`artifacts` shows it. So if you are using defaults you can re-use and debug exactly the -same ``tox.ini`` file and make use of automatical sharing of +same ``tox.ini`` file and make use of automatic sharing of your artifacts between runs or Jenkins jobs. .. include:: ../links.txt Repository URL: https://bitbucket.org/hpk42/tox/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From issues-reply at bitbucket.org Mon May 4 17:09:48 2015 From: issues-reply at bitbucket.org (Jacobo Giralt) Date: Mon, 04 May 2015 15:09:48 -0000 Subject: [Pytest-commit] Issue #736: Parametrized argument named similarly to fixture (contains it) breaks that fixture's parametrization (pytest-dev/pytest) Message-ID: <20150504150948.31762.89634@app09.ash-private.bitbucket.org> New issue 736: Parametrized argument named similarly to fixture (contains it) breaks that fixture's parametrization https://bitbucket.org/pytest-dev/pytest/issue/736/parametrized-argument-named-similarly-to Jacobo Giralt: ``` #!python import pytest @pytest.fixture(params=[1,2,3]) def foo(request): return request.param @pytest.mark.parametrize('foobar', [4,5,6]) def test_issue(foo, foobar): assert True ``` ``` #!python request = > @pytest.fixture(params=[1,2,3]) def foo(request): > return request.param AttributeError: SubRequest instance has no attribute 'param' ``` The issue seems reproducible as long as the parametrized test argument contains the fixture name as a substring, e.g. foobar, barfoo, foox, xfoo, disappears as soon as I modify the fixture name to anything else. This same code works in pytest-2.6.4 and fails for 2.7. From issues-reply at bitbucket.org Tue May 5 10:56:21 2015 From: issues-reply at bitbucket.org (Ronny Pfannschmidt) Date: Tue, 05 May 2015 08:56:21 -0000 Subject: [Pytest-commit] Issue #737: position tracked captureas optimization (pytest-dev/pytest) Message-ID: <20150505085621.2683.81518@app06.ash-private.bitbucket.org> New issue 737: position tracked captureas optimization https://bitbucket.org/pytest-dev/pytest/issue/737/position-tracked-captureas-optimization Ronny Pfannschmidt: currently details like io capture and log capure make a new fd/item per test i believe the io capture setup/teardown process could be much quicker and more detailed, if instead of having a new capture per item/detail, ther would be one tracked io per stream, and pytest would track boundaries by stream positions as downside its possible that unrelated items from other tests write, but practically that is also possible with the current capture when the writes come from a c api for example From commits-noreply at bitbucket.org Tue May 5 21:55:56 2015 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 05 May 2015 19:55:56 -0000 Subject: [Pytest-commit] commit/pytest: 3 new changesets Message-ID: <20150505195556.22669.40093@app02.ash-private.bitbucket.org> 3 new commits in pytest: https://bitbucket.org/pytest-dev/pytest/commits/e2b5345d4554/ Changeset: e2b5345d4554 User: hpk42 Date: 2015-05-04 12:27:09+00:00 Summary: merging pytest-2.7 Affected #: 4 files diff -r ce106625d6b53f0ff6e9dce8a1cf0c761341b2f4 -r e2b5345d455488007861895c7f13b01ffddb1c97 HOWTORELEASE.rst --- a/HOWTORELEASE.rst +++ b/HOWTORELEASE.rst @@ -2,11 +2,11 @@ How to release pytest (draft) -------------------------------------------- -1. bump version numbers in setup.py and pytest/__init__.py +1. bump version numbers in pytest/__init__.py (setup.py reads it) 2. check and finalize CHANGELOG -3. write doc/en/announce/pytest-VERSION.txt and include +3. write doc/en/announce/release-VERSION.txt and include it in doc/en/announce/index.txt 4. use devpi for uploading a release tarball to a staging area: @@ -15,7 +15,7 @@ 5. run from multiple machines: - ``devpi use https://devpi.net/USER/dev`` - - ``devpi test pytest-VERSION`` + - ``devpi test pytest==VERSION`` 6. check that tests pass for relevant combinations with ``devpi list pytest`` @@ -25,14 +25,15 @@ which is ok (tox does not support skipping on per-platform basis yet). -7. XXX "regen docs" (not easy to do currently as it requires - a development version of the regendoc tool from ronny) +7. Regenerate the docs using the toplevel makefile:: + make docs -8. go to "doc/en" and upload docs with "make install" - (the latter requires ssh-login permissions on pytest.org - because it uses rsync). Note that the "install" target of - doc/en/Makefile defines where the rsync goes to, typically - to the "latest" section of pytest.org. +8. Upload the docs using the toplevel makefile:: + make upload-docs + This requires ssh-login permission on pytest.org because it uses + rsync. + Note that the "install" target of doc/en/Makefile defines where the + rsync goes to, typically to the "latest" section of pytest.org. 9. publish to pypi "devpi push pytest-2.6.2 pypi:NAME" where NAME is the name of pypi.python.org as configured in your diff -r ce106625d6b53f0ff6e9dce8a1cf0c761341b2f4 -r e2b5345d455488007861895c7f13b01ffddb1c97 doc/en/announce/index.txt --- a/doc/en/announce/index.txt +++ b/doc/en/announce/index.txt @@ -5,6 +5,8 @@ .. toctree:: :maxdepth: 2 + release-2.7.1 + release-2.7.0 release-2.6.3 release-2.6.2 release-2.6.1 diff -r ce106625d6b53f0ff6e9dce8a1cf0c761341b2f4 -r e2b5345d455488007861895c7f13b01ffddb1c97 doc/en/announce/release-2.7.1.txt --- /dev/null +++ b/doc/en/announce/release-2.7.1.txt @@ -0,0 +1,58 @@ +pytest-2.7.1: bug fixes +======================= + +pytest is a mature Python testing tool with more than a 1100 tests +against itself, passing on many different interpreters and platforms. +This release is supposed to be drop-in compatible to 2.7.0. + +See below for the changes and see docs at: + + http://pytest.org + +As usual, you can upgrade from pypi via:: + + pip install -U pytest + +Thanks to all who contributed to this release, among them: + + Bruno Oliveira + Holger Krekel + Ionel Maries Cristian + Floris Bruynooghe + +Happy testing, +The py.test Development Team + + +2.7.1 (compared to 2.7.0) +------------------------- + +- fix issue731: do not get confused by the braces which may be present + and unbalanced in an object's repr while collapsing False + explanations. Thanks Carl Meyer for the report and test case. + +- fix issue553: properly handling inspect.getsourcelines failures in + FixtureLookupError which would lead to to an internal error, + obfuscating the original problem. Thanks talljosh for initial + diagnose/patch and Bruno Oliveira for final patch. + +- fix issue660: properly report scope-mismatch-access errors + independently from ordering of fixture arguments. Also + avoid the pytest internal traceback which does not provide + information to the user. Thanks Holger Krekel. + +- streamlined and documented release process. Also all versions + (in setup.py and documentation generation) are now read + from _pytest/__init__.py. Thanks Holger Krekel. + +- fixed docs to remove the notion that yield-fixtures are experimental. + They are here to stay :) Thanks Bruno Oliveira. + +- Support building wheels by using environment markers for the + requirements. Thanks Ionel Maries Cristian. + +- fixed regression to 2.6.4 which surfaced e.g. in lost stdout capture printing + when tests raised SystemExit. Thanks Holger Krekel. + +- reintroduced _pytest fixture of the pytester plugin which is used + at least by pytest-xdist. diff -r ce106625d6b53f0ff6e9dce8a1cf0c761341b2f4 -r e2b5345d455488007861895c7f13b01ffddb1c97 doc/en/example/markers.txt --- a/doc/en/example/markers.txt +++ b/doc/en/example/markers.txt @@ -55,8 +55,8 @@ ================= 1 tests deselected by "-m 'not webtest'" ================= ================== 3 passed, 1 deselected in 0.01 seconds ================== -Selecing tests based on their node ID -------------------------------------- +Selecting tests based on their node ID +-------------------------------------- You can provide one or more :ref:`node IDs ` as positional arguments to select only specified tests. This makes it easy to select https://bitbucket.org/pytest-dev/pytest/commits/e42ecb41adbd/ Changeset: e42ecb41adbd Branch: famousgarkin/monkeypatchchdir-docstring-punctuation-1430453576841 User: hpk42 Date: 2015-05-05 19:54:34+00:00 Summary: close branch Affected #: 0 files https://bitbucket.org/pytest-dev/pytest/commits/e14e1c5215fc/ Changeset: e14e1c5215fc User: hpk42 Date: 2015-05-05 19:55:39+00:00 Summary: merge Affected #: 4 files diff -r f78c26828b715a99d7373c6e4b4618215872d403 -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 HOWTORELEASE.rst --- a/HOWTORELEASE.rst +++ b/HOWTORELEASE.rst @@ -2,11 +2,11 @@ How to release pytest (draft) -------------------------------------------- -1. bump version numbers in setup.py and pytest/__init__.py +1. bump version numbers in pytest/__init__.py (setup.py reads it) 2. check and finalize CHANGELOG -3. write doc/en/announce/pytest-VERSION.txt and include +3. write doc/en/announce/release-VERSION.txt and include it in doc/en/announce/index.txt 4. use devpi for uploading a release tarball to a staging area: @@ -15,7 +15,7 @@ 5. run from multiple machines: - ``devpi use https://devpi.net/USER/dev`` - - ``devpi test pytest-VERSION`` + - ``devpi test pytest==VERSION`` 6. check that tests pass for relevant combinations with ``devpi list pytest`` @@ -25,14 +25,15 @@ which is ok (tox does not support skipping on per-platform basis yet). -7. XXX "regen docs" (not easy to do currently as it requires - a development version of the regendoc tool from ronny) +7. Regenerate the docs using the toplevel makefile:: + make docs -8. go to "doc/en" and upload docs with "make install" - (the latter requires ssh-login permissions on pytest.org - because it uses rsync). Note that the "install" target of - doc/en/Makefile defines where the rsync goes to, typically - to the "latest" section of pytest.org. +8. Upload the docs using the toplevel makefile:: + make upload-docs + This requires ssh-login permission on pytest.org because it uses + rsync. + Note that the "install" target of doc/en/Makefile defines where the + rsync goes to, typically to the "latest" section of pytest.org. 9. publish to pypi "devpi push pytest-2.6.2 pypi:NAME" where NAME is the name of pypi.python.org as configured in your diff -r f78c26828b715a99d7373c6e4b4618215872d403 -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 doc/en/announce/index.txt --- a/doc/en/announce/index.txt +++ b/doc/en/announce/index.txt @@ -5,6 +5,8 @@ .. toctree:: :maxdepth: 2 + release-2.7.1 + release-2.7.0 release-2.6.3 release-2.6.2 release-2.6.1 diff -r f78c26828b715a99d7373c6e4b4618215872d403 -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 doc/en/announce/release-2.7.1.txt --- /dev/null +++ b/doc/en/announce/release-2.7.1.txt @@ -0,0 +1,58 @@ +pytest-2.7.1: bug fixes +======================= + +pytest is a mature Python testing tool with more than a 1100 tests +against itself, passing on many different interpreters and platforms. +This release is supposed to be drop-in compatible to 2.7.0. + +See below for the changes and see docs at: + + http://pytest.org + +As usual, you can upgrade from pypi via:: + + pip install -U pytest + +Thanks to all who contributed to this release, among them: + + Bruno Oliveira + Holger Krekel + Ionel Maries Cristian + Floris Bruynooghe + +Happy testing, +The py.test Development Team + + +2.7.1 (compared to 2.7.0) +------------------------- + +- fix issue731: do not get confused by the braces which may be present + and unbalanced in an object's repr while collapsing False + explanations. Thanks Carl Meyer for the report and test case. + +- fix issue553: properly handling inspect.getsourcelines failures in + FixtureLookupError which would lead to to an internal error, + obfuscating the original problem. Thanks talljosh for initial + diagnose/patch and Bruno Oliveira for final patch. + +- fix issue660: properly report scope-mismatch-access errors + independently from ordering of fixture arguments. Also + avoid the pytest internal traceback which does not provide + information to the user. Thanks Holger Krekel. + +- streamlined and documented release process. Also all versions + (in setup.py and documentation generation) are now read + from _pytest/__init__.py. Thanks Holger Krekel. + +- fixed docs to remove the notion that yield-fixtures are experimental. + They are here to stay :) Thanks Bruno Oliveira. + +- Support building wheels by using environment markers for the + requirements. Thanks Ionel Maries Cristian. + +- fixed regression to 2.6.4 which surfaced e.g. in lost stdout capture printing + when tests raised SystemExit. Thanks Holger Krekel. + +- reintroduced _pytest fixture of the pytester plugin which is used + at least by pytest-xdist. diff -r f78c26828b715a99d7373c6e4b4618215872d403 -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 doc/en/example/markers.txt --- a/doc/en/example/markers.txt +++ b/doc/en/example/markers.txt @@ -55,8 +55,8 @@ ================= 1 tests deselected by "-m 'not webtest'" ================= ================== 3 passed, 1 deselected in 0.01 seconds ================== -Selecing tests based on their node ID -------------------------------------- +Selecting tests based on their node ID +-------------------------------------- You can provide one or more :ref:`node IDs ` as positional arguments to select only specified tests. This makes it easy to select Repository URL: https://bitbucket.org/pytest-dev/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From builds at drone.io Tue May 5 21:57:19 2015 From: builds at drone.io (Drone.io Build) Date: Tue, 05 May 2015 19:57:19 +0000 Subject: [Pytest-commit] [FAIL] pytest - # 109 Message-ID: <20150505195718.14356.83505@drone.io> Build Failed Build : https://drone.io/bitbucket.org/pytest-dev/pytest/109 Project : https://drone.io/bitbucket.org/pytest-dev/pytest Repository : https://bitbucket.org/pytest-dev/pytest Version : 4228:e14e1c5215fc Author : holger krekel Branch : default Message: merge -------------- next part -------------- An HTML attachment was scrubbed... URL: From issues-reply at bitbucket.org Wed May 6 12:01:29 2015 From: issues-reply at bitbucket.org (teudimundo) Date: Wed, 06 May 2015 10:01:29 -0000 Subject: [Pytest-commit] Issue #738: With xdist: allow to decide the minimal size of chunks for the slaves (pytest-dev/pytest) Message-ID: <20150506100129.10641.97280@app08.ash-private.bitbucket.org> New issue 738: With xdist: allow to decide the minimal size of chunks for the slaves https://bitbucket.org/pytest-dev/pytest/issue/738/with-xdist-allow-to-decide-the-minimal teudimundo: In a specific [scenario](http://stackoverflow.com/questions/30058216/pytest-xdist-running-1-test-per-slave), I would need all the tests to be run in parallel at the same time. Unfortunately seems that at the moment each slave will be fed with chunk of a minimal size of 2 tests. I don't know if it might work but an argument to decide the size (or the minimal size) of chunks can be given. A further possibility would be to have a new value for -n could mean to allocate the number of slaves needed to run all the chunks in parallel. So that using py.test -n by-chunk-size --chunk-size 1 would execute all of them in parallel. From commits-noreply at bitbucket.org Wed May 6 15:02:11 2015 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 06 May 2015 13:02:11 -0000 Subject: [Pytest-commit] commit/pytest: hpk42: Merged in hpk42/pytest-patches/pluggy1 (pull request #290) Message-ID: <20150506130211.16963.36221@app06.ash-private.bitbucket.org> 1 new commit in pytest: https://bitbucket.org/pytest-dev/pytest/commits/af98ffcfb0fb/ Changeset: af98ffcfb0fb User: hpk42 Date: 2015-05-06 13:02:05+00:00 Summary: Merged in hpk42/pytest-patches/pluggy1 (pull request #290) integrate pluggy as external plugin manager Affected #: 29 files diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -27,8 +27,8 @@ details like especially the pluginmanager.add_shutdown() API. Thanks Holger Krekel. -- pluginmanagement: introduce ``pytest.hookimpl_opts`` and - ``pytest.hookspec_opts`` decorators for setting impl/spec +- pluginmanagement: introduce ``pytest.hookimpl`` and + ``pytest.hookspec`` decorators for setting impl/spec specific parameters. This substitutes the previous now deprecated use of ``pytest.mark`` which is meant to contain markers for test functions only. diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/__init__.py --- a/_pytest/__init__.py +++ b/_pytest/__init__.py @@ -1,2 +1,2 @@ # -__version__ = '2.8.0.dev2' +__version__ = '2.8.0.dev3' diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/capture.py --- a/_pytest/capture.py +++ b/_pytest/capture.py @@ -29,7 +29,7 @@ help="shortcut for --capture=no.") - at pytest.hookimpl_opts(hookwrapper=True) + at pytest.hookimpl(hookwrapper=True) def pytest_load_initial_conftests(early_config, parser, args): ns = early_config.known_args_namespace pluginmanager = early_config.pluginmanager @@ -101,7 +101,7 @@ if capfuncarg is not None: capfuncarg.close() - @pytest.hookimpl_opts(hookwrapper=True) + @pytest.hookimpl(hookwrapper=True) def pytest_make_collect_report(self, collector): if isinstance(collector, pytest.File): self.resumecapture() @@ -115,13 +115,13 @@ else: yield - @pytest.hookimpl_opts(hookwrapper=True) + @pytest.hookimpl(hookwrapper=True) def pytest_runtest_setup(self, item): self.resumecapture() yield self.suspendcapture_item(item, "setup") - @pytest.hookimpl_opts(hookwrapper=True) + @pytest.hookimpl(hookwrapper=True) def pytest_runtest_call(self, item): self.resumecapture() self.activate_funcargs(item) @@ -129,17 +129,17 @@ #self.deactivate_funcargs() called from suspendcapture() self.suspendcapture_item(item, "call") - @pytest.hookimpl_opts(hookwrapper=True) + @pytest.hookimpl(hookwrapper=True) def pytest_runtest_teardown(self, item): self.resumecapture() yield self.suspendcapture_item(item, "teardown") - @pytest.hookimpl_opts(tryfirst=True) + @pytest.hookimpl(tryfirst=True) def pytest_keyboard_interrupt(self, excinfo): self.reset_capturings() - @pytest.hookimpl_opts(tryfirst=True) + @pytest.hookimpl(tryfirst=True) def pytest_internalerror(self, excinfo): self.reset_capturings() diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/config.py --- a/_pytest/config.py +++ b/_pytest/config.py @@ -8,8 +8,11 @@ import py # DON't import pytest here because it causes import cycle troubles import sys, os -from _pytest import hookspec # the extension point definitions -from _pytest.core import PluginManager, hookimpl_opts, varnames +import _pytest.hookspec # the extension point definitions +from pluggy import PluginManager, HookimplMarker, HookspecMarker + +hookimpl = HookimplMarker("pytest") +hookspec = HookspecMarker("pytest") # pytest startup # @@ -106,10 +109,10 @@ name.startswith("pytest_funcarg__") + class PytestPluginManager(PluginManager): def __init__(self): - super(PytestPluginManager, self).__init__(prefix="pytest_", - excludefunc=exclude_pytest_names) + super(PytestPluginManager, self).__init__("pytest", implprefix="pytest_") self._warnings = [] self._conftest_plugins = set() @@ -118,7 +121,7 @@ self._conftestpath2mod = {} self._confcutdir = None - self.addhooks(hookspec) + self.add_hookspecs(_pytest.hookspec) self.register(self) if os.environ.get('PYTEST_DEBUG'): err = sys.stderr @@ -130,12 +133,39 @@ self.trace.root.setwriter(err.write) self.enable_tracing() + def addhooks(self, module_or_class): + warning = dict(code="I2", + fslocation=py.code.getfslineno(sys._getframe(1)), + message="use pluginmanager.add_hookspecs instead of " + "deprecated addhooks() method.") + self._warnings.append(warning) + return self.add_hookspecs(module_or_class) - def _verify_hook(self, hook, plugin): - super(PytestPluginManager, self)._verify_hook(hook, plugin) - method = getattr(plugin, hook.name) - if "__multicall__" in varnames(method): - fslineno = py.code.getfslineno(method) + def parse_hookimpl_opts(self, plugin, name): + if exclude_pytest_names(name): + return None + + method = getattr(plugin, name) + opts = super(PytestPluginManager, self).parse_hookimpl_opts(plugin, name) + if opts is not None: + for name in ("tryfirst", "trylast", "optionalhook", "hookwrapper"): + opts.setdefault(name, hasattr(method, name)) + return opts + + def parse_hookspec_opts(self, module_or_class, name): + opts = super(PytestPluginManager, self).parse_hookspec_opts( + module_or_class, name) + if opts is None: + method = getattr(module_or_class, name) + if name.startswith("pytest_"): + opts = {"firstresult": hasattr(method, "firstresult"), + "historic": hasattr(method, "historic")} + return opts + + def _verify_hook(self, hook, hookmethod): + super(PytestPluginManager, self)._verify_hook(hook, hookmethod) + if "__multicall__" in hookmethod.argnames: + fslineno = py.code.getfslineno(hookmethod.function) warning = dict(code="I1", fslocation=fslineno, message="%r hook uses deprecated __multicall__ " @@ -154,7 +184,7 @@ return self.get_plugin(name) def pytest_configure(self, config): - # XXX now that the pluginmanager exposes hookimpl_opts(tryfirst...) + # XXX now that the pluginmanager exposes hookimpl(tryfirst...) # we should remove tryfirst/trylast as markers config.addinivalue_line("markers", "tryfirst: mark a hook implementation function such that the " @@ -797,7 +827,7 @@ if not hasattr(self.option, opt.dest): setattr(self.option, opt.dest, opt.default) - @hookimpl_opts(trylast=True) + @hookimpl(trylast=True) def pytest_load_initial_conftests(self, early_config): self.pluginmanager._set_initial_conftests(early_config.known_args_namespace) diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/core.py --- a/_pytest/core.py +++ /dev/null @@ -1,590 +0,0 @@ -""" -PluginManager, basic initialization and tracing. -""" -import sys -from inspect import isfunction, ismethod, isclass, formatargspec, getargspec -import py - -py3 = sys.version_info > (3,0) - -def hookspec_opts(firstresult=False, historic=False): - """ returns a decorator which will define a function as a hook specfication. - - If firstresult is True the 1:N hook call (N being the number of registered - hook implementation functions) will stop at I<=N when the I'th function - returns a non-None result. - - If historic is True calls to a hook will be memorized and replayed - on later registered plugins. - """ - def setattr_hookspec_opts(func): - if historic and firstresult: - raise ValueError("cannot have a historic firstresult hook") - if firstresult: - func.firstresult = firstresult - if historic: - func.historic = historic - return func - return setattr_hookspec_opts - - -def hookimpl_opts(hookwrapper=False, optionalhook=False, - tryfirst=False, trylast=False): - """ Return a decorator which marks a function as a hook implementation. - - If optionalhook is True a missing matching hook specification will not result - in an error (by default it is an error if no matching spec is found). - - If tryfirst is True this hook implementation will run as early as possible - in the chain of N hook implementations for a specfication. - - If trylast is True this hook implementation will run as late as possible - in the chain of N hook implementations. - - If hookwrapper is True the hook implementations needs to execute exactly - one "yield". The code before the yield is run early before any non-hookwrapper - function is run. The code after the yield is run after all non-hookwrapper - function have run. The yield receives an ``CallOutcome`` object representing - the exception or result outcome of the inner calls (including other hookwrapper - calls). - """ - def setattr_hookimpl_opts(func): - if hookwrapper: - func.hookwrapper = True - if optionalhook: - func.optionalhook = True - if tryfirst: - func.tryfirst = True - if trylast: - func.trylast = True - return func - return setattr_hookimpl_opts - - -class TagTracer: - def __init__(self): - self._tag2proc = {} - self.writer = None - self.indent = 0 - - def get(self, name): - return TagTracerSub(self, (name,)) - - def format_message(self, tags, args): - if isinstance(args[-1], dict): - extra = args[-1] - args = args[:-1] - else: - extra = {} - - content = " ".join(map(str, args)) - indent = " " * self.indent - - lines = [ - "%s%s [%s]\n" %(indent, content, ":".join(tags)) - ] - - for name, value in extra.items(): - lines.append("%s %s: %s\n" % (indent, name, value)) - return lines - - def processmessage(self, tags, args): - if self.writer is not None and args: - lines = self.format_message(tags, args) - self.writer(''.join(lines)) - try: - self._tag2proc[tags](tags, args) - except KeyError: - pass - - def setwriter(self, writer): - self.writer = writer - - def setprocessor(self, tags, processor): - if isinstance(tags, str): - tags = tuple(tags.split(":")) - else: - assert isinstance(tags, tuple) - self._tag2proc[tags] = processor - - -class TagTracerSub: - def __init__(self, root, tags): - self.root = root - self.tags = tags - - def __call__(self, *args): - self.root.processmessage(self.tags, args) - - def setmyprocessor(self, processor): - self.root.setprocessor(self.tags, processor) - - def get(self, name): - return self.__class__(self.root, self.tags + (name,)) - - -def raise_wrapfail(wrap_controller, msg): - co = wrap_controller.gi_code - raise RuntimeError("wrap_controller at %r %s:%d %s" % - (co.co_name, co.co_filename, co.co_firstlineno, msg)) - - -def wrapped_call(wrap_controller, func): - """ Wrap calling to a function with a generator which needs to yield - exactly once. The yield point will trigger calling the wrapped function - and return its CallOutcome to the yield point. The generator then needs - to finish (raise StopIteration) in order for the wrapped call to complete. - """ - try: - next(wrap_controller) # first yield - except StopIteration: - raise_wrapfail(wrap_controller, "did not yield") - call_outcome = CallOutcome(func) - try: - wrap_controller.send(call_outcome) - raise_wrapfail(wrap_controller, "has second yield") - except StopIteration: - pass - return call_outcome.get_result() - - -class CallOutcome: - """ Outcome of a function call, either an exception or a proper result. - Calling the ``get_result`` method will return the result or reraise - the exception raised when the function was called. """ - excinfo = None - def __init__(self, func): - try: - self.result = func() - except BaseException: - self.excinfo = sys.exc_info() - - def force_result(self, result): - self.result = result - self.excinfo = None - - def get_result(self): - if self.excinfo is None: - return self.result - else: - ex = self.excinfo - if py3: - raise ex[1].with_traceback(ex[2]) - py.builtin._reraise(*ex) - - -class TracedHookExecution: - def __init__(self, pluginmanager, before, after): - self.pluginmanager = pluginmanager - self.before = before - self.after = after - self.oldcall = pluginmanager._inner_hookexec - assert not isinstance(self.oldcall, TracedHookExecution) - self.pluginmanager._inner_hookexec = self - - def __call__(self, hook, methods, kwargs): - self.before(hook, methods, kwargs) - outcome = CallOutcome(lambda: self.oldcall(hook, methods, kwargs)) - self.after(outcome, hook, methods, kwargs) - return outcome.get_result() - - def undo(self): - self.pluginmanager._inner_hookexec = self.oldcall - - -class PluginManager(object): - """ Core Pluginmanager class which manages registration - of plugin objects and 1:N hook calling. - - You can register new hooks by calling ``addhooks(module_or_class)``. - You can register plugin objects (which contain hooks) by calling - ``register(plugin)``. The Pluginmanager is initialized with a - prefix that is searched for in the names of the dict of registered - plugin objects. An optional excludefunc allows to blacklist names which - are not considered as hooks despite a matching prefix. - - For debugging purposes you can call ``enable_tracing()`` - which will subsequently send debug information to the trace helper. - """ - - def __init__(self, prefix, excludefunc=None): - self._prefix = prefix - self._excludefunc = excludefunc - self._name2plugin = {} - self._plugin2hookcallers = {} - self._plugin_distinfo = [] - self.trace = TagTracer().get("pluginmanage") - self.hook = HookRelay(self.trace.root.get("hook")) - self._inner_hookexec = lambda hook, methods, kwargs: \ - MultiCall(methods, kwargs, hook.firstresult).execute() - - def _hookexec(self, hook, methods, kwargs): - # called from all hookcaller instances. - # enable_tracing will set its own wrapping function at self._inner_hookexec - return self._inner_hookexec(hook, methods, kwargs) - - def enable_tracing(self): - """ enable tracing of hook calls and return an undo function. """ - hooktrace = self.hook._trace - - def before(hook, methods, kwargs): - hooktrace.root.indent += 1 - hooktrace(hook.name, kwargs) - - def after(outcome, hook, methods, kwargs): - if outcome.excinfo is None: - hooktrace("finish", hook.name, "-->", outcome.result) - hooktrace.root.indent -= 1 - - return TracedHookExecution(self, before, after).undo - - def subset_hook_caller(self, name, remove_plugins): - """ Return a new HookCaller instance for the named method - which manages calls to all registered plugins except the - ones from remove_plugins. """ - orig = getattr(self.hook, name) - plugins_to_remove = [plugin for plugin in remove_plugins - if hasattr(plugin, name)] - if plugins_to_remove: - hc = HookCaller(orig.name, orig._hookexec, orig._specmodule_or_class) - for plugin in orig._plugins: - if plugin not in plugins_to_remove: - hc._add_plugin(plugin) - # we also keep track of this hook caller so it - # gets properly removed on plugin unregistration - self._plugin2hookcallers.setdefault(plugin, []).append(hc) - return hc - return orig - - def register(self, plugin, name=None): - """ Register a plugin and return its canonical name or None if the name - is blocked from registering. Raise a ValueError if the plugin is already - registered. """ - plugin_name = name or self.get_canonical_name(plugin) - - if plugin_name in self._name2plugin or plugin in self._plugin2hookcallers: - if self._name2plugin.get(plugin_name, -1) is None: - return # blocked plugin, return None to indicate no registration - raise ValueError("Plugin already registered: %s=%s\n%s" %( - plugin_name, plugin, self._name2plugin)) - - self._name2plugin[plugin_name] = plugin - - # register prefix-matching hook specs of the plugin - self._plugin2hookcallers[plugin] = hookcallers = [] - for name in dir(plugin): - if name.startswith(self._prefix): - hook = getattr(self.hook, name, None) - if hook is None: - if self._excludefunc is not None and self._excludefunc(name): - continue - hook = HookCaller(name, self._hookexec) - setattr(self.hook, name, hook) - elif hook.has_spec(): - self._verify_hook(hook, plugin) - hook._maybe_apply_history(getattr(plugin, name)) - hookcallers.append(hook) - hook._add_plugin(plugin) - return plugin_name - - def unregister(self, plugin=None, name=None): - """ unregister a plugin object and all its contained hook implementations - from internal data structures. """ - if name is None: - assert plugin is not None, "one of name or plugin needs to be specified" - name = self.get_name(plugin) - - if plugin is None: - plugin = self.get_plugin(name) - - # if self._name2plugin[name] == None registration was blocked: ignore - if self._name2plugin.get(name): - del self._name2plugin[name] - - for hookcaller in self._plugin2hookcallers.pop(plugin, []): - hookcaller._remove_plugin(plugin) - - return plugin - - def set_blocked(self, name): - """ block registrations of the given name, unregister if already registered. """ - self.unregister(name=name) - self._name2plugin[name] = None - - def addhooks(self, module_or_class): - """ add new hook definitions from the given module_or_class using - the prefix/excludefunc with which the PluginManager was initialized. """ - names = [] - for name in dir(module_or_class): - if name.startswith(self._prefix): - hc = getattr(self.hook, name, None) - if hc is None: - hc = HookCaller(name, self._hookexec, module_or_class) - setattr(self.hook, name, hc) - else: - # plugins registered this hook without knowing the spec - hc.set_specification(module_or_class) - for plugin in hc._plugins: - self._verify_hook(hc, plugin) - names.append(name) - - if not names: - raise ValueError("did not find new %r hooks in %r" - %(self._prefix, module_or_class)) - - def get_plugins(self): - """ return the set of registered plugins. """ - return set(self._plugin2hookcallers) - - def is_registered(self, plugin): - """ Return True if the plugin is already registered. """ - return plugin in self._plugin2hookcallers - - def get_canonical_name(self, plugin): - """ Return canonical name for a plugin object. Note that a plugin - may be registered under a different name which was specified - by the caller of register(plugin, name). To obtain the name - of an registered plugin use ``get_name(plugin)`` instead.""" - return getattr(plugin, "__name__", None) or str(id(plugin)) - - def get_plugin(self, name): - """ Return a plugin or None for the given name. """ - return self._name2plugin.get(name) - - def get_name(self, plugin): - """ Return name for registered plugin or None if not registered. """ - for name, val in self._name2plugin.items(): - if plugin == val: - return name - - def _verify_hook(self, hook, plugin): - method = getattr(plugin, hook.name) - pluginname = self.get_name(plugin) - - if hook.is_historic() and hasattr(method, "hookwrapper"): - raise PluginValidationError( - "Plugin %r\nhook %r\nhistoric incompatible to hookwrapper" %( - pluginname, hook.name)) - - for arg in varnames(method): - if arg not in hook.argnames: - raise PluginValidationError( - "Plugin %r\nhook %r\nargument %r not available\n" - "plugin definition: %s\n" - "available hookargs: %s" %( - pluginname, hook.name, arg, formatdef(method), - ", ".join(hook.argnames))) - - def check_pending(self): - """ Verify that all hooks which have not been verified against - a hook specification are optional, otherwise raise PluginValidationError""" - for name in self.hook.__dict__: - if name.startswith(self._prefix): - hook = getattr(self.hook, name) - if not hook.has_spec(): - for plugin in hook._plugins: - method = getattr(plugin, hook.name) - if not getattr(method, "optionalhook", False): - raise PluginValidationError( - "unknown hook %r in plugin %r" %(name, plugin)) - - def load_setuptools_entrypoints(self, entrypoint_name): - """ Load modules from querying the specified setuptools entrypoint name. - Return the number of loaded plugins. """ - from pkg_resources import iter_entry_points, DistributionNotFound - for ep in iter_entry_points(entrypoint_name): - # is the plugin registered or blocked? - if self.get_plugin(ep.name) or ep.name in self._name2plugin: - continue - try: - plugin = ep.load() - except DistributionNotFound: - continue - self.register(plugin, name=ep.name) - self._plugin_distinfo.append((ep.dist, plugin)) - return len(self._plugin_distinfo) - - -class MultiCall: - """ execute a call into multiple python functions/methods. """ - - # XXX note that the __multicall__ argument is supported only - # for pytest compatibility reasons. It was never officially - # supported there and is explicitely deprecated since 2.8 - # so we can remove it soon, allowing to avoid the below recursion - # in execute() and simplify/speed up the execute loop. - - def __init__(self, methods, kwargs, firstresult=False): - self.methods = methods - self.kwargs = kwargs - self.kwargs["__multicall__"] = self - self.firstresult = firstresult - - def execute(self): - all_kwargs = self.kwargs - self.results = results = [] - firstresult = self.firstresult - - while self.methods: - method = self.methods.pop() - args = [all_kwargs[argname] for argname in varnames(method)] - if hasattr(method, "hookwrapper"): - return wrapped_call(method(*args), self.execute) - res = method(*args) - if res is not None: - if firstresult: - return res - results.append(res) - - if not firstresult: - return results - - def __repr__(self): - status = "%d meths" % (len(self.methods),) - if hasattr(self, "results"): - status = ("%d results, " % len(self.results)) + status - return "" %(status, self.kwargs) - - - -def varnames(func, startindex=None): - """ return argument name tuple for a function, method, class or callable. - - In case of a class, its "__init__" method is considered. - For methods the "self" parameter is not included unless you are passing - an unbound method with Python3 (which has no supports for unbound methods) - """ - cache = getattr(func, "__dict__", {}) - try: - return cache["_varnames"] - except KeyError: - pass - if isclass(func): - try: - func = func.__init__ - except AttributeError: - return () - startindex = 1 - else: - if not isfunction(func) and not ismethod(func): - func = getattr(func, '__call__', func) - if startindex is None: - startindex = int(ismethod(func)) - - rawcode = py.code.getrawcode(func) - try: - x = rawcode.co_varnames[startindex:rawcode.co_argcount] - except AttributeError: - x = () - else: - defaults = func.__defaults__ - if defaults: - x = x[:-len(defaults)] - try: - cache["_varnames"] = x - except TypeError: - pass - return x - - -class HookRelay: - def __init__(self, trace): - self._trace = trace - - -class HookCaller(object): - def __init__(self, name, hook_execute, specmodule_or_class=None): - self.name = name - self._plugins = [] - self._wrappers = [] - self._nonwrappers = [] - self._hookexec = hook_execute - if specmodule_or_class is not None: - self.set_specification(specmodule_or_class) - - def has_spec(self): - return hasattr(self, "_specmodule_or_class") - - def set_specification(self, specmodule_or_class): - assert not self.has_spec() - self._specmodule_or_class = specmodule_or_class - specfunc = getattr(specmodule_or_class, self.name) - argnames = varnames(specfunc, startindex=isclass(specmodule_or_class)) - assert "self" not in argnames # sanity check - self.argnames = ["__multicall__"] + list(argnames) - self.firstresult = getattr(specfunc, 'firstresult', False) - if hasattr(specfunc, "historic"): - self._call_history = [] - - def is_historic(self): - return hasattr(self, "_call_history") - - def _remove_plugin(self, plugin): - self._plugins.remove(plugin) - meth = getattr(plugin, self.name) - try: - self._nonwrappers.remove(meth) - except ValueError: - self._wrappers.remove(meth) - - def _add_plugin(self, plugin): - self._plugins.append(plugin) - self._add_method(getattr(plugin, self.name)) - - def _add_method(self, meth): - if hasattr(meth, 'hookwrapper'): - methods = self._wrappers - else: - methods = self._nonwrappers - - if hasattr(meth, 'trylast'): - methods.insert(0, meth) - elif hasattr(meth, 'tryfirst'): - methods.append(meth) - else: - # find last non-tryfirst method - i = len(methods) - 1 - while i >= 0 and hasattr(methods[i], "tryfirst"): - i -= 1 - methods.insert(i + 1, meth) - - def __repr__(self): - return "" %(self.name,) - - def __call__(self, **kwargs): - assert not self.is_historic() - return self._hookexec(self, self._nonwrappers + self._wrappers, kwargs) - - def call_historic(self, proc=None, kwargs=None): - self._call_history.append((kwargs or {}, proc)) - # historizing hooks don't return results - self._hookexec(self, self._nonwrappers + self._wrappers, kwargs) - - def call_extra(self, methods, kwargs): - """ Call the hook with some additional temporarily participating - methods using the specified kwargs as call parameters. """ - old = list(self._nonwrappers), list(self._wrappers) - for method in methods: - self._add_method(method) - try: - return self(**kwargs) - finally: - self._nonwrappers, self._wrappers = old - - def _maybe_apply_history(self, method): - if self.is_historic(): - for kwargs, proc in self._call_history: - res = self._hookexec(self, [method], kwargs) - if res and proc is not None: - proc(res[0]) - - -class PluginValidationError(Exception): - """ plugin failed validation. """ - - -def formatdef(func): - return "%s%s" % ( - func.__name__, - formatargspec(*getargspec(func)) - ) diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/genscript.py --- a/_pytest/genscript.py +++ b/_pytest/genscript.py @@ -4,7 +4,6 @@ import pkgutil import py - import _pytest @@ -33,6 +32,9 @@ for pyfile in toplevel.visit('*.py'): pkg = pkgname(name, toplevel, pyfile) name2src[pkg] = pyfile.read() + # with wheels py source code might be not be installed + # and the resulting genscript is useless, just bail out. + assert name2src, "no source code found for %r at %r" %(name, toplevel) return name2src def compress_mapping(mapping): @@ -69,7 +71,7 @@ genscript = config.getvalue("genscript") if genscript: tw = py.io.TerminalWriter() - deps = ['py', '_pytest', 'pytest'] + deps = ['py', 'pluggy', '_pytest', 'pytest'] if sys.version_info < (2,7): deps.append("argparse") tw.line("generated script will run on python2.6-python3.3++") diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/helpconfig.py --- a/_pytest/helpconfig.py +++ b/_pytest/helpconfig.py @@ -22,7 +22,7 @@ help="store internal tracing debug information in 'pytestdebug.log'.") - at pytest.hookimpl_opts(hookwrapper=True) + at pytest.hookimpl(hookwrapper=True) def pytest_cmdline_parse(): outcome = yield config = outcome.get_result() @@ -96,10 +96,10 @@ def getpluginversioninfo(config): lines = [] - plugininfo = config.pluginmanager._plugin_distinfo + plugininfo = config.pluginmanager.list_plugin_distinfo() if plugininfo: lines.append("setuptools registered plugins:") - for dist, plugin in plugininfo: + for plugin, dist in plugininfo: loc = getattr(plugin, '__file__', repr(plugin)) content = "%s-%s at %s" % (dist.project_name, dist.version, loc) lines.append(" " + content) @@ -117,7 +117,7 @@ if config.option.traceconfig: lines.append("active plugins:") - items = config.pluginmanager._name2plugin.items() + items = config.pluginmanager.list_name_plugin() for name, plugin in items: if hasattr(plugin, '__file__'): r = plugin.__file__ diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/hookspec.py --- a/_pytest/hookspec.py +++ b/_pytest/hookspec.py @@ -1,30 +1,32 @@ """ hook specifications for pytest plugins, invoked from main.py and builtin plugins. """ -from _pytest.core import hookspec_opts +from pluggy import HookspecMarker + +hookspec = HookspecMarker("pytest") # ------------------------------------------------------------------------- # Initialization hooks called for every plugin # ------------------------------------------------------------------------- - at hookspec_opts(historic=True) + at hookspec(historic=True) def pytest_addhooks(pluginmanager): """called at plugin registration time to allow adding new hooks via a call to - pluginmanager.addhooks(module_or_class, prefix).""" + pluginmanager.add_hookspecs(module_or_class, prefix).""" - at hookspec_opts(historic=True) + at hookspec(historic=True) def pytest_namespace(): """return dict of name->object to be made globally available in the pytest namespace. This hook is called at plugin registration time. """ - at hookspec_opts(historic=True) + at hookspec(historic=True) def pytest_plugin_registered(plugin, manager): """ a new pytest plugin got registered. """ - at hookspec_opts(historic=True) + at hookspec(historic=True) def pytest_addoption(parser): """register argparse-style options and ini-style config values. @@ -50,7 +52,7 @@ via (deprecated) ``pytest.config``. """ - at hookspec_opts(historic=True) + at hookspec(historic=True) def pytest_configure(config): """ called after command line options have been parsed and all plugins and initial conftest files been loaded. @@ -63,14 +65,14 @@ # discoverable conftest.py local plugins. # ------------------------------------------------------------------------- - at hookspec_opts(firstresult=True) + at hookspec(firstresult=True) def pytest_cmdline_parse(pluginmanager, args): """return initialized config object, parsing the specified args. """ def pytest_cmdline_preparse(config, args): """(deprecated) modify command line arguments before option parsing. """ - at hookspec_opts(firstresult=True) + at hookspec(firstresult=True) def pytest_cmdline_main(config): """ called for performing the main command line action. The default implementation will invoke the configure hooks and runtest_mainloop. """ @@ -84,7 +86,7 @@ # collection hooks # ------------------------------------------------------------------------- - at hookspec_opts(firstresult=True) + at hookspec(firstresult=True) def pytest_collection(session): """ perform the collection protocol for the given session. """ @@ -95,14 +97,14 @@ def pytest_collection_finish(session): """ called after collection has been performed and modified. """ - at hookspec_opts(firstresult=True) + at hookspec(firstresult=True) def pytest_ignore_collect(path, config): """ return True to prevent considering this path for collection. This hook is consulted for all files and directories prior to calling more specific hooks. """ - at hookspec_opts(firstresult=True) + at hookspec(firstresult=True) def pytest_collect_directory(path, parent): """ called before traversing a directory for collection files. """ @@ -123,7 +125,7 @@ def pytest_deselected(items): """ called for test items deselected by keyword. """ - at hookspec_opts(firstresult=True) + at hookspec(firstresult=True) def pytest_make_collect_report(collector): """ perform ``collector.collect()`` and return a CollectReport. """ @@ -131,7 +133,7 @@ # Python test function related hooks # ------------------------------------------------------------------------- - at hookspec_opts(firstresult=True) + at hookspec(firstresult=True) def pytest_pycollect_makemodule(path, parent): """ return a Module collector or None for the given path. This hook will be called for each matching test module path. @@ -139,11 +141,11 @@ create test modules for files that do not match as a test module. """ - at hookspec_opts(firstresult=True) + at hookspec(firstresult=True) def pytest_pycollect_makeitem(collector, name, obj): """ return custom item/collector for a python object in a module, or None. """ - at hookspec_opts(firstresult=True) + at hookspec(firstresult=True) def pytest_pyfunc_call(pyfuncitem): """ call underlying test function. """ @@ -154,7 +156,7 @@ # generic runtest related hooks # ------------------------------------------------------------------------- - at hookspec_opts(firstresult=True) + at hookspec(firstresult=True) def pytest_runtestloop(session): """ called for performing the main runtest loop (after collection finished). """ @@ -162,7 +164,7 @@ def pytest_itemstart(item, node): """ (deprecated, use pytest_runtest_logstart). """ - at hookspec_opts(firstresult=True) + at hookspec(firstresult=True) def pytest_runtest_protocol(item, nextitem): """ implements the runtest_setup/call/teardown protocol for the given test item, including capturing exceptions and calling @@ -195,7 +197,7 @@ so that nextitem only needs to call setup-functions. """ - at hookspec_opts(firstresult=True) + at hookspec(firstresult=True) def pytest_runtest_makereport(item, call): """ return a :py:class:`_pytest.runner.TestReport` object for the given :py:class:`pytest.Item` and @@ -240,7 +242,7 @@ def pytest_report_header(config, startdir): """ return a string to be displayed as header info for terminal reporting.""" - at hookspec_opts(firstresult=True) + at hookspec(firstresult=True) def pytest_report_teststatus(report): """ return result-category, shortletter and verbose word for reporting.""" @@ -256,7 +258,7 @@ # doctest hooks # ------------------------------------------------------------------------- - at hookspec_opts(firstresult=True) + at hookspec(firstresult=True) def pytest_doctest_prepare_content(content): """ return processed content for a given doctest""" diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/main.py --- a/_pytest/main.py +++ b/_pytest/main.py @@ -501,23 +501,23 @@ def __init__(self, config): FSCollector.__init__(self, config.rootdir, parent=None, config=config, session=self) - self.config.pluginmanager.register(self, name="session") + self._fs2hookproxy = {} self._testsfailed = 0 self.shouldstop = False self.trace = config.trace.root.get("collection") self._norecursepatterns = config.getini("norecursedirs") self.startdir = py.path.local() - self._fs2hookproxy = {} + self.config.pluginmanager.register(self, name="session") def _makeid(self): return "" - @pytest.hookimpl_opts(tryfirst=True) + @pytest.hookimpl(tryfirst=True) def pytest_collectstart(self): if self.shouldstop: raise self.Interrupted(self.shouldstop) - @pytest.hookimpl_opts(tryfirst=True) + @pytest.hookimpl(tryfirst=True) def pytest_runtest_logreport(self, report): if report.failed and not hasattr(report, 'wasxfail'): self._testsfailed += 1 diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/nose.py --- a/_pytest/nose.py +++ b/_pytest/nose.py @@ -24,7 +24,7 @@ call.excinfo = call2.excinfo - at pytest.hookimpl_opts(trylast=True) + at pytest.hookimpl(trylast=True) def pytest_runtest_setup(item): if is_potential_nosetest(item): if isinstance(item.parent, pytest.Generator): diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/pastebin.py --- a/_pytest/pastebin.py +++ b/_pytest/pastebin.py @@ -11,7 +11,7 @@ choices=['failed', 'all'], help="send failed|all info to bpaste.net pastebin service.") - at pytest.hookimpl_opts(trylast=True) + at pytest.hookimpl(trylast=True) def pytest_configure(config): if config.option.pastebin == "all": tr = config.pluginmanager.getplugin('terminalreporter') diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/pytester.py --- a/_pytest/pytester.py +++ b/_pytest/pytester.py @@ -13,7 +13,7 @@ import py import pytest from py.builtin import print_ -from _pytest.core import TracedHookExecution +from pluggy import _TracedHookExecution from _pytest.main import Session, EXIT_OK @@ -80,7 +80,7 @@ else: return True - @pytest.hookimpl_opts(hookwrapper=True, tryfirst=True) + @pytest.hookimpl(hookwrapper=True, tryfirst=True) def pytest_runtest_item(self, item): lines1 = self.get_open_files() yield @@ -198,7 +198,7 @@ self.calls.append(ParsedCall(hook.name, kwargs)) def after(outcome, hook, method, kwargs): pass - executor = TracedHookExecution(pluginmanager, before, after) + executor = _TracedHookExecution(pluginmanager, before, after) self._undo_wrapping = executor.undo def finish_recording(self): @@ -712,8 +712,20 @@ option "--runpytest" and return a :py:class:`RunResult`. """ + args = self._ensure_basetemp(args) return self._runpytest_method(*args, **kwargs) + def _ensure_basetemp(self, args): + args = [str(x) for x in args] + for x in args: + if str(x).startswith('--basetemp'): + #print ("basedtemp exists: %s" %(args,)) + break + else: + args.append("--basetemp=%s" % self.tmpdir.dirpath('basetemp')) + #print ("added basetemp: %s" %(args,)) + return args + def parseconfig(self, *args): """Return a new py.test Config instance from given commandline args. @@ -726,12 +738,8 @@ modules which will be registered with the PluginManager. """ - args = [str(x) for x in args] - for x in args: - if str(x).startswith('--basetemp'): - break - else: - args.append("--basetemp=%s" % self.tmpdir.dirpath('basetemp')) + args = self._ensure_basetemp(args) + import _pytest.config config = _pytest.config._prepareconfig(args, self.plugins) # we don't know what the test will do with this half-setup config diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/python.py --- a/_pytest/python.py +++ b/_pytest/python.py @@ -8,7 +8,11 @@ from py._code.code import TerminalRepr import _pytest -cutdir = py.path.local(_pytest.__file__).dirpath() +import pluggy + +cutdir2 = py.path.local(_pytest.__file__).dirpath() +cutdir1 = py.path.local(pluggy.__file__.rstrip("oc")) + NoneType = type(None) NOTSET = object() @@ -18,6 +22,11 @@ # used to work around a python2 exception info leak exc_clear = getattr(sys, 'exc_clear', lambda: None) + +def filter_traceback(entry): + return entry.path != cutdir1 and not entry.path.relto(cutdir2) + + def getfslineno(obj): # xxx let decorators etc specify a sane ordering while hasattr(obj, "__wrapped__"): @@ -172,7 +181,7 @@ def pytest_sessionstart(session): session._fixturemanager = FixtureManager(session) - at pytest.hookimpl_opts(trylast=True) + at pytest.hookimpl(trylast=True) def pytest_namespace(): raises.Exception = pytest.fail.Exception return { @@ -191,7 +200,7 @@ return request.config - at pytest.hookimpl_opts(trylast=True) + at pytest.hookimpl(trylast=True) def pytest_pyfunc_call(pyfuncitem): testfunction = pyfuncitem.obj if pyfuncitem._isyieldedfunction(): @@ -219,7 +228,7 @@ def pytest_pycollect_makemodule(path, parent): return Module(path, parent) - at pytest.hookimpl_opts(hookwrapper=True) + at pytest.hookimpl(hookwrapper=True) def pytest_pycollect_makeitem(collector, name, obj): outcome = yield res = outcome.get_result() @@ -604,7 +613,11 @@ if ntraceback == traceback: ntraceback = ntraceback.cut(path=path) if ntraceback == traceback: - ntraceback = ntraceback.cut(excludepath=cutdir) + #ntraceback = ntraceback.cut(excludepath=cutdir2) + ntraceback = ntraceback.filter(filter_traceback) + if not ntraceback: + ntraceback = traceback + excinfo.traceback = ntraceback.filter() # issue364: mark all but first and last frames to # only show a single-line message for each frame diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/skipping.py --- a/_pytest/skipping.py +++ b/_pytest/skipping.py @@ -133,7 +133,7 @@ return expl - at pytest.hookimpl_opts(tryfirst=True) + at pytest.hookimpl(tryfirst=True) def pytest_runtest_setup(item): evalskip = MarkEvaluator(item, 'skipif') if evalskip.istrue(): @@ -151,7 +151,7 @@ if not evalxfail.get('run', True): pytest.xfail("[NOTRUN] " + evalxfail.getexplanation()) - at pytest.hookimpl_opts(hookwrapper=True) + at pytest.hookimpl(hookwrapper=True) def pytest_runtest_makereport(item, call): outcome = yield rep = outcome.get_result() diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/terminal.py --- a/_pytest/terminal.py +++ b/_pytest/terminal.py @@ -3,6 +3,7 @@ This is a good source for looking at the various reporting hooks. """ import pytest +import pluggy import py import sys import time @@ -267,7 +268,7 @@ def pytest_collection_modifyitems(self): self.report_collect(True) - @pytest.hookimpl_opts(trylast=True) + @pytest.hookimpl(trylast=True) def pytest_sessionstart(self, session): self._sessionstarttime = time.time() if not self.showheader: @@ -278,7 +279,8 @@ if hasattr(sys, 'pypy_version_info'): verinfo = ".".join(map(str, sys.pypy_version_info[:3])) msg += "[pypy-%s-%s]" % (verinfo, sys.pypy_version_info[3]) - msg += " -- py-%s -- pytest-%s" % (py.__version__, pytest.__version__) + msg += ", pytest-%s, py-%s, pluggy-%s" % ( + pytest.__version__, py.__version__, pluggy.__version__) if self.verbosity > 0 or self.config.option.debug or \ getattr(self.config.option, 'pastebin', None): msg += " -- " + str(sys.executable) @@ -294,10 +296,11 @@ if config.inifile: inifile = config.rootdir.bestrelpath(config.inifile) lines = ["rootdir: %s, inifile: %s" %(config.rootdir, inifile)] - plugininfo = config.pluginmanager._plugin_distinfo + + plugininfo = config.pluginmanager.list_plugin_distinfo() if plugininfo: l = [] - for dist, plugin in plugininfo: + for plugin, dist in plugininfo: name = dist.project_name if name.startswith("pytest-"): name = name[7:] @@ -352,7 +355,7 @@ indent = (len(stack) - 1) * " " self._tw.line("%s%s" % (indent, col)) - @pytest.hookimpl_opts(hookwrapper=True) + @pytest.hookimpl(hookwrapper=True) def pytest_sessionfinish(self, exitstatus): outcome = yield outcome.get_result() diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/unittest.py --- a/_pytest/unittest.py +++ b/_pytest/unittest.py @@ -140,7 +140,7 @@ if traceback: excinfo.traceback = traceback - at pytest.hookimpl_opts(tryfirst=True) + at pytest.hookimpl(tryfirst=True) def pytest_runtest_makereport(item, call): if isinstance(item, TestCaseFunction): if item._excinfo: @@ -152,7 +152,7 @@ # twisted trial support - at pytest.hookimpl_opts(hookwrapper=True) + at pytest.hookimpl(hookwrapper=True) def pytest_runtest_protocol(item): if isinstance(item, TestCaseFunction) and \ 'twisted.trial.unittest' in sys.modules: diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 doc/en/example/markers.txt --- a/doc/en/example/markers.txt +++ b/doc/en/example/markers.txt @@ -201,9 +201,9 @@ @pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures - @pytest.hookimpl_opts(tryfirst=True): mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible. + @pytest.hookimpl(tryfirst=True): mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible. - @pytest.hookimpl_opts(trylast=True): mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible. + @pytest.hookimpl(trylast=True): mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible. For an example on how to add and work with markers from a plugin, see @@ -375,9 +375,9 @@ @pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures - @pytest.hookimpl_opts(tryfirst=True): mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible. + @pytest.hookimpl(tryfirst=True): mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible. - @pytest.hookimpl_opts(trylast=True): mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible. + @pytest.hookimpl(trylast=True): mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible. Reading markers which were set from multiple places diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 doc/en/example/simple.txt --- a/doc/en/example/simple.txt +++ b/doc/en/example/simple.txt @@ -534,7 +534,7 @@ import pytest import os.path - @pytest.hookimpl_opts(tryfirst=True) + @pytest.hookimpl(tryfirst=True) def pytest_runtest_makereport(item, call, __multicall__): # execute all other hooks to obtain the report object rep = __multicall__.execute() @@ -607,7 +607,7 @@ import pytest - @pytest.hookimpl_opts(tryfirst=True) + @pytest.hookimpl(tryfirst=True) def pytest_runtest_makereport(item, call, __multicall__): # execute all other hooks to obtain the report object rep = __multicall__.execute() diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 doc/en/goodpractises.txt --- a/doc/en/goodpractises.txt +++ b/doc/en/goodpractises.txt @@ -169,6 +169,14 @@ python runtests.py +.. note:: + + You must have pytest and its dependencies installed as an sdist, not + as wheels because genscript need the source code for generating a + standalone script. + + + Integrating with distutils / ``python setup.py test`` -------------------------------------------------------- diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 doc/en/writing_plugins.txt --- a/doc/en/writing_plugins.txt +++ b/doc/en/writing_plugins.txt @@ -292,7 +292,7 @@ import pytest - @pytest.hookimpl_opts(hookwrapper=True) + @pytest.hookimpl(hookwrapper=True) def pytest_pyfunc_call(pyfuncitem): # do whatever you want before the next hook executes @@ -305,8 +305,7 @@ Note that hook wrappers don't return results themselves, they merely perform tracing or other side effects around the actual hook implementations. If the result of the underlying hook is a mutable object, they may modify -that result, however. - +that result but it's probably better to avoid it. Hook function ordering / call example @@ -338,16 +337,24 @@ Here is the order of execution: 1. Plugin3's pytest_collection_modifyitems called until the yield point -2. Plugin1's pytest_collection_modifyitems is called -3. Plugin2's pytest_collection_modifyitems is called -4. Plugin3's pytest_collection_modifyitems called for executing after the yield - The yield receives a :py:class:`CallOutcome` instance which encapsulates - the result from calling the non-wrappers. Wrappers cannot modify the result. + because it is a hook wrapper. + +2. Plugin1's pytest_collection_modifyitems is called because it is marked + with ``tryfirst=True``. + +3. Plugin2's pytest_collection_modifyitems is called because it is marked + with ``trylast=True`` (but even without this mark it would come after + Plugin1). + +4. Plugin3's pytest_collection_modifyitems then executing the code after the yield + point. The yield receives a :py:class:`CallOutcome` instance which encapsulates + the result from calling the non-wrappers. Wrappers shall not modify the result. It's possible to use ``tryfirst`` and ``trylast`` also in conjunction with ``hookwrapper=True`` in which case it will influence the ordering of hookwrappers among each other. + Declaring new hooks ------------------------ @@ -368,11 +375,11 @@ .. _`newhooks.py`: https://bitbucket.org/pytest-dev/pytest-xdist/src/52082f70e7dd04b00361091b8af906c60fd6700f/xdist/newhooks.py?at=default -Using hooks from 3rd party plugins -------------------------------------- +Optionally using hooks from 3rd party plugins +--------------------------------------------- Using new hooks from plugins as explained above might be a little tricky -because the standard :ref:`validation mechanism `: +because of the standard :ref:`validation mechanism `: if you depend on a plugin that is not installed, validation will fail and the error message will not make much sense to your users. @@ -395,7 +402,6 @@ This has the added benefit of allowing you to conditionally install hooks depending on which plugins are installed. - .. _`well specified hooks`: .. currentmodule:: _pytest.hookspec diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 pytest.py --- a/pytest.py +++ b/pytest.py @@ -11,8 +11,10 @@ # else we are imported -from _pytest.config import main, UsageError, _preloadplugins, cmdline -from _pytest.core import hookspec_opts, hookimpl_opts +from _pytest.config import ( + main, UsageError, _preloadplugins, cmdline, + hookspec, hookimpl +) from _pytest import __version__ _preloadplugins() # to populate pytest.* namespace so help(pytest) works diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 setup.py --- a/setup.py +++ b/setup.py @@ -48,7 +48,7 @@ def main(): - install_requires = ['py>=1.4.27.dev2'] + install_requires = ['py>=1.4.27.dev2', 'pluggy>=0.2.0,<0.3.0'] extras_require = {} if has_environment_marker_support(): extras_require[':python_version=="2.6" or python_version=="3.0" or python_version=="3.1"'] = ['argparse'] diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 testing/python/collect.py --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -559,7 +559,7 @@ b = testdir.mkdir("a").mkdir("b") b.join("conftest.py").write(py.code.Source(""" import pytest - @pytest.hookimpl_opts(hookwrapper=True) + @pytest.hookimpl(hookwrapper=True) def pytest_pycollect_makeitem(): outcome = yield if outcome.excinfo is None: diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 testing/test_config.py --- a/testing/test_config.py +++ b/testing/test_config.py @@ -357,9 +357,9 @@ pm.register(m) hc = pm.hook.pytest_load_initial_conftests l = hc._nonwrappers + hc._wrappers - assert l[-1].__module__ == "_pytest.capture" - assert l[-2] == m.pytest_load_initial_conftests - assert l[-3].__module__ == "_pytest.config" + assert l[-1].function.__module__ == "_pytest.capture" + assert l[-2].function == m.pytest_load_initial_conftests + assert l[-3].function.__module__ == "_pytest.config" class TestWarning: def test_warn_config(self, testdir): This diff is so big that we needed to truncate the remainder. Repository URL: https://bitbucket.org/pytest-dev/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Wed May 6 15:02:47 2015 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 06 May 2015 13:02:47 -0000 Subject: [Pytest-commit] commit/pytest: hpk42: close branch Message-ID: <20150506130247.32175.59385@app02.ash-private.bitbucket.org> 1 new commit in pytest: https://bitbucket.org/pytest-dev/pytest/commits/8f7dd5596515/ Changeset: 8f7dd5596515 Branch: pluggy1 User: hpk42 Date: 2015-05-06 13:02:37+00:00 Summary: close branch Affected #: 0 files Repository URL: https://bitbucket.org/pytest-dev/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From builds at drone.io Wed May 6 15:03:30 2015 From: builds at drone.io (Drone.io Build) Date: Wed, 06 May 2015 13:03:30 +0000 Subject: [Pytest-commit] [FAIL] pytest - # 110 Message-ID: <20150506130328.76602.87062@drone.io> Build Failed Build : https://drone.io/bitbucket.org/pytest-dev/pytest/110 Project : https://drone.io/bitbucket.org/pytest-dev/pytest Repository : https://bitbucket.org/pytest-dev/pytest Version : 4238:af98ffcfb0fb Author : holger krekel Branch : default Message: Merged in hpk42/pytest-patches/pluggy1 (pull request #290) -------------- next part -------------- An HTML attachment was scrubbed... URL: From builds at drone.io Wed May 6 15:04:46 2015 From: builds at drone.io (Drone.io Build) Date: Wed, 06 May 2015 13:04:46 +0000 Subject: [Pytest-commit] [FAIL] pytest - # 111 Message-ID: <20150506130443.98677.30781@drone.io> Build Failed Build : https://drone.io/bitbucket.org/pytest-dev/pytest/111 Project : https://drone.io/bitbucket.org/pytest-dev/pytest Repository : https://bitbucket.org/pytest-dev/pytest Version : 4239:8f7dd5596515 Author : holger krekel Branch : default Message: close branch -------------- next part -------------- An HTML attachment was scrubbed... URL: From builds at drone.io Wed May 6 15:05:53 2015 From: builds at drone.io (Drone.io Build) Date: Wed, 06 May 2015 13:05:53 +0000 Subject: [Pytest-commit] [FAIL] pytest - # 112 Message-ID: <20150506130553.25437.31819@drone.io> Build Failed Build : https://drone.io/bitbucket.org/pytest-dev/pytest/112 Project : https://drone.io/bitbucket.org/pytest-dev/pytest Repository : https://bitbucket.org/pytest-dev/pytest Version : 4066:8f7dd5596515 Author : holger krekel Branch : pluggy1 Message: close branch -------------- next part -------------- An HTML attachment was scrubbed... URL: From commits-noreply at bitbucket.org Wed May 6 15:02:13 2015 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 06 May 2015 13:02:13 -0000 Subject: [Pytest-commit] commit/pytest: 10 new changesets Message-ID: <20150506130213.7856.99110@app07.ash-private.bitbucket.org> 10 new commits in pytest: https://bitbucket.org/pytest-dev/pytest/commits/8d6251cf5ac6/ Changeset: 8d6251cf5ac6 Branch: pluggy1 User: hpk42 Date: 2015-04-29 14:40:51+00:00 Summary: adapt pytest to using pluggy (current master) Affected #: 10 files diff -r bcbb77bff338c564d2b28500d0444932a04ad35a -r 8d6251cf5ac6dbbf41a3ce8dcf0dba400c769edf _pytest/config.py --- a/_pytest/config.py +++ b/_pytest/config.py @@ -9,7 +9,7 @@ # DON't import pytest here because it causes import cycle troubles import sys, os from _pytest import hookspec # the extension point definitions -from _pytest.core import PluginManager, hookimpl_opts, varnames +from pluggy import PluginManager, hookimpl_opts, varnames # pytest startup # diff -r bcbb77bff338c564d2b28500d0444932a04ad35a -r 8d6251cf5ac6dbbf41a3ce8dcf0dba400c769edf _pytest/core.py --- a/_pytest/core.py +++ /dev/null @@ -1,590 +0,0 @@ -""" -PluginManager, basic initialization and tracing. -""" -import sys -from inspect import isfunction, ismethod, isclass, formatargspec, getargspec -import py - -py3 = sys.version_info > (3,0) - -def hookspec_opts(firstresult=False, historic=False): - """ returns a decorator which will define a function as a hook specfication. - - If firstresult is True the 1:N hook call (N being the number of registered - hook implementation functions) will stop at I<=N when the I'th function - returns a non-None result. - - If historic is True calls to a hook will be memorized and replayed - on later registered plugins. - """ - def setattr_hookspec_opts(func): - if historic and firstresult: - raise ValueError("cannot have a historic firstresult hook") - if firstresult: - func.firstresult = firstresult - if historic: - func.historic = historic - return func - return setattr_hookspec_opts - - -def hookimpl_opts(hookwrapper=False, optionalhook=False, - tryfirst=False, trylast=False): - """ Return a decorator which marks a function as a hook implementation. - - If optionalhook is True a missing matching hook specification will not result - in an error (by default it is an error if no matching spec is found). - - If tryfirst is True this hook implementation will run as early as possible - in the chain of N hook implementations for a specfication. - - If trylast is True this hook implementation will run as late as possible - in the chain of N hook implementations. - - If hookwrapper is True the hook implementations needs to execute exactly - one "yield". The code before the yield is run early before any non-hookwrapper - function is run. The code after the yield is run after all non-hookwrapper - function have run. The yield receives an ``CallOutcome`` object representing - the exception or result outcome of the inner calls (including other hookwrapper - calls). - """ - def setattr_hookimpl_opts(func): - if hookwrapper: - func.hookwrapper = True - if optionalhook: - func.optionalhook = True - if tryfirst: - func.tryfirst = True - if trylast: - func.trylast = True - return func - return setattr_hookimpl_opts - - -class TagTracer: - def __init__(self): - self._tag2proc = {} - self.writer = None - self.indent = 0 - - def get(self, name): - return TagTracerSub(self, (name,)) - - def format_message(self, tags, args): - if isinstance(args[-1], dict): - extra = args[-1] - args = args[:-1] - else: - extra = {} - - content = " ".join(map(str, args)) - indent = " " * self.indent - - lines = [ - "%s%s [%s]\n" %(indent, content, ":".join(tags)) - ] - - for name, value in extra.items(): - lines.append("%s %s: %s\n" % (indent, name, value)) - return lines - - def processmessage(self, tags, args): - if self.writer is not None and args: - lines = self.format_message(tags, args) - self.writer(''.join(lines)) - try: - self._tag2proc[tags](tags, args) - except KeyError: - pass - - def setwriter(self, writer): - self.writer = writer - - def setprocessor(self, tags, processor): - if isinstance(tags, str): - tags = tuple(tags.split(":")) - else: - assert isinstance(tags, tuple) - self._tag2proc[tags] = processor - - -class TagTracerSub: - def __init__(self, root, tags): - self.root = root - self.tags = tags - - def __call__(self, *args): - self.root.processmessage(self.tags, args) - - def setmyprocessor(self, processor): - self.root.setprocessor(self.tags, processor) - - def get(self, name): - return self.__class__(self.root, self.tags + (name,)) - - -def raise_wrapfail(wrap_controller, msg): - co = wrap_controller.gi_code - raise RuntimeError("wrap_controller at %r %s:%d %s" % - (co.co_name, co.co_filename, co.co_firstlineno, msg)) - - -def wrapped_call(wrap_controller, func): - """ Wrap calling to a function with a generator which needs to yield - exactly once. The yield point will trigger calling the wrapped function - and return its CallOutcome to the yield point. The generator then needs - to finish (raise StopIteration) in order for the wrapped call to complete. - """ - try: - next(wrap_controller) # first yield - except StopIteration: - raise_wrapfail(wrap_controller, "did not yield") - call_outcome = CallOutcome(func) - try: - wrap_controller.send(call_outcome) - raise_wrapfail(wrap_controller, "has second yield") - except StopIteration: - pass - return call_outcome.get_result() - - -class CallOutcome: - """ Outcome of a function call, either an exception or a proper result. - Calling the ``get_result`` method will return the result or reraise - the exception raised when the function was called. """ - excinfo = None - def __init__(self, func): - try: - self.result = func() - except BaseException: - self.excinfo = sys.exc_info() - - def force_result(self, result): - self.result = result - self.excinfo = None - - def get_result(self): - if self.excinfo is None: - return self.result - else: - ex = self.excinfo - if py3: - raise ex[1].with_traceback(ex[2]) - py.builtin._reraise(*ex) - - -class TracedHookExecution: - def __init__(self, pluginmanager, before, after): - self.pluginmanager = pluginmanager - self.before = before - self.after = after - self.oldcall = pluginmanager._inner_hookexec - assert not isinstance(self.oldcall, TracedHookExecution) - self.pluginmanager._inner_hookexec = self - - def __call__(self, hook, methods, kwargs): - self.before(hook, methods, kwargs) - outcome = CallOutcome(lambda: self.oldcall(hook, methods, kwargs)) - self.after(outcome, hook, methods, kwargs) - return outcome.get_result() - - def undo(self): - self.pluginmanager._inner_hookexec = self.oldcall - - -class PluginManager(object): - """ Core Pluginmanager class which manages registration - of plugin objects and 1:N hook calling. - - You can register new hooks by calling ``addhooks(module_or_class)``. - You can register plugin objects (which contain hooks) by calling - ``register(plugin)``. The Pluginmanager is initialized with a - prefix that is searched for in the names of the dict of registered - plugin objects. An optional excludefunc allows to blacklist names which - are not considered as hooks despite a matching prefix. - - For debugging purposes you can call ``enable_tracing()`` - which will subsequently send debug information to the trace helper. - """ - - def __init__(self, prefix, excludefunc=None): - self._prefix = prefix - self._excludefunc = excludefunc - self._name2plugin = {} - self._plugin2hookcallers = {} - self._plugin_distinfo = [] - self.trace = TagTracer().get("pluginmanage") - self.hook = HookRelay(self.trace.root.get("hook")) - self._inner_hookexec = lambda hook, methods, kwargs: \ - MultiCall(methods, kwargs, hook.firstresult).execute() - - def _hookexec(self, hook, methods, kwargs): - # called from all hookcaller instances. - # enable_tracing will set its own wrapping function at self._inner_hookexec - return self._inner_hookexec(hook, methods, kwargs) - - def enable_tracing(self): - """ enable tracing of hook calls and return an undo function. """ - hooktrace = self.hook._trace - - def before(hook, methods, kwargs): - hooktrace.root.indent += 1 - hooktrace(hook.name, kwargs) - - def after(outcome, hook, methods, kwargs): - if outcome.excinfo is None: - hooktrace("finish", hook.name, "-->", outcome.result) - hooktrace.root.indent -= 1 - - return TracedHookExecution(self, before, after).undo - - def subset_hook_caller(self, name, remove_plugins): - """ Return a new HookCaller instance for the named method - which manages calls to all registered plugins except the - ones from remove_plugins. """ - orig = getattr(self.hook, name) - plugins_to_remove = [plugin for plugin in remove_plugins - if hasattr(plugin, name)] - if plugins_to_remove: - hc = HookCaller(orig.name, orig._hookexec, orig._specmodule_or_class) - for plugin in orig._plugins: - if plugin not in plugins_to_remove: - hc._add_plugin(plugin) - # we also keep track of this hook caller so it - # gets properly removed on plugin unregistration - self._plugin2hookcallers.setdefault(plugin, []).append(hc) - return hc - return orig - - def register(self, plugin, name=None): - """ Register a plugin and return its canonical name or None if the name - is blocked from registering. Raise a ValueError if the plugin is already - registered. """ - plugin_name = name or self.get_canonical_name(plugin) - - if plugin_name in self._name2plugin or plugin in self._plugin2hookcallers: - if self._name2plugin.get(plugin_name, -1) is None: - return # blocked plugin, return None to indicate no registration - raise ValueError("Plugin already registered: %s=%s\n%s" %( - plugin_name, plugin, self._name2plugin)) - - self._name2plugin[plugin_name] = plugin - - # register prefix-matching hook specs of the plugin - self._plugin2hookcallers[plugin] = hookcallers = [] - for name in dir(plugin): - if name.startswith(self._prefix): - hook = getattr(self.hook, name, None) - if hook is None: - if self._excludefunc is not None and self._excludefunc(name): - continue - hook = HookCaller(name, self._hookexec) - setattr(self.hook, name, hook) - elif hook.has_spec(): - self._verify_hook(hook, plugin) - hook._maybe_apply_history(getattr(plugin, name)) - hookcallers.append(hook) - hook._add_plugin(plugin) - return plugin_name - - def unregister(self, plugin=None, name=None): - """ unregister a plugin object and all its contained hook implementations - from internal data structures. """ - if name is None: - assert plugin is not None, "one of name or plugin needs to be specified" - name = self.get_name(plugin) - - if plugin is None: - plugin = self.get_plugin(name) - - # if self._name2plugin[name] == None registration was blocked: ignore - if self._name2plugin.get(name): - del self._name2plugin[name] - - for hookcaller in self._plugin2hookcallers.pop(plugin, []): - hookcaller._remove_plugin(plugin) - - return plugin - - def set_blocked(self, name): - """ block registrations of the given name, unregister if already registered. """ - self.unregister(name=name) - self._name2plugin[name] = None - - def addhooks(self, module_or_class): - """ add new hook definitions from the given module_or_class using - the prefix/excludefunc with which the PluginManager was initialized. """ - names = [] - for name in dir(module_or_class): - if name.startswith(self._prefix): - hc = getattr(self.hook, name, None) - if hc is None: - hc = HookCaller(name, self._hookexec, module_or_class) - setattr(self.hook, name, hc) - else: - # plugins registered this hook without knowing the spec - hc.set_specification(module_or_class) - for plugin in hc._plugins: - self._verify_hook(hc, plugin) - names.append(name) - - if not names: - raise ValueError("did not find new %r hooks in %r" - %(self._prefix, module_or_class)) - - def get_plugins(self): - """ return the set of registered plugins. """ - return set(self._plugin2hookcallers) - - def is_registered(self, plugin): - """ Return True if the plugin is already registered. """ - return plugin in self._plugin2hookcallers - - def get_canonical_name(self, plugin): - """ Return canonical name for a plugin object. Note that a plugin - may be registered under a different name which was specified - by the caller of register(plugin, name). To obtain the name - of an registered plugin use ``get_name(plugin)`` instead.""" - return getattr(plugin, "__name__", None) or str(id(plugin)) - - def get_plugin(self, name): - """ Return a plugin or None for the given name. """ - return self._name2plugin.get(name) - - def get_name(self, plugin): - """ Return name for registered plugin or None if not registered. """ - for name, val in self._name2plugin.items(): - if plugin == val: - return name - - def _verify_hook(self, hook, plugin): - method = getattr(plugin, hook.name) - pluginname = self.get_name(plugin) - - if hook.is_historic() and hasattr(method, "hookwrapper"): - raise PluginValidationError( - "Plugin %r\nhook %r\nhistoric incompatible to hookwrapper" %( - pluginname, hook.name)) - - for arg in varnames(method): - if arg not in hook.argnames: - raise PluginValidationError( - "Plugin %r\nhook %r\nargument %r not available\n" - "plugin definition: %s\n" - "available hookargs: %s" %( - pluginname, hook.name, arg, formatdef(method), - ", ".join(hook.argnames))) - - def check_pending(self): - """ Verify that all hooks which have not been verified against - a hook specification are optional, otherwise raise PluginValidationError""" - for name in self.hook.__dict__: - if name.startswith(self._prefix): - hook = getattr(self.hook, name) - if not hook.has_spec(): - for plugin in hook._plugins: - method = getattr(plugin, hook.name) - if not getattr(method, "optionalhook", False): - raise PluginValidationError( - "unknown hook %r in plugin %r" %(name, plugin)) - - def load_setuptools_entrypoints(self, entrypoint_name): - """ Load modules from querying the specified setuptools entrypoint name. - Return the number of loaded plugins. """ - from pkg_resources import iter_entry_points, DistributionNotFound - for ep in iter_entry_points(entrypoint_name): - # is the plugin registered or blocked? - if self.get_plugin(ep.name) or ep.name in self._name2plugin: - continue - try: - plugin = ep.load() - except DistributionNotFound: - continue - self.register(plugin, name=ep.name) - self._plugin_distinfo.append((ep.dist, plugin)) - return len(self._plugin_distinfo) - - -class MultiCall: - """ execute a call into multiple python functions/methods. """ - - # XXX note that the __multicall__ argument is supported only - # for pytest compatibility reasons. It was never officially - # supported there and is explicitely deprecated since 2.8 - # so we can remove it soon, allowing to avoid the below recursion - # in execute() and simplify/speed up the execute loop. - - def __init__(self, methods, kwargs, firstresult=False): - self.methods = methods - self.kwargs = kwargs - self.kwargs["__multicall__"] = self - self.firstresult = firstresult - - def execute(self): - all_kwargs = self.kwargs - self.results = results = [] - firstresult = self.firstresult - - while self.methods: - method = self.methods.pop() - args = [all_kwargs[argname] for argname in varnames(method)] - if hasattr(method, "hookwrapper"): - return wrapped_call(method(*args), self.execute) - res = method(*args) - if res is not None: - if firstresult: - return res - results.append(res) - - if not firstresult: - return results - - def __repr__(self): - status = "%d meths" % (len(self.methods),) - if hasattr(self, "results"): - status = ("%d results, " % len(self.results)) + status - return "" %(status, self.kwargs) - - - -def varnames(func, startindex=None): - """ return argument name tuple for a function, method, class or callable. - - In case of a class, its "__init__" method is considered. - For methods the "self" parameter is not included unless you are passing - an unbound method with Python3 (which has no supports for unbound methods) - """ - cache = getattr(func, "__dict__", {}) - try: - return cache["_varnames"] - except KeyError: - pass - if isclass(func): - try: - func = func.__init__ - except AttributeError: - return () - startindex = 1 - else: - if not isfunction(func) and not ismethod(func): - func = getattr(func, '__call__', func) - if startindex is None: - startindex = int(ismethod(func)) - - rawcode = py.code.getrawcode(func) - try: - x = rawcode.co_varnames[startindex:rawcode.co_argcount] - except AttributeError: - x = () - else: - defaults = func.__defaults__ - if defaults: - x = x[:-len(defaults)] - try: - cache["_varnames"] = x - except TypeError: - pass - return x - - -class HookRelay: - def __init__(self, trace): - self._trace = trace - - -class HookCaller(object): - def __init__(self, name, hook_execute, specmodule_or_class=None): - self.name = name - self._plugins = [] - self._wrappers = [] - self._nonwrappers = [] - self._hookexec = hook_execute - if specmodule_or_class is not None: - self.set_specification(specmodule_or_class) - - def has_spec(self): - return hasattr(self, "_specmodule_or_class") - - def set_specification(self, specmodule_or_class): - assert not self.has_spec() - self._specmodule_or_class = specmodule_or_class - specfunc = getattr(specmodule_or_class, self.name) - argnames = varnames(specfunc, startindex=isclass(specmodule_or_class)) - assert "self" not in argnames # sanity check - self.argnames = ["__multicall__"] + list(argnames) - self.firstresult = getattr(specfunc, 'firstresult', False) - if hasattr(specfunc, "historic"): - self._call_history = [] - - def is_historic(self): - return hasattr(self, "_call_history") - - def _remove_plugin(self, plugin): - self._plugins.remove(plugin) - meth = getattr(plugin, self.name) - try: - self._nonwrappers.remove(meth) - except ValueError: - self._wrappers.remove(meth) - - def _add_plugin(self, plugin): - self._plugins.append(plugin) - self._add_method(getattr(plugin, self.name)) - - def _add_method(self, meth): - if hasattr(meth, 'hookwrapper'): - methods = self._wrappers - else: - methods = self._nonwrappers - - if hasattr(meth, 'trylast'): - methods.insert(0, meth) - elif hasattr(meth, 'tryfirst'): - methods.append(meth) - else: - # find last non-tryfirst method - i = len(methods) - 1 - while i >= 0 and hasattr(methods[i], "tryfirst"): - i -= 1 - methods.insert(i + 1, meth) - - def __repr__(self): - return "" %(self.name,) - - def __call__(self, **kwargs): - assert not self.is_historic() - return self._hookexec(self, self._nonwrappers + self._wrappers, kwargs) - - def call_historic(self, proc=None, kwargs=None): - self._call_history.append((kwargs or {}, proc)) - # historizing hooks don't return results - self._hookexec(self, self._nonwrappers + self._wrappers, kwargs) - - def call_extra(self, methods, kwargs): - """ Call the hook with some additional temporarily participating - methods using the specified kwargs as call parameters. """ - old = list(self._nonwrappers), list(self._wrappers) - for method in methods: - self._add_method(method) - try: - return self(**kwargs) - finally: - self._nonwrappers, self._wrappers = old - - def _maybe_apply_history(self, method): - if self.is_historic(): - for kwargs, proc in self._call_history: - res = self._hookexec(self, [method], kwargs) - if res and proc is not None: - proc(res[0]) - - -class PluginValidationError(Exception): - """ plugin failed validation. """ - - -def formatdef(func): - return "%s%s" % ( - func.__name__, - formatargspec(*getargspec(func)) - ) diff -r bcbb77bff338c564d2b28500d0444932a04ad35a -r 8d6251cf5ac6dbbf41a3ce8dcf0dba400c769edf _pytest/genscript.py --- a/_pytest/genscript.py +++ b/_pytest/genscript.py @@ -4,6 +4,7 @@ import pkgutil import py +import pluggy import _pytest @@ -69,7 +70,7 @@ genscript = config.getvalue("genscript") if genscript: tw = py.io.TerminalWriter() - deps = ['py', '_pytest', 'pytest'] + deps = ['py', '_pytest', 'pytest', 'pluggy'] if sys.version_info < (2,7): deps.append("argparse") tw.line("generated script will run on python2.6-python3.3++") diff -r bcbb77bff338c564d2b28500d0444932a04ad35a -r 8d6251cf5ac6dbbf41a3ce8dcf0dba400c769edf _pytest/hookspec.py --- a/_pytest/hookspec.py +++ b/_pytest/hookspec.py @@ -1,6 +1,6 @@ """ hook specifications for pytest plugins, invoked from main.py and builtin plugins. """ -from _pytest.core import hookspec_opts +from pluggy import hookspec_opts # ------------------------------------------------------------------------- # Initialization hooks called for every plugin diff -r bcbb77bff338c564d2b28500d0444932a04ad35a -r 8d6251cf5ac6dbbf41a3ce8dcf0dba400c769edf _pytest/pytester.py --- a/_pytest/pytester.py +++ b/_pytest/pytester.py @@ -13,7 +13,7 @@ import py import pytest from py.builtin import print_ -from _pytest.core import TracedHookExecution +from pluggy import _TracedHookExecution from _pytest.main import Session, EXIT_OK @@ -198,7 +198,7 @@ self.calls.append(ParsedCall(hook.name, kwargs)) def after(outcome, hook, method, kwargs): pass - executor = TracedHookExecution(pluginmanager, before, after) + executor = _TracedHookExecution(pluginmanager, before, after) self._undo_wrapping = executor.undo def finish_recording(self): diff -r bcbb77bff338c564d2b28500d0444932a04ad35a -r 8d6251cf5ac6dbbf41a3ce8dcf0dba400c769edf _pytest/python.py --- a/_pytest/python.py +++ b/_pytest/python.py @@ -8,7 +8,11 @@ from py._code.code import TerminalRepr import _pytest -cutdir = py.path.local(_pytest.__file__).dirpath() +import pluggy + +cutdir2 = py.path.local(_pytest.__file__).dirpath() +cutdir1 = py.path.local(pluggy.__file__.rstrip("oc")) + NoneType = type(None) NOTSET = object() @@ -18,6 +22,11 @@ # used to work around a python2 exception info leak exc_clear = getattr(sys, 'exc_clear', lambda: None) + +def filter_traceback(entry): + return entry.path != cutdir1 and not entry.path.relto(cutdir2) + + def getfslineno(obj): # xxx let decorators etc specify a sane ordering while hasattr(obj, "__wrapped__"): @@ -604,7 +613,11 @@ if ntraceback == traceback: ntraceback = ntraceback.cut(path=path) if ntraceback == traceback: - ntraceback = ntraceback.cut(excludepath=cutdir) + #ntraceback = ntraceback.cut(excludepath=cutdir2) + ntraceback = ntraceback.filter(filter_traceback) + if not ntraceback: + ntraceback = traceback + excinfo.traceback = ntraceback.filter() # issue364: mark all but first and last frames to # only show a single-line message for each frame diff -r bcbb77bff338c564d2b28500d0444932a04ad35a -r 8d6251cf5ac6dbbf41a3ce8dcf0dba400c769edf pytest.py --- a/pytest.py +++ b/pytest.py @@ -12,7 +12,7 @@ # else we are imported from _pytest.config import main, UsageError, _preloadplugins, cmdline -from _pytest.core import hookspec_opts, hookimpl_opts +from pluggy import hookspec_opts, hookimpl_opts from _pytest import __version__ _preloadplugins() # to populate pytest.* namespace so help(pytest) works diff -r bcbb77bff338c564d2b28500d0444932a04ad35a -r 8d6251cf5ac6dbbf41a3ce8dcf0dba400c769edf setup.py --- a/setup.py +++ b/setup.py @@ -48,7 +48,7 @@ def main(): - install_requires = ['py>=1.4.27.dev2'] + install_requires = ['py>=1.4.27.dev2', 'pluggy>=0.1.0,<0.2.0'] extras_require = {} if has_environment_marker_support(): extras_require[':python_version=="2.6" or python_version=="3.0" or python_version=="3.1"'] = ['argparse'] diff -r bcbb77bff338c564d2b28500d0444932a04ad35a -r 8d6251cf5ac6dbbf41a3ce8dcf0dba400c769edf testing/test_core.py --- a/testing/test_core.py +++ /dev/null @@ -1,1050 +0,0 @@ -import pytest, py, os -from _pytest.core import * # noqa -from _pytest.config import get_config - - - at pytest.fixture -def pm(): - return PluginManager("he") - - at pytest.fixture -def pytestpm(): - return PytestPluginManager() - - -class TestPluginManager: - def test_plugin_double_register(self, pm): - pm.register(42, name="abc") - with pytest.raises(ValueError): - pm.register(42, name="abc") - with pytest.raises(ValueError): - pm.register(42, name="def") - - def test_pm(self, pm): - class A: pass - a1, a2 = A(), A() - pm.register(a1) - assert pm.is_registered(a1) - pm.register(a2, "hello") - assert pm.is_registered(a2) - l = pm.get_plugins() - assert a1 in l - assert a2 in l - assert pm.get_plugin('hello') == a2 - assert pm.unregister(a1) == a1 - assert not pm.is_registered(a1) - - def test_pm_name(self, pm): - class A: pass - a1 = A() - name = pm.register(a1, name="hello") - assert name == "hello" - pm.unregister(a1) - assert pm.get_plugin(a1) is None - assert not pm.is_registered(a1) - assert not pm.get_plugins() - name2 = pm.register(a1, name="hello") - assert name2 == name - pm.unregister(name="hello") - assert pm.get_plugin(a1) is None - assert not pm.is_registered(a1) - assert not pm.get_plugins() - - def test_set_blocked(self, pm): - class A: pass - a1 = A() - name = pm.register(a1) - assert pm.is_registered(a1) - pm.set_blocked(name) - assert not pm.is_registered(a1) - - pm.set_blocked("somename") - assert not pm.register(A(), "somename") - pm.unregister(name="somename") - - def test_register_mismatch_method(self, pytestpm): - class hello: - def pytest_gurgel(self): - pass - pytestpm.register(hello()) - with pytest.raises(PluginValidationError): - pytestpm.check_pending() - - def test_register_mismatch_arg(self): - pm = get_config().pluginmanager - class hello: - def pytest_configure(self, asd): - pass - pytest.raises(Exception, lambda: pm.register(hello())) - - def test_register(self): - pm = get_config().pluginmanager - class MyPlugin: - pass - my = MyPlugin() - pm.register(my) - assert pm.get_plugins() - my2 = MyPlugin() - pm.register(my2) - assert set([my,my2]).issubset(pm.get_plugins()) - - assert pm.is_registered(my) - assert pm.is_registered(my2) - pm.unregister(my) - assert not pm.is_registered(my) - assert my not in pm.get_plugins() - - def test_register_unknown_hooks(self, pm): - class Plugin1: - def he_method1(self, arg): - return arg + 1 - - pm.register(Plugin1()) - class Hooks: - def he_method1(self, arg): - pass - pm.addhooks(Hooks) - #assert not pm._unverified_hooks - assert pm.hook.he_method1(arg=1) == [2] - - def test_register_historic(self, pm): - class Hooks: - @hookspec_opts(historic=True) - def he_method1(self, arg): - pass - pm.addhooks(Hooks) - - pm.hook.he_method1.call_historic(kwargs=dict(arg=1)) - l = [] - class Plugin: - def he_method1(self, arg): - l.append(arg) - - pm.register(Plugin()) - assert l == [1] - - class Plugin2: - def he_method1(self, arg): - l.append(arg*10) - pm.register(Plugin2()) - assert l == [1, 10] - pm.hook.he_method1.call_historic(kwargs=dict(arg=12)) - assert l == [1, 10, 120, 12] - - def test_with_result_memorized(self, pm): - class Hooks: - @hookspec_opts(historic=True) - def he_method1(self, arg): - pass - pm.addhooks(Hooks) - - he_method1 = pm.hook.he_method1 - he_method1.call_historic(lambda res: l.append(res), dict(arg=1)) - l = [] - class Plugin: - def he_method1(self, arg): - return arg * 10 - - pm.register(Plugin()) - assert l == [10] - - def test_register_historic_incompat_hookwrapper(self, pm): - class Hooks: - @hookspec_opts(historic=True) - def he_method1(self, arg): - pass - pm.addhooks(Hooks) - - l = [] - class Plugin: - @hookimpl_opts(hookwrapper=True) - def he_method1(self, arg): - l.append(arg) - - with pytest.raises(PluginValidationError): - pm.register(Plugin()) - - def test_call_extra(self, pm): - class Hooks: - def he_method1(self, arg): - pass - pm.addhooks(Hooks) - - def he_method1(arg): - return arg * 10 - - l = pm.hook.he_method1.call_extra([he_method1], dict(arg=1)) - assert l == [10] - - def test_subset_hook_caller(self, pm): - class Hooks: - def he_method1(self, arg): - pass - pm.addhooks(Hooks) - - l = [] - class Plugin1: - def he_method1(self, arg): - l.append(arg) - class Plugin2: - def he_method1(self, arg): - l.append(arg*10) - class PluginNo: - pass - - plugin1, plugin2, plugin3 = Plugin1(), Plugin2(), PluginNo() - pm.register(plugin1) - pm.register(plugin2) - pm.register(plugin3) - pm.hook.he_method1(arg=1) - assert l == [10, 1] - l[:] = [] - - hc = pm.subset_hook_caller("he_method1", [plugin1]) - hc(arg=2) - assert l == [20] - l[:] = [] - - hc = pm.subset_hook_caller("he_method1", [plugin2]) - hc(arg=2) - assert l == [2] - l[:] = [] - - pm.unregister(plugin1) - hc(arg=2) - assert l == [] - l[:] = [] - - pm.hook.he_method1(arg=1) - assert l == [10] - - - -class TestAddMethodOrdering: - @pytest.fixture - def hc(self, pm): - class Hooks: - def he_method1(self, arg): - pass - pm.addhooks(Hooks) - return pm.hook.he_method1 - - @pytest.fixture - def addmeth(self, hc): - def addmeth(tryfirst=False, trylast=False, hookwrapper=False): - def wrap(func): - if tryfirst: - func.tryfirst = True - if trylast: - func.trylast = True - if hookwrapper: - func.hookwrapper = True - hc._add_method(func) - return func - return wrap - return addmeth - - def test_adding_nonwrappers(self, hc, addmeth): - @addmeth() - def he_method1(): - pass - - @addmeth() - def he_method2(): - pass - - @addmeth() - def he_method3(): - pass - assert hc._nonwrappers == [he_method1, he_method2, he_method3] - - def test_adding_nonwrappers_trylast(self, hc, addmeth): - @addmeth() - def he_method1_middle(): - pass - - @addmeth(trylast=True) - def he_method1(): - pass - - @addmeth() - def he_method1_b(): - pass - assert hc._nonwrappers == [he_method1, he_method1_middle, he_method1_b] - - def test_adding_nonwrappers_trylast3(self, hc, addmeth): - @addmeth() - def he_method1_a(): - pass - - @addmeth(trylast=True) - def he_method1_b(): - pass - - @addmeth() - def he_method1_c(): - pass - - @addmeth(trylast=True) - def he_method1_d(): - pass - assert hc._nonwrappers == [he_method1_d, he_method1_b, - he_method1_a, he_method1_c] - - - def test_adding_nonwrappers_trylast2(self, hc, addmeth): - @addmeth() - def he_method1_middle(): - pass - - @addmeth() - def he_method1_b(): - pass - - @addmeth(trylast=True) - def he_method1(): - pass - assert hc._nonwrappers == [he_method1, he_method1_middle, he_method1_b] - - def test_adding_nonwrappers_tryfirst(self, hc, addmeth): - @addmeth(tryfirst=True) - def he_method1(): - pass - - @addmeth() - def he_method1_middle(): - pass - - @addmeth() - def he_method1_b(): - pass - assert hc._nonwrappers == [he_method1_middle, he_method1_b, he_method1] - - def test_adding_wrappers_ordering(self, hc, addmeth): - @addmeth(hookwrapper=True) - def he_method1(): - pass - - @addmeth() - def he_method1_middle(): - pass - - @addmeth(hookwrapper=True) - def he_method3(): - pass - - assert hc._nonwrappers == [he_method1_middle] - assert hc._wrappers == [he_method1, he_method3] - - def test_adding_wrappers_ordering_tryfirst(self, hc, addmeth): - @addmeth(hookwrapper=True, tryfirst=True) - def he_method1(): - pass - - @addmeth(hookwrapper=True) - def he_method2(): - pass - - assert hc._nonwrappers == [] - assert hc._wrappers == [he_method2, he_method1] - - - def test_hookspec_opts(self, pm): - class HookSpec: - @hookspec_opts() - def he_myhook1(self, arg1): - pass - - @hookspec_opts(firstresult=True) - def he_myhook2(self, arg1): - pass - - @hookspec_opts(firstresult=False) - def he_myhook3(self, arg1): - pass - - pm.addhooks(HookSpec) - assert not pm.hook.he_myhook1.firstresult - assert pm.hook.he_myhook2.firstresult - assert not pm.hook.he_myhook3.firstresult - - - def test_hookimpl_opts(self): - for name in ["hookwrapper", "optionalhook", "tryfirst", "trylast"]: - for val in [True, False]: - @hookimpl_opts(**{name: val}) - def he_myhook1(self, arg1): - pass - if val: - assert getattr(he_myhook1, name) - else: - assert not hasattr(he_myhook1, name) - - def test_decorator_functional(self, pm): - class HookSpec: - @hookspec_opts(firstresult=True) - def he_myhook(self, arg1): - """ add to arg1 """ - pm.addhooks(HookSpec) - - class Plugin: - @hookimpl_opts() - def he_myhook(self, arg1): - return arg1 + 1 - - pm.register(Plugin()) - results = pm.hook.he_myhook(arg1=17) - assert results == 18 - - def test_load_setuptools_instantiation(self, monkeypatch, pm): - pkg_resources = pytest.importorskip("pkg_resources") - def my_iter(name): - assert name == "hello" - class EntryPoint: - name = "myname" - dist = None - def load(self): - class PseudoPlugin: - x = 42 - return PseudoPlugin() - return iter([EntryPoint()]) - - monkeypatch.setattr(pkg_resources, 'iter_entry_points', my_iter) - num = pm.load_setuptools_entrypoints("hello") - assert num == 1 - plugin = pm.get_plugin("myname") - assert plugin.x == 42 - assert pm._plugin_distinfo == [(None, plugin)] - - def test_load_setuptools_not_installed(self, monkeypatch, pm): - monkeypatch.setitem(py.std.sys.modules, 'pkg_resources', - py.std.types.ModuleType("pkg_resources")) - with pytest.raises(ImportError): - pm.load_setuptools_entrypoints("qwe") - - -class TestPytestPluginInteractions: - - def test_addhooks_conftestplugin(self, testdir): - testdir.makepyfile(newhooks=""" - def pytest_myhook(xyz): - "new hook" - """) - conf = testdir.makeconftest(""" - import sys ; sys.path.insert(0, '.') - import newhooks - def pytest_addhooks(pluginmanager): - pluginmanager.addhooks(newhooks) - def pytest_myhook(xyz): - return xyz + 1 - """) - config = get_config() - pm = config.pluginmanager - pm.hook.pytest_addhooks.call_historic( - kwargs=dict(pluginmanager=config.pluginmanager)) - config.pluginmanager._importconftest(conf) - #print(config.pluginmanager.get_plugins()) - res = config.hook.pytest_myhook(xyz=10) - assert res == [11] - - def test_addhooks_nohooks(self, testdir): - testdir.makeconftest(""" - import sys - def pytest_addhooks(pluginmanager): - pluginmanager.addhooks(sys) - """) - res = testdir.runpytest() - assert res.ret != 0 - res.stderr.fnmatch_lines([ - "*did not find*sys*" - ]) - - def test_namespace_early_from_import(self, testdir): - p = testdir.makepyfile(""" - from pytest import Item - from pytest import Item as Item2 - assert Item is Item2 - """) - result = testdir.runpython(p) - assert result.ret == 0 - - def test_do_ext_namespace(self, testdir): - testdir.makeconftest(""" - def pytest_namespace(): - return {'hello': 'world'} - """) - p = testdir.makepyfile(""" - from pytest import hello - import pytest - def test_hello(): - assert hello == "world" - assert 'hello' in pytest.__all__ - """) - reprec = testdir.inline_run(p) - reprec.assertoutcome(passed=1) - - def test_do_option_postinitialize(self, testdir): - config = testdir.parseconfigure() - assert not hasattr(config.option, 'test123') - p = testdir.makepyfile(""" - def pytest_addoption(parser): - parser.addoption('--test123', action="store_true", - default=True) - """) - config.pluginmanager._importconftest(p) - assert config.option.test123 - - def test_configure(self, testdir): - config = testdir.parseconfig() - l = [] - class A: - def pytest_configure(self, config): - l.append(self) - - config.pluginmanager.register(A()) - assert len(l) == 0 - config._do_configure() - assert len(l) == 1 - config.pluginmanager.register(A()) # leads to a configured() plugin - assert len(l) == 2 - assert l[0] != l[1] - - config._ensure_unconfigure() - config.pluginmanager.register(A()) - assert len(l) == 2 - - def test_hook_tracing(self): - pytestpm = get_config().pluginmanager # fully initialized with plugins - saveindent = [] - class api1: - def pytest_plugin_registered(self): - saveindent.append(pytestpm.trace.root.indent) - class api2: - def pytest_plugin_registered(self): - saveindent.append(pytestpm.trace.root.indent) - raise ValueError() - l = [] - pytestpm.trace.root.setwriter(l.append) - undo = pytestpm.enable_tracing() - try: - indent = pytestpm.trace.root.indent - p = api1() - pytestpm.register(p) - assert pytestpm.trace.root.indent == indent - assert len(l) >= 2 - assert 'pytest_plugin_registered' in l[0] - assert 'finish' in l[1] - - l[:] = [] - with pytest.raises(ValueError): - pytestpm.register(api2()) - assert pytestpm.trace.root.indent == indent - assert saveindent[0] > indent - finally: - undo() - - def test_warn_on_deprecated_multicall(self, pytestpm): - class Plugin: - def pytest_configure(self, __multicall__): - pass - - before = list(pytestpm._warnings) - pytestpm.register(Plugin()) - assert len(pytestpm._warnings) == len(before) + 1 - assert "deprecated" in pytestpm._warnings[-1]["message"] - - -def test_namespace_has_default_and_env_plugins(testdir): - p = testdir.makepyfile(""" - import pytest - pytest.mark - """) - result = testdir.runpython(p) - assert result.ret == 0 - -def test_varnames(): - def f(x): - i = 3 # noqa - class A: - def f(self, y): - pass - class B(object): - def __call__(self, z): - pass - assert varnames(f) == ("x",) - assert varnames(A().f) == ('y',) - assert varnames(B()) == ('z',) - -def test_varnames_default(): - def f(x, y=3): - pass - assert varnames(f) == ("x",) - -def test_varnames_class(): - class C: - def __init__(self, x): - pass - class D: - pass - assert varnames(C) == ("x",) - assert varnames(D) == () - -class TestMultiCall: - def test_uses_copy_of_methods(self): - l = [lambda: 42] - mc = MultiCall(l, {}) - repr(mc) - l[:] = [] - res = mc.execute() - return res == 42 - - def test_call_passing(self): - class P1: - def m(self, __multicall__, x): - assert len(__multicall__.results) == 1 - assert not __multicall__.methods - return 17 - - class P2: - def m(self, __multicall__, x): - assert __multicall__.results == [] - assert __multicall__.methods - return 23 - - p1 = P1() - p2 = P2() - multicall = MultiCall([p1.m, p2.m], {'x': 23}) - assert "23" in repr(multicall) - reslist = multicall.execute() - assert len(reslist) == 2 - # ensure reversed order - assert reslist == [23, 17] - - def test_keyword_args(self): - def f(x): - return x + 1 - class A: - def f(self, x, y): - return x + y - multicall = MultiCall([f, A().f], dict(x=23, y=24)) - assert "'x': 23" in repr(multicall) - assert "'y': 24" in repr(multicall) - reslist = multicall.execute() - assert reslist == [24+23, 24] - assert "2 results" in repr(multicall) - - def test_keyword_args_with_defaultargs(self): - def f(x, z=1): - return x + z - reslist = MultiCall([f], dict(x=23, y=24)).execute() - assert reslist == [24] - - def test_tags_call_error(self): - multicall = MultiCall([lambda x: x], {}) - pytest.raises(KeyError, multicall.execute) - - def test_call_subexecute(self): - def m(__multicall__): - subresult = __multicall__.execute() - return subresult + 1 - - def n(): - return 1 - - call = MultiCall([n, m], {}, firstresult=True) - res = call.execute() - assert res == 2 - - def test_call_none_is_no_result(self): - def m1(): - return 1 - def m2(): - return None - res = MultiCall([m1, m2], {}, firstresult=True).execute() - assert res == 1 - res = MultiCall([m1, m2], {}).execute() - assert res == [1] - - def test_hookwrapper(self): - l = [] - def m1(): - l.append("m1 init") - yield None - l.append("m1 finish") - m1.hookwrapper = True - - def m2(): - l.append("m2") - return 2 - res = MultiCall([m2, m1], {}).execute() - assert res == [2] - assert l == ["m1 init", "m2", "m1 finish"] - l[:] = [] - res = MultiCall([m2, m1], {}, firstresult=True).execute() - assert res == 2 - assert l == ["m1 init", "m2", "m1 finish"] - - def test_hookwrapper_order(self): - l = [] - def m1(): - l.append("m1 init") - yield 1 - l.append("m1 finish") - m1.hookwrapper = True - - def m2(): - l.append("m2 init") - yield 2 - l.append("m2 finish") - m2.hookwrapper = True - res = MultiCall([m2, m1], {}).execute() - assert res == [] - assert l == ["m1 init", "m2 init", "m2 finish", "m1 finish"] - - def test_hookwrapper_not_yield(self): - def m1(): - pass - m1.hookwrapper = True - - mc = MultiCall([m1], {}) - with pytest.raises(TypeError): - mc.execute() - - def test_hookwrapper_too_many_yield(self): - def m1(): - yield 1 - yield 2 - m1.hookwrapper = True - - mc = MultiCall([m1], {}) - with pytest.raises(RuntimeError) as ex: - mc.execute() - assert "m1" in str(ex.value) - assert "test_core.py:" in str(ex.value) - - @pytest.mark.parametrize("exc", [ValueError, SystemExit]) - def test_hookwrapper_exception(self, exc): - l = [] - def m1(): - l.append("m1 init") - yield None - l.append("m1 finish") - m1.hookwrapper = True - - def m2(): - raise exc - with pytest.raises(exc): - MultiCall([m2, m1], {}).execute() - assert l == ["m1 init", "m1 finish"] - - -class TestHookRelay: - def test_hapmypath(self): - class Api: - def hello(self, arg): - "api hook 1" - pm = PluginManager("he") - pm.addhooks(Api) - hook = pm.hook - assert hasattr(hook, 'hello') - assert repr(hook.hello).find("hello") != -1 - class Plugin: - def hello(self, arg): - return arg + 1 - plugin = Plugin() - pm.register(plugin) - l = hook.hello(arg=3) - assert l == [4] - assert not hasattr(hook, 'world') - pm.unregister(plugin) - assert hook.hello(arg=3) == [] - - def test_argmismatch(self): - class Api: - def hello(self, arg): - "api hook 1" - pm = PluginManager("he") - pm.addhooks(Api) - class Plugin: - def hello(self, argwrong): - return arg + 1 - with pytest.raises(PluginValidationError) as exc: - pm.register(Plugin()) - assert "argwrong" in str(exc.value) - - def test_only_kwargs(self): - pm = PluginManager("he") - class Api: - def hello(self, arg): - "api hook 1" - pm.addhooks(Api) - pytest.raises(TypeError, lambda: pm.hook.hello(3)) - - def test_firstresult_definition(self): - class Api: - def hello(self, arg): - "api hook 1" - hello.firstresult = True - pm = PluginManager("he") - pm.addhooks(Api) - class Plugin: - def hello(self, arg): - return arg + 1 - pm.register(Plugin()) - res = pm.hook.hello(arg=3) - assert res == 4 - -class TestTracer: - def test_simple(self): - from _pytest.core import TagTracer - rootlogger = TagTracer() - log = rootlogger.get("pytest") - log("hello") - l = [] - rootlogger.setwriter(l.append) - log("world") - assert len(l) == 1 - assert l[0] == "world [pytest]\n" - sublog = log.get("collection") - sublog("hello") - assert l[1] == "hello [pytest:collection]\n" - - def test_indent(self): - from _pytest.core import TagTracer - rootlogger = TagTracer() - log = rootlogger.get("1") - l = [] - log.root.setwriter(lambda arg: l.append(arg)) - log("hello") - log.root.indent += 1 - log("line1") - log("line2") - log.root.indent += 1 - log("line3") - log("line4") - log.root.indent -= 1 - log("line5") - log.root.indent -= 1 - log("last") - assert len(l) == 7 - names = [x[:x.rfind(' [')] for x in l] - assert names == ['hello', ' line1', ' line2', - ' line3', ' line4', ' line5', 'last'] - - def test_readable_output_dictargs(self): - from _pytest.core import TagTracer - rootlogger = TagTracer() - - out = rootlogger.format_message(['test'], [1]) - assert out == ['1 [test]\n'] - - out2= rootlogger.format_message(['test'], ['test', {'a':1}]) - assert out2 ==[ - 'test [test]\n', - ' a: 1\n' - ] - - def test_setprocessor(self): - from _pytest.core import TagTracer - rootlogger = TagTracer() - log = rootlogger.get("1") - log2 = log.get("2") - assert log2.tags == tuple("12") - l = [] - rootlogger.setprocessor(tuple("12"), lambda *args: l.append(args)) - log("not seen") - log2("seen") - assert len(l) == 1 - tags, args = l[0] - assert "1" in tags - assert "2" in tags - assert args == ("seen",) - l2 = [] - rootlogger.setprocessor("1:2", lambda *args: l2.append(args)) - log2("seen") - tags, args = l2[0] - assert args == ("seen",) - - - def test_setmyprocessor(self): - from _pytest.core import TagTracer - rootlogger = TagTracer() - log = rootlogger.get("1") - log2 = log.get("2") - l = [] - log2.setmyprocessor(lambda *args: l.append(args)) - log("not seen") - assert not l - log2(42) - assert len(l) == 1 - tags, args = l[0] - assert "1" in tags - assert "2" in tags - assert args == (42,) - -def test_default_markers(testdir): - result = testdir.runpytest("--markers") - result.stdout.fnmatch_lines([ - "*tryfirst*first*", - "*trylast*last*", - ]) - -def test_importplugin_issue375(testdir, pytestpm): - testdir.syspathinsert(testdir.tmpdir) - testdir.makepyfile(qwe="import aaaa") - with pytest.raises(ImportError) as excinfo: - pytestpm.import_plugin("qwe") - assert "qwe" not in str(excinfo.value) - assert "aaaa" in str(excinfo.value) - - -### to be shifted to own test file -from _pytest.config import PytestPluginManager - -class TestPytestPluginManager: - def test_register_imported_modules(self): - pm = PytestPluginManager() - mod = py.std.types.ModuleType("x.y.pytest_hello") - pm.register(mod) - assert pm.is_registered(mod) - l = pm.get_plugins() - assert mod in l - pytest.raises(ValueError, "pm.register(mod)") - pytest.raises(ValueError, lambda: pm.register(mod)) - #assert not pm.is_registered(mod2) - assert pm.get_plugins() == l - - def test_canonical_import(self, monkeypatch): - mod = py.std.types.ModuleType("pytest_xyz") - monkeypatch.setitem(py.std.sys.modules, 'pytest_xyz', mod) - pm = PytestPluginManager() - pm.import_plugin('pytest_xyz') - assert pm.get_plugin('pytest_xyz') == mod - assert pm.is_registered(mod) - - def test_consider_module(self, testdir, pytestpm): - testdir.syspathinsert() - testdir.makepyfile(pytest_p1="#") - testdir.makepyfile(pytest_p2="#") - mod = py.std.types.ModuleType("temp") - mod.pytest_plugins = ["pytest_p1", "pytest_p2"] - pytestpm.consider_module(mod) - assert pytestpm.get_plugin("pytest_p1").__name__ == "pytest_p1" - assert pytestpm.get_plugin("pytest_p2").__name__ == "pytest_p2" - - def test_consider_module_import_module(self, testdir): - pytestpm = get_config().pluginmanager - mod = py.std.types.ModuleType("x") - mod.pytest_plugins = "pytest_a" - aplugin = testdir.makepyfile(pytest_a="#") - reprec = testdir.make_hook_recorder(pytestpm) - #syspath.prepend(aplugin.dirpath()) - py.std.sys.path.insert(0, str(aplugin.dirpath())) - pytestpm.consider_module(mod) - call = reprec.getcall(pytestpm.hook.pytest_plugin_registered.name) - assert call.plugin.__name__ == "pytest_a" - - # check that it is not registered twice - pytestpm.consider_module(mod) - l = reprec.getcalls("pytest_plugin_registered") - assert len(l) == 1 - - def test_consider_env_fails_to_import(self, monkeypatch, pytestpm): - monkeypatch.setenv('PYTEST_PLUGINS', 'nonexisting', prepend=",") - with pytest.raises(ImportError): - pytestpm.consider_env() - - def test_plugin_skip(self, testdir, monkeypatch): - p = testdir.makepyfile(skipping1=""" - import pytest - pytest.skip("hello") - """) - p.copy(p.dirpath("skipping2.py")) - monkeypatch.setenv("PYTEST_PLUGINS", "skipping2") - result = testdir.runpytest("-rw", "-p", "skipping1", syspathinsert=True) - assert result.ret == 0 - result.stdout.fnmatch_lines([ - "WI1*skipped plugin*skipping1*hello*", - "WI1*skipped plugin*skipping2*hello*", - ]) - - def test_consider_env_plugin_instantiation(self, testdir, monkeypatch, pytestpm): - testdir.syspathinsert() - testdir.makepyfile(xy123="#") - monkeypatch.setitem(os.environ, 'PYTEST_PLUGINS', 'xy123') - l1 = len(pytestpm.get_plugins()) - pytestpm.consider_env() - l2 = len(pytestpm.get_plugins()) - assert l2 == l1 + 1 - assert pytestpm.get_plugin('xy123') - pytestpm.consider_env() - l3 = len(pytestpm.get_plugins()) - assert l2 == l3 - - def test_pluginmanager_ENV_startup(self, testdir, monkeypatch): - testdir.makepyfile(pytest_x500="#") - p = testdir.makepyfile(""" - import pytest - def test_hello(pytestconfig): - plugin = pytestconfig.pluginmanager.get_plugin('pytest_x500') - assert plugin is not None - """) - monkeypatch.setenv('PYTEST_PLUGINS', 'pytest_x500', prepend=",") - result = testdir.runpytest(p, syspathinsert=True) - assert result.ret == 0 - result.stdout.fnmatch_lines(["*1 passed*"]) - - def test_import_plugin_importname(self, testdir, pytestpm): - pytest.raises(ImportError, 'pytestpm.import_plugin("qweqwex.y")') - pytest.raises(ImportError, 'pytestpm.import_plugin("pytest_qweqwx.y")') - - testdir.syspathinsert() - pluginname = "pytest_hello" - testdir.makepyfile(**{pluginname: ""}) - pytestpm.import_plugin("pytest_hello") - len1 = len(pytestpm.get_plugins()) - pytestpm.import_plugin("pytest_hello") - len2 = len(pytestpm.get_plugins()) - assert len1 == len2 - plugin1 = pytestpm.get_plugin("pytest_hello") - assert plugin1.__name__.endswith('pytest_hello') - plugin2 = pytestpm.get_plugin("pytest_hello") - assert plugin2 is plugin1 - - def test_import_plugin_dotted_name(self, testdir, pytestpm): - pytest.raises(ImportError, 'pytestpm.import_plugin("qweqwex.y")') - pytest.raises(ImportError, 'pytestpm.import_plugin("pytest_qweqwex.y")') - - testdir.syspathinsert() - testdir.mkpydir("pkg").join("plug.py").write("x=3") - pluginname = "pkg.plug" - pytestpm.import_plugin(pluginname) - mod = pytestpm.get_plugin("pkg.plug") - assert mod.x == 3 - - def test_consider_conftest_deps(self, testdir, pytestpm): - mod = testdir.makepyfile("pytest_plugins='xyz'").pyimport() - with pytest.raises(ImportError): - pytestpm.consider_conftest(mod) - - -class TestPytestPluginManagerBootstrapming: - def test_preparse_args(self, pytestpm): - pytest.raises(ImportError, lambda: - pytestpm.consider_preparse(["xyz", "-p", "hello123"])) - - def test_plugin_prevent_register(self, pytestpm): - pytestpm.consider_preparse(["xyz", "-p", "no:abc"]) - l1 = pytestpm.get_plugins() - pytestpm.register(42, name="abc") - l2 = pytestpm.get_plugins() - assert len(l2) == len(l1) - assert 42 not in l2 - - def test_plugin_prevent_register_unregistered_alredy_registered(self, pytestpm): - pytestpm.register(42, name="abc") - l1 = pytestpm.get_plugins() - assert 42 in l1 - pytestpm.consider_preparse(["xyz", "-p", "no:abc"]) - l2 = pytestpm.get_plugins() - assert 42 not in l2 This diff is so big that we needed to truncate the remainder. https://bitbucket.org/pytest-dev/pytest/commits/dd157ad4f3fa/ Changeset: dd157ad4f3fa Branch: pluggy1 User: hpk42 Date: 2015-04-29 14:40:52+00:00 Summary: adapt pytest to pluggy's decoratorclass branch Affected #: 6 files diff -r 8d6251cf5ac6dbbf41a3ce8dcf0dba400c769edf -r dd157ad4f3facb7ef9d4667b83d76f8c1b5d9562 _pytest/config.py --- a/_pytest/config.py +++ b/_pytest/config.py @@ -9,7 +9,10 @@ # DON't import pytest here because it causes import cycle troubles import sys, os from _pytest import hookspec # the extension point definitions -from pluggy import PluginManager, hookimpl_opts, varnames +from pluggy import PluginManager, Hookimpl, Hookspec + +hookimpl_opts = Hookimpl("pytest") +hookspec_opts = Hookspec("pytest") # pytest startup # @@ -106,10 +109,10 @@ name.startswith("pytest_funcarg__") + class PytestPluginManager(PluginManager): def __init__(self): - super(PytestPluginManager, self).__init__(prefix="pytest_", - excludefunc=exclude_pytest_names) + super(PytestPluginManager, self).__init__("pytest") self._warnings = [] self._conftest_plugins = set() @@ -130,12 +133,31 @@ self.trace.root.setwriter(err.write) self.enable_tracing() + def get_hookimpl_opts(self, plugin, name): + method = getattr(plugin, name) + opts = super(PytestPluginManager, self).get_hookimpl_opts(plugin, name) + if opts is None: + if name.startswith("pytest_") and not exclude_pytest_names(name): + opts = {} + opts["tryfirst"] = hasattr(method, "tryfirst") + opts["trylast"] = hasattr(method, "trylast") + opts["optionalhook"] = hasattr(method, "optionalhook") + opts["hookwrapper"] = hasattr(method, "hookwrapper") + return opts - def _verify_hook(self, hook, plugin): - super(PytestPluginManager, self)._verify_hook(hook, plugin) - method = getattr(plugin, hook.name) - if "__multicall__" in varnames(method): - fslineno = py.code.getfslineno(method) + def get_hookspec_opts(self, module_or_class, name): + opts = super(PytestPluginManager, self).get_hookspec_opts(module_or_class, name) + if opts is None: + if name.startswith("pytest_"): + meth = getattr(module_or_class, name) + opts = {"firstresult": hasattr(meth, "firstresult"), + "historic": hasattr(meth, "historic")} + return opts + + def _verify_hook(self, hook, hookmethod): + super(PytestPluginManager, self)._verify_hook(hook, hookmethod) + if "__multicall__" in hookmethod.argnames: + fslineno = py.code.getfslineno(hookmethod.function) warning = dict(code="I1", fslocation=fslineno, message="%r hook uses deprecated __multicall__ " diff -r 8d6251cf5ac6dbbf41a3ce8dcf0dba400c769edf -r dd157ad4f3facb7ef9d4667b83d76f8c1b5d9562 _pytest/genscript.py --- a/_pytest/genscript.py +++ b/_pytest/genscript.py @@ -70,7 +70,7 @@ genscript = config.getvalue("genscript") if genscript: tw = py.io.TerminalWriter() - deps = ['py', '_pytest', 'pytest', 'pluggy'] + deps = ['py', 'pluggy', '_pytest', 'pytest'] if sys.version_info < (2,7): deps.append("argparse") tw.line("generated script will run on python2.6-python3.3++") diff -r 8d6251cf5ac6dbbf41a3ce8dcf0dba400c769edf -r dd157ad4f3facb7ef9d4667b83d76f8c1b5d9562 _pytest/hookspec.py --- a/_pytest/hookspec.py +++ b/_pytest/hookspec.py @@ -1,6 +1,8 @@ """ hook specifications for pytest plugins, invoked from main.py and builtin plugins. """ -from pluggy import hookspec_opts +from pluggy import Hookspec + +hookspec_opts = Hookspec("pytest") # ------------------------------------------------------------------------- # Initialization hooks called for every plugin diff -r 8d6251cf5ac6dbbf41a3ce8dcf0dba400c769edf -r dd157ad4f3facb7ef9d4667b83d76f8c1b5d9562 _pytest/main.py --- a/_pytest/main.py +++ b/_pytest/main.py @@ -501,13 +501,13 @@ def __init__(self, config): FSCollector.__init__(self, config.rootdir, parent=None, config=config, session=self) - self.config.pluginmanager.register(self, name="session") + self._fs2hookproxy = {} self._testsfailed = 0 self.shouldstop = False self.trace = config.trace.root.get("collection") self._norecursepatterns = config.getini("norecursedirs") self.startdir = py.path.local() - self._fs2hookproxy = {} + self.config.pluginmanager.register(self, name="session") def _makeid(self): return "" diff -r 8d6251cf5ac6dbbf41a3ce8dcf0dba400c769edf -r dd157ad4f3facb7ef9d4667b83d76f8c1b5d9562 pytest.py --- a/pytest.py +++ b/pytest.py @@ -11,8 +11,10 @@ # else we are imported -from _pytest.config import main, UsageError, _preloadplugins, cmdline -from pluggy import hookspec_opts, hookimpl_opts +from _pytest.config import ( + main, UsageError, _preloadplugins, cmdline, + hookspec_opts, hookimpl_opts +) from _pytest import __version__ _preloadplugins() # to populate pytest.* namespace so help(pytest) works diff -r 8d6251cf5ac6dbbf41a3ce8dcf0dba400c769edf -r dd157ad4f3facb7ef9d4667b83d76f8c1b5d9562 testing/test_config.py --- a/testing/test_config.py +++ b/testing/test_config.py @@ -357,9 +357,9 @@ pm.register(m) hc = pm.hook.pytest_load_initial_conftests l = hc._nonwrappers + hc._wrappers - assert l[-1].__module__ == "_pytest.capture" - assert l[-2] == m.pytest_load_initial_conftests - assert l[-3].__module__ == "_pytest.config" + assert l[-1].function.__module__ == "_pytest.capture" + assert l[-2].function == m.pytest_load_initial_conftests + assert l[-3].function.__module__ == "_pytest.config" class TestWarning: def test_warn_config(self, testdir): https://bitbucket.org/pytest-dev/pytest/commits/339611d37480/ Changeset: 339611d37480 Branch: pluggy1 User: hpk42 Date: 2015-05-04 12:42:01+00:00 Summary: now that we are going to have wheels, py source code might be not be installed and the resulting genscript is useless Affected #: 2 files diff -r dd157ad4f3facb7ef9d4667b83d76f8c1b5d9562 -r 339611d3748054b5a524993370cd78e112f46a58 _pytest/genscript.py --- a/_pytest/genscript.py +++ b/_pytest/genscript.py @@ -34,6 +34,9 @@ for pyfile in toplevel.visit('*.py'): pkg = pkgname(name, toplevel, pyfile) name2src[pkg] = pyfile.read() + # with wheels py source code might be not be installed + # and the resulting genscript is useless, just bail out. + assert name2src, "no source code found for %r at %r" %(name, toplevel) return name2src def compress_mapping(mapping): diff -r dd157ad4f3facb7ef9d4667b83d76f8c1b5d9562 -r 339611d3748054b5a524993370cd78e112f46a58 doc/en/goodpractises.txt --- a/doc/en/goodpractises.txt +++ b/doc/en/goodpractises.txt @@ -169,6 +169,14 @@ python runtests.py +.. note:: + + You must have pytest and its dependencies installed as an sdist, not + as wheels because genscript need the source code for generating a + standalone script. + + + Integrating with distutils / ``python setup.py test`` -------------------------------------------------------- https://bitbucket.org/pytest-dev/pytest/commits/8cc1f62e3798/ Changeset: 8cc1f62e3798 Branch: pluggy1 User: hpk42 Date: 2015-05-04 13:08:41+00:00 Summary: adapt for current API changes of pluggy Affected #: 1 file diff -r 339611d3748054b5a524993370cd78e112f46a58 -r 8cc1f62e3798bc0ee9316c98bda87055d834c81a _pytest/config.py --- a/_pytest/config.py +++ b/_pytest/config.py @@ -133,20 +133,21 @@ self.trace.root.setwriter(err.write) self.enable_tracing() - def get_hookimpl_opts(self, plugin, name): - method = getattr(plugin, name) - opts = super(PytestPluginManager, self).get_hookimpl_opts(plugin, name) + def parse_hookimpl_opts(self, method): + opts = super(PytestPluginManager, self).parse_hookimpl_opts(method) if opts is None: - if name.startswith("pytest_") and not exclude_pytest_names(name): - opts = {} - opts["tryfirst"] = hasattr(method, "tryfirst") - opts["trylast"] = hasattr(method, "trylast") - opts["optionalhook"] = hasattr(method, "optionalhook") - opts["hookwrapper"] = hasattr(method, "hookwrapper") + name = getattr(method, "__name__", None) + if name is not None: + if name.startswith("pytest_") and not exclude_pytest_names(name): + opts = {} + opts["tryfirst"] = hasattr(method, "tryfirst") + opts["trylast"] = hasattr(method, "trylast") + opts["optionalhook"] = hasattr(method, "optionalhook") + opts["hookwrapper"] = hasattr(method, "hookwrapper") return opts - def get_hookspec_opts(self, module_or_class, name): - opts = super(PytestPluginManager, self).get_hookspec_opts(module_or_class, name) + def parse_hookspec_opts(self, module_or_class, name): + opts = super(PytestPluginManager, self).parse_hookspec_opts(module_or_class, name) if opts is None: if name.startswith("pytest_"): meth = getattr(module_or_class, name) https://bitbucket.org/pytest-dev/pytest/commits/b4a5e00bba50/ Changeset: b4a5e00bba50 Branch: pluggy1 User: hpk42 Date: 2015-05-05 12:52:16+00:00 Summary: merge default Affected #: 4 files diff -r 8cc1f62e3798bc0ee9316c98bda87055d834c81a -r b4a5e00bba500409128c658b6440f68e2fa9eb28 CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -55,6 +55,10 @@ 2.7.1.dev (compared to 2.7.0) ----------------------------- +- fix issue731: do not get confused by the braces which may be present + and unbalanced in an object's repr while collapsing False + explanations. Thanks Carl Meyer for the report and test case. + - fix issue553: properly handling inspect.getsourcelines failures in FixtureLookupError which would lead to to an internal error, obfuscating the original problem. Thanks talljosh for initial diff -r 8cc1f62e3798bc0ee9316c98bda87055d834c81a -r b4a5e00bba500409128c658b6440f68e2fa9eb28 _pytest/assertion/util.py --- a/_pytest/assertion/util.py +++ b/_pytest/assertion/util.py @@ -45,13 +45,15 @@ if where == -1: break level = 0 + prev_c = explanation[start] for i, c in enumerate(explanation[start:]): - if c == "{": + if prev_c + c == "\n{": level += 1 - elif c == "}": + elif prev_c + c == "\n}": level -= 1 if not level: break + prev_c = c else: raise AssertionError("unbalanced braces: %r" % (explanation,)) end = start + i diff -r 8cc1f62e3798bc0ee9316c98bda87055d834c81a -r b4a5e00bba500409128c658b6440f68e2fa9eb28 _pytest/monkeypatch.py --- a/_pytest/monkeypatch.py +++ b/_pytest/monkeypatch.py @@ -177,8 +177,8 @@ sys.path.insert(0, str(path)) def chdir(self, path): - """ Change the current working directory to the specified path - path can be a string or a py.path.local object + """ Change the current working directory to the specified path. + Path can be a string or a py.path.local object. """ if self._cwd is None: self._cwd = os.getcwd() diff -r 8cc1f62e3798bc0ee9316c98bda87055d834c81a -r b4a5e00bba500409128c658b6440f68e2fa9eb28 testing/test_assertrewrite.py --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -663,3 +663,24 @@ result.stdout.fnmatch_lines([ "* 1 passed*", ]) + + +def test_issue731(testdir): + testdir.makepyfile(""" + class LongReprWithBraces(object): + def __repr__(self): + return 'LongReprWithBraces({' + ('a' * 80) + '}' + ('a' * 120) + ')' + + def some_method(self): + return False + + def test_long_repr(): + obj = LongReprWithBraces() + assert obj.some_method() + """) + result = testdir.runpytest() + assert 'unbalanced braces' not in result.stdout.str() + + +def test_collapse_false_unbalanced_braces(): + util._collapse_false('some text{ False\n{False = some more text\n}') https://bitbucket.org/pytest-dev/pytest/commits/ef30f0eeae3b/ Changeset: ef30f0eeae3b Branch: pluggy1 User: hpk42 Date: 2015-05-05 19:53:04+00:00 Summary: - some more adaptation to most recent pluggy API - avoid using pluggin underscore api - show pluggy version in header Affected #: 8 files diff -r b4a5e00bba500409128c658b6440f68e2fa9eb28 -r ef30f0eeae3bafa76c8147382ef077099c001864 _pytest/config.py --- a/_pytest/config.py +++ b/_pytest/config.py @@ -9,10 +9,10 @@ # DON't import pytest here because it causes import cycle troubles import sys, os from _pytest import hookspec # the extension point definitions -from pluggy import PluginManager, Hookimpl, Hookspec +from pluggy import PluginManager, HookimplDecorator, HookspecDecorator -hookimpl_opts = Hookimpl("pytest") -hookspec_opts = Hookspec("pytest") +hookimpl_opts = HookimplDecorator("pytest") +hookspec_opts = HookspecDecorator("pytest") # pytest startup # @@ -112,7 +112,7 @@ class PytestPluginManager(PluginManager): def __init__(self): - super(PytestPluginManager, self).__init__("pytest") + super(PytestPluginManager, self).__init__("pytest", implprefix="pytest_") self._warnings = [] self._conftest_plugins = set() @@ -121,7 +121,7 @@ self._conftestpath2mod = {} self._confcutdir = None - self.addhooks(hookspec) + self.add_hookspecs(hookspec) self.register(self) if os.environ.get('PYTEST_DEBUG'): err = sys.stderr @@ -133,26 +133,33 @@ self.trace.root.setwriter(err.write) self.enable_tracing() - def parse_hookimpl_opts(self, method): - opts = super(PytestPluginManager, self).parse_hookimpl_opts(method) - if opts is None: - name = getattr(method, "__name__", None) - if name is not None: - if name.startswith("pytest_") and not exclude_pytest_names(name): - opts = {} - opts["tryfirst"] = hasattr(method, "tryfirst") - opts["trylast"] = hasattr(method, "trylast") - opts["optionalhook"] = hasattr(method, "optionalhook") - opts["hookwrapper"] = hasattr(method, "hookwrapper") + def addhooks(self, module_or_class): + warning = dict(code="I2", + fslocation=py.code.getfslineno(sys._getframe(1)), + message="use pluginmanager.add_hookspecs instead of " + "deprecated addhooks() method.") + self._warnings.append(warning) + return self.add_hookspecs(module_or_class) + + def parse_hookimpl_opts(self, plugin, name): + if exclude_pytest_names(name): + return None + + method = getattr(plugin, name) + opts = super(PytestPluginManager, self).parse_hookimpl_opts(plugin, name) + if opts is not None: + for name in ("tryfirst", "trylast", "optionalhook", "hookwrapper"): + opts.setdefault(name, hasattr(method, name)) return opts def parse_hookspec_opts(self, module_or_class, name): - opts = super(PytestPluginManager, self).parse_hookspec_opts(module_or_class, name) + opts = super(PytestPluginManager, self).parse_hookspec_opts( + module_or_class, name) if opts is None: + method = getattr(module_or_class, name) if name.startswith("pytest_"): - meth = getattr(module_or_class, name) - opts = {"firstresult": hasattr(meth, "firstresult"), - "historic": hasattr(meth, "historic")} + opts = {"firstresult": hasattr(method, "firstresult"), + "historic": hasattr(method, "historic")} return opts def _verify_hook(self, hook, hookmethod): diff -r b4a5e00bba500409128c658b6440f68e2fa9eb28 -r ef30f0eeae3bafa76c8147382ef077099c001864 _pytest/genscript.py --- a/_pytest/genscript.py +++ b/_pytest/genscript.py @@ -4,8 +4,6 @@ import pkgutil import py -import pluggy - import _pytest diff -r b4a5e00bba500409128c658b6440f68e2fa9eb28 -r ef30f0eeae3bafa76c8147382ef077099c001864 _pytest/helpconfig.py --- a/_pytest/helpconfig.py +++ b/_pytest/helpconfig.py @@ -96,10 +96,10 @@ def getpluginversioninfo(config): lines = [] - plugininfo = config.pluginmanager._plugin_distinfo + plugininfo = config.pluginmanager.list_plugin_distinfo() if plugininfo: lines.append("setuptools registered plugins:") - for dist, plugin in plugininfo: + for plugin, dist in plugininfo: loc = getattr(plugin, '__file__', repr(plugin)) content = "%s-%s at %s" % (dist.project_name, dist.version, loc) lines.append(" " + content) @@ -117,7 +117,7 @@ if config.option.traceconfig: lines.append("active plugins:") - items = config.pluginmanager._name2plugin.items() + items = config.pluginmanager.list_name_plugin() for name, plugin in items: if hasattr(plugin, '__file__'): r = plugin.__file__ diff -r b4a5e00bba500409128c658b6440f68e2fa9eb28 -r ef30f0eeae3bafa76c8147382ef077099c001864 _pytest/hookspec.py --- a/_pytest/hookspec.py +++ b/_pytest/hookspec.py @@ -1,8 +1,8 @@ """ hook specifications for pytest plugins, invoked from main.py and builtin plugins. """ -from pluggy import Hookspec +from pluggy import HookspecDecorator -hookspec_opts = Hookspec("pytest") +hookspec_opts = HookspecDecorator("pytest") # ------------------------------------------------------------------------- # Initialization hooks called for every plugin diff -r b4a5e00bba500409128c658b6440f68e2fa9eb28 -r ef30f0eeae3bafa76c8147382ef077099c001864 _pytest/terminal.py --- a/_pytest/terminal.py +++ b/_pytest/terminal.py @@ -3,6 +3,7 @@ This is a good source for looking at the various reporting hooks. """ import pytest +import pluggy import py import sys import time @@ -278,7 +279,8 @@ if hasattr(sys, 'pypy_version_info'): verinfo = ".".join(map(str, sys.pypy_version_info[:3])) msg += "[pypy-%s-%s]" % (verinfo, sys.pypy_version_info[3]) - msg += " -- py-%s -- pytest-%s" % (py.__version__, pytest.__version__) + msg += ", pytest-%s, py-%s, pluggy-%s" % ( + pytest.__version__, py.__version__, pluggy.__version__) if self.verbosity > 0 or self.config.option.debug or \ getattr(self.config.option, 'pastebin', None): msg += " -- " + str(sys.executable) @@ -294,10 +296,11 @@ if config.inifile: inifile = config.rootdir.bestrelpath(config.inifile) lines = ["rootdir: %s, inifile: %s" %(config.rootdir, inifile)] - plugininfo = config.pluginmanager._plugin_distinfo + + plugininfo = config.pluginmanager.list_plugin_distinfo() if plugininfo: l = [] - for dist, plugin in plugininfo: + for plugin, dist in plugininfo: name = dist.project_name if name.startswith("pytest-"): name = name[7:] diff -r b4a5e00bba500409128c658b6440f68e2fa9eb28 -r ef30f0eeae3bafa76c8147382ef077099c001864 setup.py --- a/setup.py +++ b/setup.py @@ -48,7 +48,7 @@ def main(): - install_requires = ['py>=1.4.27.dev2', 'pluggy>=0.1.0,<0.2.0'] + install_requires = ['py>=1.4.27.dev2', 'pluggy>=0.1.0,<1.0.0'] extras_require = {} if has_environment_marker_support(): extras_require[':python_version=="2.6" or python_version=="3.0" or python_version=="3.1"'] = ['argparse'] diff -r b4a5e00bba500409128c658b6440f68e2fa9eb28 -r ef30f0eeae3bafa76c8147382ef077099c001864 testing/test_helpconfig.py --- a/testing/test_helpconfig.py +++ b/testing/test_helpconfig.py @@ -7,7 +7,7 @@ result.stderr.fnmatch_lines([ '*pytest*%s*imported from*' % (pytest.__version__, ) ]) - if pytestconfig.pluginmanager._plugin_distinfo: + if pytestconfig.pluginmanager.list_plugin_distinfo(): result.stderr.fnmatch_lines([ "*setuptools registered plugins:", "*at*", diff -r b4a5e00bba500409128c658b6440f68e2fa9eb28 -r ef30f0eeae3bafa76c8147382ef077099c001864 testing/test_terminal.py --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -1,7 +1,9 @@ """ terminal reporting of the full testing process. """ -import pytest, py +import pytest +import py +import pluggy import sys from _pytest.terminal import TerminalReporter, repr_pythonversion, getreportopt @@ -408,13 +410,13 @@ verinfo = ".".join(map(str, py.std.sys.version_info[:3])) result.stdout.fnmatch_lines([ "*===== test session starts ====*", - "platform %s -- Python %s* -- py-%s -- pytest-%s" % ( + "platform %s -- Python %s*pytest-%s*py-%s*pluggy-%s" % ( py.std.sys.platform, verinfo, - py.__version__, pytest.__version__), + pytest.__version__, py.__version__, pluggy.__version__), "*test_header_trailer_info.py .", "=* 1 passed*in *.[0-9][0-9] seconds *=", ]) - if pytest.config.pluginmanager._plugin_distinfo: + if pytest.config.pluginmanager.list_plugin_distinfo(): result.stdout.fnmatch_lines([ "plugins: *", ]) https://bitbucket.org/pytest-dev/pytest/commits/e3caee551cf2/ Changeset: e3caee551cf2 Branch: pluggy1 User: hpk42 Date: 2015-05-05 20:29:53+00:00 Summary: Merged default into pluggy1 Affected #: 4 files diff -r ef30f0eeae3bafa76c8147382ef077099c001864 -r e3caee551cf2f9527046fc040a5955f134420cb2 HOWTORELEASE.rst --- a/HOWTORELEASE.rst +++ b/HOWTORELEASE.rst @@ -2,11 +2,11 @@ How to release pytest (draft) -------------------------------------------- -1. bump version numbers in setup.py and pytest/__init__.py +1. bump version numbers in pytest/__init__.py (setup.py reads it) 2. check and finalize CHANGELOG -3. write doc/en/announce/pytest-VERSION.txt and include +3. write doc/en/announce/release-VERSION.txt and include it in doc/en/announce/index.txt 4. use devpi for uploading a release tarball to a staging area: @@ -15,7 +15,7 @@ 5. run from multiple machines: - ``devpi use https://devpi.net/USER/dev`` - - ``devpi test pytest-VERSION`` + - ``devpi test pytest==VERSION`` 6. check that tests pass for relevant combinations with ``devpi list pytest`` @@ -25,14 +25,15 @@ which is ok (tox does not support skipping on per-platform basis yet). -7. XXX "regen docs" (not easy to do currently as it requires - a development version of the regendoc tool from ronny) +7. Regenerate the docs using the toplevel makefile:: + make docs -8. go to "doc/en" and upload docs with "make install" - (the latter requires ssh-login permissions on pytest.org - because it uses rsync). Note that the "install" target of - doc/en/Makefile defines where the rsync goes to, typically - to the "latest" section of pytest.org. +8. Upload the docs using the toplevel makefile:: + make upload-docs + This requires ssh-login permission on pytest.org because it uses + rsync. + Note that the "install" target of doc/en/Makefile defines where the + rsync goes to, typically to the "latest" section of pytest.org. 9. publish to pypi "devpi push pytest-2.6.2 pypi:NAME" where NAME is the name of pypi.python.org as configured in your diff -r ef30f0eeae3bafa76c8147382ef077099c001864 -r e3caee551cf2f9527046fc040a5955f134420cb2 doc/en/announce/index.txt --- a/doc/en/announce/index.txt +++ b/doc/en/announce/index.txt @@ -5,6 +5,8 @@ .. toctree:: :maxdepth: 2 + release-2.7.1 + release-2.7.0 release-2.6.3 release-2.6.2 release-2.6.1 diff -r ef30f0eeae3bafa76c8147382ef077099c001864 -r e3caee551cf2f9527046fc040a5955f134420cb2 doc/en/announce/release-2.7.1.txt --- /dev/null +++ b/doc/en/announce/release-2.7.1.txt @@ -0,0 +1,58 @@ +pytest-2.7.1: bug fixes +======================= + +pytest is a mature Python testing tool with more than a 1100 tests +against itself, passing on many different interpreters and platforms. +This release is supposed to be drop-in compatible to 2.7.0. + +See below for the changes and see docs at: + + http://pytest.org + +As usual, you can upgrade from pypi via:: + + pip install -U pytest + +Thanks to all who contributed to this release, among them: + + Bruno Oliveira + Holger Krekel + Ionel Maries Cristian + Floris Bruynooghe + +Happy testing, +The py.test Development Team + + +2.7.1 (compared to 2.7.0) +------------------------- + +- fix issue731: do not get confused by the braces which may be present + and unbalanced in an object's repr while collapsing False + explanations. Thanks Carl Meyer for the report and test case. + +- fix issue553: properly handling inspect.getsourcelines failures in + FixtureLookupError which would lead to to an internal error, + obfuscating the original problem. Thanks talljosh for initial + diagnose/patch and Bruno Oliveira for final patch. + +- fix issue660: properly report scope-mismatch-access errors + independently from ordering of fixture arguments. Also + avoid the pytest internal traceback which does not provide + information to the user. Thanks Holger Krekel. + +- streamlined and documented release process. Also all versions + (in setup.py and documentation generation) are now read + from _pytest/__init__.py. Thanks Holger Krekel. + +- fixed docs to remove the notion that yield-fixtures are experimental. + They are here to stay :) Thanks Bruno Oliveira. + +- Support building wheels by using environment markers for the + requirements. Thanks Ionel Maries Cristian. + +- fixed regression to 2.6.4 which surfaced e.g. in lost stdout capture printing + when tests raised SystemExit. Thanks Holger Krekel. + +- reintroduced _pytest fixture of the pytester plugin which is used + at least by pytest-xdist. diff -r ef30f0eeae3bafa76c8147382ef077099c001864 -r e3caee551cf2f9527046fc040a5955f134420cb2 doc/en/example/markers.txt --- a/doc/en/example/markers.txt +++ b/doc/en/example/markers.txt @@ -55,8 +55,8 @@ ================= 1 tests deselected by "-m 'not webtest'" ================= ================== 3 passed, 1 deselected in 0.01 seconds ================== -Selecing tests based on their node ID -------------------------------------- +Selecting tests based on their node ID +-------------------------------------- You can provide one or more :ref:`node IDs ` as positional arguments to select only specified tests. This makes it easy to select https://bitbucket.org/pytest-dev/pytest/commits/860871699d03/ Changeset: 860871699d03 Branch: pluggy1 User: hpk42 Date: 2015-05-06 08:08:08+00:00 Summary: adapt to pluggy naming, rename pytest.hookspec_opts to pytest.hookspec,s ame with hookimpl_opts Affected #: 20 files diff -r e3caee551cf2f9527046fc040a5955f134420cb2 -r 860871699d03a09167ae732add5324224ac2bc50 CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -27,8 +27,8 @@ details like especially the pluginmanager.add_shutdown() API. Thanks Holger Krekel. -- pluginmanagement: introduce ``pytest.hookimpl_opts`` and - ``pytest.hookspec_opts`` decorators for setting impl/spec +- pluginmanagement: introduce ``pytest.hookimpl`` and + ``pytest.hookspec`` decorators for setting impl/spec specific parameters. This substitutes the previous now deprecated use of ``pytest.mark`` which is meant to contain markers for test functions only. diff -r e3caee551cf2f9527046fc040a5955f134420cb2 -r 860871699d03a09167ae732add5324224ac2bc50 _pytest/capture.py --- a/_pytest/capture.py +++ b/_pytest/capture.py @@ -29,7 +29,7 @@ help="shortcut for --capture=no.") - at pytest.hookimpl_opts(hookwrapper=True) + at pytest.hookimpl(hookwrapper=True) def pytest_load_initial_conftests(early_config, parser, args): ns = early_config.known_args_namespace pluginmanager = early_config.pluginmanager @@ -101,7 +101,7 @@ if capfuncarg is not None: capfuncarg.close() - @pytest.hookimpl_opts(hookwrapper=True) + @pytest.hookimpl(hookwrapper=True) def pytest_make_collect_report(self, collector): if isinstance(collector, pytest.File): self.resumecapture() @@ -115,13 +115,13 @@ else: yield - @pytest.hookimpl_opts(hookwrapper=True) + @pytest.hookimpl(hookwrapper=True) def pytest_runtest_setup(self, item): self.resumecapture() yield self.suspendcapture_item(item, "setup") - @pytest.hookimpl_opts(hookwrapper=True) + @pytest.hookimpl(hookwrapper=True) def pytest_runtest_call(self, item): self.resumecapture() self.activate_funcargs(item) @@ -129,17 +129,17 @@ #self.deactivate_funcargs() called from suspendcapture() self.suspendcapture_item(item, "call") - @pytest.hookimpl_opts(hookwrapper=True) + @pytest.hookimpl(hookwrapper=True) def pytest_runtest_teardown(self, item): self.resumecapture() yield self.suspendcapture_item(item, "teardown") - @pytest.hookimpl_opts(tryfirst=True) + @pytest.hookimpl(tryfirst=True) def pytest_keyboard_interrupt(self, excinfo): self.reset_capturings() - @pytest.hookimpl_opts(tryfirst=True) + @pytest.hookimpl(tryfirst=True) def pytest_internalerror(self, excinfo): self.reset_capturings() diff -r e3caee551cf2f9527046fc040a5955f134420cb2 -r 860871699d03a09167ae732add5324224ac2bc50 _pytest/config.py --- a/_pytest/config.py +++ b/_pytest/config.py @@ -8,11 +8,11 @@ import py # DON't import pytest here because it causes import cycle troubles import sys, os -from _pytest import hookspec # the extension point definitions -from pluggy import PluginManager, HookimplDecorator, HookspecDecorator +import _pytest.hookspec # the extension point definitions +from pluggy import PluginManager, HookimplMarker, HookspecMarker -hookimpl_opts = HookimplDecorator("pytest") -hookspec_opts = HookspecDecorator("pytest") +hookimpl = HookimplMarker("pytest") +hookspec = HookspecMarker("pytest") # pytest startup # @@ -121,7 +121,7 @@ self._conftestpath2mod = {} self._confcutdir = None - self.add_hookspecs(hookspec) + self.add_hookspecs(_pytest.hookspec) self.register(self) if os.environ.get('PYTEST_DEBUG'): err = sys.stderr @@ -184,7 +184,7 @@ return self.get_plugin(name) def pytest_configure(self, config): - # XXX now that the pluginmanager exposes hookimpl_opts(tryfirst...) + # XXX now that the pluginmanager exposes hookimpl(tryfirst...) # we should remove tryfirst/trylast as markers config.addinivalue_line("markers", "tryfirst: mark a hook implementation function such that the " @@ -827,7 +827,7 @@ if not hasattr(self.option, opt.dest): setattr(self.option, opt.dest, opt.default) - @hookimpl_opts(trylast=True) + @hookimpl(trylast=True) def pytest_load_initial_conftests(self, early_config): self.pluginmanager._set_initial_conftests(early_config.known_args_namespace) diff -r e3caee551cf2f9527046fc040a5955f134420cb2 -r 860871699d03a09167ae732add5324224ac2bc50 _pytest/helpconfig.py --- a/_pytest/helpconfig.py +++ b/_pytest/helpconfig.py @@ -22,7 +22,7 @@ help="store internal tracing debug information in 'pytestdebug.log'.") - at pytest.hookimpl_opts(hookwrapper=True) + at pytest.hookimpl(hookwrapper=True) def pytest_cmdline_parse(): outcome = yield config = outcome.get_result() diff -r e3caee551cf2f9527046fc040a5955f134420cb2 -r 860871699d03a09167ae732add5324224ac2bc50 _pytest/hookspec.py --- a/_pytest/hookspec.py +++ b/_pytest/hookspec.py @@ -1,32 +1,32 @@ """ hook specifications for pytest plugins, invoked from main.py and builtin plugins. """ -from pluggy import HookspecDecorator +from pluggy import HookspecMarker -hookspec_opts = HookspecDecorator("pytest") +hookspec = HookspecMarker("pytest") # ------------------------------------------------------------------------- # Initialization hooks called for every plugin # ------------------------------------------------------------------------- - at hookspec_opts(historic=True) + at hookspec(historic=True) def pytest_addhooks(pluginmanager): """called at plugin registration time to allow adding new hooks via a call to - pluginmanager.addhooks(module_or_class, prefix).""" + pluginmanager.add_hookspecs(module_or_class, prefix).""" - at hookspec_opts(historic=True) + at hookspec(historic=True) def pytest_namespace(): """return dict of name->object to be made globally available in the pytest namespace. This hook is called at plugin registration time. """ - at hookspec_opts(historic=True) + at hookspec(historic=True) def pytest_plugin_registered(plugin, manager): """ a new pytest plugin got registered. """ - at hookspec_opts(historic=True) + at hookspec(historic=True) def pytest_addoption(parser): """register argparse-style options and ini-style config values. @@ -52,7 +52,7 @@ via (deprecated) ``pytest.config``. """ - at hookspec_opts(historic=True) + at hookspec(historic=True) def pytest_configure(config): """ called after command line options have been parsed and all plugins and initial conftest files been loaded. @@ -65,14 +65,14 @@ # discoverable conftest.py local plugins. # ------------------------------------------------------------------------- - at hookspec_opts(firstresult=True) + at hookspec(firstresult=True) def pytest_cmdline_parse(pluginmanager, args): """return initialized config object, parsing the specified args. """ def pytest_cmdline_preparse(config, args): """(deprecated) modify command line arguments before option parsing. """ - at hookspec_opts(firstresult=True) + at hookspec(firstresult=True) def pytest_cmdline_main(config): """ called for performing the main command line action. The default implementation will invoke the configure hooks and runtest_mainloop. """ @@ -86,7 +86,7 @@ # collection hooks # ------------------------------------------------------------------------- - at hookspec_opts(firstresult=True) + at hookspec(firstresult=True) def pytest_collection(session): """ perform the collection protocol for the given session. """ @@ -97,14 +97,14 @@ def pytest_collection_finish(session): """ called after collection has been performed and modified. """ - at hookspec_opts(firstresult=True) + at hookspec(firstresult=True) def pytest_ignore_collect(path, config): """ return True to prevent considering this path for collection. This hook is consulted for all files and directories prior to calling more specific hooks. """ - at hookspec_opts(firstresult=True) + at hookspec(firstresult=True) def pytest_collect_directory(path, parent): """ called before traversing a directory for collection files. """ @@ -125,7 +125,7 @@ def pytest_deselected(items): """ called for test items deselected by keyword. """ - at hookspec_opts(firstresult=True) + at hookspec(firstresult=True) def pytest_make_collect_report(collector): """ perform ``collector.collect()`` and return a CollectReport. """ @@ -133,7 +133,7 @@ # Python test function related hooks # ------------------------------------------------------------------------- - at hookspec_opts(firstresult=True) + at hookspec(firstresult=True) def pytest_pycollect_makemodule(path, parent): """ return a Module collector or None for the given path. This hook will be called for each matching test module path. @@ -141,11 +141,11 @@ create test modules for files that do not match as a test module. """ - at hookspec_opts(firstresult=True) + at hookspec(firstresult=True) def pytest_pycollect_makeitem(collector, name, obj): """ return custom item/collector for a python object in a module, or None. """ - at hookspec_opts(firstresult=True) + at hookspec(firstresult=True) def pytest_pyfunc_call(pyfuncitem): """ call underlying test function. """ @@ -156,7 +156,7 @@ # generic runtest related hooks # ------------------------------------------------------------------------- - at hookspec_opts(firstresult=True) + at hookspec(firstresult=True) def pytest_runtestloop(session): """ called for performing the main runtest loop (after collection finished). """ @@ -164,7 +164,7 @@ def pytest_itemstart(item, node): """ (deprecated, use pytest_runtest_logstart). """ - at hookspec_opts(firstresult=True) + at hookspec(firstresult=True) def pytest_runtest_protocol(item, nextitem): """ implements the runtest_setup/call/teardown protocol for the given test item, including capturing exceptions and calling @@ -197,7 +197,7 @@ so that nextitem only needs to call setup-functions. """ - at hookspec_opts(firstresult=True) + at hookspec(firstresult=True) def pytest_runtest_makereport(item, call): """ return a :py:class:`_pytest.runner.TestReport` object for the given :py:class:`pytest.Item` and @@ -242,7 +242,7 @@ def pytest_report_header(config, startdir): """ return a string to be displayed as header info for terminal reporting.""" - at hookspec_opts(firstresult=True) + at hookspec(firstresult=True) def pytest_report_teststatus(report): """ return result-category, shortletter and verbose word for reporting.""" @@ -258,7 +258,7 @@ # doctest hooks # ------------------------------------------------------------------------- - at hookspec_opts(firstresult=True) + at hookspec(firstresult=True) def pytest_doctest_prepare_content(content): """ return processed content for a given doctest""" diff -r e3caee551cf2f9527046fc040a5955f134420cb2 -r 860871699d03a09167ae732add5324224ac2bc50 _pytest/main.py --- a/_pytest/main.py +++ b/_pytest/main.py @@ -512,12 +512,12 @@ def _makeid(self): return "" - @pytest.hookimpl_opts(tryfirst=True) + @pytest.hookimpl(tryfirst=True) def pytest_collectstart(self): if self.shouldstop: raise self.Interrupted(self.shouldstop) - @pytest.hookimpl_opts(tryfirst=True) + @pytest.hookimpl(tryfirst=True) def pytest_runtest_logreport(self, report): if report.failed and not hasattr(report, 'wasxfail'): self._testsfailed += 1 diff -r e3caee551cf2f9527046fc040a5955f134420cb2 -r 860871699d03a09167ae732add5324224ac2bc50 _pytest/nose.py --- a/_pytest/nose.py +++ b/_pytest/nose.py @@ -24,7 +24,7 @@ call.excinfo = call2.excinfo - at pytest.hookimpl_opts(trylast=True) + at pytest.hookimpl(trylast=True) def pytest_runtest_setup(item): if is_potential_nosetest(item): if isinstance(item.parent, pytest.Generator): diff -r e3caee551cf2f9527046fc040a5955f134420cb2 -r 860871699d03a09167ae732add5324224ac2bc50 _pytest/pastebin.py --- a/_pytest/pastebin.py +++ b/_pytest/pastebin.py @@ -11,7 +11,7 @@ choices=['failed', 'all'], help="send failed|all info to bpaste.net pastebin service.") - at pytest.hookimpl_opts(trylast=True) + at pytest.hookimpl(trylast=True) def pytest_configure(config): if config.option.pastebin == "all": tr = config.pluginmanager.getplugin('terminalreporter') diff -r e3caee551cf2f9527046fc040a5955f134420cb2 -r 860871699d03a09167ae732add5324224ac2bc50 _pytest/pytester.py --- a/_pytest/pytester.py +++ b/_pytest/pytester.py @@ -80,7 +80,7 @@ else: return True - @pytest.hookimpl_opts(hookwrapper=True, tryfirst=True) + @pytest.hookimpl(hookwrapper=True, tryfirst=True) def pytest_runtest_item(self, item): lines1 = self.get_open_files() yield diff -r e3caee551cf2f9527046fc040a5955f134420cb2 -r 860871699d03a09167ae732add5324224ac2bc50 _pytest/python.py --- a/_pytest/python.py +++ b/_pytest/python.py @@ -181,7 +181,7 @@ def pytest_sessionstart(session): session._fixturemanager = FixtureManager(session) - at pytest.hookimpl_opts(trylast=True) + at pytest.hookimpl(trylast=True) def pytest_namespace(): raises.Exception = pytest.fail.Exception return { @@ -200,7 +200,7 @@ return request.config - at pytest.hookimpl_opts(trylast=True) + at pytest.hookimpl(trylast=True) def pytest_pyfunc_call(pyfuncitem): testfunction = pyfuncitem.obj if pyfuncitem._isyieldedfunction(): @@ -228,7 +228,7 @@ def pytest_pycollect_makemodule(path, parent): return Module(path, parent) - at pytest.hookimpl_opts(hookwrapper=True) + at pytest.hookimpl(hookwrapper=True) def pytest_pycollect_makeitem(collector, name, obj): outcome = yield res = outcome.get_result() diff -r e3caee551cf2f9527046fc040a5955f134420cb2 -r 860871699d03a09167ae732add5324224ac2bc50 _pytest/skipping.py --- a/_pytest/skipping.py +++ b/_pytest/skipping.py @@ -133,7 +133,7 @@ return expl - at pytest.hookimpl_opts(tryfirst=True) + at pytest.hookimpl(tryfirst=True) def pytest_runtest_setup(item): evalskip = MarkEvaluator(item, 'skipif') if evalskip.istrue(): @@ -151,7 +151,7 @@ if not evalxfail.get('run', True): pytest.xfail("[NOTRUN] " + evalxfail.getexplanation()) - at pytest.hookimpl_opts(hookwrapper=True) + at pytest.hookimpl(hookwrapper=True) def pytest_runtest_makereport(item, call): outcome = yield rep = outcome.get_result() diff -r e3caee551cf2f9527046fc040a5955f134420cb2 -r 860871699d03a09167ae732add5324224ac2bc50 _pytest/terminal.py --- a/_pytest/terminal.py +++ b/_pytest/terminal.py @@ -268,7 +268,7 @@ def pytest_collection_modifyitems(self): self.report_collect(True) - @pytest.hookimpl_opts(trylast=True) + @pytest.hookimpl(trylast=True) def pytest_sessionstart(self, session): self._sessionstarttime = time.time() if not self.showheader: @@ -355,7 +355,7 @@ indent = (len(stack) - 1) * " " self._tw.line("%s%s" % (indent, col)) - @pytest.hookimpl_opts(hookwrapper=True) + @pytest.hookimpl(hookwrapper=True) def pytest_sessionfinish(self, exitstatus): outcome = yield outcome.get_result() diff -r e3caee551cf2f9527046fc040a5955f134420cb2 -r 860871699d03a09167ae732add5324224ac2bc50 _pytest/unittest.py --- a/_pytest/unittest.py +++ b/_pytest/unittest.py @@ -140,7 +140,7 @@ if traceback: excinfo.traceback = traceback - at pytest.hookimpl_opts(tryfirst=True) + at pytest.hookimpl(tryfirst=True) def pytest_runtest_makereport(item, call): if isinstance(item, TestCaseFunction): if item._excinfo: @@ -152,7 +152,7 @@ # twisted trial support - at pytest.hookimpl_opts(hookwrapper=True) + at pytest.hookimpl(hookwrapper=True) def pytest_runtest_protocol(item): if isinstance(item, TestCaseFunction) and \ 'twisted.trial.unittest' in sys.modules: diff -r e3caee551cf2f9527046fc040a5955f134420cb2 -r 860871699d03a09167ae732add5324224ac2bc50 doc/en/example/markers.txt --- a/doc/en/example/markers.txt +++ b/doc/en/example/markers.txt @@ -201,9 +201,9 @@ @pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures - @pytest.hookimpl_opts(tryfirst=True): mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible. + @pytest.hookimpl(tryfirst=True): mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible. - @pytest.hookimpl_opts(trylast=True): mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible. + @pytest.hookimpl(trylast=True): mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible. For an example on how to add and work with markers from a plugin, see @@ -375,9 +375,9 @@ @pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures - @pytest.hookimpl_opts(tryfirst=True): mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible. + @pytest.hookimpl(tryfirst=True): mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible. - @pytest.hookimpl_opts(trylast=True): mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible. + @pytest.hookimpl(trylast=True): mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible. Reading markers which were set from multiple places diff -r e3caee551cf2f9527046fc040a5955f134420cb2 -r 860871699d03a09167ae732add5324224ac2bc50 doc/en/example/simple.txt --- a/doc/en/example/simple.txt +++ b/doc/en/example/simple.txt @@ -534,7 +534,7 @@ import pytest import os.path - @pytest.hookimpl_opts(tryfirst=True) + @pytest.hookimpl(tryfirst=True) def pytest_runtest_makereport(item, call, __multicall__): # execute all other hooks to obtain the report object rep = __multicall__.execute() @@ -607,7 +607,7 @@ import pytest - @pytest.hookimpl_opts(tryfirst=True) + @pytest.hookimpl(tryfirst=True) def pytest_runtest_makereport(item, call, __multicall__): # execute all other hooks to obtain the report object rep = __multicall__.execute() diff -r e3caee551cf2f9527046fc040a5955f134420cb2 -r 860871699d03a09167ae732add5324224ac2bc50 doc/en/writing_plugins.txt --- a/doc/en/writing_plugins.txt +++ b/doc/en/writing_plugins.txt @@ -292,7 +292,7 @@ import pytest - @pytest.hookimpl_opts(hookwrapper=True) + @pytest.hookimpl(hookwrapper=True) def pytest_pyfunc_call(pyfuncitem): # do whatever you want before the next hook executes @@ -305,8 +305,7 @@ Note that hook wrappers don't return results themselves, they merely perform tracing or other side effects around the actual hook implementations. If the result of the underlying hook is a mutable object, they may modify -that result, however. - +that result but it's probably better to avoid it. Hook function ordering / call example @@ -338,16 +337,24 @@ Here is the order of execution: 1. Plugin3's pytest_collection_modifyitems called until the yield point -2. Plugin1's pytest_collection_modifyitems is called -3. Plugin2's pytest_collection_modifyitems is called -4. Plugin3's pytest_collection_modifyitems called for executing after the yield - The yield receives a :py:class:`CallOutcome` instance which encapsulates - the result from calling the non-wrappers. Wrappers cannot modify the result. + because it is a hook wrapper. + +2. Plugin1's pytest_collection_modifyitems is called because it is marked + with ``tryfirst=True``. + +3. Plugin2's pytest_collection_modifyitems is called because it is marked + with ``trylast=True`` (but even without this mark it would come after + Plugin1). + +4. Plugin3's pytest_collection_modifyitems then executing the code after the yield + point. The yield receives a :py:class:`CallOutcome` instance which encapsulates + the result from calling the non-wrappers. Wrappers shall not modify the result. It's possible to use ``tryfirst`` and ``trylast`` also in conjunction with ``hookwrapper=True`` in which case it will influence the ordering of hookwrappers among each other. + Declaring new hooks ------------------------ @@ -368,11 +375,11 @@ .. _`newhooks.py`: https://bitbucket.org/pytest-dev/pytest-xdist/src/52082f70e7dd04b00361091b8af906c60fd6700f/xdist/newhooks.py?at=default -Using hooks from 3rd party plugins -------------------------------------- +Optionally using hooks from 3rd party plugins +--------------------------------------------- Using new hooks from plugins as explained above might be a little tricky -because the standard :ref:`validation mechanism `: +because of the standard :ref:`validation mechanism `: if you depend on a plugin that is not installed, validation will fail and the error message will not make much sense to your users. @@ -395,7 +402,6 @@ This has the added benefit of allowing you to conditionally install hooks depending on which plugins are installed. - .. _`well specified hooks`: .. currentmodule:: _pytest.hookspec diff -r e3caee551cf2f9527046fc040a5955f134420cb2 -r 860871699d03a09167ae732add5324224ac2bc50 pytest.py --- a/pytest.py +++ b/pytest.py @@ -13,7 +13,7 @@ from _pytest.config import ( main, UsageError, _preloadplugins, cmdline, - hookspec_opts, hookimpl_opts + hookspec, hookimpl ) from _pytest import __version__ diff -r e3caee551cf2f9527046fc040a5955f134420cb2 -r 860871699d03a09167ae732add5324224ac2bc50 testing/python/collect.py --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -559,7 +559,7 @@ b = testdir.mkdir("a").mkdir("b") b.join("conftest.py").write(py.code.Source(""" import pytest - @pytest.hookimpl_opts(hookwrapper=True) + @pytest.hookimpl(hookwrapper=True) def pytest_pycollect_makeitem(): outcome = yield if outcome.excinfo is None: diff -r e3caee551cf2f9527046fc040a5955f134420cb2 -r 860871699d03a09167ae732add5324224ac2bc50 testing/test_helpconfig.py --- a/testing/test_helpconfig.py +++ b/testing/test_helpconfig.py @@ -38,7 +38,7 @@ def test_hookvalidation_optional(testdir): testdir.makeconftest(""" import pytest - @pytest.hookimpl_opts(optionalhook=True) + @pytest.hookimpl(optionalhook=True) def pytest_hello(xyz): pass """) diff -r e3caee551cf2f9527046fc040a5955f134420cb2 -r 860871699d03a09167ae732add5324224ac2bc50 testing/test_mark.py --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -510,7 +510,7 @@ """) testdir.makepyfile(conftest=""" import pytest - @pytest.hookimpl_opts(hookwrapper=True) + @pytest.hookimpl(hookwrapper=True) def pytest_pycollect_makeitem(name): outcome = yield if name == "TestClass": https://bitbucket.org/pytest-dev/pytest/commits/16fa917e78e4/ Changeset: 16fa917e78e4 Branch: pluggy1 User: hpk42 Date: 2015-05-06 12:58:46+00:00 Summary: - make sure sub pytest runs use the same basetemp - depend on pluggy < 0.3 Affected #: 3 files diff -r 860871699d03a09167ae732add5324224ac2bc50 -r 16fa917e78e49532647702b6350f1755371aafe9 _pytest/__init__.py --- a/_pytest/__init__.py +++ b/_pytest/__init__.py @@ -1,2 +1,2 @@ # -__version__ = '2.8.0.dev2' +__version__ = '2.8.0.dev3' diff -r 860871699d03a09167ae732add5324224ac2bc50 -r 16fa917e78e49532647702b6350f1755371aafe9 _pytest/pytester.py --- a/_pytest/pytester.py +++ b/_pytest/pytester.py @@ -712,8 +712,20 @@ option "--runpytest" and return a :py:class:`RunResult`. """ + args = self._ensure_basetemp(args) return self._runpytest_method(*args, **kwargs) + def _ensure_basetemp(self, args): + args = [str(x) for x in args] + for x in args: + if str(x).startswith('--basetemp'): + #print ("basedtemp exists: %s" %(args,)) + break + else: + args.append("--basetemp=%s" % self.tmpdir.dirpath('basetemp')) + #print ("added basetemp: %s" %(args,)) + return args + def parseconfig(self, *args): """Return a new py.test Config instance from given commandline args. @@ -726,12 +738,8 @@ modules which will be registered with the PluginManager. """ - args = [str(x) for x in args] - for x in args: - if str(x).startswith('--basetemp'): - break - else: - args.append("--basetemp=%s" % self.tmpdir.dirpath('basetemp')) + args = self._ensure_basetemp(args) + import _pytest.config config = _pytest.config._prepareconfig(args, self.plugins) # we don't know what the test will do with this half-setup config diff -r 860871699d03a09167ae732add5324224ac2bc50 -r 16fa917e78e49532647702b6350f1755371aafe9 setup.py --- a/setup.py +++ b/setup.py @@ -48,7 +48,7 @@ def main(): - install_requires = ['py>=1.4.27.dev2', 'pluggy>=0.1.0,<1.0.0'] + install_requires = ['py>=1.4.27.dev2', 'pluggy>=0.2.0,<0.3.0'] extras_require = {} if has_environment_marker_support(): extras_require[':python_version=="2.6" or python_version=="3.0" or python_version=="3.1"'] = ['argparse'] https://bitbucket.org/pytest-dev/pytest/commits/af98ffcfb0fb/ Changeset: af98ffcfb0fb User: hpk42 Date: 2015-05-06 13:02:05+00:00 Summary: Merged in hpk42/pytest-patches/pluggy1 (pull request #290) integrate pluggy as external plugin manager Affected #: 29 files diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -27,8 +27,8 @@ details like especially the pluginmanager.add_shutdown() API. Thanks Holger Krekel. -- pluginmanagement: introduce ``pytest.hookimpl_opts`` and - ``pytest.hookspec_opts`` decorators for setting impl/spec +- pluginmanagement: introduce ``pytest.hookimpl`` and + ``pytest.hookspec`` decorators for setting impl/spec specific parameters. This substitutes the previous now deprecated use of ``pytest.mark`` which is meant to contain markers for test functions only. diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/__init__.py --- a/_pytest/__init__.py +++ b/_pytest/__init__.py @@ -1,2 +1,2 @@ # -__version__ = '2.8.0.dev2' +__version__ = '2.8.0.dev3' diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/capture.py --- a/_pytest/capture.py +++ b/_pytest/capture.py @@ -29,7 +29,7 @@ help="shortcut for --capture=no.") - at pytest.hookimpl_opts(hookwrapper=True) + at pytest.hookimpl(hookwrapper=True) def pytest_load_initial_conftests(early_config, parser, args): ns = early_config.known_args_namespace pluginmanager = early_config.pluginmanager @@ -101,7 +101,7 @@ if capfuncarg is not None: capfuncarg.close() - @pytest.hookimpl_opts(hookwrapper=True) + @pytest.hookimpl(hookwrapper=True) def pytest_make_collect_report(self, collector): if isinstance(collector, pytest.File): self.resumecapture() @@ -115,13 +115,13 @@ else: yield - @pytest.hookimpl_opts(hookwrapper=True) + @pytest.hookimpl(hookwrapper=True) def pytest_runtest_setup(self, item): self.resumecapture() yield self.suspendcapture_item(item, "setup") - @pytest.hookimpl_opts(hookwrapper=True) + @pytest.hookimpl(hookwrapper=True) def pytest_runtest_call(self, item): self.resumecapture() self.activate_funcargs(item) @@ -129,17 +129,17 @@ #self.deactivate_funcargs() called from suspendcapture() self.suspendcapture_item(item, "call") - @pytest.hookimpl_opts(hookwrapper=True) + @pytest.hookimpl(hookwrapper=True) def pytest_runtest_teardown(self, item): self.resumecapture() yield self.suspendcapture_item(item, "teardown") - @pytest.hookimpl_opts(tryfirst=True) + @pytest.hookimpl(tryfirst=True) def pytest_keyboard_interrupt(self, excinfo): self.reset_capturings() - @pytest.hookimpl_opts(tryfirst=True) + @pytest.hookimpl(tryfirst=True) def pytest_internalerror(self, excinfo): self.reset_capturings() diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/config.py --- a/_pytest/config.py +++ b/_pytest/config.py @@ -8,8 +8,11 @@ import py # DON't import pytest here because it causes import cycle troubles import sys, os -from _pytest import hookspec # the extension point definitions -from _pytest.core import PluginManager, hookimpl_opts, varnames +import _pytest.hookspec # the extension point definitions +from pluggy import PluginManager, HookimplMarker, HookspecMarker + +hookimpl = HookimplMarker("pytest") +hookspec = HookspecMarker("pytest") # pytest startup # @@ -106,10 +109,10 @@ name.startswith("pytest_funcarg__") + class PytestPluginManager(PluginManager): def __init__(self): - super(PytestPluginManager, self).__init__(prefix="pytest_", - excludefunc=exclude_pytest_names) + super(PytestPluginManager, self).__init__("pytest", implprefix="pytest_") self._warnings = [] self._conftest_plugins = set() @@ -118,7 +121,7 @@ self._conftestpath2mod = {} self._confcutdir = None - self.addhooks(hookspec) + self.add_hookspecs(_pytest.hookspec) self.register(self) if os.environ.get('PYTEST_DEBUG'): err = sys.stderr @@ -130,12 +133,39 @@ self.trace.root.setwriter(err.write) self.enable_tracing() + def addhooks(self, module_or_class): + warning = dict(code="I2", + fslocation=py.code.getfslineno(sys._getframe(1)), + message="use pluginmanager.add_hookspecs instead of " + "deprecated addhooks() method.") + self._warnings.append(warning) + return self.add_hookspecs(module_or_class) - def _verify_hook(self, hook, plugin): - super(PytestPluginManager, self)._verify_hook(hook, plugin) - method = getattr(plugin, hook.name) - if "__multicall__" in varnames(method): - fslineno = py.code.getfslineno(method) + def parse_hookimpl_opts(self, plugin, name): + if exclude_pytest_names(name): + return None + + method = getattr(plugin, name) + opts = super(PytestPluginManager, self).parse_hookimpl_opts(plugin, name) + if opts is not None: + for name in ("tryfirst", "trylast", "optionalhook", "hookwrapper"): + opts.setdefault(name, hasattr(method, name)) + return opts + + def parse_hookspec_opts(self, module_or_class, name): + opts = super(PytestPluginManager, self).parse_hookspec_opts( + module_or_class, name) + if opts is None: + method = getattr(module_or_class, name) + if name.startswith("pytest_"): + opts = {"firstresult": hasattr(method, "firstresult"), + "historic": hasattr(method, "historic")} + return opts + + def _verify_hook(self, hook, hookmethod): + super(PytestPluginManager, self)._verify_hook(hook, hookmethod) + if "__multicall__" in hookmethod.argnames: + fslineno = py.code.getfslineno(hookmethod.function) warning = dict(code="I1", fslocation=fslineno, message="%r hook uses deprecated __multicall__ " @@ -154,7 +184,7 @@ return self.get_plugin(name) def pytest_configure(self, config): - # XXX now that the pluginmanager exposes hookimpl_opts(tryfirst...) + # XXX now that the pluginmanager exposes hookimpl(tryfirst...) # we should remove tryfirst/trylast as markers config.addinivalue_line("markers", "tryfirst: mark a hook implementation function such that the " @@ -797,7 +827,7 @@ if not hasattr(self.option, opt.dest): setattr(self.option, opt.dest, opt.default) - @hookimpl_opts(trylast=True) + @hookimpl(trylast=True) def pytest_load_initial_conftests(self, early_config): self.pluginmanager._set_initial_conftests(early_config.known_args_namespace) diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/core.py --- a/_pytest/core.py +++ /dev/null @@ -1,590 +0,0 @@ -""" -PluginManager, basic initialization and tracing. -""" -import sys -from inspect import isfunction, ismethod, isclass, formatargspec, getargspec -import py - -py3 = sys.version_info > (3,0) - -def hookspec_opts(firstresult=False, historic=False): - """ returns a decorator which will define a function as a hook specfication. - - If firstresult is True the 1:N hook call (N being the number of registered - hook implementation functions) will stop at I<=N when the I'th function - returns a non-None result. - - If historic is True calls to a hook will be memorized and replayed - on later registered plugins. - """ - def setattr_hookspec_opts(func): - if historic and firstresult: - raise ValueError("cannot have a historic firstresult hook") - if firstresult: - func.firstresult = firstresult - if historic: - func.historic = historic - return func - return setattr_hookspec_opts - - -def hookimpl_opts(hookwrapper=False, optionalhook=False, - tryfirst=False, trylast=False): - """ Return a decorator which marks a function as a hook implementation. - - If optionalhook is True a missing matching hook specification will not result - in an error (by default it is an error if no matching spec is found). - - If tryfirst is True this hook implementation will run as early as possible - in the chain of N hook implementations for a specfication. - - If trylast is True this hook implementation will run as late as possible - in the chain of N hook implementations. - - If hookwrapper is True the hook implementations needs to execute exactly - one "yield". The code before the yield is run early before any non-hookwrapper - function is run. The code after the yield is run after all non-hookwrapper - function have run. The yield receives an ``CallOutcome`` object representing - the exception or result outcome of the inner calls (including other hookwrapper - calls). - """ - def setattr_hookimpl_opts(func): - if hookwrapper: - func.hookwrapper = True - if optionalhook: - func.optionalhook = True - if tryfirst: - func.tryfirst = True - if trylast: - func.trylast = True - return func - return setattr_hookimpl_opts - - -class TagTracer: - def __init__(self): - self._tag2proc = {} - self.writer = None - self.indent = 0 - - def get(self, name): - return TagTracerSub(self, (name,)) - - def format_message(self, tags, args): - if isinstance(args[-1], dict): - extra = args[-1] - args = args[:-1] - else: - extra = {} - - content = " ".join(map(str, args)) - indent = " " * self.indent - - lines = [ - "%s%s [%s]\n" %(indent, content, ":".join(tags)) - ] - - for name, value in extra.items(): - lines.append("%s %s: %s\n" % (indent, name, value)) - return lines - - def processmessage(self, tags, args): - if self.writer is not None and args: - lines = self.format_message(tags, args) - self.writer(''.join(lines)) - try: - self._tag2proc[tags](tags, args) - except KeyError: - pass - - def setwriter(self, writer): - self.writer = writer - - def setprocessor(self, tags, processor): - if isinstance(tags, str): - tags = tuple(tags.split(":")) - else: - assert isinstance(tags, tuple) - self._tag2proc[tags] = processor - - -class TagTracerSub: - def __init__(self, root, tags): - self.root = root - self.tags = tags - - def __call__(self, *args): - self.root.processmessage(self.tags, args) - - def setmyprocessor(self, processor): - self.root.setprocessor(self.tags, processor) - - def get(self, name): - return self.__class__(self.root, self.tags + (name,)) - - -def raise_wrapfail(wrap_controller, msg): - co = wrap_controller.gi_code - raise RuntimeError("wrap_controller at %r %s:%d %s" % - (co.co_name, co.co_filename, co.co_firstlineno, msg)) - - -def wrapped_call(wrap_controller, func): - """ Wrap calling to a function with a generator which needs to yield - exactly once. The yield point will trigger calling the wrapped function - and return its CallOutcome to the yield point. The generator then needs - to finish (raise StopIteration) in order for the wrapped call to complete. - """ - try: - next(wrap_controller) # first yield - except StopIteration: - raise_wrapfail(wrap_controller, "did not yield") - call_outcome = CallOutcome(func) - try: - wrap_controller.send(call_outcome) - raise_wrapfail(wrap_controller, "has second yield") - except StopIteration: - pass - return call_outcome.get_result() - - -class CallOutcome: - """ Outcome of a function call, either an exception or a proper result. - Calling the ``get_result`` method will return the result or reraise - the exception raised when the function was called. """ - excinfo = None - def __init__(self, func): - try: - self.result = func() - except BaseException: - self.excinfo = sys.exc_info() - - def force_result(self, result): - self.result = result - self.excinfo = None - - def get_result(self): - if self.excinfo is None: - return self.result - else: - ex = self.excinfo - if py3: - raise ex[1].with_traceback(ex[2]) - py.builtin._reraise(*ex) - - -class TracedHookExecution: - def __init__(self, pluginmanager, before, after): - self.pluginmanager = pluginmanager - self.before = before - self.after = after - self.oldcall = pluginmanager._inner_hookexec - assert not isinstance(self.oldcall, TracedHookExecution) - self.pluginmanager._inner_hookexec = self - - def __call__(self, hook, methods, kwargs): - self.before(hook, methods, kwargs) - outcome = CallOutcome(lambda: self.oldcall(hook, methods, kwargs)) - self.after(outcome, hook, methods, kwargs) - return outcome.get_result() - - def undo(self): - self.pluginmanager._inner_hookexec = self.oldcall - - -class PluginManager(object): - """ Core Pluginmanager class which manages registration - of plugin objects and 1:N hook calling. - - You can register new hooks by calling ``addhooks(module_or_class)``. - You can register plugin objects (which contain hooks) by calling - ``register(plugin)``. The Pluginmanager is initialized with a - prefix that is searched for in the names of the dict of registered - plugin objects. An optional excludefunc allows to blacklist names which - are not considered as hooks despite a matching prefix. - - For debugging purposes you can call ``enable_tracing()`` - which will subsequently send debug information to the trace helper. - """ - - def __init__(self, prefix, excludefunc=None): - self._prefix = prefix - self._excludefunc = excludefunc - self._name2plugin = {} - self._plugin2hookcallers = {} - self._plugin_distinfo = [] - self.trace = TagTracer().get("pluginmanage") - self.hook = HookRelay(self.trace.root.get("hook")) - self._inner_hookexec = lambda hook, methods, kwargs: \ - MultiCall(methods, kwargs, hook.firstresult).execute() - - def _hookexec(self, hook, methods, kwargs): - # called from all hookcaller instances. - # enable_tracing will set its own wrapping function at self._inner_hookexec - return self._inner_hookexec(hook, methods, kwargs) - - def enable_tracing(self): - """ enable tracing of hook calls and return an undo function. """ - hooktrace = self.hook._trace - - def before(hook, methods, kwargs): - hooktrace.root.indent += 1 - hooktrace(hook.name, kwargs) - - def after(outcome, hook, methods, kwargs): - if outcome.excinfo is None: - hooktrace("finish", hook.name, "-->", outcome.result) - hooktrace.root.indent -= 1 - - return TracedHookExecution(self, before, after).undo - - def subset_hook_caller(self, name, remove_plugins): - """ Return a new HookCaller instance for the named method - which manages calls to all registered plugins except the - ones from remove_plugins. """ - orig = getattr(self.hook, name) - plugins_to_remove = [plugin for plugin in remove_plugins - if hasattr(plugin, name)] - if plugins_to_remove: - hc = HookCaller(orig.name, orig._hookexec, orig._specmodule_or_class) - for plugin in orig._plugins: - if plugin not in plugins_to_remove: - hc._add_plugin(plugin) - # we also keep track of this hook caller so it - # gets properly removed on plugin unregistration - self._plugin2hookcallers.setdefault(plugin, []).append(hc) - return hc - return orig - - def register(self, plugin, name=None): - """ Register a plugin and return its canonical name or None if the name - is blocked from registering. Raise a ValueError if the plugin is already - registered. """ - plugin_name = name or self.get_canonical_name(plugin) - - if plugin_name in self._name2plugin or plugin in self._plugin2hookcallers: - if self._name2plugin.get(plugin_name, -1) is None: - return # blocked plugin, return None to indicate no registration - raise ValueError("Plugin already registered: %s=%s\n%s" %( - plugin_name, plugin, self._name2plugin)) - - self._name2plugin[plugin_name] = plugin - - # register prefix-matching hook specs of the plugin - self._plugin2hookcallers[plugin] = hookcallers = [] - for name in dir(plugin): - if name.startswith(self._prefix): - hook = getattr(self.hook, name, None) - if hook is None: - if self._excludefunc is not None and self._excludefunc(name): - continue - hook = HookCaller(name, self._hookexec) - setattr(self.hook, name, hook) - elif hook.has_spec(): - self._verify_hook(hook, plugin) - hook._maybe_apply_history(getattr(plugin, name)) - hookcallers.append(hook) - hook._add_plugin(plugin) - return plugin_name - - def unregister(self, plugin=None, name=None): - """ unregister a plugin object and all its contained hook implementations - from internal data structures. """ - if name is None: - assert plugin is not None, "one of name or plugin needs to be specified" - name = self.get_name(plugin) - - if plugin is None: - plugin = self.get_plugin(name) - - # if self._name2plugin[name] == None registration was blocked: ignore - if self._name2plugin.get(name): - del self._name2plugin[name] - - for hookcaller in self._plugin2hookcallers.pop(plugin, []): - hookcaller._remove_plugin(plugin) - - return plugin - - def set_blocked(self, name): - """ block registrations of the given name, unregister if already registered. """ - self.unregister(name=name) - self._name2plugin[name] = None - - def addhooks(self, module_or_class): - """ add new hook definitions from the given module_or_class using - the prefix/excludefunc with which the PluginManager was initialized. """ - names = [] - for name in dir(module_or_class): - if name.startswith(self._prefix): - hc = getattr(self.hook, name, None) - if hc is None: - hc = HookCaller(name, self._hookexec, module_or_class) - setattr(self.hook, name, hc) - else: - # plugins registered this hook without knowing the spec - hc.set_specification(module_or_class) - for plugin in hc._plugins: - self._verify_hook(hc, plugin) - names.append(name) - - if not names: - raise ValueError("did not find new %r hooks in %r" - %(self._prefix, module_or_class)) - - def get_plugins(self): - """ return the set of registered plugins. """ - return set(self._plugin2hookcallers) - - def is_registered(self, plugin): - """ Return True if the plugin is already registered. """ - return plugin in self._plugin2hookcallers - - def get_canonical_name(self, plugin): - """ Return canonical name for a plugin object. Note that a plugin - may be registered under a different name which was specified - by the caller of register(plugin, name). To obtain the name - of an registered plugin use ``get_name(plugin)`` instead.""" - return getattr(plugin, "__name__", None) or str(id(plugin)) - - def get_plugin(self, name): - """ Return a plugin or None for the given name. """ - return self._name2plugin.get(name) - - def get_name(self, plugin): - """ Return name for registered plugin or None if not registered. """ - for name, val in self._name2plugin.items(): - if plugin == val: - return name - - def _verify_hook(self, hook, plugin): - method = getattr(plugin, hook.name) - pluginname = self.get_name(plugin) - - if hook.is_historic() and hasattr(method, "hookwrapper"): - raise PluginValidationError( - "Plugin %r\nhook %r\nhistoric incompatible to hookwrapper" %( - pluginname, hook.name)) - - for arg in varnames(method): - if arg not in hook.argnames: - raise PluginValidationError( - "Plugin %r\nhook %r\nargument %r not available\n" - "plugin definition: %s\n" - "available hookargs: %s" %( - pluginname, hook.name, arg, formatdef(method), - ", ".join(hook.argnames))) - - def check_pending(self): - """ Verify that all hooks which have not been verified against - a hook specification are optional, otherwise raise PluginValidationError""" - for name in self.hook.__dict__: - if name.startswith(self._prefix): - hook = getattr(self.hook, name) - if not hook.has_spec(): - for plugin in hook._plugins: - method = getattr(plugin, hook.name) - if not getattr(method, "optionalhook", False): - raise PluginValidationError( - "unknown hook %r in plugin %r" %(name, plugin)) - - def load_setuptools_entrypoints(self, entrypoint_name): - """ Load modules from querying the specified setuptools entrypoint name. - Return the number of loaded plugins. """ - from pkg_resources import iter_entry_points, DistributionNotFound - for ep in iter_entry_points(entrypoint_name): - # is the plugin registered or blocked? - if self.get_plugin(ep.name) or ep.name in self._name2plugin: - continue - try: - plugin = ep.load() - except DistributionNotFound: - continue - self.register(plugin, name=ep.name) - self._plugin_distinfo.append((ep.dist, plugin)) - return len(self._plugin_distinfo) - - -class MultiCall: - """ execute a call into multiple python functions/methods. """ - - # XXX note that the __multicall__ argument is supported only - # for pytest compatibility reasons. It was never officially - # supported there and is explicitely deprecated since 2.8 - # so we can remove it soon, allowing to avoid the below recursion - # in execute() and simplify/speed up the execute loop. - - def __init__(self, methods, kwargs, firstresult=False): - self.methods = methods - self.kwargs = kwargs - self.kwargs["__multicall__"] = self - self.firstresult = firstresult - - def execute(self): - all_kwargs = self.kwargs - self.results = results = [] - firstresult = self.firstresult - - while self.methods: - method = self.methods.pop() - args = [all_kwargs[argname] for argname in varnames(method)] - if hasattr(method, "hookwrapper"): - return wrapped_call(method(*args), self.execute) - res = method(*args) - if res is not None: - if firstresult: - return res - results.append(res) - - if not firstresult: - return results - - def __repr__(self): - status = "%d meths" % (len(self.methods),) - if hasattr(self, "results"): - status = ("%d results, " % len(self.results)) + status - return "" %(status, self.kwargs) - - - -def varnames(func, startindex=None): - """ return argument name tuple for a function, method, class or callable. - - In case of a class, its "__init__" method is considered. - For methods the "self" parameter is not included unless you are passing - an unbound method with Python3 (which has no supports for unbound methods) - """ - cache = getattr(func, "__dict__", {}) - try: - return cache["_varnames"] - except KeyError: - pass - if isclass(func): - try: - func = func.__init__ - except AttributeError: - return () - startindex = 1 - else: - if not isfunction(func) and not ismethod(func): - func = getattr(func, '__call__', func) - if startindex is None: - startindex = int(ismethod(func)) - - rawcode = py.code.getrawcode(func) - try: - x = rawcode.co_varnames[startindex:rawcode.co_argcount] - except AttributeError: - x = () - else: - defaults = func.__defaults__ - if defaults: - x = x[:-len(defaults)] - try: - cache["_varnames"] = x - except TypeError: - pass - return x - - -class HookRelay: - def __init__(self, trace): - self._trace = trace - - -class HookCaller(object): - def __init__(self, name, hook_execute, specmodule_or_class=None): - self.name = name - self._plugins = [] - self._wrappers = [] - self._nonwrappers = [] - self._hookexec = hook_execute - if specmodule_or_class is not None: - self.set_specification(specmodule_or_class) - - def has_spec(self): - return hasattr(self, "_specmodule_or_class") - - def set_specification(self, specmodule_or_class): - assert not self.has_spec() - self._specmodule_or_class = specmodule_or_class - specfunc = getattr(specmodule_or_class, self.name) - argnames = varnames(specfunc, startindex=isclass(specmodule_or_class)) - assert "self" not in argnames # sanity check - self.argnames = ["__multicall__"] + list(argnames) - self.firstresult = getattr(specfunc, 'firstresult', False) - if hasattr(specfunc, "historic"): - self._call_history = [] - - def is_historic(self): - return hasattr(self, "_call_history") - - def _remove_plugin(self, plugin): - self._plugins.remove(plugin) - meth = getattr(plugin, self.name) - try: - self._nonwrappers.remove(meth) - except ValueError: - self._wrappers.remove(meth) - - def _add_plugin(self, plugin): - self._plugins.append(plugin) - self._add_method(getattr(plugin, self.name)) - - def _add_method(self, meth): - if hasattr(meth, 'hookwrapper'): - methods = self._wrappers - else: - methods = self._nonwrappers - - if hasattr(meth, 'trylast'): - methods.insert(0, meth) - elif hasattr(meth, 'tryfirst'): - methods.append(meth) - else: - # find last non-tryfirst method - i = len(methods) - 1 - while i >= 0 and hasattr(methods[i], "tryfirst"): - i -= 1 - methods.insert(i + 1, meth) - - def __repr__(self): - return "" %(self.name,) - - def __call__(self, **kwargs): - assert not self.is_historic() - return self._hookexec(self, self._nonwrappers + self._wrappers, kwargs) - - def call_historic(self, proc=None, kwargs=None): - self._call_history.append((kwargs or {}, proc)) - # historizing hooks don't return results - self._hookexec(self, self._nonwrappers + self._wrappers, kwargs) - - def call_extra(self, methods, kwargs): - """ Call the hook with some additional temporarily participating - methods using the specified kwargs as call parameters. """ - old = list(self._nonwrappers), list(self._wrappers) - for method in methods: - self._add_method(method) - try: - return self(**kwargs) - finally: - self._nonwrappers, self._wrappers = old - - def _maybe_apply_history(self, method): - if self.is_historic(): - for kwargs, proc in self._call_history: - res = self._hookexec(self, [method], kwargs) - if res and proc is not None: - proc(res[0]) - - -class PluginValidationError(Exception): - """ plugin failed validation. """ - - -def formatdef(func): - return "%s%s" % ( - func.__name__, - formatargspec(*getargspec(func)) - ) diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/genscript.py --- a/_pytest/genscript.py +++ b/_pytest/genscript.py @@ -4,7 +4,6 @@ import pkgutil import py - import _pytest @@ -33,6 +32,9 @@ for pyfile in toplevel.visit('*.py'): pkg = pkgname(name, toplevel, pyfile) name2src[pkg] = pyfile.read() + # with wheels py source code might be not be installed + # and the resulting genscript is useless, just bail out. + assert name2src, "no source code found for %r at %r" %(name, toplevel) return name2src def compress_mapping(mapping): @@ -69,7 +71,7 @@ genscript = config.getvalue("genscript") if genscript: tw = py.io.TerminalWriter() - deps = ['py', '_pytest', 'pytest'] + deps = ['py', 'pluggy', '_pytest', 'pytest'] if sys.version_info < (2,7): deps.append("argparse") tw.line("generated script will run on python2.6-python3.3++") diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/helpconfig.py --- a/_pytest/helpconfig.py +++ b/_pytest/helpconfig.py @@ -22,7 +22,7 @@ help="store internal tracing debug information in 'pytestdebug.log'.") - at pytest.hookimpl_opts(hookwrapper=True) + at pytest.hookimpl(hookwrapper=True) def pytest_cmdline_parse(): outcome = yield config = outcome.get_result() @@ -96,10 +96,10 @@ def getpluginversioninfo(config): lines = [] - plugininfo = config.pluginmanager._plugin_distinfo + plugininfo = config.pluginmanager.list_plugin_distinfo() if plugininfo: lines.append("setuptools registered plugins:") - for dist, plugin in plugininfo: + for plugin, dist in plugininfo: loc = getattr(plugin, '__file__', repr(plugin)) content = "%s-%s at %s" % (dist.project_name, dist.version, loc) lines.append(" " + content) @@ -117,7 +117,7 @@ if config.option.traceconfig: lines.append("active plugins:") - items = config.pluginmanager._name2plugin.items() + items = config.pluginmanager.list_name_plugin() for name, plugin in items: if hasattr(plugin, '__file__'): r = plugin.__file__ diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/hookspec.py --- a/_pytest/hookspec.py +++ b/_pytest/hookspec.py @@ -1,30 +1,32 @@ """ hook specifications for pytest plugins, invoked from main.py and builtin plugins. """ -from _pytest.core import hookspec_opts +from pluggy import HookspecMarker + +hookspec = HookspecMarker("pytest") # ------------------------------------------------------------------------- # Initialization hooks called for every plugin # ------------------------------------------------------------------------- - at hookspec_opts(historic=True) + at hookspec(historic=True) def pytest_addhooks(pluginmanager): """called at plugin registration time to allow adding new hooks via a call to - pluginmanager.addhooks(module_or_class, prefix).""" + pluginmanager.add_hookspecs(module_or_class, prefix).""" - at hookspec_opts(historic=True) + at hookspec(historic=True) def pytest_namespace(): """return dict of name->object to be made globally available in the pytest namespace. This hook is called at plugin registration time. """ - at hookspec_opts(historic=True) + at hookspec(historic=True) def pytest_plugin_registered(plugin, manager): """ a new pytest plugin got registered. """ - at hookspec_opts(historic=True) + at hookspec(historic=True) def pytest_addoption(parser): """register argparse-style options and ini-style config values. @@ -50,7 +52,7 @@ via (deprecated) ``pytest.config``. """ - at hookspec_opts(historic=True) + at hookspec(historic=True) def pytest_configure(config): """ called after command line options have been parsed and all plugins and initial conftest files been loaded. @@ -63,14 +65,14 @@ # discoverable conftest.py local plugins. # ------------------------------------------------------------------------- - at hookspec_opts(firstresult=True) + at hookspec(firstresult=True) def pytest_cmdline_parse(pluginmanager, args): """return initialized config object, parsing the specified args. """ def pytest_cmdline_preparse(config, args): """(deprecated) modify command line arguments before option parsing. """ - at hookspec_opts(firstresult=True) + at hookspec(firstresult=True) def pytest_cmdline_main(config): """ called for performing the main command line action. The default implementation will invoke the configure hooks and runtest_mainloop. """ @@ -84,7 +86,7 @@ # collection hooks # ------------------------------------------------------------------------- - at hookspec_opts(firstresult=True) + at hookspec(firstresult=True) def pytest_collection(session): """ perform the collection protocol for the given session. """ @@ -95,14 +97,14 @@ def pytest_collection_finish(session): """ called after collection has been performed and modified. """ - at hookspec_opts(firstresult=True) + at hookspec(firstresult=True) def pytest_ignore_collect(path, config): """ return True to prevent considering this path for collection. This hook is consulted for all files and directories prior to calling more specific hooks. """ - at hookspec_opts(firstresult=True) + at hookspec(firstresult=True) def pytest_collect_directory(path, parent): """ called before traversing a directory for collection files. """ @@ -123,7 +125,7 @@ def pytest_deselected(items): """ called for test items deselected by keyword. """ - at hookspec_opts(firstresult=True) + at hookspec(firstresult=True) def pytest_make_collect_report(collector): """ perform ``collector.collect()`` and return a CollectReport. """ @@ -131,7 +133,7 @@ # Python test function related hooks # ------------------------------------------------------------------------- - at hookspec_opts(firstresult=True) + at hookspec(firstresult=True) def pytest_pycollect_makemodule(path, parent): """ return a Module collector or None for the given path. This hook will be called for each matching test module path. @@ -139,11 +141,11 @@ create test modules for files that do not match as a test module. """ - at hookspec_opts(firstresult=True) + at hookspec(firstresult=True) def pytest_pycollect_makeitem(collector, name, obj): """ return custom item/collector for a python object in a module, or None. """ - at hookspec_opts(firstresult=True) + at hookspec(firstresult=True) def pytest_pyfunc_call(pyfuncitem): """ call underlying test function. """ @@ -154,7 +156,7 @@ # generic runtest related hooks # ------------------------------------------------------------------------- - at hookspec_opts(firstresult=True) + at hookspec(firstresult=True) def pytest_runtestloop(session): """ called for performing the main runtest loop (after collection finished). """ @@ -162,7 +164,7 @@ def pytest_itemstart(item, node): """ (deprecated, use pytest_runtest_logstart). """ - at hookspec_opts(firstresult=True) + at hookspec(firstresult=True) def pytest_runtest_protocol(item, nextitem): """ implements the runtest_setup/call/teardown protocol for the given test item, including capturing exceptions and calling @@ -195,7 +197,7 @@ so that nextitem only needs to call setup-functions. """ - at hookspec_opts(firstresult=True) + at hookspec(firstresult=True) def pytest_runtest_makereport(item, call): """ return a :py:class:`_pytest.runner.TestReport` object for the given :py:class:`pytest.Item` and @@ -240,7 +242,7 @@ def pytest_report_header(config, startdir): """ return a string to be displayed as header info for terminal reporting.""" - at hookspec_opts(firstresult=True) + at hookspec(firstresult=True) def pytest_report_teststatus(report): """ return result-category, shortletter and verbose word for reporting.""" @@ -256,7 +258,7 @@ # doctest hooks # ------------------------------------------------------------------------- - at hookspec_opts(firstresult=True) + at hookspec(firstresult=True) def pytest_doctest_prepare_content(content): """ return processed content for a given doctest""" diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/main.py --- a/_pytest/main.py +++ b/_pytest/main.py @@ -501,23 +501,23 @@ def __init__(self, config): FSCollector.__init__(self, config.rootdir, parent=None, config=config, session=self) - self.config.pluginmanager.register(self, name="session") + self._fs2hookproxy = {} self._testsfailed = 0 self.shouldstop = False self.trace = config.trace.root.get("collection") self._norecursepatterns = config.getini("norecursedirs") self.startdir = py.path.local() - self._fs2hookproxy = {} + self.config.pluginmanager.register(self, name="session") def _makeid(self): return "" - @pytest.hookimpl_opts(tryfirst=True) + @pytest.hookimpl(tryfirst=True) def pytest_collectstart(self): if self.shouldstop: raise self.Interrupted(self.shouldstop) - @pytest.hookimpl_opts(tryfirst=True) + @pytest.hookimpl(tryfirst=True) def pytest_runtest_logreport(self, report): if report.failed and not hasattr(report, 'wasxfail'): self._testsfailed += 1 diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/nose.py --- a/_pytest/nose.py +++ b/_pytest/nose.py @@ -24,7 +24,7 @@ call.excinfo = call2.excinfo - at pytest.hookimpl_opts(trylast=True) + at pytest.hookimpl(trylast=True) def pytest_runtest_setup(item): if is_potential_nosetest(item): if isinstance(item.parent, pytest.Generator): diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/pastebin.py --- a/_pytest/pastebin.py +++ b/_pytest/pastebin.py @@ -11,7 +11,7 @@ choices=['failed', 'all'], help="send failed|all info to bpaste.net pastebin service.") - at pytest.hookimpl_opts(trylast=True) + at pytest.hookimpl(trylast=True) def pytest_configure(config): if config.option.pastebin == "all": tr = config.pluginmanager.getplugin('terminalreporter') diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/pytester.py --- a/_pytest/pytester.py +++ b/_pytest/pytester.py @@ -13,7 +13,7 @@ import py import pytest from py.builtin import print_ -from _pytest.core import TracedHookExecution +from pluggy import _TracedHookExecution from _pytest.main import Session, EXIT_OK @@ -80,7 +80,7 @@ else: return True - @pytest.hookimpl_opts(hookwrapper=True, tryfirst=True) + @pytest.hookimpl(hookwrapper=True, tryfirst=True) def pytest_runtest_item(self, item): lines1 = self.get_open_files() yield @@ -198,7 +198,7 @@ self.calls.append(ParsedCall(hook.name, kwargs)) def after(outcome, hook, method, kwargs): pass - executor = TracedHookExecution(pluginmanager, before, after) + executor = _TracedHookExecution(pluginmanager, before, after) self._undo_wrapping = executor.undo def finish_recording(self): @@ -712,8 +712,20 @@ option "--runpytest" and return a :py:class:`RunResult`. """ + args = self._ensure_basetemp(args) return self._runpytest_method(*args, **kwargs) + def _ensure_basetemp(self, args): + args = [str(x) for x in args] + for x in args: + if str(x).startswith('--basetemp'): + #print ("basedtemp exists: %s" %(args,)) + break + else: + args.append("--basetemp=%s" % self.tmpdir.dirpath('basetemp')) + #print ("added basetemp: %s" %(args,)) + return args + def parseconfig(self, *args): """Return a new py.test Config instance from given commandline args. @@ -726,12 +738,8 @@ modules which will be registered with the PluginManager. """ - args = [str(x) for x in args] - for x in args: - if str(x).startswith('--basetemp'): - break - else: - args.append("--basetemp=%s" % self.tmpdir.dirpath('basetemp')) + args = self._ensure_basetemp(args) + import _pytest.config config = _pytest.config._prepareconfig(args, self.plugins) # we don't know what the test will do with this half-setup config diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/python.py --- a/_pytest/python.py +++ b/_pytest/python.py @@ -8,7 +8,11 @@ from py._code.code import TerminalRepr import _pytest -cutdir = py.path.local(_pytest.__file__).dirpath() +import pluggy + +cutdir2 = py.path.local(_pytest.__file__).dirpath() +cutdir1 = py.path.local(pluggy.__file__.rstrip("oc")) + NoneType = type(None) NOTSET = object() @@ -18,6 +22,11 @@ # used to work around a python2 exception info leak exc_clear = getattr(sys, 'exc_clear', lambda: None) + +def filter_traceback(entry): + return entry.path != cutdir1 and not entry.path.relto(cutdir2) + + def getfslineno(obj): # xxx let decorators etc specify a sane ordering while hasattr(obj, "__wrapped__"): @@ -172,7 +181,7 @@ def pytest_sessionstart(session): session._fixturemanager = FixtureManager(session) - at pytest.hookimpl_opts(trylast=True) + at pytest.hookimpl(trylast=True) def pytest_namespace(): raises.Exception = pytest.fail.Exception return { @@ -191,7 +200,7 @@ return request.config - at pytest.hookimpl_opts(trylast=True) + at pytest.hookimpl(trylast=True) def pytest_pyfunc_call(pyfuncitem): testfunction = pyfuncitem.obj if pyfuncitem._isyieldedfunction(): @@ -219,7 +228,7 @@ def pytest_pycollect_makemodule(path, parent): return Module(path, parent) - at pytest.hookimpl_opts(hookwrapper=True) + at pytest.hookimpl(hookwrapper=True) def pytest_pycollect_makeitem(collector, name, obj): outcome = yield res = outcome.get_result() @@ -604,7 +613,11 @@ if ntraceback == traceback: ntraceback = ntraceback.cut(path=path) if ntraceback == traceback: - ntraceback = ntraceback.cut(excludepath=cutdir) + #ntraceback = ntraceback.cut(excludepath=cutdir2) + ntraceback = ntraceback.filter(filter_traceback) + if not ntraceback: + ntraceback = traceback + excinfo.traceback = ntraceback.filter() # issue364: mark all but first and last frames to # only show a single-line message for each frame diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/skipping.py --- a/_pytest/skipping.py +++ b/_pytest/skipping.py @@ -133,7 +133,7 @@ return expl - at pytest.hookimpl_opts(tryfirst=True) + at pytest.hookimpl(tryfirst=True) def pytest_runtest_setup(item): evalskip = MarkEvaluator(item, 'skipif') if evalskip.istrue(): @@ -151,7 +151,7 @@ if not evalxfail.get('run', True): pytest.xfail("[NOTRUN] " + evalxfail.getexplanation()) - at pytest.hookimpl_opts(hookwrapper=True) + at pytest.hookimpl(hookwrapper=True) def pytest_runtest_makereport(item, call): outcome = yield rep = outcome.get_result() diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/terminal.py --- a/_pytest/terminal.py +++ b/_pytest/terminal.py @@ -3,6 +3,7 @@ This is a good source for looking at the various reporting hooks. """ import pytest +import pluggy import py import sys import time @@ -267,7 +268,7 @@ def pytest_collection_modifyitems(self): self.report_collect(True) - @pytest.hookimpl_opts(trylast=True) + @pytest.hookimpl(trylast=True) def pytest_sessionstart(self, session): self._sessionstarttime = time.time() if not self.showheader: @@ -278,7 +279,8 @@ if hasattr(sys, 'pypy_version_info'): verinfo = ".".join(map(str, sys.pypy_version_info[:3])) msg += "[pypy-%s-%s]" % (verinfo, sys.pypy_version_info[3]) - msg += " -- py-%s -- pytest-%s" % (py.__version__, pytest.__version__) + msg += ", pytest-%s, py-%s, pluggy-%s" % ( + pytest.__version__, py.__version__, pluggy.__version__) if self.verbosity > 0 or self.config.option.debug or \ getattr(self.config.option, 'pastebin', None): msg += " -- " + str(sys.executable) @@ -294,10 +296,11 @@ if config.inifile: inifile = config.rootdir.bestrelpath(config.inifile) lines = ["rootdir: %s, inifile: %s" %(config.rootdir, inifile)] - plugininfo = config.pluginmanager._plugin_distinfo + + plugininfo = config.pluginmanager.list_plugin_distinfo() if plugininfo: l = [] - for dist, plugin in plugininfo: + for plugin, dist in plugininfo: name = dist.project_name if name.startswith("pytest-"): name = name[7:] @@ -352,7 +355,7 @@ indent = (len(stack) - 1) * " " self._tw.line("%s%s" % (indent, col)) - @pytest.hookimpl_opts(hookwrapper=True) + @pytest.hookimpl(hookwrapper=True) def pytest_sessionfinish(self, exitstatus): outcome = yield outcome.get_result() diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 _pytest/unittest.py --- a/_pytest/unittest.py +++ b/_pytest/unittest.py @@ -140,7 +140,7 @@ if traceback: excinfo.traceback = traceback - at pytest.hookimpl_opts(tryfirst=True) + at pytest.hookimpl(tryfirst=True) def pytest_runtest_makereport(item, call): if isinstance(item, TestCaseFunction): if item._excinfo: @@ -152,7 +152,7 @@ # twisted trial support - at pytest.hookimpl_opts(hookwrapper=True) + at pytest.hookimpl(hookwrapper=True) def pytest_runtest_protocol(item): if isinstance(item, TestCaseFunction) and \ 'twisted.trial.unittest' in sys.modules: diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 doc/en/example/markers.txt --- a/doc/en/example/markers.txt +++ b/doc/en/example/markers.txt @@ -201,9 +201,9 @@ @pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures - @pytest.hookimpl_opts(tryfirst=True): mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible. + @pytest.hookimpl(tryfirst=True): mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible. - @pytest.hookimpl_opts(trylast=True): mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible. + @pytest.hookimpl(trylast=True): mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible. For an example on how to add and work with markers from a plugin, see @@ -375,9 +375,9 @@ @pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures - @pytest.hookimpl_opts(tryfirst=True): mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible. + @pytest.hookimpl(tryfirst=True): mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible. - @pytest.hookimpl_opts(trylast=True): mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible. + @pytest.hookimpl(trylast=True): mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible. Reading markers which were set from multiple places diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 doc/en/example/simple.txt --- a/doc/en/example/simple.txt +++ b/doc/en/example/simple.txt @@ -534,7 +534,7 @@ import pytest import os.path - @pytest.hookimpl_opts(tryfirst=True) + @pytest.hookimpl(tryfirst=True) def pytest_runtest_makereport(item, call, __multicall__): # execute all other hooks to obtain the report object rep = __multicall__.execute() @@ -607,7 +607,7 @@ import pytest - @pytest.hookimpl_opts(tryfirst=True) + @pytest.hookimpl(tryfirst=True) def pytest_runtest_makereport(item, call, __multicall__): # execute all other hooks to obtain the report object rep = __multicall__.execute() diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 doc/en/goodpractises.txt --- a/doc/en/goodpractises.txt +++ b/doc/en/goodpractises.txt @@ -169,6 +169,14 @@ python runtests.py +.. note:: + + You must have pytest and its dependencies installed as an sdist, not + as wheels because genscript need the source code for generating a + standalone script. + + + Integrating with distutils / ``python setup.py test`` -------------------------------------------------------- diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 doc/en/writing_plugins.txt --- a/doc/en/writing_plugins.txt +++ b/doc/en/writing_plugins.txt @@ -292,7 +292,7 @@ import pytest - @pytest.hookimpl_opts(hookwrapper=True) + @pytest.hookimpl(hookwrapper=True) def pytest_pyfunc_call(pyfuncitem): # do whatever you want before the next hook executes @@ -305,8 +305,7 @@ Note that hook wrappers don't return results themselves, they merely perform tracing or other side effects around the actual hook implementations. If the result of the underlying hook is a mutable object, they may modify -that result, however. - +that result but it's probably better to avoid it. Hook function ordering / call example @@ -338,16 +337,24 @@ Here is the order of execution: 1. Plugin3's pytest_collection_modifyitems called until the yield point -2. Plugin1's pytest_collection_modifyitems is called -3. Plugin2's pytest_collection_modifyitems is called -4. Plugin3's pytest_collection_modifyitems called for executing after the yield - The yield receives a :py:class:`CallOutcome` instance which encapsulates - the result from calling the non-wrappers. Wrappers cannot modify the result. + because it is a hook wrapper. + +2. Plugin1's pytest_collection_modifyitems is called because it is marked + with ``tryfirst=True``. + +3. Plugin2's pytest_collection_modifyitems is called because it is marked + with ``trylast=True`` (but even without this mark it would come after + Plugin1). + +4. Plugin3's pytest_collection_modifyitems then executing the code after the yield + point. The yield receives a :py:class:`CallOutcome` instance which encapsulates + the result from calling the non-wrappers. Wrappers shall not modify the result. It's possible to use ``tryfirst`` and ``trylast`` also in conjunction with ``hookwrapper=True`` in which case it will influence the ordering of hookwrappers among each other. + Declaring new hooks ------------------------ @@ -368,11 +375,11 @@ .. _`newhooks.py`: https://bitbucket.org/pytest-dev/pytest-xdist/src/52082f70e7dd04b00361091b8af906c60fd6700f/xdist/newhooks.py?at=default -Using hooks from 3rd party plugins -------------------------------------- +Optionally using hooks from 3rd party plugins +--------------------------------------------- Using new hooks from plugins as explained above might be a little tricky -because the standard :ref:`validation mechanism `: +because of the standard :ref:`validation mechanism `: if you depend on a plugin that is not installed, validation will fail and the error message will not make much sense to your users. @@ -395,7 +402,6 @@ This has the added benefit of allowing you to conditionally install hooks depending on which plugins are installed. - .. _`well specified hooks`: .. currentmodule:: _pytest.hookspec diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 pytest.py --- a/pytest.py +++ b/pytest.py @@ -11,8 +11,10 @@ # else we are imported -from _pytest.config import main, UsageError, _preloadplugins, cmdline -from _pytest.core import hookspec_opts, hookimpl_opts +from _pytest.config import ( + main, UsageError, _preloadplugins, cmdline, + hookspec, hookimpl +) from _pytest import __version__ _preloadplugins() # to populate pytest.* namespace so help(pytest) works diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 setup.py --- a/setup.py +++ b/setup.py @@ -48,7 +48,7 @@ def main(): - install_requires = ['py>=1.4.27.dev2'] + install_requires = ['py>=1.4.27.dev2', 'pluggy>=0.2.0,<0.3.0'] extras_require = {} if has_environment_marker_support(): extras_require[':python_version=="2.6" or python_version=="3.0" or python_version=="3.1"'] = ['argparse'] diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 testing/python/collect.py --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -559,7 +559,7 @@ b = testdir.mkdir("a").mkdir("b") b.join("conftest.py").write(py.code.Source(""" import pytest - @pytest.hookimpl_opts(hookwrapper=True) + @pytest.hookimpl(hookwrapper=True) def pytest_pycollect_makeitem(): outcome = yield if outcome.excinfo is None: diff -r e14e1c5215fc9f2022ba103c52929f9ddfb15d81 -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 testing/test_config.py --- a/testing/test_config.py +++ b/testing/test_config.py @@ -357,9 +357,9 @@ pm.register(m) hc = pm.hook.pytest_load_initial_conftests l = hc._nonwrappers + hc._wrappers - assert l[-1].__module__ == "_pytest.capture" - assert l[-2] == m.pytest_load_initial_conftests - assert l[-3].__module__ == "_pytest.config" + assert l[-1].function.__module__ == "_pytest.capture" + assert l[-2].function == m.pytest_load_initial_conftests + assert l[-3].function.__module__ == "_pytest.config" class TestWarning: def test_warn_config(self, testdir): This diff is so big that we needed to truncate the remainder. Repository URL: https://bitbucket.org/pytest-dev/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Thu May 7 11:24:44 2015 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 07 May 2015 09:24:44 -0000 Subject: [Pytest-commit] commit/pytest: RonnyPfannschmidt: Merged in hpk42/pytest-patches/plug30 (pull request #291) Message-ID: <20150507092444.12669.32642@app02.ash-private.bitbucket.org> 1 new commit in pytest: https://bitbucket.org/pytest-dev/pytest/commits/cf2f646d6578/ Changeset: cf2f646d6578 User: RonnyPfannschmidt Date: 2015-05-07 09:24:39+00:00 Summary: Merged in hpk42/pytest-patches/plug30 (pull request #291) use new pluggy api (now at 0.3.0) for adding hookcall monitoring Affected #: 4 files diff -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 -r cf2f646d6578e354dd3c0ea45aba31f74b9cd5ff _pytest/__init__.py --- a/_pytest/__init__.py +++ b/_pytest/__init__.py @@ -1,2 +1,2 @@ # -__version__ = '2.8.0.dev3' +__version__ = '2.8.0.dev4' diff -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 -r cf2f646d6578e354dd3c0ea45aba31f74b9cd5ff _pytest/pytester.py --- a/_pytest/pytester.py +++ b/_pytest/pytester.py @@ -13,7 +13,6 @@ import py import pytest from py.builtin import print_ -from pluggy import _TracedHookExecution from _pytest.main import Session, EXIT_OK @@ -194,12 +193,13 @@ self._pluginmanager = pluginmanager self.calls = [] - def before(hook, method, kwargs): - self.calls.append(ParsedCall(hook.name, kwargs)) - def after(outcome, hook, method, kwargs): + def before(hook_name, hook_impls, kwargs): + self.calls.append(ParsedCall(hook_name, kwargs)) + + def after(outcome, hook_name, hook_impls, kwargs): pass - executor = _TracedHookExecution(pluginmanager, before, after) - self._undo_wrapping = executor.undo + + self._undo_wrapping = pluginmanager.add_hookcall_monitoring(before, after) def finish_recording(self): self._undo_wrapping() @@ -667,6 +667,7 @@ class Collect: def pytest_configure(x, config): rec.append(self.make_hook_recorder(config.pluginmanager)) + plugins = kwargs.get("plugins") or [] plugins.append(Collect()) ret = pytest.main(list(args), plugins=plugins) @@ -677,6 +678,13 @@ class reprec: pass reprec.ret = ret + + # typically we reraise keyboard interrupts from the child run + # because it's our user requesting interruption of the testing + if ret == 2 and not kwargs.get("no_reraise_ctrlc"): + calls = reprec.getcalls("pytest_keyboard_interrupt") + if calls and calls[-1].excinfo.type == KeyboardInterrupt: + raise KeyboardInterrupt() return reprec def runpytest_inprocess(self, *args, **kwargs): @@ -688,7 +696,7 @@ capture = py.io.StdCapture() try: try: - reprec = self.inline_run(*args) + reprec = self.inline_run(*args, **kwargs) except SystemExit as e: class reprec: ret = e.args[0] diff -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 -r cf2f646d6578e354dd3c0ea45aba31f74b9cd5ff setup.py --- a/setup.py +++ b/setup.py @@ -48,7 +48,7 @@ def main(): - install_requires = ['py>=1.4.27.dev2', 'pluggy>=0.2.0,<0.3.0'] + install_requires = ['py>=1.4.27.dev2', 'pluggy>=0.3.0,<0.4.0'] extras_require = {} if has_environment_marker_support(): extras_require[':python_version=="2.6" or python_version=="3.0" or python_version=="3.1"'] = ['argparse'] diff -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 -r cf2f646d6578e354dd3c0ea45aba31f74b9cd5ff testing/test_terminal.py --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -132,8 +132,8 @@ ]) def test_itemreport_directclasses_not_shown_as_subclasses(self, testdir): - a = testdir.mkpydir("a") - a.join("test_hello.py").write(py.code.Source(""" + a = testdir.mkpydir("a123") + a.join("test_hello123.py").write(py.code.Source(""" class TestClass: def test_method(self): pass @@ -141,7 +141,7 @@ result = testdir.runpytest("-v") assert result.ret == 0 result.stdout.fnmatch_lines([ - "*a/test_hello.py*PASS*", + "*a123/test_hello123.py*PASS*", ]) assert " <- " not in result.stdout.str() @@ -155,7 +155,7 @@ raise KeyboardInterrupt # simulating the user """) - result = testdir.runpytest(*option.args) + result = testdir.runpytest(*option.args, no_reraise_ctrlc=True) result.stdout.fnmatch_lines([ " def test_foobar():", "> assert 0", @@ -178,7 +178,7 @@ pass """) - result = testdir.runpytest() + result = testdir.runpytest(no_reraise_ctrlc=True) assert result.ret == 2 result.stdout.fnmatch_lines(['*KeyboardInterrupt*']) Repository URL: https://bitbucket.org/pytest-dev/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Thu May 7 11:24:44 2015 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 07 May 2015 09:24:44 -0000 Subject: [Pytest-commit] commit/pytest: 2 new changesets Message-ID: <20150507092444.25204.46420@app04.ash-private.bitbucket.org> 2 new commits in pytest: https://bitbucket.org/pytest-dev/pytest/commits/1321b34febfe/ Changeset: 1321b34febfe Branch: plug30 User: hpk42 Date: 2015-05-07 09:02:55+00:00 Summary: use new pluggy api (now at 0.3.0) for adding hookcall monitoring and reraise real keyboard interrupts during inline pytest runs to allow for better stopping of the pytest tests. Affected #: 4 files diff -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 -r 1321b34febfea57c6a3ddf16aaccda8fc3363103 _pytest/__init__.py --- a/_pytest/__init__.py +++ b/_pytest/__init__.py @@ -1,2 +1,2 @@ # -__version__ = '2.8.0.dev3' +__version__ = '2.8.0.dev4' diff -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 -r 1321b34febfea57c6a3ddf16aaccda8fc3363103 _pytest/pytester.py --- a/_pytest/pytester.py +++ b/_pytest/pytester.py @@ -13,7 +13,6 @@ import py import pytest from py.builtin import print_ -from pluggy import _TracedHookExecution from _pytest.main import Session, EXIT_OK @@ -194,12 +193,13 @@ self._pluginmanager = pluginmanager self.calls = [] - def before(hook, method, kwargs): - self.calls.append(ParsedCall(hook.name, kwargs)) - def after(outcome, hook, method, kwargs): + def before(hook_name, hook_impls, kwargs): + self.calls.append(ParsedCall(hook_name, kwargs)) + + def after(outcome, hook_name, hook_impls, kwargs): pass - executor = _TracedHookExecution(pluginmanager, before, after) - self._undo_wrapping = executor.undo + + self._undo_wrapping = pluginmanager.add_hookcall_monitoring(before, after) def finish_recording(self): self._undo_wrapping() @@ -667,6 +667,7 @@ class Collect: def pytest_configure(x, config): rec.append(self.make_hook_recorder(config.pluginmanager)) + plugins = kwargs.get("plugins") or [] plugins.append(Collect()) ret = pytest.main(list(args), plugins=plugins) @@ -677,6 +678,13 @@ class reprec: pass reprec.ret = ret + + # typically we reraise keyboard interrupts from the child run + # because it's our user requesting interruption of the testing + if ret == 2 and not kwargs.get("no_reraise_ctrlc"): + calls = reprec.getcalls("pytest_keyboard_interrupt") + if calls and calls[-1].excinfo.type == KeyboardInterrupt: + raise KeyboardInterrupt() return reprec def runpytest_inprocess(self, *args, **kwargs): @@ -688,7 +696,7 @@ capture = py.io.StdCapture() try: try: - reprec = self.inline_run(*args) + reprec = self.inline_run(*args, **kwargs) except SystemExit as e: class reprec: ret = e.args[0] diff -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 -r 1321b34febfea57c6a3ddf16aaccda8fc3363103 setup.py --- a/setup.py +++ b/setup.py @@ -48,7 +48,7 @@ def main(): - install_requires = ['py>=1.4.27.dev2', 'pluggy>=0.2.0,<0.3.0'] + install_requires = ['py>=1.4.27.dev2', 'pluggy>=0.3.0,<0.4.0'] extras_require = {} if has_environment_marker_support(): extras_require[':python_version=="2.6" or python_version=="3.0" or python_version=="3.1"'] = ['argparse'] diff -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 -r 1321b34febfea57c6a3ddf16aaccda8fc3363103 testing/test_terminal.py --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -132,8 +132,8 @@ ]) def test_itemreport_directclasses_not_shown_as_subclasses(self, testdir): - a = testdir.mkpydir("a") - a.join("test_hello.py").write(py.code.Source(""" + a = testdir.mkpydir("a123") + a.join("test_hello123.py").write(py.code.Source(""" class TestClass: def test_method(self): pass @@ -141,7 +141,7 @@ result = testdir.runpytest("-v") assert result.ret == 0 result.stdout.fnmatch_lines([ - "*a/test_hello.py*PASS*", + "*a123/test_hello123.py*PASS*", ]) assert " <- " not in result.stdout.str() @@ -155,7 +155,7 @@ raise KeyboardInterrupt # simulating the user """) - result = testdir.runpytest(*option.args) + result = testdir.runpytest(*option.args, no_reraise_ctrlc=True) result.stdout.fnmatch_lines([ " def test_foobar():", "> assert 0", @@ -178,7 +178,7 @@ pass """) - result = testdir.runpytest() + result = testdir.runpytest(no_reraise_ctrlc=True) assert result.ret == 2 result.stdout.fnmatch_lines(['*KeyboardInterrupt*']) https://bitbucket.org/pytest-dev/pytest/commits/cf2f646d6578/ Changeset: cf2f646d6578 User: RonnyPfannschmidt Date: 2015-05-07 09:24:39+00:00 Summary: Merged in hpk42/pytest-patches/plug30 (pull request #291) use new pluggy api (now at 0.3.0) for adding hookcall monitoring Affected #: 4 files diff -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 -r cf2f646d6578e354dd3c0ea45aba31f74b9cd5ff _pytest/__init__.py --- a/_pytest/__init__.py +++ b/_pytest/__init__.py @@ -1,2 +1,2 @@ # -__version__ = '2.8.0.dev3' +__version__ = '2.8.0.dev4' diff -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 -r cf2f646d6578e354dd3c0ea45aba31f74b9cd5ff _pytest/pytester.py --- a/_pytest/pytester.py +++ b/_pytest/pytester.py @@ -13,7 +13,6 @@ import py import pytest from py.builtin import print_ -from pluggy import _TracedHookExecution from _pytest.main import Session, EXIT_OK @@ -194,12 +193,13 @@ self._pluginmanager = pluginmanager self.calls = [] - def before(hook, method, kwargs): - self.calls.append(ParsedCall(hook.name, kwargs)) - def after(outcome, hook, method, kwargs): + def before(hook_name, hook_impls, kwargs): + self.calls.append(ParsedCall(hook_name, kwargs)) + + def after(outcome, hook_name, hook_impls, kwargs): pass - executor = _TracedHookExecution(pluginmanager, before, after) - self._undo_wrapping = executor.undo + + self._undo_wrapping = pluginmanager.add_hookcall_monitoring(before, after) def finish_recording(self): self._undo_wrapping() @@ -667,6 +667,7 @@ class Collect: def pytest_configure(x, config): rec.append(self.make_hook_recorder(config.pluginmanager)) + plugins = kwargs.get("plugins") or [] plugins.append(Collect()) ret = pytest.main(list(args), plugins=plugins) @@ -677,6 +678,13 @@ class reprec: pass reprec.ret = ret + + # typically we reraise keyboard interrupts from the child run + # because it's our user requesting interruption of the testing + if ret == 2 and not kwargs.get("no_reraise_ctrlc"): + calls = reprec.getcalls("pytest_keyboard_interrupt") + if calls and calls[-1].excinfo.type == KeyboardInterrupt: + raise KeyboardInterrupt() return reprec def runpytest_inprocess(self, *args, **kwargs): @@ -688,7 +696,7 @@ capture = py.io.StdCapture() try: try: - reprec = self.inline_run(*args) + reprec = self.inline_run(*args, **kwargs) except SystemExit as e: class reprec: ret = e.args[0] diff -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 -r cf2f646d6578e354dd3c0ea45aba31f74b9cd5ff setup.py --- a/setup.py +++ b/setup.py @@ -48,7 +48,7 @@ def main(): - install_requires = ['py>=1.4.27.dev2', 'pluggy>=0.2.0,<0.3.0'] + install_requires = ['py>=1.4.27.dev2', 'pluggy>=0.3.0,<0.4.0'] extras_require = {} if has_environment_marker_support(): extras_require[':python_version=="2.6" or python_version=="3.0" or python_version=="3.1"'] = ['argparse'] diff -r af98ffcfb0fb1d0116bfc4b46c49dfa31fdfc7c2 -r cf2f646d6578e354dd3c0ea45aba31f74b9cd5ff testing/test_terminal.py --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -132,8 +132,8 @@ ]) def test_itemreport_directclasses_not_shown_as_subclasses(self, testdir): - a = testdir.mkpydir("a") - a.join("test_hello.py").write(py.code.Source(""" + a = testdir.mkpydir("a123") + a.join("test_hello123.py").write(py.code.Source(""" class TestClass: def test_method(self): pass @@ -141,7 +141,7 @@ result = testdir.runpytest("-v") assert result.ret == 0 result.stdout.fnmatch_lines([ - "*a/test_hello.py*PASS*", + "*a123/test_hello123.py*PASS*", ]) assert " <- " not in result.stdout.str() @@ -155,7 +155,7 @@ raise KeyboardInterrupt # simulating the user """) - result = testdir.runpytest(*option.args) + result = testdir.runpytest(*option.args, no_reraise_ctrlc=True) result.stdout.fnmatch_lines([ " def test_foobar():", "> assert 0", @@ -178,7 +178,7 @@ pass """) - result = testdir.runpytest() + result = testdir.runpytest(no_reraise_ctrlc=True) assert result.ret == 2 result.stdout.fnmatch_lines(['*KeyboardInterrupt*']) Repository URL: https://bitbucket.org/pytest-dev/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From builds at drone.io Thu May 7 11:25:56 2015 From: builds at drone.io (Drone.io Build) Date: Thu, 07 May 2015 09:25:56 +0000 Subject: [Pytest-commit] [FAIL] pytest - # 113 Message-ID: <20150507092556.11946.54313@drone.io> Build Failed Build : https://drone.io/bitbucket.org/pytest-dev/pytest/113 Project : https://drone.io/bitbucket.org/pytest-dev/pytest Repository : https://bitbucket.org/pytest-dev/pytest Version : 4241:cf2f646d6578 Author : Ronny Pfannschmidt Branch : default Message: Merged in hpk42/pytest-patches/plug30 (pull request #291) -------------- next part -------------- An HTML attachment was scrubbed... URL: From builds at drone.io Thu May 7 11:27:10 2015 From: builds at drone.io (Drone.io Build) Date: Thu, 07 May 2015 09:27:10 +0000 Subject: [Pytest-commit] [FAIL] pytest - # 114 Message-ID: <20150507092710.8129.75294@drone.io> Build Failed Build : https://drone.io/bitbucket.org/pytest-dev/pytest/114 Project : https://drone.io/bitbucket.org/pytest-dev/pytest Repository : https://bitbucket.org/pytest-dev/pytest Version : 4241:cf2f646d6578 Author : Ronny Pfannschmidt Branch : default Message: Merged in hpk42/pytest-patches/plug30 (pull request #291) -------------- next part -------------- An HTML attachment was scrubbed... URL: From commits-noreply at bitbucket.org Thu May 7 11:50:46 2015 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 07 May 2015 09:50:46 -0000 Subject: [Pytest-commit] commit/pytest: hpk42: make default depend on just published py/pluggy releases Message-ID: <20150507095046.20177.21466@app01.ash-private.bitbucket.org> 1 new commit in pytest: https://bitbucket.org/pytest-dev/pytest/commits/4d7215daf4cc/ Changeset: 4d7215daf4cc User: hpk42 Date: 2015-05-07 09:50:08+00:00 Summary: make default depend on just published py/pluggy releases Affected #: 1 file diff -r cf2f646d6578e354dd3c0ea45aba31f74b9cd5ff -r 4d7215daf4cc1abe9d4dccab978bebcbe3d870d0 setup.py --- a/setup.py +++ b/setup.py @@ -48,7 +48,7 @@ def main(): - install_requires = ['py>=1.4.27.dev2', 'pluggy>=0.3.0,<0.4.0'] + install_requires = ['py>=1.4.27', 'pluggy>=0.3.0,<0.4.0'] extras_require = {} if has_environment_marker_support(): extras_require[':python_version=="2.6" or python_version=="3.0" or python_version=="3.1"'] = ['argparse'] Repository URL: https://bitbucket.org/pytest-dev/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From builds at drone.io Thu May 7 11:56:22 2015 From: builds at drone.io (Drone.io Build) Date: Thu, 07 May 2015 09:56:22 +0000 Subject: [Pytest-commit] [FAIL] pytest - # 115 Message-ID: <20150507095622.8129.83661@drone.io> Build Failed Build : https://drone.io/bitbucket.org/pytest-dev/pytest/115 Project : https://drone.io/bitbucket.org/pytest-dev/pytest Repository : https://bitbucket.org/pytest-dev/pytest Version : 4242:4d7215daf4cc Author : holger krekel Branch : default Message: make default depend on just published py/pluggy releases -------------- next part -------------- An HTML attachment was scrubbed... URL: From commits-noreply at bitbucket.org Thu May 7 12:02:28 2015 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 07 May 2015 10:02:28 -0000 Subject: [Pytest-commit] commit/pytest: hpk42: try to ignore cx_freeze from regular pytest runs and see if travis is happy Message-ID: <20150507100228.27694.78528@app12.ash-private.bitbucket.org> 1 new commit in pytest: https://bitbucket.org/pytest-dev/pytest/commits/157eb0bccf55/ Changeset: 157eb0bccf55 User: hpk42 Date: 2015-05-07 10:02:14+00:00 Summary: try to ignore cx_freeze from regular pytest runs and see if travis is happy Affected #: 1 file diff -r 4d7215daf4cc1abe9d4dccab978bebcbe3d870d0 -r 157eb0bccf55764dabe788b81bbcad1663774079 tox.ini --- a/tox.ini +++ b/tox.ini @@ -145,7 +145,7 @@ minversion=2.0 plugins=pytester #--pyargs --doctest-modules --ignore=.tox -addopts= -rxsX -p pytester +addopts= -rxsX -p pytester --ignore=cx_freeze rsyncdirs=tox.ini pytest.py _pytest testing python_files=test_*.py *_test.py testing/*/*.py python_classes=Test Acceptance Repository URL: https://bitbucket.org/pytest-dev/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From builds at drone.io Thu May 7 12:08:07 2015 From: builds at drone.io (Drone.io Build) Date: Thu, 07 May 2015 10:08:07 +0000 Subject: [Pytest-commit] [FAIL] pytest - # 116 Message-ID: <20150507100758.98669.3435@drone.io> Build Failed Build : https://drone.io/bitbucket.org/pytest-dev/pytest/116 Project : https://drone.io/bitbucket.org/pytest-dev/pytest Repository : https://bitbucket.org/pytest-dev/pytest Version : 4243:157eb0bccf55 Author : holger krekel Branch : default Message: try to ignore cx_freeze from regular pytest runs and see if travis is happy -------------- next part -------------- An HTML attachment was scrubbed... URL: From commits-noreply at bitbucket.org Thu May 7 12:19:51 2015 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 07 May 2015 10:19:51 -0000 Subject: [Pytest-commit] commit/pytest: hpk42: another try at ignoring cx_freeze during other tox envs Message-ID: <20150507101951.577.46417@app13.ash-private.bitbucket.org> 1 new commit in pytest: https://bitbucket.org/pytest-dev/pytest/commits/162f18fafaba/ Changeset: 162f18fafaba User: hpk42 Date: 2015-05-07 10:19:35+00:00 Summary: another try at ignoring cx_freeze during other tox envs Affected #: 1 file diff -r 157eb0bccf55764dabe788b81bbcad1663774079 -r 162f18fafaba4bc42fa61b9e4fbdf3d7b645748e tox.ini --- a/tox.ini +++ b/tox.ini @@ -145,7 +145,7 @@ minversion=2.0 plugins=pytester #--pyargs --doctest-modules --ignore=.tox -addopts= -rxsX -p pytester --ignore=cx_freeze +addopts= -rxsX -p pytester --ignore=testing/cx_freeze rsyncdirs=tox.ini pytest.py _pytest testing python_files=test_*.py *_test.py testing/*/*.py python_classes=Test Acceptance Repository URL: https://bitbucket.org/pytest-dev/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Thu May 7 12:36:07 2015 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Thu, 07 May 2015 10:36:07 -0000 Subject: [Pytest-commit] commit/pytest: hpk42: close branch Message-ID: <20150507103607.28346.49623@app13.ash-private.bitbucket.org> 1 new commit in pytest: https://bitbucket.org/pytest-dev/pytest/commits/4b186888dcb8/ Changeset: 4b186888dcb8 Branch: plug30 User: hpk42 Date: 2015-05-07 10:35:38+00:00 Summary: close branch Affected #: 0 files Repository URL: https://bitbucket.org/pytest-dev/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From builds at drone.io Thu May 7 12:41:49 2015 From: builds at drone.io (Drone.io Build) Date: Thu, 07 May 2015 10:41:49 +0000 Subject: [Pytest-commit] [FAIL] pytest - # 118 Message-ID: <20150507104148.14360.87247@drone.io> Build Failed Build : https://drone.io/bitbucket.org/pytest-dev/pytest/118 Project : https://drone.io/bitbucket.org/pytest-dev/pytest Repository : https://bitbucket.org/pytest-dev/pytest Version : 4068:4b186888dcb8 Author : holger krekel Branch : plug30 Message: close branch -------------- next part -------------- An HTML attachment was scrubbed... URL: From issues-reply at bitbucket.org Fri May 8 07:01:56 2015 From: issues-reply at bitbucket.org (=?utf-8?q?Tomi_Pievil=C3=A4inen?=) Date: Fri, 08 May 2015 05:01:56 -0000 Subject: [Pytest-commit] Issue #739: triple quotes can mess up source code parsing (pytest-dev/pytest) Message-ID: <20150508050156.4960.89432@app06.ash-private.bitbucket.org> New issue 739: triple quotes can mess up source code parsing https://bitbucket.org/pytest-dev/pytest/issue/739/triple-quotes-can-mess-up-source-code Tomi Pievil?inen: There seems to be a weird corner case with triple quotes. With a test file def test_1_works(): (''' ''').doesnt_exist() def test_2_works(): (''' ''').lower() def test_3_works(): ''' '''.doesnt_exist() def test_1_fails(): (''' ''').doesnt_exist() def test_2_fails(): (''' ''').doesnt_exist() py.test crashes with INTERNALERROR: $ py.test ============================================================================ test session starts ============================================================================ platform linux -- Python 3.4.3 -- py-1.4.27 -- pytest-2.7.0 rootdir: /home/hukka/pytest-test, inifile: collected 5 items test_pytest.py F.F INTERNALERROR> Traceback (most recent call last): INTERNALERROR> File "/home/hukka/addressimport/lib/python3.4/site-packages/_pytest/main.py", line 84, in wrap_session INTERNALERROR> doit(config, session) INTERNALERROR> File "/home/hukka/addressimport/lib/python3.4/site-packages/_pytest/main.py", line 122, in _main INTERNALERROR> config.hook.pytest_runtestloop(session=session) INTERNALERROR> File "/home/hukka/addressimport/lib/python3.4/site-packages/_pytest/core.py", line 521, in __call__ INTERNALERROR> return self._docall(self.methods, kwargs) INTERNALERROR> File "/home/hukka/addressimport/lib/python3.4/site-packages/_pytest/core.py", line 528, in _docall INTERNALERROR> firstresult=self.firstresult).execute() INTERNALERROR> File "/home/hukka/addressimport/lib/python3.4/site-packages/_pytest/core.py", line 394, in execute INTERNALERROR> res = method(*args) INTERNALERROR> File "/home/hukka/addressimport/lib/python3.4/site-packages/_pytest/main.py", line 142, in pytest_runtestloop INTERNALERROR> item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem) INTERNALERROR> File "/home/hukka/addressimport/lib/python3.4/site-packages/_pytest/core.py", line 521, in __call__ INTERNALERROR> return self._docall(self.methods, kwargs) INTERNALERROR> File "/home/hukka/addressimport/lib/python3.4/site-packages/_pytest/core.py", line 528, in _docall INTERNALERROR> firstresult=self.firstresult).execute() INTERNALERROR> File "/home/hukka/addressimport/lib/python3.4/site-packages/_pytest/core.py", line 393, in execute INTERNALERROR> return wrapped_call(method(*args), self.execute) INTERNALERROR> File "/home/hukka/addressimport/lib/python3.4/site-packages/_pytest/core.py", line 113, in wrapped_call INTERNALERROR> return call_outcome.get_result() INTERNALERROR> File "/home/hukka/addressimport/lib/python3.4/site-packages/_pytest/core.py", line 137, in get_result INTERNALERROR> raise ex[1].with_traceback(ex[2]) INTERNALERROR> File "/home/hukka/addressimport/lib/python3.4/site-packages/_pytest/core.py", line 123, in __init__ INTERNALERROR> self.result = func() INTERNALERROR> File "/home/hukka/addressimport/lib/python3.4/site-packages/_pytest/core.py", line 394, in execute INTERNALERROR> res = method(*args) INTERNALERROR> File "/home/hukka/addressimport/lib/python3.4/site-packages/_pytest/runner.py", line 65, in pytest_runtest_protocol INTERNALERROR> runtestprotocol(item, nextitem=nextitem) INTERNALERROR> File "/home/hukka/addressimport/lib/python3.4/site-packages/_pytest/runner.py", line 75, in runtestprotocol INTERNALERROR> reports.append(call_and_report(item, "call", log)) INTERNALERROR> File "/home/hukka/addressimport/lib/python3.4/site-packages/_pytest/runner.py", line 121, in call_and_report INTERNALERROR> report = hook.pytest_runtest_makereport(item=item, call=call) INTERNALERROR> File "/home/hukka/addressimport/lib/python3.4/site-packages/_pytest/core.py", line 521, in __call__ INTERNALERROR> return self._docall(self.methods, kwargs) INTERNALERROR> File "/home/hukka/addressimport/lib/python3.4/site-packages/_pytest/core.py", line 528, in _docall INTERNALERROR> firstresult=self.firstresult).execute() INTERNALERROR> File "/home/hukka/addressimport/lib/python3.4/site-packages/_pytest/core.py", line 393, in execute INTERNALERROR> return wrapped_call(method(*args), self.execute) INTERNALERROR> File "/home/hukka/addressimport/lib/python3.4/site-packages/_pytest/core.py", line 109, in wrapped_call INTERNALERROR> wrap_controller.send(call_outcome) INTERNALERROR> File "/home/hukka/addressimport/lib/python3.4/site-packages/_pytest/skipping.py", line 157, in pytest_runtest_makereport INTERNALERROR> rep = outcome.get_result() INTERNALERROR> File "/home/hukka/addressimport/lib/python3.4/site-packages/_pytest/core.py", line 137, in get_result INTERNALERROR> raise ex[1].with_traceback(ex[2]) INTERNALERROR> File "/home/hukka/addressimport/lib/python3.4/site-packages/_pytest/core.py", line 123, in __init__ INTERNALERROR> self.result = func() INTERNALERROR> File "/home/hukka/addressimport/lib/python3.4/site-packages/_pytest/core.py", line 394, in execute INTERNALERROR> res = method(*args) INTERNALERROR> File "/home/hukka/addressimport/lib/python3.4/site-packages/_pytest/runner.py", line 224, in pytest_runtest_makereport INTERNALERROR> longrepr = item.repr_failure(excinfo) INTERNALERROR> File "/home/hukka/addressimport/lib/python3.4/site-packages/_pytest/python.py", line 625, in repr_failure INTERNALERROR> return self._repr_failure_py(excinfo, style=style) INTERNALERROR> File "/home/hukka/addressimport/lib/python3.4/site-packages/_pytest/python.py", line 618, in _repr_failure_py INTERNALERROR> style=style) INTERNALERROR> File "/home/hukka/addressimport/lib/python3.4/site-packages/_pytest/main.py", line 410, in _repr_failure_py INTERNALERROR> style=style, tbfilter=tbfilter) INTERNALERROR> File "/home/hukka/addressimport/lib/python3.4/site-packages/py/_code/code.py", line 412, in getrepr INTERNALERROR> return fmt.repr_excinfo(self) INTERNALERROR> File "/home/hukka/addressimport/lib/python3.4/site-packages/py/_code/code.py", line 590, in repr_excinfo INTERNALERROR> reprtraceback = self.repr_traceback(excinfo) INTERNALERROR> File "/home/hukka/addressimport/lib/python3.4/site-packages/py/_code/code.py", line 582, in repr_traceback INTERNALERROR> reprentry = self.repr_traceback_entry(entry, einfo) INTERNALERROR> File "/home/hukka/addressimport/lib/python3.4/site-packages/py/_code/code.py", line 543, in repr_traceback_entry INTERNALERROR> s = self.get_source(source, line_index, excinfo, short=short) INTERNALERROR> File "/home/hukka/addressimport/lib/python3.4/site-packages/py/_code/code.py", line 484, in get_source INTERNALERROR> lines.append(self.flow_marker + " " + source.lines[line_index]) INTERNALERROR> IndexError: list index out of range ==================================================================== 2 failed, 1 passed in 0.03 seconds ==================================================================== The first three tests work fine on their own: $ py.test ============================================================================ test session starts ============================================================================ platform linux -- Python 3.4.3 -- py-1.4.27 -- pytest-2.7.0 rootdir: /home/hukka/pytest-test, inifile: collected 3 items test_pytest.py F.F ================================================================================= FAILURES ================================================================================== _______________________________________________________________________________ test_1_works ________________________________________________________________________________ def test_1_works(): (''' > ''').doesnt_exist() E AttributeError: 'str' object has no attribute 'doesnt_exist' test_pytest.py:3: AttributeError _______________________________________________________________________________ test_3_works ________________________________________________________________________________ def test_3_works(): ''' > '''.doesnt_exist() E AttributeError: 'str' object has no attribute 'doesnt_exist' test_pytest.py:13: AttributeError ==================================================================== 2 failed, 1 passed in 0.02 seconds ==================================================================== but either of the last two will cause this. Same thing happens also on python2.7. From commits-noreply at bitbucket.org Fri May 8 13:15:28 2015 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Fri, 08 May 2015 11:15:28 -0000 Subject: [Pytest-commit] commit/tox: hpk42: introduce little plugin system based on pluggy Message-ID: <20150508111528.11828.50587@app07.ash-private.bitbucket.org> 1 new commit in tox: https://bitbucket.org/hpk42/tox/commits/cc1933175162/ Changeset: cc1933175162 User: hpk42 Date: 2015-05-08 11:15:14+00:00 Summary: introduce little plugin system based on pluggy and refactor/streamline some code with relation to getting executables Affected #: 13 files diff -r 868a79b11ff5fc09b4d13ab7641fb47c77780a91 -r cc1933175162c4bb01669912a57df6fba4be5780 doc/conf.py --- a/doc/conf.py +++ b/doc/conf.py @@ -48,8 +48,8 @@ # built documents. # # The short X.Y version. -release = "1.9" -version = "1.9.0" +release = "2.0" +version = "2.0.0" # The full version, including alpha/beta/rc tags. # The language for content autogenerated by Sphinx. Refer to documentation diff -r 868a79b11ff5fc09b4d13ab7641fb47c77780a91 -r cc1933175162c4bb01669912a57df6fba4be5780 doc/index.txt --- a/doc/index.txt +++ b/doc/index.txt @@ -5,7 +5,7 @@ --------------------------------------------- ``tox`` aims to automate and standardize testing in Python. It is part -of a larger vision of easing the packaging, testing and release process +of a larger vision of easing the packaging, testing and release process of Python software. What is Tox? @@ -21,6 +21,7 @@ * acting as a frontend to Continuous Integration servers, greatly reducing boilerplate and merging CI and shell-based testing. + Basic example ----------------- @@ -62,10 +63,10 @@ - test-tool agnostic: runs py.test, nose or unittests in a uniform manner -* supports :ref:`using different / multiple PyPI index servers ` +* :doc:`(new in 2.0) plugin system ` to modify tox execution with simple hooks. * uses pip_ and setuptools_ by default. Experimental - support for configuring the installer command + support for configuring the installer command through :confval:`install_command=ARGV`. * **cross-Python compatible**: CPython-2.6, 2.7, 3.2 and higher, @@ -74,11 +75,11 @@ * **cross-platform**: Windows and Unix style environments * **integrates with continuous integration servers** like Jenkins_ - (formerly known as Hudson) and helps you to avoid boilerplatish + (formerly known as Hudson) and helps you to avoid boilerplatish and platform-specific build-step hacks. * **full interoperability with devpi**: is integrated with and - is used for testing in the devpi_ system, a versatile pypi + is used for testing in the devpi_ system, a versatile pypi index server and release managing tool. * **driven by a simple ini-style config file** @@ -89,6 +90,9 @@ * **professionally** :doc:`supported ` +* supports :ref:`using different / multiple PyPI index servers ` + + .. _pypy: http://pypy.org .. _`tox.ini`: :doc:configfile diff -r 868a79b11ff5fc09b4d13ab7641fb47c77780a91 -r cc1933175162c4bb01669912a57df6fba4be5780 doc/plugins.txt --- /dev/null +++ b/doc/plugins.txt @@ -0,0 +1,69 @@ +.. be in -*- rst -*- mode! + +tox plugins +=========== + +.. versionadded:: 2.0 + +With tox-2.0 a few aspects of tox running can be experimentally modified +by writing hook functions. We expect the list of hook function to grow +over time. + +writing a setuptools entrypoints plugin +--------------------------------------- + +If you have a ``tox_MYPLUGIN.py`` module you could use the following +rough ``setup.py`` to make it into a package which you can upload to the +Python packaging index:: + + # content of setup.py + from setuptools import setup + + if __name__ == "__main__": + setup( + name='tox-MYPLUGIN', + description='tox plugin decsription', + license="MIT license", + version='0.1', + py_modules=['tox_MYPLUGIN'], + entry_points={'tox': ['MYPLUGIN = tox_MYPLUGIN']}, + install_requires=['tox>=2.0'], + ) + +You can then install the plugin to develop it via:: + + pip install -e . + +and later publish it. + +The ``entry_points`` part allows tox to see your plugin during startup. + + +Writing hook implementations +---------------------------- + +A plugin module needs can define one or more hook implementation functions:: + + from tox import hookimpl + + @hookimpl + def tox_addoption(parser): + # add your own command line options + + + @hookimpl + def tox_configure(config): + # post process tox configuration after cmdline/ini file have + # been parsed + +If you put this into a module and make it pypi-installable with the ``tox`` +entry point you'll get your code executed as part of a tox run. + + + +tox hook specifications +---------------------------- + +.. automodule:: tox.hookspecs + :members: + diff -r 868a79b11ff5fc09b4d13ab7641fb47c77780a91 -r cc1933175162c4bb01669912a57df6fba4be5780 setup.py --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ def main(): version = sys.version_info[:2] - install_requires = ['virtualenv>=1.11.2', 'py>=1.4.17', ] + install_requires = ['virtualenv>=1.11.2', 'py>=1.4.17', 'pluggy>=0.3.0,<0.4.0'] if version < (2, 7): install_requires += ['argparse'] setup( diff -r 868a79b11ff5fc09b4d13ab7641fb47c77780a91 -r cc1933175162c4bb01669912a57df6fba4be5780 tests/test_config.py --- a/tests/test_config.py +++ b/tests/test_config.py @@ -6,7 +6,6 @@ import tox import tox._config from tox._config import * # noqa -from tox._config import _split_env from tox._venv import VirtualEnv @@ -1561,31 +1560,16 @@ ]) -class TestArgumentParser: - - def test_dash_e_single_1(self): - parser = prepare_parse('testpkg') - args = parser.parse_args('-e py26'.split()) - envlist = _split_env(args.env) - assert envlist == ['py26'] - - def test_dash_e_single_2(self): - parser = prepare_parse('testpkg') - args = parser.parse_args('-e py26,py33'.split()) - envlist = _split_env(args.env) - assert envlist == ['py26', 'py33'] - - def test_dash_e_same(self): - parser = prepare_parse('testpkg') - args = parser.parse_args('-e py26,py26'.split()) - envlist = _split_env(args.env) - assert envlist == ['py26', 'py26'] - - def test_dash_e_combine(self): - parser = prepare_parse('testpkg') - args = parser.parse_args('-e py26,py25,py33 -e py33,py27'.split()) - envlist = _split_env(args.env) - assert envlist == ['py26', 'py25', 'py33', 'py33', 'py27'] + at pytest.mark.parametrize("cmdline,envlist", [ + ("-e py26", ['py26']), + ("-e py26,py33", ['py26', 'py33']), + ("-e py26,py26", ['py26', 'py26']), + ("-e py26,py33 -e py33,py27", ['py26', 'py33', 'py33', 'py27']) +]) +def test_env_spec(cmdline, envlist): + args = cmdline.split() + config = parseconfig(args) + assert config.envlist == envlist class TestCommandParser: diff -r 868a79b11ff5fc09b4d13ab7641fb47c77780a91 -r cc1933175162c4bb01669912a57df6fba4be5780 tests/test_interpreters.py --- a/tests/test_interpreters.py +++ b/tests/test_interpreters.py @@ -3,11 +3,13 @@ import pytest from tox.interpreters import * # noqa +from tox._config import get_plugin_manager @pytest.fixture def interpreters(): - return Interpreters() + pm = get_plugin_manager() + return Interpreters(hook=pm.hook) @pytest.mark.skipif("sys.platform != 'win32'") @@ -28,8 +30,8 @@ assert locate_via_py('3', '2') == sys.executable -def test_find_executable(): - p = find_executable(sys.executable) +def test_tox_get_python_executable(): + p = tox_get_python_executable(sys.executable) assert p == py.path.local(sys.executable) for ver in [""] + "2.4 2.5 2.6 2.7 3.0 3.1 3.2 3.3".split(): name = "python%s" % ver @@ -42,7 +44,7 @@ else: if not py.path.local.sysfind(name): continue - p = find_executable(name) + p = tox_get_python_executable(name) assert p popen = py.std.subprocess.Popen([str(p), '-V'], stderr=py.std.subprocess.PIPE) @@ -55,7 +57,7 @@ def sysfind(x): return "hello" monkeypatch.setattr(py.path.local, "sysfind", sysfind) - t = find_executable("qweqwe") + t = tox_get_python_executable("qweqwe") assert t == "hello" diff -r 868a79b11ff5fc09b4d13ab7641fb47c77780a91 -r cc1933175162c4bb01669912a57df6fba4be5780 tests/test_venv.py --- a/tests/test_venv.py +++ b/tests/test_venv.py @@ -65,7 +65,7 @@ # assert Envconfig.toxworkdir in args assert venv.getcommandpath("easy_install", cwd=py.path.local()) interp = venv._getliveconfig().python - assert interp == venv.envconfig._basepython_info.executable + assert interp == venv.envconfig.python_info.executable assert venv.path_config.check(exists=False) diff -r 868a79b11ff5fc09b4d13ab7641fb47c77780a91 -r cc1933175162c4bb01669912a57df6fba4be5780 tox.ini --- a/tox.ini +++ b/tox.ini @@ -22,7 +22,9 @@ deps = pytest-flakes>=0.2 pytest-pep8 -commands = py.test -x --flakes --pep8 tox tests +commands = + py.test --flakes -m flakes tox tests + py.test --pep8 -m pep8 tox tests [testenv:dev] # required to make looponfail reload on every source code change diff -r 868a79b11ff5fc09b4d13ab7641fb47c77780a91 -r cc1933175162c4bb01669912a57df6fba4be5780 tox/__init__.py --- a/tox/__init__.py +++ b/tox/__init__.py @@ -1,6 +1,8 @@ # __version__ = '2.0.0.dev1' +from .hookspecs import hookspec, hookimpl # noqa + class exception: class Error(Exception): diff -r 868a79b11ff5fc09b4d13ab7641fb47c77780a91 -r cc1933175162c4bb01669912a57df6fba4be5780 tox/_cmdline.py --- a/tox/_cmdline.py +++ b/tox/_cmdline.py @@ -24,7 +24,7 @@ def main(args=None): try: - config = parseconfig(args, 'tox') + config = parseconfig(args) retcode = Session(config).runcommand() raise SystemExit(retcode) except KeyboardInterrupt: @@ -551,8 +551,7 @@ for envconfig in self.config.envconfigs.values(): self.report.line("[testenv:%s]" % envconfig.envname, bold=True) self.report.line(" basepython=%s" % envconfig.basepython) - self.report.line(" _basepython_info=%s" % - envconfig._basepython_info) + self.report.line(" pythoninfo=%s" % (envconfig.python_info,)) self.report.line(" envpython=%s" % envconfig.envpython) self.report.line(" envtmpdir=%s" % envconfig.envtmpdir) self.report.line(" envbindir=%s" % envconfig.envbindir) diff -r 868a79b11ff5fc09b4d13ab7641fb47c77780a91 -r cc1933175162c4bb01669912a57df6fba4be5780 tox/_config.py --- a/tox/_config.py +++ b/tox/_config.py @@ -8,8 +8,10 @@ import string import pkg_resources import itertools +import pluggy -from tox.interpreters import Interpreters +import tox.interpreters +from tox import hookspecs import py @@ -22,20 +24,43 @@ for version in '24,25,26,27,30,31,32,33,34,35'.split(','): default_factors['py' + version] = 'python%s.%s' % tuple(version) +hookimpl = pluggy.HookimplMarker("tox") -def parseconfig(args=None, pkg=None): + +def get_plugin_manager(): + # initialize plugin manager + pm = pluggy.PluginManager("tox") + pm.add_hookspecs(hookspecs) + pm.register(tox._config) + pm.register(tox.interpreters) + pm.load_setuptools_entrypoints("tox") + pm.check_pending() + return pm + + +def parseconfig(args=None): """ :param list[str] args: Optional list of arguments. :type pkg: str :rtype: :class:`Config` :raise SystemExit: toxinit file is not found """ + + pm = get_plugin_manager() + if args is None: args = sys.argv[1:] - parser = prepare_parse(pkg) - opts = parser.parse_args(args) - config = Config() - config.option = opts + + # prepare command line options + parser = argparse.ArgumentParser(description=__doc__) + pm.hook.tox_addoption(parser=parser) + + # parse command line options + option = parser.parse_args(args) + interpreters = tox.interpreters.Interpreters(hook=pm.hook) + config = Config(pluginmanager=pm, option=option, interpreters=interpreters) + + # parse ini file basename = config.option.configfile if os.path.isabs(basename): inipath = py.path.local(basename) @@ -52,6 +77,10 @@ exn = sys.exc_info()[1] # Use stdout to match test expectations py.builtin.print_("ERROR: " + str(exn)) + + # post process config object + pm.hook.tox_configure(config=config) + return config @@ -63,10 +92,8 @@ class VersionAction(argparse.Action): def __call__(self, argparser, *args, **kwargs): - name = argparser.pkgname - mod = __import__(name) - version = mod.__version__ - py.builtin.print_("%s imported from %s" % (version, mod.__file__)) + version = tox.__version__ + py.builtin.print_("%s imported from %s" % (version, tox.__file__)) raise SystemExit(0) @@ -78,10 +105,9 @@ setattr(namespace, self.dest, 0) -def prepare_parse(pkgname): - parser = argparse.ArgumentParser(description=__doc__,) + at hookimpl +def tox_addoption(parser): # formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser.pkgname = pkgname parser.add_argument("--version", nargs=0, action=VersionAction, dest="version", help="report version information to stdout.") @@ -153,10 +179,12 @@ class Config(object): - def __init__(self): + def __init__(self, pluginmanager, option, interpreters): self.envconfigs = {} self.invocationcwd = py.path.local() - self.interpreters = Interpreters() + self.interpreters = interpreters + self.pluginmanager = pluginmanager + self.option = option @property def homedir(self): @@ -192,10 +220,14 @@ def envsitepackagesdir(self): self.getsupportedinterpreter() # for throwing exceptions x = self.config.interpreters.get_sitepackagesdir( - info=self._basepython_info, + info=self.python_info, envdir=self.envdir) return x + @property + def python_info(self): + return self.config.interpreters.get_info(self.basepython) + def getsupportedinterpreter(self): if sys.platform == "win32" and self.basepython and \ "jython" in self.basepython: @@ -356,7 +388,7 @@ bp = next((default_factors[f] for f in factors if f in default_factors), sys.executable) vc.basepython = reader.getdefault(section, "basepython", bp) - vc._basepython_info = config.interpreters.get_info(vc.basepython) + reader.addsubstitutions(envdir=vc.envdir, envname=vc.envname, envbindir=vc.envbindir, envpython=vc.envpython, envsitepackagesdir=vc.envsitepackagesdir) diff -r 868a79b11ff5fc09b4d13ab7641fb47c77780a91 -r cc1933175162c4bb01669912a57df6fba4be5780 tox/_venv.py --- a/tox/_venv.py +++ b/tox/_venv.py @@ -143,7 +143,7 @@ self.envconfig.deps, v) def _getliveconfig(self): - python = self.envconfig._basepython_info.executable + python = self.envconfig.python_info.executable md5 = getdigest(python) version = tox.__version__ sitepackages = self.envconfig.sitepackages diff -r 868a79b11ff5fc09b4d13ab7641fb47c77780a91 -r cc1933175162c4bb01669912a57df6fba4be5780 tox/interpreters.py --- a/tox/interpreters.py +++ b/tox/interpreters.py @@ -2,12 +2,14 @@ import py import re import inspect +from tox import hookimpl class Interpreters: - def __init__(self): + def __init__(self, hook): self.name2executable = {} self.executable2info = {} + self.hook = hook def get_executable(self, name): """ return path object to the executable for the given @@ -18,8 +20,9 @@ try: return self.name2executable[name] except KeyError: - self.name2executable[name] = e = find_executable(name) - return e + exe = self.hook.tox_get_python_executable(name=name) + self.name2executable[name] = exe + return exe def get_info(self, name=None, executable=None): if name is None and executable is None: @@ -125,10 +128,33 @@ return "" % self.name if sys.platform != "win32": - def find_executable(name): + @hookimpl + def tox_get_python_executable(name): return py.path.local.sysfind(name) else: + @hookimpl + def tox_get_python_executable(name): + p = py.path.local.sysfind(name) + if p: + return p + actual = None + # Is this a standard PythonX.Y name? + m = re.match(r"python(\d)\.(\d)", name) + if m: + # The standard names are in predictable places. + actual = r"c:\python%s%s\python.exe" % m.groups() + if not actual: + actual = win32map.get(name, None) + if actual: + actual = py.path.local(actual) + if actual.check(): + return actual + # The standard executables can be found as a last resort via the + # Python launcher py.exe + if m: + return locate_via_py(*m.groups()) + # Exceptions to the usual windows mapping win32map = { 'python': sys.executable, @@ -149,27 +175,6 @@ if exe.check(): return exe - def find_executable(name): - p = py.path.local.sysfind(name) - if p: - return p - actual = None - # Is this a standard PythonX.Y name? - m = re.match(r"python(\d)\.(\d)", name) - if m: - # The standard names are in predictable places. - actual = r"c:\python%s%s\python.exe" % m.groups() - if not actual: - actual = win32map.get(name, None) - if actual: - actual = py.path.local(actual) - if actual.check(): - return actual - # The standard executables can be found as a last resort via the - # Python launcher py.exe - if m: - return locate_via_py(*m.groups()) - def pyinfo(): import sys Repository URL: https://bitbucket.org/hpk42/tox/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Fri May 8 13:19:13 2015 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Fri, 08 May 2015 11:19:13 -0000 Subject: [Pytest-commit] commit/tox: 2 new changesets Message-ID: <20150508111913.6675.76356@app14.ash-private.bitbucket.org> 2 new commits in tox: https://bitbucket.org/hpk42/tox/commits/850fb37c6062/ Changeset: 850fb37c6062 User: hpk42 Date: 2015-05-08 11:16:20+00:00 Summary: Backed out changeset cc1933175162 Affected #: 13 files diff -r cc1933175162c4bb01669912a57df6fba4be5780 -r 850fb37c60625112b2bf5ad21373d2804be98336 doc/conf.py --- a/doc/conf.py +++ b/doc/conf.py @@ -48,8 +48,8 @@ # built documents. # # The short X.Y version. -release = "2.0" -version = "2.0.0" +release = "1.9" +version = "1.9.0" # The full version, including alpha/beta/rc tags. # The language for content autogenerated by Sphinx. Refer to documentation diff -r cc1933175162c4bb01669912a57df6fba4be5780 -r 850fb37c60625112b2bf5ad21373d2804be98336 doc/index.txt --- a/doc/index.txt +++ b/doc/index.txt @@ -5,7 +5,7 @@ --------------------------------------------- ``tox`` aims to automate and standardize testing in Python. It is part -of a larger vision of easing the packaging, testing and release process +of a larger vision of easing the packaging, testing and release process of Python software. What is Tox? @@ -21,7 +21,6 @@ * acting as a frontend to Continuous Integration servers, greatly reducing boilerplate and merging CI and shell-based testing. - Basic example ----------------- @@ -63,10 +62,10 @@ - test-tool agnostic: runs py.test, nose or unittests in a uniform manner -* :doc:`(new in 2.0) plugin system ` to modify tox execution with simple hooks. +* supports :ref:`using different / multiple PyPI index servers ` * uses pip_ and setuptools_ by default. Experimental - support for configuring the installer command + support for configuring the installer command through :confval:`install_command=ARGV`. * **cross-Python compatible**: CPython-2.6, 2.7, 3.2 and higher, @@ -75,11 +74,11 @@ * **cross-platform**: Windows and Unix style environments * **integrates with continuous integration servers** like Jenkins_ - (formerly known as Hudson) and helps you to avoid boilerplatish + (formerly known as Hudson) and helps you to avoid boilerplatish and platform-specific build-step hacks. * **full interoperability with devpi**: is integrated with and - is used for testing in the devpi_ system, a versatile pypi + is used for testing in the devpi_ system, a versatile pypi index server and release managing tool. * **driven by a simple ini-style config file** @@ -90,9 +89,6 @@ * **professionally** :doc:`supported ` -* supports :ref:`using different / multiple PyPI index servers ` - - .. _pypy: http://pypy.org .. _`tox.ini`: :doc:configfile diff -r cc1933175162c4bb01669912a57df6fba4be5780 -r 850fb37c60625112b2bf5ad21373d2804be98336 doc/plugins.txt --- a/doc/plugins.txt +++ /dev/null @@ -1,69 +0,0 @@ -.. be in -*- rst -*- mode! - -tox plugins -=========== - -.. versionadded:: 2.0 - -With tox-2.0 a few aspects of tox running can be experimentally modified -by writing hook functions. We expect the list of hook function to grow -over time. - -writing a setuptools entrypoints plugin ---------------------------------------- - -If you have a ``tox_MYPLUGIN.py`` module you could use the following -rough ``setup.py`` to make it into a package which you can upload to the -Python packaging index:: - - # content of setup.py - from setuptools import setup - - if __name__ == "__main__": - setup( - name='tox-MYPLUGIN', - description='tox plugin decsription', - license="MIT license", - version='0.1', - py_modules=['tox_MYPLUGIN'], - entry_points={'tox': ['MYPLUGIN = tox_MYPLUGIN']}, - install_requires=['tox>=2.0'], - ) - -You can then install the plugin to develop it via:: - - pip install -e . - -and later publish it. - -The ``entry_points`` part allows tox to see your plugin during startup. - - -Writing hook implementations ----------------------------- - -A plugin module needs can define one or more hook implementation functions:: - - from tox import hookimpl - - @hookimpl - def tox_addoption(parser): - # add your own command line options - - - @hookimpl - def tox_configure(config): - # post process tox configuration after cmdline/ini file have - # been parsed - -If you put this into a module and make it pypi-installable with the ``tox`` -entry point you'll get your code executed as part of a tox run. - - - -tox hook specifications ----------------------------- - -.. automodule:: tox.hookspecs - :members: - diff -r cc1933175162c4bb01669912a57df6fba4be5780 -r 850fb37c60625112b2bf5ad21373d2804be98336 setup.py --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ def main(): version = sys.version_info[:2] - install_requires = ['virtualenv>=1.11.2', 'py>=1.4.17', 'pluggy>=0.3.0,<0.4.0'] + install_requires = ['virtualenv>=1.11.2', 'py>=1.4.17', ] if version < (2, 7): install_requires += ['argparse'] setup( diff -r cc1933175162c4bb01669912a57df6fba4be5780 -r 850fb37c60625112b2bf5ad21373d2804be98336 tests/test_config.py --- a/tests/test_config.py +++ b/tests/test_config.py @@ -6,6 +6,7 @@ import tox import tox._config from tox._config import * # noqa +from tox._config import _split_env from tox._venv import VirtualEnv @@ -1560,16 +1561,31 @@ ]) - at pytest.mark.parametrize("cmdline,envlist", [ - ("-e py26", ['py26']), - ("-e py26,py33", ['py26', 'py33']), - ("-e py26,py26", ['py26', 'py26']), - ("-e py26,py33 -e py33,py27", ['py26', 'py33', 'py33', 'py27']) -]) -def test_env_spec(cmdline, envlist): - args = cmdline.split() - config = parseconfig(args) - assert config.envlist == envlist +class TestArgumentParser: + + def test_dash_e_single_1(self): + parser = prepare_parse('testpkg') + args = parser.parse_args('-e py26'.split()) + envlist = _split_env(args.env) + assert envlist == ['py26'] + + def test_dash_e_single_2(self): + parser = prepare_parse('testpkg') + args = parser.parse_args('-e py26,py33'.split()) + envlist = _split_env(args.env) + assert envlist == ['py26', 'py33'] + + def test_dash_e_same(self): + parser = prepare_parse('testpkg') + args = parser.parse_args('-e py26,py26'.split()) + envlist = _split_env(args.env) + assert envlist == ['py26', 'py26'] + + def test_dash_e_combine(self): + parser = prepare_parse('testpkg') + args = parser.parse_args('-e py26,py25,py33 -e py33,py27'.split()) + envlist = _split_env(args.env) + assert envlist == ['py26', 'py25', 'py33', 'py33', 'py27'] class TestCommandParser: diff -r cc1933175162c4bb01669912a57df6fba4be5780 -r 850fb37c60625112b2bf5ad21373d2804be98336 tests/test_interpreters.py --- a/tests/test_interpreters.py +++ b/tests/test_interpreters.py @@ -3,13 +3,11 @@ import pytest from tox.interpreters import * # noqa -from tox._config import get_plugin_manager @pytest.fixture def interpreters(): - pm = get_plugin_manager() - return Interpreters(hook=pm.hook) + return Interpreters() @pytest.mark.skipif("sys.platform != 'win32'") @@ -30,8 +28,8 @@ assert locate_via_py('3', '2') == sys.executable -def test_tox_get_python_executable(): - p = tox_get_python_executable(sys.executable) +def test_find_executable(): + p = find_executable(sys.executable) assert p == py.path.local(sys.executable) for ver in [""] + "2.4 2.5 2.6 2.7 3.0 3.1 3.2 3.3".split(): name = "python%s" % ver @@ -44,7 +42,7 @@ else: if not py.path.local.sysfind(name): continue - p = tox_get_python_executable(name) + p = find_executable(name) assert p popen = py.std.subprocess.Popen([str(p), '-V'], stderr=py.std.subprocess.PIPE) @@ -57,7 +55,7 @@ def sysfind(x): return "hello" monkeypatch.setattr(py.path.local, "sysfind", sysfind) - t = tox_get_python_executable("qweqwe") + t = find_executable("qweqwe") assert t == "hello" diff -r cc1933175162c4bb01669912a57df6fba4be5780 -r 850fb37c60625112b2bf5ad21373d2804be98336 tests/test_venv.py --- a/tests/test_venv.py +++ b/tests/test_venv.py @@ -65,7 +65,7 @@ # assert Envconfig.toxworkdir in args assert venv.getcommandpath("easy_install", cwd=py.path.local()) interp = venv._getliveconfig().python - assert interp == venv.envconfig.python_info.executable + assert interp == venv.envconfig._basepython_info.executable assert venv.path_config.check(exists=False) diff -r cc1933175162c4bb01669912a57df6fba4be5780 -r 850fb37c60625112b2bf5ad21373d2804be98336 tox.ini --- a/tox.ini +++ b/tox.ini @@ -22,9 +22,7 @@ deps = pytest-flakes>=0.2 pytest-pep8 -commands = - py.test --flakes -m flakes tox tests - py.test --pep8 -m pep8 tox tests +commands = py.test -x --flakes --pep8 tox tests [testenv:dev] # required to make looponfail reload on every source code change diff -r cc1933175162c4bb01669912a57df6fba4be5780 -r 850fb37c60625112b2bf5ad21373d2804be98336 tox/__init__.py --- a/tox/__init__.py +++ b/tox/__init__.py @@ -1,8 +1,6 @@ # __version__ = '2.0.0.dev1' -from .hookspecs import hookspec, hookimpl # noqa - class exception: class Error(Exception): diff -r cc1933175162c4bb01669912a57df6fba4be5780 -r 850fb37c60625112b2bf5ad21373d2804be98336 tox/_cmdline.py --- a/tox/_cmdline.py +++ b/tox/_cmdline.py @@ -24,7 +24,7 @@ def main(args=None): try: - config = parseconfig(args) + config = parseconfig(args, 'tox') retcode = Session(config).runcommand() raise SystemExit(retcode) except KeyboardInterrupt: @@ -551,7 +551,8 @@ for envconfig in self.config.envconfigs.values(): self.report.line("[testenv:%s]" % envconfig.envname, bold=True) self.report.line(" basepython=%s" % envconfig.basepython) - self.report.line(" pythoninfo=%s" % (envconfig.python_info,)) + self.report.line(" _basepython_info=%s" % + envconfig._basepython_info) self.report.line(" envpython=%s" % envconfig.envpython) self.report.line(" envtmpdir=%s" % envconfig.envtmpdir) self.report.line(" envbindir=%s" % envconfig.envbindir) diff -r cc1933175162c4bb01669912a57df6fba4be5780 -r 850fb37c60625112b2bf5ad21373d2804be98336 tox/_config.py --- a/tox/_config.py +++ b/tox/_config.py @@ -8,10 +8,8 @@ import string import pkg_resources import itertools -import pluggy -import tox.interpreters -from tox import hookspecs +from tox.interpreters import Interpreters import py @@ -24,43 +22,20 @@ for version in '24,25,26,27,30,31,32,33,34,35'.split(','): default_factors['py' + version] = 'python%s.%s' % tuple(version) -hookimpl = pluggy.HookimplMarker("tox") - -def get_plugin_manager(): - # initialize plugin manager - pm = pluggy.PluginManager("tox") - pm.add_hookspecs(hookspecs) - pm.register(tox._config) - pm.register(tox.interpreters) - pm.load_setuptools_entrypoints("tox") - pm.check_pending() - return pm - - -def parseconfig(args=None): +def parseconfig(args=None, pkg=None): """ :param list[str] args: Optional list of arguments. :type pkg: str :rtype: :class:`Config` :raise SystemExit: toxinit file is not found """ - - pm = get_plugin_manager() - if args is None: args = sys.argv[1:] - - # prepare command line options - parser = argparse.ArgumentParser(description=__doc__) - pm.hook.tox_addoption(parser=parser) - - # parse command line options - option = parser.parse_args(args) - interpreters = tox.interpreters.Interpreters(hook=pm.hook) - config = Config(pluginmanager=pm, option=option, interpreters=interpreters) - - # parse ini file + parser = prepare_parse(pkg) + opts = parser.parse_args(args) + config = Config() + config.option = opts basename = config.option.configfile if os.path.isabs(basename): inipath = py.path.local(basename) @@ -77,10 +52,6 @@ exn = sys.exc_info()[1] # Use stdout to match test expectations py.builtin.print_("ERROR: " + str(exn)) - - # post process config object - pm.hook.tox_configure(config=config) - return config @@ -92,8 +63,10 @@ class VersionAction(argparse.Action): def __call__(self, argparser, *args, **kwargs): - version = tox.__version__ - py.builtin.print_("%s imported from %s" % (version, tox.__file__)) + name = argparser.pkgname + mod = __import__(name) + version = mod.__version__ + py.builtin.print_("%s imported from %s" % (version, mod.__file__)) raise SystemExit(0) @@ -105,9 +78,10 @@ setattr(namespace, self.dest, 0) - at hookimpl -def tox_addoption(parser): +def prepare_parse(pkgname): + parser = argparse.ArgumentParser(description=__doc__,) # formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.pkgname = pkgname parser.add_argument("--version", nargs=0, action=VersionAction, dest="version", help="report version information to stdout.") @@ -179,12 +153,10 @@ class Config(object): - def __init__(self, pluginmanager, option, interpreters): + def __init__(self): self.envconfigs = {} self.invocationcwd = py.path.local() - self.interpreters = interpreters - self.pluginmanager = pluginmanager - self.option = option + self.interpreters = Interpreters() @property def homedir(self): @@ -220,14 +192,10 @@ def envsitepackagesdir(self): self.getsupportedinterpreter() # for throwing exceptions x = self.config.interpreters.get_sitepackagesdir( - info=self.python_info, + info=self._basepython_info, envdir=self.envdir) return x - @property - def python_info(self): - return self.config.interpreters.get_info(self.basepython) - def getsupportedinterpreter(self): if sys.platform == "win32" and self.basepython and \ "jython" in self.basepython: @@ -388,7 +356,7 @@ bp = next((default_factors[f] for f in factors if f in default_factors), sys.executable) vc.basepython = reader.getdefault(section, "basepython", bp) - + vc._basepython_info = config.interpreters.get_info(vc.basepython) reader.addsubstitutions(envdir=vc.envdir, envname=vc.envname, envbindir=vc.envbindir, envpython=vc.envpython, envsitepackagesdir=vc.envsitepackagesdir) diff -r cc1933175162c4bb01669912a57df6fba4be5780 -r 850fb37c60625112b2bf5ad21373d2804be98336 tox/_venv.py --- a/tox/_venv.py +++ b/tox/_venv.py @@ -143,7 +143,7 @@ self.envconfig.deps, v) def _getliveconfig(self): - python = self.envconfig.python_info.executable + python = self.envconfig._basepython_info.executable md5 = getdigest(python) version = tox.__version__ sitepackages = self.envconfig.sitepackages diff -r cc1933175162c4bb01669912a57df6fba4be5780 -r 850fb37c60625112b2bf5ad21373d2804be98336 tox/interpreters.py --- a/tox/interpreters.py +++ b/tox/interpreters.py @@ -2,14 +2,12 @@ import py import re import inspect -from tox import hookimpl class Interpreters: - def __init__(self, hook): + def __init__(self): self.name2executable = {} self.executable2info = {} - self.hook = hook def get_executable(self, name): """ return path object to the executable for the given @@ -20,9 +18,8 @@ try: return self.name2executable[name] except KeyError: - exe = self.hook.tox_get_python_executable(name=name) - self.name2executable[name] = exe - return exe + self.name2executable[name] = e = find_executable(name) + return e def get_info(self, name=None, executable=None): if name is None and executable is None: @@ -128,13 +125,31 @@ return "" % self.name if sys.platform != "win32": - @hookimpl - def tox_get_python_executable(name): + def find_executable(name): return py.path.local.sysfind(name) else: - @hookimpl - def tox_get_python_executable(name): + # Exceptions to the usual windows mapping + win32map = { + 'python': sys.executable, + 'jython': "c:\jython2.5.1\jython.bat", + } + + def locate_via_py(v_maj, v_min): + ver = "-%s.%s" % (v_maj, v_min) + script = "import sys; print(sys.executable)" + py_exe = py.path.local.sysfind('py') + if py_exe: + try: + exe = py_exe.sysexec(ver, '-c', script).strip() + except py.process.cmdexec.Error: + exe = None + if exe: + exe = py.path.local(exe) + if exe.check(): + return exe + + def find_executable(name): p = py.path.local.sysfind(name) if p: return p @@ -155,26 +170,6 @@ if m: return locate_via_py(*m.groups()) - # Exceptions to the usual windows mapping - win32map = { - 'python': sys.executable, - 'jython': "c:\jython2.5.1\jython.bat", - } - - def locate_via_py(v_maj, v_min): - ver = "-%s.%s" % (v_maj, v_min) - script = "import sys; print(sys.executable)" - py_exe = py.path.local.sysfind('py') - if py_exe: - try: - exe = py_exe.sysexec(ver, '-c', script).strip() - except py.process.cmdexec.Error: - exe = None - if exe: - exe = py.path.local(exe) - if exe.check(): - return exe - def pyinfo(): import sys https://bitbucket.org/hpk42/tox/commits/581c658b9b39/ Changeset: 581c658b9b39 Branch: pluggy User: hpk42 Date: 2015-05-08 11:18:23+00:00 Summary: introduce little plugin system which allows to add command line options, perform extra configuration and determine how python executables are found (see hookspec) Affected #: 14 files diff -r 850fb37c60625112b2bf5ad21373d2804be98336 -r 581c658b9b3978c753213450cce5b8222100d232 doc/conf.py --- a/doc/conf.py +++ b/doc/conf.py @@ -48,8 +48,8 @@ # built documents. # # The short X.Y version. -release = "1.9" -version = "1.9.0" +release = "2.0" +version = "2.0.0" # The full version, including alpha/beta/rc tags. # The language for content autogenerated by Sphinx. Refer to documentation diff -r 850fb37c60625112b2bf5ad21373d2804be98336 -r 581c658b9b3978c753213450cce5b8222100d232 doc/index.txt --- a/doc/index.txt +++ b/doc/index.txt @@ -5,7 +5,7 @@ --------------------------------------------- ``tox`` aims to automate and standardize testing in Python. It is part -of a larger vision of easing the packaging, testing and release process +of a larger vision of easing the packaging, testing and release process of Python software. What is Tox? @@ -21,6 +21,7 @@ * acting as a frontend to Continuous Integration servers, greatly reducing boilerplate and merging CI and shell-based testing. + Basic example ----------------- @@ -62,10 +63,10 @@ - test-tool agnostic: runs py.test, nose or unittests in a uniform manner -* supports :ref:`using different / multiple PyPI index servers ` +* :doc:`(new in 2.0) plugin system ` to modify tox execution with simple hooks. * uses pip_ and setuptools_ by default. Experimental - support for configuring the installer command + support for configuring the installer command through :confval:`install_command=ARGV`. * **cross-Python compatible**: CPython-2.6, 2.7, 3.2 and higher, @@ -74,11 +75,11 @@ * **cross-platform**: Windows and Unix style environments * **integrates with continuous integration servers** like Jenkins_ - (formerly known as Hudson) and helps you to avoid boilerplatish + (formerly known as Hudson) and helps you to avoid boilerplatish and platform-specific build-step hacks. * **full interoperability with devpi**: is integrated with and - is used for testing in the devpi_ system, a versatile pypi + is used for testing in the devpi_ system, a versatile pypi index server and release managing tool. * **driven by a simple ini-style config file** @@ -89,6 +90,9 @@ * **professionally** :doc:`supported ` +* supports :ref:`using different / multiple PyPI index servers ` + + .. _pypy: http://pypy.org .. _`tox.ini`: :doc:configfile diff -r 850fb37c60625112b2bf5ad21373d2804be98336 -r 581c658b9b3978c753213450cce5b8222100d232 doc/plugins.txt --- /dev/null +++ b/doc/plugins.txt @@ -0,0 +1,69 @@ +.. be in -*- rst -*- mode! + +tox plugins +=========== + +.. versionadded:: 2.0 + +With tox-2.0 a few aspects of tox running can be experimentally modified +by writing hook functions. We expect the list of hook function to grow +over time. + +writing a setuptools entrypoints plugin +--------------------------------------- + +If you have a ``tox_MYPLUGIN.py`` module you could use the following +rough ``setup.py`` to make it into a package which you can upload to the +Python packaging index:: + + # content of setup.py + from setuptools import setup + + if __name__ == "__main__": + setup( + name='tox-MYPLUGIN', + description='tox plugin decsription', + license="MIT license", + version='0.1', + py_modules=['tox_MYPLUGIN'], + entry_points={'tox': ['MYPLUGIN = tox_MYPLUGIN']}, + install_requires=['tox>=2.0'], + ) + +You can then install the plugin to develop it via:: + + pip install -e . + +and later publish it. + +The ``entry_points`` part allows tox to see your plugin during startup. + + +Writing hook implementations +---------------------------- + +A plugin module needs can define one or more hook implementation functions:: + + from tox import hookimpl + + @hookimpl + def tox_addoption(parser): + # add your own command line options + + + @hookimpl + def tox_configure(config): + # post process tox configuration after cmdline/ini file have + # been parsed + +If you put this into a module and make it pypi-installable with the ``tox`` +entry point you'll get your code executed as part of a tox run. + + + +tox hook specifications +---------------------------- + +.. automodule:: tox.hookspecs + :members: + diff -r 850fb37c60625112b2bf5ad21373d2804be98336 -r 581c658b9b3978c753213450cce5b8222100d232 setup.py --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ def main(): version = sys.version_info[:2] - install_requires = ['virtualenv>=1.11.2', 'py>=1.4.17', ] + install_requires = ['virtualenv>=1.11.2', 'py>=1.4.17', 'pluggy>=0.3.0,<0.4.0'] if version < (2, 7): install_requires += ['argparse'] setup( diff -r 850fb37c60625112b2bf5ad21373d2804be98336 -r 581c658b9b3978c753213450cce5b8222100d232 tests/test_config.py --- a/tests/test_config.py +++ b/tests/test_config.py @@ -6,7 +6,6 @@ import tox import tox._config from tox._config import * # noqa -from tox._config import _split_env from tox._venv import VirtualEnv @@ -1561,31 +1560,16 @@ ]) -class TestArgumentParser: - - def test_dash_e_single_1(self): - parser = prepare_parse('testpkg') - args = parser.parse_args('-e py26'.split()) - envlist = _split_env(args.env) - assert envlist == ['py26'] - - def test_dash_e_single_2(self): - parser = prepare_parse('testpkg') - args = parser.parse_args('-e py26,py33'.split()) - envlist = _split_env(args.env) - assert envlist == ['py26', 'py33'] - - def test_dash_e_same(self): - parser = prepare_parse('testpkg') - args = parser.parse_args('-e py26,py26'.split()) - envlist = _split_env(args.env) - assert envlist == ['py26', 'py26'] - - def test_dash_e_combine(self): - parser = prepare_parse('testpkg') - args = parser.parse_args('-e py26,py25,py33 -e py33,py27'.split()) - envlist = _split_env(args.env) - assert envlist == ['py26', 'py25', 'py33', 'py33', 'py27'] + at pytest.mark.parametrize("cmdline,envlist", [ + ("-e py26", ['py26']), + ("-e py26,py33", ['py26', 'py33']), + ("-e py26,py26", ['py26', 'py26']), + ("-e py26,py33 -e py33,py27", ['py26', 'py33', 'py33', 'py27']) +]) +def test_env_spec(cmdline, envlist): + args = cmdline.split() + config = parseconfig(args) + assert config.envlist == envlist class TestCommandParser: diff -r 850fb37c60625112b2bf5ad21373d2804be98336 -r 581c658b9b3978c753213450cce5b8222100d232 tests/test_interpreters.py --- a/tests/test_interpreters.py +++ b/tests/test_interpreters.py @@ -3,11 +3,13 @@ import pytest from tox.interpreters import * # noqa +from tox._config import get_plugin_manager @pytest.fixture def interpreters(): - return Interpreters() + pm = get_plugin_manager() + return Interpreters(hook=pm.hook) @pytest.mark.skipif("sys.platform != 'win32'") @@ -28,8 +30,8 @@ assert locate_via_py('3', '2') == sys.executable -def test_find_executable(): - p = find_executable(sys.executable) +def test_tox_get_python_executable(): + p = tox_get_python_executable(sys.executable) assert p == py.path.local(sys.executable) for ver in [""] + "2.4 2.5 2.6 2.7 3.0 3.1 3.2 3.3".split(): name = "python%s" % ver @@ -42,7 +44,7 @@ else: if not py.path.local.sysfind(name): continue - p = find_executable(name) + p = tox_get_python_executable(name) assert p popen = py.std.subprocess.Popen([str(p), '-V'], stderr=py.std.subprocess.PIPE) @@ -55,7 +57,7 @@ def sysfind(x): return "hello" monkeypatch.setattr(py.path.local, "sysfind", sysfind) - t = find_executable("qweqwe") + t = tox_get_python_executable("qweqwe") assert t == "hello" diff -r 850fb37c60625112b2bf5ad21373d2804be98336 -r 581c658b9b3978c753213450cce5b8222100d232 tests/test_venv.py --- a/tests/test_venv.py +++ b/tests/test_venv.py @@ -65,7 +65,7 @@ # assert Envconfig.toxworkdir in args assert venv.getcommandpath("easy_install", cwd=py.path.local()) interp = venv._getliveconfig().python - assert interp == venv.envconfig._basepython_info.executable + assert interp == venv.envconfig.python_info.executable assert venv.path_config.check(exists=False) diff -r 850fb37c60625112b2bf5ad21373d2804be98336 -r 581c658b9b3978c753213450cce5b8222100d232 tox.ini --- a/tox.ini +++ b/tox.ini @@ -22,7 +22,9 @@ deps = pytest-flakes>=0.2 pytest-pep8 -commands = py.test -x --flakes --pep8 tox tests +commands = + py.test --flakes -m flakes tox tests + py.test --pep8 -m pep8 tox tests [testenv:dev] # required to make looponfail reload on every source code change diff -r 850fb37c60625112b2bf5ad21373d2804be98336 -r 581c658b9b3978c753213450cce5b8222100d232 tox/__init__.py --- a/tox/__init__.py +++ b/tox/__init__.py @@ -1,6 +1,8 @@ # __version__ = '2.0.0.dev1' +from .hookspecs import hookspec, hookimpl # noqa + class exception: class Error(Exception): diff -r 850fb37c60625112b2bf5ad21373d2804be98336 -r 581c658b9b3978c753213450cce5b8222100d232 tox/_cmdline.py --- a/tox/_cmdline.py +++ b/tox/_cmdline.py @@ -24,7 +24,7 @@ def main(args=None): try: - config = parseconfig(args, 'tox') + config = parseconfig(args) retcode = Session(config).runcommand() raise SystemExit(retcode) except KeyboardInterrupt: @@ -551,8 +551,7 @@ for envconfig in self.config.envconfigs.values(): self.report.line("[testenv:%s]" % envconfig.envname, bold=True) self.report.line(" basepython=%s" % envconfig.basepython) - self.report.line(" _basepython_info=%s" % - envconfig._basepython_info) + self.report.line(" pythoninfo=%s" % (envconfig.python_info,)) self.report.line(" envpython=%s" % envconfig.envpython) self.report.line(" envtmpdir=%s" % envconfig.envtmpdir) self.report.line(" envbindir=%s" % envconfig.envbindir) diff -r 850fb37c60625112b2bf5ad21373d2804be98336 -r 581c658b9b3978c753213450cce5b8222100d232 tox/_config.py --- a/tox/_config.py +++ b/tox/_config.py @@ -8,8 +8,10 @@ import string import pkg_resources import itertools +import pluggy -from tox.interpreters import Interpreters +import tox.interpreters +from tox import hookspecs import py @@ -22,20 +24,43 @@ for version in '24,25,26,27,30,31,32,33,34,35'.split(','): default_factors['py' + version] = 'python%s.%s' % tuple(version) +hookimpl = pluggy.HookimplMarker("tox") -def parseconfig(args=None, pkg=None): + +def get_plugin_manager(): + # initialize plugin manager + pm = pluggy.PluginManager("tox") + pm.add_hookspecs(hookspecs) + pm.register(tox._config) + pm.register(tox.interpreters) + pm.load_setuptools_entrypoints("tox") + pm.check_pending() + return pm + + +def parseconfig(args=None): """ :param list[str] args: Optional list of arguments. :type pkg: str :rtype: :class:`Config` :raise SystemExit: toxinit file is not found """ + + pm = get_plugin_manager() + if args is None: args = sys.argv[1:] - parser = prepare_parse(pkg) - opts = parser.parse_args(args) - config = Config() - config.option = opts + + # prepare command line options + parser = argparse.ArgumentParser(description=__doc__) + pm.hook.tox_addoption(parser=parser) + + # parse command line options + option = parser.parse_args(args) + interpreters = tox.interpreters.Interpreters(hook=pm.hook) + config = Config(pluginmanager=pm, option=option, interpreters=interpreters) + + # parse ini file basename = config.option.configfile if os.path.isabs(basename): inipath = py.path.local(basename) @@ -52,6 +77,10 @@ exn = sys.exc_info()[1] # Use stdout to match test expectations py.builtin.print_("ERROR: " + str(exn)) + + # post process config object + pm.hook.tox_configure(config=config) + return config @@ -63,10 +92,8 @@ class VersionAction(argparse.Action): def __call__(self, argparser, *args, **kwargs): - name = argparser.pkgname - mod = __import__(name) - version = mod.__version__ - py.builtin.print_("%s imported from %s" % (version, mod.__file__)) + version = tox.__version__ + py.builtin.print_("%s imported from %s" % (version, tox.__file__)) raise SystemExit(0) @@ -78,10 +105,9 @@ setattr(namespace, self.dest, 0) -def prepare_parse(pkgname): - parser = argparse.ArgumentParser(description=__doc__,) + at hookimpl +def tox_addoption(parser): # formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser.pkgname = pkgname parser.add_argument("--version", nargs=0, action=VersionAction, dest="version", help="report version information to stdout.") @@ -153,10 +179,12 @@ class Config(object): - def __init__(self): + def __init__(self, pluginmanager, option, interpreters): self.envconfigs = {} self.invocationcwd = py.path.local() - self.interpreters = Interpreters() + self.interpreters = interpreters + self.pluginmanager = pluginmanager + self.option = option @property def homedir(self): @@ -192,10 +220,14 @@ def envsitepackagesdir(self): self.getsupportedinterpreter() # for throwing exceptions x = self.config.interpreters.get_sitepackagesdir( - info=self._basepython_info, + info=self.python_info, envdir=self.envdir) return x + @property + def python_info(self): + return self.config.interpreters.get_info(self.basepython) + def getsupportedinterpreter(self): if sys.platform == "win32" and self.basepython and \ "jython" in self.basepython: @@ -356,7 +388,7 @@ bp = next((default_factors[f] for f in factors if f in default_factors), sys.executable) vc.basepython = reader.getdefault(section, "basepython", bp) - vc._basepython_info = config.interpreters.get_info(vc.basepython) + reader.addsubstitutions(envdir=vc.envdir, envname=vc.envname, envbindir=vc.envbindir, envpython=vc.envpython, envsitepackagesdir=vc.envsitepackagesdir) diff -r 850fb37c60625112b2bf5ad21373d2804be98336 -r 581c658b9b3978c753213450cce5b8222100d232 tox/_venv.py --- a/tox/_venv.py +++ b/tox/_venv.py @@ -143,7 +143,7 @@ self.envconfig.deps, v) def _getliveconfig(self): - python = self.envconfig._basepython_info.executable + python = self.envconfig.python_info.executable md5 = getdigest(python) version = tox.__version__ sitepackages = self.envconfig.sitepackages diff -r 850fb37c60625112b2bf5ad21373d2804be98336 -r 581c658b9b3978c753213450cce5b8222100d232 tox/hookspecs.py --- /dev/null +++ b/tox/hookspecs.py @@ -0,0 +1,28 @@ +""" Hook specifications for tox. + +""" + +from pluggy import HookspecMarker, HookimplMarker + +hookspec = HookspecMarker("tox") +hookimpl = HookimplMarker("tox") + + + at hookspec +def tox_addoption(parser): + """ add command line options to the argparse-style parser object.""" + + + at hookspec +def tox_configure(config): + """ called after command line options have been parsed and the ini-file has + been read. Please be aware that the config object layout may change as its + API was not designed yet wrt to providing stability (it was an internal + thing purely before tox-2.0). """ + + + at hookspec(firstresult=True) +def tox_get_python_executable(name): + """ return a python executable for the given python base name. + The first plugin/hook which returns an executable path will determine it. + """ diff -r 850fb37c60625112b2bf5ad21373d2804be98336 -r 581c658b9b3978c753213450cce5b8222100d232 tox/interpreters.py --- a/tox/interpreters.py +++ b/tox/interpreters.py @@ -2,12 +2,14 @@ import py import re import inspect +from tox import hookimpl class Interpreters: - def __init__(self): + def __init__(self, hook): self.name2executable = {} self.executable2info = {} + self.hook = hook def get_executable(self, name): """ return path object to the executable for the given @@ -18,8 +20,9 @@ try: return self.name2executable[name] except KeyError: - self.name2executable[name] = e = find_executable(name) - return e + exe = self.hook.tox_get_python_executable(name=name) + self.name2executable[name] = exe + return exe def get_info(self, name=None, executable=None): if name is None and executable is None: @@ -125,10 +128,33 @@ return "" % self.name if sys.platform != "win32": - def find_executable(name): + @hookimpl + def tox_get_python_executable(name): return py.path.local.sysfind(name) else: + @hookimpl + def tox_get_python_executable(name): + p = py.path.local.sysfind(name) + if p: + return p + actual = None + # Is this a standard PythonX.Y name? + m = re.match(r"python(\d)\.(\d)", name) + if m: + # The standard names are in predictable places. + actual = r"c:\python%s%s\python.exe" % m.groups() + if not actual: + actual = win32map.get(name, None) + if actual: + actual = py.path.local(actual) + if actual.check(): + return actual + # The standard executables can be found as a last resort via the + # Python launcher py.exe + if m: + return locate_via_py(*m.groups()) + # Exceptions to the usual windows mapping win32map = { 'python': sys.executable, @@ -149,27 +175,6 @@ if exe.check(): return exe - def find_executable(name): - p = py.path.local.sysfind(name) - if p: - return p - actual = None - # Is this a standard PythonX.Y name? - m = re.match(r"python(\d)\.(\d)", name) - if m: - # The standard names are in predictable places. - actual = r"c:\python%s%s\python.exe" % m.groups() - if not actual: - actual = win32map.get(name, None) - if actual: - actual = py.path.local(actual) - if actual.check(): - return actual - # The standard executables can be found as a last resort via the - # Python launcher py.exe - if m: - return locate_via_py(*m.groups()) - def pyinfo(): import sys Repository URL: https://bitbucket.org/hpk42/tox/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Fri May 8 13:20:34 2015 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Fri, 08 May 2015 11:20:34 -0000 Subject: [Pytest-commit] commit/tox: 15 new changesets Message-ID: <20150508112034.4565.86121@app11.ash-private.bitbucket.org> 15 new commits in tox: https://bitbucket.org/hpk42/tox/commits/27418a76196e/ Changeset: 27418a76196e Branch: stevepiercy/grammar-fixes-1430426650165 User: hpk42 Date: 2015-05-08 11:20:17+00:00 Summary: close branch Affected #: 0 files https://bitbucket.org/hpk42/tox/commits/2d8edfd458b6/ Changeset: 2d8edfd458b6 Branch: fix-doc-env User: hpk42 Date: 2015-05-08 11:20:18+00:00 Summary: close branch Affected #: 0 files https://bitbucket.org/hpk42/tox/commits/300cfbab1b3b/ Changeset: 300cfbab1b3b Branch: issue-235-fix-installpkg User: hpk42 Date: 2015-05-08 11:20:18+00:00 Summary: close branch Affected #: 0 files https://bitbucket.org/hpk42/tox/commits/abe50437ecb9/ Changeset: abe50437ecb9 Branch: fix-issue-120-commands-subs User: hpk42 Date: 2015-05-08 11:20:18+00:00 Summary: close branch Affected #: 0 files https://bitbucket.org/hpk42/tox/commits/0356d8af5408/ Changeset: 0356d8af5408 Branch: file-instead-of-pipe User: hpk42 Date: 2015-05-08 11:20:18+00:00 Summary: close branch Affected #: 0 files https://bitbucket.org/hpk42/tox/commits/8027efc917b0/ Changeset: 8027efc917b0 Branch: complex-projname-initproj User: hpk42 Date: 2015-05-08 11:20:19+00:00 Summary: close branch Affected #: 0 files https://bitbucket.org/hpk42/tox/commits/9092a5194fc8/ Changeset: 9092a5194fc8 Branch: fix-force-dep-with-reqs-file-with-pip User: hpk42 Date: 2015-05-08 11:20:19+00:00 Summary: close branch Affected #: 0 files https://bitbucket.org/hpk42/tox/commits/9071937b5d45/ Changeset: 9071937b5d45 Branch: fix-force-dep-with-reqs-file User: hpk42 Date: 2015-05-08 11:20:19+00:00 Summary: close branch Affected #: 0 files https://bitbucket.org/hpk42/tox/commits/7531408b542f/ Changeset: 7531408b542f Branch: generative-example User: hpk42 Date: 2015-05-08 11:20:19+00:00 Summary: close branch Affected #: 0 files https://bitbucket.org/hpk42/tox/commits/454f32ecc9eb/ Changeset: 454f32ecc9eb Branch: issue198 User: hpk42 Date: 2015-05-08 11:20:20+00:00 Summary: close branch Affected #: 0 files https://bitbucket.org/hpk42/tox/commits/04c975b769bd/ Changeset: 04c975b769bd Branch: lessen-factor-use-check User: hpk42 Date: 2015-05-08 11:20:20+00:00 Summary: close branch Affected #: 0 files https://bitbucket.org/hpk42/tox/commits/12ac34519de0/ Changeset: 12ac34519de0 Branch: add-py35 User: hpk42 Date: 2015-05-08 11:20:20+00:00 Summary: close branch Affected #: 0 files https://bitbucket.org/hpk42/tox/commits/c2d75c3eefab/ Changeset: c2d75c3eefab Branch: echo-captured-output User: hpk42 Date: 2015-05-08 11:20:20+00:00 Summary: close branch Affected #: 0 files https://bitbucket.org/hpk42/tox/commits/346791fe939d/ Changeset: 346791fe939d Branch: tomviner/typo-extra-in-docs-basictxt--1415645866375 User: hpk42 Date: 2015-05-08 11:20:21+00:00 Summary: close branch Affected #: 0 files https://bitbucket.org/hpk42/tox/commits/18c2114ec338/ Changeset: 18c2114ec338 Branch: tox-1.8-maint User: hpk42 Date: 2015-05-08 11:20:21+00:00 Summary: close branch Affected #: 0 files Repository URL: https://bitbucket.org/hpk42/tox/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From issues-reply at bitbucket.org Fri May 8 16:05:12 2015 From: issues-reply at bitbucket.org (Dima Tisnek) Date: Fri, 08 May 2015 14:05:12 -0000 Subject: [Pytest-commit] Issue #740: internal error when parametrising using yield functools.partial (pytest-dev/pytest) Message-ID: <20150508140512.21305.4159@app13.ash-private.bitbucket.org> New issue 740: internal error when parametrising using yield functools.partial https://bitbucket.org/pytest-dev/pytest/issue/740/internal-error-when-parametrising-using Dima Tisnek: Traceback is far below. Unfortunately I cannot yet create a minimum reproducible example, because everything works, if I remove propitiatory code. Note that all tests pass (I can remove all asserts even), but there's still an internal error. Below is the outline of the test: ``` #!python import functools backup = [] def setup(): backup[:] = secret, secret, secret secret.init() def teardown(): secret, secret, secret = backup[:] def test_one(foo=123): tmp = secret.secretObject() tmp.init(...) def test_all(): for somearg in range(10): yield functools.partial(test_one, somearg) ``` ``` #!python (plc)[dima at bmg 18src]$ py.test -v test_foo.py ================================================================================== test session starts ================================================================================== platform linux2 -- Python 2.7.9 -- py-1.4.27 -- pytest-2.7.0 -- /dima/plc/bin/python2 rootdir: /dima/tmp/sync_18/module/sync/python/src, inifile: plugins: incremental, ipdb, cov, random, xdist collected 6 items test_foo.py::test_convert_and_filter_external_simple PASSED [location] INTERNALERROR> Traceback (most recent call last): INTERNALERROR> File "/dima/plc/lib/python2.7/site-packages/_pytest/main.py", line 84, in wrap_session INTERNALERROR> doit(config, session) INTERNALERROR> File "/dima/plc/lib/python2.7/site-packages/_pytest/main.py", line 122, in _main INTERNALERROR> config.hook.pytest_runtestloop(session=session) INTERNALERROR> File "/dima/plc/lib/python2.7/site-packages/_pytest/core.py", line 521, in __call__ INTERNALERROR> return self._docall(self.methods, kwargs) INTERNALERROR> File "/dima/plc/lib/python2.7/site-packages/_pytest/core.py", line 528, in _docall INTERNALERROR> firstresult=self.firstresult).execute() INTERNALERROR> File "/dima/plc/lib/python2.7/site-packages/_pytest/core.py", line 394, in execute INTERNALERROR> res = method(*args) INTERNALERROR> File "/dima/plc/lib/python2.7/site-packages/_pytest/main.py", line 142, in pytest_runtestloop INTERNALERROR> item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem) INTERNALERROR> File "/dima/plc/lib/python2.7/site-packages/_pytest/core.py", line 521, in __call__ INTERNALERROR> return self._docall(self.methods, kwargs) INTERNALERROR> File "/dima/plc/lib/python2.7/site-packages/_pytest/core.py", line 528, in _docall INTERNALERROR> firstresult=self.firstresult).execute() INTERNALERROR> File "/dima/plc/lib/python2.7/site-packages/_pytest/core.py", line 393, in execute INTERNALERROR> return wrapped_call(method(*args), self.execute) INTERNALERROR> File "/dima/plc/lib/python2.7/site-packages/_pytest/core.py", line 113, in wrapped_call INTERNALERROR> return call_outcome.get_result() INTERNALERROR> File "/dima/plc/lib/python2.7/site-packages/_pytest/core.py", line 138, in get_result INTERNALERROR> py.builtin._reraise(*ex) INTERNALERROR> File "/dima/plc/lib/python2.7/site-packages/_pytest/core.py", line 123, in __init__ INTERNALERROR> self.result = func() INTERNALERROR> File "/dima/plc/lib/python2.7/site-packages/_pytest/core.py", line 394, in execute INTERNALERROR> res = method(*args) INTERNALERROR> File "/dima/plc/lib/python2.7/site-packages/_pytest/runner.py", line 65, in pytest_runtest_protocol INTERNALERROR> runtestprotocol(item, nextitem=nextitem) INTERNALERROR> File "/dima/plc/lib/python2.7/site-packages/_pytest/runner.py", line 75, in runtestprotocol INTERNALERROR> reports.append(call_and_report(item, "call", log)) INTERNALERROR> File "/dima/plc/lib/python2.7/site-packages/_pytest/runner.py", line 121, in call_and_report INTERNALERROR> report = hook.pytest_runtest_makereport(item=item, call=call) INTERNALERROR> File "/dima/plc/lib/python2.7/site-packages/_pytest/core.py", line 521, in __call__ INTERNALERROR> return self._docall(self.methods, kwargs) INTERNALERROR> File "/dima/plc/lib/python2.7/site-packages/_pytest/core.py", line 528, in _docall INTERNALERROR> firstresult=self.firstresult).execute() INTERNALERROR> File "/dima/plc/lib/python2.7/site-packages/_pytest/core.py", line 393, in execute INTERNALERROR> return wrapped_call(method(*args), self.execute) INTERNALERROR> File "/dima/plc/lib/python2.7/site-packages/_pytest/core.py", line 109, in wrapped_call INTERNALERROR> wrap_controller.send(call_outcome) INTERNALERROR> File "/dima/plc/lib/python2.7/site-packages/_pytest/skipping.py", line 157, in pytest_runtest_makereport INTERNALERROR> rep = outcome.get_result() INTERNALERROR> File "/dima/plc/lib/python2.7/site-packages/_pytest/core.py", line 138, in get_result INTERNALERROR> py.builtin._reraise(*ex) INTERNALERROR> File "/dima/plc/lib/python2.7/site-packages/_pytest/core.py", line 123, in __init__ INTERNALERROR> self.result = func() INTERNALERROR> File "/dima/plc/lib/python2.7/site-packages/_pytest/core.py", line 394, in execute INTERNALERROR> res = method(*args) INTERNALERROR> File "/dima/plc/lib/python2.7/site-packages/_pytest/runner.py", line 224, in pytest_runtest_makereport INTERNALERROR> longrepr = item.repr_failure(excinfo) INTERNALERROR> File "/dima/plc/lib/python2.7/site-packages/_pytest/python.py", line 625, in repr_failure INTERNALERROR> return self._repr_failure_py(excinfo, style=style) INTERNALERROR> File "/dima/plc/lib/python2.7/site-packages/_pytest/python.py", line 618, in _repr_failure_py INTERNALERROR> style=style) INTERNALERROR> File "/dima/plc/lib/python2.7/site-packages/_pytest/main.py", line 397, in _repr_failure_py INTERNALERROR> self._prunetraceback(excinfo) INTERNALERROR> File "/dima/plc/lib/python2.7/site-packages/_pytest/python.py", line 597, in _prunetraceback INTERNALERROR> code = py.code.Code(self.obj) INTERNALERROR> File "/dima/plc/lib/python2.7/site-packages/py/_code/code.py", line 24, in __init__ INTERNALERROR> raise TypeError("not a code object: %r" %(rawcode,)) INTERNALERROR> TypeError: not a code object: =============================================================================== 1 passed in 0.20 seconds ================================================================================ ``` From issues-reply at bitbucket.org Fri May 8 20:10:53 2015 From: issues-reply at bitbucket.org (Daniel Hahler) Date: Fri, 08 May 2015 18:10:53 -0000 Subject: [Pytest-commit] Issue #243: Regression with factor support and non-existing env (hpk42/tox) Message-ID: <20150508181053.31121.1757@app02.ash-private.bitbucket.org> New issue 243: Regression with factor support and non-existing env https://bitbucket.org/hpk42/tox/issue/243/regression-with-factor-support-and-non Daniel Hahler: There's a regression with factor support, which will happily run a non-existing env, if there are envs with that prefix: % tox -e checkqa GLOB sdist-make: ?/pytest-django/setup.py checkqa create: ?/pytest-django/.tox/checkqa checkqa inst: ?/pytest-django/.tox/dist/pytest-django-2.8.0.zip checkqa runtests: PYTHONHASHSEED='1593817085' ________________________________________________________________________________ summary ________________________________________________________________________________ checkqa: commands succeeded congratulations :) However, "checkqa" does not exist, but only multiple "checkqa-pythonX.X" envs. This is with pytest-django's tox.ini (https://raw.githubusercontent.com/pytest-dev/pytest-django/master/tox.ini). From issues-reply at bitbucket.org Sat May 9 12:50:09 2015 From: issues-reply at bitbucket.org (Daniel Hahler) Date: Sat, 09 May 2015 10:50:09 -0000 Subject: [Pytest-commit] Issue #741: Make "running ..." output from runpytest copy'n'pastable (pytest-dev/pytest) Message-ID: <20150509105009.7124.55467@app05.ash-private.bitbucket.org> New issue 741: Make "running ..." output from runpytest copy'n'pastable https://bitbucket.org/pytest-dev/pytest/issue/741/make-running-output-from-runpytest Daniel Hahler: Currently it says: ``` #! ------------------------------------------------------------------------- Captured stdout call -------------------------------------------------------------------------- running ['?/pytest-django/bin/python3.4', '?/pytest-django/lib/python3.4/site-packages/pytest.py', '--basetemp=/tmp/pytest-46/testdir/test_unittest_interaction0/runpytest-0', '-v', '--reuse-db'] curdir= /tmp/pytest-46/testdir/test_unittest_interaction0 ``` It would be nice, if this could be copied for pasting more easily: running: ?/pytest-django/bin/python3.4 ?/pytest-django/lib/python3.4/site-packages/pytest.py --basetemp=/tmp/pytest-46/testdir/test_unittest_interaction0/runpytest-0 -v --reuse-db in: /tmp/pytest-46/testdir/test_unittest_interaction0 You might want to use `shlex.quote` here (if there isn't a function like this in pylib/pytest already). This would allow for easily running the inner test. From commits-noreply at bitbucket.org Sun May 10 16:13:05 2015 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sun, 10 May 2015 14:13:05 -0000 Subject: [Pytest-commit] commit/tox: hpk42: make tox_get_python_executable hook receive "envconfig" instead of just basepython setting Message-ID: <20150510141305.12782.53850@app02.ash-private.bitbucket.org> 1 new commit in tox: https://bitbucket.org/hpk42/tox/commits/5bf717febabd/ Changeset: 5bf717febabd Branch: pluggy User: hpk42 Date: 2015-05-10 14:12:52+00:00 Summary: make tox_get_python_executable hook receive "envconfig" instead of just basepython setting Affected #: 5 files diff -r 581c658b9b3978c753213450cce5b8222100d232 -r 5bf717febabda1de4f8db338e983832d54c25e59 tests/test_interpreters.py --- a/tests/test_interpreters.py +++ b/tests/test_interpreters.py @@ -31,7 +31,10 @@ def test_tox_get_python_executable(): - p = tox_get_python_executable(sys.executable) + class envconfig: + basepython = sys.executable + envname = "pyxx" + p = tox_get_python_executable(envconfig) assert p == py.path.local(sys.executable) for ver in [""] + "2.4 2.5 2.6 2.7 3.0 3.1 3.2 3.3".split(): name = "python%s" % ver @@ -44,7 +47,8 @@ else: if not py.path.local.sysfind(name): continue - p = tox_get_python_executable(name) + envconfig.basepython = name + p = tox_get_python_executable(envconfig) assert p popen = py.std.subprocess.Popen([str(p), '-V'], stderr=py.std.subprocess.PIPE) @@ -57,7 +61,12 @@ def sysfind(x): return "hello" monkeypatch.setattr(py.path.local, "sysfind", sysfind) - t = tox_get_python_executable("qweqwe") + + class envconfig: + basepython = "1lk23j" + envname = "pyxx" + + t = tox_get_python_executable(envconfig) assert t == "hello" @@ -71,31 +80,33 @@ class TestInterpreters: - def test_get_info_self_exceptions(self, interpreters): - pytest.raises(ValueError, lambda: - interpreters.get_info()) - pytest.raises(ValueError, lambda: - interpreters.get_info(name="12", executable="123")) + def test_get_executable(self, interpreters): + class envconfig: + basepython = sys.executable + envname = "pyxx" - def test_get_executable(self, interpreters): - x = interpreters.get_executable(sys.executable) + x = interpreters.get_executable(envconfig) assert x == sys.executable - assert not interpreters.get_executable("12l3k1j23") - - def test_get_info__name(self, interpreters): - info = interpreters.get_info(executable=sys.executable) + info = interpreters.get_info(envconfig) assert info.version_info == tuple(sys.version_info) assert info.executable == sys.executable assert info.runnable - def test_get_info__name_not_exists(self, interpreters): - info = interpreters.get_info("qlwkejqwe") + def test_get_executable_no_exist(self, interpreters): + class envconfig: + basepython = "1lkj23" + envname = "pyxx" + assert not interpreters.get_executable(envconfig) + info = interpreters.get_info(envconfig) assert not info.version_info - assert info.name == "qlwkejqwe" + assert info.name == "1lkj23" assert not info.executable assert not info.runnable def test_get_sitepackagesdir_error(self, interpreters): - info = interpreters.get_info(sys.executable) + class envconfig: + basepython = sys.executable + envname = "123" + info = interpreters.get_info(envconfig) s = interpreters.get_sitepackagesdir(info, "") assert s diff -r 581c658b9b3978c753213450cce5b8222100d232 -r 5bf717febabda1de4f8db338e983832d54c25e59 tests/test_venv.py --- a/tests/test_venv.py +++ b/tests/test_venv.py @@ -35,6 +35,7 @@ py.test.raises(tox.exception.UnsupportedInterpreter, venv.getsupportedinterpreter) monkeypatch.undo() + monkeypatch.setattr(venv.envconfig, "envname", "py1") monkeypatch.setattr(venv.envconfig, 'basepython', 'notexistingpython') py.test.raises(tox.exception.InterpreterNotFound, venv.getsupportedinterpreter) @@ -42,7 +43,7 @@ # check that we properly report when no version_info is present info = NoInterpreterInfo(name=venv.name) info.executable = "something" - monkeypatch.setattr(config.interpreters, "get_info", lambda *args: info) + monkeypatch.setattr(config.interpreters, "get_info", lambda *args, **kw: info) pytest.raises(tox.exception.InvocationError, venv.getsupportedinterpreter) diff -r 581c658b9b3978c753213450cce5b8222100d232 -r 5bf717febabda1de4f8db338e983832d54c25e59 tox/_config.py --- a/tox/_config.py +++ b/tox/_config.py @@ -226,14 +226,14 @@ @property def python_info(self): - return self.config.interpreters.get_info(self.basepython) + return self.config.interpreters.get_info(envconfig=self) def getsupportedinterpreter(self): if sys.platform == "win32" and self.basepython and \ "jython" in self.basepython: raise tox.exception.UnsupportedInterpreter( "Jython/Windows does not support installing scripts") - info = self.config.interpreters.get_info(self.basepython) + info = self.config.interpreters.get_info(envconfig=self) if not info.executable: raise tox.exception.InterpreterNotFound(self.basepython) if not info.version_info: diff -r 581c658b9b3978c753213450cce5b8222100d232 -r 5bf717febabda1de4f8db338e983832d54c25e59 tox/hookspecs.py --- a/tox/hookspecs.py +++ b/tox/hookspecs.py @@ -22,7 +22,11 @@ @hookspec(firstresult=True) -def tox_get_python_executable(name): +def tox_get_python_executable(envconfig): """ return a python executable for the given python base name. The first plugin/hook which returns an executable path will determine it. + + ``envconfig`` is the testenv configuration which contains + per-testenv configuration, notably the ``.envname`` and ``.basepython`` + setting. """ diff -r 581c658b9b3978c753213450cce5b8222100d232 -r 5bf717febabda1de4f8db338e983832d54c25e59 tox/interpreters.py --- a/tox/interpreters.py +++ b/tox/interpreters.py @@ -11,26 +11,22 @@ self.executable2info = {} self.hook = hook - def get_executable(self, name): + def get_executable(self, envconfig): """ return path object to the executable for the given name (e.g. python2.6, python2.7, python etc.) if name is already an existing path, return name. If an interpreter cannot be found, return None. """ try: - return self.name2executable[name] + return self.name2executable[envconfig.envname] except KeyError: - exe = self.hook.tox_get_python_executable(name=name) - self.name2executable[name] = exe + exe = self.hook.tox_get_python_executable(envconfig=envconfig) + self.name2executable[envconfig.envname] = exe return exe - def get_info(self, name=None, executable=None): - if name is None and executable is None: - raise ValueError("need to specify name or executable") - if name: - if executable is not None: - raise ValueError("cannot specify both name, executable") - executable = self.get_executable(name) + def get_info(self, envconfig): + executable = self.get_executable(envconfig) + name = envconfig.basepython if not executable: return NoInterpreterInfo(name=name) try: @@ -129,12 +125,13 @@ if sys.platform != "win32": @hookimpl - def tox_get_python_executable(name): - return py.path.local.sysfind(name) + def tox_get_python_executable(envconfig): + return py.path.local.sysfind(envconfig.basepython) else: @hookimpl - def tox_get_python_executable(name): + def tox_get_python_executable(envconfig): + name = envconfig.basepython p = py.path.local.sysfind(name) if p: return p Repository URL: https://bitbucket.org/hpk42/tox/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Mon May 11 12:09:02 2015 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Mon, 11 May 2015 10:09:02 -0000 Subject: [Pytest-commit] commit/tox: hpk42: Merged in msabramo/tox/abort_by_default_when_a_command_fails (pull request #151) Message-ID: <20150511100902.11021.43144@app13.ash-private.bitbucket.org> 1 new commit in tox: https://bitbucket.org/hpk42/tox/commits/3d7925f2ef6c/ Changeset: 3d7925f2ef6c User: hpk42 Date: 2015-05-11 10:08:59+00:00 Summary: Merged in msabramo/tox/abort_by_default_when_a_command_fails (pull request #151) Abort command execution when a command fails by default Affected #: 6 files diff -r 850fb37c60625112b2bf5ad21373d2804be98336 -r 3d7925f2ef6c716de3bc3e84f649ce9b5f15b498 CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -17,6 +17,12 @@ If platform is set and doesn't match the platform spec in the test environment the test environment is ignored, no setup or tests are attempted. +.. (new) add per-venv "ignore_errors" setting, which defaults to False. + If ``True``, a non-zero exit code from one command will be ignored and + further commands will be executed (which was the default behavior in tox < + 2.0). If ``False`` (the default), then a non-zero exit code from one command + will abort execution of commands for that environment. + - remove the long-deprecated "distribute" option as it has no effect these days. - fix issue233: avoid hanging with tox-setuptools integration example. Thanks simonb. diff -r 850fb37c60625112b2bf5ad21373d2804be98336 -r 3d7925f2ef6c716de3bc3e84f649ce9b5f15b498 doc/config.txt --- a/doc/config.txt +++ b/doc/config.txt @@ -110,6 +110,26 @@ pip install {opts} {packages} +.. confval:: ignore_errors=True|False(default) + + .. versionadded:: 2.0 + + If ``True``, a non-zero exit code from one command will be ignored and + further commands will be executed (which was the default behavior in tox < + 2.0). If ``False`` (the default), then a non-zero exit code from one command + will abort execution of commands for that environment. + + It may be helpful to note that this setting is analogous to the ``-i`` or + ``ignore-errors`` option of GNU Make. A similar name was chosen to reflect the + similarity in function. + + Note that in tox 2.0, the default behavior of tox with respect to + treating errors from commands changed. Tox < 2.0 would ignore errors by + default. Tox >= 2.0 will abort on an error by default, which is safer and more + typical of CI and command execution tools, as it doesn't make sense to + run tests if installing some prerequisite failed and it doesn't make sense to + try to deploy if tests failed. + .. confval:: pip_pre=True|False(default) .. versionadded:: 1.9 diff -r 850fb37c60625112b2bf5ad21373d2804be98336 -r 3d7925f2ef6c716de3bc3e84f649ce9b5f15b498 tests/test_config.py --- a/tests/test_config.py +++ b/tests/test_config.py @@ -587,6 +587,7 @@ assert envconfig.changedir == config.setupdir assert envconfig.sitepackages is False assert envconfig.develop is False + assert envconfig.ignore_errors is False assert envconfig.envlogdir == envconfig.envdir.join("log") assert list(envconfig.setenv.keys()) == ['PYTHONHASHSEED'] hashseed = envconfig.setenv['PYTHONHASHSEED'] @@ -647,6 +648,15 @@ assert envconfig.changedir.basename == "xyz" assert envconfig.changedir == config.toxinidir.join("xyz") + def test_ignore_errors(self, tmpdir, newconfig): + config = newconfig(""" + [testenv] + ignore_errors=True + """) + assert len(config.envconfigs) == 1 + envconfig = config.envconfigs['python'] + assert envconfig.ignore_errors is True + def test_envbindir(self, tmpdir, newconfig): config = newconfig(""" [testenv] diff -r 850fb37c60625112b2bf5ad21373d2804be98336 -r 3d7925f2ef6c716de3bc3e84f649ce9b5f15b498 tox/_cmdline.py --- a/tox/_cmdline.py +++ b/tox/_cmdline.py @@ -157,7 +157,7 @@ raise tox.exception.InvocationError( "%s (see %s)" % (invoked, outpath), ret) else: - raise tox.exception.InvocationError("%r" % (invoked, )) + raise tox.exception.InvocationError("%r" % (invoked, ), ret) if not out and outpath: out = outpath.read() if hasattr(self, "commandlog"): diff -r 850fb37c60625112b2bf5ad21373d2804be98336 -r 3d7925f2ef6c716de3bc3e84f649ce9b5f15b498 tox/_config.py --- a/tox/_config.py +++ b/tox/_config.py @@ -442,6 +442,7 @@ section, "pip_pre", False) vc.skip_install = reader.getbool(section, "skip_install", False) + vc.ignore_errors = reader.getbool(section, "ignore_errors", False) return vc diff -r 850fb37c60625112b2bf5ad21373d2804be98336 -r 3d7925f2ef6c716de3bc3e84f649ce9b5f15b498 tox/_venv.py --- a/tox/_venv.py +++ b/tox/_venv.py @@ -361,6 +361,14 @@ val = sys.exc_info()[1] self.session.report.error(str(val)) self.status = "commands failed" + if not self.envconfig.ignore_errors: + self.session.report.error( + 'Stopping processing of commands for env %s ' + 'because `%s` failed with exit code %s' + % (self.name, + ' '.join([str(x) for x in argv]), + val.args[1])) + break # Don't process remaining commands except KeyboardInterrupt: self.status = "keyboardinterrupt" self.session.report.error(self.status) Repository URL: https://bitbucket.org/hpk42/tox/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Mon May 11 12:09:02 2015 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Mon, 11 May 2015 10:09:02 -0000 Subject: [Pytest-commit] commit/tox: 5 new changesets Message-ID: <20150511100902.612.13286@app02.ash-private.bitbucket.org> 5 new commits in tox: https://bitbucket.org/hpk42/tox/commits/74ab147d9778/ Changeset: 74ab147d9778 Branch: abort_by_default_when_a_command_fails User: msabramo Date: 2015-05-01 22:29:07+00:00 Summary: Abort command execution when a command fails by default It's pretty meaningless to keep executing commands when one command fails. So let's make it abort by default if any command returns a non-zero exit code. E.g.: $ tox py27 runtests: PYTHONHASHSEED='2154811636' py27 runtests: commands[0] | echo 1 1 py27 runtests: commands[1] | false ERROR: InvocationError: '/usr/bin/false' ERROR: Stopping processing of commands for env py27 because `/usr/bin/false` failed with exit code 1 _____________________________________________ summary ______________________________________________ ERROR: py27: commands failed Unless the `ignore_errors` setting is set to `True`, in which case, it keeps processing, like it did in older versions of tox. Affected #: 3 files diff -r 273d12589a2548f4a0603da9d82ade8a284d4196 -r 74ab147d9778f534c10c06cc60e58e07f0cd855b tox/_cmdline.py --- a/tox/_cmdline.py +++ b/tox/_cmdline.py @@ -157,7 +157,7 @@ raise tox.exception.InvocationError( "%s (see %s)" % (invoked, outpath), ret) else: - raise tox.exception.InvocationError("%r" % (invoked, )) + raise tox.exception.InvocationError("%r" % (invoked, ), ret) if not out and outpath: out = outpath.read() if hasattr(self, "commandlog"): diff -r 273d12589a2548f4a0603da9d82ade8a284d4196 -r 74ab147d9778f534c10c06cc60e58e07f0cd855b tox/_config.py --- a/tox/_config.py +++ b/tox/_config.py @@ -442,6 +442,7 @@ section, "pip_pre", False) vc.skip_install = reader.getbool(section, "skip_install", False) + vc.ignore_errors = reader.getbool(section, "ignore_errors", False) return vc diff -r 273d12589a2548f4a0603da9d82ade8a284d4196 -r 74ab147d9778f534c10c06cc60e58e07f0cd855b tox/_venv.py --- a/tox/_venv.py +++ b/tox/_venv.py @@ -361,6 +361,14 @@ val = sys.exc_info()[1] self.session.report.error(str(val)) self.status = "commands failed" + if not self.envconfig.ignore_errors: + self.session.report.error( + 'Stopping processing of commands for env %s ' + 'because `%s` failed with exit code %s' + % (self.name, + ' '.join([str(x) for x in argv]), + val.args[1])) + break # Don't process remaining commands except KeyboardInterrupt: self.status = "keyboardinterrupt" self.session.report.error(self.status) https://bitbucket.org/hpk42/tox/commits/38485f31e6a6/ Changeset: 38485f31e6a6 Branch: abort_by_default_when_a_command_fails User: msabramo Date: 2015-05-05 15:33:13+00:00 Summary: test_config: Add some tests for new ignore_errors setting Affected #: 1 file diff -r 74ab147d9778f534c10c06cc60e58e07f0cd855b -r 38485f31e6a6a413fc9332274d4eafc68e1fa400 tests/test_config.py --- a/tests/test_config.py +++ b/tests/test_config.py @@ -587,6 +587,7 @@ assert envconfig.changedir == config.setupdir assert envconfig.sitepackages is False assert envconfig.develop is False + assert envconfig.ignore_errors is False assert envconfig.envlogdir == envconfig.envdir.join("log") assert list(envconfig.setenv.keys()) == ['PYTHONHASHSEED'] hashseed = envconfig.setenv['PYTHONHASHSEED'] @@ -647,6 +648,15 @@ assert envconfig.changedir.basename == "xyz" assert envconfig.changedir == config.toxinidir.join("xyz") + def test_ignore_errors(self, tmpdir, newconfig): + config = newconfig(""" + [testenv] + ignore_errors=True + """) + assert len(config.envconfigs) == 1 + envconfig = config.envconfigs['python'] + assert envconfig.ignore_errors is True + def test_envbindir(self, tmpdir, newconfig): config = newconfig(""" [testenv] https://bitbucket.org/hpk42/tox/commits/7009b80c4bf6/ Changeset: 7009b80c4bf6 Branch: abort_by_default_when_a_command_fails User: msabramo Date: 2015-05-05 16:02:27+00:00 Summary: doc/config.txt: Document new ignore_errors setting Affected #: 1 file diff -r 38485f31e6a6a413fc9332274d4eafc68e1fa400 -r 7009b80c4bf62e44866cc484be110c56a1b13b83 doc/config.txt --- a/doc/config.txt +++ b/doc/config.txt @@ -110,6 +110,26 @@ pip install {opts} {packages} +.. confval:: ignore_errors=True|False(default) + + .. versionadded:: 2.0 + + If ``True``, a non-zero exit code from one command will be ignored and + further commands will be executed (which was the default behavior in tox < + 2.0). If ``False`` (the default), then a non-zero exit code from one command + will abort execution of commands for that environment. + + It may be helpful to note that this setting is analogous to the ``-i`` or + ``ignore-errors`` option of GNU Make. A similar name was chosen to reflect the + similarity in function. + + Note that in tox 2.0, the default behavior of tox with respect to + treating errors from commands changed. Tox < 2.0 would ignore errors by + default. Tox >= 2.0 will abort on an error by default, which is safer and more + typical of CI and command execution tools, as it doesn't make sense to + run tests if installing some prerequisite failed and it doesn't make sense to + try to deploy if tests failed. + .. confval:: pip_pre=True|False(default) .. versionadded:: 1.9 https://bitbucket.org/hpk42/tox/commits/376a0d33dc01/ Changeset: 376a0d33dc01 Branch: abort_by_default_when_a_command_fails User: msabramo Date: 2015-05-05 16:02:51+00:00 Summary: CHANGELOG: Document new "ignore_errors" setting Affected #: 1 file diff -r 7009b80c4bf62e44866cc484be110c56a1b13b83 -r 376a0d33dc016fdcf0ab12d94414b3a94369b0b3 CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -17,6 +17,12 @@ If platform is set and doesn't match the platform spec in the test environment the test environment is ignored, no setup or tests are attempted. +.. (new) add per-venv "ignore_errors" setting, which defaults to False. + If ``True``, a non-zero exit code from one command will be ignored and + further commands will be executed (which was the default behavior in tox < + 2.0). If ``False`` (the default), then a non-zero exit code from one command + will abort execution of commands for that environment. + - remove the long-deprecated "distribute" option as it has no effect these days. - fix issue233: avoid hanging with tox-setuptools integration example. Thanks simonb. https://bitbucket.org/hpk42/tox/commits/3d7925f2ef6c/ Changeset: 3d7925f2ef6c User: hpk42 Date: 2015-05-11 10:08:59+00:00 Summary: Merged in msabramo/tox/abort_by_default_when_a_command_fails (pull request #151) Abort command execution when a command fails by default Affected #: 6 files diff -r 850fb37c60625112b2bf5ad21373d2804be98336 -r 3d7925f2ef6c716de3bc3e84f649ce9b5f15b498 CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -17,6 +17,12 @@ If platform is set and doesn't match the platform spec in the test environment the test environment is ignored, no setup or tests are attempted. +.. (new) add per-venv "ignore_errors" setting, which defaults to False. + If ``True``, a non-zero exit code from one command will be ignored and + further commands will be executed (which was the default behavior in tox < + 2.0). If ``False`` (the default), then a non-zero exit code from one command + will abort execution of commands for that environment. + - remove the long-deprecated "distribute" option as it has no effect these days. - fix issue233: avoid hanging with tox-setuptools integration example. Thanks simonb. diff -r 850fb37c60625112b2bf5ad21373d2804be98336 -r 3d7925f2ef6c716de3bc3e84f649ce9b5f15b498 doc/config.txt --- a/doc/config.txt +++ b/doc/config.txt @@ -110,6 +110,26 @@ pip install {opts} {packages} +.. confval:: ignore_errors=True|False(default) + + .. versionadded:: 2.0 + + If ``True``, a non-zero exit code from one command will be ignored and + further commands will be executed (which was the default behavior in tox < + 2.0). If ``False`` (the default), then a non-zero exit code from one command + will abort execution of commands for that environment. + + It may be helpful to note that this setting is analogous to the ``-i`` or + ``ignore-errors`` option of GNU Make. A similar name was chosen to reflect the + similarity in function. + + Note that in tox 2.0, the default behavior of tox with respect to + treating errors from commands changed. Tox < 2.0 would ignore errors by + default. Tox >= 2.0 will abort on an error by default, which is safer and more + typical of CI and command execution tools, as it doesn't make sense to + run tests if installing some prerequisite failed and it doesn't make sense to + try to deploy if tests failed. + .. confval:: pip_pre=True|False(default) .. versionadded:: 1.9 diff -r 850fb37c60625112b2bf5ad21373d2804be98336 -r 3d7925f2ef6c716de3bc3e84f649ce9b5f15b498 tests/test_config.py --- a/tests/test_config.py +++ b/tests/test_config.py @@ -587,6 +587,7 @@ assert envconfig.changedir == config.setupdir assert envconfig.sitepackages is False assert envconfig.develop is False + assert envconfig.ignore_errors is False assert envconfig.envlogdir == envconfig.envdir.join("log") assert list(envconfig.setenv.keys()) == ['PYTHONHASHSEED'] hashseed = envconfig.setenv['PYTHONHASHSEED'] @@ -647,6 +648,15 @@ assert envconfig.changedir.basename == "xyz" assert envconfig.changedir == config.toxinidir.join("xyz") + def test_ignore_errors(self, tmpdir, newconfig): + config = newconfig(""" + [testenv] + ignore_errors=True + """) + assert len(config.envconfigs) == 1 + envconfig = config.envconfigs['python'] + assert envconfig.ignore_errors is True + def test_envbindir(self, tmpdir, newconfig): config = newconfig(""" [testenv] diff -r 850fb37c60625112b2bf5ad21373d2804be98336 -r 3d7925f2ef6c716de3bc3e84f649ce9b5f15b498 tox/_cmdline.py --- a/tox/_cmdline.py +++ b/tox/_cmdline.py @@ -157,7 +157,7 @@ raise tox.exception.InvocationError( "%s (see %s)" % (invoked, outpath), ret) else: - raise tox.exception.InvocationError("%r" % (invoked, )) + raise tox.exception.InvocationError("%r" % (invoked, ), ret) if not out and outpath: out = outpath.read() if hasattr(self, "commandlog"): diff -r 850fb37c60625112b2bf5ad21373d2804be98336 -r 3d7925f2ef6c716de3bc3e84f649ce9b5f15b498 tox/_config.py --- a/tox/_config.py +++ b/tox/_config.py @@ -442,6 +442,7 @@ section, "pip_pre", False) vc.skip_install = reader.getbool(section, "skip_install", False) + vc.ignore_errors = reader.getbool(section, "ignore_errors", False) return vc diff -r 850fb37c60625112b2bf5ad21373d2804be98336 -r 3d7925f2ef6c716de3bc3e84f649ce9b5f15b498 tox/_venv.py --- a/tox/_venv.py +++ b/tox/_venv.py @@ -361,6 +361,14 @@ val = sys.exc_info()[1] self.session.report.error(str(val)) self.status = "commands failed" + if not self.envconfig.ignore_errors: + self.session.report.error( + 'Stopping processing of commands for env %s ' + 'because `%s` failed with exit code %s' + % (self.name, + ' '.join([str(x) for x in argv]), + val.args[1])) + break # Don't process remaining commands except KeyboardInterrupt: self.status = "keyboardinterrupt" self.session.report.error(self.status) Repository URL: https://bitbucket.org/hpk42/tox/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Mon May 11 12:40:45 2015 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Mon, 11 May 2015 10:40:45 -0000 Subject: [Pytest-commit] commit/tox: hpk42: bump version Message-ID: <20150511104045.11613.36982@app13.ash-private.bitbucket.org> 1 new commit in tox: https://bitbucket.org/hpk42/tox/commits/ee38b60cb140/ Changeset: ee38b60cb140 User: hpk42 Date: 2015-05-11 10:39:05+00:00 Summary: bump version Affected #: 2 files diff -r 06bb44d51a4c4d02f643da800d47a7197cfab32c -r ee38b60cb1403506c703c30e31107fc594253cfb setup.py --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ description='virtualenv-based automation of test activities', long_description=open("README.rst").read(), url='http://tox.testrun.org/', - version='2.0.0.dev1', + version='2.0.0.dev2', license='http://opensource.org/licenses/MIT', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], author='holger krekel', diff -r 06bb44d51a4c4d02f643da800d47a7197cfab32c -r ee38b60cb1403506c703c30e31107fc594253cfb tox/__init__.py --- a/tox/__init__.py +++ b/tox/__init__.py @@ -1,5 +1,5 @@ # -__version__ = '2.0.0.dev1' +__version__ = '2.0.0.dev2' from .hookspecs import hookspec, hookimpl # noqa Repository URL: https://bitbucket.org/hpk42/tox/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From issues-reply at bitbucket.org Mon May 11 12:52:30 2015 From: issues-reply at bitbucket.org (Daniel Hahler) Date: Mon, 11 May 2015 10:52:30 -0000 Subject: [Pytest-commit] Issue #742: Difference of sys.path between running py.test and testdir.runpytest (pytest-dev/pytest) Message-ID: <20150511105230.12430.33642@app12.ash-private.bitbucket.org> New issue 742: Difference of sys.path between running py.test and testdir.runpytest https://bitbucket.org/pytest-dev/pytest/issue/742/difference-of-syspath-between-running Daniel Hahler: I've noticed that pytest's runpytest method fails when pies2overrides is installed in the virtualenv, which adds a reprlib.py file into site-packages. While this is clearly an [issue with pies](https://github.com/timothycrosley/pies/issues/44), pytest is triggering it by calling its script from this site-packages dir. % virtualenv venv % venv/bin/pip install pytest pies2overrides==2.6.5 # Simulate `testdir.runpytest`: % venv/bin/python venv/lib/python3.4/site-packages/pytest.py Maybe pytest could use another approach here to call itself? And/or something like `sys.path.pop(0)` in the script? I might be missing something here, of course, but it seems like the behaviour/setup of the Python path differs between running `py.test` and `runpytest`, which should be avoided. Output: % virtualenv venv Using base prefix '/home/daniel/.pyenv/versions/3.4.3' New python executable in venv/bin/python3.4 Also creating executable in venv/bin/python Installing setuptools, pip...done. % venv/bin/pip install pytest pies2overrides==2.6.5 Collecting pytest Using cached pytest-2.7.0.tar.gz Collecting pies2overrides==2.6.5 Using cached pies2overrides-2.6.5-py2.py3-none-any.whl Collecting py>=1.4.25 (from pytest) Using cached py-1.4.27-py2.py3-none-any.whl Collecting ipaddress (from pies2overrides==2.6.5) Using cached ipaddress-1.0.7.tar.gz Installing collected packages: py, pytest, ipaddress, pies2overrides Running setup.py install for pytest Running setup.py install for ipaddress Successfully installed ipaddress-1.0.7 pies2overrides-2.6.5 py-1.4.27 pytest-2.7.0 % venv/bin/python venv/lib/python3.4/site-packages/pytest.py Traceback (most recent call last): File "venv/lib/python3.4/site-packages/pytest.py", line 9, in import pytest File "/tmp/t/venv/lib/python3.4/site-packages/pytest.py", line 14, in from _pytest.config import main, UsageError, _preloadplugins, cmdline File "/tmp/t/venv/lib/python3.4/site-packages/_pytest/config.py", line 2, in import argparse File "/home/daniel/.pyenv/versions/3.4.3/lib/python3.4/argparse.py", line 86, in import collections as _collections File "/tmp/t/venv/lib/python3.4/collections/__init__.py", line 17, in from reprlib import recursive_repr as _recursive_repr File "/tmp/t/venv/lib/python3.4/site-packages/reprlib.py", line 3, in from repr import * ImportError: No module named 'repr' shell returned 1 From commits-noreply at bitbucket.org Mon May 11 12:36:05 2015 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Mon, 11 May 2015 10:36:05 -0000 Subject: [Pytest-commit] commit/tox: 6 new changesets Message-ID: <20150511103605.10667.20501@app08.ash-private.bitbucket.org> 6 new commits in tox: https://bitbucket.org/hpk42/tox/commits/1de999d6ecf9/ Changeset: 1de999d6ecf9 Branch: pluggy User: hpk42 Date: 2015-05-11 09:26:07+00:00 Summary: add changelog entry for pluggy Affected #: 1 file diff -r 5bf717febabda1de4f8db338e983832d54c25e59 -r 1de999d6ecf98715ce179636a522b86f5dd9b8dc CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -32,6 +32,9 @@ - fix issue240: allow to specify empty argument list without it being rewritten to ".". Thanks Daniel Hahler. +- introduce experimental (not much documented yet) plugin system + based on pytest's externalized "pluggy" system. + See tox/hookspecs.py for the current hooks. 1.9.2 ----------- https://bitbucket.org/hpk42/tox/commits/af0751f4403a/ Changeset: af0751f4403a Branch: abort_by_default_when_a_command_fails User: hpk42 Date: 2015-05-11 10:11:57+00:00 Summary: close branch Affected #: 0 files https://bitbucket.org/hpk42/tox/commits/7f1bcc90f673/ Changeset: 7f1bcc90f673 Branch: pluggy User: hpk42 Date: 2015-05-11 10:06:39+00:00 Summary: refactor testenv section parser to work by registering ini attributes at tox_addoption() time. Introduce new "--help-ini" or "--hi" option to show all testenv variables. Affected #: 7 files diff -r 1de999d6ecf98715ce179636a522b86f5dd9b8dc -r 7f1bcc90f673122444ce1efac01206da661c84ff CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -36,6 +36,10 @@ based on pytest's externalized "pluggy" system. See tox/hookspecs.py for the current hooks. +- introduce parser.add_testenv_attribute() to register an ini-variable + for testenv sections. Can be used from plugins through the + tox_add_option hook. + 1.9.2 ----------- diff -r 1de999d6ecf98715ce179636a522b86f5dd9b8dc -r 7f1bcc90f673122444ce1efac01206da661c84ff tests/test_config.py --- a/tests/test_config.py +++ b/tests/test_config.py @@ -18,7 +18,7 @@ assert config.toxworkdir.realpath() == tmpdir.join(".tox").realpath() assert config.envconfigs['py1'].basepython == sys.executable assert config.envconfigs['py1'].deps == [] - assert not config.envconfigs['py1'].platform + assert config.envconfigs['py1'].platform == ".*" def test_config_parsing_multienv(self, tmpdir, newconfig): config = newconfig([], """ @@ -92,12 +92,12 @@ """ Ensure correct parseini._is_same_dep is working with a few samples. """ - assert parseini._is_same_dep('pkg_hello-world3==1.0', 'pkg_hello-world3') - assert parseini._is_same_dep('pkg_hello-world3==1.0', 'pkg_hello-world3>=2.0') - assert parseini._is_same_dep('pkg_hello-world3==1.0', 'pkg_hello-world3>2.0') - assert parseini._is_same_dep('pkg_hello-world3==1.0', 'pkg_hello-world3<2.0') - assert parseini._is_same_dep('pkg_hello-world3==1.0', 'pkg_hello-world3<=2.0') - assert not parseini._is_same_dep('pkg_hello-world3==1.0', 'otherpkg>=2.0') + assert DepOption._is_same_dep('pkg_hello-world3==1.0', 'pkg_hello-world3') + assert DepOption._is_same_dep('pkg_hello-world3==1.0', 'pkg_hello-world3>=2.0') + assert DepOption._is_same_dep('pkg_hello-world3==1.0', 'pkg_hello-world3>2.0') + assert DepOption._is_same_dep('pkg_hello-world3==1.0', 'pkg_hello-world3<2.0') + assert DepOption._is_same_dep('pkg_hello-world3==1.0', 'pkg_hello-world3<=2.0') + assert not DepOption._is_same_dep('pkg_hello-world3==1.0', 'otherpkg>=2.0') class TestConfigPlatform: @@ -219,8 +219,8 @@ commands = echo {[section]key} """) - reader = IniReader(config._cfg) - x = reader.getargvlist("testenv", "commands") + reader = SectionReader("testenv", config._cfg) + x = reader.getargvlist("commands") assert x == [["echo", "whatever"]] def test_command_substitution_from_other_section_multiline(self, newconfig): @@ -244,8 +244,8 @@ # comment is omitted echo {[base]commands} """) - reader = IniReader(config._cfg) - x = reader.getargvlist("testenv", "commands") + reader = SectionReader("testenv", config._cfg) + x = reader.getargvlist("commands") assert x == [ "cmd1 param11 param12".split(), "cmd2 param21 param22".split(), @@ -256,16 +256,16 @@ class TestIniParser: - def test_getdefault_single(self, tmpdir, newconfig): + def test_getstring_single(self, tmpdir, newconfig): config = newconfig(""" [section] key=value """) - reader = IniReader(config._cfg) - x = reader.getdefault("section", "key") + reader = SectionReader("section", config._cfg) + x = reader.getstring("key") assert x == "value" - assert not reader.getdefault("section", "hello") - x = reader.getdefault("section", "hello", "world") + assert not reader.getstring("hello") + x = reader.getstring("hello", "world") assert x == "world" def test_missing_substitution(self, tmpdir, newconfig): @@ -273,40 +273,40 @@ [mydefault] key2={xyz} """) - reader = IniReader(config._cfg, fallbacksections=['mydefault']) + reader = SectionReader("mydefault", config._cfg, fallbacksections=['mydefault']) assert reader is not None - py.test.raises(tox.exception.ConfigError, - 'reader.getdefault("mydefault", "key2")') + with py.test.raises(tox.exception.ConfigError): + reader.getstring("key2") - def test_getdefault_fallback_sections(self, tmpdir, newconfig): + def test_getstring_fallback_sections(self, tmpdir, newconfig): config = newconfig(""" [mydefault] key2=value2 [section] key=value """) - reader = IniReader(config._cfg, fallbacksections=['mydefault']) - x = reader.getdefault("section", "key2") + reader = SectionReader("section", config._cfg, fallbacksections=['mydefault']) + x = reader.getstring("key2") assert x == "value2" - x = reader.getdefault("section", "key3") + x = reader.getstring("key3") assert not x - x = reader.getdefault("section", "key3", "world") + x = reader.getstring("key3", "world") assert x == "world" - def test_getdefault_substitution(self, tmpdir, newconfig): + def test_getstring_substitution(self, tmpdir, newconfig): config = newconfig(""" [mydefault] key2={value2} [section] key={value} """) - reader = IniReader(config._cfg, fallbacksections=['mydefault']) + reader = SectionReader("section", config._cfg, fallbacksections=['mydefault']) reader.addsubstitutions(value="newvalue", value2="newvalue2") - x = reader.getdefault("section", "key2") + x = reader.getstring("key2") assert x == "newvalue2" - x = reader.getdefault("section", "key3") + x = reader.getstring("key3") assert not x - x = reader.getdefault("section", "key3", "{value2}") + x = reader.getstring("key3", "{value2}") assert x == "newvalue2" def test_getlist(self, tmpdir, newconfig): @@ -316,9 +316,9 @@ item1 {item2} """) - reader = IniReader(config._cfg) + reader = SectionReader("section", config._cfg) reader.addsubstitutions(item1="not", item2="grr") - x = reader.getlist("section", "key2") + x = reader.getlist("key2") assert x == ['item1', 'grr'] def test_getdict(self, tmpdir, newconfig): @@ -328,28 +328,31 @@ key1=item1 key2={item2} """) - reader = IniReader(config._cfg) + reader = SectionReader("section", config._cfg) reader.addsubstitutions(item1="not", item2="grr") - x = reader.getdict("section", "key2") + x = reader.getdict("key2") assert 'key1' in x assert 'key2' in x assert x['key1'] == 'item1' assert x['key2'] == 'grr' - def test_getdefault_environment_substitution(self, monkeypatch, newconfig): + x = reader.getdict("key3", {1: 2}) + assert x == {1: 2} + + def test_getstring_environment_substitution(self, monkeypatch, newconfig): monkeypatch.setenv("KEY1", "hello") config = newconfig(""" [section] key1={env:KEY1} key2={env:KEY2} """) - reader = IniReader(config._cfg) - x = reader.getdefault("section", "key1") + reader = SectionReader("section", config._cfg) + x = reader.getstring("key1") assert x == "hello" - py.test.raises(tox.exception.ConfigError, - 'reader.getdefault("section", "key2")') + with py.test.raises(tox.exception.ConfigError): + reader.getstring("key2") - def test_getdefault_environment_substitution_with_default(self, monkeypatch, newconfig): + def test_getstring_environment_substitution_with_default(self, monkeypatch, newconfig): monkeypatch.setenv("KEY1", "hello") config = newconfig(""" [section] @@ -357,12 +360,12 @@ key2={env:KEY2:DEFAULT_VALUE} key3={env:KEY3:} """) - reader = IniReader(config._cfg) - x = reader.getdefault("section", "key1") + reader = SectionReader("section", config._cfg) + x = reader.getstring("key1") assert x == "hello" - x = reader.getdefault("section", "key2") + x = reader.getstring("key2") assert x == "DEFAULT_VALUE" - x = reader.getdefault("section", "key3") + x = reader.getstring("key3") assert x == "" def test_value_matches_section_substituion(self): @@ -373,15 +376,15 @@ assert is_section_substitution("{[setup]}") is None assert is_section_substitution("{[setup] commands}") is None - def test_getdefault_other_section_substitution(self, newconfig): + def test_getstring_other_section_substitution(self, newconfig): config = newconfig(""" [section] key = rue [testenv] key = t{[section]key} """) - reader = IniReader(config._cfg) - x = reader.getdefault("testenv", "key") + reader = SectionReader("testenv", config._cfg) + x = reader.getstring("key") assert x == "true" def test_argvlist(self, tmpdir, newconfig): @@ -391,12 +394,12 @@ cmd1 {item1} {item2} cmd2 {item2} """) - reader = IniReader(config._cfg) + reader = SectionReader("section", config._cfg) reader.addsubstitutions(item1="with space", item2="grr") # py.test.raises(tox.exception.ConfigError, - # "reader.getargvlist('section', 'key1')") - assert reader.getargvlist('section', 'key1') == [] - x = reader.getargvlist("section", "key2") + # "reader.getargvlist('key1')") + assert reader.getargvlist('key1') == [] + x = reader.getargvlist("key2") assert x == [["cmd1", "with", "space", "grr"], ["cmd2", "grr"]] @@ -405,9 +408,9 @@ [section] comm = py.test {posargs} """) - reader = IniReader(config._cfg) + reader = SectionReader("section", config._cfg) reader.addsubstitutions([r"hello\this"]) - argv = reader.getargv("section", "comm") + argv = reader.getargv("comm") assert argv == ["py.test", "hello\\this"] def test_argvlist_multiline(self, tmpdir, newconfig): @@ -417,12 +420,12 @@ cmd1 {item1} \ # a comment {item2} """) - reader = IniReader(config._cfg) + reader = SectionReader("section", config._cfg) reader.addsubstitutions(item1="with space", item2="grr") # py.test.raises(tox.exception.ConfigError, - # "reader.getargvlist('section', 'key1')") - assert reader.getargvlist('section', 'key1') == [] - x = reader.getargvlist("section", "key2") + # "reader.getargvlist('key1')") + assert reader.getargvlist('key1') == [] + x = reader.getargvlist("key2") assert x == [["cmd1", "with", "space", "grr"]] def test_argvlist_quoting_in_command(self, tmpdir, newconfig): @@ -432,8 +435,8 @@ cmd1 'with space' \ # a comment 'after the comment' """) - reader = IniReader(config._cfg) - x = reader.getargvlist("section", "key1") + reader = SectionReader("section", config._cfg) + x = reader.getargvlist("key1") assert x == [["cmd1", "with space", "after the comment"]] def test_argvlist_positional_substitution(self, tmpdir, newconfig): @@ -444,22 +447,22 @@ cmd2 {posargs:{item2} \ other} """) - reader = IniReader(config._cfg) + reader = SectionReader("section", config._cfg) posargs = ['hello', 'world'] reader.addsubstitutions(posargs, item2="value2") # py.test.raises(tox.exception.ConfigError, - # "reader.getargvlist('section', 'key1')") - assert reader.getargvlist('section', 'key1') == [] - argvlist = reader.getargvlist("section", "key2") + # "reader.getargvlist('key1')") + assert reader.getargvlist('key1') == [] + argvlist = reader.getargvlist("key2") assert argvlist[0] == ["cmd1"] + posargs assert argvlist[1] == ["cmd2"] + posargs - reader = IniReader(config._cfg) + reader = SectionReader("section", config._cfg) reader.addsubstitutions([], item2="value2") # py.test.raises(tox.exception.ConfigError, - # "reader.getargvlist('section', 'key1')") - assert reader.getargvlist('section', 'key1') == [] - argvlist = reader.getargvlist("section", "key2") + # "reader.getargvlist('key1')") + assert reader.getargvlist('key1') == [] + argvlist = reader.getargvlist("key2") assert argvlist[0] == ["cmd1"] assert argvlist[1] == ["cmd2", "value2", "other"] @@ -471,10 +474,10 @@ cmd2 -f '{posargs}' cmd3 -f {posargs} """) - reader = IniReader(config._cfg) + reader = SectionReader("section", config._cfg) reader.addsubstitutions(["foo", "bar"]) - assert reader.getargvlist('section', 'key1') == [] - x = reader.getargvlist("section", "key2") + assert reader.getargvlist('key1') == [] + x = reader.getargvlist("key2") assert x == [["cmd1", "--foo-args=foo bar"], ["cmd2", "-f", "foo bar"], ["cmd3", "-f", "foo", "bar"]] @@ -485,10 +488,10 @@ key2= cmd1 -f {posargs} """) - reader = IniReader(config._cfg) + reader = SectionReader("section", config._cfg) reader.addsubstitutions(["foo", "'bar", "baz'"]) - assert reader.getargvlist('section', 'key1') == [] - x = reader.getargvlist("section", "key2") + assert reader.getargvlist('key1') == [] + x = reader.getargvlist("key2") assert x == [["cmd1", "-f", "foo", "bar baz"]] def test_positional_arguments_are_only_replaced_when_standing_alone(self, tmpdir, newconfig): @@ -500,11 +503,11 @@ cmd2 -m '\'something\'' [] cmd3 something[]else """) - reader = IniReader(config._cfg) + reader = SectionReader("section", config._cfg) posargs = ['hello', 'world'] reader.addsubstitutions(posargs) - argvlist = reader.getargvlist('section', 'key') + argvlist = reader.getargvlist('key') assert argvlist[0] == ['cmd0'] + posargs assert argvlist[1] == ['cmd1', '-m', '[abc]'] assert argvlist[2] == ['cmd2', '-m', "something"] + posargs @@ -516,32 +519,32 @@ key = py.test -n5 --junitxml={envlogdir}/junit-{envname}.xml [] """ config = newconfig(inisource) - reader = IniReader(config._cfg) + reader = SectionReader("section", config._cfg) posargs = ['hello', 'world'] reader.addsubstitutions(posargs, envlogdir='ENV_LOG_DIR', envname='ENV_NAME') expected = [ 'py.test', '-n5', '--junitxml=ENV_LOG_DIR/junit-ENV_NAME.xml', 'hello', 'world' ] - assert reader.getargvlist('section', 'key')[0] == expected + assert reader.getargvlist('key')[0] == expected def test_getargv(self, newconfig): config = newconfig(""" [section] key=some command "with quoting" """) - reader = IniReader(config._cfg) + reader = SectionReader("section", config._cfg) expected = ['some', 'command', 'with quoting'] - assert reader.getargv('section', 'key') == expected + assert reader.getargv('key') == expected def test_getpath(self, tmpdir, newconfig): config = newconfig(""" [section] path1={HELLO} """) - reader = IniReader(config._cfg) + reader = SectionReader("section", config._cfg) reader.addsubstitutions(toxinidir=tmpdir, HELLO="mypath") - x = reader.getpath("section", "path1", tmpdir) + x = reader.getpath("path1", tmpdir) assert x == tmpdir.join("mypath") def test_getbool(self, tmpdir, newconfig): @@ -553,13 +556,13 @@ key2a=falsE key5=yes """) - reader = IniReader(config._cfg) - assert reader.getbool("section", "key1") is True - assert reader.getbool("section", "key1a") is True - assert reader.getbool("section", "key2") is False - assert reader.getbool("section", "key2a") is False - py.test.raises(KeyError, 'reader.getbool("section", "key3")') - py.test.raises(tox.exception.ConfigError, 'reader.getbool("section", "key5")') + reader = SectionReader("section", config._cfg) + assert reader.getbool("key1") is True + assert reader.getbool("key1a") is True + assert reader.getbool("key2") is False + assert reader.getbool("key2a") is False + py.test.raises(KeyError, 'reader.getbool("key3")') + py.test.raises(tox.exception.ConfigError, 'reader.getbool("key5")') class TestConfigTestEnv: @@ -585,7 +588,7 @@ assert envconfig.commands == [["xyz", "--abc"]] assert envconfig.changedir == config.setupdir assert envconfig.sitepackages is False - assert envconfig.develop is False + assert envconfig.usedevelop is False assert envconfig.envlogdir == envconfig.envdir.join("log") assert list(envconfig.setenv.keys()) == ['PYTHONHASHSEED'] hashseed = envconfig.setenv['PYTHONHASHSEED'] @@ -605,7 +608,7 @@ [testenv] usedevelop = True """) - assert not config.envconfigs["python"].develop + assert not config.envconfigs["python"].usedevelop def test_specific_command_overrides(self, tmpdir, newconfig): config = newconfig(""" @@ -1371,7 +1374,7 @@ def test_noset(self, tmpdir, newconfig): args = ['--hashseed', 'noset'] envconfig = self._get_envconfig(newconfig, args=args) - assert envconfig.setenv is None + assert envconfig.setenv == {} def test_noset_with_setenv(self, tmpdir, newconfig): tox_ini = """ diff -r 1de999d6ecf98715ce179636a522b86f5dd9b8dc -r 7f1bcc90f673122444ce1efac01206da661c84ff tests/test_venv.py --- a/tests/test_venv.py +++ b/tests/test_venv.py @@ -464,7 +464,7 @@ venv = VirtualEnv(envconfig, session=mocksession) venv.update() cconfig = venv._getliveconfig() - cconfig.develop = True + cconfig.usedevelop = True cconfig.writeconfig(venv.path_config) mocksession._clearmocks() venv.update() diff -r 1de999d6ecf98715ce179636a522b86f5dd9b8dc -r 7f1bcc90f673122444ce1efac01206da661c84ff tox.ini --- a/tox.ini +++ b/tox.ini @@ -5,8 +5,10 @@ commands=echo {posargs} [testenv] -commands=py.test --junitxml={envlogdir}/junit-{envname}.xml {posargs} +commands= py.test --timeout=60 {posargs} + deps=pytest>=2.3.5 + pytest-timeout [testenv:docs] basepython=python @@ -14,11 +16,10 @@ deps=sphinx {[testenv]deps} commands= - py.test -v \ - --junitxml={envlogdir}/junit-{envname}.xml \ - check_sphinx.py {posargs} + py.test -v check_sphinx.py {posargs} [testenv:flakes] +qwe = 123 deps = pytest-flakes>=0.2 pytest-pep8 diff -r 1de999d6ecf98715ce179636a522b86f5dd9b8dc -r 7f1bcc90f673122444ce1efac01206da661c84ff tox/_cmdline.py --- a/tox/_cmdline.py +++ b/tox/_cmdline.py @@ -25,12 +25,34 @@ def main(args=None): try: config = parseconfig(args) + if config.option.help: + show_help(config) + raise SystemExit(0) + elif config.option.helpini: + show_help_ini(config) + raise SystemExit(0) retcode = Session(config).runcommand() raise SystemExit(retcode) except KeyboardInterrupt: raise SystemExit(2) +def show_help(config): + tw = py.io.TerminalWriter() + tw.write(config._parser.format_help()) + tw.line() + + +def show_help_ini(config): + tw = py.io.TerminalWriter() + tw.sep("-", "per-testenv attributes") + for env_attr in config._testenv_attr: + tw.line("%-15s %-8s default: %s" % + (env_attr.name, "<" + env_attr.type + ">", env_attr.default), bold=True) + tw.line(env_attr.help) + tw.line() + + class Action(object): def __init__(self, session, venv, msg, args): self.venv = venv @@ -487,7 +509,7 @@ venv.status = "platform mismatch" continue # we simply omit non-matching platforms if self.setupenv(venv): - if venv.envconfig.develop: + if venv.envconfig.usedevelop: self.developpkg(venv, self.config.setupdir) elif self.config.skipsdist or venv.envconfig.skip_install: self.finishvenv(venv) @@ -566,7 +588,7 @@ self.report.line(" deps=%s" % envconfig.deps) self.report.line(" envdir= %s" % envconfig.envdir) self.report.line(" downloadcache=%s" % envconfig.downloadcache) - self.report.line(" usedevelop=%s" % envconfig.develop) + self.report.line(" usedevelop=%s" % envconfig.usedevelop) def showenvs(self): for env in self.config.envlist: diff -r 1de999d6ecf98715ce179636a522b86f5dd9b8dc -r 7f1bcc90f673122444ce1efac01206da661c84ff tox/_config.py --- a/tox/_config.py +++ b/tox/_config.py @@ -38,6 +38,122 @@ return pm +class MyParser: + def __init__(self): + self.argparser = argparse.ArgumentParser( + description="tox options", add_help=False) + self._testenv_attr = [] + + def add_argument(self, *args, **kwargs): + return self.argparser.add_argument(*args, **kwargs) + + def add_testenv_attribute(self, name, type, help, default=None, postprocess=None): + self._testenv_attr.append(VenvAttribute(name, type, default, help, postprocess)) + + def add_testenv_attribute_obj(self, obj): + assert hasattr(obj, "name") + assert hasattr(obj, "type") + assert hasattr(obj, "help") + assert hasattr(obj, "postprocess") + self._testenv_attr.append(obj) + + def parse_args(self, args): + return self.argparser.parse_args(args) + + def format_help(self): + return self.argparser.format_help() + + +class VenvAttribute: + def __init__(self, name, type, default, help, postprocess): + self.name = name + self.type = type + self.default = default + self.help = help + self.postprocess = postprocess + + +class DepOption: + name = "deps" + type = "line-list" + help = "each line specifies a dependency in pip/setuptools format." + default = () + + def postprocess(self, config, reader, section_val): + deps = [] + for depline in section_val: + m = re.match(r":(\w+):\s*(\S+)", depline) + if m: + iname, name = m.groups() + ixserver = config.indexserver[iname] + else: + name = depline.strip() + ixserver = None + name = self._replace_forced_dep(name, config) + deps.append(DepConfig(name, ixserver)) + return deps + + def _replace_forced_dep(self, name, config): + """ + Override the given dependency config name taking --force-dep-version + option into account. + + :param name: dep config, for example ["pkg==1.0", "other==2.0"]. + :param config: Config instance + :return: the new dependency that should be used for virtual environments + """ + if not config.option.force_dep: + return name + for forced_dep in config.option.force_dep: + if self._is_same_dep(forced_dep, name): + return forced_dep + return name + + @classmethod + def _is_same_dep(cls, dep1, dep2): + """ + Returns True if both dependency definitions refer to the + same package, even if versions differ. + """ + dep1_name = pkg_resources.Requirement.parse(dep1).project_name + dep2_name = pkg_resources.Requirement.parse(dep2).project_name + return dep1_name == dep2_name + + +class PosargsOption: + name = "args_are_paths" + type = "bool" + default = True + help = "treat positional args in commands as paths" + + def postprocess(self, config, reader, section_val): + args = config.option.args + if args: + if section_val: + args = [] + for arg in config.option.args: + if arg: + origpath = config.invocationcwd.join(arg, abs=True) + if origpath.check(): + arg = reader.getpath("changedir", ".").bestrelpath(origpath) + args.append(arg) + reader.addsubstitutions(args) + return section_val + + +class InstallcmdOption: + name = "install_command" + type = "argv" + default = "pip install {opts} {packages}" + help = "install command for dependencies and package under test." + + def postprocess(self, config, reader, section_val): + if '{packages}' not in section_val: + raise tox.exception.ConfigError( + "'install_command' must contain '{packages}' substitution") + return section_val + + def parseconfig(args=None): """ :param list[str] args: Optional list of arguments. @@ -52,13 +168,15 @@ args = sys.argv[1:] # prepare command line options - parser = argparse.ArgumentParser(description=__doc__) + parser = MyParser() pm.hook.tox_addoption(parser=parser) # parse command line options option = parser.parse_args(args) interpreters = tox.interpreters.Interpreters(hook=pm.hook) config = Config(pluginmanager=pm, option=option, interpreters=interpreters) + config._parser = parser + config._testenv_attr = parser._testenv_attr # parse ini file basename = config.option.configfile @@ -111,6 +229,10 @@ parser.add_argument("--version", nargs=0, action=VersionAction, dest="version", help="report version information to stdout.") + parser.add_argument("-h", "--help", action="store_true", dest="help", + help="show help about options") + parser.add_argument("--help-ini", "--hi", action="store_true", dest="helpini", + help="show help about ini-names") parser.add_argument("-v", nargs=0, action=CountAction, default=0, dest="verbosity", help="increase verbosity of reporting output.") @@ -156,6 +278,7 @@ "all commands and results involved. This will turn off " "pass-through output from running test commands which is " "instead captured into the json result file.") + # We choose 1 to 4294967295 because it is the range of PYTHONHASHSEED. parser.add_argument("--hashseed", action="store", metavar="SEED", default=None, @@ -175,7 +298,130 @@ parser.add_argument("args", nargs="*", help="additional arguments available to command positional substitution") - return parser + + # add various core venv interpreter attributes + + parser.add_testenv_attribute( + name="envdir", type="path", default="{toxworkdir}/{envname}", + help="venv directory") + + parser.add_testenv_attribute( + name="envtmpdir", type="path", default="{envdir}/tmp", + help="venv temporary directory") + + parser.add_testenv_attribute( + name="envlogdir", type="path", default="{envdir}/log", + help="venv log directory") + + def downloadcache(config, reader, section_val): + if section_val: + # env var, if present, takes precedence + downloadcache = os.environ.get("PIP_DOWNLOAD_CACHE", section_val) + return py.path.local(downloadcache) + + parser.add_testenv_attribute( + name="downloadcache", type="string", default=None, postprocess=downloadcache, + help="(deprecated) set PIP_DOWNLOAD_CACHE.") + + parser.add_testenv_attribute( + name="changedir", type="path", default="{toxinidir}", + help="directory to change to when running commands") + + parser.add_testenv_attribute_obj(PosargsOption()) + + parser.add_testenv_attribute( + name="skip_install", type="bool", default=False, + help="Do not install the current package. This can be used when " + "you need the virtualenv management but do not want to install " + "the current package") + + def recreate(config, reader, section_val): + if config.option.recreate: + return True + return section_val + + parser.add_testenv_attribute( + name="recreate", type="bool", default=False, postprocess=recreate, + help="always recreate this test environment.") + + def setenv(config, reader, section_val): + setenv = section_val + if "PYTHONHASHSEED" not in setenv and config.hashseed is not None: + setenv['PYTHONHASHSEED'] = config.hashseed + return setenv + + parser.add_testenv_attribute( + name="setenv", type="dict", postprocess=setenv, + help="list of X=Y lines with environment variable settings") + + def passenv(config, reader, section_val): + passenv = set(["PATH"]) + if sys.platform == "win32": + passenv.add("SYSTEMROOT") # needed for python's crypto module + passenv.add("PATHEXT") # needed for discovering executables + for spec in section_val: + for name in os.environ: + if fnmatchcase(name.upper(), spec.upper()): + passenv.add(name) + return passenv + + parser.add_testenv_attribute( + name="passenv", type="space-separated-list", postprocess=passenv, + help="environment variables names which shall be passed " + "from tox invocation to test environment when executing commands.") + + parser.add_testenv_attribute( + name="whitelist_externals", type="line-list", + help="each lines specifies a path or basename for which tox will not warn " + "about it coming from outside the test environment.") + + parser.add_testenv_attribute( + name="platform", type="string", default=".*", + help="regular expression which must match against ``sys.platform``. " + "otherwise testenv will be skipped.") + + def sitepackages(config, reader, section_val): + return config.option.sitepackages or section_val + + parser.add_testenv_attribute( + name="sitepackages", type="bool", default=False, postprocess=sitepackages, + help="Set to ``True`` if you want to create virtual environments that also " + "have access to globally installed packages.") + + def pip_pre(config, reader, section_val): + return config.option.pre or section_val + + parser.add_testenv_attribute( + name="pip_pre", type="bool", default=False, postprocess=pip_pre, + help="If ``True``, adds ``--pre`` to the ``opts`` passed to " + "the install command. ") + + def develop(config, reader, section_val): + return not config.option.installpkg and (section_val or config.option.develop) + + parser.add_testenv_attribute( + name="usedevelop", type="bool", postprocess=develop, default=False, + help="install package in develop/editable mode") + + def basepython_default(config, reader, section_val): + if section_val is None: + for f in reader.factors: + if f in default_factors: + return default_factors[f] + return sys.executable + return str(section_val) + + parser.add_testenv_attribute( + name="basepython", type="string", default=None, postprocess=basepython_default, + help="executable name or path of interpreter used to create a " + "virtual test environment.") + + parser.add_testenv_attribute_obj(InstallcmdOption()) + parser.add_testenv_attribute_obj(DepOption()) + + parser.add_testenv_attribute( + name="commands", type="argvlist", default="", + help="each line specifies a test command and can use substitution.") class Config(object): @@ -272,12 +518,10 @@ self.config = config ctxname = getcontextname() if ctxname == "jenkins": - reader = IniReader(self._cfg, fallbacksections=['tox']) - toxsection = "tox:%s" % ctxname + reader = SectionReader("tox:jenkins", self._cfg, fallbacksections=['tox']) distshare_default = "{toxworkdir}/distshare" elif not ctxname: - reader = IniReader(self._cfg) - toxsection = "tox" + reader = SectionReader("tox", self._cfg) distshare_default = "{homedir}/.tox/distshare" else: raise ValueError("invalid context") @@ -292,18 +536,17 @@ reader.addsubstitutions(toxinidir=config.toxinidir, homedir=config.homedir) - config.toxworkdir = reader.getpath(toxsection, "toxworkdir", - "{toxinidir}/.tox") - config.minversion = reader.getdefault(toxsection, "minversion", None) + config.toxworkdir = reader.getpath("toxworkdir", "{toxinidir}/.tox") + config.minversion = reader.getstring("minversion", None) if not config.option.skip_missing_interpreters: config.option.skip_missing_interpreters = \ - reader.getbool(toxsection, "skip_missing_interpreters", False) + reader.getbool("skip_missing_interpreters", False) # determine indexserver dictionary config.indexserver = {'default': IndexServerConfig('default')} prefix = "indexserver" - for line in reader.getlist(toxsection, prefix): + for line in reader.getlist(prefix): name, url = map(lambda x: x.strip(), line.split("=", 1)) config.indexserver[name] = IndexServerConfig(name, url) @@ -328,16 +571,15 @@ config.indexserver[name] = IndexServerConfig(name, override) reader.addsubstitutions(toxworkdir=config.toxworkdir) - config.distdir = reader.getpath(toxsection, "distdir", "{toxworkdir}/dist") + config.distdir = reader.getpath("distdir", "{toxworkdir}/dist") reader.addsubstitutions(distdir=config.distdir) - config.distshare = reader.getpath(toxsection, "distshare", - distshare_default) + config.distshare = reader.getpath("distshare", distshare_default) reader.addsubstitutions(distshare=config.distshare) - config.sdistsrc = reader.getpath(toxsection, "sdistsrc", None) - config.setupdir = reader.getpath(toxsection, "setupdir", "{toxinidir}") + config.sdistsrc = reader.getpath("sdistsrc", None) + config.setupdir = reader.getpath("setupdir", "{toxinidir}") config.logdir = config.toxworkdir.join("log") - config.envlist, all_envs = self._getenvdata(reader, toxsection) + config.envlist, all_envs = self._getenvdata(reader) # factors used in config or predefined known_factors = self._list_section_factors("testenv") @@ -345,7 +587,7 @@ known_factors.add("python") # factors stated in config envlist - stated_envlist = reader.getdefault(toxsection, "envlist", replace=False) + stated_envlist = reader.getstring("envlist", replace=False) if stated_envlist: for env in _split_env(stated_envlist): known_factors.update(env.split('-')) @@ -356,13 +598,13 @@ factors = set(name.split('-')) if section in self._cfg or factors <= known_factors: config.envconfigs[name] = \ - self._makeenvconfig(name, section, reader._subs, config) + self.make_envconfig(name, section, reader._subs, config) all_develop = all(name in config.envconfigs - and config.envconfigs[name].develop + and config.envconfigs[name].usedevelop for name in config.envlist) - config.skipsdist = reader.getbool(toxsection, "skipsdist", all_develop) + config.skipsdist = reader.getbool("skipsdist", all_develop) def _list_section_factors(self, section): factors = set() @@ -372,115 +614,51 @@ factors.update(*mapcat(_split_factor_expr, exprs)) return factors - def _makeenvconfig(self, name, section, subs, config): + def make_envconfig(self, name, section, subs, config): vc = VenvConfig(config=config, envname=name) factors = set(name.split('-')) - reader = IniReader(self._cfg, fallbacksections=["testenv"], factors=factors) + reader = SectionReader(section, self._cfg, fallbacksections=["testenv"], + factors=factors) reader.addsubstitutions(**subs) - vc.develop = ( - not config.option.installpkg - and reader.getbool(section, "usedevelop", config.option.develop)) - vc.envdir = reader.getpath(section, "envdir", "{toxworkdir}/%s" % name) - vc.args_are_paths = reader.getbool(section, "args_are_paths", True) - if reader.getdefault(section, "python", None): - raise tox.exception.ConfigError( - "'python=' key was renamed to 'basepython='") - bp = next((default_factors[f] for f in factors if f in default_factors), - sys.executable) - vc.basepython = reader.getdefault(section, "basepython", bp) + reader.addsubstitutions(envname=name) - reader.addsubstitutions(envdir=vc.envdir, envname=vc.envname, - envbindir=vc.envbindir, envpython=vc.envpython, - envsitepackagesdir=vc.envsitepackagesdir) - vc.envtmpdir = reader.getpath(section, "tmpdir", "{envdir}/tmp") - vc.envlogdir = reader.getpath(section, "envlogdir", "{envdir}/log") - reader.addsubstitutions(envlogdir=vc.envlogdir, envtmpdir=vc.envtmpdir) - vc.changedir = reader.getpath(section, "changedir", "{toxinidir}") - if config.option.recreate: - vc.recreate = True - else: - vc.recreate = reader.getbool(section, "recreate", False) - args = config.option.args - if args: - if vc.args_are_paths: - args = [] - for arg in config.option.args: - if arg: - origpath = config.invocationcwd.join(arg, abs=True) - if origpath.check(): - arg = vc.changedir.bestrelpath(origpath) - args.append(arg) - reader.addsubstitutions(args) - setenv = {} - if config.hashseed is not None: - setenv['PYTHONHASHSEED'] = config.hashseed - setenv.update(reader.getdict(section, 'setenv')) + for env_attr in config._testenv_attr: + atype = env_attr.type + if atype in ("bool", "path", "string", "dict", "argv", "argvlist"): + meth = getattr(reader, "get" + atype) + res = meth(env_attr.name, env_attr.default) + elif atype == "space-separated-list": + res = reader.getlist(env_attr.name, sep=" ") + elif atype == "line-list": + res = reader.getlist(env_attr.name, sep="\n") + else: + raise ValueError("unknown type %r" % (atype,)) - # read passenv - vc.passenv = set(["PATH"]) - if sys.platform == "win32": - vc.passenv.add("SYSTEMROOT") # needed for python's crypto module - vc.passenv.add("PATHEXT") # needed for discovering executables - for spec in reader.getlist(section, "passenv", sep=" "): - for name in os.environ: - if fnmatchcase(name.lower(), spec.lower()): - vc.passenv.add(name) + if env_attr.postprocess: + res = env_attr.postprocess(config, reader, res) + setattr(vc, env_attr.name, res) - vc.setenv = setenv - if not vc.setenv: - vc.setenv = None + if atype == "path": + reader.addsubstitutions(**{env_attr.name: res}) - vc.commands = reader.getargvlist(section, "commands") - vc.whitelist_externals = reader.getlist(section, - "whitelist_externals") - vc.deps = [] - for depline in reader.getlist(section, "deps"): - m = re.match(r":(\w+):\s*(\S+)", depline) - if m: - iname, name = m.groups() - ixserver = config.indexserver[iname] - else: - name = depline.strip() - ixserver = None - name = self._replace_forced_dep(name, config) - vc.deps.append(DepConfig(name, ixserver)) + if env_attr.name == "install_command": + reader.addsubstitutions(envbindir=vc.envbindir, envpython=vc.envpython, + envsitepackagesdir=vc.envsitepackagesdir) - platform = "" - for platform in reader.getlist(section, "platform"): - if platform.strip(): - break - vc.platform = platform - - vc.sitepackages = ( - self.config.option.sitepackages - or reader.getbool(section, "sitepackages", False)) - - vc.downloadcache = None - downloadcache = reader.getdefault(section, "downloadcache") - if downloadcache: - # env var, if present, takes precedence - downloadcache = os.environ.get("PIP_DOWNLOAD_CACHE", downloadcache) - vc.downloadcache = py.path.local(downloadcache) - - vc.install_command = reader.getargv( - section, - "install_command", - "pip install {opts} {packages}", - ) - if '{packages}' not in vc.install_command: - raise tox.exception.ConfigError( - "'install_command' must contain '{packages}' substitution") - vc.pip_pre = config.option.pre or reader.getbool( - section, "pip_pre", False) - - vc.skip_install = reader.getbool(section, "skip_install", False) - + # XXX introduce some testenv verification like this: + # try: + # sec = self._cfg[section] + # except KeyError: + # sec = self._cfg["testenv"] + # for name in sec: + # if name not in names: + # print ("unknown testenv attribute: %r" % (name,)) return vc - def _getenvdata(self, reader, toxsection): + def _getenvdata(self, reader): envstr = self.config.option.env \ or os.environ.get("TOXENV") \ - or reader.getdefault(toxsection, "envlist", replace=False) \ + or reader.getstring("envlist", replace=False) \ or [] envlist = _split_env(envstr) @@ -497,32 +675,6 @@ return envlist, all_envs - def _replace_forced_dep(self, name, config): - """ - Override the given dependency config name taking --force-dep-version - option into account. - - :param name: dep config, for example ["pkg==1.0", "other==2.0"]. - :param config: Config instance - :return: the new dependency that should be used for virtual environments - """ - if not config.option.force_dep: - return name - for forced_dep in config.option.force_dep: - if self._is_same_dep(forced_dep, name): - return forced_dep - return name - - @classmethod - def _is_same_dep(cls, dep1, dep2): - """ - Returns True if both dependency definitions refer to the - same package, even if versions differ. - """ - dep1_name = pkg_resources.Requirement.parse(dep1).project_name - dep2_name = pkg_resources.Requirement.parse(dep2).project_name - return dep1_name == dep2_name - def _split_env(env): """if handed a list, action="append" was used for -e """ @@ -589,8 +741,9 @@ re.VERBOSE) -class IniReader: - def __init__(self, cfgparser, fallbacksections=None, factors=()): +class SectionReader: + def __init__(self, section_name, cfgparser, fallbacksections=None, factors=()): + self.section_name = section_name self._cfg = cfgparser self.fallbacksections = fallbacksections or [] self.factors = factors @@ -602,129 +755,39 @@ if _posargs: self.posargs = _posargs - def getpath(self, section, name, defaultpath): + def getpath(self, name, defaultpath): toxinidir = self._subs['toxinidir'] - path = self.getdefault(section, name, defaultpath) + path = self.getstring(name, defaultpath) if path is None: return path return toxinidir.join(path, abs=True) - def getlist(self, section, name, sep="\n"): - s = self.getdefault(section, name, None) + def getlist(self, name, sep="\n"): + s = self.getstring(name, None) if s is None: return [] return [x.strip() for x in s.split(sep) if x.strip()] - def getdict(self, section, name, sep="\n"): - s = self.getdefault(section, name, None) + def getdict(self, name, default=None, sep="\n"): + s = self.getstring(name, None) if s is None: - return {} + return default or {} value = {} for line in s.split(sep): - if not line.strip(): - continue - name, rest = line.split('=', 1) - value[name.strip()] = rest.strip() + if line.strip(): + name, rest = line.split('=', 1) + value[name.strip()] = rest.strip() return value - def getargvlist(self, section, name): - """Get arguments for every parsed command. - - :param str section: Section name in the configuration. - :param str name: Key name in a section. - :rtype: list[list[str]] - :raise :class:`tox.exception.ConfigError`: - line-continuation ends nowhere while resolving for specified section - """ - content = self.getdefault(section, name, '', replace=False) - return self._parse_commands(section, name, content) - - def _parse_commands(self, section, name, content): - """Parse commands from key content in specified section. - - :param str section: Section name in the configuration. - :param str name: Key name in a section. - :param str content: Content stored by key. - - :rtype: list[list[str]] - :raise :class:`tox.exception.ConfigError`: - line-continuation ends nowhere while resolving for specified section - """ - commands = [] - current_command = "" - for line in content.splitlines(): - line = line.rstrip() - i = line.find("#") - if i != -1: - line = line[:i].rstrip() - if not line: - continue - if line.endswith("\\"): - current_command += " " + line[:-1] - continue - current_command += line - - if is_section_substitution(current_command): - replaced = self._replace(current_command) - commands.extend(self._parse_commands(section, name, replaced)) - else: - commands.append(self._processcommand(current_command)) - current_command = "" - else: - if current_command: - raise tox.exception.ConfigError( - "line-continuation ends nowhere while resolving for [%s] %s" % - (section, name)) - return commands - - def _processcommand(self, command): - posargs = getattr(self, "posargs", None) - - # Iterate through each word of the command substituting as - # appropriate to construct the new command string. This - # string is then broken up into exec argv components using - # shlex. - newcommand = "" - for word in CommandParser(command).words(): - if word == "{posargs}" or word == "[]": - if posargs: - newcommand += " ".join(posargs) - continue - elif word.startswith("{posargs:") and word.endswith("}"): - if posargs: - newcommand += " ".join(posargs) - continue - else: - word = word[9:-1] - new_arg = "" - new_word = self._replace(word) - new_word = self._replace(new_word) - new_arg += new_word - newcommand += new_arg - - # Construct shlex object that will not escape any values, - # use all values as is in argv. - shlexer = shlex.shlex(newcommand, posix=True) - shlexer.whitespace_split = True - shlexer.escape = '' - shlexer.commenters = '' - argv = list(shlexer) - return argv - - def getargv(self, section, name, default=None, replace=True): - command = self.getdefault( - section, name, default=default, replace=False) - return self._processcommand(command.strip()) - - def getbool(self, section, name, default=None): - s = self.getdefault(section, name, default) + def getbool(self, name, default=None): + s = self.getstring(name, default) if not s: s = default if s is None: raise KeyError("no config value [%s] %s found" % ( - section, name)) + self.section_name, name)) if not isinstance(s, bool): if s.lower() == "true": @@ -736,9 +799,16 @@ "boolean value %r needs to be 'True' or 'False'") return s - def getdefault(self, section, name, default=None, replace=True): + def getargvlist(self, name, default=""): + s = self.getstring(name, default, replace=False) + return _ArgvlistReader.getargvlist(self, s) + + def getargv(self, name, default=""): + return self.getargvlist(name, default)[0] + + def getstring(self, name, default=None, replace=True): x = None - for s in [section] + self.fallbacksections: + for s in [self.section_name] + self.fallbacksections: try: x = self._cfg[s][name] break @@ -751,12 +821,12 @@ x = self._apply_factors(x) if replace and x and hasattr(x, 'replace'): - self._subststack.append((section, name)) + self._subststack.append((self.section_name, name)) try: x = self._replace(x) finally: - assert self._subststack.pop() == (section, name) - # print "getdefault", section, name, "returned", repr(x) + assert self._subststack.pop() == (self.section_name, name) + # print "getstring", self.section_name, name, "returned", repr(x) return x def _apply_factors(self, s): @@ -852,8 +922,80 @@ return RE_ITEM_REF.sub(self._replace_match, x) return x - def _parse_command(self, command): - pass + +class _ArgvlistReader: + @classmethod + def getargvlist(cls, reader, section_val): + """Parse ``commands`` argvlist multiline string. + + :param str name: Key name in a section. + :param str section_val: Content stored by key. + + :rtype: list[list[str]] + :raise :class:`tox.exception.ConfigError`: + line-continuation ends nowhere while resolving for specified section + """ + commands = [] + current_command = "" + for line in section_val.splitlines(): + line = line.rstrip() + i = line.find("#") + if i != -1: + line = line[:i].rstrip() + if not line: + continue + if line.endswith("\\"): + current_command += " " + line[:-1] + continue + current_command += line + + if is_section_substitution(current_command): + replaced = reader._replace(current_command) + commands.extend(cls.getargvlist(reader, replaced)) + else: + commands.append(cls.processcommand(reader, current_command)) + current_command = "" + else: + if current_command: + raise tox.exception.ConfigError( + "line-continuation ends nowhere while resolving for [%s] %s" % + (reader.section_name, "commands")) + return commands + + @classmethod + def processcommand(cls, reader, command): + posargs = getattr(reader, "posargs", None) + + # Iterate through each word of the command substituting as + # appropriate to construct the new command string. This + # string is then broken up into exec argv components using + # shlex. + newcommand = "" + for word in CommandParser(command).words(): + if word == "{posargs}" or word == "[]": + if posargs: + newcommand += " ".join(posargs) + continue + elif word.startswith("{posargs:") and word.endswith("}"): + if posargs: + newcommand += " ".join(posargs) + continue + else: + word = word[9:-1] + new_arg = "" + new_word = reader._replace(word) + new_word = reader._replace(new_word) + new_arg += new_word + newcommand += new_arg + + # Construct shlex object that will not escape any values, + # use all values as is in argv. + shlexer = shlex.shlex(newcommand, posix=True) + shlexer.whitespace_split = True + shlexer.escape = '' + shlexer.commenters = '' + argv = list(shlexer) + return argv class CommandParser(object): diff -r 1de999d6ecf98715ce179636a522b86f5dd9b8dc -r 7f1bcc90f673122444ce1efac01206da661c84ff tox/_venv.py --- a/tox/_venv.py +++ b/tox/_venv.py @@ -10,17 +10,17 @@ class CreationConfig: def __init__(self, md5, python, version, sitepackages, - develop, deps): + usedevelop, deps): self.md5 = md5 self.python = python self.version = version self.sitepackages = sitepackages - self.develop = develop + self.usedevelop = usedevelop self.deps = deps def writeconfig(self, path): lines = ["%s %s" % (self.md5, self.python)] - lines.append("%s %d %d" % (self.version, self.sitepackages, self.develop)) + lines.append("%s %d %d" % (self.version, self.sitepackages, self.usedevelop)) for dep in self.deps: lines.append("%s %s" % dep) path.ensure() @@ -32,14 +32,14 @@ lines = path.readlines(cr=0) value = lines.pop(0).split(None, 1) md5, python = value - version, sitepackages, develop = lines.pop(0).split(None, 3) + version, sitepackages, usedevelop = lines.pop(0).split(None, 3) sitepackages = bool(int(sitepackages)) - develop = bool(int(develop)) + usedevelop = bool(int(usedevelop)) deps = [] for line in lines: md5, depstring = line.split(None, 1) deps.append((md5, depstring)) - return CreationConfig(md5, python, version, sitepackages, develop, deps) + return CreationConfig(md5, python, version, sitepackages, usedevelop, deps) except Exception: return None @@ -48,7 +48,7 @@ and self.python == other.python and self.version == other.version and self.sitepackages == other.sitepackages - and self.develop == other.develop + and self.usedevelop == other.usedevelop and self.deps == other.deps) @@ -147,7 +147,7 @@ md5 = getdigest(python) version = tox.__version__ sitepackages = self.envconfig.sitepackages - develop = self.envconfig.develop + develop = self.envconfig.usedevelop deps = [] for dep in self._getresolvedeps(): raw_dep = dep.name @@ -321,11 +321,13 @@ for envname in self.envconfig.passenv: if envname in os.environ: env[envname] = os.environ[envname] - setenv = self.envconfig.setenv - if setenv: - env.update(setenv) + + env.update(self.envconfig.setenv) + env['VIRTUAL_ENV'] = str(self.path) + env.update(extraenv) + return env def test(self, redirect=False): https://bitbucket.org/hpk42/tox/commits/c2e0dbe2fddd/ Changeset: c2e0dbe2fddd Branch: pluggy User: hpk42 Date: 2015-05-11 10:19:03+00:00 Summary: merge default Affected #: 6 files diff -r 7f1bcc90f673122444ce1efac01206da661c84ff -r c2e0dbe2fddd4d505b9690071608b819af1a9a08 CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -17,6 +17,12 @@ If platform is set and doesn't match the platform spec in the test environment the test environment is ignored, no setup or tests are attempted. +.. (new) add per-venv "ignore_errors" setting, which defaults to False. + If ``True``, a non-zero exit code from one command will be ignored and + further commands will be executed (which was the default behavior in tox < + 2.0). If ``False`` (the default), then a non-zero exit code from one command + will abort execution of commands for that environment. + - remove the long-deprecated "distribute" option as it has no effect these days. - fix issue233: avoid hanging with tox-setuptools integration example. Thanks simonb. diff -r 7f1bcc90f673122444ce1efac01206da661c84ff -r c2e0dbe2fddd4d505b9690071608b819af1a9a08 doc/config.txt --- a/doc/config.txt +++ b/doc/config.txt @@ -110,6 +110,26 @@ pip install {opts} {packages} +.. confval:: ignore_errors=True|False(default) + + .. versionadded:: 2.0 + + If ``True``, a non-zero exit code from one command will be ignored and + further commands will be executed (which was the default behavior in tox < + 2.0). If ``False`` (the default), then a non-zero exit code from one command + will abort execution of commands for that environment. + + It may be helpful to note that this setting is analogous to the ``-i`` or + ``ignore-errors`` option of GNU Make. A similar name was chosen to reflect the + similarity in function. + + Note that in tox 2.0, the default behavior of tox with respect to + treating errors from commands changed. Tox < 2.0 would ignore errors by + default. Tox >= 2.0 will abort on an error by default, which is safer and more + typical of CI and command execution tools, as it doesn't make sense to + run tests if installing some prerequisite failed and it doesn't make sense to + try to deploy if tests failed. + .. confval:: pip_pre=True|False(default) .. versionadded:: 1.9 diff -r 7f1bcc90f673122444ce1efac01206da661c84ff -r c2e0dbe2fddd4d505b9690071608b819af1a9a08 tests/test_config.py --- a/tests/test_config.py +++ b/tests/test_config.py @@ -589,6 +589,7 @@ assert envconfig.changedir == config.setupdir assert envconfig.sitepackages is False assert envconfig.usedevelop is False + assert envconfig.ignore_errors is False assert envconfig.envlogdir == envconfig.envdir.join("log") assert list(envconfig.setenv.keys()) == ['PYTHONHASHSEED'] hashseed = envconfig.setenv['PYTHONHASHSEED'] @@ -649,6 +650,15 @@ assert envconfig.changedir.basename == "xyz" assert envconfig.changedir == config.toxinidir.join("xyz") + def test_ignore_errors(self, tmpdir, newconfig): + config = newconfig(""" + [testenv] + ignore_errors=True + """) + assert len(config.envconfigs) == 1 + envconfig = config.envconfigs['python'] + assert envconfig.ignore_errors is True + def test_envbindir(self, tmpdir, newconfig): config = newconfig(""" [testenv] diff -r 7f1bcc90f673122444ce1efac01206da661c84ff -r c2e0dbe2fddd4d505b9690071608b819af1a9a08 tox/_cmdline.py --- a/tox/_cmdline.py +++ b/tox/_cmdline.py @@ -179,7 +179,7 @@ raise tox.exception.InvocationError( "%s (see %s)" % (invoked, outpath), ret) else: - raise tox.exception.InvocationError("%r" % (invoked, )) + raise tox.exception.InvocationError("%r" % (invoked, ), ret) if not out and outpath: out = outpath.read() if hasattr(self, "commandlog"): diff -r 7f1bcc90f673122444ce1efac01206da661c84ff -r c2e0dbe2fddd4d505b9690071608b819af1a9a08 tox/_config.py --- a/tox/_config.py +++ b/tox/_config.py @@ -335,6 +335,11 @@ "you need the virtualenv management but do not want to install " "the current package") + parser.add_testenv_attribute( + name="ignore_errors", type="bool", default=False, + help="if set to True all commands will be executed irrespective of their " + "result error status.") + def recreate(config, reader, section_val): if config.option.recreate: return True @@ -644,15 +649,6 @@ if env_attr.name == "install_command": reader.addsubstitutions(envbindir=vc.envbindir, envpython=vc.envpython, envsitepackagesdir=vc.envsitepackagesdir) - - # XXX introduce some testenv verification like this: - # try: - # sec = self._cfg[section] - # except KeyError: - # sec = self._cfg["testenv"] - # for name in sec: - # if name not in names: - # print ("unknown testenv attribute: %r" % (name,)) return vc def _getenvdata(self, reader): diff -r 7f1bcc90f673122444ce1efac01206da661c84ff -r c2e0dbe2fddd4d505b9690071608b819af1a9a08 tox/_venv.py --- a/tox/_venv.py +++ b/tox/_venv.py @@ -363,6 +363,14 @@ val = sys.exc_info()[1] self.session.report.error(str(val)) self.status = "commands failed" + if not self.envconfig.ignore_errors: + self.session.report.error( + 'Stopping processing of commands for env %s ' + 'because `%s` failed with exit code %s' + % (self.name, + ' '.join([str(x) for x in argv]), + val.args[1])) + break # Don't process remaining commands except KeyboardInterrupt: self.status = "keyboardinterrupt" self.session.report.error(self.status) https://bitbucket.org/hpk42/tox/commits/d7d35b623979/ Changeset: d7d35b623979 User: hpk42 Date: 2015-05-11 10:35:22+00:00 Summary: remove erroring code / somewhat superflous error reporting Affected #: 1 file diff -r 3d7925f2ef6c716de3bc3e84f649ce9b5f15b498 -r d7d35b623979d2e8186798c5bd29a679ef825c56 tox/_venv.py --- a/tox/_venv.py +++ b/tox/_venv.py @@ -357,17 +357,10 @@ try: self._pcall(argv, cwd=cwd, action=action, redirect=redirect, ignore_ret=ignore_ret) - except tox.exception.InvocationError: - val = sys.exc_info()[1] - self.session.report.error(str(val)) + except tox.exception.InvocationError as err: + self.session.report.error(str(err)) self.status = "commands failed" if not self.envconfig.ignore_errors: - self.session.report.error( - 'Stopping processing of commands for env %s ' - 'because `%s` failed with exit code %s' - % (self.name, - ' '.join([str(x) for x in argv]), - val.args[1])) break # Don't process remaining commands except KeyboardInterrupt: self.status = "keyboardinterrupt" https://bitbucket.org/hpk42/tox/commits/06bb44d51a4c/ Changeset: 06bb44d51a4c User: hpk42 Date: 2015-05-11 10:35:56+00:00 Summary: merge pluggy branch Affected #: 15 files diff -r d7d35b623979d2e8186798c5bd29a679ef825c56 -r 06bb44d51a4c4d02f643da800d47a7197cfab32c CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -38,6 +38,13 @@ - fix issue240: allow to specify empty argument list without it being rewritten to ".". Thanks Daniel Hahler. +- introduce experimental (not much documented yet) plugin system + based on pytest's externalized "pluggy" system. + See tox/hookspecs.py for the current hooks. + +- introduce parser.add_testenv_attribute() to register an ini-variable + for testenv sections. Can be used from plugins through the + tox_add_option hook. 1.9.2 ----------- diff -r d7d35b623979d2e8186798c5bd29a679ef825c56 -r 06bb44d51a4c4d02f643da800d47a7197cfab32c doc/conf.py --- a/doc/conf.py +++ b/doc/conf.py @@ -48,8 +48,8 @@ # built documents. # # The short X.Y version. -release = "1.9" -version = "1.9.0" +release = "2.0" +version = "2.0.0" # The full version, including alpha/beta/rc tags. # The language for content autogenerated by Sphinx. Refer to documentation diff -r d7d35b623979d2e8186798c5bd29a679ef825c56 -r 06bb44d51a4c4d02f643da800d47a7197cfab32c doc/index.txt --- a/doc/index.txt +++ b/doc/index.txt @@ -5,7 +5,7 @@ --------------------------------------------- ``tox`` aims to automate and standardize testing in Python. It is part -of a larger vision of easing the packaging, testing and release process +of a larger vision of easing the packaging, testing and release process of Python software. What is Tox? @@ -21,6 +21,7 @@ * acting as a frontend to Continuous Integration servers, greatly reducing boilerplate and merging CI and shell-based testing. + Basic example ----------------- @@ -62,10 +63,10 @@ - test-tool agnostic: runs py.test, nose or unittests in a uniform manner -* supports :ref:`using different / multiple PyPI index servers ` +* :doc:`(new in 2.0) plugin system ` to modify tox execution with simple hooks. * uses pip_ and setuptools_ by default. Experimental - support for configuring the installer command + support for configuring the installer command through :confval:`install_command=ARGV`. * **cross-Python compatible**: CPython-2.6, 2.7, 3.2 and higher, @@ -74,11 +75,11 @@ * **cross-platform**: Windows and Unix style environments * **integrates with continuous integration servers** like Jenkins_ - (formerly known as Hudson) and helps you to avoid boilerplatish + (formerly known as Hudson) and helps you to avoid boilerplatish and platform-specific build-step hacks. * **full interoperability with devpi**: is integrated with and - is used for testing in the devpi_ system, a versatile pypi + is used for testing in the devpi_ system, a versatile pypi index server and release managing tool. * **driven by a simple ini-style config file** @@ -89,6 +90,9 @@ * **professionally** :doc:`supported ` +* supports :ref:`using different / multiple PyPI index servers ` + + .. _pypy: http://pypy.org .. _`tox.ini`: :doc:configfile diff -r d7d35b623979d2e8186798c5bd29a679ef825c56 -r 06bb44d51a4c4d02f643da800d47a7197cfab32c doc/plugins.txt --- /dev/null +++ b/doc/plugins.txt @@ -0,0 +1,69 @@ +.. be in -*- rst -*- mode! + +tox plugins +=========== + +.. versionadded:: 2.0 + +With tox-2.0 a few aspects of tox running can be experimentally modified +by writing hook functions. We expect the list of hook function to grow +over time. + +writing a setuptools entrypoints plugin +--------------------------------------- + +If you have a ``tox_MYPLUGIN.py`` module you could use the following +rough ``setup.py`` to make it into a package which you can upload to the +Python packaging index:: + + # content of setup.py + from setuptools import setup + + if __name__ == "__main__": + setup( + name='tox-MYPLUGIN', + description='tox plugin decsription', + license="MIT license", + version='0.1', + py_modules=['tox_MYPLUGIN'], + entry_points={'tox': ['MYPLUGIN = tox_MYPLUGIN']}, + install_requires=['tox>=2.0'], + ) + +You can then install the plugin to develop it via:: + + pip install -e . + +and later publish it. + +The ``entry_points`` part allows tox to see your plugin during startup. + + +Writing hook implementations +---------------------------- + +A plugin module needs can define one or more hook implementation functions:: + + from tox import hookimpl + + @hookimpl + def tox_addoption(parser): + # add your own command line options + + + @hookimpl + def tox_configure(config): + # post process tox configuration after cmdline/ini file have + # been parsed + +If you put this into a module and make it pypi-installable with the ``tox`` +entry point you'll get your code executed as part of a tox run. + + + +tox hook specifications +---------------------------- + +.. automodule:: tox.hookspecs + :members: + diff -r d7d35b623979d2e8186798c5bd29a679ef825c56 -r 06bb44d51a4c4d02f643da800d47a7197cfab32c setup.py --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ def main(): version = sys.version_info[:2] - install_requires = ['virtualenv>=1.11.2', 'py>=1.4.17', ] + install_requires = ['virtualenv>=1.11.2', 'py>=1.4.17', 'pluggy>=0.3.0,<0.4.0'] if version < (2, 7): install_requires += ['argparse'] setup( diff -r d7d35b623979d2e8186798c5bd29a679ef825c56 -r 06bb44d51a4c4d02f643da800d47a7197cfab32c tests/test_config.py --- a/tests/test_config.py +++ b/tests/test_config.py @@ -6,7 +6,6 @@ import tox import tox._config from tox._config import * # noqa -from tox._config import _split_env from tox._venv import VirtualEnv @@ -19,7 +18,7 @@ assert config.toxworkdir.realpath() == tmpdir.join(".tox").realpath() assert config.envconfigs['py1'].basepython == sys.executable assert config.envconfigs['py1'].deps == [] - assert not config.envconfigs['py1'].platform + assert config.envconfigs['py1'].platform == ".*" def test_config_parsing_multienv(self, tmpdir, newconfig): config = newconfig([], """ @@ -93,12 +92,12 @@ """ Ensure correct parseini._is_same_dep is working with a few samples. """ - assert parseini._is_same_dep('pkg_hello-world3==1.0', 'pkg_hello-world3') - assert parseini._is_same_dep('pkg_hello-world3==1.0', 'pkg_hello-world3>=2.0') - assert parseini._is_same_dep('pkg_hello-world3==1.0', 'pkg_hello-world3>2.0') - assert parseini._is_same_dep('pkg_hello-world3==1.0', 'pkg_hello-world3<2.0') - assert parseini._is_same_dep('pkg_hello-world3==1.0', 'pkg_hello-world3<=2.0') - assert not parseini._is_same_dep('pkg_hello-world3==1.0', 'otherpkg>=2.0') + assert DepOption._is_same_dep('pkg_hello-world3==1.0', 'pkg_hello-world3') + assert DepOption._is_same_dep('pkg_hello-world3==1.0', 'pkg_hello-world3>=2.0') + assert DepOption._is_same_dep('pkg_hello-world3==1.0', 'pkg_hello-world3>2.0') + assert DepOption._is_same_dep('pkg_hello-world3==1.0', 'pkg_hello-world3<2.0') + assert DepOption._is_same_dep('pkg_hello-world3==1.0', 'pkg_hello-world3<=2.0') + assert not DepOption._is_same_dep('pkg_hello-world3==1.0', 'otherpkg>=2.0') class TestConfigPlatform: @@ -220,8 +219,8 @@ commands = echo {[section]key} """) - reader = IniReader(config._cfg) - x = reader.getargvlist("testenv", "commands") + reader = SectionReader("testenv", config._cfg) + x = reader.getargvlist("commands") assert x == [["echo", "whatever"]] def test_command_substitution_from_other_section_multiline(self, newconfig): @@ -245,8 +244,8 @@ # comment is omitted echo {[base]commands} """) - reader = IniReader(config._cfg) - x = reader.getargvlist("testenv", "commands") + reader = SectionReader("testenv", config._cfg) + x = reader.getargvlist("commands") assert x == [ "cmd1 param11 param12".split(), "cmd2 param21 param22".split(), @@ -257,16 +256,16 @@ class TestIniParser: - def test_getdefault_single(self, tmpdir, newconfig): + def test_getstring_single(self, tmpdir, newconfig): config = newconfig(""" [section] key=value """) - reader = IniReader(config._cfg) - x = reader.getdefault("section", "key") + reader = SectionReader("section", config._cfg) + x = reader.getstring("key") assert x == "value" - assert not reader.getdefault("section", "hello") - x = reader.getdefault("section", "hello", "world") + assert not reader.getstring("hello") + x = reader.getstring("hello", "world") assert x == "world" def test_missing_substitution(self, tmpdir, newconfig): @@ -274,40 +273,40 @@ [mydefault] key2={xyz} """) - reader = IniReader(config._cfg, fallbacksections=['mydefault']) + reader = SectionReader("mydefault", config._cfg, fallbacksections=['mydefault']) assert reader is not None - py.test.raises(tox.exception.ConfigError, - 'reader.getdefault("mydefault", "key2")') + with py.test.raises(tox.exception.ConfigError): + reader.getstring("key2") - def test_getdefault_fallback_sections(self, tmpdir, newconfig): + def test_getstring_fallback_sections(self, tmpdir, newconfig): config = newconfig(""" [mydefault] key2=value2 [section] key=value """) - reader = IniReader(config._cfg, fallbacksections=['mydefault']) - x = reader.getdefault("section", "key2") + reader = SectionReader("section", config._cfg, fallbacksections=['mydefault']) + x = reader.getstring("key2") assert x == "value2" - x = reader.getdefault("section", "key3") + x = reader.getstring("key3") assert not x - x = reader.getdefault("section", "key3", "world") + x = reader.getstring("key3", "world") assert x == "world" - def test_getdefault_substitution(self, tmpdir, newconfig): + def test_getstring_substitution(self, tmpdir, newconfig): config = newconfig(""" [mydefault] key2={value2} [section] key={value} """) - reader = IniReader(config._cfg, fallbacksections=['mydefault']) + reader = SectionReader("section", config._cfg, fallbacksections=['mydefault']) reader.addsubstitutions(value="newvalue", value2="newvalue2") - x = reader.getdefault("section", "key2") + x = reader.getstring("key2") assert x == "newvalue2" - x = reader.getdefault("section", "key3") + x = reader.getstring("key3") assert not x - x = reader.getdefault("section", "key3", "{value2}") + x = reader.getstring("key3", "{value2}") assert x == "newvalue2" def test_getlist(self, tmpdir, newconfig): @@ -317,9 +316,9 @@ item1 {item2} """) - reader = IniReader(config._cfg) + reader = SectionReader("section", config._cfg) reader.addsubstitutions(item1="not", item2="grr") - x = reader.getlist("section", "key2") + x = reader.getlist("key2") assert x == ['item1', 'grr'] def test_getdict(self, tmpdir, newconfig): @@ -329,28 +328,31 @@ key1=item1 key2={item2} """) - reader = IniReader(config._cfg) + reader = SectionReader("section", config._cfg) reader.addsubstitutions(item1="not", item2="grr") - x = reader.getdict("section", "key2") + x = reader.getdict("key2") assert 'key1' in x assert 'key2' in x assert x['key1'] == 'item1' assert x['key2'] == 'grr' - def test_getdefault_environment_substitution(self, monkeypatch, newconfig): + x = reader.getdict("key3", {1: 2}) + assert x == {1: 2} + + def test_getstring_environment_substitution(self, monkeypatch, newconfig): monkeypatch.setenv("KEY1", "hello") config = newconfig(""" [section] key1={env:KEY1} key2={env:KEY2} """) - reader = IniReader(config._cfg) - x = reader.getdefault("section", "key1") + reader = SectionReader("section", config._cfg) + x = reader.getstring("key1") assert x == "hello" - py.test.raises(tox.exception.ConfigError, - 'reader.getdefault("section", "key2")') + with py.test.raises(tox.exception.ConfigError): + reader.getstring("key2") - def test_getdefault_environment_substitution_with_default(self, monkeypatch, newconfig): + def test_getstring_environment_substitution_with_default(self, monkeypatch, newconfig): monkeypatch.setenv("KEY1", "hello") config = newconfig(""" [section] @@ -358,12 +360,12 @@ key2={env:KEY2:DEFAULT_VALUE} key3={env:KEY3:} """) - reader = IniReader(config._cfg) - x = reader.getdefault("section", "key1") + reader = SectionReader("section", config._cfg) + x = reader.getstring("key1") assert x == "hello" - x = reader.getdefault("section", "key2") + x = reader.getstring("key2") assert x == "DEFAULT_VALUE" - x = reader.getdefault("section", "key3") + x = reader.getstring("key3") assert x == "" def test_value_matches_section_substituion(self): @@ -374,15 +376,15 @@ assert is_section_substitution("{[setup]}") is None assert is_section_substitution("{[setup] commands}") is None - def test_getdefault_other_section_substitution(self, newconfig): + def test_getstring_other_section_substitution(self, newconfig): config = newconfig(""" [section] key = rue [testenv] key = t{[section]key} """) - reader = IniReader(config._cfg) - x = reader.getdefault("testenv", "key") + reader = SectionReader("testenv", config._cfg) + x = reader.getstring("key") assert x == "true" def test_argvlist(self, tmpdir, newconfig): @@ -392,12 +394,12 @@ cmd1 {item1} {item2} cmd2 {item2} """) - reader = IniReader(config._cfg) + reader = SectionReader("section", config._cfg) reader.addsubstitutions(item1="with space", item2="grr") # py.test.raises(tox.exception.ConfigError, - # "reader.getargvlist('section', 'key1')") - assert reader.getargvlist('section', 'key1') == [] - x = reader.getargvlist("section", "key2") + # "reader.getargvlist('key1')") + assert reader.getargvlist('key1') == [] + x = reader.getargvlist("key2") assert x == [["cmd1", "with", "space", "grr"], ["cmd2", "grr"]] @@ -406,9 +408,9 @@ [section] comm = py.test {posargs} """) - reader = IniReader(config._cfg) + reader = SectionReader("section", config._cfg) reader.addsubstitutions([r"hello\this"]) - argv = reader.getargv("section", "comm") + argv = reader.getargv("comm") assert argv == ["py.test", "hello\\this"] def test_argvlist_multiline(self, tmpdir, newconfig): @@ -418,12 +420,12 @@ cmd1 {item1} \ # a comment {item2} """) - reader = IniReader(config._cfg) + reader = SectionReader("section", config._cfg) reader.addsubstitutions(item1="with space", item2="grr") # py.test.raises(tox.exception.ConfigError, - # "reader.getargvlist('section', 'key1')") - assert reader.getargvlist('section', 'key1') == [] - x = reader.getargvlist("section", "key2") + # "reader.getargvlist('key1')") + assert reader.getargvlist('key1') == [] + x = reader.getargvlist("key2") assert x == [["cmd1", "with", "space", "grr"]] def test_argvlist_quoting_in_command(self, tmpdir, newconfig): @@ -433,8 +435,8 @@ cmd1 'with space' \ # a comment 'after the comment' """) - reader = IniReader(config._cfg) - x = reader.getargvlist("section", "key1") + reader = SectionReader("section", config._cfg) + x = reader.getargvlist("key1") assert x == [["cmd1", "with space", "after the comment"]] def test_argvlist_positional_substitution(self, tmpdir, newconfig): @@ -445,22 +447,22 @@ cmd2 {posargs:{item2} \ other} """) - reader = IniReader(config._cfg) + reader = SectionReader("section", config._cfg) posargs = ['hello', 'world'] reader.addsubstitutions(posargs, item2="value2") # py.test.raises(tox.exception.ConfigError, - # "reader.getargvlist('section', 'key1')") - assert reader.getargvlist('section', 'key1') == [] - argvlist = reader.getargvlist("section", "key2") + # "reader.getargvlist('key1')") + assert reader.getargvlist('key1') == [] + argvlist = reader.getargvlist("key2") assert argvlist[0] == ["cmd1"] + posargs assert argvlist[1] == ["cmd2"] + posargs - reader = IniReader(config._cfg) + reader = SectionReader("section", config._cfg) reader.addsubstitutions([], item2="value2") # py.test.raises(tox.exception.ConfigError, - # "reader.getargvlist('section', 'key1')") - assert reader.getargvlist('section', 'key1') == [] - argvlist = reader.getargvlist("section", "key2") + # "reader.getargvlist('key1')") + assert reader.getargvlist('key1') == [] + argvlist = reader.getargvlist("key2") assert argvlist[0] == ["cmd1"] assert argvlist[1] == ["cmd2", "value2", "other"] @@ -472,10 +474,10 @@ cmd2 -f '{posargs}' cmd3 -f {posargs} """) - reader = IniReader(config._cfg) + reader = SectionReader("section", config._cfg) reader.addsubstitutions(["foo", "bar"]) - assert reader.getargvlist('section', 'key1') == [] - x = reader.getargvlist("section", "key2") + assert reader.getargvlist('key1') == [] + x = reader.getargvlist("key2") assert x == [["cmd1", "--foo-args=foo bar"], ["cmd2", "-f", "foo bar"], ["cmd3", "-f", "foo", "bar"]] @@ -486,10 +488,10 @@ key2= cmd1 -f {posargs} """) - reader = IniReader(config._cfg) + reader = SectionReader("section", config._cfg) reader.addsubstitutions(["foo", "'bar", "baz'"]) - assert reader.getargvlist('section', 'key1') == [] - x = reader.getargvlist("section", "key2") + assert reader.getargvlist('key1') == [] + x = reader.getargvlist("key2") assert x == [["cmd1", "-f", "foo", "bar baz"]] def test_positional_arguments_are_only_replaced_when_standing_alone(self, tmpdir, newconfig): @@ -501,11 +503,11 @@ cmd2 -m '\'something\'' [] cmd3 something[]else """) - reader = IniReader(config._cfg) + reader = SectionReader("section", config._cfg) posargs = ['hello', 'world'] reader.addsubstitutions(posargs) - argvlist = reader.getargvlist('section', 'key') + argvlist = reader.getargvlist('key') assert argvlist[0] == ['cmd0'] + posargs assert argvlist[1] == ['cmd1', '-m', '[abc]'] assert argvlist[2] == ['cmd2', '-m', "something"] + posargs @@ -517,32 +519,32 @@ key = py.test -n5 --junitxml={envlogdir}/junit-{envname}.xml [] """ config = newconfig(inisource) - reader = IniReader(config._cfg) + reader = SectionReader("section", config._cfg) posargs = ['hello', 'world'] reader.addsubstitutions(posargs, envlogdir='ENV_LOG_DIR', envname='ENV_NAME') expected = [ 'py.test', '-n5', '--junitxml=ENV_LOG_DIR/junit-ENV_NAME.xml', 'hello', 'world' ] - assert reader.getargvlist('section', 'key')[0] == expected + assert reader.getargvlist('key')[0] == expected def test_getargv(self, newconfig): config = newconfig(""" [section] key=some command "with quoting" """) - reader = IniReader(config._cfg) + reader = SectionReader("section", config._cfg) expected = ['some', 'command', 'with quoting'] - assert reader.getargv('section', 'key') == expected + assert reader.getargv('key') == expected def test_getpath(self, tmpdir, newconfig): config = newconfig(""" [section] path1={HELLO} """) - reader = IniReader(config._cfg) + reader = SectionReader("section", config._cfg) reader.addsubstitutions(toxinidir=tmpdir, HELLO="mypath") - x = reader.getpath("section", "path1", tmpdir) + x = reader.getpath("path1", tmpdir) assert x == tmpdir.join("mypath") def test_getbool(self, tmpdir, newconfig): @@ -554,13 +556,13 @@ key2a=falsE key5=yes """) - reader = IniReader(config._cfg) - assert reader.getbool("section", "key1") is True - assert reader.getbool("section", "key1a") is True - assert reader.getbool("section", "key2") is False - assert reader.getbool("section", "key2a") is False - py.test.raises(KeyError, 'reader.getbool("section", "key3")') - py.test.raises(tox.exception.ConfigError, 'reader.getbool("section", "key5")') + reader = SectionReader("section", config._cfg) + assert reader.getbool("key1") is True + assert reader.getbool("key1a") is True + assert reader.getbool("key2") is False + assert reader.getbool("key2a") is False + py.test.raises(KeyError, 'reader.getbool("key3")') + py.test.raises(tox.exception.ConfigError, 'reader.getbool("key5")') class TestConfigTestEnv: @@ -586,7 +588,7 @@ assert envconfig.commands == [["xyz", "--abc"]] assert envconfig.changedir == config.setupdir assert envconfig.sitepackages is False - assert envconfig.develop is False + assert envconfig.usedevelop is False assert envconfig.ignore_errors is False assert envconfig.envlogdir == envconfig.envdir.join("log") assert list(envconfig.setenv.keys()) == ['PYTHONHASHSEED'] @@ -607,7 +609,7 @@ [testenv] usedevelop = True """) - assert not config.envconfigs["python"].develop + assert not config.envconfigs["python"].usedevelop def test_specific_command_overrides(self, tmpdir, newconfig): config = newconfig(""" @@ -1382,7 +1384,7 @@ def test_noset(self, tmpdir, newconfig): args = ['--hashseed', 'noset'] envconfig = self._get_envconfig(newconfig, args=args) - assert envconfig.setenv is None + assert envconfig.setenv == {} def test_noset_with_setenv(self, tmpdir, newconfig): tox_ini = """ @@ -1571,31 +1573,16 @@ ]) -class TestArgumentParser: - - def test_dash_e_single_1(self): - parser = prepare_parse('testpkg') - args = parser.parse_args('-e py26'.split()) - envlist = _split_env(args.env) - assert envlist == ['py26'] - - def test_dash_e_single_2(self): - parser = prepare_parse('testpkg') - args = parser.parse_args('-e py26,py33'.split()) - envlist = _split_env(args.env) - assert envlist == ['py26', 'py33'] - - def test_dash_e_same(self): - parser = prepare_parse('testpkg') - args = parser.parse_args('-e py26,py26'.split()) - envlist = _split_env(args.env) - assert envlist == ['py26', 'py26'] - - def test_dash_e_combine(self): - parser = prepare_parse('testpkg') - args = parser.parse_args('-e py26,py25,py33 -e py33,py27'.split()) - envlist = _split_env(args.env) - assert envlist == ['py26', 'py25', 'py33', 'py33', 'py27'] + at pytest.mark.parametrize("cmdline,envlist", [ + ("-e py26", ['py26']), + ("-e py26,py33", ['py26', 'py33']), + ("-e py26,py26", ['py26', 'py26']), + ("-e py26,py33 -e py33,py27", ['py26', 'py33', 'py33', 'py27']) +]) +def test_env_spec(cmdline, envlist): + args = cmdline.split() + config = parseconfig(args) + assert config.envlist == envlist class TestCommandParser: diff -r d7d35b623979d2e8186798c5bd29a679ef825c56 -r 06bb44d51a4c4d02f643da800d47a7197cfab32c tests/test_interpreters.py --- a/tests/test_interpreters.py +++ b/tests/test_interpreters.py @@ -3,11 +3,13 @@ import pytest from tox.interpreters import * # noqa +from tox._config import get_plugin_manager @pytest.fixture def interpreters(): - return Interpreters() + pm = get_plugin_manager() + return Interpreters(hook=pm.hook) @pytest.mark.skipif("sys.platform != 'win32'") @@ -28,8 +30,11 @@ assert locate_via_py('3', '2') == sys.executable -def test_find_executable(): - p = find_executable(sys.executable) +def test_tox_get_python_executable(): + class envconfig: + basepython = sys.executable + envname = "pyxx" + p = tox_get_python_executable(envconfig) assert p == py.path.local(sys.executable) for ver in [""] + "2.4 2.5 2.6 2.7 3.0 3.1 3.2 3.3".split(): name = "python%s" % ver @@ -42,7 +47,8 @@ else: if not py.path.local.sysfind(name): continue - p = find_executable(name) + envconfig.basepython = name + p = tox_get_python_executable(envconfig) assert p popen = py.std.subprocess.Popen([str(p), '-V'], stderr=py.std.subprocess.PIPE) @@ -55,7 +61,12 @@ def sysfind(x): return "hello" monkeypatch.setattr(py.path.local, "sysfind", sysfind) - t = find_executable("qweqwe") + + class envconfig: + basepython = "1lk23j" + envname = "pyxx" + + t = tox_get_python_executable(envconfig) assert t == "hello" @@ -69,31 +80,33 @@ class TestInterpreters: - def test_get_info_self_exceptions(self, interpreters): - pytest.raises(ValueError, lambda: - interpreters.get_info()) - pytest.raises(ValueError, lambda: - interpreters.get_info(name="12", executable="123")) + def test_get_executable(self, interpreters): + class envconfig: + basepython = sys.executable + envname = "pyxx" - def test_get_executable(self, interpreters): - x = interpreters.get_executable(sys.executable) + x = interpreters.get_executable(envconfig) assert x == sys.executable - assert not interpreters.get_executable("12l3k1j23") - - def test_get_info__name(self, interpreters): - info = interpreters.get_info(executable=sys.executable) + info = interpreters.get_info(envconfig) assert info.version_info == tuple(sys.version_info) assert info.executable == sys.executable assert info.runnable - def test_get_info__name_not_exists(self, interpreters): - info = interpreters.get_info("qlwkejqwe") + def test_get_executable_no_exist(self, interpreters): + class envconfig: + basepython = "1lkj23" + envname = "pyxx" + assert not interpreters.get_executable(envconfig) + info = interpreters.get_info(envconfig) assert not info.version_info - assert info.name == "qlwkejqwe" + assert info.name == "1lkj23" assert not info.executable assert not info.runnable def test_get_sitepackagesdir_error(self, interpreters): - info = interpreters.get_info(sys.executable) + class envconfig: + basepython = sys.executable + envname = "123" + info = interpreters.get_info(envconfig) s = interpreters.get_sitepackagesdir(info, "") assert s diff -r d7d35b623979d2e8186798c5bd29a679ef825c56 -r 06bb44d51a4c4d02f643da800d47a7197cfab32c tests/test_venv.py --- a/tests/test_venv.py +++ b/tests/test_venv.py @@ -35,6 +35,7 @@ py.test.raises(tox.exception.UnsupportedInterpreter, venv.getsupportedinterpreter) monkeypatch.undo() + monkeypatch.setattr(venv.envconfig, "envname", "py1") monkeypatch.setattr(venv.envconfig, 'basepython', 'notexistingpython') py.test.raises(tox.exception.InterpreterNotFound, venv.getsupportedinterpreter) @@ -42,7 +43,7 @@ # check that we properly report when no version_info is present info = NoInterpreterInfo(name=venv.name) info.executable = "something" - monkeypatch.setattr(config.interpreters, "get_info", lambda *args: info) + monkeypatch.setattr(config.interpreters, "get_info", lambda *args, **kw: info) pytest.raises(tox.exception.InvocationError, venv.getsupportedinterpreter) @@ -65,7 +66,7 @@ # assert Envconfig.toxworkdir in args assert venv.getcommandpath("easy_install", cwd=py.path.local()) interp = venv._getliveconfig().python - assert interp == venv.envconfig._basepython_info.executable + assert interp == venv.envconfig.python_info.executable assert venv.path_config.check(exists=False) @@ -463,7 +464,7 @@ venv = VirtualEnv(envconfig, session=mocksession) venv.update() cconfig = venv._getliveconfig() - cconfig.develop = True + cconfig.usedevelop = True cconfig.writeconfig(venv.path_config) mocksession._clearmocks() venv.update() diff -r d7d35b623979d2e8186798c5bd29a679ef825c56 -r 06bb44d51a4c4d02f643da800d47a7197cfab32c tox.ini --- a/tox.ini +++ b/tox.ini @@ -5,8 +5,10 @@ commands=echo {posargs} [testenv] -commands=py.test --junitxml={envlogdir}/junit-{envname}.xml {posargs} +commands= py.test --timeout=60 {posargs} + deps=pytest>=2.3.5 + pytest-timeout [testenv:docs] basepython=python @@ -14,15 +16,16 @@ deps=sphinx {[testenv]deps} commands= - py.test -v \ - --junitxml={envlogdir}/junit-{envname}.xml \ - check_sphinx.py {posargs} + py.test -v check_sphinx.py {posargs} [testenv:flakes] +qwe = 123 deps = pytest-flakes>=0.2 pytest-pep8 -commands = py.test -x --flakes --pep8 tox tests +commands = + py.test --flakes -m flakes tox tests + py.test --pep8 -m pep8 tox tests [testenv:dev] # required to make looponfail reload on every source code change diff -r d7d35b623979d2e8186798c5bd29a679ef825c56 -r 06bb44d51a4c4d02f643da800d47a7197cfab32c tox/__init__.py --- a/tox/__init__.py +++ b/tox/__init__.py @@ -1,6 +1,8 @@ # __version__ = '2.0.0.dev1' +from .hookspecs import hookspec, hookimpl # noqa + class exception: class Error(Exception): diff -r d7d35b623979d2e8186798c5bd29a679ef825c56 -r 06bb44d51a4c4d02f643da800d47a7197cfab32c tox/_cmdline.py --- a/tox/_cmdline.py +++ b/tox/_cmdline.py @@ -24,13 +24,35 @@ def main(args=None): try: - config = parseconfig(args, 'tox') + config = parseconfig(args) + if config.option.help: + show_help(config) + raise SystemExit(0) + elif config.option.helpini: + show_help_ini(config) + raise SystemExit(0) retcode = Session(config).runcommand() raise SystemExit(retcode) except KeyboardInterrupt: raise SystemExit(2) +def show_help(config): + tw = py.io.TerminalWriter() + tw.write(config._parser.format_help()) + tw.line() + + +def show_help_ini(config): + tw = py.io.TerminalWriter() + tw.sep("-", "per-testenv attributes") + for env_attr in config._testenv_attr: + tw.line("%-15s %-8s default: %s" % + (env_attr.name, "<" + env_attr.type + ">", env_attr.default), bold=True) + tw.line(env_attr.help) + tw.line() + + class Action(object): def __init__(self, session, venv, msg, args): self.venv = venv @@ -487,7 +509,7 @@ venv.status = "platform mismatch" continue # we simply omit non-matching platforms if self.setupenv(venv): - if venv.envconfig.develop: + if venv.envconfig.usedevelop: self.developpkg(venv, self.config.setupdir) elif self.config.skipsdist or venv.envconfig.skip_install: self.finishvenv(venv) @@ -551,8 +573,7 @@ for envconfig in self.config.envconfigs.values(): self.report.line("[testenv:%s]" % envconfig.envname, bold=True) self.report.line(" basepython=%s" % envconfig.basepython) - self.report.line(" _basepython_info=%s" % - envconfig._basepython_info) + self.report.line(" pythoninfo=%s" % (envconfig.python_info,)) self.report.line(" envpython=%s" % envconfig.envpython) self.report.line(" envtmpdir=%s" % envconfig.envtmpdir) self.report.line(" envbindir=%s" % envconfig.envbindir) @@ -567,7 +588,7 @@ self.report.line(" deps=%s" % envconfig.deps) self.report.line(" envdir= %s" % envconfig.envdir) self.report.line(" downloadcache=%s" % envconfig.downloadcache) - self.report.line(" usedevelop=%s" % envconfig.develop) + self.report.line(" usedevelop=%s" % envconfig.usedevelop) def showenvs(self): for env in self.config.envlist: diff -r d7d35b623979d2e8186798c5bd29a679ef825c56 -r 06bb44d51a4c4d02f643da800d47a7197cfab32c tox/_config.py --- a/tox/_config.py +++ b/tox/_config.py @@ -8,8 +8,10 @@ import string import pkg_resources import itertools +import pluggy -from tox.interpreters import Interpreters +import tox.interpreters +from tox import hookspecs import py @@ -22,20 +24,161 @@ for version in '24,25,26,27,30,31,32,33,34,35'.split(','): default_factors['py' + version] = 'python%s.%s' % tuple(version) +hookimpl = pluggy.HookimplMarker("tox") -def parseconfig(args=None, pkg=None): + +def get_plugin_manager(): + # initialize plugin manager + pm = pluggy.PluginManager("tox") + pm.add_hookspecs(hookspecs) + pm.register(tox._config) + pm.register(tox.interpreters) + pm.load_setuptools_entrypoints("tox") + pm.check_pending() + return pm + + +class MyParser: + def __init__(self): + self.argparser = argparse.ArgumentParser( + description="tox options", add_help=False) + self._testenv_attr = [] + + def add_argument(self, *args, **kwargs): + return self.argparser.add_argument(*args, **kwargs) + + def add_testenv_attribute(self, name, type, help, default=None, postprocess=None): + self._testenv_attr.append(VenvAttribute(name, type, default, help, postprocess)) + + def add_testenv_attribute_obj(self, obj): + assert hasattr(obj, "name") + assert hasattr(obj, "type") + assert hasattr(obj, "help") + assert hasattr(obj, "postprocess") + self._testenv_attr.append(obj) + + def parse_args(self, args): + return self.argparser.parse_args(args) + + def format_help(self): + return self.argparser.format_help() + + +class VenvAttribute: + def __init__(self, name, type, default, help, postprocess): + self.name = name + self.type = type + self.default = default + self.help = help + self.postprocess = postprocess + + +class DepOption: + name = "deps" + type = "line-list" + help = "each line specifies a dependency in pip/setuptools format." + default = () + + def postprocess(self, config, reader, section_val): + deps = [] + for depline in section_val: + m = re.match(r":(\w+):\s*(\S+)", depline) + if m: + iname, name = m.groups() + ixserver = config.indexserver[iname] + else: + name = depline.strip() + ixserver = None + name = self._replace_forced_dep(name, config) + deps.append(DepConfig(name, ixserver)) + return deps + + def _replace_forced_dep(self, name, config): + """ + Override the given dependency config name taking --force-dep-version + option into account. + + :param name: dep config, for example ["pkg==1.0", "other==2.0"]. + :param config: Config instance + :return: the new dependency that should be used for virtual environments + """ + if not config.option.force_dep: + return name + for forced_dep in config.option.force_dep: + if self._is_same_dep(forced_dep, name): + return forced_dep + return name + + @classmethod + def _is_same_dep(cls, dep1, dep2): + """ + Returns True if both dependency definitions refer to the + same package, even if versions differ. + """ + dep1_name = pkg_resources.Requirement.parse(dep1).project_name + dep2_name = pkg_resources.Requirement.parse(dep2).project_name + return dep1_name == dep2_name + + +class PosargsOption: + name = "args_are_paths" + type = "bool" + default = True + help = "treat positional args in commands as paths" + + def postprocess(self, config, reader, section_val): + args = config.option.args + if args: + if section_val: + args = [] + for arg in config.option.args: + if arg: + origpath = config.invocationcwd.join(arg, abs=True) + if origpath.check(): + arg = reader.getpath("changedir", ".").bestrelpath(origpath) + args.append(arg) + reader.addsubstitutions(args) + return section_val + + +class InstallcmdOption: + name = "install_command" + type = "argv" + default = "pip install {opts} {packages}" + help = "install command for dependencies and package under test." + + def postprocess(self, config, reader, section_val): + if '{packages}' not in section_val: + raise tox.exception.ConfigError( + "'install_command' must contain '{packages}' substitution") + return section_val + + +def parseconfig(args=None): """ :param list[str] args: Optional list of arguments. :type pkg: str :rtype: :class:`Config` :raise SystemExit: toxinit file is not found """ + + pm = get_plugin_manager() + if args is None: args = sys.argv[1:] - parser = prepare_parse(pkg) - opts = parser.parse_args(args) - config = Config() - config.option = opts + + # prepare command line options + parser = MyParser() + pm.hook.tox_addoption(parser=parser) + + # parse command line options + option = parser.parse_args(args) + interpreters = tox.interpreters.Interpreters(hook=pm.hook) + config = Config(pluginmanager=pm, option=option, interpreters=interpreters) + config._parser = parser + config._testenv_attr = parser._testenv_attr + + # parse ini file basename = config.option.configfile if os.path.isabs(basename): inipath = py.path.local(basename) @@ -52,6 +195,10 @@ exn = sys.exc_info()[1] # Use stdout to match test expectations py.builtin.print_("ERROR: " + str(exn)) + + # post process config object + pm.hook.tox_configure(config=config) + return config @@ -63,10 +210,8 @@ class VersionAction(argparse.Action): def __call__(self, argparser, *args, **kwargs): - name = argparser.pkgname - mod = __import__(name) - version = mod.__version__ - py.builtin.print_("%s imported from %s" % (version, mod.__file__)) + version = tox.__version__ + py.builtin.print_("%s imported from %s" % (version, tox.__file__)) raise SystemExit(0) @@ -78,13 +223,16 @@ setattr(namespace, self.dest, 0) -def prepare_parse(pkgname): - parser = argparse.ArgumentParser(description=__doc__,) + at hookimpl +def tox_addoption(parser): # formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser.pkgname = pkgname parser.add_argument("--version", nargs=0, action=VersionAction, dest="version", help="report version information to stdout.") + parser.add_argument("-h", "--help", action="store_true", dest="help", + help="show help about options") + parser.add_argument("--help-ini", "--hi", action="store_true", dest="helpini", + help="show help about ini-names") parser.add_argument("-v", nargs=0, action=CountAction, default=0, dest="verbosity", help="increase verbosity of reporting output.") @@ -130,6 +278,7 @@ "all commands and results involved. This will turn off " "pass-through output from running test commands which is " "instead captured into the json result file.") + # We choose 1 to 4294967295 because it is the range of PYTHONHASHSEED. parser.add_argument("--hashseed", action="store", metavar="SEED", default=None, @@ -149,14 +298,144 @@ parser.add_argument("args", nargs="*", help="additional arguments available to command positional substitution") - return parser + + # add various core venv interpreter attributes + + parser.add_testenv_attribute( + name="envdir", type="path", default="{toxworkdir}/{envname}", + help="venv directory") + + parser.add_testenv_attribute( + name="envtmpdir", type="path", default="{envdir}/tmp", + help="venv temporary directory") + + parser.add_testenv_attribute( + name="envlogdir", type="path", default="{envdir}/log", + help="venv log directory") + + def downloadcache(config, reader, section_val): + if section_val: + # env var, if present, takes precedence + downloadcache = os.environ.get("PIP_DOWNLOAD_CACHE", section_val) + return py.path.local(downloadcache) + + parser.add_testenv_attribute( + name="downloadcache", type="string", default=None, postprocess=downloadcache, + help="(deprecated) set PIP_DOWNLOAD_CACHE.") + + parser.add_testenv_attribute( + name="changedir", type="path", default="{toxinidir}", + help="directory to change to when running commands") + + parser.add_testenv_attribute_obj(PosargsOption()) + + parser.add_testenv_attribute( + name="skip_install", type="bool", default=False, + help="Do not install the current package. This can be used when " + "you need the virtualenv management but do not want to install " + "the current package") + + parser.add_testenv_attribute( + name="ignore_errors", type="bool", default=False, + help="if set to True all commands will be executed irrespective of their " + "result error status.") + + def recreate(config, reader, section_val): + if config.option.recreate: + return True + return section_val + + parser.add_testenv_attribute( + name="recreate", type="bool", default=False, postprocess=recreate, + help="always recreate this test environment.") + + def setenv(config, reader, section_val): + setenv = section_val + if "PYTHONHASHSEED" not in setenv and config.hashseed is not None: + setenv['PYTHONHASHSEED'] = config.hashseed + return setenv + + parser.add_testenv_attribute( + name="setenv", type="dict", postprocess=setenv, + help="list of X=Y lines with environment variable settings") + + def passenv(config, reader, section_val): + passenv = set(["PATH"]) + if sys.platform == "win32": + passenv.add("SYSTEMROOT") # needed for python's crypto module + passenv.add("PATHEXT") # needed for discovering executables + for spec in section_val: + for name in os.environ: + if fnmatchcase(name.upper(), spec.upper()): + passenv.add(name) + return passenv + + parser.add_testenv_attribute( + name="passenv", type="space-separated-list", postprocess=passenv, + help="environment variables names which shall be passed " + "from tox invocation to test environment when executing commands.") + + parser.add_testenv_attribute( + name="whitelist_externals", type="line-list", + help="each lines specifies a path or basename for which tox will not warn " + "about it coming from outside the test environment.") + + parser.add_testenv_attribute( + name="platform", type="string", default=".*", + help="regular expression which must match against ``sys.platform``. " + "otherwise testenv will be skipped.") + + def sitepackages(config, reader, section_val): + return config.option.sitepackages or section_val + + parser.add_testenv_attribute( + name="sitepackages", type="bool", default=False, postprocess=sitepackages, + help="Set to ``True`` if you want to create virtual environments that also " + "have access to globally installed packages.") + + def pip_pre(config, reader, section_val): + return config.option.pre or section_val + + parser.add_testenv_attribute( + name="pip_pre", type="bool", default=False, postprocess=pip_pre, + help="If ``True``, adds ``--pre`` to the ``opts`` passed to " + "the install command. ") + + def develop(config, reader, section_val): + return not config.option.installpkg and (section_val or config.option.develop) + + parser.add_testenv_attribute( + name="usedevelop", type="bool", postprocess=develop, default=False, + help="install package in develop/editable mode") + + def basepython_default(config, reader, section_val): + if section_val is None: + for f in reader.factors: + if f in default_factors: + return default_factors[f] + return sys.executable + return str(section_val) + + parser.add_testenv_attribute( + name="basepython", type="string", default=None, postprocess=basepython_default, + help="executable name or path of interpreter used to create a " + "virtual test environment.") + + parser.add_testenv_attribute_obj(InstallcmdOption()) + parser.add_testenv_attribute_obj(DepOption()) + + parser.add_testenv_attribute( + name="commands", type="argvlist", default="", + help="each line specifies a test command and can use substitution.") class Config(object): - def __init__(self): + def __init__(self, pluginmanager, option, interpreters): self.envconfigs = {} self.invocationcwd = py.path.local() - self.interpreters = Interpreters() + self.interpreters = interpreters + self.pluginmanager = pluginmanager + self.option = option @property def homedir(self): @@ -192,16 +471,20 @@ def envsitepackagesdir(self): self.getsupportedinterpreter() # for throwing exceptions x = self.config.interpreters.get_sitepackagesdir( - info=self._basepython_info, + info=self.python_info, envdir=self.envdir) return x + @property + def python_info(self): + return self.config.interpreters.get_info(envconfig=self) + def getsupportedinterpreter(self): if sys.platform == "win32" and self.basepython and \ "jython" in self.basepython: raise tox.exception.UnsupportedInterpreter( "Jython/Windows does not support installing scripts") - info = self.config.interpreters.get_info(self.basepython) + info = self.config.interpreters.get_info(envconfig=self) if not info.executable: raise tox.exception.InterpreterNotFound(self.basepython) if not info.version_info: @@ -240,12 +523,10 @@ self.config = config ctxname = getcontextname() if ctxname == "jenkins": - reader = IniReader(self._cfg, fallbacksections=['tox']) - toxsection = "tox:%s" % ctxname + reader = SectionReader("tox:jenkins", self._cfg, fallbacksections=['tox']) distshare_default = "{toxworkdir}/distshare" elif not ctxname: - reader = IniReader(self._cfg) - toxsection = "tox" + reader = SectionReader("tox", self._cfg) distshare_default = "{homedir}/.tox/distshare" else: raise ValueError("invalid context") @@ -260,18 +541,17 @@ reader.addsubstitutions(toxinidir=config.toxinidir, homedir=config.homedir) - config.toxworkdir = reader.getpath(toxsection, "toxworkdir", - "{toxinidir}/.tox") - config.minversion = reader.getdefault(toxsection, "minversion", None) + config.toxworkdir = reader.getpath("toxworkdir", "{toxinidir}/.tox") + config.minversion = reader.getstring("minversion", None) if not config.option.skip_missing_interpreters: config.option.skip_missing_interpreters = \ - reader.getbool(toxsection, "skip_missing_interpreters", False) + reader.getbool("skip_missing_interpreters", False) # determine indexserver dictionary config.indexserver = {'default': IndexServerConfig('default')} prefix = "indexserver" - for line in reader.getlist(toxsection, prefix): + for line in reader.getlist(prefix): name, url = map(lambda x: x.strip(), line.split("=", 1)) config.indexserver[name] = IndexServerConfig(name, url) @@ -296,16 +576,15 @@ config.indexserver[name] = IndexServerConfig(name, override) reader.addsubstitutions(toxworkdir=config.toxworkdir) - config.distdir = reader.getpath(toxsection, "distdir", "{toxworkdir}/dist") + config.distdir = reader.getpath("distdir", "{toxworkdir}/dist") reader.addsubstitutions(distdir=config.distdir) - config.distshare = reader.getpath(toxsection, "distshare", - distshare_default) + config.distshare = reader.getpath("distshare", distshare_default) reader.addsubstitutions(distshare=config.distshare) - config.sdistsrc = reader.getpath(toxsection, "sdistsrc", None) - config.setupdir = reader.getpath(toxsection, "setupdir", "{toxinidir}") + config.sdistsrc = reader.getpath("sdistsrc", None) + config.setupdir = reader.getpath("setupdir", "{toxinidir}") config.logdir = config.toxworkdir.join("log") - config.envlist, all_envs = self._getenvdata(reader, toxsection) + config.envlist, all_envs = self._getenvdata(reader) # factors used in config or predefined known_factors = self._list_section_factors("testenv") @@ -313,7 +592,7 @@ known_factors.add("python") # factors stated in config envlist - stated_envlist = reader.getdefault(toxsection, "envlist", replace=False) + stated_envlist = reader.getstring("envlist", replace=False) if stated_envlist: for env in _split_env(stated_envlist): known_factors.update(env.split('-')) @@ -324,13 +603,13 @@ factors = set(name.split('-')) if section in self._cfg or factors <= known_factors: config.envconfigs[name] = \ - self._makeenvconfig(name, section, reader._subs, config) + self.make_envconfig(name, section, reader._subs, config) all_develop = all(name in config.envconfigs - and config.envconfigs[name].develop + and config.envconfigs[name].usedevelop for name in config.envlist) - config.skipsdist = reader.getbool(toxsection, "skipsdist", all_develop) + config.skipsdist = reader.getbool("skipsdist", all_develop) def _list_section_factors(self, section): factors = set() @@ -340,116 +619,42 @@ factors.update(*mapcat(_split_factor_expr, exprs)) return factors - def _makeenvconfig(self, name, section, subs, config): + def make_envconfig(self, name, section, subs, config): vc = VenvConfig(config=config, envname=name) factors = set(name.split('-')) - reader = IniReader(self._cfg, fallbacksections=["testenv"], factors=factors) + reader = SectionReader(section, self._cfg, fallbacksections=["testenv"], + factors=factors) reader.addsubstitutions(**subs) - vc.develop = ( - not config.option.installpkg - and reader.getbool(section, "usedevelop", config.option.develop)) - vc.envdir = reader.getpath(section, "envdir", "{toxworkdir}/%s" % name) - vc.args_are_paths = reader.getbool(section, "args_are_paths", True) - if reader.getdefault(section, "python", None): - raise tox.exception.ConfigError( - "'python=' key was renamed to 'basepython='") - bp = next((default_factors[f] for f in factors if f in default_factors), - sys.executable) - vc.basepython = reader.getdefault(section, "basepython", bp) - vc._basepython_info = config.interpreters.get_info(vc.basepython) - reader.addsubstitutions(envdir=vc.envdir, envname=vc.envname, - envbindir=vc.envbindir, envpython=vc.envpython, - envsitepackagesdir=vc.envsitepackagesdir) - vc.envtmpdir = reader.getpath(section, "tmpdir", "{envdir}/tmp") - vc.envlogdir = reader.getpath(section, "envlogdir", "{envdir}/log") - reader.addsubstitutions(envlogdir=vc.envlogdir, envtmpdir=vc.envtmpdir) - vc.changedir = reader.getpath(section, "changedir", "{toxinidir}") - if config.option.recreate: - vc.recreate = True - else: - vc.recreate = reader.getbool(section, "recreate", False) - args = config.option.args - if args: - if vc.args_are_paths: - args = [] - for arg in config.option.args: - if arg: - origpath = config.invocationcwd.join(arg, abs=True) - if origpath.check(): - arg = vc.changedir.bestrelpath(origpath) - args.append(arg) - reader.addsubstitutions(args) - setenv = {} - if config.hashseed is not None: - setenv['PYTHONHASHSEED'] = config.hashseed - setenv.update(reader.getdict(section, 'setenv')) + reader.addsubstitutions(envname=name) - # read passenv - vc.passenv = set(["PATH"]) - if sys.platform == "win32": - vc.passenv.add("SYSTEMROOT") # needed for python's crypto module - vc.passenv.add("PATHEXT") # needed for discovering executables - for spec in reader.getlist(section, "passenv", sep=" "): - for name in os.environ: - if fnmatchcase(name.lower(), spec.lower()): - vc.passenv.add(name) + for env_attr in config._testenv_attr: + atype = env_attr.type + if atype in ("bool", "path", "string", "dict", "argv", "argvlist"): + meth = getattr(reader, "get" + atype) + res = meth(env_attr.name, env_attr.default) + elif atype == "space-separated-list": + res = reader.getlist(env_attr.name, sep=" ") + elif atype == "line-list": + res = reader.getlist(env_attr.name, sep="\n") + else: + raise ValueError("unknown type %r" % (atype,)) - vc.setenv = setenv - if not vc.setenv: - vc.setenv = None + if env_attr.postprocess: + res = env_attr.postprocess(config, reader, res) + setattr(vc, env_attr.name, res) - vc.commands = reader.getargvlist(section, "commands") - vc.whitelist_externals = reader.getlist(section, - "whitelist_externals") - vc.deps = [] - for depline in reader.getlist(section, "deps"): - m = re.match(r":(\w+):\s*(\S+)", depline) - if m: - iname, name = m.groups() - ixserver = config.indexserver[iname] - else: - name = depline.strip() - ixserver = None - name = self._replace_forced_dep(name, config) - vc.deps.append(DepConfig(name, ixserver)) + if atype == "path": + reader.addsubstitutions(**{env_attr.name: res}) - platform = "" - for platform in reader.getlist(section, "platform"): - if platform.strip(): - break - vc.platform = platform - - vc.sitepackages = ( - self.config.option.sitepackages - or reader.getbool(section, "sitepackages", False)) - - vc.downloadcache = None - downloadcache = reader.getdefault(section, "downloadcache") - if downloadcache: - # env var, if present, takes precedence - downloadcache = os.environ.get("PIP_DOWNLOAD_CACHE", downloadcache) - vc.downloadcache = py.path.local(downloadcache) - - vc.install_command = reader.getargv( - section, - "install_command", - "pip install {opts} {packages}", - ) - if '{packages}' not in vc.install_command: - raise tox.exception.ConfigError( - "'install_command' must contain '{packages}' substitution") - vc.pip_pre = config.option.pre or reader.getbool( - section, "pip_pre", False) - - vc.skip_install = reader.getbool(section, "skip_install", False) - vc.ignore_errors = reader.getbool(section, "ignore_errors", False) - + if env_attr.name == "install_command": + reader.addsubstitutions(envbindir=vc.envbindir, envpython=vc.envpython, + envsitepackagesdir=vc.envsitepackagesdir) return vc - def _getenvdata(self, reader, toxsection): + def _getenvdata(self, reader): envstr = self.config.option.env \ or os.environ.get("TOXENV") \ - or reader.getdefault(toxsection, "envlist", replace=False) \ + or reader.getstring("envlist", replace=False) \ or [] envlist = _split_env(envstr) @@ -466,32 +671,6 @@ return envlist, all_envs - def _replace_forced_dep(self, name, config): - """ - Override the given dependency config name taking --force-dep-version - option into account. - - :param name: dep config, for example ["pkg==1.0", "other==2.0"]. - :param config: Config instance - :return: the new dependency that should be used for virtual environments - """ - if not config.option.force_dep: - return name - for forced_dep in config.option.force_dep: - if self._is_same_dep(forced_dep, name): - return forced_dep - return name - - @classmethod - def _is_same_dep(cls, dep1, dep2): - """ - Returns True if both dependency definitions refer to the - same package, even if versions differ. - """ - dep1_name = pkg_resources.Requirement.parse(dep1).project_name - dep2_name = pkg_resources.Requirement.parse(dep2).project_name - return dep1_name == dep2_name - def _split_env(env): """if handed a list, action="append" was used for -e """ @@ -558,8 +737,9 @@ re.VERBOSE) -class IniReader: - def __init__(self, cfgparser, fallbacksections=None, factors=()): +class SectionReader: + def __init__(self, section_name, cfgparser, fallbacksections=None, factors=()): + self.section_name = section_name self._cfg = cfgparser self.fallbacksections = fallbacksections or [] self.factors = factors @@ -571,129 +751,39 @@ if _posargs: self.posargs = _posargs - def getpath(self, section, name, defaultpath): + def getpath(self, name, defaultpath): toxinidir = self._subs['toxinidir'] - path = self.getdefault(section, name, defaultpath) + path = self.getstring(name, defaultpath) if path is None: return path return toxinidir.join(path, abs=True) - def getlist(self, section, name, sep="\n"): - s = self.getdefault(section, name, None) + def getlist(self, name, sep="\n"): + s = self.getstring(name, None) if s is None: return [] return [x.strip() for x in s.split(sep) if x.strip()] - def getdict(self, section, name, sep="\n"): - s = self.getdefault(section, name, None) + def getdict(self, name, default=None, sep="\n"): + s = self.getstring(name, None) if s is None: - return {} + return default or {} value = {} for line in s.split(sep): - if not line.strip(): - continue - name, rest = line.split('=', 1) - value[name.strip()] = rest.strip() + if line.strip(): + name, rest = line.split('=', 1) + value[name.strip()] = rest.strip() return value - def getargvlist(self, section, name): - """Get arguments for every parsed command. - - :param str section: Section name in the configuration. - :param str name: Key name in a section. - :rtype: list[list[str]] - :raise :class:`tox.exception.ConfigError`: - line-continuation ends nowhere while resolving for specified section - """ - content = self.getdefault(section, name, '', replace=False) - return self._parse_commands(section, name, content) - - def _parse_commands(self, section, name, content): - """Parse commands from key content in specified section. - - :param str section: Section name in the configuration. - :param str name: Key name in a section. - :param str content: Content stored by key. - - :rtype: list[list[str]] - :raise :class:`tox.exception.ConfigError`: - line-continuation ends nowhere while resolving for specified section - """ - commands = [] - current_command = "" - for line in content.splitlines(): - line = line.rstrip() - i = line.find("#") - if i != -1: - line = line[:i].rstrip() - if not line: - continue - if line.endswith("\\"): - current_command += " " + line[:-1] - continue - current_command += line - - if is_section_substitution(current_command): - replaced = self._replace(current_command) - commands.extend(self._parse_commands(section, name, replaced)) - else: - commands.append(self._processcommand(current_command)) - current_command = "" - else: - if current_command: - raise tox.exception.ConfigError( - "line-continuation ends nowhere while resolving for [%s] %s" % - (section, name)) - return commands - - def _processcommand(self, command): - posargs = getattr(self, "posargs", None) - - # Iterate through each word of the command substituting as - # appropriate to construct the new command string. This - # string is then broken up into exec argv components using - # shlex. - newcommand = "" - for word in CommandParser(command).words(): - if word == "{posargs}" or word == "[]": - if posargs: - newcommand += " ".join(posargs) - continue - elif word.startswith("{posargs:") and word.endswith("}"): - if posargs: - newcommand += " ".join(posargs) - continue - else: - word = word[9:-1] - new_arg = "" - new_word = self._replace(word) - new_word = self._replace(new_word) - new_arg += new_word - newcommand += new_arg - - # Construct shlex object that will not escape any values, - # use all values as is in argv. - shlexer = shlex.shlex(newcommand, posix=True) - shlexer.whitespace_split = True - shlexer.escape = '' - shlexer.commenters = '' - argv = list(shlexer) - return argv - - def getargv(self, section, name, default=None, replace=True): - command = self.getdefault( - section, name, default=default, replace=False) - return self._processcommand(command.strip()) - - def getbool(self, section, name, default=None): - s = self.getdefault(section, name, default) + def getbool(self, name, default=None): + s = self.getstring(name, default) if not s: s = default if s is None: raise KeyError("no config value [%s] %s found" % ( - section, name)) + self.section_name, name)) if not isinstance(s, bool): if s.lower() == "true": @@ -705,9 +795,16 @@ "boolean value %r needs to be 'True' or 'False'") return s - def getdefault(self, section, name, default=None, replace=True): + def getargvlist(self, name, default=""): + s = self.getstring(name, default, replace=False) + return _ArgvlistReader.getargvlist(self, s) + + def getargv(self, name, default=""): + return self.getargvlist(name, default)[0] + + def getstring(self, name, default=None, replace=True): x = None - for s in [section] + self.fallbacksections: + for s in [self.section_name] + self.fallbacksections: try: x = self._cfg[s][name] break @@ -720,12 +817,12 @@ x = self._apply_factors(x) if replace and x and hasattr(x, 'replace'): - self._subststack.append((section, name)) + self._subststack.append((self.section_name, name)) try: x = self._replace(x) finally: - assert self._subststack.pop() == (section, name) - # print "getdefault", section, name, "returned", repr(x) + assert self._subststack.pop() == (self.section_name, name) + # print "getstring", self.section_name, name, "returned", repr(x) return x def _apply_factors(self, s): @@ -821,8 +918,80 @@ return RE_ITEM_REF.sub(self._replace_match, x) return x - def _parse_command(self, command): - pass + +class _ArgvlistReader: + @classmethod + def getargvlist(cls, reader, section_val): + """Parse ``commands`` argvlist multiline string. + + :param str name: Key name in a section. + :param str section_val: Content stored by key. + + :rtype: list[list[str]] + :raise :class:`tox.exception.ConfigError`: + line-continuation ends nowhere while resolving for specified section + """ + commands = [] + current_command = "" + for line in section_val.splitlines(): + line = line.rstrip() + i = line.find("#") + if i != -1: + line = line[:i].rstrip() + if not line: + continue + if line.endswith("\\"): + current_command += " " + line[:-1] + continue + current_command += line + + if is_section_substitution(current_command): + replaced = reader._replace(current_command) + commands.extend(cls.getargvlist(reader, replaced)) + else: + commands.append(cls.processcommand(reader, current_command)) + current_command = "" + else: + if current_command: + raise tox.exception.ConfigError( + "line-continuation ends nowhere while resolving for [%s] %s" % + (reader.section_name, "commands")) + return commands + + @classmethod + def processcommand(cls, reader, command): + posargs = getattr(reader, "posargs", None) + + # Iterate through each word of the command substituting as + # appropriate to construct the new command string. This + # string is then broken up into exec argv components using + # shlex. + newcommand = "" + for word in CommandParser(command).words(): + if word == "{posargs}" or word == "[]": + if posargs: + newcommand += " ".join(posargs) + continue + elif word.startswith("{posargs:") and word.endswith("}"): + if posargs: + newcommand += " ".join(posargs) + continue + else: + word = word[9:-1] + new_arg = "" + new_word = reader._replace(word) + new_word = reader._replace(new_word) + new_arg += new_word + newcommand += new_arg + + # Construct shlex object that will not escape any values, + # use all values as is in argv. + shlexer = shlex.shlex(newcommand, posix=True) + shlexer.whitespace_split = True + shlexer.escape = '' + shlexer.commenters = '' + argv = list(shlexer) + return argv class CommandParser(object): diff -r d7d35b623979d2e8186798c5bd29a679ef825c56 -r 06bb44d51a4c4d02f643da800d47a7197cfab32c tox/_venv.py --- a/tox/_venv.py +++ b/tox/_venv.py @@ -10,17 +10,17 @@ class CreationConfig: def __init__(self, md5, python, version, sitepackages, - develop, deps): + usedevelop, deps): self.md5 = md5 self.python = python self.version = version self.sitepackages = sitepackages - self.develop = develop + self.usedevelop = usedevelop self.deps = deps def writeconfig(self, path): lines = ["%s %s" % (self.md5, self.python)] - lines.append("%s %d %d" % (self.version, self.sitepackages, self.develop)) + lines.append("%s %d %d" % (self.version, self.sitepackages, self.usedevelop)) for dep in self.deps: lines.append("%s %s" % dep) path.ensure() @@ -32,14 +32,14 @@ lines = path.readlines(cr=0) value = lines.pop(0).split(None, 1) md5, python = value - version, sitepackages, develop = lines.pop(0).split(None, 3) + version, sitepackages, usedevelop = lines.pop(0).split(None, 3) sitepackages = bool(int(sitepackages)) - develop = bool(int(develop)) + usedevelop = bool(int(usedevelop)) deps = [] for line in lines: md5, depstring = line.split(None, 1) deps.append((md5, depstring)) - return CreationConfig(md5, python, version, sitepackages, develop, deps) + return CreationConfig(md5, python, version, sitepackages, usedevelop, deps) except Exception: return None @@ -48,7 +48,7 @@ and self.python == other.python and self.version == other.version and self.sitepackages == other.sitepackages - and self.develop == other.develop + and self.usedevelop == other.usedevelop and self.deps == other.deps) @@ -143,11 +143,11 @@ self.envconfig.deps, v) def _getliveconfig(self): - python = self.envconfig._basepython_info.executable + python = self.envconfig.python_info.executable md5 = getdigest(python) version = tox.__version__ sitepackages = self.envconfig.sitepackages - develop = self.envconfig.develop + develop = self.envconfig.usedevelop deps = [] for dep in self._getresolvedeps(): raw_dep = dep.name @@ -321,11 +321,13 @@ for envname in self.envconfig.passenv: if envname in os.environ: env[envname] = os.environ[envname] - setenv = self.envconfig.setenv - if setenv: - env.update(setenv) + + env.update(self.envconfig.setenv) + env['VIRTUAL_ENV'] = str(self.path) + env.update(extraenv) + return env def test(self, redirect=False): diff -r d7d35b623979d2e8186798c5bd29a679ef825c56 -r 06bb44d51a4c4d02f643da800d47a7197cfab32c tox/hookspecs.py --- /dev/null +++ b/tox/hookspecs.py @@ -0,0 +1,32 @@ +""" Hook specifications for tox. + +""" + +from pluggy import HookspecMarker, HookimplMarker + +hookspec = HookspecMarker("tox") +hookimpl = HookimplMarker("tox") + + + at hookspec +def tox_addoption(parser): + """ add command line options to the argparse-style parser object.""" + + + at hookspec +def tox_configure(config): + """ called after command line options have been parsed and the ini-file has + been read. Please be aware that the config object layout may change as its + API was not designed yet wrt to providing stability (it was an internal + thing purely before tox-2.0). """ + + + at hookspec(firstresult=True) +def tox_get_python_executable(envconfig): + """ return a python executable for the given python base name. + The first plugin/hook which returns an executable path will determine it. + + ``envconfig`` is the testenv configuration which contains + per-testenv configuration, notably the ``.envname`` and ``.basepython`` + setting. + """ This diff is so big that we needed to truncate the remainder. Repository URL: https://bitbucket.org/hpk42/tox/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Tue May 12 10:43:24 2015 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 12 May 2015 08:43:24 -0000 Subject: [Pytest-commit] commit/tox: 2 new changesets Message-ID: <20150512084324.31662.20990@app07.ash-private.bitbucket.org> 2 new commits in tox: https://bitbucket.org/hpk42/tox/commits/40dbaec00a30/ Changeset: 40dbaec00a30 Branch: stevepiercy/insert-missing-the-for-grammar-rewrap-to-1431248739542 User: stevepiercy Date: 2015-05-10 09:05:39+00:00 Summary: insert missing "the" for grammar; rewrap to 79 cols Affected #: 1 file diff -r 850fb37c60625112b2bf5ad21373d2804be98336 -r 40dbaec00a301f145c72d97a7ff18fced80a93d3 doc/config.txt --- a/doc/config.txt +++ b/doc/config.txt @@ -537,8 +537,8 @@ Complex factor conditions +++++++++++++++++++++++++ -Sometimes you need to specify same line for several factors or create a special -case for a combination of factors. Here is how you do it:: +Sometimes you need to specify the same line for several factors or create a +special case for a combination of factors. Here is how you do it:: [tox] envlist = py{26,27,33}-django{15,16}-{sqlite,mysql} https://bitbucket.org/hpk42/tox/commits/453b0b5844f1/ Changeset: 453b0b5844f1 User: hpk42 Date: 2015-05-12 08:43:21+00:00 Summary: Merged in stevepiercy/tox-1/stevepiercy/insert-missing-the-for-grammar-rewrap-to-1431248739542 (pull request #157) insert missing "the" for grammar; rewrap to 79 cols Affected #: 1 file diff -r ee38b60cb1403506c703c30e31107fc594253cfb -r 453b0b5844f1482f598260c5f08ea3fcd3283740 doc/config.txt --- a/doc/config.txt +++ b/doc/config.txt @@ -557,8 +557,8 @@ Complex factor conditions +++++++++++++++++++++++++ -Sometimes you need to specify same line for several factors or create a special -case for a combination of factors. Here is how you do it:: +Sometimes you need to specify the same line for several factors or create a +special case for a combination of factors. Here is how you do it:: [tox] envlist = py{26,27,33}-django{15,16}-{sqlite,mysql} Repository URL: https://bitbucket.org/hpk42/tox/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Tue May 12 10:43:25 2015 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 12 May 2015 08:43:25 -0000 Subject: [Pytest-commit] commit/tox: hpk42: Merged in stevepiercy/tox-1/stevepiercy/insert-missing-the-for-grammar-rewrap-to-1431248739542 (pull request #157) Message-ID: <20150512084325.4932.17254@app08.ash-private.bitbucket.org> 1 new commit in tox: https://bitbucket.org/hpk42/tox/commits/453b0b5844f1/ Changeset: 453b0b5844f1 User: hpk42 Date: 2015-05-12 08:43:21+00:00 Summary: Merged in stevepiercy/tox-1/stevepiercy/insert-missing-the-for-grammar-rewrap-to-1431248739542 (pull request #157) insert missing "the" for grammar; rewrap to 79 cols Affected #: 1 file diff -r ee38b60cb1403506c703c30e31107fc594253cfb -r 453b0b5844f1482f598260c5f08ea3fcd3283740 doc/config.txt --- a/doc/config.txt +++ b/doc/config.txt @@ -557,8 +557,8 @@ Complex factor conditions +++++++++++++++++++++++++ -Sometimes you need to specify same line for several factors or create a special -case for a combination of factors. Here is how you do it:: +Sometimes you need to specify the same line for several factors or create a +special case for a combination of factors. Here is how you do it:: [tox] envlist = py{26,27,33}-django{15,16}-{sqlite,mysql} Repository URL: https://bitbucket.org/hpk42/tox/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Tue May 12 13:37:48 2015 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 12 May 2015 11:37:48 -0000 Subject: [Pytest-commit] commit/pytest: 2 new changesets Message-ID: <20150512113748.5417.70602@app12.ash-private.bitbucket.org> 2 new commits in pytest: https://bitbucket.org/pytest-dev/pytest/commits/cf8974765313/ Changeset: cf8974765313 Branch: pytest-2.7 User: hpk42 Date: 2015-05-12 11:34:32+00:00 Summary: remove adopt message Affected #: 1 file diff -r c490ad387968c6c4691d369213241b9038203da1 -r cf8974765313be632a059b6c6b60190bdc78516e doc/en/index.txt --- a/doc/en/index.txt +++ b/doc/en/index.txt @@ -1,11 +1,6 @@ .. _features: -.. note:: - Are you an experienced pytest user, or an open source project that needs some help getting started with pytest? **April 2015** is `adopt pytest month`_! - - -.. _`adopt pytest month`: adopt.html pytest: helps you write better programs ============================================= https://bitbucket.org/pytest-dev/pytest/commits/6ee4b4d70a9b/ Changeset: 6ee4b4d70a9b User: hpk42 Date: 2015-05-12 11:36:43+00:00 Summary: remove adopt pytest note Affected #: 1 file diff -r 162f18fafaba4bc42fa61b9e4fbdf3d7b645748e -r 6ee4b4d70a9b81f525c6fe46eab1b2018de6f4b0 doc/en/index.txt --- a/doc/en/index.txt +++ b/doc/en/index.txt @@ -1,11 +1,6 @@ .. _features: -.. note:: - Are you an experienced pytest user, or an open source project that needs some help getting started with pytest? **April 2015** is `adopt pytest month`_! - - -.. _`adopt pytest month`: adopt.html pytest: helps you write better programs ============================================= Repository URL: https://bitbucket.org/pytest-dev/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Tue May 12 15:27:05 2015 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 12 May 2015 13:27:05 -0000 Subject: [Pytest-commit] commit/tox: 2 new changesets Message-ID: <20150512132705.12614.86050@app03.ash-private.bitbucket.org> 2 new commits in tox: https://bitbucket.org/hpk42/tox/commits/554373915117/ Changeset: 554373915117 User: hpk42 Date: 2015-05-12 12:01:28+00:00 Summary: - store and show information about what is installed in each venv - rename internal methods to accomodate the fact that we are not only installing sdist's Affected #: 13 files diff -r 453b0b5844f1482f598260c5f08ea3fcd3283740 -r 55437391511703f2fd40f3ee3cf18f89ef8f446a CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,4 @@ -2.0.0.dev1 +2.0.0 ----------- - (new) introduce environment variable isolation: @@ -23,6 +23,8 @@ 2.0). If ``False`` (the default), then a non-zero exit code from one command will abort execution of commands for that environment. +- show and store in json the version dependency information for each venv + - remove the long-deprecated "distribute" option as it has no effect these days. - fix issue233: avoid hanging with tox-setuptools integration example. Thanks simonb. @@ -46,6 +48,7 @@ for testenv sections. Can be used from plugins through the tox_add_option hook. + 1.9.2 ----------- diff -r 453b0b5844f1482f598260c5f08ea3fcd3283740 -r 55437391511703f2fd40f3ee3cf18f89ef8f446a doc/Makefile --- a/doc/Makefile +++ b/doc/Makefile @@ -37,7 +37,8 @@ -rm -rf $(BUILDDIR)/* install: clean html - @rsync -avz $(BUILDDIR)/html/ testrun.org:/www/testrun.org/tox/dev + @rsync -avz $(BUILDDIR)/html/ testrun.org:/www/testrun.org/tox/latest + #dev #latexpdf #@scp $(BUILDDIR)/latex/*.pdf testrun.org:www-tox/latest diff -r 453b0b5844f1482f598260c5f08ea3fcd3283740 -r 55437391511703f2fd40f3ee3cf18f89ef8f446a doc/announce/release-2.0.txt --- /dev/null +++ b/doc/announce/release-2.0.txt @@ -0,0 +1,54 @@ +tox-2.0: plugins, platform, env isolation +========================================== + +tox-2.0 was released to pypi, a major new release with *mostly* +backward-compatible enhancements and fixes: + +- experimental support for plugins, see https://testrun.org/tox/dev/plugins.html + which includes also a refined internal registration mechanism for new testenv + ini options. You can now ask tox which testenv ini parameters exist + with ``tox --help-ini``. + +- ENV isolation: only pass through very few environment variables from the + tox invocation to the test environments. This may break test runs that + previously worked with tox-1.9 -- you need to either use the + ``setenv`` or ``passenv`` ini variables to set appropriate environment + variables. + +- PLATFORM support: you can set ``platform=REGEX`` in your testenv sections + which lets tox skip the environment if the REGEX does not match ``sys.platform``. + +- tox now stops execution of test commands if the first of them fails unless + you set ``ignore_errors=True``. + +Thanks to Volodymyr Vitvitski, Daniel Hahler, Marc Abramowitz, Anthon van +der Neuth and others for contributions. + +More documentation about tox in general: + + http://tox.testrun.org/ + +Installation: + + pip install -U tox + +code hosting and issue tracking on bitbucket: + + https://bitbucket.org/hpk42/tox + +What is tox? +---------------- + +tox standardizes and automates tedious test activities driven from a +simple ``tox.ini`` file, including: + +* creation and management of different virtualenv environments + with different Python interpreters +* packaging and installing your package into each of them +* running your test tool of choice, be it nose, py.test or unittest2 or other tools such as "sphinx" doc checks +* testing dev packages against each other without needing to upload to PyPI + +best, +Holger Krekel, merlinux GmbH + + diff -r 453b0b5844f1482f598260c5f08ea3fcd3283740 -r 55437391511703f2fd40f3ee3cf18f89ef8f446a doc/conf.py --- a/doc/conf.py +++ b/doc/conf.py @@ -41,7 +41,7 @@ # General information about the project. project = u'tox' -copyright = u'2013, holger krekel and others' +copyright = u'2015, holger krekel and others' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff -r 453b0b5844f1482f598260c5f08ea3fcd3283740 -r 55437391511703f2fd40f3ee3cf18f89ef8f446a setup.py --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ description='virtualenv-based automation of test activities', long_description=open("README.rst").read(), url='http://tox.testrun.org/', - version='2.0.0.dev2', + version='2.0.0.dev4', license='http://opensource.org/licenses/MIT', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], author='holger krekel', diff -r 453b0b5844f1482f598260c5f08ea3fcd3283740 -r 55437391511703f2fd40f3ee3cf18f89ef8f446a tests/test_config.py --- a/tests/test_config.py +++ b/tests/test_config.py @@ -827,13 +827,13 @@ def test_substitution_error(tmpdir, newconfig): py.test.raises(tox.exception.ConfigError, newconfig, """ - [testenv:py24] + [testenv:py27] basepython={xyz} """) def test_substitution_defaults(tmpdir, newconfig): config = newconfig(""" - [testenv:py24] + [testenv:py27] commands = {toxinidir} {toxworkdir} @@ -845,7 +845,7 @@ {distshare} {envlogdir} """) - conf = config.envconfigs['py24'] + conf = config.envconfigs['py27'] argv = conf.commands assert argv[0][0] == config.toxinidir assert argv[1][0] == config.toxworkdir @@ -859,18 +859,18 @@ def test_substitution_positional(self, newconfig): inisource = """ - [testenv:py24] + [testenv:py27] commands = cmd1 [hello] \ world cmd1 {posargs:hello} \ world """ - conf = newconfig([], inisource).envconfigs['py24'] + conf = newconfig([], inisource).envconfigs['py27'] argv = conf.commands assert argv[0] == ["cmd1", "[hello]", "world"] assert argv[1] == ["cmd1", "hello", "world"] - conf = newconfig(['brave', 'new'], inisource).envconfigs['py24'] + conf = newconfig(['brave', 'new'], inisource).envconfigs['py27'] argv = conf.commands assert argv[0] == ["cmd1", "[hello]", "world"] assert argv[1] == ["cmd1", "brave", "new", "world"] @@ -886,58 +886,58 @@ def test_posargs_backslashed_or_quoted(self, tmpdir, newconfig): inisource = """ - [testenv:py24] + [testenv:py27] commands = echo "\{posargs\}" = {posargs} echo "posargs = " "{posargs}" """ - conf = newconfig([], inisource).envconfigs['py24'] + conf = newconfig([], inisource).envconfigs['py27'] argv = conf.commands assert argv[0] == ['echo', '\\{posargs\\}', '='] assert argv[1] == ['echo', 'posargs = ', ""] - conf = newconfig(['dog', 'cat'], inisource).envconfigs['py24'] + conf = newconfig(['dog', 'cat'], inisource).envconfigs['py27'] argv = conf.commands assert argv[0] == ['echo', '\\{posargs\\}', '=', 'dog', 'cat'] assert argv[1] == ['echo', 'posargs = ', 'dog cat'] def test_rewrite_posargs(self, tmpdir, newconfig): inisource = """ - [testenv:py24] + [testenv:py27] args_are_paths = True changedir = tests commands = cmd1 {posargs:hello} """ - conf = newconfig([], inisource).envconfigs['py24'] + conf = newconfig([], inisource).envconfigs['py27'] argv = conf.commands assert argv[0] == ["cmd1", "hello"] - conf = newconfig(["tests/hello"], inisource).envconfigs['py24'] + conf = newconfig(["tests/hello"], inisource).envconfigs['py27'] argv = conf.commands assert argv[0] == ["cmd1", "tests/hello"] tmpdir.ensure("tests", "hello") - conf = newconfig(["tests/hello"], inisource).envconfigs['py24'] + conf = newconfig(["tests/hello"], inisource).envconfigs['py27'] argv = conf.commands assert argv[0] == ["cmd1", "hello"] def test_rewrite_simple_posargs(self, tmpdir, newconfig): inisource = """ - [testenv:py24] + [testenv:py27] args_are_paths = True changedir = tests commands = cmd1 {posargs} """ - conf = newconfig([], inisource).envconfigs['py24'] + conf = newconfig([], inisource).envconfigs['py27'] argv = conf.commands assert argv[0] == ["cmd1"] - conf = newconfig(["tests/hello"], inisource).envconfigs['py24'] + conf = newconfig(["tests/hello"], inisource).envconfigs['py27'] argv = conf.commands assert argv[0] == ["cmd1", "tests/hello"] tmpdir.ensure("tests", "hello") - conf = newconfig(["tests/hello"], inisource).envconfigs['py24'] + conf = newconfig(["tests/hello"], inisource).envconfigs['py27'] argv = conf.commands assert argv[0] == ["cmd1", "hello"] @@ -947,12 +947,12 @@ deps= pytest pytest-cov - [testenv:py24] + [testenv:py27] deps= {[testenv]deps} fun """ - conf = newconfig([], inisource).envconfigs['py24'] + conf = newconfig([], inisource).envconfigs['py27'] packages = [dep.name for dep in conf.deps] assert packages == ['pytest', 'pytest-cov', 'fun'] @@ -1165,11 +1165,11 @@ monkeypatch, newconfig): monkeypatch.setenv("HUDSON_URL", "xyz") config = newconfig(""" - [testenv:py24] + [testenv:py27] commands = {distshare} """) - conf = config.envconfigs['py24'] + conf = config.envconfigs['py27'] argv = conf.commands expect_path = config.toxworkdir.join("distshare") assert argv[0][0] == expect_path @@ -1180,11 +1180,11 @@ config = newconfig(""" [tox:jenkins] distshare = {env:WORKSPACE}/hello - [testenv:py24] + [testenv:py27] commands = {distshare} """) - conf = config.envconfigs['py24'] + conf = config.envconfigs['py27'] argv = conf.commands assert argv[0][0] == config.distshare assert config.distshare == tmpdir.join("hello") @@ -1230,7 +1230,7 @@ assert str(env.basepython) == sys.executable def test_default_environments(self, tmpdir, newconfig, monkeypatch): - envs = "py26,py27,py31,py32,py33,py34,jython,pypy,pypy3" + envs = "py26,py27,py32,py33,py34,py35,py36,jython,pypy,pypy3" inisource = """ [tox] envlist = %s @@ -1291,21 +1291,21 @@ assert not config.option.skip_missing_interpreters def test_defaultenv_commandline(self, tmpdir, newconfig, monkeypatch): - config = newconfig(["-epy24"], "") - env = config.envconfigs['py24'] - assert env.basepython == "python2.4" + config = newconfig(["-epy27"], "") + env = config.envconfigs['py27'] + assert env.basepython == "python2.7" assert not env.commands def test_defaultenv_partial_override(self, tmpdir, newconfig, monkeypatch): inisource = """ [tox] - envlist = py24 - [testenv:py24] + envlist = py27 + [testenv:py27] commands= xyz """ config = newconfig([], inisource) - env = config.envconfigs['py24'] - assert env.basepython == "python2.4" + env = config.envconfigs['py27'] + assert env.basepython == "python2.7" assert env.commands == [['xyz']] diff -r 453b0b5844f1482f598260c5f08ea3fcd3283740 -r 55437391511703f2fd40f3ee3cf18f89ef8f446a tests/test_z_cmdline.py --- a/tests/test_z_cmdline.py +++ b/tests/test_z_cmdline.py @@ -83,15 +83,15 @@ }) config = parseconfig([]) session = Session(config) - sdist = session.sdist() + sdist = session.get_installpkg_path() assert sdist.check() assert sdist.ext == ".zip" assert sdist == config.distdir.join(sdist.basename) - sdist2 = session.sdist() + sdist2 = session.get_installpkg_path() assert sdist2 == sdist sdist.write("hello") assert sdist.stat().size < 10 - sdist_new = Session(config).sdist() + sdist_new = Session(config).get_installpkg_path() assert sdist_new == sdist assert sdist_new.stat().size > 10 @@ -106,7 +106,7 @@ }) config = parseconfig([]) session = Session(config) - sdist = session.sdist() + sdist = session.get_installpkg_path() assert sdist.check() assert sdist.ext == ".zip" assert sdist == config.distdir.join(sdist.basename) @@ -683,7 +683,7 @@ p = distshare.ensure("pkg123-1.4.5.zip") distshare.ensure("pkg123-1.4.5a1.zip") session = Session(config) - sdist_path = session.sdist() + sdist_path = session.get_installpkg_path() assert sdist_path == p @@ -691,7 +691,7 @@ p = tmpdir.ensure("pkg123-1.0.zip") config = newconfig(["--installpkg=%s" % p], "") session = Session(config) - sdist_path = session.sdist() + sdist_path = session.get_installpkg_path() assert sdist_path == p @@ -722,7 +722,9 @@ for command in envdata[commandtype]: assert command["output"] assert command["retcode"] - pyinfo = envdata["python"] - assert isinstance(pyinfo["version_info"], list) - assert pyinfo["version"] - assert pyinfo["executable"] + if envname != "GLOB": + assert isinstance(envdata["installed_packages"], list) + pyinfo = envdata["python"] + assert isinstance(pyinfo["version_info"], list) + assert pyinfo["version"] + assert pyinfo["executable"] diff -r 453b0b5844f1482f598260c5f08ea3fcd3283740 -r 55437391511703f2fd40f3ee3cf18f89ef8f446a tox.ini --- a/tox.ini +++ b/tox.ini @@ -5,7 +5,7 @@ commands=echo {posargs} [testenv] -commands= py.test --timeout=60 {posargs} +commands= py.test --timeout=180 {posargs} deps=pytest>=2.3.5 pytest-timeout diff -r 453b0b5844f1482f598260c5f08ea3fcd3283740 -r 55437391511703f2fd40f3ee3cf18f89ef8f446a tox/__init__.py --- a/tox/__init__.py +++ b/tox/__init__.py @@ -1,5 +1,5 @@ # -__version__ = '2.0.0.dev2' +__version__ = '2.0.0.dev4' from .hookspecs import hookspec, hookimpl # noqa diff -r 453b0b5844f1482f598260c5f08ea3fcd3283740 -r 55437391511703f2fd40f3ee3cf18f89ef8f446a tox/_cmdline.py --- a/tox/_cmdline.py +++ b/tox/_cmdline.py @@ -67,10 +67,12 @@ self.venvname = self.venv.name else: self.venvname = "GLOB" - cat = {"runtests": "test", "getenv": "setup"}.get(msg) - if cat: - envlog = session.resultlog.get_envlog(self.venvname) - self.commandlog = envlog.get_commandlog(cat) + if msg == "runtests": + cat = "test" + else: + cat = "setup" + envlog = session.resultlog.get_envlog(self.venvname) + self.commandlog = envlog.get_commandlog(cat) def __enter__(self): self.report.logaction_start(self) @@ -106,7 +108,7 @@ resultjson = self.session.config.option.resultjson if resultjson or redirect: fout = self._initlogpath(self.id) - fout.write("actionid=%s\nmsg=%s\ncmdargs=%r\nenv=%s\n" % ( + fout.write("actionid: %s\nmsg: %s\ncmdargs: %r\nenv: %s\n\n" % ( self.id, self.msg, args, env)) fout.flush() self.popen_outpath = outpath = py.path.local(fout.name) @@ -442,47 +444,47 @@ venv.status = sys.exc_info()[1] return False - def installpkg(self, venv, sdist_path): - """Install source package in the specified virtual environment. + def installpkg(self, venv, path): + """Install package in the specified virtual environment. :param :class:`tox._config.VenvConfig`: Destination environment - :param str sdist_path: Path to the source distribution. + :param str path: Path to the distribution package. :return: True if package installed otherwise False. :rtype: bool """ - self.resultlog.set_header(installpkg=py.path.local(sdist_path)) - action = self.newaction(venv, "installpkg", sdist_path) + self.resultlog.set_header(installpkg=py.path.local(path)) + action = self.newaction(venv, "installpkg", path) with action: try: - venv.installpkg(sdist_path, action) + venv.installpkg(path, action) return True except tox.exception.InvocationError: venv.status = sys.exc_info()[1] return False - def sdist(self): + def get_installpkg_path(self): """ - :return: Path to the source distribution + :return: Path to the distribution :rtype: py.path.local """ if not self.config.option.sdistonly and (self.config.sdistsrc or self.config.option.installpkg): - sdist_path = self.config.option.installpkg - if not sdist_path: - sdist_path = self.config.sdistsrc - sdist_path = self._resolve_pkg(sdist_path) + path = self.config.option.installpkg + if not path: + path = self.config.sdistsrc + path = self._resolve_pkg(path) self.report.info("using package %r, skipping 'sdist' activity " % - str(sdist_path)) + str(path)) else: try: - sdist_path = self._makesdist() + path = self._makesdist() except tox.exception.InvocationError: v = sys.exc_info()[1] self.report.error("FAIL could not package project - v = %r" % v) return - sdistfile = self.config.distshare.join(sdist_path.basename) - if sdistfile != sdist_path: + sdistfile = self.config.distshare.join(path.basename) + if sdistfile != path: self.report.info("copying new sdistfile to %r" % str(sdistfile)) try: @@ -491,16 +493,16 @@ self.report.warning("could not copy distfile to %s" % sdistfile.dirpath()) else: - sdist_path.copy(sdistfile) - return sdist_path + path.copy(sdistfile) + return path def subcommand_test(self): if self.config.skipsdist: self.report.info("skipping sdist step") - sdist_path = None + path = None else: - sdist_path = self.sdist() - if not sdist_path: + path = self.get_installpkg_path() + if not path: return 2 if self.config.option.sdistonly: return @@ -514,7 +516,22 @@ elif self.config.skipsdist or venv.envconfig.skip_install: self.finishvenv(venv) else: - self.installpkg(venv, sdist_path) + self.installpkg(venv, path) + + # write out version dependency information + action = self.newaction(venv, "envreport") + with action: + pip = venv.getcommandpath("pip") + # we can't really call internal helpers here easily :/ + # output = venv._pcall([str(pip), "freeze"], + # cwd=self.config.toxinidir, + # action=action) + output = py.process.cmdexec("%s freeze" % (pip)) + packages = output.strip().split("\n") + action.setactivity("installed", ",".join(packages)) + envlog = self.resultlog.get_envlog(venv.name) + envlog.set_installed(packages) + self.runtestenv(venv) retcode = self._summary() return retcode @@ -589,6 +606,8 @@ self.report.line(" envdir= %s" % envconfig.envdir) self.report.line(" downloadcache=%s" % envconfig.downloadcache) self.report.line(" usedevelop=%s" % envconfig.usedevelop) + self.report.line(" setenv=%s" % envconfig.setenv) + self.report.line(" passenv=%s" % envconfig.passenv) def showenvs(self): for env in self.config.envlist: diff -r 453b0b5844f1482f598260c5f08ea3fcd3283740 -r 55437391511703f2fd40f3ee3cf18f89ef8f446a tox/_config.py --- a/tox/_config.py +++ b/tox/_config.py @@ -21,7 +21,7 @@ default_factors = {'jython': 'jython', 'pypy': 'pypy', 'pypy3': 'pypy3', 'py': sys.executable} -for version in '24,25,26,27,30,31,32,33,34,35'.split(','): +for version in '26,27,32,33,34,35,36'.split(','): default_factors['py' + version] = 'python%s.%s' % tuple(version) hookimpl = pluggy.HookimplMarker("tox") @@ -361,9 +361,18 @@ def passenv(config, reader, section_val): passenv = set(["PATH"]) + + # we ensure that tmp directory settings are passed on + # we could also set it to the per-venv "envtmpdir" + # but this leads to very long paths when run with jenkins + # so we just pass it on by default for now. if sys.platform == "win32": passenv.add("SYSTEMROOT") # needed for python's crypto module passenv.add("PATHEXT") # needed for discovering executables + passenv.add("TEMPDIR") + passenv.add("TMP") + else: + passenv.add("TMPDIR") for spec in section_val: for name in os.environ: if fnmatchcase(name.upper(), spec.upper()): @@ -626,6 +635,7 @@ factors=factors) reader.addsubstitutions(**subs) reader.addsubstitutions(envname=name) + reader.vc = vc for env_attr in config._testenv_attr: atype = env_attr.type diff -r 453b0b5844f1482f598260c5f08ea3fcd3283740 -r 55437391511703f2fd40f3ee3cf18f89ef8f446a tox/_venv.py --- a/tox/_venv.py +++ b/tox/_venv.py @@ -327,7 +327,6 @@ env['VIRTUAL_ENV'] = str(self.path) env.update(extraenv) - return env def test(self, redirect=False): diff -r 453b0b5844f1482f598260c5f08ea3fcd3283740 -r 55437391511703f2fd40f3ee3cf18f89ef8f446a tox/result.py --- a/tox/result.py +++ b/tox/result.py @@ -63,6 +63,9 @@ l = self.dict.setdefault(name, []) return CommandLog(self, l) + def set_installed(self, packages): + self.dict["installed_packages"] = packages + class CommandLog: def __init__(self, envlog, list): https://bitbucket.org/hpk42/tox/commits/5fbd833e8b0e/ Changeset: 5fbd833e8b0e User: hpk42 Date: 2015-05-12 12:20:46+00:00 Summary: rename internal files -- in any case tox offers no external API except for the experimental plugin hooks, use tox internals at your own risk. Affected #: 16 files diff -r 55437391511703f2fd40f3ee3cf18f89ef8f446a -r 5fbd833e8b0e88cf71920a1479f47260317c98b0 CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -48,6 +48,10 @@ for testenv sections. Can be used from plugins through the tox_add_option hook. +- rename internal files -- tox offers no external API except for the + experimental plugin hooks, use tox internals at your own risk. + + 1.9.2 ----------- diff -r 55437391511703f2fd40f3ee3cf18f89ef8f446a -r 5fbd833e8b0e88cf71920a1479f47260317c98b0 setup.py --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ description='virtualenv-based automation of test activities', long_description=open("README.rst").read(), url='http://tox.testrun.org/', - version='2.0.0.dev4', + version='2.0.0.dev5', license='http://opensource.org/licenses/MIT', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], author='holger krekel', diff -r 55437391511703f2fd40f3ee3cf18f89ef8f446a -r 5fbd833e8b0e88cf71920a1479f47260317c98b0 tests/test_config.py --- a/tests/test_config.py +++ b/tests/test_config.py @@ -4,9 +4,9 @@ import py import pytest import tox -import tox._config -from tox._config import * # noqa -from tox._venv import VirtualEnv +import tox.config +from tox.config import * # noqa +from tox.venv import VirtualEnv class TestVenvConfig: @@ -1321,12 +1321,12 @@ """ if make_hashseed is None: make_hashseed = lambda: '123456789' - original_make_hashseed = tox._config.make_hashseed - tox._config.make_hashseed = make_hashseed + original_make_hashseed = tox.config.make_hashseed + tox.config.make_hashseed = make_hashseed try: config = newconfig(args, tox_ini) finally: - tox._config.make_hashseed = original_make_hashseed + tox.config.make_hashseed = original_make_hashseed return config.envconfigs def _get_envconfig(self, newconfig, args=None, tox_ini=None): diff -r 55437391511703f2fd40f3ee3cf18f89ef8f446a -r 5fbd833e8b0e88cf71920a1479f47260317c98b0 tests/test_interpreters.py --- a/tests/test_interpreters.py +++ b/tests/test_interpreters.py @@ -3,7 +3,7 @@ import pytest from tox.interpreters import * # noqa -from tox._config import get_plugin_manager +from tox.config import get_plugin_manager @pytest.fixture diff -r 55437391511703f2fd40f3ee3cf18f89ef8f446a -r 5fbd833e8b0e88cf71920a1479f47260317c98b0 tests/test_venv.py --- a/tests/test_venv.py +++ b/tests/test_venv.py @@ -3,8 +3,8 @@ import pytest import os import sys -import tox._config -from tox._venv import * # noqa +import tox.config +from tox.venv import * # noqa from tox.interpreters import NoInterpreterInfo # def test_global_virtualenv(capfd): @@ -253,14 +253,14 @@ def test_test_hashseed_is_in_output(newmocksession): - original_make_hashseed = tox._config.make_hashseed - tox._config.make_hashseed = lambda: '123456789' + original_make_hashseed = tox.config.make_hashseed + tox.config.make_hashseed = lambda: '123456789' try: mocksession = newmocksession([], ''' [testenv] ''') finally: - tox._config.make_hashseed = original_make_hashseed + tox.config.make_hashseed = original_make_hashseed venv = mocksession.getenv('python') venv.update() venv.test() @@ -620,7 +620,7 @@ mocksession = newmocksession([], "") venv = mocksession.getenv('python') action = mocksession.newaction(venv, "qwe", []) - monkeypatch.setattr(tox._venv, "hack_home_env", None) + monkeypatch.setattr(tox.venv, "hack_home_env", None) venv._install(["x"], action=action) @@ -636,7 +636,7 @@ def test_hack_home_env(tmpdir): - from tox._venv import hack_home_env + from tox.venv import hack_home_env env = hack_home_env(tmpdir, "http://index") assert env["HOME"] == str(tmpdir) assert env["PIP_INDEX_URL"] == "http://index" @@ -650,7 +650,7 @@ def test_hack_home_env_passthrough(tmpdir, monkeypatch): - from tox._venv import hack_home_env + from tox.venv import hack_home_env env = hack_home_env(tmpdir, "http://index") monkeypatch.setattr(os, "environ", env) diff -r 55437391511703f2fd40f3ee3cf18f89ef8f446a -r 5fbd833e8b0e88cf71920a1479f47260317c98b0 tests/test_z_cmdline.py --- a/tests/test_z_cmdline.py +++ b/tests/test_z_cmdline.py @@ -9,8 +9,8 @@ pytest_plugins = "pytester" -from tox._cmdline import Session -from tox._config import parseconfig +from tox.session import Session +from tox.config import parseconfig def test_report_protocol(newconfig): diff -r 55437391511703f2fd40f3ee3cf18f89ef8f446a -r 5fbd833e8b0e88cf71920a1479f47260317c98b0 tox/__init__.py --- a/tox/__init__.py +++ b/tox/__init__.py @@ -1,5 +1,5 @@ # -__version__ = '2.0.0.dev4' +__version__ = '2.0.0.dev5' from .hookspecs import hookspec, hookimpl # noqa @@ -24,4 +24,4 @@ class MissingDependency(Error): """ a dependency could not be found or determined. """ -from tox._cmdline import main as cmdline # noqa +from tox.session import main as cmdline # noqa diff -r 55437391511703f2fd40f3ee3cf18f89ef8f446a -r 5fbd833e8b0e88cf71920a1479f47260317c98b0 tox/__main__.py --- a/tox/__main__.py +++ b/tox/__main__.py @@ -1,3 +1,4 @@ -from tox._cmdline import main +from tox.session import main -main() +if __name__ == "__main__": + main() diff -r 55437391511703f2fd40f3ee3cf18f89ef8f446a -r 5fbd833e8b0e88cf71920a1479f47260317c98b0 tox/_cmdline.py --- a/tox/_cmdline.py +++ /dev/null @@ -1,673 +0,0 @@ -""" -Automatically package and test a Python project against configurable -Python2 and Python3 based virtual environments. Environments are -setup by using virtualenv. Configuration is generally done through an -INI-style "tox.ini" file. -""" -from __future__ import with_statement - -import tox -import py -import os -import sys -import subprocess -from tox._verlib import NormalizedVersion, IrrationalVersionError -from tox._venv import VirtualEnv -from tox._config import parseconfig -from tox.result import ResultLog -from subprocess import STDOUT - - -def now(): - return py.std.time.time() - - -def main(args=None): - try: - config = parseconfig(args) - if config.option.help: - show_help(config) - raise SystemExit(0) - elif config.option.helpini: - show_help_ini(config) - raise SystemExit(0) - retcode = Session(config).runcommand() - raise SystemExit(retcode) - except KeyboardInterrupt: - raise SystemExit(2) - - -def show_help(config): - tw = py.io.TerminalWriter() - tw.write(config._parser.format_help()) - tw.line() - - -def show_help_ini(config): - tw = py.io.TerminalWriter() - tw.sep("-", "per-testenv attributes") - for env_attr in config._testenv_attr: - tw.line("%-15s %-8s default: %s" % - (env_attr.name, "<" + env_attr.type + ">", env_attr.default), bold=True) - tw.line(env_attr.help) - tw.line() - - -class Action(object): - def __init__(self, session, venv, msg, args): - self.venv = venv - self.msg = msg - self.activity = msg.split(" ", 1)[0] - self.session = session - self.report = session.report - self.args = args - self.id = venv and venv.envconfig.envname or "tox" - self._popenlist = [] - if self.venv: - self.venvname = self.venv.name - else: - self.venvname = "GLOB" - if msg == "runtests": - cat = "test" - else: - cat = "setup" - envlog = session.resultlog.get_envlog(self.venvname) - self.commandlog = envlog.get_commandlog(cat) - - def __enter__(self): - self.report.logaction_start(self) - - def __exit__(self, *args): - self.report.logaction_finish(self) - - def setactivity(self, name, msg): - self.activity = name - self.report.verbosity0("%s %s: %s" % (self.venvname, name, msg), bold=True) - - def info(self, name, msg): - self.report.verbosity1("%s %s: %s" % (self.venvname, name, msg), bold=True) - - def _initlogpath(self, actionid): - if self.venv: - logdir = self.venv.envconfig.envlogdir - else: - logdir = self.session.config.logdir - try: - l = logdir.listdir("%s-*" % actionid) - except py.error.ENOENT: - logdir.ensure(dir=1) - l = [] - num = len(l) - path = logdir.join("%s-%s.log" % (actionid, num)) - f = path.open('w') - f.flush() - return f - - def popen(self, args, cwd=None, env=None, redirect=True, returnout=False, ignore_ret=False): - stdout = outpath = None - resultjson = self.session.config.option.resultjson - if resultjson or redirect: - fout = self._initlogpath(self.id) - fout.write("actionid: %s\nmsg: %s\ncmdargs: %r\nenv: %s\n\n" % ( - self.id, self.msg, args, env)) - fout.flush() - self.popen_outpath = outpath = py.path.local(fout.name) - fin = outpath.open() - fin.read() # read the header, so it won't be written to stdout - stdout = fout - elif returnout: - stdout = subprocess.PIPE - if cwd is None: - # XXX cwd = self.session.config.cwd - cwd = py.path.local() - try: - popen = self._popen(args, cwd, env=env, - stdout=stdout, stderr=STDOUT) - except OSError as e: - self.report.error("invocation failed (errno %d), args: %s, cwd: %s" % - (e.errno, args, cwd)) - raise - popen.outpath = outpath - popen.args = [str(x) for x in args] - popen.cwd = cwd - popen.action = self - self._popenlist.append(popen) - try: - self.report.logpopen(popen, env=env) - try: - if resultjson and not redirect: - assert popen.stderr is None # prevent deadlock - out = None - last_time = now() - while 1: - fin_pos = fin.tell() - # we have to read one byte at a time, otherwise there - # might be no output for a long time with slow tests - data = fin.read(1) - if data: - sys.stdout.write(data) - if '\n' in data or (now() - last_time) > 1: - # we flush on newlines or after 1 second to - # provide quick enough feedback to the user - # when printing a dot per test - sys.stdout.flush() - last_time = now() - elif popen.poll() is not None: - if popen.stdout is not None: - popen.stdout.close() - break - else: - py.std.time.sleep(0.1) - fin.seek(fin_pos) - fin.close() - else: - out, err = popen.communicate() - except KeyboardInterrupt: - self.report.keyboard_interrupt() - popen.wait() - raise KeyboardInterrupt() - ret = popen.wait() - finally: - self._popenlist.remove(popen) - if ret and not ignore_ret: - invoked = " ".join(map(str, popen.args)) - if outpath: - self.report.error("invocation failed (exit code %d), logfile: %s" % - (ret, outpath)) - out = outpath.read() - self.report.error(out) - if hasattr(self, "commandlog"): - self.commandlog.add_command(popen.args, out, ret) - raise tox.exception.InvocationError( - "%s (see %s)" % (invoked, outpath), ret) - else: - raise tox.exception.InvocationError("%r" % (invoked, ), ret) - if not out and outpath: - out = outpath.read() - if hasattr(self, "commandlog"): - self.commandlog.add_command(popen.args, out, ret) - return out - - def _rewriteargs(self, cwd, args): - newargs = [] - for arg in args: - if sys.platform != "win32" and isinstance(arg, py.path.local): - arg = cwd.bestrelpath(arg) - newargs.append(str(arg)) - - # subprocess does not always take kindly to .py scripts - # so adding the interpreter here. - if sys.platform == "win32": - ext = os.path.splitext(str(newargs[0]))[1].lower() - if ext == '.py' and self.venv: - newargs = [str(self.venv.getcommandpath())] + newargs - - return newargs - - def _popen(self, args, cwd, stdout, stderr, env=None): - args = self._rewriteargs(cwd, args) - if env is None: - env = os.environ.copy() - return self.session.popen(args, shell=False, cwd=str(cwd), - universal_newlines=True, - stdout=stdout, stderr=stderr, env=env) - - -class Reporter(object): - actionchar = "-" - - def __init__(self, session): - self.tw = py.io.TerminalWriter() - self.session = session - self._reportedlines = [] - # self.cumulated_time = 0.0 - - def logpopen(self, popen, env): - """ log information about the action.popen() created process. """ - cmd = " ".join(map(str, popen.args)) - if popen.outpath: - self.verbosity1(" %s$ %s >%s" % (popen.cwd, cmd, popen.outpath,)) - else: - self.verbosity1(" %s$ %s " % (popen.cwd, cmd)) - - def logaction_start(self, action): - msg = action.msg + " " + " ".join(map(str, action.args)) - self.verbosity2("%s start: %s" % (action.venvname, msg), bold=True) - assert not hasattr(action, "_starttime") - action._starttime = now() - - def logaction_finish(self, action): - duration = now() - action._starttime - # self.cumulated_time += duration - self.verbosity2("%s finish: %s after %.2f seconds" % ( - action.venvname, action.msg, duration), bold=True) - - def startsummary(self): - self.tw.sep("_", "summary") - - def info(self, msg): - if self.session.config.option.verbosity >= 2: - self.logline(msg) - - def using(self, msg): - if self.session.config.option.verbosity >= 1: - self.logline("using %s" % (msg,), bold=True) - - def keyboard_interrupt(self): - self.error("KEYBOARDINTERRUPT") - -# def venv_installproject(self, venv, pkg): -# self.logline("installing to %s: %s" % (venv.envconfig.envname, pkg)) - - def keyvalue(self, name, value): - if name.endswith(":"): - name += " " - self.tw.write(name, bold=True) - self.tw.write(value) - self.tw.line() - - def line(self, msg, **opts): - self.logline(msg, **opts) - - def good(self, msg): - self.logline(msg, green=True) - - def warning(self, msg): - self.logline("WARNING:" + msg, red=True) - - def error(self, msg): - self.logline("ERROR: " + msg, red=True) - - def skip(self, msg): - self.logline("SKIPPED:" + msg, yellow=True) - - def logline(self, msg, **opts): - self._reportedlines.append(msg) - self.tw.line("%s" % msg, **opts) - - def verbosity0(self, msg, **opts): - if self.session.config.option.verbosity >= 0: - self.logline("%s" % msg, **opts) - - def verbosity1(self, msg, **opts): - if self.session.config.option.verbosity >= 1: - self.logline("%s" % msg, **opts) - - def verbosity2(self, msg, **opts): - if self.session.config.option.verbosity >= 2: - self.logline("%s" % msg, **opts) - - # def log(self, msg): - # py.builtin.print_(msg, file=sys.stderr) - - -class Session: - - def __init__(self, config, popen=subprocess.Popen, Report=Reporter): - self.config = config - self.popen = popen - self.resultlog = ResultLog() - self.report = Report(self) - self.make_emptydir(config.logdir) - config.logdir.ensure(dir=1) - # self.report.using("logdir %s" %(self.config.logdir,)) - self.report.using("tox.ini: %s" % (self.config.toxinipath,)) - self._spec2pkg = {} - self._name2venv = {} - try: - self.venvlist = [ - self.getvenv(x) - for x in self.config.envlist - ] - except LookupError: - raise SystemExit(1) - self._actions = [] - - def _makevenv(self, name): - envconfig = self.config.envconfigs.get(name, None) - if envconfig is None: - self.report.error("unknown environment %r" % name) - raise LookupError(name) - venv = VirtualEnv(envconfig=envconfig, session=self) - self._name2venv[name] = venv - return venv - - def getvenv(self, name): - """ return a VirtualEnv controler object for the 'name' env. """ - try: - return self._name2venv[name] - except KeyError: - return self._makevenv(name) - - def newaction(self, venv, msg, *args): - action = Action(self, venv, msg, args) - self._actions.append(action) - return action - - def runcommand(self): - self.report.using("tox-%s from %s" % (tox.__version__, tox.__file__)) - if self.config.minversion: - minversion = NormalizedVersion(self.config.minversion) - toxversion = NormalizedVersion(tox.__version__) - if toxversion < minversion: - self.report.error( - "tox version is %s, required is at least %s" % ( - toxversion, minversion)) - raise SystemExit(1) - if self.config.option.showconfig: - self.showconfig() - elif self.config.option.listenvs: - self.showenvs() - else: - return self.subcommand_test() - - def _copyfiles(self, srcdir, pathlist, destdir): - for relpath in pathlist: - src = srcdir.join(relpath) - if not src.check(): - self.report.error("missing source file: %s" % (src,)) - raise SystemExit(1) - target = destdir.join(relpath) - target.dirpath().ensure(dir=1) - src.copy(target) - - def _makesdist(self): - setup = self.config.setupdir.join("setup.py") - if not setup.check(): - raise tox.exception.MissingFile(setup) - action = self.newaction(None, "packaging") - with action: - action.setactivity("sdist-make", setup) - self.make_emptydir(self.config.distdir) - action.popen([sys.executable, setup, "sdist", "--formats=zip", - "--dist-dir", self.config.distdir, ], - cwd=self.config.setupdir) - try: - return self.config.distdir.listdir()[0] - except py.error.ENOENT: - # check if empty or comment only - data = [] - with open(str(setup)) as fp: - for line in fp: - if line and line[0] == '#': - continue - data.append(line) - if not ''.join(data).strip(): - self.report.error( - 'setup.py is empty' - ) - raise SystemExit(1) - self.report.error( - 'No dist directory found. Please check setup.py, e.g with:\n' - ' python setup.py sdist' - ) - raise SystemExit(1) - - def make_emptydir(self, path): - if path.check(): - self.report.info(" removing %s" % path) - py.std.shutil.rmtree(str(path), ignore_errors=True) - path.ensure(dir=1) - - def setupenv(self, venv): - action = self.newaction(venv, "getenv", venv.envconfig.envdir) - with action: - venv.status = 0 - envlog = self.resultlog.get_envlog(venv.name) - try: - status = venv.update(action=action) - except tox.exception.InvocationError: - status = sys.exc_info()[1] - if status: - commandlog = envlog.get_commandlog("setup") - commandlog.add_command(["setup virtualenv"], str(status), 1) - venv.status = status - self.report.error(str(status)) - return False - commandpath = venv.getcommandpath("python") - envlog.set_python_info(commandpath) - return True - - def finishvenv(self, venv): - action = self.newaction(venv, "finishvenv") - with action: - venv.finish() - return True - - def developpkg(self, venv, setupdir): - action = self.newaction(venv, "developpkg", setupdir) - with action: - try: - venv.developpkg(setupdir, action) - return True - except tox.exception.InvocationError: - venv.status = sys.exc_info()[1] - return False - - def installpkg(self, venv, path): - """Install package in the specified virtual environment. - - :param :class:`tox._config.VenvConfig`: Destination environment - :param str path: Path to the distribution package. - :return: True if package installed otherwise False. - :rtype: bool - """ - self.resultlog.set_header(installpkg=py.path.local(path)) - action = self.newaction(venv, "installpkg", path) - with action: - try: - venv.installpkg(path, action) - return True - except tox.exception.InvocationError: - venv.status = sys.exc_info()[1] - return False - - def get_installpkg_path(self): - """ - :return: Path to the distribution - :rtype: py.path.local - """ - if not self.config.option.sdistonly and (self.config.sdistsrc or - self.config.option.installpkg): - path = self.config.option.installpkg - if not path: - path = self.config.sdistsrc - path = self._resolve_pkg(path) - self.report.info("using package %r, skipping 'sdist' activity " % - str(path)) - else: - try: - path = self._makesdist() - except tox.exception.InvocationError: - v = sys.exc_info()[1] - self.report.error("FAIL could not package project - v = %r" % - v) - return - sdistfile = self.config.distshare.join(path.basename) - if sdistfile != path: - self.report.info("copying new sdistfile to %r" % - str(sdistfile)) - try: - sdistfile.dirpath().ensure(dir=1) - except py.error.Error: - self.report.warning("could not copy distfile to %s" % - sdistfile.dirpath()) - else: - path.copy(sdistfile) - return path - - def subcommand_test(self): - if self.config.skipsdist: - self.report.info("skipping sdist step") - path = None - else: - path = self.get_installpkg_path() - if not path: - return 2 - if self.config.option.sdistonly: - return - for venv in self.venvlist: - if not venv.matching_platform(): - venv.status = "platform mismatch" - continue # we simply omit non-matching platforms - if self.setupenv(venv): - if venv.envconfig.usedevelop: - self.developpkg(venv, self.config.setupdir) - elif self.config.skipsdist or venv.envconfig.skip_install: - self.finishvenv(venv) - else: - self.installpkg(venv, path) - - # write out version dependency information - action = self.newaction(venv, "envreport") - with action: - pip = venv.getcommandpath("pip") - # we can't really call internal helpers here easily :/ - # output = venv._pcall([str(pip), "freeze"], - # cwd=self.config.toxinidir, - # action=action) - output = py.process.cmdexec("%s freeze" % (pip)) - packages = output.strip().split("\n") - action.setactivity("installed", ",".join(packages)) - envlog = self.resultlog.get_envlog(venv.name) - envlog.set_installed(packages) - - self.runtestenv(venv) - retcode = self._summary() - return retcode - - def runtestenv(self, venv, redirect=False): - if not self.config.option.notest: - if venv.status: - return - venv.test(redirect=redirect) - else: - venv.status = "skipped tests" - - def _summary(self): - self.report.startsummary() - retcode = 0 - for venv in self.venvlist: - status = venv.status - if isinstance(status, tox.exception.InterpreterNotFound): - msg = " %s: %s" % (venv.envconfig.envname, str(status)) - if self.config.option.skip_missing_interpreters: - self.report.skip(msg) - else: - retcode = 1 - self.report.error(msg) - elif status == "platform mismatch": - msg = " %s: %s" % (venv.envconfig.envname, str(status)) - self.report.verbosity1(msg) - elif status and status != "skipped tests": - msg = " %s: %s" % (venv.envconfig.envname, str(status)) - self.report.error(msg) - retcode = 1 - else: - if not status: - status = "commands succeeded" - self.report.good(" %s: %s" % (venv.envconfig.envname, status)) - if not retcode: - self.report.good(" congratulations :)") - - path = self.config.option.resultjson - if path: - path = py.path.local(path) - path.write(self.resultlog.dumps_json()) - self.report.line("wrote json report at: %s" % path) - return retcode - - def showconfig(self): - self.info_versions() - self.report.keyvalue("config-file:", self.config.option.configfile) - self.report.keyvalue("toxinipath: ", self.config.toxinipath) - self.report.keyvalue("toxinidir: ", self.config.toxinidir) - self.report.keyvalue("toxworkdir: ", self.config.toxworkdir) - self.report.keyvalue("setupdir: ", self.config.setupdir) - self.report.keyvalue("distshare: ", self.config.distshare) - self.report.keyvalue("skipsdist: ", self.config.skipsdist) - self.report.tw.line() - for envconfig in self.config.envconfigs.values(): - self.report.line("[testenv:%s]" % envconfig.envname, bold=True) - self.report.line(" basepython=%s" % envconfig.basepython) - self.report.line(" pythoninfo=%s" % (envconfig.python_info,)) - self.report.line(" envpython=%s" % envconfig.envpython) - self.report.line(" envtmpdir=%s" % envconfig.envtmpdir) - self.report.line(" envbindir=%s" % envconfig.envbindir) - self.report.line(" envlogdir=%s" % envconfig.envlogdir) - self.report.line(" changedir=%s" % envconfig.changedir) - self.report.line(" args_are_path=%s" % envconfig.args_are_paths) - self.report.line(" install_command=%s" % - envconfig.install_command) - self.report.line(" commands=") - for command in envconfig.commands: - self.report.line(" %s" % command) - self.report.line(" deps=%s" % envconfig.deps) - self.report.line(" envdir= %s" % envconfig.envdir) - self.report.line(" downloadcache=%s" % envconfig.downloadcache) - self.report.line(" usedevelop=%s" % envconfig.usedevelop) - self.report.line(" setenv=%s" % envconfig.setenv) - self.report.line(" passenv=%s" % envconfig.passenv) - - def showenvs(self): - for env in self.config.envlist: - self.report.line("%s" % env) - - def info_versions(self): - versions = ['tox-%s' % tox.__version__] - try: - version = py.process.cmdexec("virtualenv --version") - except py.process.cmdexec.Error: - versions.append("virtualenv-1.9.1 (vendored)") - else: - versions.append("virtualenv-%s" % version.strip()) - self.report.keyvalue("tool-versions:", " ".join(versions)) - - def _resolve_pkg(self, pkgspec): - try: - return self._spec2pkg[pkgspec] - except KeyError: - self._spec2pkg[pkgspec] = x = self._resolvepkg(pkgspec) - return x - - def _resolvepkg(self, pkgspec): - if not os.path.isabs(str(pkgspec)): - return pkgspec - p = py.path.local(pkgspec) - if p.check(): - return p - if not p.dirpath().check(dir=1): - raise tox.exception.MissingDirectory(p.dirpath()) - self.report.info("determining %s" % p) - candidates = p.dirpath().listdir(p.basename) - if len(candidates) == 0: - raise tox.exception.MissingDependency(pkgspec) - if len(candidates) > 1: - items = [] - for x in candidates: - ver = getversion(x.basename) - if ver is not None: - items.append((ver, x)) - else: - self.report.warning("could not determine version of: %s" % - str(x)) - items.sort() - if not items: - raise tox.exception.MissingDependency(pkgspec) - return items[-1][1] - else: - return candidates[0] - - -_rex_getversion = py.std.re.compile("[\w_\-\+\.]+-(.*)(\.zip|\.tar.gz)") - - -def getversion(basename): - m = _rex_getversion.match(basename) - if m is None: - return None - version = m.group(1) - try: - return NormalizedVersion(version) - except IrrationalVersionError: - return None diff -r 55437391511703f2fd40f3ee3cf18f89ef8f446a -r 5fbd833e8b0e88cf71920a1479f47260317c98b0 tox/_config.py --- a/tox/_config.py +++ /dev/null @@ -1,1072 +0,0 @@ -import argparse -import os -import random -from fnmatch import fnmatchcase -import sys -import re -import shlex -import string -import pkg_resources -import itertools -import pluggy - -import tox.interpreters -from tox import hookspecs - -import py - -import tox - -iswin32 = sys.platform == "win32" - -default_factors = {'jython': 'jython', 'pypy': 'pypy', 'pypy3': 'pypy3', - 'py': sys.executable} -for version in '26,27,32,33,34,35,36'.split(','): - default_factors['py' + version] = 'python%s.%s' % tuple(version) - -hookimpl = pluggy.HookimplMarker("tox") - - -def get_plugin_manager(): - # initialize plugin manager - pm = pluggy.PluginManager("tox") - pm.add_hookspecs(hookspecs) - pm.register(tox._config) - pm.register(tox.interpreters) - pm.load_setuptools_entrypoints("tox") - pm.check_pending() - return pm - - -class MyParser: - def __init__(self): - self.argparser = argparse.ArgumentParser( - description="tox options", add_help=False) - self._testenv_attr = [] - - def add_argument(self, *args, **kwargs): - return self.argparser.add_argument(*args, **kwargs) - - def add_testenv_attribute(self, name, type, help, default=None, postprocess=None): - self._testenv_attr.append(VenvAttribute(name, type, default, help, postprocess)) - - def add_testenv_attribute_obj(self, obj): - assert hasattr(obj, "name") - assert hasattr(obj, "type") - assert hasattr(obj, "help") - assert hasattr(obj, "postprocess") - self._testenv_attr.append(obj) - - def parse_args(self, args): - return self.argparser.parse_args(args) - - def format_help(self): - return self.argparser.format_help() - - -class VenvAttribute: - def __init__(self, name, type, default, help, postprocess): - self.name = name - self.type = type - self.default = default - self.help = help - self.postprocess = postprocess - - -class DepOption: - name = "deps" - type = "line-list" - help = "each line specifies a dependency in pip/setuptools format." - default = () - - def postprocess(self, config, reader, section_val): - deps = [] - for depline in section_val: - m = re.match(r":(\w+):\s*(\S+)", depline) - if m: - iname, name = m.groups() - ixserver = config.indexserver[iname] - else: - name = depline.strip() - ixserver = None - name = self._replace_forced_dep(name, config) - deps.append(DepConfig(name, ixserver)) - return deps - - def _replace_forced_dep(self, name, config): - """ - Override the given dependency config name taking --force-dep-version - option into account. - - :param name: dep config, for example ["pkg==1.0", "other==2.0"]. - :param config: Config instance - :return: the new dependency that should be used for virtual environments - """ - if not config.option.force_dep: - return name - for forced_dep in config.option.force_dep: - if self._is_same_dep(forced_dep, name): - return forced_dep - return name - - @classmethod - def _is_same_dep(cls, dep1, dep2): - """ - Returns True if both dependency definitions refer to the - same package, even if versions differ. - """ - dep1_name = pkg_resources.Requirement.parse(dep1).project_name - dep2_name = pkg_resources.Requirement.parse(dep2).project_name - return dep1_name == dep2_name - - -class PosargsOption: - name = "args_are_paths" - type = "bool" - default = True - help = "treat positional args in commands as paths" - - def postprocess(self, config, reader, section_val): - args = config.option.args - if args: - if section_val: - args = [] - for arg in config.option.args: - if arg: - origpath = config.invocationcwd.join(arg, abs=True) - if origpath.check(): - arg = reader.getpath("changedir", ".").bestrelpath(origpath) - args.append(arg) - reader.addsubstitutions(args) - return section_val - - -class InstallcmdOption: - name = "install_command" - type = "argv" - default = "pip install {opts} {packages}" - help = "install command for dependencies and package under test." - - def postprocess(self, config, reader, section_val): - if '{packages}' not in section_val: - raise tox.exception.ConfigError( - "'install_command' must contain '{packages}' substitution") - return section_val - - -def parseconfig(args=None): - """ - :param list[str] args: Optional list of arguments. - :type pkg: str - :rtype: :class:`Config` - :raise SystemExit: toxinit file is not found - """ - - pm = get_plugin_manager() - - if args is None: - args = sys.argv[1:] - - # prepare command line options - parser = MyParser() - pm.hook.tox_addoption(parser=parser) - - # parse command line options - option = parser.parse_args(args) - interpreters = tox.interpreters.Interpreters(hook=pm.hook) - config = Config(pluginmanager=pm, option=option, interpreters=interpreters) - config._parser = parser - config._testenv_attr = parser._testenv_attr - - # parse ini file - basename = config.option.configfile - if os.path.isabs(basename): - inipath = py.path.local(basename) - else: - for path in py.path.local().parts(reverse=True): - inipath = path.join(basename) - if inipath.check(): - break - else: - feedback("toxini file %r not found" % (basename), sysexit=True) - try: - parseini(config, inipath) - except tox.exception.InterpreterNotFound: - exn = sys.exc_info()[1] - # Use stdout to match test expectations - py.builtin.print_("ERROR: " + str(exn)) - - # post process config object - pm.hook.tox_configure(config=config) - - return config - - -def feedback(msg, sysexit=False): - py.builtin.print_("ERROR: " + msg, file=sys.stderr) - if sysexit: - raise SystemExit(1) - - -class VersionAction(argparse.Action): - def __call__(self, argparser, *args, **kwargs): - version = tox.__version__ - py.builtin.print_("%s imported from %s" % (version, tox.__file__)) - raise SystemExit(0) - - -class CountAction(argparse.Action): - def __call__(self, parser, namespace, values, option_string=None): - if hasattr(namespace, self.dest): - setattr(namespace, self.dest, int(getattr(namespace, self.dest)) + 1) - else: - setattr(namespace, self.dest, 0) - - - at hookimpl -def tox_addoption(parser): - # formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser.add_argument("--version", nargs=0, action=VersionAction, - dest="version", - help="report version information to stdout.") - parser.add_argument("-h", "--help", action="store_true", dest="help", - help="show help about options") - parser.add_argument("--help-ini", "--hi", action="store_true", dest="helpini", - help="show help about ini-names") - parser.add_argument("-v", nargs=0, action=CountAction, default=0, - dest="verbosity", - help="increase verbosity of reporting output.") - parser.add_argument("--showconfig", action="store_true", - help="show configuration information for all environments. ") - parser.add_argument("-l", "--listenvs", action="store_true", - dest="listenvs", help="show list of test environments") - parser.add_argument("-c", action="store", default="tox.ini", - dest="configfile", - help="use the specified config file name.") - parser.add_argument("-e", action="append", dest="env", - metavar="envlist", - help="work against specified environments (ALL selects all).") - parser.add_argument("--notest", action="store_true", dest="notest", - help="skip invoking test commands.") - parser.add_argument("--sdistonly", action="store_true", dest="sdistonly", - help="only perform the sdist packaging activity.") - parser.add_argument("--installpkg", action="store", default=None, - metavar="PATH", - help="use specified package for installation into venv, instead of " - "creating an sdist.") - parser.add_argument("--develop", action="store_true", dest="develop", - help="install package in the venv using 'setup.py develop' via " - "'pip -e .'") - parser.add_argument("--set-home", action="store_true", dest="sethome", - help="(experimental) force creating a new $HOME for each test " - "environment and create .pydistutils.cfg|pip.conf files " - "if index servers are specified with tox. ") - parser.add_argument('-i', action="append", - dest="indexurl", metavar="URL", - help="set indexserver url (if URL is of form name=url set the " - "url for the 'name' indexserver, specifically)") - parser.add_argument("--pre", action="store_true", dest="pre", - help="install pre-releases and development versions of dependencies. " - "This will pass the --pre option to install_command " - "(pip by default).") - parser.add_argument("-r", "--recreate", action="store_true", - dest="recreate", - help="force recreation of virtual environments") - parser.add_argument("--result-json", action="store", - dest="resultjson", metavar="PATH", - help="write a json file with detailed information about " - "all commands and results involved. This will turn off " - "pass-through output from running test commands which is " - "instead captured into the json result file.") - - # We choose 1 to 4294967295 because it is the range of PYTHONHASHSEED. - parser.add_argument("--hashseed", action="store", - metavar="SEED", default=None, - help="set PYTHONHASHSEED to SEED before running commands. " - "Defaults to a random integer in the range [1, 4294967295] " - "([1, 1024] on Windows). " - "Passing 'noset' suppresses this behavior.") - parser.add_argument("--force-dep", action="append", - metavar="REQ", default=None, - help="Forces a certain version of one of the dependencies " - "when configuring the virtual environment. REQ Examples " - "'pytest<2.7' or 'django>=1.6'.") - parser.add_argument("--sitepackages", action="store_true", - help="override sitepackages setting to True in all envs") - parser.add_argument("--skip-missing-interpreters", action="store_true", - help="don't fail tests for missing interpreters") - - parser.add_argument("args", nargs="*", - help="additional arguments available to command positional substitution") - - # add various core venv interpreter attributes - - parser.add_testenv_attribute( - name="envdir", type="path", default="{toxworkdir}/{envname}", - help="venv directory") - - parser.add_testenv_attribute( - name="envtmpdir", type="path", default="{envdir}/tmp", - help="venv temporary directory") - - parser.add_testenv_attribute( - name="envlogdir", type="path", default="{envdir}/log", - help="venv log directory") - - def downloadcache(config, reader, section_val): - if section_val: - # env var, if present, takes precedence - downloadcache = os.environ.get("PIP_DOWNLOAD_CACHE", section_val) - return py.path.local(downloadcache) - - parser.add_testenv_attribute( - name="downloadcache", type="string", default=None, postprocess=downloadcache, - help="(deprecated) set PIP_DOWNLOAD_CACHE.") - - parser.add_testenv_attribute( - name="changedir", type="path", default="{toxinidir}", - help="directory to change to when running commands") - - parser.add_testenv_attribute_obj(PosargsOption()) - - parser.add_testenv_attribute( - name="skip_install", type="bool", default=False, - help="Do not install the current package. This can be used when " - "you need the virtualenv management but do not want to install " - "the current package") - - parser.add_testenv_attribute( - name="ignore_errors", type="bool", default=False, - help="if set to True all commands will be executed irrespective of their " - "result error status.") - - def recreate(config, reader, section_val): - if config.option.recreate: - return True - return section_val - - parser.add_testenv_attribute( - name="recreate", type="bool", default=False, postprocess=recreate, - help="always recreate this test environment.") - - def setenv(config, reader, section_val): - setenv = section_val - if "PYTHONHASHSEED" not in setenv and config.hashseed is not None: - setenv['PYTHONHASHSEED'] = config.hashseed - return setenv - - parser.add_testenv_attribute( - name="setenv", type="dict", postprocess=setenv, - help="list of X=Y lines with environment variable settings") - - def passenv(config, reader, section_val): - passenv = set(["PATH"]) - - # we ensure that tmp directory settings are passed on - # we could also set it to the per-venv "envtmpdir" - # but this leads to very long paths when run with jenkins - # so we just pass it on by default for now. - if sys.platform == "win32": - passenv.add("SYSTEMROOT") # needed for python's crypto module - passenv.add("PATHEXT") # needed for discovering executables - passenv.add("TEMPDIR") - passenv.add("TMP") - else: - passenv.add("TMPDIR") - for spec in section_val: - for name in os.environ: - if fnmatchcase(name.upper(), spec.upper()): - passenv.add(name) - return passenv - - parser.add_testenv_attribute( - name="passenv", type="space-separated-list", postprocess=passenv, - help="environment variables names which shall be passed " - "from tox invocation to test environment when executing commands.") - - parser.add_testenv_attribute( - name="whitelist_externals", type="line-list", - help="each lines specifies a path or basename for which tox will not warn " - "about it coming from outside the test environment.") - - parser.add_testenv_attribute( - name="platform", type="string", default=".*", - help="regular expression which must match against ``sys.platform``. " - "otherwise testenv will be skipped.") - - def sitepackages(config, reader, section_val): - return config.option.sitepackages or section_val - - parser.add_testenv_attribute( - name="sitepackages", type="bool", default=False, postprocess=sitepackages, - help="Set to ``True`` if you want to create virtual environments that also " - "have access to globally installed packages.") - - def pip_pre(config, reader, section_val): - return config.option.pre or section_val - - parser.add_testenv_attribute( - name="pip_pre", type="bool", default=False, postprocess=pip_pre, - help="If ``True``, adds ``--pre`` to the ``opts`` passed to " - "the install command. ") - - def develop(config, reader, section_val): - return not config.option.installpkg and (section_val or config.option.develop) - - parser.add_testenv_attribute( - name="usedevelop", type="bool", postprocess=develop, default=False, - help="install package in develop/editable mode") - - def basepython_default(config, reader, section_val): - if section_val is None: - for f in reader.factors: - if f in default_factors: - return default_factors[f] - return sys.executable - return str(section_val) - - parser.add_testenv_attribute( - name="basepython", type="string", default=None, postprocess=basepython_default, - help="executable name or path of interpreter used to create a " - "virtual test environment.") - - parser.add_testenv_attribute_obj(InstallcmdOption()) - parser.add_testenv_attribute_obj(DepOption()) - - parser.add_testenv_attribute( - name="commands", type="argvlist", default="", - help="each line specifies a test command and can use substitution.") - - -class Config(object): - def __init__(self, pluginmanager, option, interpreters): - self.envconfigs = {} - self.invocationcwd = py.path.local() - self.interpreters = interpreters - self.pluginmanager = pluginmanager - self.option = option - - @property - def homedir(self): - homedir = get_homedir() - if homedir is None: - homedir = self.toxinidir # XXX good idea? - return homedir - - -class VenvConfig: - def __init__(self, envname, config): - self.envname = envname - self.config = config - - @property - def envbindir(self): - if (sys.platform == "win32" - and "jython" not in self.basepython - and "pypy" not in self.basepython): - return self.envdir.join("Scripts") - else: - return self.envdir.join("bin") - - @property - def envpython(self): - if "jython" in str(self.basepython): - name = "jython" - else: - name = "python" - return self.envbindir.join(name) - - # no @property to avoid early calling (see callable(subst[key]) checks) - def envsitepackagesdir(self): - self.getsupportedinterpreter() # for throwing exceptions - x = self.config.interpreters.get_sitepackagesdir( - info=self.python_info, - envdir=self.envdir) - return x - - @property - def python_info(self): - return self.config.interpreters.get_info(envconfig=self) - - def getsupportedinterpreter(self): - if sys.platform == "win32" and self.basepython and \ - "jython" in self.basepython: - raise tox.exception.UnsupportedInterpreter( - "Jython/Windows does not support installing scripts") - info = self.config.interpreters.get_info(envconfig=self) - if not info.executable: - raise tox.exception.InterpreterNotFound(self.basepython) - if not info.version_info: - raise tox.exception.InvocationError( - 'Failed to get version_info for %s: %s' % (info.name, info.err)) - if info.version_info < (2, 6): - raise tox.exception.UnsupportedInterpreter( - "python2.5 is not supported anymore, sorry") - return info.executable - - -testenvprefix = "testenv:" - - -def get_homedir(): - try: - return py.path.local._gethomedir() - except Exception: - return None - - -def make_hashseed(): - max_seed = 4294967295 - if sys.platform == 'win32': - max_seed = 1024 - return str(random.randint(1, max_seed)) - - -class parseini: - def __init__(self, config, inipath): - config.toxinipath = inipath - config.toxinidir = config.toxinipath.dirpath() - - self._cfg = py.iniconfig.IniConfig(config.toxinipath) - config._cfg = self._cfg - self.config = config - ctxname = getcontextname() - if ctxname == "jenkins": - reader = SectionReader("tox:jenkins", self._cfg, fallbacksections=['tox']) - distshare_default = "{toxworkdir}/distshare" - elif not ctxname: - reader = SectionReader("tox", self._cfg) - distshare_default = "{homedir}/.tox/distshare" - else: - raise ValueError("invalid context") - - if config.option.hashseed is None: - hashseed = make_hashseed() - elif config.option.hashseed == 'noset': - hashseed = None - else: - hashseed = config.option.hashseed - config.hashseed = hashseed - - reader.addsubstitutions(toxinidir=config.toxinidir, - homedir=config.homedir) - config.toxworkdir = reader.getpath("toxworkdir", "{toxinidir}/.tox") - config.minversion = reader.getstring("minversion", None) - - if not config.option.skip_missing_interpreters: - config.option.skip_missing_interpreters = \ - reader.getbool("skip_missing_interpreters", False) - - # determine indexserver dictionary - config.indexserver = {'default': IndexServerConfig('default')} - prefix = "indexserver" - for line in reader.getlist(prefix): - name, url = map(lambda x: x.strip(), line.split("=", 1)) - config.indexserver[name] = IndexServerConfig(name, url) - - override = False - if config.option.indexurl: - for urldef in config.option.indexurl: - m = re.match(r"\W*(\w+)=(\S+)", urldef) - if m is None: - url = urldef - name = "default" - else: - name, url = m.groups() - if not url: - url = None - if name != "ALL": - config.indexserver[name].url = url - else: - override = url - # let ALL override all existing entries - if override: - for name in config.indexserver: - config.indexserver[name] = IndexServerConfig(name, override) - - reader.addsubstitutions(toxworkdir=config.toxworkdir) - config.distdir = reader.getpath("distdir", "{toxworkdir}/dist") - reader.addsubstitutions(distdir=config.distdir) - config.distshare = reader.getpath("distshare", distshare_default) - reader.addsubstitutions(distshare=config.distshare) - config.sdistsrc = reader.getpath("sdistsrc", None) - config.setupdir = reader.getpath("setupdir", "{toxinidir}") - config.logdir = config.toxworkdir.join("log") - - config.envlist, all_envs = self._getenvdata(reader) - - # factors used in config or predefined - known_factors = self._list_section_factors("testenv") - known_factors.update(default_factors) - known_factors.add("python") - - # factors stated in config envlist - stated_envlist = reader.getstring("envlist", replace=False) - if stated_envlist: - for env in _split_env(stated_envlist): - known_factors.update(env.split('-')) - - # configure testenvs - for name in all_envs: - section = testenvprefix + name - factors = set(name.split('-')) - if section in self._cfg or factors <= known_factors: - config.envconfigs[name] = \ - self.make_envconfig(name, section, reader._subs, config) - - all_develop = all(name in config.envconfigs - and config.envconfigs[name].usedevelop - for name in config.envlist) - - config.skipsdist = reader.getbool("skipsdist", all_develop) - - def _list_section_factors(self, section): - factors = set() - if section in self._cfg: - for _, value in self._cfg[section].items(): - exprs = re.findall(r'^([\w{}\.,-]+)\:\s+', value, re.M) - factors.update(*mapcat(_split_factor_expr, exprs)) - return factors - - def make_envconfig(self, name, section, subs, config): - vc = VenvConfig(config=config, envname=name) - factors = set(name.split('-')) - reader = SectionReader(section, self._cfg, fallbacksections=["testenv"], - factors=factors) - reader.addsubstitutions(**subs) - reader.addsubstitutions(envname=name) - reader.vc = vc - - for env_attr in config._testenv_attr: - atype = env_attr.type - if atype in ("bool", "path", "string", "dict", "argv", "argvlist"): - meth = getattr(reader, "get" + atype) - res = meth(env_attr.name, env_attr.default) - elif atype == "space-separated-list": - res = reader.getlist(env_attr.name, sep=" ") - elif atype == "line-list": - res = reader.getlist(env_attr.name, sep="\n") - else: - raise ValueError("unknown type %r" % (atype,)) - - if env_attr.postprocess: - res = env_attr.postprocess(config, reader, res) - setattr(vc, env_attr.name, res) - - if atype == "path": - reader.addsubstitutions(**{env_attr.name: res}) - - if env_attr.name == "install_command": - reader.addsubstitutions(envbindir=vc.envbindir, envpython=vc.envpython, - envsitepackagesdir=vc.envsitepackagesdir) - return vc - - def _getenvdata(self, reader): - envstr = self.config.option.env \ - or os.environ.get("TOXENV") \ - or reader.getstring("envlist", replace=False) \ - or [] - envlist = _split_env(envstr) - - # collect section envs - all_envs = set(envlist) - set(["ALL"]) - for section in self._cfg: - if section.name.startswith(testenvprefix): - all_envs.add(section.name[len(testenvprefix):]) - if not all_envs: - all_envs.add("python") - - if not envlist or "ALL" in envlist: - envlist = sorted(all_envs) - - return envlist, all_envs - - -def _split_env(env): - """if handed a list, action="append" was used for -e """ - if not isinstance(env, list): - env = [env] - return mapcat(_expand_envstr, env) - - -def _split_factor_expr(expr): - partial_envs = _expand_envstr(expr) - return [set(e.split('-')) for e in partial_envs] - - -def _expand_envstr(envstr): - # split by commas not in groups - tokens = re.split(r'((?:\{[^}]+\})+)|,', envstr) - envlist = [''.join(g).strip() - for k, g in itertools.groupby(tokens, key=bool) if k] - - def expand(env): - tokens = re.split(r'\{([^}]+)\}', env) - parts = [token.split(',') for token in tokens] - return [''.join(variant) for variant in itertools.product(*parts)] - - return mapcat(expand, envlist) - - -def mapcat(f, seq): - return list(itertools.chain.from_iterable(map(f, seq))) - - -class DepConfig: - def __init__(self, name, indexserver=None): - self.name = name - self.indexserver = indexserver - - def __str__(self): - if self.indexserver: - if self.indexserver.name == "default": - return self.name - return ":%s:%s" % (self.indexserver.name, self.name) - return str(self.name) - __repr__ = __str__ - - -class IndexServerConfig: - def __init__(self, name, url=None): - self.name = name - self.url = url - - -#: Check value matches substitution form -#: of referencing value from other section. E.g. {[base]commands} -is_section_substitution = re.compile("{\[[^{}\s]+\]\S+?}").match - - -RE_ITEM_REF = re.compile( - r''' - (?[^[:{}]+):)? # optional sub_type for special rules - (?P[^{}]*) # substitution key - [}] - ''', - re.VERBOSE) - - -class SectionReader: - def __init__(self, section_name, cfgparser, fallbacksections=None, factors=()): - self.section_name = section_name - self._cfg = cfgparser - self.fallbacksections = fallbacksections or [] - self.factors = factors - self._subs = {} - self._subststack = [] - - def addsubstitutions(self, _posargs=None, **kw): - self._subs.update(kw) - if _posargs: - self.posargs = _posargs - - def getpath(self, name, defaultpath): - toxinidir = self._subs['toxinidir'] - path = self.getstring(name, defaultpath) - if path is None: - return path - return toxinidir.join(path, abs=True) - - def getlist(self, name, sep="\n"): - s = self.getstring(name, None) - if s is None: - return [] - return [x.strip() for x in s.split(sep) if x.strip()] - - def getdict(self, name, default=None, sep="\n"): - s = self.getstring(name, None) - if s is None: - return default or {} - - value = {} - for line in s.split(sep): - if line.strip(): - name, rest = line.split('=', 1) - value[name.strip()] = rest.strip() - - return value - - def getbool(self, name, default=None): - s = self.getstring(name, default) - if not s: - s = default - if s is None: - raise KeyError("no config value [%s] %s found" % ( - self.section_name, name)) - - if not isinstance(s, bool): - if s.lower() == "true": - s = True - elif s.lower() == "false": - s = False - else: - raise tox.exception.ConfigError( - "boolean value %r needs to be 'True' or 'False'") - return s - - def getargvlist(self, name, default=""): - s = self.getstring(name, default, replace=False) - return _ArgvlistReader.getargvlist(self, s) - - def getargv(self, name, default=""): - return self.getargvlist(name, default)[0] - - def getstring(self, name, default=None, replace=True): - x = None - for s in [self.section_name] + self.fallbacksections: - try: - x = self._cfg[s][name] - break - except KeyError: - continue - - if x is None: - x = default - else: - x = self._apply_factors(x) - - if replace and x and hasattr(x, 'replace'): - self._subststack.append((self.section_name, name)) - try: - x = self._replace(x) - finally: - assert self._subststack.pop() == (self.section_name, name) - # print "getstring", self.section_name, name, "returned", repr(x) - return x - - def _apply_factors(self, s): - def factor_line(line): - m = re.search(r'^([\w{}\.,-]+)\:\s+(.+)', line) - if not m: - return line - - expr, line = m.groups() - if any(fs <= self.factors for fs in _split_factor_expr(expr)): - return line - - lines = s.strip().splitlines() - return '\n'.join(filter(None, map(factor_line, lines))) - - def _replace_env(self, match): - match_value = match.group('substitution_value') - if not match_value: - raise tox.exception.ConfigError( - 'env: requires an environment variable name') - - default = None - envkey_split = match_value.split(':', 1) - - if len(envkey_split) is 2: - envkey, default = envkey_split - else: - envkey = match_value - - if envkey not in os.environ and default is None: - raise tox.exception.ConfigError( - "substitution env:%r: unkown environment variable %r" % - (envkey, envkey)) - - return os.environ.get(envkey, default) - - def _substitute_from_other_section(self, key): - if key.startswith("[") and "]" in key: - i = key.find("]") - section, item = key[1:i], key[i + 1:] - if section in self._cfg and item in self._cfg[section]: - if (section, item) in self._subststack: - raise ValueError('%s already in %s' % ( - (section, item), self._subststack)) - x = str(self._cfg[section][item]) - self._subststack.append((section, item)) - try: - return self._replace(x) - finally: - self._subststack.pop() - - raise tox.exception.ConfigError( - "substitution key %r not found" % key) - - def _replace_substitution(self, match): - sub_key = match.group('substitution_value') - val = self._subs.get(sub_key, None) - if val is None: - val = self._substitute_from_other_section(sub_key) - if py.builtin.callable(val): - val = val() - return str(val) - - def _replace_match(self, match): - g = match.groupdict() - - # special case: opts and packages. Leave {opts} and - # {packages} intact, they are replaced manually in - # _venv.VirtualEnv.run_install_command. - sub_value = g['substitution_value'] - if sub_value in ('opts', 'packages'): - return '{%s}' % sub_value - - handlers = { - 'env': self._replace_env, - None: self._replace_substitution, - } - try: - sub_type = g['sub_type'] - except KeyError: - raise tox.exception.ConfigError( - "Malformed substitution; no substitution type provided") - - try: - handler = handlers[sub_type] - except KeyError: - raise tox.exception.ConfigError("No support for the %s substitution type" % sub_type) - - return handler(match) - - def _replace(self, x): - if '{' in x: - return RE_ITEM_REF.sub(self._replace_match, x) - return x - - -class _ArgvlistReader: - @classmethod - def getargvlist(cls, reader, section_val): - """Parse ``commands`` argvlist multiline string. - - :param str name: Key name in a section. - :param str section_val: Content stored by key. - - :rtype: list[list[str]] - :raise :class:`tox.exception.ConfigError`: - line-continuation ends nowhere while resolving for specified section - """ - commands = [] - current_command = "" - for line in section_val.splitlines(): - line = line.rstrip() - i = line.find("#") - if i != -1: - line = line[:i].rstrip() - if not line: - continue - if line.endswith("\\"): - current_command += " " + line[:-1] - continue - current_command += line - - if is_section_substitution(current_command): - replaced = reader._replace(current_command) - commands.extend(cls.getargvlist(reader, replaced)) - else: - commands.append(cls.processcommand(reader, current_command)) - current_command = "" - else: - if current_command: - raise tox.exception.ConfigError( - "line-continuation ends nowhere while resolving for [%s] %s" % - (reader.section_name, "commands")) - return commands - - @classmethod - def processcommand(cls, reader, command): - posargs = getattr(reader, "posargs", None) - - # Iterate through each word of the command substituting as - # appropriate to construct the new command string. This - # string is then broken up into exec argv components using - # shlex. - newcommand = "" - for word in CommandParser(command).words(): - if word == "{posargs}" or word == "[]": - if posargs: - newcommand += " ".join(posargs) - continue - elif word.startswith("{posargs:") and word.endswith("}"): - if posargs: - newcommand += " ".join(posargs) - continue - else: - word = word[9:-1] - new_arg = "" - new_word = reader._replace(word) - new_word = reader._replace(new_word) - new_arg += new_word - newcommand += new_arg - - # Construct shlex object that will not escape any values, - # use all values as is in argv. - shlexer = shlex.shlex(newcommand, posix=True) - shlexer.whitespace_split = True - shlexer.escape = '' - shlexer.commenters = '' - argv = list(shlexer) - return argv - - -class CommandParser(object): - - class State(object): - def __init__(self): - self.word = '' - self.depth = 0 - self.yield_words = [] - - def __init__(self, command): - self.command = command - - def words(self): - ps = CommandParser.State() - - def word_has_ended(): - return ((cur_char in string.whitespace and ps.word and - ps.word[-1] not in string.whitespace) or - (cur_char == '{' and ps.depth == 0 and not ps.word.endswith('\\')) or - (ps.depth == 0 and ps.word and ps.word[-1] == '}') or - (cur_char not in string.whitespace and ps.word and - ps.word.strip() == '')) - - def yield_this_word(): - yieldword = ps.word - ps.word = '' - if yieldword: - ps.yield_words.append(yieldword) - - def yield_if_word_ended(): - if word_has_ended(): - yield_this_word() - - def accumulate(): - ps.word += cur_char - - def push_substitution(): - ps.depth += 1 - - def pop_substitution(): - ps.depth -= 1 - - for cur_char in self.command: - if cur_char in string.whitespace: - if ps.depth == 0: - yield_if_word_ended() - accumulate() - elif cur_char == '{': - yield_if_word_ended() - accumulate() - push_substitution() - elif cur_char == '}': - accumulate() - pop_substitution() - else: - yield_if_word_ended() - accumulate() - - if ps.word.strip(): - yield_this_word() - return ps.yield_words - - -def getcontextname(): - if any(env in os.environ for env in ['JENKINS_URL', 'HUDSON_URL']): - return 'jenkins' - return None diff -r 55437391511703f2fd40f3ee3cf18f89ef8f446a -r 5fbd833e8b0e88cf71920a1479f47260317c98b0 tox/_exception.py --- a/tox/_exception.py +++ /dev/null @@ -1,28 +0,0 @@ - -class Error(Exception): - def __str__(self): - return "%s: %s" % (self.__class__.__name__, self.args[0]) - - -class UnsupportedInterpreter(Error): - "signals an unsupported Interpreter" - - -class InterpreterNotFound(Error): - "signals that an interpreter could not be found" - - -class InvocationError(Error): - """ an error while invoking a script. """ - - -class MissingFile(Error): - """ an error while invoking a script. """ - - -class MissingDirectory(Error): - """ a directory did not exist. """ - - -class MissingDependency(Error): - """ a dependency could not be found or determined. """ diff -r 55437391511703f2fd40f3ee3cf18f89ef8f446a -r 5fbd833e8b0e88cf71920a1479f47260317c98b0 tox/_pytestplugin.py --- a/tox/_pytestplugin.py +++ b/tox/_pytestplugin.py @@ -6,10 +6,10 @@ from py.builtin import _isbytes, _istext, print_ from fnmatch import fnmatch import time -from tox._config import parseconfig -from tox._venv import VirtualEnv -from tox._cmdline import Action -from tox.result import ResultLog +from .config import parseconfig +from .venv import VirtualEnv +from .session import Action +from .result import ResultLog def pytest_configure(): @@ -134,7 +134,7 @@ @pytest.fixture def mocksession(request): - from tox._cmdline import Session + from tox.session import Session class MockSession(Session): def __init__(self): This diff is so big that we needed to truncate the remainder. Repository URL: https://bitbucket.org/hpk42/tox/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Tue May 12 16:01:31 2015 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 12 May 2015 14:01:31 -0000 Subject: [Pytest-commit] commit/tox: hpk42: mark distshare ini variable as deprecated Message-ID: <20150512140131.31149.13128@app14.ash-private.bitbucket.org> 1 new commit in tox: https://bitbucket.org/hpk42/tox/commits/438cc2bd3dc4/ Changeset: 438cc2bd3dc4 User: hpk42 Date: 2015-05-12 13:38:41+00:00 Summary: mark distshare ini variable as deprecated Affected #: 2 files diff -r 5fbd833e8b0e88cf71920a1479f47260317c98b0 -r 438cc2bd3dc4031438b61a5c96dd7ba3ecefb659 CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -17,7 +17,7 @@ If platform is set and doesn't match the platform spec in the test environment the test environment is ignored, no setup or tests are attempted. -.. (new) add per-venv "ignore_errors" setting, which defaults to False. +- (new) add per-venv "ignore_errors" setting, which defaults to False. If ``True``, a non-zero exit code from one command will be ignored and further commands will be executed (which was the default behavior in tox < 2.0). If ``False`` (the default), then a non-zero exit code from one command @@ -51,6 +51,8 @@ - rename internal files -- tox offers no external API except for the experimental plugin hooks, use tox internals at your own risk. +- DEPRECATE distshare in documentation + 1.9.2 diff -r 5fbd833e8b0e88cf71920a1479f47260317c98b0 -r 438cc2bd3dc4031438b61a5c96dd7ba3ecefb659 doc/config.txt --- a/doc/config.txt +++ b/doc/config.txt @@ -19,7 +19,7 @@ toxworkdir=path # tox working directory, defaults to {toxinidir}/.tox setupdir=path # defaults to {toxinidir} distdir=path # defaults to {toxworkdir}/dist - distshare=path # defaults to {homedir}/.tox/distshare + distshare=path # (DEPRECATED) defaults to {homedir}/.tox/distshare envlist=ENVLIST # defaults to the list of all environments skipsdist=BOOL # defaults to false @@ -30,7 +30,7 @@ [tox:jenkins] ... # override [tox] settings for the jenkins context - # note: for jenkins distshare defaults to ``{toxworkdir}/distshare``. + # note: for jenkins distshare defaults to ``{toxworkdir}/distshare`` (DEPRECATED) .. confval:: skip_missing_interpreters=BOOL @@ -323,7 +323,7 @@ the directory where sdist-packages will be created in ``{distshare}`` - the directory where sdist-packages will be copied to so that + (DEPRECATED) the directory where sdist-packages will be copied to so that they may be accessed by other processes or tox runs. substitutions for virtualenv-related sections Repository URL: https://bitbucket.org/hpk42/tox/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Tue May 12 17:26:40 2015 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 12 May 2015 15:26:40 -0000 Subject: [Pytest-commit] commit/tox: hpk42: - fix TMP env variable and test on windows Message-ID: <20150512152640.27140.89569@app06.ash-private.bitbucket.org> 1 new commit in tox: https://bitbucket.org/hpk42/tox/commits/6fc03c1208fd/ Changeset: 6fc03c1208fd User: hpk42 Date: 2015-05-12 15:24:16+00:00 Summary: - fix TMP env variable and test on windows - add release announcement - bump version to 2.0.0 Affected #: 6 files diff -r 438cc2bd3dc4031438b61a5c96dd7ba3ecefb659 -r 6fc03c1208fd310cd38a013e8fff7330dc10f6c3 doc/announce/release-2.0.txt --- a/doc/announce/release-2.0.txt +++ b/doc/announce/release-2.0.txt @@ -52,3 +52,60 @@ Holger Krekel, merlinux GmbH +2.0.0 +----------- + +- (new) introduce environment variable isolation: + tox now only passes the PATH variable from the tox + invocation environment to the test environment and on Windows + also ``SYSTEMROOT`` and ``PATHEXT``. If you need to pass through further + environment variables you can use the new ``passenv`` setting, + a space-separated list of environment variable names. Each name + can make use of fnmatch-style glob patterns. All environment + variables which exist in the tox-invocation environment will be copied + to the test environment. + +- (new) introduce a way to specify on which platform a testenvironment is to + execute: the new per-venv "platform" setting allows to specify + a regular expression which is matched against sys.platform. + If platform is set and doesn't match the platform spec in the test + environment the test environment is ignored, no setup or tests are attempted. + +- (new) add per-venv "ignore_errors" setting, which defaults to False. + If ``True``, a non-zero exit code from one command will be ignored and + further commands will be executed (which was the default behavior in tox < + 2.0). If ``False`` (the default), then a non-zero exit code from one command + will abort execution of commands for that environment. + +- show and store in json the version dependency information for each venv + +- remove the long-deprecated "distribute" option as it has no effect these days. + +- fix issue233: avoid hanging with tox-setuptools integration example. Thanks simonb. + +- fix issue120: allow substitution for the commands section. Thanks + Volodymyr Vitvitski. + +- fix issue235: fix AttributeError with --installpkg. Thanks + Volodymyr Vitvitski. + +- tox has now somewhat pep8 clean code, thanks to Volodymyr Vitvitski. + +- fix issue240: allow to specify empty argument list without it being + rewritten to ".". Thanks Daniel Hahler. + +- introduce experimental (not much documented yet) plugin system + based on pytest's externalized "pluggy" system. + See tox/hookspecs.py for the current hooks. + +- introduce parser.add_testenv_attribute() to register an ini-variable + for testenv sections. Can be used from plugins through the + tox_add_option hook. + +- rename internal files -- tox offers no external API except for the + experimental plugin hooks, use tox internals at your own risk. + +- DEPRECATE distshare in documentation + + + diff -r 438cc2bd3dc4031438b61a5c96dd7ba3ecefb659 -r 6fc03c1208fd310cd38a013e8fff7330dc10f6c3 doc/index.txt --- a/doc/index.txt +++ b/doc/index.txt @@ -107,6 +107,8 @@ support changelog links + plugins + example/result announce/release-0.5 announce/release-1.0 announce/release-1.1 @@ -114,6 +116,9 @@ announce/release-1.3 announce/release-1.4 announce/release-1.4.3 + announce/release-1.8 + announce/release-1.9 + announce/release-2.0 .. include:: links.txt diff -r 438cc2bd3dc4031438b61a5c96dd7ba3ecefb659 -r 6fc03c1208fd310cd38a013e8fff7330dc10f6c3 setup.py --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ description='virtualenv-based automation of test activities', long_description=open("README.rst").read(), url='http://tox.testrun.org/', - version='2.0.0.dev5', + version='2.0.0', license='http://opensource.org/licenses/MIT', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], author='holger krekel', diff -r 438cc2bd3dc4031438b61a5c96dd7ba3ecefb659 -r 6fc03c1208fd310cd38a013e8fff7330dc10f6c3 tests/test_config.py --- a/tests/test_config.py +++ b/tests/test_config.py @@ -710,6 +710,10 @@ if plat == "win32": assert "PATHEXT" in envconfig.passenv assert "SYSTEMROOT" in envconfig.passenv + assert "TEMP" in envconfig.passenv + assert "TMP" in envconfig.passenv + else: + assert "TMPDIR" in envconfig.passenv assert "PATH" in envconfig.passenv assert "A123A" in envconfig.passenv assert "A123B" in envconfig.passenv diff -r 438cc2bd3dc4031438b61a5c96dd7ba3ecefb659 -r 6fc03c1208fd310cd38a013e8fff7330dc10f6c3 tox/__init__.py --- a/tox/__init__.py +++ b/tox/__init__.py @@ -1,5 +1,5 @@ # -__version__ = '2.0.0.dev5' +__version__ = '2.0.0' from .hookspecs import hookspec, hookimpl # noqa diff -r 438cc2bd3dc4031438b61a5c96dd7ba3ecefb659 -r 6fc03c1208fd310cd38a013e8fff7330dc10f6c3 tox/config.py --- a/tox/config.py +++ b/tox/config.py @@ -369,7 +369,7 @@ if sys.platform == "win32": passenv.add("SYSTEMROOT") # needed for python's crypto module passenv.add("PATHEXT") # needed for discovering executables - passenv.add("TEMPDIR") + passenv.add("TEMP") passenv.add("TMP") else: passenv.add("TMPDIR") Repository URL: https://bitbucket.org/hpk42/tox/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Tue May 12 23:08:56 2015 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 12 May 2015 21:08:56 -0000 Subject: [Pytest-commit] commit/tox: 4 new changesets Message-ID: <20150512210856.29299.44321@app05.ash-private.bitbucket.org> 4 new commits in tox: https://bitbucket.org/hpk42/tox/commits/7c15500c3883/ Changeset: 7c15500c3883 User: hpk42 Date: 2015-05-12 17:57:41+00:00 Summary: refine docs Affected #: 5 files diff -r 6fc03c1208fd310cd38a013e8fff7330dc10f6c3 -r 7c15500c388337c46627a22668ed3d12a3df412b README.rst --- a/README.rst +++ b/README.rst @@ -21,5 +21,5 @@ have fun, -holger krekel, 2014 +holger krekel, 2015 diff -r 6fc03c1208fd310cd38a013e8fff7330dc10f6c3 -r 7c15500c388337c46627a22668ed3d12a3df412b doc/index.txt --- a/doc/index.txt +++ b/doc/index.txt @@ -65,8 +65,7 @@ * :doc:`(new in 2.0) plugin system ` to modify tox execution with simple hooks. -* uses pip_ and setuptools_ by default. Experimental - support for configuring the installer command +* uses pip_ and setuptools_ by default. Support for configuring the installer command through :confval:`install_command=ARGV`. * **cross-Python compatible**: CPython-2.6, 2.7, 3.2 and higher, diff -r 6fc03c1208fd310cd38a013e8fff7330dc10f6c3 -r 7c15500c388337c46627a22668ed3d12a3df412b doc/plugins.txt --- a/doc/plugins.txt +++ b/doc/plugins.txt @@ -6,8 +6,9 @@ .. versionadded:: 2.0 With tox-2.0 a few aspects of tox running can be experimentally modified -by writing hook functions. We expect the list of hook function to grow -over time. +by writing hook functions. The list of of available hook function is +to grow over time on a per-need basis. + writing a setuptools entrypoints plugin --------------------------------------- @@ -30,19 +31,23 @@ install_requires=['tox>=2.0'], ) -You can then install the plugin to develop it via:: +If installed, the ``entry_points`` part will make tox see and integrate +your plugin during startup. + +You can install the plugin for development ("in-place") via:: pip install -e . -and later publish it. +and later publish it via something like:: -The ``entry_points`` part allows tox to see your plugin during startup. + python setup.py sdist register upload Writing hook implementations ---------------------------- -A plugin module needs can define one or more hook implementation functions:: +A plugin module defines one or more hook implementation functions +by decorating them with tox's ``hookimpl`` marker:: from tox import hookimpl @@ -65,5 +70,9 @@ ---------------------------- .. automodule:: tox.hookspecs + +.. autoclass:: tox.config.Parser :members: +.. autoclass:: tox.config.Config + :members: diff -r 6fc03c1208fd310cd38a013e8fff7330dc10f6c3 -r 7c15500c388337c46627a22668ed3d12a3df412b tox/config.py --- a/tox/config.py +++ b/tox/config.py @@ -38,29 +38,48 @@ return pm -class MyParser: +class Parser: + """ command line and ini-parser control object. """ + def __init__(self): self.argparser = argparse.ArgumentParser( description="tox options", add_help=False) self._testenv_attr = [] def add_argument(self, *args, **kwargs): + """ add argument to command line parser. This takes the + same arguments that ``argparse.ArgumentParser.add_argument``. + """ return self.argparser.add_argument(*args, **kwargs) def add_testenv_attribute(self, name, type, help, default=None, postprocess=None): + """ add an ini-file variable for "testenv" section. + + Types are specified as strings like "bool", "line-list", "string", "argv", "path", + "argvlist". The ``postprocess`` function will be called for each testenv + like ``postprocess(config=config, reader=reader, section_val=section_val)`` + where ``section_val`` is the value as read from the ini (or the default value). + Any postprocess function must return a value which will then become the + eventual value in the testenv section. + """ self._testenv_attr.append(VenvAttribute(name, type, default, help, postprocess)) def add_testenv_attribute_obj(self, obj): + """ add an ini-file variable as an object. + + This works as ``add_testenv_attribute`` but expects "name", "type", "help", + and "postprocess" attributes on the object. + """ assert hasattr(obj, "name") assert hasattr(obj, "type") assert hasattr(obj, "help") assert hasattr(obj, "postprocess") self._testenv_attr.append(obj) - def parse_args(self, args): + def _parse_args(self, args): return self.argparser.parse_args(args) - def format_help(self): + def _format_help(self): return self.argparser.format_help() @@ -168,11 +187,11 @@ args = sys.argv[1:] # prepare command line options - parser = MyParser() + parser = Parser() pm.hook.tox_addoption(parser=parser) # parse command line options - option = parser.parse_args(args) + option = parser._parse_args(args) interpreters = tox.interpreters.Interpreters(hook=pm.hook) config = Config(pluginmanager=pm, option=option, interpreters=interpreters) config._parser = parser @@ -440,10 +459,12 @@ class Config(object): def __init__(self, pluginmanager, option, interpreters): + #: dictionary containing envname to envconfig mappings self.envconfigs = {} self.invocationcwd = py.path.local() self.interpreters = interpreters self.pluginmanager = pluginmanager + #: option namespace containing all parsed command line options self.option = option @property diff -r 6fc03c1208fd310cd38a013e8fff7330dc10f6c3 -r 7c15500c388337c46627a22668ed3d12a3df412b tox/session.py --- a/tox/session.py +++ b/tox/session.py @@ -44,7 +44,7 @@ def show_help(config): tw = py.io.TerminalWriter() - tw.write(config._parser.format_help()) + tw.write(config._parser._format_help()) tw.line() https://bitbucket.org/hpk42/tox/commits/6813a1058194/ Changeset: 6813a1058194 User: hpk42 Date: 2015-05-12 19:25:22+00:00 Summary: rename some API that is reachable by hooks Affected #: 6 files diff -r 7c15500c388337c46627a22668ed3d12a3df412b -r 6813a10581945910d6197a67b672d07276c6e2e0 CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -4,13 +4,17 @@ - (new) introduce environment variable isolation: tox now only passes the PATH variable from the tox invocation environment to the test environment and on Windows - also ``SYSTEMROOT`` and ``PATHEXT``. If you need to pass through further - environment variables you can use the new ``passenv`` setting, + also ``SYSTEMROOT``, ``PATHEXT``, ``TEMP`` and ``TMP`` whereas + on linux additionally ``TMPDIR`` is passed. If you need to pass + through further environment variables you can use the new ``passenv`` setting, a space-separated list of environment variable names. Each name can make use of fnmatch-style glob patterns. All environment variables which exist in the tox-invocation environment will be copied to the test environment. +- a new ``--help-ini`` option shows all possible testenv settings and + their defaults. + - (new) introduce a way to specify on which platform a testenvironment is to execute: the new per-venv "platform" setting allows to specify a regular expression which is matched against sys.platform. diff -r 7c15500c388337c46627a22668ed3d12a3df412b -r 6813a10581945910d6197a67b672d07276c6e2e0 doc/announce/release-2.0.txt --- a/doc/announce/release-2.0.txt +++ b/doc/announce/release-2.0.txt @@ -51,20 +51,23 @@ best, Holger Krekel, merlinux GmbH - 2.0.0 ----------- - (new) introduce environment variable isolation: tox now only passes the PATH variable from the tox invocation environment to the test environment and on Windows - also ``SYSTEMROOT`` and ``PATHEXT``. If you need to pass through further - environment variables you can use the new ``passenv`` setting, + also ``SYSTEMROOT``, ``PATHEXT``, ``TEMP`` and ``TMP`` whereas + on linux additionally ``TMPDIR`` is passed. If you need to pass + through further environment variables you can use the new ``passenv`` setting, a space-separated list of environment variable names. Each name can make use of fnmatch-style glob patterns. All environment variables which exist in the tox-invocation environment will be copied to the test environment. +- a new ``--help-ini`` option shows all possible testenv settings and + their defaults. + - (new) introduce a way to specify on which platform a testenvironment is to execute: the new per-venv "platform" setting allows to specify a regular expression which is matched against sys.platform. @@ -107,5 +110,3 @@ - DEPRECATE distshare in documentation - - diff -r 7c15500c388337c46627a22668ed3d12a3df412b -r 6813a10581945910d6197a67b672d07276c6e2e0 doc/install.txt --- a/doc/install.txt +++ b/doc/install.txt @@ -8,7 +8,7 @@ **Operating systems**: Linux, Windows, OSX, Unix -**Installer Requirements**: setuptools_ or Distribute_ +**Installer Requirements**: setuptools_ **License**: MIT license diff -r 7c15500c388337c46627a22668ed3d12a3df412b -r 6813a10581945910d6197a67b672d07276c6e2e0 doc/plugins.txt --- a/doc/plugins.txt +++ b/doc/plugins.txt @@ -66,13 +66,16 @@ -tox hook specifications ----------------------------- +tox hook specifications and related API +--------------------------------------- .. automodule:: tox.hookspecs -.. autoclass:: tox.config.Parser +.. autoclass:: tox.config.Parser() :members: -.. autoclass:: tox.config.Config +.. autoclass:: tox.config.Config() :members: + +.. autoclass:: tox.config.TestenvConfig() + :members: diff -r 7c15500c388337c46627a22668ed3d12a3df412b -r 6813a10581945910d6197a67b672d07276c6e2e0 tox.ini --- a/tox.ini +++ b/tox.ini @@ -19,7 +19,7 @@ py.test -v check_sphinx.py {posargs} [testenv:flakes] -qwe = 123 +platform=linux deps = pytest-flakes>=0.2 pytest-pep8 diff -r 7c15500c388337c46627a22668ed3d12a3df412b -r 6813a10581945910d6197a67b672d07276c6e2e0 tox/config.py --- a/tox/config.py +++ b/tox/config.py @@ -56,19 +56,24 @@ """ add an ini-file variable for "testenv" section. Types are specified as strings like "bool", "line-list", "string", "argv", "path", - "argvlist". The ``postprocess`` function will be called for each testenv - like ``postprocess(config=config, reader=reader, section_val=section_val)`` - where ``section_val`` is the value as read from the ini (or the default value). - Any postprocess function must return a value which will then become the - eventual value in the testenv section. + "argvlist". + + The ``postprocess`` function will be called for each testenv + like ``postprocess(testenv_config=testenv_config, value=value)`` + where ``value`` is the value as read from the ini (or the default value) + and ``testenv_config`` is a :py:class:`tox.config.TestenvConfig` instance + which will receive all ini-variables as object attributes. + + Any postprocess function must return a value which will then be set + as the final value in the testenv section. """ self._testenv_attr.append(VenvAttribute(name, type, default, help, postprocess)) def add_testenv_attribute_obj(self, obj): """ add an ini-file variable as an object. - This works as ``add_testenv_attribute`` but expects "name", "type", "help", - and "postprocess" attributes on the object. + This works as the ``add_testenv_attribute`` function but expects + "name", "type", "help", and "postprocess" attributes on the object. """ assert hasattr(obj, "name") assert hasattr(obj, "type") @@ -98,9 +103,10 @@ help = "each line specifies a dependency in pip/setuptools format." default = () - def postprocess(self, config, reader, section_val): + def postprocess(self, testenv_config, value): deps = [] - for depline in section_val: + config = testenv_config.config + for depline in value: m = re.match(r":(\w+):\s*(\S+)", depline) if m: iname, name = m.groups() @@ -145,19 +151,20 @@ default = True help = "treat positional args in commands as paths" - def postprocess(self, config, reader, section_val): + def postprocess(self, testenv_config, value): + config = testenv_config.config args = config.option.args if args: - if section_val: + if value: args = [] for arg in config.option.args: if arg: origpath = config.invocationcwd.join(arg, abs=True) if origpath.check(): - arg = reader.getpath("changedir", ".").bestrelpath(origpath) + arg = testenv_config.changedir.bestrelpath(origpath) args.append(arg) - reader.addsubstitutions(args) - return section_val + testenv_config._reader.addsubstitutions(args) + return value class InstallcmdOption: @@ -166,11 +173,11 @@ default = "pip install {opts} {packages}" help = "install command for dependencies and package under test." - def postprocess(self, config, reader, section_val): - if '{packages}' not in section_val: + def postprocess(self, testenv_config, value): + if '{packages}' not in value: raise tox.exception.ConfigError( "'install_command' must contain '{packages}' substitution") - return section_val + return value def parseconfig(args=None): @@ -332,10 +339,10 @@ name="envlogdir", type="path", default="{envdir}/log", help="venv log directory") - def downloadcache(config, reader, section_val): - if section_val: + def downloadcache(testenv_config, value): + if value: # env var, if present, takes precedence - downloadcache = os.environ.get("PIP_DOWNLOAD_CACHE", section_val) + downloadcache = os.environ.get("PIP_DOWNLOAD_CACHE", value) return py.path.local(downloadcache) parser.add_testenv_attribute( @@ -359,17 +366,18 @@ help="if set to True all commands will be executed irrespective of their " "result error status.") - def recreate(config, reader, section_val): - if config.option.recreate: + def recreate(testenv_config, value): + if testenv_config.config.option.recreate: return True - return section_val + return value parser.add_testenv_attribute( name="recreate", type="bool", default=False, postprocess=recreate, help="always recreate this test environment.") - def setenv(config, reader, section_val): - setenv = section_val + def setenv(testenv_config, value): + setenv = value + config = testenv_config.config if "PYTHONHASHSEED" not in setenv and config.hashseed is not None: setenv['PYTHONHASHSEED'] = config.hashseed return setenv @@ -378,7 +386,7 @@ name="setenv", type="dict", postprocess=setenv, help="list of X=Y lines with environment variable settings") - def passenv(config, reader, section_val): + def passenv(testenv_config, value): passenv = set(["PATH"]) # we ensure that tmp directory settings are passed on @@ -392,7 +400,7 @@ passenv.add("TMP") else: passenv.add("TMPDIR") - for spec in section_val: + for spec in value: for name in os.environ: if fnmatchcase(name.upper(), spec.upper()): passenv.add(name) @@ -413,36 +421,37 @@ help="regular expression which must match against ``sys.platform``. " "otherwise testenv will be skipped.") - def sitepackages(config, reader, section_val): - return config.option.sitepackages or section_val + def sitepackages(testenv_config, value): + return testenv_config.config.option.sitepackages or value parser.add_testenv_attribute( name="sitepackages", type="bool", default=False, postprocess=sitepackages, help="Set to ``True`` if you want to create virtual environments that also " "have access to globally installed packages.") - def pip_pre(config, reader, section_val): - return config.option.pre or section_val + def pip_pre(testenv_config, value): + return testenv_config.config.option.pre or value parser.add_testenv_attribute( name="pip_pre", type="bool", default=False, postprocess=pip_pre, help="If ``True``, adds ``--pre`` to the ``opts`` passed to " "the install command. ") - def develop(config, reader, section_val): - return not config.option.installpkg and (section_val or config.option.develop) + def develop(testenv_config, value): + option = testenv_config.config.option + return not option.installpkg and (value or option.develop) parser.add_testenv_attribute( name="usedevelop", type="bool", postprocess=develop, default=False, help="install package in develop/editable mode") - def basepython_default(config, reader, section_val): - if section_val is None: - for f in reader.factors: + def basepython_default(testenv_config, value): + if value is None: + for f in testenv_config.factors: if f in default_factors: return default_factors[f] return sys.executable - return str(section_val) + return str(value) parser.add_testenv_attribute( name="basepython", type="string", default=None, postprocess=basepython_default, @@ -458,6 +467,7 @@ class Config(object): + """ Global Tox config object. """ def __init__(self, pluginmanager, option, interpreters): #: dictionary containing envname to envconfig mappings self.envconfigs = {} @@ -475,13 +485,23 @@ return homedir -class VenvConfig: - def __init__(self, envname, config): +class TestenvConfig: + """ Testenv Configuration object. + In addition to some core attributes/properties this config object holds all + per-testenv ini attributes as attributes, see "tox --help-ini" for an overview. + """ + def __init__(self, envname, config, factors, reader): + #: test environment name self.envname = envname + #: global tox config object self.config = config + #: set of factors + self.factors = factors + self._reader = reader @property def envbindir(self): + """ path to directory where scripts/binaries reside. """ if (sys.platform == "win32" and "jython" not in self.basepython and "pypy" not in self.basepython): @@ -491,6 +511,7 @@ @property def envpython(self): + """ path to python/jython executable. """ if "jython" in str(self.basepython): name = "jython" else: @@ -499,6 +520,9 @@ # no @property to avoid early calling (see callable(subst[key]) checks) def envsitepackagesdir(self): + """ return sitepackagesdir of the virtualenv environment. + (only available during execution, not parsing) + """ self.getsupportedinterpreter() # for throwing exceptions x = self.config.interpreters.get_sitepackagesdir( info=self.python_info, @@ -507,6 +531,7 @@ @property def python_info(self): + """ return sitepackagesdir of the virtualenv environment. """ return self.config.interpreters.get_info(envconfig=self) def getsupportedinterpreter(self): @@ -650,13 +675,12 @@ return factors def make_envconfig(self, name, section, subs, config): - vc = VenvConfig(config=config, envname=name) factors = set(name.split('-')) reader = SectionReader(section, self._cfg, fallbacksections=["testenv"], factors=factors) + vc = TestenvConfig(config=config, envname=name, factors=factors, reader=reader) reader.addsubstitutions(**subs) reader.addsubstitutions(envname=name) - reader.vc = vc for env_attr in config._testenv_attr: atype = env_attr.type @@ -671,7 +695,7 @@ raise ValueError("unknown type %r" % (atype,)) if env_attr.postprocess: - res = env_attr.postprocess(config, reader, res) + res = env_attr.postprocess(testenv_config=vc, value=res) setattr(vc, env_attr.name, res) if atype == "path": @@ -785,9 +809,8 @@ def getpath(self, name, defaultpath): toxinidir = self._subs['toxinidir'] path = self.getstring(name, defaultpath) - if path is None: - return path - return toxinidir.join(path, abs=True) + if path is not None: + return toxinidir.join(path, abs=True) def getlist(self, name, sep="\n"): s = self.getstring(name, None) @@ -952,11 +975,11 @@ class _ArgvlistReader: @classmethod - def getargvlist(cls, reader, section_val): + def getargvlist(cls, reader, value): """Parse ``commands`` argvlist multiline string. :param str name: Key name in a section. - :param str section_val: Content stored by key. + :param str value: Content stored by key. :rtype: list[list[str]] :raise :class:`tox.exception.ConfigError`: @@ -964,7 +987,7 @@ """ commands = [] current_command = "" - for line in section_val.splitlines(): + for line in value.splitlines(): line = line.rstrip() i = line.find("#") if i != -1: https://bitbucket.org/hpk42/tox/commits/892c54b52a04/ Changeset: 892c54b52a04 User: hpk42 Date: 2015-05-12 19:42:12+00:00 Summary: show all registered per-testenv ini values Affected #: 2 files diff -r 6813a10581945910d6197a67b672d07276c6e2e0 -r 892c54b52a044ac8dc46fc302ea96f4d755dad9d tests/test_config.py --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1566,14 +1566,14 @@ result = cmd.run("tox", "--showconfig") assert result.ret == 0 result.stdout.fnmatch_lines([ - r'*deps=*dep1==2.3, dep2*', + r'*deps*dep1==2.3, dep2*', ]) # override dep1 specific version, and force version for dep2 result = cmd.run("tox", "--showconfig", "--force-dep=dep1", "--force-dep=dep2==5.0") assert result.ret == 0 result.stdout.fnmatch_lines([ - r'*deps=*dep1, dep2==5.0*', + r'*deps*dep1, dep2==5.0*', ]) diff -r 6813a10581945910d6197a67b672d07276c6e2e0 -r 892c54b52a044ac8dc46fc302ea96f4d755dad9d tox/session.py --- a/tox/session.py +++ b/tox/session.py @@ -594,25 +594,9 @@ self.report.tw.line() for envconfig in self.config.envconfigs.values(): self.report.line("[testenv:%s]" % envconfig.envname, bold=True) - self.report.line(" basepython=%s" % envconfig.basepython) - self.report.line(" pythoninfo=%s" % (envconfig.python_info,)) - self.report.line(" envpython=%s" % envconfig.envpython) - self.report.line(" envtmpdir=%s" % envconfig.envtmpdir) - self.report.line(" envbindir=%s" % envconfig.envbindir) - self.report.line(" envlogdir=%s" % envconfig.envlogdir) - self.report.line(" changedir=%s" % envconfig.changedir) - self.report.line(" args_are_path=%s" % envconfig.args_are_paths) - self.report.line(" install_command=%s" % - envconfig.install_command) - self.report.line(" commands=") - for command in envconfig.commands: - self.report.line(" %s" % command) - self.report.line(" deps=%s" % envconfig.deps) - self.report.line(" envdir= %s" % envconfig.envdir) - self.report.line(" downloadcache=%s" % envconfig.downloadcache) - self.report.line(" usedevelop=%s" % envconfig.usedevelop) - self.report.line(" setenv=%s" % envconfig.setenv) - self.report.line(" passenv=%s" % envconfig.passenv) + for attr in self.config._parser._testenv_attr: + self.report.line(" %-15s = %s" + % (attr.name, getattr(envconfig, attr.name))) def showenvs(self): for env in self.config.envlist: https://bitbucket.org/hpk42/tox/commits/b7e498efd0ec/ Changeset: b7e498efd0ec User: hpk42 Date: 2015-05-12 21:02:11+00:00 Summary: also pass PIP_INDEX_URL because it's likely that the user wants to use it with the pip commands that run inside the tox run. Affected #: 5 files diff -r 892c54b52a044ac8dc46fc302ea96f4d755dad9d -r b7e498efd0ecd543a870431ea8d34f2882d5ace8 CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -2,10 +2,10 @@ ----------- - (new) introduce environment variable isolation: - tox now only passes the PATH variable from the tox + tox now only passes the PATH and PIP_INDEX_URL variable from the tox invocation environment to the test environment and on Windows also ``SYSTEMROOT``, ``PATHEXT``, ``TEMP`` and ``TMP`` whereas - on linux additionally ``TMPDIR`` is passed. If you need to pass + on unix additionally ``TMPDIR`` is passed. If you need to pass through further environment variables you can use the new ``passenv`` setting, a space-separated list of environment variable names. Each name can make use of fnmatch-style glob patterns. All environment diff -r 892c54b52a044ac8dc46fc302ea96f4d755dad9d -r b7e498efd0ecd543a870431ea8d34f2882d5ace8 doc/announce/release-2.0.txt --- a/doc/announce/release-2.0.txt +++ b/doc/announce/release-2.0.txt @@ -55,10 +55,10 @@ ----------- - (new) introduce environment variable isolation: - tox now only passes the PATH variable from the tox + tox now only passes the PATH and PIP_INDEX_URL variable from the tox invocation environment to the test environment and on Windows also ``SYSTEMROOT``, ``PATHEXT``, ``TEMP`` and ``TMP`` whereas - on linux additionally ``TMPDIR`` is passed. If you need to pass + on unix additionally ``TMPDIR`` is passed. If you need to pass through further environment variables you can use the new ``passenv`` setting, a space-separated list of environment variable names. Each name can make use of fnmatch-style glob patterns. All environment @@ -109,4 +109,3 @@ experimental plugin hooks, use tox internals at your own risk. - DEPRECATE distshare in documentation - diff -r 892c54b52a044ac8dc46fc302ea96f4d755dad9d -r b7e498efd0ecd543a870431ea8d34f2882d5ace8 doc/config.txt --- a/doc/config.txt +++ b/doc/config.txt @@ -199,8 +199,9 @@ invocation environment it is ignored. You can use ``*`` and ``?`` to match multiple environment variables with one name. - Note that the ``PATH`` variable is unconditionally passed down and on - Windows ``SYSTEMROOT`` and ``PATHEXT`` will be passed down as well. + Note that the ``PATH`` and ``PIP_INDEX_URL`` variables are unconditionally + passed down and on Windows ``SYSTEMROOT``, ``PATHEXT``, ``TEMP`` and ``TMP`` + will be passed down as well whereas on unix ``TMPDIR`` will be passed down. You can override these variables with the ``setenv`` option. .. confval:: recreate=True|False(default) diff -r 892c54b52a044ac8dc46fc302ea96f4d755dad9d -r b7e498efd0ecd543a870431ea8d34f2882d5ace8 tests/test_config.py --- a/tests/test_config.py +++ b/tests/test_config.py @@ -715,6 +715,7 @@ else: assert "TMPDIR" in envconfig.passenv assert "PATH" in envconfig.passenv + assert "PIP_INDEX_URL" in envconfig.passenv assert "A123A" in envconfig.passenv assert "A123B" in envconfig.passenv diff -r 892c54b52a044ac8dc46fc302ea96f4d755dad9d -r b7e498efd0ecd543a870431ea8d34f2882d5ace8 tox/config.py --- a/tox/config.py +++ b/tox/config.py @@ -387,7 +387,7 @@ help="list of X=Y lines with environment variable settings") def passenv(testenv_config, value): - passenv = set(["PATH"]) + passenv = set(["PATH", "PIP_INDEX_URL"]) # we ensure that tmp directory settings are passed on # we could also set it to the per-venv "envtmpdir" Repository URL: https://bitbucket.org/hpk42/tox/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Tue May 12 23:51:25 2015 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 12 May 2015 21:51:25 -0000 Subject: [Pytest-commit] commit/tox: 2 new changesets Message-ID: <20150512215125.3178.46557@app11.ash-private.bitbucket.org> 2 new commits in tox: https://bitbucket.org/hpk42/tox/commits/e779c14a978f/ Changeset: e779c14a978f User: hpk42 Date: 2015-05-12 21:48:48+00:00 Summary: Added tag 2.0.0 for changeset b7e498efd0ec Affected #: 1 file diff -r b7e498efd0ecd543a870431ea8d34f2882d5ace8 -r e779c14a978f633e0d6739946d665c3474a14ddc .hgtags --- a/.hgtags +++ b/.hgtags @@ -23,3 +23,4 @@ ad64513cf6bf0ef99a4a11678aa3260d8078976f 1.9.0 7ccc0b0bd25e1e01dfd861b890ada69042a32c3a 1.9.1 452288d6c50042ccfc1c944b24f4eb47df8f6823 1.9.2 +b7e498efd0ecd543a870431ea8d34f2882d5ace8 2.0.0 https://bitbucket.org/hpk42/tox/commits/d6827276d86b/ Changeset: d6827276d86b User: hpk42 Date: 2015-05-12 21:50:07+00:00 Summary: fix release announce to point to "latest" doc target Affected #: 1 file diff -r e779c14a978f633e0d6739946d665c3474a14ddc -r d6827276d86b62dc50596f6c9f676898a1934438 doc/announce/release-2.0.txt --- a/doc/announce/release-2.0.txt +++ b/doc/announce/release-2.0.txt @@ -4,7 +4,7 @@ tox-2.0 was released to pypi, a major new release with *mostly* backward-compatible enhancements and fixes: -- experimental support for plugins, see https://testrun.org/tox/dev/plugins.html +- experimental support for plugins, see https://testrun.org/tox/latest/plugins.html which includes also a refined internal registration mechanism for new testenv ini options. You can now ask tox which testenv ini parameters exist with ``tox --help-ini``. Repository URL: https://bitbucket.org/hpk42/tox/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Tue May 12 23:54:03 2015 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 12 May 2015 21:54:03 -0000 Subject: [Pytest-commit] commit/tox: hpk42: actually show hook specs Message-ID: <20150512215403.28350.75868@app10.ash-private.bitbucket.org> 1 new commit in tox: https://bitbucket.org/hpk42/tox/commits/9a701e4a22b5/ Changeset: 9a701e4a22b5 User: hpk42 Date: 2015-05-12 21:53:54+00:00 Summary: actually show hook specs Affected #: 1 file diff -r d6827276d86b62dc50596f6c9f676898a1934438 -r 9a701e4a22b55b6dfb193c421381140e30ab2445 doc/plugins.txt --- a/doc/plugins.txt +++ b/doc/plugins.txt @@ -70,6 +70,7 @@ --------------------------------------- .. automodule:: tox.hookspecs + :members: .. autoclass:: tox.config.Parser() :members: Repository URL: https://bitbucket.org/hpk42/tox/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Wed May 13 01:37:59 2015 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 12 May 2015 23:37:59 -0000 Subject: [Pytest-commit] commit/tox: 2 new changesets Message-ID: <20150512233759.16046.80706@app02.ash-private.bitbucket.org> 2 new commits in tox: https://bitbucket.org/hpk42/tox/commits/2897c9e3a019/ Changeset: 2897c9e3a019 User: hpk42 Date: 2015-05-12 23:27:42+00:00 Summary: fix wheel packaging to properly require argparse on py26. Affected #: 5 files diff -r 9a701e4a22b55b6dfb193c421381140e30ab2445 -r 2897c9e3a019ee29948cbeda319ffac0e6902053 CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,8 @@ +2.0.1 +----------- + +- fix wheel packaging to properly require argparse on py26. + 2.0.0 ----------- diff -r 9a701e4a22b55b6dfb193c421381140e30ab2445 -r 2897c9e3a019ee29948cbeda319ffac0e6902053 doc/conf.py --- a/doc/conf.py +++ b/doc/conf.py @@ -49,7 +49,7 @@ # # The short X.Y version. release = "2.0" -version = "2.0.0" +version = "2.0.1" # The full version, including alpha/beta/rc tags. # The language for content autogenerated by Sphinx. Refer to documentation diff -r 9a701e4a22b55b6dfb193c421381140e30ab2445 -r 2897c9e3a019ee29948cbeda319ffac0e6902053 setup.py --- a/setup.py +++ b/setup.py @@ -1,5 +1,5 @@ import sys -from setuptools import setup +import setuptools from setuptools.command.test import test as TestCommand @@ -15,18 +15,40 @@ import tox tox.cmdline(self.test_args) +def has_environment_marker_support(): + """ + Tests that setuptools has support for PEP-426 environment marker support. + + The first known release to support it is 0.7 (and the earliest on PyPI seems to be 0.7.2 + so we're using that), see: http://pythonhosted.org/setuptools/history.html#id142 + + References: + + * https://wheel.readthedocs.org/en/latest/index.html#defining-conditional-dependencies + * https://www.python.org/dev/peps/pep-0426/#environment-markers + """ + import pkg_resources + try: + return pkg_resources.parse_version(setuptools.__version__) >= pkg_resources.parse_version('0.7.2') + except Exception as exc: + sys.stderr.write("Could not test setuptool's version: %s\n" % exc) + return False def main(): version = sys.version_info[:2] install_requires = ['virtualenv>=1.11.2', 'py>=1.4.17', 'pluggy>=0.3.0,<0.4.0'] - if version < (2, 7): - install_requires += ['argparse'] - setup( + extras_require = {} + if has_environment_marker_support(): + extras_require[':python_version=="2.6"'] = ['argparse'] + else: + if version < (2, 7): + install_requires += ['argparse'] + setuptools.setup( name='tox', description='virtualenv-based automation of test activities', long_description=open("README.rst").read(), url='http://tox.testrun.org/', - version='2.0.0', + version='2.0.1', license='http://opensource.org/licenses/MIT', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], author='holger krekel', @@ -38,6 +60,7 @@ tests_require=['tox'], cmdclass={"test": Tox}, install_requires=install_requires, + extras_require=extras_require, classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', diff -r 9a701e4a22b55b6dfb193c421381140e30ab2445 -r 2897c9e3a019ee29948cbeda319ffac0e6902053 tox.ini --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist=py27,py26,py34,py33,pypy,flakes +envlist=py27,py26,py34,py33,pypy,flakes,py26-bare [testenv:X] commands=echo {posargs} @@ -10,6 +10,10 @@ deps=pytest>=2.3.5 pytest-timeout +[testenv:py26-bare] +deps = +commands = tox -h + [testenv:docs] basepython=python changedir=doc diff -r 9a701e4a22b55b6dfb193c421381140e30ab2445 -r 2897c9e3a019ee29948cbeda319ffac0e6902053 tox/__init__.py --- a/tox/__init__.py +++ b/tox/__init__.py @@ -1,5 +1,5 @@ # -__version__ = '2.0.0' +__version__ = '2.0.1' from .hookspecs import hookspec, hookimpl # noqa https://bitbucket.org/hpk42/tox/commits/b136e1f17193/ Changeset: b136e1f17193 User: hpk42 Date: 2015-05-12 23:37:50+00:00 Summary: Added tag 2.0.1 for changeset 2897c9e3a019 Affected #: 1 file diff -r 2897c9e3a019ee29948cbeda319ffac0e6902053 -r b136e1f17193945ee7f61e2e1884564b18b489ac .hgtags --- a/.hgtags +++ b/.hgtags @@ -24,3 +24,4 @@ 7ccc0b0bd25e1e01dfd861b890ada69042a32c3a 1.9.1 452288d6c50042ccfc1c944b24f4eb47df8f6823 1.9.2 b7e498efd0ecd543a870431ea8d34f2882d5ace8 2.0.0 +2897c9e3a019ee29948cbeda319ffac0e6902053 2.0.1 Repository URL: https://bitbucket.org/hpk42/tox/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From issues-reply at bitbucket.org Wed May 13 06:06:08 2015 From: issues-reply at bitbucket.org (Takeshi Komiya) Date: Wed, 13 May 2015 04:06:08 -0000 Subject: [Pytest-commit] Issue #244: detox crashes with tox-2.0.1 (hpk42/tox) Message-ID: <20150513040608.13559.36812@app10.ash-private.bitbucket.org> New issue 244: detox crashes with tox-2.0.1 https://bitbucket.org/hpk42/tox/issue/244/detox-crashes-with-tox-201 Takeshi Komiya: I got following error from detox. ``` $ detox Traceback (most recent call last): File "/home/ubuntu/virtualenv/python2.7/bin/detox", line 9, in load_entry_point('detox==0.9.4', 'console_scripts', 'detox')() File "/home/ubuntu/virtualenv/python2.7/local/lib/python2.7/site-packages/pkg_resources.py", line 378, in load_entry_point return get_distribution(dist).load_entry_point(group, name) File "/home/ubuntu/virtualenv/python2.7/local/lib/python2.7/site-packages/pkg_resources.py", line 2566, in load_entry_point return ep.load() File "/home/ubuntu/virtualenv/python2.7/local/lib/python2.7/site-packages/pkg_resources.py", line 2260, in load entry = __import__(self.module_name, globals(),globals(), ['__name__']) File "/home/ubuntu/virtualenv/python2.7/local/lib/python2.7/site-packages/detox/main.py", line 5, in from detox.proc import Detox File "/home/ubuntu/virtualenv/python2.7/local/lib/python2.7/site-packages/detox/proc.py", line 10, in import tox._config ImportError: No module named _config ``` I use detox (HEAD) and tox-2.0.1. If downgrade tox to 1.9.2, detox works fine. From commits-noreply at bitbucket.org Wed May 13 10:47:51 2015 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 13 May 2015 08:47:51 -0000 Subject: [Pytest-commit] commit/tox: hpk42: Merged in maquilina/tox/maquilina/fix-minor-grammatical-error-in-readmerst-1431506151478 (pull request #158) Message-ID: <20150513084751.27728.91207@app08.ash-private.bitbucket.org> 1 new commit in tox: https://bitbucket.org/hpk42/tox/commits/7b318dbb8376/ Changeset: 7b318dbb8376 User: hpk42 Date: 2015-05-13 08:47:41+00:00 Summary: Merged in maquilina/tox/maquilina/fix-minor-grammatical-error-in-readmerst-1431506151478 (pull request #158) Fix minor grammatical error in README.rst Affected #: 1 file diff -r b136e1f17193945ee7f61e2e1884564b18b489ac -r 7b318dbb8376844d469588c1fc8a100f389e513d README.rst --- a/README.rst +++ b/README.rst @@ -2,7 +2,7 @@ What is Tox? -------------------- -Tox as is a generic virtualenv management and test command line tool you can use for: +Tox is a generic virtualenv management and test command line tool you can use for: * checking your package installs correctly with different Python versions and interpreters Repository URL: https://bitbucket.org/hpk42/tox/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Wed May 13 10:48:03 2015 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Wed, 13 May 2015 08:48:03 -0000 Subject: [Pytest-commit] commit/tox: 2 new changesets Message-ID: <20150513084803.25872.2532@app13.ash-private.bitbucket.org> 2 new commits in tox: https://bitbucket.org/hpk42/tox/commits/31c1be6989fe/ Changeset: 31c1be6989fe Branch: maquilina/fix-minor-grammatical-error-in-readmerst-1431506151478 User: maquilina Date: 2015-05-13 08:36:48+00:00 Summary: Fix minor grammatical error in README.rst Affected #: 1 file diff -r b136e1f17193945ee7f61e2e1884564b18b489ac -r 31c1be6989fe28d38bee8c8d9ef7a2dc776825d9 README.rst --- a/README.rst +++ b/README.rst @@ -2,7 +2,7 @@ What is Tox? -------------------- -Tox as is a generic virtualenv management and test command line tool you can use for: +Tox is a generic virtualenv management and test command line tool you can use for: * checking your package installs correctly with different Python versions and interpreters https://bitbucket.org/hpk42/tox/commits/7b318dbb8376/ Changeset: 7b318dbb8376 User: hpk42 Date: 2015-05-13 08:47:41+00:00 Summary: Merged in maquilina/tox/maquilina/fix-minor-grammatical-error-in-readmerst-1431506151478 (pull request #158) Fix minor grammatical error in README.rst Affected #: 1 file diff -r b136e1f17193945ee7f61e2e1884564b18b489ac -r 7b318dbb8376844d469588c1fc8a100f389e513d README.rst --- a/README.rst +++ b/README.rst @@ -2,7 +2,7 @@ What is Tox? -------------------- -Tox as is a generic virtualenv management and test command line tool you can use for: +Tox is a generic virtualenv management and test command line tool you can use for: * checking your package installs correctly with different Python versions and interpreters Repository URL: https://bitbucket.org/hpk42/tox/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From issues-reply at bitbucket.org Wed May 13 23:43:14 2015 From: issues-reply at bitbucket.org (gregbestland) Date: Wed, 13 May 2015 21:43:14 -0000 Subject: [Pytest-commit] Issue #245: Can Not install sure dependency in tox. (hpk42/tox) Message-ID: <20150513214314.1771.8959@app02.ash-private.bitbucket.org> New issue 245: Can Not install sure dependency in tox. https://bitbucket.org/hpk42/tox/issue/245/can-not-install-sure-dependency-in-tox gregbestland: When I specify sure as a dependency in my tox file it will error out during install. This only appears to a problem with python 3.3 and 3.4. The only reason I think this is an issue with tox is that it started happening today, and I noticed you a pushed a new version of tox to pypi, which correlates with the breakage. Here is the exact issue I'm seeing.. py33 installdeps: nose, mock, unittest2, PyYAML, six, sure, blist ERROR: invocation failed (exit code 1), logfile: /Users/gregbestland/git/python-driver/.tox/py33/log/py33-1.log ERROR: actionid: py33 msg: getenv cmdargs: [local('/Users/gregbestland/git/python-driver/.tox/py33/bin/pip'), 'install', 'nose', 'mock', 'unittest2', 'PyYAML', 'six', 'sure', 'blist'] env: {'PATH': '/Users/gregbestland/git/python-driver/.tox/py33/bin:/Users/gregbestland/.pyenv/shims:/usr/local/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/gregbestland/git/automaton/bin', 'VIRTUAL_ENV': '/Users/gregbestland/git/python-driver/.tox/py33', 'PYTHONHASHSEED': '1606799025', 'USE_CASS_EXTERNAL': '1'} Collecting nose Using cached nose-1.3.6-py3-none-any.whl Collecting mock Using cached mock-1.0.1.tar.gz Collecting unittest2 Using cached unittest2-1.0.1-py2.py3-none-any.whl Collecting PyYAML Using cached PyYAML-3.11.tar.gz Collecting six Using cached six-1.9.0-py2.py3-none-any.whl Collecting sure Using cached sure-1.2.12.tar.gz Complete output from command python setup.py egg_info: Traceback (most recent call last): File "", line 20, in File "/private/tmp/pip-build-zwg5pd/sure/setup.py", line 54, in version=read_version(), File "/private/tmp/pip-build-zwg5pd/sure/setup.py", line 40, in read_version finder.visit(ast.parse(local_file('sure', '__init__.py'))) File "/private/tmp/pip-build-zwg5pd/sure/setup.py", line 45, in open(os.path.join(os.path.dirname(__file__), *f)).read() File "/Users/gregbestland/git/python-driver/.tox/py33/lib/python3.3/encodings/ascii.py", line 26, in decode return codecs.ascii_decode(input, self.errors)[0] UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 143: ordinal not in range(128) ---------------------------------------- Command "python setup.py egg_info" failed with error code 1 in /private/tmp/pip-build-zwg5pd/sure ERROR: could not install deps [nose, mock, unittest2, PyYAML, six, sure, blist]; v = InvocationError('/Users/gregbestland/git/python-driver/.tox/py33/bin/pip install nose mock unittest2 PyYAML six sure blist (see /Users/gregbestland/git/python-driver/.tox/py33/log/py33-1.log)', 1) ___________________________________ summary ____________________________________ ERROR: py33: could not install deps [nose, mock, unittest2, PyYAML, six, sure, blist]; v = InvocationError('/Users/gregbestland/git/python-driver/.tox/py33/bin/pip install nose mock unittest2 PyYAML six sure blist (see /Users/gregbestland/git/python-driver/.tox/py33/log/py33-1.log)', 1) If I specify a much much older version of sure for instance 1.2.3 it will work. Anything>1.2.3 and the install run through tox fails. The newer versions were working great yesterday. I'm currently working around it by specifying an older version of sure, but I'm guessing this might affect other packages installed by tox maybe? From issues-reply at bitbucket.org Thu May 14 03:05:49 2015 From: issues-reply at bitbucket.org (Brant Knudson) Date: Thu, 14 May 2015 01:05:49 -0000 Subject: [Pytest-commit] Issue #246: envbindir substitution key not found (hpk42/tox) Message-ID: <20150514010549.23868.52023@app01.ash-private.bitbucket.org> New issue 246: envbindir substitution key not found https://bitbucket.org/hpk42/tox/issue/246/envbindir-substitution-key-not-found Brant Knudson: If tox.ini has {envbindir} you get an exception tox.ConfigError: ConfigError: substitution key 'envbindir' not found {envbindir} works with tox<2.0 and it's in the docs: http://tox.readthedocs.org/en/latest/config.html#substitutions-for-virtualenv-related-sections From issues-reply at bitbucket.org Thu May 14 04:04:26 2015 From: issues-reply at bitbucket.org (lcampagn) Date: Thu, 14 May 2015 02:04:26 -0000 Subject: [Pytest-commit] Issue #743: Assert rewrite causes reload() to fail (pytest-dev/pytest) Message-ID: <20150514020426.27892.61639@app07.ash-private.bitbucket.org> New issue 743: Assert rewrite causes reload() to fail https://bitbucket.org/pytest-dev/pytest/issue/743/assert-rewrite-causes-reload-to-fail lcampagn: I have a unit test that passes when run with assert=plain or reinterp, but fails with rewrite. I also looked over #435, but I believe this is a separate issue. The offending test is attached. It works by writing a module to be imported, then re-writing, reloading it, and testing that the reload worked as expected. I imagine there might be no sane way to fix this bug. If that is the case, then is there a way to disable assertion rewriting on a per-module basis? I was a bit surprised to find that adding messages to the assert statements did not fix the problem. From issues-reply at bitbucket.org Thu May 14 20:30:28 2015 From: issues-reply at bitbucket.org (gvanrossum) Date: Thu, 14 May 2015 18:30:28 -0000 Subject: [Pytest-commit] Issue #744: py.test assert rewriting broken with Python 3.5a4+ (pytest-dev/pytest) Message-ID: <20150514183028.26112.65170@app03.ash-private.bitbucket.org> New issue 744: py.test assert rewriting broken with Python 3.5a4+ https://bitbucket.org/pytest-dev/pytest/issue/744/pytest-assert-rewriting-broken-with-python gvanrossum: I am experiencing a traceback ending thus: ``` /usr/local/lib/python3.5/site-packages/_pytest/assertion/rewrite.py:137: in find_module source_stat, co = _rewrite_test(state, fn_pypath) /usr/local/lib/python3.5/site-packages/_pytest/assertion/rewrite.py:278: in _rewrite_test rewrite_asserts(tree) /usr/local/lib/python3.5/site-packages/_pytest/assertion/rewrite.py:336: in rewrite_asserts AssertionRewriter().run(mod) /usr/local/lib/python3.5/site-packages/_pytest/assertion/rewrite.py:550: in run new.extend(self.visit(child)) /usr/local/lib/python3.5/ast.py:245: in visit return visitor(node) /usr/local/lib/python3.5/site-packages/_pytest/assertion/rewrite.py:658: in visit_Assert top_condition, explanation = self.visit(assert_.test) /usr/local/lib/python3.5/ast.py:245: in visit return visitor(node) /usr/local/lib/python3.5/site-packages/_pytest/assertion/rewrite.py:789: in visit_Compare left_res, left_expl = self.visit(comp.left) /usr/local/lib/python3.5/ast.py:245: in visit return visitor(node) /usr/local/lib/python3.5/site-packages/_pytest/assertion/rewrite.py:640: in generic_visit return res, self.explanation_param(self.display(res)) /usr/local/lib/python3.5/site-packages/_pytest/assertion/rewrite.py:577: in display return self.helper("saferepr", expr) /usr/local/lib/python3.5/site-packages/_pytest/assertion/rewrite.py:583: in helper return ast.Call(attr, list(args), [], None, None) E TypeError: Call constructor takes either 0 or 3 positional arguments ``` I get this as soon as I have an assert statement in my test (even `assert True`). From issues-reply at bitbucket.org Sun May 17 02:57:45 2015 From: issues-reply at bitbucket.org (Anthony Sottile) Date: Sun, 17 May 2015 00:57:45 -0000 Subject: [Pytest-commit] Issue #247: Pass through LANG environment variable by default (hpk42/tox) Message-ID: <20150517005745.10800.8465@app05.ash-private.bitbucket.org> New issue 247: Pass through LANG environment variable by default https://bitbucket.org/hpk42/tox/issue/247/pass-through-lang-environment-variable-by Anthony Sottile: This renders io.open() and py3 open() pretty useless without it on linux at least (as they will use ASCII instead of the platform's default encoding) From issues-reply at bitbucket.org Sun May 17 04:34:34 2015 From: issues-reply at bitbucket.org (Anthony Sottile) Date: Sun, 17 May 2015 02:34:34 -0000 Subject: [Pytest-commit] Issue #248: Pass through VS*COMNTOOLS during install on windows (hpk42/tox) Message-ID: <20150517023434.14382.25484@app14.ash-private.bitbucket.org> New issue 248: Pass through VS*COMNTOOLS during install on windows https://bitbucket.org/hpk42/tox/issue/248/pass-through-vs-comntools-during-install Anthony Sottile: It's not easy at all to build binary projects without these variables. Alternatively, why not allow the full environment during installation? From issues-reply at bitbucket.org Sun May 17 09:27:31 2015 From: issues-reply at bitbucket.org (Anthon van der Neut) Date: Sun, 17 May 2015 07:27:31 -0000 Subject: [Pytest-commit] Issue #249: tox --help and other options not depending on tox.ini should always work (hpk42/tox) Message-ID: <20150517072731.17428.39103@app06.ash-private.bitbucket.org> New issue 249: tox --help and other options not depending on tox.ini should always work https://bitbucket.org/hpk42/tox/issue/249/tox-help-and-other-options-not-depending Anthon van der Neut: If you don't have a `tox.ini` in your current directory, you can run `tox --version`, but when you do `tox --help`. It should not be necessary to have a `tox.ini` installed just to look at the commandline help (which always exits), and preferably it should not be necessary for any other options that don't depend on information in the `tox.ini` file either (but for that tox would have to know about what each plugin expects). I noticed this when invoking `tox --scan PATTERN1 PATTERN2` from `winpysetup.exe`, I had to make an empty `tox.ini` in the current directory first (and remove it) if not there. From issues-reply at bitbucket.org Sun May 17 10:28:13 2015 From: issues-reply at bitbucket.org (Anthon van der Neut) Date: Sun, 17 May 2015 08:28:13 -0000 Subject: [Pytest-commit] Issue #250: tox colors the output of py.test if term=dumb on windows (hpk42/tox) Message-ID: <20150517082813.5470.26144@app12.ash-private.bitbucket.org> New issue 250: tox colors the output of py.test if term=dumb on windows https://bitbucket.org/hpk42/tox/issue/250/tox-colors-the-output-of-pytest-if-term Anthon van der Neut: If the output of `py.test` is unreadable because of the color scheme on windows, you can use `set term=dumb` to suppress coloring. This works only partly when running `tox`, because `tox` own output gets de-colored, but the output from `py.test` within that output **is** colored. (Is the TERM env. variable not passed on by `tox`?) From issues-reply at bitbucket.org Sun May 17 15:46:33 2015 From: issues-reply at bitbucket.org (Anthon van der Neut) Date: Sun, 17 May 2015 13:46:33 -0000 Subject: [Pytest-commit] Issue #251: detox gives command succeeded even if there was no setup.py (hpk42/tox) Message-ID: <20150517134633.3454.26955@app05.ash-private.bitbucket.org> New issue 251: detox gives command succeeded even if there was no setup.py https://bitbucket.org/hpk42/tox/issue/251/detox-gives-command-succeeded-even-if Anthon van der Neut: If you have a directory with **only** a `tox.ini`: ``` [tox] envlist = py27,py26,py34,py33,pypy [testenv] deps=pytest commands=py.test test ``` and run `detox` you will see an error that setup.py is not there, but you will be congratulated that all commands succeeded From issues-reply at bitbucket.org Mon May 18 12:38:33 2015 From: issues-reply at bitbucket.org (Gilles Dartiguelongue) Date: Mon, 18 May 2015 10:38:33 -0000 Subject: [Pytest-commit] Issue #252: tox >=1.9.2 fails with & in path (hpk42/tox) Message-ID: <20150518103833.1476.73934@app08.ash-private.bitbucket.org> New issue 252: tox >=1.9.2 fails with & in path https://bitbucket.org/hpk42/tox/issue/252/tox-192-fails-with-in-path Gilles Dartiguelongue: Starting with tox 2.0, tox fails when trying to launch commands with path containing & : ``` #!shell $ /var/lib/jenkins/shiningpanda/jobs/3d4c850b/tools/bin/python -c "import tox; tox.cmdline();" -c tox.ini -e py27 GLOB sdist-make: /opt/jenkins/workspace/ocs.conf/TOXENV/py27/label_exp/armhf&&python/setup.py py27 create: /opt/jenkins/workspace/ocs.conf/TOXENV/py27/label_exp/armhf&&python/.tox/py27 py27 installdeps: nose, coverage, pylint, pep8 py27 inst: /opt/jenkins/workspace/ocs.conf/TOXENV/py27/label_exp/armhf&&python/.tox/dist/ocs.conf-0.29.1.zip Traceback (most recent call last): File "", line 1, in File "/var/lib/jenkins/shiningpanda/jobs/3d4c850b/tools/local/lib/python2.7/site-packages/tox/session.py", line 39, in main retcode = Session(config).runcommand() File "/var/lib/jenkins/shiningpanda/jobs/3d4c850b/tools/local/lib/python2.7/site-packages/tox/session.py", line 367, in runcommand return self.subcommand_test() File "/var/lib/jenkins/shiningpanda/jobs/3d4c850b/tools/local/lib/python2.7/site-packages/tox/session.py", line 534, in subcommand_test output = py.process.cmdexec("%s freeze" % (pip)) File "/var/lib/jenkins/shiningpanda/jobs/3d4c850b/tools/local/lib/python2.7/site-packages/py/_process/cmdexec.py", line 28, in cmdexec raise ExecutionFailed(status, status, cmd, out, err) py.process.cmdexec.Error: ExecutionFailed: 127 /opt/jenkins/workspace/ocs.conf/TOXENV/py27/label_exp/armhf&&python/.tox/py27/bin/pip freeze /bin/sh: 1: /opt/jenkins/workspace/ocs.conf/TOXENV/py27/label_exp/armhf: not found ``` While this is imho a problem with py/pytest not taking care of escaping strings passed to shell it is a problem specifically happening with tox >=2.0. We need this to work for supporting label expressions in jenkins jobs and the jenkins tox plugin does not allow restricting the version it uses. From commits-noreply at bitbucket.org Tue May 19 10:31:42 2015 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 19 May 2015 08:31:42 -0000 Subject: [Pytest-commit] commit/pytest: 6 new changesets Message-ID: <20150519083142.25388.4456@app05.ash-private.bitbucket.org> 6 new commits in pytest: https://bitbucket.org/pytest-dev/pytest/commits/15b6e332a861/ Changeset: 15b6e332a861 Branch: pytest-2.7 User: flub Date: 2015-05-19 00:50:32+00:00 Summary: Set 2.7.1 version number Affected #: 1 file diff -r cf8974765313be632a059b6c6b60190bdc78516e -r 15b6e332a861ccbc3e0d5841605ba76dc4682f1d _pytest/__init__.py --- a/_pytest/__init__.py +++ b/_pytest/__init__.py @@ -1,2 +1,2 @@ # -__version__ = '2.7.1.dev1' +__version__ = '2.7.1' https://bitbucket.org/pytest-dev/pytest/commits/156a7427364c/ Changeset: 156a7427364c Branch: pytest-2.7 User: flub Date: 2015-05-19 00:52:06+00:00 Summary: More updates/fixes to release process Some of this is temporary, but this is how I got things to work. Affected #: 1 file diff -r 15b6e332a861ccbc3e0d5841605ba76dc4682f1d -r 156a7427364cf375ca59ecb1f13eb2c4f473c96e HOWTORELEASE.rst --- a/HOWTORELEASE.rst +++ b/HOWTORELEASE.rst @@ -2,7 +2,7 @@ How to release pytest (draft) -------------------------------------------- -1. bump version numbers in pytest/__init__.py (setup.py reads it) +1. bump version numbers in _pytest/__init__.py (setup.py reads it) 2. check and finalize CHANGELOG @@ -25,22 +25,30 @@ which is ok (tox does not support skipping on per-platform basis yet). -7. Regenerate the docs using the toplevel makefile:: - make docs +7. Regenerate the docs examples using tox:: + # Create and activate a virtualenv with regendoc installed + # (currently needs revision 4a9ec1035734) + tox -e regen -8. Upload the docs using the toplevel makefile:: - make upload-docs +8. Build the docs, you need a virtualenv with, py and sphinx + installed:: + cd docs/en + make html + +9. Upload the docs using docs/en/Makefile:: + cd docs/en + make install # or "installall" if you have LaTeX installed This requires ssh-login permission on pytest.org because it uses rsync. Note that the "install" target of doc/en/Makefile defines where the rsync goes to, typically to the "latest" section of pytest.org. -9. publish to pypi "devpi push pytest-2.6.2 pypi:NAME" where NAME +10. publish to pypi "devpi push pytest-2.6.2 pypi:NAME" where NAME is the name of pypi.python.org as configured in your ~/.pypirc file -- it's the same you would use with "setup.py upload -r NAME" -10. send release announcement to mailing lists: +11. send release announcement to mailing lists: pytest-dev testing-in-python https://bitbucket.org/pytest-dev/pytest/commits/326db802fbc0/ Changeset: 326db802fbc0 Branch: pytest-2.7 User: flub Date: 2015-05-19 00:52:33+00:00 Summary: Remove unused link Affected #: 1 file diff -r 156a7427364cf375ca59ecb1f13eb2c4f473c96e -r 326db802fbc02e1d5509106dfe81784accfb6b6d doc/en/adopt.txt --- a/doc/en/adopt.txt +++ b/doc/en/adopt.txt @@ -11,7 +11,6 @@ .. _`issue tracker`: https://bitbucket.org/pytest-dev/pytest/issue/676/adopt-pytest-month-2015 .. _`pytest-dev mailing list`: https://mail.python.org/mailman/listinfo/pytest-dev -.. _``: The ideal pytest helper https://bitbucket.org/pytest-dev/pytest/commits/d3b98f508034/ Changeset: d3b98f508034 Branch: pytest-2.7 User: flub Date: 2015-05-19 00:53:06+00:00 Summary: Fix rest formatting and set final version number Affected #: 2 files diff -r 326db802fbc02e1d5509106dfe81784accfb6b6d -r d3b98f508034d5cc2efabac801907d75cbf197e5 CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,4 @@ -2.7.1.dev (compared to 2.7.0) +2.7.1 (compared to 2.7.0) ----------------------------- - fix issue731: do not get confused by the braces which may be present @@ -91,7 +91,7 @@ it from the "decorator" case. Thanks Tom Viner. - "python_classes" and "python_functions" options now support glob-patterns - for test discovery, as discussed in issue600. Thanks Ldiary Translations. + for test discovery, as discussed in issue600. Thanks Ldiary Translations. - allow to override parametrized fixtures with non-parametrized ones and vice versa (bubenkoff). diff -r 326db802fbc02e1d5509106dfe81784accfb6b6d -r d3b98f508034d5cc2efabac801907d75cbf197e5 doc/en/announce/release-2.7.0.txt --- a/doc/en/announce/release-2.7.0.txt +++ b/doc/en/announce/release-2.7.0.txt @@ -89,7 +89,7 @@ it from the "decorator" case. Thanks Tom Viner. - "python_classes" and "python_functions" options now support glob-patterns - for test discovery, as discussed in issue600. Thanks Ldiary Translations. + for test discovery, as discussed in issue600. Thanks Ldiary Translations. - allow to override parametrized fixtures with non-parametrized ones and vice versa (bubenkoff). https://bitbucket.org/pytest-dev/pytest/commits/edc1d080bab5/ Changeset: edc1d080bab5 Branch: pytest-2.7 User: flub Date: 2015-05-19 00:54:24+00:00 Summary: Regenerate docs using 2.7.1 Affected #: 17 files diff -r d3b98f508034d5cc2efabac801907d75cbf197e5 -r edc1d080bab5a970da8f6c776be50768829a7b09 doc/en/assert.txt --- a/doc/en/assert.txt +++ b/doc/en/assert.txt @@ -26,8 +26,8 @@ $ py.test test_assert1.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-98, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-87, inifile: collected 1 items test_assert1.py F @@ -136,8 +136,8 @@ $ py.test test_assert2.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-98, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-87, inifile: collected 1 items test_assert2.py F @@ -213,7 +213,7 @@ E vals: 1 != 2 test_foocompare.py:8: AssertionError - 1 failed in 0.00 seconds + 1 failed in 0.01 seconds .. _assert-details: .. _`assert introspection`: diff -r d3b98f508034d5cc2efabac801907d75cbf197e5 -r edc1d080bab5a970da8f6c776be50768829a7b09 doc/en/capture.txt --- a/doc/en/capture.txt +++ b/doc/en/capture.txt @@ -64,8 +64,8 @@ $ py.test =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-101, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-90, inifile: collected 2 items test_module.py .F @@ -79,7 +79,7 @@ test_module.py:9: AssertionError -------------------------- Captured stdout setup --------------------------- - setting up + setting up ==================== 1 failed, 1 passed in 0.01 seconds ==================== Accessing captured output from a test function diff -r d3b98f508034d5cc2efabac801907d75cbf197e5 -r edc1d080bab5a970da8f6c776be50768829a7b09 doc/en/doctest.txt --- a/doc/en/doctest.txt +++ b/doc/en/doctest.txt @@ -44,13 +44,13 @@ $ py.test =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-107, inifile: pytest.ini + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-96, inifile: pytest.ini collected 1 items mymodule.py . - ========================= 1 passed in 0.05 seconds ========================= + ========================= 1 passed in 0.06 seconds ========================= It is possible to use fixtures using the ``getfixture`` helper:: diff -r d3b98f508034d5cc2efabac801907d75cbf197e5 -r edc1d080bab5a970da8f6c776be50768829a7b09 doc/en/example/markers.txt --- a/doc/en/example/markers.txt +++ b/doc/en/example/markers.txt @@ -31,8 +31,8 @@ $ py.test -v -m webtest =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/doc-exec-157, inifile: collecting ... collected 4 items test_server.py::test_send_http PASSED @@ -44,8 +44,8 @@ $ py.test -v -m "not webtest" =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/doc-exec-157, inifile: collecting ... collected 4 items test_server.py::test_something_quick PASSED @@ -64,8 +64,8 @@ $ py.test -v test_server.py::TestClass::test_method =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/doc-exec-157, inifile: collecting ... collected 5 items test_server.py::TestClass::test_method PASSED @@ -76,8 +76,8 @@ $ py.test -v test_server.py::TestClass =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/doc-exec-157, inifile: collecting ... collected 4 items test_server.py::TestClass::test_method PASSED @@ -88,8 +88,8 @@ $ py.test -v test_server.py::TestClass test_server.py::test_send_http =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/doc-exec-157, inifile: collecting ... collected 8 items test_server.py::TestClass::test_method PASSED @@ -125,8 +125,8 @@ $ py.test -v -k http # running with the above defined example module =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/doc-exec-157, inifile: collecting ... collected 4 items test_server.py::test_send_http PASSED @@ -138,8 +138,8 @@ $ py.test -k "not send_http" -v =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/doc-exec-157, inifile: collecting ... collected 4 items test_server.py::test_something_quick PASSED @@ -153,8 +153,8 @@ $ py.test -k "http or quick" -v =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/doc-exec-157, inifile: collecting ... collected 4 items test_server.py::test_send_http PASSED @@ -342,25 +342,25 @@ $ py.test -E stage2 =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-157, inifile: collected 1 items test_someenv.py s - ======================== 1 skipped in 0.00 seconds ========================= + ======================== 1 skipped in 0.01 seconds ========================= and here is one that specifies exactly the environment needed:: $ py.test -E stage1 =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-157, inifile: collected 1 items test_someenv.py . - ========================= 1 passed in 0.00 seconds ========================= + ========================= 1 passed in 0.01 seconds ========================= The ``--markers`` option always gives you a list of available markers:: @@ -473,13 +473,13 @@ $ py.test -rs # this option reports skip reasons =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-157, inifile: collected 4 items test_plat.py sss. ========================= short test summary info ========================== - SKIP [3] /tmp/doc-exec-167/conftest.py:12: cannot run on platform linux + SKIP [3] /tmp/doc-exec-157/conftest.py:12: cannot run on platform linux =================== 1 passed, 3 skipped in 0.01 seconds ==================== @@ -487,8 +487,8 @@ $ py.test -m linux2 =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-157, inifile: collected 4 items test_plat.py s @@ -539,8 +539,8 @@ $ py.test -m interface --tb=short =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-157, inifile: collected 4 items test_module.py FF @@ -555,14 +555,14 @@ assert 0 E assert 0 ================== 2 tests deselected by "-m 'interface'" ================== - ================== 2 failed, 2 deselected in 0.01 seconds ================== + ================== 2 failed, 2 deselected in 0.02 seconds ================== or to select both "event" and "interface" tests:: $ py.test -m "interface or event" --tb=short =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-157, inifile: collected 4 items test_module.py FFF @@ -581,4 +581,4 @@ assert 0 E assert 0 ============= 1 tests deselected by "-m 'interface or event'" ============== - ================== 3 failed, 1 deselected in 0.01 seconds ================== + ================== 3 failed, 1 deselected in 0.02 seconds ================== diff -r d3b98f508034d5cc2efabac801907d75cbf197e5 -r edc1d080bab5a970da8f6c776be50768829a7b09 doc/en/example/nonpython.txt --- a/doc/en/example/nonpython.txt +++ b/doc/en/example/nonpython.txt @@ -27,18 +27,18 @@ nonpython $ py.test test_simple.yml =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /home/hpk/p/pytest/doc/en, inifile: pytest.ini + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/sandbox/pytest/doc/en, inifile: pytest.ini collected 2 items - test_simple.yml F. + test_simple.yml .F ================================= FAILURES ================================= ______________________________ usecase: hello ______________________________ usecase execution failed spec failed: 'some': 'other' no further details known at this point. - ==================== 1 failed, 1 passed in 0.03 seconds ==================== + ==================== 1 failed, 1 passed in 0.19 seconds ==================== You get one dot for the passing ``sub1: sub1`` check and one failure. Obviously in the above ``conftest.py`` you'll want to implement a more @@ -57,30 +57,30 @@ nonpython $ py.test -v =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /home/hpk/p/pytest/doc/en, inifile: pytest.ini + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/sandbox/pytest/doc/en, inifile: pytest.ini collecting ... collected 2 items + test_simple.yml::ok PASSED test_simple.yml::hello FAILED - test_simple.yml::ok PASSED ================================= FAILURES ================================= ______________________________ usecase: hello ______________________________ usecase execution failed spec failed: 'some': 'other' no further details known at this point. - ==================== 1 failed, 1 passed in 0.03 seconds ==================== + ==================== 1 failed, 1 passed in 0.05 seconds ==================== While developing your custom test collection and execution it's also interesting to just look at the collection tree:: nonpython $ py.test --collect-only =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /home/hpk/p/pytest/doc/en, inifile: pytest.ini + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/sandbox/pytest/doc/en, inifile: pytest.ini collected 2 items + - - ============================= in 0.03 seconds ============================= + ============================= in 0.04 seconds ============================= diff -r d3b98f508034d5cc2efabac801907d75cbf197e5 -r edc1d080bab5a970da8f6c776be50768829a7b09 doc/en/example/parametrize.txt --- a/doc/en/example/parametrize.txt +++ b/doc/en/example/parametrize.txt @@ -55,15 +55,15 @@ ....F ================================= FAILURES ================================= _____________________________ test_compute[4] ______________________________ - + param1 = 4 - + def test_compute(param1): > assert param1 < 4 E assert 4 < 4 - + test_compute.py:3: AssertionError - 1 failed, 4 passed in 0.01 seconds + 1 failed, 4 passed in 0.02 seconds As expected when running the full range of ``param1`` values we'll get an error on the last one. @@ -127,8 +127,8 @@ $ py.test test_time.py --collect-only =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-169, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-159, inifile: ============================= in 0.00 seconds ============================= ERROR: file not found: test_time.py @@ -171,21 +171,21 @@ $ py.test test_scenarios.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-169, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-159, inifile: collected 4 items test_scenarios.py .... - ========================= 4 passed in 0.01 seconds ========================= + ========================= 4 passed in 0.02 seconds ========================= If you just collect tests you'll also nicely see 'advanced' and 'basic' as variants for the test function:: $ py.test --collect-only test_scenarios.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-169, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-159, inifile: collected 4 items @@ -249,14 +249,14 @@ $ py.test test_backends.py --collect-only =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-169, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-159, inifile: collected 2 items - ============================= in 0.00 seconds ============================= + ============================= in 0.01 seconds ============================= And then when we run the test:: @@ -265,7 +265,7 @@ ================================= FAILURES ================================= _________________________ test_db_initialized[d2] __________________________ - db = + db = def test_db_initialized(db): # a dummy test @@ -319,16 +319,16 @@ $ py.test -q F.. ================================= FAILURES ================================= - ________________________ TestClass.test_equals[1-2] ________________________ + ________________________ TestClass.test_equals[2-1] ________________________ - self = , a = 1, b = 2 + self = , a = 1, b = 2 def test_equals(self, a, b): > assert a == b E assert 1 == 2 test_parametrize.py:18: AssertionError - 1 failed, 2 passed in 0.01 seconds + 1 failed, 2 passed in 0.02 seconds Indirect parametrization with multiple fixtures -------------------------------------------------------------- @@ -348,7 +348,7 @@ . $ py.test -rs -q multipython.py ........................... - 27 passed in 1.70 seconds + 27 passed in 4.14 seconds Indirect parametrization of optional implementations/imports -------------------------------------------------------------------- @@ -395,13 +395,13 @@ $ py.test -rs test_module.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-169, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-159, inifile: collected 2 items test_module.py .s ========================= short test summary info ========================== - SKIP [1] /tmp/doc-exec-169/conftest.py:10: could not import 'opt2' + SKIP [1] /tmp/doc-exec-159/conftest.py:10: could not import 'opt2' =================== 1 passed, 1 skipped in 0.01 seconds ==================== diff -r d3b98f508034d5cc2efabac801907d75cbf197e5 -r edc1d080bab5a970da8f6c776be50768829a7b09 doc/en/example/pythoncollection.txt --- a/doc/en/example/pythoncollection.txt +++ b/doc/en/example/pythoncollection.txt @@ -43,8 +43,8 @@ $ py.test --collect-only =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-170, inifile: setup.cfg + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-160, inifile: setup.cfg collected 2 items @@ -89,8 +89,8 @@ . $ py.test --collect-only pythoncollection.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /home/hpk/p/pytest/doc/en, inifile: pytest.ini + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/sandbox/pytest/doc/en, inifile: pytest.ini collected 3 items @@ -143,11 +143,11 @@ $ py.test --collect-only =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-170, inifile: pytest.ini + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-160, inifile: pytest.ini collected 0 items - ============================= in 0.00 seconds ============================= + ============================= in 0.01 seconds ============================= If you run with a Python3 interpreter the moduled added through the conftest.py file will not be considered for test collection. diff -r d3b98f508034d5cc2efabac801907d75cbf197e5 -r edc1d080bab5a970da8f6c776be50768829a7b09 doc/en/example/reportingdemo.txt --- a/doc/en/example/reportingdemo.txt +++ b/doc/en/example/reportingdemo.txt @@ -13,8 +13,8 @@ assertion $ py.test failure_demo.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /home/hpk/p/pytest/doc/en, inifile: pytest.ini + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/sandbox/pytest/doc/en, inifile: pytest.ini collected 42 items failure_demo.py FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF @@ -31,7 +31,7 @@ failure_demo.py:15: AssertionError _________________________ TestFailing.test_simple __________________________ - self = + self = def test_simple(self): def f(): @@ -41,13 +41,13 @@ > assert f() == g() E assert 42 == 43 - E + where 42 = .f at 0x2b186edd09d8>() - E + and 43 = .g at 0x2b186edd9950>() + E + where 42 = .f at 0x7f65f2315510>() + E + and 43 = .g at 0x7f65f2323510>() failure_demo.py:28: AssertionError ____________________ TestFailing.test_simple_multiline _____________________ - self = + self = def test_simple_multiline(self): otherfunc_multi( @@ -67,19 +67,19 @@ failure_demo.py:11: AssertionError ___________________________ TestFailing.test_not ___________________________ - self = + self = def test_not(self): def f(): return 42 > assert not f() E assert not 42 - E + where 42 = .f at 0x2b186edd99d8>() + E + where 42 = .f at 0x7f65f2323598>() failure_demo.py:38: AssertionError _________________ TestSpecialisedExplanations.test_eq_text _________________ - self = + self = def test_eq_text(self): > assert 'spam' == 'eggs' @@ -90,7 +90,7 @@ failure_demo.py:42: AssertionError _____________ TestSpecialisedExplanations.test_eq_similar_text _____________ - self = + self = def test_eq_similar_text(self): > assert 'foo 1 bar' == 'foo 2 bar' @@ -103,7 +103,7 @@ failure_demo.py:45: AssertionError ____________ TestSpecialisedExplanations.test_eq_multiline_text ____________ - self = + self = def test_eq_multiline_text(self): > assert 'foo\nspam\nbar' == 'foo\neggs\nbar' @@ -116,7 +116,7 @@ failure_demo.py:48: AssertionError ______________ TestSpecialisedExplanations.test_eq_long_text _______________ - self = + self = def test_eq_long_text(self): a = '1'*100 + 'a' + '2'*100 @@ -133,7 +133,7 @@ failure_demo.py:53: AssertionError _________ TestSpecialisedExplanations.test_eq_long_text_multiline __________ - self = + self = def test_eq_long_text_multiline(self): a = '1\n'*100 + 'a' + '2\n'*100 @@ -157,7 +157,7 @@ failure_demo.py:58: AssertionError _________________ TestSpecialisedExplanations.test_eq_list _________________ - self = + self = def test_eq_list(self): > assert [0, 1, 2] == [0, 1, 3] @@ -168,7 +168,7 @@ failure_demo.py:61: AssertionError ______________ TestSpecialisedExplanations.test_eq_list_long _______________ - self = + self = def test_eq_list_long(self): a = [0]*100 + [1] + [3]*100 @@ -181,7 +181,7 @@ failure_demo.py:66: AssertionError _________________ TestSpecialisedExplanations.test_eq_dict _________________ - self = + self = def test_eq_dict(self): > assert {'a': 0, 'b': 1, 'c': 0} == {'a': 0, 'b': 2, 'd': 0} @@ -198,7 +198,7 @@ failure_demo.py:69: AssertionError _________________ TestSpecialisedExplanations.test_eq_set __________________ - self = + self = def test_eq_set(self): > assert set([0, 10, 11, 12]) == set([0, 20, 21]) @@ -215,7 +215,7 @@ failure_demo.py:72: AssertionError _____________ TestSpecialisedExplanations.test_eq_longer_list ______________ - self = + self = def test_eq_longer_list(self): > assert [1,2] == [1,2,3] @@ -226,7 +226,7 @@ failure_demo.py:75: AssertionError _________________ TestSpecialisedExplanations.test_in_list _________________ - self = + self = def test_in_list(self): > assert 1 in [0, 2, 3, 4, 5] @@ -235,7 +235,7 @@ failure_demo.py:78: AssertionError __________ TestSpecialisedExplanations.test_not_in_text_multiline __________ - self = + self = def test_not_in_text_multiline(self): text = 'some multiline\ntext\nwhich\nincludes foo\nand a\ntail' @@ -253,7 +253,7 @@ failure_demo.py:82: AssertionError ___________ TestSpecialisedExplanations.test_not_in_text_single ____________ - self = + self = def test_not_in_text_single(self): text = 'single foo line' @@ -266,7 +266,7 @@ failure_demo.py:86: AssertionError _________ TestSpecialisedExplanations.test_not_in_text_single_long _________ - self = + self = def test_not_in_text_single_long(self): text = 'head ' * 50 + 'foo ' + 'tail ' * 20 @@ -279,7 +279,7 @@ failure_demo.py:90: AssertionError ______ TestSpecialisedExplanations.test_not_in_text_single_long_term _______ - self = + self = def test_not_in_text_single_long_term(self): text = 'head ' * 50 + 'f'*70 + 'tail ' * 20 @@ -298,7 +298,7 @@ i = Foo() > assert i.b == 2 E assert 1 == 2 - E + where 1 = .Foo object at 0x2b186ee8d4e0>.b + E + where 1 = .Foo object at 0x7f65f1c814e0>.b failure_demo.py:101: AssertionError _________________________ test_attribute_instance __________________________ @@ -308,8 +308,8 @@ b = 1 > assert Foo().b == 2 E assert 1 == 2 - E + where 1 = .Foo object at 0x2b186eea6240>.b - E + where .Foo object at 0x2b186eea6240> = .Foo'>() + E + where 1 = .Foo object at 0x7f65f1c7f7f0>.b + E + where .Foo object at 0x7f65f1c7f7f0> = .Foo'>() failure_demo.py:107: AssertionError __________________________ test_attribute_failure __________________________ @@ -325,7 +325,7 @@ failure_demo.py:116: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ - self = .Foo object at 0x2b186ed9a4e0> + self = .Foo object at 0x7f65f1c97dd8> def _get_b(self): > raise Exception('Failed to get attrib') @@ -341,15 +341,15 @@ b = 2 > assert Foo().b == Bar().b E assert 1 == 2 - E + where 1 = .Foo object at 0x2b186ee78630>.b - E + where .Foo object at 0x2b186ee78630> = .Foo'>() - E + and 2 = .Bar object at 0x2b186ee78358>.b - E + where .Bar object at 0x2b186ee78358> = .Bar'>() + E + where 1 = .Foo object at 0x7f65f1c9b630>.b + E + where .Foo object at 0x7f65f1c9b630> = .Foo'>() + E + and 2 = .Bar object at 0x7f65f1c9b2b0>.b + E + where .Bar object at 0x7f65f1c9b2b0> = .Bar'>() failure_demo.py:124: AssertionError __________________________ TestRaises.test_raises __________________________ - self = + self = def test_raises(self): s = 'qwe' @@ -361,10 +361,10 @@ > int(s) E ValueError: invalid literal for int() with base 10: 'qwe' - <0-codegen /home/hpk/p/pytest/.tox/regen/lib/python3.4/site-packages/_pytest/python.py:1075>:1: ValueError + <0-codegen /tmp/sandbox/pytest/.tox/regen/lib/python3.4/site-packages/_pytest/python.py:1075>:1: ValueError ______________________ TestRaises.test_raises_doesnt _______________________ - self = + self = def test_raises_doesnt(self): > raises(IOError, "int('3')") @@ -373,7 +373,7 @@ failure_demo.py:136: Failed __________________________ TestRaises.test_raise ___________________________ - self = + self = def test_raise(self): > raise ValueError("demo error") @@ -382,7 +382,7 @@ failure_demo.py:139: ValueError ________________________ TestRaises.test_tupleerror ________________________ - self = + self = def test_tupleerror(self): > a,b = [1] @@ -391,7 +391,7 @@ failure_demo.py:142: ValueError ______ TestRaises.test_reinterpret_fails_with_print_for_the_fun_of_it ______ - self = + self = def test_reinterpret_fails_with_print_for_the_fun_of_it(self): l = [1,2,3] @@ -404,7 +404,7 @@ l is [1, 2, 3] ________________________ TestRaises.test_some_error ________________________ - self = + self = def test_some_error(self): > if namenotexi: @@ -429,10 +429,10 @@ > assert 1 == 0 E assert 1 == 0 - <2-codegen 'abc-123' /home/hpk/p/pytest/doc/en/example/assertion/failure_demo.py:162>:2: AssertionError + <2-codegen 'abc-123' /tmp/sandbox/pytest/doc/en/example/assertion/failure_demo.py:162>:2: AssertionError ____________________ TestMoreErrors.test_complex_error _____________________ - self = + self = def test_complex_error(self): def f(): @@ -456,7 +456,7 @@ failure_demo.py:5: AssertionError ___________________ TestMoreErrors.test_z1_unpack_error ____________________ - self = + self = def test_z1_unpack_error(self): l = [] @@ -466,7 +466,7 @@ failure_demo.py:179: ValueError ____________________ TestMoreErrors.test_z2_type_error _____________________ - self = + self = def test_z2_type_error(self): l = 3 @@ -476,19 +476,19 @@ failure_demo.py:183: TypeError ______________________ TestMoreErrors.test_startswith ______________________ - self = + self = def test_startswith(self): s = "123" g = "456" > assert s.startswith(g) - E assert ('456') - E + where = '123'.startswith + E assert ('456') + E + where = '123'.startswith failure_demo.py:188: AssertionError __________________ TestMoreErrors.test_startswith_nested ___________________ - self = + self = def test_startswith_nested(self): def f(): @@ -496,15 +496,15 @@ def g(): return "456" > assert f().startswith(g()) - E assert ('456') - E + where = '123'.startswith - E + where '123' = .f at 0x2b186eea1510>() - E + and '456' = .g at 0x2b186eea1268>() + E assert ('456') + E + where = '123'.startswith + E + where '123' = .f at 0x7f65f1c32950>() + E + and '456' = .g at 0x7f65f1c32ea0>() failure_demo.py:195: AssertionError _____________________ TestMoreErrors.test_global_func ______________________ - self = + self = def test_global_func(self): > assert isinstance(globf(42), float) @@ -514,18 +514,18 @@ failure_demo.py:198: AssertionError _______________________ TestMoreErrors.test_instance _______________________ - self = + self = def test_instance(self): self.x = 6*7 > assert self.x != 42 E assert 42 != 42 - E + where 42 = .x + E + where 42 = .x failure_demo.py:202: AssertionError _______________________ TestMoreErrors.test_compare ________________________ - self = + self = def test_compare(self): > assert globf(10) < 5 @@ -535,7 +535,7 @@ failure_demo.py:205: AssertionError _____________________ TestMoreErrors.test_try_finally ______________________ - self = + self = def test_try_finally(self): x = 1 @@ -546,7 +546,7 @@ failure_demo.py:210: AssertionError ___________________ TestCustomAssertMsg.test_single_line ___________________ - self = + self = def test_single_line(self): class A: @@ -560,7 +560,7 @@ failure_demo.py:221: AssertionError ____________________ TestCustomAssertMsg.test_multiline ____________________ - self = + self = def test_multiline(self): class A: @@ -577,7 +577,7 @@ failure_demo.py:227: AssertionError ___________________ TestCustomAssertMsg.test_custom_repr ___________________ - self = + self = def test_custom_repr(self): class JSON: @@ -595,4 +595,4 @@ E + where 1 = This is JSON\n{\n 'foo': 'bar'\n}.a failure_demo.py:237: AssertionError - ======================== 42 failed in 0.22 seconds ========================= + ======================== 42 failed in 0.35 seconds ========================= diff -r d3b98f508034d5cc2efabac801907d75cbf197e5 -r edc1d080bab5a970da8f6c776be50768829a7b09 doc/en/example/simple.txt --- a/doc/en/example/simple.txt +++ b/doc/en/example/simple.txt @@ -108,8 +108,8 @@ $ py.test =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-172, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-162, inifile: collected 0 items ============================= in 0.00 seconds ============================= @@ -153,13 +153,13 @@ $ py.test -rs # "-rs" means report details on the little 's' =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-172, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-162, inifile: collected 2 items test_module.py .s ========================= short test summary info ========================== - SKIP [1] /tmp/doc-exec-172/conftest.py:9: need --runslow option to run + SKIP [1] /tmp/doc-exec-162/conftest.py:9: need --runslow option to run =================== 1 passed, 1 skipped in 0.01 seconds ==================== @@ -167,8 +167,8 @@ $ py.test --runslow =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-172, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-162, inifile: collected 2 items test_module.py .. @@ -205,13 +205,13 @@ F ================================= FAILURES ================================= ______________________________ test_something ______________________________ - + def test_something(): > checkconfig(42) E Failed: not configured: 42 - + test_checkconfig.py:8: Failed - 1 failed in 0.01 seconds + 1 failed in 0.02 seconds Detect if running from within a pytest run -------------------------------------------------------------- @@ -259,8 +259,8 @@ $ py.test =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-172, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-162, inifile: project deps: mylib-1.1 collected 0 items @@ -283,8 +283,8 @@ $ py.test -v =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /tmp/doc-exec-172, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/doc-exec-162, inifile: info1: did you know that ... did you? collecting ... collected 0 items @@ -295,8 +295,8 @@ $ py.test =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-172, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-162, inifile: collected 0 items ============================= in 0.00 seconds ============================= @@ -328,8 +328,8 @@ $ py.test --durations=3 =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-172, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-162, inifile: collected 3 items test_some_are_slow.py ... @@ -390,8 +390,8 @@ $ py.test -rx =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-172, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-162, inifile: collected 4 items test_step.py .Fx. @@ -399,7 +399,7 @@ ================================= FAILURES ================================= ____________________ TestUserHandling.test_modification ____________________ - self = + self = def test_modification(self): > assert 0 @@ -409,7 +409,7 @@ ========================= short test summary info ========================== XFAIL test_step.py::TestUserHandling::()::test_deletion reason: previous test failed (test_modification) - ============== 1 failed, 2 passed, 1 xfailed in 0.01 seconds =============== + ============== 1 failed, 2 passed, 1 xfailed in 0.02 seconds =============== We'll see that ``test_deletion`` was not executed because ``test_modification`` failed. It is reported as an "expected failure". @@ -461,8 +461,8 @@ $ py.test =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-172, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-162, inifile: collected 7 items test_step.py .Fx. @@ -472,17 +472,17 @@ ================================== ERRORS ================================== _______________________ ERROR at setup of test_root ________________________ - file /tmp/doc-exec-172/b/test_error.py, line 1 + file /tmp/doc-exec-162/b/test_error.py, line 1 def test_root(db): # no db here, will error out fixture 'db' not found - available fixtures: pytestconfig, tmpdir, monkeypatch, capfd, recwarn, capsys + available fixtures: pytestconfig, capsys, recwarn, monkeypatch, tmpdir, capfd use 'py.test --fixtures [testpath]' for help on them. - /tmp/doc-exec-172/b/test_error.py:1 + /tmp/doc-exec-162/b/test_error.py:1 ================================= FAILURES ================================= ____________________ TestUserHandling.test_modification ____________________ - self = + self = def test_modification(self): > assert 0 @@ -491,25 +491,25 @@ test_step.py:9: AssertionError _________________________________ test_a1 __________________________________ - db = + db = def test_a1(db): > assert 0, db # to show value - E AssertionError: + E AssertionError: E assert 0 a/test_db.py:2: AssertionError _________________________________ test_a2 __________________________________ - db = + db = def test_a2(db): > assert 0, db # to show value - E AssertionError: + E AssertionError: E assert 0 a/test_db2.py:2: AssertionError - ========== 3 failed, 2 passed, 1 xfailed, 1 error in 0.03 seconds ========== + ========== 3 failed, 2 passed, 1 xfailed, 1 error in 0.05 seconds ========== The two test modules in the ``a`` directory see the same ``db`` fixture instance while the one test in the sister-directory ``b`` doesn't see it. We could of course @@ -564,8 +564,8 @@ $ py.test test_module.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-172, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-162, inifile: collected 2 items test_module.py FF @@ -573,7 +573,7 @@ ================================= FAILURES ================================= ________________________________ test_fail1 ________________________________ - tmpdir = local('/tmp/pytest-219/test_fail10') + tmpdir = local('/tmp/pytest-22/test_fail10') def test_fail1(tmpdir): > assert 0 @@ -587,12 +587,12 @@ E assert 0 test_module.py:4: AssertionError - ========================= 2 failed in 0.01 seconds ========================= + ========================= 2 failed in 0.02 seconds ========================= you will have a "failures" file which contains the failing test ids:: $ cat failures - test_module.py::test_fail1 (/tmp/pytest-219/test_fail10) + test_module.py::test_fail1 (/tmp/pytest-22/test_fail10) test_module.py::test_fail2 Making test result information available in fixtures @@ -655,8 +655,8 @@ $ py.test -s test_module.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-172, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-162, inifile: collected 3 items test_module.py Esetting up a test failed! test_module.py::test_setup_fails @@ -689,7 +689,7 @@ E assert 0 test_module.py:15: AssertionError - ==================== 2 failed, 1 error in 0.01 seconds ===================== + ==================== 2 failed, 1 error in 0.02 seconds ===================== You'll see that the fixture finalizers could use the precise reporting information. diff -r d3b98f508034d5cc2efabac801907d75cbf197e5 -r edc1d080bab5a970da8f6c776be50768829a7b09 doc/en/example/special.txt --- a/doc/en/example/special.txt +++ b/doc/en/example/special.txt @@ -69,4 +69,4 @@ .test other .test_unit1 method called . - 4 passed in 0.04 seconds + 4 passed in 0.03 seconds diff -r d3b98f508034d5cc2efabac801907d75cbf197e5 -r edc1d080bab5a970da8f6c776be50768829a7b09 doc/en/fixture.txt --- a/doc/en/fixture.txt +++ b/doc/en/fixture.txt @@ -75,8 +75,8 @@ $ py.test test_smtpsimple.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-109, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-98, inifile: collected 1 items test_smtpsimple.py F @@ -84,7 +84,7 @@ ================================= FAILURES ================================= ________________________________ test_ehlo _________________________________ - smtp = + smtp = def test_ehlo(smtp): response, msg = smtp.ehlo() @@ -93,7 +93,7 @@ E assert 0 test_smtpsimple.py:11: AssertionError - ========================= 1 failed in 0.17 seconds ========================= + ========================= 1 failed in 1.07 seconds ========================= In the failure traceback we see that the test function was called with a ``smtp`` argument, the ``smtplib.SMTP()`` instance created by the fixture @@ -193,8 +193,8 @@ $ py.test test_module.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-109, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-98, inifile: collected 2 items test_module.py FF @@ -202,7 +202,7 @@ ================================= FAILURES ================================= ________________________________ test_ehlo _________________________________ - smtp = + smtp = def test_ehlo(smtp): response = smtp.ehlo() @@ -213,7 +213,7 @@ test_module.py:5: TypeError ________________________________ test_noop _________________________________ - smtp = + smtp = def test_noop(smtp): response = smtp.noop() @@ -222,7 +222,7 @@ E assert 0 test_module.py:11: AssertionError - ========================= 2 failed in 0.20 seconds ========================= + ========================= 2 failed in 0.82 seconds ========================= You see the two ``assert 0`` failing and more importantly you can also see that the same (module-scoped) ``smtp`` object was passed into the two @@ -270,7 +270,7 @@ $ py.test -s -q --tb=no FFteardown smtp - 2 failed in 0.25 seconds + 2 failed in 1.44 seconds We see that the ``smtp`` instance is finalized after the two tests finished execution. Note that if we decorated our fixture @@ -311,7 +311,7 @@ $ py.test -s -q --tb=no FF - 2 failed in 0.17 seconds + 2 failed in 0.62 seconds Let's quickly create another test module that actually sets the server URL in its module namespace:: @@ -379,7 +379,7 @@ ================================= FAILURES ================================= __________________________ test_ehlo[merlinux.eu] __________________________ - smtp = + smtp = def test_ehlo(smtp): response = smtp.ehlo() @@ -390,7 +390,7 @@ test_module.py:5: TypeError __________________________ test_noop[merlinux.eu] __________________________ - smtp = + smtp = def test_noop(smtp): response = smtp.noop() @@ -401,7 +401,7 @@ test_module.py:11: AssertionError ________________________ test_ehlo[mail.python.org] ________________________ - smtp = + smtp = def test_ehlo(smtp): response = smtp.ehlo() @@ -411,10 +411,10 @@ test_module.py:5: TypeError -------------------------- Captured stdout setup --------------------------- - finalizing + finalizing ________________________ test_noop[mail.python.org] ________________________ - smtp = + smtp = def test_noop(smtp): response = smtp.noop() @@ -423,7 +423,7 @@ E assert 0 test_module.py:11: AssertionError - 4 failed in 6.70 seconds + 4 failed in 1.75 seconds We see that our two test functions each ran twice, against the different ``smtp`` instances. Note also, that with the ``mail.python.org`` @@ -474,8 +474,8 @@ $ py.test --collect-only =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-109, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-98, inifile: collected 6 items @@ -486,7 +486,7 @@ - ============================= in 0.01 seconds ============================= + ============================= in 0.02 seconds ============================= .. _`interdependent fixtures`: @@ -520,14 +520,14 @@ $ py.test -v test_appsetup.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /tmp/doc-exec-109, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/doc-exec-98, inifile: collecting ... collected 2 items test_appsetup.py::test_smtp_exists[merlinux.eu] PASSED test_appsetup.py::test_smtp_exists[mail.python.org] PASSED - ========================= 2 passed in 6.53 seconds ========================= + ========================= 2 passed in 1.09 seconds ========================= Due to the parametrization of ``smtp`` the test will run twice with two different ``App`` instances and respective smtp servers. There is no @@ -585,8 +585,8 @@ $ py.test -v -s test_module.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /tmp/doc-exec-109, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/doc-exec-98, inifile: collecting ... collected 8 items test_module.py::test_0[1] test0 1 @@ -608,7 +608,7 @@ test_module.py::test_2[2-mod2] test2 2 mod2 PASSED - ========================= 8 passed in 0.01 seconds ========================= + ========================= 8 passed in 0.02 seconds ========================= You can see that the parametrized module-scoped ``modarg`` resource caused an ordering of test execution that lead to the fewest possible "active" resources. The finalizer for the ``mod1`` parametrized resource was executed diff -r d3b98f508034d5cc2efabac801907d75cbf197e5 -r edc1d080bab5a970da8f6c776be50768829a7b09 doc/en/getting-started.txt --- a/doc/en/getting-started.txt +++ b/doc/en/getting-started.txt @@ -27,7 +27,7 @@ To check your installation has installed the correct version:: $ py.test --version - This is pytest version 2.7.0, imported from /home/hpk/p/pytest/.tox/regen/lib/python3.4/site-packages/pytest.py + This is pytest version 2.7.1, imported from /tmp/sandbox/pytest/.tox/regen/lib/python3.4/site-packages/pytest.py If you get an error checkout :ref:`installation issues`. @@ -49,8 +49,8 @@ $ py.test =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-112, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-101, inifile: collected 1 items test_sample.py F @@ -98,7 +98,7 @@ $ py.test -q test_sysexit.py . - 1 passed in 0.00 seconds + 1 passed in 0.01 seconds .. todo:: For further ways to assert exceptions see the `raises` @@ -128,7 +128,7 @@ ================================= FAILURES ================================= ____________________________ TestClass.test_two ____________________________ - self = + self = def test_two(self): x = "hello" @@ -164,7 +164,7 @@ ================================= FAILURES ================================= _____________________________ test_needsfiles ______________________________ - tmpdir = local('/tmp/pytest-215/test_needsfiles0') + tmpdir = local('/tmp/pytest-18/test_needsfiles0') def test_needsfiles(tmpdir): print (tmpdir) @@ -173,8 +173,8 @@ test_tmpdir.py:3: AssertionError --------------------------- Captured stdout call --------------------------- - /tmp/pytest-215/test_needsfiles0 - 1 failed in 0.01 seconds + /tmp/pytest-18/test_needsfiles0 + 1 failed in 0.05 seconds Before the test runs, a unique-per-test-invocation temporary directory was created. More info at :ref:`tmpdir handling`. diff -r d3b98f508034d5cc2efabac801907d75cbf197e5 -r edc1d080bab5a970da8f6c776be50768829a7b09 doc/en/parametrize.txt --- a/doc/en/parametrize.txt +++ b/doc/en/parametrize.txt @@ -53,8 +53,8 @@ $ py.test =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-120, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-109, inifile: collected 3 items test_expectation.py ..F @@ -75,7 +75,7 @@ E + where 54 = eval('6*9') test_expectation.py:8: AssertionError - ==================== 1 failed, 2 passed in 0.01 seconds ==================== + ==================== 1 failed, 2 passed in 0.02 seconds ==================== As designed in this example, only one pair of input/output values fails the simple test function. And as usual with test function arguments, @@ -101,13 +101,13 @@ $ py.test =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-120, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-109, inifile: collected 3 items test_expectation.py ..x - =================== 2 passed, 1 xfailed in 0.01 seconds ==================== + =================== 2 passed, 1 xfailed in 0.02 seconds ==================== The one parameter set which caused a failure previously now shows up as an "xfailed (expected to fail)" test. @@ -172,8 +172,8 @@ def test_valid_string(stringinput): > assert stringinput.isalpha() - E assert () - E + where = '!'.isalpha + E assert () + E + where = '!'.isalpha test_strings.py:3: AssertionError 1 failed in 0.01 seconds @@ -187,8 +187,8 @@ $ py.test -q -rs test_strings.py s ========================= short test summary info ========================== - SKIP [1] /home/hpk/p/pytest/.tox/regen/lib/python3.4/site-packages/_pytest/python.py:1185: got empty parameter set, function test_valid_string at /tmp/doc-exec-120/test_strings.py:1 - 1 skipped in 0.00 seconds + SKIP [1] /tmp/sandbox/pytest/.tox/regen/lib/python3.4/site-packages/_pytest/python.py:1185: got empty parameter set, function test_valid_string at /tmp/doc-exec-109/test_strings.py:1 + 1 skipped in 0.01 seconds For further examples, you might want to look at :ref:`more parametrization examples `. diff -r d3b98f508034d5cc2efabac801907d75cbf197e5 -r edc1d080bab5a970da8f6c776be50768829a7b09 doc/en/skipping.txt --- a/doc/en/skipping.txt +++ b/doc/en/skipping.txt @@ -164,8 +164,8 @@ example $ py.test -rx xfail_demo.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /home/hpk/p/pytest/doc/en, inifile: pytest.ini + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/sandbox/pytest/doc/en, inifile: pytest.ini collected 7 items xfail_demo.py xxxxxxx @@ -183,7 +183,7 @@ reason: reason XFAIL xfail_demo.py::test_hello7 - ======================== 7 xfailed in 0.05 seconds ========================= + ======================== 7 xfailed in 0.06 seconds ========================= .. _`skip/xfail with parametrize`: diff -r d3b98f508034d5cc2efabac801907d75cbf197e5 -r edc1d080bab5a970da8f6c776be50768829a7b09 doc/en/tmpdir.txt --- a/doc/en/tmpdir.txt +++ b/doc/en/tmpdir.txt @@ -29,8 +29,8 @@ $ py.test test_tmpdir.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-129, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-118, inifile: collected 1 items test_tmpdir.py F @@ -38,7 +38,7 @@ ================================= FAILURES ================================= _____________________________ test_create_file _____________________________ - tmpdir = local('/tmp/pytest-216/test_create_file0') + tmpdir = local('/tmp/pytest-19/test_create_file0') def test_create_file(tmpdir): p = tmpdir.mkdir("sub").join("hello.txt") @@ -49,7 +49,7 @@ E assert 0 test_tmpdir.py:7: AssertionError - ========================= 1 failed in 0.01 seconds ========================= + ========================= 1 failed in 0.04 seconds ========================= .. _`base temporary directory`: diff -r d3b98f508034d5cc2efabac801907d75cbf197e5 -r edc1d080bab5a970da8f6c776be50768829a7b09 doc/en/unittest.txt --- a/doc/en/unittest.txt +++ b/doc/en/unittest.txt @@ -88,8 +88,8 @@ $ py.test test_unittest_db.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-130, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-119, inifile: collected 2 items test_unittest_db.py FF @@ -102,7 +102,7 @@ def test_method1(self): assert hasattr(self, "db") > assert 0, self.db # fail for demo purposes - E AssertionError: .DummyDB object at 0x2ab102a4bac8> + E AssertionError: .DummyDB object at 0x7f97382031d0> E assert 0 test_unittest_db.py:9: AssertionError @@ -112,7 +112,7 @@ def test_method2(self): > assert 0, self.db # fail for demo purposes - E AssertionError: .DummyDB object at 0x2ab102a4bac8> + E AssertionError: .DummyDB object at 0x7f97382031d0> E assert 0 test_unittest_db.py:12: AssertionError @@ -163,7 +163,7 @@ $ py.test -q test_unittest_cleandir.py . - 1 passed in 0.04 seconds + 1 passed in 0.25 seconds ... gives us one passed test because the ``initdir`` fixture function was executed ahead of the ``test_method``. diff -r d3b98f508034d5cc2efabac801907d75cbf197e5 -r edc1d080bab5a970da8f6c776be50768829a7b09 doc/en/yieldfixture.txt --- a/doc/en/yieldfixture.txt +++ b/doc/en/yieldfixture.txt @@ -51,7 +51,7 @@ test called .teardown after yield - 1 passed in 0.00 seconds + 1 passed in 0.01 seconds We can also seamlessly use the new syntax with ``with`` statements. Let's simplify the above ``passwd`` fixture:: https://bitbucket.org/pytest-dev/pytest/commits/a62cf0013b35/ Changeset: a62cf0013b35 Branch: pytest-2.7 User: flub Date: 2015-05-19 08:26:59+00:00 Summary: Added tag 2.7.1 for changeset edc1d080bab5 Affected #: 1 file diff -r edc1d080bab5a970da8f6c776be50768829a7b09 -r a62cf0013b3546d38f1a6295d04413d9fb37f5f3 .hgtags --- a/.hgtags +++ b/.hgtags @@ -74,3 +74,4 @@ 2967aa416a4f3cdb65fc75073a2a148e1f372742 2.6.3 f03b6de8325f5b6c35cea7c3de092f134ea8ef07 2.6.4 7ed701fa2fb554bfc0618d447dfec700cc697407 2.7.0 +edc1d080bab5a970da8f6c776be50768829a7b09 2.7.1 Repository URL: https://bitbucket.org/pytest-dev/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Tue May 19 10:32:32 2015 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Tue, 19 May 2015 08:32:32 -0000 Subject: [Pytest-commit] commit/pytest: flub: Add how to upload a wheel and tag the release Message-ID: <20150519083232.31763.52361@app11.ash-private.bitbucket.org> 1 new commit in pytest: https://bitbucket.org/pytest-dev/pytest/commits/b3273466be77/ Changeset: b3273466be77 Branch: pytest-2.7 User: flub Date: 2015-05-19 08:32:21+00:00 Summary: Add how to upload a wheel and tag the release Affected #: 1 file diff -r a62cf0013b3546d38f1a6295d04413d9fb37f5f3 -r b3273466be77f4827e46d37b24bd4c974a0b8edf HOWTORELEASE.rst --- a/HOWTORELEASE.rst +++ b/HOWTORELEASE.rst @@ -11,7 +11,7 @@ 4. use devpi for uploading a release tarball to a staging area: - ``devpi use https://devpi.net/USER/dev`` - - ``devpi upload`` + - ``devpi upload --formats sdist,bdist_wheel`` 5. run from multiple machines: - ``devpi use https://devpi.net/USER/dev`` @@ -35,7 +35,10 @@ cd docs/en make html -9. Upload the docs using docs/en/Makefile:: +9. Tag the release:: + hg tag VERSION + +10. Upload the docs using docs/en/Makefile:: cd docs/en make install # or "installall" if you have LaTeX installed This requires ssh-login permission on pytest.org because it uses @@ -43,12 +46,12 @@ Note that the "install" target of doc/en/Makefile defines where the rsync goes to, typically to the "latest" section of pytest.org. -10. publish to pypi "devpi push pytest-2.6.2 pypi:NAME" where NAME +11. publish to pypi "devpi push pytest-VERSION pypi:NAME" where NAME is the name of pypi.python.org as configured in your ~/.pypirc file -- it's the same you would use with "setup.py upload -r NAME" -11. send release announcement to mailing lists: +12. send release announcement to mailing lists: pytest-dev testing-in-python Repository URL: https://bitbucket.org/pytest-dev/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From builds at drone.io Tue May 19 10:51:35 2015 From: builds at drone.io (Drone.io Build) Date: Tue, 19 May 2015 08:51:35 +0000 Subject: [Pytest-commit] [FAIL] pytest - # 120 Message-ID: <20150519084119.125536.81613@drone.io> Build Failed Build : https://drone.io/bitbucket.org/pytest-dev/pytest/120 Project : https://drone.io/bitbucket.org/pytest-dev/pytest Repository : https://bitbucket.org/pytest-dev/pytest Version : 3984:a62cf0013b35 Author : Floris Bruynooghe Branch : pytest-2.7 Message: Added tag 2.7.1 for changeset edc1d080bab5 -------------- next part -------------- An HTML attachment was scrubbed... URL: From builds at drone.io Tue May 19 10:52:04 2015 From: builds at drone.io (Drone.io Build) Date: Tue, 19 May 2015 08:52:04 +0000 Subject: [Pytest-commit] [FAIL] pytest - # 121 Message-ID: <20150519085202.125522.9726@drone.io> Build Failed Build : https://drone.io/bitbucket.org/pytest-dev/pytest/121 Project : https://drone.io/bitbucket.org/pytest-dev/pytest Repository : https://bitbucket.org/pytest-dev/pytest Version : 3985:b3273466be77 Author : Floris Bruynooghe Branch : pytest-2.7 Message: Add how to upload a wheel and tag the release -------------- next part -------------- An HTML attachment was scrubbed... URL: From issues-reply at bitbucket.org Tue May 19 13:26:17 2015 From: issues-reply at bitbucket.org (Markus Unterwaditzer) Date: Tue, 19 May 2015 11:26:17 -0000 Subject: [Pytest-commit] Issue #745: Missing `param` attribute on request if one param name is substring of other (pytest-dev/pytest) Message-ID: <20150519112617.21739.3102@app03.ash-private.bitbucket.org> New issue 745: Missing `param` attribute on request if one param name is substring of other https://bitbucket.org/pytest-dev/pytest/issue/745/missing-param-attribute-on-request-if-one Markus Unterwaditzer: This is kind-of related to #460, except that it doesn't seem to have anything to do with chained/nested fixtures. Instead, the following testsuite fails because the `item_types` parameter contains `item_type`, which is the name of another fixture! Renaming `item_types` to something else which doesn't contain `item_type` as substring fixes the issue, as does modifying `item_types` to become a fixture. ``` #!python import pytest @pytest.fixture(params=list(range(3))) def item_type(request): return request.param @pytest.mark.parametrize('item_types', list(range(3))) def test_simple(item_type, item_types): pass ``` From issues-reply at bitbucket.org Tue May 19 13:43:26 2015 From: issues-reply at bitbucket.org (Florian Bruhin) Date: Tue, 19 May 2015 11:43:26 -0000 Subject: [Pytest-commit] Issue #746: monkeypatch.setattr('foo.bar', raising=False) raises when foo.bar doesn't exist (pytest-dev/pytest) Message-ID: <20150519114326.7453.22133@app11.ash-private.bitbucket.org> New issue 746: monkeypatch.setattr('foo.bar', raising=False) raises when foo.bar doesn't exist https://bitbucket.org/pytest-dev/pytest/issue/746/monkeypatchsetattr-foobar-raising-false Florian Bruhin: I'm trying to patch `sys.frozen` to set it to `True`, but `sys.frozen` doesn't exist yet. This works: ```python monkeypatch.setattr(sys, 'frozen', True, raising=False) ``` This however raises: ```python monkeypatch.setattr('sys.frozen', True, raising=False) ``` ``` > monkeypatch.setattr('sys.frozen', True, raising=False) E Failed: object has no attribute 'frozen' ``` From issues-reply at bitbucket.org Tue May 19 21:29:47 2015 From: issues-reply at bitbucket.org (Florian Bruhin) Date: Tue, 19 May 2015 19:29:47 -0000 Subject: [Pytest-commit] Issue #747: flakes test fail (pytest-dev/pytest) Message-ID: <20150519192947.15068.92973@app01.ash-private.bitbucket.org> New issue 747: flakes test fail https://bitbucket.org/pytest-dev/pytest/issue/747/flakes-test-fail Florian Bruhin: I just tried running all tests via tox, and the flakes test fail for me: ``` =================================================== FAILURES =================================================== ________________________________________________ pyflakes-check ________________________________________________ .tox/flakes/lib/python3.4/site-packages/pluggy.py:724: in __call__ return self._hookexec(self, self._nonwrappers + self._wrappers, kwargs) .tox/flakes/lib/python3.4/site-packages/pluggy.py:338: in _hookexec return self._inner_hookexec(hook, methods, kwargs) .tox/flakes/lib/python3.4/site-packages/pluggy.py:333: in _MultiCall(methods, kwargs, hook.spec_opts).execute() .tox/flakes/lib/python3.4/site-packages/pluggy.py:595: in execute return _wrapped_call(hook_impl.function(*args), self.execute) .tox/flakes/lib/python3.4/site-packages/pluggy.py:253: in _wrapped_call return call_outcome.get_result() .tox/flakes/lib/python3.4/site-packages/pluggy.py:278: in get_result raise ex[1].with_traceback(ex[2]) .tox/flakes/lib/python3.4/site-packages/pluggy.py:264: in __init__ self.result = func() .tox/flakes/lib/python3.4/site-packages/pluggy.py:596: in execute res = hook_impl.function(*args) .tox/flakes/lib/python3.4/site-packages/_pytest/runner.py:90: in pytest_runtest_call item.runtest() .tox/flakes/lib/python3.4/site-packages/pytest_flakes.py:75: in runtest found_errors, out = check_file(self.fspath, self.flakesignore) .tox/flakes/lib/python3.4/site-packages/pytest_flakes.py:124: in check_file codeString = path.read() .tox/flakes/lib/python3.4/site-packages/py/_path/common.py:133: in read return f.read() .tox/flakes/lib/python3.4/encodings/ascii.py:26: in decode return codecs.ascii_decode(input, self.errors)[0] E UnicodeDecodeError: 'ascii' codec can't decode byte 0xc2 in position 6798: ordinal not in range(128) ________________________________________________ pyflakes-check ________________________________________________ /home/florian/proj/pytest/testing/test_capture.py:31: UndefinedName undefined name 'unicode' /home/florian/proj/pytest/testing/test_capture.py:38: UndefinedName undefined name 'unicode' /home/florian/proj/pytest/testing/test_capture.py:39: UndefinedName undefined name 'unicode' /home/florian/proj/pytest/testing/test_capture.py:583: UndefinedName undefined name 'unicode' /home/florian/proj/pytest/testing/test_capture.py:587: UndefinedName undefined name 'unicode' ________________________________________________ pyflakes-check ________________________________________________ /home/florian/proj/pytest/testing/test_collection.py:36: UndefinedName undefined name 'cmp' ________________________________________________ pyflakes-check ________________________________________________ .tox/flakes/lib/python3.4/site-packages/pluggy.py:724: in __call__ return self._hookexec(self, self._nonwrappers + self._wrappers, kwargs) .tox/flakes/lib/python3.4/site-packages/pluggy.py:338: in _hookexec return self._inner_hookexec(hook, methods, kwargs) .tox/flakes/lib/python3.4/site-packages/pluggy.py:333: in _MultiCall(methods, kwargs, hook.spec_opts).execute() .tox/flakes/lib/python3.4/site-packages/pluggy.py:595: in execute return _wrapped_call(hook_impl.function(*args), self.execute) .tox/flakes/lib/python3.4/site-packages/pluggy.py:253: in _wrapped_call return call_outcome.get_result() .tox/flakes/lib/python3.4/site-packages/pluggy.py:278: in get_result raise ex[1].with_traceback(ex[2]) .tox/flakes/lib/python3.4/site-packages/pluggy.py:264: in __init__ self.result = func() .tox/flakes/lib/python3.4/site-packages/pluggy.py:596: in execute res = hook_impl.function(*args) .tox/flakes/lib/python3.4/site-packages/_pytest/runner.py:90: in pytest_runtest_call item.runtest() .tox/flakes/lib/python3.4/site-packages/pytest_flakes.py:75: in runtest found_errors, out = check_file(self.fspath, self.flakesignore) .tox/flakes/lib/python3.4/site-packages/pytest_flakes.py:124: in check_file codeString = path.read() .tox/flakes/lib/python3.4/site-packages/py/_path/common.py:133: in read return f.read() .tox/flakes/lib/python3.4/encodings/ascii.py:26: in decode return codecs.ascii_decode(input, self.errors)[0] E UnicodeDecodeError: 'ascii' codec can't decode byte 0xd0 in position 17709: ordinal not in range(128) ________________________________________________ pyflakes-check ________________________________________________ /home/florian/proj/pytest/testing/python/collect.py:489: UndefinedName undefined name 'cmp' ==================================== 1144 tests deselected by "-m 'flakes'" ==================================== ============================= 5 failed, 61 passed, 1144 deselected in 2.81 seconds ============================= ``` Random guesses without investigating more: - Maybe `passenv` for `LC_ALL` or so should be set in `tox.ini` since the new tox version doesn't pass environment variables? - I'm on Archlinux which uses python3 for `/usr/bin/python` - it smells like the `cmp`/`unicode` failures are because it's run with python3 instead of 2? From issues-reply at bitbucket.org Wed May 20 08:52:44 2015 From: issues-reply at bitbucket.org (Thomas De Schampheleire) Date: Wed, 20 May 2015 06:52:44 -0000 Subject: [Pytest-commit] Issue #748: pytest skip: short test summary info does not display actual test file (pytest-dev/pytest) Message-ID: <20150520065244.1164.55249@app01.ash-private.bitbucket.org> New issue 748: pytest skip: short test summary info does not display actual test file https://bitbucket.org/pytest-dev/pytest/issue/748/pytest-skip-short-test-summary-info-does Thomas De Schampheleire: In Kallithea, we have test code such as:: if not ldap_lib_installed: raise SkipTest('skipping due to missing ldap lib') where the test is derived from unittest.TestCase, and SkipTest is imported from nose.plugins.skip. The 'short test summary info' provided by pytest displays:: SKIP [3] /..../local/lib/python2.7/site-packages/_pytest/unittest.py:114: skipping due to missing ldap lib This line refers to unittest.py (handling the skip), rather than the actual test file that _causes_ the skip. What I expect is output like:: SKIP [3] /..../kallithea/kallithea/tests/functional/test_admin_auth_settings.py:27: skipping due to missing ldap lib Related issues were reported a long time ago (they should probably be fixed/closed together with this one): - issue #157 - issue #114 From issues-reply at bitbucket.org Wed May 20 09:39:03 2015 From: issues-reply at bitbucket.org (Marc Schlaich) Date: Wed, 20 May 2015 07:39:03 -0000 Subject: [Pytest-commit] Issue #749: multiprocessing broken since 2.7.1 on Windows (pytest-dev/pytest) Message-ID: <20150520073903.5542.88455@app08.ash-private.bitbucket.org> New issue 749: multiprocessing broken since 2.7.1 on Windows https://bitbucket.org/pytest-dev/pytest/issue/749/multiprocessing-broken-since-271-on Marc Schlaich: With pytest 2.7.1, Processes with multiprocessing don't start anymore, I'm seeing this in stdout: Traceback (most recent call last): File "", line 1, in File "c:\python27\Lib\multiprocessing\forking.py", line 380, in main prepare(preparation_data) File "c:\python27\Lib\multiprocessing\forking.py", line 488, in prepare assert main_name not in sys.modules, main_name AssertionError: __main__ This is probably due to this upstream issue with wheels: https://github.com/pypa/pip/issues/1891 Installing pytest not as wheel with `pip install --no-use-wheel` resolves this issue. From issues-reply at bitbucket.org Thu May 21 22:34:30 2015 From: issues-reply at bitbucket.org (jbeluch) Date: Thu, 21 May 2015 20:34:30 -0000 Subject: [Pytest-commit] Issue #253: Newer tox prevents usage of ssh agent (2.0.1) (hpk42/tox) Message-ID: <20150521203430.8668.70386@app03.ash-private.bitbucket.org> New issue 253: Newer tox prevents usage of ssh agent (2.0.1) https://bitbucket.org/hpk42/tox/issue/253/newer-tox-prevents-usage-of-ssh-agent-201 jbeluch: Solution is to add `passenv = SSH_AUTH_SOCK` to your tox.ini. Perhaps make this work by default? From builds at drone.io Thu May 21 22:39:21 2015 From: builds at drone.io (Drone.io Build) Date: Thu, 21 May 2015 20:39:21 +0000 Subject: [Pytest-commit] [FAIL] py - # 13 Message-ID: <20150521203920.10806.45574@drone.io> Build Failed Build : https://drone.io/bitbucket.org/pytest-dev/py/13 Project : https://drone.io/bitbucket.org/pytest-dev/py Repository : https://bitbucket.org/pytest-dev/py Version : 2170:ea8072c85fc8 Author : holger krekel Branch : alex_gaynor/attempt-to-optimize-tracebackentryishidd-1386348507632 Message: close branch -------------- next part -------------- An HTML attachment was scrubbed... URL: From builds at drone.io Fri May 22 08:25:14 2015 From: builds at drone.io (Drone.io Build) Date: Fri, 22 May 2015 06:25:14 +0000 Subject: [Pytest-commit] [FAIL] py - # 14 Message-ID: <20150522062514.10816.15129@drone.io> Build Failed Build : https://drone.io/bitbucket.org/pytest-dev/py/14 Project : https://drone.io/bitbucket.org/pytest-dev/py Repository : https://bitbucket.org/pytest-dev/py Version : 2292:00ee89438a45 Author : holger krekel Branch : default Message: Merged in aldanor/py/dirpath-fix (pull request #29) -------------- next part -------------- An HTML attachment was scrubbed... URL: From commits-noreply at bitbucket.org Fri May 22 11:45:12 2015 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Fri, 22 May 2015 09:45:12 -0000 Subject: [Pytest-commit] commit/pytest: dareonion: add in missing finalizer to doc example Message-ID: <20150522094512.22790.2795@app07.ash-private.bitbucket.org> 1 new commit in pytest: https://bitbucket.org/pytest-dev/pytest/commits/00993fa6da31/ Changeset: 00993fa6da31 User: dareonion Date: 2015-05-22 02:15:52+00:00 Summary: add in missing finalizer to doc example Affected #: 1 file diff -r 6ee4b4d70a9b81f525c6fe46eab1b2018de6f4b0 -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac doc/en/fixture.txt --- a/doc/en/fixture.txt +++ b/doc/en/fixture.txt @@ -302,7 +302,7 @@ def fin(): print ("finalizing %s (%s)" % (smtp, server)) smtp.close() - + request.addfinalizer(fin) return smtp We use the ``request.module`` attribute to optionally obtain an Repository URL: https://bitbucket.org/pytest-dev/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From issues-reply at bitbucket.org Sat May 23 08:41:04 2015 From: issues-reply at bitbucket.org (sergey chipiga) Date: Sat, 23 May 2015 06:41:04 -0000 Subject: [Pytest-commit] Issue #750: py.test can't xfail tests parametrized with classes (pytest-dev/pytest) Message-ID: <20150523064104.8843.7929@app10.ash-private.bitbucket.org> New issue 750: py.test can't xfail tests parametrized with classes https://bitbucket.org/pytest-dev/pytest/issue/750/pytest-cant-xfail-tests-parametrized-with sergey chipiga: Code example: ``` import pytest class A(object): pass @pytest.mark.parametrize('x', [pytest.mark.xfail(A)]) def test(x): assert False ``` The result: ```proba.py::test[x0] FAILED``` Responsible: hpk42 From issues-reply at bitbucket.org Sat May 23 16:50:13 2015 From: issues-reply at bitbucket.org (sergey chipiga) Date: Sat, 23 May 2015 14:50:13 -0000 Subject: [Pytest-commit] Issue #751: py.test can't multiple parametrize with ids if it parametrize class with two or more test methods (pytest-dev/pytest) Message-ID: <20150523145013.12239.35987@app13.ash-private.bitbucket.org> New issue 751: py.test can't multiple parametrize with ids if it parametrize class with two or more test methods https://bitbucket.org/pytest-dev/pytest/issue/751/pytest-cant-multiple-parametrize-with-ids sergey chipiga: Code example %%python import pytest @pytest.mark.parametrize('x', [0], ids=['c']) @pytest.mark.parametrize('y', [0, 1], ids=['a', 'b']) class Test(object): def test1(self, x, y): pass def test2(self, x, y): pass %% stack trace %% py.test proba.py --collect-only ============================================================================== test session starts =============================================================================== platform darwin -- Python 2.7.9 -- py-1.4.26 -- pytest-2.7.0 rootdir: /Users/svchipiga/lira/tmp, inifile: collected 0 items / 1 errors ===================================================================================== ERRORS ===================================================================================== ___________________________________________________________________________ ERROR collecting proba.py ____________________________________________________________________________ /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/_pytest/runner.py:149: in __init__ self.result = func() /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/_pytest/main.py:437: in _memocollect return self._memoizedcall('_collected', lambda: list(self.collect())) /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/_pytest/main.py:314: in _memoizedcall res = function() /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/_pytest/main.py:437: in return self._memoizedcall('_collected', lambda: list(self.collect())) /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/_pytest/python.py:563: in collect return super(Instance, self).collect() /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/_pytest/python.py:355: in collect res = self.makeitem(name, obj) /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/_pytest/python.py:367: in makeitem collector=self, name=name, obj=obj) /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/_pytest/core.py:521: in __call__ return self._docall(self.methods, kwargs) /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/_pytest/core.py:528: in _docall firstresult=self.firstresult).execute() /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/_pytest/core.py:393: in execute return wrapped_call(method(*args), self.execute) /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/_pytest/core.py:109: in wrapped_call wrap_controller.send(call_outcome) /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/_pytest/python.py:245: in pytest_pycollect_makeitem res = list(collector._genfunctions(name, obj)) /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/_pytest/python.py:384: in _genfunctions self.ihook.pytest_generate_tests.callextra(methods, metafunc=metafunc) /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/_pytest/core.py:524: in callextra return self._docall(self.methods + methods, kwargs) /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/_pytest/core.py:528: in _docall firstresult=self.firstresult).execute() /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/_pytest/core.py:394: in execute res = method(*args) /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/_pytest/python.py:154: in pytest_generate_tests metafunc.parametrize(*marker.args, **marker.kwargs) /Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/_pytest/python.py:859: in parametrize len(argvalues), len(ids))) E ValueError: 2 tests specified with 1 ids %% Responsible: hpk42 From issues-reply at bitbucket.org Sat May 23 23:52:49 2015 From: issues-reply at bitbucket.org (Andrew Gorcester) Date: Sat, 23 May 2015 21:52:49 -0000 Subject: [Pytest-commit] Issue #752: INTERNALERROR> IndexError: list index out of range (pytest-dev/pytest) Message-ID: <20150523215249.5570.67476@app08.ash-private.bitbucket.org> New issue 752: INTERNALERROR> IndexError: list index out of range https://bitbucket.org/pytest-dev/pytest/issue/752/internalerror-indexerror-list-index-out-of Andrew Gorcester: After changing a line of source inside a dict literal in my project from this: 0x0b: (self.ANC, "IMM"), to this: 0x0b: None, #(self.ANC, "IMM"), py.test started throwing the following error: platform linux -- Python 3.4.0 -- py-1.4.27 -- pytest-2.7.1 rootdir: /home/andrewsg/src/python-c64, inifile: collected 108 items test_c64.py ....... INTERNALERROR> Traceback (most recent call last): INTERNALERROR> File "/home/andrewsg/src/py34env/lib/python3.4/site-packages/_pytest/main.py", line 84, in wrap_session INTERNALERROR> doit(config, session) INTERNALERROR> File "/home/andrewsg/src/py34env/lib/python3.4/site-packages/_pytest/main.py", line 122, in _main INTERNALERROR> config.hook.pytest_runtestloop(session=session) INTERNALERROR> File "/home/andrewsg/src/py34env/lib/python3.4/site-packages/_pytest/core.py", line 521, in __call__ INTERNALERROR> return self._docall(self.methods, kwargs) INTERNALERROR> File "/home/andrewsg/src/py34env/lib/python3.4/site-packages/_pytest/core.py", line 528, in _docall INTERNALERROR> firstresult=self.firstresult).execute() INTERNALERROR> File "/home/andrewsg/src/py34env/lib/python3.4/site-packages/_pytest/core.py", line 394, in execute INTERNALERROR> res = method(*args) INTERNALERROR> File "/home/andrewsg/src/py34env/lib/python3.4/site-packages/_pytest/main.py", line 142, in pytest_runtestloop INTERNALERROR> item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem) INTERNALERROR> File "/home/andrewsg/src/py34env/lib/python3.4/site-packages/_pytest/core.py", line 521, in __call__ INTERNALERROR> return self._docall(self.methods, kwargs) INTERNALERROR> File "/home/andrewsg/src/py34env/lib/python3.4/site-packages/_pytest/core.py", line 528, in _docall INTERNALERROR> firstresult=self.firstresult).execute() INTERNALERROR> File "/home/andrewsg/src/py34env/lib/python3.4/site-packages/_pytest/core.py", line 393, in execute INTERNALERROR> return wrapped_call(method(*args), self.execute) INTERNALERROR> File "/home/andrewsg/src/py34env/lib/python3.4/site-packages/_pytest/core.py", line 113, in wrapped_call INTERNALERROR> return call_outcome.get_result() INTERNALERROR> File "/home/andrewsg/src/py34env/lib/python3.4/site-packages/_pytest/core.py", line 137, in get_result INTERNALERROR> raise ex[1].with_traceback(ex[2]) INTERNALERROR> File "/home/andrewsg/src/py34env/lib/python3.4/site-packages/_pytest/core.py", line 123, in __init__ INTERNALERROR> self.result = func() INTERNALERROR> File "/home/andrewsg/src/py34env/lib/python3.4/site-packages/_pytest/core.py", line 394, in execute INTERNALERROR> res = method(*args) INTERNALERROR> File "/home/andrewsg/src/py34env/lib/python3.4/site-packages/_pytest/runner.py", line 65, in pytest_runtest_protocol INTERNALERROR> runtestprotocol(item, nextitem=nextitem) INTERNALERROR> File "/home/andrewsg/src/py34env/lib/python3.4/site-packages/_pytest/runner.py", line 75, in runtestprotocol INTERNALERROR> reports.append(call_and_report(item, "call", log)) INTERNALERROR> File "/home/andrewsg/src/py34env/lib/python3.4/site-packages/_pytest/runner.py", line 121, in call_and_report INTERNALERROR> report = hook.pytest_runtest_makereport(item=item, call=call) INTERNALERROR> File "/home/andrewsg/src/py34env/lib/python3.4/site-packages/_pytest/core.py", line 521, in __call__ INTERNALERROR> return self._docall(self.methods, kwargs) INTERNALERROR> File "/home/andrewsg/src/py34env/lib/python3.4/site-packages/_pytest/core.py", line 528, in _docall INTERNALERROR> firstresult=self.firstresult).execute() INTERNALERROR> File "/home/andrewsg/src/py34env/lib/python3.4/site-packages/_pytest/core.py", line 393, in execute INTERNALERROR> return wrapped_call(method(*args), self.execute) INTERNALERROR> File "/home/andrewsg/src/py34env/lib/python3.4/site-packages/_pytest/core.py", line 109, in wrapped_call INTERNALERROR> wrap_controller.send(call_outcome) INTERNALERROR> File "/home/andrewsg/src/py34env/lib/python3.4/site-packages/_pytest/skipping.py", line 157, in pytest_runtest_makereport INTERNALERROR> rep = outcome.get_result() INTERNALERROR> File "/home/andrewsg/src/py34env/lib/python3.4/site-packages/_pytest/core.py", line 137, in get_result INTERNALERROR> raise ex[1].with_traceback(ex[2]) INTERNALERROR> File "/home/andrewsg/src/py34env/lib/python3.4/site-packages/_pytest/core.py", line 123, in __init__ INTERNALERROR> self.result = func() INTERNALERROR> File "/home/andrewsg/src/py34env/lib/python3.4/site-packages/_pytest/core.py", line 394, in execute INTERNALERROR> res = method(*args) INTERNALERROR> File "/home/andrewsg/src/py34env/lib/python3.4/site-packages/_pytest/runner.py", line 224, in pytest_runtest_makereport INTERNALERROR> longrepr = item.repr_failure(excinfo) INTERNALERROR> File "/home/andrewsg/src/py34env/lib/python3.4/site-packages/_pytest/python.py", line 625, in repr_failure INTERNALERROR> return self._repr_failure_py(excinfo, style=style) INTERNALERROR> File "/home/andrewsg/src/py34env/lib/python3.4/site-packages/_pytest/python.py", line 618, in _repr_failure_py INTERNALERROR> style=style) INTERNALERROR> File "/home/andrewsg/src/py34env/lib/python3.4/site-packages/_pytest/main.py", line 410, in _repr_failure_py INTERNALERROR> style=style, tbfilter=tbfilter) INTERNALERROR> File "/home/andrewsg/src/py34env/lib/python3.4/site-packages/py/_code/code.py", line 412, in getrepr INTERNALERROR> return fmt.repr_excinfo(self) INTERNALERROR> File "/home/andrewsg/src/py34env/lib/python3.4/site-packages/py/_code/code.py", line 590, in repr_excinfo INTERNALERROR> reprtraceback = self.repr_traceback(excinfo) INTERNALERROR> File "/home/andrewsg/src/py34env/lib/python3.4/site-packages/py/_code/code.py", line 582, in repr_traceback INTERNALERROR> reprentry = self.repr_traceback_entry(entry, einfo) INTERNALERROR> File "/home/andrewsg/src/py34env/lib/python3.4/site-packages/py/_code/code.py", line 543, in repr_traceback_entry INTERNALERROR> s = self.get_source(source, line_index, excinfo, short=short) INTERNALERROR> File "/home/andrewsg/src/py34env/lib/python3.4/site-packages/py/_code/code.py", line 484, in get_source INTERNALERROR> lines.append(self.flow_marker + " " + source.lines[line_index]) INTERNALERROR> IndexError: list index out of range =========================== 7 passed in 0.06 seconds =========================== I saw #560 with the same error message, but it looks like this is a different issue; at the least, my version is much newer than the patch date for that issue's resolution. From issues-reply at bitbucket.org Sun May 24 08:37:45 2015 From: issues-reply at bitbucket.org (tobixx) Date: Sun, 24 May 2015 06:37:45 -0000 Subject: [Pytest-commit] Issue #753: Missing example test (pytest-dev/pytest) Message-ID: <20150524063745.5532.38256@app11.ash-private.bitbucket.org> New issue 753: Missing example test https://bitbucket.org/pytest-dev/pytest/issue/753/missing-example-test tobixx: http://pytest.org/latest/example/parametrize.html ``` #!bash $ py.test test_time.py --collect-only =========================== test session starts ============================ platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 rootdir: /tmp/doc-exec-159, inifile: ============================= in 0.00 seconds ============================= ERROR: file not found: test_time.py ``` From issues-reply at bitbucket.org Sun May 24 22:36:14 2015 From: issues-reply at bitbucket.org (=?utf-8?q?Ionel_Cristian_M=C4=83rie=C8=99?=) Date: Sun, 24 May 2015 20:36:14 -0000 Subject: [Pytest-commit] Issue #754: Can't render tracebacks that have missing sources: IndexError: list index out of range (pytest-dev/pytest) Message-ID: <20150524203614.15716.95719@app05.ash-private.bitbucket.org> New issue 754: Can't render tracebacks that have missing sources: IndexError: list index out of range https://bitbucket.org/pytest-dev/pytest/issue/754/cant-render-tracebacks-that-have-missing Ionel Cristian M?rie?: ``` INTERNALERROR> Traceback (most recent call last): INTERNALERROR> File "/home/ionel/.ve/lib/python2.7/site-packages/_pytest/main.py", line 84, in wrap_session INTERNALERROR> doit(config, session) INTERNALERROR> File "/home/ionel/.ve/lib/python2.7/site-packages/_pytest/main.py", line 122, in _main INTERNALERROR> config.hook.pytest_runtestloop(session=session) INTERNALERROR> File "/home/ionel/.ve/lib/python2.7/site-packages/_pytest/core.py", line 521, in __call__ INTERNALERROR> return self._docall(self.methods, kwargs) INTERNALERROR> File "/home/ionel/.ve/lib/python2.7/site-packages/_pytest/core.py", line 528, in _docall INTERNALERROR> firstresult=self.firstresult).execute() INTERNALERROR> File "/home/ionel/.ve/lib/python2.7/site-packages/_pytest/core.py", line 394, in execute INTERNALERROR> res = method(*args) INTERNALERROR> File "/home/ionel/.ve/lib/python2.7/site-packages/_pytest/main.py", line 142, in pytest_runtestloop INTERNALERROR> item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem) INTERNALERROR> File "/home/ionel/.ve/lib/python2.7/site-packages/_pytest/core.py", line 521, in __call__ INTERNALERROR> return self._docall(self.methods, kwargs) INTERNALERROR> File "/home/ionel/.ve/lib/python2.7/site-packages/_pytest/core.py", line 528, in _docall INTERNALERROR> firstresult=self.firstresult).execute() INTERNALERROR> File "/home/ionel/.ve/lib/python2.7/site-packages/_pytest/core.py", line 393, in execute INTERNALERROR> return wrapped_call(method(*args), self.execute) INTERNALERROR> File "/home/ionel/.ve/lib/python2.7/site-packages/_pytest/core.py", line 113, in wrapped_call INTERNALERROR> return call_outcome.get_result() INTERNALERROR> File "/home/ionel/.ve/lib/python2.7/site-packages/_pytest/core.py", line 138, in get_result INTERNALERROR> py.builtin._reraise(*ex) INTERNALERROR> File "/home/ionel/.ve/lib/python2.7/site-packages/_pytest/core.py", line 123, in __init__ INTERNALERROR> self.result = func() INTERNALERROR> File "/home/ionel/.ve/lib/python2.7/site-packages/_pytest/core.py", line 394, in execute INTERNALERROR> res = method(*args) INTERNALERROR> File "/home/ionel/.ve/lib/python2.7/site-packages/_pytest/runner.py", line 65, in pytest_runtest_protocol INTERNALERROR> runtestprotocol(item, nextitem=nextitem) INTERNALERROR> File "/home/ionel/.ve/lib/python2.7/site-packages/_pytest/runner.py", line 75, in runtestprotocol INTERNALERROR> reports.append(call_and_report(item, "call", log)) INTERNALERROR> File "/home/ionel/.ve/lib/python2.7/site-packages/_pytest/runner.py", line 121, in call_and_report INTERNALERROR> report = hook.pytest_runtest_makereport(item=item, call=call) INTERNALERROR> File "/home/ionel/.ve/lib/python2.7/site-packages/_pytest/core.py", line 521, in __call__ INTERNALERROR> return self._docall(self.methods, kwargs) INTERNALERROR> File "/home/ionel/.ve/lib/python2.7/site-packages/_pytest/core.py", line 528, in _docall INTERNALERROR> firstresult=self.firstresult).execute() INTERNALERROR> File "/home/ionel/.ve/lib/python2.7/site-packages/_pytest/core.py", line 393, in execute INTERNALERROR> return wrapped_call(method(*args), self.execute) INTERNALERROR> File "/home/ionel/.ve/lib/python2.7/site-packages/_pytest/core.py", line 109, in wrapped_call INTERNALERROR> wrap_controller.send(call_outcome) INTERNALERROR> File "/home/ionel/.ve/lib/python2.7/site-packages/_pytest/skipping.py", line 157, in pytest_runtest_makereport INTERNALERROR> rep = outcome.get_result() INTERNALERROR> File "/home/ionel/.ve/lib/python2.7/site-packages/_pytest/core.py", line 138, in get_result INTERNALERROR> py.builtin._reraise(*ex) INTERNALERROR> File "/home/ionel/.ve/lib/python2.7/site-packages/_pytest/core.py", line 123, in __init__ INTERNALERROR> self.result = func() INTERNALERROR> File "/home/ionel/.ve/lib/python2.7/site-packages/_pytest/core.py", line 394, in execute INTERNALERROR> res = method(*args) INTERNALERROR> File "/home/ionel/.ve/lib/python2.7/site-packages/pytest_capturelog.py", line 175, in pytest_runtest_makereport INTERNALERROR> report = __multicall__.execute() INTERNALERROR> File "/home/ionel/.ve/lib/python2.7/site-packages/_pytest/core.py", line 394, in execute INTERNALERROR> res = method(*args) INTERNALERROR> File "/home/ionel/.ve/lib/python2.7/site-packages/_pytest/runner.py", line 224, in pytest_runtest_makereport INTERNALERROR> longrepr = item.repr_failure(excinfo) INTERNALERROR> File "/home/ionel/.ve/lib/python2.7/site-packages/_pytest/python.py", line 625, in repr_failure INTERNALERROR> return self._repr_failure_py(excinfo, style=style) INTERNALERROR> File "/home/ionel/.ve/lib/python2.7/site-packages/_pytest/python.py", line 618, in _repr_failure_py INTERNALERROR> style=style) INTERNALERROR> File "/home/ionel/.ve/lib/python2.7/site-packages/_pytest/main.py", line 410, in _repr_failure_py INTERNALERROR> style=style, tbfilter=tbfilter) INTERNALERROR> File "/home/ionel/.ve/lib/python2.7/site-packages/py/_code/code.py", line 412, in getrepr INTERNALERROR> return fmt.repr_excinfo(self) INTERNALERROR> File "/home/ionel/.ve/lib/python2.7/site-packages/py/_code/code.py", line 590, in repr_excinfo INTERNALERROR> reprtraceback = self.repr_traceback(excinfo) INTERNALERROR> File "/home/ionel/.ve/lib/python2.7/site-packages/py/_code/code.py", line 582, in repr_traceback INTERNALERROR> reprentry = self.repr_traceback_entry(entry, einfo) INTERNALERROR> File "/home/ionel/.ve/lib/python2.7/site-packages/py/_code/code.py", line 543, in repr_traceback_entry INTERNALERROR> s = self.get_source(source, line_index, excinfo, short=short) INTERNALERROR> File "/home/ionel/.ve/lib/python2.7/site-packages/py/_code/code.py", line 480, in get_source INTERNALERROR> lines.append(space_prefix + source.lines[line_index].strip()) INTERNALERROR> IndexError: list index out of range ``` From issues-reply at bitbucket.org Tue May 26 11:09:34 2015 From: issues-reply at bitbucket.org (Ronny Pfannschmidt) Date: Tue, 26 May 2015 09:09:34 -0000 Subject: [Pytest-commit] Issue #755: addini type argument is not sufficiently documented (pytest-dev/pytest) Message-ID: <20150526090934.20968.42257@app12.ash-private.bitbucket.org> New issue 755: addini type argument is not sufficiently documented https://bitbucket.org/pytest-dev/pytest/issue/755/addini-type-argument-is-not-sufficiently Ronny Pfannschmidt: From issues-reply at bitbucket.org Wed May 27 02:12:44 2015 From: issues-reply at bitbucket.org (Alex Gaynor) Date: Wed, 27 May 2015 00:12:44 -0000 Subject: [Pytest-commit] Issue #756: internal error building traceback (pytest-dev/pytest) Message-ID: <20150527001244.4926.83759@app08.ash-private.bitbucket.org> New issue 756: internal error building traceback https://bitbucket.org/pytest-dev/pytest/issue/756/internal-error-building-traceback Alex Gaynor: The full traceback: ``` INTERNALERROR> Traceback (most recent call last): INTERNALERROR> File "/home/jenk/workspace/cryptography-pull-request-builder/TOXENV/py27/label/centos64/cryptography/.tox/py27/lib/python2.7/site-packages/_pytest/main.py", line 84, in wrap_session INTERNALERROR> doit(config, session) INTERNALERROR> File "/home/jenk/workspace/cryptography-pull-request-builder/TOXENV/py27/label/centos64/cryptography/.tox/py27/lib/python2.7/site-packages/_pytest/main.py", line 121, in _main INTERNALERROR> config.hook.pytest_collection(session=session) INTERNALERROR> File "/home/jenk/workspace/cryptography-pull-request-builder/TOXENV/py27/label/centos64/cryptography/.tox/py27/lib/python2.7/site-packages/_pytest/core.py", line 521, in __call__ INTERNALERROR> return self._docall(self.methods, kwargs) INTERNALERROR> File "/home/jenk/workspace/cryptography-pull-request-builder/TOXENV/py27/label/centos64/cryptography/.tox/py27/lib/python2.7/site-packages/_pytest/core.py", line 528, in _docall INTERNALERROR> firstresult=self.firstresult).execute() INTERNALERROR> File "/home/jenk/workspace/cryptography-pull-request-builder/TOXENV/py27/label/centos64/cryptography/.tox/py27/lib/python2.7/site-packages/_pytest/core.py", line 394, in execute INTERNALERROR> res = method(*args) INTERNALERROR> File "/home/jenk/workspace/cryptography-pull-request-builder/TOXENV/py27/label/centos64/cryptography/.tox/py27/lib/python2.7/site-packages/_pytest/main.py", line 125, in pytest_collection INTERNALERROR> return session.perform_collect() INTERNALERROR> File "/home/jenk/workspace/cryptography-pull-request-builder/TOXENV/py27/label/centos64/cryptography/.tox/py27/lib/python2.7/site-packages/_pytest/main.py", line 550, in perform_collect INTERNALERROR> items = self._perform_collect(args, genitems) INTERNALERROR> File "/home/jenk/workspace/cryptography-pull-request-builder/TOXENV/py27/label/centos64/cryptography/.tox/py27/lib/python2.7/site-packages/_pytest/main.py", line 585, in _perform_collect INTERNALERROR> self.items.extend(self.genitems(node)) INTERNALERROR> File "/home/jenk/workspace/cryptography-pull-request-builder/TOXENV/py27/label/centos64/cryptography/.tox/py27/lib/python2.7/site-packages/_pytest/main.py", line 722, in genitems INTERNALERROR> rep = collect_one_node(node) INTERNALERROR> File "/home/jenk/workspace/cryptography-pull-request-builder/TOXENV/py27/label/centos64/cryptography/.tox/py27/lib/python2.7/site-packages/_pytest/runner.py", line 411, in collect_one_node INTERNALERROR> rep = ihook.pytest_make_collect_report(collector=collector) INTERNALERROR> File "/home/jenk/workspace/cryptography-pull-request-builder/TOXENV/py27/label/centos64/cryptography/.tox/py27/lib/python2.7/site-packages/_pytest/core.py", line 521, in __call__ INTERNALERROR> return self._docall(self.methods, kwargs) INTERNALERROR> File "/home/jenk/workspace/cryptography-pull-request-builder/TOXENV/py27/label/centos64/cryptography/.tox/py27/lib/python2.7/site-packages/_pytest/core.py", line 528, in _docall INTERNALERROR> firstresult=self.firstresult).execute() INTERNALERROR> File "/home/jenk/workspace/cryptography-pull-request-builder/TOXENV/py27/label/centos64/cryptography/.tox/py27/lib/python2.7/site-packages/_pytest/core.py", line 393, in execute INTERNALERROR> return wrapped_call(method(*args), self.execute) INTERNALERROR> File "/home/jenk/workspace/cryptography-pull-request-builder/TOXENV/py27/label/centos64/cryptography/.tox/py27/lib/python2.7/site-packages/_pytest/core.py", line 109, in wrapped_call INTERNALERROR> wrap_controller.send(call_outcome) INTERNALERROR> File "/home/jenk/workspace/cryptography-pull-request-builder/TOXENV/py27/label/centos64/cryptography/.tox/py27/lib/python2.7/site-packages/_pytest/capture.py", line 110, in pytest_make_collect_report INTERNALERROR> rep = outcome.get_result() INTERNALERROR> File "/home/jenk/workspace/cryptography-pull-request-builder/TOXENV/py27/label/centos64/cryptography/.tox/py27/lib/python2.7/site-packages/_pytest/core.py", line 138, in get_result INTERNALERROR> py.builtin._reraise(*ex) INTERNALERROR> File "/home/jenk/workspace/cryptography-pull-request-builder/TOXENV/py27/label/centos64/cryptography/.tox/py27/lib/python2.7/site-packages/_pytest/core.py", line 123, in __init__ INTERNALERROR> self.result = func() INTERNALERROR> File "/home/jenk/workspace/cryptography-pull-request-builder/TOXENV/py27/label/centos64/cryptography/.tox/py27/lib/python2.7/site-packages/_pytest/core.py", line 394, in execute INTERNALERROR> res = method(*args) INTERNALERROR> File "/home/jenk/workspace/cryptography-pull-request-builder/TOXENV/py27/label/centos64/cryptography/.tox/py27/lib/python2.7/site-packages/_pytest/runner.py", line 296, in pytest_make_collect_report INTERNALERROR> errorinfo = collector.repr_failure(call.excinfo) INTERNALERROR> File "/home/jenk/workspace/cryptography-pull-request-builder/TOXENV/py27/label/centos64/cryptography/.tox/py27/lib/python2.7/site-packages/_pytest/main.py", line 433, in repr_failure INTERNALERROR> return self._repr_failure_py(excinfo, style="short") INTERNALERROR> File "/home/jenk/workspace/cryptography-pull-request-builder/TOXENV/py27/label/centos64/cryptography/.tox/py27/lib/python2.7/site-packages/_pytest/main.py", line 410, in _repr_failure_py INTERNALERROR> style=style, tbfilter=tbfilter) INTERNALERROR> File "/home/jenk/workspace/cryptography-pull-request-builder/TOXENV/py27/label/centos64/cryptography/.tox/py27/lib/python2.7/site-packages/py/_code/code.py", line 412, in getrepr INTERNALERROR> return fmt.repr_excinfo(self) INTERNALERROR> File "/home/jenk/workspace/cryptography-pull-request-builder/TOXENV/py27/label/centos64/cryptography/.tox/py27/lib/python2.7/site-packages/py/_code/code.py", line 590, in repr_excinfo INTERNALERROR> reprtraceback = self.repr_traceback(excinfo) INTERNALERROR> File "/home/jenk/workspace/cryptography-pull-request-builder/TOXENV/py27/label/centos64/cryptography/.tox/py27/lib/python2.7/site-packages/py/_code/code.py", line 582, in repr_traceback INTERNALERROR> reprentry = self.repr_traceback_entry(entry, einfo) INTERNALERROR> File "/home/jenk/workspace/cryptography-pull-request-builder/TOXENV/py27/label/centos64/cryptography/.tox/py27/lib/python2.7/site-packages/py/_code/code.py", line 543, in repr_traceback_entry INTERNALERROR> s = self.get_source(source, line_index, excinfo, short=short) INTERNALERROR> File "/home/jenk/workspace/cryptography-pull-request-builder/TOXENV/py27/label/centos64/cryptography/.tox/py27/lib/python2.7/site-packages/py/_code/code.py", line 480, in get_source INTERNALERROR> lines.append(space_prefix + source.lines[line_index].strip()) INTERNALERROR> IndexError: list index out of range ``` https://jenkins.cryptography.io/job/cryptography-pull-request-builder/3664/TOXENV=py27,label=centos64/console is the actual log. I believe that contains all the details needed to reproduce, if there's any questions, let me know. From issues-reply at bitbucket.org Wed May 27 17:13:49 2015 From: issues-reply at bitbucket.org (Maxim Yaskevich) Date: Wed, 27 May 2015 15:13:49 -0000 Subject: [Pytest-commit] Issue #757: Heavily using decorators in the System under test suppresses exceptions (pytest-dev/pytest) Message-ID: <20150527151349.4914.48047@app10.ash-private.bitbucket.org> New issue 757: Heavily using decorators in the System under test suppresses exceptions https://bitbucket.org/pytest-dev/pytest/issue/757/heavily-using-decorators-in-the-system Maxim Yaskevich: py.test version - ``platform win32 -- Python 2.7.9 -- py-1.4.27 -- pytest-2.7.1`` When a class I write my tests for heavily uses decorators, for instance for implementing a "builder" fluent method interface like below: ``` #!python from functools import wraps def fluent(method): @wraps(method) def inner(self, *args, **kwargs): try: method(self, *args, **kwargs) finally: return self return inner class Server(object): def __init__(self, host, port=9090): self.host = host self.port = port self.isrunning = False self.status = None self.response = None @fluent def start(self): self.isrunning = True print 'started' @fluent def request(self, url, context=None): assert self.isrunning print 'requesting', url, '...' self.status, self.response = 404, {'error': 'Page not found'} @fluent def await_response(self, status): print 'assert expected', status, 'got', self.status assert status == self.status # Generates AssertionError def test_simple(): Server('localhost', port=9000) \ .start() \ .request('/is_prime', {'number': 13}) \ .await_response(200) ``` the test passes. I think this is absolutely not fair and dangerous, as it can hide real problems from the people. The thing that surprised me even more is that ``nose`` didn't get to catch that error either. Am I missing something? Please run a test sample in the attachment, and feel free to ask for more details if needed. From issues-reply at bitbucket.org Thu May 28 12:11:00 2015 From: issues-reply at bitbucket.org (Holger Peters) Date: Thu, 28 May 2015 10:11:00 -0000 Subject: [Pytest-commit] Issue #758: Doctests (pytest-dev/pytest) Message-ID: <20150528101100.24615.28992@app09.ash-private.bitbucket.org> New issue 758: Doctests https://bitbucket.org/pytest-dev/pytest/issue/758/doctests Holger Peters: Pytest has support for doctests using ``--doctest-module`` and ``--doctest-glob`` flags. For a sphinx documentation, there are environments to write more complex doctests, including setup and teardown functionality, as sphinx has documented here http://sphinx-doc.org/ext/doctest.html It seems that pytest does not handle these, unfortunately for projects that use these contexts and would like to move all their testing to pytest. From issues-reply at bitbucket.org Fri May 29 11:24:05 2015 From: issues-reply at bitbucket.org (Ivan Smirnov) Date: Fri, 29 May 2015 09:24:05 -0000 Subject: [Pytest-commit] Issue #759: Allow parametrize ids to accept generators (pytest-dev/pytest) Message-ID: <20150529092405.9652.65190@app02.ash-private.bitbucket.org> New issue 759: Allow parametrize ids to accept generators https://bitbucket.org/pytest-dev/pytest/issue/759/allow-parametrize-ids-to-accept-generators Ivan Smirnov: Is it too restrictive to assert that `ids` in `Metafunc.parametrize` must always have length? It might be more Pythonic to try and create a list of `len(argvalues)` elements out of `ids` first: ```python ... idfn = None if callable(ids): idfn = ids ids = None if ids and len(ids) != len(argvalues): raise ValueError('%d tests specified with %d ids' %( len(argvalues), len(ids))) ... ``` An example use case could be something like this (enumerate the parametrized tests): ```python @pytest.mark.parametrize('param', ['foo', 'bar', 'baz'], ids=itertools.count()) def some_test(x): # ... ``` From issues-reply at bitbucket.org Fri May 29 14:48:05 2015 From: issues-reply at bitbucket.org (=?utf-8?q?Ionel_Cristian_M=C4=83rie=C8=99?=) Date: Fri, 29 May 2015 12:48:05 -0000 Subject: [Pytest-commit] Issue #760: Improve skip message summary to contain exact source location (pytest-dev/pytest) Message-ID: <20150529124805.9678.49671@app10.ash-private.bitbucket.org> New issue 760: Improve skip message summary to contain exact source location https://bitbucket.org/pytest-dev/pytest/issue/760/improve-skip-message-summary-to-contain Ionel Cristian M?rie?: Currently I get stuff like this: `SKIP [1] /foo/bar/lib/python2.7/site-packages/_pytest/skipping.py:140: some description` This doesn't really help me figuring out the exact source location beyond forcing me to grep all my code for some string. From commits-noreply at bitbucket.org Sun May 31 21:27:19 2015 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sun, 31 May 2015 19:27:19 -0000 Subject: [Pytest-commit] commit/pytest: flub: Merged in flub/pytest (pull request #297) Message-ID: <20150531192719.30114.88944@app04.ash-private.bitbucket.org> 1 new commit in pytest: https://bitbucket.org/pytest-dev/pytest/commits/1a913c55c66d/ Changeset: 1a913c55c66d User: flub Date: 2015-05-31 19:27:13+00:00 Summary: Merged in flub/pytest (pull request #297) Merge 2.7.1 release changes into default Affected #: 24 files diff -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac -r 1a913c55c66d6841c113df2247942433e693ce0f .hgtags --- a/.hgtags +++ b/.hgtags @@ -74,3 +74,4 @@ 2967aa416a4f3cdb65fc75073a2a148e1f372742 2.6.3 f03b6de8325f5b6c35cea7c3de092f134ea8ef07 2.6.4 7ed701fa2fb554bfc0618d447dfec700cc697407 2.7.0 +edc1d080bab5a970da8f6c776be50768829a7b09 2.7.1 diff -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac -r 1a913c55c66d6841c113df2247942433e693ce0f CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -52,7 +52,7 @@ one will also have a "reprec" attribute with the recorded events/reports. -2.7.1.dev (compared to 2.7.0) +2.7.1 (compared to 2.7.0) ----------------------------- - fix issue731: do not get confused by the braces which may be present diff -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac -r 1a913c55c66d6841c113df2247942433e693ce0f HOWTORELEASE.rst --- a/HOWTORELEASE.rst +++ b/HOWTORELEASE.rst @@ -2,7 +2,7 @@ How to release pytest (draft) -------------------------------------------- -1. bump version numbers in pytest/__init__.py (setup.py reads it) +1. bump version numbers in _pytest/__init__.py (setup.py reads it) 2. check and finalize CHANGELOG @@ -11,7 +11,7 @@ 4. use devpi for uploading a release tarball to a staging area: - ``devpi use https://devpi.net/USER/dev`` - - ``devpi upload`` + - ``devpi upload --formats sdist,bdist_wheel`` 5. run from multiple machines: - ``devpi use https://devpi.net/USER/dev`` @@ -25,22 +25,33 @@ which is ok (tox does not support skipping on per-platform basis yet). -7. Regenerate the docs using the toplevel makefile:: - make docs +7. Regenerate the docs examples using tox:: + # Create and activate a virtualenv with regendoc installed + # (currently needs revision 4a9ec1035734) + tox -e regen -8. Upload the docs using the toplevel makefile:: - make upload-docs +8. Build the docs, you need a virtualenv with, py and sphinx + installed:: + cd docs/en + make html + +9. Tag the release:: + hg tag VERSION + +10. Upload the docs using docs/en/Makefile:: + cd docs/en + make install # or "installall" if you have LaTeX installed This requires ssh-login permission on pytest.org because it uses rsync. Note that the "install" target of doc/en/Makefile defines where the rsync goes to, typically to the "latest" section of pytest.org. -9. publish to pypi "devpi push pytest-2.6.2 pypi:NAME" where NAME +11. publish to pypi "devpi push pytest-VERSION pypi:NAME" where NAME is the name of pypi.python.org as configured in your ~/.pypirc file -- it's the same you would use with "setup.py upload -r NAME" -10. send release announcement to mailing lists: +12. send release announcement to mailing lists: pytest-dev testing-in-python diff -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac -r 1a913c55c66d6841c113df2247942433e693ce0f doc/en/adopt.txt --- a/doc/en/adopt.txt +++ b/doc/en/adopt.txt @@ -11,7 +11,6 @@ .. _`issue tracker`: https://bitbucket.org/pytest-dev/pytest/issue/676/adopt-pytest-month-2015 .. _`pytest-dev mailing list`: https://mail.python.org/mailman/listinfo/pytest-dev -.. _``: The ideal pytest helper diff -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac -r 1a913c55c66d6841c113df2247942433e693ce0f doc/en/announce/release-2.7.0.txt --- a/doc/en/announce/release-2.7.0.txt +++ b/doc/en/announce/release-2.7.0.txt @@ -89,7 +89,7 @@ it from the "decorator" case. Thanks Tom Viner. - "python_classes" and "python_functions" options now support glob-patterns - for test discovery, as discussed in issue600. Thanks Ldiary Translations. + for test discovery, as discussed in issue600. Thanks Ldiary Translations. - allow to override parametrized fixtures with non-parametrized ones and vice versa (bubenkoff). diff -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac -r 1a913c55c66d6841c113df2247942433e693ce0f doc/en/assert.txt --- a/doc/en/assert.txt +++ b/doc/en/assert.txt @@ -26,8 +26,8 @@ $ py.test test_assert1.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-98, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-87, inifile: collected 1 items test_assert1.py F @@ -136,8 +136,8 @@ $ py.test test_assert2.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-98, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-87, inifile: collected 1 items test_assert2.py F @@ -213,7 +213,7 @@ E vals: 1 != 2 test_foocompare.py:8: AssertionError - 1 failed in 0.00 seconds + 1 failed in 0.01 seconds .. _assert-details: .. _`assert introspection`: diff -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac -r 1a913c55c66d6841c113df2247942433e693ce0f doc/en/capture.txt --- a/doc/en/capture.txt +++ b/doc/en/capture.txt @@ -64,8 +64,8 @@ $ py.test =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-101, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-90, inifile: collected 2 items test_module.py .F @@ -79,7 +79,7 @@ test_module.py:9: AssertionError -------------------------- Captured stdout setup --------------------------- - setting up + setting up ==================== 1 failed, 1 passed in 0.01 seconds ==================== Accessing captured output from a test function diff -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac -r 1a913c55c66d6841c113df2247942433e693ce0f doc/en/doctest.txt --- a/doc/en/doctest.txt +++ b/doc/en/doctest.txt @@ -44,13 +44,13 @@ $ py.test =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-107, inifile: pytest.ini + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-96, inifile: pytest.ini collected 1 items mymodule.py . - ========================= 1 passed in 0.05 seconds ========================= + ========================= 1 passed in 0.06 seconds ========================= It is possible to use fixtures using the ``getfixture`` helper:: diff -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac -r 1a913c55c66d6841c113df2247942433e693ce0f doc/en/example/markers.txt --- a/doc/en/example/markers.txt +++ b/doc/en/example/markers.txt @@ -31,8 +31,8 @@ $ py.test -v -m webtest =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/doc-exec-157, inifile: collecting ... collected 4 items test_server.py::test_send_http PASSED @@ -44,8 +44,8 @@ $ py.test -v -m "not webtest" =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/doc-exec-157, inifile: collecting ... collected 4 items test_server.py::test_something_quick PASSED @@ -64,8 +64,8 @@ $ py.test -v test_server.py::TestClass::test_method =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/doc-exec-157, inifile: collecting ... collected 5 items test_server.py::TestClass::test_method PASSED @@ -76,8 +76,8 @@ $ py.test -v test_server.py::TestClass =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/doc-exec-157, inifile: collecting ... collected 4 items test_server.py::TestClass::test_method PASSED @@ -88,8 +88,8 @@ $ py.test -v test_server.py::TestClass test_server.py::test_send_http =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/doc-exec-157, inifile: collecting ... collected 8 items test_server.py::TestClass::test_method PASSED @@ -125,8 +125,8 @@ $ py.test -v -k http # running with the above defined example module =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/doc-exec-157, inifile: collecting ... collected 4 items test_server.py::test_send_http PASSED @@ -138,8 +138,8 @@ $ py.test -k "not send_http" -v =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/doc-exec-157, inifile: collecting ... collected 4 items test_server.py::test_something_quick PASSED @@ -153,8 +153,8 @@ $ py.test -k "http or quick" -v =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/doc-exec-157, inifile: collecting ... collected 4 items test_server.py::test_send_http PASSED @@ -342,25 +342,25 @@ $ py.test -E stage2 =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-157, inifile: collected 1 items test_someenv.py s - ======================== 1 skipped in 0.00 seconds ========================= + ======================== 1 skipped in 0.01 seconds ========================= and here is one that specifies exactly the environment needed:: $ py.test -E stage1 =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-157, inifile: collected 1 items test_someenv.py . - ========================= 1 passed in 0.00 seconds ========================= + ========================= 1 passed in 0.01 seconds ========================= The ``--markers`` option always gives you a list of available markers:: @@ -473,13 +473,13 @@ $ py.test -rs # this option reports skip reasons =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-157, inifile: collected 4 items test_plat.py sss. ========================= short test summary info ========================== - SKIP [3] /tmp/doc-exec-167/conftest.py:12: cannot run on platform linux + SKIP [3] /tmp/doc-exec-157/conftest.py:12: cannot run on platform linux =================== 1 passed, 3 skipped in 0.01 seconds ==================== @@ -487,8 +487,8 @@ $ py.test -m linux2 =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-157, inifile: collected 4 items test_plat.py s @@ -539,8 +539,8 @@ $ py.test -m interface --tb=short =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-157, inifile: collected 4 items test_module.py FF @@ -555,14 +555,14 @@ assert 0 E assert 0 ================== 2 tests deselected by "-m 'interface'" ================== - ================== 2 failed, 2 deselected in 0.01 seconds ================== + ================== 2 failed, 2 deselected in 0.02 seconds ================== or to select both "event" and "interface" tests:: $ py.test -m "interface or event" --tb=short =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-157, inifile: collected 4 items test_module.py FFF @@ -581,4 +581,4 @@ assert 0 E assert 0 ============= 1 tests deselected by "-m 'interface or event'" ============== - ================== 3 failed, 1 deselected in 0.01 seconds ================== + ================== 3 failed, 1 deselected in 0.02 seconds ================== diff -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac -r 1a913c55c66d6841c113df2247942433e693ce0f doc/en/example/nonpython.txt --- a/doc/en/example/nonpython.txt +++ b/doc/en/example/nonpython.txt @@ -27,18 +27,18 @@ nonpython $ py.test test_simple.yml =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /home/hpk/p/pytest/doc/en, inifile: pytest.ini + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/sandbox/pytest/doc/en, inifile: pytest.ini collected 2 items - test_simple.yml F. + test_simple.yml .F ================================= FAILURES ================================= ______________________________ usecase: hello ______________________________ usecase execution failed spec failed: 'some': 'other' no further details known at this point. - ==================== 1 failed, 1 passed in 0.03 seconds ==================== + ==================== 1 failed, 1 passed in 0.19 seconds ==================== You get one dot for the passing ``sub1: sub1`` check and one failure. Obviously in the above ``conftest.py`` you'll want to implement a more @@ -57,30 +57,30 @@ nonpython $ py.test -v =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /home/hpk/p/pytest/doc/en, inifile: pytest.ini + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/sandbox/pytest/doc/en, inifile: pytest.ini collecting ... collected 2 items + test_simple.yml::ok PASSED test_simple.yml::hello FAILED - test_simple.yml::ok PASSED ================================= FAILURES ================================= ______________________________ usecase: hello ______________________________ usecase execution failed spec failed: 'some': 'other' no further details known at this point. - ==================== 1 failed, 1 passed in 0.03 seconds ==================== + ==================== 1 failed, 1 passed in 0.05 seconds ==================== While developing your custom test collection and execution it's also interesting to just look at the collection tree:: nonpython $ py.test --collect-only =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /home/hpk/p/pytest/doc/en, inifile: pytest.ini + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/sandbox/pytest/doc/en, inifile: pytest.ini collected 2 items + - - ============================= in 0.03 seconds ============================= + ============================= in 0.04 seconds ============================= diff -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac -r 1a913c55c66d6841c113df2247942433e693ce0f doc/en/example/parametrize.txt --- a/doc/en/example/parametrize.txt +++ b/doc/en/example/parametrize.txt @@ -55,15 +55,15 @@ ....F ================================= FAILURES ================================= _____________________________ test_compute[4] ______________________________ - + param1 = 4 - + def test_compute(param1): > assert param1 < 4 E assert 4 < 4 - + test_compute.py:3: AssertionError - 1 failed, 4 passed in 0.01 seconds + 1 failed, 4 passed in 0.02 seconds As expected when running the full range of ``param1`` values we'll get an error on the last one. @@ -127,8 +127,8 @@ $ py.test test_time.py --collect-only =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-169, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-159, inifile: ============================= in 0.00 seconds ============================= ERROR: file not found: test_time.py @@ -171,21 +171,21 @@ $ py.test test_scenarios.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-169, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-159, inifile: collected 4 items test_scenarios.py .... - ========================= 4 passed in 0.01 seconds ========================= + ========================= 4 passed in 0.02 seconds ========================= If you just collect tests you'll also nicely see 'advanced' and 'basic' as variants for the test function:: $ py.test --collect-only test_scenarios.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-169, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-159, inifile: collected 4 items @@ -249,14 +249,14 @@ $ py.test test_backends.py --collect-only =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-169, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-159, inifile: collected 2 items - ============================= in 0.00 seconds ============================= + ============================= in 0.01 seconds ============================= And then when we run the test:: @@ -265,7 +265,7 @@ ================================= FAILURES ================================= _________________________ test_db_initialized[d2] __________________________ - db = + db = def test_db_initialized(db): # a dummy test @@ -319,16 +319,16 @@ $ py.test -q F.. ================================= FAILURES ================================= - ________________________ TestClass.test_equals[1-2] ________________________ + ________________________ TestClass.test_equals[2-1] ________________________ - self = , a = 1, b = 2 + self = , a = 1, b = 2 def test_equals(self, a, b): > assert a == b E assert 1 == 2 test_parametrize.py:18: AssertionError - 1 failed, 2 passed in 0.01 seconds + 1 failed, 2 passed in 0.02 seconds Indirect parametrization with multiple fixtures -------------------------------------------------------------- @@ -348,7 +348,7 @@ . $ py.test -rs -q multipython.py ........................... - 27 passed in 1.70 seconds + 27 passed in 4.14 seconds Indirect parametrization of optional implementations/imports -------------------------------------------------------------------- @@ -395,13 +395,13 @@ $ py.test -rs test_module.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-169, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-159, inifile: collected 2 items test_module.py .s ========================= short test summary info ========================== - SKIP [1] /tmp/doc-exec-169/conftest.py:10: could not import 'opt2' + SKIP [1] /tmp/doc-exec-159/conftest.py:10: could not import 'opt2' =================== 1 passed, 1 skipped in 0.01 seconds ==================== diff -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac -r 1a913c55c66d6841c113df2247942433e693ce0f doc/en/example/pythoncollection.txt --- a/doc/en/example/pythoncollection.txt +++ b/doc/en/example/pythoncollection.txt @@ -43,8 +43,8 @@ $ py.test --collect-only =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-170, inifile: setup.cfg + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-160, inifile: setup.cfg collected 2 items @@ -89,8 +89,8 @@ . $ py.test --collect-only pythoncollection.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /home/hpk/p/pytest/doc/en, inifile: pytest.ini + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/sandbox/pytest/doc/en, inifile: pytest.ini collected 3 items @@ -143,11 +143,11 @@ $ py.test --collect-only =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-170, inifile: pytest.ini + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-160, inifile: pytest.ini collected 0 items - ============================= in 0.00 seconds ============================= + ============================= in 0.01 seconds ============================= If you run with a Python3 interpreter the moduled added through the conftest.py file will not be considered for test collection. diff -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac -r 1a913c55c66d6841c113df2247942433e693ce0f doc/en/example/reportingdemo.txt --- a/doc/en/example/reportingdemo.txt +++ b/doc/en/example/reportingdemo.txt @@ -13,8 +13,8 @@ assertion $ py.test failure_demo.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /home/hpk/p/pytest/doc/en, inifile: pytest.ini + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/sandbox/pytest/doc/en, inifile: pytest.ini collected 42 items failure_demo.py FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF @@ -31,7 +31,7 @@ failure_demo.py:15: AssertionError _________________________ TestFailing.test_simple __________________________ - self = + self = def test_simple(self): def f(): @@ -41,13 +41,13 @@ > assert f() == g() E assert 42 == 43 - E + where 42 = .f at 0x2b186edd09d8>() - E + and 43 = .g at 0x2b186edd9950>() + E + where 42 = .f at 0x7f65f2315510>() + E + and 43 = .g at 0x7f65f2323510>() failure_demo.py:28: AssertionError ____________________ TestFailing.test_simple_multiline _____________________ - self = + self = def test_simple_multiline(self): otherfunc_multi( @@ -67,19 +67,19 @@ failure_demo.py:11: AssertionError ___________________________ TestFailing.test_not ___________________________ - self = + self = def test_not(self): def f(): return 42 > assert not f() E assert not 42 - E + where 42 = .f at 0x2b186edd99d8>() + E + where 42 = .f at 0x7f65f2323598>() failure_demo.py:38: AssertionError _________________ TestSpecialisedExplanations.test_eq_text _________________ - self = + self = def test_eq_text(self): > assert 'spam' == 'eggs' @@ -90,7 +90,7 @@ failure_demo.py:42: AssertionError _____________ TestSpecialisedExplanations.test_eq_similar_text _____________ - self = + self = def test_eq_similar_text(self): > assert 'foo 1 bar' == 'foo 2 bar' @@ -103,7 +103,7 @@ failure_demo.py:45: AssertionError ____________ TestSpecialisedExplanations.test_eq_multiline_text ____________ - self = + self = def test_eq_multiline_text(self): > assert 'foo\nspam\nbar' == 'foo\neggs\nbar' @@ -116,7 +116,7 @@ failure_demo.py:48: AssertionError ______________ TestSpecialisedExplanations.test_eq_long_text _______________ - self = + self = def test_eq_long_text(self): a = '1'*100 + 'a' + '2'*100 @@ -133,7 +133,7 @@ failure_demo.py:53: AssertionError _________ TestSpecialisedExplanations.test_eq_long_text_multiline __________ - self = + self = def test_eq_long_text_multiline(self): a = '1\n'*100 + 'a' + '2\n'*100 @@ -157,7 +157,7 @@ failure_demo.py:58: AssertionError _________________ TestSpecialisedExplanations.test_eq_list _________________ - self = + self = def test_eq_list(self): > assert [0, 1, 2] == [0, 1, 3] @@ -168,7 +168,7 @@ failure_demo.py:61: AssertionError ______________ TestSpecialisedExplanations.test_eq_list_long _______________ - self = + self = def test_eq_list_long(self): a = [0]*100 + [1] + [3]*100 @@ -181,7 +181,7 @@ failure_demo.py:66: AssertionError _________________ TestSpecialisedExplanations.test_eq_dict _________________ - self = + self = def test_eq_dict(self): > assert {'a': 0, 'b': 1, 'c': 0} == {'a': 0, 'b': 2, 'd': 0} @@ -198,7 +198,7 @@ failure_demo.py:69: AssertionError _________________ TestSpecialisedExplanations.test_eq_set __________________ - self = + self = def test_eq_set(self): > assert set([0, 10, 11, 12]) == set([0, 20, 21]) @@ -215,7 +215,7 @@ failure_demo.py:72: AssertionError _____________ TestSpecialisedExplanations.test_eq_longer_list ______________ - self = + self = def test_eq_longer_list(self): > assert [1,2] == [1,2,3] @@ -226,7 +226,7 @@ failure_demo.py:75: AssertionError _________________ TestSpecialisedExplanations.test_in_list _________________ - self = + self = def test_in_list(self): > assert 1 in [0, 2, 3, 4, 5] @@ -235,7 +235,7 @@ failure_demo.py:78: AssertionError __________ TestSpecialisedExplanations.test_not_in_text_multiline __________ - self = + self = def test_not_in_text_multiline(self): text = 'some multiline\ntext\nwhich\nincludes foo\nand a\ntail' @@ -253,7 +253,7 @@ failure_demo.py:82: AssertionError ___________ TestSpecialisedExplanations.test_not_in_text_single ____________ - self = + self = def test_not_in_text_single(self): text = 'single foo line' @@ -266,7 +266,7 @@ failure_demo.py:86: AssertionError _________ TestSpecialisedExplanations.test_not_in_text_single_long _________ - self = + self = def test_not_in_text_single_long(self): text = 'head ' * 50 + 'foo ' + 'tail ' * 20 @@ -279,7 +279,7 @@ failure_demo.py:90: AssertionError ______ TestSpecialisedExplanations.test_not_in_text_single_long_term _______ - self = + self = def test_not_in_text_single_long_term(self): text = 'head ' * 50 + 'f'*70 + 'tail ' * 20 @@ -298,7 +298,7 @@ i = Foo() > assert i.b == 2 E assert 1 == 2 - E + where 1 = .Foo object at 0x2b186ee8d4e0>.b + E + where 1 = .Foo object at 0x7f65f1c814e0>.b failure_demo.py:101: AssertionError _________________________ test_attribute_instance __________________________ @@ -308,8 +308,8 @@ b = 1 > assert Foo().b == 2 E assert 1 == 2 - E + where 1 = .Foo object at 0x2b186eea6240>.b - E + where .Foo object at 0x2b186eea6240> = .Foo'>() + E + where 1 = .Foo object at 0x7f65f1c7f7f0>.b + E + where .Foo object at 0x7f65f1c7f7f0> = .Foo'>() failure_demo.py:107: AssertionError __________________________ test_attribute_failure __________________________ @@ -325,7 +325,7 @@ failure_demo.py:116: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ - self = .Foo object at 0x2b186ed9a4e0> + self = .Foo object at 0x7f65f1c97dd8> def _get_b(self): > raise Exception('Failed to get attrib') @@ -341,15 +341,15 @@ b = 2 > assert Foo().b == Bar().b E assert 1 == 2 - E + where 1 = .Foo object at 0x2b186ee78630>.b - E + where .Foo object at 0x2b186ee78630> = .Foo'>() - E + and 2 = .Bar object at 0x2b186ee78358>.b - E + where .Bar object at 0x2b186ee78358> = .Bar'>() + E + where 1 = .Foo object at 0x7f65f1c9b630>.b + E + where .Foo object at 0x7f65f1c9b630> = .Foo'>() + E + and 2 = .Bar object at 0x7f65f1c9b2b0>.b + E + where .Bar object at 0x7f65f1c9b2b0> = .Bar'>() failure_demo.py:124: AssertionError __________________________ TestRaises.test_raises __________________________ - self = + self = def test_raises(self): s = 'qwe' @@ -361,10 +361,10 @@ > int(s) E ValueError: invalid literal for int() with base 10: 'qwe' - <0-codegen /home/hpk/p/pytest/.tox/regen/lib/python3.4/site-packages/_pytest/python.py:1075>:1: ValueError + <0-codegen /tmp/sandbox/pytest/.tox/regen/lib/python3.4/site-packages/_pytest/python.py:1075>:1: ValueError ______________________ TestRaises.test_raises_doesnt _______________________ - self = + self = def test_raises_doesnt(self): > raises(IOError, "int('3')") @@ -373,7 +373,7 @@ failure_demo.py:136: Failed __________________________ TestRaises.test_raise ___________________________ - self = + self = def test_raise(self): > raise ValueError("demo error") @@ -382,7 +382,7 @@ failure_demo.py:139: ValueError ________________________ TestRaises.test_tupleerror ________________________ - self = + self = def test_tupleerror(self): > a,b = [1] @@ -391,7 +391,7 @@ failure_demo.py:142: ValueError ______ TestRaises.test_reinterpret_fails_with_print_for_the_fun_of_it ______ - self = + self = def test_reinterpret_fails_with_print_for_the_fun_of_it(self): l = [1,2,3] @@ -404,7 +404,7 @@ l is [1, 2, 3] ________________________ TestRaises.test_some_error ________________________ - self = + self = def test_some_error(self): > if namenotexi: @@ -429,10 +429,10 @@ > assert 1 == 0 E assert 1 == 0 - <2-codegen 'abc-123' /home/hpk/p/pytest/doc/en/example/assertion/failure_demo.py:162>:2: AssertionError + <2-codegen 'abc-123' /tmp/sandbox/pytest/doc/en/example/assertion/failure_demo.py:162>:2: AssertionError ____________________ TestMoreErrors.test_complex_error _____________________ - self = + self = def test_complex_error(self): def f(): @@ -456,7 +456,7 @@ failure_demo.py:5: AssertionError ___________________ TestMoreErrors.test_z1_unpack_error ____________________ - self = + self = def test_z1_unpack_error(self): l = [] @@ -466,7 +466,7 @@ failure_demo.py:179: ValueError ____________________ TestMoreErrors.test_z2_type_error _____________________ - self = + self = def test_z2_type_error(self): l = 3 @@ -476,19 +476,19 @@ failure_demo.py:183: TypeError ______________________ TestMoreErrors.test_startswith ______________________ - self = + self = def test_startswith(self): s = "123" g = "456" > assert s.startswith(g) - E assert ('456') - E + where = '123'.startswith + E assert ('456') + E + where = '123'.startswith failure_demo.py:188: AssertionError __________________ TestMoreErrors.test_startswith_nested ___________________ - self = + self = def test_startswith_nested(self): def f(): @@ -496,15 +496,15 @@ def g(): return "456" > assert f().startswith(g()) - E assert ('456') - E + where = '123'.startswith - E + where '123' = .f at 0x2b186eea1510>() - E + and '456' = .g at 0x2b186eea1268>() + E assert ('456') + E + where = '123'.startswith + E + where '123' = .f at 0x7f65f1c32950>() + E + and '456' = .g at 0x7f65f1c32ea0>() failure_demo.py:195: AssertionError _____________________ TestMoreErrors.test_global_func ______________________ - self = + self = def test_global_func(self): > assert isinstance(globf(42), float) @@ -514,18 +514,18 @@ failure_demo.py:198: AssertionError _______________________ TestMoreErrors.test_instance _______________________ - self = + self = def test_instance(self): self.x = 6*7 > assert self.x != 42 E assert 42 != 42 - E + where 42 = .x + E + where 42 = .x failure_demo.py:202: AssertionError _______________________ TestMoreErrors.test_compare ________________________ - self = + self = def test_compare(self): > assert globf(10) < 5 @@ -535,7 +535,7 @@ failure_demo.py:205: AssertionError _____________________ TestMoreErrors.test_try_finally ______________________ - self = + self = def test_try_finally(self): x = 1 @@ -546,7 +546,7 @@ failure_demo.py:210: AssertionError ___________________ TestCustomAssertMsg.test_single_line ___________________ - self = + self = def test_single_line(self): class A: @@ -560,7 +560,7 @@ failure_demo.py:221: AssertionError ____________________ TestCustomAssertMsg.test_multiline ____________________ - self = + self = def test_multiline(self): class A: @@ -577,7 +577,7 @@ failure_demo.py:227: AssertionError ___________________ TestCustomAssertMsg.test_custom_repr ___________________ - self = + self = def test_custom_repr(self): class JSON: @@ -595,4 +595,4 @@ E + where 1 = This is JSON\n{\n 'foo': 'bar'\n}.a failure_demo.py:237: AssertionError - ======================== 42 failed in 0.22 seconds ========================= + ======================== 42 failed in 0.35 seconds ========================= diff -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac -r 1a913c55c66d6841c113df2247942433e693ce0f doc/en/example/simple.txt --- a/doc/en/example/simple.txt +++ b/doc/en/example/simple.txt @@ -108,8 +108,8 @@ $ py.test =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-172, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-162, inifile: collected 0 items ============================= in 0.00 seconds ============================= @@ -153,13 +153,13 @@ $ py.test -rs # "-rs" means report details on the little 's' =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-172, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-162, inifile: collected 2 items test_module.py .s ========================= short test summary info ========================== - SKIP [1] /tmp/doc-exec-172/conftest.py:9: need --runslow option to run + SKIP [1] /tmp/doc-exec-162/conftest.py:9: need --runslow option to run =================== 1 passed, 1 skipped in 0.01 seconds ==================== @@ -167,8 +167,8 @@ $ py.test --runslow =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-172, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-162, inifile: collected 2 items test_module.py .. @@ -205,13 +205,13 @@ F ================================= FAILURES ================================= ______________________________ test_something ______________________________ - + def test_something(): > checkconfig(42) E Failed: not configured: 42 - + test_checkconfig.py:8: Failed - 1 failed in 0.01 seconds + 1 failed in 0.02 seconds Detect if running from within a pytest run -------------------------------------------------------------- @@ -259,8 +259,8 @@ $ py.test =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-172, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-162, inifile: project deps: mylib-1.1 collected 0 items @@ -283,8 +283,8 @@ $ py.test -v =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /tmp/doc-exec-172, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/doc-exec-162, inifile: info1: did you know that ... did you? collecting ... collected 0 items @@ -295,8 +295,8 @@ $ py.test =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-172, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-162, inifile: collected 0 items ============================= in 0.00 seconds ============================= @@ -328,8 +328,8 @@ $ py.test --durations=3 =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-172, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-162, inifile: collected 3 items test_some_are_slow.py ... @@ -390,8 +390,8 @@ $ py.test -rx =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-172, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-162, inifile: collected 4 items test_step.py .Fx. @@ -399,7 +399,7 @@ ================================= FAILURES ================================= ____________________ TestUserHandling.test_modification ____________________ - self = + self = def test_modification(self): > assert 0 @@ -409,7 +409,7 @@ ========================= short test summary info ========================== XFAIL test_step.py::TestUserHandling::()::test_deletion reason: previous test failed (test_modification) - ============== 1 failed, 2 passed, 1 xfailed in 0.01 seconds =============== + ============== 1 failed, 2 passed, 1 xfailed in 0.02 seconds =============== We'll see that ``test_deletion`` was not executed because ``test_modification`` failed. It is reported as an "expected failure". @@ -461,8 +461,8 @@ $ py.test =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-172, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-162, inifile: collected 7 items test_step.py .Fx. @@ -472,17 +472,17 @@ ================================== ERRORS ================================== _______________________ ERROR at setup of test_root ________________________ - file /tmp/doc-exec-172/b/test_error.py, line 1 + file /tmp/doc-exec-162/b/test_error.py, line 1 def test_root(db): # no db here, will error out fixture 'db' not found - available fixtures: pytestconfig, tmpdir, monkeypatch, capfd, recwarn, capsys + available fixtures: pytestconfig, capsys, recwarn, monkeypatch, tmpdir, capfd use 'py.test --fixtures [testpath]' for help on them. - /tmp/doc-exec-172/b/test_error.py:1 + /tmp/doc-exec-162/b/test_error.py:1 ================================= FAILURES ================================= ____________________ TestUserHandling.test_modification ____________________ - self = + self = def test_modification(self): > assert 0 @@ -491,25 +491,25 @@ test_step.py:9: AssertionError _________________________________ test_a1 __________________________________ - db = + db = def test_a1(db): > assert 0, db # to show value - E AssertionError: + E AssertionError: E assert 0 a/test_db.py:2: AssertionError _________________________________ test_a2 __________________________________ - db = + db = def test_a2(db): > assert 0, db # to show value - E AssertionError: + E AssertionError: E assert 0 a/test_db2.py:2: AssertionError - ========== 3 failed, 2 passed, 1 xfailed, 1 error in 0.03 seconds ========== + ========== 3 failed, 2 passed, 1 xfailed, 1 error in 0.05 seconds ========== The two test modules in the ``a`` directory see the same ``db`` fixture instance while the one test in the sister-directory ``b`` doesn't see it. We could of course @@ -564,8 +564,8 @@ $ py.test test_module.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-172, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-162, inifile: collected 2 items test_module.py FF @@ -573,7 +573,7 @@ ================================= FAILURES ================================= ________________________________ test_fail1 ________________________________ - tmpdir = local('/tmp/pytest-219/test_fail10') + tmpdir = local('/tmp/pytest-22/test_fail10') def test_fail1(tmpdir): > assert 0 @@ -587,12 +587,12 @@ E assert 0 test_module.py:4: AssertionError - ========================= 2 failed in 0.01 seconds ========================= + ========================= 2 failed in 0.02 seconds ========================= you will have a "failures" file which contains the failing test ids:: $ cat failures - test_module.py::test_fail1 (/tmp/pytest-219/test_fail10) + test_module.py::test_fail1 (/tmp/pytest-22/test_fail10) test_module.py::test_fail2 Making test result information available in fixtures @@ -655,8 +655,8 @@ $ py.test -s test_module.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-172, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-162, inifile: collected 3 items test_module.py Esetting up a test failed! test_module.py::test_setup_fails @@ -689,7 +689,7 @@ E assert 0 test_module.py:15: AssertionError - ==================== 2 failed, 1 error in 0.01 seconds ===================== + ==================== 2 failed, 1 error in 0.02 seconds ===================== You'll see that the fixture finalizers could use the precise reporting information. diff -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac -r 1a913c55c66d6841c113df2247942433e693ce0f doc/en/example/special.txt --- a/doc/en/example/special.txt +++ b/doc/en/example/special.txt @@ -69,4 +69,4 @@ .test other .test_unit1 method called . - 4 passed in 0.04 seconds + 4 passed in 0.03 seconds diff -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac -r 1a913c55c66d6841c113df2247942433e693ce0f doc/en/fixture.txt --- a/doc/en/fixture.txt +++ b/doc/en/fixture.txt @@ -75,8 +75,8 @@ $ py.test test_smtpsimple.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-109, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-98, inifile: collected 1 items test_smtpsimple.py F @@ -84,7 +84,7 @@ ================================= FAILURES ================================= ________________________________ test_ehlo _________________________________ - smtp = + smtp = def test_ehlo(smtp): response, msg = smtp.ehlo() @@ -93,7 +93,7 @@ E assert 0 test_smtpsimple.py:11: AssertionError - ========================= 1 failed in 0.17 seconds ========================= + ========================= 1 failed in 1.07 seconds ========================= In the failure traceback we see that the test function was called with a ``smtp`` argument, the ``smtplib.SMTP()`` instance created by the fixture @@ -193,8 +193,8 @@ $ py.test test_module.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-109, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-98, inifile: collected 2 items test_module.py FF @@ -202,7 +202,7 @@ ================================= FAILURES ================================= ________________________________ test_ehlo _________________________________ - smtp = + smtp = def test_ehlo(smtp): response = smtp.ehlo() @@ -213,7 +213,7 @@ test_module.py:5: TypeError ________________________________ test_noop _________________________________ - smtp = + smtp = def test_noop(smtp): response = smtp.noop() @@ -222,7 +222,7 @@ E assert 0 test_module.py:11: AssertionError - ========================= 2 failed in 0.20 seconds ========================= + ========================= 2 failed in 0.82 seconds ========================= You see the two ``assert 0`` failing and more importantly you can also see that the same (module-scoped) ``smtp`` object was passed into the two @@ -270,7 +270,7 @@ $ py.test -s -q --tb=no FFteardown smtp - 2 failed in 0.25 seconds + 2 failed in 1.44 seconds We see that the ``smtp`` instance is finalized after the two tests finished execution. Note that if we decorated our fixture @@ -311,7 +311,7 @@ $ py.test -s -q --tb=no FF - 2 failed in 0.17 seconds + 2 failed in 0.62 seconds Let's quickly create another test module that actually sets the server URL in its module namespace:: @@ -379,7 +379,7 @@ ================================= FAILURES ================================= __________________________ test_ehlo[merlinux.eu] __________________________ - smtp = + smtp = def test_ehlo(smtp): response = smtp.ehlo() @@ -390,7 +390,7 @@ test_module.py:5: TypeError __________________________ test_noop[merlinux.eu] __________________________ - smtp = + smtp = def test_noop(smtp): response = smtp.noop() @@ -401,7 +401,7 @@ test_module.py:11: AssertionError ________________________ test_ehlo[mail.python.org] ________________________ - smtp = + smtp = def test_ehlo(smtp): response = smtp.ehlo() @@ -411,10 +411,10 @@ test_module.py:5: TypeError -------------------------- Captured stdout setup --------------------------- - finalizing + finalizing ________________________ test_noop[mail.python.org] ________________________ - smtp = + smtp = def test_noop(smtp): response = smtp.noop() @@ -423,7 +423,7 @@ E assert 0 test_module.py:11: AssertionError - 4 failed in 6.70 seconds + 4 failed in 1.75 seconds We see that our two test functions each ran twice, against the different ``smtp`` instances. Note also, that with the ``mail.python.org`` @@ -474,8 +474,8 @@ $ py.test --collect-only =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-109, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-98, inifile: collected 6 items @@ -486,7 +486,7 @@ - ============================= in 0.01 seconds ============================= + ============================= in 0.02 seconds ============================= .. _`interdependent fixtures`: @@ -520,14 +520,14 @@ $ py.test -v test_appsetup.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /tmp/doc-exec-109, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/doc-exec-98, inifile: collecting ... collected 2 items test_appsetup.py::test_smtp_exists[merlinux.eu] PASSED test_appsetup.py::test_smtp_exists[mail.python.org] PASSED - ========================= 2 passed in 6.53 seconds ========================= + ========================= 2 passed in 1.09 seconds ========================= Due to the parametrization of ``smtp`` the test will run twice with two different ``App`` instances and respective smtp servers. There is no @@ -585,8 +585,8 @@ $ py.test -v -s test_module.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /tmp/doc-exec-109, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/doc-exec-98, inifile: collecting ... collected 8 items test_module.py::test_0[1] test0 1 @@ -608,7 +608,7 @@ test_module.py::test_2[2-mod2] test2 2 mod2 PASSED - ========================= 8 passed in 0.01 seconds ========================= + ========================= 8 passed in 0.02 seconds ========================= You can see that the parametrized module-scoped ``modarg`` resource caused an ordering of test execution that lead to the fewest possible "active" resources. The finalizer for the ``mod1`` parametrized resource was executed diff -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac -r 1a913c55c66d6841c113df2247942433e693ce0f doc/en/getting-started.txt --- a/doc/en/getting-started.txt +++ b/doc/en/getting-started.txt @@ -27,7 +27,7 @@ To check your installation has installed the correct version:: $ py.test --version - This is pytest version 2.7.0, imported from /home/hpk/p/pytest/.tox/regen/lib/python3.4/site-packages/pytest.py + This is pytest version 2.7.1, imported from /tmp/sandbox/pytest/.tox/regen/lib/python3.4/site-packages/pytest.py If you get an error checkout :ref:`installation issues`. @@ -49,8 +49,8 @@ $ py.test =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-112, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-101, inifile: collected 1 items test_sample.py F @@ -98,7 +98,7 @@ $ py.test -q test_sysexit.py . - 1 passed in 0.00 seconds + 1 passed in 0.01 seconds .. todo:: For further ways to assert exceptions see the `raises` @@ -128,7 +128,7 @@ ================================= FAILURES ================================= ____________________________ TestClass.test_two ____________________________ - self = + self = def test_two(self): x = "hello" @@ -164,7 +164,7 @@ ================================= FAILURES ================================= _____________________________ test_needsfiles ______________________________ - tmpdir = local('/tmp/pytest-215/test_needsfiles0') + tmpdir = local('/tmp/pytest-18/test_needsfiles0') def test_needsfiles(tmpdir): print (tmpdir) @@ -173,8 +173,8 @@ test_tmpdir.py:3: AssertionError --------------------------- Captured stdout call --------------------------- - /tmp/pytest-215/test_needsfiles0 - 1 failed in 0.01 seconds + /tmp/pytest-18/test_needsfiles0 + 1 failed in 0.05 seconds Before the test runs, a unique-per-test-invocation temporary directory was created. More info at :ref:`tmpdir handling`. diff -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac -r 1a913c55c66d6841c113df2247942433e693ce0f doc/en/parametrize.txt --- a/doc/en/parametrize.txt +++ b/doc/en/parametrize.txt @@ -53,8 +53,8 @@ $ py.test =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-120, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-109, inifile: collected 3 items test_expectation.py ..F @@ -75,7 +75,7 @@ E + where 54 = eval('6*9') test_expectation.py:8: AssertionError - ==================== 1 failed, 2 passed in 0.01 seconds ==================== + ==================== 1 failed, 2 passed in 0.02 seconds ==================== As designed in this example, only one pair of input/output values fails the simple test function. And as usual with test function arguments, @@ -101,13 +101,13 @@ $ py.test =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-120, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-109, inifile: collected 3 items test_expectation.py ..x - =================== 2 passed, 1 xfailed in 0.01 seconds ==================== + =================== 2 passed, 1 xfailed in 0.02 seconds ==================== The one parameter set which caused a failure previously now shows up as an "xfailed (expected to fail)" test. @@ -172,8 +172,8 @@ def test_valid_string(stringinput): > assert stringinput.isalpha() - E assert () - E + where = '!'.isalpha + E assert () + E + where = '!'.isalpha test_strings.py:3: AssertionError 1 failed in 0.01 seconds @@ -187,8 +187,8 @@ $ py.test -q -rs test_strings.py s ========================= short test summary info ========================== - SKIP [1] /home/hpk/p/pytest/.tox/regen/lib/python3.4/site-packages/_pytest/python.py:1185: got empty parameter set, function test_valid_string at /tmp/doc-exec-120/test_strings.py:1 - 1 skipped in 0.00 seconds + SKIP [1] /tmp/sandbox/pytest/.tox/regen/lib/python3.4/site-packages/_pytest/python.py:1185: got empty parameter set, function test_valid_string at /tmp/doc-exec-109/test_strings.py:1 + 1 skipped in 0.01 seconds For further examples, you might want to look at :ref:`more parametrization examples `. diff -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac -r 1a913c55c66d6841c113df2247942433e693ce0f doc/en/skipping.txt --- a/doc/en/skipping.txt +++ b/doc/en/skipping.txt @@ -164,8 +164,8 @@ example $ py.test -rx xfail_demo.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /home/hpk/p/pytest/doc/en, inifile: pytest.ini + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/sandbox/pytest/doc/en, inifile: pytest.ini collected 7 items xfail_demo.py xxxxxxx @@ -183,7 +183,7 @@ reason: reason XFAIL xfail_demo.py::test_hello7 - ======================== 7 xfailed in 0.05 seconds ========================= + ======================== 7 xfailed in 0.06 seconds ========================= .. _`skip/xfail with parametrize`: diff -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac -r 1a913c55c66d6841c113df2247942433e693ce0f doc/en/tmpdir.txt --- a/doc/en/tmpdir.txt +++ b/doc/en/tmpdir.txt @@ -29,8 +29,8 @@ $ py.test test_tmpdir.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-129, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-118, inifile: collected 1 items test_tmpdir.py F @@ -38,7 +38,7 @@ ================================= FAILURES ================================= _____________________________ test_create_file _____________________________ - tmpdir = local('/tmp/pytest-216/test_create_file0') + tmpdir = local('/tmp/pytest-19/test_create_file0') def test_create_file(tmpdir): p = tmpdir.mkdir("sub").join("hello.txt") @@ -49,7 +49,7 @@ E assert 0 test_tmpdir.py:7: AssertionError - ========================= 1 failed in 0.01 seconds ========================= + ========================= 1 failed in 0.04 seconds ========================= .. _`base temporary directory`: diff -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac -r 1a913c55c66d6841c113df2247942433e693ce0f doc/en/unittest.txt --- a/doc/en/unittest.txt +++ b/doc/en/unittest.txt @@ -88,8 +88,8 @@ $ py.test test_unittest_db.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-130, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-119, inifile: collected 2 items test_unittest_db.py FF @@ -102,7 +102,7 @@ def test_method1(self): assert hasattr(self, "db") > assert 0, self.db # fail for demo purposes - E AssertionError: .DummyDB object at 0x2ab102a4bac8> + E AssertionError: .DummyDB object at 0x7f97382031d0> E assert 0 test_unittest_db.py:9: AssertionError @@ -112,7 +112,7 @@ def test_method2(self): > assert 0, self.db # fail for demo purposes - E AssertionError: .DummyDB object at 0x2ab102a4bac8> + E AssertionError: .DummyDB object at 0x7f97382031d0> E assert 0 test_unittest_db.py:12: AssertionError @@ -163,7 +163,7 @@ $ py.test -q test_unittest_cleandir.py . - 1 passed in 0.04 seconds + 1 passed in 0.25 seconds ... gives us one passed test because the ``initdir`` fixture function was executed ahead of the ``test_method``. diff -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac -r 1a913c55c66d6841c113df2247942433e693ce0f doc/en/yieldfixture.txt --- a/doc/en/yieldfixture.txt +++ b/doc/en/yieldfixture.txt @@ -43,7 +43,7 @@ test called .teardown after yield - 1 passed in 0.00 seconds + 1 passed in 0.01 seconds We can also seamlessly use the new syntax with ``with`` statements. Let's simplify the above ``passwd`` fixture:: Repository URL: https://bitbucket.org/pytest-dev/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email. From commits-noreply at bitbucket.org Sun May 31 21:27:18 2015 From: commits-noreply at bitbucket.org (commits-noreply at bitbucket.org) Date: Sun, 31 May 2015 19:27:18 -0000 Subject: [Pytest-commit] commit/pytest: 2 new changesets Message-ID: <20150531192718.8488.53644@app14.ash-private.bitbucket.org> 2 new commits in pytest: https://bitbucket.org/pytest-dev/pytest/commits/3b7dc139bbc2/ Changeset: 3b7dc139bbc2 User: flub Date: 2015-05-19 20:43:50+00:00 Summary: Merge changes from 2.7.1 release This is mostly doc examples regeneration, other changes are: - Add 2.7.1 tag to .hgtags - Update changelog to use real versions - Update HOWTORELEASE.rst - Fix rst in doc/en/adopt.txt - Fix CHANGELOG and release-2.7.0 rst formatting Affected #: 24 files diff -r 6ee4b4d70a9b81f525c6fe46eab1b2018de6f4b0 -r 3b7dc139bbc2993c9727f5439939352cc8f53deb .hgtags --- a/.hgtags +++ b/.hgtags @@ -74,3 +74,4 @@ 2967aa416a4f3cdb65fc75073a2a148e1f372742 2.6.3 f03b6de8325f5b6c35cea7c3de092f134ea8ef07 2.6.4 7ed701fa2fb554bfc0618d447dfec700cc697407 2.7.0 +edc1d080bab5a970da8f6c776be50768829a7b09 2.7.1 diff -r 6ee4b4d70a9b81f525c6fe46eab1b2018de6f4b0 -r 3b7dc139bbc2993c9727f5439939352cc8f53deb CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -52,7 +52,7 @@ one will also have a "reprec" attribute with the recorded events/reports. -2.7.1.dev (compared to 2.7.0) +2.7.1 (compared to 2.7.0) ----------------------------- - fix issue731: do not get confused by the braces which may be present diff -r 6ee4b4d70a9b81f525c6fe46eab1b2018de6f4b0 -r 3b7dc139bbc2993c9727f5439939352cc8f53deb HOWTORELEASE.rst --- a/HOWTORELEASE.rst +++ b/HOWTORELEASE.rst @@ -2,7 +2,7 @@ How to release pytest (draft) -------------------------------------------- -1. bump version numbers in pytest/__init__.py (setup.py reads it) +1. bump version numbers in _pytest/__init__.py (setup.py reads it) 2. check and finalize CHANGELOG @@ -11,7 +11,7 @@ 4. use devpi for uploading a release tarball to a staging area: - ``devpi use https://devpi.net/USER/dev`` - - ``devpi upload`` + - ``devpi upload --formats sdist,bdist_wheel`` 5. run from multiple machines: - ``devpi use https://devpi.net/USER/dev`` @@ -25,22 +25,33 @@ which is ok (tox does not support skipping on per-platform basis yet). -7. Regenerate the docs using the toplevel makefile:: - make docs +7. Regenerate the docs examples using tox:: + # Create and activate a virtualenv with regendoc installed + # (currently needs revision 4a9ec1035734) + tox -e regen -8. Upload the docs using the toplevel makefile:: - make upload-docs +8. Build the docs, you need a virtualenv with, py and sphinx + installed:: + cd docs/en + make html + +9. Tag the release:: + hg tag VERSION + +10. Upload the docs using docs/en/Makefile:: + cd docs/en + make install # or "installall" if you have LaTeX installed This requires ssh-login permission on pytest.org because it uses rsync. Note that the "install" target of doc/en/Makefile defines where the rsync goes to, typically to the "latest" section of pytest.org. -9. publish to pypi "devpi push pytest-2.6.2 pypi:NAME" where NAME +11. publish to pypi "devpi push pytest-VERSION pypi:NAME" where NAME is the name of pypi.python.org as configured in your ~/.pypirc file -- it's the same you would use with "setup.py upload -r NAME" -10. send release announcement to mailing lists: +12. send release announcement to mailing lists: pytest-dev testing-in-python diff -r 6ee4b4d70a9b81f525c6fe46eab1b2018de6f4b0 -r 3b7dc139bbc2993c9727f5439939352cc8f53deb doc/en/adopt.txt --- a/doc/en/adopt.txt +++ b/doc/en/adopt.txt @@ -11,7 +11,6 @@ .. _`issue tracker`: https://bitbucket.org/pytest-dev/pytest/issue/676/adopt-pytest-month-2015 .. _`pytest-dev mailing list`: https://mail.python.org/mailman/listinfo/pytest-dev -.. _``: The ideal pytest helper diff -r 6ee4b4d70a9b81f525c6fe46eab1b2018de6f4b0 -r 3b7dc139bbc2993c9727f5439939352cc8f53deb doc/en/announce/release-2.7.0.txt --- a/doc/en/announce/release-2.7.0.txt +++ b/doc/en/announce/release-2.7.0.txt @@ -89,7 +89,7 @@ it from the "decorator" case. Thanks Tom Viner. - "python_classes" and "python_functions" options now support glob-patterns - for test discovery, as discussed in issue600. Thanks Ldiary Translations. + for test discovery, as discussed in issue600. Thanks Ldiary Translations. - allow to override parametrized fixtures with non-parametrized ones and vice versa (bubenkoff). diff -r 6ee4b4d70a9b81f525c6fe46eab1b2018de6f4b0 -r 3b7dc139bbc2993c9727f5439939352cc8f53deb doc/en/assert.txt --- a/doc/en/assert.txt +++ b/doc/en/assert.txt @@ -26,8 +26,8 @@ $ py.test test_assert1.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-98, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-87, inifile: collected 1 items test_assert1.py F @@ -136,8 +136,8 @@ $ py.test test_assert2.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-98, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-87, inifile: collected 1 items test_assert2.py F @@ -213,7 +213,7 @@ E vals: 1 != 2 test_foocompare.py:8: AssertionError - 1 failed in 0.00 seconds + 1 failed in 0.01 seconds .. _assert-details: .. _`assert introspection`: diff -r 6ee4b4d70a9b81f525c6fe46eab1b2018de6f4b0 -r 3b7dc139bbc2993c9727f5439939352cc8f53deb doc/en/capture.txt --- a/doc/en/capture.txt +++ b/doc/en/capture.txt @@ -64,8 +64,8 @@ $ py.test =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-101, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-90, inifile: collected 2 items test_module.py .F @@ -79,7 +79,7 @@ test_module.py:9: AssertionError -------------------------- Captured stdout setup --------------------------- - setting up + setting up ==================== 1 failed, 1 passed in 0.01 seconds ==================== Accessing captured output from a test function diff -r 6ee4b4d70a9b81f525c6fe46eab1b2018de6f4b0 -r 3b7dc139bbc2993c9727f5439939352cc8f53deb doc/en/doctest.txt --- a/doc/en/doctest.txt +++ b/doc/en/doctest.txt @@ -44,13 +44,13 @@ $ py.test =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-107, inifile: pytest.ini + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-96, inifile: pytest.ini collected 1 items mymodule.py . - ========================= 1 passed in 0.05 seconds ========================= + ========================= 1 passed in 0.06 seconds ========================= It is possible to use fixtures using the ``getfixture`` helper:: diff -r 6ee4b4d70a9b81f525c6fe46eab1b2018de6f4b0 -r 3b7dc139bbc2993c9727f5439939352cc8f53deb doc/en/example/markers.txt --- a/doc/en/example/markers.txt +++ b/doc/en/example/markers.txt @@ -31,8 +31,8 @@ $ py.test -v -m webtest =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/doc-exec-157, inifile: collecting ... collected 4 items test_server.py::test_send_http PASSED @@ -44,8 +44,8 @@ $ py.test -v -m "not webtest" =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/doc-exec-157, inifile: collecting ... collected 4 items test_server.py::test_something_quick PASSED @@ -64,8 +64,8 @@ $ py.test -v test_server.py::TestClass::test_method =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/doc-exec-157, inifile: collecting ... collected 5 items test_server.py::TestClass::test_method PASSED @@ -76,8 +76,8 @@ $ py.test -v test_server.py::TestClass =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/doc-exec-157, inifile: collecting ... collected 4 items test_server.py::TestClass::test_method PASSED @@ -88,8 +88,8 @@ $ py.test -v test_server.py::TestClass test_server.py::test_send_http =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/doc-exec-157, inifile: collecting ... collected 8 items test_server.py::TestClass::test_method PASSED @@ -125,8 +125,8 @@ $ py.test -v -k http # running with the above defined example module =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/doc-exec-157, inifile: collecting ... collected 4 items test_server.py::test_send_http PASSED @@ -138,8 +138,8 @@ $ py.test -k "not send_http" -v =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/doc-exec-157, inifile: collecting ... collected 4 items test_server.py::test_something_quick PASSED @@ -153,8 +153,8 @@ $ py.test -k "http or quick" -v =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/doc-exec-157, inifile: collecting ... collected 4 items test_server.py::test_send_http PASSED @@ -342,25 +342,25 @@ $ py.test -E stage2 =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-157, inifile: collected 1 items test_someenv.py s - ======================== 1 skipped in 0.00 seconds ========================= + ======================== 1 skipped in 0.01 seconds ========================= and here is one that specifies exactly the environment needed:: $ py.test -E stage1 =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-157, inifile: collected 1 items test_someenv.py . - ========================= 1 passed in 0.00 seconds ========================= + ========================= 1 passed in 0.01 seconds ========================= The ``--markers`` option always gives you a list of available markers:: @@ -473,13 +473,13 @@ $ py.test -rs # this option reports skip reasons =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-157, inifile: collected 4 items test_plat.py sss. ========================= short test summary info ========================== - SKIP [3] /tmp/doc-exec-167/conftest.py:12: cannot run on platform linux + SKIP [3] /tmp/doc-exec-157/conftest.py:12: cannot run on platform linux =================== 1 passed, 3 skipped in 0.01 seconds ==================== @@ -487,8 +487,8 @@ $ py.test -m linux2 =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-157, inifile: collected 4 items test_plat.py s @@ -539,8 +539,8 @@ $ py.test -m interface --tb=short =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-157, inifile: collected 4 items test_module.py FF @@ -555,14 +555,14 @@ assert 0 E assert 0 ================== 2 tests deselected by "-m 'interface'" ================== - ================== 2 failed, 2 deselected in 0.01 seconds ================== + ================== 2 failed, 2 deselected in 0.02 seconds ================== or to select both "event" and "interface" tests:: $ py.test -m "interface or event" --tb=short =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-157, inifile: collected 4 items test_module.py FFF @@ -581,4 +581,4 @@ assert 0 E assert 0 ============= 1 tests deselected by "-m 'interface or event'" ============== - ================== 3 failed, 1 deselected in 0.01 seconds ================== + ================== 3 failed, 1 deselected in 0.02 seconds ================== diff -r 6ee4b4d70a9b81f525c6fe46eab1b2018de6f4b0 -r 3b7dc139bbc2993c9727f5439939352cc8f53deb doc/en/example/nonpython.txt --- a/doc/en/example/nonpython.txt +++ b/doc/en/example/nonpython.txt @@ -27,18 +27,18 @@ nonpython $ py.test test_simple.yml =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /home/hpk/p/pytest/doc/en, inifile: pytest.ini + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/sandbox/pytest/doc/en, inifile: pytest.ini collected 2 items - test_simple.yml F. + test_simple.yml .F ================================= FAILURES ================================= ______________________________ usecase: hello ______________________________ usecase execution failed spec failed: 'some': 'other' no further details known at this point. - ==================== 1 failed, 1 passed in 0.03 seconds ==================== + ==================== 1 failed, 1 passed in 0.19 seconds ==================== You get one dot for the passing ``sub1: sub1`` check and one failure. Obviously in the above ``conftest.py`` you'll want to implement a more @@ -57,30 +57,30 @@ nonpython $ py.test -v =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /home/hpk/p/pytest/doc/en, inifile: pytest.ini + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/sandbox/pytest/doc/en, inifile: pytest.ini collecting ... collected 2 items + test_simple.yml::ok PASSED test_simple.yml::hello FAILED - test_simple.yml::ok PASSED ================================= FAILURES ================================= ______________________________ usecase: hello ______________________________ usecase execution failed spec failed: 'some': 'other' no further details known at this point. - ==================== 1 failed, 1 passed in 0.03 seconds ==================== + ==================== 1 failed, 1 passed in 0.05 seconds ==================== While developing your custom test collection and execution it's also interesting to just look at the collection tree:: nonpython $ py.test --collect-only =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /home/hpk/p/pytest/doc/en, inifile: pytest.ini + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/sandbox/pytest/doc/en, inifile: pytest.ini collected 2 items + - - ============================= in 0.03 seconds ============================= + ============================= in 0.04 seconds ============================= diff -r 6ee4b4d70a9b81f525c6fe46eab1b2018de6f4b0 -r 3b7dc139bbc2993c9727f5439939352cc8f53deb doc/en/example/parametrize.txt --- a/doc/en/example/parametrize.txt +++ b/doc/en/example/parametrize.txt @@ -55,15 +55,15 @@ ....F ================================= FAILURES ================================= _____________________________ test_compute[4] ______________________________ - + param1 = 4 - + def test_compute(param1): > assert param1 < 4 E assert 4 < 4 - + test_compute.py:3: AssertionError - 1 failed, 4 passed in 0.01 seconds + 1 failed, 4 passed in 0.02 seconds As expected when running the full range of ``param1`` values we'll get an error on the last one. @@ -127,8 +127,8 @@ $ py.test test_time.py --collect-only =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-169, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-159, inifile: ============================= in 0.00 seconds ============================= ERROR: file not found: test_time.py @@ -171,21 +171,21 @@ $ py.test test_scenarios.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-169, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-159, inifile: collected 4 items test_scenarios.py .... - ========================= 4 passed in 0.01 seconds ========================= + ========================= 4 passed in 0.02 seconds ========================= If you just collect tests you'll also nicely see 'advanced' and 'basic' as variants for the test function:: $ py.test --collect-only test_scenarios.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-169, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-159, inifile: collected 4 items @@ -249,14 +249,14 @@ $ py.test test_backends.py --collect-only =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-169, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-159, inifile: collected 2 items - ============================= in 0.00 seconds ============================= + ============================= in 0.01 seconds ============================= And then when we run the test:: @@ -265,7 +265,7 @@ ================================= FAILURES ================================= _________________________ test_db_initialized[d2] __________________________ - db = + db = def test_db_initialized(db): # a dummy test @@ -319,16 +319,16 @@ $ py.test -q F.. ================================= FAILURES ================================= - ________________________ TestClass.test_equals[1-2] ________________________ + ________________________ TestClass.test_equals[2-1] ________________________ - self = , a = 1, b = 2 + self = , a = 1, b = 2 def test_equals(self, a, b): > assert a == b E assert 1 == 2 test_parametrize.py:18: AssertionError - 1 failed, 2 passed in 0.01 seconds + 1 failed, 2 passed in 0.02 seconds Indirect parametrization with multiple fixtures -------------------------------------------------------------- @@ -348,7 +348,7 @@ . $ py.test -rs -q multipython.py ........................... - 27 passed in 1.70 seconds + 27 passed in 4.14 seconds Indirect parametrization of optional implementations/imports -------------------------------------------------------------------- @@ -395,13 +395,13 @@ $ py.test -rs test_module.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-169, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-159, inifile: collected 2 items test_module.py .s ========================= short test summary info ========================== - SKIP [1] /tmp/doc-exec-169/conftest.py:10: could not import 'opt2' + SKIP [1] /tmp/doc-exec-159/conftest.py:10: could not import 'opt2' =================== 1 passed, 1 skipped in 0.01 seconds ==================== diff -r 6ee4b4d70a9b81f525c6fe46eab1b2018de6f4b0 -r 3b7dc139bbc2993c9727f5439939352cc8f53deb doc/en/example/pythoncollection.txt --- a/doc/en/example/pythoncollection.txt +++ b/doc/en/example/pythoncollection.txt @@ -43,8 +43,8 @@ $ py.test --collect-only =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-170, inifile: setup.cfg + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-160, inifile: setup.cfg collected 2 items @@ -89,8 +89,8 @@ . $ py.test --collect-only pythoncollection.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /home/hpk/p/pytest/doc/en, inifile: pytest.ini + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/sandbox/pytest/doc/en, inifile: pytest.ini collected 3 items @@ -143,11 +143,11 @@ $ py.test --collect-only =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-170, inifile: pytest.ini + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-160, inifile: pytest.ini collected 0 items - ============================= in 0.00 seconds ============================= + ============================= in 0.01 seconds ============================= If you run with a Python3 interpreter the moduled added through the conftest.py file will not be considered for test collection. diff -r 6ee4b4d70a9b81f525c6fe46eab1b2018de6f4b0 -r 3b7dc139bbc2993c9727f5439939352cc8f53deb doc/en/example/reportingdemo.txt --- a/doc/en/example/reportingdemo.txt +++ b/doc/en/example/reportingdemo.txt @@ -13,8 +13,8 @@ assertion $ py.test failure_demo.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /home/hpk/p/pytest/doc/en, inifile: pytest.ini + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/sandbox/pytest/doc/en, inifile: pytest.ini collected 42 items failure_demo.py FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF @@ -31,7 +31,7 @@ failure_demo.py:15: AssertionError _________________________ TestFailing.test_simple __________________________ - self = + self = def test_simple(self): def f(): @@ -41,13 +41,13 @@ > assert f() == g() E assert 42 == 43 - E + where 42 = .f at 0x2b186edd09d8>() - E + and 43 = .g at 0x2b186edd9950>() + E + where 42 = .f at 0x7f65f2315510>() + E + and 43 = .g at 0x7f65f2323510>() failure_demo.py:28: AssertionError ____________________ TestFailing.test_simple_multiline _____________________ - self = + self = def test_simple_multiline(self): otherfunc_multi( @@ -67,19 +67,19 @@ failure_demo.py:11: AssertionError ___________________________ TestFailing.test_not ___________________________ - self = + self = def test_not(self): def f(): return 42 > assert not f() E assert not 42 - E + where 42 = .f at 0x2b186edd99d8>() + E + where 42 = .f at 0x7f65f2323598>() failure_demo.py:38: AssertionError _________________ TestSpecialisedExplanations.test_eq_text _________________ - self = + self = def test_eq_text(self): > assert 'spam' == 'eggs' @@ -90,7 +90,7 @@ failure_demo.py:42: AssertionError _____________ TestSpecialisedExplanations.test_eq_similar_text _____________ - self = + self = def test_eq_similar_text(self): > assert 'foo 1 bar' == 'foo 2 bar' @@ -103,7 +103,7 @@ failure_demo.py:45: AssertionError ____________ TestSpecialisedExplanations.test_eq_multiline_text ____________ - self = + self = def test_eq_multiline_text(self): > assert 'foo\nspam\nbar' == 'foo\neggs\nbar' @@ -116,7 +116,7 @@ failure_demo.py:48: AssertionError ______________ TestSpecialisedExplanations.test_eq_long_text _______________ - self = + self = def test_eq_long_text(self): a = '1'*100 + 'a' + '2'*100 @@ -133,7 +133,7 @@ failure_demo.py:53: AssertionError _________ TestSpecialisedExplanations.test_eq_long_text_multiline __________ - self = + self = def test_eq_long_text_multiline(self): a = '1\n'*100 + 'a' + '2\n'*100 @@ -157,7 +157,7 @@ failure_demo.py:58: AssertionError _________________ TestSpecialisedExplanations.test_eq_list _________________ - self = + self = def test_eq_list(self): > assert [0, 1, 2] == [0, 1, 3] @@ -168,7 +168,7 @@ failure_demo.py:61: AssertionError ______________ TestSpecialisedExplanations.test_eq_list_long _______________ - self = + self = def test_eq_list_long(self): a = [0]*100 + [1] + [3]*100 @@ -181,7 +181,7 @@ failure_demo.py:66: AssertionError _________________ TestSpecialisedExplanations.test_eq_dict _________________ - self = + self = def test_eq_dict(self): > assert {'a': 0, 'b': 1, 'c': 0} == {'a': 0, 'b': 2, 'd': 0} @@ -198,7 +198,7 @@ failure_demo.py:69: AssertionError _________________ TestSpecialisedExplanations.test_eq_set __________________ - self = + self = def test_eq_set(self): > assert set([0, 10, 11, 12]) == set([0, 20, 21]) @@ -215,7 +215,7 @@ failure_demo.py:72: AssertionError _____________ TestSpecialisedExplanations.test_eq_longer_list ______________ - self = + self = def test_eq_longer_list(self): > assert [1,2] == [1,2,3] @@ -226,7 +226,7 @@ failure_demo.py:75: AssertionError _________________ TestSpecialisedExplanations.test_in_list _________________ - self = + self = def test_in_list(self): > assert 1 in [0, 2, 3, 4, 5] @@ -235,7 +235,7 @@ failure_demo.py:78: AssertionError __________ TestSpecialisedExplanations.test_not_in_text_multiline __________ - self = + self = def test_not_in_text_multiline(self): text = 'some multiline\ntext\nwhich\nincludes foo\nand a\ntail' @@ -253,7 +253,7 @@ failure_demo.py:82: AssertionError ___________ TestSpecialisedExplanations.test_not_in_text_single ____________ - self = + self = def test_not_in_text_single(self): text = 'single foo line' @@ -266,7 +266,7 @@ failure_demo.py:86: AssertionError _________ TestSpecialisedExplanations.test_not_in_text_single_long _________ - self = + self = def test_not_in_text_single_long(self): text = 'head ' * 50 + 'foo ' + 'tail ' * 20 @@ -279,7 +279,7 @@ failure_demo.py:90: AssertionError ______ TestSpecialisedExplanations.test_not_in_text_single_long_term _______ - self = + self = def test_not_in_text_single_long_term(self): text = 'head ' * 50 + 'f'*70 + 'tail ' * 20 @@ -298,7 +298,7 @@ i = Foo() > assert i.b == 2 E assert 1 == 2 - E + where 1 = .Foo object at 0x2b186ee8d4e0>.b + E + where 1 = .Foo object at 0x7f65f1c814e0>.b failure_demo.py:101: AssertionError _________________________ test_attribute_instance __________________________ @@ -308,8 +308,8 @@ b = 1 > assert Foo().b == 2 E assert 1 == 2 - E + where 1 = .Foo object at 0x2b186eea6240>.b - E + where .Foo object at 0x2b186eea6240> = .Foo'>() + E + where 1 = .Foo object at 0x7f65f1c7f7f0>.b + E + where .Foo object at 0x7f65f1c7f7f0> = .Foo'>() failure_demo.py:107: AssertionError __________________________ test_attribute_failure __________________________ @@ -325,7 +325,7 @@ failure_demo.py:116: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ - self = .Foo object at 0x2b186ed9a4e0> + self = .Foo object at 0x7f65f1c97dd8> def _get_b(self): > raise Exception('Failed to get attrib') @@ -341,15 +341,15 @@ b = 2 > assert Foo().b == Bar().b E assert 1 == 2 - E + where 1 = .Foo object at 0x2b186ee78630>.b - E + where .Foo object at 0x2b186ee78630> = .Foo'>() - E + and 2 = .Bar object at 0x2b186ee78358>.b - E + where .Bar object at 0x2b186ee78358> = .Bar'>() + E + where 1 = .Foo object at 0x7f65f1c9b630>.b + E + where .Foo object at 0x7f65f1c9b630> = .Foo'>() + E + and 2 = .Bar object at 0x7f65f1c9b2b0>.b + E + where .Bar object at 0x7f65f1c9b2b0> = .Bar'>() failure_demo.py:124: AssertionError __________________________ TestRaises.test_raises __________________________ - self = + self = def test_raises(self): s = 'qwe' @@ -361,10 +361,10 @@ > int(s) E ValueError: invalid literal for int() with base 10: 'qwe' - <0-codegen /home/hpk/p/pytest/.tox/regen/lib/python3.4/site-packages/_pytest/python.py:1075>:1: ValueError + <0-codegen /tmp/sandbox/pytest/.tox/regen/lib/python3.4/site-packages/_pytest/python.py:1075>:1: ValueError ______________________ TestRaises.test_raises_doesnt _______________________ - self = + self = def test_raises_doesnt(self): > raises(IOError, "int('3')") @@ -373,7 +373,7 @@ failure_demo.py:136: Failed __________________________ TestRaises.test_raise ___________________________ - self = + self = def test_raise(self): > raise ValueError("demo error") @@ -382,7 +382,7 @@ failure_demo.py:139: ValueError ________________________ TestRaises.test_tupleerror ________________________ - self = + self = def test_tupleerror(self): > a,b = [1] @@ -391,7 +391,7 @@ failure_demo.py:142: ValueError ______ TestRaises.test_reinterpret_fails_with_print_for_the_fun_of_it ______ - self = + self = def test_reinterpret_fails_with_print_for_the_fun_of_it(self): l = [1,2,3] @@ -404,7 +404,7 @@ l is [1, 2, 3] ________________________ TestRaises.test_some_error ________________________ - self = + self = def test_some_error(self): > if namenotexi: @@ -429,10 +429,10 @@ > assert 1 == 0 E assert 1 == 0 - <2-codegen 'abc-123' /home/hpk/p/pytest/doc/en/example/assertion/failure_demo.py:162>:2: AssertionError + <2-codegen 'abc-123' /tmp/sandbox/pytest/doc/en/example/assertion/failure_demo.py:162>:2: AssertionError ____________________ TestMoreErrors.test_complex_error _____________________ - self = + self = def test_complex_error(self): def f(): @@ -456,7 +456,7 @@ failure_demo.py:5: AssertionError ___________________ TestMoreErrors.test_z1_unpack_error ____________________ - self = + self = def test_z1_unpack_error(self): l = [] @@ -466,7 +466,7 @@ failure_demo.py:179: ValueError ____________________ TestMoreErrors.test_z2_type_error _____________________ - self = + self = def test_z2_type_error(self): l = 3 @@ -476,19 +476,19 @@ failure_demo.py:183: TypeError ______________________ TestMoreErrors.test_startswith ______________________ - self = + self = def test_startswith(self): s = "123" g = "456" > assert s.startswith(g) - E assert ('456') - E + where = '123'.startswith + E assert ('456') + E + where = '123'.startswith failure_demo.py:188: AssertionError __________________ TestMoreErrors.test_startswith_nested ___________________ - self = + self = def test_startswith_nested(self): def f(): @@ -496,15 +496,15 @@ def g(): return "456" > assert f().startswith(g()) - E assert ('456') - E + where = '123'.startswith - E + where '123' = .f at 0x2b186eea1510>() - E + and '456' = .g at 0x2b186eea1268>() + E assert ('456') + E + where = '123'.startswith + E + where '123' = .f at 0x7f65f1c32950>() + E + and '456' = .g at 0x7f65f1c32ea0>() failure_demo.py:195: AssertionError _____________________ TestMoreErrors.test_global_func ______________________ - self = + self = def test_global_func(self): > assert isinstance(globf(42), float) @@ -514,18 +514,18 @@ failure_demo.py:198: AssertionError _______________________ TestMoreErrors.test_instance _______________________ - self = + self = def test_instance(self): self.x = 6*7 > assert self.x != 42 E assert 42 != 42 - E + where 42 = .x + E + where 42 = .x failure_demo.py:202: AssertionError _______________________ TestMoreErrors.test_compare ________________________ - self = + self = def test_compare(self): > assert globf(10) < 5 @@ -535,7 +535,7 @@ failure_demo.py:205: AssertionError _____________________ TestMoreErrors.test_try_finally ______________________ - self = + self = def test_try_finally(self): x = 1 @@ -546,7 +546,7 @@ failure_demo.py:210: AssertionError ___________________ TestCustomAssertMsg.test_single_line ___________________ - self = + self = def test_single_line(self): class A: @@ -560,7 +560,7 @@ failure_demo.py:221: AssertionError ____________________ TestCustomAssertMsg.test_multiline ____________________ - self = + self = def test_multiline(self): class A: @@ -577,7 +577,7 @@ failure_demo.py:227: AssertionError ___________________ TestCustomAssertMsg.test_custom_repr ___________________ - self = + self = def test_custom_repr(self): class JSON: @@ -595,4 +595,4 @@ E + where 1 = This is JSON\n{\n 'foo': 'bar'\n}.a failure_demo.py:237: AssertionError - ======================== 42 failed in 0.22 seconds ========================= + ======================== 42 failed in 0.35 seconds ========================= diff -r 6ee4b4d70a9b81f525c6fe46eab1b2018de6f4b0 -r 3b7dc139bbc2993c9727f5439939352cc8f53deb doc/en/example/simple.txt --- a/doc/en/example/simple.txt +++ b/doc/en/example/simple.txt @@ -108,8 +108,8 @@ $ py.test =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-172, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-162, inifile: collected 0 items ============================= in 0.00 seconds ============================= @@ -153,13 +153,13 @@ $ py.test -rs # "-rs" means report details on the little 's' =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-172, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-162, inifile: collected 2 items test_module.py .s ========================= short test summary info ========================== - SKIP [1] /tmp/doc-exec-172/conftest.py:9: need --runslow option to run + SKIP [1] /tmp/doc-exec-162/conftest.py:9: need --runslow option to run =================== 1 passed, 1 skipped in 0.01 seconds ==================== @@ -167,8 +167,8 @@ $ py.test --runslow =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-172, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-162, inifile: collected 2 items test_module.py .. @@ -205,13 +205,13 @@ F ================================= FAILURES ================================= ______________________________ test_something ______________________________ - + def test_something(): > checkconfig(42) E Failed: not configured: 42 - + test_checkconfig.py:8: Failed - 1 failed in 0.01 seconds + 1 failed in 0.02 seconds Detect if running from within a pytest run -------------------------------------------------------------- @@ -259,8 +259,8 @@ $ py.test =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-172, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-162, inifile: project deps: mylib-1.1 collected 0 items @@ -283,8 +283,8 @@ $ py.test -v =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /tmp/doc-exec-172, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/doc-exec-162, inifile: info1: did you know that ... did you? collecting ... collected 0 items @@ -295,8 +295,8 @@ $ py.test =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-172, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-162, inifile: collected 0 items ============================= in 0.00 seconds ============================= @@ -328,8 +328,8 @@ $ py.test --durations=3 =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-172, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-162, inifile: collected 3 items test_some_are_slow.py ... @@ -390,8 +390,8 @@ $ py.test -rx =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-172, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-162, inifile: collected 4 items test_step.py .Fx. @@ -399,7 +399,7 @@ ================================= FAILURES ================================= ____________________ TestUserHandling.test_modification ____________________ - self = + self = def test_modification(self): > assert 0 @@ -409,7 +409,7 @@ ========================= short test summary info ========================== XFAIL test_step.py::TestUserHandling::()::test_deletion reason: previous test failed (test_modification) - ============== 1 failed, 2 passed, 1 xfailed in 0.01 seconds =============== + ============== 1 failed, 2 passed, 1 xfailed in 0.02 seconds =============== We'll see that ``test_deletion`` was not executed because ``test_modification`` failed. It is reported as an "expected failure". @@ -461,8 +461,8 @@ $ py.test =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-172, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-162, inifile: collected 7 items test_step.py .Fx. @@ -472,17 +472,17 @@ ================================== ERRORS ================================== _______________________ ERROR at setup of test_root ________________________ - file /tmp/doc-exec-172/b/test_error.py, line 1 + file /tmp/doc-exec-162/b/test_error.py, line 1 def test_root(db): # no db here, will error out fixture 'db' not found - available fixtures: pytestconfig, tmpdir, monkeypatch, capfd, recwarn, capsys + available fixtures: pytestconfig, capsys, recwarn, monkeypatch, tmpdir, capfd use 'py.test --fixtures [testpath]' for help on them. - /tmp/doc-exec-172/b/test_error.py:1 + /tmp/doc-exec-162/b/test_error.py:1 ================================= FAILURES ================================= ____________________ TestUserHandling.test_modification ____________________ - self = + self = def test_modification(self): > assert 0 @@ -491,25 +491,25 @@ test_step.py:9: AssertionError _________________________________ test_a1 __________________________________ - db = + db = def test_a1(db): > assert 0, db # to show value - E AssertionError: + E AssertionError: E assert 0 a/test_db.py:2: AssertionError _________________________________ test_a2 __________________________________ - db = + db = def test_a2(db): > assert 0, db # to show value - E AssertionError: + E AssertionError: E assert 0 a/test_db2.py:2: AssertionError - ========== 3 failed, 2 passed, 1 xfailed, 1 error in 0.03 seconds ========== + ========== 3 failed, 2 passed, 1 xfailed, 1 error in 0.05 seconds ========== The two test modules in the ``a`` directory see the same ``db`` fixture instance while the one test in the sister-directory ``b`` doesn't see it. We could of course @@ -564,8 +564,8 @@ $ py.test test_module.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-172, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-162, inifile: collected 2 items test_module.py FF @@ -573,7 +573,7 @@ ================================= FAILURES ================================= ________________________________ test_fail1 ________________________________ - tmpdir = local('/tmp/pytest-219/test_fail10') + tmpdir = local('/tmp/pytest-22/test_fail10') def test_fail1(tmpdir): > assert 0 @@ -587,12 +587,12 @@ E assert 0 test_module.py:4: AssertionError - ========================= 2 failed in 0.01 seconds ========================= + ========================= 2 failed in 0.02 seconds ========================= you will have a "failures" file which contains the failing test ids:: $ cat failures - test_module.py::test_fail1 (/tmp/pytest-219/test_fail10) + test_module.py::test_fail1 (/tmp/pytest-22/test_fail10) test_module.py::test_fail2 Making test result information available in fixtures @@ -655,8 +655,8 @@ $ py.test -s test_module.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-172, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-162, inifile: collected 3 items test_module.py Esetting up a test failed! test_module.py::test_setup_fails @@ -689,7 +689,7 @@ E assert 0 test_module.py:15: AssertionError - ==================== 2 failed, 1 error in 0.01 seconds ===================== + ==================== 2 failed, 1 error in 0.02 seconds ===================== You'll see that the fixture finalizers could use the precise reporting information. diff -r 6ee4b4d70a9b81f525c6fe46eab1b2018de6f4b0 -r 3b7dc139bbc2993c9727f5439939352cc8f53deb doc/en/example/special.txt --- a/doc/en/example/special.txt +++ b/doc/en/example/special.txt @@ -69,4 +69,4 @@ .test other .test_unit1 method called . - 4 passed in 0.04 seconds + 4 passed in 0.03 seconds diff -r 6ee4b4d70a9b81f525c6fe46eab1b2018de6f4b0 -r 3b7dc139bbc2993c9727f5439939352cc8f53deb doc/en/fixture.txt --- a/doc/en/fixture.txt +++ b/doc/en/fixture.txt @@ -75,8 +75,8 @@ $ py.test test_smtpsimple.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-109, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-98, inifile: collected 1 items test_smtpsimple.py F @@ -84,7 +84,7 @@ ================================= FAILURES ================================= ________________________________ test_ehlo _________________________________ - smtp = + smtp = def test_ehlo(smtp): response, msg = smtp.ehlo() @@ -93,7 +93,7 @@ E assert 0 test_smtpsimple.py:11: AssertionError - ========================= 1 failed in 0.17 seconds ========================= + ========================= 1 failed in 1.07 seconds ========================= In the failure traceback we see that the test function was called with a ``smtp`` argument, the ``smtplib.SMTP()`` instance created by the fixture @@ -193,8 +193,8 @@ $ py.test test_module.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-109, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-98, inifile: collected 2 items test_module.py FF @@ -202,7 +202,7 @@ ================================= FAILURES ================================= ________________________________ test_ehlo _________________________________ - smtp = + smtp = def test_ehlo(smtp): response = smtp.ehlo() @@ -213,7 +213,7 @@ test_module.py:5: TypeError ________________________________ test_noop _________________________________ - smtp = + smtp = def test_noop(smtp): response = smtp.noop() @@ -222,7 +222,7 @@ E assert 0 test_module.py:11: AssertionError - ========================= 2 failed in 0.20 seconds ========================= + ========================= 2 failed in 0.82 seconds ========================= You see the two ``assert 0`` failing and more importantly you can also see that the same (module-scoped) ``smtp`` object was passed into the two @@ -270,7 +270,7 @@ $ py.test -s -q --tb=no FFteardown smtp - 2 failed in 0.25 seconds + 2 failed in 1.44 seconds We see that the ``smtp`` instance is finalized after the two tests finished execution. Note that if we decorated our fixture @@ -311,7 +311,7 @@ $ py.test -s -q --tb=no FF - 2 failed in 0.17 seconds + 2 failed in 0.62 seconds Let's quickly create another test module that actually sets the server URL in its module namespace:: @@ -379,7 +379,7 @@ ================================= FAILURES ================================= __________________________ test_ehlo[merlinux.eu] __________________________ - smtp = + smtp = def test_ehlo(smtp): response = smtp.ehlo() @@ -390,7 +390,7 @@ test_module.py:5: TypeError __________________________ test_noop[merlinux.eu] __________________________ - smtp = + smtp = def test_noop(smtp): response = smtp.noop() @@ -401,7 +401,7 @@ test_module.py:11: AssertionError ________________________ test_ehlo[mail.python.org] ________________________ - smtp = + smtp = def test_ehlo(smtp): response = smtp.ehlo() @@ -411,10 +411,10 @@ test_module.py:5: TypeError -------------------------- Captured stdout setup --------------------------- - finalizing + finalizing ________________________ test_noop[mail.python.org] ________________________ - smtp = + smtp = def test_noop(smtp): response = smtp.noop() @@ -423,7 +423,7 @@ E assert 0 test_module.py:11: AssertionError - 4 failed in 6.70 seconds + 4 failed in 1.75 seconds We see that our two test functions each ran twice, against the different ``smtp`` instances. Note also, that with the ``mail.python.org`` @@ -474,8 +474,8 @@ $ py.test --collect-only =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-109, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-98, inifile: collected 6 items @@ -486,7 +486,7 @@ - ============================= in 0.01 seconds ============================= + ============================= in 0.02 seconds ============================= .. _`interdependent fixtures`: @@ -520,14 +520,14 @@ $ py.test -v test_appsetup.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /tmp/doc-exec-109, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/doc-exec-98, inifile: collecting ... collected 2 items test_appsetup.py::test_smtp_exists[merlinux.eu] PASSED test_appsetup.py::test_smtp_exists[mail.python.org] PASSED - ========================= 2 passed in 6.53 seconds ========================= + ========================= 2 passed in 1.09 seconds ========================= Due to the parametrization of ``smtp`` the test will run twice with two different ``App`` instances and respective smtp servers. There is no @@ -585,8 +585,8 @@ $ py.test -v -s test_module.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /tmp/doc-exec-109, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/doc-exec-98, inifile: collecting ... collected 8 items test_module.py::test_0[1] test0 1 @@ -608,7 +608,7 @@ test_module.py::test_2[2-mod2] test2 2 mod2 PASSED - ========================= 8 passed in 0.01 seconds ========================= + ========================= 8 passed in 0.02 seconds ========================= You can see that the parametrized module-scoped ``modarg`` resource caused an ordering of test execution that lead to the fewest possible "active" resources. The finalizer for the ``mod1`` parametrized resource was executed diff -r 6ee4b4d70a9b81f525c6fe46eab1b2018de6f4b0 -r 3b7dc139bbc2993c9727f5439939352cc8f53deb doc/en/getting-started.txt --- a/doc/en/getting-started.txt +++ b/doc/en/getting-started.txt @@ -27,7 +27,7 @@ To check your installation has installed the correct version:: $ py.test --version - This is pytest version 2.7.0, imported from /home/hpk/p/pytest/.tox/regen/lib/python3.4/site-packages/pytest.py + This is pytest version 2.7.1, imported from /tmp/sandbox/pytest/.tox/regen/lib/python3.4/site-packages/pytest.py If you get an error checkout :ref:`installation issues`. @@ -49,8 +49,8 @@ $ py.test =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-112, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-101, inifile: collected 1 items test_sample.py F @@ -98,7 +98,7 @@ $ py.test -q test_sysexit.py . - 1 passed in 0.00 seconds + 1 passed in 0.01 seconds .. todo:: For further ways to assert exceptions see the `raises` @@ -128,7 +128,7 @@ ================================= FAILURES ================================= ____________________________ TestClass.test_two ____________________________ - self = + self = def test_two(self): x = "hello" @@ -164,7 +164,7 @@ ================================= FAILURES ================================= _____________________________ test_needsfiles ______________________________ - tmpdir = local('/tmp/pytest-215/test_needsfiles0') + tmpdir = local('/tmp/pytest-18/test_needsfiles0') def test_needsfiles(tmpdir): print (tmpdir) @@ -173,8 +173,8 @@ test_tmpdir.py:3: AssertionError --------------------------- Captured stdout call --------------------------- - /tmp/pytest-215/test_needsfiles0 - 1 failed in 0.01 seconds + /tmp/pytest-18/test_needsfiles0 + 1 failed in 0.05 seconds Before the test runs, a unique-per-test-invocation temporary directory was created. More info at :ref:`tmpdir handling`. diff -r 6ee4b4d70a9b81f525c6fe46eab1b2018de6f4b0 -r 3b7dc139bbc2993c9727f5439939352cc8f53deb doc/en/parametrize.txt --- a/doc/en/parametrize.txt +++ b/doc/en/parametrize.txt @@ -53,8 +53,8 @@ $ py.test =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-120, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-109, inifile: collected 3 items test_expectation.py ..F @@ -75,7 +75,7 @@ E + where 54 = eval('6*9') test_expectation.py:8: AssertionError - ==================== 1 failed, 2 passed in 0.01 seconds ==================== + ==================== 1 failed, 2 passed in 0.02 seconds ==================== As designed in this example, only one pair of input/output values fails the simple test function. And as usual with test function arguments, @@ -101,13 +101,13 @@ $ py.test =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-120, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-109, inifile: collected 3 items test_expectation.py ..x - =================== 2 passed, 1 xfailed in 0.01 seconds ==================== + =================== 2 passed, 1 xfailed in 0.02 seconds ==================== The one parameter set which caused a failure previously now shows up as an "xfailed (expected to fail)" test. @@ -172,8 +172,8 @@ def test_valid_string(stringinput): > assert stringinput.isalpha() - E assert () - E + where = '!'.isalpha + E assert () + E + where = '!'.isalpha test_strings.py:3: AssertionError 1 failed in 0.01 seconds @@ -187,8 +187,8 @@ $ py.test -q -rs test_strings.py s ========================= short test summary info ========================== - SKIP [1] /home/hpk/p/pytest/.tox/regen/lib/python3.4/site-packages/_pytest/python.py:1185: got empty parameter set, function test_valid_string at /tmp/doc-exec-120/test_strings.py:1 - 1 skipped in 0.00 seconds + SKIP [1] /tmp/sandbox/pytest/.tox/regen/lib/python3.4/site-packages/_pytest/python.py:1185: got empty parameter set, function test_valid_string at /tmp/doc-exec-109/test_strings.py:1 + 1 skipped in 0.01 seconds For further examples, you might want to look at :ref:`more parametrization examples `. diff -r 6ee4b4d70a9b81f525c6fe46eab1b2018de6f4b0 -r 3b7dc139bbc2993c9727f5439939352cc8f53deb doc/en/skipping.txt --- a/doc/en/skipping.txt +++ b/doc/en/skipping.txt @@ -164,8 +164,8 @@ example $ py.test -rx xfail_demo.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /home/hpk/p/pytest/doc/en, inifile: pytest.ini + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/sandbox/pytest/doc/en, inifile: pytest.ini collected 7 items xfail_demo.py xxxxxxx @@ -183,7 +183,7 @@ reason: reason XFAIL xfail_demo.py::test_hello7 - ======================== 7 xfailed in 0.05 seconds ========================= + ======================== 7 xfailed in 0.06 seconds ========================= .. _`skip/xfail with parametrize`: diff -r 6ee4b4d70a9b81f525c6fe46eab1b2018de6f4b0 -r 3b7dc139bbc2993c9727f5439939352cc8f53deb doc/en/tmpdir.txt --- a/doc/en/tmpdir.txt +++ b/doc/en/tmpdir.txt @@ -29,8 +29,8 @@ $ py.test test_tmpdir.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-129, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-118, inifile: collected 1 items test_tmpdir.py F @@ -38,7 +38,7 @@ ================================= FAILURES ================================= _____________________________ test_create_file _____________________________ - tmpdir = local('/tmp/pytest-216/test_create_file0') + tmpdir = local('/tmp/pytest-19/test_create_file0') def test_create_file(tmpdir): p = tmpdir.mkdir("sub").join("hello.txt") @@ -49,7 +49,7 @@ E assert 0 test_tmpdir.py:7: AssertionError - ========================= 1 failed in 0.01 seconds ========================= + ========================= 1 failed in 0.04 seconds ========================= .. _`base temporary directory`: diff -r 6ee4b4d70a9b81f525c6fe46eab1b2018de6f4b0 -r 3b7dc139bbc2993c9727f5439939352cc8f53deb doc/en/unittest.txt --- a/doc/en/unittest.txt +++ b/doc/en/unittest.txt @@ -88,8 +88,8 @@ $ py.test test_unittest_db.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-130, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-119, inifile: collected 2 items test_unittest_db.py FF @@ -102,7 +102,7 @@ def test_method1(self): assert hasattr(self, "db") > assert 0, self.db # fail for demo purposes - E AssertionError: .DummyDB object at 0x2ab102a4bac8> + E AssertionError: .DummyDB object at 0x7f97382031d0> E assert 0 test_unittest_db.py:9: AssertionError @@ -112,7 +112,7 @@ def test_method2(self): > assert 0, self.db # fail for demo purposes - E AssertionError: .DummyDB object at 0x2ab102a4bac8> + E AssertionError: .DummyDB object at 0x7f97382031d0> E assert 0 test_unittest_db.py:12: AssertionError @@ -163,7 +163,7 @@ $ py.test -q test_unittest_cleandir.py . - 1 passed in 0.04 seconds + 1 passed in 0.25 seconds ... gives us one passed test because the ``initdir`` fixture function was executed ahead of the ``test_method``. diff -r 6ee4b4d70a9b81f525c6fe46eab1b2018de6f4b0 -r 3b7dc139bbc2993c9727f5439939352cc8f53deb doc/en/yieldfixture.txt --- a/doc/en/yieldfixture.txt +++ b/doc/en/yieldfixture.txt @@ -43,7 +43,7 @@ test called .teardown after yield - 1 passed in 0.00 seconds + 1 passed in 0.01 seconds We can also seamlessly use the new syntax with ``with`` statements. Let's simplify the above ``passwd`` fixture:: https://bitbucket.org/pytest-dev/pytest/commits/1a913c55c66d/ Changeset: 1a913c55c66d User: flub Date: 2015-05-31 19:27:13+00:00 Summary: Merged in flub/pytest (pull request #297) Merge 2.7.1 release changes into default Affected #: 24 files diff -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac -r 1a913c55c66d6841c113df2247942433e693ce0f .hgtags --- a/.hgtags +++ b/.hgtags @@ -74,3 +74,4 @@ 2967aa416a4f3cdb65fc75073a2a148e1f372742 2.6.3 f03b6de8325f5b6c35cea7c3de092f134ea8ef07 2.6.4 7ed701fa2fb554bfc0618d447dfec700cc697407 2.7.0 +edc1d080bab5a970da8f6c776be50768829a7b09 2.7.1 diff -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac -r 1a913c55c66d6841c113df2247942433e693ce0f CHANGELOG --- a/CHANGELOG +++ b/CHANGELOG @@ -52,7 +52,7 @@ one will also have a "reprec" attribute with the recorded events/reports. -2.7.1.dev (compared to 2.7.0) +2.7.1 (compared to 2.7.0) ----------------------------- - fix issue731: do not get confused by the braces which may be present diff -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac -r 1a913c55c66d6841c113df2247942433e693ce0f HOWTORELEASE.rst --- a/HOWTORELEASE.rst +++ b/HOWTORELEASE.rst @@ -2,7 +2,7 @@ How to release pytest (draft) -------------------------------------------- -1. bump version numbers in pytest/__init__.py (setup.py reads it) +1. bump version numbers in _pytest/__init__.py (setup.py reads it) 2. check and finalize CHANGELOG @@ -11,7 +11,7 @@ 4. use devpi for uploading a release tarball to a staging area: - ``devpi use https://devpi.net/USER/dev`` - - ``devpi upload`` + - ``devpi upload --formats sdist,bdist_wheel`` 5. run from multiple machines: - ``devpi use https://devpi.net/USER/dev`` @@ -25,22 +25,33 @@ which is ok (tox does not support skipping on per-platform basis yet). -7. Regenerate the docs using the toplevel makefile:: - make docs +7. Regenerate the docs examples using tox:: + # Create and activate a virtualenv with regendoc installed + # (currently needs revision 4a9ec1035734) + tox -e regen -8. Upload the docs using the toplevel makefile:: - make upload-docs +8. Build the docs, you need a virtualenv with, py and sphinx + installed:: + cd docs/en + make html + +9. Tag the release:: + hg tag VERSION + +10. Upload the docs using docs/en/Makefile:: + cd docs/en + make install # or "installall" if you have LaTeX installed This requires ssh-login permission on pytest.org because it uses rsync. Note that the "install" target of doc/en/Makefile defines where the rsync goes to, typically to the "latest" section of pytest.org. -9. publish to pypi "devpi push pytest-2.6.2 pypi:NAME" where NAME +11. publish to pypi "devpi push pytest-VERSION pypi:NAME" where NAME is the name of pypi.python.org as configured in your ~/.pypirc file -- it's the same you would use with "setup.py upload -r NAME" -10. send release announcement to mailing lists: +12. send release announcement to mailing lists: pytest-dev testing-in-python diff -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac -r 1a913c55c66d6841c113df2247942433e693ce0f doc/en/adopt.txt --- a/doc/en/adopt.txt +++ b/doc/en/adopt.txt @@ -11,7 +11,6 @@ .. _`issue tracker`: https://bitbucket.org/pytest-dev/pytest/issue/676/adopt-pytest-month-2015 .. _`pytest-dev mailing list`: https://mail.python.org/mailman/listinfo/pytest-dev -.. _``: The ideal pytest helper diff -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac -r 1a913c55c66d6841c113df2247942433e693ce0f doc/en/announce/release-2.7.0.txt --- a/doc/en/announce/release-2.7.0.txt +++ b/doc/en/announce/release-2.7.0.txt @@ -89,7 +89,7 @@ it from the "decorator" case. Thanks Tom Viner. - "python_classes" and "python_functions" options now support glob-patterns - for test discovery, as discussed in issue600. Thanks Ldiary Translations. + for test discovery, as discussed in issue600. Thanks Ldiary Translations. - allow to override parametrized fixtures with non-parametrized ones and vice versa (bubenkoff). diff -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac -r 1a913c55c66d6841c113df2247942433e693ce0f doc/en/assert.txt --- a/doc/en/assert.txt +++ b/doc/en/assert.txt @@ -26,8 +26,8 @@ $ py.test test_assert1.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-98, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-87, inifile: collected 1 items test_assert1.py F @@ -136,8 +136,8 @@ $ py.test test_assert2.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-98, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-87, inifile: collected 1 items test_assert2.py F @@ -213,7 +213,7 @@ E vals: 1 != 2 test_foocompare.py:8: AssertionError - 1 failed in 0.00 seconds + 1 failed in 0.01 seconds .. _assert-details: .. _`assert introspection`: diff -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac -r 1a913c55c66d6841c113df2247942433e693ce0f doc/en/capture.txt --- a/doc/en/capture.txt +++ b/doc/en/capture.txt @@ -64,8 +64,8 @@ $ py.test =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-101, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-90, inifile: collected 2 items test_module.py .F @@ -79,7 +79,7 @@ test_module.py:9: AssertionError -------------------------- Captured stdout setup --------------------------- - setting up + setting up ==================== 1 failed, 1 passed in 0.01 seconds ==================== Accessing captured output from a test function diff -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac -r 1a913c55c66d6841c113df2247942433e693ce0f doc/en/doctest.txt --- a/doc/en/doctest.txt +++ b/doc/en/doctest.txt @@ -44,13 +44,13 @@ $ py.test =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-107, inifile: pytest.ini + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-96, inifile: pytest.ini collected 1 items mymodule.py . - ========================= 1 passed in 0.05 seconds ========================= + ========================= 1 passed in 0.06 seconds ========================= It is possible to use fixtures using the ``getfixture`` helper:: diff -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac -r 1a913c55c66d6841c113df2247942433e693ce0f doc/en/example/markers.txt --- a/doc/en/example/markers.txt +++ b/doc/en/example/markers.txt @@ -31,8 +31,8 @@ $ py.test -v -m webtest =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/doc-exec-157, inifile: collecting ... collected 4 items test_server.py::test_send_http PASSED @@ -44,8 +44,8 @@ $ py.test -v -m "not webtest" =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/doc-exec-157, inifile: collecting ... collected 4 items test_server.py::test_something_quick PASSED @@ -64,8 +64,8 @@ $ py.test -v test_server.py::TestClass::test_method =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/doc-exec-157, inifile: collecting ... collected 5 items test_server.py::TestClass::test_method PASSED @@ -76,8 +76,8 @@ $ py.test -v test_server.py::TestClass =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/doc-exec-157, inifile: collecting ... collected 4 items test_server.py::TestClass::test_method PASSED @@ -88,8 +88,8 @@ $ py.test -v test_server.py::TestClass test_server.py::test_send_http =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/doc-exec-157, inifile: collecting ... collected 8 items test_server.py::TestClass::test_method PASSED @@ -125,8 +125,8 @@ $ py.test -v -k http # running with the above defined example module =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/doc-exec-157, inifile: collecting ... collected 4 items test_server.py::test_send_http PASSED @@ -138,8 +138,8 @@ $ py.test -k "not send_http" -v =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/doc-exec-157, inifile: collecting ... collected 4 items test_server.py::test_something_quick PASSED @@ -153,8 +153,8 @@ $ py.test -k "http or quick" -v =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/doc-exec-157, inifile: collecting ... collected 4 items test_server.py::test_send_http PASSED @@ -342,25 +342,25 @@ $ py.test -E stage2 =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-157, inifile: collected 1 items test_someenv.py s - ======================== 1 skipped in 0.00 seconds ========================= + ======================== 1 skipped in 0.01 seconds ========================= and here is one that specifies exactly the environment needed:: $ py.test -E stage1 =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-157, inifile: collected 1 items test_someenv.py . - ========================= 1 passed in 0.00 seconds ========================= + ========================= 1 passed in 0.01 seconds ========================= The ``--markers`` option always gives you a list of available markers:: @@ -473,13 +473,13 @@ $ py.test -rs # this option reports skip reasons =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-157, inifile: collected 4 items test_plat.py sss. ========================= short test summary info ========================== - SKIP [3] /tmp/doc-exec-167/conftest.py:12: cannot run on platform linux + SKIP [3] /tmp/doc-exec-157/conftest.py:12: cannot run on platform linux =================== 1 passed, 3 skipped in 0.01 seconds ==================== @@ -487,8 +487,8 @@ $ py.test -m linux2 =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-157, inifile: collected 4 items test_plat.py s @@ -539,8 +539,8 @@ $ py.test -m interface --tb=short =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-157, inifile: collected 4 items test_module.py FF @@ -555,14 +555,14 @@ assert 0 E assert 0 ================== 2 tests deselected by "-m 'interface'" ================== - ================== 2 failed, 2 deselected in 0.01 seconds ================== + ================== 2 failed, 2 deselected in 0.02 seconds ================== or to select both "event" and "interface" tests:: $ py.test -m "interface or event" --tb=short =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-167, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-157, inifile: collected 4 items test_module.py FFF @@ -581,4 +581,4 @@ assert 0 E assert 0 ============= 1 tests deselected by "-m 'interface or event'" ============== - ================== 3 failed, 1 deselected in 0.01 seconds ================== + ================== 3 failed, 1 deselected in 0.02 seconds ================== diff -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac -r 1a913c55c66d6841c113df2247942433e693ce0f doc/en/example/nonpython.txt --- a/doc/en/example/nonpython.txt +++ b/doc/en/example/nonpython.txt @@ -27,18 +27,18 @@ nonpython $ py.test test_simple.yml =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /home/hpk/p/pytest/doc/en, inifile: pytest.ini + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/sandbox/pytest/doc/en, inifile: pytest.ini collected 2 items - test_simple.yml F. + test_simple.yml .F ================================= FAILURES ================================= ______________________________ usecase: hello ______________________________ usecase execution failed spec failed: 'some': 'other' no further details known at this point. - ==================== 1 failed, 1 passed in 0.03 seconds ==================== + ==================== 1 failed, 1 passed in 0.19 seconds ==================== You get one dot for the passing ``sub1: sub1`` check and one failure. Obviously in the above ``conftest.py`` you'll want to implement a more @@ -57,30 +57,30 @@ nonpython $ py.test -v =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /home/hpk/p/pytest/doc/en, inifile: pytest.ini + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/sandbox/pytest/doc/en, inifile: pytest.ini collecting ... collected 2 items + test_simple.yml::ok PASSED test_simple.yml::hello FAILED - test_simple.yml::ok PASSED ================================= FAILURES ================================= ______________________________ usecase: hello ______________________________ usecase execution failed spec failed: 'some': 'other' no further details known at this point. - ==================== 1 failed, 1 passed in 0.03 seconds ==================== + ==================== 1 failed, 1 passed in 0.05 seconds ==================== While developing your custom test collection and execution it's also interesting to just look at the collection tree:: nonpython $ py.test --collect-only =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /home/hpk/p/pytest/doc/en, inifile: pytest.ini + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/sandbox/pytest/doc/en, inifile: pytest.ini collected 2 items + - - ============================= in 0.03 seconds ============================= + ============================= in 0.04 seconds ============================= diff -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac -r 1a913c55c66d6841c113df2247942433e693ce0f doc/en/example/parametrize.txt --- a/doc/en/example/parametrize.txt +++ b/doc/en/example/parametrize.txt @@ -55,15 +55,15 @@ ....F ================================= FAILURES ================================= _____________________________ test_compute[4] ______________________________ - + param1 = 4 - + def test_compute(param1): > assert param1 < 4 E assert 4 < 4 - + test_compute.py:3: AssertionError - 1 failed, 4 passed in 0.01 seconds + 1 failed, 4 passed in 0.02 seconds As expected when running the full range of ``param1`` values we'll get an error on the last one. @@ -127,8 +127,8 @@ $ py.test test_time.py --collect-only =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-169, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-159, inifile: ============================= in 0.00 seconds ============================= ERROR: file not found: test_time.py @@ -171,21 +171,21 @@ $ py.test test_scenarios.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-169, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-159, inifile: collected 4 items test_scenarios.py .... - ========================= 4 passed in 0.01 seconds ========================= + ========================= 4 passed in 0.02 seconds ========================= If you just collect tests you'll also nicely see 'advanced' and 'basic' as variants for the test function:: $ py.test --collect-only test_scenarios.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-169, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-159, inifile: collected 4 items @@ -249,14 +249,14 @@ $ py.test test_backends.py --collect-only =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-169, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-159, inifile: collected 2 items - ============================= in 0.00 seconds ============================= + ============================= in 0.01 seconds ============================= And then when we run the test:: @@ -265,7 +265,7 @@ ================================= FAILURES ================================= _________________________ test_db_initialized[d2] __________________________ - db = + db = def test_db_initialized(db): # a dummy test @@ -319,16 +319,16 @@ $ py.test -q F.. ================================= FAILURES ================================= - ________________________ TestClass.test_equals[1-2] ________________________ + ________________________ TestClass.test_equals[2-1] ________________________ - self = , a = 1, b = 2 + self = , a = 1, b = 2 def test_equals(self, a, b): > assert a == b E assert 1 == 2 test_parametrize.py:18: AssertionError - 1 failed, 2 passed in 0.01 seconds + 1 failed, 2 passed in 0.02 seconds Indirect parametrization with multiple fixtures -------------------------------------------------------------- @@ -348,7 +348,7 @@ . $ py.test -rs -q multipython.py ........................... - 27 passed in 1.70 seconds + 27 passed in 4.14 seconds Indirect parametrization of optional implementations/imports -------------------------------------------------------------------- @@ -395,13 +395,13 @@ $ py.test -rs test_module.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-169, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-159, inifile: collected 2 items test_module.py .s ========================= short test summary info ========================== - SKIP [1] /tmp/doc-exec-169/conftest.py:10: could not import 'opt2' + SKIP [1] /tmp/doc-exec-159/conftest.py:10: could not import 'opt2' =================== 1 passed, 1 skipped in 0.01 seconds ==================== diff -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac -r 1a913c55c66d6841c113df2247942433e693ce0f doc/en/example/pythoncollection.txt --- a/doc/en/example/pythoncollection.txt +++ b/doc/en/example/pythoncollection.txt @@ -43,8 +43,8 @@ $ py.test --collect-only =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-170, inifile: setup.cfg + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-160, inifile: setup.cfg collected 2 items @@ -89,8 +89,8 @@ . $ py.test --collect-only pythoncollection.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /home/hpk/p/pytest/doc/en, inifile: pytest.ini + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/sandbox/pytest/doc/en, inifile: pytest.ini collected 3 items @@ -143,11 +143,11 @@ $ py.test --collect-only =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-170, inifile: pytest.ini + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-160, inifile: pytest.ini collected 0 items - ============================= in 0.00 seconds ============================= + ============================= in 0.01 seconds ============================= If you run with a Python3 interpreter the moduled added through the conftest.py file will not be considered for test collection. diff -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac -r 1a913c55c66d6841c113df2247942433e693ce0f doc/en/example/reportingdemo.txt --- a/doc/en/example/reportingdemo.txt +++ b/doc/en/example/reportingdemo.txt @@ -13,8 +13,8 @@ assertion $ py.test failure_demo.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /home/hpk/p/pytest/doc/en, inifile: pytest.ini + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/sandbox/pytest/doc/en, inifile: pytest.ini collected 42 items failure_demo.py FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF @@ -31,7 +31,7 @@ failure_demo.py:15: AssertionError _________________________ TestFailing.test_simple __________________________ - self = + self = def test_simple(self): def f(): @@ -41,13 +41,13 @@ > assert f() == g() E assert 42 == 43 - E + where 42 = .f at 0x2b186edd09d8>() - E + and 43 = .g at 0x2b186edd9950>() + E + where 42 = .f at 0x7f65f2315510>() + E + and 43 = .g at 0x7f65f2323510>() failure_demo.py:28: AssertionError ____________________ TestFailing.test_simple_multiline _____________________ - self = + self = def test_simple_multiline(self): otherfunc_multi( @@ -67,19 +67,19 @@ failure_demo.py:11: AssertionError ___________________________ TestFailing.test_not ___________________________ - self = + self = def test_not(self): def f(): return 42 > assert not f() E assert not 42 - E + where 42 = .f at 0x2b186edd99d8>() + E + where 42 = .f at 0x7f65f2323598>() failure_demo.py:38: AssertionError _________________ TestSpecialisedExplanations.test_eq_text _________________ - self = + self = def test_eq_text(self): > assert 'spam' == 'eggs' @@ -90,7 +90,7 @@ failure_demo.py:42: AssertionError _____________ TestSpecialisedExplanations.test_eq_similar_text _____________ - self = + self = def test_eq_similar_text(self): > assert 'foo 1 bar' == 'foo 2 bar' @@ -103,7 +103,7 @@ failure_demo.py:45: AssertionError ____________ TestSpecialisedExplanations.test_eq_multiline_text ____________ - self = + self = def test_eq_multiline_text(self): > assert 'foo\nspam\nbar' == 'foo\neggs\nbar' @@ -116,7 +116,7 @@ failure_demo.py:48: AssertionError ______________ TestSpecialisedExplanations.test_eq_long_text _______________ - self = + self = def test_eq_long_text(self): a = '1'*100 + 'a' + '2'*100 @@ -133,7 +133,7 @@ failure_demo.py:53: AssertionError _________ TestSpecialisedExplanations.test_eq_long_text_multiline __________ - self = + self = def test_eq_long_text_multiline(self): a = '1\n'*100 + 'a' + '2\n'*100 @@ -157,7 +157,7 @@ failure_demo.py:58: AssertionError _________________ TestSpecialisedExplanations.test_eq_list _________________ - self = + self = def test_eq_list(self): > assert [0, 1, 2] == [0, 1, 3] @@ -168,7 +168,7 @@ failure_demo.py:61: AssertionError ______________ TestSpecialisedExplanations.test_eq_list_long _______________ - self = + self = def test_eq_list_long(self): a = [0]*100 + [1] + [3]*100 @@ -181,7 +181,7 @@ failure_demo.py:66: AssertionError _________________ TestSpecialisedExplanations.test_eq_dict _________________ - self = + self = def test_eq_dict(self): > assert {'a': 0, 'b': 1, 'c': 0} == {'a': 0, 'b': 2, 'd': 0} @@ -198,7 +198,7 @@ failure_demo.py:69: AssertionError _________________ TestSpecialisedExplanations.test_eq_set __________________ - self = + self = def test_eq_set(self): > assert set([0, 10, 11, 12]) == set([0, 20, 21]) @@ -215,7 +215,7 @@ failure_demo.py:72: AssertionError _____________ TestSpecialisedExplanations.test_eq_longer_list ______________ - self = + self = def test_eq_longer_list(self): > assert [1,2] == [1,2,3] @@ -226,7 +226,7 @@ failure_demo.py:75: AssertionError _________________ TestSpecialisedExplanations.test_in_list _________________ - self = + self = def test_in_list(self): > assert 1 in [0, 2, 3, 4, 5] @@ -235,7 +235,7 @@ failure_demo.py:78: AssertionError __________ TestSpecialisedExplanations.test_not_in_text_multiline __________ - self = + self = def test_not_in_text_multiline(self): text = 'some multiline\ntext\nwhich\nincludes foo\nand a\ntail' @@ -253,7 +253,7 @@ failure_demo.py:82: AssertionError ___________ TestSpecialisedExplanations.test_not_in_text_single ____________ - self = + self = def test_not_in_text_single(self): text = 'single foo line' @@ -266,7 +266,7 @@ failure_demo.py:86: AssertionError _________ TestSpecialisedExplanations.test_not_in_text_single_long _________ - self = + self = def test_not_in_text_single_long(self): text = 'head ' * 50 + 'foo ' + 'tail ' * 20 @@ -279,7 +279,7 @@ failure_demo.py:90: AssertionError ______ TestSpecialisedExplanations.test_not_in_text_single_long_term _______ - self = + self = def test_not_in_text_single_long_term(self): text = 'head ' * 50 + 'f'*70 + 'tail ' * 20 @@ -298,7 +298,7 @@ i = Foo() > assert i.b == 2 E assert 1 == 2 - E + where 1 = .Foo object at 0x2b186ee8d4e0>.b + E + where 1 = .Foo object at 0x7f65f1c814e0>.b failure_demo.py:101: AssertionError _________________________ test_attribute_instance __________________________ @@ -308,8 +308,8 @@ b = 1 > assert Foo().b == 2 E assert 1 == 2 - E + where 1 = .Foo object at 0x2b186eea6240>.b - E + where .Foo object at 0x2b186eea6240> = .Foo'>() + E + where 1 = .Foo object at 0x7f65f1c7f7f0>.b + E + where .Foo object at 0x7f65f1c7f7f0> = .Foo'>() failure_demo.py:107: AssertionError __________________________ test_attribute_failure __________________________ @@ -325,7 +325,7 @@ failure_demo.py:116: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ - self = .Foo object at 0x2b186ed9a4e0> + self = .Foo object at 0x7f65f1c97dd8> def _get_b(self): > raise Exception('Failed to get attrib') @@ -341,15 +341,15 @@ b = 2 > assert Foo().b == Bar().b E assert 1 == 2 - E + where 1 = .Foo object at 0x2b186ee78630>.b - E + where .Foo object at 0x2b186ee78630> = .Foo'>() - E + and 2 = .Bar object at 0x2b186ee78358>.b - E + where .Bar object at 0x2b186ee78358> = .Bar'>() + E + where 1 = .Foo object at 0x7f65f1c9b630>.b + E + where .Foo object at 0x7f65f1c9b630> = .Foo'>() + E + and 2 = .Bar object at 0x7f65f1c9b2b0>.b + E + where .Bar object at 0x7f65f1c9b2b0> = .Bar'>() failure_demo.py:124: AssertionError __________________________ TestRaises.test_raises __________________________ - self = + self = def test_raises(self): s = 'qwe' @@ -361,10 +361,10 @@ > int(s) E ValueError: invalid literal for int() with base 10: 'qwe' - <0-codegen /home/hpk/p/pytest/.tox/regen/lib/python3.4/site-packages/_pytest/python.py:1075>:1: ValueError + <0-codegen /tmp/sandbox/pytest/.tox/regen/lib/python3.4/site-packages/_pytest/python.py:1075>:1: ValueError ______________________ TestRaises.test_raises_doesnt _______________________ - self = + self = def test_raises_doesnt(self): > raises(IOError, "int('3')") @@ -373,7 +373,7 @@ failure_demo.py:136: Failed __________________________ TestRaises.test_raise ___________________________ - self = + self = def test_raise(self): > raise ValueError("demo error") @@ -382,7 +382,7 @@ failure_demo.py:139: ValueError ________________________ TestRaises.test_tupleerror ________________________ - self = + self = def test_tupleerror(self): > a,b = [1] @@ -391,7 +391,7 @@ failure_demo.py:142: ValueError ______ TestRaises.test_reinterpret_fails_with_print_for_the_fun_of_it ______ - self = + self = def test_reinterpret_fails_with_print_for_the_fun_of_it(self): l = [1,2,3] @@ -404,7 +404,7 @@ l is [1, 2, 3] ________________________ TestRaises.test_some_error ________________________ - self = + self = def test_some_error(self): > if namenotexi: @@ -429,10 +429,10 @@ > assert 1 == 0 E assert 1 == 0 - <2-codegen 'abc-123' /home/hpk/p/pytest/doc/en/example/assertion/failure_demo.py:162>:2: AssertionError + <2-codegen 'abc-123' /tmp/sandbox/pytest/doc/en/example/assertion/failure_demo.py:162>:2: AssertionError ____________________ TestMoreErrors.test_complex_error _____________________ - self = + self = def test_complex_error(self): def f(): @@ -456,7 +456,7 @@ failure_demo.py:5: AssertionError ___________________ TestMoreErrors.test_z1_unpack_error ____________________ - self = + self = def test_z1_unpack_error(self): l = [] @@ -466,7 +466,7 @@ failure_demo.py:179: ValueError ____________________ TestMoreErrors.test_z2_type_error _____________________ - self = + self = def test_z2_type_error(self): l = 3 @@ -476,19 +476,19 @@ failure_demo.py:183: TypeError ______________________ TestMoreErrors.test_startswith ______________________ - self = + self = def test_startswith(self): s = "123" g = "456" > assert s.startswith(g) - E assert ('456') - E + where = '123'.startswith + E assert ('456') + E + where = '123'.startswith failure_demo.py:188: AssertionError __________________ TestMoreErrors.test_startswith_nested ___________________ - self = + self = def test_startswith_nested(self): def f(): @@ -496,15 +496,15 @@ def g(): return "456" > assert f().startswith(g()) - E assert ('456') - E + where = '123'.startswith - E + where '123' = .f at 0x2b186eea1510>() - E + and '456' = .g at 0x2b186eea1268>() + E assert ('456') + E + where = '123'.startswith + E + where '123' = .f at 0x7f65f1c32950>() + E + and '456' = .g at 0x7f65f1c32ea0>() failure_demo.py:195: AssertionError _____________________ TestMoreErrors.test_global_func ______________________ - self = + self = def test_global_func(self): > assert isinstance(globf(42), float) @@ -514,18 +514,18 @@ failure_demo.py:198: AssertionError _______________________ TestMoreErrors.test_instance _______________________ - self = + self = def test_instance(self): self.x = 6*7 > assert self.x != 42 E assert 42 != 42 - E + where 42 = .x + E + where 42 = .x failure_demo.py:202: AssertionError _______________________ TestMoreErrors.test_compare ________________________ - self = + self = def test_compare(self): > assert globf(10) < 5 @@ -535,7 +535,7 @@ failure_demo.py:205: AssertionError _____________________ TestMoreErrors.test_try_finally ______________________ - self = + self = def test_try_finally(self): x = 1 @@ -546,7 +546,7 @@ failure_demo.py:210: AssertionError ___________________ TestCustomAssertMsg.test_single_line ___________________ - self = + self = def test_single_line(self): class A: @@ -560,7 +560,7 @@ failure_demo.py:221: AssertionError ____________________ TestCustomAssertMsg.test_multiline ____________________ - self = + self = def test_multiline(self): class A: @@ -577,7 +577,7 @@ failure_demo.py:227: AssertionError ___________________ TestCustomAssertMsg.test_custom_repr ___________________ - self = + self = def test_custom_repr(self): class JSON: @@ -595,4 +595,4 @@ E + where 1 = This is JSON\n{\n 'foo': 'bar'\n}.a failure_demo.py:237: AssertionError - ======================== 42 failed in 0.22 seconds ========================= + ======================== 42 failed in 0.35 seconds ========================= diff -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac -r 1a913c55c66d6841c113df2247942433e693ce0f doc/en/example/simple.txt --- a/doc/en/example/simple.txt +++ b/doc/en/example/simple.txt @@ -108,8 +108,8 @@ $ py.test =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-172, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-162, inifile: collected 0 items ============================= in 0.00 seconds ============================= @@ -153,13 +153,13 @@ $ py.test -rs # "-rs" means report details on the little 's' =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-172, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-162, inifile: collected 2 items test_module.py .s ========================= short test summary info ========================== - SKIP [1] /tmp/doc-exec-172/conftest.py:9: need --runslow option to run + SKIP [1] /tmp/doc-exec-162/conftest.py:9: need --runslow option to run =================== 1 passed, 1 skipped in 0.01 seconds ==================== @@ -167,8 +167,8 @@ $ py.test --runslow =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-172, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-162, inifile: collected 2 items test_module.py .. @@ -205,13 +205,13 @@ F ================================= FAILURES ================================= ______________________________ test_something ______________________________ - + def test_something(): > checkconfig(42) E Failed: not configured: 42 - + test_checkconfig.py:8: Failed - 1 failed in 0.01 seconds + 1 failed in 0.02 seconds Detect if running from within a pytest run -------------------------------------------------------------- @@ -259,8 +259,8 @@ $ py.test =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-172, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-162, inifile: project deps: mylib-1.1 collected 0 items @@ -283,8 +283,8 @@ $ py.test -v =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /tmp/doc-exec-172, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/doc-exec-162, inifile: info1: did you know that ... did you? collecting ... collected 0 items @@ -295,8 +295,8 @@ $ py.test =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-172, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-162, inifile: collected 0 items ============================= in 0.00 seconds ============================= @@ -328,8 +328,8 @@ $ py.test --durations=3 =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-172, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-162, inifile: collected 3 items test_some_are_slow.py ... @@ -390,8 +390,8 @@ $ py.test -rx =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-172, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-162, inifile: collected 4 items test_step.py .Fx. @@ -399,7 +399,7 @@ ================================= FAILURES ================================= ____________________ TestUserHandling.test_modification ____________________ - self = + self = def test_modification(self): > assert 0 @@ -409,7 +409,7 @@ ========================= short test summary info ========================== XFAIL test_step.py::TestUserHandling::()::test_deletion reason: previous test failed (test_modification) - ============== 1 failed, 2 passed, 1 xfailed in 0.01 seconds =============== + ============== 1 failed, 2 passed, 1 xfailed in 0.02 seconds =============== We'll see that ``test_deletion`` was not executed because ``test_modification`` failed. It is reported as an "expected failure". @@ -461,8 +461,8 @@ $ py.test =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-172, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-162, inifile: collected 7 items test_step.py .Fx. @@ -472,17 +472,17 @@ ================================== ERRORS ================================== _______________________ ERROR at setup of test_root ________________________ - file /tmp/doc-exec-172/b/test_error.py, line 1 + file /tmp/doc-exec-162/b/test_error.py, line 1 def test_root(db): # no db here, will error out fixture 'db' not found - available fixtures: pytestconfig, tmpdir, monkeypatch, capfd, recwarn, capsys + available fixtures: pytestconfig, capsys, recwarn, monkeypatch, tmpdir, capfd use 'py.test --fixtures [testpath]' for help on them. - /tmp/doc-exec-172/b/test_error.py:1 + /tmp/doc-exec-162/b/test_error.py:1 ================================= FAILURES ================================= ____________________ TestUserHandling.test_modification ____________________ - self = + self = def test_modification(self): > assert 0 @@ -491,25 +491,25 @@ test_step.py:9: AssertionError _________________________________ test_a1 __________________________________ - db = + db = def test_a1(db): > assert 0, db # to show value - E AssertionError: + E AssertionError: E assert 0 a/test_db.py:2: AssertionError _________________________________ test_a2 __________________________________ - db = + db = def test_a2(db): > assert 0, db # to show value - E AssertionError: + E AssertionError: E assert 0 a/test_db2.py:2: AssertionError - ========== 3 failed, 2 passed, 1 xfailed, 1 error in 0.03 seconds ========== + ========== 3 failed, 2 passed, 1 xfailed, 1 error in 0.05 seconds ========== The two test modules in the ``a`` directory see the same ``db`` fixture instance while the one test in the sister-directory ``b`` doesn't see it. We could of course @@ -564,8 +564,8 @@ $ py.test test_module.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-172, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-162, inifile: collected 2 items test_module.py FF @@ -573,7 +573,7 @@ ================================= FAILURES ================================= ________________________________ test_fail1 ________________________________ - tmpdir = local('/tmp/pytest-219/test_fail10') + tmpdir = local('/tmp/pytest-22/test_fail10') def test_fail1(tmpdir): > assert 0 @@ -587,12 +587,12 @@ E assert 0 test_module.py:4: AssertionError - ========================= 2 failed in 0.01 seconds ========================= + ========================= 2 failed in 0.02 seconds ========================= you will have a "failures" file which contains the failing test ids:: $ cat failures - test_module.py::test_fail1 (/tmp/pytest-219/test_fail10) + test_module.py::test_fail1 (/tmp/pytest-22/test_fail10) test_module.py::test_fail2 Making test result information available in fixtures @@ -655,8 +655,8 @@ $ py.test -s test_module.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-172, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-162, inifile: collected 3 items test_module.py Esetting up a test failed! test_module.py::test_setup_fails @@ -689,7 +689,7 @@ E assert 0 test_module.py:15: AssertionError - ==================== 2 failed, 1 error in 0.01 seconds ===================== + ==================== 2 failed, 1 error in 0.02 seconds ===================== You'll see that the fixture finalizers could use the precise reporting information. diff -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac -r 1a913c55c66d6841c113df2247942433e693ce0f doc/en/example/special.txt --- a/doc/en/example/special.txt +++ b/doc/en/example/special.txt @@ -69,4 +69,4 @@ .test other .test_unit1 method called . - 4 passed in 0.04 seconds + 4 passed in 0.03 seconds diff -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac -r 1a913c55c66d6841c113df2247942433e693ce0f doc/en/fixture.txt --- a/doc/en/fixture.txt +++ b/doc/en/fixture.txt @@ -75,8 +75,8 @@ $ py.test test_smtpsimple.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-109, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-98, inifile: collected 1 items test_smtpsimple.py F @@ -84,7 +84,7 @@ ================================= FAILURES ================================= ________________________________ test_ehlo _________________________________ - smtp = + smtp = def test_ehlo(smtp): response, msg = smtp.ehlo() @@ -93,7 +93,7 @@ E assert 0 test_smtpsimple.py:11: AssertionError - ========================= 1 failed in 0.17 seconds ========================= + ========================= 1 failed in 1.07 seconds ========================= In the failure traceback we see that the test function was called with a ``smtp`` argument, the ``smtplib.SMTP()`` instance created by the fixture @@ -193,8 +193,8 @@ $ py.test test_module.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-109, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-98, inifile: collected 2 items test_module.py FF @@ -202,7 +202,7 @@ ================================= FAILURES ================================= ________________________________ test_ehlo _________________________________ - smtp = + smtp = def test_ehlo(smtp): response = smtp.ehlo() @@ -213,7 +213,7 @@ test_module.py:5: TypeError ________________________________ test_noop _________________________________ - smtp = + smtp = def test_noop(smtp): response = smtp.noop() @@ -222,7 +222,7 @@ E assert 0 test_module.py:11: AssertionError - ========================= 2 failed in 0.20 seconds ========================= + ========================= 2 failed in 0.82 seconds ========================= You see the two ``assert 0`` failing and more importantly you can also see that the same (module-scoped) ``smtp`` object was passed into the two @@ -270,7 +270,7 @@ $ py.test -s -q --tb=no FFteardown smtp - 2 failed in 0.25 seconds + 2 failed in 1.44 seconds We see that the ``smtp`` instance is finalized after the two tests finished execution. Note that if we decorated our fixture @@ -311,7 +311,7 @@ $ py.test -s -q --tb=no FF - 2 failed in 0.17 seconds + 2 failed in 0.62 seconds Let's quickly create another test module that actually sets the server URL in its module namespace:: @@ -379,7 +379,7 @@ ================================= FAILURES ================================= __________________________ test_ehlo[merlinux.eu] __________________________ - smtp = + smtp = def test_ehlo(smtp): response = smtp.ehlo() @@ -390,7 +390,7 @@ test_module.py:5: TypeError __________________________ test_noop[merlinux.eu] __________________________ - smtp = + smtp = def test_noop(smtp): response = smtp.noop() @@ -401,7 +401,7 @@ test_module.py:11: AssertionError ________________________ test_ehlo[mail.python.org] ________________________ - smtp = + smtp = def test_ehlo(smtp): response = smtp.ehlo() @@ -411,10 +411,10 @@ test_module.py:5: TypeError -------------------------- Captured stdout setup --------------------------- - finalizing + finalizing ________________________ test_noop[mail.python.org] ________________________ - smtp = + smtp = def test_noop(smtp): response = smtp.noop() @@ -423,7 +423,7 @@ E assert 0 test_module.py:11: AssertionError - 4 failed in 6.70 seconds + 4 failed in 1.75 seconds We see that our two test functions each ran twice, against the different ``smtp`` instances. Note also, that with the ``mail.python.org`` @@ -474,8 +474,8 @@ $ py.test --collect-only =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-109, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-98, inifile: collected 6 items @@ -486,7 +486,7 @@ - ============================= in 0.01 seconds ============================= + ============================= in 0.02 seconds ============================= .. _`interdependent fixtures`: @@ -520,14 +520,14 @@ $ py.test -v test_appsetup.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /tmp/doc-exec-109, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/doc-exec-98, inifile: collecting ... collected 2 items test_appsetup.py::test_smtp_exists[merlinux.eu] PASSED test_appsetup.py::test_smtp_exists[mail.python.org] PASSED - ========================= 2 passed in 6.53 seconds ========================= + ========================= 2 passed in 1.09 seconds ========================= Due to the parametrization of ``smtp`` the test will run twice with two different ``App`` instances and respective smtp servers. There is no @@ -585,8 +585,8 @@ $ py.test -v -s test_module.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 -- /home/hpk/p/pytest/.tox/regen/bin/python3.4 - rootdir: /tmp/doc-exec-109, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 -- /tmp/sandbox/pytest/.tox/regen/bin/python3.4 + rootdir: /tmp/doc-exec-98, inifile: collecting ... collected 8 items test_module.py::test_0[1] test0 1 @@ -608,7 +608,7 @@ test_module.py::test_2[2-mod2] test2 2 mod2 PASSED - ========================= 8 passed in 0.01 seconds ========================= + ========================= 8 passed in 0.02 seconds ========================= You can see that the parametrized module-scoped ``modarg`` resource caused an ordering of test execution that lead to the fewest possible "active" resources. The finalizer for the ``mod1`` parametrized resource was executed diff -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac -r 1a913c55c66d6841c113df2247942433e693ce0f doc/en/getting-started.txt --- a/doc/en/getting-started.txt +++ b/doc/en/getting-started.txt @@ -27,7 +27,7 @@ To check your installation has installed the correct version:: $ py.test --version - This is pytest version 2.7.0, imported from /home/hpk/p/pytest/.tox/regen/lib/python3.4/site-packages/pytest.py + This is pytest version 2.7.1, imported from /tmp/sandbox/pytest/.tox/regen/lib/python3.4/site-packages/pytest.py If you get an error checkout :ref:`installation issues`. @@ -49,8 +49,8 @@ $ py.test =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-112, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-101, inifile: collected 1 items test_sample.py F @@ -98,7 +98,7 @@ $ py.test -q test_sysexit.py . - 1 passed in 0.00 seconds + 1 passed in 0.01 seconds .. todo:: For further ways to assert exceptions see the `raises` @@ -128,7 +128,7 @@ ================================= FAILURES ================================= ____________________________ TestClass.test_two ____________________________ - self = + self = def test_two(self): x = "hello" @@ -164,7 +164,7 @@ ================================= FAILURES ================================= _____________________________ test_needsfiles ______________________________ - tmpdir = local('/tmp/pytest-215/test_needsfiles0') + tmpdir = local('/tmp/pytest-18/test_needsfiles0') def test_needsfiles(tmpdir): print (tmpdir) @@ -173,8 +173,8 @@ test_tmpdir.py:3: AssertionError --------------------------- Captured stdout call --------------------------- - /tmp/pytest-215/test_needsfiles0 - 1 failed in 0.01 seconds + /tmp/pytest-18/test_needsfiles0 + 1 failed in 0.05 seconds Before the test runs, a unique-per-test-invocation temporary directory was created. More info at :ref:`tmpdir handling`. diff -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac -r 1a913c55c66d6841c113df2247942433e693ce0f doc/en/parametrize.txt --- a/doc/en/parametrize.txt +++ b/doc/en/parametrize.txt @@ -53,8 +53,8 @@ $ py.test =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-120, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-109, inifile: collected 3 items test_expectation.py ..F @@ -75,7 +75,7 @@ E + where 54 = eval('6*9') test_expectation.py:8: AssertionError - ==================== 1 failed, 2 passed in 0.01 seconds ==================== + ==================== 1 failed, 2 passed in 0.02 seconds ==================== As designed in this example, only one pair of input/output values fails the simple test function. And as usual with test function arguments, @@ -101,13 +101,13 @@ $ py.test =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-120, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-109, inifile: collected 3 items test_expectation.py ..x - =================== 2 passed, 1 xfailed in 0.01 seconds ==================== + =================== 2 passed, 1 xfailed in 0.02 seconds ==================== The one parameter set which caused a failure previously now shows up as an "xfailed (expected to fail)" test. @@ -172,8 +172,8 @@ def test_valid_string(stringinput): > assert stringinput.isalpha() - E assert () - E + where = '!'.isalpha + E assert () + E + where = '!'.isalpha test_strings.py:3: AssertionError 1 failed in 0.01 seconds @@ -187,8 +187,8 @@ $ py.test -q -rs test_strings.py s ========================= short test summary info ========================== - SKIP [1] /home/hpk/p/pytest/.tox/regen/lib/python3.4/site-packages/_pytest/python.py:1185: got empty parameter set, function test_valid_string at /tmp/doc-exec-120/test_strings.py:1 - 1 skipped in 0.00 seconds + SKIP [1] /tmp/sandbox/pytest/.tox/regen/lib/python3.4/site-packages/_pytest/python.py:1185: got empty parameter set, function test_valid_string at /tmp/doc-exec-109/test_strings.py:1 + 1 skipped in 0.01 seconds For further examples, you might want to look at :ref:`more parametrization examples `. diff -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac -r 1a913c55c66d6841c113df2247942433e693ce0f doc/en/skipping.txt --- a/doc/en/skipping.txt +++ b/doc/en/skipping.txt @@ -164,8 +164,8 @@ example $ py.test -rx xfail_demo.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /home/hpk/p/pytest/doc/en, inifile: pytest.ini + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/sandbox/pytest/doc/en, inifile: pytest.ini collected 7 items xfail_demo.py xxxxxxx @@ -183,7 +183,7 @@ reason: reason XFAIL xfail_demo.py::test_hello7 - ======================== 7 xfailed in 0.05 seconds ========================= + ======================== 7 xfailed in 0.06 seconds ========================= .. _`skip/xfail with parametrize`: diff -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac -r 1a913c55c66d6841c113df2247942433e693ce0f doc/en/tmpdir.txt --- a/doc/en/tmpdir.txt +++ b/doc/en/tmpdir.txt @@ -29,8 +29,8 @@ $ py.test test_tmpdir.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-129, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-118, inifile: collected 1 items test_tmpdir.py F @@ -38,7 +38,7 @@ ================================= FAILURES ================================= _____________________________ test_create_file _____________________________ - tmpdir = local('/tmp/pytest-216/test_create_file0') + tmpdir = local('/tmp/pytest-19/test_create_file0') def test_create_file(tmpdir): p = tmpdir.mkdir("sub").join("hello.txt") @@ -49,7 +49,7 @@ E assert 0 test_tmpdir.py:7: AssertionError - ========================= 1 failed in 0.01 seconds ========================= + ========================= 1 failed in 0.04 seconds ========================= .. _`base temporary directory`: diff -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac -r 1a913c55c66d6841c113df2247942433e693ce0f doc/en/unittest.txt --- a/doc/en/unittest.txt +++ b/doc/en/unittest.txt @@ -88,8 +88,8 @@ $ py.test test_unittest_db.py =========================== test session starts ============================ - platform linux -- Python 3.4.0 -- py-1.4.26 -- pytest-2.7.0 - rootdir: /tmp/doc-exec-130, inifile: + platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1 + rootdir: /tmp/doc-exec-119, inifile: collected 2 items test_unittest_db.py FF @@ -102,7 +102,7 @@ def test_method1(self): assert hasattr(self, "db") > assert 0, self.db # fail for demo purposes - E AssertionError: .DummyDB object at 0x2ab102a4bac8> + E AssertionError: .DummyDB object at 0x7f97382031d0> E assert 0 test_unittest_db.py:9: AssertionError @@ -112,7 +112,7 @@ def test_method2(self): > assert 0, self.db # fail for demo purposes - E AssertionError: .DummyDB object at 0x2ab102a4bac8> + E AssertionError: .DummyDB object at 0x7f97382031d0> E assert 0 test_unittest_db.py:12: AssertionError @@ -163,7 +163,7 @@ $ py.test -q test_unittest_cleandir.py . - 1 passed in 0.04 seconds + 1 passed in 0.25 seconds ... gives us one passed test because the ``initdir`` fixture function was executed ahead of the ``test_method``. diff -r 00993fa6da31bc8a3860cbf14f69e1a7ab971eac -r 1a913c55c66d6841c113df2247942433e693ce0f doc/en/yieldfixture.txt --- a/doc/en/yieldfixture.txt +++ b/doc/en/yieldfixture.txt @@ -43,7 +43,7 @@ test called .teardown after yield - 1 passed in 0.00 seconds + 1 passed in 0.01 seconds We can also seamlessly use the new syntax with ``with`` statements. Let's simplify the above ``passwd`` fixture:: Repository URL: https://bitbucket.org/pytest-dev/pytest/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email.