Merge pull request #3685 from nicoddemus/merge-master-into-features

Merge master into features
This commit is contained in:
Bruno Oliveira 2018-07-15 16:53:39 -03:00 committed by GitHub
commit 0bb29d5649
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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
additional_dependencies: [pygments, restructuredtext_lint]
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:
- linting
- test
- deploy
- name: deploy
if: repo = pytest-dev/pytest AND tag IS present
python:
- '3.6'
install:

View File

@ -72,7 +72,7 @@ Bug Fixes
raises an exception. (`#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>`_)
@ -345,7 +345,7 @@ Features
``pytest_runtest_logfinish`` hooks when live logs are enabled. (`#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>`_)
- 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>`_,
`#3021 <https://github.com/pytest-dev/pytest/issues/3021>`_)
- Clean up code by replacing imports and references of `_ast` to `ast`. (`#3018
<https://github.com/pytest-dev/pytest/issues/3018>`_)
- Clean up code by replacing imports and references of ``_ast`` to ``ast``.
(`#3018 <https://github.com/pytest-dev/pytest/issues/3018>`_)
Pytest 3.3.1 (2017-12-05)
@ -1026,7 +1026,7 @@ Pytest 3.2.2 (2017-09-06)
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>`_)
- Allow tests declared as ``@staticmethod`` to use fixtures. (`#2699
@ -1048,10 +1048,10 @@ Improved Documentation
``pytest.mark.MARKER_NAME.__call__`` (`#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
user error when acessing `pytest.config` before the argument parsing. (`#2653
<https://github.com/pytest-dev/pytest/issues/2653>`_)
user error when acessing ``pytest.config`` before the argument parsing.
(`#2653 <https://github.com/pytest-dev/pytest/issues/2653>`_)
Trivial/Internal Changes
@ -1129,7 +1129,7 @@ Features
from parent classes or modules. (`#2516 <https://github.com/pytest-
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-
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.
#. Install development dependencies in a virtual environment with::
#. Using ``tox``, generate docs, changelog, announcements::
$ pip3 install -U -r tasks/requirements.txt
#. 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"
$ tox -e release -- <VERSION>
This will generate a commit with all the changes ready for pushing.
#. Open a PR for this branch targeting ``master``.

View File

@ -3,6 +3,7 @@
:align: center
:alt: pytest
------
.. 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
-------
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.

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.
* Fix `traceback style docs`_ to describe all of the available options
(auto/long/short/line/native/no), with `auto` being the default since v2.6.
(auto/long/short/line/native/no), with ``auto`` being the default since v2.6.
Thanks `@hackebrot`_ for the PR.
* Fix (`#1422`_): junit record_xml_property doesn't allow multiple records

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.
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
~~~~~~~~~~~~~~~~

View File

@ -299,10 +299,10 @@ Skip and xfail marks can also be applied in this way, see :ref:`skip/xfail with
.. note::
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
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`:

View File

@ -55,18 +55,18 @@ using it::
import pytest
@pytest.fixture
def smtp():
def smtp_connection():
import smtplib
return smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
def test_ehlo(smtp):
response, msg = smtp.ehlo()
def test_ehlo(smtp_connection):
response, msg = smtp_connection.ehlo()
assert response == 250
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>`
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
=========================== test session starts ============================
@ -79,10 +79,10 @@ marked ``smtp`` fixture function. Running the test looks like this::
================================= FAILURES =================================
________________________________ test_ehlo _________________________________
smtp = <smtplib.SMTP object at 0xdeadbeef>
smtp_connection = <smtplib.SMTP object at 0xdeadbeef>
def test_ehlo(smtp):
response, msg = smtp.ehlo()
def test_ehlo(smtp_connection):
response, msg = smtp_connection.ehlo()
assert response == 250
> assert 0 # for demo purposes
E assert 0
@ -91,18 +91,18 @@ marked ``smtp`` fixture function. Running the test looks like this::
========================= 1 failed in 0.12 seconds =========================
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
the exact protocol used by ``pytest`` to call the test function this way:
1. pytest :ref:`finds <test discovery>` the ``test_ehlo`` because
of the ``test_`` prefix. The test function needs a function argument
named ``smtp``. A matching fixture function is discovered by
looking for a fixture-marked function named ``smtp``.
named ``smtp_connection``. A matching fixture function is discovered by
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.
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
can add a ``scope="module"`` parameter to the
:py:func:`@pytest.fixture <_pytest.python.fixture>` invocation
to cause the decorated ``smtp`` fixture function to only be invoked once
per test *module* (the default is to invoke once per test *function*).
to cause the decorated ``smtp_connection`` fixture function to only be invoked
once per test *module* (the default is to invoke once per test *function*).
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
so that tests from multiple test modules in the directory can
@ -181,23 +181,24 @@ access the fixture function::
import smtplib
@pytest.fixture(scope="module")
def smtp():
def smtp_connection():
return smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
The name of the fixture again is ``smtp`` and you can access its result by
listing the name ``smtp`` as an input parameter in any test or fixture
function (in or below the directory where ``conftest.py`` is located)::
The name of the fixture again is ``smtp_connection`` and you can access its
result by listing the name ``smtp_connection`` as an input parameter in any
test or fixture function (in or below the directory where ``conftest.py`` is
located)::
# content of test_module.py
def test_ehlo(smtp):
response, msg = smtp.ehlo()
def test_ehlo(smtp_connection):
response, msg = smtp_connection.ehlo()
assert response == 250
assert b"smtp.gmail.com" in msg
assert 0 # for demo purposes
def test_noop(smtp):
response, msg = smtp.noop()
def test_noop(smtp_connection):
response, msg = smtp_connection.noop()
assert response == 250
assert 0 # for demo purposes
@ -215,10 +216,10 @@ inspect what is going on and can now run the tests::
================================= FAILURES =================================
________________________________ test_ehlo _________________________________
smtp = <smtplib.SMTP object at 0xdeadbeef>
smtp_connection = <smtplib.SMTP object at 0xdeadbeef>
def test_ehlo(smtp):
response, msg = smtp.ehlo()
def test_ehlo(smtp_connection):
response, msg = smtp_connection.ehlo()
assert response == 250
assert b"smtp.gmail.com" in msg
> 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_noop _________________________________
smtp = <smtplib.SMTP object at 0xdeadbeef>
smtp_connection = <smtplib.SMTP object at 0xdeadbeef>
def test_noop(smtp):
response, msg = smtp.noop()
def test_noop(smtp_connection):
response, msg = smtp_connection.noop()
assert response == 250
> assert 0 # for demo purposes
E assert 0
@ -239,18 +240,18 @@ inspect what is going on and can now run the tests::
========================= 2 failed in 0.12 seconds =========================
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
test functions because pytest shows the incoming argument values in the
traceback. As a result, the two test functions using ``smtp`` run as
quick as a single one because they reuse the same instance.
that the same (module-scoped) ``smtp_connection`` object was passed into the
two test functions because pytest shows the incoming argument values in the
traceback. As a result, the two test functions using ``smtp_connection`` run
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:
.. code-block:: python
@pytest.fixture(scope="session")
def smtp():
def smtp_connection():
# the returned fixture value will be shared for
# all tests needing it
...
@ -339,11 +340,11 @@ the code after the *yield* statement serves as the teardown code:
@pytest.fixture(scope="module")
def smtp():
smtp = smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
yield smtp # provide the fixture value
def smtp_connection():
smtp_connection = smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
yield smtp_connection # provide the fixture value
print("teardown smtp")
smtp.close()
smtp_connection.close()
The ``print`` and ``smtp.close()`` statements will execute when the last test in
the module has finished execution, regardless of the exception status of the
@ -356,7 +357,7 @@ Let's execute it::
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
function with ``scope='function'`` then fixture setup and cleanup would
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")
def smtp():
with smtplib.SMTP("smtp.gmail.com", 587, timeout=5) as smtp:
yield smtp # provide the fixture value
def smtp_connection():
with smtplib.SMTP("smtp.gmail.com", 587, timeout=5) as smtp_connection:
yield smtp_connection # provide the fixture value
The ``smtp`` connection will be closed after the test finished execution
because the ``smtp`` object automatically closes when
The ``smtp_connection`` connection will be closed after the test finished
execution because the ``smtp_connection`` object automatically closes when
the ``with`` statement ends.
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
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
@ -400,15 +401,15 @@ Here's the ``smtp`` fixture changed to use ``addfinalizer`` for cleanup:
@pytest.fixture(scope="module")
def smtp(request):
smtp = smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
def smtp_connection(request):
smtp_connection = smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
def fin():
print("teardown smtp")
smtp.close()
print("teardown smtp_connection")
smtp_connection.close()
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
@ -441,7 +442,7 @@ Fixtures can introspect the requesting test context
Fixture functions can accept the :py:class:`request <FixtureRequest>` object
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::
# content of conftest.py
@ -449,12 +450,12 @@ read an optional server URL from the test module which uses our fixture::
import smtplib
@pytest.fixture(scope="module")
def smtp(request):
def smtp_connection(request):
server = getattr(request.module, "smtpserver", "smtp.gmail.com")
smtp = smtplib.SMTP(server, 587, timeout=5)
yield smtp
print ("finalizing %s (%s)" % (smtp, server))
smtp.close()
smtp_connection = smtplib.SMTP(server, 587, timeout=5)
yield smtp_connection
print ("finalizing %s (%s)" % (smtp_connection, server))
smtp_connection.close()
We use the ``request.module`` attribute to optionally obtain an
``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
def test_showhelo(smtp):
assert 0, smtp.helo()
def test_showhelo(smtp_connection):
assert 0, smtp_connection.helo()
Running it::
@ -482,13 +483,13 @@ Running it::
================================= FAILURES =================================
______________________________ 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 assert 0
------------------------- Captured stdout teardown -------------------------
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.
.. _`fixture-factory`:
@ -557,7 +558,7 @@ write exhaustive functional tests for components which themselves can be
configured in multiple ways.
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
through the special :py:class:`request <FixtureRequest>` object::
@ -567,11 +568,11 @@ through the special :py:class:`request <FixtureRequest>` object::
@pytest.fixture(scope="module",
params=["smtp.gmail.com", "mail.python.org"])
def smtp(request):
smtp = smtplib.SMTP(request.param, 587, timeout=5)
yield smtp
print ("finalizing %s" % smtp)
smtp.close()
def smtp_connection(request):
smtp_connection = smtplib.SMTP(request.param, 587, timeout=5)
yield smtp_connection
print("finalizing %s" % smtp_connection)
smtp_connection.close()
The main change is the declaration of ``params`` with
:py:func:`@pytest.fixture <_pytest.python.fixture>`, a list of values
@ -584,10 +585,10 @@ So let's just do another run::
================================= FAILURES =================================
________________________ test_ehlo[smtp.gmail.com] _________________________
smtp = <smtplib.SMTP object at 0xdeadbeef>
smtp_connection = <smtplib.SMTP object at 0xdeadbeef>
def test_ehlo(smtp):
response, msg = smtp.ehlo()
def test_ehlo(smtp_connection):
response, msg = smtp_connection.ehlo()
assert response == 250
assert b"smtp.gmail.com" in msg
> assert 0 # for demo purposes
@ -596,10 +597,10 @@ So let's just do another run::
test_module.py:6: AssertionError
________________________ test_noop[smtp.gmail.com] _________________________
smtp = <smtplib.SMTP object at 0xdeadbeef>
smtp_connection = <smtplib.SMTP object at 0xdeadbeef>
def test_noop(smtp):
response, msg = smtp.noop()
def test_noop(smtp_connection):
response, msg = smtp_connection.noop()
assert response == 250
> assert 0 # for demo purposes
E assert 0
@ -607,10 +608,10 @@ So let's just do another run::
test_module.py:11: AssertionError
________________________ test_ehlo[mail.python.org] ________________________
smtp = <smtplib.SMTP object at 0xdeadbeef>
smtp_connection = <smtplib.SMTP object at 0xdeadbeef>
def test_ehlo(smtp):
response, msg = smtp.ehlo()
def test_ehlo(smtp_connection):
response, msg = smtp_connection.ehlo()
assert response == 250
> 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'
@ -620,10 +621,10 @@ So let's just do another run::
finalizing <smtplib.SMTP object at 0xdeadbeef>
________________________ test_noop[mail.python.org] ________________________
smtp = <smtplib.SMTP object at 0xdeadbeef>
smtp_connection = <smtplib.SMTP object at 0xdeadbeef>
def test_noop(smtp):
response, msg = smtp.noop()
def test_noop(smtp_connection):
response, msg = smtp_connection.noop()
assert response == 250
> assert 0 # for demo purposes
E assert 0
@ -634,7 +635,7 @@ So let's just do another run::
4 failed in 0.12 seconds
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
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
many projects. As a simple example, we can extend the previous example
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
import pytest
class App(object):
def __init__(self, smtp):
self.smtp = smtp
def __init__(self, smtp_connection):
self.smtp_connection = smtp_connection
@pytest.fixture(scope="module")
def app(smtp):
return App(smtp)
def app(smtp_connection):
return App(smtp_connection)
def test_smtp_exists(app):
assert app.smtp
def test_smtp_connection_exists(app):
assert app.smtp_connection
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
=========================== test session starts ============================
@ -773,19 +774,19 @@ Here we declare an ``app`` fixture which receives the previously defined
rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 2 items
test_appsetup.py::test_smtp_exists[smtp.gmail.com] PASSED [ 50%]
test_appsetup.py::test_smtp_exists[mail.python.org] PASSED [100%]
test_appsetup.py::test_smtp_connection_exists[smtp.gmail.com] PASSED [ 50%]
test_appsetup.py::test_smtp_connection_exists[mail.python.org] PASSED [100%]
========================= 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
need for the ``app`` fixture to be aware of the ``smtp`` parametrization
as pytest will fully analyse the fixture dependency graph.
need for the ``app`` fixture to be aware of the ``smtp_connection``
parametrization as pytest will fully analyse the fixture dependency graph.
Note, that the ``app`` fixture has a scope of ``module`` and uses a
module-scoped ``smtp`` fixture. The example would still work if ``smtp``
was cached on a ``session`` scope: it is fine for fixtures to use
module-scoped ``smtp_connection`` fixture. The example would still work if
``smtp_connection`` was cached on a ``session`` scope: it is fine for fixtures to use
"broader" scoped fixtures but not the other way round:
A session-scoped fixture could not use a module-scoped one in a
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.
``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:
.. code-block:: ini
@ -969,6 +970,22 @@ into an ini-file:
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 fixtures`:

View File

@ -187,7 +187,7 @@ You can then install your package in "editable" mode::
pip install -e .
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.
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
.. `marker-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.
.. _`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

View File

@ -1,14 +1,13 @@
"""
Invoke development tasks.
"""
import argparse
from colorama import init, Fore
from pathlib import Path
from subprocess import check_output, check_call
import invoke
from subprocess import check_output, check_call, call
@invoke.task(help={"version": "version being released"})
def announce(ctx, version):
def announce(version):
"""Generates a new release announcement entry in the docs."""
# Get our list of authors
stdout = check_output(["git", "describe", "--abbrev=0", "--tags"])
@ -38,7 +37,7 @@ def announce(ctx, version):
"../doc/en/announce/release-{}.rst".format(version)
)
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
index_path = Path(__file__).parent.joinpath("../doc/en/announce/index.rst")
@ -50,69 +49,63 @@ def announce(ctx, version):
if line != new_line:
lines.insert(index, new_line)
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:
print(
"[generate.announce] Skip {} (already contains release)".format(
index_path.name
)
f"{Fore.CYAN}[generate.announce] {Fore.RESET}Skip {index_path.name} (already contains release)"
)
break
check_call(["git", "add", str(target)])
@invoke.task()
def regen(ctx):
def regen():
"""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"])
@invoke.task()
def make_tag(ctx, version):
"""Create a new, local tag for the release, only if the repository is clean."""
from git import Repo
repo = Repo(".")
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)
def fix_formatting():
"""Runs pre-commit in all files to ensure they are formatted correctly"""
print(
f"{Fore.CYAN}[generate.fix linting] {Fore.RESET}Fixing formatting using pre-commit"
)
call(["pre-commit", "run", "--all-files"])
@invoke.task(help={"version": "version being released"})
def pre_release(ctx, version):
def pre_release(version):
"""Generates new docs, release announcements and creates a local tag."""
announce(ctx, version)
regen(ctx)
changelog(ctx, version, write_out=True)
announce(version)
regen()
changelog(version, write_out=True)
fix_formatting()
msg = "Preparing release version {}".format(version)
check_call(["git", "commit", "-a", "-m", msg])
make_tag(ctx, version)
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(
help={
"version": "version being released",
"write_out": "write changes to the actual changelog",
}
)
def changelog(ctx, version, write_out=False):
def changelog(version, write_out=False):
if write_out:
addopts = []
else:
addopts = ["--draft"]
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
from ast import PyCF_ONLY_AST as _AST_FLAG
@ -152,12 +152,7 @@ class Source(object):
return "\n".join(self.lines)
def compile(
self,
filename=None,
mode="exec",
flag=generators.compiler_flag,
dont_inherit=0,
_genframe=None,
self, filename=None, mode="exec", flag=0, dont_inherit=0, _genframe=None
):
""" return compiled code object. if filename is None
invent an artificial filename which displays
@ -201,9 +196,7 @@ class Source(object):
#
def compile_(
source, filename=None, mode="exec", flags=generators.compiler_flag, dont_inherit=0
):
def compile_(source, filename=None, mode="exec", flags=0, dont_inherit=0):
""" compile the given source to a raw code object,
and maintain an internal cache which allows later
retrieval of the source code for the code object

View File

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

View File

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

View File

@ -111,7 +111,19 @@ class ParameterSet(namedtuple("ParameterSet", "values, marks, id")):
]
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)
parameters.append(
ParameterSet(values=(NOTSET,) * len(argnames), marks=[mark], id=None)
@ -124,9 +136,9 @@ class Mark(object):
#: name of the mark
name = attr.ib(type=str)
#: positional arguments of the mark decorator
args = attr.ib(type="List[object]")
args = attr.ib() # type: List[object]
#: 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):
"""

View File

@ -23,11 +23,6 @@ from _pytest.main import Session, EXIT_OK
from _pytest.assertion.rewrite import AssertionRewritingHook
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
u"/var/lib/sss/mc/passwd"
]
@ -1077,9 +1072,7 @@ class Testdir(object):
print("couldn't print to %s because of encoding" % (fp,))
def _getpytestargs(self):
# we cannot use `(sys.executable, script)` because on Windows the
# script is e.g. `pytest.exe`
return (sys.executable, PYTEST_FULLPATH) # noqa
return (sys.executable, "-mpytest")
def runpython(self, script):
"""Run a python script using sys.executable as interpreter.

View File

@ -8,7 +8,6 @@ import os
import collections
import warnings
from textwrap import dedent
from itertools import count
import py
@ -945,46 +944,54 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
"""
from _pytest.fixtures import scope2index
from _pytest.mark import ParameterSet
from py.io import saferepr
argnames, parameters = ParameterSet._for_parametrize(
argnames, argvalues, self.function, self.config
)
del argvalues
default_arg_names = set(get_default_arg_names(self.function))
if scope is None:
scope = _find_parametrized_scope(argnames, self._arg2fixturedefs, indirect)
scopenum = scope2index(scope, descr="call to {}".format(self.parametrize))
valtypes = {}
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))
self._validate_if_using_arg_names(argnames, indirect)
arg_values_types = self._resolve_arg_value_types(argnames, indirect)
ids = self._resolve_arg_ids(argnames, ids, parameters)
scopenum = scope2index(scope, descr="call to {}".format(self.parametrize))
# create the new calls: if we are parametrize() multiple times (by applying the decorator
# more than once) then we accumulate those calls generating the cartesian product
# of all calls
newcalls = []
for callspec in self._calls or [CallSpec2(self)]:
for param_index, (param_id, param_set) in enumerate(zip(ids, parameters)):
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
if callable(ids):
idfn = ids
@ -1001,29 +1008,57 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
msg % (saferepr(id_value), type(id_value).__name__)
)
ids = idmaker(argnames, parameters, idfn, ids, self.config)
newcalls = []
for callspec in self._calls or [CallSpec2(self)]:
elements = zip(ids, parameters, count())
for a_id, param, param_index in elements:
if len(param.values) != len(argnames):
return ids
def _resolve_arg_value_types(self, argnames, indirect):
"""Resolves if each parametrized argument must be considered a parameter to a fixture or a "funcarg"
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(
'In "parametrize" the number of values ({}) must be '
"equal to the number of names ({})".format(
param.values, argnames
)
"indirect given to %r: fixture %r doesn't exist"
% (self.function, arg)
)
newcallspec = callspec.copy()
newcallspec.setmulti2(
valtypes,
argnames,
param.values,
a_id,
param.marks,
scopenum,
param_index,
)
newcalls.append(newcallspec)
self._calls = newcalls
valtypes[arg] = "params"
return valtypes
def _validate_if_using_arg_names(self, argnames, indirect):
"""
Check if all argnames are being used, by default values, or directly/indirectly.
:param List[str] argnames: list of argument names passed to ``parametrize()``.
:param indirect: same ``indirect`` parameter of ``parametrize()``.
:raise ValueError: if validation fails.
"""
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):
""" 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)
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):
testdir.makepyfile(
"""
from __future__ import with_statement
import py, pytest
import pytest
import _pytest._code
def test_simple():

View File

@ -97,7 +97,7 @@ class TestAssertionRewrite(object):
assert imp.lineno == 2
assert imp.col_offset == 0
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)
assert isinstance(m.body[0], ast.ImportFrom)
for imp in m.body[1:3]:
@ -105,7 +105,7 @@ class TestAssertionRewrite(object):
assert imp.lineno == 2
assert imp.col_offset == 0
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)
adjust_body_for_new_docstring_in_module_node(m)
assert isinstance(m.body[0], ast.ImportFrom)
@ -113,7 +113,7 @@ class TestAssertionRewrite(object):
assert isinstance(imp, ast.Import)
assert imp.lineno == 2
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)
adjust_body_for_new_docstring_in_module_node(m)
assert isinstance(m.body[0], ast.ImportFrom)
@ -941,7 +941,7 @@ class TestAssertionRewriteHookDetails(object):
e = IOError()
e.errno = 10
raise e
yield # noqa
yield
monkeypatch.setattr(atomicwrites, "atomic_write", atomic_write_failed)
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
# pylib 1.4.20.dev2 (rev 13d9af95547e)
from __future__ import with_statement
import pickle
import os
import sys

View File

@ -364,10 +364,7 @@ class TestSysPathsSnapshot(object):
original = list(sys_path)
original_other = list(getattr(sys, other_path_type))
snapshot = SysPathsSnapshot()
transformation = {
"source": (0, 1, 2, 3, 4, 5),
"target": (6, 2, 9, 7, 5, 8),
} # noqa: E201
transformation = {"source": (0, 1, 2, 3, 4, 5), "target": (6, 2, 9, 7, 5, 8)}
assert sys_path == [self.path(x) for x in transformation["source"]]
sys_path[1] = self.path(6)
sys_path[3] = self.path(7)
@ -394,3 +391,8 @@ class TestSysPathsSnapshot(object):
assert getattr(sys, path_type) == original_data
assert getattr(sys, other_path_type) is original_other
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*
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]
changedir = testing
commands =
@ -184,6 +176,20 @@ commands =
coverage report -m
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]
minversion = 2.0
plugins = pytester