Merge master into features
This commit is contained in:
		
						commit
						7f519f8ab7
					
				|  | @ -1,8 +1,10 @@ | ||||||
|  | <!-- | ||||||
| Thanks for submitting an issue! | Thanks for submitting an issue! | ||||||
| 
 | 
 | ||||||
| Here's a quick checklist in what to include: | Here's a quick checklist for what to provide: | ||||||
|  | --> | ||||||
| 
 | 
 | ||||||
| - [ ] Include a detailed description of the bug or suggestion | - [ ] a detailed description of the bug or suggestion | ||||||
| - [ ] `pip list` of the virtual environment you are using | - [ ] output of `pip list` from the virtual environment you are using | ||||||
| - [ ] pytest and operating system versions | - [ ] pytest and operating system versions | ||||||
| - [ ] Minimal example if possible | - [ ] minimal example if possible | ||||||
|  |  | ||||||
|  | @ -1,7 +1,9 @@ | ||||||
|  | <!-- | ||||||
| Thanks for submitting a PR, your contribution is really appreciated! | 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 | Here's a quick checklist that should be present in PRs. | ||||||
| just a guideline): | (please delete this text from the final description, this is just a guideline) | ||||||
|  | --> | ||||||
| 
 | 
 | ||||||
| - [ ] Create a new changelog file in the `changelog` folder, with a name like `<ISSUE NUMBER>.<TYPE>.rst`. See [changelog/README.rst](https://github.com/pytest-dev/pytest/blob/master/changelog/README.rst) for details. | - [ ] Create a new changelog file in the `changelog` folder, with a name like `<ISSUE NUMBER>.<TYPE>.rst`. See [changelog/README.rst](https://github.com/pytest-dev/pytest/blob/master/changelog/README.rst) for details. | ||||||
| - [ ] Target the `master` branch for bug fixes, documentation updates and trivial changes. | - [ ] Target the `master` branch for bug fixes, documentation updates and trivial changes. | ||||||
|  |  | ||||||
							
								
								
									
										2
									
								
								AUTHORS
								
								
								
								
							
							
						
						
									
										2
									
								
								AUTHORS
								
								
								
								
							|  | @ -105,6 +105,7 @@ Hugo van Kemenade | ||||||
| Hui Wang (coldnight) | Hui Wang (coldnight) | ||||||
| Ian Bicking | Ian Bicking | ||||||
| Ian Lesperance | Ian Lesperance | ||||||
|  | Ilya Konstantinov | ||||||
| Ionuț Turturică | Ionuț Turturică | ||||||
| Iwan Briquemont | Iwan Briquemont | ||||||
| Jaap Broekhuizen | Jaap Broekhuizen | ||||||
|  | @ -179,6 +180,7 @@ Nicholas Devenish | ||||||
| Nicholas Murphy | Nicholas Murphy | ||||||
| Niclas Olofsson | Niclas Olofsson | ||||||
| Nicolas Delaby | Nicolas Delaby | ||||||
|  | Nikolay Kondratyev | ||||||
| Oleg Pidsadnyi | Oleg Pidsadnyi | ||||||
| Oleg Sushchenko | Oleg Sushchenko | ||||||
| Oliver Bestwalter | Oliver Bestwalter | ||||||
|  |  | ||||||
|  | @ -0,0 +1 @@ | ||||||
|  | Fix crash caused by error in ``__repr__`` function with both ``showlocals`` and verbose output enabled. | ||||||
|  | @ -0,0 +1 @@ | ||||||
|  | Eliminate core dependency on 'terminal' plugin. | ||||||
|  | @ -208,7 +208,7 @@ Here's a quick guide on how to skip tests in a module in different situations: | ||||||
| 
 | 
 | ||||||
|   .. code-block:: python |   .. code-block:: python | ||||||
| 
 | 
 | ||||||
|         pytestmark = pytest.mark.skipif(sys.platform == "win32", "tests for linux only") |         pytestmark = pytest.mark.skipif(sys.platform == "win32", reason="tests for linux only") | ||||||
| 
 | 
 | ||||||
| 3. Skip all tests in a module if some import is missing: | 3. Skip all tests in a module if some import is missing: | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -3,7 +3,6 @@ from __future__ import division | ||||||
| from __future__ import print_function | from __future__ import print_function | ||||||
| 
 | 
 | ||||||
| import inspect | import inspect | ||||||
| import pprint |  | ||||||
| import re | import re | ||||||
| import sys | import sys | ||||||
| import traceback | import traceback | ||||||
|  | @ -18,6 +17,7 @@ import six | ||||||
| from six import text_type | from six import text_type | ||||||
| 
 | 
 | ||||||
| import _pytest | import _pytest | ||||||
|  | from _pytest._io.saferepr import safeformat | ||||||
| from _pytest._io.saferepr import saferepr | from _pytest._io.saferepr import saferepr | ||||||
| from _pytest.compat import _PY2 | from _pytest.compat import _PY2 | ||||||
| from _pytest.compat import _PY3 | from _pytest.compat import _PY3 | ||||||
|  | @ -614,14 +614,11 @@ class FormattedExcinfo(object): | ||||||
|             source = source.deindent() |             source = source.deindent() | ||||||
|         return source |         return source | ||||||
| 
 | 
 | ||||||
|     def _saferepr(self, obj): |  | ||||||
|         return saferepr(obj) |  | ||||||
| 
 |  | ||||||
|     def repr_args(self, entry): |     def repr_args(self, entry): | ||||||
|         if self.funcargs: |         if self.funcargs: | ||||||
|             args = [] |             args = [] | ||||||
|             for argname, argvalue in entry.frame.getargs(var=True): |             for argname, argvalue in entry.frame.getargs(var=True): | ||||||
|                 args.append((argname, self._saferepr(argvalue))) |                 args.append((argname, saferepr(argvalue))) | ||||||
|             return ReprFuncArgs(args) |             return ReprFuncArgs(args) | ||||||
| 
 | 
 | ||||||
|     def get_source(self, source, line_index=-1, excinfo=None, short=False): |     def get_source(self, source, line_index=-1, excinfo=None, short=False): | ||||||
|  | @ -674,9 +671,9 @@ class FormattedExcinfo(object): | ||||||
|                     # _repr() function, which is only reprlib.Repr in |                     # _repr() function, which is only reprlib.Repr in | ||||||
|                     # disguise, so is very configurable. |                     # disguise, so is very configurable. | ||||||
|                     if self.truncate_locals: |                     if self.truncate_locals: | ||||||
|                         str_repr = self._saferepr(value) |                         str_repr = saferepr(value) | ||||||
|                     else: |                     else: | ||||||
|                         str_repr = pprint.pformat(value) |                         str_repr = safeformat(value) | ||||||
|                     # if len(str_repr) < 70 or not isinstance(value, |                     # if len(str_repr) < 70 or not isinstance(value, | ||||||
|                     #                            (list, tuple, dict)): |                     #                            (list, tuple, dict)): | ||||||
|                     lines.append("%-10s = %s" % (name, str_repr)) |                     lines.append("%-10s = %s" % (name, str_repr)) | ||||||
|  |  | ||||||
|  | @ -1,8 +1,26 @@ | ||||||
| import sys | import pprint | ||||||
| 
 | 
 | ||||||
| from six.moves import reprlib | from six.moves import reprlib | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | def _call_and_format_exception(call, x, *args): | ||||||
|  |     try: | ||||||
|  |         # Try the vanilla repr and make sure that the result is a string | ||||||
|  |         return call(x, *args) | ||||||
|  |     except Exception as exc: | ||||||
|  |         exc_name = type(exc).__name__ | ||||||
|  |         try: | ||||||
|  |             exc_info = str(exc) | ||||||
|  |         except Exception: | ||||||
|  |             exc_info = "unknown" | ||||||
|  |         return '<[%s("%s") raised in repr()] %s object at 0x%x>' % ( | ||||||
|  |             exc_name, | ||||||
|  |             exc_info, | ||||||
|  |             x.__class__.__name__, | ||||||
|  |             id(x), | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class SafeRepr(reprlib.Repr): | class SafeRepr(reprlib.Repr): | ||||||
|     """subclass of repr.Repr that limits the resulting size of repr() |     """subclass of repr.Repr that limits the resulting size of repr() | ||||||
|     and includes information on exceptions raised during the call. |     and includes information on exceptions raised during the call. | ||||||
|  | @ -33,28 +51,20 @@ class SafeRepr(reprlib.Repr): | ||||||
|         return self._callhelper(repr, x) |         return self._callhelper(repr, x) | ||||||
| 
 | 
 | ||||||
|     def _callhelper(self, call, x, *args): |     def _callhelper(self, call, x, *args): | ||||||
|         try: |         s = _call_and_format_exception(call, x, *args) | ||||||
|             # Try the vanilla repr and make sure that the result is a string |         if len(s) > self.maxsize: | ||||||
|             s = call(x, *args) |             i = max(0, (self.maxsize - 3) // 2) | ||||||
|         except Exception: |             j = max(0, self.maxsize - 3 - i) | ||||||
|             cls, e, tb = sys.exc_info() |             s = s[:i] + "..." + s[len(s) - j :] | ||||||
|             exc_name = getattr(cls, "__name__", "unknown") |         return s | ||||||
|             try: | 
 | ||||||
|                 exc_info = str(e) | 
 | ||||||
|             except Exception: | def safeformat(obj): | ||||||
|                 exc_info = "unknown" |     """return a pretty printed string for the given object. | ||||||
|             return '<[%s("%s") raised in repr()] %s object at 0x%x>' % ( |     Failing __repr__ functions of user instances will be represented | ||||||
|                 exc_name, |     with a short exception info. | ||||||
|                 exc_info, |     """ | ||||||
|                 x.__class__.__name__, |     return _call_and_format_exception(pprint.pformat, obj) | ||||||
|                 id(x), |  | ||||||
|             ) |  | ||||||
|         else: |  | ||||||
|             if len(s) > self.maxsize: |  | ||||||
|                 i = max(0, (self.maxsize - 3) // 2) |  | ||||||
|                 j = max(0, self.maxsize - 3 - i) |  | ||||||
|                 s = s[:i] + "..." + s[len(s) - j :] |  | ||||||
|             return s |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def saferepr(obj, maxsize=240): | def saferepr(obj, maxsize=240): | ||||||
|  |  | ||||||
|  | @ -704,7 +704,7 @@ class Config(object): | ||||||
|         return self |         return self | ||||||
| 
 | 
 | ||||||
|     def notify_exception(self, excinfo, option=None): |     def notify_exception(self, excinfo, option=None): | ||||||
|         if option and option.fulltrace: |         if option and getattr(option, "fulltrace", False): | ||||||
|             style = "long" |             style = "long" | ||||||
|         else: |         else: | ||||||
|             style = "native" |             style = "native" | ||||||
|  |  | ||||||
|  | @ -248,7 +248,7 @@ class Node(object): | ||||||
|         if excinfo.errisinstance(fm.FixtureLookupError): |         if excinfo.errisinstance(fm.FixtureLookupError): | ||||||
|             return excinfo.value.formatrepr() |             return excinfo.value.formatrepr() | ||||||
|         tbfilter = True |         tbfilter = True | ||||||
|         if self.config.option.fulltrace: |         if self.config.getoption("fulltrace", False): | ||||||
|             style = "long" |             style = "long" | ||||||
|         else: |         else: | ||||||
|             tb = _pytest._code.Traceback([excinfo.traceback[-1]]) |             tb = _pytest._code.Traceback([excinfo.traceback[-1]]) | ||||||
|  | @ -260,12 +260,12 @@ class Node(object): | ||||||
|                 style = "long" |                 style = "long" | ||||||
|         # XXX should excinfo.getrepr record all data and toterminal() process it? |         # XXX should excinfo.getrepr record all data and toterminal() process it? | ||||||
|         if style is None: |         if style is None: | ||||||
|             if self.config.option.tbstyle == "short": |             if self.config.getoption("tbstyle", "auto") == "short": | ||||||
|                 style = "short" |                 style = "short" | ||||||
|             else: |             else: | ||||||
|                 style = "long" |                 style = "long" | ||||||
| 
 | 
 | ||||||
|         if self.config.option.verbose > 1: |         if self.config.getoption("verbose", 0) > 1: | ||||||
|             truncate_locals = False |             truncate_locals = False | ||||||
|         else: |         else: | ||||||
|             truncate_locals = True |             truncate_locals = True | ||||||
|  | @ -279,7 +279,7 @@ class Node(object): | ||||||
|         return excinfo.getrepr( |         return excinfo.getrepr( | ||||||
|             funcargs=True, |             funcargs=True, | ||||||
|             abspath=abspath, |             abspath=abspath, | ||||||
|             showlocals=self.config.option.showlocals, |             showlocals=self.config.getoption("showlocals", False), | ||||||
|             style=style, |             style=style, | ||||||
|             tbfilter=tbfilter, |             tbfilter=tbfilter, | ||||||
|             truncate_locals=truncate_locals, |             truncate_locals=truncate_locals, | ||||||
|  |  | ||||||
|  | @ -820,7 +820,7 @@ class FunctionMixin(PyobjMixin): | ||||||
|             self.obj = self._getobj() |             self.obj = self._getobj() | ||||||
| 
 | 
 | ||||||
|     def _prunetraceback(self, excinfo): |     def _prunetraceback(self, excinfo): | ||||||
|         if hasattr(self, "_obj") and not self.config.option.fulltrace: |         if hasattr(self, "_obj") and not self.config.getoption("fulltrace", False): | ||||||
|             code = _pytest._code.Code(get_real_func(self.obj)) |             code = _pytest._code.Code(get_real_func(self.obj)) | ||||||
|             path, firstlineno = code.path, code.firstlineno |             path, firstlineno = code.path, code.firstlineno | ||||||
|             traceback = excinfo.traceback |             traceback = excinfo.traceback | ||||||
|  | @ -835,14 +835,14 @@ class FunctionMixin(PyobjMixin): | ||||||
|             excinfo.traceback = ntraceback.filter() |             excinfo.traceback = ntraceback.filter() | ||||||
|             # issue364: mark all but first and last frames to |             # issue364: mark all but first and last frames to | ||||||
|             # only show a single-line message for each frame |             # only show a single-line message for each frame | ||||||
|             if self.config.option.tbstyle == "auto": |             if self.config.getoption("tbstyle", "auto") == "auto": | ||||||
|                 if len(excinfo.traceback) > 2: |                 if len(excinfo.traceback) > 2: | ||||||
|                     for entry in excinfo.traceback[1:-1]: |                     for entry in excinfo.traceback[1:-1]: | ||||||
|                         entry.set_repr_style("short") |                         entry.set_repr_style("short") | ||||||
| 
 | 
 | ||||||
|     def repr_failure(self, excinfo, outerr=None): |     def repr_failure(self, excinfo, outerr=None): | ||||||
|         assert outerr is None, "XXX outerr usage is deprecated" |         assert outerr is None, "XXX outerr usage is deprecated" | ||||||
|         style = self.config.option.tbstyle |         style = self.config.getoption("tbstyle", "auto") | ||||||
|         if style == "auto": |         if style == "auto": | ||||||
|             style = "long" |             style = "long" | ||||||
|         return self._repr_failure_py(excinfo, style=style) |         return self._repr_failure_py(excinfo, style=style) | ||||||
|  |  | ||||||
|  | @ -368,7 +368,7 @@ class TestReport(BaseReport): | ||||||
|                     longrepr = item.repr_failure(excinfo) |                     longrepr = item.repr_failure(excinfo) | ||||||
|                 else:  # exception in setup or teardown |                 else:  # exception in setup or teardown | ||||||
|                     longrepr = item._repr_failure_py( |                     longrepr = item._repr_failure_py( | ||||||
|                         excinfo, style=item.config.option.tbstyle |                         excinfo, style=item.config.getoption("tbstyle", "auto") | ||||||
|                     ) |                     ) | ||||||
|         for rwhen, key, content in item._report_sections: |         for rwhen, key, content in item._report_sections: | ||||||
|             sections.append(("Captured %s %s" % (key, rwhen), content)) |             sections.append(("Captured %s %s" % (key, rwhen), content)) | ||||||
|  |  | ||||||
|  | @ -598,6 +598,35 @@ raise ValueError() | ||||||
|         assert reprlocals.lines[2] == "y          = 5" |         assert reprlocals.lines[2] == "y          = 5" | ||||||
|         assert reprlocals.lines[3] == "z          = 7" |         assert reprlocals.lines[3] == "z          = 7" | ||||||
| 
 | 
 | ||||||
|  |     def test_repr_local_with_error(self): | ||||||
|  |         class ObjWithErrorInRepr: | ||||||
|  |             def __repr__(self): | ||||||
|  |                 raise NotImplementedError | ||||||
|  | 
 | ||||||
|  |         p = FormattedExcinfo(showlocals=True, truncate_locals=False) | ||||||
|  |         loc = {"x": ObjWithErrorInRepr(), "__builtins__": {}} | ||||||
|  |         reprlocals = p.repr_locals(loc) | ||||||
|  |         assert reprlocals.lines | ||||||
|  |         assert reprlocals.lines[0] == "__builtins__ = <builtins>" | ||||||
|  |         assert '[NotImplementedError("") raised in repr()]' in reprlocals.lines[1] | ||||||
|  | 
 | ||||||
|  |     def test_repr_local_with_exception_in_class_property(self): | ||||||
|  |         class ExceptionWithBrokenClass(Exception): | ||||||
|  |             @property | ||||||
|  |             def __class__(self): | ||||||
|  |                 raise TypeError("boom!") | ||||||
|  | 
 | ||||||
|  |         class ObjWithErrorInRepr: | ||||||
|  |             def __repr__(self): | ||||||
|  |                 raise ExceptionWithBrokenClass() | ||||||
|  | 
 | ||||||
|  |         p = FormattedExcinfo(showlocals=True, truncate_locals=False) | ||||||
|  |         loc = {"x": ObjWithErrorInRepr(), "__builtins__": {}} | ||||||
|  |         reprlocals = p.repr_locals(loc) | ||||||
|  |         assert reprlocals.lines | ||||||
|  |         assert reprlocals.lines[0] == "__builtins__ = <builtins>" | ||||||
|  |         assert '[ExceptionWithBrokenClass("") raised in repr()]' in reprlocals.lines[1] | ||||||
|  | 
 | ||||||
|     def test_repr_local_truncated(self): |     def test_repr_local_truncated(self): | ||||||
|         loc = {"l": [i for i in range(10)]} |         loc = {"l": [i for i in range(10)]} | ||||||
|         p = FormattedExcinfo(showlocals=True) |         p = FormattedExcinfo(showlocals=True) | ||||||
|  |  | ||||||
|  | @ -770,7 +770,7 @@ def test_notify_exception(testdir, capfd): | ||||||
|     config = testdir.parseconfig() |     config = testdir.parseconfig() | ||||||
|     with pytest.raises(ValueError) as excinfo: |     with pytest.raises(ValueError) as excinfo: | ||||||
|         raise ValueError(1) |         raise ValueError(1) | ||||||
|     config.notify_exception(excinfo) |     config.notify_exception(excinfo, config.option) | ||||||
|     out, err = capfd.readouterr() |     out, err = capfd.readouterr() | ||||||
|     assert "ValueError" in err |     assert "ValueError" in err | ||||||
| 
 | 
 | ||||||
|  | @ -779,10 +779,17 @@ def test_notify_exception(testdir, capfd): | ||||||
|             return True |             return True | ||||||
| 
 | 
 | ||||||
|     config.pluginmanager.register(A()) |     config.pluginmanager.register(A()) | ||||||
|     config.notify_exception(excinfo) |     config.notify_exception(excinfo, config.option) | ||||||
|     out, err = capfd.readouterr() |     out, err = capfd.readouterr() | ||||||
|     assert not err |     assert not err | ||||||
| 
 | 
 | ||||||
|  |     config = testdir.parseconfig("-p", "no:terminal") | ||||||
|  |     with pytest.raises(ValueError) as excinfo: | ||||||
|  |         raise ValueError(1) | ||||||
|  |     config.notify_exception(excinfo, config.option) | ||||||
|  |     out, err = capfd.readouterr() | ||||||
|  |     assert "ValueError" in err | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| def test_load_initial_conftest_last_ordering(testdir, _config_for_test): | def test_load_initial_conftest_last_ordering(testdir, _config_for_test): | ||||||
|     pm = _config_for_test.pluginmanager |     pm = _config_for_test.pluginmanager | ||||||
|  |  | ||||||
|  | @ -134,6 +134,30 @@ class SessionTests(object): | ||||||
|             != -1 |             != -1 | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|  |     def test_broken_repr_with_showlocals_verbose(self, testdir): | ||||||
|  |         p = testdir.makepyfile( | ||||||
|  |             """ | ||||||
|  |             class ObjWithErrorInRepr: | ||||||
|  |                 def __repr__(self): | ||||||
|  |                     raise NotImplementedError | ||||||
|  | 
 | ||||||
|  |             def test_repr_error(): | ||||||
|  |                 x = ObjWithErrorInRepr() | ||||||
|  |                 assert x == "value" | ||||||
|  |         """ | ||||||
|  |         ) | ||||||
|  |         reprec = testdir.inline_run("--showlocals", "-vv", p) | ||||||
|  |         passed, skipped, failed = reprec.listoutcomes() | ||||||
|  |         assert (len(passed), len(skipped), len(failed)) == (0, 0, 1) | ||||||
|  |         entries = failed[0].longrepr.reprtraceback.reprentries | ||||||
|  |         assert len(entries) == 1 | ||||||
|  |         repr_locals = entries[0].reprlocals | ||||||
|  |         assert repr_locals.lines | ||||||
|  |         assert len(repr_locals.lines) == 1 | ||||||
|  |         assert repr_locals.lines[0].startswith( | ||||||
|  |             'x          = <[NotImplementedError("") raised in repr()] ObjWithErrorInRepr' | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|     def test_skip_file_by_conftest(self, testdir): |     def test_skip_file_by_conftest(self, testdir): | ||||||
|         testdir.makepyfile( |         testdir.makepyfile( | ||||||
|             conftest=""" |             conftest=""" | ||||||
|  |  | ||||||
							
								
								
									
										6
									
								
								tox.ini
								
								
								
								
							
							
						
						
									
										6
									
								
								tox.ini
								
								
								
								
							|  | @ -73,7 +73,8 @@ commands = pre-commit run --all-files --show-diff-on-failure | ||||||
| 
 | 
 | ||||||
| [testenv:docs] | [testenv:docs] | ||||||
| basepython = python3 | basepython = python3 | ||||||
| usedevelop = True | # broken due to pip 19.1 (#5167) | ||||||
|  | # usedevelop = True | ||||||
| changedir = doc/en | changedir = doc/en | ||||||
| deps = -r{toxinidir}/doc/en/requirements.txt | deps = -r{toxinidir}/doc/en/requirements.txt | ||||||
| 
 | 
 | ||||||
|  | @ -127,7 +128,8 @@ commands = | ||||||
| [testenv:release] | [testenv:release] | ||||||
| decription = do a release, required posarg of the version number | decription = do a release, required posarg of the version number | ||||||
| basepython = python3.6 | basepython = python3.6 | ||||||
| usedevelop = True | # broken due to pip 19.1 (#5167) | ||||||
|  | # usedevelop = True | ||||||
| passenv = * | passenv = * | ||||||
| deps = | deps = | ||||||
|     colorama |     colorama | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue