From 3d18c9c1c6e58e30da97e74fc79efb91f1aae24e Mon Sep 17 00:00:00 2001 From: Barney Gale Date: Sun, 18 Dec 2016 21:30:00 +0000 Subject: [PATCH 01/23] 'xfail' markers without a condition no longer rely on the underlying `Item` deriving from `PyobjMixin` --- _pytest/skipping.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_pytest/skipping.py b/_pytest/skipping.py index a8eaea98aac..cfaed2fa783 100644 --- a/_pytest/skipping.py +++ b/_pytest/skipping.py @@ -119,7 +119,6 @@ def _istrue(self): if hasattr(self, 'result'): return self.result if self.holder: - d = self._getglobals() if self.holder.args or 'condition' in self.holder.kwargs: self.result = False # "holder" might be a MarkInfo or a MarkDecorator; only @@ -135,6 +134,7 @@ def _istrue(self): for expr in args: self.expr = expr if isinstance(expr, py.builtin._basestring): + d = self._getglobals() result = cached_eval(self.item.config, expr, d) else: if "reason" not in kwargs: From 8db9915374f60d00780b758b98ada8d3f80af9bb Mon Sep 17 00:00:00 2001 From: Barney Gale Date: Sun, 18 Dec 2016 21:39:35 +0000 Subject: [PATCH 02/23] Update AUTHORS, CHANGELOG --- AUTHORS | 1 + CHANGELOG.rst | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 8c6fa1b5d78..3999db9b36c 100644 --- a/AUTHORS +++ b/AUTHORS @@ -16,6 +16,7 @@ Antony Lee Armin Rigo Aron Curzon Aviv Palivoda +Barney Gale Ben Webb Benjamin Peterson Bernard Pratz diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 7c3df0327db..64453f259c4 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,7 +1,10 @@ 3.0.6.dev0 (unreleased) ======================= -* +* Conditionless ``xfail`` markers no longer rely on the underlying test item + being an instance of ``PyobjMixin``, and can therefore apply to tests not + collected by the built-in python test collector. Thanks `@barneygale`_ for the + PR. * pytest no longer recognizes coroutine functions as yield tests (`#2129`_). Thanks to `@malinoff`_ for the PR. From df409a0c0ee9948d3725ee79c4a0a91960466af7 Mon Sep 17 00:00:00 2001 From: Barney Gale Date: Mon, 2 Jan 2017 22:01:40 +0000 Subject: [PATCH 03/23] Fix CHANGELOG.rst --- CHANGELOG.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 64453f259c4..eca520bbc44 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -19,6 +19,7 @@ * +.. _@barneygale: https://github.com/barneygale .. _@lesteve: https://github.com/lesteve .. _@malinoff: https://github.com/malinoff .. _@pelme: https://github.com/pelme From d1c725078a8c2fccfc573772e1944b9fe2a902e6 Mon Sep 17 00:00:00 2001 From: mbyt Date: Mon, 30 Jan 2017 21:20:12 +0100 Subject: [PATCH 04/23] Allow to skip unittests if --pdb active closes #2137 --- CHANGELOG.rst | 7 ++++++- _pytest/unittest.py | 14 ++++++++++++-- testing/test_pdb.py | 14 ++++++++++++++ 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 006c0ed0f3d..7f24a32829a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,7 +1,8 @@ 3.0.7 (unreleased) ======================= -* +* Fix regression, pytest now skips unittest correctly if run with ``--pdb`` + (`#2137`_). Thanks to `@gst`_ for the report and `@mbyt`_ for the PR. * @@ -9,6 +10,10 @@ * +.. _@gst: https://github.com/gst + +.. _#2137: https://github.com/pytest-dev/pytest/issues/2137 + 3.0.6 (2017-01-22) ================== diff --git a/_pytest/unittest.py b/_pytest/unittest.py index 73224010b21..4d303e7e300 100644 --- a/_pytest/unittest.py +++ b/_pytest/unittest.py @@ -65,7 +65,6 @@ def collect(self): yield TestCaseFunction('runTest', parent=self) - class TestCaseFunction(pytest.Function): _excinfo = None @@ -157,9 +156,20 @@ def runtest(self): self._testcase(result=self) else: # disables tearDown and cleanups for post mortem debugging (see #1890) + # but still implements the skipping machinery (see #2137) + testMethod = getattr(self._testcase, self._testcase._testMethodName) + if (getattr(self._testcase.__class__, "__unittest_skip__", False) or + getattr(testMethod, "__unittest_skip__", False)): + # If the class or method was skipped. + skip_why = (getattr(self._testcase.__class__, '__unittest_skip_why__', '') + or getattr(testMethod, '__unittest_skip_why__', '')) + try: + self._testcase._addSkip(self, self._testcase, skip_why) + except TypeError: # PY2 + self._testcase._addSkip(self, skip_why) + return self._testcase.debug() - def _prunetraceback(self, excinfo): pytest.Function._prunetraceback(self, excinfo) traceback = excinfo.traceback.filter( diff --git a/testing/test_pdb.py b/testing/test_pdb.py index df58dad8729..8a2efdb87f5 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -106,6 +106,20 @@ def test_false(self): assert 'debug.me' in rest self.flush(child) + def test_pdb_unittest_skip(self, testdir): + p1 = testdir.makepyfile(""" + import unittest + @unittest.skipIf(True, 'Skipping also with pdb active') + class MyTestCase(unittest.TestCase): + def test_one(self): + assert 0 + """) + child = testdir.spawn_pytest("-rs --pdb %s" % p1) + child.expect('Skipping also with pdb active') + child.expect('1 skipped in') + child.sendeof() + self.flush(child) + def test_pdb_interaction_capture(self, testdir): p1 = testdir.makepyfile(""" def test_1(): From 36b6f17727589defa28101142009410c9c771289 Mon Sep 17 00:00:00 2001 From: mbyt Date: Tue, 31 Jan 2017 21:03:49 +0100 Subject: [PATCH 05/23] fixing code-style, keep flake8 happy --- _pytest/unittest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_pytest/unittest.py b/_pytest/unittest.py index 4d303e7e300..0126ff83833 100644 --- a/_pytest/unittest.py +++ b/_pytest/unittest.py @@ -161,8 +161,8 @@ def runtest(self): if (getattr(self._testcase.__class__, "__unittest_skip__", False) or getattr(testMethod, "__unittest_skip__", False)): # If the class or method was skipped. - skip_why = (getattr(self._testcase.__class__, '__unittest_skip_why__', '') - or getattr(testMethod, '__unittest_skip_why__', '')) + skip_why = (getattr(self._testcase.__class__, '__unittest_skip_why__', '') or + getattr(testMethod, '__unittest_skip_why__', '')) try: self._testcase._addSkip(self, self._testcase, skip_why) except TypeError: # PY2 From e1c5314d80ad7b90257bfaf7a807a3d2e10f4494 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 1 Feb 2017 02:37:55 -0200 Subject: [PATCH 06/23] Replace 'raise StopIteration' usages in the code by 'return's in accordance to PEP-479 Fix #2160 --- CHANGELOG.rst | 9 ++++++++- _pytest/python.py | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 006c0ed0f3d..612ee0f3b38 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,14 +1,21 @@ 3.0.7 (unreleased) -======================= +================== * +* Replace ``raise StopIteration`` usages in the code by simple ``returns`` to finish generators, in accordance to `PEP-479`_ (`#2160`_). + Thanks `@tgoodlet`_ for the report and `@nicoddemus`_ for the PR. + * * * +.. _#2160: https://github.com/pytest-dev/pytest/issues/2160 + +.. _PEP-479: https://www.python.org/dev/peps/pep-0479/ + 3.0.6 (2017-01-22) ================== diff --git a/_pytest/python.py b/_pytest/python.py index e46f2f1bcfb..2973d43d699 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -174,7 +174,7 @@ def pytest_pycollect_makeitem(collector, name, obj): outcome = yield res = outcome.get_result() if res is not None: - raise StopIteration + return # nothing was collected elsewhere, let's do it here if isclass(obj): if collector.istestclass(obj, name): From ad56cd8027756a8bc1aa83402bbfffeae3520129 Mon Sep 17 00:00:00 2001 From: mbyt Date: Thu, 2 Feb 2017 05:01:51 +0100 Subject: [PATCH 07/23] extract a _handle_skip method, secure PY2 branch --- _pytest/unittest.py | 30 +++++++++++++++++++----------- testing/test_pdb.py | 1 + 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/_pytest/unittest.py b/_pytest/unittest.py index 0126ff83833..34eb9885bee 100644 --- a/_pytest/unittest.py +++ b/_pytest/unittest.py @@ -151,22 +151,30 @@ def addSuccess(self, testcase): def stopTest(self, testcase): pass + def _handle_skip(self): + # implements the skipping machinery (see #2137) + # analog to pythons Lib/unittest/case.py:run + testMethod = getattr(self._testcase, self._testcase._testMethodName) + if (getattr(self._testcase.__class__, "__unittest_skip__", False) or + getattr(testMethod, "__unittest_skip__", False)): + # If the class or method was skipped. + skip_why = (getattr(self._testcase.__class__, '__unittest_skip_why__', '') or + getattr(testMethod, '__unittest_skip_why__', '')) + try: # PY3, unittest2 on PY2 + self._testcase._addSkip(self, self._testcase, skip_why) + except TypeError: # PY2 + if sys.version_info[0] != 2: + raise + self._testcase._addSkip(self, skip_why) + return True + return False + def runtest(self): if self.config.pluginmanager.get_plugin("pdbinvoke") is None: self._testcase(result=self) else: # disables tearDown and cleanups for post mortem debugging (see #1890) - # but still implements the skipping machinery (see #2137) - testMethod = getattr(self._testcase, self._testcase._testMethodName) - if (getattr(self._testcase.__class__, "__unittest_skip__", False) or - getattr(testMethod, "__unittest_skip__", False)): - # If the class or method was skipped. - skip_why = (getattr(self._testcase.__class__, '__unittest_skip_why__', '') or - getattr(testMethod, '__unittest_skip_why__', '')) - try: - self._testcase._addSkip(self, self._testcase, skip_why) - except TypeError: # PY2 - self._testcase._addSkip(self, skip_why) + if self._handle_skip(): return self._testcase.debug() diff --git a/testing/test_pdb.py b/testing/test_pdb.py index 8a2efdb87f5..52a75d916b8 100644 --- a/testing/test_pdb.py +++ b/testing/test_pdb.py @@ -107,6 +107,7 @@ def test_false(self): self.flush(child) def test_pdb_unittest_skip(self, testdir): + """Test for issue #2137""" p1 = testdir.makepyfile(""" import unittest @unittest.skipIf(True, 'Skipping also with pdb active') From bad261279c5af15e2126023a03f08389c189b2d5 Mon Sep 17 00:00:00 2001 From: Vidar Tonaas Fauske Date: Fri, 3 Feb 2017 16:04:34 +0100 Subject: [PATCH 08/23] Do not asssume `Item.obj` in 'skipping' plugin See #2231 for discussion. --- _pytest/skipping.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/_pytest/skipping.py b/_pytest/skipping.py index 91a34169f62..591977efd13 100644 --- a/_pytest/skipping.py +++ b/_pytest/skipping.py @@ -112,7 +112,8 @@ def istrue(self): def _getglobals(self): d = {'os': os, 'sys': sys, 'config': self.item.config} - d.update(self.item.obj.__globals__) + if hasattr(self.item, 'obj'): + d.update(self.item.obj.__globals__) return d def _istrue(self): From 1a88a91c7a04ed996bee3c8957e07d9a0c735de1 Mon Sep 17 00:00:00 2001 From: Vidar Tonaas Fauske Date: Fri, 3 Feb 2017 16:29:43 +0100 Subject: [PATCH 09/23] Update authors/history --- AUTHORS | 1 + CHANGELOG.rst | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index c2400f50880..d1a4aa675d9 100644 --- a/AUTHORS +++ b/AUTHORS @@ -141,5 +141,6 @@ Trevor Bekolay Tyler Goodlet Vasily Kuznetsov Victor Uriarte +Vidar T. Fauske Wouter van Ackooy Xuecong Liao diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 612ee0f3b38..b81803324a6 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,7 +6,8 @@ * Replace ``raise StopIteration`` usages in the code by simple ``returns`` to finish generators, in accordance to `PEP-479`_ (`#2160`_). Thanks `@tgoodlet`_ for the report and `@nicoddemus`_ for the PR. -* +* Skipping plugin now also works with test items generated by custom collectors (`#2231`_). + Thanks to `@vidartf`_. * @@ -16,6 +17,10 @@ .. _PEP-479: https://www.python.org/dev/peps/pep-0479/ +.. _#2231: https://github.com/pytest-dev/pytest/issues/2231 + +.. _@vidartf: https://github.com/vidartf + 3.0.6 (2017-01-22) ================== From 832c89dd5fa7c310ae05ddcffc81025bed7a3c14 Mon Sep 17 00:00:00 2001 From: Vidar Tonaas Fauske Date: Fri, 3 Feb 2017 16:59:30 +0100 Subject: [PATCH 10/23] Test for `pytest.mark.xfail` with non-Python Item --- testing/test_skipping.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/testing/test_skipping.py b/testing/test_skipping.py index 2e7868d3ab8..ac4412fcbde 100644 --- a/testing/test_skipping.py +++ b/testing/test_skipping.py @@ -969,3 +969,26 @@ def test_func(): result.stdout.fnmatch_lines( "*Using pytest.skip outside of a test is not allowed*" ) + + +def test_mark_xfail_item(testdir): + # Ensure pytest.mark.xfail works with non-Python Item + testdir.makeconftest(""" + import pytest + + class MyItem(pytest.Item): + nodeid = 'foo' + def setup(self): + marker = pytest.mark.xfail(True, reason="Expected failure") + self.add_marker(marker) + def runtest(self): + assert False + + def pytest_collect_file(path, parent): + return MyItem("foo", parent) + """) + result = testdir.inline_run() + passed, skipped, failed = result.listoutcomes() + assert not failed + xfailed = [r for r in skipped if hasattr(r, 'wasxfail')] + assert xfailed From 87fb689ab1d05935bfd75ff29fcbc1de4161f606 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Tue, 7 Feb 2017 14:00:13 +0200 Subject: [PATCH 11/23] Remove an unneeded `except KeyboardInterrupt` KeyboardInterrupt is a subclass of BaseException, but not of Exception. Hence if we remove this except, KeyboardInterrupts will still be raised so the behavior stays the same. --- _pytest/fixtures.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/_pytest/fixtures.py b/_pytest/fixtures.py index 28bcd4d8d7e..c5bf7538648 100644 --- a/_pytest/fixtures.py +++ b/_pytest/fixtures.py @@ -124,8 +124,6 @@ def getfixturemarker(obj): exceptions.""" try: return getattr(obj, "_pytestfixturefunction", None) - except KeyboardInterrupt: - raise except Exception: # some objects raise errors like request (from flask import request) # we don't expect them to be fixture functions From 3a0a0c2df93ecfcf4fb09a0f7c24bf3a06d1bae5 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sun, 5 Feb 2017 23:55:39 +0200 Subject: [PATCH 12/23] Ignore errors raised from descriptors when collecting fixtures Descriptors (e.g. properties) such as in the added test case are triggered during collection, executing arbitrary code which can raise. Previously, such exceptions were propagated and failed the collection. Now these exceptions are caught and the corresponding attributes are silently ignored. A better solution would be to completely skip access to all custom descriptors, such that the offending code doesn't even trigger. However I think this requires manually going through the instance and all of its MRO for each and every attribute checking if it might be a proper fixture before accessing it. So I took the easy route here. In other words, putting something like this in your test class is still a bad idea...: @property def innocent(self): os.system('rm -rf /') Fixes #2234. --- AUTHORS | 1 + CHANGELOG.rst | 7 +++++++ _pytest/fixtures.py | 5 ++++- testing/python/collect.py | 10 ++++++++++ 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index fa1140d4dfe..b09516a1dd0 100644 --- a/AUTHORS +++ b/AUTHORS @@ -118,6 +118,7 @@ Piotr Banaszkiewicz Punyashloka Biswal Quentin Pradet Ralf Schmitt +Ran Benita Raphael Pierzina Raquel Alegre Roberto Polli diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d9ff2afd52b..c11200d6440 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,6 +3,9 @@ * +* Ignore exceptions raised from descriptors (e.g. properties) during Python test collection (`#2234`_). + Thanks to `@bluetech`_. + * Replace ``raise StopIteration`` usages in the code by simple ``returns`` to finish generators, in accordance to `PEP-479`_ (`#2160`_). Thanks `@tgoodlet`_ for the report and `@nicoddemus`_ for the PR. @@ -16,6 +19,10 @@ * +.. _#2234: https://github.com/pytest-dev/pytest/issues/2234 + +.. _@bluetech: https://github.com/bluetech + .. _#2160: https://github.com/pytest-dev/pytest/issues/2160 .. _PEP-479: https://www.python.org/dev/peps/pep-0479/ diff --git a/_pytest/fixtures.py b/_pytest/fixtures.py index c5bf7538648..248708f6ea9 100644 --- a/_pytest/fixtures.py +++ b/_pytest/fixtures.py @@ -14,6 +14,7 @@ getfslineno, get_real_func, is_generator, isclass, getimfunc, getlocation, getfuncargnames, + safe_getattr, ) def pytest_sessionstart(session): @@ -1066,7 +1067,9 @@ def parsefactories(self, node_or_obj, nodeid=NOTSET, unittest=False): self._holderobjseen.add(holderobj) autousenames = [] for name in dir(holderobj): - obj = getattr(holderobj, name, None) + # The attribute can be an arbitrary descriptor, so the attribute + # access below can raise. safe_getatt() ignores such exceptions. + obj = safe_getattr(holderobj, name, None) # fixture functions have a pytest_funcarg__ prefix (pre-2.3 style) # or are "@pytest.fixture" marked marker = getfixturemarker(obj) diff --git a/testing/python/collect.py b/testing/python/collect.py index 1e69f2da939..cce934ddc8d 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -166,6 +166,16 @@ def test_issue1579_namedtuple(self, testdir): "because it has a __new__ constructor*" ) + def test_issue2234_property(self, testdir): + testdir.makepyfile(""" + class TestCase(object): + @property + def prop(self): + raise NotImplementedError() + """) + result = testdir.runpytest() + assert result.ret == EXIT_NOTESTSCOLLECTED + class TestGenerator: def test_generative_functions(self, testdir): From 9eb1d7395107edbb0401eca83a69cb94dbc79308 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 8 Feb 2017 22:39:34 -0200 Subject: [PATCH 13/23] --override-ini now correctly overrides some fundamental options like "python_files" #2238 --- CHANGELOG.rst | 5 ++++- _pytest/config.py | 19 ++++++++++--------- testing/test_config.py | 15 +++++++++++++++ 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f33663b2a64..a8583d09f03 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -7,7 +7,8 @@ * Ignore exceptions raised from descriptors (e.g. properties) during Python test collection (`#2234`_). Thanks to `@bluetech`_. -* +* ``--override-ini`` now correctly overrides some fundamental options like ``python_files`` (`#2238`_). + Thanks `@sirex`_ for the report and `@nicoddemus`_ for the PR. * Replace ``raise StopIteration`` usages in the code by simple ``returns`` to finish generators, in accordance to `PEP-479`_ (`#2160`_). Thanks `@tgoodlet`_ for the report and `@nicoddemus`_ for the PR. @@ -28,12 +29,14 @@ .. _@bluetech: https://github.com/bluetech .. _@gst: https://github.com/gst +.. _@sirex: https://github.com/sirex .. _@vidartf: https://github.com/vidartf .. _#2137: https://github.com/pytest-dev/pytest/issues/2137 .. _#2160: https://github.com/pytest-dev/pytest/issues/2160 .. _#2231: https://github.com/pytest-dev/pytest/issues/2231 .. _#2234: https://github.com/pytest-dev/pytest/issues/2234 +.. _#2238: https://github.com/pytest-dev/pytest/issues/2238 .. _PEP-479: https://www.python.org/dev/peps/pep-0479/ diff --git a/_pytest/config.py b/_pytest/config.py index 42d1a118aec..c73416f0a30 100644 --- a/_pytest/config.py +++ b/_pytest/config.py @@ -877,6 +877,7 @@ def __init__(self, pluginmanager): self.trace = self.pluginmanager.trace.root.get("config") self.hook = self.pluginmanager.hook self._inicache = {} + self._override_ini = () self._opt2dest = {} self._cleanup = [] self._warn = self.pluginmanager._warn @@ -977,6 +978,7 @@ def _initini(self, args): self.invocation_dir = py.path.local() self._parser.addini('addopts', 'extra command line options', 'args') self._parser.addini('minversion', 'minimally required pytest version') + self._override_ini = ns.override_ini or () def _consider_importhook(self, args, entrypoint_name): """Install the PEP 302 import hook if using assertion re-writing. @@ -1159,15 +1161,14 @@ def _get_override_ini_value(self, name): # and -o foo1=bar1 -o foo2=bar2 options # always use the last item if multiple value set for same ini-name, # e.g. -o foo=bar1 -o foo=bar2 will set foo to bar2 - if self.getoption("override_ini", None): - for ini_config_list in self.option.override_ini: - for ini_config in ini_config_list: - try: - (key, user_ini_value) = ini_config.split("=", 1) - except ValueError: - raise UsageError("-o/--override-ini expects option=value style.") - if key == name: - value = user_ini_value + for ini_config_list in self._override_ini: + for ini_config in ini_config_list: + try: + (key, user_ini_value) = ini_config.split("=", 1) + except ValueError: + raise UsageError("-o/--override-ini expects option=value style.") + if key == name: + value = user_ini_value return value def getoption(self, name, default=notset, skip=False): diff --git a/testing/test_config.py b/testing/test_config.py index e6aa423e83a..3ce51d63934 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -778,6 +778,21 @@ def test_override_ini_usage_error_bad_style(self, testdir): result = testdir.runpytest("--override-ini", 'xdist_strict True', "-s") result.stderr.fnmatch_lines(["*ERROR* *expects option=value*"]) + @pytest.mark.parametrize('with_ini', [True, False]) + def test_override_ini_handled_asap(self, testdir, with_ini): + """-o should be handled as soon as possible and always override what's in ini files (#2238)""" + if with_ini: + testdir.makeini(""" + [pytest] + python_files=test_*.py + """) + testdir.makepyfile(unittest_ini_handle=""" + def test(): + pass + """) + result = testdir.runpytest("--override-ini", 'python_files=unittest_*.py') + result.stdout.fnmatch_lines(["*1 passed in*"]) + def test_with_arg_outside_cwd_without_inifile(self, tmpdir, monkeypatch): monkeypatch.chdir(str(tmpdir)) a = tmpdir.mkdir("a") From b536fb7ace7ab4da584b942395235e12d962ae36 Mon Sep 17 00:00:00 2001 From: Floris Bruynooghe Date: Tue, 14 Feb 2017 11:45:39 +0000 Subject: [PATCH 14/23] Mention next training event. --- doc/en/talks.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/en/talks.rst b/doc/en/talks.rst index c35fba0b06b..85faf645504 100644 --- a/doc/en/talks.rst +++ b/doc/en/talks.rst @@ -4,7 +4,9 @@ Talks and Tutorials .. sidebar:: Next Open Trainings - `pytest workshop `_, 8th December 2016, Bern, Switzerland + `Professional Testing with Python + `_, + 26-28 April 2017, Leipzig, Germany. .. _`funcargs`: funcargs.html From c4d974460ca611cfcd51d05ab742ebb166df4d2a Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 15 Feb 2017 11:57:03 -0200 Subject: [PATCH 15/23] Improve pytest_plugins docs As discussed in #2246 --- doc/en/writing_plugins.rst | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/doc/en/writing_plugins.rst b/doc/en/writing_plugins.rst index 770f81e463c..de0aa139074 100644 --- a/doc/en/writing_plugins.rst +++ b/doc/en/writing_plugins.rst @@ -236,20 +236,31 @@ import ``helper.py`` normally. The contents of Requiring/Loading plugins in a test module or conftest file ----------------------------------------------------------- -You can require plugins in a test module or a conftest file like this:: +You can require plugins in a test module or a ``conftest.py`` file like this: - pytest_plugins = "name1", "name2", +.. code-block:: python + + pytest_plugins = ["name1", "name2"] When the test module or conftest plugin is loaded the specified plugins -will be loaded as well. You can also use dotted path like this:: +will be loaded as well. Any module can be blessed as a plugin, including internal +application modules: + +.. code-block:: python pytest_plugins = "myapp.testsupport.myplugin" -which will import the specified module as a ``pytest`` plugin. +``pytest_plugins`` variables are processed recursively, so note that in the example above +if ``myapp.testsupport.myplugin`` also declares ``pytest_plugins``, the contents +of the variable will also be loaded as plugins, and so on. + +This mechanism makes it easy to share textures within applications or even +external applications without the need to create external plugins using +the ``setuptools``'s entry point technique. -Plugins imported like this will automatically be marked to require -assertion rewriting using the :func:`pytest.register_assert_rewrite` -mechanism. However for this to have any effect the module must not be +Plugins imported by ``pytest_plugins`` will also automatically be marked +for assertion rewriting (see :func:`pytest.register_assert_rewrite`). +However for this to have any effect the module must not be imported already; if it was already imported at the time the ``pytest_plugins`` statement is processed, a warning will result and assertions inside the plugin will not be re-written. To fix this you From eeb6603d71737385cea62633d4db28f9e0db269b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Wed, 15 Feb 2017 16:54:53 +0200 Subject: [PATCH 16/23] Python 3.6 invalid escape sequence deprecation fixes --- _pytest/pytester.py | 2 +- _pytest/tmpdir.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/_pytest/pytester.py b/_pytest/pytester.py index 6c63bbbbf5e..977e1f9ad77 100644 --- a/_pytest/pytester.py +++ b/_pytest/pytester.py @@ -332,7 +332,7 @@ def testdir(request, tmpdir_factory): return Testdir(request, tmpdir_factory) -rex_outcome = re.compile("(\d+) ([\w-]+)") +rex_outcome = re.compile(r"(\d+) ([\w-]+)") class RunResult: """The result of running a command. diff --git a/_pytest/tmpdir.py b/_pytest/tmpdir.py index 28a6b063663..0f878ad017a 100644 --- a/_pytest/tmpdir.py +++ b/_pytest/tmpdir.py @@ -116,7 +116,7 @@ def tmpdir(request, tmpdir_factory): path object. """ name = request.node.name - name = re.sub("[\W]", "_", name) + name = re.sub(r"[\W]", "_", name) MAXVAL = 30 if len(name) > MAXVAL: name = name[:MAXVAL] From ede4e9171f3411d71ad280280fcaa45b1e5fc371 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Wed, 15 Feb 2017 17:00:18 +0200 Subject: [PATCH 17/23] Spelling fixes --- CHANGELOG.rst | 2 +- _pytest/cacheprovider.py | 2 +- _pytest/hookspec.py | 2 +- _pytest/main.py | 2 +- _pytest/mark.py | 2 +- _pytest/pytester.py | 6 +++--- _pytest/python.py | 2 +- _pytest/unittest.py | 2 +- testing/test_assertion.py | 2 +- testing/test_assertrewrite.py | 2 +- testing/test_capture.py | 2 +- testing/test_session.py | 2 +- tox.ini | 2 +- 13 files changed, 15 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f33663b2a64..3c0d6aa40ba 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2401,7 +2401,7 @@ Bug fixes: teardown function are called earlier. - add an all-powerful metafunc.parametrize function which allows to parametrize test function arguments in multiple steps and therefore - from indepdenent plugins and palces. + from independent plugins and places. - add a @pytest.mark.parametrize helper which allows to easily call a test function with different argument values - Add examples to the "parametrize" example page, including a quick port diff --git a/_pytest/cacheprovider.py b/_pytest/cacheprovider.py index 0657001f2d4..893f0eae543 100755 --- a/_pytest/cacheprovider.py +++ b/_pytest/cacheprovider.py @@ -1,7 +1,7 @@ """ merged implementation of the cache provider -the name cache was not choosen to ensure pluggy automatically +the name cache was not chosen to ensure pluggy automatically ignores the external pytest-cache """ diff --git a/_pytest/hookspec.py b/_pytest/hookspec.py index b5f51eccf50..f01235455ec 100644 --- a/_pytest/hookspec.py +++ b/_pytest/hookspec.py @@ -255,7 +255,7 @@ def pytest_assertrepr_compare(config, op, left, right): Return None for no custom explanation, otherwise return a list of strings. The strings will be joined by newlines but any newlines *in* a string will be escaped. Note that all but the first line will - be indented sligthly, the intention is for the first line to be a summary. + be indented slightly, the intention is for the first line to be a summary. """ # ------------------------------------------------------------------------- diff --git a/_pytest/main.py b/_pytest/main.py index a3235279397..b66b661c81c 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -81,7 +81,7 @@ def pytest_namespace(): def pytest_configure(config): - pytest.config = config # compatibiltiy + pytest.config = config # compatibility def wrap_session(config, doit): diff --git a/_pytest/mark.py b/_pytest/mark.py index 357a60492e4..d406bca6b6f 100644 --- a/_pytest/mark.py +++ b/_pytest/mark.py @@ -66,7 +66,7 @@ def pytest_collection_modifyitems(items, config): return # pytest used to allow "-" for negating # but today we just allow "-" at the beginning, use "not" instead - # we probably remove "-" alltogether soon + # we probably remove "-" altogether soon if keywordexpr.startswith("-"): keywordexpr = "not " + keywordexpr[1:] selectuntil = False diff --git a/_pytest/pytester.py b/_pytest/pytester.py index 6c63bbbbf5e..25f7c5de7e0 100644 --- a/_pytest/pytester.py +++ b/_pytest/pytester.py @@ -566,7 +566,7 @@ def mkdir(self, name): def mkpydir(self, name): """Create a new python package. - This creates a (sub)direcotry with an empty ``__init__.py`` + This creates a (sub)directory with an empty ``__init__.py`` file so that is recognised as a python package. """ @@ -661,7 +661,7 @@ def inline_runsource(self, source, *cmdlineargs): def inline_genitems(self, *args): """Run ``pytest.main(['--collectonly'])`` in-process. - Retuns a tuple of the collected items and a + Returns a tuple of the collected items and a :py:class:`HookRecorder` instance. This runs the :py:func:`pytest.main` function to run all of @@ -857,7 +857,7 @@ def getmodulecol(self, source, configargs=(), withinit=False): :py:meth:`parseconfigure`. :param withinit: Whether to also write a ``__init__.py`` file - to the temporarly directory to ensure it is a package. + to the temporary directory to ensure it is a package. """ kw = {self.request.function.__name__: Source(source).strip()} diff --git a/_pytest/python.py b/_pytest/python.py index 2973d43d699..3e865e9df6b 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -629,7 +629,7 @@ def collect(self): def getcallargs(self, obj): if not isinstance(obj, (tuple, list)): obj = (obj,) - # explict naming + # explicit naming if isinstance(obj[0], py.builtin._basestring): name = obj[0] obj = obj[1:] diff --git a/_pytest/unittest.py b/_pytest/unittest.py index 34eb9885bee..276b9ba1621 100644 --- a/_pytest/unittest.py +++ b/_pytest/unittest.py @@ -5,7 +5,7 @@ import traceback import pytest -# for transfering markers +# for transferring markers import _pytest._code from _pytest.python import transfer_markers from _pytest.skipping import MarkEvaluator diff --git a/testing/test_assertion.py b/testing/test_assertion.py index 9115d25e2f6..dfc9f60fb65 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -364,7 +364,7 @@ def test_iterable_full_diff(self, left, right, expected): expl = '\n'.join(callequal(left, right, verbose=True)) assert expl.endswith(textwrap.dedent(expected).strip()) - def test_list_different_lenghts(self): + def test_list_different_lengths(self): expl = callequal([0, 1], [0, 1, 2]) assert len(expl) > 1 expl = callequal([0, 1, 2], [0, 1]) diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index fdf674f25cc..7cc58e8a8fd 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -271,7 +271,7 @@ def f(): getmsg(f, must_pass=True) - def test_short_circut_evaluation(self): + def test_short_circuit_evaluation(self): def f(): assert True or explode # noqa diff --git a/testing/test_capture.py b/testing/test_capture.py index cbb5fc81bb7..763e28315df 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -604,7 +604,7 @@ def test_foo(): def test_error_during_readouterr(testdir): - """Make sure we suspend capturing if errors occurr during readouterr""" + """Make sure we suspend capturing if errors occur during readouterr""" testdir.makepyfile(pytest_xyz=""" from _pytest.capture import FDCapture def bad_snap(self): diff --git a/testing/test_session.py b/testing/test_session.py index a7dcb27a4e1..f494dbc1140 100644 --- a/testing/test_session.py +++ b/testing/test_session.py @@ -197,7 +197,7 @@ def test_minus_x_import_error(self, testdir): colfail = [x for x in finished if x.failed] assert len(colfail) == 1 - def test_minus_x_overriden_by_maxfail(self, testdir): + def test_minus_x_overridden_by_maxfail(self, testdir): testdir.makepyfile(__init__="") testdir.makepyfile(test_one="xxxx", test_two="yyyy", test_third="zzz") reprec = testdir.inline_run("-x", "--maxfail=2", testdir.tmpdir) diff --git a/tox.ini b/tox.ini index e57fabd4b45..1b9fb9f5a08 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,7 @@ [tox] minversion=2.0 distshare={homedir}/.tox/distshare -# make sure to update enviroment list on appveyor.yml +# make sure to update environment list on appveyor.yml envlist= linting py26 From 8f98ac5ae8774e51ac6336faa43d465fe013e747 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Wed, 15 Feb 2017 13:15:53 -0200 Subject: [PATCH 18/23] Fix typo in docs "textures" -> "fixtures" --- doc/en/writing_plugins.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/en/writing_plugins.rst b/doc/en/writing_plugins.rst index de0aa139074..f6ed6e4e3aa 100644 --- a/doc/en/writing_plugins.rst +++ b/doc/en/writing_plugins.rst @@ -254,7 +254,7 @@ application modules: if ``myapp.testsupport.myplugin`` also declares ``pytest_plugins``, the contents of the variable will also be loaded as plugins, and so on. -This mechanism makes it easy to share textures within applications or even +This mechanism makes it easy to share fixtures within applications or even external applications without the need to create external plugins using the ``setuptools``'s entry point technique. From 58d7f4e0487bb4049c0472f1019a6117c6f25af4 Mon Sep 17 00:00:00 2001 From: Victor Uriarte Date: Thu, 16 Feb 2017 22:52:06 -0700 Subject: [PATCH 19/23] Correct typo --- _pytest/hookspec.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_pytest/hookspec.py b/_pytest/hookspec.py index f01235455ec..ea1f0443df9 100644 --- a/_pytest/hookspec.py +++ b/_pytest/hookspec.py @@ -246,7 +246,7 @@ def pytest_unconfigure(config): # ------------------------------------------------------------------------- -# hooks for customising the assert methods +# hooks for customizing the assert methods # ------------------------------------------------------------------------- def pytest_assertrepr_compare(config, op, left, right): From a88017cf262b7d1b8d62d756232db1b5aa81542c Mon Sep 17 00:00:00 2001 From: Victor Uriarte Date: Thu, 16 Feb 2017 22:53:51 -0700 Subject: [PATCH 20/23] Add note documenting #2257 --- _pytest/hookspec.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/_pytest/hookspec.py b/_pytest/hookspec.py index ea1f0443df9..552a06575cd 100644 --- a/_pytest/hookspec.py +++ b/_pytest/hookspec.py @@ -263,7 +263,14 @@ def pytest_assertrepr_compare(config, op, left, right): # ------------------------------------------------------------------------- def pytest_report_header(config, startdir): - """ return a string to be displayed as header info for terminal reporting.""" + """ return a string to be displayed as header info for terminal reporting. + + .. note:: + + This function should be implemented only in plugins or ``conftest.py`` + files situated at the tests root directory due to how pytest + :ref:`discovers plugins during startup `. + """ @hookspec(firstresult=True) def pytest_report_teststatus(report): From 5fd010c4c3f53d69bcef396989e52a6cbfa59c4e Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Sun, 19 Feb 2017 09:02:35 -0800 Subject: [PATCH 21/23] Simplify travis.yml with tox environment variables --- .travis.yml | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/.travis.yml b/.travis.yml index f701302d9e3..1dedddfb451 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,34 +8,34 @@ install: "pip install -U tox" env: matrix: # coveralls is not listed in tox's envlist, but should run in travis - - TESTENV=coveralls + - TOXENV=coveralls # note: please use "tox --listenvs" to populate the build matrix below - - TESTENV=linting - - TESTENV=py26 - - TESTENV=py27 - - TESTENV=py33 - - TESTENV=py34 - - TESTENV=py35 - - TESTENV=pypy - - TESTENV=py27-pexpect - - TESTENV=py27-xdist - - TESTENV=py27-trial - - TESTENV=py35-pexpect - - TESTENV=py35-xdist - - TESTENV=py35-trial - - TESTENV=py27-nobyte - - TESTENV=doctesting - - TESTENV=freeze - - TESTENV=docs + - TOXENV=linting + - TOXENV=py26 + - TOXENV=py27 + - TOXENV=py33 + - TOXENV=py34 + - TOXENV=py35 + - TOXENV=pypy + - TOXENV=py27-pexpect + - TOXENV=py27-xdist + - TOXENV=py27-trial + - TOXENV=py35-pexpect + - TOXENV=py35-xdist + - TOXENV=py35-trial + - TOXENV=py27-nobyte + - TOXENV=doctesting + - TOXENV=freeze + - TOXENV=docs matrix: include: - - env: TESTENV=py36 + - env: TOXENV=py36 python: '3.6-dev' - - env: TESTENV=py37 + - env: TOXENV=py37 python: 'nightly' -script: tox --recreate -e $TESTENV +script: tox --recreate notifications: irc: From d3a6be413014b112227cbdb489c62483ec4d9192 Mon Sep 17 00:00:00 2001 From: Katerina Koukiou Date: Wed, 22 Feb 2017 14:17:45 +0100 Subject: [PATCH 22/23] junitxml: Fix double system-out tags per testcase In the xml report we now have two occurences for the system-out tag if the testcase writes to stdout both on call and teardown and fails in teardown. This behaviour is against the xsd. This patch makes sure that the system-out section exists only once per testcase. --- CHANGELOG.rst | 4 ++++ _pytest/junitxml.py | 9 ++++----- testing/test_junitxml.py | 19 +++++++++++++++++++ 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a0706243787..d309125d91a 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,9 @@ 3.0.7 (unreleased) ================== +* junitxml: Fix problematic case where system-out tag occured twice per testcase + element in the XML report. Thanks `@kkoukiou`_ for the PR. + * Fix regression, pytest now skips unittest correctly if run with ``--pdb`` (`#2137`_). Thanks to `@gst`_ for the report and `@mbyt`_ for the PR. @@ -31,6 +34,7 @@ .. _@gst: https://github.com/gst .. _@sirex: https://github.com/sirex .. _@vidartf: https://github.com/vidartf +.. _@kkoukiou: https://github.com/KKoukiou .. _#2137: https://github.com/pytest-dev/pytest/issues/2137 .. _#2160: https://github.com/pytest-dev/pytest/issues/2160 diff --git a/_pytest/junitxml.py b/_pytest/junitxml.py index 317382e6378..3f371c9d37a 100644 --- a/_pytest/junitxml.py +++ b/_pytest/junitxml.py @@ -119,7 +119,7 @@ def _add_simple(self, kind, message, data=None): node = kind(data, message=message) self.append(node) - def _write_captured_output(self, report): + def write_captured_output(self, report): for capname in ('out', 'err'): content = getattr(report, 'capstd' + capname) if content: @@ -128,7 +128,6 @@ def _write_captured_output(self, report): def append_pass(self, report): self.add_stats('passed') - self._write_captured_output(report) def append_failure(self, report): # msg = str(report.longrepr.reprtraceback.extraline) @@ -147,7 +146,6 @@ def append_failure(self, report): fail = Junit.failure(message=message) fail.append(bin_xml_escape(report.longrepr)) self.append(fail) - self._write_captured_output(report) def append_collect_error(self, report): # msg = str(report.longrepr.reprtraceback.extraline) @@ -165,7 +163,6 @@ def append_error(self, report): msg = "test setup failure" self._add_simple( Junit.error, msg, report.longrepr) - self._write_captured_output(report) def append_skipped(self, report): if hasattr(report, "wasxfail"): @@ -180,7 +177,7 @@ def append_skipped(self, report): Junit.skipped("%s:%s: %s" % (filename, lineno, skipreason), type="pytest.skip", message=skipreason)) - self._write_captured_output(report) + self.write_captured_output(report) def finalize(self): data = self.to_xml().unicode(indent=0) @@ -345,6 +342,8 @@ def pytest_runtest_logreport(self, report): reporter.append_skipped(report) self.update_testcase_duration(report) if report.when == "teardown": + reporter = self._opentestcase(report) + reporter.write_captured_output(report) self.finalize(report) def update_testcase_duration(self, report): diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index abbc9cd3325..d167f735d2c 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -557,6 +557,25 @@ def test_function(arg): systemout = pnode.find_first_by_tag("system-err") assert "hello-stderr" in systemout.toxml() + def test_avoid_double_stdout(self, testdir): + testdir.makepyfile(""" + import sys + import pytest + + @pytest.fixture + def arg(request): + yield + sys.stdout.write('hello-stdout teardown') + raise ValueError() + def test_function(arg): + sys.stdout.write('hello-stdout call') + """) + result, dom = runandparse(testdir) + node = dom.find_first_by_tag("testsuite") + pnode = node.find_first_by_tag("testcase") + systemout = pnode.find_first_by_tag("system-out") + assert "hello-stdout call" in systemout.toxml() + assert "hello-stdout teardown" in systemout.toxml() def test_mangle_test_address(): from _pytest.junitxml import mangle_test_address From 9e23f1dda9f957e57cc923d96091e451e777572e Mon Sep 17 00:00:00 2001 From: Dmitri Pribysh Date: Wed, 22 Feb 2017 22:41:48 +0300 Subject: [PATCH 23/23] Add '--junit-suite-name' option for JUnit XML reports Update Changelog Update AUTHORS --- AUTHORS | 1 + CHANGELOG.rst | 2 ++ _pytest/junitxml.py | 13 ++++++++++--- testing/test_junitxml.py | 27 +++++++++++++++++++++++++++ 4 files changed, 40 insertions(+), 3 deletions(-) diff --git a/AUTHORS b/AUTHORS index b09516a1dd0..69e26787797 100644 --- a/AUTHORS +++ b/AUTHORS @@ -146,3 +146,4 @@ Victor Uriarte Vidar T. Fauske Wouter van Ackooy Xuecong Liao +Dmitry Pribysh diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d309125d91a..0154c531b9f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,8 @@ 3.0.7 (unreleased) ================== +* junitxml: Add `--junit-suite-name` option to specify root `` name for JUnit XML reports + * junitxml: Fix problematic case where system-out tag occured twice per testcase element in the XML report. Thanks `@kkoukiou`_ for the PR. diff --git a/_pytest/junitxml.py b/_pytest/junitxml.py index 3f371c9d37a..d7f31e8c275 100644 --- a/_pytest/junitxml.py +++ b/_pytest/junitxml.py @@ -222,13 +222,19 @@ def pytest_addoption(parser): metavar="str", default=None, help="prepend prefix to classnames in junit-xml output") + group.addoption( + '--junitsuitename', '--junit-suite-name', + action="store", + metavar="name", + default="pytest", + help="set the name attribute of root tag") def pytest_configure(config): xmlpath = config.option.xmlpath # prevent opening xmllog on slave nodes (xdist) if xmlpath and not hasattr(config, 'slaveinput'): - config._xml = LogXML(xmlpath, config.option.junitprefix) + config._xml = LogXML(xmlpath, config.option.junitprefix, config.option.junitsuitename) config.pluginmanager.register(config._xml) @@ -255,10 +261,11 @@ def mangle_test_address(address): class LogXML(object): - def __init__(self, logfile, prefix): + def __init__(self, logfile, prefix, suite_name="pytest"): logfile = os.path.expanduser(os.path.expandvars(logfile)) self.logfile = os.path.normpath(os.path.abspath(logfile)) self.prefix = prefix + self.suite_name = suite_name self.stats = dict.fromkeys([ 'error', 'passed', @@ -384,7 +391,7 @@ def pytest_sessionfinish(self): logfile.write(Junit.testsuite( self._get_global_properties_node(), [x.to_xml() for x in self.node_reporters_ordered], - name="pytest", + name=self.suite_name, errors=self.stats['error'], failures=self.stats['failure'], skips=self.stats['skipped'], diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index d167f735d2c..8051b667a93 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -594,6 +594,7 @@ def __init__(self): self.option = self junitprefix = None + junitsuitename = "pytest" # XXX: shouldnt need tmpdir ? xmlpath = str(tmpdir.join('junix.xml')) register = gotten.append @@ -981,3 +982,29 @@ class Report(BaseReport): actual[k] = v assert actual == expected + + +def test_set_suite_name(testdir): + testdir.makepyfile(""" + import pytest + + def test_func(): + pass + """) + result, dom = runandparse(testdir, '--junit-suite-name', "my_suite") + assert result.ret == 0 + node = dom.find_first_by_tag("testsuite") + node.assert_attr(name="my_suite") + + +def test_set_suite_name_default(testdir): + testdir.makepyfile(""" + import pytest + + def test_func(): + pass + """) + result, dom = runandparse(testdir) + assert result.ret == 0 + node = dom.find_first_by_tag("testsuite") + node.assert_attr(name="pytest")