diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index d09edce43..198780a2a 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -2,7 +2,10 @@ Thanks for submitting a PR, your contribution is really appreciated! Here's a quick checklist that should be present in PRs: -- [ ] Target: for bug or doc fixes, target `master`; for new features, target `features` -- [ ] Make sure to include one or more tests for your change -- [ ] Add yourself to `AUTHORS` -- [ ] Add a new entry to the `CHANGELOG` (choose any open position to avoid merge conflicts with other PRs) +- [ ] Target: for bug or doc fixes, target `master`; for new features, target `features`; +- [ ] Make sure to include one or more tests for your change; +- [ ] Add yourself to `AUTHORS`; +- [ ] Add a new entry to `CHANGELOG.rst` + * Choose any open position to avoid merge conflicts with other PRs. + * Add a link to the issue you are fixing (if any) using RST syntax. + * The pytest team likes to have people to acknowledged in the `CHANGELOG`, so please add a thank note to yourself ("Thanks @user for the PR") and a link to your GitHub profile. It may sound weird thanking yourself, but otherwise a maintainer would have to do it manually before or after merging instead of just using GitHub's merge button. This makes it easier on the maintainers to merge PRs. diff --git a/AUTHORS b/AUTHORS index 5398cd1df..9102aa87f 100644 --- a/AUTHORS +++ b/AUTHORS @@ -8,8 +8,8 @@ Abhijeet Kasurde Alexei Kozlenok Anatoly Bubenkoff Andreas Zeidler -Andy Freeland Andrzej Ostrowski +Andy Freeland Anthon van der Neut Antony Lee Armin Rigo @@ -17,6 +17,7 @@ Aron Curzon Aviv Palivoda Ben Webb Benjamin Peterson +Bernard Pratz Bob Ippolito Brian Dorsey Brian Okken @@ -38,7 +39,10 @@ Dave Hunt David Díaz-Barquero David Mohr David Vierra +Diego Russo +Dmitry Dygalo Edison Gustavo Muenz +Edoardo Batini Eduardo Schettino Elizaveta Shashkova Endre Galaczi @@ -62,12 +66,14 @@ Jan Balster Janne Vanhala Jason R. Coombs Javier Domingo Cansino +Javier Romero John Towler +Jon Sonesen Joshua Bronson Jurko Gospodnetić Justyna Janczyszyn -Katarzyna Jachim Kale Kundert +Katarzyna Jachim Kevin Cox Lee Kamentsky Lukas Bednar @@ -77,8 +83,8 @@ Marc Schlaich Mark Abramowitz Markus Unterwaditzer Martijn Faassen -Martin Prusse Martin K. Scherer +Martin Prusse Matt Bachmann Matt Williams Michael Aquilina @@ -103,17 +109,17 @@ Ross Lawley Russel Winder Ryan Wooden Samuele Pedroni +Simon Gomizelj +Stefan Farmbauer +Stefan Zimmermann +Stefano Taschini Steffen Allner Stephan Obermann Tareq Alayan Ted Xiao -Simon Gomizelj -Stefano Taschini -Stefan Farmbauer Thomas Grainger Tom Viner Trevor Bekolay Vasily Kuznetsov Wouter van Ackooy -Bernard Pratz -Stefan Zimmermann + diff --git a/CHANGELOG.rst b/CHANGELOG.rst index abe528f7f..d4e5dfa60 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -229,6 +229,12 @@ time or change existing behaviors in order to make them less surprising/more use removed in pytest-4.0 (`#1684`_). Thanks `@nicoddemus`_ for the PR. +* Raise helpful failure message, when requesting parametrized fixture at runtime, + e.g. with ``request.getfuncargvalue``. BACKWARD INCOMPAT: Previously these params + were simply never defined. So a fixture decorated like ``@pytest.fixture(params=[0, 1, 2])`` + only ran once. Now a failure is raised. Fixes (`#460`_). Thanks to + `@nikratio`_ for bug report, `@RedBeardCode`_ and `@tomviner`_ for the PR. + * Passing a command-line string to ``pytest.main()`` is considered deprecated and scheduled for removal in pytest-4.0. It is recommended to pass a list of arguments instead (`#1723`_). @@ -238,12 +244,19 @@ time or change existing behaviors in order to make them less surprising/more use * ``optparse`` type usage now triggers DeprecationWarnings (`#1740`_). + * ``optparse`` backward compatibility supports float/complex types (`#457`_). +* Renamed the pytest ``pdb`` module (plugin) into ``debugging``. + * Better message in case of not using parametrized variable (see `#1539`_). Thanks to `@tramwaj29`_ for the PR. -* +* Updated docstrings with a more uniform style. + +* Add stderr write for ``pytest.exit(msg)`` during startup. Previously the message was never shown. + Thanks `@BeyondEvil`_ for reporting `#1210`_. Thanks to `@JonathonSonesen`_ and + `@tomviner`_ for PR. * @@ -271,7 +284,18 @@ time or change existing behaviors in order to make them less surprising/more use identify bugs in ``conftest.py`` files (`#1516`_). Thanks `@txomon`_ for the PR. -* +* 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. + +* Text documents without any doctests no longer appear as "skipped". + Thanks `@graingert`_ for reporting and providing a full PR (`#1580`_). + +* Fixed collection of classes with custom ``__new__`` method. + Fixes `#1579`_. Thanks to `@Stranger6667`_ for the PR. + +* Fixed scope overriding inside metafunc.parametrize (`#634`_). + Thanks to `@Stranger6667`_ for the PR. * @@ -281,12 +305,7 @@ time or change existing behaviors in order to make them less surprising/more use * -.. _#372: https://github.com/pytest-dev/pytest/issues/372 -.. _#460: https://github.com/pytest-dev/pytest/pull/460 -.. _#457: https://github.com/pytest-dev/pytest/issues/457 -.. _#1740: https://github.com/pytest-dev/pytest/issues/1740 -.. _#607: https://github.com/pytest-dev/pytest/issues/607 -.. _#925: https://github.com/pytest-dev/pytest/issues/925 +.. _#1210: https://github.com/pytest-dev/pytest/issues/1210 .. _#1235: https://github.com/pytest-dev/pytest/issues/1235 .. _#1351: https://github.com/pytest-dev/pytest/issues/1351 .. _#1421: https://github.com/pytest-dev/pytest/issues/1421 @@ -305,9 +324,11 @@ time or change existing behaviors in order to make them less surprising/more use .. _#1519: https://github.com/pytest-dev/pytest/pull/1519 .. _#1520: https://github.com/pytest-dev/pytest/pull/1520 .. _#1526: https://github.com/pytest-dev/pytest/pull/1526 +.. _#1539: https://github.com/pytest-dev/pytest/issues/1539 .. _#1544: https://github.com/pytest-dev/pytest/issues/1544 .. _#1553: https://github.com/pytest-dev/pytest/issues/1553 .. _#1562: https://github.com/pytest-dev/pytest/issues/1562 +.. _#1579: https://github.com/pytest-dev/pytest/issues/1579 .. _#1580: https://github.com/pytest-dev/pytest/pull/1580 .. _#1597: https://github.com/pytest-dev/pytest/pull/1597 .. _#1605: https://github.com/pytest-dev/pytest/issues/1605 @@ -315,32 +336,40 @@ time or change existing behaviors in order to make them less surprising/more use .. _#1618: https://github.com/pytest-dev/pytest/issues/1618 .. _#1619: https://github.com/pytest-dev/pytest/issues/1619 .. _#1626: https://github.com/pytest-dev/pytest/pull/1626 -.. _#1668: https://github.com/pytest-dev/pytest/issues/1668 .. _#1627: https://github.com/pytest-dev/pytest/pull/1627 .. _#1628: https://github.com/pytest-dev/pytest/pull/1628 .. _#1629: https://github.com/pytest-dev/pytest/issues/1629 .. _#1632: https://github.com/pytest-dev/pytest/issues/1632 .. _#1633: https://github.com/pytest-dev/pytest/pull/1633 .. _#1664: https://github.com/pytest-dev/pytest/pull/1664 +.. _#1668: https://github.com/pytest-dev/pytest/issues/1668 .. _#1684: https://github.com/pytest-dev/pytest/pull/1684 .. _#1723: https://github.com/pytest-dev/pytest/pull/1723 -.. _#1539: https://github.com/pytest-dev/pytest/issues/1539 +.. _#1740: https://github.com/pytest-dev/pytest/issues/1740 .. _#1749: https://github.com/pytest-dev/pytest/issues/1749 +.. _#372: https://github.com/pytest-dev/pytest/issues/372 +.. _#457: https://github.com/pytest-dev/pytest/issues/457 +.. _#460: https://github.com/pytest-dev/pytest/pull/460 +.. _#607: https://github.com/pytest-dev/pytest/issues/607 +.. _#634: https://github.com/pytest-dev/pytest/issues/634 +.. _#717: https://github.com/pytest-dev/pytest/issues/717 +.. _#925: https://github.com/pytest-dev/pytest/issues/925 -.. _@DRMacIver: https://github.com/DRMacIver -.. _@RedBeardCode: https://github.com/RedBeardCode -.. _@Vogtinator: https://github.com/Vogtinator .. _@anntzer: https://github.com/anntzer .. _@bagerard: https://github.com/bagerard +.. _@BeyondEvil: https://github.com/BeyondEvil .. _@blueyed: https://github.com/blueyed .. _@ceridwen: https://github.com/ceridwen .. _@csaftoiu: https://github.com/csaftoiu .. _@d6e: https://github.com/d6e .. _@davehunt: https://github.com/davehunt +.. _@DRMacIver: https://github.com/DRMacIver +.. _@eolo999: https://github.com/eolo999 .. _@fengxx: https://github.com/fengxx .. _@flub: https://github.com/flub .. _@graingert: https://github.com/graingert .. _@hartym: https://github.com/hartym +.. _@JonathonSonesen: https://github.com/JonathonSonesen .. _@kalekundert: https://github.com/kalekundert .. _@kvas-it: https://github.com/kvas-it .. _@marscher: https://github.com/marscher @@ -353,12 +382,16 @@ time or change existing behaviors in order to make them less surprising/more use .. _@olegpidsadnyi: https://github.com/olegpidsadnyi .. _@omarkohl: https://github.com/omarkohl .. _@palaviv: https://github.com/palaviv +.. _@RedBeardCode: https://github.com/RedBeardCode .. _@sallner: https://github.com/sallner .. _@sober7: https://github.com/sober7 +.. _@Stranger6667: https://github.com/Stranger6667 .. _@tareqalayan: https://github.com/tareqalayan .. _@taschini: https://github.com/taschini -.. _@txomon: https://github.com/txomon .. _@tramwaj29: https://github.com/tramwaj29 +.. _@txomon: https://github.com/txomon +.. _@Vogtinator: https://github.com/Vogtinator + 2.9.2 ===== @@ -392,8 +425,8 @@ time or change existing behaviors in order to make them less surprising/more use .. _#510: https://github.com/pytest-dev/pytest/issues/510 .. _#1506: https://github.com/pytest-dev/pytest/pull/1506 -.. _#1496: https://github.com/pytest-dev/pytest/issue/1496 -.. _#1524: https://github.com/pytest-dev/pytest/issue/1524 +.. _#1496: https://github.com/pytest-dev/pytest/issues/1496 +.. _#1524: https://github.com/pytest-dev/pytest/pull/1524 .. _@prusse-martin: https://github.com/prusse-martin .. _@astraw38: https://github.com/astraw38 diff --git a/_pytest/capture.py b/_pytest/capture.py index f97cf6256..d58c72909 100644 --- a/_pytest/capture.py +++ b/_pytest/capture.py @@ -157,7 +157,7 @@ error_capsysfderror = "cannot use capsys and capfd at the same time" @pytest.fixture def capsys(request): - """enables capturing of writes to sys.stdout/sys.stderr and makes + """Enable capturing of writes to sys.stdout/sys.stderr and make captured output available via ``capsys.readouterr()`` method calls which return a ``(out, err)`` tuple. """ @@ -168,7 +168,7 @@ def capsys(request): @pytest.fixture def capfd(request): - """enables capturing of writes to file descriptors 1 and 2 and makes + """Enable capturing of writes to file descriptors 1 and 2 and make captured output available via ``capfd.readouterr()`` method calls which return a ``(out, err)`` tuple. """ diff --git a/_pytest/fixtures.py b/_pytest/fixtures.py index a42ddf627..90845df5c 100644 --- a/_pytest/fixtures.py +++ b/_pytest/fixtures.py @@ -673,8 +673,13 @@ class FixtureLookupErrorRepr(TerminalRepr): #tw.line("FixtureLookupError: %s" %(self.argname), red=True) for tbline in self.tblines: tw.line(tbline.rstrip()) - for line in self.errorstring.split("\n"): - tw.line(" " + line.strip(), red=True) + lines = self.errorstring.split("\n") + for line in lines: + if line == lines[0]: + prefix = 'E ' + else: + prefix = ' ' + tw.line(prefix + line.strip(), red=True) tw.line() tw.line("%s:%d" % (self.filename, self.firstlineno+1)) diff --git a/_pytest/junitxml.py b/_pytest/junitxml.py index 6ff5e4249..704e62a1b 100644 --- a/_pytest/junitxml.py +++ b/_pytest/junitxml.py @@ -186,8 +186,8 @@ class _NodeReporter(object): @pytest.fixture def record_xml_property(request): - """Fixture that adds extra xml properties to the tag for the calling test. - The fixture is callable with (name, value), with value being automatically + """Add extra xml properties to the tag for the calling test. + The fixture is callable with ``(name, value)``, with value being automatically xml-encoded. """ request.node.warn( diff --git a/_pytest/main.py b/_pytest/main.py index 9a4384cce..6140f2885 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -98,6 +98,10 @@ def wrap_session(config, doit): raise except KeyboardInterrupt: excinfo = _pytest._code.ExceptionInfo() + if initstate < 2 and isinstance( + excinfo.value, pytest.exit.Exception): + sys.stderr.write('{0}: {1}\n'.format( + excinfo.typename, excinfo.value.msg)) config.hook.pytest_keyboard_interrupt(excinfo=excinfo) session.exitstatus = EXIT_INTERRUPTED except: diff --git a/_pytest/monkeypatch.py b/_pytest/monkeypatch.py index 258de385f..3c61d6f5b 100644 --- a/_pytest/monkeypatch.py +++ b/_pytest/monkeypatch.py @@ -226,10 +226,10 @@ class MonkeyPatch: """ Undo previous changes. This call consumes the undo stack. Calling it a second time has no effect unless you do more monkeypatching after the undo call. - + There is generally no need to call `undo()`, since it is called automatically during tear-down. - + Note that the same `monkeypatch` fixture is used across a single test function invocation. If `monkeypatch` is used both by the test function itself and one of the test fixtures, diff --git a/_pytest/python.py b/_pytest/python.py index 8db0d9960..d099c6279 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -489,6 +489,10 @@ class Class(PyCollector): self.warn("C1", "cannot collect test class %r because it has a " "__init__ constructor" % self.obj.__name__) return [] + elif hasnew(self.obj): + self.warn("C1", "cannot collect test class %r because it has a " + "__new__ constructor" % self.obj.__name__) + return [] return [self._getcustomclass("Instance")(name="()", parent=self)] def setup(self): @@ -506,8 +510,7 @@ class Class(PyCollector): class Instance(PyCollector): def _getobj(self): - obj = self.parent.obj() - return obj + return self.parent.obj() def collect(self): self.session._fixturemanager.parsefactories(self) @@ -622,8 +625,13 @@ class Generator(FunctionMixin, PyCollector): def hasinit(obj): init = getattr(obj, '__init__', None) if init: - if init != object.__init__: - return True + return init != object.__init__ + + +def hasnew(obj): + new = getattr(obj, '__new__', None) + if new: + return new != object.__new__ class CallSpec2(object): @@ -763,7 +771,7 @@ class Metafunc(fixtures.FuncargnamesCompatAttr): It will also override any fixture-function defined scope, allowing to set a dynamic scope using test context or configuration. """ - + from _pytest.fixtures import scopes # individual parametrized argument sets can be wrapped in a series # of markers in which case we unwrap the values and apply the mark # at Function init @@ -794,10 +802,16 @@ class Metafunc(fixtures.FuncargnamesCompatAttr): newmarks = newkeywords.setdefault(0, {}) newmarks[newmark.markname] = newmark - if scope is None: - scope = "function" - scopenum = fixtures.scopes.index(scope) + if self._arg2fixturedefs: + # Takes the most narrow scope from used fixtures + fixtures_scopes = [fixturedef[0].scope for fixturedef in self._arg2fixturedefs.values()] + for scope in reversed(scopes): + if scope in fixtures_scopes: + break + else: + scope = 'function' + scopenum = scopes.index(scope) valtypes = {} for arg in argnames: if arg not in self.fixturenames: diff --git a/_pytest/tmpdir.py b/_pytest/tmpdir.py index 88e7250db..5c0802508 100644 --- a/_pytest/tmpdir.py +++ b/_pytest/tmpdir.py @@ -108,7 +108,7 @@ def tmpdir_factory(request): @pytest.fixture def tmpdir(request, tmpdir_factory): - """return a temporary directory path object + """Return a temporary directory path object which is unique to each test function invocation, created as a sub directory of the base temporary directory. The returned object is a `py.path.local`_ diff --git a/doc/en/contents.rst b/doc/en/contents.rst index b007e7de8..897f04672 100644 --- a/doc/en/contents.rst +++ b/doc/en/contents.rst @@ -19,6 +19,7 @@ Full pytest documentation recwarn cache plugins + nose backwards-compatibility contributing diff --git a/doc/en/nose.rst b/doc/en/nose.rst index ffad1f4d3..987ef9314 100644 --- a/doc/en/nose.rst +++ b/doc/en/nose.rst @@ -24,7 +24,7 @@ Supported nose Idioms * setup and teardown at module/class/method level * SkipTest exceptions and markers * setup/teardown decorators -* yield-based tests and their setup +* ``yield``-based tests and their setup * ``__test__`` attribute on modules/classes/functions * general usage of nose utilities @@ -51,5 +51,12 @@ Unsupported idioms / known issues - nose-style doctests are not collected and executed correctly, also doctest fixtures don't work. -- no nose-configuration is recognized +- no nose-configuration is recognized. + +- ``yield``-based methods don't support ``setup`` properly because + the ``setup`` method is always called in the same class instance. + There are no plans to fix this currently because ``yield``-tests + are deprecated in pytest 3.0, with ``pytest.mark.parametrize`` + being the recommended alternative. + diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index 2e495e246..189bdc712 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -377,7 +377,7 @@ class TestGeneralUsage: res = testdir.runpytest(p) res.stdout.fnmatch_lines([ "*source code not available*", - "*fixture 'invalid_fixture' not found", + "E*fixture 'invalid_fixture' not found", ]) def test_plugins_given_as_strings(self, tmpdir, monkeypatch): diff --git a/testing/python/collect.py b/testing/python/collect.py index fb2264f0a..692854ce6 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -121,6 +121,18 @@ class TestClass: colitems = modcol.collect() assert len(colitems) == 0 + def test_issue1579_namedtuple(self, testdir): + testdir.makepyfile(""" + import collections + + TestCase = collections.namedtuple('TestCase', ['a']) + """) + result = testdir.runpytest('-rw') + result.stdout.fnmatch_lines( + "*cannot collect test class 'TestCase' " + "because it has a __new__ constructor*" + ) + class TestGenerator: def test_generative_functions(self, testdir): diff --git a/testing/python/fixture.py b/testing/python/fixture.py index 8870d76a7..a16f552c9 100644 --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -351,6 +351,38 @@ class TestFillFixtures: result = testdir.runpytest(testfile) result.stdout.fnmatch_lines(["*3 passed*"]) + def test_override_autouse_fixture_with_parametrized_fixture_conftest_conftest(self, testdir): + """Test override of the autouse fixture with parametrized one on the conftest level. + This test covers the issue explained in issue 1601 + """ + testdir.makeconftest(""" + import pytest + + @pytest.fixture(autouse=True) + def spam(): + return 'spam' + """) + subdir = testdir.mkpydir('subdir') + subdir.join("conftest.py").write(_pytest._code.Source(""" + import pytest + + @pytest.fixture(params=[1, 2, 3]) + def spam(request): + return request.param + """)) + testfile = subdir.join("test_spam.py") + testfile.write(_pytest._code.Source(""" + params = {'spam': 1} + + def test_spam(spam): + assert spam == params['spam'] + params['spam'] += 1 + """)) + result = testdir.runpytest() + result.stdout.fnmatch_lines(["*3 passed*"]) + result = testdir.runpytest(testfile) + result.stdout.fnmatch_lines(["*3 passed*"]) + def test_autouse_fixture_plugin(self, testdir): # A fixture from a plugin has no baseid set, which screwed up # the autouse fixture handling. diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index 98864ea02..e8efd67c4 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -930,6 +930,43 @@ class TestMetafuncFunctional: reprec = testdir.inline_run() reprec.assertoutcome(passed=5) + def test_parametrize_issue634(self, testdir): + testdir.makepyfile(''' + import pytest + + @pytest.fixture(scope='module') + def foo(request): + print('preparing foo-%d' % request.param) + return 'foo-%d' % request.param + + + def test_one(foo): + pass + + + def test_two(foo): + pass + + + test_two.test_with = (2, 3) + + + def pytest_generate_tests(metafunc): + params = (1, 2, 3, 4) + if not 'foo' in metafunc.fixturenames: + return + + test_with = getattr(metafunc.function, 'test_with', None) + if test_with: + params = test_with + metafunc.parametrize('foo', params, indirect=True) + + ''') + result = testdir.runpytest("-s") + output = result.stdout.str() + assert output.count('preparing foo-2') == 1 + assert output.count('preparing foo-3') == 1 + def test_parametrize_issue323(self, testdir): testdir.makepyfile(""" import pytest diff --git a/testing/test_capture.py b/testing/test_capture.py index c197c85e7..b2e5be0be 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -416,9 +416,9 @@ class TestCaptureFixture: result = testdir.runpytest(p) result.stdout.fnmatch_lines([ "*ERROR*setup*test_one*", - "*capsys*capfd*same*time*", + "E*capsys*capfd*same*time*", "*ERROR*setup*test_two*", - "*capsys*capfd*same*time*", + "E*capsys*capfd*same*time*", "*2 error*"]) def test_capturing_getfixturevalue(self, testdir): diff --git a/testing/test_runner.py b/testing/test_runner.py index bc3ff6c89..49379e439 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -448,6 +448,18 @@ def test_pytest_fail(): s = excinfo.exconly(tryshort=True) assert s.startswith("Failed") +def test_pytest_exit_msg(testdir): + testdir.makeconftest(""" + import pytest + + def pytest_configure(config): + pytest.exit('oh noes') + """) + result = testdir.runpytest() + result.stderr.fnmatch_lines([ + "Exit: oh noes", + ]) + def test_pytest_fail_notrace(testdir): testdir.makepyfile(""" import pytest