Merge remote-tracking branch 'upstream/master' into features
This commit is contained in:
commit
654af0ba25
3
AUTHORS
3
AUTHORS
|
@ -78,6 +78,7 @@ Kale Kundert
|
||||||
Katarzyna Jachim
|
Katarzyna Jachim
|
||||||
Kevin Cox
|
Kevin Cox
|
||||||
Lee Kamentsky
|
Lee Kamentsky
|
||||||
|
Lev Maximov
|
||||||
Lukas Bednar
|
Lukas Bednar
|
||||||
Maciek Fijalkowski
|
Maciek Fijalkowski
|
||||||
Maho
|
Maho
|
||||||
|
@ -107,6 +108,7 @@ Punyashloka Biswal
|
||||||
Quentin Pradet
|
Quentin Pradet
|
||||||
Ralf Schmitt
|
Ralf Schmitt
|
||||||
Raphael Pierzina
|
Raphael Pierzina
|
||||||
|
Raquel Alegre
|
||||||
Roberto Polli
|
Roberto Polli
|
||||||
Romain Dorgueil
|
Romain Dorgueil
|
||||||
Roman Bolshakov
|
Roman Bolshakov
|
||||||
|
@ -126,6 +128,7 @@ Ted Xiao
|
||||||
Thomas Grainger
|
Thomas Grainger
|
||||||
Tom Viner
|
Tom Viner
|
||||||
Trevor Bekolay
|
Trevor Bekolay
|
||||||
|
Tyler Goodlet
|
||||||
Vasily Kuznetsov
|
Vasily Kuznetsov
|
||||||
Wouter van Ackooy
|
Wouter van Ackooy
|
||||||
Xuecong Liao
|
Xuecong Liao
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
New Features
|
New Features
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
|
||||||
*
|
*
|
||||||
|
|
||||||
*
|
*
|
||||||
|
@ -14,8 +13,8 @@ New Features
|
||||||
Changes
|
Changes
|
||||||
-------
|
-------
|
||||||
|
|
||||||
* Testcase reports with a url attribute will now properly write this to junitxml.
|
* Testcase reports with a ``url`` attribute will now properly write this to junitxml.
|
||||||
Thanks `@fushi`_ for the PR
|
Thanks `@fushi`_ for the PR (`#1874`_).
|
||||||
|
|
||||||
* Remove common items from dict comparision output when verbosity=1. Also update
|
* Remove common items from dict comparision output when verbosity=1. Also update
|
||||||
the truncation message to make it clearer that pytest truncates all
|
the truncation message to make it clearer that pytest truncates all
|
||||||
|
@ -35,9 +34,44 @@ Changes
|
||||||
.. _@mattduck: https://github.com/mattduck
|
.. _@mattduck: https://github.com/mattduck
|
||||||
|
|
||||||
.. _#1512: https://github.com/pytest-dev/pytest/issues/1512
|
.. _#1512: https://github.com/pytest-dev/pytest/issues/1512
|
||||||
|
.. _#1874: https://github.com/pytest-dev/pytest/pull/1874
|
||||||
.. _#1952: https://github.com/pytest-dev/pytest/pull/1952
|
.. _#1952: https://github.com/pytest-dev/pytest/pull/1952
|
||||||
|
|
||||||
|
|
||||||
|
3.0.3.dev
|
||||||
|
=========
|
||||||
|
|
||||||
|
* The ``ids`` argument to ``parametrize`` again accepts ``unicode`` strings
|
||||||
|
in Python 2 (`#1905`_).
|
||||||
|
Thanks `@philpep`_ for the report and `@nicoddemus`_ for the PR.
|
||||||
|
|
||||||
|
* Assertions are now being rewritten for plugins in development mode
|
||||||
|
(``pip install -e``) (`#1934`_).
|
||||||
|
Thanks `@nicoddemus`_ for the PR.
|
||||||
|
|
||||||
|
* Fix pkg_resources import error in Jython projects (`#1853`_).
|
||||||
|
Thanks `@raquel-ucl`_ for the PR.
|
||||||
|
|
||||||
|
* Got rid of ``AttributeError: 'Module' object has no attribute '_obj'`` exception
|
||||||
|
in Python 3 (`#1944`_).
|
||||||
|
Thanks `@axil`_ for the PR.
|
||||||
|
|
||||||
|
* Explain a bad scope value passed to ``@fixture`` declarations or
|
||||||
|
a ``MetaFunc.parametrize()`` call. Thanks `@tgoodlet`_ for the PR.
|
||||||
|
|
||||||
|
|
||||||
|
.. _@philpep: https://github.com/philpep
|
||||||
|
.. _@raquel-ucl: https://github.com/raquel-ucl
|
||||||
|
.. _@axil: https://github.com/axil
|
||||||
|
.. _@tgoodlet: https://github.com/tgoodlet
|
||||||
|
|
||||||
|
.. _#1853: https://github.com/pytest-dev/pytest/issues/1853
|
||||||
|
.. _#1905: https://github.com/pytest-dev/pytest/issues/1905
|
||||||
|
.. _#1934: https://github.com/pytest-dev/pytest/issues/1934
|
||||||
|
.. _#1944: https://github.com/pytest-dev/pytest/issues/1944
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
3.0.2
|
3.0.2
|
||||||
=====
|
=====
|
||||||
|
|
||||||
|
@ -126,7 +160,11 @@ time or change existing behaviors in order to make them less surprising/more use
|
||||||
|
|
||||||
* Reinterpretation mode has now been removed. Only plain and rewrite
|
* Reinterpretation mode has now been removed. Only plain and rewrite
|
||||||
mode are available, consequently the ``--assert=reinterp`` option is
|
mode are available, consequently the ``--assert=reinterp`` option is
|
||||||
no longer available. Thanks `@flub`_ for the PR.
|
no longer available. This also means files imported from plugins or
|
||||||
|
``conftest.py`` will not benefit from improved assertions by
|
||||||
|
default, you should use ``pytest.register_assert_rewrite()`` to
|
||||||
|
explicitly turn on assertion rewriting for those files. Thanks
|
||||||
|
`@flub`_ for the PR.
|
||||||
|
|
||||||
* The following deprecated commandline options were removed:
|
* The following deprecated commandline options were removed:
|
||||||
|
|
||||||
|
|
|
@ -54,8 +54,8 @@ Note: this assumes you have already registered on pypi.
|
||||||
11. Send release announcement to mailing lists:
|
11. Send release announcement to mailing lists:
|
||||||
|
|
||||||
- pytest-dev@python.org
|
- pytest-dev@python.org
|
||||||
- testing-in-python@lists.idyll.org
|
|
||||||
- python-announce-list@python.org
|
- python-announce-list@python.org
|
||||||
|
- testing-in-python@lists.idyll.org (only for minor/major releases)
|
||||||
|
|
||||||
And announce the release on Twitter, making sure to add the hashtag ``#pytest``.
|
And announce the release on Twitter, making sure to add the hashtag ``#pytest``.
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ if sys.version_info[0] >= 3:
|
||||||
else:
|
else:
|
||||||
from ._py2traceback import format_exception_only
|
from ._py2traceback import format_exception_only
|
||||||
|
|
||||||
|
|
||||||
class Code(object):
|
class Code(object):
|
||||||
""" wrapper around Python code objects """
|
""" wrapper around Python code objects """
|
||||||
def __init__(self, rawcode):
|
def __init__(self, rawcode):
|
||||||
|
@ -28,6 +29,8 @@ class Code(object):
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return self.raw == other.raw
|
return self.raw == other.raw
|
||||||
|
|
||||||
|
__hash__ = None
|
||||||
|
|
||||||
def __ne__(self, other):
|
def __ne__(self, other):
|
||||||
return not self == other
|
return not self == other
|
||||||
|
|
||||||
|
|
|
@ -52,22 +52,21 @@ class Source(object):
|
||||||
return str(self) == other
|
return str(self) == other
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
__hash__ = None
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
if isinstance(key, int):
|
if isinstance(key, int):
|
||||||
return self.lines[key]
|
return self.lines[key]
|
||||||
else:
|
else:
|
||||||
if key.step not in (None, 1):
|
if key.step not in (None, 1):
|
||||||
raise IndexError("cannot slice a Source with a step")
|
raise IndexError("cannot slice a Source with a step")
|
||||||
return self.__getslice__(key.start, key.stop)
|
newsource = Source()
|
||||||
|
newsource.lines = self.lines[key.start:key.stop]
|
||||||
|
return newsource
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
return len(self.lines)
|
return len(self.lines)
|
||||||
|
|
||||||
def __getslice__(self, start, end):
|
|
||||||
newsource = Source()
|
|
||||||
newsource.lines = self.lines[start:end]
|
|
||||||
return newsource
|
|
||||||
|
|
||||||
def strip(self):
|
def strip(self):
|
||||||
""" return new source object with trailing
|
""" return new source object with trailing
|
||||||
and leading blank lines removed.
|
and leading blank lines removed.
|
||||||
|
|
|
@ -105,7 +105,7 @@ except NameError:
|
||||||
def assertrepr_compare(config, op, left, right):
|
def assertrepr_compare(config, op, left, right):
|
||||||
"""Return specialised explanations for some operators/operands"""
|
"""Return specialised explanations for some operators/operands"""
|
||||||
width = 80 - 15 - len(op) - 2 # 15 chars indentation, 1 space around op
|
width = 80 - 15 - len(op) - 2 # 15 chars indentation, 1 space around op
|
||||||
left_repr = py.io.saferepr(left, maxsize=int(width/2))
|
left_repr = py.io.saferepr(left, maxsize=int(width//2))
|
||||||
right_repr = py.io.saferepr(right, maxsize=width-len(left_repr))
|
right_repr = py.io.saferepr(right, maxsize=width-len(left_repr))
|
||||||
|
|
||||||
summary = u('%s %s %s') % (ecu(left_repr), op, ecu(right_repr))
|
summary = u('%s %s %s') % (ecu(left_repr), op, ecu(right_repr))
|
||||||
|
|
|
@ -5,7 +5,6 @@ import traceback
|
||||||
import types
|
import types
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
import pkg_resources
|
|
||||||
import py
|
import py
|
||||||
# DON't import pytest here because it causes import cycle troubles
|
# DON't import pytest here because it causes import cycle troubles
|
||||||
import sys, os
|
import sys, os
|
||||||
|
@ -952,18 +951,24 @@ class Config(object):
|
||||||
except SystemError:
|
except SystemError:
|
||||||
mode = 'plain'
|
mode = 'plain'
|
||||||
else:
|
else:
|
||||||
|
import pkg_resources
|
||||||
self.pluginmanager.rewrite_hook = hook
|
self.pluginmanager.rewrite_hook = hook
|
||||||
for entrypoint in pkg_resources.iter_entry_points('pytest11'):
|
for entrypoint in pkg_resources.iter_entry_points('pytest11'):
|
||||||
for entry in entrypoint.dist._get_metadata('RECORD'):
|
# 'RECORD' available for plugins installed normally (pip install)
|
||||||
fn = entry.split(',')[0]
|
# 'SOURCES.txt' available for plugins installed in dev mode (pip install -e)
|
||||||
is_simple_module = os.sep not in fn and fn.endswith('.py')
|
# for installed plugins 'SOURCES.txt' returns an empty list, and vice-versa
|
||||||
is_package = fn.count(os.sep) == 1 and fn.endswith('__init__.py')
|
# so it shouldn't be an issue
|
||||||
if is_simple_module:
|
for metadata in ('RECORD', 'SOURCES.txt'):
|
||||||
module_name, ext = os.path.splitext(fn)
|
for entry in entrypoint.dist._get_metadata(metadata):
|
||||||
hook.mark_rewrite(module_name)
|
fn = entry.split(',')[0]
|
||||||
elif is_package:
|
is_simple_module = os.sep not in fn and fn.endswith('.py')
|
||||||
package_name = os.path.dirname(fn)
|
is_package = fn.count(os.sep) == 1 and fn.endswith('__init__.py')
|
||||||
hook.mark_rewrite(package_name)
|
if is_simple_module:
|
||||||
|
module_name, ext = os.path.splitext(fn)
|
||||||
|
hook.mark_rewrite(module_name)
|
||||||
|
elif is_package:
|
||||||
|
package_name = os.path.dirname(fn)
|
||||||
|
hook.mark_rewrite(package_name)
|
||||||
self._warn_about_missing_assertion(mode)
|
self._warn_about_missing_assertion(mode)
|
||||||
|
|
||||||
def _warn_about_missing_assertion(self, mode):
|
def _warn_about_missing_assertion(self, mode):
|
||||||
|
|
|
@ -599,12 +599,29 @@ class ScopeMismatchError(Exception):
|
||||||
which has a lower scope (e.g. a Session one calls a function one)
|
which has a lower scope (e.g. a Session one calls a function one)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
scopes = "session module class function".split()
|
scopes = "session module class function".split()
|
||||||
scopenum_function = scopes.index("function")
|
scopenum_function = scopes.index("function")
|
||||||
|
|
||||||
|
|
||||||
def scopemismatch(currentscope, newscope):
|
def scopemismatch(currentscope, newscope):
|
||||||
return scopes.index(newscope) > scopes.index(currentscope)
|
return scopes.index(newscope) > scopes.index(currentscope)
|
||||||
|
|
||||||
|
|
||||||
|
def scope2index(scope, descr, where=None):
|
||||||
|
"""Look up the index of ``scope`` and raise a descriptive value error
|
||||||
|
if not defined.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return scopes.index(scope)
|
||||||
|
except ValueError:
|
||||||
|
raise ValueError(
|
||||||
|
"{0} {1}has an unsupported scope value '{2}'".format(
|
||||||
|
descr, 'from {0} '.format(where) if where else '',
|
||||||
|
scope)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class FixtureLookupError(LookupError):
|
class FixtureLookupError(LookupError):
|
||||||
""" could not return a requested Fixture (missing or invalid). """
|
""" could not return a requested Fixture (missing or invalid). """
|
||||||
def __init__(self, argname, request, msg=None):
|
def __init__(self, argname, request, msg=None):
|
||||||
|
@ -703,6 +720,7 @@ def call_fixture_func(fixturefunc, request, kwargs):
|
||||||
res = fixturefunc(**kwargs)
|
res = fixturefunc(**kwargs)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
class FixtureDef:
|
class FixtureDef:
|
||||||
""" A container for a factory definition. """
|
""" A container for a factory definition. """
|
||||||
def __init__(self, fixturemanager, baseid, argname, func, scope, params,
|
def __init__(self, fixturemanager, baseid, argname, func, scope, params,
|
||||||
|
@ -713,7 +731,11 @@ class FixtureDef:
|
||||||
self.func = func
|
self.func = func
|
||||||
self.argname = argname
|
self.argname = argname
|
||||||
self.scope = scope
|
self.scope = scope
|
||||||
self.scopenum = scopes.index(scope or "function")
|
self.scopenum = scope2index(
|
||||||
|
scope or "function",
|
||||||
|
descr='fixture {0}'.format(func.__name__),
|
||||||
|
where=baseid
|
||||||
|
)
|
||||||
self.params = params
|
self.params = params
|
||||||
startindex = unittest and 1 or None
|
startindex = unittest and 1 or None
|
||||||
self.argnames = getfuncargnames(func, startindex=startindex)
|
self.argnames = getfuncargnames(func, startindex=startindex)
|
||||||
|
|
|
@ -25,7 +25,7 @@ def pytest_addoption(parser):
|
||||||
help="only run tests which match the given substring expression. "
|
help="only run tests which match the given substring expression. "
|
||||||
"An expression is a python evaluatable expression "
|
"An expression is a python evaluatable expression "
|
||||||
"where all names are substring-matched against test names "
|
"where all names are substring-matched against test names "
|
||||||
"and their parent classes. Example: -k 'test_method or test "
|
"and their parent classes. Example: -k 'test_method or test_"
|
||||||
"other' matches all test functions and classes whose name "
|
"other' matches all test functions and classes whose name "
|
||||||
"contains 'test_method' or 'test_other'. "
|
"contains 'test_method' or 'test_other'. "
|
||||||
"Additionally keywords are matched to classes and functions "
|
"Additionally keywords are matched to classes and functions "
|
||||||
|
|
|
@ -205,11 +205,10 @@ class PyobjContext(object):
|
||||||
class PyobjMixin(PyobjContext):
|
class PyobjMixin(PyobjContext):
|
||||||
def obj():
|
def obj():
|
||||||
def fget(self):
|
def fget(self):
|
||||||
try:
|
obj = getattr(self, '_obj', None)
|
||||||
return self._obj
|
if obj is None:
|
||||||
except AttributeError:
|
|
||||||
self._obj = obj = self._getobj()
|
self._obj = obj = self._getobj()
|
||||||
return obj
|
return obj
|
||||||
def fset(self, value):
|
def fset(self, value):
|
||||||
self._obj = value
|
self._obj = value
|
||||||
return property(fget, fset, None, "underlying python object")
|
return property(fget, fset, None, "underlying python object")
|
||||||
|
@ -772,7 +771,7 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
|
||||||
It will also override any fixture-function defined scope, allowing
|
It will also override any fixture-function defined scope, allowing
|
||||||
to set a dynamic scope using test context or configuration.
|
to set a dynamic scope using test context or configuration.
|
||||||
"""
|
"""
|
||||||
from _pytest.fixtures import scopes
|
from _pytest.fixtures import scope2index
|
||||||
from _pytest.mark import extract_argvalue
|
from _pytest.mark import extract_argvalue
|
||||||
from py.io import saferepr
|
from py.io import saferepr
|
||||||
|
|
||||||
|
@ -801,7 +800,8 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
|
||||||
if scope is None:
|
if scope is None:
|
||||||
scope = _find_parametrized_scope(argnames, self._arg2fixturedefs, indirect)
|
scope = _find_parametrized_scope(argnames, self._arg2fixturedefs, indirect)
|
||||||
|
|
||||||
scopenum = scopes.index(scope)
|
scopenum = scope2index(
|
||||||
|
scope, descr='call to {0}'.format(self.parametrize))
|
||||||
valtypes = {}
|
valtypes = {}
|
||||||
for arg in argnames:
|
for arg in argnames:
|
||||||
if arg not in self.fixturenames:
|
if arg not in self.fixturenames:
|
||||||
|
@ -833,7 +833,7 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
|
||||||
raise ValueError('%d tests specified with %d ids' %(
|
raise ValueError('%d tests specified with %d ids' %(
|
||||||
len(argvalues), len(ids)))
|
len(argvalues), len(ids)))
|
||||||
for id_value in ids:
|
for id_value in ids:
|
||||||
if id_value is not None and not isinstance(id_value, str):
|
if id_value is not None and not isinstance(id_value, py.builtin._basestring):
|
||||||
msg = 'ids must be list of strings, found: %s (type: %s)'
|
msg = 'ids must be list of strings, found: %s (type: %s)'
|
||||||
raise ValueError(msg % (saferepr(id_value), type(id_value).__name__))
|
raise ValueError(msg % (saferepr(id_value), type(id_value).__name__))
|
||||||
ids = idmaker(argnames, argvalues, idfn, ids, self.config)
|
ids = idmaker(argnames, argvalues, idfn, ids, self.config)
|
||||||
|
@ -1352,6 +1352,8 @@ class approx(object):
|
||||||
return False
|
return False
|
||||||
return all(a == x for a, x in zip(actual, self.expected))
|
return all(a == x for a, x in zip(actual, self.expected))
|
||||||
|
|
||||||
|
__hash__ = None
|
||||||
|
|
||||||
def __ne__(self, actual):
|
def __ne__(self, actual):
|
||||||
return not (actual == self)
|
return not (actual == self)
|
||||||
|
|
||||||
|
@ -1431,6 +1433,8 @@ class ApproxNonIterable(object):
|
||||||
# Return true if the two numbers are within the tolerance.
|
# Return true if the two numbers are within the tolerance.
|
||||||
return abs(self.expected - actual) <= self.tolerance
|
return abs(self.expected - actual) <= self.tolerance
|
||||||
|
|
||||||
|
__hash__ = None
|
||||||
|
|
||||||
def __ne__(self, actual):
|
def __ne__(self, actual):
|
||||||
return not (actual == self)
|
return not (actual == self)
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,8 @@ environment:
|
||||||
matrix:
|
matrix:
|
||||||
# create multiple jobs to execute a set of tox runs on each; this is to workaround having
|
# create multiple jobs to execute a set of tox runs on each; this is to workaround having
|
||||||
# builds timing out in AppVeyor
|
# builds timing out in AppVeyor
|
||||||
- TOXENV: "linting,py26,py27,py33,py34,py35,pypy"
|
# pypy is disabled until #1963 gets fixed
|
||||||
|
- TOXENV: "linting,py26,py27,py33,py34,py35"
|
||||||
- TOXENV: "py27-pexpect,py27-xdist,py27-trial,py35-pexpect,py35-xdist,py35-trial"
|
- TOXENV: "py27-pexpect,py27-xdist,py27-trial,py35-pexpect,py35-xdist,py35-trial"
|
||||||
- TOXENV: "py27-nobyte,doctesting,freeze,docs"
|
- TOXENV: "py27-nobyte,doctesting,freeze,docs"
|
||||||
|
|
||||||
|
|
|
@ -19,10 +19,9 @@ REGENDOC_ARGS := \
|
||||||
--normalize "@/tmp/pytest-of-.*/pytest-\d+@PYTEST_TMPDIR@" \
|
--normalize "@/tmp/pytest-of-.*/pytest-\d+@PYTEST_TMPDIR@" \
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
|
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
|
||||||
|
|
||||||
|
|
||||||
help:
|
help:
|
||||||
@echo "Please use \`make <target>' where <target> is one of"
|
@echo "Please use \`make <target>' where <target> is one of"
|
||||||
@echo " html to make standalone HTML files"
|
@echo " html to make standalone HTML files"
|
||||||
|
@ -36,22 +35,6 @@ help:
|
||||||
clean:
|
clean:
|
||||||
-rm -rf $(BUILDDIR)/*
|
-rm -rf $(BUILDDIR)/*
|
||||||
|
|
||||||
SITETARGET=$(shell ./_getdoctarget.py)
|
|
||||||
|
|
||||||
showtarget:
|
|
||||||
@echo $(SITETARGET)
|
|
||||||
|
|
||||||
install: html
|
|
||||||
# for access talk to someone with login rights to
|
|
||||||
# pytest-dev@pytest.org to add your ssh key
|
|
||||||
rsync -avz _build/html/ pytest-dev@pytest.org:pytest.org/$(SITETARGET)
|
|
||||||
|
|
||||||
installpdf: latexpdf
|
|
||||||
@scp $(BUILDDIR)/latex/pytest.pdf pytest-dev@pytest.org:pytest.org/$(SITETARGET)
|
|
||||||
|
|
||||||
installall: clean install installpdf
|
|
||||||
@echo "done"
|
|
||||||
|
|
||||||
regen:
|
regen:
|
||||||
PYTHONDONTWRITEBYTECODE=1 COLUMNS=76 regendoc --update *.rst */*.rst ${REGENDOC_ARGS}
|
PYTHONDONTWRITEBYTECODE=1 COLUMNS=76 regendoc --update *.rst */*.rst ${REGENDOC_ARGS}
|
||||||
|
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
#!/usr/bin/env python
|
|
||||||
|
|
||||||
import py
|
|
||||||
|
|
||||||
def get_version_string():
|
|
||||||
fn = py.path.local(__file__).join("..", "..", "..",
|
|
||||||
"_pytest", "__init__.py")
|
|
||||||
for line in fn.readlines():
|
|
||||||
if "version" in line and not line.strip().startswith('#'):
|
|
||||||
return eval(line.split("=")[-1])
|
|
||||||
|
|
||||||
def get_minor_version_string():
|
|
||||||
return ".".join(get_version_string().split(".")[:2])
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
print (get_minor_version_string())
|
|
|
@ -6,6 +6,6 @@
|
||||||
<li><a href="https://github.com/pytest-dev/pytest/">pytest @ GitHub</a></li>
|
<li><a href="https://github.com/pytest-dev/pytest/">pytest @ GitHub</a></li>
|
||||||
<li><a href="http://plugincompat.herokuapp.com/">3rd party plugins</a></li>
|
<li><a href="http://plugincompat.herokuapp.com/">3rd party plugins</a></li>
|
||||||
<li><a href="https://github.com/pytest-dev/pytest/issues">Issue Tracker</a></li>
|
<li><a href="https://github.com/pytest-dev/pytest/issues">Issue Tracker</a></li>
|
||||||
<li><a href="http://pytest.org/latest/pytest.pdf">PDF Documentation</a>
|
<li><a href="https://media.readthedocs.org/pdf/pytest/latest/pytest.pdf">PDF Documentation</a>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
|
@ -19,11 +19,8 @@
|
||||||
# The short X.Y version.
|
# The short X.Y version.
|
||||||
|
|
||||||
import os, sys
|
import os, sys
|
||||||
sys.path.insert(0, os.path.dirname(__file__))
|
from _pytest import __version__ as version
|
||||||
import _getdoctarget
|
release = ".".join(version.split(".")[:2])
|
||||||
|
|
||||||
version = _getdoctarget.get_minor_version_string()
|
|
||||||
release = _getdoctarget.get_version_string()
|
|
||||||
|
|
||||||
# If extensions (or modules to document with autodoc) are in another directory,
|
# If extensions (or modules to document with autodoc) are in another directory,
|
||||||
# add these directories to sys.path here. If the directory is relative to the
|
# add these directories to sys.path here. If the directory is relative to the
|
||||||
|
|
|
@ -59,6 +59,7 @@ Method reference of the monkeypatch fixture
|
||||||
-------------------------------------------
|
-------------------------------------------
|
||||||
|
|
||||||
.. autoclass:: MonkeyPatch
|
.. autoclass:: MonkeyPatch
|
||||||
|
:members:
|
||||||
|
|
||||||
``monkeypatch.setattr/delattr/delitem/delenv()`` all
|
``monkeypatch.setattr/delattr/delitem/delenv()`` all
|
||||||
by default raise an Exception if the target does not exist.
|
by default raise an Exception if the target does not exist.
|
||||||
|
|
|
@ -216,6 +216,3 @@ The **metafunc** object
|
||||||
.. currentmodule:: _pytest.python
|
.. currentmodule:: _pytest.python
|
||||||
.. autoclass:: Metafunc
|
.. autoclass:: Metafunc
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
.. automethod:: Metafunc.parametrize
|
|
||||||
.. automethod:: Metafunc.addcall(funcargs=None,id=_notexists,param=_notexists)
|
|
||||||
|
|
|
@ -293,6 +293,20 @@ imperatively, in test or setup code::
|
||||||
# or
|
# or
|
||||||
pytest.skip("unsupported configuration")
|
pytest.skip("unsupported configuration")
|
||||||
|
|
||||||
|
Note that calling ``pytest.skip`` at the module level
|
||||||
|
is not allowed since pytest 3.0. If you are upgrading
|
||||||
|
and ``pytest.skip`` was being used at the module level, you can set a
|
||||||
|
``pytestmark`` variable:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
# before pytest 3.0
|
||||||
|
pytest.skip('skipping all tests because of reasons')
|
||||||
|
# after pytest 3.0
|
||||||
|
pytestmark = pytest.mark.skip('skipping all tests because of reasons')
|
||||||
|
|
||||||
|
``pytestmark`` applies a mark or list of marks to all tests in a module.
|
||||||
|
|
||||||
|
|
||||||
Skipping on a missing import dependency
|
Skipping on a missing import dependency
|
||||||
--------------------------------------------------
|
--------------------------------------------------
|
||||||
|
@ -371,3 +385,27 @@ The equivalent with "boolean conditions" is::
|
||||||
imported before pytest's argument parsing takes place. For example,
|
imported before pytest's argument parsing takes place. For example,
|
||||||
``conftest.py`` files are imported before command line parsing and thus
|
``conftest.py`` files are imported before command line parsing and thus
|
||||||
``config.getvalue()`` will not execute correctly.
|
``config.getvalue()`` will not execute correctly.
|
||||||
|
|
||||||
|
|
||||||
|
Summary
|
||||||
|
-------
|
||||||
|
|
||||||
|
Here's a quick guide on how to skip tests in a module in different situations:
|
||||||
|
|
||||||
|
1. Skip all tests in a module unconditionally:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.skip('all tests still WIP')
|
||||||
|
|
||||||
|
2. Skip all tests in a module based on some condition:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.skipif(sys.platform == 'win32', 'tests for linux only')
|
||||||
|
|
||||||
|
3. Skip all tests in a module if some import is missing:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
pexpect = pytest.importorskip('pexpect')
|
||||||
|
|
|
@ -38,7 +38,11 @@ the general ``pytest`` documentation for many more examples.
|
||||||
Running tests from ``unittest.TestCase`` subclasses with ``--pdb`` will
|
Running tests from ``unittest.TestCase`` subclasses with ``--pdb`` will
|
||||||
disable tearDown and cleanup methods for the case that an Exception
|
disable tearDown and cleanup methods for the case that an Exception
|
||||||
occurs. This allows proper post mortem debugging for all applications
|
occurs. This allows proper post mortem debugging for all applications
|
||||||
which have significant logic in their tearDown machinery.
|
which have significant logic in their tearDown machinery. However,
|
||||||
|
supporting this feature has the following side effect: If people
|
||||||
|
overwrite ``unittest.TestCase`` ``__call__`` or ``run``, they need to
|
||||||
|
to overwrite ``debug`` in the same way (this is also true for standard
|
||||||
|
unittest).
|
||||||
|
|
||||||
Mixing pytest fixtures into unittest.TestCase style tests
|
Mixing pytest fixtures into unittest.TestCase style tests
|
||||||
-----------------------------------------------------------
|
-----------------------------------------------------------
|
||||||
|
|
|
@ -1063,6 +1063,22 @@ class TestFixtureUsages:
|
||||||
"*1 error*"
|
"*1 error*"
|
||||||
])
|
])
|
||||||
|
|
||||||
|
def test_invalid_scope(self, testdir):
|
||||||
|
testdir.makepyfile("""
|
||||||
|
import pytest
|
||||||
|
@pytest.fixture(scope="functions")
|
||||||
|
def badscope():
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_nothing(badscope):
|
||||||
|
pass
|
||||||
|
""")
|
||||||
|
result = testdir.runpytest_inprocess()
|
||||||
|
result.stdout.fnmatch_lines(
|
||||||
|
("*ValueError: fixture badscope from test_invalid_scope.py has an unsupported"
|
||||||
|
" scope value 'functions'")
|
||||||
|
)
|
||||||
|
|
||||||
def test_funcarg_parametrized_and_used_twice(self, testdir):
|
def test_funcarg_parametrized_and_used_twice(self, testdir):
|
||||||
testdir.makepyfile("""
|
testdir.makepyfile("""
|
||||||
import pytest
|
import pytest
|
||||||
|
|
|
@ -96,6 +96,14 @@ class TestMetafunc:
|
||||||
pytest.raises(ValueError, lambda: metafunc.parametrize("y", [5,6]))
|
pytest.raises(ValueError, lambda: metafunc.parametrize("y", [5,6]))
|
||||||
pytest.raises(ValueError, lambda: metafunc.parametrize("y", [5,6]))
|
pytest.raises(ValueError, lambda: metafunc.parametrize("y", [5,6]))
|
||||||
|
|
||||||
|
def test_parametrize_bad_scope(self, testdir):
|
||||||
|
def func(x): pass
|
||||||
|
metafunc = self.Metafunc(func)
|
||||||
|
try:
|
||||||
|
metafunc.parametrize("x", [1], scope='doggy')
|
||||||
|
except ValueError as ve:
|
||||||
|
assert "has an unsupported scope value 'doggy'" in str(ve)
|
||||||
|
|
||||||
def test_parametrize_and_id(self):
|
def test_parametrize_and_id(self):
|
||||||
def func(x, y): pass
|
def func(x, y): pass
|
||||||
metafunc = self.Metafunc(func)
|
metafunc = self.Metafunc(func)
|
||||||
|
@ -105,6 +113,14 @@ class TestMetafunc:
|
||||||
ids = [x.id for x in metafunc._calls]
|
ids = [x.id for x in metafunc._calls]
|
||||||
assert ids == ["basic-abc", "basic-def", "advanced-abc", "advanced-def"]
|
assert ids == ["basic-abc", "basic-def", "advanced-abc", "advanced-def"]
|
||||||
|
|
||||||
|
def test_parametrize_and_id_unicode(self):
|
||||||
|
"""Allow unicode strings for "ids" parameter in Python 2 (##1905)"""
|
||||||
|
def func(x): pass
|
||||||
|
metafunc = self.Metafunc(func)
|
||||||
|
metafunc.parametrize("x", [1, 2], ids=[u'basic', u'advanced'])
|
||||||
|
ids = [x.id for x in metafunc._calls]
|
||||||
|
assert ids == [u"basic", u"advanced"]
|
||||||
|
|
||||||
def test_parametrize_with_wrong_number_of_ids(self, testdir):
|
def test_parametrize_with_wrong_number_of_ids(self, testdir):
|
||||||
def func(x, y): pass
|
def func(x, y): pass
|
||||||
metafunc = self.Metafunc(func)
|
metafunc = self.Metafunc(func)
|
||||||
|
|
|
@ -108,7 +108,8 @@ class TestImportHookInstallation:
|
||||||
assert result.ret == 0
|
assert result.ret == 0
|
||||||
|
|
||||||
@pytest.mark.parametrize('mode', ['plain', 'rewrite'])
|
@pytest.mark.parametrize('mode', ['plain', 'rewrite'])
|
||||||
def test_installed_plugin_rewrite(self, testdir, mode):
|
@pytest.mark.parametrize('plugin_state', ['development', 'installed'])
|
||||||
|
def test_installed_plugin_rewrite(self, testdir, mode, plugin_state):
|
||||||
# Make sure the hook is installed early enough so that plugins
|
# Make sure the hook is installed early enough so that plugins
|
||||||
# installed via setuptools are re-written.
|
# installed via setuptools are re-written.
|
||||||
testdir.tmpdir.join('hampkg').ensure(dir=1)
|
testdir.tmpdir.join('hampkg').ensure(dir=1)
|
||||||
|
@ -135,13 +136,22 @@ class TestImportHookInstallation:
|
||||||
'mainwrapper.py': """
|
'mainwrapper.py': """
|
||||||
import pytest, pkg_resources
|
import pytest, pkg_resources
|
||||||
|
|
||||||
|
plugin_state = "{plugin_state}"
|
||||||
|
|
||||||
class DummyDistInfo:
|
class DummyDistInfo:
|
||||||
project_name = 'spam'
|
project_name = 'spam'
|
||||||
version = '1.0'
|
version = '1.0'
|
||||||
|
|
||||||
def _get_metadata(self, name):
|
def _get_metadata(self, name):
|
||||||
return ['spamplugin.py,sha256=abc,123',
|
# 'RECORD' meta-data only available in installed plugins
|
||||||
'hampkg/__init__.py,sha256=abc,123']
|
if name == 'RECORD' and plugin_state == "installed":
|
||||||
|
return ['spamplugin.py,sha256=abc,123',
|
||||||
|
'hampkg/__init__.py,sha256=abc,123']
|
||||||
|
# 'SOURCES.txt' meta-data only available for plugins in development mode
|
||||||
|
elif name == 'SOURCES.txt' and plugin_state == "development":
|
||||||
|
return ['spamplugin.py',
|
||||||
|
'hampkg/__init__.py']
|
||||||
|
return []
|
||||||
|
|
||||||
class DummyEntryPoint:
|
class DummyEntryPoint:
|
||||||
name = 'spam'
|
name = 'spam'
|
||||||
|
@ -159,7 +169,7 @@ class TestImportHookInstallation:
|
||||||
|
|
||||||
pkg_resources.iter_entry_points = iter_entry_points
|
pkg_resources.iter_entry_points = iter_entry_points
|
||||||
pytest.main()
|
pytest.main()
|
||||||
""",
|
""".format(plugin_state=plugin_state),
|
||||||
'test_foo.py': """
|
'test_foo.py': """
|
||||||
def test(check_first):
|
def test(check_first):
|
||||||
check_first([10, 30], 30)
|
check_first([10, 30], 30)
|
||||||
|
@ -862,3 +872,15 @@ def test_assert_with_unicode(monkeypatch, testdir):
|
||||||
""")
|
""")
|
||||||
result = testdir.runpytest()
|
result = testdir.runpytest()
|
||||||
result.stdout.fnmatch_lines(['*AssertionError*'])
|
result.stdout.fnmatch_lines(['*AssertionError*'])
|
||||||
|
|
||||||
|
def test_issue_1944(testdir):
|
||||||
|
testdir.makepyfile("""
|
||||||
|
def f():
|
||||||
|
return
|
||||||
|
|
||||||
|
assert f() == 10
|
||||||
|
""")
|
||||||
|
result = testdir.runpytest()
|
||||||
|
result.stdout.fnmatch_lines(["*1 error*"])
|
||||||
|
assert "AttributeError: 'Module' object has no attribute '_obj'" not in result.stdout.str()
|
||||||
|
|
||||||
|
|
11
tox.ini
11
tox.ini
|
@ -10,8 +10,8 @@ envlist=
|
||||||
[testenv]
|
[testenv]
|
||||||
commands= pytest --lsof -rfsxX {posargs:testing}
|
commands= pytest --lsof -rfsxX {posargs:testing}
|
||||||
passenv = USER USERNAME
|
passenv = USER USERNAME
|
||||||
deps=
|
deps=
|
||||||
hypothesis
|
hypothesis>=3.5.2
|
||||||
nose
|
nose
|
||||||
mock
|
mock
|
||||||
requests
|
requests
|
||||||
|
@ -47,7 +47,7 @@ commands = flake8 pytest.py _pytest testing
|
||||||
deps=pytest-xdist>=1.13
|
deps=pytest-xdist>=1.13
|
||||||
mock
|
mock
|
||||||
nose
|
nose
|
||||||
hypothesis
|
hypothesis>=3.5.2
|
||||||
commands=
|
commands=
|
||||||
pytest -n1 -rfsxX {posargs:testing}
|
pytest -n1 -rfsxX {posargs:testing}
|
||||||
|
|
||||||
|
@ -71,8 +71,9 @@ commands=
|
||||||
pytest -rfsxX test_pdb.py test_terminal.py test_unittest.py
|
pytest -rfsxX test_pdb.py test_terminal.py test_unittest.py
|
||||||
|
|
||||||
[testenv:py27-nobyte]
|
[testenv:py27-nobyte]
|
||||||
deps=pytest-xdist>=1.13
|
deps=
|
||||||
hypothesis
|
pytest-xdist>=1.13
|
||||||
|
hypothesis>=3.5.2
|
||||||
distribute=true
|
distribute=true
|
||||||
setenv=
|
setenv=
|
||||||
PYTHONDONTWRITEBYTECODE=1
|
PYTHONDONTWRITEBYTECODE=1
|
||||||
|
|
Loading…
Reference in New Issue