Merge pull request #2790 from nicoddemus/merge-master-into-features

Merge master into features
This commit is contained in:
Ronny Pfannschmidt 2017-09-20 10:25:56 +02:00 committed by GitHub
commit de0d19ca09
20 changed files with 172 additions and 35 deletions

View File

@ -20,11 +20,11 @@ env:
- TOXENV=py27-trial - TOXENV=py27-trial
- TOXENV=py27-numpy - TOXENV=py27-numpy
- TOXENV=py27-pluggymaster - TOXENV=py27-pluggymaster
- TOXENV=py35-pexpect - TOXENV=py36-pexpect
- TOXENV=py35-xdist - TOXENV=py36-xdist
- TOXENV=py35-trial - TOXENV=py36-trial
- TOXENV=py35-numpy - TOXENV=py36-numpy
- TOXENV=py35-pluggymaster - TOXENV=py36-pluggymaster
- TOXENV=py27-nobyte - TOXENV=py27-nobyte
- TOXENV=doctesting - TOXENV=doctesting
- TOXENV=docs - TOXENV=docs

View File

@ -1,5 +1,9 @@
How to release pytest Release Procedure
-------------------------------------------- -----------------
Our current policy for releasing is to aim for a bugfix every few weeks and a minor release every 2-3 months. The idea
is to get fixes and new features out instead of trying to cram a ton of features into a release and by consequence
taking a lot of time to make a new one.
.. important:: .. important::
@ -21,7 +25,7 @@ How to release pytest
#. Generate docs, changelog, announcements and upload a package to #. Generate docs, changelog, announcements and upload a package to
your ``devpi`` staging server:: your ``devpi`` staging server::
invoke generate.pre_release <VERSION> <DEVPI USER> --password <DEVPI PASSWORD> invoke generate.pre-release <VERSION> <DEVPI USER> --password <DEVPI PASSWORD>
If ``--password`` is not given, it is assumed the user is already logged in ``devpi``. If ``--password`` is not given, it is assumed the user is already logged in ``devpi``.
If you don't have an account, please ask for one. If you don't have an account, please ask for one.
@ -49,7 +53,7 @@ How to release pytest
#. Publish to PyPI:: #. Publish to PyPI::
invoke generate.publish_release <VERSION> <DEVPI USER> <PYPI_NAME> invoke generate.publish-release <VERSION> <DEVPI USER> <PYPI_NAME>
where PYPI_NAME is the name of pypi.python.org as configured in your ``~/.pypirc`` where PYPI_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>`_. file `for devpi <http://doc.devpi.net/latest/quickstart-releaseprocess.html?highlight=pypirc#devpi-push-releasing-to-an-external-index>`_.

View File

@ -78,7 +78,8 @@ class FastFilesCompleter:
completion = [] completion = []
globbed = [] globbed = []
if '*' not in prefix and '?' not in prefix: if '*' not in prefix and '?' not in prefix:
if prefix[-1] == os.path.sep: # we are on unix, otherwise no bash # we are on unix, otherwise no bash
if not prefix or prefix[-1] == os.path.sep:
globbed.extend(glob(prefix + '.*')) globbed.extend(glob(prefix + '.*'))
prefix += '*' prefix += '*'
globbed.extend(glob(prefix)) globbed.extend(glob(prefix))
@ -98,7 +99,7 @@ if os.environ.get('_ARGCOMPLETE'):
filescompleter = FastFilesCompleter() filescompleter = FastFilesCompleter()
def try_argcomplete(parser): def try_argcomplete(parser):
argcomplete.autocomplete(parser) argcomplete.autocomplete(parser, always_complete_options=False)
else: else:
def try_argcomplete(parser): def try_argcomplete(parser):
pass pass

View File

@ -326,7 +326,7 @@ class MarkDecorator:
return self.name # for backward-compat (2.4.1 had this attr) return self.name # for backward-compat (2.4.1 had this attr)
def __eq__(self, other): def __eq__(self, other):
return self.mark == other.mark return self.mark == other.mark if isinstance(other, MarkDecorator) else False
def __repr__(self): def __repr__(self):
return "<MarkDecorator %r>" % (self.mark,) return "<MarkDecorator %r>" % (self.mark,)

