Merge branch 'master' into merge-master-into-features

This commit is contained in:
Bruno Oliveira 2016-11-27 17:30:40 -02:00
commit b30a6d22c5
18 changed files with 163 additions and 42 deletions

View File

@ -44,6 +44,7 @@ David Mohr
David Vierra David Vierra
Diego Russo Diego Russo
Dmitry Dygalo Dmitry Dygalo
Duncan Betts
Edison Gustavo Muenz Edison Gustavo Muenz
Edoardo Batini Edoardo Batini
Eduardo Schettino Eduardo Schettino
@ -82,6 +83,7 @@ Kevin Cox
Lee Kamentsky Lee Kamentsky
Lev Maximov Lev Maximov
Lukas Bednar Lukas Bednar
Luke Murphy
Maciek Fijalkowski Maciek Fijalkowski
Maho Maho
Marc Schlaich Marc Schlaich
@ -102,6 +104,8 @@ Michael Birtwell
Michael Droettboom Michael Droettboom
Michael Seifert Michael Seifert
Mike Lundy Mike Lundy
Ned Batchelder
Neven Mundar
Nicolas Delaby Nicolas Delaby
Oleg Pidsadnyi Oleg Pidsadnyi
Oliver Bestwalter Oliver Bestwalter

View File

@ -45,16 +45,44 @@ Changes
========== ==========
* Add hint to error message hinting possible missing ``__init__.py`` (`#478`_). Thanks `@DuncanBetts`_.
* Provide ``:ref:`` targets for ``recwarn.rst`` so we can use intersphinx referencing.
Thanks to `@dupuy`_ for the report and `@lwm`_ for the PR.
* Using ``item.Function``, ``item.Module``, etc., is now issuing deprecation warnings, prefer
``pytest.Function``, ``pytest.Module``, etc., instead (`#2034`_).
Thanks `@nmundar`_ for the PR.
* An error message is now displayed if ``--confcutdir`` is not a valid directory, avoiding
subtle bugs (`#2078`_).
Thanks `@nicoddemus`_ for the PR.
* Fix error message using ``approx`` with complex numbers (`#2082`_).
Thanks `@adler-j`_ for the report and `@nicoddemus`_ for the PR.
*
* Cope gracefully with a .pyc file with no matching .py file (`#2038`_). Thanks
`@nedbat`_.
* *
* *
* .. _@dupuy: https://bitbucket.org/dupuy/
.. _@lwm: https://github.com/lwm
.. _@adler-j: https://github.com/adler-j
.. _@DuncanBetts: https://github.com/DuncanBetts
.. _@nedbat: https://github.com/nedbat
.. _@nmundar: https://github.com/nmundar
* .. _#478: https://github.com/pytest-dev/pytest/issues/478
.. _#2034: https://github.com/pytest-dev/pytest/issues/2034
* .. _#2038: https://github.com/pytest-dev/pytest/issues/2038
.. _#2078: https://github.com/pytest-dev/pytest/issues/2078
.. _#2082: https://github.com/pytest-dev/pytest/issues/2082
3.0.4 3.0.4

View File

@ -79,6 +79,16 @@ Pytest could always use more documentation. What exactly is needed?
You can also edit documentation files directly in the GitHub web interface, You can also edit documentation files directly in the GitHub web interface,
without using a local copy. This can be convenient for small fixes. without using a local copy. This can be convenient for small fixes.
.. note::
Build the documentation locally with the following command:
.. code:: bash
$ tox -e docs
The built documentation should be available in the ``doc/en/_build/``.
Where 'en' refers to the documentation language.
.. _submitplugin: .. _submitplugin:

View File

@ -24,31 +24,31 @@ An example of a simple test:
.. code-block:: python .. code-block:: python
# content of test_sample.py # content of test_sample.py
def func(x): def inc(x):
return x + 1 return x + 1
def test_answer(): def test_answer():
assert func(3) == 5 assert inc(3) == 5
To execute it:: To execute it::
$ pytest $ pytest
======= test session starts ======== ============================= test session starts =============================
collected 1 items collected 1 items
test_sample.py F test_sample.py F
======= FAILURES ======== ================================== FAILURES ===================================
_______ test_answer ________ _________________________________ test_answer _________________________________
def test_answer(): def test_answer():
> assert func(3) == 5 > assert inc(3) == 5
E assert 4 == 5 E assert 4 == 5
E + where 4 = func(3) E + where 4 = inc(3)
test_sample.py:5: AssertionError test_sample.py:5: AssertionError
======= 1 failed in 0.12 seconds ======== ========================== 1 failed in 0.04 seconds ===========================
Due to ``pytest``'s detailed assertion introspection, only plain ``assert`` statements are used. See `getting-started <http://docs.pytest.org/en/latest/getting-started.html#our-first-test-run>`_ for more examples. Due to ``pytest``'s detailed assertion introspection, only plain ``assert`` statements are used. See `getting-started <http://docs.pytest.org/en/latest/getting-started.html#our-first-test-run>`_ for more examples.

View File

@ -80,7 +80,12 @@ class AssertionRewritingHook(object):
tp = desc[2] tp = desc[2]
if tp == imp.PY_COMPILED: if tp == imp.PY_COMPILED:
if hasattr(imp, "source_from_cache"): if hasattr(imp, "source_from_cache"):
fn = imp.source_from_cache(fn) try:
fn = imp.source_from_cache(fn)
except ValueError:
# Python 3 doesn't like orphaned but still-importable
# .pyc files.
fn = fn[:-1]
else: else:
fn = fn[:-1] fn = fn[:-1]
elif tp != imp.PY_SOURCE: elif tp != imp.PY_SOURCE:

View File

@ -996,6 +996,7 @@ class Config(object):
"(are you using python -O?)\n") "(are you using python -O?)\n")
def _preparse(self, args, addopts=True): def _preparse(self, args, addopts=True):
import pytest
self._initini(args) self._initini(args)
if addopts: if addopts:
args[:] = shlex.split(os.environ.get('PYTEST_ADDOPTS', '')) + args args[:] = shlex.split(os.environ.get('PYTEST_ADDOPTS', '')) + args
@ -1007,7 +1008,10 @@ class Config(object):
self.pluginmanager.load_setuptools_entrypoints(entrypoint_name) self.pluginmanager.load_setuptools_entrypoints(entrypoint_name)
self.pluginmanager.consider_env() self.pluginmanager.consider_env()
self.known_args_namespace = ns = self._parser.parse_known_args(args, namespace=self.option.copy()) self.known_args_namespace = ns = self._parser.parse_known_args(args, namespace=self.option.copy())
if self.known_args_namespace.confcutdir is None and self.inifile: confcutdir = self.known_args_namespace.confcutdir
if confcutdir and not os.path.isdir(confcutdir):
raise pytest.UsageError('--confcutdir must be a directory, given: {0}'.format(confcutdir))
if confcutdir is None and self.inifile:
confcutdir = py.path.local(self.inifile).dirname confcutdir = py.path.local(self.inifile).dirname
self.known_args_namespace.confcutdir = confcutdir self.known_args_namespace.confcutdir = confcutdir
try: try:

View File

@ -190,7 +190,9 @@ class FSHookProxy:
def compatproperty(name): def compatproperty(name):
def fget(self): def fget(self):
# deprecated - use pytest.name import warnings
warnings.warn("This usage is deprecated, please use pytest.{0} instead".format(name),
PendingDeprecationWarning, stacklevel=2)
return getattr(pytest, name) return getattr(pytest, name)
return property(fget) return property(fget)
@ -700,10 +702,9 @@ class Session(FSCollector):
path = self.config.invocation_dir.join(relpath, abs=True) path = self.config.invocation_dir.join(relpath, abs=True)
if not path.check(): if not path.check():
if self.config.option.pyargs: if self.config.option.pyargs:
msg = "file or package not found: " raise pytest.UsageError("file or package not found: " + arg + " (missing __init__.py?)")
else: else:
msg = "file not found: " raise pytest.UsageError("file not found: " + arg)
raise pytest.UsageError(msg + arg)
parts[0] = path parts[0] = path
return parts return parts

View File

@ -1414,6 +1414,9 @@ class ApproxNonIterable(object):
self.rel = rel self.rel = rel
def __repr__(self): def __repr__(self):
if isinstance(self.expected, complex):
return str(self.expected)
# Infinities aren't compared using tolerances, so don't show a # Infinities aren't compared using tolerances, so don't show a
# tolerance. # tolerance.
if math.isinf(self.expected): if math.isinf(self.expected):

View File

@ -6,6 +6,8 @@ environment:
# https://www.appveyor.com/docs/build-configuration#secure-variables # https://www.appveyor.com/docs/build-configuration#secure-variables
matrix: matrix:
# coveralls is not in the default env list
- TOXENV: "coveralls"
# note: please use "tox --listenvs" to populate the build matrix below # note: please use "tox --listenvs" to populate the build matrix below
- TOXENV: "linting" - TOXENV: "linting"
- TOXENV: "py26" - TOXENV: "py26"
@ -29,14 +31,11 @@ install:
- echo Installed Pythons - echo Installed Pythons
- dir c:\Python* - dir c:\Python*
- if "%TOXENV%" == "pypy" scripts\install-pypy.bat - if "%TOXENV%" == "pypy" call scripts\install-pypy.bat
- C:\Python35\python -m pip install tox - C:\Python35\python -m pip install tox
build: false # Not a C# project, build stuff at the test step instead. build: false # Not a C# project, build stuff at the test step instead.
test_script: test_script:
- C:\Python35\python -m tox - call scripts\call-tox.bat
# coveralls is not in tox's envlist, plus for PRs the secure variable
# is not defined so we have to check for it
- if defined COVERALLS_REPO_TOKEN C:\Python35\python -m tox -e coveralls

