Merge branch 'features' into conftest-exception-printing

This commit is contained in:
Bruno Oliveira 2016-07-20 19:33:36 -03:00
commit e0f08a73ab
67 changed files with 2774 additions and 2762 deletions

View File

@ -9,7 +9,9 @@ Alexei Kozlenok
Anatoly Bubenkoff Anatoly Bubenkoff
Andreas Zeidler Andreas Zeidler
Andy Freeland Andy Freeland
Andrzej Ostrowski
Anthon van der Neut Anthon van der Neut
Antony Lee
Armin Rigo Armin Rigo
Aron Curzon Aron Curzon
Aviv Palivoda Aviv Palivoda

View File

@ -1,55 +1,76 @@
3.0.0.dev1 3.0.0.dev1
========== ==========
**Changes**
*
*
*
* Fix (`#607`_): pytest.skip() is no longer allowed at module level to
prevent misleading use as test function decorator. When used at a module
level an error will be raised during collection.
Thanks `@omarkohl`_ for the complete PR (`#1519`_).
* Fix (`#1516`_): ConftestImportFailure now shows the traceback
**Incompatible changes** **Incompatible changes**
* Removing the following deprecated commandline options 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.
* ``--genscript`` * Reinterpretation mode has now been removed. Only plain and rewrite
* ``--no-assert`` mode are available, consequently the ``--assert=reinterp`` option is
* ``--nomagic`` no longer available. Thanks `@flub`_ for the PR.
* ``--report``
Thanks to `@RedBeardCode`_ for the PR(`#1664`_) * The following deprecated commandline options were removed:
* removed support code for python 3 < 3.3 addressing (`#1627`_) * ``--genscript``: no longer supported;
* ``--no-assert``: use ``--assert=plain`` instead;
* ``--nomagic``: use ``--assert=plain`` instead;
* ``--report``: use ``-r`` instead;
.. _#607: https://github.com/pytest-dev/pytest/issues/607 Thanks to `@RedBeardCode`_ for the PR (`#1664`_).
.. _#1516: https://github.com/pytest-dev/pytest/issues/1516
.. _#1519: https://github.com/pytest-dev/pytest/pull/1519
.. _#1664: https://github.com/pytest-dev/pytest/pull/1664
.. _#1627: https://github.com/pytest-dev/pytest/pull/1627
* ImportErrors in plugins now are a fatal error instead of issuing a
pytest warning (`#1479`_). Thanks to `@The-Compiler`_ for the PR.
2.10.0.dev1 * 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** **New Features**
* Support nose-style ``__test__`` attribute on methods of classes, * Support nose-style ``__test__`` attribute on methods of classes,
including unittest-style Classes. If set to False, the test will not be including unittest-style Classes. If set to ``False``, the test will not be
collected. collected.
* New ``doctest_namespace`` fixture for injecting names into the * New ``doctest_namespace`` fixture for injecting names into the
namespace in which your doctests run. namespace in which doctests run.
Thanks `@milliams`_ for the complete PR (`#1428`_). Thanks `@milliams`_ for the complete PR (`#1428`_).
* New ``name`` argument to ``pytest.fixture`` mark, which allows a custom name * New ``name`` argument to ``pytest.fixture`` decorator which allows a custom name
for a fixture (to solve the funcarg-shadowing-fixture problem). for a fixture (to solve the funcarg-shadowing-fixture problem).
Thanks `@novas0x2a`_ for the complete PR (`#1444`_). Thanks `@novas0x2a`_ for the complete PR (`#1444`_).
@ -57,88 +78,112 @@
tests. tests.
Thanks `@kalekundert`_ for the complete PR (`#1441`_). Thanks `@kalekundert`_ for the complete PR (`#1441`_).
* New Add ability to add global properties in the final xunit output file. * 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`_). Thanks `@tareqalayan`_ for the complete PR `#1454`_).
* New ``ExceptionInfo.match()`` method to match a regular expression on the * New ``ExceptionInfo.match()`` method to match a regular expression on the
string representation of an exception. Closes proposal `#372`_. string representation of an exception (`#372`_).
Thanks `@omarkohl`_ for the complete PR (`#1502`_) and `@nicoddemus`_ for the Thanks `@omarkohl`_ for the complete PR (`#1502`_).
implementation tips.
* ``__tracebackhide__`` can now also be set to a callable which then can decide * ``__tracebackhide__`` can now also be set to a callable which then can decide
whether to filter the traceback based on the ``ExceptionInfo`` object passed whether to filter the traceback based on the ``ExceptionInfo`` object passed
to it. to it. Thanks `@The-Compiler`_ for the complete PR (`#1526`_).
* New ``pytest_make_parametrize_id`` hook. * 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. Thanks `@palaviv`_ for the PR.
* ``capsys`` and ``capfd`` now have a ``disabled()`` method, which is a context manager * ``capsys`` and ``capfd`` now have a ``disabled()`` context-manager method, which
that can be used to temporarily disable capture within a test. can be used to temporarily disable capture within a test.
Thanks `@nicoddemus`_ for the PR. Thanks `@nicoddemus`_ for the PR.
* New cli flag ``--fixtures-per-test`` that shows which fixtures are being used * New cli flag ``--fixtures-per-test``: shows which fixtures are being used
for each selected test item. Features doc strings of fixtures by default. for each selected test item. Features doc strings of fixtures by default.
Can also show where fixtures are defined if combined with ``-v``. Can also show where fixtures are defined if combined with ``-v``.
Thanks `@hackebrot`_ for the PR. Thanks `@hackebrot`_ for the PR.
* Introduce pytest command as recommended entry point. Closes proposal * 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 `#1629`_. Thanks `@obestwalter`_ and `@davehunt`_ for the complete PR
(`#1633`_) (`#1633`_).
* New cli flags: (1) ``--setup-plan`` performs normal collection and reports * New cli flags:
the potential setup and teardown, does not execute any fixtures and tests (2)
``--setup-only`` performs normal collection, executes setup and teardown of
fixtures and reports them. Thanks `@d6e`_, `@kvas-it`_, `@sallner`_
and `@omarkohl`_ for the PR.
* Added two new hooks: ``pytest_fixture_setup`` which executes the fixture + ``--setup-plan``: performs normal collection and reports
setup and ``pytest_fixture_post_finalizer`` which is called after the fixture's the potential setup and teardown and does not execute any fixtures and tests;
finalizer and has access to the fixture's result cache. + ``--setup-only``: performs normal collection, executes setup and teardown of
Thanks `@d6e`_, `@sallner`_ fixtures and reports them;
+ ``--setup-show``: performs normal test execution and additionally shows
setup and teardown of fixtures;
* Issue a warning for asserts whose test is a tuple literal. Such asserts will Thanks `@d6e`_, `@kvas-it`_, `@sallner`_ 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 never fail because tuples are always truthy and are usually a mistake
(see `#1562`_). Thanks `@kvas-it`_, for the PR. (see `#1562`_). Thanks `@kvas-it`_, for the PR.
* New cli flag ``--override-ini`` or ``-o`` that overrides values from the ini file. * Allow passing a custom debugger class (e.g. ``--pdbcls=IPython.core.debugger:Pdb``).
Example '-o xfail_strict=True'. A complete ini-options can be viewed Thanks to `@anntzer`_ for the PR.
by py.test --help. Thanks `@blueyed`_ and `@fengxx`_ for the PR
* Remove 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. *
*
**Changes** **Changes**
* Plugins now benefit from assertion rewriting. Thanks
`@sober7`_, `@nicoddemus`_ and `@flub`_ for the PR.
* Fixtures marked with ``@pytest.fixture`` can now use ``yield`` statements exactly like * Fixtures marked with ``@pytest.fixture`` can now use ``yield`` statements exactly like
those marked with the ``@pytest.yield_fixture`` decorator. This change renders those marked with the ``@pytest.yield_fixture`` decorator. This change renders
``@pytest.yield_fixture`` deprecated and makes ``@pytest.fixture`` with ``yield`` statements ``@pytest.yield_fixture`` deprecated and makes ``@pytest.fixture`` with ``yield`` statements
the preferred way to write teardown code (`#1461`_). the preferred way to write teardown code (`#1461`_).
Thanks `@csaftoiu`_ for bringing this to attention and `@nicoddemus`_ for the PR. Thanks `@csaftoiu`_ for bringing this to attention and `@nicoddemus`_ for the PR.
* Fix (`#1351`_): * Explicitly passed parametrize ids do not get escaped to ascii (`#1351`_).
explicitly passed parametrize ids do not get escaped to ascii.
Thanks `@ceridwen`_ for the PR. Thanks `@ceridwen`_ for the PR.
* parametrize ids can accept None as specific test id. The * Parametrize ids can accept ``None`` as specific test id, in which case the
automatically generated id for that argument will be used. automatically generated id for that argument will be used.
Thanks `@palaviv`_ for the complete PR (`#1468`_). Thanks `@palaviv`_ for the complete PR (`#1468`_).
* improved idmaker name selection in case of duplicate ids in * 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. parametrize.
Thanks `@palaviv`_ for the complete PR (`#1474`_). Thanks `@palaviv`_ for the complete PR (`#1474`_).
* Fix `#1426`_ Make ImportError during collection more explicit by reminding * Now pytest warnings summary is shown up by default. Added a new flag
the user to check the name of the test module/package(s). ``--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`_). Thanks `@omarkohl`_ for the complete PR (`#1520`_).
* Add ``build/`` and ``dist/`` to the default ``--norecursedirs`` list. Thanks * Add ``build/`` and ``dist/`` to the default ``--norecursedirs`` list. Thanks
`@mikofski`_ for the report and `@tomviner`_ for the PR (`#1544`_). `@mikofski`_ for the report and `@tomviner`_ for the PR (`#1544`_).
* pytest.raises in the context manager form accepts a custom * ``pytest.raises`` in the context manager form accepts a custom
message to raise when no exception occurred. ``message`` to raise when no exception occurred.
Thanks `@palaviv`_ for the complete PR (`#1616`_). Thanks `@palaviv`_ for the complete PR (`#1616`_).
* ``conftest.py`` files now benefit from assertion rewriting; previously it * ``conftest.py`` files now benefit from assertion rewriting; previously it
@ -148,102 +193,150 @@
* Text documents without any doctests no longer appear as "skipped". * Text documents without any doctests no longer appear as "skipped".
Thanks `@graingert`_ for reporting and providing a full PR (`#1580`_). Thanks `@graingert`_ for reporting and providing a full PR (`#1580`_).
* Fix internal error issue when ``method`` argument is missing for
``teardown_method()``. Fixes (`#1605`_).
* Fix exception visualization in case the current working directory (CWD) gets
deleted during testing. Fixes (`#1235`). Thanks `@bukzor`_ for reporting. PR by
`@marscher`. Thanks `@nicoddemus`_ for his help.
* Ensure that a module within a namespace package can be found when it * Ensure that a module within a namespace package can be found when it
is specified on the command line together with the ``--pyargs`` is specified on the command line together with the ``--pyargs``
option. Thanks to `@taschini`_ for the PR (`#1597`_). option. Thanks to `@taschini`_ for the PR (`#1597`_).
* Raise helpful failure message, when requesting parametrized fixture at runtime, * Always include full assertion explanation during assertion rewriting. The previous behaviour was hiding
e.g. with ``request.getfuncargvalue``. BACKWARD INCOMPAT: Previously these params sub-expressions that happened to be ``False``, assuming this was redundant information.
were simply never defined. So a fixture decorated like ``@pytest.fixture(params=[0, 1, 2])`` Thanks `@bagerard`_ for reporting (`#1503`_). Thanks to `@davehunt`_ and
only ran once. Now a failure is raised. Fixes (`#460`_). Thanks to `@tomviner`_ for the PR.
`@nikratio`_ for bug report, `@RedBeardCode`_ and `@tomviner`_ for PR.
* Create correct diff for strings ending with newlines. Fixes (`#1553`_).
Thanks `@Vogtinator`_ for reporting. Thanks to `@RedBeardCode`_ and
`@tomviner`_ for PR.
* Add proposal to docs for a new feature that enables users to combine multiple
fixtures into one. Thanks to `@hpk42`_ and `@hackebrot`_.
.. _#1632: https://github.com/pytest-dev/pytest/issues/1632
.. _#1580: https://github.com/pytest-dev/pytest/pull/1580
.. _#1605: https://github.com/pytest-dev/pytest/issues/1605
.. _#1597: https://github.com/pytest-dev/pytest/pull/1597
.. _#460: https://github.com/pytest-dev/pytest/pull/460
.. _#1553: https://github.com/pytest-dev/pytest/issues/1553
.. _@graingert: https://github.com/graingert
.. _@taschini: https://github.com/taschini
.. _@nikratio: https://github.com/nikratio
.. _@RedBeardCode: https://github.com/RedBeardCode
.. _@Vogtinator: https://github.com/Vogtinator
.. _@blueyed: https://github.com/blueyed
.. _@fengxx: https://github.com/fengxx
* Fix `#1421`_: Exit tests if a collection error occurs and add
``--continue-on-collection-errors`` option to restore previous behaviour.
Thanks `@olegpidsadnyi`_ and `@omarkohl`_ for the complete PR (`#1628`_).
*
* ``OptionGroup.addoption()`` now checks if option names were already * ``OptionGroup.addoption()`` now checks if option names were already
added before, to make it easier to track down issues like `#1618`_. added before, to make it easier to track down issues like `#1618`_.
Before, you only got exceptions later from ``argparse`` library, Before, you only got exceptions later from ``argparse`` library,
giving no clue about the actual reason for double-added options. giving no clue about the actual reason for double-added options.
.. _@milliams: https://github.com/milliams * ``yield``-based tests are considered deprecated and will be removed in pytest-4.0.
.. _@csaftoiu: https://github.com/csaftoiu Thanks `@nicoddemus`_ for the PR.
.. _@flub: https://github.com/flub
.. _@novas0x2a: https://github.com/novas0x2a
.. _@kalekundert: https://github.com/kalekundert
.. _@tareqalayan: https://github.com/tareqalayan
.. _@ceridwen: https://github.com/ceridwen
.. _@palaviv: https://github.com/palaviv
.. _@omarkohl: https://github.com/omarkohl
.. _@mikofski: https://github.com/mikofski
.. _@sober7: https://github.com/sober7
.. _@olegpidsadnyi: https://github.com/olegpidsadnyi
.. _@obestwalter: https://github.com/obestwalter
.. _@davehunt: https://github.com/davehunt
.. _@sallner: https://github.com/sallner
.. _@d6e: https://github.com/d6e
.. _@kvas-it: https://github.com/kvas-it
.. _#1421: https://github.com/pytest-dev/pytest/issues/1421 * Using ``pytest_funcarg__`` prefix to declare fixtures is considered deprecated and will be
.. _#1426: https://github.com/pytest-dev/pytest/issues/1426 removed in pytest-4.0 (`#1684`_).
.. _#1428: https://github.com/pytest-dev/pytest/pull/1428 Thanks `@nicoddemus`_ for the PR.
.. _#1444: https://github.com/pytest-dev/pytest/pull/1444
.. _#1441: https://github.com/pytest-dev/pytest/pull/1441
.. _#1454: https://github.com/pytest-dev/pytest/pull/1454
.. _#1351: https://github.com/pytest-dev/pytest/issues/1351
.. _#1461: https://github.com/pytest-dev/pytest/pull/1461
.. _#1468: https://github.com/pytest-dev/pytest/pull/1468
.. _#1474: https://github.com/pytest-dev/pytest/pull/1474
.. _#1502: https://github.com/pytest-dev/pytest/pull/1502
.. _#1520: https://github.com/pytest-dev/pytest/pull/1520
.. _#1619: https://github.com/pytest-dev/pytest/issues/1619
.. _#372: https://github.com/pytest-dev/pytest/issues/372
.. _#1544: https://github.com/pytest-dev/pytest/issues/1544
.. _#1562: https://github.com/pytest-dev/pytest/issues/1562
.. _#1616: https://github.com/pytest-dev/pytest/pull/1616
.. _#1628: https://github.com/pytest-dev/pytest/pull/1628
.. _#1629: https://github.com/pytest-dev/pytest/issues/1629
.. _#1633: https://github.com/pytest-dev/pytest/pull/1633
.. _#1618: https://github.com/pytest-dev/pytest/issues/1618
* 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`_).
*
*
*
**Bug Fixes** **Bug Fixes**
* When receiving identical test ids in parametrize we generate unique test ids. * 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.
*
*
*
*
*
.. _#372: https://github.com/pytest-dev/pytest/issues/372
.. _#460: https://github.com/pytest-dev/pytest/pull/460
.. _#457: https://github.com/pytest-dev/pytest/issues/457
.. _#1740: https://github.com/pytest-dev/pytest/issues/1740
.. _#607: https://github.com/pytest-dev/pytest/issues/607
.. _#925: https://github.com/pytest-dev/pytest/issues/925
.. _#1235: https://github.com/pytest-dev/pytest/issues/1235
.. _#1351: https://github.com/pytest-dev/pytest/issues/1351
.. _#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
.. _#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
.. _#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
.. _#1544: https://github.com/pytest-dev/pytest/issues/1544
.. _#1553: https://github.com/pytest-dev/pytest/issues/1553
.. _#1562: https://github.com/pytest-dev/pytest/issues/1562
.. _#1580: https://github.com/pytest-dev/pytest/pull/1580
.. _#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
.. _#1668: https://github.com/pytest-dev/pytest/issues/1668
.. _#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
.. _#1684: https://github.com/pytest-dev/pytest/pull/1684
.. _#1723: https://github.com/pytest-dev/pytest/pull/1723
.. _@DRMacIver: https://github.com/DRMacIver
.. _@RedBeardCode: https://github.com/RedBeardCode
.. _@Vogtinator: https://github.com/Vogtinator
.. _@anntzer: https://github.com/anntzer
.. _@bagerard: https://github.com/bagerard
.. _@blueyed: https://github.com/blueyed
.. _@ceridwen: https://github.com/ceridwen
.. _@csaftoiu: https://github.com/csaftoiu
.. _@d6e: https://github.com/d6e
.. _@davehunt: https://github.com/davehunt
.. _@fengxx: https://github.com/fengxx
.. _@flub: https://github.com/flub
.. _@graingert: https://github.com/graingert
.. _@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
.. _@sallner: https://github.com/sallner
.. _@sober7: https://github.com/sober7
.. _@tareqalayan: https://github.com/tareqalayan
.. _@taschini: https://github.com/taschini
.. _@txomon: https://github.com/txomon
2.9.2 2.9.2
===== =====

View File

@ -120,6 +120,8 @@ the following:
- an issue tracker for bug reports and enhancement requests. - an issue tracker for bug reports and enhancement requests.
- a `changelog <http://keepachangelog.com/>`_
If no contributor strongly objects and two agree, the repository can then be If no contributor strongly objects and two agree, the repository can then be
transferred to the ``pytest-dev`` organisation. transferred to the ``pytest-dev`` organisation.

View File

@ -4,9 +4,6 @@ from .code import ExceptionInfo # noqa
from .code import Frame # noqa from .code import Frame # noqa
from .code import Traceback # noqa from .code import Traceback # noqa
from .code import getrawcode # 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 Source # noqa
from .source import compile_ as compile # noqa from .source import compile_ as compile # noqa
from .source import getfslineno # noqa from .source import getfslineno # noqa

View File

@ -179,18 +179,6 @@ class TracebackEntry(object):
return self.frame.f_locals return self.frame.f_locals
locals = property(getlocals, None, None, "locals of underlaying frame") 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): def getfirstlinesource(self):
# on Jython this firstlineno can be -1 apparently # on Jython this firstlineno can be -1 apparently
return max(self.frame.code.firstlineno, 0) return max(self.frame.code.firstlineno, 0)
@ -830,29 +818,6 @@ class ReprFuncArgs(TerminalRepr):
tw.line("") 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): def getrawcode(obj, trycall=True):
""" return code object for given function. """ """ return code object for given function. """
try: try:

View File

@ -5,9 +5,8 @@ import py
import os import os
import sys import sys
from _pytest.config import hookimpl
from _pytest.monkeypatch import monkeypatch
from _pytest.assertion import util from _pytest.assertion import util
from _pytest.assertion import rewrite
def pytest_addoption(parser): def pytest_addoption(parser):
@ -15,16 +14,42 @@ def pytest_addoption(parser):
group.addoption('--assert', group.addoption('--assert',
action="store", action="store",
dest="assertmode", dest="assertmode",
choices=("rewrite", "reinterp", "plain",), choices=("rewrite", "plain",),
default="rewrite", default="rewrite",
metavar="MODE", metavar="MODE",
help="""control assertion debugging tools. 'plain' help="""Control assertion debugging tools. 'plain'
performs no assertion debugging. 'reinterp' performs no assertion debugging. 'rewrite'
reinterprets assert statements after they failed (the default) rewrites assert statements in
to provide assertion expression information. test modules on import to provide assert
'rewrite' (the default) rewrites assert expression information.""")
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 a module name to be rewritten on import.
This function will make sure that the module will get it's assert
statements rewritten when it is imported. 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.
"""
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: class AssertionState:
@ -33,55 +58,37 @@ class AssertionState:
def __init__(self, config, mode): def __init__(self, config, mode):
self.mode = mode self.mode = mode
self.trace = config.trace.root.get("assertion") self.trace = config.trace.root.get("assertion")
self.hook = None
@hookimpl(tryfirst=True) def install_importhook(config):
def pytest_load_initial_conftests(early_config, parser, args): """Try to install the rewrite hook, raise SystemError if it fails."""
ns, ns_unknown_args = parser.parse_known_and_unknown_args(args) # Both Jython and CPython 2.6.0 have AST bugs that make the
mode = ns.assertmode # assertion rewriting hook malfunction.
if mode == "rewrite": if (sys.platform.startswith('java') or
try: sys.version_info[:3] == (2, 6, 0)):
import ast # noqa raise SystemError('rewrite not supported')
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"
early_config._assertstate = AssertionState(early_config, mode) config._assertstate = AssertionState(config, 'rewrite')
warn_about_missing_assertion(mode, early_config.pluginmanager) config._assertstate.hook = hook = rewrite.AssertionRewritingHook(config)
sys.meta_path.insert(0, hook)
if mode != "plain": config._assertstate.trace('installed rewrite import hook')
_load_modules(mode)
m = monkeypatch()
early_config._cleanup.append(m.undo)
m.setattr(py.builtin.builtins, 'AssertionError',
reinterpret.AssertionError) # noqa
hook = None
if mode == "rewrite":
hook = rewrite.AssertionRewritingHook(early_config) # noqa
sys.meta_path.insert(0, hook)
early_config._assertstate.hook = hook
early_config._assertstate.trace("configured with mode set to %r" % (mode,))
def undo(): def undo():
hook = early_config._assertstate.hook hook = config._assertstate.hook
if hook is not None and hook in sys.meta_path: if hook is not None and hook in sys.meta_path:
sys.meta_path.remove(hook) sys.meta_path.remove(hook)
early_config.add_cleanup(undo) config.add_cleanup(undo)
return hook
def pytest_collection(session): def pytest_collection(session):
# this hook is only called when test modules are collected # this hook is only called when test modules are collected
# so for example not in the master process of pytest-xdist # so for example not in the master process of pytest-xdist
# (which does not collect test modules) # (which does not collect test modules)
hook = session.config._assertstate.hook assertstate = getattr(session.config, '_assertstate', None)
if hook is not None: if assertstate:
hook.set_session(session) if assertstate.hook is not None:
assertstate.hook.set_session(session)
def _running_on_ci(): def _running_on_ci():
@ -138,43 +145,10 @@ def pytest_runtest_teardown(item):
def pytest_sessionfinish(session): def pytest_sessionfinish(session):
hook = session.config._assertstate.hook assertstate = getattr(session.config, '_assertstate', None)
if hook is not None: if assertstate:
hook.session = None if assertstate.hook is not None:
assertstate.hook.set_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, pluginmanager):
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"
# temporarily disable capture so we can print our warning
capman = pluginmanager.getplugin('capturemanager')
try:
out, err = capman.suspendcapture()
sys.stderr.write("WARNING: " + specifically +
" because assert statements are not executed "
"by the underlying Python interpreter "
"(are you using python -O?)\n")
finally:
capman.resumecapture()
sys.stdout.write(out)
sys.stderr.write(err)
# Expose this plugin's implementation for the pytest_assertrepr_compare hook # Expose this plugin's implementation for the pytest_assertrepr_compare hook

View File

@ -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 "<run>".
if source:
self.msg = reinterpret(source, f, should_fail=True)
else:
self.msg = "<could not determine information>"
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, "<assertion interpretation>", 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

View File

@ -1,6 +1,7 @@
"""Rewrite assertion AST to produce nice error messages""" """Rewrite assertion AST to produce nice error messages"""
import ast import ast
import _ast
import errno import errno
import itertools import itertools
import imp import imp
@ -50,6 +51,7 @@ class AssertionRewritingHook(object):
self.session = None self.session = None
self.modules = {} self.modules = {}
self._register_with_pkg_resources() self._register_with_pkg_resources()
self._must_rewrite = set()
def set_session(self, session): def set_session(self, session):
self.session = session self.session = session
@ -86,7 +88,7 @@ class AssertionRewritingHook(object):
fn = os.path.join(pth, name.rpartition(".")[2] + ".py") fn = os.path.join(pth, name.rpartition(".")[2] + ".py")
fn_pypath = py.path.local(fn) fn_pypath = py.path.local(fn)
if not self._should_rewrite(fn_pypath, state): if not self._should_rewrite(name, fn_pypath, state):
return None return None
# The requested module looks like a test file, so rewrite it. This is # The requested module looks like a test file, so rewrite it. This is
@ -136,7 +138,7 @@ class AssertionRewritingHook(object):
self.modules[name] = co, pyc self.modules[name] = co, pyc
return self return self
def _should_rewrite(self, fn_pypath, state): def _should_rewrite(self, name, fn_pypath, state):
# always rewrite conftest files # always rewrite conftest files
fn = str(fn_pypath) fn = str(fn_pypath)
if fn_pypath.basename == 'conftest.py': if fn_pypath.basename == 'conftest.py':
@ -160,8 +162,29 @@ class AssertionRewritingHook(object):
finally: finally:
self.session = session self.session = session
del session del session
else:
for marked in self._must_rewrite:
if marked.startswith(name):
return True
return False 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:
self._warn_already_imported(already_imported)
self._must_rewrite.update(names)
def _warn_already_imported(self, names):
self.config.warn(
'P1',
'Modules are already imported so can not be re-written: %s' %
','.join(names))
def load_module(self, name): def load_module(self, name):
# If there is an existing module object named 'fullname' in # If there is an existing module object named 'fullname' in
# sys.modules, the loader must use that existing module. (Otherwise, # sys.modules, the loader must use that existing module. (Otherwise,
@ -876,6 +899,8 @@ class AssertionRewriter(ast.NodeVisitor):
def visit_Compare(self, comp): def visit_Compare(self, comp):
self.push_format_context() self.push_format_context()
left_res, left_expl = self.visit(comp.left) 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))] res_variables = [self.variable() for i in range(len(comp.ops))]
load_names = [ast.Name(v, ast.Load()) for v in res_variables] load_names = [ast.Name(v, ast.Load()) for v in res_variables]
store_names = [ast.Name(v, ast.Store()) for v in res_variables] store_names = [ast.Name(v, ast.Store()) for v in res_variables]
@ -885,6 +910,8 @@ class AssertionRewriter(ast.NodeVisitor):
results = [left_res] results = [left_res]
for i, op, next_operand in it: for i, op, next_operand in it:
next_res, next_expl = self.visit(next_operand) 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) results.append(next_res)
sym = binop_map[op.__class__] sym = binop_map[op.__class__]
syms.append(ast.Str(sym)) syms.append(ast.Str(sym))

View File

@ -38,44 +38,11 @@ def format_explanation(explanation):
displaying diffs. displaying diffs.
""" """
explanation = ecu(explanation) explanation = ecu(explanation)
explanation = _collapse_false(explanation)
lines = _split_explanation(explanation) lines = _split_explanation(explanation)
result = _format_lines(lines) result = _format_lines(lines)
return u('\n').join(result) 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): def _split_explanation(explanation):
"""Return a list of individual lines in the explanation """Return a list of individual lines in the explanation

203
_pytest/compat.py Normal file
View File

