From b48f1d378b4a9f96425aac848eacf3ef1ee1cce5 Mon Sep 17 00:00:00 2001 From: Hugo Martins Date: Sat, 30 Jun 2018 02:01:49 +0100 Subject: [PATCH 01/26] Clarify examples in fixtures' documentation --- changelog/3592.doc.rst | 1 + doc/en/fixture.rst | 155 +++++++++++++++++++++-------------------- 2 files changed, 79 insertions(+), 77 deletions(-) create mode 100644 changelog/3592.doc.rst diff --git a/changelog/3592.doc.rst b/changelog/3592.doc.rst new file mode 100644 index 000000000..cbdf99666 --- /dev/null +++ b/changelog/3592.doc.rst @@ -0,0 +1 @@ +Clarify confusing examples in fixtures' documentation. \ No newline at end of file diff --git a/doc/en/fixture.rst b/doc/en/fixture.rst index e07d00eaa..c48caf0af 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 ============================ @@ -81,8 +81,8 @@ marked ``smtp`` fixture function. Running the test looks like this:: smtp = - 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,16 +91,16 @@ 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 line of the test function. @@ -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 @@ -217,8 +218,8 @@ inspect what is going on and can now run the tests:: smtp = - 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 ... @@ -323,9 +324,9 @@ the code after the *yield* statement serves as the teardown code: @pytest.fixture(scope="module") - def smtp(): + def smtp_connection(): smtp = smtplib.SMTP("smtp.gmail.com", 587, timeout=5) - yield smtp # provide the fixture value + yield smtp_connection # provide the fixture value print("teardown smtp") smtp.close() @@ -336,11 +337,11 @@ tests. Let's execute it:: $ pytest -s -q --tb=no - FFteardown smtp + FFteardown smtp_connection 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 @@ -358,13 +359,13 @@ Note that we can also seamlessly use the ``yield`` syntax with ``with`` statemen @pytest.fixture(scope="module") - def smtp(): + def smtp_connection(): with smtplib.SMTP("smtp.gmail.com", 587, timeout=5) as smtp: - yield smtp # provide the fixture value + 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 @@ -374,7 +375,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 @@ -384,7 +385,7 @@ Here's the ``smtp`` fixture changed to use ``addfinalizer`` for cleanup: @pytest.fixture(scope="module") - def smtp(request): + def smtp_connection(request): smtp = smtplib.SMTP("smtp.gmail.com", 587, timeout=5) def fin(): @@ -425,7 +426,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 @@ -433,10 +434,10 @@ 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 + yield smtp_connection print ("finalizing %s (%s)" % (smtp, server)) smtp.close() @@ -456,7 +457,7 @@ server URL in its module namespace:: smtpserver = "mail.python.org" # will be read by smtp fixture - def test_showhelo(smtp): + def test_showhelo(smtp_connection): assert 0, smtp.helo() Running it:: @@ -472,7 +473,7 @@ Running it:: ------------------------- 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`: @@ -541,7 +542,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:: @@ -551,9 +552,9 @@ through the special :py:class:`request ` object:: @pytest.fixture(scope="module", params=["smtp.gmail.com", "mail.python.org"]) - def smtp(request): + def smtp_connection(request): smtp = smtplib.SMTP(request.param, 587, timeout=5) - yield smtp + yield smtp_connection print ("finalizing %s" % smtp) smtp.close() @@ -568,10 +569,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 @@ -580,10 +581,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 @@ -591,10 +592,10 @@ So let's just do another run:: test_module.py:11: AssertionError ________________________ test_ehlo[mail.python.org] ________________________ - smtp = + ssmtp_connectionmtp = - 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' @@ -604,10 +605,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 @@ -618,7 +619,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. @@ -730,25 +731,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 ============================ @@ -762,14 +763,14 @@ Here we declare an ``app`` fixture which receives the previously defined ========================= 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. From f7c929c932c4a73934ee1ea3cb3fc2a5e2e3e852 Mon Sep 17 00:00:00 2001 From: Hugo Martins Date: Sat, 30 Jun 2018 14:34:19 +0100 Subject: [PATCH 02/26] Fix linting errors in docs/fixtures.rst --- changelog/3592.doc.rst | 2 +- doc/en/fixture.rst | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/changelog/3592.doc.rst b/changelog/3592.doc.rst index cbdf99666..38fc4a944 100644 --- a/changelog/3592.doc.rst +++ b/changelog/3592.doc.rst @@ -1 +1 @@ -Clarify confusing examples in fixtures' documentation. \ No newline at end of file +Clarify confusing examples in fixtures' documentation. diff --git a/doc/en/fixture.rst b/doc/en/fixture.rst index c48caf0af..e5c5d0b45 100644 --- a/doc/en/fixture.rst +++ b/doc/en/fixture.rst @@ -167,7 +167,7 @@ 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_connection`` fixture function to only be invoked +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_connection`` fixture instance, thus saving time. @@ -184,9 +184,9 @@ access the fixture function:: def smtp_connection(): return smtplib.SMTP("smtp.gmail.com", 587, timeout=5) -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 +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 @@ -240,9 +240,9 @@ 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_connection`` object was passed into the +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 +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_connection`` @@ -364,7 +364,7 @@ Note that we can also seamlessly use the ``yield`` syntax with ``with`` statemen yield smtp_connection # provide the fixture value -The ``smtp_connection`` connection will be closed after the test finished +The ``smtp_connection`` connection will be closed after the test finished execution because the ``smtp_connection`` object automatically closes when the ``with`` statement ends. @@ -765,11 +765,11 @@ Here we declare an ``app`` fixture which receives the previously defined 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_connection`` +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_connection`` fixture. The example would still work if +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 From 8232fd1a2d1534b7a99479f12c37ad23a00e0716 Mon Sep 17 00:00:00 2001 From: Hugo Martins Date: Tue, 3 Jul 2018 23:24:59 +0100 Subject: [PATCH 03/26] Fix remaining "smtp" references --- doc/en/fixture.rst | 162 +++++++++++++++++++++------------------------ 1 file changed, 76 insertions(+), 86 deletions(-) diff --git a/doc/en/fixture.rst b/doc/en/fixture.rst index e5c5d0b45..070989c47 100644 --- a/doc/en/fixture.rst +++ b/doc/en/fixture.rst @@ -73,20 +73,20 @@ marked ``smtp_connection`` fixture function. Running the test looks like this:: platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 1 item - + test_smtpsimple.py F [100%] - + ================================= FAILURES ================================= ________________________________ test_ehlo _________________________________ - - smtp = - + + smtp_connection = + def test_ehlo(smtp_connection): response, msg = smtp_connection.ehlo() assert response == 250 > assert 0 # for demo purposes E assert 0 - + test_smtpsimple.py:11: AssertionError ========================= 1 failed in 0.12 seconds ========================= @@ -102,7 +102,7 @@ the exact protocol used by ``pytest`` to call the test function this way: 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 @@ -210,32 +210,32 @@ inspect what is going on and can now run the tests:: platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 2 items - + test_module.py FF [100%] - + ================================= FAILURES ================================= ________________________________ test_ehlo _________________________________ - - smtp = - + + smtp_connection = + 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 E assert 0 - + test_module.py:6: AssertionError ________________________________ test_noop _________________________________ - + smtp_connection = - + def test_noop(smtp_connection): response, msg = smtp_connection.noop() assert response == 250 > assert 0 # for demo purposes E assert 0 - + test_module.py:11: AssertionError ========================= 2 failed in 0.12 seconds ========================= @@ -325,10 +325,10 @@ the code after the *yield* statement serves as the teardown code: @pytest.fixture(scope="module") def smtp_connection(): - smtp = smtplib.SMTP("smtp.gmail.com", 587, timeout=5) + 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 @@ -338,7 +338,7 @@ Let's execute it:: $ pytest -s -q --tb=no FFteardown smtp_connection - + 2 failed in 0.12 seconds We see that the ``smtp_connection`` instance is finalized after the two @@ -360,7 +360,7 @@ Note that we can also seamlessly use the ``yield`` syntax with ``with`` statemen @pytest.fixture(scope="module") def smtp_connection(): - with smtplib.SMTP("smtp.gmail.com", 587, timeout=5) as smtp: + with smtplib.SMTP("smtp.gmail.com", 587, timeout=5) as smtp_connection: yield smtp_connection # provide the fixture value @@ -386,14 +386,14 @@ Here's the ``smtp_connection`` fixture changed to use ``addfinalizer`` for clean @pytest.fixture(scope="module") def smtp_connection(request): - smtp = smtplib.SMTP("smtp.gmail.com", 587, timeout=5) + smtp_connection = smtplib.SMTP("smtp.gmail.com", 587, timeout=5) def fin(): - 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 @@ -436,10 +436,10 @@ read an optional server URL from the test module which uses our fixture:: @pytest.fixture(scope="module") def smtp_connection(request): server = getattr(request.module, "smtpserver", "smtp.gmail.com") - smtp = smtplib.SMTP(server, 587, timeout=5) + smtp_connection = smtplib.SMTP(server, 587, timeout=5) yield smtp_connection - print ("finalizing %s (%s)" % (smtp, server)) - smtp.close() + 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 @@ -447,7 +447,7 @@ again, nothing much has changed:: $ pytest -s -q --tb=no FFfinalizing (smtp.gmail.com) - + 2 failed in 0.12 seconds Let's quickly create another test module that actually sets the @@ -458,7 +458,7 @@ server URL in its module namespace:: smtpserver = "mail.python.org" # will be read by smtp fixture def test_showhelo(smtp_connection): - assert 0, smtp.helo() + assert 0, smtp_connection.helo() Running it:: @@ -467,9 +467,8 @@ Running it:: ================================= FAILURES ================================= ______________________________ test_showhelo _______________________________ test_anothersmtp.py:5: in test_showhelo - assert 0, smtp.helo() - E AssertionError: (250, b'mail.python.org') - E assert 0 + assert 0, smtp_connection.helo() + E NameError: name 'smtp_connection' is not defined ------------------------- Captured stdout teardown ------------------------- finalizing (mail.python.org) @@ -553,10 +552,10 @@ through the special :py:class:`request ` object:: @pytest.fixture(scope="module", params=["smtp.gmail.com", "mail.python.org"]) def smtp_connection(request): - smtp = smtplib.SMTP(request.param, 587, timeout=5) + smtp_connection = smtplib.SMTP(request.param, 587, timeout=5) yield smtp_connection print ("finalizing %s" % smtp) - smtp.close() + smtp_connection.close() The main change is the declaration of ``params`` with :py:func:`@pytest.fixture <_pytest.python.fixture>`, a list of values @@ -568,52 +567,43 @@ So let's just do another run:: FFFF [100%] ================================= FAILURES ================================= ________________________ test_ehlo[smtp.gmail.com] _________________________ - - smtp_connection = - + + smtp_connection = + 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 - E assert 0 - - test_module.py:6: AssertionError + > response, msg = smtp_connection.ehlo() + E AttributeError: 'function' object has no attribute 'ehlo' + + test_module.py:3: AttributeError ________________________ test_noop[smtp.gmail.com] _________________________ - - smtp_connection = - + + smtp_connection = + def test_noop(smtp_connection): - response, msg = smtp_connection.noop() - assert response == 250 - > assert 0 # for demo purposes - E assert 0 - - test_module.py:11: AssertionError + > response, msg = smtp_connection.noop() + E AttributeError: 'function' object has no attribute 'noop' + + test_module.py:9: AttributeError ________________________ test_ehlo[mail.python.org] ________________________ - - ssmtp_connectionmtp = - + + smtp_connection = + 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' - - test_module.py:5: AssertionError + > response, msg = smtp_connection.ehlo() + E AttributeError: 'function' object has no attribute 'ehlo' + + test_module.py:3: AttributeError -------------------------- Captured stdout setup --------------------------- finalizing ________________________ test_noop[mail.python.org] ________________________ - - smtp_connection = - + + smtp_connection = + def test_noop(smtp_connection): - response, msg = smtp_connection.noop() - assert response == 250 - > assert 0 # for demo purposes - E assert 0 - - test_module.py:11: AssertionError + > response, msg = smtp_connection.noop() + E AttributeError: 'function' object has no attribute 'noop' + + test_module.py:9: AttributeError ------------------------- Captured stdout teardown ------------------------- finalizing 4 failed in 0.12 seconds @@ -684,7 +674,7 @@ Running the above tests results in the following test IDs being used:: - + ======================= no tests ran in 0.12 seconds ======================= .. _`fixture-parametrize-marks`: @@ -714,11 +704,11 @@ Running this test will *skip* the invocation of ``data_set`` with value ``2``:: cachedir: .pytest_cache rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 3 items - + test_fixture_marks.py::test_data[0] PASSED [ 33%] test_fixture_marks.py::test_data[1] PASSED [ 66%] test_fixture_marks.py::test_data[2] SKIPPED [100%] - + =================== 2 passed, 1 skipped in 0.12 seconds ==================== .. _`interdependent fixtures`: @@ -757,10 +747,10 @@ Here we declare an ``app`` fixture which receives the previously defined cachedir: .pytest_cache 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_connection`` the test will run twice with two @@ -826,26 +816,26 @@ Let's run the tests in verbose mode and with looking at the print-output:: cachedir: .pytest_cache rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 8 items - + test_module.py::test_0[1] SETUP otherarg 1 RUN test0 with otherarg 1 PASSED TEARDOWN otherarg 1 - + test_module.py::test_0[2] SETUP otherarg 2 RUN test0 with otherarg 2 PASSED TEARDOWN otherarg 2 - + test_module.py::test_1[mod1] SETUP modarg mod1 RUN test1 with modarg mod1 PASSED test_module.py::test_2[mod1-1] SETUP otherarg 1 RUN test2 with otherarg 1 and modarg mod1 PASSED TEARDOWN otherarg 1 - + test_module.py::test_2[mod1-2] SETUP otherarg 2 RUN test2 with otherarg 2 and modarg mod1 PASSED TEARDOWN otherarg 2 - + test_module.py::test_1[mod2] TEARDOWN modarg mod1 SETUP modarg mod2 RUN test1 with modarg mod2 @@ -853,13 +843,13 @@ Let's run the tests in verbose mode and with looking at the print-output:: test_module.py::test_2[mod2-1] SETUP otherarg 1 RUN test2 with otherarg 1 and modarg mod2 PASSED TEARDOWN otherarg 1 - + test_module.py::test_2[mod2-2] SETUP otherarg 2 RUN test2 with otherarg 2 and modarg mod2 PASSED TEARDOWN otherarg 2 TEARDOWN modarg mod2 - - + + ========================= 8 passed in 0.12 seconds ========================= You can see that the parametrized module-scoped ``modarg`` resource caused an From 63b25304c3727f37874d0923db4603a08ba7b1b2 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 3 Jul 2018 22:20:29 -0300 Subject: [PATCH 04/26] Run pre-commit fixers --- doc/en/fixture.rst | 80 +++++++++++++++++++++++----------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/doc/en/fixture.rst b/doc/en/fixture.rst index 070989c47..18e431196 100644 --- a/doc/en/fixture.rst +++ b/doc/en/fixture.rst @@ -73,20 +73,20 @@ marked ``smtp_connection`` fixture function. Running the test looks like this:: platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 1 item - + test_smtpsimple.py F [100%] - + ================================= FAILURES ================================= ________________________________ test_ehlo _________________________________ - + smtp_connection = - + def test_ehlo(smtp_connection): response, msg = smtp_connection.ehlo() assert response == 250 > assert 0 # for demo purposes E assert 0 - + test_smtpsimple.py:11: AssertionError ========================= 1 failed in 0.12 seconds ========================= @@ -210,32 +210,32 @@ inspect what is going on and can now run the tests:: platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y rootdir: $REGENDOC_TMPDIR, inifile: collected 2 items - + test_module.py FF [100%] - + ================================= FAILURES ================================= ________________________________ test_ehlo _________________________________ - + smtp_connection = - + 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 E assert 0 - + test_module.py:6: AssertionError ________________________________ test_noop _________________________________ - + smtp_connection = - + def test_noop(smtp_connection): response, msg = smtp_connection.noop() assert response == 250 > assert 0 # for demo purposes E assert 0 - + test_module.py:11: AssertionError ========================= 2 failed in 0.12 seconds ========================= @@ -338,7 +338,7 @@ Let's execute it:: $ pytest -s -q --tb=no FFteardown smtp_connection - + 2 failed in 0.12 seconds We see that the ``smtp_connection`` instance is finalized after the two @@ -447,7 +447,7 @@ again, nothing much has changed:: $ pytest -s -q --tb=no FFfinalizing (smtp.gmail.com) - + 2 failed in 0.12 seconds Let's quickly create another test module that actually sets the @@ -567,42 +567,42 @@ So let's just do another run:: FFFF [100%] ================================= FAILURES ================================= ________________________ test_ehlo[smtp.gmail.com] _________________________ - + smtp_connection = - + def test_ehlo(smtp_connection): > response, msg = smtp_connection.ehlo() E AttributeError: 'function' object has no attribute 'ehlo' - + test_module.py:3: AttributeError ________________________ test_noop[smtp.gmail.com] _________________________ - + smtp_connection = - + def test_noop(smtp_connection): > response, msg = smtp_connection.noop() E AttributeError: 'function' object has no attribute 'noop' - + test_module.py:9: AttributeError ________________________ test_ehlo[mail.python.org] ________________________ - + smtp_connection = - + def test_ehlo(smtp_connection): > response, msg = smtp_connection.ehlo() E AttributeError: 'function' object has no attribute 'ehlo' - + test_module.py:3: AttributeError -------------------------- Captured stdout setup --------------------------- finalizing ________________________ test_noop[mail.python.org] ________________________ - + smtp_connection = - + def test_noop(smtp_connection): > response, msg = smtp_connection.noop() E AttributeError: 'function' object has no attribute 'noop' - + test_module.py:9: AttributeError ------------------------- Captured stdout teardown ------------------------- finalizing @@ -674,7 +674,7 @@ Running the above tests results in the following test IDs being used:: - + ======================= no tests ran in 0.12 seconds ======================= .. _`fixture-parametrize-marks`: @@ -704,11 +704,11 @@ Running this test will *skip* the invocation of ``data_set`` with value ``2``:: cachedir: .pytest_cache rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 3 items - + test_fixture_marks.py::test_data[0] PASSED [ 33%] test_fixture_marks.py::test_data[1] PASSED [ 66%] test_fixture_marks.py::test_data[2] SKIPPED [100%] - + =================== 2 passed, 1 skipped in 0.12 seconds ==================== .. _`interdependent fixtures`: @@ -747,10 +747,10 @@ Here we declare an ``app`` fixture which receives the previously defined cachedir: .pytest_cache rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 2 items - + 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_connection`` the test will run twice with two @@ -816,26 +816,26 @@ Let's run the tests in verbose mode and with looking at the print-output:: cachedir: .pytest_cache rootdir: $REGENDOC_TMPDIR, inifile: collecting ... collected 8 items - + test_module.py::test_0[1] SETUP otherarg 1 RUN test0 with otherarg 1 PASSED TEARDOWN otherarg 1 - + test_module.py::test_0[2] SETUP otherarg 2 RUN test0 with otherarg 2 PASSED TEARDOWN otherarg 2 - + test_module.py::test_1[mod1] SETUP modarg mod1 RUN test1 with modarg mod1 PASSED test_module.py::test_2[mod1-1] SETUP otherarg 1 RUN test2 with otherarg 1 and modarg mod1 PASSED TEARDOWN otherarg 1 - + test_module.py::test_2[mod1-2] SETUP otherarg 2 RUN test2 with otherarg 2 and modarg mod1 PASSED TEARDOWN otherarg 2 - + test_module.py::test_1[mod2] TEARDOWN modarg mod1 SETUP modarg mod2 RUN test1 with modarg mod2 @@ -843,13 +843,13 @@ Let's run the tests in verbose mode and with looking at the print-output:: test_module.py::test_2[mod2-1] SETUP otherarg 1 RUN test2 with otherarg 1 and modarg mod2 PASSED TEARDOWN otherarg 1 - + test_module.py::test_2[mod2-2] SETUP otherarg 2 RUN test2 with otherarg 2 and modarg mod2 PASSED TEARDOWN otherarg 2 TEARDOWN modarg mod2 - - + + ========================= 8 passed in 0.12 seconds ========================= You can see that the parametrized module-scoped ``modarg`` resource caused an From 50f030d2330280abc31824075660a9d5d0aaa808 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Wed, 4 Jul 2018 15:16:34 -0700 Subject: [PATCH 05/26] Correct code blocks in docs --- .pre-commit-config.yaml | 5 +++++ CHANGELOG.rst | 18 +++++++++--------- doc/en/announce/release-2.9.0.rst | 2 +- doc/en/example/markers.rst | 4 ++-- doc/en/goodpractices.rst | 2 +- doc/en/mark.rst | 2 -- 6 files changed, 18 insertions(+), 15 deletions(-) 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/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/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/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/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 --------------------------- From d7b722e2ae6dbf4b10bbba14c3a2dc5c4f2738b4 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 6 Jul 2018 20:55:42 -0300 Subject: [PATCH 06/26] Add reference docs for pytest.mark.usefixtures --- doc/en/reference.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/doc/en/reference.rst b/doc/en/reference.rst index fe9e87042..cdae37f95 100644 --- a/doc/en/reference.rst +++ b/doc/en/reference.rst @@ -161,6 +161,20 @@ 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. + +.. py:function:: pytest.mark.usefixtures(*names) + + :param args: the names of the fixture to use as strings + + .. _`pytest.mark.xfail ref`: pytest.mark.xfail From 18b2fc11adf4b79d860ba91b73678a22c0c53083 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 6 Jul 2018 20:57:30 -0300 Subject: [PATCH 07/26] Dummy change --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index 564ffff6c..c0b2d658f 100644 --- a/README.rst +++ b/README.rst @@ -3,6 +3,7 @@ :align: center :alt: pytest + ------ .. image:: https://img.shields.io/pypi/v/pytest.svg From f359b50fe5e8c232c34d7400b00d4763ce2f43a4 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 6 Jul 2018 21:03:27 -0300 Subject: [PATCH 08/26] Adjust copyright in README --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index c0b2d658f..97b21898e 100644 --- a/README.rst +++ b/README.rst @@ -110,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. From d26a596072c4b83340904512d5d34a107daf13eb Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 7 Jul 2018 10:01:10 -0300 Subject: [PATCH 09/26] Add a warning about usefixtures mark not working in fixtures Fix #1014 --- doc/en/fixture.rst | 18 +++++++++++++++++- doc/en/reference.rst | 7 ++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/doc/en/fixture.rst b/doc/en/fixture.rst index e07d00eaa..301bcee4c 100644 --- a/doc/en/fixture.rst +++ b/doc/en/fixture.rst @@ -943,7 +943,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 @@ -953,6 +953,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/reference.rst b/doc/en/reference.rst index cdae37f95..b65e15822 100644 --- a/doc/en/reference.rst +++ b/doc/en/reference.rst @@ -170,9 +170,14 @@ pytest.mark.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 + :param args: the names of the fixture to use, as strings .. _`pytest.mark.xfail ref`: From 49e82a4be8efe8a38b9208724e3681db413f22be Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 7 Jul 2018 12:12:07 -0300 Subject: [PATCH 10/26] Skip deploy stage entirely unless we have a tag Borrowed from https://github.com/tox-dev/tox/pull/877 --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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: From 42bbb4fa8a43b913ce267454f417605f9744a95b Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sat, 7 Jul 2018 17:18:44 -0700 Subject: [PATCH 11/26] Use -mpytest when invoking pytest in pytester --- changelog/742.bugfix.rst | 1 + src/_pytest/pytester.py | 9 +-------- testing/test_pytester.py | 5 +++++ 3 files changed, 7 insertions(+), 8 deletions(-) create mode 100644 changelog/742.bugfix.rst 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/src/_pytest/pytester.py b/src/_pytest/pytester.py index ce1c8ea1c..7c9c09b1c 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 -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" ] @@ -1029,9 +1024,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/testing/test_pytester.py b/testing/test_pytester.py index 195f2c7f1..9776a8659 100644 --- a/testing/test_pytester.py +++ b/testing/test_pytester.py @@ -394,3 +394,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 From 4ae93a7a078fc92d910b632a46a86cb779b3e2b7 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 8 Jul 2018 08:35:53 -0700 Subject: [PATCH 12/26] Remove obsolete __future__ imports --- changelog/2319.trivial.rst | 1 + src/_pytest/_code/source.py | 13 +++---------- testing/python/raises.py | 3 +-- testing/test_assertrewrite.py | 6 +++--- testing/test_capture.py | 1 - 5 files changed, 8 insertions(+), 16 deletions(-) create mode 100644 changelog/2319.trivial.rst 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/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/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..2640beb63 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) 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 From af0059079cb35372fe9fceeeb6bb183795ce3b7e Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 8 Jul 2018 17:05:01 -0700 Subject: [PATCH 13/26] Remove unused fix-lint tox environment --- testing/code/test_source.py | 16 ++++++++++++ testing/code/test_source_multiline_block.py | 28 --------------------- tox.ini | 8 ------ 3 files changed, 16 insertions(+), 36 deletions(-) delete mode 100644 testing/code/test_source_multiline_block.py 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/tox.ini b/tox.ini index adc4e9746..6e5d7ca68 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 = From 61301d934e5e08620c5f7be82384bc07d3d51ea9 Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 8 Jul 2018 16:57:18 -0700 Subject: [PATCH 14/26] Remove some extraneous `# noqa` comments This was partially automated with https://github.com/asottile/yesqa _with a few caveats_: - it was run under python2 (chosen arbitrarily, when run under python3 other things were changed) - I used `git checkout -p` to revert the removal of `noqa` comments from `cmp()` lines. --- src/_pytest/compat.py | 4 ++-- src/_pytest/config/__init__.py | 2 +- testing/test_assertrewrite.py | 2 +- testing/test_pytester.py | 5 +---- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/_pytest/compat.py b/src/_pytest/compat.py index 3ca27fe60..dab09c9d5 100644 --- a/src/_pytest/compat.py +++ b/src/_pytest/compat.py @@ -41,8 +41,8 @@ PY36 = sys.version_info[:2] >= (3, 6) MODULE_NOT_FOUND_ERROR = "ModuleNotFoundError" if PY36 else "ImportError" 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/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index 2640beb63..274b1ac53 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -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_pytester.py b/testing/test_pytester.py index 9776a8659..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) From 4dfe2eee945db025aad942fef71b98b4f789c349 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 11 Jul 2018 20:24:39 -0300 Subject: [PATCH 15/26] Fix finalize call --- doc/en/fixture.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/en/fixture.rst b/doc/en/fixture.rst index 18e431196..a02ab8215 100644 --- a/doc/en/fixture.rst +++ b/doc/en/fixture.rst @@ -554,7 +554,7 @@ through the special :py:class:`request ` object:: def smtp_connection(request): smtp_connection = smtplib.SMTP(request.param, 587, timeout=5) yield smtp_connection - print ("finalizing %s" % smtp) + print("finalizing %s" % smtp_connection) smtp_connection.close() The main change is the declaration of ``params`` with From 2c4759ce5761b3fbc32f1d98a808b655ae964f2d Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 11 Jul 2018 23:36:25 +0000 Subject: [PATCH 16/26] Run regendocs --- doc/en/builtin.rst | 2 +- doc/en/example/reportingdemo.rst | 100 ++++++++++++++----------------- doc/en/example/simple.rst | 2 +- doc/en/fixture.rst | 46 ++++++++------ 4 files changed, 74 insertions(+), 76 deletions(-) diff --git a/doc/en/builtin.rst b/doc/en/builtin.rst index c2d23469b..e52151a1b 100644 --- a/doc/en/builtin.rst +++ b/doc/en/builtin.rst @@ -90,7 +90,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a monkeypatch.setitem(mapping, name, value) monkeypatch.delitem(obj, name, raising=True) monkeypatch.setenv(name, value, prepend=False) - monkeypatch.delenv(name, value, raising=True) + monkeypatch.delenv(name, raising=True) monkeypatch.syspath_prepend(path) monkeypatch.chdir(path) diff --git a/doc/en/example/reportingdemo.rst b/doc/en/example/reportingdemo.rst index 4691b128b..a7cc81694 100644 --- a/doc/en/example/reportingdemo.rst +++ b/doc/en/example/reportingdemo.rst @@ -32,7 +32,6 @@ get on the terminal - we are working on that):: self = def test_simple(self): - def f(): return 42 @@ -44,7 +43,7 @@ get on the terminal - we are working on that):: E + where 42 = .f at 0xdeadbeef>() E + and 43 = .g at 0xdeadbeef>() - failure_demo.py:37: AssertionError + failure_demo.py:35: AssertionError ____________________ TestFailing.test_simple_multiline _____________________ self = @@ -52,7 +51,7 @@ get on the terminal - we are working on that):: def test_simple_multiline(self): > otherfunc_multi(42, 6 * 9) - failure_demo.py:40: + failure_demo.py:38: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ a = 42, b = 54 @@ -67,7 +66,6 @@ get on the terminal - we are working on that):: self = def test_not(self): - def f(): return 42 @@ -75,7 +73,7 @@ get on the terminal - we are working on that):: E assert not 42 E + where 42 = .f at 0xdeadbeef>() - failure_demo.py:47: AssertionError + failure_demo.py:44: AssertionError _________________ TestSpecialisedExplanations.test_eq_text _________________ self = @@ -86,7 +84,7 @@ get on the terminal - we are working on that):: E - spam E + eggs - failure_demo.py:53: AssertionError + failure_demo.py:49: AssertionError _____________ TestSpecialisedExplanations.test_eq_similar_text _____________ self = @@ -99,7 +97,7 @@ get on the terminal - we are working on that):: E + foo 2 bar E ? ^ - failure_demo.py:56: AssertionError + failure_demo.py:52: AssertionError ____________ TestSpecialisedExplanations.test_eq_multiline_text ____________ self = @@ -112,7 +110,7 @@ get on the terminal - we are working on that):: E + eggs E bar - failure_demo.py:59: AssertionError + failure_demo.py:55: AssertionError ______________ TestSpecialisedExplanations.test_eq_long_text _______________ self = @@ -129,7 +127,7 @@ get on the terminal - we are working on that):: E + 1111111111b222222222 E ? ^ - failure_demo.py:64: AssertionError + failure_demo.py:60: AssertionError _________ TestSpecialisedExplanations.test_eq_long_text_multiline __________ self = @@ -149,7 +147,7 @@ get on the terminal - we are working on that):: E E ...Full output truncated (7 lines hidden), use '-vv' to show - failure_demo.py:69: AssertionError + failure_demo.py:65: AssertionError _________________ TestSpecialisedExplanations.test_eq_list _________________ self = @@ -160,7 +158,7 @@ get on the terminal - we are working on that):: E At index 2 diff: 2 != 3 E Use -v to get the full diff - failure_demo.py:72: AssertionError + failure_demo.py:68: AssertionError ______________ TestSpecialisedExplanations.test_eq_list_long _______________ self = @@ -173,7 +171,7 @@ get on the terminal - we are working on that):: E At index 100 diff: 1 != 2 E Use -v to get the full diff - failure_demo.py:77: AssertionError + failure_demo.py:73: AssertionError _________________ TestSpecialisedExplanations.test_eq_dict _________________ self = @@ -191,7 +189,7 @@ get on the terminal - we are working on that):: E E ...Full output truncated (2 lines hidden), use '-vv' to show - failure_demo.py:80: AssertionError + failure_demo.py:76: AssertionError _________________ TestSpecialisedExplanations.test_eq_set __________________ self = @@ -209,7 +207,7 @@ get on the terminal - we are working on that):: E E ...Full output truncated (2 lines hidden), use '-vv' to show - failure_demo.py:83: AssertionError + failure_demo.py:79: AssertionError _____________ TestSpecialisedExplanations.test_eq_longer_list ______________ self = @@ -220,7 +218,7 @@ get on the terminal - we are working on that):: E Right contains more items, first extra item: 3 E Use -v to get the full diff - failure_demo.py:86: AssertionError + failure_demo.py:82: AssertionError _________________ TestSpecialisedExplanations.test_in_list _________________ self = @@ -229,7 +227,7 @@ get on the terminal - we are working on that):: > assert 1 in [0, 2, 3, 4, 5] E assert 1 in [0, 2, 3, 4, 5] - failure_demo.py:89: AssertionError + failure_demo.py:85: AssertionError __________ TestSpecialisedExplanations.test_not_in_text_multiline __________ self = @@ -248,7 +246,7 @@ get on the terminal - we are working on that):: E E ...Full output truncated (2 lines hidden), use '-vv' to show - failure_demo.py:93: AssertionError + failure_demo.py:89: AssertionError ___________ TestSpecialisedExplanations.test_not_in_text_single ____________ self = @@ -261,7 +259,7 @@ get on the terminal - we are working on that):: E single foo line E ? +++ - failure_demo.py:97: AssertionError + failure_demo.py:93: AssertionError _________ TestSpecialisedExplanations.test_not_in_text_single_long _________ self = @@ -274,7 +272,7 @@ get on the terminal - we are working on that):: E head head foo tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail E ? +++ - failure_demo.py:101: AssertionError + failure_demo.py:97: AssertionError ______ TestSpecialisedExplanations.test_not_in_text_single_long_term _______ self = @@ -287,11 +285,10 @@ get on the terminal - we are working on that):: E head head fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffftail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail E ? ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ - failure_demo.py:105: AssertionError + failure_demo.py:101: AssertionError ______________________________ test_attribute ______________________________ def test_attribute(): - class Foo(object): b = 1 @@ -300,11 +297,10 @@ get on the terminal - we are working on that):: E assert 1 == 2 E + where 1 = .Foo object at 0xdeadbeef>.b - failure_demo.py:114: AssertionError + failure_demo.py:109: AssertionError _________________________ test_attribute_instance __________________________ def test_attribute_instance(): - class Foo(object): b = 1 @@ -313,13 +309,11 @@ get on the terminal - we are working on that):: E + where 1 = .Foo object at 0xdeadbeef>.b E + where .Foo object at 0xdeadbeef> = .Foo'>() - failure_demo.py:122: AssertionError + failure_demo.py:116: AssertionError __________________________ test_attribute_failure __________________________ def test_attribute_failure(): - class Foo(object): - def _get_b(self): raise Exception("Failed to get attrib") @@ -328,7 +322,7 @@ get on the terminal - we are working on that):: i = Foo() > assert i.b == 2 - failure_demo.py:135: + failure_demo.py:127: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = .Foo object at 0xdeadbeef> @@ -337,11 +331,10 @@ get on the terminal - we are working on that):: > raise Exception("Failed to get attrib") E Exception: Failed to get attrib - failure_demo.py:130: Exception + failure_demo.py:122: Exception _________________________ test_attribute_multiple __________________________ def test_attribute_multiple(): - class Foo(object): b = 1 @@ -355,7 +348,7 @@ get on the terminal - we are working on that):: E + and 2 = .Bar object at 0xdeadbeef>.b E + where .Bar object at 0xdeadbeef> = .Bar'>() - failure_demo.py:146: AssertionError + failure_demo.py:137: AssertionError __________________________ TestRaises.test_raises __________________________ self = @@ -364,13 +357,13 @@ get on the terminal - we are working on that):: s = "qwe" # NOQA > raises(TypeError, "int(s)") - failure_demo.py:157: + failure_demo.py:147: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ > int(s) E ValueError: invalid literal for int() with base 10: 'qwe' - <0-codegen $PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/python_api.py:634>:1: ValueError + <0-codegen $PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/python_api.py:635>:1: ValueError ______________________ TestRaises.test_raises_doesnt _______________________ self = @@ -379,7 +372,7 @@ get on the terminal - we are working on that):: > raises(IOError, "int('3')") E Failed: DID NOT RAISE - failure_demo.py:160: Failed + failure_demo.py:150: Failed __________________________ TestRaises.test_raise ___________________________ self = @@ -388,7 +381,7 @@ get on the terminal - we are working on that):: > raise ValueError("demo error") E ValueError: demo error - failure_demo.py:163: ValueError + failure_demo.py:153: ValueError ________________________ TestRaises.test_tupleerror ________________________ self = @@ -397,7 +390,7 @@ get on the terminal - we are working on that):: > a, b = [1] # NOQA E ValueError: not enough values to unpack (expected 2, got 1) - failure_demo.py:166: ValueError + failure_demo.py:156: ValueError ______ TestRaises.test_reinterpret_fails_with_print_for_the_fun_of_it ______ self = @@ -408,7 +401,7 @@ get on the terminal - we are working on that):: > a, b = items.pop() E TypeError: 'int' object is not iterable - failure_demo.py:171: TypeError + failure_demo.py:161: TypeError --------------------------- Captured stdout call --------------------------- items is [1, 2, 3] ________________________ TestRaises.test_some_error ________________________ @@ -419,7 +412,7 @@ get on the terminal - we are working on that):: > if namenotexi: # NOQA E NameError: name 'namenotexi' is not defined - failure_demo.py:174: NameError + failure_demo.py:164: NameError ____________________ test_dynamic_compile_shows_nicely _____________________ def test_dynamic_compile_shows_nicely(): @@ -434,20 +427,19 @@ get on the terminal - we are working on that):: sys.modules[name] = module > module.foo() - failure_demo.py:192: + failure_demo.py:182: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ def foo(): > assert 1 == 0 E AssertionError - <2-codegen 'abc-123' $REGENDOC_TMPDIR/assertion/failure_demo.py:189>:2: AssertionError + <2-codegen 'abc-123' $REGENDOC_TMPDIR/assertion/failure_demo.py:179>:2: AssertionError ____________________ TestMoreErrors.test_complex_error _____________________ self = def test_complex_error(self): - def f(): return 44 @@ -456,7 +448,7 @@ get on the terminal - we are working on that):: > somefunc(f(), g()) - failure_demo.py:205: + failure_demo.py:193: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ failure_demo.py:11: in somefunc otherfunc(x, y) @@ -478,7 +470,7 @@ get on the terminal - we are working on that):: > a, b = items E ValueError: not enough values to unpack (expected 2, got 0) - failure_demo.py:209: ValueError + failure_demo.py:197: ValueError ____________________ TestMoreErrors.test_z2_type_error _____________________ self = @@ -488,7 +480,7 @@ get on the terminal - we are working on that):: > a, b = items E TypeError: 'int' object is not iterable - failure_demo.py:213: TypeError + failure_demo.py:201: TypeError ______________________ TestMoreErrors.test_startswith ______________________ self = @@ -501,13 +493,12 @@ get on the terminal - we are working on that):: E + where False = ('456') E + where = '123'.startswith - failure_demo.py:218: AssertionError + failure_demo.py:206: AssertionError __________________ TestMoreErrors.test_startswith_nested ___________________ self = def test_startswith_nested(self): - def f(): return "123" @@ -521,7 +512,7 @@ get on the terminal - we are working on that):: E + where '123' = .f at 0xdeadbeef>() E + and '456' = .g at 0xdeadbeef>() - failure_demo.py:228: AssertionError + failure_demo.py:215: AssertionError _____________________ TestMoreErrors.test_global_func ______________________ self = @@ -532,7 +523,7 @@ get on the terminal - we are working on that):: E + where False = isinstance(43, float) E + where 43 = globf(42) - failure_demo.py:231: AssertionError + failure_demo.py:218: AssertionError _______________________ TestMoreErrors.test_instance _______________________ self = @@ -543,7 +534,7 @@ get on the terminal - we are working on that):: E assert 42 != 42 E + where 42 = .x - failure_demo.py:235: AssertionError + failure_demo.py:222: AssertionError _______________________ TestMoreErrors.test_compare ________________________ self = @@ -553,7 +544,7 @@ get on the terminal - we are working on that):: E assert 11 < 5 E + where 11 = globf(10) - failure_demo.py:238: AssertionError + failure_demo.py:225: AssertionError _____________________ TestMoreErrors.test_try_finally ______________________ self = @@ -564,13 +555,12 @@ get on the terminal - we are working on that):: > assert x == 0 E assert 1 == 0 - failure_demo.py:243: AssertionError + failure_demo.py:230: AssertionError ___________________ TestCustomAssertMsg.test_single_line ___________________ self = def test_single_line(self): - class A(object): a = 1 @@ -580,13 +570,12 @@ get on the terminal - we are working on that):: E assert 1 == 2 E + where 1 = .A'>.a - failure_demo.py:256: AssertionError + failure_demo.py:241: AssertionError ____________________ TestCustomAssertMsg.test_multiline ____________________ self = def test_multiline(self): - class A(object): a = 1 @@ -600,13 +589,12 @@ get on the terminal - we are working on that):: E assert 1 == 2 E + where 1 = .A'>.a - failure_demo.py:264: AssertionError + failure_demo.py:248: AssertionError ___________________ TestCustomAssertMsg.test_custom_repr ___________________ self = def test_custom_repr(self): - class JSON(object): a = 1 @@ -623,7 +611,7 @@ get on the terminal - we are working on that):: E assert 1 == 2 E + where 1 = This is JSON\n{\n 'foo': 'bar'\n}.a - failure_demo.py:278: AssertionError + failure_demo.py:261: AssertionError ============================= warnings summary ============================= Metafunc.addcall is deprecated and scheduled to be removed in pytest 4.0. diff --git a/doc/en/example/simple.rst b/doc/en/example/simple.rst index 180637ae9..f8c1fd279 100644 --- a/doc/en/example/simple.rst +++ b/doc/en/example/simple.rst @@ -415,7 +415,7 @@ Now we can profile which test functions execute the slowest:: ========================= slowest 3 test durations ========================= 0.30s call test_some_are_slow.py::test_funcslow2 - 0.20s call test_some_are_slow.py::test_funcslow1 + 0.21s call test_some_are_slow.py::test_funcslow1 0.10s call test_some_are_slow.py::test_funcfast ========================= 3 passed in 0.12 seconds ========================= diff --git a/doc/en/fixture.rst b/doc/en/fixture.rst index a02ab8215..1fd16fa34 100644 --- a/doc/en/fixture.rst +++ b/doc/en/fixture.rst @@ -337,7 +337,7 @@ tests. Let's execute it:: $ pytest -s -q --tb=no - FFteardown smtp_connection + FFteardown smtp 2 failed in 0.12 seconds @@ -468,7 +468,8 @@ Running it:: ______________________________ test_showhelo _______________________________ test_anothersmtp.py:5: in test_showhelo assert 0, smtp_connection.helo() - E NameError: name 'smtp_connection' is not defined + E AssertionError: (250, b'mail.python.org') + E assert 0 ------------------------- Captured stdout teardown ------------------------- finalizing (mail.python.org) @@ -568,42 +569,51 @@ So let's just do another run:: ================================= FAILURES ================================= ________________________ test_ehlo[smtp.gmail.com] _________________________ - smtp_connection = + smtp_connection = def test_ehlo(smtp_connection): - > response, msg = smtp_connection.ehlo() - E AttributeError: 'function' object has no attribute 'ehlo' + response, msg = smtp_connection.ehlo() + assert response == 250 + assert b"smtp.gmail.com" in msg + > assert 0 # for demo purposes + E assert 0 - test_module.py:3: AttributeError + test_module.py:6: AssertionError ________________________ test_noop[smtp.gmail.com] _________________________ - smtp_connection = + smtp_connection = def test_noop(smtp_connection): - > response, msg = smtp_connection.noop() - E AttributeError: 'function' object has no attribute 'noop' + response, msg = smtp_connection.noop() + assert response == 250 + > assert 0 # for demo purposes + E assert 0 - test_module.py:9: AttributeError + test_module.py:11: AssertionError ________________________ test_ehlo[mail.python.org] ________________________ - smtp_connection = + smtp_connection = def test_ehlo(smtp_connection): - > response, msg = smtp_connection.ehlo() - E AttributeError: 'function' object has no attribute 'ehlo' + 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' - test_module.py:3: AttributeError + test_module.py:5: AssertionError -------------------------- Captured stdout setup --------------------------- finalizing ________________________ test_noop[mail.python.org] ________________________ - smtp_connection = + smtp_connection = def test_noop(smtp_connection): - > response, msg = smtp_connection.noop() - E AttributeError: 'function' object has no attribute 'noop' + response, msg = smtp_connection.noop() + assert response == 250 + > assert 0 # for demo purposes + E assert 0 - test_module.py:9: AttributeError + test_module.py:11: AssertionError ------------------------- Captured stdout teardown ------------------------- finalizing 4 failed in 0.12 seconds From aa47b64e2a337df4520eea5a680b14990b724ee1 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 11 Jul 2018 21:07:21 -0300 Subject: [PATCH 17/26] Improve CHANGELOG entry --- changelog/3592.doc.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/3592.doc.rst b/changelog/3592.doc.rst index 38fc4a944..1d4d35352 100644 --- a/changelog/3592.doc.rst +++ b/changelog/3592.doc.rst @@ -1 +1 @@ -Clarify confusing examples in fixtures' documentation. +Use ``smtp_connection`` instead of ``smtp`` in fixtures documentation to avoid possible confusion. From 54fbc6f6e1404dcd133f8e01d0fd09518664c3ad Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 11 Jul 2018 21:09:58 -0300 Subject: [PATCH 18/26] Refactor parametrize() code for readability Extract the parametrize() block of code into methods for better readability --- src/_pytest/python.py | 142 +++++++++++++++++++++++++++--------------- 1 file changed, 93 insertions(+), 49 deletions(-) diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 724a6098a..4d0b4d537 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -865,46 +865,62 @@ 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) - 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: + 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)]: + elements = zip(ids, parameters, count()) + for a_id, param, param_index in elements: + if len(param.values) != len(argnames): raise ValueError( - "indirect given to %r: fixture %r doesn't exist" - % (self.function, arg) + 'In "parametrize" the number of values ({}) must be ' + "equal to the number of names ({})".format( + param.values, argnames + ) ) - valtypes[arg] = "params" + newcallspec = callspec.copy() + newcallspec.setmulti2( + arg_values_types, + argnames, + param.values, + a_id, + param.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 + idfn = None if callable(ids): idfn = ids @@ -921,29 +937,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. From 3e599dc1493fdc38819e96f0176ddde5e46b9622 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 11 Jul 2018 22:57:53 -0300 Subject: [PATCH 19/26] Check that param sets match number of args during _for_parametrize It makes sense to validate them during creation of the parameter set --- src/_pytest/mark/structures.py | 14 +++++++++++++- src/_pytest/python.py | 17 ++++------------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/_pytest/mark/structures.py b/src/_pytest/mark/structures.py index 3c161c4a6..e3a07f021 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) diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 4d0b4d537..02e9381a5 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 @@ -887,22 +886,14 @@ class Metafunc(fixtures.FuncargnamesCompatAttr): # of all calls 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): - raise ValueError( - 'In "parametrize" the number of values ({}) must be ' - "equal to the number of names ({})".format( - param.values, argnames - ) - ) + for param_index, (param_id, param_set) in enumerate(zip(ids, parameters)): newcallspec = callspec.copy() newcallspec.setmulti2( arg_values_types, argnames, - param.values, - a_id, - param.marks, + param_set.values, + param_id, + param_set.marks, scopenum, param_index, ) From 35ffd2940427f875f03d6325bb0caf3d8c988461 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 14 Jul 2018 10:09:22 -0300 Subject: [PATCH 20/26] Manage GH labels using the new 'labels' tool --- .github/labels.toml | 149 +++++++++++++++++++++++++++++++++++ doc/en/development_guide.rst | 5 ++ 2 files changed, 154 insertions(+) create mode 100644 .github/labels.toml 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/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 ~~~~~~~~~~~~~~~~ From a0b0c37febd8752cdf1cb74fe80cd1e180056ba8 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 14 Jul 2018 10:21:31 -0300 Subject: [PATCH 21/26] Revamp the release script: drop invoke and use tox directly Following the lead from tox, use a simple Python script instead of depending on ``invoke``. Other changes: * Some colors using ``colorama``. * Run ``pre-commit`` before the final commit to ensure everything is neatly formatted. * Drop generating local tag: legacy from the time we used ``devpi`` as staging area, currently we no longer use it, and we should push a tag from the last HEAD of the PR always to ensure it is correct. --- HOWTORELEASE.rst | 14 +---- {tasks => scripts}/release.minor.rst | 0 {tasks => scripts}/release.patch.rst | 0 tasks/generate.py => scripts/release.py | 83 +++++++++++-------------- tasks/__init__.py | 10 --- tasks/requirements.txt | 6 -- tox.ini | 14 +++++ 7 files changed, 55 insertions(+), 72 deletions(-) rename {tasks => scripts}/release.minor.rst (100%) rename {tasks => scripts}/release.patch.rst (100%) rename tasks/generate.py => scripts/release.py (57%) delete mode 100644 tasks/__init__.py delete mode 100644 tasks/requirements.txt 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/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/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/tox.ini b/tox.ini index 6e5d7ca68..ae7de1e40 100644 --- a/tox.ini +++ b/tox.ini @@ -176,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 From a8464a95cef6c8648c3c05e1e18601e3348f21f5 Mon Sep 17 00:00:00 2001 From: Hugo Martins Date: Sat, 14 Jul 2018 16:32:29 +0100 Subject: [PATCH 22/26] Add CITATION Relates to #3402 --- CITATION | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 CITATION diff --git a/CITATION b/CITATION new file mode 100644 index 000000000..e1807f5d9 --- /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}, +} \ No newline at end of file From e9fd038aaeb4951460b94aaa96f1ece174875d66 Mon Sep 17 00:00:00 2001 From: Hugo Martins Date: Sat, 14 Jul 2018 16:35:33 +0100 Subject: [PATCH 23/26] Fix linting issues --- CITATION | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CITATION b/CITATION index e1807f5d9..d4e9d8ec7 100644 --- a/CITATION +++ b/CITATION @@ -1,4 +1,4 @@ -NOTE: Change "x.y" by the version you use. If you are unsure about which version +NOTE: Change "x.y" by the version you use. If you are unsure about which version you are using run: `pip show pytest`. Text: @@ -13,4 +13,4 @@ BibTeX: 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}, -} \ No newline at end of file +} From 5be03bff61f6e84929db10b76271eca1d4278018 Mon Sep 17 00:00:00 2001 From: Hugo Martins Date: Sat, 14 Jul 2018 16:37:55 +0100 Subject: [PATCH 24/26] Add changelog information --- changelog/3402.trivial.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/3402.trivial.rst diff --git a/changelog/3402.trivial.rst b/changelog/3402.trivial.rst new file mode 100644 index 000000000..471b98a83 --- /dev/null +++ b/changelog/3402.trivial.rst @@ -0,0 +1 @@ +Add CITATION to provide information on how to formally cite pytest. \ No newline at end of file From 6a4fa4f4859741550ac7469b18f490ab25e30ca3 Mon Sep 17 00:00:00 2001 From: Hugo Martins Date: Sat, 14 Jul 2018 16:44:47 +0100 Subject: [PATCH 25/26] Fix more linting issues --- changelog/3402.trivial.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/3402.trivial.rst b/changelog/3402.trivial.rst index 471b98a83..c5f340c85 100644 --- a/changelog/3402.trivial.rst +++ b/changelog/3402.trivial.rst @@ -1 +1 @@ -Add CITATION to provide information on how to formally cite pytest. \ No newline at end of file +Add CITATION to provide information on how to formally cite pytest. From 58e77f58bd5bfea593daeaacc3f934b3c1dfbeb6 Mon Sep 17 00:00:00 2001 From: Vlad Shcherbina Date: Sun, 15 Jul 2018 16:58:39 +0300 Subject: [PATCH 26/26] Replace broken type annotations with type comments Fixes #3635. --- changelog/3635.trivial.rst | 1 + src/_pytest/mark/structures.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 changelog/3635.trivial.rst 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/src/_pytest/mark/structures.py b/src/_pytest/mark/structures.py index e3a07f021..9bd89c3c3 100644 --- a/src/_pytest/mark/structures.py +++ b/src/_pytest/mark/structures.py @@ -136,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): """