View File

@ -16,10 +16,12 @@ Conventions for Python test discovery
* If no arguments are specified then collection starts from :confval:`testpaths` * If no arguments are specified then collection starts from :confval:`testpaths`
(if configured) or the current directory. Alternatively, command line arguments (if configured) or the current directory. Alternatively, command line arguments
can be used in any combination of directories, file names or node ids. can be used in any combination of directories, file names or node ids.
* recurse into directories, unless they match :confval:`norecursedirs` * Recurse into directories, unless they match :confval:`norecursedirs`.
* ``test_*.py`` or ``*_test.py`` files, imported by their `test package name`_. * In those directories, search for ``test_*.py`` or ``*_test.py`` files, imported by their `test package name`_.
* ``Test`` prefixed test classes (without an ``__init__`` method) * From those files, collect test items:
* ``test_`` prefixed test functions or methods are test items
* ``test_`` prefixed test functions or methods outside of class
* ``test_`` prefixed test functions or methods inside ``Test`` prefixed test classes (without an ``__init__`` method)
For examples of how to customize your test discovery :doc:`example/pythoncollection`. For examples of how to customize your test discovery :doc:`example/pythoncollection`.

View File

@ -14,33 +14,31 @@ An example of a simple test:
.. code-block:: python .. code-block:: python
# content of test_sample.py # content of test_sample.py
def func(x): def inc(x):
return x + 1 return x + 1
def test_answer(): def test_answer():
assert func(3) == 5 assert inc(3) == 5
To execute it:: To execute it::
$ pytest $ pytest
======= test session starts ======== ============================= test session starts =============================
platform linux -- Python 3.5.2, pytest-3.0.4, py-1.4.31, pluggy-0.4.0
rootdir: $REGENDOC_TMPDIR, inifile:
collected 1 items collected 1 items
test_sample.py F test_sample.py F
======= FAILURES ======== ================================== FAILURES ===================================
_______ test_answer ________ _________________________________ test_answer _________________________________
def test_answer(): def test_answer():
> assert func(3) == 5 > assert inc(3) == 5
E assert 4 == 5 E assert 4 == 5
E + where 4 = func(3) E + where 4 = inc(3)
test_sample.py:5: AssertionError test_sample.py:5: AssertionError
======= 1 failed in 0.12 seconds ======== ========================== 1 failed in 0.04 seconds ===========================
Due to ``pytest``'s detailed assertion introspection, only plain ``assert`` statements are used. Due to ``pytest``'s detailed assertion introspection, only plain ``assert`` statements are used.
See :ref:`Getting Started <getstarted>` for more examples. See :ref:`Getting Started <getstarted>` for more examples.

View File

@ -1,8 +1,12 @@
.. _`asserting warnings`:
.. _assertwarnings: .. _assertwarnings:
Asserting Warnings Asserting Warnings
===================================================== =====================================================
.. _`asserting warnings with the warns function`:
.. _warns: .. _warns:
Asserting warnings with the warns function Asserting warnings with the warns function
@ -46,6 +50,8 @@ Alternatively, you can examine raised warnings in detail using the
``DeprecationWarning`` and ``PendingDeprecationWarning`` are treated ``DeprecationWarning`` and ``PendingDeprecationWarning`` are treated
differently; see :ref:`ensuring_function_triggers`. differently; see :ref:`ensuring_function_triggers`.
.. _`recording warnings`:
.. _recwarn: .. _recwarn:
Recording warnings Recording warnings
@ -99,6 +105,8 @@ class of the warning. The ``message`` is the warning itself; calling
``DeprecationWarning`` and ``PendingDeprecationWarning`` are treated ``DeprecationWarning`` and ``PendingDeprecationWarning`` are treated
differently; see :ref:`ensuring_function_triggers`. differently; see :ref:`ensuring_function_triggers`.
.. _`ensuring a function triggers a deprecation warning`:
.. _ensuring_function_triggers: .. _ensuring_function_triggers:
Ensuring a function triggers a deprecation warning Ensuring a function triggers a deprecation warning

8
scripts/call-tox.bat Normal file
View File

