Skip to content

Commit bb3d6d8

Browse files
committed
Merge branch 'master' into fix-report-outcome-for-xpass
2 parents 018197d + 4971525 commit bb3d6d8

File tree

11 files changed

+147
-48
lines changed

11 files changed

+147
-48
lines changed

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Carl Friedrich Bolz
2424
Charles Cloud
2525
Charnjit SiNGH (CCSJ)
2626
Chris Lamb
27+
Christian Boelsen
2728
Christian Theunert
2829
Christian Tismer
2930
Christopher Gilling

CHANGELOG.rst

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33

44
**Bug Fixes**
55

6-
* Add an 'E' to the first line of error messages from FixtureLookupErrorRepr.
7-
Fixes `#717`_. Thanks `@blueyed`_ for reporting, `@eolo999`_ for the PR
8-
and `@tomviner`_ for his guidance during EuroPython2016 sprint.
6+
* Improve error message with fixture lookup errors: add an 'E' to the first
7+
line and '>' to the rest. Fixes `#717`_. Thanks `@blueyed`_ for reporting and
8+
a PR, `@eolo999`_ for the initial PR and `@tomviner`_ for his guidance during
9+
EuroPython2016 sprint.
910

1011
* Text documents without any doctests no longer appear as "skipped".
1112
Thanks `@graingert`_ for reporting and providing a full PR (`#1580`_).
@@ -35,6 +36,10 @@
3536
deprecated but still present. Thanks to `@RedBeardCode`_ and `@tomviner`_
3637
for PR (`#1626`_).
3738

39+
* Refined logic for determining the ``rootdir``, considering only valid
40+
paths which fixes a number of issues: `#1594`_, `#1435`_ and `#1471`_.
41+
Thanks to `@blueyed`_ and `@davehunt`_ for the PR.
42+
3843
* Always include full assertion explanation. The previous behaviour was hiding
3944
sub-expressions that happened to be False, assuming this was redundant information.
4045
Thanks `@bagerard`_ for reporting (`#1503`_). Thanks to `@davehunt`_ and
@@ -61,23 +66,33 @@
6166
* Fixed scope overriding inside metafunc.parametrize (`#634`_).
6267
Thanks to `@Stranger6667`_ for the PR.
6368

64-
*
69+
* Fixed the total tests tally in junit xml output (`#1798`_).
70+
Thanks to `@cryporchild`_ for the PR.
6571

66-
*
72+
* ``pytest_terminal_summary`` hook now receives the ``exitstatus``
73+
of the test session as argument. Thanks `@blueyed`_ for the PR (`#1809`_).
6774

6875
*
6976

77+
* Fixed off-by-one error with lines from ``request.node.warn``.
78+
Thanks to `@blueyed`_ for the PR.
79+
7080
*
7181

7282
.. _#1210: https://github.com/pytest-dev/pytest/issues/1210
83+
.. _#1435: https://github.com/pytest-dev/pytest/issues/1435
84+
.. _#1471: https://github.com/pytest-dev/pytest/issues/1471
7385
.. _#1479: https://github.com/pytest-dev/pytest/issues/1479
7486
.. _#1503: https://github.com/pytest-dev/pytest/issues/1503
7587
.. _#1553: https://github.com/pytest-dev/pytest/issues/1553
7688
.. _#1579: https://github.com/pytest-dev/pytest/issues/1579
7789
.. _#1580: https://github.com/pytest-dev/pytest/pull/1580
90+
.. _#1594: https://github.com/pytest-dev/pytest/issues/1594
7891
.. _#1597: https://github.com/pytest-dev/pytest/pull/1597
7992
.. _#1605: https://github.com/pytest-dev/pytest/issues/1605
8093
.. _#1626: https://github.com/pytest-dev/pytest/pull/1626
94+
.. _#1798: https://github.com/pytest-dev/pytest/pull/1798
95+
.. _#1809: https://github.com/pytest-dev/pytest/pull/1809
8196
.. _#460: https://github.com/pytest-dev/pytest/pull/460
8297
.. _#634: https://github.com/pytest-dev/pytest/issues/634
8398
.. _#717: https://github.com/pytest-dev/pytest/issues/717
@@ -86,6 +101,7 @@
86101
.. _@bagerard: https://github.com/bagerard
87102
.. _@BeyondEvil: https://github.com/BeyondEvil
88103
.. _@blueyed: https://github.com/blueyed
104+
.. _@cryporchild: https://github.com/cryporchild
89105
.. _@davehunt: https://github.com/davehunt
90106
.. _@DRMacIver: https://github.com/DRMacIver
91107
.. _@eolo999: https://github.com/eolo999

_pytest/config.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1095,6 +1095,8 @@ def get_common_ancestor(args):
10951095
if str(arg)[0] == "-":
10961096
continue
10971097
p = py.path.local(arg)
1098+
if not p.exists():
1099+
continue
10981100
if common_ancestor is None:
10991101
common_ancestor = p
11001102
else:
@@ -1108,29 +1110,42 @@ def get_common_ancestor(args):
11081110
common_ancestor = shared
11091111
if common_ancestor is None:
11101112
common_ancestor = py.path.local()
1111-
elif not common_ancestor.isdir():
1113+
elif common_ancestor.isfile():
11121114
common_ancestor = common_ancestor.dirpath()
11131115
return common_ancestor
11141116

11151117

1118+
def get_dirs_from_args(args):
1119+
return [d for d in (py.path.local(x) for x in args
1120+
if not str(x).startswith("-"))
1121+
if d.exists()]
1122+
1123+
11161124
def determine_setup(inifile, args):
1125+
dirs = get_dirs_from_args(args)
11171126
if inifile:
11181127
iniconfig = py.iniconfig.IniConfig(inifile)
11191128
try:
11201129
inicfg = iniconfig["pytest"]
11211130
except KeyError:
11221131
inicfg = None
1123-
rootdir = get_common_ancestor(args)
1132+
rootdir = get_common_ancestor(dirs)
11241133
else:
1125-
ancestor = get_common_ancestor(args)
1134+
ancestor = get_common_ancestor(dirs)
11261135
rootdir, inifile, inicfg = getcfg(
11271136
[ancestor], ["pytest.ini", "tox.ini", "setup.cfg"])
11281137
if rootdir is None:
11291138
for rootdir in ancestor.parts(reverse=True):
11301139
if rootdir.join("setup.py").exists():
11311140
break
11321141
else:
1133-
rootdir = ancestor
1142+
rootdir, inifile, inicfg = getcfg(
1143+
dirs, ["pytest.ini", "tox.ini", "setup.cfg"])
1144+
if rootdir is None:
1145+
rootdir = get_common_ancestor([py.path.local(), ancestor])
1146+
is_fs_root = os.path.splitdrive(str(rootdir))[1] == os.sep
1147+
if is_fs_root:
1148+
rootdir = ancestor
11341149
return rootdir, inifile, inicfg or {}
11351150

11361151

_pytest/junitxml.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,7 @@ def pytest_sessionfinish(self):
369369
suite_stop_time = time.time()
370370
suite_time_delta = suite_stop_time - self.suite_start_time
371371

372-
numtests = self.stats['passed'] + self.stats['failure'] + self.stats['skipped']
372+
numtests = self.stats['passed'] + self.stats['failure'] + self.stats['skipped'] + self.stats['error']
373373