View File

@ -22,11 +22,11 @@ environment:
- TOXENV: "py27-trial" - TOXENV: "py27-trial"
- TOXENV: "py27-numpy" - TOXENV: "py27-numpy"
- TOXENV: "py27-pluggymaster" - TOXENV: "py27-pluggymaster"
- TOXENV: "py35-pexpect" - TOXENV: "py36-pexpect"
- TOXENV: "py35-xdist" - TOXENV: "py36-xdist"
- TOXENV: "py35-trial" - TOXENV: "py36-trial"
- TOXENV: "py35-numpy" - TOXENV: "py36-numpy"
- TOXENV: "py35-pluggymaster" - TOXENV: "py36-pluggymaster"
- TOXENV: "py27-nobyte" - TOXENV: "py27-nobyte"
- TOXENV: "doctesting" - TOXENV: "doctesting"
- TOXENV: "py35-freeze" - TOXENV: "py35-freeze"

1
changelog/1548.doc Normal file
View File

@ -0,0 +1 @@
Add note in ``parametrize.rst`` about calling ``metafunc.parametrize`` multiple times.

1
changelog/2722.trivial Normal file
View File

@ -0,0 +1 @@
Set ``xfail_strict=True`` in pytest's own test suite to catch expected failures as soon as they start to pass.

1
changelog/2748.bugfix Normal file
View File

@ -0,0 +1 @@
Fix crash in tab completion when no prefix is given.

1
changelog/2758.bugfix Normal file
View File

@ -0,0 +1 @@
The equality checking function (``__eq__``) of ``MarkDecorator`` returns ``False`` if one object is not an instance of ``MarkDecorator``.

1
changelog/2765.trivial Normal file
View File

@ -0,0 +1 @@
Fix typo in example of passing a callable to markers (in example/markers.rst)

View File

@ -41,6 +41,7 @@ Full pytest documentation
historical-notes historical-notes
license license
contributing contributing
development_guide
talks talks
projects projects
faq faq

View File

@ -0,0 +1,108 @@
=================
Development Guide
=================
Some general guidelines regarding development in pytest for core maintainers and general contributors. Nothing here
is set in stone and can't be changed, feel free to suggest improvements or changes in the workflow.
Code Style
----------
* `PEP-8 <https://www.python.org/dev/peps/pep-0008>`_
* `flake8 <https://pypi.python.org/pypi/flake8>`_ for quality checks
* `invoke <http://www.pyinvoke.org/>`_ to automate development tasks
Branches
--------
We have two long term branches:
* ``master``: contains the code for the next bugfix release.
* ``features``: contains the code with new features for the next minor release.
The official repository usually does not contain topic branches, developers and contributors should create topic
branches in their own forks.
Exceptions can be made for cases where more than one contributor is working on the same
topic or where it makes sense to use some automatic capability of the main repository, such as automatic docs from
`readthedocs <readthedocs.org>`_ for a branch dealing with documentation refactoring.
Issues
------
Any question, feature, bug or proposal is welcome as an issue. Users are encouraged to use them whenever they need.
GitHub issues should use labels to categorize them. Labels should be created sporadically, to fill a niche; we should
avoid creating labels just for the sake of creating them.
Here is a list of labels and a brief description mentioning their intent.
**Type**
* ``type: backward compatibility``: issue that will cause problems with old pytest versions.
* ``type: bug``: problem that needs to be addressed.
* ``type: deprecation``: feature that will be deprecated in the future.
* ``type: docs``: documentation missing or needing clarification.
* ``type: enhancement``: new feature or API change, should be merged into ``features``.
* ``type: feature-branch``: new feature or API change, should be merged into ``features``.
* ``type: infrastructure``: improvement to development/releases/CI structure.
* ``type: performance``: performance or memory problem/improvement.
* ``type: proposal``: proposal for a new feature, often to gather opinions or design the API around the new feature.
* ``type: question``: question regarding usage, installation, internals or how to test something.
* ``type: refactoring``: internal improvements to the code.
* ``type: regression``: indicates a problem that was introduced in a release which was working previously.
**Status**
* ``status: critical``: grave problem or usability issue that affects lots of users.
* ``status: easy``: easy issue that is friendly to new contributors.
* ``status: help wanted``: core developers need help from experts on this topic.
* ``status: needs information``: reporter needs to provide more information; can be closed after 2 or more weeks of inactivity.
**Topic**
* ``topic: collection``
* ``topic: fixtures``
* ``topic: parametrize``
* ``topic: reporting``
* ``topic: selection``
* ``topic: tracebacks``
**Plugin (internal or external)**
* ``plugin: cache``
* ``plugin: capture``
* ``plugin: doctests``
* ``plugin: junitxml``
* ``plugin: monkeypatch``
* ``plugin: nose``
* ``plugin: pastebin``
* ``plugin: pytester``
* ``plugin: tmpdir``
* ``plugin: unittest``
* ``plugin: warnings``
* ``plugin: xdist``
**OS**
Issues specific to a single operating system. Do not use as a means to indicate where an issue originated from, only
for problems that happen **only** in that system.
* ``os: linux``
* ``os: mac``
* ``os: windows``
**Temporary**
Used to classify issues for limited time, to help find issues related in events for example.
They should be removed after they are no longer relevant.
* ``temporary: EP2017 sprint``:
* ``temporary: sprint-candidate``:
.. include:: ../../HOWTORELEASE.rst

