commit
e67d66a5d4
|
@ -0,0 +1 @@
|
|||
CHANGELOG merge=union
|
|
@ -17,6 +17,11 @@ include/
|
|||
*.orig
|
||||
*~
|
||||
|
||||
.eggs/
|
||||
|
||||
# this file is managed by setuptools_scm
|
||||
_pytest/__init__.py
|
||||
|
||||
doc/*/_build
|
||||
build/
|
||||
dist/
|
||||
|
|
|
@ -28,7 +28,7 @@ env:
|
|||
- TESTENV=py35
|
||||
- TESTENV=pypy
|
||||
|
||||
script: tox --recreate -i ALL=https://devpi.net/hpk/dev/ -e $TESTENV
|
||||
script: tox --recreate -e $TESTENV
|
||||
|
||||
notifications:
|
||||
irc:
|
||||
|
|
7
AUTHORS
7
AUTHORS
|
@ -3,6 +3,7 @@ merlinux GmbH, Germany, office at merlinux eu
|
|||
|
||||
Contributors include::
|
||||
|
||||
Abhijeet Kasurde
|
||||
Anatoly Bubenkoff
|
||||
Andreas Zeidler
|
||||
Andy Freeland
|
||||
|
@ -14,6 +15,7 @@ Bob Ippolito
|
|||
Brian Dorsey
|
||||
Brian Okken
|
||||
Brianna Laugher
|
||||
Bruno Oliveira
|
||||
Carl Friedrich Bolz
|
||||
Charles Cloud
|
||||
Chris Lamb
|
||||
|
@ -24,11 +26,12 @@ Daniel Grana
|
|||
Daniel Nuri
|
||||
Dave Hunt
|
||||
David Mohr
|
||||
Edison Gustavo Muenz
|
||||
Eduardo Schettino
|
||||
Elizaveta Shashkova
|
||||
Eric Hunsberger
|
||||
Eric Siegerman
|
||||
Florian Bruhin
|
||||
Edison Gustavo Muenz
|
||||
Floris Bruynooghe
|
||||
Graham Horler
|
||||
Grig Gheorghiu
|
||||
|
@ -46,6 +49,7 @@ Maciek Fijalkowski
|
|||
Maho
|
||||
Marc Schlaich
|
||||
Mark Abramowitz
|
||||
Markus Unterwaditzer
|
||||
Martijn Faassen
|
||||
Nicolas Delaby
|
||||
Pieter Mulder
|
||||
|
@ -58,3 +62,4 @@ Samuele Pedroni
|
|||
Tom Viner
|
||||
Trevor Bekolay
|
||||
Wouter van Ackooy
|
||||
David Díaz-Barquero
|
||||
|
|
48
CHANGELOG
48
CHANGELOG
|
@ -1,6 +1,40 @@
|
|||
2.8.0.dev (compared to 2.7.X)
|
||||
-----------------------------
|
||||
|
||||
- Fix #562: @nose.tools.istest now fully respected.
|
||||
|
||||
- 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.
|
||||
|
||||
|
@ -24,6 +58,15 @@
|
|||
- 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
|
||||
|
@ -103,6 +146,11 @@
|
|||
|
||||
- fix issue714: add ability to apply indirect=True parameter on particular argnames.
|
||||
|
||||
- fix issue890: changed extension of all documentation files from ``txt`` to
|
||||
``rst``. Thanks to Abhijeet for the PR.
|
||||
|
||||
- issue951: add new record_xml_property fixture, that supports logging
|
||||
additional information on xml output. Thanks David Diaz for the PR.
|
||||
|
||||
2.7.3 (compared to 2.7.2)
|
||||
-----------------------------
|
||||
|
|
|
@ -21,7 +21,7 @@ in repositories living under:
|
|||
|
||||
- `the pytest-dev bitbucket team <https://bitbucket.org/pytest-dev>`_
|
||||
|
||||
All pytest-dev team members have write access to all contained
|
||||
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.
|
||||
|
||||
|
@ -46,9 +46,9 @@ the following:
|
|||
|
||||
If no contributor strongly objects and two agree, the repo will be
|
||||
transferred to the ``pytest-dev`` organisation and you'll become a
|
||||
member of the ``pytest-dev`` team, with commit rights to all projects.
|
||||
We recommend that each plugin has at least three people who have the
|
||||
right to release to pypi.
|
||||
member of the ``pytest-dev Contributors`` team, with commit rights
|
||||
to all projects. We recommend that each plugin has at least three
|
||||
people who have the right to release to pypi.
|
||||
|
||||
|
||||
.. _reportbugs:
|
||||
|
@ -66,6 +66,10 @@ If you are reporting a bug, please include:
|
|||
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.
|
||||
|
||||
.. _submitfeedback:
|
||||
|
||||
Submit feedback for developers
|
||||
|
@ -93,6 +97,8 @@ 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
|
||||
|
@ -111,10 +117,14 @@ 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 English and Japanese versions.
|
||||
* Documentation translations. We currently have only English.
|
||||
* Docstrings. There's never too much 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.
|
||||
|
||||
.. _`pull requests`:
|
||||
.. _pull-requests:
|
||||
|
||||
|
@ -181,9 +191,13 @@ but here is a simple overview:
|
|||
$ 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:
|
||||
|
||||
.. image:: img/pullrequest.png
|
||||
.. image:: doc/en/img/pullrequest.png
|
||||
:width: 700px
|
||||
:align: center
|
||||
|
||||
|
|
|
@ -1,58 +1,87 @@
|
|||
|
||||
How to release pytest (draft)
|
||||
How to release pytest
|
||||
--------------------------------------------
|
||||
|
||||
1. bump version numbers in _pytest/__init__.py (setup.py reads it)
|
||||
Note: this assumes you have already registered on pypi.
|
||||
|
||||
2. check and finalize CHANGELOG
|
||||
1. Bump version numbers in _pytest/__init__.py (setup.py reads it)
|
||||
|
||||
3. write doc/en/announce/release-VERSION.txt and include
|
||||
2. Check and finalize CHANGELOG
|
||||
|
||||
3. Write doc/en/announce/release-VERSION.txt and include
|
||||
it in doc/en/announce/index.txt
|
||||
|
||||
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``
|
||||
4. Use devpi for uploading a release tarball to a staging area:
|
||||
|
||||
5. run from multiple machines:
|
||||
- ``devpi use https://devpi.net/USER/dev``
|
||||
- ``devpi test pytest==VERSION``
|
||||
``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
|
||||
|
||||
6. check that tests pass for relevant combinations with
|
||||
``devpi list pytest``
|
||||
|
||||
or look at failures with "devpi list -f pytest".
|
||||
There will be some failed environments like e.g. the py33-trial
|
||||
or py27-pexpect tox environments on Win32 platforms
|
||||
which is ok (tox does not support skipping on
|
||||
per-platform basis yet).
|
||||
|
||||
7. Regenerate the docs examples using tox::
|
||||
# Create and activate a virtualenv with regendoc installed
|
||||
# (currently needs revision 4a9ec1035734)
|
||||
tox -e regen
|
||||
7. Regenerate the docs examples using tox, and check for regressions::
|
||||
|
||||
8. Build the docs, you need a virtualenv with, py and sphinx
|
||||
tox -e regen
|
||||
git diff
|
||||
|
||||
|
||||
8. Build the docs, you need a virtualenv with py and sphinx
|
||||
installed::
|
||||
cd docs/en
|
||||
|
||||
cd doc/en
|
||||
make html
|
||||
|
||||
9. Tag the release::
|
||||
hg tag VERSION
|
||||
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
|
||||
|
||||
10. Upload the docs using docs/en/Makefile::
|
||||
cd docs/en
|
||||
make install # or "installall" if you have LaTeX installed
|
||||
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.
|
||||
|
||||
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 -- it's the same you would use with
|
||||
"setup.py upload -r NAME"
|
||||
If you are making a minor release (e.g. 5.4), you also need to manually
|
||||
create a symlink for "latest"::
|
||||
|
||||
12. send release announcement to mailing lists:
|
||||
ssh pytest-dev@pytest.org
|
||||
ln -s 5.4 latest
|
||||
|
||||
pytest-dev
|
||||
testing-in-python
|
||||
python-announce-list@python.org
|
||||
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``).
|
||||
|
|
4
Makefile
4
Makefile
|
@ -21,11 +21,11 @@ clean:
|
|||
|
||||
# generate documentation
|
||||
docs: develop
|
||||
find doc/en -name '*.txt' -not -path 'doc/en/_build/*' | xargs .env/bin/regendoc ${REGENDOC_ARGS}
|
||||
find doc/en -name '*.rst' -not -path 'doc/en/_build/*' | xargs .env/bin/regendoc ${REGENDOC_ARGS}
|
||||
cd doc/en; make html
|
||||
|
||||
# upload documentation
|
||||
upload-docs: develop
|
||||
find doc/en -name '*.txt' -not -path 'doc/en/_build/*' | xargs .env/bin/regendoc ${REGENDOC_ARGS} --update
|
||||
find doc/en -name '*.rst' -not -path 'doc/en/_build/*' | xargs .env/bin/regendoc ${REGENDOC_ARGS} --update
|
||||
#cd doc/en; make install
|
||||
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
#
|
||||
__version__ = '2.8.0.dev4'
|
|
@ -86,7 +86,9 @@ class CaptureManager:
|
|||
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
|
||||
|
||||
|
|
|
@ -211,6 +211,10 @@ class PytestPluginManager(PluginManager):
|
|||
# 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
|
||||
|
@ -897,6 +901,9 @@ class Config(object):
|
|||
self.warn("I2", "could not load setuptools entry import: %s" % (e,))
|
||||
self.pluginmanager.consider_env()
|
||||
self.known_args_namespace = ns = self._parser.parse_known_args(args)
|
||||
if self.known_args_namespace.confcutdir is None and self.inifile:
|
||||
confcutdir = py.path.local(self.inifile).dirname
|
||||
self.known_args_namespace.confcutdir = confcutdir
|
||||
try:
|
||||
self.hook.pytest_load_initial_conftests(early_config=self,
|
||||
args=args, parser=self._parser)
|
||||
|
|
|
@ -63,7 +63,7 @@ class DoctestItem(pytest.Item):
|
|||
lineno = test.lineno + example.lineno + 1
|
||||
message = excinfo.type.__name__
|
||||
reprlocation = ReprFileLocation(filename, lineno, message)
|
||||
checker = doctest.OutputChecker()
|
||||
checker = _get_unicode_checker()
|
||||
REPORT_UDIFF = doctest.REPORT_UDIFF
|
||||
filelines = py.path.local(filename).readlines(cr=0)
|
||||
lines = []
|
||||
|
@ -100,7 +100,8 @@ def _get_flag_lookup():
|
|||
NORMALIZE_WHITESPACE=doctest.NORMALIZE_WHITESPACE,
|
||||
ELLIPSIS=doctest.ELLIPSIS,
|
||||
IGNORE_EXCEPTION_DETAIL=doctest.IGNORE_EXCEPTION_DETAIL,
|
||||
COMPARISON_FLAGS=doctest.COMPARISON_FLAGS)
|
||||
COMPARISON_FLAGS=doctest.COMPARISON_FLAGS,
|
||||
ALLOW_UNICODE=_get_allow_unicode_flag())
|
||||
|
||||
def get_optionflags(parent):
|
||||
optionflags_str = parent.config.getini("doctest_optionflags")
|
||||
|
@ -110,15 +111,30 @@ def get_optionflags(parent):
|
|||
flag_acc |= flag_lookup_table[flag]
|
||||
return flag_acc
|
||||
|
||||
|
||||
class DoctestTextfile(DoctestItem, pytest.File):
|
||||
|
||||
def runtest(self):
|
||||
import doctest
|
||||
fixture_request = _setup_fixtures(self)
|
||||
failed, tot = doctest.testfile(
|
||||
str(self.fspath), module_relative=False,
|
||||
optionflags=get_optionflags(self),
|
||||
extraglobs=dict(getfixture=fixture_request.getfuncargvalue),
|
||||
raise_on_error=True, verbose=0)
|
||||
|
||||
# inspired by doctest.testfile; ideally we would use it directly,
|
||||
# but it doesn't support passing a custom checker
|
||||
text = self.fspath.read()
|
||||
filename = str(self.fspath)
|
||||
name = self.fspath.basename
|
||||
globs = dict(getfixture=fixture_request.getfuncargvalue)
|
||||
if '__name__' not in globs:
|
||||
globs['__name__'] = '__main__'
|
||||
|
||||
optionflags = get_optionflags(self)
|
||||
runner = doctest.DebugRunner(verbose=0, optionflags=optionflags,
|
||||
checker=_get_unicode_checker())
|
||||
|
||||
parser = doctest.DocTestParser()
|
||||
test = parser.get_doctest(text, globs, name, filename, 0)
|
||||
runner.run(test)
|
||||
|
||||
|
||||
class DoctestModule(pytest.File):
|
||||
def collect(self):
|
||||
|
@ -139,7 +155,8 @@ class DoctestModule(pytest.File):
|
|||
# uses internal doctest module parsing mechanism
|
||||
finder = doctest.DocTestFinder()
|
||||
optionflags = get_optionflags(self)
|
||||
runner = doctest.DebugRunner(verbose=0, optionflags=optionflags)
|
||||
runner = doctest.DebugRunner(verbose=0, optionflags=optionflags,
|
||||
checker=_get_unicode_checker())
|
||||
for test in finder.find(module, module.__name__,
|
||||
extraglobs=doctest_globals):
|
||||
if test.examples: # skip empty doctests
|
||||
|
@ -160,3 +177,59 @@ def _setup_fixtures(doctest_item):
|
|||
fixture_request = FixtureRequest(doctest_item)
|
||||
fixture_request._fillfixtures()
|
||||
return fixture_request
|
||||
|
||||
|
||||
def _get_unicode_checker():
|
||||
"""
|
||||
Returns a doctest.OutputChecker subclass that takes in account the
|
||||
ALLOW_UNICODE option to ignore u'' prefixes in strings. Useful
|
||||
when the same doctest should run in Python 2 and Python 3.
|
||||
|
||||
An inner class is used to avoid importing "doctest" at the module
|
||||
level.
|
||||
"""
|
||||
if hasattr(_get_unicode_checker, 'UnicodeOutputChecker'):
|
||||
return _get_unicode_checker.UnicodeOutputChecker()
|
||||
|
||||
import doctest
|
||||
import re
|
||||
|
||||
class UnicodeOutputChecker(doctest.OutputChecker):
|
||||
"""
|
||||
Copied from doctest_nose_plugin.py from the nltk project:
|
||||
https://github.com/nltk/nltk
|
||||
"""
|
||||
|
||||
_literal_re = re.compile(r"(\W|^)[uU]([rR]?[\'\"])", re.UNICODE)
|
||||
|
||||
def check_output(self, want, got, optionflags):
|
||||
res = doctest.OutputChecker.check_output(self, want, got,
|
||||
optionflags)
|
||||
if res:
|
||||
return True
|
||||
|
||||
if not (optionflags & _get_allow_unicode_flag()):
|
||||
return False
|
||||
|
||||
else: # pragma: no cover
|
||||
# the code below will end up executed only in Python 2 in
|
||||
# our tests, and our coverage check runs in Python 3 only
|
||||
def remove_u_prefixes(txt):
|
||||
return re.sub(self._literal_re, r'\1\2', txt)
|
||||
|
||||
want = remove_u_prefixes(want)
|
||||
got = remove_u_prefixes(got)
|
||||
res = doctest.OutputChecker.check_output(self, want, got,
|
||||
optionflags)
|
||||
return res
|
||||
|
||||
_get_unicode_checker.UnicodeOutputChecker = UnicodeOutputChecker
|
||||
return _get_unicode_checker.UnicodeOutputChecker()
|
||||
|
||||
|
||||
def _get_allow_unicode_flag():
|
||||
"""
|
||||
Registers and returns the ALLOW_UNICODE flag.
|
||||
"""
|
||||
import doctest
|
||||
return doctest.register_optionflag('ALLOW_UNICODE')
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
""" generate a single-file self-contained version of pytest """
|
||||
""" (deprecated) generate a single-file self-contained version of pytest """
|
||||
import os
|
||||
import sys
|
||||
import pkgutil
|
||||
|
@ -31,6 +31,11 @@ def pkg_to_mapping(name):
|
|||
else: # package
|
||||
for pyfile in toplevel.visit('*.py'):
|
||||
pkg = pkgname(name, toplevel, pyfile)
|
||||
if pkg == '_pytest.__init__':
|
||||
# remove the coding comment line to avoid python bug
|
||||
lines = pyfile.read().splitlines(True)
|
||||
name2src[pkg] = ''.join(lines[1:])
|
||||
else:
|
||||
name2src[pkg] = pyfile.read()
|
||||
# with wheels py source code might be not be installed
|
||||
# and the resulting genscript is useless, just bail out.
|
||||
|
@ -72,6 +77,8 @@ def pytest_cmdline_main(config):
|
|||
genscript = config.getvalue("genscript")
|
||||
if genscript:
|
||||
tw = _pytest.config.create_terminal_writer(config)
|
||||
tw.line("WARNING: usage of genscript is deprecated.",
|
||||
red=True)
|
||||
deps = ['py', 'pluggy', '_pytest', 'pytest']
|
||||
if sys.version_info < (2,7):
|
||||
deps.append("argparse")
|
||||
|
|
|
@ -9,6 +9,7 @@ import os
|
|||
import re
|
||||
import sys
|
||||
import time
|
||||
import pytest
|
||||
|
||||
# Python 2.X and 3.X compatibility
|
||||
if sys.version_info[0] < 3:
|
||||
|
@ -53,6 +54,20 @@ def bin_xml_escape(arg):
|
|||
return unicode('#x%04X') % i
|
||||
return py.xml.raw(illegal_xml_re.sub(repl, py.xml.escape(arg)))
|
||||
|
||||
@pytest.fixture
|
||||
def record_xml_property(request):
|
||||
"""Fixture that adds extra xml properties to the tag for the calling test.
|
||||
The fixture is callable with (name, value), with value being automatically
|
||||
xml-encoded.
|
||||
"""
|
||||
def inner(name, value):
|
||||
if hasattr(request.config, "_xml"):
|
||||
request.config._xml.add_custom_property(name, value)
|
||||
msg = 'record_xml_property is an experimental feature'
|
||||
request.config.warn(code='C3', message=msg,
|
||||
fslocation=request.node.location[:2])
|
||||
return inner
|
||||
|
||||
def pytest_addoption(parser):
|
||||
group = parser.getgroup("terminal reporting")
|
||||
group.addoption('--junitxml', '--junit-xml', action="store",
|
||||
|
@ -75,7 +90,6 @@ def pytest_unconfigure(config):
|
|||
del config._xml
|
||||
config.pluginmanager.unregister(xml)
|
||||
|
||||
|
||||
def mangle_testnames(names):
|
||||
names = [x.replace(".py", "") for x in names if x != '()']
|
||||
names[0] = names[0].replace("/", '.')
|
||||
|
@ -89,6 +103,10 @@ class LogXML(object):
|
|||
self.tests = []
|
||||
self.passed = self.skipped = 0
|
||||
self.failed = self.errors = 0
|
||||
self.custom_properties = {}
|
||||
|
||||
def add_custom_property(self, name, value):
|
||||
self.custom_properties[str(name)] = bin_xml_escape(str(value))
|
||||
|
||||
def _opentestcase(self, report):
|
||||
names = mangle_testnames(report.nodeid.split("::"))
|
||||
|
@ -118,6 +136,10 @@ class LogXML(object):
|
|||
def append(self, obj):
|
||||
self.tests[-1].append(obj)
|
||||
|
||||
def append_custom_properties(self):
|
||||
self.tests[-1].attr.__dict__.update(self.custom_properties)
|
||||
self.custom_properties.clear()
|
||||
|
||||
def append_pass(self, report):
|
||||
self.passed += 1
|
||||
self._write_captured_output(report)
|
||||
|
@ -179,6 +201,7 @@ class LogXML(object):
|
|||
if report.when == "setup":
|
||||
self._opentestcase(report)
|
||||
self.tests[-1].attr.time += getattr(report, 'duration', 0)
|
||||
self.append_custom_properties()
|
||||
if report.passed:
|
||||
if report.when == "call": # ignore setup/teardown
|
||||
self.append_pass(report)
|
||||
|
|
|
@ -19,6 +19,7 @@ EXIT_TESTSFAILED = 1
|
|||
EXIT_INTERRUPTED = 2
|
||||
EXIT_INTERNALERROR = 3
|
||||
EXIT_USAGEERROR = 4
|
||||
EXIT_NOTESTSCOLLECTED = 5
|
||||
|
||||
name_re = re.compile("^[a-zA-Z_]\w*$")
|
||||
|
||||
|
@ -100,8 +101,10 @@ def wrap_session(config, doit):
|
|||
if excinfo.errisinstance(SystemExit):
|
||||
sys.stderr.write("mainloop: caught Spurious SystemExit!\n")
|
||||
else:
|
||||
if session._testsfailed:
|
||||
if session.testsfailed:
|
||||
session.exitstatus = EXIT_TESTSFAILED
|
||||
elif session.testscollected == 0:
|
||||
session.exitstatus = EXIT_NOTESTSCOLLECTED
|
||||
finally:
|
||||
excinfo = None # Explicitly break reference cycle.
|
||||
session.startdir.chdir()
|
||||
|
@ -509,7 +512,8 @@ class Session(FSCollector):
|
|||
FSCollector.__init__(self, config.rootdir, parent=None,
|
||||
config=config, session=self)
|
||||
self._fs2hookproxy = {}
|
||||
self._testsfailed = 0
|
||||
self.testsfailed = 0
|
||||
self.testscollected = 0
|
||||
self.shouldstop = False
|
||||
self.trace = config.trace.root.get("collection")
|
||||
self._norecursepatterns = config.getini("norecursedirs")
|
||||
|
@ -527,11 +531,11 @@ class Session(FSCollector):
|
|||
@pytest.hookimpl(tryfirst=True)
|
||||
def pytest_runtest_logreport(self, report):
|
||||
if report.failed and not hasattr(report, 'wasxfail'):
|
||||
self._testsfailed += 1
|
||||
self.testsfailed += 1
|
||||
maxfail = self.config.getvalue("maxfail")
|
||||
if maxfail and self._testsfailed >= maxfail:
|
||||
if maxfail and self.testsfailed >= maxfail:
|
||||
self.shouldstop = "stopping after %d failures" % (
|
||||
self._testsfailed)
|
||||
self.testsfailed)
|
||||
pytest_collectreport = pytest_runtest_logreport
|
||||
|
||||
def isinitpath(self, path):
|
||||
|
@ -564,6 +568,7 @@ class Session(FSCollector):
|
|||
config=self.config, items=items)
|
||||
finally:
|
||||
hook.pytest_collection_finish(session=self)
|
||||
self.testscollected = len(items)
|
||||
return items
|
||||
|
||||
def _perform_collect(self, args, genitems):
|
||||
|
|
|
@ -291,7 +291,7 @@ class MarkInfo:
|
|||
#: positional argument list, empty if none specified
|
||||
self.args = args
|
||||
#: keyword argument dictionary, empty if nothing specified
|
||||
self.kwargs = kwargs
|
||||
self.kwargs = kwargs.copy()
|
||||
self._arglist = [(args, kwargs.copy())]
|
||||
|
||||
def __repr__(self):
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
""" Python test discovery, setup and run of test functions. """
|
||||
import re
|
||||
import fnmatch
|
||||
import functools
|
||||
import py
|
||||
|
@ -8,6 +9,12 @@ import pytest
|
|||
from _pytest.mark import MarkDecorator, MarkerError
|
||||
from py._code.code import TerminalRepr
|
||||
|
||||
try:
|
||||
import enum
|
||||
except ImportError: # pragma: no cover
|
||||
# Only available in Python 3.4+ or as a backport
|
||||
enum = None
|
||||
|
||||
import _pytest
|
||||
import pluggy
|
||||
|
||||
|
@ -22,6 +29,8 @@ isclass = inspect.isclass
|
|||
callable = py.builtin.callable
|
||||
# used to work around a python2 exception info leak
|
||||
exc_clear = getattr(sys, 'exc_clear', lambda: None)
|
||||
# The type of re.compile objects is not exposed in Python.
|
||||
REGEX_TYPE = type(re.compile(''))
|
||||
|
||||
def filter_traceback(entry):
|
||||
return entry.path != cutdir1 and not entry.path.relto(cutdir2)
|
||||
|
@ -55,6 +64,17 @@ def getimfunc(func):
|
|||
except AttributeError:
|
||||
return func
|
||||
|
||||
def safe_getattr(object, name, default):
|
||||
""" Like getattr but return default upon any Exception.
|
||||
|
||||
Attribute access can potentially fail for 'evil' Python objects.
|
||||
See issue214
|
||||
"""
|
||||
try:
|
||||
return getattr(object, name, default)
|
||||
except Exception:
|
||||
return default
|
||||
|
||||
|
||||
class FixtureFunctionMarker:
|
||||
def __init__(self, scope, params,
|
||||
|
@ -160,10 +180,13 @@ def pytest_cmdline_main(config):
|
|||
|
||||
|
||||
def pytest_generate_tests(metafunc):
|
||||
# this misspelling is common - raise a specific error to alert the user
|
||||
if hasattr(metafunc.function, 'parameterize'):
|
||||
msg = "{0} has 'parameterize', spelling should be 'parametrize'"
|
||||
raise MarkerError(msg.format(metafunc.function.__name__))
|
||||
# those alternative spellings are common - raise a specific error to alert
|
||||
# the user
|
||||
alt_spellings = ['parameterize', 'parametrise', 'parameterise']
|
||||
for attr in alt_spellings:
|
||||
if hasattr(metafunc.function, attr):
|
||||
msg = "{0} has '{1}', spelling should be 'parametrize'"
|
||||
raise MarkerError(msg.format(metafunc.function.__name__, attr))
|
||||
try:
|
||||
markers = metafunc.function.parametrize
|
||||
except AttributeError:
|
||||
|
@ -245,11 +268,10 @@ def pytest_pycollect_makeitem(collector, name, obj):
|
|||
raise StopIteration
|
||||
# nothing was collected elsewhere, let's do it here
|
||||
if isclass(obj):
|
||||
if collector.classnamefilter(name):
|
||||
if collector.istestclass(obj, name):
|
||||
Class = collector._getcustomclass("Class")
|
||||
outcome.force_result(Class(name, parent=collector))
|
||||
elif collector.funcnamefilter(name) and hasattr(obj, "__call__") and\
|
||||
getfixturemarker(obj) is None:
|
||||
elif collector.istestfunction(obj, name):
|
||||
# mock seems to store unbound methods (issue473), normalize it
|
||||
obj = getattr(obj, "__func__", obj)
|
||||
if not isfunction(obj):
|
||||
|
@ -335,9 +357,24 @@ class PyCollector(PyobjMixin, pytest.Collector):
|
|||
def funcnamefilter(self, name):
|
||||
return self._matches_prefix_or_glob_option('python_functions', name)
|
||||
|
||||
def isnosetest(self, obj):
|
||||
""" Look for the __test__ attribute, which is applied by the
|
||||
@nose.tools.istest decorator
|
||||
"""
|
||||
return safe_getattr(obj, '__test__', False)
|
||||
|
||||
def classnamefilter(self, name):
|
||||
return self._matches_prefix_or_glob_option('python_classes', name)
|
||||
|
||||
def istestfunction(self, obj, name):
|
||||
return (
|
||||
(self.funcnamefilter(name) or self.isnosetest(obj))
|
||||
and safe_getattr(obj, "__call__", False) and getfixturemarker(obj) is None
|
||||
)
|
||||
|
||||
def istestclass(self, obj, name):
|
||||
return self.classnamefilter(name) or self.isnosetest(obj)
|
||||
|
||||
def _matches_prefix_or_glob_option(self, option_name, name):
|
||||
"""
|
||||
checks if the given name matches the prefix or glob-pattern defined
|
||||
|
@ -480,6 +517,19 @@ class FuncFixtureInfo:
|
|||
self.names_closure = names_closure
|
||||
self.name2fixturedefs = name2fixturedefs
|
||||
|
||||
|
||||
def _marked(func, mark):
|
||||
""" Returns True if :func: is already marked with :mark:, False otherwise.
|
||||
This can happen if marker is applied to class and the test file is
|
||||
invoked more than once.
|
||||
"""
|
||||
try:
|
||||
func_mark = getattr(func, mark.name)
|
||||
except AttributeError:
|
||||
return False
|
||||
return mark.args == func_mark.args and mark.kwargs == func_mark.kwargs
|
||||
|
||||
|
||||
def transfer_markers(funcobj, cls, mod):
|
||||
# XXX this should rather be code in the mark plugin or the mark
|
||||
# plugin should merge with the python plugin.
|
||||
|
@ -490,8 +540,10 @@ def transfer_markers(funcobj, cls, mod):
|
|||
continue
|
||||
if isinstance(pytestmark, list):
|
||||
for mark in pytestmark:
|
||||
if not _marked(funcobj, mark):
|
||||
mark(funcobj)
|
||||
else:
|
||||
if not _marked(funcobj, pytestmark):
|
||||
pytestmark(funcobj)
|
||||
|
||||
class Module(pytest.File, PyCollector):
|
||||
|
@ -973,8 +1025,15 @@ def _idval(val, argname, idx, idfn):
|
|||
return s
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if isinstance(val, (float, int, str, bool, NoneType)):
|
||||
return str(val)
|
||||
elif isinstance(val, REGEX_TYPE):
|
||||
return val.pattern
|
||||
elif enum is not None and isinstance(val, enum.Enum):
|
||||
return str(val)
|
||||
elif isclass(val) and hasattr(val, '__name__'):
|
||||
return val.__name__
|
||||
return str(argname)+str(idx)
|
||||
|
||||
def _idvalset(idx, valset, argnames, idfn):
|
||||
|
@ -1049,8 +1108,8 @@ def getlocation(function, curdir):
|
|||
|
||||
# builtin pytest.raises helper
|
||||
|
||||
def raises(ExpectedException, *args, **kwargs):
|
||||
""" assert that a code block/function call raises @ExpectedException
|
||||
def raises(expected_exception, *args, **kwargs):
|
||||
""" assert that a code block/function call raises @expected_exception
|
||||
and raise a failure exception otherwise.
|
||||
|
||||
This helper produces a ``py.code.ExceptionInfo()`` object.
|
||||
|
@ -1098,23 +1157,23 @@ def raises(ExpectedException, *args, **kwargs):
|
|||
|
||||
"""
|
||||
__tracebackhide__ = True
|
||||
if ExpectedException is AssertionError:
|
||||
if expected_exception is AssertionError:
|
||||
# we want to catch a AssertionError
|
||||
# replace our subclass with the builtin one
|
||||
# see https://github.com/pytest-dev/pytest/issues/176
|
||||
from _pytest.assertion.util import BuiltinAssertionError \
|
||||
as ExpectedException
|
||||
as expected_exception
|
||||
msg = ("exceptions must be old-style classes or"
|
||||
" derived from BaseException, not %s")
|
||||
if isinstance(ExpectedException, tuple):
|
||||
for exc in ExpectedException:
|
||||
if not inspect.isclass(exc):
|
||||
if isinstance(expected_exception, tuple):
|
||||
for exc in expected_exception:
|
||||
if not isclass(exc):
|
||||
raise TypeError(msg % type(exc))
|
||||
elif not inspect.isclass(ExpectedException):
|
||||
raise TypeError(msg % type(ExpectedException))
|
||||
elif not isclass(expected_exception):
|
||||
raise TypeError(msg % type(expected_exception))
|
||||
|
||||
if not args:
|
||||
return RaisesContext(ExpectedException)
|
||||
return RaisesContext(expected_exception)
|
||||
elif isinstance(args[0], str):
|
||||
code, = args
|
||||
assert isinstance(code, str)
|
||||
|
@ -1127,19 +1186,19 @@ def raises(ExpectedException, *args, **kwargs):
|
|||
py.builtin.exec_(code, frame.f_globals, loc)
|
||||
# XXX didn'T mean f_globals == f_locals something special?
|
||||
# this is destroyed here ...
|
||||
except ExpectedException:
|
||||
except expected_exception:
|
||||
return py.code.ExceptionInfo()
|
||||
else:
|
||||
func = args[0]
|
||||
try:
|
||||
func(*args[1:], **kwargs)
|
||||
except ExpectedException:
|
||||
except expected_exception:
|
||||
return py.code.ExceptionInfo()
|
||||
pytest.fail("DID NOT RAISE")
|
||||
|
||||
class RaisesContext(object):
|
||||
def __init__(self, ExpectedException):
|
||||
self.ExpectedException = ExpectedException
|
||||
def __init__(self, expected_exception):
|
||||
self.expected_exception = expected_exception
|
||||
self.excinfo = None
|
||||
|
||||
def __enter__(self):
|
||||
|
@ -1158,7 +1217,7 @@ class RaisesContext(object):
|
|||
exc_type, value, traceback = tp
|
||||
tp = exc_type, exc_type(value), traceback
|
||||
self.excinfo.__init__(tp)
|
||||
return issubclass(self.excinfo.type, self.ExpectedException)
|
||||
return issubclass(self.excinfo.type, self.expected_exception)
|
||||
|
||||
#
|
||||
# the basic pytest Function item
|
||||
|
@ -1771,7 +1830,7 @@ class FixtureManager:
|
|||
if fixturedef.params is not None:
|
||||
func_params = getattr(getattr(metafunc.function, 'parametrize', None), 'args', [[None]])
|
||||
# skip directly parametrized arguments
|
||||
if argname not in func_params and argname not in func_params[0]:
|
||||
if argname not in func_params:
|
||||
metafunc.parametrize(argname, fixturedef.params,
|
||||
indirect=True, scope=fixturedef.scope,
|
||||
ids=fixturedef.ids)
|
||||
|
@ -2120,4 +2179,3 @@ def get_scope_node(node, scope):
|
|||
return node.session
|
||||
raise ValueError("unknown scope")
|
||||
return node.getparent(cls)
|
||||
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
""" recording warnings during test function execution. """
|
||||
|
||||
import inspect
|
||||
import py
|
||||
import sys
|
||||
import warnings
|
||||
import pytest
|
||||
|
||||
|
||||
def pytest_funcarg__recwarn(request):
|
||||
@pytest.yield_fixture
|
||||
def recwarn(request):
|
||||
"""Return a WarningsRecorder instance that provides these methods:
|
||||
|
||||
* ``pop(category=None)``: return last warning matching the category.
|
||||
|
@ -13,83 +17,173 @@ def pytest_funcarg__recwarn(request):
|
|||
See http://docs.python.org/library/warnings.html for information
|
||||
on warning categories.
|
||||
"""
|
||||
if sys.version_info >= (2,7):
|
||||
oldfilters = warnings.filters[:]
|
||||
warnings.simplefilter('default')
|
||||
def reset_filters():
|
||||
warnings.filters[:] = oldfilters
|
||||
request.addfinalizer(reset_filters)
|
||||
wrec = WarningsRecorder()
|
||||
request.addfinalizer(wrec.finalize)
|
||||
return wrec
|
||||
with wrec:
|
||||
warnings.simplefilter('default')
|
||||
yield wrec
|
||||
|
||||
|
||||
def pytest_namespace():
|
||||
return {'deprecated_call': deprecated_call}
|
||||
return {'deprecated_call': deprecated_call,
|
||||
'warns': warns}
|
||||
|
||||
|
||||
def deprecated_call(func, *args, **kwargs):
|
||||
""" assert that calling ``func(*args, **kwargs)``
|
||||
triggers a DeprecationWarning.
|
||||
"""Assert that ``func(*args, **kwargs)`` triggers a DeprecationWarning.
|
||||
"""
|
||||
l = []
|
||||
oldwarn_explicit = getattr(warnings, 'warn_explicit')
|
||||
def warn_explicit(*args, **kwargs):
|
||||
l.append(args)
|
||||
oldwarn_explicit(*args, **kwargs)
|
||||
oldwarn = getattr(warnings, 'warn')
|
||||
def warn(*args, **kwargs):
|
||||
l.append(args)
|
||||
oldwarn(*args, **kwargs)
|
||||
|
||||
warnings.warn_explicit = warn_explicit
|
||||
warnings.warn = warn
|
||||
try:
|
||||
wrec = WarningsRecorder()
|
||||
with wrec:
|
||||
warnings.simplefilter('always') # ensure all warnings are triggered
|
||||
ret = func(*args, **kwargs)
|
||||
finally:
|
||||
warnings.warn_explicit = oldwarn_explicit
|
||||
warnings.warn = oldwarn
|
||||
if not l:
|
||||
|
||||
if not any(r.category is DeprecationWarning for r in wrec):
|
||||
__tracebackhide__ = True
|
||||
raise AssertionError("%r did not produce DeprecationWarning" % (func,))
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
class RecordedWarning:
|
||||
def __init__(self, message, category, filename, lineno, line):
|
||||
def warns(expected_warning, *args, **kwargs):
|
||||
"""Assert that code raises a particular class of warning.
|
||||
|
||||
Specifically, the input @expected_warning can be a warning class or
|
||||
tuple of warning classes, and the code must return that warning
|
||||
(if a single class) or one of those warnings (if a tuple).
|
||||
|
||||
This helper produces a list of ``warnings.WarningMessage`` objects,
|
||||
one for each warning raised.
|
||||
|
||||
This function can be used as a context manager, or any of the other ways
|
||||
``pytest.raises`` can be used::
|
||||
|
||||
>>> with warns(RuntimeWarning):
|
||||
... warnings.warn("my warning", RuntimeWarning)
|
||||
"""
|
||||
wcheck = WarningsChecker(expected_warning)
|
||||
if not args:
|
||||
return wcheck
|
||||
elif isinstance(args[0], str):
|
||||
code, = args
|
||||
assert isinstance(code, str)
|
||||
frame = sys._getframe(1)
|
||||
loc = frame.f_locals.copy()
|
||||
loc.update(kwargs)
|
||||
|
||||
with wcheck:
|
||||
code = py.code.Source(code).compile()
|
||||
py.builtin.exec_(code, frame.f_globals, loc)
|
||||
else:
|
||||
func = args[0]
|
||||
with wcheck:
|
||||
return func(*args[1:], **kwargs)
|
||||
|
||||
|
||||
class RecordedWarning(object):
|
||||
def __init__(self, message, category, filename, lineno, file, line):
|
||||
self.message = message
|
||||
self.category = category
|
||||
self.filename = filename
|
||||
self.lineno = lineno
|
||||
self.file = file
|
||||
self.line = line
|
||||
|
||||
class WarningsRecorder:
|
||||
def __init__(self):
|
||||
self.list = []
|
||||
def showwarning(message, category, filename, lineno, line=0):
|
||||
self.list.append(RecordedWarning(
|
||||
message, category, filename, lineno, line))
|
||||
try:
|
||||
self.old_showwarning(message, category,
|
||||
filename, lineno, line=line)
|
||||
except TypeError:
|
||||
# < python2.6
|
||||
self.old_showwarning(message, category, filename, lineno)
|
||||
self.old_showwarning = warnings.showwarning
|
||||
warnings.showwarning = showwarning
|
||||
|
||||
class WarningsRecorder(object):
|
||||
"""A context manager to record raised warnings.
|
||||
|
||||
Adapted from `warnings.catch_warnings`.
|
||||
"""
|
||||
|
||||
def __init__(self, module=None):
|
||||
self._module = sys.modules['warnings'] if module is None else module
|
||||
self._entered = False
|
||||
self._list = []
|
||||
|
||||
@property
|
||||
def list(self):
|
||||
"""The list of recorded warnings."""
|
||||
return self._list
|
||||
|
||||
def __getitem__(self, i):
|
||||
"""Get a recorded warning by index."""
|
||||
return self._list[i]
|
||||
|
||||
def __iter__(self):
|
||||
"""Iterate through the recorded warnings."""
|
||||
return iter(self._list)
|
||||
|
||||
def __len__(self):
|
||||
"""The number of recorded warnings."""
|
||||
return len(self._list)
|
||||
|
||||
def pop(self, cls=Warning):
|
||||
""" pop the first recorded warning, raise exception if not exists."""
|
||||
for i, w in enumerate(self.list):
|
||||
"""Pop the first recorded warning, raise exception if not exists."""
|
||||
for i, w in enumerate(self._list):
|
||||
if issubclass(w.category, cls):
|
||||
return self.list.pop(i)
|
||||
return self._list.pop(i)
|
||||
__tracebackhide__ = True
|
||||
assert 0, "%r not found in %r" %(cls, self.list)
|
||||
|
||||
#def resetregistry(self):
|
||||
# warnings.onceregistry.clear()
|
||||
# warnings.__warningregistry__.clear()
|
||||
raise AssertionError("%r not found in warning list" % cls)
|
||||
|
||||
def clear(self):
|
||||
self.list[:] = []
|
||||
"""Clear the list of recorded warnings."""
|
||||
self._list[:] = []
|
||||
|
||||
def finalize(self):
|
||||
warnings.showwarning = self.old_showwarning
|
||||
def __enter__(self):
|
||||
if self._entered:
|
||||
__tracebackhide__ = True
|
||||
raise RuntimeError("Cannot enter %r twice" % self)
|
||||
self._entered = True
|
||||
self._filters = self._module.filters
|
||||
self._module.filters = self._filters[:]
|
||||
self._showwarning = self._module.showwarning
|
||||
|
||||
def showwarning(message, category, filename, lineno,
|
||||
file=None, line=None):
|
||||
self._list.append(RecordedWarning(
|
||||
message, category, filename, lineno, file, line))
|
||||
|
||||
# still perform old showwarning functionality
|
||||
self._showwarning(
|
||||
message, category, filename, lineno, file=file, line=line)
|
||||
|
||||
self._module.showwarning = showwarning
|
||||
|
||||
# allow the same warning to be raised more than once
|
||||
self._module.simplefilter('always', append=True)
|
||||
|
||||
return self
|
||||
|
||||
def __exit__(self, *exc_info):
|
||||
if not self._entered:
|
||||
__tracebackhide__ = True
|
||||
raise RuntimeError("Cannot exit %r without entering first" % self)
|
||||
self._module.filters = self._filters
|
||||
self._module.showwarning = self._showwarning
|
||||
|
||||
|
||||
class WarningsChecker(WarningsRecorder):
|
||||
def __init__(self, expected_warning=None, module=None):
|
||||
super(WarningsChecker, self).__init__(module=module)
|
||||
|
||||
msg = ("exceptions must be old-style classes or "
|
||||
"derived from Warning, not %s")
|
||||
if isinstance(expected_warning, tuple):
|
||||
for exc in expected_warning:
|
||||
if not inspect.isclass(exc):
|
||||
raise TypeError(msg % type(exc))
|
||||
elif inspect.isclass(expected_warning):
|
||||
expected_warning = (expected_warning,)
|
||||
elif expected_warning is not None:
|
||||
raise TypeError(msg % type(expected_warning))
|
||||
|
||||
self.expected_warning = expected_warning
|
||||
|
||||
def __exit__(self, *exc_info):
|
||||
super(WarningsChecker, self).__exit__(*exc_info)
|
||||
|
||||
# only check if we're not currently handling an exception
|
||||
if all(a is None for a in exc_info):
|
||||
if self.expected_warning is not None:
|
||||
if not any(r.category in self.expected_warning for r in self):
|
||||
__tracebackhide__ = True
|
||||
pytest.fail("DID NOT WARN")
|
||||
|
|
|
@ -68,6 +68,11 @@ class DictImporter(object):
|
|||
return res
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
import pkg_resources # noqa
|
||||
except ImportError:
|
||||
sys.stderr.write("ERROR: setuptools not installed\n")
|
||||
sys.exit(2)
|
||||
if sys.version_info >= (3, 0):
|
||||
exec("def do_exec(co, loc): exec(co, loc)\n")
|
||||
import pickle
|
||||
|
@ -80,6 +85,5 @@ if __name__ == "__main__":
|
|||
|
||||
importer = DictImporter(sources)
|
||||
sys.meta_path.insert(0, importer)
|
||||
|
||||
entry = "@ENTRY@"
|
||||
do_exec(entry, locals()) # noqa
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
This is a good source for looking at the various reporting hooks.
|
||||
"""
|
||||
from _pytest.main import EXIT_OK, EXIT_TESTSFAILED, EXIT_INTERRUPTED, \
|
||||
EXIT_USAGEERROR, EXIT_NOTESTSCOLLECTED
|
||||
import pytest
|
||||
import pluggy
|
||||
import py
|
||||
|
@ -298,13 +300,9 @@ class TerminalReporter:
|
|||
|
||||
plugininfo = config.pluginmanager.list_plugin_distinfo()
|
||||
if plugininfo:
|
||||
l = []
|
||||
for plugin, dist in plugininfo:
|
||||
name = dist.project_name
|
||||
if name.startswith("pytest-"):
|
||||
name = name[7:]
|
||||
l.append(name)
|
||||
lines.append("plugins: %s" % ", ".join(l))
|
||||
|
||||
lines.append(
|
||||
"plugins: %s" % ", ".join(_plugin_nameversions(plugininfo)))
|
||||
return lines
|
||||
|
||||
def pytest_collection_finish(self, session):
|
||||
|
@ -359,12 +357,15 @@ class TerminalReporter:
|
|||
outcome = yield
|
||||
outcome.get_result()
|
||||
self._tw.line("")
|
||||
if exitstatus in (0, 1, 2, 4):
|
||||
summary_exit_codes = (
|
||||
EXIT_OK, EXIT_TESTSFAILED, EXIT_INTERRUPTED, EXIT_USAGEERROR,
|
||||
EXIT_NOTESTSCOLLECTED)
|
||||
if exitstatus in summary_exit_codes:
|
||||
self.summary_errors()
|
||||
self.summary_failures()
|
||||
self.summary_warnings()
|
||||
self.config.hook.pytest_terminal_summary(terminalreporter=self)
|
||||
if exitstatus == 2:
|
||||
if exitstatus == EXIT_INTERRUPTED:
|
||||
self._report_keyboardinterrupt()
|
||||
del self._keyboardinterrupt_memo
|
||||
self.summary_deselected()
|
||||
|
@ -549,3 +550,18 @@ def build_summary_stats_line(stats):
|
|||
color = 'yellow'
|
||||
|
||||
return (line, color)
|
||||
|
||||
|
||||
def _plugin_nameversions(plugininfo):
|
||||
l = []
|
||||
for plugin, dist in plugininfo:
|
||||
# gets us name and version!
|
||||
name = '{dist.project_name}-{dist.version}'.format(dist=dist)
|
||||
# questionable convenience, but it keeps things short
|
||||
if name.startswith("pytest-"):
|
||||
name = name[7:]
|
||||
# we decided to print python package names
|
||||
# they can have more than one plugin
|
||||
if name not in l:
|
||||
l.append(name)
|
||||
return l
|
||||
|
|
|
@ -56,7 +56,7 @@ class TempdirFactory:
|
|||
# make_numbered_dir() call
|
||||
import getpass
|
||||
temproot = py.path.local.get_temproot()
|
||||
rootdir = temproot.join('pytest-%s' % getpass.getuser())
|
||||
rootdir = temproot.join('pytest-of-%s' % getpass.getuser())
|
||||
rootdir.ensure(dir=1)
|
||||
basetemp = py.path.local.make_numbered_dir(prefix='pytest-',
|
||||
rootdir=rootdir)
|
||||
|
|
|
@ -46,7 +46,7 @@ installall: clean install installpdf
|
|||
@echo "done"
|
||||
|
||||
regen:
|
||||
PYTHONDONTWRITEBYTECODE=1 COLUMNS=76 regendoc --update *.txt */*.txt
|
||||
PYTHONDONTWRITEBYTECODE=1 COLUMNS=76 regendoc --update *.rst */*.rst
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
|
|
|
@ -6,7 +6,7 @@ def get_version_string():
|
|||
fn = py.path.local(__file__).join("..", "..", "..",
|
||||
"_pytest", "__init__.py")
|
||||
for line in fn.readlines():
|
||||
if "version" in line:
|
||||
if "version" in line and not line.strip().startswith('#'):
|
||||
return eval(line.split("=")[-1])
|
||||
|
||||
def get_minor_version_string():
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
|
||||
.. _apiref:
|
||||
|
||||
pytest reference documentation
|
||||
================================================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
builtin
|
||||
customize
|
||||
assert
|
||||
fixture
|
||||
yieldfixture
|
||||
parametrize
|
||||
xunit_setup
|
||||
capture
|
||||
monkeypatch
|
||||
xdist
|
||||
tmpdir
|
||||
mark
|
||||
skipping
|
||||
recwarn
|
||||
unittest
|
||||
nose
|
||||
doctest
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
|
||||
.. _apiref:
|
||||
|
||||
pytest reference documentation
|
||||
================================================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
builtin.txt
|
||||
customize.txt
|
||||
assert.txt
|
||||
fixture.txt
|
||||
yieldfixture.txt
|
||||
parametrize.txt
|
||||
xunit_setup.txt
|
||||
capture.txt
|
||||
monkeypatch.txt
|
||||
xdist.txt
|
||||
tmpdir.txt
|
||||
mark.txt
|
||||
skipping.txt
|
||||
recwarn.txt
|
||||
unittest.txt
|
||||
nose.txt
|
||||
doctest.txt
|
||||
|
|
@ -114,6 +114,16 @@ like documenting unfixed bugs (where the test describes what "should" happen)
|
|||
or bugs in dependencies.
|
||||
|
||||
|
||||
.. _`assertwarns`:
|
||||
|
||||
Assertions about expected warnings
|
||||
-----------------------------------------
|
||||
|
||||
.. versionadded:: 2.8
|
||||
|
||||
You can check that code raises a particular warning using
|
||||
:ref:`pytest.warns <warns>`.
|
||||
|
||||
|
||||
.. _newreport:
|
||||
|
||||
|
@ -228,9 +238,7 @@ Reporting details about a failing assertion is achieved either by rewriting
|
|||
assert statements before they are run or re-evaluating the assert expression and
|
||||
recording the intermediate values. Which technique is used depends on the
|
||||
location of the assert, ``pytest`` configuration, and Python version being used
|
||||
to run ``pytest``. Note that for assert statements with a manually provided
|
||||
message, i.e. ``assert expr, message``, no assertion introspection takes place
|
||||
and the manually provided message will be rendered in tracebacks.
|
||||
to run ``pytest``.
|
||||
|
||||
By default, if the Python version is greater than or equal to 2.6, ``pytest``
|
||||
rewrites assert statements in test modules. Rewritten assert statements put
|
|
@ -47,7 +47,7 @@ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.autosummary',
|
|||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.txt'
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
#source_encoding = 'utf-8-sig'
|
||||
|
@ -73,13 +73,13 @@ copyright = u'2015, holger krekel and pytest-dev team'
|
|||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['links.inc', '_build', 'naming20.txt', 'test/*',
|
||||
exclude_patterns = ['links.inc', '_build', 'naming20.rst', 'test/*',
|
||||
"old_*",
|
||||
'*attic*',
|
||||
'*/attic*',
|
||||
'funcargs.txt',
|
||||
'setup.txt',
|
||||
'example/remoteinterp.txt',
|
||||
'funcargs.rst',
|
||||
'setup.rst',
|
||||
'example/remoteinterp.rst',
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -17,11 +17,11 @@ Full pytest documentation
|
|||
example/index
|
||||
talks
|
||||
contributing
|
||||
funcarg_compare.txt
|
||||
funcarg_compare
|
||||
announce/index
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
|
||||
changelog.txt
|
||||
changelog
|
||||
|
|
@ -219,3 +219,10 @@ Builtin configuration file options
|
|||
|
||||
One or more doctest flag names from the standard ``doctest`` module.
|
||||
:doc:`See how py.test handles doctests <doctest>`.
|
||||
|
||||
.. confval:: confcutdir
|
||||
|
||||
Sets a directory where search upwards for ``conftest.py`` files stops.
|
||||
By default, pytest will stop searching for ``conftest.py`` files upwards
|
||||
from ``pytest.ini``/``tox.ini``/``setup.cfg`` of the project if any,
|
||||
or up to the file-system root.
|
|
@ -72,3 +72,18 @@ ignore lengthy exception stack traces you can just write::
|
|||
# content of pytest.ini
|
||||
[pytest]
|
||||
doctest_optionflags= NORMALIZE_WHITESPACE IGNORE_EXCEPTION_DETAIL
|
||||
|
||||
|
||||
py.test also introduces a new ``ALLOW_UNICODE`` option flag: 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.
|
||||
|
||||
As with any other option flag, this flag can be enabled in ``pytest.ini`` using
|
||||
the ``doctest_optionflags`` ini option or by an inline comment in the doc test
|
||||
itself::
|
||||
|
||||
# content of example.rst
|
||||
>>> get_unicode_greeting() # doctest: +ALLOW_UNICODE
|
||||
'Hello'
|
||||
|
||||
|
|
@ -25,10 +25,10 @@ The following examples aim at various use cases you might encounter.
|
|||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
reportingdemo.txt
|
||||
simple.txt
|
||||
parametrize.txt
|
||||
markers.txt
|
||||
special.txt
|
||||
pythoncollection.txt
|
||||
nonpython.txt
|
||||
reportingdemo
|
||||
simple
|
||||
parametrize
|
||||
markers
|
||||
special
|
||||
pythoncollection
|
||||
nonpython
|
|
@ -81,7 +81,7 @@ Numbers, strings, booleans and None will have their usual string representation
|
|||
used in the test ID. For other objects, pytest will make a string based on
|
||||
the argument name::
|
||||
|
||||
# contents of test_time.py
|
||||
# content of test_time.py
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
|
@ -534,23 +534,24 @@ case we just write some informations out to a ``failures`` file::
|
|||
import pytest
|
||||
import os.path
|
||||
|
||||
@pytest.hookimpl(tryfirst=True)
|
||||
def pytest_runtest_makereport(item, call, __multicall__):
|
||||
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
|
||||
def pytest_runtest_makereport(item, call):
|
||||
# execute all other hooks to obtain the report object
|
||||
rep = __multicall__.execute()
|
||||
outcome = yield
|
||||
rep = outcome.get_result()
|
||||
|
||||
# we only look at actual failing test calls, not setup/teardown
|
||||
if rep.when == "call" and rep.failed:
|
||||
mode = "a" if os.path.exists("failures") else "w"
|
||||
with open("failures", mode) as f:
|
||||
# let's also access a fixture for the fun of it
|
||||
if "tmpdir" in item.funcargs:
|
||||
if "tmpdir" in item.fixturenames:
|
||||
extra = " (%s)" % item.funcargs["tmpdir"]
|
||||
else:
|
||||
extra = ""
|
||||
|
||||
f.write(rep.nodeid + extra + "\n")
|
||||
return rep
|
||||
|
||||
|
||||
if you then have failing tests::
|
||||
|
||||
|
@ -606,16 +607,16 @@ here is a little example implemented via a local plugin::
|
|||
|
||||
import pytest
|
||||
|
||||
@pytest.hookimpl(tryfirst=True)
|
||||
def pytest_runtest_makereport(item, call, __multicall__):
|
||||
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
|
||||
def pytest_runtest_makereport(item, call):
|
||||
# execute all other hooks to obtain the report object
|
||||
rep = __multicall__.execute()
|
||||
outcome = yield
|
||||
rep = outcome.get_result()
|
||||
|
||||
# set an report attribute for each phase of a call, which can
|
||||
# be "setup", "call", "teardown"
|
||||
|
||||
setattr(item, "rep_" + rep.when, rep)
|
||||
return rep
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
@ -742,5 +743,4 @@ over to ``pytest`` instead. For example::
|
|||
This makes it convenient to execute your tests from within your frozen
|
||||
application, using standard ``py.test`` command-line options::
|
||||
|
||||
$ ./app_main --pytest --verbose --tb=long --junit-xml=results.xml test-suite/
|
||||
/bin/sh: ./app_main: No such file or directory
|
||||
./app_main --pytest --verbose --tb=long --junit-xml=results.xml test-suite/
|
|
@ -154,8 +154,8 @@ to create a JUnitXML file that Jenkins_ can pick up and generate reports.
|
|||
.. _standalone:
|
||||
.. _`genscript method`:
|
||||
|
||||
Create a pytest standalone script
|
||||
-------------------------------------------
|
||||
(deprecated) Create a pytest standalone script
|
||||
-----------------------------------------------
|
||||
|
||||
If you are a maintainer or application developer and want people
|
||||
who don't deal with python much to easily run tests you may generate
|
|
@ -5,10 +5,10 @@ Getting started basics
|
|||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
index.txt
|
||||
getting-started.txt
|
||||
usage.txt
|
||||
goodpractises.txt
|
||||
projects.txt
|
||||
faq.txt
|
||||
index
|
||||
getting-started
|
||||
usage
|
||||
goodpractises
|
||||
projects
|
||||
faq
|
||||
|
|
@ -114,6 +114,18 @@ Let's run this::
|
|||
The one parameter set which caused a failure previously now
|
||||
shows up as an "xfailed (expected to fail)" test.
|
||||
|
||||
To get all combinations of multiple parametrized arguments you can stack
|
||||
``parametrize`` decorators::
|
||||
|
||||
import pytest
|
||||
@pytest.mark.parametrize("x", [0, 1])
|
||||
@pytest.mark.parametrize("y", [2, 3])
|
||||
def test_foo(x, y):
|
||||
pass
|
||||
|
||||
This will run the test with the arguments set to x=0/y=2, x=0/y=3, x=1/y=2 and
|
||||
x=1/y=3.
|
||||
|
||||
.. note::
|
||||
|
||||
In versions prior to 2.4 one needed to specify the argument
|
|
@ -0,0 +1,116 @@
|
|||
|
||||
Asserting Warnings
|
||||
=====================================================
|
||||
|
||||
.. _warns:
|
||||
|
||||
Asserting warnings with the warns function
|
||||
-----------------------------------------------
|
||||
|
||||
.. versionadded:: 2.8
|
||||
|
||||
You can check that code raises a particular warning using ``pytest.warns``,
|
||||
which works in a similar manner to :ref:`raises <assertraises>`::
|
||||
|
||||
import warnings
|
||||
import pytest
|
||||
|
||||
def test_warning():
|
||||
with pytest.warns(UserWarning):
|
||||
warnings.warn("my warning", UserWarning)
|
||||
|
||||
The test will fail if the warning in question is not raised.
|
||||
|
||||
You can also call ``pytest.warns`` on a function or code string::
|
||||
|
||||
pytest.warns(expected_warning, func, *args, **kwargs)
|
||||
pytest.warns(expected_warning, "func(*args, **kwargs)")
|
||||
|
||||
The function also returns a list of all raised warnings (as
|
||||
``warnings.WarningMessage`` objects), which you can query for
|
||||
additional information::
|
||||
|
||||
with pytest.warns(RuntimeWarning) as record:
|
||||
warnings.warn("another warning", RuntimeWarning)
|
||||
|
||||
# check that only one warning was raised
|
||||
assert len(record) == 1
|
||||
# check that the message matches
|
||||
assert record[0].message.args[0] == "another warning"
|
||||
|
||||
Alternatively, you can examine raised warnings in detail using the
|
||||
:ref:`recwarn <recwarn>` fixture (see below).
|
||||
|
||||
.. _recwarn:
|
||||
|
||||
Recording warnings
|
||||
------------------------
|
||||
|
||||
You can record raised warnings either using ``pytest.warns`` or with
|
||||
the ``recwarn`` fixture.
|
||||
|
||||
To record with ``pytest.warns`` without asserting anything about the warnings,
|
||||
pass ``None`` as the expected warning type::
|
||||
|
||||
with pytest.warns(None) as record:
|
||||
warnings.warn("user", UserWarning)
|
||||
warnings.warn("runtime", RuntimeWarning)
|
||||
|
||||
assert len(record) == 2
|
||||
assert str(record[0].message) == "user"
|
||||
assert str(record[1].message) == "runtime"
|
||||
|
||||
The ``recwarn`` fixture will record warnings for the whole function::
|
||||
|
||||
import warnings
|
||||
|
||||
def test_hello(recwarn):
|
||||
warnings.warn("hello", UserWarning)
|
||||
assert len(recwarn) == 1
|
||||
w = recwarn.pop(UserWarning)
|
||||
assert issubclass(w.category, UserWarning)
|
||||
assert str(w.message) == "hello"
|
||||
assert w.filename
|
||||
assert w.lineno
|
||||
|
||||
Both ``recwarn`` and ``pytest.warns`` return the same interface for recorded
|
||||
warnings: a WarningsRecorder instance. To view the recorded warnings, you can
|
||||
iterate over this instance, call ``len`` on it to get the number of recorded
|
||||
warnings, or index into it to get a particular recorded warning. It also
|
||||
provides these methods:
|
||||
|
||||
.. autoclass:: _pytest.recwarn.WarningsRecorder()
|
||||
:members:
|
||||
|
||||
Each recorded warning has the attributes ``message``, ``category``,
|
||||
``filename``, ``lineno``, ``file``, and ``line``. The ``category`` is the
|
||||
class of the warning. The ``message`` is the warning itself; calling
|
||||
``str(message)`` will return the actual message of the warning.
|
||||
|
||||
|
||||
.. _ensuring_function_triggers:
|
||||
|
||||
Ensuring a function triggers a deprecation warning
|
||||
-------------------------------------------------------
|
||||
|
||||
You can also call a global helper for checking
|
||||
that a certain function call triggers a ``DeprecationWarning``::
|
||||
|
||||
import pytest
|
||||
|
||||
def test_global():
|
||||
pytest.deprecated_call(myfunction, 17)
|
||||
|
||||
By default, deprecation warnings will not be caught when using ``pytest.warns``
|
||||
or ``recwarn``, since the default Python warnings filters hide
|
||||
DeprecationWarnings. If you wish to record them in your own code, use the
|
||||
command ``warnings.simplefilter('always')``::
|
||||
|
||||
import warnings
|
||||
import pytest
|
||||
|
||||
def test_deprecation(recwarn):
|
||||
warnings.simplefilter('always')
|
||||
warnings.warn("deprecated", DeprecationWarning)
|
||||
assert len(recwarn) == 1
|
||||
assert recwarn.pop(DeprecationWarning)
|
|
@ -1,46 +0,0 @@
|
|||
|
||||
Asserting deprecation and other warnings
|
||||
=====================================================
|
||||
|
||||
.. _function_argument:
|
||||
|
||||
The recwarn function argument
|
||||
------------------------------------
|
||||
|
||||
You can use the ``recwarn`` funcarg to assert that code triggers
|
||||
warnings through the Python warnings system. Here is a simple
|
||||
self-contained test::
|
||||
|
||||
# content of test_recwarn.py
|
||||
def test_hello(recwarn):
|
||||
from warnings import warn
|
||||
warn("hello", DeprecationWarning)
|
||||
w = recwarn.pop(DeprecationWarning)
|
||||
assert issubclass(w.category, DeprecationWarning)
|
||||
assert 'hello' in str(w.message)
|
||||
assert w.filename
|
||||
assert w.lineno
|
||||
|
||||
The ``recwarn`` function argument provides these methods:
|
||||
|
||||
.. method:: pop(category=None)
|
||||
|
||||
Return last warning matching the category.
|
||||
|
||||
.. method:: clear()
|
||||
|
||||
Clear list of warnings
|
||||
|
||||
|
||||
.. _ensuring_function_triggers:
|
||||
|
||||
Ensuring a function triggers a deprecation warning
|
||||
-------------------------------------------------------
|
||||
|
||||
You can also call a global helper for checking
|
||||
that a certain function call triggers a ``DeprecationWarning``::
|
||||
|
||||
import pytest
|
||||
|
||||
def test_global():
|
||||
pytest.deprecated_call(myfunction, 17)
|
|
@ -1,54 +0,0 @@
|
|||
pytest release checklist
|
||||
-------------------------
|
||||
|
||||
For doing a release of pytest (status April 2015) this rough checklist is used:
|
||||
|
||||
1. change version numbers in ``_pytest/__init__.py`` to the to-be-released version.
|
||||
(the version number in ``setup.py`` reads from that init file as well)
|
||||
|
||||
2. finalize ``./CHANGELOG`` (don't forget the the header).
|
||||
|
||||
3. write ``doc/en/announce/release-VERSION.txt``
|
||||
(usually copying from an earlier release version).
|
||||
|
||||
4. regenerate doc examples with ``tox -e regen`` and check with ``git diff``
|
||||
if the differences show regressions. It's a bit of a manual process because
|
||||
there a large part of the diff is about pytest headers or differences in
|
||||
speed ("tests took X.Y seconds"). (XXX automate doc/example diffing to ignore
|
||||
such changes and integrate it into "tox -e regen").
|
||||
|
||||
5. ``devpi upload`` to `your developer devpi index <http://doc.devpi.net/latest/quickstart-releaseprocess.html>`_. You can create your own user and index on https://devpi.net,
|
||||
an inofficial service from the devpi authors.
|
||||
|
||||
6. run ``devpi use INDEX`` and ``devpi test`` from linux and windows machines
|
||||
and verify test results on the index. On linux typically all environments
|
||||
pass (April 2015 there is a setup problem with a cx_freeze environment)
|
||||
but on windows all involving ``pexpect`` fail because pexpect does not exist
|
||||
on windows and tox does not allow to have platform-specific environments.
|
||||
Also on windows ``py33-trial`` fails but should probably pass (March 2015).
|
||||
In any case, py26,py27,py33,py34 are required to pass for all platforms.
|
||||
|
||||
7. You can fix tests/code and repeat number 6. until everything passes.
|
||||
|
||||
8. Once you have sufficiently passing tox tests you can do the actual release::
|
||||
|
||||
cd doc/en/
|
||||
make install # will install to 2.7, 2.8, ... according to _pytest/__init__.py
|
||||
make install-pdf # optional, requires latex packages installed
|
||||
ssh pytest-dev@pytest.org # MANUAL: symlink "pytest.org/latest" to the just
|
||||
# installed release docs
|
||||
# browse to pytest.org to see
|
||||
|
||||
devpi push pytest-VERSION pypi:NAME
|
||||
git commit -a -m "... finalized pytest-VERSION"
|
||||
git tag VERSION
|
||||
git push
|
||||
|
||||
9. send out release announcement to pytest-dev@python.org,
|
||||
testing-in-python@lists.idyll.org and python-announce-list@python.org .
|
||||
|
||||
10. **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``).
|
||||
|
||||
11. already done :)
|
|
@ -83,7 +83,7 @@ As with all function :ref:`marking <mark>` you can skip test functions at the
|
|||
`whole class- or module level`_. If your code targets python2.6 or above you
|
||||
use the skipif decorator (and any other marker) on classes::
|
||||
|
||||
@pytest.mark.skipif(sys.platform == 'win32',
|
||||
@pytest.mark.skipif(sys.platform != 'win32',
|
||||
reason="requires windows")
|
||||
class TestPosixCalls:
|
||||
|
||||
|
@ -97,7 +97,7 @@ If your code targets python2.5 where class-decorators are not available,
|
|||
you can set the ``pytestmark`` attribute of a class::
|
||||
|
||||
class TestPosixCalls:
|
||||
pytestmark = pytest.mark.skipif(sys.platform == 'win32',
|
||||
pytestmark = pytest.mark.skipif(sys.platform != 'win32',
|
||||
reason="requires Windows")
|
||||
|
||||
def test_function(self):
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue