diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 198780a2a..1191fad27 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -3,6 +3,9 @@ Thanks for submitting a PR, your contribution is really appreciated! Here's a quick checklist that should be present in PRs: - [ ] Target: for bug or doc fixes, target `master`; for new features, target `features`; + +Unless your change is trivial documentation fix (e.g., a typo or reword of a small section) please: + - [ ] Make sure to include one or more tests for your change; - [ ] Add yourself to `AUTHORS`; - [ ] Add a new entry to `CHANGELOG.rst` diff --git a/.travis.yml b/.travis.yml index 2f282e94b..41537a466 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,27 +8,37 @@ install: "pip install -U tox" env: matrix: # coveralls is not listed in tox's envlist, but should run in travis - - TESTENV=coveralls + - TOXENV=coveralls # note: please use "tox --listenvs" to populate the build matrix below - - TESTENV=linting - - TESTENV=py26 - - TESTENV=py27 - - TESTENV=py33 - - TESTENV=py34 - - TESTENV=py35 - - TESTENV=pypy - - TESTENV=py27-pexpect - - TESTENV=py27-xdist - - TESTENV=py27-trial - - TESTENV=py35-pexpect - - TESTENV=py35-xdist - - TESTENV=py35-trial - - TESTENV=py27-nobyte - - TESTENV=doctesting - - TESTENV=freeze - - TESTENV=docs + - TOXENV=linting + - TOXENV=py26 + - TOXENV=py27 + - TOXENV=py33 + - TOXENV=py34 + - TOXENV=py35 + - TOXENV=pypy + - TOXENV=py27-pexpect + - TOXENV=py27-xdist + - TOXENV=py27-trial + - TOXENV=py35-pexpect + - TOXENV=py35-xdist + - TOXENV=py35-trial + - TOXENV=py27-nobyte + - TOXENV=doctesting + - TOXENV=freeze + - TOXENV=docs -script: tox --recreate -e $TESTENV +matrix: + include: + - env: TOXENV=py36 + python: '3.6-dev' + - env: TOXENV=py37 + python: 'nightly' + allow_failures: + - env: TOXENV=py37 + python: 'nightly' + +script: tox --recreate notifications: irc: diff --git a/AUTHORS b/AUTHORS index 6e4655f37..717595346 100644 --- a/AUTHORS +++ b/AUTHORS @@ -6,6 +6,7 @@ Contributors include:: Abdeali JK Abhijeet Kasurde Ahn Ki-Wook +Alexander Johnson Alexei Kozlenok Anatoly Bubenkoff Andreas Zeidler @@ -16,6 +17,7 @@ Antony Lee Armin Rigo Aron Curzon Aviv Palivoda +Barney Gale Ben Webb Benjamin Peterson Bernard Pratz @@ -36,16 +38,20 @@ Christopher Gilling Daniel Grana Daniel Hahler Daniel Nuri +Daniel Wandschneider Danielle Jenkins Dave Hunt David Díaz-Barquero David Mohr David Vierra +Denis Kirisov Diego Russo Dmitry Dygalo +Duncan Betts Edison Gustavo Muenz Edoardo Batini Eduardo Schettino +Eli Boyarski Elizaveta Shashkova Endre Galaczi Eric Hunsberger @@ -59,6 +65,7 @@ Georgy Dyuldin Graham Horler Greg Price Grig Gheorghiu +Grigorii Eremeev (budulianin) Guido Wesdorp Harald Armin Massa Ian Bicking @@ -68,6 +75,7 @@ Janne Vanhala Jason R. Coombs Javier Domingo Cansino Javier Romero +Jeff Widman John Towler Jon Sonesen Jordan Guymon @@ -78,7 +86,10 @@ Kale Kundert Katarzyna Jachim Kevin Cox Lee Kamentsky +Lev Maximov +Loic Esteve Lukas Bednar +Luke Murphy Maciek Fijalkowski Maho Marc Schlaich @@ -88,6 +99,7 @@ Markus Unterwaditzer Martijn Faassen Martin K. Scherer Martin Prusse +Mathieu Clabaut Matt Bachmann Matt Williams Matthias Hafner @@ -95,17 +107,25 @@ mbyt Michael Aquilina Michael Birtwell Michael Droettboom +Michael Seifert Mike Lundy +Ned Batchelder +Neven Mundar Nicolas Delaby Oleg Pidsadnyi Oliver Bestwalter Omar Kohl +Omer Hadari +Patrick Hayes +Paweł Adamczak Pieter Mulder Piotr Banaszkiewicz Punyashloka Biswal Quentin Pradet Ralf Schmitt +Ran Benita Raphael Pierzina +Raquel Alegre Roberto Polli Romain Dorgueil Roman Bolshakov @@ -126,6 +146,9 @@ Ted Xiao Thomas Grainger Tom Viner Trevor Bekolay +Tyler Goodlet Vasily Kuznetsov +Victor Uriarte +Vidar T. Fauske Wouter van Ackooy Xuecong Liao diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c5fb31151..decea4b25 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,31 +1,304 @@ -3.0.3.dev0 -========== +3.0.8 (unreleased) +================== + +* Change capture.py's ``DontReadFromInput`` class to throw ``io.UnsupportedOperation`` errors rather + than ValueErrors in the ``fileno`` method (`#2276`_). + Thanks `@metasyn`_ for the PR. + +* Fix exception formatting while importing modules when the exception message + contains non-ascii characters (`#2336`_). + Thanks `@fabioz`_ for the report and `@nicoddemus`_ for the PR. + +* Added documentation related to issue (`#1937`_) + Thanks `@skylarjhdownes`_ for the PR. + +* + +* + +* + + + +.. _@skylarjhdownes: https://github.com/skylarjhdownes +.. _@fabioz: https://github.com/fabioz +.. _@metasyn: https://github.com/metasyn + + +.. _#2276: https://github.com/pytest-dev/pytest/issues/2276 +.. _#2336: https://github.com/pytest-dev/pytest/issues/2336 + + +3.0.7 (2017-03-14) +================== + + +* Fix issue in assertion rewriting breaking due to modules silently discarding + other modules when importing fails + Notably, importing the ``anydbm`` module is fixed. (`#2248`_). + Thanks `@pfhayes`_ for the PR. + +* junitxml: Fix problematic case where system-out tag occured twice per testcase + element in the XML report. Thanks `@kkoukiou`_ for the PR. + +* Fix regression, pytest now skips unittest correctly if run with ``--pdb`` + (`#2137`_). Thanks to `@gst`_ for the report and `@mbyt`_ for the PR. + +* Ignore exceptions raised from descriptors (e.g. properties) during Python test collection (`#2234`_). + Thanks to `@bluetech`_. + +* ``--override-ini`` now correctly overrides some fundamental options like ``python_files`` (`#2238`_). + Thanks `@sirex`_ for the report and `@nicoddemus`_ for the PR. + +* Replace ``raise StopIteration`` usages in the code by simple ``returns`` to finish generators, in accordance to `PEP-479`_ (`#2160`_). + Thanks `@tgoodlet`_ for the report and `@nicoddemus`_ for the PR. + +* Fix internal errors when an unprintable ``AssertionError`` is raised inside a test. + Thanks `@omerhadari`_ for the PR. + +* Skipping plugin now also works with test items generated by custom collectors (`#2231`_). + Thanks to `@vidartf`_. + +* Fix trailing whitespace in console output if no .ini file presented (`#2281`_). Thanks `@fbjorn`_ for the PR. + +* Conditionless ``xfail`` markers no longer rely on the underlying test item + being an instance of ``PyobjMixin``, and can therefore apply to tests not + collected by the built-in python test collector. Thanks `@barneygale`_ for the + PR. + + +.. _@pfhayes: https://github.com/pfhayes +.. _@bluetech: https://github.com/bluetech +.. _@gst: https://github.com/gst +.. _@sirex: https://github.com/sirex +.. _@vidartf: https://github.com/vidartf +.. _@kkoukiou: https://github.com/KKoukiou +.. _@omerhadari: https://github.com/omerhadari +.. _@fbjorn: https://github.com/fbjorn + +.. _#2248: https://github.com/pytest-dev/pytest/issues/2248 +.. _#2137: https://github.com/pytest-dev/pytest/issues/2137 +.. _#2160: https://github.com/pytest-dev/pytest/issues/2160 +.. _#2231: https://github.com/pytest-dev/pytest/issues/2231 +.. _#2234: https://github.com/pytest-dev/pytest/issues/2234 +.. _#2238: https://github.com/pytest-dev/pytest/issues/2238 +.. _#2281: https://github.com/pytest-dev/pytest/issues/2281 + +.. _PEP-479: https://www.python.org/dev/peps/pep-0479/ + + +3.0.6 (2017-01-22) +================== + +* pytest no longer generates ``PendingDeprecationWarning`` from its own operations, which was introduced by mistake in version ``3.0.5`` (`#2118`_). + Thanks to `@nicoddemus`_ for the report and `@RonnyPfannschmidt`_ for the PR. + + +* pytest no longer recognizes coroutine functions as yield tests (`#2129`_). + Thanks to `@malinoff`_ for the PR. + +* Plugins loaded by the ``PYTEST_PLUGINS`` environment variable are now automatically + considered for assertion rewriting (`#2185`_). + Thanks `@nicoddemus`_ for the PR. + +* Improve error message when pytest.warns fails (`#2150`_). The type(s) of the + expected warnings and the list of caught warnings is added to the + error message. Thanks `@lesteve`_ for the PR. + +* Fix ``pytester`` internal plugin to work correctly with latest versions of + ``zope.interface`` (`#1989`_). Thanks `@nicoddemus`_ for the PR. + +* Assert statements of the ``pytester`` plugin again benefit from assertion rewriting (`#1920`_). + Thanks `@RonnyPfannschmidt`_ for the report and `@nicoddemus`_ for the PR. + +* Specifying tests with colons like ``test_foo.py::test_bar`` for tests in + subdirectories with ini configuration files now uses the correct ini file + (`#2148`_). Thanks `@pelme`_. + +* Fail ``testdir.runpytest().assert_outcomes()`` explicitly if the pytest + terminal output it relies on is missing. Thanks to `@eli-b`_ for the PR. + + +.. _@barneygale: https://github.com/barneygale +.. _@lesteve: https://github.com/lesteve +.. _@malinoff: https://github.com/malinoff +.. _@pelme: https://github.com/pelme +.. _@eli-b: https://github.com/eli-b + +.. _#2118: https://github.com/pytest-dev/pytest/issues/2118 + +.. _#1989: https://github.com/pytest-dev/pytest/issues/1989 +.. _#1920: https://github.com/pytest-dev/pytest/issues/1920 +.. _#2129: https://github.com/pytest-dev/pytest/issues/2129 +.. _#2148: https://github.com/pytest-dev/pytest/issues/2148 +.. _#2150: https://github.com/pytest-dev/pytest/issues/2150 +.. _#2185: https://github.com/pytest-dev/pytest/issues/2185 + + +3.0.5 (2016-12-05) +================== + +* Add warning when not passing ``option=value`` correctly to ``-o/--override-ini`` (`#2105`_). + Also improved the help documentation. Thanks to `@mbukatov`_ for the report and + `@lwm`_ for the PR. + +* Now ``--confcutdir`` and ``--junit-xml`` are properly validated if they are directories + and filenames, respectively (`#2089`_ and `#2078`_). Thanks to `@lwm`_ for the PR. + +* Add hint to error message hinting possible missing ``__init__.py`` (`#478`_). Thanks `@DuncanBetts`_. + +* More accurately describe when fixture finalization occurs in documentation (`#687`_). Thanks `@DuncanBetts`_. + +* Provide ``:ref:`` targets for ``recwarn.rst`` so we can use intersphinx referencing. + Thanks to `@dupuy`_ for the report and `@lwm`_ for the PR. + +* In Python 2, use a simple ``+-`` ASCII string in the string representation of ``pytest.approx`` (for example ``"4 +- 4.0e-06"``) + because it is brittle to handle that in different contexts and representations internally in pytest + which can result in bugs such as `#2111`_. In Python 3, the representation still uses ``±`` (for example ``4 ± 4.0e-06``). + Thanks `@kerrick-lyft`_ for the report and `@nicoddemus`_ for the PR. + +* Using ``item.Function``, ``item.Module``, etc., is now issuing deprecation warnings, prefer + ``pytest.Function``, ``pytest.Module``, etc., instead (`#2034`_). + Thanks `@nmundar`_ for the PR. + +* Fix error message using ``approx`` with complex numbers (`#2082`_). + Thanks `@adler-j`_ for the report and `@nicoddemus`_ for the PR. + +* Fixed false-positives warnings from assertion rewrite hook for modules imported more than + once by the ``pytest_plugins`` mechanism. + Thanks `@nicoddemus`_ for the PR. + +* Remove an internal cache which could cause hooks from ``conftest.py`` files in + sub-directories to be called in other directories incorrectly (`#2016`_). + Thanks `@d-b-w`_ for the report and `@nicoddemus`_ for the PR. + +* Remove internal code meant to support earlier Python 3 versions that produced the side effect + of leaving ``None`` in ``sys.modules`` when expressions were evaluated by pytest (for example passing a condition + as a string to ``pytest.mark.skipif``)(`#2103`_). + Thanks `@jaraco`_ for the report and `@nicoddemus`_ for the PR. + +* Cope gracefully with a .pyc file with no matching .py file (`#2038`_). Thanks + `@nedbat`_. + +.. _@adler-j: https://github.com/adler-j +.. _@d-b-w: https://bitbucket.org/d-b-w/ +.. _@DuncanBetts: https://github.com/DuncanBetts +.. _@dupuy: https://bitbucket.org/dupuy/ +.. _@kerrick-lyft: https://github.com/kerrick-lyft +.. _@lwm: https://github.com/lwm +.. _@mbukatov: https://github.com/mbukatov +.. _@nedbat: https://github.com/nedbat +.. _@nmundar: https://github.com/nmundar + +.. _#2016: https://github.com/pytest-dev/pytest/issues/2016 +.. _#2034: https://github.com/pytest-dev/pytest/issues/2034 +.. _#2038: https://github.com/pytest-dev/pytest/issues/2038 +.. _#2078: https://github.com/pytest-dev/pytest/issues/2078 +.. _#2082: https://github.com/pytest-dev/pytest/issues/2082 +.. _#2089: https://github.com/pytest-dev/pytest/issues/2089 +.. _#2103: https://github.com/pytest-dev/pytest/issues/2103 +.. _#2105: https://github.com/pytest-dev/pytest/issues/2105 +.. _#2111: https://github.com/pytest-dev/pytest/issues/2111 +.. _#478: https://github.com/pytest-dev/pytest/issues/478 +.. _#687: https://github.com/pytest-dev/pytest/issues/687 + + +3.0.4 (2016-11-09) +================== + +* Import errors when collecting test modules now display the full traceback (`#1976`_). + Thanks `@cwitty`_ for the report and `@nicoddemus`_ for the PR. + +* Fix confusing command-line help message for custom options with two or more ``metavar`` properties (`#2004`_). + Thanks `@okulynyak`_ and `@davehunt`_ for the report and `@nicoddemus`_ for the PR. + +* When loading plugins, import errors which contain non-ascii messages are now properly handled in Python 2 (`#1998`_). + Thanks `@nicoddemus`_ for the PR. + +* Fixed cyclic reference when ``pytest.raises`` is used in context-manager form (`#1965`_). Also as a + result of this fix, ``sys.exc_info()`` is left empty in both context-manager and function call usages. + Previously, ``sys.exc_info`` would contain the exception caught by the context manager, + even when the expected exception occurred. + Thanks `@MSeifert04`_ for the report and the PR. + +* Fixed false-positives warnings from assertion rewrite hook for modules that were rewritten but + were later marked explicitly by ``pytest.register_assert_rewrite`` + or implicitly as a plugin (`#2005`_). + Thanks `@RonnyPfannschmidt`_ for the report and `@nicoddemus`_ for the PR. + +* Report teardown output on test failure (`#442`_). + Thanks `@matclab`_ for the PR. + +* Fix teardown error message in generated xUnit XML. + Thanks `@gdyuldin`_ for the PR. + +* Properly handle exceptions in ``multiprocessing`` tasks (`#1984`_). + Thanks `@adborden`_ for the report and `@nicoddemus`_ for the PR. + +* Clean up unittest TestCase objects after tests are complete (`#1649`_). + Thanks `@d_b_w`_ for the report and PR. + + +.. _@adborden: https://github.com/adborden +.. _@cwitty: https://github.com/cwitty +.. _@d_b_w: https://github.com/d_b_w +.. _@gdyuldin: https://github.com/gdyuldin +.. _@matclab: https://github.com/matclab +.. _@MSeifert04: https://github.com/MSeifert04 +.. _@okulynyak: https://github.com/okulynyak + +.. _#442: https://github.com/pytest-dev/pytest/issues/442 +.. _#1965: https://github.com/pytest-dev/pytest/issues/1965 +.. _#1976: https://github.com/pytest-dev/pytest/issues/1976 +.. _#1984: https://github.com/pytest-dev/pytest/issues/1984 +.. _#1998: https://github.com/pytest-dev/pytest/issues/1998 +.. _#2004: https://github.com/pytest-dev/pytest/issues/2004 +.. _#2005: https://github.com/pytest-dev/pytest/issues/2005 +.. _#1649: https://github.com/pytest-dev/pytest/issues/1649 + + +3.0.3 (2016-09-28) +================== * The ``ids`` argument to ``parametrize`` again accepts ``unicode`` strings in Python 2 (`#1905`_). Thanks `@philpep`_ for the report and `@nicoddemus`_ for the PR. -* - * Assertions are now being rewritten for plugins in development mode (``pip install -e``) (`#1934`_). Thanks `@nicoddemus`_ for the PR. -* +* Fix pkg_resources import error in Jython projects (`#1853`_). + Thanks `@raquel-ucl`_ for the PR. + +* Got rid of ``AttributeError: 'Module' object has no attribute '_obj'`` exception + in Python 3 (`#1944`_). + Thanks `@axil`_ for the PR. + +* Explain a bad scope value passed to ``@fixture`` declarations or + a ``MetaFunc.parametrize()`` call. Thanks `@tgoodlet`_ for the PR. + +* This version includes ``pluggy-0.4.0``, which correctly handles + ``VersionConflict`` errors in plugins (`#704`_). + Thanks `@nicoddemus`_ for the PR. -* Added documentation related to issue (`#1937`_) - Thanks `@skylarjhdownes`_ for the PR. .. _@philpep: https://github.com/philpep -.. _@skylarjhdownes: https://github.com/skylarjhdownes +.. _@raquel-ucl: https://github.com/raquel-ucl +.. _@axil: https://github.com/axil +.. _@tgoodlet: https://github.com/tgoodlet +.. _#1853: https://github.com/pytest-dev/pytest/issues/1853 .. _#1905: https://github.com/pytest-dev/pytest/issues/1905 .. _#1934: https://github.com/pytest-dev/pytest/issues/1934 -.. _#1937: https://github.com/pytest-dev/pytest/issues/1937 +.. _#1944: https://github.com/pytest-dev/pytest/issues/1944 +.. _#704: https://github.com/pytest-dev/pytest/issues/704 -3.0.2 -===== + + +3.0.2 (2016-09-01) +================== * Improve error message when passing non-string ids to ``pytest.mark.parametrize`` (`#1857`_). Thanks `@okken`_ for the report and `@nicoddemus`_ for the PR. @@ -47,7 +320,7 @@ enabled. This allows proper post mortem debugging for all applications which have significant logic in their tearDown machinery (`#1890`_). Thanks `@mbyt`_ for the PR. - + * Fix use of deprecated ``getfuncargvalue`` method in the internal doctest plugin. Thanks `@ViviCoder`_ for the report (`#1898`_). @@ -64,8 +337,8 @@ .. _#1898: https://github.com/pytest-dev/pytest/issues/1898 -3.0.1 -===== +3.0.1 (2016-08-23) +================== * Fix regression when ``importorskip`` is used at module level (`#1822`_). Thanks `@jaraco`_ and `@The-Compiler`_ for the report and `@nicoddemus`_ for the PR. @@ -90,8 +363,8 @@ .. _#1849: https://github.com/pytest-dev/pytest/issues/1849 -3.0.0 -===== +3.0.0 (2016-08-18) +================== **Incompatible changes** @@ -344,7 +617,7 @@ time or change existing behaviors in order to make them less surprising/more use * Refined logic for determining the ``rootdir``, considering only valid paths which fixes a number of issues: `#1594`_, `#1435`_ and `#1471`_. - Updated the documentation according to current behavior. Thanks to + Updated the documentation according to current behavior. Thanks to `@blueyed`_, `@davehunt`_ and `@matthiasha`_ for the PR. * Always include full assertion explanation. The previous behaviour was hiding @@ -516,8 +789,8 @@ time or change existing behaviors in order to make them less surprising/more use .. _@matthiasha: https://github.com/matthiasha -2.9.2 -===== +2.9.2 (2016-05-31) +================== **Bug Fixes** @@ -528,7 +801,7 @@ time or change existing behaviors in order to make them less surprising/more use Thanks `@astraw38`_ for reporting the issue (`#1496`_) and `@tomviner`_ for PR the (`#1524`_). -* Fix win32 path issue when puttinging custom config file with absolute path +* Fix win32 path issue when putting custom config file with absolute path in ``pytest.main("-c your_absolute_path")``. * Fix maximum recursion depth detection when raised error class is not aware @@ -555,8 +828,8 @@ time or change existing behaviors in order to make them less surprising/more use .. _@astraw38: https://github.com/astraw38 -2.9.1 -===== +2.9.1 (2016-03-17) +================== **Bug Fixes** @@ -591,8 +864,8 @@ time or change existing behaviors in order to make them less surprising/more use .. _@asottile: https://github.com/asottile -2.9.0 -===== +2.9.0 (2016-02-29) +================== **New Features** @@ -712,13 +985,13 @@ time or change existing behaviors in order to make them less surprising/more use .. _@pquentin: https://github.com/pquentin .. _@ioggstream: https://github.com/ioggstream -2.8.7 -===== +2.8.7 (2016-01-24) +================== - fix #1338: use predictable object resolution for monkeypatch -2.8.6 -===== +2.8.6 (2016-01-21) +================== - fix #1259: allow for double nodeids in junitxml, this was a regression failing plugins combinations @@ -749,8 +1022,8 @@ time or change existing behaviors in order to make them less surprising/more use Thanks Georgy Dyuldin for the PR. -2.8.5 -===== +2.8.5 (2015-12-11) +================== - fix #1243: fixed issue where class attributes injected during collection could break pytest. PR by Alexei Kozlenok, thanks Ronny Pfannschmidt and Bruno Oliveira for the review and help. @@ -763,8 +1036,8 @@ time or change existing behaviors in order to make them less surprising/more use Bruno Oliveira for the PR. -2.8.4 -===== +2.8.4 (2015-12-06) +================== - fix #1190: ``deprecated_call()`` now works when the deprecated function has been already called by another test in the same @@ -787,8 +1060,8 @@ time or change existing behaviors in order to make them less surprising/more use - a number of documentation modernizations wrt good practices. Thanks Bruno Oliveira for the PR. -2.8.3 -===== +2.8.3 (2015-11-18) +================== - fix #1169: add __name__ attribute to testcases in TestCaseFunction to support the @unittest.skip decorator on functions and methods. @@ -815,8 +1088,8 @@ time or change existing behaviors in order to make them less surprising/more use system integrity protection (thanks Florian) -2.8.2 -===== +2.8.2 (2015-10-07) +================== - fix #1085: proper handling of encoding errors when passing encoded byte strings to pytest.parametrize in Python 2. @@ -835,8 +1108,8 @@ time or change existing behaviors in order to make them less surprising/more use Thanks Sergey B Kirpichev and Vital Kudzelka for contributing and Bruno Oliveira for the PR. -2.8.1 -===== +2.8.1 (2015-09-29) +================== - fix #1034: Add missing nodeid on pytest_logwarning call in addhook. Thanks Simon Gomizelj for the PR. @@ -860,7 +1133,7 @@ time or change existing behaviors in order to make them less surprising/more use - (experimental) adapt more SEMVER style versioning and change meaning of master branch in git repo: "master" branch now keeps the bugfixes, changes - aimed for micro releases. "features" branch will only be be released + aimed for micro releases. "features" branch will only be released with minor or major pytest releases. - Fix issue #766 by removing documentation references to distutils. @@ -882,8 +1155,8 @@ time or change existing behaviors in order to make them less surprising/more use - fix issue 1029: transform errors when writing cache values into pytest-warnings -2.8.0 -===== +2.8.0 (2015-09-18) +================== - new ``--lf`` and ``-ff`` options to run only the last failing tests or "failing tests first" from the last run. This functionality is provided @@ -994,7 +1267,7 @@ time or change existing behaviors in order to make them less surprising/more use - new option ``--import-mode`` to allow to change test module importing behaviour to append to sys.path instead of prepending. This better allows - to run test modules against installated versions of a package even if the + to run test modules against installed versions of a package even if the package under test has the same import root. In this example:: testing/__init__.py @@ -1072,8 +1345,8 @@ time or change existing behaviors in order to make them less surprising/more use properly used to discover ``rootdir`` and ``ini`` files. Thanks Peter Lauri for the report and Bruno Oliveira for the PR. -2.7.3 (compared to 2.7.2) -============================= +2.7.3 (2015-09-15) +================== - Allow 'dev', 'rc', or other non-integer version strings in ``importorskip``. Thanks to Eric Hunsberger for the PR. @@ -1115,8 +1388,8 @@ time or change existing behaviors in order to make them less surprising/more use directories created by this fixture (defaults to $TEMP/pytest-$USER). Thanks Bruno Oliveira for the PR. -2.7.2 (compared to 2.7.1) -============================= +2.7.2 (2015-06-23) +================== - fix issue767: pytest.raises value attribute does not contain the exception instance on Python 2.6. Thanks Eric Siegerman for providing the test @@ -1144,15 +1417,15 @@ time or change existing behaviors in order to make them less surprising/more use which has a refined algorithm for traceback generation. -2.7.1 (compared to 2.7.0) -============================= +2.7.1 (2015-05-19) +================== - 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, + FixtureLookupError which would lead to an internal error, obfuscating the original problem. Thanks talljosh for initial diagnose/patch and Bruno Oliveira for final patch. @@ -1177,8 +1450,8 @@ time or change existing behaviors in order to make them less surprising/more use - reintroduced _pytest fixture of the pytester plugin which is used at least by pytest-xdist. -2.7.0 (compared to 2.6.4) -============================= +2.7.0 (2015-03-26) +================== - fix issue435: make reload() work when assert rewriting is active. Thanks Daniel Hahler. @@ -1247,8 +1520,8 @@ time or change existing behaviors in order to make them less surprising/more use ``sys.last_traceback`` are set, so that a user can inspect the error via postmortem debugging (almarklein). -2.6.4 -===== +2.6.4 (2014-10-24) +================== - Improve assertion failure reporting on iterables, by using ndiff and pprint. @@ -1276,8 +1549,8 @@ time or change existing behaviors in order to make them less surprising/more use - fix issue614: fixed pastebin support. -2.6.3 -===== +2.6.3 (2014-09-24) +================== - fix issue575: xunit-xml was reporting collection errors as failures instead of errors, thanks Oleg Sinyavskiy. @@ -1295,7 +1568,7 @@ time or change existing behaviors in order to make them less surprising/more use - fix conftest related fixture visibility issue: when running with a CWD outside of a test package pytest would get fixture discovery wrong. - Thanks to Wolfgang Schnerring for figuring out a reproducable example. + Thanks to Wolfgang Schnerring for figuring out a reproducible example. - Introduce pytest_enter_pdb hook (needed e.g. by pytest_timeout to cancel the timeout when interactively entering pdb). Thanks Wolfgang Schnerring. @@ -1303,8 +1576,8 @@ time or change existing behaviors in order to make them less surprising/more use - check xfail/skip also with non-python function test items. Thanks Floris Bruynooghe. -2.6.2 -===== +2.6.2 (2014-09-05) +================== - Added function pytest.freeze_includes(), which makes it easy to embed pytest into executables using tools like cx_freeze. @@ -1332,8 +1605,8 @@ time or change existing behaviors in order to make them less surprising/more use replace the py.test introspection message but are shown in addition to them. -2.6.1 -===== +2.6.1 (2014-08-07) +================== - No longer show line numbers in the --verbose output, the output is now purely the nodeid. The line number is still shown in failure reports. @@ -1469,8 +1742,8 @@ time or change existing behaviors in order to make them less surprising/more use in monkeypatch plugin. Improves output in documentation. -2.5.2 -===== +2.5.2 (2014-01-29) +================== - fix issue409 -- better interoperate with cx_freeze by not trying to import from collections.abc which causes problems @@ -1494,11 +1767,11 @@ time or change existing behaviors in order to make them less surprising/more use - fix issue429: comparing byte strings with non-ascii chars in assert expressions now work better. Thanks Floris Bruynooghe. -- make capfd/capsys.capture private, its unused and shouldnt be exposed +- make capfd/capsys.capture private, its unused and shouldn't be exposed -2.5.1 -===== +2.5.1 (2013-12-17) +================== - merge new documentation styling PR from Tobias Bieniek. @@ -1518,8 +1791,8 @@ time or change existing behaviors in order to make them less surprising/more use -2.5.0 -===== +2.5.0 (2013-12-12) +================== - dropped python2.5 from automated release testing of pytest itself which means it's probably going to break soon (but still works @@ -1551,7 +1824,7 @@ time or change existing behaviors in order to make them less surprising/more use to problems for more than >966 non-function scoped parameters). - fix issue290 - there is preliminary support now for parametrizing - with repeated same values (sometimes useful to to test if calling + with repeated same values (sometimes useful to test if calling a second time works as with the first time). - close issue240 - document precisely how pytest module importing @@ -1654,8 +1927,8 @@ time or change existing behaviors in order to make them less surprising/more use - fix verbose reporting for @mock'd test functions -2.4.2 -===== +2.4.2 (2013-10-04) +================== - on Windows require colorama and a newer py lib so that py.io.TerminalWriter() now uses colorama instead of its own ctypes hacks. (fixes issue365) @@ -1685,8 +1958,8 @@ time or change existing behaviors in order to make them less surprising/more use - add pluginmanager.do_configure(config) as a link to config.do_configure() for plugin-compatibility -2.4.1 -===== +2.4.1 (2013-10-02) +================== - When using parser.addoption() unicode arguments to the "type" keyword should also be converted to the respective types. @@ -1765,7 +2038,7 @@ new features: - fix issue322: tearDownClass is not run if setUpClass failed. Thanks Mathieu Agopian for the initial fix. Also make all of pytest/nose - finalizer mimick the same generic behaviour: if a setupX exists and + finalizer mimic the same generic behaviour: if a setupX exists and fails, don't run teardownX. This internally introduces a new method "node.addfinalizer()" helper which can only be called during the setup phase of a node. @@ -1870,8 +2143,8 @@ Bug fixes: ".section(title)" and ".line(msg)" methods to print extra information at the end of a test run. -2.3.5 -===== +2.3.5 (2013-04-30) +================== - fix issue169: respect --tb=style with setup/teardown errors as well. @@ -1884,11 +2157,11 @@ Bug fixes: (thanks Adam Goucher) - Issue 265 - integrate nose setup/teardown with setupstate - so it doesnt try to teardown if it did not setup + so it doesn't try to teardown if it did not setup -- issue 271 - dont write junitxml on slave nodes +- issue 271 - don't write junitxml on slave nodes -- Issue 274 - dont try to show full doctest example +- Issue 274 - don't try to show full doctest example when doctest does not know the example location - issue 280 - disable assertion rewriting on buggy CPython 2.6.0 @@ -1924,7 +2197,7 @@ Bug fixes: - allow to specify prefixes starting with "_" when customizing python_functions test discovery. (thanks Graham Horler) -- improve PYTEST_DEBUG tracing output by puting +- improve PYTEST_DEBUG tracing output by putting extra data on a new lines with additional indent - ensure OutcomeExceptions like skip/fail have initialized exception attributes @@ -1935,8 +2208,8 @@ Bug fixes: - fix issue266 - accept unicode in MarkEvaluator expressions -2.3.4 -===== +2.3.4 (2012-11-20) +================== - yielded test functions will now have autouse-fixtures active but cannot accept fixtures as funcargs - it's anyway recommended to @@ -1955,8 +2228,8 @@ Bug fixes: need to write as -k "TestClass and test_method" to match a certain method in a certain test class. -2.3.3 -===== +2.3.3 (2012-11-06) +================== - fix issue214 - parse modules that contain special objects like e. g. flask's request object which blows up on getattr access if no request @@ -1973,7 +2246,7 @@ Bug fixes: - fix issue209 - reintroduce python2.4 support by depending on newer pylib which re-introduced statement-finding for pre-AST interpreters -- nose support: only call setup if its a callable, thanks Andrew +- nose support: only call setup if it's a callable, thanks Andrew Taumoefolau - fix issue219 - add py2.4-3.3 classifiers to TROVE list @@ -1987,8 +2260,8 @@ Bug fixes: - fix issue127 - improve documentation for pytest_addoption() and add a ``config.getoption(name)`` helper function for consistency. -2.3.2 -===== +2.3.2 (2012-10-25) +================== - fix issue208 and fix issue29 use new py version to avoid long pauses when printing tracebacks in long modules @@ -2020,8 +2293,8 @@ Bug fixes: - add tox.ini to pytest distribution so that ignore-dirs and others config bits are properly distributed for maintainers who run pytest-own tests -2.3.1 -===== +2.3.1 (2012-10-20) +================== - fix issue202 - fix regression: using "self" from fixture functions now works as expected (it's the same "self" instance that a test method @@ -2033,8 +2306,8 @@ Bug fixes: - link to web pages from --markers output which provides help for pytest.mark.* usage. -2.3.0 -===== +2.3.0 (2012-10-19) +================== - fix issue202 - better automatic names for parametrized test functions - fix issue139 - introduce @pytest.fixture which allows direct scoping @@ -2069,7 +2342,7 @@ Bug fixes: - fix issue128: show captured output when capsys/capfd are used -- fix issue179: propperly show the dependency chain of factories +- fix issue179: properly show the dependency chain of factories - pluginmanager.register(...) now raises ValueError if the plugin has been already registered or the name is taken @@ -2110,10 +2383,10 @@ Bug fixes: - don't show deselected reason line if there is none - - py.test -vv will show all of assert comparisations instead of truncating + - py.test -vv will show all of assert comparisons instead of truncating -2.2.4 -===== +2.2.4 (2012-05-22) +================== - fix error message for rewritten assertions involving the % operator - fix issue 126: correctly match all invalid xml characters for junitxml @@ -2121,7 +2394,7 @@ Bug fixes: - fix issue with unittest: now @unittest.expectedFailure markers should be processed correctly (you can also use @pytest.mark markers) - document integration with the extended distribute/setuptools test commands -- fix issue 140: propperly get the real functions +- fix issue 140: properly get the real functions of bound classmethods for setup/teardown_class - fix issue #141: switch from the deceased paste.pocoo.org to bpaste.net - fix issue #143: call unconfigure/sessionfinish always when @@ -2129,13 +2402,13 @@ Bug fixes: - fix issue #144: better mangle test ids to junitxml classnames - upgrade distribute_setup.py to 0.6.27 -2.2.3 -===== +2.2.3 (2012-02-05) +================== -- fix uploaded package to only include neccesary files +- fix uploaded package to only include necessary files -2.2.2 -===== +2.2.2 (2012-02-05) +================== - fix issue101: wrong args to unittest.TestCase test function now produce better output @@ -2154,8 +2427,8 @@ Bug fixes: - allow adding of attributes to test reports such that it also works with distributed testing (no upgrade of pytest-xdist needed) -2.2.1 -===== +2.2.1 (2011-12-16) +================== - fix issue99 (in pytest and py) internallerrors with resultlog now produce better output - fixed by normalizing pytest_internalerror @@ -2171,14 +2444,14 @@ Bug fixes: - fix collection crash due to unknown-source collected items, thanks to Ralf Schmitt (fixed by depending on a more recent pylib) -2.2.0 -===== +2.2.0 (2011-11-18) +================== - fix issue90: introduce eager tearing down of test items so that teardown function are called earlier. - add an all-powerful metafunc.parametrize function which allows to parametrize test function arguments in multiple steps and therefore - from indepdenent plugins and palces. + from independent plugins and places. - add a @pytest.mark.parametrize helper which allows to easily call a test function with different argument values - Add examples to the "parametrize" example page, including a quick port @@ -2206,8 +2479,8 @@ Bug fixes: - simplify junitxml output code by relying on py.xml - add support for skip properties on unittest classes and functions -2.1.3 -===== +2.1.3 (2011-10-18) +================== - fix issue79: assertion rewriting failed on some comparisons in boolops - correctly handle zero length arguments (a la pytest '') @@ -2215,8 +2488,8 @@ Bug fixes: - fix issue75 / skipping test failure on jython - fix issue77 / Allow assertrepr_compare hook to apply to a subset of tests -2.1.2 -===== +2.1.2 (2011-09-24) +================== - fix assertion rewriting on files with windows newlines on some Python versions - refine test discovery by package/module name (--pyargs), thanks Florian Mayer @@ -2238,8 +2511,8 @@ Bug fixes: - fix issue61: assertion rewriting on boolean operations with 3 or more operands - you can now build a man page with "cd doc ; make man" -2.1.0 -===== +2.1.0 (2011-07-09) +================== - fix issue53 call nosestyle setup functions with correct ordering - fix issue58 and issue59: new assertion code fixes @@ -2258,8 +2531,8 @@ Bug fixes: - report KeyboardInterrupt even if interrupted during session startup - fix issue 35 - provide PDF doc version and download link from index page -2.0.3 -===== +2.0.3 (2011-05-11) +================== - fix issue38: nicer tracebacks on calls to hooks, particularly early configure/sessionstart ones @@ -2273,13 +2546,13 @@ Bug fixes: - don't require zlib (and other libs) for genscript plugin without --genscript actually being used. -- speed up skips (by not doing a full traceback represenation +- speed up skips (by not doing a full traceback representation internally) - fix issue37: avoid invalid characters in junitxml's output -2.0.2 -===== +2.0.2 (2011-03-09) +================== - tackle issue32 - speed up test runs of very quick test functions by reducing the relative overhead @@ -2321,17 +2594,17 @@ Bug fixes: this. - fixed typos in the docs (thanks Victor Garcia, Brianna Laugher) and particular - thanks to Laura Creighton who also revieved parts of the documentation. + thanks to Laura Creighton who also reviewed parts of the documentation. -- fix slighly wrong output of verbose progress reporting for classes +- fix slightly wrong output of verbose progress reporting for classes (thanks Amaury) - more precise (avoiding of) deprecation warnings for node.Class|Function accesses - avoid std unittest assertion helper code in tracebacks (thanks Ronny) -2.0.1 -===== +2.0.1 (2011-02-07) +================== - refine and unify initial capturing so that it works nicely even if the logging module is used on an early-loaded conftest.py @@ -2379,12 +2652,12 @@ Bug fixes: parametraization remains the "pytest_generate_tests" mechanism, see the docs. -2.0.0 -===== +2.0.0 (2010-11-25) +================== - pytest-2.0 is now its own package and depends on pylib-2.0 - new ability: python -m pytest / python -m pytest.main ability -- new python invcation: pytest.main(args, plugins) to load +- new python invocation: pytest.main(args, plugins) to load some custom plugins early. - try harder to run unittest test suites in a more compatible manner by deferring setup/teardown semantics to the unittest package. @@ -2424,8 +2697,8 @@ Bug fixes: - add ability to use "class" level for cached_setup helper - fix strangeness: mark.* objects are now immutable, create new instances -1.3.4 -===== +1.3.4 (2010-09-14) +================== - fix issue111: improve install documentation for windows - fix issue119: fix custom collectability of __init__.py as a module @@ -2433,8 +2706,8 @@ Bug fixes: - fix issue115: unify internal exception passthrough/catching/GeneratorExit - fix issue118: new --tb=native for presenting cpython-standard exceptions -1.3.3 -===== +1.3.3 (2010-07-30) +================== - fix issue113: assertion representation problem with triple-quoted strings (and possibly other cases) @@ -2448,8 +2721,8 @@ Bug fixes: (thanks Armin Ronacher for reporting) - remove trailing whitespace in all py/text distribution files -1.3.2 -===== +1.3.2 (2010-07-08) +================== **New features** @@ -2521,8 +2794,8 @@ Bug fixes: - fix homedir detection on Windows - ship distribute_setup.py version 0.6.13 -1.3.1 -===== +1.3.1 (2010-05-25) +================== **New features** @@ -2591,8 +2864,8 @@ Bug fixes: (and internally be more careful when presenting unexpected byte sequences) -1.3.0 -===== +1.3.0 (2010-05-05) +================== - deprecate --report option in favour of a new shorter and easier to remember -r option: it takes a string argument consisting of any @@ -2623,7 +2896,7 @@ Bug fixes: - extend and refine xfail mechanism: ``@py.test.mark.xfail(run=False)`` do not run the decorated test ``@py.test.mark.xfail(reason="...")`` prints the reason string in xfail summaries - specifiying ``--runxfail`` on command line virtually ignores xfail markers + specifying ``--runxfail`` on command line virtually ignores xfail markers - expose (previously internal) commonly useful methods: py.io.get_terminal_with() -> return terminal width @@ -2656,8 +2929,8 @@ Bug fixes: - added links to the new capturelog and coverage plugins -1.2.0 -===== +1.2.0 (2010-01-18) +================== - refined usage and options for "py.cleanup":: @@ -2695,8 +2968,8 @@ Bug fixes: - fix plugin links -1.1.1 -===== +1.1.1 (2009-11-24) +================== - moved dist/looponfailing from py.test core into a new separately released pytest-xdist plugin. @@ -2779,8 +3052,8 @@ Bug fixes: - fix docs, fix internal bin/ script generation -1.1.0 -===== +1.1.0 (2009-11-05) +================== - introduce automatic plugin registration via 'pytest11' entrypoints via setuptools' pkg_resources.iter_entry_points @@ -2848,7 +3121,7 @@ Bug fixes: * add the ability to specify a path for py.lookup to search in -* fix a funcarg cached_setup bug probably only occuring +* fix a funcarg cached_setup bug probably only occurring in distributed testing and "module" scope with teardown. * many fixes and changes for making the code base python3 compatible, @@ -2883,16 +3156,16 @@ Bug fixes: * simplified internal localpath implementation -1.0.2 -===== +1.0.2 (2009-08-27) +================== * fixing packaging issues, triggered by fedora redhat packaging, also added doc, examples and contrib dirs to the tarball. * added a documentation link to the new django plugin. -1.0.1 -===== +1.0.1 (2009-08-19) +================== * added a 'pytest_nose' plugin which handles nose.SkipTest, nose-style function/method/generator setup/teardown and @@ -2925,14 +3198,14 @@ Bug fixes: * simplified multicall mechanism and plugin architecture, renamed some internal methods and argnames -1.0.0 -===== +1.0.0 (2009-08-04) +================== * more terse reporting try to show filesystem path relatively to current dir * improve xfail output a bit -1.0.0b9 -======= +1.0.0b9 (2009-07-31) +==================== * cleanly handle and report final teardown of test setup @@ -2965,8 +3238,8 @@ Bug fixes: * item.repr_failure(excinfo) instead of item.repr_failure(excinfo, outerr) -1.0.0b8 -======= +1.0.0b8 (2009-07-22) +==================== * pytest_unittest-plugin is now enabled by default @@ -3019,8 +3292,8 @@ Bug fixes: * make __name__ == "__channelexec__" for remote_exec code -1.0.0b3 -======= +1.0.0b3 (2009-06-19) +==================== * plugin classes are removed: one now defines hooks directly in conftest.py or global pytest_*.py @@ -3114,10 +3387,10 @@ serve as a reference for developers. * fixed issue with 2.5 type representations in py.test [45483, 45484] * made that internal reporting issues displaying is done atomically in py.test [45518] -* made that non-existing files are igored by the py.lookup script [45519] +* made that non-existing files are ignored by the py.lookup script [45519] * improved exception name creation in py.test [45535] * made that less threads are used in execnet [merge in 45539] -* removed lock required for atomical reporting issue displaying in py.test +* removed lock required for atomic reporting issue displaying in py.test [45545] * removed globals from execnet [45541, 45547] * refactored cleanup mechanics, made that setDaemon is set to 1 to make atexit diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 947d4e655..71dc04d91 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -79,6 +79,16 @@ Pytest could always use more documentation. What exactly is needed? You can also edit documentation files directly in the GitHub web interface, without using a local copy. This can be convenient for small fixes. +.. note:: + Build the documentation locally with the following command: + + .. code:: bash + + $ tox -e docs + + The built documentation should be available in the ``doc/en/_build/``. + + Where 'en' refers to the documentation language. .. _submitplugin: @@ -199,13 +209,10 @@ but here is a simple overview: You need to have Python 2.7 and 3.5 available in your system. Now running tests is as simple as issuing this command:: - $ python3 runtox.py -e linting,py27,py35 + $ tox -e linting,py27,py35 This command will run tests via the "tox" tool against Python 2.7 and 3.5 - and also perform "lint" coding-style checks. ``runtox.py`` is - a thin wrapper around ``tox`` which installs from a development package - index where newer (not yet released to PyPI) versions of dependencies - (especially ``py``) might be present. + and also perform "lint" coding-style checks. #. You can now edit your local working copy. @@ -214,11 +221,11 @@ but here is a simple overview: To run tests on Python 2.7 and pass options to pytest (e.g. enter pdb on failure) to pytest you can do:: - $ python3 runtox.py -e py27 -- --pdb + $ tox -e py27 -- --pdb Or to only run tests in a particular test module on Python 3.5:: - $ python3 runtox.py -e py35 -- testing/test_config.py + $ tox -e py35 -- testing/test_config.py #. Commit and push once your tests pass and you are happy with your change(s):: diff --git a/MANIFEST.in b/MANIFEST.in index 079b225ec..c57cbd911 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -9,24 +9,28 @@ include HOWTORELEASE.rst include tox.ini include setup.py -include .coveragerc +recursive-include scripts *.py +recursive-include scripts *.bat -include plugin-test.sh -include requirements-docs.txt -include runtox.py +include .coveragerc recursive-include bench *.py recursive-include extra *.py graft testing graft doc +prune doc/en/_build exclude _pytest/impl graft _pytest/vendored_packages recursive-exclude * *.pyc *.pyo +recursive-exclude testing/.hypothesis * +recursive-exclude testing/freeze/~ * +recursive-exclude testing/freeze/build * +recursive-exclude testing/freeze/dist * -exclude appveyor/install.ps1 exclude appveyor.yml -exclude appveyor +exclude .travis.yml +prune .github diff --git a/README.rst b/README.rst index 6a5179ed5..d5650af65 100644 --- a/README.rst +++ b/README.rst @@ -24,31 +24,31 @@ An example of a simple test: .. code-block:: python # content of test_sample.py - def func(x): + def inc(x): return x + 1 def test_answer(): - assert func(3) == 5 + assert inc(3) == 5 To execute it:: $ pytest - ======= test session starts ======== + ============================= test session starts ============================= collected 1 items test_sample.py F - ======= FAILURES ======== - _______ test_answer ________ + ================================== FAILURES =================================== + _________________________________ test_answer _________________________________ def test_answer(): - > assert func(3) == 5 + > assert inc(3) == 5 E assert 4 == 5 - E + where 4 = func(3) + E + where 4 = inc(3) test_sample.py:5: AssertionError - ======= 1 failed in 0.12 seconds ======== + ========================== 1 failed in 0.04 seconds =========================== Due to ``pytest``'s detailed assertion introspection, only plain ``assert`` statements are used. See `getting-started `_ for more examples. @@ -89,7 +89,7 @@ Please use the `GitHub issue tracker `_ page for fixes and enhancements of each version. +Consult the `Changelog `__ page for fixes and enhancements of each version. License diff --git a/_pytest/__init__.py b/_pytest/__init__.py index 4d8be3d3a..dd7876046 100644 --- a/_pytest/__init__.py +++ b/_pytest/__init__.py @@ -1,2 +1,2 @@ # -__version__ = '3.0.3.dev0' +__version__ = '3.0.8.dev' diff --git a/_pytest/_argcomplete.py b/_pytest/_argcomplete.py index 955855a96..3ab679d8b 100644 --- a/_pytest/_argcomplete.py +++ b/_pytest/_argcomplete.py @@ -87,6 +87,7 @@ class FastFilesCompleter: completion.append(x[prefix_dir:]) return completion + if os.environ.get('_ARGCOMPLETE'): try: import argcomplete.completers diff --git a/_pytest/_code/code.py b/_pytest/_code/code.py index 9a6d32665..6eceb0c7f 100644 --- a/_pytest/_code/code.py +++ b/_pytest/_code/code.py @@ -1,6 +1,7 @@ import sys from inspect import CO_VARARGS, CO_VARKEYWORDS import re +from weakref import ref import py builtin_repr = repr @@ -12,6 +13,7 @@ if sys.version_info[0] >= 3: else: from ._py2traceback import format_exception_only + class Code(object): """ wrapper around Python code objects """ def __init__(self, rawcode): @@ -28,6 +30,8 @@ class Code(object): def __eq__(self, other): return self.raw == other.raw + __hash__ = None + def __ne__(self, other): return not self == other @@ -227,7 +231,7 @@ class TracebackEntry(object): return False if py.builtin.callable(tbh): - return tbh(self._excinfo) + return tbh(None if self._excinfo is None else self._excinfo()) else: return tbh @@ -339,6 +343,7 @@ class Traceback(list): l.append(entry.frame.f_locals) return None + co_equal = compile('__recursioncache_locals_1 == __recursioncache_locals_2', '?', 'eval') @@ -347,6 +352,8 @@ class ExceptionInfo(object): help for navigating the traceback. """ _striptext = '' + _assert_start_repr = "AssertionError(u\'assert " if sys.version_info[0] < 3 else "AssertionError(\'assert " + def __init__(self, tup=None, exprinfo=None): import _pytest._code if tup is None: @@ -354,8 +361,8 @@ class ExceptionInfo(object): if exprinfo is None and isinstance(tup[1], AssertionError): exprinfo = getattr(tup[1], 'msg', None) if exprinfo is None: - exprinfo = py._builtin._totext(tup[1]) - if exprinfo and exprinfo.startswith('assert '): + exprinfo = py.io.saferepr(tup[1]) + if exprinfo and exprinfo.startswith(self._assert_start_repr): self._striptext = 'AssertionError: ' self._excinfo = tup #: the exception class @@ -367,7 +374,7 @@ class ExceptionInfo(object): #: the exception type name self.typename = self.type.__name__ #: the exception traceback (_pytest._code.Traceback instance) - self.traceback = _pytest._code.Traceback(self.tb, excinfo=self) + self.traceback = _pytest._code.Traceback(self.tb, excinfo=ref(self)) def __repr__(self): return "" % (self.typename, len(self.traceback)) @@ -620,16 +627,23 @@ class FormattedExcinfo(object): e = excinfo.value descr = None while e is not None: - reprtraceback = self.repr_traceback(excinfo) - reprcrash = excinfo._getreprcrash() + if excinfo: + reprtraceback = self.repr_traceback(excinfo) + reprcrash = excinfo._getreprcrash() + else: + # fallback to native repr if the exception doesn't have a traceback: + # ExceptionInfo objects require a full traceback to work + reprtraceback = ReprTracebackNative(py.std.traceback.format_exception(type(e), e, None)) + reprcrash = None + repr_chain += [(reprtraceback, reprcrash, descr)] if e.__cause__ is not None: e = e.__cause__ - excinfo = ExceptionInfo((type(e), e, e.__traceback__)) + excinfo = ExceptionInfo((type(e), e, e.__traceback__)) if e.__traceback__ else None descr = 'The above exception was the direct cause of the following exception:' elif e.__context__ is not None: e = e.__context__ - excinfo = ExceptionInfo((type(e), e, e.__traceback__)) + excinfo = ExceptionInfo((type(e), e, e.__traceback__)) if e.__traceback__ else None descr = 'During handling of the above exception, another exception occurred:' else: e = None @@ -835,6 +849,7 @@ def getrawcode(obj, trycall=True): return x return obj + if sys.version_info[:2] >= (3, 5): # RecursionError introduced in 3.5 def is_recursion_error(excinfo): return excinfo.errisinstance(RecursionError) # noqa diff --git a/_pytest/_code/source.py b/_pytest/_code/source.py index a1521f8a2..fcec0f5ca 100644 --- a/_pytest/_code/source.py +++ b/_pytest/_code/source.py @@ -4,7 +4,6 @@ from bisect import bisect_right import sys import inspect, tokenize import py -from types import ModuleType cpy_compile = compile try: @@ -52,22 +51,21 @@ class Source(object): return str(self) == other return False + __hash__ = None + def __getitem__(self, key): if isinstance(key, int): return self.lines[key] else: if key.step not in (None, 1): raise IndexError("cannot slice a Source with a step") - return self.__getslice__(key.start, key.stop) + newsource = Source() + newsource.lines = self.lines[key.start:key.stop] + return newsource def __len__(self): return len(self.lines) - def __getslice__(self, start, end): - newsource = Source() - newsource.lines = self.lines[start:end] - return newsource - def strip(self): """ return new source object with trailing and leading blank lines removed. @@ -193,14 +191,6 @@ class Source(object): if flag & _AST_FLAG: return co lines = [(x + "\n") for x in self.lines] - if sys.version_info[0] >= 3: - # XXX py3's inspect.getsourcefile() checks for a module - # and a pep302 __loader__ ... we don't have a module - # at code compile-time so we need to fake it here - m = ModuleType("_pycodecompile_pseudo_module") - py.std.inspect.modulesbyfile[filename] = None - py.std.sys.modules[None] = m - m.__loader__ = 1 py.std.linecache.cache[filename] = (1, None, lines, filename) return co @@ -266,6 +256,7 @@ def findsource(obj): source.lines = [line.rstrip() for line in sourcelines] return source, lineno + def getsource(obj, **kwargs): import _pytest._code obj = _pytest._code.getrawcode(obj) @@ -276,6 +267,7 @@ def getsource(obj, **kwargs): assert isinstance(strsrc, str) return Source(strsrc, **kwargs) + def deindent(lines, offset=None): if offset is None: for line in lines: @@ -289,6 +281,7 @@ def deindent(lines, offset=None): if offset == 0: return list(lines) newlines = [] + def readline_generator(lines): for line in lines: yield line + '\n' diff --git a/_pytest/assertion/__init__.py b/_pytest/assertion/__init__.py index fd1ebe2c1..3f14a7ae7 100644 --- a/_pytest/assertion/__init__.py +++ b/_pytest/assertion/__init__.py @@ -29,7 +29,7 @@ def pytest_namespace(): def register_assert_rewrite(*names): - """Register a module name to be rewritten on import. + """Register one or more module names to be rewritten on import. This function will make sure that this module or all modules inside the package will get their assert statements rewritten. @@ -80,10 +80,12 @@ def install_importhook(config): config._assertstate.hook = hook = rewrite.AssertionRewritingHook(config) sys.meta_path.insert(0, hook) config._assertstate.trace('installed rewrite import hook') + def undo(): hook = config._assertstate.hook if hook is not None and hook in sys.meta_path: sys.meta_path.remove(hook) + config.add_cleanup(undo) return hook diff --git a/_pytest/assertion/rewrite.py b/_pytest/assertion/rewrite.py index 80d6ee3ba..7408c4746 100644 --- a/_pytest/assertion/rewrite.py +++ b/_pytest/assertion/rewrite.py @@ -51,6 +51,7 @@ class AssertionRewritingHook(object): self.fnpats = config.getini("python_files") self.session = None self.modules = {} + self._rewritten_names = set() self._register_with_pkg_resources() self._must_rewrite = set() @@ -79,7 +80,12 @@ class AssertionRewritingHook(object): tp = desc[2] if tp == imp.PY_COMPILED: if hasattr(imp, "source_from_cache"): - fn = imp.source_from_cache(fn) + try: + fn = imp.source_from_cache(fn) + except ValueError: + # Python 3 doesn't like orphaned but still-importable + # .pyc files. + fn = fn[:-1] else: fn = fn[:-1] elif tp != imp.PY_SOURCE: @@ -92,6 +98,8 @@ class AssertionRewritingHook(object): if not self._should_rewrite(name, fn_pypath, state): return None + self._rewritten_names.add(name) + # The requested module looks like a test file, so rewrite it. This is # the most magical part of the process: load the source, rewrite the # asserts, and load the rewritten source. We also cache the rewritten @@ -178,14 +186,15 @@ class AssertionRewritingHook(object): """ already_imported = set(names).intersection(set(sys.modules)) if already_imported: - self._warn_already_imported(already_imported) + for name in already_imported: + if name not in self._rewritten_names: + self._warn_already_imported(name) self._must_rewrite.update(names) - def _warn_already_imported(self, names): + def _warn_already_imported(self, name): self.config.warn( 'P1', - 'Modules are already imported so can not be re-written: %s' % - ','.join(names)) + 'Module already imported so can not be re-written: %s' % name) def load_module(self, name): # If there is an existing module object named 'fullname' in @@ -206,7 +215,8 @@ class AssertionRewritingHook(object): mod.__loader__ = self py.builtin.exec_(co, mod.__dict__) except: - del sys.modules[name] + if name in sys.modules: + del sys.modules[name] raise return sys.modules[name] @@ -271,6 +281,7 @@ def _write_pyc(state, co, source_stat, pyc): fp.close() return True + RN = "\r\n".encode("utf-8") N = "\n".encode("utf-8") diff --git a/_pytest/assertion/util.py b/_pytest/assertion/util.py index 2481cf34c..4a0a4e431 100644 --- a/_pytest/assertion/util.py +++ b/_pytest/assertion/util.py @@ -105,7 +105,7 @@ except NameError: def assertrepr_compare(config, op, left, right): """Return specialised explanations for some operators/operands""" width = 80 - 15 - len(op) - 2 # 15 chars indentation, 1 space around op - left_repr = py.io.saferepr(left, maxsize=int(width/2)) + left_repr = py.io.saferepr(left, maxsize=int(width//2)) right_repr = py.io.saferepr(right, maxsize=width-len(left_repr)) summary = u('%s %s %s') % (ecu(left_repr), op, ecu(right_repr)) diff --git a/_pytest/cacheprovider.py b/_pytest/cacheprovider.py index 0657001f2..893f0eae5 100755 --- a/_pytest/cacheprovider.py +++ b/_pytest/cacheprovider.py @@ -1,7 +1,7 @@ """ merged implementation of the cache provider -the name cache was not choosen to ensure pluggy automatically +the name cache was not chosen to ensure pluggy automatically ignores the external pytest-cache """ diff --git a/_pytest/capture.py b/_pytest/capture.py index 9f60db6ac..07ec662b6 100644 --- a/_pytest/capture.py +++ b/_pytest/capture.py @@ -13,6 +13,7 @@ import py import pytest from py.io import TextIO +from io import UnsupportedOperation unicode = py.builtin.text patchsysdict = {0: 'stdin', 1: 'stdout', 2: 'stderr'} @@ -152,6 +153,7 @@ class CaptureManager: item.add_report_section(when, "stdout", out) item.add_report_section(when, "stderr", err) + error_capsysfderror = "cannot use capsys and capfd at the same time" @@ -447,7 +449,7 @@ class DontReadFromInput: __iter__ = read def fileno(self): - raise ValueError("redirected Stdin is pseudofile, has no fileno()") + raise UnsupportedOperation("redirected Stdin is pseudofile, has no fileno()") def isatty(self): return False diff --git a/_pytest/compat.py b/_pytest/compat.py index 1d8c2f331..4f2013dac 100644 --- a/_pytest/compat.py +++ b/_pytest/compat.py @@ -19,6 +19,7 @@ except ImportError: # pragma: no cover # Only available in Python 3.4+ or as a backport enum = None + _PY3 = sys.version_info > (3, 0) _PY2 = not _PY3 @@ -26,6 +27,9 @@ _PY2 = not _PY3 NoneType = type(None) NOTSET = object() +PY36 = sys.version_info[:2] >= (3, 6) +MODULE_NOT_FOUND_ERROR = 'ModuleNotFoundError' if PY36 else 'ImportError' + if hasattr(inspect, 'signature'): def _format_args(func): return str(inspect.signature(func)) @@ -42,11 +46,18 @@ REGEX_TYPE = type(re.compile('')) def is_generator(func): - try: - return _pytest._code.getrawcode(func).co_flags & 32 # generator function - except AttributeError: # builtin functions have no bytecode - # assume them to not be generators - return False + genfunc = inspect.isgeneratorfunction(func) + return genfunc and not iscoroutinefunction(func) + + +def iscoroutinefunction(func): + """Return True if func is a decorated coroutine function. + + Note: copied and modified from Python 3.5's builtin couroutines.py to avoid import asyncio directly, + which in turns also initializes the "logging" module as side-effect (see issue #8). + """ + return (getattr(func, '_is_coroutine', False) or + (hasattr(inspect, 'iscoroutinefunction') and inspect.iscoroutinefunction(func))) def getlocation(function, curdir): @@ -213,4 +224,20 @@ def _is_unittest_unexpected_success_a_failure(): Changed in version 3.4: Returns False if there were any unexpectedSuccesses from tests marked with the expectedFailure() decorator. """ - return sys.version_info >= (3, 4) \ No newline at end of file + return sys.version_info >= (3, 4) + + +if _PY3: + def safe_str(v): + """returns v as string""" + return str(v) +else: + def safe_str(v): + """returns v as string, converting to ascii if necessary""" + try: + return str(v) + except UnicodeError: + if not isinstance(v, unicode): + v = unicode(v) + errors = 'replace' + return v.encode('utf-8', errors) diff --git a/_pytest/config.py b/_pytest/config.py index 55ad538d3..c73416f0a 100644 --- a/_pytest/config.py +++ b/_pytest/config.py @@ -5,7 +5,6 @@ import traceback import types import warnings -import pkg_resources import py # DON't import pytest here because it causes import cycle troubles import sys, os @@ -13,6 +12,7 @@ import _pytest._code import _pytest.hookspec # the extension point definitions import _pytest.assertion from _pytest._pluggy import PluginManager, HookimplMarker, HookspecMarker +from _pytest.compat import safe_str hookimpl = HookimplMarker("pytest") hookspec = HookspecMarker("pytest") @@ -65,9 +65,33 @@ def main(args=None, plugins=None): class cmdline: # compatibility namespace main = staticmethod(main) + class UsageError(Exception): """ error in pytest usage or invocation""" + +def filename_arg(path, optname): + """ Argparse type validator for filename arguments. + + :path: path of filename + :optname: name of the option + """ + if os.path.isdir(path): + raise UsageError("{0} must be a filename, given: {1}".format(optname, path)) + return path + + +def directory_arg(path, optname): + """Argparse type validator for directory arguments. + + :path: path of directory + :optname: name of the option + """ + if not os.path.isdir(path): + raise UsageError("{0} must be a directory, given: {1}".format(optname, path)) + return path + + _preinit = [] default_plugins = ( @@ -378,35 +402,30 @@ class PytestPluginManager(PluginManager): self._import_plugin_specs(os.environ.get("PYTEST_PLUGINS")) def consider_module(self, mod): - plugins = getattr(mod, 'pytest_plugins', []) - if isinstance(plugins, str): - plugins = [plugins] - self.rewrite_hook.mark_rewrite(*plugins) - self._import_plugin_specs(plugins) + self._import_plugin_specs(getattr(mod, 'pytest_plugins', [])) def _import_plugin_specs(self, spec): - if spec: - if isinstance(spec, str): - spec = spec.split(",") - for import_spec in spec: - self.import_plugin(import_spec) + plugins = _get_plugin_specs_as_list(spec) + for import_spec in plugins: + self.import_plugin(import_spec) def import_plugin(self, modname): # most often modname refers to builtin modules, e.g. "pytester", # "terminal" or "capture". Those plugins are registered under their # basename for historic purposes but must be imported with the # _pytest prefix. - assert isinstance(modname, str) + assert isinstance(modname, str), "module name as string required, got %r" % modname if self.get_plugin(modname) is not None: return if modname in builtin_plugins: importspec = "_pytest." + modname else: importspec = modname + self.rewrite_hook.mark_rewrite(importspec) try: __import__(importspec) except ImportError as e: - new_exc = ImportError('Error importing plugin "%s": %s' % (modname, e)) + new_exc = ImportError('Error importing plugin "%s": %s' % (modname, safe_str(e.args[0]))) # copy over name and path attributes for attr in ('name', 'path'): if hasattr(e, attr): @@ -423,6 +442,24 @@ class PytestPluginManager(PluginManager): self.consider_module(mod) +def _get_plugin_specs_as_list(specs): + """ + Parses a list of "plugin specs" and returns a list of plugin names. + + Plugin specs can be given as a list of strings separated by "," or already as a list/tuple in + which case it is returned as a list. Specs can also be `None` in which case an + empty list is returned. + """ + if specs is not None: + if isinstance(specs, str): + specs = specs.split(',') if specs else [] + if not isinstance(specs, (list, tuple)): + raise UsageError("Plugin specs must be a ','-separated string or a " + "list/tuple of strings for plugin names. Given: %r" % specs) + return list(specs) + return [] + + class Parser: """ Parser for command line arguments and ini-file values. @@ -594,7 +631,7 @@ class Argument: if typ == 'choice': warnings.warn( 'type argument to addoption() is a string %r.' - ' For parsearg this is optional and when supplied ' + ' For parsearg this is optional and when supplied' ' should be a type.' ' (options: %s)' % (typ, names), DeprecationWarning, @@ -793,7 +830,7 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter): if len(option) == 2 or option[2] == ' ': return_list.append(option) if option[2:] == short_long.get(option.replace('-', '')): - return_list.append(option.replace(' ', '=')) + return_list.append(option.replace(' ', '=', 1)) action._formatted_action_invocation = ', '.join(return_list) return action._formatted_action_invocation @@ -818,9 +855,11 @@ class Notset: def __repr__(self): return "" + notset = Notset() FILE_OR_DIR = 'file_or_dir' + class Config(object): """ access to configuration values, pluginmanager and plugin hooks. """ @@ -838,14 +877,17 @@ class Config(object): self.trace = self.pluginmanager.trace.root.get("config") self.hook = self.pluginmanager.hook self._inicache = {} + self._override_ini = () self._opt2dest = {} self._cleanup = [] self._warn = self.pluginmanager._warn self.pluginmanager.register(self, "pytestconfig") self._configured = False + def do_setns(dic): import pytest setns(pytest, dic) + self.hook.pytest_namespace.call_historic(do_setns, {}) self.hook.pytest_addoption.call_historic(kwargs=dict(parser=self._parser)) @@ -936,6 +978,7 @@ class Config(object): self.invocation_dir = py.path.local() self._parser.addini('addopts', 'extra command line options', 'args') self._parser.addini('minversion', 'minimally required pytest version') + self._override_ini = ns.override_ini or () def _consider_importhook(self, args, entrypoint_name): """Install the PEP 302 import hook if using assertion re-writing. @@ -952,6 +995,7 @@ class Config(object): except SystemError: mode = 'plain' else: + import pkg_resources self.pluginmanager.rewrite_hook = hook for entrypoint in pkg_resources.iter_entry_points('pytest11'): # 'RECORD' available for plugins installed normally (pip install) @@ -1000,6 +1044,7 @@ class Config(object): self.pluginmanager.load_setuptools_entrypoints(entrypoint_name) self.pluginmanager.consider_env() self.known_args_namespace = ns = self._parser.parse_known_args(args, namespace=self.option.copy()) + confcutdir = self.known_args_namespace.confcutdir if self.known_args_namespace.confcutdir is None and self.inifile: confcutdir = py.path.local(self.inifile).dirname self.known_args_namespace.confcutdir = confcutdir @@ -1116,12 +1161,14 @@ class Config(object): # and -o foo1=bar1 -o foo2=bar2 options # always use the last item if multiple value set for same ini-name, # e.g. -o foo=bar1 -o foo=bar2 will set foo to bar2 - if self.getoption("override_ini", None): - for ini_config_list in self.option.override_ini: - for ini_config in ini_config_list: + for ini_config_list in self._override_ini: + for ini_config in ini_config_list: + try: (key, user_ini_value) = ini_config.split("=", 1) - if key == name: - value = user_ini_value + except ValueError: + raise UsageError("-o/--override-ini expects option=value style.") + if key == name: + value = user_ini_value return value def getoption(self, name, default=notset, skip=False): @@ -1195,25 +1242,20 @@ def getcfg(args, warnfunc=None): return None, None, None -def get_common_ancestor(args): - # args are what we get after early command line parsing (usually - # strings, but can be py.path.local objects as well) +def get_common_ancestor(paths): common_ancestor = None - for arg in args: - if str(arg)[0] == "-": - continue - p = py.path.local(arg) - if not p.exists(): + for path in paths: + if not path.exists(): continue if common_ancestor is None: - common_ancestor = p + common_ancestor = path else: - if p.relto(common_ancestor) or p == common_ancestor: + if path.relto(common_ancestor) or path == common_ancestor: continue - elif common_ancestor.relto(p): - common_ancestor = p + elif common_ancestor.relto(path): + common_ancestor = path else: - shared = p.common(common_ancestor) + shared = path.common(common_ancestor) if shared is not None: common_ancestor = shared if common_ancestor is None: @@ -1224,9 +1266,29 @@ def get_common_ancestor(args): def get_dirs_from_args(args): - return [d for d in (py.path.local(x) for x in args - if not str(x).startswith("-")) - if d.exists()] + def is_option(x): + return str(x).startswith('-') + + def get_file_part_from_node_id(x): + return str(x).split('::')[0] + + def get_dir_from_path(path): + if path.isdir(): + return path + return py.path.local(path.dirname) + + # These look like paths but may not exist + possible_paths = ( + py.path.local(get_file_part_from_node_id(arg)) + for arg in args + if not is_option(arg) + ) + + return [ + get_dir_from_path(path) + for path in possible_paths + if path.exists() + ] def determine_setup(inifile, args, warnfunc=None): diff --git a/_pytest/debugging.py b/_pytest/debugging.py index 299b05028..d96170bd8 100644 --- a/_pytest/debugging.py +++ b/_pytest/debugging.py @@ -31,10 +31,12 @@ def pytest_configure(config): pytestPDB._pdb_cls = pdb_cls old = (pdb.set_trace, pytestPDB._pluginmanager) + def fin(): pdb.set_trace, pytestPDB._pluginmanager = old pytestPDB._config = None pytestPDB._pdb_cls = pdb.Pdb + pdb.set_trace = pytest.set_trace pytestPDB._pluginmanager = config.pluginmanager pytestPDB._config = config diff --git a/_pytest/fixtures.py b/_pytest/fixtures.py index cf3e9dd93..248708f6e 100644 --- a/_pytest/fixtures.py +++ b/_pytest/fixtures.py @@ -14,6 +14,7 @@ from _pytest.compat import ( getfslineno, get_real_func, is_generator, isclass, getimfunc, getlocation, getfuncargnames, + safe_getattr, ) def pytest_sessionstart(session): @@ -32,11 +33,13 @@ scope2props["function"] = scope2props["instance"] + ("function", "keywords") def scopeproperty(name=None, doc=None): def decoratescope(func): scopename = name or func.__name__ + def provide(self): if func.__name__ in scope2props[self.scope]: return func(self) raise AttributeError("%s not available in %s-scoped context" % ( scopename, self.scope)) + return property(provide, None, None, func.__doc__) return decoratescope @@ -122,8 +125,6 @@ def getfixturemarker(obj): exceptions.""" try: return getattr(obj, "_pytestfixturefunction", None) - except KeyboardInterrupt: - raise except Exception: # some objects raise errors like request (from flask import request) # we don't expect them to be fixture functions @@ -599,12 +600,29 @@ class ScopeMismatchError(Exception): which has a lower scope (e.g. a Session one calls a function one) """ + scopes = "session module class function".split() scopenum_function = scopes.index("function") + + def scopemismatch(currentscope, newscope): return scopes.index(newscope) > scopes.index(currentscope) +def scope2index(scope, descr, where=None): + """Look up the index of ``scope`` and raise a descriptive value error + if not defined. + """ + try: + return scopes.index(scope) + except ValueError: + raise ValueError( + "{0} {1}has an unsupported scope value '{2}'".format( + descr, 'from {0} '.format(where) if where else '', + scope) + ) + + class FixtureLookupError(LookupError): """ could not return a requested Fixture (missing or invalid). """ def __init__(self, argname, request, msg=None): @@ -703,6 +721,7 @@ def call_fixture_func(fixturefunc, request, kwargs): res = fixturefunc(**kwargs) return res + class FixtureDef: """ A container for a factory definition. """ def __init__(self, fixturemanager, baseid, argname, func, scope, params, @@ -713,7 +732,11 @@ class FixtureDef: self.func = func self.argname = argname self.scope = scope - self.scopenum = scopes.index(scope or "function") + self.scopenum = scope2index( + scope or "function", + descr='fixture {0}'.format(func.__name__), + where=baseid + ) self.params = params startindex = unittest and 1 or None self.argnames = getfuncargnames(func, startindex=startindex) @@ -1044,7 +1067,9 @@ class FixtureManager: self._holderobjseen.add(holderobj) autousenames = [] for name in dir(holderobj): - obj = getattr(holderobj, name, None) + # The attribute can be an arbitrary descriptor, so the attribute + # access below can raise. safe_getatt() ignores such exceptions. + obj = safe_getattr(holderobj, name, None) # fixture functions have a pytest_funcarg__ prefix (pre-2.3 style) # or are "@pytest.fixture" marked marker = getfixturemarker(obj) diff --git a/_pytest/helpconfig.py b/_pytest/helpconfig.py index 84f419a08..6e66b11c4 100644 --- a/_pytest/helpconfig.py +++ b/_pytest/helpconfig.py @@ -23,7 +23,7 @@ def pytest_addoption(parser): group._addoption( '-o', '--override-ini', nargs='*', dest="override_ini", action="append", - help="override config option, e.g. `-o xfail_strict=True`.") + help="override config option with option=value style, e.g. `-o xfail_strict=True`.") @pytest.hookimpl(hookwrapper=True) @@ -41,12 +41,14 @@ def pytest_cmdline_parse(): config.trace.root.setwriter(debugfile.write) undo_tracing = config.pluginmanager.enable_tracing() sys.stderr.write("writing pytestdebug information to %s\n" % path) + def unset_tracing(): debugfile.close() sys.stderr.write("wrote pytestdebug information to %s\n" % debugfile.name) config.trace.root.setwriter(None) undo_tracing() + config.add_cleanup(unset_tracing) def pytest_cmdline_main(config): @@ -71,8 +73,8 @@ def showhelp(config): tw.write(config._parser.optparser.format_help()) tw.line() tw.line() - tw.line("[pytest] ini-options in the next " - "pytest.ini|tox.ini|setup.cfg file:") + tw.line("[pytest] ini-options in the first " + "pytest.ini|tox.ini|setup.cfg file found:") tw.line() for name in config._parser._ininames: diff --git a/_pytest/hookspec.py b/_pytest/hookspec.py index b5f51eccf..552a06575 100644 --- a/_pytest/hookspec.py +++ b/_pytest/hookspec.py @@ -246,7 +246,7 @@ def pytest_unconfigure(config): # ------------------------------------------------------------------------- -# hooks for customising the assert methods +# hooks for customizing the assert methods # ------------------------------------------------------------------------- def pytest_assertrepr_compare(config, op, left, right): @@ -255,7 +255,7 @@ def pytest_assertrepr_compare(config, op, left, right): Return None for no custom explanation, otherwise return a list of strings. The strings will be joined by newlines but any newlines *in* a string will be escaped. Note that all but the first line will - be indented sligthly, the intention is for the first line to be a summary. + be indented slightly, the intention is for the first line to be a summary. """ # ------------------------------------------------------------------------- @@ -263,7 +263,14 @@ def pytest_assertrepr_compare(config, op, left, right): # ------------------------------------------------------------------------- def pytest_report_header(config, startdir): - """ return a string to be displayed as header info for terminal reporting.""" + """ return a string to be displayed as header info for terminal reporting. + + .. note:: + + This function should be implemented only in plugins or ``conftest.py`` + files situated at the tests root directory due to how pytest + :ref:`discovers plugins during startup `. + """ @hookspec(firstresult=True) def pytest_report_teststatus(report): diff --git a/_pytest/junitxml.py b/_pytest/junitxml.py index da190ff21..3f371c9d3 100644 --- a/_pytest/junitxml.py +++ b/_pytest/junitxml.py @@ -8,12 +8,14 @@ Based on initial code from Ross Lawley. # Output conforms to https://github.com/jenkinsci/xunit-plugin/blob/master/ # src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd +import functools import py import os import re import sys import time import pytest +from _pytest.config import filename_arg # Python 2.X and 3.X compatibility if sys.version_info[0] < 3: @@ -27,6 +29,7 @@ else: class Junit(py.xml.Namespace): pass + # We need to get the subset of the invalid unicode ranges according to # XML 1.0 which are valid in this python build. Hence we calculate # this dynamically instead of hardcoding it. The spec range of valid @@ -116,7 +119,7 @@ class _NodeReporter(object): node = kind(data, message=message) self.append(node) - def _write_captured_output(self, report): + def write_captured_output(self, report): for capname in ('out', 'err'): content = getattr(report, 'capstd' + capname) if content: @@ -125,7 +128,6 @@ class _NodeReporter(object): def append_pass(self, report): self.add_stats('passed') - self._write_captured_output(report) def append_failure(self, report): # msg = str(report.longrepr.reprtraceback.extraline) @@ -144,7 +146,6 @@ class _NodeReporter(object): fail = Junit.failure(message=message) fail.append(bin_xml_escape(report.longrepr)) self.append(fail) - self._write_captured_output(report) def append_collect_error(self, report): # msg = str(report.longrepr.reprtraceback.extraline) @@ -156,9 +157,12 @@ class _NodeReporter(object): Junit.skipped, "collection skipped", report.longrepr) def append_error(self, report): + if getattr(report, 'when', None) == 'teardown': + msg = "test teardown failure" + else: + msg = "test setup failure" self._add_simple( - Junit.error, "test setup failure", report.longrepr) - self._write_captured_output(report) + Junit.error, msg, report.longrepr) def append_skipped(self, report): if hasattr(report, "wasxfail"): @@ -173,7 +177,7 @@ class _NodeReporter(object): Junit.skipped("%s:%s: %s" % (filename, lineno, skipreason), type="pytest.skip", message=skipreason)) - self._write_captured_output(report) + self.write_captured_output(report) def finalize(self): data = self.to_xml().unicode(indent=0) @@ -209,6 +213,7 @@ def pytest_addoption(parser): action="store", dest="xmlpath", metavar="path", + type=functools.partial(filename_arg, optname="--junitxml"), default=None, help="create junit-xml style report file at given path.") group.addoption( @@ -337,6 +342,8 @@ class LogXML(object): reporter.append_skipped(report) self.update_testcase_duration(report) if report.when == "teardown": + reporter = self._opentestcase(report) + reporter.write_captured_output(report) self.finalize(report) def update_testcase_duration(self, report): diff --git a/_pytest/main.py b/_pytest/main.py index 5771a1699..b66b661c8 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -1,4 +1,5 @@ """ core implementation of testing process: init, session, runtest loop. """ +import functools import os import sys @@ -11,6 +12,7 @@ try: except ImportError: from UserDict import DictMixin as MappingMixin +from _pytest.config import directory_arg from _pytest.runner import collect_one_node tracebackcutdir = py.path.local(_pytest.__file__).dirpath() @@ -58,7 +60,7 @@ def pytest_addoption(parser): # when changing this to --conf-cut-dir, config.py Conftest.setinitial # needs upgrading as well group.addoption('--confcutdir', dest="confcutdir", default=None, - metavar="dir", + metavar="dir", type=functools.partial(directory_arg, optname="--confcutdir"), help="only load conftest.py's relative to specified dir.") group.addoption('--noconftest', action="store_true", dest="noconftest", default=False, @@ -79,7 +81,7 @@ def pytest_namespace(): def pytest_configure(config): - pytest.config = config # compatibiltiy + pytest.config = config # compatibility def wrap_session(config, doit): @@ -188,12 +190,22 @@ class FSHookProxy: self.__dict__[name] = x return x -def compatproperty(name): - def fget(self): - # deprecated - use pytest.name - return getattr(pytest, name) +class _CompatProperty(object): + def __init__(self, name): + self.name = name + + def __get__(self, obj, owner): + if obj is None: + return self + + # TODO: reenable in the features branch + # warnings.warn( + # "usage of {owner!r}.{name} is deprecated, please use pytest.{name} instead".format( + # name=self.name, owner=type(owner).__name__), + # PendingDeprecationWarning, stacklevel=2) + return getattr(pytest, self.name) + - return property(fget) class NodeKeywords(MappingMixin): def __init__(self, node): @@ -265,19 +277,23 @@ class Node(object): """ fspath sensitive hook proxy used to call pytest hooks""" return self.session.gethookproxy(self.fspath) - Module = compatproperty("Module") - Class = compatproperty("Class") - Instance = compatproperty("Instance") - Function = compatproperty("Function") - File = compatproperty("File") - Item = compatproperty("Item") + Module = _CompatProperty("Module") + Class = _CompatProperty("Class") + Instance = _CompatProperty("Instance") + Function = _CompatProperty("Function") + File = _CompatProperty("File") + Item = _CompatProperty("Item") def _getcustomclass(self, name): - cls = getattr(self, name) - if cls != getattr(pytest, name): - py.log._apiwarn("2.0", "use of node.%s is deprecated, " - "use pytest_pycollect_makeitem(...) to create custom " - "collection nodes" % name) + maybe_compatprop = getattr(type(self), name) + if isinstance(maybe_compatprop, _CompatProperty): + return getattr(pytest, name) + else: + cls = getattr(self, name) + # TODO: reenable in the features branch + # warnings.warn("use of node.%s is deprecated, " + # "use pytest_pycollect_makeitem(...) to create custom " + # "collection nodes" % name, category=DeprecationWarning) return cls def __repr__(self): @@ -535,7 +551,6 @@ class Session(FSCollector): def __init__(self, config): FSCollector.__init__(self, config.rootdir, parent=None, config=config, session=self) - self._fs2hookproxy = {} self.testsfailed = 0 self.testscollected = 0 self.shouldstop = False @@ -566,23 +581,18 @@ class Session(FSCollector): return path in self._initialpaths def gethookproxy(self, fspath): - try: - return self._fs2hookproxy[fspath] - except KeyError: - # check if we have the common case of running - # hooks with all conftest.py filesall conftest.py - pm = self.config.pluginmanager - my_conftestmodules = pm._getconftestmodules(fspath) - remove_mods = pm._conftest_plugins.difference(my_conftestmodules) - if remove_mods: - # one or more conftests are not in use at this fspath - proxy = FSHookProxy(fspath, pm, remove_mods) - else: - # all plugis are active for this fspath - proxy = self.config.hook - - self._fs2hookproxy[fspath] = proxy - return proxy + # check if we have the common case of running + # hooks with all conftest.py filesall conftest.py + pm = self.config.pluginmanager + my_conftestmodules = pm._getconftestmodules(fspath) + remove_mods = pm._conftest_plugins.difference(my_conftestmodules) + if remove_mods: + # one or more conftests are not in use at this fspath + proxy = FSHookProxy(fspath, pm, remove_mods) + else: + # all plugis are active for this fspath + proxy = self.config.hook + return proxy def perform_collect(self, args=None, genitems=True): hook = self.config.hook @@ -704,10 +714,9 @@ class Session(FSCollector): path = self.config.invocation_dir.join(relpath, abs=True) if not path.check(): if self.config.option.pyargs: - msg = "file or package not found: " + raise pytest.UsageError("file or package not found: " + arg + " (missing __init__.py?)") else: - msg = "file not found: " - raise pytest.UsageError(msg + arg) + raise pytest.UsageError("file not found: " + arg) parts[0] = path return parts diff --git a/_pytest/mark.py b/_pytest/mark.py index 640c4e61c..d406bca6b 100644 --- a/_pytest/mark.py +++ b/_pytest/mark.py @@ -54,6 +54,8 @@ def pytest_cmdline_main(config): tw.line() config._ensure_unconfigure() return 0 + + pytest_cmdline_main.tryfirst = True @@ -64,7 +66,7 @@ def pytest_collection_modifyitems(items, config): return # pytest used to allow "-" for negating # but today we just allow "-" at the beginning, use "not" instead - # we probably remove "-" alltogether soon + # we probably remove "-" altogether soon if keywordexpr.startswith("-"): keywordexpr = "not " + keywordexpr[1:] selectuntil = False diff --git a/_pytest/monkeypatch.py b/_pytest/monkeypatch.py index 852e72bed..2651cf044 100644 --- a/_pytest/monkeypatch.py +++ b/_pytest/monkeypatch.py @@ -11,7 +11,7 @@ RE_IMPORT_ERROR_NAME = re.compile("^No module named (.*)$") @pytest.fixture -def monkeypatch(request): +def monkeypatch(): """The returned ``monkeypatch`` fixture provides these helper methods to modify objects, dictionaries or os.environ:: @@ -30,8 +30,8 @@ def monkeypatch(request): will be raised if the set/deletion operation has no target. """ mpatch = MonkeyPatch() - request.addfinalizer(mpatch.undo) - return mpatch + yield mpatch + mpatch.undo() def resolve(name): diff --git a/_pytest/pastebin.py b/_pytest/pastebin.py index 4ec62d022..9f1cf9063 100644 --- a/_pytest/pastebin.py +++ b/_pytest/pastebin.py @@ -11,6 +11,7 @@ def pytest_addoption(parser): choices=['failed', 'all'], help="send failed|all info to bpaste.net pastebin service.") + @pytest.hookimpl(trylast=True) def pytest_configure(config): import py @@ -23,13 +24,16 @@ def pytest_configure(config): # pastebin file will be utf-8 encoded binary file config._pastebinfile = tempfile.TemporaryFile('w+b') oldwrite = tr._tw.write + def tee_write(s, **kwargs): oldwrite(s, **kwargs) if py.builtin._istext(s): s = s.encode('utf-8') config._pastebinfile.write(s) + tr._tw.write = tee_write + def pytest_unconfigure(config): if hasattr(config, '_pastebinfile'): # get terminal contents and delete file @@ -45,6 +49,7 @@ def pytest_unconfigure(config): pastebinurl = create_new_paste(sessionlog) tr.write_line("pastebin session-log: %s\n" % pastebinurl) + def create_new_paste(contents): """ Creates a new paste using bpaste.net service. @@ -72,6 +77,7 @@ def create_new_paste(contents): else: return 'bad response: ' + response + def pytest_terminal_summary(terminalreporter): import _pytest.config if terminalreporter.config.option.pastebin != "failed": diff --git a/_pytest/pytester.py b/_pytest/pytester.py index 651160cc7..de24f9044 100644 --- a/_pytest/pytester.py +++ b/_pytest/pytester.py @@ -332,7 +332,7 @@ def testdir(request, tmpdir_factory): return Testdir(request, tmpdir_factory) -rex_outcome = re.compile("(\d+) ([\w-]+)") +rex_outcome = re.compile(r"(\d+) ([\w-]+)") class RunResult: """The result of running a command. @@ -367,6 +367,7 @@ class RunResult: for num, cat in outcomes: d[cat] = int(num) return d + raise ValueError("Pytest terminal report not found") def assert_outcomes(self, passed=0, skipped=0, failed=0): """ assert that the specified outcomes appear with the respective @@ -446,9 +447,9 @@ class Testdir: the module is re-imported. """ for name in set(sys.modules).difference(self._savemodulekeys): - # it seems zope.interfaces is keeping some state - # (used by twisted related tests) - if name != "zope.interface": + # zope.interface (used by twisted-related tests) keeps internal + # state and can't be deleted + if not name.startswith("zope.interface"): del sys.modules[name] def make_hook_recorder(self, pluginmanager): @@ -478,11 +479,14 @@ class Testdir: ret = None for name, value in items: p = self.tmpdir.join(name).new(ext=ext) + p.dirpath().ensure_dir() source = Source(value) + def my_totext(s, encoding="utf-8"): if py.builtin._isbytes(s): s = py.builtin._totext(s, encoding=encoding) return s + source_unicode = "\n".join([my_totext(line) for line in source.lines]) source = py.builtin._totext(source_unicode) content = source.strip().encode("utf-8") # + "\n" @@ -562,7 +566,7 @@ class Testdir: def mkpydir(self, name): """Create a new python package. - This creates a (sub)direcotry with an empty ``__init__.py`` + This creates a (sub)directory with an empty ``__init__.py`` file so that is recognised as a python package. """ @@ -657,7 +661,7 @@ class Testdir: def inline_genitems(self, *args): """Run ``pytest.main(['--collectonly'])`` in-process. - Retuns a tuple of the collected items and a + Returns a tuple of the collected items and a :py:class:`HookRecorder` instance. This runs the :py:func:`pytest.main` function to run all of @@ -692,12 +696,15 @@ class Testdir: # warning which will trigger to say they can no longer be # re-written, which is fine as they are already re-written. orig_warn = AssertionRewritingHook._warn_already_imported + def revert(): AssertionRewritingHook._warn_already_imported = orig_warn + self.request.addfinalizer(revert) AssertionRewritingHook._warn_already_imported = lambda *a: None rec = [] + class Collect: def pytest_configure(x, config): rec.append(self.make_hook_recorder(config.pluginmanager)) @@ -732,10 +739,13 @@ class Testdir: try: reprec = self.inline_run(*args, **kwargs) except SystemExit as e: + class reprec: ret = e.args[0] + except Exception: traceback.print_exc() + class reprec: ret = 3 finally: @@ -847,7 +857,7 @@ class Testdir: :py:meth:`parseconfigure`. :param withinit: Whether to also write a ``__init__.py`` file - to the temporarly directory to ensure it is a package. + to the temporary directory to ensure it is a package. """ kw = {self.request.function.__name__: Source(source).strip()} @@ -1002,8 +1012,6 @@ class Testdir: pexpect = pytest.importorskip("pexpect", "3.0") if hasattr(sys, 'pypy_version_info') and '64' in platform.machine(): pytest.skip("pypy-64 bit not supported") - if sys.platform == "darwin": - pytest.xfail("pexpect does not work reliably on darwin?!") if sys.platform.startswith("freebsd"): pytest.xfail("pexpect does not work reliably on freebsd") logfile = self.tmpdir.join("spawn.out").open("wb") diff --git a/_pytest/python.py b/_pytest/python.py index 33e7cff66..59492bc41 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -19,14 +19,19 @@ from _pytest.compat import ( isclass, isfunction, is_generator, _escape_strings, REGEX_TYPE, STRING_TYPES, NoneType, NOTSET, get_real_func, getfslineno, safe_getattr, - getlocation, enum, + safe_str, getlocation, enum, ) -cutdir2 = py.path.local(_pytest.__file__).dirpath() cutdir1 = py.path.local(pluggy.__file__.rstrip("oc")) +cutdir2 = py.path.local(_pytest.__file__).dirpath() +cutdir3 = py.path.local(py.__file__).dirpath() def filter_traceback(entry): + """Return True if a TracebackEntry instance should be removed from tracebacks: + * dynamically generated code (no code to show up for it); + * internal traceback from pytest or its internal libraries, py and pluggy. + """ # entry.path might sometimes return a str object when the entry # points to dynamically generated code # see https://bitbucket.org/pytest-dev/py/issues/71 @@ -37,7 +42,7 @@ def filter_traceback(entry): # entry.path might point to an inexisting file, in which case it will # alsso return a str object. see #1133 p = py.path.local(entry.path) - return p != cutdir1 and not p.relto(cutdir2) + return p != cutdir1 and not p.relto(cutdir2) and not p.relto(cutdir3) @@ -169,7 +174,7 @@ def pytest_pycollect_makeitem(collector, name, obj): outcome = yield res = outcome.get_result() if res is not None: - raise StopIteration + return # nothing was collected elsewhere, let's do it here if isclass(obj): if collector.istestclass(obj, name): @@ -205,14 +210,16 @@ class PyobjContext(object): class PyobjMixin(PyobjContext): def obj(): def fget(self): - try: - return self._obj - except AttributeError: + obj = getattr(self, '_obj', None) + if obj is None: self._obj = obj = self._getobj() - return obj + return obj + def fset(self, value): self._obj = value + return property(fget, fset, None, "underlying python object") + obj = obj() def _getobj(self): @@ -425,20 +432,25 @@ class Module(pytest.File, PyCollector): % e.args ) except ImportError: - exc_class, exc, _ = sys.exc_info() + from _pytest._code.code import ExceptionInfo + exc_info = ExceptionInfo() + if self.config.getoption('verbose') < 2: + exc_info.traceback = exc_info.traceback.filter(filter_traceback) + exc_repr = exc_info.getrepr(style='short') if exc_info.traceback else exc_info.exconly() + formatted_tb = safe_str(exc_repr) raise self.CollectError( - "ImportError while importing test module '%s'.\n" - "Original error message:\n'%s'\n" - "Make sure your test modules/packages have valid Python names." - % (self.fspath, exc or exc_class) + "ImportError while importing test module '{fspath}'.\n" + "Hint: make sure your test modules/packages have valid Python names.\n" + "Traceback:\n" + "{traceback}".format(fspath=self.fspath, traceback=formatted_tb) ) except _pytest.runner.Skipped as e: if e.allow_module_level: raise raise self.CollectError( - "Using @pytest.skip outside of a test (e.g. as a test " - "function decorator) is not allowed. Use @pytest.mark.skip or " - "@pytest.mark.skipif instead." + "Using pytest.skip outside of a test is not allowed. If you are " + "trying to decorate a test function, use the @pytest.mark.skip " + "or @pytest.mark.skipif decorators instead." ) self.config.pluginmanager.consider_module(mod) return mod @@ -617,7 +629,7 @@ class Generator(FunctionMixin, PyCollector): def getcallargs(self, obj): if not isinstance(obj, (tuple, list)): obj = (obj,) - # explict naming + # explicit naming if isinstance(obj[0], py.builtin._basestring): name = obj[0] obj = obj[1:] @@ -771,7 +783,7 @@ class Metafunc(fixtures.FuncargnamesCompatAttr): It will also override any fixture-function defined scope, allowing to set a dynamic scope using test context or configuration. """ - from _pytest.fixtures import scopes + from _pytest.fixtures import scope2index from _pytest.mark import extract_argvalue from py.io import saferepr @@ -800,7 +812,8 @@ class Metafunc(fixtures.FuncargnamesCompatAttr): if scope is None: scope = _find_parametrized_scope(argnames, self._arg2fixturedefs, indirect) - scopenum = scopes.index(scope) + scopenum = scope2index( + scope, descr='call to {0}'.format(self.parametrize)) valtypes = {} for arg in argnames: if arg not in self.fixturenames: @@ -1095,7 +1108,9 @@ def raises(expected_exception, *args, **kwargs): >>> with raises(ZeroDivisionError, message="Expecting ZeroDivisionError"): ... pass - ... Failed: Expecting ZeroDivisionError + Traceback (most recent call last): + ... + Failed: Expecting ZeroDivisionError .. note:: @@ -1106,19 +1121,21 @@ def raises(expected_exception, *args, **kwargs): Lines of code after that, within the scope of the context manager will not be executed. For example:: - >>> with raises(OSError) as exc_info: - assert 1 == 1 # this will execute as expected - raise OSError(errno.EEXISTS, 'directory exists') - assert exc_info.value.errno == errno.EEXISTS # this will not execute + >>> value = 15 + >>> with raises(ValueError) as exc_info: + ... if value > 10: + ... raise ValueError("value must be <= 10") + ... assert str(exc_info.value) == "value must be <= 10" # this will not execute Instead, the following approach must be taken (note the difference in scope):: - >>> with raises(OSError) as exc_info: - assert 1 == 1 # this will execute as expected - raise OSError(errno.EEXISTS, 'directory exists') + >>> with raises(ValueError) as exc_info: + ... if value > 10: + ... raise ValueError("value must be <= 10") + ... + >>> assert str(exc_info.value) == "value must be <= 10" - assert exc_info.value.errno == errno.EEXISTS # this will now execute Or you can specify a callable by passing a to-be-called lambda:: @@ -1223,7 +1240,11 @@ class RaisesContext(object): exc_type, value, traceback = tp tp = exc_type, exc_type(value), traceback self.excinfo.__init__(tp) - return issubclass(self.excinfo.type, self.expected_exception) + suppress_exception = issubclass(self.excinfo.type, self.expected_exception) + if sys.version_info[0] == 2 and suppress_exception: + sys.exc_clear() + return suppress_exception + # builtin pytest.approx helper @@ -1357,6 +1378,8 @@ class approx(object): return False return all(a == x for a, x in zip(actual, self.expected)) + __hash__ = None + def __ne__(self, actual): return not (actual == self) @@ -1396,6 +1419,9 @@ class ApproxNonIterable(object): self.rel = rel def __repr__(self): + if isinstance(self.expected, complex): + return str(self.expected) + # Infinities aren't compared using tolerances, so don't show a # tolerance. if math.isinf(self.expected): @@ -1408,16 +1434,10 @@ class ApproxNonIterable(object): except ValueError: vetted_tolerance = '???' - plus_minus = u'{0} \u00b1 {1}'.format(self.expected, vetted_tolerance) - - # In python2, __repr__() must return a string (i.e. not a unicode - # object). In python3, __repr__() must return a unicode object - # (although now strings are unicode objects and bytes are what - # strings were). if sys.version_info[0] == 2: - return plus_minus.encode('utf-8') + return '{0} +- {1}'.format(self.expected, vetted_tolerance) else: - return plus_minus + return u'{0} \u00b1 {1}'.format(self.expected, vetted_tolerance) def __eq__(self, actual): # Short-circuit exact equality. @@ -1436,6 +1456,8 @@ class ApproxNonIterable(object): # Return true if the two numbers are within the tolerance. return abs(self.expected - actual) <= self.tolerance + __hash__ = None + def __ne__(self, actual): return not (actual == self) diff --git a/_pytest/recwarn.py b/_pytest/recwarn.py index a89474c03..d66f87e72 100644 --- a/_pytest/recwarn.py +++ b/_pytest/recwarn.py @@ -36,8 +36,13 @@ def deprecated_call(func=None, *args, **kwargs): This function can be used as a context manager:: + >>> import warnings + >>> def api_call_v2(): + ... warnings.warn('use v3 of this api', DeprecationWarning) + ... return 200 + >>> with deprecated_call(): - ... myobject.deprecated_method() + ... assert api_call_v2() == 200 Note: we cannot use WarningsRecorder here because it is still subject to the mechanism that prevents warnings of the same type from being @@ -218,4 +223,7 @@ class WarningsChecker(WarningsRecorder): if self.expected_warning is not None: if not any(r.category in self.expected_warning for r in self): __tracebackhide__ = True - pytest.fail("DID NOT WARN") + pytest.fail("DID NOT WARN. No warnings of type {0} was emitted. " + "The list of emitted warnings is: {1}.".format( + self.expected_warning, + [each.message for each in self])) diff --git a/_pytest/runner.py b/_pytest/runner.py index d1a155415..eb29e7370 100644 --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -515,8 +515,10 @@ def exit(msg): __tracebackhide__ = True raise Exit(msg) + exit.Exception = Exit + def skip(msg=""): """ skip an executing test with the given message. Note: it's usually better to use the pytest.mark.skipif marker to declare a test to be @@ -525,8 +527,11 @@ def skip(msg=""): """ __tracebackhide__ = True raise Skipped(msg=msg) + + skip.Exception = Skipped + def fail(msg="", pytrace=True): """ explicitly fail an currently-executing test with the given Message. @@ -535,6 +540,8 @@ def fail(msg="", pytrace=True): """ __tracebackhide__ = True raise Failed(msg=msg, pytrace=pytrace) + + fail.Exception = Failed diff --git a/_pytest/setuponly.py b/_pytest/setuponly.py index 0ecdbc2bd..1752c575f 100644 --- a/_pytest/setuponly.py +++ b/_pytest/setuponly.py @@ -5,9 +5,9 @@ import sys def pytest_addoption(parser): group = parser.getgroup("debugconfig") group.addoption('--setuponly', '--setup-only', action="store_true", - help="only setup fixtures, don't execute the tests.") + help="only setup fixtures, do not execute tests.") group.addoption('--setupshow', '--setup-show', action="store_true", - help="show setup fixtures while executing the tests.") + help="show setup of fixtures while executing tests.") @pytest.hookimpl(hookwrapper=True) diff --git a/_pytest/skipping.py b/_pytest/skipping.py index 2f000b7b9..0af5573ac 100644 --- a/_pytest/skipping.py +++ b/_pytest/skipping.py @@ -25,8 +25,10 @@ def pytest_configure(config): if config.option.runxfail: old = pytest.xfail config._cleanup.append(lambda: setattr(pytest, "xfail", old)) + def nop(*args, **kwargs): pass + nop.Exception = XFailed setattr(pytest, "xfail", nop) @@ -44,7 +46,7 @@ def pytest_configure(config): ) config.addinivalue_line("markers", "xfail(condition, reason=None, run=True, raises=None, strict=False): " - "mark the the test function as an expected failure if eval(condition) " + "mark the test function as an expected failure if eval(condition) " "has a True value. Optionally specify a reason for better reporting " "and run=False if you don't even want to execute the test function. " "If only specific exception(s) are expected, you can list them in " @@ -65,6 +67,8 @@ def xfail(reason=""): """ xfail an executing test or setup functions with the given reason.""" __tracebackhide__ = True raise XFailed(reason) + + xfail.Exception = XFailed @@ -108,14 +112,14 @@ class MarkEvaluator: def _getglobals(self): d = {'os': os, 'sys': sys, 'config': self.item.config} - d.update(self.item.obj.__globals__) + if hasattr(self.item, 'obj'): + d.update(self.item.obj.__globals__) return d def _istrue(self): if hasattr(self, 'result'): return self.result if self.holder: - d = self._getglobals() if self.holder.args or 'condition' in self.holder.kwargs: self.result = False # "holder" might be a MarkInfo or a MarkDecorator; only @@ -131,6 +135,7 @@ class MarkEvaluator: for expr in args: self.expr = expr if isinstance(expr, py.builtin._basestring): + d = self._getglobals() result = cached_eval(self.item.config, expr, d) else: if "reason" not in kwargs: diff --git a/_pytest/terminal.py b/_pytest/terminal.py index fdbe4ec36..79e065329 100644 --- a/_pytest/terminal.py +++ b/_pytest/terminal.py @@ -295,8 +295,8 @@ class TerminalReporter: def pytest_report_header(self, config): inifile = "" if config.inifile: - inifile = config.rootdir.bestrelpath(config.inifile) - lines = ["rootdir: %s, inifile: %s" %(config.rootdir, inifile)] + inifile = " " + config.rootdir.bestrelpath(config.inifile) + lines = ["rootdir: %s, inifile:%s" % (config.rootdir, inifile)] plugininfo = config.pluginmanager.list_plugin_distinfo() if plugininfo: @@ -458,6 +458,15 @@ class TerminalReporter: self.write_sep("_", msg) self._outrep_summary(rep) + def print_teardown_sections(self, rep): + for secname, content in rep.sections: + if 'teardown' in secname: + self._tw.sep('-', secname) + if content[-1:] == "\n": + content = content[:-1] + self._tw.line(content) + + def summary_failures(self): if self.config.option.tbstyle != "no": reports = self.getreports('failed') @@ -473,6 +482,9 @@ class TerminalReporter: markup = {'red': True, 'bold': True} self.write_sep("_", msg, **markup) self._outrep_summary(rep) + for report in self.getreports(''): + if report.nodeid == rep.nodeid and report.when == 'teardown': + self.print_teardown_sections(report) def summary_errors(self): if self.config.option.tbstyle != "no": diff --git a/_pytest/tmpdir.py b/_pytest/tmpdir.py index 5c0802508..0f878ad01 100644 --- a/_pytest/tmpdir.py +++ b/_pytest/tmpdir.py @@ -81,6 +81,7 @@ def get_user(): except (ImportError, KeyError): return None + # backward compatibility TempdirHandler = TempdirFactory @@ -115,7 +116,7 @@ def tmpdir(request, tmpdir_factory): path object. """ name = request.node.name - name = re.sub("[\W]", "_", name) + name = re.sub(r"[\W]", "_", name) MAXVAL = 30 if len(name) > MAXVAL: name = name[:MAXVAL] diff --git a/_pytest/unittest.py b/_pytest/unittest.py index 47868f448..276b9ba16 100644 --- a/_pytest/unittest.py +++ b/_pytest/unittest.py @@ -5,7 +5,7 @@ import sys import traceback import pytest -# for transfering markers +# for transferring markers import _pytest._code from _pytest.python import transfer_markers from _pytest.skipping import MarkEvaluator @@ -65,7 +65,6 @@ class UnitTestCase(pytest.Class): yield TestCaseFunction('runTest', parent=self) - class TestCaseFunction(pytest.Function): _excinfo = None @@ -94,6 +93,9 @@ class TestCaseFunction(pytest.Function): def teardown(self): if hasattr(self._testcase, 'teardown_method'): self._testcase.teardown_method(self._obj) + # Allow garbage collection on TestCase instance attributes. + self._testcase = None + self._obj = None def startTest(self, testcase): pass @@ -149,14 +151,33 @@ class TestCaseFunction(pytest.Function): def stopTest(self, testcase): pass + def _handle_skip(self): + # implements the skipping machinery (see #2137) + # analog to pythons Lib/unittest/case.py:run + testMethod = getattr(self._testcase, self._testcase._testMethodName) + if (getattr(self._testcase.__class__, "__unittest_skip__", False) or + getattr(testMethod, "__unittest_skip__", False)): + # If the class or method was skipped. + skip_why = (getattr(self._testcase.__class__, '__unittest_skip_why__', '') or + getattr(testMethod, '__unittest_skip_why__', '')) + try: # PY3, unittest2 on PY2 + self._testcase._addSkip(self, self._testcase, skip_why) + except TypeError: # PY2 + if sys.version_info[0] != 2: + raise + self._testcase._addSkip(self, skip_why) + return True + return False + def runtest(self): if self.config.pluginmanager.get_plugin("pdbinvoke") is None: self._testcase(result=self) else: # disables tearDown and cleanups for post mortem debugging (see #1890) + if self._handle_skip(): + return self._testcase.debug() - def _prunetraceback(self, excinfo): pytest.Function._prunetraceback(self, excinfo) traceback = excinfo.traceback.filter( @@ -183,6 +204,7 @@ def pytest_runtest_protocol(item): ut = sys.modules['twisted.python.failure'] Failure__init__ = ut.Failure.__init__ check_testcase_implements_trial_reporter() + def excstore(self, exc_value=None, exc_type=None, exc_tb=None, captureVars=None): if exc_value is None: @@ -196,6 +218,7 @@ def pytest_runtest_protocol(item): captureVars=captureVars) except TypeError: Failure__init__(self, exc_value, exc_type, exc_tb) + ut.Failure.__init__ = excstore yield ut.Failure.__init__ = Failure__init__ diff --git a/_pytest/vendored_packages/README.md b/_pytest/vendored_packages/README.md index eab7c714f..b5fe6febb 100644 --- a/_pytest/vendored_packages/README.md +++ b/_pytest/vendored_packages/README.md @@ -10,4 +10,4 @@ $ pip install -U pluggy== --no-compile --target=_pytest/vendored_packag ``` And commit the modified files. The `pluggy-.dist-info` directory -created by `pip` should be ignored. +created by `pip` should be added as well. diff --git a/_pytest/vendored_packages/pluggy-0.3.1.dist-info/RECORD b/_pytest/vendored_packages/pluggy-0.3.1.dist-info/RECORD deleted file mode 100644 index 9626673c4..000000000 --- a/_pytest/vendored_packages/pluggy-0.3.1.dist-info/RECORD +++ /dev/null @@ -1,8 +0,0 @@ -pluggy.py,sha256=v_RfWzyW6DPU1cJu_EFoL_OHq3t13qloVdR6UaMCXQA,29862 -pluggy-0.3.1.dist-info/top_level.txt,sha256=xKSCRhai-v9MckvMuWqNz16c1tbsmOggoMSwTgcpYHE,7 -pluggy-0.3.1.dist-info/pbr.json,sha256=xX3s6__wOcAyF-AZJX1sdZyW6PUXT-FkfBlM69EEUCg,47 -pluggy-0.3.1.dist-info/RECORD,, -pluggy-0.3.1.dist-info/metadata.json,sha256=nLKltOT78dMV-00uXD6Aeemp4xNsz2q59j6ORSDeLjw,1027 -pluggy-0.3.1.dist-info/METADATA,sha256=1b85Ho2u4iK30M099k7axMzcDDhLcIMb-A82JUJZnSo,1334 -pluggy-0.3.1.dist-info/WHEEL,sha256=AvR0WeTpDaxT645bl5FQxUK6NPsTls2ttpcGJg3j1Xg,110 -pluggy-0.3.1.dist-info/DESCRIPTION.rst,sha256=P5Akh1EdIBR6CeqtV2P8ZwpGSpZiTKPw0NyS7jEiD-g,306 diff --git a/_pytest/vendored_packages/pluggy-0.3.1.dist-info/metadata.json b/_pytest/vendored_packages/pluggy-0.3.1.dist-info/metadata.json deleted file mode 100644 index 426a3a7ad..000000000 --- a/_pytest/vendored_packages/pluggy-0.3.1.dist-info/metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"license": "MIT license", "name": "pluggy", "metadata_version": "2.0", "generator": "bdist_wheel (0.24.0)", "summary": "plugin and hook calling mechanisms for python", "platform": "unix", "version": "0.3.1", "extensions": {"python.details": {"document_names": {"description": "DESCRIPTION.rst"}, "contacts": [{"role": "author", "email": "holger at merlinux.eu", "name": "Holger Krekel"}]}}, "classifiers": ["Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: POSIX", "Operating System :: Microsoft :: Windows", "Operating System :: MacOS :: MacOS X", "Topic :: Software Development :: Testing", "Topic :: Software Development :: Libraries", "Topic :: Utilities", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5"]} \ No newline at end of file diff --git a/_pytest/vendored_packages/pluggy-0.3.1.dist-info/pbr.json b/_pytest/vendored_packages/pluggy-0.3.1.dist-info/pbr.json deleted file mode 100644 index d6b798640..000000000 --- a/_pytest/vendored_packages/pluggy-0.3.1.dist-info/pbr.json +++ /dev/null @@ -1 +0,0 @@ -{"is_release": false, "git_version": "7d4c9cd"} \ No newline at end of file diff --git a/_pytest/vendored_packages/pluggy-0.3.1.dist-info/DESCRIPTION.rst b/_pytest/vendored_packages/pluggy-0.4.0.dist-info/DESCRIPTION.rst similarity index 99% rename from _pytest/vendored_packages/pluggy-0.3.1.dist-info/DESCRIPTION.rst rename to _pytest/vendored_packages/pluggy-0.4.0.dist-info/DESCRIPTION.rst index aa3bbf812..da0e7a6ed 100644 --- a/_pytest/vendored_packages/pluggy-0.3.1.dist-info/DESCRIPTION.rst +++ b/_pytest/vendored_packages/pluggy-0.4.0.dist-info/DESCRIPTION.rst @@ -1,3 +1,4 @@ + Plugin registration and hook calling for Python =============================================== diff --git a/_pytest/vendored_packages/pluggy-0.4.0.dist-info/INSTALLER b/_pytest/vendored_packages/pluggy-0.4.0.dist-info/INSTALLER new file mode 100644 index 000000000..a1b589e38 --- /dev/null +++ b/_pytest/vendored_packages/pluggy-0.4.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/_pytest/vendored_packages/pluggy-0.4.0.dist-info/LICENSE.txt b/_pytest/vendored_packages/pluggy-0.4.0.dist-info/LICENSE.txt new file mode 100644 index 000000000..121017d08 --- /dev/null +++ b/_pytest/vendored_packages/pluggy-0.4.0.dist-info/LICENSE.txt @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 holger krekel (rather uses bitbucket/hpk42) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/_pytest/vendored_packages/pluggy-0.3.1.dist-info/METADATA b/_pytest/vendored_packages/pluggy-0.4.0.dist-info/METADATA similarity index 95% rename from _pytest/vendored_packages/pluggy-0.3.1.dist-info/METADATA rename to _pytest/vendored_packages/pluggy-0.4.0.dist-info/METADATA index ec81f0a6b..bd88517c9 100644 --- a/_pytest/vendored_packages/pluggy-0.3.1.dist-info/METADATA +++ b/_pytest/vendored_packages/pluggy-0.4.0.dist-info/METADATA @@ -1,8 +1,8 @@ Metadata-Version: 2.0 Name: pluggy -Version: 0.3.1 +Version: 0.4.0 Summary: plugin and hook calling mechanisms for python -Home-page: UNKNOWN +Home-page: https://github.com/pytest-dev/pluggy Author: Holger Krekel Author-email: holger at merlinux.eu License: MIT license @@ -27,6 +27,7 @@ Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 + Plugin registration and hook calling for Python =============================================== diff --git a/_pytest/vendored_packages/pluggy-0.4.0.dist-info/RECORD b/_pytest/vendored_packages/pluggy-0.4.0.dist-info/RECORD new file mode 100644 index 000000000..3003a3bf2 --- /dev/null +++ b/_pytest/vendored_packages/pluggy-0.4.0.dist-info/RECORD @@ -0,0 +1,9 @@ +pluggy.py,sha256=u0oG9cv-oLOkNvEBlwnnu8pp1AyxpoERgUO00S3rvpQ,31543 +pluggy-0.4.0.dist-info/DESCRIPTION.rst,sha256=ltvjkFd40LW_xShthp6RRVM6OB_uACYDFR3kTpKw7o4,307 +pluggy-0.4.0.dist-info/LICENSE.txt,sha256=ruwhUOyV1HgE9F35JVL9BCZ9vMSALx369I4xq9rhpkM,1134 +pluggy-0.4.0.dist-info/METADATA,sha256=pe2hbsqKFaLHC6wAQPpFPn0KlpcPfLBe_BnS4O70bfk,1364 +pluggy-0.4.0.dist-info/RECORD,, +pluggy-0.4.0.dist-info/WHEEL,sha256=9Z5Xm-eel1bTS7e6ogYiKz0zmPEqDwIypurdHN1hR40,116 +pluggy-0.4.0.dist-info/metadata.json,sha256=T3go5L2qOa_-H-HpCZi3EoVKb8sZ3R-fOssbkWo2nvM,1119 +pluggy-0.4.0.dist-info/top_level.txt,sha256=xKSCRhai-v9MckvMuWqNz16c1tbsmOggoMSwTgcpYHE,7 +pluggy-0.4.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 diff --git a/_pytest/vendored_packages/pluggy-0.3.1.dist-info/WHEEL b/_pytest/vendored_packages/pluggy-0.4.0.dist-info/WHEEL similarity index 70% rename from _pytest/vendored_packages/pluggy-0.3.1.dist-info/WHEEL rename to _pytest/vendored_packages/pluggy-0.4.0.dist-info/WHEEL index 9dff69d86..8b6dd1b5a 100644 --- a/_pytest/vendored_packages/pluggy-0.3.1.dist-info/WHEEL +++ b/_pytest/vendored_packages/pluggy-0.4.0.dist-info/WHEEL @@ -1,5 +1,5 @@ Wheel-Version: 1.0 -Generator: bdist_wheel (0.24.0) +Generator: bdist_wheel (0.29.0) Root-Is-Purelib: true Tag: py2-none-any Tag: py3-none-any diff --git a/_pytest/vendored_packages/pluggy-0.4.0.dist-info/metadata.json b/_pytest/vendored_packages/pluggy-0.4.0.dist-info/metadata.json new file mode 100644 index 000000000..cde22aff0 --- /dev/null +++ b/_pytest/vendored_packages/pluggy-0.4.0.dist-info/metadata.json @@ -0,0 +1 @@ +{"classifiers": ["Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: POSIX", "Operating System :: Microsoft :: Windows", "Operating System :: MacOS :: MacOS X", "Topic :: Software Development :: Testing", "Topic :: Software Development :: Libraries", "Topic :: Utilities", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5"], "extensions": {"python.details": {"contacts": [{"email": "holger at merlinux.eu", "name": "Holger Krekel", "role": "author"}], "document_names": {"description": "DESCRIPTION.rst", "license": "LICENSE.txt"}, "project_urls": {"Home": "https://github.com/pytest-dev/pluggy"}}}, "generator": "bdist_wheel (0.29.0)", "license": "MIT license", "metadata_version": "2.0", "name": "pluggy", "platform": "unix", "summary": "plugin and hook calling mechanisms for python", "version": "0.4.0"} \ No newline at end of file diff --git a/_pytest/vendored_packages/pluggy-0.3.1.dist-info/top_level.txt b/_pytest/vendored_packages/pluggy-0.4.0.dist-info/top_level.txt similarity index 100% rename from _pytest/vendored_packages/pluggy-0.3.1.dist-info/top_level.txt rename to _pytest/vendored_packages/pluggy-0.4.0.dist-info/top_level.txt diff --git a/_pytest/vendored_packages/pluggy.py b/_pytest/vendored_packages/pluggy.py index 2f848b23d..9c13932b3 100644 --- a/_pytest/vendored_packages/pluggy.py +++ b/_pytest/vendored_packages/pluggy.py @@ -67,8 +67,9 @@ Pluggy currently consists of functionality for: import sys import inspect -__version__ = '0.3.1' -__all__ = ["PluginManager", "PluginValidationError", +__version__ = '0.4.0' + +__all__ = ["PluginManager", "PluginValidationError", "HookCallError", "HookspecMarker", "HookimplMarker"] _py3 = sys.version_info > (3, 0) @@ -308,7 +309,7 @@ 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 new hooks by calling ``add_hookspec(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 @@ -374,7 +375,10 @@ class PluginManager(object): def parse_hookimpl_opts(self, plugin, name): method = getattr(plugin, name) - res = getattr(method, self.project_name + "_impl", None) + try: + res = getattr(method, self.project_name + "_impl", None) + except Exception: + res = {} if res is not None and not isinstance(res, dict): # false positive res = None @@ -455,6 +459,10 @@ class PluginManager(object): """ Return a plugin or None for the given name. """ return self._name2plugin.get(name) + def has_plugin(self, name): + """ Return True if a plugin with the given name is registered. """ + return self.get_plugin(name) is not None + def get_name(self, plugin): """ Return name for registered plugin or None if not registered. """ for name, val in self._name2plugin.items(): @@ -492,7 +500,8 @@ class PluginManager(object): 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 + from pkg_resources import (iter_entry_points, DistributionNotFound, + VersionConflict) for ep in iter_entry_points(entrypoint_name): # is the plugin registered or blocked? if self.get_plugin(ep.name) or self.is_blocked(ep.name): @@ -501,6 +510,9 @@ class PluginManager(object): plugin = ep.load() except DistributionNotFound: continue + except VersionConflict as e: + raise PluginValidationError( + "Plugin %r could not be loaded: %s!" % (ep.name, e)) self.register(plugin, name=ep.name) self._plugin_distinfo.append((plugin, ep.dist)) return len(self._plugin_distinfo) @@ -573,7 +585,7 @@ class _MultiCall: # XXX note that the __multicall__ argument is supported only # for pytest compatibility reasons. It was never officially - # supported there and is explicitly deprecated since 2.8 + # 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. @@ -590,7 +602,13 @@ class _MultiCall: while self.hook_impls: hook_impl = self.hook_impls.pop() - args = [all_kwargs[argname] for argname in hook_impl.argnames] + try: + args = [all_kwargs[argname] for argname in hook_impl.argnames] + except KeyError: + for argname in hook_impl.argnames: + if argname not in all_kwargs: + raise HookCallError( + "hook call must provide argument %r" % (argname,)) if hook_impl.hookwrapper: return _wrapped_call(hook_impl.function(*args), self.execute) res = hook_impl.function(*args) @@ -629,7 +647,10 @@ def varnames(func, startindex=None): startindex = 1 else: if not inspect.isfunction(func) and not inspect.ismethod(func): - func = getattr(func, '__call__', func) + try: + func = getattr(func, '__call__', func) + except Exception: + return () if startindex is None: startindex = int(inspect.ismethod(func)) @@ -763,6 +784,10 @@ class PluginValidationError(Exception): """ plugin failed validation. """ +class HookCallError(Exception): + """ Hook was called wrongly. """ + + if hasattr(inspect, 'signature'): def _formatdef(func): return "%s%s" % ( diff --git a/appveyor.yml b/appveyor.yml index 38f3d6fad..cc72b4b70 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -6,30 +6,37 @@ environment: # https://www.appveyor.com/docs/build-configuration#secure-variables matrix: - # create multiple jobs to execute a set of tox runs on each; this is to workaround having - # builds timing out in AppVeyor - - TOXENV: "linting,py26,py27,py33,py34,py35,pypy" - - TOXENV: "py27-pexpect,py27-xdist,py27-trial,py35-pexpect,py35-xdist,py35-trial" - - TOXENV: "py27-nobyte,doctesting,freeze,docs" + # coveralls is not in the default env list + - TOXENV: "coveralls" + # note: please use "tox --listenvs" to populate the build matrix below + - TOXENV: "linting" + - TOXENV: "py26" + - TOXENV: "py27" + - TOXENV: "py33" + - TOXENV: "py34" + - TOXENV: "py35" + - TOXENV: "py36" + - TOXENV: "pypy" + - TOXENV: "py27-pexpect" + - TOXENV: "py27-xdist" + - TOXENV: "py27-trial" + - TOXENV: "py35-pexpect" + - TOXENV: "py35-xdist" + - TOXENV: "py35-trial" + - TOXENV: "py27-nobyte" + - TOXENV: "doctesting" + - TOXENV: "freeze" + - TOXENV: "docs" install: - echo Installed Pythons - dir c:\Python* - # install pypy using choco (redirect to a file and write to console in case - # choco install returns non-zero, because choco install python.pypy is too - # noisy) - - choco install python.pypy > pypy-inst.log 2>&1 || (type pypy-inst.log & exit /b 1) - - set PATH=C:\tools\pypy\pypy;%PATH% # so tox can find pypy - - echo PyPy installed - - pypy --version + - if "%TOXENV%" == "pypy" call scripts\install-pypy.bat - C:\Python35\python -m pip install tox build: false # Not a C# project, build stuff at the test step instead. test_script: - - C:\Python35\python -m tox - # coveralls is not in tox's envlist, plus for PRs the secure variable - # is not defined so we have to check for it - - if defined COVERALLS_REPO_TOKEN C:\Python35\python -m tox -e coveralls + - call scripts\call-tox.bat diff --git a/doc/en/announce/index.rst b/doc/en/announce/index.rst index 90e6fcb6e..52dd90de0 100644 --- a/doc/en/announce/index.rst +++ b/doc/en/announce/index.rst @@ -6,6 +6,11 @@ Release announcements :maxdepth: 2 + release-3.0.7 + release-3.0.6 + release-3.0.5 + release-3.0.4 + release-3.0.3 release-3.0.2 release-3.0.1 release-3.0.0 diff --git a/doc/en/announce/release-2.0.2.rst b/doc/en/announce/release-2.0.2.rst index 733a9f7bd..f1f44f34f 100644 --- a/doc/en/announce/release-2.0.2.rst +++ b/doc/en/announce/release-2.0.2.rst @@ -63,9 +63,9 @@ Changes between 2.0.1 and 2.0.2 this. - fixed typos in the docs (thanks Victor Garcia, Brianna Laugher) and particular - thanks to Laura Creighton who also revieved parts of the documentation. + thanks to Laura Creighton who also reviewed parts of the documentation. -- fix slighly wrong output of verbose progress reporting for classes +- fix slightly wrong output of verbose progress reporting for classes (thanks Amaury) - more precise (avoiding of) deprecation warnings for node.Class|Function accesses diff --git a/doc/en/announce/release-2.0.3.rst b/doc/en/announce/release-2.0.3.rst index ed746e851..9bbfdaab3 100644 --- a/doc/en/announce/release-2.0.3.rst +++ b/doc/en/announce/release-2.0.3.rst @@ -13,7 +13,7 @@ If you want to install or upgrade pytest, just type one of:: easy_install -U pytest There also is a bugfix release 1.6 of pytest-xdist, the plugin -that enables seemless distributed and "looponfail" testing for Python. +that enables seamless distributed and "looponfail" testing for Python. best, holger krekel @@ -33,7 +33,7 @@ Changes between 2.0.2 and 2.0.3 - don't require zlib (and other libs) for genscript plugin without --genscript actually being used. -- speed up skips (by not doing a full traceback represenation +- speed up skips (by not doing a full traceback representation internally) - fix issue37: avoid invalid characters in junitxml's output diff --git a/doc/en/announce/release-2.2.1.rst b/doc/en/announce/release-2.2.1.rst index f9764634c..5d28bcb01 100644 --- a/doc/en/announce/release-2.2.1.rst +++ b/doc/en/announce/release-2.2.1.rst @@ -2,7 +2,7 @@ pytest-2.2.1: bug fixes, perfect teardowns =========================================================================== -pytest-2.2.1 is a minor backward-compatible release of the the py.test +pytest-2.2.1 is a minor backward-compatible release of the py.test testing tool. It contains bug fixes and little improvements, including documentation fixes. If you are using the distributed testing pluginmake sure to upgrade it to pytest-xdist-1.8. diff --git a/doc/en/announce/release-2.2.4.rst b/doc/en/announce/release-2.2.4.rst index 8720bdb28..67f0feb27 100644 --- a/doc/en/announce/release-2.2.4.rst +++ b/doc/en/announce/release-2.2.4.rst @@ -29,7 +29,7 @@ Changes between 2.2.3 and 2.2.4 - fix issue with unittest: now @unittest.expectedFailure markers should be processed correctly (you can also use @pytest.mark markers) - document integration with the extended distribute/setuptools test commands -- fix issue 140: propperly get the real functions +- fix issue 140: properly get the real functions of bound classmethods for setup/teardown_class - fix issue #141: switch from the deceased paste.pocoo.org to bpaste.net - fix issue #143: call unconfigure/sessionfinish always when diff --git a/doc/en/announce/release-2.3.0.rst b/doc/en/announce/release-2.3.0.rst index 54fe3961f..f863aad0a 100644 --- a/doc/en/announce/release-2.3.0.rst +++ b/doc/en/announce/release-2.3.0.rst @@ -89,7 +89,7 @@ Changes between 2.2.4 and 2.3.0 - fix issue128: show captured output when capsys/capfd are used -- fix issue179: propperly show the dependency chain of factories +- fix issue179: properly show the dependency chain of factories - pluginmanager.register(...) now raises ValueError if the plugin has been already registered or the name is taken @@ -130,5 +130,5 @@ Changes between 2.2.4 and 2.3.0 - don't show deselected reason line if there is none - - py.test -vv will show all of assert comparisations instead of truncating + - py.test -vv will show all of assert comparisons instead of truncating diff --git a/doc/en/announce/release-2.3.2.rst b/doc/en/announce/release-2.3.2.rst index 948b374d4..75312b429 100644 --- a/doc/en/announce/release-2.3.2.rst +++ b/doc/en/announce/release-2.3.2.rst @@ -1,7 +1,7 @@ pytest-2.3.2: some fixes and more traceback-printing speed =========================================================================== -pytest-2.3.2 is a another stabilization release: +pytest-2.3.2 is another stabilization release: - issue 205: fixes a regression with conftest detection - issue 208/29: fixes traceback-printing speed in some bad cases diff --git a/doc/en/announce/release-2.3.3.rst b/doc/en/announce/release-2.3.3.rst index 1d7c7027b..3a48b6ac4 100644 --- a/doc/en/announce/release-2.3.3.rst +++ b/doc/en/announce/release-2.3.3.rst @@ -1,7 +1,7 @@ -pytest-2.3.3: integration fixes, py24 suport, ``*/**`` shown in traceback +pytest-2.3.3: integration fixes, py24 support, ``*/**`` shown in traceback =========================================================================== -pytest-2.3.3 is a another stabilization release of the py.test tool +pytest-2.3.3 is another stabilization release of the py.test tool which offers uebersimple assertions, scalable fixture mechanisms and deep customization for testing with Python. Particularly, this release provides: @@ -46,7 +46,7 @@ Changes between 2.3.2 and 2.3.3 - fix issue209 - reintroduce python2.4 support by depending on newer pylib which re-introduced statement-finding for pre-AST interpreters -- nose support: only call setup if its a callable, thanks Andrew +- nose support: only call setup if it's a callable, thanks Andrew Taumoefolau - fix issue219 - add py2.4-3.3 classifiers to TROVE list diff --git a/doc/en/announce/release-2.3.5.rst b/doc/en/announce/release-2.3.5.rst index c4e91e0e6..112399ef3 100644 --- a/doc/en/announce/release-2.3.5.rst +++ b/doc/en/announce/release-2.3.5.rst @@ -44,11 +44,11 @@ Changes between 2.3.4 and 2.3.5 (thanks Adam Goucher) - Issue 265 - integrate nose setup/teardown with setupstate - so it doesnt try to teardown if it did not setup + so it doesn't try to teardown if it did not setup -- issue 271 - dont write junitxml on slave nodes +- issue 271 - don't write junitxml on slave nodes -- Issue 274 - dont try to show full doctest example +- Issue 274 - don't try to show full doctest example when doctest does not know the example location - issue 280 - disable assertion rewriting on buggy CPython 2.6.0 @@ -84,7 +84,7 @@ Changes between 2.3.4 and 2.3.5 - allow to specify prefixes starting with "_" when customizing python_functions test discovery. (thanks Graham Horler) -- improve PYTEST_DEBUG tracing output by puting +- improve PYTEST_DEBUG tracing output by putting extra data on a new lines with additional indent - ensure OutcomeExceptions like skip/fail have initialized exception attributes diff --git a/doc/en/announce/release-2.4.0.rst b/doc/en/announce/release-2.4.0.rst index 88130c481..be3aaedb0 100644 --- a/doc/en/announce/release-2.4.0.rst +++ b/doc/en/announce/release-2.4.0.rst @@ -36,7 +36,7 @@ a full list of details. A few feature highlights: - reporting: color the last line red or green depending if failures/errors occurred or everything passed. -The documentation has been updated to accomodate the changes, +The documentation has been updated to accommodate the changes, see `http://pytest.org `_ To install or upgrade pytest:: @@ -118,7 +118,7 @@ new features: - fix issue322: tearDownClass is not run if setUpClass failed. Thanks Mathieu Agopian for the initial fix. Also make all of pytest/nose - finalizer mimick the same generic behaviour: if a setupX exists and + finalizer mimic the same generic behaviour: if a setupX exists and fails, don't run teardownX. This internally introduces a new method "node.addfinalizer()" helper which can only be called during the setup phase of a node. diff --git a/doc/en/announce/release-2.5.0.rst b/doc/en/announce/release-2.5.0.rst index b8f28d6fd..b04a825cd 100644 --- a/doc/en/announce/release-2.5.0.rst +++ b/doc/en/announce/release-2.5.0.rst @@ -70,7 +70,7 @@ holger krekel to problems for more than >966 non-function scoped parameters). - fix issue290 - there is preliminary support now for parametrizing - with repeated same values (sometimes useful to to test if calling + with repeated same values (sometimes useful to test if calling a second time works as with the first time). - close issue240 - document precisely how pytest module importing @@ -149,7 +149,7 @@ holger krekel would not work correctly because pytest assumes @pytest.mark.some gets a function to be decorated already. We now at least detect if this - arg is an lambda and thus the example will work. Thanks Alex Gaynor + arg is a lambda and thus the example will work. Thanks Alex Gaynor for bringing it up. - xfail a test on pypy that checks wrong encoding/ascii (pypy does diff --git a/doc/en/announce/release-2.5.2.rst b/doc/en/announce/release-2.5.2.rst index 9308ffdd6..d5cfca2db 100644 --- a/doc/en/announce/release-2.5.2.rst +++ b/doc/en/announce/release-2.5.2.rst @@ -60,5 +60,5 @@ holger krekel - fix issue429: comparing byte strings with non-ascii chars in assert expressions now work better. Thanks Floris Bruynooghe. -- make capfd/capsys.capture private, its unused and shouldnt be exposed +- make capfd/capsys.capture private, its unused and shouldn't be exposed diff --git a/doc/en/announce/release-2.6.3.rst b/doc/en/announce/release-2.6.3.rst index c1d0ad278..ee0d2692c 100644 --- a/doc/en/announce/release-2.6.3.rst +++ b/doc/en/announce/release-2.6.3.rst @@ -42,7 +42,7 @@ Changes 2.6.3 - fix conftest related fixture visibility issue: when running with a CWD outside of a test package pytest would get fixture discovery wrong. - Thanks to Wolfgang Schnerring for figuring out a reproducable example. + Thanks to Wolfgang Schnerring for figuring out a reproducible example. - Introduce pytest_enter_pdb hook (needed e.g. by pytest_timeout to cancel the timeout when interactively entering pdb). Thanks Wolfgang Schnerring. diff --git a/doc/en/announce/release-2.7.1.rst b/doc/en/announce/release-2.7.1.rst index cd37cad0c..fdc71eebb 100644 --- a/doc/en/announce/release-2.7.1.rst +++ b/doc/en/announce/release-2.7.1.rst @@ -32,7 +32,7 @@ The py.test Development Team 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, + FixtureLookupError which would lead to an internal error, obfuscating the original problem. Thanks talljosh for initial diagnose/patch and Bruno Oliveira for final patch. diff --git a/doc/en/announce/release-2.9.2.rst b/doc/en/announce/release-2.9.2.rst index b71ae85dd..8f274cdf3 100644 --- a/doc/en/announce/release-2.9.2.rst +++ b/doc/en/announce/release-2.9.2.rst @@ -46,7 +46,7 @@ The py.test Development Team Thanks `@astraw38`_ for reporting the issue (`#1496`_) and `@tomviner`_ for PR the (`#1524`_). -* Fix win32 path issue when puttinging custom config file with absolute path +* Fix win32 path issue when putting custom config file with absolute path in ``pytest.main("-c your_absolute_path")``. * Fix maximum recursion depth detection when raised error class is not aware diff --git a/doc/en/announce/release-3.0.3.rst b/doc/en/announce/release-3.0.3.rst new file mode 100644 index 000000000..f00172195 --- /dev/null +++ b/doc/en/announce/release-3.0.3.rst @@ -0,0 +1,27 @@ +pytest-3.0.3 +============ + +pytest 3.0.3 has just been released to PyPI. + +This release fixes some regressions and bugs reported in the last version, +being a drop-in replacement. To upgrade:: + + pip install --upgrade pytest + +The changelog is available at http://doc.pytest.org/en/latest/changelog.html. + +Thanks to all who contributed to this release, among them: + +* Bruno Oliveira +* Florian Bruhin +* Floris Bruynooghe +* Huayi Zhang +* Lev Maximov +* Raquel Alegre +* Ronny Pfannschmidt +* Roy Williams +* Tyler Goodlet +* mbyt + +Happy testing, +The pytest Development Team diff --git a/doc/en/announce/release-3.0.4.rst b/doc/en/announce/release-3.0.4.rst new file mode 100644 index 000000000..852057037 --- /dev/null +++ b/doc/en/announce/release-3.0.4.rst @@ -0,0 +1,29 @@ +pytest-3.0.4 +============ + +pytest 3.0.4 has just been released to PyPI. + +This release fixes some regressions and bugs reported in the last version, +being a drop-in replacement. To upgrade:: + + pip install --upgrade pytest + +The changelog is available at http://doc.pytest.org/en/latest/changelog.html. + +Thanks to all who contributed to this release, among them: + +* Bruno Oliveira +* Dan Wandschneider +* Florian Bruhin +* Georgy Dyuldin +* Grigorii Eremeev +* Jason R. Coombs +* Manuel Jacob +* Mathieu Clabaut +* Michael Seifert +* Nikolaus Rath +* Ronny Pfannschmidt +* Tom V + +Happy testing, +The pytest Development Team diff --git a/doc/en/announce/release-3.0.5.rst b/doc/en/announce/release-3.0.5.rst new file mode 100644 index 000000000..3e2419d7e --- /dev/null +++ b/doc/en/announce/release-3.0.5.rst @@ -0,0 +1,27 @@ +pytest-3.0.5 +============ + +pytest 3.0.5 has just been released to PyPI. + +This is a bug-fix release, being a drop-in replacement. To upgrade:: + + pip install --upgrade pytest + +The changelog is available at http://doc.pytest.org/en/latest/changelog.html. + +Thanks to all who contributed to this release, among them: + +* Ana Vojnovic +* Bruno Oliveira +* Daniel Hahler +* Duncan Betts +* Igor Starikov +* Ismail +* Luke Murphy +* Ned Batchelder +* Ronny Pfannschmidt +* Sebastian Ramacher +* nmundar + +Happy testing, +The pytest Development Team diff --git a/doc/en/announce/release-3.0.6.rst b/doc/en/announce/release-3.0.6.rst new file mode 100644 index 000000000..2988b9cb3 --- /dev/null +++ b/doc/en/announce/release-3.0.6.rst @@ -0,0 +1,33 @@ +pytest-3.0.6 +============ + +pytest 3.0.6 has just been released to PyPI. + +This is a bug-fix release, being a drop-in replacement. To upgrade:: + + pip install --upgrade pytest + +The full changelog is available at http://doc.pytest.org/en/latest/changelog.html. + + +Thanks to all who contributed to this release, among them: + +* Andreas Pelme +* Bruno Oliveira +* Dmitry Malinovsky +* Eli Boyarski +* Jakub Wilk +* Jeff Widman +* Loïc Estève +* Luke Murphy +* Miro Hrončok +* Oscar Hellström +* Peter Heatwole +* Philippe Ombredanne +* Ronny Pfannschmidt +* Rutger Prins +* Stefan Scherfke + + +Happy testing, +The pytest Development Team diff --git a/doc/en/announce/release-3.0.7.rst b/doc/en/announce/release-3.0.7.rst new file mode 100644 index 000000000..591557aa7 --- /dev/null +++ b/doc/en/announce/release-3.0.7.rst @@ -0,0 +1,33 @@ +pytest-3.0.7 +============ + +pytest 3.0.7 has just been released to PyPI. + +This is a bug-fix release, being a drop-in replacement. To upgrade:: + + pip install --upgrade pytest + +The full changelog is available at http://doc.pytest.org/en/latest/changelog.html. + +Thanks to all who contributed to this release, among them: + +* Anthony Sottile +* Barney Gale +* Bruno Oliveira +* Florian Bruhin +* Floris Bruynooghe +* Ionel Cristian Mărieș +* Katerina Koukiou +* NODA, Kai +* Omer Hadari +* Patrick Hayes +* Ran Benita +* Ronny Pfannschmidt +* Victor Uriarte +* Vidar Tonaas Fauske +* Ville Skyttä +* fbjorn +* mbyt + +Happy testing, +The pytest Development Team diff --git a/doc/en/assert.rst b/doc/en/assert.rst index 9a283b629..b674f90b0 100644 --- a/doc/en/assert.rst +++ b/doc/en/assert.rst @@ -26,8 +26,8 @@ you will see the return value of the function call:: $ pytest test_assert1.py ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 - rootdir: $REGENDOC_TMPDIR, inifile: + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + rootdir: $REGENDOC_TMPDIR, inifile: collected 1 items test_assert1.py F @@ -170,8 +170,8 @@ if you run this module:: $ pytest test_assert2.py ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 - rootdir: $REGENDOC_TMPDIR, inifile: + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + rootdir: $REGENDOC_TMPDIR, inifile: collected 1 items test_assert2.py F @@ -183,7 +183,7 @@ if you run this module:: set1 = set("1308") set2 = set("8035") > assert set1 == set2 - E assert {'0', '1', '3', '8'} == {'0', '3', '5', '8'} + E AssertionError: assert {'0', '1', '3', '8'} == {'0', '3', '5', '8'} E Extra items in the left set: E '1' E Extra items in the right set: @@ -262,50 +262,20 @@ Advanced assertion introspection .. versionadded:: 2.1 -Reporting details about a failing assertion is achieved either by rewriting -assert statements before they are run or re-evaluating the assert expression and -recording the intermediate values. Which technique is used depends on the -location of the assert, ``pytest`` configuration, and Python version being used -to run ``pytest``. - -By default, ``pytest`` rewrites assert statements in test modules. -Rewritten assert statements put introspection information into the assertion failure message. -``pytest`` only rewrites test modules directly discovered by its test collection process, so -asserts in supporting modules which are not themselves test modules will not be -rewritten. +Reporting details about a failing assertion is achieved by rewriting assert +statements before they are run. Rewritten assert statements put introspection +information into the assertion failure message. ``pytest`` only rewrites test +modules directly discovered by its test collection process, so asserts in +supporting modules which are not themselves test modules will not be rewritten. .. note:: ``pytest`` rewrites test modules on import. It does this by using an import - hook to write a new pyc files. Most of the time this works transparently. + hook to write new pyc files. Most of the time this works transparently. However, if you are messing with import yourself, the import hook may - interfere. If this is the case, simply use ``--assert=reinterp`` or - ``--assert=plain``. Additionally, rewriting will fail silently if it cannot - write new pycs, i.e. in a read-only filesystem or a zipfile. - -If an assert statement has not been rewritten or the Python version is less than -2.6, ``pytest`` falls back on assert reinterpretation. In assert -reinterpretation, ``pytest`` walks the frame of the function containing the -assert statement to discover sub-expression results of the failing assert -statement. You can force ``pytest`` to always use assertion reinterpretation by -passing the ``--assert=reinterp`` option. - -Assert reinterpretation has a caveat not present with assert rewriting: If -evaluating the assert expression has side effects you may get a warning that the -intermediate values could not be determined safely. A common example of this -issue is an assertion which reads from a file:: - - assert f.read() != '...' - -If this assertion fails then the re-evaluation will probably succeed! -This is because ``f.read()`` will return an empty string when it is -called the second time during the re-evaluation. However, it is -easy to rewrite the assertion and avoid any trouble:: - - content = f.read() - assert content != '...' - -All assert introspection can be turned off by passing ``--assert=plain``. + interfere. If this is the case, use ``--assert=plain``. Additionally, + rewriting will fail silently if it cannot write new pycs, i.e. in a read-only + filesystem or a zipfile. For further information, Benjamin Peterson wrote up `Behind the scenes of pytest's new assertion rewriting `_. @@ -317,4 +287,5 @@ For further information, Benjamin Peterson wrote up `Behind the scenes of pytest ``--nomagic``. .. versionchanged:: 3.0 - Removes the ``--no-assert`` and``--nomagic`` options. + Removes the ``--no-assert`` and ``--nomagic`` options. + Removes the ``--assert=reinterp`` option. diff --git a/doc/en/cache.rst b/doc/en/cache.rst index 5435d435d..b3b992507 100644 --- a/doc/en/cache.rst +++ b/doc/en/cache.rst @@ -80,9 +80,9 @@ If you then run it with ``--lf``:: $ pytest --lf ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 run-last-failure: rerun last 2 failures - rootdir: $REGENDOC_TMPDIR, inifile: + rootdir: $REGENDOC_TMPDIR, inifile: collected 50 items test_50.py FF @@ -122,9 +122,9 @@ of ``FF`` and dots):: $ pytest --ff ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 run-last-failure: rerun last 2 failures first - rootdir: $REGENDOC_TMPDIR, inifile: + rootdir: $REGENDOC_TMPDIR, inifile: collected 50 items test_50.py FF................................................ @@ -227,14 +227,14 @@ You can always peek at the content of the cache using the $ py.test --cache-show ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 - rootdir: $REGENDOC_TMPDIR, inifile: + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + rootdir: $REGENDOC_TMPDIR, inifile: cachedir: $REGENDOC_TMPDIR/.cache ------------------------------- cache values ------------------------------- - example/value contains: - 42 cache/lastfailed contains: {'test_caching.py::test_function': True} + example/value contains: + 42 ======= no tests ran in 0.12 seconds ======== @@ -246,7 +246,7 @@ by adding the ``--cache-clear`` option like this:: pytest --cache-clear -This is recommended for invocations from Continous Integration +This is recommended for invocations from Continuous Integration servers where isolation and correctness is more important than speed. diff --git a/doc/en/capture.rst b/doc/en/capture.rst index f816628d1..2f666b7bc 100644 --- a/doc/en/capture.rst +++ b/doc/en/capture.rst @@ -64,8 +64,8 @@ of the failing function and hide the other one:: $ pytest ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 - rootdir: $REGENDOC_TMPDIR, inifile: + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + rootdir: $REGENDOC_TMPDIR, inifile: collected 2 items test_module.py .F diff --git a/doc/en/conf.py b/doc/en/conf.py index f3b8d7d1e..40f1e4165 100644 --- a/doc/en/conf.py +++ b/doc/en/conf.py @@ -303,7 +303,7 @@ texinfo_documents = [ ('Holger Krekel@*Benjamin Peterson@*Ronny Pfannschmidt@*' 'Floris Bruynooghe@*others'), 'pytest', - 'simple powerful testing with Pytho', + 'simple powerful testing with Python', 'Programming', 1), ] diff --git a/doc/en/doctest.rst b/doc/en/doctest.rst index 198fb18da..fd92eb7be 100644 --- a/doc/en/doctest.rst +++ b/doc/en/doctest.rst @@ -49,7 +49,7 @@ then you can just invoke ``pytest`` without command line options:: $ pytest ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini collected 1 items diff --git a/doc/en/example/markers.rst b/doc/en/example/markers.rst index 414deb89a..0c96d408c 100644 --- a/doc/en/example/markers.rst +++ b/doc/en/example/markers.rst @@ -31,9 +31,9 @@ You can then restrict a test run to only run tests marked with ``webtest``:: $ pytest -v -m webtest ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5 cachedir: .cache - rootdir: $REGENDOC_TMPDIR, inifile: + rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 4 items test_server.py::test_send_http PASSED @@ -45,9 +45,9 @@ Or the inverse, running all tests except the webtest ones:: $ pytest -v -m "not webtest" ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5 cachedir: .cache - rootdir: $REGENDOC_TMPDIR, inifile: + rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 4 items test_server.py::test_something_quick PASSED @@ -66,9 +66,9 @@ tests based on their module, class, method, or function name:: $ pytest -v test_server.py::TestClass::test_method ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5 cachedir: .cache - rootdir: $REGENDOC_TMPDIR, inifile: + rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 5 items test_server.py::TestClass::test_method PASSED @@ -79,9 +79,9 @@ You can also select on the class:: $ pytest -v test_server.py::TestClass ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5 cachedir: .cache - rootdir: $REGENDOC_TMPDIR, inifile: + rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 4 items test_server.py::TestClass::test_method PASSED @@ -92,9 +92,9 @@ Or select multiple nodes:: $ pytest -v test_server.py::TestClass test_server.py::test_send_http ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5 cachedir: .cache - rootdir: $REGENDOC_TMPDIR, inifile: + rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 8 items test_server.py::TestClass::test_method PASSED @@ -130,9 +130,9 @@ select tests based on their names:: $ pytest -v -k http # running with the above defined example module ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5 cachedir: .cache - rootdir: $REGENDOC_TMPDIR, inifile: + rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 4 items test_server.py::test_send_http PASSED @@ -144,9 +144,9 @@ And you can also run all tests except the ones that match the keyword:: $ pytest -k "not send_http" -v ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5 cachedir: .cache - rootdir: $REGENDOC_TMPDIR, inifile: + rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 4 items test_server.py::test_something_quick PASSED @@ -160,9 +160,9 @@ Or to select "http" and "quick" tests:: $ pytest -k "http or quick" -v ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5 cachedir: .cache - rootdir: $REGENDOC_TMPDIR, inifile: + rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 4 items test_server.py::test_send_http PASSED @@ -205,7 +205,7 @@ You can ask which markers exist for your test suite - the list includes our just @pytest.mark.skipif(condition): skip the given test function if eval(condition) results in a True value. Evaluation happens within the module global context. Example: skipif('sys.platform == "win32"') skips the test if we are on the win32 platform. see http://pytest.org/latest/skipping.html - @pytest.mark.xfail(condition, reason=None, run=True, raises=None, strict=False): mark the the test function as an expected failure if eval(condition) has a True value. Optionally specify a reason for better reporting and run=False if you don't even want to execute the test function. If only specific exception(s) are expected, you can list them in raises, and if the test fails in other ways, it will be reported as a true failure. See http://pytest.org/latest/skipping.html + @pytest.mark.xfail(condition, reason=None, run=True, raises=None, strict=False): mark the test function as an expected failure if eval(condition) has a True value. Optionally specify a reason for better reporting and run=False if you don't even want to execute the test function. If only specific exception(s) are expected, you can list them in raises, and if the test fails in other ways, it will be reported as a true failure. See http://pytest.org/latest/skipping.html @pytest.mark.parametrize(argnames, argvalues): call a test function multiple times passing in different arguments in turn. argvalues generally needs to be a list of values if argnames specifies only one name or a list of tuples of values if argnames specifies multiple names. Example: @parametrize('arg1', [1,2]) would lead to two calls of the decorated test function, one with arg1=1 and another with arg1=2.see http://pytest.org/latest/parametrize.html for more info and examples. @@ -352,8 +352,8 @@ the test needs:: $ pytest -E stage2 ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 - rootdir: $REGENDOC_TMPDIR, inifile: + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + rootdir: $REGENDOC_TMPDIR, inifile: collected 1 items test_someenv.py s @@ -364,8 +364,8 @@ and here is one that specifies exactly the environment needed:: $ pytest -E stage1 ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 - rootdir: $REGENDOC_TMPDIR, inifile: + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + rootdir: $REGENDOC_TMPDIR, inifile: collected 1 items test_someenv.py . @@ -381,7 +381,7 @@ The ``--markers`` option always gives you a list of available markers:: @pytest.mark.skipif(condition): skip the given test function if eval(condition) results in a True value. Evaluation happens within the module global context. Example: skipif('sys.platform == "win32"') skips the test if we are on the win32 platform. see http://pytest.org/latest/skipping.html - @pytest.mark.xfail(condition, reason=None, run=True, raises=None, strict=False): mark the the test function as an expected failure if eval(condition) has a True value. Optionally specify a reason for better reporting and run=False if you don't even want to execute the test function. If only specific exception(s) are expected, you can list them in raises, and if the test fails in other ways, it will be reported as a true failure. See http://pytest.org/latest/skipping.html + @pytest.mark.xfail(condition, reason=None, run=True, raises=None, strict=False): mark the test function as an expected failure if eval(condition) has a True value. Optionally specify a reason for better reporting and run=False if you don't even want to execute the test function. If only specific exception(s) are expected, you can list them in raises, and if the test fails in other ways, it will be reported as a true failure. See http://pytest.org/latest/skipping.html @pytest.mark.parametrize(argnames, argvalues): call a test function multiple times passing in different arguments in turn. argvalues generally needs to be a list of values if argnames specifies only one name or a list of tuples of values if argnames specifies multiple names. Example: @parametrize('arg1', [1,2]) would lead to two calls of the decorated test function, one with arg1=1 and another with arg1=2.see http://pytest.org/latest/parametrize.html for more info and examples. @@ -450,7 +450,7 @@ for your particular platform, you could use the following plugin:: import sys import pytest - ALL = set("darwin linux2 win32".split()) + ALL = set("darwin linux win32".split()) def pytest_runtest_setup(item): if isinstance(item, item.Function): @@ -470,7 +470,7 @@ Let's do a little test file to show how this looks like:: def test_if_apple_is_evil(): pass - @pytest.mark.linux2 + @pytest.mark.linux def test_if_linux_works(): pass @@ -481,32 +481,32 @@ Let's do a little test file to show how this looks like:: def test_runs_everywhere(): pass -then you will see two test skipped and two executed tests as expected:: +then you will see two tests skipped and two executed tests as expected:: $ pytest -rs # this option reports skip reasons ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 - rootdir: $REGENDOC_TMPDIR, inifile: + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + rootdir: $REGENDOC_TMPDIR, inifile: collected 4 items - test_plat.py sss. + test_plat.py s.s. ======= short test summary info ======== - SKIP [3] $REGENDOC_TMPDIR/conftest.py:12: cannot run on platform linux + SKIP [2] $REGENDOC_TMPDIR/conftest.py:12: cannot run on platform linux - ======= 1 passed, 3 skipped in 0.12 seconds ======== + ======= 2 passed, 2 skipped in 0.12 seconds ======== Note that if you specify a platform via the marker-command line option like this:: - $ pytest -m linux2 + $ pytest -m linux ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 - rootdir: $REGENDOC_TMPDIR, inifile: + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + rootdir: $REGENDOC_TMPDIR, inifile: collected 4 items - test_plat.py s + test_plat.py . ======= 3 tests deselected ======== - ======= 1 skipped, 3 deselected in 0.12 seconds ======== + ======= 1 passed, 3 deselected in 0.12 seconds ======== then the unmarked-tests will not be run. It is thus a way to restrict the run to the specific tests. @@ -551,8 +551,8 @@ We can now use the ``-m option`` to select one set:: $ pytest -m interface --tb=short ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 - rootdir: $REGENDOC_TMPDIR, inifile: + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + rootdir: $REGENDOC_TMPDIR, inifile: collected 4 items test_module.py FF @@ -573,8 +573,8 @@ or to select both "event" and "interface" tests:: $ pytest -m "interface or event" --tb=short ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 - rootdir: $REGENDOC_TMPDIR, inifile: + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + rootdir: $REGENDOC_TMPDIR, inifile: collected 4 items test_module.py FFF diff --git a/doc/en/example/nonpython.rst b/doc/en/example/nonpython.rst index 9840e1079..38918dba4 100644 --- a/doc/en/example/nonpython.rst +++ b/doc/en/example/nonpython.rst @@ -27,8 +27,8 @@ now execute the test specification:: nonpython $ pytest test_simple.yml ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 - rootdir: $REGENDOC_TMPDIR/nonpython, inifile: + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + rootdir: $REGENDOC_TMPDIR/nonpython, inifile: collected 2 items test_simple.yml F. @@ -59,9 +59,9 @@ consulted when reporting in ``verbose`` mode:: nonpython $ pytest -v ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5 cachedir: .cache - rootdir: $REGENDOC_TMPDIR/nonpython, inifile: + rootdir: $REGENDOC_TMPDIR/nonpython, inifile: collecting ... collected 2 items test_simple.yml::hello FAILED @@ -81,8 +81,8 @@ interesting to just look at the collection tree:: nonpython $ pytest --collect-only ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 - rootdir: $REGENDOC_TMPDIR/nonpython, inifile: + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + rootdir: $REGENDOC_TMPDIR/nonpython, inifile: collected 2 items diff --git a/doc/en/example/parametrize.rst b/doc/en/example/parametrize.rst index 4ba743044..9dd2829de 100644 --- a/doc/en/example/parametrize.rst +++ b/doc/en/example/parametrize.rst @@ -130,8 +130,8 @@ objects, they are still using the default pytest representation:: $ pytest test_time.py --collect-only ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 - rootdir: $REGENDOC_TMPDIR, inifile: + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + rootdir: $REGENDOC_TMPDIR, inifile: collected 6 items @@ -181,8 +181,8 @@ this is a fully self-contained example which you can run with:: $ pytest test_scenarios.py ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 - rootdir: $REGENDOC_TMPDIR, inifile: + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + rootdir: $REGENDOC_TMPDIR, inifile: collected 4 items test_scenarios.py .... @@ -194,8 +194,8 @@ If you just collect tests you'll also nicely see 'advanced' and 'basic' as varia $ pytest --collect-only test_scenarios.py ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 - rootdir: $REGENDOC_TMPDIR, inifile: + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + rootdir: $REGENDOC_TMPDIR, inifile: collected 4 items @@ -259,8 +259,8 @@ Let's first see how it looks like at collection time:: $ pytest test_backends.py --collect-only ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 - rootdir: $REGENDOC_TMPDIR, inifile: + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + rootdir: $REGENDOC_TMPDIR, inifile: collected 2 items @@ -320,8 +320,8 @@ The result of this test will be successful:: $ pytest test_indirect_list.py --collect-only ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 - rootdir: $REGENDOC_TMPDIR, inifile: + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + rootdir: $REGENDOC_TMPDIR, inifile: collected 1 items @@ -447,8 +447,8 @@ If you run this with reporting for skips enabled:: $ pytest -rs test_module.py ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 - rootdir: $REGENDOC_TMPDIR, inifile: + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + rootdir: $REGENDOC_TMPDIR, inifile: collected 2 items test_module.py .s diff --git a/doc/en/example/pythoncollection.rst b/doc/en/example/pythoncollection.rst index 83fcace6e..428b832f7 100644 --- a/doc/en/example/pythoncollection.rst +++ b/doc/en/example/pythoncollection.rst @@ -95,7 +95,7 @@ the :confval:`python_files`, :confval:`python_classes` and :confval:`python_functions` configuration options. Example:: # content of pytest.ini - # can also be defined in in tox.ini or setup.cfg file, although the section + # can also be defined in tox.ini or setup.cfg file, although the section # name in setup.cfg files should be "tool:pytest" [pytest] python_files=check_*.py @@ -117,7 +117,7 @@ then the test collection looks like this:: $ pytest --collect-only ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini collected 2 items @@ -163,7 +163,7 @@ You can always peek at the collection tree without running tests like this:: . $ pytest --collect-only pythoncollection.py ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini collected 3 items @@ -230,7 +230,7 @@ will be left out:: $ pytest --collect-only ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini collected 0 items diff --git a/doc/en/example/reportingdemo.rst b/doc/en/example/reportingdemo.rst index cddd13d2d..1323f1933 100644 --- a/doc/en/example/reportingdemo.rst +++ b/doc/en/example/reportingdemo.rst @@ -11,8 +11,8 @@ get on the terminal - we are working on that):: assertion $ pytest failure_demo.py ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 - rootdir: $REGENDOC_TMPDIR/assertion, inifile: + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + rootdir: $REGENDOC_TMPDIR/assertion, inifile: collected 42 items failure_demo.py FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF @@ -81,7 +81,7 @@ get on the terminal - we are working on that):: def test_eq_text(self): > assert 'spam' == 'eggs' - E assert 'spam' == 'eggs' + E AssertionError: assert 'spam' == 'eggs' E - spam E + eggs @@ -92,7 +92,7 @@ get on the terminal - we are working on that):: def test_eq_similar_text(self): > assert 'foo 1 bar' == 'foo 2 bar' - E assert 'foo 1 bar' == 'foo 2 bar' + E AssertionError: assert 'foo 1 bar' == 'foo 2 bar' E - foo 1 bar E ? ^ E + foo 2 bar @@ -105,7 +105,7 @@ get on the terminal - we are working on that):: def test_eq_multiline_text(self): > assert 'foo\nspam\nbar' == 'foo\neggs\nbar' - E assert 'foo\nspam\nbar' == 'foo\neggs\nbar' + E AssertionError: assert 'foo\nspam\nbar' == 'foo\neggs\nbar' E foo E - spam E + eggs @@ -120,7 +120,7 @@ get on the terminal - we are working on that):: a = '1'*100 + 'a' + '2'*100 b = '1'*100 + 'b' + '2'*100 > assert a == b - E assert '111111111111...2222222222222' == '1111111111111...2222222222222' + E AssertionError: assert '111111111111...2222222222222' == '1111111111111...2222222222222' E Skipping 90 identical leading characters in diff, use -v to show E Skipping 91 identical trailing characters in diff, use -v to show E - 1111111111a222222222 @@ -137,7 +137,7 @@ get on the terminal - we are working on that):: a = '1\n'*100 + 'a' + '2\n'*100 b = '1\n'*100 + 'b' + '2\n'*100 > assert a == b - E assert '1\n1\n1\n1\n...n2\n2\n2\n2\n' == '1\n1\n1\n1\n1...n2\n2\n2\n2\n' + E AssertionError: assert '1\n1\n1\n1\n...n2\n2\n2\n2\n' == '1\n1\n1\n1\n1...n2\n2\n2\n2\n' E Skipping 190 identical leading characters in diff, use -v to show E Skipping 191 identical trailing characters in diff, use -v to show E 1 @@ -183,7 +183,7 @@ get on the terminal - we are working on that):: def test_eq_dict(self): > assert {'a': 0, 'b': 1, 'c': 0} == {'a': 0, 'b': 2, 'd': 0} - E assert {'a': 0, 'b': 1, 'c': 0} == {'a': 0, 'b': 2, 'd': 0} + E AssertionError: assert {'a': 0, 'b': 1, 'c': 0} == {'a': 0, 'b': 2, 'd': 0} E Omitting 1 identical items, use -v to show E Differing items: E {'b': 1} != {'b': 2} @@ -238,7 +238,7 @@ get on the terminal - we are working on that):: def test_not_in_text_multiline(self): text = 'some multiline\ntext\nwhich\nincludes foo\nand a\ntail' > assert 'foo' not in text - E assert 'foo' not in 'some multiline\ntext\nw...ncludes foo\nand a\ntail' + E AssertionError: assert 'foo' not in 'some multiline\ntext\nw...ncludes foo\nand a\ntail' E 'foo' is contained here: E some multiline E text @@ -256,7 +256,7 @@ get on the terminal - we are working on that):: def test_not_in_text_single(self): text = 'single foo line' > assert 'foo' not in text - E assert 'foo' not in 'single foo line' + E AssertionError: assert 'foo' not in 'single foo line' E 'foo' is contained here: E single foo line E ? +++ @@ -269,7 +269,7 @@ get on the terminal - we are working on that):: def test_not_in_text_single_long(self): text = 'head ' * 50 + 'foo ' + 'tail ' * 20 > assert 'foo' not in text - E assert 'foo' not in 'head head head head hea...ail tail tail tail tail ' + E AssertionError: assert 'foo' not in 'head head head head hea...ail tail tail tail tail ' E 'foo' is contained here: E head head foo tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail E ? +++ @@ -282,7 +282,7 @@ get on the terminal - we are working on that):: def test_not_in_text_single_long_term(self): text = 'head ' * 50 + 'f'*70 + 'tail ' * 20 > assert 'f'*70 not in text - E assert 'fffffffffff...ffffffffffff' not in 'head head he...l tail tail ' + E AssertionError: assert 'fffffffffff...ffffffffffff' not in 'head head he...l tail tail ' E 'ffffffffffffffffff...fffffffffffffffffff' is contained here: E head head fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffftail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail E ? ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ @@ -305,7 +305,7 @@ get on the terminal - we are working on that):: class Foo(object): b = 1 > assert Foo().b == 2 - E assert 1 == 2 + E AssertionError: assert 1 == 2 E + where 1 = .Foo object at 0xdeadbeef>.b E + where .Foo object at 0xdeadbeef> = .Foo'>() @@ -338,7 +338,7 @@ get on the terminal - we are working on that):: class Bar(object): b = 2 > assert Foo().b == Bar().b - E assert 1 == 2 + E AssertionError: assert 1 == 2 E + where 1 = .Foo object at 0xdeadbeef>.b E + where .Foo object at 0xdeadbeef> = .Foo'>() E + and 2 = .Bar object at 0xdeadbeef>.b @@ -359,7 +359,7 @@ get on the terminal - we are working on that):: > int(s) E ValueError: invalid literal for int() with base 10: 'qwe' - <0-codegen $PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/python.py:1190>:1: ValueError + <0-codegen $PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/python.py:1207>:1: ValueError _______ TestRaises.test_raises_doesnt ________ self = @@ -480,7 +480,7 @@ get on the terminal - we are working on that):: s = "123" g = "456" > assert s.startswith(g) - E assert False + E AssertionError: assert False E + where False = ('456') E + where = '123'.startswith @@ -495,7 +495,7 @@ get on the terminal - we are working on that):: def g(): return "456" > assert f().startswith(g()) - E assert False + E AssertionError: assert False E + where False = ('456') E + where = '123'.startswith E + where '123' = .f at 0xdeadbeef>() diff --git a/doc/en/example/simple.rst b/doc/en/example/simple.rst index 4d63eef7f..b9f3daecc 100644 --- a/doc/en/example/simple.rst +++ b/doc/en/example/simple.rst @@ -113,8 +113,8 @@ directory with the above conftest.py:: $ pytest ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 - rootdir: $REGENDOC_TMPDIR, inifile: + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + rootdir: $REGENDOC_TMPDIR, inifile: collected 0 items ======= no tests ran in 0.12 seconds ======== @@ -164,8 +164,8 @@ and when running it will see a skipped "slow" test:: $ pytest -rs # "-rs" means report details on the little 's' ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 - rootdir: $REGENDOC_TMPDIR, inifile: + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + rootdir: $REGENDOC_TMPDIR, inifile: collected 2 items test_module.py .s @@ -178,8 +178,8 @@ Or run it including the ``slow`` marked test:: $ pytest --runslow ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 - rootdir: $REGENDOC_TMPDIR, inifile: + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + rootdir: $REGENDOC_TMPDIR, inifile: collected 2 items test_module.py .. @@ -302,9 +302,9 @@ which will add the string to the test header accordingly:: $ pytest ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 project deps: mylib-1.1 - rootdir: $REGENDOC_TMPDIR, inifile: + rootdir: $REGENDOC_TMPDIR, inifile: collected 0 items ======= no tests ran in 0.12 seconds ======== @@ -327,11 +327,11 @@ which will add info only when run with "--v":: $ pytest -v ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5 cachedir: .cache info1: did you know that ... did you? - rootdir: $REGENDOC_TMPDIR, inifile: + rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 0 items ======= no tests ran in 0.12 seconds ======== @@ -340,8 +340,8 @@ and nothing when run plainly:: $ pytest ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 - rootdir: $REGENDOC_TMPDIR, inifile: + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + rootdir: $REGENDOC_TMPDIR, inifile: collected 0 items ======= no tests ran in 0.12 seconds ======== @@ -374,8 +374,8 @@ Now we can profile which test functions execute the slowest:: $ pytest --durations=3 ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 - rootdir: $REGENDOC_TMPDIR, inifile: + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + rootdir: $REGENDOC_TMPDIR, inifile: collected 3 items test_some_are_slow.py ... @@ -440,8 +440,8 @@ If we run this:: $ pytest -rx ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 - rootdir: $REGENDOC_TMPDIR, inifile: + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + rootdir: $REGENDOC_TMPDIR, inifile: collected 4 items test_step.py .Fx. @@ -476,7 +476,7 @@ concept. It's however recommended to have explicit fixture references in your tests or test classes rather than relying on implicitly executing setup/teardown functions, especially if they are far away from the actual tests. -Here is a an example for making a ``db`` fixture available in a directory: +Here is an example for making a ``db`` fixture available in a directory: .. code-block:: python @@ -519,8 +519,8 @@ We can run this:: $ pytest ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 - rootdir: $REGENDOC_TMPDIR, inifile: + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + rootdir: $REGENDOC_TMPDIR, inifile: collected 7 items test_step.py .Fx. @@ -585,7 +585,7 @@ environment you can implement a hook that gets called when the test "report" object is about to be created. Here we write out all failing test calls and also access a fixture (if it was used by the test) in case you want to query/look at it during your post processing. In our -case we just write some informations out to a ``failures`` file: +case we just write some information out to a ``failures`` file: .. code-block:: python @@ -627,8 +627,8 @@ and run them:: $ pytest test_module.py ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 - rootdir: $REGENDOC_TMPDIR, inifile: + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + rootdir: $REGENDOC_TMPDIR, inifile: collected 2 items test_module.py FF @@ -678,7 +678,7 @@ here is a little example implemented via a local plugin: outcome = yield rep = outcome.get_result() - # set an report attribute for each phase of a call, which can + # set a report attribute for each phase of a call, which can # be "setup", "call", "teardown" setattr(item, "rep_" + rep.when, rep) @@ -721,8 +721,8 @@ and run it:: $ pytest -s test_module.py ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 - rootdir: $REGENDOC_TMPDIR, inifile: + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + rootdir: $REGENDOC_TMPDIR, inifile: collected 3 items test_module.py Esetting up a test failed! test_module.py::test_setup_fails diff --git a/doc/en/faq.rst b/doc/en/faq.rst index 774998b14..27d74e114 100644 --- a/doc/en/faq.rst +++ b/doc/en/faq.rst @@ -66,14 +66,6 @@ This completely avoids previous issues of confusing assertion-reporting. It also means, that you can use Python's ``-O`` optimization without losing assertions in test modules. -``pytest`` contains a second, mostly obsolete, assert debugging technique -invoked via ``--assert=reinterpret``: When an ``assert`` statement fails, ``pytest`` re-interprets -the expression part to show intermediate values. This technique suffers -from a caveat that the rewriting does not: If your expression has side -effects (better to avoid them anyway!) the intermediate values may not -be the same, confusing the reinterpreter and obfuscating the initial -error (this is also explained at the command line if it happens). - You can also turn off all assertion interaction using the ``--assert=plain`` option. diff --git a/doc/en/fixture.rst b/doc/en/fixture.rst index 7057024ab..01706479d 100644 --- a/doc/en/fixture.rst +++ b/doc/en/fixture.rst @@ -11,7 +11,7 @@ pytest fixtures: explicit, modular, scalable .. _`xUnit`: http://en.wikipedia.org/wiki/XUnit .. _`purpose of test fixtures`: http://en.wikipedia.org/wiki/Test_fixture#Software -.. _`Dependency injection`: http://en.wikipedia.org/wiki/Dependency_injection#Definition +.. _`Dependency injection`: http://en.wikipedia.org/wiki/Dependency_injection The `purpose of test fixtures`_ is to provide a fixed baseline upon which tests can reliably and repeatedly execute. pytest fixtures @@ -70,8 +70,8 @@ marked ``smtp`` fixture function. Running the test looks like this:: $ pytest test_smtpsimple.py ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 - rootdir: $REGENDOC_TMPDIR, inifile: + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + rootdir: $REGENDOC_TMPDIR, inifile: collected 1 items test_smtpsimple.py F @@ -188,8 +188,8 @@ inspect what is going on and can now run the tests:: $ pytest test_module.py ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 - rootdir: $REGENDOC_TMPDIR, inifile: + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + rootdir: $REGENDOC_TMPDIR, inifile: collected 2 items test_module.py FF @@ -243,7 +243,9 @@ Fixture finalization / executing teardown code pytest supports execution of fixture specific finalization code when the fixture goes out of scope. By using a ``yield`` statement instead of ``return``, all -the code after the *yield* statement serves as the teardown code.:: +the code after the *yield* statement serves as the teardown code: + +.. code-block:: python # content of conftest.py @@ -257,8 +259,9 @@ the code after the *yield* statement serves as the teardown code.:: print("teardown smtp") smtp.close() -The ``print`` and ``smtp.close()`` statements will execute when the last test using -the fixture in the module has finished execution, regardless of the exception status of the tests. +The ``print`` and ``smtp.close()`` statements will execute when the last test in +the module has finished execution, regardless of the exception status of the +tests. Let's execute it:: @@ -274,22 +277,23 @@ occur around each single test. In either case the test module itself does not need to change or know about these details of fixture setup. -Note that we can also seamlessly use the ``yield`` syntax with ``with`` statements:: +Note that we can also seamlessly use the ``yield`` syntax with ``with`` statements: + +.. code-block:: python # content of test_yield2.py + import smtplib import pytest - @pytest.fixture - def passwd(): - with open("/etc/passwd") as f: - yield f.readlines() + @pytest.fixture(scope="module") + def smtp(request): + with smtplib.SMTP("smtp.gmail.com") as smtp: + yield smtp # provide the fixture value - def test_has_lines(passwd): - assert len(passwd) >= 1 -The file ``f`` will be closed after the test finished execution -because the Python ``file`` object supports finalization when +The ``smtp`` connection will be closed after the test finished execution +because the ``smtp`` object automatically closes when the ``with`` statement ends. @@ -318,8 +322,7 @@ the ``with`` statement ends. request.addfinalizer(fin) return smtp # provide the fixture value - The ``fin`` function will execute when the last test using - the fixture in the module has finished execution. + The ``fin`` function will execute when the last test in the module has finished execution. This method is still fully supported, but ``yield`` is recommended from 2.10 onward because it is considered simpler and better describes the natural code flow. @@ -352,8 +355,8 @@ again, nothing much has changed:: $ pytest -s -q --tb=no FFfinalizing (smtp.gmail.com) - . - 2 failed, 1 passed in 0.12 seconds + + 2 failed in 0.12 seconds Let's quickly create another test module that actually sets the server URL in its module namespace:: @@ -375,6 +378,8 @@ Running it:: assert 0, smtp.helo() E AssertionError: (250, b'mail.python.org') E assert 0 + ------------------------- Captured stdout teardown ------------------------- + finalizing (mail.python.org) voila! The ``smtp`` fixture function picked up our mail server name from the module namespace. @@ -448,7 +453,7 @@ So let's just do another run:: response, msg = smtp.ehlo() assert response == 250 > assert b"smtp.gmail.com" in msg - E assert b'smtp.gmail.com' in b'mail.python.org\nSIZE 51200000\nETRN\nSTARTTLS\nENHANCEDSTATUSCODES\n8BITMIME\nDSN\nSMTPUTF8' + E AssertionError: assert b'smtp.gmail.com' in b'mail.python.org\nSIZE 51200000\nETRN\nSTARTTLS\nENHANCEDSTATUSCODES\n8BITMIME\nDSN\nSMTPUTF8' test_module.py:5: AssertionError -------------------------- Captured stdout setup --------------------------- @@ -464,6 +469,8 @@ So let's just do another run:: E assert 0 test_module.py:11: AssertionError + ------------------------- Captured stdout teardown ------------------------- + finalizing 4 failed in 0.12 seconds We see that our two test functions each ran twice, against the different @@ -516,9 +523,9 @@ Running the above tests results in the following test IDs being used:: $ pytest --collect-only ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 - rootdir: $REGENDOC_TMPDIR, inifile: - collected 11 items + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + rootdir: $REGENDOC_TMPDIR, inifile: + collected 10 items @@ -532,8 +539,6 @@ Running the above tests results in the following test IDs being used:: - - ======= no tests ran in 0.12 seconds ======== @@ -569,9 +574,9 @@ Here we declare an ``app`` fixture which receives the previously defined $ pytest -v test_appsetup.py ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5 cachedir: .cache - rootdir: $REGENDOC_TMPDIR, inifile: + rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 2 items test_appsetup.py::test_smtp_exists[smtp.gmail.com] PASSED @@ -638,9 +643,9 @@ Let's run the tests in verbose mode and with looking at the print-output:: $ pytest -v -s test_module.py ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5 cachedir: .cache - rootdir: $REGENDOC_TMPDIR, inifile: + rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 8 items test_module.py::test_0[1] SETUP otherarg 1 @@ -701,7 +706,7 @@ Using fixtures from classes, modules or projects Sometimes test functions do not directly need access to a fixture object. For example, tests may require to operate with an empty directory as the current working directory but otherwise do not care for the concrete -directory. Here is how you can can use the standard `tempfile +directory. Here is how you can use the standard `tempfile `_ and pytest fixtures to achieve it. We separate the creation of the fixture into a conftest.py file:: @@ -998,7 +1003,7 @@ Given the tests file structure is: @pytest.mark.parametrize('username', ['directly-overridden-username-other']) def test_username_other(other_username): - assert username == 'other-directly-overridden-username-other' + assert other_username == 'other-directly-overridden-username-other' In the example above, a fixture value is overridden by the test parameter value. Note that the value of the fixture can be overridden this way even if the test doesn't use it directly (doesn't mention it in the function prototype). diff --git a/doc/en/funcarg_compare.rst b/doc/en/funcarg_compare.rst index 88a34215f..3d121b944 100644 --- a/doc/en/funcarg_compare.rst +++ b/doc/en/funcarg_compare.rst @@ -97,7 +97,7 @@ sets. pytest-2.3 introduces a decorator for use on the factory itself:: ... # use request.param Here the factory will be invoked twice (with the respective "mysql" -and "pg" values set as ``request.param`` attributes) and and all of +and "pg" values set as ``request.param`` attributes) and all of the tests requiring "db" will run twice as well. The "mysql" and "pg" values will also be used for reporting the test-invocation variants. diff --git a/doc/en/getting-started.rst b/doc/en/getting-started.rst index 836498c69..071b296db 100644 --- a/doc/en/getting-started.rst +++ b/doc/en/getting-started.rst @@ -26,7 +26,7 @@ Installation:: To check your installation has installed the correct version:: $ pytest --version - This is pytest version 3.0.2, imported from $PYTHON_PREFIX/lib/python3.5/site-packages/pytest.py + This is pytest version 3.0.7, imported from $PYTHON_PREFIX/lib/python3.5/site-packages/pytest.py .. _`simpletest`: @@ -46,8 +46,8 @@ That's it. You can execute the test function now:: $ pytest ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 - rootdir: $REGENDOC_TMPDIR, inifile: + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + rootdir: $REGENDOC_TMPDIR, inifile: collected 1 items test_sample.py F @@ -134,7 +134,7 @@ run the module by passing its filename:: def test_two(self): x = "hello" > assert hasattr(x, 'check') - E assert False + E AssertionError: assert False E + where False = hasattr('hello', 'check') test_class.py:8: AssertionError diff --git a/doc/en/goodpractices.rst b/doc/en/goodpractices.rst index c125cc7ca..9e8811c7f 100644 --- a/doc/en/goodpractices.rst +++ b/doc/en/goodpractices.rst @@ -16,10 +16,12 @@ Conventions for Python test discovery * If no arguments are specified then collection starts from :confval:`testpaths` (if configured) or the current directory. Alternatively, command line arguments can be used in any combination of directories, file names or node ids. -* recurse into directories, unless they match :confval:`norecursedirs` -* ``test_*.py`` or ``*_test.py`` files, imported by their `test package name`_. -* ``Test`` prefixed test classes (without an ``__init__`` method) -* ``test_`` prefixed test functions or methods are test items +* Recurse into directories, unless they match :confval:`norecursedirs`. +* In those directories, search for ``test_*.py`` or ``*_test.py`` files, imported by their `test package name`_. +* From those files, collect test items: + + * ``test_`` prefixed test functions or methods outside of class + * ``test_`` prefixed test functions or methods inside ``Test`` prefixed test classes (without an ``__init__`` method) For examples of how to customize your test discovery :doc:`example/pythoncollection`. @@ -28,75 +30,108 @@ Within Python modules, ``pytest`` also discovers tests using the standard Choosing a test layout / import rules ------------------------------------------- +------------------------------------- ``pytest`` supports two common test layouts: -* putting tests into an extra directory outside your actual application - code, useful if you have many functional tests or for other reasons - want to keep tests separate from actual application code (often a good - idea):: +Tests outside application code +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - setup.py # your setuptools Python package metadata +Putting tests into an extra directory outside your actual application code +might be useful if you have many functional tests or for other reasons want +to keep tests separate from actual application code (often a good idea):: + + setup.py mypkg/ __init__.py - appmodule.py + app.py + view.py tests/ test_app.py + test_view.py ... +This way your tests can run easily against an installed version +of ``mypkg``. -* inlining test directories into your application package, useful if you - have direct relation between (unit-)test and application modules and - want to distribute your tests along with your application:: +Note that using this scheme your test files must have **unique names**, because +``pytest`` will import them as *top-level* modules since there are no packages +to derive a full package name from. In other words, the test files in the example above will +be imported as ``test_app`` and ``test_view`` top-level modules by adding ``tests/`` to +``sys.path``. - setup.py # your setuptools Python package metadata +If you need to have test modules with the same name, you might add ``__init__.py`` files to your +``tests`` folder and subfolders, changing them to packages:: + + setup.py + mypkg/ + ... + tests/ + __init__.py + foo/ + __init__.py + test_view.py + bar/ + __init__.py + test_view.py + +Now pytest will load the modules as ``tests.foo.test_view`` and ``tests.bar.test_view``, allowing +you to have modules with the same name. But now this introduces a subtle problem: in order to load +the test modules from the ``tests`` directory, pytest prepends the root of the repository to +``sys.path``, which adds the side-effect that now ``mypkg`` is also importable. +This is problematic if you are using a tool like `tox`_ to test your package in a virtual environment, +because you want to test the *installed* version of your package, not the local code from the repository. + +In this situation, it is **strongly** suggested to use a ``src`` layout where application root package resides in a +sub-directory of your root:: + + setup.py + src/ + mypkg/ + __init__.py + app.py + view.py + tests/ + __init__.py + foo/ + __init__.py + test_view.py + bar/ + __init__.py + test_view.py + + +This layout prevents a lot of common pitfalls and has many benefits, which are better explained in this excellent +`blog post by Ionel Cristian Mărieș `_. + + +Tests as part of application code +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + +Inlining test directories into your application package +is useful if you have direct relation between tests and application modules and +want to distribute them along with your application:: + + setup.py mypkg/ __init__.py - appmodule.py - ... + app.py + view.py test/ + __init__.py test_app.py + test_view.py ... -Important notes relating to both schemes: +In this scheme, it is easy to your run tests using the ``--pyargs`` option:: -- **make sure that "mypkg" is importable**, for example by typing once:: + pytest --pyargs mypkg - pip install -e . # install package using setup.py in editable mode - # similar to running `python setup.py develop` or - # `conda develop` - - This installs your package with a symlink to your development code - instead of placing the code directly in the install directory. - This way you can edit the code and run tests on your edits without - having to reinstall every time. +``pytest`` will discover where ``mypkg`` is installed and collect tests from there. -- **avoid "__init__.py" files in your test directories**. - This way your tests can run easily against an installed version - of ``mypkg``, independently from the installed package if it contains - the tests or not. +Note that this layout also works in conjunction with the ``src`` layout mentioned in the previous section. -- With inlined tests you might put ``__init__.py`` into test - directories and make them installable as part of your application. - Using the ``pytest --pyargs mypkg`` invocation pytest will - discover where mypkg is installed and collect tests from there. - With the "external" test you can still distribute tests but they - will not be installed or become importable. - -Typically you can run tests by pointing to test directories or modules:: - - pytest tests/test_app.py # for external test dirs - pytest mypkg/test/test_app.py # for inlined test dirs - pytest mypkg # run tests in all below test directories - pytest # run all tests below current dir - ... - -Because of the above ``editable install`` mode you can change your -source code (both tests and the app) and rerun tests at will. -Once you are done with your work, you can `use tox`_ to make sure -that the package is really correct and tests pass in all -required configurations. .. note:: @@ -132,7 +167,7 @@ required configurations. The reason for this somewhat evolved importing technique is that in larger projects multiple test modules might import from each other and thus deriving a canonical import name helps - to avoid surprises such as a test modules getting imported twice. + to avoid surprises such as a test module getting imported twice. .. _`virtualenv`: http://pypi.python.org/pypi/virtualenv @@ -149,21 +184,24 @@ for installing your application and any dependencies as well as the ``pytest`` package itself. This ensures your code and dependencies are isolated from the system Python installation. -If you frequently release code and want to make sure that your actual +You can then install your package in "editable" mode:: + + pip install -e . + +which lets you change your source code (both tests and application) and rerun tests at will. +This is similar to running `python setup.py develop` or `conda develop` in that it installs +your package using a symlink to your development code. + +Once you are done with your work and want to make sure that your actual package passes all tests you may want to look into `tox`_, the virtualenv test automation tool and its `pytest support -`_. +`_. Tox helps you to setup virtualenv environments with pre-defined dependencies and then executing a pre-configured test command with options. It will run tests against the installed package and not against your source code checkout, helping to detect packaging glitches. -Continuous integration services such as Jenkins_ can make use of the -``--junitxml=PATH`` option to create a JUnitXML file and generate reports (e.g. -by publishing the results in a nice format with the `Jenkins xUnit Plugin -`_). - Integrating with setuptools / ``python setup.py test`` / ``pytest-runner`` -------------------------------------------------------------------------- @@ -243,9 +281,10 @@ your own setuptools Test command for invoking pytest. self.pytest_args = [] def run_tests(self): + import shlex #import here, cause outside the eggs aren't loaded import pytest - errno = pytest.main(self.pytest_args) + errno = pytest.main(shlex.split(self.pytest_args)) sys.exit(errno) diff --git a/doc/en/index.rst b/doc/en/index.rst index 7544b8716..24ae67957 100644 --- a/doc/en/index.rst +++ b/doc/en/index.rst @@ -14,19 +14,19 @@ An example of a simple test: .. code-block:: python # content of test_sample.py - def func(x): + def inc(x): return x + 1 def test_answer(): - assert func(3) == 5 + assert inc(3) == 5 To execute it:: $ pytest ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 - rootdir: $REGENDOC_TMPDIR, inifile: + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + rootdir: $REGENDOC_TMPDIR, inifile: collected 1 items test_sample.py F @@ -35,9 +35,9 @@ To execute it:: _______ test_answer ________ def test_answer(): - > assert func(3) == 5 + > assert inc(3) == 5 E assert 4 == 5 - E + where 4 = func(3) + E + where 4 = inc(3) test_sample.py:5: AssertionError ======= 1 failed in 0.12 seconds ======== diff --git a/doc/en/monkeypatch.rst b/doc/en/monkeypatch.rst index e85d94d6d..0c07b2f44 100644 --- a/doc/en/monkeypatch.rst +++ b/doc/en/monkeypatch.rst @@ -35,7 +35,7 @@ patch this function before calling into a function which uses it:: assert x == '/abc/.ssh' Here our test function monkeypatches ``os.path.expanduser`` and -then calls into an function that calls it. After the test function +then calls into a function that calls it. After the test function finishes the ``os.path.expanduser`` modification will be undone. example: preventing "requests" from remote operations @@ -55,6 +55,14 @@ will delete the method ``request.session.Session.request`` so that any attempts within tests to create http requests will fail. +.. note:: + + Be advised that it is not recommended to patch builtin functions such as ``open``, + ``compile``, etc., because it might break pytest's internals. If that's + unavoidable, passing ``--tb=native``, ``--assert=plain`` and ``--capture=no`` might + help although there's no guarantee. + + Method reference of the monkeypatch fixture ------------------------------------------- diff --git a/doc/en/parametrize.rst b/doc/en/parametrize.rst index 825fda8e8..b93099e7d 100644 --- a/doc/en/parametrize.rst +++ b/doc/en/parametrize.rst @@ -55,8 +55,8 @@ them in turn:: $ pytest ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 - rootdir: $REGENDOC_TMPDIR, inifile: + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + rootdir: $REGENDOC_TMPDIR, inifile: collected 3 items test_expectation.py ..F @@ -73,7 +73,7 @@ them in turn:: ]) def test_eval(test_input, expected): > assert eval(test_input) == expected - E assert 54 == 42 + E AssertionError: assert 54 == 42 E + where 54 = eval('6*9') test_expectation.py:8: AssertionError @@ -103,8 +103,8 @@ Let's run this:: $ pytest ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 - rootdir: $REGENDOC_TMPDIR, inifile: + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + rootdir: $REGENDOC_TMPDIR, inifile: collected 3 items test_expectation.py ..x @@ -186,7 +186,7 @@ Let's also run with a stringinput that will lead to a failing test:: def test_valid_string(stringinput): > assert stringinput.isalpha() - E assert False + E AssertionError: assert False E + where False = () E + where = '!'.isalpha diff --git a/doc/en/projects.rst b/doc/en/projects.rst index fa7a2f29a..a2edbf68f 100644 --- a/doc/en/projects.rst +++ b/doc/en/projects.rst @@ -58,7 +58,7 @@ Here are some examples of projects using ``pytest`` (please send notes via :ref: * `katcp `_ Telescope communication protocol over Twisted * `kss plugin timer `_ * `pyudev `_ a pure Python binding to the Linux library libudev -* `pytest-localserver `_ a plugin for pytest that provides a httpserver and smtpserver +* `pytest-localserver `_ a plugin for pytest that provides an httpserver and smtpserver * `pytest-monkeyplus `_ a plugin that extends monkeypatch These projects help integrate ``pytest`` into other Python frameworks: diff --git a/doc/en/recwarn.rst b/doc/en/recwarn.rst index 735006016..7bb193c99 100644 --- a/doc/en/recwarn.rst +++ b/doc/en/recwarn.rst @@ -1,8 +1,12 @@ +.. _`asserting warnings`: + .. _assertwarnings: Asserting Warnings ===================================================== +.. _`asserting warnings with the warns function`: + .. _warns: Asserting warnings with the warns function @@ -46,6 +50,8 @@ Alternatively, you can examine raised warnings in detail using the ``DeprecationWarning`` and ``PendingDeprecationWarning`` are treated differently; see :ref:`ensuring_function_triggers`. +.. _`recording warnings`: + .. _recwarn: Recording warnings @@ -96,6 +102,8 @@ class of the warning. The ``message`` is the warning itself; calling ``DeprecationWarning`` and ``PendingDeprecationWarning`` are treated differently; see :ref:`ensuring_function_triggers`. +.. _`ensuring a function triggers a deprecation warning`: + .. _ensuring_function_triggers: Ensuring a function triggers a deprecation warning diff --git a/doc/en/requirements.txt b/doc/en/requirements.txt new file mode 100644 index 000000000..72bb60a81 --- /dev/null +++ b/doc/en/requirements.txt @@ -0,0 +1,3 @@ +# pinning sphinx to 1.4.* due to search issues with rtd: +# https://github.com/rtfd/readthedocs-sphinx-ext/issues/25 +sphinx ==1.4.* diff --git a/doc/en/skipping.rst b/doc/en/skipping.rst index dde89705e..0597c76e7 100644 --- a/doc/en/skipping.rst +++ b/doc/en/skipping.rst @@ -2,7 +2,7 @@ .. _skipping: -Skip and xfail: dealing with tests that can not succeed +Skip and xfail: dealing with tests that cannot succeed ===================================================================== If you have test functions that cannot be run on certain platforms @@ -224,8 +224,8 @@ Running it with the report-on-xfail option gives this output:: example $ pytest -rx xfail_demo.py ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 - rootdir: $REGENDOC_TMPDIR/example, inifile: + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + rootdir: $REGENDOC_TMPDIR/example, inifile: collected 7 items xfail_demo.py xxxxxxx @@ -293,6 +293,20 @@ imperatively, in test or setup code:: # or pytest.skip("unsupported configuration") +Note that calling ``pytest.skip`` at the module level +is not allowed since pytest 3.0. If you are upgrading +and ``pytest.skip`` was being used at the module level, you can set a +``pytestmark`` variable: + +.. code-block:: python + + # before pytest 3.0 + pytest.skip('skipping all tests because of reasons') + # after pytest 3.0 + pytestmark = pytest.mark.skip('skipping all tests because of reasons') + +``pytestmark`` applies a mark or list of marks to all tests in a module. + Skipping on a missing import dependency -------------------------------------------------- @@ -371,3 +385,27 @@ The equivalent with "boolean conditions" is:: imported before pytest's argument parsing takes place. For example, ``conftest.py`` files are imported before command line parsing and thus ``config.getvalue()`` will not execute correctly. + + +Summary +------- + +Here's a quick guide on how to skip tests in a module in different situations: + +1. Skip all tests in a module unconditionally: + + .. code-block:: python + + pytestmark = pytest.mark.skip('all tests still WIP') + +2. Skip all tests in a module based on some condition: + + .. code-block:: python + + pytestmark = pytest.mark.skipif(sys.platform == 'win32', 'tests for linux only') + +3. Skip all tests in a module if some import is missing: + + .. code-block:: python + + pexpect = pytest.importorskip('pexpect') diff --git a/doc/en/talks.rst b/doc/en/talks.rst index 1572832f0..85faf6455 100644 --- a/doc/en/talks.rst +++ b/doc/en/talks.rst @@ -4,13 +4,18 @@ Talks and Tutorials .. sidebar:: Next Open Trainings - `professional testing with pytest and tox `_, 27-29th June 2016, Freiburg, Germany + `Professional Testing with Python + `_, + 26-28 April 2017, Leipzig, Germany. .. _`funcargs`: funcargs.html Talks and blog postings --------------------------------------------- +- `Pythonic testing, Igor Starikov (Russian, PyNsk, November 2016) + `_. + - `pytest - Rapid Simple Testing, Florian Bruhin, Swiss Python Summit 2016 `_. diff --git a/doc/en/test/plugin/xdist.rst b/doc/en/test/plugin/xdist.rst index 79440998b..506d240ae 100644 --- a/doc/en/test/plugin/xdist.rst +++ b/doc/en/test/plugin/xdist.rst @@ -71,7 +71,7 @@ you can ad-hoc distribute your tests by typing:: pytest -d --tx ssh=myhostpopen --rsyncdir mypkg mypkg This will synchronize your ``mypkg`` package directory -to an remote ssh account and then locally collect tests +to a remote ssh account and then locally collect tests and send them to remote places for execution. You can specify multiple ``--rsyncdir`` directories diff --git a/doc/en/tmpdir.rst b/doc/en/tmpdir.rst index 880be2d73..637047195 100644 --- a/doc/en/tmpdir.rst +++ b/doc/en/tmpdir.rst @@ -29,8 +29,8 @@ Running this would result in a passed test except for the last $ pytest test_tmpdir.py ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 - rootdir: $REGENDOC_TMPDIR, inifile: + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + rootdir: $REGENDOC_TMPDIR, inifile: collected 1 items test_tmpdir.py F diff --git a/doc/en/unittest.rst b/doc/en/unittest.rst index 3d1ebbbf3..6809f68fd 100644 --- a/doc/en/unittest.rst +++ b/doc/en/unittest.rst @@ -17,6 +17,14 @@ run using ``pytest``. We assume here that you are familiar with writing ``unittest.TestCase`` style tests and rather focus on integration aspects. +Note that this is meant as a provisional way of running your test code +until you fully convert to pytest-style tests. To fully take advantage of +:ref:`fixtures `, :ref:`parametrization ` and +:ref:`hooks ` you should convert (tools like `unittest2pytest +`__ are helpful). +Also, not all 3rd party pluging are expected to work best with +``unittest.TestCase`` style tests. + Usage ------------------------------------------------------------------- @@ -38,7 +46,11 @@ the general ``pytest`` documentation for many more examples. Running tests from ``unittest.TestCase`` subclasses with ``--pdb`` will disable tearDown and cleanup methods for the case that an Exception occurs. This allows proper post mortem debugging for all applications - which have significant logic in their tearDown machinery. + which have significant logic in their tearDown machinery. However, + supporting this feature has the following side effect: If people + overwrite ``unittest.TestCase`` ``__call__`` or ``run``, they need to + to overwrite ``debug`` in the same way (this is also true for standard + unittest). Mixing pytest fixtures into unittest.TestCase style tests ----------------------------------------------------------- @@ -96,8 +108,8 @@ the ``self.db`` values in the traceback:: $ pytest test_unittest_db.py ======= test session starts ======== - platform linux -- Python 3.5.2, pytest-3.0.2, py-1.4.31, pluggy-0.3.1 - rootdir: $REGENDOC_TMPDIR, inifile: + platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 + rootdir: $REGENDOC_TMPDIR, inifile: collected 2 items test_unittest_db.py FF @@ -187,12 +199,3 @@ was executed ahead of the ``test_method``. pytest fixtures into unittest suites. And of course you can also start to selectively leave away the ``unittest.TestCase`` subclassing, use plain asserts and get the unlimited pytest feature set. - - -Converting from unittest to pytest ---------------------------------------- - -If you want to convert your unittest testcases to pytest, there are -some helpers like `unittest2pytest -`__, which uses lib2to3 -and introspection for the transformation. diff --git a/doc/en/usage.rst b/doc/en/usage.rst index f87e1496d..8cc682787 100644 --- a/doc/en/usage.rst +++ b/doc/en/usage.rst @@ -16,8 +16,20 @@ You can invoke testing through the Python interpreter from the command line:: python -m pytest [...] -This is equivalent to invoking the command line script ``pytest [...]`` -directly. +This is almost equivalent to invoking the command line script ``pytest [...]`` +directly, except that python will also add the current directory to ``sys.path``. + +Possible exit codes +-------------------------------------------------------------- + +Running ``pytest`` can result in six different exit codes: + +:Exit code 0: All tests were collected and passed successfully +:Exit code 1: Tests were collected and run but some of the tests failed +:Exit code 2: Test execution was interrupted by the user +:Exit code 3: Internal error happened while executing tests +:Exit code 4: pytest command line usage error +:Exit code 5: No tests were collected Getting help on version, option names, environment variables -------------------------------------------------------------- @@ -49,7 +61,7 @@ Several test run options:: # will select TestMyClass.test_something # but not TestMyClass.test_method_simple pytest test_mod.py::test_func # only run tests that match the "node ID", - # e.g "test_mod.py::test_func" will select + # e.g. "test_mod.py::test_func" will select # only test_func in test_mod.py pytest test_mod.py::TestClass::test_method # run a single method in # a single class @@ -76,7 +88,7 @@ Examples for modifying traceback printing:: The ``--full-trace`` causes very long traces to be printed on error (longer than ``--tb=long``). It also ensures that a stack trace is printed on -**KeyboardInterrrupt** (Ctrl+C). +**KeyboardInterrupt** (Ctrl+C). This is very useful if the tests are taking too long and you interrupt them with Ctrl+C to find out where the tests are *hanging*. By default no output will be shown (because KeyboardInterrupt is caught by pytest). By using this @@ -192,7 +204,7 @@ This will add an extra property ``example_key="1"`` to the generated .. warning:: - This is an experimental feature, and its interface might be replaced + ``record_xml_property`` is an experimental feature, and its interface might be replaced by something more powerful and general in future versions. The functionality per-se will be kept, however. @@ -310,10 +322,6 @@ You can pass in options and arguments:: pytest.main(['-x', 'mytestdir']) -or pass in a string:: - - pytest.main("-x mytestdir") - You can specify additional plugins to ``pytest.main``:: # content of myinvoke.py diff --git a/doc/en/writing_plugins.rst b/doc/en/writing_plugins.rst index 8a46648cd..f6ed6e4e3 100644 --- a/doc/en/writing_plugins.rst +++ b/doc/en/writing_plugins.rst @@ -172,7 +172,7 @@ If a package is installed this way, ``pytest`` will load .. note:: Make sure to include ``Framework :: Pytest`` in your list of - `PyPI classifiers `_ + `PyPI classifiers `_ to make it easy for users to find your plugin. @@ -236,22 +236,33 @@ import ``helper.py`` normally. The contents of Requiring/Loading plugins in a test module or conftest file ----------------------------------------------------------- -You can require plugins in a test module or a conftest file like this:: +You can require plugins in a test module or a ``conftest.py`` file like this: - pytest_plugins = "name1", "name2", +.. code-block:: python + + pytest_plugins = ["name1", "name2"] When the test module or conftest plugin is loaded the specified plugins -will be loaded as well. You can also use dotted path like this:: +will be loaded as well. Any module can be blessed as a plugin, including internal +application modules: + +.. code-block:: python pytest_plugins = "myapp.testsupport.myplugin" -which will import the specified module as a ``pytest`` plugin. +``pytest_plugins`` variables are processed recursively, so note that in the example above +if ``myapp.testsupport.myplugin`` also declares ``pytest_plugins``, the contents +of the variable will also be loaded as plugins, and so on. -Plugins imported like this will automatically be marked to require -assertion rewriting using the :func:`pytest.register_assert_rewrite` -mechanism. However for this to have any effect the module must not be -imported already, it it was already imported at the time the -``pytest_plugins`` statement is processed a warning will result and +This mechanism makes it easy to share fixtures within applications or even +external applications without the need to create external plugins using +the ``setuptools``'s entry point technique. + +Plugins imported by ``pytest_plugins`` will also automatically be marked +for assertion rewriting (see :func:`pytest.register_assert_rewrite`). +However for this to have any effect the module must not be +imported already; if it was already imported at the time the +``pytest_plugins`` statement is processed, a warning will result and assertions inside the plugin will not be re-written. To fix this you can either call :func:`pytest.register_assert_rewrite` yourself before the module is imported, or you can arrange the code to delay the diff --git a/plugin-test.sh b/plugin-test.sh deleted file mode 100644 index 7830b7c7d..000000000 --- a/plugin-test.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash - -# this assumes plugins are installed as sister directories - -set -e -cd ../pytest-pep8 -pytest -cd ../pytest-instafail -pytest -cd ../pytest-cache -pytest -cd ../pytest-xprocess -pytest -#cd ../pytest-cov -#pytest -cd ../pytest-capturelog -pytest -cd ../pytest-xdist -pytest - diff --git a/requirements-docs.txt b/requirements-docs.txt deleted file mode 100644 index be3a232e5..000000000 --- a/requirements-docs.txt +++ /dev/null @@ -1,3 +0,0 @@ -sphinx==1.2.3 -regendoc -pyyaml diff --git a/runtox.py b/runtox.py deleted file mode 100644 index 8c13c53e1..000000000 --- a/runtox.py +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env python - -if __name__ == "__main__": - import subprocess - import sys - subprocess.call([sys.executable, "-m", "tox", - "-i", "ALL=https://devpi.net/hpk/dev/", - "--develop"] + sys.argv[1:]) diff --git a/scripts/call-tox.bat b/scripts/call-tox.bat new file mode 100644 index 000000000..3ca9eb6d7 --- /dev/null +++ b/scripts/call-tox.bat @@ -0,0 +1,8 @@ +REM skip "coveralls" run in PRs or forks +if "%TOXENV%" == "coveralls" ( + if not defined COVERALLS_REPO_TOKEN ( + echo skipping coveralls run because COVERALLS_REPO_TOKEN is not defined + exit /b 0 + ) +) +C:\Python35\python -m tox diff --git a/scripts/check-manifest.py b/scripts/check-manifest.py new file mode 100644 index 000000000..5911a84fe --- /dev/null +++ b/scripts/check-manifest.py @@ -0,0 +1,21 @@ +""" +Script used by tox.ini to check the manifest file if we are under version control, or skip the +check altogether if not. + +"check-manifest" will needs a vcs to work, which is not available when testing the package +instead of the source code (with ``devpi test`` for example). +""" + +from __future__ import print_function + +import os +import subprocess +import sys + + +if os.path.isdir('.git'): + sys.exit(subprocess.call('check-manifest', shell=True)) +else: + print('No .git directory found, skipping checking the manifest file') + sys.exit(0) + diff --git a/scripts/install-pypy.bat b/scripts/install-pypy.bat new file mode 100644 index 000000000..8012ea46a --- /dev/null +++ b/scripts/install-pypy.bat @@ -0,0 +1,6 @@ +REM install pypy using choco +REM redirect to a file because choco install python.pypy is too noisy. If the command fails, write output to console +choco install python.pypy > pypy-inst.log 2>&1 || (type pypy-inst.log & exit /b 1) +set PATH=C:\tools\pypy\pypy;%PATH% # so tox can find pypy +echo PyPy installed +pypy --version diff --git a/setup.cfg b/setup.cfg index 1ab4fd059..f3299af5b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -9,5 +9,8 @@ upload-dir = doc/en/build/html [bdist_wheel] universal = 1 +[metadata] +license_file = LICENSE + [devpi:upload] formats = sdist.tgz,bdist_wheel diff --git a/setup.py b/setup.py index 23ac0eb03..1d0630cd2 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ classifiers = ['Development Status :: 6 - Mature', 'Topic :: Software Development :: Libraries', 'Topic :: Utilities'] + [ ('Programming Language :: Python :: %s' % x) for x in - '2 2.6 2.7 3 3.3 3.4 3.5'.split()] + '2 2.6 2.7 3 3.3 3.4 3.5 3.6'.split()] with open('README.rst') as fd: long_description = fd.read() @@ -48,7 +48,7 @@ def has_environment_marker_support(): def main(): - install_requires = ['py>=1.4.29'] # pluggy is vendored in _pytest.vendored_packages + install_requires = ['py>=1.4.29', 'setuptools'] # pluggy is vendored in _pytest.vendored_packages extras_require = {} if has_environment_marker_support(): extras_require[':python_version=="2.6"'] = ['argparse'] @@ -68,10 +68,10 @@ def main(): license='MIT license', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], author='Holger Krekel, Bruno Oliveira, Ronny Pfannschmidt, Floris Bruynooghe, Brianna Laugher, Florian Bruhin and others', - author_email='holger at merlinux.eu', entry_points={'console_scripts': ['pytest=pytest:main', 'py.test=pytest:main']}, classifiers=classifiers, + keywords="test unittest", cmdclass={'test': PyTest}, # the following should be enabled for release install_requires=install_requires, diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index b03f7fe4c..88e3fa449 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -120,7 +120,7 @@ class TestGeneralUsage: result.stdout.fnmatch_lines([ #XXX on jython this fails: "> import import_fails", "ImportError while importing test module*", - "'No module named *does_not_work*", + "*No module named *does_not_work*", ]) assert result.ret == 2 diff --git a/testing/code/test_code.py b/testing/code/test_code.py index 6f1d9d3cc..ad9db6d2e 100644 --- a/testing/code/test_code.py +++ b/testing/code/test_code.py @@ -24,6 +24,7 @@ def test_code_with_class(): pass pytest.raises(TypeError, "_pytest._code.Code(A)") + if True: def x(): pass @@ -68,8 +69,10 @@ def test_code_from_func(): def test_unicode_handling(): value = py.builtin._totext('\xc4\x85\xc4\x87\n', 'utf-8').encode('utf8') + def f(): raise Exception(value) + excinfo = pytest.raises(Exception, f) str(excinfo) if sys.version_info[0] < 3: @@ -79,8 +82,10 @@ def test_unicode_handling(): @pytest.mark.skipif(sys.version_info[0] >= 3, reason='python 2 only issue') def test_unicode_handling_syntax_error(): value = py.builtin._totext('\xc4\x85\xc4\x87\n', 'utf-8').encode('utf8') + def f(): raise SyntaxError('invalid syntax', (None, 1, 3, value)) + excinfo = pytest.raises(Exception, f) str(excinfo) if sys.version_info[0] < 3: diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index 283f8eb76..23b0a985e 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -56,13 +56,15 @@ def test_excinfo_simple(): def test_excinfo_getstatement(): def g(): raise ValueError + def f(): g() + try: f() except ValueError: excinfo = _pytest._code.ExceptionInfo() - linenumbers = [_pytest._code.getrawcode(f).co_firstlineno - 1 + 3, + linenumbers = [_pytest._code.getrawcode(f).co_firstlineno - 1 + 4, _pytest._code.getrawcode(f).co_firstlineno - 1 + 1, _pytest._code.getrawcode(g).co_firstlineno - 1 + 1, ] l = list(excinfo.traceback) @@ -168,11 +170,13 @@ class TestTraceback_f_g_h: # raise ValueError # + def g(): # __tracebackhide__ = tracebackhide f() # + def h(): # g() @@ -214,15 +218,18 @@ class TestTraceback_f_g_h: def test_traceback_no_recursion_index(self): def do_stuff(): raise RuntimeError + def reraise_me(): import sys exc, val, tb = sys.exc_info() py.builtin._reraise(exc, val, tb) + def f(n): try: do_stuff() except: reraise_me() + excinfo = pytest.raises(RuntimeError, f, 8) traceback = excinfo.traceback recindex = traceback.recursionindex() @@ -245,17 +252,18 @@ class TestTraceback_f_g_h: excinfo = pytest.raises(ValueError, fail) assert excinfo.traceback.recursionindex() is None - - def test_traceback_getcrashentry(self): def i(): __tracebackhide__ = True raise ValueError + def h(): i() + def g(): __tracebackhide__ = True h() + def f(): g() @@ -271,6 +279,7 @@ class TestTraceback_f_g_h: def g(): __tracebackhide__ = True raise ValueError + def f(): __tracebackhide__ = True g() @@ -465,11 +474,13 @@ raise ValueError() class FakeCode(object): class raw: co_filename = '?' + path = '?' firstlineno = 5 def fullsource(self): return None + fullsource = property(fullsource) class FakeFrame(object): @@ -491,17 +502,21 @@ raise ValueError() class FakeExcinfo(_pytest._code.ExceptionInfo): typename = "Foo" value = Exception() + def __init__(self): pass def exconly(self, tryshort): return "EXC" + def errisinstance(self, cls): return False excinfo = FakeExcinfo() + class FakeRawTB(object): tb_next = None + tb = FakeRawTB() excinfo.traceback = Traceback(tb) @@ -719,8 +734,10 @@ raise ValueError() excinfo = pytest.raises(ValueError, mod.entry) p = FormattedExcinfo() + def raiseos(): raise OSError(2) + monkeypatch.setattr(py.std.os, 'getcwd', raiseos) assert p._makepath(__file__) == __file__ p.repr_traceback(excinfo) @@ -789,9 +806,11 @@ raise ValueError() def test_reprexcinfo_unicode(self): from _pytest._code.code import TerminalRepr + class MyRepr(TerminalRepr): def toterminal(self, tw): tw.line(py.builtin._totext("я", "utf-8")) + x = py.builtin._totext(MyRepr()) assert x == py.builtin._totext("я", "utf-8") @@ -1050,6 +1069,50 @@ raise ValueError() assert line.endswith('mod.py') assert tw.lines[47] == ":15: AttributeError" + @pytest.mark.skipif("sys.version_info[0] < 3") + @pytest.mark.parametrize('reason, description', [ + ('cause', 'The above exception was the direct cause of the following exception:'), + ('context', 'During handling of the above exception, another exception occurred:'), + ]) + def test_exc_chain_repr_without_traceback(self, importasmod, reason, description): + """ + Handle representation of exception chains where one of the exceptions doesn't have a + real traceback, such as those raised in a subprocess submitted by the multiprocessing + module (#1984). + """ + from _pytest.pytester import LineMatcher + exc_handling_code = ' from e' if reason == 'cause' else '' + mod = importasmod(""" + def f(): + try: + g() + except Exception as e: + raise RuntimeError('runtime problem'){exc_handling_code} + def g(): + raise ValueError('invalid value') + """.format(exc_handling_code=exc_handling_code)) + + with pytest.raises(RuntimeError) as excinfo: + mod.f() + + # emulate the issue described in #1984 + attr = '__%s__' % reason + getattr(excinfo.value, attr).__traceback__ = None + + r = excinfo.getrepr() + tw = py.io.TerminalWriter(stringio=True) + tw.hasmarkup = False + r.toterminal(tw) + + matcher = LineMatcher(tw.stringio.getvalue().splitlines()) + matcher.fnmatch_lines([ + "ValueError: invalid value", + description, + "* except Exception as e:", + "> * raise RuntimeError('runtime problem')" + exc_handling_code, + "E *RuntimeError: runtime problem", + ]) + @pytest.mark.parametrize("style", ["short", "long"]) @pytest.mark.parametrize("encoding", [None, "utf8", "utf16"]) diff --git a/testing/python/approx.py b/testing/python/approx.py index 2720c573f..fc1cbf9ab 100644 --- a/testing/python/approx.py +++ b/testing/python/approx.py @@ -1,5 +1,5 @@ # encoding: utf-8 - +import sys import pytest import doctest @@ -9,6 +9,7 @@ from decimal import Decimal from fractions import Fraction inf, nan = float('inf'), float('nan') + class MyDocTestRunner(doctest.DocTestRunner): def __init__(self): @@ -22,12 +23,17 @@ class MyDocTestRunner(doctest.DocTestRunner): class TestApprox: def test_repr_string(self): - # Just make sure the Unicode handling doesn't raise any exceptions. - print(approx(1.0)) - print(approx([1.0, 2.0, 3.0])) - print(approx(inf)) - print(approx(1.0, rel=nan)) - print(approx(1.0, rel=inf)) + # for some reason in Python 2.6 it is not displaying the tolerance representation correctly + plus_minus = u'\u00b1' if sys.version_info[0] > 2 else u'+-' + tol1, tol2, infr = '1.0e-06', '2.0e-06', 'inf' + if sys.version_info[:2] == (2, 6): + tol1, tol2, infr = '???', '???', '???' + assert repr(approx(1.0)) == '1.0 {pm} {tol1}'.format(pm=plus_minus, tol1=tol1) + assert repr(approx([1.0, 2.0])) == '1.0 {pm} {tol1}, 2.0 {pm} {tol2}'.format(pm=plus_minus, tol1=tol1, tol2=tol2) + assert repr(approx(inf)) == 'inf' + assert repr(approx(1.0, rel=nan)) == '1.0 {pm} ???'.format(pm=plus_minus) + assert repr(approx(1.0, rel=inf)) == '1.0 {pm} {infr}'.format(pm=plus_minus, infr=infr) + assert repr(approx(1.0j, rel=inf)) == '1j' def test_operator_overloading(self): assert 1 == approx(1, rel=1e-6, abs=1e-12) @@ -284,3 +290,23 @@ class TestApprox: runner = MyDocTestRunner() runner.run(test) + def test_unicode_plus_minus(self, testdir): + """ + Comparing approx instances inside lists should not produce an error in the detailed diff. + Integration test for issue #2111. + """ + testdir.makepyfile(""" + import pytest + def test_foo(): + assert [3] == [pytest.approx(4)] + """) + expected = '4.0e-06' + # for some reason in Python 2.6 it is not displaying the tolerance representation correctly + if sys.version_info[:2] == (2, 6): + expected = '???' + result = testdir.runpytest() + result.stdout.fnmatch_lines([ + '*At index 0 diff: 3 != 4 * {0}'.format(expected), + '=* 1 failed in *=', + ]) + diff --git a/testing/python/collect.py b/testing/python/collect.py index 843f26a73..d0e490832 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +import os import sys from textwrap import dedent @@ -68,9 +69,58 @@ class TestModule: result = testdir.runpytest("-rw") result.stdout.fnmatch_lines([ "ImportError while importing test module*test_one.part1*", - "Make sure your test modules/packages have valid Python names.", + "Hint: make sure your test modules/packages have valid Python names.", ]) + @pytest.mark.parametrize('verbose', [0, 1, 2]) + def test_show_traceback_import_error(self, testdir, verbose): + """Import errors when collecting modules should display the traceback (#1976). + + With low verbosity we omit pytest and internal modules, otherwise show all traceback entries. + """ + testdir.makepyfile( + foo_traceback_import_error=""" + from bar_traceback_import_error import NOT_AVAILABLE + """, + bar_traceback_import_error="", + ) + testdir.makepyfile(""" + import foo_traceback_import_error + """) + args = ('-v',) * verbose + result = testdir.runpytest(*args) + result.stdout.fnmatch_lines([ + "ImportError while importing test module*", + "Traceback:", + "*from bar_traceback_import_error import NOT_AVAILABLE", + "*cannot import name *NOT_AVAILABLE*", + ]) + assert result.ret == 2 + + stdout = result.stdout.str() + for name in ('_pytest', os.path.join('py', '_path')): + if verbose == 2: + assert name in stdout + else: + assert name not in stdout + + + def test_show_traceback_import_error_unicode(self, testdir): + """Check test modules collected which raise ImportError with unicode messages + are handled properly (#2336). + """ + testdir.makepyfile(u""" + # -*- coding: utf-8 -*- + raise ImportError(u'Something bad happened ☺') + """) + result = testdir.runpytest() + result.stdout.fnmatch_lines([ + "ImportError while importing test module*", + "Traceback:", + "*raise ImportError*Something bad happened*", + ]) + assert result.ret == 2 + class TestClass: def test_class_with_init_warning(self, testdir): @@ -133,6 +183,16 @@ class TestClass: "because it has a __new__ constructor*" ) + def test_issue2234_property(self, testdir): + testdir.makepyfile(""" + class TestCase(object): + @property + def prop(self): + raise NotImplementedError() + """) + result = testdir.runpytest() + assert result.ret == EXIT_NOTESTSCOLLECTED + class TestGenerator: def test_generative_functions(self, testdir): @@ -350,10 +410,13 @@ class TestFunction: config = testdir.parseconfigure() session = testdir.Session(config) session._fixturemanager = FixtureManager(session) + def func1(): pass + def func2(): pass + f1 = pytest.Function(name="name", parent=session, config=config, args=(1,), callobj=func1) assert f1 == f1 @@ -514,12 +577,15 @@ class TestFunction: def test_pyfunc_call(self, testdir): item = testdir.getitem("def test_func(): raise ValueError") config = item.config + class MyPlugin1: def pytest_pyfunc_call(self, pyfuncitem): raise ValueError + class MyPlugin2: def pytest_pyfunc_call(self, pyfuncitem): return True + config.pluginmanager.register(MyPlugin1()) config.pluginmanager.register(MyPlugin2()) config.hook.pytest_runtest_setup(item=item) diff --git a/testing/python/fixture.py b/testing/python/fixture.py index 5ff459cbc..be99ed833 100644 --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -10,15 +10,20 @@ from _pytest import fixtures def test_getfuncargnames(): def f(): pass assert not fixtures.getfuncargnames(f) + def g(arg): pass assert fixtures.getfuncargnames(g) == ('arg',) + def h(arg1, arg2="hello"): pass assert fixtures.getfuncargnames(h) == ('arg1',) + def h(arg1, arg2, arg3="hello"): pass assert fixtures.getfuncargnames(h) == ('arg1', 'arg2') + class A: def f(self, arg1, arg2="hello"): pass + assert fixtures.getfuncargnames(A().f) == ('arg1',) if sys.version_info < (3,0): assert fixtures.getfuncargnames(A.f) == ('arg1',) @@ -869,8 +874,10 @@ class TestRequestCachedSetup: item1 = testdir.getitem("def test_func(): pass") req1 = fixtures.FixtureRequest(item1) l = ["hello", "world"] + def setup(): return l.pop() + ret1 = req1.cached_setup(setup, extrakey=1) ret2 = req1.cached_setup(setup, extrakey=2) assert ret2 == "hello" @@ -884,10 +891,13 @@ class TestRequestCachedSetup: item1 = testdir.getitem("def test_func(): pass") req1 = fixtures.FixtureRequest(item1) l = [] + def setup(): l.append("setup") + def teardown(val): l.append("teardown") + req1.cached_setup(setup, teardown, scope="function") assert l == ['setup'] # artificial call of finalizer @@ -1063,6 +1073,22 @@ class TestFixtureUsages: "*1 error*" ]) + def test_invalid_scope(self, testdir): + testdir.makepyfile(""" + import pytest + @pytest.fixture(scope="functions") + def badscope(): + pass + + def test_nothing(badscope): + pass + """) + result = testdir.runpytest_inprocess() + result.stdout.fnmatch_lines( + ("*ValueError: fixture badscope from test_invalid_scope.py has an unsupported" + " scope value 'functions'") + ) + def test_funcarg_parametrized_and_used_twice(self, testdir): testdir.makepyfile(""" import pytest diff --git a/testing/python/integration.py b/testing/python/integration.py index 237becd6f..6697342ea 100644 --- a/testing/python/integration.py +++ b/testing/python/integration.py @@ -63,10 +63,12 @@ class TestOEJSKITSpecials: def test_wrapped_getfslineno(): def func(): pass + def wrap(f): func.__wrapped__ = f func.patchings = ["qwe"] return func + @wrap def wrapped_func(x, y, z): pass @@ -77,28 +79,36 @@ def test_wrapped_getfslineno(): class TestMockDecoration: def test_wrapped_getfuncargnames(self): from _pytest.compat import getfuncargnames + def wrap(f): + def func(): pass + func.__wrapped__ = f return func + @wrap def f(x): pass + l = getfuncargnames(f) assert l == ("x",) def test_wrapped_getfuncargnames_patching(self): from _pytest.compat import getfuncargnames + def wrap(f): def func(): pass func.__wrapped__ = f func.patchings = ["qwe"] return func + @wrap def f(x, y, z): pass + l = getfuncargnames(f) assert l == ("y", "z") diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index b9bf589c3..a7e1d5699 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -20,8 +20,10 @@ class TestMetafunc: # initiliazation class FixtureInfo: name2fixturedefs = None + def __init__(self, names): self.names_closure = names + names = fixtures.getfuncargnames(func) fixtureinfo = FixtureInfo(names) return python.Metafunc(func, fixtureinfo, None) @@ -65,7 +67,9 @@ class TestMetafunc: def test_addcall_param(self): def func(arg1): pass metafunc = self.Metafunc(func) + class obj: pass + metafunc.addcall(param=obj) metafunc.addcall(param=obj) metafunc.addcall(param=1) @@ -76,8 +80,11 @@ class TestMetafunc: def test_addcall_funcargs(self): def func(x): pass + metafunc = self.Metafunc(func) + class obj: pass + metafunc.addcall(funcargs={"x": 2}) metafunc.addcall(funcargs={"x": 3}) pytest.raises(pytest.fail.Exception, "metafunc.addcall({'xyz': 0})") @@ -96,6 +103,14 @@ class TestMetafunc: pytest.raises(ValueError, lambda: metafunc.parametrize("y", [5,6])) pytest.raises(ValueError, lambda: metafunc.parametrize("y", [5,6])) + def test_parametrize_bad_scope(self, testdir): + def func(x): pass + metafunc = self.Metafunc(func) + try: + metafunc.parametrize("x", [1], scope='doggy') + except ValueError as ve: + assert "has an unsupported scope value 'doggy'" in str(ve) + def test_parametrize_and_id(self): def func(x, y): pass metafunc = self.Metafunc(func) @@ -134,8 +149,10 @@ class TestMetafunc: def test_parametrize_with_userobjects(self): def func(x, y): pass metafunc = self.Metafunc(func) + class A: pass + metafunc.parametrize("x", [A(), A()]) metafunc.parametrize("y", list("ab")) assert metafunc._calls[0].id == "x0-a" @@ -246,6 +263,7 @@ class TestMetafunc: @pytest.mark.issue351 def test_idmaker_idfn(self): from _pytest.python import idmaker + def ids(val): if isinstance(val, Exception): return repr(val) @@ -262,6 +280,7 @@ class TestMetafunc: @pytest.mark.issue351 def test_idmaker_idfn_unique_names(self): from _pytest.python import idmaker + def ids(val): return 'a' @@ -277,6 +296,7 @@ class TestMetafunc: @pytest.mark.issue351 def test_idmaker_idfn_exception(self): from _pytest.python import idmaker + def ids(val): raise Exception("bad code") diff --git a/testing/python/raises.py b/testing/python/raises.py index 59fd622fd..8f141cfa1 100644 --- a/testing/python/raises.py +++ b/testing/python/raises.py @@ -1,4 +1,6 @@ import pytest +import sys + class TestRaises: def test_raises(self): @@ -96,3 +98,31 @@ class TestRaises: assert e.msg == message else: assert False, "Expected pytest.raises.Exception" + + @pytest.mark.parametrize('method', ['function', 'with']) + def test_raises_cyclic_reference(self, method): + """ + Ensure pytest.raises does not leave a reference cycle (#1965). + """ + import gc + + class T(object): + def __call__(self): + raise ValueError + + t = T() + if method == 'function': + pytest.raises(ValueError, t) + else: + with pytest.raises(ValueError): + t() + + # ensure both forms of pytest.raises don't leave exceptions in sys.exc_info() + assert sys.exc_info() == (None, None, None) + + del t + + # ensure the t instance is not stuck in a cyclic reference + for o in gc.get_objects(): + assert type(o) is not T + diff --git a/testing/test_assertion.py b/testing/test_assertion.py index af7e7e0fe..bc814590a 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -12,12 +12,15 @@ PY3 = sys.version_info >= (3, 0) @pytest.fixture def mock_config(): + class Config(object): verbose = False + def getoption(self, name): if name == 'verbose': return self.verbose raise KeyError('Not mocked out: %s' % name) + return Config() @@ -55,6 +58,23 @@ class TestImportHookInstallation: assert 0 result.stdout.fnmatch_lines([expected]) + def test_rewrite_assertions_pytester_plugin(self, testdir): + """ + Assertions in the pytester plugin must also benefit from assertion + rewriting (#1920). + """ + testdir.makepyfile(""" + pytest_plugins = ['pytester'] + def test_dummy_failure(testdir): # how meta! + testdir.makepyfile('def test(): assert 0') + r = testdir.inline_run() + r.assertoutcome(passed=1) + """) + result = testdir.runpytest_subprocess() + result.stdout.fnmatch_lines([ + '*assert 1 == 0*', + ]) + @pytest.mark.parametrize('mode', ['plain', 'rewrite']) def test_pytest_plugins_rewrite(self, testdir, mode): contents = { @@ -344,7 +364,7 @@ class TestAssert_reprcompare: expl = '\n'.join(callequal(left, right, verbose=True)) assert expl.endswith(textwrap.dedent(expected).strip()) - def test_list_different_lenghts(self): + def test_list_different_lengths(self): expl = callequal([0, 1], [0, 1, 2]) assert len(expl) > 1 expl = callequal([0, 1, 2], [0, 1]) @@ -749,6 +769,37 @@ def test_traceback_failure(testdir): "*test_traceback_failure.py:4: AssertionError" ]) + +@pytest.mark.skipif(sys.version_info[:2] <= (3, 3), reason='Python 3.4+ shows chained exceptions on multiprocess') +def test_exception_handling_no_traceback(testdir): + """ + Handle chain exceptions in tasks submitted by the multiprocess module (#1984). + """ + p1 = testdir.makepyfile(""" + from multiprocessing import Pool + + def process_task(n): + assert n == 10 + + def multitask_job(): + tasks = [1] + with Pool(processes=1) as pool: + pool.map(process_task, tasks) + + def test_multitask_job(): + multitask_job() + """) + result = testdir.runpytest(p1, "--tb=long") + result.stdout.fnmatch_lines([ + "====* FAILURES *====", + "*multiprocessing.pool.RemoteTraceback:*", + "Traceback (most recent call last):", + "*assert n == 10", + "The above exception was the direct cause of the following exception:", + "> * multitask_job()", + ]) + + @pytest.mark.skipif("'__pypy__' in sys.builtin_module_names or sys.platform.startswith('java')" ) def test_warn_missing(testdir): testdir.makepyfile("") @@ -864,3 +915,34 @@ def test_assert_with_unicode(monkeypatch, testdir): """) result = testdir.runpytest() result.stdout.fnmatch_lines(['*AssertionError*']) + +def test_raise_unprintable_assertion_error(testdir): + testdir.makepyfile(r""" + def test_raise_assertion_error(): + raise AssertionError('\xff') + """) + result = testdir.runpytest() + result.stdout.fnmatch_lines([r"> raise AssertionError('\xff')", 'E AssertionError: *']) + +def test_raise_assertion_error_raisin_repr(testdir): + testdir.makepyfile(u""" + class RaisingRepr(object): + def __repr__(self): + raise Exception() + def test_raising_repr(): + raise AssertionError(RaisingRepr()) + """) + result = testdir.runpytest() + result.stdout.fnmatch_lines(['E AssertionError: ']) + +def test_issue_1944(testdir): + testdir.makepyfile(""" + def f(): + return + + assert f() == 10 + """) + result = testdir.runpytest() + result.stdout.fnmatch_lines(["*1 error*"]) + assert "AttributeError: 'Module' object has no attribute '_obj'" not in result.stdout.str() + diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index cedd435f8..7cc58e8a8 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -1,7 +1,10 @@ +import glob import os +import py_compile import stat import sys import zipfile + import py import pytest @@ -104,20 +107,29 @@ class TestAssertionRewrite: def f(): assert False assert getmsg(f) == "assert False" + def f(): f = False assert f + assert getmsg(f) == "assert False" + def f(): assert a_global # noqa + assert getmsg(f, {"a_global" : False}) == "assert False" + def f(): assert sys == 42 + assert getmsg(f, {"sys" : sys}) == "assert sys == 42" + def f(): assert cls == 42 # noqa + class X(object): pass + assert getmsg(f, {"cls" : X}) == "assert cls == 42" def test_assert_already_has_message(self): @@ -190,78 +202,110 @@ class TestAssertionRewrite: def f(): f = g = False assert f and g + assert getmsg(f) == "assert (False)" + def f(): f = True g = False assert f and g + assert getmsg(f) == "assert (True and False)" + def f(): f = False g = True assert f and g + assert getmsg(f) == "assert (False)" + def f(): f = g = False assert f or g + assert getmsg(f) == "assert (False or False)" + def f(): f = g = False assert not f and not g + getmsg(f, must_pass=True) + def x(): return False + def f(): assert x() and x() + assert getmsg(f, {"x" : x}) == """assert (False) + where False = x()""" + def f(): assert False or x() + assert getmsg(f, {"x" : x}) == """assert (False or False) + where False = x()""" + def f(): assert 1 in {} and 2 in {} + assert getmsg(f) == "assert (1 in {})" + def f(): x = 1 y = 2 assert x in {1 : None} and y in {} + assert getmsg(f) == "assert (1 in {1: None} and 2 in {})" + def f(): f = True g = False assert f or g + getmsg(f, must_pass=True) + def f(): f = g = h = lambda: True assert f() and g() and h() + getmsg(f, must_pass=True) - def test_short_circut_evaluation(self): + def test_short_circuit_evaluation(self): def f(): assert True or explode # noqa + getmsg(f, must_pass=True) + def f(): x = 1 assert x == 1 or x == 2 + getmsg(f, must_pass=True) def test_unary_op(self): def f(): x = True assert not x + assert getmsg(f) == "assert not True" + def f(): x = 0 assert ~x + 1 + assert getmsg(f) == "assert (~0 + 1)" + def f(): x = 3 assert -x + x + assert getmsg(f) == "assert (-3 + 3)" + def f(): x = 0 assert +x + x + assert getmsg(f) == "assert (+0 + 0)" def test_binary_op(self): @@ -269,7 +313,9 @@ class TestAssertionRewrite: x = 1 y = -1 assert x + y + assert getmsg(f) == "assert (1 + -1)" + def f(): assert not 5 % 4 assert getmsg(f) == "assert not (5 % 4)" @@ -277,7 +323,9 @@ class TestAssertionRewrite: def test_boolop_percent(self): def f(): assert 3 % 2 and False + assert getmsg(f) == "assert ((3 % 2) and False)" + def f(): assert False or 4 % 2 assert getmsg(f) == "assert (False or (4 % 2))" @@ -298,113 +346,159 @@ class TestAssertionRewrite: def test_call(self): def g(a=42, *args, **kwargs): return False + ns = {"g" : g} + def f(): assert g() + assert getmsg(f, ns) == """assert False + where False = g()""" + def f(): assert g(1) + assert getmsg(f, ns) == """assert False + where False = g(1)""" + def f(): assert g(1, 2) + assert getmsg(f, ns) == """assert False + where False = g(1, 2)""" + def f(): assert g(1, g=42) + assert getmsg(f, ns) == """assert False + where False = g(1, g=42)""" + def f(): assert g(1, 3, g=23) + assert getmsg(f, ns) == """assert False + where False = g(1, 3, g=23)""" + def f(): seq = [1, 2, 3] assert g(*seq) + assert getmsg(f, ns) == """assert False + where False = g(*[1, 2, 3])""" + def f(): x = "a" assert g(**{x : 2}) + assert getmsg(f, ns) == """assert False + where False = g(**{'a': 2})""" def test_attribute(self): class X(object): g = 3 + ns = {"x" : X} + def f(): assert not x.g # noqa + assert getmsg(f, ns) == """assert not 3 + where 3 = x.g""" + def f(): x.a = False # noqa assert x.a # noqa + assert getmsg(f, ns) == """assert False + where False = x.a""" def test_comparisons(self): + def f(): a, b = range(2) assert b < a + assert getmsg(f) == """assert 1 < 0""" + def f(): a, b, c = range(3) assert a > b > c + assert getmsg(f) == """assert 0 > 1""" + def f(): a, b, c = range(3) assert a < b > c + assert getmsg(f) == """assert 1 > 2""" + def f(): a, b, c = range(3) assert a < b <= c + getmsg(f, must_pass=True) + def f(): a, b, c = range(3) assert a < b assert b < c + getmsg(f, must_pass=True) def test_len(self): + def f(): l = list(range(10)) assert len(l) == 11 + assert getmsg(f).startswith("""assert 10 == 11 + where 10 = len([""") def test_custom_reprcompare(self, monkeypatch): def my_reprcompare(op, left, right): return "42" + monkeypatch.setattr(util, "_reprcompare", my_reprcompare) + def f(): assert 42 < 3 + assert getmsg(f) == "assert 42" + def my_reprcompare(op, left, right): return "%s %s %s" % (left, op, right) + monkeypatch.setattr(util, "_reprcompare", my_reprcompare) + def f(): assert 1 < 3 < 5 <= 4 < 7 + assert getmsg(f) == "assert 5 <= 4" def test_assert_raising_nonzero_in_comparison(self): def f(): class A(object): + def __nonzero__(self): raise ValueError(42) + def __lt__(self, other): return A() + def __repr__(self): return "" + def myany(x): return False + assert myany(A() < 0) + assert " < 0" in getmsg(f) def test_formatchar(self): def f(): assert "%test" == "test" + assert getmsg(f).startswith("assert '%test' == 'test'") def test_custom_repr(self): @@ -414,8 +508,10 @@ class TestAssertionRewrite: def __repr__(self): return "\n{ \n~ \n}" + f = Foo() assert 0 == f.a + assert r"where 1 = \n{ \n~ \n}.a" in util._format_lines([getmsg(f)])[0] @@ -480,6 +576,31 @@ def test_rewritten(): monkeypatch.setenv("PYTHONDONTWRITEBYTECODE", "1") assert testdir.runpytest_subprocess().ret == 0 + def test_orphaned_pyc_file(self, testdir): + if sys.version_info < (3, 0) and hasattr(sys, 'pypy_version_info'): + pytest.skip("pypy2 doesn't run orphaned pyc files") + + testdir.makepyfile(""" + import orphan + def test_it(): + assert orphan.value == 17 + """) + testdir.makepyfile(orphan=""" + value = 17 + """) + py_compile.compile("orphan.py") + os.remove("orphan.py") + + # Python 3 puts the .pyc files in a __pycache__ directory, and will + # not import from there without source. It will import a .pyc from + # the source location though. + if not os.path.exists("orphan.pyc"): + pycs = glob.glob("__pycache__/orphan.*.pyc") + assert len(pycs) == 1 + os.rename(pycs[0], "orphan.pyc") + + assert testdir.runpytest().ret == 0 + @pytest.mark.skipif('"__pypy__" in sys.modules') def test_pyc_vs_pyo(self, testdir, monkeypatch): testdir.makepyfile(""" @@ -527,8 +648,10 @@ def test_rewritten(): def test_rewrite_warning(self, pytestconfig, monkeypatch): hook = AssertionRewritingHook(pytestconfig) warnings = [] + def mywarn(code, msg): warnings.append((code, msg)) + monkeypatch.setattr(hook.config, 'warn', mywarn) hook.mark_rewrite('_pytest') assert '_pytest' in warnings[0][1] @@ -543,6 +666,51 @@ def test_rewritten(): ''') assert testdir.runpytest_subprocess().ret == 0 + def test_remember_rewritten_modules(self, pytestconfig, testdir, monkeypatch): + """ + AssertionRewriteHook should remember rewritten modules so it + doesn't give false positives (#2005). + """ + monkeypatch.syspath_prepend(testdir.tmpdir) + testdir.makepyfile(test_remember_rewritten_modules='') + warnings = [] + hook = AssertionRewritingHook(pytestconfig) + monkeypatch.setattr(hook.config, 'warn', lambda code, msg: warnings.append(msg)) + hook.find_module('test_remember_rewritten_modules') + hook.load_module('test_remember_rewritten_modules') + hook.mark_rewrite('test_remember_rewritten_modules') + hook.mark_rewrite('test_remember_rewritten_modules') + assert warnings == [] + + def test_rewrite_warning_using_pytest_plugins(self, testdir): + testdir.makepyfile(**{ + 'conftest.py': "pytest_plugins = ['core', 'gui', 'sci']", + 'core.py': "", + 'gui.py': "pytest_plugins = ['core', 'sci']", + 'sci.py': "pytest_plugins = ['core']", + 'test_rewrite_warning_pytest_plugins.py': "def test(): pass", + }) + testdir.chdir() + result = testdir.runpytest_subprocess() + result.stdout.fnmatch_lines(['*= 1 passed in *=*']) + assert 'pytest-warning summary' not in result.stdout.str() + + def test_rewrite_warning_using_pytest_plugins_env_var(self, testdir, monkeypatch): + monkeypatch.setenv('PYTEST_PLUGINS', 'plugin') + testdir.makepyfile(**{ + 'plugin.py': "", + 'test_rewrite_warning_using_pytest_plugins_env_var.py': """ + import plugin + pytest_plugins = ['plugin'] + def test(): + pass + """, + }) + testdir.chdir() + result = testdir.runpytest_subprocess() + result.stdout.fnmatch_lines(['*= 1 passed in *=*']) + assert 'pytest-warning summary' not in result.stdout.str() + class TestAssertionRewriteHookDetails(object): def test_loader_is_package_false_for_module(self, testdir): @@ -626,10 +794,12 @@ class TestAssertionRewriteHookDetails(object): source_path = tmpdir.ensure("source.py") pycpath = tmpdir.join("pyc").strpath assert _write_pyc(state, [1], source_path.stat(), pycpath) + def open(*args): e = IOError() e.errno = 10 raise e + monkeypatch.setattr(b, "open", open) assert not _write_pyc(state, [1], source_path.stat(), pycpath) diff --git a/testing/test_capture.py b/testing/test_capture.py index cbb5fc81b..978e67b7e 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -4,6 +4,7 @@ from __future__ import with_statement import pickle import os import sys +from io import UnsupportedOperation import _pytest._code import py @@ -604,7 +605,7 @@ def test_capture_binary_output(testdir): def test_error_during_readouterr(testdir): - """Make sure we suspend capturing if errors occurr during readouterr""" + """Make sure we suspend capturing if errors occur during readouterr""" testdir.makepyfile(pytest_xyz=""" from _pytest.capture import FDCapture def bad_snap(self): @@ -658,7 +659,7 @@ def test_dontreadfrominput(): pytest.raises(IOError, f.read) pytest.raises(IOError, f.readlines) pytest.raises(IOError, iter, f) - pytest.raises(ValueError, f.fileno) + pytest.raises(UnsupportedOperation, f.fileno) f.close() # just for completeness diff --git a/testing/test_collection.py b/testing/test_collection.py index 8e44ba55d..9cf4de895 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -150,11 +150,13 @@ class TestCollectFS: class TestCollectPluginHookRelay: def test_pytest_collect_file(self, testdir): wascalled = [] + class Plugin: def pytest_collect_file(self, path, parent): if not path.basename.startswith("."): # Ignore hidden files, e.g. .testmondata. wascalled.append(path) + testdir.makefile(".abc", "xyz") pytest.main([testdir.tmpdir], plugins=[Plugin()]) assert len(wascalled) == 1 @@ -162,27 +164,19 @@ class TestCollectPluginHookRelay: def test_pytest_collect_directory(self, testdir): wascalled = [] + class Plugin: def pytest_collect_directory(self, path, parent): wascalled.append(path.basename) + testdir.mkdir("hello") testdir.mkdir("world") pytest.main(testdir.tmpdir, plugins=[Plugin()]) assert "hello" in wascalled assert "world" in wascalled + class TestPrunetraceback: - def test_collection_error(self, testdir): - p = testdir.makepyfile(""" - import not_exists - """) - result = testdir.runpytest(p) - assert "__import__" not in result.stdout.str(), "too long traceback" - result.stdout.fnmatch_lines([ - "*ERROR collecting*", - "ImportError while importing test module*", - "'No module named *not_exists*", - ]) def test_custom_repr_failure(self, testdir): p = testdir.makepyfile(""" diff --git a/testing/test_compat.py b/testing/test_compat.py new file mode 100644 index 000000000..1fdd07e29 --- /dev/null +++ b/testing/test_compat.py @@ -0,0 +1,50 @@ +import sys + +import pytest +from _pytest.compat import is_generator + + +def test_is_generator(): + def zap(): + yield + + def foo(): + pass + + assert is_generator(zap) + assert not is_generator(foo) + + +@pytest.mark.skipif(sys.version_info < (3, 4), reason='asyncio available in Python 3.4+') +def test_is_generator_asyncio(testdir): + testdir.makepyfile(""" + from _pytest.compat import is_generator + import asyncio + @asyncio.coroutine + def baz(): + yield from [1,2,3] + + def test_is_generator_asyncio(): + assert not is_generator(baz) + """) + # avoid importing asyncio into pytest's own process, which in turn imports logging (#8) + result = testdir.runpytest_subprocess() + result.stdout.fnmatch_lines(['*1 passed*']) + + +@pytest.mark.skipif(sys.version_info < (3, 5), reason='async syntax available in Python 3.5+') +def test_is_generator_async_syntax(testdir): + testdir.makepyfile(""" + from _pytest.compat import is_generator + def test_is_generator_py35(): + async def foo(): + await foo() + + async def bar(): + pass + + assert not is_generator(foo) + assert not is_generator(bar) + """) + result = testdir.runpytest() + result.stdout.fnmatch_lines(['*1 passed*']) diff --git a/testing/test_config.py b/testing/test_config.py index 83e8f4e6a..b6ccd7085 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -83,7 +83,7 @@ class TestParseIni: """) result = testdir.inline_run("--confcutdir=.") assert result.ret == 0 - + class TestConfigCmdlineParsing: def test_parsing_again_fails(self, testdir): config = testdir.parseconfig() @@ -294,6 +294,15 @@ class TestConfigAPI: assert len(l) == 2 assert l == ["456", "123"] + def test_confcutdir_check_isdir(self, testdir): + """Give an error if --confcutdir is not a valid directory (#2078)""" + with pytest.raises(pytest.UsageError): + testdir.parseconfig('--confcutdir', testdir.tmpdir.join('file').ensure(file=1)) + with pytest.raises(pytest.UsageError): + testdir.parseconfig('--confcutdir', testdir.tmpdir.join('inexistant')) + config = testdir.parseconfig('--confcutdir', testdir.tmpdir.join('dir').ensure(dir=1)) + assert config.getoption('confcutdir') == str(testdir.tmpdir.join('dir')) + class TestConfigFromdictargs: def test_basic_behavior(self): @@ -373,23 +382,31 @@ def test_options_on_small_file_do_not_blow_up(testdir): ['--traceconfig'], ['-v'], ['-v', '-v']): runfiletest(opts + [path]) + def test_preparse_ordering_with_setuptools(testdir, monkeypatch): pkg_resources = pytest.importorskip("pkg_resources") + def my_iter(name): assert name == "pytest11" + class Dist: project_name = 'spam' version = '1.0' + def _get_metadata(self, name): return ['foo.txt,sha256=abc,123'] + class EntryPoint: name = "mytestplugin" dist = Dist() + def load(self): class PseudoPlugin: x = 42 return PseudoPlugin() + return iter([EntryPoint()]) + monkeypatch.setattr(pkg_resources, 'iter_entry_points', my_iter) testdir.makeconftest(""" pytest_plugins = "mytestplugin", @@ -402,18 +419,24 @@ def test_preparse_ordering_with_setuptools(testdir, monkeypatch): def test_setuptools_importerror_issue1479(testdir, monkeypatch): pkg_resources = pytest.importorskip("pkg_resources") + def my_iter(name): assert name == "pytest11" + class Dist: project_name = 'spam' version = '1.0' + def _get_metadata(self, name): return ['foo.txt,sha256=abc,123'] + class EntryPoint: name = "mytestplugin" dist = Dist() + def load(self): raise ImportError("Don't hide me!") + return iter([EntryPoint()]) monkeypatch.setattr(pkg_resources, 'iter_entry_points', my_iter) @@ -423,19 +446,26 @@ def test_setuptools_importerror_issue1479(testdir, monkeypatch): def test_plugin_preparse_prevents_setuptools_loading(testdir, monkeypatch): pkg_resources = pytest.importorskip("pkg_resources") + def my_iter(name): assert name == "pytest11" + class Dist: project_name = 'spam' version = '1.0' + def _get_metadata(self, name): return ['foo.txt,sha256=abc,123'] + class EntryPoint: name = "mytestplugin" dist = Dist() + def load(self): assert 0, "should not arrive here" + return iter([EntryPoint()]) + monkeypatch.setattr(pkg_resources, 'iter_entry_points', my_iter) config = testdir.parseconfig("-p", "no:mytestplugin") plugin = config.pluginmanager.getplugin("mytestplugin") @@ -489,7 +519,7 @@ def test_consider_args_after_options_for_rootdir_and_inifile(testdir, args): args[i] = d2 with root.as_cwd(): result = testdir.runpytest(*args) - result.stdout.fnmatch_lines(['*rootdir: *myroot, inifile: ']) + result.stdout.fnmatch_lines(['*rootdir: *myroot, inifile:']) @pytest.mark.skipif("sys.platform == 'win32'") @@ -497,15 +527,40 @@ def test_toolongargs_issue224(testdir): result = testdir.runpytest("-m", "hello" * 500) assert result.ret == EXIT_NOTESTSCOLLECTED +def test_config_in_subdirectory_colon_command_line_issue2148(testdir): + conftest_source = ''' + def pytest_addoption(parser): + parser.addini('foo', 'foo') + ''' + + testdir.makefile('.ini', **{ + 'pytest': '[pytest]\nfoo = root', + 'subdir/pytest': '[pytest]\nfoo = subdir', + }) + + testdir.makepyfile(**{ + 'conftest': conftest_source, + 'subdir/conftest': conftest_source, + 'subdir/test_foo': ''' + def test_foo(pytestconfig): + assert pytestconfig.getini('foo') == 'subdir' + '''}) + + result = testdir.runpytest('subdir/test_foo.py::test_foo') + assert result.ret == 0 + + def test_notify_exception(testdir, capfd): config = testdir.parseconfig() excinfo = pytest.raises(ValueError, "raise ValueError(1)") config.notify_exception(excinfo) out, err = capfd.readouterr() assert "ValueError" in err + class A: def pytest_internalerror(self, excrepr): return True + config.pluginmanager.register(A()) config.notify_exception(excinfo) out, err = capfd.readouterr() @@ -515,9 +570,11 @@ def test_notify_exception(testdir, capfd): def test_load_initial_conftest_last_ordering(testdir): from _pytest.config import get_config pm = get_config().pluginmanager + class My: def pytest_load_initial_conftests(self): pass + m = My() pm.register(m) hc = pm.hook.pytest_load_initial_conftests @@ -530,6 +587,21 @@ def test_load_initial_conftest_last_ordering(testdir): assert [x.function.__module__ for x in l] == expected +def test_get_plugin_specs_as_list(): + from _pytest.config import _get_plugin_specs_as_list + with pytest.raises(pytest.UsageError): + _get_plugin_specs_as_list(set(['foo'])) + with pytest.raises(pytest.UsageError): + _get_plugin_specs_as_list(dict()) + + assert _get_plugin_specs_as_list(None) == [] + assert _get_plugin_specs_as_list('') == [] + assert _get_plugin_specs_as_list('foo') == ['foo'] + assert _get_plugin_specs_as_list('foo,bar') == ['foo', 'bar'] + assert _get_plugin_specs_as_list(['foo', 'bar']) == ['foo', 'bar'] + assert _get_plugin_specs_as_list(('foo', 'bar')) == ['foo', 'bar'] + + class TestWarning: def test_warn_config(self, testdir): testdir.makeconftest(""" @@ -698,6 +770,29 @@ class TestOverrideIniArgs: "ini3:True", "ini4:False"]) + def test_override_ini_usage_error_bad_style(self, testdir): + testdir.makeini(""" + [pytest] + xdist_strict=False + """) + result = testdir.runpytest("--override-ini", 'xdist_strict True', "-s") + result.stderr.fnmatch_lines(["*ERROR* *expects option=value*"]) + + @pytest.mark.parametrize('with_ini', [True, False]) + def test_override_ini_handled_asap(self, testdir, with_ini): + """-o should be handled as soon as possible and always override what's in ini files (#2238)""" + if with_ini: + testdir.makeini(""" + [pytest] + python_files=test_*.py + """) + testdir.makepyfile(unittest_ini_handle=""" + def test(): + pass + """) + result = testdir.runpytest("--override-ini", 'python_files=unittest_*.py') + result.stdout.fnmatch_lines(["*1 passed in*"]) + def test_with_arg_outside_cwd_without_inifile(self, tmpdir, monkeypatch): monkeypatch.chdir(str(tmpdir)) a = tmpdir.mkdir("a") diff --git a/testing/test_conftest.py b/testing/test_conftest.py index 377283eb9..c0fa74701 100644 --- a/testing/test_conftest.py +++ b/testing/test_conftest.py @@ -200,8 +200,10 @@ def test_conftest_import_order(testdir, monkeypatch): sub = testdir.mkdir("sub") ct2 = sub.join("conftest.py") ct2.write("") + def impct(p): return p + conftest = PytestPluginManager() conftest._confcutdir = testdir.tmpdir monkeypatch.setattr(conftest, '_importconftest', impct) @@ -421,3 +423,28 @@ def test_conftest_exception_handling(testdir): res = testdir.runpytest() assert res.ret == 4 assert 'raise ValueError()' in [line.strip() for line in res.errlines] + + +def test_hook_proxy(testdir): + """Session's gethookproxy() would cache conftests incorrectly (#2016). + It was decided to remove the cache altogether. + """ + testdir.makepyfile(**{ + 'root/demo-0/test_foo1.py': "def test1(): pass", + + 'root/demo-a/test_foo2.py': "def test1(): pass", + 'root/demo-a/conftest.py': """ + def pytest_ignore_collect(path, config): + return True + """, + + 'root/demo-b/test_foo3.py': "def test1(): pass", + 'root/demo-c/test_foo4.py': "def test1(): pass", + }) + result = testdir.runpytest() + result.stdout.fnmatch_lines([ + '*test_foo1.py*', + '*test_foo3.py*', + '*test_foo4.py*', + '*3 passed*', + ]) diff --git a/testing/test_doctest.py b/testing/test_doctest.py index 4ea2cc58e..faf75ef33 100644 --- a/testing/test_doctest.py +++ b/testing/test_doctest.py @@ -1,9 +1,11 @@ # encoding: utf-8 import sys import _pytest._code +from _pytest.compat import MODULE_NOT_FOUND_ERROR from _pytest.doctest import DoctestItem, DoctestModule, DoctestTextfile import pytest + class TestDoctests: def test_collect_testtextfile(self, testdir): @@ -211,8 +213,8 @@ class TestDoctests: # doctest is never executed because of error during hello.py collection result.stdout.fnmatch_lines([ "*>>> import asdals*", - "*UNEXPECTED*ImportError*", - "ImportError: No module named *asdal*", + "*UNEXPECTED*{e}*".format(e=MODULE_NOT_FOUND_ERROR), + "{e}: No module named *asdal*".format(e=MODULE_NOT_FOUND_ERROR), ]) def test_doctest_unex_importerror_with_module(self, testdir): @@ -227,7 +229,7 @@ class TestDoctests: # doctest is never executed because of error during hello.py collection result.stdout.fnmatch_lines([ "*ERROR collecting hello.py*", - "*ImportError: No module named *asdals*", + "*{e}: No module named *asdals*".format(e=MODULE_NOT_FOUND_ERROR), "*Interrupted: 1 errors during collection*", ]) diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index aec2741f1..d167f735d 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -165,6 +165,30 @@ class TestPython: fnode.assert_attr(message="test setup failure") assert "ValueError" in fnode.toxml() + def test_teardown_error(self, testdir): + testdir.makepyfile(""" + import pytest + + @pytest.fixture + def arg(): + yield + raise ValueError() + def test_function(arg): + pass + """) + result, dom = runandparse(testdir) + assert result.ret + node = dom.find_first_by_tag("testsuite") + tnode = node.find_first_by_tag("testcase") + tnode.assert_attr( + file="test_teardown_error.py", + line="6", + classname="test_teardown_error", + name="test_function") + fnode = tnode.find_first_by_tag("error") + fnode.assert_attr(message="test teardown failure") + assert "ValueError" in fnode.toxml() + def test_skip_contains_name_reason(self, testdir): testdir.makepyfile(""" import pytest @@ -225,6 +249,18 @@ class TestPython: snode = tnode.find_first_by_tag("skipped") snode.assert_attr(type="pytest.skip", message="hello25", ) + def test_mark_skip_doesnt_capture_output(self, testdir): + testdir.makepyfile(""" + import pytest + @pytest.mark.skip(reason="foo") + def test_skip(): + print("bar!") + """) + result, dom = runandparse(testdir) + assert result.ret == 0 + node_xml = dom.find_first_by_tag("testsuite").toxml() + assert "bar!" not in node_xml + def test_classname_instance(self, testdir): testdir.makepyfile(""" class TestClass: @@ -521,6 +557,25 @@ class TestPython: systemout = pnode.find_first_by_tag("system-err") assert "hello-stderr" in systemout.toxml() + def test_avoid_double_stdout(self, testdir): + testdir.makepyfile(""" + import sys + import pytest + + @pytest.fixture + def arg(request): + yield + sys.stdout.write('hello-stdout teardown') + raise ValueError() + def test_function(arg): + sys.stdout.write('hello-stdout call') + """) + result, dom = runandparse(testdir) + node = dom.find_first_by_tag("testsuite") + pnode = node.find_first_by_tag("testcase") + systemout = pnode.find_first_by_tag("system-out") + assert "hello-stdout call" in systemout.toxml() + assert "hello-stdout teardown" in systemout.toxml() def test_mangle_test_address(): from _pytest.junitxml import mangle_test_address @@ -679,6 +734,10 @@ def test_logxml_makedir(testdir): assert result.ret == 0 assert testdir.tmpdir.join("path/to/results.xml").check() +def test_logxml_check_isdir(testdir): + """Give an error if --junit-xml is a directory (#2089)""" + result = testdir.runpytest("--junit-xml=.") + result.stderr.fnmatch_lines(["*--junitxml must be a filename*"]) def test_escaped_parametrized_names_xml(testdir): testdir.makepyfile(""" diff --git a/testing/test_mark.py b/testing/test_mark.py index e0bf3c3c8..a4430b4c8 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -23,15 +23,19 @@ class TestMark: def test_pytest_mark_bare(self): mark = Mark() + def f(): pass + mark.hello(f) assert f.hello def test_pytest_mark_keywords(self): mark = Mark() + def f(): pass + mark.world(x=3, y=4)(f) assert f.world assert f.world.kwargs['x'] == 3 @@ -39,8 +43,10 @@ class TestMark: def test_apply_multiple_and_merge(self): mark = Mark() + def f(): pass + mark.world mark.world(x=3)(f) assert f.world.kwargs['x'] == 3 @@ -53,33 +59,43 @@ class TestMark: def test_pytest_mark_positional(self): mark = Mark() + def f(): pass + mark.world("hello")(f) assert f.world.args[0] == "hello" mark.world("world")(f) def test_pytest_mark_positional_func_and_keyword(self): mark = Mark() + def f(): raise Exception + m = mark.world(f, omega="hello") + def g(): pass + assert m(g) == g assert g.world.args[0] is f assert g.world.kwargs["omega"] == "hello" def test_pytest_mark_reuse(self): mark = Mark() + def f(): pass + w = mark.some w("hello", reason="123")(f) assert f.some.args[0] == "hello" assert f.some.kwargs['reason'] == "123" + def g(): pass + w("world", reason2="456")(g) assert g.some.args[0] == "world" assert 'reason' not in g.some.kwargs @@ -610,11 +626,12 @@ class TestFunctional: def test_1(parameter): assert True """) - reprec = testdir.inline_run() reprec.assertoutcome(skipped=1) + class TestKeywordSelection: + def test_select_simple(self, testdir): file_test = testdir.makepyfile(""" def test_one(): @@ -623,6 +640,7 @@ class TestKeywordSelection: def test_method_one(self): assert 42 == 43 """) + def check(keyword, name): reprec = testdir.inline_run("-s", "-k", keyword, file_test) passed, skipped, failed = reprec.listoutcomes() @@ -709,6 +727,7 @@ class TestKeywordSelection: p = testdir.makepyfile(""" def test_one(): assert 1 """) + def assert_test_is_not_selected(keyword): reprec = testdir.inline_run("-k", keyword, p) passed, skipped, failed = reprec.countoutcomes() diff --git a/testing/test_monkeypatch.py b/testing/test_monkeypatch.py index 3fcd20f32..9d02e2cc0 100644 --- a/testing/test_monkeypatch.py +++ b/testing/test_monkeypatch.py @@ -7,16 +7,12 @@ from _pytest.monkeypatch import MonkeyPatch @pytest.fixture -def mp(request): +def mp(): cwd = os.getcwd() sys_path = list(sys.path) - - def cleanup(): - sys.path[:] = sys_path - os.chdir(cwd) - - request.addfinalizer(cleanup) - return MonkeyPatch() + yield MonkeyPatch() + sys.path[:] = sys_path + os.chdir(cwd) def test_setattr(): @@ -329,5 +325,3 @@ def test_issue1338_name_resolving(): monkeypatch.delattr('requests.sessions.Session.request') finally: monkeypatch.undo() - - diff --git a/testing/test_nose.py b/testing/test_nose.py index a5162381e..f54246111 100644 --- a/testing/test_nose.py +++ b/testing/test_nose.py @@ -25,18 +25,22 @@ def test_nose_setup(testdir): def test_setup_func_with_setup_decorator(): from _pytest.nose import call_optional l = [] + class A: @pytest.fixture(autouse=True) def f(self): l.append(1) + call_optional(A(), "f") assert not l def test_setup_func_not_callable(): from _pytest.nose import call_optional + class A: f = 1 + call_optional(A(), "f") def test_nose_setup_func(testdir): diff --git a/testing/test_parseopt.py b/testing/test_parseopt.py index cc9aa23cd..e933dbb8d 100644 --- a/testing/test_parseopt.py +++ b/testing/test_parseopt.py @@ -138,7 +138,10 @@ class TestParser: def test_parse_setoption(self, parser): parser.addoption("--hello", dest="hello", action="store") parser.addoption("--world", dest="world", default=42) - class A: pass + + class A: + pass + option = A() args = parser.parse_setoption(['--hello', 'world'], option) assert option.hello == "world" @@ -248,7 +251,19 @@ class TestParser: help="show help message and configuration info") parser.parse(['-h']) help = parser.optparser.format_help() - assert '-doit, --func-args foo' in help + assert '-doit, --func-args foo' in help + + def test_multiple_metavar_help(self, parser): + """ + Help text for options with a metavar tuple should display help + in the form "--preferences=value1 value2 value3" (#2004). + """ + group = parser.getgroup("general") + group.addoption('--preferences', metavar=('value1', 'value2', 'value3'), nargs=3) + group._addoption("-h", "--help", action="store_true", dest="help") + parser.parse(['-h']) + help = parser.optparser.format_help() + assert '--preferences=value1 value2 value3' in help def test_argcomplete(testdir, monkeypatch): diff --git a/testing/test_pastebin.py b/testing/test_pastebin.py index 03570a5c7..8123424ca 100644 --- a/testing/test_pastebin.py +++ b/testing/test_pastebin.py @@ -84,8 +84,10 @@ class TestPaste: function that connects to bpaste service. """ calls = [] + def mocked(url, data): calls.append((url, data)) + class DummyFile: def read(self): # part of html of a normal response diff --git a/testing/test_pdb.py b/testing/test_pdb.py index d79d71262..52a75d916 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -1,4 +1,5 @@ import sys +import platform import _pytest._code import pytest @@ -18,8 +19,10 @@ class TestPDB: def pdblist(self, request): monkeypatch = request.getfixturevalue("monkeypatch") pdblist = [] + def mypdb(*args): pdblist.append(args) + plugin = request.config.pluginmanager.getplugin('debugging') monkeypatch.setattr(plugin, 'post_mortem', mypdb) return pdblist @@ -76,6 +79,12 @@ class TestPDB: rest = child.read().decode("utf8") assert "1 failed" in rest assert "def test_1" not in rest + self.flush(child) + + @staticmethod + def flush(child): + if platform.system() == 'Darwin': + return if child.isalive(): child.wait() @@ -95,8 +104,22 @@ class TestPDB: child.sendeof() rest = child.read().decode("utf8") assert 'debug.me' in rest - if child.isalive(): - child.wait() + self.flush(child) + + def test_pdb_unittest_skip(self, testdir): + """Test for issue #2137""" + p1 = testdir.makepyfile(""" + import unittest + @unittest.skipIf(True, 'Skipping also with pdb active') + class MyTestCase(unittest.TestCase): + def test_one(self): + assert 0 + """) + child = testdir.spawn_pytest("-rs --pdb %s" % p1) + child.expect('Skipping also with pdb active') + child.expect('1 skipped in') + child.sendeof() + self.flush(child) def test_pdb_interaction_capture(self, testdir): p1 = testdir.makepyfile(""" @@ -111,8 +134,7 @@ class TestPDB: rest = child.read().decode("utf8") assert "1 failed" in rest assert "getrekt" not in rest - if child.isalive(): - child.wait() + self.flush(child) def test_pdb_interaction_exception(self, testdir): p1 = testdir.makepyfile(""" @@ -130,8 +152,7 @@ class TestPDB: child.expect(".*function") child.sendeof() child.expect("1 failed") - if child.isalive(): - child.wait() + self.flush(child) def test_pdb_interaction_on_collection_issue181(self, testdir): p1 = testdir.makepyfile(""" @@ -143,8 +164,7 @@ class TestPDB: child.expect("(Pdb)") child.sendeof() child.expect("1 error") - if child.isalive(): - child.wait() + self.flush(child) def test_pdb_interaction_on_internal_error(self, testdir): testdir.makeconftest(""" @@ -156,8 +176,7 @@ class TestPDB: #child.expect(".*import pytest.*") child.expect("(Pdb)") child.sendeof() - if child.isalive(): - child.wait() + self.flush(child) def test_pdb_interaction_capturing_simple(self, testdir): p1 = testdir.makepyfile(""" @@ -177,8 +196,7 @@ class TestPDB: assert "1 failed" in rest assert "def test_1" in rest assert "hello17" in rest # out is captured - if child.isalive(): - child.wait() + self.flush(child) def test_pdb_set_trace_interception(self, testdir): p1 = testdir.makepyfile(""" @@ -193,8 +211,7 @@ class TestPDB: rest = child.read().decode("utf8") assert "1 failed" in rest assert "reading from stdin while output" not in rest - if child.isalive(): - child.wait() + self.flush(child) def test_pdb_and_capsys(self, testdir): p1 = testdir.makepyfile(""" @@ -209,8 +226,7 @@ class TestPDB: child.expect("hello1") child.sendeof() child.read() - if child.isalive(): - child.wait() + self.flush(child) def test_set_trace_capturing_afterwards(self, testdir): p1 = testdir.makepyfile(""" @@ -229,8 +245,7 @@ class TestPDB: child.expect("hello") child.sendeof() child.read() - if child.isalive(): - child.wait() + self.flush(child) def test_pdb_interaction_doctest(self, testdir): p1 = testdir.makepyfile(""" @@ -249,8 +264,7 @@ class TestPDB: child.sendeof() rest = child.read().decode("utf8") assert "1 failed" in rest - if child.isalive(): - child.wait() + self.flush(child) def test_pdb_interaction_capturing_twice(self, testdir): p1 = testdir.makepyfile(""" @@ -276,8 +290,7 @@ class TestPDB: assert "def test_1" in rest assert "hello17" in rest # out is captured assert "hello18" in rest # out is captured - if child.isalive(): - child.wait() + self.flush(child) def test_pdb_used_outside_test(self, testdir): p1 = testdir.makepyfile(""" @@ -288,7 +301,7 @@ class TestPDB: child = testdir.spawn("%s %s" %(sys.executable, p1)) child.expect("x = 5") child.sendeof() - child.wait() + self.flush(child) def test_pdb_used_in_generate_tests(self, testdir): p1 = testdir.makepyfile(""" @@ -302,7 +315,7 @@ class TestPDB: child = testdir.spawn_pytest(str(p1)) child.expect("x = 5") child.sendeof() - child.wait() + self.flush(child) def test_pdb_collection_failure_is_shown(self, testdir): p1 = testdir.makepyfile("""xxx """) @@ -331,8 +344,7 @@ class TestPDB: child.expect("enter_pdb_hook") child.send('c\n') child.sendeof() - if child.isalive(): - child.wait() + self.flush(child) def test_pdb_custom_cls(self, testdir): called = [] diff --git a/testing/test_pluginmanager.py b/testing/test_pluginmanager.py index 36847638d..45ad321a3 100644 --- a/testing/test_pluginmanager.py +++ b/testing/test_pluginmanager.py @@ -1,9 +1,11 @@ +# encoding: UTF-8 import pytest import py import os from _pytest.config import get_config, PytestPluginManager -from _pytest.main import EXIT_NOTESTSCOLLECTED +from _pytest.main import EXIT_NOTESTSCOLLECTED, Session + @pytest.fixture def pytestpm(): @@ -82,6 +84,7 @@ class TestPytestPluginInteractions: def test_configure(self, testdir): config = testdir.parseconfig() l = [] + class A: def pytest_configure(self, config): l.append(self) @@ -101,13 +104,16 @@ class TestPytestPluginInteractions: 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() @@ -128,6 +134,25 @@ class TestPytestPluginInteractions: finally: undo() + def test_hook_proxy(self, testdir): + """Test the gethookproxy function(#2016)""" + config = testdir.parseconfig() + session = Session(config) + testdir.makepyfile(**{ + 'tests/conftest.py': '', + 'tests/subdir/conftest.py': '', + }) + + conftest1 = testdir.tmpdir.join('tests/conftest.py') + conftest2 = testdir.tmpdir.join('tests/subdir/conftest.py') + + config.pluginmanager._importconftest(conftest1) + ihook_a = session.gethookproxy(testdir.tmpdir.join('tests')) + assert ihook_a is not None + config.pluginmanager._importconftest(conftest2) + ihook_b = session.gethookproxy(testdir.tmpdir.join('tests')) + assert ihook_a is not ihook_b + def test_warn_on_deprecated_multicall(self, pytestpm): warnings = [] @@ -179,15 +204,20 @@ def test_default_markers(testdir): ]) -def test_importplugin_issue375(testdir, pytestpm): +def test_importplugin_error_message(testdir, pytestpm): """Don't hide import errors when importing plugins and provide an easy to debug message. + + See #375 and #1998. """ testdir.syspathinsert(testdir.tmpdir) - testdir.makepyfile(qwe="import aaaa") + testdir.makepyfile(qwe=""" + # encoding: UTF-8 + raise ImportError(u'Not possible to import: ☺') + """) with pytest.raises(ImportError) as excinfo: pytestpm.import_plugin("qwe") - expected = '.*Error importing plugin "qwe": No module named \'?aaaa\'?' + expected = '.*Error importing plugin "qwe": Not possible to import: .' assert py.std.re.match(expected, str(excinfo.value)) diff --git a/testing/test_pytester.py b/testing/test_pytester.py index 65660afdf..49cf43a3e 100644 --- a/testing/test_pytester.py +++ b/testing/test_pytester.py @@ -11,6 +11,7 @@ def test_make_hook_recorder(testdir): assert not recorder.getfailures() pytest.xfail("internal reportrecorder tests need refactoring") + class rep: excinfo = None passed = False @@ -80,10 +81,13 @@ def make_holder(): "x" apimod = type(os)('api') + def pytest_xyz(arg): "x" + def pytest_xyz_noarg(): "x" + apimod.pytest_xyz = pytest_xyz apimod.pytest_xyz_noarg = pytest_xyz_noarg return apiclass, apimod @@ -120,3 +124,10 @@ def test_inline_run_clean_modules(testdir): test_mod.write("def test_foo(): assert False") result2 = testdir.inline_run(str(test_mod)) assert result2.ret == EXIT_TESTSFAILED + +def test_assert_outcomes_after_pytest_erro(testdir): + testdir.makepyfile("def test_foo(): assert True") + + result = testdir.runpytest('--unexpected-argument') + with pytest.raises(ValueError, message="Pytest terminal report not found"): + result.assert_outcomes(passed=0) diff --git a/testing/test_recwarn.py b/testing/test_recwarn.py index 87e5846c2..0f3bf6a14 100644 --- a/testing/test_recwarn.py +++ b/testing/test_recwarn.py @@ -1,4 +1,5 @@ import warnings +import re import py import pytest from _pytest.recwarn import WarningsRecorder @@ -114,7 +115,7 @@ class TestDeprecatedCall(object): with pytest.raises(pytest.fail.Exception) as ex: with pytest.deprecated_call(): self.dep(1) - assert str(ex.value) == "DID NOT WARN" + assert str(ex.value).startswith("DID NOT WARN") def test_deprecated_call_as_context_manager(self): with pytest.deprecated_call(): @@ -185,17 +186,39 @@ class TestWarns(object): with pytest.warns(RuntimeWarning): warnings.warn("runtime", RuntimeWarning) - with pytest.raises(pytest.fail.Exception): - with pytest.warns(RuntimeWarning): - warnings.warn("user", UserWarning) - - with pytest.raises(pytest.fail.Exception): - with pytest.warns(UserWarning): - warnings.warn("runtime", RuntimeWarning) - with pytest.warns(UserWarning): warnings.warn("user", UserWarning) + with pytest.raises(pytest.fail.Exception) as excinfo: + with pytest.warns(RuntimeWarning): + warnings.warn("user", UserWarning) + excinfo.match(r"DID NOT WARN. No warnings of type \(.+RuntimeWarning.+,\) was emitted. " + r"The list of emitted warnings is: \[UserWarning\('user',\)\].") + + with pytest.raises(pytest.fail.Exception) as excinfo: + with pytest.warns(UserWarning): + warnings.warn("runtime", RuntimeWarning) + excinfo.match(r"DID NOT WARN. No warnings of type \(.+UserWarning.+,\) was emitted. " + r"The list of emitted warnings is: \[RuntimeWarning\('runtime',\)\].") + + with pytest.raises(pytest.fail.Exception) as excinfo: + with pytest.warns(UserWarning): + pass + excinfo.match(r"DID NOT WARN. No warnings of type \(.+UserWarning.+,\) was emitted. " + r"The list of emitted warnings is: \[\].") + + warning_classes = (UserWarning, FutureWarning) + with pytest.raises(pytest.fail.Exception) as excinfo: + with pytest.warns(warning_classes) as warninfo: + warnings.warn("runtime", RuntimeWarning) + warnings.warn("import", ImportWarning) + + message_template = ("DID NOT WARN. No warnings of type {0} was emitted. " + "The list of emitted warnings is: {1}.") + excinfo.match(re.escape(message_template.format(warning_classes, + [each.message for each in warninfo]))) + + def test_record(self): with pytest.warns(UserWarning) as record: warnings.warn("user", UserWarning) diff --git a/testing/test_runner.py b/testing/test_runner.py index e88a548d7..727defa92 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -38,9 +38,13 @@ class TestSetupState: def test_teardown_multiple_one_fails(self, testdir): r = [] + def fin1(): r.append('fin1') + def fin2(): raise Exception('oops') + def fin3(): r.append('fin3') + item = testdir.getitem("def test_func(): pass") ss = runner.SetupState() ss.addfinalizer(fin1, item) @@ -55,7 +59,9 @@ class TestSetupState: # Ensure the first exception is the one which is re-raised. # Ideally both would be reported however. def fin1(): raise Exception('oops1') + def fin2(): raise Exception('oops2') + item = testdir.getitem("def test_func(): pass") ss = runner.SetupState() ss.addfinalizer(fin1, item) @@ -527,8 +533,10 @@ def test_exception_printing_skip(): def test_importorskip(monkeypatch): importorskip = pytest.importorskip + def f(): importorskip("asdlkj") + try: sys = importorskip("sys") # noqa assert sys == py.std.sys @@ -643,11 +651,13 @@ def test_makereport_getsource_dynamic_code(testdir, monkeypatch): """Test that exception in dynamically generated code doesn't break getting the source line.""" import inspect original_findsource = inspect.findsource + def findsource(obj, *args, **kwargs): # Can be triggered by dynamically created functions if obj.__name__ == 'foo': raise IndexError() return original_findsource(obj, *args, **kwargs) + monkeypatch.setattr(inspect, 'findsource', findsource) testdir.makepyfile(""" diff --git a/testing/test_session.py b/testing/test_session.py index a7dcb27a4..f494dbc11 100644 --- a/testing/test_session.py +++ b/testing/test_session.py @@ -197,7 +197,7 @@ class TestNewSession(SessionTests): colfail = [x for x in finished if x.failed] assert len(colfail) == 1 - def test_minus_x_overriden_by_maxfail(self, testdir): + def test_minus_x_overridden_by_maxfail(self, testdir): testdir.makepyfile(__init__="") testdir.makepyfile(test_one="xxxx", test_two="yyyy", test_third="zzz") reprec = testdir.inline_run("-x", "--maxfail=2", testdir.tmpdir) diff --git a/testing/test_skipping.py b/testing/test_skipping.py index 12b18ca33..ac4412fcb 100644 --- a/testing/test_skipping.py +++ b/testing/test_skipping.py @@ -967,5 +967,28 @@ def test_module_level_skip_error(testdir): """) result = testdir.runpytest() result.stdout.fnmatch_lines( - "*Using @pytest.skip outside of a test * is not allowed*" + "*Using pytest.skip outside of a test is not allowed*" ) + + +def test_mark_xfail_item(testdir): + # Ensure pytest.mark.xfail works with non-Python Item + testdir.makeconftest(""" + import pytest + + class MyItem(pytest.Item): + nodeid = 'foo' + def setup(self): + marker = pytest.mark.xfail(True, reason="Expected failure") + self.add_marker(marker) + def runtest(self): + assert False + + def pytest_collect_file(path, parent): + return MyItem("foo", parent) + """) + result = testdir.inline_run() + passed, skipped, failed = result.listoutcomes() + assert not failed + xfailed = [r for r in skipped if hasattr(r, 'wasxfail')] + assert xfailed diff --git a/testing/test_terminal.py b/testing/test_terminal.py index fc6f3b7b1..0f919b5ed 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -370,6 +370,31 @@ class TestFixtureReporting: "*1 failed*1 error*", ]) + def test_setup_teardown_output_and_test_failure(self, testdir): + """ Test for issue #442 """ + testdir.makepyfile(""" + def setup_function(function): + print ("setup func") + + def test_fail(): + assert 0, "failingfunc" + + def teardown_function(function): + print ("teardown func") + """) + result = testdir.runpytest() + result.stdout.fnmatch_lines([ + "*test_fail*", + "*def test_fail():", + "*failingfunc*", + "*Captured stdout setup*", + "*setup func*", + "*Captured stdout teardown*", + "*teardown func*", + + "*1 failed*", + ]) + class TestTerminalFunctional: def test_deselected(self, testdir): testpath = testdir.makepyfile(""" @@ -667,7 +692,7 @@ class TestGenericReporting: result = testdir.runpytest(*option.args) result.stdout.fnmatch_lines([ "ImportError while importing*", - "'No module named *xyz*", + "*No module named *xyz*", "*1 error*", ]) @@ -881,3 +906,12 @@ def test_summary_stats(exp_line, exp_color, stats_arg): print("Actually got: \"%s\"; with color \"%s\"" % (line, color)) assert line == exp_line assert color == exp_color + + +def test_no_trailing_whitespace_after_inifile_word(testdir): + result = testdir.runpytest('') + assert 'inifile:\n' in result.stdout.str() + + testdir.makeini('[pytest]') + result = testdir.runpytest('') + assert 'inifile: tox.ini\n' in result.stdout.str() diff --git a/testing/test_unittest.py b/testing/test_unittest.py index b6be95fa5..9625ae0f8 100644 --- a/testing/test_unittest.py +++ b/testing/test_unittest.py @@ -1,5 +1,6 @@ from _pytest.main import EXIT_NOTESTSCOLLECTED import pytest +import gc def test_simple_unittest(testdir): testpath = testdir.makepyfile(""" @@ -134,6 +135,28 @@ def test_teardown(testdir): assert passed == 2 assert passed + skipped + failed == 2 +def test_teardown_issue1649(testdir): + """ + Are TestCase objects cleaned up? Often unittest TestCase objects set + attributes that are large and expensive during setUp. + + The TestCase will not be cleaned up if the test fails, because it + would then exist in the stackframe. + """ + testpath = testdir.makepyfile(""" + import unittest + class TestCaseObjectsShouldBeCleanedUp(unittest.TestCase): + def setUp(self): + self.an_expensive_object = 1 + def test_demo(self): + pass + + """) + testdir.inline_run("-s", testpath) + gc.collect() + for obj in gc.get_objects(): + assert type(obj).__name__ != 'TestCaseObjectsShouldBeCleanedUp' + @pytest.mark.skipif("sys.version_info < (2,7)") def test_unittest_skip_issue148(testdir): testpath = testdir.makepyfile(""" diff --git a/tox.ini b/tox.ini index 3c12a8da3..a47fc5132 100644 --- a/tox.ini +++ b/tox.ini @@ -1,17 +1,28 @@ [tox] minversion=2.0 distshare={homedir}/.tox/distshare -# make sure to update enviroment list on appveyor.yml +# make sure to update environment list on appveyor.yml envlist= - linting,py26,py27,py33,py34,py35,pypy, - {py27,py35}-{pexpect,xdist,trial}, - py27-nobyte,doctesting,freeze,docs + linting + py26 + py27 + py33 + py34 + py35 + py36 + py37 + pypy + {py27,py35}-{pexpect,xdist,trial} + py27-nobyte + doctesting + freeze + docs [testenv] commands= pytest --lsof -rfsxX {posargs:testing} passenv = USER USERNAME -deps= - hypothesis +deps= + hypothesis>=3.5.2 nose mock requests @@ -33,21 +44,25 @@ deps=pytest-xdist>=1.13 commands= pytest -n3 -rfsxX --runpytest=subprocess {posargs:testing} -[testenv:genscript] -commands= pytest --genscript=pytest1 [testenv:linting] basepython = python2.7 -deps = flake8 +deps = + flake8 + # pygments required by rst-lint + pygments restructuredtext_lint -commands = flake8 pytest.py _pytest testing - rst-lint CHANGELOG.rst HOWTORELEASE.rst + check-manifest +commands = + {envpython} scripts/check-manifest.py + flake8 pytest.py _pytest testing + rst-lint CHANGELOG.rst HOWTORELEASE.rst README.rst [testenv:py27-xdist] deps=pytest-xdist>=1.13 mock nose - hypothesis + hypothesis>=3.5.2 commands= pytest -n1 -rfsxX {posargs:testing} @@ -71,8 +86,9 @@ commands= pytest -rfsxX test_pdb.py test_terminal.py test_unittest.py [testenv:py27-nobyte] -deps=pytest-xdist>=1.13 - hypothesis +deps= + pytest-xdist>=1.13 + hypothesis>=3.5.2 distribute=true setenv= PYTHONDONTWRITEBYTECODE=1 @@ -89,11 +105,9 @@ deps={[testenv:py27-trial]deps} commands= pytest -ra {posargs:testing/test_unittest.py} -[testenv:doctest] -commands=pytest --doctest-modules _pytest -deps= - [testenv:docs] +skipsdist=True +usedevelop=True basepython=python changedir=doc/en deps= @@ -105,9 +119,15 @@ commands= [testenv:doctesting] basepython = python -changedir=doc/en -deps=PyYAML -commands= pytest -rfsxX {posargs} +usedevelop=True +skipsdist=True +# ensure the given pyargs cant mean anytrhing else +changedir=doc/ +deps= + PyYAML +commands= + pytest -rfsxX en + pytest --doctest-modules --pyargs _pytest [testenv:regen] changedir=doc/en @@ -138,7 +158,7 @@ commands= [testenv:coveralls] passenv = TRAVIS TRAVIS_JOB_ID TRAVIS_BRANCH COVERALLS_REPO_TOKEN usedevelop=True -basepython=python3.4 +basepython=python3.5 changedir=. deps = {[testenv]deps} @@ -162,3 +182,4 @@ norecursedirs = .tox ja .hg cx_freeze_source [flake8] ignore =E401,E225,E261,E128,E124,E301,E302,E121,E303,W391,E501,E231,E126,E701,E265,E241,E251,E226,E101,W191,E131,E203,E122,E123,E271,E712,E222,E127,E125,E221,W292,E111,E113,E293,E262,W293,E129,E702,E201,E272,E202,E704,E731,E402 +exclude = _pytest/vendored_packages/pluggy.py