Merge master into features

This commit is contained in:
Daniel Hahler 2019-04-14 23:22:21 +02:00
commit 6f0a5789fb
23 changed files with 359 additions and 161 deletions

View File

@ -1,16 +1,16 @@
exclude: doc/en/example/py2py3/test_py2.py
repos:
- repo: https://github.com/ambv/black
rev: 18.9b0
rev: 19.3b0
hooks:
- id: black
args: [--safe, --quiet]
language_version: python3
- repo: https://github.com/asottile/blacken-docs
rev: v0.3.0
rev: v0.5.0
hooks:
- id: blacken-docs
additional_dependencies: [black==18.9b0]
additional_dependencies: [black==19.3b0]
language_version: python3
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.1.0
@ -22,22 +22,22 @@ repos:
exclude: _pytest/debugging.py
language_version: python3
- repo: https://gitlab.com/pycqa/flake8
rev: 3.7.0
rev: 3.7.7
hooks:
- id: flake8
language_version: python3
- repo: https://github.com/asottile/reorder_python_imports
rev: v1.3.5
rev: v1.4.0
hooks:
- id: reorder-python-imports
args: ['--application-directories=.:src']
- repo: https://github.com/asottile/pyupgrade
rev: v1.11.1
rev: v1.15.0
hooks:
- id: pyupgrade
args: [--keep-percent-format]
- repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.2.0
rev: v1.3.0
hooks:
- id: rst-backticks
- repo: local

View File

@ -16,4 +16,4 @@ run = 'fc("/d")'
if __name__ == "__main__":
print(timeit.timeit(run, setup=setup % imports[0], number=count))
print((timeit.timeit(run, setup=setup % imports[1], number=count)))
print(timeit.timeit(run, setup=setup % imports[1], number=count))

View File

@ -0,0 +1 @@
Produce a warning when unknown keywords are passed to ``pytest.param(...)``.

View File

@ -0,0 +1 @@
Invalidate import caches with ``monkeypatch.syspath_prepend``, which is required with namespace packages being used.

View File

@ -12,12 +12,15 @@ Asserting with the ``assert`` statement
``pytest`` allows you to use the standard python ``assert`` for verifying
expectations and values in Python tests. For example, you can write the
following::
following:
.. code-block:: python
# content of test_assert1.py
def f():
return 3
def test_function():
assert f() == 4
@ -52,7 +55,9 @@ operators. (See :ref:`tbreportdemo`). This allows you to use the
idiomatic python constructs without boilerplate code while not losing
introspection information.
However, if you specify a message with the assertion like this::
However, if you specify a message with the assertion like this:
.. code-block:: python
assert a % 2 == 0, "value was odd, should be even"
@ -67,22 +72,29 @@ Assertions about expected exceptions
------------------------------------------
In order to write assertions about raised exceptions, you can use
``pytest.raises`` as a context manager like this::
``pytest.raises`` as a context manager like this:
.. code-block:: python
import pytest
def test_zero_division():
with pytest.raises(ZeroDivisionError):
1 / 0
and if you need to have access to the actual exception info you may use::
and if you need to have access to the actual exception info you may use:
.. code-block:: python
def test_recursion_depth():
with pytest.raises(RuntimeError) as excinfo:
def f():
f()
f()
assert 'maximum recursion' in str(excinfo.value)
assert "maximum recursion" in str(excinfo.value)
``excinfo`` is a ``ExceptionInfo`` instance, which is a wrapper around
the actual exception raised. The main attributes of interest are
@ -90,15 +102,19 @@ the actual exception raised. The main attributes of interest are
You can pass a ``match`` keyword parameter to the context-manager to test
that a regular expression matches on the string representation of an exception
(similar to the ``TestCase.assertRaisesRegexp`` method from ``unittest``)::
(similar to the ``TestCase.assertRaisesRegexp`` method from ``unittest``):
.. code-block:: python
import pytest
def myfunc():
raise ValueError("Exception 123 raised")
def test_match():
with pytest.raises(ValueError, match=r'.* 123 .*'):
with pytest.raises(ValueError, match=r".* 123 .*"):
myfunc()
The regexp parameter of the ``match`` method is matched with the ``re.search``
@ -107,7 +123,9 @@ well.
There's an alternate form of the ``pytest.raises`` function where you pass
a function that will be executed with the given ``*args`` and ``**kwargs`` and
assert that the given exception is raised::
assert that the given exception is raised:
.. code-block:: python
pytest.raises(ExpectedException, func, *args, **kwargs)
@ -116,7 +134,9 @@ exception* or *wrong exception*.
Note that it is also possible to specify a "raises" argument to
``pytest.mark.xfail``, which checks that the test is failing in a more
specific way than just having any exception raised::
specific way than just having any exception raised:
.. code-block:: python
@pytest.mark.xfail(raises=IndexError)
def test_f():
@ -148,10 +168,13 @@ Making use of context-sensitive comparisons
.. versionadded:: 2.0
``pytest`` has rich support for providing context-sensitive information
when it encounters comparisons. For example::
when it encounters comparisons. For example:
.. code-block:: python
# content of test_assert2.py
def test_set_comparison():
set1 = set("1308")
set2 = set("8035")
@ -205,16 +228,21 @@ the ``pytest_assertrepr_compare`` hook.
:noindex:
As an example consider adding the following hook in a :ref:`conftest.py <conftest.py>`
file which provides an alternative explanation for ``Foo`` objects::
file which provides an alternative explanation for ``Foo`` objects:
.. code-block:: python
# content of conftest.py
from test_foocompare import Foo
def pytest_assertrepr_compare(op, left, right):
if isinstance(left, Foo) and isinstance(right, Foo) and op == "==":
return ['Comparing Foo instances:',
' vals: %s != %s' % (left.val, right.val)]
return ["Comparing Foo instances:", " vals: %s != %s" % (left.val, right.val)]
now, given this test module::
now, given this test module:
.. code-block:: python
# content of test_foocompare.py
class Foo(object):
@ -224,6 +252,7 @@ now, given this test module::
def __eq__(self, other):
return self.val == other.val
def test_compare():
f1 = Foo(1)
f2 = Foo(2)

View File

@ -9,18 +9,28 @@ Here are some example using the :ref:`mark` mechanism.
Marking test functions and selecting them for a run
----------------------------------------------------
You can "mark" a test function with custom metadata like this::
You can "mark" a test function with custom metadata like this:
.. code-block:: python
# content of test_server.py
import pytest
@pytest.mark.webtest
def test_send_http():
pass # perform some webtest test for your app
pass # perform some webtest test for your app
def test_something_quick():
pass
def test_another():
pass
class TestClass(object):
def test_method(self):
pass
@ -257,14 +267,19 @@ Marking whole classes or modules
----------------------------------------------------
You may use ``pytest.mark`` decorators with classes to apply markers to all of
its test methods::
its test methods:
.. code-block:: python
# content of test_mark_classlevel.py
import pytest
@pytest.mark.webtest
class TestClass(object):
def test_startup(self):
pass
def test_startup_and_more(self):
pass
@ -272,17 +287,23 @@ This is equivalent to directly applying the decorator to the
two test functions.
To remain backward-compatible with Python 2.4 you can also set a
``pytestmark`` attribute on a TestClass like this::
``pytestmark`` attribute on a TestClass like this:
.. code-block:: python
import pytest
class TestClass(object):
pytestmark = pytest.mark.webtest
or if you need to use multiple markers you can use a list::
or if you need to use multiple markers you can use a list:
.. code-block:: python
import pytest
class TestClass(object):
pytestmark = [pytest.mark.webtest, pytest.mark.slowtest]
@ -305,18 +326,19 @@ Marking individual tests when using parametrize
When using parametrize, applying a mark will make it apply
to each individual test. However it is also possible to
apply a marker to an individual test instance::
apply a marker to an individual test instance:
.. code-block:: python
import pytest
@pytest.mark.foo
@pytest.mark.parametrize(("n", "expected"), [
(1, 2),
pytest.param((1, 3), marks=pytest.mark.bar),
(2, 3),
])
@pytest.mark.parametrize(
("n", "expected"), [(1, 2), pytest.param((1, 3), marks=pytest.mark.bar), (2, 3)]
)
def test_increment(n, expected):
assert n + 1 == expected
assert n + 1 == expected
In this example the mark "foo" will apply to each of the three
tests, whereas the "bar" mark is only applied to the second test.
@ -332,31 +354,46 @@ Custom marker and command line option to control test runs
Plugins can provide custom markers and implement specific behaviour
based on it. This is a self-contained example which adds a command
line option and a parametrized test function marker to run tests
specifies via named environments::
specifies via named environments:
.. code-block:: python
# content of conftest.py
import pytest
def pytest_addoption(parser):
parser.addoption("-E", action="store", metavar="NAME",
help="only run tests matching the environment NAME.")
parser.addoption(
"-E",
action="store",
metavar="NAME",
help="only run tests matching the environment NAME.",
)
def pytest_configure(config):
# register an additional marker
config.addinivalue_line("markers",
"env(name): mark test to run only on named environment")
config.addinivalue_line(
"markers", "env(name): mark test to run only on named environment"
)
def pytest_runtest_setup(item):
envnames = [mark.args[0] for mark in item.iter_markers(name='env')]
envnames = [mark.args[0] for mark in item.iter_markers(name="env")]
if envnames:
if item.config.getoption("-E") not in envnames:
pytest.skip("test requires env in %r" % envnames)
A test file using this local plugin::
A test file using this local plugin:
.. code-block:: python
# content of test_someenv.py
import pytest
@pytest.mark.env("stage1")
def test_basic_db_operation():
pass
@ -423,25 +460,32 @@ Passing a callable to custom markers
.. regendoc:wipe
Below is the config file that will be used in the next examples::
Below is the config file that will be used in the next examples:
.. code-block:: python
# content of conftest.py
import sys
def pytest_runtest_setup(item):
for marker in item.iter_markers(name='my_marker'):
for marker in item.iter_markers(name="my_marker"):
print(marker)
sys.stdout.flush()
A custom marker can have its argument set, i.e. ``args`` and ``kwargs`` properties, defined by either invoking it as a callable or using ``pytest.mark.MARKER_NAME.with_args``. These two methods achieve the same effect most of the time.
However, if there is a callable as the single positional argument with no keyword arguments, using the ``pytest.mark.MARKER_NAME(c)`` will not pass ``c`` as a positional argument but decorate ``c`` with the custom marker (see :ref:`MarkDecorator <mark>`). Fortunately, ``pytest.mark.MARKER_NAME.with_args`` comes to the rescue::
However, if there is a callable as the single positional argument with no keyword arguments, using the ``pytest.mark.MARKER_NAME(c)`` will not pass ``c`` as a positional argument but decorate ``c`` with the custom marker (see :ref:`MarkDecorator <mark>`). Fortunately, ``pytest.mark.MARKER_NAME.with_args`` comes to the rescue:
.. code-block:: python
# content of test_custom_marker.py
import pytest
def hello_world(*args, **kwargs):
return 'Hello World'
return "Hello World"
@pytest.mark.my_marker.with_args(hello_world)
def test_with_args():
@ -467,12 +511,16 @@ Reading markers which were set from multiple places
.. regendoc:wipe
If you are heavily using markers in your test suite you may encounter the case where a marker is applied several times to a test function. From plugin
code you can read over all such settings. Example::
code you can read over all such settings. Example:
.. code-block:: python
# content of test_mark_three_times.py
import pytest
pytestmark = pytest.mark.glob("module", x=1)
@pytest.mark.glob("class", x=2)
class TestClass(object):
@pytest.mark.glob("function", x=3)
@ -480,13 +528,16 @@ code you can read over all such settings. Example::
pass
Here we have the marker "glob" applied three times to the same
test function. From a conftest file we can read it like this::
test function. From a conftest file we can read it like this:
.. code-block:: python
# content of conftest.py
import sys
def pytest_runtest_setup(item):
for mark in item.iter_markers(name='glob'):
for mark in item.iter_markers(name="glob"):
print("glob args=%s kwargs=%s" % (mark.args, mark.kwargs))
sys.stdout.flush()
@ -510,7 +561,9 @@ Consider you have a test suite which marks tests for particular platforms,
namely ``pytest.mark.darwin``, ``pytest.mark.win32`` etc. and you
also have tests that run on all platforms and have no specific
marker. If you now want to have a way to only run the tests
for your particular platform, you could use the following plugin::
for your particular platform, you could use the following plugin:
.. code-block:: python
# content of conftest.py
#
@ -519,6 +572,7 @@ for your particular platform, you could use the following plugin::
ALL = set("darwin linux win32".split())
def pytest_runtest_setup(item):
supported_platforms = ALL.intersection(mark.name for mark in item.iter_markers())
plat = sys.platform
@ -526,24 +580,30 @@ for your particular platform, you could use the following plugin::
pytest.skip("cannot run on platform %s" % (plat))
then tests will be skipped if they were specified for a different platform.
Let's do a little test file to show how this looks like::
Let's do a little test file to show how this looks like:
.. code-block:: python
# content of test_plat.py
import pytest
@pytest.mark.darwin
def test_if_apple_is_evil():
pass
@pytest.mark.linux
def test_if_linux_works():
pass
@pytest.mark.win32
def test_if_win32_crashes():
pass
def test_runs_everywhere():
pass
@ -589,28 +649,38 @@ Automatically adding markers based on test names
If you a test suite where test function names indicate a certain
type of test, you can implement a hook that automatically defines
markers so that you can use the ``-m`` option with it. Let's look
at this test module::
at this test module:
.. code-block:: python
# content of test_module.py
def test_interface_simple():
assert 0
def test_interface_complex():
assert 0
def test_event_simple():
assert 0
def test_something_else():
assert 0
We want to dynamically define two markers and can do it in a
``conftest.py`` plugin::
``conftest.py`` plugin:
.. code-block:: python
# content of conftest.py
import pytest
def pytest_collection_modifyitems(items):
for item in items:
if "interface" in item.nodeid:

