Compare commits
101 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e8368e6c2e | ||
|
|
fac8208e8f | ||
|
|
51abdb80db | ||
|
|
65534682aa | ||
|
|
e980fbbe39 | ||
|
|
07e768ab68 | ||
|
|
9a2e0c061d | ||
|
|
056d9e8dc2 | ||
|
|
46f5d7a1bb | ||
|
|
30453057e8 | ||
|
|
3b757b1b8c | ||
|
|
14a9b1ec83 | ||
|
|
9fcbf57163 | ||
|
|
1fb2457018 | ||
|
|
92219e576b | ||
|
|
143ac5af99 | ||
|
|
409b919fc0 | ||
|
|
eadd15fe45 | ||
|
|
3f7223af44 | ||
|
|
20085542e2 | ||
|
|
cfaf3600c1 | ||
|
|
188df8100c | ||
|
|
44fa5a77d4 | ||
|
|
31476c69ab | ||
|
|
6200920dc3 | ||
|
|
46c5d5355e | ||
|
|
39024a7536 | ||
|
|
ae62ced080 | ||
|
|
6166151ee4 | ||
|
|
bedceaacc4 | ||
|
|
1127d519db | ||
|
|
b5ac61657a | ||
|
|
48548767fc | ||
|
|
7536e949b1 | ||
|
|
287c003cfd | ||
|
|
aa53e37fa2 | ||
|
|
dd97c94035 | ||
|
|
264e455410 | ||
|
|
75f11f0b65 | ||
|
|
45d0a21294 | ||
|
|
147b43f832 | ||
|
|
6e14585ca2 | ||
|
|
dae74b674e | ||
|
|
2a99e5dd2a | ||
|
|
9361d48b61 | ||
|
|
ebddac6a5c | ||
|
|
b319375592 | ||
|
|
f8fdf0ae91 | ||
|
|
053fc118b7 | ||
|
|
b486e1294b | ||
|
|
9d879bee36 | ||
|
|
4131d3f300 | ||
|
|
254e357076 | ||
|
|
0f6879bf5e | ||
|
|
bfe2cbe875 | ||
|
|
00d3001138 | ||
|
|
6bc45d158d | ||
|
|
371eb8c6af | ||
|
|
435b8ddc7c | ||
|
|
3cbf0c8ec0 | ||
|
|
9849022eb2 | ||
|
|
18c84a1904 | ||
|
|
247cdb835a | ||
|
|
ce1872e7e8 | ||
|
|
d845af7b24 | ||
|
|
391553887b | ||
|
|
7656fc8320 | ||
|
|
b6166dccb4 | ||
|
|
e7bcc854d9 | ||
|
|
ffee213c85 | ||
|
|
e5b527d0e3 | ||
|
|
c04e248de5 | ||
|
|
3685c1bc01 | ||
|
|
eea169e515 | ||
|
|
16c52f05f1 | ||
|
|
f72182977d | ||
|
|
40d0ade2d9 | ||
|
|
7152707280 | ||
|
|
42c1f85257 | ||
|
|
0d15a46863 | ||
|
|
570edb466b | ||
|
|
7c80c81433 | ||
|
|
9202ba91cf | ||
|
|
ea8997a108 | ||
|
|
867344d0d7 | ||
|
|
e64feaba7a | ||
|
|
74633815aa | ||
|
|
e289c60c3a | ||
|
|
8c81722a0c | ||
|
|
3425edd2a5 | ||
|
|
4458e65fe7 | ||
|
|
653abad27b | ||
|
|
89a55d85a9 | ||
|
|
ef7df8f167 | ||
|
|
90a8faabba | ||
|
|
e12a588c39 | ||
|
|
547070e2d8 | ||
|
|
f2fb841b29 | ||
|
|
3256fa9ee9 | ||
|
|
dc9a9ec4c2 | ||
|
|
2a1b1107c5 |
19
.github/PULL_REQUEST_TEMPLATE.md
vendored
19
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,15 +1,14 @@
|
||||
Thanks for submitting a PR, your contribution is really appreciated!
|
||||
|
||||
Here's a quick checklist that should be present in PRs:
|
||||
Here's a quick checklist that should be present in PRs (you can delete this text from the final description, this is
|
||||
just a guideline):
|
||||
|
||||
- [ ] Add a new news fragment into the changelog folder
|
||||
* name it `$issue_id.$type` for example (588.bugfix)
|
||||
* if you don't have an issue_id change it to the pr id after creating the pr
|
||||
* ensure type is one of `removal`, `feature`, `bugfix`, `vendor`, `doc` or `trivial`
|
||||
* Make sure to use full sentences with correct case and punctuation, for example: "Fix issue with non-ascii contents in doctest text files."
|
||||
- [ ] Target: for `bugfix`, `vendor`, `doc` or `trivial` fixes, target `master`; for removals or features target `features`;
|
||||
- [ ] Make sure to include reasonable tests for your change if necessary
|
||||
- [ ] Create a new changelog file in the `changelog` folder, with a name like `<ISSUE NUMBER>.<TYPE>.rst`. See [changelog/README.rst](/changelog/README.rst) for details.
|
||||
- [ ] Target the `master` branch for bug fixes, documentation updates and trivial changes.
|
||||
- [ ] Target the `features` branch for new features and removals/deprecations.
|
||||
- [ ] Include documentation when adding new features.
|
||||
- [ ] Include new tests or update existing tests when applicable.
|
||||
|
||||
Unless your change is a trivial or a documentation fix (e.g., a typo or reword of a small section) please:
|
||||
Unless your change is trivial or a small documentation fix (e.g., a typo or reword of a small section) please:
|
||||
|
||||
- [ ] Add yourself to `AUTHORS`, in alphabetical order;
|
||||
- [ ] Add yourself to `AUTHORS` in alphabetical order;
|
||||
|
||||
23
.travis.yml
23
.travis.yml
@@ -2,10 +2,8 @@ sudo: false
|
||||
language: python
|
||||
python:
|
||||
- '3.6'
|
||||
# command to install dependencies
|
||||
install:
|
||||
- pip install --upgrade --pre tox
|
||||
# # command to run tests
|
||||
env:
|
||||
matrix:
|
||||
# coveralls is not listed in tox's envlist, but should run in travis
|
||||
@@ -29,7 +27,7 @@ env:
|
||||
- TOXENV=doctesting
|
||||
- TOXENV=docs
|
||||
|
||||
matrix:
|
||||
jobs:
|
||||
include:
|
||||
- env: TOXENV=pypy
|
||||
python: 'pypy-5.4'
|
||||
@@ -39,9 +37,22 @@ matrix:
|
||||
python: '3.5'
|
||||
- env: TOXENV=py37
|
||||
python: 'nightly'
|
||||
allow_failures:
|
||||
- env: TOXENV=py37
|
||||
python: 'nightly'
|
||||
|
||||
- stage: deploy
|
||||
python: '3.6'
|
||||
env:
|
||||
install: pip install -U setuptools setuptools_scm
|
||||
script: skip
|
||||
deploy:
|
||||
provider: pypi
|
||||
user: nicoddemus
|
||||
distributions: sdist bdist_wheel
|
||||
skip_upload_docs: true
|
||||
password:
|
||||
secure: xanTgTUu6XDQVqB/0bwJQXoDMnU5tkwZc5koz6mBkkqZhKdNOi2CLoC1XhiSZ+ah24l4V1E0GAqY5kBBcy9d7NVe4WNg4tD095LsHw+CRU6/HCVIFfyk2IZ+FPAlguesCcUiJSXOrlBF+Wj68wEvLoK7EoRFbJeiZ/f91Ww1sbtDlqXABWGHrmhPJL5Wva7o7+wG7JwJowqdZg1pbQExsCc7b53w4v2RBu3D6TJaTAzHiVsW+nUSI67vKI/uf+cR/OixsTfy37wlHgSwihYmrYLFls3V0bSpahCim3bCgMaFZx8S8xrdgJ++PzBCof2HeflFKvW+VCkoYzGEG4NrTWJoNz6ni4red9GdvfjGH3YCjAKS56h9x58zp2E5rpsb/kVq5/45xzV+dq6JRuhQ1nJWjBC6fSKAc/bfwnuFK3EBxNLkvBssLHvsNjj5XG++cB8DdS9wVGUqjpoK4puaXUWFqy4q3S9F86HEsKNgExtieA9qNx+pCIZVs6JCXZNjr0I5eVNzqJIyggNgJG6RyravsU35t9Zd9doL5g4Y7UKmAGTn1Sz24HQ4sMQgXdm2SyD8gEK5je4tlhUvfGtDvMSlstq71kIn9nRpFnqB6MFlbYSEAZmo8dGbCquoUc++6Rum208wcVbrzzVtGlXB/Ow9AbFMYeAGA0+N/K1e59c=
|
||||
on:
|
||||
tags: true
|
||||
repo: pytest-dev/pytest
|
||||
|
||||
script: tox --recreate
|
||||
|
||||
|
||||
2
AUTHORS
2
AUTHORS
@@ -29,6 +29,7 @@ Benjamin Peterson
|
||||
Bernard Pratz
|
||||
Bob Ippolito
|
||||
Brian Dorsey
|
||||
Brian Maissy
|
||||
Brian Okken
|
||||
Brianna Laugher
|
||||
Bruno Oliveira
|
||||
@@ -196,3 +197,4 @@ Xuan Luong
|
||||
Xuecong Liao
|
||||
Zoltán Máté
|
||||
Roland Puntaier
|
||||
Allan Feldman
|
||||
|
||||
@@ -8,6 +8,104 @@
|
||||
|
||||
.. towncrier release notes start
|
||||
|
||||
Pytest 3.4.2 (2018-03-04)
|
||||
=========================
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- Removed progress information when capture option is ``no``. (`#3203
|
||||
<https://github.com/pytest-dev/pytest/issues/3203>`_)
|
||||
|
||||
- Refactor check of bindir from ``exists`` to ``isdir``. (`#3241
|
||||
<https://github.com/pytest-dev/pytest/issues/3241>`_)
|
||||
|
||||
- Fix ``TypeError`` issue when using ``approx`` with a ``Decimal`` value.
|
||||
(`#3247 <https://github.com/pytest-dev/pytest/issues/3247>`_)
|
||||
|
||||
- Fix reference cycle generated when using the ``request`` fixture. (`#3249
|
||||
<https://github.com/pytest-dev/pytest/issues/3249>`_)
|
||||
|
||||
- ``[tool:pytest]`` sections in ``*.cfg`` files passed by the ``-c`` option are
|
||||
now properly recognized. (`#3260
|
||||
<https://github.com/pytest-dev/pytest/issues/3260>`_)
|
||||
|
||||
|
||||
Improved Documentation
|
||||
----------------------
|
||||
|
||||
- Add logging plugin to plugins list. (`#3209
|
||||
<https://github.com/pytest-dev/pytest/issues/3209>`_)
|
||||
|
||||
|
||||
Trivial/Internal Changes
|
||||
------------------------
|
||||
|
||||
- Fix minor typo in fixture.rst (`#3259
|
||||
<https://github.com/pytest-dev/pytest/issues/3259>`_)
|
||||
|
||||
|
||||
Pytest 3.4.1 (2018-02-20)
|
||||
=========================
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- Move import of ``doctest.UnexpectedException`` to top-level to avoid possible
|
||||
errors when using ``--pdb``. (`#1810
|
||||
<https://github.com/pytest-dev/pytest/issues/1810>`_)
|
||||
|
||||
- Added printing of captured stdout/stderr before entering pdb, and improved a
|
||||
test which was giving false negatives about output capturing. (`#3052
|
||||
<https://github.com/pytest-dev/pytest/issues/3052>`_)
|
||||
|
||||
- Fix ordering of tests using parametrized fixtures which can lead to fixtures
|
||||
being created more than necessary. (`#3161
|
||||
<https://github.com/pytest-dev/pytest/issues/3161>`_)
|
||||
|
||||
- Fix bug where logging happening at hooks outside of "test run" hooks would
|
||||
cause an internal error. (`#3184
|
||||
<https://github.com/pytest-dev/pytest/issues/3184>`_)
|
||||
|
||||
- Detect arguments injected by ``unittest.mock.patch`` decorator correctly when
|
||||
pypi ``mock.patch`` is installed and imported. (`#3206
|
||||
<https://github.com/pytest-dev/pytest/issues/3206>`_)
|
||||
|
||||
- Errors shown when a ``pytest.raises()`` with ``match=`` fails are now cleaner
|
||||
on what happened: When no exception was raised, the "matching '...'" part got
|
||||
removed as it falsely implies that an exception was raised but it didn't
|
||||
match. When a wrong exception was raised, it's now thrown (like
|
||||
``pytest.raised()`` without ``match=`` would) instead of complaining about
|
||||
the unmatched text. (`#3222
|
||||
<https://github.com/pytest-dev/pytest/issues/3222>`_)
|
||||
|
||||
- Fixed output capture handling in doctests on macOS. (`#985
|
||||
<https://github.com/pytest-dev/pytest/issues/985>`_)
|
||||
|
||||
|
||||
Improved Documentation
|
||||
----------------------
|
||||
|
||||
- Add Sphinx parameter docs for ``match`` and ``message`` args to
|
||||
``pytest.raises``. (`#3202
|
||||
<https://github.com/pytest-dev/pytest/issues/3202>`_)
|
||||
|
||||
|
||||
Trivial/Internal Changes
|
||||
------------------------
|
||||
|
||||
- pytest has changed the publication procedure and is now being published to
|
||||
PyPI directly from Travis. (`#3060
|
||||
<https://github.com/pytest-dev/pytest/issues/3060>`_)
|
||||
|
||||
- Rename ``ParameterSet._for_parameterize()`` to ``_for_parametrize()`` in
|
||||
order to comply with the naming convention. (`#3166
|
||||
<https://github.com/pytest-dev/pytest/issues/3166>`_)
|
||||
|
||||
- Skip failing pdb/doctest test on mac. (`#985
|
||||
<https://github.com/pytest-dev/pytest/issues/985>`_)
|
||||
|
||||
|
||||
Pytest 3.4.0 (2018-01-30)
|
||||
=========================
|
||||
|
||||
|
||||
@@ -22,44 +22,28 @@ taking a lot of time to make a new one.
|
||||
|
||||
Ensure your are in a clean work tree.
|
||||
|
||||
#. Generate docs, changelog, announcements and upload a package to
|
||||
your ``devpi`` staging server::
|
||||
#. Generate docs, changelog, announcements and a **local** tag::
|
||||
|
||||
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 you don't have an account, please ask for one.
|
||||
invoke generate.pre-release <VERSION>
|
||||
|
||||
#. Open a PR for this branch targeting ``master``.
|
||||
|
||||
#. Test the package
|
||||
#. After all tests pass and the PR has been approved, publish to PyPI by pushing the tag::
|
||||
|
||||
* **Manual method**
|
||||
git push git@github.com:pytest-dev/pytest.git <VERSION>
|
||||
|
||||
Run from multiple machines::
|
||||
Wait for the deploy to complete, then make sure it is `available on PyPI <https://pypi.org/project/pytest>`_.
|
||||
|
||||
devpi use https://devpi.net/USER/dev
|
||||
devpi test pytest==VERSION
|
||||
#. Send an email announcement with the contents from::
|
||||
|
||||
Check that tests pass for relevant combinations with::
|
||||
doc/en/announce/release-<VERSION>.rst
|
||||
|
||||
devpi list pytest
|
||||
To the following mailing lists:
|
||||
|
||||
* **CI servers**
|
||||
* pytest-dev@python.org (all releases)
|
||||
* python-announce-list@python.org (all releases)
|
||||
* testing-in-python@lists.idyll.org (only major/minor releases)
|
||||
|
||||
Configure a repository as per-instructions on
|
||||
devpi-cloud-test_ to test the package on Travis_ and AppVeyor_.
|
||||
All test environments should pass.
|
||||
|
||||
#. Publish to PyPI::
|
||||
|
||||
invoke generate.publish-release <VERSION> <DEVPI USER> <PYPI_NAME>
|
||||
|
||||
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>`_.
|
||||
And announce it on `Twitter <https://twitter.com/>`_ with the ``#pytest`` hashtag.
|
||||
|
||||
#. After a minor/major release, merge ``release-X.Y.Z`` into ``master`` and push (or open a PR).
|
||||
|
||||
.. _devpi-cloud-test: https://github.com/obestwalter/devpi-cloud-test
|
||||
.. _AppVeyor: https://www.appveyor.com/
|
||||
.. _Travis: https://travis-ci.org
|
||||
|
||||
@@ -23,6 +23,9 @@
|
||||
.. image:: https://ci.appveyor.com/api/projects/status/mrgbjaua7t33pg6b?svg=true
|
||||
:target: https://ci.appveyor.com/project/pytestbot/pytest
|
||||
|
||||
.. image:: https://www.codetriage.com/pytest-dev/pytest/badges/users.svg
|
||||
:target: https://www.codetriage.com/pytest-dev/pytest
|
||||
|
||||
The ``pytest`` framework makes it easy to write small tests, yet
|
||||
scales to support complex functional testing for applications and libraries.
|
||||
|
||||
|
||||
@@ -79,10 +79,11 @@ def num_mock_patch_args(function):
|
||||
patchings = getattr(function, "patchings", None)
|
||||
if not patchings:
|
||||
return 0
|
||||
mock = sys.modules.get("mock", sys.modules.get("unittest.mock", None))
|
||||
if mock is not None:
|
||||
mock_modules = [sys.modules.get("mock"), sys.modules.get("unittest.mock")]
|
||||
if any(mock_modules):
|
||||
sentinels = [m.DEFAULT for m in mock_modules if m is not None]
|
||||
return len([p for p in patchings
|
||||
if not p.attribute_name and p.new is mock.DEFAULT])
|
||||
if not p.attribute_name and p.new in sentinels])
|
||||
return len(patchings)
|
||||
|
||||
|
||||
|
||||
@@ -1327,10 +1327,14 @@ def determine_setup(inifile, args, warnfunc=None):
|
||||
dirs = get_dirs_from_args(args)
|
||||
if inifile:
|
||||
iniconfig = py.iniconfig.IniConfig(inifile)
|
||||
try:
|
||||
inicfg = iniconfig["pytest"]
|
||||
except KeyError:
|
||||
inicfg = None
|
||||
is_cfg_file = str(inifile).endswith('.cfg')
|
||||
sections = ['tool:pytest', 'pytest'] if is_cfg_file else ['pytest']
|
||||
for section in sections:
|
||||
try:
|
||||
inicfg = iniconfig[section]
|
||||
break
|
||||
except KeyError:
|
||||
inicfg = None
|
||||
rootdir = get_common_ancestor(dirs)
|
||||
else:
|
||||
ancestor = get_common_ancestor(dirs)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
import pdb
|
||||
import sys
|
||||
from doctest import UnexpectedException
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
@@ -85,6 +86,17 @@ def _enter_pdb(node, excinfo, rep):
|
||||
# for not completely clear reasons.
|
||||
tw = node.config.pluginmanager.getplugin("terminalreporter")._tw
|
||||
tw.line()
|
||||
|
||||
captured_stdout = rep.capstdout
|
||||
if len(captured_stdout) > 0:
|
||||
tw.sep(">", "captured stdout")
|
||||
tw.line(captured_stdout)
|
||||
|
||||
captured_stderr = rep.capstderr
|
||||
if len(captured_stderr) > 0:
|
||||
tw.sep(">", "captured stderr")
|
||||
tw.line(captured_stderr)
|
||||
|
||||
tw.sep(">", "traceback")
|
||||
rep.toterminal(tw)
|
||||
tw.sep(">", "entering PDB")
|
||||
@@ -95,10 +107,9 @@ def _enter_pdb(node, excinfo, rep):
|
||||
|
||||
|
||||
def _postmortem_traceback(excinfo):
|
||||
# A doctest.UnexpectedException is not useful for post_mortem.
|
||||
# Use the underlying exception instead:
|
||||
from doctest import UnexpectedException
|
||||
if isinstance(excinfo.value, UnexpectedException):
|
||||
# A doctest.UnexpectedException is not useful for post_mortem.
|
||||
# Use the underlying exception instead:
|
||||
return excinfo.value.exc_info[2]
|
||||
else:
|
||||
return excinfo._excinfo[2]
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
import traceback
|
||||
import sys
|
||||
import platform
|
||||
|
||||
import pytest
|
||||
from _pytest._code.code import ExceptionInfo, ReprFileLocation, TerminalRepr
|
||||
@@ -103,8 +105,21 @@ class DoctestItem(pytest.Item):
|
||||
|
||||
def runtest(self):
|
||||
_check_all_skipped(self.dtest)
|
||||
self._disable_output_capturing_for_darwin()
|
||||
self.runner.run(self.dtest)
|
||||
|
||||
def _disable_output_capturing_for_darwin(self):
|
||||
"""
|
||||
Disable output capturing. Otherwise, stdout is lost to doctest (#985)
|
||||
"""
|
||||
if platform.system() != 'Darwin':
|
||||
return
|
||||
capman = self.config.pluginmanager.getplugin("capturemanager")
|
||||
if capman:
|
||||
out, err = capman.suspend_global_capture(in_=True)
|
||||
sys.stdout.write(out)
|
||||
sys.stderr.write(err)
|
||||
|
||||
def repr_failure(self, excinfo):
|
||||
import doctest
|
||||
if excinfo.errisinstance((doctest.DocTestFailure,
|
||||
|
||||
@@ -24,6 +24,12 @@ from _pytest.compat import (
|
||||
from _pytest.outcomes import fail, TEST_OUTCOME
|
||||
|
||||
|
||||
@attr.s(frozen=True)
|
||||
class PseudoFixtureDef(object):
|
||||
cached_result = attr.ib()
|
||||
scope = attr.ib()
|
||||
|
||||
|
||||
def pytest_sessionstart(session):
|
||||
import _pytest.python
|
||||
import _pytest.nodes
|
||||
@@ -166,7 +172,7 @@ def reorder_items(items):
|
||||
items_by_argkey = {}
|
||||
for scopenum in range(0, scopenum_function):
|
||||
argkeys_cache[scopenum] = d = {}
|
||||
items_by_argkey[scopenum] = item_d = defaultdict(list)
|
||||
items_by_argkey[scopenum] = item_d = defaultdict(deque)
|
||||
for item in items:
|
||||
keys = OrderedDict.fromkeys(get_parametrized_fixture_keys(item, scopenum))
|
||||
if keys:
|
||||
@@ -174,12 +180,19 @@ def reorder_items(items):
|
||||
for key in keys:
|
||||
item_d[key].append(item)
|
||||
items = OrderedDict.fromkeys(items)
|
||||
return list(reorder_items_atscope(items, set(), argkeys_cache, items_by_argkey, 0))
|
||||
return list(reorder_items_atscope(items, argkeys_cache, items_by_argkey, 0))
|
||||
|
||||
|
||||
def reorder_items_atscope(items, ignore, argkeys_cache, items_by_argkey, scopenum):
|
||||
def fix_cache_order(item, argkeys_cache, items_by_argkey):
|
||||
for scopenum in range(0, scopenum_function):
|
||||
for key in argkeys_cache[scopenum].get(item, []):
|
||||
items_by_argkey[scopenum][key].appendleft(item)
|
||||
|
||||
|
||||
def reorder_items_atscope(items, argkeys_cache, items_by_argkey, scopenum):
|
||||
if scopenum >= scopenum_function or len(items) < 3:
|
||||
return items
|
||||
ignore = set()
|
||||
items_deque = deque(items)
|
||||
items_done = OrderedDict()
|
||||
scoped_items_by_argkey = items_by_argkey[scopenum]
|
||||
@@ -197,13 +210,14 @@ def reorder_items_atscope(items, ignore, argkeys_cache, items_by_argkey, scopenu
|
||||
else:
|
||||
slicing_argkey, _ = argkeys.popitem()
|
||||
# we don't have to remove relevant items from later in the deque because they'll just be ignored
|
||||
for i in reversed(scoped_items_by_argkey[slicing_argkey]):
|
||||
if i in items:
|
||||
items_deque.appendleft(i)
|
||||
matching_items = [i for i in scoped_items_by_argkey[slicing_argkey] if i in items]
|
||||
for i in reversed(matching_items):
|
||||
fix_cache_order(i, argkeys_cache, items_by_argkey)
|
||||
items_deque.appendleft(i)
|
||||
break
|
||||
if no_argkey_group:
|
||||
no_argkey_group = reorder_items_atscope(
|
||||
no_argkey_group, set(), argkeys_cache, items_by_argkey, scopenum + 1)
|
||||
no_argkey_group, argkeys_cache, items_by_argkey, scopenum + 1)
|
||||
for item in no_argkey_group:
|
||||
items_done[item] = None
|
||||
ignore.add(slicing_argkey)
|
||||
@@ -432,10 +446,9 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
||||
fixturedef = self._getnextfixturedef(argname)
|
||||
except FixtureLookupError:
|
||||
if argname == "request":
|
||||
class PseudoFixtureDef(object):
|
||||
cached_result = (self, [0], None)
|
||||
scope = "function"
|
||||
return PseudoFixtureDef
|
||||
cached_result = (self, [0], None)
|
||||
scope = "function"
|
||||
return PseudoFixtureDef(cached_result, scope)
|
||||
raise
|
||||
# remove indent to prevent the python3 exception
|
||||
# from leaking into the call
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
""" Access and control log capturing. """
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
import logging
|
||||
@@ -477,7 +478,7 @@ class _LiveLoggingStreamHandler(logging.StreamHandler):
|
||||
if not self._first_record_emitted or self._when == 'teardown':
|
||||
self.stream.write('\n')
|
||||
self._first_record_emitted = True
|
||||
if not self._section_name_shown:
|
||||
if not self._section_name_shown and self._when:
|
||||
self.stream.section('live log ' + self._when, sep='-', bold=True)
|
||||
self._section_name_shown = True
|
||||
logging.StreamHandler.emit(self, record)
|
||||
|
||||
@@ -170,7 +170,7 @@ def _in_venv(path):
|
||||
"""Attempts to detect if ``path`` is the root of a Virtual Environment by
|
||||
checking for the existence of the appropriate activate script"""
|
||||
bindir = path.join('Scripts' if sys.platform.startswith('win') else 'bin')
|
||||
if not bindir.exists():
|
||||
if not bindir.isdir():
|
||||
return False
|
||||
activates = ('activate', 'activate.csh', 'activate.fish',
|
||||
'Activate', 'Activate.bat', 'Activate.ps1')
|
||||
|
||||
@@ -75,7 +75,7 @@ class ParameterSet(namedtuple('ParameterSet', 'values, marks, id')):
|
||||
return cls(argval, marks=newmarks, id=None)
|
||||
|
||||
@classmethod
|
||||
def _for_parameterize(cls, argnames, argvalues, function, config):
|
||||
def _for_parametrize(cls, argnames, argvalues, function, config):
|
||||
if not isinstance(argnames, (tuple, list)):
|
||||
argnames = [x.strip() for x in argnames.split(",") if x.strip()]
|
||||
force_tuple = len(argnames) == 1
|
||||
|
||||
@@ -785,7 +785,8 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
|
||||
from _pytest.fixtures import scope2index
|
||||
from _pytest.mark import ParameterSet
|
||||
from py.io import saferepr
|
||||
argnames, parameters = ParameterSet._for_parameterize(
|
||||
|
||||
argnames, parameters = ParameterSet._for_parametrize(
|
||||
argnames, argvalues, self.function, self.config)
|
||||
del argvalues
|
||||
|
||||
|
||||
@@ -153,6 +153,8 @@ class ApproxScalar(ApproxBase):
|
||||
"""
|
||||
Perform approximate comparisons for single numbers only.
|
||||
"""
|
||||
DEFAULT_ABSOLUTE_TOLERANCE = 1e-12
|
||||
DEFAULT_RELATIVE_TOLERANCE = 1e-6
|
||||
|
||||
def __repr__(self):
|
||||
"""
|
||||
@@ -223,7 +225,7 @@ class ApproxScalar(ApproxBase):
|
||||
|
||||
# Figure out what the absolute tolerance should be. ``self.abs`` is
|
||||
# either None or a value specified by the user.
|
||||
absolute_tolerance = set_default(self.abs, 1e-12)
|
||||
absolute_tolerance = set_default(self.abs, self.DEFAULT_ABSOLUTE_TOLERANCE)
|
||||
|
||||
if absolute_tolerance < 0:
|
||||
raise ValueError("absolute tolerance can't be negative: {}".format(absolute_tolerance))
|
||||
@@ -241,7 +243,7 @@ class ApproxScalar(ApproxBase):
|
||||
# we've made sure the user didn't ask for an absolute tolerance only,
|
||||
# because we don't want to raise errors about the relative tolerance if
|
||||
# we aren't even going to use it.
|
||||
relative_tolerance = set_default(self.rel, 1e-6) * abs(self.expected)
|
||||
relative_tolerance = set_default(self.rel, self.DEFAULT_RELATIVE_TOLERANCE) * abs(self.expected)
|
||||
|
||||
if relative_tolerance < 0:
|
||||
raise ValueError("relative tolerance can't be negative: {}".format(absolute_tolerance))
|
||||
@@ -252,6 +254,13 @@ class ApproxScalar(ApproxBase):
|
||||
return max(relative_tolerance, absolute_tolerance)
|
||||
|
||||
|
||||
class ApproxDecimal(ApproxScalar):
|
||||
from decimal import Decimal
|
||||
|
||||
DEFAULT_ABSOLUTE_TOLERANCE = Decimal('1e-12')
|
||||
DEFAULT_RELATIVE_TOLERANCE = Decimal('1e-6')
|
||||
|
||||
|
||||
def approx(expected, rel=None, abs=None, nan_ok=False):
|
||||
"""
|
||||
Assert that two numbers (or two sets of numbers) are equal to each other
|
||||
@@ -401,6 +410,7 @@ def approx(expected, rel=None, abs=None, nan_ok=False):
|
||||
|
||||
from collections import Mapping, Sequence
|
||||
from _pytest.compat import STRING_TYPES as String
|
||||
from decimal import Decimal
|
||||
|
||||
# Delegate the comparison to a class that knows how to deal with the type
|
||||
# of the expected value (e.g. int, float, list, dict, numpy.array, etc).
|
||||
@@ -422,6 +432,8 @@ def approx(expected, rel=None, abs=None, nan_ok=False):
|
||||
cls = ApproxMapping
|
||||
elif isinstance(expected, Sequence) and not isinstance(expected, String):
|
||||
cls = ApproxSequence
|
||||
elif isinstance(expected, Decimal):
|
||||
cls = ApproxDecimal
|
||||
else:
|
||||
cls = ApproxScalar
|
||||
|
||||
@@ -453,6 +465,10 @@ def raises(expected_exception, *args, **kwargs):
|
||||
Assert that a code block/function call raises ``expected_exception``
|
||||
and raise a failure exception otherwise.
|
||||
|
||||
:arg message: if specified, provides a custom failure message if the
|
||||
exception is not raised
|
||||
:arg match: if specified, asserts that the exception matches a text or regex
|
||||
|
||||
This helper produces a ``ExceptionInfo()`` object (see below).
|
||||
|
||||
You may use this function as a context manager::
|
||||
@@ -567,7 +583,6 @@ def raises(expected_exception, *args, **kwargs):
|
||||
message = kwargs.pop("message")
|
||||
if "match" in kwargs:
|
||||
match_expr = kwargs.pop("match")
|
||||
message += " matching '{0}'".format(match_expr)
|
||||
return RaisesContext(expected_exception, message, match_expr)
|
||||
elif isinstance(args[0], str):
|
||||
code, = args
|
||||
@@ -614,6 +629,6 @@ class RaisesContext(object):
|
||||
suppress_exception = issubclass(self.excinfo.type, self.expected_exception)
|
||||
if sys.version_info[0] == 2 and suppress_exception:
|
||||
sys.exc_clear()
|
||||
if self.match_expr:
|
||||
if self.match_expr and suppress_exception:
|
||||
self.excinfo.match(self.match_expr)
|
||||
return suppress_exception
|
||||
|
||||
@@ -324,6 +324,8 @@ class TerminalReporter(object):
|
||||
_PROGRESS_LENGTH = len(' [100%]')
|
||||
|
||||
def _get_progress_information_message(self):
|
||||
if self.config.getoption('capture') == 'no':
|
||||
return ''
|
||||
collected = self._session.testscollected
|
||||
if collected:
|
||||
progress = len(self._progress_nodeids_reported) * 100 // collected
|
||||
|
||||
32
changelog/README.rst
Normal file
32
changelog/README.rst
Normal file
@@ -0,0 +1,32 @@
|
||||
This directory contains "newsfragments" which are short files that contain a small **ReST**-formatted
|
||||
text that will be added to the next ``CHANGELOG``.
|
||||
|
||||
The ``CHANGELOG`` will be read by users, so this description should be aimed to pytest users
|
||||
instead of describing internal changes which are only relevant to the developers.
|
||||
|
||||
Make sure to use full sentences with correct case and punctuation, for example::
|
||||
|
||||
Fix issue with non-ascii messages from the ``warnings`` module.
|
||||
|
||||
Each file should be named like ``<ISSUE>.<TYPE>.rst``, where
|
||||
``<ISSUE>`` is an issue number, and ``<TYPE>`` is one of:
|
||||
|
||||
* ``feature``: new user facing features, like new command-line options and new behavior.
|
||||
* ``bugfix``: fixes a reported bug.
|
||||
* ``doc``: documentation improvement, like rewording an entire session or adding missing docs.
|
||||
* ``removal``: feature deprecation or removal.
|
||||
* ``vendor``: changes in packages vendored in pytest.
|
||||
* ``trivial``: fixing a small typo or internal change that might be noteworthy.
|
||||
|
||||
So for example: ``123.feature.rst``, ``456.bugfix.rst``.
|
||||
|
||||
If your PR fixes an issue, use that number here. If there is no issue,
|
||||
then after you submit the PR and get the PR number you can add a
|
||||
changelog using that instead.
|
||||
|
||||
If you are not sure what issue type to use, don't hesitate to ask in your PR.
|
||||
|
||||
Note that the ``towncrier`` tool will automatically
|
||||
reflow your text, so it will work best if you stick to a single paragraph, but multiple sentences and links are OK
|
||||
and encouraged. You can install ``towncrier`` and then run ``towncrier --draft``
|
||||
if you want to get a preview of how your change will look in the final release notes.
|
||||
@@ -6,6 +6,8 @@ Release announcements
|
||||
:maxdepth: 2
|
||||
|
||||
|
||||
release-3.4.2
|
||||
release-3.4.1
|
||||
release-3.4.0
|
||||
release-3.3.2
|
||||
release-3.3.1
|
||||
|
||||
27
doc/en/announce/release-3.4.1.rst
Normal file
27
doc/en/announce/release-3.4.1.rst
Normal file
@@ -0,0 +1,27 @@
|
||||
pytest-3.4.1
|
||||
=======================================
|
||||
|
||||
pytest 3.4.1 has just been released to PyPI.
|
||||
|
||||
This is a bug-fix release, being a drop-in replacement. To upgrade::
|
||||
|
||||
pip install --upgrade pytest
|
||||
|
||||
The full changelog is available at http://doc.pytest.org/en/latest/changelog.html.
|
||||
|
||||
Thanks to all who contributed to this release, among them:
|
||||
|
||||
* Aaron
|
||||
* Alan Velasco
|
||||
* Andy Freeland
|
||||
* Brian Maissy
|
||||
* Bruno Oliveira
|
||||
* Florian Bruhin
|
||||
* Jason R. Coombs
|
||||
* Marcin Bachry
|
||||
* Pedro Algarvio
|
||||
* Ronny Pfannschmidt
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
||||
28
doc/en/announce/release-3.4.2.rst
Normal file
28
doc/en/announce/release-3.4.2.rst
Normal file
@@ -0,0 +1,28 @@
|
||||
pytest-3.4.2
|
||||
=======================================
|
||||
|
||||
pytest 3.4.2 has just been released to PyPI.
|
||||
|
||||
This is a bug-fix release, being a drop-in replacement. To upgrade::
|
||||
|
||||
pip install --upgrade pytest
|
||||
|
||||
The full changelog is available at http://doc.pytest.org/en/latest/changelog.html.
|
||||
|
||||
Thanks to all who contributed to this release, among them:
|
||||
|
||||
* Allan Feldman
|
||||
* Bruno Oliveira
|
||||
* Florian Bruhin
|
||||
* Jason R. Coombs
|
||||
* Kyle Altendorf
|
||||
* Maik Figura
|
||||
* Ronny Pfannschmidt
|
||||
* codetriage-readme-bot
|
||||
* feuillemorte
|
||||
* joshm91
|
||||
* mike
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
||||
@@ -358,7 +358,7 @@ get on the terminal - we are working on that)::
|
||||
> int(s)
|
||||
E ValueError: invalid literal for int() with base 10: 'qwe'
|
||||
|
||||
<0-codegen $PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/python_api.py:580>:1: ValueError
|
||||
<0-codegen $PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/python_api.py:595>:1: ValueError
|
||||
______________________ TestRaises.test_raises_doesnt _______________________
|
||||
|
||||
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
||||
|
||||
@@ -385,8 +385,8 @@ Now we can profile which test functions execute the slowest::
|
||||
test_some_are_slow.py ... [100%]
|
||||
|
||||
========================= slowest 3 test durations =========================
|
||||
0.58s call test_some_are_slow.py::test_funcslow2
|
||||
0.41s call test_some_are_slow.py::test_funcslow1
|
||||
0.30s call test_some_are_slow.py::test_funcslow2
|
||||
0.20s call test_some_are_slow.py::test_funcslow1
|
||||
0.10s call test_some_are_slow.py::test_funcfast
|
||||
========================= 3 passed in 0.12 seconds =========================
|
||||
|
||||
|
||||
@@ -369,7 +369,7 @@ ends, but ``addfinalizer`` has two key differences over ``yield``:
|
||||
Fixtures can introspect the requesting test context
|
||||
-------------------------------------------------------------
|
||||
|
||||
Fixture function can accept the :py:class:`request <FixtureRequest>` object
|
||||
Fixture functions can accept the :py:class:`request <FixtureRequest>` object
|
||||
to introspect the "requesting" test function, class or module context.
|
||||
Further extending the previous ``smtp`` fixture example, let's
|
||||
read an optional server URL from the test module which uses our fixture::
|
||||
|
||||
@@ -160,9 +160,9 @@ List the name ``tmpdir`` in the test function signature and ``pytest`` will look
|
||||
PYTEST_TMPDIR/test_needsfiles0
|
||||
1 failed in 0.12 seconds
|
||||
|
||||
More info on tmpdir handling is available at `Temporary directories and files <tmpdir handling>`_.
|
||||
More info on tmpdir handling is available at :ref:`Temporary directories and files <tmpdir handling>`.
|
||||
|
||||
Find out what kind of builtin ```pytest`` fixtures <fixtures>`_ exist with the command::
|
||||
Find out what kind of builtin :ref:`pytest fixtures <fixtures>` exist with the command::
|
||||
|
||||
pytest --fixtures # shows builtin and custom fixtures
|
||||
|
||||
|
||||
@@ -148,6 +148,7 @@ in the `pytest repository <https://github.com/pytest-dev/pytest>`_.
|
||||
_pytest.resultlog
|
||||
_pytest.runner
|
||||
_pytest.main
|
||||
_pytest.logging
|
||||
_pytest.skipping
|
||||
_pytest.terminal
|
||||
_pytest.tmpdir
|
||||
|
||||
@@ -278,7 +278,7 @@ the plugin manager like this:
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
plugin = config.pluginmanager.getplugin("name_of_plugin")
|
||||
plugin = config.pluginmanager.get_plugin("name_of_plugin")
|
||||
|
||||
If you want to look at the names of existing plugins, use
|
||||
the ``--trace-config`` option.
|
||||
@@ -462,19 +462,24 @@ Here is an example definition of a hook wrapper::
|
||||
|
||||
@pytest.hookimpl(hookwrapper=True)
|
||||
def pytest_pyfunc_call(pyfuncitem):
|
||||
# do whatever you want before the next hook executes
|
||||
do_something_before_next_hook_executes()
|
||||
|
||||
outcome = yield
|
||||
# outcome.excinfo may be None or a (cls, val, tb) tuple
|
||||
|
||||
res = outcome.get_result() # will raise if outcome was exception
|
||||
# postprocess result
|
||||
|
||||
post_process_result(res)
|
||||
|
||||
outcome.force_result(new_res) # to override the return value to the plugin system
|
||||
|
||||
Note that hook wrappers don't return results themselves, they merely
|
||||
perform tracing or other side effects around the actual hook implementations.
|
||||
If the result of the underlying hook is a mutable object, they may modify
|
||||
that result but it's probably better to avoid it.
|
||||
|
||||
For more information, consult the `pluggy documentation <http://pluggy.readthedocs.io/en/latest/#wrappers>`_.
|
||||
|
||||
|
||||
Hook function ordering / call example
|
||||
-------------------------------------
|
||||
|
||||
2
setup.py
2
setup.py
@@ -16,7 +16,7 @@ classifiers = [
|
||||
'Topic :: Utilities',
|
||||
] + [
|
||||
('Programming Language :: Python :: %s' % x)
|
||||
for x in '2 2.7 3 3.4 3.5 3.6'.split()
|
||||
for x in '2 2.7 3 3.4 3.5 3.6 3.7'.split()
|
||||
]
|
||||
|
||||
with open('README.rst') as fd:
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import os
|
||||
"""
|
||||
Invoke development tasks.
|
||||
"""
|
||||
from pathlib import Path
|
||||
from subprocess import check_output, check_call
|
||||
|
||||
@@ -57,7 +59,7 @@ def regen(ctx):
|
||||
|
||||
@invoke.task()
|
||||
def make_tag(ctx, version):
|
||||
"""Create a new (local) tag for the release, only if the repository is clean."""
|
||||
"""Create a new, local tag for the release, only if the repository is clean."""
|
||||
from git import Repo
|
||||
|
||||
repo = Repo('.')
|
||||
@@ -74,81 +76,24 @@ def make_tag(ctx, version):
|
||||
repo.create_tag(version)
|
||||
|
||||
|
||||
@invoke.task()
|
||||
def devpi_upload(ctx, version, user, password=None):
|
||||
"""Creates and uploads a package to devpi for testing."""
|
||||
if password:
|
||||
print("[generate.devpi_upload] devpi login {}".format(user))
|
||||
check_call(['devpi', 'login', user, '--password', password])
|
||||
|
||||
check_call(['devpi', 'use', 'https://devpi.net/{}/dev'.format(user)])
|
||||
|
||||
env = os.environ.copy()
|
||||
env['SETUPTOOLS_SCM_PRETEND_VERSION'] = version
|
||||
check_call(['devpi', 'upload', '--formats', 'sdist,bdist_wheel'], env=env)
|
||||
print("[generate.devpi_upload] package uploaded")
|
||||
|
||||
|
||||
@invoke.task(help={
|
||||
'version': 'version being released',
|
||||
'user': 'name of the user on devpi to stage the generated package',
|
||||
'password': 'user password on devpi to stage the generated package '
|
||||
'(if not given assumed logged in)',
|
||||
})
|
||||
def pre_release(ctx, version, user, password=None):
|
||||
"""Generates new docs, release announcements and uploads a new release to devpi for testing."""
|
||||
def pre_release(ctx, version):
|
||||
"""Generates new docs, release announcements and creates a local tag."""
|
||||
announce(ctx, version)
|
||||
regen(ctx)
|
||||
changelog(ctx, version, write_out=True)
|
||||
|
||||
msg = 'Preparing release version {}'.format(version)
|
||||
check_call(['git', 'commit', '-a', '-m', msg])
|
||||
|
||||
|
||||
make_tag(ctx, version)
|
||||
|
||||
devpi_upload(ctx, version=version, user=user, password=password)
|
||||
|
||||
print()
|
||||
print('[generate.pre_release] Please push your branch and open a PR.')
|
||||
|
||||
|
||||
@invoke.task(help={
|
||||
'version': 'version being released',
|
||||
'user': 'name of the user on devpi to stage the generated package',
|
||||
'pypi_name': 'name of the pypi configuration section in your ~/.pypirc',
|
||||
})
|
||||
def publish_release(ctx, version, user, pypi_name):
|
||||
"""Publishes a package previously created by the 'pre_release' command."""
|
||||
from git import Repo
|
||||
repo = Repo('.')
|
||||
tag_names = [x.name for x in repo.tags]
|
||||
if version not in tag_names:
|
||||
print('Could not find tag for version {}, exiting...'.format(version))
|
||||
raise invoke.Exit(code=2)
|
||||
|
||||
check_call(['devpi', 'use', 'https://devpi.net/{}/dev'.format(user)])
|
||||
check_call(['devpi', 'push', 'pytest=={}'.format(version), 'pypi:{}'.format(pypi_name)])
|
||||
check_call(['git', 'push', 'git@github.com:pytest-dev/pytest.git', version])
|
||||
|
||||
emails = [
|
||||
'pytest-dev@python.org',
|
||||
'python-announce-list@python.org'
|
||||
]
|
||||
if version.endswith('.0'):
|
||||
emails.append('testing-in-python@lists.idyll.org')
|
||||
print('Version {} has been published to PyPI!'.format(version))
|
||||
print()
|
||||
print('Please send an email announcement with the contents from:')
|
||||
print()
|
||||
print(' doc/en/announce/release-{}.rst'.format(version))
|
||||
print()
|
||||
print('To the following mail lists:')
|
||||
print()
|
||||
print(' ', ','.join(emails))
|
||||
print()
|
||||
print('And announce it on twitter adding the #pytest hash tag.')
|
||||
|
||||
|
||||
@invoke.task(help={
|
||||
'version': 'version being released',
|
||||
'write_out': 'write changes to the actual changelog'
|
||||
@@ -158,5 +103,4 @@ def changelog(ctx, version, write_out=False):
|
||||
addopts = []
|
||||
else:
|
||||
addopts = ['--draft']
|
||||
check_call(['towncrier', '--version', version] + addopts)
|
||||
|
||||
check_call(['towncrier', '--yes', '--version', version] + addopts)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
invoke
|
||||
tox
|
||||
gitpython
|
||||
invoke
|
||||
towncrier
|
||||
wheel
|
||||
tox
|
||||
|
||||
@@ -272,6 +272,60 @@ def test_log_cli_default_level_sections(testdir, request):
|
||||
])
|
||||
|
||||
|
||||
def test_live_logs_unknown_sections(testdir, request):
|
||||
"""Check that with live logging enable we are printing the correct headers during
|
||||
start/setup/call/teardown/finish."""
|
||||
filename = request.node.name + '.py'
|
||||
testdir.makeconftest('''
|
||||
import pytest
|
||||
import logging
|
||||
|
||||
def pytest_runtest_protocol(item, nextitem):
|
||||
logging.warning('Unknown Section!')
|
||||
|
||||
def pytest_runtest_logstart():
|
||||
logging.warning('>>>>> START >>>>>')
|
||||
|
||||
def pytest_runtest_logfinish():
|
||||
logging.warning('<<<<< END <<<<<<<')
|
||||
''')
|
||||
|
||||
testdir.makepyfile('''
|
||||
import pytest
|
||||
import logging
|
||||
|
||||
@pytest.fixture
|
||||
def fix(request):
|
||||
logging.warning("log message from setup of {}".format(request.node.name))
|
||||
yield
|
||||
logging.warning("log message from teardown of {}".format(request.node.name))
|
||||
|
||||
def test_log_1(fix):
|
||||
logging.warning("log message from test_log_1")
|
||||
|
||||
''')
|
||||
testdir.makeini('''
|
||||
[pytest]
|
||||
log_cli=true
|
||||
''')
|
||||
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines([
|
||||
'*WARNING*Unknown Section*',
|
||||
'{}::test_log_1 '.format(filename),
|
||||
'*WARNING* >>>>> START >>>>>*',
|
||||
'*-- live log setup --*',
|
||||
'*WARNING*log message from setup of test_log_1*',
|
||||
'*-- live log call --*',
|
||||
'*WARNING*log message from test_log_1*',
|
||||
'PASSED *100%*',
|
||||
'*-- live log teardown --*',
|
||||
'*WARNING*log message from teardown of test_log_1*',
|
||||
'*WARNING* <<<<< END <<<<<<<*',
|
||||
'=* 1 passed in *=',
|
||||
])
|
||||
|
||||
|
||||
def test_log_cli_level(testdir):
|
||||
# Default log file level
|
||||
testdir.makepyfile('''
|
||||
|
||||
@@ -249,6 +249,7 @@ class TestApprox(object):
|
||||
(Decimal('-1.000001'), Decimal('-1.0')),
|
||||
]
|
||||
for a, x in within_1e6:
|
||||
assert a == approx(x)
|
||||
assert a == approx(x, rel=Decimal('5e-6'), abs=0)
|
||||
assert a != approx(x, rel=Decimal('5e-7'), abs=0)
|
||||
assert approx(x, rel=Decimal('5e-6'), abs=0) == a
|
||||
|
||||
@@ -519,6 +519,41 @@ class TestRequestBasic(object):
|
||||
assert len(arg2fixturedefs) == 1
|
||||
assert arg2fixturedefs['something'][0].argname == "something"
|
||||
|
||||
def test_request_garbage(self, testdir):
|
||||
testdir.makepyfile("""
|
||||
import sys
|
||||
import pytest
|
||||
import gc
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def something(request):
|
||||
# this method of test doesn't work on pypy
|
||||
if hasattr(sys, "pypy_version_info"):
|
||||
yield
|
||||
else:
|
||||
original = gc.get_debug()
|
||||
gc.set_debug(gc.DEBUG_SAVEALL)
|
||||
gc.collect()
|
||||
|
||||
yield
|
||||
|
||||
gc.collect()
|
||||
leaked_types = sum(1 for _ in gc.garbage
|
||||
if 'PseudoFixtureDef' in str(_))
|
||||
|
||||
gc.garbage[:] = []
|
||||
|
||||
try:
|
||||
assert leaked_types == 0
|
||||
finally:
|
||||
gc.set_debug(original)
|
||||
|
||||
def test_func():
|
||||
pass
|
||||
""")
|
||||
reprec = testdir.inline_run()
|
||||
reprec.assertoutcome(passed=1)
|
||||
|
||||
def test_getfixturevalue_recursive(self, testdir):
|
||||
testdir.makeconftest("""
|
||||
import pytest
|
||||
@@ -2168,6 +2203,47 @@ class TestFixtureMarker(object):
|
||||
test_mod1.py::test_func1[m2] PASSED
|
||||
""")
|
||||
|
||||
def test_dynamic_parametrized_ordering(self, testdir):
|
||||
testdir.makeini("""
|
||||
[pytest]
|
||||
console_output_style=classic
|
||||
""")
|
||||
testdir.makeconftest("""
|
||||
import pytest
|
||||
|
||||
def pytest_configure(config):
|
||||
class DynamicFixturePlugin(object):
|
||||
@pytest.fixture(scope='session', params=['flavor1', 'flavor2'])
|
||||
def flavor(self, request):
|
||||
return request.param
|
||||
config.pluginmanager.register(DynamicFixturePlugin(), 'flavor-fixture')
|
||||
|
||||
@pytest.fixture(scope='session', params=['vxlan', 'vlan'])
|
||||
def encap(request):
|
||||
return request.param
|
||||
|
||||
@pytest.fixture(scope='session', autouse='True')
|
||||
def reprovision(request, flavor, encap):
|
||||
pass
|
||||
""")
|
||||
testdir.makepyfile("""
|
||||
def test(reprovision):
|
||||
pass
|
||||
def test2(reprovision):
|
||||
pass
|
||||
""")
|
||||
result = testdir.runpytest("-v")
|
||||
result.stdout.fnmatch_lines("""
|
||||
test_dynamic_parametrized_ordering.py::test[flavor1-vxlan] PASSED
|
||||
test_dynamic_parametrized_ordering.py::test2[flavor1-vxlan] PASSED
|
||||
test_dynamic_parametrized_ordering.py::test[flavor2-vxlan] PASSED
|
||||
test_dynamic_parametrized_ordering.py::test2[flavor2-vxlan] PASSED
|
||||
test_dynamic_parametrized_ordering.py::test[flavor2-vlan] PASSED
|
||||
test_dynamic_parametrized_ordering.py::test2[flavor2-vlan] PASSED
|
||||
test_dynamic_parametrized_ordering.py::test[flavor1-vlan] PASSED
|
||||
test_dynamic_parametrized_ordering.py::test2[flavor1-vlan] PASSED
|
||||
""")
|
||||
|
||||
def test_class_ordering(self, testdir):
|
||||
testdir.makeini("""
|
||||
[pytest]
|
||||
|
||||
@@ -147,6 +147,28 @@ class TestMockDecoration(object):
|
||||
reprec = testdir.inline_run()
|
||||
reprec.assertoutcome(passed=1)
|
||||
|
||||
def test_unittest_mock_and_pypi_mock(self, testdir):
|
||||
pytest.importorskip("unittest.mock")
|
||||
pytest.importorskip("mock", "1.0.1")
|
||||
testdir.makepyfile("""
|
||||
import mock
|
||||
import unittest.mock
|
||||
class TestBoth(object):
|
||||
@unittest.mock.patch("os.path.abspath")
|
||||
def test_hello(self, abspath):
|
||||
import os
|
||||
os.path.abspath("hello")
|
||||
abspath.assert_any_call("hello")
|
||||
|
||||
@mock.patch("os.path.abspath")
|
||||
def test_hello_mock(self, abspath):
|
||||
import os
|
||||
os.path.abspath("hello")
|
||||
abspath.assert_any_call("hello")
|
||||
""")
|
||||
reprec = testdir.inline_run()
|
||||
reprec.assertoutcome(passed=2)
|
||||
|
||||
def test_mock(self, testdir):
|
||||
pytest.importorskip("mock", "1.0.1")
|
||||
testdir.makepyfile("""
|
||||
|
||||
@@ -132,3 +132,13 @@ class TestRaises(object):
|
||||
with pytest.raises(AssertionError, match=expr):
|
||||
with pytest.raises(ValueError, match=msg):
|
||||
int('asdf', base=10)
|
||||
|
||||
def test_raises_match_wrong_type(self):
|
||||
"""Raising an exception with the wrong type and match= given.
|
||||
|
||||
pytest should throw the unexpected exception - the pattern match is not
|
||||
really relevant if we got a different exception.
|
||||
"""
|
||||
with pytest.raises(ValueError):
|
||||
with pytest.raises(IndexError, match='nomatch'):
|
||||
int('asdf')
|
||||
|
||||
@@ -110,6 +110,13 @@ class TestConfigCmdlineParsing(object):
|
||||
config = testdir.parseconfig("-c", "custom.cfg")
|
||||
assert config.getini("custom") == "1"
|
||||
|
||||
testdir.makefile(".cfg", custom_tool_pytest_section="""
|
||||
[tool:pytest]
|
||||
custom = 1
|
||||
""")
|
||||
config = testdir.parseconfig("-c", "custom_tool_pytest_section.cfg")
|
||||
assert config.getini("custom") == "1"
|
||||
|
||||
def test_absolute_win32_path(self, testdir):
|
||||
temp_cfg_file = testdir.makefile(".cfg", custom="""
|
||||
[pytest]
|
||||
|
||||
@@ -141,19 +141,50 @@ class TestPDB(object):
|
||||
child.sendeof()
|
||||
self.flush(child)
|
||||
|
||||
def test_pdb_interaction_capture(self, testdir):
|
||||
def test_pdb_print_captured_stdout(self, testdir):
|
||||
p1 = testdir.makepyfile("""
|
||||
def test_1():
|
||||
print("getrekt")
|
||||
print("get\\x20rekt")
|
||||
assert False
|
||||
""")
|
||||
child = testdir.spawn_pytest("--pdb %s" % p1)
|
||||
child.expect("getrekt")
|
||||
child.expect("captured stdout")
|
||||
child.expect("get rekt")
|
||||
child.expect("(Pdb)")
|
||||
child.sendeof()
|
||||
rest = child.read().decode("utf8")
|
||||
assert "1 failed" in rest
|
||||
assert "getrekt" not in rest
|
||||
assert "get rekt" not in rest
|
||||
self.flush(child)
|
||||
|
||||
def test_pdb_print_captured_stderr(self, testdir):
|
||||
p1 = testdir.makepyfile("""
|
||||
def test_1():
|
||||
import sys
|
||||
sys.stderr.write("get\\x20rekt")
|
||||
assert False
|
||||
""")
|
||||
child = testdir.spawn_pytest("--pdb %s" % p1)
|
||||
child.expect("captured stderr")
|
||||
child.expect("get rekt")
|
||||
child.expect("(Pdb)")
|
||||
child.sendeof()
|
||||
rest = child.read().decode("utf8")
|
||||
assert "1 failed" in rest
|
||||
assert "get rekt" not in rest
|
||||
self.flush(child)
|
||||
|
||||
def test_pdb_dont_print_empty_captured_stdout_and_stderr(self, testdir):
|
||||
p1 = testdir.makepyfile("""
|
||||
def test_1():
|
||||
assert False
|
||||
""")
|
||||
child = testdir.spawn_pytest("--pdb %s" % p1)
|
||||
child.expect("(Pdb)")
|
||||
output = child.before.decode("utf8")
|
||||
child.sendeof()
|
||||
assert "captured stdout" not in output
|
||||
assert "captured stderr" not in output
|
||||
self.flush(child)
|
||||
|
||||
def test_pdb_interaction_exception(self, testdir):
|
||||
|
||||
@@ -206,13 +206,13 @@ class TestWarns(object):
|
||||
with pytest.warns(RuntimeWarning):
|
||||
warnings.warn("user", UserWarning)
|
||||
excinfo.match(r"DID NOT WARN. No warnings of type \(.+RuntimeWarning.+,\) was emitted. "
|
||||
r"The list of emitted warnings is: \[UserWarning\('user',\)\].")
|
||||
r"The list of emitted warnings is: \[UserWarning\('user',?\)\].")
|
||||
|
||||
with pytest.raises(pytest.fail.Exception) as excinfo:
|
||||
with pytest.warns(UserWarning):
|
||||
warnings.warn("runtime", RuntimeWarning)
|
||||
excinfo.match(r"DID NOT WARN. No warnings of type \(.+UserWarning.+,\) was emitted. "
|
||||
r"The list of emitted warnings is: \[RuntimeWarning\('runtime',\)\].")
|
||||
r"The list of emitted warnings is: \[RuntimeWarning\('runtime',?\)\].")
|
||||
|
||||
with pytest.raises(pytest.fail.Exception) as excinfo:
|
||||
with pytest.warns(UserWarning):
|
||||
|
||||
@@ -1046,6 +1046,9 @@ class TestProgress(object):
|
||||
r'test_foobar.py \.{5}',
|
||||
])
|
||||
|
||||
output = testdir.runpytest('--capture=no')
|
||||
assert "%]" not in output.stdout.str()
|
||||
|
||||
|
||||
class TestProgressWithTeardown(object):
|
||||
"""Ensure we show the correct percentages for tests that fail during teardown (#3088)"""
|
||||
|
||||
Reference in New Issue
Block a user