View File

@ -435,7 +435,7 @@ The output is as follows::
. .
1 passed in 0.12 seconds 1 passed in 0.12 seconds
We can see that the custom marker has its argument set extended with the function ``hello_world``. This is the key different between creating a custom marker as a callable, which invokes ``__call__`` behind the scenes, and using ``with_args``. We can see that the custom marker has its argument set extended with the function ``hello_world``. This is the key difference between creating a custom marker as a callable, which invokes ``__call__`` behind the scenes, and using ``with_args``.
Reading markers which were set from multiple places Reading markers which were set from multiple places

View File

@ -198,6 +198,10 @@ list::
SKIP [1] test_strings.py:2: got empty parameter set ['stringinput'], function test_valid_string at $REGENDOC_TMPDIR/test_strings.py:1 SKIP [1] test_strings.py:2: got empty parameter set ['stringinput'], function test_valid_string at $REGENDOC_TMPDIR/test_strings.py:1
1 skipped in 0.12 seconds 1 skipped in 0.12 seconds
Note that when calling ``metafunc.parametrize`` multiple times with different parameter sets, all parameter names across
those sets cannot be duplicated, otherwise an error will be raised.
For further examples, you might want to look at :ref:`more For further examples, you might want to look at :ref:`more
parametrization examples <paramexamples>`. parametrization examples <paramexamples>`.

View File

@ -54,7 +54,7 @@ by calling the ``pytest.skip(reason)`` function:
if not valid_config(): if not valid_config():
pytest.skip("unsupported configuration") pytest.skip("unsupported configuration")
The imperative method is useful when it is not possible to evaluate the skip condition The imperative method is useful when it is not possible to evaluate the skip condition
during import time. during import time.
``skipif`` ``skipif``
@ -73,7 +73,7 @@ when run on a Python3.3 interpreter::
... ...
If the condition evaluates to ``True`` during collection, the test function will be skipped, If the condition evaluates to ``True`` during collection, the test function will be skipped,
with the specified reason appearing in the summary when using ``-rs``. with the specified reason appearing in the summary when using ``-rs``.
You can share ``skipif`` markers between modules. Consider this test module:: You can share ``skipif`` markers between modules. Consider this test module::
@ -118,6 +118,12 @@ You can use the ``skipif`` marker (as any other marker) on classes::
If the condition is ``True``, this marker will produce a skip result for If the condition is ``True``, this marker will produce a skip result for
each of the test methods of that class. each of the test methods of that class.
.. warning::
The use of ``skipif`` on classes that use inheritance is strongly
discouraged. `A Known bug <https://github.com/pytest-dev/pytest/issues/568>`_
in pytest's markers may cause unexpected behavior in super classes.
If you want to skip all test functions of a module, you may use If you want to skip all test functions of a module, you may use
the ``pytestmark`` name on the global level: the ``pytestmark`` name on the global level:
@ -305,12 +311,12 @@ Running it with the report-on-xfail option gives this output::
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR/example, inifile: rootdir: $REGENDOC_TMPDIR/example, inifile:
collected 7 items collected 7 items
xfail_demo.py xxxxxxx xfail_demo.py xxxxxxx
======= short test summary info ======== ======= short test summary info ========
XFAIL xfail_demo.py::test_hello XFAIL xfail_demo.py::test_hello
XFAIL xfail_demo.py::test_hello2 XFAIL xfail_demo.py::test_hello2
reason: [NOTRUN] reason: [NOTRUN]
XFAIL xfail_demo.py::test_hello3 XFAIL xfail_demo.py::test_hello3
condition: hasattr(os, 'sep') condition: hasattr(os, 'sep')
XFAIL xfail_demo.py::test_hello4 XFAIL xfail_demo.py::test_hello4
@ -320,7 +326,7 @@ Running it with the report-on-xfail option gives this output::
XFAIL xfail_demo.py::test_hello6 XFAIL xfail_demo.py::test_hello6
reason: reason reason: reason
XFAIL xfail_demo.py::test_hello7 XFAIL xfail_demo.py::test_hello7
======= 7 xfailed in 0.12 seconds ======== ======= 7 xfailed in 0.12 seconds ========
.. _`skip/xfail with parametrize`: .. _`skip/xfail with parametrize`:
@ -346,5 +352,3 @@ test instances when using parametrize:
]) ])
def test_increment(n, expected): def test_increment(n, expected):
assert n + 1 == expected assert n + 1 == expected

View File

@ -391,7 +391,6 @@ def test_deindent():
assert lines == ['', 'def f():', ' def g():', ' pass', ' '] assert lines == ['', 'def f():', ' def g():', ' pass', ' ']
@pytest.mark.xfail("sys.version_info[:3] < (2,7,0)")
def test_source_of_class_at_eof_without_newline(tmpdir): def test_source_of_class_at_eof_without_newline(tmpdir):
# this test fails because the implicit inspect.getsource(A) below # this test fails because the implicit inspect.getsource(A) below
# does not return the "x = 1" last line. # does not return the "x = 1" last line.

View File

@ -841,7 +841,7 @@ class TestConftestCustomization(object):
def pytest_pycollect_makeitem(): def pytest_pycollect_makeitem():
outcome = yield outcome = yield
if outcome.excinfo is None: if outcome.excinfo is None:
result = outcome.result result = outcome.get_result()
if result: if result:
for func in result: for func in result:
func._some123 = "world" func._some123 = "world"

View File

@ -82,7 +82,7 @@ class TestArgComplete(object):
from _pytest._argcomplete import FastFilesCompleter from _pytest._argcomplete import FastFilesCompleter
ffc = FastFilesCompleter() ffc = FastFilesCompleter()
fc = FilesCompleter() fc = FilesCompleter()
for x in '/ /d /data qqq'.split(): for x in ['/', '/d', '/data', 'qqq', '']:
assert equal_with_bash(x, ffc, fc, out=py.std.sys.stdout) assert equal_with_bash(x, ffc, fc, out=py.std.sys.stdout)
@pytest.mark.skipif("sys.platform in ('win32', 'darwin')") @pytest.mark.skipif("sys.platform in ('win32', 'darwin')")

View File