374374
logfile.write('<?xml version="1.0" encoding="utf-8"?>')
375375
logfile.write(Junit.testsuite(

_pytest/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ def warn(self, code, message):
267267
if fslocation is None:
268268
fslocation = getattr(self, "fspath", None)
269269
else:
270-
fslocation = "%s:%s" % fslocation[:2]
270+
fslocation = "%s:%s" % (fslocation[0], fslocation[1] + 1)
271271

272272
self.ihook.pytest_logwarning.call_historic(kwargs=dict(
273273
code=code, message=message,

_pytest/python.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
import py
1111
import pytest
12-
from _pytest._code.code import TerminalRepr
12+
from _pytest._code.code import FormattedExcinfo, TerminalRepr
1313
from _pytest.mark import MarkDecorator, MarkerError
1414

1515
try:
@@ -1836,6 +1836,7 @@ def formatrepr(self):
18361836

18371837
return FixtureLookupErrorRepr(fspath, lineno, tblines, msg, self.argname)
18381838

1839+
18391840
class FixtureLookupErrorRepr(TerminalRepr):
18401841
def __init__(self, filename, firstlineno, tblines, errorstring, argname):
18411842
self.tblines = tblines
@@ -1845,19 +1846,20 @@ def __init__(self, filename, firstlineno, tblines, errorstring, argname):
18451846
self.argname = argname
18461847

18471848
def toterminal(self, tw):
1848-
#tw.line("FixtureLookupError: %s" %(self.argname), red=True)
1849+
# tw.line("FixtureLookupError: %s" %(self.argname), red=True)
18491850
for tbline in self.tblines:
18501851
tw.line(tbline.rstrip())
18511852
lines = self.errorstring.split("\n")
1852-
for line in lines:
1853-
if line == lines[0]:
1854-
prefix = 'E '
1855-
else:
1856-
prefix = ' '
1857-
tw.line(prefix + line.strip(), red=True)
1853+
if lines:
1854+
tw.line('{0} {1}'.format(FormattedExcinfo.fail_marker,
1855+
lines[0].strip()), red=True)
1856+
for line in lines[1:]:
1857+
tw.line('{0} {1}'.format(FormattedExcinfo.flow_marker,
1858+
line.strip()), red=True)
18581859
tw.line()
18591860
tw.line("%s:%d" % (self.filename, self.firstlineno+1))
18601861

1862+
18611863
class FixtureManager:
18621864
"""
18631865
pytest fixtures definitions and information is stored and managed

doc/en/customize.rst

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,25 +29,29 @@ project/testrun-specific information.
2929

3030
Here is the algorithm which finds the rootdir from ``args``:
3131

32-
- determine the common ancestor directory for the specified ``args``.
32+
- determine the common ancestor directory for the specified ``args`` that are
33+
recognised as paths that exist in the file system. If no such paths are
34+
found, the common ancestor directory is set to the current working directory.
3335

34-
- look for ``pytest.ini``, ``tox.ini`` and ``setup.cfg`` files in the
35-
ancestor directory and upwards. If one is matched, it becomes the
36-
ini-file and its directory becomes the rootdir. An existing
37-
``pytest.ini`` file will always be considered a match whereas
38-
``tox.ini`` and ``setup.cfg`` will only match if they contain
39-
a ``[pytest]`` section.
36+
- look for ``pytest.ini``, ``tox.ini`` and ``setup.cfg`` files in the ancestor
37+
directory and upwards. If one is matched, it becomes the ini-file and its
38+
directory becomes the rootdir.
4039

41-
- if no ini-file was found, look for ``setup.py`` upwards from
42-
the common ancestor directory to determine the ``rootdir``.
40+
- if no ini-file was found, look for ``setup.py`` upwards from the common
41+
ancestor directory to determine the ``rootdir``.
4342

44-
- if no ini-file and no ``setup.py`` was found, use the already
45-
determined common ancestor as root directory. This allows to
46-
work with pytest in structures that are not part of a package
47-
and don't have any particular ini-file configuration.
43+
- if no ``setup.py`` was found, look for ``pytest.ini``, ``tox.ini`` and
44+
``setup.cfg`` in each of the specified ``args`` and upwards. If one is
45+
matched, it becomes the ini-file and its directory becomes the rootdir.
4846

49-
Note that options from multiple ini-files candidates are never merged,
50-
the first one wins (``pytest.ini`` always wins even if it does not
47+
- if no ini-file was found, use the already determined common ancestor as root
48+
directory. This allows to work with pytest in structures that are not part of
49+
a package and don't have any particular ini-file configuration.
50+
51+
Note that an existing ``pytest.ini`` file will always be considered a match,
52+
whereas ``tox.ini`` and ``setup.cfg`` will only match if they contain a
53+
``[pytest]`` section. Options from multiple ini-files candidates are never
54+
merged - the first one wins (``pytest.ini`` always wins, even if it does not
5155
contain a ``[pytest]`` section).
5256

5357
The ``config`` object will subsequently carry these attributes:

testing/python/fixture.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -395,10 +395,11 @@ def test_lookup_error(unknown):
395395
""")
396396
result = testdir.runpytest()
397397
result.stdout.fnmatch_lines([
398-
"*ERROR*test_lookup_error*",
399-
"*def test_lookup_error(unknown):*",
400-
"*fixture*unknown*not found*",
401-
"*available fixtures*",
398+
"*ERROR at setup of test_lookup_error*",
399+
" def test_lookup_error(unknown):*",
400+
"E fixture 'unknown' not found",
401+
"> available fixtures:*",
402+
"> use 'py*test --fixtures *' for help on them.",
402403
"*1 error*",
403404
])
404405
assert "INTERNAL" not in result.stdout.str()

testing/test_collection.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -490,7 +490,8 @@ def test_method(self):
490490
class Test_getinitialnodes:
491491
def test_global_file(self, testdir, tmpdir):
492492
x = tmpdir.ensure("x.py")
493-
config = testdir.parseconfigure(x)
493+
with tmpdir.as_cwd():
494+
config = testdir.parseconfigure(x)
494495
col = testdir.getnode(config, x)
495496
assert isinstance(col, pytest.Module)
496497
assert col.name == 'x.py'
@@ -504,7 +505,8 @@ def test_pkgfile(self, testdir):
504505
subdir = tmpdir.join("subdir")
505506
x = subdir.ensure("x.py")
506507
subdir.ensure("__init__.py")
507-
config = testdir.parseconfigure(x)
508+
with subdir.as_cwd():
509+
config = testdir.parseconfigure(x)
508510
col = testdir.getnode(config, x)
509511
assert isinstance(col, pytest.Module)
510512
assert col.name == 'x.py'

testing/test_config.py

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -468,7 +468,8 @@ def test_consider_args_after_options_for_rootdir_and_inifile(testdir, args):
468468
args[i] = d1
469469
elif arg == 'dir2':
470470
args[i] = d2
471-
result = testdir.runpytest(*args)
471+
with root.as_cwd():
472+
result = testdir.runpytest(*args)
472473
result.stdout.fnmatch_lines(['*rootdir: *myroot, inifile: '])
473474

474475

@@ -524,13 +525,14 @@ def test_proper(pytestconfig):
524525
reprec = testdir.inline_run()
525526
reprec.assertoutcome(passed=1)
526527

527-
def test_warn_on_test_item_from_request(self, testdir):
528+
def test_warn_on_test_item_from_request(self, testdir, request):
528529
testdir.makepyfile("""
529530
import pytest
530531
531532
@pytest.fixture
532533
def fix(request):
533534
request.node.warn("T1", "hello")
535+
534536
def test_hello(fix):
535537
pass
536538
""")
@@ -541,16 +543,20 @@ def test_hello(fix):
541543
result = testdir.runpytest("-rw")
542544
result.stdout.fnmatch_lines("""
543545
===*pytest-warning summary*===
544-
*WT1*test_warn_on_test_item*:5*hello*
546+
*WT1*test_warn_on_test_item*:7 hello*
545547
""")
546548

547549
class TestRootdir:
548550
def test_simple_noini(self, tmpdir):
549551
assert get_common_ancestor([tmpdir]) == tmpdir
550-
assert get_common_ancestor([tmpdir.mkdir("a"), tmpdir]) == tmpdir
551-
assert get_common_ancestor([tmpdir, tmpdir.join("a")]) == tmpdir
552+
a = tmpdir.mkdir("a")
553+
assert get_common_ancestor([a, tmpdir]) == tmpdir
554+
assert get_common_ancestor([tmpdir, a]) == tmpdir
552555
with tmpdir.as_cwd():
553556
assert get_common_ancestor([]) == tmpdir
557+
no_path = tmpdir.join('does-not-exist')
558+
assert get_common_ancestor([no_path]) == tmpdir
559+
assert get_common_ancestor([no_path.join('a')]) == tmpdir
554560

555561
@pytest.mark.parametrize("name", "setup.cfg tox.ini pytest.ini".split())
556562
def test_with_ini(self, tmpdir, name):
@@ -595,3 +601,34 @@ def test_with_specific_inifile(self, tmpdir):
595601
inifile = tmpdir.ensure("pytest.ini")
596602
rootdir, inifile, inicfg = determine_setup(inifile, [tmpdir])
597603
assert rootdir == tmpdir
604+
605+
def test_with_arg_outside_cwd_without_inifile(self, tmpdir):
606+
a = tmpdir.mkdir("a")
607+
b = tmpdir.mkdir("b")
608+
rootdir, inifile, inicfg = determine_setup(None, [a, b])
609+
assert rootdir == tmpdir
610+
assert inifile is None
611+
612+
def test_with_arg_outside_cwd_with_inifile(self, tmpdir):
613+
a = tmpdir.mkdir("a")
614+
b = tmpdir.mkdir("b")
615+
inifile = a.ensure("pytest.ini")
616+
rootdir, parsed_inifile, inicfg = determine_setup(None, [a, b])
617+
assert rootdir == a
618+
assert inifile == parsed_inifile
619+
620+
@pytest.mark.parametrize('dirs', ([], ['does-not-exist'],
621+
['a/does-not-exist']))
622+
def test_with_non_dir_arg(self, dirs, tmpdir):
623+
with tmpdir.ensure(dir=True).as_cwd():
624+
rootdir, inifile, inicfg = determine_setup(None, dirs)
625+
assert rootdir == tmpdir
626+
assert inifile is None
627+
628+
def test_with_existing_file_in_subdir(self, tmpdir):
629+
a = tmpdir.mkdir("a")
630+
a.ensure("exist")
631+
with tmpdir.as_cwd():
632+
rootdir, inifile, inicfg = determine_setup(None, ['a/exist'])
633+
assert rootdir == tmpdir
634+
assert inifile is None

0 commit comments

Comments
 (0)