From a2891420de5a020fbcc09ee0af3124325334fc6d Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Mon, 20 Jun 2016 18:47:12 +0200 Subject: [PATCH 1/8] Fix determining rootdir from common_ancestor --- _pytest/config.py | 23 +++++++++++++++++---- testing/test_collection.py | 6 ++++-- testing/test_config.py | 42 +++++++++++++++++++++++++++++++++++--- 3 files changed, 62 insertions(+), 9 deletions(-) diff --git a/_pytest/config.py b/_pytest/config.py index 94de8a659..7345a8652 100644 --- a/_pytest/config.py +++ b/_pytest/config.py @@ -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 {} diff --git a/testing/test_collection.py b/testing/test_collection.py index cd0a5cc19..b70dce367 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -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' diff --git a/testing/test_config.py b/testing/test_config.py index 47c4a2adc..f1c762554 100644 --- a/testing/test_config.py +++ b/testing/test_config.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: ']) @@ -547,10 +548,14 @@ class TestWarning: 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 +600,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 From eb081352805d381454cdc9902ed66196ce9bd82b Mon Sep 17 00:00:00 2001 From: Dave Hunt Date: Tue, 21 Jun 2016 21:48:59 +0200 Subject: [PATCH 2/8] Update documentation to describe expected rootdir behaviour --- doc/en/customize.rst | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/doc/en/customize.rst b/doc/en/customize.rst index 34e319c24..a8e680967 100644 --- a/doc/en/customize.rst +++ b/doc/en/customize.rst @@ -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: From 21230aa017538dd4a508778f47b2e35cd96b7f78 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 6 Aug 2016 17:14:13 -0300 Subject: [PATCH 3/8] Add CHANGELOG entry --- CHANGELOG.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5e179050d..5cebf083d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -35,6 +35,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 @@ -70,11 +74,14 @@ * .. _#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 From e4028b4505148804fe30634a49980b4fc7de9a37 Mon Sep 17 00:00:00 2001 From: Christian Boelsen Date: Mon, 8 Aug 2016 13:29:20 +0100 Subject: [PATCH 4/8] Fix #1798 to include errors in total tests in junit xml output. --- AUTHORS | 1 + CHANGELOG.rst | 3 +++ _pytest/junitxml.py | 2 +- testing/test_junitxml.py | 27 ++++++++++++++++++++++++--- 4 files changed, 29 insertions(+), 4 deletions(-) diff --git a/AUTHORS b/AUTHORS index 0d5e1bb64..254207071 100644 --- a/AUTHORS +++ b/AUTHORS @@ -24,6 +24,7 @@ Carl Friedrich Bolz Charles Cloud Charnjit SiNGH (CCSJ) Chris Lamb +Christian Boelsen Christian Theunert Christian Tismer Christopher Gilling diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5cebf083d..ca2672f53 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -65,6 +65,8 @@ * Fixed scope overriding inside metafunc.parametrize (`#634`_). Thanks to `@Stranger6667`_ for the PR. +* Fixed the total tests tally in junit xml output (`#1798`_). + * * @@ -85,6 +87,7 @@ .. _#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 .. _#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 diff --git a/_pytest/junitxml.py b/_pytest/junitxml.py index 4c2b9d149..6041cb6f0 100644 --- a/_pytest/junitxml.py +++ b/_pytest/junitxml.py @@ -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('') logfile.write(Junit.testsuite( diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index a4f10dec5..c29381a86 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -102,6 +102,27 @@ class TestPython: node = dom.find_first_by_tag("testsuite") node.assert_attr(name="pytest", errors=0, failures=1, skips=3, 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") @@ -341,7 +362,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", From c4d9c7ea55db731b40764dcad7abbf0692690f8f Mon Sep 17 00:00:00 2001 From: Christian Boelsen Date: Mon, 8 Aug 2016 13:45:10 +0100 Subject: [PATCH 5/8] Add thanks as requested. --- CHANGELOG.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ca2672f53..ed7adaf2e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -66,6 +66,7 @@ Thanks to `@Stranger6667`_ for the PR. * Fixed the total tests tally in junit xml output (`#1798`_). + Thanks to `@cryporchild`_ for the PR. * @@ -96,6 +97,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 From 16cb5d01b133c0b00526fce94f8eb6c405fe3c21 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Fri, 12 Aug 2016 00:29:03 +0200 Subject: [PATCH 6/8] Fix off-by-one error with lines from request.node.warn The line numbers in `node.location` seem to be zero-based?! --- CHANGELOG.rst | 3 ++- _pytest/main.py | 2 +- testing/test_config.py | 5 +++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ed7adaf2e..6edb8c29f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -72,7 +72,8 @@ * -* +* Fixed off-by-one error with lines from ``request.node.warn``. + Thanks to `@blueyed`_ for the PR. * diff --git a/_pytest/main.py b/_pytest/main.py index 31b52c503..2ce9957af 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -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, diff --git a/testing/test_config.py b/testing/test_config.py index f1c762554..1997ddacd 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -525,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 """) @@ -542,7 +543,7 @@ 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: From c163cc7937024309897c1b4d6526210657708c2b Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Fri, 12 Aug 2016 00:57:29 +0200 Subject: [PATCH 7/8] Improve display of continuation lines with multiline errors Fixes https://github.com/pytest-dev/pytest/issues/717. Follow-up to https://github.com/pytest-dev/pytest/pull/1762. --- CHANGELOG.rst | 7 ++++--- _pytest/python.py | 18 ++++++++++-------- testing/python/fixture.py | 9 +++++---- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ed7adaf2e..89de60ffb 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -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`_). diff --git a/_pytest/python.py b/_pytest/python.py index dea7b58ce..2a23f477f 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -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 diff --git a/testing/python/fixture.py b/testing/python/fixture.py index 8b0a203bd..7264934a4 100644 --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -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() From 497152505ea6fd3362dd8ca95d187ddf8fdb6f8a Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Mon, 15 Aug 2016 19:31:00 -0300 Subject: [PATCH 8/8] Add CHANGELOG entry for #1809 --- CHANGELOG.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 6b08341a8..e8aa1982a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -69,7 +69,8 @@ * 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`_). * @@ -91,6 +92,7 @@ .. _#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