Merge remote-tracking branch 'upstream/master' into merge-master-into-features
This commit is contained in:
commit
9d00615bbf
|
@ -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:
|
Here's a quick checklist that should be present in PRs:
|
||||||
|
|
||||||
- [ ] Target: for bug or doc fixes, target `master`; for new features, target `features`;
|
- [ ] 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;
|
- [ ] Make sure to include one or more tests for your change;
|
||||||
- [ ] Add yourself to `AUTHORS`;
|
- [ ] Add yourself to `AUTHORS`;
|
||||||
- [ ] Add a new entry to `CHANGELOG.rst`
|
- [ ] Add a new entry to `CHANGELOG.rst`
|
||||||
|
|
|
@ -22,7 +22,8 @@ env:
|
||||||
- TESTENV=py27-trial
|
- TESTENV=py27-trial
|
||||||
- TESTENV=py35-pexpect
|
- TESTENV=py35-pexpect
|
||||||
- TESTENV=py35-xdist
|
- TESTENV=py35-xdist
|
||||||
- TESTENV=py35-trial
|
# Disable py35-trial temporarily: #1989
|
||||||
|
#- TESTENV=py35-trial
|
||||||
- TESTENV=py27-nobyte
|
- TESTENV=py27-nobyte
|
||||||
- TESTENV=doctesting
|
- TESTENV=doctesting
|
||||||
- TESTENV=freeze
|
- TESTENV=freeze
|
||||||
|
|
1
AUTHORS
1
AUTHORS
|
@ -59,6 +59,7 @@ Georgy Dyuldin
|
||||||
Graham Horler
|
Graham Horler
|
||||||
Greg Price
|
Greg Price
|
||||||
Grig Gheorghiu
|
Grig Gheorghiu
|
||||||
|
Grigorii Eremeev (budulianin)
|
||||||
Guido Wesdorp
|
Guido Wesdorp
|
||||||
Harald Armin Massa
|
Harald Armin Massa
|
||||||
Ian Bicking
|
Ian Bicking
|
||||||
|
|
|
@ -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
|
3.0.3
|
||||||
|
|
|
@ -216,3 +216,17 @@ def _is_unittest_unexpected_success_a_failure():
|
||||||
unexpectedSuccesses from tests marked with the expectedFailure() decorator.
|
unexpectedSuccesses from tests marked with the expectedFailure() decorator.
|
||||||
"""
|
"""
|
||||||
return sys.version_info >= (3, 4)
|
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)
|
||||||
|
|
|
@ -12,6 +12,7 @@ import _pytest._code
|
||||||
import _pytest.hookspec # the extension point definitions
|
import _pytest.hookspec # the extension point definitions
|
||||||
import _pytest.assertion
|
import _pytest.assertion
|
||||||
from _pytest._pluggy import PluginManager, HookimplMarker, HookspecMarker
|
from _pytest._pluggy import PluginManager, HookimplMarker, HookspecMarker
|
||||||
|
from _pytest.compat import safe_str
|
||||||
|
|
||||||
hookimpl = HookimplMarker("pytest")
|
hookimpl = HookimplMarker("pytest")
|
||||||
hookspec = HookspecMarker("pytest")
|
hookspec = HookspecMarker("pytest")
|
||||||
|
@ -405,7 +406,7 @@ class PytestPluginManager(PluginManager):
|
||||||
try:
|
try:
|
||||||
__import__(importspec)
|
__import__(importspec)
|
||||||
except ImportError as e:
|
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
|
# copy over name and path attributes
|
||||||
for attr in ('name', 'path'):
|
for attr in ('name', 'path'):
|
||||||
if hasattr(e, attr):
|
if hasattr(e, attr):
|
||||||
|
@ -792,7 +793,7 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter):
|
||||||
if len(option) == 2 or option[2] == ' ':
|
if len(option) == 2 or option[2] == ' ':
|
||||||
return_list.append(option)
|
return_list.append(option)
|
||||||
if option[2:] == short_long.get(option.replace('-', '')):
|
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)
|
action._formatted_action_invocation = ', '.join(return_list)
|
||||||
return action._formatted_action_invocation
|
return action._formatted_action_invocation
|
||||||
|
|
||||||
|
|
|
@ -71,8 +71,8 @@ def showhelp(config):
|
||||||
tw.write(config._parser.optparser.format_help())
|
tw.write(config._parser.optparser.format_help())
|
||||||
tw.line()
|
tw.line()
|
||||||
tw.line()
|
tw.line()
|
||||||
tw.line("[pytest] ini-options in the next "
|
tw.line("[pytest] ini-options in the first "
|
||||||
"pytest.ini|tox.ini|setup.cfg file:")
|
"pytest.ini|tox.ini|setup.cfg file found:")
|
||||||
tw.line()
|
tw.line()
|
||||||
|
|
||||||
for name in config._parser._ininames:
|
for name in config._parser._ininames:
|
||||||
|
|
|
@ -22,11 +22,16 @@ from _pytest.compat import (
|
||||||
getlocation, enum,
|
getlocation, enum,
|
||||||
)
|
)
|
||||||
|
|
||||||
cutdir2 = py.path.local(_pytest.__file__).dirpath()
|
|
||||||
cutdir1 = py.path.local(pluggy.__file__.rstrip("oc"))
|
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):
|
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
|
# entry.path might sometimes return a str object when the entry
|
||||||
# points to dynamically generated code
|
# points to dynamically generated code
|
||||||
# see https://bitbucket.org/pytest-dev/py/issues/71
|
# 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
|
# entry.path might point to an inexisting file, in which case it will
|
||||||
# alsso return a str object. see #1133
|
# alsso return a str object. see #1133
|
||||||
p = py.path.local(entry.path)
|
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
|
% e.args
|
||||||
)
|
)
|
||||||
except ImportError:
|
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(
|
raise self.CollectError(
|
||||||
"ImportError while importing test module '%s'.\n"
|
"ImportError while importing test module '{fspath}'.\n"
|
||||||
"Original error message:\n'%s'\n"
|
"Hint: make sure your test modules/packages have valid Python names.\n"
|
||||||
"Make sure your test modules/packages have valid Python names."
|
"Traceback:\n"
|
||||||
% (self.fspath, exc or exc_class)
|
"{traceback}".format(fspath=self.fspath, traceback=formatted_tb)
|
||||||
)
|
)
|
||||||
except _pytest.runner.Skipped as e:
|
except _pytest.runner.Skipped as e:
|
||||||
if e.allow_module_level:
|
if e.allow_module_level:
|
||||||
raise
|
raise
|
||||||
raise self.CollectError(
|
raise self.CollectError(
|
||||||
"Using @pytest.skip outside of a test (e.g. as a test "
|
"Using pytest.skip outside of a test is not allowed. If you are "
|
||||||
"function decorator) is not allowed. Use @pytest.mark.skip or "
|
"trying to decorate a test function, use the @pytest.mark.skip "
|
||||||
"@pytest.mark.skipif instead."
|
"or @pytest.mark.skipif decorators instead."
|
||||||
)
|
)
|
||||||
self.config.pluginmanager.consider_module(mod)
|
self.config.pluginmanager.consider_module(mod)
|
||||||
return mod
|
return mod
|
||||||
|
|
|
@ -701,7 +701,7 @@ Using fixtures from classes, modules or projects
|
||||||
Sometimes test functions do not directly need access to a fixture object.
|
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
|
For example, tests may require to operate with an empty directory as the
|
||||||
current working directory but otherwise do not care for the concrete
|
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
|
<http://docs.python.org/library/tempfile.html>`_ and pytest fixtures to
|
||||||
achieve it. We separate the creation of the fixture into a conftest.py
|
achieve it. We separate the creation of the fixture into a conftest.py
|
||||||
file::
|
file::
|
||||||
|
|
|
@ -236,9 +236,10 @@ your own setuptools Test command for invoking pytest.
|
||||||
self.pytest_args = []
|
self.pytest_args = []
|
||||||
|
|
||||||
def run_tests(self):
|
def run_tests(self):
|
||||||
|
import shlex
|
||||||
#import here, cause outside the eggs aren't loaded
|
#import here, cause outside the eggs aren't loaded
|
||||||
import pytest
|
import pytest
|
||||||
errno = pytest.main(self.pytest_args)
|
errno = pytest.main(shlex.split(self.pytest_args))
|
||||||
sys.exit(errno)
|
sys.exit(errno)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -55,6 +55,14 @@ will delete the method ``request.session.Session.request``
|
||||||
so that any attempts within tests to create http requests will fail.
|
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
|
Method reference of the monkeypatch fixture
|
||||||
-------------------------------------------
|
-------------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -310,10 +310,6 @@ You can pass in options and arguments::
|
||||||
|
|
||||||
pytest.main(['-x', 'mytestdir'])
|
pytest.main(['-x', 'mytestdir'])
|
||||||
|
|
||||||
or pass in a string::
|
|
||||||
|
|
||||||
pytest.main("-x mytestdir")
|
|
||||||
|
|
||||||
You can specify additional plugins to ``pytest.main``::
|
You can specify additional plugins to ``pytest.main``::
|
||||||
|
|
||||||
# content of myinvoke.py
|
# content of myinvoke.py
|
||||||
|
|
|
@ -120,7 +120,7 @@ class TestGeneralUsage:
|
||||||
result.stdout.fnmatch_lines([
|
result.stdout.fnmatch_lines([
|
||||||
#XXX on jython this fails: "> import import_fails",
|
#XXX on jython this fails: "> import import_fails",
|
||||||
"ImportError while importing test module*",
|
"ImportError while importing test module*",
|
||||||
"'No module named *does_not_work*",
|
"*No module named *does_not_work*",
|
||||||
])
|
])
|
||||||
assert result.ret == 2
|
assert result.ret == 2
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
|
|
||||||
|
@ -68,9 +69,41 @@ class TestModule:
|
||||||
result = testdir.runpytest("-rw")
|
result = testdir.runpytest("-rw")
|
||||||
result.stdout.fnmatch_lines([
|
result.stdout.fnmatch_lines([
|
||||||
"ImportError while importing test module*test_one.part1*",
|
"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:
|
class TestClass:
|
||||||
def test_class_with_init_warning(self, testdir):
|
def test_class_with_init_warning(self, testdir):
|
||||||
|
|
|
@ -172,17 +172,6 @@ class TestCollectPluginHookRelay:
|
||||||
assert "world" in wascalled
|
assert "world" in wascalled
|
||||||
|
|
||||||
class TestPrunetraceback:
|
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):
|
def test_custom_repr_failure(self, testdir):
|
||||||
p = testdir.makepyfile("""
|
p = testdir.makepyfile("""
|
||||||
|
|
|
@ -250,6 +250,18 @@ class TestParser:
|
||||||
help = parser.optparser.format_help()
|
help = parser.optparser.format_help()
|
||||||
assert '-doit, --func-args foo' in 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):
|
def test_argcomplete(testdir, monkeypatch):
|
||||||
if not py.path.local.sysfind('bash'):
|
if not py.path.local.sysfind('bash'):
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
# encoding: UTF-8
|
||||||
import pytest
|
import pytest
|
||||||
import py
|
import py
|
||||||
import os
|
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
|
"""Don't hide import errors when importing plugins and provide
|
||||||
an easy to debug message.
|
an easy to debug message.
|
||||||
|
|
||||||
|
See #375 and #1998.
|
||||||
"""
|
"""
|
||||||
testdir.syspathinsert(testdir.tmpdir)
|
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:
|
with pytest.raises(ImportError) as excinfo:
|
||||||
pytestpm.import_plugin("qwe")
|
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))
|
assert py.std.re.match(expected, str(excinfo.value))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -967,5 +967,5 @@ def test_module_level_skip_error(testdir):
|
||||||
""")
|
""")
|
||||||
result = testdir.runpytest()
|
result = testdir.runpytest()
|
||||||
result.stdout.fnmatch_lines(
|
result.stdout.fnmatch_lines(
|
||||||
"*Using @pytest.skip outside of a test * is not allowed*"
|
"*Using pytest.skip outside of a test is not allowed*"
|
||||||
)
|
)
|
||||||
|
|
|
@ -667,7 +667,7 @@ class TestGenericReporting:
|
||||||
result = testdir.runpytest(*option.args)
|
result = testdir.runpytest(*option.args)
|
||||||
result.stdout.fnmatch_lines([
|
result.stdout.fnmatch_lines([
|
||||||
"ImportError while importing*",
|
"ImportError while importing*",
|
||||||
"'No module named *xyz*",
|
"*No module named *xyz*",
|
||||||
"*1 error*",
|
"*1 error*",
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue