Compare commits
224 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5bbf8ac73 | ||
|
|
253c5786af | ||
|
|
c4550bc922 | ||
|
|
2c00f8aad1 | ||
|
|
456715a5c1 | ||
|
|
54b8ad4554 | ||
|
|
2868c31495 | ||
|
|
39a13d7064 | ||
|
|
e4e4fd1e52 | ||
|
|
e8c220b9bd | ||
|
|
9646a1cd7a | ||
|
|
9087ac4010 | ||
|
|
093e19a7d9 | ||
|
|
9e867ce864 | ||
|
|
8abf30ad71 | ||
|
|
ea25eb1ecc | ||
|
|
58b6e8616c | ||
|
|
f129ba617f | ||
|
|
99d957bd3d | ||
|
|
661013c3e9 | ||
|
|
141c51f0cb | ||
|
|
d65c7658d5 | ||
|
|
7855284ef7 | ||
|
|
5b0f88712b | ||
|
|
2e42d937dc | ||
|
|
27d932e882 | ||
|
|
40091ec2c7 | ||
|
|
76fb9970c8 | ||
|
|
d32f2c5c14 | ||
|
|
49defa2890 | ||
|
|
fe2dae4885 | ||
|
|
ced62f30ba | ||
|
|
bbd1cbb0b3 | ||
|
|
d4dfd526c1 | ||
|
|
d4351ac5a2 | ||
|
|
766d2daa06 | ||
|
|
836c9f82f1 | ||
|
|
46d6a3fc27 | ||
|
|
1dfa303b1e | ||
|
|
6258248865 | ||
|
|
4808145846 | ||
|
|
a0666354dd | ||
|
|
ce55dcf64c | ||
|
|
d7be039f1b | ||
|
|
7e1fac5f91 | ||
|
|
486ded3fca | ||
|
|
0be84cd68b | ||
|
|
323c846ce6 | ||
|
|
3bd9f981a2 | ||
|
|
7ded937e19 | ||
|
|
6d0667f1db | ||
|
|
7c380b19f3 | ||
|
|
5322f422e3 | ||
|
|
c6c326f076 | ||
|
|
d6832a8b56 | ||
|
|
3bfaa8ab84 | ||
|
|
9fb305b17b | ||
|
|
e3bf9cede4 | ||
|
|
4a49715614 | ||
|
|
86c7dcff68 | ||
|
|
7268462b33 | ||
|
|
448830e656 | ||
|
|
3683d92c53 | ||
|
|
d3d8d53e41 | ||
|
|
7a271a91b0 | ||
|
|
47f5c29002 | ||
|
|
27d2683a02 | ||
|
|
792f365c14 | ||
|
|
91b2797498 | ||
|
|
c27c8f41a8 | ||
|
|
ee54fb9a6b | ||
|
|
10ddc466bf | ||
|
|
24c83d725a | ||
|
|
6bf4692c7d | ||
|
|
81426c3d19 | ||
|
|
ed42ada373 | ||
|
|
e2667106a2 | ||
|
|
29d5849519 | ||
|
|
eabf15b626 | ||
|
|
2dc619cbf4 | ||
|
|
307fa7a42a | ||
|
|
ef97121d42 | ||
|
|
d46b6b2bc3 | ||
|
|
2cb3534679 | ||
|
|
8e11fe5304 | ||
|
|
36dc671843 | ||
|
|
dbaa9464ba | ||
|
|
933de16fe4 | ||
|
|
e8348a1d12 | ||
|
|
0f5263cdc3 | ||
|
|
4736b2bdfb | ||
|
|
8ecdd4e9ff | ||
|
|
b3940666a7 | ||
|
|
e20987ce82 | ||
|
|
584051aa90 | ||
|
|
16e2737da3 | ||
|
|
36c2a101cb | ||
|
|
ebd597b2fd | ||
|
|
94829c391b | ||
|
|
b82d6f7a0b | ||
|
|
4a436b5470 | ||
|
|
ad6f63edda | ||
|
|
3036914097 | ||
|
|
2831cb9ab5 | ||
|
|
00716177b4 | ||
|
|
85cc9b8f12 | ||
|
|
fed4f73a61 | ||
|
|
d76fa59b35 | ||
|
|
2532dc1dbb | ||
|
|
642cd86dd1 | ||
|
|
b3a5b0ebe1 | ||
|
|
8b4a29357e | ||
|
|
ab3637d486 | ||
|
|
66a690928c | ||
|
|
8e00280fc1 | ||
|
|
d053cdfbbb | ||
|
|
2e39fd89d1 | ||
|
|
b48e23d54c | ||
|
|
c9a85b0e78 | ||
|
|
bf265a424d | ||
|
|
5436e42990 | ||
|
|
67f40e18a7 | ||
|
|
52ff1eaf37 | ||
|
|
602e74c2a7 | ||
|
|
be511c1a05 | ||
|
|
f36f9d2698 | ||
|
|
d1322570dd | ||
|
|
4c9015c3b1 | ||
|
|
c14a23d4e4 | ||
|
|
b8fc3e569a | ||
|
|
e0f6fce9e9 | ||
|
|
d93de6cc67 | ||
|
|
aeb92accb2 | ||
|
|
943bbdd8ce | ||
|
|
9a3836a0cf | ||
|
|
4b164d947d | ||
|
|
11a07211b6 | ||
|
|
28a3f0fcb9 | ||
|
|
b8c30aab2b | ||
|
|
b8958168f5 | ||
|
|
aaaae0b232 | ||
|
|
c55d641963 | ||
|
|
4ec85f934c | ||
|
|
8393fdd51d | ||
|
|
4071c8a4a8 | ||
|
|
e8c10d4a98 | ||
|
|
a86035625c | ||
|
|
4f631440be | ||
|
|
3901569f26 | ||
|
|
689b856cb7 | ||
|
|
65545d8fb2 | ||
|
|
1caf6d5907 | ||
|
|
55871c68a4 | ||
|
|
fc11b81005 | ||
|
|
a6fb4c8268 | ||
|
|
48dcc67274 | ||
|
|
ccaec8d360 | ||
|
|
66609665f2 | ||
|
|
4b36f9aa64 | ||
|
|
5ecbb0acba | ||
|
|
20902deb75 | ||
|
|
ed5556bdac | ||
|
|
ee64f1fb9f | ||
|
|
8e0e862c84 | ||
|
|
42422a7f62 | ||
|
|
f3a173b736 | ||
|
|
5c38a5160d | ||
|
|
dcf9eb0104 | ||
|
|
dd225e1b9d | ||
|
|
900cef6397 | ||
|
|
0d095fc978 | ||
|
|
dcd635ba0c | ||
|
|
33f0338eeb | ||
|
|
d5e5433553 | ||
|
|
d2906950ce | ||
|
|
fe7050ba00 | ||
|
|
a1208f5631 | ||
|
|
870a93c37b | ||
|
|
96b2ae6654 | ||
|
|
b098292352 | ||
|
|
212937eb3e | ||
|
|
70c7273640 | ||
|
|
e5ab62b1b6 | ||
|
|
b8b9e8d41c | ||
|
|
e712adc226 | ||
|
|
f9ac60807c | ||
|
|
3f03625a5d | ||
|
|
29d3faed66 | ||
|
|
c5dec6056f | ||
|
|
642847c079 | ||
|
|
f102ccc8f0 | ||
|
|
20f93ae8fa | ||
|
|
1101a20408 | ||
|
|
df435fa8bd | ||
|
|
a5269b26e0 | ||
|
|
5010e02eda | ||
|
|
5d8467bedc | ||
|
|
0d04aa7c59 | ||
|
|
e03a19f88d | ||
|
|
fcc5b6d604 | ||
|
|
42afce27b3 | ||
|
|
56d0b5a7e2 | ||
|
|
c30184709d | ||
|
|
83802d1494 | ||
|
|
18cc74b8d0 | ||
|
|
048342817b | ||
|
|
d1a3aa7b2b | ||
|
|
e967d4587a | ||
|
|
a79dc12f1e | ||
|
|
913c07e414 | ||
|
|
4a9f468aac | ||
|
|
05155e4db0 | ||
|
|
bceaede198 | ||
|
|
ae8f3695b5 | ||
|
|
da6830f19b | ||
|
|
32ee0b9c88 | ||
|
|
feb8240410 | ||
|
|
b7dd9154c3 | ||
|
|
482bd5efd2 | ||
|
|
bf074b37a3 | ||
|
|
495a55725b | ||
|
|
53c9124fc9 | ||
|
|
ab40696007 | ||
|
|
d12f46caef |
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -3,7 +3,7 @@ Thanks for submitting a PR, your contribution is really appreciated!
|
||||
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):
|
||||
|
||||
- [ ] 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.
|
||||
- [ ] Create a new changelog file in the `changelog` folder, with a name like `<ISSUE NUMBER>.<TYPE>.rst`. See [changelog/README.rst](https://github.com/pytest-dev/pytest/blob/master/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.
|
||||
|
||||
@@ -7,26 +7,29 @@ repos:
|
||||
args: [--safe, --quiet]
|
||||
language_version: python3
|
||||
- repo: https://github.com/asottile/blacken-docs
|
||||
rev: v0.2.0
|
||||
rev: v0.3.0
|
||||
hooks:
|
||||
- id: blacken-docs
|
||||
additional_dependencies: [black==18.6b4]
|
||||
additional_dependencies: [black==18.9b0]
|
||||
language_version: python3
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v1.3.0
|
||||
rev: v1.4.0-1
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
- id: check-yaml
|
||||
- id: debug-statements
|
||||
exclude: _pytest/debugging.py
|
||||
language_version: python3
|
||||
- id: flake8
|
||||
language_version: python3
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v1.2.0
|
||||
rev: v1.8.0
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
- id: pyupgrade
|
||||
args: [--keep-percent-format]
|
||||
- repo: https://github.com/pre-commit/pygrep-hooks
|
||||
rev: v1.0.0
|
||||
rev: v1.1.0
|
||||
hooks:
|
||||
- id: rst-backticks
|
||||
- repo: local
|
||||
@@ -40,6 +43,6 @@ repos:
|
||||
- id: changelogs-rst
|
||||
name: changelog filenames
|
||||
language: fail
|
||||
entry: 'changelog files must be named ####.(feature|bugfix|doc|removal|vendor|trivial).rst'
|
||||
exclude: changelog/(\d+\.(feature|bugfix|doc|removal|vendor|trivial).rst|README.rst|_template.rst)
|
||||
entry: 'changelog files must be named ####.(feature|bugfix|doc|deprecation|removal|vendor|trivial).rst'
|
||||
exclude: changelog/(\d+\.(feature|bugfix|doc|deprecation|removal|vendor|trivial).rst|README.rst|_template.rst)
|
||||
files: ^changelog/
|
||||
|
||||
24
.travis.yml
24
.travis.yml
@@ -12,21 +12,15 @@ install:
|
||||
- pip install --upgrade --pre tox
|
||||
env:
|
||||
matrix:
|
||||
# note: please use "tox --listenvs" to populate the build matrix below
|
||||
# please remove the linting env in all cases
|
||||
- TOXENV=py27-pexpect
|
||||
- TOXENV=py27-xdist
|
||||
- TOXENV=py27-trial
|
||||
- TOXENV=py27-numpy
|
||||
- TOXENV=py27-pluggymaster PYTEST_NO_COVERAGE=1
|
||||
- TOXENV=py36-pexpect
|
||||
- TOXENV=py36-xdist
|
||||
- TOXENV=py36-trial
|
||||
- TOXENV=py36-numpy
|
||||
- TOXENV=py36-pluggymaster PYTEST_NO_COVERAGE=1
|
||||
# Specialized factors for py27.
|
||||
- TOXENV=py27-pexpect,py27-trial,py27-numpy
|
||||
- TOXENV=py27-nobyte
|
||||
- TOXENV=doctesting
|
||||
- TOXENV=docs PYTEST_NO_COVERAGE=1
|
||||
- TOXENV=py27-xdist
|
||||
- TOXENV=py27-pluggymaster PYTEST_NO_COVERAGE=1
|
||||
# Specialized factors for py36.
|
||||
- TOXENV=py36-pexpect,py36-trial,py36-numpy
|
||||
- TOXENV=py36-xdist
|
||||
- TOXENV=py36-pluggymaster PYTEST_NO_COVERAGE=1
|
||||
|
||||
jobs:
|
||||
include:
|
||||
@@ -61,7 +55,7 @@ jobs:
|
||||
env: TOXENV=py27
|
||||
- env: TOXENV=py34
|
||||
- env: TOXENV=py36
|
||||
- env: TOXENV=linting PYTEST_NO_COVERAGE=1
|
||||
- env: TOXENV=linting,docs,doctesting PYTEST_NO_COVERAGE=1
|
||||
|
||||
- stage: deploy
|
||||
python: '3.6'
|
||||
|
||||
3
AUTHORS
3
AUTHORS
@@ -14,6 +14,7 @@ Allan Feldman
|
||||
Anatoly Bubenkoff
|
||||
Anders Hovmöller
|
||||
Andras Tim
|
||||
Andrea Cimatoribus
|
||||
Andreas Zeidler
|
||||
Andrzej Ostrowski
|
||||
Andy Freeland
|
||||
@@ -120,6 +121,7 @@ Katerina Koukiou
|
||||
Kevin Cox
|
||||
Kodi B. Arfer
|
||||
Kostis Anagnostopoulos
|
||||
Kyle Altendorf
|
||||
Lawrence Mitchell
|
||||
Lee Kamentsky
|
||||
Lev Maximov
|
||||
@@ -209,6 +211,7 @@ Thomas Hisch
|
||||
Tim Strazny
|
||||
Tom Dalton
|
||||
Tom Viner
|
||||
Tomer Keren
|
||||
Trevor Bekolay
|
||||
Tyler Goodlet
|
||||
Tzu-ping Chung
|
||||
|
||||
157
CHANGELOG.rst
157
CHANGELOG.rst
@@ -18,6 +18,163 @@ with advance notice in the **Deprecations** section of releases.
|
||||
|
||||
.. towncrier release notes start
|
||||
|
||||
pytest 3.9.1 (2018-10-16)
|
||||
=========================
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- `#4159 <https://github.com/pytest-dev/pytest/issues/4159>`_: For test-suites containing test classes, the information about the subclassed
|
||||
module is now output only if a higher verbosity level is specified (at least
|
||||
"-vv").
|
||||
|
||||
|
||||
pytest 3.9.0 (2018-10-15 - not published due to a release automation bug)
|
||||
=========================================================================
|
||||
|
||||
Deprecations
|
||||
------------
|
||||
|
||||
- `#3616 <https://github.com/pytest-dev/pytest/issues/3616>`_: The following accesses have been documented as deprecated for years, but are now actually emitting deprecation warnings.
|
||||
|
||||
* Access of ``Module``, ``Function``, ``Class``, ``Instance``, ``File`` and ``Item`` through ``Node`` instances. Now
|
||||
users will this warning::
|
||||
|
||||
usage of Function.Module is deprecated, please use pytest.Module instead
|
||||
|
||||
Users should just ``import pytest`` and access those objects using the ``pytest`` module.
|
||||
|
||||
* ``request.cached_setup``, this was the precursor of the setup/teardown mechanism available to fixtures. You can
|
||||
consult `funcarg comparision section in the docs <https://docs.pytest.org/en/latest/funcarg_compare.html>`_.
|
||||
|
||||
* Using objects named ``"Class"`` as a way to customize the type of nodes that are collected in ``Collector``
|
||||
subclasses has been deprecated. Users instead should use ``pytest_collect_make_item`` to customize node types during
|
||||
collection.
|
||||
|
||||
This issue should affect only advanced plugins who create new collection types, so if you see this warning
|
||||
message please contact the authors so they can change the code.
|
||||
|
||||
* The warning that produces the message below has changed to ``RemovedInPytest4Warning``::
|
||||
|
||||
getfuncargvalue is deprecated, use getfixturevalue
|
||||
|
||||
|
||||
- `#3988 <https://github.com/pytest-dev/pytest/issues/3988>`_: Add a Deprecation warning for pytest.ensuretemp as it was deprecated since a while.
|
||||
|
||||
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- `#2293 <https://github.com/pytest-dev/pytest/issues/2293>`_: Improve usage errors messages by hiding internal details which can be distracting and noisy.
|
||||
|
||||
This has the side effect that some error conditions that previously raised generic errors (such as
|
||||
``ValueError`` for unregistered marks) are now raising ``Failed`` exceptions.
|
||||
|
||||
|
||||
- `#3332 <https://github.com/pytest-dev/pytest/issues/3332>`_: Improve the error displayed when a ``conftest.py`` file could not be imported.
|
||||
|
||||
In order to implement this, a new ``chain`` parameter was added to ``ExceptionInfo.getrepr``
|
||||
to show or hide chained tracebacks in Python 3 (defaults to ``True``).
|
||||
|
||||
|
||||
- `#3849 <https://github.com/pytest-dev/pytest/issues/3849>`_: Add ``empty_parameter_set_mark=fail_at_collect`` ini option for raising an exception when parametrize collects an empty set.
|
||||
|
||||
|
||||
- `#3964 <https://github.com/pytest-dev/pytest/issues/3964>`_: Log messages generated in the collection phase are shown when
|
||||
live-logging is enabled and/or when they are logged to a file.
|
||||
|
||||
|
||||
- `#3985 <https://github.com/pytest-dev/pytest/issues/3985>`_: Introduce ``tmp_path`` as a fixture providing a Path object.
|
||||
|
||||
|
||||
- `#4013 <https://github.com/pytest-dev/pytest/issues/4013>`_: Deprecation warnings are now shown even if you customize the warnings filters yourself. In the previous version
|
||||
any customization would override pytest's filters and deprecation warnings would fall back to being hidden by default.
|
||||
|
||||
|
||||
- `#4073 <https://github.com/pytest-dev/pytest/issues/4073>`_: Allow specification of timeout for ``Testdir.runpytest_subprocess()`` and ``Testdir.run()``.
|
||||
|
||||
|
||||
- `#4098 <https://github.com/pytest-dev/pytest/issues/4098>`_: Add returncode argument to pytest.exit() to exit pytest with a specific return code.
|
||||
|
||||
|
||||
- `#4102 <https://github.com/pytest-dev/pytest/issues/4102>`_: Reimplement ``pytest.deprecated_call`` using ``pytest.warns`` so it supports the ``match='...'`` keyword argument.
|
||||
|
||||
This has the side effect that ``pytest.deprecated_call`` now raises ``pytest.fail.Exception`` instead
|
||||
of ``AssertionError``.
|
||||
|
||||
|
||||
- `#4149 <https://github.com/pytest-dev/pytest/issues/4149>`_: Require setuptools>=30.3 and move most of the metadata to ``setup.cfg``.
|
||||
|
||||
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#2535 <https://github.com/pytest-dev/pytest/issues/2535>`_: Improve error message when test functions of ``unittest.TestCase`` subclasses use a parametrized fixture.
|
||||
|
||||
|
||||
- `#3057 <https://github.com/pytest-dev/pytest/issues/3057>`_: ``request.fixturenames`` now correctly returns the name of fixtures created by ``request.getfixturevalue()``.
|
||||
|
||||
|
||||
- `#3946 <https://github.com/pytest-dev/pytest/issues/3946>`_: Warning filters passed as command line options using ``-W`` now take precedence over filters defined in ``ini``
|
||||
configuration files.
|
||||
|
||||
|
||||
- `#4066 <https://github.com/pytest-dev/pytest/issues/4066>`_: Fix source reindenting by using ``textwrap.dedent`` directly.
|
||||
|
||||
|
||||
- `#4102 <https://github.com/pytest-dev/pytest/issues/4102>`_: ``pytest.warn`` will capture previously-warned warnings in Python 2. Previously they were never raised.
|
||||
|
||||
|
||||
- `#4108 <https://github.com/pytest-dev/pytest/issues/4108>`_: Resolve symbolic links for args.
|
||||
|
||||
This fixes running ``pytest tests/test_foo.py::test_bar``, where ``tests``
|
||||
is a symlink to ``project/app/tests``:
|
||||
previously ``project/app/conftest.py`` would be ignored for fixtures then.
|
||||
|
||||
|
||||
- `#4132 <https://github.com/pytest-dev/pytest/issues/4132>`_: Fix duplicate printing of internal errors when using ``--pdb``.
|
||||
|
||||
|
||||
- `#4135 <https://github.com/pytest-dev/pytest/issues/4135>`_: pathlib based tmpdir cleanup now correctly handles symlinks in the folder.
|
||||
|
||||
|
||||
- `#4152 <https://github.com/pytest-dev/pytest/issues/4152>`_: Display the filename when encountering ``SyntaxWarning``.
|
||||
|
||||
|
||||
|
||||
Improved Documentation
|
||||
----------------------
|
||||
|
||||
- `#3713 <https://github.com/pytest-dev/pytest/issues/3713>`_: Update usefixtures documentation to clarify that it can't be used with fixture functions.
|
||||
|
||||
|
||||
- `#4058 <https://github.com/pytest-dev/pytest/issues/4058>`_: Update fixture documentation to specify that a fixture can be invoked twice in the scope it's defined for.
|
||||
|
||||
|
||||
- `#4064 <https://github.com/pytest-dev/pytest/issues/4064>`_: According to unittest.rst, setUpModule and tearDownModule were not implemented, but it turns out they are. So updated the documentation for unittest.
|
||||
|
||||
|
||||
- `#4151 <https://github.com/pytest-dev/pytest/issues/4151>`_: Add tempir testing example to CONTRIBUTING.rst guide
|
||||
|
||||
|
||||
|
||||
Trivial/Internal Changes
|
||||
------------------------
|
||||
|
||||
- `#2293 <https://github.com/pytest-dev/pytest/issues/2293>`_: The internal ``MarkerError`` exception has been removed.
|
||||
|
||||
|
||||
- `#3988 <https://github.com/pytest-dev/pytest/issues/3988>`_: Port the implementation of tmpdir to pathlib.
|
||||
|
||||
|
||||
- `#4063 <https://github.com/pytest-dev/pytest/issues/4063>`_: Exclude 0.00 second entries from ``--duration`` output unless ``-vv`` is passed on the command-line.
|
||||
|
||||
|
||||
- `#4093 <https://github.com/pytest-dev/pytest/issues/4093>`_: Fixed formatting of string literals in internal tests.
|
||||
|
||||
|
||||
pytest 3.8.2 (2018-10-02)
|
||||
=========================
|
||||
|
||||
|
||||
@@ -280,6 +280,47 @@ Here is a simple overview, with pytest-specific bits:
|
||||
base: features # if it's a feature
|
||||
|
||||
|
||||
Writing Tests
|
||||
----------------------------
|
||||
|
||||
Writing tests for plugins or for pytest itself is often done using the `testdir fixture <https://docs.pytest.org/en/latest/reference.html#testdir>`_, as a "black-box" test.
|
||||
|
||||
For example, to ensure a simple test passes you can write:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_true_assertion(testdir):
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
def test_foo():
|
||||
assert True
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest()
|
||||
result.assert_outcomes(failed=0, passed=1)
|
||||
|
||||
|
||||
Alternatively, it is possible to make checks based on the actual output of the termal using
|
||||
*glob-like* expressions:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_true_assertion(testdir):
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
def test_foo():
|
||||
assert False
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines(["*assert False*", "*1 failed*"])
|
||||
|
||||
When choosing a file where to write a new test, take a look at the existing files and see if there's
|
||||
one file which looks like a good fit. For example, a regression test about a bug in the ``--lf`` option
|
||||
should go into ``test_cacheprovider.py``, given that this option is implemented in ``cacheprovider.py``.
|
||||
If in doubt, go ahead and open a PR with your best guess and we can discuss this over the code.
|
||||
|
||||
|
||||
Joining the Development Team
|
||||
----------------------------
|
||||
|
||||
|
||||
29
appveyor.yml
29
appveyor.yml
@@ -1,30 +1,29 @@
|
||||
environment:
|
||||
matrix:
|
||||
- TOXENV: "linting"
|
||||
PYTEST_NO_COVERAGE: "1"
|
||||
- TOXENV: "py27"
|
||||
- TOXENV: "py34"
|
||||
- TOXENV: "py35"
|
||||
- TOXENV: "py36"
|
||||
- TOXENV: "py37"
|
||||
PYTEST_NO_COVERAGE: "1"
|
||||
- TOXENV: "linting,docs,doctesting"
|
||||
- TOXENV: "py36"
|
||||
- TOXENV: "py35"
|
||||
- TOXENV: "py34"
|
||||
- TOXENV: "pypy"
|
||||
PYTEST_NO_COVERAGE: "1"
|
||||
- TOXENV: "py27-xdist"
|
||||
- TOXENV: "py27-trial"
|
||||
- TOXENV: "py27-numpy"
|
||||
# Specialized factors for py27.
|
||||
- TOXENV: "py27-trial,py27-numpy,py27-nobyte"
|
||||
- TOXENV: "py27-pluggymaster"
|
||||
PYTEST_NO_COVERAGE: "1"
|
||||
- TOXENV: "py36-xdist"
|
||||
- TOXENV: "py36-trial"
|
||||
- TOXENV: "py36-numpy"
|
||||
- TOXENV: "py27-xdist"
|
||||
# Specialized factors for py36.
|
||||
- TOXENV: "py36-trial,py36-numpy"
|
||||
- TOXENV: "py36-pluggymaster"
|
||||
PYTEST_NO_COVERAGE: "1"
|
||||
- TOXENV: "py27-nobyte"
|
||||
- TOXENV: "doctesting"
|
||||
- TOXENV: "py36-freeze"
|
||||
PYTEST_NO_COVERAGE: "1"
|
||||
- TOXENV: "docs"
|
||||
PYTEST_NO_COVERAGE: "1"
|
||||
- TOXENV: "py36-xdist"
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
|
||||
install:
|
||||
- echo Installed Pythons
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
|
||||
|
||||
# 10000 iterations, just for relative comparison
|
||||
# 2.7.5 3.3.2
|
||||
# FilesCompleter 75.1109 69.2116
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
||||
@@ -14,7 +14,8 @@ Each file should be named like ``<ISSUE>.<TYPE>.rst``, where
|
||||
* ``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.
|
||||
* ``deprecation``: feature deprecation.
|
||||
* ``removal``: feature removal.
|
||||
* ``vendor``: changes in packages vendored in pytest.
|
||||
* ``trivial``: fixing a small typo or internal change that might be noteworthy.
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ Release announcements
|
||||
:maxdepth: 2
|
||||
|
||||
|
||||
release-3.9.1
|
||||
release-3.9.0
|
||||
release-3.8.2
|
||||
release-3.8.1
|
||||
release-3.8.0
|
||||
|
||||
@@ -20,8 +20,7 @@ Thanks to all who contributed to this release, among them:
|
||||
* Ondřej Súkup
|
||||
* Ronny Pfannschmidt
|
||||
* T.E.A de Souza
|
||||
* Victor
|
||||
* victor
|
||||
* Victor Maryama
|
||||
|
||||
|
||||
Happy testing,
|
||||
|
||||
@@ -22,10 +22,9 @@ Thanks to all who contributed to this release, among them:
|
||||
* Ronny Pfannschmidt
|
||||
* Sankt Petersbug
|
||||
* Tyler Richard
|
||||
* Victor
|
||||
* Victor Maryama
|
||||
* Vlad Shcherbina
|
||||
* turturica
|
||||
* victor
|
||||
* wim glenn
|
||||
|
||||
|
||||
|
||||
43
doc/en/announce/release-3.9.0.rst
Normal file
43
doc/en/announce/release-3.9.0.rst
Normal file
@@ -0,0 +1,43 @@
|
||||
pytest-3.9.0
|
||||
=======================================
|
||||
|
||||
The pytest team is proud to announce the 3.9.0 release!
|
||||
|
||||
pytest is a mature Python testing tool with more than a 2000 tests
|
||||
against itself, passing on many different interpreters and platforms.
|
||||
|
||||
This release contains a number of bugs fixes and improvements, so users are encouraged
|
||||
to take a look at the CHANGELOG:
|
||||
|
||||
https://docs.pytest.org/en/latest/changelog.html
|
||||
|
||||
For complete documentation, please visit:
|
||||
|
||||
https://docs.pytest.org/en/latest/
|
||||
|
||||
As usual, you can upgrade from pypi via:
|
||||
|
||||
pip install -U pytest
|
||||
|
||||
Thanks to all who contributed to this release, among them:
|
||||
|
||||
* Andrea Cimatoribus
|
||||
* Ankit Goel
|
||||
* Anthony Sottile
|
||||
* Ben Eyal
|
||||
* Bruno Oliveira
|
||||
* Daniel Hahler
|
||||
* Jeffrey Rackauckas
|
||||
* Jose Carlos Menezes
|
||||
* Kyle Altendorf
|
||||
* Niklas JQ
|
||||
* Palash Chatterjee
|
||||
* Ronny Pfannschmidt
|
||||
* Thomas Hess
|
||||
* Thomas Hisch
|
||||
* Tomer Keren
|
||||
* Victor Maryama
|
||||
|
||||
|
||||
Happy testing,
|
||||
The Pytest Development Team
|
||||
20
doc/en/announce/release-3.9.1.rst
Normal file
20
doc/en/announce/release-3.9.1.rst
Normal file
@@ -0,0 +1,20 @@
|
||||
pytest-3.9.1
|
||||
=======================================
|
||||
|
||||
pytest 3.9.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 https://docs.pytest.org/en/latest/changelog.html.
|
||||
|
||||
Thanks to all who contributed to this release, among them:
|
||||
|
||||
* Bruno Oliveira
|
||||
* Ronny Pfannschmidt
|
||||
* Thomas Hisch
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
||||
@@ -104,7 +104,9 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||
See http://docs.python.org/library/warnings.html for information
|
||||
on warning categories.
|
||||
tmpdir_factory
|
||||
Return a TempdirFactory instance for the test session.
|
||||
Return a :class:`_pytest.tmpdir.TempdirFactory` instance for the test session.
|
||||
tmp_path_factory
|
||||
Return a :class:`_pytest.tmpdir.TempPathFactory` instance for the test session.
|
||||
tmpdir
|
||||
Return a temporary directory path object
|
||||
which is unique to each test function invocation,
|
||||
@@ -113,6 +115,16 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||
path object.
|
||||
|
||||
.. _`py.path.local`: https://py.readthedocs.io/en/latest/path.html
|
||||
tmp_path
|
||||
Return a temporary directory path object
|
||||
which is unique to each test function invocation,
|
||||
created as a sub directory of the base temporary
|
||||
directory. The returned object is a :class:`pathlib.Path`
|
||||
object.
|
||||
|
||||
.. note::
|
||||
|
||||
in python < 3.6 this is a pathlib2.Path
|
||||
|
||||
no tests ran in 0.12 seconds
|
||||
|
||||
|
||||
@@ -14,6 +14,67 @@ Below is a complete list of all pytest features which are considered deprecated.
|
||||
:class:`_pytest.warning_types.PytestWarning` or subclasses, which can be filtered using
|
||||
:ref:`standard warning filters <warnings>`.
|
||||
|
||||
Internal classes accessed through ``Node``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 3.9
|
||||
|
||||
Access of ``Module``, ``Function``, ``Class``, ``Instance``, ``File`` and ``Item`` through ``Node`` instances now issue
|
||||
this warning::
|
||||
|
||||
usage of Function.Module is deprecated, please use pytest.Module instead
|
||||
|
||||
Users should just ``import pytest`` and access those objects using the ``pytest`` module.
|
||||
|
||||
This has been documented as deprecated for years, but only now we are actually emitting deprecation warnings.
|
||||
|
||||
``cached_setup``
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 3.9
|
||||
|
||||
``request.cached_setup`` was the precursor of the setup/teardown mechanism available to fixtures.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.fixture
|
||||
def db_session():
|
||||
return request.cached_setup(
|
||||
setup=Session.create, teardown=lambda session: session.close(), scope="module"
|
||||
)
|
||||
|
||||
This should be updated to make use of standard fixture mechanisms:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def db_session():
|
||||
session = Session.create()
|
||||
yield session
|
||||
session.close()
|
||||
|
||||
|
||||
You can consult `funcarg comparision section in the docs <https://docs.pytest.org/en/latest/funcarg_compare.html>`_ for
|
||||
more information.
|
||||
|
||||
This has been documented as deprecated for years, but only now we are actually emitting deprecation warnings.
|
||||
|
||||
|
||||
Using ``Class`` in custom Collectors
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 3.9
|
||||
|
||||
Using objects named ``"Class"`` as a way to customize the type of nodes that are collected in ``Collector``
|
||||
subclasses has been deprecated. Users instead should use ``pytest_collect_make_item`` to customize node types during
|
||||
collection.
|
||||
|
||||
This issue should affect only advanced plugins who create new collection types, so if you see this warning
|
||||
message please contact the authors so they can change the code.
|
||||
|
||||
|
||||
``Config.warn`` and ``Node.warn``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -243,7 +304,7 @@ This form of test function doesn't support fixtures properly, and users should s
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.mark.parametrize("x, y", [(2, 4), (3, 9)])
|
||||
def test_squared():
|
||||
def test_squared(x, y):
|
||||
assert x ** x == y
|
||||
|
||||
|
||||
|
||||
@@ -247,7 +247,7 @@ class TestCustomAssertMsg(object):
|
||||
b = 2
|
||||
assert (
|
||||
A.a == b
|
||||
), "A.a appears not to be b\n" "or does not appear to be b\none of those"
|
||||
), "A.a appears not to be b\nor does not appear to be b\none of those"
|
||||
|
||||
def test_custom_repr(self):
|
||||
class JSON(object):
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
hello = "world"
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import py
|
||||
|
||||
failure_demo = py.path.local(__file__).dirpath("failure_demo.py")
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
||||
@@ -582,7 +582,7 @@ get on the terminal - we are working on that)::
|
||||
b = 2
|
||||
> assert (
|
||||
A.a == b
|
||||
), "A.a appears not to be b\n" "or does not appear to be b\none of those"
|
||||
), "A.a appears not to be b\nor does not appear to be b\none of those"
|
||||
E AssertionError: A.a appears not to be b
|
||||
E or does not appear to be b
|
||||
E one of those
|
||||
|
||||
@@ -574,7 +574,7 @@ We can run this::
|
||||
file $REGENDOC_TMPDIR/b/test_error.py, line 1
|
||||
def test_root(db): # no db here, will error out
|
||||
E fixture 'db' not found
|
||||
> available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, monkeypatch, pytestconfig, record_property, record_xml_attribute, record_xml_property, recwarn, tmpdir, tmpdir_factory
|
||||
> available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, monkeypatch, pytestconfig, record_property, record_xml_attribute, record_xml_property, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory
|
||||
> use 'pytest --fixtures [testpath]' for help on them.
|
||||
|
||||
$REGENDOC_TMPDIR/b/test_error.py:1
|
||||
@@ -852,6 +852,8 @@ In that order.
|
||||
can be changed between releases (even bug fixes) so it shouldn't be relied on for scripting
|
||||
or automation.
|
||||
|
||||
.. _freezing-pytest:
|
||||
|
||||
Freezing pytest
|
||||
---------------
|
||||
|
||||
|
||||
@@ -259,6 +259,11 @@ instance, you can simply declare it:
|
||||
|
||||
Finally, the ``class`` scope will invoke the fixture once per test *class*.
|
||||
|
||||
.. note::
|
||||
|
||||
Pytest will only cache one instance of a fixture at a time.
|
||||
This means that when using a parametrized fixture, pytest may invoke a fixture more than once in the given scope.
|
||||
|
||||
|
||||
``package`` scope (experimental)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -175,3 +175,13 @@ Previous to version 2.4 to set a break point in code one needed to use ``pytest.
|
||||
This is no longer needed and one can use the native ``import pdb;pdb.set_trace()`` call directly.
|
||||
|
||||
For more details see :ref:`breakpoints`.
|
||||
|
||||
"compat" properties
|
||||
-------------------
|
||||
|
||||
.. deprecated:: 3.9
|
||||
|
||||
Access of ``Module``, ``Function``, ``Class``, ``Instance``, ``File`` and ``Item`` through ``Node`` instances have long
|
||||
been documented as deprecated, but started to emit warnings from pytest ``3.9`` and onward.
|
||||
|
||||
Users should just ``import pytest`` and access those objects using the ``pytest`` module.
|
||||
|
||||
@@ -69,17 +69,15 @@ You may also discover more plugins through a `pytest- pypi.python.org search`_.
|
||||
Requiring/Loading plugins in a test module or conftest file
|
||||
-----------------------------------------------------------
|
||||
|
||||
You can require plugins in a test module or a conftest file like this::
|
||||
You can require plugins in a test module or a conftest file like this:
|
||||
|
||||
pytest_plugins = "myapp.testsupport.myplugin",
|
||||
.. code-block:: python
|
||||
|
||||
pytest_plugins = ("myapp.testsupport.myplugin",)
|
||||
|
||||
When the test module or conftest plugin is loaded the specified plugins
|
||||
will be loaded as well.
|
||||
|
||||
pytest_plugins = "myapp.testsupport.myplugin"
|
||||
|
||||
which will import the specified module as a ``pytest`` plugin.
|
||||
|
||||
.. note::
|
||||
Requiring plugins using a ``pytest_plugins`` variable in non-root
|
||||
``conftest.py`` files is deprecated. See
|
||||
|
||||
@@ -84,6 +84,12 @@ pytest.warns
|
||||
.. autofunction:: pytest.warns(expected_warning: Exception, [match])
|
||||
:with:
|
||||
|
||||
pytest.freeze_includes
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
**Tutorial**: :ref:`freezing-pytest`.
|
||||
|
||||
.. autofunction:: pytest.freeze_includes
|
||||
|
||||
.. _`marks ref`:
|
||||
|
||||
@@ -172,7 +178,7 @@ Mark a test function as using the given fixture names.
|
||||
|
||||
.. warning::
|
||||
|
||||
This mark can be used with *test functions* only, having no affect when applied
|
||||
This mark has no effect when applied
|
||||
to a **fixture** function.
|
||||
|
||||
.. py:function:: pytest.mark.usefixtures(*names)
|
||||
@@ -976,6 +982,7 @@ passed multiple times. The expected format is ``name=value``. For example::
|
||||
|
||||
* ``skip`` skips tests with an empty parameterset (default)
|
||||
* ``xfail`` marks tests with an empty parameterset as xfail(run=False)
|
||||
* ``fail_at_collect`` raises an exception if parametrize collects an empty parameter set
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
|
||||
@@ -5,6 +5,76 @@
|
||||
Temporary directories and files
|
||||
================================================
|
||||
|
||||
The ``tmp_path`` fixture
|
||||
------------------------
|
||||
|
||||
.. versionadded:: 3.9
|
||||
|
||||
|
||||
You can use the ``tmpdir`` fixture which will
|
||||
provide a temporary directory unique to the test invocation,
|
||||
created in the `base temporary directory`_.
|
||||
|
||||
``tmpdir`` is a ``pathlib/pathlib2.Path`` object. Here is an example test usage:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_tmp_path.py
|
||||
import os
|
||||
|
||||
CONTENT = u"content"
|
||||
|
||||
|
||||
def test_create_file(tmp_path):
|
||||
d = tmp_path / "sub"
|
||||
d.mkdir()
|
||||
p = d / "hello.txt"
|
||||
p.write_text(CONTENT)
|
||||
assert p.read_text() == CONTENT
|
||||
assert len(list(tmp_path.iterdir())) == 1
|
||||
assert 0
|
||||
|
||||
Running this would result in a passed test except for the last
|
||||
``assert 0`` line which we use to look at values::
|
||||
|
||||
$ pytest test_tmp_path.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 1 item
|
||||
|
||||
test_tmp_path.py F [100%]
|
||||
|
||||
================================= FAILURES =================================
|
||||
_____________________________ test_create_file _____________________________
|
||||
|
||||
tmp_path = PosixPath('PYTEST_TMPDIR/test_create_file0')
|
||||
|
||||
def test_create_file(tmp_path):
|
||||
d = tmp_path / "sub"
|
||||
d.mkdir()
|
||||
p = d / "hello.txt"
|
||||
p.write_text(CONTENT)
|
||||
assert p.read_text() == CONTENT
|
||||
assert len(list(tmp_path.iterdir())) == 1
|
||||
> assert 0
|
||||
E assert 0
|
||||
|
||||
test_tmp_path.py:13: AssertionError
|
||||
========================= 1 failed in 0.12 seconds =========================
|
||||
|
||||
The ``tmp_path_factory`` fixture
|
||||
--------------------------------
|
||||
|
||||
.. versionadded:: 3.9
|
||||
|
||||
|
||||
The ``tmp_path_facotry`` is a session-scoped fixture which can be used
|
||||
to create arbitrary temporary directories from any other fixture or test.
|
||||
|
||||
its intended to replace ``tmpdir_factory`` and returns :class:`pathlib.Path` instances.
|
||||
|
||||
|
||||
The 'tmpdir' fixture
|
||||
--------------------
|
||||
|
||||
|
||||
@@ -22,16 +22,15 @@ Almost all ``unittest`` features are supported:
|
||||
|
||||
* ``@unittest.skip`` style decorators;
|
||||
* ``setUp/tearDown``;
|
||||
* ``setUpClass/tearDownClass()``;
|
||||
* ``setUpClass/tearDownClass``;
|
||||
* ``setUpModule/tearDownModule``;
|
||||
|
||||
.. _`load_tests protocol`: https://docs.python.org/3/library/unittest.html#load-tests-protocol
|
||||
.. _`setUpModule/tearDownModule`: https://docs.python.org/3/library/unittest.html#setupmodule-and-teardownmodule
|
||||
.. _`subtests`: https://docs.python.org/3/library/unittest.html#distinguishing-test-iterations-using-subtests
|
||||
|
||||
Up to this point pytest does not have support for the following features:
|
||||
|
||||
* `load_tests protocol`_;
|
||||
* `setUpModule/tearDownModule`_;
|
||||
* `subtests`_;
|
||||
|
||||
Benefits out of the box
|
||||
|
||||
@@ -269,6 +269,7 @@ To get a list of the slowest 10 test durations::
|
||||
|
||||
pytest --durations=10
|
||||
|
||||
By default, pytest will not show test durations that are too small (<0.01s) unless ``-vv`` is passed on the command-line.
|
||||
|
||||
Creating JUnitXML format files
|
||||
----------------------------------------------------
|
||||
|
||||
@@ -101,22 +101,28 @@ DeprecationWarning and PendingDeprecationWarning
|
||||
------------------------------------------------
|
||||
|
||||
.. versionadded:: 3.8
|
||||
.. versionchanged:: 3.9
|
||||
|
||||
By default pytest will display ``DeprecationWarning`` and ``PendingDeprecationWarning`` if no other warning filters
|
||||
are configured.
|
||||
By default pytest will display ``DeprecationWarning`` and ``PendingDeprecationWarning``.
|
||||
|
||||
To disable showing ``DeprecationWarning`` and ``PendingDeprecationWarning`` warnings, you might define any warnings
|
||||
filter either in the command-line or in the ini file, or you can use:
|
||||
Sometimes it is useful to hide some specific deprecation warnings that happen in code that you have no control over
|
||||
(such as third-party libraries), in which case you might use the standard warning filters options (ini or marks).
|
||||
For example:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[pytest]
|
||||
filterwarnings =
|
||||
ignore::DeprecationWarning
|
||||
ignore::PendingDeprecationWarning
|
||||
ignore:.*U.*mode is deprecated:DeprecationWarning
|
||||
|
||||
|
||||
.. note::
|
||||
This makes pytest more compliant with `PEP-0506 <https://www.python.org/dev/peps/pep-0565/#recommended-filter-settings-for-test-runners>`_ which suggests that those warnings should
|
||||
If warnings are configured at the interpreter level, using
|
||||
the `PYTHONWARNINGS <https://docs.python.org/3/using/cmdline.html#envvar-PYTHONWARNINGS>`_ environment variable or the
|
||||
``-W`` command-line option, pytest will not configure any filters by default.
|
||||
|
||||
.. note::
|
||||
This feature makes pytest more compliant with `PEP-0506 <https://www.python.org/dev/peps/pep-0565/#recommended-filter-settings-for-test-runners>`_ which suggests that those warnings should
|
||||
be shown by default by test runners, but pytest doesn't follow ``PEP-0506`` completely because resetting all
|
||||
warning filters like suggested in the PEP will break existing test suites that configure warning filters themselves
|
||||
by calling ``warnings.simplefilter`` (see issue `#2430 <https://github.com/pytest-dev/pytest/issues/2430>`_
|
||||
|
||||
@@ -420,9 +420,21 @@ additionally it is possible to copy examples for a example folder before running
|
||||
============================= warnings summary =============================
|
||||
$REGENDOC_TMPDIR/test_example.py:4: PytestExperimentalApiWarning: testdir.copy_example is an experimental api that may change over time
|
||||
testdir.copy_example("test_example.py")
|
||||
$PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:321: RemovedInPytest4Warning: usage of Session.Class is deprecated, please use pytest.Class instead
|
||||
return getattr(object, name, default)
|
||||
$PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:321: RemovedInPytest4Warning: usage of Session.File is deprecated, please use pytest.File instead
|
||||
return getattr(object, name, default)
|
||||
$PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:321: RemovedInPytest4Warning: usage of Session.Function is deprecated, please use pytest.Function instead
|
||||
return getattr(object, name, default)
|
||||
$PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:321: RemovedInPytest4Warning: usage of Session.Instance is deprecated, please use pytest.Instance instead
|
||||
return getattr(object, name, default)
|
||||
$PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:321: RemovedInPytest4Warning: usage of Session.Item is deprecated, please use pytest.Item instead
|
||||
return getattr(object, name, default)
|
||||
$PYTHON_PREFIX/lib/python3.6/site-packages/_pytest/compat.py:321: RemovedInPytest4Warning: usage of Session.Module is deprecated, please use pytest.Module instead
|
||||
return getattr(object, name, default)
|
||||
|
||||
-- Docs: https://docs.pytest.org/en/latest/warnings.html
|
||||
=================== 2 passed, 1 warnings in 0.12 seconds ===================
|
||||
=================== 2 passed, 7 warnings in 0.12 seconds ===================
|
||||
|
||||
For more information about the result object that ``runpytest()`` returns, and
|
||||
the methods that it provides please check out the :py:class:`RunResult
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
[build-system]
|
||||
requires = [
|
||||
"setuptools",
|
||||
# sync with setup.py until we discard non-pep-517/518
|
||||
"setuptools>=30.3",
|
||||
"setuptools-scm",
|
||||
"wheel",
|
||||
]
|
||||
@@ -15,7 +16,12 @@ template = "changelog/_template.rst"
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
directory = "removal"
|
||||
name = "Deprecations and Removals"
|
||||
name = "Removals"
|
||||
showcontent = true
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
directory = "deprecation"
|
||||
name = "Deprecations"
|
||||
showcontent = true
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
|
||||
54
setup.cfg
54
setup.cfg
@@ -1,3 +1,55 @@
|
||||
[metadata]
|
||||
|
||||
name = pytest
|
||||
description = pytest: simple powerful testing with Python
|
||||
long_description = file: README.rst
|
||||
url = https://docs.pytest.org/en/latest/
|
||||
project_urls =
|
||||
Source=https://github.com/pytest-dev/pytest
|
||||
Tracker=https://github.com/pytest-dev/pytest/issues
|
||||
|
||||
author = Holger Krekel, Bruno Oliveira, Ronny Pfannschmidt, Floris Bruynooghe, Brianna Laugher, Florian Bruhin and others
|
||||
|
||||
license = MIT license
|
||||
license_file = LICENSE
|
||||
keywords = test, unittest
|
||||
classifiers =
|
||||
Development Status :: 6 - Mature
|
||||
Intended Audience :: Developers
|
||||
License :: OSI Approved :: MIT License
|
||||
Operating System :: POSIX
|
||||
Operating System :: Microsoft :: Windows
|
||||
Operating System :: MacOS :: MacOS X
|
||||
Topic :: Software Development :: Testing
|
||||
Topic :: Software Development :: Libraries
|
||||
Topic :: Utilities
|
||||
Programming Language :: Python :: 2
|
||||
Programming Language :: Python :: 2.7
|
||||
Programming Language :: Python :: 3
|
||||
Programming Language :: Python :: 3.4
|
||||
Programming Language :: Python :: 3.5
|
||||
Programming Language :: Python :: 3.6
|
||||
Programming Language :: Python :: 3.7
|
||||
platforms = unix, linux, osx, cygwin, win32
|
||||
|
||||
[options]
|
||||
zip_safe = no
|
||||
packages =
|
||||
_pytest
|
||||
_pytest.assertion
|
||||
_pytest._code
|
||||
_pytest.mark
|
||||
_pytest.config
|
||||
|
||||
py_modules = pytest
|
||||
python_requires = >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
|
||||
|
||||
|
||||
[options.entry_points]
|
||||
console_scripts =
|
||||
pytest=pytest:main
|
||||
py.test=pytest:main
|
||||
|
||||
[build_sphinx]
|
||||
source-dir = doc/en/
|
||||
build-dir = doc/build
|
||||
@@ -13,8 +65,6 @@ universal = 1
|
||||
ignore =
|
||||
_pytest/_version.py
|
||||
|
||||
[metadata]
|
||||
license_file = LICENSE
|
||||
|
||||
[devpi:upload]
|
||||
formats = sdist.tgz,bdist_wheel
|
||||
|
||||
130
setup.py
130
setup.py
@@ -1,126 +1,34 @@
|
||||
import os
|
||||
import sys
|
||||
import setuptools
|
||||
import pkg_resources
|
||||
from setuptools import setup
|
||||
|
||||
classifiers = [
|
||||
"Development Status :: 6 - Mature",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: POSIX",
|
||||
"Operating System :: Microsoft :: Windows",
|
||||
"Operating System :: MacOS :: MacOS X",
|
||||
"Topic :: Software Development :: Testing",
|
||||
"Topic :: Software Development :: Libraries",
|
||||
"Topic :: Utilities",
|
||||
] + [
|
||||
("Programming Language :: Python :: %s" % x)
|
||||
for x in "2 2.7 3 3.4 3.5 3.6 3.7".split()
|
||||
|
||||
# TODO: if py gets upgrade to >=1.6,
|
||||
# remove _width_of_current_line in terminal.py
|
||||
INSTALL_REQUIRES = [
|
||||
"py>=1.5.0",
|
||||
"six>=1.10.0",
|
||||
"setuptools",
|
||||
"attrs>=17.4.0",
|
||||
"more-itertools>=4.0.0",
|
||||
"atomicwrites>=1.0",
|
||||
'funcsigs;python_version<"3.0"',
|
||||
'pathlib2>=2.2.0;python_version<"3.6"',
|
||||
'colorama;sys_platform=="win32"',
|
||||
]
|
||||
|
||||
with open("README.rst") as fd:
|
||||
long_description = fd.read()
|
||||
|
||||
|
||||
def get_environment_marker_support_level():
|
||||
"""
|
||||
Tests how well setuptools supports PEP-426 environment marker.
|
||||
|
||||
The first known release to support it is 0.7 (and the earliest on PyPI seems to be 0.7.2
|
||||
so we're using that), see: https://setuptools.readthedocs.io/en/latest/history.html#id350
|
||||
|
||||
The support is later enhanced to allow direct conditional inclusions inside install_requires,
|
||||
which is now recommended by setuptools. It first appeared in 36.2.0, went broken with 36.2.1, and
|
||||
again worked since 36.2.2, so we're using that. See:
|
||||
https://setuptools.readthedocs.io/en/latest/history.html#v36-2-2
|
||||
https://github.com/pypa/setuptools/issues/1099
|
||||
|
||||
References:
|
||||
|
||||
* https://wheel.readthedocs.io/en/latest/index.html#defining-conditional-dependencies
|
||||
* https://www.python.org/dev/peps/pep-0426/#environment-markers
|
||||
* https://setuptools.readthedocs.io/en/latest/setuptools.html#declaring-platform-specific-dependencies
|
||||
"""
|
||||
try:
|
||||
version = pkg_resources.parse_version(setuptools.__version__)
|
||||
if version >= pkg_resources.parse_version("36.2.2"):
|
||||
return 2
|
||||
if version >= pkg_resources.parse_version("0.7.2"):
|
||||
return 1
|
||||
except Exception as exc:
|
||||
sys.stderr.write("Could not test setuptool's version: %s\n" % exc)
|
||||
|
||||
# as of testing on 2018-05-26 fedora was on version 37* and debian was on version 33+
|
||||
# we should consider erroring on those
|
||||
return 0
|
||||
# if _PYTEST_SETUP_SKIP_PLUGGY_DEP is set, skip installing pluggy;
|
||||
# used by tox.ini to test with pluggy master
|
||||
if "_PYTEST_SETUP_SKIP_PLUGGY_DEP" not in os.environ:
|
||||
INSTALL_REQUIRES.append("pluggy>=0.7")
|
||||
|
||||
|
||||
def main():
|
||||
extras_require = {}
|
||||
install_requires = [
|
||||
"py>=1.5.0", # if py gets upgrade to >=1.6, remove _width_of_current_line in terminal.py
|
||||
"six>=1.10.0",
|
||||
"setuptools",
|
||||
"attrs>=17.4.0",
|
||||
"more-itertools>=4.0.0",
|
||||
"atomicwrites>=1.0",
|
||||
]
|
||||
# if _PYTEST_SETUP_SKIP_PLUGGY_DEP is set, skip installing pluggy;
|
||||
# used by tox.ini to test with pluggy master
|
||||
if "_PYTEST_SETUP_SKIP_PLUGGY_DEP" not in os.environ:
|
||||
install_requires.append("pluggy>=0.7")
|
||||
environment_marker_support_level = get_environment_marker_support_level()
|
||||
if environment_marker_support_level >= 2:
|
||||
install_requires.append('funcsigs;python_version<"3.0"')
|
||||
install_requires.append('pathlib2>=2.2.0;python_version<"3.6"')
|
||||
install_requires.append('colorama;sys_platform=="win32"')
|
||||
elif environment_marker_support_level == 1:
|
||||
extras_require[':python_version<"3.0"'] = ["funcsigs"]
|
||||
extras_require[':python_version<"3.6"'] = ["pathlib2>=2.2.0"]
|
||||
extras_require[':sys_platform=="win32"'] = ["colorama"]
|
||||
else:
|
||||
if sys.platform == "win32":
|
||||
install_requires.append("colorama")
|
||||
if sys.version_info < (3, 0):
|
||||
install_requires.append("funcsigs")
|
||||
if sys.version_info < (3, 6):
|
||||
install_requires.append("pathlib2>=2.2.0")
|
||||
|
||||
setup(
|
||||
name="pytest",
|
||||
description="pytest: simple powerful testing with Python",
|
||||
long_description=long_description,
|
||||
use_scm_version={"write_to": "src/_pytest/_version.py"},
|
||||
url="https://docs.pytest.org/en/latest/",
|
||||
project_urls={
|
||||
"Source": "https://github.com/pytest-dev/pytest",
|
||||
"Tracker": "https://github.com/pytest-dev/pytest/issues",
|
||||
},
|
||||
license="MIT license",
|
||||
platforms=["unix", "linux", "osx", "cygwin", "win32"],
|
||||
author=(
|
||||
"Holger Krekel, Bruno Oliveira, Ronny Pfannschmidt, "
|
||||
"Floris Bruynooghe, Brianna Laugher, Florian Bruhin and others"
|
||||
),
|
||||
entry_points={"console_scripts": ["pytest=pytest:main", "py.test=pytest:main"]},
|
||||
classifiers=classifiers,
|
||||
keywords="test unittest",
|
||||
# the following should be enabled for release
|
||||
setup_requires=["setuptools-scm"],
|
||||
setup_requires=["setuptools-scm", "setuptools>=30.3"],
|
||||
package_dir={"": "src"},
|
||||
python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*",
|
||||
install_requires=install_requires,
|
||||
extras_require=extras_require,
|
||||
packages=[
|
||||
"_pytest",
|
||||
"_pytest.assertion",
|
||||
"_pytest._code",
|
||||
"_pytest.mark",
|
||||
"_pytest.config",
|
||||
],
|
||||
py_modules=["pytest"],
|
||||
zip_safe=False,
|
||||
install_requires=INSTALL_REQUIRES,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
"""allow bash-completion for argparse with argcomplete if installed
|
||||
needs argcomplete>=0.5.6 for python 3.2/3.3 (older versions fail
|
||||
to find the magic string, so _ARGCOMPLETE env. var is never set, and
|
||||
|
||||
@@ -4,6 +4,7 @@ from .code import Code # noqa
|
||||
from .code import ExceptionInfo # noqa
|
||||
from .code import Frame # noqa
|
||||
from .code import Traceback # noqa
|
||||
from .code import filter_traceback # noqa
|
||||
from .code import getrawcode # noqa
|
||||
from .source import Source # noqa
|
||||
from .source import compile_ as compile # noqa
|
||||
|
||||
@@ -6,8 +6,10 @@ import traceback
|
||||
from inspect import CO_VARARGS, CO_VARKEYWORDS
|
||||
|
||||
import attr
|
||||
import pluggy
|
||||
import re
|
||||
from weakref import ref
|
||||
import _pytest
|
||||
from _pytest.compat import _PY2, _PY3, PY35, safe_str
|
||||
from six import text_type
|
||||
import py
|
||||
@@ -451,13 +453,35 @@ class ExceptionInfo(object):
|
||||
tbfilter=True,
|
||||
funcargs=False,
|
||||
truncate_locals=True,
|
||||
chain=True,
|
||||
):
|
||||
""" return str()able representation of this exception info.
|
||||
showlocals: show locals per traceback entry
|
||||
style: long|short|no|native traceback style
|
||||
tbfilter: hide entries (where __tracebackhide__ is true)
|
||||
"""
|
||||
Return str()able representation of this exception info.
|
||||
|
||||
in case of style==native, tbfilter and showlocals is ignored.
|
||||
:param bool showlocals:
|
||||
Show locals per traceback entry.
|
||||
Ignored if ``style=="native"``.
|
||||
|
||||
:param str style: long|short|no|native traceback style
|
||||
|
||||
:param bool abspath:
|
||||
If paths should be changed to absolute or left unchanged.
|
||||
|
||||
:param bool tbfilter:
|
||||
Hide entries that contain a local variable ``__tracebackhide__==True``.
|
||||
Ignored if ``style=="native"``.
|
||||
|
||||
:param bool funcargs:
|
||||
Show fixtures ("funcargs" for legacy purposes) per traceback entry.
|
||||
|
||||
:param bool truncate_locals:
|
||||
With ``showlocals==True``, make sure locals can be safely represented as strings.
|
||||
|
||||
:param bool chain: if chained exceptions in Python 3 should be shown.
|
||||
|
||||
.. versionchanged:: 3.9
|
||||
|
||||
Added the ``chain`` parameter.
|
||||
"""
|
||||
if style == "native":
|
||||
return ReprExceptionInfo(
|
||||
@@ -476,6 +500,7 @@ class ExceptionInfo(object):
|
||||
tbfilter=tbfilter,
|
||||
funcargs=funcargs,
|
||||
truncate_locals=truncate_locals,
|
||||
chain=chain,
|
||||
)
|
||||
return fmt.repr_excinfo(self)
|
||||
|
||||
@@ -516,6 +541,7 @@ class FormattedExcinfo(object):
|
||||
tbfilter = attr.ib(default=True)
|
||||
funcargs = attr.ib(default=False)
|
||||
truncate_locals = attr.ib(default=True)
|
||||
chain = attr.ib(default=True)
|
||||
astcache = attr.ib(default=attr.Factory(dict), init=False, repr=False)
|
||||
|
||||
def _getindent(self, source):
|
||||
@@ -735,7 +761,7 @@ class FormattedExcinfo(object):
|
||||
reprcrash = None
|
||||
|
||||
repr_chain += [(reprtraceback, reprcrash, descr)]
|
||||
if e.__cause__ is not None:
|
||||
if e.__cause__ is not None and self.chain:
|
||||
e = e.__cause__
|
||||
excinfo = (
|
||||
ExceptionInfo((type(e), e, e.__traceback__))
|
||||
@@ -743,7 +769,11 @@ class FormattedExcinfo(object):
|
||||
else None
|
||||
)
|
||||
descr = "The above exception was the direct cause of the following exception:"
|
||||
elif e.__context__ is not None and not e.__suppress_context__:
|
||||
elif (
|
||||
e.__context__ is not None
|
||||
and not e.__suppress_context__
|
||||
and self.chain
|
||||
):
|
||||
e = e.__context__
|
||||
excinfo = (
|
||||
ExceptionInfo((type(e), e, e.__traceback__))
|
||||
@@ -979,3 +1009,36 @@ else:
|
||||
return "maximum recursion depth exceeded" in str(excinfo.value)
|
||||
except UnicodeError:
|
||||
return False
|
||||
|
||||
|
||||
# relative paths that we use to filter traceback entries from appearing to the user;
|
||||
# see filter_traceback
|
||||
# note: if we need to add more paths than what we have now we should probably use a list
|
||||
# for better maintenance
|
||||
|
||||
_PLUGGY_DIR = py.path.local(pluggy.__file__.rstrip("oc"))
|
||||
# pluggy is either a package or a single module depending on the version
|
||||
if _PLUGGY_DIR.basename == "__init__.py":
|
||||
_PLUGGY_DIR = _PLUGGY_DIR.dirpath()
|
||||
_PYTEST_DIR = py.path.local(_pytest.__file__).dirpath()
|
||||
_PY_DIR = py.path.local(py.__file__).dirpath()
|
||||
|
||||
|
||||
def filter_traceback(entry):
|
||||
"""Return True if a TracebackEntry instance should be removed from tracebacks:
|
||||
* dynamically generated code (no code to show up for it);
|
||||
* internal traceback from pytest or its internal libraries, py and pluggy.
|
||||
"""
|
||||
# entry.path might sometimes return a str object when the entry
|
||||
# points to dynamically generated code
|
||||
# see https://bitbucket.org/pytest-dev/py/issues/71
|
||||
raw_filename = entry.frame.code.raw.co_filename
|
||||
is_generated = "<" in raw_filename and ">" in raw_filename
|
||||
if is_generated:
|
||||
return False
|
||||
# entry.path might point to a non-existing file, in which case it will
|
||||
# also return a str object. see #1133
|
||||
p = py.path.local(entry.path)
|
||||
return (
|
||||
not p.relto(_PLUGGY_DIR) and not p.relto(_PYTEST_DIR) and not p.relto(_PY_DIR)
|
||||
)
|
||||
|
||||
@@ -7,6 +7,7 @@ import linecache
|
||||
import sys
|
||||
import six
|
||||
import inspect
|
||||
import textwrap
|
||||
import tokenize
|
||||
import py
|
||||
|
||||
@@ -23,7 +24,6 @@ class Source(object):
|
||||
def __init__(self, *parts, **kwargs):
|
||||
self.lines = lines = []
|
||||
de = kwargs.get("deindent", True)
|
||||
rstrip = kwargs.get("rstrip", True)
|
||||
for part in parts:
|
||||
if not part:
|
||||
partlines = []
|
||||
@@ -33,11 +33,6 @@ class Source(object):
|
||||
partlines = [x.rstrip("\n") for x in part]
|
||||
elif isinstance(part, six.string_types):
|
||||
partlines = part.split("\n")
|
||||
if rstrip:
|
||||
while partlines:
|
||||
if partlines[-1].strip():
|
||||
break
|
||||
partlines.pop()
|
||||
else:
|
||||
partlines = getsource(part, deindent=de).lines
|
||||
if de:
|
||||
@@ -115,17 +110,10 @@ class Source(object):
|
||||
ast, start, end = getstatementrange_ast(lineno, self)
|
||||
return start, end
|
||||
|
||||
def deindent(self, offset=None):
|
||||
""" return a new source object deindented by offset.
|
||||
If offset is None then guess an indentation offset from
|
||||
the first non-blank line. Subsequent lines which have a
|
||||
lower indentation offset will be copied verbatim as
|
||||
they are assumed to be part of multilines.
|
||||
"""
|
||||
# XXX maybe use the tokenizer to properly handle multiline
|
||||
# strings etc.pp?
|
||||
def deindent(self):
|
||||
"""return a new source object deindented."""
|
||||
newsource = Source()
|
||||
newsource.lines[:] = deindent(self.lines, offset)
|
||||
newsource.lines[:] = deindent(self.lines)
|
||||
return newsource
|
||||
|
||||
def isparseable(self, deindent=True):
|
||||
@@ -268,47 +256,8 @@ def getsource(obj, **kwargs):
|
||||
return Source(strsrc, **kwargs)
|
||||
|
||||
|
||||
def deindent(lines, offset=None):
|
||||
if offset is None:
|
||||
for line in lines:
|
||||
line = line.expandtabs()
|
||||
s = line.lstrip()
|
||||
if s:
|
||||
offset = len(line) - len(s)
|
||||
break
|
||||
else:
|
||||
offset = 0
|
||||
if offset == 0:
|
||||
return list(lines)
|
||||
newlines = []
|
||||
|
||||
def readline_generator(lines):
|
||||
for line in lines:
|
||||
yield line + "\n"
|
||||
|
||||
it = readline_generator(lines)
|
||||
|
||||
try:
|
||||
for _, _, (sline, _), (eline, _), _ in tokenize.generate_tokens(
|
||||
lambda: next(it)
|
||||
):
|
||||
if sline > len(lines):
|
||||
break # End of input reached
|
||||
if sline > len(newlines):
|
||||
line = lines[sline - 1].expandtabs()
|
||||
if line.lstrip() and line[:offset].isspace():
|
||||
line = line[offset:] # Deindent
|
||||
newlines.append(line)
|
||||
|
||||
for i in range(sline, eline):
|
||||
# Don't deindent continuing lines of
|
||||
# multiline tokens (i.e. multiline strings)
|
||||
newlines.append(lines[i])
|
||||
except (IndentationError, tokenize.TokenError):
|
||||
pass
|
||||
# Add any lines we didn't see. E.g. if an exception was raised.
|
||||
newlines.extend(lines[len(newlines) :])
|
||||
return newlines
|
||||
def deindent(lines):
|
||||
return textwrap.dedent("\n".join(lines)).splitlines()
|
||||
|
||||
|
||||
def get_statement_startend2(lineno, node):
|
||||
|
||||
@@ -17,8 +17,9 @@ import atomicwrites
|
||||
import py
|
||||
|
||||
from _pytest.assertion import util
|
||||
from _pytest.compat import PurePath, spec_from_file_location
|
||||
from _pytest.paths import fnmatch_ex
|
||||
from _pytest.pathlib import PurePath
|
||||
from _pytest.compat import spec_from_file_location
|
||||
from _pytest.pathlib import fnmatch_ex
|
||||
|
||||
# pytest caches rewritten pycs in __pycache__.
|
||||
if hasattr(imp, "get_tag"):
|
||||
@@ -398,7 +399,7 @@ def _rewrite_test(config, fn):
|
||||
finally:
|
||||
del state._indecode
|
||||
try:
|
||||
tree = ast.parse(source)
|
||||
tree = ast.parse(source, filename=fn.strpath)
|
||||
except SyntaxError:
|
||||
# Let this pop up again in the real import.
|
||||
state.trace("failed to parse: %r" % (fn,))
|
||||
|
||||
@@ -199,7 +199,7 @@ def _diff_text(left, right, verbose=False):
|
||||
if i > 42:
|
||||
i -= 10 # Provide some context
|
||||
explanation = [
|
||||
u("Skipping %s identical leading " "characters in diff, use -v to show")
|
||||
u("Skipping %s identical leading characters in diff, use -v to show")
|
||||
% i
|
||||
]
|
||||
left = left[i:]
|
||||
|
||||
@@ -13,10 +13,9 @@ import attr
|
||||
|
||||
import pytest
|
||||
import json
|
||||
import shutil
|
||||
|
||||
from . import paths
|
||||
from .compat import _PY2 as PY2, Path
|
||||
from .compat import _PY2 as PY2
|
||||
from .pathlib import Path, resolve_from_str, rmtree
|
||||
|
||||
README_CONTENT = u"""\
|
||||
# pytest cache directory #
|
||||
@@ -39,13 +38,13 @@ class Cache(object):
|
||||
def for_config(cls, config):
|
||||
cachedir = cls.cache_dir_from_config(config)
|
||||
if config.getoption("cacheclear") and cachedir.exists():
|
||||
shutil.rmtree(str(cachedir))
|
||||
rmtree(cachedir, force=True)
|
||||
cachedir.mkdir()
|
||||
return cls(cachedir, config)
|
||||
|
||||
@staticmethod
|
||||
def cache_dir_from_config(config):
|
||||
return paths.resolve_from_str(config.getini("cache_dir"), config.rootdir)
|
||||
return resolve_from_str(config.getini("cache_dir"), config.rootdir)
|
||||
|
||||
def warn(self, fmt, **args):
|
||||
from _pytest.warnings import _issue_config_warning
|
||||
@@ -344,7 +343,7 @@ def cacheshow(config, session):
|
||||
key = valpath.relative_to(vdir)
|
||||
val = config.cache.get(key, dummy)
|
||||
if val is dummy:
|
||||
tw.line("%s contains unreadable content, " "will be ignored" % key)
|
||||
tw.line("%s contains unreadable content, will be ignored" % key)
|
||||
else:
|
||||
tw.line("%s contains:" % key)
|
||||
for line in pformat(val).splitlines():
|
||||
|
||||
@@ -654,7 +654,7 @@ class DontReadFromInput(six.Iterator):
|
||||
return self
|
||||
|
||||
def fileno(self):
|
||||
raise UnsupportedOperation("redirected stdin is pseudofile, " "has no fileno()")
|
||||
raise UnsupportedOperation("redirected stdin is pseudofile, has no fileno()")
|
||||
|
||||
def isatty(self):
|
||||
return False
|
||||
|
||||
@@ -23,8 +23,6 @@ except ImportError: # pragma: no cover
|
||||
# Only available in Python 3.4+ or as a backport
|
||||
enum = None
|
||||
|
||||
__all__ = ["Path", "PurePath"]
|
||||
|
||||
_PY3 = sys.version_info > (3, 0)
|
||||
_PY2 = not _PY3
|
||||
|
||||
@@ -41,11 +39,6 @@ PY35 = sys.version_info[:2] >= (3, 5)
|
||||
PY36 = sys.version_info[:2] >= (3, 6)
|
||||
MODULE_NOT_FOUND_ERROR = "ModuleNotFoundError" if PY36 else "ImportError"
|
||||
|
||||
if PY36:
|
||||
from pathlib import Path, PurePath
|
||||
else:
|
||||
from pathlib2 import Path, PurePath
|
||||
|
||||
|
||||
if _PY3:
|
||||
from collections.abc import MutableMapping as MappingMixin
|
||||
@@ -275,7 +268,7 @@ def get_real_func(obj):
|
||||
obj = new_obj
|
||||
else:
|
||||
raise ValueError(
|
||||
("could not find real function of {start}" "\nstopped at {current}").format(
|
||||
("could not find real function of {start}\nstopped at {current}").format(
|
||||
start=py.io.saferepr(start_obj), current=py.io.saferepr(obj)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -3,7 +3,6 @@ from __future__ import absolute_import, division, print_function
|
||||
import argparse
|
||||
import inspect
|
||||
import shlex
|
||||
import traceback
|
||||
import types
|
||||
import warnings
|
||||
import copy
|
||||
@@ -19,6 +18,7 @@ import _pytest._code
|
||||
import _pytest.hookspec # the extension point definitions
|
||||
import _pytest.assertion
|
||||
from pluggy import PluginManager, HookimplMarker, HookspecMarker
|
||||
from _pytest._code import ExceptionInfo, filter_traceback
|
||||
from _pytest.compat import safe_str
|
||||
from .exceptions import UsageError, PrintHelp
|
||||
from .findpaths import determine_setup, exists
|
||||
@@ -26,9 +26,6 @@ from .findpaths import determine_setup, exists
|
||||
hookimpl = HookimplMarker("pytest")
|
||||
hookspec = HookspecMarker("pytest")
|
||||
|
||||
# pytest startup
|
||||
#
|
||||
|
||||
|
||||
class ConftestImportFailure(Exception):
|
||||
def __init__(self, path, excinfo):
|
||||
@@ -36,12 +33,6 @@ class ConftestImportFailure(Exception):
|
||||
self.path = path
|
||||
self.excinfo = excinfo
|
||||
|
||||
def __str__(self):
|
||||
etype, evalue, etb = self.excinfo
|
||||
formatted = traceback.format_tb(etb)
|
||||
# The level of the tracebacks we want to print is hand crafted :(
|
||||
return repr(evalue) + "\n" + "".join(formatted[2:])
|
||||
|
||||
|
||||
def main(args=None, plugins=None):
|
||||
""" return exit code, after performing an in-process test run.
|
||||
@@ -57,10 +48,20 @@ def main(args=None, plugins=None):
|
||||
try:
|
||||
config = _prepareconfig(args, plugins)
|
||||
except ConftestImportFailure as e:
|
||||
exc_info = ExceptionInfo(e.excinfo)
|
||||
tw = py.io.TerminalWriter(sys.stderr)
|
||||
for line in traceback.format_exception(*e.excinfo):
|
||||
tw.line(
|
||||
"ImportError while loading conftest '{e.path}'.".format(e=e), red=True
|
||||
)
|
||||
exc_info.traceback = exc_info.traceback.filter(filter_traceback)
|
||||
exc_repr = (
|
||||
exc_info.getrepr(style="short", chain=False)
|
||||
if exc_info.traceback
|
||||
else exc_info.exconly()
|
||||
)
|
||||
formatted_tb = safe_str(exc_repr)
|
||||
for line in formatted_tb.splitlines():
|
||||
tw.line(line.rstrip(), red=True)
|
||||
tw.line("ERROR: could not load %s\n" % (e.path,), red=True)
|
||||
return 4
|
||||
else:
|
||||
try:
|
||||
@@ -378,25 +379,27 @@ class PytestPluginManager(PluginManager):
|
||||
def _getconftestmodules(self, path):
|
||||
if self._noconftest:
|
||||
return []
|
||||
try:
|
||||
return self._path2confmods[path]
|
||||
except KeyError:
|
||||
if path.isfile():
|
||||
clist = self._getconftestmodules(path.dirpath())
|
||||
else:
|
||||
# XXX these days we may rather want to use config.rootdir
|
||||
# and allow users to opt into looking into the rootdir parent
|
||||
# directories instead of requiring to specify confcutdir
|
||||
clist = []
|
||||
for parent in path.parts():
|
||||
if self._confcutdir and self._confcutdir.relto(parent):
|
||||
continue
|
||||
conftestpath = parent.join("conftest.py")
|
||||
if conftestpath.isfile():
|
||||
mod = self._importconftest(conftestpath)
|
||||
clist.append(mod)
|
||||
|
||||
self._path2confmods[path] = clist
|
||||
if path.isfile():
|
||||
directory = path.dirpath()
|
||||
else:
|
||||
directory = path
|
||||
try:
|
||||
return self._path2confmods[directory]
|
||||
except KeyError:
|
||||
# XXX these days we may rather want to use config.rootdir
|
||||
# and allow users to opt into looking into the rootdir parent
|
||||
# directories instead of requiring to specify confcutdir
|
||||
clist = []
|
||||
for parent in directory.parts():
|
||||
if self._confcutdir and self._confcutdir.relto(parent):
|
||||
continue
|
||||
conftestpath = parent.join("conftest.py")
|
||||
if conftestpath.isfile():
|
||||
mod = self._importconftest(conftestpath)
|
||||
clist.append(mod)
|
||||
|
||||
self._path2confmods[directory] = clist
|
||||
return clist
|
||||
|
||||
def _rget_with_confmod(self, name, path):
|
||||
|
||||
@@ -103,21 +103,18 @@ def determine_setup(inifile, args, rootdir_cmd_arg=None, config=None):
|
||||
if inifile:
|
||||
iniconfig = py.iniconfig.IniConfig(inifile)
|
||||
is_cfg_file = str(inifile).endswith(".cfg")
|
||||
# TODO: [pytest] section in *.cfg files is depricated. Need refactoring.
|
||||
sections = ["tool:pytest", "pytest"] if is_cfg_file else ["pytest"]
|
||||
for section in sections:
|
||||
try:
|
||||
inicfg = iniconfig[section]
|
||||
if is_cfg_file and section == "pytest" and config is not None:
|
||||
from _pytest.deprecated import CFG_PYTEST_SECTION
|
||||
from _pytest.warning_types import RemovedInPytest4Warning
|
||||
from _pytest.warnings import _issue_config_warning
|
||||
|
||||
# TODO: [pytest] section in *.cfg files is deprecated. Need refactoring once
|
||||
# the deprecation expires.
|
||||
_issue_config_warning(
|
||||
RemovedInPytest4Warning(
|
||||
CFG_PYTEST_SECTION.format(filename=str(inifile))
|
||||
),
|
||||
config,
|
||||
CFG_PYTEST_SECTION.format(filename=str(inifile)), config
|
||||
)
|
||||
break
|
||||
except KeyError:
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
""" interactive debugging with PDB, the Python Debugger. """
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
import os
|
||||
import pdb
|
||||
import sys
|
||||
import os
|
||||
from doctest import UnexpectedException
|
||||
|
||||
from _pytest import outcomes
|
||||
from _pytest.config import hookimpl
|
||||
|
||||
try:
|
||||
@@ -109,9 +111,6 @@ class PdbInvoke(object):
|
||||
_enter_pdb(node, call.excinfo, report)
|
||||
|
||||
def pytest_internalerror(self, excrepr, excinfo):
|
||||
for line in str(excrepr).split("\n"):
|
||||
sys.stderr.write("INTERNALERROR> %s\n" % line)
|
||||
sys.stderr.flush()
|
||||
tb = _postmortem_traceback(excinfo)
|
||||
post_mortem(tb)
|
||||
|
||||
@@ -164,8 +163,9 @@ def _enter_pdb(node, excinfo, rep):
|
||||
rep.toterminal(tw)
|
||||
tw.sep(">", "entering PDB")
|
||||
tb = _postmortem_traceback(excinfo)
|
||||
post_mortem(tb)
|
||||
rep._pdbshown = True
|
||||
if post_mortem(tb):
|
||||
outcomes.exit("Quitting debugger")
|
||||
return rep
|
||||
|
||||
|
||||
@@ -196,3 +196,4 @@ def post_mortem(t):
|
||||
p = Pdb()
|
||||
p.reset()
|
||||
p.interaction(None, t)
|
||||
return p.quitting
|
||||
|
||||
@@ -4,10 +4,15 @@ that is planned to be removed in the next pytest release.
|
||||
|
||||
Keeping it in a central location makes it easy to track what is deprecated and should
|
||||
be removed when the time comes.
|
||||
|
||||
All constants defined in this module should be either PytestWarning instances or UnformattedWarning
|
||||
in case of warnings which need to format their messages.
|
||||
"""
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
from _pytest.warning_types import RemovedInPytest4Warning
|
||||
|
||||
from _pytest.warning_types import UnformattedWarning, RemovedInPytest4Warning
|
||||
|
||||
|
||||
MAIN_STR_ARGS = RemovedInPytest4Warning(
|
||||
"passing a string to pytest.main() is deprecated, "
|
||||
@@ -18,25 +23,48 @@ YIELD_TESTS = RemovedInPytest4Warning(
|
||||
"yield tests are deprecated, and scheduled to be removed in pytest 4.0"
|
||||
)
|
||||
|
||||
FUNCARG_PREFIX = (
|
||||
CACHED_SETUP = RemovedInPytest4Warning(
|
||||
"cached_setup is deprecated and will be removed in a future release. "
|
||||
"Use standard fixture functions instead."
|
||||
)
|
||||
|
||||
COMPAT_PROPERTY = UnformattedWarning(
|
||||
RemovedInPytest4Warning,
|
||||
"usage of {owner}.{name} is deprecated, please use pytest.{name} instead",
|
||||
)
|
||||
|
||||
CUSTOM_CLASS = UnformattedWarning(
|
||||
RemovedInPytest4Warning,
|
||||
'use of special named "{name}" objects in collectors of type "{type_name}" to '
|
||||
"customize the created nodes is deprecated. "
|
||||
"Use pytest_pycollect_makeitem(...) to create custom "
|
||||
"collection nodes instead.",
|
||||
)
|
||||
|
||||
FUNCARG_PREFIX = UnformattedWarning(
|
||||
RemovedInPytest4Warning,
|
||||
'{name}: declaring fixtures using "pytest_funcarg__" prefix is deprecated '
|
||||
"and scheduled to be removed in pytest 4.0. "
|
||||
"Please remove the prefix and use the @pytest.fixture decorator instead."
|
||||
"Please remove the prefix and use the @pytest.fixture decorator instead.",
|
||||
)
|
||||
|
||||
FIXTURE_FUNCTION_CALL = (
|
||||
FIXTURE_FUNCTION_CALL = UnformattedWarning(
|
||||
RemovedInPytest4Warning,
|
||||
'Fixture "{name}" called directly. Fixtures are not meant to be called directly, '
|
||||
"are created automatically when test functions request them as parameters. "
|
||||
"See https://docs.pytest.org/en/latest/fixture.html for more information."
|
||||
"See https://docs.pytest.org/en/latest/fixture.html for more information.",
|
||||
)
|
||||
|
||||
CFG_PYTEST_SECTION = (
|
||||
"[pytest] section in {filename} files is deprecated, use [tool:pytest] instead."
|
||||
CFG_PYTEST_SECTION = UnformattedWarning(
|
||||
RemovedInPytest4Warning,
|
||||
"[pytest] section in {filename} files is deprecated, use [tool:pytest] instead.",
|
||||
)
|
||||
|
||||
GETFUNCARGVALUE = "getfuncargvalue is deprecated, use getfixturevalue"
|
||||
GETFUNCARGVALUE = RemovedInPytest4Warning(
|
||||
"getfuncargvalue is deprecated, use getfixturevalue"
|
||||
)
|
||||
|
||||
RESULT_LOG = (
|
||||
RESULT_LOG = RemovedInPytest4Warning(
|
||||
"--result-log is deprecated and scheduled for removal in pytest 4.0.\n"
|
||||
"See https://docs.pytest.org/en/latest/usage.html#creating-resultlog-format-files for more information."
|
||||
)
|
||||
@@ -64,7 +92,7 @@ RECORD_XML_PROPERTY = RemovedInPytest4Warning(
|
||||
)
|
||||
|
||||
COLLECTOR_MAKEITEM = RemovedInPytest4Warning(
|
||||
"pycollector makeitem was removed " "as it is an accidentially leaked internal api"
|
||||
"pycollector makeitem was removed as it is an accidentially leaked internal api"
|
||||
)
|
||||
|
||||
METAFUNC_ADD_CALL = RemovedInPytest4Warning(
|
||||
@@ -81,3 +109,8 @@ PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST = RemovedInPytest4Warning(
|
||||
PYTEST_NAMESPACE = RemovedInPytest4Warning(
|
||||
"pytest_namespace is deprecated and will be removed soon"
|
||||
)
|
||||
|
||||
PYTEST_ENSURETEMP = RemovedInPytest4Warning(
|
||||
"pytest/tmpdir_factory.ensuretemp is deprecated, \n"
|
||||
"please use the tmp_path fixture or tmp_path_factory.mktemp"
|
||||
)
|
||||
|
||||
@@ -32,7 +32,7 @@ from _pytest.compat import (
|
||||
get_real_method,
|
||||
_PytestWrapper,
|
||||
)
|
||||
from _pytest.deprecated import FIXTURE_FUNCTION_CALL, RemovedInPytest4Warning
|
||||
from _pytest.deprecated import FIXTURE_FUNCTION_CALL
|
||||
from _pytest.outcomes import fail, TEST_OUTCOME
|
||||
|
||||
FIXTURE_MSG = 'fixtures cannot have "pytest_funcarg__" prefix and be decorated with @pytest.fixture:\n{}'
|
||||
@@ -359,8 +359,10 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
||||
|
||||
@property
|
||||
def fixturenames(self):
|
||||
# backward incompatible note: now a readonly property
|
||||
return list(self._pyfuncitem._fixtureinfo.names_closure)
|
||||
"""names of all active fixtures in this request"""
|
||||
result = list(self._pyfuncitem._fixtureinfo.names_closure)
|
||||
result.extend(set(self._fixture_defs).difference(result))
|
||||
return result
|
||||
|
||||
@property
|
||||
def node(self):
|
||||
@@ -479,6 +481,9 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
||||
or ``session`` indicating the caching lifecycle of the resource.
|
||||
:arg extrakey: added to internal caching key of (funcargname, scope).
|
||||
"""
|
||||
from _pytest.deprecated import CACHED_SETUP
|
||||
|
||||
warnings.warn(CACHED_SETUP, stacklevel=2)
|
||||
if not hasattr(self.config, "_setupcache"):
|
||||
self.config._setupcache = {} # XXX weakref?
|
||||
cachekey = (self.fixturename, self._getscopeitem(scope), extrakey)
|
||||
@@ -512,7 +517,7 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
||||
""" Deprecated, use getfixturevalue. """
|
||||
from _pytest import deprecated
|
||||
|
||||
warnings.warn(deprecated.GETFUNCARGVALUE, DeprecationWarning, stacklevel=2)
|
||||
warnings.warn(deprecated.GETFUNCARGVALUE, stacklevel=2)
|
||||
return self.getfixturevalue(argname)
|
||||
|
||||
def _get_active_fixturedef(self, argname):
|
||||
@@ -562,7 +567,20 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
||||
except (AttributeError, ValueError):
|
||||
param = NOTSET
|
||||
param_index = 0
|
||||
if fixturedef.params is not None:
|
||||
has_params = fixturedef.params is not None
|
||||
fixtures_not_supported = getattr(funcitem, "nofuncargs", False)
|
||||
if has_params and fixtures_not_supported:
|
||||
msg = (
|
||||
"{name} does not support fixtures, maybe unittest.TestCase subclass?\n"
|
||||
"Node id: {nodeid}\n"
|
||||
"Function type: {typename}"
|
||||
).format(
|
||||
name=funcitem.name,
|
||||
nodeid=funcitem.nodeid,
|
||||
typename=type(funcitem).__name__,
|
||||
)
|
||||
fail(msg, pytrace=False)
|
||||
if has_params:
|
||||
frame = inspect.stack()[3]
|
||||
frameinfo = inspect.getframeinfo(frame[0])
|
||||
source_path = frameinfo.filename
|
||||
@@ -571,16 +589,18 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
||||
if source_path.relto(funcitem.config.rootdir):
|
||||
source_path = source_path.relto(funcitem.config.rootdir)
|
||||
msg = (
|
||||
"The requested fixture has no parameter defined for the "
|
||||
"current test.\n\nRequested fixture '{}' defined in:\n{}"
|
||||
"The requested fixture has no parameter defined for test:\n"
|
||||
" {}\n\n"
|
||||
"Requested fixture '{}' defined in:\n{}"
|
||||
"\n\nRequested here:\n{}:{}".format(
|
||||
funcitem.nodeid,
|
||||
fixturedef.argname,
|
||||
getlocation(fixturedef.func, funcitem.config.rootdir),
|
||||
source_path,
|
||||
source_lineno,
|
||||
)
|
||||
)
|
||||
fail(msg)
|
||||
fail(msg, pytrace=False)
|
||||
else:
|
||||
# indices might not be set if old-style metafunc.addcall() was used
|
||||
param_index = funcitem.callspec.indices.get(argname, 0)
|
||||
@@ -698,10 +718,11 @@ def scope2index(scope, descr, where=None):
|
||||
try:
|
||||
return scopes.index(scope)
|
||||
except ValueError:
|
||||
raise ValueError(
|
||||
"{} {}has an unsupported scope value '{}'".format(
|
||||
fail(
|
||||
"{} {}got an unexpected scope value '{}'".format(
|
||||
descr, "from {} ".format(where) if where else "", scope
|
||||
)
|
||||
),
|
||||
pytrace=False,
|
||||
)
|
||||
|
||||
|
||||
@@ -834,7 +855,9 @@ class FixtureDef(object):
|
||||
self.argname = argname
|
||||
self.scope = scope
|
||||
self.scopenum = scope2index(
|
||||
scope or "function", descr="fixture {}".format(func.__name__), where=baseid
|
||||
scope or "function",
|
||||
descr="Fixture '{}'".format(func.__name__),
|
||||
where=baseid,
|
||||
)
|
||||
self.params = params
|
||||
self.argnames = getfuncargnames(func, is_method=unittest)
|
||||
@@ -896,7 +919,7 @@ class FixtureDef(object):
|
||||
return hook.pytest_fixture_setup(fixturedef=self, request=request)
|
||||
|
||||
def __repr__(self):
|
||||
return "<FixtureDef name=%r scope=%r baseid=%r >" % (
|
||||
return "<FixtureDef name=%r scope=%r baseid=%r>" % (
|
||||
self.argname,
|
||||
self.scope,
|
||||
self.baseid,
|
||||
@@ -956,8 +979,9 @@ def wrap_function_to_warning_if_called_directly(function, fixture_marker):
|
||||
used as an argument in a test function.
|
||||
"""
|
||||
is_yield_function = is_generator(function)
|
||||
msg = FIXTURE_FUNCTION_CALL.format(name=fixture_marker.name or function.__name__)
|
||||
warning = RemovedInPytest4Warning(msg)
|
||||
warning = FIXTURE_FUNCTION_CALL.format(
|
||||
name=fixture_marker.name or function.__name__
|
||||
)
|
||||
|
||||
if is_yield_function:
|
||||
|
||||
@@ -996,7 +1020,7 @@ class FixtureFunctionMarker(object):
|
||||
|
||||
def __call__(self, function):
|
||||
if isclass(function):
|
||||
raise ValueError("class fixtures not supported (may be in the future)")
|
||||
raise ValueError("class fixtures not supported (maybe in the future)")
|
||||
|
||||
if getattr(function, "_pytestfixturefunction", False):
|
||||
raise ValueError(
|
||||
@@ -1151,7 +1175,7 @@ class FixtureManager(object):
|
||||
def pytest_plugin_registered(self, plugin):
|
||||
nodeid = None
|
||||
try:
|
||||
p = py.path.local(plugin.__file__)
|
||||
p = py.path.local(plugin.__file__).realpath()
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
@@ -1284,9 +1308,7 @@ class FixtureManager(object):
|
||||
|
||||
filename, lineno = getfslineno(obj)
|
||||
warnings.warn_explicit(
|
||||
RemovedInPytest4Warning(
|
||||
deprecated.FUNCARG_PREFIX.format(name=name)
|
||||
),
|
||||
deprecated.FUNCARG_PREFIX.format(name=name),
|
||||
category=None,
|
||||
filename=str(filename),
|
||||
lineno=lineno + 1,
|
||||
@@ -1349,8 +1371,7 @@ class FixtureManager(object):
|
||||
fixturedefs = self._arg2fixturedefs[argname]
|
||||
except KeyError:
|
||||
return None
|
||||
else:
|
||||
return tuple(self._matchfactories(fixturedefs, nodeid))
|
||||
return tuple(self._matchfactories(fixturedefs, nodeid))
|
||||
|
||||
def _matchfactories(self, fixturedefs, nodeid):
|
||||
for fixturedef in fixturedefs:
|
||||
|
||||
@@ -139,7 +139,7 @@ def showhelp(config):
|
||||
tw.line()
|
||||
tw.line()
|
||||
tw.line(
|
||||
"[pytest] ini-options in the first " "pytest.ini|tox.ini|setup.cfg file found:"
|
||||
"[pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg file found:"
|
||||
)
|
||||
tw.line()
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ class Junit(py.xml.Namespace):
|
||||
# this dynamically instead of hardcoding it. The spec range of valid
|
||||
# chars is: Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD]
|
||||
# | [#x10000-#x10FFFF]
|
||||
_legal_chars = (0x09, 0x0A, 0x0d)
|
||||
_legal_chars = (0x09, 0x0A, 0x0D)
|
||||
_legal_ranges = ((0x20, 0x7E), (0x80, 0xD7FF), (0xE000, 0xFFFD), (0x10000, 0x10FFFF))
|
||||
_legal_xml_re = [
|
||||
unicode("%s-%s") % (unichr(low), unichr(high))
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
import logging
|
||||
from contextlib import closing, contextmanager
|
||||
from contextlib import contextmanager
|
||||
import re
|
||||
import six
|
||||
|
||||
@@ -213,7 +213,8 @@ class LogCaptureFixture(object):
|
||||
def __init__(self, item):
|
||||
"""Creates a new funcarg."""
|
||||
self._item = item
|
||||
self._initial_log_levels = {} # type: Dict[str, int] # dict of log name -> log level
|
||||
# dict of log name -> log level
|
||||
self._initial_log_levels = {} # type: Dict[str, int]
|
||||
|
||||
def _finalize(self):
|
||||
"""Finalizes the fixture.
|
||||
@@ -414,7 +415,6 @@ class LoggingPlugin(object):
|
||||
else:
|
||||
self.log_file_handler = None
|
||||
|
||||
# initialized during pytest_runtestloop
|
||||
self.log_cli_handler = None
|
||||
|
||||
def _log_cli_enabled(self):
|
||||
@@ -425,6 +425,22 @@ class LoggingPlugin(object):
|
||||
"--log-cli-level"
|
||||
) is not None or self._config.getini("log_cli")
|
||||
|
||||
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
|
||||
def pytest_collection(self):
|
||||
# This has to be called before the first log message is logged,
|
||||
# so we can access the terminal reporter plugin.
|
||||
self._setup_cli_logging()
|
||||
|
||||
with self.live_logs_context():
|
||||
if self.log_cli_handler:
|
||||
self.log_cli_handler.set_when("collection")
|
||||
|
||||
if self.log_file_handler is not None:
|
||||
with catching_logs(self.log_file_handler, level=self.log_file_level):
|
||||
yield
|
||||
else:
|
||||
yield
|
||||
|
||||
@contextmanager
|
||||
def _runtest_for(self, item, when):
|
||||
"""Implements the internals of pytest_runtest_xxx() hook."""
|
||||
@@ -484,22 +500,15 @@ class LoggingPlugin(object):
|
||||
@pytest.hookimpl(hookwrapper=True)
|
||||
def pytest_runtestloop(self, session):
|
||||
"""Runs all collected test items."""
|
||||
self._setup_cli_logging()
|
||||
with self.live_logs_context:
|
||||
with self.live_logs_context():
|
||||
if self.log_file_handler is not None:
|
||||
with closing(self.log_file_handler):
|
||||
with catching_logs(
|
||||
self.log_file_handler, level=self.log_file_level
|
||||
):
|
||||
yield # run all the tests
|
||||
with catching_logs(self.log_file_handler, level=self.log_file_level):
|
||||
yield # run all the tests
|
||||
else:
|
||||
yield # run all the tests
|
||||
|
||||
def _setup_cli_logging(self):
|
||||
"""Sets up the handler and logger for the Live Logs feature, if enabled.
|
||||
|
||||
This must be done right before starting the loop so we can access the terminal reporter plugin.
|
||||
"""
|
||||
"""Sets up the handler and logger for the Live Logs feature, if enabled."""
|
||||
terminal_reporter = self._config.pluginmanager.get_plugin("terminalreporter")
|
||||
if self._log_cli_enabled() and terminal_reporter is not None:
|
||||
capture_manager = self._config.pluginmanager.get_plugin("capturemanager")
|
||||
@@ -529,11 +538,14 @@ class LoggingPlugin(object):
|
||||
self._config, "log_cli_level", "log_level"
|
||||
)
|
||||
self.log_cli_handler = log_cli_handler
|
||||
self.live_logs_context = catching_logs(
|
||||
self.live_logs_context = lambda: catching_logs(
|
||||
log_cli_handler, formatter=log_cli_formatter, level=log_cli_level
|
||||
)
|
||||
else:
|
||||
self.live_logs_context = dummy_context_manager()
|
||||
self.live_logs_context = lambda: dummy_context_manager()
|
||||
# Note that the lambda for the live_logs_context is needed because
|
||||
# live_logs_context can otherwise not be entered multiple times due
|
||||
# to limitations of contextlib.contextmanager
|
||||
|
||||
|
||||
class _LiveLoggingStreamHandler(logging.StreamHandler):
|
||||
|
||||
@@ -156,7 +156,10 @@ def pytest_addoption(parser):
|
||||
dest="basetemp",
|
||||
default=None,
|
||||
metavar="dir",
|
||||
help="base temporary directory for this test run.",
|
||||
help=(
|
||||
"base temporary directory for this test run."
|
||||
"(warning: this directory is removed if it exists)"
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -182,10 +185,13 @@ def wrap_session(config, doit):
|
||||
session.exitstatus = EXIT_TESTSFAILED
|
||||
except KeyboardInterrupt:
|
||||
excinfo = _pytest._code.ExceptionInfo()
|
||||
if initstate < 2 and isinstance(excinfo.value, exit.Exception):
|
||||
exitstatus = EXIT_INTERRUPTED
|
||||
if initstate <= 2 and isinstance(excinfo.value, exit.Exception):
|
||||
sys.stderr.write("{}: {}\n".format(excinfo.typename, excinfo.value.msg))
|
||||
if excinfo.value.returncode is not None:
|
||||
exitstatus = excinfo.value.returncode
|
||||
config.hook.pytest_keyboard_interrupt(excinfo=excinfo)
|
||||
session.exitstatus = EXIT_INTERRUPTED
|
||||
session.exitstatus = exitstatus
|
||||
except: # noqa
|
||||
excinfo = _pytest._code.ExceptionInfo()
|
||||
config.notify_exception(excinfo, config.option)
|
||||
@@ -487,7 +493,7 @@ class Session(nodes.FSCollector):
|
||||
from _pytest.python import Package
|
||||
|
||||
names = self._parsearg(arg)
|
||||
argpath = names.pop(0)
|
||||
argpath = names.pop(0).realpath()
|
||||
paths = []
|
||||
|
||||
root = self
|
||||
@@ -564,9 +570,7 @@ class Session(nodes.FSCollector):
|
||||
return True
|
||||
|
||||
def _tryconvertpyarg(self, x):
|
||||
"""Convert a dotted module name to path.
|
||||
|
||||
"""
|
||||
"""Convert a dotted module name to path."""
|
||||
try:
|
||||
with _patched_find_module():
|
||||
loader = pkgutil.find_loader(x)
|
||||
@@ -598,8 +602,7 @@ class Session(nodes.FSCollector):
|
||||
raise UsageError(
|
||||
"file or package not found: " + arg + " (missing __init__.py?)"
|
||||
)
|
||||
else:
|
||||
raise UsageError("file not found: " + arg)
|
||||
raise UsageError("file not found: " + arg)
|
||||
parts[0] = path
|
||||
return parts
|
||||
|
||||
|
||||
@@ -24,11 +24,6 @@ __all__ = [
|
||||
]
|
||||
|
||||
|
||||
class MarkerError(Exception):
|
||||
|
||||
"""Error in use of a pytest marker/attribute."""
|
||||
|
||||
|
||||
def param(*values, **kw):
|
||||
"""Specify a parameter in `pytest.mark.parametrize`_ calls or
|
||||
:ref:`parametrized fixtures <fixture-parametrize-marks>`.
|
||||
@@ -163,9 +158,9 @@ def pytest_configure(config):
|
||||
|
||||
empty_parameterset = config.getini(EMPTY_PARAMETERSET_OPTION)
|
||||
|
||||
if empty_parameterset not in ("skip", "xfail", None, ""):
|
||||
if empty_parameterset not in ("skip", "xfail", "fail_at_collect", None, ""):
|
||||
raise UsageError(
|
||||
"{!s} must be one of skip and xfail,"
|
||||
"{!s} must be one of skip, xfail or fail_at_collect"
|
||||
" but it is {!r}".format(EMPTY_PARAMETERSET_OPTION, empty_parameterset)
|
||||
)
|
||||
|
||||
|
||||
@@ -90,7 +90,10 @@ class MarkEvaluator(object):
|
||||
else:
|
||||
if "reason" not in mark.kwargs:
|
||||
# XXX better be checked at collection time
|
||||
msg = "you need to specify reason=STRING " "when using booleans as conditions."
|
||||
msg = (
|
||||
"you need to specify reason=STRING "
|
||||
"when using booleans as conditions."
|
||||
)
|
||||
fail(msg)
|
||||
result = bool(expr)
|
||||
if result:
|
||||
|
||||
@@ -6,6 +6,7 @@ from operator import attrgetter
|
||||
|
||||
import attr
|
||||
|
||||
from _pytest.outcomes import fail
|
||||
from ..deprecated import MARK_PARAMETERSET_UNPACKING, MARK_INFO_ATTRIBUTE
|
||||
from ..compat import NOTSET, getfslineno, MappingMixin
|
||||
from six.moves import map
|
||||
@@ -32,11 +33,19 @@ def istestfunc(func):
|
||||
|
||||
|
||||
def get_empty_parameterset_mark(config, argnames, func):
|
||||
from ..nodes import Collector
|
||||
|
||||
requested_mark = config.getini(EMPTY_PARAMETERSET_OPTION)
|
||||
if requested_mark in ("", None, "skip"):
|
||||
mark = MARK_GEN.skip
|
||||
elif requested_mark == "xfail":
|
||||
mark = MARK_GEN.xfail(run=False)
|
||||
elif requested_mark == "fail_at_collect":
|
||||
f_name = func.__name__
|
||||
_, lineno = getfslineno(func)
|
||||
raise Collector.CollectError(
|
||||
"Empty parameter set in '%s' at line %d" % (f_name, lineno)
|
||||
)
|
||||
else:
|
||||
raise LookupError(requested_mark)
|
||||
fs, lineno = getfslineno(func)
|
||||
@@ -307,7 +316,7 @@ def _marked(func, mark):
|
||||
return any(mark == info.combined for info in func_mark)
|
||||
|
||||
|
||||
@attr.s
|
||||
@attr.s(repr=False)
|
||||
class MarkInfo(object):
|
||||
""" Marking object created by :class:`MarkDecorator` instances. """
|
||||
|
||||
@@ -385,7 +394,7 @@ class MarkGenerator(object):
|
||||
x = marker.split("(", 1)[0]
|
||||
values.add(x)
|
||||
if name not in self._markers:
|
||||
raise AttributeError("%r not a registered marker" % (name,))
|
||||
fail("{!r} not a registered marker".format(name), pytrace=False)
|
||||
|
||||
|
||||
MARK_GEN = MarkGenerator()
|
||||
|
||||
@@ -9,6 +9,7 @@ import attr
|
||||
import _pytest
|
||||
import _pytest._code
|
||||
from _pytest.compat import getfslineno
|
||||
from _pytest.outcomes import fail
|
||||
|
||||
from _pytest.mark.structures import NodeKeywords, MarkInfo
|
||||
|
||||
@@ -61,11 +62,11 @@ class _CompatProperty(object):
|
||||
if obj is None:
|
||||
return self
|
||||
|
||||
# TODO: reenable in the features branch
|
||||
# warnings.warn(
|
||||
# "usage of {owner!r}.{name} is deprecated, please use pytest.{name} instead".format(
|
||||
# name=self.name, owner=type(owner).__name__),
|
||||
# PendingDeprecationWarning, stacklevel=2)
|
||||
from _pytest.deprecated import COMPAT_PROPERTY
|
||||
|
||||
warnings.warn(
|
||||
COMPAT_PROPERTY.format(name=self.name, owner=owner.__name__), stacklevel=2
|
||||
)
|
||||
return getattr(__import__("pytest"), self.name)
|
||||
|
||||
|
||||
@@ -126,11 +127,10 @@ class Node(object):
|
||||
if isinstance(maybe_compatprop, _CompatProperty):
|
||||
return getattr(__import__("pytest"), name)
|
||||
else:
|
||||
from _pytest.deprecated import CUSTOM_CLASS
|
||||
|
||||
cls = getattr(self, name)
|
||||
# TODO: reenable in the features branch
|
||||
# warnings.warn("use of node.%s is deprecated, "
|
||||
# "use pytest_pycollect_makeitem(...) to create custom "
|
||||
# "collection nodes" % name, category=DeprecationWarning)
|
||||
self.warn(CUSTOM_CLASS.format(name=name, type_name=type(self).__name__))
|
||||
return cls
|
||||
|
||||
def __repr__(self):
|
||||
@@ -347,6 +347,9 @@ class Node(object):
|
||||
pass
|
||||
|
||||
def _repr_failure_py(self, excinfo, style=None):
|
||||
if excinfo.errisinstance(fail.Exception):
|
||||
if not excinfo.value.pytrace:
|
||||
return six.text_type(excinfo.value)
|
||||
fm = self.session._fixturemanager
|
||||
if excinfo.errisinstance(fm.FixtureLookupError):
|
||||
return excinfo.value.formatrepr()
|
||||
|
||||
@@ -49,18 +49,24 @@ class Failed(OutcomeException):
|
||||
class Exit(KeyboardInterrupt):
|
||||
""" raised for immediate program exits (no tracebacks/summaries)"""
|
||||
|
||||
def __init__(self, msg="unknown reason"):
|
||||
def __init__(self, msg="unknown reason", returncode=None):
|
||||
self.msg = msg
|
||||
self.returncode = returncode
|
||||
KeyboardInterrupt.__init__(self, msg)
|
||||
|
||||
|
||||
# exposed helper methods
|
||||
|
||||
|
||||
def exit(msg):
|
||||
""" exit testing process as if KeyboardInterrupt was triggered. """
|
||||
def exit(msg, returncode=None):
|
||||
"""
|
||||
Exit testing process as if KeyboardInterrupt was triggered.
|
||||
|
||||
:param str msg: message to display upon exit.
|
||||
:param int returncode: return code to be used when exiting pytest.
|
||||
"""
|
||||
__tracebackhide__ = True
|
||||
raise Exit(msg)
|
||||
raise Exit(msg, returncode)
|
||||
|
||||
|
||||
exit.Exception = Exit
|
||||
|
||||
283
src/_pytest/pathlib.py
Normal file
283
src/_pytest/pathlib.py
Normal file
@@ -0,0 +1,283 @@
|
||||
|
||||
import os
|
||||
import errno
|
||||
import atexit
|
||||
import operator
|
||||
import six
|
||||
import sys
|
||||
from functools import reduce
|
||||
import uuid
|
||||
from six.moves import map
|
||||
import itertools
|
||||
import shutil
|
||||
from os.path import expanduser, expandvars, isabs, sep
|
||||
from posixpath import sep as posix_sep
|
||||
import fnmatch
|
||||
import stat
|
||||
|
||||
from .compat import PY36
|
||||
|
||||
|
||||
if PY36:
|
||||
from pathlib import Path, PurePath
|
||||
else:
|
||||
from pathlib2 import Path, PurePath
|
||||
|
||||
__all__ = ["Path", "PurePath"]
|
||||
|
||||
|
||||
LOCK_TIMEOUT = 60 * 60 * 3
|
||||
|
||||
get_lock_path = operator.methodcaller("joinpath", ".lock")
|
||||
|
||||
|
||||
def ensure_reset_dir(path):
|
||||
"""
|
||||
ensures the given path is a empty directory
|
||||
"""
|
||||
if path.exists():
|
||||
rmtree(path, force=True)
|
||||
path.mkdir()
|
||||
|
||||
|
||||
def _shutil_rmtree_remove_writable(func, fspath, _):
|
||||
"Clear the readonly bit and reattempt the removal"
|
||||
os.chmod(fspath, stat.S_IWRITE)
|
||||
func(fspath)
|
||||
|
||||
|
||||
def rmtree(path, force=False):
|
||||
if force:
|
||||
# ignore_errors leaves dead folders around
|
||||
# python needs a rm -rf as a followup
|
||||
# the trick with _shutil_rmtree_remove_writable is unreliable
|
||||
shutil.rmtree(str(path), ignore_errors=True)
|
||||
else:
|
||||
shutil.rmtree(str(path))
|
||||
|
||||
|
||||
def find_prefixed(root, prefix):
|
||||
"""finds all elements in root that begin with the prefix, case insensitive"""
|
||||
l_prefix = prefix.lower()
|
||||
for x in root.iterdir():
|
||||
if x.name.lower().startswith(l_prefix):
|
||||
yield x
|
||||
|
||||
|
||||
def extract_suffixes(iter, prefix):
|
||||
"""
|
||||
:param iter: iterator over path names
|
||||
:param prefix: expected prefix of the path names
|
||||
:returns: the parts of the paths following the prefix
|
||||
"""
|
||||
p_len = len(prefix)
|
||||
for p in iter:
|
||||
yield p.name[p_len:]
|
||||
|
||||
|
||||
def find_suffixes(root, prefix):
|
||||
"""combines find_prefixes and extract_suffixes
|
||||
"""
|
||||
return extract_suffixes(find_prefixed(root, prefix), prefix)
|
||||
|
||||
|
||||
def parse_num(maybe_num):
|
||||
"""parses number path suffixes, returns -1 on error"""
|
||||
try:
|
||||
return int(maybe_num)
|
||||
except ValueError:
|
||||
return -1
|
||||
|
||||
|
||||
if six.PY2:
|
||||
|
||||
def _max(iterable, default):
|
||||
"""needed due to python2.7 lacking the default argument for max"""
|
||||
return reduce(max, iterable, default)
|
||||
|
||||
|
||||
else:
|
||||
_max = max
|
||||
|
||||
|
||||
def make_numbered_dir(root, prefix):
|
||||
"""create a directory with a increased number as suffix for the given prefix"""
|
||||
for i in range(10):
|
||||
# try up to 10 times to create the folder
|
||||
max_existing = _max(map(parse_num, find_suffixes(root, prefix)), default=-1)
|
||||
new_number = max_existing + 1
|
||||
new_path = root.joinpath("{}{}".format(prefix, new_number))
|
||||
try:
|
||||
new_path.mkdir()
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
return new_path
|
||||
else:
|
||||
raise EnvironmentError(
|
||||
"could not create numbered dir with prefix "
|
||||
"{prefix} in {root} after 10 tries".format(prefix=prefix, root=root)
|
||||
)
|
||||
|
||||
|
||||
def create_cleanup_lock(p):
|
||||
"""crates a lock to prevent premature folder cleanup"""
|
||||
lock_path = get_lock_path(p)
|
||||
try:
|
||||
fd = os.open(str(lock_path), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o644)
|
||||
except OSError as e:
|
||||
if e.errno == errno.EEXIST:
|
||||
six.raise_from(
|
||||
EnvironmentError("cannot create lockfile in {path}".format(path=p)), e
|
||||
)
|
||||
else:
|
||||
raise
|
||||
else:
|
||||
pid = os.getpid()
|
||||
spid = str(pid)
|
||||
if not isinstance(spid, six.binary_type):
|
||||
spid = spid.encode("ascii")
|
||||
os.write(fd, spid)
|
||||
os.close(fd)
|
||||
if not lock_path.is_file():
|
||||
raise EnvironmentError("lock path got renamed after sucessfull creation")
|
||||
return lock_path
|
||||
|
||||
|
||||
def register_cleanup_lock_removal(lock_path, register=atexit.register):
|
||||
"""registers a cleanup function for removing a lock, by default on atexit"""
|
||||
pid = os.getpid()
|
||||
|
||||
def cleanup_on_exit(lock_path=lock_path, original_pid=pid):
|
||||
current_pid = os.getpid()
|
||||
if current_pid != original_pid:
|
||||
# fork
|
||||
return
|
||||
try:
|
||||
lock_path.unlink()
|
||||
except (OSError, IOError):
|
||||
pass
|
||||
|
||||
return register(cleanup_on_exit)
|
||||
|
||||
|
||||
def delete_a_numbered_dir(path):
|
||||
"""removes a numbered directory"""
|
||||
create_cleanup_lock(path)
|
||||
parent = path.parent
|
||||
|
||||
garbage = parent.joinpath("garbage-{}".format(uuid.uuid4()))
|
||||
path.rename(garbage)
|
||||
rmtree(garbage, force=True)
|
||||
|
||||
|
||||
def ensure_deletable(path, consider_lock_dead_if_created_before):
|
||||
"""checks if a lock exists and breaks it if its considered dead"""
|
||||
if path.is_symlink():
|
||||
return False
|
||||
lock = get_lock_path(path)
|
||||
if not lock.exists():
|
||||
return True
|
||||
try:
|
||||
lock_time = lock.stat().st_mtime
|
||||
except Exception:
|
||||
return False
|
||||
else:
|
||||
if lock_time < consider_lock_dead_if_created_before:
|
||||
lock.unlink()
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def try_cleanup(path, consider_lock_dead_if_created_before):
|
||||
"""tries to cleanup a folder if we can ensure its deletable"""
|
||||
if ensure_deletable(path, consider_lock_dead_if_created_before):
|
||||
delete_a_numbered_dir(path)
|
||||
|
||||
|
||||
def cleanup_candidates(root, prefix, keep):
|
||||
"""lists candidates for numbered directories to be removed - follows py.path"""
|
||||
max_existing = _max(map(parse_num, find_suffixes(root, prefix)), default=-1)
|
||||
max_delete = max_existing - keep
|
||||
paths = find_prefixed(root, prefix)
|
||||
paths, paths2 = itertools.tee(paths)
|
||||
numbers = map(parse_num, extract_suffixes(paths2, prefix))
|
||||
for path, number in zip(paths, numbers):
|
||||
if number <= max_delete:
|
||||
yield path
|
||||
|
||||
|
||||
def cleanup_numbered_dir(root, prefix, keep, consider_lock_dead_if_created_before):
|
||||
"""cleanup for lock driven numbered directories"""
|
||||
for path in cleanup_candidates(root, prefix, keep):
|
||||
try_cleanup(path, consider_lock_dead_if_created_before)
|
||||
for path in root.glob("garbage-*"):
|
||||
try_cleanup(path, consider_lock_dead_if_created_before)
|
||||
|
||||
|
||||
def make_numbered_dir_with_cleanup(root, prefix, keep, lock_timeout):
|
||||
"""creates a numbered dir with a cleanup lock and removes old ones"""
|
||||
e = None
|
||||
for i in range(10):
|
||||
try:
|
||||
p = make_numbered_dir(root, prefix)
|
||||
lock_path = create_cleanup_lock(p)
|
||||
register_cleanup_lock_removal(lock_path)
|
||||
except Exception as e:
|
||||
pass
|
||||
else:
|
||||
consider_lock_dead_if_created_before = p.stat().st_mtime - lock_timeout
|
||||
cleanup_numbered_dir(
|
||||
root=root,
|
||||
prefix=prefix,
|
||||
keep=keep,
|
||||
consider_lock_dead_if_created_before=consider_lock_dead_if_created_before,
|
||||
)
|
||||
return p
|
||||
assert e is not None
|
||||
raise e
|
||||
|
||||
|
||||
def resolve_from_str(input, root):
|
||||
assert not isinstance(input, Path), "would break on py2"
|
||||
root = Path(root)
|
||||
input = expanduser(input)
|
||||
input = expandvars(input)
|
||||
if isabs(input):
|
||||
return Path(input)
|
||||
else:
|
||||
return root.joinpath(input)
|
||||
|
||||
|
||||
def fnmatch_ex(pattern, path):
|
||||
"""FNMatcher port from py.path.common which works with PurePath() instances.
|
||||
|
||||
The difference between this algorithm and PurePath.match() is that the latter matches "**" glob expressions
|
||||
for each part of the path, while this algorithm uses the whole path instead.
|
||||
|
||||
For example:
|
||||
"tests/foo/bar/doc/test_foo.py" matches pattern "tests/**/doc/test*.py" with this algorithm, but not with
|
||||
PurePath.match().
|
||||
|
||||
This algorithm was ported to keep backward-compatibility with existing settings which assume paths match according
|
||||
this logic.
|
||||
|
||||
References:
|
||||
* https://bugs.python.org/issue29249
|
||||
* https://bugs.python.org/issue34731
|
||||
"""
|
||||
path = PurePath(path)
|
||||
iswin32 = sys.platform.startswith("win")
|
||||
|
||||
if iswin32 and sep not in pattern and posix_sep in pattern:
|
||||
# Running on Windows, the pattern has no Windows path separators,
|
||||
# and the pattern has one or more Posix path separators. Replace
|
||||
# the Posix path separators with the Windows path separator.
|
||||
pattern = pattern.replace(posix_sep, sep)
|
||||
|
||||
if sep not in pattern:
|
||||
name = path.name
|
||||
else:
|
||||
name = six.text_type(path)
|
||||
return fnmatch.fnmatch(name, pattern)
|
||||
@@ -1,52 +0,0 @@
|
||||
from os.path import expanduser, expandvars, isabs, sep
|
||||
from posixpath import sep as posix_sep
|
||||
import fnmatch
|
||||
import sys
|
||||
|
||||
import six
|
||||
|
||||
from .compat import Path, PurePath
|
||||
|
||||
|
||||
def resolve_from_str(input, root):
|
||||
assert not isinstance(input, Path), "would break on py2"
|
||||
root = Path(root)
|
||||
input = expanduser(input)
|
||||
input = expandvars(input)
|
||||
if isabs(input):
|
||||
return Path(input)
|
||||
else:
|
||||
return root.joinpath(input)
|
||||
|
||||
|
||||
def fnmatch_ex(pattern, path):
|
||||
"""FNMatcher port from py.path.common which works with PurePath() instances.
|
||||
|
||||
The difference between this algorithm and PurePath.match() is that the latter matches "**" glob expressions
|
||||
for each part of the path, while this algorithm uses the whole path instead.
|
||||
|
||||
For example:
|
||||
"tests/foo/bar/doc/test_foo.py" matches pattern "tests/**/doc/test*.py" with this algorithm, but not with
|
||||
PurePath.match().
|
||||
|
||||
This algorithm was ported to keep backward-compatibility with existing settings which assume paths match according
|
||||
this logic.
|
||||
|
||||
References:
|
||||
* https://bugs.python.org/issue29249
|
||||
* https://bugs.python.org/issue34731
|
||||
"""
|
||||
path = PurePath(path)
|
||||
iswin32 = sys.platform.startswith("win")
|
||||
|
||||
if iswin32 and sep not in pattern and posix_sep in pattern:
|
||||
# Running on Windows, the pattern has no Windows path separators,
|
||||
# and the pattern has one or more Posix path separators. Replace
|
||||
# the Posix path separators with the Windows path separator.
|
||||
pattern = pattern.replace(posix_sep, sep)
|
||||
|
||||
if sep not in pattern:
|
||||
name = path.name
|
||||
else:
|
||||
name = six.text_type(path)
|
||||
return fnmatch.fnmatch(name, pattern)
|
||||
@@ -17,12 +17,13 @@ from weakref import WeakKeyDictionary
|
||||
|
||||
from _pytest.capture import MultiCapture, SysCapture
|
||||
from _pytest._code import Source
|
||||
from _pytest.main import Session, EXIT_INTERRUPTED, EXIT_OK
|
||||
from _pytest.assertion.rewrite import AssertionRewritingHook
|
||||
from _pytest.pathlib import Path
|
||||
from _pytest.compat import safe_str
|
||||
|
||||
import py
|
||||
import pytest
|
||||
from _pytest.main import Session, EXIT_OK
|
||||
from _pytest.assertion.rewrite import AssertionRewritingHook
|
||||
from _pytest.compat import Path
|
||||
from _pytest.compat import safe_str
|
||||
|
||||
IGNORE_PAM = [ # filenames added when obtaining details about the current user
|
||||
u"/var/lib/sss/mc/passwd"
|
||||
@@ -61,6 +62,11 @@ def pytest_configure(config):
|
||||
config.pluginmanager.register(checker)
|
||||
|
||||
|
||||
def raise_on_kwargs(kwargs):
|
||||
if kwargs:
|
||||
raise TypeError("Unexpected arguments: {}".format(", ".join(sorted(kwargs))))
|
||||
|
||||
|
||||
class LsofFdLeakChecker(object):
|
||||
def get_open_files(self):
|
||||
out = self._exec_lsof()
|
||||
@@ -482,11 +488,16 @@ class Testdir(object):
|
||||
|
||||
"""
|
||||
|
||||
class TimeoutExpired(Exception):
|
||||
pass
|
||||
|
||||
def __init__(self, request, tmpdir_factory):
|
||||
self.request = request
|
||||
self._mod_collections = WeakKeyDictionary()
|
||||
name = request.function.__name__
|
||||
self.tmpdir = tmpdir_factory.mktemp(name, numbered=True)
|
||||
self.test_tmproot = tmpdir_factory.mktemp("tmp-" + name, numbered=True)
|
||||
os.environ["PYTEST_DEBUG_TEMPROOT"] = str(self.test_tmproot)
|
||||
self.plugins = []
|
||||
self._cwd_snapshot = CwdSnapshot()
|
||||
self._sys_path_snapshot = SysPathsSnapshot()
|
||||
@@ -513,6 +524,7 @@ class Testdir(object):
|
||||
self._sys_modules_snapshot.restore()
|
||||
self._sys_path_snapshot.restore()
|
||||
self._cwd_snapshot.restore()
|
||||
os.environ.pop("PYTEST_DEBUG_TEMPROOT", None)
|
||||
|
||||
def __take_sys_modules_snapshot(self):
|
||||
# some zope modules used by twisted-related tests keep internal state
|
||||
@@ -845,7 +857,7 @@ class Testdir(object):
|
||||
|
||||
# typically we reraise keyboard interrupts from the child run
|
||||
# because it's our user requesting interruption of the testing
|
||||
if ret == 2 and not kwargs.get("no_reraise_ctrlc"):
|
||||
if ret == EXIT_INTERRUPTED and not kwargs.get("no_reraise_ctrlc"):
|
||||
calls = reprec.getcalls("pytest_keyboard_interrupt")
|
||||
if calls and calls[-1].excinfo.type == KeyboardInterrupt:
|
||||
raise KeyboardInterrupt()
|
||||
@@ -1039,14 +1051,23 @@ class Testdir(object):
|
||||
|
||||
return popen
|
||||
|
||||
def run(self, *cmdargs):
|
||||
def run(self, *cmdargs, **kwargs):
|
||||
"""Run a command with arguments.
|
||||
|
||||
Run a process using subprocess.Popen saving the stdout and stderr.
|
||||
|
||||
:param args: the sequence of arguments to pass to `subprocess.Popen()`
|
||||
:param timeout: the period in seconds after which to timeout and raise
|
||||
:py:class:`Testdir.TimeoutExpired`
|
||||
|
||||
Returns a :py:class:`RunResult`.
|
||||
|
||||
"""
|
||||
__tracebackhide__ = True
|
||||
|
||||
timeout = kwargs.pop("timeout", None)
|
||||
raise_on_kwargs(kwargs)
|
||||
|
||||
cmdargs = [
|
||||
str(arg) if isinstance(arg, py.path.local) else arg for arg in cmdargs
|
||||
]
|
||||
@@ -1061,7 +1082,40 @@ class Testdir(object):
|
||||
popen = self.popen(
|
||||
cmdargs, stdout=f1, stderr=f2, close_fds=(sys.platform != "win32")
|
||||
)
|
||||
ret = popen.wait()
|
||||
|
||||
def handle_timeout():
|
||||
__tracebackhide__ = True
|
||||
|
||||
timeout_message = (
|
||||
"{seconds} second timeout expired running:"
|
||||
" {command}".format(seconds=timeout, command=cmdargs)
|
||||
)
|
||||
|
||||
popen.kill()
|
||||
popen.wait()
|
||||
raise self.TimeoutExpired(timeout_message)
|
||||
|
||||
if timeout is None:
|
||||
ret = popen.wait()
|
||||
elif six.PY3:
|
||||
try:
|
||||
ret = popen.wait(timeout)
|
||||
except subprocess.TimeoutExpired:
|
||||
handle_timeout()
|
||||
else:
|
||||
end = time.time() + timeout
|
||||
|
||||
resolution = min(0.1, timeout / 10)
|
||||
|
||||
while True:
|
||||
ret = popen.poll()
|
||||
if ret is not None:
|
||||
break
|
||||
|
||||
if time.time() > end:
|
||||
handle_timeout()
|
||||
|
||||
time.sleep(resolution)
|
||||
finally:
|
||||
f1.close()
|
||||
f2.close()
|
||||
@@ -1108,9 +1162,15 @@ class Testdir(object):
|
||||
with "runpytest-" so they do not conflict with the normal numbered
|
||||
pytest location for temporary files and directories.
|
||||
|
||||
:param args: the sequence of arguments to pass to the pytest subprocess
|
||||
:param timeout: the period in seconds after which to timeout and raise
|
||||
:py:class:`Testdir.TimeoutExpired`
|
||||
|
||||
Returns a :py:class:`RunResult`.
|
||||
|
||||
"""
|
||||
__tracebackhide__ = True
|
||||
|
||||
p = py.path.local.make_numbered_dir(
|
||||
prefix="runpytest-", keep=None, rootdir=self.tmpdir
|
||||
)
|
||||
@@ -1119,7 +1179,7 @@ class Testdir(object):
|
||||
if plugins:
|
||||
args = ("-p", plugins[0]) + args
|
||||
args = self._getpytestargs() + args
|
||||
return self.run(*args)
|
||||
return self.run(*args, timeout=kwargs.get("timeout"))
|
||||
|
||||
def spawn_pytest(self, string, expect_timeout=10.0):
|
||||
"""Run pytest using pexpect.
|
||||
@@ -1267,6 +1327,7 @@ class LineMatcher(object):
|
||||
matches and non-matches are also printed on stdout.
|
||||
|
||||
"""
|
||||
__tracebackhide__ = True
|
||||
self._match_lines(lines2, fnmatch, "fnmatch")
|
||||
|
||||
def re_match_lines(self, lines2):
|
||||
@@ -1278,6 +1339,7 @@ class LineMatcher(object):
|
||||
The matches and non-matches are also printed on stdout.
|
||||
|
||||
"""
|
||||
__tracebackhide__ = True
|
||||
self._match_lines(lines2, lambda name, pat: re.match(pat, name), "re.match")
|
||||
|
||||
def _match_lines(self, lines2, match_func, match_nickname):
|
||||
|
||||
@@ -13,11 +13,10 @@ from textwrap import dedent
|
||||
import py
|
||||
import six
|
||||
from _pytest.main import FSHookProxy
|
||||
from _pytest.mark import MarkerError
|
||||
from _pytest.config import hookimpl
|
||||
|
||||
import _pytest
|
||||
import pluggy
|
||||
from _pytest._code import filter_traceback
|
||||
from _pytest import fixtures
|
||||
from _pytest import nodes
|
||||
from _pytest import deprecated
|
||||
@@ -47,37 +46,6 @@ from _pytest.mark.structures import (
|
||||
)
|
||||
from _pytest.warning_types import RemovedInPytest4Warning, PytestWarning
|
||||
|
||||
# relative paths that we use to filter traceback entries from appearing to the user;
|
||||
# see filter_traceback
|
||||
# note: if we need to add more paths than what we have now we should probably use a list
|
||||
# for better maintenance
|
||||
_pluggy_dir = py.path.local(pluggy.__file__.rstrip("oc"))
|
||||
# pluggy is either a package or a single module depending on the version
|
||||
if _pluggy_dir.basename == "__init__.py":
|
||||
_pluggy_dir = _pluggy_dir.dirpath()
|
||||
_pytest_dir = py.path.local(_pytest.__file__).dirpath()
|
||||
_py_dir = py.path.local(py.__file__).dirpath()
|
||||
|
||||
|
||||
def filter_traceback(entry):
|
||||
"""Return True if a TracebackEntry instance should be removed from tracebacks:
|
||||
* dynamically generated code (no code to show up for it);
|
||||
* internal traceback from pytest or its internal libraries, py and pluggy.
|
||||
"""
|
||||
# entry.path might sometimes return a str object when the entry
|
||||
# points to dynamically generated code
|
||||
# see https://bitbucket.org/pytest-dev/py/issues/71
|
||||
raw_filename = entry.frame.code.raw.co_filename
|
||||
is_generated = "<" in raw_filename and ">" in raw_filename
|
||||
if is_generated:
|
||||
return False
|
||||
# entry.path might point to a non-existing file, in which case it will
|
||||
# also return a str object. see #1133
|
||||
p = py.path.local(entry.path)
|
||||
return (
|
||||
not p.relto(_pluggy_dir) and not p.relto(_pytest_dir) and not p.relto(_py_dir)
|
||||
)
|
||||
|
||||
|
||||
def pyobj_property(name):
|
||||
def get(self):
|
||||
@@ -131,7 +99,7 @@ def pytest_addoption(parser):
|
||||
"python_functions",
|
||||
type="args",
|
||||
default=["test"],
|
||||
help="prefixes or glob names for Python test function and " "method discovery",
|
||||
help="prefixes or glob names for Python test function and method discovery",
|
||||
)
|
||||
|
||||
group.addoption(
|
||||
@@ -159,8 +127,8 @@ def pytest_generate_tests(metafunc):
|
||||
alt_spellings = ["parameterize", "parametrise", "parameterise"]
|
||||
for attr in alt_spellings:
|
||||
if hasattr(metafunc.function, attr):
|
||||
msg = "{0} has '{1}', spelling should be 'parametrize'"
|
||||
raise MarkerError(msg.format(metafunc.function.__name__, attr))
|
||||
msg = "{0} has '{1}' mark, spelling should be 'parametrize'"
|
||||
fail(msg.format(metafunc.function.__name__, attr), pytrace=False)
|
||||
for marker in metafunc.definition.iter_markers(name="parametrize"):
|
||||
metafunc.parametrize(*marker.args, **marker.kwargs)
|
||||
|
||||
@@ -760,12 +728,6 @@ class FunctionMixin(PyobjMixin):
|
||||
for entry in excinfo.traceback[1:-1]:
|
||||
entry.set_repr_style("short")
|
||||
|
||||
def _repr_failure_py(self, excinfo, style="long"):
|
||||
if excinfo.errisinstance(fail.Exception):
|
||||
if not excinfo.value.pytrace:
|
||||
return six.text_type(excinfo.value)
|
||||
return super(FunctionMixin, self)._repr_failure_py(excinfo, style=style)
|
||||
|
||||
def repr_failure(self, excinfo, outerr=None):
|
||||
assert outerr is None, "XXX outerr usage is deprecated"
|
||||
style = self.config.option.tbstyle
|
||||
@@ -799,7 +761,10 @@ class Generator(FunctionMixin, PyCollector):
|
||||
"%r generated tests with non-unique name %r" % (self, name)
|
||||
)
|
||||
seen[name] = True
|
||||
values.append(self.Function(name, self, args=args, callobj=call))
|
||||
with warnings.catch_warnings():
|
||||
# ignore our own deprecation warning
|
||||
function_class = self.Function
|
||||
values.append(function_class(name, self, args=args, callobj=call))
|
||||
self.warn(deprecated.YIELD_TESTS)
|
||||
return values
|
||||
|
||||
@@ -984,7 +949,9 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
|
||||
|
||||
ids = self._resolve_arg_ids(argnames, ids, parameters, item=self.definition)
|
||||
|
||||
scopenum = scope2index(scope, descr="call to {}".format(self.parametrize))
|
||||
scopenum = scope2index(
|
||||
scope, descr="parametrize() call in {}".format(self.function.__name__)
|
||||
)
|
||||
|
||||
# create the new calls: if we are parametrize() multiple times (by applying the decorator
|
||||
# more than once) then we accumulate those calls generating the cartesian product
|
||||
@@ -1023,15 +990,16 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
|
||||
idfn = ids
|
||||
ids = None
|
||||
if ids:
|
||||
func_name = self.function.__name__
|
||||
if len(ids) != len(parameters):
|
||||
raise ValueError(
|
||||
"%d tests specified with %d ids" % (len(parameters), len(ids))
|
||||
)
|
||||
msg = "In {}: {} parameter sets specified, with different number of ids: {}"
|
||||
fail(msg.format(func_name, len(parameters), len(ids)), pytrace=False)
|
||||
for id_value in ids:
|
||||
if id_value is not None and not isinstance(id_value, six.string_types):
|
||||
msg = "ids must be list of strings, found: %s (type: %s)"
|
||||
raise ValueError(
|
||||
msg % (saferepr(id_value), type(id_value).__name__)
|
||||
msg = "In {}: ids must be list of strings, found: {} (type: {!r})"
|
||||
fail(
|
||||
msg.format(func_name, saferepr(id_value), type(id_value)),
|
||||
pytrace=False,
|
||||
)
|
||||
ids = idmaker(argnames, parameters, idfn, ids, self.config, item=item)
|
||||
return ids
|
||||
@@ -1056,9 +1024,11 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
|
||||
valtypes = dict.fromkeys(argnames, "funcargs")
|
||||
for arg in indirect:
|
||||
if arg not in argnames:
|
||||
raise ValueError(
|
||||
"indirect given to %r: fixture %r doesn't exist"
|
||||
% (self.function, arg)
|
||||
fail(
|
||||
"In {}: indirect fixture '{}' doesn't exist".format(
|
||||
self.function.__name__, arg
|
||||
),
|
||||
pytrace=False,
|
||||
)
|
||||
valtypes[arg] = "params"
|
||||
return valtypes
|
||||
@@ -1072,19 +1042,25 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
|
||||
:raise ValueError: if validation fails.
|
||||
"""
|
||||
default_arg_names = set(get_default_arg_names(self.function))
|
||||
func_name = self.function.__name__
|
||||
for arg in argnames:
|
||||
if arg not in self.fixturenames:
|
||||
if arg in default_arg_names:
|
||||
raise ValueError(
|
||||
"%r already takes an argument %r with a default value"
|
||||
% (self.function, arg)
|
||||
fail(
|
||||
"In {}: function already takes an argument '{}' with a default value".format(
|
||||
func_name, arg
|
||||
),
|
||||
pytrace=False,
|
||||
)
|
||||
else:
|
||||
if isinstance(indirect, (tuple, list)):
|
||||
name = "fixture" if arg in indirect else "argument"
|
||||
else:
|
||||
name = "fixture" if indirect else "argument"
|
||||
raise ValueError("%r uses no %s %r" % (self.function, name, arg))
|
||||
fail(
|
||||
"In {}: function uses no {} '{}'".format(func_name, name, arg),
|
||||
pytrace=False,
|
||||
)
|
||||
|
||||
def addcall(self, funcargs=None, id=NOTSET, param=NOTSET):
|
||||
""" Add a new call to the underlying test function during the collection phase of a test run.
|
||||
|
||||
@@ -43,45 +43,10 @@ def deprecated_call(func=None, *args, **kwargs):
|
||||
in which case it will ensure calling ``func(*args, **kwargs)`` produces one of the warnings
|
||||
types above.
|
||||
"""
|
||||
if not func:
|
||||
return _DeprecatedCallContext()
|
||||
else:
|
||||
__tracebackhide__ = True
|
||||
with _DeprecatedCallContext():
|
||||
return func(*args, **kwargs)
|
||||
|
||||
|
||||
class _DeprecatedCallContext(object):
|
||||
"""Implements the logic to capture deprecation warnings as a context manager."""
|
||||
|
||||
def __enter__(self):
|
||||
self._captured_categories = []
|
||||
self._old_warn = warnings.warn
|
||||
self._old_warn_explicit = warnings.warn_explicit
|
||||
warnings.warn_explicit = self._warn_explicit
|
||||
warnings.warn = self._warn
|
||||
|
||||
def _warn_explicit(self, message, category, *args, **kwargs):
|
||||
self._captured_categories.append(category)
|
||||
|
||||
def _warn(self, message, category=None, *args, **kwargs):
|
||||
if isinstance(message, Warning):
|
||||
self._captured_categories.append(message.__class__)
|
||||
else:
|
||||
self._captured_categories.append(category)
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
warnings.warn_explicit = self._old_warn_explicit
|
||||
warnings.warn = self._old_warn
|
||||
|
||||
if exc_type is None:
|
||||
deprecation_categories = (DeprecationWarning, PendingDeprecationWarning)
|
||||
if not any(
|
||||
issubclass(c, deprecation_categories) for c in self._captured_categories
|
||||
):
|
||||
__tracebackhide__ = True
|
||||
msg = "Did not produce DeprecationWarning or PendingDeprecationWarning"
|
||||
raise AssertionError(msg)
|
||||
__tracebackhide__ = True
|
||||
if func is not None:
|
||||
args = (func,) + args
|
||||
return warns((DeprecationWarning, PendingDeprecationWarning), *args, **kwargs)
|
||||
|
||||
|
||||
def warns(expected_warning, *args, **kwargs):
|
||||
@@ -116,6 +81,7 @@ def warns(expected_warning, *args, **kwargs):
|
||||
Failed: DID NOT WARN. No warnings of type ...UserWarning... was emitted...
|
||||
|
||||
"""
|
||||
__tracebackhide__ = True
|
||||
match_expr = None
|
||||
if not args:
|
||||
if "match" in kwargs:
|
||||
@@ -183,12 +149,25 @@ class WarningsRecorder(warnings.catch_warnings):
|
||||
raise RuntimeError("Cannot enter %r twice" % self)
|
||||
self._list = super(WarningsRecorder, self).__enter__()
|
||||
warnings.simplefilter("always")
|
||||
# python3 keeps track of a "filter version", when the filters are
|
||||
# updated previously seen warnings can be re-warned. python2 has no
|
||||
# concept of this so we must reset the warnings registry manually.
|
||||
# trivial patching of `warnings.warn` seems to be enough somehow?
|
||||
if six.PY2:
|
||||
|
||||
def warn(*args, **kwargs):
|
||||
return self._saved_warn(*args, **kwargs)
|
||||
|
||||
warnings.warn, self._saved_warn = warn, warnings.warn
|
||||
return self
|
||||
|
||||
def __exit__(self, *exc_info):
|
||||
if not self._entered:
|
||||
__tracebackhide__ = True
|
||||
raise RuntimeError("Cannot exit %r without entering first" % self)
|
||||
# see above where `self._saved_warn` is assigned
|
||||
if six.PY2:
|
||||
warnings.warn = self._saved_warn
|
||||
super(WarningsRecorder, self).__exit__(*exc_info)
|
||||
|
||||
|
||||
@@ -196,7 +175,7 @@ class WarningsChecker(WarningsRecorder):
|
||||
def __init__(self, expected_warning=None, match_expr=None):
|
||||
super(WarningsChecker, self).__init__()
|
||||
|
||||
msg = "exceptions must be old-style classes or " "derived from Warning, not %s"
|
||||
msg = "exceptions must be old-style classes or derived from Warning, not %s"
|
||||
if isinstance(expected_warning, tuple):
|
||||
for exc in expected_warning:
|
||||
if not inspect.isclass(exc):
|
||||
|
||||
@@ -31,10 +31,9 @@ def pytest_configure(config):
|
||||
config.pluginmanager.register(config._resultlog)
|
||||
|
||||
from _pytest.deprecated import RESULT_LOG
|
||||
from _pytest.warning_types import RemovedInPytest4Warning
|
||||
from _pytest.warnings import _issue_config_warning
|
||||
|
||||
_issue_config_warning(RemovedInPytest4Warning(RESULT_LOG), config)
|
||||
_issue_config_warning(RESULT_LOG, config)
|
||||
|
||||
|
||||
def pytest_unconfigure(config):
|
||||
|
||||
@@ -30,6 +30,7 @@ def pytest_addoption(parser):
|
||||
|
||||
def pytest_terminal_summary(terminalreporter):
|
||||
durations = terminalreporter.config.option.durations
|
||||
verbose = terminalreporter.config.getvalue("verbose")
|
||||
if durations is None:
|
||||
return
|
||||
tr = terminalreporter
|
||||
@@ -49,6 +50,10 @@ def pytest_terminal_summary(terminalreporter):
|
||||
dlist = dlist[:durations]
|
||||
|
||||
for rep in dlist:
|
||||
if verbose < 2 and rep.duration < 0.005:
|
||||
tr.write_line("")
|
||||
tr.write_line("(0.00 durations hidden. Use -vv to show these durations.)")
|
||||
break
|
||||
nodeid = rep.nodeid.replace("::()::", "::")
|
||||
tr.write_line("%02.2fs %-8s %s" % (rep.duration, rep.when, nodeid))
|
||||
|
||||
|
||||
@@ -676,7 +676,9 @@ class TerminalReporter(object):
|
||||
|
||||
if fspath:
|
||||
res = mkrel(nodeid).replace("::()", "") # parens-normalization
|
||||
if nodeid.split("::")[0] != fspath.replace("\\", nodes.SEP):
|
||||
if self.verbosity >= 2 and nodeid.split("::")[0] != fspath.replace(
|
||||
"\\", nodes.SEP
|
||||
):
|
||||
res += " <- " + self.startdir.bestrelpath(fspath)
|
||||
else:
|
||||
res = "[location]"
|
||||
@@ -835,9 +837,7 @@ def repr_pythonversion(v=None):
|
||||
|
||||
|
||||
def build_summary_stats_line(stats):
|
||||
keys = (
|
||||
"failed passed skipped deselected " "xfailed xpassed warnings error"
|
||||
).split()
|
||||
keys = ("failed passed skipped deselected xfailed xpassed warnings error").split()
|
||||
unknown_key_seen = False
|
||||
for key in stats.keys():
|
||||
if key not in keys:
|
||||
|
||||
@@ -1,22 +1,86 @@
|
||||
""" support for providing temporary directories to test functions. """
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
import pytest
|
||||
import py
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
import attr
|
||||
import tempfile
|
||||
import warnings
|
||||
|
||||
from .pathlib import (
|
||||
Path,
|
||||
make_numbered_dir,
|
||||
make_numbered_dir_with_cleanup,
|
||||
ensure_reset_dir,
|
||||
LOCK_TIMEOUT,
|
||||
)
|
||||
|
||||
|
||||
class TempdirFactory(object):
|
||||
@attr.s
|
||||
class TempPathFactory(object):
|
||||
"""Factory for temporary directories under the common base temp directory.
|
||||
|
||||
The base directory can be configured using the ``--basetemp`` option.
|
||||
The base directory can be configured using the ``--basetemp`` option."""
|
||||
|
||||
_given_basetemp = attr.ib()
|
||||
_trace = attr.ib()
|
||||
_basetemp = attr.ib(default=None)
|
||||
|
||||
@classmethod
|
||||
def from_config(cls, config):
|
||||
"""
|
||||
:param config: a pytest configuration
|
||||
"""
|
||||
return cls(
|
||||
given_basetemp=config.option.basetemp, trace=config.trace.get("tmpdir")
|
||||
)
|
||||
|
||||
def mktemp(self, basename, numbered=True):
|
||||
"""makes a temporary directory managed by the factory"""
|
||||
if not numbered:
|
||||
p = self.getbasetemp().joinpath(basename)
|
||||
p.mkdir()
|
||||
else:
|
||||
p = make_numbered_dir(root=self.getbasetemp(), prefix=basename)
|
||||
self._trace("mktemp", p)
|
||||
return p
|
||||
|
||||
def getbasetemp(self):
|
||||
""" return base temporary directory. """
|
||||
if self._basetemp is None:
|
||||
if self._given_basetemp is not None:
|
||||
basetemp = Path(self._given_basetemp)
|
||||
ensure_reset_dir(basetemp)
|
||||
else:
|
||||
from_env = os.environ.get("PYTEST_DEBUG_TEMPROOT")
|
||||
temproot = Path(from_env or tempfile.gettempdir())
|
||||
user = get_user() or "unknown"
|
||||
# use a sub-directory in the temproot to speed-up
|
||||
# make_numbered_dir() call
|
||||
rootdir = temproot.joinpath("pytest-of-{}".format(user))
|
||||
rootdir.mkdir(exist_ok=True)
|
||||
basetemp = make_numbered_dir_with_cleanup(
|
||||
prefix="pytest-", root=rootdir, keep=3, lock_timeout=LOCK_TIMEOUT
|
||||
)
|
||||
assert basetemp is not None
|
||||
self._basetemp = t = basetemp
|
||||
self._trace("new basetemp", t)
|
||||
return t
|
||||
else:
|
||||
return self._basetemp
|
||||
|
||||
|
||||
@attr.s
|
||||
class TempdirFactory(object):
|
||||
"""
|
||||
backward comptibility wrapper that implements
|
||||
:class:``py.path.local`` for :class:``TempPathFactory``
|
||||
"""
|
||||
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
self.trace = config.trace.get("tmpdir")
|
||||
_tmppath_factory = attr.ib()
|
||||
|
||||
def ensuretemp(self, string, dir=1):
|
||||
""" (deprecated) return temporary directory path with
|
||||
@@ -26,6 +90,9 @@ class TempdirFactory(object):
|
||||
and is guaranteed to be empty.
|
||||
"""
|
||||
# py.log._apiwarn(">1.1", "use tmpdir function argument")
|
||||
from .deprecated import PYTEST_ENSURETEMP
|
||||
|
||||
warnings.warn(PYTEST_ENSURETEMP, stacklevel=2)
|
||||
return self.getbasetemp().ensure(string, dir=dir)
|
||||
|
||||
def mktemp(self, basename, numbered=True):
|
||||
@@ -33,46 +100,11 @@ class TempdirFactory(object):
|
||||
If ``numbered``, ensure the directory is unique by adding a number
|
||||
prefix greater than any existing one.
|
||||
"""
|
||||
basetemp = self.getbasetemp()
|
||||
if not numbered:
|
||||
p = basetemp.mkdir(basename)
|
||||
else:
|
||||
p = py.path.local.make_numbered_dir(
|
||||
prefix=basename, keep=0, rootdir=basetemp, lock_timeout=None
|
||||
)
|
||||
self.trace("mktemp", p)
|
||||
return p
|
||||
return py.path.local(self._tmppath_factory.mktemp(basename, numbered).resolve())
|
||||
|
||||
def getbasetemp(self):
|
||||
""" return base temporary directory. """
|
||||
try:
|
||||
return self._basetemp
|
||||
except AttributeError:
|
||||
basetemp = self.config.option.basetemp
|
||||
if basetemp:
|
||||
basetemp = py.path.local(basetemp)
|
||||
if basetemp.check():
|
||||
basetemp.remove()
|
||||
basetemp.mkdir()
|
||||
else:
|
||||
temproot = py.path.local.get_temproot()
|
||||
user = get_user()
|
||||
if user:
|
||||
# use a sub-directory in the temproot to speed-up
|
||||
# make_numbered_dir() call
|
||||
rootdir = temproot.join("pytest-of-%s" % user)
|
||||
else:
|
||||
rootdir = temproot
|
||||
rootdir.ensure(dir=1)
|
||||
basetemp = py.path.local.make_numbered_dir(
|
||||
prefix="pytest-", rootdir=rootdir
|
||||
)
|
||||
self._basetemp = t = basetemp.realpath()
|
||||
self.trace("new basetemp", t)
|
||||
return t
|
||||
|
||||
def finish(self):
|
||||
self.trace("finish")
|
||||
"""backward compat wrapper for ``_tmppath_factory.getbasetemp``"""
|
||||
return py.path.local(self._tmppath_factory.getbasetemp().resolve())
|
||||
|
||||
|
||||
def get_user():
|
||||
@@ -87,10 +119,6 @@ def get_user():
|
||||
return None
|
||||
|
||||
|
||||
# backward compatibility
|
||||
TempdirHandler = TempdirFactory
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
"""Create a TempdirFactory and attach it to the config object.
|
||||
|
||||
@@ -99,19 +127,36 @@ def pytest_configure(config):
|
||||
to the tmpdir_factory session fixture.
|
||||
"""
|
||||
mp = MonkeyPatch()
|
||||
t = TempdirFactory(config)
|
||||
config._cleanup.extend([mp.undo, t.finish])
|
||||
tmppath_handler = TempPathFactory.from_config(config)
|
||||
t = TempdirFactory(tmppath_handler)
|
||||
config._cleanup.append(mp.undo)
|
||||
mp.setattr(config, "_tmp_path_factory", tmppath_handler, raising=False)
|
||||
mp.setattr(config, "_tmpdirhandler", t, raising=False)
|
||||
mp.setattr(pytest, "ensuretemp", t.ensuretemp, raising=False)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def tmpdir_factory(request):
|
||||
"""Return a TempdirFactory instance for the test session.
|
||||
"""Return a :class:`_pytest.tmpdir.TempdirFactory` instance for the test session.
|
||||
"""
|
||||
return request.config._tmpdirhandler
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def tmp_path_factory(request):
|
||||
"""Return a :class:`_pytest.tmpdir.TempPathFactory` instance for the test session.
|
||||
"""
|
||||
return request.config._tmp_path_factory
|
||||
|
||||
|
||||
def _mk_tmp(request, factory):
|
||||
name = request.node.name
|
||||
name = re.sub(r"[\W]", "_", name)
|
||||
MAXVAL = 30
|
||||
name = name[:MAXVAL]
|
||||
return factory.mktemp(name, numbered=True)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def tmpdir(request, tmpdir_factory):
|
||||
"""Return a temporary directory path object
|
||||
@@ -122,10 +167,20 @@ def tmpdir(request, tmpdir_factory):
|
||||
|
||||
.. _`py.path.local`: https://py.readthedocs.io/en/latest/path.html
|
||||
"""
|
||||
name = request.node.name
|
||||
name = re.sub(r"[\W]", "_", name)
|
||||
MAXVAL = 30
|
||||
if len(name) > MAXVAL:
|
||||
name = name[:MAXVAL]
|
||||
x = tmpdir_factory.mktemp(name, numbered=True)
|
||||
return x
|
||||
return _mk_tmp(request, tmpdir_factory)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def tmp_path(request, tmp_path_factory):
|
||||
"""Return a temporary directory path object
|
||||
which is unique to each test function invocation,
|
||||
created as a sub directory of the base temporary
|
||||
directory. The returned object is a :class:`pathlib.Path`
|
||||
object.
|
||||
|
||||
.. note::
|
||||
|
||||
in python < 3.6 this is a pathlib2.Path
|
||||
"""
|
||||
|
||||
return _mk_tmp(request, tmp_path_factory)
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import attr
|
||||
|
||||
|
||||
class PytestWarning(UserWarning):
|
||||
"""
|
||||
Bases: :class:`UserWarning`.
|
||||
@@ -39,4 +42,19 @@ class PytestExperimentalApiWarning(PytestWarning, FutureWarning):
|
||||
)
|
||||
|
||||
|
||||
@attr.s
|
||||
class UnformattedWarning(object):
|
||||
"""Used to hold warnings that need to format their message at runtime, as opposed to a direct message.
|
||||
|
||||
Using this class avoids to keep all the warning types and messages in this module, avoiding misuse.
|
||||
"""
|
||||
|
||||
category = attr.ib()
|
||||
template = attr.ib()
|
||||
|
||||
def format(self, **kwargs):
|
||||
"""Returns an instance of the warning category, formatted with given kwargs"""
|
||||
return self.category(self.template.format(**kwargs))
|
||||
|
||||
|
||||
PYTESTER_COPY_EXAMPLE = PytestExperimentalApiWarning.simple("testdir.copy_example")
|
||||
|
||||
@@ -67,27 +67,27 @@ def catch_warnings_for_item(config, ihook, when, item):
|
||||
|
||||
Each warning captured triggers the ``pytest_warning_captured`` hook.
|
||||
"""
|
||||
args = config.getoption("pythonwarnings") or []
|
||||
cmdline_filters = config.getoption("pythonwarnings") or []
|
||||
inifilters = config.getini("filterwarnings")
|
||||
with warnings.catch_warnings(record=True) as log:
|
||||
filters_configured = args or inifilters or sys.warnoptions
|
||||
|
||||
for arg in args:
|
||||
warnings._setoption(arg)
|
||||
if not sys.warnoptions:
|
||||
# if user is not explicitly configuring warning filters, show deprecation warnings by default (#2908)
|
||||
warnings.filterwarnings("always", category=DeprecationWarning)
|
||||
warnings.filterwarnings("always", category=PendingDeprecationWarning)
|
||||
|
||||
# filters should have this precedence: mark, cmdline options, ini
|
||||
# filters should be applied in the inverse order of precedence
|
||||
for arg in inifilters:
|
||||
_setoption(warnings, arg)
|
||||
|
||||
for arg in cmdline_filters:
|
||||
warnings._setoption(arg)
|
||||
|
||||
if item is not None:
|
||||
for mark in item.iter_markers(name="filterwarnings"):
|
||||
for arg in mark.args:
|
||||
_setoption(warnings, arg)
|
||||
filters_configured = True
|
||||
|
||||
if not filters_configured:
|
||||
# if user is not explicitly configuring warning filters, show deprecation warnings by default (#2908)
|
||||
warnings.filterwarnings("always", category=DeprecationWarning)
|
||||
warnings.filterwarnings("always", category=PendingDeprecationWarning)
|
||||
|
||||
yield
|
||||
|
||||
|
||||
@@ -12,6 +12,13 @@ import pytest
|
||||
from _pytest.main import EXIT_NOTESTSCOLLECTED, EXIT_USAGEERROR
|
||||
|
||||
|
||||
def prepend_pythonpath(*dirs):
|
||||
cur = os.getenv("PYTHONPATH")
|
||||
if cur:
|
||||
dirs += (cur,)
|
||||
return os.pathsep.join(str(p) for p in dirs)
|
||||
|
||||
|
||||
class TestGeneralUsage(object):
|
||||
def test_config_error(self, testdir):
|
||||
testdir.copy_example("conftest_usageerror/conftest.py")
|
||||
@@ -133,9 +140,16 @@ class TestGeneralUsage(object):
|
||||
assert result.ret
|
||||
result.stderr.fnmatch_lines(["*ERROR: not found:*{}".format(p2.basename)])
|
||||
|
||||
def test_issue486_better_reporting_on_conftest_load_failure(self, testdir):
|
||||
def test_better_reporting_on_conftest_load_failure(self, testdir, request):
|
||||
"""Show a user-friendly traceback on conftest import failures (#486, #3332)"""
|
||||
testdir.makepyfile("")
|
||||
testdir.makeconftest("import qwerty")
|
||||
testdir.makeconftest(
|
||||
"""
|
||||
def foo():
|
||||
import qwerty
|
||||
foo()
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest("--help")
|
||||
result.stdout.fnmatch_lines(
|
||||
"""
|
||||
@@ -144,10 +158,23 @@ class TestGeneralUsage(object):
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest()
|
||||
dirname = request.node.name + "0"
|
||||
exc_name = (
|
||||
"ModuleNotFoundError" if sys.version_info >= (3, 6) else "ImportError"
|
||||
)
|
||||
result.stderr.fnmatch_lines(
|
||||
"""
|
||||
*ERROR*could not load*conftest.py*
|
||||
"""
|
||||
[
|
||||
"ImportError while loading conftest '*{sep}{dirname}{sep}conftest.py'.".format(
|
||||
dirname=dirname, sep=os.sep
|
||||
),
|
||||
"conftest.py:3: in <module>",
|
||||
" foo()",
|
||||
"conftest.py:2: in foo",
|
||||
" import qwerty",
|
||||
"E {}: No module named {q}qwerty{q}".format(
|
||||
exc_name, q="'" if six.PY3 else ""
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
def test_early_skip(self, testdir):
|
||||
@@ -570,14 +597,8 @@ class TestInvocationVariants(object):
|
||||
assert result.ret == 0
|
||||
result.stdout.fnmatch_lines(["*1 passed*"])
|
||||
|
||||
def join_pythonpath(what):
|
||||
cur = os.environ.get("PYTHONPATH")
|
||||
if cur:
|
||||
return str(what) + os.pathsep + cur
|
||||
return what
|
||||
|
||||
empty_package = testdir.mkpydir("empty_package")
|
||||
monkeypatch.setenv("PYTHONPATH", str(join_pythonpath(empty_package)))
|
||||
monkeypatch.setenv("PYTHONPATH", str(empty_package), prepend=os.pathsep)
|
||||
# the path which is not a package raises a warning on pypy;
|
||||
# no idea why only pypy and not normal python warn about it here
|
||||
with warnings.catch_warnings():
|
||||
@@ -586,7 +607,7 @@ class TestInvocationVariants(object):
|
||||
assert result.ret == 0
|
||||
result.stdout.fnmatch_lines(["*2 passed*"])
|
||||
|
||||
monkeypatch.setenv("PYTHONPATH", str(join_pythonpath(testdir)))
|
||||
monkeypatch.setenv("PYTHONPATH", str(testdir), prepend=os.pathsep)
|
||||
result = testdir.runpytest("--pyargs", "tpkg.test_missing", syspathinsert=True)
|
||||
assert result.ret != 0
|
||||
result.stderr.fnmatch_lines(["*not*found*test_missing*"])
|
||||
@@ -608,7 +629,7 @@ class TestInvocationVariants(object):
|
||||
lib = ns.mkdir(dirname)
|
||||
lib.ensure("__init__.py")
|
||||
lib.join("test_{}.py".format(dirname)).write(
|
||||
"def test_{}(): pass\n" "def test_other():pass".format(dirname)
|
||||
"def test_{}(): pass\ndef test_other():pass".format(dirname)
|
||||
)
|
||||
|
||||
# The structure of the test directory is now:
|
||||
@@ -626,18 +647,13 @@ class TestInvocationVariants(object):
|
||||
# ├── __init__.py
|
||||
# └── test_world.py
|
||||
|
||||
def join_pythonpath(*dirs):
|
||||
cur = os.environ.get("PYTHONPATH")
|
||||
if cur:
|
||||
dirs += (cur,)
|
||||
return os.pathsep.join(str(p) for p in dirs)
|
||||
|
||||
monkeypatch.setenv("PYTHONPATH", join_pythonpath(*search_path))
|
||||
# NOTE: the different/reversed ordering is intentional here.
|
||||
monkeypatch.setenv("PYTHONPATH", prepend_pythonpath(*search_path))
|
||||
for p in search_path:
|
||||
monkeypatch.syspath_prepend(p)
|
||||
|
||||
# mixed module and filenames:
|
||||
os.chdir("world")
|
||||
monkeypatch.chdir("world")
|
||||
result = testdir.runpytest("--pyargs", "-v", "ns_pkg.hello", "ns_pkg/world")
|
||||
assert result.ret == 0
|
||||
result.stdout.fnmatch_lines(
|
||||
@@ -688,8 +704,6 @@ class TestInvocationVariants(object):
|
||||
pytest.skip(six.text_type(e.args[0]))
|
||||
monkeypatch.delenv("PYTHONDONTWRITEBYTECODE", raising=False)
|
||||
|
||||
search_path = ["lib", os.path.join("local", "lib")]
|
||||
|
||||
dirname = "lib"
|
||||
d = testdir.mkdir(dirname)
|
||||
foo = d.mkdir("foo")
|
||||
@@ -697,10 +711,10 @@ class TestInvocationVariants(object):
|
||||
lib = foo.mkdir("bar")
|
||||
lib.ensure("__init__.py")
|
||||
lib.join("test_bar.py").write(
|
||||
"def test_bar(): pass\n" "def test_other(a_fixture):pass"
|
||||
"def test_bar(): pass\ndef test_other(a_fixture):pass"
|
||||
)
|
||||
lib.join("conftest.py").write(
|
||||
"import pytest\n" "@pytest.fixture\n" "def a_fixture():pass"
|
||||
"import pytest\n@pytest.fixture\ndef a_fixture():pass"
|
||||
)
|
||||
|
||||
d_local = testdir.mkdir("local")
|
||||
@@ -722,27 +736,33 @@ class TestInvocationVariants(object):
|
||||
# ├── conftest.py
|
||||
# └── test_bar.py
|
||||
|
||||
def join_pythonpath(*dirs):
|
||||
cur = os.getenv("PYTHONPATH")
|
||||
if cur:
|
||||
dirs += (cur,)
|
||||
return os.pathsep.join(str(p) for p in dirs)
|
||||
|
||||
monkeypatch.setenv("PYTHONPATH", join_pythonpath(*search_path))
|
||||
# NOTE: the different/reversed ordering is intentional here.
|
||||
search_path = ["lib", os.path.join("local", "lib")]
|
||||
monkeypatch.setenv("PYTHONPATH", prepend_pythonpath(*search_path))
|
||||
for p in search_path:
|
||||
monkeypatch.syspath_prepend(p)
|
||||
|
||||
# module picked up in symlink-ed directory:
|
||||
# It picks up local/lib/foo/bar (symlink) via sys.path.
|
||||
result = testdir.runpytest("--pyargs", "-v", "foo.bar")
|
||||
testdir.chdir()
|
||||
assert result.ret == 0
|
||||
result.stdout.fnmatch_lines(
|
||||
[
|
||||
"*lib/foo/bar/test_bar.py::test_bar*PASSED*",
|
||||
"*lib/foo/bar/test_bar.py::test_other*PASSED*",
|
||||
"*2 passed*",
|
||||
]
|
||||
)
|
||||
if hasattr(py.path.local, "mksymlinkto"):
|
||||
result.stdout.fnmatch_lines(
|
||||
[
|
||||
"lib/foo/bar/test_bar.py::test_bar PASSED*",
|
||||
"lib/foo/bar/test_bar.py::test_other PASSED*",
|
||||
"*2 passed*",
|
||||
]
|
||||
)
|
||||
else:
|
||||
result.stdout.fnmatch_lines(
|
||||
[
|
||||
"*lib/foo/bar/test_bar.py::test_bar PASSED*",
|
||||
"*lib/foo/bar/test_bar.py::test_other PASSED*",
|
||||
"*2 passed*",
|
||||
]
|
||||
)
|
||||
|
||||
def test_cmdline_python_package_not_exists(self, testdir):
|
||||
result = testdir.runpytest("--pyargs", "tpkgwhatv")
|
||||
@@ -816,7 +836,10 @@ class TestDurations(object):
|
||||
result = testdir.runpytest("--durations=10")
|
||||
assert result.ret == 0
|
||||
result.stdout.fnmatch_lines_random(
|
||||
["*durations*", "*call*test_3*", "*call*test_2*", "*call*test_1*"]
|
||||
["*durations*", "*call*test_3*", "*call*test_2*"]
|
||||
)
|
||||
result.stdout.fnmatch_lines(
|
||||
["(0.00 durations hidden. Use -vv to show these durations.)"]
|
||||
)
|
||||
|
||||
def test_calls_show_2(self, testdir):
|
||||
@@ -830,6 +853,18 @@ class TestDurations(object):
|
||||
testdir.makepyfile(self.source)
|
||||
result = testdir.runpytest("--durations=0")
|
||||
assert result.ret == 0
|
||||
for x in "23":
|
||||
for y in ("call",): # 'setup', 'call', 'teardown':
|
||||
for line in result.stdout.lines:
|
||||
if ("test_%s" % x) in line and y in line:
|
||||
break
|
||||
else:
|
||||
raise AssertionError("not found {} {}".format(x, y))
|
||||
|
||||
def test_calls_showall_verbose(self, testdir):
|
||||
testdir.makepyfile(self.source)
|
||||
result = testdir.runpytest("--durations=0", "-vv")
|
||||
assert result.ret == 0
|
||||
for x in "123":
|
||||
for y in ("call",): # 'setup', 'call', 'teardown':
|
||||
for line in result.stdout.lines:
|
||||
@@ -840,9 +875,9 @@ class TestDurations(object):
|
||||
|
||||
def test_with_deselected(self, testdir):
|
||||
testdir.makepyfile(self.source)
|
||||
result = testdir.runpytest("--durations=2", "-k test_1")
|
||||
result = testdir.runpytest("--durations=2", "-k test_2")
|
||||
assert result.ret == 0
|
||||
result.stdout.fnmatch_lines(["*durations*", "*call*test_1*"])
|
||||
result.stdout.fnmatch_lines(["*durations*", "*call*test_2*"])
|
||||
|
||||
def test_with_failing_collection(self, testdir):
|
||||
testdir.makepyfile(self.source)
|
||||
@@ -862,13 +897,15 @@ class TestDurations(object):
|
||||
|
||||
class TestDurationWithFixture(object):
|
||||
source = """
|
||||
import pytest
|
||||
import time
|
||||
frag = 0.001
|
||||
def setup_function(func):
|
||||
time.sleep(frag * 3)
|
||||
def test_1():
|
||||
time.sleep(frag*2)
|
||||
def test_2():
|
||||
frag = 0.01
|
||||
|
||||
@pytest.fixture
|
||||
def setup_fixt():
|
||||
time.sleep(frag)
|
||||
|
||||
def test_1(setup_fixt):
|
||||
time.sleep(frag)
|
||||
"""
|
||||
|
||||
|
||||
@@ -31,6 +31,14 @@ failsonjython = pytest.mark.xfail("sys.platform.startswith('java')")
|
||||
pytest_version_info = tuple(map(int, pytest.__version__.split(".")[:3]))
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def limited_recursion_depth():
|
||||
before = sys.getrecursionlimit()
|
||||
sys.setrecursionlimit(150)
|
||||
yield
|
||||
sys.setrecursionlimit(before)
|
||||
|
||||
|
||||
class TWMock(object):
|
||||
WRITE = object()
|
||||
|
||||
@@ -239,7 +247,7 @@ class TestTraceback_f_g_h(object):
|
||||
raise RuntimeError("hello")
|
||||
f(n - 1)
|
||||
|
||||
excinfo = pytest.raises(RuntimeError, f, 100)
|
||||
excinfo = pytest.raises(RuntimeError, f, 25)
|
||||
monkeypatch.delattr(excinfo.traceback.__class__, "recursionindex")
|
||||
repr = excinfo.getrepr()
|
||||
assert "RuntimeError: hello" in str(repr.reprcrash)
|
||||
@@ -1176,20 +1184,28 @@ raise ValueError()
|
||||
assert tw.lines[47] == ":15: AttributeError"
|
||||
|
||||
@pytest.mark.skipif("sys.version_info[0] < 3")
|
||||
def test_exc_repr_with_raise_from_none_chain_suppression(self, importasmod):
|
||||
@pytest.mark.parametrize("mode", ["from_none", "explicit_suppress"])
|
||||
def test_exc_repr_chain_suppression(self, importasmod, mode):
|
||||
"""Check that exc repr does not show chained exceptions in Python 3.
|
||||
- When the exception is raised with "from None"
|
||||
- Explicitly suppressed with "chain=False" to ExceptionInfo.getrepr().
|
||||
"""
|
||||
raise_suffix = " from None" if mode == "from_none" else ""
|
||||
mod = importasmod(
|
||||
"""
|
||||
def f():
|
||||
try:
|
||||
g()
|
||||
except Exception:
|
||||
raise AttributeError() from None
|
||||
raise AttributeError(){raise_suffix}
|
||||
def g():
|
||||
raise ValueError()
|
||||
"""
|
||||
""".format(
|
||||
raise_suffix=raise_suffix
|
||||
)
|
||||
)
|
||||
excinfo = pytest.raises(AttributeError, mod.f)
|
||||
r = excinfo.getrepr(style="long")
|
||||
r = excinfo.getrepr(style="long", chain=mode != "explicit_suppress")
|
||||
tw = TWMock()
|
||||
r.toterminal(tw)
|
||||
for line in tw.lines:
|
||||
@@ -1199,7 +1215,9 @@ raise ValueError()
|
||||
assert tw.lines[2] == " try:"
|
||||
assert tw.lines[3] == " g()"
|
||||
assert tw.lines[4] == " except Exception:"
|
||||
assert tw.lines[5] == "> raise AttributeError() from None"
|
||||
assert tw.lines[5] == "> raise AttributeError(){}".format(
|
||||
raise_suffix
|
||||
)
|
||||
assert tw.lines[6] == "E AttributeError"
|
||||
assert tw.lines[7] == ""
|
||||
line = tw.get_write_msg(8)
|
||||
@@ -1341,11 +1359,13 @@ def test_cwd_deleted(testdir):
|
||||
assert "INTERNALERROR" not in result.stdout.str() + result.stderr.str()
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("limited_recursion_depth")
|
||||
def test_exception_repr_extraction_error_on_recursion():
|
||||
"""
|
||||
Ensure we can properly detect a recursion error even
|
||||
if some locals raise error on comparison (#2459).
|
||||
"""
|
||||
from _pytest.pytester import LineMatcher
|
||||
|
||||
class numpy_like(object):
|
||||
def __eq__(self, other):
|
||||
@@ -1361,40 +1381,30 @@ def test_exception_repr_extraction_error_on_recursion():
|
||||
def b(x):
|
||||
return a(numpy_like())
|
||||
|
||||
try:
|
||||
with pytest.raises(RuntimeError) as excinfo:
|
||||
a(numpy_like())
|
||||
except: # noqa
|
||||
from _pytest._code.code import ExceptionInfo
|
||||
from _pytest.pytester import LineMatcher
|
||||
|
||||
exc_info = ExceptionInfo()
|
||||
|
||||
matcher = LineMatcher(str(exc_info.getrepr()).splitlines())
|
||||
matcher.fnmatch_lines(
|
||||
[
|
||||
"!!! Recursion error detected, but an error occurred locating the origin of recursion.",
|
||||
"*The following exception happened*",
|
||||
"*ValueError: The truth value of an array*",
|
||||
]
|
||||
)
|
||||
matcher = LineMatcher(str(excinfo.getrepr()).splitlines())
|
||||
matcher.fnmatch_lines(
|
||||
[
|
||||
"!!! Recursion error detected, but an error occurred locating the origin of recursion.",
|
||||
"*The following exception happened*",
|
||||
"*ValueError: The truth value of an array*",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("limited_recursion_depth")
|
||||
def test_no_recursion_index_on_recursion_error():
|
||||
"""
|
||||
Ensure that we don't break in case we can't find the recursion index
|
||||
during a recursion error (#2486).
|
||||
"""
|
||||
try:
|
||||
|
||||
class RecursionDepthError(object):
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self, "_" + attr)
|
||||
class RecursionDepthError(object):
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self, "_" + attr)
|
||||
|
||||
with pytest.raises(RuntimeError) as excinfo:
|
||||
RecursionDepthError().trigger
|
||||
except: # noqa
|
||||
from _pytest._code.code import ExceptionInfo
|
||||
|
||||
exc_info = ExceptionInfo()
|
||||
assert "maximum recursion" in str(exc_info.getrepr())
|
||||
else:
|
||||
assert 0
|
||||
assert "maximum recursion" in str(excinfo.getrepr())
|
||||
|
||||
@@ -27,16 +27,7 @@ def test_source_str_function():
|
||||
x = Source(
|
||||
"""
|
||||
3
|
||||
""",
|
||||
rstrip=False,
|
||||
)
|
||||
assert str(x) == "\n3\n "
|
||||
|
||||
x = Source(
|
||||
"""
|
||||
3
|
||||
""",
|
||||
rstrip=True,
|
||||
)
|
||||
assert str(x) == "\n3"
|
||||
|
||||
@@ -400,10 +391,13 @@ def test_getfuncsource_with_multine_string():
|
||||
pass
|
||||
"""
|
||||
|
||||
assert (
|
||||
str(_pytest._code.Source(f)).strip()
|
||||
== 'def f():\n c = """while True:\n pass\n"""'
|
||||
)
|
||||
expected = '''\
|
||||
def f():
|
||||
c = """while True:
|
||||
pass
|
||||
"""
|
||||
'''
|
||||
assert str(_pytest._code.Source(f)) == expected.rstrip()
|
||||
|
||||
|
||||
def test_deindent():
|
||||
@@ -411,21 +405,13 @@ def test_deindent():
|
||||
|
||||
assert deindent(["\tfoo", "\tbar"]) == ["foo", "bar"]
|
||||
|
||||
def f():
|
||||
c = """while True:
|
||||
pass
|
||||
"""
|
||||
|
||||
lines = deindent(inspect.getsource(f).splitlines())
|
||||
assert lines == ["def f():", ' c = """while True:', " pass", '"""']
|
||||
|
||||
source = """
|
||||
source = """\
|
||||
def f():
|
||||
def g():
|
||||
pass
|
||||
"""
|
||||
lines = deindent(source.splitlines())
|
||||
assert lines == ["", "def f():", " def g():", " pass", " "]
|
||||
assert lines == ["def f():", " def g():", " pass"]
|
||||
|
||||
|
||||
def test_source_of_class_at_eof_without_newline(tmpdir):
|
||||
|
||||
@@ -30,6 +30,74 @@ def test_yield_tests_deprecation(testdir):
|
||||
assert result.stdout.str().count("yield tests are deprecated") == 2
|
||||
|
||||
|
||||
def test_compat_properties_deprecation(testdir):
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
def test_foo(request):
|
||||
print(request.node.Module)
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines(
|
||||
[
|
||||
"*test_compat_properties_deprecation.py:2:*usage of Function.Module is deprecated, "
|
||||
"please use pytest.Module instead*",
|
||||
"*1 passed, 1 warnings in*",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def test_cached_setup_deprecation(testdir):
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
import pytest
|
||||
@pytest.fixture
|
||||
def fix(request):
|
||||
return request.cached_setup(lambda: 1)
|
||||
|
||||
def test_foo(fix):
|
||||
assert fix == 1
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines(
|
||||
[
|
||||
"*test_cached_setup_deprecation.py:4:*cached_setup is deprecated*",
|
||||
"*1 passed, 1 warnings in*",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def test_custom_class_deprecation(testdir):
|
||||
testdir.makeconftest(
|
||||
"""
|
||||
import pytest
|
||||
|
||||
class MyModule(pytest.Module):
|
||||
|
||||
class Class(pytest.Class):
|
||||
pass
|
||||
|
||||
def pytest_pycollect_makemodule(path, parent):
|
||||
return MyModule(path, parent)
|
||||
"""
|
||||
)
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
class Test:
|
||||
def test_foo(self):
|
||||
pass
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines(
|
||||
[
|
||||
'*test_custom_class_deprecation.py:1:*"Class" objects in collectors of type "MyModule*',
|
||||
"*1 passed, 1 warnings in*",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.filterwarnings("default")
|
||||
def test_funcarg_prefix_deprecation(testdir):
|
||||
testdir.makepyfile(
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def dynamic():
|
||||
pass
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def a(request):
|
||||
request.getfixturevalue("dynamic")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def b(a):
|
||||
pass
|
||||
|
||||
|
||||
def test(b, request):
|
||||
assert request.fixturenames == ["b", "request", "a", "dynamic"]
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import pytest
|
||||
import pprint
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import pytest
|
||||
import unittest
|
||||
|
||||
|
||||
@pytest.fixture(params=[1, 2])
|
||||
def two(request):
|
||||
return request.param
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("two")
|
||||
class TestSomethingElse(unittest.TestCase):
|
||||
def test_two(self):
|
||||
pass
|
||||
@@ -45,7 +45,7 @@ def test_change_level_undo(testdir):
|
||||
assert 0
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest_subprocess()
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines(["*log from test1*", "*2 failed in *"])
|
||||
assert "log from test2" not in result.stdout.str()
|
||||
|
||||
|
||||
@@ -26,10 +26,10 @@ def test_coloredlogformatter():
|
||||
formatter = ColoredLevelFormatter(tw, logfmt)
|
||||
output = formatter.format(record)
|
||||
assert output == (
|
||||
"dummypath 10 " "\x1b[32mINFO \x1b[0m Test Message"
|
||||
"dummypath 10 \x1b[32mINFO \x1b[0m Test Message"
|
||||
)
|
||||
|
||||
tw.hasmarkup = False
|
||||
formatter = ColoredLevelFormatter(tw, logfmt)
|
||||
output = formatter.format(record)
|
||||
assert output == ("dummypath 10 " "INFO Test Message")
|
||||
assert output == ("dummypath 10 INFO Test Message")
|
||||
|
||||
@@ -908,3 +908,61 @@ def test_live_logging_suspends_capture(has_capture_manager, request):
|
||||
else:
|
||||
assert MockCaptureManager.calls == []
|
||||
assert out_file.getvalue() == "\nsome message\n"
|
||||
|
||||
|
||||
def test_collection_live_logging(testdir):
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
import logging
|
||||
|
||||
logging.getLogger().info("Normal message")
|
||||
"""
|
||||
)
|
||||
|
||||
result = testdir.runpytest("--log-cli-level=INFO")
|
||||
result.stdout.fnmatch_lines(
|
||||
[
|
||||
"collecting*",
|
||||
"*--- live log collection ---*",
|
||||
"*Normal message*",
|
||||
"collected 0 items",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def test_collection_logging_to_file(testdir):
|
||||
log_file = testdir.tmpdir.join("pytest.log").strpath
|
||||
|
||||
testdir.makeini(
|
||||
"""
|
||||
[pytest]
|
||||
log_file={}
|
||||
log_file_level = INFO
|
||||
""".format(
|
||||
log_file
|
||||
)
|
||||
)
|
||||
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
import logging
|
||||
|
||||
logging.getLogger().info("Normal message")
|
||||
|
||||
def test_simple():
|
||||
logging.getLogger().debug("debug message in test_simple")
|
||||
logging.getLogger().info("info message in test_simple")
|
||||
"""
|
||||
)
|
||||
|
||||
result = testdir.runpytest()
|
||||
|
||||
assert "--- live log collection ---" not in result.stdout.str()
|
||||
|
||||
assert result.ret == 0
|
||||
assert os.path.isfile(log_file)
|
||||
with open(log_file, encoding="utf-8") as rfh:
|
||||
contents = rfh.read()
|
||||
assert "Normal message" in contents
|
||||
assert "debug message in test_simple" not in contents
|
||||
assert "info message in test_simple" in contents
|
||||
|
||||
@@ -62,11 +62,11 @@ class TestApprox(object):
|
||||
@pytest.mark.parametrize(
|
||||
"value, repr_string",
|
||||
[
|
||||
(5., "approx(5.0 {pm} 5.0e-06)"),
|
||||
([5.], "approx([5.0 {pm} 5.0e-06])"),
|
||||
([[5.]], "approx([[5.0 {pm} 5.0e-06]])"),
|
||||
([[5., 6.]], "approx([[5.0 {pm} 5.0e-06, 6.0 {pm} 6.0e-06]])"),
|
||||
([[5.], [6.]], "approx([[5.0 {pm} 5.0e-06], [6.0 {pm} 6.0e-06]])"),
|
||||
(5.0, "approx(5.0 {pm} 5.0e-06)"),
|
||||
([5.0], "approx([5.0 {pm} 5.0e-06])"),
|
||||
([[5.0]], "approx([[5.0 {pm} 5.0e-06]])"),
|
||||
([[5.0, 6.0]], "approx([[5.0 {pm} 5.0e-06, 6.0 {pm} 6.0e-06]])"),
|
||||
([[5.0], [6.0]], "approx([[5.0 {pm} 5.0e-06], [6.0 {pm} 6.0e-06]])"),
|
||||
],
|
||||
)
|
||||
def test_repr_nd_array(self, plus_minus, value, repr_string):
|
||||
@@ -354,16 +354,16 @@ class TestApprox(object):
|
||||
Test all permutations of where the approx and np.array() can show up
|
||||
"""
|
||||
np = pytest.importorskip("numpy")
|
||||
expected = 100.
|
||||
actual = 99.
|
||||
expected = 100.0
|
||||
actual = 99.0
|
||||
abs_diff = expected - actual
|
||||
rel_diff = (expected - actual) / expected
|
||||
|
||||
tests = [
|
||||
(eq, abs_diff, 0),
|
||||
(eq, 0, rel_diff),
|
||||
(ne, 0, rel_diff / 2.), # rel diff fail
|
||||
(ne, abs_diff / 2., 0), # abs diff fail
|
||||
(ne, 0, rel_diff / 2.0), # rel diff fail
|
||||
(ne, abs_diff / 2.0, 0), # abs diff fail
|
||||
]
|
||||
|
||||
for op, _abs, _rel in tests:
|
||||
|
||||
@@ -240,6 +240,9 @@ class TestClass(object):
|
||||
assert result.ret == EXIT_NOTESTSCOLLECTED
|
||||
|
||||
|
||||
@pytest.mark.filterwarnings(
|
||||
"ignore:usage of Generator.Function is deprecated, please use pytest.Function instead"
|
||||
)
|
||||
class TestGenerator(object):
|
||||
def test_generative_functions(self, testdir):
|
||||
modcol = testdir.getmodulecol(
|
||||
@@ -1255,6 +1258,9 @@ class TestReportInfo(object):
|
||||
assert lineno == 1
|
||||
assert msg == "TestClass"
|
||||
|
||||
@pytest.mark.filterwarnings(
|
||||
"ignore:usage of Generator.Function is deprecated, please use pytest.Function instead"
|
||||
)
|
||||
def test_generator_reportinfo(self, testdir):
|
||||
modcol = testdir.getmodulecol(
|
||||
"""
|
||||
|
||||
@@ -6,7 +6,7 @@ import pytest
|
||||
from _pytest.pytester import get_public_names
|
||||
from _pytest.fixtures import FixtureLookupError, FixtureRequest
|
||||
from _pytest import fixtures
|
||||
from _pytest.compat import Path
|
||||
from _pytest.pathlib import Path
|
||||
|
||||
|
||||
def test_getfuncargnames():
|
||||
@@ -494,6 +494,12 @@ class TestRequestBasic(object):
|
||||
reason="this method of test doesn't work on pypy",
|
||||
)
|
||||
def test_request_garbage(self, testdir):
|
||||
try:
|
||||
import xdist # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
pytest.xfail("this test is flaky when executed with xdist")
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
import sys
|
||||
@@ -756,6 +762,12 @@ class TestRequestBasic(object):
|
||||
reprec = testdir.inline_run()
|
||||
reprec.assertoutcome(passed=1)
|
||||
|
||||
def test_request_fixturenames_dynamic_fixture(self, testdir):
|
||||
"""Regression test for #3057"""
|
||||
testdir.copy_example("fixtures/test_getfixturevalue_dynamic.py")
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines("*1 passed*")
|
||||
|
||||
def test_funcargnames_compatattr(self, testdir):
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
@@ -981,6 +993,7 @@ class TestRequestCachedSetup(object):
|
||||
)
|
||||
reprec.assertoutcome(passed=4)
|
||||
|
||||
@pytest.mark.filterwarnings("ignore:cached_setup is deprecated")
|
||||
def test_request_cachedsetup_extrakey(self, testdir):
|
||||
item1 = testdir.getitem("def test_func(): pass")
|
||||
req1 = fixtures.FixtureRequest(item1)
|
||||
@@ -998,6 +1011,7 @@ class TestRequestCachedSetup(object):
|
||||
assert ret1 == ret1b
|
||||
assert ret2 == ret2b
|
||||
|
||||
@pytest.mark.filterwarnings("ignore:cached_setup is deprecated")
|
||||
def test_request_cachedsetup_cache_deletion(self, testdir):
|
||||
item1 = testdir.getitem("def test_func(): pass")
|
||||
req1 = fixtures.FixtureRequest(item1)
|
||||
@@ -1209,8 +1223,7 @@ class TestFixtureUsages(object):
|
||||
result = testdir.runpytest_inprocess()
|
||||
result.stdout.fnmatch_lines(
|
||||
(
|
||||
"*ValueError: fixture badscope from test_invalid_scope.py has an unsupported"
|
||||
" scope value 'functions'"
|
||||
"*Fixture 'badscope' from test_invalid_scope.py got an unexpected scope value 'functions'"
|
||||
)
|
||||
)
|
||||
|
||||
@@ -3599,15 +3612,15 @@ class TestParameterizedSubRequest(object):
|
||||
)
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines(
|
||||
"""
|
||||
E*Failed: The requested fixture has no parameter defined for the current test.
|
||||
E*
|
||||
E*Requested fixture 'fix_with_param' defined in:
|
||||
E*test_call_from_fixture.py:4
|
||||
E*Requested here:
|
||||
E*test_call_from_fixture.py:9
|
||||
*1 error*
|
||||
"""
|
||||
[
|
||||
"The requested fixture has no parameter defined for test:",
|
||||
" test_call_from_fixture.py::test_foo",
|
||||
"Requested fixture 'fix_with_param' defined in:",
|
||||
"test_call_from_fixture.py:4",
|
||||
"Requested here:",
|
||||
"test_call_from_fixture.py:9",
|
||||
"*1 error in*",
|
||||
]
|
||||
)
|
||||
|
||||
def test_call_from_test(self, testdir):
|
||||
@@ -3625,15 +3638,15 @@ class TestParameterizedSubRequest(object):
|
||||
)
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines(
|
||||
"""
|
||||
E*Failed: The requested fixture has no parameter defined for the current test.
|
||||
E*
|
||||
E*Requested fixture 'fix_with_param' defined in:
|
||||
E*test_call_from_test.py:4
|
||||
E*Requested here:
|
||||
E*test_call_from_test.py:8
|
||||
*1 failed*
|
||||
"""
|
||||
[
|
||||
"The requested fixture has no parameter defined for test:",
|
||||
" test_call_from_test.py::test_foo",
|
||||
"Requested fixture 'fix_with_param' defined in:",
|
||||
"test_call_from_test.py:4",
|
||||
"Requested here:",
|
||||
"test_call_from_test.py:8",
|
||||
"*1 failed*",
|
||||
]
|
||||
)
|
||||
|
||||
def test_external_fixture(self, testdir):
|
||||
@@ -3655,15 +3668,16 @@ class TestParameterizedSubRequest(object):
|
||||
)
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines(
|
||||
"""
|
||||
E*Failed: The requested fixture has no parameter defined for the current test.
|
||||
E*
|
||||
E*Requested fixture 'fix_with_param' defined in:
|
||||
E*conftest.py:4
|
||||
E*Requested here:
|
||||
E*test_external_fixture.py:2
|
||||
*1 failed*
|
||||
"""
|
||||
[
|
||||
"The requested fixture has no parameter defined for test:",
|
||||
" test_external_fixture.py::test_foo",
|
||||
"",
|
||||
"Requested fixture 'fix_with_param' defined in:",
|
||||
"conftest.py:4",
|
||||
"Requested here:",
|
||||
"test_external_fixture.py:2",
|
||||
"*1 failed*",
|
||||
]
|
||||
)
|
||||
|
||||
def test_non_relative_path(self, testdir):
|
||||
@@ -3698,15 +3712,16 @@ class TestParameterizedSubRequest(object):
|
||||
testdir.syspathinsert(fixdir)
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines(
|
||||
"""
|
||||
E*Failed: The requested fixture has no parameter defined for the current test.
|
||||
E*
|
||||
E*Requested fixture 'fix_with_param' defined in:
|
||||
E*fix.py:4
|
||||
E*Requested here:
|
||||
E*test_foos.py:4
|
||||
*1 failed*
|
||||
"""
|
||||
[
|
||||
"The requested fixture has no parameter defined for test:",
|
||||
" test_foos.py::test_foo",
|
||||
"",
|
||||
"Requested fixture 'fix_with_param' defined in:",
|
||||
"*fix.py:4",
|
||||
"Requested here:",
|
||||
"test_foos.py:4",
|
||||
"*1 failed*",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -127,10 +127,11 @@ class TestMetafunc(object):
|
||||
pass
|
||||
|
||||
metafunc = self.Metafunc(func)
|
||||
try:
|
||||
with pytest.raises(
|
||||
pytest.fail.Exception,
|
||||
match=r"parametrize\(\) call in func got an unexpected scope value 'doggy'",
|
||||
):
|
||||
metafunc.parametrize("x", [1], scope="doggy")
|
||||
except ValueError as ve:
|
||||
assert "has an unsupported scope value 'doggy'" in str(ve)
|
||||
|
||||
def test_find_parametrized_scope(self):
|
||||
"""unittest for _find_parametrized_scope (#3941)"""
|
||||
@@ -206,16 +207,13 @@ class TestMetafunc(object):
|
||||
|
||||
metafunc = self.Metafunc(func)
|
||||
|
||||
pytest.raises(
|
||||
ValueError, lambda: metafunc.parametrize("x", [1, 2], ids=["basic"])
|
||||
)
|
||||
with pytest.raises(pytest.fail.Exception):
|
||||
metafunc.parametrize("x", [1, 2], ids=["basic"])
|
||||
|
||||
pytest.raises(
|
||||
ValueError,
|
||||
lambda: metafunc.parametrize(
|
||||
with pytest.raises(pytest.fail.Exception):
|
||||
metafunc.parametrize(
|
||||
("x", "y"), [("abc", "def"), ("ghi", "jkl")], ids=["one"]
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
@pytest.mark.issue510
|
||||
def test_parametrize_empty_list(self):
|
||||
@@ -573,7 +571,7 @@ class TestMetafunc(object):
|
||||
pass
|
||||
|
||||
metafunc = self.Metafunc(func)
|
||||
with pytest.raises(ValueError):
|
||||
with pytest.raises(pytest.fail.Exception):
|
||||
metafunc.parametrize("x, y", [("a", "b")], indirect=["x", "z"])
|
||||
|
||||
@pytest.mark.issue714
|
||||
@@ -1189,7 +1187,9 @@ class TestMetafuncFunctional(object):
|
||||
)
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines(
|
||||
["*ids must be list of strings, found: 2 (type: int)*"]
|
||||
[
|
||||
"*In test_ids_numbers: ids must be list of strings, found: 2 (type: *'int'>)*"
|
||||
]
|
||||
)
|
||||
|
||||
def test_parametrize_with_identical_ids_get_unique_names(self, testdir):
|
||||
@@ -1326,13 +1326,13 @@ class TestMetafuncFunctional(object):
|
||||
attr
|
||||
)
|
||||
)
|
||||
reprec = testdir.inline_run("--collectonly")
|
||||
failures = reprec.getfailures()
|
||||
assert len(failures) == 1
|
||||
expectederror = "MarkerError: test_foo has '{}', spelling should be 'parametrize'".format(
|
||||
attr
|
||||
result = testdir.runpytest("--collectonly")
|
||||
result.stdout.fnmatch_lines(
|
||||
[
|
||||
"test_foo has '{}' mark, spelling should be 'parametrize'".format(attr),
|
||||
"*1 error in*",
|
||||
]
|
||||
)
|
||||
assert expectederror in failures[0].longrepr.reprcrash.message
|
||||
|
||||
|
||||
class TestMetafuncFunctionalAuto(object):
|
||||
|
||||
@@ -4,7 +4,7 @@ import textwrap
|
||||
import py
|
||||
import pytest
|
||||
from _pytest.config import PytestPluginManager
|
||||
from _pytest.main import EXIT_NOTESTSCOLLECTED, EXIT_USAGEERROR
|
||||
from _pytest.main import EXIT_NOTESTSCOLLECTED, EXIT_OK, EXIT_USAGEERROR
|
||||
|
||||
|
||||
@pytest.fixture(scope="module", params=["global", "inpackage"])
|
||||
@@ -186,6 +186,52 @@ def test_conftest_confcutdir(testdir):
|
||||
assert "warning: could not load initial" not in result.stdout.str()
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not hasattr(py.path.local, "mksymlinkto"),
|
||||
reason="symlink not available on this platform",
|
||||
)
|
||||
def test_conftest_symlink(testdir):
|
||||
"""Ensure that conftest.py is used for resolved symlinks."""
|
||||
realtests = testdir.tmpdir.mkdir("real").mkdir("app").mkdir("tests")
|
||||
testdir.tmpdir.join("symlinktests").mksymlinkto(realtests)
|
||||
testdir.makepyfile(
|
||||
**{
|
||||
"real/app/tests/test_foo.py": "def test1(fixture): pass",
|
||||
"real/conftest.py": textwrap.dedent(
|
||||
"""
|
||||
import pytest
|
||||
|
||||
print("conftest_loaded")
|
||||
|
||||
@pytest.fixture
|
||||
def fixture():
|
||||
print("fixture_used")
|
||||
"""
|
||||
),
|
||||
}
|
||||
)
|
||||
result = testdir.runpytest("-vs", "symlinktests")
|
||||
result.stdout.fnmatch_lines(
|
||||
[
|
||||
"*conftest_loaded*",
|
||||
"real/app/tests/test_foo.py::test1 fixture_used",
|
||||
"PASSED",
|
||||
]
|
||||
)
|
||||
assert result.ret == EXIT_OK
|
||||
|
||||
realtests.ensure("__init__.py")
|
||||
result = testdir.runpytest("-vs", "symlinktests/test_foo.py::test1")
|
||||
result.stdout.fnmatch_lines(
|
||||
[
|
||||
"*conftest_loaded*",
|
||||
"real/app/tests/test_foo.py::test1 fixture_used",
|
||||
"PASSED",
|
||||
]
|
||||
)
|
||||
assert result.ret == EXIT_OK
|
||||
|
||||
|
||||
def test_no_conftest(testdir):
|
||||
testdir.makeconftest("assert 0")
|
||||
result = testdir.runpytest("--noconftest")
|
||||
|
||||
@@ -476,7 +476,7 @@ class TestPython(object):
|
||||
tnode.assert_attr(
|
||||
file="test_junit_prefixing.py",
|
||||
line="3",
|
||||
classname="xyz.test_junit_prefixing." "TestHello",
|
||||
classname="xyz.test_junit_prefixing.TestHello",
|
||||
name="test_hello",
|
||||
)
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ from _pytest.mark import (
|
||||
transfer_markers,
|
||||
EMPTY_PARAMETERSET_OPTION,
|
||||
)
|
||||
from _pytest.nodes import Node
|
||||
from _pytest.nodes import Node, Collector
|
||||
|
||||
ignore_markinfo = pytest.mark.filterwarnings(
|
||||
"ignore:MarkInfo objects:pytest.RemovedInPytest4Warning"
|
||||
@@ -247,7 +247,7 @@ def test_marker_without_description(testdir):
|
||||
)
|
||||
ftdir = testdir.mkdir("ft1_dummy")
|
||||
testdir.tmpdir.join("conftest.py").move(ftdir.join("conftest.py"))
|
||||
rec = testdir.runpytest_subprocess("--strict")
|
||||
rec = testdir.runpytest("--strict")
|
||||
rec.assert_outcomes()
|
||||
|
||||
|
||||
@@ -302,7 +302,7 @@ def test_strict_prohibits_unregistered_markers(testdir):
|
||||
)
|
||||
result = testdir.runpytest("--strict")
|
||||
assert result.ret != 0
|
||||
result.stdout.fnmatch_lines(["*unregisteredmark*not*registered*"])
|
||||
result.stdout.fnmatch_lines(["'unregisteredmark' not a registered marker"])
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -1103,7 +1103,14 @@ class TestMarkDecorator(object):
|
||||
@pytest.mark.parametrize("mark", [None, "", "skip", "xfail"])
|
||||
def test_parameterset_for_parametrize_marks(testdir, mark):
|
||||
if mark is not None:
|
||||
testdir.makeini("[pytest]\n{}={}".format(EMPTY_PARAMETERSET_OPTION, mark))
|
||||
testdir.makeini(
|
||||
"""
|
||||
[pytest]
|
||||
{}={}
|
||||
""".format(
|
||||
EMPTY_PARAMETERSET_OPTION, mark
|
||||
)
|
||||
)
|
||||
|
||||
config = testdir.parseconfig()
|
||||
from _pytest.mark import pytest_configure, get_empty_parameterset_mark
|
||||
@@ -1119,6 +1126,34 @@ def test_parameterset_for_parametrize_marks(testdir, mark):
|
||||
assert result_mark.kwargs.get("run") is False
|
||||
|
||||
|
||||
def test_parameterset_for_fail_at_collect(testdir):
|
||||
testdir.makeini(
|
||||
"""
|
||||
[pytest]
|
||||
{}=fail_at_collect
|
||||
""".format(
|
||||
EMPTY_PARAMETERSET_OPTION
|
||||
)
|
||||
)
|
||||
|
||||
config = testdir.parseconfig()
|
||||
from _pytest.mark import pytest_configure, get_empty_parameterset_mark
|
||||
from _pytest.compat import getfslineno
|
||||
|
||||
pytest_configure(config)
|
||||
|
||||
test_func = all
|
||||
func_name = test_func.__name__
|
||||
_, func_lineno = getfslineno(test_func)
|
||||
expected_errmsg = r"Empty parameter set in '%s' at line %d" % (
|
||||
func_name,
|
||||
func_lineno,
|
||||
)
|
||||
|
||||
with pytest.raises(Collector.CollectError, match=expected_errmsg):
|
||||
get_empty_parameterset_mark(config, ["a"], test_func)
|
||||
|
||||
|
||||
def test_parameterset_for_parametrize_bad_markname(testdir):
|
||||
with pytest.raises(pytest.UsageError):
|
||||
test_parameterset_for_parametrize_marks(testdir, "bad")
|
||||
|
||||
@@ -4,7 +4,7 @@ import py
|
||||
|
||||
import pytest
|
||||
|
||||
from _pytest.paths import fnmatch_ex
|
||||
from _pytest.pathlib import fnmatch_ex
|
||||
|
||||
|
||||
class TestPort:
|
||||
|
||||
@@ -25,6 +25,8 @@ def custom_pdb_calls():
|
||||
|
||||
# install dummy debugger class and track which methods were called on it
|
||||
class _CustomPdb(object):
|
||||
quitting = False
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
called.append("init")
|
||||
|
||||
@@ -142,16 +144,20 @@ class TestPDB(object):
|
||||
def test_1():
|
||||
i = 0
|
||||
assert i == 1
|
||||
|
||||
def test_not_called_due_to_quit():
|
||||
pass
|
||||
"""
|
||||
)
|
||||
child = testdir.spawn_pytest("--pdb %s" % p1)
|
||||
child.expect(".*def test_1")
|
||||
child.expect(".*i = 0")
|
||||
child.expect("(Pdb)")
|
||||
child.expect("Pdb")
|
||||
child.sendeof()
|
||||
rest = child.read().decode("utf8")
|
||||
assert "1 failed" in rest
|
||||
assert "= 1 failed in" in rest
|
||||
assert "def test_1" not in rest
|
||||
assert "Exit: Quitting debugger" in rest
|
||||
self.flush(child)
|
||||
|
||||
@staticmethod
|
||||
@@ -174,7 +180,7 @@ class TestPDB(object):
|
||||
"""
|
||||
)
|
||||
child = testdir.spawn_pytest("--pdb %s" % p1)
|
||||
child.expect("(Pdb)")
|
||||
child.expect("Pdb")
|
||||
child.sendline("p self.filename")
|
||||
child.sendeof()
|
||||
rest = child.read().decode("utf8")
|
||||
@@ -209,7 +215,7 @@ class TestPDB(object):
|
||||
child = testdir.spawn_pytest("--pdb %s" % p1)
|
||||
child.expect("captured stdout")
|
||||
child.expect("get rekt")
|
||||
child.expect("(Pdb)")
|
||||
child.expect("Pdb")
|
||||
child.sendeof()
|
||||
rest = child.read().decode("utf8")
|
||||
assert "1 failed" in rest
|
||||
@@ -228,7 +234,7 @@ class TestPDB(object):
|
||||
child = testdir.spawn_pytest("--pdb %s" % p1)
|
||||
child.expect("captured stderr")
|
||||
child.expect("get rekt")
|
||||
child.expect("(Pdb)")
|
||||
child.expect("Pdb")
|
||||
child.sendeof()
|
||||
rest = child.read().decode("utf8")
|
||||
assert "1 failed" in rest
|
||||
@@ -243,7 +249,7 @@ class TestPDB(object):
|
||||
"""
|
||||
)
|
||||
child = testdir.spawn_pytest("--pdb %s" % p1)
|
||||
child.expect("(Pdb)")
|
||||
child.expect("Pdb")
|
||||
output = child.before.decode("utf8")
|
||||
child.sendeof()
|
||||
assert "captured stdout" not in output
|
||||
@@ -266,7 +272,7 @@ class TestPDB(object):
|
||||
if showcapture in ("all", "log"):
|
||||
child.expect("captured log")
|
||||
child.expect("get rekt")
|
||||
child.expect("(Pdb)")
|
||||
child.expect("Pdb")
|
||||
child.sendeof()
|
||||
rest = child.read().decode("utf8")
|
||||
assert "1 failed" in rest
|
||||
@@ -281,13 +287,11 @@ class TestPDB(object):
|
||||
assert False
|
||||
"""
|
||||
)
|
||||
child = testdir.spawn_pytest(
|
||||
"--show-capture=all --pdb " "-p no:logging %s" % p1
|
||||
)
|
||||
child = testdir.spawn_pytest("--show-capture=all --pdb -p no:logging %s" % p1)
|
||||
child.expect("get rekt")
|
||||
output = child.before.decode("utf8")
|
||||
assert "captured log" not in output
|
||||
child.expect("(Pdb)")
|
||||
child.expect("Pdb")
|
||||
child.sendeof()
|
||||
rest = child.read().decode("utf8")
|
||||
assert "1 failed" in rest
|
||||
@@ -306,7 +310,7 @@ class TestPDB(object):
|
||||
child = testdir.spawn_pytest("--pdb %s" % p1)
|
||||
child.expect(".*def test_1")
|
||||
child.expect(".*pytest.raises.*globalfunc")
|
||||
child.expect("(Pdb)")
|
||||
child.expect("Pdb")
|
||||
child.sendline("globalfunc")
|
||||
child.expect(".*function")
|
||||
child.sendeof()
|
||||
@@ -322,8 +326,8 @@ class TestPDB(object):
|
||||
)
|
||||
child = testdir.spawn_pytest("--pdb %s" % p1)
|
||||
# child.expect(".*import pytest.*")
|
||||
child.expect("(Pdb)")
|
||||
child.sendeof()
|
||||
child.expect("Pdb")
|
||||
child.sendline("c")
|
||||
child.expect("1 error")
|
||||
self.flush(child)
|
||||
|
||||
@@ -336,8 +340,20 @@ class TestPDB(object):
|
||||
)
|
||||
p1 = testdir.makepyfile("def test_func(): pass")
|
||||
child = testdir.spawn_pytest("--pdb %s" % p1)
|
||||
# child.expect(".*import pytest.*")
|
||||
child.expect("(Pdb)")
|
||||
child.expect("Pdb")
|
||||
|
||||
# INTERNALERROR is only displayed once via terminal reporter.
|
||||
assert (
|
||||
len(
|
||||
[
|
||||
x
|
||||
for x in child.before.decode().splitlines()
|
||||
if x.startswith("INTERNALERROR> Traceback")
|
||||
]
|
||||
)
|
||||
== 1
|
||||
)
|
||||
|
||||
child.sendeof()
|
||||
self.flush(child)
|
||||
|
||||
@@ -347,7 +363,7 @@ class TestPDB(object):
|
||||
import pytest
|
||||
def test_1():
|
||||
i = 0
|
||||
print ("hello17")
|
||||
print("hello17")
|
||||
pytest.set_trace()
|
||||
x = 3
|
||||
"""
|
||||
@@ -355,7 +371,7 @@ class TestPDB(object):
|
||||
child = testdir.spawn_pytest(str(p1))
|
||||
child.expect("test_1")
|
||||
child.expect("x = 3")
|
||||
child.expect("(Pdb)")
|
||||
child.expect("Pdb")
|
||||
child.sendeof()
|
||||
rest = child.read().decode("utf-8")
|
||||
assert "1 failed" in rest
|
||||
@@ -373,11 +389,12 @@ class TestPDB(object):
|
||||
)
|
||||
child = testdir.spawn_pytest(str(p1))
|
||||
child.expect("test_1")
|
||||
child.expect("(Pdb)")
|
||||
child.expect("Pdb")
|
||||
child.sendeof()
|
||||
rest = child.read().decode("utf8")
|
||||
assert "1 failed" in rest
|
||||
assert "reading from stdin while output" not in rest
|
||||
assert "BdbQuit" in rest
|
||||
self.flush(child)
|
||||
|
||||
def test_pdb_and_capsys(self, testdir):
|
||||
@@ -385,7 +402,7 @@ class TestPDB(object):
|
||||
"""
|
||||
import pytest
|
||||
def test_1(capsys):
|
||||
print ("hello1")
|
||||
print("hello1")
|
||||
pytest.set_trace()
|
||||
"""
|
||||
)
|
||||
@@ -422,7 +439,7 @@ class TestPDB(object):
|
||||
def test_1():
|
||||
pdb.set_trace()
|
||||
def test_2():
|
||||
print ("hello")
|
||||
print("hello")
|
||||
assert 0
|
||||
"""
|
||||
)
|
||||
@@ -448,10 +465,10 @@ class TestPDB(object):
|
||||
"""
|
||||
)
|
||||
child = testdir.spawn_pytest("--doctest-modules --pdb %s" % p1)
|
||||
child.expect("(Pdb)")
|
||||
child.expect("Pdb")
|
||||
child.sendline("i")
|
||||
child.expect("0")
|
||||
child.expect("(Pdb)")
|
||||
child.expect("Pdb")
|
||||
child.sendeof()
|
||||
rest = child.read().decode("utf8")
|
||||
assert "1 failed" in rest
|
||||
@@ -463,10 +480,10 @@ class TestPDB(object):
|
||||
import pytest
|
||||
def test_1():
|
||||
i = 0
|
||||
print ("hello17")
|
||||
print("hello17")
|
||||
pytest.set_trace()
|
||||
x = 3
|
||||
print ("hello18")
|
||||
print("hello18")
|
||||
pytest.set_trace()
|
||||
x = 4
|
||||
"""
|
||||
@@ -474,9 +491,10 @@ class TestPDB(object):
|
||||
child = testdir.spawn_pytest(str(p1))
|
||||
child.expect("test_1")
|
||||
child.expect("x = 3")
|
||||
child.expect("(Pdb)")
|
||||
child.expect("Pdb")
|
||||
child.sendline("c")
|
||||
child.expect("x = 4")
|
||||
child.expect("Pdb")
|
||||
child.sendeof()
|
||||
rest = child.read().decode("utf8")
|
||||
assert "1 failed" in rest
|
||||
@@ -495,6 +513,7 @@ class TestPDB(object):
|
||||
)
|
||||
child = testdir.spawn("{} {}".format(sys.executable, p1))
|
||||
child.expect("x = 5")
|
||||
child.expect("Pdb")
|
||||
child.sendeof()
|
||||
self.flush(child)
|
||||
|
||||
@@ -511,20 +530,23 @@ class TestPDB(object):
|
||||
)
|
||||
child = testdir.spawn_pytest(str(p1))
|
||||
child.expect("x = 5")
|
||||
child.expect("Pdb")
|
||||
child.sendeof()
|
||||
self.flush(child)
|
||||
|
||||
def test_pdb_collection_failure_is_shown(self, testdir):
|
||||
p1 = testdir.makepyfile("xxx")
|
||||
result = testdir.runpytest_subprocess("--pdb", p1)
|
||||
result.stdout.fnmatch_lines(["*NameError*xxx*", "*1 error*"])
|
||||
result.stdout.fnmatch_lines(
|
||||
["E NameError: *xxx*", "*! *Exit: Quitting debugger !*"] # due to EOF
|
||||
)
|
||||
|
||||
def test_enter_pdb_hook_is_called(self, testdir):
|
||||
testdir.makeconftest(
|
||||
"""
|
||||
def pytest_enter_pdb(config):
|
||||
assert config.testing_verification == 'configured'
|
||||
print 'enter_pdb_hook'
|
||||
print('enter_pdb_hook')
|
||||
|
||||
def pytest_configure(config):
|
||||
config.testing_verification = 'configured'
|
||||
@@ -561,7 +583,7 @@ class TestPDB(object):
|
||||
custom_pdb="""
|
||||
class CustomPdb(object):
|
||||
def set_trace(*args, **kwargs):
|
||||
print 'custom set_trace>'
|
||||
print('custom set_trace>')
|
||||
"""
|
||||
)
|
||||
p1 = testdir.makepyfile(
|
||||
@@ -690,7 +712,7 @@ class TestDebuggingBreakpoints(object):
|
||||
)
|
||||
child = testdir.spawn_pytest(str(p1))
|
||||
child.expect("test_1")
|
||||
child.expect("(Pdb)")
|
||||
child.expect("Pdb")
|
||||
child.sendeof()
|
||||
rest = child.read().decode("utf8")
|
||||
assert "1 failed" in rest
|
||||
@@ -710,7 +732,7 @@ class TestDebuggingBreakpoints(object):
|
||||
)
|
||||
child = testdir.spawn_pytest(str(p1))
|
||||
child.expect("test_1")
|
||||
child.expect("(Pdb)")
|
||||
child.expect("Pdb")
|
||||
child.sendeof()
|
||||
rest = child.read().decode("utf8")
|
||||
assert "1 failed" in rest
|
||||
@@ -728,7 +750,7 @@ class TestTraceOption:
|
||||
)
|
||||
child = testdir.spawn_pytest("--trace " + str(p1))
|
||||
child.expect("test_1")
|
||||
child.expect("(Pdb)")
|
||||
child.expect("Pdb")
|
||||
child.sendeof()
|
||||
rest = child.read().decode("utf8")
|
||||
assert "1 passed" in rest
|
||||
@@ -747,7 +769,7 @@ class TestTraceOption:
|
||||
)
|
||||
child = testdir.spawn_pytest("--trace " + str(p1))
|
||||
child.expect("is_equal")
|
||||
child.expect("(Pdb)")
|
||||
child.expect("Pdb")
|
||||
child.sendeof()
|
||||
rest = child.read().decode("utf8")
|
||||
assert "1 passed" in rest
|
||||
|
||||
@@ -4,6 +4,7 @@ import os
|
||||
import py.path
|
||||
import pytest
|
||||
import sys
|
||||
import time
|
||||
import _pytest.pytester as pytester
|
||||
from _pytest.pytester import HookRecorder
|
||||
from _pytest.pytester import CwdSnapshot, SysModulesSnapshot, SysPathsSnapshot
|
||||
@@ -176,7 +177,7 @@ def test_makepyfile_unicode(testdir):
|
||||
unichr(65)
|
||||
except NameError:
|
||||
unichr = chr
|
||||
testdir.makepyfile(unichr(0xfffd))
|
||||
testdir.makepyfile(unichr(0xFFFD))
|
||||
|
||||
|
||||
def test_makepyfile_utf8(testdir):
|
||||
@@ -401,3 +402,34 @@ def test_testdir_subprocess(testdir):
|
||||
def test_unicode_args(testdir):
|
||||
result = testdir.runpytest("-k", u"💩")
|
||||
assert result.ret == EXIT_NOTESTSCOLLECTED
|
||||
|
||||
|
||||
def test_testdir_run_no_timeout(testdir):
|
||||
testfile = testdir.makepyfile("def test_no_timeout(): pass")
|
||||
assert testdir.runpytest_subprocess(testfile).ret == EXIT_OK
|
||||
|
||||
|
||||
def test_testdir_run_with_timeout(testdir):
|
||||
testfile = testdir.makepyfile("def test_no_timeout(): pass")
|
||||
|
||||
timeout = 120
|
||||
|
||||
start = time.time()
|
||||
result = testdir.runpytest_subprocess(testfile, timeout=timeout)
|
||||
end = time.time()
|
||||
duration = end - start
|
||||
|
||||
assert result.ret == EXIT_OK
|
||||
assert duration < timeout
|
||||
|
||||
|
||||
def test_testdir_run_timeout_expires(testdir):
|
||||
testfile = testdir.makepyfile(
|
||||
"""
|
||||
import time
|
||||
|
||||
def test_timeout():
|
||||
time.sleep(10)"""
|
||||
)
|
||||
with pytest.raises(testdir.TimeoutExpired):
|
||||
testdir.runpytest_subprocess(testfile, timeout=1)
|
||||
|
||||
@@ -76,9 +76,8 @@ class TestDeprecatedCall(object):
|
||||
)
|
||||
|
||||
def test_deprecated_call_raises(self):
|
||||
with pytest.raises(AssertionError) as excinfo:
|
||||
with pytest.raises(pytest.fail.Exception, match="No warnings of type"):
|
||||
pytest.deprecated_call(self.dep, 3, 5)
|
||||
assert "Did not produce" in str(excinfo)
|
||||
|
||||
def test_deprecated_call(self):
|
||||
pytest.deprecated_call(self.dep, 0, 5)
|
||||
@@ -100,7 +99,7 @@ class TestDeprecatedCall(object):
|
||||
assert warn_explicit is warnings.warn_explicit
|
||||
|
||||
def test_deprecated_explicit_call_raises(self):
|
||||
with pytest.raises(AssertionError):
|
||||
with pytest.raises(pytest.fail.Exception):
|
||||
pytest.deprecated_call(self.dep_explicit, 3)
|
||||
|
||||
def test_deprecated_explicit_call(self):
|
||||
@@ -116,8 +115,8 @@ class TestDeprecatedCall(object):
|
||||
def f():
|
||||
pass
|
||||
|
||||
msg = "Did not produce DeprecationWarning or PendingDeprecationWarning"
|
||||
with pytest.raises(AssertionError, match=msg):
|
||||
msg = "No warnings of type (.*DeprecationWarning.*, .*PendingDeprecationWarning.*)"
|
||||
with pytest.raises(pytest.fail.Exception, match=msg):
|
||||
if mode == "call":
|
||||
pytest.deprecated_call(f)
|
||||
else:
|
||||
@@ -179,12 +178,20 @@ class TestDeprecatedCall(object):
|
||||
def f():
|
||||
warnings.warn(warning("hi"))
|
||||
|
||||
with pytest.raises(AssertionError):
|
||||
with pytest.raises(pytest.fail.Exception):
|
||||
pytest.deprecated_call(f)
|
||||
with pytest.raises(AssertionError):
|
||||
with pytest.raises(pytest.fail.Exception):
|
||||
with pytest.deprecated_call():
|
||||
f()
|
||||
|
||||
def test_deprecated_call_supports_match(self):
|
||||
with pytest.deprecated_call(match=r"must be \d+$"):
|
||||
warnings.warn("value must be 42", DeprecationWarning)
|
||||
|
||||
with pytest.raises(pytest.fail.Exception):
|
||||
with pytest.deprecated_call(match=r"must be \d+$"):
|
||||
warnings.warn("this is not here", DeprecationWarning)
|
||||
|
||||
|
||||
class TestWarns(object):
|
||||
def test_strings(self):
|
||||
@@ -343,3 +350,13 @@ class TestWarns(object):
|
||||
with pytest.warns(UserWarning, match=r"aaa"):
|
||||
warnings.warn("bbbbbbbbbb", UserWarning)
|
||||
warnings.warn("cccccccccc", UserWarning)
|
||||
|
||||
@pytest.mark.filterwarnings("ignore")
|
||||
def test_can_capture_previously_warned(self):
|
||||
def f():
|
||||
warnings.warn(UserWarning("ohai"))
|
||||
return 10
|
||||
|
||||
assert f() == 10
|
||||
assert pytest.warns(UserWarning, f) == 10
|
||||
assert pytest.warns(UserWarning, f) == 10
|
||||
|
||||
@@ -570,7 +570,20 @@ def test_pytest_exit_msg(testdir):
|
||||
result.stderr.fnmatch_lines(["Exit: oh noes"])
|
||||
|
||||
|
||||
def test_pytest_fail_notrace(testdir):
|
||||
def test_pytest_exit_returncode(testdir):
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
import pytest
|
||||
def test_foo():
|
||||
pytest.exit("some exit msg", 99)
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest()
|
||||
assert result.ret == 99
|
||||
|
||||
|
||||
def test_pytest_fail_notrace_runtest(testdir):
|
||||
"""Test pytest.fail(..., pytrace=False) does not show tracebacks during test run."""
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
import pytest
|
||||
@@ -585,6 +598,21 @@ def test_pytest_fail_notrace(testdir):
|
||||
assert "def teardown_function" not in result.stdout.str()
|
||||
|
||||
|
||||
def test_pytest_fail_notrace_collection(testdir):
|
||||
"""Test pytest.fail(..., pytrace=False) does not show tracebacks during collection."""
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
import pytest
|
||||
def some_internal_function():
|
||||
pytest.fail("hello", pytrace=False)
|
||||
some_internal_function()
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines(["hello"])
|
||||
assert "def some_internal_function()" not in result.stdout.str()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("str_prefix", ["u", ""])
|
||||
def test_pytest_fail_notrace_non_ascii(testdir, str_prefix):
|
||||
"""Fix pytest.fail with pytrace=False with non-ascii characters (#1178).
|
||||
|
||||
@@ -154,7 +154,7 @@ class TestTerminal(object):
|
||||
)
|
||||
result = testdir.runpytest(p2)
|
||||
result.stdout.fnmatch_lines(["*test_p2.py .*", "*1 passed*"])
|
||||
result = testdir.runpytest("-v", p2)
|
||||
result = testdir.runpytest("-vv", p2)
|
||||
result.stdout.fnmatch_lines(
|
||||
["*test_p2.py::TestMore::test_p1* <- *test_p1.py*PASSED*"]
|
||||
)
|
||||
@@ -170,7 +170,7 @@ class TestTerminal(object):
|
||||
"""
|
||||
)
|
||||
)
|
||||
result = testdir.runpytest("-v")
|
||||
result = testdir.runpytest("-vv")
|
||||
assert result.ret == 0
|
||||
result.stdout.fnmatch_lines(["*a123/test_hello123.py*PASS*"])
|
||||
assert " <- " not in result.stdout.str()
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
from __future__ import absolute_import, division, print_function
|
||||
import sys
|
||||
import py
|
||||
|
||||
import six
|
||||
|
||||
import pytest
|
||||
from _pytest.pathlib import Path
|
||||
|
||||
|
||||
def test_tmpdir_fixture(testdir):
|
||||
@@ -19,11 +22,11 @@ def test_ensuretemp(recwarn):
|
||||
|
||||
class TestTempdirHandler(object):
|
||||
def test_mktemp(self, testdir):
|
||||
from _pytest.tmpdir import TempdirFactory
|
||||
from _pytest.tmpdir import TempdirFactory, TempPathFactory
|
||||
|
||||
config = testdir.parseconfig()
|
||||
config.option.basetemp = testdir.mkdir("hello")
|
||||
t = TempdirFactory(config)
|
||||
t = TempdirFactory(TempPathFactory.from_config(config))
|
||||
tmp = t.mktemp("world")
|
||||
assert tmp.relto(t.getbasetemp()) == "world0"
|
||||
tmp = t.mktemp("this")
|
||||
@@ -65,10 +68,6 @@ def test_basetemp(testdir):
|
||||
assert mytemp.join("hello").check()
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not hasattr(py.path.local, "mksymlinkto"),
|
||||
reason="symlink not available on this platform",
|
||||
)
|
||||
def test_tmpdir_always_is_realpath(testdir):
|
||||
# the reason why tmpdir should be a realpath is that
|
||||
# when you cd to it and do "os.getcwd()" you will anyway
|
||||
@@ -78,7 +77,7 @@ def test_tmpdir_always_is_realpath(testdir):
|
||||
# os.environ["PWD"]
|
||||
realtemp = testdir.tmpdir.mkdir("myrealtemp")
|
||||
linktemp = testdir.tmpdir.join("symlinktemp")
|
||||
linktemp.mksymlinkto(realtemp)
|
||||
attempt_symlink_to(linktemp, str(realtemp))
|
||||
p = testdir.makepyfile(
|
||||
"""
|
||||
def test_1(tmpdir):
|
||||
@@ -111,7 +110,7 @@ def test_tmpdir_factory(testdir):
|
||||
def session_dir(tmpdir_factory):
|
||||
return tmpdir_factory.mktemp('data', numbered=False)
|
||||
def test_some(session_dir):
|
||||
session_dir.isdir()
|
||||
assert session_dir.isdir()
|
||||
"""
|
||||
)
|
||||
reprec = testdir.inline_run()
|
||||
@@ -184,3 +183,113 @@ def test_get_user(monkeypatch):
|
||||
monkeypatch.delenv("USER", raising=False)
|
||||
monkeypatch.delenv("USERNAME", raising=False)
|
||||
assert get_user() is None
|
||||
|
||||
|
||||
class TestNumberedDir(object):
|
||||
PREFIX = "fun-"
|
||||
|
||||
def test_make(self, tmp_path):
|
||||
from _pytest.pathlib import make_numbered_dir
|
||||
|
||||
for i in range(10):
|
||||
d = make_numbered_dir(root=tmp_path, prefix=self.PREFIX)
|
||||
assert d.name.startswith(self.PREFIX)
|
||||
assert d.name.endswith(str(i))
|
||||
|
||||
def test_cleanup_lock_create(self, tmp_path):
|
||||
d = tmp_path.joinpath("test")
|
||||
d.mkdir()
|
||||
from _pytest.pathlib import create_cleanup_lock
|
||||
|
||||
lockfile = create_cleanup_lock(d)
|
||||
with pytest.raises(EnvironmentError, match="cannot create lockfile in .*"):
|
||||
create_cleanup_lock(d)
|
||||
|
||||
lockfile.unlink()
|
||||
|
||||
def test_lock_register_cleanup_removal(self, tmp_path):
|
||||
from _pytest.pathlib import create_cleanup_lock, register_cleanup_lock_removal
|
||||
|
||||
lock = create_cleanup_lock(tmp_path)
|
||||
|
||||
registry = []
|
||||
register_cleanup_lock_removal(lock, register=registry.append)
|
||||
|
||||
cleanup_func, = registry
|
||||
|
||||
assert lock.is_file()
|
||||
|
||||
cleanup_func(original_pid="intentionally_different")
|
||||
|
||||
assert lock.is_file()
|
||||
|
||||
cleanup_func()
|
||||
|
||||
assert not lock.exists()
|
||||
|
||||
cleanup_func()
|
||||
|
||||
assert not lock.exists()
|
||||
|
||||
def _do_cleanup(self, tmp_path):
|
||||
self.test_make(tmp_path)
|
||||
from _pytest.pathlib import cleanup_numbered_dir
|
||||
|
||||
cleanup_numbered_dir(
|
||||
root=tmp_path,
|
||||
prefix=self.PREFIX,
|
||||
keep=2,
|
||||
consider_lock_dead_if_created_before=0,
|
||||
)
|
||||
|
||||
def test_cleanup_keep(self, tmp_path):
|
||||
self._do_cleanup(tmp_path)
|
||||
a, b = tmp_path.iterdir()
|
||||
print(a, b)
|
||||
|
||||
def test_cleanup_locked(self, tmp_path):
|
||||
|
||||
from _pytest import pathlib
|
||||
|
||||
p = pathlib.make_numbered_dir(root=tmp_path, prefix=self.PREFIX)
|
||||
|
||||
pathlib.create_cleanup_lock(p)
|
||||
|
||||
assert not pathlib.ensure_deletable(
|
||||
p, consider_lock_dead_if_created_before=p.stat().st_mtime - 1
|
||||
)
|
||||
assert pathlib.ensure_deletable(
|
||||
p, consider_lock_dead_if_created_before=p.stat().st_mtime + 1
|
||||
)
|
||||
|
||||
def test_rmtree(self, tmp_path):
|
||||
from _pytest.pathlib import rmtree
|
||||
|
||||
adir = tmp_path / "adir"
|
||||
adir.mkdir()
|
||||
rmtree(adir)
|
||||
|
||||
assert not adir.exists()
|
||||
|
||||
adir.mkdir()
|
||||
afile = adir / "afile"
|
||||
afile.write_bytes(b"aa")
|
||||
|
||||
rmtree(adir, force=True)
|
||||
assert not adir.exists()
|
||||
|
||||
def test_cleanup_symlink(self, tmp_path):
|
||||
the_symlink = tmp_path / (self.PREFIX + "current")
|
||||
attempt_symlink_to(the_symlink, tmp_path / (self.PREFIX + "5"))
|
||||
self._do_cleanup(tmp_path)
|
||||
|
||||
|
||||
def attempt_symlink_to(path, to_path):
|
||||
"""Try to make a symlink from "path" to "to_path", skipping in case this platform
|
||||
does not support it or we don't have sufficient privileges (common on Windows)."""
|
||||
if sys.platform.startswith("win") and six.PY2:
|
||||
pytest.skip("pathlib for some reason cannot make symlinks on Python 2")
|
||||
try:
|
||||
Path(path).symlink_to(Path(to_path))
|
||||
except OSError:
|
||||
pytest.skip("could not create symbolic link")
|
||||
|
||||
@@ -1010,3 +1010,15 @@ def test_testcase_handles_init_exceptions(testdir):
|
||||
result = testdir.runpytest()
|
||||
assert "should raise this exception" in result.stdout.str()
|
||||
assert "ERROR at teardown of MyTestCase.test_hello" not in result.stdout.str()
|
||||
|
||||
|
||||
def test_error_message_with_parametrized_fixtures(testdir):
|
||||
testdir.copy_example("unittest/test_parametrized_fixture_error_message.py")
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines(
|
||||
[
|
||||
"*test_two does not support fixtures*",
|
||||
"*TestSomethingElse::test_two",
|
||||
"*Function type: TestCaseFunction",
|
||||
]
|
||||
)
|
||||
|
||||
@@ -430,6 +430,50 @@ def test_hide_pytest_internal_warnings(testdir, ignore_pytest_warnings):
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("ignore_on_cmdline", [True, False])
|
||||
def test_option_precedence_cmdline_over_ini(testdir, ignore_on_cmdline):
|
||||
"""filters defined in the command-line should take precedence over filters in ini files (#3946)."""
|
||||
testdir.makeini(
|
||||
"""
|
||||
[pytest]
|
||||
filterwarnings = error
|
||||
"""
|
||||
)
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
import warnings
|
||||
def test():
|
||||
warnings.warn(UserWarning('hello'))
|
||||
"""
|
||||
)
|
||||
args = ["-W", "ignore"] if ignore_on_cmdline else []
|
||||
result = testdir.runpytest(*args)
|
||||
if ignore_on_cmdline:
|
||||
result.stdout.fnmatch_lines(["* 1 passed in*"])
|
||||
else:
|
||||
result.stdout.fnmatch_lines(["* 1 failed in*"])
|
||||
|
||||
|
||||
def test_option_precedence_mark(testdir):
|
||||
"""Filters defined by marks should always take precedence (#3946)."""
|
||||
testdir.makeini(
|
||||
"""
|
||||
[pytest]
|
||||
filterwarnings = ignore
|
||||
"""
|
||||
)
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
import pytest, warnings
|
||||
@pytest.mark.filterwarnings('error')
|
||||
def test():
|
||||
warnings.warn(UserWarning('hello'))
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest("-W", "ignore")
|
||||
result.stdout.fnmatch_lines(["* 1 failed in*"])
|
||||
|
||||
|
||||
class TestDeprecationWarningsByDefault:
|
||||
"""
|
||||
Note: all pytest runs are executed in a subprocess so we don't inherit warning filters
|
||||
@@ -451,8 +495,18 @@ class TestDeprecationWarningsByDefault:
|
||||
)
|
||||
)
|
||||
|
||||
def test_shown_by_default(self, testdir):
|
||||
@pytest.mark.parametrize("customize_filters", [True, False])
|
||||
def test_shown_by_default(self, testdir, customize_filters):
|
||||
"""Show deprecation warnings by default, even if user has customized the warnings filters (#4013)."""
|
||||
self.create_file(testdir)
|
||||
if customize_filters:
|
||||
testdir.makeini(
|
||||
"""
|
||||
[pytest]
|
||||
filterwarnings =
|
||||
once::UserWarning
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest_subprocess()
|
||||
result.stdout.fnmatch_lines(
|
||||
[
|
||||
@@ -468,7 +522,9 @@ class TestDeprecationWarningsByDefault:
|
||||
testdir.makeini(
|
||||
"""
|
||||
[pytest]
|
||||
filterwarnings = once::UserWarning
|
||||
filterwarnings =
|
||||
ignore::DeprecationWarning
|
||||
ignore::PendingDeprecationWarning
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest_subprocess()
|
||||
@@ -479,7 +535,8 @@ class TestDeprecationWarningsByDefault:
|
||||
be displayed normally.
|
||||
"""
|
||||
self.create_file(
|
||||
testdir, mark='@pytest.mark.filterwarnings("once::UserWarning")'
|
||||
testdir,
|
||||
mark='@pytest.mark.filterwarnings("ignore::PendingDeprecationWarning")',
|
||||
)
|
||||
result = testdir.runpytest_subprocess()
|
||||
result.stdout.fnmatch_lines(
|
||||
@@ -492,7 +549,12 @@ class TestDeprecationWarningsByDefault:
|
||||
|
||||
def test_hidden_by_cmdline(self, testdir):
|
||||
self.create_file(testdir)
|
||||
result = testdir.runpytest_subprocess("-W", "once::UserWarning")
|
||||
result = testdir.runpytest_subprocess(
|
||||
"-W",
|
||||
"ignore::DeprecationWarning",
|
||||
"-W",
|
||||
"ignore::PendingDeprecationWarning",
|
||||
)
|
||||
assert WARNINGS_SUMMARY_HEADER not in result.stdout.str()
|
||||
|
||||
def test_hidden_by_system(self, testdir, monkeypatch):
|
||||
|
||||
36
tox.ini
36
tox.ini
@@ -18,10 +18,10 @@ envlist =
|
||||
|
||||
[testenv]
|
||||
commands =
|
||||
{env:_PYTEST_TOX_COVERAGE_RUN:} pytest --lsof -ra {posargs:testing}
|
||||
{env:_PYTEST_TOX_COVERAGE_RUN:} pytest --lsof
|
||||
coverage: coverage combine
|
||||
coverage: coverage report
|
||||
passenv = USER USERNAME
|
||||
passenv = USER USERNAME COVERAGE_*
|
||||
setenv =
|
||||
# configuration if a user runs tox with a "coverage" factor, for example "tox -e py36-coverage"
|
||||
coverage: _PYTEST_TOX_COVERAGE_RUN=coverage run -m
|
||||
@@ -36,14 +36,12 @@ deps =
|
||||
{env:_PYTEST_TOX_EXTRA_DEP:}
|
||||
|
||||
[testenv:py27-subprocess]
|
||||
changedir = .
|
||||
deps =
|
||||
pytest-xdist>=1.13
|
||||
py27: mock
|
||||
nose
|
||||
passenv = USER USERNAME TRAVIS
|
||||
commands =
|
||||
pytest -n auto -ra --runpytest=subprocess {posargs:testing}
|
||||
pytest -n auto --runpytest=subprocess
|
||||
|
||||
|
||||
[testenv:linting]
|
||||
@@ -59,9 +57,8 @@ deps =
|
||||
nose
|
||||
hypothesis>=3.56
|
||||
{env:_PYTEST_TOX_EXTRA_DEP:}
|
||||
passenv = USER USERNAME TRAVIS
|
||||
commands =
|
||||
{env:_PYTEST_TOX_COVERAGE_RUN:} pytest -n auto -ra {posargs:testing}
|
||||
{env:_PYTEST_TOX_COVERAGE_RUN:} pytest -n auto
|
||||
|
||||
[testenv:py36-xdist]
|
||||
# NOTE: copied from above due to https://github.com/tox-dev/tox/issues/706.
|
||||
@@ -74,16 +71,14 @@ deps =
|
||||
commands = {[testenv:py27-xdist]commands}
|
||||
|
||||
[testenv:py27-pexpect]
|
||||
changedir = testing
|
||||
platform = linux|darwin
|
||||
deps =
|
||||
pexpect
|
||||
{env:_PYTEST_TOX_EXTRA_DEP:}
|
||||
commands =
|
||||
{env:_PYTEST_TOX_COVERAGE_RUN:} pytest -ra test_pdb.py test_terminal.py test_unittest.py
|
||||
{env:_PYTEST_TOX_COVERAGE_RUN:} pytest testing/test_pdb.py testing/test_terminal.py testing/test_unittest.py {posargs}
|
||||
|
||||
[testenv:py36-pexpect]
|
||||
changedir = {[testenv:py27-pexpect]changedir}
|
||||
platform = {[testenv:py27-pexpect]platform}
|
||||
deps = {[testenv:py27-pexpect]deps}
|
||||
commands = {[testenv:py27-pexpect]commands}
|
||||
@@ -95,20 +90,18 @@ deps =
|
||||
py27: mock
|
||||
{env:_PYTEST_TOX_EXTRA_DEP:}
|
||||
distribute = true
|
||||
changedir=testing
|
||||
setenv =
|
||||
{[testenv]setenv}
|
||||
PYTHONDONTWRITEBYTECODE=1
|
||||
passenv = USER USERNAME TRAVIS
|
||||
commands =
|
||||
{env:_PYTEST_TOX_COVERAGE_RUN:} pytest -n auto -ra {posargs:.}
|
||||
{env:_PYTEST_TOX_COVERAGE_RUN:} pytest -n auto {posargs}
|
||||
|
||||
[testenv:py27-trial]
|
||||
deps =
|
||||
twisted
|
||||
{env:_PYTEST_TOX_EXTRA_DEP:}
|
||||
commands =
|
||||
{env:_PYTEST_TOX_COVERAGE_RUN:} pytest -ra {posargs:testing/test_unittest.py}
|
||||
{env:_PYTEST_TOX_COVERAGE_RUN:} pytest {posargs:testing/test_unittest.py}
|
||||
|
||||
[testenv:py36-trial]
|
||||
deps = {[testenv:py27-trial]deps}
|
||||
@@ -119,7 +112,7 @@ deps =
|
||||
numpy
|
||||
{env:_PYTEST_TOX_EXTRA_DEP:}
|
||||
commands=
|
||||
{env:_PYTEST_TOX_COVERAGE_RUN:} pytest -ra {posargs:testing/python/approx.py}
|
||||
{env:_PYTEST_TOX_COVERAGE_RUN:} pytest {posargs:testing/python/approx.py}
|
||||
|
||||
[testenv:py36-numpy]
|
||||
deps = {[testenv:py27-numpy]deps}
|
||||
@@ -154,7 +147,7 @@ deps =
|
||||
PyYAML
|
||||
{env:_PYTEST_TOX_EXTRA_DEP:}
|
||||
commands =
|
||||
{env:_PYTEST_TOX_COVERAGE_RUN:} pytest -ra doc/en
|
||||
{env:_PYTEST_TOX_COVERAGE_RUN:} pytest doc/en
|
||||
{env:_PYTEST_TOX_COVERAGE_RUN:} pytest --doctest-modules --pyargs _pytest
|
||||
|
||||
[testenv:regen]
|
||||
@@ -175,7 +168,7 @@ commands =
|
||||
[testenv:jython]
|
||||
changedir = testing
|
||||
commands =
|
||||
{envpython} {envbindir}/py.test-jython -ra {posargs}
|
||||
{envpython} {envbindir}/py.test-jython {posargs}
|
||||
|
||||
[testenv:py36-freeze]
|
||||
changedir = testing/freeze
|
||||
@@ -201,13 +194,14 @@ commands = python scripts/release.py {posargs}
|
||||
|
||||
[pytest]
|
||||
minversion = 2.0
|
||||
plugins = pytester
|
||||
addopts = -ra -p pytester --ignore=testing/cx_freeze
|
||||
rsyncdirs = tox.ini pytest.py _pytest testing
|
||||
addopts = -ra -p pytester
|
||||
rsyncdirs = tox.ini doc src testing
|
||||
python_files = test_*.py *_test.py testing/*/*.py
|
||||
python_classes = Test Acceptance
|
||||
python_functions = test
|
||||
norecursedirs = .tox ja .hg cx_freeze_source testing/example_scripts
|
||||
# NOTE: "doc" is not included here, but gets tested explicitly via "doctesting".
|
||||
testpaths = testing
|
||||
norecursedirs = testing/example_scripts
|
||||
xfail_strict=true
|
||||
filterwarnings =
|
||||
error
|
||||
|
||||
Reference in New Issue
Block a user