Merge remote-tracking branch 'upstream/master' into merge-master-into-features

This commit is contained in:
Bruno Oliveira 2016-10-20 21:51:42 -02:00
commit 9d00615bbf
19 changed files with 131 additions and 42 deletions

View File

@ -3,6 +3,9 @@ Thanks for submitting a PR, your contribution is really appreciated!
Here's a quick checklist that should be present in PRs:
- [ ] Target: for bug or doc fixes, target `master`; for new features, target `features`;
Unless your change is trivial documentation fix (e.g., a typo or reword of a small section) please:
- [ ] Make sure to include one or more tests for your change;
- [ ] Add yourself to `AUTHORS`;
- [ ] Add a new entry to `CHANGELOG.rst`

View File

@ -22,7 +22,8 @@ env:
- TESTENV=py27-trial
- TESTENV=py35-pexpect
- TESTENV=py35-xdist
- TESTENV=py35-trial
# Disable py35-trial temporarily: #1989
#- TESTENV=py35-trial
- TESTENV=py27-nobyte
- TESTENV=doctesting
- TESTENV=freeze

View File

@ -59,6 +59,7 @@ Georgy Dyuldin
Graham Horler
Greg Price
Grig Gheorghiu
Grigorii Eremeev (budulianin)
Guido Wesdorp
Harald Armin Massa
Ian Bicking

View File

@ -41,11 +41,25 @@ Changes
*
*
* Import errors when collecting test modules now display the full traceback (`#1976`_).
Thanks `@cwitty`_ for the report and `@nicoddemus`_ for the PR.
* Fix confusing command-line help message for custom options with two or more `metavar` properties (`#2004`_).
Thanks `@okulynyak`_ and `@davehunt`_ for the report and `@nicoddemus`_ for the PR.
* When loading plugins, import errors which contain non-ascii messages are now properly handled in Python 2 (`#1998`_).
Thanks `@nicoddemus`_ for the PR.
*
*
.. _@cwitty: https://github.com/cwitty
.. _@okulynyak: https://github.com/okulynyak
.. _#1976: https://github.com/pytest-dev/pytest/issues/1976
.. _#1998: https://github.com/pytest-dev/pytest/issues/1998
.. _#2004: https://github.com/pytest-dev/pytest/issues/2004
3.0.3

View File