@ -0,0 +1,203 @@
"""
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()
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):
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 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

View File

@ -5,11 +5,13 @@ import traceback
import types import types
import warnings import warnings
import pkg_resources
import py import py
# DON't import pytest here because it causes import cycle troubles # DON't import pytest here because it causes import cycle troubles
import sys, os import sys, os
import _pytest._code import _pytest._code
import _pytest.hookspec # the extension point definitions import _pytest.hookspec # the extension point definitions
import _pytest.assertion
from _pytest._pluggy import PluginManager, HookimplMarker, HookspecMarker from _pytest._pluggy import PluginManager, HookimplMarker, HookspecMarker
hookimpl = HookimplMarker("pytest") hookimpl = HookimplMarker("pytest")
@ -69,7 +71,7 @@ class UsageError(Exception):
_preinit = [] _preinit = []
default_plugins = ( default_plugins = (
"mark main terminal runner python pdb unittest capture skipping " "mark main terminal runner python fixtures debugging unittest capture skipping "
"tmpdir monkeypatch recwarn pastebin helpconfig nose assertion " "tmpdir monkeypatch recwarn pastebin helpconfig nose assertion "
"junitxml resultlog doctest cacheprovider freeze_support " "junitxml resultlog doctest cacheprovider freeze_support "
"setuponly setupplan").split() "setuponly setupplan").split()
@ -104,6 +106,7 @@ def get_plugin_manager():
return get_config().pluginmanager return get_config().pluginmanager
def _prepareconfig(args=None, plugins=None): def _prepareconfig(args=None, plugins=None):
warning = None
if args is None: if args is None:
args = sys.argv[1:] args = sys.argv[1:]
elif isinstance(args, py.path.local): elif isinstance(args, py.path.local):
@ -112,6 +115,10 @@ def _prepareconfig(args=None, plugins=None):
if not isinstance(args, str): if not isinstance(args, str):
raise ValueError("not a string or argument list: %r" % (args,)) raise ValueError("not a string or argument list: %r" % (args,))
args = shlex.split(args, posix=sys.platform != "win32") args = shlex.split(args, posix=sys.platform != "win32")
# we want to remove this way of passing arguments to pytest.main()
# in pytest-4.0
warning = ('passing a string to pytest.main() is deprecated, '
'pass a list of arguments instead.')
config = get_config() config = get_config()
pluginmanager = config.pluginmanager pluginmanager = config.pluginmanager
try: try:
@ -121,6 +128,8 @@ def _prepareconfig(args=None, plugins=None):
pluginmanager.consider_pluginarg(plugin) pluginmanager.consider_pluginarg(plugin)
else: else:
pluginmanager.register(plugin) pluginmanager.register(plugin)
if warning:
config.warn('C1', warning)
return pluginmanager.hook.pytest_cmdline_parse( return pluginmanager.hook.pytest_cmdline_parse(
pluginmanager=pluginmanager, args=args) pluginmanager=pluginmanager, args=args)
except BaseException: except BaseException:
@ -159,6 +168,9 @@ class PytestPluginManager(PluginManager):
self.trace.root.setwriter(err.write) self.trace.root.setwriter(err.write)
self.enable_tracing() 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): def addhooks(self, module_or_class):
""" """
.. deprecated:: 2.8 .. deprecated:: 2.8
@ -367,7 +379,9 @@ class PytestPluginManager(PluginManager):
self._import_plugin_specs(os.environ.get("PYTEST_PLUGINS")) self._import_plugin_specs(os.environ.get("PYTEST_PLUGINS"))
def consider_module(self, mod): def consider_module(self, mod):
self._import_plugin_specs(getattr(mod, "pytest_plugins", None)) plugins = getattr(mod, 'pytest_plugins', [])
self.rewrite_hook.mark_rewrite(*plugins)
self._import_plugin_specs(plugins)
def _import_plugin_specs(self, spec): def _import_plugin_specs(self, spec):
if spec: if spec:
@ -544,13 +558,18 @@ class ArgumentError(Exception):
class Argument: 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 = { _typ_map = {
'int': int, 'int': int,
'string': str, 'string': str,
} 'float': float,
# enable after some grace period for plugin writers 'complex': complex,
TYPE_WARN = False }
def __init__(self, *names, **attrs): def __init__(self, *names, **attrs):
"""store parms in private vars for use in add_argument""" """store parms in private vars for use in add_argument"""
@ -558,17 +577,12 @@ class Argument:
self._short_opts = [] self._short_opts = []
self._long_opts = [] self._long_opts = []
self.dest = attrs.get('dest') self.dest = attrs.get('dest')
if self.TYPE_WARN: if '%default' in (attrs.get('help') or ''):
try: warnings.warn(
help = attrs['help'] 'pytest now uses argparse. "%default" should be'
if '%default' in help: ' changed to "%(default)s" ',
warnings.warn( DeprecationWarning,
'pytest now uses argparse. "%default" should be' stacklevel=3)
' changed to "%(default)s" ',
FutureWarning,
stacklevel=3)
except KeyError:
pass
try: try:
typ = attrs['type'] typ = attrs['type']
except KeyError: except KeyError:
@ -577,25 +591,23 @@ class Argument:
# this might raise a keyerror as well, don't want to catch that # this might raise a keyerror as well, don't want to catch that
if isinstance(typ, py.builtin._basestring): if isinstance(typ, py.builtin._basestring):
if typ == 'choice': if typ == 'choice':
if self.TYPE_WARN: warnings.warn(
warnings.warn( 'type argument to addoption() is a string %r.'
'type argument to addoption() is a string %r.' ' For parsearg this is optional and when supplied '
' For parsearg this is optional and when supplied ' ' should be a type.'
' should be a type.' ' (options: %s)' % (typ, names),
' (options: %s)' % (typ, names), DeprecationWarning,
FutureWarning, stacklevel=3)
stacklevel=3)
# argparse expects a type here take it from # argparse expects a type here take it from
# the type of the first element # the type of the first element
attrs['type'] = type(attrs['choices'][0]) attrs['type'] = type(attrs['choices'][0])
else: else:
if self.TYPE_WARN: warnings.warn(
warnings.warn( 'type argument to addoption() is a string %r.'
'type argument to addoption() is a string %r.' ' For parsearg this should be a type.'
' For parsearg this should be a type.' ' (options: %s)' % (typ, names),
' (options: %s)' % (typ, names), DeprecationWarning,
FutureWarning, stacklevel=3)
stacklevel=3)
attrs['type'] = Argument._typ_map[typ] attrs['type'] = Argument._typ_map[typ]
# used in test_parseopt -> test_parse_defaultgetter # used in test_parseopt -> test_parse_defaultgetter
self.type = attrs['type'] self.type = attrs['type']
@ -662,20 +674,17 @@ class Argument:
self._long_opts.append(opt) self._long_opts.append(opt)
def __repr__(self): def __repr__(self):
retval = 'Argument(' args = []
if self._short_opts: if self._short_opts:
retval += '_short_opts: ' + repr(self._short_opts) + ', ' args += ['_short_opts: ' + repr(self._short_opts)]
if self._long_opts: if self._long_opts:
retval += '_long_opts: ' + repr(self._long_opts) + ', ' args += ['_long_opts: ' + repr(self._long_opts)]
retval += 'dest: ' + repr(self.dest) + ', ' args += ['dest: ' + repr(self.dest)]
if hasattr(self, 'type'): if hasattr(self, 'type'):
retval += 'type: ' + repr(self.type) + ', ' args += ['type: ' + repr(self.type)]
if hasattr(self, 'default'): if hasattr(self, 'default'):
retval += 'default: ' + repr(self.default) + ', ' args += ['default: ' + repr(self.default)]
if retval[-2:] == ', ': # always long enough to test ("Argument(" ) return 'Argument({0})'.format(', '.join(args))
retval = retval[:-2]
retval += ')'
return retval
class OptionGroup: class OptionGroup:
@ -927,17 +936,62 @@ class Config(object):
self._parser.addini('addopts', 'extra command line options', 'args') self._parser.addini('addopts', 'extra command line options', 'args')
self._parser.addini('minversion', 'minimally required pytest version') 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=<mode> 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:
self.pluginmanager.rewrite_hook = hook
for entrypoint in pkg_resources.iter_entry_points('pytest11'):
for entry in entrypoint.dist._get_metadata('RECORD'):
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): def _preparse(self, args, addopts=True):
self._initini(args) self._initini(args)
if addopts: if addopts:
args[:] = shlex.split(os.environ.get('PYTEST_ADDOPTS', '')) + args args[:] = shlex.split(os.environ.get('PYTEST_ADDOPTS', '')) + args
args[:] = self.getini("addopts") + args args[:] = self.getini("addopts") + args
self._checkversion() self._checkversion()
entrypoint_name = 'pytest11'
self._consider_importhook(args, entrypoint_name)
self.pluginmanager.consider_preparse(args) self.pluginmanager.consider_preparse(args)
try: self.pluginmanager.load_setuptools_entrypoints(entrypoint_name)
self.pluginmanager.load_setuptools_entrypoints("pytest11")
except ImportError as e:
self.warn("I2", "could not load setuptools entry import: %s" % (e,))
self.pluginmanager.consider_env() self.pluginmanager.consider_env()
self.known_args_namespace = ns = self._parser.parse_known_args(args, namespace=self.option.copy()) self.known_args_namespace = ns = self._parser.parse_known_args(args, namespace=self.option.copy())
if self.known_args_namespace.confcutdir is None and self.inifile: if self.known_args_namespace.confcutdir is None and self.inifile:

View File

@ -8,21 +8,33 @@ import pytest
def pytest_addoption(parser): def pytest_addoption(parser):
group = parser.getgroup("general") group = parser.getgroup("general")
group._addoption('--pdb', group._addoption(
action="store_true", dest="usepdb", default=False, '--pdb', dest="usepdb", action="store_true",
help="start the interactive Python debugger on errors.") 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.core.debugger:Pdb")
def pytest_namespace(): def pytest_namespace():
return {'set_trace': pytestPDB().set_trace} return {'set_trace': pytestPDB().set_trace}
def pytest_configure(config): def pytest_configure(config):
if config.getvalue("usepdb"): if config.getvalue("usepdb") or config.getvalue("usepdb_cls"):
config.pluginmanager.register(PdbInvoke(), 'pdbinvoke') 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) old = (pdb.set_trace, pytestPDB._pluginmanager)
def fin(): def fin():
pdb.set_trace, pytestPDB._pluginmanager = old pdb.set_trace, pytestPDB._pluginmanager = old
pytestPDB._config = None pytestPDB._config = None
pytestPDB._pdb_cls = pdb.Pdb
pdb.set_trace = pytest.set_trace pdb.set_trace = pytest.set_trace
pytestPDB._pluginmanager = config.pluginmanager pytestPDB._pluginmanager = config.pluginmanager
pytestPDB._config = config pytestPDB._config = config
@ -32,6 +44,7 @@ class pytestPDB:
""" Pseudo PDB that defers to the real pdb. """ """ Pseudo PDB that defers to the real pdb. """
_pluginmanager = None _pluginmanager = None
_config = None _config = None
_pdb_cls = pdb.Pdb
def set_trace(self): def set_trace(self):
""" invoke PDB set_trace debugging, dropping any IO capturing. """ """ invoke PDB set_trace debugging, dropping any IO capturing. """
@ -45,7 +58,7 @@ class pytestPDB:
tw.line() tw.line()
tw.sep(">", "PDB set_trace (IO-capturing turned off)") tw.sep(">", "PDB set_trace (IO-capturing turned off)")
self._pluginmanager.hook.pytest_enter_pdb(config=self._config) self._pluginmanager.hook.pytest_enter_pdb(config=self._config)
pdb.Pdb().set_trace(frame) self._pdb_cls().set_trace(frame)
class PdbInvoke: class PdbInvoke:
@ -98,7 +111,7 @@ def _find_last_non_hidden_frame(stack):
def post_mortem(t): def post_mortem(t):
class Pdb(pdb.Pdb): class Pdb(pytestPDB._pdb_cls):
def get_stack(self, f, t): def get_stack(self, f, t):
stack, i = pdb.Pdb.get_stack(self, f, t) stack, i = pdb.Pdb.get_stack(self, f, t)
if f is None: if f is None:

View File

@ -5,7 +5,7 @@ import traceback
import pytest import pytest
from _pytest._code.code import TerminalRepr, ReprFileLocation, ExceptionInfo from _pytest._code.code import TerminalRepr, ReprFileLocation, ExceptionInfo
from _pytest.python import FixtureRequest from _pytest.fixtures import FixtureRequest
@ -70,7 +70,7 @@ class DoctestItem(pytest.Item):
def setup(self): def setup(self):
if self.dtest is not None: if self.dtest is not None:
self.fixture_request = _setup_fixtures(self) 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.getfuncargvalue('doctest_namespace').items(): for name, value in self.fixture_request.getfuncargvalue('doctest_namespace').items():
globs[name] = value globs[name] = value
self.dtest.globs.update(globs) self.dtest.globs.update(globs)

1089
_pytest/fixtures.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -34,8 +34,8 @@ def pytest_addoption(parser):
# "**/test_*.py", "**/*_test.py"] # "**/test_*.py", "**/*_test.py"]
#) #)
group = parser.getgroup("general", "running and selection options") group = parser.getgroup("general", "running and selection options")
group._addoption('-x', '--exitfirst', action="store_true", default=False, group._addoption('-x', '--exitfirst', action="store_const",
dest="exitfirst", dest="maxfail", const=1,
help="exit instantly on first error or failed test."), help="exit instantly on first error or failed test."),
group._addoption('--maxfail', metavar="num", group._addoption('--maxfail', metavar="num",
action="store", type=int, dest="maxfail", default=0, action="store", type=int, dest="maxfail", default=0,
@ -74,10 +74,10 @@ def pytest_namespace():
collect = dict(Item=Item, Collector=Collector, File=File, Session=Session) collect = dict(Item=Item, Collector=Collector, File=File, Session=Session)
return dict(collect=collect) return dict(collect=collect)
def pytest_configure(config): def pytest_configure(config):
pytest.config = config # compatibiltiy pytest.config = config # compatibiltiy
if config.option.exitfirst:
config.option.maxfail = 1
def wrap_session(config, doit): def wrap_session(config, doit):
"""Skeleton command line program""" """Skeleton command line program"""

View File

@ -5,11 +5,14 @@ import re
from py.builtin import _basestring from py.builtin import _basestring
import pytest
RE_IMPORT_ERROR_NAME = re.compile("^No module named (.*)$") RE_IMPORT_ERROR_NAME = re.compile("^No module named (.*)$")
def pytest_funcarg__monkeypatch(request): @pytest.fixture
"""The returned ``monkeypatch`` funcarg provides these def monkeypatch(request):
"""The returned ``monkeypatch`` fixture provides these
helper methods to modify objects, dictionaries or os.environ:: helper methods to modify objects, dictionaries or os.environ::
monkeypatch.setattr(obj, name, value, raising=True) monkeypatch.setattr(obj, name, value, raising=True)
@ -26,7 +29,7 @@ def pytest_funcarg__monkeypatch(request):
parameter determines if a KeyError or AttributeError parameter determines if a KeyError or AttributeError
will be raised if the set/deletion operation has no target. will be raised if the set/deletion operation has no target.
""" """
mpatch = monkeypatch() mpatch = MonkeyPatch()
request.addfinalizer(mpatch.undo) request.addfinalizer(mpatch.undo)
return mpatch return mpatch
@ -93,7 +96,7 @@ class Notset:
notset = Notset() notset = Notset()
class monkeypatch: class MonkeyPatch:
""" Object keeping a record of setattr/item/env/syspath changes. """ """ Object keeping a record of setattr/item/env/syspath changes. """
def __init__(self): def __init__(self):

View File

@ -16,6 +16,7 @@ from _pytest._code import Source
import py import py
import pytest import pytest
from _pytest.main import Session, EXIT_OK from _pytest.main import Session, EXIT_OK
from _pytest.assertion.rewrite import AssertionRewritingHook
def pytest_addoption(parser): def pytest_addoption(parser):
@ -321,7 +322,8 @@ def linecomp(request):
return LineComp() return LineComp()
def pytest_funcarg__LineMatcher(request): @pytest.fixture(name='LineMatcher')
def LineMatcher_fixture(request):
return LineMatcher return LineMatcher
@ -684,8 +686,17 @@ class Testdir:
``pytest.main()`` instance should use. ``pytest.main()`` instance should use.
:return: A :py:class:`HookRecorder` instance. :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 = [] rec = []
class Collect: class Collect:
def pytest_configure(x, config): def pytest_configure(x, config):

File diff suppressed because it is too large Load Diff

View File

@ -73,9 +73,9 @@ def runtestprotocol(item, log=True, nextitem=None):
rep = call_and_report(item, "setup", log) rep = call_and_report(item, "setup", log)
reports = [rep] reports = [rep]
if rep.passed: if rep.passed:
if item.config.option.setuponly or item.config.option.setupplan: if item.config.option.setupshow:
show_test_item(item) show_test_item(item)
else: if not item.config.option.setuponly:
reports.append(call_and_report(item, "call", log)) reports.append(call_and_report(item, "call", log))
reports.append(call_and_report(item, "teardown", log, reports.append(call_and_report(item, "teardown", log,
nextitem=nextitem)) nextitem=nextitem))

View File

@ -1,16 +1,20 @@
import pytest import pytest
import sys import sys
def pytest_addoption(parser): def pytest_addoption(parser):
group = parser.getgroup("debugconfig") group = parser.getgroup("debugconfig")
group.addoption('--setuponly', '--setup-only', action="store_true", group.addoption('--setuponly', '--setup-only', action="store_true",
help="only setup fixtures, don't execute the tests.") help="only setup fixtures, don't execute the tests.")
group.addoption('--setupshow', '--setup-show', action="store_true",
help="show setup fixtures while executing the tests.")
@pytest.hookimpl(hookwrapper=True) @pytest.hookimpl(hookwrapper=True)
def pytest_fixture_setup(fixturedef, request): def pytest_fixture_setup(fixturedef, request):
yield yield
config = request.config config = request.config
if config.option.setuponly: if config.option.setupshow:
if hasattr(request, 'param'): if hasattr(request, 'param'):
# Save the fixture parameter so ._show_fixture_action() can # Save the fixture parameter so ._show_fixture_action() can
# display it now and during the teardown (in .finish()). # display it now and during the teardown (in .finish()).
@ -18,19 +22,22 @@ def pytest_fixture_setup(fixturedef, request):
if callable(fixturedef.ids): if callable(fixturedef.ids):
fixturedef.cached_param = fixturedef.ids(request.param) fixturedef.cached_param = fixturedef.ids(request.param)
else: else:
fixturedef.cached_param = fixturedef.ids[request.param_index] fixturedef.cached_param = fixturedef.ids[
request.param_index]
else: else:
fixturedef.cached_param = request.param fixturedef.cached_param = request.param
_show_fixture_action(fixturedef, 'SETUP') _show_fixture_action(fixturedef, 'SETUP')
def pytest_fixture_post_finalizer(fixturedef): def pytest_fixture_post_finalizer(fixturedef):
if hasattr(fixturedef, "cached_result"): if hasattr(fixturedef, "cached_result"):
config = fixturedef._fixturemanager.config config = fixturedef._fixturemanager.config
if config.option.setuponly: if config.option.setupshow:
_show_fixture_action(fixturedef, 'TEARDOWN') _show_fixture_action(fixturedef, 'TEARDOWN')
if hasattr(fixturedef, "cached_param"): if hasattr(fixturedef, "cached_param"):
del fixturedef.cached_param del fixturedef.cached_param
def _show_fixture_action(fixturedef, msg): def _show_fixture_action(fixturedef, msg):
config = fixturedef._fixturemanager.config config = fixturedef._fixturemanager.config
capman = config.pluginmanager.getplugin('capturemanager') capman = config.pluginmanager.getplugin('capturemanager')
@ -57,3 +64,9 @@ def _show_fixture_action(fixturedef, msg):
capman.resumecapture() capman.resumecapture()
sys.stdout.write(out) sys.stdout.write(out)
sys.stderr.write(err) sys.stderr.write(err)
@pytest.hookimpl(tryfirst=True)
def pytest_cmdline_main(config):
if config.option.setuponly:
config.option.setupshow = True

View File

@ -1,10 +1,12 @@
import pytest import pytest
def pytest_addoption(parser): def pytest_addoption(parser):
group = parser.getgroup("debugconfig") group = parser.getgroup("debugconfig")
group.addoption('--setupplan', '--setup-plan', action="store_true", group.addoption('--setupplan', '--setup-plan', action="store_true",
help="show what fixtures and tests would be executed but don't" help="show what fixtures and tests would be executed but "
" execute anything.") "don't execute anything.")
@pytest.hookimpl(tryfirst=True) @pytest.hookimpl(tryfirst=True)
def pytest_fixture_setup(fixturedef, request): def pytest_fixture_setup(fixturedef, request):
@ -13,7 +15,9 @@ def pytest_fixture_setup(fixturedef, request):
fixturedef.cached_result = (None, None, None) fixturedef.cached_result = (None, None, None)
return fixturedef.cached_result return fixturedef.cached_result
@pytest.hookimpl(tryfirst=True) @pytest.hookimpl(tryfirst=True)
def pytest_cmdline_main(config): def pytest_cmdline_main(config):
if config.option.setupplan: if config.option.setupplan:
config.option.setuponly = True config.option.setuponly = True
config.option.setupshow = True

View File

@ -108,11 +108,7 @@ class MarkEvaluator:
def _getglobals(self): def _getglobals(self):
d = {'os': os, 'sys': sys, 'config': self.item.config} d = {'os': os, 'sys': sys, 'config': self.item.config}
func = self.item.obj d.update(self.item.obj.__globals__)
try:
d.update(func.__globals__)
except AttributeError:
d.update(func.func_globals)
return d return d
def _istrue(self): def _istrue(self):

View File

@ -20,10 +20,15 @@ def pytest_addoption(parser):
group._addoption('-q', '--quiet', action="count", group._addoption('-q', '--quiet', action="count",
dest="quiet", default=0, help="decrease verbosity."), dest="quiet", default=0, help="decrease verbosity."),
group._addoption('-r', 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, " help="show extra test summary info as specified by chars (f)ailed, "
"(E)error, (s)skipped, (x)failed, (X)passed (w)pytest-warnings " "(E)error, (s)skipped, (x)failed, (X)passed, "
"(p)passed, (P)passed with output, (a)all except pP.") "(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', group._addoption('-l', '--showlocals',
action="store_true", dest="showlocals", default=False, action="store_true", dest="showlocals", default=False,
help="show locals in tracebacks (disabled by default).") help="show locals in tracebacks (disabled by default).")
@ -52,6 +57,10 @@ def pytest_configure(config):
def getreportopt(config): def getreportopt(config):
reportopts = "" reportopts = ""
reportchars = config.option.reportchars 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: if reportchars:
for char in reportchars: for char in reportchars:
if char not in reportopts and char != 'a': if char not in reportopts and char != 'a':

View File

@ -3,7 +3,7 @@ import re
import pytest import pytest
import py import py
from _pytest.monkeypatch import monkeypatch from _pytest.monkeypatch import MonkeyPatch
class TempdirFactory: class TempdirFactory:
@ -92,7 +92,7 @@ def pytest_configure(config):
available at pytest_configure time, but ideally should be moved entirely available at pytest_configure time, but ideally should be moved entirely
to the tmpdir_factory session fixture. to the tmpdir_factory session fixture.
""" """
mp = monkeypatch() mp = MonkeyPatch()
t = TempdirFactory(config) t = TempdirFactory(config)
config._cleanup.extend([mp.undo, t.finish]) config._cleanup.extend([mp.undo, t.finish])
mp.setattr(config, '_tmpdirhandler', t, raising=False) mp.setattr(config, '_tmpdirhandler', t, raising=False)

View File

@ -5,6 +5,13 @@ environment:
# using pytestbot account as detailed here: # using pytestbot account as detailed here:
# https://www.appveyor.com/docs/build-configuration#secure-variables # https://www.appveyor.com/docs/build-configuration#secure-variables
matrix:
# create multiple jobs to execute a set of tox runs on each; this is to workaround having
# builds timing out in AppVeyor
- TOXENV: "linting,py26,py27,py33,py34,py35,pypy"
- TOXENV: "py27-pexpect,py27-xdist,py27-trial,py35-pexpect,py35-xdist,py35-trial"
- TOXENV: "py27-nobyte,doctesting,py27-cxfreeze"
install: install:
- echo Installed Pythons - echo Installed Pythons
- dir c:\Python* - dir c:\Python*

View File

@ -1,19 +1,5 @@
{% extends "!layout.html" %} {% extends "!layout.html" %}
{% block header %} {% block header %}
<div align="center" xmlns="http://www.w3.org/1999/html" style="background-color: lightgreen; padding: .5em">
<h4>
Want to help improve pytest? Please
<a href="https://www.indiegogo.com/projects/python-testing-sprint-mid-2016#/">
contribute to
</a>
or
<a href="announce/sprint2016.html">
join
</a>
our upcoming sprint in June 2016!
</h4>
</div>
{{super()}} {{super()}}
{% endblock %} {% endblock %}
{% block footer %} {% block footer %}

View File

@ -1,10 +1,5 @@
<h3>Useful Links</h3> <h3>Useful Links</h3>
<ul> <ul>
<li>
<a href="https://www.indiegogo.com/projects/python-testing-sprint-mid-2016#/">
<b>Sprint funding campaign</b>
</a>
</li>
<li><a href="{{ pathto('index') }}">The pytest Website</a></li> <li><a href="{{ pathto('index') }}">The pytest Website</a></li>
<li><a href="{{ pathto('contributing') }}">Contribution Guide</a></li> <li><a href="{{ pathto('contributing') }}">Contribution Guide</a></li>
<li><a href="https://pypi.python.org/pypi/pytest">pytest @ PyPI</a></li> <li><a href="https://pypi.python.org/pypi/pytest">pytest @ PyPI</a></li>

View File

@ -4,9 +4,9 @@ python testing sprint June 20th-26th 2016
.. image:: ../img/freiburg2.jpg .. image:: ../img/freiburg2.jpg
:width: 400 :width: 400
The pytest core group is heading towards the biggest sprint The pytest core group held the biggest sprint
in its history, to take place in the black forest town Freiburg in its history in June 2016, taking place in the black forest town Freiburg
in Germany. As of February 2016 we have started a `funding in Germany. In February 2016 we started a `funding
campaign on Indiegogo to cover expenses campaign on Indiegogo to cover expenses
<http://igg.me/at/pytest-sprint/x/4034848>`_ The page also mentions <http://igg.me/at/pytest-sprint/x/4034848>`_ The page also mentions
some preliminary topics: some preliminary topics:
@ -35,73 +35,32 @@ some preliminary topics:
Participants Participants
-------------- --------------
Here are preliminary participants who said they are likely to come, Over 20 participants took part from 4 continents, including employees
given some expenses funding:: from Splunk, Personalkollen, Cobe.io, FanDuel and Dolby. Some newcomers
mixed with developers who have worked on pytest since its beginning, and
Anatoly Bubenkoff, Netherlands of course everyone in between.
Ana Ribeiro, Brazil Ana Ribeiro, Brazil
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
Ronny Pfannschmidt, Germany Ronny Pfannschmidt, Germany
Tom Viner, UK
<your name here?>
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 :)
Sprint organisation, schedule 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.
- 19/20th arrival in Freiburg Sprint activity was organised heavily around pairing, with plenty of group
- 20th social get together, initial hacking discusssions to take advantage of the high bandwidth, and lightning talks
- 21/22th full sprint days as well.
- 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 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 The Indiegogo campaign aimed for 11000 USD and in the end raised over
to give money according to an invoice, get in contact with 12000, to reimburse travel costs, pay for a sprint venue and catering.
holger at http://merlinux.eu who can invoice your organisation
properly.
If we have excess money we'll use for further sprint/travel Excess money is reserved for further sprint/travel funding for pytest/tox
funding for pytest/tox contributors. contributors.

View File

@ -85,7 +85,7 @@ and if you need to have access to the actual exception info you may use::
the actual exception raised. The main attributes of interest are the actual exception raised. The main attributes of interest are
``.type``, ``.value`` and ``.traceback``. ``.type``, ``.value`` and ``.traceback``.
.. versionchanged:: 2.10 .. versionchanged:: 3.0
In the context manager form you may use the keyword argument In the context manager form you may use the keyword argument
``message`` to specify a custom failure message:: ``message`` to specify a custom failure message::

View File

@ -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).

View File

@ -116,7 +116,7 @@ libraries or subprocesses that directly write to operating
system level output streams (FD1 and FD2). system level output streams (FD1 and FD2).
.. versionadded:: 2.10 .. versionadded:: 3.0
To temporarily disable capture within a test, both ``capsys`` To temporarily disable capture within a test, both ``capsys``
and ``capfd`` have a ``disabled()`` method that can be used and ``capfd`` have a ``disabled()`` method that can be used

View File

@ -20,6 +20,7 @@ Full pytest documentation
cache cache
plugins plugins
backwards-compatibility
contributing contributing
talks talks

View File

@ -105,7 +105,7 @@ itself::
The 'doctest_namespace' fixture The 'doctest_namespace' fixture
------------------------------- -------------------------------
.. versionadded:: 2.10 .. versionadded:: 3.0
The ``doctest_namespace`` fixture can be used to inject items into the 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 namespace in which your doctests run. It is intended to be used within

View File

@ -823,6 +823,10 @@ If we run it, we get two passing tests::
Here is how autouse fixtures work in other scopes: 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 - if an autouse fixture is defined in a test module, all its test
functions automatically use it. functions automatically use it.

View File

@ -32,7 +32,7 @@ class Writer:
def pytest_funcarg__a(request): def pytest_funcarg__a(request):
with Writer("request") as writer: with Writer("request") as writer:
writer.docmethod(request.getfuncargvalue) writer.docmethod(request.getfixturevalue)
writer.docmethod(request.cached_setup) writer.docmethod(request.cached_setup)
writer.docmethod(request.addfinalizer) writer.docmethod(request.addfinalizer)
writer.docmethod(request.applymarker) writer.docmethod(request.applymarker)

View File

@ -11,9 +11,6 @@ Talks and Tutorials
Talks and blog postings 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
- `pytest - Rapid Simple Testing, Florian Bruhin, Swiss Python Summit 2016 - `pytest - Rapid Simple Testing, Florian Bruhin, Swiss Python Summit 2016
<https://www.youtube.com/watch?v=rCBHkQ_LVIs>`_. <https://www.youtube.com/watch?v=rCBHkQ_LVIs>`_.
@ -52,12 +49,14 @@ Talks and blog postings
- `pytest introduction from Brian Okken (January 2013) - `pytest introduction from Brian Okken (January 2013)
<http://pythontesting.net/framework/pytest-introduction/>`_ <http://pythontesting.net/framework/pytest-introduction/>`_
- `monkey patching done right`_ (blog post, consult `monkeypatch - pycon australia 2012 pytest talk from Brianna Laugher (`video <http://www.youtube.com/watch?v=DTNejE9EraI>`_, `slides <http://www.slideshare.net/pfctdayelise/funcargs-other-fun-with-pytest>`_, `code <https://gist.github.com/3386951>`_)
plugin`_ for up-to-date API) - `pycon 2012 US talk video from Holger Krekel <http://www.youtube.com/watch?v=9LVqBQcFmyw>`_
- `monkey patching done right`_ (blog post, consult `monkeypatch plugin`_ for up-to-date API)
Test parametrization: Test parametrization:
- `generating parametrized tests with funcargs`_ (uses deprecated ``addcall()`` API. - `generating parametrized tests with fixtures`_.
- `test generators and cached setup`_ - `test generators and cached setup`_
- `parametrizing tests, generalized`_ (blog post) - `parametrizing tests, generalized`_ (blog post)
- `putting test-hooks into local or global plugins`_ (blog post) - `putting test-hooks into local or global plugins`_ (blog post)
@ -78,39 +77,17 @@ Plugin specific examples:
- `many examples in the docs for plugins`_ - `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 .. _`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 .. _`many examples in the docs for plugins`: plugins.html
.. _`monkeypatch plugin`: plugin/monkeypatch.html .. _`monkeypatch plugin`: monkeypatch.html
.. _`application setup in test functions with funcargs`: funcargs.html#appsetup .. _`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/ .. _`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/ .. _`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/ .. _`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/ .. _`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 .. _`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
<http://2012.pycon-au.org/schedule/52/view_talk?day=sunday>`_ (`video <http://www.youtube.com/watch?v=DTNejE9EraI>`_, `slides <http://www.slideshare.net/pfctdayelise/funcargs-other-fun-with-pytest>`_, `code <https://gist.github.com/3386951>`_)
- `pycon 2012 US talk video from Holger Krekel <http://www.youtube.com/watch?v=9LVqBQcFmyw>`_
- `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

View File

@ -79,7 +79,7 @@ than ``--tb=long``). It also ensures that a stack trace is printed on
**KeyboardInterrrupt** (Ctrl+C). **KeyboardInterrrupt** (Ctrl+C).
This is very useful if the tests are taking too long and you interrupt them 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 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. option you make sure a trace is shown.
Dropping to PDB_ (Python Debugger) on failures Dropping to PDB_ (Python Debugger) on failures
@ -204,7 +204,7 @@ This will add an extra property ``example_key="1"`` to the generated
LogXML: add_global_property LogXML: add_global_property
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. versionadded:: 2.10 .. versionadded:: 3.0
If you want to add a properties node in the testsuite level, which may contains properties that are relevant 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`` to all testcases you can use ``LogXML.add_global_properties``

View File

@ -176,6 +176,63 @@ If a package is installed this way, ``pytest`` will load
to make it easy for users to find your plugin. 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 Requiring/Loading plugins in a test module or conftest file
----------------------------------------------------------- -----------------------------------------------------------
@ -190,6 +247,16 @@ will be loaded as well. You can also use dotted path like this::
which will import the specified module as a ``pytest`` plugin. 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, it 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 Accessing another plugin by name
-------------------------------- --------------------------------

View File

@ -7,21 +7,20 @@ classic xunit-style setup
This section describes a classic and popular way how you can implement 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. 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
<fixture>` 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:: .. note::
As of pytest-2.4, teardownX functions are not called if While these setup/teardown methods are simple and familiar to those
setupX existed and failed/was skipped. This harmonizes coming from a ``unittest`` or nose ``background``, you may also consider
behaviour across all major python testing tools. using pytest's more powerful :ref:`fixture mechanism
<fixture>` 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 Module level setup/teardown
-------------------------------------- --------------------------------------
@ -38,6 +37,8 @@ which will usually be called once for all the functions::
method. method.
""" """
As of pytest-3.0, the ``module`` parameter is optional.
Class level setup/teardown Class level setup/teardown
---------------------------------- ----------------------------------
@ -71,6 +72,8 @@ Similarly, the following methods are called around each method invocation::
call. call.
""" """
As of pytest-3.0, the ``method`` parameter is optional.
If you would rather define test functions directly at module level If you would rather define test functions directly at module level
you can also use the following functions to implement fixtures:: you can also use the following functions to implement fixtures::
@ -84,7 +87,13 @@ you can also use the following functions to implement fixtures::
call. call.
""" """
Note that it is possible for setup/teardown pairs to be invoked multiple times As of pytest-3.0, the ``function`` parameter is optional.
per testing process.
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 .. _`unittest.py module`: http://docs.python.org/library/unittest.html

View File

@ -3,12 +3,12 @@
"yield_fixture" functions "yield_fixture" functions
--------------------------------------------------------------- ---------------------------------------------------------------
.. deprecated:: 2.10 .. deprecated:: 3.0
.. versionadded:: 2.4 .. versionadded:: 2.4
.. important:: .. important::
Since pytest-2.10, fixtures using the normal ``fixture`` decorator can use a ``yield`` 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`` statement to provide fixture values and execute teardown code, exactly like ``yield_fixture``
in previous versions. in previous versions.

View File

@ -762,3 +762,52 @@ class TestDurationWithFixture:
* setup *test_1* * setup *test_1*
* call *test_1* * call *test_1*
""") """)
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_str_args_deprecated(tmpdir, testdir):
"""Deprecate passing strings to pytest.main(). Scheduled for removal in pytest-4.0."""
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

View File

@ -66,24 +66,6 @@ def test_code_from_func():
assert co.path 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(): def test_unicode_handling():
value = py.builtin._totext('\xc4\x85\xc4\x87\n', 'utf-8').encode('utf8') value = py.builtin._totext('\xc4\x85\xc4\x87\n', 'utf-8').encode('utf8')
def f(): def f():

View File

@ -274,18 +274,6 @@ class TestTraceback_f_g_h:
assert entry.lineno == co.firstlineno + 2 assert entry.lineno == co.firstlineno + 2
assert entry.frame.code.name == 'g' 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(): def test_excinfo_exconly():
excinfo = pytest.raises(ValueError, h) excinfo = pytest.raises(ValueError, h)
assert excinfo.exconly().startswith('ValueError') assert excinfo.exconly().startswith('ValueError')
@ -381,10 +369,12 @@ def test_match_raises_error(testdir):
]) ])
class TestFormattedExcinfo: class TestFormattedExcinfo:
def pytest_funcarg__importasmod(self, request):
@pytest.fixture
def importasmod(self, request):
def importasmod(source): def importasmod(source):
source = _pytest._code.Source(source) source = _pytest._code.Source(source)
tmpdir = request.getfuncargvalue("tmpdir") tmpdir = request.getfixturevalue("tmpdir")
modpath = tmpdir.join("mod.py") modpath = tmpdir.join("mod.py")
tmpdir.ensure("__init__.py") tmpdir.ensure("__init__.py")
modpath.write(source) modpath.write(source)
@ -429,7 +419,7 @@ class TestFormattedExcinfo:
assert lines == [ assert lines == [
' def f():', ' def f():',
'> assert 0', '> assert 0',
'E assert 0' 'E AssertionError'
] ]
@ -768,23 +758,6 @@ raise ValueError()
assert reprtb.extraline == "!!! Recursion detected (same locals & position)" assert reprtb.extraline == "!!! Recursion detected (same locals & position)"
assert str(reprtb) 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): def test_reprexcinfo_getrepr(self, importasmod):
mod = importasmod(""" mod = importasmod("""
def f(x): def f(x):
@ -933,21 +906,6 @@ raise ValueError()
repr.toterminal(tw) repr.toterminal(tw)
assert tw.stringio.getvalue() 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): def test_traceback_repr_style(self, importasmod):
mod = importasmod(""" mod = importasmod("""
def f(): def f():
@ -1077,4 +1035,4 @@ def test_cwd_deleted(testdir):
""") """)
result = testdir.runpytest() result = testdir.runpytest()
result.stdout.fnmatch_lines(['* 1 failed in *']) result.stdout.fnmatch_lines(['* 1 failed in *'])
assert 'INTERNALERROR' not in result.stdout.str() + result.stderr.str() assert 'INTERNALERROR' not in result.stdout.str() + result.stderr.str()

View File

@ -285,13 +285,14 @@ class TestSourceParsingAndCompiling:
#print "block", str(block) #print "block", str(block)
assert str(stmt).strip().startswith('assert') 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): def check(comp, name):
co = comp(self.source, name) co = comp(self.source, name)
if not name: if not name:
expected = "codegen %s:%d>" %(mypath, mylineno+2+1) expected = "codegen %s:%d>" %(mypath, mylineno+2+2)
else: 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 fn = co.co_filename
assert fn.endswith(expected) assert fn.endswith(expected)
@ -300,8 +301,7 @@ class TestSourceParsingAndCompiling:
mypath = mycode.path mypath = mycode.path
for comp in _pytest._code.compile, _pytest._code.Source.compile: for comp in _pytest._code.compile, _pytest._code.Source.compile:
for name in '', None, 'my': check(comp, name)
yield check, comp, name
def test_offsetless_synerr(self): def test_offsetless_synerr(self):
pytest.raises(SyntaxError, _pytest._code.compile, "lambda a,a: 0", mode='eval') pytest.raises(SyntaxError, _pytest._code.compile, "lambda a,a: 0", mode='eval')

View File

@ -8,7 +8,7 @@ if __name__ == '__main__':
setup( setup(
name="runtests", name="runtests",
version="0.1", version="0.1",
description="exemple of how embedding pytest into an executable using cx_freeze", description="example of how embedding pytest into an executable using cx_freeze",
executables=[Executable("runtests_script.py")], executables=[Executable("runtests_script.py")],
options={"build_exe": {'includes': pytest.freeze_includes()}}, options={"build_exe": {'includes': pytest.freeze_includes()}},
) )

View File

@ -334,7 +334,7 @@ class TestFunction:
reprec.assertoutcome() reprec.assertoutcome()
def test_function_equality(self, testdir, tmpdir): def test_function_equality(self, testdir, tmpdir):
from _pytest.python import FixtureManager from _pytest.fixtures import FixtureManager
config = testdir.parseconfigure() config = testdir.parseconfigure()
session = testdir.Session(config) session = testdir.Session(config)
session._fixturemanager = FixtureManager(session) session._fixturemanager = FixtureManager(session)
@ -795,21 +795,24 @@ class TestTracebackCutting:
def test_traceback_argsetup(self, testdir): def test_traceback_argsetup(self, testdir):
testdir.makeconftest(""" testdir.makeconftest("""
def pytest_funcarg__hello(request): import pytest
@pytest.fixture
def hello(request):
raise ValueError("xyz") raise ValueError("xyz")
""") """)
p = testdir.makepyfile("def test(hello): pass") p = testdir.makepyfile("def test(hello): pass")
result = testdir.runpytest(p) result = testdir.runpytest(p)
assert result.ret != 0 assert result.ret != 0
out = result.stdout.str() out = result.stdout.str()
assert out.find("xyz") != -1 assert "xyz" in out
assert out.find("conftest.py:2: ValueError") != -1 assert "conftest.py:5: ValueError" in out
numentries = out.count("_ _ _") # separator for traceback entries numentries = out.count("_ _ _") # separator for traceback entries
assert numentries == 0 assert numentries == 0
result = testdir.runpytest("--fulltrace", p) result = testdir.runpytest("--fulltrace", p)
out = result.stdout.str() 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 numentries = out.count("_ _ _ _") # separator for traceback entries
assert numentries > 3 assert numentries > 3

View File

@ -3,35 +3,37 @@ from textwrap import dedent
import _pytest._code import _pytest._code
import pytest import pytest
import sys import sys
from _pytest import python as funcargs
from _pytest.pytester import get_public_names 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 test_getfuncargnames():
def f(): pass def f(): pass
assert not funcargs.getfuncargnames(f) assert not fixtures.getfuncargnames(f)
def g(arg): pass def g(arg): pass
assert funcargs.getfuncargnames(g) == ('arg',) assert fixtures.getfuncargnames(g) == ('arg',)
def h(arg1, arg2="hello"): pass def h(arg1, arg2="hello"): pass
assert funcargs.getfuncargnames(h) == ('arg1',) assert fixtures.getfuncargnames(h) == ('arg1',)
def h(arg1, arg2, arg3="hello"): pass def h(arg1, arg2, arg3="hello"): pass
assert funcargs.getfuncargnames(h) == ('arg1', 'arg2') assert fixtures.getfuncargnames(h) == ('arg1', 'arg2')
class A: class A:
def f(self, arg1, arg2="hello"): def f(self, arg1, arg2="hello"):
pass pass
assert funcargs.getfuncargnames(A().f) == ('arg1',) assert fixtures.getfuncargnames(A().f) == ('arg1',)
if sys.version_info < (3,0): if sys.version_info < (3,0):
assert funcargs.getfuncargnames(A.f) == ('arg1',) assert fixtures.getfuncargnames(A.f) == ('arg1',)
class TestFillFixtures: class TestFillFixtures:
def test_fillfuncargs_exposed(self): def test_fillfuncargs_exposed(self):
# used by oejskit, kept for compatibility # used by oejskit, kept for compatibility
assert pytest._fillfuncargs == funcargs.fillfixtures assert pytest._fillfuncargs == fixtures.fillfixtures
def test_funcarg_lookupfails(self, testdir): def test_funcarg_lookupfails(self, testdir):
testdir.makepyfile(""" testdir.makepyfile("""
def pytest_funcarg__xyzsomething(request): import pytest
@pytest.fixture
def xyzsomething(request):
return 42 return 42
def test_func(some): def test_func(some):
@ -47,14 +49,18 @@ class TestFillFixtures:
def test_funcarg_basic(self, testdir): def test_funcarg_basic(self, testdir):
item = testdir.getitem(""" item = testdir.getitem("""
def pytest_funcarg__some(request): import pytest
@pytest.fixture
def some(request):
return request.function.__name__ return request.function.__name__
def pytest_funcarg__other(request): @pytest.fixture
def other(request):
return 42 return 42
def test_func(some, other): def test_func(some, other):
pass pass
""") """)
funcargs.fillfixtures(item) fixtures.fillfixtures(item)
del item.funcargs["request"] del item.funcargs["request"]
assert len(get_public_names(item.funcargs)) == 2 assert len(get_public_names(item.funcargs)) == 2
assert item.funcargs['some'] == "test_func" assert item.funcargs['some'] == "test_func"
@ -62,7 +68,10 @@ class TestFillFixtures:
def test_funcarg_lookup_modulelevel(self, testdir): def test_funcarg_lookup_modulelevel(self, testdir):
testdir.makepyfile(""" testdir.makepyfile("""
def pytest_funcarg__something(request): import pytest
@pytest.fixture
def something(request):
return request.function.__name__ return request.function.__name__
class TestClass: class TestClass:
@ -76,9 +85,13 @@ class TestFillFixtures:
def test_funcarg_lookup_classlevel(self, testdir): def test_funcarg_lookup_classlevel(self, testdir):
p = testdir.makepyfile(""" p = testdir.makepyfile("""
import pytest
class TestClass: class TestClass:
def pytest_funcarg__something(self, request):
@pytest.fixture
def something(self, request):
return request.instance return request.instance
def test_method(self, something): def test_method(self, something):
assert something is self assert something is self
""") """)
@ -92,13 +105,15 @@ class TestFillFixtures:
sub2 = testdir.mkpydir("sub2") sub2 = testdir.mkpydir("sub2")
sub1.join("conftest.py").write(_pytest._code.Source(""" sub1.join("conftest.py").write(_pytest._code.Source("""
import pytest import pytest
def pytest_funcarg__arg1(request): @pytest.fixture
pytest.raises(Exception, "request.getfuncargvalue('arg2')") def arg1(request):
pytest.raises(Exception, "request.getfixturevalue('arg2')")
""")) """))
sub2.join("conftest.py").write(_pytest._code.Source(""" sub2.join("conftest.py").write(_pytest._code.Source("""
import pytest import pytest
def pytest_funcarg__arg2(request): @pytest.fixture
pytest.raises(Exception, "request.getfuncargvalue('arg1')") def arg2(request):
pytest.raises(Exception, "request.getfixturevalue('arg1')")
""")) """))
sub1.join("test_in_sub1.py").write("def test_1(arg1): pass") sub1.join("test_in_sub1.py").write("def test_1(arg1): pass")
@ -397,10 +412,13 @@ class TestFillFixtures:
class TestRequestBasic: class TestRequestBasic:
def test_request_attributes(self, testdir): def test_request_attributes(self, testdir):
item = testdir.getitem(""" item = testdir.getitem("""
def pytest_funcarg__something(request): pass import pytest
@pytest.fixture
def something(request): pass
def test_func(something): pass def test_func(something): pass
""") """)
req = funcargs.FixtureRequest(item) req = fixtures.FixtureRequest(item)
assert req.function == item.obj assert req.function == item.obj
assert req.keywords == item.keywords assert req.keywords == item.keywords
assert hasattr(req.module, 'test_func') assert hasattr(req.module, 'test_func')
@ -411,8 +429,11 @@ class TestRequestBasic:
def test_request_attributes_method(self, testdir): def test_request_attributes_method(self, testdir):
item, = testdir.getitems(""" item, = testdir.getitems("""
import pytest
class TestB: class TestB:
def pytest_funcarg__something(self, request):
@pytest.fixture
def something(self, request):
return 1 return 1
def test_func(self, something): def test_func(self, something):
pass pass
@ -421,9 +442,11 @@ class TestRequestBasic:
assert req.cls.__name__ == "TestB" assert req.cls.__name__ == "TestB"
assert req.instance.__class__ == req.cls 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(""" modcol = testdir.getmodulecol("""
def pytest_funcarg__something(request): import pytest
@pytest.fixture
def something(request):
pass pass
class TestClass: class TestClass:
def test_method(self, something): def test_method(self, something):
@ -431,41 +454,53 @@ class TestRequestBasic:
""") """)
item1, = testdir.genitems([modcol]) item1, = testdir.genitems([modcol])
assert item1.name == "test_method" assert item1.name == "test_method"
arg2fixturedefs = funcargs.FixtureRequest(item1)._arg2fixturedefs arg2fixturedefs = fixtures.FixtureRequest(item1)._arg2fixturedefs
assert len(arg2fixturedefs) == 1 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(""" testdir.makeconftest("""
def pytest_funcarg__something(request): import pytest
@pytest.fixture
def something(request):
return 1 return 1
""") """)
testdir.makepyfile(""" testdir.makepyfile("""
def pytest_funcarg__something(request): import pytest
return request.getfuncargvalue("something") + 1
@pytest.fixture
def something(request):
return request.getfixturevalue("something") + 1
def test_func(something): def test_func(something):
assert something == 2 assert something == 2
""") """)
reprec = testdir.inline_run() reprec = testdir.inline_run()
reprec.assertoutcome(passed=1) reprec.assertoutcome(passed=1)
def test_getfuncargvalue(self, testdir): @pytest.mark.parametrize(
'getfixmethod', ('getfixturevalue', 'getfuncargvalue'))
def test_getfixturevalue(self, testdir, getfixmethod):
item = testdir.getitem(""" item = testdir.getitem("""
import pytest
l = [2] l = [2]
def pytest_funcarg__something(request): return 1 @pytest.fixture
def pytest_funcarg__other(request): def something(request): return 1
@pytest.fixture
def other(request):
return l.pop() return l.pop()
def test_func(something): pass def test_func(something): pass
""") """)
req = item._request req = item._request
pytest.raises(FixtureLookupError, req.getfuncargvalue, "notexists") fixture_fetcher = getattr(req, getfixmethod)
val = req.getfuncargvalue("something") pytest.raises(FixtureLookupError, fixture_fetcher, "notexists")
val = fixture_fetcher("something")
assert val == 1 assert val == 1
val = req.getfuncargvalue("something") val = fixture_fetcher("something")
assert val == 1 assert val == 1
val2 = req.getfuncargvalue("other") val2 = fixture_fetcher("other")
assert val2 == 2 assert val2 == 2
val2 = req.getfuncargvalue("other") # see about caching val2 = fixture_fetcher("other") # see about caching
assert val2 == 2 assert val2 == 2
pytest._fillfuncargs(item) pytest._fillfuncargs(item)
assert item.funcargs["something"] == 1 assert item.funcargs["something"] == 1
@ -475,8 +510,10 @@ class TestRequestBasic:
def test_request_addfinalizer(self, testdir): def test_request_addfinalizer(self, testdir):
item = testdir.getitem(""" item = testdir.getitem("""
import pytest
teardownlist = [] teardownlist = []
def pytest_funcarg__something(request): @pytest.fixture
def something(request):
request.addfinalizer(lambda: teardownlist.append(1)) request.addfinalizer(lambda: teardownlist.append(1))
def test_func(something): pass def test_func(something): pass
""") """)
@ -501,7 +538,8 @@ class TestRequestBasic:
result = testdir.runpytest_subprocess() result = testdir.runpytest_subprocess()
assert result.ret != 0 assert result.ret != 0
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines([
"*AssertionError:*pytest_funcarg__marked_with_prefix_and_decorator*" "*AssertionError: fixtures cannot have*@pytest.fixture*",
"*pytest_funcarg__marked_with_prefix_and_decorator*"
]) ])
def test_request_addfinalizer_failing_setup(self, testdir): def test_request_addfinalizer_failing_setup(self, testdir):
@ -539,8 +577,10 @@ class TestRequestBasic:
def test_request_addfinalizer_partial_setup_failure(self, testdir): def test_request_addfinalizer_partial_setup_failure(self, testdir):
p = testdir.makepyfile(""" p = testdir.makepyfile("""
import pytest
l = [] l = []
def pytest_funcarg__something(request): @pytest.fixture
def something(request):
request.addfinalizer(lambda: l.append(None)) request.addfinalizer(lambda: l.append(None))
def test_func(something, missingarg): def test_func(something, missingarg):
pass pass
@ -555,7 +595,7 @@ class TestRequestBasic:
def test_request_getmodulepath(self, testdir): def test_request_getmodulepath(self, testdir):
modcol = testdir.getmodulecol("def test_somefunc(): pass") modcol = testdir.getmodulecol("def test_somefunc(): pass")
item, = testdir.genitems([modcol]) item, = testdir.genitems([modcol])
req = funcargs.FixtureRequest(item) req = fixtures.FixtureRequest(item)
assert req.fspath == modcol.fspath assert req.fspath == modcol.fspath
def test_request_fixturenames(self, testdir): def test_request_fixturenames(self, testdir):
@ -581,9 +621,11 @@ class TestRequestBasic:
def test_funcargnames_compatattr(self, testdir): def test_funcargnames_compatattr(self, testdir):
testdir.makepyfile(""" testdir.makepyfile("""
import pytest
def pytest_generate_tests(metafunc): def pytest_generate_tests(metafunc):
assert metafunc.funcargnames == metafunc.fixturenames assert metafunc.funcargnames == metafunc.fixturenames
def pytest_funcarg__fn(request): @pytest.fixture
def fn(request):
assert request._pyfuncitem.funcargnames == \ assert request._pyfuncitem.funcargnames == \
request._pyfuncitem.fixturenames request._pyfuncitem.fixturenames
return request.funcargnames, request.fixturenames return request.funcargnames, request.fixturenames
@ -628,7 +670,9 @@ class TestRequestBasic:
# this tests that normalization of nodeids takes place # this tests that normalization of nodeids takes place
b = testdir.mkdir("tests").mkdir("unit") b = testdir.mkdir("tests").mkdir("unit")
b.join("conftest.py").write(_pytest._code.Source(""" b.join("conftest.py").write(_pytest._code.Source("""
def pytest_funcarg__arg1(): import pytest
@pytest.fixture
def arg1():
pass pass
""")) """))
p = b.join("test_module.py") p = b.join("test_module.py")
@ -676,7 +720,10 @@ class TestRequestBasic:
class TestRequestMarking: class TestRequestMarking:
def test_applymarker(self, testdir): def test_applymarker(self, testdir):
item1,item2 = testdir.getitems(""" item1,item2 = testdir.getitems("""
def pytest_funcarg__something(request): import pytest
@pytest.fixture
def something(request):
pass pass
class TestClass: class TestClass:
def test_func1(self, something): def test_func1(self, something):
@ -684,7 +731,7 @@ class TestRequestMarking:
def test_func2(self, something): def test_func2(self, something):
pass pass
""") """)
req1 = funcargs.FixtureRequest(item1) req1 = fixtures.FixtureRequest(item1)
assert 'xfail' not in item1.keywords assert 'xfail' not in item1.keywords
req1.applymarker(pytest.mark.xfail) req1.applymarker(pytest.mark.xfail)
assert 'xfail' in item1.keywords assert 'xfail' in item1.keywords
@ -735,7 +782,10 @@ class TestRequestCachedSetup:
reprec = testdir.inline_runsource(""" reprec = testdir.inline_runsource("""
mysetup = ["hello",].pop mysetup = ["hello",].pop
def pytest_funcarg__something(request): import pytest
@pytest.fixture
def something(request):
return request.cached_setup(mysetup, scope="module") return request.cached_setup(mysetup, scope="module")
def test_func1(something): def test_func1(something):
@ -750,7 +800,9 @@ class TestRequestCachedSetup:
reprec = testdir.inline_runsource(""" reprec = testdir.inline_runsource("""
mysetup = ["hello", "hello2", "hello3"].pop mysetup = ["hello", "hello2", "hello3"].pop
def pytest_funcarg__something(request): import pytest
@pytest.fixture
def something(request):
return request.cached_setup(mysetup, scope="class") return request.cached_setup(mysetup, scope="class")
def test_func1(something): def test_func1(something):
assert something == "hello3" assert something == "hello3"
@ -766,7 +818,7 @@ class TestRequestCachedSetup:
def test_request_cachedsetup_extrakey(self, testdir): def test_request_cachedsetup_extrakey(self, testdir):
item1 = testdir.getitem("def test_func(): pass") item1 = testdir.getitem("def test_func(): pass")
req1 = funcargs.FixtureRequest(item1) req1 = fixtures.FixtureRequest(item1)
l = ["hello", "world"] l = ["hello", "world"]
def setup(): def setup():
return l.pop() return l.pop()
@ -781,7 +833,7 @@ class TestRequestCachedSetup:
def test_request_cachedsetup_cache_deletion(self, testdir): def test_request_cachedsetup_cache_deletion(self, testdir):
item1 = testdir.getitem("def test_func(): pass") item1 = testdir.getitem("def test_func(): pass")
req1 = funcargs.FixtureRequest(item1) req1 = fixtures.FixtureRequest(item1)
l = [] l = []
def setup(): def setup():
l.append("setup") l.append("setup")
@ -800,9 +852,13 @@ class TestRequestCachedSetup:
def test_request_cached_setup_two_args(self, testdir): def test_request_cached_setup_two_args(self, testdir):
testdir.makepyfile(""" testdir.makepyfile("""
def pytest_funcarg__arg1(request): import pytest
@pytest.fixture
def arg1(request):
return request.cached_setup(lambda: 42) return request.cached_setup(lambda: 42)
def pytest_funcarg__arg2(request): @pytest.fixture
def arg2(request):
return request.cached_setup(lambda: 17) return request.cached_setup(lambda: 17)
def test_two_different_setups(arg1, arg2): def test_two_different_setups(arg1, arg2):
assert arg1 != arg2 assert arg1 != arg2
@ -812,12 +868,16 @@ class TestRequestCachedSetup:
"*1 passed*" "*1 passed*"
]) ])
def test_request_cached_setup_getfuncargvalue(self, testdir): def test_request_cached_setup_getfixturevalue(self, testdir):
testdir.makepyfile(""" testdir.makepyfile("""
def pytest_funcarg__arg1(request): import pytest
arg1 = request.getfuncargvalue("arg2")
@pytest.fixture
def arg1(request):
arg1 = request.getfixturevalue("arg2")
return request.cached_setup(lambda: arg1 + 1) return request.cached_setup(lambda: arg1 + 1)
def pytest_funcarg__arg2(request): @pytest.fixture
def arg2(request):
return request.cached_setup(lambda: 10) return request.cached_setup(lambda: 10)
def test_two_funcarg(arg1): def test_two_funcarg(arg1):
assert arg1 == 11 assert arg1 == 11
@ -829,8 +889,10 @@ class TestRequestCachedSetup:
def test_request_cached_setup_functional(self, testdir): def test_request_cached_setup_functional(self, testdir):
testdir.makepyfile(test_0=""" testdir.makepyfile(test_0="""
import pytest
l = [] l = []
def pytest_funcarg__something(request): @pytest.fixture
def something(request):
val = request.cached_setup(fsetup, fteardown) val = request.cached_setup(fsetup, fteardown)
return val return val
def fsetup(mycache=[1]): def fsetup(mycache=[1]):
@ -856,7 +918,10 @@ class TestRequestCachedSetup:
def test_issue117_sessionscopeteardown(self, testdir): def test_issue117_sessionscopeteardown(self, testdir):
testdir.makepyfile(""" testdir.makepyfile("""
def pytest_funcarg__app(request): import pytest
@pytest.fixture
def app(request):
app = request.cached_setup( app = request.cached_setup(
scope='session', scope='session',
setup=lambda: 0, setup=lambda: 0,
@ -1117,16 +1182,23 @@ class TestFixtureUsages:
class TestFixtureManagerParseFactories: class TestFixtureManagerParseFactories:
def pytest_funcarg__testdir(self, request):
testdir = request.getfuncargvalue("testdir") @pytest.fixture
def testdir(self, request):
testdir = request.getfixturevalue("testdir")
testdir.makeconftest(""" testdir.makeconftest("""
def pytest_funcarg__hello(request): import pytest
@pytest.fixture
def hello(request):
return "conftest" return "conftest"
def pytest_funcarg__fm(request): @pytest.fixture
def fm(request):
return request._fixturemanager return request._fixturemanager
def pytest_funcarg__item(request): @pytest.fixture
def item(request):
return request._pyfuncitem return request._pyfuncitem
""") """)
return testdir return testdir
@ -1152,17 +1224,21 @@ class TestFixtureManagerParseFactories:
faclist = fm.getfixturedefs(name, item.nodeid) faclist = fm.getfixturedefs(name, item.nodeid)
assert len(faclist) == 1 assert len(faclist) == 1
fac = faclist[0] fac = faclist[0]
assert fac.func.__name__ == "pytest_funcarg__" + name assert fac.func.__name__ == name
""") """)
reprec = testdir.inline_run("-s") reprec = testdir.inline_run("-s")
reprec.assertoutcome(passed=1) reprec.assertoutcome(passed=1)
def test_parsefactories_conftest_and_module_and_class(self, testdir): def test_parsefactories_conftest_and_module_and_class(self, testdir):
testdir.makepyfile(""" testdir.makepyfile("""
def pytest_funcarg__hello(request): import pytest
@pytest.fixture
def hello(request):
return "module" return "module"
class TestClass: class TestClass:
def pytest_funcarg__hello(self, request): @pytest.fixture
def hello(self, request):
return "class" return "class"
def test_hello(self, item, fm): def test_hello(self, item, fm):
faclist = fm.getfixturedefs("hello", item.nodeid) faclist = fm.getfixturedefs("hello", item.nodeid)
@ -1210,7 +1286,9 @@ class TestFixtureManagerParseFactories:
class TestAutouseDiscovery: class TestAutouseDiscovery:
def pytest_funcarg__testdir(self, testdir):
@pytest.fixture
def testdir(self, testdir):
testdir.makeconftest(""" testdir.makeconftest("""
import pytest import pytest
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
@ -1224,10 +1302,12 @@ class TestAutouseDiscovery:
def perfunction2(arg1): def perfunction2(arg1):
pass pass
def pytest_funcarg__fm(request): @pytest.fixture
def fm(request):
return request._fixturemanager return request._fixturemanager
def pytest_funcarg__item(request): @pytest.fixture
def item(request):
return request._pyfuncitem return request._pyfuncitem
""") """)
return testdir return testdir
@ -1506,7 +1586,8 @@ class TestAutouseManagement:
def test_2(self): def test_2(self):
pass pass
""") """)
reprec = testdir.inline_run("-v","-s") confcut = "--confcutdir={0}".format(testdir.tmpdir)
reprec = testdir.inline_run("-v","-s", confcut)
reprec.assertoutcome(passed=8) reprec.assertoutcome(passed=8)
config = reprec.getcalls("pytest_unconfigure")[0].config config = reprec.getcalls("pytest_unconfigure")[0].config
l = config.pluginmanager._getconftestmodules(p)[0].l l = config.pluginmanager._getconftestmodules(p)[0].l
@ -1771,17 +1852,19 @@ class TestFixtureMarker:
def test_scope_module_and_finalizer(self, testdir): def test_scope_module_and_finalizer(self, testdir):
testdir.makeconftest(""" testdir.makeconftest("""
import pytest import pytest
finalized = [] finalized_list = []
created = [] created_list = []
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def arg(request): def arg(request):
created.append(1) created_list.append(1)
assert request.scope == "module" assert request.scope == "module"
request.addfinalizer(lambda: finalized.append(1)) request.addfinalizer(lambda: finalized_list.append(1))
def pytest_funcarg__created(request): @pytest.fixture
return len(created) def created(request):
def pytest_funcarg__finalized(request): return len(created_list)
return len(finalized) @pytest.fixture
def finalized(request):
return len(finalized_list)
""") """)
testdir.makepyfile( testdir.makepyfile(
test_mod1=""" test_mod1="""
@ -1804,9 +1887,9 @@ class TestFixtureMarker:
reprec.assertoutcome(passed=4) reprec.assertoutcome(passed=4)
@pytest.mark.parametrize("method", [ @pytest.mark.parametrize("method", [
'request.getfuncargvalue("arg")', 'request.getfixturevalue("arg")',
'request.cached_setup(lambda: None, scope="function")', 'request.cached_setup(lambda: None, scope="function")',
], ids=["getfuncargvalue", "cached_setup"]) ], ids=["getfixturevalue", "cached_setup"])
def test_scope_mismatch_various(self, testdir, method): def test_scope_mismatch_various(self, testdir, method):
testdir.makeconftest(""" testdir.makeconftest("""
import pytest import pytest
@ -2718,6 +2801,7 @@ class TestContextManagerFixtureFuncs:
""".format(flavor=flavor)) """.format(flavor=flavor))
result = testdir.runpytest("-s") result = testdir.runpytest("-s")
result.stdout.fnmatch_lines("*mew*") result.stdout.fnmatch_lines("*mew*")
class TestParameterizedSubRequest: class TestParameterizedSubRequest:
def test_call_from_fixture(self, testdir): def test_call_from_fixture(self, testdir):
testfile = testdir.makepyfile(""" testfile = testdir.makepyfile("""
@ -2729,7 +2813,7 @@ class TestParameterizedSubRequest:
@pytest.fixture @pytest.fixture
def get_named_fixture(request): def get_named_fixture(request):
return request.getfuncargvalue('fix_with_param') return request.getfixturevalue('fix_with_param')
def test_foo(request, get_named_fixture): def test_foo(request, get_named_fixture):
pass pass
@ -2754,7 +2838,7 @@ class TestParameterizedSubRequest:
return request.param return request.param
def test_foo(request): def test_foo(request):
request.getfuncargvalue('fix_with_param') request.getfixturevalue('fix_with_param')
""") """)
result = testdir.runpytest() result = testdir.runpytest()
result.stdout.fnmatch_lines(""" result.stdout.fnmatch_lines("""
@ -2778,7 +2862,7 @@ class TestParameterizedSubRequest:
testfile = testdir.makepyfile(""" testfile = testdir.makepyfile("""
def test_foo(request): def test_foo(request):
request.getfuncargvalue('fix_with_param') request.getfixturevalue('fix_with_param')
""") """)
result = testdir.runpytest() result = testdir.runpytest()
result.stdout.fnmatch_lines(""" result.stdout.fnmatch_lines("""
@ -2808,7 +2892,7 @@ class TestParameterizedSubRequest:
from fix import fix_with_param from fix import fix_with_param
def test_foo(request): def test_foo(request):
request.getfuncargvalue('fix_with_param') request.getfixturevalue('fix_with_param')
""")) """))
tests_dir.chdir() tests_dir.chdir()
@ -2823,3 +2907,7 @@ class TestParameterizedSubRequest:
E*{1}:5 E*{1}:5
*1 failed* *1 failed*
""".format(fixfile.strpath, testfile.basename)) """.format(fixfile.strpath, testfile.basename))
def test_getfuncargvalue_is_deprecated(request):
pytest.deprecated_call(request.getfuncargvalue, 'tmpdir')

View File

@ -15,7 +15,9 @@ class TestOEJSKITSpecials:
return self.fspath, 3, "xyz" return self.fspath, 3, "xyz"
""") """)
modcol = testdir.getmodulecol(""" modcol = testdir.getmodulecol("""
def pytest_funcarg__arg1(request): import pytest
@pytest.fixture
def arg1(request):
return 42 return 42
class MyClass: class MyClass:
pass pass
@ -43,7 +45,8 @@ class TestOEJSKITSpecials:
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def hello(): def hello():
pass pass
def pytest_funcarg__arg1(request): @pytest.fixture
def arg1(request):
return 42 return 42
class MyClass: class MyClass:
pass pass
@ -73,7 +76,7 @@ def test_wrapped_getfslineno():
class TestMockDecoration: class TestMockDecoration:
def test_wrapped_getfuncargnames(self): def test_wrapped_getfuncargnames(self):
from _pytest.python import getfuncargnames from _pytest.compat import getfuncargnames
def wrap(f): def wrap(f):
def func(): def func():
pass pass
@ -86,7 +89,7 @@ class TestMockDecoration:
assert l == ("x",) assert l == ("x",)
def test_wrapped_getfuncargnames_patching(self): def test_wrapped_getfuncargnames_patching(self):
from _pytest.python import getfuncargnames from _pytest.compat import getfuncargnames
def wrap(f): def wrap(f):
def func(): def func():
pass pass
@ -234,7 +237,7 @@ class TestReRunTests:
""") """)
def test_pytestconfig_is_session_scoped(): def test_pytestconfig_is_session_scoped():
from _pytest.python import pytestconfig from _pytest.fixtures import pytestconfig
assert pytestconfig._pytestfixturefunction.scope == "session" assert pytestconfig._pytestfixturefunction.scope == "session"

View File

@ -5,7 +5,7 @@ import sys
import _pytest._code import _pytest._code
import py import py
import pytest import pytest
from _pytest import python as funcargs from _pytest import python, fixtures
import hypothesis import hypothesis
from hypothesis import strategies from hypothesis import strategies
@ -22,9 +22,9 @@ class TestMetafunc:
name2fixturedefs = None name2fixturedefs = None
def __init__(self, names): def __init__(self, names):
self.names_closure = names self.names_closure = names
names = funcargs.getfuncargnames(func) names = fixtures.getfuncargnames(func)
fixtureinfo = FixtureInfo(names) fixtureinfo = FixtureInfo(names)
return funcargs.Metafunc(func, fixtureinfo, None) return python.Metafunc(func, fixtureinfo, None)
def test_no_funcargs(self, testdir): def test_no_funcargs(self, testdir):
def function(): pass def function(): pass
@ -448,13 +448,13 @@ class TestMetafunc:
def test_parametrize_functional(self, testdir): def test_parametrize_functional(self, testdir):
testdir.makepyfile(""" testdir.makepyfile("""
import pytest
def pytest_generate_tests(metafunc): def pytest_generate_tests(metafunc):
metafunc.parametrize('x', [1,2], indirect=True) metafunc.parametrize('x', [1,2], indirect=True)
metafunc.parametrize('y', [2]) metafunc.parametrize('y', [2])
def pytest_funcarg__x(request): @pytest.fixture
def x(request):
return request.param * 10 return request.param * 10
#def pytest_funcarg__y(request):
# return request.param
def test_simple(x,y): def test_simple(x,y):
assert x in (10,20) assert x in (10,20)
@ -558,16 +558,16 @@ class TestMetafunc:
def test_format_args(self): def test_format_args(self):
def function1(): pass def function1(): pass
assert funcargs._format_args(function1) == '()' assert fixtures._format_args(function1) == '()'
def function2(arg1): pass def function2(arg1): pass
assert funcargs._format_args(function2) == "(arg1)" assert fixtures._format_args(function2) == "(arg1)"
def function3(arg1, arg2="qwe"): pass 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 def function4(arg1, *args, **kwargs): pass
assert funcargs._format_args(function4) == "(arg1, *args, **kwargs)" assert fixtures._format_args(function4) == "(arg1, *args, **kwargs)"
class TestMetafuncFunctional: class TestMetafuncFunctional:
@ -578,7 +578,8 @@ class TestMetafuncFunctional:
def pytest_generate_tests(metafunc): def pytest_generate_tests(metafunc):
metafunc.addcall(param=metafunc) metafunc.addcall(param=metafunc)
def pytest_funcarg__metafunc(request): @pytest.fixture
def metafunc(request):
assert request._pyfuncitem._genid == "0" assert request._pyfuncitem._genid == "0"
return request.param return request.param
@ -630,7 +631,9 @@ class TestMetafuncFunctional:
metafunc.addcall(param=10) metafunc.addcall(param=10)
metafunc.addcall(param=20) metafunc.addcall(param=20)
def pytest_funcarg__arg1(request): import pytest
@pytest.fixture
def arg1(request):
return request.param return request.param
def test_func1(arg1): def test_func1(arg1):
@ -669,9 +672,12 @@ class TestMetafuncFunctional:
def pytest_generate_tests(metafunc): def pytest_generate_tests(metafunc):
metafunc.addcall(param=(1,1), id="hello") metafunc.addcall(param=(1,1), id="hello")
def pytest_funcarg__arg1(request): import pytest
@pytest.fixture
def arg1(request):
return request.param[0] return request.param[0]
def pytest_funcarg__arg2(request): @pytest.fixture
def arg2(request):
return request.param[1] return request.param[1]
class TestClass: class TestClass:
@ -749,17 +755,20 @@ class TestMetafuncFunctional:
"*4 failed*", "*4 failed*",
]) ])
def test_parametrize_and_inner_getfuncargvalue(self, testdir): def test_parametrize_and_inner_getfixturevalue(self, testdir):
p = testdir.makepyfile(""" p = testdir.makepyfile("""
def pytest_generate_tests(metafunc): def pytest_generate_tests(metafunc):
metafunc.parametrize("arg1", [1], indirect=True) metafunc.parametrize("arg1", [1], indirect=True)
metafunc.parametrize("arg2", [10], indirect=True) metafunc.parametrize("arg2", [10], indirect=True)
def pytest_funcarg__arg1(request): import pytest
x = request.getfuncargvalue("arg2") @pytest.fixture
def arg1(request):
x = request.getfixturevalue("arg2")
return x + request.param return x + request.param
def pytest_funcarg__arg2(request): @pytest.fixture
def arg2(request):
return request.param return request.param
def test_func1(arg1, arg2): def test_func1(arg1, arg2):
@ -777,10 +786,13 @@ class TestMetafuncFunctional:
assert "arg1" in metafunc.fixturenames assert "arg1" in metafunc.fixturenames
metafunc.parametrize("arg1", [1], indirect=True) metafunc.parametrize("arg1", [1], indirect=True)
def pytest_funcarg__arg1(request): import pytest
@pytest.fixture
def arg1(request):
return request.param return request.param
def pytest_funcarg__arg2(request, arg1): @pytest.fixture
def arg2(request, arg1):
return 10 * arg1 return 10 * arg1
def test_func(arg2): def test_func(arg2):
@ -870,7 +882,8 @@ class TestMetafuncFunctional:
if "arg" in metafunc.funcargnames: if "arg" in metafunc.funcargnames:
metafunc.parametrize("arg", [1,2], indirect=True, metafunc.parametrize("arg", [1,2], indirect=True,
scope=%r) scope=%r)
def pytest_funcarg__arg(request): @pytest.fixture
def arg(request):
l.append(request.param) l.append(request.param)
return request.param return request.param
def test_hello(arg): def test_hello(arg):

View File

@ -1,7 +1,8 @@
import pytest import pytest
@pytest.fixture(params=['--setup-only', '--setup-plan'], scope='module') @pytest.fixture(params=['--setup-only', '--setup-plan', '--setup-show'],
scope='module')
def mode(request): def mode(request):
return request.param return request.param
@ -24,7 +25,7 @@ def test_show_only_active_fixtures(testdir, mode):
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines([
'*SETUP F arg1*', '*SETUP F arg1*',
'*test_arg1 (fixtures used: arg1)', '*test_arg1 (fixtures used: arg1)*',
'*TEARDOWN F arg1*', '*TEARDOWN F arg1*',
]) ])
assert "_arg0" not in result.stdout.str() assert "_arg0" not in result.stdout.str()
@ -49,7 +50,7 @@ def test_show_different_scopes(testdir, mode):
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines([
'SETUP S arg_session*', 'SETUP S arg_session*',
'*SETUP F arg_function*', '*SETUP F arg_function*',
'*test_arg1 (fixtures used: arg_function, arg_session)', '*test_arg1 (fixtures used: arg_function, arg_session)*',
'*TEARDOWN F arg_function*', '*TEARDOWN F arg_function*',
'TEARDOWN S arg_session*', 'TEARDOWN S arg_session*',
]) ])
@ -77,7 +78,7 @@ def test_show_nested_fixtures(testdir, mode):
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines([
'SETUP S arg_same*', 'SETUP S arg_same*',
'*SETUP F arg_same (fixtures used: arg_same)*', '*SETUP F arg_same (fixtures used: arg_same)*',
'*test_arg1 (fixtures used: arg_same)', '*test_arg1 (fixtures used: arg_same)*',
'*TEARDOWN F arg_same*', '*TEARDOWN F arg_same*',
'TEARDOWN S arg_same*', 'TEARDOWN S arg_same*',
]) ])
@ -102,7 +103,7 @@ def test_show_fixtures_with_autouse(testdir, mode):
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines([
'SETUP S arg_session*', 'SETUP S arg_session*',
'*SETUP F arg_function*', '*SETUP F arg_function*',
'*test_arg1 (fixtures used: arg_function, arg_session)', '*test_arg1 (fixtures used: arg_function, arg_session)*',
]) ])
@ -219,3 +220,24 @@ def test_capturing(testdir):
'this should be captured', 'this should be captured',
'this should also 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*',
])

View File

@ -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 '<WeirdRepr\nsecond line>'
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 "<MY42 object>"
def myany(x):
return True
try:
assert not(myany(A() < 0))
except AssertionError:
e = exvalue()
s = str(e)
assert "<MY42 object> < 0" in s

View File

@ -3,10 +3,8 @@ import sys
import textwrap import textwrap
import _pytest.assertion as plugin import _pytest.assertion as plugin
import _pytest._code
import py import py
import pytest import pytest
from _pytest.assertion import reinterpret
from _pytest.assertion import util from _pytest.assertion import util
PY3 = sys.version_info >= (3, 0) PY3 = sys.version_info >= (3, 0)
@ -23,24 +21,200 @@ def mock_config():
return Config() return Config()
def interpret(expr): class TestImportHookInstallation:
return reinterpret.reinterpret(expr, _pytest._code.Frame(sys._getframe(1)))
@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])
@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', ['plain', 'rewrite'])
def test_installed_plugin_rewrite(self, testdir, mode):
# 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
class DummyDistInfo:
project_name = 'spam'
version = '1.0'
def _get_metadata(self, name):
return ['spamplugin.py,sha256=abc,123',
'hampkg/__init__.py,sha256=abc,123']
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()
""",
'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'])
class TestBinReprIntegration: class TestBinReprIntegration:
def test_pytest_assertrepr_compare_called(self, testdir): def test_pytest_assertrepr_compare_called(self, testdir):
testdir.makeconftest(""" testdir.makeconftest("""
import pytest
l = [] l = []
def pytest_assertrepr_compare(op, left, right): def pytest_assertrepr_compare(op, left, right):
l.append((op, left, right)) l.append((op, left, right))
def pytest_funcarg__l(request):
@pytest.fixture
def list(request):
return l return l
""") """)
testdir.makepyfile(""" testdir.makepyfile("""
def test_hello(): def test_hello():
assert 0 == 1 assert 0 == 1
def test_check(l): def test_check(list):
assert l == [("==", 0, 1)] assert list == [("==", 0, 1)]
""") """)
result = testdir.runpytest("-v") result = testdir.runpytest("-v")
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines([
@ -477,14 +651,6 @@ def test_assertion_options(testdir):
result = testdir.runpytest_subprocess("--assert=plain") result = testdir.runpytest_subprocess("--assert=plain")
assert "3 == 4" not in result.stdout.str() 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
def test_triple_quoted_string_issue113(testdir): def test_triple_quoted_string_issue113(testdir):
testdir.makepyfile(""" testdir.makepyfile("""
def test_hello(): def test_hello():

View File

@ -12,7 +12,7 @@ if sys.platform.startswith("java"):
import _pytest._code import _pytest._code
from _pytest.assertion import util 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 from _pytest.main import EXIT_NOTESTSCOLLECTED
@ -213,10 +213,12 @@ class TestAssertionRewrite:
return False return False
def f(): def f():
assert x() and x() assert x() and x()
assert getmsg(f, {"x" : x}) == "assert (x())" assert getmsg(f, {"x" : x}) == """assert (False)
+ where False = x()"""
def f(): def f():
assert False or x() 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(): def f():
assert 1 in {} and 2 in {} assert 1 in {} and 2 in {}
assert getmsg(f) == "assert (1 in {})" assert getmsg(f) == "assert (1 in {})"
@ -299,27 +301,34 @@ class TestAssertionRewrite:
ns = {"g" : g} ns = {"g" : g}
def f(): def f():
assert g() assert g()
assert getmsg(f, ns) == """assert g()""" assert getmsg(f, ns) == """assert False
+ where False = g()"""
def f(): def f():
assert g(1) assert g(1)
assert getmsg(f, ns) == """assert g(1)""" assert getmsg(f, ns) == """assert False
+ where False = g(1)"""
def f(): def f():
assert g(1, 2) 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(): def f():
assert g(1, g=42) 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(): def f():
assert g(1, 3, g=23) 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(): def f():
seq = [1, 2, 3] seq = [1, 2, 3]
assert g(*seq) 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(): def f():
x = "a" x = "a"
assert g(**{x : 2}) 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): def test_attribute(self):
class X(object): class X(object):
@ -332,7 +341,8 @@ class TestAssertionRewrite:
def f(): def f():
x.a = False # noqa x.a = False # noqa
assert x.a # 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 test_comparisons(self):
def f(): def f():
@ -514,6 +524,16 @@ def test_rewritten():
testdir.makepyfile("import a_package_without_init_py.module") testdir.makepyfile("import a_package_without_init_py.module")
assert testdir.runpytest().ret == EXIT_NOTESTSCOLLECTED 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]
class TestAssertionRewriteHookDetails(object): class TestAssertionRewriteHookDetails(object):
def test_loader_is_package_false_for_module(self, testdir): def test_loader_is_package_false_for_module(self, testdir):
testdir.makepyfile(test_fun=""" testdir.makepyfile(test_fun="""
@ -694,40 +714,6 @@ class TestAssertionRewriteHookDetails(object):
result = testdir.runpytest() result = testdir.runpytest()
result.stdout.fnmatch_lines('*1 passed*') result.stdout.fnmatch_lines('*1 passed*')
@pytest.mark.parametrize('initial_conftest', [True, False])
@pytest.mark.parametrize('mode', ['plain', 'rewrite', 'reinterp'])
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*'
elif mode == 'reinterp':
expected = '*AssertionError:*was re-run*'
else:
assert 0
result.stdout.fnmatch_lines([expected])
def test_issue731(testdir): def test_issue731(testdir):
testdir.makepyfile(""" testdir.makepyfile("""
@ -746,5 +732,28 @@ def test_issue731(testdir):
assert 'unbalanced braces' not in result.stdout.str() assert 'unbalanced braces' not in result.stdout.str()
def test_collapse_false_unbalanced_braces(): class TestIssue925():
util._collapse_false('some text{ False\n{False = some more text\n}') 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)')

View File

@ -373,10 +373,14 @@ def test_preparse_ordering_with_setuptools(testdir, monkeypatch):
pkg_resources = pytest.importorskip("pkg_resources") pkg_resources = pytest.importorskip("pkg_resources")
def my_iter(name): def my_iter(name):
assert name == "pytest11" assert name == "pytest11"
class Dist:
project_name = 'spam'
version = '1.0'
def _get_metadata(self, name):
return ['foo.txt,sha256=abc,123']
class EntryPoint: class EntryPoint:
name = "mytestplugin" name = "mytestplugin"
class dist: dist = Dist()
pass
def load(self): def load(self):
class PseudoPlugin: class PseudoPlugin:
x = 42 x = 42
@ -391,12 +395,40 @@ def test_preparse_ordering_with_setuptools(testdir, monkeypatch):
plugin = config.pluginmanager.getplugin("mytestplugin") plugin = config.pluginmanager.getplugin("mytestplugin")
assert plugin.x == 42 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): def test_plugin_preparse_prevents_setuptools_loading(testdir, monkeypatch):
pkg_resources = pytest.importorskip("pkg_resources") pkg_resources = pytest.importorskip("pkg_resources")
def my_iter(name): def my_iter(name):
assert name == "pytest11" assert name == "pytest11"
class Dist:
project_name = 'spam'
version = '1.0'
def _get_metadata(self, name):
return ['foo.txt,sha256=abc,123']
class EntryPoint: class EntryPoint:
name = "mytestplugin" name = "mytestplugin"
dist = Dist()
def load(self): def load(self):
assert 0, "should not arrive here" assert 0, "should not arrive here"
return iter([EntryPoint()]) return iter([EntryPoint()])
@ -488,7 +520,6 @@ def test_load_initial_conftest_last_ordering(testdir):
expected = [ expected = [
"_pytest.config", "_pytest.config",
'test_config', 'test_config',
'_pytest.assertion',
'_pytest.capture', '_pytest.capture',
] ]
assert [x.function.__module__ for x in l] == expected assert [x.function.__module__ for x in l] == expected
@ -522,11 +553,11 @@ class TestWarning:
def test_hello(fix): def test_hello(fix):
pass pass
""") """)
result = testdir.runpytest() result = testdir.runpytest("--disable-pytest-warnings")
assert result.parseoutcomes()["pytest-warnings"] > 0 assert result.parseoutcomes()["pytest-warnings"] > 0
assert "hello" not in result.stdout.str() assert "hello" not in result.stdout.str()
result = testdir.runpytest("-rw") result = testdir.runpytest()
result.stdout.fnmatch_lines(""" result.stdout.fnmatch_lines("""
===*pytest-warning summary*=== ===*pytest-warning summary*===
*WT1*test_warn_on_test_item*:5*hello* *WT1*test_warn_on_test_item*:5*hello*
@ -671,4 +702,4 @@ class TestOverrideIniArgs:
"ini2:url=/tmp/user2?a=b&d=e", "ini2:url=/tmp/user2?a=b&d=e",
"ini3:True", "ini3:True",
"ini4:False" "ini4:False"
]) ])

View File

@ -203,6 +203,7 @@ def test_conftest_import_order(testdir, monkeypatch):
def impct(p): def impct(p):
return p return p
conftest = PytestPluginManager() conftest = PytestPluginManager()
conftest._confcutdir = testdir.tmpdir
monkeypatch.setattr(conftest, '_importconftest', impct) monkeypatch.setattr(conftest, '_importconftest', impct)
assert conftest._getconftestmodules(sub) == [ct1, ct2] assert conftest._getconftestmodules(sub) == [ct1, ct2]

View File

@ -119,7 +119,10 @@ class TestPython:
def test_setup_error(self, testdir): def test_setup_error(self, testdir):
testdir.makepyfile(""" testdir.makepyfile("""
def pytest_funcarg__arg(request): import pytest
@pytest.fixture
def arg(request):
raise ValueError() raise ValueError()
def test_function(arg): def test_function(arg):
pass pass
@ -131,7 +134,7 @@ class TestPython:
tnode = node.find_first_by_tag("testcase") tnode = node.find_first_by_tag("testcase")
tnode.assert_attr( tnode.assert_attr(
file="test_setup_error.py", file="test_setup_error.py",
line="2", line="5",
classname="test_setup_error", classname="test_setup_error",
name="test_function") name="test_function")
fnode = tnode.find_first_by_tag("error") fnode = tnode.find_first_by_tag("error")
@ -444,7 +447,10 @@ class TestPython:
def test_setup_error_captures_stdout(self, testdir): def test_setup_error_captures_stdout(self, testdir):
testdir.makepyfile(""" testdir.makepyfile("""
def pytest_funcarg__arg(request): import pytest
@pytest.fixture
def arg(request):
print('hello-stdout') print('hello-stdout')
raise ValueError() raise ValueError()
def test_function(arg): def test_function(arg):
@ -459,7 +465,10 @@ class TestPython:
def test_setup_error_captures_stderr(self, testdir): def test_setup_error_captures_stderr(self, testdir):
testdir.makepyfile(""" testdir.makepyfile("""
import sys import sys
def pytest_funcarg__arg(request): import pytest
@pytest.fixture
def arg(request):
sys.stderr.write('hello-stderr') sys.stderr.write('hello-stderr')
raise ValueError() raise ValueError()
def test_function(arg): def test_function(arg):

View File

@ -481,7 +481,8 @@ class TestFunctional:
def test_mark_dynamically_in_funcarg(self, testdir): def test_mark_dynamically_in_funcarg(self, testdir):
testdir.makeconftest(""" testdir.makeconftest("""
import pytest import pytest
def pytest_funcarg__arg(request): @pytest.fixture
def arg(request):
request.applymarker(pytest.mark.hello) request.applymarker(pytest.mark.hello)
def pytest_terminal_summary(terminalreporter): def pytest_terminal_summary(terminalreporter):
l = terminalreporter.stats['passed'] l = terminalreporter.stats['passed']

View File

@ -3,10 +3,11 @@ import sys
import textwrap import textwrap
import pytest import pytest
from _pytest.monkeypatch import monkeypatch as MonkeyPatch from _pytest.monkeypatch import MonkeyPatch
def pytest_funcarg__mp(request): @pytest.fixture
def mp(request):
cwd = os.getcwd() cwd = os.getcwd()
sys_path = list(sys.path) sys_path = list(sys.path)
@ -205,7 +206,7 @@ def test_setenv_prepend():
def test_monkeypatch_plugin(testdir): def test_monkeypatch_plugin(testdir):
reprec = testdir.inline_runsource(""" reprec = testdir.inline_runsource("""
def test_method(monkeypatch): def test_method(monkeypatch):
assert monkeypatch.__class__.__name__ == "monkeypatch" assert monkeypatch.__class__.__name__ == "MonkeyPatch"
""") """)
res = reprec.countoutcomes() res = reprec.countoutcomes()
assert tuple(res) == (1, 0, 0), res assert tuple(res) == (1, 0, 0), res

View File

@ -29,6 +29,9 @@ class TestParser:
assert argument.dest == 'test' assert argument.dest == 'test'
argument = parseopt.Argument('-t', '--test', dest='abc') argument = parseopt.Argument('-t', '--test', dest='abc')
assert argument.dest == 'abc' assert argument.dest == 'abc'
assert str(argument) == (
"Argument(_short_opts: ['-t'], _long_opts: ['--test'], dest: 'abc')"
)
def test_argument_type(self): def test_argument_type(self):
argument = parseopt.Argument('-t', dest='abc', type='int') argument = parseopt.Argument('-t', dest='abc', type='int')

View File

@ -1,6 +1,7 @@
import sys import sys
import _pytest._code import _pytest._code
import pytest
def runpdb_and_get_report(testdir, source): def runpdb_and_get_report(testdir, source):
@ -12,12 +13,14 @@ def runpdb_and_get_report(testdir, source):
class TestPDB: class TestPDB:
def pytest_funcarg__pdblist(self, request):
monkeypatch = request.getfuncargvalue("monkeypatch") @pytest.fixture
def pdblist(self, request):
monkeypatch = request.getfixturevalue("monkeypatch")
pdblist = [] pdblist = []
def mypdb(*args): def mypdb(*args):
pdblist.append(args) pdblist.append(args)
plugin = request.config.pluginmanager.getplugin('pdb') plugin = request.config.pluginmanager.getplugin('debugging')
monkeypatch.setattr(plugin, 'post_mortem', mypdb) monkeypatch.setattr(plugin, 'post_mortem', mypdb)
return pdblist return pdblist
@ -311,3 +314,28 @@ class TestPDB:
child.sendeof() child.sendeof()
if child.isalive(): if child.isalive():
child.wait() child.wait()
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"]

View File

@ -229,11 +229,12 @@ class BaseFunctionalTests:
assert reps[5].failed assert reps[5].failed
def test_exact_teardown_issue1206(self, testdir): def test_exact_teardown_issue1206(self, testdir):
"""issue shadowing error with wrong number of arguments on teardown_method."""
rec = testdir.inline_runsource(""" rec = testdir.inline_runsource("""
import pytest import pytest
class TestClass: class TestClass:
def teardown_method(self): def teardown_method(self, x, y, z):
pass pass
def test_method(self): def test_method(self):
@ -256,9 +257,9 @@ class BaseFunctionalTests:
assert reps[2].when == "teardown" assert reps[2].when == "teardown"
assert reps[2].longrepr.reprcrash.message in ( assert reps[2].longrepr.reprcrash.message in (
# python3 error # python3 error
'TypeError: teardown_method() takes 1 positional argument but 2 were given', "TypeError: teardown_method() missing 2 required positional arguments: 'y' and 'z'",
# python2 error # python2 error
'TypeError: teardown_method() takes exactly 1 argument (2 given)' 'TypeError: teardown_method() takes exactly 4 arguments (2 given)'
) )
def test_failure_in_setup_function_ignores_custom_repr(self, testdir): def test_failure_in_setup_function_ignores_custom_repr(self, testdir):
@ -399,13 +400,15 @@ def test_callinfo():
@pytest.mark.xfail @pytest.mark.xfail
def test_runtest_in_module_ordering(testdir): def test_runtest_in_module_ordering(testdir):
p1 = testdir.makepyfile(""" p1 = testdir.makepyfile("""
import pytest
def pytest_runtest_setup(item): # runs after class-level! def pytest_runtest_setup(item): # runs after class-level!
item.function.mylist.append("module") item.function.mylist.append("module")
class TestClass: class TestClass:
def pytest_runtest_setup(self, item): def pytest_runtest_setup(self, item):
assert not hasattr(item.function, 'mylist') assert not hasattr(item.function, 'mylist')
item.function.mylist = ['class'] item.function.mylist = ['class']
def pytest_funcarg__mylist(self, request): @pytest.fixture
def mylist(self, request):
return request.function.mylist return request.function.mylist
def pytest_runtest_call(self, item, __multicall__): def pytest_runtest_call(self, item, __multicall__):
try: try:

View File

@ -1,6 +1,8 @@
# #
# test correct setup/teardowns at # test correct setup/teardowns at
# module, class, and instance level # module, class, and instance level
import pytest
def test_module_and_function_setup(testdir): def test_module_and_function_setup(testdir):
reprec = testdir.inline_runsource(""" reprec = testdir.inline_runsource("""
@ -234,7 +236,8 @@ def test_setup_funcarg_setup_when_outer_scope_fails(testdir):
import pytest import pytest
def setup_module(mod): def setup_module(mod):
raise ValueError(42) raise ValueError(42)
def pytest_funcarg__hello(request): @pytest.fixture
def hello(request):
raise ValueError("xyz43") raise ValueError("xyz43")
def test_function1(hello): def test_function1(hello):
pass pass
@ -250,3 +253,53 @@ def test_setup_funcarg_setup_when_outer_scope_fails(testdir):
"*2 error*" "*2 error*"
]) ])
assert "xyz43" not in result.stdout.str() 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

View File

@ -197,6 +197,14 @@ class TestNewSession(SessionTests):
colfail = [x for x in finished if x.failed] colfail = [x for x in finished if x.failed]
assert len(colfail) == 1 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): def test_plugin_specify(testdir):
pytest.raises(ImportError, """ pytest.raises(ImportError, """

View File

@ -309,7 +309,8 @@ class TestXFail:
def test_dynamic_xfail_no_run(self, testdir): def test_dynamic_xfail_no_run(self, testdir):
p = testdir.makepyfile(""" p = testdir.makepyfile("""
import pytest import pytest
def pytest_funcarg__arg(request): @pytest.fixture
def arg(request):
request.applymarker(pytest.mark.xfail(run=False)) request.applymarker(pytest.mark.xfail(run=False))
def test_this(arg): def test_this(arg):
assert 0 assert 0
@ -323,7 +324,8 @@ class TestXFail:
def test_dynamic_xfail_set_during_funcarg_setup(self, testdir): def test_dynamic_xfail_set_during_funcarg_setup(self, testdir):
p = testdir.makepyfile(""" p = testdir.makepyfile("""
import pytest import pytest
def pytest_funcarg__arg(request): @pytest.fixture
def arg(request):
request.applymarker(pytest.mark.xfail) request.applymarker(pytest.mark.xfail)
def test_this2(arg): def test_this2(arg):
assert 0 assert 0

View File

@ -590,17 +590,30 @@ def test_getreportopt():
class config: class config:
class option: class option:
reportchars = "" reportchars = ""
disablepytestwarnings = True
config.option.reportchars = "sf" config.option.reportchars = "sf"
assert getreportopt(config) == "sf" assert getreportopt(config) == "sf"
config.option.reportchars = "sfx" config.option.reportchars = "sfxw"
assert getreportopt(config) == "sfx" 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): def test_terminalreporter_reportopt_addopts(testdir):
testdir.makeini("[pytest]\naddopts=-rs") testdir.makeini("[pytest]\naddopts=-rs")
testdir.makepyfile(""" testdir.makepyfile("""
def pytest_funcarg__tr(request): import pytest
@pytest.fixture
def tr(request):
tr = request.config.pluginmanager.getplugin("terminalreporter") tr = request.config.pluginmanager.getplugin("terminalreporter")
return tr return tr
def test_opt(tr): def test_opt(tr):
@ -614,7 +627,10 @@ def test_terminalreporter_reportopt_addopts(testdir):
def test_tbstyle_short(testdir): def test_tbstyle_short(testdir):
p = testdir.makepyfile(""" p = testdir.makepyfile("""
def pytest_funcarg__arg(request): import pytest
@pytest.fixture
def arg(request):
return 42 return 42
def test_opt(arg): def test_opt(arg):
x = 0 x = 0
@ -625,7 +641,7 @@ def test_tbstyle_short(testdir):
assert 'arg = 42' not in s assert 'arg = 42' not in s
assert 'x = 0' not in s assert 'x = 0' not in s
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines([
"*%s:5*" % p.basename, "*%s:8*" % p.basename,
" assert x", " assert x",
"E assert*", "E assert*",
]) ])

View File

@ -265,8 +265,8 @@ def test_testcase_custom_exception_info(testdir, type):
def run(self, result): def run(self, result):
excinfo = pytest.raises(ZeroDivisionError, lambda: 0/0) excinfo = pytest.raises(ZeroDivisionError, lambda: 0/0)
# we fake an incompatible exception info # we fake an incompatible exception info
from _pytest.monkeypatch import monkeypatch from _pytest.monkeypatch import MonkeyPatch
mp = monkeypatch() mp = MonkeyPatch()
def t(*args): def t(*args):
mp.undo() mp.undo()
raise TypeError() raise TypeError()

View File

@ -1,6 +1,7 @@
[tox] [tox]
minversion=2.0 minversion=2.0
distshare={homedir}/.tox/distshare distshare={homedir}/.tox/distshare
# make sure to update enviroment list on appveyor.yml
envlist= envlist=
linting,py26,py27,py33,py34,py35,pypy, linting,py26,py27,py33,py34,py35,pypy,
{py27,py35}-{pexpect,xdist,trial}, {py27,py35}-{pexpect,xdist,trial},