@ -812,3 +812,15 @@ def test_legacy_transfer():
assert fake_method.fun assert fake_method.fun
# pristine marks dont transfer # pristine marks dont transfer
assert fake_method.pytestmark == [pytest.mark.fun.mark] assert fake_method.pytestmark == [pytest.mark.fun.mark]
class TestMarkDecorator(object):
@pytest.mark.parametrize('lhs, rhs, expected', [
(pytest.mark.foo(), pytest.mark.foo(), True),
(pytest.mark.foo(), pytest.mark.bar(), False),
(pytest.mark.foo(), 'bar', False),
('foo', pytest.mark.bar(), False)
])
def test__eq__(self, lhs, rhs, expected):
assert (lhs == rhs) == expected

13
tox.ini
View File

@ -12,7 +12,7 @@ envlist =
py36 py36
py37 py37
pypy pypy
{py27,py35}-{pexpect,xdist,trial,numpy,pluggymaster} {py27,py36}-{pexpect,xdist,trial,numpy,pluggymaster}
py27-nobyte py27-nobyte
doctesting doctesting
py35-freeze py35-freeze
@ -37,7 +37,6 @@ deps =
[testenv:py27-subprocess] [testenv:py27-subprocess]
changedir = . changedir = .
basepython = python2.7
deps = deps =
pytest-xdist>=1.13 pytest-xdist>=1.13
mock mock
@ -68,7 +67,7 @@ deps =
commands = commands =
pytest -n1 -ra {posargs:testing} pytest -n1 -ra {posargs:testing}
[testenv:py35-xdist] [testenv:py36-xdist]
deps = {[testenv:py27-xdist]deps} deps = {[testenv:py27-xdist]deps}
commands = commands =
pytest -n3 -ra {posargs:testing} pytest -n3 -ra {posargs:testing}
@ -80,7 +79,7 @@ deps = pexpect
commands = commands =
pytest -ra test_pdb.py test_terminal.py test_unittest.py pytest -ra test_pdb.py test_terminal.py test_unittest.py
[testenv:py35-pexpect] [testenv:py36-pexpect]
changedir = testing changedir = testing
platform = linux|darwin platform = linux|darwin
deps = {[testenv:py27-pexpect]deps} deps = {[testenv:py27-pexpect]deps}
@ -102,7 +101,7 @@ deps = twisted
commands = commands =
pytest -ra {posargs:testing/test_unittest.py} pytest -ra {posargs:testing/test_unittest.py}
[testenv:py35-trial] [testenv:py36-trial]
deps = {[testenv:py27-trial]deps} deps = {[testenv:py27-trial]deps}
commands = commands =
pytest -ra {posargs:testing/test_unittest.py} pytest -ra {posargs:testing/test_unittest.py}
@ -112,7 +111,7 @@ deps=numpy
commands= commands=
pytest -ra {posargs:testing/python/approx.py} pytest -ra {posargs:testing/python/approx.py}
[testenv:py35-numpy] [testenv:py36-numpy]
deps=numpy deps=numpy
commands= commands=
pytest -ra {posargs:testing/python/approx.py} pytest -ra {posargs:testing/python/approx.py}
@ -198,7 +197,6 @@ commands =
[testenv:coveralls] [testenv:coveralls]
passenv = TRAVIS TRAVIS_JOB_ID TRAVIS_BRANCH COVERALLS_REPO_TOKEN passenv = TRAVIS TRAVIS_JOB_ID TRAVIS_BRANCH COVERALLS_REPO_TOKEN
usedevelop = True usedevelop = True
basepython = python3.5
changedir = . changedir = .
deps = deps =
{[testenv]deps} {[testenv]deps}
@ -218,6 +216,7 @@ python_files = test_*.py *_test.py testing/*/*.py
python_classes = Test Acceptance python_classes = Test Acceptance
python_functions = test python_functions = test
norecursedirs = .tox ja .hg cx_freeze_source norecursedirs = .tox ja .hg cx_freeze_source
xfail_strict=true
filterwarnings = filterwarnings =
error error
# produced by path.local # produced by path.local