From 2a1b1107c5395560e7335ab0ef775343b3f65566 Mon Sep 17 00:00:00 2001 From: Brian Maissy Date: Thu, 25 Jan 2018 22:28:27 +0200 Subject: [PATCH 01/53] If we fail to import doctest.UnexpectedException during postmortem, fail quietly and continue --- _pytest/debugging.py | 18 +++++++++++------- changelog/1810.bugfix | 1 + 2 files changed, 12 insertions(+), 7 deletions(-) create mode 100644 changelog/1810.bugfix diff --git a/_pytest/debugging.py b/_pytest/debugging.py index d7dca7809..e458406b5 100644 --- a/_pytest/debugging.py +++ b/_pytest/debugging.py @@ -95,13 +95,17 @@ def _enter_pdb(node, excinfo, rep): def _postmortem_traceback(excinfo): - # A doctest.UnexpectedException is not useful for post_mortem. - # Use the underlying exception instead: - from doctest import UnexpectedException - if isinstance(excinfo.value, UnexpectedException): - return excinfo.value.exc_info[2] - else: - return excinfo._excinfo[2] + try: + from doctest import UnexpectedException + if isinstance(excinfo.value, UnexpectedException): + # A doctest.UnexpectedException is not useful for post_mortem. + # Use the underlying exception instead: + return excinfo.value.exc_info[2] + except ImportError: + # If we fail to import, continue quietly (if we ran out of file descriptors, for example: #1810) + pass + + return excinfo._excinfo[2] def _find_last_non_hidden_frame(stack): diff --git a/changelog/1810.bugfix b/changelog/1810.bugfix new file mode 100644 index 000000000..6e9389671 --- /dev/null +++ b/changelog/1810.bugfix @@ -0,0 +1 @@ +If we fail to import doctest.UnexpectedException during postmortem, fail quietly and continue. \ No newline at end of file From 547070e2d83617988ce07f2de6240f3ba3705d9c Mon Sep 17 00:00:00 2001 From: Alan Velasco Date: Tue, 30 Jan 2018 16:20:43 -0600 Subject: [PATCH 02/53] Switch `for_parameterize` to `for_parametrize` --- _pytest/mark.py | 2 +- _pytest/python.py | 4 ++-- changelog/3166.trivial | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 changelog/3166.trivial diff --git a/_pytest/mark.py b/_pytest/mark.py index 3f1f01b1a..f2d31761a 100644 --- a/_pytest/mark.py +++ b/_pytest/mark.py @@ -70,7 +70,7 @@ class ParameterSet(namedtuple('ParameterSet', 'values, marks, id')): return cls(argval, marks=newmarks, id=None) @classmethod - def _for_parameterize(cls, argnames, argvalues, function): + def _for_parametrize(cls, argnames, argvalues, function, config): if not isinstance(argnames, (tuple, list)): argnames = [x.strip() for x in argnames.split(",") if x.strip()] force_tuple = len(argnames) == 1 diff --git a/_pytest/python.py b/_pytest/python.py index 3940028fa..a4c233076 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -785,8 +785,8 @@ class Metafunc(fixtures.FuncargnamesCompatAttr): from _pytest.fixtures import scope2index from _pytest.mark import ParameterSet from py.io import saferepr - argnames, parameters = ParameterSet._for_parameterize( - argnames, argvalues, self.function) + argnames, parameters = ParameterSet._for_parametrize( + argnames, argvalues, self.function, self.config) del argvalues if scope is None: diff --git a/changelog/3166.trivial b/changelog/3166.trivial new file mode 100644 index 000000000..0f6ca240e --- /dev/null +++ b/changelog/3166.trivial @@ -0,0 +1 @@ +Change the naming of ``_for_parameterize()`` to ``_for_parametrize()`` in order to comply with the naming convention. \ No newline at end of file From 90a8faabbaf22ca376af71cc49d3ab81c510e736 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 30 Jan 2018 20:01:50 -0200 Subject: [PATCH 03/53] Fix test for py37 In previous Python versions, the list of warnigns appears like: ... list of emitted warnings is: [UserWarning('user',)]. In Python 3.7 apparently the string representation has been improved to: ... list of emitted warnings is: [UserWarning('user')]. Fix #3011 --- testing/test_recwarn.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testing/test_recwarn.py b/testing/test_recwarn.py index 0c80c169e..f1cf542e9 100644 --- a/testing/test_recwarn.py +++ b/testing/test_recwarn.py @@ -206,13 +206,13 @@ class TestWarns(object): with pytest.warns(RuntimeWarning): warnings.warn("user", UserWarning) excinfo.match(r"DID NOT WARN. No warnings of type \(.+RuntimeWarning.+,\) was emitted. " - r"The list of emitted warnings is: \[UserWarning\('user',\)\].") + r"The list of emitted warnings is: \[UserWarning\('user',?\)\].") with pytest.raises(pytest.fail.Exception) as excinfo: with pytest.warns(UserWarning): warnings.warn("runtime", RuntimeWarning) excinfo.match(r"DID NOT WARN. No warnings of type \(.+UserWarning.+,\) was emitted. " - r"The list of emitted warnings is: \[RuntimeWarning\('runtime',\)\].") + r"The list of emitted warnings is: \[RuntimeWarning\('runtime',?\)\].") with pytest.raises(pytest.fail.Exception) as excinfo: with pytest.warns(UserWarning): From ef7df8f167cdcf774bd3688f4df98ee10b80766d Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 30 Jan 2018 21:40:14 -0200 Subject: [PATCH 04/53] Small update to CHANGELOG --- changelog/3166.trivial | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/3166.trivial b/changelog/3166.trivial index 0f6ca240e..ce92840cb 100644 --- a/changelog/3166.trivial +++ b/changelog/3166.trivial @@ -1 +1 @@ -Change the naming of ``_for_parameterize()`` to ``_for_parametrize()`` in order to comply with the naming convention. \ No newline at end of file +Rename ``ParameterSet._for_parameterize()`` to ``_for_parametrize()`` in order to comply with the naming convention. From 653abad27bbef8cdc28dd9e3a9393fbf0e0b2606 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 31 Jan 2018 18:18:15 -0200 Subject: [PATCH 05/53] Mention outcome.force_result() and add link to pluggy's docs Related to #3169 --- doc/en/writing_plugins.rst | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/doc/en/writing_plugins.rst b/doc/en/writing_plugins.rst index 7787f8d32..f5a8ea41e 100644 --- a/doc/en/writing_plugins.rst +++ b/doc/en/writing_plugins.rst @@ -462,19 +462,24 @@ Here is an example definition of a hook wrapper:: @pytest.hookimpl(hookwrapper=True) def pytest_pyfunc_call(pyfuncitem): - # do whatever you want before the next hook executes + do_something_before_next_hook_executes() outcome = yield # outcome.excinfo may be None or a (cls, val, tb) tuple res = outcome.get_result() # will raise if outcome was exception - # postprocess result + + post_process_result(res) + + outcome.force_result(new_res) # to override the return value to the plugin system Note that hook wrappers don't return results themselves, they merely perform tracing or other side effects around the actual hook implementations. If the result of the underlying hook is a mutable object, they may modify that result but it's probably better to avoid it. +For more information, consult the `pluggy documentation `_. + Hook function ordering / call example ------------------------------------- From 4458e65fe757ee34eddcf79c50e060018d327803 Mon Sep 17 00:00:00 2001 From: Aaron Date: Thu, 1 Feb 2018 13:07:45 -0800 Subject: [PATCH 06/53] Fix ordering of tests to minimize fixture creating --- _pytest/fixtures.py | 22 ++++++++++++++------- changelog/3161.bugfix | 1 + testing/python/fixture.py | 41 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 7 deletions(-) create mode 100644 changelog/3161.bugfix diff --git a/_pytest/fixtures.py b/_pytest/fixtures.py index 2bc6f108b..a8445767c 100644 --- a/_pytest/fixtures.py +++ b/_pytest/fixtures.py @@ -166,7 +166,7 @@ def reorder_items(items): items_by_argkey = {} for scopenum in range(0, scopenum_function): argkeys_cache[scopenum] = d = {} - items_by_argkey[scopenum] = item_d = defaultdict(list) + items_by_argkey[scopenum] = item_d = defaultdict(deque) for item in items: keys = OrderedDict.fromkeys(get_parametrized_fixture_keys(item, scopenum)) if keys: @@ -174,12 +174,19 @@ def reorder_items(items): for key in keys: item_d[key].append(item) items = OrderedDict.fromkeys(items) - return list(reorder_items_atscope(items, set(), argkeys_cache, items_by_argkey, 0)) + return list(reorder_items_atscope(items, argkeys_cache, items_by_argkey, 0)) -def reorder_items_atscope(items, ignore, argkeys_cache, items_by_argkey, scopenum): +def fix_cache_order(item, argkeys_cache, items_by_argkey): + for scopenum in range(0, scopenum_function): + for key in argkeys_cache[scopenum].get(item, []): + items_by_argkey[scopenum][key].appendleft(item) + + +def reorder_items_atscope(items, argkeys_cache, items_by_argkey, scopenum): if scopenum >= scopenum_function or len(items) < 3: return items + ignore = set() items_deque = deque(items) items_done = OrderedDict() scoped_items_by_argkey = items_by_argkey[scopenum] @@ -197,13 +204,14 @@ def reorder_items_atscope(items, ignore, argkeys_cache, items_by_argkey, scopenu else: slicing_argkey, _ = argkeys.popitem() # we don't have to remove relevant items from later in the deque because they'll just be ignored - for i in reversed(scoped_items_by_argkey[slicing_argkey]): - if i in items: - items_deque.appendleft(i) + matching_items = [i for i in scoped_items_by_argkey[slicing_argkey] if i in items] + for i in reversed(matching_items): + fix_cache_order(i, argkeys_cache, items_by_argkey) + items_deque.appendleft(i) break if no_argkey_group: no_argkey_group = reorder_items_atscope( - no_argkey_group, set(), argkeys_cache, items_by_argkey, scopenum + 1) + no_argkey_group, argkeys_cache, items_by_argkey, scopenum + 1) for item in no_argkey_group: items_done[item] = None ignore.add(slicing_argkey) diff --git a/changelog/3161.bugfix b/changelog/3161.bugfix new file mode 100644 index 000000000..317c64bec --- /dev/null +++ b/changelog/3161.bugfix @@ -0,0 +1 @@ +Fix test ordering bug introduced by PR #3108. \ No newline at end of file diff --git a/testing/python/fixture.py b/testing/python/fixture.py index d22389e71..6bcb1ab00 100644 --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -2168,6 +2168,47 @@ class TestFixtureMarker(object): test_mod1.py::test_func1[m2] PASSED """) + def test_dynamic_parametrized_ordering(self, testdir): + testdir.makeini(""" + [pytest] + console_output_style=classic + """) + testdir.makeconftest(""" + import pytest + + def pytest_configure(config): + class DynamicFixturePlugin(object): + @pytest.fixture(scope='session', params=['flavor1', 'flavor2']) + def flavor(self, request): + return request.param + config.pluginmanager.register(DynamicFixturePlugin(), 'flavor-fixture') + + @pytest.fixture(scope='session', params=['vxlan', 'vlan']) + def encap(request): + return request.param + + @pytest.fixture(scope='session', autouse='True') + def reprovision(request, flavor, encap): + pass + """) + testdir.makepyfile(""" + def test(reprovision): + pass + def test2(reprovision): + pass + """) + result = testdir.runpytest("-v") + result.stdout.fnmatch_lines(""" + test_dynamic_parametrized_ordering.py::test[flavor1-vxlan] PASSED + test_dynamic_parametrized_ordering.py::test2[flavor1-vxlan] PASSED + test_dynamic_parametrized_ordering.py::test[flavor2-vxlan] PASSED + test_dynamic_parametrized_ordering.py::test2[flavor2-vxlan] PASSED + test_dynamic_parametrized_ordering.py::test[flavor2-vlan] PASSED + test_dynamic_parametrized_ordering.py::test2[flavor2-vlan] PASSED + test_dynamic_parametrized_ordering.py::test[flavor1-vlan] PASSED + test_dynamic_parametrized_ordering.py::test2[flavor1-vlan] PASSED + """) + def test_class_ordering(self, testdir): testdir.makeini(""" [pytest] From 3425edd2a52ac314118a593cbe0bff746f81d9b7 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 1 Feb 2018 19:54:51 -0200 Subject: [PATCH 07/53] Reword changelog a bit --- changelog/3161.bugfix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/3161.bugfix b/changelog/3161.bugfix index 317c64bec..73872be67 100644 --- a/changelog/3161.bugfix +++ b/changelog/3161.bugfix @@ -1 +1 @@ -Fix test ordering bug introduced by PR #3108. \ No newline at end of file +Fix ordering of tests using parametrized fixtures which can lead to fixtures being created more than necessary. From e289c60c3a6c97cc51cce8eced65c86a3d7ae750 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 2 Feb 2018 17:15:16 -0200 Subject: [PATCH 08/53] Support py37 officially Python 3.7.0b1 has been released: https://www.python.org/downloads/release/python-370b1/ Fix #3168 --- .travis.yml | 3 --- setup.py | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 938391cde..40fe3e8ba 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,9 +39,6 @@ matrix: python: '3.5' - env: TOXENV=py37 python: 'nightly' - allow_failures: - - env: TOXENV=py37 - python: 'nightly' script: tox --recreate diff --git a/setup.py b/setup.py index e08be845e..30234d2cc 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ classifiers = [ 'Topic :: Utilities', ] + [ ('Programming Language :: Python :: %s' % x) - for x in '2 2.7 3 3.4 3.5 3.6'.split() + for x in '2 2.7 3 3.4 3.5 3.6 3.7'.split() ] with open('README.rst') as fd: From 74633815aa55f6e38ca2ac4072cc02fb247b43f5 Mon Sep 17 00:00:00 2001 From: Brian Maissy Date: Sat, 3 Feb 2018 23:24:11 +0200 Subject: [PATCH 09/53] skip failing pdb/doctest test on mac --- changelog/985.bugfix | 1 + testing/test_pdb.py | 4 ++++ 2 files changed, 5 insertions(+) create mode 100644 changelog/985.bugfix diff --git a/changelog/985.bugfix b/changelog/985.bugfix new file mode 100644 index 000000000..cf4c2fe2c --- /dev/null +++ b/changelog/985.bugfix @@ -0,0 +1 @@ +Skip failing pdb/doctest test on mac. \ No newline at end of file diff --git a/testing/test_pdb.py b/testing/test_pdb.py index 8618473bb..ff6d994b5 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -267,6 +267,10 @@ class TestPDB(object): child.read() self.flush(child) + # For some reason the interaction between doctest's and pytest's output + # capturing mechanisms are messing up the stdout on mac. (See #985). + # Should be solvable, but skipping until we have a chance to investigate. + @pytest.mark.skipif("sys.platform == 'darwin'") def test_pdb_interaction_doctest(self, testdir): p1 = testdir.makepyfile(""" import pytest From e64feaba7ab05e06b65dc6b9a722a4f39c01cd58 Mon Sep 17 00:00:00 2001 From: Brian Maissy Date: Sat, 3 Feb 2018 23:28:37 +0200 Subject: [PATCH 10/53] xfail is better than skip --- testing/test_pdb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/test_pdb.py b/testing/test_pdb.py index ff6d994b5..b286d57a8 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -270,7 +270,7 @@ class TestPDB(object): # For some reason the interaction between doctest's and pytest's output # capturing mechanisms are messing up the stdout on mac. (See #985). # Should be solvable, but skipping until we have a chance to investigate. - @pytest.mark.skipif("sys.platform == 'darwin'") + @pytest.mark.xfail("sys.platform == 'darwin'", reason='See issue #985', run=False) def test_pdb_interaction_doctest(self, testdir): p1 = testdir.makepyfile(""" import pytest From 867344d0d72cfe79bb5ff316b065621f12627f40 Mon Sep 17 00:00:00 2001 From: Brian Maissy Date: Sun, 4 Feb 2018 00:03:17 +0200 Subject: [PATCH 11/53] move import to top --- _pytest/debugging.py | 18 +++++++----------- changelog/1810.bugfix | 2 +- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/_pytest/debugging.py b/_pytest/debugging.py index e458406b5..bd7a33b2d 100644 --- a/_pytest/debugging.py +++ b/_pytest/debugging.py @@ -2,6 +2,7 @@ from __future__ import absolute_import, division, print_function import pdb import sys +from doctest import UnexpectedException def pytest_addoption(parser): @@ -95,17 +96,12 @@ def _enter_pdb(node, excinfo, rep): def _postmortem_traceback(excinfo): - try: - from doctest import UnexpectedException - if isinstance(excinfo.value, UnexpectedException): - # A doctest.UnexpectedException is not useful for post_mortem. - # Use the underlying exception instead: - return excinfo.value.exc_info[2] - except ImportError: - # If we fail to import, continue quietly (if we ran out of file descriptors, for example: #1810) - pass - - return excinfo._excinfo[2] + if isinstance(excinfo.value, UnexpectedException): + # A doctest.UnexpectedException is not useful for post_mortem. + # Use the underlying exception instead: + return excinfo.value.exc_info[2] + else: + return excinfo._excinfo[2] def _find_last_non_hidden_frame(stack): diff --git a/changelog/1810.bugfix b/changelog/1810.bugfix index 6e9389671..5c3000d05 100644 --- a/changelog/1810.bugfix +++ b/changelog/1810.bugfix @@ -1 +1 @@ -If we fail to import doctest.UnexpectedException during postmortem, fail quietly and continue. \ No newline at end of file +Move import of doctest.UnexpectedException to top-level. \ No newline at end of file From ea8997a108eb78a795f461ff1f41e0771e58c2d7 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 3 Feb 2018 20:21:06 -0200 Subject: [PATCH 12/53] Update wording in CHANGELOG --- changelog/1810.bugfix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/1810.bugfix b/changelog/1810.bugfix index 5c3000d05..c91ed47d0 100644 --- a/changelog/1810.bugfix +++ b/changelog/1810.bugfix @@ -1 +1 @@ -Move import of doctest.UnexpectedException to top-level. \ No newline at end of file +Move import of ``doctest.UnexpectedException`` to top-level to avoid possible errors when using ``--pdb``. From 0d15a4686352ee2e6b8a2842fe47e1cd6d86e4f7 Mon Sep 17 00:00:00 2001 From: Pedro Algarvio Date: Wed, 31 Jan 2018 14:53:58 +0000 Subject: [PATCH 13/53] Don't traceback on unkown sections. --- _pytest/logging.py | 2 +- changelog/3184.bugfix | 1 + testing/logging/test_reporting.py | 54 +++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 changelog/3184.bugfix diff --git a/_pytest/logging.py b/_pytest/logging.py index 095115cd9..f6affc8a2 100644 --- a/_pytest/logging.py +++ b/_pytest/logging.py @@ -477,7 +477,7 @@ class _LiveLoggingStreamHandler(logging.StreamHandler): if not self._first_record_emitted or self._when == 'teardown': self.stream.write('\n') self._first_record_emitted = True - if not self._section_name_shown: + if not self._section_name_shown and self._when: self.stream.section('live log ' + self._when, sep='-', bold=True) self._section_name_shown = True logging.StreamHandler.emit(self, record) diff --git a/changelog/3184.bugfix b/changelog/3184.bugfix new file mode 100644 index 000000000..6269e7dee --- /dev/null +++ b/changelog/3184.bugfix @@ -0,0 +1 @@ +Don't traceback when logging on unknown(unhandled) sections. diff --git a/testing/logging/test_reporting.py b/testing/logging/test_reporting.py index f5272aa09..8dfe04ad9 100644 --- a/testing/logging/test_reporting.py +++ b/testing/logging/test_reporting.py @@ -272,6 +272,60 @@ def test_log_cli_default_level_sections(testdir, request): ]) +def test_live_logs_unknown_sections(testdir, request): + """Check that with live logging enable we are printing the correct headers during + start/setup/call/teardown/finish.""" + filename = request.node.name + '.py' + testdir.makeconftest(''' + import pytest + import logging + + def pytest_runtest_protocol(item, nextitem): + logging.warning('Unknown Section!') + + def pytest_runtest_logstart(): + logging.warning('>>>>> START >>>>>') + + def pytest_runtest_logfinish(): + logging.warning('<<<<< END <<<<<<<') + ''') + + testdir.makepyfile(''' + import pytest + import logging + + @pytest.fixture + def fix(request): + logging.warning("log message from setup of {}".format(request.node.name)) + yield + logging.warning("log message from teardown of {}".format(request.node.name)) + + def test_log_1(fix): + logging.warning("log message from test_log_1") + + ''') + testdir.makeini(''' + [pytest] + log_cli=true + ''') + + result = testdir.runpytest() + result.stdout.fnmatch_lines([ + '*WARNING*Unknown Section*', + '{}::test_log_1 '.format(filename), + '*WARNING* >>>>> START >>>>>*', + '*-- live log setup --*', + '*WARNING*log message from setup of test_log_1*', + '*-- live log call --*', + '*WARNING*log message from test_log_1*', + 'PASSED *100%*', + '*-- live log teardown --*', + '*WARNING*log message from teardown of test_log_1*', + '*WARNING* <<<<< END <<<<<<<*', + '=* 1 passed in *=', + ]) + + def test_log_cli_level(testdir): # Default log file level testdir.makepyfile(''' From 42c1f8525738be6f2b26f2f6ceb057f76446e3c1 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 5 Feb 2018 20:07:42 -0200 Subject: [PATCH 14/53] Update CHANGELOG --- changelog/3184.bugfix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/3184.bugfix b/changelog/3184.bugfix index 6269e7dee..875358776 100644 --- a/changelog/3184.bugfix +++ b/changelog/3184.bugfix @@ -1 +1 @@ -Don't traceback when logging on unknown(unhandled) sections. +Fix bug where logging happening at hooks outside of "test run" hooks would cause an internal error. From 71527072801b6d83db168996529be1e76ce96bc4 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 6 Feb 2018 08:54:44 -0200 Subject: [PATCH 15/53] Update changelog entry to "trivial" as it is a temporary workaround I think this is a more appropriate category given that the underlying problem still exists --- changelog/985.bugfix | 1 - changelog/985.trivial | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 changelog/985.bugfix create mode 100644 changelog/985.trivial diff --git a/changelog/985.bugfix b/changelog/985.bugfix deleted file mode 100644 index cf4c2fe2c..000000000 --- a/changelog/985.bugfix +++ /dev/null @@ -1 +0,0 @@ -Skip failing pdb/doctest test on mac. \ No newline at end of file diff --git a/changelog/985.trivial b/changelog/985.trivial new file mode 100644 index 000000000..8554f2b65 --- /dev/null +++ b/changelog/985.trivial @@ -0,0 +1 @@ +Skip failing pdb/doctest test on mac. From 40d0ade2d9d0ab6d8d8b3df759945511042f86b5 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 6 Feb 2018 22:13:04 -0200 Subject: [PATCH 16/53] Add changelog/README.rst and streamline our PR template text This streamlines the PR template text and adds a more in-depth explanation about how the changelog entries work because this topic is a common source of confusion: - How to name the files. - Which formatting to use (people in general assume it is Markdown). - Recommend adding `.rst` extension to changelog files to help with the above (`towncrier` doesn't care). This was heavily inspired by the excellent python-trio/trio docs. --- .github/PULL_REQUEST_TEMPLATE.md | 19 ++++++------- changelog/{1810.bugfix => 1810.bugfix.rst} | 0 changelog/{3166.trivial => 3166.trivial.rst} | 0 changelog/{985.trivial => 985.trivial.rst} | 0 changelog/README.rst | 30 ++++++++++++++++++++ 5 files changed, 39 insertions(+), 10 deletions(-) rename changelog/{1810.bugfix => 1810.bugfix.rst} (100%) rename changelog/{3166.trivial => 3166.trivial.rst} (100%) rename changelog/{985.trivial => 985.trivial.rst} (100%) create mode 100644 changelog/README.rst diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 690426d68..64769b8e2 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,15 +1,14 @@ Thanks for submitting a PR, your contribution is really appreciated! -Here's a quick checklist that should be present in PRs: +Here's a quick checklist that should be present in PRs (you can delete this text from the final description, this is +just a guideline): -- [ ] Add a new news fragment into the changelog folder - * name it `$issue_id.$type` for example (588.bugfix) - * if you don't have an issue_id change it to the pr id after creating the pr - * ensure type is one of `removal`, `feature`, `bugfix`, `vendor`, `doc` or `trivial` - * Make sure to use full sentences with correct case and punctuation, for example: "Fix issue with non-ascii contents in doctest text files." -- [ ] Target: for `bugfix`, `vendor`, `doc` or `trivial` fixes, target `master`; for removals or features target `features`; -- [ ] Make sure to include reasonable tests for your change if necessary +- [ ] Create a new changelog file into the `changelog` folder, with a name like `..rst`. See [changelog/README.rst](/changelog/README.rst) for details. +- [ ] Target the `master` branch for bug fixes, documentation updates and trivial changes. +- [ ] Target the `features` branch for new features and removals/deprecations. +- [ ] Include documentation when adding new features. +- [ ] Include new tests or update existing tests when applicable. -Unless your change is a trivial or a documentation fix (e.g., a typo or reword of a small section) please: +Unless your change is trivial or a small documentation fix (e.g., a typo or reword of a small section) please: -- [ ] Add yourself to `AUTHORS`, in alphabetical order; +- [ ] Add yourself to `AUTHORS` in alphabetical order; diff --git a/changelog/1810.bugfix b/changelog/1810.bugfix.rst similarity index 100% rename from changelog/1810.bugfix rename to changelog/1810.bugfix.rst diff --git a/changelog/3166.trivial b/changelog/3166.trivial.rst similarity index 100% rename from changelog/3166.trivial rename to changelog/3166.trivial.rst diff --git a/changelog/985.trivial b/changelog/985.trivial.rst similarity index 100% rename from changelog/985.trivial rename to changelog/985.trivial.rst diff --git a/changelog/README.rst b/changelog/README.rst new file mode 100644 index 000000000..ec1c7f617 --- /dev/null +++ b/changelog/README.rst @@ -0,0 +1,30 @@ +This directory contains "newsfragments" which are short that contain a small **ReST**-formatted +text that will be added to the next ``CHANGELOG``. + +The ``CHANGELOG`` will be read by users, so this description should be aimed to pytest users +instead of describing internal changes which are only relevant to the developers. + +Make sure to use full sentences with correct case and punctuation, for example: *Fix issue with non-ascii contents in doctest text files.* + +Each file should be named like ``..rst``, where +```` is an issue number, and ```` is one of: + +* ``feature``: new user facing features, like new command-line options and new behavior. +* ``bugfix``: fixes a reported bug. +* ``doc``: documentation improvement, like rewording an entire session or adding missing docs. +* ``removal``: feature deprecation or removal; +* ``vendor``: vendoring news; +* ``trivial``: fixing a small typo or internal change that might be noteworthy. + +So for example: ``123.feature.rst``, ``456.bugfix.rst``. + +If your PR fixes an issue, use that number here. If there is no issue, +then after you submit the PR and get the PR number you can add a +changelog using that instead. + +If you are not sure what issue type to use, don't hesitate to ask in your PR. + +Note that the ``towncrier`` tool will automatically +reflow your text, so it will work best if you stick to a single paragraph, but multiple sentences and links are OK +and encouraged. You can install ``towncrier`` and then run ``towncrier --draft`` +if you want to get a preview of how your change will look in the final release notes. From eea169e5151da473909fa8bcc467ac7d2380accd Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 8 Feb 2018 08:03:14 -0200 Subject: [PATCH 17/53] Code review suggestions --- .github/PULL_REQUEST_TEMPLATE.md | 2 +- changelog/README.rst | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 64769b8e2..23a9f8c56 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -3,7 +3,7 @@ Thanks for submitting a PR, your contribution is really appreciated! Here's a quick checklist that should be present in PRs (you can delete this text from the final description, this is just a guideline): -- [ ] Create a new changelog file into the `changelog` folder, with a name like `..rst`. See [changelog/README.rst](/changelog/README.rst) for details. +- [ ] Create a new changelog file in the `changelog` folder, with a name like `..rst`. See [changelog/README.rst](/changelog/README.rst) for details. - [ ] Target the `master` branch for bug fixes, documentation updates and trivial changes. - [ ] Target the `features` branch for new features and removals/deprecations. - [ ] Include documentation when adding new features. diff --git a/changelog/README.rst b/changelog/README.rst index ec1c7f617..f00720de9 100644 --- a/changelog/README.rst +++ b/changelog/README.rst @@ -12,8 +12,8 @@ Each file should be named like ``..rst``, where * ``feature``: new user facing features, like new command-line options and new behavior. * ``bugfix``: fixes a reported bug. * ``doc``: documentation improvement, like rewording an entire session or adding missing docs. -* ``removal``: feature deprecation or removal; -* ``vendor``: vendoring news; +* ``removal``: feature deprecation or removal. +* ``vendor``: changes in packages vendored in pytest. * ``trivial``: fixing a small typo or internal change that might be noteworthy. So for example: ``123.feature.rst``, ``456.bugfix.rst``. From d776e5610e163665dddc1322c49f16c70cb7a3f5 Mon Sep 17 00:00:00 2001 From: Pedro Algarvio Date: Thu, 8 Feb 2018 23:23:12 +0000 Subject: [PATCH 18/53] Fix issue where a new line was always written for the live log finish section --- _pytest/logging.py | 9 ++++- testing/logging/test_reporting.py | 59 +++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/_pytest/logging.py b/_pytest/logging.py index ecb992cea..b50347518 100644 --- a/_pytest/logging.py +++ b/_pytest/logging.py @@ -480,6 +480,7 @@ class _LiveLoggingStreamHandler(logging.StreamHandler): self.capture_manager = capture_manager self.reset() self.set_when(None) + self._test_outcome_written = False def reset(self): """Reset the handler; should be called before the start of each test""" @@ -489,14 +490,20 @@ class _LiveLoggingStreamHandler(logging.StreamHandler): """Prepares for the given test phase (setup/call/teardown)""" self._when = when self._section_name_shown = False + if when == 'start': + self._test_outcome_written = False def emit(self, record): if self.capture_manager is not None: self.capture_manager.suspend_global_capture() try: - if not self._first_record_emitted or self._when in ('teardown', 'finish'): + if not self._first_record_emitted: self.stream.write('\n') self._first_record_emitted = True + elif self._when in ('teardown', 'finish'): + if not self._test_outcome_written: + self._test_outcome_written = True + self.stream.write('\n') if not self._section_name_shown: self.stream.section('live log ' + self._when, sep='-', bold=True) self._section_name_shown = True diff --git a/testing/logging/test_reporting.py b/testing/logging/test_reporting.py index d7ba63a4e..97b31521f 100644 --- a/testing/logging/test_reporting.py +++ b/testing/logging/test_reporting.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +import re import os import six @@ -293,6 +294,64 @@ def test_log_cli_default_level_sections(testdir, request): ]) +def test_sections_single_new_line_after_test_outcome(testdir, request): + """Check that only a single new line is written between log messages during + teardown/finish.""" + filename = request.node.name + '.py' + testdir.makeconftest(''' + import pytest + import logging + + def pytest_runtest_logstart(): + logging.warning('>>>>> START >>>>>') + + def pytest_runtest_logfinish(): + logging.warning('<<<<< END <<<<<<<') + logging.warning('<<<<< END <<<<<<<') + ''') + + testdir.makepyfile(''' + import pytest + import logging + + @pytest.fixture + def fix(request): + logging.warning("log message from setup of {}".format(request.node.name)) + yield + logging.warning("log message from teardown of {}".format(request.node.name)) + logging.warning("log message from teardown of {}".format(request.node.name)) + + def test_log_1(fix): + logging.warning("log message from test_log_1") + ''') + testdir.makeini(''' + [pytest] + log_cli=true + ''') + + result = testdir.runpytest() + result.stdout.fnmatch_lines([ + '{}::test_log_1 '.format(filename), + '*-- live log start --*', + '*WARNING* >>>>> START >>>>>*', + '*-- live log setup --*', + '*WARNING*log message from setup of test_log_1*', + '*-- live log call --*', + '*WARNING*log message from test_log_1*', + 'PASSED *100%*', + '*-- live log teardown --*', + '*WARNING*log message from teardown of test_log_1*', + '*-- live log finish --*', + '*WARNING* <<<<< END <<<<<<<*', + '*WARNING* <<<<< END <<<<<<<*', + '=* 1 passed in *=', + ]) + assert re.search(r'(.+)live log teardown(.+)\n(.+)WARNING(.+)\n(.+)WARNING(.+)', + result.stdout.str(), re.MULTILINE) is not None + assert re.search(r'(.+)live log finish(.+)\n(.+)WARNING(.+)\n(.+)WARNING(.+)', + result.stdout.str(), re.MULTILINE) is not None + + def test_log_cli_level(testdir): # Default log file level testdir.makepyfile(''' From c04e248de54741b39f93d4ad07bd81e6bc6a2b3e Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 9 Feb 2018 14:06:24 -0200 Subject: [PATCH 19/53] Rename 3161.bugfix to 3161.bugfix.rst --- changelog/{3161.bugfix => 3161.bugfix.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog/{3161.bugfix => 3161.bugfix.rst} (100%) diff --git a/changelog/3161.bugfix b/changelog/3161.bugfix.rst similarity index 100% rename from changelog/3161.bugfix rename to changelog/3161.bugfix.rst From e5b527d0e3d72cf8e2cac4a4f7fb7b9a2d2f6e06 Mon Sep 17 00:00:00 2001 From: Andy Freeland Date: Fri, 9 Feb 2018 16:25:00 -0800 Subject: [PATCH 20/53] Add Sphinx parameter docs for `match` and `message` args to `pytest.raises()` --- _pytest/python_api.py | 4 ++++ changelog/3202.trivial.rst | 1 + 2 files changed, 5 insertions(+) create mode 100644 changelog/3202.trivial.rst diff --git a/_pytest/python_api.py b/_pytest/python_api.py index 81960295b..e6f002849 100644 --- a/_pytest/python_api.py +++ b/_pytest/python_api.py @@ -453,6 +453,10 @@ def raises(expected_exception, *args, **kwargs): Assert that a code block/function call raises ``expected_exception`` and raise a failure exception otherwise. + :arg message: if specified, provides a custom failure message if the + exception is not raised + :arg match: if specified, asserts that the exception matches a text or regex + This helper produces a ``ExceptionInfo()`` object (see below). You may use this function as a context manager:: diff --git a/changelog/3202.trivial.rst b/changelog/3202.trivial.rst new file mode 100644 index 000000000..767a32b42 --- /dev/null +++ b/changelog/3202.trivial.rst @@ -0,0 +1 @@ +Add Sphinx parameter docs for ``match`` and ``message`` args to :func:`pytest.raises`. From ffee213c85a39a7aa60ae84cb6badfb583fecba1 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 9 Feb 2018 22:51:15 -0200 Subject: [PATCH 21/53] Update and rename 3202.trivial.rst to 3202.doc.rst --- changelog/{3202.trivial.rst => 3202.doc.rst} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename changelog/{3202.trivial.rst => 3202.doc.rst} (73%) diff --git a/changelog/3202.trivial.rst b/changelog/3202.doc.rst similarity index 73% rename from changelog/3202.trivial.rst rename to changelog/3202.doc.rst index 767a32b42..a6f99fbf6 100644 --- a/changelog/3202.trivial.rst +++ b/changelog/3202.doc.rst @@ -1 +1 @@ -Add Sphinx parameter docs for ``match`` and ``message`` args to :func:`pytest.raises`. +Add Sphinx parameter docs for ``match`` and ``message`` args to ``pytest.raises``. From b6166dccb4d2b48173aa7e7739be52db9d2d56a0 Mon Sep 17 00:00:00 2001 From: Marcin Bachry Date: Mon, 12 Feb 2018 18:41:00 +0100 Subject: [PATCH 22/53] Fix mock patchings detection when both mock and unittest.mock are present --- _pytest/compat.py | 7 ++++--- changelog/3206.bugfix.rst | 1 + testing/python/integration.py | 22 ++++++++++++++++++++++ 3 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 changelog/3206.bugfix.rst diff --git a/_pytest/compat.py b/_pytest/compat.py index 7560fbec3..92df65656 100644 --- a/_pytest/compat.py +++ b/_pytest/compat.py @@ -79,10 +79,11 @@ def num_mock_patch_args(function): patchings = getattr(function, "patchings", None) if not patchings: return 0 - mock = sys.modules.get("mock", sys.modules.get("unittest.mock", None)) - if mock is not None: + mock_modules = [sys.modules.get("mock"), sys.modules.get("unittest.mock")] + if any(mock_modules): + sentinels = [m.DEFAULT for m in mock_modules if m is not None] return len([p for p in patchings - if not p.attribute_name and p.new is mock.DEFAULT]) + if not p.attribute_name and p.new in sentinels]) return len(patchings) diff --git a/changelog/3206.bugfix.rst b/changelog/3206.bugfix.rst new file mode 100644 index 000000000..1e2305fa2 --- /dev/null +++ b/changelog/3206.bugfix.rst @@ -0,0 +1 @@ +Detect arguments injected by ``unittest.mock.patch`` decorator correctly when pypi ``mock.patch`` is installed and imported. diff --git a/testing/python/integration.py b/testing/python/integration.py index 6ea29fa98..aade04fa9 100644 --- a/testing/python/integration.py +++ b/testing/python/integration.py @@ -147,6 +147,28 @@ class TestMockDecoration(object): reprec = testdir.inline_run() reprec.assertoutcome(passed=1) + def test_unittest_mock_and_pypi_mock(self, testdir): + pytest.importorskip("unittest.mock") + pytest.importorskip("mock", "1.0.1") + testdir.makepyfile(""" + import mock + import unittest.mock + class TestBoth(object): + @unittest.mock.patch("os.path.abspath") + def test_hello(self, abspath): + import os + os.path.abspath("hello") + abspath.assert_any_call("hello") + + @mock.patch("os.path.abspath") + def test_hello_mock(self, abspath): + import os + os.path.abspath("hello") + abspath.assert_any_call("hello") + """) + reprec = testdir.inline_run() + reprec.assertoutcome(passed=2) + def test_mock(self, testdir): pytest.importorskip("mock", "1.0.1") testdir.makepyfile(""" From 7656fc8320b5920d860e9028f322475a021e7666 Mon Sep 17 00:00:00 2001 From: Brian Maissy Date: Mon, 12 Feb 2018 23:17:51 +0200 Subject: [PATCH 23/53] Added printing of captured stdout and stderr before entering pdb --- AUTHORS | 1 + _pytest/debugging.py | 11 +++++++++++ changelog/3052.bugfix | 1 + testing/test_pdb.py | 39 +++++++++++++++++++++++++++++++++++---- 4 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 changelog/3052.bugfix diff --git a/AUTHORS b/AUTHORS index 583fa6f9e..ab646f406 100644 --- a/AUTHORS +++ b/AUTHORS @@ -29,6 +29,7 @@ Benjamin Peterson Bernard Pratz Bob Ippolito Brian Dorsey +Brian Maissy Brian Okken Brianna Laugher Bruno Oliveira diff --git a/_pytest/debugging.py b/_pytest/debugging.py index 23d94e688..8b5f5cc2c 100644 --- a/_pytest/debugging.py +++ b/_pytest/debugging.py @@ -85,6 +85,17 @@ def _enter_pdb(node, excinfo, rep): # for not completely clear reasons. tw = node.config.pluginmanager.getplugin("terminalreporter")._tw tw.line() + + captured_stdout = rep.capstdout + if len(captured_stdout) > 0: + tw.sep(">", "captured stdout") + tw.line(captured_stdout) + + captured_stderr = rep.capstderr + if len(captured_stderr) > 0: + tw.sep(">", "captured stderr") + tw.line(captured_stderr) + tw.sep(">", "traceback") rep.toterminal(tw) tw.sep(">", "entering PDB") diff --git a/changelog/3052.bugfix b/changelog/3052.bugfix new file mode 100644 index 000000000..ea8c362a4 --- /dev/null +++ b/changelog/3052.bugfix @@ -0,0 +1 @@ +Added printing of captured stdout/stderr before entering pdb, and improved a test which was giving false negatives about output capturing. diff --git a/testing/test_pdb.py b/testing/test_pdb.py index 8618473bb..01604b633 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -141,19 +141,50 @@ class TestPDB(object): child.sendeof() self.flush(child) - def test_pdb_interaction_capture(self, testdir): + def test_pdb_print_captured_stdout(self, testdir): p1 = testdir.makepyfile(""" def test_1(): - print("getrekt") + print("get\\x20rekt") assert False """) child = testdir.spawn_pytest("--pdb %s" % p1) - child.expect("getrekt") + child.expect("captured stdout") + child.expect("get rekt") child.expect("(Pdb)") child.sendeof() rest = child.read().decode("utf8") assert "1 failed" in rest - assert "getrekt" not in rest + assert "get rekt" not in rest + self.flush(child) + + def test_pdb_print_captured_stderr(self, testdir): + p1 = testdir.makepyfile(""" + def test_1(): + import sys + sys.stderr.write("get\\x20rekt") + assert False + """) + child = testdir.spawn_pytest("--pdb %s" % p1) + child.expect("captured stderr") + child.expect("get rekt") + child.expect("(Pdb)") + child.sendeof() + rest = child.read().decode("utf8") + assert "1 failed" in rest + assert "get rekt" not in rest + self.flush(child) + + def test_pdb_dont_print_empty_captured_stdout_and_stderr(self, testdir): + p1 = testdir.makepyfile(""" + def test_1(): + assert False + """) + child = testdir.spawn_pytest("--pdb %s" % p1) + child.expect("(Pdb)") + output = child.before.decode("utf8") + child.sendeof() + assert "captured stdout" not in output + assert "captured stderr" not in output self.flush(child) def test_pdb_interaction_exception(self, testdir): From 391553887b072d1a533a83226995785bef28fe34 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 13 Feb 2018 12:08:39 -0500 Subject: [PATCH 24/53] Disable output capturing in doctest to avoid losing reference to stdout. Fixes #985. --- _pytest/doctest.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/_pytest/doctest.py b/_pytest/doctest.py index bba90e551..112a700ec 100644 --- a/_pytest/doctest.py +++ b/_pytest/doctest.py @@ -2,6 +2,7 @@ from __future__ import absolute_import, division, print_function import traceback +import sys import pytest from _pytest._code.code import ExceptionInfo, ReprFileLocation, TerminalRepr @@ -103,8 +104,19 @@ class DoctestItem(pytest.Item): def runtest(self): _check_all_skipped(self.dtest) + self._disable_output_capturing() self.runner.run(self.dtest) + def _disable_output_capturing(self): + """ + Disable output capturing. Otherwise, stdout is lost to doctest (#985) + """ + capman = self.config.pluginmanager.getplugin("capturemanager") + if capman: + out, err = capman.suspend_global_capture(in_=True) + sys.stdout.write(out) + sys.stdout.write(err) + def repr_failure(self, excinfo): import doctest if excinfo.errisinstance((doctest.DocTestFailure, From d845af7b2405438a3f1b46f9ed65293543bc2cc7 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 13 Feb 2018 12:32:00 -0500 Subject: [PATCH 25/53] Add changelog. Ref #985. --- changelog/985.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/985.bugfix diff --git a/changelog/985.bugfix b/changelog/985.bugfix new file mode 100644 index 000000000..0024b9aa2 --- /dev/null +++ b/changelog/985.bugfix @@ -0,0 +1 @@ +Fixed output capture handling in doctests on macOS. From 247cdb835a404f9668022984bb688496b48e5809 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 13 Feb 2018 12:41:14 -0500 Subject: [PATCH 26/53] Remove xfail; tests now pass on macOS. Ref #985. --- testing/test_pdb.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/testing/test_pdb.py b/testing/test_pdb.py index b286d57a8..8618473bb 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -267,10 +267,6 @@ class TestPDB(object): child.read() self.flush(child) - # For some reason the interaction between doctest's and pytest's output - # capturing mechanisms are messing up the stdout on mac. (See #985). - # Should be solvable, but skipping until we have a chance to investigate. - @pytest.mark.xfail("sys.platform == 'darwin'", reason='See issue #985', run=False) def test_pdb_interaction_doctest(self, testdir): p1 = testdir.makepyfile(""" import pytest From 6496131b799afc711b96fc0464ec0f8c0c9ba785 Mon Sep 17 00:00:00 2001 From: Thomas Hisch Date: Tue, 13 Feb 2018 14:46:11 +0100 Subject: [PATCH 27/53] Show deselection count before tests are exectued Fixes #1527 --- _pytest/terminal.py | 10 ++++------ changelog/3213.feature | 1 + testing/test_cacheprovider.py | 2 +- testing/test_terminal.py | 27 ++++++++++++++++++++++++++- 4 files changed, 32 insertions(+), 8 deletions(-) create mode 100644 changelog/3213.feature diff --git a/_pytest/terminal.py b/_pytest/terminal.py index d37dd2c43..69d4ab8ad 100644 --- a/_pytest/terminal.py +++ b/_pytest/terminal.py @@ -361,6 +361,7 @@ class TerminalReporter(object): errors = len(self.stats.get('error', [])) skipped = len(self.stats.get('skipped', [])) + deselected = len(self.stats.get('deselected', [])) if final: line = "collected " else: @@ -368,6 +369,8 @@ class TerminalReporter(object): line += str(self._numcollected) + " item" + ('' if self._numcollected == 1 else 's') if errors: line += " / %d errors" % errors + if deselected: + line += " / %d deselected" % deselected if skipped: line += " / %d skipped" % skipped if self.isatty: @@ -377,6 +380,7 @@ class TerminalReporter(object): else: self.write_line(line) + @pytest.hookimpl(trylast=True) def pytest_collection_modifyitems(self): self.report_collect(True) @@ -484,7 +488,6 @@ class TerminalReporter(object): if exitstatus == EXIT_INTERRUPTED: self._report_keyboardinterrupt() del self._keyboardinterrupt_memo - self.summary_deselected() self.summary_stats() def pytest_keyboard_interrupt(self, excinfo): @@ -649,11 +652,6 @@ class TerminalReporter(object): if self.verbosity == -1: self.write_line(msg, **markup) - def summary_deselected(self): - if 'deselected' in self.stats: - self.write_sep("=", "%d tests deselected" % ( - len(self.stats['deselected'])), bold=True) - def repr_pythonversion(v=None): if v is None: diff --git a/changelog/3213.feature b/changelog/3213.feature new file mode 100644 index 000000000..755942f1b --- /dev/null +++ b/changelog/3213.feature @@ -0,0 +1 @@ +Output item deselection count before tests are run. \ No newline at end of file diff --git a/testing/test_cacheprovider.py b/testing/test_cacheprovider.py index 038fd229e..d33719920 100644 --- a/testing/test_cacheprovider.py +++ b/testing/test_cacheprovider.py @@ -361,7 +361,7 @@ class TestLastFailed(object): result = testdir.runpytest('--lf') result.stdout.fnmatch_lines([ - 'collected 4 items', + 'collected 4 items / 2 deselected', 'run-last-failure: rerun previous 2 failures', '*2 failed, 2 deselected in*', ]) diff --git a/testing/test_terminal.py b/testing/test_terminal.py index e95a3ed2b..f23dffe25 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -431,11 +431,36 @@ class TestTerminalFunctional(object): ) result = testdir.runpytest("-k", "test_two:", testpath) result.stdout.fnmatch_lines([ + "collected 3 items / 1 deselected", "*test_deselected.py ..*", - "=* 1 test*deselected *=", ]) assert result.ret == 0 + def test_show_deselected_items_using_markexpr_before_test_execution( + self, testdir): + testdir.makepyfile(""" + import pytest + + @pytest.mark.foo + def test_foobar(): + pass + + @pytest.mark.bar + def test_bar(): + pass + + def test_pass(): + pass + """) + result = testdir.runpytest('-m', 'not foo') + result.stdout.fnmatch_lines([ + "collected 3 items / 1 deselected", + "*test_show_des*.py ..*", + "*= 2 passed, 1 deselected in * =*", + ]) + assert "= 1 deselected =" not in result.stdout.str() + assert result.ret == 0 + def test_no_skip_summary_if_failure(self, testdir): testdir.makepyfile(""" import pytest From 18c84a1904e96d5a4fc265b0fde56765ca36c7c3 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 13 Feb 2018 17:51:20 -0500 Subject: [PATCH 28/53] Restrict fix to macOS only. Ref #3215. --- _pytest/doctest.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/_pytest/doctest.py b/_pytest/doctest.py index 112a700ec..6a2918b6f 100644 --- a/_pytest/doctest.py +++ b/_pytest/doctest.py @@ -3,6 +3,7 @@ from __future__ import absolute_import, division, print_function import traceback import sys +import platform import pytest from _pytest._code.code import ExceptionInfo, ReprFileLocation, TerminalRepr @@ -111,6 +112,8 @@ class DoctestItem(pytest.Item): """ Disable output capturing. Otherwise, stdout is lost to doctest (#985) """ + if platform.system() != 'Darwin': + return capman = self.config.pluginmanager.getplugin("capturemanager") if capman: out, err = capman.suspend_global_capture(in_=True) From 9849022eb2421c9e468609cbbbedf63a8a042702 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 14 Feb 2018 19:20:00 +0100 Subject: [PATCH 29/53] Remove "matching '...'" part from the pytest.raises message When a test with pytest.raises(ValueError, match='foo') doesn't raise, the following error is printed: Failed: DID NOT RAISE matching 'foo' This error message is confusing as it implies a ValueError was raised, but the message wasn't matching 'foo'. I first considered rewording it somehow to preserve the match pattern in it, but I don't think that's worthwhile as the pattern should usually be apparent from the stacktrace anyways (hard-coded, as parametrization, or with --showlocals for more sophisticated cases). --- _pytest/python_api.py | 1 - changelog/3222.trivial | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 changelog/3222.trivial diff --git a/_pytest/python_api.py b/_pytest/python_api.py index e6f002849..5bbf3ac7a 100644 --- a/_pytest/python_api.py +++ b/_pytest/python_api.py @@ -571,7 +571,6 @@ def raises(expected_exception, *args, **kwargs): message = kwargs.pop("message") if "match" in kwargs: match_expr = kwargs.pop("match") - message += " matching '{0}'".format(match_expr) return RaisesContext(expected_exception, message, match_expr) elif isinstance(args[0], str): code, = args diff --git a/changelog/3222.trivial b/changelog/3222.trivial new file mode 100644 index 000000000..fbbbd5510 --- /dev/null +++ b/changelog/3222.trivial @@ -0,0 +1 @@ +The "matching '...'" part got removed from ``pytest.raises()`` error messages as it falsely implies that an exception was raised but it didn't match. \ No newline at end of file From 3cbf0c8ec028e4fdba2e66327a2d4f01025804c4 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 15 Feb 2018 12:11:56 +0100 Subject: [PATCH 30/53] Raise unexpected exceptions with pytest.raises() using match= --- _pytest/python_api.py | 2 +- changelog/3222.bugfix | 1 + changelog/3222.trivial | 1 - testing/python/raises.py | 10 ++++++++++ 4 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 changelog/3222.bugfix delete mode 100644 changelog/3222.trivial diff --git a/_pytest/python_api.py b/_pytest/python_api.py index 5bbf3ac7a..89ef9e0a9 100644 --- a/_pytest/python_api.py +++ b/_pytest/python_api.py @@ -617,6 +617,6 @@ class RaisesContext(object): suppress_exception = issubclass(self.excinfo.type, self.expected_exception) if sys.version_info[0] == 2 and suppress_exception: sys.exc_clear() - if self.match_expr: + if self.match_expr and suppress_exception: self.excinfo.match(self.match_expr) return suppress_exception diff --git a/changelog/3222.bugfix b/changelog/3222.bugfix new file mode 100644 index 000000000..40a8e99e9 --- /dev/null +++ b/changelog/3222.bugfix @@ -0,0 +1 @@ +Errors shown when a ``pytest.raises()`` with ``match=`` fails are now cleaner on what happened: When no exception was raised, the "matching '...'" part got removed as it falsely implies that an exception was raised but it didn't match. When a wrong exception was raised, it's now thrown (like ``pytest.raised()`` without ``match=`` would) instead of complaining about the unmatched text. \ No newline at end of file diff --git a/changelog/3222.trivial b/changelog/3222.trivial deleted file mode 100644 index fbbbd5510..000000000 --- a/changelog/3222.trivial +++ /dev/null @@ -1 +0,0 @@ -The "matching '...'" part got removed from ``pytest.raises()`` error messages as it falsely implies that an exception was raised but it didn't match. \ No newline at end of file diff --git a/testing/python/raises.py b/testing/python/raises.py index 321ee349e..183259f6b 100644 --- a/testing/python/raises.py +++ b/testing/python/raises.py @@ -132,3 +132,13 @@ class TestRaises(object): with pytest.raises(AssertionError, match=expr): with pytest.raises(ValueError, match=msg): int('asdf', base=10) + + def test_raises_match_wrong_type(self): + """Raising an exception with the wrong type and match= given. + + pytest should throw the unexpected exception - the pattern match is not + really relevant if we got a different exception. + """ + with pytest.raises(ValueError): + with pytest.raises(IndexError, match='nomatch'): + int('asdf') From 435b8ddc7c681a4ba4490388756c7d49666ee8bf Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Thu, 15 Feb 2018 09:17:33 -0500 Subject: [PATCH 31/53] Rename method for additional clarity. --- _pytest/doctest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_pytest/doctest.py b/_pytest/doctest.py index 6a2918b6f..09de8ba41 100644 --- a/_pytest/doctest.py +++ b/_pytest/doctest.py @@ -105,10 +105,10 @@ class DoctestItem(pytest.Item): def runtest(self): _check_all_skipped(self.dtest) - self._disable_output_capturing() + self._disable_output_capturing_for_darwin() self.runner.run(self.dtest) - def _disable_output_capturing(self): + def _disable_output_capturing_for_darwin(self): """ Disable output capturing. Otherwise, stdout is lost to doctest (#985) """ From 82cdc487cefbbbd1848705427fed6276461835e6 Mon Sep 17 00:00:00 2001 From: Thomas Hisch Date: Thu, 15 Feb 2018 21:09:44 +0100 Subject: [PATCH 32/53] Fix raised warning when attrs 17.4.0 is used Related: #3223 --- _pytest/fixtures.py | 4 ++-- setup.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/_pytest/fixtures.py b/_pytest/fixtures.py index 2bc6f108b..27ecf37da 100644 --- a/_pytest/fixtures.py +++ b/_pytest/fixtures.py @@ -831,9 +831,9 @@ def _ensure_immutable_ids(ids): @attr.s(frozen=True) class FixtureFunctionMarker(object): scope = attr.ib() - params = attr.ib(convert=attr.converters.optional(tuple)) + params = attr.ib(converter=attr.converters.optional(tuple)) autouse = attr.ib(default=False) - ids = attr.ib(default=None, convert=_ensure_immutable_ids) + ids = attr.ib(default=None, converter=_ensure_immutable_ids) name = attr.ib(default=None) def __call__(self, function): diff --git a/setup.py b/setup.py index e08be845e..dcfac37c5 100644 --- a/setup.py +++ b/setup.py @@ -59,7 +59,7 @@ def main(): 'py>=1.5.0', 'six>=1.10.0', 'setuptools', - 'attrs>=17.2.0', + 'attrs>=17.4.0', ] # if _PYTEST_SETUP_SKIP_PLUGGY_DEP is set, skip installing pluggy; # used by tox.ini to test with pluggy master From df2f019997d77004a29d6f784f1fa76ef02265cf Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 15 Feb 2018 19:45:05 -0200 Subject: [PATCH 33/53] Slight rewording in the CHANGELOG --- changelog/3213.feature | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/3213.feature b/changelog/3213.feature index 755942f1b..1b25793a7 100644 --- a/changelog/3213.feature +++ b/changelog/3213.feature @@ -1 +1 @@ -Output item deselection count before tests are run. \ No newline at end of file +Deselected item count is now shown before tests are run, e.g. ``collected X items / Y deselected``. From 774c539f1a03b75b7b6c9b5188c5beeae9638640 Mon Sep 17 00:00:00 2001 From: Jordan Speicher Date: Fri, 9 Feb 2018 17:44:03 -0600 Subject: [PATCH 34/53] Add --deselect command line option Fixes #3198 --- AUTHORS | 1 + _pytest/main.py | 20 ++++++++++++++++++++ changelog/3198.feature.rst | 1 + doc/en/example/pythoncollection.rst | 8 ++++++++ testing/test_session.py | 14 ++++++++++++++ 5 files changed, 44 insertions(+) create mode 100644 changelog/3198.feature.rst diff --git a/AUTHORS b/AUTHORS index 3f43c7479..a4ce2e8e6 100644 --- a/AUTHORS +++ b/AUTHORS @@ -97,6 +97,7 @@ Jon Sonesen Jonas Obrist Jordan Guymon Jordan Moldow +Jordan Speicher Joshua Bronson Jurko Gospodnetić Justyna Janczyszyn diff --git a/_pytest/main.py b/_pytest/main.py index f3dbd4344..97c6e276c 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -66,6 +66,8 @@ def pytest_addoption(parser): help="try to interpret all arguments as python packages.") group.addoption("--ignore", action="append", metavar="path", help="ignore path during collection (multi-allowed).") + group.addoption("--deselect", action="append", metavar="nodeid_prefix", + help="deselect item during collection (multi-allowed).") # when changing this to --conf-cut-dir, config.py Conftest.setinitial # needs upgrading as well group.addoption('--confcutdir', dest="confcutdir", default=None, @@ -208,6 +210,24 @@ def pytest_ignore_collect(path, config): return False +def pytest_collection_modifyitems(items, config): + deselect_prefixes = tuple(config.getoption("deselect") or []) + if not deselect_prefixes: + return + + remaining = [] + deselected = [] + for colitem in items: + if colitem.nodeid.startswith(deselect_prefixes): + deselected.append(colitem) + else: + remaining.append(colitem) + + if deselected: + config.hook.pytest_deselected(items=deselected) + items[:] = remaining + + @contextlib.contextmanager def _patched_find_module(): """Patch bug in pkgutil.ImpImporter.find_module diff --git a/changelog/3198.feature.rst b/changelog/3198.feature.rst new file mode 100644 index 000000000..3c7838302 --- /dev/null +++ b/changelog/3198.feature.rst @@ -0,0 +1 @@ +Add command line option ``--deselect`` to allow deselection of individual tests at collection time. diff --git a/doc/en/example/pythoncollection.rst b/doc/en/example/pythoncollection.rst index c9d31d7c4..fc8dbf1b5 100644 --- a/doc/en/example/pythoncollection.rst +++ b/doc/en/example/pythoncollection.rst @@ -39,6 +39,14 @@ you will see that ``pytest`` only collects test-modules, which do not match the ======= 5 passed in 0.02 seconds ======= +Deselect tests during test collection +------------------------------------- + +Tests can individually be deselected during collection by passing the ``--deselect=item`` option. +For example, say ``tests/foobar/test_foobar_01.py`` contains ``test_a`` and ``test_b``. +You can run all of the tests within ``tests/`` *except* for ``tests/foobar/test_foobar_01.py::test_a`` +by invoking ``pytest`` with ``--deselect tests/foobar/test_foobar_01.py::test_a``. +``pytest`` allows multiple ``--deselect`` options. Keeping duplicate paths specified from command line ---------------------------------------------------- diff --git a/testing/test_session.py b/testing/test_session.py index 68534b102..32d8ce689 100644 --- a/testing/test_session.py +++ b/testing/test_session.py @@ -240,6 +240,20 @@ def test_exclude(testdir): result.stdout.fnmatch_lines(["*1 passed*"]) +def test_deselect(testdir): + testdir.makepyfile(test_a=""" + import pytest + def test_a1(): pass + @pytest.mark.parametrize('b', range(3)) + def test_a2(b): pass + """) + result = testdir.runpytest("-v", "--deselect=test_a.py::test_a2[1]", "--deselect=test_a.py::test_a2[2]") + assert result.ret == 0 + result.stdout.fnmatch_lines(["*2 passed, 2 deselected*"]) + for line in result.stdout.lines: + assert not line.startswith(('test_a.py::test_a2[1]', 'test_a.py::test_a2[2]')) + + def test_sessionfinish_with_start(testdir): testdir.makeconftest(""" import os From dfbaa20240bd1178496db117450aaaba7c0a913a Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 17 Feb 2018 09:24:13 -0200 Subject: [PATCH 35/53] Bring test_live_logs_unknown_sections directly due to merge conflicts --- testing/logging/test_reporting.py | 54 +++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/testing/logging/test_reporting.py b/testing/logging/test_reporting.py index 97b31521f..f84f7e459 100644 --- a/testing/logging/test_reporting.py +++ b/testing/logging/test_reporting.py @@ -294,6 +294,60 @@ def test_log_cli_default_level_sections(testdir, request): ]) +def test_live_logs_unknown_sections(testdir, request): + """Check that with live logging enable we are printing the correct headers during + start/setup/call/teardown/finish.""" + filename = request.node.name + '.py' + testdir.makeconftest(''' + import pytest + import logging + + def pytest_runtest_protocol(item, nextitem): + logging.warning('Unknown Section!') + + def pytest_runtest_logstart(): + logging.warning('>>>>> START >>>>>') + + def pytest_runtest_logfinish(): + logging.warning('<<<<< END <<<<<<<') + ''') + + testdir.makepyfile(''' + import pytest + import logging + + @pytest.fixture + def fix(request): + logging.warning("log message from setup of {}".format(request.node.name)) + yield + logging.warning("log message from teardown of {}".format(request.node.name)) + + def test_log_1(fix): + logging.warning("log message from test_log_1") + + ''') + testdir.makeini(''' + [pytest] + log_cli=true + ''') + + result = testdir.runpytest() + result.stdout.fnmatch_lines([ + '*WARNING*Unknown Section*', + '{}::test_log_1 '.format(filename), + '*WARNING* >>>>> START >>>>>*', + '*-- live log setup --*', + '*WARNING*log message from setup of test_log_1*', + '*-- live log call --*', + '*WARNING*log message from test_log_1*', + 'PASSED *100%*', + '*-- live log teardown --*', + '*WARNING*log message from teardown of test_log_1*', + '*WARNING* <<<<< END <<<<<<<*', + '=* 1 passed in *=', + ]) + + def test_sections_single_new_line_after_test_outcome(testdir, request): """Check that only a single new line is written between log messages during teardown/finish.""" From bfe2cbe875f81bd8e333796d2594a9cd36806374 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 9 Feb 2018 20:59:15 -0200 Subject: [PATCH 36/53] Implement publishing to PyPI by pushing a tag Fix #3060 --- .travis.yml | 20 +++++++++-- HOWTORELEASE.rst | 40 +++++++--------------- changelog/3060.trivial.rst | 1 + tasks/generate.py | 70 ++++---------------------------------- tasks/requirements.txt | 2 -- 5 files changed, 37 insertions(+), 96 deletions(-) create mode 100644 changelog/3060.trivial.rst diff --git a/.travis.yml b/.travis.yml index 40fe3e8ba..b0ed7bf29 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,10 +2,8 @@ sudo: false language: python python: - '3.6' -# command to install dependencies install: - pip install --upgrade --pre tox -# # command to run tests env: matrix: # coveralls is not listed in tox's envlist, but should run in travis @@ -29,7 +27,7 @@ env: - TOXENV=doctesting - TOXENV=docs -matrix: +jobs: include: - env: TOXENV=pypy python: 'pypy-5.4' @@ -40,6 +38,22 @@ matrix: - env: TOXENV=py37 python: 'nightly' + - stage: deploy + python: '3.6' + env: + install: pip install -U setuptools setuptools_scm + script: skip + deploy: + provider: pypi + user: nicoddemus + distributions: sdist bdist_wheel + skip_upload_docs: true + password: + secure: xanTgTUu6XDQVqB/0bwJQXoDMnU5tkwZc5koz6mBkkqZhKdNOi2CLoC1XhiSZ+ah24l4V1E0GAqY5kBBcy9d7NVe4WNg4tD095LsHw+CRU6/HCVIFfyk2IZ+FPAlguesCcUiJSXOrlBF+Wj68wEvLoK7EoRFbJeiZ/f91Ww1sbtDlqXABWGHrmhPJL5Wva7o7+wG7JwJowqdZg1pbQExsCc7b53w4v2RBu3D6TJaTAzHiVsW+nUSI67vKI/uf+cR/OixsTfy37wlHgSwihYmrYLFls3V0bSpahCim3bCgMaFZx8S8xrdgJ++PzBCof2HeflFKvW+VCkoYzGEG4NrTWJoNz6ni4red9GdvfjGH3YCjAKS56h9x58zp2E5rpsb/kVq5/45xzV+dq6JRuhQ1nJWjBC6fSKAc/bfwnuFK3EBxNLkvBssLHvsNjj5XG++cB8DdS9wVGUqjpoK4puaXUWFqy4q3S9F86HEsKNgExtieA9qNx+pCIZVs6JCXZNjr0I5eVNzqJIyggNgJG6RyravsU35t9Zd9doL5g4Y7UKmAGTn1Sz24HQ4sMQgXdm2SyD8gEK5je4tlhUvfGtDvMSlstq71kIn9nRpFnqB6MFlbYSEAZmo8dGbCquoUc++6Rum208wcVbrzzVtGlXB/Ow9AbFMYeAGA0+N/K1e59c= + on: + tags: true + repo: pytest-dev/pytest + script: tox --recreate notifications: diff --git a/HOWTORELEASE.rst b/HOWTORELEASE.rst index 9a251a8f0..97bddf720 100644 --- a/HOWTORELEASE.rst +++ b/HOWTORELEASE.rst @@ -22,44 +22,28 @@ taking a lot of time to make a new one. Ensure your are in a clean work tree. -#. Generate docs, changelog, announcements and upload a package to - your ``devpi`` staging server:: +#. Generate docs, changelog, announcements and a **local** tag:: - invoke generate.pre-release --password - - If ``--password`` is not given, it is assumed the user is already logged in ``devpi``. - If you don't have an account, please ask for one. + invoke generate.pre-release #. Open a PR for this branch targeting ``master``. -#. Test the package +#. After all tests pass and the PR has been approved, publish to PyPI by pushing the tag:: - * **Manual method** + git push git@github.com:pytest-dev/pytest.git - Run from multiple machines:: + Wait for the deploy to complete, then make sure it is `available on PyPI `_. - devpi use https://devpi.net/USER/dev - devpi test pytest==VERSION +#. Send an email announcement with the contents from:: - Check that tests pass for relevant combinations with:: + doc/en/announce/release-.rst - devpi list pytest + To the following mailing lists: - * **CI servers** + * pytest-dev@python.org (all releases) + * python-announce-list@python.org (all releases) + * testing-in-python@lists.idyll.org (only major/minor releases) - Configure a repository as per-instructions on - devpi-cloud-test_ to test the package on Travis_ and AppVeyor_. - All test environments should pass. - -#. Publish to PyPI:: - - invoke generate.publish-release - - where PYPI_NAME is the name of pypi.python.org as configured in your ``~/.pypirc`` - file `for devpi `_. + And announce it on `Twitter `_ with the ``#pytest`` hashtag. #. After a minor/major release, merge ``release-X.Y.Z`` into ``master`` and push (or open a PR). - -.. _devpi-cloud-test: https://github.com/obestwalter/devpi-cloud-test -.. _AppVeyor: https://www.appveyor.com/ -.. _Travis: https://travis-ci.org diff --git a/changelog/3060.trivial.rst b/changelog/3060.trivial.rst new file mode 100644 index 000000000..9ea9871e3 --- /dev/null +++ b/changelog/3060.trivial.rst @@ -0,0 +1 @@ +pytest has changed the publication procedure and is now being published to PyPI directly from Travis. diff --git a/tasks/generate.py b/tasks/generate.py index 5aa4752f5..bef2edf11 100644 --- a/tasks/generate.py +++ b/tasks/generate.py @@ -1,4 +1,6 @@ -import os +""" +Invoke development tasks. +""" from pathlib import Path from subprocess import check_output, check_call @@ -57,7 +59,7 @@ def regen(ctx): @invoke.task() def make_tag(ctx, version): - """Create a new (local) tag for the release, only if the repository is clean.""" + """Create a new, local tag for the release, only if the repository is clean.""" from git import Repo repo = Repo('.') @@ -74,81 +76,24 @@ def make_tag(ctx, version): repo.create_tag(version) -@invoke.task() -def devpi_upload(ctx, version, user, password=None): - """Creates and uploads a package to devpi for testing.""" - if password: - print("[generate.devpi_upload] devpi login {}".format(user)) - check_call(['devpi', 'login', user, '--password', password]) - - check_call(['devpi', 'use', 'https://devpi.net/{}/dev'.format(user)]) - - env = os.environ.copy() - env['SETUPTOOLS_SCM_PRETEND_VERSION'] = version - check_call(['devpi', 'upload', '--formats', 'sdist,bdist_wheel'], env=env) - print("[generate.devpi_upload] package uploaded") - - @invoke.task(help={ 'version': 'version being released', - 'user': 'name of the user on devpi to stage the generated package', - 'password': 'user password on devpi to stage the generated package ' - '(if not given assumed logged in)', }) -def pre_release(ctx, version, user, password=None): - """Generates new docs, release announcements and uploads a new release to devpi for testing.""" +def pre_release(ctx, version): + """Generates new docs, release announcements and creates a local tag.""" announce(ctx, version) regen(ctx) changelog(ctx, version, write_out=True) msg = 'Preparing release version {}'.format(version) check_call(['git', 'commit', '-a', '-m', msg]) - + make_tag(ctx, version) - devpi_upload(ctx, version=version, user=user, password=password) - print() print('[generate.pre_release] Please push your branch and open a PR.') -@invoke.task(help={ - 'version': 'version being released', - 'user': 'name of the user on devpi to stage the generated package', - 'pypi_name': 'name of the pypi configuration section in your ~/.pypirc', -}) -def publish_release(ctx, version, user, pypi_name): - """Publishes a package previously created by the 'pre_release' command.""" - from git import Repo - repo = Repo('.') - tag_names = [x.name for x in repo.tags] - if version not in tag_names: - print('Could not find tag for version {}, exiting...'.format(version)) - raise invoke.Exit(code=2) - - check_call(['devpi', 'use', 'https://devpi.net/{}/dev'.format(user)]) - check_call(['devpi', 'push', 'pytest=={}'.format(version), 'pypi:{}'.format(pypi_name)]) - check_call(['git', 'push', 'git@github.com:pytest-dev/pytest.git', version]) - - emails = [ - 'pytest-dev@python.org', - 'python-announce-list@python.org' - ] - if version.endswith('.0'): - emails.append('testing-in-python@lists.idyll.org') - print('Version {} has been published to PyPI!'.format(version)) - print() - print('Please send an email announcement with the contents from:') - print() - print(' doc/en/announce/release-{}.rst'.format(version)) - print() - print('To the following mail lists:') - print() - print(' ', ','.join(emails)) - print() - print('And announce it on twitter adding the #pytest hash tag.') - - @invoke.task(help={ 'version': 'version being released', 'write_out': 'write changes to the actual changelog' @@ -159,4 +104,3 @@ def changelog(ctx, version, write_out=False): else: addopts = ['--draft'] check_call(['towncrier', '--version', version] + addopts) - diff --git a/tasks/requirements.txt b/tasks/requirements.txt index be4bff990..7f41521e6 100644 --- a/tasks/requirements.txt +++ b/tasks/requirements.txt @@ -1,6 +1,4 @@ -devpi-client gitpython invoke towncrier tox -wheel From 0f6879bf5e55ea2dd323c25dba975ab8c864435f Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 17 Feb 2018 10:20:41 -0200 Subject: [PATCH 37/53] Small update to changelog/README.rst --- changelog/README.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/changelog/README.rst b/changelog/README.rst index f00720de9..35d3a40ed 100644 --- a/changelog/README.rst +++ b/changelog/README.rst @@ -1,10 +1,12 @@ -This directory contains "newsfragments" which are short that contain a small **ReST**-formatted +This directory contains "newsfragments" which are short files that contain a small **ReST**-formatted text that will be added to the next ``CHANGELOG``. The ``CHANGELOG`` will be read by users, so this description should be aimed to pytest users instead of describing internal changes which are only relevant to the developers. -Make sure to use full sentences with correct case and punctuation, for example: *Fix issue with non-ascii contents in doctest text files.* +Make sure to use full sentences with correct case and punctuation, for example:: + + Fix issue with non-ascii messages from the ``warnings`` module. Each file should be named like ``..rst``, where ```` is an issue number, and ```` is one of: From 254e357076a8826d19219a769ea909c28c1d6f35 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 17 Feb 2018 12:10:29 -0500 Subject: [PATCH 38/53] Correct the broken indentation. --- _pytest/doctest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_pytest/doctest.py b/_pytest/doctest.py index 09de8ba41..d61efebe7 100644 --- a/_pytest/doctest.py +++ b/_pytest/doctest.py @@ -118,7 +118,7 @@ class DoctestItem(pytest.Item): if capman: out, err = capman.suspend_global_capture(in_=True) sys.stdout.write(out) - sys.stdout.write(err) + sys.stdout.write(err) def repr_failure(self, excinfo): import doctest From 4131d3f300196da059f5abe6d36f4870459692ad Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sat, 17 Feb 2018 12:13:33 -0500 Subject: [PATCH 39/53] Probably it's best to write the err stream to stderr. --- _pytest/doctest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_pytest/doctest.py b/_pytest/doctest.py index d61efebe7..f54f833ec 100644 --- a/_pytest/doctest.py +++ b/_pytest/doctest.py @@ -118,7 +118,7 @@ class DoctestItem(pytest.Item): if capman: out, err = capman.suspend_global_capture(in_=True) sys.stdout.write(out) - sys.stdout.write(err) + sys.stderr.write(err) def repr_failure(self, excinfo): import doctest From 069f32a8c452e7dbd4d10d4da2c142dd24bb5953 Mon Sep 17 00:00:00 2001 From: Brian Maissy Date: Mon, 12 Feb 2018 22:05:46 +0200 Subject: [PATCH 40/53] print captured logs before entering pdb --- _pytest/debugging.py | 5 +++++ changelog/3204.feature | 1 + testing/test_pdb.py | 16 ++++++++++++++++ 3 files changed, 22 insertions(+) create mode 100644 changelog/3204.feature diff --git a/_pytest/debugging.py b/_pytest/debugging.py index 43472f23b..d74cbe186 100644 --- a/_pytest/debugging.py +++ b/_pytest/debugging.py @@ -97,6 +97,11 @@ def _enter_pdb(node, excinfo, rep): tw.sep(">", "captured stderr") tw.line(captured_stderr) + captured_logs = rep.caplog + if len(captured_logs) > 0: + tw.sep(">", "captured logs") + tw.line(captured_logs) + tw.sep(">", "traceback") rep.toterminal(tw) tw.sep(">", "entering PDB") diff --git a/changelog/3204.feature b/changelog/3204.feature new file mode 100644 index 000000000..8ab129a12 --- /dev/null +++ b/changelog/3204.feature @@ -0,0 +1 @@ +Captured logs are printed before entering pdb. diff --git a/testing/test_pdb.py b/testing/test_pdb.py index d882c2cf6..f6d03d6bb 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -187,6 +187,22 @@ class TestPDB(object): assert "captured stderr" not in output self.flush(child) + def test_pdb_print_captured_logs(self, testdir): + p1 = testdir.makepyfile(""" + def test_1(): + import logging + logging.warn("get rekt") + assert False + """) + child = testdir.spawn_pytest("--pdb %s" % p1) + child.expect("captured logs") + child.expect("get rekt") + child.expect("(Pdb)") + child.sendeof() + rest = child.read().decode("utf8") + assert "1 failed" in rest + self.flush(child) + def test_pdb_interaction_exception(self, testdir): p1 = testdir.makepyfile(""" import pytest From 81fa547fa8efddbf3665c7c5a4a6a6146239e3ab Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 17 Feb 2018 20:18:32 -0200 Subject: [PATCH 41/53] Add CHANGELOG entry about changed attrs req --- changelog/3228.trivial.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/3228.trivial.rst diff --git a/changelog/3228.trivial.rst b/changelog/3228.trivial.rst new file mode 100644 index 000000000..8b69e25b4 --- /dev/null +++ b/changelog/3228.trivial.rst @@ -0,0 +1 @@ +Change minimum requirement of ``attrs`` to ``17.4.0``. From 51ece00923885fa41107e7469d510cb2233406af Mon Sep 17 00:00:00 2001 From: Thomas Hisch Date: Sun, 18 Feb 2018 12:42:25 +0100 Subject: [PATCH 42/53] Add captured-log support to --show-capture Fixes: #3233 --- _pytest/debugging.py | 22 ++++++++------------ _pytest/terminal.py | 14 ++++++------- changelog/1478.feature | 2 +- testing/test_pdb.py | 12 ++++++----- testing/test_terminal.py | 44 +++++++++++++++++++++++++++------------- 5 files changed, 54 insertions(+), 40 deletions(-) diff --git a/_pytest/debugging.py b/_pytest/debugging.py index d74cbe186..fada117e5 100644 --- a/_pytest/debugging.py +++ b/_pytest/debugging.py @@ -87,20 +87,16 @@ def _enter_pdb(node, excinfo, rep): tw = node.config.pluginmanager.getplugin("terminalreporter")._tw tw.line() - captured_stdout = rep.capstdout - if len(captured_stdout) > 0: - tw.sep(">", "captured stdout") - tw.line(captured_stdout) + showcapture = node.config.option.showcapture - captured_stderr = rep.capstderr - if len(captured_stderr) > 0: - tw.sep(">", "captured stderr") - tw.line(captured_stderr) - - captured_logs = rep.caplog - if len(captured_logs) > 0: - tw.sep(">", "captured logs") - tw.line(captured_logs) + for sectionname, content in (('stdout', rep.capstdout), + ('stderr', rep.capstderr), + ('log', rep.caplog)): + if showcapture in (sectionname, 'all') and content: + tw.sep(">", "captured " + sectionname) + if content[-1:] == "\n": + content = content[:-1] + tw.line(content) tw.sep(">", "traceback") rep.toterminal(tw) diff --git a/_pytest/terminal.py b/_pytest/terminal.py index 69d4ab8ad..55a632b22 100644 --- a/_pytest/terminal.py +++ b/_pytest/terminal.py @@ -44,9 +44,9 @@ def pytest_addoption(parser): help="traceback print mode (auto/long/short/line/native/no).") group._addoption('--show-capture', action="store", dest="showcapture", - choices=['no', 'stdout', 'stderr', 'both'], default='both', - help="Controls how captured stdout/stderr is shown on failed tests. " - "Default is 'both'.") + choices=['no', 'stdout', 'stderr', 'log', 'all'], default='all', + help="Controls how captured stdout/stderr/log is shown on failed tests. " + "Default is 'all'.") group._addoption('--fulltrace', '--full-trace', action="store_true", default=False, help="don't cut any tracebacks (default is to cut).") @@ -630,12 +630,12 @@ class TerminalReporter(object): def _outrep_summary(self, rep): rep.toterminal(self._tw) - if self.config.option.showcapture == 'no': + showcapture = self.config.option.showcapture + if showcapture == 'no': return for secname, content in rep.sections: - if self.config.option.showcapture != 'both': - if not (self.config.option.showcapture in secname): - continue + if showcapture != 'all' and showcapture not in secname: + continue self._tw.sep("-", secname) if content[-1:] == "\n": content = content[:-1] diff --git a/changelog/1478.feature b/changelog/1478.feature index de6bd3118..defc79b9b 100644 --- a/changelog/1478.feature +++ b/changelog/1478.feature @@ -1 +1 @@ -New ``--show-capture`` command-line option that allows to specify how to display captured output when tests fail: ``no``, ``stdout``, ``stderr`` or ``both`` (the default). \ No newline at end of file +New ``--show-capture`` command-line option that allows to specify how to display captured output when tests fail: ``no``, ``stdout``, ``stderr``, ``log`` or ``all`` (the default). diff --git a/testing/test_pdb.py b/testing/test_pdb.py index f6d03d6bb..fa3d86d31 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -187,16 +187,18 @@ class TestPDB(object): assert "captured stderr" not in output self.flush(child) - def test_pdb_print_captured_logs(self, testdir): + @pytest.mark.parametrize('showcapture', ['all', 'no', 'log']) + def test_pdb_print_captured_logs(self, testdir, showcapture): p1 = testdir.makepyfile(""" def test_1(): import logging - logging.warn("get rekt") + logging.warn("get " + "rekt") assert False """) - child = testdir.spawn_pytest("--pdb %s" % p1) - child.expect("captured logs") - child.expect("get rekt") + child = testdir.spawn_pytest("--show-capture=%s --pdb %s" % (showcapture, p1)) + if showcapture in ('all', 'log'): + child.expect("captured log") + child.expect("get rekt") child.expect("(Pdb)") child.sendeof() rest = child.read().decode("utf8") diff --git a/testing/test_terminal.py b/testing/test_terminal.py index f23dffe25..b3ea01709 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -851,31 +851,47 @@ def pytest_report_header(config, startdir): def test_show_capture(self, testdir): testdir.makepyfile(""" import sys + import logging def test_one(): sys.stdout.write('!This is stdout!') sys.stderr.write('!This is stderr!') + logging.warning('!This is a warning log msg!') assert False, 'Something failed' """) result = testdir.runpytest("--tb=short") - result.stdout.fnmatch_lines(["!This is stdout!"]) - result.stdout.fnmatch_lines(["!This is stderr!"]) + result.stdout.fnmatch_lines(["!This is stdout!", + "!This is stderr!", + "*WARNING*!This is a warning log msg!"]) - result = testdir.runpytest("--show-capture=both", "--tb=short") - result.stdout.fnmatch_lines(["!This is stdout!"]) - result.stdout.fnmatch_lines(["!This is stderr!"]) + result = testdir.runpytest("--show-capture=all", "--tb=short") + result.stdout.fnmatch_lines(["!This is stdout!", + "!This is stderr!", + "*WARNING*!This is a warning log msg!"]) - result = testdir.runpytest("--show-capture=stdout", "--tb=short") - assert "!This is stderr!" not in result.stdout.str() - assert "!This is stdout!" in result.stdout.str() + stdout = testdir.runpytest( + "--show-capture=stdout", "--tb=short").stdout.str() + assert "!This is stderr!" not in stdout + assert "!This is stdout!" in stdout + assert "!This is a warning log msg!" not in stdout - result = testdir.runpytest("--show-capture=stderr", "--tb=short") - assert "!This is stdout!" not in result.stdout.str() - assert "!This is stderr!" in result.stdout.str() + stdout = testdir.runpytest( + "--show-capture=stderr", "--tb=short").stdout.str() + assert "!This is stdout!" not in stdout + assert "!This is stderr!" in stdout + assert "!This is a warning log msg!" not in stdout - result = testdir.runpytest("--show-capture=no", "--tb=short") - assert "!This is stdout!" not in result.stdout.str() - assert "!This is stderr!" not in result.stdout.str() + stdout = testdir.runpytest( + "--show-capture=log", "--tb=short").stdout.str() + assert "!This is stdout!" not in stdout + assert "!This is stderr!" not in stdout + assert "!This is a warning log msg!" in stdout + + stdout = testdir.runpytest( + "--show-capture=no", "--tb=short").stdout.str() + assert "!This is stdout!" not in stdout + assert "!This is stderr!" not in stdout + assert "!This is a warning log msg!" not in stdout @pytest.mark.xfail("not hasattr(os, 'dup')") From ac7eb63a6b9899250ebd58f61f348ef69bf55875 Mon Sep 17 00:00:00 2001 From: Thomas Hisch Date: Sun, 18 Feb 2018 20:48:07 +0100 Subject: [PATCH 43/53] Remove --no-print-logs option This option is superseded by the --show-capture option. With --no-print-logs it was possible to only disable the reporting of captured logs, which is no longer possible with --show-capture. If --show-capture=no is used, no captured content (stdout, stderr and logs) is reported for failed tests. --- _pytest/logging.py | 13 ++------ doc/en/logging.rst | 22 ++----------- testing/logging/test_reporting.py | 54 ------------------------------- 3 files changed, 6 insertions(+), 83 deletions(-) diff --git a/_pytest/logging.py b/_pytest/logging.py index d3ac81e65..b719f3a79 100644 --- a/_pytest/logging.py +++ b/_pytest/logging.py @@ -84,11 +84,6 @@ def pytest_addoption(parser): help='default value for ' + option) group.addoption(option, dest=dest, **kwargs) - add_option_ini( - '--no-print-logs', - dest='log_print', action='store_const', const=False, default=True, - type='bool', - help='disable printing caught logs on failed tests.') add_option_ini( '--log-level', dest='log_level', default=None, @@ -343,7 +338,6 @@ class LoggingPlugin(object): assert self._config.pluginmanager.get_plugin('terminalreporter') is None config.option.verbose = 1 - self.print_logs = get_option_ini(config, 'log_print') self.formatter = logging.Formatter(get_option_ini(config, 'log_format'), get_option_ini(config, 'log_date_format')) self.log_level = get_actual_log_level(config, 'log_level') @@ -394,10 +388,9 @@ class LoggingPlugin(object): if when == 'teardown': del item.catch_log_handlers - if self.print_logs: - # Add a captured log section to the report. - log = log_handler.stream.getvalue().strip() - item.add_report_section(when, 'log', log) + # Add a captured log section to the report. + log = log_handler.stream.getvalue().strip() + item.add_report_section(when, 'log', log) @pytest.hookimpl(hookwrapper=True) def pytest_runtest_setup(self, item): diff --git a/doc/en/logging.rst b/doc/en/logging.rst index 82119043b..ad59be83f 100644 --- a/doc/en/logging.rst +++ b/doc/en/logging.rst @@ -50,26 +50,10 @@ These options can also be customized through ``pytest.ini`` file: log_format = %(asctime)s %(levelname)s %(message)s log_date_format = %Y-%m-%d %H:%M:%S -Further it is possible to disable reporting logs on failed tests completely -with:: +Further it is possible to disable reporting of captured content (stdout, +stderr and logs) on failed tests completely with:: - pytest --no-print-logs - -Or in the ``pytest.ini`` file: - -.. code-block:: ini - - [pytest] - log_print = False - - -Shows failed tests in the normal manner as no logs were captured:: - - ----------------------- Captured stdout call ---------------------- - text going to stdout - ----------------------- Captured stderr call ---------------------- - text going to stderr - ==================== 2 failed in 0.02 seconds ===================== + pytest --show-capture=no caplog fixture diff --git a/testing/logging/test_reporting.py b/testing/logging/test_reporting.py index f84f7e459..7f4c3f17d 100644 --- a/testing/logging/test_reporting.py +++ b/testing/logging/test_reporting.py @@ -91,60 +91,6 @@ def test_teardown_logging(testdir): '*text going to logger from teardown*']) -def test_disable_log_capturing(testdir): - testdir.makepyfile(''' - import sys - import logging - - logger = logging.getLogger(__name__) - - def test_foo(): - sys.stdout.write('text going to stdout') - logger.warning('catch me if you can!') - sys.stderr.write('text going to stderr') - assert False - ''') - result = testdir.runpytest('--no-print-logs') - print(result.stdout) - assert result.ret == 1 - result.stdout.fnmatch_lines(['*- Captured stdout call -*', - 'text going to stdout']) - result.stdout.fnmatch_lines(['*- Captured stderr call -*', - 'text going to stderr']) - with pytest.raises(pytest.fail.Exception): - result.stdout.fnmatch_lines(['*- Captured *log call -*']) - - -def test_disable_log_capturing_ini(testdir): - testdir.makeini( - ''' - [pytest] - log_print=False - ''' - ) - testdir.makepyfile(''' - import sys - import logging - - logger = logging.getLogger(__name__) - - def test_foo(): - sys.stdout.write('text going to stdout') - logger.warning('catch me if you can!') - sys.stderr.write('text going to stderr') - assert False - ''') - result = testdir.runpytest() - print(result.stdout) - assert result.ret == 1 - result.stdout.fnmatch_lines(['*- Captured stdout call -*', - 'text going to stdout']) - result.stdout.fnmatch_lines(['*- Captured stderr call -*', - 'text going to stderr']) - with pytest.raises(pytest.fail.Exception): - result.stdout.fnmatch_lines(['*- Captured *log call -*']) - - @pytest.mark.parametrize('enabled', [True, False]) def test_log_cli_enabled_disabled(testdir, enabled): msg = 'critical message logged by test' From acda6c46fb188514738bbb90f17279c11daa0510 Mon Sep 17 00:00:00 2001 From: Thomas Hisch Date: Mon, 19 Feb 2018 20:34:11 +0100 Subject: [PATCH 44/53] Partially revert "Remove --no-print-logs option" We'll deprecate --no-print-logs beginning with pytest-4.0. This reverts commit ac7eb63a6b9899250ebd58f61f348ef69bf55875. --- _pytest/logging.py | 13 ++++++-- testing/logging/test_reporting.py | 54 +++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/_pytest/logging.py b/_pytest/logging.py index b719f3a79..d3ac81e65 100644 --- a/_pytest/logging.py +++ b/_pytest/logging.py @@ -84,6 +84,11 @@ def pytest_addoption(parser): help='default value for ' + option) group.addoption(option, dest=dest, **kwargs) + add_option_ini( + '--no-print-logs', + dest='log_print', action='store_const', const=False, default=True, + type='bool', + help='disable printing caught logs on failed tests.') add_option_ini( '--log-level', dest='log_level', default=None, @@ -338,6 +343,7 @@ class LoggingPlugin(object): assert self._config.pluginmanager.get_plugin('terminalreporter') is None config.option.verbose = 1 + self.print_logs = get_option_ini(config, 'log_print') self.formatter = logging.Formatter(get_option_ini(config, 'log_format'), get_option_ini(config, 'log_date_format')) self.log_level = get_actual_log_level(config, 'log_level') @@ -388,9 +394,10 @@ class LoggingPlugin(object): if when == 'teardown': del item.catch_log_handlers - # Add a captured log section to the report. - log = log_handler.stream.getvalue().strip() - item.add_report_section(when, 'log', log) + if self.print_logs: + # Add a captured log section to the report. + log = log_handler.stream.getvalue().strip() + item.add_report_section(when, 'log', log) @pytest.hookimpl(hookwrapper=True) def pytest_runtest_setup(self, item): diff --git a/testing/logging/test_reporting.py b/testing/logging/test_reporting.py index 7f4c3f17d..f84f7e459 100644 --- a/testing/logging/test_reporting.py +++ b/testing/logging/test_reporting.py @@ -91,6 +91,60 @@ def test_teardown_logging(testdir): '*text going to logger from teardown*']) +def test_disable_log_capturing(testdir): + testdir.makepyfile(''' + import sys + import logging + + logger = logging.getLogger(__name__) + + def test_foo(): + sys.stdout.write('text going to stdout') + logger.warning('catch me if you can!') + sys.stderr.write('text going to stderr') + assert False + ''') + result = testdir.runpytest('--no-print-logs') + print(result.stdout) + assert result.ret == 1 + result.stdout.fnmatch_lines(['*- Captured stdout call -*', + 'text going to stdout']) + result.stdout.fnmatch_lines(['*- Captured stderr call -*', + 'text going to stderr']) + with pytest.raises(pytest.fail.Exception): + result.stdout.fnmatch_lines(['*- Captured *log call -*']) + + +def test_disable_log_capturing_ini(testdir): + testdir.makeini( + ''' + [pytest] + log_print=False + ''' + ) + testdir.makepyfile(''' + import sys + import logging + + logger = logging.getLogger(__name__) + + def test_foo(): + sys.stdout.write('text going to stdout') + logger.warning('catch me if you can!') + sys.stderr.write('text going to stderr') + assert False + ''') + result = testdir.runpytest() + print(result.stdout) + assert result.ret == 1 + result.stdout.fnmatch_lines(['*- Captured stdout call -*', + 'text going to stdout']) + result.stdout.fnmatch_lines(['*- Captured stderr call -*', + 'text going to stderr']) + with pytest.raises(pytest.fail.Exception): + result.stdout.fnmatch_lines(['*- Captured *log call -*']) + + @pytest.mark.parametrize('enabled', [True, False]) def test_log_cli_enabled_disabled(testdir, enabled): msg = 'critical message logged by test' From f8fdf0ae915e3798c876e780fb15263ec18caf97 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 20 Feb 2018 01:43:20 +0000 Subject: [PATCH 45/53] Add --yes to towncrier to automatically remove files --- tasks/generate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks/generate.py b/tasks/generate.py index bef2edf11..268b36fd6 100644 --- a/tasks/generate.py +++ b/tasks/generate.py @@ -103,4 +103,4 @@ def changelog(ctx, version, write_out=False): addopts = [] else: addopts = ['--draft'] - check_call(['towncrier', '--version', version] + addopts) + check_call(['towncrier', '--yes', '--version', version] + addopts) From b3193755927d38816e25777d9ac6776637d82bbf Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 20 Feb 2018 01:43:59 +0000 Subject: [PATCH 46/53] Preparing release version 3.4.1 --- CHANGELOG.rst | 61 +++++++++++++++++++++++++++++++ doc/en/announce/index.rst | 1 + doc/en/announce/release-3.4.1.rst | 27 ++++++++++++++ doc/en/example/reportingdemo.rst | 2 +- doc/en/example/simple.rst | 4 +- 5 files changed, 92 insertions(+), 3 deletions(-) create mode 100644 doc/en/announce/release-3.4.1.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fa0759437..6ef104e5f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,67 @@ .. towncrier release notes start +Pytest 3.4.1 (2018-02-20) +========================= + +Bug Fixes +--------- + +- Move import of ``doctest.UnexpectedException`` to top-level to avoid possible + errors when using ``--pdb``. (`#1810 + `_) + +- Added printing of captured stdout/stderr before entering pdb, and improved a + test which was giving false negatives about output capturing. (`#3052 + `_) + +- Fix ordering of tests using parametrized fixtures which can lead to fixtures + being created more than necessary. (`#3161 + `_) + +- Fix bug where logging happening at hooks outside of "test run" hooks would + cause an internal error. (`#3184 + `_) + +- Detect arguments injected by ``unittest.mock.patch`` decorator correctly when + pypi ``mock.patch`` is installed and imported. (`#3206 + `_) + +- Errors shown when a ``pytest.raises()`` with ``match=`` fails are now cleaner + on what happened: When no exception was raised, the "matching '...'" part got + removed as it falsely implies that an exception was raised but it didn't + match. When a wrong exception was raised, it's now thrown (like + ``pytest.raised()`` without ``match=`` would) instead of complaining about + the unmatched text. (`#3222 + `_) + +- Fixed output capture handling in doctests on macOS. (`#985 + `_) + + +Improved Documentation +---------------------- + +- Add Sphinx parameter docs for ``match`` and ``message`` args to + ``pytest.raises``. (`#3202 + `_) + + +Trivial/Internal Changes +------------------------ + +- pytest has changed the publication procedure and is now being published to + PyPI directly from Travis. (`#3060 + `_) + +- Rename ``ParameterSet._for_parameterize()`` to ``_for_parametrize()`` in + order to comply with the naming convention. (`#3166 + `_) + +- Skip failing pdb/doctest test on mac. (`#985 + `_) + + Pytest 3.4.0 (2018-01-30) ========================= diff --git a/doc/en/announce/index.rst b/doc/en/announce/index.rst index 4f3ec8b4e..b130f52bd 100644 --- a/doc/en/announce/index.rst +++ b/doc/en/announce/index.rst @@ -6,6 +6,7 @@ Release announcements :maxdepth: 2 + release-3.4.1 release-3.4.0 release-3.3.2 release-3.3.1 diff --git a/doc/en/announce/release-3.4.1.rst b/doc/en/announce/release-3.4.1.rst new file mode 100644 index 000000000..0c5932e62 --- /dev/null +++ b/doc/en/announce/release-3.4.1.rst @@ -0,0 +1,27 @@ +pytest-3.4.1 +======================================= + +pytest 3.4.1 has just been released to PyPI. + +This is a bug-fix release, being a drop-in replacement. To upgrade:: + + pip install --upgrade pytest + +The full changelog is available at http://doc.pytest.org/en/latest/changelog.html. + +Thanks to all who contributed to this release, among them: + +* Aaron +* Alan Velasco +* Andy Freeland +* Brian Maissy +* Bruno Oliveira +* Florian Bruhin +* Jason R. Coombs +* Marcin Bachry +* Pedro Algarvio +* Ronny Pfannschmidt + + +Happy testing, +The pytest Development Team diff --git a/doc/en/example/reportingdemo.rst b/doc/en/example/reportingdemo.rst index b0c25dedc..eb60bf85e 100644 --- a/doc/en/example/reportingdemo.rst +++ b/doc/en/example/reportingdemo.rst @@ -358,7 +358,7 @@ get on the terminal - we are working on that):: > 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:580>:1: ValueError + <0-codegen $PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/python_api.py:583>:1: ValueError ______________________ TestRaises.test_raises_doesnt _______________________ self = diff --git a/doc/en/example/simple.rst b/doc/en/example/simple.rst index ffc68b296..8a2a92bf6 100644 --- a/doc/en/example/simple.rst +++ b/doc/en/example/simple.rst @@ -385,8 +385,8 @@ Now we can profile which test functions execute the slowest:: test_some_are_slow.py ... [100%] ========================= slowest 3 test durations ========================= - 0.58s call test_some_are_slow.py::test_funcslow2 - 0.41s call test_some_are_slow.py::test_funcslow1 + 0.30s call test_some_are_slow.py::test_funcslow2 + 0.20s call test_some_are_slow.py::test_funcslow1 0.10s call test_some_are_slow.py::test_funcfast ========================= 3 passed in 0.12 seconds ========================= From 8b49ddfa585e8dee77ba3aeef654598e0052237e Mon Sep 17 00:00:00 2001 From: Carlos Jenkins Date: Wed, 16 Aug 2017 05:23:28 -0600 Subject: [PATCH 47/53] Renamed the fixture record_xml_property to record_property and adapted logic so that the properties are passed to the TestReport object and thus allow compatibility with pytest-xdist. --- AUTHORS | 1 + _pytest/deprecated.py | 6 +++++ _pytest/junitxml.py | 44 ++++++++++++++++++++----------- _pytest/nodes.py | 4 +++ _pytest/runner.py | 8 +++++- changelog/2770.feature | 2 ++ doc/en/builtin.rst | 9 ++++--- doc/en/example/simple.rst | 2 +- doc/en/usage.rst | 54 +++++++++++++++++++++++++++++++-------- testing/test_junitxml.py | 16 ++++++------ 10 files changed, 107 insertions(+), 39 deletions(-) create mode 100644 changelog/2770.feature diff --git a/AUTHORS b/AUTHORS index cda6511a0..a008ba981 100644 --- a/AUTHORS +++ b/AUTHORS @@ -35,6 +35,7 @@ Brianna Laugher Bruno Oliveira Cal Leeming Carl Friedrich Bolz +Carlos Jenkins Ceridwen Charles Cloud Charnjit SiNGH (CCSJ) diff --git a/_pytest/deprecated.py b/_pytest/deprecated.py index 9c0fbeca7..aa1235013 100644 --- a/_pytest/deprecated.py +++ b/_pytest/deprecated.py @@ -41,6 +41,12 @@ MARK_PARAMETERSET_UNPACKING = RemovedInPytest4Warning( "For more details, see: https://docs.pytest.org/en/latest/parametrize.html" ) +RECORD_XML_PROPERTY = ( + 'Fixture renamed from "record_xml_property" to "record_property" as user ' + 'properties are now available to all reporters.\n' + '"record_xml_property" is now deprecated.' +) + COLLECTOR_MAKEITEM = RemovedInPytest4Warning( "pycollector makeitem was removed " "as it is an accidentially leaked internal api" diff --git a/_pytest/junitxml.py b/_pytest/junitxml.py index a8cea6fc1..22686313a 100644 --- a/_pytest/junitxml.py +++ b/_pytest/junitxml.py @@ -233,31 +233,41 @@ class _NodeReporter(object): @pytest.fixture -def record_xml_property(request): - """Add extra xml properties to the tag for the calling test. - The fixture is callable with ``(name, value)``, with value being automatically - xml-encoded. +def record_property(request): + """Add an extra properties the calling test. + User properties become part of the test report and are available to the + configured reporters, like JUnit XML. + The fixture is callable with ``(name, value)``. """ request.node.warn( code='C3', - message='record_xml_property is an experimental feature', + message='record_property is an experimental feature', ) - xml = getattr(request.config, "_xml", None) - if xml is not None: - node_reporter = xml.node_reporter(request.node.nodeid) - return node_reporter.add_property - else: - def add_property_noop(name, value): - pass - return add_property_noop + def append_property(name, value): + request.node.user_properties.append((name, value)) + return append_property + + +@pytest.fixture +def record_xml_property(request): + """(Deprecated) use record_property.""" + import warnings + from _pytest import deprecated + warnings.warn( + deprecated.RECORD_XML_PROPERTY, + DeprecationWarning, + stacklevel=2 + ) + + return record_property(request) @pytest.fixture def record_xml_attribute(request): """Add extra xml attributes to the tag for the calling test. - The fixture is callable with ``(name, value)``, with value being automatically - xml-encoded + The fixture is callable with ``(name, value)``, with value being + automatically xml-encoded """ request.node.warn( code='C3', @@ -442,6 +452,10 @@ class LogXML(object): if report.when == "teardown": reporter = self._opentestcase(report) reporter.write_captured_output(report) + + for propname, propvalue in report.user_properties: + reporter.add_property(propname, propvalue) + self.finalize(report) report_wid = getattr(report, "worker_id", None) report_ii = getattr(report, "item_index", None) diff --git a/_pytest/nodes.py b/_pytest/nodes.py index e836cd4d6..7d802004f 100644 --- a/_pytest/nodes.py +++ b/_pytest/nodes.py @@ -360,6 +360,10 @@ class Item(Node): super(Item, self).__init__(name, parent, config, session) self._report_sections = [] + #: user properties is a list of tuples (name, value) that holds user + #: defined properties for this test. + self.user_properties = [] + def add_report_section(self, when, key, content): """ Adds a new report section, similar to what's done internally to add stdout and diff --git a/_pytest/runner.py b/_pytest/runner.py index b41a3d350..e8aac76fc 100644 --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -317,6 +317,7 @@ def pytest_runtest_makereport(item, call): sections.append(("Captured %s %s" % (key, rwhen), content)) return TestReport(item.nodeid, item.location, keywords, outcome, longrepr, when, + item.user_properties, sections, duration) @@ -326,7 +327,8 @@ class TestReport(BaseReport): """ def __init__(self, nodeid, location, keywords, outcome, - longrepr, when, sections=(), duration=0, **extra): + longrepr, when, user_properties, + sections=(), duration=0, **extra): #: normalized collection node id self.nodeid = nodeid @@ -348,6 +350,10 @@ class TestReport(BaseReport): #: one of 'setup', 'call', 'teardown' to indicate runtest phase. self.when = when + #: user properties is a list of tuples (name, value) that holds user + #: defined properties of the test + self.user_properties = user_properties + #: list of pairs ``(str, str)`` of extra information which needs to #: marshallable. Used by pytest to add captured text #: from ``stdout`` and ``stderr``, but may be used by other plugins diff --git a/changelog/2770.feature b/changelog/2770.feature new file mode 100644 index 000000000..248f2893d --- /dev/null +++ b/changelog/2770.feature @@ -0,0 +1,2 @@ +``record_xml_property`` renamed to ``record_property`` and is now compatible with xdist, markers and any reporter. +``record_xml_property`` name is now deprecated. diff --git a/doc/en/builtin.rst b/doc/en/builtin.rst index a380b9abd..ba033849c 100644 --- a/doc/en/builtin.rst +++ b/doc/en/builtin.rst @@ -112,10 +112,11 @@ You can ask for available builtin or project-custom Inject names into the doctest namespace. pytestconfig the pytest config object with access to command line opts. - record_xml_property - Add extra xml properties to the tag for the calling test. - The fixture is callable with ``(name, value)``, with value being automatically - xml-encoded. + record_property + Add an extra properties the calling test. + User properties become part of the test report and are available to the + configured reporters, like JUnit XML. + The fixture is callable with ``(name, value)``. record_xml_attribute Add extra xml attributes to the tag for the calling test. The fixture is callable with ``(name, value)``, with value being automatically diff --git a/doc/en/example/simple.rst b/doc/en/example/simple.rst index ffc68b296..d509d56f1 100644 --- a/doc/en/example/simple.rst +++ b/doc/en/example/simple.rst @@ -537,7 +537,7 @@ We can run this:: file $REGENDOC_TMPDIR/b/test_error.py, line 1 def test_root(db): # no db here, will error out E fixture 'db' not found - > available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, monkeypatch, pytestconfig, record_xml_attribute, record_xml_property, recwarn, tmpdir, tmpdir_factory + > available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, monkeypatch, pytestconfig, record_xml_attribute, record_property, recwarn, tmpdir, tmpdir_factory > use 'pytest --fixtures [testpath]' for help on them. $REGENDOC_TMPDIR/b/test_error.py:1 diff --git a/doc/en/usage.rst b/doc/en/usage.rst index abd8bac2b..2b86420bb 100644 --- a/doc/en/usage.rst +++ b/doc/en/usage.rst @@ -220,19 +220,24 @@ To set the name of the root test suite xml item, you can configure the ``junit_s [pytest] junit_suite_name = my_suite -record_xml_property +record_property ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. versionadded:: 2.8 +.. versionchanged:: 3.5 + + Fixture renamed from ``record_xml_property`` to ``record_property`` as user + properties are now available to all reporters. + ``record_xml_property`` is now deprecated. If you want to log additional information for a test, you can use the -``record_xml_property`` fixture: +``record_property`` fixture: .. code-block:: python - def test_function(record_xml_property): - record_xml_property("example_key", 1) - assert 0 + def test_function(record_property): + record_property("example_key", 1) + assert True This will add an extra property ``example_key="1"`` to the generated ``testcase`` tag: @@ -245,13 +250,42 @@ This will add an extra property ``example_key="1"`` to the generated +Alternatively, you can integrate this functionality with custom markers: + +.. code-block:: python + + # content of conftest.py + + def pytest_collection_modifyitems(session, config, items): + for item in items: + marker = item.get_marker('test_id') + if marker is not None: + test_id = marker.args[0] + item.user_properties.append(('test_id', test_id)) + +And in your tests: + +.. code-block:: python + + # content of test_function.py + + @pytest.mark.test_id(1501) + def test_function(): + assert True + +Will result in: + +.. code-block:: xml + + + + + + + .. warning:: - ``record_xml_property`` is an experimental feature, and its interface might be replaced - by something more powerful and general in future versions. The - functionality per-se will be kept, however. - - Currently it does not work when used with the ``pytest-xdist`` plugin. + ``record_property`` is an experimental feature and may change in the future. Also please note that using this feature will break any schema verification. This might be a problem when used with some CI servers. diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index 031caeb20..b8bbd888f 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -863,10 +863,10 @@ def test_record_property(testdir): import pytest @pytest.fixture - def other(record_xml_property): - record_xml_property("bar", 1) - def test_record(record_xml_property, other): - record_xml_property("foo", "<1"); + def other(record_property): + record_property("bar", 1) + def test_record(record_property, other): + record_property("foo", "<1"); """) result, dom = runandparse(testdir, '-rw') node = dom.find_first_by_tag("testsuite") @@ -877,15 +877,15 @@ def test_record_property(testdir): pnodes[1].assert_attr(name="foo", value="<1") result.stdout.fnmatch_lines([ 'test_record_property.py::test_record', - '*record_xml_property*experimental*', + '*record_property*experimental*', ]) def test_record_property_same_name(testdir): testdir.makepyfile(""" - def test_record_with_same_name(record_xml_property): - record_xml_property("foo", "bar") - record_xml_property("foo", "baz") + def test_record_with_same_name(record_property): + record_property("foo", "bar") + record_property("foo", "baz") """) result, dom = runandparse(testdir, '-rw') node = dom.find_first_by_tag("testsuite") From 2a99e5dd2ab2a828064f5389fc1c9ca236463ac5 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 20 Feb 2018 23:39:18 -0300 Subject: [PATCH 48/53] Delete changelog entries for 3.4.1 release Unfortunately due to hawkowl/towncrier#99 those news fragments were not deleted at the time of CHANGELOG generation. --- changelog/1810.bugfix.rst | 1 - changelog/3052.bugfix | 1 - changelog/3060.trivial.rst | 1 - changelog/3161.bugfix.rst | 1 - changelog/3166.trivial.rst | 1 - changelog/3184.bugfix | 1 - changelog/3202.doc.rst | 1 - changelog/3206.bugfix.rst | 1 - changelog/3222.bugfix | 1 - changelog/985.bugfix | 1 - changelog/985.trivial.rst | 1 - 11 files changed, 11 deletions(-) delete mode 100644 changelog/1810.bugfix.rst delete mode 100644 changelog/3052.bugfix delete mode 100644 changelog/3060.trivial.rst delete mode 100644 changelog/3161.bugfix.rst delete mode 100644 changelog/3166.trivial.rst delete mode 100644 changelog/3184.bugfix delete mode 100644 changelog/3202.doc.rst delete mode 100644 changelog/3206.bugfix.rst delete mode 100644 changelog/3222.bugfix delete mode 100644 changelog/985.bugfix delete mode 100644 changelog/985.trivial.rst diff --git a/changelog/1810.bugfix.rst b/changelog/1810.bugfix.rst deleted file mode 100644 index c91ed47d0..000000000 --- a/changelog/1810.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Move import of ``doctest.UnexpectedException`` to top-level to avoid possible errors when using ``--pdb``. diff --git a/changelog/3052.bugfix b/changelog/3052.bugfix deleted file mode 100644 index ea8c362a4..000000000 --- a/changelog/3052.bugfix +++ /dev/null @@ -1 +0,0 @@ -Added printing of captured stdout/stderr before entering pdb, and improved a test which was giving false negatives about output capturing. diff --git a/changelog/3060.trivial.rst b/changelog/3060.trivial.rst deleted file mode 100644 index 9ea9871e3..000000000 --- a/changelog/3060.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -pytest has changed the publication procedure and is now being published to PyPI directly from Travis. diff --git a/changelog/3161.bugfix.rst b/changelog/3161.bugfix.rst deleted file mode 100644 index 73872be67..000000000 --- a/changelog/3161.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix ordering of tests using parametrized fixtures which can lead to fixtures being created more than necessary. diff --git a/changelog/3166.trivial.rst b/changelog/3166.trivial.rst deleted file mode 100644 index ce92840cb..000000000 --- a/changelog/3166.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -Rename ``ParameterSet._for_parameterize()`` to ``_for_parametrize()`` in order to comply with the naming convention. diff --git a/changelog/3184.bugfix b/changelog/3184.bugfix deleted file mode 100644 index 875358776..000000000 --- a/changelog/3184.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix bug where logging happening at hooks outside of "test run" hooks would cause an internal error. diff --git a/changelog/3202.doc.rst b/changelog/3202.doc.rst deleted file mode 100644 index a6f99fbf6..000000000 --- a/changelog/3202.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Add Sphinx parameter docs for ``match`` and ``message`` args to ``pytest.raises``. diff --git a/changelog/3206.bugfix.rst b/changelog/3206.bugfix.rst deleted file mode 100644 index 1e2305fa2..000000000 --- a/changelog/3206.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Detect arguments injected by ``unittest.mock.patch`` decorator correctly when pypi ``mock.patch`` is installed and imported. diff --git a/changelog/3222.bugfix b/changelog/3222.bugfix deleted file mode 100644 index 40a8e99e9..000000000 --- a/changelog/3222.bugfix +++ /dev/null @@ -1 +0,0 @@ -Errors shown when a ``pytest.raises()`` with ``match=`` fails are now cleaner on what happened: When no exception was raised, the "matching '...'" part got removed as it falsely implies that an exception was raised but it didn't match. When a wrong exception was raised, it's now thrown (like ``pytest.raised()`` without ``match=`` would) instead of complaining about the unmatched text. \ No newline at end of file diff --git a/changelog/985.bugfix b/changelog/985.bugfix deleted file mode 100644 index 0024b9aa2..000000000 --- a/changelog/985.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fixed output capture handling in doctests on macOS. diff --git a/changelog/985.trivial.rst b/changelog/985.trivial.rst deleted file mode 100644 index 8554f2b65..000000000 --- a/changelog/985.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -Skip failing pdb/doctest test on mac. From 3d4d0a261468e9f354b86f5149fe4dc2e8a85c7e Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Wed, 21 Feb 2018 18:54:39 +0100 Subject: [PATCH 49/53] remove addcall in the terminal tests --- changelog/3246.trival.rst | 1 + testing/test_terminal.py | 29 +++++++++++++++++------------ 2 files changed, 18 insertions(+), 12 deletions(-) create mode 100644 changelog/3246.trival.rst diff --git a/changelog/3246.trival.rst b/changelog/3246.trival.rst new file mode 100644 index 000000000..621028966 --- /dev/null +++ b/changelog/3246.trival.rst @@ -0,0 +1 @@ +remove usage of the deprecated addcall in our own tests \ No newline at end of file diff --git a/testing/test_terminal.py b/testing/test_terminal.py index b3ea01709..c9e0eb8b3 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -32,16 +32,19 @@ class Option(object): return values -def pytest_generate_tests(metafunc): - if "option" in metafunc.fixturenames: - metafunc.addcall(id="default", - funcargs={'option': Option(verbose=False)}) - metafunc.addcall(id="verbose", - funcargs={'option': Option(verbose=True)}) - metafunc.addcall(id="quiet", - funcargs={'option': Option(verbose=-1)}) - metafunc.addcall(id="fulltrace", - funcargs={'option': Option(fulltrace=True)}) +@pytest.fixture(params=[ + Option(verbose=False), + Option(verbose=True), + Option(verbose=-1), + Option(fulltrace=True), +], ids=[ + "default", + "verbose", + "quiet", + "fulltrace", +]) +def option(request): + return request.param @pytest.mark.parametrize('input,expected', [ @@ -682,10 +685,12 @@ def test_color_yes_collection_on_non_atty(testdir, verbose): def test_getreportopt(): - class config(object): - class option(object): + class Config(object): + class Option(object): reportchars = "" disable_warnings = True + option = Option() + config = Config() config.option.reportchars = "sf" assert getreportopt(config) == "sf" From d844ad18c2c5cc7241278e7ecb4f764c81baca53 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 21 Feb 2018 15:40:25 -0300 Subject: [PATCH 50/53] Fix formatting of CHANGELOG entry --- changelog/3246.trival.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/3246.trival.rst b/changelog/3246.trival.rst index 621028966..58e13a1dd 100644 --- a/changelog/3246.trival.rst +++ b/changelog/3246.trival.rst @@ -1 +1 @@ -remove usage of the deprecated addcall in our own tests \ No newline at end of file +Remove usage of deprecated ``metafunc.addcall`` in our own tests. From d838193d2db7f9644c7f7959c3f9cf66033b5dfa Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 21 Feb 2018 17:45:52 -0300 Subject: [PATCH 51/53] Add note about deprecating record_xml_property Also make record_xml_property return record_property directly --- _pytest/junitxml.py | 4 ++-- changelog/2770.removal.rst | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 changelog/2770.removal.rst diff --git a/_pytest/junitxml.py b/_pytest/junitxml.py index 22686313a..98b2d13cf 100644 --- a/_pytest/junitxml.py +++ b/_pytest/junitxml.py @@ -250,7 +250,7 @@ def record_property(request): @pytest.fixture -def record_xml_property(request): +def record_xml_property(record_property): """(Deprecated) use record_property.""" import warnings from _pytest import deprecated @@ -260,7 +260,7 @@ def record_xml_property(request): stacklevel=2 ) - return record_property(request) + return record_property @pytest.fixture diff --git a/changelog/2770.removal.rst b/changelog/2770.removal.rst new file mode 100644 index 000000000..0e38009ab --- /dev/null +++ b/changelog/2770.removal.rst @@ -0,0 +1 @@ +``record_xml_property`` fixture is now deprecated in favor of the more generic ``record_property``. \ No newline at end of file From 567b1ea7a137757d32d5b187abe744bd4ee27b85 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 21 Feb 2018 17:56:49 -0300 Subject: [PATCH 52/53] Move user_properties to the end of parameter list for backward compatibility --- _pytest/runner.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/_pytest/runner.py b/_pytest/runner.py index e8aac76fc..6792387db 100644 --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -317,8 +317,7 @@ def pytest_runtest_makereport(item, call): sections.append(("Captured %s %s" % (key, rwhen), content)) return TestReport(item.nodeid, item.location, keywords, outcome, longrepr, when, - item.user_properties, - sections, duration) + sections, duration, user_properties=item.user_properties) class TestReport(BaseReport): @@ -327,8 +326,7 @@ class TestReport(BaseReport): """ def __init__(self, nodeid, location, keywords, outcome, - longrepr, when, user_properties, - sections=(), duration=0, **extra): + longrepr, when, sections=(), duration=0, user_properties=(), **extra): #: normalized collection node id self.nodeid = nodeid From 0f58fc881b795478d3f93f8f2bbd6b73a11b4c57 Mon Sep 17 00:00:00 2001 From: Thomas Hisch Date: Thu, 22 Feb 2018 19:26:46 +0100 Subject: [PATCH 53/53] Add pdb test with disabled logging plugin Implement the test from #3210, which was not merged yet, because the PR was abandoned in favor or #3234. --- testing/test_pdb.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/testing/test_pdb.py b/testing/test_pdb.py index a36ada05b..445cafcc5 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -205,6 +205,24 @@ class TestPDB(object): assert "1 failed" in rest self.flush(child) + def test_pdb_print_captured_logs_nologging(self, testdir): + p1 = testdir.makepyfile(""" + def test_1(): + import logging + logging.warn("get " + "rekt") + assert False + """) + child = testdir.spawn_pytest("--show-capture=all --pdb " + "-p no:logging %s" % p1) + child.expect("get rekt") + output = child.before.decode("utf8") + assert "captured log" not in output + child.expect("(Pdb)") + child.sendeof() + rest = child.read().decode("utf8") + assert "1 failed" in rest + self.flush(child) + def test_pdb_interaction_exception(self, testdir): p1 = testdir.makepyfile(""" import pytest