Merge remote-tracking branch 'upstream/master' into merge-upstream
This commit is contained in:
		
						commit
						69d608aec3
					
				|  | @ -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 in the `changelog` folder, with a name like `<ISSUE NUMBER>.<TYPE>.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; | ||||
|  |  | |||
|  | @ -39,9 +39,6 @@ matrix: | |||
|       python: '3.5' | ||||
|     - env: TOXENV=py37 | ||||
|       python: 'nightly' | ||||
|   allow_failures: | ||||
|     - env: TOXENV=py37 | ||||
|       python: 'nightly' | ||||
| 
 | ||||
| script: tox --recreate | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										1
									
								
								AUTHORS
								
								
								
								
							
							
						
						
									
										1
									
								
								AUTHORS
								
								
								
								
							|  | @ -29,6 +29,7 @@ Benjamin Peterson | |||
| Bernard Pratz | ||||
| Bob Ippolito | ||||
| Brian Dorsey | ||||
| Brian Maissy | ||||
| Brian Okken | ||||
| Brianna Laugher | ||||
| Bruno Oliveira | ||||
|  |  | |||
|  | @ -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) | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -2,6 +2,7 @@ | |||
| from __future__ import absolute_import, division, print_function | ||||
| import pdb | ||||
| import sys | ||||
| from doctest import UnexpectedException | ||||
| 
 | ||||
| 
 | ||||
| def pytest_addoption(parser): | ||||
|  | @ -85,6 +86,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") | ||||
|  | @ -95,10 +107,9 @@ def _enter_pdb(node, excinfo, rep): | |||
| 
 | ||||
| 
 | ||||
| def _postmortem_traceback(excinfo): | ||||
|     if isinstance(excinfo.value, UnexpectedException): | ||||
|         # 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] | ||||
|  |  | |||
|  | @ -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: | ||||
|                 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) | ||||
|  |  | |||
|  | @ -504,7 +504,7 @@ class _LiveLoggingStreamHandler(logging.StreamHandler): | |||
|                 if not self._test_outcome_written: | ||||
|                     self._test_outcome_written = True | ||||
|                     self.stream.write('\n') | ||||
|             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) | ||||
|  |  | |||
|  | @ -75,7 +75,7 @@ class ParameterSet(namedtuple('ParameterSet', 'values, marks, id')): | |||
|         return cls(argval, marks=newmarks, id=None) | ||||
| 
 | ||||
|     @classmethod | ||||
|     def _for_parameterize(cls, argnames, argvalues, function, config): | ||||
|     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 | ||||
|  |  | |||
|  | @ -785,7 +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, parameters = ParameterSet._for_parametrize( | ||||
|             argnames, argvalues, self.function, self.config) | ||||
|         del argvalues | ||||
| 
 | ||||
|  |  | |||
|  | @ -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:: | ||||
|  |  | |||
|  | @ -0,0 +1 @@ | |||
| Move import of ``doctest.UnexpectedException`` to top-level to avoid possible errors when using ``--pdb``. | ||||
|  | @ -0,0 +1 @@ | |||
| Added printing of captured stdout/stderr before entering pdb, and improved a test which was giving false negatives about output capturing. | ||||
|  | @ -0,0 +1 @@ | |||
| Fix ordering of tests using parametrized fixtures which can lead to fixtures being created more than necessary. | ||||
|  | @ -0,0 +1 @@ | |||
| Rename ``ParameterSet._for_parameterize()`` to ``_for_parametrize()`` in order to comply with the naming convention. | ||||
|  | @ -0,0 +1 @@ | |||
| Fix bug where logging happening at hooks outside of "test run" hooks would cause an internal error. | ||||
|  | @ -0,0 +1 @@ | |||
| Add Sphinx parameter docs for ``match`` and ``message`` args to ``pytest.raises``. | ||||
|  | @ -0,0 +1 @@ | |||
| Detect arguments injected by ``unittest.mock.patch`` decorator correctly when pypi ``mock.patch`` is installed and imported. | ||||
|  | @ -0,0 +1 @@ | |||
| Skip failing pdb/doctest test on mac. | ||||
|  | @ -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 ``<ISSUE>.<TYPE>.rst``, where | ||||
| ``<ISSUE>`` is an issue number, and ``<TYPE>`` 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``: 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``. | ||||
| 
 | ||||
| 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. | ||||
|  | @ -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 <http://pluggy.readthedocs.io/en/latest/#wrappers>`_. | ||||
| 
 | ||||
| 
 | ||||
| Hook function ordering / call example | ||||
| ------------------------------------- | ||||
|  |  | |||
							
								
								
									
										2
									
								
								setup.py
								
								
								
								
							
							
						
						
									
										2
									
								
								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: | ||||
|  |  | |||
|  | @ -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] | ||||
|  |  | |||
|  | @ -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(""" | ||||
|  |  | |||
|  | @ -141,13 +141,14 @@ 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("captured stdout") | ||||
|         child.expect("get rekt") | ||||
|         child.expect("(Pdb)") | ||||
|         child.sendeof() | ||||
|  | @ -156,6 +157,36 @@ class TestPDB(object): | |||
|         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): | ||||
|         p1 = testdir.makepyfile(""" | ||||
|             import pytest | ||||
|  | @ -267,6 +298,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.xfail("sys.platform == 'darwin'", reason='See issue #985', run=False) | ||||
|     def test_pdb_interaction_doctest(self, testdir): | ||||
|         p1 = testdir.makepyfile(""" | ||||
|             import pytest | ||||
|  |  | |||
|  | @ -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): | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue