diff -Nru pytest-2.9.2/AUTHORS pytest-3.0.6/AUTHORS --- pytest-2.9.2/AUTHORS 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/AUTHORS 2017-01-20 16:40:36.000000000 +0000 @@ -3,45 +3,65 @@ Contributors include:: +Abdeali JK Abhijeet Kasurde +Ahn Ki-Wook +Alexei Kozlenok Anatoly Bubenkoff Andreas Zeidler +Andrzej Ostrowski Andy Freeland Anthon van der Neut +Antony Lee Armin Rigo Aron Curzon Aviv Palivoda +Ben Webb Benjamin Peterson +Bernard Pratz Bob Ippolito Brian Dorsey Brian Okken Brianna Laugher Bruno Oliveira +Cal Leeming Carl Friedrich Bolz Charles Cloud +Charnjit SiNGH (CCSJ) Chris Lamb +Christian Boelsen Christian Theunert Christian Tismer Christopher Gilling Daniel Grana Daniel Hahler Daniel Nuri +Daniel Wandschneider +Danielle Jenkins Dave Hunt +David Díaz-Barquero David Mohr David Vierra +Diego Russo +Dmitry Dygalo +Duncan Betts Edison Gustavo Muenz +Edoardo Batini Eduardo Schettino -Endre Galaczi Elizaveta Shashkova +Endre Galaczi Eric Hunsberger Eric Siegerman Erik M. Bray +Feng Ma Florian Bruhin Floris Bruynooghe Gabriel Reis Georgy Dyuldin Graham Horler +Greg Price Grig Gheorghiu +Grigorii Eremeev (budulianin) Guido Wesdorp Harald Armin Massa Ian Bicking @@ -49,43 +69,76 @@ Jan Balster Janne Vanhala Jason R. Coombs +Javier Domingo Cansino +Javier Romero +Jeff Widman John Towler +Jon Sonesen +Jordan Guymon Joshua Bronson Jurko Gospodnetić +Justyna Janczyszyn +Kale Kundert Katarzyna Jachim Kevin Cox Lee Kamentsky +Lev Maximov +Loic Esteve Lukas Bednar +Luke Murphy Maciek Fijalkowski Maho Marc Schlaich +Marcin Bachry Mark Abramowitz Markus Unterwaditzer Martijn Faassen +Martin K. Scherer Martin Prusse +Mathieu Clabaut Matt Bachmann +Matt Williams +Matthias Hafner +mbyt Michael Aquilina Michael Birtwell Michael Droettboom +Michael Seifert +Mike Lundy +Ned Batchelder +Neven Mundar Nicolas Delaby +Oleg Pidsadnyi +Oliver Bestwalter +Omar Kohl Pieter Mulder Piotr Banaszkiewicz Punyashloka Biswal Quentin Pradet Ralf Schmitt Raphael Pierzina +Raquel Alegre +Roberto Polli +Romain Dorgueil +Roman Bolshakov Ronny Pfannschmidt Ross Lawley +Russel Winder Ryan Wooden Samuele Pedroni +Simon Gomizelj +Stefan Farmbauer +Stefan Zimmermann +Stefano Taschini +Steffen Allner +Stephan Obermann +Tareq Alayan +Ted Xiao +Thomas Grainger Tom Viner Trevor Bekolay +Tyler Goodlet +Vasily Kuznetsov Wouter van Ackooy -David Díaz-Barquero -Eric Hunsberger -Simon Gomizelj -Russel Winder -Ben Webb -Alexei Kozlenok -Cal Leeming -Feng Ma +Xuecong Liao +Eli Boyarski diff -Nru pytest-2.9.2/CHANGELOG.rst pytest-3.0.6/CHANGELOG.rst --- pytest-2.9.2/CHANGELOG.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/CHANGELOG.rst 2017-01-22 17:44:30.000000000 +0000 @@ -1,5 +1,705 @@ -2.9.2 -===== +3.0.6 (2017-01-29) +======================= + +* 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. + + +.. _@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. + + +.. _@philpep: https://github.com/philpep +.. _@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 +.. _#1944: https://github.com/pytest-dev/pytest/issues/1944 +.. _#704: https://github.com/pytest-dev/pytest/issues/704 + + + +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. + +* Add ``buffer`` attribute to stdin stub class ``pytest.capture.DontReadFromInput`` + Thanks `@joguSD`_ for the PR. + +* Fix ``UnicodeEncodeError`` when string comparison with unicode has failed. (`#1864`_) + Thanks `@AiOO`_ for the PR. + +* ``pytest_plugins`` is now handled correctly if defined as a string (as opposed as + a sequence of strings) when modules are considered for assertion rewriting. + Due to this bug, much more modules were being rewritten than necessary + if a test suite uses ``pytest_plugins`` to load internal plugins (`#1888`_). + Thanks `@jaraco`_ for the report and `@nicoddemus`_ for the PR (`#1891`_). + +* Do not call tearDown and cleanups when running tests from + ``unittest.TestCase`` subclasses with ``--pdb`` + 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`_). + +.. _@joguSD: https://github.com/joguSD +.. _@AiOO: https://github.com/AiOO +.. _@mbyt: https://github.com/mbyt +.. _@ViviCoder: https://github.com/ViviCoder + +.. _#1857: https://github.com/pytest-dev/pytest/issues/1857 +.. _#1864: https://github.com/pytest-dev/pytest/issues/1864 +.. _#1888: https://github.com/pytest-dev/pytest/issues/1888 +.. _#1891: https://github.com/pytest-dev/pytest/pull/1891 +.. _#1890: https://github.com/pytest-dev/pytest/issues/1890 +.. _#1898: https://github.com/pytest-dev/pytest/issues/1898 + + +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. + +* Fix parametrization scope when session fixtures are used in conjunction + with normal parameters in the same call (`#1832`_). + Thanks `@The-Compiler`_ for the report, `@Kingdread`_ and `@nicoddemus`_ for the PR. + +* Fix internal error when parametrizing tests or fixtures using an empty ``ids`` argument (`#1849`_). + Thanks `@OPpuolitaival`_ for the report and `@nicoddemus`_ for the PR. + +* Fix loader error when running ``pytest`` embedded in a zipfile. + Thanks `@mbachry`_ for the PR. + + +.. _@Kingdread: https://github.com/Kingdread +.. _@mbachry: https://github.com/mbachry +.. _@OPpuolitaival: https://github.com/OPpuolitaival + +.. _#1822: https://github.com/pytest-dev/pytest/issues/1822 +.. _#1832: https://github.com/pytest-dev/pytest/issues/1832 +.. _#1849: https://github.com/pytest-dev/pytest/issues/1849 + + +3.0.0 (2016-08-18) +================== + +**Incompatible changes** + + +A number of incompatible changes were made in this release, with the intent of removing features deprecated for a long +time or change existing behaviors in order to make them less surprising/more useful. + +* Reinterpretation mode has now been removed. Only plain and rewrite + mode are available, consequently the ``--assert=reinterp`` option is + no longer available. This also means files imported from plugins or + ``conftest.py`` will not benefit from improved assertions by + default, you should use ``pytest.register_assert_rewrite()`` to + explicitly turn on assertion rewriting for those files. Thanks + `@flub`_ for the PR. + +* The following deprecated commandline options were removed: + + * ``--genscript``: no longer supported; + * ``--no-assert``: use ``--assert=plain`` instead; + * ``--nomagic``: use ``--assert=plain`` instead; + * ``--report``: use ``-r`` instead; + + Thanks to `@RedBeardCode`_ for the PR (`#1664`_). + +* ImportErrors in plugins now are a fatal error instead of issuing a + pytest warning (`#1479`_). Thanks to `@The-Compiler`_ for the PR. + +* Removed support code for Python 3 versions < 3.3 (`#1627`_). + +* Removed all ``py.test-X*`` entry points. The versioned, suffixed entry points + were never documented and a leftover from a pre-virtualenv era. These entry + points also created broken entry points in wheels, so removing them also + removes a source of confusion for users (`#1632`_). + Thanks `@obestwalter`_ for the PR. + +* ``pytest.skip()`` now raises an error when used to decorate a test function, + as opposed to its original intent (to imperatively skip a test inside a test function). Previously + this usage would cause the entire module to be skipped (`#607`_). + Thanks `@omarkohl`_ for the complete PR (`#1519`_). + +* Exit tests if a collection error occurs. A poll indicated most users will hit CTRL-C + anyway as soon as they see collection errors, so pytest might as well make that the default behavior (`#1421`_). + A ``--continue-on-collection-errors`` option has been added to restore the previous behaviour. + Thanks `@olegpidsadnyi`_ and `@omarkohl`_ for the complete PR (`#1628`_). + +* Renamed the pytest ``pdb`` module (plugin) into ``debugging`` to avoid clashes with the builtin ``pdb`` module. + +* Raise a helpful failure message when requesting a parametrized fixture at runtime, + e.g. with ``request.getfixturevalue``. Previously these parameters were simply + never defined, so a fixture decorated like ``@pytest.fixture(params=[0, 1, 2])`` + only ran once (`#460`_). + Thanks to `@nikratio`_ for the bug report, `@RedBeardCode`_ and `@tomviner`_ for the PR. + +* ``_pytest.monkeypatch.monkeypatch`` class has been renamed to ``_pytest.monkeypatch.MonkeyPatch`` + so it doesn't conflict with the ``monkeypatch`` fixture. + +* ``--exitfirst / -x`` can now be overridden by a following ``--maxfail=N`` + and is just a synonym for ``--maxfail=1``. + + +**New Features** + +* Support nose-style ``__test__`` attribute on methods of classes, + including unittest-style Classes. If set to ``False``, the test will not be + collected. + +* New ``doctest_namespace`` fixture for injecting names into the + namespace in which doctests run. + Thanks `@milliams`_ for the complete PR (`#1428`_). + +* New ``--doctest-report`` option available to change the output format of diffs + when running (failing) doctests (implements `#1749`_). + Thanks `@hartym`_ for the PR. + +* New ``name`` argument to ``pytest.fixture`` decorator which allows a custom name + for a fixture (to solve the funcarg-shadowing-fixture problem). + Thanks `@novas0x2a`_ for the complete PR (`#1444`_). + +* New ``approx()`` function for easily comparing floating-point numbers in + tests. + Thanks `@kalekundert`_ for the complete PR (`#1441`_). + +* Ability to add global properties in the final xunit output file by accessing + the internal ``junitxml`` plugin (experimental). + Thanks `@tareqalayan`_ for the complete PR `#1454`_). + +* New ``ExceptionInfo.match()`` method to match a regular expression on the + string representation of an exception (`#372`_). + Thanks `@omarkohl`_ for the complete PR (`#1502`_). + +* ``__tracebackhide__`` can now also be set to a callable which then can decide + whether to filter the traceback based on the ``ExceptionInfo`` object passed + to it. Thanks `@The-Compiler`_ for the complete PR (`#1526`_). + +* New ``pytest_make_parametrize_id(config, val)`` hook which can be used by plugins to provide + friendly strings for custom types. + Thanks `@palaviv`_ for the PR. + +* ``capsys`` and ``capfd`` now have a ``disabled()`` context-manager method, which + can be used to temporarily disable capture within a test. + Thanks `@nicoddemus`_ for the PR. + +* New cli flag ``--fixtures-per-test``: shows which fixtures are being used + for each selected test item. Features doc strings of fixtures by default. + Can also show where fixtures are defined if combined with ``-v``. + Thanks `@hackebrot`_ for the PR. + +* Introduce ``pytest`` command as recommended entry point. Note that ``py.test`` + still works and is not scheduled for removal. Closes proposal + `#1629`_. Thanks `@obestwalter`_ and `@davehunt`_ for the complete PR + (`#1633`_). + +* New cli flags: + + + ``--setup-plan``: performs normal collection and reports + the potential setup and teardown and does not execute any fixtures and tests; + + ``--setup-only``: performs normal collection, executes setup and teardown of + fixtures and reports them; + + ``--setup-show``: performs normal test execution and additionally shows + setup and teardown of fixtures; + + ``--keep-duplicates``: py.test now ignores duplicated paths given in the command + line. To retain the previous behavior where the same test could be run multiple + times by specifying it in the command-line multiple times, pass the ``--keep-duplicates`` + argument (`#1609`_); + + Thanks `@d6e`_, `@kvas-it`_, `@sallner`_, `@ioggstream`_ and `@omarkohl`_ for the PRs. + +* New CLI flag ``--override-ini``/``-o``: overrides values from the ini file. + For example: ``"-o xfail_strict=True"``'. + Thanks `@blueyed`_ and `@fengxx`_ for the PR. + +* New hooks: + + + ``pytest_fixture_setup(fixturedef, request)``: executes fixture setup; + + ``pytest_fixture_post_finalizer(fixturedef)``: called after the fixture's + finalizer and has access to the fixture's result cache. + + Thanks `@d6e`_, `@sallner`_. + +* Issue warnings for asserts whose test is a tuple literal. Such asserts will + never fail because tuples are always truthy and are usually a mistake + (see `#1562`_). Thanks `@kvas-it`_, for the PR. + +* Allow passing a custom debugger class (e.g. ``--pdbcls=IPython.core.debugger:Pdb``). + Thanks to `@anntzer`_ for the PR. + + +**Changes** + +* Plugins now benefit from assertion rewriting. Thanks + `@sober7`_, `@nicoddemus`_ and `@flub`_ for the PR. + +* Change ``report.outcome`` for ``xpassed`` tests to ``"passed"`` in non-strict + mode and ``"failed"`` in strict mode. Thanks to `@hackebrot`_ for the PR + (`#1795`_) and `@gprasad84`_ for report (`#1546`_). + +* Tests marked with ``xfail(strict=False)`` (the default) now appear in + JUnitXML reports as passing tests instead of skipped. + Thanks to `@hackebrot`_ for the PR (`#1795`_). + +* Highlight path of the file location in the error report to make it easier to copy/paste. + Thanks `@suzaku`_ for the PR (`#1778`_). + +* Fixtures marked with ``@pytest.fixture`` can now use ``yield`` statements exactly like + those marked with the ``@pytest.yield_fixture`` decorator. This change renders + ``@pytest.yield_fixture`` deprecated and makes ``@pytest.fixture`` with ``yield`` statements + the preferred way to write teardown code (`#1461`_). + Thanks `@csaftoiu`_ for bringing this to attention and `@nicoddemus`_ for the PR. + +* Explicitly passed parametrize ids do not get escaped to ascii (`#1351`_). + Thanks `@ceridwen`_ for the PR. + +* Fixtures are now sorted in the error message displayed when an unknown + fixture is declared in a test function. + Thanks `@nicoddemus`_ for the PR. + +* ``pytest_terminal_summary`` hook now receives the ``exitstatus`` + of the test session as argument. Thanks `@blueyed`_ for the PR (`#1809`_). + +* Parametrize ids can accept ``None`` as specific test id, in which case the + automatically generated id for that argument will be used. + Thanks `@palaviv`_ for the complete PR (`#1468`_). + +* The parameter to xunit-style setup/teardown methods (``setup_method``, + ``setup_module``, etc.) is now optional and may be omitted. + Thanks `@okken`_ for bringing this to attention and `@nicoddemus`_ for the PR. + +* Improved automatic id generation selection in case of duplicate ids in + parametrize. + Thanks `@palaviv`_ for the complete PR (`#1474`_). + +* Now pytest warnings summary is shown up by default. Added a new flag + ``--disable-pytest-warnings`` to explicitly disable the warnings summary (`#1668`_). + +* Make ImportError during collection more explicit by reminding + the user to check the name of the test module/package(s) (`#1426`_). + Thanks `@omarkohl`_ for the complete PR (`#1520`_). + +* Add ``build/`` and ``dist/`` to the default ``--norecursedirs`` list. Thanks + `@mikofski`_ for the report and `@tomviner`_ for the PR (`#1544`_). + +* ``pytest.raises`` in the context manager form accepts a custom + ``message`` to raise when no exception occurred. + Thanks `@palaviv`_ for the complete PR (`#1616`_). + +* ``conftest.py`` files now benefit from assertion rewriting; previously it + was only available for test modules. Thanks `@flub`_, `@sober7`_ and + `@nicoddemus`_ for the PR (`#1619`_). + +* Text documents without any doctests no longer appear as "skipped". + Thanks `@graingert`_ for reporting and providing a full PR (`#1580`_). + +* Ensure that a module within a namespace package can be found when it + is specified on the command line together with the ``--pyargs`` + option. Thanks to `@taschini`_ for the PR (`#1597`_). + +* Always include full assertion explanation during assertion rewriting. The previous behaviour was hiding + sub-expressions that happened to be ``False``, assuming this was redundant information. + Thanks `@bagerard`_ for reporting (`#1503`_). Thanks to `@davehunt`_ and + `@tomviner`_ for the PR. + +* ``OptionGroup.addoption()`` now checks if option names were already + added before, to make it easier to track down issues like `#1618`_. + Before, you only got exceptions later from ``argparse`` library, + giving no clue about the actual reason for double-added options. + +* ``yield``-based tests are considered deprecated and will be removed in pytest-4.0. + Thanks `@nicoddemus`_ for the PR. + +* ``[pytest]`` sections in ``setup.cfg`` files should now be named ``[tool:pytest]`` + to avoid conflicts with other distutils commands (see `#567`_). ``[pytest]`` sections in + ``pytest.ini`` or ``tox.ini`` files are supported and unchanged. + Thanks `@nicoddemus`_ for the PR. + +* Using ``pytest_funcarg__`` prefix to declare fixtures is considered deprecated and will be + removed in pytest-4.0 (`#1684`_). + Thanks `@nicoddemus`_ for the PR. + +* Passing a command-line string to ``pytest.main()`` is considered deprecated and scheduled + for removal in pytest-4.0. It is recommended to pass a list of arguments instead (`#1723`_). + +* Rename ``getfuncargvalue`` to ``getfixturevalue``. ``getfuncargvalue`` is + still present but is now considered deprecated. Thanks to `@RedBeardCode`_ and `@tomviner`_ + for the PR (`#1626`_). + +* ``optparse`` type usage now triggers DeprecationWarnings (`#1740`_). + + +* ``optparse`` backward compatibility supports float/complex types (`#457`_). + +* 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 + `@blueyed`_, `@davehunt`_ and `@matthiasha`_ for the PR. + +* Always include full assertion explanation. The previous behaviour was hiding + sub-expressions that happened to be False, assuming this was redundant information. + Thanks `@bagerard`_ for reporting (`#1503`_). Thanks to `@davehunt`_ and + `@tomviner`_ for PR. + +* Better message in case of not using parametrized variable (see `#1539`_). + Thanks to `@tramwaj29`_ for the PR. + +* Updated docstrings with a more uniform style. + +* Add stderr write for ``pytest.exit(msg)`` during startup. Previously the message was never shown. + Thanks `@BeyondEvil`_ for reporting `#1210`_. Thanks to `@JonathonSonesen`_ and + `@tomviner`_ for the PR. + +* No longer display the incorrect test deselection reason (`#1372`_). + Thanks `@ronnypfannschmidt`_ for the PR. + +* The ``--resultlog`` command line option has been deprecated: it is little used + and there are more modern and better alternatives (see `#830`_). + Thanks `@nicoddemus`_ for the PR. + +* Improve error message with fixture lookup errors: add an 'E' to the first + line and '>' to the rest. Fixes `#717`_. Thanks `@blueyed`_ for reporting and + a PR, `@eolo999`_ for the initial PR and `@tomviner`_ for his guidance during + EuroPython2016 sprint. + + +**Bug Fixes** + +* Parametrize now correctly handles duplicated test ids. + +* Fix internal error issue when the ``method`` argument is missing for + ``teardown_method()`` (`#1605`_). + +* Fix exception visualization in case the current working directory (CWD) gets + deleted during testing (`#1235`_). Thanks `@bukzor`_ for reporting. PR by + `@marscher`_. + +* Improve test output for logical expression with brackets (`#925`_). + Thanks `@DRMacIver`_ for reporting and `@RedBeardCode`_ for the PR. + +* Create correct diff for strings ending with newlines (`#1553`_). + Thanks `@Vogtinator`_ for reporting and `@RedBeardCode`_ and + `@tomviner`_ for the PR. + +* ``ConftestImportFailure`` now shows the traceback making it easier to + identify bugs in ``conftest.py`` files (`#1516`_). Thanks `@txomon`_ for + the PR. + +* Text documents without any doctests no longer appear as "skipped". + Thanks `@graingert`_ for reporting and providing a full PR (`#1580`_). + +* Fixed collection of classes with custom ``__new__`` method. + Fixes `#1579`_. Thanks to `@Stranger6667`_ for the PR. + +* Fixed scope overriding inside metafunc.parametrize (`#634`_). + Thanks to `@Stranger6667`_ for the PR. + +* Fixed the total tests tally in junit xml output (`#1798`_). + Thanks to `@cryporchild`_ for the PR. + +* Fixed off-by-one error with lines from ``request.node.warn``. + Thanks to `@blueyed`_ for the PR. + + +.. _#1210: https://github.com/pytest-dev/pytest/issues/1210 +.. _#1235: https://github.com/pytest-dev/pytest/issues/1235 +.. _#1351: https://github.com/pytest-dev/pytest/issues/1351 +.. _#1372: https://github.com/pytest-dev/pytest/issues/1372 +.. _#1421: https://github.com/pytest-dev/pytest/issues/1421 +.. _#1426: https://github.com/pytest-dev/pytest/issues/1426 +.. _#1428: https://github.com/pytest-dev/pytest/pull/1428 +.. _#1435: https://github.com/pytest-dev/pytest/issues/1435 +.. _#1441: https://github.com/pytest-dev/pytest/pull/1441 +.. _#1444: https://github.com/pytest-dev/pytest/pull/1444 +.. _#1454: https://github.com/pytest-dev/pytest/pull/1454 +.. _#1461: https://github.com/pytest-dev/pytest/pull/1461 +.. _#1468: https://github.com/pytest-dev/pytest/pull/1468 +.. _#1471: https://github.com/pytest-dev/pytest/issues/1471 +.. _#1474: https://github.com/pytest-dev/pytest/pull/1474 +.. _#1479: https://github.com/pytest-dev/pytest/issues/1479 +.. _#1502: https://github.com/pytest-dev/pytest/pull/1502 +.. _#1503: https://github.com/pytest-dev/pytest/issues/1503 +.. _#1516: https://github.com/pytest-dev/pytest/pull/1516 +.. _#1519: https://github.com/pytest-dev/pytest/pull/1519 +.. _#1520: https://github.com/pytest-dev/pytest/pull/1520 +.. _#1526: https://github.com/pytest-dev/pytest/pull/1526 +.. _#1539: https://github.com/pytest-dev/pytest/issues/1539 +.. _#1544: https://github.com/pytest-dev/pytest/issues/1544 +.. _#1546: https://github.com/pytest-dev/pytest/issues/1546 +.. _#1553: https://github.com/pytest-dev/pytest/issues/1553 +.. _#1562: https://github.com/pytest-dev/pytest/issues/1562 +.. _#1579: https://github.com/pytest-dev/pytest/issues/1579 +.. _#1580: https://github.com/pytest-dev/pytest/pull/1580 +.. _#1594: https://github.com/pytest-dev/pytest/issues/1594 +.. _#1597: https://github.com/pytest-dev/pytest/pull/1597 +.. _#1605: https://github.com/pytest-dev/pytest/issues/1605 +.. _#1616: https://github.com/pytest-dev/pytest/pull/1616 +.. _#1618: https://github.com/pytest-dev/pytest/issues/1618 +.. _#1619: https://github.com/pytest-dev/pytest/issues/1619 +.. _#1626: https://github.com/pytest-dev/pytest/pull/1626 +.. _#1627: https://github.com/pytest-dev/pytest/pull/1627 +.. _#1628: https://github.com/pytest-dev/pytest/pull/1628 +.. _#1629: https://github.com/pytest-dev/pytest/issues/1629 +.. _#1632: https://github.com/pytest-dev/pytest/issues/1632 +.. _#1633: https://github.com/pytest-dev/pytest/pull/1633 +.. _#1664: https://github.com/pytest-dev/pytest/pull/1664 +.. _#1668: https://github.com/pytest-dev/pytest/issues/1668 +.. _#1684: https://github.com/pytest-dev/pytest/pull/1684 +.. _#1723: https://github.com/pytest-dev/pytest/pull/1723 +.. _#1740: https://github.com/pytest-dev/pytest/issues/1740 +.. _#1749: https://github.com/pytest-dev/pytest/issues/1749 +.. _#1778: https://github.com/pytest-dev/pytest/pull/1778 +.. _#1795: https://github.com/pytest-dev/pytest/pull/1795 +.. _#1798: https://github.com/pytest-dev/pytest/pull/1798 +.. _#1809: https://github.com/pytest-dev/pytest/pull/1809 +.. _#372: https://github.com/pytest-dev/pytest/issues/372 +.. _#457: https://github.com/pytest-dev/pytest/issues/457 +.. _#460: https://github.com/pytest-dev/pytest/pull/460 +.. _#567: https://github.com/pytest-dev/pytest/pull/567 +.. _#607: https://github.com/pytest-dev/pytest/issues/607 +.. _#634: https://github.com/pytest-dev/pytest/issues/634 +.. _#717: https://github.com/pytest-dev/pytest/issues/717 +.. _#830: https://github.com/pytest-dev/pytest/issues/830 +.. _#925: https://github.com/pytest-dev/pytest/issues/925 + + +.. _@anntzer: https://github.com/anntzer +.. _@bagerard: https://github.com/bagerard +.. _@BeyondEvil: https://github.com/BeyondEvil +.. _@blueyed: https://github.com/blueyed +.. _@ceridwen: https://github.com/ceridwen +.. _@cryporchild: https://github.com/cryporchild +.. _@csaftoiu: https://github.com/csaftoiu +.. _@d6e: https://github.com/d6e +.. _@davehunt: https://github.com/davehunt +.. _@DRMacIver: https://github.com/DRMacIver +.. _@eolo999: https://github.com/eolo999 +.. _@fengxx: https://github.com/fengxx +.. _@flub: https://github.com/flub +.. _@gprasad84: https://github.com/gprasad84 +.. _@graingert: https://github.com/graingert +.. _@hartym: https://github.com/hartym +.. _@JonathonSonesen: https://github.com/JonathonSonesen +.. _@kalekundert: https://github.com/kalekundert +.. _@kvas-it: https://github.com/kvas-it +.. _@marscher: https://github.com/marscher +.. _@mikofski: https://github.com/mikofski +.. _@milliams: https://github.com/milliams +.. _@nikratio: https://github.com/nikratio +.. _@novas0x2a: https://github.com/novas0x2a +.. _@obestwalter: https://github.com/obestwalter +.. _@okken: https://github.com/okken +.. _@olegpidsadnyi: https://github.com/olegpidsadnyi +.. _@omarkohl: https://github.com/omarkohl +.. _@palaviv: https://github.com/palaviv +.. _@RedBeardCode: https://github.com/RedBeardCode +.. _@sallner: https://github.com/sallner +.. _@sober7: https://github.com/sober7 +.. _@Stranger6667: https://github.com/Stranger6667 +.. _@suzaku: https://github.com/suzaku +.. _@tareqalayan: https://github.com/tareqalayan +.. _@taschini: https://github.com/taschini +.. _@tramwaj29: https://github.com/tramwaj29 +.. _@txomon: https://github.com/txomon +.. _@Vogtinator: https://github.com/Vogtinator +.. _@matthiasha: https://github.com/matthiasha + + +2.9.2 (2016-05-31) +================== **Bug Fixes** @@ -10,7 +710,7 @@ 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 @@ -30,15 +730,15 @@ .. _#510: https://github.com/pytest-dev/pytest/issues/510 .. _#1506: https://github.com/pytest-dev/pytest/pull/1506 -.. _#1496: https://github.com/pytest-dev/pytest/issue/1496 -.. _#1524: https://github.com/pytest-dev/pytest/issue/1524 +.. _#1496: https://github.com/pytest-dev/pytest/issues/1496 +.. _#1524: https://github.com/pytest-dev/pytest/pull/1524 .. _@prusse-martin: https://github.com/prusse-martin .. _@astraw38: https://github.com/astraw38 -2.9.1 -===== +2.9.1 (2016-03-17) +================== **Bug Fixes** @@ -62,17 +762,19 @@ * Fix (`#649`_): parametrized test nodes cannot be specified to run on the command line. +* Fix (`#138`_): better reporting for python 3.3+ chained exceptions .. _#1437: https://github.com/pytest-dev/pytest/issues/1437 .. _#469: https://github.com/pytest-dev/pytest/issues/469 .. _#1431: https://github.com/pytest-dev/pytest/pull/1431 .. _#649: https://github.com/pytest-dev/pytest/issues/649 +.. _#138: https://github.com/pytest-dev/pytest/issues/138 .. _@asottile: https://github.com/asottile -2.9.0 -===== +2.9.0 (2016-02-29) +================== **New Features** @@ -156,7 +858,7 @@ Thanks `@biern`_ for the PR. * Fix `traceback style docs`_ to describe all of the available options - (auto/long/short/line/native/no), with `auto` being the default since v2.6. + (auto/long/short/line/native/no), with ``auto`` being the default since v2.6. Thanks `@hackebrot`_ for the PR. * Fix (`#1422`_): junit record_xml_property doesn't allow multiple records @@ -164,6 +866,7 @@ .. _`traceback style docs`: https://pytest.org/latest/usage.html#modifying-python-traceback-printing +.. _#1609: https://github.com/pytest-dev/pytest/issues/1609 .. _#1422: https://github.com/pytest-dev/pytest/issues/1422 .. _#1379: https://github.com/pytest-dev/pytest/issues/1379 .. _#1366: https://github.com/pytest-dev/pytest/issues/1366 @@ -188,16 +891,16 @@ .. _@RonnyPfannschmidt: https://github.com/RonnyPfannschmidt .. _@rabbbit: https://github.com/rabbbit .. _@hackebrot: https://github.com/hackebrot -.. _@omarkohl: https://github.com/omarkohl .. _@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 @@ -228,8 +931,8 @@ 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. @@ -242,8 +945,8 @@ 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 @@ -266,8 +969,8 @@ - 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. @@ -294,8 +997,8 @@ 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. @@ -314,8 +1017,8 @@ 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. @@ -339,7 +1042,7 @@ - (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. @@ -361,8 +1064,8 @@ - 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 @@ -473,7 +1176,7 @@ - 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 @@ -551,8 +1254,8 @@ 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. @@ -594,8 +1297,8 @@ 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 @@ -623,15 +1326,15 @@ 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. @@ -656,8 +1359,8 @@ - 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. @@ -726,8 +1429,8 @@ ``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. @@ -755,8 +1458,8 @@ - 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. @@ -773,8 +1476,8 @@ dep). Thanks Charles Cloud for analysing the issue. - fix conftest related fixture visibility issue: when running with a - CWD outside a test package pytest would get fixture discovery wrong. - Thanks to Wolfgang Schnerring for figuring out a reproducable example. + CWD outside of a test package pytest would get fixture discovery wrong. + 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. @@ -782,8 +1485,8 @@ - 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. @@ -811,8 +1514,8 @@ 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. @@ -948,8 +1651,8 @@ 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 @@ -973,11 +1676,11 @@ - 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. @@ -997,8 +1700,8 @@ -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 @@ -1030,7 +1733,7 @@ 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 @@ -1133,8 +1836,8 @@ - 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) @@ -1164,8 +1867,8 @@ - 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. @@ -1244,7 +1947,7 @@ - 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. @@ -1349,8 +2052,8 @@ ".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. @@ -1363,11 +2066,11 @@ (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 @@ -1403,7 +2106,7 @@ - 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 @@ -1414,8 +2117,8 @@ - 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 @@ -1434,8 +2137,8 @@ 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 @@ -1452,7 +2155,7 @@ - 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 @@ -1466,8 +2169,8 @@ - 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 @@ -1499,8 +2202,8 @@ - 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 @@ -1512,8 +2215,8 @@ - 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 @@ -1548,7 +2251,7 @@ - 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 @@ -1589,10 +2292,10 @@ - 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 @@ -1600,7 +2303,7 @@ - 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 @@ -1608,13 +2311,13 @@ - 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 @@ -1633,8 +2336,8 @@ - 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 @@ -1650,8 +2353,8 @@ - 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. @@ -1685,8 +2388,8 @@ - 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 '') @@ -1694,8 +2397,8 @@ - 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 @@ -1717,8 +2420,8 @@ - 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 @@ -1737,8 +2440,8 @@ - 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 @@ -1752,13 +2455,13 @@ - 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 @@ -1800,17 +2503,17 @@ 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 @@ -1858,12 +2561,12 @@ 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. @@ -1903,8 +2606,8 @@ - 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 @@ -1912,8 +2615,8 @@ - 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) @@ -1927,8 +2630,8 @@ (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** @@ -2000,8 +2703,8 @@ - fix homedir detection on Windows - ship distribute_setup.py version 0.6.13 -1.3.1 -===== +1.3.1 (2010-05-25) +================== **New features** @@ -2070,8 +2773,8 @@ (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 @@ -2102,7 +2805,7 @@ - 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 @@ -2135,8 +2838,8 @@ - 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":: @@ -2174,8 +2877,8 @@ - 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. @@ -2258,8 +2961,8 @@ - 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 @@ -2327,7 +3030,7 @@ * 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, @@ -2362,16 +3065,16 @@ * 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 @@ -2404,14 +3107,14 @@ * 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 @@ -2444,8 +3147,8 @@ * 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 @@ -2498,8 +3201,8 @@ * 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 @@ -2593,10 +3296,10 @@ * 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 -Nru pytest-2.9.2/CONTRIBUTING.rst pytest-3.0.6/CONTRIBUTING.rst --- pytest-2.9.2/CONTRIBUTING.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/CONTRIBUTING.rst 2017-01-19 09:44:52.000000000 +0000 @@ -48,7 +48,7 @@ Fix bugs -------- -Look through the GitHub issues for bugs. Here is sample filter you can use: +Look through the GitHub issues for bugs. Here is a filter you can use: https://github.com/pytest-dev/pytest/labels/bug :ref:`Talk ` to developers to find out how you can fix specific bugs. @@ -60,8 +60,7 @@ Implement features ------------------ -Look through the GitHub issues for enhancements. Here is sample filter you -can use: +Look through the GitHub issues for enhancements. Here is a filter you can use: https://github.com/pytest-dev/pytest/labels/enhancement :ref:`Talk ` to developers to find out how you can implement specific @@ -70,17 +69,26 @@ Write documentation ------------------- -pytest could always use more documentation. What exactly is needed? +Pytest could always use more documentation. What exactly is needed? * More complementary documentation. Have you perhaps found something unclear? * Documentation translations. We currently have only English. * Docstrings. There can never be too many of them. * Blog posts, articles and such -- they're all very appreciated. -You can also edit documentation files directly in the Github web interface -without needing to make a fork and local copy. This can be convenient for -small fixes. +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: @@ -95,13 +103,14 @@ - `pytest-dev on Bitbucket `_ All pytest-dev Contributors team members have write access to all contained -repositories. pytest core and plugins are generally developed +repositories. Pytest core and plugins are generally developed using `pull requests`_ to respective repositories. The objectives of the ``pytest-dev`` organisation are: * Having a central location for popular pytest plugins -* Sharing some of the maintenance responsibility (in case a maintainer no longer whishes to maintain a plugin) +* Sharing some of the maintenance responsibility (in case a maintainer no + longer wishes to maintain a plugin) You can submit your plugin by subscribing to the `pytest-dev mail list `_ and writing a @@ -121,33 +130,26 @@ - an issue tracker for bug reports and enhancement requests. +- a `changelog `_ + If no contributor strongly objects and two agree, the repository can then be transferred to the ``pytest-dev`` organisation. Here's a rundown of how a repository transfer usually proceeds (using a repository named ``joedoe/pytest-xyz`` as example): -* One of the ``pytest-dev`` administrators creates: - - - ``pytest-xyz-admin`` team, with full administration rights to - ``pytest-dev/pytest-xyz``. - - ``pytest-xyz-developers`` team, with write access to - ``pytest-dev/pytest-xyz``. - -* ``joedoe`` is invited to the ``pytest-xyz-admin`` team; - -* After accepting the invitation, ``joedoe`` transfers the repository from its - original location to ``pytest-dev/pytest-xyz`` (A nice feature is that GitHub handles URL redirection from - the old to the new location automatically). - -* ``joedoe`` is free to add any other collaborators to the - ``pytest-xyz-admin`` or ``pytest-xyz-developers`` team as desired. +* ``joedoe`` transfers repository ownership to ``pytest-dev`` administrator ``calvin``. +* ``calvin`` creates ``pytest-xyz-admin`` and ``pytest-xyz-developers`` teams, inviting ``joedoe`` to both as **maintainer**. +* ``calvin`` transfers repository to ``pytest-dev`` and configures team access: + + - ``pytest-xyz-admin`` **admin** access; + - ``pytest-xyz-developers`` **write** access; The ``pytest-dev/Contributors`` team has write access to all projects, and every project administrator is in it. We recommend that each plugin has at least three people who have the right to release to PyPI. -Repository owners can be assured that no ``pytest-dev`` administrator will ever make +Repository owners can rest assured that no ``pytest-dev`` administrator will ever make releases of your repository or take ownership in any way, except in rare cases where someone becomes unresponsive after months of contact attempts. As stated, the objective is to share maintenance and avoid "plugin-abandon". @@ -159,15 +161,11 @@ Preparing Pull Requests on GitHub --------------------------------- -There's an excellent tutorial on how Pull Requests work in the -`GitHub Help Center `_ - - .. note:: What is a "pull request"? It informs project's core developers about the changes you want to review and merge. Pull requests are stored on `GitHub servers `_. - Once you send pull request, we can discuss it's potential modifications and + Once you send a pull request, we can discuss its potential modifications and even add more commits to it later on. There's an excellent tutorial on how Pull Requests work in the @@ -211,35 +209,32 @@ You need to have Python 2.7 and 3.5 available in your system. Now running tests is as simple as issuing this command:: - $ python 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. You can now make the changes you want and run the tests again as necessary. - To run tests on py27 and pass options to pytest (e.g. enter pdb on failure) - to pytest you can do:: + To run tests on Python 2.7 and pass options to pytest (e.g. enter pdb on + failure) to pytest you can do:: - $ python runtox.py -e py27 -- --pdb + $ tox -e py27 -- --pdb - or to only run tests in a particular test module on py35:: + Or to only run tests in a particular test module on Python 3.5:: - $ python 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):: $ git commit -a -m "" $ git push -u - Make sure you add a CHANGELOG message, and add yourself to AUTHORS. If you - are unsure about either of these steps, submit your pull request and we'll - help you fix it up. + Make sure you add a message to ``CHANGELOG.rst`` and add yourself to + ``AUTHORS``. If you are unsure about either of these steps, submit your + pull request and we'll help you fix it up. #. Finally, submit a pull request through the GitHub website using this data:: @@ -248,6 +243,6 @@ base-fork: pytest-dev/pytest base: master # if it's a bugfix - base: feature # if it's a feature + base: features # if it's a feature diff -Nru pytest-2.9.2/debian/changelog pytest-3.0.6/debian/changelog --- pytest-2.9.2/debian/changelog 2016-07-18 18:01:14.000000000 +0000 +++ pytest-3.0.6/debian/changelog 2017-01-26 17:05:43.000000000 +0000 @@ -1,3 +1,87 @@ +pytest (3.0.6-1) unstable; urgency=medium + + * New upstream release. + * debian/control: Fix B-C on python3-twisted. + + -- Sebastian Ramacher Thu, 26 Jan 2017 18:05:43 +0100 + +pytest (3.0.5-2) unstable; urgency=medium + + * debian/*: Install pytest entrypoints since logilab-common removed them. + Also make the old py.test entrypoints symlinks to pytest. + * debian/{control,compat}: Bump debhelper compat to 10. + * debian/control: Use sphinxdoc:Built-Using for -doc package. + + -- Sebastian Ramacher Wed, 14 Dec 2016 20:43:52 +0100 + +pytest (3.0.5-1) unstable; urgency=medium + + * New upstream release. + + -- Sebastian Ramacher Fri, 09 Dec 2016 17:50:42 +0100 + +pytest (3.0.4-1) unstable; urgency=medium + + * New upstream release. + * debian/control: Move python{,3}-twisted-core to Build-Conflicts until + upstream issues #1989 is fixed. This is a temporary workaround for + #844919. + + -- Sebastian Ramacher Sat, 19 Nov 2016 10:59:59 +0100 + +pytest (3.0.3-1) unstable; urgency=medium + + * New upstream release. + + -- Sebastian Ramacher Mon, 10 Oct 2016 20:07:18 +0200 + +pytest (3.0.2-1) unstable; urgency=medium + + * New upstream release. + + -- Sebastian Ramacher Sat, 03 Sep 2016 11:11:27 +0200 + +pytest (3.0.1-2) unstable; urgency=medium + + [ Julian Taylor ] + * Add pypy package (Closes: #797977) + + [ Sebastian Ramacher ] + * Upload to unstable. + * debian/copyright: Fix copyright years and copyright information for + doc/en/_themes. + * debian/rules: Generate manpage for py.test-pypy. + + -- Sebastian Ramacher Tue, 30 Aug 2016 23:07:11 +0200 + +pytest (3.0.1-1) experimental; urgency=medium + + * New upstream release. + - Fixes regression when using importorskip at module level. (Closes: + #835242) + - Fixes regression with parametrizing tests. (Closes: #835248) + + -- Sebastian Ramacher Thu, 25 Aug 2016 09:58:18 +0200 + +pytest (3.0.0-1) experimental; urgency=medium + + * New upstream release. + * debian/patches/drop-entrypoints-logic.patch: Removed, included upstream. + * debian/python{,3}.*: Remove versioned entry points as they are no longer + provided upstream. + * debian/control: + - Add python{,3}-hypothesis to B-D. + - Annotate B-Ds with where possible. + - Update debhelper and dpkg-dev B-D for build profiles support. + - Update Description. + - Bump X-Python3-Version. + * debian/rules: + - Update clean target. + - Update ignored tests. + * debian/NEWS: Document entrypoint changes. + + -- Sebastian Ramacher Sun, 21 Aug 2016 20:05:49 +0200 + pytest (2.9.2-3) unstable; urgency=medium * No change rebuild because diff -Nru pytest-2.9.2/debian/clean pytest-3.0.6/debian/clean --- pytest-2.9.2/debian/clean 2016-07-14 21:02:55.000000000 +0000 +++ pytest-3.0.6/debian/clean 2017-01-26 08:09:34.000000000 +0000 @@ -1,2 +1,2 @@ -debian/py.test.1 -debian/py.test-3.1 +debian/*.1 +pytest.egg-info/* diff -Nru pytest-2.9.2/debian/compat pytest-3.0.6/debian/compat --- pytest-2.9.2/debian/compat 2016-07-14 21:02:55.000000000 +0000 +++ pytest-3.0.6/debian/compat 2017-01-26 08:09:34.000000000 +0000 @@ -1 +1 @@ -9 +10 diff -Nru pytest-2.9.2/debian/control pytest-3.0.6/debian/control --- pytest-2.9.2/debian/control 2016-07-14 21:02:55.000000000 +0000 +++ pytest-3.0.6/debian/control 2017-01-26 17:05:15.000000000 +0000 @@ -2,27 +2,37 @@ Section: python Priority: optional Maintainer: Debian Python Modules Team -Uploaders: Simon Chopin , - Sebastian Ramacher -Build-Depends: debhelper (>= 9), - dh-python, - python-all (>= 2.6.6-3~), - python-doc, - python-mock (>= 1.0.1), - python-nose, - python-pexpect, - python-py (>= 1.4.29), - python-setuptools, - python-sphinx (>= 1.0.7+dfsg), - python-twisted-core, - python3-all (>= 3.1.2-6~), - python3-mock (>= 1.0.1), - python3-nose, - python3-py (>= 1.4.29), - python3-setuptools, -Build-Conflicts: python-pytest-xdist (<< 1.5) +Uploaders: + Simon Chopin , + Sebastian Ramacher +Build-Depends: + debhelper (>= 10), + dh-python, + dpkg-dev (>= 1.17.14), + pypy, + pypy-hypothesis , + pypy-py (>= 1.4.29), + pypy-setuptools, + python-all (>= 2.6.6-3~), + python-doc, + python-hypothesis , + python-mock (>= 1.0.1) , + python-nose , + python-pexpect , + python-py (>= 1.4.29), + python-setuptools, + python-sphinx (>= 1.0.7+dfsg), + python3-all (>= 3.1.2-6~), + python3-hypothesis , + python3-mock (>= 1.0.1) , + python3-nose , + python3-py (>= 1.4.29), + python3-setuptools +Build-Conflicts: + python-twisted-core, + python3-twisted X-Python-Version: >= 2.6 -X-Python3-Version: >= 3.2 +X-Python3-Version: >= 3.3 Standards-Version: 3.9.8 Homepage: http://pytest.org/ Vcs-Git: https://anonscm.debian.org/git/python-modules/packages/pytest.git @@ -30,34 +40,61 @@ Package: python-pytest Architecture: all -Depends: python-pkg-resources, - python-py (>= 1.4.29), - ${misc:Depends}, - ${python:Depends} -Suggests: python-mock (>= 1.0.1) +Depends: + python-pkg-resources, + python-py (>= 1.4.29), + ${misc:Depends}, + ${python:Depends} +Suggests: + python-mock (>= 1.0.1) +Breaks: + python-logilab-common (<< 1.3.0-1) +Replaces: + python-logilab-common (<< 1.3.0-1) Description: Simple, powerful testing in Python This testing tool has for objective to allow the developers to limit the boilerplate code around the tests, promoting the use of built-in mechanisms such as the `assert` keyword. + . + This package provides the Python 2 modules and the py.test script. Package: python3-pytest Architecture: all -Depends: python3-pkg-resources, - python3-py (>= 1.4.29), - ${misc:Depends}, - ${python3:Depends} +Depends: + python3-pkg-resources, + python3-py (>= 1.4.29), + ${misc:Depends}, + ${python3:Depends} Description: Simple, powerful testing in Python3 This testing tool has for objective to allow the developers to limit the boilerplate code around the tests, promoting the use of built-in mechanisms such as the `assert` keyword. . - This package provides the Python 3 module and the py3.test script. + This package provides the Python 3 module and the py.test-3 script. + +Package: pypy-pytest +Architecture: all +Depends: + pypy-pkg-resources, + pypy-py (>= 1.4.29), + ${misc:Depends}, + ${pypy:Depends} +Description: Simple, powerful testing in PyPy + This testing tool has for objective to allow the developers to limit the + boilerplate code around the tests, promoting the use of built-in + mechanisms such as the `assert` keyword. + . + This package provides the PyPy module and the py.test-pypy script. Package: python-pytest-doc Section: doc Architecture: all -Depends: ${misc:Depends}, ${sphinxdoc:Depends} -Recommends: python-pytest | python3-pytest +Depends: + ${misc:Depends}, + ${sphinxdoc:Depends} +Recommends: + python-pytest | python3-pytest +Built-Using: ${sphinxdoc:Built-Using} Description: Simple, powerful testing in Python - Documentation This testing tool has for objective to allow the developers to limit the boilerplate code around the tests, promoting the use of built-in diff -Nru pytest-2.9.2/debian/copyright pytest-3.0.6/debian/copyright --- pytest-2.9.2/debian/copyright 2016-07-14 21:02:55.000000000 +0000 +++ pytest-3.0.6/debian/copyright 2017-01-26 08:11:26.000000000 +0000 @@ -3,7 +3,7 @@ Source: http://pytest.org/ Files: * -Copyright: 2005-2016 Holger Kregel +Copyright: 2004-2016 Holger Kregel License: Expat Files: debian/* @@ -32,10 +32,8 @@ SOFTWARE. Files: doc/en/_themes/* -Copyright: 2010 the Sphinx Team +Copyright: 2010 Armin Ronacher License: BSD-3-clause - Copyright (c) 2010 by Armin Ronacher. - . Some rights reserved. . Redistribution and use in source and binary forms of the theme, with or diff -Nru pytest-2.9.2/debian/.git-dpm pytest-3.0.6/debian/.git-dpm --- pytest-2.9.2/debian/.git-dpm 2016-07-14 21:02:55.000000000 +0000 +++ pytest-3.0.6/debian/.git-dpm 2017-01-26 08:09:35.000000000 +0000 @@ -1,11 +1,11 @@ # see git-dpm(1) from git-dpm package -0542b7c2618956eb202a15c68edba7f2fedbb6b4 -0542b7c2618956eb202a15c68edba7f2fedbb6b4 -25454da31503990848b614982e8b2d43ec96beac -25454da31503990848b614982e8b2d43ec96beac -pytest_2.9.2.orig.tar.gz -586675a7ce39cd33e16b34cc539240b38c10d5e8 -700808 +0522ce0b62c58959b4e733dd686706e62fba6073 +0522ce0b62c58959b4e733dd686706e62fba6073 +094551e172d9415ad7c68b3d0aa2e63b76e920bc +094551e172d9415ad7c68b3d0aa2e63b76e920bc +pytest_3.0.6.orig.tar.gz +16a43b3e6908ccdff64079a82b9c065cc5ff3a33 +748748 debianTag="debian/%e%v" patchedTag="patched/%e%v" upstreamTag="upstream/%e%u" diff -Nru pytest-2.9.2/debian/NEWS pytest-3.0.6/debian/NEWS --- pytest-2.9.2/debian/NEWS 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/debian/NEWS 2017-01-26 08:09:34.000000000 +0000 @@ -0,0 +1,7 @@ +pytest (3.0.0-1) experimental; urgency=medium + + pytest dropped all versioned py.test-X.Y entrypoints. If you need to run a + test suite with a specific Python version, please use `pythonX.Y -m pytest` + instead. + + -- Sebastian Ramacher Sun, 21 Aug 2016 19:24:52 +0200 diff -Nru pytest-2.9.2/debian/patches/drop-entrypoint-logic.patch pytest-3.0.6/debian/patches/drop-entrypoint-logic.patch --- pytest-2.9.2/debian/patches/drop-entrypoint-logic.patch 2016-07-14 21:02:55.000000000 +0000 +++ pytest-3.0.6/debian/patches/drop-entrypoint-logic.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,55 +0,0 @@ -From fbb23c4df1493b8ded70386e5ced1ab76e12c198 Mon Sep 17 00:00:00 2001 -From: Ronny Pfannschmidt -Date: Mon, 15 Feb 2016 21:50:17 +0100 -Subject: drop entrypoint logic as it missmatches setup time and wheel install - time - -Patch-Name: drop-entrypoint-logic.patch ---- - setup.py | 26 +++----------------------- - 1 file changed, 3 insertions(+), 23 deletions(-) - -diff --git a/setup.py b/setup.py -index 7cdcdfb..8ba08da 100644 ---- a/setup.py -+++ b/setup.py -@@ -69,7 +69,9 @@ def main(): - 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=make_entry_points(), -+ entry_points={ -+ 'console_scripts': ['py.test = pytest:main'] -+ }, - classifiers=classifiers, - cmdclass={'test': PyTest}, - # the following should be enabled for release -@@ -81,28 +83,6 @@ def main(): - ) - - --def cmdline_entrypoints(versioninfo, platform, basename): -- target = 'pytest:main' -- if platform.startswith('java'): -- points = {'py.test-jython': target} -- else: -- if basename.startswith('pypy'): -- points = {'py.test-%s' % basename: target} -- else: # cpython -- points = {'py.test-%s.%s' % versioninfo[:2] : target} -- points['py.test'] = target -- return points -- -- --def make_entry_points(): -- basename = os.path.basename(sys.executable) -- points = cmdline_entrypoints(sys.version_info, sys.platform, basename) -- keys = list(points.keys()) -- keys.sort() -- l = ['%s = %s' % (x, points[x]) for x in keys] -- return {'console_scripts': l} -- -- - class PyTest(Command): - user_options = [] - def initialize_options(self): diff -Nru pytest-2.9.2/debian/patches/intersphinx.patch pytest-3.0.6/debian/patches/intersphinx.patch --- pytest-2.9.2/debian/patches/intersphinx.patch 2016-07-14 21:02:55.000000000 +0000 +++ pytest-3.0.6/debian/patches/intersphinx.patch 2017-01-26 08:09:35.000000000 +0000 @@ -1,4 +1,4 @@ -From 0542b7c2618956eb202a15c68edba7f2fedbb6b4 Mon Sep 17 00:00:00 2001 +From 0522ce0b62c58959b4e733dd686706e62fba6073 Mon Sep 17 00:00:00 2001 From: Sebastian Ramacher Date: Thu, 14 Jul 2016 23:01:01 +0200 Subject: Use local intersphinx mappings @@ -9,10 +9,10 @@ 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/doc/en/conf.py b/doc/en/conf.py -index aca0442..f0eab9c 100644 +index 40f1e41..3bb64cc 100644 --- a/doc/en/conf.py +++ b/doc/en/conf.py -@@ -312,11 +312,18 @@ texinfo_documents = [ +@@ -309,11 +309,18 @@ texinfo_documents = [ ] diff -Nru pytest-2.9.2/debian/patches/remove_google_js pytest-3.0.6/debian/patches/remove_google_js --- pytest-2.9.2/debian/patches/remove_google_js 2016-07-14 21:02:55.000000000 +0000 +++ pytest-3.0.6/debian/patches/remove_google_js 2017-01-26 08:09:35.000000000 +0000 @@ -1,4 +1,4 @@ -From 800982325cd2f744bfef4b15b9e2cd3e475cf35a Mon Sep 17 00:00:00 2001 +From 3c64bc322679543f5563b60dd8a0d5385bf665b2 Mon Sep 17 00:00:00 2001 From: Simon Chopin Date: Thu, 8 Oct 2015 11:04:33 -0700 Subject: Remove Google Analytics and Google+ javascript. @@ -12,10 +12,10 @@ 1 file changed, 13 deletions(-) diff --git a/doc/en/_templates/layout.html b/doc/en/_templates/layout.html -index 0ce480b..580070b 100644 +index 2fc8e2a..3e30439 100644 --- a/doc/en/_templates/layout.html +++ b/doc/en/_templates/layout.html -@@ -18,17 +18,4 @@ +@@ -4,17 +4,4 @@ {% endblock %} {% block footer %} {{ super() }} diff -Nru pytest-2.9.2/debian/patches/series pytest-3.0.6/debian/patches/series --- pytest-2.9.2/debian/patches/series 2016-07-14 21:02:55.000000000 +0000 +++ pytest-3.0.6/debian/patches/series 2017-01-26 08:09:34.000000000 +0000 @@ -1,3 +1,2 @@ remove_google_js -drop-entrypoint-logic.patch intersphinx.patch diff -Nru pytest-2.9.2/debian/pypy-pytest.links pytest-3.0.6/debian/pypy-pytest.links --- pytest-2.9.2/debian/pypy-pytest.links 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/debian/pypy-pytest.links 2017-01-26 08:11:26.000000000 +0000 @@ -0,0 +1,2 @@ +usr/bin/pytest-pypy usr/bin/py.test-pypy +usr/share/man/man1/pytest-pypy.1 usr/share/man/man1/py.test-pypy.1 diff -Nru pytest-2.9.2/debian/pypy-pytest.manpages pytest-3.0.6/debian/pypy-pytest.manpages --- pytest-2.9.2/debian/pypy-pytest.manpages 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/debian/pypy-pytest.manpages 2017-01-26 08:11:26.000000000 +0000 @@ -0,0 +1 @@ +debian/pytest-pypy.1 diff -Nru pytest-2.9.2/debian/python3-pytest.install pytest-3.0.6/debian/python3-pytest.install --- pytest-2.9.2/debian/python3-pytest.install 2016-07-14 21:02:55.000000000 +0000 +++ pytest-3.0.6/debian/python3-pytest.install 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -debian/python3-pytest.rt* /usr/share/python3/runtime.d diff -Nru pytest-2.9.2/debian/python3-pytest.links pytest-3.0.6/debian/python3-pytest.links --- pytest-2.9.2/debian/python3-pytest.links 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/debian/python3-pytest.links 2017-01-26 08:11:26.000000000 +0000 @@ -0,0 +1,2 @@ +usr/bin/pytest-3 usr/bin/py.test-3 +usr/share/man/man1/pytest-3.1 usr/share/man/man1/py.test-3.1 diff -Nru pytest-2.9.2/debian/python3-pytest.manpages pytest-3.0.6/debian/python3-pytest.manpages --- pytest-2.9.2/debian/python3-pytest.manpages 2016-07-14 21:02:55.000000000 +0000 +++ pytest-3.0.6/debian/python3-pytest.manpages 2017-01-26 08:11:26.000000000 +0000 @@ -1 +1 @@ -debian/py.test-3.1 +debian/pytest-3.1 diff -Nru pytest-2.9.2/debian/python3-pytest.postinst pytest-3.0.6/debian/python3-pytest.postinst --- pytest-2.9.2/debian/python3-pytest.postinst 2016-07-14 21:02:55.000000000 +0000 +++ pytest-3.0.6/debian/python3-pytest.postinst 1970-01-01 00:00:00.000000000 +0000 @@ -1,39 +0,0 @@ -#!/bin/sh -set -e - -# summary of how this script can be called: -# * `configure' -# * `abort-upgrade' -# * `abort-remove' `in-favour' -# -# * `abort-remove' -# * `abort-deconfigure' `in-favour' -# `removing' -# -# for details, see http://www.debian.org/doc/debian-policy/ or -# the debian-policy package - - -case "$1" in - configure|abort-upgrade|abort-remove|abort-deconfigure) - # Just in case, recreate all scripts - for version in `py3versions -vi`; do - if [ $version ]; then - cp /usr/bin/py.test-3 /usr/bin/py.test-$version - sed -i "s,#! */usr/bin/python3,#!/usr/bin/python$version," "/usr/bin/py.test-$version" - fi - done - ;; - - *) - echo "postinst called with unknown argument \`$1'" >&2 - exit 1 - ;; -esac - -# dh_installdeb will replace this with shell code automatically -# generated by other debhelper scripts. - -#DEBHELPER# - -exit 0 diff -Nru pytest-2.9.2/debian/python3-pytest.prerm pytest-3.0.6/debian/python3-pytest.prerm --- pytest-2.9.2/debian/python3-pytest.prerm 2016-07-14 21:02:55.000000000 +0000 +++ pytest-3.0.6/debian/python3-pytest.prerm 1970-01-01 00:00:00.000000000 +0000 @@ -1,29 +0,0 @@ -#!/bin/sh -set -e - -# summary of how this script can be called: -# * `remove' -# * `upgrade' -# * `failed-upgrade' -# * `remove' `in-favour' -# * `deconfigure' `in-favour' -# `removing' -# - -case "$1" in - remove|upgrade|deconfigure|failed-upgrade) - rm -f /usr/bin/py.test-3.[0-9] - ;; - - *) - echo "prerm called with unknown argument \`$1'" >&2 - exit 1 - ;; -esac - -# dh_installdeb will replace this with shell code automatically -# generated by other debhelper scripts. - -#DEBHELPER# - -exit 0 diff -Nru pytest-2.9.2/debian/python3-pytest.rtinstall pytest-3.0.6/debian/python3-pytest.rtinstall --- pytest-2.9.2/debian/python3-pytest.rtinstall 2016-07-14 21:02:55.000000000 +0000 +++ pytest-3.0.6/debian/python3-pytest.rtinstall 1970-01-01 00:00:00.000000000 +0000 @@ -1,7 +0,0 @@ -#!/bin/sh -set -e -PYVERSION="${2##*python}" -if [ ! -e "/usr/bin/py.test-$PYVERSION" ]; then - cp /usr/bin/py.test-3 /usr/bin/py.test-$PYVERSION - sed -i "s,#! */usr/bin/python3,#!/usr/bin/$2," /usr/bin/py.test-$PYVERSION -fi diff -Nru pytest-2.9.2/debian/python3-pytest.rtremove pytest-3.0.6/debian/python3-pytest.rtremove --- pytest-2.9.2/debian/python3-pytest.rtremove 2016-07-14 21:02:55.000000000 +0000 +++ pytest-3.0.6/debian/python3-pytest.rtremove 1970-01-01 00:00:00.000000000 +0000 @@ -1,3 +0,0 @@ -#!/bin/sh -set -e -rm -f "/usr/bin/py.test-${2##*python}" diff -Nru pytest-2.9.2/debian/python-pytest.links pytest-3.0.6/debian/python-pytest.links --- pytest-2.9.2/debian/python-pytest.links 2016-07-14 21:02:55.000000000 +0000 +++ pytest-3.0.6/debian/python-pytest.links 2017-01-26 08:11:26.000000000 +0000 @@ -1,2 +1,2 @@ -usr/bin/py.test usr/bin/py.test-2.7 -usr/share/man/man1/py.test.1.gz usr/share/man/man1/py.test-2.7.1.gz +usr/bin/pytest usr/bin/py.test +usr/share/man/man1/pytest.1 usr/share/man/man1/py.test.1 diff -Nru pytest-2.9.2/debian/python-pytest.manpages pytest-3.0.6/debian/python-pytest.manpages --- pytest-2.9.2/debian/python-pytest.manpages 2016-07-14 21:02:55.000000000 +0000 +++ pytest-3.0.6/debian/python-pytest.manpages 2017-01-26 08:11:26.000000000 +0000 @@ -1 +1 @@ -debian/py.test.1 +doc/en/_build/man/pytest.1 diff -Nru pytest-2.9.2/debian/README.Debian pytest-3.0.6/debian/README.Debian --- pytest-2.9.2/debian/README.Debian 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/debian/README.Debian 2017-01-26 08:09:34.000000000 +0000 @@ -0,0 +1,9 @@ +Running test suites with pytest during package build +---------------------------------------------------- + +Since version 3.0.0 of pytest dropped the versioned entry points py.test-X.Y, +the recommended way to execute test suites using pytest is by running + + pythonX.Y -m pytest ... + +instead. diff -Nru pytest-2.9.2/debian/rules pytest-3.0.6/debian/rules --- pytest-2.9.2/debian/rules 2016-07-14 21:02:55.000000000 +0000 +++ pytest-3.0.6/debian/rules 2017-01-26 08:09:34.000000000 +0000 @@ -4,31 +4,34 @@ export PYBUILD_NAME=pytest %: - dh $@ --with python2,python3,sphinxdoc --buildsystem=pybuild + dh $@ --with python2,python3,pypy,sphinxdoc --buildsystem=pybuild override_dh_auto_build: dh_auto_build PYTHONPATH=$(CURDIR) $(MAKE) -C doc/en man html - cp doc/en/_build/man/pytest.1 debian/py.test.1 - cp debian/py.test.1 debian/py.test-3.1 - sed -i 's/PYTEST/PY.TEST/g' debian/py.test.1 - sed -i 's/PYTEST/PY.TEST-3/g' debian/py.test-3.1 - sed -i 's/py\.test/py.test-3/g' debian/py.test-3.1 + mkdir -p debian/tmp + sed 's/PYTEST/PYTEST-3/g' doc/en/_build/man/pytest.1 > debian/pytest-3.1 + sed 's/PYTEST/PYTEST-PYPY/g' doc/en/_build/man/pytest.1 > debian/pytest-pypy.1 # 2015-12-16 barry@debian.org: Because pytest does not clean up after itself, # use a custom temporary directory (which is easier to clean up manually, # e.g. in an sbuild). override_dh_auto_test: mkdir -p debian/tmp/test-working-directory - PYBUILD_SYSTEM=custom \ - PYBUILD_TEST_ARGS="cd debian/tmp/test-working-directory && {interpreter} -m pytest --lsof -rfsxX --ignore={dir}/testing/cx_freeze {dir}/testing" dh_auto_test + dh_auto_test -- \ + --system=custom \ + --test-args="cd debian/tmp/test-working-directory && {interpreter} -m pytest --lsof -rfsxX --ignore={dir}/testing/freeze --ignore={dir}/testing/test_entry_points.py --ignore={dir}/testing/test_pdb.py {dir}/testing" override_dh_auto_install: dh_auto_install - mv debian/python3-pytest/usr/bin/py.test \ - debian/python3-pytest/usr/bin/py.test-3 + rm debian/python-pytest/usr/bin/py.test + rm debian/python3-pytest/usr/bin/py.test + rm debian/pypy-pytest/usr/bin/py.test + mv debian/python3-pytest/usr/bin/pytest \ + debian/python3-pytest/usr/bin/pytest-3 + mv debian/pypy-pytest/usr/bin/pytest \ + debian/pypy-pytest/usr/bin/pytest-pypy override_dh_auto_clean: rm -rf doc/en/_build - rm -rf pytest.egg-info .cache dh_auto_clean diff -Nru pytest-2.9.2/debian/tests/control pytest-3.0.6/debian/tests/control --- pytest-2.9.2/debian/tests/control 2016-07-14 21:02:55.000000000 +0000 +++ pytest-3.0.6/debian/tests/control 2017-01-26 08:09:34.000000000 +0000 @@ -3,3 +3,6 @@ Tests: smoketest-3 Depends: python3-pytest, python3-all + +Tests: smoketest-pypy +Depends: pypy-pytest diff -Nru pytest-2.9.2/debian/tests/smoketest-pypy pytest-3.0.6/debian/tests/smoketest-pypy --- pytest-2.9.2/debian/tests/smoketest-pypy 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/debian/tests/smoketest-pypy 2017-01-26 08:09:34.000000000 +0000 @@ -0,0 +1,14 @@ +#!/bin/sh + +cat > "$ADTTMP"/smoketest.py <`_ To install or upgrade pytest:: @@ -118,7 +118,7 @@ - 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 -Nru pytest-2.9.2/doc/en/announce/release-2.5.0.rst pytest-3.0.6/doc/en/announce/release-2.5.0.rst --- pytest-2.9.2/doc/en/announce/release-2.5.0.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/announce/release-2.5.0.rst 2017-01-20 17:15:39.000000000 +0000 @@ -70,7 +70,7 @@ 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 @@ 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 -Nru pytest-2.9.2/doc/en/announce/release-2.5.2.rst pytest-3.0.6/doc/en/announce/release-2.5.2.rst --- pytest-2.9.2/doc/en/announce/release-2.5.2.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/announce/release-2.5.2.rst 2017-01-20 17:15:39.000000000 +0000 @@ -60,5 +60,5 @@ - 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 -Nru pytest-2.9.2/doc/en/announce/release-2.6.3.rst pytest-3.0.6/doc/en/announce/release-2.6.3.rst --- pytest-2.9.2/doc/en/announce/release-2.6.3.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/announce/release-2.6.3.rst 2017-01-20 17:15:39.000000000 +0000 @@ -41,8 +41,8 @@ dep). Thanks Charles Cloud for analysing the issue. - fix conftest related fixture visibility issue: when running with a - CWD outside a test package pytest would get fixture discovery wrong. - Thanks to Wolfgang Schnerring for figuring out a reproducable example. + CWD outside of a test package pytest would get fixture discovery wrong. + 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 -Nru pytest-2.9.2/doc/en/announce/release-2.7.1.rst pytest-3.0.6/doc/en/announce/release-2.7.1.rst --- pytest-2.9.2/doc/en/announce/release-2.7.1.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/announce/release-2.7.1.rst 2017-01-20 17:15:39.000000000 +0000 @@ -32,7 +32,7 @@ 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 -Nru pytest-2.9.2/doc/en/announce/release-2.9.1.rst pytest-3.0.6/doc/en/announce/release-2.9.1.rst --- pytest-2.9.2/doc/en/announce/release-2.9.1.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/announce/release-2.9.1.rst 2017-01-20 17:15:39.000000000 +0000 @@ -63,3 +63,5 @@ .. _#649: https://github.com/pytest-dev/pytest/issues/649 .. _@asottile: https://github.com/asottile +.. _@nicoddemus: https://github.com/nicoddemus +.. _@tomviner: https://github.com/tomviner diff -Nru pytest-2.9.2/doc/en/announce/release-2.9.2.rst pytest-3.0.6/doc/en/announce/release-2.9.2.rst --- pytest-2.9.2/doc/en/announce/release-2.9.2.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/announce/release-2.9.2.rst 2017-01-20 17:15:39.000000000 +0000 @@ -1,4 +1,4 @@ -pytest-2.9.1 +pytest-2.9.2 ============ pytest is a mature Python testing tool with more than a 1100 tests @@ -46,7 +46,7 @@ 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 @@ -69,5 +69,10 @@ .. _#1496: https://github.com/pytest-dev/pytest/issue/1496 .. _#1524: https://github.com/pytest-dev/pytest/issue/1524 -.. _@prusse-martin: https://github.com/prusse-martin .. _@astraw38: https://github.com/astraw38 +.. _@hackebrot: https://github.com/hackebrot +.. _@omarkohl: https://github.com/omarkohl +.. _@pquentin: https://github.com/pquentin +.. _@prusse-martin: https://github.com/prusse-martin +.. _@RonnyPfannschmidt: https://github.com/RonnyPfannschmidt +.. _@tomviner: https://github.com/tomviner diff -Nru pytest-2.9.2/doc/en/announce/release-3.0.0.rst pytest-3.0.6/doc/en/announce/release-3.0.0.rst --- pytest-2.9.2/doc/en/announce/release-3.0.0.rst 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/doc/en/announce/release-3.0.0.rst 2017-01-20 17:15:39.000000000 +0000 @@ -0,0 +1,82 @@ +pytest-3.0.0 +============ + +The pytest team is proud to announce the 3.0.0 release! + +pytest is a mature Python testing tool with more than a 1600 tests +against itself, passing on many different interpreters and platforms. + +This release contains a lot of bugs fixes and improvements, and much of +the work done on it was possible because of the 2016 Sprint[1], which +was funded by an indiegogo campaign which raised over US$12,000 with +nearly 100 backers. + +There's a "What's new in pytest 3.0" [2] blog post highlighting the +major features in this release. + +To see the complete changelog and documentation, please visit: + + http://docs.pytest.org + +As usual, you can upgrade from pypi via: + + pip install -U pytest + +Thanks to all who contributed to this release, among them: + + AbdealiJK + Ana Ribeiro + Antony Lee + Brandon W Maister + Brianna Laugher + Bruno Oliveira + Ceridwen + Christian Boelsen + Daniel Hahler + Danielle Jenkins + Dave Hunt + Diego Russo + Dmitry Dygalo + Edoardo Batini + Eli Boyarski + Florian Bruhin + Floris Bruynooghe + Greg Price + Guyzmo + HEAD KANGAROO + JJ + Javi Romero + Javier Domingo Cansino + Kale Kundert + Kalle Bronsen + Marius Gedminas + Matt Williams + Mike Lundy + Oliver Bestwalter + Omar Kohl + Raphael Pierzina + RedBeardCode + Roberto Polli + Romain Dorgueil + Roman Bolshakov + Ronny Pfannschmidt + Stefan Zimmermann + Steffen Allner + Tareq Alayan + Ted Xiao + Thomas Grainger + Tom Viner + TomV + Vasily Kuznetsov + aostr + marscher + palaviv + satoru + taschini + + +Happy testing, +The Pytest Development Team + +[1] http://blog.pytest.org/2016/pytest-development-sprint/ +[2] http://blog.pytest.org/2016/whats-new-in-pytest-30/ diff -Nru pytest-2.9.2/doc/en/announce/release-3.0.1.rst pytest-3.0.6/doc/en/announce/release-3.0.1.rst --- pytest-2.9.2/doc/en/announce/release-3.0.1.rst 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/doc/en/announce/release-3.0.1.rst 2017-01-20 17:15:39.000000000 +0000 @@ -0,0 +1,26 @@ +pytest-3.0.1 +============ + +pytest 3.0.1 has just been released to PyPI. + +This release fixes some regressions reported in version 3.0.0, 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: + + Adam Chainz + Andrew Svetlov + Bruno Oliveira + Daniel Hahler + Dmitry Dygalo + Florian Bruhin + Marcin Bachry + Ronny Pfannschmidt + matthiasha + +Happy testing, +The py.test Development Team diff -Nru pytest-2.9.2/doc/en/announce/release-3.0.2.rst pytest-3.0.6/doc/en/announce/release-3.0.2.rst --- pytest-2.9.2/doc/en/announce/release-3.0.2.rst 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/doc/en/announce/release-3.0.2.rst 2017-01-20 17:15:39.000000000 +0000 @@ -0,0 +1,24 @@ +pytest-3.0.2 +============ + +pytest 3.0.2 has just been released to PyPI. + +This release fixes some regressions and bugs reported in version 3.0.1, 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: + +* Ahn Ki-Wook +* Bruno Oliveira +* Florian Bruhin +* Jordan Guymon +* Raphael Pierzina +* Ronny Pfannschmidt +* mbyt + +Happy testing, +The pytest Development Team diff -Nru pytest-2.9.2/doc/en/announce/release-3.0.3.rst pytest-3.0.6/doc/en/announce/release-3.0.3.rst --- pytest-2.9.2/doc/en/announce/release-3.0.3.rst 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/doc/en/announce/release-3.0.3.rst 2017-01-20 17:15:39.000000000 +0000 @@ -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 -Nru pytest-2.9.2/doc/en/announce/release-3.0.4.rst pytest-3.0.6/doc/en/announce/release-3.0.4.rst --- pytest-2.9.2/doc/en/announce/release-3.0.4.rst 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/doc/en/announce/release-3.0.4.rst 2017-01-20 17:15:39.000000000 +0000 @@ -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 -Nru pytest-2.9.2/doc/en/announce/release-3.0.5.rst pytest-3.0.6/doc/en/announce/release-3.0.5.rst --- pytest-2.9.2/doc/en/announce/release-3.0.5.rst 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/doc/en/announce/release-3.0.5.rst 2017-01-20 17:15:39.000000000 +0000 @@ -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 -Nru pytest-2.9.2/doc/en/announce/release-3.0.6.rst pytest-3.0.6/doc/en/announce/release-3.0.6.rst --- pytest-2.9.2/doc/en/announce/release-3.0.6.rst 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/doc/en/announce/release-3.0.6.rst 2017-01-22 17:44:30.000000000 +0000 @@ -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 -Nru pytest-2.9.2/doc/en/announce/sprint2016.rst pytest-3.0.6/doc/en/announce/sprint2016.rst --- pytest-2.9.2/doc/en/announce/sprint2016.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/announce/sprint2016.rst 2017-01-20 17:15:39.000000000 +0000 @@ -4,9 +4,9 @@ .. image:: ../img/freiburg2.jpg :width: 400 -The pytest core group is heading towards the biggest sprint -in its history, to take place in the black forest town Freiburg -in Germany. As of February 2016 we have started a `funding +The pytest core group held the biggest sprint +in its history in June 2016, taking place in the black forest town Freiburg +in Germany. In February 2016 we started a `funding campaign on Indiegogo to cover expenses `_ The page also mentions some preliminary topics: @@ -35,71 +35,30 @@ Participants -------------- -Here are preliminary participants who said they are likely to come, -given some expenses funding:: - - Anatoly Bubenkoff, Netherlands - Andreas Pelme, Personalkollen, Sweden - Anthony Wang, Splunk, US - Brianna Laugher, Australia - Bruno Oliveira, Brazil - Danielle Jenkins, Splunk, US - Dave Hunt, UK - Florian Bruhin, Switzerland - Floris Bruynooghe, Cobe.io, UK - Holger Krekel, merlinux, Germany - Oliver Bestwalter, Avira, Germany - Omar Kohl, Germany - Raphael Pierzina, FanDuel, UK - Tom Viner, UK - - - -Other contributors and experienced newcomers are invited to join as well -but please send a mail to the pytest-dev mailing list if you intend to -do so somewhat soon, also how much funding you need if so. And if you -are working for a company and using pytest heavily you are welcome to -join and we encourage your company to provide some funding for the -sprint. They may see it, and rightfully so, as a very cheap and deep -training which brings you together with the experts in the field :) +Over 20 participants took part from 4 continents, including employees +from Splunk, Personalkollen, Cobe.io, FanDuel and Dolby. Some newcomers +mixed with developers who have worked on pytest since its beginning, and +of course everyone in between. Sprint organisation, schedule ------------------------------- -tentative schedule: +People arrived in Freiburg on the 19th, with sprint development taking +place on 20th, 21st, 22nd, 24th and 25th. On the 23rd we took a break +day for some hot hiking in the Black Forest. + +Sprint activity was organised heavily around pairing, with plenty of group +discusssions to take advantage of the high bandwidth, and lightning talks +as well. -- 19/20th arrival in Freiburg -- 20th social get together, initial hacking -- 21/22th full sprint days -- 23rd break day, hiking -- 24/25th full sprint days -- 26th departure - -We might adjust according to weather to make sure that if -we do some hiking or excursion we'll have good weather. -Freiburg is one of the sunniest places in Germany so -it shouldn't be too much of a constraint. - - -Accomodation ----------------- - -We'll see to arrange for renting a flat with multiple -beds/rooms. Hotels are usually below 100 per night. -The earlier we book the better. Money / funding --------------- -The Indiegogo campaign asks for 11000 USD which should cover -the costs for flights and accomodation, renting a sprint place -and maybe a bit of food as well. - -If your organisation wants to support the sprint but prefers -to give money according to an invoice, get in contact with -holger at http://merlinux.eu who can invoice your organisation -properly. -If we have excess money we'll use for further sprint/travel -funding for pytest/tox contributors. +The Indiegogo campaign aimed for 11000 USD and in the end raised over +12000, to reimburse travel costs, pay for a sprint venue and catering. + +Excess money is reserved for further sprint/travel funding for pytest/tox +contributors. diff -Nru pytest-2.9.2/doc/en/assert.rst pytest-3.0.6/doc/en/assert.rst --- pytest-2.9.2/doc/en/assert.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/assert.rst 2017-01-22 17:44:30.000000000 +0000 @@ -24,9 +24,9 @@ to assert that your function returns a certain value. If this assertion fails you will see the return value of the function call:: - $ py.test test_assert1.py + $ pytest test_assert1.py ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR, inifile: collected 1 items @@ -85,6 +85,15 @@ the actual exception raised. The main attributes of interest are ``.type``, ``.value`` and ``.traceback``. +.. versionchanged:: 3.0 + +In the context manager form you may use the keyword argument +``message`` to specify a custom failure message:: + + >>> with raises(ZeroDivisionError, message="Expecting ZeroDivisionError"): + ... pass + ... Failed: Expecting ZeroDivisionError + If you want to write test code that works on Python 2.4 as well, you may also use two other ways to test for an expected exception:: @@ -110,6 +119,24 @@ like documenting unfixed bugs (where the test describes what "should" happen) or bugs in dependencies. +If you want to test that a regular expression matches on the string +representation of an exception (like the ``TestCase.assertRaisesRegexp`` method +from ``unittest``) you can use the ``ExceptionInfo.match`` method:: + + import pytest + + def myfunc(): + raise ValueError("Exception 123 raised") + + def test_match(): + with pytest.raises(ValueError) as excinfo: + myfunc() + excinfo.match(r'.* 123 .*') + +The regexp parameter of the ``match`` method is matched with the ``re.search`` +function. So in the above example ``excinfo.match('123')`` would have worked as +well. + .. _`assertwarns`: @@ -141,9 +168,9 @@ if you run this module:: - $ py.test test_assert2.py + $ pytest test_assert2.py ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR, inifile: collected 1 items @@ -181,6 +208,7 @@ the ``pytest_assertrepr_compare`` hook. .. autofunction:: _pytest.hookspec.pytest_assertrepr_compare + :noindex: As an example consider adding the following hook in a conftest.py which provides an alternative explanation for ``Foo`` objects:: @@ -210,7 +238,7 @@ you can run the test module and get the custom output defined in the conftest file:: - $ py.test -q test_foocompare.py + $ pytest -q test_foocompare.py F ======= FAILURES ======== _______ test_compare ________ @@ -234,50 +262,20 @@ .. 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 `_. @@ -287,3 +285,7 @@ .. versionchanged:: 2.1 Introduce the ``--assert`` option. Deprecate ``--no-assert`` and ``--nomagic``. + +.. versionchanged:: 3.0 + Removes the ``--no-assert`` and``--nomagic`` options. + Removes the ``--assert=reinterp`` option. diff -Nru pytest-2.9.2/doc/en/backwards-compatibility.rst pytest-3.0.6/doc/en/backwards-compatibility.rst --- pytest-2.9.2/doc/en/backwards-compatibility.rst 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/doc/en/backwards-compatibility.rst 2017-01-20 17:15:14.000000000 +0000 @@ -0,0 +1,12 @@ +.. _backwards-compatibility: + +Backwards Compatibility Policy +============================== + +Keeping backwards compatibility has a very high priority in the pytest project. Although we have deprecated functionality over the years, most of it is still supported. All deprecations in pytest were done because simpler or more efficient ways of accomplishing the same tasks have emerged, making the old way of doing things unnecessary. + +With the pytest 3.0 release we introduced a clear communication scheme for when we will actually remove the old busted joint and politely ask you to use the new hotness instead, while giving you enough time to adjust your tests or raise concerns if there are valid reasons to keep deprecated functionality around. + +To communicate changes we are already issuing deprecation warnings, but they are not displayed by default. In pytest 3.0 we changed the default setting so that pytest deprecation warnings are displayed if not explicitly silenced (with ``--disable-pytest-warnings``). + +We will only remove deprecated functionality in major releases (e.g. if we deprecate something in 3.0 we will remove it in 4.0), and keep it around for at least two minor releases (e.g. if we deprecate something in 3.9 and 4.0 is the next release, we will not remove it in 4.0 but in 5.0). diff -Nru pytest-2.9.2/doc/en/bash-completion.rst pytest-3.0.6/doc/en/bash-completion.rst --- pytest-2.9.2/doc/en/bash-completion.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/bash-completion.rst 2017-01-20 17:15:14.000000000 +0000 @@ -18,11 +18,11 @@ For permanent (but not global) ``pytest`` activation, use:: - register-python-argcomplete py.test >> ~/.bashrc + register-python-argcomplete pytest >> ~/.bashrc For one-time activation of argcomplete for ``pytest`` only, use:: - eval "$(register-python-argcomplete py.test)" + eval "$(register-python-argcomplete pytest)" diff -Nru pytest-2.9.2/doc/en/builtin.rst pytest-3.0.6/doc/en/builtin.rst --- pytest-2.9.2/doc/en/builtin.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/builtin.rst 2017-01-20 17:15:15.000000000 +0000 @@ -35,6 +35,11 @@ .. autofunction:: deprecated_call +Comparing floating point numbers +-------------------------------- + +.. autoclass:: approx + Raising a specific test outcome -------------------------------------- @@ -48,18 +53,18 @@ .. autofunction:: _pytest.skipping.xfail .. autofunction:: _pytest.runner.exit -fixtures and requests +Fixtures and requests ----------------------------------------------------- To mark a fixture function: -.. autofunction:: _pytest.python.fixture +.. autofunction:: _pytest.fixtures.fixture Tutorial at :ref:`fixtures`. The ``request`` object that can be used from fixture functions. -.. autoclass:: _pytest.python.FixtureRequest() +.. autoclass:: _pytest.fixtures.FixtureRequest() :members: @@ -72,7 +77,7 @@ You can ask for available builtin or project-custom :ref:`fixtures ` by typing:: - $ py.test -q --fixtures + $ pytest -q --fixtures cache Return a cache object that can persist state between testing sessions. @@ -84,19 +89,23 @@ Values can be any object handled by the json stdlib module. capsys - enables capturing of writes to sys.stdout/sys.stderr and makes + Enable capturing of writes to sys.stdout/sys.stderr and make captured output available via ``capsys.readouterr()`` method calls which return a ``(out, err)`` tuple. capfd - enables capturing of writes to file descriptors 1 and 2 and makes + Enable capturing of writes to file descriptors 1 and 2 and make captured output available via ``capfd.readouterr()`` method calls which return a ``(out, err)`` tuple. + doctest_namespace + Inject names into the doctest namespace. + pytestconfig + the pytest config object with access to command line opts. record_xml_property - Fixture that adds extra xml properties to the tag for the calling test. - The fixture is callable with (name, value), with value being automatically + Add extra xml properties to the tag for the calling test. + The fixture is callable with ``(name, value)``, with value being automatically xml-encoded. monkeypatch - The returned ``monkeypatch`` funcarg provides these + The returned ``monkeypatch`` fixture provides these helper methods to modify objects, dictionaries or os.environ:: monkeypatch.setattr(obj, name, value, raising=True) @@ -109,11 +118,9 @@ monkeypatch.chdir(path) All modifications will be undone after the requesting - test function has finished. The ``raising`` + test function or fixture has finished. The ``raising`` parameter determines if a KeyError or AttributeError will be raised if the set/deletion operation has no target. - pytestconfig - the pytest config object with access to command line opts. recwarn Return a WarningsRecorder instance that provides these methods: @@ -125,7 +132,7 @@ tmpdir_factory Return a TempdirFactory instance for the test session. tmpdir - return a temporary directory path object + Return a temporary directory path object which is unique to each test function invocation, created as a sub directory of the base temporary directory. The returned object is a `py.path.local`_ diff -Nru pytest-2.9.2/doc/en/cache.rst pytest-3.0.6/doc/en/cache.rst --- pytest-2.9.2/doc/en/cache.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/cache.rst 2017-01-22 17:44:30.000000000 +0000 @@ -15,7 +15,7 @@ --------- The plugin provides two command line options to rerun failures from the -last ``py.test`` invocation: +last ``pytest`` invocation: * ``--lf``, ``--last-failed`` - to only re-run the failures. * ``--ff``, ``--failed-first`` - to run the failures first and then the rest of @@ -25,7 +25,7 @@ all cross-session cache contents ahead of a test run. Other plugins may access the `config.cache`_ object to set/get -**json encodable** values between ``py.test`` invocations. +**json encodable** values between ``pytest`` invocations. .. note:: @@ -49,7 +49,7 @@ If you run this for the first time you will see two failures:: - $ py.test -q + $ pytest -q .................F.......F........................ ======= FAILURES ======== _______ test_num[17] ________ @@ -78,9 +78,9 @@ If you then run it with ``--lf``:: - $ py.test --lf + $ pytest --lf ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 run-last-failure: rerun last 2 failures rootdir: $REGENDOC_TMPDIR, inifile: collected 50 items @@ -110,6 +110,7 @@ E Failed: bad luck test_50.py:6: Failed + ======= 48 tests deselected ======== ======= 2 failed, 48 deselected in 0.12 seconds ======== You have run only the two failing test from the last run, while 48 tests have @@ -119,9 +120,9 @@ previous failures will be executed first (as can be seen from the series of ``FF`` and dots):: - $ py.test --ff + $ pytest --ff ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 run-last-failure: rerun last 2 failures first rootdir: $REGENDOC_TMPDIR, inifile: collected 50 items @@ -163,7 +164,7 @@ Plugins or conftest.py support code can get a cached value using the pytest ``config`` object. Here is a basic example plugin which implements a :ref:`fixture` which re-uses previously created state -across py.test invocations:: +across pytest invocations:: # content of test_caching.py import pytest @@ -184,7 +185,7 @@ If you run this command once, it will take a while because of the sleep:: - $ py.test -q + $ pytest -q F ======= FAILURES ======== _______ test_function ________ @@ -201,7 +202,7 @@ If you run it a second time the value will be retrieved from the cache and this will be quick:: - $ py.test -q + $ pytest -q F ======= FAILURES ======== _______ test_function ________ @@ -222,27 +223,20 @@ ------------------------------- You can always peek at the content of the cache using the -``--cache-clear`` command line option:: +``--cache-show`` command line option:: - $ py.test --cache-clear + $ py.test --cache-show ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR, inifile: - collected 1 items + cachedir: $REGENDOC_TMPDIR/.cache + ------------------------------- cache values ------------------------------- + example/value contains: + 42 + cache/lastfailed contains: + {'test_caching.py::test_function': True} - test_caching.py F - - ======= FAILURES ======== - _______ test_function ________ - - mydata = 42 - - def test_function(mydata): - > assert mydata == 23 - E assert 42 == 23 - - test_caching.py:14: AssertionError - ======= 1 failed in 0.12 seconds ======== + ======= no tests ran in 0.12 seconds ======== Clearing Cache content ------------------------------- @@ -250,9 +244,9 @@ You can instruct pytest to clear all cache files and values by adding the ``--cache-clear`` option like this:: - py.test --cache-clear + 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 -Nru pytest-2.9.2/doc/en/capture.rst pytest-3.0.6/doc/en/capture.rst --- pytest-2.9.2/doc/en/capture.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/capture.rst 2017-01-22 17:44:30.000000000 +0000 @@ -36,9 +36,9 @@ You can influence output capturing mechanisms from the command line:: - py.test -s # disable all capturing - py.test --capture=sys # replace sys.stdout/stderr with in-mem files - py.test --capture=fd # also point filedescriptors 1 and 2 to temp file + pytest -s # disable all capturing + pytest --capture=sys # replace sys.stdout/stderr with in-mem files + pytest --capture=fd # also point filedescriptors 1 and 2 to temp file .. _printdebugging: @@ -62,9 +62,9 @@ and running this module will show you precisely the output of the failing function and hide the other one:: - $ py.test + $ pytest ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR, inifile: collected 2 items @@ -97,7 +97,7 @@ out, err = capsys.readouterr() assert out == "hello\n" assert err == "world\n" - print "next" + print ("next") out, err = capsys.readouterr() assert out == "next\n" @@ -115,4 +115,19 @@ libraries or subprocesses that directly write to operating system level output streams (FD1 and FD2). + +.. versionadded:: 3.0 + +To temporarily disable capture within a test, both ``capsys`` +and ``capfd`` have a ``disabled()`` method that can be used +as a context manager, disabling capture inside the ``with`` block: + +.. code-block:: python + + def test_disabling_capturing(capsys): + print('this output is captured') + with capsys.disabled(): + print('output not captured, going directly to sys.stdout') + print('this output is also captured') + .. include:: links.inc diff -Nru pytest-2.9.2/doc/en/conf.py pytest-3.0.6/doc/en/conf.py --- pytest-2.9.2/doc/en/conf.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/conf.py 2017-01-20 16:40:21.000000000 +0000 @@ -19,11 +19,8 @@ # The short X.Y version. import os, sys -sys.path.insert(0, os.path.dirname(__file__)) -import _getdoctarget - -version = _getdoctarget.get_minor_version_string() -release = _getdoctarget.get_version_string() +from _pytest import __version__ as version +release = ".".join(version.split(".")[:2]) # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the @@ -127,7 +124,7 @@ # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". -html_title = None +html_title = 'pytest documentation' # A shorter title for the navigation bar. Default is the same as html_title. html_short_title = "pytest-%s" % release @@ -144,7 +141,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +# html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. @@ -306,7 +303,7 @@ ('Holger Krekel@*Benjamin Peterson@*Ronny Pfannschmidt@*' 'Floris Bruynooghe@*others'), 'pytest', - 'simple powerful testing with Pytho', + 'simple powerful testing with Python', 'Programming', 1), ] diff -Nru pytest-2.9.2/doc/en/contents.rst pytest-3.0.6/doc/en/contents.rst --- pytest-2.9.2/doc/en/contents.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/contents.rst 2017-01-20 17:15:23.000000000 +0000 @@ -3,37 +3,58 @@ Full pytest documentation =========================== -`Download latest version as PDF `_ +`Download latest version as PDF `_ .. `Download latest version as EPUB `_ .. toctree:: :maxdepth: 2 - overview - apiref - example/index + getting-started + usage + assert + builtin + fixture monkeypatch tmpdir capture recwarn + doctest + mark + skipping + parametrize cache + unittest + nose + xunit_setup plugins + writing_plugins + + example/index + goodpractices + customize + bash-completion + backwards-compatibility + license contributing talks + projects + faq + contact .. only:: html .. toctree:: + :maxdepth: 1 - funcarg_compare announce/index .. only:: html .. toctree:: :hidden: + :maxdepth: 1 changelog diff -Nru pytest-2.9.2/doc/en/customize.rst pytest-3.0.6/doc/en/customize.rst --- pytest-2.9.2/doc/en/customize.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/customize.rst 2017-01-20 17:15:23.000000000 +0000 @@ -7,7 +7,7 @@ You can get help on command line options and values in INI-style configurations files by using the general help option:: - py.test -h # prints options _and_ config file settings + pytest -h # prints options _and_ config file settings This will display command line and configuration file settings which were registered by installed plugins. @@ -29,25 +29,39 @@ Here is the algorithm which finds the rootdir from ``args``: -- determine the common ancestor directory for the specified ``args``. - -- look for ``pytest.ini``, ``tox.ini`` and ``setup.cfg`` files in the - ancestor directory and upwards. If one is matched, it becomes the - ini-file and its directory becomes the rootdir. An existing - ``pytest.ini`` file will always be considered a match whereas - ``tox.ini`` and ``setup.cfg`` will only match if they contain - a ``[pytest]`` section. - -- if no ini-file was found, look for ``setup.py`` upwards from - the common ancestor directory to determine the ``rootdir``. - -- if no ini-file and no ``setup.py`` was found, use the already - determined common ancestor as root directory. This allows to - work with pytest in structures that are not part of a package - and don't have any particular ini-file configuration. - -Note that options from multiple ini-files candidates are never merged, -the first one wins (``pytest.ini`` always wins even if it does not +- determine the common ancestor directory for the specified ``args`` that are + recognised as paths that exist in the file system. If no such paths are + found, the common ancestor directory is set to the current working directory. + +- look for ``pytest.ini``, ``tox.ini`` and ``setup.cfg`` files in the ancestor + directory and upwards. If one is matched, it becomes the ini-file and its + directory becomes the rootdir. + +- if no ini-file was found, look for ``setup.py`` upwards from the common + ancestor directory to determine the ``rootdir``. + +- if no ``setup.py`` was found, look for ``pytest.ini``, ``tox.ini`` and + ``setup.cfg`` in each of the specified ``args`` and upwards. If one is + matched, it becomes the ini-file and its directory becomes the rootdir. + +- if no ini-file was found, use the already determined common ancestor as root + directory. This allows to work with pytest in structures that are not part of + a package and don't have any particular ini-file configuration. + +If no ``args`` are given, pytest collects test below the current working +directory and also starts determining the rootdir from there. + +:warning: custom pytest plugin commandline arguments may include a path, as in + ``pytest --log-output ../../test.log args``. Then ``args`` is mandatory, + otherwise pytest uses the folder of test.log for rootdir determination + (see also `issue 1435 `_). + A dot ``.`` for referencing to the current working directory is also + possible. + +Note that an existing ``pytest.ini`` file will always be considered a match, +whereas ``tox.ini`` and ``setup.cfg`` will only match if they contain a +``[pytest]`` or ``[tool:pytest]`` section, respectively. Options from multiple ini-files candidates are never +merged - the first one wins (``pytest.ini`` always wins, even if it does not contain a ``[pytest]`` section). The ``config`` object will subsequently carry these attributes: @@ -62,14 +76,14 @@ Example:: - py.test path/to/testdir path/other/ + pytest path/to/testdir path/other/ will determine the common ancestor as ``path`` and then check for ini-files as follows:: # first look for pytest.ini files path/pytest.ini - path/setup.cfg # must also contain [pytest] section to match + path/setup.cfg # must also contain [tool:pytest] section to match path/tox.ini # must also contain [pytest] section to match pytest.ini ... # all the way down to the root @@ -126,9 +140,9 @@ [pytest] addopts = --maxfail=2 -rf # exit after 2 failures, report fail info - issuing ``py.test test_hello.py`` actually means:: + issuing ``pytest test_hello.py`` actually means:: - py.test --maxfail=2 -rf test_hello.py + pytest --maxfail=2 -rf test_hello.py Default is to add no options. @@ -144,13 +158,13 @@ [seq] matches any character in seq [!seq] matches any char not in seq - Default patterns are ``'.*', 'CVS', '_darcs', '{arch}', '*.egg'``. + Default patterns are ``'.*', 'build', 'dist', 'CVS', '_darcs', '{arch}', '*.egg'``. Setting a ``norecursedirs`` replaces the default. Here is an example of how to avoid certain directories: .. code-block:: ini - # content of setup.cfg + # content of pytest.ini [pytest] norecursedirs = .svn _build tmp* @@ -218,7 +232,7 @@ .. confval:: doctest_optionflags One or more doctest flag names from the standard ``doctest`` module. - :doc:`See how py.test handles doctests `. + :doc:`See how pytest handles doctests `. .. confval:: confcutdir diff -Nru pytest-2.9.2/doc/en/doctest.rst pytest-3.0.6/doc/en/doctest.rst --- pytest-2.9.2/doc/en/doctest.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/doctest.rst 2017-01-22 17:44:30.000000000 +0000 @@ -6,7 +6,7 @@ be run through the python standard ``doctest`` module. You can change the pattern by issuing:: - py.test --doctest-glob='*.rst' + pytest --doctest-glob='*.rst' on the command line. Since version ``2.9``, ``--doctest-glob`` can be given multiple times in the command-line. @@ -15,7 +15,7 @@ from docstrings in all python modules (including regular python test modules):: - py.test --doctest-modules + pytest --doctest-modules You can make these changes permanent in your project by putting them into a pytest.ini file like this: @@ -45,11 +45,11 @@ """ return 42 -then you can just invoke ``py.test`` without command line options:: +then you can just invoke ``pytest`` without command line options:: - $ py.test + $ pytest ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini collected 1 items @@ -68,7 +68,7 @@ when executing text doctest files. The standard ``doctest`` module provides some setting flags to configure the -strictness of doctest tests. In py.test You can enable those flags those flags +strictness of doctest tests. In pytest You can enable those flags those flags using the configuration file. To make pytest ignore trailing whitespaces and ignore lengthy exception stack traces you can just write: @@ -77,7 +77,7 @@ [pytest] doctest_optionflags= NORMALIZE_WHITESPACE IGNORE_EXCEPTION_DETAIL -py.test also introduces new options to allow doctests to run in Python 2 and +pytest also introduces new options to allow doctests to run in Python 2 and Python 3 unchanged: * ``ALLOW_UNICODE``: when enabled, the ``u`` prefix is stripped from unicode @@ -103,3 +103,50 @@ 'Hello' +The 'doctest_namespace' fixture +------------------------------- + +.. versionadded:: 3.0 + +The ``doctest_namespace`` fixture can be used to inject items into the +namespace in which your doctests run. It is intended to be used within +your own fixtures to provide the tests that use them with context. + +``doctest_namespace`` is a standard ``dict`` object into which you +place the objects you want to appear in the doctest namespace:: + + # content of conftest.py + import numpy + @pytest.fixture(autouse=True) + def add_np(doctest_namespace): + doctest_namespace['np'] = numpy + +which can then be used in your doctests directly:: + + # content of numpy.py + def arange(): + """ + >>> a = np.arange(10) + >>> len(a) + 10 + """ + pass + + +Output format +------------- + +.. versionadded:: 3.0 + +You can change the diff output format on failure for your doctests +by using one of standard doctest modules format in options +(see :data:`python:doctest.REPORT_UDIFF`, :data:`python:doctest.REPORT_CDIFF`, +:data:`python:doctest.REPORT_NDIFF`, :data:`python:doctest.REPORT_ONLY_FIRST_FAILURE`):: + + pytest --doctest-modules --doctest-report none + pytest --doctest-modules --doctest-report udiff + pytest --doctest-modules --doctest-report cdiff + pytest --doctest-modules --doctest-report ndiff + pytest --doctest-modules --doctest-report only_first_failure + + diff -Nru pytest-2.9.2/doc/en/example/costlysetup/conftest.py pytest-3.0.6/doc/en/example/costlysetup/conftest.py --- pytest-2.9.2/doc/en/example/costlysetup/conftest.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/example/costlysetup/conftest.py 2016-08-20 20:00:30.000000000 +0000 @@ -4,8 +4,8 @@ @pytest.fixture("session") def setup(request): setup = CostlySetup() - request.addfinalizer(setup.finalize) - return setup + yield setup + setup.finalize() class CostlySetup: def __init__(self): diff -Nru pytest-2.9.2/doc/en/example/layout1/setup.cfg pytest-3.0.6/doc/en/example/layout1/setup.cfg --- pytest-2.9.2/doc/en/example/layout1/setup.cfg 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/example/layout1/setup.cfg 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ -[pytest] -testfilepatterns = - ${topdir}/tests/unit/test_${basename} - ${topdir}/tests/functional/*.py diff -Nru pytest-2.9.2/doc/en/example/markers.rst pytest-3.0.6/doc/en/example/markers.rst --- pytest-2.9.2/doc/en/example/markers.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/example/markers.rst 2017-01-22 17:44:30.000000000 +0000 @@ -29,23 +29,23 @@ You can then restrict a test run to only run tests marked with ``webtest``:: - $ py.test -v -m webtest + $ pytest -v -m webtest ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5m cachedir: .cache rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 4 items test_server.py::test_send_http PASSED - ======= 3 tests deselected by "-m 'webtest'" ======== + ======= 3 tests deselected ======== ======= 1 passed, 3 deselected in 0.12 seconds ======== Or the inverse, running all tests except the webtest ones:: - $ py.test -v -m "not webtest" + $ pytest -v -m "not webtest" ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5m cachedir: .cache rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 4 items @@ -54,7 +54,7 @@ test_server.py::test_another PASSED test_server.py::TestClass::test_method PASSED - ======= 1 tests deselected by "-m 'not webtest'" ======== + ======= 1 tests deselected ======== ======= 3 passed, 1 deselected in 0.12 seconds ======== Selecting tests based on their node ID @@ -64,9 +64,9 @@ arguments to select only specified tests. This makes it easy to select tests based on their module, class, method, or function name:: - $ py.test -v test_server.py::TestClass::test_method + $ pytest -v test_server.py::TestClass::test_method ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5m cachedir: .cache rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 5 items @@ -77,9 +77,9 @@ You can also select on the class:: - $ py.test -v test_server.py::TestClass + $ pytest -v test_server.py::TestClass ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5m cachedir: .cache rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 4 items @@ -90,9 +90,9 @@ Or select multiple nodes:: - $ py.test -v test_server.py::TestClass test_server.py::test_send_http + $ pytest -v test_server.py::TestClass test_server.py::test_send_http ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5m cachedir: .cache rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 8 items @@ -115,8 +115,8 @@ ``module.py::function[param]``. Node IDs for failing tests are displayed in the test summary info - when running py.test with the ``-rf`` option. You can also - construct Node IDs from the output of ``py.test --collectonly``. + when running pytest with the ``-rf`` option. You can also + construct Node IDs from the output of ``pytest --collectonly``. Using ``-k expr`` to select tests based on their name ------------------------------------------------------- @@ -128,23 +128,23 @@ exact match on markers that ``-m`` provides. This makes it easy to select tests based on their names:: - $ py.test -v -k http # running with the above defined example module + $ pytest -v -k http # running with the above defined example module ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5m cachedir: .cache rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 4 items test_server.py::test_send_http PASSED - ======= 3 tests deselected by '-khttp' ======== + ======= 3 tests deselected ======== ======= 1 passed, 3 deselected in 0.12 seconds ======== And you can also run all tests except the ones that match the keyword:: - $ py.test -k "not send_http" -v + $ pytest -k "not send_http" -v ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5m cachedir: .cache rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 4 items @@ -153,14 +153,14 @@ test_server.py::test_another PASSED test_server.py::TestClass::test_method PASSED - ======= 1 tests deselected by '-knot send_http' ======== + ======= 1 tests deselected ======== ======= 3 passed, 1 deselected in 0.12 seconds ======== Or to select "http" and "quick" tests:: - $ py.test -k "http or quick" -v + $ pytest -k "http or quick" -v ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5m cachedir: .cache rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 4 items @@ -168,7 +168,7 @@ test_server.py::test_send_http PASSED test_server.py::test_something_quick PASSED - ======= 2 tests deselected by '-khttp or quick' ======== + ======= 2 tests deselected ======== ======= 2 passed, 2 deselected in 0.12 seconds ======== .. note:: @@ -198,14 +198,14 @@ You can ask which markers exist for your test suite - the list includes our just defined ``webtest`` markers:: - $ py.test --markers + $ pytest --markers @pytest.mark.webtest: mark a test as a webtest. @pytest.mark.skip(reason=None): skip the given test function with an optional reason. Example: skip(reason="no way of currently testing this") skips the test. @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. @@ -225,7 +225,7 @@ * there is one place in your test suite defining your markers - * asking for existing markers via ``py.test --markers`` gives good output + * asking for existing markers via ``pytest --markers`` gives good output * typos in function markers are treated as an error if you use the ``--strict`` option. Future versions of ``pytest`` are probably @@ -350,9 +350,9 @@ and an example invocations specifying a different environment than what the test needs:: - $ py.test -E stage2 + $ pytest -E stage2 ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR, inifile: collected 1 items @@ -362,9 +362,9 @@ and here is one that specifies exactly the environment needed:: - $ py.test -E stage1 + $ pytest -E stage1 ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR, inifile: collected 1 items @@ -374,14 +374,14 @@ The ``--markers`` option always gives you a list of available markers:: - $ py.test --markers + $ pytest --markers @pytest.mark.env(name): mark test to run only on named environment @pytest.mark.skip(reason=None): skip the given test function with an optional reason. Example: skip(reason="no way of currently testing this") skips the test. @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. @@ -427,7 +427,7 @@ Let's run this without capturing output and see what we get:: - $ py.test -q -s + $ pytest -q -s glob args=('function',) kwargs={'x': 3} glob args=('class',) kwargs={'x': 2} glob args=('module',) kwargs={'x': 1} @@ -450,7 +450,7 @@ 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 @@ def test_if_apple_is_evil(): pass - @pytest.mark.linux2 + @pytest.mark.linux def test_if_linux_works(): pass @@ -481,32 +481,32 @@ 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:: - $ py.test -rs # this option reports skip reasons + $ pytest -rs # this option reports skip reasons ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, 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:: - $ py.test -m linux2 + $ pytest -m linux ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR, inifile: collected 4 items - test_plat.py s + test_plat.py . - ======= 3 tests deselected by "-m 'linux2'" ======== - ======= 1 skipped, 3 deselected in 0.12 seconds ======== + ======= 3 tests deselected ======== + ======= 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. @@ -549,9 +549,9 @@ We can now use the ``-m option`` to select one set:: - $ py.test -m interface --tb=short + $ pytest -m interface --tb=short ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR, inifile: collected 4 items @@ -566,14 +566,14 @@ test_module.py:6: in test_interface_complex assert 0 E assert 0 - ======= 2 tests deselected by "-m 'interface'" ======== + ======= 2 tests deselected ======== ======= 2 failed, 2 deselected in 0.12 seconds ======== or to select both "event" and "interface" tests:: - $ py.test -m "interface or event" --tb=short + $ pytest -m "interface or event" --tb=short ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR, inifile: collected 4 items @@ -592,5 +592,5 @@ test_module.py:9: in test_event_simple assert 0 E assert 0 - ======= 1 tests deselected by "-m 'interface or event'" ======== + ======= 1 tests deselected ======== ======= 3 failed, 1 deselected in 0.12 seconds ======== diff -Nru pytest-2.9.2/doc/en/example/multipython.py pytest-3.0.6/doc/en/example/multipython.py --- pytest-2.9.2/doc/en/example/multipython.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/example/multipython.py 2016-08-27 09:59:07.000000000 +0000 @@ -6,7 +6,7 @@ import pytest import _pytest._code -pythonlist = ['python2.6', 'python2.7', 'python3.3'] +pythonlist = ['python2.6', 'python2.7', 'python3.4', 'python3.5'] @pytest.fixture(params=pythonlist) def python1(request, tmpdir): picklefile = tmpdir.join("data.pickle") diff -Nru pytest-2.9.2/doc/en/example/nonpython/conftest.py pytest-3.0.6/doc/en/example/nonpython/conftest.py --- pytest-2.9.2/doc/en/example/nonpython/conftest.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/example/nonpython/conftest.py 2016-08-27 09:59:07.000000000 +0000 @@ -10,7 +10,7 @@ def collect(self): import yaml # we need a yaml parser, e.g. PyYAML raw = yaml.safe_load(self.fspath.open()) - for name, spec in raw.items(): + for name, spec in sorted(raw.items()): yield YamlItem(name, self, spec) class YamlItem(pytest.Item): @@ -19,7 +19,7 @@ self.spec = spec def runtest(self): - for name, value in self.spec.items(): + for name, value in sorted(self.spec.items()): # some custom test execution (dumb example follows) if name != value: raise YamlException(self, name, value) diff -Nru pytest-2.9.2/doc/en/example/nonpython.rst pytest-3.0.6/doc/en/example/nonpython.rst --- pytest-2.9.2/doc/en/example/nonpython.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/example/nonpython.rst 2017-01-22 17:44:30.000000000 +0000 @@ -25,13 +25,13 @@ and if you installed `PyYAML`_ or a compatible YAML-parser you can now execute the test specification:: - nonpython $ py.test test_simple.yml + nonpython $ pytest test_simple.yml ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR/nonpython, inifile: collected 2 items - test_simple.yml .F + test_simple.yml F. ======= FAILURES ======== _______ usecase: hello ________ @@ -57,15 +57,15 @@ ``reportinfo()`` is used for representing the test location and is also consulted when reporting in ``verbose`` mode:: - nonpython $ py.test -v + nonpython $ pytest -v ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5m cachedir: .cache rootdir: $REGENDOC_TMPDIR/nonpython, inifile: collecting ... collected 2 items - test_simple.yml::ok PASSED test_simple.yml::hello FAILED + test_simple.yml::ok PASSED ======= FAILURES ======== _______ usecase: hello ________ @@ -79,13 +79,13 @@ While developing your custom test collection and execution it's also interesting to just look at the collection tree:: - nonpython $ py.test --collect-only + nonpython $ pytest --collect-only ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR/nonpython, inifile: collected 2 items - + ======= no tests ran in 0.12 seconds ======== diff -Nru pytest-2.9.2/doc/en/example/parametrize.rst pytest-3.0.6/doc/en/example/parametrize.rst --- pytest-2.9.2/doc/en/example/parametrize.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/example/parametrize.rst 2017-01-22 17:44:30.000000000 +0000 @@ -44,14 +44,14 @@ This means that we only run 2 tests if we do not pass ``--all``:: - $ py.test -q test_compute.py + $ pytest -q test_compute.py .. 2 passed in 0.12 seconds We run only two computations, so we see two dots. let's run the full monty:: - $ py.test -q --all + $ pytest -q --all ....F ======= FAILURES ======== _______ test_compute[4] ________ @@ -128,9 +128,9 @@ objects, they are still using the default pytest representation:: - $ py.test test_time.py --collect-only + $ pytest test_time.py --collect-only ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR, inifile: collected 6 items @@ -179,9 +179,9 @@ this is a fully self-contained example which you can run with:: - $ py.test test_scenarios.py + $ pytest test_scenarios.py ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR, inifile: collected 4 items @@ -192,9 +192,9 @@ If you just collect tests you'll also nicely see 'advanced' and 'basic' as variants for the test function:: - $ py.test --collect-only test_scenarios.py + $ pytest --collect-only test_scenarios.py ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR, inifile: collected 4 items @@ -257,9 +257,9 @@ Let's first see how it looks like at collection time:: - $ py.test test_backends.py --collect-only + $ pytest test_backends.py --collect-only ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR, inifile: collected 2 items @@ -270,7 +270,7 @@ And then when we run the test:: - $ py.test -q test_backends.py + $ pytest -q test_backends.py .F ======= FAILURES ======== _______ test_db_initialized[d2] ________ @@ -318,9 +318,9 @@ The result of this test will be successful:: - $ py.test test_indirect_list.py --collect-only + $ pytest test_indirect_list.py --collect-only ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR, inifile: collected 1 items @@ -346,7 +346,7 @@ def pytest_generate_tests(metafunc): # called once per each test function funcarglist = metafunc.cls.params[metafunc.function.__name__] - argnames = list(funcarglist[0]) + argnames = sorted(funcarglist[0]) metafunc.parametrize(argnames, [[funcargs[name] for name in argnames] for funcargs in funcarglist]) @@ -366,7 +366,7 @@ Our test generator looks up a class-level definition which specifies which argument sets to use for each test function. Let's run it:: - $ py.test -q + $ pytest -q F.. ======= FAILURES ======== _______ TestClass.test_equals[1-2] ________ @@ -396,9 +396,13 @@ Running it results in some skips if we don't have all the python interpreters installed and otherwise runs all combinations (5 interpreters times 5 interpreters times 3 objects to serialize/deserialize):: - . $ py.test -rs -q multipython.py - ........................... - 27 passed in 0.12 seconds + . $ pytest -rs -q multipython.py + sssssssssssssssssssssssssssssssssssssssssssss... + ======= short test summary info ======== + SKIP [15] $REGENDOC_TMPDIR/CWD/multipython.py:23: 'python2.6' not found + SKIP [15] $REGENDOC_TMPDIR/CWD/multipython.py:23: 'python3.4' not found + SKIP [15] $REGENDOC_TMPDIR/CWD/multipython.py:23: 'python2.7' not found + 3 passed, 45 skipped in 0.12 seconds Indirect parametrization of optional implementations/imports -------------------------------------------------------------------- @@ -443,9 +447,9 @@ If you run this with reporting for skips enabled:: - $ py.test -rs test_module.py + $ pytest -rs test_module.py ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR, inifile: collected 2 items diff -Nru pytest-2.9.2/doc/en/example/pythoncollection.py pytest-3.0.6/doc/en/example/pythoncollection.py --- pytest-2.9.2/doc/en/example/pythoncollection.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/example/pythoncollection.py 2016-08-20 20:00:30.000000000 +0000 @@ -1,5 +1,5 @@ -# run this with $ py.test --collect-only test_collectonly.py +# run this with $ pytest --collect-only test_collectonly.py # def test_function(): pass diff -Nru pytest-2.9.2/doc/en/example/pythoncollection.rst pytest-3.0.6/doc/en/example/pythoncollection.rst --- pytest-2.9.2/doc/en/example/pythoncollection.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/example/pythoncollection.rst 2017-01-22 17:44:30.000000000 +0000 @@ -40,12 +40,46 @@ ======= 5 passed in 0.02 seconds ======= +Keeping duplicate paths specified from command line +---------------------------------------------------- + +Default behavior of ``pytest`` is to ignore duplicate paths specified from the command line. +Example:: + + py.test path_a path_a + + ... + collected 1 item + ... + +Just collect tests once. + +To collect duplicate tests, use the ``--keep-duplicates`` option on the cli. +Example:: + + py.test --keep-duplicates path_a path_a + + ... + collected 2 items + ... + +As the collector just works on directories, if you specify twice a single test file, ``pytest`` will +still collect it twice, no matter if the ``--keep-duplicates`` is not specified. +Example:: + + py.test test_a.py test_a.py + + ... + collected 2 items + ... + + Changing directory recursion ----------------------------------------------------- -You can set the :confval:`norecursedirs` option in an ini-file, for example your ``setup.cfg`` in the project root directory:: +You can set the :confval:`norecursedirs` option in an ini-file, for example your ``pytest.ini`` in the project root directory:: - # content of setup.cfg + # content of pytest.ini [pytest] norecursedirs = .svn _build tmp* @@ -60,8 +94,9 @@ the :confval:`python_files`, :confval:`python_classes` and :confval:`python_functions` configuration options. Example:: - # content of setup.cfg - # can also be defined in in tox.ini or pytest.ini file + # content of pytest.ini + # 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 python_classes=Check @@ -80,10 +115,10 @@ then the test collection looks like this:: - $ py.test --collect-only + $ pytest --collect-only ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 - rootdir: $REGENDOC_TMPDIR, inifile: setup.cfg + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 + rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini collected 2 items @@ -107,7 +142,7 @@ their file system path and then running the test. For example if you have unittest2 installed you can type:: - py.test --pyargs unittest2.test.test_skipping -q + pytest --pyargs unittest2.test.test_skipping -q which would run the respective test module. Like with other options, through an ini-file and the :confval:`addopts` option you @@ -117,7 +152,7 @@ [pytest] addopts = --pyargs -Now a simple invocation of ``py.test NAME`` will check +Now a simple invocation of ``pytest NAME`` will check if NAME exists as an importable package/module and otherwise treat it as a filesystem path. @@ -126,9 +161,9 @@ You can always peek at the collection tree without running tests like this:: - . $ py.test --collect-only pythoncollection.py + . $ pytest --collect-only pythoncollection.py ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini collected 3 items @@ -180,7 +215,7 @@ then a pytest run on Python2 will find the one test and will leave out the setup.py file:: - #$ py.test --collect-only + #$ pytest --collect-only ====== test session starts ====== platform linux2 -- Python 2.7.10, pytest-2.9.1, py-1.4.31, pluggy-0.3.1 rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini @@ -193,9 +228,9 @@ If you run with a Python3 interpreter both the one test and the setup.py file will be left out:: - $ py.test --collect-only + $ pytest --collect-only ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini collected 0 items diff -Nru pytest-2.9.2/doc/en/example/reportingdemo.rst pytest-3.0.6/doc/en/example/reportingdemo.rst --- pytest-2.9.2/doc/en/example/reportingdemo.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/example/reportingdemo.rst 2017-01-22 17:44:30.000000000 +0000 @@ -7,13 +7,11 @@ Here is a nice run of several tens of failures and how ``pytest`` presents things (unfortunately not showing the nice colors here in the HTML that you -get on the terminal - we are working on that): +get on the terminal - we are working on that):: -.. code-block:: python - - assertion $ py.test failure_demo.py + assertion $ pytest failure_demo.py ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR/assertion, inifile: collected 42 items @@ -361,7 +359,7 @@ > int(s) E ValueError: invalid literal for int() with base 10: 'qwe' - <0-codegen $PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/python.py:1309>:1: ValueError + <0-codegen $PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/python.py:1207>:1: ValueError _______ TestRaises.test_raises_doesnt ________ self = @@ -427,7 +425,7 @@ def foo(): > assert 1 == 0 - E assert 1 == 0 + E AssertionError <2-codegen 'abc-123' $REGENDOC_TMPDIR/assertion/failure_demo.py:163>:2: AssertionError _______ TestMoreErrors.test_complex_error ________ @@ -482,8 +480,9 @@ s = "123" g = "456" > assert s.startswith(g) - E assert ('456') - E + where = '123'.startswith + E assert False + E + where False = ('456') + E + where = '123'.startswith failure_demo.py:189: AssertionError _______ TestMoreErrors.test_startswith_nested ________ @@ -496,10 +495,11 @@ def g(): return "456" > assert f().startswith(g()) - E assert ('456') - E + where = '123'.startswith - E + where '123' = .f at 0xdeadbeef>() - E + and '456' = .g at 0xdeadbeef>() + E assert False + E + where False = ('456') + E + where = '123'.startswith + E + where '123' = .f at 0xdeadbeef>() + E + and '456' = .g at 0xdeadbeef>() failure_demo.py:196: AssertionError _______ TestMoreErrors.test_global_func ________ @@ -508,8 +508,9 @@ def test_global_func(self): > assert isinstance(globf(42), float) - E assert isinstance(43, float) - E + where 43 = globf(42) + E assert False + E + where False = isinstance(43, float) + E + where 43 = globf(42) failure_demo.py:199: AssertionError _______ TestMoreErrors.test_instance ________ diff -Nru pytest-2.9.2/doc/en/example/simple.rst pytest-3.0.6/doc/en/example/simple.rst --- pytest-2.9.2/doc/en/example/simple.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/example/simple.rst 2017-01-22 17:44:30.000000000 +0000 @@ -1,5 +1,4 @@ -.. highlightlang:: python Basic patterns and examples ========================================================== @@ -10,7 +9,9 @@ .. regendoc:wipe Suppose we want to write a test that depends on a command line option. -Here is a basic pattern to achieve this:: +Here is a basic pattern to achieve this: + +.. code-block:: python # content of test_sample.py def test_answer(cmdopt): @@ -22,7 +23,9 @@ For this to work we need to add a command line option and -provide the ``cmdopt`` through a :ref:`fixture function `:: +provide the ``cmdopt`` through a :ref:`fixture function `: + +.. code-block:: python # content of conftest.py import pytest @@ -37,7 +40,7 @@ Let's run this without supplying our new option:: - $ py.test -q test_sample.py + $ pytest -q test_sample.py F ======= FAILURES ======== _______ test_answer ________ @@ -59,7 +62,7 @@ And now with supplying a command line option:: - $ py.test -q --cmdopt=type2 + $ pytest -q --cmdopt=type2 F ======= FAILURES ======== _______ test_answer ________ @@ -91,7 +94,9 @@ Through :confval:`addopts` you can statically add command line options for your project. You can also dynamically modify -the command line arguments before they get processed:: +the command line arguments before they get processed: + +.. code-block:: python # content of conftest.py import sys @@ -101,14 +106,14 @@ num = max(multiprocessing.cpu_count() / 2, 1) args[:] = ["-n", str(num)] + args -If you have the :ref:`xdist plugin ` installed +If you have the `xdist plugin `_ installed you will now always perform test runs using a number of subprocesses close to your CPU. Running in an empty directory with the above conftest.py:: - $ py.test + $ pytest ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR, inifile: collected 0 items @@ -122,7 +127,9 @@ .. regendoc:wipe Here is a ``conftest.py`` file adding a ``--runslow`` command -line option to control skipping of ``slow`` marked tests:: +line option to control skipping of ``slow`` marked tests: + +.. code-block:: python # content of conftest.py @@ -131,10 +138,11 @@ parser.addoption("--runslow", action="store_true", help="run slow tests") -We can now write a test module like this:: +We can now write a test module like this: - # content of test_module.py +.. code-block:: python + # content of test_module.py import pytest @@ -154,23 +162,23 @@ and when running it will see a skipped "slow" test:: - $ py.test -rs # "-rs" means report details on the little 's' + $ pytest -rs # "-rs" means report details on the little 's' ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR, inifile: collected 2 items test_module.py .s ======= short test summary info ======== - SKIP [1] test_module.py:14: need --runslow option to run + SKIP [1] test_module.py:13: need --runslow option to run ======= 1 passed, 1 skipped in 0.12 seconds ======== Or run it including the ``slow`` marked test:: - $ py.test --runslow + $ pytest --runslow ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR, inifile: collected 2 items @@ -187,7 +195,9 @@ use the ``pytest.fail`` marker to fail a test with a certain message. The test support function will not show up in the traceback if you set the ``__tracebackhide__`` option somewhere in the helper function. -Example:: +Example: + +.. code-block:: python # content of test_checkconfig.py import pytest @@ -204,7 +214,7 @@ unless the ``--full-trace`` command line option is specified. Let's run our little function:: - $ py.test -q test_checkconfig.py + $ pytest -q test_checkconfig.py F ======= FAILURES ======== _______ test_something ________ @@ -216,6 +226,30 @@ test_checkconfig.py:8: Failed 1 failed in 0.12 seconds +If you only want to hide certain exceptions, you can set ``__tracebackhide__`` +to a callable which gets the ``ExceptionInfo`` object. You can for example use +this to make sure unexpected exception types aren't hidden: + +.. code-block:: python + + import operator + import pytest + + class ConfigException(Exception): + pass + + def checkconfig(x): + __tracebackhide__ = operator.methodcaller('errisinstance', ConfigException) + if not hasattr(x, "config"): + raise ConfigException("not configured: %s" %(x,)) + + def test_something(): + checkconfig(42) + +This will avoid hiding the exception traceback on unrelated exceptions (i.e. +bugs in assertion helpers). + + Detect if running from within a pytest run -------------------------------------------------------------- @@ -224,7 +258,9 @@ Usually it is a bad idea to make application code behave differently if called from a test. But if you absolutely must find out if your application code is -running from a test you can do something like this:: +running from a test you can do something like this: + +.. code-block:: python # content of conftest.py @@ -235,7 +271,9 @@ def pytest_unconfigure(config): del sys._called_from_test -and then check for the ``sys._called_from_test`` flag:: +and then check for the ``sys._called_from_test`` flag: + +.. code-block:: python if hasattr(sys, '_called_from_test'): # called from within a test run @@ -251,7 +289,9 @@ .. regendoc:wipe -It's easy to present extra information in a ``pytest`` run:: +It's easy to present extra information in a ``pytest`` run: + +.. code-block:: python # content of conftest.py @@ -260,9 +300,9 @@ which will add the string to the test header accordingly:: - $ py.test + $ pytest ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 project deps: mylib-1.1 rootdir: $REGENDOC_TMPDIR, inifile: collected 0 items @@ -271,22 +311,23 @@ .. regendoc:wipe -You can also return a list of strings which will be considered as several -lines of information. You can of course also make the amount of reporting -information on e.g. the value of ``config.option.verbose`` so that -you present more information appropriately:: +It is also possible to return a list of strings which will be considered as several +lines of information. You may consider ``config.getoption('verbose')`` in order to +display more information if applicable: + +.. code-block:: python # content of conftest.py def pytest_report_header(config): - if config.option.verbose > 0: + if config.getoption('verbose') > 0: return ["info1: did you know that ...", "did you?"] which will add info only when run with "--v":: - $ py.test -v + $ pytest -v ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5m cachedir: .cache info1: did you know that ... did you? @@ -297,9 +338,9 @@ and nothing when run plainly:: - $ py.test + $ pytest ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR, inifile: collected 0 items @@ -313,10 +354,11 @@ .. versionadded: 2.2 If you have a slow running large test suite you might want to find -out which tests are the slowest. Let's make an artificial test suite:: +out which tests are the slowest. Let's make an artificial test suite: - # content of test_some_are_slow.py +.. code-block:: python + # content of test_some_are_slow.py import time def test_funcfast(): @@ -330,9 +372,9 @@ Now we can profile which test functions execute the slowest:: - $ py.test --durations=3 + $ pytest --durations=3 ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR, inifile: collected 3 items @@ -341,7 +383,7 @@ ======= slowest 3 test durations ======== 0.20s call test_some_are_slow.py::test_funcslow2 0.10s call test_some_are_slow.py::test_funcslow1 - 0.00s teardown test_some_are_slow.py::test_funcslow2 + 0.00s setup test_some_are_slow.py::test_funcfast ======= 3 passed in 0.12 seconds ======== incremental testing - test steps @@ -353,7 +395,9 @@ of test steps. If one step fails it makes no sense to execute further steps as they are all expected to fail anyway and their tracebacks add no insight. Here is a simple ``conftest.py`` file which introduces -an ``incremental`` marker which is to be used on classes:: +an ``incremental`` marker which is to be used on classes: + +.. code-block:: python # content of conftest.py @@ -372,7 +416,9 @@ pytest.xfail("previous test failed (%s)" %previousfailed.name) These two hook implementations work together to abort incremental-marked -tests in a class. Here is a test module example:: +tests in a class. Here is a test module example: + +.. code-block:: python # content of test_step.py @@ -392,9 +438,9 @@ If we run this:: - $ py.test -rx + $ pytest -rx ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR, inifile: collected 4 items @@ -430,7 +476,9 @@ 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 # content of a/conftest.py import pytest @@ -442,20 +490,26 @@ def db(): return DB() -and then a test module in that directory:: +and then a test module in that directory: + +.. code-block:: python # content of a/test_db.py def test_a1(db): assert 0, db # to show value -another test module:: +another test module: + +.. code-block:: python # content of a/test_db2.py def test_a2(db): assert 0, db # to show value and then a module in a sister directory which will not see -the ``db`` fixture:: +the ``db`` fixture: + +.. code-block:: python # content of b/test_error.py def test_root(db): # no db here, will error out @@ -463,9 +517,9 @@ We can run this:: - $ py.test + $ pytest ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR, inifile: collected 7 items @@ -478,9 +532,9 @@ _______ ERROR at setup of test_root ________ file $REGENDOC_TMPDIR/b/test_error.py, line 1 def test_root(db): # no db here, will error out - fixture 'db' not found - available fixtures: tmpdir_factory, cache, tmpdir, pytestconfig, recwarn, monkeypatch, capfd, record_xml_property, capsys - use 'py.test --fixtures [testpath]' for help on them. + E fixture 'db' not found + > available fixtures: cache, capfd, capsys, doctest_namespace, monkeypatch, pytestconfig, record_xml_property, recwarn, tmpdir, tmpdir_factory + > use 'pytest --fixtures [testpath]' for help on them. $REGENDOC_TMPDIR/b/test_error.py:1 ======= FAILURES ======== @@ -531,7 +585,9 @@ "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 # content of conftest.py @@ -557,7 +613,9 @@ f.write(rep.nodeid + extra + "\n") -if you then have failing tests:: +if you then have failing tests: + +.. code-block:: python # content of test_module.py def test_fail1(tmpdir): @@ -567,9 +625,9 @@ and run them:: - $ py.test test_module.py + $ pytest test_module.py ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR, inifile: collected 2 items @@ -606,7 +664,9 @@ .. regendoc:wipe If you want to make test result reports available in fixture finalizers -here is a little example implemented via a local plugin:: +here is a little example implemented via a local plugin: + +.. code-block:: python # content of conftest.py @@ -618,7 +678,7 @@ 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) @@ -626,18 +686,19 @@ @pytest.fixture def something(request): - def fin(): - # request.node is an "item" because we use the default - # "function" scope - if request.node.rep_setup.failed: - print ("setting up a test failed!", request.node.nodeid) - elif request.node.rep_setup.passed: - if request.node.rep_call.failed: - print ("executing test failed", request.node.nodeid) - request.addfinalizer(fin) + yield + # request.node is an "item" because we use the default + # "function" scope + if request.node.rep_setup.failed: + print ("setting up a test failed!", request.node.nodeid) + elif request.node.rep_setup.passed: + if request.node.rep_call.failed: + print ("executing test failed", request.node.nodeid) + +if you then have failing tests: -if you then have failing tests:: +.. code-block:: python # content of test_module.py @@ -658,9 +719,9 @@ and run it:: - $ py.test -s test_module.py + $ pytest -s test_module.py ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR, inifile: collected 3 items @@ -699,40 +760,29 @@ You'll see that the fixture finalizers could use the precise reporting information. -Integrating pytest runner and cx_freeze ------------------------------------------------------------ +Freezing pytest +--------------- If you freeze your application using a tool like -`cx_freeze `_ in order to distribute it -to your end-users, it is a good idea to also package your test runner and run -your tests using the frozen application. - -This way packaging errors such as dependencies not being -included into the executable can be detected early while also allowing you to -send test files to users so they can run them in their machines, which can be -invaluable to obtain more information about a hard to reproduce bug. - -Unfortunately ``cx_freeze`` can't discover them -automatically because of ``pytest``'s use of dynamic module loading, so you -must declare them explicitly by using ``pytest.freeze_includes()``:: - - # contents of setup.py - from cx_Freeze import setup, Executable - import pytest - - setup( - name="app_main", - executables=[Executable("app_main.py")], - options={"build_exe": - { - 'includes': pytest.freeze_includes()} - }, - # ... other options - ) +`PyInstaller `_ +in order to distribute it to your end-users, it is a good idea to also package +your test runner and run your tests using the frozen application. This way packaging +errors such as dependencies not being included into the executable can be detected early +while also allowing you to send test files to users so they can run them in their +machines, which can be useful to obtain more information about a hard to reproduce bug. + +Fortunately recent ``PyInstaller`` releases already have a custom hook +for pytest, but if you are using another tool to freeze executables +such as ``cx_freeze`` or ``py2exe``, you can use ``pytest.freeze_includes()`` +to obtain the full list of internal pytest modules. How to configure the tools +to find the internal modules varies from tool to tool, however. + +Instead of freezing the pytest runner as a separate executable, you can make +your frozen program work as the pytest runner by some clever +argument handling during program startup. This allows you to +have a single executable, which is usually more convenient. -If you don't want to ship a different executable just in order to run your tests, -you can make your program check for a certain flag and pass control -over to ``pytest`` instead. For example:: +.. code-block:: python # contents of app_main.py import sys @@ -745,7 +795,8 @@ # by your argument-parsing library of choice as usual ... -This makes it convenient to execute your tests from within your frozen -application, using standard ``py.test`` command-line options:: + +This allows you to execute tests using the frozen +application with standard ``pytest`` command-line options:: ./app_main --pytest --verbose --tb=long --junitxml=results.xml test-suite/ diff -Nru pytest-2.9.2/doc/en/example/special.rst pytest-3.0.6/doc/en/example/special.rst --- pytest-2.9.2/doc/en/example/special.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/example/special.rst 2017-01-20 17:15:59.000000000 +0000 @@ -59,7 +59,7 @@ If you run this without output capturing:: - $ py.test -q -s test_module.py + $ pytest -q -s test_module.py callattr_ahead_of_alltests called callme called! callme other called diff -Nru pytest-2.9.2/doc/en/faq.rst pytest-3.0.6/doc/en/faq.rst --- pytest-2.9.2/doc/en/faq.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/faq.rst 2017-01-20 17:15:23.000000000 +0000 @@ -66,14 +66,6 @@ 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. @@ -81,18 +73,17 @@ .. _`py/__init__.py`: http://bitbucket.org/hpk42/py-trunk/src/trunk/py/__init__.py -Why a ``py.test`` instead of a ``pytest`` command? -++++++++++++++++++++++++++++++++++++++++++++++++++ +Why can I use both ``pytest`` and ``py.test`` commands? ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -Some of the reasons are historic, others are practical. ``pytest`` -used to be part of the ``py`` package which provided several developer -utilities, all starting with ``py.``, thus providing nice -TAB-completion. If -you install ``pip install pycmd`` you get these tools from a separate -package. These days the command line tool could be called ``pytest`` -but since many people have gotten used to the old name and there -is another tool named "pytest" we just decided to stick with -``py.test`` for now. +pytest used to be part of the py package, which provided several developer +utilities, all starting with ``py.``, thus providing nice TAB-completion. +If you install ``pip install pycmd`` you get these tools from a separate +package. Once ``pytest`` became a separate package, the ``py.test`` name was +retained due to avoid a naming conflict with another tool. This conflict was +eventually resolved, and the ``pytest`` command was therefore introduced. In +future versions of pytest, we may deprecate and later remove the ``py.test`` +command to avoid perpetuating the confusion. pytest fixtures, parametrized tests ------------------------------------------------------- diff -Nru pytest-2.9.2/doc/en/feedback.rst pytest-3.0.6/doc/en/feedback.rst --- pytest-2.9.2/doc/en/feedback.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/feedback.rst 1970-01-01 00:00:00.000000000 +0000 @@ -1,8 +0,0 @@ - -What users say: - - `py.test is pretty much the best thing ever`_ (Alex Gaynor) - - -.. _`py.test is pretty much the best thing ever`_ (Alex Gaynor) - http://twitter.com/#!/alex_gaynor/status/22389410366 diff -Nru pytest-2.9.2/doc/en/fixture.rst pytest-3.0.6/doc/en/fixture.rst --- pytest-2.9.2/doc/en/fixture.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/fixture.rst 2017-01-22 17:44:30.000000000 +0000 @@ -11,7 +11,7 @@ .. _`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 @@ -34,11 +34,6 @@ prefer. You can also start out from existing :ref:`unittest.TestCase style ` or :ref:`nose based ` projects. -.. note:: - - pytest-2.4 introduced an additional :ref:`yield fixture mechanism - ` for easier context manager integration and more linear - writing of teardown code. .. _`funcargs`: .. _`funcarg mechanism`: @@ -73,9 +68,9 @@ will discover and call the :py:func:`@pytest.fixture <_pytest.python.fixture>` marked ``smtp`` fixture function. Running the test looks like this:: - $ py.test test_smtpsimple.py + $ pytest test_smtpsimple.py ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR, inifile: collected 1 items @@ -118,7 +113,7 @@ You can always issue:: - py.test --fixtures test_simplefactory.py + pytest --fixtures test_simplefactory.py to see available fixtures. @@ -191,9 +186,9 @@ We deliberately insert failing ``assert 0`` statements in order to inspect what is going on and can now run the tests:: - $ py.test test_module.py + $ pytest test_module.py ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR, inifile: collected 2 items @@ -247,9 +242,8 @@ ------------------------------------------------------------- pytest supports execution of fixture specific finalization code -when the fixture goes out of scope. By accepting a ``request`` object -into your fixture function you can call its ``request.addfinalizer`` one -or multiple times:: +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.:: # content of conftest.py @@ -259,18 +253,17 @@ @pytest.fixture(scope="module") def smtp(request): smtp = smtplib.SMTP("smtp.gmail.com") - def fin(): - print ("teardown smtp") - smtp.close() - 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. + yield smtp # provide the fixture value + print("teardown smtp") + smtp.close() + +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:: - $ py.test -s -q --tb=no + $ pytest -s -q --tb=no FFteardown smtp 2 failed in 0.12 seconds @@ -282,15 +275,55 @@ 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:: -Finalization/teardown with yield fixtures -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + # content of test_yield2.py -Another alternative to the *request.addfinalizer()* method is to use *yield -fixtures*. All the code after the *yield* statement serves as the teardown -code. See the :ref:`yield fixture documentation `. + import pytest + + @pytest.fixture + def passwd(): + with open("/etc/passwd") as f: + yield f.readlines() + + 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 ``with`` statement ends. +.. note:: + Prior to version 2.10, in order to use a ``yield`` statement to execute teardown code one + had to mark a fixture using the ``yield_fixture`` marker. From 2.10 onward, normal + fixtures can use ``yield`` directly so the ``yield_fixture`` decorator is no longer needed + and considered deprecated. + +.. note:: + As historical note, another way to write teardown code is + by accepting a ``request`` object into your fixture function and can call its + ``request.addfinalizer`` one or multiple times:: + + # content of conftest.py + + import smtplib + import pytest + + @pytest.fixture(scope="module") + def smtp(request): + smtp = smtplib.SMTP("smtp.gmail.com") + def fin(): + print ("teardown smtp") + smtp.close() + request.addfinalizer(fin) + return smtp # provide the fixture value + + 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. + .. _`request-context`: Fixtures can introspect the requesting test context @@ -309,21 +342,18 @@ def smtp(request): server = getattr(request.module, "smtpserver", "smtp.gmail.com") smtp = smtplib.SMTP(server) - - def fin(): - print ("finalizing %s (%s)" % (smtp, server)) - smtp.close() - request.addfinalizer(fin) - return smtp + yield smtp + print ("finalizing %s (%s)" % (smtp, server)) + smtp.close() We use the ``request.module`` attribute to optionally obtain an ``smtpserver`` attribute from the test module. If we just execute again, nothing much has changed:: - $ py.test -s -q --tb=no + $ pytest -s -q --tb=no FFfinalizing (smtp.gmail.com) - - 2 failed in 0.12 seconds + . + 2 failed, 1 passed in 0.12 seconds Let's quickly create another test module that actually sets the server URL in its module namespace:: @@ -337,7 +367,7 @@ Running it:: - $ py.test -qq --tb=short test_anothersmtp.py + $ pytest -qq --tb=short test_anothersmtp.py F ======= FAILURES ======== _______ test_showhelo ________ @@ -345,13 +375,15 @@ 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. .. _`fixture-parametrize`: -Parametrizing a fixture +Parametrizing fixtures ----------------------------------------------------------------- Fixture functions can be parametrized in which case they will be called @@ -374,11 +406,9 @@ params=["smtp.gmail.com", "mail.python.org"]) def smtp(request): smtp = smtplib.SMTP(request.param) - def fin(): - print ("finalizing %s" % smtp) - smtp.close() - request.addfinalizer(fin) - return smtp + yield smtp + print ("finalizing %s" % smtp) + smtp.close() The main change is the declaration of ``params`` with :py:func:`@pytest.fixture <_pytest.python.fixture>`, a list of values @@ -386,7 +416,7 @@ a value via ``request.param``. No test function code needs to change. So let's just do another run:: - $ py.test -q test_module.py + $ pytest -q test_module.py FFFF ======= FAILURES ======== _______ test_ehlo[smtp.gmail.com] ________ @@ -436,6 +466,8 @@ 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 @@ -486,11 +518,11 @@ Running the above tests results in the following test IDs being used:: - $ py.test --collect-only + $ pytest --collect-only ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR, inifile: - collected 10 items + collected 11 items @@ -504,6 +536,8 @@ + + ======= no tests ran in 0.12 seconds ======== @@ -537,9 +571,9 @@ Here we declare an ``app`` fixture which receives the previously defined ``smtp`` fixture and instantiates an ``App`` object with it. Let's run it:: - $ py.test -v test_appsetup.py + $ pytest -v test_appsetup.py ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5m cachedir: .cache rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 2 items @@ -575,7 +609,7 @@ before the next fixture instance is created. Among other things, this eases testing of applications which create and use global state. -The following example uses two parametrized funcargs, one of which is +The following example uses two parametrized fixture, one of which is scoped on a per-module basis, and all the functions perform ``print`` calls to show the setup/teardown flow:: @@ -586,19 +620,15 @@ def modarg(request): param = request.param print (" SETUP modarg %s" % param) - def fin(): - print (" TEARDOWN modarg %s" % param) - request.addfinalizer(fin) - return param + yield param + print (" TEARDOWN modarg %s" % param) @pytest.fixture(scope="function", params=[1,2]) def otherarg(request): param = request.param print (" SETUP otherarg %s" % param) - def fin(): - print (" TEARDOWN otherarg %s" % param) - request.addfinalizer(fin) - return param + yield param + print (" TEARDOWN otherarg %s" % param) def test_0(otherarg): print (" RUN test0 with otherarg %s" % otherarg) @@ -610,9 +640,9 @@ Let's run the tests in verbose mode and with looking at the print-output:: - $ py.test -v -s test_module.py + $ pytest -v -s test_module.py ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.5 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5m cachedir: .cache rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 8 items @@ -675,7 +705,7 @@ 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:: @@ -712,7 +742,7 @@ you specified a "cleandir" function argument to each of them. Let's run it to verify our fixture is activated and the tests pass:: - $ py.test -q + $ pytest -q .. 2 passed in 0.12 seconds @@ -777,7 +807,8 @@ @pytest.fixture(autouse=True) def transact(self, request, db): db.begin(request.function.__name__) - request.addfinalizer(db.rollback) + yield + db.rollback() def test_method1(self, db): assert db.intransaction == ["test_method1"] @@ -792,12 +823,16 @@ If we run it, we get two passing tests:: - $ py.test -q + $ pytest -q .. 2 passed in 0.12 seconds Here is how autouse fixtures work in other scopes: +- autouse fixtures obey the ``scope=`` keyword-argument: if an autouse fixture + has ``scope='session'`` it will only be run once, no matter where it is + defined. ``scope='class'`` means it will be run once per class, etc. + - if an autouse fixture is defined in a test module, all its test functions automatically use it. @@ -817,10 +852,11 @@ into a conftest.py file **without** using ``autouse``:: # content of conftest.py - @pytest.fixture() + @pytest.fixture def transact(self, request, db): db.begin() - request.addfinalizer(db.rollback) + yield + db.rollback() and then e.g. have a TestClass using it by declaring the need:: @@ -833,6 +869,7 @@ other test classes or functions in the module will not use it unless they also add a ``transact`` reference. + Shifting (visibility of) fixture functions ---------------------------------------------------- @@ -965,7 +1002,7 @@ @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 -Nru pytest-2.9.2/doc/en/funcarg_compare.rst pytest-3.0.6/doc/en/funcarg_compare.rst --- pytest-2.9.2/doc/en/funcarg_compare.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/funcarg_compare.rst 2017-01-20 17:15:32.000000000 +0000 @@ -1,3 +1,4 @@ +:orphan: .. _`funcargcompare`: @@ -96,7 +97,7 @@ ... # 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. @@ -172,17 +173,17 @@ during test execution and parametrization happens at collection time. It follows that pytest_configure/session/runtest_setup are often not -appropriate for implementing common fixture needs. Therefore, +appropriate for implementing common fixture needs. Therefore, pytest-2.3 introduces :ref:`autouse fixtures` which fully -integrate with the generic :ref:`fixture mechanism ` +integrate with the generic :ref:`fixture mechanism ` and obsolete many prior uses of pytest hooks. funcargs/fixture discovery now happens at collection time --------------------------------------------------------------------- -pytest-2.3 takes care to discover fixture/funcarg factories -at collection time. This is more efficient especially for large test suites. -Moreover, a call to "py.test --collect-only" should be able to in the future +Since pytest-2.3, discovery of fixture/funcarg factories are taken care of +at collection time. This is more efficient especially for large test suites. +Moreover, a call to "pytest --collect-only" should be able to in the future show a lot of setup-information and thus presents a nice method to get an overview of fixture management in your project. diff -Nru pytest-2.9.2/doc/en/genapi.py pytest-3.0.6/doc/en/genapi.py --- pytest-2.9.2/doc/en/genapi.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/genapi.py 2016-07-20 15:37:38.000000000 +0000 @@ -32,7 +32,7 @@ def pytest_funcarg__a(request): with Writer("request") as writer: - writer.docmethod(request.getfuncargvalue) + writer.docmethod(request.getfixturevalue) writer.docmethod(request.cached_setup) writer.docmethod(request.addfinalizer) writer.docmethod(request.applymarker) diff -Nru pytest-2.9.2/doc/en/_getdoctarget.py pytest-3.0.6/doc/en/_getdoctarget.py --- pytest-2.9.2/doc/en/_getdoctarget.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/_getdoctarget.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,16 +0,0 @@ -#!/usr/bin/env python - -import py - -def get_version_string(): - fn = py.path.local(__file__).join("..", "..", "..", - "_pytest", "__init__.py") - for line in fn.readlines(): - if "version" in line and not line.strip().startswith('#'): - return eval(line.split("=")[-1]) - -def get_minor_version_string(): - return ".".join(get_version_string().split(".")[:2]) - -if __name__ == "__main__": - print (get_minor_version_string()) diff -Nru pytest-2.9.2/doc/en/getting-started.rst pytest-3.0.6/doc/en/getting-started.rst --- pytest-2.9.2/doc/en/getting-started.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/getting-started.rst 2017-01-22 17:44:30.000000000 +0000 @@ -11,7 +11,7 @@ `colorama (Windows) `_, `argparse (py26) `_. -**documentation as PDF**: `download latest `_ +**documentation as PDF**: `download latest `_ .. _`getstarted`: .. _installation: @@ -19,17 +19,14 @@ Installation ---------------------------------------- -Installation options:: +Installation:: - pip install -U pytest # or - easy_install -U pytest + pip install -U pytest To check your installation has installed the correct version:: - $ py.test --version - This is pytest version 2.9.2, imported from $PYTHON_PREFIX/lib/python3.5/site-packages/pytest.py - -If you get an error checkout :ref:`installation issues`. + $ pytest --version + This is pytest version 3.0.6, imported from $PYTHON_PREFIX/lib/python3.5/site-packages/pytest.py .. _`simpletest`: @@ -47,9 +44,9 @@ That's it. You can execute the test function now:: - $ py.test + $ pytest ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR, inifile: collected 1 items @@ -102,7 +99,7 @@ Running it with, this time in "quiet" reporting mode:: - $ py.test -q test_sysexit.py + $ pytest -q test_sysexit.py . 1 passed in 0.12 seconds @@ -127,7 +124,7 @@ There is no need to subclass anything. We can simply run the module by passing its filename:: - $ py.test -q test_class.py + $ pytest -q test_class.py .F ======= FAILURES ======== _______ TestClass.test_two ________ @@ -137,7 +134,8 @@ def test_two(self): x = "hello" > assert hasattr(x, 'check') - E assert hasattr('hello', 'check') + E assert False + E + where False = hasattr('hello', 'check') test_class.py:8: AssertionError 1 failed, 1 passed in 0.12 seconds @@ -163,7 +161,7 @@ ``pytest`` will lookup and call a fixture factory to create the resource before performing the test function call. Let's just run it:: - $ py.test -q test_tmpdir.py + $ pytest -q test_tmpdir.py F ======= FAILURES ======== _______ test_needsfiles ________ @@ -185,7 +183,7 @@ You can find out what kind of builtin :ref:`fixtures` exist by typing:: - py.test --fixtures # shows builtin and custom fixtures + pytest --fixtures # shows builtin and custom fixtures Where to go next ------------------------------------- @@ -193,45 +191,8 @@ Here are a few suggestions where to go next: * :ref:`cmdline` for command line invocation examples -* :ref:`good practices ` for virtualenv, test layout, genscript support +* :ref:`good practices ` for virtualenv, test layout * :ref:`fixtures` for providing a functional baseline to your tests -* :ref:`apiref` for documentation and examples on using ``pytest`` * :ref:`plugins` managing and writing plugins -.. _`installation issues`: - -Known Installation issues ------------------------------- - -easy_install or pip not found? -++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -.. _`install pip`: http://www.pip-installer.org/en/latest/index.html - -`Install pip`_ for a state of the art python package installer. - -Install `setuptools`_ to get ``easy_install`` which allows to install -``.egg`` binary format packages in addition to source-based ones. - -py.test not found on Windows despite installation? -++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -.. _`Python for Windows`: http://www.imladris.com/Scripts/PythonForWindows.html - -- **Windows**: If "easy_install" or "py.test" are not found - you need to add the Python script path to your ``PATH``, see here: - `Python for Windows`_. You may alternatively use an `ActivePython install`_ - which does this for you automatically. - -.. _`ActivePython install`: http://www.activestate.com/activepython/downloads - -.. _`Jython does not create command line launchers`: http://bugs.jython.org/issue1491 - -- **Jython2.5.1 on Windows XP**: `Jython does not create command line launchers`_ - so ``py.test`` will not work correctly. You may install py.test on - CPython and type ``py.test --genscript=mytest`` and then use - ``jython mytest`` to run your tests with Jython using ``pytest``. - - :ref:`examples` for more complex examples - .. include:: links.inc diff -Nru pytest-2.9.2/doc/en/goodpractices.rst pytest-3.0.6/doc/en/goodpractices.rst --- pytest-2.9.2/doc/en/goodpractices.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/goodpractices.rst 2017-01-20 17:15:34.000000000 +0000 @@ -16,10 +16,12 @@ * 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`. @@ -72,17 +74,17 @@ - With inlined tests you might put ``__init__.py`` into test directories and make them installable as part of your application. - Using the ``py.test --pyargs mypkg`` invocation pytest will + 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:: - py.test tests/test_app.py # for external test dirs - py.test mypkg/test/test_app.py # for inlined test dirs - py.test mypkg # run tests in all below test directories - py.test # run all tests below current dir + 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 @@ -125,7 +127,7 @@ 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 @@ -145,7 +147,7 @@ If you frequently release code 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 @@ -193,9 +195,27 @@ this will execute your tests using ``pytest-runner``. As this is a standalone version of ``pytest`` no prior installation whatsoever is required for calling the test command. You can also pass additional -arguments to py.test such as your test directory or other +arguments to pytest such as your test directory or other options using ``--addopts``. +You can also specify other pytest-ini options in your ``setup.cfg`` file +by putting them into a ``[tool:pytest]`` section: + +.. code-block:: ini + + [tool:pytest] + addopts = --verbose + python_files = testing/*/*.py + + +.. note:: + Prior to 3.0, the supported section name was ``[pytest]``. Due to how + this may collide with some distutils commands, the recommended + section name for ``setup.cfg`` files is now ``[tool:pytest]``. + + Note that for ``pytest.ini`` and ``tox.ini`` files the section + name is ``[pytest]``. + Manual Integration ^^^^^^^^^^^^^^^^^^ @@ -211,16 +231,17 @@ class PyTest(TestCommand): - user_options = [('pytest-args=', 'a', "Arguments to pass to py.test")] + user_options = [('pytest-args=', 'a', "Arguments to pass to pytest")] def initialize_options(self): TestCommand.initialize_options(self) 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) @@ -240,41 +261,7 @@ python setup.py test -a "--durations=5" -is equivalent to running ``py.test --durations=5``. - - -.. _standalone: -.. _`genscript method`: - -(deprecated) Create a pytest standalone script ------------------------------------------------ - -.. deprecated:: 2.8 - -.. note:: - - ``genscript`` has been deprecated because: - - * It cannot support plugins, rendering its usefulness extremely limited; - * Tooling has become much better since ``genscript`` was introduced; - * It is possible to build a zipped ``pytest`` application without the - shortcomings above. - - There's no planned version in which this command will be removed - at the moment of this writing, but its use is discouraged for new - applications. - -If you are a maintainer or application developer and want people -who don't deal with python much to easily run tests you may generate -a standalone ``pytest`` script:: - - py.test --genscript=runtests.py - -This generates a ``runtests.py`` script which is a fully functional basic -``pytest`` script, running unchanged under Python2 and Python3. -You can tell people to download the script and then e.g. run it like this:: - - python runtests.py +is equivalent to running ``pytest --durations=5``. .. include:: links.inc diff -Nru pytest-2.9.2/doc/en/index.rst pytest-3.0.6/doc/en/index.rst --- pytest-2.9.2/doc/en/index.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/index.rst 2017-01-22 17:44:30.000000000 +0000 @@ -1,61 +1,90 @@ +:orphan: .. _features: pytest: helps you write better programs -============================================= +======================================= -**a mature full-featured Python testing tool** - - runs on Posix/Windows, Python 2.6-3.5, PyPy and (possibly still) Jython-2.5.1 - - free and open source software, distributed under the terms of the :ref:`MIT license ` - - **well tested** with more than a thousand tests against itself - - **strict backward compatibility policy** for safe pytest upgrades - - :ref:`comprehensive online ` and `PDF documentation `_ - - many :ref:`third party plugins ` and :ref:`builtin helpers `, - - used in :ref:`many small and large projects and organisations ` - - comes with many :ref:`tested examples ` - -**provides easy no-boilerplate testing** - - - makes it :ref:`easy to get started `, - has many :ref:`usage options ` - - :ref:`assert with the assert statement` - - helpful :ref:`traceback and failing assertion reporting ` - - :ref:`print debugging ` and :ref:`the - capturing of standard output during test execution ` - -**scales from simple unit to complex functional testing** - - - :ref:`modular parametrizeable fixtures ` (new in 2.3, - continuously improved) - - :ref:`parametrized test functions ` - - :ref:`mark` - - :ref:`skipping` (improved in 2.4) - - :ref:`distribute tests to multiple CPUs ` through :ref:`xdist plugin ` - - :ref:`continuously re-run failing tests ` - - :doc:`cache` - - flexible :ref:`Python test discovery` - -**integrates with other testing methods and tools**: - - - multi-paradigm: pytest can run ``nose``, ``unittest`` and - ``doctest`` style test suites, including running testcases made for - Django and trial - - supports :ref:`good integration practices ` - - supports extended :ref:`xUnit style setup ` - - supports domain-specific :ref:`non-python tests` - - supports generating `test coverage reports - `_ - - supports :pep:`8` compliant coding styles in tests - -**extensive plugin and customization system**: - - - all collection, reporting, running aspects are delegated to hook functions - - customizations can be per-directory, per-project or per PyPI released plugin - - it is easy to add command line options or customize existing behaviour - - :ref:`easy to write your own plugins ` +The ``pytest`` framework makes it easy to write small tests, yet +scales to support complex functional testing for applications and libraries. +An example of a simple test: -.. _`easy`: http://bruynooghe.blogspot.com/2009/12/skipping-slow-test-by-default-in-pytest.html +.. code-block:: python + # content of test_sample.py + def inc(x): + return x + 1 + def test_answer(): + assert inc(3) == 5 + + +To execute it:: + + $ pytest + ======= test session starts ======== + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 + rootdir: $REGENDOC_TMPDIR, inifile: + collected 1 items + + test_sample.py F + + ======= FAILURES ======== + _______ test_answer ________ + + def test_answer(): + > assert inc(3) == 5 + E assert 4 == 5 + E + where 4 = inc(3) + + test_sample.py:5: AssertionError + ======= 1 failed in 0.12 seconds ======== + +Due to ``pytest``'s detailed assertion introspection, only plain ``assert`` statements are used. +See :ref:`Getting Started ` for more examples. + + +Features +-------- + +- Detailed info on failing :ref:`assert statements ` (no need to remember ``self.assert*`` names); + +- :ref:`Auto-discovery ` of test modules and functions; + +- :ref:`Modular fixtures ` for managing small or parametrized long-lived test resources; + +- Can run :ref:`unittest ` (including trial) and :ref:`nose ` test suites out of the box; + +- Python2.6+, Python3.3+, PyPy-2.3, Jython-2.5 (untested); + +- Rich plugin architecture, with over 150+ :ref:`external plugins ` and thriving community; + + +Documentation +------------- + +Please see :ref:`Contents ` for full documentation, including installation, tutorials and PDF documents. + + +Bugs/Requests +------------- + +Please use the `GitHub issue tracker `_ to submit bugs or request features. + + +Changelog +--------- + +Consult the :ref:`Changelog ` page for fixes and enhancements of each version. + + +License +------- + +Copyright Holger Krekel and others, 2004-2016. + +Distributed under the terms of the `MIT`_ license, pytest is free and open source software. + +.. _`MIT`: https://github.com/pytest-dev/pytest/blob/master/LICENSE \ No newline at end of file diff -Nru pytest-2.9.2/doc/en/Makefile pytest-3.0.6/doc/en/Makefile --- pytest-2.9.2/doc/en/Makefile 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/Makefile 2017-01-19 09:44:52.000000000 +0000 @@ -19,10 +19,9 @@ --normalize "@/tmp/pytest-of-.*/pytest-\d+@PYTEST_TMPDIR@" \ - .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest - + help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @@ -36,22 +35,6 @@ clean: -rm -rf $(BUILDDIR)/* -SITETARGET=$(shell ./_getdoctarget.py) - -showtarget: - @echo $(SITETARGET) - -install: html - # for access talk to someone with login rights to - # pytest-dev@pytest.org to add your ssh key - rsync -avz _build/html/ pytest-dev@pytest.org:pytest.org/$(SITETARGET) - -installpdf: latexpdf - @scp $(BUILDDIR)/latex/pytest.pdf pytest-dev@pytest.org:pytest.org/$(SITETARGET) - -installall: clean install installpdf - @echo "done" - regen: PYTHONDONTWRITEBYTECODE=1 COLUMNS=76 regendoc --update *.rst */*.rst ${REGENDOC_ARGS} diff -Nru pytest-2.9.2/doc/en/monkeypatch.rst pytest-3.0.6/doc/en/monkeypatch.rst --- pytest-2.9.2/doc/en/monkeypatch.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/monkeypatch.rst 2017-01-20 17:15:34.000000000 +0000 @@ -6,7 +6,7 @@ Sometimes tests need to invoke functionality which depends on global settings or which invokes code which cannot be easily -tested such as network access. The ``monkeypatch`` function argument +tested such as network access. The ``monkeypatch`` fixture helps you to safely set/delete an attribute, dictionary item or environment variable or to modify ``sys.path`` for importing. See the `monkeypatch blog post`_ for some introduction material @@ -14,6 +14,7 @@ .. _`monkeypatch blog post`: http://tetamap.wordpress.com/2009/03/03/monkeypatching-in-unit-tests-done-right/ + Simple example: monkeypatching functions --------------------------------------------------- @@ -34,7 +35,7 @@ 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 @@ -53,28 +54,20 @@ will delete the method ``request.session.Session.request`` so that any attempts within tests to create http requests will fail. -example: setting an attribute on some class ------------------------------------------------------- - -If you need to patch out ``os.getcwd()`` to return an artificial -value:: - - def test_some_interaction(monkeypatch): - monkeypatch.setattr("os.getcwd", lambda: "/") -which is equivalent to the long form:: - - def test_some_interaction(monkeypatch): - import os - monkeypatch.setattr(os, "getcwd", lambda: "/") +.. 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 +------------------------------------------- -Method reference of the monkeypatch function argument ------------------------------------------------------ - -.. autoclass:: monkeypatch - :members: setattr, replace, delattr, setitem, delitem, setenv, delenv, syspath_prepend, chdir, undo +.. autoclass:: MonkeyPatch + :members: ``monkeypatch.setattr/delattr/delitem/delenv()`` all by default raise an Exception if the target does not exist. diff -Nru pytest-2.9.2/doc/en/nose.rst pytest-3.0.6/doc/en/nose.rst --- pytest-2.9.2/doc/en/nose.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/nose.rst 2017-01-20 17:15:34.000000000 +0000 @@ -1,3 +1,5 @@ +.. _`noseintegration`: + Running tests written for nose ======================================= @@ -13,7 +15,7 @@ After :ref:`installation` type:: python setup.py develop # make sure tests can import our package - py.test # instead of 'nosetests' + pytest # instead of 'nosetests' and you should be able to run your nose style tests and make use of pytest's capabilities. @@ -24,7 +26,7 @@ * setup and teardown at module/class/method level * SkipTest exceptions and markers * setup/teardown decorators -* yield-based tests and their setup +* ``yield``-based tests and their setup * ``__test__`` attribute on modules/classes/functions * general usage of nose utilities @@ -51,5 +53,12 @@ - nose-style doctests are not collected and executed correctly, also doctest fixtures don't work. -- no nose-configuration is recognized +- no nose-configuration is recognized. + +- ``yield``-based methods don't support ``setup`` properly because + the ``setup`` method is always called in the same class instance. + There are no plans to fix this currently because ``yield``-tests + are deprecated in pytest 3.0, with ``pytest.mark.parametrize`` + being the recommended alternative. + diff -Nru pytest-2.9.2/doc/en/overview.rst pytest-3.0.6/doc/en/overview.rst --- pytest-2.9.2/doc/en/overview.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/overview.rst 1970-01-01 00:00:00.000000000 +0000 @@ -1,13 +0,0 @@ -================================================== -Getting started basics -================================================== - -.. toctree:: - :maxdepth: 2 - - getting-started - usage - goodpractices - projects - faq - diff -Nru pytest-2.9.2/doc/en/parametrize.rst pytest-3.0.6/doc/en/parametrize.rst --- pytest-2.9.2/doc/en/parametrize.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/parametrize.rst 2017-01-22 17:44:30.000000000 +0000 @@ -53,9 +53,9 @@ tuples so that the ``test_eval`` function will run three times using them in turn:: - $ py.test + $ pytest ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR, inifile: collected 3 items @@ -101,9 +101,9 @@ Let's run this:: - $ py.test + $ pytest ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR, inifile: collected 3 items @@ -171,13 +171,13 @@ If we now pass two stringinput values, our test will run twice:: - $ py.test -q --stringinput="hello" --stringinput="world" test_strings.py + $ pytest -q --stringinput="hello" --stringinput="world" test_strings.py .. 2 passed in 0.12 seconds Let's also run with a stringinput that will lead to a failing test:: - $ py.test -q --stringinput="!" test_strings.py + $ pytest -q --stringinput="!" test_strings.py F ======= FAILURES ======== _______ test_valid_string[!] ________ @@ -186,8 +186,9 @@ def test_valid_string(stringinput): > assert stringinput.isalpha() - E assert () - E + where = '!'.isalpha + E assert False + E + where False = () + E + where = '!'.isalpha test_strings.py:3: AssertionError 1 failed in 0.12 seconds @@ -198,7 +199,7 @@ ``metafunc.parametrize()`` will be called with an empty parameter list:: - $ py.test -q -rs test_strings.py + $ pytest -q -rs test_strings.py s ======= short test summary info ======== SKIP [1] test_strings.py:1: got empty parameter set ['stringinput'], function test_valid_string at $REGENDOC_TMPDIR/test_strings.py:1 @@ -214,6 +215,4 @@ .. currentmodule:: _pytest.python .. autoclass:: Metafunc - - .. automethod:: Metafunc.parametrize - .. automethod:: Metafunc.addcall(funcargs=None,id=_notexists,param=_notexists) + :members: diff -Nru pytest-2.9.2/doc/en/parametrize.rst.orig pytest-3.0.6/doc/en/parametrize.rst.orig --- pytest-2.9.2/doc/en/parametrize.rst.orig 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/doc/en/parametrize.rst.orig 2017-01-19 09:27:41.000000000 +0000 @@ -0,0 +1,238 @@ + +.. _`test generators`: +.. _`parametrizing-tests`: +.. _`parametrized test functions`: +.. _`parametrize`: + +.. _`parametrize-basics`: + +Parametrizing fixtures and test functions +========================================================================== + +pytest supports test parametrization in several well-integrated ways: + +- :py:func:`pytest.fixture` allows to define :ref:`parametrization + at the level of fixture functions `. + +* `@pytest.mark.parametrize`_ allows to define parametrization at the + function or class level, provides multiple argument/fixture sets + for a particular test function or class. + +* `pytest_generate_tests`_ enables implementing your own custom + dynamic parametrization scheme or extensions. + +.. _parametrizemark: +.. _`@pytest.mark.parametrize`: + + +``@pytest.mark.parametrize``: parametrizing test functions +--------------------------------------------------------------------- + +.. regendoc: wipe + +.. versionadded:: 2.2 +.. versionchanged:: 2.4 + Several improvements. + +The builtin ``pytest.mark.parametrize`` decorator enables +parametrization of arguments for a test function. Here is a typical example +of a test function that implements checking that a certain input leads +to an expected output:: + + # content of test_expectation.py + import pytest + @pytest.mark.parametrize("test_input,expected", [ + ("3+5", 8), + ("2+4", 6), + ("6*9", 42), + ]) + def test_eval(test_input, expected): + assert eval(test_input) == expected + +Here, the ``@parametrize`` decorator defines three different ``(test_input,expected)`` +tuples so that the ``test_eval`` function will run three times using +them in turn:: + + $ pytest + ======= test session starts ======== + platform linux -- Python 3.5.2, pytest-3.0.3, py-1.4.31, pluggy-0.4.0 + rootdir: $REGENDOC_TMPDIR, inifile: + collected 3 items + + test_expectation.py ..F + + ======= FAILURES ======== + _______ test_eval[6*9-42] ________ + + test_input = '6*9', expected = 42 + + @pytest.mark.parametrize("test_input,expected", [ + ("3+5", 8), + ("2+4", 6), + ("6*9", 42), + ]) + def test_eval(test_input, expected): + > assert eval(test_input) == expected + E assert 54 == 42 + E + where 54 = eval('6*9') + + test_expectation.py:8: AssertionError + ======= 1 failed, 2 passed in 0.12 seconds ======== + +As designed in this example, only one pair of input/output values fails +the simple test function. And as usual with test function arguments, +you can see the ``input`` and ``output`` values in the traceback. + +Note that you could also use the parametrize marker on a class or a module +(see :ref:`mark`) which would invoke several functions with the argument sets. + +It is also possible to mark individual test instances within parametrize, +for example with the builtin ``mark.xfail``:: + + # content of test_expectation.py + import pytest + @pytest.mark.parametrize("test_input,expected", [ + ("3+5", 8), + ("2+4", 6), +<<<<<<< HEAD + pytest.mark.xfail(("6*9", 42)), +======= + pytest.param("6*9", 42, + marks=pytest.mark.xfail), +>>>>>>> pytest.param: update docs from marked + ]) + def test_eval(test_input, expected): + assert eval(test_input) == expected + +.. note:: + + prior to version 3.1 the supported mechanism for marking values + used the syntax:: + + import pytest + @pytest.mark.parametrize("test_input,expected", [ + ("3+5", 8), + ("2+4", 6), + pytest.mark.xfail(("6*9", 42),), + ]) + def test_eval(test_input, expected): + assert eval(test_input) == expected + + +Let's run this:: + + $ pytest + ======= test session starts ======== + platform linux -- Python 3.5.2, pytest-3.0.3, py-1.4.31, pluggy-0.4.0 + rootdir: $REGENDOC_TMPDIR, inifile: + collected 3 items + + test_expectation.py ..x + + ======= 2 passed, 1 xfailed in 0.12 seconds ======== + +The one parameter set which caused a failure previously now +shows up as an "xfailed (expected to fail)" test. + +To get all combinations of multiple parametrized arguments you can stack +``parametrize`` decorators:: + + import pytest + @pytest.mark.parametrize("x", [0, 1]) + @pytest.mark.parametrize("y", [2, 3]) + def test_foo(x, y): + pass + +This will run the test with the arguments set to x=0/y=2, x=0/y=3, x=1/y=2 and +x=1/y=3. + +.. note:: + + In versions prior to 2.4 one needed to specify the argument + names as a tuple. This remains valid but the simpler ``"name1,name2,..."`` + comma-separated-string syntax is now advertised first because + it's easier to write and produces less line noise. + +.. _`pytest_generate_tests`: + +Basic ``pytest_generate_tests`` example +--------------------------------------------- + +Sometimes you may want to implement your own parametrization scheme +or implement some dynamism for determining the parameters or scope +of a fixture. For this, you can use the ``pytest_generate_tests`` hook +which is called when collecting a test function. Through the passed in +``metafunc`` object you can inspect the requesting test context and, most +importantly, you can call ``metafunc.parametrize()`` to cause +parametrization. + +For example, let's say we want to run a test taking string inputs which +we want to set via a new ``pytest`` command line option. Let's first write +a simple test accepting a ``stringinput`` fixture function argument:: + + # content of test_strings.py + + def test_valid_string(stringinput): + assert stringinput.isalpha() + +Now we add a ``conftest.py`` file containing the addition of a +command line option and the parametrization of our test function:: + + # content of conftest.py + + def pytest_addoption(parser): + parser.addoption("--stringinput", action="append", default=[], + help="list of stringinputs to pass to test functions") + + def pytest_generate_tests(metafunc): + if 'stringinput' in metafunc.fixturenames: + metafunc.parametrize("stringinput", + metafunc.config.option.stringinput) + +If we now pass two stringinput values, our test will run twice:: + + $ pytest -q --stringinput="hello" --stringinput="world" test_strings.py + .. + 2 passed in 0.12 seconds + +Let's also run with a stringinput that will lead to a failing test:: + + $ pytest -q --stringinput="!" test_strings.py + F + ======= FAILURES ======== + _______ test_valid_string[!] ________ + + stringinput = '!' + + def test_valid_string(stringinput): + > assert stringinput.isalpha() + E assert False + E + where False = () + E + where = '!'.isalpha + + test_strings.py:3: AssertionError + 1 failed in 0.12 seconds + +As expected our test function fails. + +If you don't specify a stringinput it will be skipped because +``metafunc.parametrize()`` will be called with an empty parameter +list:: + + $ pytest -q -rs test_strings.py + s + ======= short test summary info ======== + SKIP [1] test_strings.py:1: got empty parameter set ['stringinput'], function test_valid_string at $REGENDOC_TMPDIR/test_strings.py:1 + 1 skipped in 0.12 seconds + +For further examples, you might want to look at :ref:`more +parametrization examples `. + +.. _`metafunc object`: + +The **metafunc** object +------------------------------------------- + +.. currentmodule:: _pytest.python +.. autoclass:: Metafunc + :members: diff -Nru pytest-2.9.2/doc/en/plugins.rst pytest-3.0.6/doc/en/plugins.rst --- pytest-2.9.2/doc/en/plugins.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/plugins.rst 2017-01-20 17:15:36.000000000 +0000 @@ -37,7 +37,7 @@ to distribute tests to CPUs and remote hosts, to run in boxed mode which allows to survive segmentation faults, to run in looponfailing mode, automatically re-running failing tests - on file changes, see also :ref:`xdist` + on file changes. * `pytest-instafail `_: to report failures while the test run is happening. @@ -59,7 +59,7 @@ a plugin to run javascript unittests in live browsers. To see a complete list of all plugins with their latest testing -status against different py.test and Python versions, please visit +status against different pytest and Python versions, please visit `plugincompat `_. You may also discover more plugins through a `pytest- pypi.python.org search`_. @@ -90,7 +90,7 @@ If you want to find out which plugins are active in your environment you can type:: - py.test --trace-config + pytest --trace-config and will get an extended test header which shows activated plugins and their names. It will also print local plugins aka @@ -103,7 +103,7 @@ You can prevent plugins from loading or unregister them:: - py.test -p no:NAME + pytest -p no:NAME This means that any subsequent try to activate/load the named plugin will not work. @@ -138,14 +138,13 @@ _pytest.capture _pytest.config _pytest.doctest - _pytest.genscript _pytest.helpconfig _pytest.junitxml _pytest.mark _pytest.monkeypatch _pytest.nose _pytest.pastebin - _pytest.pdb + _pytest.debugging _pytest.pytester _pytest.python _pytest.recwarn diff -Nru pytest-2.9.2/doc/en/projects.rst pytest-3.0.6/doc/en/projects.rst --- pytest-2.9.2/doc/en/projects.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/projects.rst 2017-01-20 17:15:36.000000000 +0000 @@ -58,7 +58,7 @@ * `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 -Nru pytest-2.9.2/doc/en/proposals/parametrize_with_fixtures.rst pytest-3.0.6/doc/en/proposals/parametrize_with_fixtures.rst --- pytest-2.9.2/doc/en/proposals/parametrize_with_fixtures.rst 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/doc/en/proposals/parametrize_with_fixtures.rst 2017-01-20 17:15:59.000000000 +0000 @@ -0,0 +1,150 @@ +:orphan: + +========================= +Parametrize with fixtures +========================= + +Problem +------- + +As a user I have functional tests that I would like to run against various +scenarios. + +In this particular example we want to generate a new project based on a +cookiecutter template. We want to test default values but also data that +emulates user input. + +- use default values + +- emulate user input + + - specify 'author' + + - specify 'project_slug' + + - specify 'author' and 'project_slug' + +This is how a functional test could look like: + +.. code-block:: python + + import pytest + + @pytest.fixture + def default_context(): + return {'extra_context': {}} + + + @pytest.fixture(params=[ + {'author': 'alice'}, + {'project_slug': 'helloworld'}, + {'author': 'bob', 'project_slug': 'foobar'}, + ]) + def extra_context(request): + return {'extra_context': request.param} + + + @pytest.fixture(params=['default', 'extra']) + def context(request): + if request.param == 'default': + return request.getfuncargvalue('default_context') + else: + return request.getfuncargvalue('extra_context') + + + def test_generate_project(cookies, context): + """Call the cookiecutter API to generate a new project from a + template. + """ + result = cookies.bake(extra_context=context) + + assert result.exit_code == 0 + assert result.exception is None + assert result.project.isdir() + + +Issues +------ + +* By using ``request.getfuncargvalue()`` we rely on actual fixture function + execution to know what fixtures are involved, due to it's dynamic nature +* More importantly, ``request.getfuncargvalue()`` cannot be combined with + parametrized fixtures, such as ``extra_context`` +* This is very inconvenient if you wish to extend an existing test suite by + certain parameters for fixtures that are already used by tests + +pytest version 3.0 reports an error if you try to run above code:: + + Failed: The requested fixture has no parameter defined for the current + test. + + Requested fixture 'extra_context' + + +Proposed solution +----------------- + +A new function that can be used in modules can be used to dynamically define +fixtures from existing ones. + +.. code-block:: python + + pytest.define_combined_fixture( + name='context', + fixtures=['default_context', 'extra_context'], + ) + +The new fixture ``context`` inherits the scope from the used fixtures and yield +the following values. + +- ``{}`` + +- ``{'author': 'alice'}`` + +- ``{'project_slug': 'helloworld'}`` + +- ``{'author': 'bob', 'project_slug': 'foobar'}`` + +Alternative approach +-------------------- + +A new helper function named ``fixture_request`` tells pytest to yield all +parameters of a fixture. + +.. code-block:: python + + @pytest.fixture(params=[ + pytest.fixture_request('default_context'), + pytest.fixture_request('extra_context'), + ]) + def context(request): + """Returns all values for ``default_context``, one-by-one before it + does the same for ``extra_context``. + + request.param: + - {} + - {'author': 'alice'} + - {'project_slug': 'helloworld'} + - {'author': 'bob', 'project_slug': 'foobar'} + """ + return request.param + +The same helper can be used in combination with ``pytest.mark.parametrize``. + +.. code-block:: python + + + @pytest.mark.parametrize( + 'context, expected_response_code', + [ + (pytest.fixture_request('default_context'), 0), + (pytest.fixture_request('extra_context'), 0), + ], + ) + def test_generate_project(cookies, context, exit_code): + """Call the cookiecutter API to generate a new project from a + template. + """ + result = cookies.bake(extra_context=context) + + assert result.exit_code == exit_code diff -Nru pytest-2.9.2/doc/en/recwarn.rst pytest-3.0.6/doc/en/recwarn.rst --- pytest-2.9.2/doc/en/recwarn.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/recwarn.rst 2017-01-20 17:15:36.000000000 +0000 @@ -1,7 +1,12 @@ +.. _`asserting warnings`: + +.. _assertwarnings: Asserting Warnings ===================================================== +.. _`asserting warnings with the warns function`: + .. _warns: Asserting warnings with the warns function @@ -45,6 +50,8 @@ ``DeprecationWarning`` and ``PendingDeprecationWarning`` are treated differently; see :ref:`ensuring_function_triggers`. +.. _`recording warnings`: + .. _recwarn: Recording warnings @@ -95,6 +102,8 @@ ``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 -Nru pytest-2.9.2/doc/en/skipping.rst pytest-3.0.6/doc/en/skipping.rst --- pytest-2.9.2/doc/en/skipping.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/skipping.rst 2017-01-22 17:44:30.000000000 +0000 @@ -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 @@ -19,7 +19,7 @@ cluttering the output. You can use the ``-r`` option to see details corresponding to the "short" letters shown in the test progress:: - py.test -rxs # show extra info on skips and xfails + pytest -rxs # show extra info on skips and xfails (See :ref:`how to change command line options defaults`) @@ -222,9 +222,9 @@ Running it with the report-on-xfail option gives this output:: - example $ py.test -rx xfail_demo.py + example $ pytest -rx xfail_demo.py ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR/example, inifile: collected 7 items @@ -293,6 +293,20 @@ # 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 -------------------------------------------------- @@ -368,6 +382,30 @@ .. note:: You cannot use ``pytest.config.getvalue()`` in code - imported before py.test's argument parsing takes place. For example, + 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 -Nru pytest-2.9.2/doc/en/status.rst pytest-3.0.6/doc/en/status.rst --- pytest-2.9.2/doc/en/status.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/status.rst 1970-01-01 00:00:00.000000000 +0000 @@ -1,5 +0,0 @@ -pytest development status -================================ - -https://travis-ci.org/pytest-dev/pytest - diff -Nru pytest-2.9.2/doc/en/talks.rst pytest-3.0.6/doc/en/talks.rst --- pytest-2.9.2/doc/en/talks.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/talks.rst 2017-01-20 17:15:37.000000000 +0000 @@ -4,15 +4,15 @@ .. sidebar:: Next Open Trainings - `professional testing with pytest and tox `_, 27-29th June 2016, Freiburg, Germany + `pytest workshop `_, 8th December 2016, Bern, Switzerland .. _`funcargs`: funcargs.html Talks and blog postings --------------------------------------------- -.. _`tutorial1 repository`: http://bitbucket.org/pytest-dev/pytest-tutorial1/ -.. _`pycon 2010 tutorial PDF`: http://bitbucket.org/pytest-dev/pytest-tutorial1/raw/tip/pytest-basic.pdf +- `Pythonic testing, Igor Starikov (Russian, PyNsk, November 2016) + `_. - `pytest - Rapid Simple Testing, Florian Bruhin, Swiss Python Summit 2016 `_. @@ -52,12 +52,14 @@ - `pytest introduction from Brian Okken (January 2013) `_ -- `monkey patching done right`_ (blog post, consult `monkeypatch - plugin`_ for up-to-date API) +- pycon australia 2012 pytest talk from Brianna Laugher (`video `_, `slides `_, `code `_) +- `pycon 2012 US talk video from Holger Krekel `_ + +- `monkey patching done right`_ (blog post, consult `monkeypatch plugin`_ for up-to-date API) Test parametrization: -- `generating parametrized tests with funcargs`_ (uses deprecated ``addcall()`` API. +- `generating parametrized tests with fixtures`_. - `test generators and cached setup`_ - `parametrizing tests, generalized`_ (blog post) - `putting test-hooks into local or global plugins`_ (blog post) @@ -78,39 +80,17 @@ - `many examples in the docs for plugins`_ .. _`skipping slow tests by default in pytest`: http://bruynooghe.blogspot.com/2009/12/skipping-slow-test-by-default-in-pytest.html -.. _`many examples in the docs for plugins`: plugin/index.html -.. _`monkeypatch plugin`: plugin/monkeypatch.html -.. _`application setup in test functions with funcargs`: funcargs.html#appsetup +.. _`many examples in the docs for plugins`: plugins.html +.. _`monkeypatch plugin`: monkeypatch.html +.. _`application setup in test functions with fixtures`: fixture.html#interdependent-fixtures .. _`simultaneously test your code on all platforms`: http://tetamap.wordpress.com/2009/03/23/new-simultanously-test-your-code-on-all-platforms/ .. _`monkey patching done right`: http://tetamap.wordpress.com/2009/03/03/monkeypatching-in-unit-tests-done-right/ .. _`putting test-hooks into local or global plugins`: http://tetamap.wordpress.com/2009/05/14/putting-test-hooks-into-local-and-global-plugins/ .. _`parametrizing tests, generalized`: http://tetamap.wordpress.com/2009/05/13/parametrizing-python-tests-generalized/ -.. _`generating parametrized tests with funcargs`: funcargs.html#test-generators +.. _`generating parametrized tests with fixtures`: parametrize.html#test-generators .. _`test generators and cached setup`: http://bruynooghe.blogspot.com/2010/06/pytest-test-generators-and-cached-setup.html -Older conference talks and tutorials ----------------------------------------- - -- `pycon australia 2012 pytest talk from Brianna Laugher - `_ (`video `_, `slides `_, `code `_) -- `pycon 2012 US talk video from Holger Krekel `_ -- `pycon 2010 tutorial PDF`_ and `tutorial1 repository`_ - -- `ep2009-rapidtesting.pdf`_ tutorial slides (July 2009): - - - testing terminology - - basic pytest usage, file system layout - - test function arguments (funcargs_) and test fixtures - - existing plugins - - distributed testing -- `ep2009-pytest.pdf`_ 60 minute pytest talk, highlighting unique features and a roadmap (July 2009) -- `pycon2009-pytest-introduction.zip`_ slides and files, extended version of pytest basic introduction, discusses more options, also introduces old-style xUnit setup, looponfailing and other features. -- `pycon2009-pytest-advanced.pdf`_ contain a slightly older version of funcargs and distributed testing, compared to the EuroPython 2009 slides. -.. _`ep2009-rapidtesting.pdf`: http://codespeak.net/download/py/ep2009-rapidtesting.pdf -.. _`ep2009-pytest.pdf`: http://codespeak.net/download/py/ep2009-pytest.pdf -.. _`pycon2009-pytest-introduction.zip`: http://codespeak.net/download/py/pycon2009-pytest-introduction.zip -.. _`pycon2009-pytest-advanced.pdf`: http://codespeak.net/download/py/pycon2009-pytest-advanced.pdf diff -Nru pytest-2.9.2/doc/en/_templates/layout.html pytest-3.0.6/doc/en/_templates/layout.html --- pytest-2.9.2/doc/en/_templates/layout.html 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/_templates/layout.html 2016-07-20 15:37:38.000000000 +0000 @@ -1,19 +1,5 @@ {% extends "!layout.html" %} {% block header %} -
-

- Want to help improve pytest? Please - - contribute to - - or - - join - - our upcoming sprint in June 2016! - -

-
{{super()}} {% endblock %} {% block footer %} diff -Nru pytest-2.9.2/doc/en/_templates/links.html pytest-3.0.6/doc/en/_templates/links.html --- pytest-2.9.2/doc/en/_templates/links.html 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/_templates/links.html 2017-01-19 09:44:52.000000000 +0000 @@ -1,16 +1,11 @@

Useful Links

diff -Nru pytest-2.9.2/doc/en/test/attic.rst pytest-3.0.6/doc/en/test/attic.rst --- pytest-2.9.2/doc/en/test/attic.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/test/attic.rst 2017-01-20 17:15:59.000000000 +0000 @@ -21,7 +21,7 @@ first. There is a flag that helps you debugging your conftest.py configurations:: - py.test --trace-config + pytest --trace-config customizing the collecting and running process diff -Nru pytest-2.9.2/doc/en/test/mission.rst pytest-3.0.6/doc/en/test/mission.rst --- pytest-2.9.2/doc/en/test/mission.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/test/mission.rst 2017-01-20 17:15:59.000000000 +0000 @@ -5,7 +5,7 @@ ``pytest`` strives to make testing a fun and no-boilerplate effort. The tool is distributed as a `pytest` package. Its project independent -``py.test`` command line tool helps you to: +``pytest`` command line tool helps you to: * rapidly collect and run tests * run unit- or doctests, functional or integration tests diff -Nru pytest-2.9.2/doc/en/test/plugin/coverage.rst pytest-3.0.6/doc/en/test/plugin/coverage.rst --- pytest-2.9.2/doc/en/test/plugin/coverage.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/test/plugin/coverage.rst 2016-08-20 20:00:30.000000000 +0000 @@ -26,7 +26,7 @@ To get full test coverage reports for a particular package type:: - py.test --cover-report=report + pytest --cover-report=report command line options -------------------- diff -Nru pytest-2.9.2/doc/en/test/plugin/cov.rst pytest-3.0.6/doc/en/test/plugin/cov.rst --- pytest-2.9.2/doc/en/test/plugin/cov.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/test/plugin/cov.rst 2016-08-20 20:00:30.000000000 +0000 @@ -53,7 +53,7 @@ Running centralised testing:: - py.test --cov myproj tests/ + pytest --cov myproj tests/ Shows a terminal report:: @@ -76,7 +76,7 @@ Running distributed testing with dist mode set to load:: - py.test --cov myproj -n 2 tests/ + pytest --cov myproj -n 2 tests/ Shows a terminal report:: @@ -92,7 +92,7 @@ Again but spread over different hosts and different directories:: - py.test --cov myproj --dist load + pytest --cov myproj --dist load --tx ssh=memedough@host1//chdir=testenv1 --tx ssh=memedough@host2//chdir=/tmp/testenv2//python=/tmp/env1/bin/python --rsyncdir myproj --rsyncdir tests --rsync examples @@ -119,7 +119,7 @@ Running distributed testing with dist mode set to each:: - py.test --cov myproj --dist each + pytest --cov myproj --dist each --tx popen//chdir=/tmp/testenv3//python=/usr/local/python27/bin/python --tx ssh=memedough@host2//chdir=/tmp/testenv4//python=/tmp/env2/bin/python --rsyncdir myproj --rsyncdir tests --rsync examples @@ -149,7 +149,7 @@ The terminal report without line numbers (default):: - py.test --cov-report term --cov myproj tests/ + pytest --cov-report term --cov myproj tests/ -------------------- coverage: platform linux2, python 2.6.4-final-0 --------------------- Name Stmts Miss Cover @@ -163,7 +163,7 @@ The terminal report with line numbers:: - py.test --cov-report term-missing --cov myproj tests/ + pytest --cov-report term-missing --cov myproj tests/ -------------------- coverage: platform linux2, python 2.6.4-final-0 --------------------- Name Stmts Miss Cover Missing @@ -178,7 +178,7 @@ The remaining three reports output to files without showing anything on the terminal (useful for when the output is going to a continuous integration server):: - py.test --cov-report html --cov-report xml --cov-report annotate --cov myproj tests/ + pytest --cov-report html --cov-report xml --cov-report annotate --cov myproj tests/ Coverage Data File diff -Nru pytest-2.9.2/doc/en/test/plugin/figleaf.rst pytest-3.0.6/doc/en/test/plugin/figleaf.rst --- pytest-2.9.2/doc/en/test/plugin/figleaf.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/test/plugin/figleaf.rst 2016-08-20 20:00:30.000000000 +0000 @@ -24,7 +24,7 @@ After installation you can simply type:: - py.test --figleaf [...] + pytest --figleaf [...] to enable figleaf coverage in your test run. A default ".figleaf" data file and "html" directory will be created. You can use command line options diff -Nru pytest-2.9.2/doc/en/test/plugin/genscript.rst pytest-3.0.6/doc/en/test/plugin/genscript.rst --- pytest-2.9.2/doc/en/test/plugin/genscript.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/test/plugin/genscript.rst 1970-01-01 00:00:00.000000000 +0000 @@ -1,28 +0,0 @@ - -(deprecated) generate standalone test script to be distributed along with an application. -============================================================================ - - -.. contents:: - :local: - - - -command line options --------------------- - - -``--genscript=path`` - create standalone ``pytest`` script at given target path. - -Start improving this plugin in 30 seconds -========================================= - - -1. Download `pytest_genscript.py`_ plugin source code -2. put it somewhere as ``pytest_genscript.py`` into your import path -3. a subsequent ``pytest`` run will use your local version - -Checkout customize_, other plugins_ or `get in contact`_. - -.. include:: links.txt diff -Nru pytest-2.9.2/doc/en/test/plugin/helpconfig.rst pytest-3.0.6/doc/en/test/plugin/helpconfig.rst --- pytest-2.9.2/doc/en/test/plugin/helpconfig.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/test/plugin/helpconfig.rst 2016-08-20 20:00:30.000000000 +0000 @@ -18,8 +18,6 @@ early-load given plugin (multi-allowed). ``--trace-config`` trace considerations of conftest.py files. -``--nomagic`` - don't reinterpret asserts, no traceback cutting. ``--debug`` generate and show internal debugging information. ``--help-config`` diff -Nru pytest-2.9.2/doc/en/test/plugin/links.rst pytest-3.0.6/doc/en/test/plugin/links.rst --- pytest-2.9.2/doc/en/test/plugin/links.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/test/plugin/links.rst 2016-08-20 20:00:30.000000000 +0000 @@ -2,10 +2,8 @@ .. _`pytest_recwarn.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_recwarn.py .. _`unittest`: unittest.html .. _`pytest_monkeypatch.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_monkeypatch.py -.. _`pytest_genscript.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_genscript.py .. _`pastebin`: pastebin.html .. _`skipping`: skipping.html -.. _`genscript`: genscript.html .. _`plugins`: index.html .. _`mark`: mark.html .. _`tmpdir`: tmpdir.html diff -Nru pytest-2.9.2/doc/en/test/plugin/nose.rst pytest-3.0.6/doc/en/test/plugin/nose.rst --- pytest-2.9.2/doc/en/test/plugin/nose.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/test/plugin/nose.rst 2016-08-20 20:00:30.000000000 +0000 @@ -14,7 +14,7 @@ type:: - py.test # instead of 'nosetests' + pytest # instead of 'nosetests' and you should be able to run nose style tests and at the same time can make full use of pytest's capabilities. @@ -38,7 +38,7 @@ If you find other issues or have suggestions please run:: - py.test --pastebin=all + pytest --pastebin=all and send the resulting URL to a ``pytest`` contact channel, at best to the mailing list. diff -Nru pytest-2.9.2/doc/en/test/plugin/terminal.rst pytest-3.0.6/doc/en/test/plugin/terminal.rst --- pytest-2.9.2/doc/en/test/plugin/terminal.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/test/plugin/terminal.rst 2016-08-20 20:00:30.000000000 +0000 @@ -18,8 +18,6 @@ show extra test summary info as specified by chars (f)ailed, (s)skipped, (x)failed, (X)passed. ``-l, --showlocals`` show locals in tracebacks (disabled by default). -``--report=opts`` - (deprecated, use -r) ``--tb=style`` traceback print mode (long/short/line/no). ``--full-trace`` diff -Nru pytest-2.9.2/doc/en/test/plugin/xdist.rst pytest-3.0.6/doc/en/test/plugin/xdist.rst --- pytest-2.9.2/doc/en/test/plugin/xdist.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/test/plugin/xdist.rst 2017-01-20 16:40:21.000000000 +0000 @@ -36,7 +36,7 @@ To send tests to multiple CPUs, type:: - py.test -n NUM + pytest -n NUM Especially for longer running tests or tests requiring a lot of IO this can lead to considerable speed ups. @@ -47,7 +47,7 @@ To instantiate a python2.4 sub process and send tests to it, you may type:: - py.test -d --tx popen//python=python2.4 + pytest -d --tx popen//python=python2.4 This will start a subprocess which is run with the "python2.4" Python interpreter, found in your system binary lookup path. @@ -68,10 +68,10 @@ have a ssh-reachable machine ``myhost``. Then you can ad-hoc distribute your tests by typing:: - py.test -d --tx ssh=myhostpopen --rsyncdir mypkg mypkg + 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 @@ -97,7 +97,7 @@ port. You can now on your home machine specify this new socket host with something like this:: - py.test -d --tx socket=192.168.1.102:8888 --rsyncdir mypkg mypkg + pytest -d --tx socket=192.168.1.102:8888 --rsyncdir mypkg mypkg .. _`atonce`: @@ -107,7 +107,7 @@ The basic command to run tests on multiple platforms is:: - py.test --dist=each --tx=spec1 --tx=spec2 + pytest --dist=each --tx=spec1 --tx=spec2 If you specify a windows host, an OSX host and a Linux environment this command will send each tests to all diff -Nru pytest-2.9.2/doc/en/tmpdir.rst pytest-3.0.6/doc/en/tmpdir.rst --- pytest-2.9.2/doc/en/tmpdir.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/tmpdir.rst 2017-01-22 17:44:30.000000000 +0000 @@ -27,9 +27,9 @@ Running this would result in a passed test except for the last ``assert 0`` line which we use to look at values:: - $ py.test test_tmpdir.py + $ pytest test_tmpdir.py ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR, inifile: collected 1 items @@ -100,7 +100,7 @@ You can override the default temporary directory setting like this:: - py.test --basetemp=mydir + pytest --basetemp=mydir When distributing tests on the local machine, ``pytest`` takes care to configure a basetemp directory for the sub processes such that all temporary diff -Nru pytest-2.9.2/doc/en/unittest.rst pytest-3.0.6/doc/en/unittest.rst --- pytest-2.9.2/doc/en/unittest.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/unittest.rst 2017-01-22 17:44:30.000000000 +0000 @@ -1,5 +1,6 @@ .. _`unittest.TestCase`: +.. _`unittest`: Support for unittest.TestCase / Integration of fixtures ===================================================================== @@ -21,7 +22,7 @@ After :ref:`installation` type:: - py.test + pytest and you should be able to run your unittest-style tests if they are contained in ``test_*`` modules. If that works for you then @@ -32,6 +33,17 @@ installed the ``pytest-xdist`` plugin. Please refer to the general ``pytest`` documentation for many more examples. +.. note:: + + 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. 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 ----------------------------------------------------------- @@ -86,9 +98,9 @@ Due to the deliberately failing assert statements, we can take a look at the ``self.db`` values in the traceback:: - $ py.test test_unittest_db.py + $ pytest test_unittest_db.py ======= test session starts ======== - platform linux -- Python 3.5.1, pytest-2.9.2, py-1.4.31, pluggy-0.3.1 + platform linux -- Python 3.5.2, pytest-3.0.6, py-1.4.33, pluggy-0.4.0 rootdir: $REGENDOC_TMPDIR, inifile: collected 2 items @@ -161,7 +173,7 @@ Running this test module ...:: - $ py.test -q test_unittest_cleandir.py + $ pytest -q test_unittest_cleandir.py . 1 passed in 0.12 seconds diff -Nru pytest-2.9.2/doc/en/usage.rst pytest-3.0.6/doc/en/usage.rst --- pytest-2.9.2/doc/en/usage.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/usage.rst 2017-01-20 17:15:39.000000000 +0000 @@ -16,17 +16,17 @@ python -m pytest [...] -This is equivalent to invoking the command line script ``py.test [...]`` -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``. Getting help on version, option names, environment variables -------------------------------------------------------------- :: - py.test --version # shows where pytest was imported from - py.test --fixtures # show available builtin function arguments - py.test -h | --help # show help on command line and config file options + pytest --version # shows where pytest was imported from + pytest --fixtures # show available builtin function arguments + pytest -h | --help # show help on command line and config file options Stopping after the first (or N) failures @@ -34,52 +34,52 @@ To stop the testing process after the first (N) failures:: - py.test -x # stop after first failure - py.test --maxfail=2 # stop after two failures + pytest -x # stop after first failure + pytest --maxfail=2 # stop after two failures Specifying tests / selecting tests --------------------------------------------------- Several test run options:: - py.test test_mod.py # run tests in module - py.test somepath # run all tests below somepath - py.test -k stringexpr # only run tests with names that match the + pytest test_mod.py # run tests in module + pytest somepath # run all tests below somepath + pytest -k stringexpr # only run tests with names that match the # "string expression", e.g. "MyClass and not method" # will select TestMyClass.test_something # but not TestMyClass.test_method_simple - py.test test_mod.py::test_func # only run tests that match the "node ID", - # e.g "test_mod.py::test_func" will select + pytest test_mod.py::test_func # only run tests that match the "node ID", + # e.g. "test_mod.py::test_func" will select # only test_func in test_mod.py - py.test test_mod.py::TestClass::test_method # run a single method in + pytest test_mod.py::TestClass::test_method # run a single method in # a single class Import 'pkg' and use its filesystem location to find and run tests:: - py.test --pyargs pkg # run all tests found below directory of pkg + pytest --pyargs pkg # run all tests found below directory of pkg Modifying Python traceback printing ---------------------------------------------- Examples for modifying traceback printing:: - py.test --showlocals # show local variables in tracebacks - py.test -l # show local variables (shortcut) + pytest --showlocals # show local variables in tracebacks + pytest -l # show local variables (shortcut) - py.test --tb=auto # (default) 'long' tracebacks for the first and last + pytest --tb=auto # (default) 'long' tracebacks for the first and last # entry, but 'short' style for the other entries - py.test --tb=long # exhaustive, informative traceback formatting - py.test --tb=short # shorter traceback format - py.test --tb=line # only one line per failure - py.test --tb=native # Python standard library formatting - py.test --tb=no # no traceback at all + pytest --tb=long # exhaustive, informative traceback formatting + pytest --tb=short # shorter traceback format + pytest --tb=line # only one line per failure + pytest --tb=native # Python standard library formatting + pytest --tb=no # no traceback at all 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 catched by pytest). By using this +will be shown (because KeyboardInterrupt is caught by pytest). By using this option you make sure a trace is shown. Dropping to PDB_ (Python Debugger) on failures @@ -90,14 +90,14 @@ Python comes with a builtin Python debugger called PDB_. ``pytest`` allows one to drop into the PDB_ prompt via a command line option:: - py.test --pdb + pytest --pdb This will invoke the Python debugger on every failure. Often you might only want to do this for the first failing test to understand a certain failure situation:: - py.test -x --pdb # drop to PDB on first failure, then end test session - py.test --pdb --maxfail=3 # drop to PDB for first three failures + pytest -x --pdb # drop to PDB on first failure, then end test session + pytest --pdb --maxfail=3 # drop to PDB for first three failures Note that on any failure the exception information is stored on ``sys.last_value``, ``sys.last_type`` and ``sys.last_traceback``. In @@ -125,7 +125,7 @@ .. versionadded: 2.0.0 Prior to pytest version 2.0.0 you could only enter PDB_ tracing if you disabled -capturing on the command line via ``py.test -s``. In later versions, pytest +capturing on the command line via ``pytest -s``. In later versions, pytest automatically disables its output capture when you enter PDB_ tracing: * Output capture in other tests is not affected. @@ -141,7 +141,7 @@ Since pytest version 2.4.0 you can also use the native Python ``import pdb;pdb.set_trace()`` call to enter PDB_ tracing without having to use the ``pytest.set_trace()`` wrapper or explicitly disable pytest's output -capturing via ``py.test -s``. +capturing via ``pytest -s``. .. _durations: @@ -152,7 +152,7 @@ To get a list of the slowest 10 test durations:: - py.test --durations=10 + pytest --durations=10 Creating JUnitXML format files @@ -161,7 +161,7 @@ To create result files which can be read by Jenkins_ or other Continuous integration servers, use this invocation:: - py.test --junitxml=path + pytest --junitxml=path to create an XML file at ``path``. @@ -201,12 +201,63 @@ Also please note that using this feature will break any schema verification. This might be a problem when used with some CI servers. +LogXML: add_global_property +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. versionadded:: 3.0 + +If you want to add a properties node in the testsuite level, which may contains properties that are relevant +to all testcases you can use ``LogXML.add_global_properties`` + +.. code-block:: python + + import pytest + + @pytest.fixture(scope="session") + def log_global_env_facts(f): + + if pytest.config.pluginmanager.hasplugin('junitxml'): + my_junit = getattr(pytest.config, '_xml', None) + + my_junit.add_global_property('ARCH', 'PPC') + my_junit.add_global_property('STORAGE_TYPE', 'CEPH') + + @pytest.mark.usefixtures(log_global_env_facts) + def start_and_prepare_env(): + pass + + class TestMe: + def test_foo(self): + assert True + +This will add a property node below the testsuite node to the generated xml: + +.. code-block:: xml + + + + + + + + + +.. warning:: + + This 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. + Creating resultlog format files ---------------------------------------------------- +.. deprecated:: 3.0 + + This option is rarely used and is scheduled for removal in 4.0. + To create plain-text machine-readable result files you can issue:: - py.test --resultlog=path + pytest --resultlog=path and look at the content at the ``path`` location. Such files are used e.g. by the `PyPy-test`_ web page to show test results over several revisions. @@ -219,7 +270,7 @@ **Creating a URL for each test failure**:: - py.test --pastebin=failed + pytest --pastebin=failed This will submit test run information to a remote Paste service and provide a URL for each failure. You may select tests as usual or add @@ -227,7 +278,7 @@ **Creating a URL for a whole test session log**:: - py.test --pastebin=all + pytest --pastebin=all Currently only pasting to the http://bpaste.net service is implemented. @@ -238,9 +289,9 @@ together with the prefix ``no:``. Example: to disable loading the plugin ``doctest``, which is responsible for -executing doctest tests from text files, invoke py.test like this:: +executing doctest tests from text files, invoke pytest like this:: - py.test -p no:doctest + pytest -p no:doctest .. _`pytest.main-usage`: @@ -253,16 +304,12 @@ pytest.main() -this acts as if you would call "py.test" from the command line. +this acts as if you would call "pytest" from the command line. It will not raise ``SystemExit`` but return the exitcode instead. 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 @@ -271,7 +318,7 @@ def pytest_sessionfinish(self): print("*** test run reporting finishing") - pytest.main("-qq", plugins=[MyPlugin()]) + pytest.main(["-qq"], plugins=[MyPlugin()]) Running it will show that ``MyPlugin`` was added and its hook was invoked:: diff -Nru pytest-2.9.2/doc/en/writing_plugins.rst pytest-3.0.6/doc/en/writing_plugins.rst --- pytest-2.9.2/doc/en/writing_plugins.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/writing_plugins.rst 2017-01-20 17:15:39.000000000 +0000 @@ -87,8 +87,8 @@ Here is how you might run it:: - py.test test_flat.py # will not show "setting up" - py.test a/test_sub.py # will show "setting up" + pytest test_flat.py # will not show "setting up" + pytest a/test_sub.py # will show "setting up" .. Note:: If you have ``conftest.py`` files which do not reside in a @@ -172,10 +172,67 @@ .. 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. +Assertion Rewriting +------------------- + +One of the main features of ``pytest`` is the use of plain assert +statements and the detailed introspection of expressions upon +assertion failures. This is provided by "assertion rewriting" which +modifies the parsed AST before it gets compiled to bytecode. This is +done via a :pep:`302` import hook which gets installed early on when +``pytest`` starts up and will perform this re-writing when modules get +imported. However since we do not want to test different bytecode +then you will run in production this hook only re-writes test modules +themselves as well as any modules which are part of plugins. Any +other imported module will not be re-written and normal assertion +behaviour will happen. + +If you have assertion helpers in other modules where you would need +assertion rewriting to be enabled you need to ask ``pytest`` +explicitly to re-write this module before it gets imported. + +.. autofunction:: pytest.register_assert_rewrite + +This is especially important when you write a pytest plugin which is +created using a package. The import hook only treats ``conftest.py`` +files and any modules which are listed in the ``pytest11`` entrypoint +as plugins. As an example consider the following package:: + + pytest_foo/__init__.py + pytest_foo/plugin.py + pytest_foo/helper.py + +With the following typical ``setup.py`` extract: + +.. code-block:: python + + setup( + ... + entry_points={'pytest11': ['foo = pytest_foo.plugin']}, + ... + ) + +In this case only ``pytest_foo/plugin.py`` will be re-written. If the +helper module also contains assert statements which need to be +re-written it needs to be marked as such, before it gets imported. +This is easiest by marking it for re-writing inside the +``__init__.py`` module, which will always be imported first when a +module inside a package is imported. This way ``plugin.py`` can still +import ``helper.py`` normally. The contents of +``pytest_foo/__init__.py`` will then need to look like this: + +.. code-block:: python + + import pytest + + pytest.register_assert_rewrite('pytest_foo.helper') + + + Requiring/Loading plugins in a test module or conftest file ----------------------------------------------------------- @@ -190,6 +247,16 @@ which will import the specified module as a ``pytest`` plugin. +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; 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 +importing until after the plugin is registered. + Accessing another plugin by name -------------------------------- @@ -226,7 +293,7 @@ pass """) result = testdir.runpytest("--verbose") - result.fnmatch_lines(""" + result.stdout.fnmatch_lines(""" test_example* """) @@ -393,7 +460,7 @@ documentation describing when the hook will be called and what return values are expected. -For an example, see `newhooks.py`_ from :ref:`xdist`. +For an example, see `newhooks.py`_ from `xdist `_. .. _`newhooks.py`: https://github.com/pytest-dev/pytest-xdist/blob/974bd566c599dc6a9ea291838c6f226197208b46/xdist/newhooks.py @@ -479,6 +546,7 @@ .. autofunction:: pytest_pycollect_makeitem .. autofunction:: pytest_generate_tests +.. autofunction:: pytest_make_parametrize_id After collection is complete, you can modify the order of items, delete or otherwise amend the test items: @@ -497,6 +565,8 @@ .. autofunction:: pytest_report_header .. autofunction:: pytest_report_teststatus .. autofunction:: pytest_terminal_summary +.. autofunction:: pytest_fixture_setup +.. autofunction:: pytest_fixture_post_finalizer And here is the central hook for reporting about test execution: @@ -553,11 +623,16 @@ :members: :show-inheritance: +.. autoclass:: _pytest.fixtures.FixtureDef() + :members: + :show-inheritance: + .. autoclass:: _pytest.runner.CallInfo() :members: .. autoclass:: _pytest.runner.TestReport() :members: + :inherited-members: .. autoclass:: _pytest.vendored_packages.pluggy._CallOutcome() :members: @@ -569,7 +644,7 @@ :undoc-members: :show-inheritance: -.. autoclass:: pluggy.PluginManager() +.. autoclass:: _pytest.vendored_packages.pluggy.PluginManager() :members: .. currentmodule:: _pytest.pytester diff -Nru pytest-2.9.2/doc/en/xdist.rst pytest-3.0.6/doc/en/xdist.rst --- pytest-2.9.2/doc/en/xdist.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/xdist.rst 1970-01-01 00:00:00.000000000 +0000 @@ -1,197 +0,0 @@ - -.. _`xdist`: - -xdist: pytest distributed testing plugin -=============================================================== - -The `pytest-xdist`_ plugin extends ``pytest`` with some unique -test execution modes: - -* Looponfail: run your tests repeatedly in a subprocess. After each - run, ``pytest`` waits until a file in your project changes and then - re-runs the previously failing tests. This is repeated until all - tests pass. At this point a full run is again performed. - -* multiprocess Load-balancing: if you have multiple CPUs or hosts you can use - them for a combined test run. This allows to speed up - development or to use special resources of remote machines. - -* Multi-Platform coverage: you can specify different Python interpreters - or different platforms and run tests in parallel on all of them. - -Before running tests remotely, ``pytest`` efficiently "rsyncs" your -program source code to the remote place. All test results -are reported back and displayed to your local terminal. -You may specify different Python versions and interpreters. - - -Installation of xdist plugin ------------------------------- - -Install the plugin with:: - - easy_install pytest-xdist - - # or - - pip install pytest-xdist - -or use the package in develop/in-place mode with -a checkout of the `pytest-xdist repository`_ :: - - python setup.py develop - - -Usage examples ---------------------- - -.. _`xdistcpu`: - -Speed up test runs by sending tests to multiple CPUs -+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -To send tests to multiple CPUs, type:: - - py.test -n NUM - -Especially for longer running tests or tests requiring -a lot of I/O this can lead to considerable speed ups. - - -Running tests in a Python subprocess -+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -To instantiate a Python-2.7 subprocess and send tests to it, you may type:: - - py.test -d --tx popen//python=python2.7 - -This will start a subprocess which is run with the "python2.7" -Python interpreter, found in your system binary lookup path. - -If you prefix the --tx option value like this:: - - py.test -d --tx 3*popen//python=python2.7 - -then three subprocesses would be created and the tests -will be distributed to three subprocesses and run simultanously. - -.. _looponfailing: - - -Running tests in looponfailing mode -+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -For refactoring a project with a medium or large test suite -you can use the looponfailing mode. Simply add the ``--f`` option:: - - py.test -f - -and ``pytest`` will run your tests. Assuming you have failures it will then -wait for file changes and re-run the failing test set. File changes are detected by looking at ``looponfailingroots`` root directories and all of their contents (recursively). If the default for this value does not work for you you -can change it in your project by setting a configuration option:: - - # content of a pytest.ini, setup.cfg or tox.ini file - [pytest] - looponfailroots = mypkg testdir - -This would lead to only looking for file changes in the respective directories, specified relatively to the ini-file's directory. - -Sending tests to remote SSH accounts -+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -Suppose you have a package ``mypkg`` which contains some -tests that you can successfully run locally. And you also -have a ssh-reachable machine ``myhost``. Then -you can ad-hoc distribute your tests by typing:: - - py.test -d --tx ssh=myhostpopen --rsyncdir mypkg mypkg - -This will synchronize your ``mypkg`` package directory -with a remote ssh account and then collect and run your -tests at the remote side. - -You can specify multiple ``--rsyncdir`` directories -to be sent to the remote side. - -.. XXX CHECK - - **NOTE:** For ``pytest`` to collect and send tests correctly - you not only need to make sure all code and tests - directories are rsynced, but that any test (sub) directory - also has an ``__init__.py`` file because internally - ``pytest`` references tests as a fully qualified python - module path. **You will otherwise get strange errors** - during setup of the remote side. - -Sending tests to remote Socket Servers -+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -Download the single-module `socketserver.py`_ Python program -and run it like this:: - - python socketserver.py - -It will tell you that it starts listening on the default -port. You can now on your home machine specify this -new socket host with something like this:: - - py.test -d --tx socket=192.168.1.102:8888 --rsyncdir mypkg mypkg - - -.. _`atonce`: - -Running tests on many platforms at once -+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -The basic command to run tests on multiple platforms is:: - - py.test --dist=each --tx=spec1 --tx=spec2 - -If you specify a windows host, an OSX host and a Linux -environment this command will send each tests to all -platforms - and report back failures from all platforms -at once. The specifications strings use the `xspec syntax`_. - -.. _`xspec syntax`: http://codespeak.net/execnet/basics.html#xspec - -.. _`socketserver.py`: http://bitbucket.org/hpk42/execnet/raw/2af991418160/execnet/script/socketserver.py - -.. _`execnet`: http://codespeak.net/execnet - -Specifying test exec environments in an ini file -+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -pytest (since version 2.0) supports ini-style configuration. -For example, you could make running with three subprocesses your default:: - - [pytest] - addopts = -n3 - -You can also add default environments like this:: - - [pytest] - addopts = --tx ssh=myhost//python=python2.7 --tx ssh=myhost//python=python2.6 - -and then just type:: - - py.test --dist=each - -to run tests in each of the environments. - -Specifying "rsync" dirs in an ini-file -+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - -In a ``tox.ini`` or ``setup.cfg`` file in your root project directory -you may specify directories to include or to exclude in synchronisation:: - - [pytest] - rsyncdirs = . mypkg helperpkg - rsyncignore = .hg - -These directory specifications are relative to the directory -where the configuration file was found. - -.. _`pytest-xdist`: http://pypi.python.org/pypi/pytest-xdist -.. _`pytest-xdist repository`: http://bitbucket.org/pytest-dev/pytest-xdist -.. _`pytest`: http://pytest.org - diff -Nru pytest-2.9.2/doc/en/xunit_setup.rst pytest-3.0.6/doc/en/xunit_setup.rst --- pytest-2.9.2/doc/en/xunit_setup.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/xunit_setup.rst 2017-01-20 17:15:39.000000000 +0000 @@ -7,21 +7,20 @@ This section describes a classic and popular way how you can implement fixtures (setup and teardown test state) on a per-module/class/function basis. -pytest started supporting these methods around 2005 and subsequently -nose and the standard library introduced them (under slightly different -names). While these setup/teardown methods are and will remain fully -supported you may also use pytest's more powerful :ref:`fixture mechanism -` which leverages the concept of dependency injection, allowing -for a more modular and more scalable approach for managing test state, -especially for larger projects and for functional testing. You can -mix both fixture mechanisms in the same file but unittest-based -test methods cannot receive fixture arguments. + .. note:: - As of pytest-2.4, teardownX functions are not called if - setupX existed and failed/was skipped. This harmonizes - behaviour across all major python testing tools. + While these setup/teardown methods are simple and familiar to those + coming from a ``unittest`` or nose ``background``, you may also consider + using pytest's more powerful :ref:`fixture mechanism + ` which leverages the concept of dependency injection, allowing + for a more modular and more scalable approach for managing test state, + especially for larger projects and for functional testing. You can + mix both fixture mechanisms in the same file but + test methods of ``unittest.TestCase`` subclasses + cannot receive fixture arguments. + Module level setup/teardown -------------------------------------- @@ -38,6 +37,8 @@ method. """ +As of pytest-3.0, the ``module`` parameter is optional. + Class level setup/teardown ---------------------------------- @@ -71,6 +72,8 @@ call. """ +As of pytest-3.0, the ``method`` parameter is optional. + If you would rather define test functions directly at module level you can also use the following functions to implement fixtures:: @@ -84,7 +87,13 @@ call. """ -Note that it is possible for setup/teardown pairs to be invoked multiple times -per testing process. +As of pytest-3.0, the ``function`` parameter is optional. + +Remarks: + +* It is possible for setup/teardown pairs to be invoked multiple times + per testing process. +* teardown functions are not called if the corresponding setup function existed + and failed/was skipped. .. _`unittest.py module`: http://docs.python.org/library/unittest.html diff -Nru pytest-2.9.2/doc/en/yieldfixture.rst pytest-3.0.6/doc/en/yieldfixture.rst --- pytest-2.9.2/doc/en/yieldfixture.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/doc/en/yieldfixture.rst 2017-01-20 17:15:39.000000000 +0000 @@ -1,100 +1,18 @@ +:orphan: + .. _yieldfixture: -Fixture functions using "yield" / context manager integration +"yield_fixture" functions --------------------------------------------------------------- -.. versionadded:: 2.4 - -.. regendoc:wipe +.. deprecated:: 3.0 -pytest-2.4 allows fixture functions to seamlessly use a ``yield`` instead -of a ``return`` statement to provide a fixture value while otherwise -fully supporting all other fixture features. - -Let's look at a simple standalone-example using the ``yield`` syntax:: - - # content of test_yield.py - - import pytest - - @pytest.yield_fixture - def passwd(): - print ("\nsetup before yield") - f = open("/etc/passwd") - yield f.readlines() - print ("teardown after yield") - f.close() - - def test_has_lines(passwd): - print ("test called") - assert passwd - -In contrast to :ref:`finalization through registering callbacks -`, our fixture function used a ``yield`` -statement to provide the lines of the ``/etc/passwd`` file. -The code after the ``yield`` statement serves as the teardown code, -avoiding the indirection of registering a teardown callback function. - -Let's run it with output capturing disabled:: - - $ py.test -q -s test_yield.py - - setup before yield - test called - .teardown after yield - - 1 passed in 0.12 seconds - -We can also seamlessly use the new syntax with ``with`` statements. -Let's simplify the above ``passwd`` fixture:: - - # content of test_yield2.py - - import pytest - - @pytest.yield_fixture - def passwd(): - with open("/etc/passwd") as f: - yield f.readlines() - - 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 ``with`` statement ends. - -Note that the yield fixture form supports all other fixture -features such as ``scope``, ``params``, etc., thus changing existing -fixture functions to use ``yield`` is straightforward. - -.. note:: - - While the ``yield`` syntax is similar to what - :py:func:`contextlib.contextmanager` decorated functions - provide, with pytest fixture functions the part after the - "yield" will always be invoked, independently from the - exception status of the test function which uses the fixture. - This behaviour makes sense if you consider that many different - test functions might use a module or session scoped fixture. - - -Discussion and future considerations / feedback -++++++++++++++++++++++++++++++++++++++++++++++++++++ - -There are some topics that are worth mentioning: - -- usually ``yield`` is used for producing multiple values. - But fixture functions can only yield exactly one value. - Yielding a second fixture value will get you an error. - It's possible we can evolve pytest to allow for producing - multiple values as an alternative to current parametrization. - For now, you can just use the normal - :ref:`fixture parametrization ` - mechanisms together with ``yield``-style fixtures. +.. versionadded:: 2.4 -- lastly ``yield`` introduces more than one way to write - fixture functions, so what's the obvious way to a newcomer? +.. important:: + Since pytest-3.0, fixtures using the normal ``fixture`` decorator can use a ``yield`` + statement to provide fixture values and execute teardown code, exactly like ``yield_fixture`` + in previous versions. -If you want to feedback or participate in discussion of the above -topics, please join our :ref:`contact channels`, you are most welcome. + Marking functions as ``yield_fixture`` is still supported, but deprecated and should not + be used in new code. diff -Nru pytest-2.9.2/.gitattributes pytest-3.0.6/.gitattributes --- pytest-2.9.2/.gitattributes 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/.gitattributes 2016-06-25 14:19:06.000000000 +0000 @@ -0,0 +1 @@ +CHANGELOG merge=union diff -Nru pytest-2.9.2/.gitignore pytest-3.0.6/.gitignore --- pytest-2.9.2/.gitignore 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/.gitignore 2016-12-06 18:09:16.000000000 +0000 @@ -0,0 +1,36 @@ +# Automatically generated by `hgimportsvn` +.svn +.hgsvn + +# Ignore local virtualenvs +lib/ +bin/ +include/ +.Python/ + +# These lines are suggested according to the svn:ignore property +# Feel free to enable them by uncommenting them +*.pyc +*.pyo +*.swp +*.class +*.orig +*~ +.hypothesis/ + +.eggs/ + +doc/*/_build +build/ +dist/ +*.egg-info +issue/ +env/ +.env/ +3rdparty/ +.tox +.cache +.coverage +.ropeproject +.idea +.hypothesis diff -Nru pytest-2.9.2/HOWTORELEASE.rst pytest-3.0.6/HOWTORELEASE.rst --- pytest-2.9.2/HOWTORELEASE.rst 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/HOWTORELEASE.rst 2017-01-19 09:44:52.000000000 +0000 @@ -0,0 +1,85 @@ +How to release pytest +-------------------------------------------- + +Note: this assumes you have already registered on pypi. + +1. Bump version numbers in ``_pytest/__init__.py`` (``setup.py`` reads it). + +2. Check and finalize ``CHANGELOG.rst``. + +3. Write ``doc/en/announce/release-VERSION.txt`` and include + it in ``doc/en/announce/index.txt``. Run this command to list names of authors involved:: + + git log $(git describe --abbrev=0 --tags)..HEAD --format='%aN' | sort -u + +4. Regenerate the docs examples using tox:: + + tox -e regen + +5. At this point, open a PR named ``release-X`` so others can help find regressions or provide suggestions. + +6. Use devpi for uploading a release tarball to a staging area:: + + devpi use https://devpi.net/USER/dev + devpi upload --formats sdist,bdist_wheel + +7. Run from multiple machines:: + + devpi use https://devpi.net/USER/dev + devpi test pytest==VERSION + + Alternatively, you can use `devpi-cloud-tester `_ to test + the package on AppVeyor and Travis (follow instructions on the ``README``). + +8. Check that tests pass for relevant combinations with:: + + devpi list pytest + + or look at failures with "devpi list -f pytest". + +9. Feeling confident? Publish to pypi:: + + devpi push pytest==VERSION pypi:NAME + + where NAME is the name of pypi.python.org as configured in your ``~/.pypirc`` + file `for devpi `_. + +10. Tag the release:: + + git tag VERSION + git push origin VERSION + + Make sure ```` is **exactly** the git hash at the time the package was created. + +11. Send release announcement to mailing lists: + + - pytest-dev@python.org + - python-announce-list@python.org + - testing-in-python@lists.idyll.org (only for minor/major releases) + + And announce the release on Twitter, making sure to add the hashtag ``#pytest``. + +12. **After the release** + + a. **patch release (2.8.3)**: + + 1. Checkout ``master``. + 2. Update version number in ``_pytest/__init__.py`` to ``"2.8.4.dev0"``. + 3. Create a new section in ``CHANGELOG.rst`` titled ``2.8.4.dev0`` and add a few bullet points as placeholders for new entries. + 4. Commit and push. + + b. **minor release (2.9.0)**: + + 1. Merge ``features`` into ``master``. + 2. Checkout ``master``. + 3. Follow the same steps for a **patch release** above, using the next patch release: ``2.9.1.dev0``. + 4. Commit ``master``. + 5. Checkout ``features`` and merge with ``master`` (should be a fast-forward at this point). + 6. Update version number in ``_pytest/__init__.py`` to the next minor release: ``"2.10.0.dev0"``. + 7. Create a new section in ``CHANGELOG.rst`` titled ``2.10.0.dev0``, above ``2.9.1.dev0``, and add a few bullet points as placeholders for new entries. + 8. Commit ``features``. + 9. Push ``master`` and ``features``. + + c. **major release (3.0.0)**: same steps as that of a **minor release** + + diff -Nru pytest-2.9.2/MANIFEST.in pytest-3.0.6/MANIFEST.in --- pytest-2.9.2/MANIFEST.in 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/MANIFEST.in 2017-01-19 09:44:52.000000000 +0000 @@ -4,31 +4,33 @@ include README.rst include CONTRIBUTING.rst +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 ISSUES.txt -exclude HOWTORELEASE.rst +exclude .travis.yml +prune .github diff -Nru pytest-2.9.2/PKG-INFO pytest-3.0.6/PKG-INFO --- pytest-2.9.2/PKG-INFO 2016-05-31 17:08:16.000000000 +0000 +++ pytest-3.0.6/PKG-INFO 2017-01-22 21:15:07.000000000 +0000 @@ -1,13 +1,13 @@ Metadata-Version: 1.1 Name: pytest -Version: 2.9.2 +Version: 3.0.6 Summary: pytest: simple powerful testing with Python Home-page: http://pytest.org Author: Holger Krekel, Bruno Oliveira, Ronny Pfannschmidt, Floris Bruynooghe, Brianna Laugher, Florian Bruhin and others -Author-email: holger at merlinux.eu +Author-email: UNKNOWN License: MIT license -Description: .. image:: http://pytest.org/latest/_static/pytest1.png - :target: http://pytest.org +Description: .. image:: http://docs.pytest.org/en/latest/_static/pytest1.png + :target: http://docs.pytest.org :align: center :alt: pytest @@ -25,67 +25,67 @@ :target: https://ci.appveyor.com/project/pytestbot/pytest The ``pytest`` framework makes it easy to write small tests, yet - scales to support complex functional testing for applications and libraries. + scales to support complex functional testing for applications and libraries. 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:: - $ py.test - ======= test session starts ======== - platform linux -- Python 3.4.3, pytest-2.8.5, py-1.4.31, pluggy-0.3.1 + $ pytest + ============================= 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. - Due to ``py.test``'s detailed assertion introspection, only plain ``assert`` statements are used. See `getting-started `_ for more examples. - Features -------- - - Detailed info on failing `assert statements `_ (no need to remember ``self.assert*`` names); + - Detailed info on failing `assert statements `_ (no need to remember ``self.assert*`` names); - `Auto-discovery - `_ + `_ of test modules and functions; - - `Modular fixtures `_ for + - `Modular fixtures `_ for managing small or parametrized long-lived test resources; - - Can run `unittest `_ (or trial), - `nose `_ test suites out of the box; + - Can run `unittest `_ (or trial), + `nose `_ test suites out of the box; - - Python2.6+, Python3.2+, PyPy-2.3, Jython-2.5 (untested); + - Python2.6+, Python3.3+, PyPy-2.3, Jython-2.5 (untested); - - Rich plugin architecture, with over 150+ `external plugins `_ and thriving community; + - Rich plugin architecture, with over 150+ `external plugins `_ and thriving community; Documentation ------------- - For full documentation, including installation, tutorials and PDF documents, please see http://pytest.org. + For full documentation, including installation, tutorials and PDF documents, please see http://docs.pytest.org. Bugs/Requests @@ -97,7 +97,7 @@ Changelog --------- - Consult the `Changelog `_ page for fixes and enhancements of each version. + Consult the `Changelog `__ page for fixes and enhancements of each version. License @@ -109,6 +109,7 @@ .. _`MIT`: https://github.com/pytest-dev/pytest/blob/master/LICENSE +Keywords: test unittest Platform: unix Platform: linux Platform: osx @@ -127,7 +128,6 @@ Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 diff -Nru pytest-2.9.2/plugin-test.sh pytest-3.0.6/plugin-test.sh --- pytest-2.9.2/plugin-test.sh 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/plugin-test.sh 1970-01-01 00:00:00.000000000 +0000 @@ -1,20 +0,0 @@ -#!/bin/bash - -# this assumes plugins are installed as sister directories - -set -e -cd ../pytest-pep8 -py.test -cd ../pytest-instafail -py.test -cd ../pytest-cache -py.test -cd ../pytest-xprocess -py.test -#cd ../pytest-cov -#py.test -cd ../pytest-capturelog -py.test -cd ../pytest-xdist -py.test - diff -Nru pytest-2.9.2/_pytest/_argcomplete.py pytest-3.0.6/_pytest/_argcomplete.py --- pytest-2.9.2/_pytest/_argcomplete.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/_pytest/_argcomplete.py 2017-01-19 13:24:11.000000000 +0000 @@ -87,6 +87,7 @@ completion.append(x[prefix_dir:]) return completion + if os.environ.get('_ARGCOMPLETE'): try: import argcomplete.completers diff -Nru pytest-2.9.2/_pytest/assertion/__init__.py pytest-3.0.6/_pytest/assertion/__init__.py --- pytest-2.9.2/_pytest/assertion/__init__.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/_pytest/assertion/__init__.py 2017-01-19 13:24:11.000000000 +0000 @@ -4,8 +4,9 @@ import py import os import sys -from _pytest.monkeypatch import monkeypatch + from _pytest.assertion import util +from _pytest.assertion import rewrite def pytest_addoption(parser): @@ -13,25 +14,49 @@ group.addoption('--assert', action="store", dest="assertmode", - choices=("rewrite", "reinterp", "plain",), + choices=("rewrite", "plain",), default="rewrite", metavar="MODE", - help="""control assertion debugging tools. 'plain' - performs no assertion debugging. 'reinterp' - reinterprets assert statements after they failed - to provide assertion expression information. - 'rewrite' (the default) rewrites assert - statements in test modules on import to - provide assert expression information. """) - group.addoption('--no-assert', - action="store_true", - default=False, - dest="noassert", - help="DEPRECATED equivalent to --assert=plain") - group.addoption('--nomagic', '--no-magic', - action="store_true", - default=False, - help="DEPRECATED equivalent to --assert=plain") + help="""Control assertion debugging tools. 'plain' + performs no assertion debugging. 'rewrite' + (the default) rewrites assert statements in + test modules on import to provide assert + expression information.""") + + +def pytest_namespace(): + return {'register_assert_rewrite': register_assert_rewrite} + + +def register_assert_rewrite(*names): + """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. + Thus you should make sure to call this before the module is + actually imported, usually in your __init__.py if you are a plugin + using a package. + + :raise TypeError: if the given module names are not strings. + """ + for name in names: + if not isinstance(name, str): + msg = 'expected module names as *args, got {0} instead' + raise TypeError(msg.format(repr(names))) + for hook in sys.meta_path: + if isinstance(hook, rewrite.AssertionRewritingHook): + importhook = hook + break + else: + importhook = DummyRewriteHook() + importhook.mark_rewrite(*names) + + +class DummyRewriteHook(object): + """A no-op import hook for when rewriting is disabled.""" + + def mark_rewrite(self, *names): + pass class AssertionState: @@ -40,51 +65,39 @@ def __init__(self, config, mode): self.mode = mode self.trace = config.trace.root.get("assertion") + self.hook = None -def pytest_configure(config): - mode = config.getvalue("assertmode") - if config.getvalue("noassert") or config.getvalue("nomagic"): - mode = "plain" - if mode == "rewrite": - try: - import ast # noqa - except ImportError: - mode = "reinterp" - else: - # Both Jython and CPython 2.6.0 have AST bugs that make the - # assertion rewriting hook malfunction. - if (sys.platform.startswith('java') or - sys.version_info[:3] == (2, 6, 0)): - mode = "reinterp" - if mode != "plain": - _load_modules(mode) - m = monkeypatch() - config._cleanup.append(m.undo) - m.setattr(py.builtin.builtins, 'AssertionError', - reinterpret.AssertionError) # noqa - hook = None - if mode == "rewrite": - hook = rewrite.AssertionRewritingHook() # noqa - sys.meta_path.insert(0, hook) - warn_about_missing_assertion(mode) - config._assertstate = AssertionState(config, mode) - config._assertstate.hook = hook - config._assertstate.trace("configured with mode set to %r" % (mode,)) +def install_importhook(config): + """Try to install the rewrite hook, raise SystemError if it fails.""" + # Both Jython and CPython 2.6.0 have AST bugs that make the + # assertion rewriting hook malfunction. + if (sys.platform.startswith('java') or + sys.version_info[:3] == (2, 6, 0)): + raise SystemError('rewrite not supported') + + config._assertstate = AssertionState(config, 'rewrite') + 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 def pytest_collection(session): # this hook is only called when test modules are collected # so for example not in the master process of pytest-xdist # (which does not collect test modules) - hook = session.config._assertstate.hook - if hook is not None: - hook.set_session(session) + assertstate = getattr(session.config, '_assertstate', None) + if assertstate: + if assertstate.hook is not None: + assertstate.hook.set_session(session) def _running_on_ci(): @@ -141,35 +154,10 @@ def pytest_sessionfinish(session): - hook = session.config._assertstate.hook - if hook is not None: - hook.session = None - - -def _load_modules(mode): - """Lazily import assertion related code.""" - global rewrite, reinterpret - from _pytest.assertion import reinterpret # noqa - if mode == "rewrite": - from _pytest.assertion import rewrite # noqa - - -def warn_about_missing_assertion(mode): - try: - assert False - except AssertionError: - pass - else: - if mode == "rewrite": - specifically = ("assertions which are not in test modules " - "will be ignored") - else: - specifically = "failing tests may report as passing" - - sys.stderr.write("WARNING: " + specifically + - " because assert statements are not executed " - "by the underlying Python interpreter " - "(are you using python -O?)\n") + assertstate = getattr(session.config, '_assertstate', None) + if assertstate: + if assertstate.hook is not None: + assertstate.hook.set_session(None) # Expose this plugin's implementation for the pytest_assertrepr_compare hook diff -Nru pytest-2.9.2/_pytest/assertion/reinterpret.py pytest-3.0.6/_pytest/assertion/reinterpret.py --- pytest-2.9.2/_pytest/assertion/reinterpret.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/_pytest/assertion/reinterpret.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,407 +0,0 @@ -""" -Find intermediate evalutation results in assert statements through builtin AST. -""" -import ast -import sys - -import _pytest._code -import py -from _pytest.assertion import util -u = py.builtin._totext - - -class AssertionError(util.BuiltinAssertionError): - def __init__(self, *args): - util.BuiltinAssertionError.__init__(self, *args) - if args: - # on Python2.6 we get len(args)==2 for: assert 0, (x,y) - # on Python2.7 and above we always get len(args) == 1 - # with args[0] being the (x,y) tuple. - if len(args) > 1: - toprint = args - else: - toprint = args[0] - try: - self.msg = u(toprint) - except Exception: - self.msg = u( - "<[broken __repr__] %s at %0xd>" - % (toprint.__class__, id(toprint))) - else: - f = _pytest._code.Frame(sys._getframe(1)) - try: - source = f.code.fullsource - if source is not None: - try: - source = source.getstatement(f.lineno, assertion=True) - except IndexError: - source = None - else: - source = str(source.deindent()).strip() - except py.error.ENOENT: - source = None - # this can also occur during reinterpretation, when the - # co_filename is set to "". - if source: - self.msg = reinterpret(source, f, should_fail=True) - else: - self.msg = "" - if not self.args: - self.args = (self.msg,) - -if sys.version_info > (3, 0): - AssertionError.__module__ = "builtins" - -if sys.platform.startswith("java"): - # See http://bugs.jython.org/issue1497 - _exprs = ("BoolOp", "BinOp", "UnaryOp", "Lambda", "IfExp", "Dict", - "ListComp", "GeneratorExp", "Yield", "Compare", "Call", - "Repr", "Num", "Str", "Attribute", "Subscript", "Name", - "List", "Tuple") - _stmts = ("FunctionDef", "ClassDef", "Return", "Delete", "Assign", - "AugAssign", "Print", "For", "While", "If", "With", "Raise", - "TryExcept", "TryFinally", "Assert", "Import", "ImportFrom", - "Exec", "Global", "Expr", "Pass", "Break", "Continue") - _expr_nodes = set(getattr(ast, name) for name in _exprs) - _stmt_nodes = set(getattr(ast, name) for name in _stmts) - def _is_ast_expr(node): - return node.__class__ in _expr_nodes - def _is_ast_stmt(node): - return node.__class__ in _stmt_nodes -else: - def _is_ast_expr(node): - return isinstance(node, ast.expr) - def _is_ast_stmt(node): - return isinstance(node, ast.stmt) - -try: - _Starred = ast.Starred -except AttributeError: - # Python 2. Define a dummy class so isinstance() will always be False. - class _Starred(object): pass - - -class Failure(Exception): - """Error found while interpreting AST.""" - - def __init__(self, explanation=""): - self.cause = sys.exc_info() - self.explanation = explanation - - -def reinterpret(source, frame, should_fail=False): - mod = ast.parse(source) - visitor = DebugInterpreter(frame) - try: - visitor.visit(mod) - except Failure: - failure = sys.exc_info()[1] - return getfailure(failure) - if should_fail: - return ("(assertion failed, but when it was re-run for " - "printing intermediate values, it did not fail. Suggestions: " - "compute assert expression before the assert or use --assert=plain)") - -def run(offending_line, frame=None): - if frame is None: - frame = _pytest._code.Frame(sys._getframe(1)) - return reinterpret(offending_line, frame) - -def getfailure(e): - explanation = util.format_explanation(e.explanation) - value = e.cause[1] - if str(value): - lines = explanation.split('\n') - lines[0] += " << %s" % (value,) - explanation = '\n'.join(lines) - text = "%s: %s" % (e.cause[0].__name__, explanation) - if text.startswith('AssertionError: assert '): - text = text[16:] - return text - -operator_map = { - ast.BitOr : "|", - ast.BitXor : "^", - ast.BitAnd : "&", - ast.LShift : "<<", - ast.RShift : ">>", - ast.Add : "+", - ast.Sub : "-", - ast.Mult : "*", - ast.Div : "/", - ast.FloorDiv : "//", - ast.Mod : "%", - ast.Eq : "==", - ast.NotEq : "!=", - ast.Lt : "<", - ast.LtE : "<=", - ast.Gt : ">", - ast.GtE : ">=", - ast.Pow : "**", - ast.Is : "is", - ast.IsNot : "is not", - ast.In : "in", - ast.NotIn : "not in" -} - -unary_map = { - ast.Not : "not %s", - ast.Invert : "~%s", - ast.USub : "-%s", - ast.UAdd : "+%s" -} - - -class DebugInterpreter(ast.NodeVisitor): - """Interpret AST nodes to gleam useful debugging information. """ - - def __init__(self, frame): - self.frame = frame - - def generic_visit(self, node): - # Fallback when we don't have a special implementation. - if _is_ast_expr(node): - mod = ast.Expression(node) - co = self._compile(mod) - try: - result = self.frame.eval(co) - except Exception: - raise Failure() - explanation = self.frame.repr(result) - return explanation, result - elif _is_ast_stmt(node): - mod = ast.Module([node]) - co = self._compile(mod, "exec") - try: - self.frame.exec_(co) - except Exception: - raise Failure() - return None, None - else: - raise AssertionError("can't handle %s" %(node,)) - - def _compile(self, source, mode="eval"): - return compile(source, "", mode) - - def visit_Expr(self, expr): - return self.visit(expr.value) - - def visit_Module(self, mod): - for stmt in mod.body: - self.visit(stmt) - - def visit_Name(self, name): - explanation, result = self.generic_visit(name) - # See if the name is local. - source = "%r in locals() is not globals()" % (name.id,) - co = self._compile(source) - try: - local = self.frame.eval(co) - except Exception: - # have to assume it isn't - local = None - if local is None or not self.frame.is_true(local): - return name.id, result - return explanation, result - - def visit_Compare(self, comp): - left = comp.left - left_explanation, left_result = self.visit(left) - for op, next_op in zip(comp.ops, comp.comparators): - next_explanation, next_result = self.visit(next_op) - op_symbol = operator_map[op.__class__] - explanation = "%s %s %s" % (left_explanation, op_symbol, - next_explanation) - source = "__exprinfo_left %s __exprinfo_right" % (op_symbol,) - co = self._compile(source) - try: - result = self.frame.eval(co, __exprinfo_left=left_result, - __exprinfo_right=next_result) - except Exception: - raise Failure(explanation) - try: - if not self.frame.is_true(result): - break - except KeyboardInterrupt: - raise - except: - break - left_explanation, left_result = next_explanation, next_result - - if util._reprcompare is not None: - res = util._reprcompare(op_symbol, left_result, next_result) - if res: - explanation = res - return explanation, result - - def visit_BoolOp(self, boolop): - is_or = isinstance(boolop.op, ast.Or) - explanations = [] - for operand in boolop.values: - explanation, result = self.visit(operand) - explanations.append(explanation) - if result == is_or: - break - name = is_or and " or " or " and " - explanation = "(" + name.join(explanations) + ")" - return explanation, result - - def visit_UnaryOp(self, unary): - pattern = unary_map[unary.op.__class__] - operand_explanation, operand_result = self.visit(unary.operand) - explanation = pattern % (operand_explanation,) - co = self._compile(pattern % ("__exprinfo_expr",)) - try: - result = self.frame.eval(co, __exprinfo_expr=operand_result) - except Exception: - raise Failure(explanation) - return explanation, result - - def visit_BinOp(self, binop): - left_explanation, left_result = self.visit(binop.left) - right_explanation, right_result = self.visit(binop.right) - symbol = operator_map[binop.op.__class__] - explanation = "(%s %s %s)" % (left_explanation, symbol, - right_explanation) - source = "__exprinfo_left %s __exprinfo_right" % (symbol,) - co = self._compile(source) - try: - result = self.frame.eval(co, __exprinfo_left=left_result, - __exprinfo_right=right_result) - except Exception: - raise Failure(explanation) - return explanation, result - - def visit_Call(self, call): - func_explanation, func = self.visit(call.func) - arg_explanations = [] - ns = {"__exprinfo_func" : func} - arguments = [] - for arg in call.args: - arg_explanation, arg_result = self.visit(arg) - if isinstance(arg, _Starred): - arg_name = "__exprinfo_star" - ns[arg_name] = arg_result - arguments.append("*%s" % (arg_name,)) - arg_explanations.append("*%s" % (arg_explanation,)) - else: - arg_name = "__exprinfo_%s" % (len(ns),) - ns[arg_name] = arg_result - arguments.append(arg_name) - arg_explanations.append(arg_explanation) - for keyword in call.keywords: - arg_explanation, arg_result = self.visit(keyword.value) - if keyword.arg: - arg_name = "__exprinfo_%s" % (len(ns),) - keyword_source = "%s=%%s" % (keyword.arg) - arguments.append(keyword_source % (arg_name,)) - arg_explanations.append(keyword_source % (arg_explanation,)) - else: - arg_name = "__exprinfo_kwds" - arguments.append("**%s" % (arg_name,)) - arg_explanations.append("**%s" % (arg_explanation,)) - - ns[arg_name] = arg_result - - if getattr(call, 'starargs', None): - arg_explanation, arg_result = self.visit(call.starargs) - arg_name = "__exprinfo_star" - ns[arg_name] = arg_result - arguments.append("*%s" % (arg_name,)) - arg_explanations.append("*%s" % (arg_explanation,)) - - if getattr(call, 'kwargs', None): - arg_explanation, arg_result = self.visit(call.kwargs) - arg_name = "__exprinfo_kwds" - ns[arg_name] = arg_result - arguments.append("**%s" % (arg_name,)) - arg_explanations.append("**%s" % (arg_explanation,)) - args_explained = ", ".join(arg_explanations) - explanation = "%s(%s)" % (func_explanation, args_explained) - args = ", ".join(arguments) - source = "__exprinfo_func(%s)" % (args,) - co = self._compile(source) - try: - result = self.frame.eval(co, **ns) - except Exception: - raise Failure(explanation) - pattern = "%s\n{%s = %s\n}" - rep = self.frame.repr(result) - explanation = pattern % (rep, rep, explanation) - return explanation, result - - def _is_builtin_name(self, name): - pattern = "%r not in globals() and %r not in locals()" - source = pattern % (name.id, name.id) - co = self._compile(source) - try: - return self.frame.eval(co) - except Exception: - return False - - def visit_Attribute(self, attr): - if not isinstance(attr.ctx, ast.Load): - return self.generic_visit(attr) - source_explanation, source_result = self.visit(attr.value) - explanation = "%s.%s" % (source_explanation, attr.attr) - source = "__exprinfo_expr.%s" % (attr.attr,) - co = self._compile(source) - try: - try: - result = self.frame.eval(co, __exprinfo_expr=source_result) - except AttributeError: - # Maybe the attribute name needs to be mangled? - if not attr.attr.startswith("__") or attr.attr.endswith("__"): - raise - source = "getattr(__exprinfo_expr.__class__, '__name__', '')" - co = self._compile(source) - class_name = self.frame.eval(co, __exprinfo_expr=source_result) - mangled_attr = "_" + class_name + attr.attr - source = "__exprinfo_expr.%s" % (mangled_attr,) - co = self._compile(source) - result = self.frame.eval(co, __exprinfo_expr=source_result) - except Exception: - raise Failure(explanation) - explanation = "%s\n{%s = %s.%s\n}" % (self.frame.repr(result), - self.frame.repr(result), - source_explanation, attr.attr) - # Check if the attr is from an instance. - source = "%r in getattr(__exprinfo_expr, '__dict__', {})" - source = source % (attr.attr,) - co = self._compile(source) - try: - from_instance = self.frame.eval(co, __exprinfo_expr=source_result) - except Exception: - from_instance = None - if from_instance is None or self.frame.is_true(from_instance): - rep = self.frame.repr(result) - pattern = "%s\n{%s = %s\n}" - explanation = pattern % (rep, rep, explanation) - return explanation, result - - def visit_Assert(self, assrt): - test_explanation, test_result = self.visit(assrt.test) - explanation = "assert %s" % (test_explanation,) - if not self.frame.is_true(test_result): - try: - raise util.BuiltinAssertionError - except Exception: - raise Failure(explanation) - return explanation, test_result - - def visit_Assign(self, assign): - value_explanation, value_result = self.visit(assign.value) - explanation = "... = %s" % (value_explanation,) - name = ast.Name("__exprinfo_expr", ast.Load(), - lineno=assign.value.lineno, - col_offset=assign.value.col_offset) - new_assign = ast.Assign(assign.targets, name, lineno=assign.lineno, - col_offset=assign.col_offset) - mod = ast.Module([new_assign]) - co = self._compile(mod, "exec") - try: - self.frame.exec_(co, __exprinfo_expr=value_result) - except Exception: - raise Failure(explanation) - return explanation, value_result - diff -Nru pytest-2.9.2/_pytest/assertion/rewrite.py pytest-3.0.6/_pytest/assertion/rewrite.py --- pytest-2.9.2/_pytest/assertion/rewrite.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/_pytest/assertion/rewrite.py 2017-01-19 09:44:52.000000000 +0000 @@ -1,6 +1,7 @@ """Rewrite assertion AST to produce nice error messages""" import ast +import _ast import errno import itertools import imp @@ -10,6 +11,7 @@ import struct import sys import types +from fnmatch import fnmatch import py from _pytest.assertion import util @@ -44,20 +46,20 @@ class AssertionRewritingHook(object): """PEP302 Import hook which rewrites asserts.""" - def __init__(self): + def __init__(self, config): + self.config = config + self.fnpats = config.getini("python_files") self.session = None self.modules = {} + self._rewritten_names = set() self._register_with_pkg_resources() + self._must_rewrite = set() def set_session(self, session): - self.fnpats = session.config.getini("python_files") self.session = session def find_module(self, name, path=None): - if self.session is None: - return None - sess = self.session - state = sess.config._assertstate + state = self.config._assertstate state.trace("find_module called for: %s" % name) names = name.rsplit(".", 1) lastname = names[-1] @@ -78,7 +80,12 @@ 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: @@ -86,24 +93,13 @@ return None else: fn = os.path.join(pth, name.rpartition(".")[2] + ".py") + fn_pypath = py.path.local(fn) - # Is this a test file? - if not sess.isinitpath(fn): - # We have to be very careful here because imports in this code can - # trigger a cycle. - self.session = None - try: - for pat in self.fnpats: - if fn_pypath.fnmatch(pat): - state.trace("matched test file %r" % (fn,)) - break - else: - return None - finally: - self.session = sess - else: - state.trace("matched test file (was specified on cmdline): %r" % - (fn,)) + 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 @@ -140,7 +136,7 @@ co = _read_pyc(fn_pypath, pyc, state.trace) if co is None: state.trace("rewriting %r" % (fn,)) - source_stat, co = _rewrite_test(state, fn_pypath) + source_stat, co = _rewrite_test(self.config, fn_pypath) if co is None: # Probably a SyntaxError in the test. return None @@ -151,6 +147,55 @@ self.modules[name] = co, pyc return self + def _should_rewrite(self, name, fn_pypath, state): + # always rewrite conftest files + fn = str(fn_pypath) + if fn_pypath.basename == 'conftest.py': + state.trace("rewriting conftest file: %r" % (fn,)) + return True + + if self.session is not None: + if self.session.isinitpath(fn): + state.trace("matched test file (was specified on cmdline): %r" % + (fn,)) + return True + + # modules not passed explicitly on the command line are only + # rewritten if they match the naming convention for test files + for pat in self.fnpats: + # use fnmatch instead of fn_pypath.fnmatch because the + # latter might trigger an import to fnmatch.fnmatch + # internally, which would cause this method to be + # called recursively + if fnmatch(fn_pypath.basename, pat): + state.trace("matched test file %r" % (fn,)) + return True + + for marked in self._must_rewrite: + if name.startswith(marked): + state.trace("matched marked file %r (from %r)" % (name, marked)) + return True + + return False + + def mark_rewrite(self, *names): + """Mark import names as needing to be re-written. + + The named module or package as well as any nested modules will + be re-written on import. + """ + already_imported = set(names).intersection(set(sys.modules)) + if 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, name): + self.config.warn( + 'P1', + '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 # sys.modules, the loader must use that existing module. (Otherwise, @@ -235,14 +280,16 @@ fp.close() return True + RN = "\r\n".encode("utf-8") N = "\n".encode("utf-8") cookie_re = re.compile(r"^[ \t\f]*#.*coding[:=][ \t]*[-\w.]+") BOM_UTF8 = '\xef\xbb\xbf' -def _rewrite_test(state, fn): +def _rewrite_test(config, fn): """Try to read and rewrite *fn* and return the code object.""" + state = config._assertstate try: stat = fn.stat() source = fn.read("rb") @@ -287,7 +334,7 @@ # Let this pop up again in the real import. state.trace("failed to parse: %r" % (fn,)) return None, None - rewrite_asserts(tree) + rewrite_asserts(tree, fn, config) try: co = compile(tree, fn.strpath, "exec") except SyntaxError: @@ -343,9 +390,9 @@ return co -def rewrite_asserts(mod): +def rewrite_asserts(mod, module_path=None, config=None): """Rewrite the assert statements in mod.""" - AssertionRewriter().run(mod) + AssertionRewriter(module_path, config).run(mod) def _saferepr(obj): @@ -532,6 +579,11 @@ """ + def __init__(self, module_path, config): + super(AssertionRewriter, self).__init__() + self.module_path = module_path + self.config = config + def run(self, mod): """Find all assert statements in *mod* and rewrite them.""" if not mod.body: @@ -672,6 +724,10 @@ the expression is false. """ + if isinstance(assert_.test, ast.Tuple) and self.config is not None: + fslocation = (self.module_path, assert_.lineno) + self.config.warn('R1', 'assertion is always true, perhaps ' + 'remove parentheses?', fslocation=fslocation) self.statements = [] self.variables = [] self.variable_counter = itertools.count() @@ -855,6 +911,8 @@ def visit_Compare(self, comp): self.push_format_context() left_res, left_expl = self.visit(comp.left) + if isinstance(comp.left, (_ast.Compare, _ast.BoolOp)): + left_expl = "({0})".format(left_expl) res_variables = [self.variable() for i in range(len(comp.ops))] load_names = [ast.Name(v, ast.Load()) for v in res_variables] store_names = [ast.Name(v, ast.Store()) for v in res_variables] @@ -864,6 +922,8 @@ results = [left_res] for i, op, next_operand in it: next_res, next_expl = self.visit(next_operand) + if isinstance(next_operand, (_ast.Compare, _ast.BoolOp)): + next_expl = "({0})".format(next_expl) results.append(next_res) sym = binop_map[op.__class__] syms.append(ast.Str(sym)) diff -Nru pytest-2.9.2/_pytest/assertion/util.py pytest-3.0.6/_pytest/assertion/util.py --- pytest-2.9.2/_pytest/assertion/util.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/_pytest/assertion/util.py 2017-01-19 13:24:11.000000000 +0000 @@ -38,44 +38,11 @@ displaying diffs. """ explanation = ecu(explanation) - explanation = _collapse_false(explanation) lines = _split_explanation(explanation) result = _format_lines(lines) return u('\n').join(result) -def _collapse_false(explanation): - """Collapse expansions of False - - So this strips out any "assert False\n{where False = ...\n}" - blocks. - """ - where = 0 - while True: - start = where = explanation.find("False\n{False = ", where) - if where == -1: - break - level = 0 - prev_c = explanation[start] - for i, c in enumerate(explanation[start:]): - if prev_c + c == "\n{": - level += 1 - elif prev_c + c == "\n}": - level -= 1 - if not level: - break - prev_c = c - else: - raise AssertionError("unbalanced braces: %r" % (explanation,)) - end = start + i - where = end - if explanation[end - 1] == '\n': - explanation = (explanation[:start] + explanation[start+15:end-1] + - explanation[end+1:]) - where -= 17 - return explanation - - def _split_explanation(explanation): """Return a list of individual lines in the explanation @@ -138,7 +105,7 @@ 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)) @@ -225,9 +192,10 @@ 'characters in diff, use -v to show') % i] left = left[:-i] right = right[:-i] + keepends = True explanation += [line.strip('\n') - for line in ndiff(left.splitlines(), - right.splitlines())] + for line in ndiff(left.splitlines(keepends), + right.splitlines(keepends))] return explanation diff -Nru pytest-2.9.2/_pytest/capture.py pytest-3.0.6/_pytest/capture.py --- pytest-2.9.2/_pytest/capture.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/_pytest/capture.py 2017-01-19 13:24:11.000000000 +0000 @@ -4,6 +4,7 @@ """ from __future__ import with_statement +import contextlib import sys import os from tempfile import TemporaryFile @@ -146,46 +147,48 @@ def pytest_internalerror(self, excinfo): self.reset_capturings() - def suspendcapture_item(self, item, when): - out, err = self.suspendcapture() + def suspendcapture_item(self, item, when, in_=False): + out, err = self.suspendcapture(in_=in_) 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" @pytest.fixture def capsys(request): - """enables capturing of writes to sys.stdout/sys.stderr and makes + """Enable capturing of writes to sys.stdout/sys.stderr and make captured output available via ``capsys.readouterr()`` method calls which return a ``(out, err)`` tuple. """ - if "capfd" in request._funcargs: + if "capfd" in request.fixturenames: raise request.raiseerror(error_capsysfderror) - request.node._capfuncarg = c = CaptureFixture(SysCapture) + request.node._capfuncarg = c = CaptureFixture(SysCapture, request) return c @pytest.fixture def capfd(request): - """enables capturing of writes to file descriptors 1 and 2 and makes + """Enable capturing of writes to file descriptors 1 and 2 and make captured output available via ``capfd.readouterr()`` method calls which return a ``(out, err)`` tuple. """ - if "capsys" in request._funcargs: + if "capsys" in request.fixturenames: request.raiseerror(error_capsysfderror) if not hasattr(os, 'dup'): pytest.skip("capfd funcarg needs os.dup") - request.node._capfuncarg = c = CaptureFixture(FDCapture) + request.node._capfuncarg = c = CaptureFixture(FDCapture, request) return c class CaptureFixture: - def __init__(self, captureclass): + def __init__(self, captureclass, request): self.captureclass = captureclass + self.request = request def _start(self): self._capture = MultiCapture(out=True, err=True, in_=False, - Capture=self.captureclass) + Capture=self.captureclass) self._capture.start_capturing() def close(self): @@ -200,6 +203,15 @@ except AttributeError: return self._outerr + @contextlib.contextmanager + def disabled(self): + capmanager = self.request.config.pluginmanager.getplugin('capturemanager') + capmanager.suspendcapture_item(self.request.node, "call", in_=True) + try: + yield + finally: + capmanager.resumecapture() + def safe_text_dupfile(f, mode, default_encoding="UTF8"): """ return a open text file object that's a duplicate of f on the @@ -444,6 +456,13 @@ def close(self): pass + @property + def buffer(self): + if sys.version_info >= (3,0): + return self + else: + raise AttributeError('redirected stdin has no attribute buffer') + def _readline_workaround(): """ @@ -452,7 +471,7 @@ Pdb uses readline support where available--when not running from the Python prompt, the readline module is not imported until running the pdb REPL. If - running py.test with the --pdb option this means the readline module is not + running pytest with the --pdb option this means the readline module is not imported until after I/O capture has been started. This is a problem for pyreadline, which is often used to implement readline diff -Nru pytest-2.9.2/_pytest/_code/code.py pytest-3.0.6/_pytest/_code/code.py --- pytest-2.9.2/_pytest/_code/code.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/_pytest/_code/code.py 2017-01-19 09:44:52.000000000 +0000 @@ -1,8 +1,9 @@ import sys from inspect import CO_VARARGS, CO_VARKEYWORDS +import re +from weakref import ref import py - builtin_repr = repr reprlib = py.builtin._tryimport('repr', 'reprlib') @@ -12,6 +13,7 @@ else: from ._py2traceback import format_exception_only + class Code(object): """ wrapper around Python code objects """ def __init__(self, rawcode): @@ -28,6 +30,8 @@ def __eq__(self, other): return self.raw == other.raw + __hash__ = None + def __ne__(self, other): return not self == other @@ -35,12 +39,16 @@ def path(self): """ return a path object pointing to source code (note that it might not point to an actually existing file). """ - p = py.path.local(self.raw.co_filename) - # maybe don't try this checking - if not p.check(): + try: + p = py.path.local(self.raw.co_filename) + # maybe don't try this checking + if not p.check(): + raise OSError("py.path check failed.") + except OSError: # XXX maybe try harder like the weird logic # in the standard lib [linecache.updatecache] does? p = self.raw.co_filename + return p @property @@ -139,7 +147,8 @@ _repr_style = None exprinfo = None - def __init__(self, rawentry): + def __init__(self, rawentry, excinfo=None): + self._excinfo = excinfo self._rawentry = rawentry self.lineno = rawentry.tb_lineno - 1 @@ -174,18 +183,6 @@ return self.frame.f_locals locals = property(getlocals, None, None, "locals of underlaying frame") - def reinterpret(self): - """Reinterpret the failing statement and returns a detailed information - about what operations are performed.""" - from _pytest.assertion.reinterpret import reinterpret - if self.exprinfo is None: - source = py.builtin._totext(self.statement).strip() - x = reinterpret(source, self.frame, should_fail=True) - if not py.builtin._istext(x): - raise TypeError("interpret returned non-string %r" % (x,)) - self.exprinfo = x - return self.exprinfo - def getfirstlinesource(self): # on Jython this firstlineno can be -1 apparently return max(self.frame.code.firstlineno, 0) @@ -220,16 +217,24 @@ """ return True if the current frame has a var __tracebackhide__ resolving to True + If __tracebackhide__ is a callable, it gets called with the + ExceptionInfo instance and can decide whether to hide the traceback. + mostly for internal use """ try: - return self.frame.f_locals['__tracebackhide__'] + tbh = self.frame.f_locals['__tracebackhide__'] except KeyError: try: - return self.frame.f_globals['__tracebackhide__'] + tbh = self.frame.f_globals['__tracebackhide__'] except KeyError: return False + if py.builtin.callable(tbh): + return tbh(None if self._excinfo is None else self._excinfo()) + else: + return tbh + def __str__(self): try: fn = str(self.path) @@ -253,12 +258,13 @@ access to Traceback entries. """ Entry = TracebackEntry - def __init__(self, tb): - """ initialize from given python traceback object. """ + def __init__(self, tb, excinfo=None): + """ initialize from given python traceback object and ExceptionInfo """ + self._excinfo = excinfo if hasattr(tb, 'tb_next'): def f(cur): while cur is not None: - yield self.Entry(cur) + yield self.Entry(cur, excinfo=excinfo) cur = cur.tb_next list.__init__(self, f(tb)) else: @@ -282,7 +288,7 @@ not codepath.relto(excludepath)) and (lineno is None or x.lineno == lineno) and (firstlineno is None or x.frame.code.firstlineno == firstlineno)): - return Traceback(x._rawentry) + return Traceback(x._rawentry, self._excinfo) return self def __getitem__(self, key): @@ -301,7 +307,7 @@ by default this removes all the TracebackEntries which are hidden (see ishidden() above) """ - return Traceback(filter(fn, self)) + return Traceback(filter(fn, self), self._excinfo) def getcrashentry(self): """ return last non-hidden traceback entry that lead @@ -337,6 +343,7 @@ l.append(entry.frame.f_locals) return None + co_equal = compile('__recursioncache_locals_1 == __recursioncache_locals_2', '?', 'eval') @@ -352,7 +359,7 @@ if exprinfo is None and isinstance(tup[1], AssertionError): exprinfo = getattr(tup[1], 'msg', None) if exprinfo is None: - exprinfo = str(tup[1]) + exprinfo = py._builtin._totext(tup[1]) if exprinfo and exprinfo.startswith('assert '): self._striptext = 'AssertionError: ' self._excinfo = tup @@ -365,7 +372,7 @@ #: the exception type name self.typename = self.type.__name__ #: the exception traceback (_pytest._code.Traceback instance) - self.traceback = _pytest._code.Traceback(self.tb) + self.traceback = _pytest._code.Traceback(self.tb, excinfo=ref(self)) def __repr__(self): return "" % (self.typename, len(self.traceback)) @@ -427,6 +434,19 @@ loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly()) return unicode(loc) + def match(self, regexp): + """ + Match the regular expression 'regexp' on the string representation of + the exception. If it matches then True is returned (so that it is + possible to write 'assert excinfo.match()'). If it doesn't match an + AssertionError is raised. + """ + __tracebackhide__ = True + if not re.search(regexp, str(self.value)): + assert 0, "Pattern '{0!s}' not found in '{1!s}'".format( + regexp, self.value) + return True + class FormattedExcinfo(object): """ presenting information about failing Functions and Generators. """ @@ -593,12 +613,43 @@ break return ReprTraceback(entries, extraline, style=self.style) + def repr_excinfo(self, excinfo): - reprtraceback = self.repr_traceback(excinfo) - reprcrash = excinfo._getreprcrash() - return ReprExceptionInfo(reprtraceback, reprcrash) + if sys.version_info[0] < 3: + reprtraceback = self.repr_traceback(excinfo) + reprcrash = excinfo._getreprcrash() + + return ReprExceptionInfo(reprtraceback, reprcrash) + else: + repr_chain = [] + e = excinfo.value + descr = None + while e is not None: + 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__)) 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__)) if e.__traceback__ else None + descr = 'During handling of the above exception, another exception occurred:' + else: + e = None + repr_chain.reverse() + return ExceptionChainRepr(repr_chain) + -class TerminalRepr: +class TerminalRepr(object): def __str__(self): s = self.__unicode__() if sys.version_info[0] < 3: @@ -617,21 +668,47 @@ return "<%s instance at %0x>" %(self.__class__, id(self)) -class ReprExceptionInfo(TerminalRepr): - def __init__(self, reprtraceback, reprcrash): - self.reprtraceback = reprtraceback - self.reprcrash = reprcrash +class ExceptionRepr(TerminalRepr): + def __init__(self): self.sections = [] def addsection(self, name, content, sep="-"): self.sections.append((name, content, sep)) def toterminal(self, tw): - self.reprtraceback.toterminal(tw) for name, content, sep in self.sections: tw.sep(sep, name) tw.line(content) + +class ExceptionChainRepr(ExceptionRepr): + def __init__(self, chain): + super(ExceptionChainRepr, self).__init__() + self.chain = chain + # reprcrash and reprtraceback of the outermost (the newest) exception + # in the chain + self.reprtraceback = chain[-1][0] + self.reprcrash = chain[-1][1] + + def toterminal(self, tw): + for element in self.chain: + element[0].toterminal(tw) + if element[2] is not None: + tw.line("") + tw.line(element[2], yellow=True) + super(ExceptionChainRepr, self).toterminal(tw) + + +class ReprExceptionInfo(ExceptionRepr): + def __init__(self, reprtraceback, reprcrash): + super(ReprExceptionInfo, self).__init__() + self.reprtraceback = reprtraceback + self.reprcrash = reprcrash + + def toterminal(self, tw): + self.reprtraceback.toterminal(tw) + super(ReprExceptionInfo, self).toterminal(tw) + class ReprTraceback(TerminalRepr): entrysep = "_ " @@ -720,7 +797,8 @@ i = msg.find("\n") if i != -1: msg = msg[:i] - tw.line("%s:%s: %s" %(self.path, self.lineno, msg)) + tw.write(self.path, bold=True, red=True) + tw.line(":%s: %s" % (self.lineno, msg)) class ReprLocals(TerminalRepr): def __init__(self, lines): @@ -753,29 +831,6 @@ tw.line("") - -oldbuiltins = {} - -def patch_builtins(assertion=True, compile=True): - """ put compile and AssertionError builtins to Python's builtins. """ - if assertion: - from _pytest.assertion import reinterpret - l = oldbuiltins.setdefault('AssertionError', []) - l.append(py.builtin.builtins.AssertionError) - py.builtin.builtins.AssertionError = reinterpret.AssertionError - if compile: - import _pytest._code - l = oldbuiltins.setdefault('compile', []) - l.append(py.builtin.builtins.compile) - py.builtin.builtins.compile = _pytest._code.compile - -def unpatch_builtins(assertion=True, compile=True): - """ remove compile and AssertionError builtins from Python builtins. """ - if assertion: - py.builtin.builtins.AssertionError = oldbuiltins['AssertionError'].pop() - if compile: - py.builtin.builtins.compile = oldbuiltins['compile'].pop() - def getrawcode(obj, trycall=True): """ return code object for given function. """ try: @@ -792,6 +847,7 @@ 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 -Nru pytest-2.9.2/_pytest/_code/__init__.py pytest-3.0.6/_pytest/_code/__init__.py --- pytest-2.9.2/_pytest/_code/__init__.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/_pytest/_code/__init__.py 2016-08-20 20:00:30.000000000 +0000 @@ -4,9 +4,6 @@ from .code import Frame # noqa from .code import Traceback # noqa from .code import getrawcode # noqa -from .code import patch_builtins # noqa -from .code import unpatch_builtins # noqa from .source import Source # noqa from .source import compile_ as compile # noqa from .source import getfslineno # noqa - diff -Nru pytest-2.9.2/_pytest/_code/source.py pytest-3.0.6/_pytest/_code/source.py --- pytest-2.9.2/_pytest/_code/source.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/_pytest/_code/source.py 2017-01-19 09:44:52.000000000 +0000 @@ -4,7 +4,6 @@ import sys import inspect, tokenize import py -from types import ModuleType cpy_compile = compile try: @@ -52,22 +51,21 @@ 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 @@ 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 @@ 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 @@ 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 @@ if offset == 0: return list(lines) newlines = [] + def readline_generator(lines): for line in lines: yield line + '\n' diff -Nru pytest-2.9.2/_pytest/compat.py pytest-3.0.6/_pytest/compat.py --- pytest-2.9.2/_pytest/compat.py 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/_pytest/compat.py 2017-01-20 16:40:21.000000000 +0000 @@ -0,0 +1,241 @@ +""" +python version compatibility code +""" +import sys +import inspect +import types +import re +import functools + +import py + +import _pytest + + + +try: + import enum +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 + + +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)) +else: + def _format_args(func): + return inspect.formatargspec(*inspect.getargspec(func)) + +isfunction = inspect.isfunction +isclass = inspect.isclass +# used to work around a python2 exception info leak +exc_clear = getattr(sys, 'exc_clear', lambda: None) +# The type of re.compile objects is not exposed in Python. +REGEX_TYPE = type(re.compile('')) + + +def is_generator(func): + 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): + import inspect + fn = py.path.local(inspect.getfile(function)) + lineno = py.builtin._getcode(function).co_firstlineno + if fn.relto(curdir): + fn = fn.relto(curdir) + return "%s:%d" %(fn, lineno+1) + + +def num_mock_patch_args(function): + """ return number of arguments used up by mock arguments (if any) """ + patchings = getattr(function, "patchings", None) + if not patchings: + return 0 + mock = sys.modules.get("mock", sys.modules.get("unittest.mock", None)) + if mock is not None: + return len([p for p in patchings + if not p.attribute_name and p.new is mock.DEFAULT]) + return len(patchings) + + +def getfuncargnames(function, startindex=None): + # XXX merge with main.py's varnames + #assert not isclass(function) + realfunction = function + while hasattr(realfunction, "__wrapped__"): + realfunction = realfunction.__wrapped__ + if startindex is None: + startindex = inspect.ismethod(function) and 1 or 0 + if realfunction != function: + startindex += num_mock_patch_args(function) + function = realfunction + if isinstance(function, functools.partial): + argnames = inspect.getargs(_pytest._code.getrawcode(function.func))[0] + partial = function + argnames = argnames[len(partial.args):] + if partial.keywords: + for kw in partial.keywords: + argnames.remove(kw) + else: + argnames = inspect.getargs(_pytest._code.getrawcode(function))[0] + defaults = getattr(function, 'func_defaults', + getattr(function, '__defaults__', None)) or () + numdefaults = len(defaults) + if numdefaults: + return tuple(argnames[startindex:-numdefaults]) + return tuple(argnames[startindex:]) + + + +if sys.version_info[:2] == (2, 6): + def isclass(object): + """ Return true if the object is a class. Overrides inspect.isclass for + python 2.6 because it will return True for objects which always return + something on __getattr__ calls (see #1035). + Backport of https://hg.python.org/cpython/rev/35bf8f7a8edc + """ + return isinstance(object, (type, types.ClassType)) + + +if _PY3: + import codecs + + STRING_TYPES = bytes, str + + def _escape_strings(val): + """If val is pure ascii, returns it as a str(). Otherwise, escapes + bytes objects into a sequence of escaped bytes: + + b'\xc3\xb4\xc5\xd6' -> u'\\xc3\\xb4\\xc5\\xd6' + + and escapes unicode objects into a sequence of escaped unicode + ids, e.g.: + + '4\\nV\\U00043efa\\x0eMXWB\\x1e\\u3028\\u15fd\\xcd\\U0007d944' + + note: + the obvious "v.decode('unicode-escape')" will return + valid utf-8 unicode if it finds them in bytes, but we + want to return escaped bytes for any byte, even if they match + a utf-8 string. + + """ + if isinstance(val, bytes): + if val: + # source: http://goo.gl/bGsnwC + encoded_bytes, _ = codecs.escape_encode(val) + return encoded_bytes.decode('ascii') + else: + # empty bytes crashes codecs.escape_encode (#1087) + return '' + else: + return val.encode('unicode_escape').decode('ascii') +else: + STRING_TYPES = bytes, str, unicode + + def _escape_strings(val): + """In py2 bytes and str are the same type, so return if it's a bytes + object, return it unchanged if it is a full ascii string, + otherwise escape it into its binary form. + + If it's a unicode string, change the unicode characters into + unicode escapes. + + """ + if isinstance(val, bytes): + try: + return val.encode('ascii') + except UnicodeDecodeError: + return val.encode('string-escape') + else: + return val.encode('unicode-escape') + + +def get_real_func(obj): + """ gets the real function object of the (possibly) wrapped object by + functools.wraps or functools.partial. + """ + while hasattr(obj, "__wrapped__"): + obj = obj.__wrapped__ + if isinstance(obj, functools.partial): + obj = obj.func + return obj + + +def getfslineno(obj): + # xxx let decorators etc specify a sane ordering + obj = get_real_func(obj) + if hasattr(obj, 'place_as'): + obj = obj.place_as + fslineno = _pytest._code.getfslineno(obj) + assert isinstance(fslineno[1], int), obj + return fslineno + + +def getimfunc(func): + try: + return func.__func__ + except AttributeError: + try: + return func.im_func + except AttributeError: + return func + + +def safe_getattr(object, name, default): + """ Like getattr but return default upon any Exception. + + Attribute access can potentially fail for 'evil' Python objects. + See issue214 + """ + try: + return getattr(object, name, default) + except Exception: + return default + + +def _is_unittest_unexpected_success_a_failure(): + """Return if the test suite should fail if a @expectedFailure unittest test PASSES. + + From https://docs.python.org/3/library/unittest.html?highlight=unittest#unittest.TestResult.wasSuccessful: + 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) + + +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: + errors = 'replace' + return v.encode('ascii', errors) diff -Nru pytest-2.9.2/_pytest/config.py pytest-3.0.6/_pytest/config.py --- pytest-2.9.2/_pytest/config.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/_pytest/config.py 2017-01-20 16:40:36.000000000 +0000 @@ -10,7 +10,9 @@ import sys, os 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") @@ -25,6 +27,12 @@ self.path = path self.excinfo = excinfo + def __str__(self): + etype, evalue, etb = self.excinfo + formatted = traceback.format_tb(etb) + # The level of the tracebacks we want to print is hand crafted :( + return repr(evalue) + '\n' + ''.join(formatted[2:]) + def main(args=None, plugins=None): """ return exit code, after performing an in-process test run. @@ -57,15 +65,40 @@ 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 = ( - "mark main terminal runner python pdb unittest capture skipping " - "tmpdir monkeypatch recwarn pastebin helpconfig nose assertion genscript " - "junitxml resultlog doctest cacheprovider").split() + "mark main terminal runner python fixtures debugging unittest capture skipping " + "tmpdir monkeypatch recwarn pastebin helpconfig nose assertion " + "junitxml resultlog doctest cacheprovider freeze_support " + "setuponly setupplan").split() builtin_plugins = set(default_plugins) builtin_plugins.add("pytester") @@ -97,6 +130,7 @@ return get_config().pluginmanager def _prepareconfig(args=None, plugins=None): + warning = None if args is None: args = sys.argv[1:] elif isinstance(args, py.path.local): @@ -105,6 +139,8 @@ if not isinstance(args, str): raise ValueError("not a string or argument list: %r" % (args,)) args = shlex.split(args, posix=sys.platform != "win32") + from _pytest import deprecated + warning = deprecated.MAIN_STR_ARGS config = get_config() pluginmanager = config.pluginmanager try: @@ -114,6 +150,8 @@ pluginmanager.consider_pluginarg(plugin) else: pluginmanager.register(plugin) + if warning: + config.warn('C1', warning) return pluginmanager.hook.pytest_cmdline_parse( pluginmanager=pluginmanager, args=args) except BaseException: @@ -139,6 +177,7 @@ self._conftestpath2mod = {} self._confcutdir = None self._noconftest = False + self._duplicatepaths = set() self.add_hookspecs(_pytest.hookspec) self.register(self) @@ -152,6 +191,9 @@ self.trace.root.setwriter(err.write) self.enable_tracing() + # Config._consider_importhook will set a real object if required. + self.rewrite_hook = _pytest.assertion.DummyRewriteHook() + def addhooks(self, module_or_class): """ .. deprecated:: 2.8 @@ -360,31 +402,30 @@ self._import_plugin_specs(os.environ.get("PYTEST_PLUGINS")) def consider_module(self, mod): - self._import_plugin_specs(getattr(mod, "pytest_plugins", None)) + 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): @@ -401,6 +442,24 @@ 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. @@ -537,13 +596,18 @@ class Argument: - """class that mimics the necessary behaviour of optparse.Option """ + """class that mimics the necessary behaviour of optparse.Option + + its currently a least effort implementation + and ignoring choices and integer prefixes + https://docs.python.org/3/library/optparse.html#optparse-standard-option-types + """ _typ_map = { 'int': int, 'string': str, - } - # enable after some grace period for plugin writers - TYPE_WARN = False + 'float': float, + 'complex': complex, + } def __init__(self, *names, **attrs): """store parms in private vars for use in add_argument""" @@ -551,17 +615,12 @@ self._short_opts = [] self._long_opts = [] self.dest = attrs.get('dest') - if self.TYPE_WARN: - try: - help = attrs['help'] - if '%default' in help: - warnings.warn( - 'pytest now uses argparse. "%default" should be' - ' changed to "%(default)s" ', - FutureWarning, - stacklevel=3) - except KeyError: - pass + if '%default' in (attrs.get('help') or ''): + warnings.warn( + 'pytest now uses argparse. "%default" should be' + ' changed to "%(default)s" ', + DeprecationWarning, + stacklevel=3) try: typ = attrs['type'] except KeyError: @@ -570,25 +629,23 @@ # this might raise a keyerror as well, don't want to catch that if isinstance(typ, py.builtin._basestring): if typ == 'choice': - if self.TYPE_WARN: - warnings.warn( - 'type argument to addoption() is a string %r.' - ' For parsearg this is optional and when supplied ' - ' should be a type.' - ' (options: %s)' % (typ, names), - FutureWarning, - stacklevel=3) + warnings.warn( + 'type argument to addoption() is a string %r.' + ' For parsearg this is optional and when supplied' + ' should be a type.' + ' (options: %s)' % (typ, names), + DeprecationWarning, + stacklevel=3) # argparse expects a type here take it from # the type of the first element attrs['type'] = type(attrs['choices'][0]) else: - if self.TYPE_WARN: - warnings.warn( - 'type argument to addoption() is a string %r.' - ' For parsearg this should be a type.' - ' (options: %s)' % (typ, names), - FutureWarning, - stacklevel=3) + warnings.warn( + 'type argument to addoption() is a string %r.' + ' For parsearg this should be a type.' + ' (options: %s)' % (typ, names), + DeprecationWarning, + stacklevel=3) attrs['type'] = Argument._typ_map[typ] # used in test_parseopt -> test_parse_defaultgetter self.type = attrs['type'] @@ -655,20 +712,17 @@ self._long_opts.append(opt) def __repr__(self): - retval = 'Argument(' + args = [] if self._short_opts: - retval += '_short_opts: ' + repr(self._short_opts) + ', ' + args += ['_short_opts: ' + repr(self._short_opts)] if self._long_opts: - retval += '_long_opts: ' + repr(self._long_opts) + ', ' - retval += 'dest: ' + repr(self.dest) + ', ' + args += ['_long_opts: ' + repr(self._long_opts)] + args += ['dest: ' + repr(self.dest)] if hasattr(self, 'type'): - retval += 'type: ' + repr(self.type) + ', ' + args += ['type: ' + repr(self.type)] if hasattr(self, 'default'): - retval += 'default: ' + repr(self.default) + ', ' - if retval[-2:] == ', ': # always long enough to test ("Argument(" ) - retval = retval[:-2] - retval += ')' - return retval + args += ['default: ' + repr(self.default)] + return 'Argument({0})'.format(', '.join(args)) class OptionGroup: @@ -686,6 +740,10 @@ results in help showing '--two-words' only, but --twowords gets accepted **and** the automatic destination is in args.twowords """ + conflict = set(optnames).intersection( + name for opt in self.options for name in opt.names()) + if conflict: + raise ValueError("option names %s already added" % conflict) option = Argument(*optnames, **attrs) self._addoption_instance(option, shortupper=False) @@ -772,7 +830,7 @@ 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 @@ -797,9 +855,11 @@ def __repr__(self): return "" + notset = Notset() FILE_OR_DIR = 'file_or_dir' + class Config(object): """ access to configuration values, pluginmanager and plugin hooks. """ @@ -822,9 +882,11 @@ 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)) @@ -908,7 +970,7 @@ def _initini(self, args): ns, unknown_args = self._parser.parse_known_and_unknown_args(args, namespace=self.option.copy()) - r = determine_setup(ns.inifilename, ns.file_or_dir + unknown_args) + r = determine_setup(ns.inifilename, ns.file_or_dir + unknown_args, warnfunc=self.warn) self.rootdir, self.inifile, self.inicfg = r self._parser.extra_info['rootdir'] = self.rootdir self._parser.extra_info['inifile'] = self.inifile @@ -916,19 +978,71 @@ self._parser.addini('addopts', 'extra command line options', 'args') self._parser.addini('minversion', 'minimally required pytest version') + def _consider_importhook(self, args, entrypoint_name): + """Install the PEP 302 import hook if using assertion re-writing. + + Needs to parse the --assert= option from the commandline + and find all the installed plugins to mark them for re-writing + by the importhook. + """ + ns, unknown_args = self._parser.parse_known_and_unknown_args(args) + mode = ns.assertmode + if mode == 'rewrite': + try: + hook = _pytest.assertion.install_importhook(self) + 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) + # 'SOURCES.txt' available for plugins installed in dev mode (pip install -e) + # for installed plugins 'SOURCES.txt' returns an empty list, and vice-versa + # so it shouldn't be an issue + for metadata in ('RECORD', 'SOURCES.txt'): + for entry in entrypoint.dist._get_metadata(metadata): + fn = entry.split(',')[0] + is_simple_module = os.sep not in fn and fn.endswith('.py') + is_package = fn.count(os.sep) == 1 and fn.endswith('__init__.py') + if is_simple_module: + module_name, ext = os.path.splitext(fn) + hook.mark_rewrite(module_name) + elif is_package: + package_name = os.path.dirname(fn) + hook.mark_rewrite(package_name) + self._warn_about_missing_assertion(mode) + + def _warn_about_missing_assertion(self, mode): + try: + assert False + except AssertionError: + pass + else: + if mode == 'plain': + sys.stderr.write("WARNING: ASSERTIONS ARE NOT EXECUTED" + " and FAILING TESTS WILL PASS. Are you" + " using python -O?") + else: + sys.stderr.write("WARNING: assertions not in test modules or" + " plugins will be ignored" + " because assert statements are not executed " + "by the underlying Python interpreter " + "(are you using python -O?)\n") + def _preparse(self, args, addopts=True): self._initini(args) if addopts: args[:] = shlex.split(os.environ.get('PYTEST_ADDOPTS', '')) + args args[:] = self.getini("addopts") + args self._checkversion() + entrypoint_name = 'pytest11' + self._consider_importhook(args, entrypoint_name) self.pluginmanager.consider_preparse(args) - try: - self.pluginmanager.load_setuptools_entrypoints("pytest11") - except ImportError as e: - self.warn("I2", "could not load setuptools entry import: %s" % (e,)) + 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 @@ -999,14 +1113,16 @@ description, type, default = self._parser._inidict[name] except KeyError: raise ValueError("unknown configuration value: %r" %(name,)) - try: - value = self.inicfg[name] - except KeyError: - if default is not None: - return default - if type is None: - return '' - return [] + value = self._get_override_ini_value(name) + if value is None: + try: + value = self.inicfg[name] + except KeyError: + if default is not None: + return default + if type is None: + return '' + return [] if type == "pathlist": dp = py.path.local(self.inicfg.config.path).dirpath() l = [] @@ -1037,6 +1153,23 @@ l.append(relroot) return l + def _get_override_ini_value(self, name): + value = None + # override_ini is a list of list, to support both -o foo1=bar1 foo2=bar2 and + # 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: + try: + (key, user_ini_value) = ini_config.split("=", 1) + 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): """ return command line option value. @@ -1074,7 +1207,18 @@ except ignore: return False -def getcfg(args, inibasenames): +def getcfg(args, warnfunc=None): + """ + Search the list of arguments for a valid ini-file for pytest, + and return a tuple of (rootdir, inifile, cfg-dict). + + note: warnfunc is an optional function used to warn + about ini-files that use deprecated features. + This parameter should be removed when pytest + adopts standard deprecation warnings (#1804). + """ + from _pytest.deprecated import SETUP_CFG_PYTEST + inibasenames = ["pytest.ini", "tox.ini", "setup.cfg"] args = [x for x in args if not str(x).startswith("-")] if not args: args = [py.path.local()] @@ -1086,57 +1230,89 @@ if exists(p): iniconfig = py.iniconfig.IniConfig(p) if 'pytest' in iniconfig.sections: + if inibasename == 'setup.cfg' and warnfunc: + warnfunc('C1', SETUP_CFG_PYTEST) return base, p, iniconfig['pytest'] + if inibasename == 'setup.cfg' and 'tool:pytest' in iniconfig.sections: + return base, p, iniconfig['tool:pytest'] elif inibasename == "pytest.ini": # allowed to be empty return base, p, {} 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] == "-": + for path in paths: + if not path.exists(): continue - p = py.path.local(arg) 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: common_ancestor = py.path.local() - elif not common_ancestor.isdir(): + elif common_ancestor.isfile(): common_ancestor = common_ancestor.dirpath() return common_ancestor -def determine_setup(inifile, args): +def get_dirs_from_args(args): + 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): + dirs = get_dirs_from_args(args) if inifile: iniconfig = py.iniconfig.IniConfig(inifile) try: inicfg = iniconfig["pytest"] except KeyError: inicfg = None - rootdir = get_common_ancestor(args) + rootdir = get_common_ancestor(dirs) else: - ancestor = get_common_ancestor(args) - rootdir, inifile, inicfg = getcfg( - [ancestor], ["pytest.ini", "tox.ini", "setup.cfg"]) + ancestor = get_common_ancestor(dirs) + rootdir, inifile, inicfg = getcfg([ancestor], warnfunc=warnfunc) if rootdir is None: for rootdir in ancestor.parts(reverse=True): if rootdir.join("setup.py").exists(): break else: - rootdir = ancestor + rootdir, inifile, inicfg = getcfg(dirs, warnfunc=warnfunc) + if rootdir is None: + rootdir = get_common_ancestor([py.path.local(), ancestor]) + is_fs_root = os.path.splitdrive(str(rootdir))[1] == os.sep + if is_fs_root: + rootdir = ancestor return rootdir, inifile, inicfg or {} diff -Nru pytest-2.9.2/_pytest/debugging.py pytest-3.0.6/_pytest/debugging.py --- pytest-2.9.2/_pytest/debugging.py 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/_pytest/debugging.py 2017-01-19 13:24:11.000000000 +0000 @@ -0,0 +1,124 @@ +""" interactive debugging with PDB, the Python Debugger. """ +from __future__ import absolute_import +import pdb +import sys + +import pytest + + +def pytest_addoption(parser): + group = parser.getgroup("general") + group._addoption( + '--pdb', dest="usepdb", action="store_true", + help="start the interactive Python debugger on errors.") + group._addoption( + '--pdbcls', dest="usepdb_cls", metavar="modulename:classname", + help="start a custom interactive Python debugger on errors. " + "For example: --pdbcls=IPython.terminal.debugger:TerminalPdb") + +def pytest_namespace(): + return {'set_trace': pytestPDB().set_trace} + +def pytest_configure(config): + if config.getvalue("usepdb") or config.getvalue("usepdb_cls"): + config.pluginmanager.register(PdbInvoke(), 'pdbinvoke') + if config.getvalue("usepdb_cls"): + modname, classname = config.getvalue("usepdb_cls").split(":") + __import__(modname) + pdb_cls = getattr(sys.modules[modname], classname) + else: + pdb_cls = pdb.Pdb + 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 + config._cleanup.append(fin) + +class pytestPDB: + """ Pseudo PDB that defers to the real pdb. """ + _pluginmanager = None + _config = None + _pdb_cls = pdb.Pdb + + def set_trace(self): + """ invoke PDB set_trace debugging, dropping any IO capturing. """ + import _pytest.config + frame = sys._getframe().f_back + if self._pluginmanager is not None: + capman = self._pluginmanager.getplugin("capturemanager") + if capman: + capman.suspendcapture(in_=True) + tw = _pytest.config.create_terminal_writer(self._config) + tw.line() + tw.sep(">", "PDB set_trace (IO-capturing turned off)") + self._pluginmanager.hook.pytest_enter_pdb(config=self._config) + self._pdb_cls().set_trace(frame) + + +class PdbInvoke: + def pytest_exception_interact(self, node, call, report): + capman = node.config.pluginmanager.getplugin("capturemanager") + if capman: + out, err = capman.suspendcapture(in_=True) + sys.stdout.write(out) + sys.stdout.write(err) + _enter_pdb(node, call.excinfo, report) + + def pytest_internalerror(self, excrepr, excinfo): + for line in str(excrepr).split("\n"): + sys.stderr.write("INTERNALERROR> %s\n" %line) + sys.stderr.flush() + tb = _postmortem_traceback(excinfo) + post_mortem(tb) + + +def _enter_pdb(node, excinfo, rep): + # XXX we re-use the TerminalReporter's terminalwriter + # because this seems to avoid some encoding related troubles + # for not completely clear reasons. + tw = node.config.pluginmanager.getplugin("terminalreporter")._tw + tw.line() + tw.sep(">", "traceback") + rep.toterminal(tw) + tw.sep(">", "entering PDB") + tb = _postmortem_traceback(excinfo) + post_mortem(tb) + rep._pdbshown = True + return rep + + +def _postmortem_traceback(excinfo): + # A doctest.UnexpectedException is not useful for post_mortem. + # Use the underlying exception instead: + from doctest import UnexpectedException + if isinstance(excinfo.value, UnexpectedException): + return excinfo.value.exc_info[2] + else: + return excinfo._excinfo[2] + + +def _find_last_non_hidden_frame(stack): + i = max(0, len(stack) - 1) + while i and stack[i][0].f_locals.get("__tracebackhide__", False): + i -= 1 + return i + + +def post_mortem(t): + class Pdb(pytestPDB._pdb_cls): + def get_stack(self, f, t): + stack, i = pdb.Pdb.get_stack(self, f, t) + if f is None: + i = _find_last_non_hidden_frame(stack) + return stack, i + p = Pdb() + p.reset() + p.interaction(None, t) diff -Nru pytest-2.9.2/_pytest/deprecated.py pytest-3.0.6/_pytest/deprecated.py --- pytest-2.9.2/_pytest/deprecated.py 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/_pytest/deprecated.py 2016-08-27 09:59:07.000000000 +0000 @@ -0,0 +1,24 @@ +""" +This module contains deprecation messages and bits of code used elsewhere in the codebase +that is planned to be removed in the next pytest release. + +Keeping it in a central location makes it easy to track what is deprecated and should +be removed when the time comes. +""" + + +MAIN_STR_ARGS = 'passing a string to pytest.main() is deprecated, ' \ + 'pass a list of arguments instead.' + +YIELD_TESTS = 'yield tests are deprecated, and scheduled to be removed in pytest 4.0' + +FUNCARG_PREFIX = ( + '{name}: declaring fixtures using "pytest_funcarg__" prefix is deprecated ' + 'and scheduled to be removed in pytest 4.0. ' + 'Please remove the prefix and use the @pytest.fixture decorator instead.') + +SETUP_CFG_PYTEST = '[pytest] section in setup.cfg files is deprecated, use [tool:pytest] instead.' + +GETFUNCARGVALUE = "use of getfuncargvalue is deprecated, use getfixturevalue" + +RESULT_LOG = '--result-log is deprecated and scheduled for removal in pytest 4.0' diff -Nru pytest-2.9.2/_pytest/doctest.py pytest-3.0.6/_pytest/doctest.py --- pytest-2.9.2/_pytest/doctest.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/_pytest/doctest.py 2017-01-19 13:24:11.000000000 +0000 @@ -4,10 +4,23 @@ import traceback import pytest -from _pytest._code.code import TerminalRepr, ReprFileLocation, ExceptionInfo -from _pytest.python import FixtureRequest +from _pytest._code.code import ExceptionInfo, ReprFileLocation, TerminalRepr +from _pytest.fixtures import FixtureRequest +DOCTEST_REPORT_CHOICE_NONE = 'none' +DOCTEST_REPORT_CHOICE_CDIFF = 'cdiff' +DOCTEST_REPORT_CHOICE_NDIFF = 'ndiff' +DOCTEST_REPORT_CHOICE_UDIFF = 'udiff' +DOCTEST_REPORT_CHOICE_ONLY_FIRST_FAILURE = 'only_first_failure' + +DOCTEST_REPORT_CHOICES = ( + DOCTEST_REPORT_CHOICE_NONE, + DOCTEST_REPORT_CHOICE_CDIFF, + DOCTEST_REPORT_CHOICE_NDIFF, + DOCTEST_REPORT_CHOICE_UDIFF, + DOCTEST_REPORT_CHOICE_ONLY_FIRST_FAILURE, +) def pytest_addoption(parser): parser.addini('doctest_optionflags', 'option flags for doctests', @@ -17,6 +30,11 @@ action="store_true", default=False, help="run doctests in all .py modules", dest="doctestmodules") + group.addoption("--doctest-report", + type=str.lower, default="udiff", + help="choose another output format for diffs on doctest failure", + choices=DOCTEST_REPORT_CHOICES, + dest="doctestreport") group.addoption("--doctest-glob", action="append", default=[], metavar="pat", help="doctests file matching pattern, default: test*.txt", @@ -59,7 +77,6 @@ class DoctestItem(pytest.Item): - def __init__(self, name, parent, runner=None, dtest=None): super(DoctestItem, self).__init__(name, parent) self.runner = runner @@ -70,7 +87,9 @@ def setup(self): if self.dtest is not None: self.fixture_request = _setup_fixtures(self) - globs = dict(getfixture=self.fixture_request.getfuncargvalue) + globs = dict(getfixture=self.fixture_request.getfixturevalue) + for name, value in self.fixture_request.getfixturevalue('doctest_namespace').items(): + globs[name] = value self.dtest.globs.update(globs) def runtest(self): @@ -92,7 +111,7 @@ message = excinfo.type.__name__ reprlocation = ReprFileLocation(filename, lineno, message) checker = _get_checker() - REPORT_UDIFF = doctest.REPORT_UDIFF + report_choice = _get_report_choice(self.config.getoption("doctestreport")) if lineno is not None: lines = doctestfailure.test.docstring.splitlines(False) # add line numbers to the left of the error message @@ -108,7 +127,7 @@ indent = '...' if excinfo.errisinstance(doctest.DocTestFailure): lines += checker.output_difference(example, - doctestfailure.got, REPORT_UDIFF).split("\n") + doctestfailure.got, report_choice).split("\n") else: inner_excinfo = ExceptionInfo(excinfo.value.exc_info) lines += ["UNEXPECTED EXCEPTION: %s" % @@ -144,20 +163,19 @@ return flag_acc -class DoctestTextfile(DoctestItem, pytest.Module): +class DoctestTextfile(pytest.Module): + obj = None - def runtest(self): + def collect(self): import doctest - fixture_request = _setup_fixtures(self) # inspired by doctest.testfile; ideally we would use it directly, # but it doesn't support passing a custom checker text = self.fspath.read() filename = str(self.fspath) name = self.fspath.basename - globs = dict(getfixture=fixture_request.getfuncargvalue) - if '__name__' not in globs: - globs['__name__'] = '__main__' + globs = {'__name__': '__main__'} + optionflags = get_optionflags(self) runner = doctest.DebugRunner(verbose=0, optionflags=optionflags, @@ -165,8 +183,8 @@ parser = doctest.DocTestParser() test = parser.get_doctest(text, globs, name, filename, 0) - _check_all_skipped(test) - runner.run(test) + if test.examples: + yield DoctestItem(test.name, self, runner, test) def _check_all_skipped(test): @@ -288,3 +306,26 @@ """ import doctest return doctest.register_optionflag('ALLOW_BYTES') + + +def _get_report_choice(key): + """ + This function returns the actual `doctest` module flag value, we want to do it as late as possible to avoid + importing `doctest` and all its dependencies when parsing options, as it adds overhead and breaks tests. + """ + import doctest + + return { + DOCTEST_REPORT_CHOICE_UDIFF: doctest.REPORT_UDIFF, + DOCTEST_REPORT_CHOICE_CDIFF: doctest.REPORT_CDIFF, + DOCTEST_REPORT_CHOICE_NDIFF: doctest.REPORT_NDIFF, + DOCTEST_REPORT_CHOICE_ONLY_FIRST_FAILURE: doctest.REPORT_ONLY_FIRST_FAILURE, + DOCTEST_REPORT_CHOICE_NONE: 0, + }[key] + +@pytest.fixture(scope='session') +def doctest_namespace(): + """ + Inject names into the doctest namespace. + """ + return dict() diff -Nru pytest-2.9.2/_pytest/fixtures.py pytest-3.0.6/_pytest/fixtures.py --- pytest-2.9.2/_pytest/fixtures.py 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/_pytest/fixtures.py 2017-01-19 13:24:11.000000000 +0000 @@ -0,0 +1,1134 @@ +import sys + +from py._code.code import FormattedExcinfo + +import py +import pytest +import warnings + +import inspect +import _pytest +from _pytest._code.code import TerminalRepr +from _pytest.compat import ( + NOTSET, exc_clear, _format_args, + getfslineno, get_real_func, + is_generator, isclass, getimfunc, + getlocation, getfuncargnames, +) + +def pytest_sessionstart(session): + session._fixturemanager = FixtureManager(session) + + +scopename2class = {} + + +scope2props = dict(session=()) +scope2props["module"] = ("fspath", "module") +scope2props["class"] = scope2props["module"] + ("cls",) +scope2props["instance"] = scope2props["class"] + ("instance", ) +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 + + +def pytest_namespace(): + scopename2class.update({ + 'class': pytest.Class, + 'module': pytest.Module, + 'function': pytest.Item, + }) + return { + 'fixture': fixture, + 'yield_fixture': yield_fixture, + 'collect': {'_fillfuncargs': fillfixtures} + } + + +def get_scope_node(node, scope): + cls = scopename2class.get(scope) + if cls is None: + if scope == "session": + return node.session + raise ValueError("unknown scope") + return node.getparent(cls) + + +def add_funcarg_pseudo_fixture_def(collector, metafunc, fixturemanager): + # this function will transform all collected calls to a functions + # if they use direct funcargs (i.e. direct parametrization) + # because we want later test execution to be able to rely on + # an existing FixtureDef structure for all arguments. + # XXX we can probably avoid this algorithm if we modify CallSpec2 + # to directly care for creating the fixturedefs within its methods. + if not metafunc._calls[0].funcargs: + return # this function call does not have direct parametrization + # collect funcargs of all callspecs into a list of values + arg2params = {} + arg2scope = {} + for callspec in metafunc._calls: + for argname, argvalue in callspec.funcargs.items(): + assert argname not in callspec.params + callspec.params[argname] = argvalue + arg2params_list = arg2params.setdefault(argname, []) + callspec.indices[argname] = len(arg2params_list) + arg2params_list.append(argvalue) + if argname not in arg2scope: + scopenum = callspec._arg2scopenum.get(argname, + scopenum_function) + arg2scope[argname] = scopes[scopenum] + callspec.funcargs.clear() + + # register artificial FixtureDef's so that later at test execution + # time we can rely on a proper FixtureDef to exist for fixture setup. + arg2fixturedefs = metafunc._arg2fixturedefs + for argname, valuelist in arg2params.items(): + # if we have a scope that is higher than function we need + # to make sure we only ever create an according fixturedef on + # a per-scope basis. We thus store and cache the fixturedef on the + # node related to the scope. + scope = arg2scope[argname] + node = None + if scope != "function": + node = get_scope_node(collector, scope) + if node is None: + assert scope == "class" and isinstance(collector, pytest.Module) + # use module-level collector for class-scope (for now) + node = collector + if node and argname in node._name2pseudofixturedef: + arg2fixturedefs[argname] = [node._name2pseudofixturedef[argname]] + else: + fixturedef = FixtureDef(fixturemanager, '', argname, + get_direct_param_fixture_func, + arg2scope[argname], + valuelist, False, False) + arg2fixturedefs[argname] = [fixturedef] + if node is not None: + node._name2pseudofixturedef[argname] = fixturedef + + + +def getfixturemarker(obj): + """ return fixturemarker or None if it doesn't exist or raised + 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 + return None + + + +def get_parametrized_fixture_keys(item, scopenum): + """ return list of keys for all parametrized arguments which match + the specified scope. """ + assert scopenum < scopenum_function # function + try: + cs = item.callspec + except AttributeError: + pass + else: + # cs.indictes.items() is random order of argnames but + # then again different functions (items) can change order of + # arguments so it doesn't matter much probably + for argname, param_index in cs.indices.items(): + if cs._arg2scopenum[argname] != scopenum: + continue + if scopenum == 0: # session + key = (argname, param_index) + elif scopenum == 1: # module + key = (argname, param_index, item.fspath) + elif scopenum == 2: # class + key = (argname, param_index, item.fspath, item.cls) + yield key + + +# algorithm for sorting on a per-parametrized resource setup basis +# it is called for scopenum==0 (session) first and performs sorting +# down to the lower scopes such as to minimize number of "high scope" +# setups and teardowns + +def reorder_items(items): + argkeys_cache = {} + for scopenum in range(0, scopenum_function): + argkeys_cache[scopenum] = d = {} + for item in items: + keys = set(get_parametrized_fixture_keys(item, scopenum)) + if keys: + d[item] = keys + return reorder_items_atscope(items, set(), argkeys_cache, 0) + +def reorder_items_atscope(items, ignore, argkeys_cache, scopenum): + if scopenum >= scopenum_function or len(items) < 3: + return items + items_done = [] + while 1: + items_before, items_same, items_other, newignore = \ + slice_items(items, ignore, argkeys_cache[scopenum]) + items_before = reorder_items_atscope( + items_before, ignore, argkeys_cache,scopenum+1) + if items_same is None: + # nothing to reorder in this scope + assert items_other is None + return items_done + items_before + items_done.extend(items_before) + items = items_same + items_other + ignore = newignore + + +def slice_items(items, ignore, scoped_argkeys_cache): + # we pick the first item which uses a fixture instance in the + # requested scope and which we haven't seen yet. We slice the input + # items list into a list of items_nomatch, items_same and + # items_other + if scoped_argkeys_cache: # do we need to do work at all? + it = iter(items) + # first find a slicing key + for i, item in enumerate(it): + argkeys = scoped_argkeys_cache.get(item) + if argkeys is not None: + argkeys = argkeys.difference(ignore) + if argkeys: # found a slicing key + slicing_argkey = argkeys.pop() + items_before = items[:i] + items_same = [item] + items_other = [] + # now slice the remainder of the list + for item in it: + argkeys = scoped_argkeys_cache.get(item) + if argkeys and slicing_argkey in argkeys and \ + slicing_argkey not in ignore: + items_same.append(item) + else: + items_other.append(item) + newignore = ignore.copy() + newignore.add(slicing_argkey) + return (items_before, items_same, items_other, newignore) + return items, None, None, None + + + +class FuncargnamesCompatAttr: + """ helper class so that Metafunc, Function and FixtureRequest + don't need to each define the "funcargnames" compatibility attribute. + """ + @property + def funcargnames(self): + """ alias attribute for ``fixturenames`` for pre-2.3 compatibility""" + return self.fixturenames + + +def fillfixtures(function): + """ fill missing funcargs for a test function. """ + try: + request = function._request + except AttributeError: + # XXX this special code path is only expected to execute + # with the oejskit plugin. It uses classes with funcargs + # and we thus have to work a bit to allow this. + fm = function.session._fixturemanager + fi = fm.getfixtureinfo(function.parent, function.obj, None) + function._fixtureinfo = fi + request = function._request = FixtureRequest(function) + request._fillfixtures() + # prune out funcargs for jstests + newfuncargs = {} + for name in fi.argnames: + newfuncargs[name] = function.funcargs[name] + function.funcargs = newfuncargs + else: + request._fillfixtures() + + + +def get_direct_param_fixture_func(request): + return request.param + +class FuncFixtureInfo: + def __init__(self, argnames, names_closure, name2fixturedefs): + self.argnames = argnames + self.names_closure = names_closure + self.name2fixturedefs = name2fixturedefs + + +class FixtureRequest(FuncargnamesCompatAttr): + """ A request for a fixture from a test or fixture function. + + A request object gives access to the requesting test context + and has an optional ``param`` attribute in case + the fixture is parametrized indirectly. + """ + + def __init__(self, pyfuncitem): + self._pyfuncitem = pyfuncitem + #: fixture for which this request is being performed + self.fixturename = None + #: Scope string, one of "function", "class", "module", "session" + self.scope = "function" + self._fixture_values = {} # argname -> fixture value + self._fixture_defs = {} # argname -> FixtureDef + fixtureinfo = pyfuncitem._fixtureinfo + self._arg2fixturedefs = fixtureinfo.name2fixturedefs.copy() + self._arg2index = {} + self._fixturemanager = pyfuncitem.session._fixturemanager + + @property + def fixturenames(self): + # backward incompatible note: now a readonly property + return list(self._pyfuncitem._fixtureinfo.names_closure) + + @property + def node(self): + """ underlying collection node (depends on current request scope)""" + return self._getscopeitem(self.scope) + + + def _getnextfixturedef(self, argname): + fixturedefs = self._arg2fixturedefs.get(argname, None) + if fixturedefs is None: + # we arrive here because of a a dynamic call to + # getfixturevalue(argname) usage which was naturally + # not known at parsing/collection time + parentid = self._pyfuncitem.parent.nodeid + fixturedefs = self._fixturemanager.getfixturedefs(argname, parentid) + self._arg2fixturedefs[argname] = fixturedefs + # fixturedefs list is immutable so we maintain a decreasing index + index = self._arg2index.get(argname, 0) - 1 + if fixturedefs is None or (-index > len(fixturedefs)): + raise FixtureLookupError(argname, self) + self._arg2index[argname] = index + return fixturedefs[index] + + @property + def config(self): + """ the pytest config object associated with this request. """ + return self._pyfuncitem.config + + + @scopeproperty() + def function(self): + """ test function object if the request has a per-function scope. """ + return self._pyfuncitem.obj + + @scopeproperty("class") + def cls(self): + """ class (can be None) where the test function was collected. """ + clscol = self._pyfuncitem.getparent(pytest.Class) + if clscol: + return clscol.obj + + @property + def instance(self): + """ instance (can be None) on which test function was collected. """ + # unittest support hack, see _pytest.unittest.TestCaseFunction + try: + return self._pyfuncitem._testcase + except AttributeError: + function = getattr(self, "function", None) + if function is not None: + return py.builtin._getimself(function) + + @scopeproperty() + def module(self): + """ python module object where the test function was collected. """ + return self._pyfuncitem.getparent(pytest.Module).obj + + @scopeproperty() + def fspath(self): + """ the file system path of the test module which collected this test. """ + return self._pyfuncitem.fspath + + @property + def keywords(self): + """ keywords/markers dictionary for the underlying node. """ + return self.node.keywords + + @property + def session(self): + """ pytest session object. """ + return self._pyfuncitem.session + + def addfinalizer(self, finalizer): + """ add finalizer/teardown function to be called after the + last test within the requesting test context finished + execution. """ + # XXX usually this method is shadowed by fixturedef specific ones + self._addfinalizer(finalizer, scope=self.scope) + + def _addfinalizer(self, finalizer, scope): + colitem = self._getscopeitem(scope) + self._pyfuncitem.session._setupstate.addfinalizer( + finalizer=finalizer, colitem=colitem) + + def applymarker(self, marker): + """ Apply a marker to a single test function invocation. + This method is useful if you don't want to have a keyword/marker + on all function invocations. + + :arg marker: a :py:class:`_pytest.mark.MarkDecorator` object + created by a call to ``pytest.mark.NAME(...)``. + """ + try: + self.node.keywords[marker.markname] = marker + except AttributeError: + raise ValueError(marker) + + def raiseerror(self, msg): + """ raise a FixtureLookupError with the given message. """ + raise self._fixturemanager.FixtureLookupError(None, self, msg) + + def _fillfixtures(self): + item = self._pyfuncitem + fixturenames = getattr(item, "fixturenames", self.fixturenames) + for argname in fixturenames: + if argname not in item.funcargs: + item.funcargs[argname] = self.getfixturevalue(argname) + + def cached_setup(self, setup, teardown=None, scope="module", extrakey=None): + """ (deprecated) Return a testing resource managed by ``setup`` & + ``teardown`` calls. ``scope`` and ``extrakey`` determine when the + ``teardown`` function will be called so that subsequent calls to + ``setup`` would recreate the resource. With pytest-2.3 you often + do not need ``cached_setup()`` as you can directly declare a scope + on a fixture function and register a finalizer through + ``request.addfinalizer()``. + + :arg teardown: function receiving a previously setup resource. + :arg setup: a no-argument function creating a resource. + :arg scope: a string value out of ``function``, ``class``, ``module`` + or ``session`` indicating the caching lifecycle of the resource. + :arg extrakey: added to internal caching key of (funcargname, scope). + """ + if not hasattr(self.config, '_setupcache'): + self.config._setupcache = {} # XXX weakref? + cachekey = (self.fixturename, self._getscopeitem(scope), extrakey) + cache = self.config._setupcache + try: + val = cache[cachekey] + except KeyError: + self._check_scope(self.fixturename, self.scope, scope) + val = setup() + cache[cachekey] = val + if teardown is not None: + def finalizer(): + del cache[cachekey] + teardown(val) + self._addfinalizer(finalizer, scope=scope) + return val + + def getfixturevalue(self, argname): + """ Dynamically run a named fixture function. + + Declaring fixtures via function argument is recommended where possible. + But if you can only decide whether to use another fixture at test + setup time, you may use this function to retrieve it inside a fixture + or test function body. + """ + return self._get_active_fixturedef(argname).cached_result[0] + + def getfuncargvalue(self, argname): + """ Deprecated, use getfixturevalue. """ + from _pytest import deprecated + warnings.warn( + deprecated.GETFUNCARGVALUE, + DeprecationWarning) + return self.getfixturevalue(argname) + + def _get_active_fixturedef(self, argname): + try: + return self._fixture_defs[argname] + except KeyError: + try: + fixturedef = self._getnextfixturedef(argname) + except FixtureLookupError: + if argname == "request": + class PseudoFixtureDef: + cached_result = (self, [0], None) + scope = "function" + return PseudoFixtureDef + raise + # remove indent to prevent the python3 exception + # from leaking into the call + result = self._getfixturevalue(fixturedef) + self._fixture_values[argname] = result + self._fixture_defs[argname] = fixturedef + return fixturedef + + def _get_fixturestack(self): + current = self + l = [] + while 1: + fixturedef = getattr(current, "_fixturedef", None) + if fixturedef is None: + l.reverse() + return l + l.append(fixturedef) + current = current._parent_request + + def _getfixturevalue(self, fixturedef): + # prepare a subrequest object before calling fixture function + # (latter managed by fixturedef) + argname = fixturedef.argname + funcitem = self._pyfuncitem + scope = fixturedef.scope + try: + param = funcitem.callspec.getparam(argname) + except (AttributeError, ValueError): + param = NOTSET + param_index = 0 + if fixturedef.params is not None: + frame = inspect.stack()[3] + frameinfo = inspect.getframeinfo(frame[0]) + source_path = frameinfo.filename + source_lineno = frameinfo.lineno + source_path = py.path.local(source_path) + if source_path.relto(funcitem.config.rootdir): + source_path = source_path.relto(funcitem.config.rootdir) + msg = ( + "The requested fixture has no parameter defined for the " + "current test.\n\nRequested fixture '{0}' defined in:\n{1}" + "\n\nRequested here:\n{2}:{3}".format( + fixturedef.argname, + getlocation(fixturedef.func, funcitem.config.rootdir), + source_path, + source_lineno, + ) + ) + pytest.fail(msg) + else: + # indices might not be set if old-style metafunc.addcall() was used + param_index = funcitem.callspec.indices.get(argname, 0) + # if a parametrize invocation set a scope it will override + # the static scope defined with the fixture function + paramscopenum = funcitem.callspec._arg2scopenum.get(argname) + if paramscopenum is not None: + scope = scopes[paramscopenum] + + subrequest = SubRequest(self, scope, param, param_index, fixturedef) + + # check if a higher-level scoped fixture accesses a lower level one + subrequest._check_scope(argname, self.scope, scope) + + # clear sys.exc_info before invoking the fixture (python bug?) + # if its not explicitly cleared it will leak into the call + exc_clear() + try: + # call the fixture function + val = fixturedef.execute(request=subrequest) + finally: + # if fixture function failed it might have registered finalizers + self.session._setupstate.addfinalizer(fixturedef.finish, + subrequest.node) + return val + + def _check_scope(self, argname, invoking_scope, requested_scope): + if argname == "request": + return + if scopemismatch(invoking_scope, requested_scope): + # try to report something helpful + lines = self._factorytraceback() + pytest.fail("ScopeMismatch: You tried to access the %r scoped " + "fixture %r with a %r scoped request object, " + "involved factories\n%s" %( + (requested_scope, argname, invoking_scope, "\n".join(lines))), + pytrace=False) + + def _factorytraceback(self): + lines = [] + for fixturedef in self._get_fixturestack(): + factory = fixturedef.func + fs, lineno = getfslineno(factory) + p = self._pyfuncitem.session.fspath.bestrelpath(fs) + args = _format_args(factory) + lines.append("%s:%d: def %s%s" %( + p, lineno, factory.__name__, args)) + return lines + + def _getscopeitem(self, scope): + if scope == "function": + # this might also be a non-function Item despite its attribute name + return self._pyfuncitem + node = get_scope_node(self._pyfuncitem, scope) + if node is None and scope == "class": + # fallback to function item itself + node = self._pyfuncitem + assert node + return node + + def __repr__(self): + return "" %(self.node) + + +class SubRequest(FixtureRequest): + """ a sub request for handling getting a fixture from a + test function/fixture. """ + def __init__(self, request, scope, param, param_index, fixturedef): + self._parent_request = request + self.fixturename = fixturedef.argname + if param is not NOTSET: + self.param = param + self.param_index = param_index + self.scope = scope + self._fixturedef = fixturedef + self.addfinalizer = fixturedef.addfinalizer + self._pyfuncitem = request._pyfuncitem + self._fixture_values = request._fixture_values + self._fixture_defs = request._fixture_defs + self._arg2fixturedefs = request._arg2fixturedefs + self._arg2index = request._arg2index + self._fixturemanager = request._fixturemanager + + def __repr__(self): + return "" % (self.fixturename, self._pyfuncitem) + + +class ScopeMismatchError(Exception): + """ A fixture function tries to use a different fixture function which + 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): + self.argname = argname + self.request = request + self.fixturestack = request._get_fixturestack() + self.msg = msg + + def formatrepr(self): + tblines = [] + addline = tblines.append + stack = [self.request._pyfuncitem.obj] + stack.extend(map(lambda x: x.func, self.fixturestack)) + msg = self.msg + if msg is not None: + # the last fixture raise an error, let's present + # it at the requesting side + stack = stack[:-1] + for function in stack: + fspath, lineno = getfslineno(function) + try: + lines, _ = inspect.getsourcelines(get_real_func(function)) + except (IOError, IndexError, TypeError): + error_msg = "file %s, line %s: source code not available" + addline(error_msg % (fspath, lineno+1)) + else: + addline("file %s, line %s" % (fspath, lineno+1)) + for i, line in enumerate(lines): + line = line.rstrip() + addline(" " + line) + if line.lstrip().startswith('def'): + break + + if msg is None: + fm = self.request._fixturemanager + available = [] + parentid = self.request._pyfuncitem.parent.nodeid + for name, fixturedefs in fm._arg2fixturedefs.items(): + faclist = list(fm._matchfactories(fixturedefs, parentid)) + if faclist and name not in available: + available.append(name) + msg = "fixture %r not found" % (self.argname,) + msg += "\n available fixtures: %s" %(", ".join(sorted(available)),) + msg += "\n use 'pytest --fixtures [testpath]' for help on them." + + return FixtureLookupErrorRepr(fspath, lineno, tblines, msg, self.argname) + + +class FixtureLookupErrorRepr(TerminalRepr): + def __init__(self, filename, firstlineno, tblines, errorstring, argname): + self.tblines = tblines + self.errorstring = errorstring + self.filename = filename + self.firstlineno = firstlineno + self.argname = argname + + def toterminal(self, tw): + # tw.line("FixtureLookupError: %s" %(self.argname), red=True) + for tbline in self.tblines: + tw.line(tbline.rstrip()) + lines = self.errorstring.split("\n") + if lines: + tw.line('{0} {1}'.format(FormattedExcinfo.fail_marker, + lines[0].strip()), red=True) + for line in lines[1:]: + tw.line('{0} {1}'.format(FormattedExcinfo.flow_marker, + line.strip()), red=True) + tw.line() + tw.line("%s:%d" % (self.filename, self.firstlineno+1)) + + +def fail_fixturefunc(fixturefunc, msg): + fs, lineno = getfslineno(fixturefunc) + location = "%s:%s" % (fs, lineno+1) + source = _pytest._code.Source(fixturefunc) + pytest.fail(msg + ":\n\n" + str(source.indent()) + "\n" + location, + pytrace=False) + +def call_fixture_func(fixturefunc, request, kwargs): + yieldctx = is_generator(fixturefunc) + if yieldctx: + it = fixturefunc(**kwargs) + res = next(it) + + def teardown(): + try: + next(it) + except StopIteration: + pass + else: + fail_fixturefunc(fixturefunc, + "yield_fixture function has more than one 'yield'") + + request.addfinalizer(teardown) + else: + res = fixturefunc(**kwargs) + return res + + +class FixtureDef: + """ A container for a factory definition. """ + def __init__(self, fixturemanager, baseid, argname, func, scope, params, + unittest=False, ids=None): + self._fixturemanager = fixturemanager + self.baseid = baseid or '' + self.has_location = baseid is not None + self.func = func + self.argname = argname + self.scope = scope + 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) + self.unittest = unittest + self.ids = ids + self._finalizer = [] + + def addfinalizer(self, finalizer): + self._finalizer.append(finalizer) + + def finish(self): + try: + while self._finalizer: + func = self._finalizer.pop() + func() + finally: + ihook = self._fixturemanager.session.ihook + ihook.pytest_fixture_post_finalizer(fixturedef=self) + # even if finalization fails, we invalidate + # the cached fixture value + if hasattr(self, "cached_result"): + del self.cached_result + + def execute(self, request): + # get required arguments and register our own finish() + # with their finalization + for argname in self.argnames: + fixturedef = request._get_active_fixturedef(argname) + if argname != "request": + fixturedef.addfinalizer(self.finish) + + my_cache_key = request.param_index + cached_result = getattr(self, "cached_result", None) + if cached_result is not None: + result, cache_key, err = cached_result + if my_cache_key == cache_key: + if err is not None: + py.builtin._reraise(*err) + else: + return result + # we have a previous but differently parametrized fixture instance + # so we need to tear it down before creating a new one + self.finish() + assert not hasattr(self, "cached_result") + + ihook = self._fixturemanager.session.ihook + return ihook.pytest_fixture_setup(fixturedef=self, request=request) + + def __repr__(self): + return ("" % + (self.argname, self.scope, self.baseid)) + +def pytest_fixture_setup(fixturedef, request): + """ Execution of fixture setup. """ + kwargs = {} + for argname in fixturedef.argnames: + fixdef = request._get_active_fixturedef(argname) + result, arg_cache_key, exc = fixdef.cached_result + request._check_scope(argname, request.scope, fixdef.scope) + kwargs[argname] = result + + fixturefunc = fixturedef.func + if fixturedef.unittest: + if request.instance is not None: + # bind the unbound method to the TestCase instance + fixturefunc = fixturedef.func.__get__(request.instance) + else: + # the fixture function needs to be bound to the actual + # request.instance so that code working with "fixturedef" behaves + # as expected. + if request.instance is not None: + fixturefunc = getimfunc(fixturedef.func) + if fixturefunc != fixturedef.func: + fixturefunc = fixturefunc.__get__(request.instance) + my_cache_key = request.param_index + try: + result = call_fixture_func(fixturefunc, request, kwargs) + except Exception: + fixturedef.cached_result = (None, my_cache_key, sys.exc_info()) + raise + fixturedef.cached_result = (result, my_cache_key, None) + return result + + +class FixtureFunctionMarker: + def __init__(self, scope, params, autouse=False, ids=None, name=None): + self.scope = scope + self.params = params + self.autouse = autouse + self.ids = ids + self.name = name + + def __call__(self, function): + if isclass(function): + raise ValueError( + "class fixtures not supported (may be in the future)") + function._pytestfixturefunction = self + return function + + + +def fixture(scope="function", params=None, autouse=False, ids=None, name=None): + """ (return a) decorator to mark a fixture factory function. + + This decorator can be used (with or or without parameters) to define + a fixture function. The name of the fixture function can later be + referenced to cause its invocation ahead of running tests: test + modules or classes can use the pytest.mark.usefixtures(fixturename) + marker. Test functions can directly use fixture names as input + arguments in which case the fixture instance returned from the fixture + function will be injected. + + :arg scope: the scope for which this fixture is shared, one of + "function" (default), "class", "module" or "session". + + :arg params: an optional list of parameters which will cause multiple + invocations of the fixture function and all of the tests + using it. + + :arg autouse: if True, the fixture func is activated for all tests that + can see it. If False (the default) then an explicit + reference is needed to activate the fixture. + + :arg ids: list of string ids each corresponding to the params + so that they are part of the test id. If no ids are provided + they will be generated automatically from the params. + + :arg name: the name of the fixture. This defaults to the name of the + decorated function. If a fixture is used in the same module in + which it is defined, the function name of the fixture will be + shadowed by the function arg that requests the fixture; one way + to resolve this is to name the decorated function + ``fixture_`` and then use + ``@pytest.fixture(name='')``. + + Fixtures can optionally provide their values to test functions using a ``yield`` statement, + instead of ``return``. In this case, the code block after the ``yield`` statement is executed + as teardown code regardless of the test outcome. A fixture function must yield exactly once. + """ + if callable(scope) and params is None and autouse == False: + # direct decoration + return FixtureFunctionMarker( + "function", params, autouse, name=name)(scope) + if params is not None and not isinstance(params, (list, tuple)): + params = list(params) + return FixtureFunctionMarker(scope, params, autouse, ids=ids, name=name) + + +def yield_fixture(scope="function", params=None, autouse=False, ids=None, name=None): + """ (return a) decorator to mark a yield-fixture factory function. + + .. deprecated:: 3.0 + Use :py:func:`pytest.fixture` directly instead. + """ + if callable(scope) and params is None and not autouse: + # direct decoration + return FixtureFunctionMarker( + "function", params, autouse, ids=ids, name=name)(scope) + else: + return FixtureFunctionMarker(scope, params, autouse, ids=ids, name=name) + + +defaultfuncargprefixmarker = fixture() + + +@fixture(scope="session") +def pytestconfig(request): + """ the pytest config object with access to command line opts.""" + return request.config + + +class FixtureManager: + """ + pytest fixtures definitions and information is stored and managed + from this class. + + During collection fm.parsefactories() is called multiple times to parse + fixture function definitions into FixtureDef objects and internal + data structures. + + During collection of test functions, metafunc-mechanics instantiate + a FuncFixtureInfo object which is cached per node/func-name. + This FuncFixtureInfo object is later retrieved by Function nodes + which themselves offer a fixturenames attribute. + + The FuncFixtureInfo object holds information about fixtures and FixtureDefs + relevant for a particular function. An initial list of fixtures is + assembled like this: + + - ini-defined usefixtures + - autouse-marked fixtures along the collection chain up from the function + - usefixtures markers at module/class/function level + - test function funcargs + + Subsequently the funcfixtureinfo.fixturenames attribute is computed + as the closure of the fixtures needed to setup the initial fixtures, + i. e. fixtures needed by fixture functions themselves are appended + to the fixturenames list. + + Upon the test-setup phases all fixturenames are instantiated, retrieved + by a lookup of their FuncFixtureInfo. + """ + + _argprefix = "pytest_funcarg__" + FixtureLookupError = FixtureLookupError + FixtureLookupErrorRepr = FixtureLookupErrorRepr + + def __init__(self, session): + self.session = session + self.config = session.config + self._arg2fixturedefs = {} + self._holderobjseen = set() + self._arg2finish = {} + self._nodeid_and_autousenames = [("", self.config.getini("usefixtures"))] + session.config.pluginmanager.register(self, "funcmanage") + + + def getfixtureinfo(self, node, func, cls, funcargs=True): + if funcargs and not hasattr(node, "nofuncargs"): + if cls is not None: + startindex = 1 + else: + startindex = None + argnames = getfuncargnames(func, startindex) + else: + argnames = () + usefixtures = getattr(func, "usefixtures", None) + initialnames = argnames + if usefixtures is not None: + initialnames = usefixtures.args + initialnames + fm = node.session._fixturemanager + names_closure, arg2fixturedefs = fm.getfixtureclosure(initialnames, + node) + return FuncFixtureInfo(argnames, names_closure, arg2fixturedefs) + + def pytest_plugin_registered(self, plugin): + nodeid = None + try: + p = py.path.local(plugin.__file__) + except AttributeError: + pass + else: + # construct the base nodeid which is later used to check + # what fixtures are visible for particular tests (as denoted + # by their test id) + if p.basename.startswith("conftest.py"): + nodeid = p.dirpath().relto(self.config.rootdir) + if p.sep != "/": + nodeid = nodeid.replace(p.sep, "/") + self.parsefactories(plugin, nodeid) + + def _getautousenames(self, nodeid): + """ return a tuple of fixture names to be used. """ + autousenames = [] + for baseid, basenames in self._nodeid_and_autousenames: + if nodeid.startswith(baseid): + if baseid: + i = len(baseid) + nextchar = nodeid[i:i+1] + if nextchar and nextchar not in ":/": + continue + autousenames.extend(basenames) + # make sure autousenames are sorted by scope, scopenum 0 is session + autousenames.sort( + key=lambda x: self._arg2fixturedefs[x][-1].scopenum) + return autousenames + + def getfixtureclosure(self, fixturenames, parentnode): + # collect the closure of all fixtures , starting with the given + # fixturenames as the initial set. As we have to visit all + # factory definitions anyway, we also return a arg2fixturedefs + # mapping so that the caller can reuse it and does not have + # to re-discover fixturedefs again for each fixturename + # (discovering matching fixtures for a given name/node is expensive) + + parentid = parentnode.nodeid + fixturenames_closure = self._getautousenames(parentid) + + def merge(otherlist): + for arg in otherlist: + if arg not in fixturenames_closure: + fixturenames_closure.append(arg) + + merge(fixturenames) + arg2fixturedefs = {} + lastlen = -1 + while lastlen != len(fixturenames_closure): + lastlen = len(fixturenames_closure) + for argname in fixturenames_closure: + if argname in arg2fixturedefs: + continue + fixturedefs = self.getfixturedefs(argname, parentid) + if fixturedefs: + arg2fixturedefs[argname] = fixturedefs + merge(fixturedefs[-1].argnames) + return fixturenames_closure, arg2fixturedefs + + def pytest_generate_tests(self, metafunc): + for argname in metafunc.fixturenames: + faclist = metafunc._arg2fixturedefs.get(argname) + if faclist: + fixturedef = faclist[-1] + if fixturedef.params is not None: + func_params = getattr(getattr(metafunc.function, 'parametrize', None), 'args', [[None]]) + # skip directly parametrized arguments + argnames = func_params[0] + if not isinstance(argnames, (tuple, list)): + argnames = [x.strip() for x in argnames.split(",") if x.strip()] + if argname not in func_params and argname not in argnames: + metafunc.parametrize(argname, fixturedef.params, + indirect=True, scope=fixturedef.scope, + ids=fixturedef.ids) + else: + continue # will raise FixtureLookupError at setup time + + def pytest_collection_modifyitems(self, items): + # separate parametrized setups + items[:] = reorder_items(items) + + def parsefactories(self, node_or_obj, nodeid=NOTSET, unittest=False): + if nodeid is not NOTSET: + holderobj = node_or_obj + else: + holderobj = node_or_obj.obj + nodeid = node_or_obj.nodeid + if holderobj in self._holderobjseen: + return + self._holderobjseen.add(holderobj) + autousenames = [] + for name in dir(holderobj): + obj = getattr(holderobj, name, None) + # fixture functions have a pytest_funcarg__ prefix (pre-2.3 style) + # or are "@pytest.fixture" marked + marker = getfixturemarker(obj) + if marker is None: + if not name.startswith(self._argprefix): + continue + if not callable(obj): + continue + marker = defaultfuncargprefixmarker + from _pytest import deprecated + self.config.warn('C1', deprecated.FUNCARG_PREFIX.format(name=name)) + name = name[len(self._argprefix):] + elif not isinstance(marker, FixtureFunctionMarker): + # magic globals with __getattr__ might have got us a wrong + # fixture attribute + continue + else: + if marker.name: + name = marker.name + msg = 'fixtures cannot have "pytest_funcarg__" prefix ' \ + 'and be decorated with @pytest.fixture:\n%s' % name + assert not name.startswith(self._argprefix), msg + + fixture_def = FixtureDef(self, nodeid, name, obj, + marker.scope, marker.params, + unittest=unittest, ids=marker.ids) + + faclist = self._arg2fixturedefs.setdefault(name, []) + if fixture_def.has_location: + faclist.append(fixture_def) + else: + # fixturedefs with no location are at the front + # so this inserts the current fixturedef after the + # existing fixturedefs from external plugins but + # before the fixturedefs provided in conftests. + i = len([f for f in faclist if not f.has_location]) + faclist.insert(i, fixture_def) + if marker.autouse: + autousenames.append(name) + + if autousenames: + self._nodeid_and_autousenames.append((nodeid or '', autousenames)) + + def getfixturedefs(self, argname, nodeid): + """ + Gets a list of fixtures which are applicable to the given node id. + + :param str argname: name of the fixture to search for + :param str nodeid: full node id of the requesting test. + :return: list[FixtureDef] + """ + try: + fixturedefs = self._arg2fixturedefs[argname] + except KeyError: + return None + else: + return tuple(self._matchfactories(fixturedefs, nodeid)) + + def _matchfactories(self, fixturedefs, nodeid): + for fixturedef in fixturedefs: + if nodeid.startswith(fixturedef.baseid): + yield fixturedef + diff -Nru pytest-2.9.2/_pytest/freeze_support.py pytest-3.0.6/_pytest/freeze_support.py --- pytest-2.9.2/_pytest/freeze_support.py 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/_pytest/freeze_support.py 2016-08-20 20:00:30.000000000 +0000 @@ -0,0 +1,45 @@ +""" +Provides a function to report all internal modules for using freezing tools +pytest +""" + +def pytest_namespace(): + return {'freeze_includes': freeze_includes} + + +def freeze_includes(): + """ + Returns a list of module names used by py.test that should be + included by cx_freeze. + """ + import py + import _pytest + result = list(_iter_all_modules(py)) + result += list(_iter_all_modules(_pytest)) + return result + + +def _iter_all_modules(package, prefix=''): + """ + Iterates over the names of all modules that can be found in the given + package, recursively. + Example: + _iter_all_modules(_pytest) -> + ['_pytest.assertion.newinterpret', + '_pytest.capture', + '_pytest.core', + ... + ] + """ + import os + import pkgutil + if type(package) is not str: + path, prefix = package.__path__[0], package.__name__ + '.' + else: + path = package + for _, name, is_package in pkgutil.iter_modules([path]): + if is_package: + for m in _iter_all_modules(os.path.join(path, name), prefix=name + '.'): + yield prefix + m + else: + yield prefix + name \ No newline at end of file diff -Nru pytest-2.9.2/_pytest/genscript.py pytest-3.0.6/_pytest/genscript.py --- pytest-2.9.2/_pytest/genscript.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/_pytest/genscript.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,132 +0,0 @@ -""" (deprecated) generate a single-file self-contained version of pytest """ -import os -import sys -import pkgutil - -import py -import _pytest - - - -def find_toplevel(name): - for syspath in sys.path: - base = py.path.local(syspath) - lib = base/name - if lib.check(dir=1): - return lib - mod = base.join("%s.py" % name) - if mod.check(file=1): - return mod - raise LookupError(name) - -def pkgname(toplevel, rootpath, path): - parts = path.parts()[len(rootpath.parts()):] - return '.'.join([toplevel] + [x.purebasename for x in parts]) - -def pkg_to_mapping(name): - toplevel = find_toplevel(name) - name2src = {} - if toplevel.check(file=1): # module - name2src[toplevel.purebasename] = toplevel.read() - else: # package - for pyfile in toplevel.visit('*.py'): - pkg = pkgname(name, toplevel, pyfile) - name2src[pkg] = pyfile.read() - # with wheels py source code might be not be installed - # and the resulting genscript is useless, just bail out. - assert name2src, "no source code found for %r at %r" %(name, toplevel) - return name2src - -def compress_mapping(mapping): - import base64, pickle, zlib - data = pickle.dumps(mapping, 2) - data = zlib.compress(data, 9) - data = base64.encodestring(data) - data = data.decode('ascii') - return data - - -def compress_packages(names): - mapping = {} - for name in names: - mapping.update(pkg_to_mapping(name)) - return compress_mapping(mapping) - -def generate_script(entry, packages): - data = compress_packages(packages) - tmpl = py.path.local(__file__).dirpath().join('standalonetemplate.py') - exe = tmpl.read() - exe = exe.replace('@SOURCES@', data) - exe = exe.replace('@ENTRY@', entry) - return exe - - -def pytest_addoption(parser): - group = parser.getgroup("debugconfig") - group.addoption("--genscript", action="store", default=None, - dest="genscript", metavar="path", - help="create standalone pytest script at given target path.") - -def pytest_cmdline_main(config): - import _pytest.config - genscript = config.getvalue("genscript") - if genscript: - tw = _pytest.config.create_terminal_writer(config) - tw.line("WARNING: usage of genscript is deprecated.", - red=True) - deps = ['py', '_pytest', 'pytest'] # pluggy is vendored - if sys.version_info < (2,7): - deps.append("argparse") - tw.line("generated script will run on python2.6-python3.3++") - else: - tw.line("WARNING: generated script will not run on python2.6 " - "due to 'argparse' dependency. Use python2.6 " - "to generate a python2.6 compatible script", red=True) - script = generate_script( - 'import pytest; raise SystemExit(pytest.cmdline.main())', - deps, - ) - genscript = py.path.local(genscript) - genscript.write(script) - tw.line("generated pytest standalone script: %s" % genscript, - bold=True) - return 0 - - -def pytest_namespace(): - return {'freeze_includes': freeze_includes} - - -def freeze_includes(): - """ - Returns a list of module names used by py.test that should be - included by cx_freeze. - """ - result = list(_iter_all_modules(py)) - result += list(_iter_all_modules(_pytest)) - return result - - -def _iter_all_modules(package, prefix=''): - """ - Iterates over the names of all modules that can be found in the given - package, recursively. - - Example: - _iter_all_modules(_pytest) -> - ['_pytest.assertion.newinterpret', - '_pytest.capture', - '_pytest.core', - ... - ] - """ - if type(package) is not str: - path, prefix = package.__path__[0], package.__name__ + '.' - else: - path = package - for _, name, is_package in pkgutil.iter_modules([path]): - if is_package: - for m in _iter_all_modules(os.path.join(path, name), prefix=name + '.'): - yield prefix + m - else: - yield prefix + name diff -Nru pytest-2.9.2/_pytest/helpconfig.py pytest-3.0.6/_pytest/helpconfig.py --- pytest-2.9.2/_pytest/helpconfig.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/_pytest/helpconfig.py 2017-01-19 09:44:52.000000000 +0000 @@ -20,6 +20,10 @@ group.addoption('--debug', action="store_true", dest="debug", default=False, help="store internal tracing debug information in 'pytestdebug.log'.") + group._addoption( + '-o', '--override-ini', nargs='*', dest="override_ini", + action="append", + help="override config option with option=value style, e.g. `-o xfail_strict=True`.") @pytest.hookimpl(hookwrapper=True) @@ -37,12 +41,14 @@ 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): @@ -67,9 +73,8 @@ tw.write(config._parser.optparser.format_help()) tw.line() tw.line() - #tw.sep( "=", "config file settings") - 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: @@ -92,8 +97,8 @@ tw.line() tw.line() - tw.line("to see available markers type: py.test --markers") - tw.line("to see available fixtures type: py.test --fixtures") + tw.line("to see available markers type: pytest --markers") + tw.line("to see available fixtures type: pytest --fixtures") tw.line("(shown according to specified file_or_dir or current dir " "if not specified)") diff -Nru pytest-2.9.2/_pytest/hookspec.py pytest-3.0.6/_pytest/hookspec.py --- pytest-2.9.2/_pytest/hookspec.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/_pytest/hookspec.py 2016-08-27 09:59:07.000000000 +0000 @@ -34,7 +34,7 @@ .. note:: This function should be implemented only in plugins or ``conftest.py`` - files situated at the tests root directory due to how py.test + files situated at the tests root directory due to how pytest :ref:`discovers plugins during startup `. :arg parser: To add command line options, call @@ -156,6 +156,12 @@ def pytest_generate_tests(metafunc): """ generate (multiple) parametrized calls to a test function.""" +@hookspec(firstresult=True) +def pytest_make_parametrize_id(config, val): + """Return a user-friendly string representation of the given ``val`` that will be used + by @pytest.mark.parametrize calls. Return None if the hook doesn't know about ``val``. + """ + # ------------------------------------------------------------------------- # generic runtest related hooks # ------------------------------------------------------------------------- @@ -213,6 +219,19 @@ the respective phase of executing a test. """ # ------------------------------------------------------------------------- +# Fixture related hooks +# ------------------------------------------------------------------------- + +@hookspec(firstresult=True) +def pytest_fixture_setup(fixturedef, request): + """ performs fixture setup execution. """ + +def pytest_fixture_post_finalizer(fixturedef): + """ called after fixture teardown, but before the cache is cleared so + the fixture result cache ``fixturedef.cached_result`` can + still be accessed.""" + +# ------------------------------------------------------------------------- # test session related hooks # ------------------------------------------------------------------------- @@ -250,7 +269,7 @@ def pytest_report_teststatus(report): """ return result-category, shortletter and verbose word for reporting.""" -def pytest_terminal_summary(terminalreporter): +def pytest_terminal_summary(terminalreporter, exitstatus): """ add additional section in terminal summary reporting. """ diff -Nru pytest-2.9.2/_pytest/__init__.py pytest-3.0.6/_pytest/__init__.py --- pytest-2.9.2/_pytest/__init__.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/_pytest/__init__.py 2017-01-22 17:44:30.000000000 +0000 @@ -1,2 +1,2 @@ # -__version__ = '2.9.2' +__version__ = '3.0.6' diff -Nru pytest-2.9.2/_pytest/junitxml.py pytest-3.0.6/_pytest/junitxml.py --- pytest-2.9.2/_pytest/junitxml.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/_pytest/junitxml.py 2017-01-19 13:24:11.000000000 +0000 @@ -8,12 +8,14 @@ # 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 @@ 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 @@ -118,13 +121,10 @@ def _write_captured_output(self, report): for capname in ('out', 'err'): - allcontent = "" - for name, content in report.get_sections("Captured std%s" % - capname): - allcontent += content - if allcontent: + content = getattr(report, 'capstd' + capname) + if content: tag = getattr(Junit, 'system-' + capname) - self.append(tag(bin_xml_escape(allcontent))) + self.append(tag(bin_xml_escape(content))) def append_pass(self, report): self.add_stats('passed') @@ -159,8 +159,12 @@ 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) + Junit.error, msg, report.longrepr) self._write_captured_output(report) def append_skipped(self, report): @@ -186,8 +190,8 @@ @pytest.fixture def record_xml_property(request): - """Fixture that adds extra xml properties to the tag for the calling test. - The fixture is callable with (name, value), with value being automatically + """Add extra xml properties to the tag for the calling test. + The fixture is callable with ``(name, value)``, with value being automatically xml-encoded. """ request.node.warn( @@ -212,6 +216,7 @@ 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( @@ -265,6 +270,7 @@ ], 0) self.node_reporters = {} # nodeid -> _NodeReporter self.node_reporters_ordered = [] + self.global_properties = [] def finalize(self, report): nodeid = getattr(report, 'nodeid', report) @@ -284,9 +290,12 @@ if key in self.node_reporters: # TODO: breasks for --dist=each return self.node_reporters[key] + reporter = _NodeReporter(nodeid, self) + self.node_reporters[key] = reporter self.node_reporters_ordered.append(reporter) + return reporter def add_stats(self, key): @@ -369,10 +378,12 @@ suite_stop_time = time.time() suite_time_delta = suite_stop_time - self.suite_start_time - numtests = self.stats['passed'] + self.stats['failure'] + self.stats['skipped'] + numtests = self.stats['passed'] + self.stats['failure'] + self.stats['skipped'] + self.stats['error'] logfile.write('') + logfile.write(Junit.testsuite( + self._get_global_properties_node(), [x.to_xml() for x in self.node_reporters_ordered], name="pytest", errors=self.stats['error'], @@ -385,3 +396,18 @@ def pytest_terminal_summary(self, terminalreporter): terminalreporter.write_sep("-", "generated xml file: %s" % (self.logfile)) + + def add_global_property(self, name, value): + self.global_properties.append((str(name), bin_xml_escape(value))) + + def _get_global_properties_node(self): + """Return a Junit node containing custom properties, if any. + """ + if self.global_properties: + return Junit.properties( + [ + Junit.property(name=name, value=value) + for name, value in self.global_properties + ] + ) + return '' diff -Nru pytest-2.9.2/_pytest/main.py pytest-3.0.6/_pytest/main.py --- pytest-2.9.2/_pytest/main.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/_pytest/main.py 2017-01-20 16:40:36.000000000 +0000 @@ -1,7 +1,6 @@ """ core implementation of testing process: init, session, runtest loop. """ -import imp +import functools import os -import re import sys import _pytest @@ -13,6 +12,7 @@ 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() @@ -25,11 +25,9 @@ EXIT_USAGEERROR = 4 EXIT_NOTESTSCOLLECTED = 5 -name_re = re.compile("^[a-zA-Z_]\w*$") - def pytest_addoption(parser): parser.addini("norecursedirs", "directory patterns to avoid for recursion", - type="args", default=['.*', 'CVS', '_darcs', '{arch}', '*.egg']) + type="args", default=['.*', 'build', 'dist', 'CVS', '_darcs', '{arch}', '*.egg']) parser.addini("testpaths", "directories to search for tests when no files or directories are given in the command line.", type="args", default=[]) #parser.addini("dirpatterns", @@ -38,8 +36,8 @@ # "**/test_*.py", "**/*_test.py"] #) group = parser.getgroup("general", "running and selection options") - group._addoption('-x', '--exitfirst', action="store_true", default=False, - dest="exitfirst", + group._addoption('-x', '--exitfirst', action="store_const", + dest="maxfail", const=1, help="exit instantly on first error or failed test."), group._addoption('--maxfail', metavar="num", action="store", type=int, dest="maxfail", default=0, @@ -48,6 +46,9 @@ help="run pytest in strict mode, warnings become errors.") group._addoption("-c", metavar="file", type=str, dest="inifilename", help="load configuration from `file` instead of trying to locate one of the implicit configuration files.") + group._addoption("--continue-on-collection-errors", action="store_true", + default=False, dest="continue_on_collection_errors", + help="Force test execution even if collection errors occur.") group = parser.getgroup("collect", "collection") group.addoption('--collectonly', '--collect-only', action="store_true", @@ -59,11 +60,14 @@ # 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, help="Don't load any conftest.py files.") + group.addoption('--keepduplicates', '--keep-duplicates', action="store_true", + dest="keepduplicates", default=False, + help="Keep duplicate tests.") group = parser.getgroup("debugconfig", "test session debugging and configuration") @@ -75,10 +79,10 @@ collect = dict(Item=Item, Collector=Collector, File=File, Session=Session) return dict(collect=collect) + def pytest_configure(config): pytest.config = config # compatibiltiy - if config.option.exitfirst: - config.option.maxfail = 1 + def wrap_session(config, doit): """Skeleton command line program""" @@ -96,6 +100,10 @@ raise except KeyboardInterrupt: excinfo = _pytest._code.ExceptionInfo() + if initstate < 2 and isinstance( + excinfo.value, pytest.exit.Exception): + sys.stderr.write('{0}: {1}\n'.format( + excinfo.typename, excinfo.value.msg)) config.hook.pytest_keyboard_interrupt(excinfo=excinfo) session.exitstatus = EXIT_INTERRUPTED except: @@ -133,20 +141,16 @@ return session.perform_collect() def pytest_runtestloop(session): + if (session.testsfailed and + not session.config.option.continue_on_collection_errors): + raise session.Interrupted( + "%d errors during collection" % session.testsfailed) + if session.config.option.collectonly: return True - def getnextitem(i): - # this is a function to avoid python2 - # keeping sys.exc_info set when calling into a test - # python2 keeps sys.exc_info till the frame is left - try: - return session.items[i+1] - except IndexError: - return None - for i, item in enumerate(session.items): - nextitem = getnextitem(i) + nextitem = session.items[i+1] if i+1 < len(session.items) else None item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem) if session.shouldstop: raise session.Interrupted(session.shouldstop) @@ -159,7 +163,21 @@ excludeopt = config.getoption("ignore") if excludeopt: ignore_paths.extend([py.path.local(x) for x in excludeopt]) - return path in ignore_paths + + if path in ignore_paths: + return True + + # Skip duplicate paths. + keepduplicates = config.getoption("keepduplicates") + duplicate_paths = config.pluginmanager._duplicatepaths + if not keepduplicates: + if path in duplicate_paths: + return True + else: + duplicate_paths.add(path) + + return False + class FSHookProxy: def __init__(self, fspath, pm, remove_mods): @@ -172,12 +190,22 @@ 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): @@ -249,19 +277,23 @@ """ 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): @@ -276,7 +308,7 @@ if fslocation is None: fslocation = getattr(self, "fspath", None) else: - fslocation = "%s:%s" % fslocation[:2] + fslocation = "%s:%s" % (fslocation[0], fslocation[1] + 1) self.ihook.pytest_logwarning.call_historic(kwargs=dict( code=code, message=message, @@ -392,7 +424,10 @@ if self.config.option.fulltrace: style="long" else: + tb = _pytest._code.Traceback([excinfo.traceback[-1]]) self._prunetraceback(excinfo) + if len(excinfo.traceback) == 0: + excinfo.traceback = tb tbfilter = False # prunetraceback already does it if style == "auto": style = "long" @@ -403,7 +438,13 @@ else: style = "long" - return excinfo.getrepr(funcargs=True, + try: + os.getcwd() + abspath = False + except OSError: + abspath = True + + return excinfo.getrepr(funcargs=True, abspath=abspath, showlocals=self.config.option.showlocals, style=style, tbfilter=tbfilter) @@ -510,7 +551,6 @@ 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 @@ -541,23 +581,18 @@ 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 @@ -649,44 +684,39 @@ return True def _tryconvertpyarg(self, x): - mod = None - path = [os.path.abspath('.')] + sys.path - for name in x.split('.'): - # ignore anything that's not a proper name here - # else something like --pyargs will mess up '.' - # since imp.find_module will actually sometimes work for it - # but it's supposed to be considered a filesystem path - # not a package - if name_re.match(name) is None: - return x - try: - fd, mod, type_ = imp.find_module(name, path) - except ImportError: - return x - else: - if fd is not None: - fd.close() + """Convert a dotted module name to path. - if type_[2] != imp.PKG_DIRECTORY: - path = [os.path.dirname(mod)] - else: - path = [mod] - return mod + """ + import pkgutil + try: + loader = pkgutil.find_loader(x) + except ImportError: + return x + if loader is None: + return x + # This method is sometimes invoked when AssertionRewritingHook, which + # does not define a get_filename method, is already in place: + try: + path = loader.get_filename(x) + except AttributeError: + # Retrieve path from AssertionRewritingHook: + path = loader.modules[x][0].co_filename + if loader.is_package(x): + path = os.path.dirname(path) + return path def _parsearg(self, arg): """ return (fspath, names) tuple after checking the file exists. """ - arg = str(arg) - if self.config.option.pyargs: - arg = self._tryconvertpyarg(arg) parts = str(arg).split("::") + if self.config.option.pyargs: + parts[0] = self._tryconvertpyarg(parts[0]) relpath = parts[0].replace("/", os.sep) 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 -Nru pytest-2.9.2/_pytest/mark.py pytest-3.0.6/_pytest/mark.py --- pytest-2.9.2/_pytest/mark.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/_pytest/mark.py 2017-01-19 13:24:11.000000000 +0000 @@ -19,7 +19,7 @@ help="only run tests which match the given substring expression. " "An expression is a python evaluatable expression " "where all names are substring-matched against test names " - "and their parent classes. Example: -k 'test_method or test " + "and their parent classes. Example: -k 'test_method or test_" "other' matches all test functions and classes whose name " "contains 'test_method' or 'test_other'. " "Additionally keywords are matched to classes and functions " @@ -54,6 +54,8 @@ tw.line() config._ensure_unconfigure() return 0 + + pytest_cmdline_main.tryfirst = True @@ -283,6 +285,21 @@ return self.__class__(self.name, args=args, kwargs=kw) +def extract_argvalue(maybe_marked_args): + # TODO: incorrect mark data, the old code wanst able to collect lists + # individual parametrized argument sets can be wrapped in a series + # of markers in which case we unwrap the values and apply the mark + # at Function init + newmarks = {} + argval = maybe_marked_args + while isinstance(argval, MarkDecorator): + newmark = MarkDecorator(argval.markname, + argval.args[:-1], argval.kwargs) + newmarks[newmark.markname] = newmark + argval = argval.args[-1] + return argval, newmarks + + class MarkInfo: """ Marking object created by :class:`MarkDecorator` instances. """ def __init__(self, name, args, kwargs): diff -Nru pytest-2.9.2/_pytest/monkeypatch.py pytest-3.0.6/_pytest/monkeypatch.py --- pytest-2.9.2/_pytest/monkeypatch.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/_pytest/monkeypatch.py 2017-01-20 16:40:21.000000000 +0000 @@ -5,11 +5,14 @@ from py.builtin import _basestring +import pytest + RE_IMPORT_ERROR_NAME = re.compile("^No module named (.*)$") -def pytest_funcarg__monkeypatch(request): - """The returned ``monkeypatch`` funcarg provides these +@pytest.fixture +def monkeypatch(): + """The returned ``monkeypatch`` fixture provides these helper methods to modify objects, dictionaries or os.environ:: monkeypatch.setattr(obj, name, value, raising=True) @@ -22,13 +25,13 @@ monkeypatch.chdir(path) All modifications will be undone after the requesting - test function has finished. The ``raising`` + test function or fixture has finished. The ``raising`` parameter determines if a KeyError or AttributeError will be raised if the set/deletion operation has no target. """ - mpatch = monkeypatch() - request.addfinalizer(mpatch.undo) - return mpatch + mpatch = MonkeyPatch() + yield mpatch + mpatch.undo() def resolve(name): @@ -93,8 +96,9 @@ notset = Notset() -class monkeypatch: - """ Object keeping a record of setattr/item/env/syspath changes. """ +class MonkeyPatch: + """ Object returned by the ``monkeypatch`` fixture keeping a record of setattr/item/env/syspath changes. + """ def __init__(self): self._setattr = [] @@ -220,10 +224,10 @@ """ Undo previous changes. This call consumes the undo stack. Calling it a second time has no effect unless you do more monkeypatching after the undo call. - + There is generally no need to call `undo()`, since it is called automatically during tear-down. - + Note that the same `monkeypatch` fixture is used across a single test function invocation. If `monkeypatch` is used both by the test function itself and one of the test fixtures, diff -Nru pytest-2.9.2/_pytest/pastebin.py pytest-3.0.6/_pytest/pastebin.py --- pytest-2.9.2/_pytest/pastebin.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/_pytest/pastebin.py 2017-01-19 09:44:52.000000000 +0000 @@ -11,6 +11,7 @@ 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 @@ # 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 @@ 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 @@ else: return 'bad response: ' + response + def pytest_terminal_summary(terminalreporter): import _pytest.config if terminalreporter.config.option.pastebin != "failed": diff -Nru pytest-2.9.2/_pytest/pdb.py pytest-3.0.6/_pytest/pdb.py --- pytest-2.9.2/_pytest/pdb.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/_pytest/pdb.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,109 +0,0 @@ -""" interactive debugging with PDB, the Python Debugger. """ -from __future__ import absolute_import -import pdb -import sys - -import pytest - - -def pytest_addoption(parser): - group = parser.getgroup("general") - group._addoption('--pdb', - action="store_true", dest="usepdb", default=False, - help="start the interactive Python debugger on errors.") - -def pytest_namespace(): - return {'set_trace': pytestPDB().set_trace} - -def pytest_configure(config): - if config.getvalue("usepdb"): - config.pluginmanager.register(PdbInvoke(), 'pdbinvoke') - - old = (pdb.set_trace, pytestPDB._pluginmanager) - def fin(): - pdb.set_trace, pytestPDB._pluginmanager = old - pytestPDB._config = None - pdb.set_trace = pytest.set_trace - pytestPDB._pluginmanager = config.pluginmanager - pytestPDB._config = config - config._cleanup.append(fin) - -class pytestPDB: - """ Pseudo PDB that defers to the real pdb. """ - _pluginmanager = None - _config = None - - def set_trace(self): - """ invoke PDB set_trace debugging, dropping any IO capturing. """ - import _pytest.config - frame = sys._getframe().f_back - if self._pluginmanager is not None: - capman = self._pluginmanager.getplugin("capturemanager") - if capman: - capman.suspendcapture(in_=True) - tw = _pytest.config.create_terminal_writer(self._config) - tw.line() - tw.sep(">", "PDB set_trace (IO-capturing turned off)") - self._pluginmanager.hook.pytest_enter_pdb(config=self._config) - pdb.Pdb().set_trace(frame) - - -class PdbInvoke: - def pytest_exception_interact(self, node, call, report): - capman = node.config.pluginmanager.getplugin("capturemanager") - if capman: - out, err = capman.suspendcapture(in_=True) - sys.stdout.write(out) - sys.stdout.write(err) - _enter_pdb(node, call.excinfo, report) - - def pytest_internalerror(self, excrepr, excinfo): - for line in str(excrepr).split("\n"): - sys.stderr.write("INTERNALERROR> %s\n" %line) - sys.stderr.flush() - tb = _postmortem_traceback(excinfo) - post_mortem(tb) - - -def _enter_pdb(node, excinfo, rep): - # XXX we re-use the TerminalReporter's terminalwriter - # because this seems to avoid some encoding related troubles - # for not completely clear reasons. - tw = node.config.pluginmanager.getplugin("terminalreporter")._tw - tw.line() - tw.sep(">", "traceback") - rep.toterminal(tw) - tw.sep(">", "entering PDB") - tb = _postmortem_traceback(excinfo) - post_mortem(tb) - rep._pdbshown = True - return rep - - -def _postmortem_traceback(excinfo): - # A doctest.UnexpectedException is not useful for post_mortem. - # Use the underlying exception instead: - from doctest import UnexpectedException - if isinstance(excinfo.value, UnexpectedException): - return excinfo.value.exc_info[2] - else: - return excinfo._excinfo[2] - - -def _find_last_non_hidden_frame(stack): - i = max(0, len(stack) - 1) - while i and stack[i][0].f_locals.get("__tracebackhide__", False): - i -= 1 - return i - - -def post_mortem(t): - class Pdb(pdb.Pdb): - def get_stack(self, f, t): - stack, i = pdb.Pdb.get_stack(self, f, t) - if f is None: - i = _find_last_non_hidden_frame(stack) - return stack, i - p = Pdb() - p.reset() - p.interaction(None, t) diff -Nru pytest-2.9.2/_pytest/pytester.py pytest-3.0.6/_pytest/pytester.py --- pytest-2.9.2/_pytest/pytester.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/_pytest/pytester.py 2017-01-20 16:40:36.000000000 +0000 @@ -16,6 +16,7 @@ import py import pytest from _pytest.main import Session, EXIT_OK +from _pytest.assertion.rewrite import AssertionRewritingHook def pytest_addoption(parser): @@ -123,15 +124,18 @@ except KeyError: executable = py.path.local.sysfind(name) if executable: + import subprocess + popen = subprocess.Popen([str(executable), "--version"], + universal_newlines=True, stderr=subprocess.PIPE) + out, err = popen.communicate() if name == "jython": - import subprocess - popen = subprocess.Popen([str(executable), "--version"], - universal_newlines=True, stderr=subprocess.PIPE) - out, err = popen.communicate() if not err or "2.5" not in err: executable = None if "2.5.2" in err: executable = None # http://bugs.jython.org/issue1790 + elif popen.returncode != 0: + # Handle pyenv's 127. + executable = None cache[name] = executable return executable @@ -318,7 +322,8 @@ return LineComp() -def pytest_funcarg__LineMatcher(request): +@pytest.fixture(name='LineMatcher') +def LineMatcher_fixture(request): return LineMatcher @@ -362,6 +367,7 @@ 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 @@ -374,10 +380,10 @@ class Testdir: - """Temporary test directory with tools to test/run py.test itself. + """Temporary test directory with tools to test/run pytest itself. This is based on the ``tmpdir`` fixture but provides a number of - methods which aid with testing py.test itself. Unless + methods which aid with testing pytest itself. Unless :py:meth:`chdir` is used all methods will use :py:attr:`tmpdir` as current working directory. @@ -441,9 +447,9 @@ 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): @@ -473,11 +479,14 @@ 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" @@ -588,7 +597,7 @@ """Return the collection node of a file. This is like :py:meth:`getnode` but uses - :py:meth:`parseconfigure` to create the (configured) py.test + :py:meth:`parseconfigure` to create the (configured) pytest Config instance. :param path: A :py:class:`py.path.local` instance of the file. @@ -656,7 +665,7 @@ :py:class:`HookRecorder` instance. This runs the :py:func:`pytest.main` function to run all of - py.test inside the test process itself like + pytest inside the test process itself like :py:meth:`inline_run`. However the return value is a tuple of the collection items and a :py:class:`HookRecorder` instance. @@ -669,7 +678,7 @@ """Run ``pytest.main()`` in-process, returning a HookRecorder. This runs the :py:func:`pytest.main` function to run all of - py.test inside the test process itself. This means it can + pytest inside the test process itself. This means it can return a :py:class:`HookRecorder` instance which gives more detailed results from then run then can be done by matching stdout/stderr from :py:meth:`runpytest`. @@ -681,9 +690,21 @@ ``pytest.main()`` instance should use. :return: A :py:class:`HookRecorder` instance. - """ + # When running py.test inline any plugins active in the main + # test process are already imported. So this disables the + # 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)) @@ -718,10 +739,13 @@ 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: @@ -755,9 +779,9 @@ return args def parseconfig(self, *args): - """Return a new py.test Config instance from given commandline args. + """Return a new pytest Config instance from given commandline args. - This invokes the py.test bootstrapping code in _pytest.config + This invokes the pytest bootstrapping code in _pytest.config to create a new :py:class:`_pytest.core.PluginManager` and call the pytest_cmdline_parse hook to create new :py:class:`_pytest.config.Config` instance. @@ -777,7 +801,7 @@ return config def parseconfigure(self, *args): - """Return a new py.test configured Config instance. + """Return a new pytest configured Config instance. This returns a new :py:class:`_pytest.config.Config` instance like :py:meth:`parseconfig`, but also calls the @@ -792,7 +816,7 @@ def getitem(self, source, funcname="test_func"): """Return the test item for a test function. - This writes the source to a python file and runs py.test's + This writes the source to a python file and runs pytest's collection on the resulting module, returning the test item for the requested function name. @@ -812,7 +836,7 @@ def getitems(self, source): """Return all test items collected from the module. - This writes the source to a python file and runs py.test's + This writes the source to a python file and runs pytest's collection on the resulting module, returning all test items contained within. @@ -824,7 +848,7 @@ """Return the module collection node for ``source``. This writes ``source`` to a file using :py:meth:`makepyfile` - and then runs the py.test collection on it, returning the + and then runs the pytest collection on it, returning the collection node for the test module. :param source: The source code of the module to collect. @@ -924,7 +948,7 @@ def _getpytestargs(self): # we cannot use "(sys.executable,script)" - # because on windows the script is e.g. a py.test.exe + # because on windows the script is e.g. a pytest.exe return (sys.executable, _pytest_fullpath,) # noqa def runpython(self, script): @@ -939,7 +963,7 @@ return self.run(sys.executable, "-c", command) def runpytest_subprocess(self, *args, **kwargs): - """Run py.test as a subprocess with given arguments. + """Run pytest as a subprocess with given arguments. Any plugins added to the :py:attr:`plugins` list will added using the ``-p`` command line option. Addtionally @@ -967,9 +991,9 @@ return self.run(*args) def spawn_pytest(self, string, expect_timeout=10.0): - """Run py.test using pexpect. + """Run pytest using pexpect. - This makes sure to use the right py.test and sets up the + This makes sure to use the right pytest and sets up the temporary directory locations. The pexpect child is returned. @@ -988,8 +1012,6 @@ 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") @@ -1035,6 +1057,7 @@ def __init__(self, lines): self.lines = lines + self._log_output = [] def str(self): """Return the entire original text.""" @@ -1058,10 +1081,11 @@ for line in lines2: for x in self.lines: if line == x or fnmatch(x, line): - print_("matched: ", repr(line)) + self._log("matched: ", repr(line)) break else: - raise ValueError("line %r not found in output" % line) + self._log("line %r not found in output" % line) + raise ValueError(self._log_text) def get_lines_after(self, fnline): """Return all lines following the given line in the text. @@ -1073,6 +1097,13 @@ return self.lines[i+1:] raise ValueError("line %r not found in output" % fnline) + def _log(self, *args): + self._log_output.append(' '.join((str(x) for x in args))) + + @property + def _log_text(self): + return '\n'.join(self._log_output) + def fnmatch_lines(self, lines2): """Search the text for matching lines. @@ -1082,8 +1113,6 @@ stdout. """ - def show(arg1, arg2): - py.builtin.print_(arg1, arg2, file=sys.stderr) lines2 = self._getlines(lines2) lines1 = self.lines[:] nextline = None @@ -1094,17 +1123,18 @@ while lines1: nextline = lines1.pop(0) if line == nextline: - show("exact match:", repr(line)) + self._log("exact match:", repr(line)) break elif fnmatch(nextline, line): - show("fnmatch:", repr(line)) - show(" with:", repr(nextline)) + self._log("fnmatch:", repr(line)) + self._log(" with:", repr(nextline)) break else: if not nomatchprinted: - show("nomatch:", repr(line)) + self._log("nomatch:", repr(line)) nomatchprinted = True - show(" and:", repr(nextline)) + self._log(" and:", repr(nextline)) extralines.append(nextline) else: - pytest.fail("remains unmatched: %r, see stderr" % (line,)) + self._log("remains unmatched: %r" % (line,)) + pytest.fail(self._log_text) diff -Nru pytest-2.9.2/_pytest/python.py pytest-3.0.6/_pytest/python.py --- pytest-2.9.2/_pytest/python.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/_pytest/python.py 2017-01-19 13:24:11.000000000 +0000 @@ -1,64 +1,37 @@ """ Python test discovery, setup and run of test functions. """ + import fnmatch -import functools import inspect -import re -import types import sys +import collections +import math +from itertools import count import py import pytest -from _pytest._code.code import TerminalRepr -from _pytest.mark import MarkDecorator, MarkerError +from _pytest.mark import MarkerError -try: - import enum -except ImportError: # pragma: no cover - # Only available in Python 3.4+ or as a backport - enum = None import _pytest import _pytest._pluggy as pluggy +from _pytest import fixtures +from _pytest.compat import ( + isclass, isfunction, is_generator, _escape_strings, + REGEX_TYPE, STRING_TYPES, NoneType, NOTSET, + get_real_func, getfslineno, safe_getattr, + getlocation, enum, +) -cutdir2 = py.path.local(_pytest.__file__).dirpath() cutdir1 = py.path.local(pluggy.__file__.rstrip("oc")) - - -NoneType = type(None) -NOTSET = object() -isfunction = inspect.isfunction -isclass = inspect.isclass -callable = py.builtin.callable -# used to work around a python2 exception info leak -exc_clear = getattr(sys, 'exc_clear', lambda: None) -# The type of re.compile objects is not exposed in Python. -REGEX_TYPE = type(re.compile('')) - -_PY3 = sys.version_info > (3, 0) -_PY2 = not _PY3 - - -if hasattr(inspect, 'signature'): - def _format_args(func): - return str(inspect.signature(func)) -else: - def _format_args(func): - return inspect.formatargspec(*inspect.getargspec(func)) - -if sys.version_info[:2] == (2, 6): - def isclass(object): - """ Return true if the object is a class. Overrides inspect.isclass for - python 2.6 because it will return True for objects which always return - something on __getattr__ calls (see #1035). - Backport of https://hg.python.org/cpython/rev/35bf8f7a8edc - """ - return isinstance(object, (type, types.ClassType)) - -def _has_positional_arg(func): - return func.__code__.co_argcount +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 @@ -69,119 +42,9 @@ # 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) - - -def get_real_func(obj): - """ gets the real function object of the (possibly) wrapped object by - functools.wraps or functools.partial. - """ - while hasattr(obj, "__wrapped__"): - obj = obj.__wrapped__ - if isinstance(obj, functools.partial): - obj = obj.func - return obj - -def getfslineno(obj): - # xxx let decorators etc specify a sane ordering - obj = get_real_func(obj) - if hasattr(obj, 'place_as'): - obj = obj.place_as - fslineno = _pytest._code.getfslineno(obj) - assert isinstance(fslineno[1], int), obj - return fslineno - -def getimfunc(func): - try: - return func.__func__ - except AttributeError: - try: - return func.im_func - except AttributeError: - return func - -def safe_getattr(object, name, default): - """ Like getattr but return default upon any Exception. - - Attribute access can potentially fail for 'evil' Python objects. - See issue214 - """ - try: - return getattr(object, name, default) - except Exception: - return default - - -class FixtureFunctionMarker: - def __init__(self, scope, params, - autouse=False, yieldctx=False, ids=None): - self.scope = scope - self.params = params - self.autouse = autouse - self.yieldctx = yieldctx - self.ids = ids - - def __call__(self, function): - if isclass(function): - raise ValueError( - "class fixtures not supported (may be in the future)") - function._pytestfixturefunction = self - return function - - -def fixture(scope="function", params=None, autouse=False, ids=None): - """ (return a) decorator to mark a fixture factory function. - - This decorator can be used (with or or without parameters) to define - a fixture function. The name of the fixture function can later be - referenced to cause its invocation ahead of running tests: test - modules or classes can use the pytest.mark.usefixtures(fixturename) - marker. Test functions can directly use fixture names as input - arguments in which case the fixture instance returned from the fixture - function will be injected. - - :arg scope: the scope for which this fixture is shared, one of - "function" (default), "class", "module", "session". - - :arg params: an optional list of parameters which will cause multiple - invocations of the fixture function and all of the tests - using it. - - :arg autouse: if True, the fixture func is activated for all tests that - can see it. If False (the default) then an explicit - reference is needed to activate the fixture. - - :arg ids: list of string ids each corresponding to the params - so that they are part of the test id. If no ids are provided - they will be generated automatically from the params. + return p != cutdir1 and not p.relto(cutdir2) and not p.relto(cutdir3) - """ - if callable(scope) and params is None and autouse == False: - # direct decoration - return FixtureFunctionMarker( - "function", params, autouse)(scope) - if params is not None and not isinstance(params, (list, tuple)): - params = list(params) - return FixtureFunctionMarker(scope, params, autouse, ids=ids) - -def yield_fixture(scope="function", params=None, autouse=False, ids=None): - """ (return a) decorator to mark a yield-fixture factory function - (EXPERIMENTAL). - - This takes the same arguments as :py:func:`pytest.fixture` but - expects a fixture function to use a ``yield`` instead of a ``return`` - statement to provide a fixture. See - http://pytest.org/en/latest/yieldfixture.html for more info. - """ - if callable(scope) and params is None and autouse == False: - # direct decoration - return FixtureFunctionMarker( - "function", params, autouse, yieldctx=True)(scope) - else: - return FixtureFunctionMarker(scope, params, autouse, - yieldctx=True, ids=ids) -defaultfuncargprefixmarker = fixture() def pyobj_property(name): def get(self): @@ -198,6 +61,13 @@ group.addoption('--fixtures', '--funcargs', action="store_true", dest="showfixtures", default=False, help="show available fixtures, sorted by plugin appearance") + group.addoption( + '--fixtures-per-test', + action="store_true", + dest="show_fixtures_per_test", + default=False, + help="show fixtures per test", + ) parser.addini("usefixtures", type="args", default=[], help="list of default fixtures to be used with this project") parser.addini("python_files", type="args", @@ -219,6 +89,9 @@ if config.option.showfixtures: showfixtures(config) return 0 + if config.option.show_fixtures_per_test: + show_fixtures_per_test(config) + return 0 def pytest_generate_tests(metafunc): @@ -252,27 +125,21 @@ "all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures " ) -def pytest_sessionstart(session): - session._fixturemanager = FixtureManager(session) - @pytest.hookimpl(trylast=True) def pytest_namespace(): raises.Exception = pytest.fail.Exception return { - 'fixture': fixture, - 'yield_fixture': yield_fixture, - 'raises' : raises, + 'raises': raises, + 'approx': approx, 'collect': { - 'Module': Module, 'Class': Class, 'Instance': Instance, - 'Function': Function, 'Generator': Generator, - '_fillfuncargs': fillfixtures} + 'Module': Module, + 'Class': Class, + 'Instance': Instance, + 'Function': Function, + 'Generator': Generator, + } } -@fixture(scope="session") -def pytestconfig(request): - """ the pytest config object with access to command line opts.""" - return request.config - @pytest.hookimpl(trylast=True) def pytest_pyfunc_call(pyfuncitem): @@ -330,12 +197,10 @@ res = list(collector._genfunctions(name, obj)) outcome.force_result(res) -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 +def pytest_make_parametrize_id(config, val): + return None + + class PyobjContext(object): module = pyobj_property("Module") @@ -345,14 +210,16 @@ 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): @@ -418,7 +285,7 @@ def istestfunction(self, obj, name): return ( (self.funcnamefilter(name) or self.isnosetest(obj)) and - safe_getattr(obj, "__call__", False) and getfixturemarker(obj) is None + safe_getattr(obj, "__call__", False) and fixtures.getfixturemarker(obj) is None ) def istestclass(self, obj, name): @@ -495,76 +362,16 @@ yield Function(name, parent=self, fixtureinfo=fixtureinfo) else: # add funcargs() as fixturedefs to fixtureinfo.arg2fixturedefs - add_funcarg_pseudo_fixture_def(self, metafunc, fm) + fixtures.add_funcarg_pseudo_fixture_def(self, metafunc, fm) for callspec in metafunc._calls: - subname = "%s[%s]" %(name, callspec.id) + subname = "%s[%s]" % (name, callspec.id) yield Function(name=subname, parent=self, callspec=callspec, callobj=funcobj, fixtureinfo=fixtureinfo, - keywords={callspec.id:True}) - -def add_funcarg_pseudo_fixture_def(collector, metafunc, fixturemanager): - # this function will transform all collected calls to a functions - # if they use direct funcargs (i.e. direct parametrization) - # because we want later test execution to be able to rely on - # an existing FixtureDef structure for all arguments. - # XXX we can probably avoid this algorithm if we modify CallSpec2 - # to directly care for creating the fixturedefs within its methods. - if not metafunc._calls[0].funcargs: - return # this function call does not have direct parametrization - # collect funcargs of all callspecs into a list of values - arg2params = {} - arg2scope = {} - for callspec in metafunc._calls: - for argname, argvalue in callspec.funcargs.items(): - assert argname not in callspec.params - callspec.params[argname] = argvalue - arg2params_list = arg2params.setdefault(argname, []) - callspec.indices[argname] = len(arg2params_list) - arg2params_list.append(argvalue) - if argname not in arg2scope: - scopenum = callspec._arg2scopenum.get(argname, - scopenum_function) - arg2scope[argname] = scopes[scopenum] - callspec.funcargs.clear() - - # register artificial FixtureDef's so that later at test execution - # time we can rely on a proper FixtureDef to exist for fixture setup. - arg2fixturedefs = metafunc._arg2fixturedefs - for argname, valuelist in arg2params.items(): - # if we have a scope that is higher than function we need - # to make sure we only ever create an according fixturedef on - # a per-scope basis. We thus store and cache the fixturedef on the - # node related to the scope. - scope = arg2scope[argname] - node = None - if scope != "function": - node = get_scope_node(collector, scope) - if node is None: - assert scope == "class" and isinstance(collector, Module) - # use module-level collector for class-scope (for now) - node = collector - if node and argname in node._name2pseudofixturedef: - arg2fixturedefs[argname] = [node._name2pseudofixturedef[argname]] - else: - fixturedef = FixtureDef(fixturemanager, '', argname, - get_direct_param_fixture_func, - arg2scope[argname], - valuelist, False, False) - arg2fixturedefs[argname] = [fixturedef] - if node is not None: - node._name2pseudofixturedef[argname] = fixturedef - - -def get_direct_param_fixture_func(request): - return request.param - -class FuncFixtureInfo: - def __init__(self, argnames, names_closure, name2fixturedefs): - self.argnames = argnames - self.names_closure = names_closure - self.name2fixturedefs = name2fixturedefs + keywords={callspec.id:True}, + originalname=name, + ) def _marked(func, mark): @@ -624,34 +431,72 @@ "unique basename for your test file modules" % e.args ) - #print "imported test module", mod + except ImportError: + 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 = py._builtin._totext(exc_repr) + raise self.CollectError( + "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 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 def setup(self): - setup_module = xunitsetup(self.obj, "setUpModule") + setup_module = _get_xunit_setup_teardown(self.obj, "setUpModule") if setup_module is None: - setup_module = xunitsetup(self.obj, "setup_module") + setup_module = _get_xunit_setup_teardown(self.obj, "setup_module") if setup_module is not None: - #XXX: nose compat hack, move to nose plugin - # if it takes a positional arg, its probably a pytest style one - # so we pass the current module object - if _has_positional_arg(setup_module): - setup_module(self.obj) - else: - setup_module() - fin = getattr(self.obj, 'tearDownModule', None) - if fin is None: - fin = getattr(self.obj, 'teardown_module', None) - if fin is not None: - #XXX: nose compat hack, move to nose plugin - # if it takes a positional arg, it's probably a pytest style one - # so we pass the current module object - if _has_positional_arg(fin): - finalizer = lambda: fin(self.obj) - else: - finalizer = fin - self.addfinalizer(finalizer) + setup_module() + + teardown_module = _get_xunit_setup_teardown(self.obj, 'tearDownModule') + if teardown_module is None: + teardown_module = _get_xunit_setup_teardown(self.obj, 'teardown_module') + if teardown_module is not None: + self.addfinalizer(teardown_module) + + +def _get_xunit_setup_teardown(holder, attr_name, param_obj=None): + """ + Return a callable to perform xunit-style setup or teardown if + the function exists in the ``holder`` object. + The ``param_obj`` parameter is the parameter which will be passed to the function + when the callable is called without arguments, defaults to the ``holder`` object. + Return ``None`` if a suitable callable is not found. + """ + param_obj = param_obj if param_obj is not None else holder + result = _get_xunit_func(holder, attr_name) + if result is not None: + arg_count = result.__code__.co_argcount + if inspect.ismethod(result): + arg_count -= 1 + if arg_count: + return lambda: result(param_obj) + else: + return result + + +def _get_xunit_func(obj, name): + """Return the attribute from the given object to be used as a setup/teardown + xunit-style function, but only if not marked as a fixture to + avoid calling it twice. + """ + meth = getattr(obj, name, None) + if fixtures.getfixturemarker(meth) is None: + return meth class Class(PyCollector): @@ -661,10 +506,14 @@ self.warn("C1", "cannot collect test class %r because it has a " "__init__ constructor" % self.obj.__name__) return [] + elif hasnew(self.obj): + self.warn("C1", "cannot collect test class %r because it has a " + "__new__ constructor" % self.obj.__name__) + return [] return [self._getcustomclass("Instance")(name="()", parent=self)] def setup(self): - setup_class = xunitsetup(self.obj, 'setup_class') + setup_class = _get_xunit_func(self.obj, 'setup_class') if setup_class is not None: setup_class = getattr(setup_class, 'im_func', setup_class) setup_class = getattr(setup_class, '__func__', setup_class) @@ -678,8 +527,7 @@ class Instance(PyCollector): def _getobj(self): - obj = self.parent.obj() - return obj + return self.parent.obj() def collect(self): self.session._fixturemanager.parsefactories(self) @@ -708,12 +556,12 @@ else: setup_name = 'setup_function' teardown_name = 'teardown_function' - setup_func_or_method = xunitsetup(obj, setup_name) + setup_func_or_method = _get_xunit_setup_teardown(obj, setup_name, param_obj=self.obj) if setup_func_or_method is not None: - setup_func_or_method(self.obj) - fin = getattr(obj, teardown_name, None) - if fin is not None: - self.addfinalizer(lambda: fin(self.obj)) + setup_func_or_method() + teardown_func_or_method = _get_xunit_setup_teardown(obj, teardown_name, param_obj=self.obj) + if teardown_func_or_method is not None: + self.addfinalizer(teardown_func_or_method) def _prunetraceback(self, excinfo): if hasattr(self, '_obj') and not self.config.option.fulltrace: @@ -757,6 +605,7 @@ # test generators are seen as collectors but they also # invoke setup/teardown on popular request # (induced by the common "test_*" naming shared with normal tests) + from _pytest import deprecated self.session._setupstate.prepare(self) # see FunctionMixin.setup and test_setupstate_is_preserved_134 self._preservedparent = self.parent.obj @@ -774,6 +623,7 @@ raise ValueError("%r generated tests with non-unique name %r" %(self, name)) seen[name] = True l.append(self.Function(name, self, args=args, callobj=call)) + self.config.warn('C1', deprecated.YIELD_TESTS, fslocation=self.fspath) return l def getcallargs(self, obj): @@ -792,44 +642,24 @@ def hasinit(obj): init = getattr(obj, '__init__', None) if init: - if init != object.__init__: - return True + return init != object.__init__ - -def fillfixtures(function): - """ fill missing funcargs for a test function. """ - try: - request = function._request - except AttributeError: - # XXX this special code path is only expected to execute - # with the oejskit plugin. It uses classes with funcargs - # and we thus have to work a bit to allow this. - fm = function.session._fixturemanager - fi = fm.getfixtureinfo(function.parent, function.obj, None) - function._fixtureinfo = fi - request = function._request = FixtureRequest(function) - request._fillfixtures() - # prune out funcargs for jstests - newfuncargs = {} - for name in fi.argnames: - newfuncargs[name] = function.funcargs[name] - function.funcargs = newfuncargs - else: - request._fillfixtures() +def hasnew(obj): + new = getattr(obj, '__new__', None) + if new: + return new != object.__new__ -_notexists = object() - class CallSpec2(object): def __init__(self, metafunc): self.metafunc = metafunc self.funcargs = {} self._idlist = [] self.params = {} - self._globalid = _notexists + self._globalid = NOTSET self._globalid_args = set() - self._globalparam = _notexists + self._globalparam = NOTSET self._arg2scopenum = {} # used for sorting parametrized resources self.keywords = {} self.indices = {} @@ -855,7 +685,7 @@ try: return self.params[name] except KeyError: - if self._globalparam is _notexists: + if self._globalparam is NOTSET: raise ValueError(name) return self._globalparam @@ -878,55 +708,41 @@ for x in funcargs: self._checkargnotcontained(x) self.funcargs.update(funcargs) - if id is not _notexists: + if id is not NOTSET: self._idlist.append(id) - if param is not _notexists: - assert self._globalparam is _notexists + if param is not NOTSET: + assert self._globalparam is NOTSET self._globalparam = param for arg in funcargs: - self._arg2scopenum[arg] = scopenum_function + self._arg2scopenum[arg] = fixtures.scopenum_function -class FuncargnamesCompatAttr: - """ helper class so that Metafunc, Function and FixtureRequest - don't need to each define the "funcargnames" compatibility attribute. - """ - @property - def funcargnames(self): - """ alias attribute for ``fixturenames`` for pre-2.3 compatibility""" - return self.fixturenames - -class Metafunc(FuncargnamesCompatAttr): +class Metafunc(fixtures.FuncargnamesCompatAttr): """ Metafunc objects are passed to the ``pytest_generate_tests`` hook. They help to inspect a test function and to generate tests according to test configuration or values specified in the class or module where a test function is defined. - - :ivar fixturenames: set of fixture names required by the test function - - :ivar function: underlying python test function - - :ivar cls: class object where the test function is defined in or ``None``. - - :ivar module: the module object where the test function is defined in. - - :ivar config: access to the :class:`_pytest.config.Config` object for the - test session. - - :ivar funcargnames: - .. deprecated:: 2.3 - Use ``fixturenames`` instead. """ def __init__(self, function, fixtureinfo, config, cls=None, module=None): + #: access to the :class:`_pytest.config.Config` object for the test session self.config = config + + #: the module object where the test function is defined in. self.module = module + + #: underlying python test function self.function = function + + #: set of fixture names required by the test function self.fixturenames = fixtureinfo.names_closure - self._arg2fixturedefs = fixtureinfo.name2fixturedefs + + #: class object where the test function is defined in or ``None``. self.cls = cls + self._calls = [] self._ids = py.builtin.set() + self._arg2fixturedefs = fixtureinfo.name2fixturedefs def parametrize(self, argnames, argvalues, indirect=False, ids=None, scope=None): @@ -954,7 +770,8 @@ :arg ids: list of string ids, or a callable. If strings, each is corresponding to the argvalues so that they are - part of the test id. + part of the test id. If None is given as id of specific test, the + automatically generated id for that argument will be used. If callable, it should take one argument (a single argvalue) and return a string or return None. If None, the automatically generated id for that argument will be used. @@ -966,20 +783,16 @@ It will also override any fixture-function defined scope, allowing to set a dynamic scope using test context or configuration. """ + from _pytest.fixtures import scope2index + from _pytest.mark import extract_argvalue + from py.io import saferepr - # individual parametrized argument sets can be wrapped in a series - # of markers in which case we unwrap the values and apply the mark - # at Function init - newkeywords = {} unwrapped_argvalues = [] - for i, argval in enumerate(argvalues): - while isinstance(argval, MarkDecorator): - newmark = MarkDecorator(argval.markname, - argval.args[:-1], argval.kwargs) - newmarks = newkeywords.setdefault(i, {}) - newmarks[newmark.markname] = newmark - argval = argval.args[-1] + newkeywords = [] + for maybe_marked_args in argvalues: + argval, newmarks = extract_argvalue(maybe_marked_args) unwrapped_argvalues.append(argval) + newkeywords.append(newmarks) argvalues = unwrapped_argvalues if not isinstance(argnames, (tuple, list)): @@ -987,24 +800,30 @@ if len(argnames) == 1: argvalues = [(val,) for val in argvalues] if not argvalues: - argvalues = [(_notexists,) * len(argnames)] + argvalues = [(NOTSET,) * len(argnames)] # we passed a empty list to parameterize, skip that test # fs, lineno = getfslineno(self.function) newmark = pytest.mark.skip( reason="got empty parameter set %r, function %s at %s:%d" % ( argnames, self.function.__name__, fs, lineno)) - newmarks = newkeywords.setdefault(0, {}) - newmarks[newmark.markname] = newmark - + newkeywords = [{newmark.markname: newmark}] if scope is None: - scope = "function" - scopenum = scopes.index(scope) + scope = _find_parametrized_scope(argnames, self._arg2fixturedefs, indirect) + + scopenum = scope2index( + scope, descr='call to {0}'.format(self.parametrize)) valtypes = {} for arg in argnames: if arg not in self.fixturenames: - raise ValueError("%r uses no fixture %r" %(self.function, arg)) + if isinstance(indirect, (tuple, list)): + name = 'fixture' if arg in indirect else 'argument' + else: + name = 'fixture' if indirect else 'argument' + raise ValueError( + "%r uses no %s %r" % ( + self.function, name, arg)) if indirect is True: valtypes = dict.fromkeys(argnames, "params") @@ -1014,30 +833,34 @@ valtypes = dict.fromkeys(argnames, "funcargs") for arg in indirect: if arg not in argnames: - raise ValueError("indirect given to %r: fixture %r doesn't exist" %( + raise ValueError("indirect given to %r: fixture %r doesn't exist" % ( self.function, arg)) valtypes[arg] = "params" idfn = None if callable(ids): idfn = ids ids = None - if ids and len(ids) != len(argvalues): - raise ValueError('%d tests specified with %d ids' %( - len(argvalues), len(ids))) - if not ids: - ids = idmaker(argnames, argvalues, idfn) + if ids: + if len(ids) != len(argvalues): + raise ValueError('%d tests specified with %d ids' %( + len(argvalues), len(ids))) + for id_value in ids: + if id_value is not None and not isinstance(id_value, py.builtin._basestring): + msg = 'ids must be list of strings, found: %s (type: %s)' + raise ValueError(msg % (saferepr(id_value), type(id_value).__name__)) + ids = idmaker(argnames, argvalues, idfn, ids, self.config) newcalls = [] for callspec in self._calls or [CallSpec2(self)]: - for param_index, valset in enumerate(argvalues): + elements = zip(ids, argvalues, newkeywords, count()) + for a_id, valset, keywords, param_index in elements: assert len(valset) == len(argnames) newcallspec = callspec.copy(self) - newcallspec.setmulti(valtypes, argnames, valset, ids[param_index], - newkeywords.get(param_index, {}), scopenum, - param_index) + newcallspec.setmulti(valtypes, argnames, valset, a_id, + keywords, scopenum, param_index) newcalls.append(newcallspec) self._calls = newcalls - def addcall(self, funcargs=None, id=_notexists, param=_notexists): + def addcall(self, funcargs=None, id=NOTSET, param=NOTSET): """ (deprecated, use parametrize) Add a new call to the underlying test function during the collection phase of a test run. Note that request.addcall() is called during the test collection phase prior and @@ -1062,7 +885,7 @@ funcargs = {} if id is None: raise ValueError("id=None not allowed") - if id is _notexists: + if id is NOTSET: id = len(self._calls) id = str(id) if id in self._ids: @@ -1074,82 +897,140 @@ self._calls.append(cs) -if _PY3: - import codecs +def _find_parametrized_scope(argnames, arg2fixturedefs, indirect): + """Find the most appropriate scope for a parametrized call based on its arguments. - def _escape_bytes(val): - """ - If val is pure ascii, returns it as a str(), otherwise escapes - into a sequence of escaped bytes: - b'\xc3\xb4\xc5\xd6' -> u'\\xc3\\xb4\\xc5\\xd6' - - note: - the obvious "v.decode('unicode-escape')" will return - valid utf-8 unicode if it finds them in the string, but we - want to return escaped bytes for any byte, even if they match - a utf-8 string. - """ - if val: - # source: http://goo.gl/bGsnwC - encoded_bytes, _ = codecs.escape_encode(val) - return encoded_bytes.decode('ascii') - else: - # empty bytes crashes codecs.escape_encode (#1087) - return '' -else: - def _escape_bytes(val): - """ - In py2 bytes and str are the same type, so return it unchanged if it - is a full ascii string, otherwise escape it into its binary form. - """ - try: - return val.decode('ascii') - except UnicodeDecodeError: - return val.encode('string-escape') + When there's at least one direct argument, always use "function" scope. + + When a test function is parametrized and all its arguments are indirect + (e.g. fixtures), return the most narrow scope based on the fixtures used. + + Related to issue #1832, based on code posted by @Kingdread. + """ + from _pytest.fixtures import scopes + indirect_as_list = isinstance(indirect, (list, tuple)) + all_arguments_are_fixtures = indirect is True or \ + indirect_as_list and len(indirect) == argnames + if all_arguments_are_fixtures: + fixturedefs = arg2fixturedefs or {} + used_scopes = [fixturedef[0].scope for name, fixturedef in fixturedefs.items()] + if used_scopes: + # Takes the most narrow scope from used fixtures + for scope in reversed(scopes): + if scope in used_scopes: + return scope + + return 'function' -def _idval(val, argname, idx, idfn): +def _idval(val, argname, idx, idfn, config=None): if idfn: try: s = idfn(val) if s: - return s + return _escape_strings(s) except Exception: pass - if isinstance(val, bytes): - return _escape_bytes(val) - elif isinstance(val, (float, int, str, bool, NoneType)): + if config: + hook_id = config.hook.pytest_make_parametrize_id(config=config, val=val) + if hook_id: + return hook_id + + if isinstance(val, STRING_TYPES): + return _escape_strings(val) + elif isinstance(val, (float, int, bool, NoneType)): return str(val) elif isinstance(val, REGEX_TYPE): - return _escape_bytes(val.pattern) if isinstance(val.pattern, bytes) else val.pattern + return _escape_strings(val.pattern) elif enum is not None and isinstance(val, enum.Enum): return str(val) elif isclass(val) and hasattr(val, '__name__'): return val.__name__ - elif _PY2 and isinstance(val, unicode): - # special case for python 2: if a unicode string is - # convertible to ascii, return it as an str() object instead - try: - return str(val) - except UnicodeError: - # fallthrough - pass return str(argname)+str(idx) -def _idvalset(idx, valset, argnames, idfn): - this_id = [_idval(val, argname, idx, idfn) - for val, argname in zip(valset, argnames)] - return "-".join(this_id) +def _idvalset(idx, valset, argnames, idfn, ids, config=None): + if ids is None or (idx >= len(ids) or ids[idx] is None): + this_id = [_idval(val, argname, idx, idfn, config) + for val, argname in zip(valset, argnames)] + return "-".join(this_id) + else: + return _escape_strings(ids[idx]) -def idmaker(argnames, argvalues, idfn=None): - ids = [_idvalset(valindex, valset, argnames, idfn) +def idmaker(argnames, argvalues, idfn=None, ids=None, config=None): + ids = [_idvalset(valindex, valset, argnames, idfn, ids, config) for valindex, valset in enumerate(argvalues)] - if len(set(ids)) < len(ids): - # user may have provided a bad idfn which means the ids are not unique - ids = [str(i) + testid for i, testid in enumerate(ids)] + if len(set(ids)) != len(ids): + # The ids are not unique + duplicates = [testid for testid in ids if ids.count(testid) > 1] + counters = collections.defaultdict(lambda: 0) + for index, testid in enumerate(ids): + if testid in duplicates: + ids[index] = testid + str(counters[testid]) + counters[testid] += 1 return ids + +def show_fixtures_per_test(config): + from _pytest.main import wrap_session + return wrap_session(config, _show_fixtures_per_test) + + +def _show_fixtures_per_test(config, session): + import _pytest.config + session.perform_collect() + curdir = py.path.local() + tw = _pytest.config.create_terminal_writer(config) + verbose = config.getvalue("verbose") + + def get_best_rel(func): + loc = getlocation(func, curdir) + return curdir.bestrelpath(loc) + + def write_fixture(fixture_def): + argname = fixture_def.argname + + if verbose <= 0 and argname.startswith("_"): + return + if verbose > 0: + bestrel = get_best_rel(fixture_def.func) + funcargspec = "{0} -- {1}".format(argname, bestrel) + else: + funcargspec = argname + tw.line(funcargspec, green=True) + + INDENT = ' {0}' + fixture_doc = fixture_def.func.__doc__ + + if fixture_doc: + for line in fixture_doc.strip().split('\n'): + tw.line(INDENT.format(line.strip())) + else: + tw.line(INDENT.format('no docstring available'), red=True) + + def write_item(item): + name2fixturedefs = item._fixtureinfo.name2fixturedefs + + if not name2fixturedefs: + # The given test item does not use any fixtures + return + bestrel = get_best_rel(item.function) + + tw.line() + tw.sep('-', 'fixtures used by {0}'.format(item.name)) + tw.sep('-', '({0})'.format(bestrel)) + for argname, fixture_defs in sorted(name2fixturedefs.items()): + assert fixture_defs is not None + if not fixture_defs: + continue + # The last fixture def item in the list is expected + # to be the one used by the test item + write_fixture(fixture_defs[-1]) + + for item in session.items: + write_item(item) + + def showfixtures(config): from _pytest.main import wrap_session return wrap_session(config, _showfixtures_main) @@ -1164,12 +1045,17 @@ fm = session._fixturemanager available = [] + seen = set() + for argname, fixturedefs in fm._arg2fixturedefs.items(): assert fixturedefs is not None if not fixturedefs: continue for fixturedef in fixturedefs: loc = getlocation(fixturedef.func, curdir) + if (fixturedef.argname, loc) in seen: + continue + seen.add((fixturedef.argname, loc)) available.append((len(fixturedef.baseid), fixturedef.func.__module__, curdir.bestrelpath(loc), @@ -1199,18 +1085,12 @@ tw.line(" %s: no docstring available" %(loc,), red=True) -def getlocation(function, curdir): - import inspect - fn = py.path.local(inspect.getfile(function)) - lineno = py.builtin._getcode(function).co_firstlineno - if fn.relto(curdir): - fn = fn.relto(curdir) - return "%s:%d" %(fn, lineno+1) # builtin pytest.raises helper def raises(expected_exception, *args, **kwargs): - """ assert that a code block/function call raises ``expected_exception`` + """ + Assert that a code block/function call raises ``expected_exception`` and raise a failure exception otherwise. This helper produces a ``ExceptionInfo()`` object (see below). @@ -1221,6 +1101,18 @@ >>> with raises(ZeroDivisionError): ... 1/0 + .. versionchanged:: 2.10 + + In the context manager form you may use the keyword argument + ``message`` to specify a custom failure message:: + + >>> with raises(ZeroDivisionError, message="Expecting ZeroDivisionError"): + ... pass + Traceback (most recent call last): + ... + Failed: Expecting ZeroDivisionError + + .. note:: When using ``pytest.raises`` as a context manager, it's worthwhile to @@ -1229,19 +1121,21 @@ 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:: @@ -1296,8 +1190,12 @@ elif not isclass(expected_exception): raise TypeError(msg % type(expected_exception)) + message = "DID NOT RAISE {0}".format(expected_exception) + if not args: - return RaisesContext(expected_exception) + if "message" in kwargs: + message = kwargs.pop("message") + return RaisesContext(expected_exception, message) elif isinstance(args[0], str): code, = args assert isinstance(code, str) @@ -1318,11 +1216,12 @@ func(*args[1:], **kwargs) except expected_exception: return _pytest._code.ExceptionInfo() - pytest.fail("DID NOT RAISE {0}".format(expected_exception)) + pytest.fail(message) class RaisesContext(object): - def __init__(self, expected_exception): + def __init__(self, expected_exception, message): self.expected_exception = expected_exception + self.message = message self.excinfo = None def __enter__(self): @@ -1332,7 +1231,7 @@ def __exit__(self, *tp): __tracebackhide__ = True if tp[0] is None: - pytest.fail("DID NOT RAISE") + pytest.fail(self.message) if sys.version_info < (2, 7): # py26: on __exit__() exc_value often does not contain the # exception value. @@ -1341,20 +1240,274 @@ 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 + +class approx(object): + """ + Assert that two numbers (or two sets of numbers) are equal to each other + within some tolerance. + + Due to the `intricacies of floating-point arithmetic`__, numbers that we + would intuitively expect to be equal are not always so:: + + >>> 0.1 + 0.2 == 0.3 + False + + __ https://docs.python.org/3/tutorial/floatingpoint.html + + This problem is commonly encountered when writing tests, e.g. when making + sure that floating-point values are what you expect them to be. One way to + deal with this problem is to assert that two floating-point numbers are + equal to within some appropriate tolerance:: + + >>> abs((0.1 + 0.2) - 0.3) < 1e-6 + True + + However, comparisons like this are tedious to write and difficult to + understand. Furthermore, absolute comparisons like the one above are + usually discouraged because there's no tolerance that works well for all + situations. ``1e-6`` is good for numbers around ``1``, but too small for + very big numbers and too big for very small ones. It's better to express + the tolerance as a fraction of the expected value, but relative comparisons + like that are even more difficult to write correctly and concisely. + + The ``approx`` class performs floating-point comparisons using a syntax + that's as intuitive as possible:: + + >>> from pytest import approx + >>> 0.1 + 0.2 == approx(0.3) + True + + The same syntax also works on sequences of numbers:: + + >>> (0.1 + 0.2, 0.2 + 0.4) == approx((0.3, 0.6)) + True + + By default, ``approx`` considers numbers within a relative tolerance of + ``1e-6`` (i.e. one part in a million) of its expected value to be equal. + This treatment would lead to surprising results if the expected value was + ``0.0``, because nothing but ``0.0`` itself is relatively close to ``0.0``. + To handle this case less surprisingly, ``approx`` also considers numbers + within an absolute tolerance of ``1e-12`` of its expected value to be + equal. Infinite numbers are another special case. They are only + considered equal to themselves, regardless of the relative tolerance. Both + the relative and absolute tolerances can be changed by passing arguments to + the ``approx`` constructor:: + + >>> 1.0001 == approx(1) + False + >>> 1.0001 == approx(1, rel=1e-3) + True + >>> 1.0001 == approx(1, abs=1e-3) + True + + If you specify ``abs`` but not ``rel``, the comparison will not consider + the relative tolerance at all. In other words, two numbers that are within + the default relative tolerance of ``1e-6`` will still be considered unequal + if they exceed the specified absolute tolerance. If you specify both + ``abs`` and ``rel``, the numbers will be considered equal if either + tolerance is met:: + + >>> 1 + 1e-8 == approx(1) + True + >>> 1 + 1e-8 == approx(1, abs=1e-12) + False + >>> 1 + 1e-8 == approx(1, rel=1e-6, abs=1e-12) + True + + If you're thinking about using ``approx``, then you might want to know how + it compares to other good ways of comparing floating-point numbers. All of + these algorithms are based on relative and absolute tolerances and should + agree for the most part, but they do have meaningful differences: + + - ``math.isclose(a, b, rel_tol=1e-9, abs_tol=0.0)``: True if the relative + tolerance is met w.r.t. either ``a`` or ``b`` or if the absolute + tolerance is met. Because the relative tolerance is calculated w.r.t. + both ``a`` and ``b``, this test is symmetric (i.e. neither ``a`` nor + ``b`` is a "reference value"). You have to specify an absolute tolerance + if you want to compare to ``0.0`` because there is no tolerance by + default. Only available in python>=3.5. `More information...`__ + + __ https://docs.python.org/3/library/math.html#math.isclose + + - ``numpy.isclose(a, b, rtol=1e-5, atol=1e-8)``: True if the difference + between ``a`` and ``b`` is less that the sum of the relative tolerance + w.r.t. ``b`` and the absolute tolerance. Because the relative tolerance + is only calculated w.r.t. ``b``, this test is asymmetric and you can + think of ``b`` as the reference value. Support for comparing sequences + is provided by ``numpy.allclose``. `More information...`__ + + __ http://docs.scipy.org/doc/numpy-1.10.0/reference/generated/numpy.isclose.html + + - ``unittest.TestCase.assertAlmostEqual(a, b)``: True if ``a`` and ``b`` + are within an absolute tolerance of ``1e-7``. No relative tolerance is + considered and the absolute tolerance cannot be changed, so this function + is not appropriate for very large or very small numbers. Also, it's only + available in subclasses of ``unittest.TestCase`` and it's ugly because it + doesn't follow PEP8. `More information...`__ + + __ https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertAlmostEqual + + - ``a == pytest.approx(b, rel=1e-6, abs=1e-12)``: True if the relative + tolerance is met w.r.t. ``b`` or if the absolute tolerance is met. + Because the relative tolerance is only calculated w.r.t. ``b``, this test + is asymmetric and you can think of ``b`` as the reference value. In the + special case that you explicitly specify an absolute tolerance but not a + relative tolerance, only the absolute tolerance is considered. + """ + + def __init__(self, expected, rel=None, abs=None): + self.expected = expected + self.abs = abs + self.rel = rel + + def __repr__(self): + return ', '.join(repr(x) for x in self.expected) + + def __eq__(self, actual): + from collections import Iterable + if not isinstance(actual, Iterable): + actual = [actual] + if len(actual) != len(self.expected): + return False + return all(a == x for a, x in zip(actual, self.expected)) + + __hash__ = None + + def __ne__(self, actual): + return not (actual == self) + + @property + def expected(self): + # Regardless of whether the user-specified expected value is a number + # or a sequence of numbers, return a list of ApproxNotIterable objects + # that can be compared against. + from collections import Iterable + approx_non_iter = lambda x: ApproxNonIterable(x, self.rel, self.abs) + if isinstance(self._expected, Iterable): + return [approx_non_iter(x) for x in self._expected] + else: + return [approx_non_iter(self._expected)] + + @expected.setter + def expected(self, expected): + self._expected = expected + + +class ApproxNonIterable(object): + """ + Perform approximate comparisons for single numbers only. + + In other words, the ``expected`` attribute for objects of this class must + be some sort of number. This is in contrast to the ``approx`` class, where + the ``expected`` attribute can either be a number of a sequence of numbers. + This class is responsible for making comparisons, while ``approx`` is + responsible for abstracting the difference between numbers and sequences of + numbers. Although this class can stand on its own, it's only meant to be + used within ``approx``. + """ + + def __init__(self, expected, rel=None, abs=None): + self.expected = expected + self.abs = abs + 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): + return str(self.expected) + + # If a sensible tolerance can't be calculated, self.tolerance will + # raise a ValueError. In this case, display '???'. + try: + vetted_tolerance = '{:.1e}'.format(self.tolerance) + except ValueError: + vetted_tolerance = '???' + + if sys.version_info[0] == 2: + return '{0} +- {1}'.format(self.expected, vetted_tolerance) + else: + return u'{0} \u00b1 {1}'.format(self.expected, vetted_tolerance) + + def __eq__(self, actual): + # Short-circuit exact equality. + if actual == self.expected: + return True + + # Infinity shouldn't be approximately equal to anything but itself, but + # if there's a relative tolerance, it will be infinite and infinity + # will seem approximately equal to everything. The equal-to-itself + # case would have been short circuited above, so here we can just + # return false if the expected value is infinite. The abs() call is + # for compatibility with complex numbers. + if math.isinf(abs(self.expected)): + return False + + # 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) + + @property + def tolerance(self): + set_default = lambda x, default: x if x is not None else default + + # Figure out what the absolute tolerance should be. ``self.abs`` is + # either None or a value specified by the user. + absolute_tolerance = set_default(self.abs, 1e-12) + + if absolute_tolerance < 0: + raise ValueError("absolute tolerance can't be negative: {}".format(absolute_tolerance)) + if math.isnan(absolute_tolerance): + raise ValueError("absolute tolerance can't be NaN.") + + # If the user specified an absolute tolerance but not a relative one, + # just return the absolute tolerance. + if self.rel is None: + if self.abs is not None: + return absolute_tolerance + + # Figure out what the relative tolerance should be. ``self.rel`` is + # either None or a value specified by the user. This is done after + # we've made sure the user didn't ask for an absolute tolerance only, + # because we don't want to raise errors about the relative tolerance if + # we aren't even going to use it. + relative_tolerance = set_default(self.rel, 1e-6) * abs(self.expected) + + if relative_tolerance < 0: + raise ValueError("relative tolerance can't be negative: {}".format(absolute_tolerance)) + if math.isnan(relative_tolerance): + raise ValueError("relative tolerance can't be NaN.") + + # Return the larger of the relative and absolute tolerances. + return max(relative_tolerance, absolute_tolerance) + # # the basic pytest Function item # -class Function(FunctionMixin, pytest.Item, FuncargnamesCompatAttr): +class Function(FunctionMixin, pytest.Item, fixtures.FuncargnamesCompatAttr): """ a Function Item is responsible for setting up and executing a Python test function. """ _genid = None def __init__(self, name, parent, args=None, config=None, callspec=None, callobj=NOTSET, keywords=None, session=None, - fixtureinfo=None): + fixtureinfo=None, originalname=None): super(Function, self).__init__(name, parent, config=config, session=session) self._args = args @@ -1376,6 +1529,12 @@ self.fixturenames = fixtureinfo.names_closure self._initrequest() + #: original function name, without any decorations (for example + #: parametrization adds a ``"[...]"`` suffix to function names). + #: + #: .. versionadded:: 3.0 + self.originalname = originalname + def _initrequest(self): self.funcargs = {} if self._isyieldedfunction(): @@ -1388,7 +1547,7 @@ self._genid = callspec.id if hasattr(callspec, "param"): self.param = callspec.param - self._request = FixtureRequest(self) + self._request = fixtures.FixtureRequest(self) @property def function(self): @@ -1416,885 +1575,4 @@ def setup(self): super(Function, self).setup() - fillfixtures(self) - - -scope2props = dict(session=()) -scope2props["module"] = ("fspath", "module") -scope2props["class"] = scope2props["module"] + ("cls",) -scope2props["instance"] = scope2props["class"] + ("instance", ) -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 - - -class FixtureRequest(FuncargnamesCompatAttr): - """ A request for a fixture from a test or fixture function. - - A request object gives access to the requesting test context - and has an optional ``param`` attribute in case - the fixture is parametrized indirectly. - """ - - def __init__(self, pyfuncitem): - self._pyfuncitem = pyfuncitem - #: fixture for which this request is being performed - self.fixturename = None - #: Scope string, one of "function", "class", "module", "session" - self.scope = "function" - self._funcargs = {} - self._fixturedefs = {} - fixtureinfo = pyfuncitem._fixtureinfo - self._arg2fixturedefs = fixtureinfo.name2fixturedefs.copy() - self._arg2index = {} - self.fixturenames = fixtureinfo.names_closure - self._fixturemanager = pyfuncitem.session._fixturemanager - - @property - def node(self): - """ underlying collection node (depends on current request scope)""" - return self._getscopeitem(self.scope) - - - def _getnextfixturedef(self, argname): - fixturedefs = self._arg2fixturedefs.get(argname, None) - if fixturedefs is None: - # we arrive here because of a a dynamic call to - # getfuncargvalue(argname) usage which was naturally - # not known at parsing/collection time - fixturedefs = self._fixturemanager.getfixturedefs( - argname, self._pyfuncitem.parent.nodeid) - self._arg2fixturedefs[argname] = fixturedefs - # fixturedefs list is immutable so we maintain a decreasing index - index = self._arg2index.get(argname, 0) - 1 - if fixturedefs is None or (-index > len(fixturedefs)): - raise FixtureLookupError(argname, self) - self._arg2index[argname] = index - return fixturedefs[index] - - @property - def config(self): - """ the pytest config object associated with this request. """ - return self._pyfuncitem.config - - - @scopeproperty() - def function(self): - """ test function object if the request has a per-function scope. """ - return self._pyfuncitem.obj - - @scopeproperty("class") - def cls(self): - """ class (can be None) where the test function was collected. """ - clscol = self._pyfuncitem.getparent(pytest.Class) - if clscol: - return clscol.obj - - @property - def instance(self): - """ instance (can be None) on which test function was collected. """ - # unittest support hack, see _pytest.unittest.TestCaseFunction - try: - return self._pyfuncitem._testcase - except AttributeError: - function = getattr(self, "function", None) - if function is not None: - return py.builtin._getimself(function) - - @scopeproperty() - def module(self): - """ python module object where the test function was collected. """ - return self._pyfuncitem.getparent(pytest.Module).obj - - @scopeproperty() - def fspath(self): - """ the file system path of the test module which collected this test. """ - return self._pyfuncitem.fspath - - @property - def keywords(self): - """ keywords/markers dictionary for the underlying node. """ - return self.node.keywords - - @property - def session(self): - """ pytest session object. """ - return self._pyfuncitem.session - - def addfinalizer(self, finalizer): - """ add finalizer/teardown function to be called after the - last test within the requesting test context finished - execution. """ - # XXX usually this method is shadowed by fixturedef specific ones - self._addfinalizer(finalizer, scope=self.scope) - - def _addfinalizer(self, finalizer, scope): - colitem = self._getscopeitem(scope) - self._pyfuncitem.session._setupstate.addfinalizer( - finalizer=finalizer, colitem=colitem) - - def applymarker(self, marker): - """ Apply a marker to a single test function invocation. - This method is useful if you don't want to have a keyword/marker - on all function invocations. - - :arg marker: a :py:class:`_pytest.mark.MarkDecorator` object - created by a call to ``pytest.mark.NAME(...)``. - """ - try: - self.node.keywords[marker.markname] = marker - except AttributeError: - raise ValueError(marker) - - def raiseerror(self, msg): - """ raise a FixtureLookupError with the given message. """ - raise self._fixturemanager.FixtureLookupError(None, self, msg) - - def _fillfixtures(self): - item = self._pyfuncitem - fixturenames = getattr(item, "fixturenames", self.fixturenames) - for argname in fixturenames: - if argname not in item.funcargs: - item.funcargs[argname] = self.getfuncargvalue(argname) - - def cached_setup(self, setup, teardown=None, scope="module", extrakey=None): - """ (deprecated) Return a testing resource managed by ``setup`` & - ``teardown`` calls. ``scope`` and ``extrakey`` determine when the - ``teardown`` function will be called so that subsequent calls to - ``setup`` would recreate the resource. With pytest-2.3 you often - do not need ``cached_setup()`` as you can directly declare a scope - on a fixture function and register a finalizer through - ``request.addfinalizer()``. - - :arg teardown: function receiving a previously setup resource. - :arg setup: a no-argument function creating a resource. - :arg scope: a string value out of ``function``, ``class``, ``module`` - or ``session`` indicating the caching lifecycle of the resource. - :arg extrakey: added to internal caching key of (funcargname, scope). - """ - if not hasattr(self.config, '_setupcache'): - self.config._setupcache = {} # XXX weakref? - cachekey = (self.fixturename, self._getscopeitem(scope), extrakey) - cache = self.config._setupcache - try: - val = cache[cachekey] - except KeyError: - self._check_scope(self.fixturename, self.scope, scope) - val = setup() - cache[cachekey] = val - if teardown is not None: - def finalizer(): - del cache[cachekey] - teardown(val) - self._addfinalizer(finalizer, scope=scope) - return val - - def getfuncargvalue(self, argname): - """ Dynamically retrieve a named fixture function argument. - - As of pytest-2.3, it is easier and usually better to access other - fixture values by stating it as an input argument in the fixture - function. If you only can decide about using another fixture at test - setup time, you may use this function to retrieve it inside a fixture - function body. - """ - return self._get_active_fixturedef(argname).cached_result[0] - - def _get_active_fixturedef(self, argname): - try: - return self._fixturedefs[argname] - except KeyError: - try: - fixturedef = self._getnextfixturedef(argname) - except FixtureLookupError: - if argname == "request": - class PseudoFixtureDef: - cached_result = (self, [0], None) - scope = "function" - return PseudoFixtureDef - raise - # remove indent to prevent the python3 exception - # from leaking into the call - result = self._getfuncargvalue(fixturedef) - self._funcargs[argname] = result - self._fixturedefs[argname] = fixturedef - return fixturedef - - def _get_fixturestack(self): - current = self - l = [] - while 1: - fixturedef = getattr(current, "_fixturedef", None) - if fixturedef is None: - l.reverse() - return l - l.append(fixturedef) - current = current._parent_request - - def _getfuncargvalue(self, fixturedef): - # prepare a subrequest object before calling fixture function - # (latter managed by fixturedef) - argname = fixturedef.argname - funcitem = self._pyfuncitem - scope = fixturedef.scope - try: - param = funcitem.callspec.getparam(argname) - except (AttributeError, ValueError): - param = NOTSET - param_index = 0 - else: - # indices might not be set if old-style metafunc.addcall() was used - param_index = funcitem.callspec.indices.get(argname, 0) - # if a parametrize invocation set a scope it will override - # the static scope defined with the fixture function - paramscopenum = funcitem.callspec._arg2scopenum.get(argname) - if paramscopenum is not None: - scope = scopes[paramscopenum] - - subrequest = SubRequest(self, scope, param, param_index, fixturedef) - - # check if a higher-level scoped fixture accesses a lower level one - subrequest._check_scope(argname, self.scope, scope) - - # clear sys.exc_info before invoking the fixture (python bug?) - # if its not explicitly cleared it will leak into the call - exc_clear() - try: - # call the fixture function - val = fixturedef.execute(request=subrequest) - finally: - # if fixture function failed it might have registered finalizers - self.session._setupstate.addfinalizer(fixturedef.finish, - subrequest.node) - return val - - def _check_scope(self, argname, invoking_scope, requested_scope): - if argname == "request": - return - if scopemismatch(invoking_scope, requested_scope): - # try to report something helpful - lines = self._factorytraceback() - pytest.fail("ScopeMismatch: You tried to access the %r scoped " - "fixture %r with a %r scoped request object, " - "involved factories\n%s" %( - (requested_scope, argname, invoking_scope, "\n".join(lines))), - pytrace=False) - - def _factorytraceback(self): - lines = [] - for fixturedef in self._get_fixturestack(): - factory = fixturedef.func - fs, lineno = getfslineno(factory) - p = self._pyfuncitem.session.fspath.bestrelpath(fs) - args = _format_args(factory) - lines.append("%s:%d: def %s%s" %( - p, lineno, factory.__name__, args)) - return lines - - def _getscopeitem(self, scope): - if scope == "function": - # this might also be a non-function Item despite its attribute name - return self._pyfuncitem - node = get_scope_node(self._pyfuncitem, scope) - if node is None and scope == "class": - # fallback to function item itself - node = self._pyfuncitem - assert node - return node - - def __repr__(self): - return "" %(self.node) - - -class SubRequest(FixtureRequest): - """ a sub request for handling getting a fixture from a - test function/fixture. """ - def __init__(self, request, scope, param, param_index, fixturedef): - self._parent_request = request - self.fixturename = fixturedef.argname - if param is not NOTSET: - self.param = param - self.param_index = param_index - self.scope = scope - self._fixturedef = fixturedef - self.addfinalizer = fixturedef.addfinalizer - self._pyfuncitem = request._pyfuncitem - self._funcargs = request._funcargs - self._fixturedefs = request._fixturedefs - self._arg2fixturedefs = request._arg2fixturedefs - self._arg2index = request._arg2index - self.fixturenames = request.fixturenames - self._fixturemanager = request._fixturemanager - - def __repr__(self): - return "" % (self.fixturename, self._pyfuncitem) - - -class ScopeMismatchError(Exception): - """ A fixture function tries to use a different fixture function which - 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) - - -class FixtureLookupError(LookupError): - """ could not return a requested Fixture (missing or invalid). """ - def __init__(self, argname, request, msg=None): - self.argname = argname - self.request = request - self.fixturestack = request._get_fixturestack() - self.msg = msg - - def formatrepr(self): - tblines = [] - addline = tblines.append - stack = [self.request._pyfuncitem.obj] - stack.extend(map(lambda x: x.func, self.fixturestack)) - msg = self.msg - if msg is not None: - # the last fixture raise an error, let's present - # it at the requesting side - stack = stack[:-1] - for function in stack: - fspath, lineno = getfslineno(function) - try: - lines, _ = inspect.getsourcelines(get_real_func(function)) - except (IOError, IndexError): - error_msg = "file %s, line %s: source code not available" - addline(error_msg % (fspath, lineno+1)) - else: - addline("file %s, line %s" % (fspath, lineno+1)) - for i, line in enumerate(lines): - line = line.rstrip() - addline(" " + line) - if line.lstrip().startswith('def'): - break - - if msg is None: - fm = self.request._fixturemanager - available = [] - for name, fixturedef in fm._arg2fixturedefs.items(): - parentid = self.request._pyfuncitem.parent.nodeid - faclist = list(fm._matchfactories(fixturedef, parentid)) - if faclist: - available.append(name) - msg = "fixture %r not found" % (self.argname,) - msg += "\n available fixtures: %s" %(", ".join(available),) - msg += "\n use 'py.test --fixtures [testpath]' for help on them." - - return FixtureLookupErrorRepr(fspath, lineno, tblines, msg, self.argname) - -class FixtureLookupErrorRepr(TerminalRepr): - def __init__(self, filename, firstlineno, tblines, errorstring, argname): - self.tblines = tblines - self.errorstring = errorstring - self.filename = filename - self.firstlineno = firstlineno - self.argname = argname - - def toterminal(self, tw): - #tw.line("FixtureLookupError: %s" %(self.argname), red=True) - for tbline in self.tblines: - tw.line(tbline.rstrip()) - for line in self.errorstring.split("\n"): - tw.line(" " + line.strip(), red=True) - tw.line() - tw.line("%s:%d" % (self.filename, self.firstlineno+1)) - -class FixtureManager: - """ - pytest fixtures definitions and information is stored and managed - from this class. - - During collection fm.parsefactories() is called multiple times to parse - fixture function definitions into FixtureDef objects and internal - data structures. - - During collection of test functions, metafunc-mechanics instantiate - a FuncFixtureInfo object which is cached per node/func-name. - This FuncFixtureInfo object is later retrieved by Function nodes - which themselves offer a fixturenames attribute. - - The FuncFixtureInfo object holds information about fixtures and FixtureDefs - relevant for a particular function. An initial list of fixtures is - assembled like this: - - - ini-defined usefixtures - - autouse-marked fixtures along the collection chain up from the function - - usefixtures markers at module/class/function level - - test function funcargs - - Subsequently the funcfixtureinfo.fixturenames attribute is computed - as the closure of the fixtures needed to setup the initial fixtures, - i. e. fixtures needed by fixture functions themselves are appended - to the fixturenames list. - - Upon the test-setup phases all fixturenames are instantiated, retrieved - by a lookup of their FuncFixtureInfo. - """ - - _argprefix = "pytest_funcarg__" - FixtureLookupError = FixtureLookupError - FixtureLookupErrorRepr = FixtureLookupErrorRepr - - def __init__(self, session): - self.session = session - self.config = session.config - self._arg2fixturedefs = {} - self._holderobjseen = set() - self._arg2finish = {} - self._nodeid_and_autousenames = [("", self.config.getini("usefixtures"))] - session.config.pluginmanager.register(self, "funcmanage") - - - def getfixtureinfo(self, node, func, cls, funcargs=True): - if funcargs and not hasattr(node, "nofuncargs"): - if cls is not None: - startindex = 1 - else: - startindex = None - argnames = getfuncargnames(func, startindex) - else: - argnames = () - usefixtures = getattr(func, "usefixtures", None) - initialnames = argnames - if usefixtures is not None: - initialnames = usefixtures.args + initialnames - fm = node.session._fixturemanager - names_closure, arg2fixturedefs = fm.getfixtureclosure(initialnames, - node) - return FuncFixtureInfo(argnames, names_closure, arg2fixturedefs) - - def pytest_plugin_registered(self, plugin): - nodeid = None - try: - p = py.path.local(plugin.__file__) - except AttributeError: - pass - else: - # construct the base nodeid which is later used to check - # what fixtures are visible for particular tests (as denoted - # by their test id) - if p.basename.startswith("conftest.py"): - nodeid = p.dirpath().relto(self.config.rootdir) - if p.sep != "/": - nodeid = nodeid.replace(p.sep, "/") - self.parsefactories(plugin, nodeid) - - def _getautousenames(self, nodeid): - """ return a tuple of fixture names to be used. """ - autousenames = [] - for baseid, basenames in self._nodeid_and_autousenames: - if nodeid.startswith(baseid): - if baseid: - i = len(baseid) - nextchar = nodeid[i:i+1] - if nextchar and nextchar not in ":/": - continue - autousenames.extend(basenames) - # make sure autousenames are sorted by scope, scopenum 0 is session - autousenames.sort( - key=lambda x: self._arg2fixturedefs[x][-1].scopenum) - return autousenames - - def getfixtureclosure(self, fixturenames, parentnode): - # collect the closure of all fixtures , starting with the given - # fixturenames as the initial set. As we have to visit all - # factory definitions anyway, we also return a arg2fixturedefs - # mapping so that the caller can reuse it and does not have - # to re-discover fixturedefs again for each fixturename - # (discovering matching fixtures for a given name/node is expensive) - - parentid = parentnode.nodeid - fixturenames_closure = self._getautousenames(parentid) - def merge(otherlist): - for arg in otherlist: - if arg not in fixturenames_closure: - fixturenames_closure.append(arg) - merge(fixturenames) - arg2fixturedefs = {} - lastlen = -1 - while lastlen != len(fixturenames_closure): - lastlen = len(fixturenames_closure) - for argname in fixturenames_closure: - if argname in arg2fixturedefs: - continue - fixturedefs = self.getfixturedefs(argname, parentid) - if fixturedefs: - arg2fixturedefs[argname] = fixturedefs - merge(fixturedefs[-1].argnames) - return fixturenames_closure, arg2fixturedefs - - def pytest_generate_tests(self, metafunc): - for argname in metafunc.fixturenames: - faclist = metafunc._arg2fixturedefs.get(argname) - if faclist: - fixturedef = faclist[-1] - if fixturedef.params is not None: - func_params = getattr(getattr(metafunc.function, 'parametrize', None), 'args', [[None]]) - # skip directly parametrized arguments - argnames = func_params[0] - if not isinstance(argnames, (tuple, list)): - argnames = [x.strip() for x in argnames.split(",") if x.strip()] - if argname not in func_params and argname not in argnames: - metafunc.parametrize(argname, fixturedef.params, - indirect=True, scope=fixturedef.scope, - ids=fixturedef.ids) - else: - continue # will raise FixtureLookupError at setup time - - def pytest_collection_modifyitems(self, items): - # separate parametrized setups - items[:] = reorder_items(items) - - def parsefactories(self, node_or_obj, nodeid=NOTSET, unittest=False): - if nodeid is not NOTSET: - holderobj = node_or_obj - else: - holderobj = node_or_obj.obj - nodeid = node_or_obj.nodeid - if holderobj in self._holderobjseen: - return - self._holderobjseen.add(holderobj) - autousenames = [] - for name in dir(holderobj): - obj = getattr(holderobj, name, None) - # fixture functions have a pytest_funcarg__ prefix (pre-2.3 style) - # or are "@pytest.fixture" marked - marker = getfixturemarker(obj) - if marker is None: - if not name.startswith(self._argprefix): - continue - if not callable(obj): - continue - marker = defaultfuncargprefixmarker - name = name[len(self._argprefix):] - elif not isinstance(marker, FixtureFunctionMarker): - # magic globals with __getattr__ might have got us a wrong - # fixture attribute - continue - else: - assert not name.startswith(self._argprefix) - fixturedef = FixtureDef(self, nodeid, name, obj, - marker.scope, marker.params, - yieldctx=marker.yieldctx, - unittest=unittest, ids=marker.ids) - faclist = self._arg2fixturedefs.setdefault(name, []) - if fixturedef.has_location: - faclist.append(fixturedef) - else: - # fixturedefs with no location are at the front - # so this inserts the current fixturedef after the - # existing fixturedefs from external plugins but - # before the fixturedefs provided in conftests. - i = len([f for f in faclist if not f.has_location]) - faclist.insert(i, fixturedef) - if marker.autouse: - autousenames.append(name) - if autousenames: - self._nodeid_and_autousenames.append((nodeid or '', autousenames)) - - def getfixturedefs(self, argname, nodeid): - try: - fixturedefs = self._arg2fixturedefs[argname] - except KeyError: - return None - else: - return tuple(self._matchfactories(fixturedefs, nodeid)) - - def _matchfactories(self, fixturedefs, nodeid): - for fixturedef in fixturedefs: - if nodeid.startswith(fixturedef.baseid): - yield fixturedef - - -def fail_fixturefunc(fixturefunc, msg): - fs, lineno = getfslineno(fixturefunc) - location = "%s:%s" % (fs, lineno+1) - source = _pytest._code.Source(fixturefunc) - pytest.fail(msg + ":\n\n" + str(source.indent()) + "\n" + location, - pytrace=False) - -def call_fixture_func(fixturefunc, request, kwargs, yieldctx): - if yieldctx: - if not is_generator(fixturefunc): - fail_fixturefunc(fixturefunc, - msg="yield_fixture requires yield statement in function") - iter = fixturefunc(**kwargs) - next = getattr(iter, "__next__", None) - if next is None: - next = getattr(iter, "next") - res = next() - def teardown(): - try: - next() - except StopIteration: - pass - else: - fail_fixturefunc(fixturefunc, - "yield_fixture function has more than one 'yield'") - request.addfinalizer(teardown) - else: - if is_generator(fixturefunc): - fail_fixturefunc(fixturefunc, - msg="pytest.fixture functions cannot use ``yield``. " - "Instead write and return an inner function/generator " - "and let the consumer call and iterate over it.") - res = fixturefunc(**kwargs) - return res - -class FixtureDef: - """ A container for a factory definition. """ - def __init__(self, fixturemanager, baseid, argname, func, scope, params, - yieldctx, unittest=False, ids=None): - self._fixturemanager = fixturemanager - self.baseid = baseid or '' - self.has_location = baseid is not None - self.func = func - self.argname = argname - self.scope = scope - self.scopenum = scopes.index(scope or "function") - self.params = params - startindex = unittest and 1 or None - self.argnames = getfuncargnames(func, startindex=startindex) - self.yieldctx = yieldctx - self.unittest = unittest - self.ids = ids - self._finalizer = [] - - def addfinalizer(self, finalizer): - self._finalizer.append(finalizer) - - def finish(self): - try: - while self._finalizer: - func = self._finalizer.pop() - func() - finally: - # even if finalization fails, we invalidate - # the cached fixture value - if hasattr(self, "cached_result"): - del self.cached_result - - def execute(self, request): - # get required arguments and register our own finish() - # with their finalization - kwargs = {} - for argname in self.argnames: - fixturedef = request._get_active_fixturedef(argname) - result, arg_cache_key, exc = fixturedef.cached_result - request._check_scope(argname, request.scope, fixturedef.scope) - kwargs[argname] = result - if argname != "request": - fixturedef.addfinalizer(self.finish) - - my_cache_key = request.param_index - cached_result = getattr(self, "cached_result", None) - if cached_result is not None: - result, cache_key, err = cached_result - if my_cache_key == cache_key: - if err is not None: - py.builtin._reraise(*err) - else: - return result - # we have a previous but differently parametrized fixture instance - # so we need to tear it down before creating a new one - self.finish() - assert not hasattr(self, "cached_result") - - fixturefunc = self.func - - if self.unittest: - if request.instance is not None: - # bind the unbound method to the TestCase instance - fixturefunc = self.func.__get__(request.instance) - else: - # the fixture function needs to be bound to the actual - # request.instance so that code working with "self" behaves - # as expected. - if request.instance is not None: - fixturefunc = getimfunc(self.func) - if fixturefunc != self.func: - fixturefunc = fixturefunc.__get__(request.instance) - - try: - result = call_fixture_func(fixturefunc, request, kwargs, - self.yieldctx) - except Exception: - self.cached_result = (None, my_cache_key, sys.exc_info()) - raise - self.cached_result = (result, my_cache_key, None) - return result - - def __repr__(self): - return ("" % - (self.argname, self.scope, self.baseid)) - -def num_mock_patch_args(function): - """ return number of arguments used up by mock arguments (if any) """ - patchings = getattr(function, "patchings", None) - if not patchings: - return 0 - mock = sys.modules.get("mock", sys.modules.get("unittest.mock", None)) - if mock is not None: - return len([p for p in patchings - if not p.attribute_name and p.new is mock.DEFAULT]) - return len(patchings) - - -def getfuncargnames(function, startindex=None): - # XXX merge with main.py's varnames - #assert not isclass(function) - realfunction = function - while hasattr(realfunction, "__wrapped__"): - realfunction = realfunction.__wrapped__ - if startindex is None: - startindex = inspect.ismethod(function) and 1 or 0 - if realfunction != function: - startindex += num_mock_patch_args(function) - function = realfunction - if isinstance(function, functools.partial): - argnames = inspect.getargs(_pytest._code.getrawcode(function.func))[0] - partial = function - argnames = argnames[len(partial.args):] - if partial.keywords: - for kw in partial.keywords: - argnames.remove(kw) - else: - argnames = inspect.getargs(_pytest._code.getrawcode(function))[0] - defaults = getattr(function, 'func_defaults', - getattr(function, '__defaults__', None)) or () - numdefaults = len(defaults) - if numdefaults: - return tuple(argnames[startindex:-numdefaults]) - return tuple(argnames[startindex:]) - -# algorithm for sorting on a per-parametrized resource setup basis -# it is called for scopenum==0 (session) first and performs sorting -# down to the lower scopes such as to minimize number of "high scope" -# setups and teardowns - -def reorder_items(items): - argkeys_cache = {} - for scopenum in range(0, scopenum_function): - argkeys_cache[scopenum] = d = {} - for item in items: - keys = set(get_parametrized_fixture_keys(item, scopenum)) - if keys: - d[item] = keys - return reorder_items_atscope(items, set(), argkeys_cache, 0) - -def reorder_items_atscope(items, ignore, argkeys_cache, scopenum): - if scopenum >= scopenum_function or len(items) < 3: - return items - items_done = [] - while 1: - items_before, items_same, items_other, newignore = \ - slice_items(items, ignore, argkeys_cache[scopenum]) - items_before = reorder_items_atscope( - items_before, ignore, argkeys_cache,scopenum+1) - if items_same is None: - # nothing to reorder in this scope - assert items_other is None - return items_done + items_before - items_done.extend(items_before) - items = items_same + items_other - ignore = newignore - - -def slice_items(items, ignore, scoped_argkeys_cache): - # we pick the first item which uses a fixture instance in the - # requested scope and which we haven't seen yet. We slice the input - # items list into a list of items_nomatch, items_same and - # items_other - if scoped_argkeys_cache: # do we need to do work at all? - it = iter(items) - # first find a slicing key - for i, item in enumerate(it): - argkeys = scoped_argkeys_cache.get(item) - if argkeys is not None: - argkeys = argkeys.difference(ignore) - if argkeys: # found a slicing key - slicing_argkey = argkeys.pop() - items_before = items[:i] - items_same = [item] - items_other = [] - # now slice the remainder of the list - for item in it: - argkeys = scoped_argkeys_cache.get(item) - if argkeys and slicing_argkey in argkeys and \ - slicing_argkey not in ignore: - items_same.append(item) - else: - items_other.append(item) - newignore = ignore.copy() - newignore.add(slicing_argkey) - return (items_before, items_same, items_other, newignore) - return items, None, None, None - -def get_parametrized_fixture_keys(item, scopenum): - """ return list of keys for all parametrized arguments which match - the specified scope. """ - assert scopenum < scopenum_function # function - try: - cs = item.callspec - except AttributeError: - pass - else: - # cs.indictes.items() is random order of argnames but - # then again different functions (items) can change order of - # arguments so it doesn't matter much probably - for argname, param_index in cs.indices.items(): - if cs._arg2scopenum[argname] != scopenum: - continue - if scopenum == 0: # session - key = (argname, param_index) - elif scopenum == 1: # module - key = (argname, param_index, item.fspath) - elif scopenum == 2: # class - key = (argname, param_index, item.fspath, item.cls) - yield key - - -def xunitsetup(obj, name): - meth = getattr(obj, name, None) - if getfixturemarker(meth) is None: - return meth - -def getfixturemarker(obj): - """ return fixturemarker or None if it doesn't exist or raised - 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 - return None - -scopename2class = { - 'class': Class, - 'module': Module, - 'function': pytest.Item, -} -def get_scope_node(node, scope): - cls = scopename2class.get(scope) - if cls is None: - if scope == "session": - return node.session - raise ValueError("unknown scope") - return node.getparent(cls) + fixtures.fillfixtures(self) diff -Nru pytest-2.9.2/_pytest/recwarn.py pytest-3.0.6/_pytest/recwarn.py --- pytest-2.9.2/_pytest/recwarn.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/_pytest/recwarn.py 2017-01-20 16:40:21.000000000 +0000 @@ -36,8 +36,13 @@ 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 @@ 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 -Nru pytest-2.9.2/_pytest/resultlog.py pytest-3.0.6/_pytest/resultlog.py --- pytest-2.9.2/_pytest/resultlog.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/_pytest/resultlog.py 2016-08-27 09:59:07.000000000 +0000 @@ -9,7 +9,7 @@ group = parser.getgroup("terminal reporting", "resultlog plugin options") group.addoption('--resultlog', '--result-log', action="store", metavar="path", default=None, - help="path for machine-readable result log.") + help="DEPRECATED path for machine-readable result log.") def pytest_configure(config): resultlog = config.option.resultlog @@ -22,6 +22,9 @@ config._resultlog = ResultLog(config, logfile) config.pluginmanager.register(config._resultlog) + from _pytest.deprecated import RESULT_LOG + config.warn('C1', RESULT_LOG) + def pytest_unconfigure(config): resultlog = getattr(config, '_resultlog', None) if resultlog: diff -Nru pytest-2.9.2/_pytest/runner.py pytest-3.0.6/_pytest/runner.py --- pytest-2.9.2/_pytest/runner.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/_pytest/runner.py 2017-01-19 13:24:11.000000000 +0000 @@ -73,7 +73,10 @@ rep = call_and_report(item, "setup", log) reports = [rep] if rep.passed: - reports.append(call_and_report(item, "call", log)) + if item.config.option.setupshow: + show_test_item(item) + if not item.config.option.setuponly: + reports.append(call_and_report(item, "call", log)) reports.append(call_and_report(item, "teardown", log, nextitem=nextitem)) # after all teardown hooks have been called @@ -83,6 +86,16 @@ item.funcargs = None return reports +def show_test_item(item): + """Show test function, parameters and the fixtures of the test item.""" + tw = item.config.get_terminal_writer() + tw.line() + tw.write(' ' * 8) + tw.write(item._nodeid) + used_fixtures = sorted(item._fixtureinfo.name2fixturedefs.keys()) + if used_fixtures: + tw.write(' (fixtures used: {0})'.format(', '.join(used_fixtures))) + def pytest_runtest_setup(item): item.session._setupstate.prepare(item) @@ -198,6 +211,36 @@ if name.startswith(prefix): yield prefix, content + @property + def longreprtext(self): + """ + Read-only property that returns the full string representation + of ``longrepr``. + + .. versionadded:: 3.0 + """ + tw = py.io.TerminalWriter(stringio=True) + tw.hasmarkup = False + self.toterminal(tw) + exc = tw.stringio.getvalue() + return exc.strip() + + @property + def capstdout(self): + """Return captured text from stdout, if capturing is enabled + + .. versionadded:: 3.0 + """ + return ''.join(content for (prefix, content) in self.get_sections('Captured stdout')) + + @property + def capstderr(self): + """Return captured text from stderr, if capturing is enabled + + .. versionadded:: 3.0 + """ + return ''.join(content for (prefix, content) in self.get_sections('Captured stderr')) + passed = property(lambda x: x.outcome == "passed") failed = property(lambda x: x.outcome == "failed") skipped = property(lambda x: x.outcome == "skipped") @@ -263,8 +306,10 @@ #: one of 'setup', 'call', 'teardown' to indicate runtest phase. self.when = when - #: list of (secname, data) extra information which needs to - #: marshallable + #: list of pairs ``(str, str)`` of extra information which needs to + #: marshallable. Used by pytest to add captured text + #: from ``stdout`` and ``stderr``, but may be used by other plugins + #: to add arbitrary information to reports. self.sections = list(sections) #: time it took to run just the test @@ -447,10 +492,16 @@ # in order to have Skipped exception printing shorter/nicer __module__ = 'builtins' + def __init__(self, msg=None, pytrace=True, allow_module_level=False): + OutcomeException.__init__(self, msg=msg, pytrace=pytrace) + self.allow_module_level = allow_module_level + + class Failed(OutcomeException): """ raised from an explicit call to pytest.fail() """ __module__ = 'builtins' + class Exit(KeyboardInterrupt): """ raised for immediate program exits (no tracebacks/summaries)""" def __init__(self, msg="unknown reason"): @@ -464,8 +515,10 @@ __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 @@ -474,8 +527,11 @@ """ __tracebackhide__ = True raise Skipped(msg=msg) + + skip.Exception = Skipped + def fail(msg="", pytrace=True): """ explicitly fail an currently-executing test with the given Message. @@ -484,6 +540,8 @@ """ __tracebackhide__ = True raise Failed(msg=msg, pytrace=pytrace) + + fail.Exception = Failed @@ -494,10 +552,14 @@ """ __tracebackhide__ = True compile(modname, '', 'eval') # to catch syntaxerrors + should_skip = False try: __import__(modname) except ImportError: - skip("could not import %r" %(modname,)) + # Do not raise chained exception here(#1485) + should_skip = True + if should_skip: + raise Skipped("could not import %r" %(modname,), allow_module_level=True) mod = sys.modules[modname] if minversion is None: return mod @@ -506,10 +568,11 @@ try: from pkg_resources import parse_version as pv except ImportError: - skip("we have a required version for %r but can not import " - "no pkg_resources to parse version strings." %(modname,)) + raise Skipped("we have a required version for %r but can not import " + "pkg_resources to parse version strings." % (modname,), + allow_module_level=True) if verattr is None or pv(verattr) < pv(minversion): - skip("module %r has __version__ %r, required is: %r" %( - modname, verattr, minversion)) + raise Skipped("module %r has __version__ %r, required is: %r" %( + modname, verattr, minversion), allow_module_level=True) return mod diff -Nru pytest-2.9.2/_pytest/setuponly.py pytest-3.0.6/_pytest/setuponly.py --- pytest-2.9.2/_pytest/setuponly.py 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/_pytest/setuponly.py 2017-01-19 09:44:52.000000000 +0000 @@ -0,0 +1,72 @@ +import pytest +import sys + + +def pytest_addoption(parser): + group = parser.getgroup("debugconfig") + group.addoption('--setuponly', '--setup-only', action="store_true", + help="only setup fixtures, do not execute tests.") + group.addoption('--setupshow', '--setup-show', action="store_true", + help="show setup of fixtures while executing tests.") + + +@pytest.hookimpl(hookwrapper=True) +def pytest_fixture_setup(fixturedef, request): + yield + config = request.config + if config.option.setupshow: + if hasattr(request, 'param'): + # Save the fixture parameter so ._show_fixture_action() can + # display it now and during the teardown (in .finish()). + if fixturedef.ids: + if callable(fixturedef.ids): + fixturedef.cached_param = fixturedef.ids(request.param) + else: + fixturedef.cached_param = fixturedef.ids[ + request.param_index] + else: + fixturedef.cached_param = request.param + _show_fixture_action(fixturedef, 'SETUP') + + +def pytest_fixture_post_finalizer(fixturedef): + if hasattr(fixturedef, "cached_result"): + config = fixturedef._fixturemanager.config + if config.option.setupshow: + _show_fixture_action(fixturedef, 'TEARDOWN') + if hasattr(fixturedef, "cached_param"): + del fixturedef.cached_param + + +def _show_fixture_action(fixturedef, msg): + config = fixturedef._fixturemanager.config + capman = config.pluginmanager.getplugin('capturemanager') + if capman: + out, err = capman.suspendcapture() + + tw = config.get_terminal_writer() + tw.line() + tw.write(' ' * 2 * fixturedef.scopenum) + tw.write('{step} {scope} {fixture}'.format( + step=msg.ljust(8), # align the output to TEARDOWN + scope=fixturedef.scope[0].upper(), + fixture=fixturedef.argname)) + + if msg == 'SETUP': + deps = sorted(arg for arg in fixturedef.argnames if arg != 'request') + if deps: + tw.write(' (fixtures used: {0})'.format(', '.join(deps))) + + if hasattr(fixturedef, 'cached_param'): + tw.write('[{0}]'.format(fixturedef.cached_param)) + + if capman: + capman.resumecapture() + sys.stdout.write(out) + sys.stderr.write(err) + + +@pytest.hookimpl(tryfirst=True) +def pytest_cmdline_main(config): + if config.option.setuponly: + config.option.setupshow = True diff -Nru pytest-2.9.2/_pytest/setupplan.py pytest-3.0.6/_pytest/setupplan.py --- pytest-2.9.2/_pytest/setupplan.py 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/_pytest/setupplan.py 2016-08-20 20:00:30.000000000 +0000 @@ -0,0 +1,23 @@ +import pytest + + +def pytest_addoption(parser): + group = parser.getgroup("debugconfig") + group.addoption('--setupplan', '--setup-plan', action="store_true", + help="show what fixtures and tests would be executed but " + "don't execute anything.") + + +@pytest.hookimpl(tryfirst=True) +def pytest_fixture_setup(fixturedef, request): + # Will return a dummy fixture if the setuponly option is provided. + if request.config.option.setupplan: + fixturedef.cached_result = (None, None, None) + return fixturedef.cached_result + + +@pytest.hookimpl(tryfirst=True) +def pytest_cmdline_main(config): + if config.option.setupplan: + config.option.setuponly = True + config.option.setupshow = True diff -Nru pytest-2.9.2/_pytest/skipping.py pytest-3.0.6/_pytest/skipping.py --- pytest-2.9.2/_pytest/skipping.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/_pytest/skipping.py 2017-01-22 17:44:30.000000000 +0000 @@ -25,8 +25,10 @@ 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 @@ ) 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 @@ """ xfail an executing test or setup functions with the given reason.""" __tracebackhide__ = True raise XFailed(reason) + + xfail.Exception = XFailed @@ -108,11 +112,7 @@ def _getglobals(self): d = {'os': os, 'sys': sys, 'config': self.item.config} - func = self.item.obj - try: - d.update(func.__globals__) - except AttributeError: - d.update(func.func_globals) + d.update(self.item.obj.__globals__) return d def _istrue(self): @@ -228,9 +228,16 @@ evalskip = getattr(item, '_evalskip', None) # unitttest special case, see setting of _unexpectedsuccess if hasattr(item, '_unexpectedsuccess') and rep.when == "call": - # we need to translate into how pytest encodes xpass - rep.wasxfail = "reason: " + repr(item._unexpectedsuccess) - rep.outcome = "failed" + from _pytest.compat import _is_unittest_unexpected_success_a_failure + if item._unexpectedsuccess: + rep.longrepr = "Unexpected success: {0}".format(item._unexpectedsuccess) + else: + rep.longrepr = "Unexpected success" + if _is_unittest_unexpected_success_a_failure(): + rep.outcome = "failed" + else: + rep.outcome = "passed" + rep.wasxfail = rep.longrepr elif item.config.option.runxfail: pass # don't interefere elif call.excinfo and call.excinfo.errisinstance(pytest.xfail.Exception): @@ -245,8 +252,15 @@ rep.outcome = "skipped" rep.wasxfail = evalxfail.getexplanation() elif call.when == "call": - rep.outcome = "failed" # xpass outcome - rep.wasxfail = evalxfail.getexplanation() + strict_default = item.config.getini('xfail_strict') + is_strict_xfail = evalxfail.get('strict', strict_default) + explanation = evalxfail.getexplanation() + if is_strict_xfail: + rep.outcome = "failed" + rep.longrepr = "[XPASS(strict)] {0}".format(explanation) + else: + rep.outcome = "passed" + rep.wasxfail = explanation elif evalskip is not None and rep.skipped and type(rep.longrepr) is tuple: # skipped by mark.skipif; change the location of the failure # to point to the item definition, otherwise it will display @@ -260,7 +274,7 @@ if hasattr(report, "wasxfail"): if report.skipped: return "xfailed", "x", "xfail" - elif report.failed: + elif report.passed: return "xpassed", "X", ("XPASS", {'yellow': True}) # called by the terminalreporter instance/plugin diff -Nru pytest-2.9.2/_pytest/standalonetemplate.py pytest-3.0.6/_pytest/standalonetemplate.py --- pytest-2.9.2/_pytest/standalonetemplate.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/_pytest/standalonetemplate.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,89 +0,0 @@ -#! /usr/bin/env python - -# Hi There! -# You may be wondering what this giant blob of binary data here is, you might -# even be worried that we're up to something nefarious (good for you for being -# paranoid!). This is a base64 encoding of a zip file, this zip file contains -# a fully functional basic pytest script. -# -# Pytest is a thing that tests packages, pytest itself is a package that some- -# one might want to install, especially if they're looking to run tests inside -# some package they want to install. Pytest has a lot of code to collect and -# execute tests, and other such sort of "tribal knowledge" that has been en- -# coded in its code base. Because of this we basically include a basic copy -# of pytest inside this blob. We do this because it let's you as a maintainer -# or application developer who wants people who don't deal with python much to -# easily run tests without installing the complete pytest package. -# -# If you're wondering how this is created: you can create it yourself if you -# have a complete pytest installation by using this command on the command- -# line: ``py.test --genscript=runtests.py``. - -sources = """ -@SOURCES@""" - -import sys -import base64 -import zlib - -class DictImporter(object): - def __init__(self, sources): - self.sources = sources - - def find_module(self, fullname, path=None): - if fullname == "argparse" and sys.version_info >= (2,7): - # we were generated with = (3, 0): - exec("def do_exec(co, loc): exec(co, loc)\n") - import pickle - sources = sources.encode("ascii") # ensure bytes - sources = pickle.loads(zlib.decompress(base64.decodebytes(sources))) - else: - import cPickle as pickle - exec("def do_exec(co, loc): exec co in loc\n") - sources = pickle.loads(zlib.decompress(base64.decodestring(sources))) - - importer = DictImporter(sources) - sys.meta_path.insert(0, importer) - entry = "@ENTRY@" - do_exec(entry, locals()) # noqa diff -Nru pytest-2.9.2/_pytest/terminal.py pytest-3.0.6/_pytest/terminal.py --- pytest-2.9.2/_pytest/terminal.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/_pytest/terminal.py 2017-01-19 13:24:11.000000000 +0000 @@ -20,16 +20,18 @@ group._addoption('-q', '--quiet', action="count", dest="quiet", default=0, help="decrease verbosity."), group._addoption('-r', - action="store", dest="reportchars", default=None, metavar="chars", + action="store", dest="reportchars", default='', metavar="chars", help="show extra test summary info as specified by chars (f)ailed, " - "(E)error, (s)skipped, (x)failed, (X)passed (w)pytest-warnings " - "(p)passed, (P)passed with output, (a)all except pP.") + "(E)error, (s)skipped, (x)failed, (X)passed, " + "(p)passed, (P)passed with output, (a)all except pP. " + "The pytest warnings are displayed at all times except when " + "--disable-pytest-warnings is set") + group._addoption('--disable-pytest-warnings', default=False, + dest='disablepytestwarnings', action='store_true', + help='disable warnings summary, overrides -r w flag') group._addoption('-l', '--showlocals', action="store_true", dest="showlocals", default=False, help="show locals in tracebacks (disabled by default).") - group._addoption('--report', - action="store", dest="report", default=None, metavar="opts", - help="(deprecated, use -r)") group._addoption('--tb', metavar="style", action="store", dest="tbstyle", default='auto', choices=['auto', 'long', 'short', 'no', 'line', 'native'], @@ -54,18 +56,11 @@ def getreportopt(config): reportopts = "" - optvalue = config.option.report - if optvalue: - py.builtin.print_("DEPRECATED: use -r instead of --report option.", - file=sys.stderr) - if optvalue: - for setting in optvalue.split(","): - setting = setting.strip() - if setting == "skipped": - reportopts += "s" - elif setting == "xfailed": - reportopts += "x" reportchars = config.option.reportchars + if not config.option.disablepytestwarnings and 'w' not in reportchars: + reportchars += 'w' + elif config.option.disablepytestwarnings and 'w' in reportchars: + reportchars = reportchars.replace('w', '') if reportchars: for char in reportchars: if char not in reportopts and char != 'a': @@ -366,7 +361,8 @@ EXIT_OK, EXIT_TESTSFAILED, EXIT_INTERRUPTED, EXIT_USAGEERROR, EXIT_NOTESTSCOLLECTED) if exitstatus in summary_exit_codes: - self.config.hook.pytest_terminal_summary(terminalreporter=self) + self.config.hook.pytest_terminal_summary(terminalreporter=self, + exitstatus=exitstatus) self.summary_errors() self.summary_failures() self.summary_warnings() @@ -462,6 +458,15 @@ 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') @@ -477,6 +482,9 @@ 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": @@ -517,16 +525,8 @@ def summary_deselected(self): if 'deselected' in self.stats: - l = [] - k = self.config.option.keyword - if k: - l.append("-k%s" % k) - m = self.config.option.markexpr - if m: - l.append("-m %r" % m) - if l: - self.write_sep("=", "%d tests deselected by %r" % ( - len(self.stats['deselected']), " ".join(l)), bold=True) + self.write_sep("=", "%d tests deselected" % ( + len(self.stats['deselected'])), bold=True) def repr_pythonversion(v=None): if v is None: diff -Nru pytest-2.9.2/_pytest/tmpdir.py pytest-3.0.6/_pytest/tmpdir.py --- pytest-2.9.2/_pytest/tmpdir.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/_pytest/tmpdir.py 2017-01-19 13:24:11.000000000 +0000 @@ -3,7 +3,7 @@ import pytest import py -from _pytest.monkeypatch import monkeypatch +from _pytest.monkeypatch import MonkeyPatch class TempdirFactory: @@ -81,6 +81,7 @@ except (ImportError, KeyError): return None + # backward compatibility TempdirHandler = TempdirFactory @@ -92,7 +93,7 @@ available at pytest_configure time, but ideally should be moved entirely to the tmpdir_factory session fixture. """ - mp = monkeypatch() + mp = MonkeyPatch() t = TempdirFactory(config) config._cleanup.extend([mp.undo, t.finish]) mp.setattr(config, '_tmpdirhandler', t, raising=False) @@ -108,7 +109,7 @@ @pytest.fixture def tmpdir(request, tmpdir_factory): - """return a temporary directory path object + """Return a temporary directory path object which is unique to each test function invocation, created as a sub directory of the base temporary directory. The returned object is a `py.path.local`_ diff -Nru pytest-2.9.2/_pytest/unittest.py pytest-3.0.6/_pytest/unittest.py --- pytest-2.9.2/_pytest/unittest.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/_pytest/unittest.py 2017-01-19 09:44:52.000000000 +0000 @@ -50,6 +50,8 @@ foundsomething = False for name in loader.getTestCaseNames(self.obj): x = getattr(self.obj, name) + if not getattr(x, '__test__', True): + continue funcobj = getattr(x, 'im_func', x) transfer_markers(funcobj, cls, module) yield TestCaseFunction(name, parent=self) @@ -92,6 +94,9 @@ 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 @@ -148,7 +153,12 @@ pass def runtest(self): - self._testcase(result=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) + self._testcase.debug() + def _prunetraceback(self, excinfo): pytest.Function._prunetraceback(self, excinfo) @@ -176,6 +186,7 @@ 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: @@ -189,6 +200,7 @@ captureVars=captureVars) except TypeError: Failure__init__(self, exc_value, exc_type, exc_tb) + ut.Failure.__init__ = excstore yield ut.Failure.__init__ = Failure__init__ diff -Nru pytest-2.9.2/_pytest/vendored_packages/pluggy-0.3.1.dist-info/DESCRIPTION.rst pytest-3.0.6/_pytest/vendored_packages/pluggy-0.3.1.dist-info/DESCRIPTION.rst --- pytest-2.9.2/_pytest/vendored_packages/pluggy-0.3.1.dist-info/DESCRIPTION.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/_pytest/vendored_packages/pluggy-0.3.1.dist-info/DESCRIPTION.rst 1970-01-01 00:00:00.000000000 +0000 @@ -1,10 +0,0 @@ -Plugin registration and hook calling for Python -=============================================== - -This is the plugin manager as used by pytest but stripped -of pytest specific details. - -During the 0.x series this plugin does not have much documentation -except extensive docstrings in the pluggy.py module. - - diff -Nru pytest-2.9.2/_pytest/vendored_packages/pluggy-0.3.1.dist-info/METADATA pytest-3.0.6/_pytest/vendored_packages/pluggy-0.3.1.dist-info/METADATA --- pytest-2.9.2/_pytest/vendored_packages/pluggy-0.3.1.dist-info/METADATA 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/_pytest/vendored_packages/pluggy-0.3.1.dist-info/METADATA 1970-01-01 00:00:00.000000000 +0000 @@ -1,39 +0,0 @@ -Metadata-Version: 2.0 -Name: pluggy -Version: 0.3.1 -Summary: plugin and hook calling mechanisms for python -Home-page: UNKNOWN -Author: Holger Krekel -Author-email: holger at merlinux.eu -License: MIT license -Platform: unix -Platform: linux -Platform: osx -Platform: win32 -Classifier: Development Status :: 4 - Beta -Classifier: Intended Audience :: Developers -Classifier: License :: OSI Approved :: MIT License -Classifier: Operating System :: POSIX -Classifier: Operating System :: Microsoft :: Windows -Classifier: Operating System :: MacOS :: MacOS X -Classifier: Topic :: Software Development :: Testing -Classifier: Topic :: Software Development :: Libraries -Classifier: Topic :: Utilities -Classifier: Programming Language :: Python :: 2 -Classifier: Programming Language :: Python :: 2.6 -Classifier: Programming Language :: Python :: 2.7 -Classifier: Programming Language :: Python :: 3 -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 -=============================================== - -This is the plugin manager as used by pytest but stripped -of pytest specific details. - -During the 0.x series this plugin does not have much documentation -except extensive docstrings in the pluggy.py module. - - diff -Nru pytest-2.9.2/_pytest/vendored_packages/pluggy-0.3.1.dist-info/metadata.json pytest-3.0.6/_pytest/vendored_packages/pluggy-0.3.1.dist-info/metadata.json --- pytest-2.9.2/_pytest/vendored_packages/pluggy-0.3.1.dist-info/metadata.json 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/_pytest/vendored_packages/pluggy-0.3.1.dist-info/metadata.json 1970-01-01 00:00:00.000000000 +0000 @@ -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 -Nru pytest-2.9.2/_pytest/vendored_packages/pluggy-0.3.1.dist-info/pbr.json pytest-3.0.6/_pytest/vendored_packages/pluggy-0.3.1.dist-info/pbr.json --- pytest-2.9.2/_pytest/vendored_packages/pluggy-0.3.1.dist-info/pbr.json 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/_pytest/vendored_packages/pluggy-0.3.1.dist-info/pbr.json 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -{"is_release": false, "git_version": "7d4c9cd"} \ No newline at end of file diff -Nru pytest-2.9.2/_pytest/vendored_packages/pluggy-0.3.1.dist-info/RECORD pytest-3.0.6/_pytest/vendored_packages/pluggy-0.3.1.dist-info/RECORD --- pytest-2.9.2/_pytest/vendored_packages/pluggy-0.3.1.dist-info/RECORD 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/_pytest/vendored_packages/pluggy-0.3.1.dist-info/RECORD 1970-01-01 00:00:00.000000000 +0000 @@ -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 -Nru pytest-2.9.2/_pytest/vendored_packages/pluggy-0.3.1.dist-info/top_level.txt pytest-3.0.6/_pytest/vendored_packages/pluggy-0.3.1.dist-info/top_level.txt --- pytest-2.9.2/_pytest/vendored_packages/pluggy-0.3.1.dist-info/top_level.txt 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/_pytest/vendored_packages/pluggy-0.3.1.dist-info/top_level.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -pluggy diff -Nru pytest-2.9.2/_pytest/vendored_packages/pluggy-0.3.1.dist-info/WHEEL pytest-3.0.6/_pytest/vendored_packages/pluggy-0.3.1.dist-info/WHEEL --- pytest-2.9.2/_pytest/vendored_packages/pluggy-0.3.1.dist-info/WHEEL 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/_pytest/vendored_packages/pluggy-0.3.1.dist-info/WHEEL 1970-01-01 00:00:00.000000000 +0000 @@ -1,6 +0,0 @@ -Wheel-Version: 1.0 -Generator: bdist_wheel (0.24.0) -Root-Is-Purelib: true -Tag: py2-none-any -Tag: py3-none-any - diff -Nru pytest-2.9.2/_pytest/vendored_packages/pluggy-0.4.0.dist-info/DESCRIPTION.rst pytest-3.0.6/_pytest/vendored_packages/pluggy-0.4.0.dist-info/DESCRIPTION.rst --- pytest-2.9.2/_pytest/vendored_packages/pluggy-0.4.0.dist-info/DESCRIPTION.rst 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/_pytest/vendored_packages/pluggy-0.4.0.dist-info/DESCRIPTION.rst 2017-01-19 09:44:52.000000000 +0000 @@ -0,0 +1,11 @@ + +Plugin registration and hook calling for Python +=============================================== + +This is the plugin manager as used by pytest but stripped +of pytest specific details. + +During the 0.x series this plugin does not have much documentation +except extensive docstrings in the pluggy.py module. + + diff -Nru pytest-2.9.2/_pytest/vendored_packages/pluggy-0.4.0.dist-info/INSTALLER pytest-3.0.6/_pytest/vendored_packages/pluggy-0.4.0.dist-info/INSTALLER --- pytest-2.9.2/_pytest/vendored_packages/pluggy-0.4.0.dist-info/INSTALLER 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/_pytest/vendored_packages/pluggy-0.4.0.dist-info/INSTALLER 2017-01-19 09:44:52.000000000 +0000 @@ -0,0 +1 @@ +pip diff -Nru pytest-2.9.2/_pytest/vendored_packages/pluggy-0.4.0.dist-info/LICENSE.txt pytest-3.0.6/_pytest/vendored_packages/pluggy-0.4.0.dist-info/LICENSE.txt --- pytest-2.9.2/_pytest/vendored_packages/pluggy-0.4.0.dist-info/LICENSE.txt 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/_pytest/vendored_packages/pluggy-0.4.0.dist-info/LICENSE.txt 2017-01-19 09:44:52.000000000 +0000 @@ -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 -Nru pytest-2.9.2/_pytest/vendored_packages/pluggy-0.4.0.dist-info/METADATA pytest-3.0.6/_pytest/vendored_packages/pluggy-0.4.0.dist-info/METADATA --- pytest-2.9.2/_pytest/vendored_packages/pluggy-0.4.0.dist-info/METADATA 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/_pytest/vendored_packages/pluggy-0.4.0.dist-info/METADATA 2017-01-19 09:44:52.000000000 +0000 @@ -0,0 +1,40 @@ +Metadata-Version: 2.0 +Name: pluggy +Version: 0.4.0 +Summary: plugin and hook calling mechanisms for python +Home-page: https://github.com/pytest-dev/pluggy +Author: Holger Krekel +Author-email: holger at merlinux.eu +License: MIT license +Platform: unix +Platform: linux +Platform: osx +Platform: win32 +Classifier: Development Status :: 4 - Beta +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: POSIX +Classifier: Operating System :: Microsoft :: Windows +Classifier: Operating System :: MacOS :: MacOS X +Classifier: Topic :: Software Development :: Testing +Classifier: Topic :: Software Development :: Libraries +Classifier: Topic :: Utilities +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.6 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +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 +=============================================== + +This is the plugin manager as used by pytest but stripped +of pytest specific details. + +During the 0.x series this plugin does not have much documentation +except extensive docstrings in the pluggy.py module. + + diff -Nru pytest-2.9.2/_pytest/vendored_packages/pluggy-0.4.0.dist-info/metadata.json pytest-3.0.6/_pytest/vendored_packages/pluggy-0.4.0.dist-info/metadata.json --- pytest-2.9.2/_pytest/vendored_packages/pluggy-0.4.0.dist-info/metadata.json 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/_pytest/vendored_packages/pluggy-0.4.0.dist-info/metadata.json 2017-01-19 09:44:52.000000000 +0000 @@ -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 -Nru pytest-2.9.2/_pytest/vendored_packages/pluggy-0.4.0.dist-info/RECORD pytest-3.0.6/_pytest/vendored_packages/pluggy-0.4.0.dist-info/RECORD --- pytest-2.9.2/_pytest/vendored_packages/pluggy-0.4.0.dist-info/RECORD 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/_pytest/vendored_packages/pluggy-0.4.0.dist-info/RECORD 2017-01-19 09:44:52.000000000 +0000 @@ -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 -Nru pytest-2.9.2/_pytest/vendored_packages/pluggy-0.4.0.dist-info/top_level.txt pytest-3.0.6/_pytest/vendored_packages/pluggy-0.4.0.dist-info/top_level.txt --- pytest-2.9.2/_pytest/vendored_packages/pluggy-0.4.0.dist-info/top_level.txt 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/_pytest/vendored_packages/pluggy-0.4.0.dist-info/top_level.txt 2017-01-19 09:44:52.000000000 +0000 @@ -0,0 +1 @@ +pluggy diff -Nru pytest-2.9.2/_pytest/vendored_packages/pluggy-0.4.0.dist-info/WHEEL pytest-3.0.6/_pytest/vendored_packages/pluggy-0.4.0.dist-info/WHEEL --- pytest-2.9.2/_pytest/vendored_packages/pluggy-0.4.0.dist-info/WHEEL 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/_pytest/vendored_packages/pluggy-0.4.0.dist-info/WHEEL 2017-01-19 09:44:52.000000000 +0000 @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.29.0) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff -Nru pytest-2.9.2/_pytest/vendored_packages/pluggy.py pytest-3.0.6/_pytest/vendored_packages/pluggy.py --- pytest-2.9.2/_pytest/vendored_packages/pluggy.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/_pytest/vendored_packages/pluggy.py 2017-01-19 13:24:11.000000000 +0000 @@ -67,8 +67,9 @@ 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 @@ """ 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 @@ 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 @@ """ 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 @@ 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 @@ 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 @@ # 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 @@ 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 @@ 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 @@ """ plugin failed validation. """ +class HookCallError(Exception): + """ Hook was called wrongly. """ + + if hasattr(inspect, 'signature'): def _formatdef(func): return "%s%s" % ( diff -Nru pytest-2.9.2/_pytest/vendored_packages/README.md pytest-3.0.6/_pytest/vendored_packages/README.md --- pytest-2.9.2/_pytest/vendored_packages/README.md 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/_pytest/vendored_packages/README.md 2017-01-19 09:44:52.000000000 +0000 @@ -10,4 +10,4 @@ ``` 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 -Nru pytest-2.9.2/pytest.egg-info/entry_points.txt pytest-3.0.6/pytest.egg-info/entry_points.txt --- pytest-2.9.2/pytest.egg-info/entry_points.txt 2016-05-31 17:08:16.000000000 +0000 +++ pytest-3.0.6/pytest.egg-info/entry_points.txt 2017-01-22 21:15:07.000000000 +0000 @@ -1,4 +1,4 @@ [console_scripts] py.test = pytest:main -py.test-2.7 = pytest:main +pytest = pytest:main diff -Nru pytest-2.9.2/pytest.egg-info/PKG-INFO pytest-3.0.6/pytest.egg-info/PKG-INFO --- pytest-2.9.2/pytest.egg-info/PKG-INFO 2016-05-31 17:08:16.000000000 +0000 +++ pytest-3.0.6/pytest.egg-info/PKG-INFO 2017-01-22 21:15:07.000000000 +0000 @@ -1,13 +1,13 @@ Metadata-Version: 1.1 Name: pytest -Version: 2.9.2 +Version: 3.0.6 Summary: pytest: simple powerful testing with Python Home-page: http://pytest.org Author: Holger Krekel, Bruno Oliveira, Ronny Pfannschmidt, Floris Bruynooghe, Brianna Laugher, Florian Bruhin and others -Author-email: holger at merlinux.eu +Author-email: UNKNOWN License: MIT license -Description: .. image:: http://pytest.org/latest/_static/pytest1.png - :target: http://pytest.org +Description: .. image:: http://docs.pytest.org/en/latest/_static/pytest1.png + :target: http://docs.pytest.org :align: center :alt: pytest @@ -25,67 +25,67 @@ :target: https://ci.appveyor.com/project/pytestbot/pytest The ``pytest`` framework makes it easy to write small tests, yet - scales to support complex functional testing for applications and libraries. + scales to support complex functional testing for applications and libraries. 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:: - $ py.test - ======= test session starts ======== - platform linux -- Python 3.4.3, pytest-2.8.5, py-1.4.31, pluggy-0.3.1 + $ pytest + ============================= 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. - Due to ``py.test``'s detailed assertion introspection, only plain ``assert`` statements are used. See `getting-started `_ for more examples. - Features -------- - - Detailed info on failing `assert statements `_ (no need to remember ``self.assert*`` names); + - Detailed info on failing `assert statements `_ (no need to remember ``self.assert*`` names); - `Auto-discovery - `_ + `_ of test modules and functions; - - `Modular fixtures `_ for + - `Modular fixtures `_ for managing small or parametrized long-lived test resources; - - Can run `unittest `_ (or trial), - `nose `_ test suites out of the box; + - Can run `unittest `_ (or trial), + `nose `_ test suites out of the box; - - Python2.6+, Python3.2+, PyPy-2.3, Jython-2.5 (untested); + - Python2.6+, Python3.3+, PyPy-2.3, Jython-2.5 (untested); - - Rich plugin architecture, with over 150+ `external plugins `_ and thriving community; + - Rich plugin architecture, with over 150+ `external plugins `_ and thriving community; Documentation ------------- - For full documentation, including installation, tutorials and PDF documents, please see http://pytest.org. + For full documentation, including installation, tutorials and PDF documents, please see http://docs.pytest.org. Bugs/Requests @@ -97,7 +97,7 @@ Changelog --------- - Consult the `Changelog `_ page for fixes and enhancements of each version. + Consult the `Changelog `__ page for fixes and enhancements of each version. License @@ -109,6 +109,7 @@ .. _`MIT`: https://github.com/pytest-dev/pytest/blob/master/LICENSE +Keywords: test unittest Platform: unix Platform: linux Platform: osx @@ -127,7 +128,6 @@ Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 diff -Nru pytest-2.9.2/pytest.egg-info/requires.txt pytest-3.0.6/pytest.egg-info/requires.txt --- pytest-2.9.2/pytest.egg-info/requires.txt 2016-05-31 17:08:16.000000000 +0000 +++ pytest-3.0.6/pytest.egg-info/requires.txt 2017-01-22 21:15:07.000000000 +0000 @@ -1,6 +1,7 @@ py>=1.4.29 +setuptools -[:python_version=="2.6" or python_version=="3.0" or python_version=="3.1"] +[:python_version=="2.6"] argparse [:sys_platform=="win32"] diff -Nru pytest-2.9.2/pytest.egg-info/SOURCES.txt pytest-3.0.6/pytest.egg-info/SOURCES.txt --- pytest-2.9.2/pytest.egg-info/SOURCES.txt 2016-05-31 17:08:16.000000000 +0000 +++ pytest-3.0.6/pytest.egg-info/SOURCES.txt 2017-01-22 21:15:07.000000000 +0000 @@ -1,14 +1,14 @@ .coveragerc +.gitattributes +.gitignore AUTHORS CHANGELOG.rst CONTRIBUTING.rst +HOWTORELEASE.rst LICENSE MANIFEST.in README.rst -plugin-test.sh pytest.py -requirements-docs.txt -runtox.py setup.cfg setup.py tox.ini @@ -17,9 +17,13 @@ _pytest/_pluggy.py _pytest/cacheprovider.py _pytest/capture.py +_pytest/compat.py _pytest/config.py +_pytest/debugging.py +_pytest/deprecated.py _pytest/doctest.py -_pytest/genscript.py +_pytest/fixtures.py +_pytest/freeze_support.py _pytest/helpconfig.py _pytest/hookspec.py _pytest/junitxml.py @@ -28,14 +32,14 @@ _pytest/monkeypatch.py _pytest/nose.py _pytest/pastebin.py -_pytest/pdb.py _pytest/pytester.py _pytest/python.py _pytest/recwarn.py _pytest/resultlog.py _pytest/runner.py +_pytest/setuponly.py +_pytest/setupplan.py _pytest/skipping.py -_pytest/standalonetemplate.py _pytest/terminal.py _pytest/tmpdir.py _pytest/unittest.py @@ -44,28 +48,28 @@ _pytest/_code/code.py _pytest/_code/source.py _pytest/assertion/__init__.py -_pytest/assertion/reinterpret.py _pytest/assertion/rewrite.py _pytest/assertion/util.py _pytest/vendored_packages/README.md _pytest/vendored_packages/__init__.py _pytest/vendored_packages/pluggy.py -_pytest/vendored_packages/pluggy-0.3.1.dist-info/DESCRIPTION.rst -_pytest/vendored_packages/pluggy-0.3.1.dist-info/METADATA -_pytest/vendored_packages/pluggy-0.3.1.dist-info/RECORD -_pytest/vendored_packages/pluggy-0.3.1.dist-info/WHEEL -_pytest/vendored_packages/pluggy-0.3.1.dist-info/metadata.json -_pytest/vendored_packages/pluggy-0.3.1.dist-info/pbr.json -_pytest/vendored_packages/pluggy-0.3.1.dist-info/top_level.txt +_pytest/vendored_packages/pluggy-0.4.0.dist-info/DESCRIPTION.rst +_pytest/vendored_packages/pluggy-0.4.0.dist-info/INSTALLER +_pytest/vendored_packages/pluggy-0.4.0.dist-info/LICENSE.txt +_pytest/vendored_packages/pluggy-0.4.0.dist-info/METADATA +_pytest/vendored_packages/pluggy-0.4.0.dist-info/RECORD +_pytest/vendored_packages/pluggy-0.4.0.dist-info/WHEEL +_pytest/vendored_packages/pluggy-0.4.0.dist-info/metadata.json +_pytest/vendored_packages/pluggy-0.4.0.dist-info/top_level.txt bench/bench.py bench/bench_argcomplete.py bench/empty.py bench/manyparam.py bench/skip.py doc/en/Makefile -doc/en/_getdoctarget.py doc/en/adopt.rst doc/en/assert.rst +doc/en/backwards-compatibility.rst doc/en/bash-completion.rst doc/en/builtin.rst doc/en/cache.rst @@ -80,7 +84,6 @@ doc/en/customize.rst doc/en/doctest.rst doc/en/faq.rst -doc/en/feedback.rst doc/en/fixture.rst doc/en/funcarg_compare.rst doc/en/funcargs.rst @@ -94,21 +97,19 @@ doc/en/monkeypatch.rst doc/en/naming20.rst doc/en/nose.rst -doc/en/overview.rst doc/en/parametrize.rst +doc/en/parametrize.rst.orig doc/en/plugins.rst doc/en/projects.rst doc/en/pytest.ini doc/en/recwarn.rst doc/en/setup.rst doc/en/skipping.rst -doc/en/status.rst doc/en/talks.rst doc/en/tmpdir.rst doc/en/unittest.rst doc/en/usage.rst doc/en/writing_plugins.rst -doc/en/xdist.rst doc/en/xunit_setup.rst doc/en/yieldfixture.rst doc/en/_templates/globaltoc.html @@ -164,6 +165,13 @@ doc/en/announce/release-2.9.0.rst doc/en/announce/release-2.9.1.rst doc/en/announce/release-2.9.2.rst +doc/en/announce/release-3.0.0.rst +doc/en/announce/release-3.0.1.rst +doc/en/announce/release-3.0.2.rst +doc/en/announce/release-3.0.3.rst +doc/en/announce/release-3.0.4.rst +doc/en/announce/release-3.0.5.rst +doc/en/announce/release-3.0.6.rst doc/en/announce/sprint2016.rst doc/en/example/attic.rst doc/en/example/conftest.py @@ -188,7 +196,6 @@ doc/en/example/costlysetup/sub1/test_quick.py doc/en/example/costlysetup/sub2/__init__.py doc/en/example/costlysetup/sub2/test_two.py -doc/en/example/layout1/setup.cfg doc/en/example/nonpython/__init__.py doc/en/example/nonpython/conftest.py doc/en/example/nonpython/test_simple.yml @@ -204,6 +211,7 @@ doc/en/img/pytest1.png doc/en/img/pytest1favi.ico doc/en/img/theuni.png +doc/en/proposals/parametrize_with_fixtures.rst doc/en/test/attic.rst doc/en/test/config.html doc/en/test/dist.html @@ -215,7 +223,6 @@ doc/en/test/plugin/coverage.rst doc/en/test/plugin/django.rst doc/en/test/plugin/figleaf.rst -doc/en/test/plugin/genscript.rst doc/en/test/plugin/helpconfig.rst doc/en/test/plugin/index.rst doc/en/test/plugin/links.rst @@ -232,18 +239,22 @@ pytest.egg-info/not-zip-safe pytest.egg-info/requires.txt pytest.egg-info/top_level.txt +scripts/call-tox.bat +scripts/check-manifest.py +scripts/install-pypy.bat testing/acceptance_test.py +testing/deprecated_test.py testing/test_argcomplete.py -testing/test_assertinterpret.py testing/test_assertion.py testing/test_assertrewrite.py testing/test_cache.py testing/test_capture.py testing/test_collection.py +testing/test_compat.py testing/test_config.py testing/test_conftest.py testing/test_doctest.py -testing/test_genscript.py +testing/test_entry_points.py testing/test_helpconfig.py testing/test_junitxml.py testing/test_mark.py @@ -266,14 +277,19 @@ testing/code/test_code.py testing/code/test_excinfo.py testing/code/test_source.py -testing/cx_freeze/install_cx_freeze.py -testing/cx_freeze/runtests_script.py -testing/cx_freeze/runtests_setup.py -testing/cx_freeze/tox_run.py -testing/cx_freeze/tests/test_doctest.txt -testing/cx_freeze/tests/test_trivial.py +testing/freeze/.gitignore +testing/freeze/create_executable.py +testing/freeze/runtests_script.py +testing/freeze/tox_run.py +testing/freeze/tests/test_doctest.txt +testing/freeze/tests/test_trivial.py +testing/python/approx.py testing/python/collect.py testing/python/fixture.py testing/python/integration.py testing/python/metafunc.py -testing/python/raises.py \ No newline at end of file +testing/python/metafunc.py.orig +testing/python/raises.py +testing/python/setup_only.py +testing/python/setup_plan.py +testing/python/show_fixtures_per_test.py \ No newline at end of file diff -Nru pytest-2.9.2/README.rst pytest-3.0.6/README.rst --- pytest-2.9.2/README.rst 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/README.rst 2017-01-19 09:44:52.000000000 +0000 @@ -1,5 +1,5 @@ -.. image:: http://pytest.org/latest/_static/pytest1.png - :target: http://pytest.org +.. image:: http://docs.pytest.org/en/latest/_static/pytest1.png + :target: http://docs.pytest.org :align: center :alt: pytest @@ -17,67 +17,67 @@ :target: https://ci.appveyor.com/project/pytestbot/pytest The ``pytest`` framework makes it easy to write small tests, yet -scales to support complex functional testing for applications and libraries. +scales to support complex functional testing for applications and libraries. 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:: - $ py.test - ======= test session starts ======== - platform linux -- Python 3.4.3, pytest-2.8.5, py-1.4.31, pluggy-0.3.1 + $ pytest + ============================= 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. -Due to ``py.test``'s detailed assertion introspection, only plain ``assert`` statements are used. See `getting-started `_ for more examples. - Features -------- -- Detailed info on failing `assert statements `_ (no need to remember ``self.assert*`` names); +- Detailed info on failing `assert statements `_ (no need to remember ``self.assert*`` names); - `Auto-discovery - `_ + `_ of test modules and functions; -- `Modular fixtures `_ for +- `Modular fixtures `_ for managing small or parametrized long-lived test resources; -- Can run `unittest `_ (or trial), - `nose `_ test suites out of the box; +- Can run `unittest `_ (or trial), + `nose `_ test suites out of the box; -- Python2.6+, Python3.2+, PyPy-2.3, Jython-2.5 (untested); +- Python2.6+, Python3.3+, PyPy-2.3, Jython-2.5 (untested); -- Rich plugin architecture, with over 150+ `external plugins `_ and thriving community; +- Rich plugin architecture, with over 150+ `external plugins `_ and thriving community; Documentation ------------- -For full documentation, including installation, tutorials and PDF documents, please see http://pytest.org. +For full documentation, including installation, tutorials and PDF documents, please see http://docs.pytest.org. Bugs/Requests @@ -89,7 +89,7 @@ Changelog --------- -Consult the `Changelog `_ page for fixes and enhancements of each version. +Consult the `Changelog `__ page for fixes and enhancements of each version. License diff -Nru pytest-2.9.2/requirements-docs.txt pytest-3.0.6/requirements-docs.txt --- pytest-2.9.2/requirements-docs.txt 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/requirements-docs.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,3 +0,0 @@ -sphinx==1.2.3 -regendoc -pyyaml diff -Nru pytest-2.9.2/runtox.py pytest-3.0.6/runtox.py --- pytest-2.9.2/runtox.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/runtox.py 1970-01-01 00:00:00.000000000 +0000 @@ -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 -Nru pytest-2.9.2/scripts/call-tox.bat pytest-3.0.6/scripts/call-tox.bat --- pytest-2.9.2/scripts/call-tox.bat 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/scripts/call-tox.bat 2017-01-19 09:44:52.000000000 +0000 @@ -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 -Nru pytest-2.9.2/scripts/check-manifest.py pytest-3.0.6/scripts/check-manifest.py --- pytest-2.9.2/scripts/check-manifest.py 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/scripts/check-manifest.py 2017-01-19 09:44:52.000000000 +0000 @@ -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 -Nru pytest-2.9.2/scripts/install-pypy.bat pytest-3.0.6/scripts/install-pypy.bat --- pytest-2.9.2/scripts/install-pypy.bat 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/scripts/install-pypy.bat 2017-01-19 09:44:52.000000000 +0000 @@ -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 -Nru pytest-2.9.2/setup.cfg pytest-3.0.6/setup.cfg --- pytest-2.9.2/setup.cfg 2016-05-31 17:08:16.000000000 +0000 +++ pytest-3.0.6/setup.cfg 2017-01-22 21:15:07.000000000 +0000 @@ -9,11 +9,13 @@ [bdist_wheel] universal = 1 +[metadata] +license_file = LICENSE + [devpi:upload] formats = sdist.tgz,bdist_wheel [egg_info] tag_build = tag_date = 0 -tag_svn_revision = 0 diff -Nru pytest-2.9.2/setup.py pytest-3.0.6/setup.py --- pytest-2.9.2/setup.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/setup.py 2017-01-22 21:14:36.000000000 +0000 @@ -13,7 +13,7 @@ 'Topic :: Software Development :: Libraries', 'Topic :: Utilities'] + [ ('Programming Language :: Python :: %s' % x) for x in - '2 2.6 2.7 3 3.2 3.3 3.4 3.5'.split()] + '2 2.6 2.7 3 3.3 3.4 3.5'.split()] with open('README.rst') as fd: long_description = fd.read() @@ -48,13 +48,13 @@ 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" or python_version=="3.0" or python_version=="3.1"'] = ['argparse'] + extras_require[':python_version=="2.6"'] = ['argparse'] extras_require[':sys_platform=="win32"'] = ['colorama'] else: - if sys.version_info < (2, 7) or (3,) <= sys.version_info < (3, 2): + if sys.version_info < (2, 7): install_requires.append('argparse') if sys.platform == 'win32': install_requires.append('colorama') @@ -68,9 +68,10 @@ 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=make_entry_points(), + 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, @@ -81,28 +82,6 @@ ) -def cmdline_entrypoints(versioninfo, platform, basename): - target = 'pytest:main' - if platform.startswith('java'): - points = {'py.test-jython': target} - else: - if basename.startswith('pypy'): - points = {'py.test-%s' % basename: target} - else: # cpython - points = {'py.test-%s.%s' % versioninfo[:2] : target} - points['py.test'] = target - return points - - -def make_entry_points(): - basename = os.path.basename(sys.executable) - points = cmdline_entrypoints(sys.version_info, sys.platform, basename) - keys = list(points.keys()) - keys.sort() - l = ['%s = %s' % (x, points[x]) for x in keys] - return {'console_scripts': l} - - class PyTest(Command): user_options = [] def initialize_options(self): diff -Nru pytest-2.9.2/testing/acceptance_test.py pytest-3.0.6/testing/acceptance_test.py --- pytest-2.9.2/testing/acceptance_test.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/testing/acceptance_test.py 2017-01-19 09:44:52.000000000 +0000 @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- +import os import sys import _pytest._code @@ -117,9 +119,10 @@ result = testdir.runpytest(p) result.stdout.fnmatch_lines([ #XXX on jython this fails: "> import import_fails", - "E ImportError: No module named *does_not_work*", + "ImportError while importing test module*", + "*No module named *does_not_work*", ]) - assert result.ret == 1 + assert result.ret == 2 def test_not_collectable_arguments(self, testdir): p1 = testdir.makepyfile("") @@ -374,7 +377,7 @@ res = testdir.runpytest(p) res.stdout.fnmatch_lines([ "*source code not available*", - "*fixture 'invalid_fixture' not found", + "E*fixture 'invalid_fixture' not found", ]) def test_plugins_given_as_strings(self, tmpdir, monkeypatch): @@ -512,12 +515,11 @@ path = testdir.mkpydir("tpkg") path.join("test_hello.py").write('raise ImportError') - result = testdir.runpytest("--pyargs", "tpkg.test_hello") + result = testdir.runpytest_subprocess("--pyargs", "tpkg.test_hello") assert result.ret != 0 - # FIXME: It would be more natural to match NOT - # "ERROR*file*or*package*not*found*". + result.stdout.fnmatch_lines([ - "*collected 0 items*" + "collected*0*items*/*1*errors" ]) def test_cmdline_python_package(self, testdir, monkeypatch): @@ -539,7 +541,7 @@ def join_pythonpath(what): cur = py.std.os.environ.get('PYTHONPATH') if cur: - return str(what) + ':' + cur + return str(what) + os.pathsep + cur return what empty_package = testdir.mkpydir("empty_package") monkeypatch.setenv('PYTHONPATH', join_pythonpath(empty_package)) @@ -550,11 +552,72 @@ ]) monkeypatch.setenv('PYTHONPATH', join_pythonpath(testdir)) - path.join('test_hello.py').remove() - result = testdir.runpytest("--pyargs", "tpkg.test_hello") + result = testdir.runpytest("--pyargs", "tpkg.test_missing") assert result.ret != 0 result.stderr.fnmatch_lines([ - "*not*found*test_hello*", + "*not*found*test_missing*", + ]) + + def test_cmdline_python_namespace_package(self, testdir, monkeypatch): + """ + test --pyargs option with namespace packages (#1567) + """ + monkeypatch.delenv('PYTHONDONTWRITEBYTECODE', raising=False) + + search_path = [] + for dirname in "hello", "world": + d = testdir.mkdir(dirname) + search_path.append(d) + ns = d.mkdir("ns_pkg") + ns.join("__init__.py").write( + "__import__('pkg_resources').declare_namespace(__name__)") + lib = ns.mkdir(dirname) + lib.ensure("__init__.py") + lib.join("test_{0}.py".format(dirname)). \ + write("def test_{0}(): pass\n" + "def test_other():pass".format(dirname)) + + # The structure of the test directory is now: + # . + # ├── hello + # │   └── ns_pkg + # │   ├── __init__.py + # │   └── hello + # │   ├── __init__.py + # │   └── test_hello.py + # └── world + # └── ns_pkg + # ├── __init__.py + # └── world + # ├── __init__.py + # └── test_world.py + + def join_pythonpath(*dirs): + cur = py.std.os.environ.get('PYTHONPATH') + if cur: + dirs += (cur,) + return os.pathsep.join(str(p) for p in dirs) + monkeypatch.setenv('PYTHONPATH', join_pythonpath(*search_path)) + for p in search_path: + monkeypatch.syspath_prepend(p) + + # mixed module and filenames: + result = testdir.runpytest("--pyargs", "-v", "ns_pkg.hello", "world/ns_pkg") + assert result.ret == 0 + result.stdout.fnmatch_lines([ + "*test_hello.py::test_hello*PASSED", + "*test_hello.py::test_other*PASSED", + "*test_world.py::test_world*PASSED", + "*test_world.py::test_other*PASSED", + "*4 passed*" + ]) + + # specify tests within a module + result = testdir.runpytest("--pyargs", "-v", "ns_pkg.world.test_world::test_other") + assert result.ret == 0 + result.stdout.fnmatch_lines([ + "*test_world.py::test_other*PASSED", + "*1 passed*" ]) def test_cmdline_python_package_not_exists(self, testdir): @@ -664,11 +727,13 @@ testdir.makepyfile(self.source) testdir.makepyfile(test_collecterror="""xyz""") result = testdir.runpytest("--durations=2", "-k test_1") - assert result.ret != 0 + assert result.ret == 2 result.stdout.fnmatch_lines([ - "*durations*", - "*call*test_1*", + "*Interrupted: 1 errors during collection*", ]) + # Collection errors abort test execution, therefore no duration is + # output + assert "duration" not in result.stdout.str() def test_with_not(self, testdir): testdir.makepyfile(self.source) @@ -698,3 +763,21 @@ * call *test_1* """) + +def test_zipimport_hook(testdir, tmpdir): + """Test package loader is being used correctly (see #1837).""" + zipapp = pytest.importorskip('zipapp') + testdir.tmpdir.join('app').ensure(dir=1) + testdir.makepyfile(**{ + 'app/foo.py': """ + import pytest + def main(): + pytest.main(['--pyarg', 'foo']) + """, + }) + target = tmpdir.join('foo.zip') + zipapp.create_archive(str(testdir.tmpdir.join('app')), str(target), main='foo:main') + result = testdir.runpython(target) + assert result.ret == 0 + result.stderr.fnmatch_lines(['*not found*foo*']) + assert 'INTERNALERROR>' not in result.stdout.str() diff -Nru pytest-2.9.2/testing/code/test_code.py pytest-3.0.6/testing/code/test_code.py --- pytest-2.9.2/testing/code/test_code.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/testing/code/test_code.py 2017-01-19 09:44:52.000000000 +0000 @@ -24,6 +24,7 @@ pass pytest.raises(TypeError, "_pytest._code.Code(A)") + if True: def x(): pass @@ -66,28 +67,12 @@ assert co.path - -def test_builtin_patch_unpatch(monkeypatch): - cpy_builtin = py.builtin.builtins - comp = cpy_builtin.compile - def mycompile(*args, **kwargs): - return comp(*args, **kwargs) - class Sub(AssertionError): - pass - monkeypatch.setattr(cpy_builtin, 'AssertionError', Sub) - monkeypatch.setattr(cpy_builtin, 'compile', mycompile) - _pytest._code.patch_builtins() - assert cpy_builtin.AssertionError != Sub - assert cpy_builtin.compile != mycompile - _pytest._code.unpatch_builtins() - assert cpy_builtin.AssertionError is Sub - assert cpy_builtin.compile == mycompile - - 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: @@ -97,8 +82,10 @@ @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 -Nru pytest-2.9.2/testing/code/test_excinfo.py pytest-3.0.6/testing/code/test_excinfo.py --- pytest-2.9.2/testing/code/test_excinfo.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/testing/code/test_excinfo.py 2017-01-19 09:44:52.000000000 +0000 @@ -1,9 +1,14 @@ # -*- coding: utf-8 -*- +import operator import _pytest import py import pytest -from _pytest._code.code import ExceptionInfo, FormattedExcinfo, ReprExceptionInfo +from _pytest._code.code import ( + ExceptionInfo, + FormattedExcinfo, + ReprExceptionInfo, + ExceptionChainRepr) queue = py.builtin._tryimport('queue', 'Queue') @@ -21,14 +26,23 @@ pytest_version_info = tuple(map(int, pytest.__version__.split(".")[:3])) class TWMock: + WRITE = object() + def __init__(self): self.lines = [] + self.is_writing = False def sep(self, sep, line=None): self.lines.append((sep, line)) + def write(self, msg, **kw): + self.lines.append((TWMock.WRITE, msg)) def line(self, line, **kw): self.lines.append(line) def markup(self, text, **kw): return text + def get_write_msg(self, idx): + flag, msg = self.lines[idx] + assert flag == TWMock.WRITE + return msg fullwidth = 80 @@ -42,13 +56,15 @@ 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) @@ -143,6 +159,41 @@ ntraceback = traceback.filter() assert len(ntraceback) == len(traceback) - 1 + @pytest.mark.parametrize('tracebackhide, matching', [ + (lambda info: True, True), + (lambda info: False, False), + (operator.methodcaller('errisinstance', ValueError), True), + (operator.methodcaller('errisinstance', IndexError), False), + ]) + def test_traceback_filter_selective(self, tracebackhide, matching): + def f(): + # + raise ValueError + # + + def g(): + # + __tracebackhide__ = tracebackhide + f() + # + + def h(): + # + g() + # + + excinfo = pytest.raises(ValueError, h) + traceback = excinfo.traceback + ntraceback = traceback.filter() + print('old: {0!r}'.format(traceback)) + print('new: {0!r}'.format(ntraceback)) + + if matching: + assert len(ntraceback) == len(traceback) - 2 + else: + # -1 because of the __tracebackhide__ in pytest.raises + assert len(ntraceback) == len(traceback) - 1 + def test_traceback_recursion_index(self): def f(n): if n < 10: @@ -167,15 +218,18 @@ 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() @@ -198,17 +252,18 @@ 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() @@ -224,6 +279,7 @@ def g(): __tracebackhide__ = True raise ValueError + def f(): __tracebackhide__ = True g() @@ -236,18 +292,6 @@ assert entry.lineno == co.firstlineno + 2 assert entry.frame.code.name == 'g' -def hello(x): - x + 5 - -def test_tbentry_reinterpret(): - try: - hello("hello") - except TypeError: - excinfo = _pytest._code.ExceptionInfo() - tbentry = excinfo.traceback[-1] - msg = tbentry.reinterpret() - assert msg.startswith("TypeError: ('hello' + 5)") - def test_excinfo_exconly(): excinfo = pytest.raises(ValueError, h) assert excinfo.exconly().startswith('ValueError') @@ -323,11 +367,32 @@ assert path.basename.lower() == "queue.py" assert path.check() +def test_match_succeeds(): + with pytest.raises(ZeroDivisionError) as excinfo: + 0 / 0 + excinfo.match(r'.*zero.*') + +def test_match_raises_error(testdir): + testdir.makepyfile(""" + import pytest + def test_division_zero(): + with pytest.raises(ZeroDivisionError) as excinfo: + 0 / 0 + excinfo.match(r'[123]+') + """) + result = testdir.runpytest() + assert result.ret != 0 + result.stdout.fnmatch_lines([ + "*AssertionError*Pattern*[123]*not found*", + ]) + class TestFormattedExcinfo: - def pytest_funcarg__importasmod(self, request): + + @pytest.fixture + def importasmod(self, request): def importasmod(source): source = _pytest._code.Source(source) - tmpdir = request.getfuncargvalue("tmpdir") + tmpdir = request.getfixturevalue("tmpdir") modpath = tmpdir.join("mod.py") tmpdir.ensure("__init__.py") modpath.write(source) @@ -372,7 +437,7 @@ assert lines == [ ' def f():', '> assert 0', - 'E assert 0' + 'E AssertionError' ] @@ -385,6 +450,8 @@ excinfo = _pytest._code.ExceptionInfo() repr = pr.repr_excinfo(excinfo) assert repr.reprtraceback.reprentries[1].lines[0] == "> ???" + if py.std.sys.version_info[0] >= 3: + assert repr.chain[0][0].reprentries[1].lines[0] == "> ???" def test_repr_many_line_source_not_existing(self): pr = FormattedExcinfo() @@ -398,6 +465,8 @@ excinfo = _pytest._code.ExceptionInfo() repr = pr.repr_excinfo(excinfo) assert repr.reprtraceback.reprentries[1].lines[0] == "> ???" + if py.std.sys.version_info[0] >= 3: + assert repr.chain[0][0].reprentries[1].lines[0] == "> ???" def test_repr_source_failing_fullsource(self): pr = FormattedExcinfo() @@ -405,11 +474,13 @@ class FakeCode(object): class raw: co_filename = '?' + path = '?' firstlineno = 5 def fullsource(self): return None + fullsource = property(fullsource) class FakeFrame(object): @@ -418,7 +489,7 @@ f_globals = {} class FakeTracebackEntry(_pytest._code.Traceback.Entry): - def __init__(self, tb): + def __init__(self, tb, excinfo=None): self.lineno = 5+3 @property @@ -430,27 +501,37 @@ 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) fail = IOError() # noqa repr = pr.repr_excinfo(excinfo) assert repr.reprtraceback.reprentries[0].lines[0] == "> ???" + if py.std.sys.version_info[0] >= 3: + assert repr.chain[0][0].reprentries[0].lines[0] == "> ???" + fail = py.error.ENOENT # noqa repr = pr.repr_excinfo(excinfo) assert repr.reprtraceback.reprentries[0].lines[0] == "> ???" + if py.std.sys.version_info[0] >= 3: + assert repr.chain[0][0].reprentries[0].lines[0] == "> ???" def test_repr_local(self): @@ -637,6 +718,9 @@ repr = p.repr_excinfo(excinfo) assert repr.reprtraceback assert len(repr.reprtraceback.reprentries) == len(reprtb.reprentries) + if py.std.sys.version_info[0] >= 3: + assert repr.chain[0][0] + assert len(repr.chain[0][0].reprentries) == len(reprtb.reprentries) assert repr.reprcrash.path.endswith("mod.py") assert repr.reprcrash.message == "ValueError: 0" @@ -650,8 +734,10 @@ 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) @@ -698,23 +784,6 @@ assert reprtb.extraline == "!!! Recursion detected (same locals & position)" assert str(reprtb) - def test_tb_entry_AssertionError(self, importasmod): - # probably this test is a bit redundant - # as py/magic/testing/test_assertion.py - # already tests correctness of - # assertion-reinterpretation logic - mod = importasmod(""" - def somefunc(): - x = 1 - assert x == 2 - """) - excinfo = pytest.raises(AssertionError, mod.somefunc) - - p = FormattedExcinfo() - reprentry = p.repr_traceback_entry(excinfo.traceback[-1], excinfo) - lines = reprentry.lines - assert lines[-1] == "E assert 1 == 2" - def test_reprexcinfo_getrepr(self, importasmod): mod = importasmod(""" def f(x): @@ -727,14 +796,21 @@ for style in ("short", "long", "no"): for showlocals in (True, False): repr = excinfo.getrepr(style=style, showlocals=showlocals) - assert isinstance(repr, ReprExceptionInfo) + if py.std.sys.version_info[0] < 3: + assert isinstance(repr, ReprExceptionInfo) assert repr.reprtraceback.style == style + if py.std.sys.version_info[0] >= 3: + assert isinstance(repr, ExceptionChainRepr) + for repr in repr.chain: + assert repr[0].style == style 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") @@ -755,14 +831,18 @@ assert tw.lines[0] == " def f():" assert tw.lines[1] == "> g(3)" assert tw.lines[2] == "" - assert tw.lines[3].endswith("mod.py:5: ") - assert tw.lines[4] == ("_ ", None) - assert tw.lines[5] == "" - assert tw.lines[6] == " def g(x):" - assert tw.lines[7] == "> raise ValueError(x)" - assert tw.lines[8] == "E ValueError: 3" - assert tw.lines[9] == "" - assert tw.lines[10].endswith("mod.py:3: ValueError") + line = tw.get_write_msg(3) + assert line.endswith("mod.py") + assert tw.lines[4] == (":5: ") + assert tw.lines[5] == ("_ ", None) + assert tw.lines[6] == "" + assert tw.lines[7] == " def g(x):" + assert tw.lines[8] == "> raise ValueError(x)" + assert tw.lines[9] == "E ValueError: 3" + assert tw.lines[10] == "" + line = tw.get_write_msg(11) + assert line.endswith("mod.py") + assert tw.lines[12] == ":3: ValueError" def test_toterminal_long_missing_source(self, importasmod, tmpdir): mod = importasmod(""" @@ -781,13 +861,17 @@ tw.lines.pop(0) assert tw.lines[0] == "> ???" assert tw.lines[1] == "" - assert tw.lines[2].endswith("mod.py:5: ") - assert tw.lines[3] == ("_ ", None) - assert tw.lines[4] == "" - assert tw.lines[5] == "> ???" - assert tw.lines[6] == "E ValueError: 3" - assert tw.lines[7] == "" - assert tw.lines[8].endswith("mod.py:3: ValueError") + line = tw.get_write_msg(2) + assert line.endswith("mod.py") + assert tw.lines[3] == ":5: " + assert tw.lines[4] == ("_ ", None) + assert tw.lines[5] == "" + assert tw.lines[6] == "> ???" + assert tw.lines[7] == "E ValueError: 3" + assert tw.lines[8] == "" + line = tw.get_write_msg(9) + assert line.endswith("mod.py") + assert tw.lines[10] == ":3: ValueError" def test_toterminal_long_incomplete_source(self, importasmod, tmpdir): mod = importasmod(""" @@ -806,13 +890,17 @@ tw.lines.pop(0) assert tw.lines[0] == "> ???" assert tw.lines[1] == "" - assert tw.lines[2].endswith("mod.py:5: ") - assert tw.lines[3] == ("_ ", None) - assert tw.lines[4] == "" - assert tw.lines[5] == "> ???" - assert tw.lines[6] == "E ValueError: 3" - assert tw.lines[7] == "" - assert tw.lines[8].endswith("mod.py:3: ValueError") + line = tw.get_write_msg(2) + assert line.endswith("mod.py") + assert tw.lines[3] == ":5: " + assert tw.lines[4] == ("_ ", None) + assert tw.lines[5] == "" + assert tw.lines[6] == "> ???" + assert tw.lines[7] == "E ValueError: 3" + assert tw.lines[8] == "" + line = tw.get_write_msg(9) + assert line.endswith("mod.py") + assert tw.lines[10] == ":3: ValueError" def test_toterminal_long_filenames(self, importasmod): mod = importasmod(""" @@ -826,15 +914,18 @@ try: repr = excinfo.getrepr(abspath=False) repr.toterminal(tw) - line = tw.lines[-1] x = py.path.local().bestrelpath(path) if len(x) < len(str(path)): - assert line == "mod.py:3: ValueError" + msg = tw.get_write_msg(-2) + assert msg == "mod.py" + assert tw.lines[-1] == ":3: ValueError" repr = excinfo.getrepr(abspath=True) repr.toterminal(tw) + msg = tw.get_write_msg(-2) + assert msg == path line = tw.lines[-1] - assert line == "%s:3: ValueError" %(path,) + assert line == ":3: ValueError" finally: old.chdir() @@ -858,21 +949,6 @@ repr.toterminal(tw) assert tw.stringio.getvalue() - - def test_native_style(self): - excinfo = self.excinfo_from_exec(""" - assert 0 - """) - repr = excinfo.getrepr(style='native') - assert "assert 0" in str(repr.reprcrash) - s = str(repr) - assert s.startswith('Traceback (most recent call last):\n File') - assert s.endswith('\nAssertionError: assert 0') - assert 'exec (source.compile())' in s - # python 2.4 fails to get the source line for the assert - if py.std.sys.version_info >= (2, 5): - assert s.count('assert 0') == 2 - def test_traceback_repr_style(self, importasmod): mod = importasmod(""" def f(): @@ -896,19 +972,146 @@ assert tw.lines[1] == " def f():" assert tw.lines[2] == "> g()" assert tw.lines[3] == "" - assert tw.lines[4].endswith("mod.py:3: ") - assert tw.lines[5] == ("_ ", None) - assert tw.lines[6].endswith("in g") - assert tw.lines[7] == " h()" - assert tw.lines[8].endswith("in h") - assert tw.lines[9] == " i()" - assert tw.lines[10] == ("_ ", None) - assert tw.lines[11] == "" - assert tw.lines[12] == " def i():" - assert tw.lines[13] == "> raise ValueError()" - assert tw.lines[14] == "E ValueError" + msg = tw.get_write_msg(4) + assert msg.endswith("mod.py") + assert tw.lines[5] == ":3: " + assert tw.lines[6] == ("_ ", None) + tw.get_write_msg(7) + assert tw.lines[8].endswith("in g") + assert tw.lines[9] == " h()" + tw.get_write_msg(10) + assert tw.lines[11].endswith("in h") + assert tw.lines[12] == " i()" + assert tw.lines[13] == ("_ ", None) + assert tw.lines[14] == "" + assert tw.lines[15] == " def i():" + assert tw.lines[16] == "> raise ValueError()" + assert tw.lines[17] == "E ValueError" + assert tw.lines[18] == "" + msg = tw.get_write_msg(19) + msg.endswith("mod.py") + assert tw.lines[20] == ":9: ValueError" + + @pytest.mark.skipif("sys.version_info[0] < 3") + def test_exc_chain_repr(self, importasmod): + mod = importasmod(""" + class Err(Exception): + pass + def f(): + try: + g() + except Exception as e: + raise Err() from e + finally: + h() + def g(): + raise ValueError() + + def h(): + raise AttributeError() + """) + excinfo = pytest.raises(AttributeError, mod.f) + r = excinfo.getrepr(style="long") + tw = TWMock() + r.toterminal(tw) + for line in tw.lines: print (line) + assert tw.lines[0] == "" + assert tw.lines[1] == " def f():" + assert tw.lines[2] == " try:" + assert tw.lines[3] == "> g()" + assert tw.lines[4] == "" + line = tw.get_write_msg(5) + assert line.endswith('mod.py') + assert tw.lines[6] == ':6: ' + assert tw.lines[7] == ("_ ", None) + assert tw.lines[8] == "" + assert tw.lines[9] == " def g():" + assert tw.lines[10] == "> raise ValueError()" + assert tw.lines[11] == "E ValueError" + assert tw.lines[12] == "" + line = tw.get_write_msg(13) + assert line.endswith('mod.py') + assert tw.lines[14] == ':12: ValueError' assert tw.lines[15] == "" - assert tw.lines[16].endswith("mod.py:9: ValueError") + assert tw.lines[16] == "The above exception was the direct cause of the following exception:" + assert tw.lines[17] == "" + assert tw.lines[18] == " def f():" + assert tw.lines[19] == " try:" + assert tw.lines[20] == " g()" + assert tw.lines[21] == " except Exception as e:" + assert tw.lines[22] == "> raise Err() from e" + assert tw.lines[23] == "E test_exc_chain_repr0.mod.Err" + assert tw.lines[24] == "" + line = tw.get_write_msg(25) + assert line.endswith('mod.py') + assert tw.lines[26] == ":8: Err" + assert tw.lines[27] == "" + assert tw.lines[28] == "During handling of the above exception, another exception occurred:" + assert tw.lines[29] == "" + assert tw.lines[30] == " def f():" + assert tw.lines[31] == " try:" + assert tw.lines[32] == " g()" + assert tw.lines[33] == " except Exception as e:" + assert tw.lines[34] == " raise Err() from e" + assert tw.lines[35] == " finally:" + assert tw.lines[36] == "> h()" + assert tw.lines[37] == "" + line = tw.get_write_msg(38) + assert line.endswith('mod.py') + assert tw.lines[39] == ":10: " + assert tw.lines[40] == ('_ ', None) + assert tw.lines[41] == "" + assert tw.lines[42] == " def h():" + assert tw.lines[43] == "> raise AttributeError()" + assert tw.lines[44] == "E AttributeError" + assert tw.lines[45] == "" + line = tw.get_write_msg(46) + 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"]) @@ -925,3 +1128,14 @@ repr_traceback = formatter.repr_traceback(e_info) assert repr_traceback is not None + +def test_cwd_deleted(testdir): + testdir.makepyfile(""" + def test(tmpdir): + tmpdir.chdir() + tmpdir.remove() + assert False + """) + result = testdir.runpytest() + result.stdout.fnmatch_lines(['* 1 failed in *']) + assert 'INTERNALERROR' not in result.stdout.str() + result.stderr.str() diff -Nru pytest-2.9.2/testing/code/test_source.py pytest-3.0.6/testing/code/test_source.py --- pytest-2.9.2/testing/code/test_source.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/testing/code/test_source.py 2016-08-20 20:00:30.000000000 +0000 @@ -285,13 +285,14 @@ #print "block", str(block) assert str(stmt).strip().startswith('assert') - def test_compilefuncs_and_path_sanity(self): + @pytest.mark.parametrize('name', ['', None, 'my']) + def test_compilefuncs_and_path_sanity(self, name): def check(comp, name): co = comp(self.source, name) if not name: - expected = "codegen %s:%d>" %(mypath, mylineno+2+1) + expected = "codegen %s:%d>" %(mypath, mylineno+2+2) else: - expected = "codegen %r %s:%d>" % (name, mypath, mylineno+2+1) + expected = "codegen %r %s:%d>" % (name, mypath, mylineno+2+2) fn = co.co_filename assert fn.endswith(expected) @@ -300,8 +301,7 @@ mypath = mycode.path for comp in _pytest._code.compile, _pytest._code.Source.compile: - for name in '', None, 'my': - yield check, comp, name + check(comp, name) def test_offsetless_synerr(self): pytest.raises(SyntaxError, _pytest._code.compile, "lambda a,a: 0", mode='eval') @@ -385,8 +385,7 @@ lines = deindent(source.splitlines()) assert lines == ['', 'def f():', ' def g():', ' pass', ' '] -@pytest.mark.xfail("sys.version_info[:3] < (2,7,0) or " - "((3,0) <= sys.version_info[:2] < (3,2))") +@pytest.mark.xfail("sys.version_info[:3] < (2,7,0)") def test_source_of_class_at_eof_without_newline(tmpdir): # this test fails because the implicit inspect.getsource(A) below # does not return the "x = 1" last line. @@ -656,4 +655,3 @@ '''""" result = getstatement(1, source) assert str(result) == "'''\n'''" - diff -Nru pytest-2.9.2/testing/cx_freeze/install_cx_freeze.py pytest-3.0.6/testing/cx_freeze/install_cx_freeze.py --- pytest-2.9.2/testing/cx_freeze/install_cx_freeze.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/testing/cx_freeze/install_cx_freeze.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,64 +0,0 @@ -""" -Installs cx_freeze from source, but first patching -setup.py as described here: - -http://stackoverflow.com/questions/25107697/compiling-cx-freeze-under-ubuntu -""" -import glob -import tarfile -import os -import sys -import platform -import py - -if __name__ == '__main__': - if 'ubuntu' not in platform.version().lower(): - - print('Not Ubuntu, installing using pip. (platform.version() is %r)' % - platform.version()) - res = os.system('pip install cx_freeze') - if res != 0: - sys.exit(res) - sys.exit(0) - - rootdir = py.path.local.make_numbered_dir(prefix='cx_freeze') - - res = os.system('pip install --download %s --no-use-wheel ' - 'cx_freeze' % rootdir) - if res != 0: - sys.exit(res) - - packages = glob.glob('%s/*.tar.gz' % rootdir) - assert len(packages) == 1 - tar_filename = packages[0] - - tar_file = tarfile.open(tar_filename) - try: - tar_file.extractall(path=str(rootdir)) - finally: - tar_file.close() - - basename = os.path.basename(tar_filename).replace('.tar.gz', '') - setup_py_filename = '%s/%s/setup.py' % (rootdir, basename) - with open(setup_py_filename) as f: - lines = f.readlines() - - line_to_patch = 'if not vars.get("Py_ENABLE_SHARED", 0):' - for index, line in enumerate(lines): - if line_to_patch in line: - indent = line[:line.index(line_to_patch)] - lines[index] = indent + 'if True:\n' - print('Patched line %d' % (index + 1)) - break - else: - sys.exit('Could not find line in setup.py to patch!') - - with open(setup_py_filename, 'w') as f: - f.writelines(lines) - - os.chdir('%s/%s' % (rootdir, basename)) - res = os.system('python setup.py install') - if res != 0: - sys.exit(res) - - sys.exit(0) diff -Nru pytest-2.9.2/testing/cx_freeze/runtests_script.py pytest-3.0.6/testing/cx_freeze/runtests_script.py --- pytest-2.9.2/testing/cx_freeze/runtests_script.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/testing/cx_freeze/runtests_script.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,9 +0,0 @@ -""" -This is the script that is actually frozen into an executable: simply executes -py.test main(). -""" - -if __name__ == '__main__': - import sys - import pytest - sys.exit(pytest.main()) \ No newline at end of file diff -Nru pytest-2.9.2/testing/cx_freeze/runtests_setup.py pytest-3.0.6/testing/cx_freeze/runtests_setup.py --- pytest-2.9.2/testing/cx_freeze/runtests_setup.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/testing/cx_freeze/runtests_setup.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,15 +0,0 @@ -""" -Sample setup.py script that generates an executable with pytest runner embedded. -""" -if __name__ == '__main__': - from cx_Freeze import setup, Executable - import pytest - - setup( - name="runtests", - version="0.1", - description="exemple of how embedding py.test into an executable using cx_freeze", - executables=[Executable("runtests_script.py")], - options={"build_exe": {'includes': pytest.freeze_includes()}}, - ) - diff -Nru pytest-2.9.2/testing/cx_freeze/tests/test_doctest.txt pytest-3.0.6/testing/cx_freeze/tests/test_doctest.txt --- pytest-2.9.2/testing/cx_freeze/tests/test_doctest.txt 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/testing/cx_freeze/tests/test_doctest.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,6 +0,0 @@ - - -Testing doctest:: - - >>> 1 + 1 - 2 diff -Nru pytest-2.9.2/testing/cx_freeze/tests/test_trivial.py pytest-3.0.6/testing/cx_freeze/tests/test_trivial.py --- pytest-2.9.2/testing/cx_freeze/tests/test_trivial.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/testing/cx_freeze/tests/test_trivial.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,6 +0,0 @@ - -def test_upper(): - assert 'foo'.upper() == 'FOO' - -def test_lower(): - assert 'FOO'.lower() == 'foo' \ No newline at end of file diff -Nru pytest-2.9.2/testing/cx_freeze/tox_run.py pytest-3.0.6/testing/cx_freeze/tox_run.py --- pytest-2.9.2/testing/cx_freeze/tox_run.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/testing/cx_freeze/tox_run.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,15 +0,0 @@ -""" -Called by tox.ini: uses the generated executable to run the tests in ./tests/ -directory. - -.. note:: somehow calling "build/runtests_script" directly from tox doesn't - seem to work (at least on Windows). -""" -if __name__ == '__main__': - import os - import sys - - executable = os.path.join(os.getcwd(), 'build', 'runtests_script') - if sys.platform.startswith('win'): - executable += '.exe' - sys.exit(os.system('%s tests' % executable)) \ No newline at end of file diff -Nru pytest-2.9.2/testing/deprecated_test.py pytest-3.0.6/testing/deprecated_test.py --- pytest-2.9.2/testing/deprecated_test.py 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/testing/deprecated_test.py 2016-08-27 09:59:07.000000000 +0000 @@ -0,0 +1,76 @@ +import pytest + + +def test_yield_tests_deprecation(testdir): + testdir.makepyfile(""" + def func1(arg, arg2): + assert arg == arg2 + def test_gen(): + yield "m1", func1, 15, 3*5 + yield "m2", func1, 42, 6*7 + """) + result = testdir.runpytest('-ra') + result.stdout.fnmatch_lines([ + '*yield tests are deprecated, and scheduled to be removed in pytest 4.0*', + '*2 passed*', + ]) + + +def test_funcarg_prefix_deprecation(testdir): + testdir.makepyfile(""" + def pytest_funcarg__value(): + return 10 + + def test_funcarg_prefix(value): + assert value == 10 + """) + result = testdir.runpytest('-ra') + result.stdout.fnmatch_lines([ + ('WC1 None pytest_funcarg__value: ' + 'declaring fixtures using "pytest_funcarg__" prefix is deprecated ' + 'and scheduled to be removed in pytest 4.0. ' + 'Please remove the prefix and use the @pytest.fixture decorator instead.'), + '*1 passed*', + ]) + + +def test_pytest_setup_cfg_deprecated(testdir): + testdir.makefile('.cfg', setup=''' + [pytest] + addopts = --verbose + ''') + result = testdir.runpytest() + result.stdout.fnmatch_lines(['*pytest*section in setup.cfg files is deprecated*use*tool:pytest*instead*']) + + +def test_str_args_deprecated(tmpdir, testdir): + """Deprecate passing strings to pytest.main(). Scheduled for removal in pytest-4.0.""" + from _pytest.main import EXIT_NOTESTSCOLLECTED + warnings = [] + + class Collect: + def pytest_logwarning(self, message): + warnings.append(message) + + ret = pytest.main("%s -x" % tmpdir, plugins=[Collect()]) + testdir.delete_loaded_modules() + msg = ('passing a string to pytest.main() is deprecated, ' + 'pass a list of arguments instead.') + assert msg in warnings + assert ret == EXIT_NOTESTSCOLLECTED + + +def test_getfuncargvalue_is_deprecated(request): + pytest.deprecated_call(request.getfuncargvalue, 'tmpdir') + + +def test_resultlog_is_deprecated(testdir): + result = testdir.runpytest('--help') + result.stdout.fnmatch_lines(['*DEPRECATED path for machine-readable result log*']) + + testdir.makepyfile(''' + def test(): + pass + ''') + result = testdir.runpytest('--result-log=%s' % testdir.tmpdir.join('result.log')) + result.stdout.fnmatch_lines(['*--result-log is deprecated and scheduled for removal in pytest 4.0*']) diff -Nru pytest-2.9.2/testing/freeze/create_executable.py pytest-3.0.6/testing/freeze/create_executable.py --- pytest-2.9.2/testing/freeze/create_executable.py 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/testing/freeze/create_executable.py 2016-08-27 09:59:07.000000000 +0000 @@ -0,0 +1,13 @@ +""" +Generates an executable with pytest runner embedded using PyInstaller. +""" +if __name__ == '__main__': + import pytest + import subprocess + + hidden = [] + for x in pytest.freeze_includes(): + hidden.extend(['--hidden-import', x]) + args = ['pyinstaller', '--noconfirm'] + hidden + ['runtests_script.py'] + subprocess.check_call(' '.join(args), shell=True) + diff -Nru pytest-2.9.2/testing/freeze/.gitignore pytest-3.0.6/testing/freeze/.gitignore --- pytest-2.9.2/testing/freeze/.gitignore 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/testing/freeze/.gitignore 2016-08-27 09:59:07.000000000 +0000 @@ -0,0 +1,3 @@ +build/ +dist/ +*.spec \ No newline at end of file diff -Nru pytest-2.9.2/testing/freeze/runtests_script.py pytest-3.0.6/testing/freeze/runtests_script.py --- pytest-2.9.2/testing/freeze/runtests_script.py 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/testing/freeze/runtests_script.py 2016-08-27 09:59:07.000000000 +0000 @@ -0,0 +1,9 @@ +""" +This is the script that is actually frozen into an executable: simply executes +py.test main(). +""" + +if __name__ == '__main__': + import sys + import pytest + sys.exit(pytest.main()) \ No newline at end of file diff -Nru pytest-2.9.2/testing/freeze/tests/test_doctest.txt pytest-3.0.6/testing/freeze/tests/test_doctest.txt --- pytest-2.9.2/testing/freeze/tests/test_doctest.txt 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/testing/freeze/tests/test_doctest.txt 2016-08-27 09:59:07.000000000 +0000 @@ -0,0 +1,6 @@ + + +Testing doctest:: + + >>> 1 + 1 + 2 diff -Nru pytest-2.9.2/testing/freeze/tests/test_trivial.py pytest-3.0.6/testing/freeze/tests/test_trivial.py --- pytest-2.9.2/testing/freeze/tests/test_trivial.py 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/testing/freeze/tests/test_trivial.py 2016-08-27 09:59:07.000000000 +0000 @@ -0,0 +1,6 @@ + +def test_upper(): + assert 'foo'.upper() == 'FOO' + +def test_lower(): + assert 'FOO'.lower() == 'foo' \ No newline at end of file diff -Nru pytest-2.9.2/testing/freeze/tox_run.py pytest-3.0.6/testing/freeze/tox_run.py --- pytest-2.9.2/testing/freeze/tox_run.py 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/testing/freeze/tox_run.py 2016-08-27 09:59:07.000000000 +0000 @@ -0,0 +1,12 @@ +""" +Called by tox.ini: uses the generated executable to run the tests in ./tests/ +directory. +""" +if __name__ == '__main__': + import os + import sys + + executable = os.path.join(os.getcwd(), 'dist', 'runtests_script', 'runtests_script') + if sys.platform.startswith('win'): + executable += '.exe' + sys.exit(os.system('%s tests' % executable)) \ No newline at end of file diff -Nru pytest-2.9.2/testing/python/approx.py pytest-3.0.6/testing/python/approx.py --- pytest-2.9.2/testing/python/approx.py 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/testing/python/approx.py 2017-01-19 09:44:52.000000000 +0000 @@ -0,0 +1,312 @@ +# encoding: utf-8 +import sys +import pytest +import doctest + +from pytest import approx +from operator import eq, ne +from decimal import Decimal +from fractions import Fraction +inf, nan = float('inf'), float('nan') + + +class MyDocTestRunner(doctest.DocTestRunner): + + def __init__(self): + doctest.DocTestRunner.__init__(self) + + def report_failure(self, out, test, example, got): + raise AssertionError("'{}' evaluates to '{}', not '{}'".format( + example.source.strip(), got.strip(), example.want.strip())) + + +class TestApprox: + + def test_repr_string(self): + # 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) + assert not (1 != approx(1, rel=1e-6, abs=1e-12)) + assert 10 != approx(1, rel=1e-6, abs=1e-12) + assert not (10 == approx(1, rel=1e-6, abs=1e-12)) + + def test_exactly_equal(self): + examples = [ + (2.0, 2.0), + (0.1e200, 0.1e200), + (1.123e-300, 1.123e-300), + (12345, 12345.0), + (0.0, -0.0), + (345678, 345678), + (Decimal('1.0001'), Decimal('1.0001')), + (Fraction(1, 3), Fraction(-1, -3)), + ] + for a, x in examples: + assert a == approx(x) + + def test_opposite_sign(self): + examples = [ + (eq, 1e-100, -1e-100), + (ne, 1e100, -1e100), + ] + for op, a, x in examples: + assert op(a, approx(x)) + + def test_zero_tolerance(self): + within_1e10 = [ + (1.1e-100, 1e-100), + (-1.1e-100, -1e-100), + ] + for a, x in within_1e10: + assert x == approx(x, rel=0.0, abs=0.0) + assert a != approx(x, rel=0.0, abs=0.0) + assert a == approx(x, rel=0.0, abs=5e-101) + assert a != approx(x, rel=0.0, abs=5e-102) + assert a == approx(x, rel=5e-1, abs=0.0) + assert a != approx(x, rel=5e-2, abs=0.0) + + def test_negative_tolerance(self): + # Negative tolerances are not allowed. + illegal_kwargs = [ + dict(rel=-1e100), + dict(abs=-1e100), + dict(rel=1e100, abs=-1e100), + dict(rel=-1e100, abs=1e100), + dict(rel=-1e100, abs=-1e100), + ] + for kwargs in illegal_kwargs: + with pytest.raises(ValueError): + 1.1 == approx(1, **kwargs) + + def test_inf_tolerance(self): + # Everything should be equal if the tolerance is infinite. + large_diffs = [ + (1, 1000), + (1e-50, 1e50), + (-1.0, -1e300), + (0.0, 10), + ] + for a, x in large_diffs: + assert a != approx(x, rel=0.0, abs=0.0) + assert a == approx(x, rel=inf, abs=0.0) + assert a == approx(x, rel=0.0, abs=inf) + assert a == approx(x, rel=inf, abs=inf) + + def test_inf_tolerance_expecting_zero(self): + # If the relative tolerance is zero but the expected value is infinite, + # the actual tolerance is a NaN, which should be an error. + illegal_kwargs = [ + dict(rel=inf, abs=0.0), + dict(rel=inf, abs=inf), + ] + for kwargs in illegal_kwargs: + with pytest.raises(ValueError): + 1 == approx(0, **kwargs) + + def test_nan_tolerance(self): + illegal_kwargs = [ + dict(rel=nan), + dict(abs=nan), + dict(rel=nan, abs=nan), + ] + for kwargs in illegal_kwargs: + with pytest.raises(ValueError): + 1.1 == approx(1, **kwargs) + + def test_reasonable_defaults(self): + # Whatever the defaults are, they should work for numbers close to 1 + # than have a small amount of floating-point error. + assert 0.1 + 0.2 == approx(0.3) + + def test_default_tolerances(self): + # This tests the defaults as they are currently set. If you change the + # defaults, this test will fail but you should feel free to change it. + # None of the other tests (except the doctests) should be affected by + # the choice of defaults. + examples = [ + # Relative tolerance used. + (eq, 1e100 + 1e94, 1e100), + (ne, 1e100 + 2e94, 1e100), + (eq, 1e0 + 1e-6, 1e0), + (ne, 1e0 + 2e-6, 1e0), + # Absolute tolerance used. + (eq, 1e-100, + 1e-106), + (eq, 1e-100, + 2e-106), + (eq, 1e-100, 0), + ] + for op, a, x in examples: + assert op(a, approx(x)) + + def test_custom_tolerances(self): + assert 1e8 + 1e0 == approx(1e8, rel=5e-8, abs=5e0) + assert 1e8 + 1e0 == approx(1e8, rel=5e-9, abs=5e0) + assert 1e8 + 1e0 == approx(1e8, rel=5e-8, abs=5e-1) + assert 1e8 + 1e0 != approx(1e8, rel=5e-9, abs=5e-1) + + assert 1e0 + 1e-8 == approx(1e0, rel=5e-8, abs=5e-8) + assert 1e0 + 1e-8 == approx(1e0, rel=5e-9, abs=5e-8) + assert 1e0 + 1e-8 == approx(1e0, rel=5e-8, abs=5e-9) + assert 1e0 + 1e-8 != approx(1e0, rel=5e-9, abs=5e-9) + + assert 1e-8 + 1e-16 == approx(1e-8, rel=5e-8, abs=5e-16) + assert 1e-8 + 1e-16 == approx(1e-8, rel=5e-9, abs=5e-16) + assert 1e-8 + 1e-16 == approx(1e-8, rel=5e-8, abs=5e-17) + assert 1e-8 + 1e-16 != approx(1e-8, rel=5e-9, abs=5e-17) + + def test_relative_tolerance(self): + within_1e8_rel = [ + (1e8 + 1e0, 1e8), + (1e0 + 1e-8, 1e0), + (1e-8 + 1e-16, 1e-8), + ] + for a, x in within_1e8_rel: + assert a == approx(x, rel=5e-8, abs=0.0) + assert a != approx(x, rel=5e-9, abs=0.0) + + def test_absolute_tolerance(self): + within_1e8_abs = [ + (1e8 + 9e-9, 1e8), + (1e0 + 9e-9, 1e0), + (1e-8 + 9e-9, 1e-8), + ] + for a, x in within_1e8_abs: + assert a == approx(x, rel=0, abs=5e-8) + assert a != approx(x, rel=0, abs=5e-9) + + def test_expecting_zero(self): + examples = [ + (ne, 1e-6, 0.0), + (ne, -1e-6, 0.0), + (eq, 1e-12, 0.0), + (eq, -1e-12, 0.0), + (ne, 2e-12, 0.0), + (ne, -2e-12, 0.0), + (ne, inf, 0.0), + (ne, nan, 0.0), + ] + for op, a, x in examples: + assert op(a, approx(x, rel=0.0, abs=1e-12)) + assert op(a, approx(x, rel=1e-6, abs=1e-12)) + + def test_expecting_inf(self): + examples = [ + (eq, inf, inf), + (eq, -inf, -inf), + (ne, inf, -inf), + (ne, 0.0, inf), + (ne, nan, inf), + ] + for op, a, x in examples: + assert op(a, approx(x)) + + def test_expecting_nan(self): + examples = [ + (nan, nan), + (-nan, -nan), + (nan, -nan), + (0.0, nan), + (inf, nan), + ] + for a, x in examples: + # If there is a relative tolerance and the expected value is NaN, + # the actual tolerance is a NaN, which should be an error. + with pytest.raises(ValueError): + a != approx(x, rel=inf) + + # You can make comparisons against NaN by not specifying a relative + # tolerance, so only an absolute tolerance is calculated. + assert a != approx(x, abs=inf) + + def test_expecting_sequence(self): + within_1e8 = [ + (1e8 + 1e0, 1e8), + (1e0 + 1e-8, 1e0), + (1e-8 + 1e-16, 1e-8), + ] + actual, expected = zip(*within_1e8) + assert actual == approx(expected, rel=5e-8, abs=0.0) + + def test_expecting_sequence_wrong_len(self): + assert [1, 2] != approx([1]) + assert [1, 2] != approx([1,2,3]) + + def test_complex(self): + within_1e6 = [ + ( 1.000001 + 1.0j, 1.0 + 1.0j), + (1.0 + 1.000001j, 1.0 + 1.0j), + (-1.000001 + 1.0j, -1.0 + 1.0j), + (1.0 - 1.000001j, 1.0 - 1.0j), + ] + for a, x in within_1e6: + assert a == approx(x, rel=5e-6, abs=0) + assert a != approx(x, rel=5e-7, abs=0) + + def test_int(self): + within_1e6 = [ + (1000001, 1000000), + (-1000001, -1000000), + ] + for a, x in within_1e6: + assert a == approx(x, rel=5e-6, abs=0) + assert a != approx(x, rel=5e-7, abs=0) + + def test_decimal(self): + within_1e6 = [ + (Decimal('1.000001'), Decimal('1.0')), + (Decimal('-1.000001'), Decimal('-1.0')), + ] + for a, x in within_1e6: + assert a == approx(x, rel=Decimal('5e-6'), abs=0) + assert a != approx(x, rel=Decimal('5e-7'), abs=0) + + def test_fraction(self): + within_1e6 = [ + (1 + Fraction(1, 1000000), Fraction(1)), + (-1 - Fraction(-1, 1000000), Fraction(-1)), + ] + for a, x in within_1e6: + assert a == approx(x, rel=5e-6, abs=0) + assert a != approx(x, rel=5e-7, abs=0) + + def test_doctests(self): + parser = doctest.DocTestParser() + test = parser.get_doctest( + approx.__doc__, + {'approx': approx}, + approx.__name__, + None, None, + ) + 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 -Nru pytest-2.9.2/testing/python/collect.py pytest-3.0.6/testing/python/collect.py --- pytest-2.9.2/testing/python/collect.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/testing/python/collect.py 2017-01-19 09:44:52.000000000 +0000 @@ -1,18 +1,21 @@ # -*- coding: utf-8 -*- +import os import sys from textwrap import dedent import _pytest._code import py import pytest -from _pytest.main import EXIT_NOTESTSCOLLECTED +from _pytest.main import ( + Collector, + EXIT_NOTESTSCOLLECTED +) class TestModule: def test_failing_import(self, testdir): modcol = testdir.getmodulecol("import alksdjalskdjalkjals") - pytest.raises(ImportError, modcol.collect) - pytest.raises(ImportError, modcol.collect) + pytest.raises(Collector.CollectError, modcol.collect) def test_import_duplicate(self, testdir): a = testdir.mkdir("a") @@ -60,6 +63,48 @@ modcol = testdir.getmodulecol("pytest_plugins='xasdlkj',") pytest.raises(ImportError, lambda: modcol.obj) + def test_invalid_test_module_name(self, testdir): + a = testdir.mkdir('a') + a.ensure('test_one.part1.py') + result = testdir.runpytest("-rw") + result.stdout.fnmatch_lines([ + "ImportError while importing test module*test_one.part1*", + "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 + + class TestClass: def test_class_with_init_warning(self, testdir): testdir.makepyfile(""" @@ -109,6 +154,18 @@ colitems = modcol.collect() assert len(colitems) == 0 + def test_issue1579_namedtuple(self, testdir): + testdir.makepyfile(""" + import collections + + TestCase = collections.namedtuple('TestCase', ['a']) + """) + result = testdir.runpytest('-rw') + result.stdout.fnmatch_lines( + "*cannot collect test class 'TestCase' " + "because it has a __new__ constructor*" + ) + class TestGenerator: def test_generative_functions(self, testdir): @@ -322,14 +379,17 @@ reprec.assertoutcome() def test_function_equality(self, testdir, tmpdir): - from _pytest.python import FixtureManager + from _pytest.fixtures import FixtureManager 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 @@ -490,12 +550,15 @@ 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) @@ -610,6 +673,15 @@ result = testdir.runpytest() result.stdout.fnmatch_lines('* 3 passed in *') + def test_function_original_name(self, testdir): + items = testdir.getitems(""" + import pytest + @pytest.mark.parametrize('arg', [1,2]) + def test_func(arg): + pass + """) + assert [x.originalname for x in items] == ['test_func', 'test_func'] + class TestSorting: def test_check_equality(self, testdir): @@ -783,21 +855,24 @@ def test_traceback_argsetup(self, testdir): testdir.makeconftest(""" - def pytest_funcarg__hello(request): + import pytest + + @pytest.fixture + def hello(request): raise ValueError("xyz") """) p = testdir.makepyfile("def test(hello): pass") result = testdir.runpytest(p) assert result.ret != 0 out = result.stdout.str() - assert out.find("xyz") != -1 - assert out.find("conftest.py:2: ValueError") != -1 + assert "xyz" in out + assert "conftest.py:5: ValueError" in out numentries = out.count("_ _ _") # separator for traceback entries assert numentries == 0 result = testdir.runpytest("--fulltrace", p) out = result.stdout.str() - assert out.find("conftest.py:2: ValueError") != -1 + assert "conftest.py:5: ValueError" in out numentries = out.count("_ _ _ _") # separator for traceback entries assert numentries > 3 @@ -1198,3 +1273,40 @@ '*SyntaxError*', '*1 error in*', ]) + + +def test_skip_duplicates_by_default(testdir): + """Test for issue https://github.com/pytest-dev/pytest/issues/1609 (#1609) + + Ignore duplicate directories. + """ + a = testdir.mkdir("a") + fh = a.join("test_a.py") + fh.write(_pytest._code.Source(""" + import pytest + def test_real(): + pass + """)) + result = testdir.runpytest(a.strpath, a.strpath) + result.stdout.fnmatch_lines([ + '*collected 1 item*', + ]) + + + +def test_keep_duplicates(testdir): + """Test for issue https://github.com/pytest-dev/pytest/issues/1609 (#1609) + + Use --keep-duplicates to collect tests from duplicate directories. + """ + a = testdir.mkdir("a") + fh = a.join("test_a.py") + fh.write(_pytest._code.Source(""" + import pytest + def test_real(): + pass + """)) + result = testdir.runpytest("--keep-duplicates", a.strpath, a.strpath) + result.stdout.fnmatch_lines([ + '*collected 2 item*', + ]) diff -Nru pytest-2.9.2/testing/python/fixture.py pytest-3.0.6/testing/python/fixture.py --- pytest-2.9.2/testing/python/fixture.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/testing/python/fixture.py 2017-01-19 09:44:52.000000000 +0000 @@ -3,35 +3,42 @@ import _pytest._code import pytest import sys -from _pytest import python as funcargs from _pytest.pytester import get_public_names -from _pytest.python import FixtureLookupError - +from _pytest.fixtures import FixtureLookupError +from _pytest import fixtures def test_getfuncargnames(): def f(): pass - assert not funcargs.getfuncargnames(f) + assert not fixtures.getfuncargnames(f) + def g(arg): pass - assert funcargs.getfuncargnames(g) == ('arg',) + assert fixtures.getfuncargnames(g) == ('arg',) + def h(arg1, arg2="hello"): pass - assert funcargs.getfuncargnames(h) == ('arg1',) + assert fixtures.getfuncargnames(h) == ('arg1',) + def h(arg1, arg2, arg3="hello"): pass - assert funcargs.getfuncargnames(h) == ('arg1', 'arg2') + assert fixtures.getfuncargnames(h) == ('arg1', 'arg2') + class A: def f(self, arg1, arg2="hello"): pass - assert funcargs.getfuncargnames(A().f) == ('arg1',) + + assert fixtures.getfuncargnames(A().f) == ('arg1',) if sys.version_info < (3,0): - assert funcargs.getfuncargnames(A.f) == ('arg1',) + assert fixtures.getfuncargnames(A.f) == ('arg1',) class TestFillFixtures: def test_fillfuncargs_exposed(self): # used by oejskit, kept for compatibility - assert pytest._fillfuncargs == funcargs.fillfixtures + assert pytest._fillfuncargs == fixtures.fillfixtures def test_funcarg_lookupfails(self, testdir): testdir.makepyfile(""" - def pytest_funcarg__xyzsomething(request): + import pytest + + @pytest.fixture + def xyzsomething(request): return 42 def test_func(some): @@ -47,14 +54,18 @@ def test_funcarg_basic(self, testdir): item = testdir.getitem(""" - def pytest_funcarg__some(request): + import pytest + + @pytest.fixture + def some(request): return request.function.__name__ - def pytest_funcarg__other(request): + @pytest.fixture + def other(request): return 42 def test_func(some, other): pass """) - funcargs.fillfixtures(item) + fixtures.fillfixtures(item) del item.funcargs["request"] assert len(get_public_names(item.funcargs)) == 2 assert item.funcargs['some'] == "test_func" @@ -62,7 +73,10 @@ def test_funcarg_lookup_modulelevel(self, testdir): testdir.makepyfile(""" - def pytest_funcarg__something(request): + import pytest + + @pytest.fixture + def something(request): return request.function.__name__ class TestClass: @@ -76,9 +90,13 @@ def test_funcarg_lookup_classlevel(self, testdir): p = testdir.makepyfile(""" + import pytest class TestClass: - def pytest_funcarg__something(self, request): + + @pytest.fixture + def something(self, request): return request.instance + def test_method(self, something): assert something is self """) @@ -92,13 +110,15 @@ sub2 = testdir.mkpydir("sub2") sub1.join("conftest.py").write(_pytest._code.Source(""" import pytest - def pytest_funcarg__arg1(request): - pytest.raises(Exception, "request.getfuncargvalue('arg2')") + @pytest.fixture + def arg1(request): + pytest.raises(Exception, "request.getfixturevalue('arg2')") """)) sub2.join("conftest.py").write(_pytest._code.Source(""" import pytest - def pytest_funcarg__arg2(request): - pytest.raises(Exception, "request.getfuncargvalue('arg1')") + @pytest.fixture + def arg2(request): + pytest.raises(Exception, "request.getfixturevalue('arg1')") """)) sub1.join("test_in_sub1.py").write("def test_1(arg1): pass") @@ -336,6 +356,38 @@ result = testdir.runpytest(testfile) result.stdout.fnmatch_lines(["*3 passed*"]) + def test_override_autouse_fixture_with_parametrized_fixture_conftest_conftest(self, testdir): + """Test override of the autouse fixture with parametrized one on the conftest level. + This test covers the issue explained in issue 1601 + """ + testdir.makeconftest(""" + import pytest + + @pytest.fixture(autouse=True) + def spam(): + return 'spam' + """) + subdir = testdir.mkpydir('subdir') + subdir.join("conftest.py").write(_pytest._code.Source(""" + import pytest + + @pytest.fixture(params=[1, 2, 3]) + def spam(request): + return request.param + """)) + testfile = subdir.join("test_spam.py") + testfile.write(_pytest._code.Source(""" + params = {'spam': 1} + + def test_spam(spam): + assert spam == params['spam'] + params['spam'] += 1 + """)) + result = testdir.runpytest() + result.stdout.fnmatch_lines(["*3 passed*"]) + result = testdir.runpytest(testfile) + result.stdout.fnmatch_lines(["*3 passed*"]) + def test_autouse_fixture_plugin(self, testdir): # A fixture from a plugin has no baseid set, which screwed up # the autouse fixture handling. @@ -357,20 +409,37 @@ assert result.ret == 0 def test_funcarg_lookup_error(self, testdir): + testdir.makeconftest(""" + import pytest + + @pytest.fixture + def a_fixture(): pass + + @pytest.fixture + def b_fixture(): pass + + @pytest.fixture + def c_fixture(): pass + + @pytest.fixture + def d_fixture(): pass + """) testdir.makepyfile(""" def test_lookup_error(unknown): pass """) result = testdir.runpytest() result.stdout.fnmatch_lines([ - "*ERROR*test_lookup_error*", - "*def test_lookup_error(unknown):*", - "*fixture*unknown*not found*", - "*available fixtures*", + "*ERROR at setup of test_lookup_error*", + " def test_lookup_error(unknown):*", + "E fixture 'unknown' not found", + "> available fixtures:*a_fixture,*b_fixture,*c_fixture,*d_fixture*monkeypatch,*", # sorted + "> use 'py*test --fixtures *' for help on them.", "*1 error*", ]) assert "INTERNAL" not in result.stdout.str() + def test_fixture_excinfo_leak(self, testdir): # on python2 sys.excinfo would leak into fixture executions testdir.makepyfile(""" @@ -397,10 +466,13 @@ class TestRequestBasic: def test_request_attributes(self, testdir): item = testdir.getitem(""" - def pytest_funcarg__something(request): pass + import pytest + + @pytest.fixture + def something(request): pass def test_func(something): pass """) - req = funcargs.FixtureRequest(item) + req = fixtures.FixtureRequest(item) assert req.function == item.obj assert req.keywords == item.keywords assert hasattr(req.module, 'test_func') @@ -411,8 +483,11 @@ def test_request_attributes_method(self, testdir): item, = testdir.getitems(""" + import pytest class TestB: - def pytest_funcarg__something(self, request): + + @pytest.fixture + def something(self, request): return 1 def test_func(self, something): pass @@ -421,9 +496,11 @@ assert req.cls.__name__ == "TestB" assert req.instance.__class__ == req.cls - def XXXtest_request_contains_funcarg_arg2fixturedefs(self, testdir): + def test_request_contains_funcarg_arg2fixturedefs(self, testdir): modcol = testdir.getmodulecol(""" - def pytest_funcarg__something(request): + import pytest + @pytest.fixture + def something(request): pass class TestClass: def test_method(self, something): @@ -431,41 +508,53 @@ """) item1, = testdir.genitems([modcol]) assert item1.name == "test_method" - arg2fixturedefs = funcargs.FixtureRequest(item1)._arg2fixturedefs + arg2fixturedefs = fixtures.FixtureRequest(item1)._arg2fixturedefs assert len(arg2fixturedefs) == 1 - assert arg2fixturedefs[0].__name__ == "pytest_funcarg__something" + assert arg2fixturedefs['something'][0].argname == "something" - def test_getfuncargvalue_recursive(self, testdir): + def test_getfixturevalue_recursive(self, testdir): testdir.makeconftest(""" - def pytest_funcarg__something(request): + import pytest + + @pytest.fixture + def something(request): return 1 """) testdir.makepyfile(""" - def pytest_funcarg__something(request): - return request.getfuncargvalue("something") + 1 + import pytest + + @pytest.fixture + def something(request): + return request.getfixturevalue("something") + 1 def test_func(something): assert something == 2 """) reprec = testdir.inline_run() reprec.assertoutcome(passed=1) - def test_getfuncargvalue(self, testdir): + @pytest.mark.parametrize( + 'getfixmethod', ('getfixturevalue', 'getfuncargvalue')) + def test_getfixturevalue(self, testdir, getfixmethod): item = testdir.getitem(""" + import pytest l = [2] - def pytest_funcarg__something(request): return 1 - def pytest_funcarg__other(request): + @pytest.fixture + def something(request): return 1 + @pytest.fixture + def other(request): return l.pop() def test_func(something): pass """) req = item._request - pytest.raises(FixtureLookupError, req.getfuncargvalue, "notexists") - val = req.getfuncargvalue("something") + fixture_fetcher = getattr(req, getfixmethod) + pytest.raises(FixtureLookupError, fixture_fetcher, "notexists") + val = fixture_fetcher("something") assert val == 1 - val = req.getfuncargvalue("something") + val = fixture_fetcher("something") assert val == 1 - val2 = req.getfuncargvalue("other") + val2 = fixture_fetcher("other") assert val2 == 2 - val2 = req.getfuncargvalue("other") # see about caching + val2 = fixture_fetcher("other") # see about caching assert val2 == 2 pytest._fillfuncargs(item) assert item.funcargs["something"] == 1 @@ -475,8 +564,10 @@ def test_request_addfinalizer(self, testdir): item = testdir.getitem(""" + import pytest teardownlist = [] - def pytest_funcarg__something(request): + @pytest.fixture + def something(request): request.addfinalizer(lambda: teardownlist.append(1)) def test_func(something): pass """) @@ -490,6 +581,21 @@ print(ss.stack) assert teardownlist == [1] + def test_mark_as_fixture_with_prefix_and_decorator_fails(self, testdir): + testdir.makeconftest(""" + import pytest + + @pytest.fixture + def pytest_funcarg__marked_with_prefix_and_decorator(): + pass + """) + result = testdir.runpytest_subprocess() + assert result.ret != 0 + result.stdout.fnmatch_lines([ + "*AssertionError: fixtures cannot have*@pytest.fixture*", + "*pytest_funcarg__marked_with_prefix_and_decorator*" + ]) + def test_request_addfinalizer_failing_setup(self, testdir): testdir.makepyfile(""" import pytest @@ -525,8 +631,10 @@ def test_request_addfinalizer_partial_setup_failure(self, testdir): p = testdir.makepyfile(""" + import pytest l = [] - def pytest_funcarg__something(request): + @pytest.fixture + def something(request): request.addfinalizer(lambda: l.append(None)) def test_func(something, missingarg): pass @@ -541,7 +649,7 @@ def test_request_getmodulepath(self, testdir): modcol = testdir.getmodulecol("def test_somefunc(): pass") item, = testdir.genitems([modcol]) - req = funcargs.FixtureRequest(item) + req = fixtures.FixtureRequest(item) assert req.fspath == modcol.fspath def test_request_fixturenames(self, testdir): @@ -567,9 +675,11 @@ def test_funcargnames_compatattr(self, testdir): testdir.makepyfile(""" + import pytest def pytest_generate_tests(metafunc): assert metafunc.funcargnames == metafunc.fixturenames - def pytest_funcarg__fn(request): + @pytest.fixture + def fn(request): assert request._pyfuncitem.funcargnames == \ request._pyfuncitem.fixturenames return request.funcargnames, request.fixturenames @@ -614,7 +724,9 @@ # this tests that normalization of nodeids takes place b = testdir.mkdir("tests").mkdir("unit") b.join("conftest.py").write(_pytest._code.Source(""" - def pytest_funcarg__arg1(): + import pytest + @pytest.fixture + def arg1(): pass """)) p = b.join("test_module.py") @@ -662,7 +774,10 @@ class TestRequestMarking: def test_applymarker(self, testdir): item1,item2 = testdir.getitems(""" - def pytest_funcarg__something(request): + import pytest + + @pytest.fixture + def something(request): pass class TestClass: def test_func1(self, something): @@ -670,7 +785,7 @@ def test_func2(self, something): pass """) - req1 = funcargs.FixtureRequest(item1) + req1 = fixtures.FixtureRequest(item1) assert 'xfail' not in item1.keywords req1.applymarker(pytest.mark.xfail) assert 'xfail' in item1.keywords @@ -721,7 +836,10 @@ reprec = testdir.inline_runsource(""" mysetup = ["hello",].pop - def pytest_funcarg__something(request): + import pytest + + @pytest.fixture + def something(request): return request.cached_setup(mysetup, scope="module") def test_func1(something): @@ -736,7 +854,9 @@ reprec = testdir.inline_runsource(""" mysetup = ["hello", "hello2", "hello3"].pop - def pytest_funcarg__something(request): + import pytest + @pytest.fixture + def something(request): return request.cached_setup(mysetup, scope="class") def test_func1(something): assert something == "hello3" @@ -752,10 +872,12 @@ def test_request_cachedsetup_extrakey(self, testdir): item1 = testdir.getitem("def test_func(): pass") - req1 = funcargs.FixtureRequest(item1) + 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" @@ -767,12 +889,15 @@ def test_request_cachedsetup_cache_deletion(self, testdir): item1 = testdir.getitem("def test_func(): pass") - req1 = funcargs.FixtureRequest(item1) + 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 @@ -786,9 +911,13 @@ def test_request_cached_setup_two_args(self, testdir): testdir.makepyfile(""" - def pytest_funcarg__arg1(request): + import pytest + + @pytest.fixture + def arg1(request): return request.cached_setup(lambda: 42) - def pytest_funcarg__arg2(request): + @pytest.fixture + def arg2(request): return request.cached_setup(lambda: 17) def test_two_different_setups(arg1, arg2): assert arg1 != arg2 @@ -798,12 +927,16 @@ "*1 passed*" ]) - def test_request_cached_setup_getfuncargvalue(self, testdir): + def test_request_cached_setup_getfixturevalue(self, testdir): testdir.makepyfile(""" - def pytest_funcarg__arg1(request): - arg1 = request.getfuncargvalue("arg2") + import pytest + + @pytest.fixture + def arg1(request): + arg1 = request.getfixturevalue("arg2") return request.cached_setup(lambda: arg1 + 1) - def pytest_funcarg__arg2(request): + @pytest.fixture + def arg2(request): return request.cached_setup(lambda: 10) def test_two_funcarg(arg1): assert arg1 == 11 @@ -815,8 +948,10 @@ def test_request_cached_setup_functional(self, testdir): testdir.makepyfile(test_0=""" + import pytest l = [] - def pytest_funcarg__something(request): + @pytest.fixture + def something(request): val = request.cached_setup(fsetup, fteardown) return val def fsetup(mycache=[1]): @@ -842,7 +977,10 @@ def test_issue117_sessionscopeteardown(self, testdir): testdir.makepyfile(""" - def pytest_funcarg__app(request): + import pytest + + @pytest.fixture + def app(request): app = request.cached_setup( scope='session', setup=lambda: 0, @@ -935,6 +1073,22 @@ "*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 @@ -1103,16 +1257,23 @@ class TestFixtureManagerParseFactories: - def pytest_funcarg__testdir(self, request): - testdir = request.getfuncargvalue("testdir") + + @pytest.fixture + def testdir(self, request): + testdir = request.getfixturevalue("testdir") testdir.makeconftest(""" - def pytest_funcarg__hello(request): + import pytest + + @pytest.fixture + def hello(request): return "conftest" - def pytest_funcarg__fm(request): + @pytest.fixture + def fm(request): return request._fixturemanager - def pytest_funcarg__item(request): + @pytest.fixture + def item(request): return request._pyfuncitem """) return testdir @@ -1138,17 +1299,21 @@ faclist = fm.getfixturedefs(name, item.nodeid) assert len(faclist) == 1 fac = faclist[0] - assert fac.func.__name__ == "pytest_funcarg__" + name + assert fac.func.__name__ == name """) reprec = testdir.inline_run("-s") reprec.assertoutcome(passed=1) def test_parsefactories_conftest_and_module_and_class(self, testdir): testdir.makepyfile(""" - def pytest_funcarg__hello(request): + import pytest + + @pytest.fixture + def hello(request): return "module" class TestClass: - def pytest_funcarg__hello(self, request): + @pytest.fixture + def hello(self, request): return "class" def test_hello(self, item, fm): faclist = fm.getfixturedefs("hello", item.nodeid) @@ -1196,7 +1361,9 @@ class TestAutouseDiscovery: - def pytest_funcarg__testdir(self, testdir): + + @pytest.fixture + def testdir(self, testdir): testdir.makeconftest(""" import pytest @pytest.fixture(autouse=True) @@ -1210,10 +1377,12 @@ def perfunction2(arg1): pass - def pytest_funcarg__fm(request): + @pytest.fixture + def fm(request): return request._fixturemanager - def pytest_funcarg__item(request): + @pytest.fixture + def item(request): return request._pyfuncitem """) return testdir @@ -1492,7 +1661,8 @@ def test_2(self): pass """) - reprec = testdir.inline_run("-v","-s") + confcut = "--confcutdir={0}".format(testdir.tmpdir) + reprec = testdir.inline_run("-v","-s", confcut) reprec.assertoutcome(passed=8) config = reprec.getcalls("pytest_unconfigure")[0].config l = config.pluginmanager._getconftestmodules(p)[0].l @@ -1757,17 +1927,19 @@ def test_scope_module_and_finalizer(self, testdir): testdir.makeconftest(""" import pytest - finalized = [] - created = [] + finalized_list = [] + created_list = [] @pytest.fixture(scope="module") def arg(request): - created.append(1) + created_list.append(1) assert request.scope == "module" - request.addfinalizer(lambda: finalized.append(1)) - def pytest_funcarg__created(request): - return len(created) - def pytest_funcarg__finalized(request): - return len(finalized) + request.addfinalizer(lambda: finalized_list.append(1)) + @pytest.fixture + def created(request): + return len(created_list) + @pytest.fixture + def finalized(request): + return len(finalized_list) """) testdir.makepyfile( test_mod1=""" @@ -1790,9 +1962,9 @@ reprec.assertoutcome(passed=4) @pytest.mark.parametrize("method", [ - 'request.getfuncargvalue("arg")', + 'request.getfixturevalue("arg")', 'request.cached_setup(lambda: None, scope="function")', - ], ids=["getfuncargvalue", "cached_setup"]) + ], ids=["getfixturevalue", "cached_setup"]) def test_scope_mismatch_various(self, testdir, method): testdir.makeconftest(""" import pytest @@ -2083,7 +2255,7 @@ return {} """) b = testdir.mkdir("subdir") - b.join("test_overriden_fixture_finalizer.py").write(dedent(""" + b.join("test_overridden_fixture_finalizer.py").write(dedent(""" import pytest @pytest.fixture def browser(browser): @@ -2597,11 +2769,13 @@ ''') +@pytest.mark.parametrize('flavor', ['fixture', 'yield_fixture']) class TestContextManagerFixtureFuncs: - def test_simple(self, testdir): + + def test_simple(self, testdir, flavor): testdir.makepyfile(""" import pytest - @pytest.yield_fixture + @pytest.{flavor} def arg1(): print ("setup") yield 1 @@ -2611,7 +2785,7 @@ def test_2(arg1): print ("test2 %s" % arg1) assert 0 - """) + """.format(flavor=flavor)) result = testdir.runpytest("-s") result.stdout.fnmatch_lines(""" *setup* @@ -2622,10 +2796,10 @@ *teardown* """) - def test_scoped(self, testdir): + def test_scoped(self, testdir, flavor): testdir.makepyfile(""" import pytest - @pytest.yield_fixture(scope="module") + @pytest.{flavor}(scope="module") def arg1(): print ("setup") yield 1 @@ -2634,7 +2808,7 @@ print ("test1 %s" % arg1) def test_2(arg1): print ("test2 %s" % arg1) - """) + """.format(flavor=flavor)) result = testdir.runpytest("-s") result.stdout.fnmatch_lines(""" *setup* @@ -2643,83 +2817,171 @@ *teardown* """) - def test_setup_exception(self, testdir): + def test_setup_exception(self, testdir, flavor): testdir.makepyfile(""" import pytest - @pytest.yield_fixture(scope="module") + @pytest.{flavor}(scope="module") def arg1(): pytest.fail("setup") yield 1 def test_1(arg1): pass - """) + """.format(flavor=flavor)) result = testdir.runpytest("-s") result.stdout.fnmatch_lines(""" *pytest.fail*setup* *1 error* """) - def test_teardown_exception(self, testdir): + def test_teardown_exception(self, testdir, flavor): testdir.makepyfile(""" import pytest - @pytest.yield_fixture(scope="module") + @pytest.{flavor}(scope="module") def arg1(): yield 1 pytest.fail("teardown") def test_1(arg1): pass - """) + """.format(flavor=flavor)) result = testdir.runpytest("-s") result.stdout.fnmatch_lines(""" *pytest.fail*teardown* *1 passed*1 error* """) - def test_yields_more_than_one(self, testdir): + def test_yields_more_than_one(self, testdir, flavor): testdir.makepyfile(""" import pytest - @pytest.yield_fixture(scope="module") + @pytest.{flavor}(scope="module") def arg1(): yield 1 yield 2 def test_1(arg1): pass - """) + """.format(flavor=flavor)) result = testdir.runpytest("-s") result.stdout.fnmatch_lines(""" *fixture function* *test_yields*:2* """) - - def test_no_yield(self, testdir): + def test_custom_name(self, testdir, flavor): testdir.makepyfile(""" import pytest - @pytest.yield_fixture(scope="module") + @pytest.{flavor}(name='meow') def arg1(): - return 1 - def test_1(arg1): - pass - """) + return 'mew' + def test_1(meow): + print(meow) + """.format(flavor=flavor)) result = testdir.runpytest("-s") - result.stdout.fnmatch_lines(""" - *yield_fixture*requires*yield* - *yield_fixture* - *def arg1* - """) + result.stdout.fnmatch_lines("*mew*") - def test_yield_not_allowed_in_non_yield(self, testdir): - testdir.makepyfile(""" +class TestParameterizedSubRequest: + def test_call_from_fixture(self, testdir): + testfile = testdir.makepyfile(""" import pytest - @pytest.fixture(scope="module") - def arg1(): - yield 1 - def test_1(arg1): + + @pytest.fixture(params=[0, 1, 2]) + def fix_with_param(request): + return request.param + + @pytest.fixture + def get_named_fixture(request): + return request.getfixturevalue('fix_with_param') + + def test_foo(request, get_named_fixture): pass - """) - result = testdir.runpytest("-s") + """) + result = testdir.runpytest() result.stdout.fnmatch_lines(""" - *fixture*cannot use*yield* - *def arg1* - """) + E*Failed: The requested fixture has no parameter defined for the current test. + E* + E*Requested fixture 'fix_with_param' defined in: + E*{0}:4 + E*Requested here: + E*{1}:9 + *1 error* + """.format(testfile.basename, testfile.basename)) + + def test_call_from_test(self, testdir): + testfile = testdir.makepyfile(""" + import pytest + + @pytest.fixture(params=[0, 1, 2]) + def fix_with_param(request): + return request.param + + def test_foo(request): + request.getfixturevalue('fix_with_param') + """) + result = testdir.runpytest() + result.stdout.fnmatch_lines(""" + E*Failed: The requested fixture has no parameter defined for the current test. + E* + E*Requested fixture 'fix_with_param' defined in: + E*{0}:4 + E*Requested here: + E*{1}:8 + *1 failed* + """.format(testfile.basename, testfile.basename)) + + def test_external_fixture(self, testdir): + conffile = testdir.makeconftest(""" + import pytest + + @pytest.fixture(params=[0, 1, 2]) + def fix_with_param(request): + return request.param + """) + + testfile = testdir.makepyfile(""" + def test_foo(request): + request.getfixturevalue('fix_with_param') + """) + result = testdir.runpytest() + result.stdout.fnmatch_lines(""" + E*Failed: The requested fixture has no parameter defined for the current test. + E* + E*Requested fixture 'fix_with_param' defined in: + E*{0}:4 + E*Requested here: + E*{1}:2 + *1 failed* + """.format(conffile.basename, testfile.basename)) + + def test_non_relative_path(self, testdir): + tests_dir = testdir.mkdir('tests') + fixdir = testdir.mkdir('fixtures') + fixfile = fixdir.join("fix.py") + fixfile.write(_pytest._code.Source(""" + import pytest + + @pytest.fixture(params=[0, 1, 2]) + def fix_with_param(request): + return request.param + """)) + + testfile = tests_dir.join("test_foos.py") + testfile.write(_pytest._code.Source(""" + from fix import fix_with_param + + def test_foo(request): + request.getfixturevalue('fix_with_param') + """)) + + tests_dir.chdir() + testdir.syspathinsert(fixdir) + result = testdir.runpytest() + result.stdout.fnmatch_lines(""" + E*Failed: The requested fixture has no parameter defined for the current test. + E* + E*Requested fixture 'fix_with_param' defined in: + E*{0}:5 + E*Requested here: + E*{1}:5 + *1 failed* + """.format(fixfile.strpath, testfile.basename)) + + diff -Nru pytest-2.9.2/testing/python/integration.py pytest-3.0.6/testing/python/integration.py --- pytest-2.9.2/testing/python/integration.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/testing/python/integration.py 2017-01-19 09:44:52.000000000 +0000 @@ -15,7 +15,9 @@ return self.fspath, 3, "xyz" """) modcol = testdir.getmodulecol(""" - def pytest_funcarg__arg1(request): + import pytest + @pytest.fixture + def arg1(request): return 42 class MyClass: pass @@ -43,7 +45,8 @@ @pytest.fixture(autouse=True) def hello(): pass - def pytest_funcarg__arg1(request): + @pytest.fixture + def arg1(request): return 42 class MyClass: pass @@ -60,10 +63,12 @@ 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 @@ -73,29 +78,37 @@ class TestMockDecoration: def test_wrapped_getfuncargnames(self): - from _pytest.python import getfuncargnames + 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.python import getfuncargnames + 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") @@ -234,7 +247,7 @@ """) def test_pytestconfig_is_session_scoped(): - from _pytest.python import pytestconfig + from _pytest.fixtures import pytestconfig assert pytestconfig._pytestfixturefunction.scope == "session" diff -Nru pytest-2.9.2/testing/python/metafunc.py pytest-3.0.6/testing/python/metafunc.py --- pytest-2.9.2/testing/python/metafunc.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/testing/python/metafunc.py 2017-01-19 13:24:11.000000000 +0000 @@ -1,10 +1,17 @@ # -*- coding: utf-8 -*- import re +import sys import _pytest._code import py import pytest -from _pytest import python as funcargs +from _pytest import python, fixtures + +import hypothesis +from hypothesis import strategies + +PY3 = sys.version_info >= (3, 0) + class TestMetafunc: def Metafunc(self, func): @@ -13,11 +20,13 @@ # initiliazation class FixtureInfo: name2fixturedefs = None + def __init__(self, names): self.names_closure = names - names = funcargs.getfuncargnames(func) + + names = fixtures.getfuncargnames(func) fixtureinfo = FixtureInfo(names) - return funcargs.Metafunc(func, fixtureinfo, None) + return python.Metafunc(func, fixtureinfo, None) def test_no_funcargs(self, testdir): def function(): pass @@ -58,7 +67,9 @@ 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) @@ -69,8 +80,11 @@ 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})") @@ -89,6 +103,14 @@ 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) @@ -98,6 +120,14 @@ ids = [x.id for x in metafunc._calls] assert ids == ["basic-abc", "basic-def", "advanced-abc", "advanced-def"] + def test_parametrize_and_id_unicode(self): + """Allow unicode strings for "ids" parameter in Python 2 (##1905)""" + def func(x): pass + metafunc = self.Metafunc(func) + metafunc.parametrize("x", [1, 2], ids=[u'basic', u'advanced']) + ids = [x.id for x in metafunc._calls] + assert ids == [u"basic", u"advanced"] + def test_parametrize_with_wrong_number_of_ids(self, testdir): def func(x, y): pass metafunc = self.Metafunc(func) @@ -119,8 +149,10 @@ 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" @@ -128,20 +160,29 @@ assert metafunc._calls[2].id == "x1-a" assert metafunc._calls[3].id == "x1-b" - @pytest.mark.skipif('sys.version_info[0] >= 3') - def test_unicode_idval_python2(self): - """unittest for the expected behavior to obtain ids for parametrized - unicode values in Python 2: if convertible to ascii, they should appear - as ascii values, otherwise fallback to hide the value behind the name - of the parametrized variable name. #1086 + @hypothesis.given(strategies.text() | strategies.binary()) + def test_idval_hypothesis(self, value): + from _pytest.python import _idval + escaped = _idval(value, 'a', 6, None) + assert isinstance(escaped, str) + if PY3: + escaped.encode('ascii') + else: + escaped.decode('ascii') + + def test_unicode_idval(self): + """This tests that Unicode strings outside the ASCII character set get + escaped, using byte escapes if they're in that range or unicode + escapes if they're not. + """ from _pytest.python import _idval values = [ (u'', ''), (u'ascii', 'ascii'), - (u'ação', 'a6'), - (u'josé@blah.com', 'a6'), - (u'δοκ.ιμή@παράδειγμα.δοκιμή', 'a6'), + (u'ação', 'a\\xe7\\xe3o'), + (u'josé@blah.com', 'jos\\xe9@blah.com'), + (u'δοκ.ιμή@παράδειγμα.δοκιμή', '\\u03b4\\u03bf\\u03ba.\\u03b9\\u03bc\\u03ae@\\u03c0\\u03b1\\u03c1\\u03ac\\u03b4\\u03b5\\u03b9\\u03b3\\u03bc\\u03b1.\\u03b4\\u03bf\\u03ba\\u03b9\\u03bc\\u03ae'), ] for val, expected in values: assert _idval(val, 'a', 6, None) == expected @@ -222,6 +263,7 @@ @pytest.mark.issue351 def test_idmaker_idfn(self): from _pytest.python import idmaker + def ids(val): if isinstance(val, Exception): return repr(val) @@ -238,6 +280,7 @@ @pytest.mark.issue351 def test_idmaker_idfn_unique_names(self): from _pytest.python import idmaker + def ids(val): return 'a' @@ -245,14 +288,15 @@ (20, KeyError()), ("three", [1, 2, 3]), ], idfn=ids) - assert result == ["0a-a", - "1a-a", - "2a-a", + assert result == ["a-a0", + "a-a1", + "a-a2", ] @pytest.mark.issue351 def test_idmaker_idfn_exception(self): from _pytest.python import idmaker + def ids(val): raise Exception("bad code") @@ -265,6 +309,19 @@ "three-b2", ] + def test_idmaker_with_ids(self): + from _pytest.python import idmaker + result = idmaker(("a", "b"), [(1, 2), + (3, 4)], + ids=["a", None]) + assert result == ["a", "3-4"] + + def test_idmaker_with_ids_unique_names(self): + from _pytest.python import idmaker + result = idmaker(("a"), [1,2,3,4,5], + ids=["a", "a", "b", "c", "b"]) + assert result == ["a0", "a1", "b0", "c", "b1"] + def test_addcall_and_parametrize(self): def func(x, y): pass metafunc = self.Metafunc(func) @@ -365,7 +422,7 @@ """) result = testdir.runpytest("--collect-only") result.stdout.fnmatch_lines([ - "*uses no fixture 'y'*", + "*uses no argument 'y'*", ]) @pytest.mark.issue714 @@ -389,6 +446,23 @@ ]) @pytest.mark.issue714 + def test_parametrize_indirect_uses_no_fixture_error_indirect_string(self, testdir): + testdir.makepyfile(""" + import pytest + @pytest.fixture(scope='function') + def x(request): + return request.param * 3 + + @pytest.mark.parametrize('x, y', [('a', 'b')], indirect='y') + def test_simple(x): + assert len(x) == 3 + """) + result = testdir.runpytest("--collect-only") + result.stdout.fnmatch_lines([ + "*uses no fixture 'y'*", + ]) + + @pytest.mark.issue714 def test_parametrize_indirect_uses_no_fixture_error_indirect_list(self, testdir): testdir.makepyfile(""" import pytest @@ -396,7 +470,7 @@ def x(request): return request.param * 3 - @pytest.mark.parametrize('x, y', [('a', 'b')], indirect=['x']) + @pytest.mark.parametrize('x, y', [('a', 'b')], indirect=['y']) def test_simple(x): assert len(x) == 3 """) @@ -405,6 +479,23 @@ "*uses no fixture 'y'*", ]) + @pytest.mark.issue714 + def test_parametrize_argument_not_in_indirect_list(self, testdir): + testdir.makepyfile(""" + import pytest + @pytest.fixture(scope='function') + def x(request): + return request.param * 3 + + @pytest.mark.parametrize('x, y', [('a', 'b')], indirect=['x']) + def test_simple(x): + assert len(x) == 3 + """) + result = testdir.runpytest("--collect-only") + result.stdout.fnmatch_lines([ + "*uses no argument 'y'*", + ]) + def test_addcalls_and_parametrize_indirect(self): def func(x, y): pass metafunc = self.Metafunc(func) @@ -419,13 +510,13 @@ def test_parametrize_functional(self, testdir): testdir.makepyfile(""" + import pytest def pytest_generate_tests(metafunc): metafunc.parametrize('x', [1,2], indirect=True) metafunc.parametrize('y', [2]) - def pytest_funcarg__x(request): + @pytest.fixture + def x(request): return request.param * 10 - #def pytest_funcarg__y(request): - # return request.param def test_simple(x,y): assert x in (10,20) @@ -529,16 +620,16 @@ def test_format_args(self): def function1(): pass - assert funcargs._format_args(function1) == '()' + assert fixtures._format_args(function1) == '()' def function2(arg1): pass - assert funcargs._format_args(function2) == "(arg1)" + assert fixtures._format_args(function2) == "(arg1)" def function3(arg1, arg2="qwe"): pass - assert funcargs._format_args(function3) == "(arg1, arg2='qwe')" + assert fixtures._format_args(function3) == "(arg1, arg2='qwe')" def function4(arg1, *args, **kwargs): pass - assert funcargs._format_args(function4) == "(arg1, *args, **kwargs)" + assert fixtures._format_args(function4) == "(arg1, *args, **kwargs)" class TestMetafuncFunctional: @@ -549,7 +640,8 @@ def pytest_generate_tests(metafunc): metafunc.addcall(param=metafunc) - def pytest_funcarg__metafunc(request): + @pytest.fixture + def metafunc(request): assert request._pyfuncitem._genid == "0" return request.param @@ -601,7 +693,9 @@ metafunc.addcall(param=10) metafunc.addcall(param=20) - def pytest_funcarg__arg1(request): + import pytest + @pytest.fixture + def arg1(request): return request.param def test_func1(arg1): @@ -640,9 +734,12 @@ def pytest_generate_tests(metafunc): metafunc.addcall(param=(1,1), id="hello") - def pytest_funcarg__arg1(request): + import pytest + @pytest.fixture + def arg1(request): return request.param[0] - def pytest_funcarg__arg2(request): + @pytest.fixture + def arg2(request): return request.param[1] class TestClass: @@ -720,17 +817,20 @@ "*4 failed*", ]) - def test_parametrize_and_inner_getfuncargvalue(self, testdir): + def test_parametrize_and_inner_getfixturevalue(self, testdir): p = testdir.makepyfile(""" def pytest_generate_tests(metafunc): metafunc.parametrize("arg1", [1], indirect=True) metafunc.parametrize("arg2", [10], indirect=True) - def pytest_funcarg__arg1(request): - x = request.getfuncargvalue("arg2") + import pytest + @pytest.fixture + def arg1(request): + x = request.getfixturevalue("arg2") return x + request.param - def pytest_funcarg__arg2(request): + @pytest.fixture + def arg2(request): return request.param def test_func1(arg1, arg2): @@ -748,10 +848,13 @@ assert "arg1" in metafunc.fixturenames metafunc.parametrize("arg1", [1], indirect=True) - def pytest_funcarg__arg1(request): + import pytest + @pytest.fixture + def arg1(request): return request.param - def pytest_funcarg__arg2(request, arg1): + @pytest.fixture + def arg2(request, arg1): return 10 * arg1 def test_func(arg2): @@ -796,6 +899,80 @@ *test_function*1.3-b1* """) + def test_parametrize_with_None_in_ids(self, testdir): + testdir.makepyfile(""" + import pytest + def pytest_generate_tests(metafunc): + metafunc.parametrize(("a", "b"), [(1,1), (1,1), (1,2)], + ids=["basic", None, "advanced"]) + + def test_function(a, b): + assert a == b + """) + result = testdir.runpytest("-v") + assert result.ret == 1 + result.stdout.fnmatch_lines_random([ + "*test_function*basic*PASSED", + "*test_function*1-1*PASSED", + "*test_function*advanced*FAILED", + ]) + + def test_fixture_parametrized_empty_ids(self, testdir): + """Fixtures parametrized with empty ids cause an internal error (#1849).""" + testdir.makepyfile(''' + import pytest + + @pytest.fixture(scope="module", ids=[], params=[]) + def temp(request): + return request.param + + def test_temp(temp): + pass + ''') + result = testdir.runpytest() + result.stdout.fnmatch_lines(['* 1 skipped *']) + + def test_parametrized_empty_ids(self, testdir): + """Tests parametrized with empty ids cause an internal error (#1849).""" + testdir.makepyfile(''' + import pytest + + @pytest.mark.parametrize('temp', [], ids=list()) + def test_temp(temp): + pass + ''') + result = testdir.runpytest() + result.stdout.fnmatch_lines(['* 1 skipped *']) + + def test_parametrized_ids_invalid_type(self, testdir): + """Tests parametrized with ids as non-strings (#1857).""" + testdir.makepyfile(''' + import pytest + + @pytest.mark.parametrize("x, expected", [(10, 20), (40, 80)], ids=(None, 2)) + def test_ids_numbers(x,expected): + assert x * 2 == expected + ''') + result = testdir.runpytest() + result.stdout.fnmatch_lines(['*ids must be list of strings, found: 2 (type: int)*']) + + def test_parametrize_with_identical_ids_get_unique_names(self, testdir): + testdir.makepyfile(""" + import pytest + def pytest_generate_tests(metafunc): + metafunc.parametrize(("a", "b"), [(1,1), (1,2)], + ids=["a", "a"]) + + def test_function(a, b): + assert a == b + """) + result = testdir.runpytest("-v") + assert result.ret == 1 + result.stdout.fnmatch_lines_random([ + "*test_function*a0*PASSED", + "*test_function*a1*FAILED" + ]) + @pytest.mark.parametrize(("scope", "length"), [("module", 2), ("function", 4)]) def test_parametrize_scope_overrides(self, testdir, scope, length): @@ -806,7 +983,8 @@ if "arg" in metafunc.funcargnames: metafunc.parametrize("arg", [1,2], indirect=True, scope=%r) - def pytest_funcarg__arg(request): + @pytest.fixture + def arg(request): l.append(request.param) return request.param def test_hello(arg): @@ -862,7 +1040,7 @@ """)) sub1.join("test_in_sub1.py").write("def test_1(): pass") sub2.join("test_in_sub2.py").write("def test_2(): pass") - result = testdir.runpytest("-v", "-s", sub1, sub2, sub1) + result = testdir.runpytest("--keep-duplicates", "-v", "-s", sub1, sub2, sub1) result.assert_outcomes(passed=3) def test_generate_same_function_names_issue403(self, testdir): @@ -899,6 +1077,125 @@ assert expectederror in failures[0].longrepr.reprcrash.message +class TestMetafuncFunctionalAuto: + """ + Tests related to automatically find out the correct scope for parametrized tests (#1832). + """ + + def test_parametrize_auto_scope(self, testdir): + testdir.makepyfile(''' + import pytest + + @pytest.fixture(scope='session', autouse=True) + def fixture(): + return 1 + + @pytest.mark.parametrize('animal', ["dog", "cat"]) + def test_1(animal): + assert animal in ('dog', 'cat') + + @pytest.mark.parametrize('animal', ['fish']) + def test_2(animal): + assert animal == 'fish' + + ''') + result = testdir.runpytest() + result.stdout.fnmatch_lines(['* 3 passed *']) + + def test_parametrize_auto_scope_indirect(self, testdir): + testdir.makepyfile(''' + import pytest + + @pytest.fixture(scope='session') + def echo(request): + return request.param + + @pytest.mark.parametrize('animal, echo', [("dog", 1), ("cat", 2)], indirect=['echo']) + def test_1(animal, echo): + assert animal in ('dog', 'cat') + assert echo in (1, 2, 3) + + @pytest.mark.parametrize('animal, echo', [('fish', 3)], indirect=['echo']) + def test_2(animal, echo): + assert animal == 'fish' + assert echo in (1, 2, 3) + ''') + result = testdir.runpytest() + result.stdout.fnmatch_lines(['* 3 passed *']) + + def test_parametrize_auto_scope_override_fixture(self, testdir): + testdir.makepyfile(''' + import pytest + + @pytest.fixture(scope='session', autouse=True) + def animal(): + return 'fox' + + @pytest.mark.parametrize('animal', ["dog", "cat"]) + def test_1(animal): + assert animal in ('dog', 'cat') + ''') + result = testdir.runpytest() + result.stdout.fnmatch_lines(['* 2 passed *']) + + def test_parametrize_all_indirects(self, testdir): + testdir.makepyfile(''' + import pytest + + @pytest.fixture() + def animal(request): + return request.param + + @pytest.fixture(scope='session') + def echo(request): + return request.param + + @pytest.mark.parametrize('animal, echo', [("dog", 1), ("cat", 2)], indirect=True) + def test_1(animal, echo): + assert animal in ('dog', 'cat') + assert echo in (1, 2, 3) + + @pytest.mark.parametrize('animal, echo', [("fish", 3)], indirect=True) + def test_2(animal, echo): + assert animal == 'fish' + assert echo in (1, 2, 3) + ''') + result = testdir.runpytest() + result.stdout.fnmatch_lines(['* 3 passed *']) + + def test_parametrize_issue634(self, testdir): + testdir.makepyfile(''' + import pytest + + @pytest.fixture(scope='module') + def foo(request): + print('preparing foo-%d' % request.param) + return 'foo-%d' % request.param + + def test_one(foo): + pass + + def test_two(foo): + pass + + test_two.test_with = (2, 3) + + def pytest_generate_tests(metafunc): + params = (1, 2, 3, 4) + if not 'foo' in metafunc.fixturenames: + return + + test_with = getattr(metafunc.function, 'test_with', None) + if test_with: + params = test_with + metafunc.parametrize('foo', params, indirect=True) + ''') + result = testdir.runpytest("-s") + output = result.stdout.str() + assert output.count('preparing foo-2') == 1 + assert output.count('preparing foo-3') == 1 + + class TestMarkersWithParametrization: pytestmark = pytest.mark.issue308 def test_simple_mark(self, testdir): @@ -1043,22 +1340,23 @@ reprec = testdir.inline_run() reprec.assertoutcome(passed=2, skipped=1) - def test_xfail_passing_is_xpass(self, testdir): + @pytest.mark.parametrize('strict', [True, False]) + def test_xfail_passing_is_xpass(self, testdir, strict): s = """ import pytest @pytest.mark.parametrize(("n", "expected"), [ (1, 2), - pytest.mark.xfail("sys.version > 0", reason="some bug")((2, 3)), + pytest.mark.xfail("sys.version_info > (0, 0, 0)", reason="some bug", strict={strict})((2, 3)), (3, 4), ]) def test_increment(n, expected): assert n + 1 == expected - """ + """.format(strict=strict) testdir.makepyfile(s) reprec = testdir.inline_run() - # xpass is fail, obviously :) - reprec.assertoutcome(passed=2, failed=1) + passed, failed = (2, 1) if strict else (3, 0) + reprec.assertoutcome(passed=passed, failed=failed) def test_parametrize_called_in_generate_tests(self, testdir): s = """ @@ -1099,3 +1397,21 @@ """) reprec = testdir.inline_run() reprec.assertoutcome(passed=2) + + def test_pytest_make_parametrize_id(self, testdir): + testdir.makeconftest(""" + def pytest_make_parametrize_id(config, val): + return str(val * 2) + """) + testdir.makepyfile(""" + import pytest + + @pytest.mark.parametrize("x", range(2)) + def test_func(x): + pass + """) + result = testdir.runpytest("-v") + result.stdout.fnmatch_lines([ + "*test_func*0*PASS*", + "*test_func*2*PASS*", + ]) diff -Nru pytest-2.9.2/testing/python/metafunc.py.orig pytest-3.0.6/testing/python/metafunc.py.orig --- pytest-2.9.2/testing/python/metafunc.py.orig 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/testing/python/metafunc.py.orig 2017-01-19 09:24:23.000000000 +0000 @@ -0,0 +1,1494 @@ +# -*- coding: utf-8 -*- +import re +import sys + +import _pytest._code +import py +import pytest +from _pytest import python, fixtures + +import hypothesis +from hypothesis import strategies + +PY3 = sys.version_info >= (3, 0) + + +class TestMetafunc: + def Metafunc(self, func): + # the unit tests of this class check if things work correctly + # on the funcarg level, so we don't need a full blown + # 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) + + def test_no_funcargs(self, testdir): + def function(): pass + metafunc = self.Metafunc(function) + assert not metafunc.fixturenames + repr(metafunc._calls) + + def test_function_basic(self): + def func(arg1, arg2="qwe"): pass + metafunc = self.Metafunc(func) + assert len(metafunc.fixturenames) == 1 + assert 'arg1' in metafunc.fixturenames + assert metafunc.function is func + assert metafunc.cls is None + + def test_addcall_no_args(self): + def func(arg1): pass + metafunc = self.Metafunc(func) + metafunc.addcall() + assert len(metafunc._calls) == 1 + call = metafunc._calls[0] + assert call.id == "0" + assert not hasattr(call, 'param') + + def test_addcall_id(self): + def func(arg1): pass + metafunc = self.Metafunc(func) + pytest.raises(ValueError, "metafunc.addcall(id=None)") + + metafunc.addcall(id=1) + pytest.raises(ValueError, "metafunc.addcall(id=1)") + pytest.raises(ValueError, "metafunc.addcall(id='1')") + metafunc.addcall(id=2) + assert len(metafunc._calls) == 2 + assert metafunc._calls[0].id == "1" + assert metafunc._calls[1].id == "2" + + 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) + assert len(metafunc._calls) == 3 + assert metafunc._calls[0].getparam("arg1") == obj + assert metafunc._calls[1].getparam("arg1") == obj + assert metafunc._calls[2].getparam("arg1") == 1 + + 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})") + assert len(metafunc._calls) == 2 + assert metafunc._calls[0].funcargs == {'x': 2} + assert metafunc._calls[1].funcargs == {'x': 3} + assert not hasattr(metafunc._calls[1], 'param') + + def test_parametrize_error(self): + def func(x, y): pass + metafunc = self.Metafunc(func) + metafunc.parametrize("x", [1,2]) + pytest.raises(ValueError, lambda: metafunc.parametrize("x", [5,6])) + pytest.raises(ValueError, lambda: metafunc.parametrize("x", [5,6])) + metafunc.parametrize("y", [1,2]) + 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) + + metafunc.parametrize("x", [1,2], ids=['basic', 'advanced']) + metafunc.parametrize("y", ["abc", "def"]) + ids = [x.id for x in metafunc._calls] + assert ids == ["basic-abc", "basic-def", "advanced-abc", "advanced-def"] + + def test_parametrize_and_id_unicode(self): + """Allow unicode strings for "ids" parameter in Python 2 (##1905)""" + def func(x): pass + metafunc = self.Metafunc(func) + metafunc.parametrize("x", [1, 2], ids=[u'basic', u'advanced']) + ids = [x.id for x in metafunc._calls] + assert ids == [u"basic", u"advanced"] + + def test_parametrize_with_wrong_number_of_ids(self, testdir): + def func(x, y): pass + metafunc = self.Metafunc(func) + + pytest.raises(ValueError, lambda: + metafunc.parametrize("x", [1,2], ids=['basic'])) + + pytest.raises(ValueError, lambda: + metafunc.parametrize(("x","y"), [("abc", "def"), + ("ghi", "jkl")], ids=["one"])) + + @pytest.mark.issue510 + def test_parametrize_empty_list(self): + def func( y): pass + metafunc = self.Metafunc(func) + metafunc.parametrize("y", []) + assert 'skip' in metafunc._calls[0].keywords + + 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" + assert metafunc._calls[1].id == "x0-b" + assert metafunc._calls[2].id == "x1-a" + assert metafunc._calls[3].id == "x1-b" + + @hypothesis.given(strategies.text() | strategies.binary()) + def test_idval_hypothesis(self, value): + from _pytest.python import _idval + escaped = _idval(value, 'a', 6, None) + assert isinstance(escaped, str) + if PY3: + escaped.encode('ascii') + else: + escaped.decode('ascii') + + def test_unicode_idval(self): + """This tests that Unicode strings outside the ASCII character set get + escaped, using byte escapes if they're in that range or unicode + escapes if they're not. + + """ + from _pytest.python import _idval + values = [ + (u'', ''), + (u'ascii', 'ascii'), + (u'ação', 'a\\xe7\\xe3o'), + (u'josé@blah.com', 'jos\\xe9@blah.com'), + (u'δοκ.ιμή@παράδειγμα.δοκιμή', '\\u03b4\\u03bf\\u03ba.\\u03b9\\u03bc\\u03ae@\\u03c0\\u03b1\\u03c1\\u03ac\\u03b4\\u03b5\\u03b9\\u03b3\\u03bc\\u03b1.\\u03b4\\u03bf\\u03ba\\u03b9\\u03bc\\u03ae'), + ] + for val, expected in values: + assert _idval(val, 'a', 6, None) == expected + + def test_bytes_idval(self): + """unittest for the expected behavior to obtain ids for parametrized + bytes values: + - python2: non-ascii strings are considered bytes and formatted using + "binary escape", where any byte < 127 is escaped into its hex form. + - python3: bytes objects are always escaped using "binary escape". + """ + from _pytest.python import _idval + values = [ + (b'', ''), + (b'\xc3\xb4\xff\xe4', '\\xc3\\xb4\\xff\\xe4'), + (b'ascii', 'ascii'), + (u'αρά'.encode('utf-8'), '\\xce\\xb1\\xcf\\x81\\xce\\xac'), + ] + for val, expected in values: + assert _idval(val, 'a', 6, None) == expected + + @pytest.mark.issue250 + def test_idmaker_autoname(self): + from _pytest.python import idmaker + result = idmaker(("a", "b"), [pytest.param("string", 1.0), + pytest.param("st-ring", 2.0)]) + assert result == ["string-1.0", "st-ring-2.0"] + + result = idmaker(("a", "b"), [pytest.param(object(), 1.0), + pytest.param(object(), object())]) + assert result == ["a0-1.0", "a1-b1"] + # unicode mixing, issue250 + result = idmaker((py.builtin._totext("a"), "b"), [pytest.param({}, b'\xc3\xb4')]) + assert result == ['a0-\\xc3\\xb4'] + + def test_idmaker_with_bytes_regex(self): + from _pytest.python import idmaker + result = idmaker(("a"), [pytest.param(re.compile(b'foo'), 1.0)]) + assert result == ["foo"] + + def test_idmaker_native_strings(self): + from _pytest.python import idmaker + totext = py.builtin._totext + result = idmaker(("a", "b"), [ + pytest.param(1.0, -1.1), + pytest.param(2, -202), + pytest.param("three", "three hundred"), + pytest.param(True, False), + pytest.param(None, None), + pytest.param(re.compile('foo'), re.compile('bar')), + pytest.param(str, int), + pytest.param(list("six"), [66, 66]), + pytest.param(set([7]), set("seven")), + pytest.param(tuple("eight"), (8, -8, 8)), + pytest.param(b'\xc3\xb4', b"name"), + pytest.param(b'\xc3\xb4', totext("other")), + ]) + assert result == ["1.0--1.1", + "2--202", + "three-three hundred", + "True-False", + "None-None", + "foo-bar", + "str-int", + "a7-b7", + "a8-b8", + "a9-b9", + "\\xc3\\xb4-name", + "\\xc3\\xb4-other", + ] + + def test_idmaker_enum(self): + from _pytest.python import idmaker + enum = pytest.importorskip("enum") + e = enum.Enum("Foo", "one, two") + result = idmaker(("a", "b"), [pytest.param(e.one, e.two)]) + assert result == ["Foo.one-Foo.two"] + + @pytest.mark.issue351 + def test_idmaker_idfn(self): + from _pytest.python import idmaker + + def ids(val): + if isinstance(val, Exception): + return repr(val) + + result = idmaker(("a", "b"), [pytest.param(10.0, IndexError()), + pytest.param(20, KeyError()), + pytest.param("three", [1, 2, 3]), + ], idfn=ids) + assert result == ["10.0-IndexError()", + "20-KeyError()", + "three-b2", + ] + + @pytest.mark.issue351 + def test_idmaker_idfn_unique_names(self): + from _pytest.python import idmaker + + def ids(val): + return 'a' + + result = idmaker(("a", "b"), [pytest.param(10.0, IndexError()), + pytest.param(20, KeyError()), + pytest.param("three", [1, 2, 3]), + ], idfn=ids) + assert result == ["a-a0", + "a-a1", + "a-a2", + ] + + @pytest.mark.issue351 + def test_idmaker_idfn_exception(self): + from _pytest.python import idmaker + from _pytest.recwarn import WarningsRecorder + + class BadIdsException(Exception): + pass + + def ids(val): + raise BadIdsException("ids raised") + + rec = WarningsRecorder() + with rec: + idmaker(("a", "b"), [(10.0, IndexError()), + (20, KeyError()), + ("three", [1, 2, 3]), + ], idfn=ids) + + assert [str(i.message) for i in rec.list] == [ + "Raised while trying to determine id of parameter a at position 0." + "\nUpdate your code as this will raise an error in pytest-4.0.", + "Raised while trying to determine id of parameter b at position 0." + "\nUpdate your code as this will raise an error in pytest-4.0.", + "Raised while trying to determine id of parameter a at position 1." + "\nUpdate your code as this will raise an error in pytest-4.0.", + "Raised while trying to determine id of parameter b at position 1." + "\nUpdate your code as this will raise an error in pytest-4.0.", + "Raised while trying to determine id of parameter a at position 2." + "\nUpdate your code as this will raise an error in pytest-4.0.", + "Raised while trying to determine id of parameter b at position 2." + "\nUpdate your code as this will raise an error in pytest-4.0.", + ] + +<<<<<<< HEAD + + def test_parametrize_ids_exception(self, testdir): + """ + :param testdir: the instance of Testdir class, a temporary + test directory. + """ + testdir.makepyfile(""" + import pytest + + def ids(arg): + raise Exception("bad ids") + + @pytest.mark.parametrize("arg", ["a", "b"], ids=ids) + def test_foo(arg): + pass + """) + result = testdir.runpytest("--collect-only") + result.stdout.fnmatch_lines([ + "", + " ", + " ", + ]) +======= + result = idmaker(("a", "b"), [pytest.param(10.0, IndexError()), + pytest.param(20, KeyError()), + pytest.param("three", [1, 2, 3]), + ], idfn=ids) + assert result == ["10.0-b0", + "20-b1", + "three-b2", + ] +>>>>>>> correct idmaker and calls to parameterset + + def test_idmaker_with_ids(self): + from _pytest.python import idmaker + result = idmaker(("a", "b"), [pytest.param(1, 2), + pytest.param(3, 4)], + ids=["a", None]) + assert result == ["a", "3-4"] + + def test_idmaker_with_ids_unique_names(self): + from _pytest.python import idmaker + result = idmaker(("a"), map(pytest.param, [1,2,3,4,5]), + ids=["a", "a", "b", "c", "b"]) + assert result == ["a0", "a1", "b0", "c", "b1"] + + def test_addcall_and_parametrize(self): + def func(x, y): pass + metafunc = self.Metafunc(func) + metafunc.addcall({'x': 1}) + metafunc.parametrize('y', [2,3]) + assert len(metafunc._calls) == 2 + assert metafunc._calls[0].funcargs == {'x': 1, 'y': 2} + assert metafunc._calls[1].funcargs == {'x': 1, 'y': 3} + assert metafunc._calls[0].id == "0-2" + assert metafunc._calls[1].id == "0-3" + + @pytest.mark.issue714 + def test_parametrize_indirect(self): + def func(x, y): pass + metafunc = self.Metafunc(func) + metafunc.parametrize('x', [1], indirect=True) + metafunc.parametrize('y', [2,3], indirect=True) + assert len(metafunc._calls) == 2 + assert metafunc._calls[0].funcargs == {} + assert metafunc._calls[1].funcargs == {} + assert metafunc._calls[0].params == dict(x=1,y=2) + assert metafunc._calls[1].params == dict(x=1,y=3) + + @pytest.mark.issue714 + def test_parametrize_indirect_list(self): + def func(x, y): pass + metafunc = self.Metafunc(func) + metafunc.parametrize('x, y', [('a', 'b')], indirect=['x']) + assert metafunc._calls[0].funcargs == dict(y='b') + assert metafunc._calls[0].params == dict(x='a') + + @pytest.mark.issue714 + def test_parametrize_indirect_list_all(self): + def func(x, y): pass + metafunc = self.Metafunc(func) + metafunc.parametrize('x, y', [('a', 'b')], indirect=['x', 'y']) + assert metafunc._calls[0].funcargs == {} + assert metafunc._calls[0].params == dict(x='a', y='b') + + @pytest.mark.issue714 + def test_parametrize_indirect_list_empty(self): + def func(x, y): pass + metafunc = self.Metafunc(func) + metafunc.parametrize('x, y', [('a', 'b')], indirect=[]) + assert metafunc._calls[0].funcargs == dict(x='a', y='b') + assert metafunc._calls[0].params == {} + + @pytest.mark.issue714 + def test_parametrize_indirect_list_functional(self, testdir): + """ + Test parametrization with 'indirect' parameter applied on + particular arguments. As y is is direct, its value should + be used directly rather than being passed to the fixture + y. + + :param testdir: the instance of Testdir class, a temporary + test directory. + """ + testdir.makepyfile(""" + import pytest + @pytest.fixture(scope='function') + def x(request): + return request.param * 3 + @pytest.fixture(scope='function') + def y(request): + return request.param * 2 + @pytest.mark.parametrize('x, y', [('a', 'b')], indirect=['x']) + def test_simple(x,y): + assert len(x) == 3 + assert len(y) == 1 + """) + result = testdir.runpytest("-v") + result.stdout.fnmatch_lines([ + "*test_simple*a-b*", + "*1 passed*", + ]) + + @pytest.mark.issue714 + def test_parametrize_indirect_list_error(self, testdir): + def func(x, y): pass + metafunc = self.Metafunc(func) + with pytest.raises(ValueError): + metafunc.parametrize('x, y', [('a', 'b')], indirect=['x', 'z']) + + @pytest.mark.issue714 + def test_parametrize_uses_no_fixture_error_indirect_false(self, testdir): + """The 'uses no fixture' error tells the user at collection time + that the parametrize data they've set up doesn't correspond to the + fixtures in their test function, rather than silently ignoring this + and letting the test potentially pass. + """ + testdir.makepyfile(""" + import pytest + + @pytest.mark.parametrize('x, y', [('a', 'b')], indirect=False) + def test_simple(x): + assert len(x) == 3 + """) + result = testdir.runpytest("--collect-only") + result.stdout.fnmatch_lines([ + "*uses no argument 'y'*", + ]) + + @pytest.mark.issue714 + def test_parametrize_uses_no_fixture_error_indirect_true(self, testdir): + testdir.makepyfile(""" + import pytest + @pytest.fixture(scope='function') + def x(request): + return request.param * 3 + @pytest.fixture(scope='function') + def y(request): + return request.param * 2 + + @pytest.mark.parametrize('x, y', [('a', 'b')], indirect=True) + def test_simple(x): + assert len(x) == 3 + """) + result = testdir.runpytest("--collect-only") + result.stdout.fnmatch_lines([ + "*uses no fixture 'y'*", + ]) + + @pytest.mark.issue714 + def test_parametrize_indirect_uses_no_fixture_error_indirect_string(self, testdir): + testdir.makepyfile(""" + import pytest + @pytest.fixture(scope='function') + def x(request): + return request.param * 3 + + @pytest.mark.parametrize('x, y', [('a', 'b')], indirect='y') + def test_simple(x): + assert len(x) == 3 + """) + result = testdir.runpytest("--collect-only") + result.stdout.fnmatch_lines([ + "*uses no fixture 'y'*", + ]) + + @pytest.mark.issue714 + def test_parametrize_indirect_uses_no_fixture_error_indirect_list(self, testdir): + testdir.makepyfile(""" + import pytest + @pytest.fixture(scope='function') + def x(request): + return request.param * 3 + + @pytest.mark.parametrize('x, y', [('a', 'b')], indirect=['y']) + def test_simple(x): + assert len(x) == 3 + """) + result = testdir.runpytest("--collect-only") + result.stdout.fnmatch_lines([ + "*uses no fixture 'y'*", + ]) + + @pytest.mark.issue714 + def test_parametrize_argument_not_in_indirect_list(self, testdir): + testdir.makepyfile(""" + import pytest + @pytest.fixture(scope='function') + def x(request): + return request.param * 3 + + @pytest.mark.parametrize('x, y', [('a', 'b')], indirect=['x']) + def test_simple(x): + assert len(x) == 3 + """) + result = testdir.runpytest("--collect-only") + result.stdout.fnmatch_lines([ + "*uses no argument 'y'*", + ]) + + def test_addcalls_and_parametrize_indirect(self): + def func(x, y): pass + metafunc = self.Metafunc(func) + metafunc.addcall(param="123") + metafunc.parametrize('x', [1], indirect=True) + metafunc.parametrize('y', [2,3], indirect=True) + assert len(metafunc._calls) == 2 + assert metafunc._calls[0].funcargs == {} + assert metafunc._calls[1].funcargs == {} + assert metafunc._calls[0].params == dict(x=1,y=2) + assert metafunc._calls[1].params == dict(x=1,y=3) + + def test_parametrize_functional(self, testdir): + testdir.makepyfile(""" + import pytest + def pytest_generate_tests(metafunc): + metafunc.parametrize('x', [1,2], indirect=True) + metafunc.parametrize('y', [2]) + @pytest.fixture + def x(request): + return request.param * 10 + + def test_simple(x,y): + assert x in (10,20) + assert y == 2 + """) + result = testdir.runpytest("-v") + result.stdout.fnmatch_lines([ + "*test_simple*1-2*", + "*test_simple*2-2*", + "*2 passed*", + ]) + + def test_parametrize_onearg(self): + metafunc = self.Metafunc(lambda x: None) + metafunc.parametrize("x", [1,2]) + assert len(metafunc._calls) == 2 + assert metafunc._calls[0].funcargs == dict(x=1) + assert metafunc._calls[0].id == "1" + assert metafunc._calls[1].funcargs == dict(x=2) + assert metafunc._calls[1].id == "2" + + def test_parametrize_onearg_indirect(self): + metafunc = self.Metafunc(lambda x: None) + metafunc.parametrize("x", [1,2], indirect=True) + assert metafunc._calls[0].params == dict(x=1) + assert metafunc._calls[0].id == "1" + assert metafunc._calls[1].params == dict(x=2) + assert metafunc._calls[1].id == "2" + + def test_parametrize_twoargs(self): + metafunc = self.Metafunc(lambda x,y: None) + metafunc.parametrize(("x", "y"), [(1,2), (3,4)]) + assert len(metafunc._calls) == 2 + assert metafunc._calls[0].funcargs == dict(x=1, y=2) + assert metafunc._calls[0].id == "1-2" + assert metafunc._calls[1].funcargs == dict(x=3, y=4) + assert metafunc._calls[1].id == "3-4" + + def test_parametrize_multiple_times(self, testdir): + testdir.makepyfile(""" + import pytest + pytestmark = pytest.mark.parametrize("x", [1,2]) + def test_func(x): + assert 0, x + class TestClass: + pytestmark = pytest.mark.parametrize("y", [3,4]) + def test_meth(self, x, y): + assert 0, x + """) + result = testdir.runpytest() + assert result.ret == 1 + result.assert_outcomes(failed=6) + + def test_parametrize_CSV(self, testdir): + testdir.makepyfile(""" + import pytest + @pytest.mark.parametrize("x, y,", [(1,2), (2,3)]) + def test_func(x, y): + assert x+1 == y + """) + reprec = testdir.inline_run() + reprec.assertoutcome(passed=2) + + def test_parametrize_class_scenarios(self, testdir): + testdir.makepyfile(""" + # same as doc/en/example/parametrize scenario example + def pytest_generate_tests(metafunc): + idlist = [] + argvalues = [] + for scenario in metafunc.cls.scenarios: + idlist.append(scenario[0]) + items = scenario[1].items() + argnames = [x[0] for x in items] + argvalues.append(([x[1] for x in items])) + metafunc.parametrize(argnames, argvalues, ids=idlist, scope="class") + + class Test(object): + scenarios = [['1', {'arg': {1: 2}, "arg2": "value2"}], + ['2', {'arg':'value2', "arg2": "value2"}]] + + def test_1(self, arg, arg2): + pass + + def test_2(self, arg2, arg): + pass + + def test_3(self, arg, arg2): + pass + """) + result = testdir.runpytest("-v") + assert result.ret == 0 + result.stdout.fnmatch_lines(""" + *test_1*1* + *test_2*1* + *test_3*1* + *test_1*2* + *test_2*2* + *test_3*2* + *6 passed* + """) + + def test_format_args(self): + def function1(): pass + assert fixtures._format_args(function1) == '()' + + def function2(arg1): pass + assert fixtures._format_args(function2) == "(arg1)" + + def function3(arg1, arg2="qwe"): pass + assert fixtures._format_args(function3) == "(arg1, arg2='qwe')" + + def function4(arg1, *args, **kwargs): pass + assert fixtures._format_args(function4) == "(arg1, *args, **kwargs)" + + +class TestMetafuncFunctional: + def test_attributes(self, testdir): + p = testdir.makepyfile(""" + # assumes that generate/provide runs in the same process + import py, pytest + def pytest_generate_tests(metafunc): + metafunc.addcall(param=metafunc) + + @pytest.fixture + def metafunc(request): + assert request._pyfuncitem._genid == "0" + return request.param + + def test_function(metafunc, pytestconfig): + assert metafunc.config == pytestconfig + assert metafunc.module.__name__ == __name__ + assert metafunc.function == test_function + assert metafunc.cls is None + + class TestClass: + def test_method(self, metafunc, pytestconfig): + assert metafunc.config == pytestconfig + assert metafunc.module.__name__ == __name__ + if py.std.sys.version_info > (3, 0): + unbound = TestClass.test_method + else: + unbound = TestClass.test_method.im_func + # XXX actually have an unbound test function here? + assert metafunc.function == unbound + assert metafunc.cls == TestClass + """) + result = testdir.runpytest(p, "-v") + result.assert_outcomes(passed=2) + + def test_addcall_with_two_funcargs_generators(self, testdir): + testdir.makeconftest(""" + def pytest_generate_tests(metafunc): + assert "arg1" in metafunc.fixturenames + metafunc.addcall(funcargs=dict(arg1=1, arg2=2)) + """) + p = testdir.makepyfile(""" + def pytest_generate_tests(metafunc): + metafunc.addcall(funcargs=dict(arg1=1, arg2=1)) + + class TestClass: + def test_myfunc(self, arg1, arg2): + assert arg1 == arg2 + """) + result = testdir.runpytest("-v", p) + result.stdout.fnmatch_lines([ + "*test_myfunc*0*PASS*", + "*test_myfunc*1*FAIL*", + "*1 failed, 1 passed*" + ]) + + def test_two_functions(self, testdir): + p = testdir.makepyfile(""" + def pytest_generate_tests(metafunc): + metafunc.addcall(param=10) + metafunc.addcall(param=20) + + import pytest + @pytest.fixture + def arg1(request): + return request.param + + def test_func1(arg1): + assert arg1 == 10 + def test_func2(arg1): + assert arg1 in (10, 20) + """) + result = testdir.runpytest("-v", p) + result.stdout.fnmatch_lines([ + "*test_func1*0*PASS*", + "*test_func1*1*FAIL*", + "*test_func2*PASS*", + "*1 failed, 3 passed*" + ]) + + def test_noself_in_method(self, testdir): + p = testdir.makepyfile(""" + def pytest_generate_tests(metafunc): + assert 'xyz' not in metafunc.fixturenames + + class TestHello: + def test_hello(xyz): + pass + """) + result = testdir.runpytest(p) + result.assert_outcomes(passed=1) + + + def test_generate_plugin_and_module(self, testdir): + testdir.makeconftest(""" + def pytest_generate_tests(metafunc): + assert "arg1" in metafunc.fixturenames + metafunc.addcall(id="world", param=(2,100)) + """) + p = testdir.makepyfile(""" + def pytest_generate_tests(metafunc): + metafunc.addcall(param=(1,1), id="hello") + + import pytest + @pytest.fixture + def arg1(request): + return request.param[0] + @pytest.fixture + def arg2(request): + return request.param[1] + + class TestClass: + def test_myfunc(self, arg1, arg2): + assert arg1 == arg2 + """) + result = testdir.runpytest("-v", p) + result.stdout.fnmatch_lines([ + "*test_myfunc*hello*PASS*", + "*test_myfunc*world*FAIL*", + "*1 failed, 1 passed*" + ]) + + def test_generate_tests_in_class(self, testdir): + p = testdir.makepyfile(""" + class TestClass: + def pytest_generate_tests(self, metafunc): + metafunc.addcall(funcargs={'hello': 'world'}, id="hello") + + def test_myfunc(self, hello): + assert hello == "world" + """) + result = testdir.runpytest("-v", p) + result.stdout.fnmatch_lines([ + "*test_myfunc*hello*PASS*", + "*1 passed*" + ]) + + def test_two_functions_not_same_instance(self, testdir): + p = testdir.makepyfile(""" + def pytest_generate_tests(metafunc): + metafunc.addcall({'arg1': 10}) + metafunc.addcall({'arg1': 20}) + + class TestClass: + def test_func(self, arg1): + assert not hasattr(self, 'x') + self.x = 1 + """) + result = testdir.runpytest("-v", p) + result.stdout.fnmatch_lines([ + "*test_func*0*PASS*", + "*test_func*1*PASS*", + "*2 pass*", + ]) + + def test_issue28_setup_method_in_generate_tests(self, testdir): + p = testdir.makepyfile(""" + def pytest_generate_tests(metafunc): + metafunc.addcall({'arg1': 1}) + + class TestClass: + def test_method(self, arg1): + assert arg1 == self.val + def setup_method(self, func): + self.val = 1 + """) + result = testdir.runpytest(p) + result.assert_outcomes(passed=1) + + def test_parametrize_functional2(self, testdir): + testdir.makepyfile(""" + def pytest_generate_tests(metafunc): + metafunc.parametrize("arg1", [1,2]) + metafunc.parametrize("arg2", [4,5]) + def test_hello(arg1, arg2): + assert 0, (arg1, arg2) + """) + result = testdir.runpytest() + result.stdout.fnmatch_lines([ + "*(1, 4)*", + "*(1, 5)*", + "*(2, 4)*", + "*(2, 5)*", + "*4 failed*", + ]) + + def test_parametrize_and_inner_getfixturevalue(self, testdir): + p = testdir.makepyfile(""" + def pytest_generate_tests(metafunc): + metafunc.parametrize("arg1", [1], indirect=True) + metafunc.parametrize("arg2", [10], indirect=True) + + import pytest + @pytest.fixture + def arg1(request): + x = request.getfixturevalue("arg2") + return x + request.param + + @pytest.fixture + def arg2(request): + return request.param + + def test_func1(arg1, arg2): + assert arg1 == 11 + """) + result = testdir.runpytest("-v", p) + result.stdout.fnmatch_lines([ + "*test_func1*1*PASS*", + "*1 passed*" + ]) + + def test_parametrize_on_setup_arg(self, testdir): + p = testdir.makepyfile(""" + def pytest_generate_tests(metafunc): + assert "arg1" in metafunc.fixturenames + metafunc.parametrize("arg1", [1], indirect=True) + + import pytest + @pytest.fixture + def arg1(request): + return request.param + + @pytest.fixture + def arg2(request, arg1): + return 10 * arg1 + + def test_func(arg2): + assert arg2 == 10 + """) + result = testdir.runpytest("-v", p) + result.stdout.fnmatch_lines([ + "*test_func*1*PASS*", + "*1 passed*" + ]) + + def test_parametrize_with_ids(self, testdir): + testdir.makepyfile(""" + import pytest + def pytest_generate_tests(metafunc): + metafunc.parametrize(("a", "b"), [(1,1), (1,2)], + ids=["basic", "advanced"]) + + def test_function(a, b): + assert a == b + """) + result = testdir.runpytest("-v") + assert result.ret == 1 + result.stdout.fnmatch_lines_random([ + "*test_function*basic*PASSED", + "*test_function*advanced*FAILED", + ]) + + def test_parametrize_without_ids(self, testdir): + testdir.makepyfile(""" + import pytest + def pytest_generate_tests(metafunc): + metafunc.parametrize(("a", "b"), + [(1,object()), (1.3,object())]) + + def test_function(a, b): + assert 1 + """) + result = testdir.runpytest("-v") + result.stdout.fnmatch_lines(""" + *test_function*1-b0* + *test_function*1.3-b1* + """) + + def test_parametrize_with_None_in_ids(self, testdir): + testdir.makepyfile(""" + import pytest + def pytest_generate_tests(metafunc): + metafunc.parametrize(("a", "b"), [(1,1), (1,1), (1,2)], + ids=["basic", None, "advanced"]) + + def test_function(a, b): + assert a == b + """) + result = testdir.runpytest("-v") + assert result.ret == 1 + result.stdout.fnmatch_lines_random([ + "*test_function*basic*PASSED", + "*test_function*1-1*PASSED", + "*test_function*advanced*FAILED", + ]) + + def test_fixture_parametrized_empty_ids(self, testdir): + """Fixtures parametrized with empty ids cause an internal error (#1849).""" + testdir.makepyfile(''' + import pytest + + @pytest.fixture(scope="module", ids=[], params=[]) + def temp(request): + return request.param + + def test_temp(temp): + pass + ''') + result = testdir.runpytest() + result.stdout.fnmatch_lines(['* 1 skipped *']) + + def test_parametrized_empty_ids(self, testdir): + """Tests parametrized with empty ids cause an internal error (#1849).""" + testdir.makepyfile(''' + import pytest + + @pytest.mark.parametrize('temp', [], ids=list()) + def test_temp(temp): + pass + ''') + result = testdir.runpytest() + result.stdout.fnmatch_lines(['* 1 skipped *']) + + def test_parametrized_ids_invalid_type(self, testdir): + """Tests parametrized with ids as non-strings (#1857).""" + testdir.makepyfile(''' + import pytest + + @pytest.mark.parametrize("x, expected", [(10, 20), (40, 80)], ids=(None, 2)) + def test_ids_numbers(x,expected): + assert x * 2 == expected + ''') + result = testdir.runpytest() + result.stdout.fnmatch_lines(['*ids must be list of strings, found: 2 (type: int)*']) + + def test_parametrize_with_identical_ids_get_unique_names(self, testdir): + testdir.makepyfile(""" + import pytest + def pytest_generate_tests(metafunc): + metafunc.parametrize(("a", "b"), [(1,1), (1,2)], + ids=["a", "a"]) + + def test_function(a, b): + assert a == b + """) + result = testdir.runpytest("-v") + assert result.ret == 1 + result.stdout.fnmatch_lines_random([ + "*test_function*a0*PASSED", + "*test_function*a1*FAILED" + ]) + + @pytest.mark.parametrize(("scope", "length"), + [("module", 2), ("function", 4)]) + def test_parametrize_scope_overrides(self, testdir, scope, length): + testdir.makepyfile(""" + import pytest + l = [] + def pytest_generate_tests(metafunc): + if "arg" in metafunc.funcargnames: + metafunc.parametrize("arg", [1,2], indirect=True, + scope=%r) + @pytest.fixture + def arg(request): + l.append(request.param) + return request.param + def test_hello(arg): + assert arg in (1,2) + def test_world(arg): + assert arg in (1,2) + def test_checklength(): + assert len(l) == %d + """ % (scope, length)) + reprec = testdir.inline_run() + reprec.assertoutcome(passed=5) + + def test_parametrize_issue323(self, testdir): + testdir.makepyfile(""" + import pytest + + @pytest.fixture(scope='module', params=range(966)) + def foo(request): + return request.param + + def test_it(foo): + pass + def test_it2(foo): + pass + """) + reprec = testdir.inline_run("--collect-only") + assert not reprec.getcalls("pytest_internalerror") + + def test_usefixtures_seen_in_generate_tests(self, testdir): + testdir.makepyfile(""" + import pytest + def pytest_generate_tests(metafunc): + assert "abc" in metafunc.fixturenames + metafunc.parametrize("abc", [1]) + + @pytest.mark.usefixtures("abc") + def test_function(): + pass + """) + reprec = testdir.runpytest() + reprec.assert_outcomes(passed=1) + + def test_generate_tests_only_done_in_subdir(self, testdir): + sub1 = testdir.mkpydir("sub1") + sub2 = testdir.mkpydir("sub2") + sub1.join("conftest.py").write(_pytest._code.Source(""" + def pytest_generate_tests(metafunc): + assert metafunc.function.__name__ == "test_1" + """)) + sub2.join("conftest.py").write(_pytest._code.Source(""" + def pytest_generate_tests(metafunc): + assert metafunc.function.__name__ == "test_2" + """)) + sub1.join("test_in_sub1.py").write("def test_1(): pass") + sub2.join("test_in_sub2.py").write("def test_2(): pass") + result = testdir.runpytest("--keep-duplicates", "-v", "-s", sub1, sub2, sub1) + result.assert_outcomes(passed=3) + + def test_generate_same_function_names_issue403(self, testdir): + testdir.makepyfile(""" + import pytest + + def make_tests(): + @pytest.mark.parametrize("x", range(2)) + def test_foo(x): + pass + return test_foo + + test_x = make_tests() + test_y = make_tests() + """) + reprec = testdir.runpytest() + reprec.assert_outcomes(passed=4) + + @pytest.mark.issue463 + @pytest.mark.parametrize('attr', ['parametrise', 'parameterize', + 'parameterise']) + def test_parametrize_misspelling(self, testdir, attr): + testdir.makepyfile(""" + import pytest + + @pytest.mark.{0}("x", range(2)) + def test_foo(x): + pass + """.format(attr)) + reprec = testdir.inline_run('--collectonly') + failures = reprec.getfailures() + assert len(failures) == 1 + expectederror = "MarkerError: test_foo has '{0}', spelling should be 'parametrize'".format(attr) + assert expectederror in failures[0].longrepr.reprcrash.message + + +class TestMetafuncFunctionalAuto: + """ + Tests related to automatically find out the correct scope for parametrized tests (#1832). + """ + + def test_parametrize_auto_scope(self, testdir): + testdir.makepyfile(''' + import pytest + + @pytest.fixture(scope='session', autouse=True) + def fixture(): + return 1 + + @pytest.mark.parametrize('animal', ["dog", "cat"]) + def test_1(animal): + assert animal in ('dog', 'cat') + + @pytest.mark.parametrize('animal', ['fish']) + def test_2(animal): + assert animal == 'fish' + + ''') + result = testdir.runpytest() + result.stdout.fnmatch_lines(['* 3 passed *']) + + def test_parametrize_auto_scope_indirect(self, testdir): + testdir.makepyfile(''' + import pytest + + @pytest.fixture(scope='session') + def echo(request): + return request.param + + @pytest.mark.parametrize('animal, echo', [("dog", 1), ("cat", 2)], indirect=['echo']) + def test_1(animal, echo): + assert animal in ('dog', 'cat') + assert echo in (1, 2, 3) + + @pytest.mark.parametrize('animal, echo', [('fish', 3)], indirect=['echo']) + def test_2(animal, echo): + assert animal == 'fish' + assert echo in (1, 2, 3) + ''') + result = testdir.runpytest() + result.stdout.fnmatch_lines(['* 3 passed *']) + + def test_parametrize_auto_scope_override_fixture(self, testdir): + testdir.makepyfile(''' + import pytest + + @pytest.fixture(scope='session', autouse=True) + def animal(): + return 'fox' + + @pytest.mark.parametrize('animal', ["dog", "cat"]) + def test_1(animal): + assert animal in ('dog', 'cat') + ''') + result = testdir.runpytest() + result.stdout.fnmatch_lines(['* 2 passed *']) + + def test_parametrize_all_indirects(self, testdir): + testdir.makepyfile(''' + import pytest + + @pytest.fixture() + def animal(request): + return request.param + + @pytest.fixture(scope='session') + def echo(request): + return request.param + + @pytest.mark.parametrize('animal, echo', [("dog", 1), ("cat", 2)], indirect=True) + def test_1(animal, echo): + assert animal in ('dog', 'cat') + assert echo in (1, 2, 3) + + @pytest.mark.parametrize('animal, echo', [("fish", 3)], indirect=True) + def test_2(animal, echo): + assert animal == 'fish' + assert echo in (1, 2, 3) + ''') + result = testdir.runpytest() + result.stdout.fnmatch_lines(['* 3 passed *']) + + def test_parametrize_issue634(self, testdir): + testdir.makepyfile(''' + import pytest + + @pytest.fixture(scope='module') + def foo(request): + print('preparing foo-%d' % request.param) + return 'foo-%d' % request.param + + def test_one(foo): + pass + + def test_two(foo): + pass + + test_two.test_with = (2, 3) + + def pytest_generate_tests(metafunc): + params = (1, 2, 3, 4) + if not 'foo' in metafunc.fixturenames: + return + + test_with = getattr(metafunc.function, 'test_with', None) + if test_with: + params = test_with + metafunc.parametrize('foo', params, indirect=True) + ''') + result = testdir.runpytest("-s") + output = result.stdout.str() + assert output.count('preparing foo-2') == 1 + assert output.count('preparing foo-3') == 1 + + +class TestMarkersWithParametrization: + pytestmark = pytest.mark.issue308 + def test_simple_mark(self, testdir): + s = """ + import pytest + + @pytest.mark.foo + @pytest.mark.parametrize(("n", "expected"), [ + (1, 2), + pytest.mark.bar((1, 3)), + (2, 3), + ]) + def test_increment(n, expected): + assert n + 1 == expected + """ + items = testdir.getitems(s) + assert len(items) == 3 + for item in items: + assert 'foo' in item.keywords + assert 'bar' not in items[0].keywords + assert 'bar' in items[1].keywords + assert 'bar' not in items[2].keywords + + def test_select_based_on_mark(self, testdir): + s = """ + import pytest + + @pytest.mark.parametrize(("n", "expected"), [ + (1, 2), + pytest.mark.foo((2, 3)), + (3, 4), + ]) + def test_increment(n, expected): + assert n + 1 == expected + """ + testdir.makepyfile(s) + rec = testdir.inline_run("-m", 'foo') + passed, skipped, fail = rec.listoutcomes() + assert len(passed) == 1 + assert len(skipped) == 0 + assert len(fail) == 0 + + @pytest.mark.xfail(reason="is this important to support??") + def test_nested_marks(self, testdir): + s = """ + import pytest + mastermark = pytest.mark.foo(pytest.mark.bar) + + @pytest.mark.parametrize(("n", "expected"), [ + (1, 2), + mastermark((1, 3)), + (2, 3), + ]) + def test_increment(n, expected): + assert n + 1 == expected + """ + items = testdir.getitems(s) + assert len(items) == 3 + for mark in ['foo', 'bar']: + assert mark not in items[0].keywords + assert mark in items[1].keywords + assert mark not in items[2].keywords + + def test_simple_xfail(self, testdir): + s = """ + import pytest + + @pytest.mark.parametrize(("n", "expected"), [ + (1, 2), + pytest.mark.xfail((1, 3)), + (2, 3), + ]) + def test_increment(n, expected): + assert n + 1 == expected + """ + testdir.makepyfile(s) + reprec = testdir.inline_run() + # xfail is skip?? + reprec.assertoutcome(passed=2, skipped=1) + + def test_simple_xfail_single_argname(self, testdir): + s = """ + import pytest + + @pytest.mark.parametrize("n", [ + 2, + pytest.mark.xfail(3), + 4, + ]) + def test_isEven(n): + assert n % 2 == 0 + """ + testdir.makepyfile(s) + reprec = testdir.inline_run() + reprec.assertoutcome(passed=2, skipped=1) + + def test_xfail_with_arg(self, testdir): + s = """ + import pytest + + @pytest.mark.parametrize(("n", "expected"), [ + (1, 2), + pytest.mark.xfail("True")((1, 3)), + (2, 3), + ]) + def test_increment(n, expected): + assert n + 1 == expected + """ + testdir.makepyfile(s) + reprec = testdir.inline_run() + reprec.assertoutcome(passed=2, skipped=1) + + def test_xfail_with_kwarg(self, testdir): + s = """ + import pytest + + @pytest.mark.parametrize(("n", "expected"), [ + (1, 2), + pytest.mark.xfail(reason="some bug")((1, 3)), + (2, 3), + ]) + def test_increment(n, expected): + assert n + 1 == expected + """ + testdir.makepyfile(s) + reprec = testdir.inline_run() + reprec.assertoutcome(passed=2, skipped=1) + + def test_xfail_with_arg_and_kwarg(self, testdir): + s = """ + import pytest + + @pytest.mark.parametrize(("n", "expected"), [ + (1, 2), + pytest.mark.xfail("True", reason="some bug")((1, 3)), + (2, 3), + ]) + def test_increment(n, expected): + assert n + 1 == expected + """ + testdir.makepyfile(s) + reprec = testdir.inline_run() + reprec.assertoutcome(passed=2, skipped=1) + + @pytest.mark.parametrize('strict', [True, False]) + def test_xfail_passing_is_xpass(self, testdir, strict): + s = """ + import pytest + + @pytest.mark.parametrize(("n", "expected"), [ + (1, 2), + pytest.mark.xfail("sys.version_info > (0, 0, 0)", reason="some bug", strict={strict})((2, 3)), + (3, 4), + ]) + def test_increment(n, expected): + assert n + 1 == expected + """.format(strict=strict) + testdir.makepyfile(s) + reprec = testdir.inline_run() + passed, failed = (2, 1) if strict else (3, 0) + reprec.assertoutcome(passed=passed, failed=failed) + + def test_parametrize_called_in_generate_tests(self, testdir): + s = """ + import pytest + + + def pytest_generate_tests(metafunc): + passingTestData = [(1, 2), + (2, 3)] + failingTestData = [(1, 3), + (2, 2)] + + testData = passingTestData + [pytest.mark.xfail(d) + for d in failingTestData] + metafunc.parametrize(("n", "expected"), testData) + + + def test_increment(n, expected): + assert n + 1 == expected + """ + testdir.makepyfile(s) + reprec = testdir.inline_run() + reprec.assertoutcome(passed=2, skipped=2) + + + @pytest.mark.issue290 + def test_parametrize_ID_generation_string_int_works(self, testdir): + testdir.makepyfile(""" + import pytest + + @pytest.fixture + def myfixture(): + return 'example' + @pytest.mark.parametrize( + 'limit', (0, '0')) + def test_limit(limit, myfixture): + return + """) + reprec = testdir.inline_run() + reprec.assertoutcome(passed=2) + + + @pytest.mark.parametrize('strict', [True, False]) + def test_parametrize_marked_value(self, testdir, strict): + s = """ + import pytest + + @pytest.mark.parametrize(("n", "expected"), [ + pytest.param( + 2,3, + marks=pytest.mark.xfail("sys.version_info > (0, 0, 0)", reason="some bug", strict={strict}), + ), + pytest.param( + 2,3, + marks=[pytest.mark.xfail("sys.version_info > (0, 0, 0)", reason="some bug", strict={strict})], + ), + ]) + def test_increment(n, expected): + assert n + 1 == expected + """.format(strict=strict) + testdir.makepyfile(s) + reprec = testdir.inline_run() + passed, failed = (0, 2) if strict else (2, 0) + reprec.assertoutcome(passed=passed, failed=failed) + + + def test_pytest_make_parametrize_id(self, testdir): + testdir.makeconftest(""" + def pytest_make_parametrize_id(config, val): + return str(val * 2) + """) + testdir.makepyfile(""" + import pytest + + @pytest.mark.parametrize("x", range(2)) + def test_func(x): + pass + """) + result = testdir.runpytest("-v") + result.stdout.fnmatch_lines([ + "*test_func*0*PASS*", + "*test_func*2*PASS*", + ]) diff -Nru pytest-2.9.2/testing/python/raises.py pytest-3.0.6/testing/python/raises.py --- pytest-2.9.2/testing/python/raises.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/testing/python/raises.py 2017-01-19 13:24:11.000000000 +0000 @@ -1,4 +1,6 @@ import pytest +import sys + class TestRaises: def test_raises(self): @@ -76,3 +78,51 @@ pytest.raises(ValueError, int, '0') except pytest.raises.Exception as e: assert e.msg == "DID NOT RAISE {0}".format(repr(ValueError)) + else: + assert False, "Expected pytest.raises.Exception" + + try: + with pytest.raises(ValueError): + pass + except pytest.raises.Exception as e: + assert e.msg == "DID NOT RAISE {0}".format(repr(ValueError)) + else: + assert False, "Expected pytest.raises.Exception" + + def test_custom_raise_message(self): + message = "TEST_MESSAGE" + try: + with pytest.raises(ValueError, message=message): + pass + except pytest.raises.Exception as e: + 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 -Nru pytest-2.9.2/testing/python/setup_only.py pytest-3.0.6/testing/python/setup_only.py --- pytest-2.9.2/testing/python/setup_only.py 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/testing/python/setup_only.py 2016-08-20 20:00:30.000000000 +0000 @@ -0,0 +1,243 @@ +import pytest + + +@pytest.fixture(params=['--setup-only', '--setup-plan', '--setup-show'], + scope='module') +def mode(request): + return request.param + + +def test_show_only_active_fixtures(testdir, mode): + p = testdir.makepyfile(''' + import pytest + @pytest.fixture + def _arg0(): + """hidden arg0 fixture""" + @pytest.fixture + def arg1(): + """arg1 docstring""" + def test_arg1(arg1): + pass + ''') + + result = testdir.runpytest(mode, p) + assert result.ret == 0 + + result.stdout.fnmatch_lines([ + '*SETUP F arg1*', + '*test_arg1 (fixtures used: arg1)*', + '*TEARDOWN F arg1*', + ]) + assert "_arg0" not in result.stdout.str() + + +def test_show_different_scopes(testdir, mode): + p = testdir.makepyfile(''' + import pytest + @pytest.fixture + def arg_function(): + """function scoped fixture""" + @pytest.fixture(scope='session') + def arg_session(): + """session scoped fixture""" + def test_arg1(arg_session, arg_function): + pass + ''') + + result = testdir.runpytest(mode, p) + assert result.ret == 0 + + result.stdout.fnmatch_lines([ + 'SETUP S arg_session*', + '*SETUP F arg_function*', + '*test_arg1 (fixtures used: arg_function, arg_session)*', + '*TEARDOWN F arg_function*', + 'TEARDOWN S arg_session*', + ]) + + +def test_show_nested_fixtures(testdir, mode): + testdir.makeconftest(''' + import pytest + @pytest.fixture(scope='session') + def arg_same(): + """session scoped fixture""" + ''') + p = testdir.makepyfile(''' + import pytest + @pytest.fixture(scope='function') + def arg_same(arg_same): + """function scoped fixture""" + def test_arg1(arg_same): + pass + ''') + + result = testdir.runpytest(mode, p) + assert result.ret == 0 + + result.stdout.fnmatch_lines([ + 'SETUP S arg_same*', + '*SETUP F arg_same (fixtures used: arg_same)*', + '*test_arg1 (fixtures used: arg_same)*', + '*TEARDOWN F arg_same*', + 'TEARDOWN S arg_same*', + ]) + + +def test_show_fixtures_with_autouse(testdir, mode): + p = testdir.makepyfile(''' + import pytest + @pytest.fixture + def arg_function(): + """function scoped fixture""" + @pytest.fixture(scope='session', autouse=True) + def arg_session(): + """session scoped fixture""" + def test_arg1(arg_function): + pass + ''') + + result = testdir.runpytest(mode, p) + assert result.ret == 0 + + result.stdout.fnmatch_lines([ + 'SETUP S arg_session*', + '*SETUP F arg_function*', + '*test_arg1 (fixtures used: arg_function, arg_session)*', + ]) + + +def test_show_fixtures_with_parameters(testdir, mode): + testdir.makeconftest(''' + import pytest + @pytest.fixture(scope='session', params=['foo', 'bar']) + def arg_same(): + """session scoped fixture""" + ''') + p = testdir.makepyfile(''' + import pytest + @pytest.fixture(scope='function') + def arg_other(arg_same): + """function scoped fixture""" + def test_arg1(arg_other): + pass + ''') + + result = testdir.runpytest(mode, p) + assert result.ret == 0 + + result.stdout.fnmatch_lines([ + 'SETUP S arg_same?foo?', + 'TEARDOWN S arg_same?foo?', + 'SETUP S arg_same?bar?', + 'TEARDOWN S arg_same?bar?', + ]) + + +def test_show_fixtures_with_parameter_ids(testdir, mode): + testdir.makeconftest(''' + import pytest + @pytest.fixture( + scope='session', params=['foo', 'bar'], ids=['spam', 'ham']) + def arg_same(): + """session scoped fixture""" + ''') + p = testdir.makepyfile(''' + import pytest + @pytest.fixture(scope='function') + def arg_other(arg_same): + """function scoped fixture""" + def test_arg1(arg_other): + pass + ''') + + result = testdir.runpytest(mode, p) + assert result.ret == 0 + + result.stdout.fnmatch_lines([ + 'SETUP S arg_same?spam?', + 'SETUP S arg_same?ham?', + ]) + + +def test_show_fixtures_with_parameter_ids_function(testdir, mode): + p = testdir.makepyfile(''' + import pytest + @pytest.fixture(params=['foo', 'bar'], ids=lambda p: p.upper()) + def foobar(): + pass + def test_foobar(foobar): + pass + ''') + + result = testdir.runpytest(mode, p) + assert result.ret == 0 + + result.stdout.fnmatch_lines([ + '*SETUP F foobar?FOO?', + '*SETUP F foobar?BAR?', + ]) + + +def test_dynamic_fixture_request(testdir): + p = testdir.makepyfile(''' + import pytest + @pytest.fixture() + def dynamically_requested_fixture(): + pass + @pytest.fixture() + def dependent_fixture(request): + request.getfuncargvalue('dynamically_requested_fixture') + def test_dyn(dependent_fixture): + pass + ''') + + result = testdir.runpytest('--setup-only', p) + assert result.ret == 0 + + result.stdout.fnmatch_lines([ + '*SETUP F dynamically_requested_fixture', + '*TEARDOWN F dynamically_requested_fixture' + ]) + + +def test_capturing(testdir): + p = testdir.makepyfile(''' + import pytest, sys + @pytest.fixture() + def one(): + sys.stdout.write('this should be captured') + sys.stderr.write('this should also be captured') + @pytest.fixture() + def two(one): + assert 0 + def test_capturing(two): + pass + ''') + + result = testdir.runpytest('--setup-only', p) + result.stdout.fnmatch_lines([ + 'this should be captured', + 'this should also be captured' + ]) + + +def test_show_fixtures_and_execute_test(testdir): + """ Verifies that setups are shown and tests are executed. """ + p = testdir.makepyfile(''' + import pytest + @pytest.fixture + def arg(): + assert True + def test_arg(arg): + assert False + ''') + + result = testdir.runpytest("--setup-show", p) + assert result.ret == 1 + + result.stdout.fnmatch_lines([ + '*SETUP F arg*', + '*test_arg (fixtures used: arg)F', + '*TEARDOWN F arg*', + ]) diff -Nru pytest-2.9.2/testing/python/setup_plan.py pytest-3.0.6/testing/python/setup_plan.py --- pytest-2.9.2/testing/python/setup_plan.py 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/testing/python/setup_plan.py 2016-08-20 20:00:30.000000000 +0000 @@ -0,0 +1,19 @@ +def test_show_fixtures_and_test(testdir): + """ Verifies that fixtures are not executed. """ + p = testdir.makepyfile(''' + import pytest + @pytest.fixture + def arg(): + assert False + def test_arg(arg): + assert False + ''') + + result = testdir.runpytest("--setup-plan", p) + assert result.ret == 0 + + result.stdout.fnmatch_lines([ + '*SETUP F arg*', + '*test_arg (fixtures used: arg)', + '*TEARDOWN F arg*', + ]) diff -Nru pytest-2.9.2/testing/python/show_fixtures_per_test.py pytest-3.0.6/testing/python/show_fixtures_per_test.py --- pytest-2.9.2/testing/python/show_fixtures_per_test.py 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/testing/python/show_fixtures_per_test.py 2016-08-20 20:00:30.000000000 +0000 @@ -0,0 +1,137 @@ +# -*- coding: utf-8 -*- + + +def test_no_items_should_not_show_output(testdir): + result = testdir.runpytest('--fixtures-per-test') + assert 'fixtures used by' not in result.stdout.str() + assert result.ret == 0 + + +def test_fixtures_in_module(testdir): + p = testdir.makepyfile(''' + import pytest + @pytest.fixture + def _arg0(): + """hidden arg0 fixture""" + @pytest.fixture + def arg1(): + """arg1 docstring""" + def test_arg1(arg1): + pass + ''') + + result = testdir.runpytest("--fixtures-per-test", p) + assert result.ret == 0 + + result.stdout.fnmatch_lines([ + '*fixtures used by test_arg1*', + '*(test_fixtures_in_module.py:9)*', + 'arg1', + ' arg1 docstring', + ]) + assert "_arg0" not in result.stdout.str() + + +def test_fixtures_in_conftest(testdir): + testdir.makeconftest(''' + import pytest + @pytest.fixture + def arg1(): + """arg1 docstring""" + @pytest.fixture + def arg2(): + """arg2 docstring""" + @pytest.fixture + def arg3(arg1, arg2): + """arg3 + docstring + """ + ''') + p = testdir.makepyfile(''' + def test_arg2(arg2): + pass + def test_arg3(arg3): + pass + ''') + result = testdir.runpytest("--fixtures-per-test", p) + assert result.ret == 0 + + result.stdout.fnmatch_lines([ + '*fixtures used by test_arg2*', + '*(test_fixtures_in_conftest.py:2)*', + 'arg2', + ' arg2 docstring', + '*fixtures used by test_arg3*', + '*(test_fixtures_in_conftest.py:4)*', + 'arg1', + ' arg1 docstring', + 'arg2', + ' arg2 docstring', + 'arg3', + ' arg3', + ' docstring', + ]) + + +def test_should_show_fixtures_used_by_test(testdir): + testdir.makeconftest(''' + import pytest + @pytest.fixture + def arg1(): + """arg1 from conftest""" + @pytest.fixture + def arg2(): + """arg2 from conftest""" + ''') + p = testdir.makepyfile(''' + import pytest + @pytest.fixture + def arg1(): + """arg1 from testmodule""" + def test_args(arg1, arg2): + pass + ''') + result = testdir.runpytest("--fixtures-per-test", p) + assert result.ret == 0 + + result.stdout.fnmatch_lines([ + '*fixtures used by test_args*', + '*(test_should_show_fixtures_used_by_test.py:6)*', + 'arg1', + ' arg1 from testmodule', + 'arg2', + ' arg2 from conftest', + ]) + + +def test_verbose_include_private_fixtures_and_loc(testdir): + testdir.makeconftest(''' + import pytest + @pytest.fixture + def _arg1(): + """_arg1 from conftest""" + @pytest.fixture + def arg2(_arg1): + """arg2 from conftest""" + ''') + p = testdir.makepyfile(''' + import pytest + @pytest.fixture + def arg3(): + """arg3 from testmodule""" + def test_args(arg2, arg3): + pass + ''') + result = testdir.runpytest("--fixtures-per-test", "-v", p) + assert result.ret == 0 + + result.stdout.fnmatch_lines([ + '*fixtures used by test_args*', + '*(test_verbose_include_private_fixtures_and_loc.py:6)*', + '_arg1 -- conftest.py:3', + ' _arg1 from conftest', + 'arg2 -- conftest.py:6', + ' arg2 from conftest', + 'arg3 -- test_verbose_include_private_fixtures_and_loc.py:3', + ' arg3 from testmodule', + ]) diff -Nru pytest-2.9.2/testing/test_assertinterpret.py pytest-3.0.6/testing/test_assertinterpret.py --- pytest-2.9.2/testing/test_assertinterpret.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/testing/test_assertinterpret.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,274 +0,0 @@ -"PYTEST_DONT_REWRITE" -import py -import pytest -from _pytest.assertion import util - - -def exvalue(): - return py.std.sys.exc_info()[1] - -def f(): - return 2 - -def test_not_being_rewritten(): - assert "@py_builtins" not in globals() - -def test_assert(): - try: - assert f() == 3 - except AssertionError: - e = exvalue() - s = str(e) - assert s.startswith('assert 2 == 3\n') - -def test_assert_with_explicit_message(): - try: - assert f() == 3, "hello" - except AssertionError: - e = exvalue() - assert e.msg == 'hello' - -def test_assert_within_finally(): - excinfo = pytest.raises(ZeroDivisionError, """ - try: - 1/0 - finally: - i = 42 - """) - s = excinfo.exconly() - assert py.std.re.search("division.+by zero", s) is not None - - #def g(): - # A.f() - #excinfo = getexcinfo(TypeError, g) - #msg = getmsg(excinfo) - #assert msg.find("must be called with A") != -1 - - -def test_assert_multiline_1(): - try: - assert (f() == - 3) - except AssertionError: - e = exvalue() - s = str(e) - assert s.startswith('assert 2 == 3\n') - -def test_assert_multiline_2(): - try: - assert (f() == (4, - 3)[-1]) - except AssertionError: - e = exvalue() - s = str(e) - assert s.startswith('assert 2 ==') - -def test_in(): - try: - assert "hi" in [1, 2] - except AssertionError: - e = exvalue() - s = str(e) - assert s.startswith("assert 'hi' in") - -def test_is(): - try: - assert 1 is 2 - except AssertionError: - e = exvalue() - s = str(e) - assert s.startswith("assert 1 is 2") - - -def test_attrib(): - class Foo(object): - b = 1 - i = Foo() - try: - assert i.b == 2 - except AssertionError: - e = exvalue() - s = str(e) - assert s.startswith("assert 1 == 2") - -def test_attrib_inst(): - class Foo(object): - b = 1 - try: - assert Foo().b == 2 - except AssertionError: - e = exvalue() - s = str(e) - assert s.startswith("assert 1 == 2") - -def test_len(): - l = list(range(42)) - try: - assert len(l) == 100 - except AssertionError: - e = exvalue() - s = str(e) - assert s.startswith("assert 42 == 100") - assert "where 42 = len([" in s - -def test_assert_non_string_message(): - class A: - def __str__(self): - return "hello" - try: - assert 0 == 1, A() - except AssertionError: - e = exvalue() - assert e.msg == "hello" - -def test_assert_keyword_arg(): - def f(x=3): - return False - try: - assert f(x=5) - except AssertionError: - e = exvalue() - assert "x=5" in e.msg - -def test_private_class_variable(): - class X: - def __init__(self): - self.__v = 41 - def m(self): - assert self.__v == 42 - try: - X().m() - except AssertionError: - e = exvalue() - assert "== 42" in e.msg - -# These tests should both fail, but should fail nicely... -class WeirdRepr: - def __repr__(self): - return '' - -def bug_test_assert_repr(): - v = WeirdRepr() - try: - assert v == 1 - except AssertionError: - e = exvalue() - assert e.msg.find('WeirdRepr') != -1 - assert e.msg.find('second line') != -1 - assert 0 - -def test_assert_non_string(): - try: - assert 0, ['list'] - except AssertionError: - e = exvalue() - assert e.msg.find("list") != -1 - -def test_assert_implicit_multiline(): - try: - x = [1,2,3] - assert x != [1, - 2, 3] - except AssertionError: - e = exvalue() - assert e.msg.find('assert [1, 2, 3] !=') != -1 - - -def test_assert_with_brokenrepr_arg(): - class BrokenRepr: - def __repr__(self): 0 / 0 - e = AssertionError(BrokenRepr()) - if e.msg.find("broken __repr__") == -1: - pytest.fail("broken __repr__ not handle correctly") - -def test_multiple_statements_per_line(): - try: - a = 1; assert a == 2 - except AssertionError: - e = exvalue() - assert "assert 1 == 2" in e.msg - -def test_power(): - try: - assert 2**3 == 7 - except AssertionError: - e = exvalue() - assert "assert (2 ** 3) == 7" in e.msg - - -def test_assert_customizable_reprcompare(monkeypatch): - monkeypatch.setattr(util, '_reprcompare', lambda *args: 'hello') - try: - assert 3 == 4 - except AssertionError: - e = exvalue() - s = str(e) - assert "hello" in s - -def test_assert_long_source_1(): - try: - assert len == [ - (None, ['somet text', 'more text']), - ] - except AssertionError: - e = exvalue() - s = str(e) - assert 're-run' not in s - assert 'somet text' in s - -def test_assert_long_source_2(): - try: - assert(len == [ - (None, ['somet text', 'more text']), - ]) - except AssertionError: - e = exvalue() - s = str(e) - assert 're-run' not in s - assert 'somet text' in s - -def test_assert_raise_alias(testdir): - testdir.makepyfile(""" - "PYTEST_DONT_REWRITE" - import sys - EX = AssertionError - def test_hello(): - raise EX("hello" - "multi" - "line") - """) - result = testdir.runpytest() - result.stdout.fnmatch_lines([ - "*def test_hello*", - "*raise EX*", - "*1 failed*", - ]) - - -def test_assert_raise_subclass(): - class SomeEx(AssertionError): - def __init__(self, *args): - super(SomeEx, self).__init__() - try: - raise SomeEx("hello") - except AssertionError: - s = str(exvalue()) - assert 're-run' not in s - assert 'could not determine' in s - -def test_assert_raises_in_nonzero_of_object_pytest_issue10(): - class A(object): - def __nonzero__(self): - raise ValueError(42) - def __lt__(self, other): - return A() - def __repr__(self): - return "" - def myany(x): - return True - try: - assert not(myany(A() < 0)) - except AssertionError: - e = exvalue() - s = str(e) - assert " < 0" in s diff -Nru pytest-2.9.2/testing/test_assertion.py pytest-3.0.6/testing/test_assertion.py --- pytest-2.9.2/testing/test_assertion.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/testing/test_assertion.py 2017-01-20 16:40:36.000000000 +0000 @@ -3,10 +3,8 @@ import textwrap import _pytest.assertion as plugin -import _pytest._code import py import pytest -from _pytest.assertion import reinterpret from _pytest.assertion import util PY3 = sys.version_info >= (3, 0) @@ -14,33 +12,268 @@ @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() -def interpret(expr): - return reinterpret.reinterpret(expr, _pytest._code.Frame(sys._getframe(1))) +class TestImportHookInstallation: + + @pytest.mark.parametrize('initial_conftest', [True, False]) + @pytest.mark.parametrize('mode', ['plain', 'rewrite']) + def test_conftest_assertion_rewrite(self, testdir, initial_conftest, mode): + """Test that conftest files are using assertion rewrite on import. + (#1619) + """ + testdir.tmpdir.join('foo/tests').ensure(dir=1) + conftest_path = 'conftest.py' if initial_conftest else 'foo/conftest.py' + contents = { + conftest_path: """ + import pytest + @pytest.fixture + def check_first(): + def check(values, value): + assert values.pop(0) == value + return check + """, + 'foo/tests/test_foo.py': """ + def test(check_first): + check_first([10, 30], 30) + """ + } + testdir.makepyfile(**contents) + result = testdir.runpytest_subprocess('--assert=%s' % mode) + if mode == 'plain': + expected = 'E AssertionError' + elif mode == 'rewrite': + expected = '*assert 10 == 30*' + else: + 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 = { + 'conftest.py': """ + pytest_plugins = ['ham'] + """, + 'ham.py': """ + import pytest + @pytest.fixture + def check_first(): + def check(values, value): + assert values.pop(0) == value + return check + """, + 'test_foo.py': """ + def test_foo(check_first): + check_first([10, 30], 30) + """, + } + testdir.makepyfile(**contents) + result = testdir.runpytest_subprocess('--assert=%s' % mode) + if mode == 'plain': + expected = 'E AssertionError' + elif mode == 'rewrite': + expected = '*assert 10 == 30*' + else: + assert 0 + result.stdout.fnmatch_lines([expected]) + + @pytest.mark.parametrize('mode', ['str', 'list']) + def test_pytest_plugins_rewrite_module_names(self, testdir, mode): + """Test that pluginmanager correct marks pytest_plugins variables + for assertion rewriting if they are defined as plain strings or + list of strings (#1888). + """ + plugins = '"ham"' if mode == 'str' else '["ham"]' + contents = { + 'conftest.py': """ + pytest_plugins = {plugins} + """.format(plugins=plugins), + 'ham.py': """ + import pytest + """, + 'test_foo.py': """ + def test_foo(pytestconfig): + assert 'ham' in pytestconfig.pluginmanager.rewrite_hook._must_rewrite + """, + } + testdir.makepyfile(**contents) + result = testdir.runpytest_subprocess('--assert=rewrite') + assert result.ret == 0 + + @pytest.mark.parametrize('mode', ['plain', 'rewrite']) + @pytest.mark.parametrize('plugin_state', ['development', 'installed']) + def test_installed_plugin_rewrite(self, testdir, mode, plugin_state): + # Make sure the hook is installed early enough so that plugins + # installed via setuptools are re-written. + testdir.tmpdir.join('hampkg').ensure(dir=1) + contents = { + 'hampkg/__init__.py': """ + import pytest + + @pytest.fixture + def check_first2(): + def check(values, value): + assert values.pop(0) == value + return check + """, + 'spamplugin.py': """ + import pytest + from hampkg import check_first2 + + @pytest.fixture + def check_first(): + def check(values, value): + assert values.pop(0) == value + return check + """, + 'mainwrapper.py': """ + import pytest, pkg_resources + + plugin_state = "{plugin_state}" + + class DummyDistInfo: + project_name = 'spam' + version = '1.0' + + def _get_metadata(self, name): + # 'RECORD' meta-data only available in installed plugins + if name == 'RECORD' and plugin_state == "installed": + return ['spamplugin.py,sha256=abc,123', + 'hampkg/__init__.py,sha256=abc,123'] + # 'SOURCES.txt' meta-data only available for plugins in development mode + elif name == 'SOURCES.txt' and plugin_state == "development": + return ['spamplugin.py', + 'hampkg/__init__.py'] + return [] + + class DummyEntryPoint: + name = 'spam' + module_name = 'spam.py' + attrs = () + extras = None + dist = DummyDistInfo() + + def load(self, require=True, *args, **kwargs): + import spamplugin + return spamplugin + + def iter_entry_points(name): + yield DummyEntryPoint() + + pkg_resources.iter_entry_points = iter_entry_points + pytest.main() + """.format(plugin_state=plugin_state), + 'test_foo.py': """ + def test(check_first): + check_first([10, 30], 30) + + def test2(check_first2): + check_first([10, 30], 30) + """, + } + testdir.makepyfile(**contents) + result = testdir.run(sys.executable, 'mainwrapper.py', '-s', '--assert=%s' % mode) + if mode == 'plain': + expected = 'E AssertionError' + elif mode == 'rewrite': + expected = '*assert 10 == 30*' + else: + assert 0 + result.stdout.fnmatch_lines([expected]) + + def test_rewrite_ast(self, testdir): + testdir.tmpdir.join('pkg').ensure(dir=1) + contents = { + 'pkg/__init__.py': """ + import pytest + pytest.register_assert_rewrite('pkg.helper') + """, + 'pkg/helper.py': """ + def tool(): + a, b = 2, 3 + assert a == b + """, + 'pkg/plugin.py': """ + import pytest, pkg.helper + @pytest.fixture + def tool(): + return pkg.helper.tool + """, + 'pkg/other.py': """ + l = [3, 2] + def tool(): + assert l.pop() == 3 + """, + 'conftest.py': """ + pytest_plugins = ['pkg.plugin'] + """, + 'test_pkg.py': """ + import pkg.other + def test_tool(tool): + tool() + def test_other(): + pkg.other.tool() + """, + } + testdir.makepyfile(**contents) + result = testdir.runpytest_subprocess('--assert=rewrite') + result.stdout.fnmatch_lines(['>*assert a == b*', + 'E*assert 2 == 3*', + '>*assert l.pop() == 3*', + 'E*AssertionError']) + + def test_register_assert_rewrite_checks_types(self): + with pytest.raises(TypeError): + pytest.register_assert_rewrite(['pytest_tests_internal_non_existing']) + pytest.register_assert_rewrite('pytest_tests_internal_non_existing', + 'pytest_tests_internal_non_existing2') + class TestBinReprIntegration: def test_pytest_assertrepr_compare_called(self, testdir): testdir.makeconftest(""" + import pytest l = [] def pytest_assertrepr_compare(op, left, right): l.append((op, left, right)) - def pytest_funcarg__l(request): + + @pytest.fixture + def list(request): return l """) testdir.makepyfile(""" def test_hello(): assert 0 == 1 - def test_check(l): - assert l == [("==", 0, 1)] + def test_check(list): + assert list == [("==", 0, 1)] """) result = testdir.runpytest("-v") result.stdout.fnmatch_lines([ @@ -428,7 +661,7 @@ "*- 3", "*- 5", "*- 7", - "*truncated (191 more lines)*use*-vv*", + "*truncated (193 more lines)*use*-vv*", ]) @@ -474,24 +707,8 @@ """) result = testdir.runpytest() assert "3 == 4" in result.stdout.str() - off_options = (("--no-assert",), - ("--nomagic",), - ("--no-assert", "--nomagic"), - ("--assert=plain",), - ("--assert=plain", "--no-assert"), - ("--assert=plain", "--nomagic"), - ("--assert=plain", "--no-assert", "--nomagic")) - for opt in off_options: - result = testdir.runpytest_subprocess(*opt) - assert "3 == 4" not in result.stdout.str() - -def test_old_assert_mode(testdir): - testdir.makepyfile(""" - def test_in_old_mode(): - assert "@py_builtins" not in globals() - """) - result = testdir.runpytest_subprocess("--assert=reinterp") - assert result.ret == 0 + result = testdir.runpytest_subprocess("--assert=plain") + assert "3 == 4" not in result.stdout.str() def test_triple_quoted_string_issue113(testdir): testdir.makepyfile(""" @@ -552,6 +769,37 @@ "*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("") @@ -559,7 +807,7 @@ result.stderr.fnmatch_lines([ "*WARNING*assert statements are not executed*", ]) - result = testdir.run(sys.executable, "-OO", "-m", "pytest", "--no-assert") + result = testdir.run(sys.executable, "-OO", "-m", "pytest") result.stderr.fnmatch_lines([ "*WARNING*assert statements are not executed*", ]) @@ -626,3 +874,56 @@ + repr(3) """).strip() assert '\n'.join(expl) == dedent + +def test_diff_newline_at_end(monkeypatch, testdir): + testdir.makepyfile(r""" + def test_diff(): + assert 'asdf' == 'asdf\n' + """) + + result = testdir.runpytest() + result.stdout.fnmatch_lines(r""" + *assert 'asdf' == 'asdf\n' + * - asdf + * + asdf + * ? + + """) + +def test_assert_tuple_warning(testdir): + testdir.makepyfile(""" + def test_tuple(): + assert(False, 'you shall not pass') + """) + result = testdir.runpytest('-rw') + result.stdout.fnmatch_lines('WR1*:2 assertion is always true*') + +def test_assert_indirect_tuple_no_warning(testdir): + testdir.makepyfile(""" + def test_tuple(): + tpl = ('foo', 'bar') + assert tpl + """) + result = testdir.runpytest('-rw') + output = '\n'.join(result.stdout.lines) + assert 'WR1' not in output + +def test_assert_with_unicode(monkeypatch, testdir): + testdir.makepyfile(u""" + # -*- coding: utf-8 -*- + def test_unicode(): + assert u'유니코드' == u'Unicode' + """) + result = testdir.runpytest() + result.stdout.fnmatch_lines(['*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 -Nru pytest-2.9.2/testing/test_assertrewrite.py pytest-3.0.6/testing/test_assertrewrite.py --- pytest-2.9.2/testing/test_assertrewrite.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/testing/test_assertrewrite.py 2017-01-20 16:40:21.000000000 +0000 @@ -1,7 +1,10 @@ +import glob import os +import py_compile import stat import sys import zipfile + import py import pytest @@ -12,7 +15,7 @@ import _pytest._code from _pytest.assertion import util -from _pytest.assertion.rewrite import rewrite_asserts, PYTEST_TAG +from _pytest.assertion.rewrite import rewrite_asserts, PYTEST_TAG, AssertionRewritingHook from _pytest.main import EXIT_NOTESTSCOLLECTED @@ -104,20 +107,29 @@ 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,76 +202,110 @@ 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 (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 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 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): @@ -267,7 +313,9 @@ 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)" @@ -275,7 +323,9 @@ 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))" @@ -296,105 +346,159 @@ def test_call(self): def g(a=42, *args, **kwargs): return False + ns = {"g" : g} + def f(): assert g() - assert getmsg(f, ns) == """assert g()""" + + assert getmsg(f, ns) == """assert False + + where False = g()""" + def f(): assert g(1) - assert getmsg(f, ns) == """assert g(1)""" + + assert getmsg(f, ns) == """assert False + + where False = g(1)""" + def f(): assert g(1, 2) - assert getmsg(f, ns) == """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 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 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 g(*[1, 2, 3])""" + + 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 g(**{'a': 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 x.a""" + + 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): @@ -404,8 +508,10 @@ 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] @@ -470,6 +576,31 @@ 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(""" @@ -514,6 +645,73 @@ testdir.makepyfile("import a_package_without_init_py.module") assert testdir.runpytest().ret == EXIT_NOTESTSCOLLECTED + 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] + + def test_rewrite_module_imported_from_conftest(self, testdir): + testdir.makeconftest(''' + import test_rewrite_module_imported + ''') + testdir.makepyfile(test_rewrite_module_imported=''' + def test_rewritten(): + assert "@py_builtins" in globals() + ''') + 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): testdir.makepyfile(test_fun=""" @@ -596,10 +794,12 @@ 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) @@ -712,5 +912,28 @@ assert 'unbalanced braces' not in result.stdout.str() -def test_collapse_false_unbalanced_braces(): - util._collapse_false('some text{ False\n{False = some more text\n}') +class TestIssue925(): + def test_simple_case(self, testdir): + testdir.makepyfile(""" + def test_ternary_display(): + assert (False == False) == False + """) + result = testdir.runpytest() + result.stdout.fnmatch_lines('*E*assert (False == False) == False') + + def test_long_case(self, testdir): + testdir.makepyfile(""" + def test_ternary_display(): + assert False == (False == True) == True + """) + result = testdir.runpytest() + result.stdout.fnmatch_lines('*E*assert (False == True) == True') + + def test_many_brackets(self, testdir): + testdir.makepyfile(""" + def test_ternary_display(): + assert True == ((False == True) == True) + """) + result = testdir.runpytest() + result.stdout.fnmatch_lines('*E*assert True == ((False == True) == True)') + diff -Nru pytest-2.9.2/testing/test_capture.py pytest-3.0.6/testing/test_capture.py --- pytest-2.9.2/testing/test_capture.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/testing/test_capture.py 2016-09-05 12:37:27.000000000 +0000 @@ -416,11 +416,30 @@ result = testdir.runpytest(p) result.stdout.fnmatch_lines([ "*ERROR*setup*test_one*", - "*capsys*capfd*same*time*", + "E*capsys*capfd*same*time*", "*ERROR*setup*test_two*", - "*capsys*capfd*same*time*", + "E*capsys*capfd*same*time*", "*2 error*"]) + def test_capturing_getfixturevalue(self, testdir): + """Test that asking for "capfd" and "capsys" using request.getfixturevalue + in the same test is an error. + """ + testdir.makepyfile(""" + def test_one(capsys, request): + request.getfixturevalue("capfd") + def test_two(capfd, request): + request.getfixturevalue("capsys") + """) + result = testdir.runpytest() + result.stdout.fnmatch_lines([ + "*test_one*", + "*capsys*capfd*same*time*", + "*test_two*", + "*capsys*capfd*same*time*", + "*2 failed in*", + ]) + @pytest.mark.parametrize("method", ["sys", "fd"]) def test_capture_is_represented_on_failure_issue128(self, testdir, method): p = testdir.makepyfile(""" @@ -480,6 +499,22 @@ result = testdir.runpytest_subprocess(p) assert 'closed' not in result.stderr.str() + @pytest.mark.parametrize('fixture', ['capsys', 'capfd']) + def test_disabled_capture_fixture(self, testdir, fixture): + testdir.makepyfile(""" + def test_disabled({fixture}): + print('captured before') + with {fixture}.disabled(): + print('while capture is disabled') + print('captured after') + """.format(fixture=fixture)) + result = testdir.runpytest_subprocess() + result.stdout.fnmatch_lines(""" + *while capture is disabled* + """) + assert 'captured before' not in result.stdout.str() + assert 'captured after' not in result.stdout.str() + def test_setup_failure_does_not_kill_capturing(testdir): sub1 = testdir.mkpydir("sub1") @@ -627,6 +662,28 @@ f.close() # just for completeness +@pytest.mark.skipif('sys.version_info < (3,)', reason='python2 has no buffer') +def test_dontreadfrominput_buffer_python3(): + from _pytest.capture import DontReadFromInput + f = DontReadFromInput() + fb = f.buffer + assert not fb.isatty() + pytest.raises(IOError, fb.read) + pytest.raises(IOError, fb.readlines) + pytest.raises(IOError, iter, fb) + pytest.raises(ValueError, fb.fileno) + f.close() # just for completeness + + +@pytest.mark.skipif('sys.version_info >= (3,)', reason='python2 has no buffer') +def test_dontreadfrominput_buffer_python2(): + from _pytest.capture import DontReadFromInput + f = DontReadFromInput() + with pytest.raises(AttributeError): + f.buffer + f.close() # just for completeness + + @pytest.yield_fixture def tmpfile(testdir): f = testdir.makepyfile("").open('wb+') diff -Nru pytest-2.9.2/testing/test_collection.py pytest-3.0.6/testing/test_collection.py --- pytest-2.9.2/testing/test_collection.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/testing/test_collection.py 2017-01-19 13:24:11.000000000 +0000 @@ -88,6 +88,8 @@ class TestCollectFS: def test_ignored_certain_directories(self, testdir): tmpdir = testdir.tmpdir + tmpdir.ensure("build", 'test_notfound.py') + tmpdir.ensure("dist", 'test_notfound.py') tmpdir.ensure("_darcs", 'test_notfound.py') tmpdir.ensure("CVS", 'test_notfound.py') tmpdir.ensure("{arch}", 'test_notfound.py') @@ -148,9 +150,13 @@ class TestCollectPluginHookRelay: def test_pytest_collect_file(self, testdir): wascalled = [] + class Plugin: def pytest_collect_file(self, path, parent): - wascalled.append(path) + 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 @@ -158,26 +164,19 @@ 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*", - "*mport*not_exists*" - ]) def test_custom_repr_failure(self, testdir): p = testdir.makepyfile(""" @@ -488,7 +487,8 @@ class Test_getinitialnodes: def test_global_file(self, testdir, tmpdir): x = tmpdir.ensure("x.py") - config = testdir.parseconfigure(x) + with tmpdir.as_cwd(): + config = testdir.parseconfigure(x) col = testdir.getnode(config, x) assert isinstance(col, pytest.Module) assert col.name == 'x.py' @@ -502,7 +502,8 @@ subdir = tmpdir.join("subdir") x = subdir.ensure("x.py") subdir.ensure("__init__.py") - config = testdir.parseconfigure(x) + with subdir.as_cwd(): + config = testdir.parseconfigure(x) col = testdir.getnode(config, x) assert isinstance(col, pytest.Module) assert col.name == 'x.py' @@ -639,3 +640,114 @@ """) reprec = testdir.inline_run("-k repr") reprec.assertoutcome(passed=1, failed=0) + + +COLLECTION_ERROR_PY_FILES = dict( + test_01_failure=""" + def test_1(): + assert False + """, + test_02_import_error=""" + import asdfasdfasdf + def test_2(): + assert True + """, + test_03_import_error=""" + import asdfasdfasdf + def test_3(): + assert True + """, + test_04_success=""" + def test_4(): + assert True + """, +) + +def test_exit_on_collection_error(testdir): + """Verify that all collection errors are collected and no tests executed""" + testdir.makepyfile(**COLLECTION_ERROR_PY_FILES) + + res = testdir.runpytest() + assert res.ret == 2 + + res.stdout.fnmatch_lines([ + "collected 2 items / 2 errors", + "*ERROR collecting test_02_import_error.py*", + "*No module named *asdfa*", + "*ERROR collecting test_03_import_error.py*", + "*No module named *asdfa*", + ]) + + +def test_exit_on_collection_with_maxfail_smaller_than_n_errors(testdir): + """ + Verify collection is aborted once maxfail errors are encountered ignoring + further modules which would cause more collection errors. + """ + testdir.makepyfile(**COLLECTION_ERROR_PY_FILES) + + res = testdir.runpytest("--maxfail=1") + assert res.ret == 2 + + res.stdout.fnmatch_lines([ + "*ERROR collecting test_02_import_error.py*", + "*No module named *asdfa*", + "*Interrupted: stopping after 1 failures*", + ]) + + assert 'test_03' not in res.stdout.str() + + +def test_exit_on_collection_with_maxfail_bigger_than_n_errors(testdir): + """ + Verify the test run aborts due to collection errors even if maxfail count of + errors was not reached. + """ + testdir.makepyfile(**COLLECTION_ERROR_PY_FILES) + + res = testdir.runpytest("--maxfail=4") + assert res.ret == 2 + + res.stdout.fnmatch_lines([ + "collected 2 items / 2 errors", + "*ERROR collecting test_02_import_error.py*", + "*No module named *asdfa*", + "*ERROR collecting test_03_import_error.py*", + "*No module named *asdfa*", + ]) + + +def test_continue_on_collection_errors(testdir): + """ + Verify tests are executed even when collection errors occur when the + --continue-on-collection-errors flag is set + """ + testdir.makepyfile(**COLLECTION_ERROR_PY_FILES) + + res = testdir.runpytest("--continue-on-collection-errors") + assert res.ret == 1 + + res.stdout.fnmatch_lines([ + "collected 2 items / 2 errors", + "*1 failed, 1 passed, 2 error*", + ]) + + +def test_continue_on_collection_errors_maxfail(testdir): + """ + Verify tests are executed even when collection errors occur and that maxfail + is honoured (including the collection error count). + 4 tests: 2 collection errors + 1 failure + 1 success + test_4 is never executed because the test run is with --maxfail=3 which + means it is interrupted after the 2 collection errors + 1 failure. + """ + testdir.makepyfile(**COLLECTION_ERROR_PY_FILES) + + res = testdir.runpytest("--continue-on-collection-errors", "--maxfail=3") + assert res.ret == 2 + + res.stdout.fnmatch_lines([ + "collected 2 items / 2 errors", + "*Interrupted: stopping after 3 failures*", + "*1 failed, 2 error*", + ]) diff -Nru pytest-2.9.2/testing/test_compat.py pytest-3.0.6/testing/test_compat.py --- pytest-2.9.2/testing/test_compat.py 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/testing/test_compat.py 2017-01-20 16:40:21.000000000 +0000 @@ -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 -Nru pytest-2.9.2/testing/test_config.py pytest-3.0.6/testing/test_config.py --- pytest-2.9.2/testing/test_config.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/testing/test_config.py 2017-01-20 16:40:21.000000000 +0000 @@ -5,24 +5,28 @@ from _pytest.main import EXIT_NOTESTSCOLLECTED class TestParseIni: - def test_getcfg_and_config(self, testdir, tmpdir): + + @pytest.mark.parametrize('section, filename', + [('pytest', 'pytest.ini'), ('tool:pytest', 'setup.cfg')]) + def test_getcfg_and_config(self, testdir, tmpdir, section, filename): sub = tmpdir.mkdir("sub") sub.chdir() - tmpdir.join("setup.cfg").write(_pytest._code.Source(""" - [pytest] + tmpdir.join(filename).write(_pytest._code.Source(""" + [{section}] name = value - """)) - rootdir, inifile, cfg = getcfg([sub], ["setup.cfg"]) + """.format(section=section))) + rootdir, inifile, cfg = getcfg([sub]) assert cfg['name'] == "value" config = testdir.parseconfigure(sub) assert config.inicfg['name'] == 'value' - def test_getcfg_empty_path(self, tmpdir): - getcfg([''], ['setup.cfg']) #happens on py.test "" + def test_getcfg_empty_path(self): + """correctly handle zero length arguments (a la pytest '')""" + getcfg(['']) def test_append_parse_args(self, testdir, tmpdir, monkeypatch): monkeypatch.setenv('PYTEST_ADDOPTS', '--color no -rs --tb="short"') - tmpdir.join("setup.cfg").write(_pytest._code.Source(""" + tmpdir.join("pytest.ini").write(_pytest._code.Source(""" [pytest] addopts = --verbose """)) @@ -31,10 +35,6 @@ assert config.option.reportchars == 's' assert config.option.tbstyle == 'short' assert config.option.verbose - #config = testdir.Config() - #args = [tmpdir,] - #config._preparse(args, addopts=False) - #assert len(args) == 1 def test_tox_ini_wrong_version(self, testdir): testdir.makefile('.ini', tox=""" @@ -47,12 +47,16 @@ "*tox.ini:2*requires*9.0*actual*" ]) - @pytest.mark.parametrize("name", "setup.cfg tox.ini pytest.ini".split()) - def test_ini_names(self, testdir, name): + @pytest.mark.parametrize("section, name", [ + ('tool:pytest', 'setup.cfg'), + ('pytest', 'tox.ini'), + ('pytest', 'pytest.ini')], + ) + def test_ini_names(self, testdir, name, section): testdir.tmpdir.join(name).write(py.std.textwrap.dedent(""" - [pytest] + [{section}] minversion = 1.0 - """)) + """.format(section=section))) config = testdir.parseconfig() assert config.getini("minversion") == "1.0" @@ -79,7 +83,7 @@ """) result = testdir.inline_run("--confcutdir=.") assert result.ret == 0 - + class TestConfigCmdlineParsing: def test_parsing_again_fails(self, testdir): config = testdir.parseconfig() @@ -290,6 +294,15 @@ 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): @@ -365,23 +378,35 @@ """) for opts in ([], ['-l'], ['-s'], ['--tb=no'], ['--tb=short'], - ['--tb=long'], ['--fulltrace'], ['--nomagic'], + ['--tb=long'], ['--fulltrace'], ['--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" - class dist: - pass + 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", @@ -391,15 +416,56 @@ plugin = config.pluginmanager.getplugin("mytestplugin") assert plugin.x == 42 + +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) + with pytest.raises(ImportError): + testdir.parseconfig() + + 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") @@ -451,7 +517,8 @@ args[i] = d1 elif arg == 'dir2': args[i] = d2 - result = testdir.runpytest(*args) + with root.as_cwd(): + result = testdir.runpytest(*args) result.stdout.fnmatch_lines(['*rootdir: *myroot, inifile: ']) @@ -460,15 +527,40 @@ 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() @@ -478,16 +570,37 @@ 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 l = hc._nonwrappers + hc._wrappers - assert l[-1].function.__module__ == "_pytest.capture" - assert l[-2].function == m.pytest_load_initial_conftests - assert l[-3].function.__module__ == "_pytest.config" + expected = [ + "_pytest.config", + 'test_config', + '_pytest.capture', + ] + 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): @@ -507,33 +620,38 @@ reprec = testdir.inline_run() reprec.assertoutcome(passed=1) - def test_warn_on_test_item_from_request(self, testdir): + def test_warn_on_test_item_from_request(self, testdir, request): testdir.makepyfile(""" import pytest @pytest.fixture def fix(request): request.node.warn("T1", "hello") + def test_hello(fix): pass """) - result = testdir.runpytest() + result = testdir.runpytest("--disable-pytest-warnings") assert result.parseoutcomes()["pytest-warnings"] > 0 assert "hello" not in result.stdout.str() - result = testdir.runpytest("-rw") + result = testdir.runpytest() result.stdout.fnmatch_lines(""" ===*pytest-warning summary*=== - *WT1*test_warn_on_test_item*:5*hello* + *WT1*test_warn_on_test_item*:7 hello* """) class TestRootdir: def test_simple_noini(self, tmpdir): assert get_common_ancestor([tmpdir]) == tmpdir - assert get_common_ancestor([tmpdir.mkdir("a"), tmpdir]) == tmpdir - assert get_common_ancestor([tmpdir, tmpdir.join("a")]) == tmpdir + a = tmpdir.mkdir("a") + assert get_common_ancestor([a, tmpdir]) == tmpdir + assert get_common_ancestor([tmpdir, a]) == tmpdir with tmpdir.as_cwd(): assert get_common_ancestor([]) == tmpdir + no_path = tmpdir.join('does-not-exist') + assert get_common_ancestor([no_path]) == tmpdir + assert get_common_ancestor([no_path.join('a')]) == tmpdir @pytest.mark.parametrize("name", "setup.cfg tox.ini pytest.ini".split()) def test_with_ini(self, tmpdir, name): @@ -568,7 +686,8 @@ assert inifile is None assert inicfg == {} - def test_nothing(self, tmpdir): + def test_nothing(self, tmpdir, monkeypatch): + monkeypatch.chdir(str(tmpdir)) rootdir, inifile, inicfg = determine_setup(None, [tmpdir]) assert rootdir == tmpdir assert inifile is None @@ -578,3 +697,116 @@ inifile = tmpdir.ensure("pytest.ini") rootdir, inifile, inicfg = determine_setup(inifile, [tmpdir]) assert rootdir == tmpdir + + +class TestOverrideIniArgs: + @pytest.mark.parametrize("name", "setup.cfg tox.ini pytest.ini".split()) + def test_override_ini_names(self, testdir, name): + testdir.tmpdir.join(name).write(py.std.textwrap.dedent(""" + [pytest] + custom = 1.0""")) + testdir.makeconftest(""" + def pytest_addoption(parser): + parser.addini("custom", "")""") + testdir.makepyfile(""" + def test_pass(pytestconfig): + ini_val = pytestconfig.getini("custom") + print('\\ncustom_option:%s\\n' % ini_val)""") + + result = testdir.runpytest("--override-ini", "custom=2.0", "-s") + assert result.ret == 0 + result.stdout.fnmatch_lines(["custom_option:2.0"]) + + result = testdir.runpytest("--override-ini", "custom=2.0", + "--override-ini=custom=3.0", "-s") + assert result.ret == 0 + result.stdout.fnmatch_lines(["custom_option:3.0"]) + + + def test_override_ini_pathlist(self, testdir): + testdir.makeconftest(""" + def pytest_addoption(parser): + parser.addini("paths", "my new ini value", type="pathlist")""") + testdir.makeini(""" + [pytest] + paths=blah.py""") + testdir.makepyfile(""" + import py.path + def test_pathlist(pytestconfig): + config_paths = pytestconfig.getini("paths") + print(config_paths) + for cpf in config_paths: + print('\\nuser_path:%s' % cpf.basename)""") + result = testdir.runpytest("--override-ini", + 'paths=foo/bar1.py foo/bar2.py', "-s") + result.stdout.fnmatch_lines(["user_path:bar1.py", + "user_path:bar2.py"]) + + def test_override_multiple_and_default(self, testdir): + testdir.makeconftest(""" + def pytest_addoption(parser): + addini = parser.addini + addini("custom_option_1", "", default="o1") + addini("custom_option_2", "", default="o2") + addini("custom_option_3", "", default=False, type="bool") + addini("custom_option_4", "", default=True, type="bool")""") + testdir.makeini(""" + [pytest] + custom_option_1=custom_option_1 + custom_option_2=custom_option_2""") + testdir.makepyfile(""" + def test_multiple_options(pytestconfig): + prefix = "custom_option" + for x in range(1, 5): + ini_value=pytestconfig.getini("%s_%d" % (prefix, x)) + print('\\nini%d:%s' % (x, ini_value))""") + result = testdir.runpytest( + "--override-ini", 'custom_option_1=fulldir=/tmp/user1', + 'custom_option_2=url=/tmp/user2?a=b&d=e', + "-o", 'custom_option_3=True', + "-o", 'custom_option_4=no', "-s") + result.stdout.fnmatch_lines(["ini1:fulldir=/tmp/user1", + "ini2:url=/tmp/user2?a=b&d=e", + "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*"]) + + def test_with_arg_outside_cwd_without_inifile(self, tmpdir, monkeypatch): + monkeypatch.chdir(str(tmpdir)) + a = tmpdir.mkdir("a") + b = tmpdir.mkdir("b") + rootdir, inifile, inicfg = determine_setup(None, [a, b]) + assert rootdir == tmpdir + assert inifile is None + + def test_with_arg_outside_cwd_with_inifile(self, tmpdir): + a = tmpdir.mkdir("a") + b = tmpdir.mkdir("b") + inifile = a.ensure("pytest.ini") + rootdir, parsed_inifile, inicfg = determine_setup(None, [a, b]) + assert rootdir == a + assert inifile == parsed_inifile + + @pytest.mark.parametrize('dirs', ([], ['does-not-exist'], + ['a/does-not-exist'])) + def test_with_non_dir_arg(self, dirs, tmpdir): + with tmpdir.ensure(dir=True).as_cwd(): + rootdir, inifile, inicfg = determine_setup(None, dirs) + assert rootdir == tmpdir + assert inifile is None + + def test_with_existing_file_in_subdir(self, tmpdir): + a = tmpdir.mkdir("a") + a.ensure("exist") + with tmpdir.as_cwd(): + rootdir, inifile, inicfg = determine_setup(None, ['a/exist']) + assert rootdir == tmpdir + assert inifile is None + diff -Nru pytest-2.9.2/testing/test_conftest.py pytest-3.0.6/testing/test_conftest.py --- pytest-2.9.2/testing/test_conftest.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/testing/test_conftest.py 2017-01-19 09:44:52.000000000 +0000 @@ -200,9 +200,12 @@ 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) assert conftest._getconftestmodules(sub) == [ct1, ct2] @@ -407,3 +410,41 @@ """) res = testdir.runpytest() assert res.ret == 0 + + +def test_conftest_exception_handling(testdir): + testdir.makeconftest(''' + raise ValueError() + ''') + testdir.makepyfile(""" + def test_some(): + pass + """) + 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 -Nru pytest-2.9.2/testing/test_doctest.py pytest-3.0.6/testing/test_doctest.py --- pytest-2.9.2/testing/test_doctest.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/testing/test_doctest.py 2017-01-20 16:40:21.000000000 +0000 @@ -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): @@ -14,13 +16,16 @@ >>> i-1 4 """) + for x in (testdir.tmpdir, checkfile): #print "checking that %s returns custom items" % (x,) items, reprec = testdir.inline_genitems(x) assert len(items) == 1 - assert isinstance(items[0], DoctestTextfile) + assert isinstance(items[0], DoctestItem) + assert isinstance(items[0].parent, DoctestTextfile) + # Empty file has no items. items, reprec = testdir.inline_genitems(w) - assert len(items) == 1 + assert len(items) == 0 def test_collect_module_empty(self, testdir): path = testdir.makepyfile(whatever="#") @@ -199,8 +204,20 @@ "*1 failed*", ]) + def test_doctest_unex_importerror_only_txt(self, testdir): + testdir.maketxtfile(""" + >>> import asdalsdkjaslkdjasd + >>> + """) + result = testdir.runpytest() + # doctest is never executed because of error during hello.py collection + result.stdout.fnmatch_lines([ + "*>>> import asdals*", + "*UNEXPECTED*{e}*".format(e=MODULE_NOT_FOUND_ERROR), + "{e}: No module named *asdal*".format(e=MODULE_NOT_FOUND_ERROR), + ]) - def test_doctest_unex_importerror(self, testdir): + def test_doctest_unex_importerror_with_module(self, testdir): testdir.tmpdir.join("hello.py").write(_pytest._code.Source(""" import asdalsdkjaslkdjasd """)) @@ -209,10 +226,11 @@ >>> """) result = testdir.runpytest("--doctest-modules") + # doctest is never executed because of error during hello.py collection result.stdout.fnmatch_lines([ - "*>>> import hello", - "*UNEXPECTED*ImportError*", - "*import asdals*", + "*ERROR collecting hello.py*", + "*{e}: No module named *asdals*".format(e=MODULE_NOT_FOUND_ERROR), + "*Interrupted: 1 errors during collection*", ]) def test_doctestmodule(self, testdir): @@ -595,6 +613,11 @@ reprec = testdir.inline_run("--doctest-modules") reprec.assertoutcome(skipped=1) + def test_vacuous_all_skipped(self, testdir, makedoctest): + makedoctest('') + reprec = testdir.inline_run("--doctest-modules") + reprec.assertoutcome(passed=0, skipped=0) + class TestDoctestAutoUseFixtures: @@ -713,3 +736,131 @@ result = testdir.runpytest('--doctest-modules') assert 'FAILURES' not in str(result.stdout.str()) result.stdout.fnmatch_lines(['*=== 1 passed in *']) + + +class TestDoctestNamespaceFixture: + + SCOPES = ['module', 'session', 'class', 'function'] + + @pytest.mark.parametrize('scope', SCOPES) + def test_namespace_doctestfile(self, testdir, scope): + """ + Check that inserting something into the namespace works in a + simple text file doctest + """ + testdir.makeconftest(""" + import pytest + import contextlib + + @pytest.fixture(autouse=True, scope="{scope}") + def add_contextlib(doctest_namespace): + doctest_namespace['cl'] = contextlib + """.format(scope=scope)) + p = testdir.maketxtfile(""" + >>> print(cl.__name__) + contextlib + """) + reprec = testdir.inline_run(p) + reprec.assertoutcome(passed=1) + + @pytest.mark.parametrize('scope', SCOPES) + def test_namespace_pyfile(self, testdir, scope): + """ + Check that inserting something into the namespace works in a + simple Python file docstring doctest + """ + testdir.makeconftest(""" + import pytest + import contextlib + + @pytest.fixture(autouse=True, scope="{scope}") + def add_contextlib(doctest_namespace): + doctest_namespace['cl'] = contextlib + """.format(scope=scope)) + p = testdir.makepyfile(""" + def foo(): + ''' + >>> print(cl.__name__) + contextlib + ''' + """) + reprec = testdir.inline_run(p, "--doctest-modules") + reprec.assertoutcome(passed=1) + + +class TestDoctestReportingOption: + def _run_doctest_report(self, testdir, format): + testdir.makepyfile(""" + def foo(): + ''' + >>> foo() + a b + 0 1 4 + 1 2 4 + 2 3 6 + ''' + print(' a b\\n' + '0 1 4\\n' + '1 2 5\\n' + '2 3 6') + """) + return testdir.runpytest("--doctest-modules", "--doctest-report", format) + + @pytest.mark.parametrize('format', ['udiff', 'UDIFF', 'uDiFf']) + def test_doctest_report_udiff(self, testdir, format): + result = self._run_doctest_report(testdir, format) + result.stdout.fnmatch_lines([ + ' 0 1 4', + ' -1 2 4', + ' +1 2 5', + ' 2 3 6', + ]) + + def test_doctest_report_cdiff(self, testdir): + result = self._run_doctest_report(testdir, 'cdiff') + result.stdout.fnmatch_lines([ + ' a b', + ' 0 1 4', + ' ! 1 2 4', + ' 2 3 6', + ' --- 1,4 ----', + ' a b', + ' 0 1 4', + ' ! 1 2 5', + ' 2 3 6', + ]) + + def test_doctest_report_ndiff(self, testdir): + result = self._run_doctest_report(testdir, 'ndiff') + result.stdout.fnmatch_lines([ + ' a b', + ' 0 1 4', + ' - 1 2 4', + ' ? ^', + ' + 1 2 5', + ' ? ^', + ' 2 3 6', + ]) + + @pytest.mark.parametrize('format', ['none', 'only_first_failure']) + def test_doctest_report_none_or_only_first_failure(self, testdir, format): + result = self._run_doctest_report(testdir, format) + result.stdout.fnmatch_lines([ + 'Expected:', + ' a b', + ' 0 1 4', + ' 1 2 4', + ' 2 3 6', + 'Got:', + ' a b', + ' 0 1 4', + ' 1 2 5', + ' 2 3 6', + ]) + + def test_doctest_report_invalid(self, testdir): + result = self._run_doctest_report(testdir, 'obviously_invalid_format') + result.stderr.fnmatch_lines([ + "*error: argument --doctest-report: invalid choice: 'obviously_invalid_format' (choose from*" + ]) + diff -Nru pytest-2.9.2/testing/test_entry_points.py pytest-3.0.6/testing/test_entry_points.py --- pytest-2.9.2/testing/test_entry_points.py 1970-01-01 00:00:00.000000000 +0000 +++ pytest-3.0.6/testing/test_entry_points.py 2016-08-20 20:00:30.000000000 +0000 @@ -0,0 +1,13 @@ +import pkg_resources + +import pytest + + +@pytest.mark.parametrize("entrypoint", ['py.test', 'pytest']) +def test_entry_point_exist(entrypoint): + assert entrypoint in pkg_resources.get_entry_map('pytest')['console_scripts'] + + +def test_pytest_entry_points_are_identical(): + entryMap = pkg_resources.get_entry_map('pytest')['console_scripts'] + assert entryMap['pytest'].module_name == entryMap['py.test'].module_name diff -Nru pytest-2.9.2/testing/test_genscript.py pytest-3.0.6/testing/test_genscript.py --- pytest-2.9.2/testing/test_genscript.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/testing/test_genscript.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,51 +0,0 @@ -import pytest -import sys - - -@pytest.fixture(scope="module") -def standalone(request): - return Standalone(request) - -class Standalone: - def __init__(self, request): - self.testdir = request.getfuncargvalue("testdir") - script = "mypytest" - result = self.testdir.runpytest("--genscript=%s" % script) - assert result.ret == 0 - self.script = self.testdir.tmpdir.join(script) - assert self.script.check() - - def run(self, anypython, testdir, *args): - return testdir._run(anypython, self.script, *args) - -def test_gen(testdir, anypython, standalone): - if sys.version_info >= (2,7): - result = testdir._run(anypython, "-c", - "import sys;print (sys.version_info >=(2,7))") - assert result.ret == 0 - if result.stdout.str() == "False": - pytest.skip("genscript called from python2.7 cannot work " - "earlier python versions") - result = standalone.run(anypython, testdir, '--version') - if result.ret == 2: - result.stderr.fnmatch_lines(["*ERROR: setuptools not installed*"]) - elif result.ret == 0: - result.stderr.fnmatch_lines([ - "*imported from*mypytest*" - ]) - p = testdir.makepyfile("def test_func(): assert 0") - result = standalone.run(anypython, testdir, p) - assert result.ret != 0 - else: - pytest.fail("Unexpected return code") - - -def test_freeze_includes(): - """ - Smoke test for freeze_includes(), to ensure that it works across all - supported python versions. - """ - includes = pytest.freeze_includes() - assert len(includes) > 1 - assert '_pytest.genscript' in includes - diff -Nru pytest-2.9.2/testing/test_helpconfig.py pytest-3.0.6/testing/test_helpconfig.py --- pytest-2.9.2/testing/test_helpconfig.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/testing/test_helpconfig.py 2016-08-20 20:00:30.000000000 +0000 @@ -21,8 +21,8 @@ *-v*verbose* *setup.cfg* *minversion* - *to see*markers*py.test --markers* - *to see*fixtures*py.test --fixtures* + *to see*markers*pytest --markers* + *to see*fixtures*pytest --fixtures* """) def test_hookvalidation_unknown(testdir): diff -Nru pytest-2.9.2/testing/test_junitxml.py pytest-3.0.6/testing/test_junitxml.py --- pytest-2.9.2/testing/test_junitxml.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/testing/test_junitxml.py 2017-01-19 13:24:11.000000000 +0000 @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- from xml.dom import minidom -from _pytest.main import EXIT_NOTESTSCOLLECTED import py import sys import os @@ -100,7 +99,31 @@ result, dom = runandparse(testdir) assert result.ret node = dom.find_first_by_tag("testsuite") - node.assert_attr(name="pytest", errors=0, failures=1, skips=3, tests=5) + node.assert_attr(name="pytest", errors=0, failures=1, skips=2, tests=5) + + def test_summing_simple_with_errors(self, testdir): + testdir.makepyfile(""" + import pytest + @pytest.fixture + def fixture(): + raise Exception() + def test_pass(): + pass + def test_fail(): + assert 0 + def test_error(fixture): + pass + @pytest.mark.xfail + def test_xfail(): + assert False + @pytest.mark.xfail(strict=True) + def test_xpass(): + assert True + """) + result, dom = runandparse(testdir) + assert result.ret + node = dom.find_first_by_tag("testsuite") + node.assert_attr(name="pytest", errors=1, failures=2, skips=1, tests=5) def test_timing_function(self, testdir): testdir.makepyfile(""" @@ -120,7 +143,10 @@ def test_setup_error(self, testdir): testdir.makepyfile(""" - def pytest_funcarg__arg(request): + import pytest + + @pytest.fixture + def arg(request): raise ValueError() def test_function(arg): pass @@ -128,17 +154,41 @@ result, dom = runandparse(testdir) assert result.ret node = dom.find_first_by_tag("testsuite") - node.assert_attr(errors=1, tests=0) + node.assert_attr(errors=1, tests=1) tnode = node.find_first_by_tag("testcase") tnode.assert_attr( file="test_setup_error.py", - line="2", + line="5", classname="test_setup_error", name="test_function") fnode = tnode.find_first_by_tag("error") 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 @@ -158,6 +208,59 @@ snode = tnode.find_first_by_tag("skipped") snode.assert_attr(type="pytest.skip", message="hello23", ) + def test_mark_skip_contains_name_reason(self, testdir): + testdir.makepyfile(""" + import pytest + @pytest.mark.skip(reason="hello24") + def test_skip(): + assert True + """) + result, dom = runandparse(testdir) + assert result.ret == 0 + node = dom.find_first_by_tag("testsuite") + node.assert_attr(skips=1) + tnode = node.find_first_by_tag("testcase") + tnode.assert_attr( + file="test_mark_skip_contains_name_reason.py", + line="1", + classname="test_mark_skip_contains_name_reason", + name="test_skip") + snode = tnode.find_first_by_tag("skipped") + snode.assert_attr(type="pytest.skip", message="hello24", ) + + def test_mark_skipif_contains_name_reason(self, testdir): + testdir.makepyfile(""" + import pytest + GLOBAL_CONDITION = True + @pytest.mark.skipif(GLOBAL_CONDITION, reason="hello25") + def test_skip(): + assert True + """) + result, dom = runandparse(testdir) + assert result.ret == 0 + node = dom.find_first_by_tag("testsuite") + node.assert_attr(skips=1) + tnode = node.find_first_by_tag("testcase") + tnode.assert_attr( + file="test_mark_skipif_contains_name_reason.py", + line="2", + classname="test_mark_skipif_contains_name_reason", + name="test_skip") + 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: @@ -195,7 +298,7 @@ result, dom = runandparse(testdir) assert result.ret node = dom.find_first_by_tag("testsuite") - node.assert_attr(errors=1, tests=0) + node.assert_attr(errors=1, tests=1) tnode = node.find_first_by_tag("testcase") tnode.assert_attr(classname="pytest", name="internal") fnode = tnode.find_first_by_tag("error") @@ -325,23 +428,40 @@ result, dom = runandparse(testdir) # assert result.ret node = dom.find_first_by_tag("testsuite") - node.assert_attr(skips=1, tests=1) + node.assert_attr(skips=0, tests=1) tnode = node.find_first_by_tag("testcase") tnode.assert_attr( file="test_xfailure_xpass.py", line="1", classname="test_xfailure_xpass", name="test_xpass") - fnode = tnode.find_first_by_tag("skipped") - fnode.assert_attr(message="xfail-marked test passes unexpectedly") - # assert "ValueError" in fnode.toxml() + + def test_xfailure_xpass_strict(self, testdir): + testdir.makepyfile(""" + import pytest + @pytest.mark.xfail(strict=True, reason="This needs to fail!") + def test_xpass(): + pass + """) + result, dom = runandparse(testdir) + # assert result.ret + node = dom.find_first_by_tag("testsuite") + node.assert_attr(skips=0, tests=1) + tnode = node.find_first_by_tag("testcase") + tnode.assert_attr( + file="test_xfailure_xpass_strict.py", + line="1", + classname="test_xfailure_xpass_strict", + name="test_xpass") + fnode = tnode.find_first_by_tag("failure") + fnode.assert_attr(message="[XPASS(strict)] This needs to fail!") def test_collect_error(self, testdir): testdir.makepyfile("syntax error") result, dom = runandparse(testdir) assert result.ret node = dom.find_first_by_tag("testsuite") - node.assert_attr(errors=1, tests=0) + node.assert_attr(errors=1, tests=1) tnode = node.find_first_by_tag("testcase") tnode.assert_attr( file="test_collect_error.py", @@ -351,23 +471,6 @@ fnode.assert_attr(message="collection failure") assert "SyntaxError" in fnode.toxml() - def test_collect_skipped(self, testdir): - testdir.makepyfile("import pytest; pytest.skip('xyz')") - result, dom = runandparse(testdir) - assert result.ret == EXIT_NOTESTSCOLLECTED - node = dom.find_first_by_tag("testsuite") - node.assert_attr(skips=1, tests=1) - tnode = node.find_first_by_tag("testcase") - tnode.assert_attr( - file="test_collect_skipped.py", - name="test_collect_skipped") - - # py.test doesn't give us a line here. - assert tnode["line"] is None - - fnode = tnode.find_first_by_tag("skipped") - fnode.assert_attr(message="collection skipped") - def test_unicode(self, testdir): value = 'hx\xc4\x85\xc4\x87\n' testdir.makepyfile(""" @@ -421,7 +524,10 @@ def test_setup_error_captures_stdout(self, testdir): testdir.makepyfile(""" - def pytest_funcarg__arg(request): + import pytest + + @pytest.fixture + def arg(request): print('hello-stdout') raise ValueError() def test_function(arg): @@ -436,7 +542,10 @@ def test_setup_error_captures_stderr(self, testdir): testdir.makepyfile(""" import sys - def pytest_funcarg__arg(request): + import pytest + + @pytest.fixture + def arg(request): sys.stderr.write('hello-stderr') raise ValueError() def test_function(arg): @@ -606,18 +715,22 @@ 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(""" import pytest - @pytest.mark.parametrize('char', ["\\x00"]) + @pytest.mark.parametrize('char', [u"\\x00"]) def test_func(char): assert char """) result, dom = runandparse(testdir) assert result.ret == 0 node = dom.find_first_by_tag("testcase") - node.assert_attr(name="test_func[#x00]") + node.assert_attr(name="test_func[\\x00]") def test_double_colon_split_function_issue469(testdir): @@ -814,3 +927,38 @@ u'test_fancy_items_regression test_pass' u' test_fancy_items_regression.py', ] + + +def test_global_properties(testdir): + path = testdir.tmpdir.join("test_global_properties.xml") + log = LogXML(str(path), None) + from _pytest.runner import BaseReport + + class Report(BaseReport): + sections = [] + nodeid = "test_node_id" + + log.pytest_sessionstart() + log.add_global_property('foo', 1) + log.add_global_property('bar', 2) + log.pytest_sessionfinish() + + dom = minidom.parse(str(path)) + + properties = dom.getElementsByTagName('properties') + + assert (properties.length == 1), "There must be one node" + + property_list = dom.getElementsByTagName('property') + + assert (property_list.length == 2), "There most be only 2 property nodes" + + expected = {'foo': '1', 'bar': '2'} + actual = {} + + for p in property_list: + k = str(p.getAttribute('name')) + v = str(p.getAttribute('value')) + actual[k] = v + + assert actual == expected diff -Nru pytest-2.9.2/testing/test_mark.py pytest-3.0.6/testing/test_mark.py --- pytest-2.9.2/testing/test_mark.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/testing/test_mark.py 2017-01-19 13:24:11.000000000 +0000 @@ -23,15 +23,19 @@ 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 @@ 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 @@ 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 @@ -418,6 +434,32 @@ items, rec = testdir.inline_genitems(p) self.assert_markers(items, test_foo=('a', 'b'), test_bar=('a',)) + + @pytest.mark.issue568 + @pytest.mark.xfail(reason="markers smear on methods of base classes") + def test_mark_should_not_pass_to_siebling_class(self, testdir): + p = testdir.makepyfile(""" + import pytest + + class TestBase: + def test_foo(self): + pass + + @pytest.mark.b + class TestSub(TestBase): + pass + + + class TestOtherSub(TestBase): + pass + + """) + items, rec = testdir.inline_genitems(p) + base_item, sub_item, sub_item_other = items + assert not hasattr(base_item.obj, 'b') + assert not hasattr(sub_item_other.obj, 'b') + + def test_mark_decorator_baseclasses_merged(self, testdir): p = testdir.makepyfile(""" import pytest @@ -455,7 +497,8 @@ def test_mark_dynamically_in_funcarg(self, testdir): testdir.makeconftest(""" import pytest - def pytest_funcarg__arg(request): + @pytest.fixture + def arg(request): request.applymarker(pytest.mark.hello) def pytest_terminal_summary(terminalreporter): l = terminalreporter.stats['passed'] @@ -563,8 +606,32 @@ if isinstance(v, MarkInfo)]) assert marker_names == set(expected_markers) + @pytest.mark.xfail(reason='callspec2.setmulti misuses keywords') + @pytest.mark.issue1540 + def test_mark_from_parameters(self, testdir): + testdir.makepyfile(""" + import pytest + + pytestmark = pytest.mark.skipif(True, reason='skip all') + + # skipifs inside fixture params + params = [pytest.mark.skipif(False, reason='dont skip')('parameter')] + + + @pytest.fixture(params=params) + def parameter(request): + return request.param + + + 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(): @@ -573,6 +640,7 @@ 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() @@ -659,6 +727,7 @@ 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() @@ -669,4 +738,3 @@ assert_test_is_not_selected("__") assert_test_is_not_selected("()") - diff -Nru pytest-2.9.2/testing/test_monkeypatch.py pytest-3.0.6/testing/test_monkeypatch.py --- pytest-2.9.2/testing/test_monkeypatch.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/testing/test_monkeypatch.py 2017-01-20 16:40:21.000000000 +0000 @@ -3,19 +3,16 @@ import textwrap import pytest -from _pytest.monkeypatch import monkeypatch as MonkeyPatch +from _pytest.monkeypatch import MonkeyPatch -def pytest_funcarg__mp(request): +@pytest.fixture +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(): @@ -205,7 +202,7 @@ def test_monkeypatch_plugin(testdir): reprec = testdir.inline_runsource(""" def test_method(monkeypatch): - assert monkeypatch.__class__.__name__ == "monkeypatch" + assert monkeypatch.__class__.__name__ == "MonkeyPatch" """) res = reprec.countoutcomes() assert tuple(res) == (1, 0, 0), res @@ -327,4 +324,4 @@ try: monkeypatch.delattr('requests.sessions.Session.request') finally: - monkeypatch.undo() \ No newline at end of file + monkeypatch.undo() diff -Nru pytest-2.9.2/testing/test_nose.py pytest-3.0.6/testing/test_nose.py --- pytest-2.9.2/testing/test_nose.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/testing/test_nose.py 2017-01-19 09:44:52.000000000 +0000 @@ -25,18 +25,22 @@ 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 -Nru pytest-2.9.2/testing/test_parseopt.py pytest-3.0.6/testing/test_parseopt.py --- pytest-2.9.2/testing/test_parseopt.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/testing/test_parseopt.py 2017-01-19 09:44:52.000000000 +0000 @@ -29,6 +29,9 @@ assert argument.dest == 'test' argument = parseopt.Argument('-t', '--test', dest='abc') assert argument.dest == 'abc' + assert str(argument) == ( + "Argument(_short_opts: ['-t'], _long_opts: ['--test'], dest: 'abc')" + ) def test_argument_type(self): argument = parseopt.Argument('-t', dest='abc', type='int') @@ -77,6 +80,13 @@ assert len(group.options) == 1 assert isinstance(group.options[0], parseopt.Argument) + def test_group_addoption_conflict(self): + group = parseopt.OptionGroup("hello again") + group.addoption("--option1", "--option-1", action="store_true") + with pytest.raises(ValueError) as err: + group.addoption("--option1", "--option-one", action="store_true") + assert str(set(["--option1"])) in str(err.value) + def test_group_shortopt_lowercase(self, parser): group = parser.getgroup("hello") pytest.raises(ValueError, """ @@ -128,7 +138,10 @@ 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" @@ -238,7 +251,19 @@ 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): @@ -246,8 +271,8 @@ pytest.skip("bash not available") script = str(testdir.tmpdir.join("test_argcomplete")) pytest_bin = sys.argv[0] - if "py.test" not in os.path.basename(pytest_bin): - pytest.skip("need to be run with py.test executable, not %s" %(pytest_bin,)) + if "pytest" not in os.path.basename(pytest_bin): + pytest.skip("need to be run with pytest executable, not %s" %(pytest_bin,)) with open(str(script), 'w') as fp: # redirect output from argcomplete to stdin and stderr is not trivial @@ -262,8 +287,8 @@ monkeypatch.setenv('COMP_WORDBREAKS', ' \\t\\n"\\\'><=;|&(:') arg = '--fu' - monkeypatch.setenv('COMP_LINE', "py.test " + arg) - monkeypatch.setenv('COMP_POINT', str(len("py.test " + arg))) + monkeypatch.setenv('COMP_LINE', "pytest " + arg) + monkeypatch.setenv('COMP_POINT', str(len("pytest " + arg))) result = testdir.run('bash', str(script), arg) if result.ret == 255: # argcomplete not found @@ -280,8 +305,7 @@ return os.mkdir('test_argcomplete.d') arg = 'test_argc' - monkeypatch.setenv('COMP_LINE', "py.test " + arg) - monkeypatch.setenv('COMP_POINT', str(len('py.test ' + arg))) + monkeypatch.setenv('COMP_LINE', "pytest " + arg) + monkeypatch.setenv('COMP_POINT', str(len('pytest ' + arg))) result = testdir.run('bash', str(script), arg) result.stdout.fnmatch_lines(["test_argcomplete", "test_argcomplete.d/"]) - diff -Nru pytest-2.9.2/testing/test_pastebin.py pytest-3.0.6/testing/test_pastebin.py --- pytest-2.9.2/testing/test_pastebin.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/testing/test_pastebin.py 2017-01-19 09:44:52.000000000 +0000 @@ -84,8 +84,10 @@ 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 -Nru pytest-2.9.2/testing/test_pdb.py pytest-3.0.6/testing/test_pdb.py --- pytest-2.9.2/testing/test_pdb.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/testing/test_pdb.py 2017-01-19 13:24:11.000000000 +0000 @@ -1,6 +1,8 @@ import sys +import platform import _pytest._code +import pytest def runpdb_and_get_report(testdir, source): @@ -12,12 +14,16 @@ class TestPDB: - def pytest_funcarg__pdblist(self, request): - monkeypatch = request.getfuncargvalue("monkeypatch") + + @pytest.fixture + def pdblist(self, request): + monkeypatch = request.getfixturevalue("monkeypatch") pdblist = [] + def mypdb(*args): pdblist.append(args) - plugin = request.config.pluginmanager.getplugin('pdb') + + plugin = request.config.pluginmanager.getplugin('debugging') monkeypatch.setattr(plugin, 'post_mortem', mypdb) return pdblist @@ -73,9 +79,33 @@ 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() + def test_pdb_unittest_postmortem(self, testdir): + p1 = testdir.makepyfile(""" + import unittest + class Blub(unittest.TestCase): + def tearDown(self): + self.filename = None + def test_false(self): + self.filename = 'debug' + '.me' + assert 0 + """) + child = testdir.spawn_pytest("--pdb %s" % p1) + child.expect('(Pdb)') + child.sendline('p self.filename') + child.sendeof() + rest = child.read().decode("utf8") + assert 'debug.me' in rest + self.flush(child) + def test_pdb_interaction_capture(self, testdir): p1 = testdir.makepyfile(""" def test_1(): @@ -89,8 +119,7 @@ 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(""" @@ -108,8 +137,7 @@ 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(""" @@ -121,8 +149,7 @@ 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(""" @@ -134,8 +161,7 @@ #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(""" @@ -155,8 +181,7 @@ 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(""" @@ -171,8 +196,7 @@ 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(""" @@ -187,8 +211,7 @@ 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(""" @@ -207,8 +230,7 @@ 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(""" @@ -227,8 +249,7 @@ 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(""" @@ -254,8 +275,7 @@ 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(""" @@ -266,7 +286,7 @@ 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(""" @@ -280,7 +300,7 @@ 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 """) @@ -309,5 +329,29 @@ 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 = [] + + # install dummy debugger class and track which methods were called on it + class _CustomPdb: + def __init__(self, *args, **kwargs): + called.append("init") + + def reset(self): + called.append("reset") + + def interaction(self, *args): + called.append("interaction") + + _pytest._CustomPdb = _CustomPdb + + p1 = testdir.makepyfile("""xxx """) + result = testdir.runpytest_inprocess( + "--pdbcls=_pytest:_CustomPdb", p1) + result.stdout.fnmatch_lines([ + "*NameError*xxx*", + "*1 error*", + ]) + assert called == ["init", "reset", "interaction"] diff -Nru pytest-2.9.2/testing/test_pluginmanager.py pytest-3.0.6/testing/test_pluginmanager.py --- pytest-2.9.2/testing/test_pluginmanager.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/testing/test_pluginmanager.py 2017-01-19 09:44:52.000000000 +0000 @@ -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 @@ def test_configure(self, testdir): config = testdir.parseconfig() l = [] + class A: def pytest_configure(self, config): l.append(self) @@ -101,13 +104,16 @@ 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 @@ 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_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 -Nru pytest-2.9.2/testing/test_pytester.py pytest-3.0.6/testing/test_pytester.py --- pytest-2.9.2/testing/test_pytester.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/testing/test_pytester.py 2017-01-20 16:40:36.000000000 +0000 @@ -11,6 +11,7 @@ assert not recorder.getfailures() pytest.xfail("internal reportrecorder tests need refactoring") + class rep: excinfo = None passed = False @@ -80,10 +81,13 @@ "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 @@ 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 -Nru pytest-2.9.2/testing/test_recwarn.py pytest-3.0.6/testing/test_recwarn.py --- pytest-2.9.2/testing/test_recwarn.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/testing/test_recwarn.py 2017-01-20 16:40:21.000000000 +0000 @@ -1,4 +1,5 @@ import warnings +import re import py import pytest from _pytest.recwarn import WarningsRecorder @@ -114,7 +115,7 @@ 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,16 +186,38 @@ with pytest.warns(RuntimeWarning): warnings.warn("runtime", RuntimeWarning) - with pytest.raises(pytest.fail.Exception): + 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): + 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]))) - with pytest.warns(UserWarning): - warnings.warn("user", UserWarning) def test_record(self): with pytest.warns(UserWarning) as record: diff -Nru pytest-2.9.2/testing/test_resultlog.py pytest-3.0.6/testing/test_resultlog.py --- pytest-2.9.2/testing/test_resultlog.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/testing/test_resultlog.py 2016-08-20 20:00:30.000000000 +0000 @@ -83,19 +83,10 @@ def test_collection_report(self, testdir): ok = testdir.makepyfile(test_collection_ok="") - skip = testdir.makepyfile(test_collection_skip= - "import pytest ; pytest.skip('hello')") fail = testdir.makepyfile(test_collection_fail="XXX") lines = self.getresultlog(testdir, ok) assert not lines - lines = self.getresultlog(testdir, skip) - assert len(lines) == 2 - assert lines[0].startswith("S ") - assert lines[0].endswith("test_collection_skip.py") - assert lines[1].startswith(" ") - assert lines[1].endswith("test_collection_skip.py:1: Skipped: hello") - lines = self.getresultlog(testdir, fail) assert lines assert lines[0].startswith("F ") @@ -231,6 +222,6 @@ pass """) result = testdir.runpytest("--resultlog=log") - assert result.ret == 1 + assert result.ret == 2 diff -Nru pytest-2.9.2/testing/test_runner.py pytest-3.0.6/testing/test_runner.py --- pytest-2.9.2/testing/test_runner.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/testing/test_runner.py 2017-01-19 09:44:52.000000000 +0000 @@ -38,9 +38,13 @@ 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 @@ # 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) @@ -228,6 +234,40 @@ assert reps[5].nodeid.endswith("test_func") assert reps[5].failed + def test_exact_teardown_issue1206(self, testdir): + """issue shadowing error with wrong number of arguments on teardown_method.""" + rec = testdir.inline_runsource(""" + import pytest + + class TestClass: + def teardown_method(self, x, y, z): + pass + + def test_method(self): + assert True + """) + reps = rec.getreports("pytest_runtest_logreport") + print (reps) + assert len(reps) == 3 + # + assert reps[0].nodeid.endswith("test_method") + assert reps[0].passed + assert reps[0].when == 'setup' + # + assert reps[1].nodeid.endswith("test_method") + assert reps[1].passed + assert reps[1].when == 'call' + # + assert reps[2].nodeid.endswith("test_method") + assert reps[2].failed + assert reps[2].when == "teardown" + assert reps[2].longrepr.reprcrash.message in ( + # python3 error + "TypeError: teardown_method() missing 2 required positional arguments: 'y' and 'z'", + # python2 error + 'TypeError: teardown_method() takes exactly 4 arguments (2 given)' + ) + def test_failure_in_setup_function_ignores_custom_repr(self, testdir): testdir.makepyfile(conftest=""" import pytest @@ -332,18 +372,6 @@ assert res[0].name == "test_func1" assert res[1].name == "TestClass" - def test_skip_at_module_scope(self, testdir): - col = testdir.getmodulecol(""" - import pytest - pytest.skip("hello") - def test_func(): - pass - """) - rep = main.collect_one_node(col) - assert not rep.failed - assert not rep.passed - assert rep.skipped - reporttypes = [ runner.BaseReport, @@ -378,13 +406,15 @@ @pytest.mark.xfail def test_runtest_in_module_ordering(testdir): p1 = testdir.makepyfile(""" + import pytest def pytest_runtest_setup(item): # runs after class-level! item.function.mylist.append("module") class TestClass: def pytest_runtest_setup(self, item): assert not hasattr(item.function, 'mylist') item.function.mylist = ['class'] - def pytest_funcarg__mylist(self, request): + @pytest.fixture + def mylist(self, request): return request.function.mylist def pytest_runtest_call(self, item, __multicall__): try: @@ -424,6 +454,18 @@ s = excinfo.exconly(tryshort=True) assert s.startswith("Failed") +def test_pytest_exit_msg(testdir): + testdir.makeconftest(""" + import pytest + + def pytest_configure(config): + pytest.exit('oh noes') + """) + result = testdir.runpytest() + result.stderr.fnmatch_lines([ + "Exit: oh noes", + ]) + def test_pytest_fail_notrace(testdir): testdir.makepyfile(""" import pytest @@ -491,8 +533,10 @@ def test_importorskip(monkeypatch): importorskip = pytest.importorskip + def f(): importorskip("asdlkj") + try: sys = importorskip("sys") # noqa assert sys == py.std.sys @@ -535,6 +579,19 @@ pytest.fail("spurious skip") +def test_importorskip_module_level(testdir): + """importorskip must be able to skip entire modules when used at module level""" + testdir.makepyfile(''' + import pytest + foobarbaz = pytest.importorskip("foobarbaz") + + def test_foo(): + pass + ''') + result = testdir.runpytest() + result.stdout.fnmatch_lines(['*collected 0 items / 1 skipped*']) + + def test_pytest_cmdline_main(testdir): p = testdir.makepyfile(""" import pytest @@ -594,11 +651,13 @@ """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(""" @@ -632,3 +691,68 @@ assert sys.last_type is IndexError assert sys.last_value.args[0] == 'TEST' assert sys.last_traceback + + +class TestReportContents: + """ + Test user-level API of ``TestReport`` objects. + """ + + def getrunner(self): + return lambda item: runner.runtestprotocol(item, log=False) + + def test_longreprtext_pass(self, testdir): + reports = testdir.runitem(""" + def test_func(): + pass + """) + rep = reports[1] + assert rep.longreprtext == '' + + def test_longreprtext_failure(self, testdir): + reports = testdir.runitem(""" + def test_func(): + x = 1 + assert x == 4 + """) + rep = reports[1] + assert 'assert 1 == 4' in rep.longreprtext + + def test_captured_text(self, testdir): + reports = testdir.runitem(""" + import pytest + import sys + + @pytest.fixture + def fix(): + sys.stdout.write('setup: stdout\\n') + sys.stderr.write('setup: stderr\\n') + yield + sys.stdout.write('teardown: stdout\\n') + sys.stderr.write('teardown: stderr\\n') + assert 0 + + def test_func(fix): + sys.stdout.write('call: stdout\\n') + sys.stderr.write('call: stderr\\n') + assert 0 + """) + setup, call, teardown = reports + assert setup.capstdout == 'setup: stdout\n' + assert call.capstdout == 'setup: stdout\ncall: stdout\n' + assert teardown.capstdout == 'setup: stdout\ncall: stdout\nteardown: stdout\n' + + assert setup.capstderr == 'setup: stderr\n' + assert call.capstderr == 'setup: stderr\ncall: stderr\n' + assert teardown.capstderr == 'setup: stderr\ncall: stderr\nteardown: stderr\n' + + def test_no_captured_text(self, testdir): + reports = testdir.runitem(""" + def test_func(): + pass + """) + rep = reports[1] + assert rep.capstdout == '' + assert rep.capstderr == '' + + diff -Nru pytest-2.9.2/testing/test_runner_xunit.py pytest-3.0.6/testing/test_runner_xunit.py --- pytest-2.9.2/testing/test_runner_xunit.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/testing/test_runner_xunit.py 2016-08-20 20:00:30.000000000 +0000 @@ -1,6 +1,8 @@ # # test correct setup/teardowns at # module, class, and instance level +import pytest + def test_module_and_function_setup(testdir): reprec = testdir.inline_runsource(""" @@ -234,7 +236,8 @@ import pytest def setup_module(mod): raise ValueError(42) - def pytest_funcarg__hello(request): + @pytest.fixture + def hello(request): raise ValueError("xyz43") def test_function1(hello): pass @@ -250,3 +253,53 @@ "*2 error*" ]) assert "xyz43" not in result.stdout.str() + + +@pytest.mark.parametrize('arg', ['', 'arg']) +def test_setup_teardown_function_level_with_optional_argument(testdir, monkeypatch, arg): + """parameter to setup/teardown xunit-style functions parameter is now optional (#1728).""" + import sys + trace_setups_teardowns = [] + monkeypatch.setattr(sys, 'trace_setups_teardowns', trace_setups_teardowns, raising=False) + p = testdir.makepyfile(""" + import pytest + import sys + + trace = sys.trace_setups_teardowns.append + + def setup_module({arg}): trace('setup_module') + def teardown_module({arg}): trace('teardown_module') + + def setup_function({arg}): trace('setup_function') + def teardown_function({arg}): trace('teardown_function') + + def test_function_1(): pass + def test_function_2(): pass + + class Test: + def setup_method(self, {arg}): trace('setup_method') + def teardown_method(self, {arg}): trace('teardown_method') + + def test_method_1(self): pass + def test_method_2(self): pass + """.format(arg=arg)) + result = testdir.inline_run(p) + result.assertoutcome(passed=4) + + expected = [ + 'setup_module', + + 'setup_function', + 'teardown_function', + 'setup_function', + 'teardown_function', + + 'setup_method', + 'teardown_method', + + 'setup_method', + 'teardown_method', + + 'teardown_module', + ] + assert trace_setups_teardowns == expected diff -Nru pytest-2.9.2/testing/test_session.py pytest-3.0.6/testing/test_session.py --- pytest-2.9.2/testing/test_session.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/testing/test_session.py 2016-08-21 18:04:29.000000000 +0000 @@ -42,7 +42,7 @@ reprec = testdir.inline_run(tfile) l = reprec.getfailedcollections() assert len(l) == 1 - out = l[0].longrepr.reprcrash.message + out = str(l[0].longrepr) assert out.find('does_not_work') != -1 def test_raises_output(self, testdir): @@ -174,10 +174,6 @@ class TestY(TestX): pass """, - test_two=""" - import pytest - pytest.skip('xxx') - """, test_three="xxxdsadsadsadsa", __init__="" ) @@ -189,11 +185,9 @@ started = reprec.getcalls("pytest_collectstart") finished = reprec.getreports("pytest_collectreport") assert len(started) == len(finished) - assert len(started) == 8 # XXX extra TopCollector + assert len(started) == 7 # XXX extra TopCollector colfail = [x for x in finished if x.failed] - colskipped = [x for x in finished if x.skipped] assert len(colfail) == 1 - assert len(colskipped) == 1 def test_minus_x_import_error(self, testdir): testdir.makepyfile(__init__="") @@ -203,6 +197,14 @@ colfail = [x for x in finished if x.failed] assert len(colfail) == 1 + def test_minus_x_overriden_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) + finished = reprec.getreports("pytest_collectreport") + colfail = [x for x in finished if x.failed] + assert len(colfail) == 2 + def test_plugin_specify(testdir): pytest.raises(ImportError, """ diff -Nru pytest-2.9.2/testing/test_skipping.py pytest-3.0.6/testing/test_skipping.py --- pytest-2.9.2/testing/test_skipping.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/testing/test_skipping.py 2017-01-19 09:44:52.000000000 +0000 @@ -145,7 +145,20 @@ def test_xfail_xpassed(self, testdir): item = testdir.getitem(""" import pytest - @pytest.mark.xfail + @pytest.mark.xfail(reason="this is an xfail") + def test_func(): + assert 1 + """) + reports = runtestprotocol(item, log=False) + assert len(reports) == 3 + callreport = reports[1] + assert callreport.passed + assert callreport.wasxfail == "this is an xfail" + + def test_xfail_xpassed_strict(self, testdir): + item = testdir.getitem(""" + import pytest + @pytest.mark.xfail(strict=True, reason="nope") def test_func(): assert 1 """) @@ -153,7 +166,8 @@ assert len(reports) == 3 callreport = reports[1] assert callreport.failed - assert callreport.wasxfail == "" + assert callreport.longrepr == "[XPASS(strict)] nope" + assert not hasattr(callreport, "wasxfail") def test_xfail_run_anyway(self, testdir): testdir.makepyfile(""" @@ -209,7 +223,7 @@ def test_this_false(): assert 1 """) - result = testdir.runpytest(p, '--report=xfailed', ) + result = testdir.runpytest(p, '-rx', ) result.stdout.fnmatch_lines([ "*test_one*test_this*", "*NOTRUN*noway", @@ -227,7 +241,7 @@ def setup_module(mod): raise ValueError(42) """) - result = testdir.runpytest(p, '--report=xfailed', ) + result = testdir.runpytest(p, '-rx', ) result.stdout.fnmatch_lines([ "*test_one*test_this*", "*NOTRUN*hello", @@ -309,7 +323,8 @@ def test_dynamic_xfail_no_run(self, testdir): p = testdir.makepyfile(""" import pytest - def pytest_funcarg__arg(request): + @pytest.fixture + def arg(request): request.applymarker(pytest.mark.xfail(run=False)) def test_this(arg): assert 0 @@ -323,7 +338,8 @@ def test_dynamic_xfail_set_during_funcarg_setup(self, testdir): p = testdir.makepyfile(""" import pytest - def pytest_funcarg__arg(request): + @pytest.fixture + def arg(request): request.applymarker(pytest.mark.xfail) def test_this2(arg): assert 0 @@ -682,19 +698,15 @@ def test_method(self): doskip() """, - test_two = """ - from conftest import doskip - doskip() - """, conftest = """ import pytest def doskip(): pytest.skip('test') """ ) - result = testdir.runpytest('--report=skipped') + result = testdir.runpytest('-rs') result.stdout.fnmatch_lines([ - "*SKIP*3*conftest.py:3: test", + "*SKIP*2*conftest.py:3: test", ]) assert result.ret == 0 @@ -941,3 +953,19 @@ assert not failed xfailed = [r for r in skipped if hasattr(r, 'wasxfail')] assert xfailed + + +def test_module_level_skip_error(testdir): + """ + Verify that using pytest.skip at module level causes a collection error + """ + testdir.makepyfile(""" + import pytest + @pytest.skip + def test_func(): + assert True + """) + result = testdir.runpytest() + result.stdout.fnmatch_lines( + "*Using pytest.skip outside of a test is not allowed*" + ) diff -Nru pytest-2.9.2/testing/test_terminal.py pytest-3.0.6/testing/test_terminal.py --- pytest-2.9.2/testing/test_terminal.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/testing/test_terminal.py 2017-01-19 09:44:52.000000000 +0000 @@ -8,16 +8,11 @@ import _pytest._code import py import pytest -from _pytest import runner from _pytest.main import EXIT_NOTESTSCOLLECTED from _pytest.terminal import TerminalReporter, repr_pythonversion, getreportopt from _pytest.terminal import build_summary_stats_line, _plugin_nameversions -def basic_run_report(item): - runner.call_and_report(item, "setup", log=False) - return runner.call_and_report(item, "call", log=False) - DistInfo = collections.namedtuple('DistInfo', ['project_name', 'version']) @@ -228,8 +223,7 @@ """) result = testdir.runpytest("--collect-only", "-rs") result.stdout.fnmatch_lines([ - "SKIP*hello*", - "*1 skip*", + "*ERROR collecting*", ]) def test_collectonly_failed_module(self, testdir): @@ -273,11 +267,11 @@ def test_collectonly_error(self, testdir): p = testdir.makepyfile("import Errlkjqweqwe") result = testdir.runpytest("--collect-only", p) - assert result.ret == 1 + assert result.ret == 2 result.stdout.fnmatch_lines(_pytest._code.Source(""" *ERROR* - *import Errlk* *ImportError* + *No module named *Errlk* *1 error* """).strip()) @@ -376,6 +370,31 @@ "*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(""" @@ -390,7 +409,7 @@ result = testdir.runpytest("-k", "test_two:", testpath) result.stdout.fnmatch_lines([ "*test_deselected.py ..", - "=* 1 test*deselected by*test_two:*=", + "=* 1 test*deselected *=", ]) assert result.ret == 0 @@ -596,26 +615,30 @@ class config: class option: reportchars = "" - config.option.report = "xfailed" - assert getreportopt(config) == "x" + disablepytestwarnings = True - config.option.report = "xfailed,skipped" - assert getreportopt(config) == "xs" - - config.option.report = "skipped,xfailed" - assert getreportopt(config) == "sx" - - config.option.report = "skipped" config.option.reportchars = "sf" assert getreportopt(config) == "sf" - config.option.reportchars = "sfx" + config.option.reportchars = "sfxw" assert getreportopt(config) == "sfx" + config.option.reportchars = "sfx" + config.option.disablepytestwarnings = False + assert getreportopt(config) == "sfxw" + + config.option.reportchars = "sfxw" + config.option.disablepytestwarnings = False + assert getreportopt(config) == "sfxw" + + def test_terminalreporter_reportopt_addopts(testdir): testdir.makeini("[pytest]\naddopts=-rs") testdir.makepyfile(""" - def pytest_funcarg__tr(request): + import pytest + + @pytest.fixture + def tr(request): tr = request.config.pluginmanager.getplugin("terminalreporter") return tr def test_opt(tr): @@ -629,7 +652,10 @@ def test_tbstyle_short(testdir): p = testdir.makepyfile(""" - def pytest_funcarg__arg(request): + import pytest + + @pytest.fixture + def arg(request): return 42 def test_opt(arg): x = 0 @@ -640,7 +666,7 @@ assert 'arg = 42' not in s assert 'x = 0' not in s result.stdout.fnmatch_lines([ - "*%s:5*" % p.basename, + "*%s:8*" % p.basename, " assert x", "E assert*", ]) @@ -665,8 +691,8 @@ testdir.makepyfile("import xyz\n") result = testdir.runpytest(*option.args) result.stdout.fnmatch_lines([ - "? import xyz", - "E ImportError: No module named *xyz*", + "ImportError while importing*", + "*No module named *xyz*", "*1 error*", ]) @@ -786,15 +812,17 @@ def test_terminal_summary(testdir): testdir.makeconftest(""" - def pytest_terminal_summary(terminalreporter): + def pytest_terminal_summary(terminalreporter, exitstatus): w = terminalreporter w.section("hello") w.line("world") + w.line("exitstatus: {0}".format(exitstatus)) """) result = testdir.runpytest() result.stdout.fnmatch_lines(""" *==== hello ====* world + exitstatus: 5 """) diff -Nru pytest-2.9.2/testing/test_tmpdir.py pytest-3.0.6/testing/test_tmpdir.py --- pytest-2.9.2/testing/test_tmpdir.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/testing/test_tmpdir.py 2016-08-21 18:11:05.000000000 +0000 @@ -29,7 +29,6 @@ assert bn == "qwe__abc" def test_ensuretemp(recwarn): - #pytest.deprecated_call(pytest.ensuretemp, 'hello') d1 = pytest.ensuretemp('hello') d2 = pytest.ensuretemp('hello') assert d1 == d2 diff -Nru pytest-2.9.2/testing/test_unittest.py pytest-3.0.6/testing/test_unittest.py --- pytest-2.9.2/testing/test_unittest.py 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/testing/test_unittest.py 2017-01-19 09:44:52.000000000 +0000 @@ -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 @@ 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(""" @@ -265,8 +288,8 @@ def run(self, result): excinfo = pytest.raises(ZeroDivisionError, lambda: 0/0) # we fake an incompatible exception info - from _pytest.monkeypatch import monkeypatch - mp = monkeypatch() + from _pytest.monkeypatch import MonkeyPatch + mp = MonkeyPatch() def t(*args): mp.undo() raise TypeError() @@ -419,8 +442,9 @@ def test_method(self): pass """) + from _pytest.compat import _is_unittest_unexpected_success_a_failure + should_fail = _is_unittest_unexpected_success_a_failure() result = testdir.runpytest("-rxs") - assert result.ret == 0 result.stdout.fnmatch_lines_random([ "*XFAIL*test_trial_todo*", "*trialselfskip*", @@ -429,8 +453,9 @@ "*i2wanto*", "*sys.version_info*", "*skip_in_method*", - "*4 skipped*3 xfail*1 xpass*", + "*1 failed*4 skipped*3 xfailed*" if should_fail else "*4 skipped*3 xfail*1 xpass*", ]) + assert result.ret == (1 if should_fail else 0) def test_trial_error(self, testdir): testdir.makepyfile(""" @@ -587,24 +612,62 @@ assert "TypeError" in result.stdout.str() assert result.ret == 1 + @pytest.mark.skipif("sys.version_info < (2,7)") -def test_unittest_unexpected_failure(testdir): - testdir.makepyfile(""" +@pytest.mark.parametrize('runner', ['pytest', 'unittest']) +def test_unittest_expected_failure_for_failing_test_is_xfail(testdir, runner): + script = testdir.makepyfile(""" import unittest class MyTestCase(unittest.TestCase): @unittest.expectedFailure - def test_func1(self): - assert 0 - @unittest.expectedFailure - def test_func2(self): - assert 1 + def test_failing_test_is_xfail(self): + assert False + if __name__ == '__main__': + unittest.main() """) - result = testdir.runpytest("-rxX") - result.stdout.fnmatch_lines([ - "*XFAIL*MyTestCase*test_func1*", - "*XPASS*MyTestCase*test_func2*", - "*1 xfailed*1 xpass*", - ]) + if runner == 'pytest': + result = testdir.runpytest("-rxX") + result.stdout.fnmatch_lines([ + "*XFAIL*MyTestCase*test_failing_test_is_xfail*", + "*1 xfailed*", + ]) + else: + result = testdir.runpython(script) + result.stderr.fnmatch_lines([ + "*1 test in*", + "*OK*(expected failures=1)*", + ]) + assert result.ret == 0 + + +@pytest.mark.skipif("sys.version_info < (2,7)") +@pytest.mark.parametrize('runner', ['pytest', 'unittest']) +def test_unittest_expected_failure_for_passing_test_is_fail(testdir, runner): + script = testdir.makepyfile(""" + import unittest + class MyTestCase(unittest.TestCase): + @unittest.expectedFailure + def test_passing_test_is_fail(self): + assert True + if __name__ == '__main__': + unittest.main() + """) + from _pytest.compat import _is_unittest_unexpected_success_a_failure + should_fail = _is_unittest_unexpected_success_a_failure() + if runner == 'pytest': + result = testdir.runpytest("-rxX") + result.stdout.fnmatch_lines([ + "*MyTestCase*test_passing_test_is_fail*", + "*1 failed*" if should_fail else "*1 xpassed*", + ]) + else: + result = testdir.runpython(script) + result.stderr.fnmatch_lines([ + "*1 test in*", + "*(unexpected successes=1)*", + ]) + + assert result.ret == (1 if should_fail else 0) @pytest.mark.parametrize('fix_type, stmt', [ @@ -735,3 +798,17 @@ *SKIP*[1]*skipping due to reasons* *1 skipped* """) + +def test_class_method_containing_test_issue1558(testdir): + testdir.makepyfile(test_foo=""" + import unittest + + class MyTestCase(unittest.TestCase): + def test_should_run(self): + pass + def test_should_not_run(self): + pass + test_should_not_run.__test__ = False + """) + reprec = testdir.inline_run() + reprec.assertoutcome(passed=1) diff -Nru pytest-2.9.2/tox.ini pytest-3.0.6/tox.ini --- pytest-2.9.2/tox.ini 2016-05-31 17:08:14.000000000 +0000 +++ pytest-3.0.6/tox.ini 2017-01-22 17:44:30.000000000 +0000 @@ -1,23 +1,36 @@ [tox] minversion=2.0 distshare={homedir}/.tox/distshare +# make sure to update enviroment list on appveyor.yml envlist= - linting,py26,py27,py33,py34,py35,pypy, - {py27,py35}-{pexpect,xdist,trial}, - py27-nobyte,doctesting,py27-cxfreeze + linting + py26 + py27 + py33 + py34 + py35 + py36 + pypy + {py27,py35}-{pexpect,xdist,trial} + py27-nobyte + doctesting + freeze + docs [testenv] -commands= py.test --lsof -rfsxX {posargs:testing} +commands= pytest --lsof -rfsxX {posargs:testing} passenv = USER USERNAME -deps= +deps= + hypothesis>=3.5.2 nose mock requests [testenv:py26] -commands= py.test --lsof -rfsxX {posargs:testing} +commands= pytest --lsof -rfsxX {posargs:testing} # pinning mock to last supported version for python 2.6 deps= + hypothesis<3.0 nose mock<1.1 @@ -28,82 +41,90 @@ mock nose commands= - py.test -n3 -rfsxX --runpytest=subprocess {posargs:testing} + pytest -n3 -rfsxX --runpytest=subprocess {posargs:testing} -[testenv:genscript] -commands= py.test --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 + 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>=3.5.2 commands= - py.test -n1 -rfsxX {posargs:testing} + pytest -n1 -rfsxX {posargs:testing} [testenv:py35-xdist] deps={[testenv:py27-xdist]deps} commands= - py.test -n3 -rfsxX {posargs:testing} + pytest -n3 -rfsxX {posargs:testing} [testenv:py27-pexpect] changedir=testing platform=linux|darwin deps=pexpect commands= - py.test -rfsxX test_pdb.py test_terminal.py test_unittest.py + pytest -rfsxX test_pdb.py test_terminal.py test_unittest.py [testenv:py35-pexpect] changedir=testing platform=linux|darwin deps={[testenv:py27-pexpect]deps} commands= - py.test -rfsxX test_pdb.py test_terminal.py test_unittest.py + pytest -rfsxX test_pdb.py test_terminal.py test_unittest.py [testenv:py27-nobyte] -deps=pytest-xdist>=1.13 +deps= + pytest-xdist>=1.13 + hypothesis>=3.5.2 distribute=true setenv= PYTHONDONTWRITEBYTECODE=1 commands= - py.test -n3 -rfsxX {posargs:testing} + pytest -n3 -rfsxX {posargs:testing} [testenv:py27-trial] deps=twisted commands= - py.test -rsxf {posargs:testing/test_unittest.py} + pytest -ra {posargs:testing/test_unittest.py} [testenv:py35-trial] -platform=linux|darwin deps={[testenv:py27-trial]deps} commands= - py.test -rsxf {posargs:testing/test_unittest.py} - -[testenv:doctest] -commands=py.test --doctest-modules _pytest -deps= + pytest -ra {posargs:testing/test_unittest.py} -[testenv:doc] +[testenv:docs] basepython=python changedir=doc/en -deps=sphinx - PyYAML +deps= + sphinx + PyYAML commands= - make clean - make html + sphinx-build -W -b html . _build [testenv:doctesting] -basepython = python3 -changedir=doc/en -deps=PyYAML -commands= py.test -rfsxX {posargs} +basepython = python +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 @@ -123,19 +144,18 @@ commands= {envpython} {envbindir}/py.test-jython -rfsxX {posargs} -[testenv:py27-cxfreeze] -changedir=testing/cx_freeze -platform=linux|darwin +[testenv:freeze] +changedir=testing/freeze +deps=pyinstaller commands= - {envpython} install_cx_freeze.py - {envpython} runtests_setup.py build --build-exe build + {envpython} create_executable.py {envpython} tox_run.py [testenv:coveralls] passenv = TRAVIS TRAVIS_JOB_ID TRAVIS_BRANCH COVERALLS_REPO_TOKEN usedevelop=True -basepython=python3.4 +basepython=python3.5 changedir=. deps = {[testenv]deps} @@ -159,3 +179,4 @@ [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