Merge remote-tracking branch 'upstream/master' into merge-master-into-features

This commit is contained in:
Bruno Oliveira 2018-07-15 11:54:14 -03:00
commit ac9ceaacd8
37 changed files with 521 additions and 309 deletions

149
.github/labels.toml vendored Normal file
View File

@ -0,0 +1,149 @@
["os: cygwin"]
color = "006b75"
description = "cygwin platform-specific problem"
name = "os: cygwin"
["os: linux"]
color = "1d76db"
description = "linux platform-specific problem"
name = "os: linux"
["os: mac"]
color = "bfdadc"
description = "mac platform-specific problem"
name = "os: mac"
["os: windows"]
color = "fbca04"
description = "windows platform-specific problem"
name = "os: windows"
["plugin: argcomplete"]
color = "d4c5f9"
description = "related to the argcomplete builtin plugin"
name = "plugin: argcomplete"
["plugin: cache"]
color = "c7def8"
description = "related to the cache builtin plugin"
name = "plugin: cache"
["plugin: capture"]
color = "1d76db"
description = "related to the capture builtin plugin"
name = "plugin: capture"
["plugin: debugging"]
color = "dd52a8"
description = "related to the debugging builtin plugin"
name = "plugin: debugging"
["plugin: doctests"]
color = "fad8c7"
description = "related to the doctests builtin plugin"
name = "plugin: doctests"
["plugin: junitxml"]
color = "c5def5"
description = "related to the junitxml builtin plugin"
name = "plugin: junitxml"
["plugin: logging"]
color = "ff5432"
description = "related to the logging builtin plugin"
name = "plugin: logging"
["plugin: monkeypatch"]
color = "0e8a16"
description = "related to the monkeypatch builtin plugin"
name = "plugin: monkeypatch"
["plugin: nose"]
color = "bfdadc"
description = "related to the nose integration builtin plugin"
name = "plugin: nose"
["plugin: pastebin"]
color = "bfd4f2"
description = "related to the pastebin builtin plugin"
name = "plugin: pastebin"
["plugin: pytester"]
color = "c5def5"
description = "related to the pytester builtin plugin"
name = "plugin: pytester"
["plugin: tmpdir"]
color = "bfd4f2"
description = "related to the tmpdir builtin plugin"
name = "plugin: tmpdir"
["plugin: unittest"]
color = "006b75"
description = "related to the unittest integration builtin plugin"
name = "plugin: unittest"
["plugin: warnings"]
color = "fef2c0"
description = "related to the warnings builtin plugin"
name = "plugin: warnings"
["plugin: xdist"]
color = "5319e7"
description = "related to the xdist external plugin"
name = "plugin: xdist"
["status: critical"]
color = "e11d21"
description = "grave problem or usability issue that affects lots of users"
name = "status: critical"
["status: easy"]
color = "bfe5bf"
description = "easy issue that is friendly to new contributor"
name = "status: easy"
["status: help wanted"]
color = "159818"
description = "developers would like help from experts on this topic"
name = "status: help wanted"
["status: needs information"]
color = "5319e7"
description = "reporter needs to provide more information; can be closed after 2 or more weeks of inactivity"
name = "status: needs information"
["topic: collection"]
color = "006b75"
description = "related to the collection phase"
name = "topic: collection"
["topic: config"]
color = "006b75"
description = "related to config handling, argument parsing and config file"
name = "topic: config"
["topic: fixtures"]
color = "5319e7"
description = "anything involving fixtures directly or indirectly"
name = "topic: fixtures"
["topic: marks"]
color = "b60205"
description = "related to marks, either the general marks or builtin"
name = "topic: marks"
["topic: parametrize"]
color = "fbca04"
description = "related to @pytest.mark.parametrize"
name = "topic: parametrize"
["topic: reporting"]
color = "fef2c0"
description = "related to terminal output and user-facing messages and errors"
name = "topic: reporting"
["topic: rewrite"]
color = "0e8a16"
description = "related to the assertion rewrite mechanism"
name = "topic: rewrite"

View File

@ -34,3 +34,8 @@ repos:
language: python language: python
additional_dependencies: [pygments, restructuredtext_lint] additional_dependencies: [pygments, restructuredtext_lint]
python_version: python3.6 python_version: python3.6
- id: rst-backticks
name: rst ``code`` is two backticks
entry: ' `[^`]+[^_]`([^_]|$)'
language: pygrep
types: [rst]

View File

@ -3,7 +3,8 @@ language: python
stages: stages:
- linting - linting
- test - test
- deploy - name: deploy
if: repo = pytest-dev/pytest AND tag IS present
python: python:
- '3.6' - '3.6'
install: install:

View File

@ -72,7 +72,7 @@ Bug Fixes
raises an exception. (`#3569 raises an exception. (`#3569
<https://github.com/pytest-dev/pytest/issues/3569>`_) <https://github.com/pytest-dev/pytest/issues/3569>`_)
- Fix encoding error with `print` statements in doctests (`#3583 - Fix encoding error with ``print`` statements in doctests (`#3583
<https://github.com/pytest-dev/pytest/issues/3583>`_) <https://github.com/pytest-dev/pytest/issues/3583>`_)
@ -345,7 +345,7 @@ Features
``pytest_runtest_logfinish`` hooks when live logs are enabled. (`#3189 ``pytest_runtest_logfinish`` hooks when live logs are enabled. (`#3189
<https://github.com/pytest-dev/pytest/issues/3189>`_) <https://github.com/pytest-dev/pytest/issues/3189>`_)
- Passing `--log-cli-level` in the command-line now automatically activates - Passing ``--log-cli-level`` in the command-line now automatically activates
live logging. (`#3190 <https://github.com/pytest-dev/pytest/issues/3190>`_) live logging. (`#3190 <https://github.com/pytest-dev/pytest/issues/3190>`_)
- Add command line option ``--deselect`` to allow deselection of individual - Add command line option ``--deselect`` to allow deselection of individual
@ -697,8 +697,8 @@ Trivial/Internal Changes
- Code cleanup. (`#3015 <https://github.com/pytest-dev/pytest/issues/3015>`_, - Code cleanup. (`#3015 <https://github.com/pytest-dev/pytest/issues/3015>`_,
`#3021 <https://github.com/pytest-dev/pytest/issues/3021>`_) `#3021 <https://github.com/pytest-dev/pytest/issues/3021>`_)
- Clean up code by replacing imports and references of `_ast` to `ast`. (`#3018 - Clean up code by replacing imports and references of ``_ast`` to ``ast``.
<https://github.com/pytest-dev/pytest/issues/3018>`_) (`#3018 <https://github.com/pytest-dev/pytest/issues/3018>`_)
Pytest 3.3.1 (2017-12-05) Pytest 3.3.1 (2017-12-05)
@ -1026,7 +1026,7 @@ Pytest 3.2.2 (2017-09-06)
Bug Fixes Bug Fixes
--------- ---------
- Calling the deprecated `request.getfuncargvalue()` now shows the source of - Calling the deprecated ``request.getfuncargvalue()`` now shows the source of
the call. (`#2681 <https://github.com/pytest-dev/pytest/issues/2681>`_) the call. (`#2681 <https://github.com/pytest-dev/pytest/issues/2681>`_)
- Allow tests declared as ``@staticmethod`` to use fixtures. (`#2699 - Allow tests declared as ``@staticmethod`` to use fixtures. (`#2699
@ -1048,10 +1048,10 @@ Improved Documentation
``pytest.mark.MARKER_NAME.__call__`` (`#2604 ``pytest.mark.MARKER_NAME.__call__`` (`#2604
<https://github.com/pytest-dev/pytest/issues/2604>`_) <https://github.com/pytest-dev/pytest/issues/2604>`_)
- In one of the simple examples, use `pytest_collection_modifyitems()` to skip - In one of the simple examples, use ``pytest_collection_modifyitems()`` to skip
tests based on a command-line option, allowing its sharing while preventing a tests based on a command-line option, allowing its sharing while preventing a
user error when acessing `pytest.config` before the argument parsing. (`#2653 user error when acessing ``pytest.config`` before the argument parsing.
<https://github.com/pytest-dev/pytest/issues/2653>`_) (`#2653 <https://github.com/pytest-dev/pytest/issues/2653>`_)
Trivial/Internal Changes Trivial/Internal Changes
@ -1129,7 +1129,7 @@ Features
from parent classes or modules. (`#2516 <https://github.com/pytest- from parent classes or modules. (`#2516 <https://github.com/pytest-
dev/pytest/issues/2516>`_) dev/pytest/issues/2516>`_)
- Collection ignores local virtualenvs by default; `--collect-in-virtualenv` - Collection ignores local virtualenvs by default; ``--collect-in-virtualenv``
overrides this behavior. (`#2518 <https://github.com/pytest- overrides this behavior. (`#2518 <https://github.com/pytest-
dev/pytest/issues/2518>`_) dev/pytest/issues/2518>`_)

16
CITATION Normal file
View File

@ -0,0 +1,16 @@
NOTE: Change "x.y" by the version you use. If you are unsure about which version
you are using run: `pip show pytest`.
Text:
[pytest] pytest x.y, 2004
Krekel et al., https://github.com/pytest-dev/pytest
BibTeX:
@misc{pytestx.y,
title = {pytest x.y},
author = {Krekel, Holger and Oliveira, Bruno and Pfannschmidt, Ronny and Bruynooghe, Floris and Laugher, Brianna and Bruhin, Florian},
year = {2004},
url = {https://github.com/pytest-dev/pytest},
}

View File

@ -18,19 +18,11 @@ taking a lot of time to make a new one.
Ensure your are in a clean work tree. Ensure your are in a clean work tree.
#. Install development dependencies in a virtual environment with:: #. Using ``tox``, generate docs, changelog, announcements::
$ pip3 install -U -r tasks/requirements.txt $ tox -e release -- <VERSION>
#. Generate docs, changelog, announcements, and a **local** tag::
$ invoke generate.pre-release <VERSION>
#. Execute pre-commit on all files to ensure the docs are conformant and commit your results::
$ pre-commit run --all-files
$ git commit -am "Fix files with pre-commit"
This will generate a commit with all the changes ready for pushing.
#. Open a PR for this branch targeting ``master``. #. Open a PR for this branch targeting ``master``.

View File

@ -3,6 +3,7 @@
:align: center :align: center
:alt: pytest :alt: pytest
------ ------
.. image:: https://img.shields.io/pypi/v/pytest.svg .. image:: https://img.shields.io/pypi/v/pytest.svg
@ -109,7 +110,7 @@ Consult the `Changelog <http://docs.pytest.org/en/latest/changelog.html>`__ page
License License
------- -------
Copyright Holger Krekel and others, 2004-2017. Copyright Holger Krekel and others, 2004-2018.
Distributed under the terms of the `MIT`_ license, pytest is free and open source software. Distributed under the terms of the `MIT`_ license, pytest is free and open source software.

View File

@ -0,0 +1 @@
Remove obsolete ``__future__`` imports.

View File

@ -0,0 +1 @@
Add CITATION to provide information on how to formally cite pytest.

1
changelog/3592.doc.rst Normal file
View File

@ -0,0 +1 @@
Use ``smtp_connection`` instead of ``smtp`` in fixtures documentation to avoid possible confusion.

View File

@ -0,0 +1 @@
Replace broken type annotations with type comments.

1
changelog/742.bugfix.rst Normal file
View File

@ -0,0 +1 @@
Invoke pytest using ``-mpytest`` so ``sys.path`` does not get polluted by packages installed in ``site-packages``.

View File

@ -124,7 +124,7 @@ The py.test Development Team
Thanks `@biern`_ for the PR. Thanks `@biern`_ for the PR.
* Fix `traceback style docs`_ to describe all of the available options * Fix `traceback style docs`_ to describe all of the available options
(auto/long/short/line/native/no), with `auto` being the default since v2.6. (auto/long/short/line/native/no), with ``auto`` being the default since v2.6.
Thanks `@hackebrot`_ for the PR. Thanks `@hackebrot`_ for the PR.
* Fix (`#1422`_): junit record_xml_property doesn't allow multiple records * Fix (`#1422`_): junit record_xml_property doesn't allow multiple records

View File

@ -39,6 +39,11 @@ avoid creating labels just for the sake of creating them.
Each label should include a description in the GitHub's interface stating its purpose. Each label should include a description in the GitHub's interface stating its purpose.
Labels are managed using `labels <https://github.com/hackebrot/labels>`_. All the labels in the repository
are kept in ``.github/labels.toml``, so any changes should be via PRs to that file.
After a PR is accepted and merged, one of the maintainers must manually synchronize the labels file with the
GitHub repository.
Temporary labels Temporary labels
~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~

View File

@ -299,10 +299,10 @@ Skip and xfail marks can also be applied in this way, see :ref:`skip/xfail with
.. note:: .. note::
If the data you are parametrizing happen to be single callables, you need to be careful If the data you are parametrizing happen to be single callables, you need to be careful
when marking these items. `pytest.mark.xfail(my_func)` won't work because it's also the when marking these items. ``pytest.mark.xfail(my_func)`` won't work because it's also the
signature of a function being decorated. To resolve this ambiguity, you need to pass a signature of a function being decorated. To resolve this ambiguity, you need to pass a
reason argument: reason argument:
`pytest.mark.xfail(func_bar, reason="Issue#7")`. ``pytest.mark.xfail(func_bar, reason="Issue#7")``.
.. _`adding a custom marker from a plugin`: .. _`adding a custom marker from a plugin`:

View File

@ -55,18 +55,18 @@ using it::
import pytest import pytest
@pytest.fixture @pytest.fixture
def smtp(): def smtp_connection():
import smtplib import smtplib
return smtplib.SMTP("smtp.gmail.com", 587, timeout=5) return smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
def test_ehlo(smtp): def test_ehlo(smtp_connection):
response, msg = smtp.ehlo() response, msg = smtp_connection.ehlo()
assert response == 250 assert response == 250
assert 0 # for demo purposes assert 0 # for demo purposes
Here, the ``test_ehlo`` needs the ``smtp`` fixture value. pytest Here, the ``test_ehlo`` needs the ``smtp_connection`` fixture value. pytest
will discover and call the :py:func:`@pytest.fixture <_pytest.python.fixture>` will discover and call the :py:func:`@pytest.fixture <_pytest.python.fixture>`
marked ``smtp`` fixture function. Running the test looks like this:: marked ``smtp_connection`` fixture function. Running the test looks like this::
$ pytest test_smtpsimple.py $ pytest test_smtpsimple.py
=========================== test session starts ============================ =========================== test session starts ============================
@ -79,10 +79,10 @@ marked ``smtp`` fixture function. Running the test looks like this::
================================= FAILURES ================================= ================================= FAILURES =================================
________________________________ test_ehlo _________________________________ ________________________________ test_ehlo _________________________________
smtp = <smtplib.SMTP object at 0xdeadbeef> smtp_connection = <smtplib.SMTP object at 0xdeadbeef>
def test_ehlo(smtp): def test_ehlo(smtp_connection):
response, msg = smtp.ehlo() response, msg = smtp_connection.ehlo()
assert response == 250 assert response == 250
> assert 0 # for demo purposes > assert 0 # for demo purposes
E assert 0 E assert 0
@ -91,18 +91,18 @@ marked ``smtp`` fixture function. Running the test looks like this::
========================= 1 failed in 0.12 seconds ========================= ========================= 1 failed in 0.12 seconds =========================
In the failure traceback we see that the test function was called with a In the failure traceback we see that the test function was called with a
``smtp`` argument, the ``smtplib.SMTP()`` instance created by the fixture ``smtp_connection`` argument, the ``smtplib.SMTP()`` instance created by the fixture
function. The test function fails on our deliberate ``assert 0``. Here is function. The test function fails on our deliberate ``assert 0``. Here is
the exact protocol used by ``pytest`` to call the test function this way: the exact protocol used by ``pytest`` to call the test function this way:
1. pytest :ref:`finds <test discovery>` the ``test_ehlo`` because 1. pytest :ref:`finds <test discovery>` the ``test_ehlo`` because
of the ``test_`` prefix. The test function needs a function argument of the ``test_`` prefix. The test function needs a function argument
named ``smtp``. A matching fixture function is discovered by named ``smtp_connection``. A matching fixture function is discovered by
looking for a fixture-marked function named ``smtp``. looking for a fixture-marked function named ``smtp_connection``.
2. ``smtp()`` is called to create an instance. 2. ``smtp_connection()`` is called to create an instance.
3. ``test_ehlo(<SMTP instance>)`` is called and fails in the last 3. ``test_ehlo(<smtp_connection instance>)`` is called and fails in the last
line of the test function. line of the test function.
Note that if you misspell a function argument or want Note that if you misspell a function argument or want
@ -167,10 +167,10 @@ Fixtures requiring network access depend on connectivity and are
usually time-expensive to create. Extending the previous example, we usually time-expensive to create. Extending the previous example, we
can add a ``scope="module"`` parameter to the can add a ``scope="module"`` parameter to the
:py:func:`@pytest.fixture <_pytest.python.fixture>` invocation :py:func:`@pytest.fixture <_pytest.python.fixture>` invocation
to cause the decorated ``smtp`` fixture function to only be invoked once to cause the decorated ``smtp_connection`` fixture function to only be invoked
per test *module* (the default is to invoke once per test *function*). once per test *module* (the default is to invoke once per test *function*).
Multiple test functions in a test module will thus Multiple test functions in a test module will thus
each receive the same ``smtp`` fixture instance, thus saving time. each receive the same ``smtp_connection`` fixture instance, thus saving time.
The next example puts the fixture function into a separate ``conftest.py`` file The next example puts the fixture function into a separate ``conftest.py`` file
so that tests from multiple test modules in the directory can so that tests from multiple test modules in the directory can
@ -181,23 +181,24 @@ access the fixture function::
import smtplib import smtplib
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def smtp(): def smtp_connection():
return smtplib.SMTP("smtp.gmail.com", 587, timeout=5) return smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
The name of the fixture again is ``smtp`` and you can access its result by The name of the fixture again is ``smtp_connection`` and you can access its
listing the name ``smtp`` as an input parameter in any test or fixture result by listing the name ``smtp_connection`` as an input parameter in any
function (in or below the directory where ``conftest.py`` is located):: test or fixture function (in or below the directory where ``conftest.py`` is
located)::
# content of test_module.py # content of test_module.py
def test_ehlo(smtp): def test_ehlo(smtp_connection):
response, msg = smtp.ehlo() response, msg = smtp_connection.ehlo()
assert response == 250 assert response == 250
assert b"smtp.gmail.com" in msg assert b"smtp.gmail.com" in msg
assert 0 # for demo purposes assert 0 # for demo purposes
def test_noop(smtp): def test_noop(smtp_connection):
response, msg = smtp.noop() response, msg = smtp_connection.noop()
assert response == 250 assert response == 250
assert 0 # for demo purposes assert 0 # for demo purposes
@ -215,10 +216,10 @@ inspect what is going on and can now run the tests::
================================= FAILURES ================================= ================================= FAILURES =================================
________________________________ test_ehlo _________________________________ ________________________________ test_ehlo _________________________________
smtp = <smtplib.SMTP object at 0xdeadbeef> smtp_connection = <smtplib.SMTP object at 0xdeadbeef>
def test_ehlo(smtp): def test_ehlo(smtp_connection):
response, msg = smtp.ehlo() response, msg = smtp_connection.ehlo()
assert response == 250 assert response == 250
assert b"smtp.gmail.com" in msg assert b"smtp.gmail.com" in msg
> assert 0 # for demo purposes > assert 0 # for demo purposes
@ -227,10 +228,10 @@ inspect what is going on and can now run the tests::
test_module.py:6: AssertionError test_module.py:6: AssertionError
________________________________ test_noop _________________________________ ________________________________ test_noop _________________________________
smtp = <smtplib.SMTP object at 0xdeadbeef> smtp_connection = <smtplib.SMTP object at 0xdeadbeef>
def test_noop(smtp): def test_noop(smtp_connection):
response, msg = smtp.noop() response, msg = smtp_connection.noop()
assert response == 250 assert response == 250
> assert 0 # for demo purposes > assert 0 # for demo purposes
E assert 0 E assert 0
@ -239,18 +240,18 @@ inspect what is going on and can now run the tests::
========================= 2 failed in 0.12 seconds ========================= ========================= 2 failed in 0.12 seconds =========================
You see the two ``assert 0`` failing and more importantly you can also see You see the two ``assert 0`` failing and more importantly you can also see
that the same (module-scoped) ``smtp`` object was passed into the two that the same (module-scoped) ``smtp_connection`` object was passed into the
test functions because pytest shows the incoming argument values in the two test functions because pytest shows the incoming argument values in the
traceback. As a result, the two test functions using ``smtp`` run as traceback. As a result, the two test functions using ``smtp_connection`` run
quick as a single one because they reuse the same instance. as quick as a single one because they reuse the same instance.
If you decide that you rather want to have a session-scoped ``smtp`` If you decide that you rather want to have a session-scoped ``smtp_connection``
instance, you can simply declare it: instance, you can simply declare it:
.. code-block:: python .. code-block:: python
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
def smtp(): def smtp_connection():
# the returned fixture value will be shared for # the returned fixture value will be shared for
# all tests needing it # all tests needing it
... ...
@ -339,11 +340,11 @@ the code after the *yield* statement serves as the teardown code:
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def smtp(): def smtp_connection():
smtp = smtplib.SMTP("smtp.gmail.com", 587, timeout=5) smtp_connection = smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
yield smtp # provide the fixture value yield smtp_connection # provide the fixture value
print("teardown smtp") print("teardown smtp")
smtp.close() smtp_connection.close()
The ``print`` and ``smtp.close()`` statements will execute when the last test in The ``print`` and ``smtp.close()`` statements will execute when the last test in
the module has finished execution, regardless of the exception status of the the module has finished execution, regardless of the exception status of the
@ -356,7 +357,7 @@ Let's execute it::
2 failed in 0.12 seconds 2 failed in 0.12 seconds
We see that the ``smtp`` instance is finalized after the two We see that the ``smtp_connection`` instance is finalized after the two
tests finished execution. Note that if we decorated our fixture tests finished execution. Note that if we decorated our fixture
function with ``scope='function'`` then fixture setup and cleanup would function with ``scope='function'`` then fixture setup and cleanup would
occur around each single test. In either case the test occur around each single test. In either case the test
@ -374,13 +375,13 @@ Note that we can also seamlessly use the ``yield`` syntax with ``with`` statemen
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def smtp(): def smtp_connection():
with smtplib.SMTP("smtp.gmail.com", 587, timeout=5) as smtp: with smtplib.SMTP("smtp.gmail.com", 587, timeout=5) as smtp_connection:
yield smtp # provide the fixture value yield smtp_connection # provide the fixture value
The ``smtp`` connection will be closed after the test finished execution The ``smtp_connection`` connection will be closed after the test finished
because the ``smtp`` object automatically closes when execution because the ``smtp_connection`` object automatically closes when
the ``with`` statement ends. the ``with`` statement ends.
Note that if an exception happens during the *setup* code (before the ``yield`` keyword), the Note that if an exception happens during the *setup* code (before the ``yield`` keyword), the
@ -390,7 +391,7 @@ An alternative option for executing *teardown* code is to
make use of the ``addfinalizer`` method of the `request-context`_ object to register make use of the ``addfinalizer`` method of the `request-context`_ object to register
finalization functions. finalization functions.
Here's the ``smtp`` fixture changed to use ``addfinalizer`` for cleanup: Here's the ``smtp_connection`` fixture changed to use ``addfinalizer`` for cleanup:
.. code-block:: python .. code-block:: python
@ -400,15 +401,15 @@ Here's the ``smtp`` fixture changed to use ``addfinalizer`` for cleanup:
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def smtp(request): def smtp_connection(request):
smtp = smtplib.SMTP("smtp.gmail.com", 587, timeout=5) smtp_connection = smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
def fin(): def fin():
print("teardown smtp") print("teardown smtp_connection")
smtp.close() smtp_connection.close()
request.addfinalizer(fin) request.addfinalizer(fin)
return smtp # provide the fixture value return smtp_connection # provide the fixture value
Both ``yield`` and ``addfinalizer`` methods work similarly by calling their code after the test Both ``yield`` and ``addfinalizer`` methods work similarly by calling their code after the test
@ -441,7 +442,7 @@ Fixtures can introspect the requesting test context
Fixture functions can accept the :py:class:`request <FixtureRequest>` object Fixture functions can accept the :py:class:`request <FixtureRequest>` object
to introspect the "requesting" test function, class or module context. to introspect the "requesting" test function, class or module context.
Further extending the previous ``smtp`` fixture example, let's Further extending the previous ``smtp_connection`` fixture example, let's
read an optional server URL from the test module which uses our fixture:: read an optional server URL from the test module which uses our fixture::
# content of conftest.py # content of conftest.py
@ -449,12 +450,12 @@ read an optional server URL from the test module which uses our fixture::
import smtplib import smtplib
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def smtp(request): def smtp_connection(request):
server = getattr(request.module, "smtpserver", "smtp.gmail.com") server = getattr(request.module, "smtpserver", "smtp.gmail.com")
smtp = smtplib.SMTP(server, 587, timeout=5) smtp_connection = smtplib.SMTP(server, 587, timeout=5)
yield smtp yield smtp_connection
print ("finalizing %s (%s)" % (smtp, server)) print ("finalizing %s (%s)" % (smtp_connection, server))
smtp.close() smtp_connection.close()
We use the ``request.module`` attribute to optionally obtain an We use the ``request.module`` attribute to optionally obtain an
``smtpserver`` attribute from the test module. If we just execute ``smtpserver`` attribute from the test module. If we just execute
@ -472,8 +473,8 @@ server URL in its module namespace::
smtpserver = "mail.python.org" # will be read by smtp fixture smtpserver = "mail.python.org" # will be read by smtp fixture
def test_showhelo(smtp): def test_showhelo(smtp_connection):
assert 0, smtp.helo() assert 0, smtp_connection.helo()
Running it:: Running it::
@ -482,13 +483,13 @@ Running it::
================================= FAILURES ================================= ================================= FAILURES =================================
______________________________ test_showhelo _______________________________ ______________________________ test_showhelo _______________________________
test_anothersmtp.py:5: in test_showhelo test_anothersmtp.py:5: in test_showhelo
assert 0, smtp.helo() assert 0, smtp_connection.helo()
E AssertionError: (250, b'mail.python.org') E AssertionError: (250, b'mail.python.org')
E assert 0 E assert 0
------------------------- Captured stdout teardown ------------------------- ------------------------- Captured stdout teardown -------------------------
finalizing <smtplib.SMTP object at 0xdeadbeef> (mail.python.org) finalizing <smtplib.SMTP object at 0xdeadbeef> (mail.python.org)
voila! The ``smtp`` fixture function picked up our mail server name voila! The ``smtp_connection`` fixture function picked up our mail server name
from the module namespace. from the module namespace.
.. _`fixture-factory`: .. _`fixture-factory`:
@ -557,7 +558,7 @@ write exhaustive functional tests for components which themselves can be
configured in multiple ways. configured in multiple ways.
Extending the previous example, we can flag the fixture to create two Extending the previous example, we can flag the fixture to create two
``smtp`` fixture instances which will cause all tests using the fixture ``smtp_connection`` fixture instances which will cause all tests using the fixture
to run twice. The fixture function gets access to each parameter to run twice. The fixture function gets access to each parameter
through the special :py:class:`request <FixtureRequest>` object:: through the special :py:class:`request <FixtureRequest>` object::
@ -567,11 +568,11 @@ through the special :py:class:`request <FixtureRequest>` object::
@pytest.fixture(scope="module", @pytest.fixture(scope="module",
params=["smtp.gmail.com", "mail.python.org"]) params=["smtp.gmail.com", "mail.python.org"])
def smtp(request): def smtp_connection(request):
smtp = smtplib.SMTP(request.param, 587, timeout=5) smtp_connection = smtplib.SMTP(request.param, 587, timeout=5)
yield smtp yield smtp_connection
print ("finalizing %s" % smtp) print("finalizing %s" % smtp_connection)
smtp.close() smtp_connection.close()
The main change is the declaration of ``params`` with The main change is the declaration of ``params`` with
:py:func:`@pytest.fixture <_pytest.python.fixture>`, a list of values :py:func:`@pytest.fixture <_pytest.python.fixture>`, a list of values
@ -584,10 +585,10 @@ So let's just do another run::
================================= FAILURES ================================= ================================= FAILURES =================================
________________________ test_ehlo[smtp.gmail.com] _________________________ ________________________ test_ehlo[smtp.gmail.com] _________________________
smtp = <smtplib.SMTP object at 0xdeadbeef> smtp_connection = <smtplib.SMTP object at 0xdeadbeef>
def test_ehlo(smtp): def test_ehlo(smtp_connection):
response, msg = smtp.ehlo() response, msg = smtp_connection.ehlo()
assert response == 250 assert response == 250
assert b"smtp.gmail.com" in msg assert b"smtp.gmail.com" in msg
> assert 0 # for demo purposes > assert 0 # for demo purposes
@ -596,10 +597,10 @@ So let's just do another run::
test_module.py:6: AssertionError test_module.py:6: AssertionError
________________________ test_noop[smtp.gmail.com] _________________________ ________________________ test_noop[smtp.gmail.com] _________________________
smtp = <smtplib.SMTP object at 0xdeadbeef> smtp_connection = <smtplib.SMTP object at 0xdeadbeef>
def test_noop(smtp): def test_noop(smtp_connection):
response, msg = smtp.noop() response, msg = smtp_connection.noop()
assert response == 250 assert response == 250
> assert 0 # for demo purposes > assert 0 # for demo purposes
E assert 0 E assert 0
@ -607,10 +608,10 @@ So let's just do another run::
test_module.py:11: AssertionError test_module.py:11: AssertionError
________________________ test_ehlo[mail.python.org] ________________________ ________________________ test_ehlo[mail.python.org] ________________________
smtp = <smtplib.SMTP object at 0xdeadbeef> smtp_connection = <smtplib.SMTP object at 0xdeadbeef>
def test_ehlo(smtp): def test_ehlo(smtp_connection):
response, msg = smtp.ehlo() response, msg = smtp_connection.ehlo()
assert response == 250 assert response == 250
> assert b"smtp.gmail.com" in msg > assert b"smtp.gmail.com" in msg
E AssertionError: assert b'smtp.gmail.com' in b'mail.python.org\nPIPELINING\nSIZE 51200000\nETRN\nSTARTTLS\nAUTH DIGEST-MD5 NTLM CRAM-MD5\nENHANCEDSTATUSCODES\n8BITMIME\nDSN\nSMTPUTF8' E AssertionError: assert b'smtp.gmail.com' in b'mail.python.org\nPIPELINING\nSIZE 51200000\nETRN\nSTARTTLS\nAUTH DIGEST-MD5 NTLM CRAM-MD5\nENHANCEDSTATUSCODES\n8BITMIME\nDSN\nSMTPUTF8'
@ -620,10 +621,10 @@ So let's just do another run::
finalizing <smtplib.SMTP object at 0xdeadbeef> finalizing <smtplib.SMTP object at 0xdeadbeef>
________________________ test_noop[mail.python.org] ________________________ ________________________ test_noop[mail.python.org] ________________________
smtp = <smtplib.SMTP object at 0xdeadbeef> smtp_connection = <smtplib.SMTP object at 0xdeadbeef>
def test_noop(smtp): def test_noop(smtp_connection):
response, msg = smtp.noop() response, msg = smtp_connection.noop()
assert response == 250 assert response == 250
> assert 0 # for demo purposes > assert 0 # for demo purposes
E assert 0 E assert 0
@ -634,7 +635,7 @@ So let's just do another run::
4 failed in 0.12 seconds 4 failed in 0.12 seconds
We see that our two test functions each ran twice, against the different We see that our two test functions each ran twice, against the different
``smtp`` instances. Note also, that with the ``mail.python.org`` ``smtp_connection`` instances. Note also, that with the ``mail.python.org``
connection the second test fails in ``test_ehlo`` because a connection the second test fails in ``test_ehlo`` because a
different server string is expected than what arrived. different server string is expected than what arrived.
@ -746,25 +747,25 @@ can use other fixtures themselves. This contributes to a modular design
of your fixtures and allows re-use of framework-specific fixtures across of your fixtures and allows re-use of framework-specific fixtures across
many projects. As a simple example, we can extend the previous example many projects. As a simple example, we can extend the previous example
and instantiate an object ``app`` where we stick the already defined and instantiate an object ``app`` where we stick the already defined
``smtp`` resource into it:: ``smtp_connection`` resource into it::
# content of test_appsetup.py # content of test_appsetup.py
import pytest import pytest
class App(object): class App(object):
def __init__(self, smtp): def __init__(self, smtp_connection):
self.smtp = smtp self.smtp_connection = smtp_connection
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def app(smtp): def app(smtp_connection):
return App(smtp) return App(smtp_connection)
def test_smtp_exists(app): def test_smtp_connection_exists(app):
assert app.smtp assert app.smtp_connection
Here we declare an ``app`` fixture which receives the previously defined Here we declare an ``app`` fixture which receives the previously defined
``smtp`` fixture and instantiates an ``App`` object with it. Let's run it:: ``smtp_connection`` fixture and instantiates an ``App`` object with it. Let's run it::
$ pytest -v test_appsetup.py $ pytest -v test_appsetup.py
=========================== test session starts ============================ =========================== test session starts ============================
@ -773,19 +774,19 @@ Here we declare an ``app`` fixture which receives the previously defined
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 2 items collecting ... collected 2 items
test_appsetup.py::test_smtp_exists[smtp.gmail.com] PASSED [ 50%] test_appsetup.py::test_smtp_connection_exists[smtp.gmail.com] PASSED [ 50%]
test_appsetup.py::test_smtp_exists[mail.python.org] PASSED [100%] test_appsetup.py::test_smtp_connection_exists[mail.python.org] PASSED [100%]
========================= 2 passed in 0.12 seconds ========================= ========================= 2 passed in 0.12 seconds =========================
Due to the parametrization of ``smtp`` the test will run twice with two Due to the parametrization of ``smtp_connection`` the test will run twice with two
different ``App`` instances and respective smtp servers. There is no different ``App`` instances and respective smtp servers. There is no
need for the ``app`` fixture to be aware of the ``smtp`` parametrization need for the ``app`` fixture to be aware of the ``smtp_connection``
as pytest will fully analyse the fixture dependency graph. parametrization as pytest will fully analyse the fixture dependency graph.
Note, that the ``app`` fixture has a scope of ``module`` and uses a Note, that the ``app`` fixture has a scope of ``module`` and uses a
module-scoped ``smtp`` fixture. The example would still work if ``smtp`` module-scoped ``smtp_connection`` fixture. The example would still work if
was cached on a ``session`` scope: it is fine for fixtures to use ``smtp_connection`` was cached on a ``session`` scope: it is fine for fixtures to use
"broader" scoped fixtures but not the other way round: "broader" scoped fixtures but not the other way round:
A session-scoped fixture could not use a module-scoped one in a A session-scoped fixture could not use a module-scoped one in a
meaningful way. meaningful way.
@ -959,7 +960,7 @@ a generic feature of the mark mechanism:
Note that the assigned variable *must* be called ``pytestmark``, assigning e.g. Note that the assigned variable *must* be called ``pytestmark``, assigning e.g.
``foomark`` will not activate the fixtures. ``foomark`` will not activate the fixtures.
Lastly you can put fixtures required by all tests in your project It is also possible to put fixtures required by all tests in your project
into an ini-file: into an ini-file:
.. code-block:: ini .. code-block:: ini
@ -969,6 +970,22 @@ into an ini-file:
usefixtures = cleandir usefixtures = cleandir
.. warning::
Note this mark has no effect in **fixture functions**. For example,
this **will not work as expected**:
.. code-block:: python
@pytest.mark.usefixtures("my_other_fixture")
@pytest.fixture
def my_fixture_that_sadly_wont_use_my_other_fixture():
...
Currently this will not generate any error or warning, but this is intended
to be handled by `#3664 <https://github.com/pytest-dev/pytest/issues/3664>`_.
.. _`autouse`: .. _`autouse`:
.. _`autouse fixtures`: .. _`autouse fixtures`:

View File

@ -187,7 +187,7 @@ You can then install your package in "editable" mode::
pip install -e . pip install -e .
which lets you change your source code (both tests and application) and rerun tests at will. which lets you change your source code (both tests and application) and rerun tests at will.
This is similar to running `python setup.py develop` or `conda develop` in that it installs This is similar to running ``python setup.py develop`` or ``conda develop`` in that it installs
your package using a symlink to your development code. your package using a symlink to your development code.
Once you are done with your work and want to make sure that your actual Once you are done with your work and want to make sure that your actual

View File

@ -52,8 +52,6 @@ should add ``--strict`` to ``addopts``:
serial serial
.. `marker-iteration`
Marker revamp and iteration Marker revamp and iteration
--------------------------- ---------------------------

View File

@ -161,6 +161,25 @@ Skip a test function if a condition is ``True``.
:keyword str reason: Reason why the test function is being skipped. :keyword str reason: Reason why the test function is being skipped.
.. _`pytest.mark.usefixtures ref`:
pytest.mark.usefixtures
~~~~~~~~~~~~~~~~~~~~~~~
**Tutorial**: :ref:`usefixtures`.
Mark a test function as using the given fixture names.
.. warning::
This mark can be used with *test functions* only, having no affect when applied
to a **fixture** function.
.. py:function:: pytest.mark.usefixtures(*names)
:param args: the names of the fixture to use, as strings
.. _`pytest.mark.xfail ref`: .. _`pytest.mark.xfail ref`:
pytest.mark.xfail pytest.mark.xfail

View File

@ -1,14 +1,13 @@
""" """
Invoke development tasks. Invoke development tasks.
""" """
import argparse
from colorama import init, Fore
from pathlib import Path from pathlib import Path
from subprocess import check_output, check_call from subprocess import check_output, check_call, call
import invoke
@invoke.task(help={"version": "version being released"}) def announce(version):
def announce(ctx, version):
"""Generates a new release announcement entry in the docs.""" """Generates a new release announcement entry in the docs."""
# Get our list of authors # Get our list of authors
stdout = check_output(["git", "describe", "--abbrev=0", "--tags"]) stdout = check_output(["git", "describe", "--abbrev=0", "--tags"])
@ -38,7 +37,7 @@ def announce(ctx, version):
"../doc/en/announce/release-{}.rst".format(version) "../doc/en/announce/release-{}.rst".format(version)
) )
target.write_text(text, encoding="UTF-8") target.write_text(text, encoding="UTF-8")
print("[generate.announce] Generated {}".format(target.name)) print(f"{Fore.CYAN}[generate.announce] {Fore.RESET}Generated {target.name}")
# Update index with the new release entry # Update index with the new release entry
index_path = Path(__file__).parent.joinpath("../doc/en/announce/index.rst") index_path = Path(__file__).parent.joinpath("../doc/en/announce/index.rst")
@ -50,69 +49,63 @@ def announce(ctx, version):
if line != new_line: if line != new_line:
lines.insert(index, new_line) lines.insert(index, new_line)
index_path.write_text("\n".join(lines) + "\n", encoding="UTF-8") index_path.write_text("\n".join(lines) + "\n", encoding="UTF-8")
print("[generate.announce] Updated {}".format(index_path.name)) print(
f"{Fore.CYAN}[generate.announce] {Fore.RESET}Updated {index_path.name}"
)
else: else:
print( print(
"[generate.announce] Skip {} (already contains release)".format( f"{Fore.CYAN}[generate.announce] {Fore.RESET}Skip {index_path.name} (already contains release)"
index_path.name
)
) )
break break
check_call(["git", "add", str(target)]) check_call(["git", "add", str(target)])
@invoke.task() def regen():
def regen(ctx):
"""Call regendoc tool to update examples and pytest output in the docs.""" """Call regendoc tool to update examples and pytest output in the docs."""
print("[generate.regen] Updating docs") print(f"{Fore.CYAN}[generate.regen] {Fore.RESET}Updating docs")
check_call(["tox", "-e", "regen"]) check_call(["tox", "-e", "regen"])
@invoke.task() def fix_formatting():
def make_tag(ctx, version): """Runs pre-commit in all files to ensure they are formatted correctly"""
"""Create a new, local tag for the release, only if the repository is clean.""" print(
from git import Repo f"{Fore.CYAN}[generate.fix linting] {Fore.RESET}Fixing formatting using pre-commit"
)
repo = Repo(".") call(["pre-commit", "run", "--all-files"])
if repo.is_dirty():
print("Current repository is dirty. Please commit any changes and try again.")
raise invoke.Exit(code=2)
tag_names = [x.name for x in repo.tags]
if version in tag_names:
print("[generate.make_tag] Delete existing tag {}".format(version))
repo.delete_tag(version)
print("[generate.make_tag] Create tag {}".format(version))
repo.create_tag(version)
@invoke.task(help={"version": "version being released"}) def pre_release(version):
def pre_release(ctx, version):
"""Generates new docs, release announcements and creates a local tag.""" """Generates new docs, release announcements and creates a local tag."""
announce(ctx, version) announce(version)
regen(ctx) regen()
changelog(ctx, version, write_out=True) changelog(version, write_out=True)
fix_formatting()
msg = "Preparing release version {}".format(version) msg = "Preparing release version {}".format(version)
check_call(["git", "commit", "-a", "-m", msg]) check_call(["git", "commit", "-a", "-m", msg])
make_tag(ctx, version)
print() print()
print("[generate.pre_release] Please push your branch and open a PR.") print(f"{Fore.CYAN}[generate.pre_release] {Fore.GREEN}All done!")
print()
print(f"Please push your branch and open a PR.")
@invoke.task( def changelog(version, write_out=False):
help={
"version": "version being released",
"write_out": "write changes to the actual changelog",
}
)
def changelog(ctx, version, write_out=False):
if write_out: if write_out:
addopts = [] addopts = []
else: else:
addopts = ["--draft"] addopts = ["--draft"]
check_call(["towncrier", "--yes", "--version", version] + addopts) check_call(["towncrier", "--yes", "--version", version] + addopts)
def main():
init(autoreset=True)
parser = argparse.ArgumentParser()
parser.add_argument("version", help="Release version")
options = parser.parse_args()
pre_release(options.version)
if __name__ == "__main__":
main()

View File

@ -1,4 +1,4 @@
from __future__ import absolute_import, division, generators, print_function from __future__ import absolute_import, division, print_function
import ast import ast
from ast import PyCF_ONLY_AST as _AST_FLAG from ast import PyCF_ONLY_AST as _AST_FLAG
@ -152,12 +152,7 @@ class Source(object):
return "\n".join(self.lines) return "\n".join(self.lines)
def compile( def compile(
self, self, filename=None, mode="exec", flag=0, dont_inherit=0, _genframe=None
filename=None,
mode="exec",
flag=generators.compiler_flag,
dont_inherit=0,
_genframe=None,
): ):
""" return compiled code object. if filename is None """ return compiled code object. if filename is None
invent an artificial filename which displays invent an artificial filename which displays
@ -201,9 +196,7 @@ class Source(object):
# #
def compile_( def compile_(source, filename=None, mode="exec", flags=0, dont_inherit=0):
source, filename=None, mode="exec", flags=generators.compiler_flag, dont_inherit=0
):
""" compile the given source to a raw code object, """ compile the given source to a raw code object,
and maintain an internal cache which allows later and maintain an internal cache which allows later
retrieval of the source code for the code object retrieval of the source code for the code object

View File

@ -47,8 +47,8 @@ else:
if _PY3: if _PY3:
from collections.abc import MutableMapping as MappingMixin # noqa from collections.abc import MutableMapping as MappingMixin
from collections.abc import Mapping, Sequence # noqa from collections.abc import Mapping, Sequence
else: else:
# those raise DeprecationWarnings in Python >=3.7 # those raise DeprecationWarnings in Python >=3.7
from collections import MutableMapping as MappingMixin # noqa from collections import MutableMapping as MappingMixin # noqa

View File

@ -71,7 +71,7 @@ def main(args=None, plugins=None):
return 4 return 4
class cmdline(object): # NOQA compatibility namespace class cmdline(object): # compatibility namespace
main = staticmethod(main) main = staticmethod(main)

View File

@ -111,7 +111,19 @@ class ParameterSet(namedtuple("ParameterSet", "values, marks, id")):
] ]
del argvalues del argvalues
if not parameters: if parameters:
# check all parameter sets have the correct number of values
for param in parameters:
if len(param.values) != len(argnames):
raise ValueError(
'In "parametrize" the number of values ({}) must be '
"equal to the number of names ({})".format(
param.values, argnames
)
)
else:
# empty parameter set (likely computed at runtime): create a single
# parameter set with NOSET values, with the "empty parameter set" mark applied to it
mark = get_empty_parameterset_mark(config, argnames, func) mark = get_empty_parameterset_mark(config, argnames, func)
parameters.append( parameters.append(
ParameterSet(values=(NOTSET,) * len(argnames), marks=[mark], id=None) ParameterSet(values=(NOTSET,) * len(argnames), marks=[mark], id=None)
@ -124,9 +136,9 @@ class Mark(object):
#: name of the mark #: name of the mark
name = attr.ib(type=str) name = attr.ib(type=str)
#: positional arguments of the mark decorator #: positional arguments of the mark decorator
args = attr.ib(type="List[object]") args = attr.ib() # type: List[object]
#: keyword arguments of the mark decorator #: keyword arguments of the mark decorator
kwargs = attr.ib(type="Dict[str, object]") kwargs = attr.ib() # type: Dict[str, object]
def combined_with(self, other): def combined_with(self, other):
""" """

View File

@ -23,11 +23,6 @@ from _pytest.main import Session, EXIT_OK
from _pytest.assertion.rewrite import AssertionRewritingHook from _pytest.assertion.rewrite import AssertionRewritingHook
from _pytest.compat import Path from _pytest.compat import Path
PYTEST_FULLPATH = os.path.abspath(pytest.__file__.rstrip("oc")).replace(
"$py.class", ".py"
)
IGNORE_PAM = [ # filenames added when obtaining details about the current user IGNORE_PAM = [ # filenames added when obtaining details about the current user
u"/var/lib/sss/mc/passwd" u"/var/lib/sss/mc/passwd"
] ]
@ -1077,9 +1072,7 @@ class Testdir(object):
print("couldn't print to %s because of encoding" % (fp,)) print("couldn't print to %s because of encoding" % (fp,))
def _getpytestargs(self): def _getpytestargs(self):
# we cannot use `(sys.executable, script)` because on Windows the return (sys.executable, "-mpytest")
# script is e.g. `pytest.exe`
return (sys.executable, PYTEST_FULLPATH) # noqa
def runpython(self, script): def runpython(self, script):
"""Run a python script using sys.executable as interpreter. """Run a python script using sys.executable as interpreter.

View File

@ -8,7 +8,6 @@ import os
import collections import collections
import warnings import warnings
from textwrap import dedent from textwrap import dedent
from itertools import count
import py import py
@ -945,46 +944,54 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
""" """
from _pytest.fixtures import scope2index from _pytest.fixtures import scope2index
from _pytest.mark import ParameterSet from _pytest.mark import ParameterSet
from py.io import saferepr
argnames, parameters = ParameterSet._for_parametrize( argnames, parameters = ParameterSet._for_parametrize(
argnames, argvalues, self.function, self.config argnames, argvalues, self.function, self.config
) )
del argvalues del argvalues
default_arg_names = set(get_default_arg_names(self.function))
if scope is None: if scope is None:
scope = _find_parametrized_scope(argnames, self._arg2fixturedefs, indirect) scope = _find_parametrized_scope(argnames, self._arg2fixturedefs, indirect)
scopenum = scope2index(scope, descr="call to {}".format(self.parametrize)) self._validate_if_using_arg_names(argnames, indirect)
valtypes = {}
for arg in argnames: arg_values_types = self._resolve_arg_value_types(argnames, indirect)
if arg not in self.fixturenames:
if arg in default_arg_names: ids = self._resolve_arg_ids(argnames, ids, parameters)
raise ValueError(
"%r already takes an argument %r with a default value" scopenum = scope2index(scope, descr="call to {}".format(self.parametrize))
% (self.function, arg)
) # create the new calls: if we are parametrize() multiple times (by applying the decorator
else: # more than once) then we accumulate those calls generating the cartesian product
if isinstance(indirect, (tuple, list)): # of all calls
name = "fixture" if arg in indirect else "argument" newcalls = []
else: for callspec in self._calls or [CallSpec2(self)]:
name = "fixture" if indirect else "argument" for param_index, (param_id, param_set) in enumerate(zip(ids, parameters)):
raise ValueError("%r uses no %s %r" % (self.function, name, arg)) newcallspec = callspec.copy()
newcallspec.setmulti2(
arg_values_types,
argnames,
param_set.values,
param_id,
param_set.marks,
scopenum,
param_index,
)
newcalls.append(newcallspec)
self._calls = newcalls
def _resolve_arg_ids(self, argnames, ids, parameters):
"""Resolves the actual ids for the given argnames, based on the ``ids`` parameter given
to ``parametrize``.
:param List[str] argnames: list of argument names passed to ``parametrize()``.
:param ids: the ids parameter of the parametrized call (see docs).
:param List[ParameterSet] parameters: the list of parameter values, same size as ``argnames``.
:rtype: List[str]
:return: the list of ids for each argname given
"""
from py.io import saferepr
if indirect is True:
valtypes = dict.fromkeys(argnames, "params")
elif indirect is False:
valtypes = dict.fromkeys(argnames, "funcargs")
elif isinstance(indirect, (tuple, list)):
valtypes = dict.fromkeys(argnames, "funcargs")
for arg in indirect:
if arg not in argnames:
raise ValueError(
"indirect given to %r: fixture %r doesn't exist"
% (self.function, arg)
)
valtypes[arg] = "params"
idfn = None idfn = None
if callable(ids): if callable(ids):
idfn = ids idfn = ids
@ -1001,29 +1008,57 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
msg % (saferepr(id_value), type(id_value).__name__) msg % (saferepr(id_value), type(id_value).__name__)
) )
ids = idmaker(argnames, parameters, idfn, ids, self.config) ids = idmaker(argnames, parameters, idfn, ids, self.config)
newcalls = [] return ids
for callspec in self._calls or [CallSpec2(self)]:
elements = zip(ids, parameters, count()) def _resolve_arg_value_types(self, argnames, indirect):
for a_id, param, param_index in elements: """Resolves if each parametrized argument must be considered a parameter to a fixture or a "funcarg"
if len(param.values) != len(argnames): to the function, based on the ``indirect`` parameter of the parametrized() call.
:param List[str] argnames: list of argument names passed to ``parametrize()``.
:param indirect: same ``indirect`` parameter of ``parametrize()``.
:rtype: Dict[str, str]
A dict mapping each arg name to either:
* "params" if the argname should be the parameter of a fixture of the same name.
* "funcargs" if the argname should be a parameter to the parametrized test function.
"""
valtypes = {}
if indirect is True:
valtypes = dict.fromkeys(argnames, "params")
elif indirect is False:
valtypes = dict.fromkeys(argnames, "funcargs")
elif isinstance(indirect, (tuple, list)):
valtypes = dict.fromkeys(argnames, "funcargs")
for arg in indirect:
if arg not in argnames:
raise ValueError( raise ValueError(
'In "parametrize" the number of values ({}) must be ' "indirect given to %r: fixture %r doesn't exist"
"equal to the number of names ({})".format( % (self.function, arg)
param.values, argnames
)
) )
newcallspec = callspec.copy() valtypes[arg] = "params"
newcallspec.setmulti2( return valtypes
valtypes,
argnames, def _validate_if_using_arg_names(self, argnames, indirect):
param.values, """
a_id, Check if all argnames are being used, by default values, or directly/indirectly.
param.marks,
scopenum, :param List[str] argnames: list of argument names passed to ``parametrize()``.
param_index, :param indirect: same ``indirect`` parameter of ``parametrize()``.
) :raise ValueError: if validation fails.
newcalls.append(newcallspec) """
self._calls = newcalls default_arg_names = set(get_default_arg_names(self.function))
for arg in argnames:
if arg not in self.fixturenames:
if arg in default_arg_names:
raise ValueError(
"%r already takes an argument %r with a default value"
% (self.function, arg)
)
else:
if isinstance(indirect, (tuple, list)):
name = "fixture" if arg in indirect else "argument"
else:
name = "fixture" if indirect else "argument"
raise ValueError("%r uses no %s %r" % (self.function, name, arg))
def addcall(self, funcargs=None, id=NOTSET, param=NOTSET): def addcall(self, funcargs=None, id=NOTSET, param=NOTSET):
""" Add a new call to the underlying test function during the collection phase of a test run. """ Add a new call to the underlying test function during the collection phase of a test run.

View File

@ -1,10 +0,0 @@
"""
Invoke tasks to help with pytest development and release process.
"""
import invoke
from . import generate
ns = invoke.Collection(generate)

View File

@ -1,6 +0,0 @@
-e .
gitpython
invoke
towncrier
tox
wheel

View File

@ -744,3 +744,19 @@ something
'''""" '''"""
result = getstatement(1, source) result = getstatement(1, source)
assert str(result) == "'''\n'''" assert str(result) == "'''\n'''"
def test_getstartingblock_multiline():
class A(object):
def __init__(self, *args):
frame = sys._getframe(1)
self.source = _pytest._code.Frame(frame).statement
# fmt: off
x = A('x',
'y'
,
'z')
# fmt: on
values = [i for i in x.source.lines if i.strip()]
assert len(values) == 4

View File

@ -1,28 +0,0 @@
# flake8: noqa
import sys
import _pytest._code
def test_getstartingblock_multiline():
"""
This test was originally found in test_source.py, but it depends on the weird
formatting of the ``x = A`` construct seen here and our autopep8 tool can only exclude entire
files (it does not support excluding lines/blocks using the traditional #noqa comment yet,
see hhatto/autopep8#307). It was considered better to just move this single test to its own
file and exclude it from autopep8 than try to complicate things.
"""
class A(object):
def __init__(self, *args):
frame = sys._getframe(1)
self.source = _pytest._code.Frame(frame).statement
# fmt: off
x = A('x',
'y'
,
'z')
# fmt: on
values = [i for i in x.source.lines if i.strip()]
assert len(values) == 4

View File

@ -33,8 +33,7 @@ class TestRaises(object):
def test_raises_as_contextmanager(self, testdir): def test_raises_as_contextmanager(self, testdir):
testdir.makepyfile( testdir.makepyfile(
""" """
from __future__ import with_statement import pytest
import py, pytest
import _pytest._code import _pytest._code
def test_simple(): def test_simple():

View File

@ -97,7 +97,7 @@ class TestAssertionRewrite(object):
assert imp.lineno == 2 assert imp.lineno == 2
assert imp.col_offset == 0 assert imp.col_offset == 0
assert isinstance(m.body[2], ast.Assign) assert isinstance(m.body[2], ast.Assign)
s = """from __future__ import with_statement\nother_stuff""" s = """from __future__ import division\nother_stuff"""
m = rewrite(s) m = rewrite(s)
assert isinstance(m.body[0], ast.ImportFrom) assert isinstance(m.body[0], ast.ImportFrom)
for imp in m.body[1:3]: for imp in m.body[1:3]:
@ -105,7 +105,7 @@ class TestAssertionRewrite(object):
assert imp.lineno == 2 assert imp.lineno == 2
assert imp.col_offset == 0 assert imp.col_offset == 0
assert isinstance(m.body[3], ast.Expr) assert isinstance(m.body[3], ast.Expr)
s = """'doc string'\nfrom __future__ import with_statement""" s = """'doc string'\nfrom __future__ import division"""
m = rewrite(s) m = rewrite(s)
adjust_body_for_new_docstring_in_module_node(m) adjust_body_for_new_docstring_in_module_node(m)
assert isinstance(m.body[0], ast.ImportFrom) assert isinstance(m.body[0], ast.ImportFrom)
@ -113,7 +113,7 @@ class TestAssertionRewrite(object):
assert isinstance(imp, ast.Import) assert isinstance(imp, ast.Import)
assert imp.lineno == 2 assert imp.lineno == 2
assert imp.col_offset == 0 assert imp.col_offset == 0
s = """'doc string'\nfrom __future__ import with_statement\nother""" s = """'doc string'\nfrom __future__ import division\nother"""
m = rewrite(s) m = rewrite(s)
adjust_body_for_new_docstring_in_module_node(m) adjust_body_for_new_docstring_in_module_node(m)
assert isinstance(m.body[0], ast.ImportFrom) assert isinstance(m.body[0], ast.ImportFrom)
@ -941,7 +941,7 @@ class TestAssertionRewriteHookDetails(object):
e = IOError() e = IOError()
e.errno = 10 e.errno = 10
raise e raise e
yield # noqa yield
monkeypatch.setattr(atomicwrites, "atomic_write", atomic_write_failed) monkeypatch.setattr(atomicwrites, "atomic_write", atomic_write_failed)
assert not _write_pyc(state, [1], source_path.stat(), pycpath) assert not _write_pyc(state, [1], source_path.stat(), pycpath)

View File

@ -2,7 +2,6 @@ from __future__ import absolute_import, division, print_function
# note: py.io capture tests where copied from # note: py.io capture tests where copied from
# pylib 1.4.20.dev2 (rev 13d9af95547e) # pylib 1.4.20.dev2 (rev 13d9af95547e)
from __future__ import with_statement
import pickle import pickle
import os import os
import sys import sys

View File

@ -364,10 +364,7 @@ class TestSysPathsSnapshot(object):
original = list(sys_path) original = list(sys_path)
original_other = list(getattr(sys, other_path_type)) original_other = list(getattr(sys, other_path_type))
snapshot = SysPathsSnapshot() snapshot = SysPathsSnapshot()
transformation = { transformation = {"source": (0, 1, 2, 3, 4, 5), "target": (6, 2, 9, 7, 5, 8)}
"source": (0, 1, 2, 3, 4, 5),
"target": (6, 2, 9, 7, 5, 8),
} # noqa: E201
assert sys_path == [self.path(x) for x in transformation["source"]] assert sys_path == [self.path(x) for x in transformation["source"]]
sys_path[1] = self.path(6) sys_path[1] = self.path(6)
sys_path[3] = self.path(7) sys_path[3] = self.path(7)
@ -394,3 +391,8 @@ class TestSysPathsSnapshot(object):
assert getattr(sys, path_type) == original_data assert getattr(sys, path_type) == original_data
assert getattr(sys, other_path_type) is original_other assert getattr(sys, other_path_type) is original_other
assert getattr(sys, other_path_type) == original_other_data assert getattr(sys, other_path_type) == original_other_data
def test_testdir_subprocess(testdir):
testfile = testdir.makepyfile("def test_one(): pass")
assert testdir.runpytest_subprocess(testfile).ret == 0

22
tox.ini
View File

@ -151,14 +151,6 @@ commands =
rm -rf /tmp/doc-exec* rm -rf /tmp/doc-exec*
make regen make regen
[testenv:fix-lint]
skipsdist = True
usedevelop = True
deps =
autopep8
commands =
autopep8 --in-place -r --max-line-length=120 --exclude=test_source_multiline_block.py _pytest testing setup.py pytest.py
[testenv:jython] [testenv:jython]
changedir = testing changedir = testing
commands = commands =
@ -184,6 +176,20 @@ commands =
coverage report -m coverage report -m
coveralls coveralls
[testenv:release]
decription = do a release, required posarg of the version number
basepython = python3.6
skipsdist = True
usedevelop = True
passenv = *
deps =
colorama
gitpython
pre-commit
towncrier
wheel
commands = python scripts/release.py {posargs}
[pytest] [pytest]
minversion = 2.0 minversion = 2.0
plugins = pytester plugins = pytester