@ -0,0 +1,8 @@
REM skip "coveralls" run in PRs or forks
if "%TOXENV%" == "coveralls" (
if not defined COVERALLS_REPO_TOKEN (
echo skipping coveralls run because COVERALLS_REPO_TOKEN is not defined
exit /b 0
)
)
C:\Python35\python -m tox

View File

@ -28,6 +28,7 @@ class TestApprox:
print(approx(inf)) print(approx(inf))
print(approx(1.0, rel=nan)) print(approx(1.0, rel=nan))
print(approx(1.0, rel=inf)) print(approx(1.0, rel=inf))
print(approx(1.0j, rel=inf))
def test_operator_overloading(self): def test_operator_overloading(self):
assert 1 == approx(1, rel=1e-6, abs=1e-12) assert 1 == approx(1, rel=1e-6, abs=1e-12)

View File

@ -1,7 +1,10 @@
import glob
import os import os
import py_compile
import stat import stat
import sys import sys
import zipfile import zipfile
import py import py
import pytest import pytest
@ -573,6 +576,31 @@ def test_rewritten():
monkeypatch.setenv("PYTHONDONTWRITEBYTECODE", "1") monkeypatch.setenv("PYTHONDONTWRITEBYTECODE", "1")
assert testdir.runpytest_subprocess().ret == 0 assert testdir.runpytest_subprocess().ret == 0
def test_orphaned_pyc_file(self, testdir):
if sys.version_info < (3, 0) and hasattr(sys, 'pypy_version_info'):
pytest.skip("pypy2 doesn't run orphaned pyc files")
testdir.makepyfile("""
import orphan
def test_it():
assert orphan.value == 17
""")
testdir.makepyfile(orphan="""
value = 17
""")
py_compile.compile("orphan.py")
os.remove("orphan.py")
# Python 3 puts the .pyc files in a __pycache__ directory, and will
# not import from there without source. It will import a .pyc from
# the source location though.
if not os.path.exists("orphan.pyc"):
pycs = glob.glob("__pycache__/orphan.*.pyc")
assert len(pycs) == 1
os.rename(pycs[0], "orphan.pyc")
assert testdir.runpytest().ret == 0
@pytest.mark.skipif('"__pypy__" in sys.modules') @pytest.mark.skipif('"__pypy__" in sys.modules')
def test_pyc_vs_pyo(self, testdir, monkeypatch): def test_pyc_vs_pyo(self, testdir, monkeypatch):
testdir.makepyfile(""" testdir.makepyfile("""

View File

@ -294,6 +294,15 @@ class TestConfigAPI:
assert len(l) == 2 assert len(l) == 2
assert l == ["456", "123"] assert l == ["456", "123"]
def test_confcutdir_check_isdir(self, testdir):
"""Give an error if --confcutdir is not a valid directory (#2078)"""
with pytest.raises(pytest.UsageError):
testdir.parseconfig('--confcutdir', testdir.tmpdir.join('file').ensure(file=1))
with pytest.raises(pytest.UsageError):
testdir.parseconfig('--confcutdir', testdir.tmpdir.join('inexistant'))
config = testdir.parseconfig('--confcutdir', testdir.tmpdir.join('dir').ensure(dir=1))
assert config.getoption('confcutdir') == str(testdir.tmpdir.join('dir'))
class TestConfigFromdictargs: class TestConfigFromdictargs:
def test_basic_behavior(self): def test_basic_behavior(self):

View File

@ -249,6 +249,18 @@ class TestPython:
snode = tnode.find_first_by_tag("skipped") snode = tnode.find_first_by_tag("skipped")
snode.assert_attr(type="pytest.skip", message="hello25", ) snode.assert_attr(type="pytest.skip", message="hello25", )
def test_mark_skip_doesnt_capture_output(self, testdir):
testdir.makepyfile("""
import pytest
@pytest.mark.skip(reason="foo")
def test_skip():
print("bar!")
""")
result, dom = runandparse(testdir)
assert result.ret == 0
node_xml = dom.find_first_by_tag("testsuite").toxml()
assert "bar!" not in node_xml
def test_classname_instance(self, testdir): def test_classname_instance(self, testdir):
testdir.makepyfile(""" testdir.makepyfile("""
class TestClass: class TestClass:

View File

@ -117,7 +117,8 @@ commands=
basepython = python basepython = python
usedevelop=True usedevelop=True
skipsdist=True skipsdist=True
deps=PyYAML deps=
PyYAML
commands= commands=
pytest -rfsxX doc/en pytest -rfsxX doc/en
pytest --doctest-modules {toxinidir}/_pytest pytest --doctest-modules {toxinidir}/_pytest