View File

@ -515,21 +515,25 @@ Set marks or test ID for individual parametrized test
--------------------------------------------------------------------
Use ``pytest.param`` to apply marks or set test ID to individual parametrized test.
For example::
For example:
.. code-block:: python
# content of test_pytest_param_example.py
import pytest
@pytest.mark.parametrize('test_input,expected', [
('3+5', 8),
pytest.param('1+7', 8,
marks=pytest.mark.basic),
pytest.param('2+4', 6,
marks=pytest.mark.basic,
id='basic_2+4'),
pytest.param('6*9', 42,
marks=[pytest.mark.basic, pytest.mark.xfail],
id='basic_6*9'),
])
@pytest.mark.parametrize(
"test_input,expected",
[
("3+5", 8),
pytest.param("1+7", 8, marks=pytest.mark.basic),
pytest.param("2+4", 6, marks=pytest.mark.basic, id="basic_2+4"),
pytest.param(
"6*9", 42, marks=[pytest.mark.basic, pytest.mark.xfail], id="basic_6*9"
),
],
)
def test_eval(test_input, expected):
assert eval(test_input) == expected

View File

@ -1,13 +1,9 @@
.. _`tbreportdemo`:
Demo of Python failure reports with pytest
==================================================
==========================================
Here is a nice run of several tens of failures
and how ``pytest`` presents things (unfortunately
not showing the nice colors here in the HTML that you
get on the terminal - we are working on that):
Here is a nice run of several failures and how ``pytest`` presents things:
.. code-block:: pytest

