diff --git a/AUTHORS b/AUTHORS index 49986ef2d..0c40517ce 100644 --- a/AUTHORS +++ b/AUTHORS @@ -50,6 +50,7 @@ Jaap Broekhuizen Jan Balster Janne Vanhala Jason R. Coombs +John Towler Joshua Bronson Jurko Gospodnetić Katarzyna Jachim @@ -63,6 +64,7 @@ Marc Schlaich Mark Abramowitz Markus Unterwaditzer Martijn Faassen +Martin Prusse Matt Bachmann Matt Williams Michael Aquilina @@ -74,6 +76,7 @@ Omar Kohl Pieter Mulder Piotr Banaszkiewicz Punyashloka Biswal +Quentin Pradet Ralf Schmitt Raphael Pierzina Roman Bolshakov @@ -92,3 +95,4 @@ Russel Winder Ben Webb Alexei Kozlenok Cal Leeming +Feng Ma diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fcd2a7a2b..01546c2b7 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -15,7 +15,7 @@ for a fixture (to solve the funcarg-shadowing-fixture problem). Thanks `@novas0x2a`_ for the complete PR (`#1444`_). -* New ``approx()`` function for easily comparing floating-point numbers in +* New ``approx()`` function for easily comparing floating-point numbers in tests. Thanks `@kalekundert`_ for the complete PR (`#1441`_). @@ -86,11 +86,24 @@ * When receiving identical test ids in parametrize we generate unique test ids. -* +* Fix win32 path issue when puttinging custom config file with absolute path + in ``pytest.main("-c your_absolute_path")``. -* +* Fix maximum recursion depth detection when raised error class is not aware + of unicode/encoded bytes. + Thanks `@prusse-martin`_ for the PR (`#1506`_). -* +* Fix ``pytest.mark.skip`` mark when used in strict mode. + Thanks `@pquentin`_ for the PR and `@RonnyPfannschmidt`_ for + showing how to fix the bug. + +* Minor improvements and fixes to the documentation. + Thanks `@omarkohl`_ for the PR. + + +.. _#1506: https://github.com/pytest-dev/pytest/pull/1506 + +.. _@prusse-martin: https://github.com/prusse-martin 2.9.1 @@ -148,7 +161,7 @@ ``xfail_strict`` ini option that can be used to configure it project-wise. Thanks `@rabbbit`_ for the request and `@nicoddemus`_ for the PR (`#1355`_). -* ``Parser.addini`` now supports options of type ``bool``. +* ``Parser.addini`` now supports options of type ``bool``. Thanks `@nicoddemus`_ for the PR. * New ``ALLOW_BYTES`` doctest option. This strips ``b`` prefixes from byte strings @@ -159,26 +172,26 @@ Fixes `#1366`_. Thanks to `@hpk42`_ for the report and `@RonnyPfannschmidt`_ for the PR. -* Catch ``IndexError`` exceptions when getting exception source location. +* Catch ``IndexError`` exceptions when getting exception source location. Fixes a pytest internal error for dynamically generated code (fixtures and tests) where source lines are fake by intention. **Changes** -* **Important**: `py.code `_ has been - merged into the ``pytest`` repository as ``pytest._code``. This decision - was made because ``py.code`` had very few uses outside ``pytest`` and the - fact that it was in a different repository made it difficult to fix bugs on +* **Important**: `py.code `_ has been + merged into the ``pytest`` repository as ``pytest._code``. This decision + was made because ``py.code`` had very few uses outside ``pytest`` and the + fact that it was in a different repository made it difficult to fix bugs on its code in a timely manner. The team hopes with this to be able to better refactor out and improve that code. This change shouldn't affect users, but it is useful to let users aware if they encounter any strange behavior. - - Keep in mind that the code for ``pytest._code`` is **private** and + + Keep in mind that the code for ``pytest._code`` is **private** and **experimental**, so you definitely should not import it explicitly! - Please note that the original ``py.code`` is still available in - `pylib `_. + Please note that the original ``py.code`` is still available in + `pylib `_. * ``pytest_enter_pdb`` now optionally receives the pytest config object. Thanks `@nicoddemus`_ for the PR. @@ -246,6 +259,7 @@ .. _@RonnyPfannschmidt: https://github.com/RonnyPfannschmidt .. _@rabbbit: https://github.com/rabbbit .. _@hackebrot: https://github.com/hackebrot +.. _@pquentin: https://github.com/pquentin 2.8.7 ===== @@ -269,7 +283,7 @@ - fix #1292: monkeypatch calls (setattr, setenv, etc.) are now O(1). Thanks David R. MacIver for the report and Bruno Oliveira for the PR. -- fix #1223: captured stdout and stderr are now properly displayed before +- fix #1223: captured stdout and stderr are now properly displayed before entering pdb when ``--pdb`` is used instead of being thrown away. Thanks Cal Leeming for the PR. @@ -344,8 +358,8 @@ Thanks Gabriel Reis for the PR. - add more talks to the documentation -- extend documentation on the --ignore cli option -- use pytest-runner for setuptools integration +- extend documentation on the --ignore cli option +- use pytest-runner for setuptools integration - minor fixes for interaction with OS X El Capitan system integrity protection (thanks Florian) diff --git a/_pytest/_code/code.py b/_pytest/_code/code.py index feb69f9e6..78bd7368e 100644 --- a/_pytest/_code/code.py +++ b/_pytest/_code/code.py @@ -305,11 +305,11 @@ class Traceback(list): def filter(self, fn=lambda x: not x.ishidden()): """ return a Traceback instance with certain items removed - fn is a function that gets a single argument, a TracebackItem + fn is a function that gets a single argument, a TracebackEntry instance, and should return True when the item should be added to the Traceback, False when not - by default this removes all the TracebackItems which are hidden + by default this removes all the TracebackEntries which are hidden (see ishidden() above) """ return Traceback(filter(fn, self), self._excinfo) @@ -325,7 +325,7 @@ class Traceback(list): return self[-1] def recursionindex(self): - """ return the index of the frame/TracebackItem where recursion + """ return the index of the frame/TracebackEntry where recursion originates if appropriate, None if no recursion occurred """ cache = {} @@ -603,9 +603,8 @@ class FormattedExcinfo(object): if self.tbfilter: traceback = traceback.filter() recursionindex = None - if excinfo.errisinstance(RuntimeError): - if "maximum recursion depth exceeded" in str(excinfo.value): - recursionindex = traceback.recursionindex() + if is_recursion_error(excinfo): + recursionindex = traceback.recursionindex() last = traceback[-1] entries = [] extraline = None @@ -867,3 +866,14 @@ def getrawcode(obj, trycall=True): return x return obj +if sys.version_info[:2] >= (3, 5): # RecursionError introduced in 3.5 + def is_recursion_error(excinfo): + return excinfo.errisinstance(RecursionError) # noqa +else: + def is_recursion_error(excinfo): + if not excinfo.errisinstance(RuntimeError): + return False + try: + return "maximum recursion depth exceeded" in str(excinfo.value) + except UnicodeError: + return False diff --git a/_pytest/config.py b/_pytest/config.py index fb7b1774f..9a308df2b 100644 --- a/_pytest/config.py +++ b/_pytest/config.py @@ -104,7 +104,7 @@ def _prepareconfig(args=None, plugins=None): elif not isinstance(args, (tuple, list)): if not isinstance(args, str): raise ValueError("not a string or argument list: %r" % (args,)) - args = shlex.split(args) + args = shlex.split(args, posix=sys.platform != "win32") config = get_config() pluginmanager = config.pluginmanager try: diff --git a/_pytest/junitxml.py b/_pytest/junitxml.py index 56400892a..6ff5e4249 100644 --- a/_pytest/junitxml.py +++ b/_pytest/junitxml.py @@ -373,7 +373,7 @@ class LogXML(object): suite_stop_time = time.time() suite_time_delta = suite_stop_time - self.suite_start_time - numtests = self.stats['passed'] + self.stats['failure'] + numtests = self.stats['passed'] + self.stats['failure'] + self.stats['skipped'] logfile.write('') diff --git a/_pytest/mark.py b/_pytest/mark.py index 1a7635402..d8b60def3 100644 --- a/_pytest/mark.py +++ b/_pytest/mark.py @@ -58,7 +58,7 @@ pytest_cmdline_main.tryfirst = True def pytest_collection_modifyitems(items, config): - keywordexpr = config.option.keyword + keywordexpr = config.option.keyword.lstrip() matchexpr = config.option.markexpr if not keywordexpr and not matchexpr: return diff --git a/_pytest/python.py b/_pytest/python.py index b434e6b40..847645cbe 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -1746,7 +1746,7 @@ class FixtureRequest(FuncargnamesCompatAttr): self._pyfuncitem = pyfuncitem #: fixture for which this request is being performed self.fixturename = None - #: Scope string, one of "function", "cls", "module", "session" + #: Scope string, one of "function", "class", "module", "session" self.scope = "function" self._funcargs = {} self._fixturedefs = {} diff --git a/_pytest/skipping.py b/_pytest/skipping.py index 69157f485..55a24ddb9 100644 --- a/_pytest/skipping.py +++ b/_pytest/skipping.py @@ -30,6 +30,11 @@ def pytest_configure(config): nop.Exception = XFailed setattr(pytest, "xfail", nop) + config.addinivalue_line("markers", + "skip(reason=None): skip the given test function with an optional reason. " + "Example: skip(reason=\"no way of currently testing this\") skips the " + "test." + ) config.addinivalue_line("markers", "skipif(condition): skip the given test function if eval(condition) " "results in a True value. Evaluation happens within the " @@ -38,13 +43,13 @@ def pytest_configure(config): "http://pytest.org/latest/skipping.html" ) config.addinivalue_line("markers", - "xfail(condition, reason=None, run=True, raises=None): mark the the test function " - "as an expected failure if eval(condition) has a True value. " - "Optionally specify a reason for better reporting and run=False if " - "you don't even want to execute the test function. If only specific " - "exception(s) are expected, you can list them in raises, and if the test fails " - "in other ways, it will be reported as a true failure. " - "See http://pytest.org/latest/skipping.html" + "xfail(condition, reason=None, run=True, raises=None, strict=False): " + "mark the the test function as an expected failure if eval(condition) " + "has a True value. Optionally specify a reason for better reporting " + "and run=False if you don't even want to execute the test function. " + "If only specific exception(s) are expected, you can list them in " + "raises, and if the test fails in other ways, it will be reported as " + "a true failure. See http://pytest.org/latest/skipping.html" ) diff --git a/doc/en/announce/release-2.9.0.rst b/doc/en/announce/release-2.9.0.rst index 99c1c631f..011b1ffb9 100644 --- a/doc/en/announce/release-2.9.0.rst +++ b/doc/en/announce/release-2.9.0.rst @@ -75,7 +75,7 @@ The py.test Development Team **Changes** -* **Important**: `py.code `_ has been +* **Important**: `py.code `_ has been merged into the ``pytest`` repository as ``pytest._code``. This decision was made because ``py.code`` had very few uses outside ``pytest`` and the fact that it was in a different repository made it difficult to fix bugs on @@ -88,7 +88,7 @@ The py.test Development Team **experimental**, so you definitely should not import it explicitly! Please note that the original ``py.code`` is still available in - `pylib `_. + `pylib `_. * ``pytest_enter_pdb`` now optionally receives the pytest config object. Thanks `@nicoddemus`_ for the PR. diff --git a/doc/en/bash-completion.rst b/doc/en/bash-completion.rst index b2a52fa63..b820944bf 100644 --- a/doc/en/bash-completion.rst +++ b/doc/en/bash-completion.rst @@ -5,7 +5,7 @@ Setting up bash completion ========================== When using bash as your shell, ``pytest`` can use argcomplete -(https://argcomplete.readthedocs.org/) for auto-completion. +(https://argcomplete.readthedocs.io/) for auto-completion. For this ``argcomplete`` needs to be installed **and** enabled. Install argcomplete using:: diff --git a/doc/en/example/pythoncollection.rst b/doc/en/example/pythoncollection.rst index 5faf4c6c8..f37c12c51 100644 --- a/doc/en/example/pythoncollection.rst +++ b/doc/en/example/pythoncollection.rst @@ -9,19 +9,19 @@ by passing the ``--ignore=path`` option on the cli. ``pytest`` allows multiple ``--ignore`` options. Example:: tests/ - ├── example - │   ├── test_example_01.py - │   ├── test_example_02.py - │   └── test_example_03.py - ├── foobar - │   ├── test_foobar_01.py - │   ├── test_foobar_02.py - │   └── test_foobar_03.py - └── hello - └── world - ├── test_world_01.py - ├── test_world_02.py - └── test_world_03.py + |-- example + | |-- test_example_01.py + | |-- test_example_02.py + | '-- test_example_03.py + |-- foobar + | |-- test_foobar_01.py + | |-- test_foobar_02.py + | '-- test_foobar_03.py + '-- hello + '-- world + |-- test_world_01.py + |-- test_world_02.py + '-- test_world_03.py Now if you invoke ``pytest`` with ``--ignore=tests/foobar/test_foobar_03.py --ignore=tests/hello/``, you will see that ``pytest`` only collects test-modules, which do not match the patterns specified:: @@ -177,16 +177,27 @@ and a setup.py dummy file like this:: # content of setup.py 0/0 # will raise exception if imported -then a pytest run on python2 will find the one test when run with a python2 -interpreters and will leave out the setup.py file:: +then a pytest run on Python2 will find the one test and will leave out the +setup.py file:: $ py.test --collect-only - ======= test session starts ======== - platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1 + ====== test session starts ====== + platform linux2 -- Python 2.7.10, pytest-2.9.1, py-1.4.31, pluggy-0.3.1 + rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini + collected 1 items + + + + ====== no tests ran in 0.04 seconds ====== + +If you run with a Python3 interpreter both the one test and the setup.py file +will be left out:: + + $ py.test --collect-only + ====== test session starts ====== + platform linux -- Python 3.4.3+, pytest-2.9.1, py-1.4.31, pluggy-0.3.1 rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini collected 0 items - - ======= no tests ran in 0.12 seconds ======== -If you run with a Python3 interpreter the moduled added through the conftest.py file will not be considered for test collection. + ====== no tests ran in 0.03 seconds ====== diff --git a/doc/en/example/simple.rst b/doc/en/example/simple.rst index 0bf1d7bfa..43470be3c 100644 --- a/doc/en/example/simple.rst +++ b/doc/en/example/simple.rst @@ -201,7 +201,7 @@ Example:: The ``__tracebackhide__`` setting influences ``pytest`` showing of tracebacks: the ``checkconfig`` function will not be shown -unless the ``--fulltrace`` command line option is specified. +unless the ``--full-trace`` command line option is specified. Let's run our little function:: $ py.test -q test_checkconfig.py @@ -725,7 +725,7 @@ Integrating pytest runner and cx_freeze ----------------------------------------------------------- If you freeze your application using a tool like -`cx_freeze `_ in order to distribute it +`cx_freeze `_ in order to distribute it to your end-users, it is a good idea to also package your test runner and run your tests using the frozen application. diff --git a/doc/en/fixture.rst b/doc/en/fixture.rst index f48607ae2..0235448ee 100644 --- a/doc/en/fixture.rst +++ b/doc/en/fixture.rst @@ -36,9 +36,9 @@ style ` or :ref:`nose based ` projects. .. note:: - pytest-2.4 introduced an additional experimental - :ref:`yield fixture mechanism ` for easier context manager - integration and more linear writing of teardown code. + pytest-2.4 introduced an additional :ref:`yield fixture mechanism + ` for easier context manager integration and more linear + writing of teardown code. .. _`funcargs`: .. _`funcarg mechanism`: @@ -283,6 +283,14 @@ module itself does not need to change or know about these details of fixture setup. +Finalization/teardown with yield fixtures +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Another alternative to the *request.addfinalizer()* method is to use *yield +fixtures*. All the code after the *yield* statement serves as the teardown +code. See the :ref:`yield fixture documentation `. + + .. _`request-context`: Fixtures can introspect the requesting test context @@ -577,55 +585,85 @@ to show the setup/teardown flow:: @pytest.fixture(scope="module", params=["mod1", "mod2"]) def modarg(request): param = request.param - print ("create", param) + print (" SETUP modarg %s" % param) def fin(): - print ("fin %s" % param) + print (" TEARDOWN modarg %s" % param) + request.addfinalizer(fin) return param @pytest.fixture(scope="function", params=[1,2]) def otherarg(request): - return request.param + param = request.param + print (" SETUP otherarg %s" % param) + def fin(): + print (" TEARDOWN otherarg %s" % param) + request.addfinalizer(fin) + return param def test_0(otherarg): - print (" test0", otherarg) + print (" RUN test0 with otherarg %s" % otherarg) def test_1(modarg): - print (" test1", modarg) + print (" RUN test1 with modarg %s" % modarg) def test_2(otherarg, modarg): - print (" test2", otherarg, modarg) + print (" RUN test2 with otherarg %s and modarg %s" % (otherarg, modarg)) + Let's run the tests in verbose mode and with looking at the print-output:: $ py.test -v -s test_module.py - ======= test session starts ======== - platform linux -- Python 3.4.0, pytest-2.9.1, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3.4 + ====== test session starts ====== + platform linux -- Python 3.4.3+, pytest-2.9.1, py-1.4.31, pluggy-0.3.1 -- $PYTHON_PREFIX/bin/python3 cachedir: .cache rootdir: $REGENDOC_TMPDIR, inifile: - collecting ... collected 8 items - - test_module.py::test_0[1] test0 1 - PASSED - test_module.py::test_0[2] test0 2 - PASSED - test_module.py::test_1[mod1] create mod1 - test1 mod1 - PASSED - test_module.py::test_2[1-mod1] test2 1 mod1 - PASSED - test_module.py::test_2[2-mod1] test2 2 mod1 - PASSED - test_module.py::test_1[mod2] create mod2 - test1 mod2 - PASSED - test_module.py::test_2[1-mod2] test2 1 mod2 - PASSED - test_module.py::test_2[2-mod2] test2 2 mod2 - PASSED - - ======= 8 passed in 0.12 seconds ======== + collected 8 items -You can see that the parametrized module-scoped ``modarg`` resource caused -an ordering of test execution that lead to the fewest possible "active" resources. The finalizer for the ``mod1`` parametrized resource was executed -before the ``mod2`` resource was setup. + test_module.py::test_0[1] SETUP otherarg 1 + RUN test0 with otherarg 1 + PASSED TEARDOWN otherarg 1 + + test_module.py::test_0[2] SETUP otherarg 2 + RUN test0 with otherarg 2 + PASSED TEARDOWN otherarg 2 + + test_module.py::test_1[mod1] SETUP modarg mod1 + RUN test1 with modarg mod1 + PASSED + test_module.py::test_2[1-mod1] SETUP otherarg 1 + RUN test2 with otherarg 1 and modarg mod1 + PASSED TEARDOWN otherarg 1 + + test_module.py::test_2[2-mod1] SETUP otherarg 2 + RUN test2 with otherarg 2 and modarg mod1 + PASSED TEARDOWN otherarg 2 + + test_module.py::test_1[mod2] TEARDOWN modarg mod1 + SETUP modarg mod2 + RUN test1 with modarg mod2 + PASSED + test_module.py::test_2[1-mod2] SETUP otherarg 1 + RUN test2 with otherarg 1 and modarg mod2 + PASSED TEARDOWN otherarg 1 + + test_module.py::test_2[2-mod2] SETUP otherarg 2 + RUN test2 with otherarg 2 and modarg mod2 + PASSED TEARDOWN otherarg 2 + TEARDOWN modarg mod2 + + + ====== 8 passed in 0.01 seconds ====== + + +You can see that the parametrized module-scoped ``modarg`` resource caused an +ordering of test execution that lead to the fewest possible "active" resources. +The finalizer for the ``mod1`` parametrized resource was executed before the +``mod2`` resource was setup. + +In particular notice that test_0 is completely independent and finishes first. +Then test_1 is executed with ``mod1``, then test_2 with ``mod1``, then test_1 +with ``mod2`` and finally test_2 with ``mod2``. + +The ``otherarg`` parametrized resource (having function scope) was set up before +and teared down after every test that used it. .. _`usefixtures`: diff --git a/doc/en/goodpractices.rst b/doc/en/goodpractices.rst index 2d8050bd9..89396b57e 100644 --- a/doc/en/goodpractices.rst +++ b/doc/en/goodpractices.rst @@ -153,7 +153,9 @@ against your source code checkout, helping to detect packaging glitches. Continuous integration services such as Jenkins_ can make use of the -``--junitxml=PATH`` option to create a JUnitXML file and generate reports. +``--junitxml=PATH`` option to create a JUnitXML file and generate reports (e.g. +by publishing the results in a nice format with the `Jenkins xUnit Plugin +`_). Integrating with setuptools / ``python setup.py test`` / ``pytest-runner`` diff --git a/doc/en/links.inc b/doc/en/links.inc index 3d7863751..b69390baa 100644 --- a/doc/en/links.inc +++ b/doc/en/links.inc @@ -6,7 +6,7 @@ .. _`pytest_nose`: plugin/nose.html .. _`reStructured Text`: http://docutils.sourceforge.net .. _`Python debugger`: http://docs.python.org/lib/module-pdb.html -.. _nose: https://nose.readthedocs.org/en/latest/ +.. _nose: https://nose.readthedocs.io/en/latest/ .. _pytest: http://pypi.python.org/pypi/pytest .. _mercurial: http://mercurial.selenic.com/wiki/ .. _`setuptools`: http://pypi.python.org/pypi/setuptools @@ -18,4 +18,4 @@ .. _hudson: http://hudson-ci.org/ .. _jenkins: http://jenkins-ci.org/ .. _tox: http://testrun.org/tox -.. _pylib: http://py.readthedocs.org/en/latest/ +.. _pylib: https://py.readthedocs.io/en/latest/ diff --git a/doc/en/nose.rst b/doc/en/nose.rst index 3b92e04cf..04386ea0f 100644 --- a/doc/en/nose.rst +++ b/doc/en/nose.rst @@ -46,7 +46,7 @@ Unsupported idioms / known issues (e.g. ``tests/test_mode.py`` and ``other/tests/test_mode.py``) by extending sys.path/import semantics. pytest does not do that but there is discussion in `issue268 `_ for adding some support. Note that - `nose2 choose to avoid this sys.path/import hackery `_. + `nose2 choose to avoid this sys.path/import hackery `_. - nose-style doctests are not collected and executed correctly, also doctest fixtures don't work. diff --git a/doc/en/plugins.rst b/doc/en/plugins.rst index e9b3f460c..3db7f0f59 100644 --- a/doc/en/plugins.rst +++ b/doc/en/plugins.rst @@ -90,7 +90,7 @@ Finding out which plugins are active If you want to find out which plugins are active in your environment you can type:: - py.test --traceconfig + py.test --trace-config and will get an extended test header which shows activated plugins and their names. It will also print local plugins aka diff --git a/doc/en/projects.rst b/doc/en/projects.rst index 76d004916..fa7a2f29a 100644 --- a/doc/en/projects.rst +++ b/doc/en/projects.rst @@ -57,7 +57,7 @@ Here are some examples of projects using ``pytest`` (please send notes via :ref: * `bu `_ a microscopic build system * `katcp `_ Telescope communication protocol over Twisted * `kss plugin timer `_ -* `pyudev `_ a pure Python binding to the Linux library libudev +* `pyudev `_ a pure Python binding to the Linux library libudev * `pytest-localserver `_ a plugin for pytest that provides a httpserver and smtpserver * `pytest-monkeyplus `_ a plugin that extends monkeypatch diff --git a/doc/en/test/attic.rst b/doc/en/test/attic.rst index 6408c7225..2ab9c237a 100644 --- a/doc/en/test/attic.rst +++ b/doc/en/test/attic.rst @@ -21,7 +21,7 @@ but note that project specific settings will be considered first. There is a flag that helps you debugging your conftest.py configurations:: - py.test --traceconfig + py.test --trace-config customizing the collecting and running process diff --git a/doc/en/test/plugin/helpconfig.rst b/doc/en/test/plugin/helpconfig.rst index 9b5b8cddd..00399b690 100644 --- a/doc/en/test/plugin/helpconfig.rst +++ b/doc/en/test/plugin/helpconfig.rst @@ -16,7 +16,7 @@ command line options display py lib version and import information. ``-p name`` early-load given plugin (multi-allowed). -``--traceconfig`` +``--trace-config`` trace considerations of conftest.py files. ``--nomagic`` don't reinterpret asserts, no traceback cutting. diff --git a/doc/en/test/plugin/terminal.rst b/doc/en/test/plugin/terminal.rst index 214c24dfc..0c0796415 100644 --- a/doc/en/test/plugin/terminal.rst +++ b/doc/en/test/plugin/terminal.rst @@ -22,7 +22,7 @@ command line options (deprecated, use -r) ``--tb=style`` traceback print mode (long/short/line/no). -``--fulltrace`` +``--full-trace`` don't cut any tracebacks (default is to cut). ``--fixtures`` show available function arguments, sorted by plugin diff --git a/doc/en/usage.rst b/doc/en/usage.rst index 4a44e26dc..f9add12ab 100644 --- a/doc/en/usage.rst +++ b/doc/en/usage.rst @@ -56,7 +56,7 @@ Several test run options:: Import 'pkg' and use its filesystem location to find and run tests:: - py.test --pyargs pkg # run all tests found below directory of pypkg + py.test --pyargs pkg # run all tests found below directory of pkg Modifying Python traceback printing ---------------------------------------------- @@ -74,6 +74,14 @@ Examples for modifying traceback printing:: py.test --tb=native # Python standard library formatting py.test --tb=no # no traceback at all +The ``--full-trace`` causes very long traces to be printed on error (longer +than ``--tb=long``). It also ensures that a stack trace is printed on +**KeyboardInterrrupt** (Ctrl+C). +This is very useful if the tests are taking too long and you interrupt them +with Ctrl+C to find out where the tests are *hanging*. By default no output +will be shown (because KeyboardInterrupt is catched by pytest). By using this +option you make sure a trace is shown. + Dropping to PDB_ (Python Debugger) on failures ----------------------------------------------- @@ -150,7 +158,7 @@ To get a list of the slowest 10 test durations:: Creating JUnitXML format files ---------------------------------------------------- -To create result files which can be read by Hudson_ or other Continuous +To create result files which can be read by Jenkins_ or other Continuous integration servers, use this invocation:: py.test --junitxml=path diff --git a/doc/en/writing_plugins.rst b/doc/en/writing_plugins.rst index 38d47bf6d..ae02b2f72 100644 --- a/doc/en/writing_plugins.rst +++ b/doc/en/writing_plugins.rst @@ -23,7 +23,7 @@ reporting by calling `well specified hooks`_ of the following plugins: In principle, each hook call is a ``1:N`` Python function call where ``N`` is the number of registered implementation functions for a given specification. -All specifications and implementations following the ``pytest_`` prefix +All specifications and implementations follow the ``pytest_`` prefix naming convention, making them easy to distinguish and find. .. _`pluginorder`: @@ -158,13 +158,22 @@ it in your setuptools-invocation: 'name_of_plugin = myproject.pluginmodule', ] }, + + # custom PyPI classifier for pytest plugins + classifiers=[ + "Framework :: Pytest", + ], ) If a package is installed this way, ``pytest`` will load ``myproject.pluginmodule`` as a plugin which can define `well specified hooks`_. +.. note:: + Make sure to include ``Framework :: Pytest`` in your list of + `PyPI classifiers `_ + to make it easy for users to find your plugin. Requiring/Loading plugins in a test module or conftest file @@ -194,7 +203,7 @@ the plugin manager like this: plugin = config.pluginmanager.getplugin("name_of_plugin") If you want to look at the names of existing plugins, use -the ``--traceconfig`` option. +the ``--trace-config`` option. Testing plugins --------------- diff --git a/extra/get_issues.py b/extra/get_issues.py index 6437ba4c3..2a8f8c316 100644 --- a/extra/get_issues.py +++ b/extra/get_issues.py @@ -2,30 +2,34 @@ import json import py import textwrap -issues_url = "http://bitbucket.org/api/1.0/repositories/pytest-dev/pytest/issues" +issues_url = "https://api.github.com/repos/pytest-dev/pytest/issues" import requests + def get_issues(): - chunksize = 50 - start = 0 issues = [] + url = issues_url while 1: - post_data = {"accountname": "pytest-dev", - "repo_slug": "pytest", - "start": start, - "limit": chunksize} - print ("getting from", start) - r = requests.get(issues_url, params=post_data) + get_data = {"state": "all"} + r = requests.get(url, params=get_data) data = r.json() - issues.extend(data["issues"]) - if start + chunksize >= data["count"]: + if r.status_code == 403: + # API request limit exceeded + print(data['message']) + exit(1) + issues.extend(data) + + # Look for next page + links = requests.utils.parse_header_links(r.headers['Link']) + another_page = False + for link in links: + if link['rel'] == 'next': + url = link['url'] + another_page = True + if not another_page: return issues - start += chunksize -kind2num = "bug enhancement task proposal".split() - -status2num = "new open resolved duplicate invalid wontfix".split() def main(args): cachefile = py.path.local(args.cache) @@ -35,33 +39,38 @@ def main(args): else: issues = json.loads(cachefile.read()) - open_issues = [x for x in issues - if x["status"] in ("new", "open")] + open_issues = [x for x in issues if x["state"] == "open"] - def kind_and_id(x): - kind = x["metadata"]["kind"] - return kind2num.index(kind), len(issues)-int(x["local_id"]) - open_issues.sort(key=kind_and_id) + open_issues.sort(key=lambda x: x["number"]) report(open_issues) + +def _get_kind(issue): + labels = [l['name'] for l in issue['labels']] + for key in ('bug', 'enhancement', 'proposal'): + if key in labels: + return key + return 'issue' + + def report(issues): for issue in issues: - metadata = issue["metadata"] - priority = issue["priority"] title = issue["title"] - content = issue["content"] - kind = metadata["kind"] - status = issue["status"] - id = issue["local_id"] - link = "https://bitbucket.org/pytest-dev/pytest/issue/%s/" % id + body = issue["body"] + kind = _get_kind(issue) + status = issue["state"] + number = issue["number"] + link = "https://github.com/pytest-dev/pytest/issues/%s/" % number print("----") print(status, kind, link) print(title) #print() - #lines = content.split("\n") + #lines = body.split("\n") #print ("\n".join(lines[:3])) - #if len(lines) > 3 or len(content) > 240: + #if len(lines) > 3 or len(body) > 240: # print ("...") + print("\n\nFound %s open issues" % len(issues)) + if __name__ == "__main__": import argparse @@ -72,3 +81,4 @@ if __name__ == "__main__": help="cache file") args = parser.parse_args() main(args) + diff --git a/setup.py b/setup.py index 6660f2160..7cdcdfb99 100644 --- a/setup.py +++ b/setup.py @@ -37,7 +37,7 @@ def has_environment_marker_support(): References: - * https://wheel.readthedocs.org/en/latest/index.html#defining-conditional-dependencies + * https://wheel.readthedocs.io/en/latest/index.html#defining-conditional-dependencies * https://www.python.org/dev/peps/pep-0426/#environment-markers """ try: diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index 9c7fb4c88..0c9d58885 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -671,6 +671,11 @@ class TestDurations: "*call*test_1*", ]) + def test_with_not(self, testdir): + testdir.makepyfile(self.source) + result = testdir.runpytest("-k not 1") + assert result.ret == 0 + class TestDurationWithFixture: source = """ diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index 1d15a852b..d519dc01e 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -4,8 +4,11 @@ import operator import _pytest import py import pytest -from _pytest._code.code import (FormattedExcinfo, ReprExceptionInfo, - ExceptionChainRepr) +from _pytest._code.code import ( + ExceptionInfo, + FormattedExcinfo, + ReprExceptionInfo, + ExceptionChainRepr) queue = py.builtin._tryimport('queue', 'Queue') @@ -1048,3 +1051,18 @@ raise ValueError() assert tw.lines[40] == "E AttributeError" assert tw.lines[41] == "" assert tw.lines[42].endswith("mod.py:15: AttributeError") + + +@pytest.mark.parametrize("style", ["short", "long"]) +@pytest.mark.parametrize("encoding", [None, "utf8", "utf16"]) +def test_repr_traceback_with_unicode(style, encoding): + msg = u'☹' + if encoding is not None: + msg = msg.encode(encoding) + try: + raise RuntimeError(msg) + except RuntimeError: + e_info = ExceptionInfo() + formatter = FormattedExcinfo(style=style) + repr_traceback = formatter.repr_traceback(e_info) + assert repr_traceback is not None diff --git a/testing/test_config.py b/testing/test_config.py index 92c9bdb8b..fe0654017 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -79,7 +79,7 @@ class TestParseIni: """) result = testdir.inline_run("--confcutdir=.") assert result.ret == 0 - + class TestConfigCmdlineParsing: def test_parsing_again_fails(self, testdir): config = testdir.parseconfig() @@ -101,6 +101,16 @@ class TestConfigCmdlineParsing: config = testdir.parseconfig("-c", "custom.cfg") assert config.getini("custom") == "1" + def test_absolute_win32_path(self, testdir): + temp_cfg_file = testdir.makefile(".cfg", custom=""" + [pytest] + addopts = --version + """) + from os.path import normpath + temp_cfg_file = normpath(str(temp_cfg_file)) + ret = pytest.main("-c " + temp_cfg_file) + assert ret == _pytest.main.EXIT_OK + class TestConfigAPI: def test_config_trace(self, testdir): config = testdir.parseconfig() diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index 8eda22f7f..0b00c3a70 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -100,7 +100,7 @@ class TestPython: result, dom = runandparse(testdir) assert result.ret node = dom.find_first_by_tag("testsuite") - node.assert_attr(name="pytest", errors=0, failures=1, skips=3, tests=2) + node.assert_attr(name="pytest", errors=0, failures=1, skips=3, tests=5) def test_timing_function(self, testdir): testdir.makepyfile(""" @@ -304,7 +304,7 @@ class TestPython: result, dom = runandparse(testdir) assert not result.ret node = dom.find_first_by_tag("testsuite") - node.assert_attr(skips=1, tests=0) + node.assert_attr(skips=1, tests=1) tnode = node.find_first_by_tag("testcase") tnode.assert_attr( file="test_xfailure_function.py", @@ -325,7 +325,7 @@ class TestPython: result, dom = runandparse(testdir) # assert result.ret node = dom.find_first_by_tag("testsuite") - node.assert_attr(skips=1, tests=0) + node.assert_attr(skips=1, tests=1) tnode = node.find_first_by_tag("testcase") tnode.assert_attr( file="test_xfailure_xpass.py", @@ -356,7 +356,7 @@ class TestPython: result, dom = runandparse(testdir) assert result.ret == EXIT_NOTESTSCOLLECTED node = dom.find_first_by_tag("testsuite") - node.assert_attr(skips=1, tests=0) + node.assert_attr(skips=1, tests=1) tnode = node.find_first_by_tag("testcase") tnode.assert_attr( file="test_collect_skipped.py", diff --git a/testing/test_skipping.py b/testing/test_skipping.py index 3464974e0..194c8692b 100644 --- a/testing/test_skipping.py +++ b/testing/test_skipping.py @@ -539,6 +539,19 @@ class TestSkip: "*1 passed*2 skipped*", ]) + def test_strict_and_skip(self, testdir): + testdir.makepyfile(""" + import pytest + @pytest.mark.skip + def test_hello(): + pass + """) + result = testdir.runpytest("-rs --strict") + result.stdout.fnmatch_lines([ + "*unconditional skip*", + "*1 skipped*", + ]) + class TestSkipif: def test_skipif_conditional(self, testdir): item = testdir.getitem(""" @@ -812,7 +825,7 @@ def test_default_markers(testdir): result = testdir.runpytest("--markers") result.stdout.fnmatch_lines([ "*skipif(*condition)*skip*", - "*xfail(*condition, reason=None, run=True, raises=None)*expected failure*", + "*xfail(*condition, reason=None, run=True, raises=None, strict=False)*expected failure*", ]) def test_xfail_test_setup_exception(testdir): diff --git a/tox.ini b/tox.ini index ac604b88e..f6eabfbcb 100644 --- a/tox.ini +++ b/tox.ini @@ -17,10 +17,11 @@ deps= [testenv:py26] commands= py.test --lsof -rfsxX {posargs:testing} +# pinning mock to last supported version for python 2.6 deps= hypothesis<3.0 nose - mock<1.1 # last supported version for py26 + mock<1.1 [testenv:py27-subprocess] changedir=.