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/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/python.py b/_pytest/python.py index f9f17afd7..7444819d8 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -75,7 +75,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", 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/2798.bugfix.rst b/changelog/2798.bugfix.rst new file mode 100644 index 000000000..dc24a724c --- /dev/null +++ b/changelog/2798.bugfix.rst @@ -0,0 +1,3 @@ +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. diff --git a/changelog/3398.trivial.rst b/changelog/3398.trivial.rst new file mode 100644 index 000000000..a4e9ed12c --- /dev/null +++ b/changelog/3398.trivial.rst @@ -0,0 +1 @@ +Mention in documentation and CLI help that fixtures with leading ``_`` are printed by ``pytest --fixtures`` only if the ``-v`` option is added. diff --git a/doc/en/builtin.rst b/doc/en/builtin.rst index 7edb1c453..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 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 984994a7f..8be0438ff 100644 --- a/setup.py +++ b/setup.py @@ -87,6 +87,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/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