diff --git a/AUTHORS b/AUTHORS
index c1dec8150..1f842780f 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -9,7 +9,9 @@ Alexei Kozlenok
Anatoly Bubenkoff
Andreas Zeidler
Andy Freeland
+Andrzej Ostrowski
Anthon van der Neut
+Antony Lee
Armin Rigo
Aron Curzon
Aviv Palivoda
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 2dc03d693..b33ba167d 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,55 +1,76 @@
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**
-* 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``
- * ``--no-assert``
- * ``--nomagic``
- * ``--report``
+* Reinterpretation mode has now been removed. Only plain and rewrite
+ mode are available, consequently the ``--assert=reinterp`` option is
+ no longer available. Thanks `@flub`_ for the PR.
- 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
-.. _#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
+ Thanks to `@RedBeardCode`_ for the PR (`#1664`_).
+* ImportErrors in plugins now are a fatal error instead of issuing a
+ pytest warning (`#1479`_). Thanks to `@The-Compiler`_ for the PR.
-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**
* 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.
* 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`_).
-* 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).
Thanks `@novas0x2a`_ for the complete PR (`#1444`_).
@@ -57,88 +78,112 @@
tests.
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`_).
* New ``ExceptionInfo.match()`` method to match a regular expression on the
- string representation of an exception. Closes proposal `#372`_.
- Thanks `@omarkohl`_ for the complete PR (`#1502`_) and `@nicoddemus`_ for the
- implementation tips.
+ string representation of an exception (`#372`_).
+ Thanks `@omarkohl`_ for the complete PR (`#1502`_).
* ``__tracebackhide__`` can now also be set to a callable which then can decide
whether to filter the traceback based on the ``ExceptionInfo`` object passed
- to it.
+ 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.
-* ``capsys`` and ``capfd`` now have a ``disabled()`` method, which is a context manager
- that can be used to temporarily disable capture within a test.
+* ``capsys`` and ``capfd`` now have a ``disabled()`` context-manager method, which
+ can be used to temporarily disable capture within a test.
Thanks `@nicoddemus`_ for the PR.
-* New cli flag ``--fixtures-per-test`` 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.
Can also show where fixtures are defined if combined with ``-v``.
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
- (`#1633`_)
+ (`#1633`_).
-* New cli flags: (1) ``--setup-plan`` performs normal collection and reports
- 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.
+* New cli flags:
-* Added two new hooks: ``pytest_fixture_setup`` which executes the fixture
- setup and ``pytest_fixture_post_finalizer`` which is called after the fixture's
- finalizer and has access to the fixture's result cache.
- Thanks `@d6e`_, `@sallner`_
+ + ``--setup-plan``: performs normal collection and reports
+ the potential setup and teardown and does not execute any fixtures and tests;
+ + ``--setup-only``: performs normal collection, executes setup and teardown of
+ fixtures and reports them;
+ + ``--setup-show``: performs normal test execution and additionally shows
+ setup and teardown of fixtures;
-* 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
(see `#1562`_). Thanks `@kvas-it`_, for the PR.
-* New cli flag ``--override-ini`` or ``-o`` that overrides values from the ini file.
- Example '-o xfail_strict=True'. A complete ini-options can be viewed
- by py.test --help. Thanks `@blueyed`_ and `@fengxx`_ for the PR
+* Allow passing a custom debugger class (e.g. ``--pdbcls=IPython.core.debugger:Pdb``).
+ Thanks to `@anntzer`_ 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**
+* 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
those marked with the ``@pytest.yield_fixture`` decorator. This change renders
``@pytest.yield_fixture`` deprecated and makes ``@pytest.fixture`` with ``yield`` statements
the preferred way to write teardown code (`#1461`_).
Thanks `@csaftoiu`_ for bringing this to attention and `@nicoddemus`_ for the PR.
-* Fix (`#1351`_):
- explicitly passed parametrize ids do not get escaped to ascii.
+* Explicitly passed parametrize ids do not get escaped to ascii (`#1351`_).
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.
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.
Thanks `@palaviv`_ for the complete PR (`#1474`_).
-* Fix `#1426`_ Make ImportError during collection more explicit by reminding
- the user to check the name of the test module/package(s).
+* Now pytest warnings summary is shown up by default. Added a new flag
+ ``--disable-pytest-warnings`` to explicitly disable the warnings summary (`#1668`_).
+
+* Make ImportError during collection more explicit by reminding
+ the user to check the name of the test module/package(s) (`#1426`_).
Thanks `@omarkohl`_ for the complete PR (`#1520`_).
* Add ``build/`` and ``dist/`` to the default ``--norecursedirs`` list. Thanks
`@mikofski`_ for the report and `@tomviner`_ for the PR (`#1544`_).
-* pytest.raises in the context manager form accepts a custom
- message to raise when no exception occurred.
+* ``pytest.raises`` in the context manager form accepts a custom
+ ``message`` to raise when no exception occurred.
Thanks `@palaviv`_ for the complete PR (`#1616`_).
* ``conftest.py`` files now benefit from assertion rewriting; previously it
@@ -148,102 +193,150 @@
* Text documents without any doctests no longer appear as "skipped".
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
is specified on the command line together with the ``--pyargs``
option. Thanks to `@taschini`_ for the PR (`#1597`_).
-* Raise helpful failure message, when requesting parametrized fixture at runtime,
- e.g. with ``request.getfuncargvalue``. BACKWARD INCOMPAT: Previously these params
- were simply never defined. So a fixture decorated like ``@pytest.fixture(params=[0, 1, 2])``
- only ran once. Now a failure is raised. Fixes (`#460`_). Thanks to
- `@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`_).
-
-
-*
+* Always include full assertion explanation during assertion rewriting. The previous behaviour was hiding
+ sub-expressions that happened to be ``False``, assuming this was redundant information.
+ Thanks `@bagerard`_ for reporting (`#1503`_). Thanks to `@davehunt`_ and
+ `@tomviner`_ for the PR.
* ``OptionGroup.addoption()`` now checks if option names were already
added before, to make it easier to track down issues like `#1618`_.
Before, you only got exceptions later from ``argparse`` library,
giving no clue about the actual reason for double-added options.
-.. _@milliams: https://github.com/milliams
-.. _@csaftoiu: https://github.com/csaftoiu
-.. _@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
+* ``yield``-based tests are considered deprecated and will be removed in pytest-4.0.
+ Thanks `@nicoddemus`_ for the PR.
-.. _#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
-.. _#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
+* Using ``pytest_funcarg__`` prefix to declare fixtures is considered deprecated and will be
+ removed in pytest-4.0 (`#1684`_).
+ Thanks `@nicoddemus`_ for the PR.
+* Passing a command-line string to ``pytest.main()`` is considered deprecated and scheduled
+ for removal in pytest-4.0. It is recommended to pass a list of arguments instead (`#1723`_).
+
+* Rename ``getfuncargvalue`` to ``getfixturevalue``. ``getfuncargvalue`` is
+ still present but is now considered deprecated. Thanks to `@RedBeardCode`_ and `@tomviner`_
+ for the PR (`#1626`_).
+
+* ``optparse`` type usage now triggers DeprecationWarnings (`#1740`_).
+
+* ``optparse`` backward compatibility supports float/complex types (`#457`_).
+
+*
+
+*
+
+*
**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
=====
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index bedc82c65..947d4e655 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -120,6 +120,8 @@ the following:
- an issue tracker for bug reports and enhancement requests.
+- a `changelog `_
+
If no contributor strongly objects and two agree, the repository can then be
transferred to the ``pytest-dev`` organisation.
diff --git a/_pytest/_code/__init__.py b/_pytest/_code/__init__.py
index c046b9716..3463c11ea 100644
--- a/_pytest/_code/__init__.py
+++ b/_pytest/_code/__init__.py
@@ -4,9 +4,6 @@ from .code import ExceptionInfo # noqa
from .code import Frame # noqa
from .code import Traceback # noqa
from .code import getrawcode # noqa
-from .code import patch_builtins # noqa
-from .code import unpatch_builtins # noqa
from .source import Source # noqa
from .source import compile_ as compile # noqa
from .source import getfslineno # noqa
-
diff --git a/_pytest/_code/code.py b/_pytest/_code/code.py
index 0f1ffb918..0af5a3465 100644
--- a/_pytest/_code/code.py
+++ b/_pytest/_code/code.py
@@ -179,18 +179,6 @@ class TracebackEntry(object):
return self.frame.f_locals
locals = property(getlocals, None, None, "locals of underlaying frame")
- def reinterpret(self):
- """Reinterpret the failing statement and returns a detailed information
- about what operations are performed."""
- from _pytest.assertion.reinterpret import reinterpret
- if self.exprinfo is None:
- source = py.builtin._totext(self.statement).strip()
- x = reinterpret(source, self.frame, should_fail=True)
- if not py.builtin._istext(x):
- raise TypeError("interpret returned non-string %r" % (x,))
- self.exprinfo = x
- return self.exprinfo
-
def getfirstlinesource(self):
# on Jython this firstlineno can be -1 apparently
return max(self.frame.code.firstlineno, 0)
@@ -830,29 +818,6 @@ class ReprFuncArgs(TerminalRepr):
tw.line("")
-
-oldbuiltins = {}
-
-def patch_builtins(assertion=True, compile=True):
- """ put compile and AssertionError builtins to Python's builtins. """
- if assertion:
- from _pytest.assertion import reinterpret
- l = oldbuiltins.setdefault('AssertionError', [])
- l.append(py.builtin.builtins.AssertionError)
- py.builtin.builtins.AssertionError = reinterpret.AssertionError
- if compile:
- import _pytest._code
- l = oldbuiltins.setdefault('compile', [])
- l.append(py.builtin.builtins.compile)
- py.builtin.builtins.compile = _pytest._code.compile
-
-def unpatch_builtins(assertion=True, compile=True):
- """ remove compile and AssertionError builtins from Python builtins. """
- if assertion:
- py.builtin.builtins.AssertionError = oldbuiltins['AssertionError'].pop()
- if compile:
- py.builtin.builtins.compile = oldbuiltins['compile'].pop()
-
def getrawcode(obj, trycall=True):
""" return code object for given function. """
try:
diff --git a/_pytest/assertion/__init__.py b/_pytest/assertion/__init__.py
index 4f28c8da7..bca626931 100644
--- a/_pytest/assertion/__init__.py
+++ b/_pytest/assertion/__init__.py
@@ -5,9 +5,8 @@ import py
import os
import sys
-from _pytest.config import hookimpl
-from _pytest.monkeypatch import monkeypatch
from _pytest.assertion import util
+from _pytest.assertion import rewrite
def pytest_addoption(parser):
@@ -15,16 +14,42 @@ def pytest_addoption(parser):
group.addoption('--assert',
action="store",
dest="assertmode",
- choices=("rewrite", "reinterp", "plain",),
+ choices=("rewrite", "plain",),
default="rewrite",
metavar="MODE",
- help="""control assertion debugging tools. 'plain'
- performs no assertion debugging. 'reinterp'
- reinterprets assert statements after they failed
- to provide assertion expression information.
- 'rewrite' (the default) rewrites assert
- statements in test modules on import to
- provide assert expression information. """)
+ help="""Control assertion debugging tools. 'plain'
+ performs no assertion debugging. 'rewrite'
+ (the default) rewrites assert statements in
+ test modules on import to provide assert
+ expression information.""")
+
+
+def pytest_namespace():
+ return {'register_assert_rewrite': register_assert_rewrite}
+
+
+def register_assert_rewrite(*names):
+ """Register 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:
@@ -33,55 +58,37 @@ class AssertionState:
def __init__(self, config, mode):
self.mode = mode
self.trace = config.trace.root.get("assertion")
+ self.hook = None
-@hookimpl(tryfirst=True)
-def pytest_load_initial_conftests(early_config, parser, args):
- ns, ns_unknown_args = parser.parse_known_and_unknown_args(args)
- mode = ns.assertmode
- if mode == "rewrite":
- try:
- import ast # noqa
- except ImportError:
- mode = "reinterp"
- else:
- # Both Jython and CPython 2.6.0 have AST bugs that make the
- # assertion rewriting hook malfunction.
- if (sys.platform.startswith('java') or
- sys.version_info[:3] == (2, 6, 0)):
- mode = "reinterp"
+def install_importhook(config):
+ """Try to install the rewrite hook, raise SystemError if it fails."""
+ # Both Jython and CPython 2.6.0 have AST bugs that make the
+ # assertion rewriting hook malfunction.
+ if (sys.platform.startswith('java') or
+ sys.version_info[:3] == (2, 6, 0)):
+ raise SystemError('rewrite not supported')
- early_config._assertstate = AssertionState(early_config, mode)
- warn_about_missing_assertion(mode, early_config.pluginmanager)
-
- if mode != "plain":
- _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,))
+ config._assertstate = AssertionState(config, 'rewrite')
+ config._assertstate.hook = hook = rewrite.AssertionRewritingHook(config)
+ sys.meta_path.insert(0, hook)
+ config._assertstate.trace('installed rewrite import hook')
def undo():
- hook = early_config._assertstate.hook
+ hook = config._assertstate.hook
if hook is not None and hook in sys.meta_path:
sys.meta_path.remove(hook)
- early_config.add_cleanup(undo)
+ config.add_cleanup(undo)
+ return hook
def pytest_collection(session):
# this hook is only called when test modules are collected
# so for example not in the master process of pytest-xdist
# (which does not collect test modules)
- hook = session.config._assertstate.hook
- if hook is not None:
- hook.set_session(session)
+ assertstate = getattr(session.config, '_assertstate', None)
+ if assertstate:
+ if assertstate.hook is not None:
+ assertstate.hook.set_session(session)
def _running_on_ci():
@@ -138,43 +145,10 @@ def pytest_runtest_teardown(item):
def pytest_sessionfinish(session):
- hook = session.config._assertstate.hook
- if hook is not None:
- hook.session = None
-
-
-def _load_modules(mode):
- """Lazily import assertion related code."""
- global rewrite, reinterpret
- from _pytest.assertion import reinterpret # noqa
- if mode == "rewrite":
- from _pytest.assertion import rewrite # noqa
-
-
-def warn_about_missing_assertion(mode, 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)
+ assertstate = getattr(session.config, '_assertstate', None)
+ if assertstate:
+ if assertstate.hook is not None:
+ assertstate.hook.set_session(None)
# Expose this plugin's implementation for the pytest_assertrepr_compare hook
diff --git a/_pytest/assertion/reinterpret.py b/_pytest/assertion/reinterpret.py
deleted file mode 100644
index f4262c3ac..000000000
--- a/_pytest/assertion/reinterpret.py
+++ /dev/null
@@ -1,407 +0,0 @@
-"""
-Find intermediate evalutation results in assert statements through builtin AST.
-"""
-import ast
-import sys
-
-import _pytest._code
-import py
-from _pytest.assertion import util
-u = py.builtin._totext
-
-
-class AssertionError(util.BuiltinAssertionError):
- def __init__(self, *args):
- util.BuiltinAssertionError.__init__(self, *args)
- if args:
- # on Python2.6 we get len(args)==2 for: assert 0, (x,y)
- # on Python2.7 and above we always get len(args) == 1
- # with args[0] being the (x,y) tuple.
- if len(args) > 1:
- toprint = args
- else:
- toprint = args[0]
- try:
- self.msg = u(toprint)
- except Exception:
- self.msg = u(
- "<[broken __repr__] %s at %0xd>"
- % (toprint.__class__, id(toprint)))
- else:
- f = _pytest._code.Frame(sys._getframe(1))
- try:
- source = f.code.fullsource
- if source is not None:
- try:
- source = source.getstatement(f.lineno, assertion=True)
- except IndexError:
- source = None
- else:
- source = str(source.deindent()).strip()
- except py.error.ENOENT:
- source = None
- # this can also occur during reinterpretation, when the
- # co_filename is set to "".
- if source:
- self.msg = reinterpret(source, f, should_fail=True)
- else:
- self.msg = ""
- if not self.args:
- self.args = (self.msg,)
-
-if sys.version_info > (3, 0):
- AssertionError.__module__ = "builtins"
-
-if sys.platform.startswith("java"):
- # See http://bugs.jython.org/issue1497
- _exprs = ("BoolOp", "BinOp", "UnaryOp", "Lambda", "IfExp", "Dict",
- "ListComp", "GeneratorExp", "Yield", "Compare", "Call",
- "Repr", "Num", "Str", "Attribute", "Subscript", "Name",
- "List", "Tuple")
- _stmts = ("FunctionDef", "ClassDef", "Return", "Delete", "Assign",
- "AugAssign", "Print", "For", "While", "If", "With", "Raise",
- "TryExcept", "TryFinally", "Assert", "Import", "ImportFrom",
- "Exec", "Global", "Expr", "Pass", "Break", "Continue")
- _expr_nodes = set(getattr(ast, name) for name in _exprs)
- _stmt_nodes = set(getattr(ast, name) for name in _stmts)
- def _is_ast_expr(node):
- return node.__class__ in _expr_nodes
- def _is_ast_stmt(node):
- return node.__class__ in _stmt_nodes
-else:
- def _is_ast_expr(node):
- return isinstance(node, ast.expr)
- def _is_ast_stmt(node):
- return isinstance(node, ast.stmt)
-
-try:
- _Starred = ast.Starred
-except AttributeError:
- # Python 2. Define a dummy class so isinstance() will always be False.
- class _Starred(object): pass
-
-
-class Failure(Exception):
- """Error found while interpreting AST."""
-
- def __init__(self, explanation=""):
- self.cause = sys.exc_info()
- self.explanation = explanation
-
-
-def reinterpret(source, frame, should_fail=False):
- mod = ast.parse(source)
- visitor = DebugInterpreter(frame)
- try:
- visitor.visit(mod)
- except Failure:
- failure = sys.exc_info()[1]
- return getfailure(failure)
- if should_fail:
- return ("(assertion failed, but when it was re-run for "
- "printing intermediate values, it did not fail. Suggestions: "
- "compute assert expression before the assert or use --assert=plain)")
-
-def run(offending_line, frame=None):
- if frame is None:
- frame = _pytest._code.Frame(sys._getframe(1))
- return reinterpret(offending_line, frame)
-
-def getfailure(e):
- explanation = util.format_explanation(e.explanation)
- value = e.cause[1]
- if str(value):
- lines = explanation.split('\n')
- lines[0] += " << %s" % (value,)
- explanation = '\n'.join(lines)
- text = "%s: %s" % (e.cause[0].__name__, explanation)
- if text.startswith('AssertionError: assert '):
- text = text[16:]
- return text
-
-operator_map = {
- ast.BitOr : "|",
- ast.BitXor : "^",
- ast.BitAnd : "&",
- ast.LShift : "<<",
- ast.RShift : ">>",
- ast.Add : "+",
- ast.Sub : "-",
- ast.Mult : "*",
- ast.Div : "/",
- ast.FloorDiv : "//",
- ast.Mod : "%",
- ast.Eq : "==",
- ast.NotEq : "!=",
- ast.Lt : "<",
- ast.LtE : "<=",
- ast.Gt : ">",
- ast.GtE : ">=",
- ast.Pow : "**",
- ast.Is : "is",
- ast.IsNot : "is not",
- ast.In : "in",
- ast.NotIn : "not in"
-}
-
-unary_map = {
- ast.Not : "not %s",
- ast.Invert : "~%s",
- ast.USub : "-%s",
- ast.UAdd : "+%s"
-}
-
-
-class DebugInterpreter(ast.NodeVisitor):
- """Interpret AST nodes to gleam useful debugging information. """
-
- def __init__(self, frame):
- self.frame = frame
-
- def generic_visit(self, node):
- # Fallback when we don't have a special implementation.
- if _is_ast_expr(node):
- mod = ast.Expression(node)
- co = self._compile(mod)
- try:
- result = self.frame.eval(co)
- except Exception:
- raise Failure()
- explanation = self.frame.repr(result)
- return explanation, result
- elif _is_ast_stmt(node):
- mod = ast.Module([node])
- co = self._compile(mod, "exec")
- try:
- self.frame.exec_(co)
- except Exception:
- raise Failure()
- return None, None
- else:
- raise AssertionError("can't handle %s" %(node,))
-
- def _compile(self, source, mode="eval"):
- return compile(source, "", mode)
-
- def visit_Expr(self, expr):
- return self.visit(expr.value)
-
- def visit_Module(self, mod):
- for stmt in mod.body:
- self.visit(stmt)
-
- def visit_Name(self, name):
- explanation, result = self.generic_visit(name)
- # See if the name is local.
- source = "%r in locals() is not globals()" % (name.id,)
- co = self._compile(source)
- try:
- local = self.frame.eval(co)
- except Exception:
- # have to assume it isn't
- local = None
- if local is None or not self.frame.is_true(local):
- return name.id, result
- return explanation, result
-
- def visit_Compare(self, comp):
- left = comp.left
- left_explanation, left_result = self.visit(left)
- for op, next_op in zip(comp.ops, comp.comparators):
- next_explanation, next_result = self.visit(next_op)
- op_symbol = operator_map[op.__class__]
- explanation = "%s %s %s" % (left_explanation, op_symbol,
- next_explanation)
- source = "__exprinfo_left %s __exprinfo_right" % (op_symbol,)
- co = self._compile(source)
- try:
- result = self.frame.eval(co, __exprinfo_left=left_result,
- __exprinfo_right=next_result)
- except Exception:
- raise Failure(explanation)
- try:
- if not self.frame.is_true(result):
- break
- except KeyboardInterrupt:
- raise
- except:
- break
- left_explanation, left_result = next_explanation, next_result
-
- if util._reprcompare is not None:
- res = util._reprcompare(op_symbol, left_result, next_result)
- if res:
- explanation = res
- return explanation, result
-
- def visit_BoolOp(self, boolop):
- is_or = isinstance(boolop.op, ast.Or)
- explanations = []
- for operand in boolop.values:
- explanation, result = self.visit(operand)
- explanations.append(explanation)
- if result == is_or:
- break
- name = is_or and " or " or " and "
- explanation = "(" + name.join(explanations) + ")"
- return explanation, result
-
- def visit_UnaryOp(self, unary):
- pattern = unary_map[unary.op.__class__]
- operand_explanation, operand_result = self.visit(unary.operand)
- explanation = pattern % (operand_explanation,)
- co = self._compile(pattern % ("__exprinfo_expr",))
- try:
- result = self.frame.eval(co, __exprinfo_expr=operand_result)
- except Exception:
- raise Failure(explanation)
- return explanation, result
-
- def visit_BinOp(self, binop):
- left_explanation, left_result = self.visit(binop.left)
- right_explanation, right_result = self.visit(binop.right)
- symbol = operator_map[binop.op.__class__]
- explanation = "(%s %s %s)" % (left_explanation, symbol,
- right_explanation)
- source = "__exprinfo_left %s __exprinfo_right" % (symbol,)
- co = self._compile(source)
- try:
- result = self.frame.eval(co, __exprinfo_left=left_result,
- __exprinfo_right=right_result)
- except Exception:
- raise Failure(explanation)
- return explanation, result
-
- def visit_Call(self, call):
- func_explanation, func = self.visit(call.func)
- arg_explanations = []
- ns = {"__exprinfo_func" : func}
- arguments = []
- for arg in call.args:
- arg_explanation, arg_result = self.visit(arg)
- if isinstance(arg, _Starred):
- arg_name = "__exprinfo_star"
- ns[arg_name] = arg_result
- arguments.append("*%s" % (arg_name,))
- arg_explanations.append("*%s" % (arg_explanation,))
- else:
- arg_name = "__exprinfo_%s" % (len(ns),)
- ns[arg_name] = arg_result
- arguments.append(arg_name)
- arg_explanations.append(arg_explanation)
- for keyword in call.keywords:
- arg_explanation, arg_result = self.visit(keyword.value)
- if keyword.arg:
- arg_name = "__exprinfo_%s" % (len(ns),)
- keyword_source = "%s=%%s" % (keyword.arg)
- arguments.append(keyword_source % (arg_name,))
- arg_explanations.append(keyword_source % (arg_explanation,))
- else:
- arg_name = "__exprinfo_kwds"
- arguments.append("**%s" % (arg_name,))
- arg_explanations.append("**%s" % (arg_explanation,))
-
- ns[arg_name] = arg_result
-
- if getattr(call, 'starargs', None):
- arg_explanation, arg_result = self.visit(call.starargs)
- arg_name = "__exprinfo_star"
- ns[arg_name] = arg_result
- arguments.append("*%s" % (arg_name,))
- arg_explanations.append("*%s" % (arg_explanation,))
-
- if getattr(call, 'kwargs', None):
- arg_explanation, arg_result = self.visit(call.kwargs)
- arg_name = "__exprinfo_kwds"
- ns[arg_name] = arg_result
- arguments.append("**%s" % (arg_name,))
- arg_explanations.append("**%s" % (arg_explanation,))
- args_explained = ", ".join(arg_explanations)
- explanation = "%s(%s)" % (func_explanation, args_explained)
- args = ", ".join(arguments)
- source = "__exprinfo_func(%s)" % (args,)
- co = self._compile(source)
- try:
- result = self.frame.eval(co, **ns)
- except Exception:
- raise Failure(explanation)
- pattern = "%s\n{%s = %s\n}"
- rep = self.frame.repr(result)
- explanation = pattern % (rep, rep, explanation)
- return explanation, result
-
- def _is_builtin_name(self, name):
- pattern = "%r not in globals() and %r not in locals()"
- source = pattern % (name.id, name.id)
- co = self._compile(source)
- try:
- return self.frame.eval(co)
- except Exception:
- return False
-
- def visit_Attribute(self, attr):
- if not isinstance(attr.ctx, ast.Load):
- return self.generic_visit(attr)
- source_explanation, source_result = self.visit(attr.value)
- explanation = "%s.%s" % (source_explanation, attr.attr)
- source = "__exprinfo_expr.%s" % (attr.attr,)
- co = self._compile(source)
- try:
- try:
- result = self.frame.eval(co, __exprinfo_expr=source_result)
- except AttributeError:
- # Maybe the attribute name needs to be mangled?
- if not attr.attr.startswith("__") or attr.attr.endswith("__"):
- raise
- source = "getattr(__exprinfo_expr.__class__, '__name__', '')"
- co = self._compile(source)
- class_name = self.frame.eval(co, __exprinfo_expr=source_result)
- mangled_attr = "_" + class_name + attr.attr
- source = "__exprinfo_expr.%s" % (mangled_attr,)
- co = self._compile(source)
- result = self.frame.eval(co, __exprinfo_expr=source_result)
- except Exception:
- raise Failure(explanation)
- explanation = "%s\n{%s = %s.%s\n}" % (self.frame.repr(result),
- self.frame.repr(result),
- source_explanation, attr.attr)
- # Check if the attr is from an instance.
- source = "%r in getattr(__exprinfo_expr, '__dict__', {})"
- source = source % (attr.attr,)
- co = self._compile(source)
- try:
- from_instance = self.frame.eval(co, __exprinfo_expr=source_result)
- except Exception:
- from_instance = None
- if from_instance is None or self.frame.is_true(from_instance):
- rep = self.frame.repr(result)
- pattern = "%s\n{%s = %s\n}"
- explanation = pattern % (rep, rep, explanation)
- return explanation, result
-
- def visit_Assert(self, assrt):
- test_explanation, test_result = self.visit(assrt.test)
- explanation = "assert %s" % (test_explanation,)
- if not self.frame.is_true(test_result):
- try:
- raise util.BuiltinAssertionError
- except Exception:
- raise Failure(explanation)
- return explanation, test_result
-
- def visit_Assign(self, assign):
- value_explanation, value_result = self.visit(assign.value)
- explanation = "... = %s" % (value_explanation,)
- name = ast.Name("__exprinfo_expr", ast.Load(),
- lineno=assign.value.lineno,
- col_offset=assign.value.col_offset)
- new_assign = ast.Assign(assign.targets, name, lineno=assign.lineno,
- col_offset=assign.col_offset)
- mod = ast.Module([new_assign])
- co = self._compile(mod, "exec")
- try:
- self.frame.exec_(co, __exprinfo_expr=value_result)
- except Exception:
- raise Failure(explanation)
- return explanation, value_result
-
diff --git a/_pytest/assertion/rewrite.py b/_pytest/assertion/rewrite.py
index 921d17a10..50d8062ae 100644
--- a/_pytest/assertion/rewrite.py
+++ b/_pytest/assertion/rewrite.py
@@ -1,6 +1,7 @@
"""Rewrite assertion AST to produce nice error messages"""
import ast
+import _ast
import errno
import itertools
import imp
@@ -50,6 +51,7 @@ class AssertionRewritingHook(object):
self.session = None
self.modules = {}
self._register_with_pkg_resources()
+ self._must_rewrite = set()
def set_session(self, session):
self.session = session
@@ -86,7 +88,7 @@ class AssertionRewritingHook(object):
fn = os.path.join(pth, name.rpartition(".")[2] + ".py")
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
# 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
return self
- def _should_rewrite(self, fn_pypath, state):
+ def _should_rewrite(self, name, fn_pypath, state):
# always rewrite conftest files
fn = str(fn_pypath)
if fn_pypath.basename == 'conftest.py':
@@ -160,8 +162,29 @@ class AssertionRewritingHook(object):
finally:
self.session = session
del session
+ else:
+ for marked in self._must_rewrite:
+ if marked.startswith(name):
+ return True
return False
+ def mark_rewrite(self, *names):
+ """Mark import names as needing to be re-written.
+
+ The named module or package as well as any nested modules will
+ be re-written on import.
+ """
+ already_imported = set(names).intersection(set(sys.modules))
+ if already_imported:
+ 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):
# If there is an existing module object named 'fullname' in
# sys.modules, the loader must use that existing module. (Otherwise,
@@ -876,6 +899,8 @@ class AssertionRewriter(ast.NodeVisitor):
def visit_Compare(self, comp):
self.push_format_context()
left_res, left_expl = self.visit(comp.left)
+ if isinstance(comp.left, (_ast.Compare, _ast.BoolOp)):
+ left_expl = "({0})".format(left_expl)
res_variables = [self.variable() for i in range(len(comp.ops))]
load_names = [ast.Name(v, ast.Load()) for v in res_variables]
store_names = [ast.Name(v, ast.Store()) for v in res_variables]
@@ -885,6 +910,8 @@ class AssertionRewriter(ast.NodeVisitor):
results = [left_res]
for i, op, next_operand in it:
next_res, next_expl = self.visit(next_operand)
+ if isinstance(next_operand, (_ast.Compare, _ast.BoolOp)):
+ next_expl = "({0})".format(next_expl)
results.append(next_res)
sym = binop_map[op.__class__]
syms.append(ast.Str(sym))
diff --git a/_pytest/assertion/util.py b/_pytest/assertion/util.py
index 8bf425caf..2481cf34c 100644
--- a/_pytest/assertion/util.py
+++ b/_pytest/assertion/util.py
@@ -38,44 +38,11 @@ def format_explanation(explanation):
displaying diffs.
"""
explanation = ecu(explanation)
- explanation = _collapse_false(explanation)
lines = _split_explanation(explanation)
result = _format_lines(lines)
return u('\n').join(result)
-def _collapse_false(explanation):
- """Collapse expansions of False
-
- So this strips out any "assert False\n{where False = ...\n}"
- blocks.
- """
- where = 0
- while True:
- start = where = explanation.find("False\n{False = ", where)
- if where == -1:
- break
- level = 0
- prev_c = explanation[start]
- for i, c in enumerate(explanation[start:]):
- if prev_c + c == "\n{":
- level += 1
- elif prev_c + c == "\n}":
- level -= 1
- if not level:
- break
- prev_c = c
- else:
- raise AssertionError("unbalanced braces: %r" % (explanation,))
- end = start + i
- where = end
- if explanation[end - 1] == '\n':
- explanation = (explanation[:start] + explanation[start+15:end-1] +
- explanation[end+1:])
- where -= 17
- return explanation
-
-
def _split_explanation(explanation):
"""Return a list of individual lines in the explanation
diff --git a/_pytest/compat.py b/_pytest/compat.py
new file mode 100644
index 000000000..1a2f1e24a
--- /dev/null
+++ b/_pytest/compat.py
@@ -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
diff --git a/_pytest/config.py b/_pytest/config.py
index 577425d6a..46c53ce68 100644
--- a/_pytest/config.py
+++ b/_pytest/config.py
@@ -5,11 +5,13 @@ import traceback
import types
import warnings
+import pkg_resources
import py
# DON't import pytest here because it causes import cycle troubles
import sys, os
import _pytest._code
import _pytest.hookspec # the extension point definitions
+import _pytest.assertion
from _pytest._pluggy import PluginManager, HookimplMarker, HookspecMarker
hookimpl = HookimplMarker("pytest")
@@ -69,7 +71,7 @@ class UsageError(Exception):
_preinit = []
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 "
"junitxml resultlog doctest cacheprovider freeze_support "
"setuponly setupplan").split()
@@ -104,6 +106,7 @@ def get_plugin_manager():
return get_config().pluginmanager
def _prepareconfig(args=None, plugins=None):
+ warning = None
if args is None:
args = sys.argv[1:]
elif isinstance(args, py.path.local):
@@ -112,6 +115,10 @@ def _prepareconfig(args=None, plugins=None):
if not isinstance(args, str):
raise ValueError("not a string or argument list: %r" % (args,))
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()
pluginmanager = config.pluginmanager
try:
@@ -121,6 +128,8 @@ def _prepareconfig(args=None, plugins=None):
pluginmanager.consider_pluginarg(plugin)
else:
pluginmanager.register(plugin)
+ if warning:
+ config.warn('C1', warning)
return pluginmanager.hook.pytest_cmdline_parse(
pluginmanager=pluginmanager, args=args)
except BaseException:
@@ -159,6 +168,9 @@ class PytestPluginManager(PluginManager):
self.trace.root.setwriter(err.write)
self.enable_tracing()
+ # Config._consider_importhook will set a real object if required.
+ self.rewrite_hook = _pytest.assertion.DummyRewriteHook()
+
def addhooks(self, module_or_class):
"""
.. deprecated:: 2.8
@@ -367,7 +379,9 @@ class PytestPluginManager(PluginManager):
self._import_plugin_specs(os.environ.get("PYTEST_PLUGINS"))
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):
if spec:
@@ -544,13 +558,18 @@ class ArgumentError(Exception):
class Argument:
- """class that mimics the necessary behaviour of optparse.Option """
+ """class that mimics the necessary behaviour of optparse.Option
+
+ its currently a least effort implementation
+ and ignoring choices and integer prefixes
+ https://docs.python.org/3/library/optparse.html#optparse-standard-option-types
+ """
_typ_map = {
'int': int,
'string': str,
- }
- # enable after some grace period for plugin writers
- TYPE_WARN = False
+ 'float': float,
+ 'complex': complex,
+ }
def __init__(self, *names, **attrs):
"""store parms in private vars for use in add_argument"""
@@ -558,17 +577,12 @@ class Argument:
self._short_opts = []
self._long_opts = []
self.dest = attrs.get('dest')
- if self.TYPE_WARN:
- try:
- help = attrs['help']
- if '%default' in help:
- warnings.warn(
- 'pytest now uses argparse. "%default" should be'
- ' changed to "%(default)s" ',
- FutureWarning,
- stacklevel=3)
- except KeyError:
- pass
+ if '%default' in (attrs.get('help') or ''):
+ warnings.warn(
+ 'pytest now uses argparse. "%default" should be'
+ ' changed to "%(default)s" ',
+ DeprecationWarning,
+ stacklevel=3)
try:
typ = attrs['type']
except KeyError:
@@ -577,25 +591,23 @@ class Argument:
# this might raise a keyerror as well, don't want to catch that
if isinstance(typ, py.builtin._basestring):
if typ == 'choice':
- if self.TYPE_WARN:
- warnings.warn(
- 'type argument to addoption() is a string %r.'
- ' For parsearg this is optional and when supplied '
- ' should be a type.'
- ' (options: %s)' % (typ, names),
- FutureWarning,
- stacklevel=3)
+ warnings.warn(
+ 'type argument to addoption() is a string %r.'
+ ' For parsearg this is optional and when supplied '
+ ' should be a type.'
+ ' (options: %s)' % (typ, names),
+ DeprecationWarning,
+ stacklevel=3)
# argparse expects a type here take it from
# the type of the first element
attrs['type'] = type(attrs['choices'][0])
else:
- if self.TYPE_WARN:
- warnings.warn(
- 'type argument to addoption() is a string %r.'
- ' For parsearg this should be a type.'
- ' (options: %s)' % (typ, names),
- FutureWarning,
- stacklevel=3)
+ warnings.warn(
+ 'type argument to addoption() is a string %r.'
+ ' For parsearg this should be a type.'
+ ' (options: %s)' % (typ, names),
+ DeprecationWarning,
+ stacklevel=3)
attrs['type'] = Argument._typ_map[typ]
# used in test_parseopt -> test_parse_defaultgetter
self.type = attrs['type']
@@ -662,20 +674,17 @@ class Argument:
self._long_opts.append(opt)
def __repr__(self):
- retval = 'Argument('
+ args = []
if self._short_opts:
- retval += '_short_opts: ' + repr(self._short_opts) + ', '
+ args += ['_short_opts: ' + repr(self._short_opts)]
if self._long_opts:
- retval += '_long_opts: ' + repr(self._long_opts) + ', '
- retval += 'dest: ' + repr(self.dest) + ', '
+ args += ['_long_opts: ' + repr(self._long_opts)]
+ args += ['dest: ' + repr(self.dest)]
if hasattr(self, 'type'):
- retval += 'type: ' + repr(self.type) + ', '
+ args += ['type: ' + repr(self.type)]
if hasattr(self, 'default'):
- retval += 'default: ' + repr(self.default) + ', '
- if retval[-2:] == ', ': # always long enough to test ("Argument(" )
- retval = retval[:-2]
- retval += ')'
- return retval
+ args += ['default: ' + repr(self.default)]
+ return 'Argument({0})'.format(', '.join(args))
class OptionGroup:
@@ -927,17 +936,62 @@ class Config(object):
self._parser.addini('addopts', 'extra command line options', 'args')
self._parser.addini('minversion', 'minimally required pytest version')
+ def _consider_importhook(self, args, entrypoint_name):
+ """Install the PEP 302 import hook if using assertion re-writing.
+
+ Needs to parse the --assert= option from the commandline
+ and find all the installed plugins to mark them for re-writing
+ by the importhook.
+ """
+ ns, unknown_args = self._parser.parse_known_and_unknown_args(args)
+ mode = ns.assertmode
+ if mode == 'rewrite':
+ try:
+ hook = _pytest.assertion.install_importhook(self)
+ except SystemError:
+ mode = 'plain'
+ else:
+ 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):
self._initini(args)
if addopts:
args[:] = shlex.split(os.environ.get('PYTEST_ADDOPTS', '')) + args
args[:] = self.getini("addopts") + args
self._checkversion()
+ entrypoint_name = 'pytest11'
+ self._consider_importhook(args, entrypoint_name)
self.pluginmanager.consider_preparse(args)
- try:
- self.pluginmanager.load_setuptools_entrypoints("pytest11")
- except ImportError as e:
- self.warn("I2", "could not load setuptools entry import: %s" % (e,))
+ self.pluginmanager.load_setuptools_entrypoints(entrypoint_name)
self.pluginmanager.consider_env()
self.known_args_namespace = ns = self._parser.parse_known_args(args, namespace=self.option.copy())
if self.known_args_namespace.confcutdir is None and self.inifile:
diff --git a/_pytest/pdb.py b/_pytest/debugging.py
similarity index 78%
rename from _pytest/pdb.py
rename to _pytest/debugging.py
index 84c920d17..d8cd5a4e3 100644
--- a/_pytest/pdb.py
+++ b/_pytest/debugging.py
@@ -8,21 +8,33 @@ import pytest
def pytest_addoption(parser):
group = parser.getgroup("general")
- group._addoption('--pdb',
- action="store_true", dest="usepdb", default=False,
- help="start the interactive Python debugger on errors.")
+ group._addoption(
+ '--pdb', dest="usepdb", action="store_true",
+ help="start the interactive Python debugger on errors.")
+ group._addoption(
+ '--pdbcls', dest="usepdb_cls", metavar="modulename:classname",
+ help="start a custom interactive Python debugger on errors. "
+ "For example: --pdbcls=IPython.core.debugger:Pdb")
def pytest_namespace():
return {'set_trace': pytestPDB().set_trace}
def pytest_configure(config):
- if config.getvalue("usepdb"):
+ if config.getvalue("usepdb") or config.getvalue("usepdb_cls"):
config.pluginmanager.register(PdbInvoke(), 'pdbinvoke')
+ if config.getvalue("usepdb_cls"):
+ modname, classname = config.getvalue("usepdb_cls").split(":")
+ __import__(modname)
+ pdb_cls = getattr(sys.modules[modname], classname)
+ else:
+ pdb_cls = pdb.Pdb
+ pytestPDB._pdb_cls = pdb_cls
old = (pdb.set_trace, pytestPDB._pluginmanager)
def fin():
pdb.set_trace, pytestPDB._pluginmanager = old
pytestPDB._config = None
+ pytestPDB._pdb_cls = pdb.Pdb
pdb.set_trace = pytest.set_trace
pytestPDB._pluginmanager = config.pluginmanager
pytestPDB._config = config
@@ -32,6 +44,7 @@ class pytestPDB:
""" Pseudo PDB that defers to the real pdb. """
_pluginmanager = None
_config = None
+ _pdb_cls = pdb.Pdb
def set_trace(self):
""" invoke PDB set_trace debugging, dropping any IO capturing. """
@@ -45,7 +58,7 @@ class pytestPDB:
tw.line()
tw.sep(">", "PDB set_trace (IO-capturing turned off)")
self._pluginmanager.hook.pytest_enter_pdb(config=self._config)
- pdb.Pdb().set_trace(frame)
+ self._pdb_cls().set_trace(frame)
class PdbInvoke:
@@ -98,7 +111,7 @@ def _find_last_non_hidden_frame(stack):
def post_mortem(t):
- class Pdb(pdb.Pdb):
+ class Pdb(pytestPDB._pdb_cls):
def get_stack(self, f, t):
stack, i = pdb.Pdb.get_stack(self, f, t)
if f is None:
diff --git a/_pytest/doctest.py b/_pytest/doctest.py
index 4411158ab..649077533 100644
--- a/_pytest/doctest.py
+++ b/_pytest/doctest.py
@@ -5,7 +5,7 @@ import traceback
import pytest
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):
if self.dtest is not None:
self.fixture_request = _setup_fixtures(self)
- globs = dict(getfixture=self.fixture_request.getfuncargvalue)
+ globs = dict(getfixture=self.fixture_request.getfixturevalue)
for name, value in self.fixture_request.getfuncargvalue('doctest_namespace').items():
globs[name] = value
self.dtest.globs.update(globs)
diff --git a/_pytest/fixtures.py b/_pytest/fixtures.py
new file mode 100644
index 000000000..ab437459b
--- /dev/null
+++ b/_pytest/fixtures.py
@@ -0,0 +1,1089 @@
+import sys
+import py
+import pytest
+import warnings
+
+import inspect
+import _pytest
+from _pytest._code.code import TerminalRepr
+from _pytest.compat import (
+ NOTSET, exc_clear, _format_args,
+ getfslineno, get_real_func,
+ is_generator, isclass, getimfunc,
+ getlocation, getfuncargnames,
+)
+
+def pytest_sessionstart(session):
+ session._fixturemanager = FixtureManager(session)
+
+
+scopename2class = {}
+
+
+scope2props = dict(session=())
+scope2props["module"] = ("fspath", "module")
+scope2props["class"] = scope2props["module"] + ("cls",)
+scope2props["instance"] = scope2props["class"] + ("instance", )
+scope2props["function"] = scope2props["instance"] + ("function", "keywords")
+
+def scopeproperty(name=None, doc=None):
+ def decoratescope(func):
+ scopename = name or func.__name__
+ def provide(self):
+ if func.__name__ in scope2props[self.scope]:
+ return func(self)
+ raise AttributeError("%s not available in %s-scoped context" % (
+ scopename, self.scope))
+ return property(provide, None, None, func.__doc__)
+ return decoratescope
+
+
+def pytest_namespace():
+ scopename2class.update({
+ 'class': pytest.Class,
+ 'module': pytest.Module,
+ 'function': pytest.Item,
+ })
+ return {
+ 'fixture': fixture,
+ 'yield_fixture': yield_fixture,
+ 'collect': {'_fillfuncargs': fillfixtures}
+ }
+
+
+def get_scope_node(node, scope):
+ cls = scopename2class.get(scope)
+ if cls is None:
+ if scope == "session":
+ return node.session
+ raise ValueError("unknown scope")
+ return node.getparent(cls)
+
+
+def add_funcarg_pseudo_fixture_def(collector, metafunc, fixturemanager):
+ # this function will transform all collected calls to a functions
+ # if they use direct funcargs (i.e. direct parametrization)
+ # because we want later test execution to be able to rely on
+ # an existing FixtureDef structure for all arguments.
+ # XXX we can probably avoid this algorithm if we modify CallSpec2
+ # to directly care for creating the fixturedefs within its methods.
+ if not metafunc._calls[0].funcargs:
+ return # this function call does not have direct parametrization
+ # collect funcargs of all callspecs into a list of values
+ arg2params = {}
+ arg2scope = {}
+ for callspec in metafunc._calls:
+ for argname, argvalue in callspec.funcargs.items():
+ assert argname not in callspec.params
+ callspec.params[argname] = argvalue
+ arg2params_list = arg2params.setdefault(argname, [])
+ callspec.indices[argname] = len(arg2params_list)
+ arg2params_list.append(argvalue)
+ if argname not in arg2scope:
+ scopenum = callspec._arg2scopenum.get(argname,
+ scopenum_function)
+ arg2scope[argname] = scopes[scopenum]
+ callspec.funcargs.clear()
+
+ # register artificial FixtureDef's so that later at test execution
+ # time we can rely on a proper FixtureDef to exist for fixture setup.
+ arg2fixturedefs = metafunc._arg2fixturedefs
+ for argname, valuelist in arg2params.items():
+ # if we have a scope that is higher than function we need
+ # to make sure we only ever create an according fixturedef on
+ # a per-scope basis. We thus store and cache the fixturedef on the
+ # node related to the scope.
+ scope = arg2scope[argname]
+ node = None
+ if scope != "function":
+ node = get_scope_node(collector, scope)
+ if node is None:
+ assert scope == "class" and isinstance(collector, pytest.Module)
+ # use module-level collector for class-scope (for now)
+ node = collector
+ if node and argname in node._name2pseudofixturedef:
+ arg2fixturedefs[argname] = [node._name2pseudofixturedef[argname]]
+ else:
+ fixturedef = FixtureDef(fixturemanager, '', argname,
+ get_direct_param_fixture_func,
+ arg2scope[argname],
+ valuelist, False, False)
+ arg2fixturedefs[argname] = [fixturedef]
+ if node is not None:
+ node._name2pseudofixturedef[argname] = fixturedef
+
+
+
+def getfixturemarker(obj):
+ """ return fixturemarker or None if it doesn't exist or raised
+ exceptions."""
+ try:
+ return getattr(obj, "_pytestfixturefunction", None)
+ except KeyboardInterrupt:
+ raise
+ except Exception:
+ # some objects raise errors like request (from flask import request)
+ # we don't expect them to be fixture functions
+ return None
+
+
+
+def get_parametrized_fixture_keys(item, scopenum):
+ """ return list of keys for all parametrized arguments which match
+ the specified scope. """
+ assert scopenum < scopenum_function # function
+ try:
+ cs = item.callspec
+ except AttributeError:
+ pass
+ else:
+ # cs.indictes.items() is random order of argnames but
+ # then again different functions (items) can change order of
+ # arguments so it doesn't matter much probably
+ for argname, param_index in cs.indices.items():
+ if cs._arg2scopenum[argname] != scopenum:
+ continue
+ if scopenum == 0: # session
+ key = (argname, param_index)
+ elif scopenum == 1: # module
+ key = (argname, param_index, item.fspath)
+ elif scopenum == 2: # class
+ key = (argname, param_index, item.fspath, item.cls)
+ yield key
+
+
+# algorithm for sorting on a per-parametrized resource setup basis
+# it is called for scopenum==0 (session) first and performs sorting
+# down to the lower scopes such as to minimize number of "high scope"
+# setups and teardowns
+
+def reorder_items(items):
+ argkeys_cache = {}
+ for scopenum in range(0, scopenum_function):
+ argkeys_cache[scopenum] = d = {}
+ for item in items:
+ keys = set(get_parametrized_fixture_keys(item, scopenum))
+ if keys:
+ d[item] = keys
+ return reorder_items_atscope(items, set(), argkeys_cache, 0)
+
+def reorder_items_atscope(items, ignore, argkeys_cache, scopenum):
+ if scopenum >= scopenum_function or len(items) < 3:
+ return items
+ items_done = []
+ while 1:
+ items_before, items_same, items_other, newignore = \
+ slice_items(items, ignore, argkeys_cache[scopenum])
+ items_before = reorder_items_atscope(
+ items_before, ignore, argkeys_cache,scopenum+1)
+ if items_same is None:
+ # nothing to reorder in this scope
+ assert items_other is None
+ return items_done + items_before
+ items_done.extend(items_before)
+ items = items_same + items_other
+ ignore = newignore
+
+
+def slice_items(items, ignore, scoped_argkeys_cache):
+ # we pick the first item which uses a fixture instance in the
+ # requested scope and which we haven't seen yet. We slice the input
+ # items list into a list of items_nomatch, items_same and
+ # items_other
+ if scoped_argkeys_cache: # do we need to do work at all?
+ it = iter(items)
+ # first find a slicing key
+ for i, item in enumerate(it):
+ argkeys = scoped_argkeys_cache.get(item)
+ if argkeys is not None:
+ argkeys = argkeys.difference(ignore)
+ if argkeys: # found a slicing key
+ slicing_argkey = argkeys.pop()
+ items_before = items[:i]
+ items_same = [item]
+ items_other = []
+ # now slice the remainder of the list
+ for item in it:
+ argkeys = scoped_argkeys_cache.get(item)
+ if argkeys and slicing_argkey in argkeys and \
+ slicing_argkey not in ignore:
+ items_same.append(item)
+ else:
+ items_other.append(item)
+ newignore = ignore.copy()
+ newignore.add(slicing_argkey)
+ return (items_before, items_same, items_other, newignore)
+ return items, None, None, None
+
+
+
+class FuncargnamesCompatAttr:
+ """ helper class so that Metafunc, Function and FixtureRequest
+ don't need to each define the "funcargnames" compatibility attribute.
+ """
+ @property
+ def funcargnames(self):
+ """ alias attribute for ``fixturenames`` for pre-2.3 compatibility"""
+ return self.fixturenames
+
+
+def fillfixtures(function):
+ """ fill missing funcargs for a test function. """
+ try:
+ request = function._request
+ except AttributeError:
+ # XXX this special code path is only expected to execute
+ # with the oejskit plugin. It uses classes with funcargs
+ # and we thus have to work a bit to allow this.
+ fm = function.session._fixturemanager
+ fi = fm.getfixtureinfo(function.parent, function.obj, None)
+ function._fixtureinfo = fi
+ request = function._request = FixtureRequest(function)
+ request._fillfixtures()
+ # prune out funcargs for jstests
+ newfuncargs = {}
+ for name in fi.argnames:
+ newfuncargs[name] = function.funcargs[name]
+ function.funcargs = newfuncargs
+ else:
+ request._fillfixtures()
+
+
+
+def get_direct_param_fixture_func(request):
+ return request.param
+
+class FuncFixtureInfo:
+ def __init__(self, argnames, names_closure, name2fixturedefs):
+ self.argnames = argnames
+ self.names_closure = names_closure
+ self.name2fixturedefs = name2fixturedefs
+
+
+
+
+class FixtureRequest(FuncargnamesCompatAttr):
+ """ A request for a fixture from a test or fixture function.
+
+ A request object gives access to the requesting test context
+ and has an optional ``param`` attribute in case
+ the fixture is parametrized indirectly.
+ """
+
+ def __init__(self, pyfuncitem):
+ self._pyfuncitem = pyfuncitem
+ #: fixture for which this request is being performed
+ self.fixturename = None
+ #: Scope string, one of "function", "class", "module", "session"
+ self.scope = "function"
+ self._funcargs = {}
+ self._fixturedefs = {}
+ fixtureinfo = pyfuncitem._fixtureinfo
+ self._arg2fixturedefs = fixtureinfo.name2fixturedefs.copy()
+ self._arg2index = {}
+ self.fixturenames = fixtureinfo.names_closure
+ self._fixturemanager = pyfuncitem.session._fixturemanager
+
+ @property
+ def node(self):
+ """ underlying collection node (depends on current request scope)"""
+ return self._getscopeitem(self.scope)
+
+
+ def _getnextfixturedef(self, argname):
+ fixturedefs = self._arg2fixturedefs.get(argname, None)
+ if fixturedefs is None:
+ # we arrive here because of a a dynamic call to
+ # getfixturevalue(argname) usage which was naturally
+ # not known at parsing/collection time
+ fixturedefs = self._fixturemanager.getfixturedefs(
+ argname, self._pyfuncitem.parent.nodeid)
+ self._arg2fixturedefs[argname] = fixturedefs
+ # fixturedefs list is immutable so we maintain a decreasing index
+ index = self._arg2index.get(argname, 0) - 1
+ if fixturedefs is None or (-index > len(fixturedefs)):
+ raise FixtureLookupError(argname, self)
+ self._arg2index[argname] = index
+ return fixturedefs[index]
+
+ @property
+ def config(self):
+ """ the pytest config object associated with this request. """
+ return self._pyfuncitem.config
+
+
+ @scopeproperty()
+ def function(self):
+ """ test function object if the request has a per-function scope. """
+ return self._pyfuncitem.obj
+
+ @scopeproperty("class")
+ def cls(self):
+ """ class (can be None) where the test function was collected. """
+ clscol = self._pyfuncitem.getparent(pytest.Class)
+ if clscol:
+ return clscol.obj
+
+ @property
+ def instance(self):
+ """ instance (can be None) on which test function was collected. """
+ # unittest support hack, see _pytest.unittest.TestCaseFunction
+ try:
+ return self._pyfuncitem._testcase
+ except AttributeError:
+ function = getattr(self, "function", None)
+ if function is not None:
+ return py.builtin._getimself(function)
+
+ @scopeproperty()
+ def module(self):
+ """ python module object where the test function was collected. """
+ return self._pyfuncitem.getparent(pytest.Module).obj
+
+ @scopeproperty()
+ def fspath(self):
+ """ the file system path of the test module which collected this test. """
+ return self._pyfuncitem.fspath
+
+ @property
+ def keywords(self):
+ """ keywords/markers dictionary for the underlying node. """
+ return self.node.keywords
+
+ @property
+ def session(self):
+ """ pytest session object. """
+ return self._pyfuncitem.session
+
+ def addfinalizer(self, finalizer):
+ """ add finalizer/teardown function to be called after the
+ last test within the requesting test context finished
+ execution. """
+ # XXX usually this method is shadowed by fixturedef specific ones
+ self._addfinalizer(finalizer, scope=self.scope)
+
+ def _addfinalizer(self, finalizer, scope):
+ colitem = self._getscopeitem(scope)
+ self._pyfuncitem.session._setupstate.addfinalizer(
+ finalizer=finalizer, colitem=colitem)
+
+ def applymarker(self, marker):
+ """ Apply a marker to a single test function invocation.
+ This method is useful if you don't want to have a keyword/marker
+ on all function invocations.
+
+ :arg marker: a :py:class:`_pytest.mark.MarkDecorator` object
+ created by a call to ``pytest.mark.NAME(...)``.
+ """
+ try:
+ self.node.keywords[marker.markname] = marker
+ except AttributeError:
+ raise ValueError(marker)
+
+ def raiseerror(self, msg):
+ """ raise a FixtureLookupError with the given message. """
+ raise self._fixturemanager.FixtureLookupError(None, self, msg)
+
+ def _fillfixtures(self):
+ item = self._pyfuncitem
+ fixturenames = getattr(item, "fixturenames", self.fixturenames)
+ for argname in fixturenames:
+ if argname not in item.funcargs:
+ item.funcargs[argname] = self.getfixturevalue(argname)
+
+ def cached_setup(self, setup, teardown=None, scope="module", extrakey=None):
+ """ (deprecated) Return a testing resource managed by ``setup`` &
+ ``teardown`` calls. ``scope`` and ``extrakey`` determine when the
+ ``teardown`` function will be called so that subsequent calls to
+ ``setup`` would recreate the resource. With pytest-2.3 you often
+ do not need ``cached_setup()`` as you can directly declare a scope
+ on a fixture function and register a finalizer through
+ ``request.addfinalizer()``.
+
+ :arg teardown: function receiving a previously setup resource.
+ :arg setup: a no-argument function creating a resource.
+ :arg scope: a string value out of ``function``, ``class``, ``module``
+ or ``session`` indicating the caching lifecycle of the resource.
+ :arg extrakey: added to internal caching key of (funcargname, scope).
+ """
+ if not hasattr(self.config, '_setupcache'):
+ self.config._setupcache = {} # XXX weakref?
+ cachekey = (self.fixturename, self._getscopeitem(scope), extrakey)
+ cache = self.config._setupcache
+ try:
+ val = cache[cachekey]
+ except KeyError:
+ self._check_scope(self.fixturename, self.scope, scope)
+ val = setup()
+ cache[cachekey] = val
+ if teardown is not None:
+ def finalizer():
+ del cache[cachekey]
+ teardown(val)
+ self._addfinalizer(finalizer, scope=scope)
+ return val
+
+ def getfixturevalue(self, argname):
+ """ Dynamically run a named fixture function.
+
+ Declaring fixtures via function argument is recommended where possible.
+ But if you can only decide whether to use another fixture at test
+ setup time, you may use this function to retrieve it inside a fixture
+ or test function body.
+ """
+ return self._get_active_fixturedef(argname).cached_result[0]
+
+ def getfuncargvalue(self, argname):
+ """ Deprecated, use getfixturevalue. """
+ warnings.warn(
+ "use of getfuncargvalue is deprecated, use getfixturevalue",
+ DeprecationWarning)
+ return self.getfixturevalue(argname)
+
+ def _get_active_fixturedef(self, argname):
+ try:
+ return self._fixturedefs[argname]
+ except KeyError:
+ try:
+ fixturedef = self._getnextfixturedef(argname)
+ except FixtureLookupError:
+ if argname == "request":
+ class PseudoFixtureDef:
+ cached_result = (self, [0], None)
+ scope = "function"
+ return PseudoFixtureDef
+ raise
+ # remove indent to prevent the python3 exception
+ # from leaking into the call
+ result = self._getfixturevalue(fixturedef)
+ self._funcargs[argname] = result
+ self._fixturedefs[argname] = fixturedef
+ return fixturedef
+
+ def _get_fixturestack(self):
+ current = self
+ l = []
+ while 1:
+ fixturedef = getattr(current, "_fixturedef", None)
+ if fixturedef is None:
+ l.reverse()
+ return l
+ l.append(fixturedef)
+ current = current._parent_request
+
+ def _getfixturevalue(self, fixturedef):
+ # prepare a subrequest object before calling fixture function
+ # (latter managed by fixturedef)
+ argname = fixturedef.argname
+ funcitem = self._pyfuncitem
+ scope = fixturedef.scope
+ try:
+ param = funcitem.callspec.getparam(argname)
+ except (AttributeError, ValueError):
+ param = NOTSET
+ param_index = 0
+ if fixturedef.params is not None:
+ frame = inspect.stack()[3]
+ frameinfo = inspect.getframeinfo(frame[0])
+ source_path = frameinfo.filename
+ source_lineno = frameinfo.lineno
+ source_path = py.path.local(source_path)
+ if source_path.relto(funcitem.config.rootdir):
+ source_path = source_path.relto(funcitem.config.rootdir)
+ msg = (
+ "The requested fixture has no parameter defined for the "
+ "current test.\n\nRequested fixture '{0}' defined in:\n{1}"
+ "\n\nRequested here:\n{2}:{3}".format(
+ fixturedef.argname,
+ getlocation(fixturedef.func, funcitem.config.rootdir),
+ source_path,
+ source_lineno,
+ )
+ )
+ pytest.fail(msg)
+ else:
+ # indices might not be set if old-style metafunc.addcall() was used
+ param_index = funcitem.callspec.indices.get(argname, 0)
+ # if a parametrize invocation set a scope it will override
+ # the static scope defined with the fixture function
+ paramscopenum = funcitem.callspec._arg2scopenum.get(argname)
+ if paramscopenum is not None:
+ scope = scopes[paramscopenum]
+
+ subrequest = SubRequest(self, scope, param, param_index, fixturedef)
+
+ # check if a higher-level scoped fixture accesses a lower level one
+ subrequest._check_scope(argname, self.scope, scope)
+
+ # clear sys.exc_info before invoking the fixture (python bug?)
+ # if its not explicitly cleared it will leak into the call
+ exc_clear()
+ try:
+ # call the fixture function
+ val = fixturedef.execute(request=subrequest)
+ finally:
+ # if fixture function failed it might have registered finalizers
+ self.session._setupstate.addfinalizer(fixturedef.finish,
+ subrequest.node)
+ return val
+
+ def _check_scope(self, argname, invoking_scope, requested_scope):
+ if argname == "request":
+ return
+ if scopemismatch(invoking_scope, requested_scope):
+ # try to report something helpful
+ lines = self._factorytraceback()
+ pytest.fail("ScopeMismatch: You tried to access the %r scoped "
+ "fixture %r with a %r scoped request object, "
+ "involved factories\n%s" %(
+ (requested_scope, argname, invoking_scope, "\n".join(lines))),
+ pytrace=False)
+
+ def _factorytraceback(self):
+ lines = []
+ for fixturedef in self._get_fixturestack():
+ factory = fixturedef.func
+ fs, lineno = getfslineno(factory)
+ p = self._pyfuncitem.session.fspath.bestrelpath(fs)
+ args = _format_args(factory)
+ lines.append("%s:%d: def %s%s" %(
+ p, lineno, factory.__name__, args))
+ return lines
+
+ def _getscopeitem(self, scope):
+ if scope == "function":
+ # this might also be a non-function Item despite its attribute name
+ return self._pyfuncitem
+ node = get_scope_node(self._pyfuncitem, scope)
+ if node is None and scope == "class":
+ # fallback to function item itself
+ node = self._pyfuncitem
+ assert node
+ return node
+
+ def __repr__(self):
+ return "" %(self.node)
+
+
+class SubRequest(FixtureRequest):
+ """ a sub request for handling getting a fixture from a
+ test function/fixture. """
+ def __init__(self, request, scope, param, param_index, fixturedef):
+ self._parent_request = request
+ self.fixturename = fixturedef.argname
+ if param is not NOTSET:
+ self.param = param
+ self.param_index = param_index
+ self.scope = scope
+ self._fixturedef = fixturedef
+ self.addfinalizer = fixturedef.addfinalizer
+ self._pyfuncitem = request._pyfuncitem
+ self._funcargs = request._funcargs
+ self._fixturedefs = request._fixturedefs
+ self._arg2fixturedefs = request._arg2fixturedefs
+ self._arg2index = request._arg2index
+ self.fixturenames = request.fixturenames
+ self._fixturemanager = request._fixturemanager
+
+ def __repr__(self):
+ return "" % (self.fixturename, self._pyfuncitem)
+
+
+class ScopeMismatchError(Exception):
+ """ A fixture function tries to use a different fixture function which
+ which has a lower scope (e.g. a Session one calls a function one)
+ """
+
+scopes = "session module class function".split()
+scopenum_function = scopes.index("function")
+def scopemismatch(currentscope, newscope):
+ return scopes.index(newscope) > scopes.index(currentscope)
+
+
+class FixtureLookupError(LookupError):
+ """ could not return a requested Fixture (missing or invalid). """
+ def __init__(self, argname, request, msg=None):
+ self.argname = argname
+ self.request = request
+ self.fixturestack = request._get_fixturestack()
+ self.msg = msg
+
+ def formatrepr(self):
+ tblines = []
+ addline = tblines.append
+ stack = [self.request._pyfuncitem.obj]
+ stack.extend(map(lambda x: x.func, self.fixturestack))
+ msg = self.msg
+ if msg is not None:
+ # the last fixture raise an error, let's present
+ # it at the requesting side
+ stack = stack[:-1]
+ for function in stack:
+ fspath, lineno = getfslineno(function)
+ try:
+ lines, _ = inspect.getsourcelines(get_real_func(function))
+ except (IOError, IndexError):
+ error_msg = "file %s, line %s: source code not available"
+ addline(error_msg % (fspath, lineno+1))
+ else:
+ addline("file %s, line %s" % (fspath, lineno+1))
+ for i, line in enumerate(lines):
+ line = line.rstrip()
+ addline(" " + line)
+ if line.lstrip().startswith('def'):
+ break
+
+ if msg is None:
+ fm = self.request._fixturemanager
+ available = []
+ for name, fixturedef in fm._arg2fixturedefs.items():
+ parentid = self.request._pyfuncitem.parent.nodeid
+ faclist = list(fm._matchfactories(fixturedef, parentid))
+ if faclist:
+ available.append(name)
+ msg = "fixture %r not found" % (self.argname,)
+ msg += "\n available fixtures: %s" %(", ".join(available),)
+ msg += "\n use 'pytest --fixtures [testpath]' for help on them."
+
+ return FixtureLookupErrorRepr(fspath, lineno, tblines, msg, self.argname)
+
+class FixtureLookupErrorRepr(TerminalRepr):
+ def __init__(self, filename, firstlineno, tblines, errorstring, argname):
+ self.tblines = tblines
+ self.errorstring = errorstring
+ self.filename = filename
+ self.firstlineno = firstlineno
+ self.argname = argname
+
+ def toterminal(self, tw):
+ #tw.line("FixtureLookupError: %s" %(self.argname), red=True)
+ for tbline in self.tblines:
+ tw.line(tbline.rstrip())
+ for line in self.errorstring.split("\n"):
+ tw.line(" " + line.strip(), red=True)
+ tw.line()
+ tw.line("%s:%d" % (self.filename, self.firstlineno+1))
+
+
+def fail_fixturefunc(fixturefunc, msg):
+ fs, lineno = getfslineno(fixturefunc)
+ location = "%s:%s" % (fs, lineno+1)
+ source = _pytest._code.Source(fixturefunc)
+ pytest.fail(msg + ":\n\n" + str(source.indent()) + "\n" + location,
+ pytrace=False)
+
+def call_fixture_func(fixturefunc, request, kwargs):
+ yieldctx = is_generator(fixturefunc)
+ if yieldctx:
+ it = fixturefunc(**kwargs)
+ res = next(it)
+
+ def teardown():
+ try:
+ next(it)
+ except StopIteration:
+ pass
+ else:
+ fail_fixturefunc(fixturefunc,
+ "yield_fixture function has more than one 'yield'")
+
+ request.addfinalizer(teardown)
+ else:
+ res = fixturefunc(**kwargs)
+ return res
+
+class FixtureDef:
+ """ A container for a factory definition. """
+ def __init__(self, fixturemanager, baseid, argname, func, scope, params,
+ unittest=False, ids=None):
+ self._fixturemanager = fixturemanager
+ self.baseid = baseid or ''
+ self.has_location = baseid is not None
+ self.func = func
+ self.argname = argname
+ self.scope = scope
+ self.scopenum = scopes.index(scope or "function")
+ self.params = params
+ startindex = unittest and 1 or None
+ self.argnames = getfuncargnames(func, startindex=startindex)
+ self.unittest = unittest
+ self.ids = ids
+ self._finalizer = []
+
+ def addfinalizer(self, finalizer):
+ self._finalizer.append(finalizer)
+
+ def finish(self):
+ try:
+ while self._finalizer:
+ func = self._finalizer.pop()
+ func()
+ finally:
+ ihook = self._fixturemanager.session.ihook
+ ihook.pytest_fixture_post_finalizer(fixturedef=self)
+ # even if finalization fails, we invalidate
+ # the cached fixture value
+ if hasattr(self, "cached_result"):
+ del self.cached_result
+
+ def execute(self, request):
+ # get required arguments and register our own finish()
+ # with their finalization
+ for argname in self.argnames:
+ fixturedef = request._get_active_fixturedef(argname)
+ if argname != "request":
+ fixturedef.addfinalizer(self.finish)
+
+ my_cache_key = request.param_index
+ cached_result = getattr(self, "cached_result", None)
+ if cached_result is not None:
+ result, cache_key, err = cached_result
+ if my_cache_key == cache_key:
+ if err is not None:
+ py.builtin._reraise(*err)
+ else:
+ return result
+ # we have a previous but differently parametrized fixture instance
+ # so we need to tear it down before creating a new one
+ self.finish()
+ assert not hasattr(self, "cached_result")
+
+ ihook = self._fixturemanager.session.ihook
+ ihook.pytest_fixture_setup(fixturedef=self, request=request)
+
+ def __repr__(self):
+ return ("" %
+ (self.argname, self.scope, self.baseid))
+
+def pytest_fixture_setup(fixturedef, request):
+ """ Execution of fixture setup. """
+ kwargs = {}
+ for argname in fixturedef.argnames:
+ fixdef = request._get_active_fixturedef(argname)
+ result, arg_cache_key, exc = fixdef.cached_result
+ request._check_scope(argname, request.scope, fixdef.scope)
+ kwargs[argname] = result
+
+ fixturefunc = fixturedef.func
+ if fixturedef.unittest:
+ if request.instance is not None:
+ # bind the unbound method to the TestCase instance
+ fixturefunc = fixturedef.func.__get__(request.instance)
+ else:
+ # the fixture function needs to be bound to the actual
+ # request.instance so that code working with "fixturedef" behaves
+ # as expected.
+ if request.instance is not None:
+ fixturefunc = getimfunc(fixturedef.func)
+ if fixturefunc != fixturedef.func:
+ fixturefunc = fixturefunc.__get__(request.instance)
+ my_cache_key = request.param_index
+ try:
+ result = call_fixture_func(fixturefunc, request, kwargs)
+ except Exception:
+ fixturedef.cached_result = (None, my_cache_key, sys.exc_info())
+ raise
+ fixturedef.cached_result = (result, my_cache_key, None)
+ return result
+
+
+class FixtureFunctionMarker:
+ def __init__(self, scope, params, autouse=False, ids=None, name=None):
+ self.scope = scope
+ self.params = params
+ self.autouse = autouse
+ self.ids = ids
+ self.name = name
+
+ def __call__(self, function):
+ if isclass(function):
+ raise ValueError(
+ "class fixtures not supported (may be in the future)")
+ function._pytestfixturefunction = self
+ return function
+
+
+
+def fixture(scope="function", params=None, autouse=False, ids=None, name=None):
+ """ (return a) decorator to mark a fixture factory function.
+
+ This decorator can be used (with or or without parameters) to define
+ a fixture function. The name of the fixture function can later be
+ referenced to cause its invocation ahead of running tests: test
+ modules or classes can use the pytest.mark.usefixtures(fixturename)
+ marker. Test functions can directly use fixture names as input
+ arguments in which case the fixture instance returned from the fixture
+ function will be injected.
+
+ :arg scope: the scope for which this fixture is shared, one of
+ "function" (default), "class", "module", "session".
+
+ :arg params: an optional list of parameters which will cause multiple
+ invocations of the fixture function and all of the tests
+ using it.
+
+ :arg autouse: if True, the fixture func is activated for all tests that
+ can see it. If False (the default) then an explicit
+ reference is needed to activate the fixture.
+
+ :arg ids: list of string ids each corresponding to the params
+ so that they are part of the test id. If no ids are provided
+ they will be generated automatically from the params.
+
+ :arg name: the name of the fixture. This defaults to the name of the
+ decorated function. If a fixture is used in the same module in
+ which it is defined, the function name of the fixture will be
+ shadowed by the function arg that requests the fixture; one way
+ to resolve this is to name the decorated function
+ ``fixture_`` and then use
+ ``@pytest.fixture(name='')``.
+
+ Fixtures can optionally provide their values to test functions using a ``yield`` statement,
+ instead of ``return``. In this case, the code block after the ``yield`` statement is executed
+ as teardown code regardless of the test outcome. A fixture function must yield exactly once.
+ """
+ if callable(scope) and params is None and autouse == False:
+ # direct decoration
+ return FixtureFunctionMarker(
+ "function", params, autouse, name=name)(scope)
+ if params is not None and not isinstance(params, (list, tuple)):
+ params = list(params)
+ return FixtureFunctionMarker(scope, params, autouse, ids=ids, name=name)
+
+
+def yield_fixture(scope="function", params=None, autouse=False, ids=None, name=None):
+ """ (return a) decorator to mark a yield-fixture factory function.
+
+ .. deprecated:: 1.10
+ Use :py:func:`pytest.fixture` directly instead.
+ """
+ if callable(scope) and params is None and not autouse:
+ # direct decoration
+ return FixtureFunctionMarker(
+ "function", params, autouse, ids=ids, name=name)(scope)
+ else:
+ return FixtureFunctionMarker(scope, params, autouse, ids=ids, name=name)
+
+
+defaultfuncargprefixmarker = fixture()
+funcarg_prefix_warning = (
+ '{name}: declaring fixtures using "pytest_funcarg__" prefix is deprecated '
+ 'and scheduled to be removed in pytest 4.0. '
+ 'Please remove the prefix and use the @pytest.fixture decorator instead.')
+
+
+@fixture(scope="session")
+def pytestconfig(request):
+ """ the pytest config object with access to command line opts."""
+ return request.config
+
+
+class FixtureManager:
+ """
+ pytest fixtures definitions and information is stored and managed
+ from this class.
+
+ During collection fm.parsefactories() is called multiple times to parse
+ fixture function definitions into FixtureDef objects and internal
+ data structures.
+
+ During collection of test functions, metafunc-mechanics instantiate
+ a FuncFixtureInfo object which is cached per node/func-name.
+ This FuncFixtureInfo object is later retrieved by Function nodes
+ which themselves offer a fixturenames attribute.
+
+ The FuncFixtureInfo object holds information about fixtures and FixtureDefs
+ relevant for a particular function. An initial list of fixtures is
+ assembled like this:
+
+ - ini-defined usefixtures
+ - autouse-marked fixtures along the collection chain up from the function
+ - usefixtures markers at module/class/function level
+ - test function funcargs
+
+ Subsequently the funcfixtureinfo.fixturenames attribute is computed
+ as the closure of the fixtures needed to setup the initial fixtures,
+ i. e. fixtures needed by fixture functions themselves are appended
+ to the fixturenames list.
+
+ Upon the test-setup phases all fixturenames are instantiated, retrieved
+ by a lookup of their FuncFixtureInfo.
+ """
+
+ _argprefix = "pytest_funcarg__"
+ FixtureLookupError = FixtureLookupError
+ FixtureLookupErrorRepr = FixtureLookupErrorRepr
+
+ def __init__(self, session):
+ self.session = session
+ self.config = session.config
+ self._arg2fixturedefs = {}
+ self._holderobjseen = set()
+ self._arg2finish = {}
+ self._nodeid_and_autousenames = [("", self.config.getini("usefixtures"))]
+ session.config.pluginmanager.register(self, "funcmanage")
+
+
+ def getfixtureinfo(self, node, func, cls, funcargs=True):
+ if funcargs and not hasattr(node, "nofuncargs"):
+ if cls is not None:
+ startindex = 1
+ else:
+ startindex = None
+ argnames = getfuncargnames(func, startindex)
+ else:
+ argnames = ()
+ usefixtures = getattr(func, "usefixtures", None)
+ initialnames = argnames
+ if usefixtures is not None:
+ initialnames = usefixtures.args + initialnames
+ fm = node.session._fixturemanager
+ names_closure, arg2fixturedefs = fm.getfixtureclosure(initialnames,
+ node)
+ return FuncFixtureInfo(argnames, names_closure, arg2fixturedefs)
+
+ def pytest_plugin_registered(self, plugin):
+ nodeid = None
+ try:
+ p = py.path.local(plugin.__file__)
+ except AttributeError:
+ pass
+ else:
+ # construct the base nodeid which is later used to check
+ # what fixtures are visible for particular tests (as denoted
+ # by their test id)
+ if p.basename.startswith("conftest.py"):
+ nodeid = p.dirpath().relto(self.config.rootdir)
+ if p.sep != "/":
+ nodeid = nodeid.replace(p.sep, "/")
+ self.parsefactories(plugin, nodeid)
+
+ def _getautousenames(self, nodeid):
+ """ return a tuple of fixture names to be used. """
+ autousenames = []
+ for baseid, basenames in self._nodeid_and_autousenames:
+ if nodeid.startswith(baseid):
+ if baseid:
+ i = len(baseid)
+ nextchar = nodeid[i:i+1]
+ if nextchar and nextchar not in ":/":
+ continue
+ autousenames.extend(basenames)
+ # make sure autousenames are sorted by scope, scopenum 0 is session
+ autousenames.sort(
+ key=lambda x: self._arg2fixturedefs[x][-1].scopenum)
+ return autousenames
+
+ def getfixtureclosure(self, fixturenames, parentnode):
+ # collect the closure of all fixtures , starting with the given
+ # fixturenames as the initial set. As we have to visit all
+ # factory definitions anyway, we also return a arg2fixturedefs
+ # mapping so that the caller can reuse it and does not have
+ # to re-discover fixturedefs again for each fixturename
+ # (discovering matching fixtures for a given name/node is expensive)
+
+ parentid = parentnode.nodeid
+ fixturenames_closure = self._getautousenames(parentid)
+ def merge(otherlist):
+ for arg in otherlist:
+ if arg not in fixturenames_closure:
+ fixturenames_closure.append(arg)
+ merge(fixturenames)
+ arg2fixturedefs = {}
+ lastlen = -1
+ while lastlen != len(fixturenames_closure):
+ lastlen = len(fixturenames_closure)
+ for argname in fixturenames_closure:
+ if argname in arg2fixturedefs:
+ continue
+ fixturedefs = self.getfixturedefs(argname, parentid)
+ if fixturedefs:
+ arg2fixturedefs[argname] = fixturedefs
+ merge(fixturedefs[-1].argnames)
+ return fixturenames_closure, arg2fixturedefs
+
+ def pytest_generate_tests(self, metafunc):
+ for argname in metafunc.fixturenames:
+ faclist = metafunc._arg2fixturedefs.get(argname)
+ if faclist:
+ fixturedef = faclist[-1]
+ if fixturedef.params is not None:
+ func_params = getattr(getattr(metafunc.function, 'parametrize', None), 'args', [[None]])
+ # skip directly parametrized arguments
+ argnames = func_params[0]
+ if not isinstance(argnames, (tuple, list)):
+ argnames = [x.strip() for x in argnames.split(",") if x.strip()]
+ if argname not in func_params and argname not in argnames:
+ metafunc.parametrize(argname, fixturedef.params,
+ indirect=True, scope=fixturedef.scope,
+ ids=fixturedef.ids)
+ else:
+ continue # will raise FixtureLookupError at setup time
+
+ def pytest_collection_modifyitems(self, items):
+ # separate parametrized setups
+ items[:] = reorder_items(items)
+
+ def parsefactories(self, node_or_obj, nodeid=NOTSET, unittest=False):
+ if nodeid is not NOTSET:
+ holderobj = node_or_obj
+ else:
+ holderobj = node_or_obj.obj
+ nodeid = node_or_obj.nodeid
+ if holderobj in self._holderobjseen:
+ return
+ self._holderobjseen.add(holderobj)
+ autousenames = []
+ for name in dir(holderobj):
+ obj = getattr(holderobj, name, None)
+ # fixture functions have a pytest_funcarg__ prefix (pre-2.3 style)
+ # or are "@pytest.fixture" marked
+ marker = getfixturemarker(obj)
+ if marker is None:
+ if not name.startswith(self._argprefix):
+ continue
+ if not callable(obj):
+ continue
+ marker = defaultfuncargprefixmarker
+ self.config.warn('C1', funcarg_prefix_warning.format(name=name))
+ name = name[len(self._argprefix):]
+ elif not isinstance(marker, FixtureFunctionMarker):
+ # magic globals with __getattr__ might have got us a wrong
+ # fixture attribute
+ continue
+ else:
+ if marker.name:
+ name = marker.name
+ msg = 'fixtures cannot have "pytest_funcarg__" prefix ' \
+ 'and be decorated with @pytest.fixture:\n%s' % name
+ assert not name.startswith(self._argprefix), msg
+ fixturedef = FixtureDef(self, nodeid, name, obj,
+ marker.scope, marker.params,
+ unittest=unittest, ids=marker.ids)
+ faclist = self._arg2fixturedefs.setdefault(name, [])
+ if fixturedef.has_location:
+ faclist.append(fixturedef)
+ else:
+ # fixturedefs with no location are at the front
+ # so this inserts the current fixturedef after the
+ # existing fixturedefs from external plugins but
+ # before the fixturedefs provided in conftests.
+ i = len([f for f in faclist if not f.has_location])
+ faclist.insert(i, fixturedef)
+ if marker.autouse:
+ autousenames.append(name)
+ if autousenames:
+ self._nodeid_and_autousenames.append((nodeid or '', autousenames))
+
+ def getfixturedefs(self, argname, nodeid):
+ try:
+ fixturedefs = self._arg2fixturedefs[argname]
+ except KeyError:
+ return None
+ else:
+ return tuple(self._matchfactories(fixturedefs, nodeid))
+
+ def _matchfactories(self, fixturedefs, nodeid):
+ for fixturedef in fixturedefs:
+ if nodeid.startswith(fixturedef.baseid):
+ yield fixturedef
diff --git a/_pytest/main.py b/_pytest/main.py
index 845d5dd00..6119fcb03 100644
--- a/_pytest/main.py
+++ b/_pytest/main.py
@@ -34,8 +34,8 @@ def pytest_addoption(parser):
# "**/test_*.py", "**/*_test.py"]
#)
group = parser.getgroup("general", "running and selection options")
- group._addoption('-x', '--exitfirst', action="store_true", default=False,
- dest="exitfirst",
+ group._addoption('-x', '--exitfirst', action="store_const",
+ dest="maxfail", const=1,
help="exit instantly on first error or failed test."),
group._addoption('--maxfail', metavar="num",
action="store", type=int, dest="maxfail", default=0,
@@ -74,10 +74,10 @@ def pytest_namespace():
collect = dict(Item=Item, Collector=Collector, File=File, Session=Session)
return dict(collect=collect)
+
def pytest_configure(config):
pytest.config = config # compatibiltiy
- if config.option.exitfirst:
- config.option.maxfail = 1
+
def wrap_session(config, doit):
"""Skeleton command line program"""
diff --git a/_pytest/monkeypatch.py b/_pytest/monkeypatch.py
index d4c169d37..d1de4a679 100644
--- a/_pytest/monkeypatch.py
+++ b/_pytest/monkeypatch.py
@@ -5,11 +5,14 @@ import re
from py.builtin import _basestring
+import pytest
+
RE_IMPORT_ERROR_NAME = re.compile("^No module named (.*)$")
-def pytest_funcarg__monkeypatch(request):
- """The returned ``monkeypatch`` funcarg provides these
+@pytest.fixture
+def monkeypatch(request):
+ """The returned ``monkeypatch`` fixture provides these
helper methods to modify objects, dictionaries or os.environ::
monkeypatch.setattr(obj, name, value, raising=True)
@@ -26,7 +29,7 @@ def pytest_funcarg__monkeypatch(request):
parameter determines if a KeyError or AttributeError
will be raised if the set/deletion operation has no target.
"""
- mpatch = monkeypatch()
+ mpatch = MonkeyPatch()
request.addfinalizer(mpatch.undo)
return mpatch
@@ -93,7 +96,7 @@ class Notset:
notset = Notset()
-class monkeypatch:
+class MonkeyPatch:
""" Object keeping a record of setattr/item/env/syspath changes. """
def __init__(self):
diff --git a/_pytest/pytester.py b/_pytest/pytester.py
index fa63219d8..7831655d1 100644
--- a/_pytest/pytester.py
+++ b/_pytest/pytester.py
@@ -16,6 +16,7 @@ from _pytest._code import Source
import py
import pytest
from _pytest.main import Session, EXIT_OK
+from _pytest.assertion.rewrite import AssertionRewritingHook
def pytest_addoption(parser):
@@ -321,7 +322,8 @@ def linecomp(request):
return LineComp()
-def pytest_funcarg__LineMatcher(request):
+@pytest.fixture(name='LineMatcher')
+def LineMatcher_fixture(request):
return LineMatcher
@@ -684,8 +686,17 @@ class Testdir:
``pytest.main()`` instance should use.
:return: A :py:class:`HookRecorder` instance.
-
"""
+ # When running py.test inline any plugins active in the main
+ # test process are already imported. So this disables the
+ # warning which will trigger to say they can no longer be
+ # re-written, which is fine as they are already re-written.
+ orig_warn = AssertionRewritingHook._warn_already_imported
+ def revert():
+ AssertionRewritingHook._warn_already_imported = orig_warn
+ self.request.addfinalizer(revert)
+ AssertionRewritingHook._warn_already_imported = lambda *a: None
+
rec = []
class Collect:
def pytest_configure(x, config):
diff --git a/_pytest/python.py b/_pytest/python.py
index a0624839b..fb374381d 100644
--- a/_pytest/python.py
+++ b/_pytest/python.py
@@ -1,66 +1,30 @@
""" Python test discovery, setup and run of test functions. """
import fnmatch
-import functools
import inspect
-import re
-import types
import sys
-import math
import collections
+import math
import py
import pytest
-from _pytest._code.code import TerminalRepr
from _pytest.mark import MarkDecorator, MarkerError
-try:
- import enum
-except ImportError: # pragma: no cover
- # Only available in Python 3.4+ or as a backport
- enum = None
import _pytest
import _pytest._pluggy as pluggy
+from _pytest import fixtures
+from _pytest.compat import (
+ isclass, isfunction, is_generator, _escape_strings,
+ REGEX_TYPE, STRING_TYPES, NoneType, NOTSET,
+ get_real_func, getfslineno, safe_getattr,
+ getlocation, enum,
+)
cutdir2 = py.path.local(_pytest.__file__).dirpath()
cutdir1 = py.path.local(pluggy.__file__.rstrip("oc"))
-NoneType = type(None)
-NOTSET = object()
-isfunction = inspect.isfunction
-isclass = inspect.isclass
-callable = py.builtin.callable
-# used to work around a python2 exception info leak
-exc_clear = getattr(sys, 'exc_clear', lambda: None)
-# The type of re.compile objects is not exposed in Python.
-REGEX_TYPE = type(re.compile(''))
-
-_PY3 = sys.version_info > (3, 0)
-_PY2 = not _PY3
-
-
-if hasattr(inspect, 'signature'):
- def _format_args(func):
- return str(inspect.signature(func))
-else:
- def _format_args(func):
- return inspect.formatargspec(*inspect.getargspec(func))
-
-if sys.version_info[:2] == (2, 6):
- def isclass(object):
- """ Return true if the object is a class. Overrides inspect.isclass for
- python 2.6 because it will return True for objects which always return
- something on __getattr__ calls (see #1035).
- Backport of https://hg.python.org/cpython/rev/35bf8f7a8edc
- """
- return isinstance(object, (type, types.ClassType))
-
-def _has_positional_arg(func):
- return func.__code__.co_argcount
-
-
def filter_traceback(entry):
# entry.path might sometimes return a str object when the entry
# points to dynamically generated code
@@ -75,123 +39,6 @@ def filter_traceback(entry):
return p != cutdir1 and not p.relto(cutdir2)
-def get_real_func(obj):
- """ gets the real function object of the (possibly) wrapped object by
- functools.wraps or functools.partial.
- """
- while hasattr(obj, "__wrapped__"):
- obj = obj.__wrapped__
- if isinstance(obj, functools.partial):
- obj = obj.func
- return obj
-
-def getfslineno(obj):
- # xxx let decorators etc specify a sane ordering
- obj = get_real_func(obj)
- if hasattr(obj, 'place_as'):
- obj = obj.place_as
- fslineno = _pytest._code.getfslineno(obj)
- assert isinstance(fslineno[1], int), obj
- return fslineno
-
-def getimfunc(func):
- try:
- return func.__func__
- except AttributeError:
- try:
- return func.im_func
- except AttributeError:
- return func
-
-def safe_getattr(object, name, default):
- """ Like getattr but return default upon any Exception.
-
- Attribute access can potentially fail for 'evil' Python objects.
- See issue214
- """
- try:
- return getattr(object, name, default)
- except Exception:
- return default
-
-
-class FixtureFunctionMarker:
- def __init__(self, scope, params, autouse=False, ids=None, name=None):
- self.scope = scope
- self.params = params
- self.autouse = autouse
- self.ids = ids
- self.name = name
-
- def __call__(self, function):
- if isclass(function):
- raise ValueError(
- "class fixtures not supported (may be in the future)")
- function._pytestfixturefunction = self
- return function
-
-
-def fixture(scope="function", params=None, autouse=False, ids=None, name=None):
- """ (return a) decorator to mark a fixture factory function.
-
- This decorator can be used (with or or without parameters) to define
- a fixture function. The name of the fixture function can later be
- referenced to cause its invocation ahead of running tests: test
- modules or classes can use the pytest.mark.usefixtures(fixturename)
- marker. Test functions can directly use fixture names as input
- arguments in which case the fixture instance returned from the fixture
- function will be injected.
-
- :arg scope: the scope for which this fixture is shared, one of
- "function" (default), "class", "module", "session".
-
- :arg params: an optional list of parameters which will cause multiple
- invocations of the fixture function and all of the tests
- using it.
-
- :arg autouse: if True, the fixture func is activated for all tests that
- can see it. If False (the default) then an explicit
- reference is needed to activate the fixture.
-
- :arg ids: list of string ids each corresponding to the params
- so that they are part of the test id. If no ids are provided
- they will be generated automatically from the params.
-
- :arg name: the name of the fixture. This defaults to the name of the
- decorated function. If a fixture is used in the same module in
- which it is defined, the function name of the fixture will be
- shadowed by the function arg that requests the fixture; one way
- to resolve this is to name the decorated function
- ``fixture_`` and then use
- ``@pytest.fixture(name='')``.
-
- Fixtures can optionally provide their values to test functions using a ``yield`` statement,
- instead of ``return``. In this case, the code block after the ``yield`` statement is executed
- as teardown code regardless of the test outcome. A fixture function must yield exactly once.
- """
- if callable(scope) and params is None and autouse == False:
- # direct decoration
- return FixtureFunctionMarker(
- "function", params, autouse, name=name)(scope)
- if params is not None and not isinstance(params, (list, tuple)):
- params = list(params)
- return FixtureFunctionMarker(scope, params, autouse, ids=ids, name=name)
-
-
-def yield_fixture(scope="function", params=None, autouse=False, ids=None, name=None):
- """ (return a) decorator to mark a yield-fixture factory function.
-
- .. deprecated:: 1.10
- Use :py:func:`pytest.fixture` directly instead.
- """
- if callable(scope) and params is None and not autouse:
- # direct decoration
- return FixtureFunctionMarker(
- "function", params, autouse, ids=ids, name=name)(scope)
- else:
- return FixtureFunctionMarker(scope, params, autouse, ids=ids, name=name)
-
-defaultfuncargprefixmarker = fixture()
def pyobj_property(name):
def get(self):
@@ -272,28 +119,21 @@ def pytest_configure(config):
"all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures "
)
-def pytest_sessionstart(session):
- session._fixturemanager = FixtureManager(session)
-
@pytest.hookimpl(trylast=True)
def pytest_namespace():
raises.Exception = pytest.fail.Exception
return {
- 'fixture': fixture,
- 'yield_fixture': yield_fixture,
'raises': raises,
'approx': approx,
'collect': {
- 'Module': Module, 'Class': Class, 'Instance': Instance,
- 'Function': Function, 'Generator': Generator,
- '_fillfuncargs': fillfixtures}
+ 'Module': Module,
+ 'Class': Class,
+ 'Instance': Instance,
+ 'Function': Function,
+ 'Generator': Generator,
+ }
}
-@fixture(scope="session")
-def pytestconfig(request):
- """ the pytest config object with access to command line opts."""
- return request.config
-
@pytest.hookimpl(trylast=True)
def pytest_pyfunc_call(pyfuncitem):
@@ -354,12 +194,7 @@ def pytest_pycollect_makeitem(collector, name, obj):
def pytest_make_parametrize_id(config, val):
return None
-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
+
class PyobjContext(object):
module = pyobj_property("Module")
@@ -442,7 +277,7 @@ class PyCollector(PyobjMixin, pytest.Collector):
def istestfunction(self, obj, name):
return (
(self.funcnamefilter(name) or self.isnosetest(obj)) and
- safe_getattr(obj, "__call__", False) and getfixturemarker(obj) is None
+ safe_getattr(obj, "__call__", False) and fixtures.getfixturemarker(obj) is None
)
def istestclass(self, obj, name):
@@ -519,7 +354,7 @@ class PyCollector(PyobjMixin, pytest.Collector):
yield Function(name, parent=self, fixtureinfo=fixtureinfo)
else:
# add funcargs() as fixturedefs to fixtureinfo.arg2fixturedefs
- add_funcarg_pseudo_fixture_def(self, metafunc, fm)
+ fixtures.add_funcarg_pseudo_fixture_def(self, metafunc, fm)
for callspec in metafunc._calls:
subname = "%s[%s]" %(name, callspec.id)
@@ -528,68 +363,6 @@ class PyCollector(PyobjMixin, pytest.Collector):
fixtureinfo=fixtureinfo,
keywords={callspec.id:True})
-def add_funcarg_pseudo_fixture_def(collector, metafunc, fixturemanager):
- # this function will transform all collected calls to a functions
- # if they use direct funcargs (i.e. direct parametrization)
- # because we want later test execution to be able to rely on
- # an existing FixtureDef structure for all arguments.
- # XXX we can probably avoid this algorithm if we modify CallSpec2
- # to directly care for creating the fixturedefs within its methods.
- if not metafunc._calls[0].funcargs:
- return # this function call does not have direct parametrization
- # collect funcargs of all callspecs into a list of values
- arg2params = {}
- arg2scope = {}
- for callspec in metafunc._calls:
- for argname, argvalue in callspec.funcargs.items():
- assert argname not in callspec.params
- callspec.params[argname] = argvalue
- arg2params_list = arg2params.setdefault(argname, [])
- callspec.indices[argname] = len(arg2params_list)
- arg2params_list.append(argvalue)
- if argname not in arg2scope:
- scopenum = callspec._arg2scopenum.get(argname,
- scopenum_function)
- arg2scope[argname] = scopes[scopenum]
- callspec.funcargs.clear()
-
- # register artificial FixtureDef's so that later at test execution
- # time we can rely on a proper FixtureDef to exist for fixture setup.
- arg2fixturedefs = metafunc._arg2fixturedefs
- for argname, valuelist in arg2params.items():
- # if we have a scope that is higher than function we need
- # to make sure we only ever create an according fixturedef on
- # a per-scope basis. We thus store and cache the fixturedef on the
- # node related to the scope.
- scope = arg2scope[argname]
- node = None
- if scope != "function":
- node = get_scope_node(collector, scope)
- if node is None:
- assert scope == "class" and isinstance(collector, Module)
- # use module-level collector for class-scope (for now)
- node = collector
- if node and argname in node._name2pseudofixturedef:
- arg2fixturedefs[argname] = [node._name2pseudofixturedef[argname]]
- else:
- fixturedef = FixtureDef(fixturemanager, '', argname,
- get_direct_param_fixture_func,
- arg2scope[argname],
- valuelist, False, False)
- arg2fixturedefs[argname] = [fixturedef]
- if node is not None:
- node._name2pseudofixturedef[argname] = fixturedef
-
-
-def get_direct_param_fixture_func(request):
- return request.param
-
-class FuncFixtureInfo:
- def __init__(self, argnames, names_closure, name2fixturedefs):
- self.argnames = argnames
- self.names_closure = names_closure
- self.name2fixturedefs = name2fixturedefs
-
def _marked(func, mark):
""" Returns True if :func: is already marked with :mark:, False otherwise.
@@ -662,34 +435,51 @@ class Module(pytest.File, PyCollector):
"decorator) is not allowed. Use @pytest.mark.skip or "
"@pytest.mark.skipif instead."
)
- #print "imported test module", mod
self.config.pluginmanager.consider_module(mod)
return mod
def setup(self):
- setup_module = xunitsetup(self.obj, "setUpModule")
+ setup_module = _get_xunit_setup_teardown(self.obj, "setUpModule")
if setup_module is None:
- setup_module = xunitsetup(self.obj, "setup_module")
+ setup_module = _get_xunit_setup_teardown(self.obj, "setup_module")
if setup_module is not None:
- #XXX: nose compat hack, move to nose plugin
- # if it takes a positional arg, its probably a pytest style one
- # so we pass the current module object
- if _has_positional_arg(setup_module):
- setup_module(self.obj)
- else:
- setup_module()
- fin = getattr(self.obj, 'tearDownModule', None)
- if fin is None:
- fin = getattr(self.obj, 'teardown_module', None)
- if fin is not None:
- #XXX: nose compat hack, move to nose plugin
- # if it takes a positional arg, it's probably a pytest style one
- # so we pass the current module object
- if _has_positional_arg(fin):
- finalizer = lambda: fin(self.obj)
- else:
- finalizer = fin
- self.addfinalizer(finalizer)
+ setup_module()
+
+ teardown_module = _get_xunit_setup_teardown(self.obj, 'tearDownModule')
+ if teardown_module is None:
+ teardown_module = _get_xunit_setup_teardown(self.obj, 'teardown_module')
+ if teardown_module is not None:
+ self.addfinalizer(teardown_module)
+
+
+def _get_xunit_setup_teardown(holder, attr_name, param_obj=None):
+ """
+ Return a callable to perform xunit-style setup or teardown if
+ the function exists in the ``holder`` object.
+ The ``param_obj`` parameter is the parameter which will be passed to the function
+ when the callable is called without arguments, defaults to the ``holder`` object.
+ Return ``None`` if a suitable callable is not found.
+ """
+ param_obj = param_obj if param_obj is not None else holder
+ result = _get_xunit_func(holder, attr_name)
+ if result is not None:
+ arg_count = result.__code__.co_argcount
+ if inspect.ismethod(result):
+ arg_count -= 1
+ if arg_count:
+ return lambda: result(param_obj)
+ else:
+ return result
+
+
+def _get_xunit_func(obj, name):
+ """Return the attribute from the given object to be used as a setup/teardown
+ xunit-style function, but only if not marked as a fixture to
+ avoid calling it twice.
+ """
+ meth = getattr(obj, name, None)
+ if fixtures.getfixturemarker(meth) is None:
+ return meth
class Class(PyCollector):
@@ -702,7 +492,7 @@ class Class(PyCollector):
return [self._getcustomclass("Instance")(name="()", parent=self)]
def setup(self):
- setup_class = xunitsetup(self.obj, 'setup_class')
+ setup_class = _get_xunit_func(self.obj, 'setup_class')
if setup_class is not None:
setup_class = getattr(setup_class, 'im_func', setup_class)
setup_class = getattr(setup_class, '__func__', setup_class)
@@ -746,12 +536,12 @@ class FunctionMixin(PyobjMixin):
else:
setup_name = 'setup_function'
teardown_name = 'teardown_function'
- setup_func_or_method = xunitsetup(obj, setup_name)
+ setup_func_or_method = _get_xunit_setup_teardown(obj, setup_name, param_obj=self.obj)
if setup_func_or_method is not None:
- setup_func_or_method(self.obj)
- fin = getattr(obj, teardown_name, None)
- if fin is not None:
- self.addfinalizer(lambda: fin(self.obj))
+ setup_func_or_method()
+ teardown_func_or_method = _get_xunit_setup_teardown(obj, teardown_name, param_obj=self.obj)
+ if teardown_func_or_method is not None:
+ self.addfinalizer(teardown_func_or_method)
def _prunetraceback(self, excinfo):
if hasattr(self, '_obj') and not self.config.option.fulltrace:
@@ -812,6 +602,8 @@ class Generator(FunctionMixin, PyCollector):
raise ValueError("%r generated tests with non-unique name %r" %(self, name))
seen[name] = True
l.append(self.Function(name, self, args=args, callobj=call))
+ msg = 'yield tests are deprecated, and scheduled to be removed in pytest 4.0'
+ self.config.warn('C1', msg, fslocation=self.fspath)
return l
def getcallargs(self, obj):
@@ -834,40 +626,15 @@ def hasinit(obj):
return True
-
-def fillfixtures(function):
- """ fill missing funcargs for a test function. """
- try:
- request = function._request
- except AttributeError:
- # XXX this special code path is only expected to execute
- # with the oejskit plugin. It uses classes with funcargs
- # and we thus have to work a bit to allow this.
- fm = function.session._fixturemanager
- fi = fm.getfixtureinfo(function.parent, function.obj, None)
- function._fixtureinfo = fi
- request = function._request = FixtureRequest(function)
- request._fillfixtures()
- # prune out funcargs for jstests
- newfuncargs = {}
- for name in fi.argnames:
- newfuncargs[name] = function.funcargs[name]
- function.funcargs = newfuncargs
- else:
- request._fillfixtures()
-
-
-_notexists = object()
-
class CallSpec2(object):
def __init__(self, metafunc):
self.metafunc = metafunc
self.funcargs = {}
self._idlist = []
self.params = {}
- self._globalid = _notexists
+ self._globalid = NOTSET
self._globalid_args = set()
- self._globalparam = _notexists
+ self._globalparam = NOTSET
self._arg2scopenum = {} # used for sorting parametrized resources
self.keywords = {}
self.indices = {}
@@ -893,7 +660,7 @@ class CallSpec2(object):
try:
return self.params[name]
except KeyError:
- if self._globalparam is _notexists:
+ if self._globalparam is NOTSET:
raise ValueError(name)
return self._globalparam
@@ -916,25 +683,16 @@ class CallSpec2(object):
for x in funcargs:
self._checkargnotcontained(x)
self.funcargs.update(funcargs)
- if id is not _notexists:
+ if id is not NOTSET:
self._idlist.append(id)
- if param is not _notexists:
- assert self._globalparam is _notexists
+ if param is not NOTSET:
+ assert self._globalparam is NOTSET
self._globalparam = param
for arg in funcargs:
- self._arg2scopenum[arg] = scopenum_function
+ self._arg2scopenum[arg] = fixtures.scopenum_function
-class FuncargnamesCompatAttr:
- """ helper class so that Metafunc, Function and FixtureRequest
- don't need to each define the "funcargnames" compatibility attribute.
- """
- @property
- def funcargnames(self):
- """ alias attribute for ``fixturenames`` for pre-2.3 compatibility"""
- return self.fixturenames
-
-class Metafunc(FuncargnamesCompatAttr):
+class Metafunc(fixtures.FuncargnamesCompatAttr):
"""
Metafunc objects are passed to the ``pytest_generate_tests`` hook.
They help to inspect a test function and to generate tests according to
@@ -1026,7 +784,7 @@ class Metafunc(FuncargnamesCompatAttr):
if len(argnames) == 1:
argvalues = [(val,) for val in argvalues]
if not argvalues:
- argvalues = [(_notexists,) * len(argnames)]
+ argvalues = [(NOTSET,) * len(argnames)]
# we passed a empty list to parameterize, skip that test
#
fs, lineno = getfslineno(self.function)
@@ -1039,7 +797,7 @@ class Metafunc(FuncargnamesCompatAttr):
if scope is None:
scope = "function"
- scopenum = scopes.index(scope)
+ scopenum = fixtures.scopes.index(scope)
valtypes = {}
for arg in argnames:
if arg not in self.fixturenames:
@@ -1075,7 +833,7 @@ class Metafunc(FuncargnamesCompatAttr):
newcalls.append(newcallspec)
self._calls = newcalls
- def addcall(self, funcargs=None, id=_notexists, param=_notexists):
+ def addcall(self, funcargs=None, id=NOTSET, param=NOTSET):
""" (deprecated, use parametrize) Add a new call to the underlying
test function during the collection phase of a test run. Note that
request.addcall() is called during the test collection phase prior and
@@ -1100,7 +858,7 @@ class Metafunc(FuncargnamesCompatAttr):
funcargs = {}
if id is None:
raise ValueError("id=None not allowed")
- if id is _notexists:
+ if id is NOTSET:
id = len(self._calls)
id = str(id)
if id in self._ids:
@@ -1113,55 +871,6 @@ class Metafunc(FuncargnamesCompatAttr):
-if _PY3:
- import codecs
-
- 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:
- 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 _idval(val, argname, idx, idfn, config=None):
if idfn:
@@ -1177,7 +886,7 @@ def _idval(val, argname, idx, idfn, config=None):
if hook_id:
return hook_id
- if isinstance(val, (bytes, str)) or (_PY2 and isinstance(val, unicode)):
+ if isinstance(val, STRING_TYPES):
return _escape_strings(val)
elif isinstance(val, (float, int, bool, NoneType)):
return str(val)
@@ -1320,13 +1029,6 @@ def _showfixtures_main(config, session):
tw.line(" %s: no docstring available" %(loc,),
red=True)
-def getlocation(function, curdir):
- import inspect
- fn = py.path.local(inspect.getfile(function))
- lineno = py.builtin._getcode(function).co_firstlineno
- if fn.relto(curdir):
- fn = fn.relto(curdir)
- return "%s:%d" %(fn, lineno+1)
# builtin pytest.raises helper
@@ -1733,7 +1435,7 @@ class ApproxNonIterable(object):
# the basic pytest Function item
#
-class Function(FunctionMixin, pytest.Item, FuncargnamesCompatAttr):
+class Function(FunctionMixin, pytest.Item, fixtures.FuncargnamesCompatAttr):
""" a Function Item is responsible for setting up and executing a
Python test function.
"""
@@ -1774,7 +1476,7 @@ class Function(FunctionMixin, pytest.Item, FuncargnamesCompatAttr):
self._genid = callspec.id
if hasattr(callspec, "param"):
self.param = callspec.param
- self._request = FixtureRequest(self)
+ self._request = fixtures.FixtureRequest(self)
@property
def function(self):
@@ -1802,904 +1504,6 @@ class Function(FunctionMixin, pytest.Item, FuncargnamesCompatAttr):
def setup(self):
super(Function, self).setup()
- fillfixtures(self)
+ fixtures.fillfixtures(self)
-scope2props = dict(session=())
-scope2props["module"] = ("fspath", "module")
-scope2props["class"] = scope2props["module"] + ("cls",)
-scope2props["instance"] = scope2props["class"] + ("instance", )
-scope2props["function"] = scope2props["instance"] + ("function", "keywords")
-
-def scopeproperty(name=None, doc=None):
- def decoratescope(func):
- scopename = name or func.__name__
- def provide(self):
- if func.__name__ in scope2props[self.scope]:
- return func(self)
- raise AttributeError("%s not available in %s-scoped context" % (
- scopename, self.scope))
- return property(provide, None, None, func.__doc__)
- return decoratescope
-
-
-class FixtureRequest(FuncargnamesCompatAttr):
- """ A request for a fixture from a test or fixture function.
-
- A request object gives access to the requesting test context
- and has an optional ``param`` attribute in case
- the fixture is parametrized indirectly.
- """
-
- def __init__(self, pyfuncitem):
- self._pyfuncitem = pyfuncitem
- #: fixture for which this request is being performed
- self.fixturename = None
- #: Scope string, one of "function", "class", "module", "session"
- self.scope = "function"
- self._funcargs = {}
- self._fixturedefs = {}
- fixtureinfo = pyfuncitem._fixtureinfo
- self._arg2fixturedefs = fixtureinfo.name2fixturedefs.copy()
- self._arg2index = {}
- self.fixturenames = fixtureinfo.names_closure
- self._fixturemanager = pyfuncitem.session._fixturemanager
-
- @property
- def node(self):
- """ underlying collection node (depends on current request scope)"""
- return self._getscopeitem(self.scope)
-
-
- def _getnextfixturedef(self, argname):
- fixturedefs = self._arg2fixturedefs.get(argname, None)
- if fixturedefs is None:
- # we arrive here because of a a dynamic call to
- # getfuncargvalue(argname) usage which was naturally
- # not known at parsing/collection time
- fixturedefs = self._fixturemanager.getfixturedefs(
- argname, self._pyfuncitem.parent.nodeid)
- self._arg2fixturedefs[argname] = fixturedefs
- # fixturedefs list is immutable so we maintain a decreasing index
- index = self._arg2index.get(argname, 0) - 1
- if fixturedefs is None or (-index > len(fixturedefs)):
- raise FixtureLookupError(argname, self)
- self._arg2index[argname] = index
- return fixturedefs[index]
-
- @property
- def config(self):
- """ the pytest config object associated with this request. """
- return self._pyfuncitem.config
-
-
- @scopeproperty()
- def function(self):
- """ test function object if the request has a per-function scope. """
- return self._pyfuncitem.obj
-
- @scopeproperty("class")
- def cls(self):
- """ class (can be None) where the test function was collected. """
- clscol = self._pyfuncitem.getparent(pytest.Class)
- if clscol:
- return clscol.obj
-
- @property
- def instance(self):
- """ instance (can be None) on which test function was collected. """
- # unittest support hack, see _pytest.unittest.TestCaseFunction
- try:
- return self._pyfuncitem._testcase
- except AttributeError:
- function = getattr(self, "function", None)
- if function is not None:
- return py.builtin._getimself(function)
-
- @scopeproperty()
- def module(self):
- """ python module object where the test function was collected. """
- return self._pyfuncitem.getparent(pytest.Module).obj
-
- @scopeproperty()
- def fspath(self):
- """ the file system path of the test module which collected this test. """
- return self._pyfuncitem.fspath
-
- @property
- def keywords(self):
- """ keywords/markers dictionary for the underlying node. """
- return self.node.keywords
-
- @property
- def session(self):
- """ pytest session object. """
- return self._pyfuncitem.session
-
- def addfinalizer(self, finalizer):
- """ add finalizer/teardown function to be called after the
- last test within the requesting test context finished
- execution. """
- # XXX usually this method is shadowed by fixturedef specific ones
- self._addfinalizer(finalizer, scope=self.scope)
-
- def _addfinalizer(self, finalizer, scope):
- colitem = self._getscopeitem(scope)
- self._pyfuncitem.session._setupstate.addfinalizer(
- finalizer=finalizer, colitem=colitem)
-
- def applymarker(self, marker):
- """ Apply a marker to a single test function invocation.
- This method is useful if you don't want to have a keyword/marker
- on all function invocations.
-
- :arg marker: a :py:class:`_pytest.mark.MarkDecorator` object
- created by a call to ``pytest.mark.NAME(...)``.
- """
- try:
- self.node.keywords[marker.markname] = marker
- except AttributeError:
- raise ValueError(marker)
-
- def raiseerror(self, msg):
- """ raise a FixtureLookupError with the given message. """
- raise self._fixturemanager.FixtureLookupError(None, self, msg)
-
- def _fillfixtures(self):
- item = self._pyfuncitem
- fixturenames = getattr(item, "fixturenames", self.fixturenames)
- for argname in fixturenames:
- if argname not in item.funcargs:
- item.funcargs[argname] = self.getfuncargvalue(argname)
-
- def cached_setup(self, setup, teardown=None, scope="module", extrakey=None):
- """ (deprecated) Return a testing resource managed by ``setup`` &
- ``teardown`` calls. ``scope`` and ``extrakey`` determine when the
- ``teardown`` function will be called so that subsequent calls to
- ``setup`` would recreate the resource. With pytest-2.3 you often
- do not need ``cached_setup()`` as you can directly declare a scope
- on a fixture function and register a finalizer through
- ``request.addfinalizer()``.
-
- :arg teardown: function receiving a previously setup resource.
- :arg setup: a no-argument function creating a resource.
- :arg scope: a string value out of ``function``, ``class``, ``module``
- or ``session`` indicating the caching lifecycle of the resource.
- :arg extrakey: added to internal caching key of (funcargname, scope).
- """
- if not hasattr(self.config, '_setupcache'):
- self.config._setupcache = {} # XXX weakref?
- cachekey = (self.fixturename, self._getscopeitem(scope), extrakey)
- cache = self.config._setupcache
- try:
- val = cache[cachekey]
- except KeyError:
- self._check_scope(self.fixturename, self.scope, scope)
- val = setup()
- cache[cachekey] = val
- if teardown is not None:
- def finalizer():
- del cache[cachekey]
- teardown(val)
- self._addfinalizer(finalizer, scope=scope)
- return val
-
- def getfuncargvalue(self, argname):
- """ Dynamically retrieve a named fixture function argument.
-
- As of pytest-2.3, it is easier and usually better to access other
- fixture values by stating it as an input argument in the fixture
- function. If you only can decide about using another fixture at test
- setup time, you may use this function to retrieve it inside a fixture
- function body.
- """
- return self._get_active_fixturedef(argname).cached_result[0]
-
- def _get_active_fixturedef(self, argname):
- try:
- return self._fixturedefs[argname]
- except KeyError:
- try:
- fixturedef = self._getnextfixturedef(argname)
- except FixtureLookupError:
- if argname == "request":
- class PseudoFixtureDef:
- cached_result = (self, [0], None)
- scope = "function"
- return PseudoFixtureDef
- raise
- # remove indent to prevent the python3 exception
- # from leaking into the call
- result = self._getfuncargvalue(fixturedef)
- self._funcargs[argname] = result
- self._fixturedefs[argname] = fixturedef
- return fixturedef
-
- def _get_fixturestack(self):
- current = self
- l = []
- while 1:
- fixturedef = getattr(current, "_fixturedef", None)
- if fixturedef is None:
- l.reverse()
- return l
- l.append(fixturedef)
- current = current._parent_request
-
- def _getfuncargvalue(self, fixturedef):
- # prepare a subrequest object before calling fixture function
- # (latter managed by fixturedef)
- argname = fixturedef.argname
- funcitem = self._pyfuncitem
- scope = fixturedef.scope
- try:
- param = funcitem.callspec.getparam(argname)
- except (AttributeError, ValueError):
- param = NOTSET
- param_index = 0
- if fixturedef.params is not None:
- frame = inspect.stack()[3]
- frameinfo = inspect.getframeinfo(frame[0])
- source_path = frameinfo.filename
- source_lineno = frameinfo.lineno
- source_path = py.path.local(source_path)
- if source_path.relto(funcitem.config.rootdir):
- source_path = source_path.relto(funcitem.config.rootdir)
- msg = (
- "The requested fixture has no parameter defined for the "
- "current test.\n\nRequested fixture '{0}' defined in:\n{1}"
- "\n\nRequested here:\n{2}:{3}".format(
- fixturedef.argname,
- getlocation(fixturedef.func, funcitem.config.rootdir),
- source_path,
- source_lineno,
- )
- )
- pytest.fail(msg)
- else:
- # indices might not be set if old-style metafunc.addcall() was used
- param_index = funcitem.callspec.indices.get(argname, 0)
- # if a parametrize invocation set a scope it will override
- # the static scope defined with the fixture function
- paramscopenum = funcitem.callspec._arg2scopenum.get(argname)
- if paramscopenum is not None:
- scope = scopes[paramscopenum]
-
- subrequest = SubRequest(self, scope, param, param_index, fixturedef)
-
- # check if a higher-level scoped fixture accesses a lower level one
- subrequest._check_scope(argname, self.scope, scope)
-
- # clear sys.exc_info before invoking the fixture (python bug?)
- # if its not explicitly cleared it will leak into the call
- exc_clear()
- try:
- # call the fixture function
- val = fixturedef.execute(request=subrequest)
- finally:
- # if fixture function failed it might have registered finalizers
- self.session._setupstate.addfinalizer(fixturedef.finish,
- subrequest.node)
- return val
-
- def _check_scope(self, argname, invoking_scope, requested_scope):
- if argname == "request":
- return
- if scopemismatch(invoking_scope, requested_scope):
- # try to report something helpful
- lines = self._factorytraceback()
- pytest.fail("ScopeMismatch: You tried to access the %r scoped "
- "fixture %r with a %r scoped request object, "
- "involved factories\n%s" %(
- (requested_scope, argname, invoking_scope, "\n".join(lines))),
- pytrace=False)
-
- def _factorytraceback(self):
- lines = []
- for fixturedef in self._get_fixturestack():
- factory = fixturedef.func
- fs, lineno = getfslineno(factory)
- p = self._pyfuncitem.session.fspath.bestrelpath(fs)
- args = _format_args(factory)
- lines.append("%s:%d: def %s%s" %(
- p, lineno, factory.__name__, args))
- return lines
-
- def _getscopeitem(self, scope):
- if scope == "function":
- # this might also be a non-function Item despite its attribute name
- return self._pyfuncitem
- node = get_scope_node(self._pyfuncitem, scope)
- if node is None and scope == "class":
- # fallback to function item itself
- node = self._pyfuncitem
- assert node
- return node
-
- def __repr__(self):
- return "" %(self.node)
-
-
-class SubRequest(FixtureRequest):
- """ a sub request for handling getting a fixture from a
- test function/fixture. """
- def __init__(self, request, scope, param, param_index, fixturedef):
- self._parent_request = request
- self.fixturename = fixturedef.argname
- if param is not NOTSET:
- self.param = param
- self.param_index = param_index
- self.scope = scope
- self._fixturedef = fixturedef
- self.addfinalizer = fixturedef.addfinalizer
- self._pyfuncitem = request._pyfuncitem
- self._funcargs = request._funcargs
- self._fixturedefs = request._fixturedefs
- self._arg2fixturedefs = request._arg2fixturedefs
- self._arg2index = request._arg2index
- self.fixturenames = request.fixturenames
- self._fixturemanager = request._fixturemanager
-
- def __repr__(self):
- return "" % (self.fixturename, self._pyfuncitem)
-
-
-class ScopeMismatchError(Exception):
- """ A fixture function tries to use a different fixture function which
- which has a lower scope (e.g. a Session one calls a function one)
- """
-
-scopes = "session module class function".split()
-scopenum_function = scopes.index("function")
-def scopemismatch(currentscope, newscope):
- return scopes.index(newscope) > scopes.index(currentscope)
-
-
-class FixtureLookupError(LookupError):
- """ could not return a requested Fixture (missing or invalid). """
- def __init__(self, argname, request, msg=None):
- self.argname = argname
- self.request = request
- self.fixturestack = request._get_fixturestack()
- self.msg = msg
-
- def formatrepr(self):
- tblines = []
- addline = tblines.append
- stack = [self.request._pyfuncitem.obj]
- stack.extend(map(lambda x: x.func, self.fixturestack))
- msg = self.msg
- if msg is not None:
- # the last fixture raise an error, let's present
- # it at the requesting side
- stack = stack[:-1]
- for function in stack:
- fspath, lineno = getfslineno(function)
- try:
- lines, _ = inspect.getsourcelines(get_real_func(function))
- except (IOError, IndexError):
- error_msg = "file %s, line %s: source code not available"
- addline(error_msg % (fspath, lineno+1))
- else:
- addline("file %s, line %s" % (fspath, lineno+1))
- for i, line in enumerate(lines):
- line = line.rstrip()
- addline(" " + line)
- if line.lstrip().startswith('def'):
- break
-
- if msg is None:
- fm = self.request._fixturemanager
- available = []
- for name, fixturedef in fm._arg2fixturedefs.items():
- parentid = self.request._pyfuncitem.parent.nodeid
- faclist = list(fm._matchfactories(fixturedef, parentid))
- if faclist:
- available.append(name)
- msg = "fixture %r not found" % (self.argname,)
- msg += "\n available fixtures: %s" %(", ".join(available),)
- msg += "\n use 'pytest --fixtures [testpath]' for help on them."
-
- return FixtureLookupErrorRepr(fspath, lineno, tblines, msg, self.argname)
-
-class FixtureLookupErrorRepr(TerminalRepr):
- def __init__(self, filename, firstlineno, tblines, errorstring, argname):
- self.tblines = tblines
- self.errorstring = errorstring
- self.filename = filename
- self.firstlineno = firstlineno
- self.argname = argname
-
- def toterminal(self, tw):
- #tw.line("FixtureLookupError: %s" %(self.argname), red=True)
- for tbline in self.tblines:
- tw.line(tbline.rstrip())
- for line in self.errorstring.split("\n"):
- tw.line(" " + line.strip(), red=True)
- tw.line()
- tw.line("%s:%d" % (self.filename, self.firstlineno+1))
-
-class FixtureManager:
- """
- pytest fixtures definitions and information is stored and managed
- from this class.
-
- During collection fm.parsefactories() is called multiple times to parse
- fixture function definitions into FixtureDef objects and internal
- data structures.
-
- During collection of test functions, metafunc-mechanics instantiate
- a FuncFixtureInfo object which is cached per node/func-name.
- This FuncFixtureInfo object is later retrieved by Function nodes
- which themselves offer a fixturenames attribute.
-
- The FuncFixtureInfo object holds information about fixtures and FixtureDefs
- relevant for a particular function. An initial list of fixtures is
- assembled like this:
-
- - ini-defined usefixtures
- - autouse-marked fixtures along the collection chain up from the function
- - usefixtures markers at module/class/function level
- - test function funcargs
-
- Subsequently the funcfixtureinfo.fixturenames attribute is computed
- as the closure of the fixtures needed to setup the initial fixtures,
- i. e. fixtures needed by fixture functions themselves are appended
- to the fixturenames list.
-
- Upon the test-setup phases all fixturenames are instantiated, retrieved
- by a lookup of their FuncFixtureInfo.
- """
-
- _argprefix = "pytest_funcarg__"
- FixtureLookupError = FixtureLookupError
- FixtureLookupErrorRepr = FixtureLookupErrorRepr
-
- def __init__(self, session):
- self.session = session
- self.config = session.config
- self._arg2fixturedefs = {}
- self._holderobjseen = set()
- self._arg2finish = {}
- self._nodeid_and_autousenames = [("", self.config.getini("usefixtures"))]
- session.config.pluginmanager.register(self, "funcmanage")
-
-
- def getfixtureinfo(self, node, func, cls, funcargs=True):
- if funcargs and not hasattr(node, "nofuncargs"):
- if cls is not None:
- startindex = 1
- else:
- startindex = None
- argnames = getfuncargnames(func, startindex)
- else:
- argnames = ()
- usefixtures = getattr(func, "usefixtures", None)
- initialnames = argnames
- if usefixtures is not None:
- initialnames = usefixtures.args + initialnames
- fm = node.session._fixturemanager
- names_closure, arg2fixturedefs = fm.getfixtureclosure(initialnames,
- node)
- return FuncFixtureInfo(argnames, names_closure, arg2fixturedefs)
-
- def pytest_plugin_registered(self, plugin):
- nodeid = None
- try:
- p = py.path.local(plugin.__file__)
- except AttributeError:
- pass
- else:
- # construct the base nodeid which is later used to check
- # what fixtures are visible for particular tests (as denoted
- # by their test id)
- if p.basename.startswith("conftest.py"):
- nodeid = p.dirpath().relto(self.config.rootdir)
- if p.sep != "/":
- nodeid = nodeid.replace(p.sep, "/")
- self.parsefactories(plugin, nodeid)
-
- def _getautousenames(self, nodeid):
- """ return a tuple of fixture names to be used. """
- autousenames = []
- for baseid, basenames in self._nodeid_and_autousenames:
- if nodeid.startswith(baseid):
- if baseid:
- i = len(baseid)
- nextchar = nodeid[i:i+1]
- if nextchar and nextchar not in ":/":
- continue
- autousenames.extend(basenames)
- # make sure autousenames are sorted by scope, scopenum 0 is session
- autousenames.sort(
- key=lambda x: self._arg2fixturedefs[x][-1].scopenum)
- return autousenames
-
- def getfixtureclosure(self, fixturenames, parentnode):
- # collect the closure of all fixtures , starting with the given
- # fixturenames as the initial set. As we have to visit all
- # factory definitions anyway, we also return a arg2fixturedefs
- # mapping so that the caller can reuse it and does not have
- # to re-discover fixturedefs again for each fixturename
- # (discovering matching fixtures for a given name/node is expensive)
-
- parentid = parentnode.nodeid
- fixturenames_closure = self._getautousenames(parentid)
- def merge(otherlist):
- for arg in otherlist:
- if arg not in fixturenames_closure:
- fixturenames_closure.append(arg)
- merge(fixturenames)
- arg2fixturedefs = {}
- lastlen = -1
- while lastlen != len(fixturenames_closure):
- lastlen = len(fixturenames_closure)
- for argname in fixturenames_closure:
- if argname in arg2fixturedefs:
- continue
- fixturedefs = self.getfixturedefs(argname, parentid)
- if fixturedefs:
- arg2fixturedefs[argname] = fixturedefs
- merge(fixturedefs[-1].argnames)
- return fixturenames_closure, arg2fixturedefs
-
- def pytest_generate_tests(self, metafunc):
- for argname in metafunc.fixturenames:
- faclist = metafunc._arg2fixturedefs.get(argname)
- if faclist:
- fixturedef = faclist[-1]
- if fixturedef.params is not None:
- func_params = getattr(getattr(metafunc.function, 'parametrize', None), 'args', [[None]])
- # skip directly parametrized arguments
- argnames = func_params[0]
- if not isinstance(argnames, (tuple, list)):
- argnames = [x.strip() for x in argnames.split(",") if x.strip()]
- if argname not in func_params and argname not in argnames:
- metafunc.parametrize(argname, fixturedef.params,
- indirect=True, scope=fixturedef.scope,
- ids=fixturedef.ids)
- else:
- continue # will raise FixtureLookupError at setup time
-
- def pytest_collection_modifyitems(self, items):
- # separate parametrized setups
- items[:] = reorder_items(items)
-
- def parsefactories(self, node_or_obj, nodeid=NOTSET, unittest=False):
- if nodeid is not NOTSET:
- holderobj = node_or_obj
- else:
- holderobj = node_or_obj.obj
- nodeid = node_or_obj.nodeid
- if holderobj in self._holderobjseen:
- return
- self._holderobjseen.add(holderobj)
- autousenames = []
- for name in dir(holderobj):
- obj = getattr(holderobj, name, None)
- # fixture functions have a pytest_funcarg__ prefix (pre-2.3 style)
- # or are "@pytest.fixture" marked
- marker = getfixturemarker(obj)
- if marker is None:
- if not name.startswith(self._argprefix):
- continue
- if not callable(obj):
- continue
- marker = defaultfuncargprefixmarker
- name = name[len(self._argprefix):]
- elif not isinstance(marker, FixtureFunctionMarker):
- # magic globals with __getattr__ might have got us a wrong
- # fixture attribute
- continue
- else:
- if marker.name:
- name = marker.name
- assert not name.startswith(self._argprefix), name
- fixturedef = FixtureDef(self, nodeid, name, obj,
- marker.scope, marker.params,
- unittest=unittest, ids=marker.ids)
- faclist = self._arg2fixturedefs.setdefault(name, [])
- if fixturedef.has_location:
- faclist.append(fixturedef)
- else:
- # fixturedefs with no location are at the front
- # so this inserts the current fixturedef after the
- # existing fixturedefs from external plugins but
- # before the fixturedefs provided in conftests.
- i = len([f for f in faclist if not f.has_location])
- faclist.insert(i, fixturedef)
- if marker.autouse:
- autousenames.append(name)
- if autousenames:
- self._nodeid_and_autousenames.append((nodeid or '', autousenames))
-
- def getfixturedefs(self, argname, nodeid):
- try:
- fixturedefs = self._arg2fixturedefs[argname]
- except KeyError:
- return None
- else:
- return tuple(self._matchfactories(fixturedefs, nodeid))
-
- def _matchfactories(self, fixturedefs, nodeid):
- for fixturedef in fixturedefs:
- if nodeid.startswith(fixturedef.baseid):
- yield fixturedef
-
-
-def fail_fixturefunc(fixturefunc, msg):
- fs, lineno = getfslineno(fixturefunc)
- location = "%s:%s" % (fs, lineno+1)
- source = _pytest._code.Source(fixturefunc)
- pytest.fail(msg + ":\n\n" + str(source.indent()) + "\n" + location,
- pytrace=False)
-
-def call_fixture_func(fixturefunc, request, kwargs):
- yieldctx = is_generator(fixturefunc)
- if yieldctx:
- it = fixturefunc(**kwargs)
- res = next(it)
-
- def teardown():
- try:
- next(it)
- except StopIteration:
- pass
- else:
- fail_fixturefunc(fixturefunc,
- "yield_fixture function has more than one 'yield'")
-
- request.addfinalizer(teardown)
- else:
- res = fixturefunc(**kwargs)
- return res
-
-class FixtureDef:
- """ A container for a factory definition. """
- def __init__(self, fixturemanager, baseid, argname, func, scope, params,
- unittest=False, ids=None):
- self._fixturemanager = fixturemanager
- self.baseid = baseid or ''
- self.has_location = baseid is not None
- self.func = func
- self.argname = argname
- self.scope = scope
- self.scopenum = scopes.index(scope or "function")
- self.params = params
- startindex = unittest and 1 or None
- self.argnames = getfuncargnames(func, startindex=startindex)
- self.unittest = unittest
- self.ids = ids
- self._finalizer = []
-
- def addfinalizer(self, finalizer):
- self._finalizer.append(finalizer)
-
- def finish(self):
- try:
- while self._finalizer:
- func = self._finalizer.pop()
- func()
- finally:
- ihook = self._fixturemanager.session.ihook
- ihook.pytest_fixture_post_finalizer(fixturedef=self)
- # even if finalization fails, we invalidate
- # the cached fixture value
- if hasattr(self, "cached_result"):
- del self.cached_result
-
- def execute(self, request):
- # get required arguments and register our own finish()
- # with their finalization
- for argname in self.argnames:
- fixturedef = request._get_active_fixturedef(argname)
- if argname != "request":
- fixturedef.addfinalizer(self.finish)
-
- my_cache_key = request.param_index
- cached_result = getattr(self, "cached_result", None)
- if cached_result is not None:
- result, cache_key, err = cached_result
- if my_cache_key == cache_key:
- if err is not None:
- py.builtin._reraise(*err)
- else:
- return result
- # we have a previous but differently parametrized fixture instance
- # so we need to tear it down before creating a new one
- self.finish()
- assert not hasattr(self, "cached_result")
-
- ihook = self._fixturemanager.session.ihook
- ihook.pytest_fixture_setup(fixturedef=self, request=request)
-
- def __repr__(self):
- return ("" %
- (self.argname, self.scope, self.baseid))
-
-def pytest_fixture_setup(fixturedef, request):
- """ Execution of fixture setup. """
- kwargs = {}
- for argname in fixturedef.argnames:
- fixdef = request._get_active_fixturedef(argname)
- result, arg_cache_key, exc = fixdef.cached_result
- request._check_scope(argname, request.scope, fixdef.scope)
- kwargs[argname] = result
-
- fixturefunc = fixturedef.func
- if fixturedef.unittest:
- if request.instance is not None:
- # bind the unbound method to the TestCase instance
- fixturefunc = fixturedef.func.__get__(request.instance)
- else:
- # the fixture function needs to be bound to the actual
- # request.instance so that code working with "fixturedef" behaves
- # as expected.
- if request.instance is not None:
- fixturefunc = getimfunc(fixturedef.func)
- if fixturefunc != fixturedef.func:
- fixturefunc = fixturefunc.__get__(request.instance)
- my_cache_key = request.param_index
- try:
- result = call_fixture_func(fixturefunc, request, kwargs)
- except Exception:
- fixturedef.cached_result = (None, my_cache_key, sys.exc_info())
- raise
- fixturedef.cached_result = (result, my_cache_key, None)
- return result
-
-def num_mock_patch_args(function):
- """ return number of arguments used up by mock arguments (if any) """
- patchings = getattr(function, "patchings", None)
- if not patchings:
- return 0
- mock = sys.modules.get("mock", sys.modules.get("unittest.mock", None))
- if mock is not None:
- return len([p for p in patchings
- if not p.attribute_name and p.new is mock.DEFAULT])
- return len(patchings)
-
-
-def getfuncargnames(function, startindex=None):
- # XXX merge with main.py's varnames
- #assert not isclass(function)
- realfunction = function
- while hasattr(realfunction, "__wrapped__"):
- realfunction = realfunction.__wrapped__
- if startindex is None:
- startindex = inspect.ismethod(function) and 1 or 0
- if realfunction != function:
- startindex += num_mock_patch_args(function)
- function = realfunction
- if isinstance(function, functools.partial):
- argnames = inspect.getargs(_pytest._code.getrawcode(function.func))[0]
- partial = function
- argnames = argnames[len(partial.args):]
- if partial.keywords:
- for kw in partial.keywords:
- argnames.remove(kw)
- else:
- argnames = inspect.getargs(_pytest._code.getrawcode(function))[0]
- defaults = getattr(function, 'func_defaults',
- getattr(function, '__defaults__', None)) or ()
- numdefaults = len(defaults)
- if numdefaults:
- return tuple(argnames[startindex:-numdefaults])
- return tuple(argnames[startindex:])
-
-# algorithm for sorting on a per-parametrized resource setup basis
-# it is called for scopenum==0 (session) first and performs sorting
-# down to the lower scopes such as to minimize number of "high scope"
-# setups and teardowns
-
-def reorder_items(items):
- argkeys_cache = {}
- for scopenum in range(0, scopenum_function):
- argkeys_cache[scopenum] = d = {}
- for item in items:
- keys = set(get_parametrized_fixture_keys(item, scopenum))
- if keys:
- d[item] = keys
- return reorder_items_atscope(items, set(), argkeys_cache, 0)
-
-def reorder_items_atscope(items, ignore, argkeys_cache, scopenum):
- if scopenum >= scopenum_function or len(items) < 3:
- return items
- items_done = []
- while 1:
- items_before, items_same, items_other, newignore = \
- slice_items(items, ignore, argkeys_cache[scopenum])
- items_before = reorder_items_atscope(
- items_before, ignore, argkeys_cache,scopenum+1)
- if items_same is None:
- # nothing to reorder in this scope
- assert items_other is None
- return items_done + items_before
- items_done.extend(items_before)
- items = items_same + items_other
- ignore = newignore
-
-
-def slice_items(items, ignore, scoped_argkeys_cache):
- # we pick the first item which uses a fixture instance in the
- # requested scope and which we haven't seen yet. We slice the input
- # items list into a list of items_nomatch, items_same and
- # items_other
- if scoped_argkeys_cache: # do we need to do work at all?
- it = iter(items)
- # first find a slicing key
- for i, item in enumerate(it):
- argkeys = scoped_argkeys_cache.get(item)
- if argkeys is not None:
- argkeys = argkeys.difference(ignore)
- if argkeys: # found a slicing key
- slicing_argkey = argkeys.pop()
- items_before = items[:i]
- items_same = [item]
- items_other = []
- # now slice the remainder of the list
- for item in it:
- argkeys = scoped_argkeys_cache.get(item)
- if argkeys and slicing_argkey in argkeys and \
- slicing_argkey not in ignore:
- items_same.append(item)
- else:
- items_other.append(item)
- newignore = ignore.copy()
- newignore.add(slicing_argkey)
- return (items_before, items_same, items_other, newignore)
- return items, None, None, None
-
-def get_parametrized_fixture_keys(item, scopenum):
- """ return list of keys for all parametrized arguments which match
- the specified scope. """
- assert scopenum < scopenum_function # function
- try:
- cs = item.callspec
- except AttributeError:
- pass
- else:
- # cs.indictes.items() is random order of argnames but
- # then again different functions (items) can change order of
- # arguments so it doesn't matter much probably
- for argname, param_index in cs.indices.items():
- if cs._arg2scopenum[argname] != scopenum:
- continue
- if scopenum == 0: # session
- key = (argname, param_index)
- elif scopenum == 1: # module
- key = (argname, param_index, item.fspath)
- elif scopenum == 2: # class
- key = (argname, param_index, item.fspath, item.cls)
- yield key
-
-
-def xunitsetup(obj, name):
- meth = getattr(obj, name, None)
- if getfixturemarker(meth) is None:
- return meth
-
-def getfixturemarker(obj):
- """ return fixturemarker or None if it doesn't exist or raised
- exceptions."""
- try:
- return getattr(obj, "_pytestfixturefunction", None)
- except KeyboardInterrupt:
- raise
- except Exception:
- # some objects raise errors like request (from flask import request)
- # we don't expect them to be fixture functions
- return None
-
-scopename2class = {
- 'class': Class,
- 'module': Module,
- 'function': pytest.Item,
-}
-def get_scope_node(node, scope):
- cls = scopename2class.get(scope)
- if cls is None:
- if scope == "session":
- return node.session
- raise ValueError("unknown scope")
- return node.getparent(cls)
diff --git a/_pytest/runner.py b/_pytest/runner.py
index dff321a75..2ec24cd53 100644
--- a/_pytest/runner.py
+++ b/_pytest/runner.py
@@ -73,9 +73,9 @@ def runtestprotocol(item, log=True, nextitem=None):
rep = call_and_report(item, "setup", log)
reports = [rep]
if rep.passed:
- if item.config.option.setuponly or item.config.option.setupplan:
+ if item.config.option.setupshow:
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, "teardown", log,
nextitem=nextitem))
diff --git a/_pytest/setuponly.py b/_pytest/setuponly.py
index abb578da7..0ecdbc2bd 100644
--- a/_pytest/setuponly.py
+++ b/_pytest/setuponly.py
@@ -1,16 +1,20 @@
import pytest
import sys
+
def pytest_addoption(parser):
group = parser.getgroup("debugconfig")
group.addoption('--setuponly', '--setup-only', action="store_true",
- help="only setup fixtures, don't execute the tests.")
+ help="only setup fixtures, 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)
def pytest_fixture_setup(fixturedef, request):
yield
config = request.config
- if config.option.setuponly:
+ if config.option.setupshow:
if hasattr(request, 'param'):
# Save the fixture parameter so ._show_fixture_action() can
# display it now and during the teardown (in .finish()).
@@ -18,19 +22,22 @@ def pytest_fixture_setup(fixturedef, request):
if callable(fixturedef.ids):
fixturedef.cached_param = fixturedef.ids(request.param)
else:
- fixturedef.cached_param = fixturedef.ids[request.param_index]
+ fixturedef.cached_param = fixturedef.ids[
+ request.param_index]
else:
fixturedef.cached_param = request.param
_show_fixture_action(fixturedef, 'SETUP')
+
def pytest_fixture_post_finalizer(fixturedef):
if hasattr(fixturedef, "cached_result"):
config = fixturedef._fixturemanager.config
- if config.option.setuponly:
+ if config.option.setupshow:
_show_fixture_action(fixturedef, 'TEARDOWN')
if hasattr(fixturedef, "cached_param"):
del fixturedef.cached_param
+
def _show_fixture_action(fixturedef, msg):
config = fixturedef._fixturemanager.config
capman = config.pluginmanager.getplugin('capturemanager')
@@ -57,3 +64,9 @@ def _show_fixture_action(fixturedef, msg):
capman.resumecapture()
sys.stdout.write(out)
sys.stderr.write(err)
+
+
+@pytest.hookimpl(tryfirst=True)
+def pytest_cmdline_main(config):
+ if config.option.setuponly:
+ config.option.setupshow = True
diff --git a/_pytest/setupplan.py b/_pytest/setupplan.py
index c7c8ff60d..f0853dee5 100644
--- a/_pytest/setupplan.py
+++ b/_pytest/setupplan.py
@@ -1,10 +1,12 @@
import pytest
+
def pytest_addoption(parser):
group = parser.getgroup("debugconfig")
group.addoption('--setupplan', '--setup-plan', action="store_true",
- help="show what fixtures and tests would be executed but don't"
- " execute anything.")
+ help="show what fixtures and tests would be executed but "
+ "don't execute anything.")
+
@pytest.hookimpl(tryfirst=True)
def pytest_fixture_setup(fixturedef, request):
@@ -13,7 +15,9 @@ def pytest_fixture_setup(fixturedef, request):
fixturedef.cached_result = (None, None, None)
return fixturedef.cached_result
+
@pytest.hookimpl(tryfirst=True)
def pytest_cmdline_main(config):
if config.option.setupplan:
config.option.setuponly = True
+ config.option.setupshow = True
diff --git a/_pytest/skipping.py b/_pytest/skipping.py
index 18e038d2c..1d557a3fe 100644
--- a/_pytest/skipping.py
+++ b/_pytest/skipping.py
@@ -108,11 +108,7 @@ class MarkEvaluator:
def _getglobals(self):
d = {'os': os, 'sys': sys, 'config': self.item.config}
- func = self.item.obj
- try:
- d.update(func.__globals__)
- except AttributeError:
- d.update(func.func_globals)
+ d.update(self.item.obj.__globals__)
return d
def _istrue(self):
diff --git a/_pytest/terminal.py b/_pytest/terminal.py
index b88a19e13..7db0fda74 100644
--- a/_pytest/terminal.py
+++ b/_pytest/terminal.py
@@ -20,10 +20,15 @@ def pytest_addoption(parser):
group._addoption('-q', '--quiet', action="count",
dest="quiet", default=0, help="decrease verbosity."),
group._addoption('-r',
- action="store", dest="reportchars", default=None, metavar="chars",
+ action="store", dest="reportchars", default='', metavar="chars",
help="show extra test summary info as specified by chars (f)ailed, "
- "(E)error, (s)skipped, (x)failed, (X)passed (w)pytest-warnings "
- "(p)passed, (P)passed with output, (a)all except pP.")
+ "(E)error, (s)skipped, (x)failed, (X)passed, "
+ "(p)passed, (P)passed with output, (a)all except pP. "
+ "The pytest warnings are displayed at all times except when "
+ "--disable-pytest-warnings is set")
+ group._addoption('--disable-pytest-warnings', default=False,
+ dest='disablepytestwarnings', action='store_true',
+ help='disable warnings summary, overrides -r w flag')
group._addoption('-l', '--showlocals',
action="store_true", dest="showlocals", default=False,
help="show locals in tracebacks (disabled by default).")
@@ -52,6 +57,10 @@ def pytest_configure(config):
def getreportopt(config):
reportopts = ""
reportchars = config.option.reportchars
+ if not config.option.disablepytestwarnings and 'w' not in reportchars:
+ reportchars += 'w'
+ elif config.option.disablepytestwarnings and 'w' in reportchars:
+ reportchars = reportchars.replace('w', '')
if reportchars:
for char in reportchars:
if char not in reportopts and char != 'a':
diff --git a/_pytest/tmpdir.py b/_pytest/tmpdir.py
index ebc48dbe5..88e7250db 100644
--- a/_pytest/tmpdir.py
+++ b/_pytest/tmpdir.py
@@ -3,7 +3,7 @@ import re
import pytest
import py
-from _pytest.monkeypatch import monkeypatch
+from _pytest.monkeypatch import MonkeyPatch
class TempdirFactory:
@@ -92,7 +92,7 @@ def pytest_configure(config):
available at pytest_configure time, but ideally should be moved entirely
to the tmpdir_factory session fixture.
"""
- mp = monkeypatch()
+ mp = MonkeyPatch()
t = TempdirFactory(config)
config._cleanup.extend([mp.undo, t.finish])
mp.setattr(config, '_tmpdirhandler', t, raising=False)
diff --git a/appveyor.yml b/appveyor.yml
index 4b73645f7..2bd72db45 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -5,6 +5,13 @@ environment:
# using pytestbot account as detailed here:
# 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:
- echo Installed Pythons
- dir c:\Python*
diff --git a/doc/en/_templates/layout.html b/doc/en/_templates/layout.html
index 0ce480be3..2fc8e2a7f 100644
--- a/doc/en/_templates/layout.html
+++ b/doc/en/_templates/layout.html
@@ -1,19 +1,5 @@
{% extends "!layout.html" %}
{% block header %}
-
{{super()}}
{% endblock %}
{% block footer %}
diff --git a/doc/en/_templates/links.html b/doc/en/_templates/links.html
index 200258e16..56486a750 100644
--- a/doc/en/_templates/links.html
+++ b/doc/en/_templates/links.html
@@ -1,10 +1,5 @@
Useful Links
- -
-
- Sprint funding campaign
-
-
- The pytest Website
- Contribution Guide
- pytest @ PyPI
diff --git a/doc/en/announce/sprint2016.rst b/doc/en/announce/sprint2016.rst
index 86dd499c9..16e0f0e37 100644
--- a/doc/en/announce/sprint2016.rst
+++ b/doc/en/announce/sprint2016.rst
@@ -4,9 +4,9 @@ python testing sprint June 20th-26th 2016
.. image:: ../img/freiburg2.jpg
:width: 400
-The pytest core group is heading towards the biggest sprint
-in its history, to take place in the black forest town Freiburg
-in Germany. As of February 2016 we have started a `funding
+The pytest core group held the biggest sprint
+in its history in June 2016, taking place in the black forest town Freiburg
+in Germany. In February 2016 we started a `funding
campaign on Indiegogo to cover expenses
`_ The page also mentions
some preliminary topics:
@@ -35,73 +35,32 @@ some preliminary topics:
Participants
--------------
-Here are preliminary participants who said they are likely to come,
-given some expenses funding::
-
- Anatoly Bubenkoff, Netherlands
+Over 20 participants took part from 4 continents, including employees
+from Splunk, Personalkollen, Cobe.io, FanDuel and Dolby. Some newcomers
+mixed with developers who have worked on pytest since its beginning, and
+of course everyone in between.
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
- Tom Viner, UK
-
-
-
-Other contributors and experienced newcomers are invited to join as well
-but please send a mail to the pytest-dev mailing list if you intend to
-do so somewhat soon, also how much funding you need if so. And if you
-are working for a company and using pytest heavily you are welcome to
-join and we encourage your company to provide some funding for the
-sprint. They may see it, and rightfully so, as a very cheap and deep
-training which brings you together with the experts in the field :)
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
-- 20th social get together, initial hacking
-- 21/22th full sprint days
-- 23rd break day, hiking
-- 24/25th full sprint days
-- 26th departure
+Sprint activity was organised heavily around pairing, with plenty of group
+discusssions to take advantage of the high bandwidth, and lightning talks
+as well.
-We might adjust according to weather to make sure that if
-we do some hiking or excursion we'll have good weather.
-Freiburg is one of the sunniest places in Germany so
-it shouldn't be too much of a constraint.
-
-
-Accomodation
-----------------
-
-We'll see to arrange for renting a flat with multiple
-beds/rooms. Hotels are usually below 100 per night.
-The earlier we book the better.
Money / funding
---------------
-The Indiegogo campaign asks for 11000 USD which should cover
-the costs for flights and accomodation, renting a sprint place
-and maybe a bit of food as well.
-If your organisation wants to support the sprint but prefers
-to give money according to an invoice, get in contact with
-holger at http://merlinux.eu who can invoice your organisation
-properly.
+The Indiegogo campaign aimed for 11000 USD and in the end raised over
+12000, to reimburse travel costs, pay for a sprint venue and catering.
-If we have excess money we'll use for further sprint/travel
-funding for pytest/tox contributors.
+Excess money is reserved for further sprint/travel funding for pytest/tox
+contributors.
diff --git a/doc/en/assert.rst b/doc/en/assert.rst
index bde51fd35..2074cfe53 100644
--- a/doc/en/assert.rst
+++ b/doc/en/assert.rst
@@ -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
``.type``, ``.value`` and ``.traceback``.
-.. versionchanged:: 2.10
+.. versionchanged:: 3.0
In the context manager form you may use the keyword argument
``message`` to specify a custom failure message::
diff --git a/doc/en/backwards-compatibility.rst b/doc/en/backwards-compatibility.rst
new file mode 100644
index 000000000..8ceada52d
--- /dev/null
+++ b/doc/en/backwards-compatibility.rst
@@ -0,0 +1,12 @@
+.. _backwards-compatibility:
+
+Backwards Compatibility Policy
+==============================
+
+Keeping backwards compatibility has a very high priority in the pytest project. Although we have deprecated functionality over the years, most of it is still supported. All deprecations in pytest were done because simpler or more efficient ways of accomplishing the same tasks have emerged, making the old way of doing things unnecessary.
+
+With the pytest 3.0 release we introduced a clear communication scheme for when we will actually remove the old busted joint and politely ask you to use the new hotness instead, while giving you enough time to adjust your tests or raise concerns if there are valid reasons to keep deprecated functionality around.
+
+To communicate changes we are already issuing deprecation warnings, but they are not displayed by default. In pytest 3.0 we changed the default setting so that pytest deprecation warnings are displayed if not explicitly silenced (with ``--disable-pytest-warnings``).
+
+We will only remove deprecated functionality in major releases (e.g. if we deprecate something in 3.0 we will remove it in 4.0), and keep it around for at least two minor releases (e.g. if we deprecate something in 3.9 and 4.0 is the next release, we will not remove it in 4.0 but in 5.0).
diff --git a/doc/en/capture.rst b/doc/en/capture.rst
index 0004f5b18..8128419e1 100644
--- a/doc/en/capture.rst
+++ b/doc/en/capture.rst
@@ -116,7 +116,7 @@ libraries or subprocesses that directly write to operating
system level output streams (FD1 and FD2).
-.. versionadded:: 2.10
+.. versionadded:: 3.0
To temporarily disable capture within a test, both ``capsys``
and ``capfd`` have a ``disabled()`` method that can be used
diff --git a/doc/en/contents.rst b/doc/en/contents.rst
index 48c3471b5..b007e7de8 100644
--- a/doc/en/contents.rst
+++ b/doc/en/contents.rst
@@ -20,6 +20,7 @@ Full pytest documentation
cache
plugins
+ backwards-compatibility
contributing
talks
diff --git a/doc/en/doctest.rst b/doc/en/doctest.rst
index 80f5c9506..b9b19cb85 100644
--- a/doc/en/doctest.rst
+++ b/doc/en/doctest.rst
@@ -105,7 +105,7 @@ itself::
The 'doctest_namespace' fixture
-------------------------------
-.. versionadded:: 2.10
+.. versionadded:: 3.0
The ``doctest_namespace`` fixture can be used to inject items into the
namespace in which your doctests run. It is intended to be used within
diff --git a/doc/en/fixture.rst b/doc/en/fixture.rst
index b40c3943a..2077f2db0 100644
--- a/doc/en/fixture.rst
+++ b/doc/en/fixture.rst
@@ -823,6 +823,10 @@ If we run it, we get two passing tests::
Here is how autouse fixtures work in other scopes:
+- autouse fixtures obey the ``scope=`` keyword-argument: if an autouse fixture
+ has ``scope='session'`` it will only be run once, no matter where it is
+ defined. ``scope='class'`` means it will be run once per class, etc.
+
- if an autouse fixture is defined in a test module, all its test
functions automatically use it.
diff --git a/doc/en/genapi.py b/doc/en/genapi.py
index f8cdda6cf..89ddc8731 100644
--- a/doc/en/genapi.py
+++ b/doc/en/genapi.py
@@ -32,7 +32,7 @@ class Writer:
def pytest_funcarg__a(request):
with Writer("request") as writer:
- writer.docmethod(request.getfuncargvalue)
+ writer.docmethod(request.getfixturevalue)
writer.docmethod(request.cached_setup)
writer.docmethod(request.addfinalizer)
writer.docmethod(request.applymarker)
diff --git a/doc/en/talks.rst b/doc/en/talks.rst
index 7a5221845..1572832f0 100644
--- a/doc/en/talks.rst
+++ b/doc/en/talks.rst
@@ -11,9 +11,6 @@ Talks and Tutorials
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
`_.
@@ -52,12 +49,14 @@ Talks and blog postings
- `pytest introduction from Brian Okken (January 2013)
`_
-- `monkey patching done right`_ (blog post, consult `monkeypatch
- plugin`_ for up-to-date API)
+- pycon australia 2012 pytest talk from Brianna Laugher (`video `_, `slides `_, `code `_)
+- `pycon 2012 US talk video from Holger Krekel `_
+
+- `monkey patching done right`_ (blog post, consult `monkeypatch plugin`_ for up-to-date API)
Test parametrization:
-- `generating parametrized tests with funcargs`_ (uses deprecated ``addcall()`` API.
+- `generating parametrized tests with fixtures`_.
- `test generators and cached setup`_
- `parametrizing tests, generalized`_ (blog post)
- `putting test-hooks into local or global plugins`_ (blog post)
@@ -78,39 +77,17 @@ Plugin specific examples:
- `many examples in the docs for plugins`_
.. _`skipping slow tests by default in pytest`: http://bruynooghe.blogspot.com/2009/12/skipping-slow-test-by-default-in-pytest.html
-.. _`many examples in the docs for plugins`: plugin/index.html
-.. _`monkeypatch plugin`: plugin/monkeypatch.html
-.. _`application setup in test functions with funcargs`: funcargs.html#appsetup
+.. _`many examples in the docs for plugins`: plugins.html
+.. _`monkeypatch plugin`: monkeypatch.html
+.. _`application setup in test functions with fixtures`: fixture.html#interdependent-fixtures
.. _`simultaneously test your code on all platforms`: http://tetamap.wordpress.com/2009/03/23/new-simultanously-test-your-code-on-all-platforms/
.. _`monkey patching done right`: http://tetamap.wordpress.com/2009/03/03/monkeypatching-in-unit-tests-done-right/
.. _`putting test-hooks into local or global plugins`: http://tetamap.wordpress.com/2009/05/14/putting-test-hooks-into-local-and-global-plugins/
.. _`parametrizing tests, generalized`: http://tetamap.wordpress.com/2009/05/13/parametrizing-python-tests-generalized/
-.. _`generating parametrized tests with funcargs`: funcargs.html#test-generators
+.. _`generating parametrized tests with fixtures`: parametrize.html#test-generators
.. _`test generators and cached setup`: http://bruynooghe.blogspot.com/2010/06/pytest-test-generators-and-cached-setup.html
-Older conference talks and tutorials
-----------------------------------------
-- `pycon australia 2012 pytest talk from Brianna Laugher
- `_ (`video `_, `slides `_, `code `_)
-- `pycon 2012 US talk video from Holger Krekel `_
-- `pycon 2010 tutorial PDF`_ and `tutorial1 repository`_
-- `ep2009-rapidtesting.pdf`_ tutorial slides (July 2009):
- - testing terminology
- - basic pytest usage, file system layout
- - test function arguments (funcargs_) and test fixtures
- - existing plugins
- - distributed testing
-- `ep2009-pytest.pdf`_ 60 minute pytest talk, highlighting unique features and a roadmap (July 2009)
-
-- `pycon2009-pytest-introduction.zip`_ slides and files, extended version of pytest basic introduction, discusses more options, also introduces old-style xUnit setup, looponfailing and other features.
-
-- `pycon2009-pytest-advanced.pdf`_ contain a slightly older version of funcargs and distributed testing, compared to the EuroPython 2009 slides.
-
-.. _`ep2009-rapidtesting.pdf`: http://codespeak.net/download/py/ep2009-rapidtesting.pdf
-.. _`ep2009-pytest.pdf`: http://codespeak.net/download/py/ep2009-pytest.pdf
-.. _`pycon2009-pytest-introduction.zip`: http://codespeak.net/download/py/pycon2009-pytest-introduction.zip
-.. _`pycon2009-pytest-advanced.pdf`: http://codespeak.net/download/py/pycon2009-pytest-advanced.pdf
diff --git a/doc/en/usage.rst b/doc/en/usage.rst
index 06cc18969..351ad526a 100644
--- a/doc/en/usage.rst
+++ b/doc/en/usage.rst
@@ -79,7 +79,7 @@ than ``--tb=long``). It also ensures that a stack trace is printed on
**KeyboardInterrrupt** (Ctrl+C).
This is very useful if the tests are taking too long and you interrupt them
with Ctrl+C to find out where the tests are *hanging*. By default no output
-will be shown (because KeyboardInterrupt is catched by pytest). By using this
+will be shown (because KeyboardInterrupt is caught by pytest). By using this
option you make sure a trace is shown.
Dropping to PDB_ (Python Debugger) on failures
@@ -204,7 +204,7 @@ This will add an extra property ``example_key="1"`` to the generated
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
to all testcases you can use ``LogXML.add_global_properties``
diff --git a/doc/en/writing_plugins.rst b/doc/en/writing_plugins.rst
index bcd795d82..af47bad09 100644
--- a/doc/en/writing_plugins.rst
+++ b/doc/en/writing_plugins.rst
@@ -176,6 +176,63 @@ If a package is installed this way, ``pytest`` will load
to make it easy for users to find your plugin.
+Assertion Rewriting
+-------------------
+
+One of the main features of ``pytest`` is the use of plain assert
+statements and the detailed introspection of expressions upon
+assertion failures. This is provided by "assertion rewriting" which
+modifies the parsed AST before it gets compiled to bytecode. This is
+done via a :pep:`302` import hook which gets installed early on when
+``pytest`` starts up and will perform this re-writing when modules get
+imported. However since we do not want to test different bytecode
+then you will run in production this hook only re-writes test modules
+themselves as well as any modules which are part of plugins. Any
+other imported module will not be re-written and normal assertion
+behaviour will happen.
+
+If you have assertion helpers in other modules where you would need
+assertion rewriting to be enabled you need to ask ``pytest``
+explicitly to re-write this module before it gets imported.
+
+.. autofunction:: pytest.register_assert_rewrite
+
+This is especially important when you write a pytest plugin which is
+created using a package. The import hook only treats ``conftest.py``
+files and any modules which are listed in the ``pytest11`` entrypoint
+as plugins. As an example consider the following package::
+
+ pytest_foo/__init__.py
+ pytest_foo/plugin.py
+ pytest_foo/helper.py
+
+With the following typical ``setup.py`` extract:
+
+.. code-block:: python
+
+ setup(
+ ...
+ entry_points={'pytest11': ['foo = pytest_foo.plugin']},
+ ...
+ )
+
+In this case only ``pytest_foo/plugin.py`` will be re-written. If the
+helper module also contains assert statements which need to be
+re-written it needs to be marked as such, before it gets imported.
+This is easiest by marking it for re-writing inside the
+``__init__.py`` module, which will always be imported first when a
+module inside a package is imported. This way ``plugin.py`` can still
+import ``helper.py`` normally. The contents of
+``pytest_foo/__init__.py`` will then need to look like this:
+
+.. code-block:: python
+
+ import pytest
+
+ pytest.register_assert_rewrite('pytest_foo.helper')
+
+
+
Requiring/Loading plugins in a test module or conftest file
-----------------------------------------------------------
@@ -190,6 +247,16 @@ will be loaded as well. You can also use dotted path like this::
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
--------------------------------
diff --git a/doc/en/xunit_setup.rst b/doc/en/xunit_setup.rst
index 7a80f1299..148fb1209 100644
--- a/doc/en/xunit_setup.rst
+++ b/doc/en/xunit_setup.rst
@@ -7,21 +7,20 @@ classic xunit-style setup
This section describes a classic and popular way how you can implement
fixtures (setup and teardown test state) on a per-module/class/function basis.
-pytest started supporting these methods around 2005 and subsequently
-nose and the standard library introduced them (under slightly different
-names). While these setup/teardown methods are and will remain fully
-supported you may also use pytest's more powerful :ref:`fixture mechanism
-` which leverages the concept of dependency injection, allowing
-for a more modular and more scalable approach for managing test state,
-especially for larger projects and for functional testing. You can
-mix both fixture mechanisms in the same file but unittest-based
-test methods cannot receive fixture arguments.
+
.. note::
- As of pytest-2.4, teardownX functions are not called if
- setupX existed and failed/was skipped. This harmonizes
- behaviour across all major python testing tools.
+ While these setup/teardown methods are simple and familiar to those
+ coming from a ``unittest`` or nose ``background``, you may also consider
+ using pytest's more powerful :ref:`fixture mechanism
+ ` which leverages the concept of dependency injection, allowing
+ for a more modular and more scalable approach for managing test state,
+ especially for larger projects and for functional testing. You can
+ mix both fixture mechanisms in the same file but
+ test methods of ``unittest.TestCase`` subclasses
+ cannot receive fixture arguments.
+
Module level setup/teardown
--------------------------------------
@@ -38,6 +37,8 @@ which will usually be called once for all the functions::
method.
"""
+As of pytest-3.0, the ``module`` parameter is optional.
+
Class level setup/teardown
----------------------------------
@@ -71,6 +72,8 @@ Similarly, the following methods are called around each method invocation::
call.
"""
+As of pytest-3.0, the ``method`` parameter is optional.
+
If you would rather define test functions directly at module level
you can also use the following functions to implement fixtures::
@@ -84,7 +87,13 @@ you can also use the following functions to implement fixtures::
call.
"""
-Note that it is possible for setup/teardown pairs to be invoked multiple times
-per testing process.
+As of pytest-3.0, the ``function`` parameter is optional.
+
+Remarks:
+
+* It is possible for setup/teardown pairs to be invoked multiple times
+ per testing process.
+* teardown functions are not called if the corresponding setup function existed
+ and failed/was skipped.
.. _`unittest.py module`: http://docs.python.org/library/unittest.html
diff --git a/doc/en/yieldfixture.rst b/doc/en/yieldfixture.rst
index b372973e5..f69b38eb3 100644
--- a/doc/en/yieldfixture.rst
+++ b/doc/en/yieldfixture.rst
@@ -3,12 +3,12 @@
"yield_fixture" functions
---------------------------------------------------------------
-.. deprecated:: 2.10
+.. deprecated:: 3.0
.. versionadded:: 2.4
.. 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``
in previous versions.
diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py
index c5401cda6..2e495e246 100644
--- a/testing/acceptance_test.py
+++ b/testing/acceptance_test.py
@@ -762,3 +762,52 @@ class TestDurationWithFixture:
* setup *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
diff --git a/testing/code/test_code.py b/testing/code/test_code.py
index 0db4ad2ab..6f1d9d3cc 100644
--- a/testing/code/test_code.py
+++ b/testing/code/test_code.py
@@ -66,24 +66,6 @@ def test_code_from_func():
assert co.path
-
-def test_builtin_patch_unpatch(monkeypatch):
- cpy_builtin = py.builtin.builtins
- comp = cpy_builtin.compile
- def mycompile(*args, **kwargs):
- return comp(*args, **kwargs)
- class Sub(AssertionError):
- pass
- monkeypatch.setattr(cpy_builtin, 'AssertionError', Sub)
- monkeypatch.setattr(cpy_builtin, 'compile', mycompile)
- _pytest._code.patch_builtins()
- assert cpy_builtin.AssertionError != Sub
- assert cpy_builtin.compile != mycompile
- _pytest._code.unpatch_builtins()
- assert cpy_builtin.AssertionError is Sub
- assert cpy_builtin.compile == mycompile
-
-
def test_unicode_handling():
value = py.builtin._totext('\xc4\x85\xc4\x87\n', 'utf-8').encode('utf8')
def f():
diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py
index 59756645a..64d1ff89e 100644
--- a/testing/code/test_excinfo.py
+++ b/testing/code/test_excinfo.py
@@ -274,18 +274,6 @@ class TestTraceback_f_g_h:
assert entry.lineno == co.firstlineno + 2
assert entry.frame.code.name == 'g'
-def hello(x):
- x + 5
-
-def test_tbentry_reinterpret():
- try:
- hello("hello")
- except TypeError:
- excinfo = _pytest._code.ExceptionInfo()
- tbentry = excinfo.traceback[-1]
- msg = tbentry.reinterpret()
- assert msg.startswith("TypeError: ('hello' + 5)")
-
def test_excinfo_exconly():
excinfo = pytest.raises(ValueError, h)
assert excinfo.exconly().startswith('ValueError')
@@ -381,10 +369,12 @@ def test_match_raises_error(testdir):
])
class TestFormattedExcinfo:
- def pytest_funcarg__importasmod(self, request):
+
+ @pytest.fixture
+ def importasmod(self, request):
def importasmod(source):
source = _pytest._code.Source(source)
- tmpdir = request.getfuncargvalue("tmpdir")
+ tmpdir = request.getfixturevalue("tmpdir")
modpath = tmpdir.join("mod.py")
tmpdir.ensure("__init__.py")
modpath.write(source)
@@ -429,7 +419,7 @@ class TestFormattedExcinfo:
assert lines == [
' def f():',
'> assert 0',
- 'E assert 0'
+ 'E AssertionError'
]
@@ -768,23 +758,6 @@ raise ValueError()
assert reprtb.extraline == "!!! Recursion detected (same locals & position)"
assert str(reprtb)
- def test_tb_entry_AssertionError(self, importasmod):
- # probably this test is a bit redundant
- # as py/magic/testing/test_assertion.py
- # already tests correctness of
- # assertion-reinterpretation logic
- mod = importasmod("""
- def somefunc():
- x = 1
- assert x == 2
- """)
- excinfo = pytest.raises(AssertionError, mod.somefunc)
-
- p = FormattedExcinfo()
- reprentry = p.repr_traceback_entry(excinfo.traceback[-1], excinfo)
- lines = reprentry.lines
- assert lines[-1] == "E assert 1 == 2"
-
def test_reprexcinfo_getrepr(self, importasmod):
mod = importasmod("""
def f(x):
@@ -933,21 +906,6 @@ raise ValueError()
repr.toterminal(tw)
assert tw.stringio.getvalue()
-
- def test_native_style(self):
- excinfo = self.excinfo_from_exec("""
- assert 0
- """)
- repr = excinfo.getrepr(style='native')
- assert "assert 0" in str(repr.reprcrash)
- s = str(repr)
- assert s.startswith('Traceback (most recent call last):\n File')
- assert s.endswith('\nAssertionError: assert 0')
- assert 'exec (source.compile())' in s
- # python 2.4 fails to get the source line for the assert
- if py.std.sys.version_info >= (2, 5):
- assert s.count('assert 0') == 2
-
def test_traceback_repr_style(self, importasmod):
mod = importasmod("""
def f():
@@ -1077,4 +1035,4 @@ def test_cwd_deleted(testdir):
""")
result = testdir.runpytest()
result.stdout.fnmatch_lines(['* 1 failed in *'])
- assert 'INTERNALERROR' not in result.stdout.str() + result.stderr.str()
\ No newline at end of file
+ assert 'INTERNALERROR' not in result.stdout.str() + result.stderr.str()
diff --git a/testing/code/test_source.py b/testing/code/test_source.py
index e78f4b241..13bfccd54 100644
--- a/testing/code/test_source.py
+++ b/testing/code/test_source.py
@@ -285,13 +285,14 @@ class TestSourceParsingAndCompiling:
#print "block", str(block)
assert str(stmt).strip().startswith('assert')
- def test_compilefuncs_and_path_sanity(self):
+ @pytest.mark.parametrize('name', ['', None, 'my'])
+ def test_compilefuncs_and_path_sanity(self, name):
def check(comp, name):
co = comp(self.source, name)
if not name:
- expected = "codegen %s:%d>" %(mypath, mylineno+2+1)
+ expected = "codegen %s:%d>" %(mypath, mylineno+2+2)
else:
- expected = "codegen %r %s:%d>" % (name, mypath, mylineno+2+1)
+ expected = "codegen %r %s:%d>" % (name, mypath, mylineno+2+2)
fn = co.co_filename
assert fn.endswith(expected)
@@ -300,8 +301,7 @@ class TestSourceParsingAndCompiling:
mypath = mycode.path
for comp in _pytest._code.compile, _pytest._code.Source.compile:
- for name in '', None, 'my':
- yield check, comp, name
+ check(comp, name)
def test_offsetless_synerr(self):
pytest.raises(SyntaxError, _pytest._code.compile, "lambda a,a: 0", mode='eval')
diff --git a/testing/cx_freeze/runtests_setup.py b/testing/cx_freeze/runtests_setup.py
index ee11d8854..6d7e43750 100644
--- a/testing/cx_freeze/runtests_setup.py
+++ b/testing/cx_freeze/runtests_setup.py
@@ -8,7 +8,7 @@ if __name__ == '__main__':
setup(
name="runtests",
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")],
options={"build_exe": {'includes': pytest.freeze_includes()}},
)
diff --git a/testing/python/collect.py b/testing/python/collect.py
index 2eae8a9f9..c4b9259bd 100644
--- a/testing/python/collect.py
+++ b/testing/python/collect.py
@@ -334,7 +334,7 @@ class TestFunction:
reprec.assertoutcome()
def test_function_equality(self, testdir, tmpdir):
- from _pytest.python import FixtureManager
+ from _pytest.fixtures import FixtureManager
config = testdir.parseconfigure()
session = testdir.Session(config)
session._fixturemanager = FixtureManager(session)
@@ -795,21 +795,24 @@ class TestTracebackCutting:
def test_traceback_argsetup(self, testdir):
testdir.makeconftest("""
- def pytest_funcarg__hello(request):
+ import pytest
+
+ @pytest.fixture
+ def hello(request):
raise ValueError("xyz")
""")
p = testdir.makepyfile("def test(hello): pass")
result = testdir.runpytest(p)
assert result.ret != 0
out = result.stdout.str()
- assert out.find("xyz") != -1
- assert out.find("conftest.py:2: ValueError") != -1
+ assert "xyz" in out
+ assert "conftest.py:5: ValueError" in out
numentries = out.count("_ _ _") # separator for traceback entries
assert numentries == 0
result = testdir.runpytest("--fulltrace", p)
out = result.stdout.str()
- assert out.find("conftest.py:2: ValueError") != -1
+ assert "conftest.py:5: ValueError" in out
numentries = out.count("_ _ _ _") # separator for traceback entries
assert numentries > 3
diff --git a/testing/python/fixture.py b/testing/python/fixture.py
index 83ec29cb5..e7232a25f 100644
--- a/testing/python/fixture.py
+++ b/testing/python/fixture.py
@@ -3,35 +3,37 @@ from textwrap import dedent
import _pytest._code
import pytest
import sys
-from _pytest import python as funcargs
from _pytest.pytester import get_public_names
-from _pytest.python import FixtureLookupError
-
+from _pytest.fixtures import FixtureLookupError
+from _pytest import fixtures
def test_getfuncargnames():
def f(): pass
- assert not funcargs.getfuncargnames(f)
+ assert not fixtures.getfuncargnames(f)
def g(arg): pass
- assert funcargs.getfuncargnames(g) == ('arg',)
+ assert fixtures.getfuncargnames(g) == ('arg',)
def h(arg1, arg2="hello"): pass
- assert funcargs.getfuncargnames(h) == ('arg1',)
+ assert fixtures.getfuncargnames(h) == ('arg1',)
def h(arg1, arg2, arg3="hello"): pass
- assert funcargs.getfuncargnames(h) == ('arg1', 'arg2')
+ assert fixtures.getfuncargnames(h) == ('arg1', 'arg2')
class A:
def f(self, arg1, arg2="hello"):
pass
- assert funcargs.getfuncargnames(A().f) == ('arg1',)
+ assert fixtures.getfuncargnames(A().f) == ('arg1',)
if sys.version_info < (3,0):
- assert funcargs.getfuncargnames(A.f) == ('arg1',)
+ assert fixtures.getfuncargnames(A.f) == ('arg1',)
class TestFillFixtures:
def test_fillfuncargs_exposed(self):
# used by oejskit, kept for compatibility
- assert pytest._fillfuncargs == funcargs.fillfixtures
+ assert pytest._fillfuncargs == fixtures.fillfixtures
def test_funcarg_lookupfails(self, testdir):
testdir.makepyfile("""
- def pytest_funcarg__xyzsomething(request):
+ import pytest
+
+ @pytest.fixture
+ def xyzsomething(request):
return 42
def test_func(some):
@@ -47,14 +49,18 @@ class TestFillFixtures:
def test_funcarg_basic(self, testdir):
item = testdir.getitem("""
- def pytest_funcarg__some(request):
+ import pytest
+
+ @pytest.fixture
+ def some(request):
return request.function.__name__
- def pytest_funcarg__other(request):
+ @pytest.fixture
+ def other(request):
return 42
def test_func(some, other):
pass
""")
- funcargs.fillfixtures(item)
+ fixtures.fillfixtures(item)
del item.funcargs["request"]
assert len(get_public_names(item.funcargs)) == 2
assert item.funcargs['some'] == "test_func"
@@ -62,7 +68,10 @@ class TestFillFixtures:
def test_funcarg_lookup_modulelevel(self, testdir):
testdir.makepyfile("""
- def pytest_funcarg__something(request):
+ import pytest
+
+ @pytest.fixture
+ def something(request):
return request.function.__name__
class TestClass:
@@ -76,9 +85,13 @@ class TestFillFixtures:
def test_funcarg_lookup_classlevel(self, testdir):
p = testdir.makepyfile("""
+ import pytest
class TestClass:
- def pytest_funcarg__something(self, request):
+
+ @pytest.fixture
+ def something(self, request):
return request.instance
+
def test_method(self, something):
assert something is self
""")
@@ -92,13 +105,15 @@ class TestFillFixtures:
sub2 = testdir.mkpydir("sub2")
sub1.join("conftest.py").write(_pytest._code.Source("""
import pytest
- def pytest_funcarg__arg1(request):
- pytest.raises(Exception, "request.getfuncargvalue('arg2')")
+ @pytest.fixture
+ def arg1(request):
+ pytest.raises(Exception, "request.getfixturevalue('arg2')")
"""))
sub2.join("conftest.py").write(_pytest._code.Source("""
import pytest
- def pytest_funcarg__arg2(request):
- pytest.raises(Exception, "request.getfuncargvalue('arg1')")
+ @pytest.fixture
+ def arg2(request):
+ pytest.raises(Exception, "request.getfixturevalue('arg1')")
"""))
sub1.join("test_in_sub1.py").write("def test_1(arg1): pass")
@@ -397,10 +412,13 @@ class TestFillFixtures:
class TestRequestBasic:
def test_request_attributes(self, testdir):
item = testdir.getitem("""
- def pytest_funcarg__something(request): pass
+ import pytest
+
+ @pytest.fixture
+ def something(request): pass
def test_func(something): pass
""")
- req = funcargs.FixtureRequest(item)
+ req = fixtures.FixtureRequest(item)
assert req.function == item.obj
assert req.keywords == item.keywords
assert hasattr(req.module, 'test_func')
@@ -411,8 +429,11 @@ class TestRequestBasic:
def test_request_attributes_method(self, testdir):
item, = testdir.getitems("""
+ import pytest
class TestB:
- def pytest_funcarg__something(self, request):
+
+ @pytest.fixture
+ def something(self, request):
return 1
def test_func(self, something):
pass
@@ -421,9 +442,11 @@ class TestRequestBasic:
assert req.cls.__name__ == "TestB"
assert req.instance.__class__ == req.cls
- def XXXtest_request_contains_funcarg_arg2fixturedefs(self, testdir):
+ def test_request_contains_funcarg_arg2fixturedefs(self, testdir):
modcol = testdir.getmodulecol("""
- def pytest_funcarg__something(request):
+ import pytest
+ @pytest.fixture
+ def something(request):
pass
class TestClass:
def test_method(self, something):
@@ -431,41 +454,53 @@ class TestRequestBasic:
""")
item1, = testdir.genitems([modcol])
assert item1.name == "test_method"
- arg2fixturedefs = funcargs.FixtureRequest(item1)._arg2fixturedefs
+ arg2fixturedefs = fixtures.FixtureRequest(item1)._arg2fixturedefs
assert len(arg2fixturedefs) == 1
- assert arg2fixturedefs[0].__name__ == "pytest_funcarg__something"
+ assert arg2fixturedefs['something'][0].argname == "something"
- def test_getfuncargvalue_recursive(self, testdir):
+ def test_getfixturevalue_recursive(self, testdir):
testdir.makeconftest("""
- def pytest_funcarg__something(request):
+ import pytest
+
+ @pytest.fixture
+ def something(request):
return 1
""")
testdir.makepyfile("""
- def pytest_funcarg__something(request):
- return request.getfuncargvalue("something") + 1
+ import pytest
+
+ @pytest.fixture
+ def something(request):
+ return request.getfixturevalue("something") + 1
def test_func(something):
assert something == 2
""")
reprec = testdir.inline_run()
reprec.assertoutcome(passed=1)
- def test_getfuncargvalue(self, testdir):
+ @pytest.mark.parametrize(
+ 'getfixmethod', ('getfixturevalue', 'getfuncargvalue'))
+ def test_getfixturevalue(self, testdir, getfixmethod):
item = testdir.getitem("""
+ import pytest
l = [2]
- def pytest_funcarg__something(request): return 1
- def pytest_funcarg__other(request):
+ @pytest.fixture
+ def something(request): return 1
+ @pytest.fixture
+ def other(request):
return l.pop()
def test_func(something): pass
""")
req = item._request
- pytest.raises(FixtureLookupError, req.getfuncargvalue, "notexists")
- val = req.getfuncargvalue("something")
+ fixture_fetcher = getattr(req, getfixmethod)
+ pytest.raises(FixtureLookupError, fixture_fetcher, "notexists")
+ val = fixture_fetcher("something")
assert val == 1
- val = req.getfuncargvalue("something")
+ val = fixture_fetcher("something")
assert val == 1
- val2 = req.getfuncargvalue("other")
+ val2 = fixture_fetcher("other")
assert val2 == 2
- val2 = req.getfuncargvalue("other") # see about caching
+ val2 = fixture_fetcher("other") # see about caching
assert val2 == 2
pytest._fillfuncargs(item)
assert item.funcargs["something"] == 1
@@ -475,8 +510,10 @@ class TestRequestBasic:
def test_request_addfinalizer(self, testdir):
item = testdir.getitem("""
+ import pytest
teardownlist = []
- def pytest_funcarg__something(request):
+ @pytest.fixture
+ def something(request):
request.addfinalizer(lambda: teardownlist.append(1))
def test_func(something): pass
""")
@@ -501,7 +538,8 @@ class TestRequestBasic:
result = testdir.runpytest_subprocess()
assert result.ret != 0
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):
@@ -539,8 +577,10 @@ class TestRequestBasic:
def test_request_addfinalizer_partial_setup_failure(self, testdir):
p = testdir.makepyfile("""
+ import pytest
l = []
- def pytest_funcarg__something(request):
+ @pytest.fixture
+ def something(request):
request.addfinalizer(lambda: l.append(None))
def test_func(something, missingarg):
pass
@@ -555,7 +595,7 @@ class TestRequestBasic:
def test_request_getmodulepath(self, testdir):
modcol = testdir.getmodulecol("def test_somefunc(): pass")
item, = testdir.genitems([modcol])
- req = funcargs.FixtureRequest(item)
+ req = fixtures.FixtureRequest(item)
assert req.fspath == modcol.fspath
def test_request_fixturenames(self, testdir):
@@ -581,9 +621,11 @@ class TestRequestBasic:
def test_funcargnames_compatattr(self, testdir):
testdir.makepyfile("""
+ import pytest
def pytest_generate_tests(metafunc):
assert metafunc.funcargnames == metafunc.fixturenames
- def pytest_funcarg__fn(request):
+ @pytest.fixture
+ def fn(request):
assert request._pyfuncitem.funcargnames == \
request._pyfuncitem.fixturenames
return request.funcargnames, request.fixturenames
@@ -628,7 +670,9 @@ class TestRequestBasic:
# this tests that normalization of nodeids takes place
b = testdir.mkdir("tests").mkdir("unit")
b.join("conftest.py").write(_pytest._code.Source("""
- def pytest_funcarg__arg1():
+ import pytest
+ @pytest.fixture
+ def arg1():
pass
"""))
p = b.join("test_module.py")
@@ -676,7 +720,10 @@ class TestRequestBasic:
class TestRequestMarking:
def test_applymarker(self, testdir):
item1,item2 = testdir.getitems("""
- def pytest_funcarg__something(request):
+ import pytest
+
+ @pytest.fixture
+ def something(request):
pass
class TestClass:
def test_func1(self, something):
@@ -684,7 +731,7 @@ class TestRequestMarking:
def test_func2(self, something):
pass
""")
- req1 = funcargs.FixtureRequest(item1)
+ req1 = fixtures.FixtureRequest(item1)
assert 'xfail' not in item1.keywords
req1.applymarker(pytest.mark.xfail)
assert 'xfail' in item1.keywords
@@ -735,7 +782,10 @@ class TestRequestCachedSetup:
reprec = testdir.inline_runsource("""
mysetup = ["hello",].pop
- def pytest_funcarg__something(request):
+ import pytest
+
+ @pytest.fixture
+ def something(request):
return request.cached_setup(mysetup, scope="module")
def test_func1(something):
@@ -750,7 +800,9 @@ class TestRequestCachedSetup:
reprec = testdir.inline_runsource("""
mysetup = ["hello", "hello2", "hello3"].pop
- def pytest_funcarg__something(request):
+ import pytest
+ @pytest.fixture
+ def something(request):
return request.cached_setup(mysetup, scope="class")
def test_func1(something):
assert something == "hello3"
@@ -766,7 +818,7 @@ class TestRequestCachedSetup:
def test_request_cachedsetup_extrakey(self, testdir):
item1 = testdir.getitem("def test_func(): pass")
- req1 = funcargs.FixtureRequest(item1)
+ req1 = fixtures.FixtureRequest(item1)
l = ["hello", "world"]
def setup():
return l.pop()
@@ -781,7 +833,7 @@ class TestRequestCachedSetup:
def test_request_cachedsetup_cache_deletion(self, testdir):
item1 = testdir.getitem("def test_func(): pass")
- req1 = funcargs.FixtureRequest(item1)
+ req1 = fixtures.FixtureRequest(item1)
l = []
def setup():
l.append("setup")
@@ -800,9 +852,13 @@ class TestRequestCachedSetup:
def test_request_cached_setup_two_args(self, testdir):
testdir.makepyfile("""
- def pytest_funcarg__arg1(request):
+ import pytest
+
+ @pytest.fixture
+ def arg1(request):
return request.cached_setup(lambda: 42)
- def pytest_funcarg__arg2(request):
+ @pytest.fixture
+ def arg2(request):
return request.cached_setup(lambda: 17)
def test_two_different_setups(arg1, arg2):
assert arg1 != arg2
@@ -812,12 +868,16 @@ class TestRequestCachedSetup:
"*1 passed*"
])
- def test_request_cached_setup_getfuncargvalue(self, testdir):
+ def test_request_cached_setup_getfixturevalue(self, testdir):
testdir.makepyfile("""
- def pytest_funcarg__arg1(request):
- arg1 = request.getfuncargvalue("arg2")
+ import pytest
+
+ @pytest.fixture
+ def arg1(request):
+ arg1 = request.getfixturevalue("arg2")
return request.cached_setup(lambda: arg1 + 1)
- def pytest_funcarg__arg2(request):
+ @pytest.fixture
+ def arg2(request):
return request.cached_setup(lambda: 10)
def test_two_funcarg(arg1):
assert arg1 == 11
@@ -829,8 +889,10 @@ class TestRequestCachedSetup:
def test_request_cached_setup_functional(self, testdir):
testdir.makepyfile(test_0="""
+ import pytest
l = []
- def pytest_funcarg__something(request):
+ @pytest.fixture
+ def something(request):
val = request.cached_setup(fsetup, fteardown)
return val
def fsetup(mycache=[1]):
@@ -856,7 +918,10 @@ class TestRequestCachedSetup:
def test_issue117_sessionscopeteardown(self, testdir):
testdir.makepyfile("""
- def pytest_funcarg__app(request):
+ import pytest
+
+ @pytest.fixture
+ def app(request):
app = request.cached_setup(
scope='session',
setup=lambda: 0,
@@ -1117,16 +1182,23 @@ class TestFixtureUsages:
class TestFixtureManagerParseFactories:
- def pytest_funcarg__testdir(self, request):
- testdir = request.getfuncargvalue("testdir")
+
+ @pytest.fixture
+ def testdir(self, request):
+ testdir = request.getfixturevalue("testdir")
testdir.makeconftest("""
- def pytest_funcarg__hello(request):
+ import pytest
+
+ @pytest.fixture
+ def hello(request):
return "conftest"
- def pytest_funcarg__fm(request):
+ @pytest.fixture
+ def fm(request):
return request._fixturemanager
- def pytest_funcarg__item(request):
+ @pytest.fixture
+ def item(request):
return request._pyfuncitem
""")
return testdir
@@ -1152,17 +1224,21 @@ class TestFixtureManagerParseFactories:
faclist = fm.getfixturedefs(name, item.nodeid)
assert len(faclist) == 1
fac = faclist[0]
- assert fac.func.__name__ == "pytest_funcarg__" + name
+ assert fac.func.__name__ == name
""")
reprec = testdir.inline_run("-s")
reprec.assertoutcome(passed=1)
def test_parsefactories_conftest_and_module_and_class(self, testdir):
testdir.makepyfile("""
- def pytest_funcarg__hello(request):
+ import pytest
+
+ @pytest.fixture
+ def hello(request):
return "module"
class TestClass:
- def pytest_funcarg__hello(self, request):
+ @pytest.fixture
+ def hello(self, request):
return "class"
def test_hello(self, item, fm):
faclist = fm.getfixturedefs("hello", item.nodeid)
@@ -1210,7 +1286,9 @@ class TestFixtureManagerParseFactories:
class TestAutouseDiscovery:
- def pytest_funcarg__testdir(self, testdir):
+
+ @pytest.fixture
+ def testdir(self, testdir):
testdir.makeconftest("""
import pytest
@pytest.fixture(autouse=True)
@@ -1224,10 +1302,12 @@ class TestAutouseDiscovery:
def perfunction2(arg1):
pass
- def pytest_funcarg__fm(request):
+ @pytest.fixture
+ def fm(request):
return request._fixturemanager
- def pytest_funcarg__item(request):
+ @pytest.fixture
+ def item(request):
return request._pyfuncitem
""")
return testdir
@@ -1506,7 +1586,8 @@ class TestAutouseManagement:
def test_2(self):
pass
""")
- reprec = testdir.inline_run("-v","-s")
+ confcut = "--confcutdir={0}".format(testdir.tmpdir)
+ reprec = testdir.inline_run("-v","-s", confcut)
reprec.assertoutcome(passed=8)
config = reprec.getcalls("pytest_unconfigure")[0].config
l = config.pluginmanager._getconftestmodules(p)[0].l
@@ -1771,17 +1852,19 @@ class TestFixtureMarker:
def test_scope_module_and_finalizer(self, testdir):
testdir.makeconftest("""
import pytest
- finalized = []
- created = []
+ finalized_list = []
+ created_list = []
@pytest.fixture(scope="module")
def arg(request):
- created.append(1)
+ created_list.append(1)
assert request.scope == "module"
- request.addfinalizer(lambda: finalized.append(1))
- def pytest_funcarg__created(request):
- return len(created)
- def pytest_funcarg__finalized(request):
- return len(finalized)
+ request.addfinalizer(lambda: finalized_list.append(1))
+ @pytest.fixture
+ def created(request):
+ return len(created_list)
+ @pytest.fixture
+ def finalized(request):
+ return len(finalized_list)
""")
testdir.makepyfile(
test_mod1="""
@@ -1804,9 +1887,9 @@ class TestFixtureMarker:
reprec.assertoutcome(passed=4)
@pytest.mark.parametrize("method", [
- 'request.getfuncargvalue("arg")',
+ 'request.getfixturevalue("arg")',
'request.cached_setup(lambda: None, scope="function")',
- ], ids=["getfuncargvalue", "cached_setup"])
+ ], ids=["getfixturevalue", "cached_setup"])
def test_scope_mismatch_various(self, testdir, method):
testdir.makeconftest("""
import pytest
@@ -2718,6 +2801,7 @@ class TestContextManagerFixtureFuncs:
""".format(flavor=flavor))
result = testdir.runpytest("-s")
result.stdout.fnmatch_lines("*mew*")
+
class TestParameterizedSubRequest:
def test_call_from_fixture(self, testdir):
testfile = testdir.makepyfile("""
@@ -2729,7 +2813,7 @@ class TestParameterizedSubRequest:
@pytest.fixture
def get_named_fixture(request):
- return request.getfuncargvalue('fix_with_param')
+ return request.getfixturevalue('fix_with_param')
def test_foo(request, get_named_fixture):
pass
@@ -2754,7 +2838,7 @@ class TestParameterizedSubRequest:
return request.param
def test_foo(request):
- request.getfuncargvalue('fix_with_param')
+ request.getfixturevalue('fix_with_param')
""")
result = testdir.runpytest()
result.stdout.fnmatch_lines("""
@@ -2778,7 +2862,7 @@ class TestParameterizedSubRequest:
testfile = testdir.makepyfile("""
def test_foo(request):
- request.getfuncargvalue('fix_with_param')
+ request.getfixturevalue('fix_with_param')
""")
result = testdir.runpytest()
result.stdout.fnmatch_lines("""
@@ -2808,7 +2892,7 @@ class TestParameterizedSubRequest:
from fix import fix_with_param
def test_foo(request):
- request.getfuncargvalue('fix_with_param')
+ request.getfixturevalue('fix_with_param')
"""))
tests_dir.chdir()
@@ -2823,3 +2907,7 @@ class TestParameterizedSubRequest:
E*{1}:5
*1 failed*
""".format(fixfile.strpath, testfile.basename))
+
+
+def test_getfuncargvalue_is_deprecated(request):
+ pytest.deprecated_call(request.getfuncargvalue, 'tmpdir')
diff --git a/testing/python/integration.py b/testing/python/integration.py
index dea86f942..237becd6f 100644
--- a/testing/python/integration.py
+++ b/testing/python/integration.py
@@ -15,7 +15,9 @@ class TestOEJSKITSpecials:
return self.fspath, 3, "xyz"
""")
modcol = testdir.getmodulecol("""
- def pytest_funcarg__arg1(request):
+ import pytest
+ @pytest.fixture
+ def arg1(request):
return 42
class MyClass:
pass
@@ -43,7 +45,8 @@ class TestOEJSKITSpecials:
@pytest.fixture(autouse=True)
def hello():
pass
- def pytest_funcarg__arg1(request):
+ @pytest.fixture
+ def arg1(request):
return 42
class MyClass:
pass
@@ -73,7 +76,7 @@ def test_wrapped_getfslineno():
class TestMockDecoration:
def test_wrapped_getfuncargnames(self):
- from _pytest.python import getfuncargnames
+ from _pytest.compat import getfuncargnames
def wrap(f):
def func():
pass
@@ -86,7 +89,7 @@ class TestMockDecoration:
assert l == ("x",)
def test_wrapped_getfuncargnames_patching(self):
- from _pytest.python import getfuncargnames
+ from _pytest.compat import getfuncargnames
def wrap(f):
def func():
pass
@@ -234,7 +237,7 @@ class TestReRunTests:
""")
def test_pytestconfig_is_session_scoped():
- from _pytest.python import pytestconfig
+ from _pytest.fixtures import pytestconfig
assert pytestconfig._pytestfixturefunction.scope == "session"
diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py
index 0c07dca36..ac8e512d4 100644
--- a/testing/python/metafunc.py
+++ b/testing/python/metafunc.py
@@ -5,7 +5,7 @@ import sys
import _pytest._code
import py
import pytest
-from _pytest import python as funcargs
+from _pytest import python, fixtures
import hypothesis
from hypothesis import strategies
@@ -22,9 +22,9 @@ class TestMetafunc:
name2fixturedefs = None
def __init__(self, names):
self.names_closure = names
- names = funcargs.getfuncargnames(func)
+ names = fixtures.getfuncargnames(func)
fixtureinfo = FixtureInfo(names)
- return funcargs.Metafunc(func, fixtureinfo, None)
+ return python.Metafunc(func, fixtureinfo, None)
def test_no_funcargs(self, testdir):
def function(): pass
@@ -448,13 +448,13 @@ class TestMetafunc:
def test_parametrize_functional(self, testdir):
testdir.makepyfile("""
+ import pytest
def pytest_generate_tests(metafunc):
metafunc.parametrize('x', [1,2], indirect=True)
metafunc.parametrize('y', [2])
- def pytest_funcarg__x(request):
+ @pytest.fixture
+ def x(request):
return request.param * 10
- #def pytest_funcarg__y(request):
- # return request.param
def test_simple(x,y):
assert x in (10,20)
@@ -558,16 +558,16 @@ class TestMetafunc:
def test_format_args(self):
def function1(): pass
- assert funcargs._format_args(function1) == '()'
+ assert fixtures._format_args(function1) == '()'
def function2(arg1): pass
- assert funcargs._format_args(function2) == "(arg1)"
+ assert fixtures._format_args(function2) == "(arg1)"
def function3(arg1, arg2="qwe"): pass
- assert funcargs._format_args(function3) == "(arg1, arg2='qwe')"
+ assert fixtures._format_args(function3) == "(arg1, arg2='qwe')"
def function4(arg1, *args, **kwargs): pass
- assert funcargs._format_args(function4) == "(arg1, *args, **kwargs)"
+ assert fixtures._format_args(function4) == "(arg1, *args, **kwargs)"
class TestMetafuncFunctional:
@@ -578,7 +578,8 @@ class TestMetafuncFunctional:
def pytest_generate_tests(metafunc):
metafunc.addcall(param=metafunc)
- def pytest_funcarg__metafunc(request):
+ @pytest.fixture
+ def metafunc(request):
assert request._pyfuncitem._genid == "0"
return request.param
@@ -630,7 +631,9 @@ class TestMetafuncFunctional:
metafunc.addcall(param=10)
metafunc.addcall(param=20)
- def pytest_funcarg__arg1(request):
+ import pytest
+ @pytest.fixture
+ def arg1(request):
return request.param
def test_func1(arg1):
@@ -669,9 +672,12 @@ class TestMetafuncFunctional:
def pytest_generate_tests(metafunc):
metafunc.addcall(param=(1,1), id="hello")
- def pytest_funcarg__arg1(request):
+ import pytest
+ @pytest.fixture
+ def arg1(request):
return request.param[0]
- def pytest_funcarg__arg2(request):
+ @pytest.fixture
+ def arg2(request):
return request.param[1]
class TestClass:
@@ -749,17 +755,20 @@ class TestMetafuncFunctional:
"*4 failed*",
])
- def test_parametrize_and_inner_getfuncargvalue(self, testdir):
+ def test_parametrize_and_inner_getfixturevalue(self, testdir):
p = testdir.makepyfile("""
def pytest_generate_tests(metafunc):
metafunc.parametrize("arg1", [1], indirect=True)
metafunc.parametrize("arg2", [10], indirect=True)
- def pytest_funcarg__arg1(request):
- x = request.getfuncargvalue("arg2")
+ import pytest
+ @pytest.fixture
+ def arg1(request):
+ x = request.getfixturevalue("arg2")
return x + request.param
- def pytest_funcarg__arg2(request):
+ @pytest.fixture
+ def arg2(request):
return request.param
def test_func1(arg1, arg2):
@@ -777,10 +786,13 @@ class TestMetafuncFunctional:
assert "arg1" in metafunc.fixturenames
metafunc.parametrize("arg1", [1], indirect=True)
- def pytest_funcarg__arg1(request):
+ import pytest
+ @pytest.fixture
+ def arg1(request):
return request.param
- def pytest_funcarg__arg2(request, arg1):
+ @pytest.fixture
+ def arg2(request, arg1):
return 10 * arg1
def test_func(arg2):
@@ -870,7 +882,8 @@ class TestMetafuncFunctional:
if "arg" in metafunc.funcargnames:
metafunc.parametrize("arg", [1,2], indirect=True,
scope=%r)
- def pytest_funcarg__arg(request):
+ @pytest.fixture
+ def arg(request):
l.append(request.param)
return request.param
def test_hello(arg):
diff --git a/testing/python/setup_only.py b/testing/python/setup_only.py
index e7403420b..c780b197e 100644
--- a/testing/python/setup_only.py
+++ b/testing/python/setup_only.py
@@ -1,7 +1,8 @@
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):
return request.param
@@ -24,7 +25,7 @@ def test_show_only_active_fixtures(testdir, mode):
result.stdout.fnmatch_lines([
'*SETUP F arg1*',
- '*test_arg1 (fixtures used: arg1)',
+ '*test_arg1 (fixtures used: arg1)*',
'*TEARDOWN F arg1*',
])
assert "_arg0" not in result.stdout.str()
@@ -49,7 +50,7 @@ def test_show_different_scopes(testdir, mode):
result.stdout.fnmatch_lines([
'SETUP S arg_session*',
'*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 S arg_session*',
])
@@ -77,7 +78,7 @@ def test_show_nested_fixtures(testdir, mode):
result.stdout.fnmatch_lines([
'SETUP S 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 S arg_same*',
])
@@ -102,7 +103,7 @@ def test_show_fixtures_with_autouse(testdir, mode):
result.stdout.fnmatch_lines([
'SETUP S arg_session*',
'*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 also be captured'
])
+
+
+def test_show_fixtures_and_execute_test(testdir):
+ """ Verifies that setups are shown and tests are executed. """
+ p = testdir.makepyfile('''
+ import pytest
+ @pytest.fixture
+ def arg():
+ assert True
+ def test_arg(arg):
+ assert False
+ ''')
+
+ result = testdir.runpytest("--setup-show", p)
+ assert result.ret == 1
+
+ result.stdout.fnmatch_lines([
+ '*SETUP F arg*',
+ '*test_arg (fixtures used: arg)F',
+ '*TEARDOWN F arg*',
+ ])
diff --git a/testing/test_assertinterpret.py b/testing/test_assertinterpret.py
deleted file mode 100644
index 67a352ce7..000000000
--- a/testing/test_assertinterpret.py
+++ /dev/null
@@ -1,274 +0,0 @@
-"PYTEST_DONT_REWRITE"
-import py
-import pytest
-from _pytest.assertion import util
-
-
-def exvalue():
- return py.std.sys.exc_info()[1]
-
-def f():
- return 2
-
-def test_not_being_rewritten():
- assert "@py_builtins" not in globals()
-
-def test_assert():
- try:
- assert f() == 3
- except AssertionError:
- e = exvalue()
- s = str(e)
- assert s.startswith('assert 2 == 3\n')
-
-def test_assert_with_explicit_message():
- try:
- assert f() == 3, "hello"
- except AssertionError:
- e = exvalue()
- assert e.msg == 'hello'
-
-def test_assert_within_finally():
- excinfo = pytest.raises(ZeroDivisionError, """
- try:
- 1/0
- finally:
- i = 42
- """)
- s = excinfo.exconly()
- assert py.std.re.search("division.+by zero", s) is not None
-
- #def g():
- # A.f()
- #excinfo = getexcinfo(TypeError, g)
- #msg = getmsg(excinfo)
- #assert msg.find("must be called with A") != -1
-
-
-def test_assert_multiline_1():
- try:
- assert (f() ==
- 3)
- except AssertionError:
- e = exvalue()
- s = str(e)
- assert s.startswith('assert 2 == 3\n')
-
-def test_assert_multiline_2():
- try:
- assert (f() == (4,
- 3)[-1])
- except AssertionError:
- e = exvalue()
- s = str(e)
- assert s.startswith('assert 2 ==')
-
-def test_in():
- try:
- assert "hi" in [1, 2]
- except AssertionError:
- e = exvalue()
- s = str(e)
- assert s.startswith("assert 'hi' in")
-
-def test_is():
- try:
- assert 1 is 2
- except AssertionError:
- e = exvalue()
- s = str(e)
- assert s.startswith("assert 1 is 2")
-
-
-def test_attrib():
- class Foo(object):
- b = 1
- i = Foo()
- try:
- assert i.b == 2
- except AssertionError:
- e = exvalue()
- s = str(e)
- assert s.startswith("assert 1 == 2")
-
-def test_attrib_inst():
- class Foo(object):
- b = 1
- try:
- assert Foo().b == 2
- except AssertionError:
- e = exvalue()
- s = str(e)
- assert s.startswith("assert 1 == 2")
-
-def test_len():
- l = list(range(42))
- try:
- assert len(l) == 100
- except AssertionError:
- e = exvalue()
- s = str(e)
- assert s.startswith("assert 42 == 100")
- assert "where 42 = len([" in s
-
-def test_assert_non_string_message():
- class A:
- def __str__(self):
- return "hello"
- try:
- assert 0 == 1, A()
- except AssertionError:
- e = exvalue()
- assert e.msg == "hello"
-
-def test_assert_keyword_arg():
- def f(x=3):
- return False
- try:
- assert f(x=5)
- except AssertionError:
- e = exvalue()
- assert "x=5" in e.msg
-
-def test_private_class_variable():
- class X:
- def __init__(self):
- self.__v = 41
- def m(self):
- assert self.__v == 42
- try:
- X().m()
- except AssertionError:
- e = exvalue()
- assert "== 42" in e.msg
-
-# These tests should both fail, but should fail nicely...
-class WeirdRepr:
- def __repr__(self):
- return ''
-
-def bug_test_assert_repr():
- v = WeirdRepr()
- try:
- assert v == 1
- except AssertionError:
- e = exvalue()
- assert e.msg.find('WeirdRepr') != -1
- assert e.msg.find('second line') != -1
- assert 0
-
-def test_assert_non_string():
- try:
- assert 0, ['list']
- except AssertionError:
- e = exvalue()
- assert e.msg.find("list") != -1
-
-def test_assert_implicit_multiline():
- try:
- x = [1,2,3]
- assert x != [1,
- 2, 3]
- except AssertionError:
- e = exvalue()
- assert e.msg.find('assert [1, 2, 3] !=') != -1
-
-
-def test_assert_with_brokenrepr_arg():
- class BrokenRepr:
- def __repr__(self): 0 / 0
- e = AssertionError(BrokenRepr())
- if e.msg.find("broken __repr__") == -1:
- pytest.fail("broken __repr__ not handle correctly")
-
-def test_multiple_statements_per_line():
- try:
- a = 1; assert a == 2
- except AssertionError:
- e = exvalue()
- assert "assert 1 == 2" in e.msg
-
-def test_power():
- try:
- assert 2**3 == 7
- except AssertionError:
- e = exvalue()
- assert "assert (2 ** 3) == 7" in e.msg
-
-
-def test_assert_customizable_reprcompare(monkeypatch):
- monkeypatch.setattr(util, '_reprcompare', lambda *args: 'hello')
- try:
- assert 3 == 4
- except AssertionError:
- e = exvalue()
- s = str(e)
- assert "hello" in s
-
-def test_assert_long_source_1():
- try:
- assert len == [
- (None, ['somet text', 'more text']),
- ]
- except AssertionError:
- e = exvalue()
- s = str(e)
- assert 're-run' not in s
- assert 'somet text' in s
-
-def test_assert_long_source_2():
- try:
- assert(len == [
- (None, ['somet text', 'more text']),
- ])
- except AssertionError:
- e = exvalue()
- s = str(e)
- assert 're-run' not in s
- assert 'somet text' in s
-
-def test_assert_raise_alias(testdir):
- testdir.makepyfile("""
- "PYTEST_DONT_REWRITE"
- import sys
- EX = AssertionError
- def test_hello():
- raise EX("hello"
- "multi"
- "line")
- """)
- result = testdir.runpytest()
- result.stdout.fnmatch_lines([
- "*def test_hello*",
- "*raise EX*",
- "*1 failed*",
- ])
-
-
-def test_assert_raise_subclass():
- class SomeEx(AssertionError):
- def __init__(self, *args):
- super(SomeEx, self).__init__()
- try:
- raise SomeEx("hello")
- except AssertionError:
- s = str(exvalue())
- assert 're-run' not in s
- assert 'could not determine' in s
-
-def test_assert_raises_in_nonzero_of_object_pytest_issue10():
- class A(object):
- def __nonzero__(self):
- raise ValueError(42)
- def __lt__(self, other):
- return A()
- def __repr__(self):
- return ""
- def myany(x):
- return True
- try:
- assert not(myany(A() < 0))
- except AssertionError:
- e = exvalue()
- s = str(e)
- assert " < 0" in s
diff --git a/testing/test_assertion.py b/testing/test_assertion.py
index 90f74ca7f..63d6ac98e 100644
--- a/testing/test_assertion.py
+++ b/testing/test_assertion.py
@@ -3,10 +3,8 @@ import sys
import textwrap
import _pytest.assertion as plugin
-import _pytest._code
import py
import pytest
-from _pytest.assertion import reinterpret
from _pytest.assertion import util
PY3 = sys.version_info >= (3, 0)
@@ -23,24 +21,200 @@ def mock_config():
return Config()
-def interpret(expr):
- return reinterpret.reinterpret(expr, _pytest._code.Frame(sys._getframe(1)))
+class TestImportHookInstallation:
+
+ @pytest.mark.parametrize('initial_conftest', [True, False])
+ @pytest.mark.parametrize('mode', ['plain', 'rewrite'])
+ def test_conftest_assertion_rewrite(self, testdir, initial_conftest, mode):
+ """Test that conftest files are using assertion rewrite on import.
+ (#1619)
+ """
+ testdir.tmpdir.join('foo/tests').ensure(dir=1)
+ conftest_path = 'conftest.py' if initial_conftest else 'foo/conftest.py'
+ contents = {
+ conftest_path: """
+ import pytest
+ @pytest.fixture
+ def check_first():
+ def check(values, value):
+ assert values.pop(0) == value
+ return check
+ """,
+ 'foo/tests/test_foo.py': """
+ def test(check_first):
+ check_first([10, 30], 30)
+ """
+ }
+ testdir.makepyfile(**contents)
+ result = testdir.runpytest_subprocess('--assert=%s' % mode)
+ if mode == 'plain':
+ expected = 'E AssertionError'
+ elif mode == 'rewrite':
+ expected = '*assert 10 == 30*'
+ else:
+ assert 0
+ result.stdout.fnmatch_lines([expected])
+
+ @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:
def test_pytest_assertrepr_compare_called(self, testdir):
testdir.makeconftest("""
+ import pytest
l = []
def pytest_assertrepr_compare(op, left, right):
l.append((op, left, right))
- def pytest_funcarg__l(request):
+
+ @pytest.fixture
+ def list(request):
return l
""")
testdir.makepyfile("""
def test_hello():
assert 0 == 1
- def test_check(l):
- assert l == [("==", 0, 1)]
+ def test_check(list):
+ assert list == [("==", 0, 1)]
""")
result = testdir.runpytest("-v")
result.stdout.fnmatch_lines([
@@ -477,14 +651,6 @@ def test_assertion_options(testdir):
result = testdir.runpytest_subprocess("--assert=plain")
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):
testdir.makepyfile("""
def test_hello():
diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py
index 8d16bfc66..496034c23 100644
--- a/testing/test_assertrewrite.py
+++ b/testing/test_assertrewrite.py
@@ -12,7 +12,7 @@ if sys.platform.startswith("java"):
import _pytest._code
from _pytest.assertion import util
-from _pytest.assertion.rewrite import rewrite_asserts, PYTEST_TAG
+from _pytest.assertion.rewrite import rewrite_asserts, PYTEST_TAG, AssertionRewritingHook
from _pytest.main import EXIT_NOTESTSCOLLECTED
@@ -213,10 +213,12 @@ class TestAssertionRewrite:
return False
def f():
assert x() and x()
- assert getmsg(f, {"x" : x}) == "assert (x())"
+ assert getmsg(f, {"x" : x}) == """assert (False)
+ + where False = x()"""
def f():
assert False or x()
- assert getmsg(f, {"x" : x}) == "assert (False or x())"
+ assert getmsg(f, {"x" : x}) == """assert (False or False)
+ + where False = x()"""
def f():
assert 1 in {} and 2 in {}
assert getmsg(f) == "assert (1 in {})"
@@ -299,27 +301,34 @@ class TestAssertionRewrite:
ns = {"g" : g}
def f():
assert g()
- assert getmsg(f, ns) == """assert g()"""
+ assert getmsg(f, ns) == """assert False
+ + where False = g()"""
def f():
assert g(1)
- assert getmsg(f, ns) == """assert g(1)"""
+ assert getmsg(f, ns) == """assert False
+ + where False = g(1)"""
def f():
assert g(1, 2)
- assert getmsg(f, ns) == """assert g(1, 2)"""
+ assert getmsg(f, ns) == """assert False
+ + where False = g(1, 2)"""
def f():
assert g(1, g=42)
- assert getmsg(f, ns) == """assert g(1, g=42)"""
+ assert getmsg(f, ns) == """assert False
+ + where False = g(1, g=42)"""
def f():
assert g(1, 3, g=23)
- assert getmsg(f, ns) == """assert g(1, 3, g=23)"""
+ assert getmsg(f, ns) == """assert False
+ + where False = g(1, 3, g=23)"""
def f():
seq = [1, 2, 3]
assert g(*seq)
- assert getmsg(f, ns) == """assert g(*[1, 2, 3])"""
+ assert getmsg(f, ns) == """assert False
+ + where False = g(*[1, 2, 3])"""
def f():
x = "a"
assert g(**{x : 2})
- assert getmsg(f, ns) == """assert g(**{'a': 2})"""
+ assert getmsg(f, ns) == """assert False
+ + where False = g(**{'a': 2})"""
def test_attribute(self):
class X(object):
@@ -332,7 +341,8 @@ class TestAssertionRewrite:
def f():
x.a = False # noqa
assert x.a # noqa
- assert getmsg(f, ns) == """assert x.a"""
+ assert getmsg(f, ns) == """assert False
+ + where False = x.a"""
def test_comparisons(self):
def f():
@@ -514,6 +524,16 @@ def test_rewritten():
testdir.makepyfile("import a_package_without_init_py.module")
assert testdir.runpytest().ret == EXIT_NOTESTSCOLLECTED
+ def test_rewrite_warning(self, pytestconfig, monkeypatch):
+ hook = AssertionRewritingHook(pytestconfig)
+ warnings = []
+ def mywarn(code, msg):
+ warnings.append((code, msg))
+ monkeypatch.setattr(hook.config, 'warn', mywarn)
+ hook.mark_rewrite('_pytest')
+ assert '_pytest' in warnings[0][1]
+
+
class TestAssertionRewriteHookDetails(object):
def test_loader_is_package_false_for_module(self, testdir):
testdir.makepyfile(test_fun="""
@@ -694,40 +714,6 @@ class TestAssertionRewriteHookDetails(object):
result = testdir.runpytest()
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):
testdir.makepyfile("""
@@ -746,5 +732,28 @@ def test_issue731(testdir):
assert 'unbalanced braces' not in result.stdout.str()
-def test_collapse_false_unbalanced_braces():
- util._collapse_false('some text{ False\n{False = some more text\n}')
+class TestIssue925():
+ def test_simple_case(self, testdir):
+ testdir.makepyfile("""
+ def test_ternary_display():
+ assert (False == False) == False
+ """)
+ result = testdir.runpytest()
+ result.stdout.fnmatch_lines('*E*assert (False == False) == False')
+
+ def test_long_case(self, testdir):
+ testdir.makepyfile("""
+ def test_ternary_display():
+ assert False == (False == True) == True
+ """)
+ result = testdir.runpytest()
+ result.stdout.fnmatch_lines('*E*assert (False == True) == True')
+
+ def test_many_brackets(self, testdir):
+ testdir.makepyfile("""
+ def test_ternary_display():
+ assert True == ((False == True) == True)
+ """)
+ result = testdir.runpytest()
+ result.stdout.fnmatch_lines('*E*assert True == ((False == True) == True)')
+
diff --git a/testing/test_config.py b/testing/test_config.py
index fe550dbdd..12de364cc 100644
--- a/testing/test_config.py
+++ b/testing/test_config.py
@@ -373,10 +373,14 @@ def test_preparse_ordering_with_setuptools(testdir, monkeypatch):
pkg_resources = pytest.importorskip("pkg_resources")
def my_iter(name):
assert name == "pytest11"
+ class Dist:
+ project_name = 'spam'
+ version = '1.0'
+ def _get_metadata(self, name):
+ return ['foo.txt,sha256=abc,123']
class EntryPoint:
name = "mytestplugin"
- class dist:
- pass
+ dist = Dist()
def load(self):
class PseudoPlugin:
x = 42
@@ -391,12 +395,40 @@ def test_preparse_ordering_with_setuptools(testdir, monkeypatch):
plugin = config.pluginmanager.getplugin("mytestplugin")
assert plugin.x == 42
+
+def test_setuptools_importerror_issue1479(testdir, monkeypatch):
+ pkg_resources = pytest.importorskip("pkg_resources")
+ def my_iter(name):
+ assert name == "pytest11"
+ class Dist:
+ project_name = 'spam'
+ version = '1.0'
+ def _get_metadata(self, name):
+ return ['foo.txt,sha256=abc,123']
+ class EntryPoint:
+ name = "mytestplugin"
+ dist = Dist()
+ def load(self):
+ raise ImportError("Don't hide me!")
+ return iter([EntryPoint()])
+
+ monkeypatch.setattr(pkg_resources, 'iter_entry_points', my_iter)
+ with pytest.raises(ImportError):
+ testdir.parseconfig()
+
+
def test_plugin_preparse_prevents_setuptools_loading(testdir, monkeypatch):
pkg_resources = pytest.importorskip("pkg_resources")
def my_iter(name):
assert name == "pytest11"
+ class Dist:
+ project_name = 'spam'
+ version = '1.0'
+ def _get_metadata(self, name):
+ return ['foo.txt,sha256=abc,123']
class EntryPoint:
name = "mytestplugin"
+ dist = Dist()
def load(self):
assert 0, "should not arrive here"
return iter([EntryPoint()])
@@ -488,7 +520,6 @@ def test_load_initial_conftest_last_ordering(testdir):
expected = [
"_pytest.config",
'test_config',
- '_pytest.assertion',
'_pytest.capture',
]
assert [x.function.__module__ for x in l] == expected
@@ -522,11 +553,11 @@ class TestWarning:
def test_hello(fix):
pass
""")
- result = testdir.runpytest()
+ result = testdir.runpytest("--disable-pytest-warnings")
assert result.parseoutcomes()["pytest-warnings"] > 0
assert "hello" not in result.stdout.str()
- result = testdir.runpytest("-rw")
+ result = testdir.runpytest()
result.stdout.fnmatch_lines("""
===*pytest-warning summary*===
*WT1*test_warn_on_test_item*:5*hello*
@@ -671,4 +702,4 @@ class TestOverrideIniArgs:
"ini2:url=/tmp/user2?a=b&d=e",
"ini3:True",
"ini4:False"
- ])
\ No newline at end of file
+ ])
diff --git a/testing/test_conftest.py b/testing/test_conftest.py
index 54a7d4009..377283eb9 100644
--- a/testing/test_conftest.py
+++ b/testing/test_conftest.py
@@ -203,6 +203,7 @@ def test_conftest_import_order(testdir, monkeypatch):
def impct(p):
return p
conftest = PytestPluginManager()
+ conftest._confcutdir = testdir.tmpdir
monkeypatch.setattr(conftest, '_importconftest', impct)
assert conftest._getconftestmodules(sub) == [ct1, ct2]
diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py
index 8291ea148..e05875942 100644
--- a/testing/test_junitxml.py
+++ b/testing/test_junitxml.py
@@ -119,7 +119,10 @@ class TestPython:
def test_setup_error(self, testdir):
testdir.makepyfile("""
- def pytest_funcarg__arg(request):
+ import pytest
+
+ @pytest.fixture
+ def arg(request):
raise ValueError()
def test_function(arg):
pass
@@ -131,7 +134,7 @@ class TestPython:
tnode = node.find_first_by_tag("testcase")
tnode.assert_attr(
file="test_setup_error.py",
- line="2",
+ line="5",
classname="test_setup_error",
name="test_function")
fnode = tnode.find_first_by_tag("error")
@@ -444,7 +447,10 @@ class TestPython:
def test_setup_error_captures_stdout(self, testdir):
testdir.makepyfile("""
- def pytest_funcarg__arg(request):
+ import pytest
+
+ @pytest.fixture
+ def arg(request):
print('hello-stdout')
raise ValueError()
def test_function(arg):
@@ -459,7 +465,10 @@ class TestPython:
def test_setup_error_captures_stderr(self, testdir):
testdir.makepyfile("""
import sys
- def pytest_funcarg__arg(request):
+ import pytest
+
+ @pytest.fixture
+ def arg(request):
sys.stderr.write('hello-stderr')
raise ValueError()
def test_function(arg):
diff --git a/testing/test_mark.py b/testing/test_mark.py
index cf59baed9..e0bf3c3c8 100644
--- a/testing/test_mark.py
+++ b/testing/test_mark.py
@@ -481,7 +481,8 @@ class TestFunctional:
def test_mark_dynamically_in_funcarg(self, testdir):
testdir.makeconftest("""
import pytest
- def pytest_funcarg__arg(request):
+ @pytest.fixture
+ def arg(request):
request.applymarker(pytest.mark.hello)
def pytest_terminal_summary(terminalreporter):
l = terminalreporter.stats['passed']
diff --git a/testing/test_monkeypatch.py b/testing/test_monkeypatch.py
index 048c942c8..7599be47f 100644
--- a/testing/test_monkeypatch.py
+++ b/testing/test_monkeypatch.py
@@ -3,10 +3,11 @@ import sys
import textwrap
import pytest
-from _pytest.monkeypatch import monkeypatch as MonkeyPatch
+from _pytest.monkeypatch import MonkeyPatch
-def pytest_funcarg__mp(request):
+@pytest.fixture
+def mp(request):
cwd = os.getcwd()
sys_path = list(sys.path)
@@ -205,7 +206,7 @@ def test_setenv_prepend():
def test_monkeypatch_plugin(testdir):
reprec = testdir.inline_runsource("""
def test_method(monkeypatch):
- assert monkeypatch.__class__.__name__ == "monkeypatch"
+ assert monkeypatch.__class__.__name__ == "MonkeyPatch"
""")
res = reprec.countoutcomes()
assert tuple(res) == (1, 0, 0), res
diff --git a/testing/test_parseopt.py b/testing/test_parseopt.py
index afec1114c..cc9aa23cd 100644
--- a/testing/test_parseopt.py
+++ b/testing/test_parseopt.py
@@ -29,6 +29,9 @@ class TestParser:
assert argument.dest == 'test'
argument = parseopt.Argument('-t', '--test', dest='abc')
assert argument.dest == 'abc'
+ assert str(argument) == (
+ "Argument(_short_opts: ['-t'], _long_opts: ['--test'], dest: 'abc')"
+ )
def test_argument_type(self):
argument = parseopt.Argument('-t', dest='abc', type='int')
diff --git a/testing/test_pdb.py b/testing/test_pdb.py
index eeddcf0ae..6e4f3e805 100644
--- a/testing/test_pdb.py
+++ b/testing/test_pdb.py
@@ -1,6 +1,7 @@
import sys
import _pytest._code
+import pytest
def runpdb_and_get_report(testdir, source):
@@ -12,12 +13,14 @@ def runpdb_and_get_report(testdir, source):
class TestPDB:
- def pytest_funcarg__pdblist(self, request):
- monkeypatch = request.getfuncargvalue("monkeypatch")
+
+ @pytest.fixture
+ def pdblist(self, request):
+ monkeypatch = request.getfixturevalue("monkeypatch")
pdblist = []
def mypdb(*args):
pdblist.append(args)
- plugin = request.config.pluginmanager.getplugin('pdb')
+ plugin = request.config.pluginmanager.getplugin('debugging')
monkeypatch.setattr(plugin, 'post_mortem', mypdb)
return pdblist
@@ -311,3 +314,28 @@ class TestPDB:
child.sendeof()
if child.isalive():
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"]
diff --git a/testing/test_runner.py b/testing/test_runner.py
index 7db809b24..bc3ff6c89 100644
--- a/testing/test_runner.py
+++ b/testing/test_runner.py
@@ -229,11 +229,12 @@ class BaseFunctionalTests:
assert reps[5].failed
def test_exact_teardown_issue1206(self, testdir):
+ """issue shadowing error with wrong number of arguments on teardown_method."""
rec = testdir.inline_runsource("""
import pytest
class TestClass:
- def teardown_method(self):
+ def teardown_method(self, x, y, z):
pass
def test_method(self):
@@ -256,9 +257,9 @@ class BaseFunctionalTests:
assert reps[2].when == "teardown"
assert reps[2].longrepr.reprcrash.message in (
# 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
- '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):
@@ -399,13 +400,15 @@ def test_callinfo():
@pytest.mark.xfail
def test_runtest_in_module_ordering(testdir):
p1 = testdir.makepyfile("""
+ import pytest
def pytest_runtest_setup(item): # runs after class-level!
item.function.mylist.append("module")
class TestClass:
def pytest_runtest_setup(self, item):
assert not hasattr(item.function, 'mylist')
item.function.mylist = ['class']
- def pytest_funcarg__mylist(self, request):
+ @pytest.fixture
+ def mylist(self, request):
return request.function.mylist
def pytest_runtest_call(self, item, __multicall__):
try:
diff --git a/testing/test_runner_xunit.py b/testing/test_runner_xunit.py
index f32a1311b..e1f0924c6 100644
--- a/testing/test_runner_xunit.py
+++ b/testing/test_runner_xunit.py
@@ -1,6 +1,8 @@
#
# test correct setup/teardowns at
# module, class, and instance level
+import pytest
+
def test_module_and_function_setup(testdir):
reprec = testdir.inline_runsource("""
@@ -234,7 +236,8 @@ def test_setup_funcarg_setup_when_outer_scope_fails(testdir):
import pytest
def setup_module(mod):
raise ValueError(42)
- def pytest_funcarg__hello(request):
+ @pytest.fixture
+ def hello(request):
raise ValueError("xyz43")
def test_function1(hello):
pass
@@ -250,3 +253,53 @@ def test_setup_funcarg_setup_when_outer_scope_fails(testdir):
"*2 error*"
])
assert "xyz43" not in result.stdout.str()
+
+
+@pytest.mark.parametrize('arg', ['', 'arg'])
+def test_setup_teardown_function_level_with_optional_argument(testdir, monkeypatch, arg):
+ """parameter to setup/teardown xunit-style functions parameter is now optional (#1728)."""
+ import sys
+ trace_setups_teardowns = []
+ monkeypatch.setattr(sys, 'trace_setups_teardowns', trace_setups_teardowns, raising=False)
+ p = testdir.makepyfile("""
+ import pytest
+ import sys
+
+ trace = sys.trace_setups_teardowns.append
+
+ def setup_module({arg}): trace('setup_module')
+ def teardown_module({arg}): trace('teardown_module')
+
+ def setup_function({arg}): trace('setup_function')
+ def teardown_function({arg}): trace('teardown_function')
+
+ def test_function_1(): pass
+ def test_function_2(): pass
+
+ class Test:
+ def setup_method(self, {arg}): trace('setup_method')
+ def teardown_method(self, {arg}): trace('teardown_method')
+
+ def test_method_1(self): pass
+ def test_method_2(self): pass
+ """.format(arg=arg))
+ result = testdir.inline_run(p)
+ result.assertoutcome(passed=4)
+
+ expected = [
+ 'setup_module',
+
+ 'setup_function',
+ 'teardown_function',
+ 'setup_function',
+ 'teardown_function',
+
+ 'setup_method',
+ 'teardown_method',
+
+ 'setup_method',
+ 'teardown_method',
+
+ 'teardown_module',
+ ]
+ assert trace_setups_teardowns == expected
diff --git a/testing/test_session.py b/testing/test_session.py
index e3b9f0fcc..a7dcb27a4 100644
--- a/testing/test_session.py
+++ b/testing/test_session.py
@@ -197,6 +197,14 @@ class TestNewSession(SessionTests):
colfail = [x for x in finished if x.failed]
assert len(colfail) == 1
+ def test_minus_x_overriden_by_maxfail(self, testdir):
+ testdir.makepyfile(__init__="")
+ testdir.makepyfile(test_one="xxxx", test_two="yyyy", test_third="zzz")
+ reprec = testdir.inline_run("-x", "--maxfail=2", testdir.tmpdir)
+ finished = reprec.getreports("pytest_collectreport")
+ colfail = [x for x in finished if x.failed]
+ assert len(colfail) == 2
+
def test_plugin_specify(testdir):
pytest.raises(ImportError, """
diff --git a/testing/test_skipping.py b/testing/test_skipping.py
index d2a925a4e..bc719b142 100644
--- a/testing/test_skipping.py
+++ b/testing/test_skipping.py
@@ -309,7 +309,8 @@ class TestXFail:
def test_dynamic_xfail_no_run(self, testdir):
p = testdir.makepyfile("""
import pytest
- def pytest_funcarg__arg(request):
+ @pytest.fixture
+ def arg(request):
request.applymarker(pytest.mark.xfail(run=False))
def test_this(arg):
assert 0
@@ -323,7 +324,8 @@ class TestXFail:
def test_dynamic_xfail_set_during_funcarg_setup(self, testdir):
p = testdir.makepyfile("""
import pytest
- def pytest_funcarg__arg(request):
+ @pytest.fixture
+ def arg(request):
request.applymarker(pytest.mark.xfail)
def test_this2(arg):
assert 0
diff --git a/testing/test_terminal.py b/testing/test_terminal.py
index 0d1aac999..51e62f3a0 100644
--- a/testing/test_terminal.py
+++ b/testing/test_terminal.py
@@ -590,17 +590,30 @@ def test_getreportopt():
class config:
class option:
reportchars = ""
+ disablepytestwarnings = True
config.option.reportchars = "sf"
assert getreportopt(config) == "sf"
- config.option.reportchars = "sfx"
+ config.option.reportchars = "sfxw"
assert getreportopt(config) == "sfx"
+ config.option.reportchars = "sfx"
+ config.option.disablepytestwarnings = False
+ assert getreportopt(config) == "sfxw"
+
+ config.option.reportchars = "sfxw"
+ config.option.disablepytestwarnings = False
+ assert getreportopt(config) == "sfxw"
+
+
def test_terminalreporter_reportopt_addopts(testdir):
testdir.makeini("[pytest]\naddopts=-rs")
testdir.makepyfile("""
- def pytest_funcarg__tr(request):
+ import pytest
+
+ @pytest.fixture
+ def tr(request):
tr = request.config.pluginmanager.getplugin("terminalreporter")
return tr
def test_opt(tr):
@@ -614,7 +627,10 @@ def test_terminalreporter_reportopt_addopts(testdir):
def test_tbstyle_short(testdir):
p = testdir.makepyfile("""
- def pytest_funcarg__arg(request):
+ import pytest
+
+ @pytest.fixture
+ def arg(request):
return 42
def test_opt(arg):
x = 0
@@ -625,7 +641,7 @@ def test_tbstyle_short(testdir):
assert 'arg = 42' not in s
assert 'x = 0' not in s
result.stdout.fnmatch_lines([
- "*%s:5*" % p.basename,
+ "*%s:8*" % p.basename,
" assert x",
"E assert*",
])
diff --git a/testing/test_unittest.py b/testing/test_unittest.py
index 73735b8cd..88c117657 100644
--- a/testing/test_unittest.py
+++ b/testing/test_unittest.py
@@ -265,8 +265,8 @@ def test_testcase_custom_exception_info(testdir, type):
def run(self, result):
excinfo = pytest.raises(ZeroDivisionError, lambda: 0/0)
# we fake an incompatible exception info
- from _pytest.monkeypatch import monkeypatch
- mp = monkeypatch()
+ from _pytest.monkeypatch import MonkeyPatch
+ mp = MonkeyPatch()
def t(*args):
mp.undo()
raise TypeError()
diff --git a/tox.ini b/tox.ini
index 957849559..e2ccea837 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,6 +1,7 @@
[tox]
minversion=2.0
distshare={homedir}/.tox/distshare
+# make sure to update enviroment list on appveyor.yml
envlist=
linting,py26,py27,py33,py34,py35,pypy,
{py27,py35}-{pexpect,xdist,trial},