diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 1896f550b..f2d86b24f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -8,6 +8,58 @@ .. towncrier release notes start +Pytest 3.5.1 (2018-04-23) +========================= + + +Bug Fixes +--------- + +- Reset ``sys.last_type``, ``sys.last_value`` and ``sys.last_traceback`` before + each test executes. Those attributes are added by pytest during the test run + to aid debugging, but were never reset so they would create a leaking + reference to the last failing test's frame which in turn could never be + reclaimed by the garbage collector. (`#2798 + `_) + +- ``pytest.raises`` now raises ``TypeError`` when receiving an unknown keyword + argument. (`#3348 `_) + +- ``pytest.raises`` now works with exception classes that look like iterables. + (`#3372 `_) + + +Improved Documentation +---------------------- + +- Fix typo in ``caplog`` fixture documentation, which incorrectly identified + certain attributes as methods. (`#3406 + `_) + + +Trivial/Internal Changes +------------------------ + +- Added a more indicative error message when parametrizing a function whose + argument takes a default value. (`#3221 + `_) + +- Remove internal ``_pytest.terminal.flatten`` function in favor of + ``more_itertools.collapse``. (`#3330 + `_) + +- Import some modules from ``collections.abc`` instead of ``collections`` as + the former modules trigger ``DeprecationWarning`` in Python 3.7. (`#3339 + `_) + +- record_property is no longer experimental, removing the warnings was + forgotten. (`#3360 `_) + +- Mention in documentation and CLI help that fixtures with leading ``_`` are + printed by ``pytest --fixtures`` only if the ``-v`` option is added. (`#3398 + `_) + + Pytest 3.5.0 (2018-03-21) ========================= diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 79be79fa6..da73acda2 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -48,8 +48,7 @@ fix the bug itself. Fix bugs -------- -Look through the GitHub issues for bugs. Here is a filter you can use: -https://github.com/pytest-dev/pytest/labels/type%3A%20bug +Look through the `GitHub issues for bugs `_. :ref:`Talk ` to developers to find out how you can fix specific bugs. @@ -60,8 +59,7 @@ Don't forget to check the issue trackers of your favourite plugins, too! Implement features ------------------ -Look through the GitHub issues for enhancements. Here is a filter you can use: -https://github.com/pytest-dev/pytest/labels/enhancement +Look through the `GitHub issues for enhancements `_. :ref:`Talk ` to developers to find out how you can implement specific features. diff --git a/_pytest/compat.py b/_pytest/compat.py index bcb31cb88..a5fa03719 100644 --- a/_pytest/compat.py +++ b/_pytest/compat.py @@ -135,6 +135,14 @@ def getfuncargnames(function, is_method=False, cls=None): return arg_names +def get_default_arg_names(function): + # Note: this code intentionally mirrors the code at the beginning of getfuncargnames, + # to get the arguments which were excluded from its result because they had default values + return tuple(p.name for p in signature(function).parameters.values() + if p.kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY) and + p.default is not Parameter.empty) + + if _PY3: STRING_TYPES = bytes, str UNICODE_TYPES = str, diff --git a/_pytest/helpconfig.py b/_pytest/helpconfig.py index 5a81a5bd3..42636bdb0 100644 --- a/_pytest/helpconfig.py +++ b/_pytest/helpconfig.py @@ -138,7 +138,8 @@ def showhelp(config): tw.line("to see available markers type: pytest --markers") tw.line("to see available fixtures type: pytest --fixtures") tw.line("(shown according to specified file_or_dir or current dir " - "if not specified)") + "if not specified; fixtures with leading '_' are only shown " + "with the '-v' option") for warningreport in reporter.stats.get('warnings', []): tw.line("warning : " + warningreport.message, red=True) diff --git a/_pytest/logging.py b/_pytest/logging.py index 89d1c7242..66ed6900b 100644 --- a/_pytest/logging.py +++ b/_pytest/logging.py @@ -289,9 +289,9 @@ def caplog(request): Captured logs are available through the following methods:: - * caplog.text() -> string containing formatted log output - * caplog.records() -> list of logging.LogRecord instances - * caplog.record_tuples() -> list of (logger_name, level, message) tuples + * caplog.text -> string containing formatted log output + * caplog.records -> list of logging.LogRecord instances + * caplog.record_tuples -> list of (logger_name, level, message) tuples * caplog.clear() -> clear captured records and formatted log output string """ result = LogCaptureFixture(request.node) diff --git a/_pytest/main.py b/_pytest/main.py index 9ca20a73c..f56cbaf8a 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -335,7 +335,7 @@ class Session(nodes.FSCollector): def gethookproxy(self, fspath): # check if we have the common case of running - # hooks with all conftest.py filesall conftest.py + # hooks with all conftest.py files pm = self.config.pluginmanager my_conftestmodules = pm._getconftestmodules(fspath) remove_mods = pm._conftest_plugins.difference(my_conftestmodules) diff --git a/_pytest/python.py b/_pytest/python.py index fa5de427e..901892c73 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -26,7 +26,7 @@ from _pytest.compat import ( isclass, isfunction, is_generator, ascii_escaped, REGEX_TYPE, STRING_TYPES, NoneType, NOTSET, get_real_func, getfslineno, safe_getattr, - safe_str, getlocation, enum, + safe_str, getlocation, enum, get_default_arg_names ) from _pytest.outcomes import fail from _pytest.mark.structures import transfer_markers, get_unpacked_marks @@ -76,7 +76,8 @@ def pytest_addoption(parser): group = parser.getgroup("general") group.addoption('--fixtures', '--funcargs', action="store_true", dest="showfixtures", default=False, - help="show available fixtures, sorted by plugin appearance") + help="show available fixtures, sorted by plugin appearance " + "(fixtures with leading '_' are only shown with '-v')") group.addoption( '--fixtures-per-test', action="store_true", @@ -885,6 +886,7 @@ class Metafunc(fixtures.FuncargnamesCompatAttr): argnames, parameters = ParameterSet._for_parametrize( argnames, argvalues, self.function, self.config) del argvalues + default_arg_names = set(get_default_arg_names(self.function)) if scope is None: scope = _find_parametrized_scope(argnames, self._arg2fixturedefs, indirect) @@ -893,13 +895,16 @@ class Metafunc(fixtures.FuncargnamesCompatAttr): valtypes = {} for arg in argnames: if arg not in self.fixturenames: - if isinstance(indirect, (tuple, list)): - name = 'fixture' if arg in indirect else 'argument' + if arg in default_arg_names: + raise ValueError("%r already takes an argument %r with a default value" % (self.function, arg)) else: - name = 'fixture' if indirect else 'argument' - raise ValueError( - "%r uses no %s %r" % ( - self.function, name, arg)) + if isinstance(indirect, (tuple, list)): + name = 'fixture' if arg in indirect else 'argument' + else: + name = 'fixture' if indirect else 'argument' + raise ValueError( + "%r uses no %s %r" % ( + self.function, name, arg)) if indirect is True: valtypes = dict.fromkeys(argnames, "params") diff --git a/_pytest/runner.py b/_pytest/runner.py index 6792387db..f62d34df2 100644 --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -105,6 +105,7 @@ def pytest_runtest_setup(item): def pytest_runtest_call(item): _update_current_test_var(item, 'call') + sys.last_type, sys.last_value, sys.last_traceback = (None, None, None) try: item.runtest() except Exception: @@ -114,7 +115,7 @@ def pytest_runtest_call(item): sys.last_type = type sys.last_value = value sys.last_traceback = tb - del tb # Get rid of it in this namespace + del type, value, tb # Get rid of these in this frame raise diff --git a/changelog/3330.trivial.rst b/changelog/3330.trivial.rst deleted file mode 100644 index ce5ec5882..000000000 --- a/changelog/3330.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -Remove internal ``_pytest.terminal.flatten`` function in favor of ``more_itertools.collapse``. diff --git a/changelog/3339.trivial b/changelog/3339.trivial deleted file mode 100644 index 20196e144..000000000 --- a/changelog/3339.trivial +++ /dev/null @@ -1 +0,0 @@ -Import some modules from ``collections`` instead of ``collections.abc`` as the former modules trigger ``DeprecationWarning`` in Python 3.7. diff --git a/changelog/3348.bugfix.rst b/changelog/3348.bugfix.rst deleted file mode 100644 index 7cf13ab2c..000000000 --- a/changelog/3348.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -``pytest.raises`` now raises ``TypeError`` when receiving an unknown keyword argument. diff --git a/changelog/3360.trivial b/changelog/3360.trivial deleted file mode 100644 index 3b0e89e1f..000000000 --- a/changelog/3360.trivial +++ /dev/null @@ -1,2 +0,0 @@ -record_property is no longer experimental, removing the warnings was forgotten. - diff --git a/changelog/3372.bugfix.rst b/changelog/3372.bugfix.rst deleted file mode 100644 index 722bdab1e..000000000 --- a/changelog/3372.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -``pytest.raises`` now works with exception classes that look like iterables. diff --git a/doc/en/announce/index.rst b/doc/en/announce/index.rst index b03e0f79d..f802c9e4c 100644 --- a/doc/en/announce/index.rst +++ b/doc/en/announce/index.rst @@ -6,6 +6,7 @@ Release announcements :maxdepth: 2 + release-3.5.1 release-3.5.0 release-3.4.2 release-3.4.1 diff --git a/doc/en/announce/release-3.5.1.rst b/doc/en/announce/release-3.5.1.rst new file mode 100644 index 000000000..8eadcc3ac --- /dev/null +++ b/doc/en/announce/release-3.5.1.rst @@ -0,0 +1,30 @@ +pytest-3.5.1 +======================================= + +pytest 3.5.1 has just been released to PyPI. + +This is a bug-fix release, being a drop-in replacement. To upgrade:: + + pip install --upgrade pytest + +The full changelog is available at http://doc.pytest.org/en/latest/changelog.html. + +Thanks to all who contributed to this release, among them: + +* Brian Maissy +* Bruno Oliveira +* Darren Burns +* David Chudzicki +* Floris Bruynooghe +* Holger Kohr +* Irmen de Jong +* Jeffrey Rackauckas +* Rachel Kogan +* Ronny Pfannschmidt +* Stefan Scherfke +* Tim Strazny +* Семён Марьясин + + +Happy testing, +The pytest Development Team diff --git a/doc/en/builtin.rst b/doc/en/builtin.rst index 7a71827e9..554cf59b2 100644 --- a/doc/en/builtin.rst +++ b/doc/en/builtin.rst @@ -12,7 +12,7 @@ For information on plugin hooks and objects, see :ref:`plugins`. For information on the ``pytest.mark`` mechanism, see :ref:`mark`. -For information about fixtures, see :ref:`fixtures`. To see a complete list of available fixtures, type:: +For information about fixtures, see :ref:`fixtures`. To see a complete list of available fixtures (add ``-v`` to also see fixtures with leading ``_``), type :: $ pytest -q --fixtures cache @@ -77,9 +77,9 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a Captured logs are available through the following methods:: - * caplog.text() -> string containing formatted log output - * caplog.records() -> list of logging.LogRecord instances - * caplog.record_tuples() -> list of (logger_name, level, message) tuples + * caplog.text -> string containing formatted log output + * caplog.records -> list of logging.LogRecord instances + * caplog.record_tuples -> list of (logger_name, level, message) tuples * caplog.clear() -> clear captured records and formatted log output string monkeypatch The returned ``monkeypatch`` fixture provides these diff --git a/doc/en/example/reportingdemo.rst b/doc/en/example/reportingdemo.rst index 256fe9a16..b7b6ffaf3 100644 --- a/doc/en/example/reportingdemo.rst +++ b/doc/en/example/reportingdemo.rst @@ -358,7 +358,7 @@ get on the terminal - we are working on that):: > int(s) E ValueError: invalid literal for int() with base 10: 'qwe' - <0-codegen $PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/python_api.py:613>:1: ValueError + <0-codegen $PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/python_api.py:615>:1: ValueError ______________________ TestRaises.test_raises_doesnt _______________________ self = diff --git a/doc/en/example/simple.rst b/doc/en/example/simple.rst index 3dc942018..ce9ec8603 100644 --- a/doc/en/example/simple.rst +++ b/doc/en/example/simple.rst @@ -102,7 +102,7 @@ the command line arguments before they get processed: # content of conftest.py import sys - def pytest_cmdline_preparse(args): + def pytest_load_initial_conftests(args): if 'xdist' in sys.modules: # pytest-xdist plugin import multiprocessing num = max(multiprocessing.cpu_count() / 2, 1) diff --git a/doc/en/fixture.rst b/doc/en/fixture.rst index 6f3debd80..fbb922d79 100644 --- a/doc/en/fixture.rst +++ b/doc/en/fixture.rst @@ -111,11 +111,11 @@ with a list of available function arguments. .. note:: - You can always issue:: + You can always issue :: pytest --fixtures test_simplefactory.py - to see available fixtures. + to see available fixtures (fixtures with leading ``_`` are only shown if you add the ``-v`` option). Fixtures: a prime example of dependency injection --------------------------------------------------- @@ -141,7 +141,7 @@ automatically gets discovered by pytest. The discovery of fixture functions starts at test classes, then test modules, then ``conftest.py`` files and finally builtin and third party plugins. -You can also use the ``conftest.py`` file to implement +You can also use the ``conftest.py`` file to implement :ref:`local per-directory plugins `. Sharing test data diff --git a/doc/en/getting-started.rst b/doc/en/getting-started.rst index 0965c2a61..7b9bfba57 100644 --- a/doc/en/getting-started.rst +++ b/doc/en/getting-started.rst @@ -166,6 +166,8 @@ Find out what kind of builtin :ref:`pytest fixtures ` exist with the c pytest --fixtures # shows builtin and custom fixtures +Note that this command omits fixtures with leading ``_`` unless the ``-v`` option is added. + Continue reading ------------------------------------- diff --git a/doc/en/test/plugin/terminal.rst b/doc/en/test/plugin/terminal.rst index e07d4f721..bcfe53f38 100644 --- a/doc/en/test/plugin/terminal.rst +++ b/doc/en/test/plugin/terminal.rst @@ -23,7 +23,7 @@ command line options ``--full-trace`` don't cut any tracebacks (default is to cut). ``--fixtures`` - show available function arguments, sorted by plugin + show available fixtures, sorted by plugin appearance (fixtures with leading ``_`` are only shown with '-v') Start improving this plugin in 30 seconds ========================================= diff --git a/setup.py b/setup.py index d6defba6b..ffccbaa87 100644 --- a/setup.py +++ b/setup.py @@ -88,6 +88,10 @@ def main(): 'write_to': '_pytest/_version.py', }, url='http://pytest.org', + project_urls={ + 'Source': 'https://github.com/pytest-dev/pytest', + 'Tracker': 'https://github.com/pytest-dev/pytest/issues', + }, license='MIT license', platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], author=( diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index 89a44911f..8ceb3bae1 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -988,3 +988,33 @@ def test_fixture_order_respects_scope(testdir): ''') result = testdir.runpytest() assert result.ret == 0 + + +def test_frame_leak_on_failing_test(testdir): + """pytest would leak garbage referencing the frames of tests that failed that could never be reclaimed (#2798) + + Unfortunately it was not possible to remove the actual circles because most of them + are made of traceback objects which cannot be weakly referenced. Those objects at least + can be eventually claimed by the garbage collector. + """ + testdir.makepyfile(''' + import gc + import weakref + + class Obj: + pass + + ref = None + + def test1(): + obj = Obj() + global ref + ref = weakref.ref(obj) + assert 0 + + def test2(): + gc.collect() + assert ref() is None + ''') + result = testdir.runpytest_subprocess() + result.stdout.fnmatch_lines(['*1 failed, 1 passed in*']) diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index 9b70c3305..b96851627 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -622,6 +622,19 @@ class TestMetafunc(object): "*uses no argument 'y'*", ]) + def test_parametrize_gives_indicative_error_on_function_with_default_argument(self, testdir): + testdir.makepyfile(""" + import pytest + + @pytest.mark.parametrize('x, y', [('a', 'b')]) + def test_simple(x, y=1): + assert len(x) == 1 + """) + result = testdir.runpytest("--collect-only") + result.stdout.fnmatch_lines([ + "*already takes an argument 'y' with a default value", + ]) + def test_addcalls_and_parametrize_indirect(self): def func(x, y): pass diff --git a/testing/test_runner.py b/testing/test_runner.py index a3bd8ecb4..7c179b1f2 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -719,18 +719,20 @@ def test_makereport_getsource_dynamic_code(testdir, monkeypatch): result.stdout.fnmatch_lines(["*test_fix*", "*fixture*'missing'*not found*"]) -def test_store_except_info_on_eror(): +def test_store_except_info_on_error(): """ Test that upon test failure, the exception info is stored on sys.last_traceback and friends. """ - # Simulate item that raises a specific exception - class ItemThatRaises(object): + # Simulate item that might raise a specific exception, depending on `raise_error` class var + class ItemMightRaise(object): nodeid = 'item_that_raises' + raise_error = True def runtest(self): - raise IndexError('TEST') + if self.raise_error: + raise IndexError('TEST') try: - runner.pytest_runtest_call(ItemThatRaises()) + runner.pytest_runtest_call(ItemMightRaise()) except IndexError: pass # Check that exception info is stored on sys @@ -738,6 +740,13 @@ def test_store_except_info_on_eror(): assert sys.last_value.args[0] == 'TEST' assert sys.last_traceback + # The next run should clear the exception info stored by the previous run + ItemMightRaise.raise_error = False + runner.pytest_runtest_call(ItemMightRaise()) + assert sys.last_type is None + assert sys.last_value is None + assert sys.last_traceback is None + def test_current_test_env_var(testdir, monkeypatch): pytest_current_test_vars = [] diff --git a/tox.ini b/tox.ini index 75f28ca92..f4f5c3bf2 100644 --- a/tox.ini +++ b/tox.ini @@ -13,7 +13,7 @@ envlist = {py27,py36}-{pexpect,xdist,trial,numpy,pluggymaster} py27-nobyte doctesting - py35-freeze + py36-freeze docs [testenv] @@ -56,12 +56,11 @@ deps = hypothesis>=3.5.2 changedir=testing commands = - pytest -n1 -ra {posargs:.} + pytest -n8 -ra {posargs:.} [testenv:py36-xdist] deps = {[testenv:py27-xdist]deps} -commands = - pytest -n3 -ra {posargs:testing} +commands = {[testenv:py27-xdist]commands} [testenv:py27-pexpect] changedir = testing @@ -71,11 +70,10 @@ commands = pytest -ra test_pdb.py test_terminal.py test_unittest.py [testenv:py36-pexpect] -changedir = testing -platform = linux|darwin +changedir = {[testenv:py27-pexpect]changedir} +platform = {[testenv:py27-pexpect]platform} deps = {[testenv:py27-pexpect]deps} -commands = - pytest -ra test_pdb.py test_terminal.py test_unittest.py +commands = {[testenv:py27-pexpect]commands} [testenv:py27-nobyte] deps = @@ -95,18 +93,16 @@ commands = [testenv:py36-trial] deps = {[testenv:py27-trial]deps} -commands = - pytest -ra {posargs:testing/test_unittest.py} +commands = {[testenv:py27-trial]commands} [testenv:py27-numpy] -deps=numpy +deps = numpy commands= pytest -ra {posargs:testing/python/approx.py} [testenv:py36-numpy] -deps=numpy -commands= - pytest -ra {posargs:testing/python/approx.py} +deps = {[testenv:py27-numpy]deps} +commands = {[testenv:py27-numpy]commands} [testenv:py27-pluggymaster] setenv= @@ -115,12 +111,9 @@ deps = {[testenv]deps} git+https://github.com/pytest-dev/pluggy.git@master -[testenv:py35-pluggymaster] -setenv= - _PYTEST_SETUP_SKIP_PLUGGY_DEP=1 -deps = - {[testenv:py27-pluggymaster]deps} - git+https://github.com/pytest-dev/pluggy.git@master +[testenv:py36-pluggymaster] +setenv = {[testenv:py27-pluggymaster]setenv} +deps = {[testenv:py27-pluggymaster]deps} [testenv:docs] skipsdist = True @@ -176,7 +169,7 @@ changedir = testing commands = {envpython} {envbindir}/py.test-jython -ra {posargs} -[testenv:py35-freeze] +[testenv:py36-freeze] changedir = testing/freeze deps = pyinstaller commands = @@ -199,7 +192,6 @@ commands = [pytest] minversion = 2.0 plugins = pytester -#--pyargs --doctest-modules --ignore=.tox addopts = -ra -p pytester --ignore=testing/cx_freeze rsyncdirs = tox.ini pytest.py _pytest testing python_files = test_*.py *_test.py testing/*/*.py