Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master' into merge-upstream
Browse files Browse the repository at this point in the history
  • Loading branch information
nicoddemus committed Feb 17, 2018
2 parents dfbaa20 + 00d3001 commit 69d608a
Show file tree
Hide file tree
Showing 25 changed files with 201 additions and 38 deletions.
19 changes: 9 additions & 10 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -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;
3 changes: 0 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,6 @@ matrix:
python: '3.5'
- env: TOXENV=py37
python: 'nightly'
allow_failures:
- env: TOXENV=py37
python: 'nightly'

script: tox --recreate

Expand Down
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Benjamin Peterson
Bernard Pratz
Bob Ippolito
Brian Dorsey
Brian Maissy
Brian Okken
Brianna Laugher
Bruno Oliveira
Expand Down
7 changes: 4 additions & 3 deletions _pytest/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)


Expand Down
17 changes: 14 additions & 3 deletions _pytest/debugging.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from __future__ import absolute_import, division, print_function
import pdb
import sys
from doctest import UnexpectedException


def pytest_addoption(parser):
Expand Down Expand Up @@ -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")
Expand All @@ -95,10 +107,9 @@ 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):
# 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]
Expand Down
22 changes: 15 additions & 7 deletions _pytest/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,20 +166,27 @@ 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:
d[item] = keys
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]
Expand All @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion _pytest/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,7 @@ def emit(self, record):
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)
Expand Down
2 changes: 1 addition & 1 deletion _pytest/mark.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def extract_from(cls, parameterset, legacy_force_tuple=False):
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
Expand Down
3 changes: 2 additions & 1 deletion _pytest/python.py
Original file line number Diff line number Diff line change
Expand Up @@ -785,7 +785,8 @@ def parametrize(self, argnames, argvalues, indirect=False, ids=None,
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

Expand Down
4 changes: 4 additions & 0 deletions _pytest/python_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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::
Expand Down
1 change: 1 addition & 0 deletions changelog/1810.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Move import of ``doctest.UnexpectedException`` to top-level to avoid possible errors when using ``--pdb``.
1 change: 1 addition & 0 deletions changelog/3052.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added printing of captured stdout/stderr before entering pdb, and improved a test which was giving false negatives about output capturing.
1 change: 1 addition & 0 deletions changelog/3161.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix ordering of tests using parametrized fixtures which can lead to fixtures being created more than necessary.
1 change: 1 addition & 0 deletions changelog/3166.trivial.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Rename ``ParameterSet._for_parameterize()`` to ``_for_parametrize()`` in order to comply with the naming convention.
1 change: 1 addition & 0 deletions changelog/3184.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix bug where logging happening at hooks outside of "test run" hooks would cause an internal error.
1 change: 1 addition & 0 deletions changelog/3202.doc.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add Sphinx parameter docs for ``match`` and ``message`` args to ``pytest.raises``.
1 change: 1 addition & 0 deletions changelog/3206.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Detect arguments injected by ``unittest.mock.patch`` decorator correctly when pypi ``mock.patch`` is installed and imported.
1 change: 1 addition & 0 deletions changelog/985.trivial.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Skip failing pdb/doctest test on mac.
30 changes: 30 additions & 0 deletions changelog/README.rst
Original file line number Diff line number Diff line change
@@ -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.
9 changes: 7 additions & 2 deletions doc/en/writing_plugins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
-------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
'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:
Expand Down
41 changes: 41 additions & 0 deletions testing/python/fixture.py
Original file line number Diff line number Diff line change
Expand Up @@ -2168,6 +2168,47 @@ def test_func4(marg):
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]
Expand Down
22 changes: 22 additions & 0 deletions testing/python/integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,28 @@ def test_hello(inject_me):
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("""
Expand Down

0 comments on commit 69d608a

Please sign in to comment.