@ -216,3 +216,17 @@ def _is_unittest_unexpected_success_a_failure():
unexpectedSuccesses from tests marked with the expectedFailure() decorator.
"""
return sys.version_info >= (3, 4)
if _PY3:
def safe_str(v):
"""returns v as string"""
return str(v)
else:
def safe_str(v):
"""returns v as string, converting to ascii if necessary"""
try:
return str(v)
except UnicodeError:
errors = 'replace'
return v.encode('ascii', errors)

View File

@ -12,6 +12,7 @@ import _pytest._code
import _pytest.hookspec # the extension point definitions
import _pytest.assertion
from _pytest._pluggy import PluginManager, HookimplMarker, HookspecMarker
from _pytest.compat import safe_str
hookimpl = HookimplMarker("pytest")
hookspec = HookspecMarker("pytest")
@ -405,7 +406,7 @@ class PytestPluginManager(PluginManager):
try:
__import__(importspec)
except ImportError as e:
new_exc = ImportError('Error importing plugin "%s": %s' % (modname, e))
new_exc = ImportError('Error importing plugin "%s": %s' % (modname, safe_str(e.args[0])))
# copy over name and path attributes
for attr in ('name', 'path'):
if hasattr(e, attr):
@ -792,7 +793,7 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter):
if len(option) == 2 or option[2] == ' ':
return_list.append(option)
if option[2:] == short_long.get(option.replace('-', '')):
return_list.append(option.replace(' ', '='))
return_list.append(option.replace(' ', '=', 1))
action._formatted_action_invocation = ', '.join(return_list)
return action._formatted_action_invocation

View File

@ -71,8 +71,8 @@ def showhelp(config):
tw.write(config._parser.optparser.format_help())
tw.line()
tw.line()
tw.line("[pytest] ini-options in the next "
"pytest.ini|tox.ini|setup.cfg file:")
tw.line("[pytest] ini-options in the first "
"pytest.ini|tox.ini|setup.cfg file found:")
tw.line()
for name in config._parser._ininames:

View File

@ -22,11 +22,16 @@ from _pytest.compat import (
getlocation, enum,
)
cutdir2 = py.path.local(_pytest.__file__).dirpath()
cutdir1 = py.path.local(pluggy.__file__.rstrip("oc"))
cutdir2 = py.path.local(_pytest.__file__).dirpath()
cutdir3 = py.path.local(py.__file__).dirpath()
def filter_traceback(entry):
"""Return True if a TracebackEntry instance should be removed from tracebacks:
* dynamically generated code (no code to show up for it);
* internal traceback from pytest or its internal libraries, py and pluggy.
"""
# entry.path might sometimes return a str object when the entry
# points to dynamically generated code
# see https://bitbucket.org/pytest-dev/py/issues/71
@ -37,7 +42,7 @@ def filter_traceback(entry):
# entry.path might point to an inexisting file, in which case it will
# alsso return a str object. see #1133
p = py.path.local(entry.path)
return p != cutdir1 and not p.relto(cutdir2)
return p != cutdir1 and not p.relto(cutdir2) and not p.relto(cutdir3)
@ -425,20 +430,25 @@ class Module(pytest.File, PyCollector):
% e.args
)
except ImportError:
exc_class, exc, _ = sys.exc_info()
from _pytest._code.code import ExceptionInfo
exc_info = ExceptionInfo()
if self.config.getoption('verbose') < 2:
exc_info.traceback = exc_info.traceback.filter(filter_traceback)
exc_repr = exc_info.getrepr(style='short') if exc_info.traceback else exc_info.exconly()
formatted_tb = py._builtin._totext(exc_repr)
raise self.CollectError(
"ImportError while importing test module '%s'.\n"
"Original error message:\n'%s'\n"
"Make sure your test modules/packages have valid Python names."
% (self.fspath, exc or exc_class)
"ImportError while importing test module '{fspath}'.\n"
"Hint: make sure your test modules/packages have valid Python names.\n"
"Traceback:\n"
"{traceback}".format(fspath=self.fspath, traceback=formatted_tb)
)
except _pytest.runner.Skipped as e:
if e.allow_module_level:
raise
raise self.CollectError(
"Using @pytest.skip outside of a test (e.g. as a test "
"function decorator) is not allowed. Use @pytest.mark.skip or "
"@pytest.mark.skipif instead."
"Using pytest.skip outside of a test is not allowed. If you are "
"trying to decorate a test function, use the @pytest.mark.skip "
"or @pytest.mark.skipif decorators instead."
)
self.config.pluginmanager.consider_module(mod)
return mod

View File

@ -701,7 +701,7 @@ Using fixtures from classes, modules or projects
Sometimes test functions do not directly need access to a fixture object.
For example, tests may require to operate with an empty directory as the
current working directory but otherwise do not care for the concrete
directory. Here is how you can can use the standard `tempfile
directory. Here is how you can use the standard `tempfile
<http://docs.python.org/library/tempfile.html>`_ and pytest fixtures to
achieve it. We separate the creation of the fixture into a conftest.py
file::

View File

@ -236,9 +236,10 @@ your own setuptools Test command for invoking pytest.
self.pytest_args = []
def run_tests(self):
import shlex
#import here, cause outside the eggs aren't loaded
import pytest
errno = pytest.main(self.pytest_args)
errno = pytest.main(shlex.split(self.pytest_args))
sys.exit(errno)

View File

@ -55,6 +55,14 @@ will delete the method ``request.session.Session.request``
so that any attempts within tests to create http requests will fail.
.. note::
Be advised that it is not recommended to patch builtin functions such as ``open``,
``compile``, etc., because it might break pytest's internals. If that's
unavoidable, passing ``--tb=native``, ``--assert=plain`` and ``--capture=no`` might
help althought there's no guarantee.
Method reference of the monkeypatch fixture
-------------------------------------------

View File

@ -310,10 +310,6 @@ You can pass in options and arguments::
pytest.main(['-x', 'mytestdir'])
or pass in a string::
pytest.main("-x mytestdir")
You can specify additional plugins to ``pytest.main``::
# content of myinvoke.py

View File

@ -120,7 +120,7 @@ class TestGeneralUsage:
result.stdout.fnmatch_lines([
#XXX on jython this fails: "> import import_fails",
"ImportError while importing test module*",
"'No module named *does_not_work*",
"*No module named *does_not_work*",
])
assert result.ret == 2

View File

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
import os
import sys
from textwrap import dedent
@ -68,9 +69,41 @@ class TestModule:
result = testdir.runpytest("-rw")
result.stdout.fnmatch_lines([
"ImportError while importing test module*test_one.part1*",
"Make sure your test modules/packages have valid Python names.",
"Hint: make sure your test modules/packages have valid Python names.",
])
@pytest.mark.parametrize('verbose', [0, 1, 2])
def test_show_traceback_import_error(self, testdir, verbose):
"""Import errors when collecting modules should display the traceback (#1976).
With low verbosity we omit pytest and internal modules, otherwise show all traceback entries.
"""
testdir.makepyfile(
foo_traceback_import_error="""
from bar_traceback_import_error import NOT_AVAILABLE
""",
bar_traceback_import_error="",
)
testdir.makepyfile("""
import foo_traceback_import_error
""")
args = ('-v',) * verbose
result = testdir.runpytest(*args)
result.stdout.fnmatch_lines([
"ImportError while importing test module*",
"Traceback:",
"*from bar_traceback_import_error import NOT_AVAILABLE",
"*cannot import name *NOT_AVAILABLE*",
])
assert result.ret == 2
stdout = result.stdout.str()
for name in ('_pytest', os.path.join('py', '_path')):
if verbose == 2:
assert name in stdout
else:
assert name not in stdout
class TestClass:
def test_class_with_init_warning(self, testdir):

View File

@ -172,17 +172,6 @@ class TestCollectPluginHookRelay:
assert "world" in wascalled
class TestPrunetraceback:
def test_collection_error(self, testdir):
p = testdir.makepyfile("""
import not_exists
""")
result = testdir.runpytest(p)
assert "__import__" not in result.stdout.str(), "too long traceback"
result.stdout.fnmatch_lines([
"*ERROR collecting*",
"ImportError while importing test module*",
"'No module named *not_exists*",
])
def test_custom_repr_failure(self, testdir):
p = testdir.makepyfile("""

View File

@ -250,6 +250,18 @@ class TestParser:
help = parser.optparser.format_help()
assert '-doit, --func-args foo' in help
def test_multiple_metavar_help(self, parser):
"""
Help text for options with a metavar tuple should display help
in the form "--preferences=value1 value2 value3" (#2004).
"""
group = parser.getgroup("general")
group.addoption('--preferences', metavar=('value1', 'value2', 'value3'), nargs=3)
group._addoption("-h", "--help", action="store_true", dest="help")
parser.parse(['-h'])
help = parser.optparser.format_help()
assert '--preferences=value1 value2 value3' in help
def test_argcomplete(testdir, monkeypatch):
if not py.path.local.sysfind('bash'):

View File

@ -1,3 +1,4 @@
# encoding: UTF-8
import pytest
import py
import os
@ -179,15 +180,20 @@ def test_default_markers(testdir):
])
def test_importplugin_issue375(testdir, pytestpm):
def test_importplugin_error_message(testdir, pytestpm):
"""Don't hide import errors when importing plugins and provide
an easy to debug message.
See #375 and #1998.
"""
testdir.syspathinsert(testdir.tmpdir)
testdir.makepyfile(qwe="import aaaa")
testdir.makepyfile(qwe="""
# encoding: UTF-8
raise ImportError(u'Not possible to import: ☺')
""")
with pytest.raises(ImportError) as excinfo:
pytestpm.import_plugin("qwe")
expected = '.*Error importing plugin "qwe": No module named \'?aaaa\'?'
expected = '.*Error importing plugin "qwe": Not possible to import: .'
assert py.std.re.match(expected, str(excinfo.value))

View File

@ -967,5 +967,5 @@ def test_module_level_skip_error(testdir):
""")
result = testdir.runpytest()
result.stdout.fnmatch_lines(
"*Using @pytest.skip outside of a test * is not allowed*"
"*Using pytest.skip outside of a test is not allowed*"
)

View File

@ -667,7 +667,7 @@ class TestGenericReporting:
result = testdir.runpytest(*option.args)
result.stdout.fnmatch_lines([
"ImportError while importing*",
"'No module named *xyz*",
"*No module named *xyz*",
"*1 error*",
])