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:
|
||||
|
||||
- [ ] 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`
|
||||
|
|
|
@ -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
|
||||
|
|
1
AUTHORS
1
AUTHORS
|
@ -59,6 +59,7 @@ Georgy Dyuldin
|
|||
Graham Horler
|
||||
Greg Price
|
||||
Grig Gheorghiu
|
||||
Grigorii Eremeev (budulianin)
|
||||
Guido Wesdorp
|
||||
Harald Armin Massa
|
||||
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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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::
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
-------------------------------------------
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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("""
|
||||
|
|
|
@ -248,7 +248,19 @@ class TestParser:
|
|||
help="show help message and configuration info")
|
||||
parser.parse(['-h'])
|
||||
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):
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
||||
|
|
|
@ -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*"
|
||||
)
|
||||
|
|
|
@ -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*",
|
||||
])
|
||||
|
||||
|
|
Loading…
Reference in New Issue