View File

@ -57,14 +57,16 @@ Applying marks to ``@pytest.mark.parametrize`` parameters
.. versionchanged:: 3.1
Prior to version 3.1 the supported mechanism for marking values
used the syntax::
used the syntax:
.. code-block:: python
import pytest
@pytest.mark.parametrize("test_input,expected", [
("3+5", 8),
("2+4", 6),
pytest.mark.xfail(("6*9", 42),),
])
@pytest.mark.parametrize(
"test_input,expected", [("3+5", 8), ("2+4", 6), pytest.mark.xfail(("6*9", 42))]
)
def test_eval(test_input, expected):
assert eval(test_input) == expected
@ -105,9 +107,13 @@ Conditions as strings instead of booleans
.. versionchanged:: 2.4
Prior to pytest-2.4 the only way to specify skipif/xfail conditions was
to use strings::
to use strings:
.. code-block:: python
import sys
@pytest.mark.skipif("sys.version_info >= (3,3)")
def test_function():
...
@ -139,17 +145,20 @@ dictionary which is constructed as follows:
expression is applied.
The pytest ``config`` object allows you to skip based on a test
configuration value which you might have added::
configuration value which you might have added:
.. code-block:: python
@pytest.mark.skipif("not config.getvalue('db')")
def test_function(...):
def test_function():
...
The equivalent with "boolean conditions" is::
The equivalent with "boolean conditions" is:
@pytest.mark.skipif(not pytest.config.getvalue("db"),
reason="--db was not specified")
def test_function(...):
.. code-block:: python
@pytest.mark.skipif(not pytest.config.getvalue("db"), reason="--db was not specified")
def test_function():
pass
.. note::
@ -164,12 +173,16 @@ The equivalent with "boolean conditions" is::
.. versionchanged:: 2.4
Previous to version 2.4 to set a break point in code one needed to use ``pytest.set_trace()``::
Previous to version 2.4 to set a break point in code one needed to use ``pytest.set_trace()``:
.. code-block:: python
import pytest
def test_function():
...
pytest.set_trace() # invoke PDB debugger and tracing
pytest.set_trace() # invoke PDB debugger and tracing
This is no longer needed and one can use the native ``import pdb;pdb.set_trace()`` call directly.

View File

@ -36,15 +36,15 @@ pytest enables test parametrization at several levels:
The builtin :ref:`pytest.mark.parametrize ref` decorator enables
parametrization of arguments for a test function. Here is a typical example
of a test function that implements checking that a certain input leads
to an expected output::
to an expected output:
.. code-block:: python
# content of test_expectation.py
import pytest
@pytest.mark.parametrize("test_input,expected", [
("3+5", 8),
("2+4", 6),
("6*9", 42),
])
@pytest.mark.parametrize("test_input,expected", [("3+5", 8), ("2+4", 6), ("6*9", 42)])
def test_eval(test_input, expected):
assert eval(test_input) == expected
@ -104,16 +104,18 @@ Note that you could also use the parametrize marker on a class or a module
(see :ref:`mark`) which would invoke several functions with the argument sets.
It is also possible to mark individual test instances within parametrize,
for example with the builtin ``mark.xfail``::
for example with the builtin ``mark.xfail``:
.. code-block:: python
# content of test_expectation.py
import pytest
@pytest.mark.parametrize("test_input,expected", [
("3+5", 8),
("2+4", 6),
pytest.param("6*9", 42,
marks=pytest.mark.xfail),
])
@pytest.mark.parametrize(
"test_input,expected",
[("3+5", 8), ("2+4", 6), pytest.param("6*9", 42, marks=pytest.mark.xfail)],
)
def test_eval(test_input, expected):
assert eval(test_input) == expected
@ -140,9 +142,13 @@ example, if they're dynamically generated by some function - the behaviour of
pytest is defined by the :confval:`empty_parameter_set_mark` option.
To get all combinations of multiple parametrized arguments you can stack
``parametrize`` decorators::
``parametrize`` decorators:
.. code-block:: python
import pytest
@pytest.mark.parametrize("x", [0, 1])
@pytest.mark.parametrize("y", [2, 3])
def test_foo(x, y):
@ -166,26 +172,36 @@ parametrization.
For example, let's say we want to run a test taking string inputs which
we want to set via a new ``pytest`` command line option. Let's first write
a simple test accepting a ``stringinput`` fixture function argument::
a simple test accepting a ``stringinput`` fixture function argument:
.. code-block:: python
# content of test_strings.py
def test_valid_string(stringinput):
assert stringinput.isalpha()
Now we add a ``conftest.py`` file containing the addition of a
command line option and the parametrization of our test function::
command line option and the parametrization of our test function:
.. code-block:: python
# content of conftest.py
def pytest_addoption(parser):
parser.addoption("--stringinput", action="append", default=[],
help="list of stringinputs to pass to test functions")
parser.addoption(
"--stringinput",
action="append",
default=[],
help="list of stringinputs to pass to test functions",
)
def pytest_generate_tests(metafunc):
if 'stringinput' in metafunc.fixturenames:
metafunc.parametrize("stringinput",
metafunc.config.getoption('stringinput'))
if "stringinput" in metafunc.fixturenames:
metafunc.parametrize("stringinput", metafunc.config.getoption("stringinput"))
If we now pass two stringinput values, our test will run twice:

View File

@ -84,32 +84,44 @@ It is also possible to skip the whole module using
If you wish to skip something conditionally then you can use ``skipif`` instead.
Here is an example of marking a test function to be skipped
when run on an interpreter earlier than Python3.6::
when run on an interpreter earlier than Python3.6:
.. code-block:: python
import sys
@pytest.mark.skipif(sys.version_info < (3,6),
reason="requires python3.6 or higher")
@pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher")
def test_function():
...
If the condition evaluates to ``True`` during collection, the test function will be skipped,
with the specified reason appearing in the summary when using ``-rs``.
You can share ``skipif`` markers between modules. Consider this test module::
You can share ``skipif`` markers between modules. Consider this test module:
.. code-block:: python
# content of test_mymodule.py
import mymodule
minversion = pytest.mark.skipif(mymodule.__versioninfo__ < (1,1),
reason="at least mymodule-1.1 required")
minversion = pytest.mark.skipif(
mymodule.__versioninfo__ < (1, 1), reason="at least mymodule-1.1 required"
)
@minversion
def test_function():
...
You can import the marker and reuse it in another test module::
You can import the marker and reuse it in another test module:
.. code-block:: python
# test_myothermodule.py
from test_mymodule import minversion
@minversion
def test_anotherfunction():
...
@ -128,12 +140,12 @@ so they are supported mainly for backward compatibility reasons.
Skip all test functions of a class or module
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can use the ``skipif`` marker (as any other marker) on classes::
You can use the ``skipif`` marker (as any other marker) on classes:
@pytest.mark.skipif(sys.platform == 'win32',
reason="does not run on windows")
.. code-block:: python
@pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows")
class TestPosixCalls(object):
def test_function(self):
"will not be setup or run under 'win32' platform"
@ -269,10 +281,11 @@ You can change the default value of the ``strict`` parameter using the
~~~~~~~~~~~~~~~~~~~~
As with skipif_ you can also mark your expectation of a failure
on a particular platform::
on a particular platform:
@pytest.mark.xfail(sys.version_info >= (3,6),
reason="python3.6 api changes")
.. code-block:: python
@pytest.mark.xfail(sys.version_info >= (3, 6), reason="python3.6 api changes")
def test_function():
...

View File

@ -6,15 +6,19 @@ Warnings Capture
.. versionadded:: 3.1
Starting from version ``3.1``, pytest now automatically catches warnings during test execution
and displays them at the end of the session::
and displays them at the end of the session:
.. code-block:: python
# content of test_show_warnings.py
import warnings
def api_v1():
warnings.warn(UserWarning("api v1, should use functions from v2"))
return 1
def test_one():
assert api_v1() == 1
@ -195,28 +199,36 @@ Ensuring code triggers a deprecation warning
You can also call a global helper for checking
that a certain function call triggers a ``DeprecationWarning`` or
``PendingDeprecationWarning``::
``PendingDeprecationWarning``:
.. code-block:: python
import pytest
def test_global():
pytest.deprecated_call(myfunction, 17)
By default, ``DeprecationWarning`` and ``PendingDeprecationWarning`` will not be
caught when using ``pytest.warns`` or ``recwarn`` because default Python warnings filters hide
them. If you wish to record them in your own code, use the
command ``warnings.simplefilter('always')``::
command ``warnings.simplefilter('always')``:
.. code-block:: python
import warnings
import pytest
def test_deprecation(recwarn):
warnings.simplefilter('always')
warnings.simplefilter("always")
warnings.warn("deprecated", DeprecationWarning)
assert len(recwarn) == 1
assert recwarn.pop(DeprecationWarning)
You can also use it as a contextmanager::
You can also use it as a contextmanager:
.. code-block:: python
def test_global():
with pytest.deprecated_call():
@ -238,11 +250,14 @@ Asserting warnings with the warns function
.. versionadded:: 2.8
You can check that code raises a particular warning using ``pytest.warns``,
which works in a similar manner to :ref:`raises <assertraises>`::
which works in a similar manner to :ref:`raises <assertraises>`:
.. code-block:: python
import warnings
import pytest
def test_warning():
with pytest.warns(UserWarning):
warnings.warn("my warning", UserWarning)
@ -269,7 +284,9 @@ You can also call ``pytest.warns`` on a function or code string::
The function also returns a list of all raised warnings (as
``warnings.WarningMessage`` objects), which you can query for
additional information::
additional information:
.. code-block:: python
with pytest.warns(RuntimeWarning) as record:
warnings.warn("another warning", RuntimeWarning)
@ -297,7 +314,9 @@ You can record raised warnings either using ``pytest.warns`` or with
the ``recwarn`` fixture.
To record with ``pytest.warns`` without asserting anything about the warnings,
pass ``None`` as the expected warning type::
pass ``None`` as the expected warning type:
.. code-block:: python
with pytest.warns(None) as record:
warnings.warn("user", UserWarning)
@ -307,10 +326,13 @@ pass ``None`` as the expected warning type::
assert str(record[0].message) == "user"
assert str(record[1].message) == "runtime"
The ``recwarn`` fixture will record warnings for the whole function::
The ``recwarn`` fixture will record warnings for the whole function:
.. code-block:: python
import warnings
def test_hello(recwarn):
warnings.warn("hello", UserWarning)
assert len(recwarn) == 1

View File

@ -528,10 +528,13 @@ a :py:class:`Result <pluggy._Result>` instance which encapsulates a result or
exception info. The yield point itself will thus typically not raise
exceptions (unless there are bugs).
Here is an example definition of a hook wrapper::
Here is an example definition of a hook wrapper:
.. code-block:: python
import pytest
@pytest.hookimpl(hookwrapper=True)
def pytest_pyfunc_call(pyfuncitem):
do_something_before_next_hook_executes()
@ -636,10 +639,13 @@ if you depend on a plugin that is not installed, validation will fail and
the error message will not make much sense to your users.
One approach is to defer the hook implementation to a new plugin instead of
declaring the hook functions directly in your plugin module, for example::
declaring the hook functions directly in your plugin module, for example:
.. code-block:: python
# contents of myplugin.py
class DeferPlugin(object):
"""Simple plugin to defer pytest-xdist hook functions."""
@ -647,8 +653,9 @@ declaring the hook functions directly in your plugin module, for example::
"""standard xdist hook function.
"""
def pytest_configure(config):
if config.pluginmanager.hasplugin('xdist'):
if config.pluginmanager.hasplugin("xdist"):
config.pluginmanager.register(DeferPlugin())
This has the added benefit of allowing you to conditionally install hooks

View File

@ -93,3 +93,9 @@ PYTEST_WARNS_UNKNOWN_KWARGS = UnformattedWarning(
"pytest.warns() got unexpected keyword arguments: {args!r}.\n"
"This will be an error in future versions.",
)
PYTEST_PARAM_UNKNOWN_KWARGS = UnformattedWarning(
PytestDeprecationWarning,
"pytest.param() got unexpected keyword arguments: {args!r}.\n"
"This will be an error in future versions.",
)

View File

@ -853,7 +853,9 @@ class FixtureDef(object):
exceptions.append(sys.exc_info())
if exceptions:
e = exceptions[0]
del exceptions # ensure we don't keep all frames alive because of the traceback
del (
exceptions
) # ensure we don't keep all frames alive because of the traceback
six.reraise(*e)
finally:

View File

@ -10,6 +10,7 @@ from ..compat import ascii_escaped
from ..compat import getfslineno
from ..compat import MappingMixin
from ..compat import NOTSET
from _pytest.deprecated import PYTEST_PARAM_UNKNOWN_KWARGS
from _pytest.outcomes import fail
from _pytest.warning_types import UnknownMarkWarning
@ -61,20 +62,25 @@ def get_empty_parameterset_mark(config, argnames, func):
class ParameterSet(namedtuple("ParameterSet", "values, marks, id")):
@classmethod
def param(cls, *values, **kw):
marks = kw.pop("marks", ())
def param(cls, *values, **kwargs):
marks = kwargs.pop("marks", ())
if isinstance(marks, MarkDecorator):
marks = (marks,)
else:
assert isinstance(marks, (tuple, list, set))
id_ = kw.pop("id", None)
id_ = kwargs.pop("id", None)
if id_ is not None:
if not isinstance(id_, six.string_types):
raise TypeError(
"Expected id to be a string, got {}: {!r}".format(type(id_), id_)
)
id_ = ascii_escaped(id_)
if kwargs:
warnings.warn(
PYTEST_PARAM_UNKNOWN_KWARGS.format(args=sorted(kwargs)), stacklevel=3
)
return cls(values, marks, id_)
@classmethod

View File

