+2017-12-01 Carlos Garcia Campos <cgarcia@igalia.com>
+
+ WebDriver: auto-install pytest instead of importing it from wpt tools directory
+ https://bugs.webkit.org/show_bug.cgi?id=180243
+
+ Reviewed by Brian Burg.
+
+ We don't really need the (old) version included in wpt tools dir, so we can simply remove it and use autoinstall
+ instead.
+
+ * Scripts/webkitpy/thirdparty/__init__.py:
+ (AutoinstallImportHook.find_module): Check pytest.
+ (AutoinstallImportHook._install_pytest): Install pytest.
+ * Scripts/webkitpy/webdriver_tests/webdriver_selenium_executor.py: Import autoinstalled pytest.
+ * Scripts/webkitpy/webdriver_tests/webdriver_test_runner_w3c.py:
+ (WebDriverTestRunnerW3C.run): Update the subtest path since the new pytest uses a different strategy for
+ rootdir.
+ * Scripts/webkitpy/webdriver_tests/webdriver_w3c_executor.py: Import autoinstalled pytest.
+
2017-12-01 Dewei Zhu <dewei_zhu@apple.com>
Hardcoded python path is not compatible with virtual environment.
self._install_mozprocess()
elif '.pytest_timeout' in fullname:
self._install_pytest_timeout()
+ elif '.pytest' in fullname:
+ self._install_pytest()
def _install_mechanize(self):
self._install("https://pypi.python.org/packages/source/m/mechanize/mechanize-0.2.5.tar.gz",
self._install("https://pypi.python.org/packages/cc/b7/b2a61365ea6b6d2e8881360ae7ed8dad0327ad2df89f2f0be4a02304deb2/pytest-timeout-1.2.0.tar.gz#md5=83607d91aa163562c7ee835da57d061d",
"pytest-timeout-1.2.0/pytest_timeout.py")
+ def _install_pytest(self):
+ self._install("https://pypi.python.org/packages/1f/f8/8cd74c16952163ce0db0bd95fdd8810cbf093c08be00e6e665ebf0dc3138/pytest-3.2.5.tar.gz#md5=6dbe9bb093883f75394a689a1426ac6f",
+ "pytest-3.2.5/_pytest")
+ self._install("https://pypi.python.org/packages/1f/f8/8cd74c16952163ce0db0bd95fdd8810cbf093c08be00e6e665ebf0dc3138/pytest-3.2.5.tar.gz#md5=6dbe9bb093883f75394a689a1426ac6f",
+ "pytest-3.2.5/pytest.py")
+
def _install_pylint(self):
self._ensure_autoinstalled_dir_is_in_sys_path()
if (not self._fs.exists(self._fs.join(_AUTOINSTALLED_DIR, "pylint")) or
from webkitpy.common.system.filesystem import FileSystem
from webkitpy.common.webkit_finder import WebKitFinder
import webkitpy.thirdparty.autoinstalled.mozlog
+import webkitpy.thirdparty.autoinstalled.pytest
+import webkitpy.thirdparty.autoinstalled.pytest_timeout
+import pytest
# Since W3C tests also use pytest, we use pytest and some other tools for selenium too.
w3c_tools_dir = WebKitFinder(FileSystem()).path_from_webkit_base('WebDriverTests', 'imported', 'w3c', 'tools')
def _ensure_directory_in_path(directory):
if not directory in sys.path:
sys.path.insert(0, directory)
-_ensure_directory_in_path(os.path.join(w3c_tools_dir, 'pytest'))
_ensure_directory_in_path(os.path.join(w3c_tools_dir, 'wptrunner'))
-import pytest
-import webkitpy.thirdparty.autoinstalled.pytest_timeout
from wptrunner.executors.pytestrunner.runner import HarnessResultRecorder, SubtestResultRecorder, TemporaryDirectory
_log = logging.getLogger(__name__)
harness_result, test_results = executor.run(test)
result = WebDriverTestResult(test_name, *harness_result)
if harness_result[0] == 'OK':
- for test_result in test_results:
- result.add_subtest_results(*test_result)
+ for subtest, status, message, backtrace in test_results:
+ result.add_subtest_results(os.path.basename(subtest), status, message, backtrace)
else:
# FIXME: handle other results.
pass
from webkitpy.common.webkit_finder import WebKitFinder
import webkitpy.thirdparty.autoinstalled.mozlog
import webkitpy.thirdparty.autoinstalled.mozprocess
+import webkitpy.thirdparty.autoinstalled.pytest
+import webkitpy.thirdparty.autoinstalled.pytest_timeout
from mozlog import structuredlog
w3c_tools_dir = WebKitFinder(FileSystem()).path_from_webkit_base('WebDriverTests', 'imported', 'w3c', 'tools')
def _ensure_directory_in_path(directory):
if not directory in sys.path:
sys.path.insert(0, directory)
-_ensure_directory_in_path(os.path.join(w3c_tools_dir, 'pytest'))
_ensure_directory_in_path(os.path.join(w3c_tools_dir, 'webdriver'))
_ensure_directory_in_path(os.path.join(w3c_tools_dir, 'wptrunner'))
-import webkitpy.thirdparty.autoinstalled.pytest_timeout
from wptrunner.executors.base import WdspecExecutor, WebDriverProtocol
from wptrunner.webdriver_server import WebDriverServer
+2017-12-01 Carlos Garcia Campos <cgarcia@igalia.com>
+
+ WebDriver: auto-install pytest instead of importing it from wpt tools directory
+ https://bugs.webkit.org/show_bug.cgi?id=180243
+
+ Reviewed by Brian Burg.
+
+ * imported/selenium/py/conftest.py: Stop patching this to use yield_fixture, new pytest supports this.
+ * imported/selenium/py/setup.cfg: Stop patching this, since pytest supports tool:pytest as group name.
+ * imported/selenium/py/test/selenium/webdriver/common/alerts_tests.py: Stop patching this to use yield_fixture,
+ new pytest supports this.
+ * imported/selenium/py/test/selenium/webdriver/common/cookie_tests.py: Ditto.
+ * imported/selenium/py/test/selenium/webdriver/common/frame_switching_tests.py: Ditto.
+ * imported/selenium/py/test/selenium/webdriver/common/page_load_timeout_tests.py: Ditto.
+ * imported/selenium/py/test/selenium/webdriver/common/window_switching_tests.py: Ditto.
+ * imported/selenium/py/test/selenium/webdriver/safari/conftest.py: Ditto.
+ * imported/selenium/py/test/selenium/webdriver/support/event_firing_webdriver_tests.py: Ditto.
+ * imported/w3c/importer.json: Stop importing pytest.
+ * imported/w3c/pytest.ini: Added.
+ * imported/w3c/tools/pytest/: Removed.
+
2017-11-30 Carlos Garcia Campos <cgarcia@igalia.com>
WebDriver: add support for importing and running selenium tests
driver_instance = None
-@pytest.yield_fixture(scope='function')
+@pytest.fixture(scope='function')
def driver(request):
kwargs = {}
return Pages()
-@pytest.yield_fixture(autouse=True, scope='session')
+@pytest.fixture(autouse=True, scope='session')
def server(request):
drivers = request.config.getoption('drivers')
if drivers is None or 'Remote' not in drivers:
print('Selenium server has been terminated')
-@pytest.yield_fixture(autouse=True, scope='session')
+@pytest.fixture(autouse=True, scope='session')
def webserver():
webserver = SimpleWebServer(host=get_lan_ip())
webserver.start()
exclude = .tox,docs/source/conf.py
ignore = E501
-[pytest]
+[tool:pytest]
addopts = -r=a
python_files = test_*.py *_tests.py
testpaths = test
WebDriverException)
-@pytest.yield_fixture(autouse=True)
+@pytest.fixture(autouse=True)
def close_alert(driver):
yield
try:
return cookie
-@pytest.yield_fixture(autouse=True)
+@pytest.fixture(autouse=True)
def pages(request, driver, pages):
pages.load('simpleTest.html')
yield pages
# ----------------------------------------------------------------------------------------------
-@pytest.yield_fixture(autouse=True)
+@pytest.fixture(autouse=True)
def restore_default_context(driver):
yield
driver.switch_to.default_content()
from selenium.common.exceptions import TimeoutException
-@pytest.yield_fixture(autouse=True)
+@pytest.fixture(autouse=True)
def reset_timeouts(driver):
yield
driver.set_page_load_timeout(300)
from selenium.webdriver.support.ui import WebDriverWait
-@pytest.yield_fixture(autouse=True)
+@pytest.fixture(autouse=True)
def close_windows(driver):
main_windows_handle = driver.current_window_handle
yield
return {}
-@pytest.yield_fixture
+@pytest.fixture
def driver(driver_class, driver_kwargs):
driver = driver_class(**driver_kwargs)
yield driver
from selenium.webdriver.support.ui import WebDriverWait
-@pytest.yield_fixture
+@pytest.fixture
def log():
log = BytesIO()
yield log
"repository": "https://github.com/w3c/web-platform-tests.git",
"revision": "2b50389ee72d89dd0be12bc6ca54a6e95c98d163",
"paths_to_import": [
- "tools/pytest",
"tools/webdriver",
"tools/wptrunner",
"webdriver"
--- /dev/null
+# Pytest config file. We don't have any special configuration for pytest,
+# but the existance of this file here determines the rootdir used by pytest.
+++ /dev/null
-[run]
-omit =
- # standlonetemplate is read dynamically and tested by test_genscript
- *standalonetemplate.py
- # oldinterpret could be removed, as it is no longer used in py26+
- *oldinterpret.py
- vendored_packages
+++ /dev/null
-CHANGELOG merge=union
+++ /dev/null
-Thanks for submitting an issue!
-
-Here's a quick checklist in what to include:
-
-- [ ] Include a detailed description of the bug or suggestion
-- [ ] `pip list` of the virtual environment you are using
-- [ ] py.test and operating system versions
-- [ ] Minimal example if possible
+++ /dev/null
-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)
+++ /dev/null
-# Automatically generated by `hgimportsvn`
-.svn
-.hgsvn
-
-# Ignore local virtualenvs
-lib/
-bin/
-include/
-.Python/
-
-# These lines are suggested according to the svn:ignore property
-# Feel free to enable them by uncommenting them
-*.pyc
-*.pyo
-*.swp
-*.class
-*.orig
-*~
-
-.eggs/
-
-doc/*/_build
-build/
-dist/
-*.egg-info
-issue/
-env/
-.env/
-3rdparty/
-.tox
-.cache
-.coverage
-.ropeproject
-.idea
+++ /dev/null
-sudo: false
-language: python
-python:
- - '3.5'
-# command to install dependencies
-install: "pip install -U tox"
-# # command to run tests
-env:
- matrix:
- # coveralls is not listed in tox's envlist, but should run in travis
- - TESTENV=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=py27-cxfreeze
-
-script: tox --recreate -e $TESTENV
-
-notifications:
- irc:
- channels:
- - "chat.freenode.net#pytest"
- on_success: change
- on_failure: change
- skip_join: true
- email:
- - pytest-commit@python.org
+++ /dev/null
-Holger Krekel, holger at merlinux eu
-merlinux GmbH, Germany, office at merlinux eu
-
-Contributors include::
-
-Abhijeet Kasurde
-Anatoly Bubenkoff
-Andreas Zeidler
-Andy Freeland
-Anthon van der Neut
-Armin Rigo
-Aron Curzon
-Aviv Palivoda
-Benjamin Peterson
-Bob Ippolito
-Brian Dorsey
-Brian Okken
-Brianna Laugher
-Bruno Oliveira
-Carl Friedrich Bolz
-Charles Cloud
-Chris Lamb
-Christian Theunert
-Christian Tismer
-Christopher Gilling
-Daniel Grana
-Daniel Hahler
-Daniel Nuri
-Dave Hunt
-David Mohr
-David Vierra
-Edison Gustavo Muenz
-Eduardo Schettino
-Endre Galaczi
-Elizaveta Shashkova
-Eric Hunsberger
-Eric Siegerman
-Erik M. Bray
-Florian Bruhin
-Floris Bruynooghe
-Gabriel Reis
-Georgy Dyuldin
-Graham Horler
-Grig Gheorghiu
-Guido Wesdorp
-Harald Armin Massa
-Ian Bicking
-Jaap Broekhuizen
-Jan Balster
-Janne Vanhala
-Jason R. Coombs
-Joshua Bronson
-Jurko Gospodnetić
-Katarzyna Jachim
-Kevin Cox
-Lee Kamentsky
-Lukas Bednar
-Maciek Fijalkowski
-Maho
-Marc Schlaich
-Mark Abramowitz
-Markus Unterwaditzer
-Martijn Faassen
-Matt Bachmann
-Michael Aquilina
-Michael Birtwell
-Michael Droettboom
-Nicolas Delaby
-Pieter Mulder
-Piotr Banaszkiewicz
-Punyashloka Biswal
-Ralf Schmitt
-Raphael Pierzina
-Ronny Pfannschmidt
-Ross Lawley
-Ryan Wooden
-Samuele Pedroni
-Tom Viner
-Trevor Bekolay
-Wouter van Ackooy
-David DÃaz-Barquero
-Eric Hunsberger
-Simon Gomizelj
-Russel Winder
-Ben Webb
-Alexei Kozlenok
-Cal Leeming
+++ /dev/null
-2.9.1
-=====
-
-**Bug Fixes**
-
-* Improve error message when a plugin fails to load.
- Thanks `@nicoddemus`_ for the PR.
-
-* Fix (`#1178 <https://github.com/pytest-dev/pytest/issues/1178>`_):
- ``pytest.fail`` with non-ascii characters raises an internal pytest error.
- Thanks `@nicoddemus`_ for the PR.
-
-* Fix (`#469`_): junit parses report.nodeid incorrectly, when params IDs
- contain ``::``. Thanks `@tomviner`_ for the PR (`#1431`_).
-
-* Fix (`#578 <https://github.com/pytest-dev/pytest/issues/578>`_): SyntaxErrors
- containing non-ascii lines at the point of failure generated an internal
- py.test error.
- Thanks `@asottile`_ for the report and `@nicoddemus`_ for the PR.
-
-* Fix (`#1437`_): When passing in a bytestring regex pattern to parameterize
- attempt to decode it as utf-8 ignoring errors.
-
-* Fix (`#649`_): parametrized test nodes cannot be specified to run on the command line.
-
-
-.. _#1437: https://github.com/pytest-dev/pytest/issues/1437
-.. _#469: https://github.com/pytest-dev/pytest/issues/469
-.. _#1431: https://github.com/pytest-dev/pytest/pull/1431
-.. _#649: https://github.com/pytest-dev/pytest/issues/649
-
-.. _@asottile: https://github.com/asottile
-
-
-2.9.0
-=====
-
-**New Features**
-
-* New ``pytest.mark.skip`` mark, which unconditionally skips marked tests.
- Thanks `@MichaelAquilina`_ for the complete PR (`#1040`_).
-
-* ``--doctest-glob`` may now be passed multiple times in the command-line.
- Thanks `@jab`_ and `@nicoddemus`_ for the PR.
-
-* New ``-rp`` and ``-rP`` reporting options give the summary and full output
- of passing tests, respectively. Thanks to `@codewarrior0`_ for the PR.
-
-* ``pytest.mark.xfail`` now has a ``strict`` option, which makes ``XPASS``
- tests to fail the test suite (defaulting to ``False``). There's also a
- ``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``.
- Thanks `@nicoddemus`_ for the PR.
-
-* New ``ALLOW_BYTES`` doctest option. This strips ``b`` prefixes from byte strings
- in doctest output (similar to ``ALLOW_UNICODE``).
- Thanks `@jaraco`_ for the request and `@nicoddemus`_ for the PR (`#1287`_).
-
-* Give a hint on ``KeyboardInterrupt`` to use the ``--fulltrace`` option to show the errors.
- Fixes `#1366`_.
- Thanks to `@hpk42`_ for the report and `@RonnyPfannschmidt`_ for the PR.
-
-* 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 <http://pylib.readthedocs.org/en/latest/code.html>`_ 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
- **experimental**, so you definitely should not import it explicitly!
-
- Please note that the original ``py.code`` is still available in
- `pylib <http://pylib.readthedocs.org>`_.
-
-* ``pytest_enter_pdb`` now optionally receives the pytest config object.
- Thanks `@nicoddemus`_ for the PR.
-
-* Removed code and documentation for Python 2.5 or lower versions,
- including removal of the obsolete ``_pytest.assertion.oldinterpret`` module.
- Thanks `@nicoddemus`_ for the PR (`#1226`_).
-
-* Comparisons now always show up in full when ``CI`` or ``BUILD_NUMBER`` is
- found in the environment, even when ``-vv`` isn't used.
- Thanks `@The-Compiler`_ for the PR.
-
-* ``--lf`` and ``--ff`` now support long names: ``--last-failed`` and
- ``--failed-first`` respectively.
- Thanks `@MichaelAquilina`_ for the PR.
-
-* Added expected exceptions to ``pytest.raises`` fail message.
-
-* Collection only displays progress ("collecting X items") when in a terminal.
- This avoids cluttering the output when using ``--color=yes`` to obtain
- colors in CI integrations systems (`#1397`_).
-
-**Bug Fixes**
-
-* The ``-s`` and ``-c`` options should now work under ``xdist``;
- ``Config.fromdictargs`` now represents its input much more faithfully.
- Thanks to `@bukzor`_ for the complete PR (`#680`_).
-
-* Fix (`#1290`_): support Python 3.5's ``@`` operator in assertion rewriting.
- Thanks `@Shinkenjoe`_ for report with test case and `@tomviner`_ for the PR.
-
-* Fix formatting utf-8 explanation messages (`#1379`_).
- Thanks `@biern`_ for the PR.
-
-* Fix `traceback style docs`_ to describe all of the available options
- (auto/long/short/line/native/no), with `auto` being the default since v2.6.
- Thanks `@hackebrot`_ for the PR.
-
-* Fix (`#1422`_): junit record_xml_property doesn't allow multiple records
- with same name.
-
-.. _`traceback style docs`: https://pytest.org/latest/usage.html#modifying-python-traceback-printing
-
-.. _#1422: https://github.com/pytest-dev/pytest/issues/1422
-.. _#1379: https://github.com/pytest-dev/pytest/issues/1379
-.. _#1366: https://github.com/pytest-dev/pytest/issues/1366
-.. _#1040: https://github.com/pytest-dev/pytest/pull/1040
-.. _#680: https://github.com/pytest-dev/pytest/issues/680
-.. _#1287: https://github.com/pytest-dev/pytest/pull/1287
-.. _#1226: https://github.com/pytest-dev/pytest/pull/1226
-.. _#1290: https://github.com/pytest-dev/pytest/pull/1290
-.. _#1355: https://github.com/pytest-dev/pytest/pull/1355
-.. _#1397: https://github.com/pytest-dev/pytest/issues/1397
-.. _@biern: https://github.com/biern
-.. _@MichaelAquilina: https://github.com/MichaelAquilina
-.. _@bukzor: https://github.com/bukzor
-.. _@hpk42: https://github.com/hpk42
-.. _@nicoddemus: https://github.com/nicoddemus
-.. _@jab: https://github.com/jab
-.. _@codewarrior0: https://github.com/codewarrior0
-.. _@jaraco: https://github.com/jaraco
-.. _@The-Compiler: https://github.com/The-Compiler
-.. _@Shinkenjoe: https://github.com/Shinkenjoe
-.. _@tomviner: https://github.com/tomviner
-.. _@RonnyPfannschmidt: https://github.com/RonnyPfannschmidt
-.. _@rabbbit: https://github.com/rabbbit
-.. _@hackebrot: https://github.com/hackebrot
-
-2.8.7
-=====
-
-- fix #1338: use predictable object resolution for monkeypatch
-
-2.8.6
-=====
-
-- fix #1259: allow for double nodeids in junitxml,
- this was a regression failing plugins combinations
- like pytest-pep8 + pytest-flakes
-
-- Workaround for exception that occurs in pyreadline when using
- ``--pdb`` with standard I/O capture enabled.
- Thanks Erik M. Bray for the PR.
-
-- fix #900: Better error message in case the target of a ``monkeypatch`` call
- raises an ``ImportError``.
-
-- 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
- entering pdb when ``--pdb`` is used instead of being thrown away.
- Thanks Cal Leeming for the PR.
-
-- fix #1305: pytest warnings emitted during ``pytest_terminal_summary`` are now
- properly displayed.
- Thanks Ionel Maries Cristian for the report and Bruno Oliveira for the PR.
-
-- fix #628: fixed internal UnicodeDecodeError when doctests contain unicode.
- Thanks Jason R. Coombs for the report and Bruno Oliveira for the PR.
-
-- fix #1334: Add captured stdout to jUnit XML report on setup error.
- Thanks Georgy Dyuldin for the PR.
-
-
-2.8.5
-=====
-
-- fix #1243: fixed issue where class attributes injected during collection could break pytest.
- PR by Alexei Kozlenok, thanks Ronny Pfannschmidt and Bruno Oliveira for the review and help.
-
-- fix #1074: precompute junitxml chunks instead of storing the whole tree in objects
- Thanks Bruno Oliveira for the report and Ronny Pfannschmidt for the PR
-
-- fix #1238: fix ``pytest.deprecated_call()`` receiving multiple arguments
- (Regression introduced in 2.8.4). Thanks Alex Gaynor for the report and
- Bruno Oliveira for the PR.
-
-
-2.8.4
-=====
-
-- fix #1190: ``deprecated_call()`` now works when the deprecated
- function has been already called by another test in the same
- module. Thanks Mikhail Chernykh for the report and Bruno Oliveira for the
- PR.
-
-- fix #1198: ``--pastebin`` option now works on Python 3. Thanks
- Mehdy Khoshnoody for the PR.
-
-- fix #1219: ``--pastebin`` now works correctly when captured output contains
- non-ascii characters. Thanks Bruno Oliveira for the PR.
-
-- fix #1204: another error when collecting with a nasty __getattr__().
- Thanks Florian Bruhin for the PR.
-
-- fix the summary printed when no tests did run.
- Thanks Florian Bruhin for the PR.
-- fix #1185 - ensure MANIFEST.in exactly matches what should go to a sdist
-
-- a number of documentation modernizations wrt good practices.
- Thanks Bruno Oliveira for the PR.
-
-2.8.3
-=====
-
-- fix #1169: add __name__ attribute to testcases in TestCaseFunction to
- support the @unittest.skip decorator on functions and methods.
- Thanks Lee Kamentsky for the PR.
-
-- fix #1035: collecting tests if test module level obj has __getattr__().
- Thanks Suor for the report and Bruno Oliveira / Tom Viner for the PR.
-
-- fix #331: don't collect tests if their failure cannot be reported correctly
- e.g. they are a callable instance of a class.
-
-- fix #1133: fixed internal error when filtering tracebacks where one entry
- belongs to a file which is no longer available.
- Thanks Bruno Oliveira for the PR.
-
-- enhancement made to highlight in red the name of the failing tests so
- they stand out in the output.
- 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
-- minor fixes for interaction with OS X El Capitan
- system integrity protection (thanks Florian)
-
-
-2.8.2
-=====
-
-- fix #1085: proper handling of encoding errors when passing encoded byte
- strings to pytest.parametrize in Python 2.
- Thanks Themanwithoutaplan for the report and Bruno Oliveira for the PR.
-
-- fix #1087: handling SystemError when passing empty byte strings to
- pytest.parametrize in Python 3.
- Thanks Paul Kehrer for the report and Bruno Oliveira for the PR.
-
-- fix #995: fixed internal error when filtering tracebacks where one entry
- was generated by an exec() statement.
- Thanks Daniel Hahler, Ashley C Straw, Philippe Gauthier and Pavel Savchenko
- for contributing and Bruno Oliveira for the PR.
-
-- fix #1100 and #1057: errors when using autouse fixtures and doctest modules.
- Thanks Sergey B Kirpichev and Vital Kudzelka for contributing and Bruno
- Oliveira for the PR.
-
-2.8.1
-=====
-
-- fix #1034: Add missing nodeid on pytest_logwarning call in
- addhook. Thanks Simon Gomizelj for the PR.
-
-- 'deprecated_call' is now only satisfied with a DeprecationWarning or
- PendingDeprecationWarning. Before 2.8.0, it accepted any warning, and 2.8.0
- made it accept only DeprecationWarning (but not PendingDeprecationWarning).
- Thanks Alex Gaynor for the issue and Eric Hunsberger for the PR.
-
-- fix issue #1073: avoid calling __getattr__ on potential plugin objects.
- This fixes an incompatibility with pytest-django. Thanks Andreas Pelme,
- Bruno Oliveira and Ronny Pfannschmidt for contributing and Holger Krekel
- for the fix.
-
-- Fix issue #704: handle versionconflict during plugin loading more
- gracefully. Thanks Bruno Oliveira for the PR.
-
-- Fix issue #1064: ""--junitxml" regression when used with the
- "pytest-xdist" plugin, with test reports being assigned to the wrong tests.
- Thanks Daniel Grunwald for the report and Bruno Oliveira for the PR.
-
-- (experimental) adapt more SEMVER style versioning and change meaning of
- master branch in git repo: "master" branch now keeps the bugfixes, changes
- aimed for micro releases. "features" branch will only be be released
- with minor or major pytest releases.
-
-- Fix issue #766 by removing documentation references to distutils.
- Thanks Russel Winder.
-
-- Fix issue #1030: now byte-strings are escaped to produce item node ids
- to make them always serializable.
- Thanks Andy Freeland for the report and Bruno Oliveira for the PR.
-
-- Python 2: if unicode parametrized values are convertible to ascii, their
- ascii representation is used for the node id.
-
-- Fix issue #411: Add __eq__ method to assertion comparison example.
- Thanks Ben Webb.
-- Fix issue #653: deprecated_call can be used as context manager.
-
-- fix issue 877: properly handle assertion explanations with non-ascii repr
- Thanks Mathieu Agopian for the report and Ronny Pfannschmidt for the PR.
-
-- fix issue 1029: transform errors when writing cache values into pytest-warnings
-
-2.8.0
-=====
-
-- new ``--lf`` and ``-ff`` options to run only the last failing tests or
- "failing tests first" from the last run. This functionality is provided
- through porting the formerly external pytest-cache plugin into pytest core.
- BACKWARD INCOMPAT: if you used pytest-cache's functionality to persist
- data between test runs be aware that we don't serialize sets anymore.
- Thanks Ronny Pfannschmidt for most of the merging work.
-
-- "-r" option now accepts "a" to include all possible reports, similar
- to passing "fEsxXw" explicitly (isse960).
- Thanks Abhijeet Kasurde for the PR.
-
-- avoid python3.5 deprecation warnings by introducing version
- specific inspection helpers, thanks Michael Droettboom.
-
-- fix issue562: @nose.tools.istest now fully respected.
-
-- fix issue934: when string comparison fails and a diff is too large to display
- without passing -vv, still show a few lines of the diff.
- Thanks Florian Bruhin for the report and Bruno Oliveira for the PR.
-
-- fix issue736: Fix a bug where fixture params would be discarded when combined
- with parametrization markers.
- Thanks to Markus Unterwaditzer for the PR.
-
-- fix issue710: introduce ALLOW_UNICODE doctest option: when enabled, the
- ``u`` prefix is stripped from unicode strings in expected doctest output. This
- allows doctests which use unicode to run in Python 2 and 3 unchanged.
- Thanks Jason R. Coombs for the report and Bruno Oliveira for the PR.
-
-- parametrize now also generates meaningful test IDs for enum, regex and class
- objects (as opposed to class instances).
- Thanks to Florian Bruhin for the PR.
-
-- Add 'warns' to assert that warnings are thrown (like 'raises').
- Thanks to Eric Hunsberger for the PR.
-
-- Fix issue683: Do not apply an already applied mark. Thanks ojake for the PR.
-
-- Deal with capturing failures better so fewer exceptions get lost to
- /dev/null. Thanks David Szotten for the PR.
-
-- fix issue730: deprecate and warn about the --genscript option.
- Thanks Ronny Pfannschmidt for the report and Christian Pommranz for the PR.
-
-- fix issue751: multiple parametrize with ids bug if it parametrizes class with
- two or more test methods. Thanks Sergey Chipiga for reporting and Jan
- Bednarik for PR.
-
-- fix issue82: avoid loading conftest files from setup.cfg/pytest.ini/tox.ini
- files and upwards by default (--confcutdir can still be set to override this).
- Thanks Bruno Oliveira for the PR.
-
-- fix issue768: docstrings found in python modules were not setting up session
- fixtures. Thanks Jason R. Coombs for reporting and Bruno Oliveira for the PR.
-
-- added ``tmpdir_factory``, a session-scoped fixture that can be used to create
- directories under the base temporary directory. Previously this object was
- installed as a ``_tmpdirhandler`` attribute of the ``config`` object, but now it
- is part of the official API and using ``config._tmpdirhandler`` is
- deprecated.
- Thanks Bruno Oliveira for the PR.
-
-- fix issue808: pytest's internal assertion rewrite hook now implements the
- optional PEP302 get_data API so tests can access data files next to them.
- Thanks xmo-odoo for request and example and Bruno Oliveira for
- the PR.
-
-- rootdir and inifile are now displayed during usage errors to help
- users diagnose problems such as unexpected ini files which add
- unknown options being picked up by pytest. Thanks to Pavel Savchenko for
- bringing the problem to attention in #821 and Bruno Oliveira for the PR.
-
-- Summary bar now is colored yellow for warning
- situations such as: all tests either were skipped or xpass/xfailed,
- or no tests were run at all (this is a partial fix for issue500).
-
-- fix issue812: pytest now exits with status code 5 in situations where no
- tests were run at all, such as the directory given in the command line does
- not contain any tests or as result of a command line option filters
- all out all tests (-k for example).
- Thanks Eric Siegerman (issue812) and Bruno Oliveira for the PR.
-
-- Summary bar now is colored yellow for warning
- situations such as: all tests either were skipped or xpass/xfailed,
- or no tests were run at all (related to issue500).
- Thanks Eric Siegerman.
-
-- New ``testpaths`` ini option: list of directories to search for tests
- when executing pytest from the root directory. This can be used
- to speed up test collection when a project has well specified directories
- for tests, being usually more practical than configuring norecursedirs for
- all directories that do not contain tests.
- Thanks to Adrian for idea (#694) and Bruno Oliveira for the PR.
-
-- fix issue713: JUnit XML reports for doctest failures.
- Thanks Punyashloka Biswal.
-
-- fix issue970: internal pytest warnings now appear as "pytest-warnings" in
- the terminal instead of "warnings", so it is clear for users that those
- warnings are from pytest and not from the builtin "warnings" module.
- Thanks Bruno Oliveira.
-
-- Include setup and teardown in junitxml test durations.
- Thanks Janne Vanhala.
-
-- fix issue735: assertion failures on debug versions of Python 3.4+
-
-- new option ``--import-mode`` to allow to change test module importing
- behaviour to append to sys.path instead of prepending. This better allows
- to run test modules against installated versions of a package even if the
- package under test has the same import root. In this example::
-
- testing/__init__.py
- testing/test_pkg_under_test.py
- pkg_under_test/
-
- the tests will run against the installed version
- of pkg_under_test when ``--import-mode=append`` is used whereas
- by default they would always pick up the local version. Thanks Holger Krekel.
-
-- pytester: add method ``TmpTestdir.delete_loaded_modules()``, and call it
- from ``inline_run()`` to allow temporary modules to be reloaded.
- Thanks Eduardo Schettino.
-
-- internally refactor pluginmanager API and code so that there
- is a clear distinction between a pytest-agnostic rather simple
- pluginmanager and the PytestPluginManager which adds a lot of
- behaviour, among it handling of the local conftest files.
- In terms of documented methods this is a backward compatible
- change but it might still break 3rd party plugins which relied on
- details like especially the pluginmanager.add_shutdown() API.
- Thanks Holger Krekel.
-
-- pluginmanagement: introduce ``pytest.hookimpl`` and
- ``pytest.hookspec`` decorators for setting impl/spec
- specific parameters. This substitutes the previous
- now deprecated use of ``pytest.mark`` which is meant to
- contain markers for test functions only.
-
-- write/refine docs for "writing plugins" which now have their
- own page and are separate from the "using/installing plugins`` page.
-
-- fix issue732: properly unregister plugins from any hook calling
- sites allowing to have temporary plugins during test execution.
-
-- deprecate and warn about ``__multicall__`` argument in hook
- implementations. Use the ``hookwrapper`` mechanism instead already
- introduced with pytest-2.7.
-
-- speed up pytest's own test suite considerably by using inprocess
- tests by default (testrun can be modified with --runpytest=subprocess
- to create subprocesses in many places instead). The main
- APIs to run pytest in a test is "runpytest()" or "runpytest_subprocess"
- and "runpytest_inprocess" if you need a particular way of running
- the test. In all cases you get back a RunResult but the inprocess
- one will also have a "reprec" attribute with the recorded events/reports.
-
-- fix monkeypatch.setattr("x.y", raising=False) to actually not raise
- if "y" is not a pre-existing attribute. Thanks Florian Bruhin.
-
-- fix issue741: make running output from testdir.run copy/pasteable
- Thanks Bruno Oliveira.
-
-- add a new ``--noconftest`` argument which ignores all ``conftest.py`` files.
-
-- add ``file`` and ``line`` attributes to JUnit-XML output.
-
-- fix issue890: changed extension of all documentation files from ``txt`` to
- ``rst``. Thanks to Abhijeet for the PR.
-
-- fix issue714: add ability to apply indirect=True parameter on particular argnames.
- Thanks Elizaveta239.
-
-- fix issue890: changed extension of all documentation files from ``txt`` to
- ``rst``. Thanks to Abhijeet for the PR.
-
-- fix issue957: "# doctest: SKIP" option will now register doctests as SKIPPED
- rather than PASSED.
- Thanks Thomas Grainger for the report and Bruno Oliveira for the PR.
-
-- issue951: add new record_xml_property fixture, that supports logging
- additional information on xml output. Thanks David Diaz for the PR.
-
-- issue949: paths after normal options (for example ``-s``, ``-v``, etc) are now
- properly used to discover ``rootdir`` and ``ini`` files.
- Thanks Peter Lauri for the report and Bruno Oliveira for the PR.
-
-2.7.3 (compared to 2.7.2)
-=============================
-
-- Allow 'dev', 'rc', or other non-integer version strings in ``importorskip``.
- Thanks to Eric Hunsberger for the PR.
-
-- fix issue856: consider --color parameter in all outputs (for example
- --fixtures). Thanks Barney Gale for the report and Bruno Oliveira for the PR.
-
-- fix issue855: passing str objects as ``plugins`` argument to pytest.main
- is now interpreted as a module name to be imported and registered as a
- plugin, instead of silently having no effect.
- Thanks xmo-odoo for the report and Bruno Oliveira for the PR.
-
-- fix issue744: fix for ast.Call changes in Python 3.5+. Thanks
- Guido van Rossum, Matthias Bussonnier, Stefan Zimmermann and
- Thomas Kluyver.
-
-- fix issue842: applying markers in classes no longer propagate this markers
- to superclasses which also have markers.
- Thanks xmo-odoo for the report and Bruno Oliveira for the PR.
-
-- preserve warning functions after call to pytest.deprecated_call. Thanks
- Pieter Mulder for PR.
-
-- fix issue854: autouse yield_fixtures defined as class members of
- unittest.TestCase subclasses now work as expected.
- Thannks xmo-odoo for the report and Bruno Oliveira for the PR.
-
-- fix issue833: --fixtures now shows all fixtures of collected test files, instead of just the
- fixtures declared on the first one.
- Thanks Florian Bruhin for reporting and Bruno Oliveira for the PR.
-
-- fix issue863: skipped tests now report the correct reason when a skip/xfail
- condition is met when using multiple markers.
- Thanks Raphael Pierzina for reporting and Bruno Oliveira for the PR.
-
-- optimized tmpdir fixture initialization, which should make test sessions
- faster (specially when using pytest-xdist). The only visible effect
- is that now pytest uses a subdirectory in the $TEMP directory for all
- directories created by this fixture (defaults to $TEMP/pytest-$USER).
- Thanks Bruno Oliveira for the PR.
-
-2.7.2 (compared to 2.7.1)
-=============================
-
-- fix issue767: pytest.raises value attribute does not contain the exception
- instance on Python 2.6. Thanks Eric Siegerman for providing the test
- case and Bruno Oliveira for PR.
-
-- Automatically create directory for junitxml and results log.
- Thanks Aron Curzon.
-
-- fix issue713: JUnit XML reports for doctest failures.
- Thanks Punyashloka Biswal.
-
-- fix issue735: assertion failures on debug versions of Python 3.4+
- Thanks Benjamin Peterson.
-
-- fix issue114: skipif marker reports to internal skipping plugin;
- Thanks Floris Bruynooghe for reporting and Bruno Oliveira for the PR.
-
-- fix issue748: unittest.SkipTest reports to internal pytest unittest plugin.
- Thanks Thomas De Schampheleire for reporting and Bruno Oliveira for the PR.
-
-- fix issue718: failed to create representation of sets containing unsortable
- elements in python 2. Thanks Edison Gustavo Muenz.
-
-- fix issue756, fix issue752 (and similar issues): depend on py-1.4.29
- which has a refined algorithm for traceback generation.
-
-
-2.7.1 (compared to 2.7.0)
-=============================
-
-- fix issue731: do not get confused by the braces which may be present
- and unbalanced in an object's repr while collapsing False
- explanations. Thanks Carl Meyer for the report and test case.
-
-- fix issue553: properly handling inspect.getsourcelines failures in
- FixtureLookupError which would lead to to an internal error,
- obfuscating the original problem. Thanks talljosh for initial
- diagnose/patch and Bruno Oliveira for final patch.
-
-- fix issue660: properly report scope-mismatch-access errors
- independently from ordering of fixture arguments. Also
- avoid the pytest internal traceback which does not provide
- information to the user. Thanks Holger Krekel.
-
-- streamlined and documented release process. Also all versions
- (in setup.py and documentation generation) are now read
- from _pytest/__init__.py. Thanks Holger Krekel.
-
-- fixed docs to remove the notion that yield-fixtures are experimental.
- They are here to stay :) Thanks Bruno Oliveira.
-
-- Support building wheels by using environment markers for the
- requirements. Thanks Ionel Maries Cristian.
-
-- fixed regression to 2.6.4 which surfaced e.g. in lost stdout capture printing
- when tests raised SystemExit. Thanks Holger Krekel.
-
-- reintroduced _pytest fixture of the pytester plugin which is used
- at least by pytest-xdist.
-
-2.7.0 (compared to 2.6.4)
-=============================
-
-- fix issue435: make reload() work when assert rewriting is active.
- Thanks Daniel Hahler.
-
-- fix issue616: conftest.py files and their contained fixutres are now
- properly considered for visibility, independently from the exact
- current working directory and test arguments that are used.
- Many thanks to Eric Siegerman and his PR235 which contains
- systematic tests for conftest visibility and now passes.
- This change also introduces the concept of a ``rootdir`` which
- is printed as a new pytest header and documented in the pytest
- customize web page.
-
-- change reporting of "diverted" tests, i.e. tests that are collected
- in one file but actually come from another (e.g. when tests in a test class
- come from a base class in a different file). We now show the nodeid
- and indicate via a postfix the other file.
-
-- add ability to set command line options by environment variable PYTEST_ADDOPTS.
-
-- added documentation on the new pytest-dev teams on bitbucket and
- github. See https://pytest.org/latest/contributing.html .
- Thanks to Anatoly for pushing and initial work on this.
-
-- fix issue650: new option ``--docttest-ignore-import-errors`` which
- will turn import errors in doctests into skips. Thanks Charles Cloud
- for the complete PR.
-
-- fix issue655: work around different ways that cause python2/3
- to leak sys.exc_info into fixtures/tests causing failures in 3rd party code
-
-- fix issue615: assertion re-writing did not correctly escape % signs
- when formatting boolean operations, which tripped over mixing
- booleans with modulo operators. Thanks to Tom Viner for the report,
- triaging and fix.
-
-- implement issue351: add ability to specify parametrize ids as a callable
- to generate custom test ids. Thanks Brianna Laugher for the idea and
- implementation.
-
-- introduce and document new hookwrapper mechanism useful for plugins
- which want to wrap the execution of certain hooks for their purposes.
- This supersedes the undocumented ``__multicall__`` protocol which
- pytest itself and some external plugins use. Note that pytest-2.8
- is scheduled to drop supporting the old ``__multicall__``
- and only support the hookwrapper protocol.
-
-- majorly speed up invocation of plugin hooks
-
-- use hookwrapper mechanism in builtin pytest plugins.
-
-- add a doctest ini option for doctest flags, thanks Holger Peters.
-
-- add note to docs that if you want to mark a parameter and the
- parameter is a callable, you also need to pass in a reason to disambiguate
- it from the "decorator" case. Thanks Tom Viner.
-
-- "python_classes" and "python_functions" options now support glob-patterns
- for test discovery, as discussed in issue600. Thanks Ldiary Translations.
-
-- allow to override parametrized fixtures with non-parametrized ones and vice versa (bubenkoff).
-
-- fix issue463: raise specific error for 'parameterize' misspelling (pfctdayelise).
-
-- On failure, the ``sys.last_value``, ``sys.last_type`` and
- ``sys.last_traceback`` are set, so that a user can inspect the error
- via postmortem debugging (almarklein).
-
-2.6.4
-=====
-
-- Improve assertion failure reporting on iterables, by using ndiff and
- pprint.
-
-- removed outdated japanese docs from source tree.
-
-- docs for "pytest_addhooks" hook. Thanks Bruno Oliveira.
-
-- updated plugin index docs. Thanks Bruno Oliveira.
-
-- fix issue557: with "-k" we only allow the old style "-" for negation
- at the beginning of strings and even that is deprecated. Use "not" instead.
- This should allow to pick parametrized tests where "-" appeared in the parameter.
-
-- fix issue604: Escape % character in the assertion message.
-
-- fix issue620: add explanation in the --genscript target about what
- the binary blob means. Thanks Dinu Gherman.
-
-- fix issue614: fixed pastebin support.
-
-
-- fix issue620: add explanation in the --genscript target about what
- the binary blob means. Thanks Dinu Gherman.
-
-- fix issue614: fixed pastebin support.
-
-2.6.3
-=====
-
-- fix issue575: xunit-xml was reporting collection errors as failures
- instead of errors, thanks Oleg Sinyavskiy.
-
-- fix issue582: fix setuptools example, thanks Laszlo Papp and Ronny
- Pfannschmidt.
-
-- Fix infinite recursion bug when pickling capture.EncodedFile, thanks
- Uwe Schmitt.
-
-- fix issue589: fix bad interaction with numpy and others when showing
- exceptions. Check for precise "maximum recursion depth exceed" exception
- instead of presuming any RuntimeError is that one (implemented in py
- dep). Thanks Charles Cloud for analysing the issue.
-
-- fix conftest related fixture visibility issue: when running with a
- CWD outside a test package pytest would get fixture discovery wrong.
- Thanks to Wolfgang Schnerring for figuring out a reproducable example.
-
-- Introduce pytest_enter_pdb hook (needed e.g. by pytest_timeout to cancel the
- timeout when interactively entering pdb). Thanks Wolfgang Schnerring.
-
-- check xfail/skip also with non-python function test items. Thanks
- Floris Bruynooghe.
-
-2.6.2
-=====
-
-- Added function pytest.freeze_includes(), which makes it easy to embed
- pytest into executables using tools like cx_freeze.
- See docs for examples and rationale. Thanks Bruno Oliveira.
-
-- Improve assertion rewriting cache invalidation precision.
-
-- fixed issue561: adapt autouse fixture example for python3.
-
-- fixed issue453: assertion rewriting issue with __repr__ containing
- "\n{", "\n}" and "\n~".
-
-- fix issue560: correctly display code if an "else:" or "finally:" is
- followed by statements on the same line.
-
-- Fix example in monkeypatch documentation, thanks t-8ch.
-
-- fix issue572: correct tmpdir doc example for python3.
-
-- Do not mark as universal wheel because Python 2.6 is different from
- other builds due to the extra argparse dependency. Fixes issue566.
- Thanks sontek.
-
-- Implement issue549: user-provided assertion messages now no longer
- replace the py.test introspection message but are shown in addition
- to them.
-
-2.6.1
-=====
-
-- No longer show line numbers in the --verbose output, the output is now
- purely the nodeid. The line number is still shown in failure reports.
- Thanks Floris Bruynooghe.
-
-- fix issue437 where assertion rewriting could cause pytest-xdist slaves
- to collect different tests. Thanks Bruno Oliveira.
-
-- fix issue555: add "errors" attribute to capture-streams to satisfy
- some distutils and possibly other code accessing sys.stdout.errors.
-
-- fix issue547 capsys/capfd also work when output capturing ("-s") is disabled.
-
-- address issue170: allow pytest.mark.xfail(...) to specify expected exceptions via
- an optional "raises=EXC" argument where EXC can be a single exception
- or a tuple of exception classes. Thanks David Mohr for the complete
- PR.
-
-- fix integration of pytest with unittest.mock.patch decorator when
- it uses the "new" argument. Thanks Nicolas Delaby for test and PR.
-
-- fix issue with detecting conftest files if the arguments contain
- "::" node id specifications (copy pasted from "-v" output)
-
-- fix issue544 by only removing "@NUM" at the end of "::" separated parts
- and if the part has an ".py" extension
-
-- don't use py.std import helper, rather import things directly.
- Thanks Bruno Oliveira.
-
-2.6
-===
-
-- Cache exceptions from fixtures according to their scope (issue 467).
-
-- fix issue537: Avoid importing old assertion reinterpretation code by default.
-
-- fix issue364: shorten and enhance tracebacks representation by default.
- The new "--tb=auto" option (default) will only display long tracebacks
- for the first and last entry. You can get the old behaviour of printing
- all entries as long entries with "--tb=long". Also short entries by
- default are now printed very similarly to "--tb=native" ones.
-
-- fix issue514: teach assertion reinterpretation about private class attributes
-
-- change -v output to include full node IDs of tests. Users can copy
- a node ID from a test run, including line number, and use it as a
- positional argument in order to run only a single test.
-
-- fix issue 475: fail early and comprehensible if calling
- pytest.raises with wrong exception type.
-
-- fix issue516: tell in getting-started about current dependencies.
-
-- cleanup setup.py a bit and specify supported versions. Thanks Jurko
- Gospodnetic for the PR.
-
-- change XPASS colour to yellow rather then red when tests are run
- with -v.
-
-- fix issue473: work around mock putting an unbound method into a class
- dict when double-patching.
-
-- fix issue498: if a fixture finalizer fails, make sure that
- the fixture is still invalidated.
-
-- fix issue453: the result of the pytest_assertrepr_compare hook now gets
- it's newlines escaped so that format_exception does not blow up.
-
-- internal new warning system: pytest will now produce warnings when
- it detects oddities in your test collection or execution.
- Warnings are ultimately sent to a new pytest_logwarning hook which is
- currently only implemented by the terminal plugin which displays
- warnings in the summary line and shows more details when -rw (report on
- warnings) is specified.
-
-- change skips into warnings for test classes with an __init__ and
- callables in test modules which look like a test but are not functions.
-
-- fix issue436: improved finding of initial conftest files from command
- line arguments by using the result of parse_known_args rather than
- the previous flaky heuristics. Thanks Marc Abramowitz for tests
- and initial fixing approaches in this area.
-
-- fix issue #479: properly handle nose/unittest(2) SkipTest exceptions
- during collection/loading of test modules. Thanks to Marc Schlaich
- for the complete PR.
-
-- fix issue490: include pytest_load_initial_conftests in documentation
- and improve docstring.
-
-- fix issue472: clarify that ``pytest.config.getvalue()`` cannot work
- if it's triggered ahead of command line parsing.
-
-- merge PR123: improved integration with mock.patch decorator on tests.
-
-- fix issue412: messing with stdout/stderr FD-level streams is now
- captured without crashes.
-
-- fix issue483: trial/py33 works now properly. Thanks Daniel Grana for PR.
-
-- improve example for pytest integration with "python setup.py test"
- which now has a generic "-a" or "--pytest-args" option where you
- can pass additional options as a quoted string. Thanks Trevor Bekolay.
-
-- simplified internal capturing mechanism and made it more robust
- against tests or setups changing FD1/FD2, also better integrated
- now with pytest.pdb() in single tests.
-
-- improvements to pytest's own test-suite leakage detection, courtesy of PRs
- from Marc Abramowitz
-
-- fix issue492: avoid leak in test_writeorg. Thanks Marc Abramowitz.
-
-- fix issue493: don't run tests in doc directory with ``python setup.py test``
- (use tox -e doctesting for that)
-
-- fix issue486: better reporting and handling of early conftest loading failures
-
-- some cleanup and simplification of internal conftest handling.
-
-- work a bit harder to break reference cycles when catching exceptions.
- Thanks Jurko Gospodnetic.
-
-- fix issue443: fix skip examples to use proper comparison. Thanks Alex
- Groenholm.
-
-- support nose-style ``__test__`` attribute on modules, classes and
- functions, including unittest-style Classes. If set to False, the
- test will not be collected.
-
-- fix issue512: show "<notset>" for arguments which might not be set
- in monkeypatch plugin. Improves output in documentation.
-
-
-2.5.2
-=====
-
-- fix issue409 -- better interoperate with cx_freeze by not
- trying to import from collections.abc which causes problems
- for py27/cx_freeze. Thanks Wolfgang L. for reporting and tracking it down.
-
-- fixed docs and code to use "pytest" instead of "py.test" almost everywhere.
- Thanks Jurko Gospodnetic for the complete PR.
-
-- fix issue425: mention at end of "py.test -h" that --markers
- and --fixtures work according to specified test path (or current dir)
-
-- fix issue413: exceptions with unicode attributes are now printed
- correctly also on python2 and with pytest-xdist runs. (the fix
- requires py-1.4.20)
-
-- copy, cleanup and integrate py.io capture
- from pylib 1.4.20.dev2 (rev 13d9af95547e)
-
-- address issue416: clarify docs as to conftest.py loading semantics
-
-- fix issue429: comparing byte strings with non-ascii chars in assert
- expressions now work better. Thanks Floris Bruynooghe.
-
-- make capfd/capsys.capture private, its unused and shouldnt be exposed
-
-
-2.5.1
-=====
-
-- merge new documentation styling PR from Tobias Bieniek.
-
-- fix issue403: allow parametrize of multiple same-name functions within
- a collection node. Thanks Andreas Kloeckner and Alex Gaynor for reporting
- and analysis.
-
-- Allow parameterized fixtures to specify the ID of the parameters by
- adding an ids argument to pytest.fixture() and pytest.yield_fixture().
- Thanks Floris Bruynooghe.
-
-- fix issue404 by always using the binary xml escape in the junitxml
- plugin. Thanks Ronny Pfannschmidt.
-
-- fix issue407: fix addoption docstring to point to argparse instead of
- optparse. Thanks Daniel D. Wright.
-
-
-
-2.5.0
-=====
-
-- dropped python2.5 from automated release testing of pytest itself
- which means it's probably going to break soon (but still works
- with this release we believe).
-
-- simplified and fixed implementation for calling finalizers when
- parametrized fixtures or function arguments are involved. finalization
- is now performed lazily at setup time instead of in the "teardown phase".
- While this might sound odd at first, it helps to ensure that we are
- correctly handling setup/teardown even in complex code. User-level code
- should not be affected unless it's implementing the pytest_runtest_teardown
- hook and expecting certain fixture instances are torn down within (very
- unlikely and would have been unreliable anyway).
-
-- PR90: add --color=yes|no|auto option to force terminal coloring
- mode ("auto" is default). Thanks Marc Abramowitz.
-
-- fix issue319 - correctly show unicode in assertion errors. Many
- thanks to Floris Bruynooghe for the complete PR. Also means
- we depend on py>=1.4.19 now.
-
-- fix issue396 - correctly sort and finalize class-scoped parametrized
- tests independently from number of methods on the class.
-
-- refix issue323 in a better way -- parametrization should now never
- cause Runtime Recursion errors because the underlying algorithm
- for re-ordering tests per-scope/per-fixture is not recursive
- anymore (it was tail-call recursive before which could lead
- to problems for more than >966 non-function scoped parameters).
-
-- fix issue290 - there is preliminary support now for parametrizing
- with repeated same values (sometimes useful to to test if calling
- a second time works as with the first time).
-
-- close issue240 - document precisely how pytest module importing
- works, discuss the two common test directory layouts, and how it
- interacts with PEP420-namespace packages.
-
-- fix issue246 fix finalizer order to be LIFO on independent fixtures
- depending on a parametrized higher-than-function scoped fixture.
- (was quite some effort so please bear with the complexity of this sentence :)
- Thanks Ralph Schmitt for the precise failure example.
-
-- fix issue244 by implementing special index for parameters to only use
- indices for paramentrized test ids
-
-- fix issue287 by running all finalizers but saving the exception
- from the first failing finalizer and re-raising it so teardown will
- still have failed. We reraise the first failing exception because
- it might be the cause for other finalizers to fail.
-
-- fix ordering when mock.patch or other standard decorator-wrappings
- are used with test methods. This fixues issue346 and should
- help with random "xdist" collection failures. Thanks to
- Ronny Pfannschmidt and Donald Stufft for helping to isolate it.
-
-- fix issue357 - special case "-k" expressions to allow for
- filtering with simple strings that are not valid python expressions.
- Examples: "-k 1.3" matches all tests parametrized with 1.3.
- "-k None" filters all tests that have "None" in their name
- and conversely "-k 'not None'".
- Previously these examples would raise syntax errors.
-
-- fix issue384 by removing the trial support code
- since the unittest compat enhancements allow
- trial to handle it on its own
-
-- don't hide an ImportError when importing a plugin produces one.
- fixes issue375.
-
-- fix issue275 - allow usefixtures and autouse fixtures
- for running doctest text files.
-
-- fix issue380 by making --resultlog only rely on longrepr instead
- of the "reprcrash" attribute which only exists sometimes.
-
-- address issue122: allow @pytest.fixture(params=iterator) by exploding
- into a list early on.
-
-- fix pexpect-3.0 compatibility for pytest's own tests.
- (fixes issue386)
-
-- allow nested parametrize-value markers, thanks James Lan for the PR.
-
-- fix unicode handling with new monkeypatch.setattr(import_path, value)
- API. Thanks Rob Dennis. Fixes issue371.
-
-- fix unicode handling with junitxml, fixes issue368.
-
-- In assertion rewriting mode on Python 2, fix the detection of coding
- cookies. See issue #330.
-
-- make "--runxfail" turn imperative pytest.xfail calls into no ops
- (it already did neutralize pytest.mark.xfail markers)
-
-- refine pytest / pkg_resources interactions: The AssertionRewritingHook
- PEP302 compliant loader now registers itself with setuptools/pkg_resources
- properly so that the pkg_resources.resource_stream method works properly.
- Fixes issue366. Thanks for the investigations and full PR to Jason R. Coombs.
-
-- pytestconfig fixture is now session-scoped as it is the same object during the
- whole test run. Fixes issue370.
-
-- avoid one surprising case of marker malfunction/confusion::
-
- @pytest.mark.some(lambda arg: ...)
- def test_function():
-
- would not work correctly because pytest assumes @pytest.mark.some
- gets a function to be decorated already. We now at least detect if this
- arg is an lambda and thus the example will work. Thanks Alex Gaynor
- for bringing it up.
-
-- xfail a test on pypy that checks wrong encoding/ascii (pypy does
- not error out). fixes issue385.
-
-- internally make varnames() deal with classes's __init__,
- although it's not needed by pytest itself atm. Also
- fix caching. Fixes issue376.
-
-- fix issue221 - handle importing of namespace-package with no
- __init__.py properly.
-
-- refactor internal FixtureRequest handling to avoid monkeypatching.
- One of the positive user-facing effects is that the "request" object
- can now be used in closures.
-
-- fixed version comparison in pytest.importskip(modname, minverstring)
-
-- fix issue377 by clarifying in the nose-compat docs that pytest
- does not duplicate the unittest-API into the "plain" namespace.
-
-- fix verbose reporting for @mock'd test functions
-
-2.4.2
-=====
-
-- on Windows require colorama and a newer py lib so that py.io.TerminalWriter()
- now uses colorama instead of its own ctypes hacks. (fixes issue365)
- thanks Paul Moore for bringing it up.
-
-- fix "-k" matching of tests where "repr" and "attr" and other names would
- cause wrong matches because of an internal implementation quirk
- (don't ask) which is now properly implemented. fixes issue345.
-
-- avoid tmpdir fixture to create too long filenames especially
- when parametrization is used (issue354)
-
-- fix pytest-pep8 and pytest-flakes / pytest interactions
- (collection names in mark plugin was assuming an item always
- has a function which is not true for those plugins etc.)
- Thanks Andi Zeidler.
-
-- introduce node.get_marker/node.add_marker API for plugins
- like pytest-pep8 and pytest-flakes to avoid the messy
- details of the node.keywords pseudo-dicts. Adapted
- docs.
-
-- remove attempt to "dup" stdout at startup as it's icky.
- the normal capturing should catch enough possibilities
- of tests messing up standard FDs.
-
-- add pluginmanager.do_configure(config) as a link to
- config.do_configure() for plugin-compatibility
-
-2.4.1
-=====
-
-- When using parser.addoption() unicode arguments to the
- "type" keyword should also be converted to the respective types.
- thanks Floris Bruynooghe, @dnozay. (fixes issue360 and issue362)
-
-- fix dotted filename completion when using argcomplete
- thanks Anthon van der Neuth. (fixes issue361)
-
-- fix regression when a 1-tuple ("arg",) is used for specifying
- parametrization (the values of the parametrization were passed
- nested in a tuple). Thanks Donald Stufft.
-
-- merge doc typo fixes, thanks Andy Dirnberger
-
-2.4
-===
-
-known incompatibilities:
-
-- if calling --genscript from python2.7 or above, you only get a
- standalone script which works on python2.7 or above. Use Python2.6
- to also get a python2.5 compatible version.
-
-- all xunit-style teardown methods (nose-style, pytest-style,
- unittest-style) will not be called if the corresponding setup method failed,
- see issue322 below.
-
-- the pytest_plugin_unregister hook wasn't ever properly called
- and there is no known implementation of the hook - so it got removed.
-
-- pytest.fixture-decorated functions cannot be generators (i.e. use
- yield) anymore. This change might be reversed in 2.4.1 if it causes
- unforeseen real-life issues. However, you can always write and return
- an inner function/generator and change the fixture consumer to iterate
- over the returned generator. This change was done in lieu of the new
- ``pytest.yield_fixture`` decorator, see below.
-
-new features:
-
-- experimentally introduce a new ``pytest.yield_fixture`` decorator
- which accepts exactly the same parameters as pytest.fixture but
- mandates a ``yield`` statement instead of a ``return statement`` from
- fixture functions. This allows direct integration with "with-style"
- context managers in fixture functions and generally avoids registering
- of finalization callbacks in favour of treating the "after-yield" as
- teardown code. Thanks Andreas Pelme, Vladimir Keleshev, Floris
- Bruynooghe, Ronny Pfannschmidt and many others for discussions.
-
-- allow boolean expression directly with skipif/xfail
- if a "reason" is also specified. Rework skipping documentation
- to recommend "condition as booleans" because it prevents surprises
- when importing markers between modules. Specifying conditions
- as strings will remain fully supported.
-
-- reporting: color the last line red or green depending if
- failures/errors occurred or everything passed. thanks Christian
- Theunert.
-
-- make "import pdb ; pdb.set_trace()" work natively wrt capturing (no
- "-s" needed anymore), making ``pytest.set_trace()`` a mere shortcut.
-
-- fix issue181: --pdb now also works on collect errors (and
- on internal errors) . This was implemented by a slight internal
- refactoring and the introduction of a new hook
- ``pytest_exception_interact`` hook (see next item).
-
-- fix issue341: introduce new experimental hook for IDEs/terminals to
- intercept debugging: ``pytest_exception_interact(node, call, report)``.
-
-- new monkeypatch.setattr() variant to provide a shorter
- invocation for patching out classes/functions from modules:
-
- monkeypatch.setattr("requests.get", myfunc)
-
- will replace the "get" function of the "requests" module with ``myfunc``.
-
-- fix issue322: tearDownClass is not run if setUpClass failed. Thanks
- Mathieu Agopian for the initial fix. Also make all of pytest/nose
- finalizer mimick the same generic behaviour: if a setupX exists and
- fails, don't run teardownX. This internally introduces a new method
- "node.addfinalizer()" helper which can only be called during the setup
- phase of a node.
-
-- simplify pytest.mark.parametrize() signature: allow to pass a
- CSV-separated string to specify argnames. For example:
- ``pytest.mark.parametrize("input,expected", [(1,2), (2,3)])``
- works as well as the previous:
- ``pytest.mark.parametrize(("input", "expected"), ...)``.
-
-- add support for setUpModule/tearDownModule detection, thanks Brian Okken.
-
-- integrate tab-completion on options through use of "argcomplete".
- Thanks Anthon van der Neut for the PR.
-
-- change option names to be hyphen-separated long options but keep the
- old spelling backward compatible. py.test -h will only show the
- hyphenated version, for example "--collect-only" but "--collectonly"
- will remain valid as well (for backward-compat reasons). Many thanks to
- Anthon van der Neut for the implementation and to Hynek Schlawack for
- pushing us.
-
-- fix issue 308 - allow to mark/xfail/skip individual parameter sets
- when parametrizing. Thanks Brianna Laugher.
-
-- call new experimental pytest_load_initial_conftests hook to allow
- 3rd party plugins to do something before a conftest is loaded.
-
-Bug fixes:
-
-- fix issue358 - capturing options are now parsed more properly
- by using a new parser.parse_known_args method.
-
-- pytest now uses argparse instead of optparse (thanks Anthon) which
- means that "argparse" is added as a dependency if installing into python2.6
- environments or below.
-
-- fix issue333: fix a case of bad unittest/pytest hook interaction.
-
-- PR27: correctly handle nose.SkipTest during collection. Thanks
- Antonio Cuni, Ronny Pfannschmidt.
-
-- fix issue355: junitxml puts name="pytest" attribute to testsuite tag.
-
-- fix issue336: autouse fixture in plugins should work again.
-
-- fix issue279: improve object comparisons on assertion failure
- for standard datatypes and recognise collections.abc. Thanks to
- Brianna Laugher and Mathieu Agopian.
-
-- fix issue317: assertion rewriter support for the is_package method
-
-- fix issue335: document py.code.ExceptionInfo() object returned
- from pytest.raises(), thanks Mathieu Agopian.
-
-- remove implicit distribute_setup support from setup.py.
-
-- fix issue305: ignore any problems when writing pyc files.
-
-- SO-17664702: call fixture finalizers even if the fixture function
- partially failed (finalizers would not always be called before)
-
-- fix issue320 - fix class scope for fixtures when mixed with
- module-level functions. Thanks Anatloy Bubenkoff.
-
-- you can specify "-q" or "-qq" to get different levels of "quieter"
- reporting (thanks Katarzyna Jachim)
-
-- fix issue300 - Fix order of conftest loading when starting py.test
- in a subdirectory.
-
-- fix issue323 - sorting of many module-scoped arg parametrizations
-
-- make sessionfinish hooks execute with the same cwd-context as at
- session start (helps fix plugin behaviour which write output files
- with relative path such as pytest-cov)
-
-- fix issue316 - properly reference collection hooks in docs
-
-- fix issue 306 - cleanup of -k/-m options to only match markers/test
- names/keywords respectively. Thanks Wouter van Ackooy.
-
-- improved doctest counting for doctests in python modules --
- files without any doctest items will not show up anymore
- and doctest examples are counted as separate test items.
- thanks Danilo Bellini.
-
-- fix issue245 by depending on the released py-1.4.14
- which fixes py.io.dupfile to work with files with no
- mode. Thanks Jason R. Coombs.
-
-- fix junitxml generation when test output contains control characters,
- addressing issue267, thanks Jaap Broekhuizen
-
-- fix issue338: honor --tb style for setup/teardown errors as well. Thanks Maho.
-
-- fix issue307 - use yaml.safe_load in example, thanks Mark Eichin.
-
-- better parametrize error messages, thanks Brianna Laugher
-
-- pytest_terminal_summary(terminalreporter) hooks can now use
- ".section(title)" and ".line(msg)" methods to print extra
- information at the end of a test run.
-
-2.3.5
-=====
-
-- fix issue169: respect --tb=style with setup/teardown errors as well.
-
-- never consider a fixture function for test function collection
-
-- allow re-running of test items / helps to fix pytest-reruntests plugin
- and also help to keep less fixture/resource references alive
-
-- put captured stdout/stderr into junitxml output even for passing tests
- (thanks Adam Goucher)
-
-- Issue 265 - integrate nose setup/teardown with setupstate
- so it doesnt try to teardown if it did not setup
-
-- issue 271 - dont write junitxml on slave nodes
-
-- Issue 274 - dont try to show full doctest example
- when doctest does not know the example location
-
-- issue 280 - disable assertion rewriting on buggy CPython 2.6.0
-
-- inject "getfixture()" helper to retrieve fixtures from doctests,
- thanks Andreas Zeidler
-
-- issue 259 - when assertion rewriting, be consistent with the default
- source encoding of ASCII on Python 2
-
-- issue 251 - report a skip instead of ignoring classes with init
-
-- issue250 unicode/str mixes in parametrization names and values now works
-
-- issue257, assertion-triggered compilation of source ending in a
- comment line doesn't blow up in python2.5 (fixed through py>=1.4.13.dev6)
-
-- fix --genscript option to generate standalone scripts that also
- work with python3.3 (importer ordering)
-
-- issue171 - in assertion rewriting, show the repr of some
- global variables
-
-- fix option help for "-k"
-
-- move long description of distribution into README.rst
-
-- improve docstring for metafunc.parametrize()
-
-- fix bug where using capsys with pytest.set_trace() in a test
- function would break when looking at capsys.readouterr()
-
-- allow to specify prefixes starting with "_" when
- customizing python_functions test discovery. (thanks Graham Horler)
-
-- improve PYTEST_DEBUG tracing output by puting
- extra data on a new lines with additional indent
-
-- ensure OutcomeExceptions like skip/fail have initialized exception attributes
-
-- issue 260 - don't use nose special setup on plain unittest cases
-
-- fix issue134 - print the collect errors that prevent running specified test items
-
-- fix issue266 - accept unicode in MarkEvaluator expressions
-
-2.3.4
-=====
-
-- yielded test functions will now have autouse-fixtures active but
- cannot accept fixtures as funcargs - it's anyway recommended to
- rather use the post-2.0 parametrize features instead of yield, see:
- http://pytest.org/latest/example/parametrize.html
-- fix autouse-issue where autouse-fixtures would not be discovered
- if defined in a a/conftest.py file and tests in a/tests/test_some.py
-- fix issue226 - LIFO ordering for fixture teardowns
-- fix issue224 - invocations with >256 char arguments now work
-- fix issue91 - add/discuss package/directory level setups in example
-- allow to dynamically define markers via
- item.keywords[...]=assignment integrating with "-m" option
-- make "-k" accept an expressions the same as with "-m" so that one
- can write: -k "name1 or name2" etc. This is a slight incompatibility
- if you used special syntax like "TestClass.test_method" which you now
- need to write as -k "TestClass and test_method" to match a certain
- method in a certain test class.
-
-2.3.3
-=====
-
-- fix issue214 - parse modules that contain special objects like e. g.
- flask's request object which blows up on getattr access if no request
- is active. thanks Thomas Waldmann.
-
-- fix issue213 - allow to parametrize with values like numpy arrays that
- do not support an __eq__ operator
-
-- fix issue215 - split test_python.org into multiple files
-
-- fix issue148 - @unittest.skip on classes is now recognized and avoids
- calling setUpClass/tearDownClass, thanks Pavel Repin
-
-- fix issue209 - reintroduce python2.4 support by depending on newer
- pylib which re-introduced statement-finding for pre-AST interpreters
-
-- nose support: only call setup if its a callable, thanks Andrew
- Taumoefolau
-
-- fix issue219 - add py2.4-3.3 classifiers to TROVE list
-
-- in tracebacks *,** arg values are now shown next to normal arguments
- (thanks Manuel Jacob)
-
-- fix issue217 - support mock.patch with pytest's fixtures - note that
- you need either mock-1.0.1 or the python3.3 builtin unittest.mock.
-
-- fix issue127 - improve documentation for pytest_addoption() and
- add a ``config.getoption(name)`` helper function for consistency.
-
-2.3.2
-=====
-
-- fix issue208 and fix issue29 use new py version to avoid long pauses
- when printing tracebacks in long modules
-
-- fix issue205 - conftests in subdirs customizing
- pytest_pycollect_makemodule and pytest_pycollect_makeitem
- now work properly
-
-- fix teardown-ordering for parametrized setups
-
-- fix issue127 - better documentation for pytest_addoption
- and related objects.
-
-- fix unittest behaviour: TestCase.runtest only called if there are
- test methods defined
-
-- improve trial support: don't collect its empty
- unittest.TestCase.runTest() method
-
-- "python setup.py test" now works with pytest itself
-
-- fix/improve internal/packaging related bits:
-
- - exception message check of test_nose.py now passes on python33 as well
-
- - issue206 - fix test_assertrewrite.py to work when a global
- PYTHONDONTWRITEBYTECODE=1 is present
-
- - add tox.ini to pytest distribution so that ignore-dirs and others config
- bits are properly distributed for maintainers who run pytest-own tests
-
-2.3.1
-=====
-
-- fix issue202 - fix regression: using "self" from fixture functions now
- works as expected (it's the same "self" instance that a test method
- which uses the fixture sees)
-
-- skip pexpect using tests (test_pdb.py mostly) on freebsd* systems
- due to pexpect not supporting it properly (hanging)
-
-- link to web pages from --markers output which provides help for
- pytest.mark.* usage.
-
-2.3.0
-=====
-
-- fix issue202 - better automatic names for parametrized test functions
-- fix issue139 - introduce @pytest.fixture which allows direct scoping
- and parametrization of funcarg factories.
-- fix issue198 - conftest fixtures were not found on windows32 in some
- circumstances with nested directory structures due to path manipulation issues
-- fix issue193 skip test functions with were parametrized with empty
- parameter sets
-- fix python3.3 compat, mostly reporting bits that previously depended
- on dict ordering
-- introduce re-ordering of tests by resource and parametrization setup
- which takes precedence to the usual file-ordering
-- fix issue185 monkeypatching time.time does not cause pytest to fail
-- fix issue172 duplicate call of pytest.fixture decoratored setup_module
- functions
-- fix junitxml=path construction so that if tests change the
- current working directory and the path is a relative path
- it is constructed correctly from the original current working dir.
-- fix "python setup.py test" example to cause a proper "errno" return
-- fix issue165 - fix broken doc links and mention stackoverflow for FAQ
-- catch unicode-issues when writing failure representations
- to terminal to prevent the whole session from crashing
-- fix xfail/skip confusion: a skip-mark or an imperative pytest.skip
- will now take precedence before xfail-markers because we
- can't determine xfail/xpass status in case of a skip. see also:
- http://stackoverflow.com/questions/11105828/in-py-test-when-i-explicitly-skip-a-test-that-is-marked-as-xfail-how-can-i-get
-
-- always report installed 3rd party plugins in the header of a test run
-
-- fix issue160: a failing setup of an xfail-marked tests should
- be reported as xfail (not xpass)
-
-- fix issue128: show captured output when capsys/capfd are used
-
-- fix issue179: propperly show the dependency chain of factories
-
-- pluginmanager.register(...) now raises ValueError if the
- plugin has been already registered or the name is taken
-
-- fix issue159: improve http://pytest.org/latest/faq.html
- especially with respect to the "magic" history, also mention
- pytest-django, trial and unittest integration.
-
-- make request.keywords and node.keywords writable. All descendant
- collection nodes will see keyword values. Keywords are dictionaries
- containing markers and other info.
-
-- fix issue 178: xml binary escapes are now wrapped in py.xml.raw
-
-- fix issue 176: correctly catch the builtin AssertionError
- even when we replaced AssertionError with a subclass on the
- python level
-
-- factory discovery no longer fails with magic global callables
- that provide no sane __code__ object (mock.call for example)
-
-- fix issue 182: testdir.inprocess_run now considers passed plugins
-
-- fix issue 188: ensure sys.exc_info is clear on python2
- before calling into a test
-
-- fix issue 191: add unittest TestCase runTest method support
-- fix issue 156: monkeypatch correctly handles class level descriptors
-
-- reporting refinements:
-
- - pytest_report_header now receives a "startdir" so that
- you can use startdir.bestrelpath(yourpath) to show
- nice relative path
-
- - allow plugins to implement both pytest_report_header and
- pytest_sessionstart (sessionstart is invoked first).
-
- - don't show deselected reason line if there is none
-
- - py.test -vv will show all of assert comparisations instead of truncating
-
-2.2.4
-=====
-
-- fix error message for rewritten assertions involving the % operator
-- fix issue 126: correctly match all invalid xml characters for junitxml
- binary escape
-- fix issue with unittest: now @unittest.expectedFailure markers should
- be processed correctly (you can also use @pytest.mark markers)
-- document integration with the extended distribute/setuptools test commands
-- fix issue 140: propperly get the real functions
- of bound classmethods for setup/teardown_class
-- fix issue #141: switch from the deceased paste.pocoo.org to bpaste.net
-- fix issue #143: call unconfigure/sessionfinish always when
- configure/sessionstart where called
-- fix issue #144: better mangle test ids to junitxml classnames
-- upgrade distribute_setup.py to 0.6.27
-
-2.2.3
-=====
-
-- fix uploaded package to only include neccesary files
-
-2.2.2
-=====
-
-- fix issue101: wrong args to unittest.TestCase test function now
- produce better output
-- fix issue102: report more useful errors and hints for when a
- test directory was renamed and some pyc/__pycache__ remain
-- fix issue106: allow parametrize to be applied multiple times
- e.g. from module, class and at function level.
-- fix issue107: actually perform session scope finalization
-- don't check in parametrize if indirect parameters are funcarg names
-- add chdir method to monkeypatch funcarg
-- fix crash resulting from calling monkeypatch undo a second time
-- fix issue115: make --collectonly robust against early failure
- (missing files/directories)
-- "-qq --collectonly" now shows only files and the number of tests in them
-- "-q --collectonly" now shows test ids
-- allow adding of attributes to test reports such that it also works
- with distributed testing (no upgrade of pytest-xdist needed)
-
-2.2.1
-=====
-
-- fix issue99 (in pytest and py) internallerrors with resultlog now
- produce better output - fixed by normalizing pytest_internalerror
- input arguments.
-- fix issue97 / traceback issues (in pytest and py) improve traceback output
- in conjunction with jinja2 and cython which hack tracebacks
-- fix issue93 (in pytest and pytest-xdist) avoid "delayed teardowns":
- the final test in a test node will now run its teardown directly
- instead of waiting for the end of the session. Thanks Dave Hunt for
- the good reporting and feedback. The pytest_runtest_protocol as well
- as the pytest_runtest_teardown hooks now have "nextitem" available
- which will be None indicating the end of the test run.
-- fix collection crash due to unknown-source collected items, thanks
- to Ralf Schmitt (fixed by depending on a more recent pylib)
-
-2.2.0
-=====
-
-- fix issue90: introduce eager tearing down of test items so that
- 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.
-- 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
- of Test scenarios and the new parametrize function and decorator.
-- introduce registration for "pytest.mark.*" helpers via ini-files
- or through plugin hooks. Also introduce a "--strict" option which
- will treat unregistered markers as errors
- allowing to avoid typos and maintain a well described set of markers
- for your test suite. See exaples at http://pytest.org/latest/mark.html
- and its links.
-- issue50: introduce "-m marker" option to select tests based on markers
- (this is a stricter and more predictable version of '-k' in that "-m"
- only matches complete markers and has more obvious rules for and/or
- semantics.
-- new feature to help optimizing the speed of your tests:
- --durations=N option for displaying N slowest test calls
- and setup/teardown methods.
-- fix issue87: --pastebin now works with python3
-- fix issue89: --pdb with unexpected exceptions in doctest work more sensibly
-- fix and cleanup pytest's own test suite to not leak FDs
-- fix issue83: link to generated funcarg list
-- fix issue74: pyarg module names are now checked against imp.find_module false positives
-- fix compatibility with twisted/trial-11.1.0 use cases
-- simplify Node.listchain
-- simplify junitxml output code by relying on py.xml
-- add support for skip properties on unittest classes and functions
-
-2.1.3
-=====
-
-- fix issue79: assertion rewriting failed on some comparisons in boolops
-- correctly handle zero length arguments (a la pytest '')
-- fix issue67 / junitxml now contains correct test durations, thanks ronny
-- fix issue75 / skipping test failure on jython
-- fix issue77 / Allow assertrepr_compare hook to apply to a subset of tests
-
-2.1.2
-=====
-
-- fix assertion rewriting on files with windows newlines on some Python versions
-- refine test discovery by package/module name (--pyargs), thanks Florian Mayer
-- fix issue69 / assertion rewriting fixed on some boolean operations
-- fix issue68 / packages now work with assertion rewriting
-- fix issue66: use different assertion rewriting caches when the -O option is passed
-- don't try assertion rewriting on Jython, use reinterp
-
-2.1.1
-=====
-
-- fix issue64 / pytest.set_trace now works within pytest_generate_tests hooks
-- fix issue60 / fix error conditions involving the creation of __pycache__
-- fix issue63 / assertion rewriting on inserts involving strings containing '%'
-- fix assertion rewriting on calls with a ** arg
-- don't cache rewritten modules if bytecode generation is disabled
-- fix assertion rewriting in read-only directories
-- fix issue59: provide system-out/err tags for junitxml output
-- fix issue61: assertion rewriting on boolean operations with 3 or more operands
-- you can now build a man page with "cd doc ; make man"
-
-2.1.0
-=====
-
-- fix issue53 call nosestyle setup functions with correct ordering
-- fix issue58 and issue59: new assertion code fixes
-- merge Benjamin's assertionrewrite branch: now assertions
- for test modules on python 2.6 and above are done by rewriting
- the AST and saving the pyc file before the test module is imported.
- see doc/assert.txt for more info.
-- fix issue43: improve doctests with better traceback reporting on
- unexpected exceptions
-- fix issue47: timing output in junitxml for test cases is now correct
-- fix issue48: typo in MarkInfo repr leading to exception
-- fix issue49: avoid confusing error when initizaliation partially fails
-- fix issue44: env/username expansion for junitxml file path
-- show releaselevel information in test runs for pypy
-- reworked doc pages for better navigation and PDF generation
-- report KeyboardInterrupt even if interrupted during session startup
-- fix issue 35 - provide PDF doc version and download link from index page
-
-2.0.3
-=====
-
-- fix issue38: nicer tracebacks on calls to hooks, particularly early
- configure/sessionstart ones
-
-- fix missing skip reason/meta information in junitxml files, reported
- via http://lists.idyll.org/pipermail/testing-in-python/2011-March/003928.html
-
-- fix issue34: avoid collection failure with "test" prefixed classes
- deriving from object.
-
-- don't require zlib (and other libs) for genscript plugin without
- --genscript actually being used.
-
-- speed up skips (by not doing a full traceback represenation
- internally)
-
-- fix issue37: avoid invalid characters in junitxml's output
-
-2.0.2
-=====
-
-- tackle issue32 - speed up test runs of very quick test functions
- by reducing the relative overhead
-
-- fix issue30 - extended xfail/skipif handling and improved reporting.
- If you have a syntax error in your skip/xfail
- expressions you now get nice error reports.
-
- Also you can now access module globals from xfail/skipif
- expressions so that this for example works now::
-
- import pytest
- import mymodule
- @pytest.mark.skipif("mymodule.__version__[0] == "1")
- def test_function():
- pass
-
- This will not run the test function if the module's version string
- does not start with a "1". Note that specifying a string instead
- of a boolean expressions allows py.test to report meaningful information
- when summarizing a test run as to what conditions lead to skipping
- (or xfail-ing) tests.
-
-- fix issue28 - setup_method and pytest_generate_tests work together
- The setup_method fixture method now gets called also for
- test function invocations generated from the pytest_generate_tests
- hook.
-
-- fix issue27 - collectonly and keyword-selection (-k) now work together
- Also, if you do "py.test --collectonly -q" you now get a flat list
- of test ids that you can use to paste to the py.test commandline
- in order to execute a particular test.
-
-- fix issue25 avoid reported problems with --pdb and python3.2/encodings output
-
-- fix issue23 - tmpdir argument now works on Python3.2 and WindowsXP
- Starting with Python3.2 os.symlink may be supported. By requiring
- a newer py lib version the py.path.local() implementation acknowledges
- this.
-
-- fixed typos in the docs (thanks Victor Garcia, Brianna Laugher) and particular
- thanks to Laura Creighton who also revieved parts of the documentation.
-
-- fix slighly wrong output of verbose progress reporting for classes
- (thanks Amaury)
-
-- more precise (avoiding of) deprecation warnings for node.Class|Function accesses
-
-- avoid std unittest assertion helper code in tracebacks (thanks Ronny)
-
-2.0.1
-=====
-
-- refine and unify initial capturing so that it works nicely
- even if the logging module is used on an early-loaded conftest.py
- file or plugin.
-- allow to omit "()" in test ids to allow for uniform test ids
- as produced by Alfredo's nice pytest.vim plugin.
-- fix issue12 - show plugin versions with "--version" and
- "--traceconfig" and also document how to add extra information
- to reporting test header
-- fix issue17 (import-* reporting issue on python3) by
- requiring py>1.4.0 (1.4.1 is going to include it)
-- fix issue10 (numpy arrays truth checking) by refining
- assertion interpretation in py lib
-- fix issue15: make nose compatibility tests compatible
- with python3 (now that nose-1.0 supports python3)
-- remove somewhat surprising "same-conftest" detection because
- it ignores conftest.py when they appear in several subdirs.
-- improve assertions ("not in"), thanks Floris Bruynooghe
-- improve behaviour/warnings when running on top of "python -OO"
- (assertions and docstrings are turned off, leading to potential
- false positives)
-- introduce a pytest_cmdline_processargs(args) hook
- to allow dynamic computation of command line arguments.
- This fixes a regression because py.test prior to 2.0
- allowed to set command line options from conftest.py
- files which so far pytest-2.0 only allowed from ini-files now.
-- fix issue7: assert failures in doctest modules.
- unexpected failures in doctests will not generally
- show nicer, i.e. within the doctest failing context.
-- fix issue9: setup/teardown functions for an xfail-marked
- test will report as xfail if they fail but report as normally
- passing (not xpassing) if they succeed. This only is true
- for "direct" setup/teardown invocations because teardown_class/
- teardown_module cannot closely relate to a single test.
-- fix issue14: no logging errors at process exit
-- refinements to "collecting" output on non-ttys
-- refine internal plugin registration and --traceconfig output
-- introduce a mechanism to prevent/unregister plugins from the
- command line, see http://pytest.org/plugins.html#cmdunregister
-- activate resultlog plugin by default
-- fix regression wrt yielded tests which due to the
- collection-before-running semantics were not
- setup as with pytest 1.3.4. Note, however, that
- the recommended and much cleaner way to do test
- parametraization remains the "pytest_generate_tests"
- mechanism, see the docs.
-
-2.0.0
-=====
-
-- pytest-2.0 is now its own package and depends on pylib-2.0
-- new ability: python -m pytest / python -m pytest.main ability
-- new python invcation: pytest.main(args, plugins) to load
- some custom plugins early.
-- try harder to run unittest test suites in a more compatible manner
- by deferring setup/teardown semantics to the unittest package.
- also work harder to run twisted/trial and Django tests which
- should now basically work by default.
-- introduce a new way to set config options via ini-style files,
- by default setup.cfg and tox.ini files are searched. The old
- ways (certain environment variables, dynamic conftest.py reading
- is removed).
-- add a new "-q" option which decreases verbosity and prints a more
- nose/unittest-style "dot" output.
-- fix issue135 - marks now work with unittest test cases as well
-- fix issue126 - introduce py.test.set_trace() to trace execution via
- PDB during the running of tests even if capturing is ongoing.
-- fix issue123 - new "python -m py.test" invocation for py.test
- (requires Python 2.5 or above)
-- fix issue124 - make reporting more resilient against tests opening
- files on filedescriptor 1 (stdout).
-- fix issue109 - sibling conftest.py files will not be loaded.
- (and Directory collectors cannot be customized anymore from a Directory's
- conftest.py - this needs to happen at least one level up).
-- introduce (customizable) assertion failure representations and enhance
- output on assertion failures for comparisons and other cases (Floris Bruynooghe)
-- nose-plugin: pass through type-signature failures in setup/teardown
- functions instead of not calling them (Ed Singleton)
-- remove py.test.collect.Directory (follows from a major refactoring
- and simplification of the collection process)
-- majorly reduce py.test core code, shift function/python testing to own plugin
-- fix issue88 (finding custom test nodes from command line arg)
-- refine 'tmpdir' creation, will now create basenames better associated
- with test names (thanks Ronny)
-- "xpass" (unexpected pass) tests don't cause exitcode!=0
-- fix issue131 / issue60 - importing doctests in __init__ files used as namespace packages
-- fix issue93 stdout/stderr is captured while importing conftest.py
-- fix bug: unittest collected functions now also can have "pytestmark"
- applied at class/module level
-- add ability to use "class" level for cached_setup helper
-- fix strangeness: mark.* objects are now immutable, create new instances
-
-1.3.4
-=====
-
-- fix issue111: improve install documentation for windows
-- fix issue119: fix custom collectability of __init__.py as a module
-- fix issue116: --doctestmodules work with __init__.py files as well
-- fix issue115: unify internal exception passthrough/catching/GeneratorExit
-- fix issue118: new --tb=native for presenting cpython-standard exceptions
-
-1.3.3
-=====
-
-- fix issue113: assertion representation problem with triple-quoted strings
- (and possibly other cases)
-- make conftest loading detect that a conftest file with the same
- content was already loaded, avoids surprises in nested directory structures
- which can be produced e.g. by Hudson. It probably removes the need to use
- --confcutdir in most cases.
-- fix terminal coloring for win32
- (thanks Michael Foord for reporting)
-- fix weirdness: make terminal width detection work on stdout instead of stdin
- (thanks Armin Ronacher for reporting)
-- remove trailing whitespace in all py/text distribution files
-
-1.3.2
-=====
-
-**New features**
-
-- fix issue103: introduce py.test.raises as context manager, examples::
-
- with py.test.raises(ZeroDivisionError):
- x = 0
- 1 / x
-
- with py.test.raises(RuntimeError) as excinfo:
- call_something()
-
- # you may do extra checks on excinfo.value|type|traceback here
-
- (thanks Ronny Pfannschmidt)
-
-- Funcarg factories can now dynamically apply a marker to a
- test invocation. This is for example useful if a factory
- provides parameters to a test which are expected-to-fail::
-
- def pytest_funcarg__arg(request):
- request.applymarker(py.test.mark.xfail(reason="flaky config"))
- ...
-
- def test_function(arg):
- ...
-
-- improved error reporting on collection and import errors. This makes
- use of a more general mechanism, namely that for custom test item/collect
- nodes ``node.repr_failure(excinfo)`` is now uniformly called so that you can
- override it to return a string error representation of your choice
- which is going to be reported as a (red) string.
-
-- introduce '--junitprefix=STR' option to prepend a prefix
- to all reports in the junitxml file.
-
-**Bug fixes**
-
-- make tests and the ``pytest_recwarn`` plugin in particular fully compatible
- to Python2.7 (if you use the ``recwarn`` funcarg warnings will be enabled so that
- you can properly check for their existence in a cross-python manner).
-- refine --pdb: ignore xfailed tests, unify its TB-reporting and
- don't display failures again at the end.
-- fix assertion interpretation with the ** operator (thanks Benjamin Peterson)
-- fix issue105 assignment on the same line as a failing assertion (thanks Benjamin Peterson)
-- fix issue104 proper escaping for test names in junitxml plugin (thanks anonymous)
-- fix issue57 -f|--looponfail to work with xpassing tests (thanks Ronny)
-- fix issue92 collectonly reporter and --pastebin (thanks Benjamin Peterson)
-- fix py.code.compile(source) to generate unique filenames
-- fix assertion re-interp problems on PyPy, by defering code
- compilation to the (overridable) Frame.eval class. (thanks Amaury Forgeot)
-- fix py.path.local.pyimport() to work with directories
-- streamline py.path.local.mkdtemp implementation and usage
-- don't print empty lines when showing junitxml-filename
-- add optional boolean ignore_errors parameter to py.path.local.remove
-- fix terminal writing on win32/python2.4
-- py.process.cmdexec() now tries harder to return properly encoded unicode objects
- on all python versions
-- install plain py.test/py.which scripts also for Jython, this helps to
- get canonical script paths in virtualenv situations
-- make path.bestrelpath(path) return ".", note that when calling
- X.bestrelpath the assumption is that X is a directory.
-- make initial conftest discovery ignore "--" prefixed arguments
-- fix resultlog plugin when used in an multicpu/multihost xdist situation
- (thanks Jakub Gustak)
-- perform distributed testing related reporting in the xdist-plugin
- rather than having dist-related code in the generic py.test
- distribution
-- fix homedir detection on Windows
-- ship distribute_setup.py version 0.6.13
-
-1.3.1
-=====
-
-**New features**
-
-- issue91: introduce new py.test.xfail(reason) helper
- to imperatively mark a test as expected to fail. Can
- be used from within setup and test functions. This is
- useful especially for parametrized tests when certain
- configurations are expected-to-fail. In this case the
- declarative approach with the @py.test.mark.xfail cannot
- be used as it would mark all configurations as xfail.
-
-- issue102: introduce new --maxfail=NUM option to stop
- test runs after NUM failures. This is a generalization
- of the '-x' or '--exitfirst' option which is now equivalent
- to '--maxfail=1'. Both '-x' and '--maxfail' will
- now also print a line near the end indicating the Interruption.
-
-- issue89: allow py.test.mark decorators to be used on classes
- (class decorators were introduced with python2.6) and
- also allow to have multiple markers applied at class/module level
- by specifying a list.
-
-- improve and refine letter reporting in the progress bar:
- . pass
- f failed test
- s skipped tests (reminder: use for dependency/platform mismatch only)
- x xfailed test (test that was expected to fail)
- X xpassed test (test that was expected to fail but passed)
-
- You can use any combination of 'fsxX' with the '-r' extended
- reporting option. The xfail/xpass results will show up as
- skipped tests in the junitxml output - which also fixes
- issue99.
-
-- make py.test.cmdline.main() return the exitstatus instead of raising
- SystemExit and also allow it to be called multiple times. This of
- course requires that your application and tests are properly teared
- down and don't have global state.
-
-**Bug Fixes**
-
-- improved traceback presentation:
- - improved and unified reporting for "--tb=short" option
- - Errors during test module imports are much shorter, (using --tb=short style)
- - raises shows shorter more relevant tracebacks
- - --fulltrace now more systematically makes traces longer / inhibits cutting
-
-- improve support for raises and other dynamically compiled code by
- manipulating python's linecache.cache instead of the previous
- rather hacky way of creating custom code objects. This makes
- it seemlessly work on Jython and PyPy where it previously didn't.
-
-- fix issue96: make capturing more resilient against Control-C
- interruptions (involved somewhat substantial refactoring
- to the underlying capturing functionality to avoid race
- conditions).
-
-- fix chaining of conditional skipif/xfail decorators - so it works now
- as expected to use multiple @py.test.mark.skipif(condition) decorators,
- including specific reporting which of the conditions lead to skipping.
-
-- fix issue95: late-import zlib so that it's not required
- for general py.test startup.
-
-- fix issue94: make reporting more robust against bogus source code
- (and internally be more careful when presenting unexpected byte sequences)
-
-
-1.3.0
-=====
-
-- deprecate --report option in favour of a new shorter and easier to
- remember -r option: it takes a string argument consisting of any
- combination of 'xfsX' characters. They relate to the single chars
- you see during the dotted progress printing and will print an extra line
- per test at the end of the test run. This extra line indicates the exact
- position or test ID that you directly paste to the py.test cmdline in order
- to re-run a particular test.
-
-- allow external plugins to register new hooks via the new
- pytest_addhooks(pluginmanager) hook. The new release of
- the pytest-xdist plugin for distributed and looponfailing
- testing requires this feature.
-
-- add a new pytest_ignore_collect(path, config) hook to allow projects and
- plugins to define exclusion behaviour for their directory structure -
- for example you may define in a conftest.py this method::
-
- def pytest_ignore_collect(path):
- return path.check(link=1)
-
- to prevent even a collection try of any tests in symlinked dirs.
-
-- new pytest_pycollect_makemodule(path, parent) hook for
- allowing customization of the Module collection object for a
- matching test module.
-
-- extend and refine xfail mechanism:
- ``@py.test.mark.xfail(run=False)`` do not run the decorated test
- ``@py.test.mark.xfail(reason="...")`` prints the reason string in xfail summaries
- specifiying ``--runxfail`` on command line virtually ignores xfail markers
-
-- expose (previously internal) commonly useful methods:
- py.io.get_terminal_with() -> return terminal width
- py.io.ansi_print(...) -> print colored/bold text on linux/win32
- py.io.saferepr(obj) -> return limited representation string
-
-- expose test outcome related exceptions as py.test.skip.Exception,
- py.test.raises.Exception etc., useful mostly for plugins
- doing special outcome interpretation/tweaking
-
-- (issue85) fix junitxml plugin to handle tests with non-ascii output
-
-- fix/refine python3 compatibility (thanks Benjamin Peterson)
-
-- fixes for making the jython/win32 combination work, note however:
- jython2.5.1/win32 does not provide a command line launcher, see
- http://bugs.jython.org/issue1491 . See pylib install documentation
- for how to work around.
-
-- fixes for handling of unicode exception values and unprintable objects
-
-- (issue87) fix unboundlocal error in assertionold code
-
-- (issue86) improve documentation for looponfailing
-
-- refine IO capturing: stdin-redirect pseudo-file now has a NOP close() method
-
-- ship distribute_setup.py version 0.6.10
-
-- added links to the new capturelog and coverage plugins
-
-
-1.2.0
-=====
-
-- refined usage and options for "py.cleanup"::
-
- py.cleanup # remove "*.pyc" and "*$py.class" (jython) files
- py.cleanup -e .swp -e .cache # also remove files with these extensions
- py.cleanup -s # remove "build" and "dist" directory next to setup.py files
- py.cleanup -d # also remove empty directories
- py.cleanup -a # synonym for "-s -d -e 'pip-log.txt'"
- py.cleanup -n # dry run, only show what would be removed
-
-- add a new option "py.test --funcargs" which shows available funcargs
- and their help strings (docstrings on their respective factory function)
- for a given test path
-
-- display a short and concise traceback if a funcarg lookup fails
-
-- early-load "conftest.py" files in non-dot first-level sub directories.
- allows to conveniently keep and access test-related options in a ``test``
- subdir and still add command line options.
-
-- fix issue67: new super-short traceback-printing option: "--tb=line" will print a single line for each failing (python) test indicating its filename, lineno and the failure value
-
-- fix issue78: always call python-level teardown functions even if the
- according setup failed. This includes refinements for calling setup_module/class functions
- which will now only be called once instead of the previous behaviour where they'd be called
- multiple times if they raise an exception (including a Skipped exception). Any exception
- will be re-corded and associated with all tests in the according module/class scope.
-
-- fix issue63: assume <40 columns to be a bogus terminal width, default to 80
-
-- fix pdb debugging to be in the correct frame on raises-related errors
-
-- update apipkg.py to fix an issue where recursive imports might
- unnecessarily break importing
-
-- fix plugin links
-
-1.1.1
-=====
-
-- moved dist/looponfailing from py.test core into a new
- separately released pytest-xdist plugin.
-
-- new junitxml plugin: --junitxml=path will generate a junit style xml file
- which is processable e.g. by the Hudson CI system.
-
-- new option: --genscript=path will generate a standalone py.test script
- which will not need any libraries installed. thanks to Ralf Schmitt.
-
-- new option: --ignore will prevent specified path from collection.
- Can be specified multiple times.
-
-- new option: --confcutdir=dir will make py.test only consider conftest
- files that are relative to the specified dir.
-
-- new funcarg: "pytestconfig" is the pytest config object for access
- to command line args and can now be easily used in a test.
-
-- install ``py.test`` and ``py.which`` with a ``-$VERSION`` suffix to
- disambiguate between Python3, python2.X, Jython and PyPy installed versions.
-
-- new "pytestconfig" funcarg allows access to test config object
-
-- new "pytest_report_header" hook can return additional lines
- to be displayed at the header of a test run.
-
-- (experimental) allow "py.test path::name1::name2::..." for pointing
- to a test within a test collection directly. This might eventually
- evolve as a full substitute to "-k" specifications.
-
-- streamlined plugin loading: order is now as documented in
- customize.html: setuptools, ENV, commandline, conftest.
- also setuptools entry point names are turned to canonical namees ("pytest_*")
-
-- automatically skip tests that need 'capfd' but have no os.dup
-
-- allow pytest_generate_tests to be defined in classes as well
-
-- deprecate usage of 'disabled' attribute in favour of pytestmark
-- deprecate definition of Directory, Module, Class and Function nodes
- in conftest.py files. Use pytest collect hooks instead.
-
-- collection/item node specific runtest/collect hooks are only called exactly
- on matching conftest.py files, i.e. ones which are exactly below
- the filesystem path of an item
-
-- change: the first pytest_collect_directory hook to return something
- will now prevent further hooks to be called.
-
-- change: figleaf plugin now requires --figleaf to run. Also
- change its long command line options to be a bit shorter (see py.test -h).
-
-- change: pytest doctest plugin is now enabled by default and has a
- new option --doctest-glob to set a pattern for file matches.
-
-- change: remove internal py._* helper vars, only keep py._pydir
-
-- robustify capturing to survive if custom pytest_runtest_setup
- code failed and prevented the capturing setup code from running.
-
-- make py.test.* helpers provided by default plugins visible early -
- works transparently both for pydoc and for interactive sessions
- which will regularly see e.g. py.test.mark and py.test.importorskip.
-
-- simplify internal plugin manager machinery
-- simplify internal collection tree by introducing a RootCollector node
-
-- fix assert reinterpreation that sees a call containing "keyword=..."
-
-- fix issue66: invoke pytest_sessionstart and pytest_sessionfinish
- hooks on slaves during dist-testing, report module/session teardown
- hooks correctly.
-
-- fix issue65: properly handle dist-testing if no
- execnet/py lib installed remotely.
-
-- skip some install-tests if no execnet is available
-
-- fix docs, fix internal bin/ script generation
-
-
-1.1.0
-=====
-
-- introduce automatic plugin registration via 'pytest11'
- entrypoints via setuptools' pkg_resources.iter_entry_points
-
-- fix py.test dist-testing to work with execnet >= 1.0.0b4
-
-- re-introduce py.test.cmdline.main() for better backward compatibility
-
-- svn paths: fix a bug with path.check(versioned=True) for svn paths,
- allow '%' in svn paths, make svnwc.update() default to interactive mode
- like in 1.0.x and add svnwc.update(interactive=False) to inhibit interaction.
-
-- refine distributed tarball to contain test and no pyc files
-
-- try harder to have deprecation warnings for py.compat.* accesses
- report a correct location
-
-1.0.3
-=====
-
-* adjust and improve docs
-
-* remove py.rest tool and internal namespace - it was
- never really advertised and can still be used with
- the old release if needed. If there is interest
- it could be revived into its own tool i guess.
-
-* fix issue48 and issue59: raise an Error if the module
- from an imported test file does not seem to come from
- the filepath - avoids "same-name" confusion that has
- been reported repeatedly
-
-* merged Ronny's nose-compatibility hacks: now
- nose-style setup_module() and setup() functions are
- supported
-
-* introduce generalized py.test.mark function marking
-
-* reshuffle / refine command line grouping
-
-* deprecate parser.addgroup in favour of getgroup which creates option group
-
-* add --report command line option that allows to control showing of skipped/xfailed sections
-
-* generalized skipping: a new way to mark python functions with skipif or xfail
- at function, class and modules level based on platform or sys-module attributes.
-
-* extend py.test.mark decorator to allow for positional args
-
-* introduce and test "py.cleanup -d" to remove empty directories
-
-* fix issue #59 - robustify unittest test collection
-
-* make bpython/help interaction work by adding an __all__ attribute
- to ApiModule, cleanup initpkg
-
-* use MIT license for pylib, add some contributors
-
-* remove py.execnet code and substitute all usages with 'execnet' proper
-
-* fix issue50 - cached_setup now caches more to expectations
- for test functions with multiple arguments.
-
-* merge Jarko's fixes, issue #45 and #46
-
-* add the ability to specify a path for py.lookup to search in
-
-* fix a funcarg cached_setup bug probably only occuring
- in distributed testing and "module" scope with teardown.
-
-* many fixes and changes for making the code base python3 compatible,
- many thanks to Benjamin Peterson for helping with this.
-
-* consolidate builtins implementation to be compatible with >=2.3,
- add helpers to ease keeping 2 and 3k compatible code
-
-* deprecate py.compat.doctest|subprocess|textwrap|optparse
-
-* deprecate py.magic.autopath, remove py/magic directory
-
-* move pytest assertion handling to py/code and a pytest_assertion
- plugin, add "--no-assert" option, deprecate py.magic namespaces
- in favour of (less) py.code ones.
-
-* consolidate and cleanup py/code classes and files
-
-* cleanup py/misc, move tests to bin-for-dist
-
-* introduce delattr/delitem/delenv methods to py.test's monkeypatch funcarg
-
-* consolidate py.log implementation, remove old approach.
-
-* introduce py.io.TextIO and py.io.BytesIO for distinguishing between
- text/unicode and byte-streams (uses underlying standard lib io.*
- if available)
-
-* make py.unittest_convert helper script available which converts "unittest.py"
- style files into the simpler assert/direct-test-classes py.test/nosetests
- style. The script was written by Laura Creighton.
-
-* simplified internal localpath implementation
-
-1.0.2
-=====
-
-* fixing packaging issues, triggered by fedora redhat packaging,
- also added doc, examples and contrib dirs to the tarball.
-
-* added a documentation link to the new django plugin.
-
-1.0.1
-=====
-
-* added a 'pytest_nose' plugin which handles nose.SkipTest,
- nose-style function/method/generator setup/teardown and
- tries to report functions correctly.
-
-* capturing of unicode writes or encoded strings to sys.stdout/err
- work better, also terminalwriting was adapted and somewhat
- unified between windows and linux.
-
-* improved documentation layout and content a lot
-
-* added a "--help-config" option to show conftest.py / ENV-var names for
- all longopt cmdline options, and some special conftest.py variables.
- renamed 'conf_capture' conftest setting to 'option_capture' accordingly.
-
-* fix issue #27: better reporting on non-collectable items given on commandline
- (e.g. pyc files)
-
-* fix issue #33: added --version flag (thanks Benjamin Peterson)
-
-* fix issue #32: adding support for "incomplete" paths to wcpath.status()
-
-* "Test" prefixed classes are *not* collected by default anymore if they
- have an __init__ method
-
-* monkeypatch setenv() now accepts a "prepend" parameter
-
-* improved reporting of collection error tracebacks
-
-* simplified multicall mechanism and plugin architecture,
- renamed some internal methods and argnames
-
-1.0.0
-=====
-
-* more terse reporting try to show filesystem path relatively to current dir
-* improve xfail output a bit
-
-1.0.0b9
-=======
-
-* cleanly handle and report final teardown of test setup
-
-* fix svn-1.6 compat issue with py.path.svnwc().versioned()
- (thanks Wouter Vanden Hove)
-
-* setup/teardown or collection problems now show as ERRORs
- or with big "E"'s in the progress lines. they are reported
- and counted separately.
-
-* dist-testing: properly handle test items that get locally
- collected but cannot be collected on the remote side - often
- due to platform/dependency reasons
-
-* simplified py.test.mark API - see keyword plugin documentation
-
-* integrate better with logging: capturing now by default captures
- test functions and their immediate setup/teardown in a single stream
-
-* capsys and capfd funcargs now have a readouterr() and a close() method
- (underlyingly py.io.StdCapture/FD objects are used which grew a
- readouterr() method as well to return snapshots of captured out/err)
-
-* make assert-reinterpretation work better with comparisons not
- returning bools (reported with numpy from thanks maciej fijalkowski)
-
-* reworked per-test output capturing into the pytest_iocapture.py plugin
- and thus removed capturing code from config object
-
-* item.repr_failure(excinfo) instead of item.repr_failure(excinfo, outerr)
-
-
-1.0.0b8
-=======
-
-* pytest_unittest-plugin is now enabled by default
-
-* introduced pytest_keyboardinterrupt hook and
- refined pytest_sessionfinish hooked, added tests.
-
-* workaround a buggy logging module interaction ("closing already closed
- files"). Thanks to Sridhar Ratnakumar for triggering.
-
-* if plugins use "py.test.importorskip" for importing
- a dependency only a warning will be issued instead
- of exiting the testing process.
-
-* many improvements to docs:
- - refined funcargs doc , use the term "factory" instead of "provider"
- - added a new talk/tutorial doc page
- - better download page
- - better plugin docstrings
- - added new plugins page and automatic doc generation script
-
-* fixed teardown problem related to partially failing funcarg setups
- (thanks MrTopf for reporting), "pytest_runtest_teardown" is now
- always invoked even if the "pytest_runtest_setup" failed.
-
-* tweaked doctest output for docstrings in py modules,
- thanks Radomir.
-
-1.0.0b7
-=======
-
-* renamed py.test.xfail back to py.test.mark.xfail to avoid
- two ways to decorate for xfail
-
-* re-added py.test.mark decorator for setting keywords on functions
- (it was actually documented so removing it was not nice)
-
-* remove scope-argument from request.addfinalizer() because
- request.cached_setup has the scope arg. TOOWTDI.
-
-* perform setup finalization before reporting failures
-
-* apply modified patches from Andreas Kloeckner to allow
- test functions to have no func_code (#22) and to make
- "-k" and function keywords work (#20)
-
-* apply patch from Daniel Peolzleithner (issue #23)
-
-* resolve issue #18, multiprocessing.Manager() and
- redirection clash
-
-* make __name__ == "__channelexec__" for remote_exec code
-
-1.0.0b3
-=======
-
-* plugin classes are removed: one now defines
- hooks directly in conftest.py or global pytest_*.py
- files.
-
-* added new pytest_namespace(config) hook that allows
- to inject helpers directly to the py.test.* namespace.
-
-* documented and refined many hooks
-
-* added new style of generative tests via
- pytest_generate_tests hook that integrates
- well with function arguments.
-
-
-1.0.0b1
-=======
-
-* introduced new "funcarg" setup method,
- see doc/test/funcarg.txt
-
-* introduced plugin architecture and many
- new py.test plugins, see
- doc/test/plugins.txt
-
-* teardown_method is now guaranteed to get
- called after a test method has run.
-
-* new method: py.test.importorskip(mod,minversion)
- will either import or call py.test.skip()
-
-* completely revised internal py.test architecture
-
-* new py.process.ForkedFunc object allowing to
- fork execution of a function to a sub process
- and getting a result back.
-
-XXX lots of things missing here XXX
-
-0.9.2
-=====
-
-* refined installation and metadata, created new setup.py,
- now based on setuptools/ez_setup (thanks to Ralf Schmitt
- for his support).
-
-* improved the way of making py.* scripts available in
- windows environments, they are now added to the
- Scripts directory as ".cmd" files.
-
-* py.path.svnwc.status() now is more complete and
- uses xml output from the 'svn' command if available
- (Guido Wesdorp)
-
-* fix for py.path.svn* to work with svn 1.5
- (Chris Lamb)
-
-* fix path.relto(otherpath) method on windows to
- use normcase for checking if a path is relative.
-
-* py.test's traceback is better parseable from editors
- (follows the filenames:LINENO: MSG convention)
- (thanks to Osmo Salomaa)
-
-* fix to javascript-generation, "py.test --runbrowser"
- should work more reliably now
-
-* removed previously accidentally added
- py.test.broken and py.test.notimplemented helpers.
-
-* there now is a py.__version__ attribute
-
-0.9.1
-=====
-
-This is a fairly complete list of v0.9.1, which can
-serve as a reference for developers.
-
-* allowing + signs in py.path.svn urls [39106]
-* fixed support for Failed exceptions without excinfo in py.test [39340]
-* added support for killing processes for Windows (as well as platforms that
- support os.kill) in py.misc.killproc [39655]
-* added setup/teardown for generative tests to py.test [40702]
-* added detection of FAILED TO LOAD MODULE to py.test [40703, 40738, 40739]
-* fixed problem with calling .remove() on wcpaths of non-versioned files in
- py.path [44248]
-* fixed some import and inheritance issues in py.test [41480, 44648, 44655]
-* fail to run greenlet tests when pypy is available, but without stackless
- [45294]
-* small fixes in rsession tests [45295]
-* fixed issue with 2.5 type representations in py.test [45483, 45484]
-* made that internal reporting issues displaying is done atomically in py.test
- [45518]
-* made that non-existing files are igored by the py.lookup script [45519]
-* improved exception name creation in py.test [45535]
-* made that less threads are used in execnet [merge in 45539]
-* removed lock required for atomical reporting issue displaying in py.test
- [45545]
-* removed globals from execnet [45541, 45547]
-* refactored cleanup mechanics, made that setDaemon is set to 1 to make atexit
- get called in 2.5 (py.execnet) [45548]
-* fixed bug in joining threads in py.execnet's servemain [45549]
-* refactored py.test.rsession tests to not rely on exact output format anymore
- [45646]
-* using repr() on test outcome [45647]
-* added 'Reason' classes for py.test.skip() [45648, 45649]
-* killed some unnecessary sanity check in py.test.collect [45655]
-* avoid using os.tmpfile() in py.io.fdcapture because on Windows it's only
- usable by Administrators [45901]
-* added support for locking and non-recursive commits to py.path.svnwc [45994]
-* locking files in py.execnet to prevent CPython from segfaulting [46010]
-* added export() method to py.path.svnurl
-* fixed -d -x in py.test [47277]
-* fixed argument concatenation problem in py.path.svnwc [49423]
-* restore py.test behaviour that it exits with code 1 when there are failures
- [49974]
-* don't fail on html files that don't have an accompanying .txt file [50606]
-* fixed 'utestconvert.py < input' [50645]
-* small fix for code indentation in py.code.source [50755]
-* fix _docgen.py documentation building [51285]
-* improved checks for source representation of code blocks in py.test [51292]
-* added support for passing authentication to py.path.svn* objects [52000,
- 52001]
-* removed sorted() call for py.apigen tests in favour of [].sort() to support
- Python 2.3 [52481]
+++ /dev/null
-============================
-Contribution getting started
-============================
-
-Contributions are highly welcomed and appreciated. Every little help counts,
-so do not hesitate!
-
-.. contents:: Contribution links
- :depth: 2
-
-
-.. _submitfeedback:
-
-Feature requests and feedback
------------------------------
-
-Do you like pytest? Share some love on Twitter or in your blog posts!
-
-We'd also like to hear about your propositions and suggestions. Feel free to
-`submit them as issues <https://github.com/pytest-dev/pytest/issues>`_ and:
-
-* Explain in detail how they should work.
-* Keep the scope as narrow as possible. This will make it easier to implement.
-
-
-.. _reportbugs:
-
-Report bugs
------------
-
-Report bugs for pytest in the `issue tracker <https://github.com/pytest-dev/pytest/issues>`_.
-
-If you are reporting a bug, please include:
-
-* Your operating system name and version.
-* Any details about your local setup that might be helpful in troubleshooting,
- specifically Python interpreter version,
- installed libraries and pytest version.
-* Detailed steps to reproduce the bug.
-
-If you can write a demonstration test that currently fails but should pass (xfail),
-that is a very useful commit to make as well, even if you can't find how
-to fix the bug yet.
-
-
-.. _fixbugs:
-
-Fix bugs
---------
-
-Look through the GitHub issues for bugs. Here is sample filter you can use:
-https://github.com/pytest-dev/pytest/labels/bug
-
-:ref:`Talk <contact>` to developers to find out how you can fix specific bugs.
-
-Don't forget to check the issue trackers of your favourite plugins, too!
-
-.. _writeplugins:
-
-Implement features
-------------------
-
-Look through the GitHub issues for enhancements. Here is sample filter you
-can use:
-https://github.com/pytest-dev/pytest/labels/enhancement
-
-:ref:`Talk <contact>` to developers to find out how you can implement specific
-features.
-
-Write documentation
--------------------
-
-pytest could always use more documentation. What exactly is needed?
-
-* More complementary documentation. Have you perhaps found something unclear?
-* Documentation translations. We currently have only English.
-* Docstrings. There can never be too many of them.
-* Blog posts, articles and such -- they're all very appreciated.
-
-You can also edit documentation files directly in the Github web interface
-without needing to make a fork and local copy. This can be convenient for
-small fixes.
-
-
-.. _submitplugin:
-
-Submitting Plugins to pytest-dev
---------------------------------
-
-Pytest development of the core, some plugins and support code happens
-in repositories living under the ``pytest-dev`` organisations:
-
-- `pytest-dev on GitHub <https://github.com/pytest-dev>`_
-
-- `pytest-dev on Bitbucket <https://bitbucket.org/pytest-dev>`_
-
-All pytest-dev Contributors team members have write access to all contained
-repositories. pytest core and plugins are generally developed
-using `pull requests`_ to respective repositories.
-
-The objectives of the ``pytest-dev`` organisation are:
-
-* Having a central location for popular pytest plugins
-* Sharing some of the maintenance responsibility (in case a maintainer no longer whishes to maintain a plugin)
-
-You can submit your plugin by subscribing to the `pytest-dev mail list
-<https://mail.python.org/mailman/listinfo/pytest-dev>`_ and writing a
-mail pointing to your existing pytest plugin repository which must have
-the following:
-
-- PyPI presence with a ``setup.py`` that contains a license, ``pytest-``
- prefixed name, version number, authors, short and long description.
-
-- a ``tox.ini`` for running tests using `tox <http://tox.testrun.org>`_.
-
-- a ``README.txt`` describing how to use the plugin and on which
- platforms it runs.
-
-- a ``LICENSE.txt`` file or equivalent containing the licensing
- information, with matching info in ``setup.py``.
-
-- an issue tracker for bug reports and enhancement requests.
-
-If no contributor strongly objects and two agree, the repository can then be
-transferred to the ``pytest-dev`` organisation.
-
-Here's a rundown of how a repository transfer usually proceeds
-(using a repository named ``joedoe/pytest-xyz`` as example):
-
-* One of the ``pytest-dev`` administrators creates:
-
- - ``pytest-xyz-admin`` team, with full administration rights to
- ``pytest-dev/pytest-xyz``.
- - ``pytest-xyz-developers`` team, with write access to
- ``pytest-dev/pytest-xyz``.
-
-* ``joedoe`` is invited to the ``pytest-xyz-admin`` team;
-
-* After accepting the invitation, ``joedoe`` transfers the repository from its
- original location to ``pytest-dev/pytest-xyz`` (A nice feature is that GitHub handles URL redirection from
- the old to the new location automatically).
-
-* ``joedoe`` is free to add any other collaborators to the
- ``pytest-xyz-admin`` or ``pytest-xyz-developers`` team as desired.
-
-The ``pytest-dev/Contributors`` team has write access to all projects, and
-every project administrator is in it. We recommend that each plugin has at least three
-people who have the right to release to PyPI.
-
-Repository owners can be assured that no ``pytest-dev`` administrator will ever make
-releases of your repository or take ownership in any way, except in rare cases
-where someone becomes unresponsive after months of contact attempts.
-As stated, the objective is to share maintenance and avoid "plugin-abandon".
-
-
-.. _`pull requests`:
-.. _pull-requests:
-
-Preparing Pull Requests on GitHub
----------------------------------
-
-There's an excellent tutorial on how Pull Requests work in the
-`GitHub Help Center <https://help.github.com/articles/using-pull-requests/>`_
-
-
-.. note::
- What is a "pull request"? It informs project's core developers about the
- changes you want to review and merge. Pull requests are stored on
- `GitHub servers <https://github.com/pytest-dev/pytest/pulls>`_.
- Once you send pull request, we can discuss it's potential modifications and
- even add more commits to it later on.
-
-There's an excellent tutorial on how Pull Requests work in the
-`GitHub Help Center <https://help.github.com/articles/using-pull-requests/>`_,
-but here is a simple overview:
-
-#. Fork the
- `pytest GitHub repository <https://github.com/pytest-dev/pytest>`__. It's
- fine to use ``pytest`` as your fork repository name because it will live
- under your user.
-
-#. Clone your fork locally using `git <https://git-scm.com/>`_ and create a branch::
-
- $ git clone git@github.com:YOUR_GITHUB_USERNAME/pytest.git
- $ cd pytest
- # now, to fix a bug create your own branch off "master":
-
- $ git checkout -b your-bugfix-branch-name master
-
- # or to instead add a feature create your own branch off "features":
-
- $ git checkout -b your-feature-branch-name features
-
- Given we have "major.minor.micro" version numbers, bugfixes will usually
- be released in micro releases whereas features will be released in
- minor releases and incompatible changes in major releases.
-
- If you need some help with Git, follow this quick start
- guide: https://git.wiki.kernel.org/index.php/QuickStart
-
-#. Install tox
-
- Tox is used to run all the tests and will automatically setup virtualenvs
- to run the tests in.
- (will implicitly use http://www.virtualenv.org/en/latest/)::
-
- $ pip install tox
-
-#. Run all the tests
-
- You need to have Python 2.7 and 3.5 available in your system. Now
- running tests is as simple as issuing this command::
-
- $ python runtox.py -e linting,py27,py35
-
- This command will run tests via the "tox" tool against Python 2.7 and 3.5
- and also perform "lint" coding-style checks. ``runtox.py`` is
- a thin wrapper around ``tox`` which installs from a development package
- index where newer (not yet released to pypi) versions of dependencies
- (especially ``py``) might be present.
-
-#. You can now edit your local working copy.
-
- You can now make the changes you want and run the tests again as necessary.
-
- To run tests on py27 and pass options to pytest (e.g. enter pdb on failure)
- to pytest you can do::
-
- $ python runtox.py -e py27 -- --pdb
-
- or to only run tests in a particular test module on py35::
-
- $ python runtox.py -e py35 -- testing/test_config.py
-
-#. Commit and push once your tests pass and you are happy with your change(s)::
-
- $ git commit -a -m "<commit message>"
- $ git push -u
-
- Make sure you add a CHANGELOG message, and add yourself to AUTHORS. If you
- are unsure about either of these steps, submit your pull request and we'll
- help you fix it up.
-
-#. Finally, submit a pull request through the GitHub website using this data::
-
- head-fork: YOUR_GITHUB_USERNAME/pytest
- compare: your-branch-name
-
- base-fork: pytest-dev/pytest
- base: master # if it's a bugfix
- base: feature # if it's a feature
-
-
+++ /dev/null
-How to release pytest
---------------------------------------------
-
-Note: this assumes you have already registered on pypi.
-
-0. create the branch release-VERSION
- use features as base for minor/major releases
- and master as base for bugfix releases
-
-1. Bump version numbers in _pytest/__init__.py (setup.py reads it)
-
-2. Check and finalize CHANGELOG
-
-3. Write doc/en/announce/release-VERSION.txt and include
- it in doc/en/announce/index.txt::
-
- git log 2.8.2..HEAD --format='%aN' | sort -u # lists the names of authors involved
-
-4. Use devpi for uploading a release tarball to a staging area::
-
- devpi use https://devpi.net/USER/dev
- devpi upload --formats sdist,bdist_wheel
-
-5. Run from multiple machines::
-
- devpi use https://devpi.net/USER/dev
- devpi test pytest==VERSION
-
-6. Check that tests pass for relevant combinations with::
-
- devpi list pytest
-
- or look at failures with "devpi list -f pytest".
-
-7. Regenerate the docs examples using tox, and check for regressions::
-
- tox -e regen
- git diff
-
-
-8. Build the docs, you need a virtualenv with py and sphinx
- installed::
-
- cd doc/en
- make html
-
- Commit any changes before tagging the release.
-
-9. Tag the release::
-
- git tag VERSION
- git push
-
-10. Upload the docs using doc/en/Makefile::
-
- cd doc/en
- make install # or "installall" if you have LaTeX installed for PDF
-
- This requires ssh-login permission on pytest.org because it uses
- rsync.
- Note that the ``install`` target of ``doc/en/Makefile`` defines where the
- rsync goes to, typically to the "latest" section of pytest.org.
-
- If you are making a minor release (e.g. 5.4), you also need to manually
- create a symlink for "latest"::
-
- ssh pytest-dev@pytest.org
- ln -s 5.4 latest
-
- Browse to pytest.org to verify.
-
-11. Publish to pypi::
-
- devpi push pytest-VERSION pypi:NAME
-
- where NAME is the name of pypi.python.org as configured in your ``~/.pypirc``
- file `for devpi <http://doc.devpi.net/latest/quickstart-releaseprocess.html?highlight=pypirc#devpi-push-releasing-to-an-external-index>`_.
-
-
-12. Send release announcement to mailing lists:
-
- - pytest-dev
- - testing-in-python
- - python-announce-list@python.org
-
-
-13. **after the release** Bump the version number in ``_pytest/__init__.py``,
- to the next Minor release version (i.e. if you released ``pytest-2.8.0``,
- set it to ``pytest-2.9.0.dev1``).
-
-14. merge the actual release into the master branch and do a pull request against it
-15. merge from master to features
+++ /dev/null
-
-
-recorder = monkeypatch.function(".......")
--------------------------------------------------------------
-tags: nice feature
-
-Like monkeypatch.replace but sets a mock-like call recorder:
-
- recorder = monkeypatch.function("os.path.abspath")
- recorder.set_return("/hello")
- os.path.abspath("hello")
- call, = recorder.calls
- assert call.args.path == "hello"
- assert call.returned == "/hello"
- ...
-
-Unlike mock, "args.path" acts on the parsed auto-spec'ed ``os.path.abspath``
-so it's independent from if the client side called "os.path.abspath(path=...)"
-or "os.path.abspath('positional')".
-
-
-refine parametrize API
--------------------------------------------------------------
-tags: critical feature
-
-extend metafunc.parametrize to directly support indirection, example:
-
- def setupdb(request, config):
- # setup "resource" based on test request and the values passed
- # in to parametrize. setupfunc is called for each such value.
- # you may use request.addfinalizer() or request.cached_setup ...
- return dynamic_setup_database(val)
-
- @pytest.mark.parametrize("db", ["pg", "mysql"], setupfunc=setupdb)
- def test_heavy_functional_test(db):
- ...
-
-There would be no need to write or explain funcarg factories and
-their special __ syntax.
-
-The examples and improvements should also show how to put the parametrize
-decorator to a class, to a module or even to a directory. For the directory
-part a conftest.py content like this::
-
- pytestmark = [
- @pytest.mark.parametrize_setup("db", ...),
- ]
-
-probably makes sense in order to keep the declarative nature. This mirrors
-the marker-mechanism with respect to a test module but puts it to a directory
-scale.
-
-When doing larger scoped parametrization it probably becomes necessary
-to allow parametrization to be ignored if the according parameter is not
-used (currently any parametrized argument that is not present in a function will cause a ValueError). Example:
-
- @pytest.mark.parametrize("db", ..., mustmatch=False)
-
-means to not raise an error but simply ignore the parametrization
-if the signature of a decorated function does not match. XXX is it
-not sufficient to always allow non-matches?
-
-
-allow parametrized attributes on classes
---------------------------------------------------
-
-tags: wish 2.4
-
-example:
-
- @pytest.mark.parametrize_attr("db", setupfunc, [1,2,3], scope="class")
- @pytest.mark.parametrize_attr("tmp", setupfunc, scope="...")
- class TestMe:
- def test_hello(self):
- access self.db ...
-
-this would run the test_hello() function three times with three
-different values for self.db. This could also work with unittest/nose
-style tests, i.e. it leverages existing test suites without needing
-to rewrite them. Together with the previously mentioned setup_test()
-maybe the setupfunc could be omitted?
-
-optimizations
----------------------------------------------------------------
-tags: 2.4 core
-
-- look at ihook optimization such that all lookups for
- hooks relating to the same fspath are cached.
-
-fix start/finish partial finailization problem
----------------------------------------------------------------
-tags: bug core
-
-if a configure/runtest_setup/sessionstart/... hook invocation partially
-fails the sessionfinishes is not called. Each hook implementation
-should better be repsonsible for registering a cleanup/finalizer
-appropriately to avoid this issue. Moreover/Alternatively, we could
-record which implementations of a hook succeeded and only call their
-teardown.
-
-
-relax requirement to have tests/testing contain an __init__
-----------------------------------------------------------------
-tags: feature
-bb: http://bitbucket.org/hpk42/py-trunk/issue/64
-
-A local test run of a "tests" directory may work
-but a remote one fail because the tests directory
-does not contain an "__init__.py". Either give
-an error or make it work without the __init__.py
-i.e. port the nose-logic of unloading a test module.
-
-customize test function collection
--------------------------------------------------------
-tags: feature
-
-- introduce pytest.mark.nocollect for not considering a function for
- test collection at all. maybe also introduce a pytest.mark.test to
- explicitly mark a function to become a tested one. Lookup JUnit ways
- of tagging tests.
-
-introduce pytest.mark.importorskip
--------------------------------------------------------
-tags: feature
-
-in addition to the imperative pytest.importorskip also introduce
-a pytest.mark.importorskip so that the test count is more correct.
-
-
-introduce pytest.mark.platform
--------------------------------------------------------
-tags: feature
-
-Introduce nice-to-spell platform-skipping, examples:
-
- @pytest.mark.platform("python3")
- @pytest.mark.platform("not python3")
- @pytest.mark.platform("win32 and not python3")
- @pytest.mark.platform("darwin")
- @pytest.mark.platform("not (jython and win32)")
- @pytest.mark.platform("not (jython and win32)", xfail=True)
-
-etc. Idea is to allow Python expressions which can operate
-on common spellings for operating systems and python
-interpreter versions.
-
-pytest.mark.xfail signature change
--------------------------------------------------------
-tags: feature
-
-change to pytest.mark.xfail(reason, (optional)condition)
-to better implement the word meaning. It also signals
-better that we always have some kind of an implementation
-reason that can be formualated.
-Compatibility? how to introduce a new name/keep compat?
-
-allow to non-intrusively apply skipfs/xfail/marks
----------------------------------------------------
-tags: feature
-
-use case: mark a module or directory structures
-to be skipped on certain platforms (i.e. no import
-attempt will be made).
-
-consider introducing a hook/mechanism that allows to apply marks
-from conftests or plugins. (See extended parametrization)
-
-
-explicit referencing of conftest.py files
------------------------------------------
-tags: feature
-
-allow to name conftest.py files (in sub directories) that should
-be imported early, as to include command line options.
-
-improve central pytest ini file
--------------------------------
-tags: feature
-
-introduce more declarative configuration options:
-- (to-be-collected test directories)
-- required plugins
-- test func/class/file matching patterns
-- skip/xfail (non-intrusive)
-- pytest.ini and tox.ini and setup.cfg configuration in the same file
-
-new documentation
-----------------------------------
-tags: feature
-
-- logo pytest
-- examples for unittest or functional testing
-- resource management for functional testing
-- patterns: page object
-
-have imported module mismatch honour relative paths
---------------------------------------------------------
-tags: bug
-
-With 1.1.1 pytest fails at least on windows if an import
-is relative and compared against an absolute conftest.py
-path. Normalize.
-
-consider globals: pytest.ensuretemp and config
---------------------------------------------------------------
-tags: experimental-wish
-
-consider deprecating pytest.ensuretemp and pytest.config
-to further reduce pytest globality. Also consider
-having pytest.config and ensuretemp coming from
-a plugin rather than being there from the start.
-
-
-consider pytest_addsyspath hook
------------------------------------------
-tags: wish
-
-pytest could call a new pytest_addsyspath() in order to systematically
-allow manipulation of sys.path and to inhibit it via --no-addsyspath
-in order to more easily run against installed packages.
-
-Alternatively it could also be done via the config object
-and pytest_configure.
-
-
-
-deprecate global pytest.config usage
-----------------------------------------------------------------
-tags: feature
-
-pytest.ensuretemp and pytest.config are probably the last
-objects containing global state. Often using them is not
-necessary. This is about trying to get rid of them, i.e.
-deprecating them and checking with PyPy's usages as well
-as others.
-
-remove deprecated bits in collect.py
--------------------------------------------------------------------
-tags: feature
-
-In an effort to further simplify code, review and remove deprecated bits
-in collect.py. Probably good:
-- inline consider_file/dir methods, no need to have them
- subclass-overridable because of hooks
-
-implement fslayout decorator
----------------------------------
-tags: feature
-
-Improve the way how tests can work with pre-made examples,
-keeping the layout close to the test function:
-
-@pytest.mark.fslayout("""
- conftest.py:
- # empty
- tests/
- test_%(NAME)s: # becomes test_run1.py
- def test_function(self):
- pass
-""")
-def test_run(pytester, fslayout):
- p = fslayout.findone("test_*.py")
- result = pytester.runpytest(p)
- assert result.ret == 0
- assert result.passed == 1
-
-Another idea is to allow to define a full scenario including the run
-in one content string::
-
- runscenario("""
- test_{TESTNAME}.py:
- import pytest
- @pytest.mark.xfail
- def test_that_fails():
- assert 0
-
- @pytest.mark.skipif("True")
- def test_hello():
- pass
-
- conftest.py:
- import pytest
- def pytest_runsetup_setup(item):
- pytest.skip("abc")
-
- runpytest -rsxX
- *SKIP*{TESTNAME}*
- *1 skipped*
- """)
-
-This could be run with at least three different ways to invoke pytest:
-through the shell, through "python -m pytest" and inlined. As inlined
-would be the fastest it could be run first (or "--fast" mode).
-
-
-Create isolate plugin
----------------------
-tags: feature
-
-The idea is that you can e.g. import modules in a test and afterwards
-sys.modules, sys.meta_path etc would be reverted. It can go further
-then just importing however, e.g. current working directory, file
-descriptors, ...
-
-This would probably be done by marking::
-
- @pytest.mark.isolate(importing=True, cwd=True, fds=False)
- def test_foo():
- ...
-
-With the possibility of doing this globally in an ini-file.
-
-
-fnmatch for test names
-----------------------
-tags: feature-wish
-
-various testsuites use suffixes instead of prefixes for test classes
-also it lends itself to bdd style test names::
-
- class UserBehaviour:
- def anonymous_should_not_have_inbox(user):
- ...
- def registred_should_have_inbox(user):
- ..
-
-using the following in pytest.ini::
-
- [pytest]
- python_classes = Test *Behaviour *Test
- python_functions = test *_should_*
-
-
-mechanism for running named parts of tests with different reporting behaviour
-------------------------------------------------------------------------------
-tags: feature-wish-incomplete
-
-a few use-cases come to mind:
-
-* fail assertions and record that without stopping a complete test
-
- * this is in particular hepfull if a small bit of a test is known to fail/xfail::
-
- def test_fun():
- with pytest.section('fdcheck, marks=pytest.mark.xfail_if(...)):
- breaks_on_windows()
-
-* divide functional/acceptance tests into sections
-* provide a different mechanism for generators, maybe something like::
-
- def pytest_runtest_call(item)
- if not generator:
- ...
- prepare_check = GeneratorCheckprepare()
-
- gen = item.obj(**fixtures)
- for check in gen
- id, call = prepare_check(check)
- # bubble should only prevent exception propagation after a failure
- # the whole test should still fail
- # there might be need for a lower level api and taking custom markers into account
- with pytest.section(id, bubble=False):
- call()
-
-
+++ /dev/null
-The MIT License (MIT)
-
-Copyright (c) 2004-2016 Holger Krekel and others
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of
-this software and associated documentation files (the "Software"), to deal in
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-of the Software, and to permit persons to whom the Software is furnished to do
-so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
+++ /dev/null
-include CHANGELOG.rst
-include LICENSE
-include AUTHORS
-
-include README.rst
-include CONTRIBUTING.rst
-
-include tox.ini
-include setup.py
-
-include .coveragerc
-
-include plugin-test.sh
-include requirements-docs.txt
-include runtox.py
-
-recursive-include bench *.py
-recursive-include extra *.py
-
-graft testing
-graft doc
-
-exclude _pytest/impl
-
-graft _pytest/vendored_packages
-
-recursive-exclude * *.pyc *.pyo
-
-exclude appveyor/install.ps1
-exclude appveyor.yml
-exclude appveyor
-
-exclude ISSUES.txt
-exclude HOWTORELEASE.rst
+++ /dev/null
-.. image:: http://pytest.org/latest/_static/pytest1.png
- :target: http://pytest.org
- :align: center
- :alt: pytest
-
-------
-
-.. image:: https://img.shields.io/pypi/v/pytest.svg
- :target: https://pypi.python.org/pypi/pytest
-.. image:: https://img.shields.io/pypi/pyversions/pytest.svg
- :target: https://pypi.python.org/pypi/pytest
-.. image:: https://img.shields.io/coveralls/pytest-dev/pytest/master.svg
- :target: https://coveralls.io/r/pytest-dev/pytest
-.. image:: https://travis-ci.org/pytest-dev/pytest.svg?branch=master
- :target: https://travis-ci.org/pytest-dev/pytest
-.. image:: https://ci.appveyor.com/api/projects/status/mrgbjaua7t33pg6b?svg=true
- :target: https://ci.appveyor.com/project/pytestbot/pytest
-
-The ``pytest`` framework makes it easy to write small tests, yet
-scales to support complex functional testing for applications and libraries.
-
-An example of a simple test:
-
-.. code-block:: python
-
- # content of test_sample.py
- def func(x):
- return x + 1
-
- def test_answer():
- assert func(3) == 5
-
-
-To execute it::
-
- $ py.test
- ======= test session starts ========
- platform linux -- Python 3.4.3, pytest-2.8.5, py-1.4.31, pluggy-0.3.1
- collected 1 items
-
- test_sample.py F
-
- ======= FAILURES ========
- _______ test_answer ________
-
- def test_answer():
- > assert func(3) == 5
- E assert 4 == 5
- E + where 4 = func(3)
-
- test_sample.py:5: AssertionError
- ======= 1 failed in 0.12 seconds ========
-
-Due to ``py.test``'s detailed assertion introspection, only plain ``assert`` statements are used. See `getting-started <http://pytest.org/latest/getting-started.html#our-first-test-run>`_ for more examples.
-
-
-Features
---------
-
-- Detailed info on failing `assert statements <http://pytest.org/latest/assert.html>`_ (no need to remember ``self.assert*`` names);
-
-- `Auto-discovery
- <http://pytest.org/latest/goodpractices.html#python-test-discovery>`_
- of test modules and functions;
-
-- `Modular fixtures <http://pytest.org/latest/fixture.html>`_ for
- managing small or parametrized long-lived test resources;
-
-- Can run `unittest <http://pytest.org/latest/unittest.html>`_ (or trial),
- `nose <http://pytest.org/latest/nose.html>`_ test suites out of the box;
-
-- Python2.6+, Python3.2+, PyPy-2.3, Jython-2.5 (untested);
-
-- Rich plugin architecture, with over 150+ `external plugins <http://pytest.org/latest/plugins.html#installing-external-plugins-searching>`_ and thriving community;
-
-
-Documentation
--------------
-
-For full documentation, including installation, tutorials and PDF documents, please see http://pytest.org.
-
-
-Bugs/Requests
--------------
-
-Please use the `GitHub issue tracker <https://github.com/pytest-dev/pytest/issues>`_ to submit bugs or request features.
-
-
-Changelog
----------
-
-Consult the `Changelog <http://pytest.org/latest/changelog.html>`_ page for fixes and enhancements of each version.
-
-
-License
--------
-
-Copyright Holger Krekel and others, 2004-2016.
-
-Distributed under the terms of the `MIT`_ license, pytest is free and open source software.
-
-.. _`MIT`: https://github.com/pytest-dev/pytest/blob/master/LICENSE
+++ /dev/null
-#
-__version__ = '2.9.1'
+++ /dev/null
-
-"""allow bash-completion for argparse with argcomplete if installed
-needs argcomplete>=0.5.6 for python 3.2/3.3 (older versions fail
-to find the magic string, so _ARGCOMPLETE env. var is never set, and
-this does not need special code.
-
-argcomplete does not support python 2.5 (although the changes for that
-are minor).
-
-Function try_argcomplete(parser) should be called directly before
-the call to ArgumentParser.parse_args().
-
-The filescompleter is what you normally would use on the positional
-arguments specification, in order to get "dirname/" after "dirn<TAB>"
-instead of the default "dirname ":
-
- optparser.add_argument(Config._file_or_dir, nargs='*'
- ).completer=filescompleter
-
-Other, application specific, completers should go in the file
-doing the add_argument calls as they need to be specified as .completer
-attributes as well. (If argcomplete is not installed, the function the
-attribute points to will not be used).
-
-SPEEDUP
-=======
-The generic argcomplete script for bash-completion
-(/etc/bash_completion.d/python-argcomplete.sh )
-uses a python program to determine startup script generated by pip.
-You can speed up completion somewhat by changing this script to include
- # PYTHON_ARGCOMPLETE_OK
-so the the python-argcomplete-check-easy-install-script does not
-need to be called to find the entry point of the code and see if that is
-marked with PYTHON_ARGCOMPLETE_OK
-
-INSTALL/DEBUGGING
-=================
-To include this support in another application that has setup.py generated
-scripts:
-- add the line:
- # PYTHON_ARGCOMPLETE_OK
- near the top of the main python entry point
-- include in the file calling parse_args():
- from _argcomplete import try_argcomplete, filescompleter
- , call try_argcomplete just before parse_args(), and optionally add
- filescompleter to the positional arguments' add_argument()
-If things do not work right away:
-- switch on argcomplete debugging with (also helpful when doing custom
- completers):
- export _ARC_DEBUG=1
-- run:
- python-argcomplete-check-easy-install-script $(which appname)
- echo $?
- will echo 0 if the magic line has been found, 1 if not
-- sometimes it helps to find early on errors using:
- _ARGCOMPLETE=1 _ARC_DEBUG=1 appname
- which should throw a KeyError: 'COMPLINE' (which is properly set by the
- global argcomplete script).
-"""
-
-import sys
-import os
-from glob import glob
-
-class FastFilesCompleter:
- 'Fast file completer class'
- def __init__(self, directories=True):
- self.directories = directories
-
- def __call__(self, prefix, **kwargs):
- """only called on non option completions"""
- if os.path.sep in prefix[1:]: #
- prefix_dir = len(os.path.dirname(prefix) + os.path.sep)
- else:
- prefix_dir = 0
- completion = []
- globbed = []
- if '*' not in prefix and '?' not in prefix:
- if prefix[-1] == os.path.sep: # we are on unix, otherwise no bash
- globbed.extend(glob(prefix + '.*'))
- prefix += '*'
- globbed.extend(glob(prefix))
- for x in sorted(globbed):
- if os.path.isdir(x):
- x += '/'
- # append stripping the prefix (like bash, not like compgen)
- completion.append(x[prefix_dir:])
- return completion
-
-if os.environ.get('_ARGCOMPLETE'):
- try:
- import argcomplete.completers
- except ImportError:
- sys.exit(-1)
- filescompleter = FastFilesCompleter()
-
- def try_argcomplete(parser):
- argcomplete.autocomplete(parser)
-else:
- def try_argcomplete(parser): pass
- filescompleter = None
+++ /dev/null
-""" python inspection/code generation API """
-from .code import Code # noqa
-from .code import ExceptionInfo # noqa
-from .code import Frame # noqa
-from .code import Traceback # noqa
-from .code import getrawcode # noqa
-from .code import patch_builtins # noqa
-from .code import unpatch_builtins # noqa
-from .source import Source # noqa
-from .source import compile_ as compile # noqa
-from .source import getfslineno # noqa
-
+++ /dev/null
-# copied from python-2.7.3's traceback.py
-# CHANGES:
-# - some_str is replaced, trying to create unicode strings
-#
-import types
-
-def format_exception_only(etype, value):
- """Format the exception part of a traceback.
-
- The arguments are the exception type and value such as given by
- sys.last_type and sys.last_value. The return value is a list of
- strings, each ending in a newline.
-
- Normally, the list contains a single string; however, for
- SyntaxError exceptions, it contains several lines that (when
- printed) display detailed information about where the syntax
- error occurred.
-
- The message indicating which exception occurred is always the last
- string in the list.
-
- """
-
- # An instance should not have a meaningful value parameter, but
- # sometimes does, particularly for string exceptions, such as
- # >>> raise string1, string2 # deprecated
- #
- # Clear these out first because issubtype(string1, SyntaxError)
- # would throw another exception and mask the original problem.
- if (isinstance(etype, BaseException) or
- isinstance(etype, types.InstanceType) or
- etype is None or type(etype) is str):
- return [_format_final_exc_line(etype, value)]
-
- stype = etype.__name__
-
- if not issubclass(etype, SyntaxError):
- return [_format_final_exc_line(stype, value)]
-
- # It was a syntax error; show exactly where the problem was found.
- lines = []
- try:
- msg, (filename, lineno, offset, badline) = value.args
- except Exception:
- pass
- else:
- filename = filename or "<string>"
- lines.append(' File "%s", line %d\n' % (filename, lineno))
- if badline is not None:
- if isinstance(badline, bytes): # python 2 only
- badline = badline.decode('utf-8', 'replace')
- lines.append(u' %s\n' % badline.strip())
- if offset is not None:
- caretspace = badline.rstrip('\n')[:offset].lstrip()
- # non-space whitespace (likes tabs) must be kept for alignment
- caretspace = ((c.isspace() and c or ' ') for c in caretspace)
- # only three spaces to account for offset1 == pos 0
- lines.append(' %s^\n' % ''.join(caretspace))
- value = msg
-
- lines.append(_format_final_exc_line(stype, value))
- return lines
-
-def _format_final_exc_line(etype, value):
- """Return a list of a single line -- normal case for format_exception_only"""
- valuestr = _some_str(value)
- if value is None or not valuestr:
- line = "%s\n" % etype
- else:
- line = "%s: %s\n" % (etype, valuestr)
- return line
-
-def _some_str(value):
- try:
- return unicode(value)
- except Exception:
- try:
- return str(value)
- except Exception:
- pass
- return '<unprintable %s object>' % type(value).__name__
+++ /dev/null
-import sys
-from inspect import CO_VARARGS, CO_VARKEYWORDS
-
-import py
-
-builtin_repr = repr
-
-reprlib = py.builtin._tryimport('repr', 'reprlib')
-
-if sys.version_info[0] >= 3:
- from traceback import format_exception_only
-else:
- from ._py2traceback import format_exception_only
-
-class Code(object):
- """ wrapper around Python code objects """
- def __init__(self, rawcode):
- if not hasattr(rawcode, "co_filename"):
- rawcode = getrawcode(rawcode)
- try:
- self.filename = rawcode.co_filename
- self.firstlineno = rawcode.co_firstlineno - 1
- self.name = rawcode.co_name
- except AttributeError:
- raise TypeError("not a code object: %r" %(rawcode,))
- self.raw = rawcode
-
- def __eq__(self, other):
- return self.raw == other.raw
-
- def __ne__(self, other):
- return not self == other
-
- @property
- def path(self):
- """ return a path object pointing to source code (note that it
- might not point to an actually existing file). """
- p = py.path.local(self.raw.co_filename)
- # maybe don't try this checking
- if not p.check():
- # XXX maybe try harder like the weird logic
- # in the standard lib [linecache.updatecache] does?
- p = self.raw.co_filename
- return p
-
- @property
- def fullsource(self):
- """ return a _pytest._code.Source object for the full source file of the code
- """
- from _pytest._code import source
- full, _ = source.findsource(self.raw)
- return full
-
- def source(self):
- """ return a _pytest._code.Source object for the code object's source only
- """
- # return source only for that part of code
- import _pytest._code
- return _pytest._code.Source(self.raw)
-
- def getargs(self, var=False):
- """ return a tuple with the argument names for the code object
-
- if 'var' is set True also return the names of the variable and
- keyword arguments when present
- """
- # handfull shortcut for getting args
- raw = self.raw
- argcount = raw.co_argcount
- if var:
- argcount += raw.co_flags & CO_VARARGS
- argcount += raw.co_flags & CO_VARKEYWORDS
- return raw.co_varnames[:argcount]
-
-class Frame(object):
- """Wrapper around a Python frame holding f_locals and f_globals
- in which expressions can be evaluated."""
-
- def __init__(self, frame):
- self.lineno = frame.f_lineno - 1
- self.f_globals = frame.f_globals
- self.f_locals = frame.f_locals
- self.raw = frame
- self.code = Code(frame.f_code)
-
- @property
- def statement(self):
- """ statement this frame is at """
- import _pytest._code
- if self.code.fullsource is None:
- return _pytest._code.Source("")
- return self.code.fullsource.getstatement(self.lineno)
-
- def eval(self, code, **vars):
- """ evaluate 'code' in the frame
-
- 'vars' are optional additional local variables
-
- returns the result of the evaluation
- """
- f_locals = self.f_locals.copy()
- f_locals.update(vars)
- return eval(code, self.f_globals, f_locals)
-
- def exec_(self, code, **vars):
- """ exec 'code' in the frame
-
- 'vars' are optiona; additional local variables
- """
- f_locals = self.f_locals.copy()
- f_locals.update(vars)
- py.builtin.exec_(code, self.f_globals, f_locals )
-
- def repr(self, object):
- """ return a 'safe' (non-recursive, one-line) string repr for 'object'
- """
- return py.io.saferepr(object)
-
- def is_true(self, object):
- return object
-
- def getargs(self, var=False):
- """ return a list of tuples (name, value) for all arguments
-
- if 'var' is set True also include the variable and keyword
- arguments when present
- """
- retval = []
- for arg in self.code.getargs(var):
- try:
- retval.append((arg, self.f_locals[arg]))
- except KeyError:
- pass # this can occur when using Psyco
- return retval
-
-class TracebackEntry(object):
- """ a single entry in a traceback """
-
- _repr_style = None
- exprinfo = None
-
- def __init__(self, rawentry):
- self._rawentry = rawentry
- self.lineno = rawentry.tb_lineno - 1
-
- def set_repr_style(self, mode):
- assert mode in ("short", "long")
- self._repr_style = mode
-
- @property
- def frame(self):
- import _pytest._code
- return _pytest._code.Frame(self._rawentry.tb_frame)
-
- @property
- def relline(self):
- return self.lineno - self.frame.code.firstlineno
-
- def __repr__(self):
- return "<TracebackEntry %s:%d>" %(self.frame.code.path, self.lineno+1)
-
- @property
- def statement(self):
- """ _pytest._code.Source object for the current statement """
- source = self.frame.code.fullsource
- return source.getstatement(self.lineno)
-
- @property
- def path(self):
- """ path to the source code """
- return self.frame.code.path
-
- def getlocals(self):
- return self.frame.f_locals
- locals = property(getlocals, None, None, "locals of underlaying frame")
-
- def reinterpret(self):
- """Reinterpret the failing statement and returns a detailed information
- about what operations are performed."""
- from _pytest.assertion.reinterpret import reinterpret
- if self.exprinfo is None:
- source = py.builtin._totext(self.statement).strip()
- x = reinterpret(source, self.frame, should_fail=True)
- if not py.builtin._istext(x):
- raise TypeError("interpret returned non-string %r" % (x,))
- self.exprinfo = x
- return self.exprinfo
-
- def getfirstlinesource(self):
- # on Jython this firstlineno can be -1 apparently
- return max(self.frame.code.firstlineno, 0)
-
- def getsource(self, astcache=None):
- """ return failing source code. """
- # we use the passed in astcache to not reparse asttrees
- # within exception info printing
- from _pytest._code.source import getstatementrange_ast
- source = self.frame.code.fullsource
- if source is None:
- return None
- key = astnode = None
- if astcache is not None:
- key = self.frame.code.path
- if key is not None:
- astnode = astcache.get(key, None)
- start = self.getfirstlinesource()
- try:
- astnode, _, end = getstatementrange_ast(self.lineno, source,
- astnode=astnode)
- except SyntaxError:
- end = self.lineno + 1
- else:
- if key is not None:
- astcache[key] = astnode
- return source[start:end]
-
- source = property(getsource)
-
- def ishidden(self):
- """ return True if the current frame has a var __tracebackhide__
- resolving to True
-
- mostly for internal use
- """
- try:
- return self.frame.f_locals['__tracebackhide__']
- except KeyError:
- try:
- return self.frame.f_globals['__tracebackhide__']
- except KeyError:
- return False
-
- def __str__(self):
- try:
- fn = str(self.path)
- except py.error.Error:
- fn = '???'
- name = self.frame.code.name
- try:
- line = str(self.statement).lstrip()
- except KeyboardInterrupt:
- raise
- except:
- line = "???"
- return " File %r:%d in %s\n %s\n" %(fn, self.lineno+1, name, line)
-
- def name(self):
- return self.frame.code.raw.co_name
- name = property(name, None, None, "co_name of underlaying code")
-
-class Traceback(list):
- """ Traceback objects encapsulate and offer higher level
- access to Traceback entries.
- """
- Entry = TracebackEntry
- def __init__(self, tb):
- """ initialize from given python traceback object. """
- if hasattr(tb, 'tb_next'):
- def f(cur):
- while cur is not None:
- yield self.Entry(cur)
- cur = cur.tb_next
- list.__init__(self, f(tb))
- else:
- list.__init__(self, tb)
-
- def cut(self, path=None, lineno=None, firstlineno=None, excludepath=None):
- """ return a Traceback instance wrapping part of this Traceback
-
- by provding any combination of path, lineno and firstlineno, the
- first frame to start the to-be-returned traceback is determined
-
- this allows cutting the first part of a Traceback instance e.g.
- for formatting reasons (removing some uninteresting bits that deal
- with handling of the exception/traceback)
- """
- for x in self:
- code = x.frame.code
- codepath = code.path
- if ((path is None or codepath == path) and
- (excludepath is None or not hasattr(codepath, 'relto') or
- not codepath.relto(excludepath)) and
- (lineno is None or x.lineno == lineno) and
- (firstlineno is None or x.frame.code.firstlineno == firstlineno)):
- return Traceback(x._rawentry)
- return self
-
- def __getitem__(self, key):
- val = super(Traceback, self).__getitem__(key)
- if isinstance(key, type(slice(0))):
- val = self.__class__(val)
- return val
-
- 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
- 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
- (see ishidden() above)
- """
- return Traceback(filter(fn, self))
-
- def getcrashentry(self):
- """ return last non-hidden traceback entry that lead
- to the exception of a traceback.
- """
- for i in range(-1, -len(self)-1, -1):
- entry = self[i]
- if not entry.ishidden():
- return entry
- return self[-1]
-
- def recursionindex(self):
- """ return the index of the frame/TracebackItem where recursion
- originates if appropriate, None if no recursion occurred
- """
- cache = {}
- for i, entry in enumerate(self):
- # id for the code.raw is needed to work around
- # the strange metaprogramming in the decorator lib from pypi
- # which generates code objects that have hash/value equality
- #XXX needs a test
- key = entry.frame.code.path, id(entry.frame.code.raw), entry.lineno
- #print "checking for recursion at", key
- l = cache.setdefault(key, [])
- if l:
- f = entry.frame
- loc = f.f_locals
- for otherloc in l:
- if f.is_true(f.eval(co_equal,
- __recursioncache_locals_1=loc,
- __recursioncache_locals_2=otherloc)):
- return i
- l.append(entry.frame.f_locals)
- return None
-
-co_equal = compile('__recursioncache_locals_1 == __recursioncache_locals_2',
- '?', 'eval')
-
-class ExceptionInfo(object):
- """ wraps sys.exc_info() objects and offers
- help for navigating the traceback.
- """
- _striptext = ''
- def __init__(self, tup=None, exprinfo=None):
- import _pytest._code
- if tup is None:
- tup = sys.exc_info()
- if exprinfo is None and isinstance(tup[1], AssertionError):
- exprinfo = getattr(tup[1], 'msg', None)
- if exprinfo is None:
- exprinfo = str(tup[1])
- if exprinfo and exprinfo.startswith('assert '):
- self._striptext = 'AssertionError: '
- self._excinfo = tup
- #: the exception class
- self.type = tup[0]
- #: the exception instance
- self.value = tup[1]
- #: the exception raw traceback
- self.tb = tup[2]
- #: the exception type name
- self.typename = self.type.__name__
- #: the exception traceback (_pytest._code.Traceback instance)
- self.traceback = _pytest._code.Traceback(self.tb)
-
- def __repr__(self):
- return "<ExceptionInfo %s tblen=%d>" % (self.typename, len(self.traceback))
-
- def exconly(self, tryshort=False):
- """ return the exception as a string
-
- when 'tryshort' resolves to True, and the exception is a
- _pytest._code._AssertionError, only the actual exception part of
- the exception representation is returned (so 'AssertionError: ' is
- removed from the beginning)
- """
- lines = format_exception_only(self.type, self.value)
- text = ''.join(lines)
- text = text.rstrip()
- if tryshort:
- if text.startswith(self._striptext):
- text = text[len(self._striptext):]
- return text
-
- def errisinstance(self, exc):
- """ return True if the exception is an instance of exc """
- return isinstance(self.value, exc)
-
- def _getreprcrash(self):
- exconly = self.exconly(tryshort=True)
- entry = self.traceback.getcrashentry()
- path, lineno = entry.frame.code.raw.co_filename, entry.lineno
- return ReprFileLocation(path, lineno+1, exconly)
-
- def getrepr(self, showlocals=False, style="long",
- abspath=False, tbfilter=True, funcargs=False):
- """ return str()able representation of this exception info.
- showlocals: show locals per traceback entry
- style: long|short|no|native traceback style
- tbfilter: hide entries (where __tracebackhide__ is true)
-
- in case of style==native, tbfilter and showlocals is ignored.
- """
- if style == 'native':
- return ReprExceptionInfo(ReprTracebackNative(
- py.std.traceback.format_exception(
- self.type,
- self.value,
- self.traceback[0]._rawentry,
- )), self._getreprcrash())
-
- fmt = FormattedExcinfo(showlocals=showlocals, style=style,
- abspath=abspath, tbfilter=tbfilter, funcargs=funcargs)
- return fmt.repr_excinfo(self)
-
- def __str__(self):
- entry = self.traceback[-1]
- loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
- return str(loc)
-
- def __unicode__(self):
- entry = self.traceback[-1]
- loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
- return unicode(loc)
-
-
-class FormattedExcinfo(object):
- """ presenting information about failing Functions and Generators. """
- # for traceback entries
- flow_marker = ">"
- fail_marker = "E"
-
- def __init__(self, showlocals=False, style="long", abspath=True, tbfilter=True, funcargs=False):
- self.showlocals = showlocals
- self.style = style
- self.tbfilter = tbfilter
- self.funcargs = funcargs
- self.abspath = abspath
- self.astcache = {}
-
- def _getindent(self, source):
- # figure out indent for given source
- try:
- s = str(source.getstatement(len(source)-1))
- except KeyboardInterrupt:
- raise
- except:
- try:
- s = str(source[-1])
- except KeyboardInterrupt:
- raise
- except:
- return 0
- return 4 + (len(s) - len(s.lstrip()))
-
- def _getentrysource(self, entry):
- source = entry.getsource(self.astcache)
- if source is not None:
- source = source.deindent()
- return source
-
- def _saferepr(self, obj):
- return py.io.saferepr(obj)
-
- def repr_args(self, entry):
- if self.funcargs:
- args = []
- for argname, argvalue in entry.frame.getargs(var=True):
- args.append((argname, self._saferepr(argvalue)))
- return ReprFuncArgs(args)
-
- def get_source(self, source, line_index=-1, excinfo=None, short=False):
- """ return formatted and marked up source lines. """
- import _pytest._code
- lines = []
- if source is None or line_index >= len(source.lines):
- source = _pytest._code.Source("???")
- line_index = 0
- if line_index < 0:
- line_index += len(source)
- space_prefix = " "
- if short:
- lines.append(space_prefix + source.lines[line_index].strip())
- else:
- for line in source.lines[:line_index]:
- lines.append(space_prefix + line)
- lines.append(self.flow_marker + " " + source.lines[line_index])
- for line in source.lines[line_index+1:]:
- lines.append(space_prefix + line)
- if excinfo is not None:
- indent = 4 if short else self._getindent(source)
- lines.extend(self.get_exconly(excinfo, indent=indent, markall=True))
- return lines
-
- def get_exconly(self, excinfo, indent=4, markall=False):
- lines = []
- indent = " " * indent
- # get the real exception information out
- exlines = excinfo.exconly(tryshort=True).split('\n')
- failindent = self.fail_marker + indent[1:]
- for line in exlines:
- lines.append(failindent + line)
- if not markall:
- failindent = indent
- return lines
-
- def repr_locals(self, locals):
- if self.showlocals:
- lines = []
- keys = [loc for loc in locals if loc[0] != "@"]
- keys.sort()
- for name in keys:
- value = locals[name]
- if name == '__builtins__':
- lines.append("__builtins__ = <builtins>")
- else:
- # This formatting could all be handled by the
- # _repr() function, which is only reprlib.Repr in
- # disguise, so is very configurable.
- str_repr = self._saferepr(value)
- #if len(str_repr) < 70 or not isinstance(value,
- # (list, tuple, dict)):
- lines.append("%-10s = %s" %(name, str_repr))
- #else:
- # self._line("%-10s =\\" % (name,))
- # # XXX
- # py.std.pprint.pprint(value, stream=self.excinfowriter)
- return ReprLocals(lines)
-
- def repr_traceback_entry(self, entry, excinfo=None):
- import _pytest._code
- source = self._getentrysource(entry)
- if source is None:
- source = _pytest._code.Source("???")
- line_index = 0
- else:
- # entry.getfirstlinesource() can be -1, should be 0 on jython
- line_index = entry.lineno - max(entry.getfirstlinesource(), 0)
-
- lines = []
- style = entry._repr_style
- if style is None:
- style = self.style
- if style in ("short", "long"):
- short = style == "short"
- reprargs = self.repr_args(entry) if not short else None
- s = self.get_source(source, line_index, excinfo, short=short)
- lines.extend(s)
- if short:
- message = "in %s" %(entry.name)
- else:
- message = excinfo and excinfo.typename or ""
- path = self._makepath(entry.path)
- filelocrepr = ReprFileLocation(path, entry.lineno+1, message)
- localsrepr = None
- if not short:
- localsrepr = self.repr_locals(entry.locals)
- return ReprEntry(lines, reprargs, localsrepr, filelocrepr, style)
- if excinfo:
- lines.extend(self.get_exconly(excinfo, indent=4))
- return ReprEntry(lines, None, None, None, style)
-
- def _makepath(self, path):
- if not self.abspath:
- try:
- np = py.path.local().bestrelpath(path)
- except OSError:
- return path
- if len(np) < len(str(path)):
- path = np
- return path
-
- def repr_traceback(self, excinfo):
- traceback = excinfo.traceback
- if self.tbfilter:
- traceback = traceback.filter()
- recursionindex = None
- if excinfo.errisinstance(RuntimeError):
- if "maximum recursion depth exceeded" in str(excinfo.value):
- recursionindex = traceback.recursionindex()
- last = traceback[-1]
- entries = []
- extraline = None
- for index, entry in enumerate(traceback):
- einfo = (last == entry) and excinfo or None
- reprentry = self.repr_traceback_entry(entry, einfo)
- entries.append(reprentry)
- if index == recursionindex:
- extraline = "!!! Recursion detected (same locals & position)"
- break
- return ReprTraceback(entries, extraline, style=self.style)
-
- def repr_excinfo(self, excinfo):
- reprtraceback = self.repr_traceback(excinfo)
- reprcrash = excinfo._getreprcrash()
- return ReprExceptionInfo(reprtraceback, reprcrash)
-
-class TerminalRepr:
- def __str__(self):
- s = self.__unicode__()
- if sys.version_info[0] < 3:
- s = s.encode('utf-8')
- return s
-
- def __unicode__(self):
- # FYI this is called from pytest-xdist's serialization of exception
- # information.
- io = py.io.TextIO()
- tw = py.io.TerminalWriter(file=io)
- self.toterminal(tw)
- return io.getvalue().strip()
-
- def __repr__(self):
- return "<%s instance at %0x>" %(self.__class__, id(self))
-
-
-class ReprExceptionInfo(TerminalRepr):
- def __init__(self, reprtraceback, reprcrash):
- self.reprtraceback = reprtraceback
- self.reprcrash = reprcrash
- self.sections = []
-
- def addsection(self, name, content, sep="-"):
- self.sections.append((name, content, sep))
-
- def toterminal(self, tw):
- self.reprtraceback.toterminal(tw)
- for name, content, sep in self.sections:
- tw.sep(sep, name)
- tw.line(content)
-
-class ReprTraceback(TerminalRepr):
- entrysep = "_ "
-
- def __init__(self, reprentries, extraline, style):
- self.reprentries = reprentries
- self.extraline = extraline
- self.style = style
-
- def toterminal(self, tw):
- # the entries might have different styles
- for i, entry in enumerate(self.reprentries):
- if entry.style == "long":
- tw.line("")
- entry.toterminal(tw)
- if i < len(self.reprentries) - 1:
- next_entry = self.reprentries[i+1]
- if entry.style == "long" or \
- entry.style == "short" and next_entry.style == "long":
- tw.sep(self.entrysep)
-
- if self.extraline:
- tw.line(self.extraline)
-
-class ReprTracebackNative(ReprTraceback):
- def __init__(self, tblines):
- self.style = "native"
- self.reprentries = [ReprEntryNative(tblines)]
- self.extraline = None
-
-class ReprEntryNative(TerminalRepr):
- style = "native"
-
- def __init__(self, tblines):
- self.lines = tblines
-
- def toterminal(self, tw):
- tw.write("".join(self.lines))
-
-class ReprEntry(TerminalRepr):
- localssep = "_ "
-
- def __init__(self, lines, reprfuncargs, reprlocals, filelocrepr, style):
- self.lines = lines
- self.reprfuncargs = reprfuncargs
- self.reprlocals = reprlocals
- self.reprfileloc = filelocrepr
- self.style = style
-
- def toterminal(self, tw):
- if self.style == "short":
- self.reprfileloc.toterminal(tw)
- for line in self.lines:
- red = line.startswith("E ")
- tw.line(line, bold=True, red=red)
- #tw.line("")
- return
- if self.reprfuncargs:
- self.reprfuncargs.toterminal(tw)
- for line in self.lines:
- red = line.startswith("E ")
- tw.line(line, bold=True, red=red)
- if self.reprlocals:
- #tw.sep(self.localssep, "Locals")
- tw.line("")
- self.reprlocals.toterminal(tw)
- if self.reprfileloc:
- if self.lines:
- tw.line("")
- self.reprfileloc.toterminal(tw)
-
- def __str__(self):
- return "%s\n%s\n%s" % ("\n".join(self.lines),
- self.reprlocals,
- self.reprfileloc)
-
-class ReprFileLocation(TerminalRepr):
- def __init__(self, path, lineno, message):
- self.path = str(path)
- self.lineno = lineno
- self.message = message
-
- def toterminal(self, tw):
- # filename and lineno output for each entry,
- # using an output format that most editors unterstand
- msg = self.message
- i = msg.find("\n")
- if i != -1:
- msg = msg[:i]
- tw.line("%s:%s: %s" %(self.path, self.lineno, msg))
-
-class ReprLocals(TerminalRepr):
- def __init__(self, lines):
- self.lines = lines
-
- def toterminal(self, tw):
- for line in self.lines:
- tw.line(line)
-
-class ReprFuncArgs(TerminalRepr):
- def __init__(self, args):
- self.args = args
-
- def toterminal(self, tw):
- if self.args:
- linesofar = ""
- for name, value in self.args:
- ns = "%s = %s" %(name, value)
- if len(ns) + len(linesofar) + 2 > tw.fullwidth:
- if linesofar:
- tw.line(linesofar)
- linesofar = ns
- else:
- if linesofar:
- linesofar += ", " + ns
- else:
- linesofar = ns
- if linesofar:
- tw.line(linesofar)
- tw.line("")
-
-
-
-oldbuiltins = {}
-
-def patch_builtins(assertion=True, compile=True):
- """ put compile and AssertionError builtins to Python's builtins. """
- if assertion:
- from _pytest.assertion import reinterpret
- l = oldbuiltins.setdefault('AssertionError', [])
- l.append(py.builtin.builtins.AssertionError)
- py.builtin.builtins.AssertionError = reinterpret.AssertionError
- if compile:
- import _pytest._code
- l = oldbuiltins.setdefault('compile', [])
- l.append(py.builtin.builtins.compile)
- py.builtin.builtins.compile = _pytest._code.compile
-
-def unpatch_builtins(assertion=True, compile=True):
- """ remove compile and AssertionError builtins from Python builtins. """
- if assertion:
- py.builtin.builtins.AssertionError = oldbuiltins['AssertionError'].pop()
- if compile:
- py.builtin.builtins.compile = oldbuiltins['compile'].pop()
-
-def getrawcode(obj, trycall=True):
- """ return code object for given function. """
- try:
- return obj.__code__
- except AttributeError:
- obj = getattr(obj, 'im_func', obj)
- obj = getattr(obj, 'func_code', obj)
- obj = getattr(obj, 'f_code', obj)
- obj = getattr(obj, '__code__', obj)
- if trycall and not hasattr(obj, 'co_firstlineno'):
- if hasattr(obj, '__call__') and not py.std.inspect.isclass(obj):
- x = getrawcode(obj.__call__, trycall=False)
- if hasattr(x, 'co_firstlineno'):
- return x
- return obj
-
+++ /dev/null
-from __future__ import generators
-
-from bisect import bisect_right
-import sys
-import inspect, tokenize
-import py
-from types import ModuleType
-cpy_compile = compile
-
-try:
- import _ast
- from _ast import PyCF_ONLY_AST as _AST_FLAG
-except ImportError:
- _AST_FLAG = 0
- _ast = None
-
-
-class Source(object):
- """ a immutable object holding a source code fragment,
- possibly deindenting it.
- """
- _compilecounter = 0
- def __init__(self, *parts, **kwargs):
- self.lines = lines = []
- de = kwargs.get('deindent', True)
- rstrip = kwargs.get('rstrip', True)
- for part in parts:
- if not part:
- partlines = []
- if isinstance(part, Source):
- partlines = part.lines
- elif isinstance(part, (tuple, list)):
- partlines = [x.rstrip("\n") for x in part]
- elif isinstance(part, py.builtin._basestring):
- partlines = part.split('\n')
- if rstrip:
- while partlines:
- if partlines[-1].strip():
- break
- partlines.pop()
- else:
- partlines = getsource(part, deindent=de).lines
- if de:
- partlines = deindent(partlines)
- lines.extend(partlines)
-
- def __eq__(self, other):
- try:
- return self.lines == other.lines
- except AttributeError:
- if isinstance(other, str):
- return str(self) == other
- return False
-
- def __getitem__(self, key):
- if isinstance(key, int):
- return self.lines[key]
- else:
- if key.step not in (None, 1):
- raise IndexError("cannot slice a Source with a step")
- return self.__getslice__(key.start, key.stop)
-
- def __len__(self):
- return len(self.lines)
-
- def __getslice__(self, start, end):
- newsource = Source()
- newsource.lines = self.lines[start:end]
- return newsource
-
- def strip(self):
- """ return new source object with trailing
- and leading blank lines removed.
- """
- start, end = 0, len(self)
- while start < end and not self.lines[start].strip():
- start += 1
- while end > start and not self.lines[end-1].strip():
- end -= 1
- source = Source()
- source.lines[:] = self.lines[start:end]
- return source
-
- def putaround(self, before='', after='', indent=' ' * 4):
- """ return a copy of the source object with
- 'before' and 'after' wrapped around it.
- """
- before = Source(before)
- after = Source(after)
- newsource = Source()
- lines = [ (indent + line) for line in self.lines]
- newsource.lines = before.lines + lines + after.lines
- return newsource
-
- def indent(self, indent=' ' * 4):
- """ return a copy of the source object with
- all lines indented by the given indent-string.
- """
- newsource = Source()
- newsource.lines = [(indent+line) for line in self.lines]
- return newsource
-
- def getstatement(self, lineno, assertion=False):
- """ return Source statement which contains the
- given linenumber (counted from 0).
- """
- start, end = self.getstatementrange(lineno, assertion)
- return self[start:end]
-
- def getstatementrange(self, lineno, assertion=False):
- """ return (start, end) tuple which spans the minimal
- statement region which containing the given lineno.
- """
- if not (0 <= lineno < len(self)):
- raise IndexError("lineno out of range")
- ast, start, end = getstatementrange_ast(lineno, self)
- return start, end
-
- def deindent(self, offset=None):
- """ return a new source object deindented by offset.
- If offset is None then guess an indentation offset from
- the first non-blank line. Subsequent lines which have a
- lower indentation offset will be copied verbatim as
- they are assumed to be part of multilines.
- """
- # XXX maybe use the tokenizer to properly handle multiline
- # strings etc.pp?
- newsource = Source()
- newsource.lines[:] = deindent(self.lines, offset)
- return newsource
-
- def isparseable(self, deindent=True):
- """ return True if source is parseable, heuristically
- deindenting it by default.
- """
- try:
- import parser
- except ImportError:
- syntax_checker = lambda x: compile(x, 'asd', 'exec')
- else:
- syntax_checker = parser.suite
-
- if deindent:
- source = str(self.deindent())
- else:
- source = str(self)
- try:
- #compile(source+'\n', "x", "exec")
- syntax_checker(source+'\n')
- except KeyboardInterrupt:
- raise
- except Exception:
- return False
- else:
- return True
-
- def __str__(self):
- return "\n".join(self.lines)
-
- def compile(self, filename=None, mode='exec',
- flag=generators.compiler_flag,
- dont_inherit=0, _genframe=None):
- """ return compiled code object. if filename is None
- invent an artificial filename which displays
- the source/line position of the caller frame.
- """
- if not filename or py.path.local(filename).check(file=0):
- if _genframe is None:
- _genframe = sys._getframe(1) # the caller
- fn,lineno = _genframe.f_code.co_filename, _genframe.f_lineno
- base = "<%d-codegen " % self._compilecounter
- self.__class__._compilecounter += 1
- if not filename:
- filename = base + '%s:%d>' % (fn, lineno)
- else:
- filename = base + '%r %s:%d>' % (filename, fn, lineno)
- source = "\n".join(self.lines) + '\n'
- try:
- co = cpy_compile(source, filename, mode, flag)
- except SyntaxError:
- ex = sys.exc_info()[1]
- # re-represent syntax errors from parsing python strings
- msglines = self.lines[:ex.lineno]
- if ex.offset:
- msglines.append(" "*ex.offset + '^')
- msglines.append("(code was compiled probably from here: %s)" % filename)
- newex = SyntaxError('\n'.join(msglines))
- newex.offset = ex.offset
- newex.lineno = ex.lineno
- newex.text = ex.text
- raise newex
- else:
- if flag & _AST_FLAG:
- return co
- lines = [(x + "\n") for x in self.lines]
- if sys.version_info[0] >= 3:
- # XXX py3's inspect.getsourcefile() checks for a module
- # and a pep302 __loader__ ... we don't have a module
- # at code compile-time so we need to fake it here
- m = ModuleType("_pycodecompile_pseudo_module")
- py.std.inspect.modulesbyfile[filename] = None
- py.std.sys.modules[None] = m
- m.__loader__ = 1
- py.std.linecache.cache[filename] = (1, None, lines, filename)
- return co
-
-#
-# public API shortcut functions
-#
-
-def compile_(source, filename=None, mode='exec', flags=
- generators.compiler_flag, dont_inherit=0):
- """ compile the given source to a raw code object,
- and maintain an internal cache which allows later
- retrieval of the source code for the code object
- and any recursively created code objects.
- """
- if _ast is not None and isinstance(source, _ast.AST):
- # XXX should Source support having AST?
- return cpy_compile(source, filename, mode, flags, dont_inherit)
- _genframe = sys._getframe(1) # the caller
- s = Source(source)
- co = s.compile(filename, mode, flags, _genframe=_genframe)
- return co
-
-
-def getfslineno(obj):
- """ Return source location (path, lineno) for the given object.
- If the source cannot be determined return ("", -1)
- """
- import _pytest._code
- try:
- code = _pytest._code.Code(obj)
- except TypeError:
- try:
- fn = (py.std.inspect.getsourcefile(obj) or
- py.std.inspect.getfile(obj))
- except TypeError:
- return "", -1
-
- fspath = fn and py.path.local(fn) or None
- lineno = -1
- if fspath:
- try:
- _, lineno = findsource(obj)
- except IOError:
- pass
- else:
- fspath = code.path
- lineno = code.firstlineno
- assert isinstance(lineno, int)
- return fspath, lineno
-
-#
-# helper functions
-#
-
-def findsource(obj):
- try:
- sourcelines, lineno = py.std.inspect.findsource(obj)
- except py.builtin._sysex:
- raise
- except:
- return None, -1
- source = Source()
- source.lines = [line.rstrip() for line in sourcelines]
- return source, lineno
-
-def getsource(obj, **kwargs):
- import _pytest._code
- obj = _pytest._code.getrawcode(obj)
- try:
- strsrc = inspect.getsource(obj)
- except IndentationError:
- strsrc = "\"Buggy python version consider upgrading, cannot get source\""
- assert isinstance(strsrc, str)
- return Source(strsrc, **kwargs)
-
-def deindent(lines, offset=None):
- if offset is None:
- for line in lines:
- line = line.expandtabs()
- s = line.lstrip()
- if s:
- offset = len(line)-len(s)
- break
- else:
- offset = 0
- if offset == 0:
- return list(lines)
- newlines = []
- def readline_generator(lines):
- for line in lines:
- yield line + '\n'
- while True:
- yield ''
-
- it = readline_generator(lines)
-
- try:
- for _, _, (sline, _), (eline, _), _ in tokenize.generate_tokens(lambda: next(it)):
- if sline > len(lines):
- break # End of input reached
- if sline > len(newlines):
- line = lines[sline - 1].expandtabs()
- if line.lstrip() and line[:offset].isspace():
- line = line[offset:] # Deindent
- newlines.append(line)
-
- for i in range(sline, eline):
- # Don't deindent continuing lines of
- # multiline tokens (i.e. multiline strings)
- newlines.append(lines[i])
- except (IndentationError, tokenize.TokenError):
- pass
- # Add any lines we didn't see. E.g. if an exception was raised.
- newlines.extend(lines[len(newlines):])
- return newlines
-
-
-def get_statement_startend2(lineno, node):
- import ast
- # flatten all statements and except handlers into one lineno-list
- # AST's line numbers start indexing at 1
- l = []
- for x in ast.walk(node):
- if isinstance(x, _ast.stmt) or isinstance(x, _ast.ExceptHandler):
- l.append(x.lineno - 1)
- for name in "finalbody", "orelse":
- val = getattr(x, name, None)
- if val:
- # treat the finally/orelse part as its own statement
- l.append(val[0].lineno - 1 - 1)
- l.sort()
- insert_index = bisect_right(l, lineno)
- start = l[insert_index - 1]
- if insert_index >= len(l):
- end = None
- else:
- end = l[insert_index]
- return start, end
-
-
-def getstatementrange_ast(lineno, source, assertion=False, astnode=None):
- if astnode is None:
- content = str(source)
- if sys.version_info < (2,7):
- content += "\n"
- try:
- astnode = compile(content, "source", "exec", 1024) # 1024 for AST
- except ValueError:
- start, end = getstatementrange_old(lineno, source, assertion)
- return None, start, end
- start, end = get_statement_startend2(lineno, astnode)
- # we need to correct the end:
- # - ast-parsing strips comments
- # - there might be empty lines
- # - we might have lesser indented code blocks at the end
- if end is None:
- end = len(source.lines)
-
- if end > start + 1:
- # make sure we don't span differently indented code blocks
- # by using the BlockFinder helper used which inspect.getsource() uses itself
- block_finder = inspect.BlockFinder()
- # if we start with an indented line, put blockfinder to "started" mode
- block_finder.started = source.lines[start][0].isspace()
- it = ((x + "\n") for x in source.lines[start:end])
- try:
- for tok in tokenize.generate_tokens(lambda: next(it)):
- block_finder.tokeneater(*tok)
- except (inspect.EndOfBlock, IndentationError):
- end = block_finder.last + start
- except Exception:
- pass
-
- # the end might still point to a comment or empty line, correct it
- while end:
- line = source.lines[end - 1].lstrip()
- if line.startswith("#") or not line:
- end -= 1
- else:
- break
- return astnode, start, end
-
-
-def getstatementrange_old(lineno, source, assertion=False):
- """ return (start, end) tuple which spans the minimal
- statement region which containing the given lineno.
- raise an IndexError if no such statementrange can be found.
- """
- # XXX this logic is only used on python2.4 and below
- # 1. find the start of the statement
- from codeop import compile_command
- for start in range(lineno, -1, -1):
- if assertion:
- line = source.lines[start]
- # the following lines are not fully tested, change with care
- if 'super' in line and 'self' in line and '__init__' in line:
- raise IndexError("likely a subclass")
- if "assert" not in line and "raise" not in line:
- continue
- trylines = source.lines[start:lineno+1]
- # quick hack to prepare parsing an indented line with
- # compile_command() (which errors on "return" outside defs)
- trylines.insert(0, 'def xxx():')
- trysource = '\n '.join(trylines)
- # ^ space here
- try:
- compile_command(trysource)
- except (SyntaxError, OverflowError, ValueError):
- continue
-
- # 2. find the end of the statement
- for end in range(lineno+1, len(source)+1):
- trysource = source[start:end]
- if trysource.isparseable():
- return start, end
- raise SyntaxError("no valid source range around line %d " % (lineno,))
-
-
+++ /dev/null
-"""
-imports symbols from vendored "pluggy" if available, otherwise
-falls back to importing "pluggy" from the default namespace.
-"""
-
-try:
- from _pytest.vendored_packages.pluggy import * # noqa
- from _pytest.vendored_packages.pluggy import __version__ # noqa
-except ImportError:
- from pluggy import * # noqa
- from pluggy import __version__ # noqa
+++ /dev/null
-"""
-support for presenting detailed information in failing assertions.
-"""
-import py
-import os
-import sys
-from _pytest.monkeypatch import monkeypatch
-from _pytest.assertion import util
-
-
-def pytest_addoption(parser):
- group = parser.getgroup("debugconfig")
- group.addoption('--assert',
- action="store",
- dest="assertmode",
- choices=("rewrite", "reinterp", "plain",),
- default="rewrite",
- metavar="MODE",
- help="""control assertion debugging tools. 'plain'
- performs no assertion debugging. 'reinterp'
- reinterprets assert statements after they failed
- to provide assertion expression information.
- 'rewrite' (the default) rewrites assert
- statements in test modules on import to
- provide assert expression information. """)
- group.addoption('--no-assert',
- action="store_true",
- default=False,
- dest="noassert",
- help="DEPRECATED equivalent to --assert=plain")
- group.addoption('--nomagic', '--no-magic',
- action="store_true",
- default=False,
- help="DEPRECATED equivalent to --assert=plain")
-
-
-class AssertionState:
- """State for the assertion plugin."""
-
- def __init__(self, config, mode):
- self.mode = mode
- self.trace = config.trace.root.get("assertion")
-
-
-def pytest_configure(config):
- mode = config.getvalue("assertmode")
- if config.getvalue("noassert") or config.getvalue("nomagic"):
- mode = "plain"
- if mode == "rewrite":
- try:
- import ast # noqa
- except ImportError:
- mode = "reinterp"
- else:
- # Both Jython and CPython 2.6.0 have AST bugs that make the
- # assertion rewriting hook malfunction.
- if (sys.platform.startswith('java') or
- sys.version_info[:3] == (2, 6, 0)):
- mode = "reinterp"
- if mode != "plain":
- _load_modules(mode)
- m = monkeypatch()
- config._cleanup.append(m.undo)
- m.setattr(py.builtin.builtins, 'AssertionError',
- reinterpret.AssertionError) # noqa
- hook = None
- if mode == "rewrite":
- hook = rewrite.AssertionRewritingHook() # noqa
- sys.meta_path.insert(0, hook)
- warn_about_missing_assertion(mode)
- config._assertstate = AssertionState(config, mode)
- config._assertstate.hook = hook
- config._assertstate.trace("configured with mode set to %r" % (mode,))
- def undo():
- hook = config._assertstate.hook
- if hook is not None and hook in sys.meta_path:
- sys.meta_path.remove(hook)
- config.add_cleanup(undo)
-
-
-def pytest_collection(session):
- # this hook is only called when test modules are collected
- # so for example not in the master process of pytest-xdist
- # (which does not collect test modules)
- hook = session.config._assertstate.hook
- if hook is not None:
- hook.set_session(session)
-
-
-def _running_on_ci():
- """Check if we're currently running on a CI system."""
- env_vars = ['CI', 'BUILD_NUMBER']
- return any(var in os.environ for var in env_vars)
-
-
-def pytest_runtest_setup(item):
- """Setup the pytest_assertrepr_compare hook
-
- The newinterpret and rewrite modules will use util._reprcompare if
- it exists to use custom reporting via the
- pytest_assertrepr_compare hook. This sets up this custom
- comparison for the test.
- """
- def callbinrepr(op, left, right):
- """Call the pytest_assertrepr_compare hook and prepare the result
-
- This uses the first result from the hook and then ensures the
- following:
- * Overly verbose explanations are dropped unless -vv was used or
- running on a CI.
- * Embedded newlines are escaped to help util.format_explanation()
- later.
- * If the rewrite mode is used embedded %-characters are replaced
- to protect later % formatting.
-
- The result can be formatted by util.format_explanation() for
- pretty printing.
- """
- hook_result = item.ihook.pytest_assertrepr_compare(
- config=item.config, op=op, left=left, right=right)
- for new_expl in hook_result:
- if new_expl:
- if (sum(len(p) for p in new_expl[1:]) > 80*8 and
- item.config.option.verbose < 2 and
- not _running_on_ci()):
- show_max = 10
- truncated_lines = len(new_expl) - show_max
- new_expl[show_max:] = [py.builtin._totext(
- 'Detailed information truncated (%d more lines)'
- ', use "-vv" to show' % truncated_lines)]
- new_expl = [line.replace("\n", "\\n") for line in new_expl]
- res = py.builtin._totext("\n~").join(new_expl)
- if item.config.getvalue("assertmode") == "rewrite":
- res = res.replace("%", "%%")
- return res
- util._reprcompare = callbinrepr
-
-
-def pytest_runtest_teardown(item):
- util._reprcompare = None
-
-
-def pytest_sessionfinish(session):
- hook = session.config._assertstate.hook
- if hook is not None:
- hook.session = None
-
-
-def _load_modules(mode):
- """Lazily import assertion related code."""
- global rewrite, reinterpret
- from _pytest.assertion import reinterpret # noqa
- if mode == "rewrite":
- from _pytest.assertion import rewrite # noqa
-
-
-def warn_about_missing_assertion(mode):
- try:
- assert False
- except AssertionError:
- pass
- else:
- if mode == "rewrite":
- specifically = ("assertions which are not in test modules "
- "will be ignored")
- else:
- specifically = "failing tests may report as passing"
-
- sys.stderr.write("WARNING: " + specifically +
- " because assert statements are not executed "
- "by the underlying Python interpreter "
- "(are you using python -O?)\n")
-
-
-# Expose this plugin's implementation for the pytest_assertrepr_compare hook
-pytest_assertrepr_compare = util.assertrepr_compare
+++ /dev/null
-"""
-Find intermediate evalutation results in assert statements through builtin AST.
-"""
-import ast
-import sys
-
-import _pytest._code
-import py
-from _pytest.assertion import util
-u = py.builtin._totext
-
-
-class AssertionError(util.BuiltinAssertionError):
- def __init__(self, *args):
- util.BuiltinAssertionError.__init__(self, *args)
- if args:
- # on Python2.6 we get len(args)==2 for: assert 0, (x,y)
- # on Python2.7 and above we always get len(args) == 1
- # with args[0] being the (x,y) tuple.
- if len(args) > 1:
- toprint = args
- else:
- toprint = args[0]
- try:
- self.msg = u(toprint)
- except Exception:
- self.msg = u(
- "<[broken __repr__] %s at %0xd>"
- % (toprint.__class__, id(toprint)))
- else:
- f = _pytest._code.Frame(sys._getframe(1))
- try:
- source = f.code.fullsource
- if source is not None:
- try:
- source = source.getstatement(f.lineno, assertion=True)
- except IndexError:
- source = None
- else:
- source = str(source.deindent()).strip()
- except py.error.ENOENT:
- source = None
- # this can also occur during reinterpretation, when the
- # co_filename is set to "<run>".
- if source:
- self.msg = reinterpret(source, f, should_fail=True)
- else:
- self.msg = "<could not determine information>"
- if not self.args:
- self.args = (self.msg,)
-
-if sys.version_info > (3, 0):
- AssertionError.__module__ = "builtins"
-
-if sys.platform.startswith("java"):
- # See http://bugs.jython.org/issue1497
- _exprs = ("BoolOp", "BinOp", "UnaryOp", "Lambda", "IfExp", "Dict",
- "ListComp", "GeneratorExp", "Yield", "Compare", "Call",
- "Repr", "Num", "Str", "Attribute", "Subscript", "Name",
- "List", "Tuple")
- _stmts = ("FunctionDef", "ClassDef", "Return", "Delete", "Assign",
- "AugAssign", "Print", "For", "While", "If", "With", "Raise",
- "TryExcept", "TryFinally", "Assert", "Import", "ImportFrom",
- "Exec", "Global", "Expr", "Pass", "Break", "Continue")
- _expr_nodes = set(getattr(ast, name) for name in _exprs)
- _stmt_nodes = set(getattr(ast, name) for name in _stmts)
- def _is_ast_expr(node):
- return node.__class__ in _expr_nodes
- def _is_ast_stmt(node):
- return node.__class__ in _stmt_nodes
-else:
- def _is_ast_expr(node):
- return isinstance(node, ast.expr)
- def _is_ast_stmt(node):
- return isinstance(node, ast.stmt)
-
-try:
- _Starred = ast.Starred
-except AttributeError:
- # Python 2. Define a dummy class so isinstance() will always be False.
- class _Starred(object): pass
-
-
-class Failure(Exception):
- """Error found while interpreting AST."""
-
- def __init__(self, explanation=""):
- self.cause = sys.exc_info()
- self.explanation = explanation
-
-
-def reinterpret(source, frame, should_fail=False):
- mod = ast.parse(source)
- visitor = DebugInterpreter(frame)
- try:
- visitor.visit(mod)
- except Failure:
- failure = sys.exc_info()[1]
- return getfailure(failure)
- if should_fail:
- return ("(assertion failed, but when it was re-run for "
- "printing intermediate values, it did not fail. Suggestions: "
- "compute assert expression before the assert or use --assert=plain)")
-
-def run(offending_line, frame=None):
- if frame is None:
- frame = _pytest._code.Frame(sys._getframe(1))
- return reinterpret(offending_line, frame)
-
-def getfailure(e):
- explanation = util.format_explanation(e.explanation)
- value = e.cause[1]
- if str(value):
- lines = explanation.split('\n')
- lines[0] += " << %s" % (value,)
- explanation = '\n'.join(lines)
- text = "%s: %s" % (e.cause[0].__name__, explanation)
- if text.startswith('AssertionError: assert '):
- text = text[16:]
- return text
-
-operator_map = {
- ast.BitOr : "|",
- ast.BitXor : "^",
- ast.BitAnd : "&",
- ast.LShift : "<<",
- ast.RShift : ">>",
- ast.Add : "+",
- ast.Sub : "-",
- ast.Mult : "*",
- ast.Div : "/",
- ast.FloorDiv : "//",
- ast.Mod : "%",
- ast.Eq : "==",
- ast.NotEq : "!=",
- ast.Lt : "<",
- ast.LtE : "<=",
- ast.Gt : ">",
- ast.GtE : ">=",
- ast.Pow : "**",
- ast.Is : "is",
- ast.IsNot : "is not",
- ast.In : "in",
- ast.NotIn : "not in"
-}
-
-unary_map = {
- ast.Not : "not %s",
- ast.Invert : "~%s",
- ast.USub : "-%s",
- ast.UAdd : "+%s"
-}
-
-
-class DebugInterpreter(ast.NodeVisitor):
- """Interpret AST nodes to gleam useful debugging information. """
-
- def __init__(self, frame):
- self.frame = frame
-
- def generic_visit(self, node):
- # Fallback when we don't have a special implementation.
- if _is_ast_expr(node):
- mod = ast.Expression(node)
- co = self._compile(mod)
- try:
- result = self.frame.eval(co)
- except Exception:
- raise Failure()
- explanation = self.frame.repr(result)
- return explanation, result
- elif _is_ast_stmt(node):
- mod = ast.Module([node])
- co = self._compile(mod, "exec")
- try:
- self.frame.exec_(co)
- except Exception:
- raise Failure()
- return None, None
- else:
- raise AssertionError("can't handle %s" %(node,))
-
- def _compile(self, source, mode="eval"):
- return compile(source, "<assertion interpretation>", mode)
-
- def visit_Expr(self, expr):
- return self.visit(expr.value)
-
- def visit_Module(self, mod):
- for stmt in mod.body:
- self.visit(stmt)
-
- def visit_Name(self, name):
- explanation, result = self.generic_visit(name)
- # See if the name is local.
- source = "%r in locals() is not globals()" % (name.id,)
- co = self._compile(source)
- try:
- local = self.frame.eval(co)
- except Exception:
- # have to assume it isn't
- local = None
- if local is None or not self.frame.is_true(local):
- return name.id, result
- return explanation, result
-
- def visit_Compare(self, comp):
- left = comp.left
- left_explanation, left_result = self.visit(left)
- for op, next_op in zip(comp.ops, comp.comparators):
- next_explanation, next_result = self.visit(next_op)
- op_symbol = operator_map[op.__class__]
- explanation = "%s %s %s" % (left_explanation, op_symbol,
- next_explanation)
- source = "__exprinfo_left %s __exprinfo_right" % (op_symbol,)
- co = self._compile(source)
- try:
- result = self.frame.eval(co, __exprinfo_left=left_result,
- __exprinfo_right=next_result)
- except Exception:
- raise Failure(explanation)
- try:
- if not self.frame.is_true(result):
- break
- except KeyboardInterrupt:
- raise
- except:
- break
- left_explanation, left_result = next_explanation, next_result
-
- if util._reprcompare is not None:
- res = util._reprcompare(op_symbol, left_result, next_result)
- if res:
- explanation = res
- return explanation, result
-
- def visit_BoolOp(self, boolop):
- is_or = isinstance(boolop.op, ast.Or)
- explanations = []
- for operand in boolop.values:
- explanation, result = self.visit(operand)
- explanations.append(explanation)
- if result == is_or:
- break
- name = is_or and " or " or " and "
- explanation = "(" + name.join(explanations) + ")"
- return explanation, result
-
- def visit_UnaryOp(self, unary):
- pattern = unary_map[unary.op.__class__]
- operand_explanation, operand_result = self.visit(unary.operand)
- explanation = pattern % (operand_explanation,)
- co = self._compile(pattern % ("__exprinfo_expr",))
- try:
- result = self.frame.eval(co, __exprinfo_expr=operand_result)
- except Exception:
- raise Failure(explanation)
- return explanation, result
-
- def visit_BinOp(self, binop):
- left_explanation, left_result = self.visit(binop.left)
- right_explanation, right_result = self.visit(binop.right)
- symbol = operator_map[binop.op.__class__]
- explanation = "(%s %s %s)" % (left_explanation, symbol,
- right_explanation)
- source = "__exprinfo_left %s __exprinfo_right" % (symbol,)
- co = self._compile(source)
- try:
- result = self.frame.eval(co, __exprinfo_left=left_result,
- __exprinfo_right=right_result)
- except Exception:
- raise Failure(explanation)
- return explanation, result
-
- def visit_Call(self, call):
- func_explanation, func = self.visit(call.func)
- arg_explanations = []
- ns = {"__exprinfo_func" : func}
- arguments = []
- for arg in call.args:
- arg_explanation, arg_result = self.visit(arg)
- if isinstance(arg, _Starred):
- arg_name = "__exprinfo_star"
- ns[arg_name] = arg_result
- arguments.append("*%s" % (arg_name,))
- arg_explanations.append("*%s" % (arg_explanation,))
- else:
- arg_name = "__exprinfo_%s" % (len(ns),)
- ns[arg_name] = arg_result
- arguments.append(arg_name)
- arg_explanations.append(arg_explanation)
- for keyword in call.keywords:
- arg_explanation, arg_result = self.visit(keyword.value)
- if keyword.arg:
- arg_name = "__exprinfo_%s" % (len(ns),)
- keyword_source = "%s=%%s" % (keyword.arg)
- arguments.append(keyword_source % (arg_name,))
- arg_explanations.append(keyword_source % (arg_explanation,))
- else:
- arg_name = "__exprinfo_kwds"
- arguments.append("**%s" % (arg_name,))
- arg_explanations.append("**%s" % (arg_explanation,))
-
- ns[arg_name] = arg_result
-
- if getattr(call, 'starargs', None):
- arg_explanation, arg_result = self.visit(call.starargs)
- arg_name = "__exprinfo_star"
- ns[arg_name] = arg_result
- arguments.append("*%s" % (arg_name,))
- arg_explanations.append("*%s" % (arg_explanation,))
-
- if getattr(call, 'kwargs', None):
- arg_explanation, arg_result = self.visit(call.kwargs)
- arg_name = "__exprinfo_kwds"
- ns[arg_name] = arg_result
- arguments.append("**%s" % (arg_name,))
- arg_explanations.append("**%s" % (arg_explanation,))
- args_explained = ", ".join(arg_explanations)
- explanation = "%s(%s)" % (func_explanation, args_explained)
- args = ", ".join(arguments)
- source = "__exprinfo_func(%s)" % (args,)
- co = self._compile(source)
- try:
- result = self.frame.eval(co, **ns)
- except Exception:
- raise Failure(explanation)
- pattern = "%s\n{%s = %s\n}"
- rep = self.frame.repr(result)
- explanation = pattern % (rep, rep, explanation)
- return explanation, result
-
- def _is_builtin_name(self, name):
- pattern = "%r not in globals() and %r not in locals()"
- source = pattern % (name.id, name.id)
- co = self._compile(source)
- try:
- return self.frame.eval(co)
- except Exception:
- return False
-
- def visit_Attribute(self, attr):
- if not isinstance(attr.ctx, ast.Load):
- return self.generic_visit(attr)
- source_explanation, source_result = self.visit(attr.value)
- explanation = "%s.%s" % (source_explanation, attr.attr)
- source = "__exprinfo_expr.%s" % (attr.attr,)
- co = self._compile(source)
- try:
- try:
- result = self.frame.eval(co, __exprinfo_expr=source_result)
- except AttributeError:
- # Maybe the attribute name needs to be mangled?
- if not attr.attr.startswith("__") or attr.attr.endswith("__"):
- raise
- source = "getattr(__exprinfo_expr.__class__, '__name__', '')"
- co = self._compile(source)
- class_name = self.frame.eval(co, __exprinfo_expr=source_result)
- mangled_attr = "_" + class_name + attr.attr
- source = "__exprinfo_expr.%s" % (mangled_attr,)
- co = self._compile(source)
- result = self.frame.eval(co, __exprinfo_expr=source_result)
- except Exception:
- raise Failure(explanation)
- explanation = "%s\n{%s = %s.%s\n}" % (self.frame.repr(result),
- self.frame.repr(result),
- source_explanation, attr.attr)
- # Check if the attr is from an instance.
- source = "%r in getattr(__exprinfo_expr, '__dict__', {})"
- source = source % (attr.attr,)
- co = self._compile(source)
- try:
- from_instance = self.frame.eval(co, __exprinfo_expr=source_result)
- except Exception:
- from_instance = None
- if from_instance is None or self.frame.is_true(from_instance):
- rep = self.frame.repr(result)
- pattern = "%s\n{%s = %s\n}"
- explanation = pattern % (rep, rep, explanation)
- return explanation, result
-
- def visit_Assert(self, assrt):
- test_explanation, test_result = self.visit(assrt.test)
- explanation = "assert %s" % (test_explanation,)
- if not self.frame.is_true(test_result):
- try:
- raise util.BuiltinAssertionError
- except Exception:
- raise Failure(explanation)
- return explanation, test_result
-
- def visit_Assign(self, assign):
- value_explanation, value_result = self.visit(assign.value)
- explanation = "... = %s" % (value_explanation,)
- name = ast.Name("__exprinfo_expr", ast.Load(),
- lineno=assign.value.lineno,
- col_offset=assign.value.col_offset)
- new_assign = ast.Assign(assign.targets, name, lineno=assign.lineno,
- col_offset=assign.col_offset)
- mod = ast.Module([new_assign])
- co = self._compile(mod, "exec")
- try:
- self.frame.exec_(co, __exprinfo_expr=value_result)
- except Exception:
- raise Failure(explanation)
- return explanation, value_result
-
+++ /dev/null
-"""Rewrite assertion AST to produce nice error messages"""
-
-import ast
-import errno
-import itertools
-import imp
-import marshal
-import os
-import re
-import struct
-import sys
-import types
-
-import py
-from _pytest.assertion import util
-
-
-# pytest caches rewritten pycs in __pycache__.
-if hasattr(imp, "get_tag"):
- PYTEST_TAG = imp.get_tag() + "-PYTEST"
-else:
- if hasattr(sys, "pypy_version_info"):
- impl = "pypy"
- elif sys.platform == "java":
- impl = "jython"
- else:
- impl = "cpython"
- ver = sys.version_info
- PYTEST_TAG = "%s-%s%s-PYTEST" % (impl, ver[0], ver[1])
- del ver, impl
-
-PYC_EXT = ".py" + (__debug__ and "c" or "o")
-PYC_TAIL = "." + PYTEST_TAG + PYC_EXT
-
-REWRITE_NEWLINES = sys.version_info[:2] != (2, 7) and sys.version_info < (3, 2)
-ASCII_IS_DEFAULT_ENCODING = sys.version_info[0] < 3
-
-if sys.version_info >= (3,5):
- ast_Call = ast.Call
-else:
- ast_Call = lambda a,b,c: ast.Call(a, b, c, None, None)
-
-
-class AssertionRewritingHook(object):
- """PEP302 Import hook which rewrites asserts."""
-
- def __init__(self):
- self.session = None
- self.modules = {}
- self._register_with_pkg_resources()
-
- def set_session(self, session):
- self.fnpats = session.config.getini("python_files")
- self.session = session
-
- def find_module(self, name, path=None):
- if self.session is None:
- return None
- sess = self.session
- state = sess.config._assertstate
- state.trace("find_module called for: %s" % name)
- names = name.rsplit(".", 1)
- lastname = names[-1]
- pth = None
- if path is not None:
- # Starting with Python 3.3, path is a _NamespacePath(), which
- # causes problems if not converted to list.
- path = list(path)
- if len(path) == 1:
- pth = path[0]
- if pth is None:
- try:
- fd, fn, desc = imp.find_module(lastname, path)
- except ImportError:
- return None
- if fd is not None:
- fd.close()
- tp = desc[2]
- if tp == imp.PY_COMPILED:
- if hasattr(imp, "source_from_cache"):
- fn = imp.source_from_cache(fn)
- else:
- fn = fn[:-1]
- elif tp != imp.PY_SOURCE:
- # Don't know what this is.
- return None
- else:
- fn = os.path.join(pth, name.rpartition(".")[2] + ".py")
- fn_pypath = py.path.local(fn)
- # Is this a test file?
- if not sess.isinitpath(fn):
- # We have to be very careful here because imports in this code can
- # trigger a cycle.
- self.session = None
- try:
- for pat in self.fnpats:
- if fn_pypath.fnmatch(pat):
- state.trace("matched test file %r" % (fn,))
- break
- else:
- return None
- finally:
- self.session = sess
- else:
- state.trace("matched test file (was specified on cmdline): %r" %
- (fn,))
- # The requested module looks like a test file, so rewrite it. This is
- # the most magical part of the process: load the source, rewrite the
- # asserts, and load the rewritten source. We also cache the rewritten
- # module code in a special pyc. We must be aware of the possibility of
- # concurrent pytest processes rewriting and loading pycs. To avoid
- # tricky race conditions, we maintain the following invariant: The
- # cached pyc is always a complete, valid pyc. Operations on it must be
- # atomic. POSIX's atomic rename comes in handy.
- write = not sys.dont_write_bytecode
- cache_dir = os.path.join(fn_pypath.dirname, "__pycache__")
- if write:
- try:
- os.mkdir(cache_dir)
- except OSError:
- e = sys.exc_info()[1].errno
- if e == errno.EEXIST:
- # Either the __pycache__ directory already exists (the
- # common case) or it's blocked by a non-dir node. In the
- # latter case, we'll ignore it in _write_pyc.
- pass
- elif e in [errno.ENOENT, errno.ENOTDIR]:
- # One of the path components was not a directory, likely
- # because we're in a zip file.
- write = False
- elif e in [errno.EACCES, errno.EROFS, errno.EPERM]:
- state.trace("read only directory: %r" % fn_pypath.dirname)
- write = False
- else:
- raise
- cache_name = fn_pypath.basename[:-3] + PYC_TAIL
- pyc = os.path.join(cache_dir, cache_name)
- # Notice that even if we're in a read-only directory, I'm going
- # to check for a cached pyc. This may not be optimal...
- co = _read_pyc(fn_pypath, pyc, state.trace)
- if co is None:
- state.trace("rewriting %r" % (fn,))
- source_stat, co = _rewrite_test(state, fn_pypath)
- if co is None:
- # Probably a SyntaxError in the test.
- return None
- if write:
- _make_rewritten_pyc(state, source_stat, pyc, co)
- else:
- state.trace("found cached rewritten pyc for %r" % (fn,))
- self.modules[name] = co, pyc
- return self
-
- def load_module(self, name):
- # If there is an existing module object named 'fullname' in
- # sys.modules, the loader must use that existing module. (Otherwise,
- # the reload() builtin will not work correctly.)
- if name in sys.modules:
- return sys.modules[name]
-
- co, pyc = self.modules.pop(name)
- # I wish I could just call imp.load_compiled here, but __file__ has to
- # be set properly. In Python 3.2+, this all would be handled correctly
- # by load_compiled.
- mod = sys.modules[name] = imp.new_module(name)
- try:
- mod.__file__ = co.co_filename
- # Normally, this attribute is 3.2+.
- mod.__cached__ = pyc
- mod.__loader__ = self
- py.builtin.exec_(co, mod.__dict__)
- except:
- del sys.modules[name]
- raise
- return sys.modules[name]
-
-
-
- def is_package(self, name):
- try:
- fd, fn, desc = imp.find_module(name)
- except ImportError:
- return False
- if fd is not None:
- fd.close()
- tp = desc[2]
- return tp == imp.PKG_DIRECTORY
-
- @classmethod
- def _register_with_pkg_resources(cls):
- """
- Ensure package resources can be loaded from this loader. May be called
- multiple times, as the operation is idempotent.
- """
- try:
- import pkg_resources
- # access an attribute in case a deferred importer is present
- pkg_resources.__name__
- except ImportError:
- return
-
- # Since pytest tests are always located in the file system, the
- # DefaultProvider is appropriate.
- pkg_resources.register_loader_type(cls, pkg_resources.DefaultProvider)
-
- def get_data(self, pathname):
- """Optional PEP302 get_data API.
- """
- with open(pathname, 'rb') as f:
- return f.read()
-
-
-def _write_pyc(state, co, source_stat, pyc):
- # Technically, we don't have to have the same pyc format as
- # (C)Python, since these "pycs" should never be seen by builtin
- # import. However, there's little reason deviate, and I hope
- # sometime to be able to use imp.load_compiled to load them. (See
- # the comment in load_module above.)
- try:
- fp = open(pyc, "wb")
- except IOError:
- err = sys.exc_info()[1].errno
- state.trace("error writing pyc file at %s: errno=%s" %(pyc, err))
- # we ignore any failure to write the cache file
- # there are many reasons, permission-denied, __pycache__ being a
- # file etc.
- return False
- try:
- fp.write(imp.get_magic())
- mtime = int(source_stat.mtime)
- size = source_stat.size & 0xFFFFFFFF
- fp.write(struct.pack("<ll", mtime, size))
- marshal.dump(co, fp)
- finally:
- fp.close()
- return True
-
-RN = "\r\n".encode("utf-8")
-N = "\n".encode("utf-8")
-
-cookie_re = re.compile(r"^[ \t\f]*#.*coding[:=][ \t]*[-\w.]+")
-BOM_UTF8 = '\xef\xbb\xbf'
-
-def _rewrite_test(state, fn):
- """Try to read and rewrite *fn* and return the code object."""
- try:
- stat = fn.stat()
- source = fn.read("rb")
- except EnvironmentError:
- return None, None
- if ASCII_IS_DEFAULT_ENCODING:
- # ASCII is the default encoding in Python 2. Without a coding
- # declaration, Python 2 will complain about any bytes in the file
- # outside the ASCII range. Sadly, this behavior does not extend to
- # compile() or ast.parse(), which prefer to interpret the bytes as
- # latin-1. (At least they properly handle explicit coding cookies.) To
- # preserve this error behavior, we could force ast.parse() to use ASCII
- # as the encoding by inserting a coding cookie. Unfortunately, that
- # messes up line numbers. Thus, we have to check ourselves if anything
- # is outside the ASCII range in the case no encoding is explicitly
- # declared. For more context, see issue #269. Yay for Python 3 which
- # gets this right.
- end1 = source.find("\n")
- end2 = source.find("\n", end1 + 1)
- if (not source.startswith(BOM_UTF8) and
- cookie_re.match(source[0:end1]) is None and
- cookie_re.match(source[end1 + 1:end2]) is None):
- if hasattr(state, "_indecode"):
- # encodings imported us again, so don't rewrite.
- return None, None
- state._indecode = True
- try:
- try:
- source.decode("ascii")
- except UnicodeDecodeError:
- # Let it fail in real import.
- return None, None
- finally:
- del state._indecode
- # On Python versions which are not 2.7 and less than or equal to 3.1, the
- # parser expects *nix newlines.
- if REWRITE_NEWLINES:
- source = source.replace(RN, N) + N
- try:
- tree = ast.parse(source)
- except SyntaxError:
- # Let this pop up again in the real import.
- state.trace("failed to parse: %r" % (fn,))
- return None, None
- rewrite_asserts(tree)
- try:
- co = compile(tree, fn.strpath, "exec")
- except SyntaxError:
- # It's possible that this error is from some bug in the
- # assertion rewriting, but I don't know of a fast way to tell.
- state.trace("failed to compile: %r" % (fn,))
- return None, None
- return stat, co
-
-def _make_rewritten_pyc(state, source_stat, pyc, co):
- """Try to dump rewritten code to *pyc*."""
- if sys.platform.startswith("win"):
- # Windows grants exclusive access to open files and doesn't have atomic
- # rename, so just write into the final file.
- _write_pyc(state, co, source_stat, pyc)
- else:
- # When not on windows, assume rename is atomic. Dump the code object
- # into a file specific to this process and atomically replace it.
- proc_pyc = pyc + "." + str(os.getpid())
- if _write_pyc(state, co, source_stat, proc_pyc):
- os.rename(proc_pyc, pyc)
-
-def _read_pyc(source, pyc, trace=lambda x: None):
- """Possibly read a pytest pyc containing rewritten code.
-
- Return rewritten code if successful or None if not.
- """
- try:
- fp = open(pyc, "rb")
- except IOError:
- return None
- with fp:
- try:
- mtime = int(source.mtime())
- size = source.size()
- data = fp.read(12)
- except EnvironmentError as e:
- trace('_read_pyc(%s): EnvironmentError %s' % (source, e))
- return None
- # Check for invalid or out of date pyc file.
- if (len(data) != 12 or data[:4] != imp.get_magic() or
- struct.unpack("<ll", data[4:]) != (mtime, size)):
- trace('_read_pyc(%s): invalid or out of date pyc' % source)
- return None
- try:
- co = marshal.load(fp)
- except Exception as e:
- trace('_read_pyc(%s): marshal.load error %s' % (source, e))
- return None
- if not isinstance(co, types.CodeType):
- trace('_read_pyc(%s): not a code object' % source)
- return None
- return co
-
-
-def rewrite_asserts(mod):
- """Rewrite the assert statements in mod."""
- AssertionRewriter().run(mod)
-
-
-def _saferepr(obj):
- """Get a safe repr of an object for assertion error messages.
-
- The assertion formatting (util.format_explanation()) requires
- newlines to be escaped since they are a special character for it.
- Normally assertion.util.format_explanation() does this but for a
- custom repr it is possible to contain one of the special escape
- sequences, especially '\n{' and '\n}' are likely to be present in
- JSON reprs.
-
- """
- repr = py.io.saferepr(obj)
- if py.builtin._istext(repr):
- t = py.builtin.text
- else:
- t = py.builtin.bytes
- return repr.replace(t("\n"), t("\\n"))
-
-
-from _pytest.assertion.util import format_explanation as _format_explanation # noqa
-
-def _format_assertmsg(obj):
- """Format the custom assertion message given.
-
- For strings this simply replaces newlines with '\n~' so that
- util.format_explanation() will preserve them instead of escaping
- newlines. For other objects py.io.saferepr() is used first.
-
- """
- # reprlib appears to have a bug which means that if a string
- # contains a newline it gets escaped, however if an object has a
- # .__repr__() which contains newlines it does not get escaped.
- # However in either case we want to preserve the newline.
- if py.builtin._istext(obj) or py.builtin._isbytes(obj):
- s = obj
- is_repr = False
- else:
- s = py.io.saferepr(obj)
- is_repr = True
- if py.builtin._istext(s):
- t = py.builtin.text
- else:
- t = py.builtin.bytes
- s = s.replace(t("\n"), t("\n~")).replace(t("%"), t("%%"))
- if is_repr:
- s = s.replace(t("\\n"), t("\n~"))
- return s
-
-def _should_repr_global_name(obj):
- return not hasattr(obj, "__name__") and not py.builtin.callable(obj)
-
-def _format_boolop(explanations, is_or):
- explanation = "(" + (is_or and " or " or " and ").join(explanations) + ")"
- if py.builtin._istext(explanation):
- t = py.builtin.text
- else:
- t = py.builtin.bytes
- return explanation.replace(t('%'), t('%%'))
-
-def _call_reprcompare(ops, results, expls, each_obj):
- for i, res, expl in zip(range(len(ops)), results, expls):
- try:
- done = not res
- except Exception:
- done = True
- if done:
- break
- if util._reprcompare is not None:
- custom = util._reprcompare(ops[i], each_obj[i], each_obj[i + 1])
- if custom is not None:
- return custom
- return expl
-
-
-unary_map = {
- ast.Not: "not %s",
- ast.Invert: "~%s",
- ast.USub: "-%s",
- ast.UAdd: "+%s"
-}
-
-binop_map = {
- ast.BitOr: "|",
- ast.BitXor: "^",
- ast.BitAnd: "&",
- ast.LShift: "<<",
- ast.RShift: ">>",
- ast.Add: "+",
- ast.Sub: "-",
- ast.Mult: "*",
- ast.Div: "/",
- ast.FloorDiv: "//",
- ast.Mod: "%%", # escaped for string formatting
- ast.Eq: "==",
- ast.NotEq: "!=",
- ast.Lt: "<",
- ast.LtE: "<=",
- ast.Gt: ">",
- ast.GtE: ">=",
- ast.Pow: "**",
- ast.Is: "is",
- ast.IsNot: "is not",
- ast.In: "in",
- ast.NotIn: "not in"
-}
-# Python 3.5+ compatibility
-try:
- binop_map[ast.MatMult] = "@"
-except AttributeError:
- pass
-
-# Python 3.4+ compatibility
-if hasattr(ast, "NameConstant"):
- _NameConstant = ast.NameConstant
-else:
- def _NameConstant(c):
- return ast.Name(str(c), ast.Load())
-
-
-def set_location(node, lineno, col_offset):
- """Set node location information recursively."""
- def _fix(node, lineno, col_offset):
- if "lineno" in node._attributes:
- node.lineno = lineno
- if "col_offset" in node._attributes:
- node.col_offset = col_offset
- for child in ast.iter_child_nodes(node):
- _fix(child, lineno, col_offset)
- _fix(node, lineno, col_offset)
- return node
-
-
-class AssertionRewriter(ast.NodeVisitor):
- """Assertion rewriting implementation.
-
- The main entrypoint is to call .run() with an ast.Module instance,
- this will then find all the assert statements and re-write them to
- provide intermediate values and a detailed assertion error. See
- http://pybites.blogspot.be/2011/07/behind-scenes-of-pytests-new-assertion.html
- for an overview of how this works.
-
- The entry point here is .run() which will iterate over all the
- statements in an ast.Module and for each ast.Assert statement it
- finds call .visit() with it. Then .visit_Assert() takes over and
- is responsible for creating new ast statements to replace the
- original assert statement: it re-writes the test of an assertion
- to provide intermediate values and replace it with an if statement
- which raises an assertion error with a detailed explanation in
- case the expression is false.
-
- For this .visit_Assert() uses the visitor pattern to visit all the
- AST nodes of the ast.Assert.test field, each visit call returning
- an AST node and the corresponding explanation string. During this
- state is kept in several instance attributes:
-
- :statements: All the AST statements which will replace the assert
- statement.
-
- :variables: This is populated by .variable() with each variable
- used by the statements so that they can all be set to None at
- the end of the statements.
-
- :variable_counter: Counter to create new unique variables needed
- by statements. Variables are created using .variable() and
- have the form of "@py_assert0".
-
- :on_failure: The AST statements which will be executed if the
- assertion test fails. This is the code which will construct
- the failure message and raises the AssertionError.
-
- :explanation_specifiers: A dict filled by .explanation_param()
- with %-formatting placeholders and their corresponding
- expressions to use in the building of an assertion message.
- This is used by .pop_format_context() to build a message.
-
- :stack: A stack of the explanation_specifiers dicts maintained by
- .push_format_context() and .pop_format_context() which allows
- to build another %-formatted string while already building one.
-
- This state is reset on every new assert statement visited and used
- by the other visitors.
-
- """
-
- def run(self, mod):
- """Find all assert statements in *mod* and rewrite them."""
- if not mod.body:
- # Nothing to do.
- return
- # Insert some special imports at the top of the module but after any
- # docstrings and __future__ imports.
- aliases = [ast.alias(py.builtin.builtins.__name__, "@py_builtins"),
- ast.alias("_pytest.assertion.rewrite", "@pytest_ar")]
- expect_docstring = True
- pos = 0
- lineno = 0
- for item in mod.body:
- if (expect_docstring and isinstance(item, ast.Expr) and
- isinstance(item.value, ast.Str)):
- doc = item.value.s
- if "PYTEST_DONT_REWRITE" in doc:
- # The module has disabled assertion rewriting.
- return
- lineno += len(doc) - 1
- expect_docstring = False
- elif (not isinstance(item, ast.ImportFrom) or item.level > 0 or
- item.module != "__future__"):
- lineno = item.lineno
- break
- pos += 1
- imports = [ast.Import([alias], lineno=lineno, col_offset=0)
- for alias in aliases]
- mod.body[pos:pos] = imports
- # Collect asserts.
- nodes = [mod]
- while nodes:
- node = nodes.pop()
- for name, field in ast.iter_fields(node):
- if isinstance(field, list):
- new = []
- for i, child in enumerate(field):
- if isinstance(child, ast.Assert):
- # Transform assert.
- new.extend(self.visit(child))
- else:
- new.append(child)
- if isinstance(child, ast.AST):
- nodes.append(child)
- setattr(node, name, new)
- elif (isinstance(field, ast.AST) and
- # Don't recurse into expressions as they can't contain
- # asserts.
- not isinstance(field, ast.expr)):
- nodes.append(field)
-
- def variable(self):
- """Get a new variable."""
- # Use a character invalid in python identifiers to avoid clashing.
- name = "@py_assert" + str(next(self.variable_counter))
- self.variables.append(name)
- return name
-
- def assign(self, expr):
- """Give *expr* a name."""
- name = self.variable()
- self.statements.append(ast.Assign([ast.Name(name, ast.Store())], expr))
- return ast.Name(name, ast.Load())
-
- def display(self, expr):
- """Call py.io.saferepr on the expression."""
- return self.helper("saferepr", expr)
-
- def helper(self, name, *args):
- """Call a helper in this module."""
- py_name = ast.Name("@pytest_ar", ast.Load())
- attr = ast.Attribute(py_name, "_" + name, ast.Load())
- return ast_Call(attr, list(args), [])
-
- def builtin(self, name):
- """Return the builtin called *name*."""
- builtin_name = ast.Name("@py_builtins", ast.Load())
- return ast.Attribute(builtin_name, name, ast.Load())
-
- def explanation_param(self, expr):
- """Return a new named %-formatting placeholder for expr.
-
- This creates a %-formatting placeholder for expr in the
- current formatting context, e.g. ``%(py0)s``. The placeholder
- and expr are placed in the current format context so that it
- can be used on the next call to .pop_format_context().
-
- """
- specifier = "py" + str(next(self.variable_counter))
- self.explanation_specifiers[specifier] = expr
- return "%(" + specifier + ")s"
-
- def push_format_context(self):
- """Create a new formatting context.
-
- The format context is used for when an explanation wants to
- have a variable value formatted in the assertion message. In
- this case the value required can be added using
- .explanation_param(). Finally .pop_format_context() is used
- to format a string of %-formatted values as added by
- .explanation_param().
-
- """
- self.explanation_specifiers = {}
- self.stack.append(self.explanation_specifiers)
-
- def pop_format_context(self, expl_expr):
- """Format the %-formatted string with current format context.
-
- The expl_expr should be an ast.Str instance constructed from
- the %-placeholders created by .explanation_param(). This will
- add the required code to format said string to .on_failure and
- return the ast.Name instance of the formatted string.
-
- """
- current = self.stack.pop()
- if self.stack:
- self.explanation_specifiers = self.stack[-1]
- keys = [ast.Str(key) for key in current.keys()]
- format_dict = ast.Dict(keys, list(current.values()))
- form = ast.BinOp(expl_expr, ast.Mod(), format_dict)
- name = "@py_format" + str(next(self.variable_counter))
- self.on_failure.append(ast.Assign([ast.Name(name, ast.Store())], form))
- return ast.Name(name, ast.Load())
-
- def generic_visit(self, node):
- """Handle expressions we don't have custom code for."""
- assert isinstance(node, ast.expr)
- res = self.assign(node)
- return res, self.explanation_param(self.display(res))
-
- def visit_Assert(self, assert_):
- """Return the AST statements to replace the ast.Assert instance.
-
- This re-writes the test of an assertion to provide
- intermediate values and replace it with an if statement which
- raises an assertion error with a detailed explanation in case
- the expression is false.
-
- """
- self.statements = []
- self.variables = []
- self.variable_counter = itertools.count()
- self.stack = []
- self.on_failure = []
- self.push_format_context()
- # Rewrite assert into a bunch of statements.
- top_condition, explanation = self.visit(assert_.test)
- # Create failure message.
- body = self.on_failure
- negation = ast.UnaryOp(ast.Not(), top_condition)
- self.statements.append(ast.If(negation, body, []))
- if assert_.msg:
- assertmsg = self.helper('format_assertmsg', assert_.msg)
- explanation = "\n>assert " + explanation
- else:
- assertmsg = ast.Str("")
- explanation = "assert " + explanation
- template = ast.BinOp(assertmsg, ast.Add(), ast.Str(explanation))
- msg = self.pop_format_context(template)
- fmt = self.helper("format_explanation", msg)
- err_name = ast.Name("AssertionError", ast.Load())
- exc = ast_Call(err_name, [fmt], [])
- if sys.version_info[0] >= 3:
- raise_ = ast.Raise(exc, None)
- else:
- raise_ = ast.Raise(exc, None, None)
- body.append(raise_)
- # Clear temporary variables by setting them to None.
- if self.variables:
- variables = [ast.Name(name, ast.Store())
- for name in self.variables]
- clear = ast.Assign(variables, _NameConstant(None))
- self.statements.append(clear)
- # Fix line numbers.
- for stmt in self.statements:
- set_location(stmt, assert_.lineno, assert_.col_offset)
- return self.statements
-
- def visit_Name(self, name):
- # Display the repr of the name if it's a local variable or
- # _should_repr_global_name() thinks it's acceptable.
- locs = ast_Call(self.builtin("locals"), [], [])
- inlocs = ast.Compare(ast.Str(name.id), [ast.In()], [locs])
- dorepr = self.helper("should_repr_global_name", name)
- test = ast.BoolOp(ast.Or(), [inlocs, dorepr])
- expr = ast.IfExp(test, self.display(name), ast.Str(name.id))
- return name, self.explanation_param(expr)
-
- def visit_BoolOp(self, boolop):
- res_var = self.variable()
- expl_list = self.assign(ast.List([], ast.Load()))
- app = ast.Attribute(expl_list, "append", ast.Load())
- is_or = int(isinstance(boolop.op, ast.Or))
- body = save = self.statements
- fail_save = self.on_failure
- levels = len(boolop.values) - 1
- self.push_format_context()
- # Process each operand, short-circuting if needed.
- for i, v in enumerate(boolop.values):
- if i:
- fail_inner = []
- # cond is set in a prior loop iteration below
- self.on_failure.append(ast.If(cond, fail_inner, [])) # noqa
- self.on_failure = fail_inner
- self.push_format_context()
- res, expl = self.visit(v)
- body.append(ast.Assign([ast.Name(res_var, ast.Store())], res))
- expl_format = self.pop_format_context(ast.Str(expl))
- call = ast_Call(app, [expl_format], [])
- self.on_failure.append(ast.Expr(call))
- if i < levels:
- cond = res
- if is_or:
- cond = ast.UnaryOp(ast.Not(), cond)
- inner = []
- self.statements.append(ast.If(cond, inner, []))
- self.statements = body = inner
- self.statements = save
- self.on_failure = fail_save
- expl_template = self.helper("format_boolop", expl_list, ast.Num(is_or))
- expl = self.pop_format_context(expl_template)
- return ast.Name(res_var, ast.Load()), self.explanation_param(expl)
-
- def visit_UnaryOp(self, unary):
- pattern = unary_map[unary.op.__class__]
- operand_res, operand_expl = self.visit(unary.operand)
- res = self.assign(ast.UnaryOp(unary.op, operand_res))
- return res, pattern % (operand_expl,)
-
- def visit_BinOp(self, binop):
- symbol = binop_map[binop.op.__class__]
- left_expr, left_expl = self.visit(binop.left)
- right_expr, right_expl = self.visit(binop.right)
- explanation = "(%s %s %s)" % (left_expl, symbol, right_expl)
- res = self.assign(ast.BinOp(left_expr, binop.op, right_expr))
- return res, explanation
-
- def visit_Call_35(self, call):
- """
- visit `ast.Call` nodes on Python3.5 and after
- """
- new_func, func_expl = self.visit(call.func)
- arg_expls = []
- new_args = []
- new_kwargs = []
- for arg in call.args:
- res, expl = self.visit(arg)
- arg_expls.append(expl)
- new_args.append(res)
- for keyword in call.keywords:
- res, expl = self.visit(keyword.value)
- new_kwargs.append(ast.keyword(keyword.arg, res))
- if keyword.arg:
- arg_expls.append(keyword.arg + "=" + expl)
- else: ## **args have `arg` keywords with an .arg of None
- arg_expls.append("**" + expl)
-
- expl = "%s(%s)" % (func_expl, ', '.join(arg_expls))
- new_call = ast.Call(new_func, new_args, new_kwargs)
- res = self.assign(new_call)
- res_expl = self.explanation_param(self.display(res))
- outer_expl = "%s\n{%s = %s\n}" % (res_expl, res_expl, expl)
- return res, outer_expl
-
- def visit_Starred(self, starred):
- # From Python 3.5, a Starred node can appear in a function call
- res, expl = self.visit(starred.value)
- return starred, '*' + expl
-
- def visit_Call_legacy(self, call):
- """
- visit `ast.Call nodes on 3.4 and below`
- """
- new_func, func_expl = self.visit(call.func)
- arg_expls = []
- new_args = []
- new_kwargs = []
- new_star = new_kwarg = None
- for arg in call.args:
- res, expl = self.visit(arg)
- new_args.append(res)
- arg_expls.append(expl)
- for keyword in call.keywords:
- res, expl = self.visit(keyword.value)
- new_kwargs.append(ast.keyword(keyword.arg, res))
- arg_expls.append(keyword.arg + "=" + expl)
- if call.starargs:
- new_star, expl = self.visit(call.starargs)
- arg_expls.append("*" + expl)
- if call.kwargs:
- new_kwarg, expl = self.visit(call.kwargs)
- arg_expls.append("**" + expl)
- expl = "%s(%s)" % (func_expl, ', '.join(arg_expls))
- new_call = ast.Call(new_func, new_args, new_kwargs,
- new_star, new_kwarg)
- res = self.assign(new_call)
- res_expl = self.explanation_param(self.display(res))
- outer_expl = "%s\n{%s = %s\n}" % (res_expl, res_expl, expl)
- return res, outer_expl
-
- # ast.Call signature changed on 3.5,
- # conditionally change which methods is named
- # visit_Call depending on Python version
- if sys.version_info >= (3, 5):
- visit_Call = visit_Call_35
- else:
- visit_Call = visit_Call_legacy
-
-
- def visit_Attribute(self, attr):
- if not isinstance(attr.ctx, ast.Load):
- return self.generic_visit(attr)
- value, value_expl = self.visit(attr.value)
- res = self.assign(ast.Attribute(value, attr.attr, ast.Load()))
- res_expl = self.explanation_param(self.display(res))
- pat = "%s\n{%s = %s.%s\n}"
- expl = pat % (res_expl, res_expl, value_expl, attr.attr)
- return res, expl
-
- def visit_Compare(self, comp):
- self.push_format_context()
- left_res, left_expl = self.visit(comp.left)
- res_variables = [self.variable() for i in range(len(comp.ops))]
- load_names = [ast.Name(v, ast.Load()) for v in res_variables]
- store_names = [ast.Name(v, ast.Store()) for v in res_variables]
- it = zip(range(len(comp.ops)), comp.ops, comp.comparators)
- expls = []
- syms = []
- results = [left_res]
- for i, op, next_operand in it:
- next_res, next_expl = self.visit(next_operand)
- results.append(next_res)
- sym = binop_map[op.__class__]
- syms.append(ast.Str(sym))
- expl = "%s %s %s" % (left_expl, sym, next_expl)
- expls.append(ast.Str(expl))
- res_expr = ast.Compare(left_res, [op], [next_res])
- self.statements.append(ast.Assign([store_names[i]], res_expr))
- left_res, left_expl = next_res, next_expl
- # Use pytest.assertion.util._reprcompare if that's available.
- expl_call = self.helper("call_reprcompare",
- ast.Tuple(syms, ast.Load()),
- ast.Tuple(load_names, ast.Load()),
- ast.Tuple(expls, ast.Load()),
- ast.Tuple(results, ast.Load()))
- if len(comp.ops) > 1:
- res = ast.BoolOp(ast.And(), load_names)
- else:
- res = load_names[0]
- return res, self.explanation_param(self.pop_format_context(expl_call))
+++ /dev/null
-"""Utilities for assertion debugging"""
-import pprint
-
-import _pytest._code
-import py
-try:
- from collections import Sequence
-except ImportError:
- Sequence = list
-
-BuiltinAssertionError = py.builtin.builtins.AssertionError
-u = py.builtin._totext
-
-# The _reprcompare attribute on the util module is used by the new assertion
-# interpretation code and assertion rewriter to detect this plugin was
-# loaded and in turn call the hooks defined here as part of the
-# DebugInterpreter.
-_reprcompare = None
-
-
-# the re-encoding is needed for python2 repr
-# with non-ascii characters (see issue 877 and 1379)
-def ecu(s):
- try:
- return u(s, 'utf-8', 'replace')
- except TypeError:
- return s
-
-
-def format_explanation(explanation):
- """This formats an explanation
-
- Normally all embedded newlines are escaped, however there are
- three exceptions: \n{, \n} and \n~. The first two are intended
- cover nested explanations, see function and attribute explanations
- for examples (.visit_Call(), visit_Attribute()). The last one is
- for when one explanation needs to span multiple lines, e.g. when
- displaying diffs.
- """
- explanation = ecu(explanation)
- explanation = _collapse_false(explanation)
- lines = _split_explanation(explanation)
- result = _format_lines(lines)
- return u('\n').join(result)
-
-
-def _collapse_false(explanation):
- """Collapse expansions of False
-
- So this strips out any "assert False\n{where False = ...\n}"
- blocks.
- """
- where = 0
- while True:
- start = where = explanation.find("False\n{False = ", where)
- if where == -1:
- break
- level = 0
- prev_c = explanation[start]
- for i, c in enumerate(explanation[start:]):
- if prev_c + c == "\n{":
- level += 1
- elif prev_c + c == "\n}":
- level -= 1
- if not level:
- break
- prev_c = c
- else:
- raise AssertionError("unbalanced braces: %r" % (explanation,))
- end = start + i
- where = end
- if explanation[end - 1] == '\n':
- explanation = (explanation[:start] + explanation[start+15:end-1] +
- explanation[end+1:])
- where -= 17
- return explanation
-
-
-def _split_explanation(explanation):
- """Return a list of individual lines in the explanation
-
- This will return a list of lines split on '\n{', '\n}' and '\n~'.
- Any other newlines will be escaped and appear in the line as the
- literal '\n' characters.
- """
- raw_lines = (explanation or u('')).split('\n')
- lines = [raw_lines[0]]
- for l in raw_lines[1:]:
- if l and l[0] in ['{', '}', '~', '>']:
- lines.append(l)
- else:
- lines[-1] += '\\n' + l
- return lines
-
-
-def _format_lines(lines):
- """Format the individual lines
-
- This will replace the '{', '}' and '~' characters of our mini
- formatting language with the proper 'where ...', 'and ...' and ' +
- ...' text, taking care of indentation along the way.
-
- Return a list of formatted lines.
- """
- result = lines[:1]
- stack = [0]
- stackcnt = [0]
- for line in lines[1:]:
- if line.startswith('{'):
- if stackcnt[-1]:
- s = u('and ')
- else:
- s = u('where ')
- stack.append(len(result))
- stackcnt[-1] += 1
- stackcnt.append(0)
- result.append(u(' +') + u(' ')*(len(stack)-1) + s + line[1:])
- elif line.startswith('}'):
- stack.pop()
- stackcnt.pop()
- result[stack[-1]] += line[1:]
- else:
- assert line[0] in ['~', '>']
- stack[-1] += 1
- indent = len(stack) if line.startswith('~') else len(stack) - 1
- result.append(u(' ')*indent + line[1:])
- assert len(stack) == 1
- return result
-
-
-# Provide basestring in python3
-try:
- basestring = basestring
-except NameError:
- basestring = str
-
-
-def assertrepr_compare(config, op, left, right):
- """Return specialised explanations for some operators/operands"""
- width = 80 - 15 - len(op) - 2 # 15 chars indentation, 1 space around op
- left_repr = py.io.saferepr(left, maxsize=int(width/2))
- right_repr = py.io.saferepr(right, maxsize=width-len(left_repr))
-
- summary = u('%s %s %s') % (ecu(left_repr), op, ecu(right_repr))
-
- issequence = lambda x: (isinstance(x, (list, tuple, Sequence)) and
- not isinstance(x, basestring))
- istext = lambda x: isinstance(x, basestring)
- isdict = lambda x: isinstance(x, dict)
- isset = lambda x: isinstance(x, (set, frozenset))
-
- def isiterable(obj):
- try:
- iter(obj)
- return not istext(obj)
- except TypeError:
- return False
-
- verbose = config.getoption('verbose')
- explanation = None
- try:
- if op == '==':
- if istext(left) and istext(right):
- explanation = _diff_text(left, right, verbose)
- else:
- if issequence(left) and issequence(right):
- explanation = _compare_eq_sequence(left, right, verbose)
- elif isset(left) and isset(right):
- explanation = _compare_eq_set(left, right, verbose)
- elif isdict(left) and isdict(right):
- explanation = _compare_eq_dict(left, right, verbose)
- if isiterable(left) and isiterable(right):
- expl = _compare_eq_iterable(left, right, verbose)
- if explanation is not None:
- explanation.extend(expl)
- else:
- explanation = expl
- elif op == 'not in':
- if istext(left) and istext(right):
- explanation = _notin_text(left, right, verbose)
- except Exception:
- explanation = [
- u('(pytest_assertion plugin: representation of details failed. '
- 'Probably an object has a faulty __repr__.)'),
- u(_pytest._code.ExceptionInfo())]
-
- if not explanation:
- return None
-
- return [summary] + explanation
-
-
-def _diff_text(left, right, verbose=False):
- """Return the explanation for the diff between text or bytes
-
- Unless --verbose is used this will skip leading and trailing
- characters which are identical to keep the diff minimal.
-
- If the input are bytes they will be safely converted to text.
- """
- from difflib import ndiff
- explanation = []
- if isinstance(left, py.builtin.bytes):
- left = u(repr(left)[1:-1]).replace(r'\n', '\n')
- if isinstance(right, py.builtin.bytes):
- right = u(repr(right)[1:-1]).replace(r'\n', '\n')
- if not verbose:
- i = 0 # just in case left or right has zero length
- for i in range(min(len(left), len(right))):
- if left[i] != right[i]:
- break
- if i > 42:
- i -= 10 # Provide some context
- explanation = [u('Skipping %s identical leading '
- 'characters in diff, use -v to show') % i]
- left = left[i:]
- right = right[i:]
- if len(left) == len(right):
- for i in range(len(left)):
- if left[-i] != right[-i]:
- break
- if i > 42:
- i -= 10 # Provide some context
- explanation += [u('Skipping %s identical trailing '
- 'characters in diff, use -v to show') % i]
- left = left[:-i]
- right = right[:-i]
- explanation += [line.strip('\n')
- for line in ndiff(left.splitlines(),
- right.splitlines())]
- return explanation
-
-
-def _compare_eq_iterable(left, right, verbose=False):
- if not verbose:
- return [u('Use -v to get the full diff')]
- # dynamic import to speedup pytest
- import difflib
-
- try:
- left_formatting = pprint.pformat(left).splitlines()
- right_formatting = pprint.pformat(right).splitlines()
- explanation = [u('Full diff:')]
- except Exception:
- # hack: PrettyPrinter.pformat() in python 2 fails when formatting items that can't be sorted(), ie, calling
- # sorted() on a list would raise. See issue #718.
- # As a workaround, the full diff is generated by using the repr() string of each item of each container.
- left_formatting = sorted(repr(x) for x in left)
- right_formatting = sorted(repr(x) for x in right)
- explanation = [u('Full diff (fallback to calling repr on each item):')]
- explanation.extend(line.strip() for line in difflib.ndiff(left_formatting, right_formatting))
- return explanation
-
-
-def _compare_eq_sequence(left, right, verbose=False):
- explanation = []
- for i in range(min(len(left), len(right))):
- if left[i] != right[i]:
- explanation += [u('At index %s diff: %r != %r')
- % (i, left[i], right[i])]
- break
- if len(left) > len(right):
- explanation += [u('Left contains more items, first extra item: %s')
- % py.io.saferepr(left[len(right)],)]
- elif len(left) < len(right):
- explanation += [
- u('Right contains more items, first extra item: %s') %
- py.io.saferepr(right[len(left)],)]
- return explanation
-
-
-def _compare_eq_set(left, right, verbose=False):
- explanation = []
- diff_left = left - right
- diff_right = right - left
- if diff_left:
- explanation.append(u('Extra items in the left set:'))
- for item in diff_left:
- explanation.append(py.io.saferepr(item))
- if diff_right:
- explanation.append(u('Extra items in the right set:'))
- for item in diff_right:
- explanation.append(py.io.saferepr(item))
- return explanation
-
-
-def _compare_eq_dict(left, right, verbose=False):
- explanation = []
- common = set(left).intersection(set(right))
- same = dict((k, left[k]) for k in common if left[k] == right[k])
- if same and not verbose:
- explanation += [u('Omitting %s identical items, use -v to show') %
- len(same)]
- elif same:
- explanation += [u('Common items:')]
- explanation += pprint.pformat(same).splitlines()
- diff = set(k for k in common if left[k] != right[k])
- if diff:
- explanation += [u('Differing items:')]
- for k in diff:
- explanation += [py.io.saferepr({k: left[k]}) + ' != ' +
- py.io.saferepr({k: right[k]})]
- extra_left = set(left) - set(right)
- if extra_left:
- explanation.append(u('Left contains more items:'))
- explanation.extend(pprint.pformat(
- dict((k, left[k]) for k in extra_left)).splitlines())
- extra_right = set(right) - set(left)
- if extra_right:
- explanation.append(u('Right contains more items:'))
- explanation.extend(pprint.pformat(
- dict((k, right[k]) for k in extra_right)).splitlines())
- return explanation
-
-
-def _notin_text(term, text, verbose=False):
- index = text.find(term)
- head = text[:index]
- tail = text[index+len(term):]
- correct_text = head + tail
- diff = _diff_text(correct_text, text, verbose)
- newdiff = [u('%s is contained here:') % py.io.saferepr(term, maxsize=42)]
- for line in diff:
- if line.startswith(u('Skipping')):
- continue
- if line.startswith(u('- ')):
- continue
- if line.startswith(u('+ ')):
- newdiff.append(u(' ') + line[2:])
- else:
- newdiff.append(line)
- return newdiff
+++ /dev/null
-"""
-merged implementation of the cache provider
-
-the name cache was not choosen to ensure pluggy automatically
-ignores the external pytest-cache
-"""
-
-import py
-import pytest
-import json
-from os.path import sep as _sep, altsep as _altsep
-
-
-class Cache(object):
- def __init__(self, config):
- self.config = config
- self._cachedir = config.rootdir.join(".cache")
- self.trace = config.trace.root.get("cache")
- if config.getvalue("cacheclear"):
- self.trace("clearing cachedir")
- if self._cachedir.check():
- self._cachedir.remove()
- self._cachedir.mkdir()
-
- def makedir(self, name):
- """ return a directory path object with the given name. If the
- directory does not yet exist, it will be created. You can use it
- to manage files likes e. g. store/retrieve database
- dumps across test sessions.
-
- :param name: must be a string not containing a ``/`` separator.
- Make sure the name contains your plugin or application
- identifiers to prevent clashes with other cache users.
- """
- if _sep in name or _altsep is not None and _altsep in name:
- raise ValueError("name is not allowed to contain path separators")
- return self._cachedir.ensure_dir("d", name)
-
- def _getvaluepath(self, key):
- return self._cachedir.join('v', *key.split('/'))
-
- def get(self, key, default):
- """ return cached value for the given key. If no value
- was yet cached or the value cannot be read, the specified
- default is returned.
-
- :param key: must be a ``/`` separated value. Usually the first
- name is the name of your plugin or your application.
- :param default: must be provided in case of a cache-miss or
- invalid cache values.
-
- """
- path = self._getvaluepath(key)
- if path.check():
- try:
- with path.open("r") as f:
- return json.load(f)
- except ValueError:
- self.trace("cache-invalid at %s" % (path,))
- return default
-
- def set(self, key, value):
- """ save value for the given key.
-
- :param key: must be a ``/`` separated value. Usually the first
- name is the name of your plugin or your application.
- :param value: must be of any combination of basic
- python types, including nested types
- like e. g. lists of dictionaries.
- """
- path = self._getvaluepath(key)
- try:
- path.dirpath().ensure_dir()
- except (py.error.EEXIST, py.error.EACCES):
- self.config.warn(
- code='I9', message='could not create cache path %s' % (path,)
- )
- return
- try:
- f = path.open('w')
- except py.error.ENOTDIR:
- self.config.warn(
- code='I9', message='cache could not write path %s' % (path,))
- else:
- with f:
- self.trace("cache-write %s: %r" % (key, value,))
- json.dump(value, f, indent=2, sort_keys=True)
-
-
-class LFPlugin:
- """ Plugin which implements the --lf (run last-failing) option """
- def __init__(self, config):
- self.config = config
- active_keys = 'lf', 'failedfirst'
- self.active = any(config.getvalue(key) for key in active_keys)
- if self.active:
- self.lastfailed = config.cache.get("cache/lastfailed", {})
- else:
- self.lastfailed = {}
-
- def pytest_report_header(self):
- if self.active:
- if not self.lastfailed:
- mode = "run all (no recorded failures)"
- else:
- mode = "rerun last %d failures%s" % (
- len(self.lastfailed),
- " first" if self.config.getvalue("failedfirst") else "")
- return "run-last-failure: %s" % mode
-
- def pytest_runtest_logreport(self, report):
- if report.failed and "xfail" not in report.keywords:
- self.lastfailed[report.nodeid] = True
- elif not report.failed:
- if report.when == "call":
- self.lastfailed.pop(report.nodeid, None)
-
- def pytest_collectreport(self, report):
- passed = report.outcome in ('passed', 'skipped')
- if passed:
- if report.nodeid in self.lastfailed:
- self.lastfailed.pop(report.nodeid)
- self.lastfailed.update(
- (item.nodeid, True)
- for item in report.result)
- else:
- self.lastfailed[report.nodeid] = True
-
- def pytest_collection_modifyitems(self, session, config, items):
- if self.active and self.lastfailed:
- previously_failed = []
- previously_passed = []
- for item in items:
- if item.nodeid in self.lastfailed:
- previously_failed.append(item)
- else:
- previously_passed.append(item)
- if not previously_failed and previously_passed:
- # running a subset of all tests with recorded failures outside
- # of the set of tests currently executing
- pass
- elif self.config.getvalue("failedfirst"):
- items[:] = previously_failed + previously_passed
- else:
- items[:] = previously_failed
- config.hook.pytest_deselected(items=previously_passed)
-
- def pytest_sessionfinish(self, session):
- config = self.config
- if config.getvalue("cacheshow") or hasattr(config, "slaveinput"):
- return
- prev_failed = config.cache.get("cache/lastfailed", None) is not None
- if (session.testscollected and prev_failed) or self.lastfailed:
- config.cache.set("cache/lastfailed", self.lastfailed)
-
-
-def pytest_addoption(parser):
- group = parser.getgroup("general")
- group.addoption(
- '--lf', '--last-failed', action='store_true', dest="lf",
- help="rerun only the tests that failed "
- "at the last run (or all if none failed)")
- group.addoption(
- '--ff', '--failed-first', action='store_true', dest="failedfirst",
- help="run all tests but run the last failures first. "
- "This may re-order tests and thus lead to "
- "repeated fixture setup/teardown")
- group.addoption(
- '--cache-show', action='store_true', dest="cacheshow",
- help="show cache contents, don't perform collection or tests")
- group.addoption(
- '--cache-clear', action='store_true', dest="cacheclear",
- help="remove all cache contents at start of test run.")
-
-
-def pytest_cmdline_main(config):
- if config.option.cacheshow:
- from _pytest.main import wrap_session
- return wrap_session(config, cacheshow)
-
-
-
-@pytest.hookimpl(tryfirst=True)
-def pytest_configure(config):
- config.cache = Cache(config)
- config.pluginmanager.register(LFPlugin(config), "lfplugin")
-
-
-@pytest.fixture
-def cache(request):
- """
- Return a cache object that can persist state between testing sessions.
-
- cache.get(key, default)
- cache.set(key, value)
-
- Keys must be a ``/`` separated value, where the first part is usually the
- name of your plugin or application to avoid clashes with other cache users.
-
- Values can be any object handled by the json stdlib module.
- """
- return request.config.cache
-
-
-def pytest_report_header(config):
- if config.option.verbose:
- relpath = py.path.local().bestrelpath(config.cache._cachedir)
- return "cachedir: %s" % relpath
-
-
-def cacheshow(config, session):
- from pprint import pprint
- tw = py.io.TerminalWriter()
- tw.line("cachedir: " + str(config.cache._cachedir))
- if not config.cache._cachedir.check():
- tw.line("cache is empty")
- return 0
- dummy = object()
- basedir = config.cache._cachedir
- vdir = basedir.join("v")
- tw.sep("-", "cache values")
- for valpath in vdir.visit(lambda x: x.isfile()):
- key = valpath.relto(vdir).replace(valpath.sep, "/")
- val = config.cache.get(key, dummy)
- if val is dummy:
- tw.line("%s contains unreadable content, "
- "will be ignored" % key)
- else:
- tw.line("%s contains:" % key)
- stream = py.io.TextIO()
- pprint(val, stream=stream)
- for line in stream.getvalue().splitlines():
- tw.line(" " + line)
-
- ddir = basedir.join("d")
- if ddir.isdir() and ddir.listdir():
- tw.sep("-", "cache directories")
- for p in basedir.join("d").visit():
- #if p.check(dir=1):
- # print("%s/" % p.relto(basedir))
- if p.isfile():
- key = p.relto(basedir)
- tw.line("%s is a file of length %d" % (
- key, p.size()))
- return 0
+++ /dev/null
-"""
-per-test stdout/stderr capturing mechanism.
-
-"""
-from __future__ import with_statement
-
-import sys
-import os
-from tempfile import TemporaryFile
-
-import py
-import pytest
-
-from py.io import TextIO
-unicode = py.builtin.text
-
-patchsysdict = {0: 'stdin', 1: 'stdout', 2: 'stderr'}
-
-
-def pytest_addoption(parser):
- group = parser.getgroup("general")
- group._addoption(
- '--capture', action="store",
- default="fd" if hasattr(os, "dup") else "sys",
- metavar="method", choices=['fd', 'sys', 'no'],
- help="per-test capturing method: one of fd|sys|no.")
- group._addoption(
- '-s', action="store_const", const="no", dest="capture",
- help="shortcut for --capture=no.")
-
-
-@pytest.hookimpl(hookwrapper=True)
-def pytest_load_initial_conftests(early_config, parser, args):
- _readline_workaround()
- ns = early_config.known_args_namespace
- pluginmanager = early_config.pluginmanager
- capman = CaptureManager(ns.capture)
- pluginmanager.register(capman, "capturemanager")
-
- # make sure that capturemanager is properly reset at final shutdown
- early_config.add_cleanup(capman.reset_capturings)
-
- # make sure logging does not raise exceptions at the end
- def silence_logging_at_shutdown():
- if "logging" in sys.modules:
- sys.modules["logging"].raiseExceptions = False
- early_config.add_cleanup(silence_logging_at_shutdown)
-
- # finally trigger conftest loading but while capturing (issue93)
- capman.init_capturings()
- outcome = yield
- out, err = capman.suspendcapture()
- if outcome.excinfo is not None:
- sys.stdout.write(out)
- sys.stderr.write(err)
-
-
-class CaptureManager:
- def __init__(self, method):
- self._method = method
-
- def _getcapture(self, method):
- if method == "fd":
- return MultiCapture(out=True, err=True, Capture=FDCapture)
- elif method == "sys":
- return MultiCapture(out=True, err=True, Capture=SysCapture)
- elif method == "no":
- return MultiCapture(out=False, err=False, in_=False)
- else:
- raise ValueError("unknown capturing method: %r" % method)
-
- def init_capturings(self):
- assert not hasattr(self, "_capturing")
- self._capturing = self._getcapture(self._method)
- self._capturing.start_capturing()
-
- def reset_capturings(self):
- cap = self.__dict__.pop("_capturing", None)
- if cap is not None:
- cap.pop_outerr_to_orig()
- cap.stop_capturing()
-
- def resumecapture(self):
- self._capturing.resume_capturing()
-
- def suspendcapture(self, in_=False):
- self.deactivate_funcargs()
- cap = getattr(self, "_capturing", None)
- if cap is not None:
- try:
- outerr = cap.readouterr()
- finally:
- cap.suspend_capturing(in_=in_)
- return outerr
-
- def activate_funcargs(self, pyfuncitem):
- capfuncarg = pyfuncitem.__dict__.pop("_capfuncarg", None)
- if capfuncarg is not None:
- capfuncarg._start()
- self._capfuncarg = capfuncarg
-
- def deactivate_funcargs(self):
- capfuncarg = self.__dict__.pop("_capfuncarg", None)
- if capfuncarg is not None:
- capfuncarg.close()
-
- @pytest.hookimpl(hookwrapper=True)
- def pytest_make_collect_report(self, collector):
- if isinstance(collector, pytest.File):
- self.resumecapture()
- outcome = yield
- out, err = self.suspendcapture()
- rep = outcome.get_result()
- if out:
- rep.sections.append(("Captured stdout", out))
- if err:
- rep.sections.append(("Captured stderr", err))
- else:
- yield
-
- @pytest.hookimpl(hookwrapper=True)
- def pytest_runtest_setup(self, item):
- self.resumecapture()
- yield
- self.suspendcapture_item(item, "setup")
-
- @pytest.hookimpl(hookwrapper=True)
- def pytest_runtest_call(self, item):
- self.resumecapture()
- self.activate_funcargs(item)
- yield
- #self.deactivate_funcargs() called from suspendcapture()
- self.suspendcapture_item(item, "call")
-
- @pytest.hookimpl(hookwrapper=True)
- def pytest_runtest_teardown(self, item):
- self.resumecapture()
- yield
- self.suspendcapture_item(item, "teardown")
-
- @pytest.hookimpl(tryfirst=True)
- def pytest_keyboard_interrupt(self, excinfo):
- self.reset_capturings()
-
- @pytest.hookimpl(tryfirst=True)
- def pytest_internalerror(self, excinfo):
- self.reset_capturings()
-
- def suspendcapture_item(self, item, when):
- out, err = self.suspendcapture()
- item.add_report_section(when, "stdout", out)
- item.add_report_section(when, "stderr", err)
-
-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
- captured output available via ``capsys.readouterr()`` method calls
- which return a ``(out, err)`` tuple.
- """
- if "capfd" in request._funcargs:
- raise request.raiseerror(error_capsysfderror)
- request.node._capfuncarg = c = CaptureFixture(SysCapture)
- return c
-
-@pytest.fixture
-def capfd(request):
- """enables capturing of writes to file descriptors 1 and 2 and makes
- captured output available via ``capfd.readouterr()`` method calls
- which return a ``(out, err)`` tuple.
- """
- if "capsys" in request._funcargs:
- request.raiseerror(error_capsysfderror)
- if not hasattr(os, 'dup'):
- pytest.skip("capfd funcarg needs os.dup")
- request.node._capfuncarg = c = CaptureFixture(FDCapture)
- return c
-
-
-class CaptureFixture:
- def __init__(self, captureclass):
- self.captureclass = captureclass
-
- def _start(self):
- self._capture = MultiCapture(out=True, err=True, in_=False,
- Capture=self.captureclass)
- self._capture.start_capturing()
-
- def close(self):
- cap = self.__dict__.pop("_capture", None)
- if cap is not None:
- self._outerr = cap.pop_outerr_to_orig()
- cap.stop_capturing()
-
- def readouterr(self):
- try:
- return self._capture.readouterr()
- except AttributeError:
- return self._outerr
-
-
-def safe_text_dupfile(f, mode, default_encoding="UTF8"):
- """ return a open text file object that's a duplicate of f on the
- FD-level if possible.
- """
- encoding = getattr(f, "encoding", None)
- try:
- fd = f.fileno()
- except Exception:
- if "b" not in getattr(f, "mode", "") and hasattr(f, "encoding"):
- # we seem to have a text stream, let's just use it
- return f
- else:
- newfd = os.dup(fd)
- if "b" not in mode:
- mode += "b"
- f = os.fdopen(newfd, mode, 0) # no buffering
- return EncodedFile(f, encoding or default_encoding)
-
-
-class EncodedFile(object):
- errors = "strict" # possibly needed by py3 code (issue555)
- def __init__(self, buffer, encoding):
- self.buffer = buffer
- self.encoding = encoding
-
- def write(self, obj):
- if isinstance(obj, unicode):
- obj = obj.encode(self.encoding, "replace")
- self.buffer.write(obj)
-
- def writelines(self, linelist):
- data = ''.join(linelist)
- self.write(data)
-
- def __getattr__(self, name):
- return getattr(object.__getattribute__(self, "buffer"), name)
-
-
-class MultiCapture(object):
- out = err = in_ = None
-
- def __init__(self, out=True, err=True, in_=True, Capture=None):
- if in_:
- self.in_ = Capture(0)
- if out:
- self.out = Capture(1)
- if err:
- self.err = Capture(2)
-
- def start_capturing(self):
- if self.in_:
- self.in_.start()
- if self.out:
- self.out.start()
- if self.err:
- self.err.start()
-
- def pop_outerr_to_orig(self):
- """ pop current snapshot out/err capture and flush to orig streams. """
- out, err = self.readouterr()
- if out:
- self.out.writeorg(out)
- if err:
- self.err.writeorg(err)
- return out, err
-
- def suspend_capturing(self, in_=False):
- if self.out:
- self.out.suspend()
- if self.err:
- self.err.suspend()
- if in_ and self.in_:
- self.in_.suspend()
- self._in_suspended = True
-
- def resume_capturing(self):
- if self.out:
- self.out.resume()
- if self.err:
- self.err.resume()
- if hasattr(self, "_in_suspended"):
- self.in_.resume()
- del self._in_suspended
-
- def stop_capturing(self):
- """ stop capturing and reset capturing streams """
- if hasattr(self, '_reset'):
- raise ValueError("was already stopped")
- self._reset = True
- if self.out:
- self.out.done()
- if self.err:
- self.err.done()
- if self.in_:
- self.in_.done()
-
- def readouterr(self):
- """ return snapshot unicode value of stdout/stderr capturings. """
- return (self.out.snap() if self.out is not None else "",
- self.err.snap() if self.err is not None else "")
-
-class NoCapture:
- __init__ = start = done = suspend = resume = lambda *args: None
-
-class FDCapture:
- """ Capture IO to/from a given os-level filedescriptor. """
-
- def __init__(self, targetfd, tmpfile=None):
- self.targetfd = targetfd
- try:
- self.targetfd_save = os.dup(self.targetfd)
- except OSError:
- self.start = lambda: None
- self.done = lambda: None
- else:
- if targetfd == 0:
- assert not tmpfile, "cannot set tmpfile with stdin"
- tmpfile = open(os.devnull, "r")
- self.syscapture = SysCapture(targetfd)
- else:
- if tmpfile is None:
- f = TemporaryFile()
- with f:
- tmpfile = safe_text_dupfile(f, mode="wb+")
- if targetfd in patchsysdict:
- self.syscapture = SysCapture(targetfd, tmpfile)
- else:
- self.syscapture = NoCapture()
- self.tmpfile = tmpfile
- self.tmpfile_fd = tmpfile.fileno()
-
- def __repr__(self):
- return "<FDCapture %s oldfd=%s>" % (self.targetfd, self.targetfd_save)
-
- def start(self):
- """ Start capturing on targetfd using memorized tmpfile. """
- try:
- os.fstat(self.targetfd_save)
- except (AttributeError, OSError):
- raise ValueError("saved filedescriptor not valid anymore")
- os.dup2(self.tmpfile_fd, self.targetfd)
- self.syscapture.start()
-
- def snap(self):
- f = self.tmpfile
- f.seek(0)
- res = f.read()
- if res:
- enc = getattr(f, "encoding", None)
- if enc and isinstance(res, bytes):
- res = py.builtin._totext(res, enc, "replace")
- f.truncate(0)
- f.seek(0)
- return res
- return ''
-
- def done(self):
- """ stop capturing, restore streams, return original capture file,
- seeked to position zero. """
- targetfd_save = self.__dict__.pop("targetfd_save")
- os.dup2(targetfd_save, self.targetfd)
- os.close(targetfd_save)
- self.syscapture.done()
- self.tmpfile.close()
-
- def suspend(self):
- self.syscapture.suspend()
- os.dup2(self.targetfd_save, self.targetfd)
-
- def resume(self):
- self.syscapture.resume()
- os.dup2(self.tmpfile_fd, self.targetfd)
-
- def writeorg(self, data):
- """ write to original file descriptor. """
- if py.builtin._istext(data):
- data = data.encode("utf8") # XXX use encoding of original stream
- os.write(self.targetfd_save, data)
-
-
-class SysCapture:
- def __init__(self, fd, tmpfile=None):
- name = patchsysdict[fd]
- self._old = getattr(sys, name)
- self.name = name
- if tmpfile is None:
- if name == "stdin":
- tmpfile = DontReadFromInput()
- else:
- tmpfile = TextIO()
- self.tmpfile = tmpfile
-
- def start(self):
- setattr(sys, self.name, self.tmpfile)
-
- def snap(self):
- f = self.tmpfile
- res = f.getvalue()
- f.truncate(0)
- f.seek(0)
- return res
-
- def done(self):
- setattr(sys, self.name, self._old)
- del self._old
- self.tmpfile.close()
-
- def suspend(self):
- setattr(sys, self.name, self._old)
-
- def resume(self):
- setattr(sys, self.name, self.tmpfile)
-
- def writeorg(self, data):
- self._old.write(data)
- self._old.flush()
-
-
-class DontReadFromInput:
- """Temporary stub class. Ideally when stdin is accessed, the
- capturing should be turned off, with possibly all data captured
- so far sent to the screen. This should be configurable, though,
- because in automated test runs it is better to crash than
- hang indefinitely.
- """
-
- encoding = None
-
- def read(self, *args):
- raise IOError("reading from stdin while output is captured")
- readline = read
- readlines = read
- __iter__ = read
-
- def fileno(self):
- raise ValueError("redirected Stdin is pseudofile, has no fileno()")
-
- def isatty(self):
- return False
-
- def close(self):
- pass
-
-
-def _readline_workaround():
- """
- Ensure readline is imported so that it attaches to the correct stdio
- handles on Windows.
-
- Pdb uses readline support where available--when not running from the Python
- prompt, the readline module is not imported until running the pdb REPL. If
- running py.test with the --pdb option this means the readline module is not
- imported until after I/O capture has been started.
-
- This is a problem for pyreadline, which is often used to implement readline
- support on Windows, as it does not attach to the correct handles for stdout
- and/or stdin if they have been redirected by the FDCapture mechanism. This
- workaround ensures that readline is imported before I/O capture is setup so
- that it can attach to the actual stdin/out for the console.
-
- See https://github.com/pytest-dev/pytest/pull/1281
- """
-
- if not sys.platform.startswith('win32'):
- return
- try:
- import readline # noqa
- except ImportError:
- pass
+++ /dev/null
-""" command line options, ini-file and conftest.py processing. """
-import argparse
-import shlex
-import traceback
-import types
-import warnings
-
-import py
-# DON't import pytest here because it causes import cycle troubles
-import sys, os
-import _pytest._code
-import _pytest.hookspec # the extension point definitions
-from _pytest._pluggy import PluginManager, HookimplMarker, HookspecMarker
-
-hookimpl = HookimplMarker("pytest")
-hookspec = HookspecMarker("pytest")
-
-# pytest startup
-#
-
-
-class ConftestImportFailure(Exception):
- def __init__(self, path, excinfo):
- Exception.__init__(self, path, excinfo)
- self.path = path
- self.excinfo = excinfo
-
-
-def main(args=None, plugins=None):
- """ return exit code, after performing an in-process test run.
-
- :arg args: list of command line arguments.
-
- :arg plugins: list of plugin objects to be auto-registered during
- initialization.
- """
- try:
- try:
- config = _prepareconfig(args, plugins)
- except ConftestImportFailure as e:
- tw = py.io.TerminalWriter(sys.stderr)
- for line in traceback.format_exception(*e.excinfo):
- tw.line(line.rstrip(), red=True)
- tw.line("ERROR: could not load %s\n" % (e.path), red=True)
- return 4
- else:
- try:
- config.pluginmanager.check_pending()
- return config.hook.pytest_cmdline_main(config=config)
- finally:
- config._ensure_unconfigure()
- except UsageError as e:
- for msg in e.args:
- sys.stderr.write("ERROR: %s\n" %(msg,))
- return 4
-
-class cmdline: # compatibility namespace
- main = staticmethod(main)
-
-class UsageError(Exception):
- """ error in pytest usage or invocation"""
-
-_preinit = []
-
-default_plugins = (
- "mark main terminal runner python pdb unittest capture skipping "
- "tmpdir monkeypatch recwarn pastebin helpconfig nose assertion genscript "
- "junitxml resultlog doctest cacheprovider").split()
-
-builtin_plugins = set(default_plugins)
-builtin_plugins.add("pytester")
-
-
-def _preloadplugins():
- assert not _preinit
- _preinit.append(get_config())
-
-def get_config():
- if _preinit:
- return _preinit.pop(0)
- # subsequent calls to main will create a fresh instance
- pluginmanager = PytestPluginManager()
- config = Config(pluginmanager)
- for spec in default_plugins:
- pluginmanager.import_plugin(spec)
- return config
-
-def get_plugin_manager():
- """
- Obtain a new instance of the
- :py:class:`_pytest.config.PytestPluginManager`, with default plugins
- already loaded.
-
- This function can be used by integration with other tools, like hooking
- into pytest to run tests into an IDE.
- """
- return get_config().pluginmanager
-
-def _prepareconfig(args=None, plugins=None):
- if args is None:
- args = sys.argv[1:]
- elif isinstance(args, py.path.local):
- args = [str(args)]
- 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)
- config = get_config()
- pluginmanager = config.pluginmanager
- try:
- if plugins:
- for plugin in plugins:
- if isinstance(plugin, py.builtin._basestring):
- pluginmanager.consider_pluginarg(plugin)
- else:
- pluginmanager.register(plugin)
- return pluginmanager.hook.pytest_cmdline_parse(
- pluginmanager=pluginmanager, args=args)
- except BaseException:
- config._ensure_unconfigure()
- raise
-
-
-class PytestPluginManager(PluginManager):
- """
- Overwrites :py:class:`pluggy.PluginManager` to add pytest-specific
- functionality:
-
- * loading plugins from the command line, ``PYTEST_PLUGIN`` env variable and
- ``pytest_plugins`` global variables found in plugins being loaded;
- * ``conftest.py`` loading during start-up;
- """
- def __init__(self):
- super(PytestPluginManager, self).__init__("pytest", implprefix="pytest_")
- self._conftest_plugins = set()
-
- # state related to local conftest plugins
- self._path2confmods = {}
- self._conftestpath2mod = {}
- self._confcutdir = None
- self._noconftest = False
-
- self.add_hookspecs(_pytest.hookspec)
- self.register(self)
- if os.environ.get('PYTEST_DEBUG'):
- err = sys.stderr
- encoding = getattr(err, 'encoding', 'utf8')
- try:
- err = py.io.dupfile(err, encoding=encoding)
- except Exception:
- pass
- self.trace.root.setwriter(err.write)
- self.enable_tracing()
-
- def addhooks(self, module_or_class):
- """
- .. deprecated:: 2.8
-
- Use :py:meth:`pluggy.PluginManager.add_hookspecs` instead.
- """
- warning = dict(code="I2",
- fslocation=_pytest._code.getfslineno(sys._getframe(1)),
- nodeid=None,
- message="use pluginmanager.add_hookspecs instead of "
- "deprecated addhooks() method.")
- self._warn(warning)
- return self.add_hookspecs(module_or_class)
-
- def parse_hookimpl_opts(self, plugin, name):
- # pytest hooks are always prefixed with pytest_
- # so we avoid accessing possibly non-readable attributes
- # (see issue #1073)
- if not name.startswith("pytest_"):
- return
- # ignore some historic special names which can not be hooks anyway
- if name == "pytest_plugins" or name.startswith("pytest_funcarg__"):
- return
-
- method = getattr(plugin, name)
- opts = super(PytestPluginManager, self).parse_hookimpl_opts(plugin, name)
- if opts is not None:
- for name in ("tryfirst", "trylast", "optionalhook", "hookwrapper"):
- opts.setdefault(name, hasattr(method, name))
- return opts
-
- def parse_hookspec_opts(self, module_or_class, name):
- opts = super(PytestPluginManager, self).parse_hookspec_opts(
- module_or_class, name)
- if opts is None:
- method = getattr(module_or_class, name)
- if name.startswith("pytest_"):
- opts = {"firstresult": hasattr(method, "firstresult"),
- "historic": hasattr(method, "historic")}
- return opts
-
- def _verify_hook(self, hook, hookmethod):
- super(PytestPluginManager, self)._verify_hook(hook, hookmethod)
- if "__multicall__" in hookmethod.argnames:
- fslineno = _pytest._code.getfslineno(hookmethod.function)
- warning = dict(code="I1",
- fslocation=fslineno,
- nodeid=None,
- message="%r hook uses deprecated __multicall__ "
- "argument" % (hook.name))
- self._warn(warning)
-
- def register(self, plugin, name=None):
- ret = super(PytestPluginManager, self).register(plugin, name)
- if ret:
- self.hook.pytest_plugin_registered.call_historic(
- kwargs=dict(plugin=plugin, manager=self))
- return ret
-
- def getplugin(self, name):
- # support deprecated naming because plugins (xdist e.g.) use it
- return self.get_plugin(name)
-
- def hasplugin(self, name):
- """Return True if the plugin with the given name is registered."""
- return bool(self.get_plugin(name))
-
- def pytest_configure(self, config):
- # XXX now that the pluginmanager exposes hookimpl(tryfirst...)
- # we should remove tryfirst/trylast as markers
- config.addinivalue_line("markers",
- "tryfirst: mark a hook implementation function such that the "
- "plugin machinery will try to call it first/as early as possible.")
- config.addinivalue_line("markers",
- "trylast: mark a hook implementation function such that the "
- "plugin machinery will try to call it last/as late as possible.")
-
- def _warn(self, message):
- kwargs = message if isinstance(message, dict) else {
- 'code': 'I1',
- 'message': message,
- 'fslocation': None,
- 'nodeid': None,
- }
- self.hook.pytest_logwarning.call_historic(kwargs=kwargs)
-
- #
- # internal API for local conftest plugin handling
- #
- def _set_initial_conftests(self, namespace):
- """ load initial conftest files given a preparsed "namespace".
- As conftest files may add their own command line options
- which have arguments ('--my-opt somepath') we might get some
- false positives. All builtin and 3rd party plugins will have
- been loaded, however, so common options will not confuse our logic
- here.
- """
- current = py.path.local()
- self._confcutdir = current.join(namespace.confcutdir, abs=True) \
- if namespace.confcutdir else None
- self._noconftest = namespace.noconftest
- testpaths = namespace.file_or_dir
- foundanchor = False
- for path in testpaths:
- path = str(path)
- # remove node-id syntax
- i = path.find("::")
- if i != -1:
- path = path[:i]
- anchor = current.join(path, abs=1)
- if exists(anchor): # we found some file object
- self._try_load_conftest(anchor)
- foundanchor = True
- if not foundanchor:
- self._try_load_conftest(current)
-
- def _try_load_conftest(self, anchor):
- self._getconftestmodules(anchor)
- # let's also consider test* subdirs
- if anchor.check(dir=1):
- for x in anchor.listdir("test*"):
- if x.check(dir=1):
- self._getconftestmodules(x)
-
- def _getconftestmodules(self, path):
- if self._noconftest:
- return []
- try:
- return self._path2confmods[path]
- except KeyError:
- if path.isfile():
- clist = self._getconftestmodules(path.dirpath())
- else:
- # XXX these days we may rather want to use config.rootdir
- # and allow users to opt into looking into the rootdir parent
- # directories instead of requiring to specify confcutdir
- clist = []
- for parent in path.parts():
- if self._confcutdir and self._confcutdir.relto(parent):
- continue
- conftestpath = parent.join("conftest.py")
- if conftestpath.isfile():
- mod = self._importconftest(conftestpath)
- clist.append(mod)
-
- self._path2confmods[path] = clist
- return clist
-
- def _rget_with_confmod(self, name, path):
- modules = self._getconftestmodules(path)
- for mod in reversed(modules):
- try:
- return mod, getattr(mod, name)
- except AttributeError:
- continue
- raise KeyError(name)
-
- def _importconftest(self, conftestpath):
- try:
- return self._conftestpath2mod[conftestpath]
- except KeyError:
- pkgpath = conftestpath.pypkgpath()
- if pkgpath is None:
- _ensure_removed_sysmodule(conftestpath.purebasename)
- try:
- mod = conftestpath.pyimport()
- except Exception:
- raise ConftestImportFailure(conftestpath, sys.exc_info())
-
- self._conftest_plugins.add(mod)
- self._conftestpath2mod[conftestpath] = mod
- dirpath = conftestpath.dirpath()
- if dirpath in self._path2confmods:
- for path, mods in self._path2confmods.items():
- if path and path.relto(dirpath) or path == dirpath:
- assert mod not in mods
- mods.append(mod)
- self.trace("loaded conftestmodule %r" %(mod))
- self.consider_conftest(mod)
- return mod
-
- #
- # API for bootstrapping plugin loading
- #
- #
-
- def consider_preparse(self, args):
- for opt1,opt2 in zip(args, args[1:]):
- if opt1 == "-p":
- self.consider_pluginarg(opt2)
-
- def consider_pluginarg(self, arg):
- if arg.startswith("no:"):
- name = arg[3:]
- self.set_blocked(name)
- if not name.startswith("pytest_"):
- self.set_blocked("pytest_" + name)
- else:
- self.import_plugin(arg)
-
- def consider_conftest(self, conftestmodule):
- if self.register(conftestmodule, name=conftestmodule.__file__):
- self.consider_module(conftestmodule)
-
- def consider_env(self):
- self._import_plugin_specs(os.environ.get("PYTEST_PLUGINS"))
-
- def consider_module(self, mod):
- self._import_plugin_specs(getattr(mod, "pytest_plugins", None))
-
- def _import_plugin_specs(self, spec):
- if spec:
- if isinstance(spec, str):
- spec = spec.split(",")
- for import_spec in spec:
- self.import_plugin(import_spec)
-
- def import_plugin(self, modname):
- # most often modname refers to builtin modules, e.g. "pytester",
- # "terminal" or "capture". Those plugins are registered under their
- # basename for historic purposes but must be imported with the
- # _pytest prefix.
- assert isinstance(modname, str)
- if self.get_plugin(modname) is not None:
- return
- if modname in builtin_plugins:
- importspec = "_pytest." + modname
- else:
- importspec = modname
- try:
- __import__(importspec)
- except ImportError as e:
- new_exc = ImportError('Error importing plugin "%s": %s' % (modname, e))
- # copy over name and path attributes
- for attr in ('name', 'path'):
- if hasattr(e, attr):
- setattr(new_exc, attr, getattr(e, attr))
- raise new_exc
- except Exception as e:
- import pytest
- if not hasattr(pytest, 'skip') or not isinstance(e, pytest.skip.Exception):
- raise
- self._warn("skipped plugin %r: %s" %((modname, e.msg)))
- else:
- mod = sys.modules[importspec]
- self.register(mod, modname)
- self.consider_module(mod)
-
-
-class Parser:
- """ Parser for command line arguments and ini-file values.
-
- :ivar extra_info: dict of generic param -> value to display in case
- there's an error processing the command line arguments.
- """
-
- def __init__(self, usage=None, processopt=None):
- self._anonymous = OptionGroup("custom options", parser=self)
- self._groups = []
- self._processopt = processopt
- self._usage = usage
- self._inidict = {}
- self._ininames = []
- self.extra_info = {}
-
- def processoption(self, option):
- if self._processopt:
- if option.dest:
- self._processopt(option)
-
- def getgroup(self, name, description="", after=None):
- """ get (or create) a named option Group.
-
- :name: name of the option group.
- :description: long description for --help output.
- :after: name of other group, used for ordering --help output.
-
- The returned group object has an ``addoption`` method with the same
- signature as :py:func:`parser.addoption
- <_pytest.config.Parser.addoption>` but will be shown in the
- respective group in the output of ``pytest. --help``.
- """
- for group in self._groups:
- if group.name == name:
- return group
- group = OptionGroup(name, description, parser=self)
- i = 0
- for i, grp in enumerate(self._groups):
- if grp.name == after:
- break
- self._groups.insert(i+1, group)
- return group
-
- def addoption(self, *opts, **attrs):
- """ register a command line option.
-
- :opts: option names, can be short or long options.
- :attrs: same attributes which the ``add_option()`` function of the
- `argparse library
- <http://docs.python.org/2/library/argparse.html>`_
- accepts.
-
- After command line parsing options are available on the pytest config
- object via ``config.option.NAME`` where ``NAME`` is usually set
- by passing a ``dest`` attribute, for example
- ``addoption("--long", dest="NAME", ...)``.
- """
- self._anonymous.addoption(*opts, **attrs)
-
- def parse(self, args, namespace=None):
- from _pytest._argcomplete import try_argcomplete
- self.optparser = self._getparser()
- try_argcomplete(self.optparser)
- return self.optparser.parse_args([str(x) for x in args], namespace=namespace)
-
- def _getparser(self):
- from _pytest._argcomplete import filescompleter
- optparser = MyOptionParser(self, self.extra_info)
- groups = self._groups + [self._anonymous]
- for group in groups:
- if group.options:
- desc = group.description or group.name
- arggroup = optparser.add_argument_group(desc)
- for option in group.options:
- n = option.names()
- a = option.attrs()
- arggroup.add_argument(*n, **a)
- # bash like autocompletion for dirs (appending '/')
- optparser.add_argument(FILE_OR_DIR, nargs='*').completer=filescompleter
- return optparser
-
- def parse_setoption(self, args, option, namespace=None):
- parsedoption = self.parse(args, namespace=namespace)
- for name, value in parsedoption.__dict__.items():
- setattr(option, name, value)
- return getattr(parsedoption, FILE_OR_DIR)
-
- def parse_known_args(self, args, namespace=None):
- """parses and returns a namespace object with known arguments at this
- point.
- """
- return self.parse_known_and_unknown_args(args, namespace=namespace)[0]
-
- def parse_known_and_unknown_args(self, args, namespace=None):
- """parses and returns a namespace object with known arguments, and
- the remaining arguments unknown at this point.
- """
- optparser = self._getparser()
- args = [str(x) for x in args]
- return optparser.parse_known_args(args, namespace=namespace)
-
- def addini(self, name, help, type=None, default=None):
- """ register an ini-file option.
-
- :name: name of the ini-variable
- :type: type of the variable, can be ``pathlist``, ``args``, ``linelist``
- or ``bool``.
- :default: default value if no ini-file option exists but is queried.
-
- The value of ini-variables can be retrieved via a call to
- :py:func:`config.getini(name) <_pytest.config.Config.getini>`.
- """
- assert type in (None, "pathlist", "args", "linelist", "bool")
- self._inidict[name] = (help, type, default)
- self._ininames.append(name)
-
-
-class ArgumentError(Exception):
- """
- Raised if an Argument instance is created with invalid or
- inconsistent arguments.
- """
-
- def __init__(self, msg, option):
- self.msg = msg
- self.option_id = str(option)
-
- def __str__(self):
- if self.option_id:
- return "option %s: %s" % (self.option_id, self.msg)
- else:
- return self.msg
-
-
-class Argument:
- """class that mimics the necessary behaviour of optparse.Option """
- _typ_map = {
- 'int': int,
- 'string': str,
- }
- # enable after some grace period for plugin writers
- TYPE_WARN = False
-
- def __init__(self, *names, **attrs):
- """store parms in private vars for use in add_argument"""
- self._attrs = attrs
- self._short_opts = []
- self._long_opts = []
- self.dest = attrs.get('dest')
- if self.TYPE_WARN:
- try:
- help = attrs['help']
- if