Merge branch 'master' into fix-report-outcome-for-xpass
This commit is contained in:
commit
bb3d6d87b6
1
AUTHORS
1
AUTHORS
|
@ -24,6 +24,7 @@ Carl Friedrich Bolz
|
|||
Charles Cloud
|
||||
Charnjit SiNGH (CCSJ)
|
||||
Chris Lamb
|
||||
Christian Boelsen
|
||||
Christian Theunert
|
||||
Christian Tismer
|
||||
Christopher Gilling
|
||||
|
|
|
@ -3,9 +3,10 @@
|
|||
|
||||
**Bug Fixes**
|
||||
|
||||
* Add an 'E' to the first line of error messages from FixtureLookupErrorRepr.
|
||||
Fixes `#717`_. Thanks `@blueyed`_ for reporting, `@eolo999`_ for the PR
|
||||
and `@tomviner`_ for his guidance during EuroPython2016 sprint.
|
||||
* Improve error message with fixture lookup errors: add an 'E' to the first
|
||||
line and '>' to the rest. Fixes `#717`_. Thanks `@blueyed`_ for reporting and
|
||||
a PR, `@eolo999`_ for the initial PR and `@tomviner`_ for his guidance during
|
||||
EuroPython2016 sprint.
|
||||
|
||||
* Text documents without any doctests no longer appear as "skipped".
|
||||
Thanks `@graingert`_ for reporting and providing a full PR (`#1580`_).
|
||||
|
@ -35,6 +36,10 @@
|
|||
deprecated but still present. Thanks to `@RedBeardCode`_ and `@tomviner`_
|
||||
for PR (`#1626`_).
|
||||
|
||||
* Refined logic for determining the ``rootdir``, considering only valid
|
||||
paths which fixes a number of issues: `#1594`_, `#1435`_ and `#1471`_.
|
||||
Thanks to `@blueyed`_ and `@davehunt`_ for the PR.
|
||||
|
||||
* Always include full assertion explanation. The previous behaviour was hiding
|
||||
sub-expressions that happened to be False, assuming this was redundant information.
|
||||
Thanks `@bagerard`_ for reporting (`#1503`_). Thanks to `@davehunt`_ and
|
||||
|
@ -61,23 +66,33 @@
|
|||
* Fixed scope overriding inside metafunc.parametrize (`#634`_).
|
||||
Thanks to `@Stranger6667`_ for the PR.
|
||||
|
||||
*
|
||||
* Fixed the total tests tally in junit xml output (`#1798`_).
|
||||
Thanks to `@cryporchild`_ for the PR.
|
||||
|
||||
* ``pytest_terminal_summary`` hook now receives the ``exitstatus``
|
||||
of the test session as argument. Thanks `@blueyed`_ for the PR (`#1809`_).
|
||||
|
||||
*
|
||||
|
||||
*
|
||||
* Fixed off-by-one error with lines from ``request.node.warn``.
|
||||
Thanks to `@blueyed`_ for the PR.
|
||||
|
||||
*
|
||||
|
||||
.. _#1210: https://github.com/pytest-dev/pytest/issues/1210
|
||||
.. _#1435: https://github.com/pytest-dev/pytest/issues/1435
|
||||
.. _#1471: https://github.com/pytest-dev/pytest/issues/1471
|
||||
.. _#1479: https://github.com/pytest-dev/pytest/issues/1479
|
||||
.. _#1503: https://github.com/pytest-dev/pytest/issues/1503
|
||||
.. _#1553: https://github.com/pytest-dev/pytest/issues/1553
|
||||
.. _#1579: https://github.com/pytest-dev/pytest/issues/1579
|
||||
.. _#1580: https://github.com/pytest-dev/pytest/pull/1580
|
||||
.. _#1594: https://github.com/pytest-dev/pytest/issues/1594
|
||||
.. _#1597: https://github.com/pytest-dev/pytest/pull/1597
|
||||
.. _#1605: https://github.com/pytest-dev/pytest/issues/1605
|
||||
.. _#1626: https://github.com/pytest-dev/pytest/pull/1626
|
||||
.. _#1798: https://github.com/pytest-dev/pytest/pull/1798
|
||||
.. _#1809: https://github.com/pytest-dev/pytest/pull/1809
|
||||
.. _#460: https://github.com/pytest-dev/pytest/pull/460
|
||||
.. _#634: https://github.com/pytest-dev/pytest/issues/634
|
||||
.. _#717: https://github.com/pytest-dev/pytest/issues/717
|
||||
|
@ -86,6 +101,7 @@
|
|||
.. _@bagerard: https://github.com/bagerard
|
||||
.. _@BeyondEvil: https://github.com/BeyondEvil
|
||||
.. _@blueyed: https://github.com/blueyed
|
||||
.. _@cryporchild: https://github.com/cryporchild
|
||||
.. _@davehunt: https://github.com/davehunt
|
||||
.. _@DRMacIver: https://github.com/DRMacIver
|
||||
.. _@eolo999: https://github.com/eolo999
|
||||
|
|
|
@ -1095,6 +1095,8 @@ def get_common_ancestor(args):
|
|||
if str(arg)[0] == "-":
|
||||
continue
|
||||
p = py.path.local(arg)
|
||||
if not p.exists():
|
||||
continue
|
||||
if common_ancestor is None:
|
||||
common_ancestor = p
|
||||
else:
|
||||
|
@ -1108,21 +1110,28 @@ def get_common_ancestor(args):
|
|||
common_ancestor = shared
|
||||
if common_ancestor is None:
|
||||
common_ancestor = py.path.local()
|
||||
elif not common_ancestor.isdir():
|
||||
elif common_ancestor.isfile():
|
||||
common_ancestor = common_ancestor.dirpath()
|
||||
return common_ancestor
|
||||
|
||||
|
||||
def get_dirs_from_args(args):
|
||||
return [d for d in (py.path.local(x) for x in args
|
||||
if not str(x).startswith("-"))
|
||||
if d.exists()]
|
||||
|
||||
|
||||
def determine_setup(inifile, args):
|
||||
dirs = get_dirs_from_args(args)
|
||||
if inifile:
|
||||
iniconfig = py.iniconfig.IniConfig(inifile)
|
||||
try:
|
||||
inicfg = iniconfig["pytest"]
|
||||
except KeyError:
|
||||
inicfg = None
|
||||
rootdir = get_common_ancestor(args)
|
||||
rootdir = get_common_ancestor(dirs)
|
||||
else:
|
||||
ancestor = get_common_ancestor(args)
|
||||
ancestor = get_common_ancestor(dirs)
|
||||
rootdir, inifile, inicfg = getcfg(
|
||||
[ancestor], ["pytest.ini", "tox.ini", "setup.cfg"])
|
||||
if rootdir is None:
|
||||
|
@ -1130,7 +1139,13 @@ def determine_setup(inifile, args):
|
|||
if rootdir.join("setup.py").exists():
|
||||
break
|
||||
else:
|
||||
rootdir = ancestor
|
||||
rootdir, inifile, inicfg = getcfg(
|
||||
dirs, ["pytest.ini", "tox.ini", "setup.cfg"])
|
||||
if rootdir is None:
|
||||
rootdir = get_common_ancestor([py.path.local(), ancestor])
|
||||
is_fs_root = os.path.splitdrive(str(rootdir))[1] == os.sep
|
||||
if is_fs_root:
|
||||
rootdir = ancestor
|
||||
return rootdir, inifile, inicfg or {}
|
||||
|
||||
|
||||
|
|
|
@ -369,7 +369,7 @@ class LogXML(object):
|
|||
suite_stop_time = time.time()
|
||||
suite_time_delta = suite_stop_time - self.suite_start_time
|
||||
|
||||
numtests = self.stats['passed'] + self.stats['failure'] + self.stats['skipped']
|
||||
numtests = self.stats['passed'] + self.stats['failure'] + self.stats['skipped'] + self.stats['error']
|
||||
|
||||
logfile.write('<?xml version="1.0" encoding="utf-8"?>')
|
||||
logfile.write(Junit.testsuite(
|
||||
|
|
|
@ -267,7 +267,7 @@ class Node(object):
|
|||
if fslocation is None:
|
||||
fslocation = getattr(self, "fspath", None)
|
||||
else:
|
||||
fslocation = "%s:%s" % fslocation[:2]
|
||||
fslocation = "%s:%s" % (fslocation[0], fslocation[1] + 1)
|
||||
|
||||
self.ihook.pytest_logwarning.call_historic(kwargs=dict(
|
||||
code=code, message=message,
|
||||
|
|
|
@ -9,7 +9,7 @@ import warnings
|
|||
|
||||
import py
|
||||
import pytest
|
||||
from _pytest._code.code import TerminalRepr
|
||||
from _pytest._code.code import FormattedExcinfo, TerminalRepr
|
||||
from _pytest.mark import MarkDecorator, MarkerError
|
||||
|
||||
try:
|
||||
|
@ -1836,6 +1836,7 @@ class FixtureLookupError(LookupError):
|
|||
|
||||
return FixtureLookupErrorRepr(fspath, lineno, tblines, msg, self.argname)
|
||||
|
||||
|
||||
class FixtureLookupErrorRepr(TerminalRepr):
|
||||
def __init__(self, filename, firstlineno, tblines, errorstring, argname):
|
||||
self.tblines = tblines
|
||||
|
@ -1845,19 +1846,20 @@ class FixtureLookupErrorRepr(TerminalRepr):
|
|||
self.argname = argname
|
||||
|
||||
def toterminal(self, tw):
|
||||
#tw.line("FixtureLookupError: %s" %(self.argname), red=True)
|
||||
# tw.line("FixtureLookupError: %s" %(self.argname), red=True)
|
||||
for tbline in self.tblines:
|
||||
tw.line(tbline.rstrip())
|
||||
lines = self.errorstring.split("\n")
|
||||
for line in lines:
|
||||
if line == lines[0]:
|
||||
prefix = 'E '
|
||||
else:
|
||||
prefix = ' '
|
||||
tw.line(prefix + line.strip(), red=True)
|
||||
if lines:
|
||||
tw.line('{0} {1}'.format(FormattedExcinfo.fail_marker,
|
||||
lines[0].strip()), red=True)
|
||||
for line in lines[1:]:
|
||||
tw.line('{0} {1}'.format(FormattedExcinfo.flow_marker,
|
||||
line.strip()), red=True)
|
||||
tw.line()
|
||||
tw.line("%s:%d" % (self.filename, self.firstlineno+1))
|
||||
|
||||
|
||||
class FixtureManager:
|
||||
"""
|
||||
pytest fixtures definitions and information is stored and managed
|
||||
|
|
|
@ -29,25 +29,29 @@ project/testrun-specific information.
|
|||
|
||||
Here is the algorithm which finds the rootdir from ``args``:
|
||||
|
||||
- determine the common ancestor directory for the specified ``args``.
|
||||
- determine the common ancestor directory for the specified ``args`` that are
|
||||
recognised as paths that exist in the file system. If no such paths are
|
||||
found, the common ancestor directory is set to the current working directory.
|
||||
|
||||
- look for ``pytest.ini``, ``tox.ini`` and ``setup.cfg`` files in the
|
||||
ancestor directory and upwards. If one is matched, it becomes the
|
||||
ini-file and its directory becomes the rootdir. An existing
|
||||
``pytest.ini`` file will always be considered a match whereas
|
||||
``tox.ini`` and ``setup.cfg`` will only match if they contain
|
||||
a ``[pytest]`` section.
|
||||
- look for ``pytest.ini``, ``tox.ini`` and ``setup.cfg`` files in the ancestor
|
||||
directory and upwards. If one is matched, it becomes the ini-file and its
|
||||
directory becomes the rootdir.
|
||||
|
||||
- if no ini-file was found, look for ``setup.py`` upwards from
|
||||
the common ancestor directory to determine the ``rootdir``.
|
||||
- if no ini-file was found, look for ``setup.py`` upwards from the common
|
||||
ancestor directory to determine the ``rootdir``.
|
||||
|
||||
- if no ini-file and no ``setup.py`` was found, use the already
|
||||
determined common ancestor as root directory. This allows to
|
||||
work with pytest in structures that are not part of a package
|
||||
and don't have any particular ini-file configuration.
|
||||
- if no ``setup.py`` was found, look for ``pytest.ini``, ``tox.ini`` and
|
||||
``setup.cfg`` in each of the specified ``args`` and upwards. If one is
|
||||
matched, it becomes the ini-file and its directory becomes the rootdir.
|
||||
|
||||
Note that options from multiple ini-files candidates are never merged,
|
||||
the first one wins (``pytest.ini`` always wins even if it does not
|
||||
- if no ini-file was found, use the already determined common ancestor as root
|
||||
directory. This allows to work with pytest in structures that are not part of
|
||||
a package and don't have any particular ini-file configuration.
|
||||
|
||||
Note that an existing ``pytest.ini`` file will always be considered a match,
|
||||
whereas ``tox.ini`` and ``setup.cfg`` will only match if they contain a
|
||||
``[pytest]`` section. Options from multiple ini-files candidates are never
|
||||
merged - the first one wins (``pytest.ini`` always wins, even if it does not
|
||||
contain a ``[pytest]`` section).
|
||||
|
||||
The ``config`` object will subsequently carry these attributes:
|
||||
|
|
|
@ -395,10 +395,11 @@ class TestFillFixtures:
|
|||
""")
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines([
|
||||
"*ERROR*test_lookup_error*",
|
||||
"*def test_lookup_error(unknown):*",
|
||||
"*fixture*unknown*not found*",
|
||||
"*available fixtures*",
|
||||
"*ERROR at setup of test_lookup_error*",
|
||||
" def test_lookup_error(unknown):*",
|
||||
"E fixture 'unknown' not found",
|
||||
"> available fixtures:*",
|
||||
"> use 'py*test --fixtures *' for help on them.",
|
||||
"*1 error*",
|
||||
])
|
||||
assert "INTERNAL" not in result.stdout.str()
|
||||
|
|
|
@ -490,7 +490,8 @@ class TestSession:
|
|||
class Test_getinitialnodes:
|
||||
def test_global_file(self, testdir, tmpdir):
|
||||
x = tmpdir.ensure("x.py")
|
||||
config = testdir.parseconfigure(x)
|
||||
with tmpdir.as_cwd():
|
||||
config = testdir.parseconfigure(x)
|
||||
col = testdir.getnode(config, x)
|
||||
assert isinstance(col, pytest.Module)
|
||||
assert col.name == 'x.py'
|
||||
|
@ -504,7 +505,8 @@ class Test_getinitialnodes:
|
|||
subdir = tmpdir.join("subdir")
|
||||
x = subdir.ensure("x.py")
|
||||
subdir.ensure("__init__.py")
|
||||
config = testdir.parseconfigure(x)
|
||||
with subdir.as_cwd():
|
||||
config = testdir.parseconfigure(x)
|
||||
col = testdir.getnode(config, x)
|
||||
assert isinstance(col, pytest.Module)
|
||||
assert col.name == 'x.py'
|
||||
|
|
|
@ -468,7 +468,8 @@ def test_consider_args_after_options_for_rootdir_and_inifile(testdir, args):
|
|||
args[i] = d1
|
||||
elif arg == 'dir2':
|
||||
args[i] = d2
|
||||
result = testdir.runpytest(*args)
|
||||
with root.as_cwd():
|
||||
result = testdir.runpytest(*args)
|
||||
result.stdout.fnmatch_lines(['*rootdir: *myroot, inifile: '])
|
||||
|
||||
|
||||
|
@ -524,13 +525,14 @@ class TestWarning:
|
|||
reprec = testdir.inline_run()
|
||||
reprec.assertoutcome(passed=1)
|
||||
|
||||
def test_warn_on_test_item_from_request(self, testdir):
|
||||
def test_warn_on_test_item_from_request(self, testdir, request):
|
||||
testdir.makepyfile("""
|
||||
import pytest
|
||||
|
||||
@pytest.fixture
|
||||
def fix(request):
|
||||
request.node.warn("T1", "hello")
|
||||
|
||||
def test_hello(fix):
|
||||
pass
|
||||
""")
|
||||
|
@ -541,16 +543,20 @@ class TestWarning:
|
|||
result = testdir.runpytest("-rw")
|
||||
result.stdout.fnmatch_lines("""
|
||||
===*pytest-warning summary*===
|
||||
*WT1*test_warn_on_test_item*:5*hello*
|
||||
*WT1*test_warn_on_test_item*:7 hello*
|
||||
""")
|
||||
|
||||
class TestRootdir:
|
||||
def test_simple_noini(self, tmpdir):
|
||||
assert get_common_ancestor([tmpdir]) == tmpdir
|
||||
assert get_common_ancestor([tmpdir.mkdir("a"), tmpdir]) == tmpdir
|
||||
assert get_common_ancestor([tmpdir, tmpdir.join("a")]) == tmpdir
|
||||
a = tmpdir.mkdir("a")
|
||||
assert get_common_ancestor([a, tmpdir]) == tmpdir
|
||||
assert get_common_ancestor([tmpdir, a]) == tmpdir
|
||||
with tmpdir.as_cwd():
|
||||
assert get_common_ancestor([]) == tmpdir
|
||||
no_path = tmpdir.join('does-not-exist')
|
||||
assert get_common_ancestor([no_path]) == tmpdir
|
||||
assert get_common_ancestor([no_path.join('a')]) == tmpdir
|
||||
|
||||
@pytest.mark.parametrize("name", "setup.cfg tox.ini pytest.ini".split())
|
||||
def test_with_ini(self, tmpdir, name):
|
||||
|
@ -595,3 +601,34 @@ class TestRootdir:
|
|||
inifile = tmpdir.ensure("pytest.ini")
|
||||
rootdir, inifile, inicfg = determine_setup(inifile, [tmpdir])
|
||||
assert rootdir == tmpdir
|
||||
|
||||
def test_with_arg_outside_cwd_without_inifile(self, tmpdir):
|
||||
a = tmpdir.mkdir("a")
|
||||
b = tmpdir.mkdir("b")
|
||||
rootdir, inifile, inicfg = determine_setup(None, [a, b])
|
||||
assert rootdir == tmpdir
|
||||
assert inifile is None
|
||||
|
||||
def test_with_arg_outside_cwd_with_inifile(self, tmpdir):
|
||||
a = tmpdir.mkdir("a")
|
||||
b = tmpdir.mkdir("b")
|
||||
inifile = a.ensure("pytest.ini")
|
||||
rootdir, parsed_inifile, inicfg = determine_setup(None, [a, b])
|
||||
assert rootdir == a
|
||||
assert inifile == parsed_inifile
|
||||
|
||||
@pytest.mark.parametrize('dirs', ([], ['does-not-exist'],
|
||||
['a/does-not-exist']))
|
||||
def test_with_non_dir_arg(self, dirs, tmpdir):
|
||||
with tmpdir.ensure(dir=True).as_cwd():
|
||||
rootdir, inifile, inicfg = determine_setup(None, dirs)
|
||||
assert rootdir == tmpdir
|
||||
assert inifile is None
|
||||
|
||||
def test_with_existing_file_in_subdir(self, tmpdir):
|
||||
a = tmpdir.mkdir("a")
|
||||
a.ensure("exist")
|
||||
with tmpdir.as_cwd():
|
||||
rootdir, inifile, inicfg = determine_setup(None, ['a/exist'])
|
||||
assert rootdir == tmpdir
|
||||
assert inifile is None
|
||||
|
|
|
@ -102,6 +102,27 @@ class TestPython:
|
|||
node = dom.find_first_by_tag("testsuite")
|
||||
node.assert_attr(name="pytest", errors=0, failures=1, skips=2, tests=5)
|
||||
|
||||
def test_summing_simple_with_errors(self, testdir):
|
||||
testdir.makepyfile("""
|
||||
import pytest
|
||||
@pytest.fixture
|
||||
def fixture():
|
||||
raise Exception()
|
||||
def test_pass():
|
||||
pass
|
||||
def test_fail():
|
||||
assert 0
|
||||
def test_error(fixture):
|
||||
pass
|
||||
@pytest.mark.xfail
|
||||
def test_xpass():
|
||||
assert 1
|
||||
""")
|
||||
result, dom = runandparse(testdir)
|
||||
assert result.ret
|
||||
node = dom.find_first_by_tag("testsuite")
|
||||
node.assert_attr(name="pytest", errors=1, failures=1, skips=1, tests=4)
|
||||
|
||||
def test_timing_function(self, testdir):
|
||||
testdir.makepyfile("""
|
||||
import time, pytest
|
||||
|
@ -128,7 +149,7 @@ class TestPython:
|
|||
result, dom = runandparse(testdir)
|
||||
assert result.ret
|
||||
node = dom.find_first_by_tag("testsuite")
|
||||
node.assert_attr(errors=1, tests=0)
|
||||
node.assert_attr(errors=1, tests=1)
|
||||
tnode = node.find_first_by_tag("testcase")
|
||||
tnode.assert_attr(
|
||||
file="test_setup_error.py",
|
||||
|
@ -195,7 +216,7 @@ class TestPython:
|
|||
result, dom = runandparse(testdir)
|
||||
assert result.ret
|
||||
node = dom.find_first_by_tag("testsuite")
|
||||
node.assert_attr(errors=1, tests=0)
|
||||
node.assert_attr(errors=1, tests=1)
|
||||
tnode = node.find_first_by_tag("testcase")
|
||||
tnode.assert_attr(classname="pytest", name="internal")
|
||||
fnode = tnode.find_first_by_tag("error")
|
||||
|
@ -358,7 +379,7 @@ class TestPython:
|
|||
result, dom = runandparse(testdir)
|
||||
assert result.ret
|
||||
node = dom.find_first_by_tag("testsuite")
|
||||
node.assert_attr(errors=1, tests=0)
|
||||
node.assert_attr(errors=1, tests=1)
|
||||
tnode = node.find_first_by_tag("testcase")
|
||||
tnode.assert_attr(
|
||||
file="test_collect_error.py",
|
||||
|
|
Loading…
Reference in New Issue