diff --git a/.github/labels.toml b/.github/labels.toml new file mode 100644 index 000000000..aef1e913a --- /dev/null +++ b/.github/labels.toml @@ -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" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9e9549ed9..cae90a428 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -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] diff --git a/.travis.yml b/.travis.yml index f2921e118..28393a0b7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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: diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 9c9d254d0..0c3bb2476 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -72,7 +72,7 @@ Bug Fixes raises an exception. (`#3569 `_) -- Fix encoding error with `print` statements in doctests (`#3583 +- Fix encoding error with ``print`` statements in doctests (`#3583 `_) @@ -345,7 +345,7 @@ Features ``pytest_runtest_logfinish`` hooks when live logs are enabled. (`#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 `_) - Add command line option ``--deselect`` to allow deselection of individual @@ -697,8 +697,8 @@ Trivial/Internal Changes - Code cleanup. (`#3015 `_, `#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``. + (`#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 `_) - Allow tests declared as ``@staticmethod`` to use fixtures. (`#2699 @@ -1048,10 +1048,10 @@ Improved Documentation ``pytest.mark.MARKER_NAME.__call__`` (`#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 - `_) + user error when acessing ``pytest.config`` before the argument parsing. + (`#2653 `_) Trivial/Internal Changes @@ -1129,7 +1129,7 @@ Features from parent classes or modules. (`#2516 `_) -- Collection ignores local virtualenvs by default; `--collect-in-virtualenv` +- Collection ignores local virtualenvs by default; ``--collect-in-virtualenv`` overrides this behavior. (`#2518 `_) diff --git a/CITATION b/CITATION new file mode 100644 index 000000000..d4e9d8ec7 --- /dev/null +++ b/CITATION @@ -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}, +} diff --git a/HOWTORELEASE.rst b/HOWTORELEASE.rst index b5e852d3b..3d2d6a769 100644 --- a/HOWTORELEASE.rst +++ b/HOWTORELEASE.rst @@ -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 - -#. 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 -- + This will generate a commit with all the changes ready for pushing. #. Open a PR for this branch targeting ``master``. diff --git a/README.rst b/README.rst index 564ffff6c..97b21898e 100644 --- a/README.rst +++ b/README.rst @@ -3,6 +3,7 @@ :align: center :alt: pytest + ------ .. image:: https://img.shields.io/pypi/v/pytest.svg @@ -109,7 +110,7 @@ Consult the `Changelog `__ 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. diff --git a/changelog/2319.trivial.rst b/changelog/2319.trivial.rst new file mode 100644 index 000000000..a69ec1345 --- /dev/null +++ b/changelog/2319.trivial.rst @@ -0,0 +1 @@ +Remove obsolete ``__future__`` imports. diff --git a/changelog/3402.trivial.rst b/changelog/3402.trivial.rst new file mode 100644 index 000000000..c5f340c85 --- /dev/null +++ b/changelog/3402.trivial.rst @@ -0,0 +1 @@ +Add CITATION to provide information on how to formally cite pytest. diff --git a/changelog/3592.doc.rst b/changelog/3592.doc.rst new file mode 100644 index 000000000..1d4d35352 --- /dev/null +++ b/changelog/3592.doc.rst @@ -0,0 +1 @@ +Use ``smtp_connection`` instead of ``smtp`` in fixtures documentation to avoid possible confusion. diff --git a/changelog/3635.trivial.rst b/changelog/3635.trivial.rst new file mode 100644 index 000000000..5354d0df9 --- /dev/null +++ b/changelog/3635.trivial.rst @@ -0,0 +1 @@ +Replace broken type annotations with type comments. diff --git a/changelog/742.bugfix.rst b/changelog/742.bugfix.rst new file mode 100644 index 000000000..51dfce972 --- /dev/null +++ b/changelog/742.bugfix.rst @@ -0,0 +1 @@ +Invoke pytest using ``-mpytest`` so ``sys.path`` does not get polluted by packages installed in ``site-packages``. diff --git a/doc/en/announce/release-2.9.0.rst b/doc/en/announce/release-2.9.0.rst index 8d829996d..c079fdf6b 100644 --- a/doc/en/announce/release-2.9.0.rst +++ b/doc/en/announce/release-2.9.0.rst @@ -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 diff --git a/doc/en/development_guide.rst b/doc/en/development_guide.rst index 69e866943..649419316 100644 --- a/doc/en/development_guide.rst +++ b/doc/en/development_guide.rst @@ -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 `_. 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 ~~~~~~~~~~~~~~~~ diff --git a/doc/en/example/markers.rst b/doc/en/example/markers.rst index bf352bc81..1b4aa9279 100644 --- a/doc/en/example/markers.rst +++ b/doc/en/example/markers.rst @@ -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`: diff --git a/doc/en/fixture.rst b/doc/en/fixture.rst index aca0e456f..5e5cba452 100644 --- a/doc/en/fixture.rst +++ b/doc/en/fixture.rst @@ -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 = + smtp_connection = - 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 ` 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()`` is called and fails in the last +3. ``test_ehlo()`` 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 = + smtp_connection = - 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 = + smtp_connection = - 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 ` 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 (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 ` object:: @@ -567,11 +568,11 @@ through the special :py:class:`request ` 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 = + smtp_connection = - 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 = + smtp_connection = - 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 = + smtp_connection = - 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 ________________________ test_noop[mail.python.org] ________________________ - smtp = + smtp_connection = - 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 `_. + + .. _`autouse`: .. _`autouse fixtures`: diff --git a/doc/en/goodpractices.rst b/doc/en/goodpractices.rst index 2bbd9d0ae..d9c685299 100644 --- a/doc/en/goodpractices.rst +++ b/doc/en/goodpractices.rst @@ -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 diff --git a/doc/en/mark.rst b/doc/en/mark.rst index aa1210bb6..c99768ce0 100644 --- a/doc/en/mark.rst +++ b/doc/en/mark.rst @@ -52,8 +52,6 @@ should add ``--strict`` to ``addopts``: serial -.. `marker-iteration` - Marker revamp and iteration --------------------------- diff --git a/doc/en/reference.rst b/doc/en/reference.rst index fe9e87042..b65e15822 100644 --- a/doc/en/reference.rst +++ b/doc/en/reference.rst @@ -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 diff --git a/tasks/release.minor.rst b/scripts/release.minor.rst similarity index 100% rename from tasks/release.minor.rst rename to scripts/release.minor.rst diff --git a/tasks/release.patch.rst b/scripts/release.patch.rst similarity index 100% rename from tasks/release.patch.rst rename to scripts/release.patch.rst diff --git a/tasks/generate.py b/scripts/release.py similarity index 57% rename from tasks/generate.py rename to scripts/release.py index 89452aa5c..f187ba585 100644 --- a/tasks/generate.py +++ b/scripts/release.py @@ -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() diff --git a/src/_pytest/_code/source.py b/src/_pytest/_code/source.py index 711408f61..3b037b7d4 100644 --- a/src/_pytest/_code/source.py +++ b/src/_pytest/_code/source.py @@ -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 diff --git a/src/_pytest/compat.py b/src/_pytest/compat.py index cc7877fe0..3a29c688e 100644 --- a/src/_pytest/compat.py +++ b/src/_pytest/compat.py @@ -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 diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 7e7f3b6d2..421d124e9 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -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) diff --git a/src/_pytest/mark/structures.py b/src/_pytest/mark/structures.py index 3c161c4a6..9bd89c3c3 100644 --- a/src/_pytest/mark/structures.py +++ b/src/_pytest/mark/structures.py @@ -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): """ diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 08a02f6f5..5b42b81ee 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -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. diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 54fa56026..5b8305e77 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -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. diff --git a/tasks/__init__.py b/tasks/__init__.py deleted file mode 100644 index ea5b1293e..000000000 --- a/tasks/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -""" -Invoke tasks to help with pytest development and release process. -""" - -import invoke - -from . import generate - - -ns = invoke.Collection(generate) diff --git a/tasks/requirements.txt b/tasks/requirements.txt deleted file mode 100644 index db54e76e8..000000000 --- a/tasks/requirements.txt +++ /dev/null @@ -1,6 +0,0 @@ --e . -gitpython -invoke -towncrier -tox -wheel diff --git a/testing/code/test_source.py b/testing/code/test_source.py index 7982cfa35..995fabcf4 100644 --- a/testing/code/test_source.py +++ b/testing/code/test_source.py @@ -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 diff --git a/testing/code/test_source_multiline_block.py b/testing/code/test_source_multiline_block.py deleted file mode 100644 index 009bb87ae..000000000 --- a/testing/code/test_source_multiline_block.py +++ /dev/null @@ -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 diff --git a/testing/python/raises.py b/testing/python/raises.py index 99aeffdf2..732e1e82c 100644 --- a/testing/python/raises.py +++ b/testing/python/raises.py @@ -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(): diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index 20cc476d8..274b1ac53 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -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) diff --git a/testing/test_capture.py b/testing/test_capture.py index 54f0fbc44..5f5e1b98d 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -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 diff --git a/testing/test_pytester.py b/testing/test_pytester.py index 195f2c7f1..86dc35796 100644 --- a/testing/test_pytester.py +++ b/testing/test_pytester.py @@ -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 diff --git a/tox.ini b/tox.ini index 2a4f6566b..304cc973d 100644 --- a/tox.ini +++ b/tox.ini @@ -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