@ -271,6 +271,18 @@ class MonkeyPatch(object):
# https://github.com/pypa/setuptools/blob/d8b901bc/docs/pkg_resources.txt#L162-L171
fixup_namespace_packages(str(path))
# A call to syspathinsert() usually means that the caller wants to
# import some dynamically created files, thus with python3 we
# invalidate its import caches.
# This is especially important when any namespace package is in used,
# since then the mtime based FileFinder cache (that gets created in
# this case already) gets not invalidated when writing the new files
# quickly afterwards.
if sys.version_info >= (3, 3):
from importlib import invalidate_caches
invalidate_caches()
def chdir(self, path):
""" Change the current working directory to the specified path.
Path can be a string or a py.path.local object.

View File

@ -97,8 +97,7 @@ def skip(msg="", **kwargs):
__tracebackhide__ = True
allow_module_level = kwargs.pop("allow_module_level", False)
if kwargs:
keys = [k for k in kwargs.keys()]
raise TypeError("unexpected keyword arguments: {}".format(keys))
raise TypeError("unexpected keyword arguments: {}".format(sorted(kwargs)))
raise Skipped(msg=msg, allow_module_level=allow_module_level)

View File

@ -627,27 +627,10 @@ class Testdir(object):
This is undone automatically when this object dies at the end of each
test.
"""
from pkg_resources import fixup_namespace_packages
if path is None:
path = self.tmpdir
dirname = str(path)
sys.path.insert(0, dirname)
fixup_namespace_packages(dirname)
# a call to syspathinsert() usually means that the caller wants to
# import some dynamically created files, thus with python3 we
# invalidate its import caches
self._possibly_invalidate_import_caches()
def _possibly_invalidate_import_caches(self):
# invalidate caches if we can (py33 and above)
try:
from importlib import invalidate_caches
except ImportError:
return
invalidate_caches()
self.monkeypatch.syspath_prepend(str(path))
def mkdir(self, name):
"""Create a new (sub)directory."""
@ -1361,7 +1344,7 @@ class LineMatcher(object):
raise ValueError("line %r not found in output" % fnline)
def _log(self, *args):
self._log_output.append(" ".join((str(x) for x in args)))
self._log_output.append(" ".join(str(x) for x in args))
@property
def _log_text(self):

View File

@ -682,7 +682,7 @@ def raises(expected_exception, *args, **kwargs):
match_expr = kwargs.pop("match")
if kwargs:
msg = "Unexpected keyword arguments passed to pytest.raises: "
msg += ", ".join(kwargs.keys())
msg += ", ".join(sorted(kwargs))
raise TypeError(msg)
return RaisesContext(expected_exception, message, match_expr)
elif isinstance(args[0], str):

View File

@ -1071,9 +1071,7 @@ class TestFixtureUsages(object):
)
result = testdir.runpytest_inprocess()
result.stdout.fnmatch_lines(
(
"*Fixture 'badscope' from test_invalid_scope.py got an unexpected scope value 'functions'"
)
"*Fixture 'badscope' from test_invalid_scope.py got an unexpected scope value 'functions'"
)
def test_funcarg_parametrized_and_used_twice(self, testdir):

View File

@ -13,6 +13,7 @@ from _pytest.mark import EMPTY_PARAMETERSET_OPTION
from _pytest.mark import MarkGenerator as Mark
from _pytest.nodes import Collector
from _pytest.nodes import Node
from _pytest.warning_types import PytestDeprecationWarning
from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG
try:
@ -991,3 +992,15 @@ def test_pytest_param_id_requires_string():
@pytest.mark.parametrize("s", (None, "hello world"))
def test_pytest_param_id_allows_none_or_string(s):
assert pytest.param(id=s)
def test_pytest_param_warning_on_unknown_kwargs():
with pytest.warns(PytestDeprecationWarning) as warninfo:
# typo, should be marks=
pytest.param(1, 2, mark=pytest.mark.xfail())
assert warninfo[0].filename == __file__
msg, = warninfo[0].message.args
assert msg == (
"pytest.param() got unexpected keyword arguments: ['mark'].\n"
"This will be an error in future versions."
)

View File

@ -462,3 +462,10 @@ def test_syspath_prepend_with_namespace_packages(testdir, monkeypatch):
import ns_pkg.world
assert ns_pkg.world.check() == "world"
# Should invalidate caches via importlib.invalidate_caches.
tmpdir = testdir.tmpdir
modules_tmpdir = tmpdir.mkdir("modules_tmpdir")
monkeypatch.syspath_prepend(str(modules_tmpdir))
modules_tmpdir.join("main_app.py").write("app = True")
from main_app import app # noqa: F401