Compare commits

...

504 Commits
5.1.2 ... 5.3.1

Author SHA1 Message Date
Bruno Oliveira
05008f6b55 Preparing release version 5.3.1 2019-11-25 14:32:37 -03:00
Anthony Sottile
9d900930db Merge pull request #6242 from nicoddemus/testdir-changelog
Add link to testdir fixture in CHANGELOG entry
2019-11-24 17:23:02 -08:00
Daniel Hahler
a8230d77f4 Merge pull request #6270 from blueyed/docs-log_cli
docs: add missing `log_cli` confval
2019-11-24 23:21:49 +01:00
Daniel Hahler
b7d908f4a5 Merge pull request #6269 from blueyed/docs
tox: docs: --keep-going and posargs
2019-11-24 23:21:26 +01:00
Daniel Hahler
b9dd0e6210 docs: add missing log_cli confval 2019-11-24 22:34:15 +01:00
Daniel Hahler
57f3dc19b9 tox: docs: --keep-going and posargs
`--keep-going` makes sense with `-W` to see all warnings/errors.

`{posargs:}` is useful for passing in custom args.
2019-11-24 22:21:08 +01:00
Florian Bruhin
ed67312bca Merge pull request #6268 from felixonmars/patch-2
Correct a typo in _pytest/config/__init__.py
2019-11-24 17:04:26 +01:00
Felix Yan
b7bc52f770 Correct a typo in _pytest/config/__init__.py 2019-11-24 20:28:10 +08:00
Daniel Hahler
d1eb89d694 Merge pull request #6086 from kondratyev-nv/master
Fix line detection for properties in doctest tests
2019-11-22 23:09:10 +01:00
Nikolay Kondratyev
5e097970df Fix line detection for properties in doctest tests
Co-Authored-By: Daniel Hahler <github@thequod.de>
2019-11-22 23:50:20 +03:00
Daniel Hahler
0601f5cdad Merge pull request #6261 from blueyed/stats-keys
terminal: _get_main_color: help pytest-parallel
2019-11-22 17:12:33 +01:00
Daniel Hahler
a7268aaa5d Merge pull request #6258 from blueyed/notify_exception-handle-exit-exc-upstream
main: wrap_session: handle exit.Exception with notify_exception
2019-11-22 05:49:04 +01:00
Daniel Hahler
1f736a663d terminal: _get_main_color: help pytest-parallel
Use `dict.keys()` to work around `__iter__` not working with a
multiprocessing DictProxy.

Ref: https://github.com/python/cpython/pull/17333
Fixes https://github.com/pytest-dev/pytest/issues/6254.
Ref: https://github.com/browsertron/pytest-parallel/issues/36
2019-11-22 05:48:10 +01:00
Daniel Hahler
2344982d7f main: wrap_session: handle exit.Exception with notify_exception
Fixes https://github.com/pytest-dev/pytest/issues/6257.
Via https://github.com/blueyed/pytest/pull/132.
2019-11-21 22:50:33 +01:00
Daniel Hahler
63c9ad02f4 Merge pull request #6252 from blueyed/minor-changelog
CHANGELOG: two minor fixes/improvements
2019-11-21 18:44:41 +01:00
Daniel Hahler
8b7aeefd7d Merge pull request #6248 from blueyed/default-role
docs: configure default_role=literal
2019-11-21 18:44:17 +01:00
Bruno Oliveira
f1ac0eeef0 Remove sys.last_traceback attribute using del instead of settin… (#6256)
Remove sys.last_traceback attribute using del instead of setting to None
2019-11-21 11:16:19 -03:00
Mark Dickinson
82424c9270 Fix reST markup. 2019-11-21 13:37:17 +00:00
Mark Dickinson
dbb8c146f0 Use proper reST attribute markup. 2019-11-21 13:22:34 +00:00
Mark Dickinson
8d686a8e46 Add self to AUTHORS 2019-11-21 13:14:19 +00:00
Mark Dickinson
9d1082bd30 Add changelog file. 2019-11-21 13:13:36 +00:00
Bruno Oliveira
490c7c7262 Add develop instructions to CONTRIBUTING (#6249)
Add develop instructions to CONTRIBUTING
2019-11-21 10:07:00 -03:00
Mark Dickinson
2ffbe41ae5 clear sys.last_traceback via del instead of = None 2019-11-21 13:06:47 +00:00
Bruno Oliveira
8f2fd8ffc0 Add develop instructions to CONTRIBUTING
From: https://github.com/pytest-dev/pytest/pull/6244
2019-11-21 10:06:32 -03:00
Mark Dickinson
64eb9ea670 Modify test for new expected behaviour 2019-11-21 11:50:40 +00:00
Zac Hatfield-Dodds
7e10c8191d Added link checking to tox and release.py (#5614)
Added link checking to tox and release.py
2019-11-21 16:46:13 +11:00
Daniel Hahler
6659fe0edc CHANGELOG: two minor fixes/improvements 2019-11-21 06:20:57 +01:00
Daniel Hahler
4ee984ff0a Merge pull request #6250 from blueyed/tbh
pytester: LineMatcher: __tracebackhide__ with _fail
2019-11-21 04:26:20 +01:00
Daniel Hahler
b96e0a71a6 pytester: LineMatcher: __tracebackhide__ with _fail
Follow-up to 2228ccb (gone lost in resolving the conflict).
2019-11-21 01:23:36 +01:00
Daniel Hahler
be722652f0 docs: configure default_role=literal
This configures the default role for interpreted text (single
backticks), avoiding the need to check for / enforce double backticks.

Fixes also one instance in the existing changelog:

    - Detect `pytest_` prefixed hooks using the internal plugin manager since
      ``pluggy`` is deprecating the ``implprefix`` argument to ``PluginManager``.
      (`#3487 <https://github.com/pytest-dev/pytest/issues/3487>`_)
2019-11-21 00:35:00 +01:00
Daniel Hahler
58ec5bea35 Merge pull request #6237 from blueyed/fix-no_fnmatch_line
pytester: reset log output in _match_lines
2019-11-20 18:44:37 +01:00
Bruno Oliveira
0be03d7fe4 Add link to testdir fixture in CHANGELOG entry
As per https://github.com/pytest-dev/pytest/pull/5914#issuecomment-556063975
2019-11-20 13:14:17 -03:00
Bruno Oliveira
2879d25812 Fix rendering of Before/After in changelog (#6238)
Fix rendering of Before/After in changelog
2019-11-20 07:32:17 -03:00
Bruno Oliveira
fe69a2cfb7 Delete 5934.feature.rst included in the wrong folder by accident 2019-11-20 07:06:11 -03:00
Bruno Oliveira
af9dfc604d Introduce 5934 in CHANGELOG and fix "pytest" blocks 2019-11-20 07:05:31 -03:00
Anthony Sottile
8c65eae5f4 Fix rendering of Before/After in changelog
Apparently the version of sphinx that rtd uses is a little more strict about whether an anonymous `code-block` can happen
2019-11-19 21:12:30 -08:00
Daniel Hahler
2228ccbfb4 pytester: reset log output in _match_lines (#70)
This is necessary for when using e.g. `no_fnmatch_line` after it.
Factor it out into `_fail`.

(cherry picked from commit aade7ed0045ba32557ef8565cbab28a2c91053a7)

Ref: https://github.com/pytest-dev/pytest/pull/5914#issuecomment-549182242
2019-11-20 05:24:18 +01:00
Bruno Oliveira
7e5ad31428 Merge features into master after 5.3 (#6236)
Merge features into master after 5.3
2019-11-19 19:17:06 -03:00
Bruno Oliveira
688bbefed1 Improve instructions on how to write CHANGELOG entries (#6235)
Improve instructions on how to write CHANGELOG entries
2019-11-19 19:10:41 -03:00
Bruno Oliveira
5b3867fd65 Release 5.3.0 (#6233)
Release 5.3.0
2019-11-19 18:49:14 -03:00
Bruno Oliveira
36ef545b2d Improve instructions on how to write CHANGELOG entries
This makes easier for contributors to get the CHANGELOG entry
right the first time.
2019-11-19 14:15:55 -03:00
Daniel Hahler
1d368e0ed4 Merge pull request #6231 from blueyed/param-spell
Improve check for misspelling of parametrize
2019-11-19 17:57:28 +01:00
Bruno Oliveira
be59827216 Small fixes in the CHANGELOG for 5.3.0 2019-11-19 13:56:22 -03:00
Bruno Oliveira
4b16b93cf5 Preparing release version 5.3.0 2019-11-19 12:43:51 -03:00
Bruno Oliveira
21622d0df4 Merge remote-tracking branch 'upstream/master' into release-5.3.0 2019-11-19 12:42:11 -03:00
Daniel Hahler
4ad61cbcf6 Improve check for misspelling of parametrize
- there is no need to do this with `--strict-markers`
- it can be done when looking up marks, instead of for every generated
  test
2019-11-19 16:05:52 +01:00
Daniel Hahler
d1e2d12b3f python: remove unused pytest_make_parametrize_id hookimpl (#6228) 2019-11-19 02:55:24 +01:00
Bruno Oliveira
f36ea240fe Remove check for os.symlink, always there in py3+ (#6227)
Remove check for os.symlink, always there in py3+
2019-11-18 22:37:59 -03:00
Daniel Hahler
4804d4bc98 python: remove unused pytest_make_parametrize_id hookimpl
Added in 79927428d initially, but never used.
2019-11-19 02:27:53 +01:00
Daniel Hahler
b820b7e384 Merge pull request #6224 from blueyed/visit_Assert-minor-cleanup
minor: visit_Assert: move setting of `negation` out of branches
2019-11-19 01:11:53 +01:00
Daniel Hahler
8d3e8b1c54 Revert "ci: use tox -vv" (#6226) 2019-11-19 01:10:06 +01:00
Anthony Sottile
63a23d876c Remove check for os.symlink, always there in py3+ 2019-11-18 16:01:44 -08:00
Anthony Sottile
eeeb19626b Merge pull request #6202 from linw1995/fix_getmodpath
Fix incorrect result of getmodpath method.
2019-11-18 15:14:52 -08:00
Bruno Oliveira
bebd80456b Fix --setup-plan fixture lifetimes (#6214)
Fix --setup-plan fixture lifetimes
2019-11-18 20:04:17 -03:00
Daniel Hahler
f38f2d402e minor: visit_Assert: move setting of negation out of branches 2019-11-18 23:21:00 +01:00
Daniel Hahler
f9feef6808 Revert "ci: use tox -vv"
`tox -vv` is too verbose, and was only used as a hack to get the output
of durations.

As for information in logs `-v` could be used maybe still, but I've
decided to revert it for now.

This reverts commit 56cec5fa79.
2019-11-18 23:14:03 +01:00
Bruno Oliveira
89eeefbbaf Merge pull request #6192 from nicoddemus/remove-reportlog-6180
Remove report_log in favor of pytest-reportlog
2019-11-18 17:58:37 -03:00
Daniel Hahler
ed5f191da2 mypy: config: use mypy_path=src (#6222) 2019-11-18 21:51:39 +01:00
Daniel Hahler
b4ff6b3672 Metafunc: remove hack for DefinitionMock (#6223) 2019-11-18 19:56:00 +01:00
Daniel Hahler
2ad2fbc9a2 Metafunc: remove hack for DefinitionMock
Done initially in 99015bfc8.
2019-11-18 18:19:34 +01:00
Daniel Hahler
b461010f32 mypy: config: use mypy_path=src
This allows for checking files inside of "testing" without having
"src/…" as an argument also.
2019-11-18 18:12:59 +01:00
Daniel Hahler
64d8910516 Metafunc: remove unused _ids (#6220) 2019-11-18 18:00:29 +01:00
Daniel Hahler
b412661de9 Factor out _validate_parametrize_spelling (#6221) 2019-11-18 18:00:00 +01:00
林玮
5d5f480979 Hardening an existing test for demonstrating this change. 2019-11-18 23:46:38 +08:00
Daniel Hahler
91dec8e2bf Factor out _validate_parametrize_spelling
This makes it easier to read `pytest_generate_tests`.
2019-11-18 16:36:12 +01:00
Daniel Hahler
f3a10245d0 Metafunc: remove unused _ids
Forgotten in 40b85d7ee.
2019-11-18 16:21:13 +01:00
Josh Karpel
46ffdf0e3a Update AUTHORS 2019-11-17 17:17:47 -06:00
JoshKarpel
1e3be8ada4 fix whitespace issues in tests for #2049 2019-11-17 17:14:17 -06:00
JoshKarpel
6dfd683a0c changelog entry for #2049 2019-11-17 16:47:09 -06:00
JoshKarpel
9e759010d9 resolve #2049 2019-11-17 16:45:42 -06:00
Anthony Sottile
a2d48332fc Merge pull request #6201 from asottile/mm
Merge master into features
2019-11-17 11:30:51 -08:00
Daniel Hahler
bac6eebfff tests: revisit test_cacheprovider (#6199) 2019-11-17 19:23:16 +01:00
Ran Benita
fa578d7329 Merge pull request #6205 from bluetech/type-annotations-8
Add type annotations to _pytest.compat and _pytest._code.code
2019-11-17 09:45:32 +02:00
Daniel Hahler
b9a3ba1fe8 test_cache_writefail_permissions: ignore any other plugins 2019-11-16 23:29:24 +01:00
Daniel Hahler
1b4623a6d1 tests: revisit test_cacheprovider 2019-11-16 23:29:24 +01:00
Ran Benita
eaa34a9df0 Add type annotations to _pytest._code.code 2019-11-16 22:29:57 +02:00
Daniel Hahler
e3796047c1 pre-commit: upgrade black (#6208) 2019-11-16 19:20:12 +01:00
Daniel Hahler
54a954514b re-run black 2019-11-16 18:55:32 +01:00
Daniel Hahler
b1a597ab02 Remove (now) unnecessary fmt: off 2019-11-16 18:51:02 +01:00
Daniel Hahler
5d247b9caf pre-commit: upgrade black
This brings https://github.com/psf/black/pull/826, which helps with
https://github.com/psf/black/issues/601.
2019-11-16 18:42:17 +01:00
Daniel Hahler
4b7148f9a4 cacheprovider: set: use json.dumps + write (#6206) 2019-11-16 18:25:28 +01:00
Daniel Hahler
786d839db1 cacheprovider: set: use json.dumps + write
``json.dump`` is slower since it iterates over chunks [1].

For 100 ``cache.set`` calls this saved ~0.5s (2.5s => 2s), using a dict
with 1500 entries, and an encoded size of 500kb.

Python 3.7.4.

1: https://github.com/blueyed/cpython/blob/1c2e81ed00/Lib/json/__init__.py#L177-L180
2019-11-16 17:40:56 +01:00
Ran Benita
562d4811d5 Add type annotations to _pytest.compat 2019-11-16 17:22:11 +02:00
Ran Benita
a649f157de Make Source explicitly implement __iter__()
Source was previously iterable because it implements `__getitem__()`,
which is apparently a thing from before `__iter__()` was introduced.
To reduce mypy's and my own confusion, implement `__iter__()` directly.
2019-11-16 17:22:10 +02:00
Ran Benita
307add025b Simplify a FormattedExcinfo test
The previous test was better in that it used fakes to test all of the
real code paths. The problem with that is that it makes it impossible to
simplify the code with `isinstance` checks. So let's just simulate the
issue directly with a monkeypatch.
2019-11-16 17:22:09 +02:00
Ran Benita
e3ac44df36 Inline the FuncargnamesCompatAttr compat helper
It doesn't help much IMO, just adds indirection and makes it harder to
type.
2019-11-16 17:22:09 +02:00
Ran Benita
5bfe793fd5 Remove unneeded getrawcode() calls from tests 2019-11-16 17:22:08 +02:00
Ran Benita
04d68fbc9e Remove checks for Python2-only fields im_func and func_code 2019-11-16 17:22:07 +02:00
Ran Benita
c7a83a0f31 Remove a PyPy version check for an unsupported version
pytest doesn't support these PyPy versions anymore, so no need to have
checks for them.
2019-11-16 17:22:07 +02:00
Ran Benita
f760356578 A few linting fixes
Add some Python 3.8 type: ignores; all are already fixed in the next
mypy release, so can be removed once we upgrade.

Also move some flake8 ignores which seem to have changed places.
2019-11-16 17:22:06 +02:00
Zac Hatfield-Dodds
f24f20a46e Merge pull request #6204 from TH3CHARLie/add-changelog-on-tmp_path_factory
changelog: #3985 also introduce `tmp_path_factory`
2019-11-16 23:57:53 +11:00
TH3CHARLie
b090ac6204 remove trailing-whitespace 2019-11-16 18:01:08 +08:00
TH3CHARLie
9c681b45e3 change: #3985 also introduce 2019-11-16 17:34:05 +08:00
林玮
329f56ecec Fix incorrect result of getmodpath method. 2019-11-16 15:28:04 +08:00
Anthony Sottile
cc78444c30 Merge remote-tracking branch 'origin/master' into mm 2019-11-15 15:26:57 -08:00
Anthony Sottile
3a668ea6ff Merge pull request #6198 from asottile/release-5.2.4
Preparing release version 5.2.4
2019-11-15 14:57:08 -08:00
Daniel Hahler
c49c61fdaf Import Path from _pytest.pathlib for py35 (#6193) 2019-11-15 23:17:43 +01:00
Daniel Hahler
1abb08d52f tests: use sys.dont_write_bytecode
Setting PYTHONDONTWRITEBYTECODE in the environment does not change it
for the current process.
2019-11-15 23:13:08 +01:00
Anthony Sottile
c9a96cdee8 Preparing release version 5.2.4 2019-11-15 13:26:56 -08:00
Daniel Hahler
5979837c60 Import Path from _pytest.pathlib for py35
This is important for `isinstance` checks etc.
2019-11-15 22:19:53 +01:00
Anthony Sottile
19a15a94ee Merge pull request #6197 from asottile/fix_init_py_discovery
Fix incorrect discovery of non-test `__init__.py` files.
2019-11-15 13:19:48 -08:00
Anthony Sottile
4e0f99260d Add regression tests for __init__.py breakage 2019-11-15 13:19:31 -08:00
Anthony Sottile
176c7771fb Revert "fix bug with nonskipped first test in package (#5831)"
This reverts commit 85288b5321, reversing
changes made to 5f9db8a017.
2019-11-15 08:29:52 -08:00
Bruno Oliveira
d2ea9e2db5 Remove report_log in favor of pytest-reportlog
Fix #6180
2019-11-14 19:47:26 -03:00
Bruno Oliveira
e856638ba0 Preparing release version 5.2.3 (#6190)
Preparing release version 5.2.3
2019-11-14 18:17:28 -03:00
Bruno Oliveira
99f487864c Issue a warning to prepare change of 'junit_family' default val… (#6186)
Issue a warning to prepare change of 'junit_family' default value
2019-11-14 18:11:10 -03:00
Bruno Oliveira
dd9a27cf54 Adjust CHANGELOG 2019-11-14 17:51:38 -03:00
Daniel Hahler
d4e4ab5b3d Update text in PR template (#6188) 2019-11-14 21:30:16 +01:00
Bruno Oliveira
e2a0987156 Update advice about _called_from_test. (#6168)
Update advice about _called_from_test.
2019-11-14 17:10:53 -03:00
Michael Shields
bd68c2a3dc Update advice about _called_from_test.
Instead of giving an example of using sys and then, at the end,
advising not to use sys, just give a correct example. This is
especially helpful since mypy 0.740 has started (correctly) complaining
about sys._called_from_pytest not being present.
2019-11-14 19:44:27 +00:00
Bruno Oliveira
5e8c47faad Preparing release version 5.2.3 2019-11-14 11:12:06 -03:00
Hugo
48ec7d28c6 Make whole checklist a comment to avoid incomplete TODOs in PRs 2019-11-14 12:01:40 +02:00
Hugo
350c27c8b4 Update text in PR template 2019-11-14 11:36:47 +02:00
Daniel Hahler
2fc7d04fc3 Merge pull request #6187 from blueyed/minor
Minor fixes
2019-11-14 00:45:54 +01:00
Bruno Oliveira
92d6a0500b Show a better message when 'request' is used in parametrize (#6184)
Show a better message when 'request' is used in parametrize
2019-11-13 20:36:23 -03:00
Bruno Oliveira
6f2c0fd2e8 Show a better message when 'request' is used in parametrize
Fix #6183
2019-11-13 19:57:10 -03:00
Bruno Oliveira
2a67637acc Issue a warning to prepare change of 'junit_family' default value
Fix #6179
2019-11-13 19:55:13 -03:00
Daniel Hahler
772dfc4f9d terminal: fix/remove wrong typing for currentfspath
Can be -2, or py.path.local (not typed).
2019-11-13 23:24:24 +01:00
Daniel Hahler
55bc084dcc doc: s/_pytest.config.Parser/_pytest.config.argparsing.Parser/ 2019-11-13 23:22:25 +01:00
Daniel Hahler
b3bb604683 fix typo in _issue_warning_captured doc 2019-11-13 23:22:25 +01:00
Daniel Hahler
f91bf48a40 Merge pull request #6176 from blueyed/assertoutcome
pytester: Hookrecorder: improve assertoutcome
2019-11-13 19:27:18 +01:00
Daniel Hahler
a6e10cc2e3 Merge pull request #6181 from blueyed/maxfail-terminal-upstream
terminal: report ``session.shouldfail`` reason (``-x``)
2019-11-13 19:27:01 +01:00
Daniel Hahler
c232366f2f Merge pull request #6171 from blueyed/rm-xfail
tests: remove test_nested_marks (xfail)
2019-11-13 16:23:55 +01:00
Daniel Hahler
b06f33f474 terminal: report `session.shouldfail reason (-x`)
Via https://github.com/blueyed/pytest/pull/108.
2019-11-13 16:18:41 +01:00
Daniel Hahler
6ddf7c3d42 pytester: Hookrecorder: improve assertoutcome
Before:

        def assertoutcome(self, passed: int = 0, skipped: int = 0, failed: int = 0) -> None:
            realpassed, realskipped, realfailed = self.listoutcomes()
            assert passed == len(realpassed)
    >       assert skipped == len(realskipped)
    E       assert 1 == 0
    E        +  where 0 = len([])

After:

    >       reprec = testdir.inline_run(testpath, "-s")
    E       AssertionError: ([], [], [<TestReport 'nodeid' when='call' outcome='failed'>])
    E       assert {'failed': 1, 'passed': 0, 'skipped': 0} == {'failed': 0, 'passed': 0, 'skipped': 1}
2019-11-13 13:48:20 +01:00
Daniel Hahler
e2022a6d48 pytester: assert_outcomes: use/set __tracebackhide__ (#6172) 2019-11-12 23:07:58 +01:00
Zac Hatfield-Dodds
55a58bcd3d Merge pull request #6173 from brettcannon/patch-1
Delineate syntactically that the 'match' argument to 'raises' is keyword-only
2019-11-13 08:17:25 +11:00
Brett Cannon
0b40749c98 Delineate syntactically that the 'match' argument to 'raises' is keyword-only 2019-11-12 12:32:05 -08:00
Bruno Oliveira
1b84f099b6 Merge pull request #6170 from blueyed/imp
filterwarnings: ignore DeprecationWarning from nose
2019-11-12 13:00:50 -03:00
Bruno Oliveira
adccb63de0 setup.cfg: fix check-manifest ignore [ci skip] (#6156)
setup.cfg: fix check-manifest ignore  [ci skip]
2019-11-12 11:45:31 -03:00
Daniel Hahler
86e9ae39f0 pytester: assert_outcomes: use/set __tracebackhide__ 2019-11-12 15:28:36 +01:00
Daniel Hahler
dad4985be1 A bit more typing around Node (#6167) 2019-11-12 14:46:05 +01:00
Daniel Hahler
fc1c015c6b tests: remove test_nested_marks (xfail)
It currently fails with a TypeError, and was not updated since 2013 -
therefore it can be assumed that it is not important to support it.

```
____________________ ERROR collecting test_nested_marks.py _____________________
…/Vcs/pluggy/src/pluggy/hooks.py:286: in __call__
    return self._hookexec(self, self.get_hookimpls(), kwargs)
…/Vcs/pluggy/src/pluggy/manager.py:93: in _hookexec
    return self._inner_hookexec(hook, methods, kwargs)
…/Vcs/pluggy/src/pluggy/manager.py:337: in traced_hookexec
    return outcome.get_result()
…/Vcs/pluggy/src/pluggy/manager.py:335: in <lambda>
    outcome = _Result.from_call(lambda: oldcall(hook, hook_impls, kwargs))
…/Vcs/pluggy/src/pluggy/manager.py:87: in <lambda>
    firstresult=hook.spec.opts.get("firstresult") if hook.spec else False,
…/Vcs/pytest/src/_pytest/python.py:235: in pytest_pycollect_makeitem
    res = list(collector._genfunctions(name, obj))
…/Vcs/pytest/src/_pytest/python.py:404: in _genfunctions
    self.ihook.pytest_generate_tests.call_extra(methods, dict(metafunc=metafunc))
…/Vcs/pluggy/src/pluggy/hooks.py:324: in call_extra
    return self(**kwargs)
…/Vcs/pluggy/src/pluggy/hooks.py:286: in __call__
    return self._hookexec(self, self.get_hookimpls(), kwargs)
…/Vcs/pluggy/src/pluggy/manager.py:93: in _hookexec
    return self._inner_hookexec(hook, methods, kwargs)
…/Vcs/pluggy/src/pluggy/manager.py:337: in traced_hookexec
    return outcome.get_result()
…/Vcs/pluggy/src/pluggy/manager.py:335: in <lambda>
    outcome = _Result.from_call(lambda: oldcall(hook, hook_impls, kwargs))
…/Vcs/pluggy/src/pluggy/manager.py:87: in <lambda>
    firstresult=hook.spec.opts.get("firstresult") if hook.spec else False,
…/Vcs/pytest/src/_pytest/python.py:130: in pytest_generate_tests
    metafunc.parametrize(*marker.args, **marker.kwargs)
…/Vcs/pytest/src/_pytest/python.py:965: in parametrize
    function_definition=self.definition,
…/Vcs/pytest/src/_pytest/mark/structures.py:111: in _for_parametrize
    if len(param.values) != len(argnames):
E   TypeError: object of type 'MarkDecorator' has no len()
!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!
```
2019-11-12 14:44:03 +01:00
Daniel Hahler
4c7d971f13 filterwarnings: ignore DeprecationWarning from nose
This comes via hypothesis:

```
% COLUMNS=80 p testing/python/metafunc.py::TestMetafunc::test_idval_hypothesis -vv --tb=short
============================= test session starts ==============================
platform linux -- Python 3.7.4, pytest-3.1.4.dev721+g3367bf03b.d20191112, py-1.8.1.dev11+g34f716fe, pluggy-0.13.1.dev8+ga5130ac.d20191103 -- …/Vcs/pytest/.venv/bin/python
cachedir: .pytest_cache
hypothesis profile 'default' -> database=DirectoryBasedExampleDatabase('…/Vcs/pytest/.hypothesis/examples')
rootdir: …/Vcs/pytest, inifile: tox.ini
plugins: forked-1.1.3, hypothesis-4.44.1, cov-2.8.1, coverage-pytest-plugin-0.1, enhancements-0.0.5.dev1-gf361636-dirty, xdist-1.30.0
collected 1 item

testing/python/metafunc.py::TestMetafunc::test_idval_hypothesis FAILED   [100%]

=================================== FAILURES ===================================
______________________ TestMetafunc.test_idval_hypothesis ______________________
.venv/lib/python3.7/site-packages/hypothesis/core.py:588: in evaluate_test_data
    result = self.execute(data)
.venv/lib/python3.7/site-packages/hypothesis/core.py:553: in execute
    result = self.test_runner(data, run)
.venv/lib/python3.7/site-packages/hypothesis/executors.py:56: in default_new_style_executor
    return function(data)
.venv/lib/python3.7/site-packages/hypothesis/core.py:536: in run
    args, kwargs = data.draw(self.search_strategy)
.venv/lib/python3.7/site-packages/hypothesis/internal/conjecture/data.py:857: in draw
    return strategy.do_draw(self)
.venv/lib/python3.7/site-packages/hypothesis/core.py:223: in do_draw
    return self.mapped_strategy.do_draw(data)
.venv/lib/python3.7/site-packages/hypothesis/searchstrategy/collections.py:60: in do_draw
    return tuple(data.draw(e) for e in self.element_strategies)
.venv/lib/python3.7/site-packages/hypothesis/searchstrategy/collections.py:60: in <genexpr>
    return tuple(data.draw(e) for e in self.element_strategies)
.venv/lib/python3.7/site-packages/hypothesis/internal/conjecture/data.py:852: in draw
    return strategy.do_draw(self)
.venv/lib/python3.7/site-packages/hypothesis/searchstrategy/strategies.py:570: in do_draw
    result = self.pack(data.draw(self.mapped_strategy))
.venv/lib/python3.7/site-packages/hypothesis/internal/conjecture/data.py:852: in draw
    return strategy.do_draw(self)
.venv/lib/python3.7/site-packages/hypothesis/searchstrategy/lazy.py:156: in do_draw
    return data.draw(self.wrapped_strategy)
.venv/lib/python3.7/site-packages/hypothesis/internal/conjecture/data.py:852: in draw
    return strategy.do_draw(self)
.venv/lib/python3.7/site-packages/hypothesis/searchstrategy/strategies.py:570: in do_draw
    result = self.pack(data.draw(self.mapped_strategy))
.venv/lib/python3.7/site-packages/hypothesis/internal/conjecture/data.py:852: in draw
    return strategy.do_draw(self)
.venv/lib/python3.7/site-packages/hypothesis/searchstrategy/collections.py:60: in do_draw
    return tuple(data.draw(e) for e in self.element_strategies)
.venv/lib/python3.7/site-packages/hypothesis/searchstrategy/collections.py:60: in <genexpr>
    return tuple(data.draw(e) for e in self.element_strategies)
.venv/lib/python3.7/site-packages/hypothesis/internal/conjecture/data.py:852: in draw
    return strategy.do_draw(self)
.venv/lib/python3.7/site-packages/hypothesis/searchstrategy/strategies.py:508: in do_draw
    return data.draw(self.element_strategies[i], label=self.branch_labels[i])
.venv/lib/python3.7/site-packages/hypothesis/internal/conjecture/data.py:852: in draw
    return strategy.do_draw(self)
.venv/lib/python3.7/site-packages/hypothesis/searchstrategy/lazy.py:156: in do_draw
    return data.draw(self.wrapped_strategy)
.venv/lib/python3.7/site-packages/hypothesis/internal/conjecture/data.py:852: in draw
    return strategy.do_draw(self)
.venv/lib/python3.7/site-packages/hypothesis/searchstrategy/strategies.py:570: in do_draw
    result = self.pack(data.draw(self.mapped_strategy))
.venv/lib/python3.7/site-packages/hypothesis/internal/conjecture/data.py:852: in draw
    return strategy.do_draw(self)
.venv/lib/python3.7/site-packages/hypothesis/searchstrategy/lazy.py:156: in do_draw
    return data.draw(self.wrapped_strategy)
.venv/lib/python3.7/site-packages/hypothesis/internal/conjecture/data.py:852: in draw
    return strategy.do_draw(self)
.venv/lib/python3.7/site-packages/hypothesis/searchstrategy/collections.py:120: in do_draw
    result.append(data.draw(self.element_strategy))
.venv/lib/python3.7/site-packages/hypothesis/internal/conjecture/data.py:852: in draw
    return strategy.do_draw(self)
.venv/lib/python3.7/site-packages/hypothesis/searchstrategy/numbers.py:62: in do_draw
    return d.integer_range(data, self.start, self.end)
.venv/lib/python3.7/site-packages/hypothesis/internal/conjecture/utils.py:105: in integer_range
    probe = data.draw_bits(bits)
.venv/lib/python3.7/site-packages/hypothesis/internal/conjecture/data.py:974: in draw_bits
    self.__check_capacity(n_bytes)
.venv/lib/python3.7/site-packages/hypothesis/internal/conjecture/data.py:1019: in __check_capacity
    self.mark_overrun()
.venv/lib/python3.7/site-packages/hypothesis/internal/conjecture/data.py:1036: in mark_overrun
    self.conclude_test(Status.OVERRUN)
.venv/lib/python3.7/site-packages/hypothesis/internal/conjecture/data.py:1027: in conclude_test
    raise StopTest(self.testcounter)
E   hypothesis.errors.StopTest: 0

During handling of the above exception, another exception occurred:
testing/python/metafunc.py:195: in test_idval_hypothesis
    @hypothesis.settings(
.venv/lib/python3.7/site-packages/nose/__init__.py:1: in <module>
    from nose.core import collector, main, run, run_exit, runmodule
.venv/lib/python3.7/site-packages/nose/core.py:12: in <module>
    from nose.loader import defaultTestLoader
.venv/lib/python3.7/site-packages/nose/loader.py:21: in <module>
    from nose.importer import Importer, add_path, remove_path
.venv/lib/python3.7/site-packages/nose/importer.py:12: in <module>
    from imp import find_module, load_module, acquire_model1, release_model1
/usr/lib/python3.7/imp.py:33: in <module>
    DeprecationWarning, stacklevel=2)
E   DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses
---------------------------------- Hypothesis ----------------------------------
You can add @seed(198901559535749756451579900660745168041) to this test or run pytest with --hypothesis-seed=198901559535749756451579900660745168041 to reproduce this failure.
=============================== warnings summary ===============================
testing/python/metafunc.py::TestMetafunc::test_idval_hypothesis
  …/Vcs/pytest/.venv/lib/python3.7/site-packages/unittest2/compatibility.py:143: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
    class ChainMap(collections.MutableMapping):

-- Docs: https://docs.pytest.org/en/latest/warnings.html
=========================== short test summary info ============================
FAILED testing/python/metafunc.py::TestMetafunc::test_idval_hypothesis - Depr...
```
2019-11-12 14:32:11 +01:00
Ran Benita
b352e34938 Merge pull request #6149 from bluetech/cached-property
Add a @cached_property implementation
2019-11-10 22:11:04 +02:00
Daniel Hahler
3ef8aa8173 A bit more typing around Node 2019-11-10 19:06:27 +01:00
Daniel Hahler
abcedd6095 _compare_eq_iterable: use AlwaysDispatchingPrettyPrinter (#6151) 2019-11-10 14:08:25 +01:00
Ran Benita
42a46ea786 Add a @cached_property implementation
This is a useful utility to abstract the caching property idiom.

It is in compat.py since eventually it will be replaced by
functools.cached_property.

Fixes #6131.
2019-11-10 10:21:55 +02:00
Daniel Hahler
710e3c40e0 typing around terminal (#6157) 2019-11-10 00:19:11 +01:00
Daniel Hahler
0bbc032db0 [WIP] typing around terminal 2019-11-09 23:53:37 +01:00
Daniel Hahler
96c315e439 showversion: no need for py.path.local (#6163) 2019-11-09 22:45:02 +01:00
Daniel Hahler
28edbaace4 showversion: no need for py.path.local 2019-11-09 21:45:35 +01:00
Daniel Hahler
41604eeb3e setup.cfg: fix check-manifest ignore [ci skip] 2019-11-08 22:39:39 +01:00
Bruno Oliveira
245e1f10e5 Merge pull request #6152 from grlee77/module_name_in_id
use __name__ attribute in the parametrize id for modules as well
2019-11-08 14:16:34 -03:00
Bruno Oliveira
c16b121594 Add CHANGELOG for #6152 2019-11-08 10:52:04 -03:00
Bruno Oliveira
04891048e1 Two minor tweaks in MarkDecorator's implementation (#6150)
Two minor tweaks in MarkDecorator's implementation
2019-11-08 10:46:48 -03:00
Ran Benita
84b2c81db4 Drop the "alias" helper used in MarkDecorator
It is a little too obscure IMO, but the reason I want to drop it is that
type checking has no hope of understanding such dynamic constructs.

The warning argument wasn't used.
2019-11-08 11:22:46 +02:00
Ran Benita
984d90a811 Drop redundant custom MarkDecorator __eq__ implementation
This is already covered by attrs.

Also, the custom implementation returns False when the types don't
match, but it's better to return `NotImplemented`. attrs does this.
2019-11-08 11:14:31 +02:00
Gregory Lee
db82432ec8 add minimal test case 2019-11-08 01:39:29 -05:00
Gregory Lee
cc6c5e15b8 update AUTHORS list 2019-11-08 01:39:29 -05:00
Gregory Lee
c22ce1a12c parametrize: allow __name__ id for modules or other objects as well 2019-11-08 01:39:29 -05:00
Daniel Hahler
cc503c1821 _compare_eq_iterable: use AlwaysDispatchingPrettyPrinter
This fixes/removes the previous hack of re-trying with minimum width,
which fails short when it splits strings.

This inherits from `pprint.PrettyPrinter` to override `_format` in a
minimal way to always dispatch, regardless of the given width.

Code ref: 5c0c325453/Lib/pprint.py (L170-L178)
2019-11-08 04:07:09 +01:00
Bruno Oliveira
6ad95716da add --co option to collect-only (#6116)
add --co option to collect-only
2019-11-07 19:03:32 -03:00
Bruno Oliveira
3b243404e6 Explicitly implement pytest_assertrepr_compare in assertion plu… (#6140)
Explicitly implement pytest_assertrepr_compare in assertion plugin
2019-11-07 18:45:26 -03:00
Daniel Hahler
09709bba06 Use atomicrewrites only on Windows (#6148) 2019-11-07 22:13:26 +01:00
Daniel Hahler
40626f48e7 Update changelog/6148.improvement.rst
Co-Authored-By: Bruno Oliveira <nicoddemus@gmail.com>
2019-11-07 22:13:03 +01:00
Bruno Oliveira
7ed33996f1 on_rm_rf_error: ignore os.open (no warning) (#6074)
on_rm_rf_error: ignore os.open (no warning)
2019-11-07 18:06:05 -03:00
Bruno Oliveira
0cf2002a1f Explicitly implement pytest_assertrepr_compare in assertion plugin
Previously it was an alias, which makes it unnecessary hard to find
all implementations (either by IDE or using a simple search).
2019-11-07 18:00:27 -03:00
NNRepos
4946cc8282 Add --co option to collect-only
Fix #5845
2019-11-07 17:50:27 -03:00
Daniel Hahler
45c4a8fb3d Use atomicrewrites only on Windows
Fixes https://github.com/pytest-dev/pytest/issues/6147
2019-11-07 20:57:45 +01:00
Ran Benita
e670ff76cb Merge pull request #6141 from bluetech/type-annotations-7
Add type annotations to _pytest.{warning_types,_code.source,pytester}
2019-11-07 17:11:01 +02:00
Daniel Hahler
19b2f4bb8a tests: use ids (#6145) 2019-11-07 14:43:46 +01:00
Daniel Hahler
f11237b066 _perform_collect: remove comment about untested code (#6144) 2019-11-07 13:18:08 +01:00
Daniel Hahler
14eaa05b60 Merge pull request #6143 from blueyed/test_source
test_source: do not instantiate Source objects during collection
2019-11-07 13:16:34 +01:00
Ran Benita
265a9eb6a2 Add type annotations to some of _pytest.pytester 2019-11-07 14:13:47 +02:00
Ran Benita
58f2849bf6 Add type annotations to _pytest._code.source
At least most of it.
2019-11-07 14:13:47 +02:00
Ran Benita
b2537b22d7 Add type annotations to _pytest.warning_types 2019-11-07 14:11:39 +02:00
Daniel Hahler
2adc84ed6c changelog 2019-11-07 12:55:01 +01:00
Daniel Hahler
2e5cf1cc78 Fix order of format args with warning 2019-11-07 12:50:04 +01:00
Daniel Hahler
8aa0809fbc on_rm_rf_error: ignore os.open (no warning)
Ref: https://github.com/pytest-dev/pytest/pull/6044/files#r339321752
2019-11-07 12:50:04 +01:00
Daniel Hahler
ab101658f0 saferepr: handle BaseExceptions (#6047) 2019-11-07 12:33:22 +01:00
Daniel Hahler
b268463243 Merge master into features (#6111) 2019-11-07 12:31:33 +01:00
Daniel Hahler
dd852ded70 _perform_collect: remove comment about untested code
Harden one test where it is tested.

All tests testing this:

    testing/acceptance_test.py:184(TestGeneralUsage::test_not_collectable_arguments)
    testing/acceptance_test.py:373(TestGeneralUsage::test_direct_addressing_notfound)
    testing/acceptance_test.py:403(TestGeneralUsage::test_issue134_report_error_when_collecting_member[test_fun.py::test_a])
    testing/acceptance_test.py:420(TestGeneralUsage::test_report_all_failed_collections_initargs)
    testing/test_config.py:1309(test_config_blocked_default_plugins[python])

(via https://github.com/blueyed/pytest/pull/88)
2019-11-07 12:29:36 +01:00
Daniel Hahler
dd6cf7c172 test_exc_chain_repr_without_traceback: use ids 2019-11-07 12:25:46 +01:00
Daniel Hahler
5c00226847 test_iterable_full_diff: use test ids 2019-11-07 12:24:01 +01:00
Daniel Hahler
e8a3d1adf2 Fix test_trace_with_parametrize_handles_shared_fixtureinfo for colors 2019-11-07 11:48:51 +01:00
Daniel Hahler
cb21a8db1d test_source: do not instantiate Source objects during collection 2019-11-07 11:44:26 +01:00
Daniel Hahler
0c0d33f78e Session: collect: keep/use already parsed initialpart (#6120) 2019-11-07 11:16:24 +01:00
Daniel Hahler
c4a110b20a Session: collect: keep/use already parsed initialpart
Via https://github.com/blueyed/pytest/pull/42.
2019-11-06 22:09:24 +01:00
Daniel Hahler
fee7c7b032 py38: do not call None() directly
Works around:

    _____ ERROR collecting testing/io/test_saferepr.py _____
    src/_pytest/python.py:502: in _importtestmodule
        mod = self.fspath.pyimport(ensuresyspath=importmode)
    .venv38/lib/python3.8/site-packages/py/_path/local.py:701: in pyimport
        __import__(modname)
    <frozen importlib._bootstrap>:991: in _find_and_load
        ???
    <frozen importlib._bootstrap>:975: in _find_and_load_unlocked
        ???
    <frozen importlib._bootstrap>:671: in _load_unlocked
        ???
    src/_pytest/assertion/rewrite.py:136: in exec_module
        source_stat, co = _rewrite_test(fn, self.config)
    src/_pytest/assertion/rewrite.py:288: in _rewrite_test
        co = compile(tree, fn, "exec", dont_inherit=True)
    E     File "…/Vcs/pytest/testing/io/test_saferepr.py", line 45
    E       None()
    E       ^
    E   SyntaxError: 'NoneType' object is not callable; perhaps you missed a comma?
2019-11-06 22:08:10 +01:00
Daniel Hahler
eb7a4e32ad saferepr: handle BaseExceptions
This causes INTERNALERRORs with pytest-django, which uses
`pytest.fail` (derived from `BaseException`) to prevent DB access, when
pytest then tries to e.g. display the `repr()` for a Django `QuerySet`
etc.

Ref: https://github.com/pytest-dev/pytest-django/pull/776
2019-11-06 22:08:10 +01:00
Daniel Hahler
5be3a9b5ce tests: speed up test_faulthandler.test_timeout (#6075) 2019-11-06 22:07:28 +01:00
Daniel Hahler
fe429d4ce8 assert: fix _compare_eq_iterable: re-format both sides (#6137) 2019-11-06 22:06:39 +01:00
Steffen Schroeder
ceeb7bd085 Fixed broken links 2019-11-06 20:54:41 +01:00
Steffen Schroeder
1cecdf6619 Added checklinks to tox and release.py 2019-11-06 20:54:41 +01:00
Bruno Oliveira
5738d189a4 [RDY] tox: remove platform restriction, only used for pexpect (#6068)
[RDY] tox: remove platform restriction, only used for pexpect
2019-11-06 16:09:27 -03:00
Bruno Oliveira
85288b5321 fix bug with nonskipped first test in package (#5831)
fix bug with nonskipped first test in package
2019-11-06 16:06:46 -03:00
Bruno Oliveira
74f4ec5986 Making it possible to access the pluginmanager in the pytest_ad… (#6106)
Making it possible to access the pluginmanager in the pytest_addoptio…
2019-11-06 15:18:59 -03:00
Daniel Hahler
8dcee39ce9 Merge pull request #6103 from blueyed/lsof_check
tests: lsof_check: include exc with skip message
2019-11-06 18:44:19 +01:00
Daniel Hahler
4e45472405 Merge master into features
Conflicts:
	src/_pytest/debugging.py
2019-11-06 14:22:07 +01:00
Daniel Hahler
92b436c938 Merge pull request #6136 from blueyed/fix-rm_rf-tests
Fix rm_rf tests
2019-11-06 14:19:47 +01:00
Daniel Hahler
5f9db8a017 Merge pull request #6138 from blueyed/ci-py38
ci: Travis: remove py38 from allowed failures; do not use "-dev"
2019-11-06 14:19:14 +01:00
Daniel Hahler
01769b141a Merge pull request #6100 from blueyed/fix-skip-offset
terminal: fix line offset with skip reports
2019-11-06 13:29:40 +01:00
Daniel Hahler
0485b07ff0 Merge pull request #6071 from blueyed/tests-doctest-mock
Tests: approx: mock doctest runner's pdb usage
2019-11-06 13:27:50 +01:00
Daniel Hahler
eb4b3ce1c8 Merge pull request #6084 from blueyed/merge-rm
tests: merge/remove test_dontreadfrominput_buffer_python3
2019-11-06 13:24:47 +01:00
Daniel Hahler
9071a2a5e0 Merge pull request #6119 from blueyed/FSCollector-fspath
FSCollector: keep/use given fspath
2019-11-06 13:10:27 +01:00
Daniel Hahler
957adbbbc7 ci: Travis: remove py38 from allowed failures; do not use "-dev" 2019-11-06 13:09:11 +01:00
Daniel Hahler
ce3d431002 assert: fix _compare_eq_iterable: re-format both sides
Follow-up to 946434c61 (#5924).

Before this patch the test would look like this:

    {'env': {'sub...s wrapped'}}}} == {'env': {'sub...}}}, 'new': 1}
    Omitting 1 identical items, use -vv to show
    Right contains 1 more item:
    {'new': 1}
    Full diff:
      {
       'env': {'sub': {'long_a': 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
    -                  'sub1': {'long_a': 'substring '
    +                  'sub1': {'long_a': 'substring that gets wrapped'}}},
    ?                                                +++++++++++++++++ ++++
    +  'new': 1,
    -                                     'that '
    -                                     'gets '
    -                                     'wrapped'}}},
      }
2019-11-06 11:23:15 +01:00
Daniel Hahler
e7320c6b54 Merge pull request #5926 from AtakamaLLC/optional-multiline
Add log-auto-indent option to control multiline formatting
2019-11-06 00:35:24 +01:00
Daniel Hahler
00f67494e5 Merge pull request #6107 from MarcoGorelli/color-percentage-indicator
Color percentage indicator
2019-11-06 00:28:45 +01:00
Daniel Hahler
d8096925fa Fix for Python 3.5 not handling LocalPath 2019-11-06 00:22:46 +01:00
Daniel Hahler
9309ae299a Use try/finally to ensure chmod is run, filter warning 2019-11-05 22:28:32 +01:00
Daniel Hahler
dc2c51302a Revert "tests: filterwarnings: do not crash with "(rm_rf)" warning"
This reverts commit 6b2bae9392.
2019-11-05 22:11:56 +01:00
Tibor Arpas
262ed567d0 tests: clean up chmod-related tests to fix rm_rf warnings
Fixed https://github.com/pytest-dev/pytest/issues/5974#issuecomment-549822509.
2019-11-05 22:10:27 +01:00
Daniel Hahler
0794289689 Merge pull request #6129 from blueyed/typing
Typing around Node.location, reportinfo, repr_excinfo etc
2019-11-05 18:29:29 +01:00
Daniel Hahler
cab29dc861 Merge pull request #6124 from blueyed/test_group_warnings_by_message-ignore-own-warning
test_group_warnings_by_message: ignore own PytestExperimentalApiWarning
2019-11-05 16:24:10 +01:00
Daniel Hahler
741f0fedd1 typing around Node.location, reportinfo, repr_excinfo etc 2019-11-05 16:22:58 +01:00
Florian Bruhin
990d75b7e6 Merge pull request #6134 from zmhassan/small_bug1
Removing variable being reassigned
2019-11-05 10:17:04 +01:00
Zak Hassan
f9e3a5395c Removing variable being reassigned 2019-11-04 21:11:54 -05:00
MarcoGorelli
0d79061432 Color percentage indicator according to color of final line
indicate current outcome/status with color of percentage indicator

Fix type annotation, refactor _write_progress_information_filling_space

Keep code in _get_main_color as similar as possible to how it was before

Write test

Make black-compliant

Fix error in newly introduced test_collecterror

Make tests more readable by using constants and f-strings

Remove accidentally added monkeypatch

Make Python 3.5-compatible, add changelog entry

Add newline at the end of changelog file
2019-11-04 19:57:07 +00:00
Bruno Oliveira
0760406480 update chanelog link to moved pip docs page (#6127)
update chanelog link to moved pip docs page
2019-11-04 13:18:09 -03:00
Daniel Hahler
d53794f916 Merge pull request #6125 from blueyed/upstream-test_terminal-fulltrace
test_terminal: reduce number of tests (single --fulltrace param)
2019-11-04 11:50:18 +01:00
David Szotten
14580c7e31 update chanelog link to moved pip docs page 2019-11-04 10:05:05 +00:00
Ran Benita
08c25b7fe0 Merge pull request #6122 from bluetech/type-annotations-6
Add type annotations to _pytest._io.saferepr and _pytest.assertion.util._diff_text
2019-11-04 09:58:09 +02:00
Daniel Hahler
9f800b2a77 test_terminal: reduce number of tests (single --fulltrace param)
Remove the `--fulltrace` arg from the `Option` fixture used in several
tests, but not checked for.  Only use it with `test_keyboard_interrupt`.

(removes 8 tests, coverage not affected)
2019-11-03 21:23:18 +01:00
Daniel Hahler
68dbc24dcb test_group_warnings_by_message: ignore own PytestExperimentalApiWarning 2019-11-03 21:14:24 +01:00
Ran Benita
7d3ce374d2 Add type annotations to _pytest.assertion.util 2019-11-03 20:28:43 +02:00
Ran Benita
18d181fa77 Remove dead code in _pytest.assertion.util._diff_text
The function handles bytes input, however that is never used.
The function has two callers:

1)

```
            if istext(left) and istext(right):
                explanation = _diff_text(left, right, verbose
```

`istext` checks `isinstance(str)`.

2)

```
def _notin_text(term: str, text: str, verbose: int = 0) -> List[str]:
    ...
    diff = _diff_text(correct_text, text, verbose
```

and `_notin_text` is called once:

```
            if istext(left) and istext(right):
                explanation = _notin_text(left, right, verbose
```
2019-11-03 20:28:43 +02:00
Ran Benita
dc30d78845 Add type annotations to _pytest._io.saferepr 2019-11-03 20:28:43 +02:00
MarcoGorelli
9303de877a Fix error in newly introduced test_collecterror
Via https://github.com/pytest-dev/pytest/pull/6107.

(cherry picked from commit 1b9fbbfa195aa20c48574265935dc5e66b96ec16)
2019-11-03 18:16:46 +01:00
Daniel Hahler
3fb969897a Merge pull request #6059 from blueyed/collect-error-short-summary
terminal: report collection errors as "ERROR" in short summary
2019-11-02 15:34:12 +01:00
Daniel Hahler
6df4d07a57 Merge pull request #6067 from blueyed/harden-test
tests: harden test_disabled_capture_fixture
2019-11-02 15:33:55 +01:00
Bruno Oliveira
0947ecd6f0 Change 5924 and 5936 changelog entries to improvement (#6065)
Change 5924 and 5936 changelog entries to improvement
2019-11-02 11:23:06 -03:00
Daniel Hahler
0c7c26fe6e FSCollector: keep/use given fspath
Via https://github.com/blueyed/pytest/pull/42.
2019-11-02 10:02:23 +01:00
Florian Bruhin
35800a2f73 Merge pull request #6112 from gaucheph/fix-small-typo
typos
2019-11-01 11:18:06 +01:00
Patrick Harmon
abc890079f typos 2019-10-31 23:19:35 -05:00
Daniel Hahler
dc5a4fbe23 Merge pull request #6099 from davidszotten/trace_parametrize
Fix --trace for parametrized tests
2019-11-01 05:06:26 +01:00
David Szotten
285524c6cd Fix --trace for parametrized tests
Without this, the second time it tries to stop in a parametrized
function it raises instead:

`ValueError: --trace can't be used with a fixture named func!`

Implementation idea, test (and changelog tweaks) thanks to blueyed

Co-Authored-By: Ronny Pfannschmidt <opensource@ronnypfannschmidt.de>
Co-Authored-By: Daniel Hahler <git@thequod.de>
2019-10-31 21:41:33 +00:00
Daniel Hahler
7f851a215b Merge pull request #6032 from blueyed/ci-branches
ci: Travis: configure/restrict branch builds
2019-10-31 10:38:01 +01:00
Joshua Storck
f400804206 Removing pluginmanager as parameter in definition of pytest_addoption hook 2019-10-30 16:25:50 -04:00
Joshua Storck
0027908e9e Removing :py:func: and :ref: from changelog as it's not supported by towncrier 2019-10-30 15:02:18 -04:00
Joshua Storck
7a96d94fd4 Making it possible to access the pluginmanager in the pytest_addoption hook 2019-10-30 14:18:13 -04:00
Bruno Oliveira
3406857284 Introduce --report-log (#5980)
Introduce --report-log
2019-10-30 10:24:35 -03:00
Bruno Oliveira
09096f7436 Remove 'experimental' status from report serialization hooks 2019-10-30 09:43:33 -03:00
Bruno Oliveira
b99661b9d7 Introduce --report-log option
Fix #4488
2019-10-30 09:43:33 -03:00
Daniel Hahler
8c21416798 lsof_check: include exc with skip message 2019-10-29 15:25:15 +01:00
Daniel Hahler
6d2cabae57 terminal: fix line offset with skip reports
The original fix in https://github.com/pytest-dev/pytest/pull/2548 was
wrong, and was likely meant to fix the use with decorators instead,
which this does now (while reverting 869eed9898).
2019-10-29 13:11:02 +01:00
Daniel Hahler
0225cb37c0 Merge pull request #6077 from blueyed/ci-remove-single-py37-pexpect
ci: Travis: include pexpect in main py37 job
2019-10-28 17:05:31 +01:00
Daniel Hahler
cdc53da19c Merge pull request #5990 from MarcoGorelli/plurality-matching
Plurality matching
2019-10-28 17:03:15 +01:00
Daniel Hahler
e7898dedf4 Merge pull request #6093 from blueyed/fix-flaky
tests: fix testing/test_capture.py::test_typeerror_encodedfile_write
2019-10-28 16:17:46 +01:00
Daniel Hahler
6d33f4cdce Merge pull request #6076 from blueyed/runpytest_subprocess-slowest
tests: conftest: handle tests using runpytest_subprocess as "slowest"
2019-10-28 14:49:54 +01:00
Daniel Hahler
60ceec6eb1 tests: fix testing/test_capture.py::test_typeerror_encodedfile_write
Failed for me due to different indent (?) - not reproducible:

    >   ???
    E   Failed: nomatch: 'E           TypeError: write() argument must be str, not bytes'
    …
    E       and: '>   def mode(self):'
    E       and: 'E   TypeError: write() argument must be str, not bytes'
    …
    E   remains unmatched: 'E           TypeError: write() argument must be str, not bytes'
2019-10-28 14:37:26 +01:00
Ran Benita
7f8bf4d9f6 Merge pull request #6066 from bluetech/type-annotations-paths
Add type annotations to _pytest.pathlib and _pytest.tmpdir
2019-10-28 15:20:45 +02:00
Daniel Hahler
a4554e666a tests: speed up test_faulthandler.test_timeout 2019-10-28 13:44:06 +01:00
Daniel Hahler
d6e324a5e6 tests: conftest: handle tests using runpytest_subprocess as "slowest" 2019-10-28 13:43:42 +01:00
Daniel Hahler
023dde89e1 ci: Travis: include pexpect in main py37 job
This removes xdist there (not compatible with the pexpect tests), but it
is better to have one job less, although slower due to not using xdist.
2019-10-28 13:39:43 +01:00
Daniel Hahler
a5bd19e3b4 tests: lazily import doctest in approx tests 2019-10-28 12:55:16 +01:00
Daniel Hahler
32412532ef tests: mock doctest.DocTestRunner to not use real pdb
It is not used there anyway, and might cause false positives.
2019-10-28 12:55:16 +01:00
Daniel Hahler
ec27363748 Merge pull request #6025 from blueyed/pytester-typing
pytester: typing
2019-10-28 05:31:31 +01:00
Daniel Hahler
716f532a38 Merge pull request #6070 from blueyed/pexpect-freebsd
pytester: spawn: do not skip FreeBSD
2019-10-28 05:13:31 +01:00
Daniel Hahler
820b747e7a tests: merge/remove test_dontreadfrominput_buffer_python3 2019-10-27 19:32:06 +01:00
MarcoGorelli
d863c30c74 Fix plurality mismatch for and in pytest terminal summary 2019-10-27 15:16:24 +00:00
Daniel Hahler
886a3ad609 pytester: typing 2019-10-27 12:32:14 +01:00
Daniel Hahler
cbc39dd86e Merge pull request #6079 from blueyed/tryfirst
mark: move pytest_cmdline_main.tryfist into decorator
2019-10-27 07:21:31 +01:00
Daniel Hahler
a4faac6c94 mark: move pytest_cmdline_main.tryfist into decorator
Avoids comments for ignored typing.
2019-10-27 06:01:18 +01:00
Daniel Hahler
81c3bc76bc tests: harden test_disabled_capture_fixture 2019-10-27 03:02:24 +01:00
Daniel Hahler
a92f49afa7 Merge pull request #6073 from blueyed/merge-master-into-features
Merge master into features
2019-10-27 02:57:00 +01:00
Daniel Hahler
7f1af84f47 Merge master into features
Conflicts:
	src/_pytest/logging.py
2019-10-27 02:06:36 +01:00
Daniel Hahler
8e8a8fa4b9 pytester: spawn: do not skip FreeBSD
Fixes https://github.com/pytest-dev/pytest/issues/6069
2019-10-27 00:44:25 +02:00
Daniel Hahler
be514178d0 tox: remove platform restriction, only used for pexpect
This would prevent TOXENV=py37-pexpect-… from running on e.g. FreeBSD.
And even on Windows it is pytest's job of skipping the tests then.  This
was probably still from when the pexpect env was only running
pexpect-based tests.
2019-10-27 00:23:25 +02:00
Ran Benita
00a278cdb4 Add type annotations to _pytest.tmpdir
At least the ones I was able to.
2019-10-26 20:30:44 +03:00
Ran Benita
59a59f371b Add type annotations to _pytest.pathlib
At least the ones I was sure of.
2019-10-26 20:08:36 +03:00
Bruno Oliveira
928587da60 Change 5924 and 5936 changelog entries to improvement [ci skip] 2019-10-26 13:02:47 -03:00
Bruno Oliveira
cefe6bfec3 Replace a few outdated references to py.test with pytest (#6063)
Replace a few outdated references to py.test with pytest
2019-10-26 12:59:22 -03:00
Bruno Oliveira
ac633b8969 Replace py.io.TextIO with io.StringIO (#6064)
Replace py.io.TextIO with io.StringIO
2019-10-26 12:58:52 -03:00
Bruno Oliveira
1ad4ca6ac1 Support sys.pycache_prefix on py38 (#5864)
Support sys.pycache_prefix on py38
2019-10-26 11:29:09 -03:00
Bruno Oliveira
6f20b4b014 Introduce compat.fspath 2019-10-26 10:37:44 -03:00
Ran Benita
0b8c35516f Replace py.io.TextIO with io.StringIO
In Python3, py.io.TextIO is just an alias to io.StringIO. Remove the
indirection.
2019-10-26 16:33:57 +03:00
Ran Benita
96de232791 Replace a few outdated references to py.test with pytest 2019-10-26 16:28:17 +03:00
Bruno Oliveira
f93f284356 Support sys.pycache_prefix on py38
Fix #4730
2019-10-26 10:17:21 -03:00
Kale Kundert
b9df9a4761 Merge pull request #6058 from AnjoMan/6057-tolerance-on-complex-approx
6057 tolerance on complex approx
2019-10-25 15:31:47 -04:00
Bruno Oliveira
39066d5a42 Merge master into features (#6056)
Merge master into features
2019-10-25 16:27:14 -03:00
Anton Lodder
3c7fbe2d8b Document evaluating complex number for infinity 2019-10-25 12:03:03 -04:00
Michael Krebs
1f5b454355 Add log-auto-indent option to control multiline formatting 2019-10-25 11:31:33 -04:00
Daniel Hahler
82753bec50 terminal: report collection errors as "ERROR" in short summary 2019-10-25 07:03:32 +02:00
AnjoMan
34a02121ad Drop python 2 unicode tests for approx repr 2019-10-24 23:44:13 -04:00
AnjoMan
ed9fda84d3 Add tolerance to complex numbers 2019-10-24 23:44:13 -04:00
Bruno Oliveira
2fc1f7b8dc Put the 4.6 changelogs together (#5969)
Put the 4.6 changelogs together
2019-10-24 21:21:59 -03:00
Bruno Oliveira
fb0e8b99d1 Merge remote-tracking branch 'upstream/master' into mm
Conflicts:
- 	src/_pytest/cacheprovider.py
2019-10-24 21:13:43 -03:00
Bruno Oliveira
dae238c9b1 Release version 5.2.2 (#6055)
Release version 5.2.2
2019-10-24 21:09:15 -03:00
Bruno Oliveira
b27ba97721 Preparing release version 5.2.2 2019-10-24 19:24:04 -04:00
Bruno Oliveira
d1bc2601e4 pytester: align prefixes (#6026)
pytester: align prefixes
2019-10-24 18:50:29 -03:00
Bruno Oliveira
2b56c7e1ce Update Tidelift docs with latest campaign (#6053)
Update Tidelift docs with latest campaign
2019-10-24 18:46:21 -03:00
Bruno Oliveira
73a77c90ca Change #5061 changelog to 'improvement' (#6051)
Change #5061 changelog to 'improvement'
2019-10-24 18:44:48 -03:00
Daniel Hahler
8ef4287bf0 pytester: align prefixes
This is important for using another match_nickname, e.g. "re.match".

TODO:

- [ ] changelog
- [ ] test
2019-10-24 23:20:12 +02:00
Daniel Hahler
45fc0d9cd8 Merge pull request #6040 from blueyed/test_meta-slow
tests: mark test_meta as slow
2019-10-24 22:53:04 +02:00
Bruno Oliveira
2bee7d7c3e Update Tidelift docs with latest campaign
Tidelift has launched a new marketing campaign as outlined here:

* https://forum.tidelift.com/t/task-enhancement-marketing-the-tidelift-subscription-to-your-users/321

This PR splits the previous "sponsor" information into two, Open Collective
and Tidelift (as they have very different target audiences).

Also took the opportunity to reorder some items at the end of
the contents page in a manner that I believe make more sense.
2019-10-24 14:58:58 -03:00
Bruno Oliveira
92418b8d5d Change #5061 changelog to 'improvement' 2019-10-23 21:27:07 -03:00
Bruno Oliveira
432e5550e5 assertrepr_compare: use safeformat with -vv (#5936)
assertrepr_compare: use safeformat with -vv
2019-10-23 19:51:40 -03:00
Bruno Oliveira
713b9e54c3 Review rm_rf handling of FileNotFoundErrors (#6044)
Review rm_rf handling of FileNotFoundErrors
2019-10-23 19:22:26 -03:00
Daniel Hahler
8316d4392a Merge pull request #6028 from blueyed/DontReadFromInput-msg
capture: improve message with DontReadFromInput's IOError
2019-10-23 22:48:15 +02:00
Ran Benita
7a2d2d8f07 Merge pull request #5847 from bluetech/type-annotations-4
2/X Fix check_untyped_defs = True mypy errors
2019-10-23 22:52:23 +03:00
Daniel Hahler
6242777818 Merge pull request #6041 from blueyed/pytester-splitlines
pytester: runpytest_inprocess: use splitlines()
2019-10-23 21:42:21 +02:00
Bruno Oliveira
20ee883b5f Show the mnemonic of pytest.ExitCode in RunResult's repr (#6043)
Show the mnemonic of pytest.ExitCode in RunResult's repr
2019-10-23 10:46:58 -03:00
Bruno Oliveira
7beb520555 Show the mnemonic of pytest.ExitCode in RunResult's repr
Fix #4901
2019-10-23 09:16:02 -03:00
Ran Benita
1cc1ac5183 Remove some type: ignores fixed in typeshed 2019-10-23 14:47:56 +03:00
Bruno Oliveira
ba4b8c869c Review rm_rf handling of FileNotFoundErrors 2019-10-23 08:30:52 -03:00
Ran Benita
1787bffda0 Fix check_untyped_defs errors in capture 2019-10-23 14:20:15 +03:00
Ran Benita
0267b25c66 Fix some check_untyped_defs mypy errors in terminal 2019-10-23 14:20:15 +03:00
Ran Benita
5dca7a2f4f Fix check_untyped_defs errors in cacheprovider 2019-10-23 14:20:15 +03:00
Ran Benita
93c8822f26 Fix check_untyped_defs errors in warnings 2019-10-23 14:20:15 +03:00
Ran Benita
583c2a2f9b Fix check_untyped_defs errors in logging 2019-10-23 14:20:15 +03:00
Ran Benita
1984c10427 Fix check_untyped_defs errors in doctest
In order to make the LiteralOutputChecker lazy initialization more
amenable to type checking, I changed it to match the scheme already used
in this file to lazy-initialize PytestDoctestRunner.
2019-10-23 14:20:14 +03:00
Ran Benita
3246d8a6e9 Merge pull request #6048 from bluetech/mypy-0.740
Update mypy 0.720 -> 0.740
2019-10-23 12:47:21 +03:00
Daniel Hahler
b30e7bd1de Merge pull request #6045 from blueyed/minor
minor: typing for ReprFailDoctest
2019-10-23 11:44:37 +02:00
Daniel Hahler
db9e248b2e Merge pull request #6039 from blueyed/test_doctest_id
doctest: unset RUNNER_CLASS in pytest_unconfigure
2019-10-23 11:38:59 +02:00
Daniel Hahler
1371b01f78 typing for ReprFailDoctest 2019-10-23 10:52:42 +02:00
Ran Benita
52b85f6f1a Update mypy 0.720 -> 0.740
Changelogs:
http://mypy-lang.blogspot.com/2019/09/mypy-730-released.html
http://mypy-lang.blogspot.com/2019/10/mypy-0740-released.html

New errors:
src/_pytest/recwarn.py:77: error: Missing return statement
src/_pytest/recwarn.py:185: error: "bool" is invalid as return type for "__exit__" that always returns False
src/_pytest/recwarn.py:185: note: Use "typing_extensions.Literal[False]" as the return type or change it to "None"
src/_pytest/recwarn.py:185: note: If return type of "__exit__" implies that it may return True, the context manager may swallow exceptions
src/_pytest/recwarn.py:185: error: Return type "bool" of "__exit__" incompatible with return type "None" in supertype "catch_warnings"
src/_pytest/recwarn.py:230: error: "bool" is invalid as return type for "__exit__" that always returns False
src/_pytest/recwarn.py:230: note: Use "typing_extensions.Literal[False]" as the return type or change it to "None"
src/_pytest/recwarn.py:230: note: If return type of "__exit__" implies that it may return True, the context manager may swallow exceptions
src/_pytest/recwarn.py:230: error: Return type "bool" of "__exit__" incompatible with return type "None" in supertype "catch_warnings"

The errors are due to this new error:
https://mypy.readthedocs.io/en/latest/error_code_list.html#check-the-return-type-of-exit-exit-return
2019-10-23 10:34:14 +03:00
Daniel Hahler
c71a2c9f80 Merge pull request #6023 from blueyed/main-exitcode
pytest.main: return ExitCode
2019-10-23 07:48:17 +02:00
Daniel Hahler
2f589a9769 pytester: runpytest_inprocess: use splitlines()
This avoids having a trailing empty lines always.
2019-10-23 04:34:29 +02:00
Daniel Hahler
046aa0b6e9 pytest.main: return ExitCode 2019-10-23 04:33:05 +02:00
Daniel Hahler
f0c2b070c5 Merge pull request #6046 from blueyed/fix-features
Fix test_doctest_set_trace_quit on features
2019-10-23 04:32:10 +02:00
Daniel Hahler
b079dc2dbe Fix test_doctest_set_trace_quit on features 2019-10-23 04:13:37 +02:00
Daniel Hahler
4af89bba9d Merge pull request #5061 from blueyed/summary_stats-multi-color
Multiple colors with terminal summary_stats
2019-10-23 03:21:39 +02:00
Daniel Hahler
5e7b2ae704 doctest: pytest_unconfigure: reset RUNNER_CLASS
This is important when used with ``pytester``'s ``runpytest_inprocess``.

Since 07f20ccab `pytest testing/acceptance_test.py -k test_doctest_id`
would fail, since the second run would not consider the exception to be
an instance of `doctest.DocTestFailure` anymore, since the module was
re-imported, and use another failure message then in the short test
summary info (and in the report itself):

> FAILED test_doctest_id.txt::test_doctest_id.txt - doctest.DocTestFailure: <Do...

while it should be:

> FAILED test_doctest_id.txt::test_doctest_id.txt
2019-10-23 03:09:41 +02:00
Daniel Hahler
bae22e1fdd Merge pull request #6016 from blueyed/pytest-_ensure_unconfigure-twice
pytester: parseconfigure: remove duplicate config._ensure_unconfigure
2019-10-23 01:39:23 +02:00
Bruno Oliveira
215be88fed Minor: help / direct usage of config (#6011)
Minor: help / direct usage of config
2019-10-22 20:24:08 -03:00
Daniel Hahler
98fc9377d9 Merge pull request #5630 from blueyed/pdb-doctest-bdbquit
doctest: handle BdbQuit
2019-10-23 01:10:19 +02:00
Daniel Hahler
8683293031 Merge pull request #6017 from blueyed/DontReadFromInput-remove-comment
Remove (rejected) comment from DontReadFromInput
2019-10-23 01:07:12 +02:00
Daniel Hahler
bdadf12af1 Merge pull request #6030 from blueyed/tox-vv
ci: use tox -vv
2019-10-23 01:05:21 +02:00
Daniel Hahler
82e9013e73 Merge pull request #6004 from blueyed/fix-nf
cache: NFPlugin: keep known nodeids
2019-10-23 01:04:48 +02:00
Bruno Oliveira
a51bb3eedb Add CHANGELOG for #5630 2019-10-22 19:43:42 -03:00
Bruno Oliveira
f4734213e5 Merge remote-tracking branch 'upstream/features' into blueyed/pdb-doctest-bdbquit 2019-10-22 19:43:35 -03:00
Daniel Hahler
0dd68ba0b6 tests: mark test_meta as slow
This moves it to the end of tests during collection.  Takes ~7s for me.
2019-10-22 23:44:52 +02:00
Daniel Hahler
851fc0280f ci: Travis: configure/restrict branch builds [ci skip] 2019-10-22 23:03:31 +02:00
Daniel Hahler
56cec5fa79 ci: use tox -vv
This will display durations, and is useful in logs in general.
2019-10-22 05:46:52 +02:00
Daniel Hahler
3c14dd7f55 capture: improve message with DontReadFromInput's IOError
Ref: https://github.com/pytest-dev/pytest/pull/4996#issuecomment-479686487
2019-10-22 02:03:18 +02:00
Daniel Hahler
978c7ae1b7 Merge pull request #6015 from blueyed/merge-master-into-features
Merge master into features
2019-10-21 21:59:09 +02:00
Daniel Hahler
554dba391c Multiple colors with terminal summary_stats
Ref: https://github.com/pytest-dev/pytest/issues/5060
2019-10-21 04:35:45 +02:00
Daniel Hahler
995990c61b Remove (rejected) comment from DontReadFromInput
Ref: https://github.com/pytest-dev/pytest/pull/4996#issuecomment-479686487
2019-10-21 02:26:29 +02:00
Daniel Hahler
b47f57a08a pytester: parseconfigure: remove duplicate config._ensure_unconfigure
This gets done in `parseconfig` already.
2019-10-21 01:15:27 +02:00
Daniil Galiev
5cefcb2052 refactor disabling markers 2019-10-21 00:11:24 +02:00
Daniil Galiev
b94eb4cb7b disable _ALLOW_MARKERS in module __init__.py 2019-10-21 00:11:24 +02:00
Daniil Galiev
9275012ef7 fix bug with nonskipped first test in package 2019-10-21 00:11:24 +02:00
Daniel Hahler
803cc1f294 Merge master into features 2019-10-21 00:08:03 +02:00
Bruno Oliveira
14142b9113 tests: remove unnecessary test, clarify (#6013)
tests: remove unnecessary test, clarify
2019-10-20 18:36:27 -03:00
Daniel Hahler
46fbf22524 ci: Travis: cover verbose=1 2019-10-20 20:51:03 +02:00
Daniel Hahler
d91ff0af8a assertrepr_compare: use safeformat with -vv 2019-10-20 20:51:03 +02:00
Daniel Hahler
0123b29ed7 tests: remove unnecessary test, clarify
Follow-up to https://github.com/pytest-dev/pytest/pull/6009.
2019-10-20 20:40:13 +02:00
Bruno Oliveira
16efa1bfef Merge pull request #6009 from yoavcaspi/fix_keyboardInterrupt_on_setup_show
setuponly: remove printing out/err from capman
2019-10-20 14:00:06 -03:00
Daniel Hahler
a6152db84a setuponly: pytest_fixture_setup: use option directly 2019-10-20 17:57:25 +02:00
Daniel Hahler
83351a3368 doc: improve help for filterwarnings 2019-10-20 17:56:59 +02:00
Daniel Hahler
fbb7f663be Merge pull request #6005 from blueyed/harden-trial
tests: harden/fix test_trial_error
2019-10-20 17:35:15 +02:00
Yoav Caspi
5624e366c1 add more indications to the result of the tests 2019-10-20 09:54:23 +03:00
Anthony Sottile
b88f5df4ce Merge pull request #6010 from pytest-dev/asottile-patch-2
Ensure .pytest_cache file has a newline at the end
2019-10-19 16:21:21 -07:00
Anthony Sottile
3a402811de Ensure .pytest_cache file has a newline at the end 2019-10-19 14:42:06 -07:00
Bruno Oliveira
b5579d2cf2 Make InvocationParams.args a tuple (#6008)
Make InvocationParams.args a tuple
2019-10-19 17:36:42 -03:00
Daniel Hahler
9b673bcc44 Improve/revisit CallInfo.__repr__ (#6007) 2019-10-19 21:45:27 +02:00
Bruno Oliveira
d12cdd3127 Make InvocationParams.args a tuple
This avoids mutating the original list to reflect on InvocationParams,
which is supposed to be an immutable snapshot of the state of pytest.main()
at the moment of invocation (see pytest-dev/pytest-xdist#478).
2019-10-19 16:42:58 -03:00
Yoav Caspi
e05b33ed16 setuponly: remove printing out/err from capman 2019-10-19 22:33:19 +03:00
Daniel Hahler
119bf66d7a ExceptionInfo.from_current: pass through exprinfo (#6002) 2019-10-19 19:20:55 +02:00
Daniel Hahler
15f9568694 Improve/revisit CallInfo.__repr__ 2019-10-19 19:07:11 +02:00
Florian Bruhin
2e11ea6108 Merge pull request #6006 from atugushev/update-doc-contextlib2
Update doc to use contextlib2.nullcontext
2019-10-19 14:29:16 +02:00
Albert Tugushev
18786992bb Update doc to use contextlib2.nullcontext
nullcontext has been backported in contextlib2==0.6.0
2019-10-19 15:09:37 +07:00
Daniel Hahler
5b88612e5b tests: harden/fix test_trial_error 2019-10-19 09:03:26 +02:00
Daniel Hahler
3173a26388 cache: NFPlugin: keep known nodeids
Caveat: does not forget about old nodeids

Fixes https://github.com/pytest-dev/pytest/issues/5206
2019-10-19 04:53:01 +02:00
Daniel Hahler
cd753aa4ab ExceptionInfo.from_current: pass through exprinfo
This was lost in 11f1f79222.
2019-10-19 03:02:47 +02:00
Daniel Hahler
5e7b8d813b Remove unreachable code from config._prepareconfig (#6001) 2019-10-18 23:47:09 +02:00
Daniel Hahler
94c4dd6ad7 help: display default verbosity (#5998) 2019-10-18 23:34:38 +02:00
Daniel Hahler
de7c1aa0b7 Merge pull request #5999 from blueyed/passenv-TERM
tox: pass $TERM
2019-10-18 23:34:03 +02:00
Philipp Loose
afac1f0021 Remove unreachable code from config._prepareconfig
The code that could trigger the execution of the removed lines was
removed with a7e4016.
2019-10-18 23:11:41 +02:00
Daniel Hahler
ab245ccdc3 help: display default verbosity 2019-10-18 22:11:54 +02:00
Daniel Hahler
9da73541b7 tox: pass TERM
Ref: https://github.com/tox-dev/tox/issues/1441
2019-10-18 22:11:00 +02:00
Daniel Hahler
c9524af5ae Merge master into features (#5995) 2019-10-18 21:23:28 +02:00
Daniel Hahler
0976e2f50d Merge master into features 2019-10-18 17:51:42 +02:00
Daniel Hahler
0783030357 Merge pull request #5992 from blueyed/tests-PDBPP_HIJACK_PDB
tests: debugging: disable pdb++ within inner tests
2019-10-18 17:50:03 +02:00
Daniel Hahler
d910175b9f Merge pull request #5993 from blueyed/filterwarnings-error-removing
tests: filterwarnings: do not crash with "(rm_rf)" warning
2019-10-18 17:38:40 +02:00
Daniel Hahler
6b2bae9392 tests: filterwarnings: do not crash with "(rm_rf)" warning
Ref: https://github.com/pytest-dev/pytest/issues/5974
2019-10-18 17:22:56 +02:00
Daniel Hahler
7ef44913a1 tests: debugging: disable pdb++ within inner tests
Ref: https://github.com/pytest-dev/pytest/pull/5306#issuecomment-495690643
2019-10-18 17:08:39 +02:00
Daniel Hahler
813ef9e88f Merge pull request #5983 from aklajnert/fix_5.2.0_announce
Remove redundant mention from 5.2.0 release notes.
2019-10-17 15:33:53 +02:00
Andrzej Klajnert
f2dd9cc63e Remove redundant mention from 5.2.0 release notes. 2019-10-17 13:17:34 +02:00
Daniel Hahler
ce8b1dfa04 ci: Travis: move py37-pexpect to another job (#5979) 2019-10-17 07:54:04 +02:00
Daniel Hahler
a73d0151a6 ci: Travis: move py37-pexpect to another job
It does not have to run all tests again by itself.
2019-10-17 02:19:46 +02:00
Bruno Oliveira
3cb1457e6d Merge pull request #5977 from blueyed/numpy
tests: keep numpy being optional
2019-10-16 20:13:29 -03:00
Daniel Hahler
90dfee5da5 tests: keep numpy being optional
Ref: https://github.com/pytest-dev/pytest/pull/5950#discussion_r335254774
2019-10-16 23:50:23 +02:00
Bruno Oliveira
77a995ffad Add missing version added/changed markers to docs (#5966)
Add missing version added/changed markers to docs
2019-10-16 10:43:45 -03:00
Anthony Sottile
810db2726d Put the 4.6 changelogs together 2019-10-15 18:57:59 -07:00
Bruno Oliveira
914a9465ab tests: move tests for setuponly/setupplan (#5961)
tests: move tests for setuponly/setupplan
2019-10-15 19:49:26 -03:00
Bruno Oliveira
f739d511b0 minor: test_failure_function: use vars (#5958)
minor: test_failure_function: use vars
2019-10-15 19:48:41 -03:00
Bruno Oliveira
0383d43645 Add missing version added/changed markers to docs
Notice some features since 5.0 were not being properly
marked in which version they have been added/changed.
2019-10-15 19:45:58 -03:00
Bruno Oliveira
76c2a8ebbe doc: caplog: add caplog.messages (#5963)
doc: caplog: add caplog.messages
2019-10-15 19:42:15 -03:00
Daniel Hahler
71a7fd02a5 doc: caplog: add caplog.messages 2019-10-15 16:18:50 +02:00
Daniel Hahler
c2ae0e0dc6 tests: move tests for setuponly/setupplan
Forgotten in 032ce8baf.
2019-10-15 00:41:05 +02:00
Anthony Sottile
7bc8cb8e2b Merge pull request #5954 from blueyed/changelog-remove-dupe
changelog: #5523 was fixed in 5.0.1 already
2019-10-14 15:37:21 -07:00
Daniel Hahler
361f0e6ba7 minor: test_failure_function: use vars 2019-10-14 22:45:25 +02:00
Daniel Hahler
dee8d94876 changelog: #5523 was fixed in 5.0.1 already
Ref: https://github.com/pytest-dev/pytest/pull/5952#issuecomment-541801883
2019-10-14 19:20:57 +02:00
Anthony Sottile
a20880cca2 Merge pull request #5952 from nicoddemus/port-changelog
Port CHANGELOG from 4.6.6 release
2019-10-13 10:20:34 -07:00
Bruno Oliveira
ae9465215e Port CHANGELOG from 4.6.6 release 2019-10-13 11:54:02 -03:00
Bruno Oliveira
1555973487 Workaround curl bug which makes retries of fetching codecov.io/… (#5951)
Workaround curl bug which makes retries of fetching codecov.io/bash not work
2019-10-12 17:08:47 -03:00
Bruno Oliveira
3322c1e033 Casting fixture parameter to list at the beginning of parameter… (#5950)
Casting fixture parameter to list at the beginning of parameter parsing.
2019-10-12 17:02:48 -03:00
Ran Benita
7678f891f9 Workaround curl bug which makes retries of fetching codecov.io/bash not work 2019-10-12 21:52:01 +03:00
Florian Bruhin
4f2abd7ae0 Merge pull request #5948 from attomos/update-projects-doc
Documentation update to project examples
2019-10-12 17:31:24 +02:00
Victor Maryama
122cf60b27 Always creating list for consistency.
Co-Authored-By: Bruno Oliveira <nicoddemus@gmail.com>
2019-10-12 15:46:28 +02:00
Victor Maryama
63e3d89647 Fixed linting. 2019-10-12 15:08:47 +02:00
Victor Maryama
122748a6cf Added changelog file. 2019-10-12 14:38:58 +02:00
Victor Maryama
1f639e2c22 Casting fixture parameter to list at the beginning of parameter parsing. 2019-10-12 14:33:43 +02:00
Nattaphoom Chaipreecha
83ba5eb58a Add pudb to project examples 2019-10-12 08:10:04 +07:00
Nattaphoom Chaipreecha
d07c5ba4ae Update pdb++ link (moved to GitHub) 2019-10-12 08:09:49 +07:00
Anthony Sottile
b162ab6a45 Merge pull request #5943 from nicoddemus/py2-py37-tech-docs
Add link to technical aspects issue to the py27-py34 docs
2019-10-11 11:07:58 -07:00
Bruno Oliveira
57141dc708 Add link to technical aspects issue to the py27-py34 docs 2019-10-11 08:50:38 -03:00
Bruno Oliveira
afabbb6346 minor: test_assertion: improve mock_config (#5940)
minor: test_assertion: improve mock_config
2019-10-11 08:28:42 -03:00
Daniel Hahler
fb90259460 test_assertion: improve mock_config 2019-10-11 04:19:07 +02:00
Bruno Oliveira
bad4ffc3a7 remove cancelled training sidebar (#5938)
remove cancelled training sidebar
2019-10-10 07:45:26 -03:00
Oliver Bestwalter
71ad5b0fbb remove cancelled training sidebar 2019-10-10 12:11:16 +02:00
Bruno Oliveira
db6653ce3b Improve ExceptionInfo.__repr__ (#5934)
Improve ExceptionInfo.__repr__
2019-10-09 17:08:55 -03:00
Daniel Hahler
2a2fe7d3db Improve ExceptionInfo.__repr__ 2019-10-09 19:27:46 +02:00
Bruno Oliveira
5c92a0f695 Merge master into features (#5930)
Merge master into features
2019-10-08 17:39:49 -03:00
Daniel Hahler
21c038f304 Merge master into features 2019-10-08 18:57:15 +02:00
Daniel Hahler
fca462cf7d Merge pull request #5924 from blueyed/improve-list-diff
Improve full diff output for lists
2019-10-08 17:58:25 +02:00
Daniel Hahler
946434c610 Improve full diff output for lists
Massage text input for difflib when comparing pformat output of
different line lengths.

Also do not strip ndiff output on the left, which currently already
removes indenting for lines with no differences.

Before:

    E   AssertionError: assert ['version', '...version_info'] == ['version', '...version', ...]
    E     Right contains 3 more items, first extra item: ' '
    E     Full diff:
    E     - ['version', 'version_info', 'sys.version', 'sys.version_info']
    E     + ['version',
    E     +  'version_info',
    E     +  'sys.version',
    E     +  'sys.version_info',
    E     +  ' ',
    E     +  'sys.version',
    E     +  'sys.version_info']

After:

    E   AssertionError: assert ['version', '...version_info'] == ['version', '...version', ...]
    E     Right contains 3 more items, first extra item: ' '
    E     Full diff:
    E       [
    E        'version',
    E        'version_info',
    E        'sys.version',
    E        'sys.version_info',
    E     +  ' ',
    E     +  'sys.version',
    E     +  'sys.version_info',
    E       ]
2019-10-08 15:27:51 +02:00
Anthony Sottile
3fada8c8ee Merge pull request #5925 from asottile/fix_resource_warning_again_5088
Fix spurious ResourceWarning stderr in testsuite again
2019-10-07 16:26:38 -07:00
Anthony Sottile
271dc7f17a Merge pull request #5904 from asottile/warnings_v2
Add test to ensure _pytest is warning-clean on import
2019-10-06 19:24:07 -07:00
Anthony Sottile
19eb0590f1 Fix spurious ResourceWarning stderr in testsuite again 2019-10-06 19:11:33 -07:00
Bruno Oliveira
5186635387 Introduce no_fnmatch_line/no_re_match_line in pytester (#5914)
Introduce no_fnmatch_line/no_re_match_line in pytester
2019-10-06 20:27:20 -03:00
Daniel Hahler
cd398e289f Merge pull request #5920 from blueyed/lazy-import-pdb
Allow for "pdb" module to be rewritten
2019-10-06 23:46:11 +02:00
Anthony Sottile
eaa05531ed Add test to ensure _pytest is warning-clean on import 2019-10-06 14:11:08 -07:00
Bruno Oliveira
47c2091ecd Use new no-match functions to replace previous idiom 2019-10-06 18:05:24 -03:00
Bruno Oliveira
0c18e24433 Introduce no_fnmatch_line/no_re_match_line in pytester
The current idiom is to use:

  assert re.match(pat, result.stdout.str())

Or

  assert line in result.stdout.str()

But this does not really give good results when it fails.

Those new functions produce similar output to ther other match lines functions.
2019-10-06 18:05:24 -03:00
Daniel Hahler
07f20ccab6 Allow for "pdb" module to be rewritten 2019-10-06 23:02:24 +02:00
Daniel Hahler
b847d5712b Merge pull request #5923 from nicoddemus/mm
Merge master into features
2019-10-06 23:01:19 +02:00
Bruno Oliveira
74aed6ea4c Release 5.2.1 (#5917)
Release 5.2.1
2019-10-06 12:55:15 -03:00
Bruno Oliveira
cfa9ebc91f ci: test oldest supported attrs (#5922)
ci: test oldest supported attrs
2019-10-06 12:55:03 -03:00
Daniel Hahler
b0fd8742da ci: test oldest supported attrs 2019-10-06 16:12:56 +02:00
Bruno Oliveira
12cc729f6b Preparing release version 5.2.1 2019-10-06 08:00:49 -04:00
Bruno Oliveira
1c5efffd90 Add changelog entry for #5902 2019-10-06 07:58:23 -04:00
Bruno Oliveira
8c9ea5e055 Fix warnings with attrs 19.2 and fix object assertions (#5902)
Fix warnings with attrs 19.2 and fix object assertions
2019-10-06 08:47:17 -03:00
Anthony Sottile
c58b0fb4ac Use ATTRS_EQ_FIELD for attrs 19.2 compat 2019-10-05 18:16:35 -07:00
Anthony Sottile
4011af68cd Merge pull request #5910 from pytest-dev/asottile-patch-1
Fix dynamic scoping changelog link
2019-10-03 09:39:12 -07:00
Anthony Sottile
9637b3e376 Fix dynamic scoping changelog link 2019-10-03 09:01:08 -07:00
Anthony Sottile
33c3ec66b7 Merge pull request #5898 from kevinjfoley/doc-typo-fix
Fix doc typo
2019-10-01 12:58:54 -07:00
Hynek Schlawack
a79acf279a Fix warnings with attrs 19.2 and fix object assertions
attrs 19.2 deprecated cmp in favor of the dataclass-ish eq/order duo.

This causes deprecation warnings that in turn break some of the cool new deep
object comparisons. Since we at attrs expected this to be a problem, it shipped
with helpers to write backward and forward compatible code.

This PR uses that and avoids changed to minimal versions.
2019-10-01 20:47:59 +02:00
Bruno Oliveira
9a4c0b991b Update doc: pytest section in setup.cfg (#5894)
Update doc: pytest section in setup.cfg
2019-10-01 11:45:01 -03:00
Kevin J. Foley
b490f5f979 Fix doc typo 2019-10-01 10:17:26 -04:00
tadashigaki
acfd0fd9d6 Update doc: pytest section in setup.cfg 2019-10-01 02:44:07 +09:00
Bruno Oliveira
88434f1f42 Release 5.2.0 (#5885)
Release 5.2.0
2019-09-29 10:26:06 -03:00
Florian Bruhin
4d01740be3 Merge pull request #5887 from asottile/fix_attributes_docs_pytester
Fix attribute docs in _pytest.pytester
2019-09-29 14:22:54 +02:00
Anthony Sottile
07792c7113 Fix attribute docs in _pytest.pytester 2019-09-28 18:19:53 -07:00
Bruno Oliveira
068ef90b92 Preparing release version 5.2.0 2019-09-28 21:18:37 -04:00
Bruno Oliveira
065773aa97 Use 'python3' instead of 'python3.6' on tox
This allows us to use python3.7+ to use tox
2019-09-28 21:16:20 -04:00
Anthony Sottile
b62276826c Merge pull request #5886 from nicoddemus/setup-plan-custom-items-5884
Fix --setup-only and --setup-show for custom pytest items
2019-09-28 18:08:36 -07:00
Bruno Oliveira
7bdfba3578 Fix --setup-only and --setup-show for custom pytest items
Fix #5884
2019-09-28 11:52:09 -03:00
Daniel Hahler
6bfd30d169 Merge pull request #5878 from blueyed/codecov-retry-6
ci: codecov: use 6 retries with curl
2019-09-24 10:30:23 +02:00
Daniel Hahler
7731e45615 ci: codecov: use 6 retries with curl
This should result in retries of 1+2+4+8+16+32 = 63s.

Ref: https://github.com/pytest-dev/pytest/pull/5869#issuecomment-534235437
2019-09-23 20:58:52 +02:00
Anthony Sottile
8806b1f531 Merge pull request #5875 from asottile/patch-1
Make sure to quote `sys.executable` as we're running a shell
2019-09-23 18:55:33 +02:00
Anthony Sottile
19c9e53604 Make sure to quote sys.executable as we're running a shell 2019-09-23 08:58:56 -07:00
Bruno Oliveira
c28b63135f Merge master into features (#5874)
Merge master into features
2019-09-23 12:44:20 -03:00
Bruno Oliveira
7c64d5d882 Do not call python directly but use sys.executable. Fixes #5872 (#5873)
Do not call python directly but use sys.executable. Fixes #5872
2019-09-23 12:28:00 -03:00
Bruno Oliveira
d3d9f9f668 Merge remote-tracking branch 'upstream/master' into mm 2019-09-23 12:09:01 -03:00
Bruno Oliveira
018edf2a0e Change report-coverage.sh in attempt to fix Azure (#5869)
Change report-coverage.sh in attempt to fix Azure
2019-09-23 12:01:19 -03:00
Tomáš Chvátal
04c01fb606 test_argcomplete do not call python directly #5872
Use sys.executable to detect which python we should actually be testing.
2019-09-23 16:38:15 +02:00
Daniel Hahler
ea0c7e43b6 Remove unneeded codecov options (implied with "-f") 2019-09-23 02:26:53 +02:00
Bruno Oliveira
de8fdab7a9 Change report-coverage.sh in attempt to fix Azure
Recently sometimes Azure has failed with:

++ curl -s https://codecov.io/bash
bash: /dev/fd/63: No such file or directory

This attempts to fix this by modifying report-coverage.sh slightly.
2019-09-21 16:02:48 -03:00
Daniel Hahler
c1361b48f8 Merge pull request #5868 from blueyed/mm
Merge master into features
2019-09-21 16:29:49 +02:00
Bruno Oliveira
1b4ad7774b Fix logging doc: change x.level to x.levelno (#5866)
Fix logging doc: change x.level to x.levelno
2019-09-21 11:28:40 -03:00
Daniel Hahler
409cc2946a Merge master into features 2019-09-21 16:22:48 +02:00
Bruno Oliveira
3114be9181 Revert "Show banner/full page for the Digital Climate Strike (#… (#5867)
Revert "Show banner/full page for the Digital Climate Strike (#5861)"
2019-09-21 11:09:34 -03:00
Bruno Oliveira
e4103cb02c Release version 5.1.3 (#5859)
Release version 5.1.3
2019-09-21 10:56:16 -03:00
Bruno Oliveira
217605c217 Revert "Show banner/full page for the Digital Climate Strike (#5861)"
This reverts commit c8cf748c49, reversing
changes made to 702acdba46.
2019-09-21 10:38:10 -03:00
James Cooke
2fcf21a6c7 Fix logging doc: change x.level to x.levelno 2019-09-20 18:38:47 +01:00
Bruno Oliveira
c8cf748c49 Show banner/full page for the Digital Climate Strike (#5861)
Show banner/full page for the Digital Climate Strike
2019-09-19 10:49:10 -03:00
Bruno Oliveira
249b53e623 Show banner/full page for the Ditigal Climate Strike
As discussed in the mailing list, pytest will join the
digital strike for the climate on Sep 20th. This will show
a closable banner on the docs until the date, and when the date
comes the banner will become a (closable) full page.

I will also pin an issue saying that the developers won't be
available on Sep 20th.
2019-09-19 08:26:25 -03:00
Bruno Oliveira
9669413b1f Merge pull request #5776 from aklajnert/1682-dynamic-scope
Implemented the dynamic scope feature.
2019-09-19 08:22:45 -03:00
Andrzej Klajnert
e2382e96ed Minor cleanup in tests. 2019-09-19 11:13:22 +02:00
Bruno Oliveira
1a9f4a51cb Preparing release version 5.1.3 2019-09-18 10:11:59 -03:00
Bruno Oliveira
892bdd59dc Normalize all summary durations, including quiet ones 2019-09-18 10:10:25 -03:00
Bruno Oliveira
df46afc96d Change fixture argument handling tests to unit-tests 2019-09-18 07:50:35 -03:00
Bruno Oliveira
6918d07560 Merge remote-tracking branch 'upstream/features' into aklajnert/1682-dynamic-scope 2019-09-18 07:44:18 -03:00
Daniel Hahler
c997c32004 Merge pull request #5856 from blueyed/mm
Merge master into features
2019-09-17 21:47:40 +02:00
Daniel Hahler
450409d123 Merge master into features
Conflicts:
	src/_pytest/reports.py
        (via 7259c453d, moved the type annotation; setting it to `None`
        was removed in 3c82b1cb9 already)
2019-09-17 12:46:36 +02:00
Daniel Hahler
702acdba46 Merge pull request #5811 from blueyed/fulltrace-pytest-raises
Handle --fulltrace with pytest.raises
2019-09-14 02:09:36 +02:00
Daniel Hahler
f832ac3316 Handle --fulltrace with pytest.raises
This changes `_repr_failure_py` to use `tbfilter=False` always.
2019-09-14 01:41:43 +02:00
Bruno Oliveira
9422e10322 Fix regression due to different cases on Windows (#5840)
Fix regression due to different cases on Windows
2019-09-13 18:11:12 -03:00
Bruno Oliveira
5c3b4a6f52 Add CHANGELOG entry for #5792 2019-09-12 08:05:50 -03:00
Christian Neumüller
05850d73bd Re-introduce Christian Neumüller to AUTHORS
The introduction was reverted by cd29d56
2019-09-12 08:05:50 -03:00
Bruno Oliveira
b48f51eb03 Use Path() objects to store conftest files
Using Path().resolve() is better than py.path.realpath because
it resolves to the correct path/drive in case-insensitive file systems (#5792):

>>> from py.path import local
>>> from pathlib import Path
>>>
>>> local('d:\\projects').realpath()
local('d:\\projects')
>>> Path('d:\\projects').resolve()
WindowsPath('D:/projects')

Fix #5819
2019-09-12 08:05:50 -03:00
Bruno Oliveira
cf5b544db3 Revert "Merge pull request #5792 from dynatrace-oss-contrib/bugfix/badcase"
This reverts commit 955e542210, reversing
changes made to 0215bcd84e.

Will attempt a simpler approach
2019-09-12 08:05:43 -03:00
Bruno Oliveira
73c5b7f4b1 Clarify docs by showing tox.ini considered before setup.cfg (#5839)
Clarify docs by showing tox.ini considered before setup.cfg
2019-09-11 22:14:31 -03:00
Anthony Sottile
8f2f51be6d Clarify docs by showing tox.ini considered before setup.cfg 2019-09-11 14:07:06 -07:00
Andrzej Klajnert
f2f3ced508 Fixed the fixture function signature. 2019-09-10 16:20:44 +02:00
Bruno Oliveira
23102a7d84 Update doc regarding pytest.raises (#5834)
Update doc regarding pytest.raises
2019-09-09 16:18:58 -03:00
Gene Wood
f0d538329c Update doc regarding pytest.raises
Remove reference to the `message` argument in the docs as it was deprecated in #4539
2019-09-09 12:14:09 -07:00
Bruno Oliveira
6c8bcf601c Fix pypy3.6 on windows (#5828)
Fix pypy3.6 on windows
2019-09-08 12:11:28 -03:00
Anthony Sottile
9d7b919c7d Fix pypy3.6 on windows 2019-09-07 16:49:05 -07:00
Bruno Oliveira
333e9d5c10 Merge pull request #5824 from blueyed/revert-py350
ci: Travis: do not test with 3.5.0
2019-09-06 08:05:51 -03:00
Daniel Hahler
f1b605c95e ci: Travis: do not test with 3.5.0
This causes flaky test failures (crashes).

Closes https://github.com/pytest-dev/pytest/issues/5795.
2019-09-06 12:29:17 +02:00
Bruno Oliveira
2bb8d93001 Fix for Python 4: replace unsafe PY3 with PY2 (#5820)
Fix for Python 4: replace unsafe PY3 with PY2
2019-09-05 12:39:06 -03:00
Hugo
d049b35397 Fix for Python 4: replace unsafe PY3 with PY2 2019-09-05 18:06:47 +03:00
Bruno Oliveira
8ee557f7ae Fix pythonpath anchor (#5817)
Fix pythonpath anchor
2019-09-04 22:19:57 -03:00
Gene Wood
ca3884d9bb Add Gene Wood to authors 2019-09-04 09:21:10 -07:00
Gene Wood
bc163605ab Fix anchor link from Good Practices to Pythonpath doc 2019-09-04 09:18:10 -07:00
Bruno Oliveira
1675048b35 Merge pull request #5808 from goerz/pastebin
Fix "lexer" being used when uploading to bpaste.net
2019-08-31 16:11:39 -03:00
aklajnert
10bf6aac76 Implemented the dynamic scope feature. 2019-08-31 18:12:24 +02:00
Michael Goerz
f8dd6349c1 Fix "lexer" being used when uploading to bpaste.net
Closes #5806.
2019-08-30 15:34:03 -04:00
Bruno Oliveira
8c8809e1aa Merge pull request #5805 from nicoddemus/release-5.1.2
Release 5.1.2
2019-08-30 16:05:14 -03:00
Bruno Oliveira
404cf0c872 Merge pull request #5764 from goerz/pastebin
Gracefully handle HTTP errors from pastebin
2019-08-30 07:29:14 -03:00
Michael Goerz
d47b9d04d4 Gracefully handle HTTP errors from pastebin
We find that the --pastebin option to pytest sometimes fails with "HTTP
Error 400: Bad Request". We're still investigating the exact cause of
these errors, but in the meantime, a failure to upload to the pastebin
service should probably not crash pytest and cause a test failure in the
continuous-integration.

This patch catches exceptions like HTTPError that may be thrown while
trying to communicate with the pastebin service, and reports them as a
"bad response", without crashing with a backtrace or failing the entire
test suite.
2019-08-26 23:50:46 -04:00
Anthony Sottile
5bf9f9a711 Merge pull request #5788 from nicoddemus/mm
Merge master into features
2019-08-26 18:20:07 -07:00
Bruno Oliveira
c28e428249 Merge remote-tracking branch 'upstream/master' into mm 2019-08-26 20:00:30 -03:00
Ran Benita
c2f762460f Merge pull request #5673 from bluetech/type-annotations-3
1/X Fix check_untyped_defs = True mypy errors
2019-08-20 17:20:40 +03:00
Daniel Hahler
f05ca74d27 Merge pull request #5056 from blueyed/argparsing-width
Inject width via pylib to argparse formatter
2019-08-17 21:46:02 +02:00
Daniel Hahler
2a6a1ca07d Inject width via pylib to argparse formatter
`argparse.HelpFormatter` looks at `$COLUMNS` only, falling back to a
default of 80.

`py.io.get_terminal_width()` is smarter there, and could even work
better with https://github.com/pytest-dev/py/pull/219.

This ensures to use a consistent value for formatting the ini values etc.
2019-08-17 16:51:02 +02:00
Ran Benita
7259c453d6 Fix some check_untyped_defs = True mypy warnings 2019-08-16 10:41:57 +03:00
Ran Benita
28761c8da1 Have AssertionRewritingHook derive from importlib.abc.MetaPathFinder
This is nice for self-documentation, and is the type required by mypy
for adding to sys.meta_path.
2019-08-16 10:41:52 +03:00
Daniel Hahler
63d517645c doctest: handle BdbQuit
Map `BdbQuit` exception to `outcomes.Exit`.

This is necessary since we are not wrapping `pdb.set_trace` there, and
therefore our `do_quit` is not called.
2019-07-19 02:57:25 +02:00
156 changed files with 5206 additions and 1980 deletions

View File

@@ -2,15 +2,22 @@
Thanks for submitting a PR, your contribution is really appreciated!
Here is a quick checklist that should be present in PRs.
(please delete this text from the final description, this is just a guideline)
-->
- [ ] Target the `master` branch for bug fixes, documentation updates and trivial changes.
- [ ] Target the `features` branch for new features, improvements, and removals/deprecations.
- [ ] Include documentation when adding new features.
- [ ] Include new tests or update existing tests when applicable.
Unless your change is trivial or a small documentation fix (e.g., a typo or reword of a small section) please:
Unless your change is trivial or a small documentation fix (e.g., a typo or reword of a small section) please:
- [ ] 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.
- [ ] Add yourself to `AUTHORS` in alphabetical order;
Write sentences in the **past or present tense**, examples:
* *Improved verbose diff output with sequences.*
* *Terminal summary statistics now use multiple colors.*
Also make sure to end the sentence with a `.`.
- [ ] Add yourself to `AUTHORS` in alphabetical order.
-->

1
.gitignore vendored
View File

@@ -31,6 +31,7 @@ dist/
issue/
env/
.env/
.venv/
3rdparty/
.tox
.cache

View File

@@ -1,7 +1,7 @@
exclude: doc/en/example/py2py3/test_py2.py
repos:
- repo: https://github.com/psf/black
rev: 19.3b0
rev: 19.10b0
hooks:
- id: black
args: [--safe, --quiet]
@@ -37,12 +37,8 @@ repos:
hooks:
- id: pyupgrade
args: [--py3-plus]
- repo: https://github.com/pre-commit/pygrep-hooks
rev: v1.4.0
hooks:
- id: rst-backticks
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.720
rev: v0.740
hooks:
- id: mypy
files: ^(src/|testing/)

View File

@@ -23,10 +23,13 @@ install:
jobs:
include:
# OSX tests - first (in test stage), since they are the slower ones.
# Coverage for:
# - osx
# - verbose=1
- os: osx
osx_image: xcode10.1
language: generic
env: TOXENV=py37-xdist PYTEST_COVERAGE=1
env: TOXENV=py37-xdist PYTEST_COVERAGE=1 PYTEST_ADDOPTS=-v
before_install:
- which python3
- python3 -V
@@ -35,33 +38,29 @@ jobs:
- test $(python -c 'import sys; print("%d%d" % sys.version_info[0:2])') = 37
# Full run of latest supported version, without xdist.
- env: TOXENV=py37
# Coverage for:
# - pytester's LsofFdLeakChecker
# - TestArgComplete (linux only)
# - numpy
# - old attrs
# - verbose=0
# - test_sys_breakpoint_interception (via pexpect).
- env: TOXENV=py37-lsof-numpy-oldattrs-pexpect-twisted PYTEST_COVERAGE=1 PYTEST_ADDOPTS=
python: '3.7'
# Coverage tracking is slow with pypy, skip it.
- env: TOXENV=pypy3-xdist
python: 'pypy3'
- env: TOXENV=py35
dist: trusty
python: '3.5.0'
# Coverage for:
# - pytester's LsofFdLeakChecker
# - TestArgComplete (linux only)
# - numpy
# Empty PYTEST_ADDOPTS to run this non-verbose.
- env: TOXENV=py37-lsof-numpy-twisted-xdist PYTEST_COVERAGE=1 PYTEST_ADDOPTS=
- env: TOXENV=py35-xdist
python: '3.5'
# Specialized factors for py37.
# Coverage for:
# - test_sys_breakpoint_interception (via pexpect).
- env: TOXENV=py37-pexpect PYTEST_COVERAGE=1
- env: TOXENV=py37-pluggymaster-xdist
- env: TOXENV=py37-freeze
- env: TOXENV=py38-xdist
python: '3.8-dev'
python: '3.8'
- stage: baseline
env: TOXENV=py36-xdist
@@ -95,11 +94,6 @@ jobs:
tags: true
repo: pytest-dev/pytest
matrix:
allow_failures:
- python: '3.8-dev'
env: TOXENV=py38-xdist
before_script:
- |
# Do not (re-)upload coverage with cron runs.
@@ -131,3 +125,10 @@ notifications:
skip_join: true
email:
- pytest-commit@python.org
branches:
only:
- master
- features
- 4.6-maintenance
- /^\d+(\.\d+)+$/

10
AUTHORS
View File

@@ -70,6 +70,7 @@ Daniel Hahler
Daniel Nuri
Daniel Wandschneider
Danielle Jenkins
Daniil Galiev
Dave Hunt
David Díaz-Barquero
David Mohr
@@ -98,10 +99,12 @@ Feng Ma
Florian Bruhin
Floris Bruynooghe
Gabriel Reis
Gene Wood
George Kussumoto
Georgy Dyuldin
Graham Horler
Greg Price
Gregory Lee
Grig Gheorghiu
Grigorii Eremeev (budulianin)
Guido Wesdorp
@@ -132,6 +135,7 @@ Jordan Guymon
Jordan Moldow
Jordan Speicher
Joseph Hunkeler
Josh Karpel
Joshua Bronson
Jurko Gospodnetić
Justyna Janczyszyn
@@ -159,7 +163,9 @@ Manuel Krebber
Marc Schlaich
Marcelo Duarte Trevisani
Marcin Bachry
Marco Gorelli
Mark Abramowitz
Mark Dickinson
Markus Unterwaditzer
Martijn Faassen
Martin Altmayer
@@ -175,6 +181,8 @@ mbyt
Michael Aquilina
Michael Birtwell
Michael Droettboom
Michael Goerz
Michael Krebs
Michael Seifert
Michal Wajszczuk
Mihai Capotă
@@ -258,6 +266,7 @@ Virgil Dupras
Vitaly Lashmanov
Vlad Dragos
Volodymyr Piskun
Wei Lin
Wil Cooley
William Lee
Wim Glenn
@@ -265,5 +274,6 @@ Wouter van Ackooy
Xixi Zhao
Xuan Luong
Xuecong Liao
Yoav Caspi
Zac Hatfield-Dodds
Zoltán Máté

View File

@@ -13,11 +13,365 @@ with advance notice in the **Deprecations** section of releases.
file is managed by towncrier. You *may* edit previous change logs to
fix problems like typo corrections or such.
To add a new change log entry, please see
https://pip.pypa.io/en/latest/development/#adding-a-news-entry
https://pip.pypa.io/en/latest/development/contributing/#news-entries
we named the news folder changelog
.. towncrier release notes start
pytest 5.3.1 (2019-11-25)
=========================
No significant changes.
pytest 5.3.1 (2019-11-25)
=========================
Improvements
------------
- `#6231 <https://github.com/pytest-dev/pytest/issues/6231>`_: Improve check for misspelling of ``pytest.mark.parametrize``.
- `#6257 <https://github.com/pytest-dev/pytest/issues/6257>`_: Handle `exit.Exception` raised in `notify_exception` (via `pytest_internalerror`), e.g. when quitting pdb from post mortem.
Bug Fixes
---------
- `#5914 <https://github.com/pytest-dev/pytest/issues/5914>`_: pytester: fix ``no_fnmatch_line`` when used after positive matching.
- `#6082 <https://github.com/pytest-dev/pytest/issues/6082>`_: Fix line detection for doctest samples inside ``property`` docstrings, as a workaround to `bpo-17446 <https://bugs.python.org/issue17446>`__.
- `#6254 <https://github.com/pytest-dev/pytest/issues/6254>`_: Fix compatibility with pytest-parallel (regression in pytest 5.3.0).
- `#6255 <https://github.com/pytest-dev/pytest/issues/6255>`_: Clear the ``sys.last_traceback``, ``sys.last_type`` and ``sys.last_value``
attributes by deleting them instead of setting them to ``None``. This better
matches the behaviour of the Python standard library.
pytest 5.3.0 (2019-11-19)
=========================
Deprecations
------------
- `#6179 <https://github.com/pytest-dev/pytest/issues/6179>`_: The default value of ``junit_family`` option will change to ``xunit2`` in pytest 6.0, given
that this is the version supported by default in modern tools that manipulate this type of file.
In order to smooth the transition, pytest will issue a warning in case the ``--junitxml`` option
is given in the command line but ``junit_family`` is not explicitly configured in ``pytest.ini``.
For more information, `see the docs <https://docs.pytest.org/en/latest/deprecations.html#junit-family-default-value-change-to-xunit2>`__.
Features
--------
- `#4488 <https://github.com/pytest-dev/pytest/issues/4488>`_: The pytest team has created the `pytest-reportlog <https://github.com/pytest-dev/pytest-reportlog>`__
plugin, which provides a new ``--report-log=FILE`` option that writes *report logs* into a file as the test session executes.
Each line of the report log contains a self contained JSON object corresponding to a testing event,
such as a collection or a test result report. The file is guaranteed to be flushed after writing
each line, so systems can read and process events in real-time.
The plugin is meant to replace the ``--resultlog`` option, which is deprecated and meant to be removed
in a future release. If you use ``--resultlog``, please try out ``pytest-reportlog`` and
provide feedback.
- `#4730 <https://github.com/pytest-dev/pytest/issues/4730>`_: When ``sys.pycache_prefix`` (Python 3.8+) is set, it will be used by pytest to cache test files changed by the assertion rewriting mechanism.
This makes it easier to benefit of cached ``.pyc`` files even on file systems without permissions.
- `#5515 <https://github.com/pytest-dev/pytest/issues/5515>`_: Allow selective auto-indentation of multiline log messages.
Adds command line option ``--log-auto-indent``, config option
``log_auto_indent`` and support for per-entry configuration of
indentation behavior on calls to ``logging.log()``.
Alters the default for auto-indention from ``on`` to ``off``. This
restores the older behavior that existed prior to v4.6.0. This
reversion to earlier behavior was done because it is better to
activate new features that may lead to broken tests explicitly
rather than implicitly.
- `#5914 <https://github.com/pytest-dev/pytest/issues/5914>`_: `testdir <https://docs.pytest.org/en/latest/reference.html#testdir>`__ learned two new functions, `no_fnmatch_line <https://docs.pytest.org/en/latest/reference.html#_pytest.pytester.LineMatcher.no_fnmatch_line>`_ and
`no_re_match_line <https://docs.pytest.org/en/latest/reference.html#_pytest.pytester.LineMatcher.no_re_match_line>`_.
The functions are used to ensure the captured text *does not* match the given
pattern.
The previous idiom was to use ``re.match``:
.. code-block:: python
result = testdir.runpytest()
assert re.match(pat, result.stdout.str()) is None
Or the ``in`` operator:
.. code-block:: python
result = testdir.runpytest()
assert text in result.stdout.str()
But the new functions produce best output on failure.
- `#6057 <https://github.com/pytest-dev/pytest/issues/6057>`_: Added tolerances to complex values when printing ``pytest.approx``.
For example, ``repr(pytest.approx(3+4j))`` returns ``(3+4j) ± 5e-06 ∠ ±180°``. This is polar notation indicating a circle around the expected value, with a radius of 5e-06. For ``approx`` comparisons to return ``True``, the actual value should fall within this circle.
- `#6061 <https://github.com/pytest-dev/pytest/issues/6061>`_: Added the pluginmanager as an argument to ``pytest_addoption``
so that hooks can be invoked when setting up command line options. This is
useful for having one plugin communicate things to another plugin,
such as default values or which set of command line options to add.
Improvements
------------
- `#5061 <https://github.com/pytest-dev/pytest/issues/5061>`_: Use multiple colors with terminal summary statistics.
- `#5630 <https://github.com/pytest-dev/pytest/issues/5630>`_: Quitting from debuggers is now properly handled in ``doctest`` items.
- `#5924 <https://github.com/pytest-dev/pytest/issues/5924>`_: Improved verbose diff output with sequences.
Before:
::
E AssertionError: assert ['version', '...version_info'] == ['version', '...version', ...]
E Right contains 3 more items, first extra item: ' '
E Full diff:
E - ['version', 'version_info', 'sys.version', 'sys.version_info']
E + ['version',
E + 'version_info',
E + 'sys.version',
E + 'sys.version_info',
E + ' ',
E + 'sys.version',
E + 'sys.version_info']
After:
::
E AssertionError: assert ['version', '...version_info'] == ['version', '...version', ...]
E Right contains 3 more items, first extra item: ' '
E Full diff:
E [
E 'version',
E 'version_info',
E 'sys.version',
E 'sys.version_info',
E + ' ',
E + 'sys.version',
E + 'sys.version_info',
E ]
- `#5934 <https://github.com/pytest-dev/pytest/issues/5934>`_: ``repr`` of ``ExceptionInfo`` objects has been improved to honor the ``__repr__`` method of the underlying exception.
- `#5936 <https://github.com/pytest-dev/pytest/issues/5936>`_: Display untruncated assertion message with ``-vv``.
- `#5990 <https://github.com/pytest-dev/pytest/issues/5990>`_: Fixed plurality mismatch in test summary (e.g. display "1 error" instead of "1 errors").
- `#6008 <https://github.com/pytest-dev/pytest/issues/6008>`_: ``Config.InvocationParams.args`` is now always a ``tuple`` to better convey that it should be
immutable and avoid accidental modifications.
- `#6023 <https://github.com/pytest-dev/pytest/issues/6023>`_: ``pytest.main`` returns a ``pytest.ExitCode`` instance now, except for when custom exit codes are used (where it returns ``int`` then still).
- `#6026 <https://github.com/pytest-dev/pytest/issues/6026>`_: Align prefixes in output of pytester's ``LineMatcher``.
- `#6059 <https://github.com/pytest-dev/pytest/issues/6059>`_: Collection errors are reported as errors (and not failures like before) in the terminal's short test summary.
- `#6069 <https://github.com/pytest-dev/pytest/issues/6069>`_: ``pytester.spawn`` does not skip/xfail tests on FreeBSD anymore unconditionally.
- `#6097 <https://github.com/pytest-dev/pytest/issues/6097>`_: The "[...%]" indicator in the test summary is now colored according to the final (new) multi-colored line's main color.
- `#6116 <https://github.com/pytest-dev/pytest/issues/6116>`_: Added ``--co`` as a synonym to ``--collect-only``.
- `#6148 <https://github.com/pytest-dev/pytest/issues/6148>`_: ``atomicwrites`` is now only used on Windows, fixing a performance regression with assertion rewriting on Unix.
- `#6152 <https://github.com/pytest-dev/pytest/issues/6152>`_: Now parametrization will use the ``__name__`` attribute of any object for the id, if present. Previously it would only use ``__name__`` for functions and classes.
- `#6176 <https://github.com/pytest-dev/pytest/issues/6176>`_: Improved failure reporting with pytester's ``Hookrecorder.assertoutcome``.
- `#6181 <https://github.com/pytest-dev/pytest/issues/6181>`_: The reason for a stopped session, e.g. with ``--maxfail`` / ``-x``, now gets reported in the test summary.
- `#6206 <https://github.com/pytest-dev/pytest/issues/6206>`_: Improved ``cache.set`` robustness and performance.
Bug Fixes
---------
- `#2049 <https://github.com/pytest-dev/pytest/issues/2049>`_: Fixed ``--setup-plan`` showing inaccurate information about fixture lifetimes.
- `#2548 <https://github.com/pytest-dev/pytest/issues/2548>`_: Fixed line offset mismatch of skipped tests in terminal summary.
- `#6039 <https://github.com/pytest-dev/pytest/issues/6039>`_: The ``PytestDoctestRunner`` is now properly invalidated when unconfiguring the doctest plugin.
This is important when used with ``pytester``'s ``runpytest_inprocess``.
- `#6047 <https://github.com/pytest-dev/pytest/issues/6047>`_: BaseExceptions are now handled in ``saferepr``, which includes ``pytest.fail.Exception`` etc.
- `#6074 <https://github.com/pytest-dev/pytest/issues/6074>`_: pytester: fixed order of arguments in ``rm_rf`` warning when cleaning up temporary directories, and do not emit warnings for errors with ``os.open``.
- `#6189 <https://github.com/pytest-dev/pytest/issues/6189>`_: Fixed result of ``getmodpath`` method.
Trivial/Internal Changes
------------------------
- `#4901 <https://github.com/pytest-dev/pytest/issues/4901>`_: ``RunResult`` from ``pytester`` now displays the mnemonic of the ``ret`` attribute when it is a
valid ``pytest.ExitCode`` value.
pytest 5.2.4 (2019-11-15)
=========================
Bug Fixes
---------
- `#6194 <https://github.com/pytest-dev/pytest/issues/6194>`_: Fix incorrect discovery of non-test ``__init__.py`` files.
- `#6197 <https://github.com/pytest-dev/pytest/issues/6197>`_: Revert "The first test in a package (``__init__.py``) marked with ``@pytest.mark.skip`` is now correctly skipped.".
pytest 5.2.3 (2019-11-14)
=========================
Bug Fixes
---------
- `#5830 <https://github.com/pytest-dev/pytest/issues/5830>`_: The first test in a package (``__init__.py``) marked with ``@pytest.mark.skip`` is now correctly skipped.
- `#6099 <https://github.com/pytest-dev/pytest/issues/6099>`_: Fix ``--trace`` when used with parametrized functions.
- `#6183 <https://github.com/pytest-dev/pytest/issues/6183>`_: Using ``request`` as a parameter name in ``@pytest.mark.parametrize`` now produces a more
user-friendly error.
pytest 5.2.2 (2019-10-24)
=========================
Bug Fixes
---------
- `#5206 <https://github.com/pytest-dev/pytest/issues/5206>`_: Fix ``--nf`` to not forget about known nodeids with partial test selection.
- `#5906 <https://github.com/pytest-dev/pytest/issues/5906>`_: Fix crash with ``KeyboardInterrupt`` during ``--setup-show``.
- `#5946 <https://github.com/pytest-dev/pytest/issues/5946>`_: Fixed issue when parametrizing fixtures with numpy arrays (and possibly other sequence-like types).
- `#6044 <https://github.com/pytest-dev/pytest/issues/6044>`_: Properly ignore ``FileNotFoundError`` exceptions when trying to remove old temporary directories,
for instance when multiple processes try to remove the same directory (common with ``pytest-xdist``
for example).
pytest 5.2.1 (2019-10-06)
=========================
Bug Fixes
---------
- `#5902 <https://github.com/pytest-dev/pytest/issues/5902>`_: Fix warnings about deprecated ``cmp`` attribute in ``attrs>=19.2``.
pytest 5.2.0 (2019-09-28)
=========================
Deprecations
------------
- `#1682 <https://github.com/pytest-dev/pytest/issues/1682>`_: Passing arguments to pytest.fixture() as positional arguments is deprecated - pass them
as a keyword argument instead.
Features
--------
- `#1682 <https://github.com/pytest-dev/pytest/issues/1682>`_: The ``scope`` parameter of ``@pytest.fixture`` can now be a callable that receives
the fixture name and the ``config`` object as keyword-only parameters.
See `the docs <https://docs.pytest.org/en/latest/fixture.html#dynamic-scope>`__ for more information.
- `#5764 <https://github.com/pytest-dev/pytest/issues/5764>`_: New behavior of the ``--pastebin`` option: failures to connect to the pastebin server are reported, without failing the pytest run
Bug Fixes
---------
- `#5806 <https://github.com/pytest-dev/pytest/issues/5806>`_: Fix "lexer" being used when uploading to bpaste.net from ``--pastebin`` to "text".
- `#5884 <https://github.com/pytest-dev/pytest/issues/5884>`_: Fix ``--setup-only`` and ``--setup-show`` for custom pytest items.
Trivial/Internal Changes
------------------------
- `#5056 <https://github.com/pytest-dev/pytest/issues/5056>`_: The HelpFormatter uses ``py.io.get_terminal_width`` for better width detection.
pytest 5.1.3 (2019-09-18)
=========================
Bug Fixes
---------
- `#5807 <https://github.com/pytest-dev/pytest/issues/5807>`_: Fix pypy3.6 (nightly) on windows.
- `#5811 <https://github.com/pytest-dev/pytest/issues/5811>`_: Handle ``--fulltrace`` correctly with ``pytest.raises``.
- `#5819 <https://github.com/pytest-dev/pytest/issues/5819>`_: Windows: Fix regression with conftest whose qualified name contains uppercase
characters (introduced by #5792).
pytest 5.1.2 (2019-08-30)
=========================
@@ -150,9 +504,6 @@ Bug Fixes
- `#5477 <https://github.com/pytest-dev/pytest/issues/5477>`_: The XML file produced by ``--junitxml`` now correctly contain a ``<testsuites>`` root element.
- `#5523 <https://github.com/pytest-dev/pytest/issues/5523>`_: Fixed using multiple short options together in the command-line (for example ``-vs``) in Python 3.8+.
- `#5524 <https://github.com/pytest-dev/pytest/issues/5524>`_: Fix issue where ``tmp_path`` and ``tmpdir`` would not remove directories containing files marked as read-only,
which could lead to pytest crashing when executed a second time with the ``--basetemp`` option.
@@ -436,6 +787,32 @@ Improved Documentation
- `#5416 <https://github.com/pytest-dev/pytest/issues/5416>`_: Fix PytestUnknownMarkWarning in run/skip example.
pytest 4.6.6 (2019-10-11)
=========================
Bug Fixes
---------
- `#5523 <https://github.com/pytest-dev/pytest/issues/5523>`_: Fixed using multiple short options together in the command-line (for example ``-vs``) in Python 3.8+.
- `#5537 <https://github.com/pytest-dev/pytest/issues/5537>`_: Replace ``importlib_metadata`` backport with ``importlib.metadata`` from the
standard library on Python 3.8+.
- `#5806 <https://github.com/pytest-dev/pytest/issues/5806>`_: Fix "lexer" being used when uploading to bpaste.net from ``--pastebin`` to "text".
- `#5902 <https://github.com/pytest-dev/pytest/issues/5902>`_: Fix warnings about deprecated ``cmp`` attribute in ``attrs>=19.2``.
Trivial/Internal Changes
------------------------
- `#5801 <https://github.com/pytest-dev/pytest/issues/5801>`_: Fixes python version checks (detected by ``flake8-2020``) in case python4 becomes a thing.
pytest 4.6.5 (2019-08-05)
=========================
@@ -1766,7 +2143,8 @@ Features
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.
- `#3985 <https://github.com/pytest-dev/pytest/issues/3985>`_: Introduce ``tmp_path`` as a fixture providing a Path object. Also introduce ``tmp_path_factory`` as
a session-scoped fixture for creating arbitrary temporary directories from any other fixture or test.
- `#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
@@ -3355,7 +3733,7 @@ Deprecations and Removals
- ``pytest.approx`` no longer supports ``>``, ``>=``, ``<`` and ``<=``
operators to avoid surprising/inconsistent behavior. See `the approx docs
<https://docs.pytest.org/en/latest/builtin.html#pytest.approx>`_ for more
<https://docs.pytest.org/en/latest/reference.html#pytest-approx>`_ for more
information. (`#2003 <https://github.com/pytest-dev/pytest/issues/2003>`_)
- All old-style specific behavior in current classes in the pytest's API is
@@ -4712,7 +5090,7 @@ time or change existing behaviors in order to make them less surprising/more use
* Fix (`#1422`_): junit record_xml_property doesn't allow multiple records
with same name.
.. _`traceback style docs`: https://pytest.org/latest/usage.html#modifying-python-traceback-printing
.. _`traceback style docs`: https://pytest.org/en/latest/usage.html#modifying-python-traceback-printing
.. _#1609: https://github.com/pytest-dev/pytest/issues/1609
.. _#1422: https://github.com/pytest-dev/pytest/issues/1422
@@ -5230,7 +5608,7 @@ time or change existing behaviors in order to make them less surprising/more use
- add ability to set command line options by environment variable PYTEST_ADDOPTS.
- added documentation on the new pytest-dev teams on bitbucket and
github. See https://pytest.org/latest/contributing.html .
github. See https://pytest.org/en/latest/contributing.html .
Thanks to Anatoly for pushing and initial work on this.
- fix issue650: new option ``--docttest-ignore-import-errors`` which
@@ -5971,7 +6349,7 @@ Bug fixes:
- yielded test functions will now have autouse-fixtures active but
cannot accept fixtures as funcargs - it's anyway recommended to
rather use the post-2.0 parametrize features instead of yield, see:
http://pytest.org/latest/example/parametrize.html
http://pytest.org/en/latest/example/parametrize.html
- fix autouse-issue where autouse-fixtures would not be discovered
if defined in an a/conftest.py file and tests in a/tests/test_some.py
- fix issue226 - LIFO ordering for fixture teardowns
@@ -6104,7 +6482,7 @@ Bug fixes:
- pluginmanager.register(...) now raises ValueError if the
plugin has been already registered or the name is taken
- fix issue159: improve http://pytest.org/latest/faq.html
- fix issue159: improve http://pytest.org/en/latest/faq.html
especially with respect to the "magic" history, also mention
pytest-django, trial and unittest integration.
@@ -6217,7 +6595,7 @@ Bug fixes:
or through plugin hooks. Also introduce a "--strict" option which
will treat unregistered markers as errors
allowing to avoid typos and maintain a well described set of markers
for your test suite. See exaples at http://pytest.org/latest/mark.html
for your test suite. See exaples at http://pytest.org/en/latest/mark.html
and its links.
- issue50: introduce "-m marker" option to select tests based on markers
(this is a stricter and more predictable version of '-k' in that "-m"
@@ -6400,7 +6778,7 @@ Bug fixes:
- refinements to "collecting" output on non-ttys
- refine internal plugin registration and --traceconfig output
- introduce a mechanism to prevent/unregister plugins from the
command line, see http://pytest.org/plugins.html#cmdunregister
command line, see http://pytest.org/en/latest/plugins.html#cmdunregister
- activate resultlog plugin by default
- fix regression wrt yielded tests which due to the
collection-before-running semantics were not

View File

@@ -262,6 +262,19 @@ Here is a simple overview, with pytest-specific bits:
When committing, ``pre-commit`` will re-format the files if necessary.
#. If instead of using ``tox`` you prefer to run the tests directly, then we suggest to create a virtual environment and use
an editable install with the ``testing`` extra::
$ python3 -m venv .venv
$ source .venv/bin/activate # Linux
$ .venv/Scripts/activate.bat # Windows
$ pip install -e ".[testing]"
Afterwards, you can edit the files and run pytest normally::
$ pytest testing/test_config.py
#. Commit and push once your tests pass and you are happy with your change(s)::
$ git commit -a -m "<commit message>"

View File

@@ -111,14 +111,28 @@ Consult the `Changelog <https://docs.pytest.org/en/latest/changelog.html>`__ pag
Support pytest
--------------
You can support pytest by obtaining a `Tidelift subscription`_.
`Open Collective`_ is an online funding platform for open and transparent communities.
It provide tools to raise money and share your finances in full transparency.
Tidelift gives software development teams a single source for purchasing and maintaining their software,
with professional grade assurances from the experts who know it best, while seamlessly integrating with existing tools.
It is the platform of choice for individuals and companies that want to make one-time or
monthly donations directly to the project.
See more datails in the `pytest collective`_.
.. _Open Collective: https://opencollective.com
.. _pytest collective: https://opencollective.com/pytest
.. _`Tidelift subscription`: https://tidelift.com/subscription/pkg/pypi-pytest?utm_source=pypi-pytest&utm_medium=referral&utm_campaign=readme
pytest for enterprise
---------------------
Available as part of the Tidelift Subscription.
The maintainers of pytest and thousands of other packages are working with Tidelift to deliver commercial support and
maintenance for the open source dependencies you use to build your applications.
Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use.
`Learn more. <https://tidelift.com/subscription/pkg/pypi-pytest?utm_source=pypi-pytest&utm_medium=referral&utm_campaign=enterprise&utm_term=repo>`_
Security
^^^^^^^^

View File

@@ -1,12 +1,14 @@
This directory contains "newsfragments" which are short files that contain a small **ReST**-formatted
text that will be added to the next ``CHANGELOG``.
The ``CHANGELOG`` will be read by users, so this description should be aimed to pytest users
The ``CHANGELOG`` will be read by **users**, so this description should be aimed to pytest users
instead of describing internal changes which are only relevant to the developers.
Make sure to use full sentences with correct case and punctuation, for example::
Make sure to use full sentences in the **past or present tense** and use punctuation, examples::
Fix issue with non-ascii messages from the ``warnings`` module.
Improved verbose diff output with sequences.
Terminal summary statistics now use multiple colors.
Each file should be named like ``<ISSUE>.<TYPE>.rst``, where
``<ISSUE>`` is an issue number, and ``<TYPE>`` is one of:

View File

@@ -16,7 +16,7 @@ REGENDOC_ARGS := \
--normalize "/[ \t]+\n/\n/" \
--normalize "~\$$REGENDOC_TMPDIR~/home/sweet/project~" \
--normalize "~/path/to/example~/home/sweet/project~" \
--normalize "/in \d+.\d+s ==/in 0.12s ==/" \
--normalize "/in \d.\d\ds/in 0.12s/" \
--normalize "@/tmp/pytest-of-.*/pytest-\d+@PYTEST_TMPDIR@" \
--normalize "@pytest-(\d+)\\.[^ ,]+@pytest-\1.x.y@" \
--normalize "@(This is pytest version )(\d+)\\.[^ ,]+@\1\2.x.y@" \

View File

@@ -12,6 +12,7 @@
<li><a href="{{ pathto('backwards-compatibility') }}">Backwards Compatibility</a></li>
<li><a href="{{ pathto('py27-py34-deprecation') }}">Python 2.7 and 3.4 Support</a></li>
<li><a href="{{ pathto('sponsor') }}">Sponsor</a></li>
<li><a href="{{ pathto('tidelift') }}">pytest for Enterprise</a></li>
<li><a href="{{ pathto('license') }}">License</a></li>
<li><a href="{{ pathto('contact') }}">Contact Channels</a></li>
</ul>

View File

@@ -6,6 +6,14 @@ Release announcements
:maxdepth: 2
release-5.3.1
release-5.3.0
release-5.2.4
release-5.2.3
release-5.2.2
release-5.2.1
release-5.2.0
release-5.1.3
release-5.1.2
release-5.1.1
release-5.1.0

View File

@@ -7,7 +7,7 @@ see below for summary and detailed lists. A lot of long-deprecated code
has been removed, resulting in a much smaller and cleaner
implementation. See the new docs with examples here:
http://pytest.org/2.0.0/index.html
http://pytest.org/en/latest/index.html
A note on packaging: pytest used to part of the "py" distribution up
until version py-1.3.4 but this has changed now: pytest-2.0.0 only
@@ -36,12 +36,12 @@ New Features
import pytest ; pytest.main(arglist, pluginlist)
see http://pytest.org/2.0.0/usage.html for details.
see http://pytest.org/en/latest/usage.html for details.
- new and better reporting information in assert expressions
if comparing lists, sequences or strings.
see http://pytest.org/2.0.0/assert.html#newreport
see http://pytest.org/en/latest/assert.html#newreport
- new configuration through ini-files (setup.cfg or tox.ini recognized),
for example::
@@ -50,7 +50,7 @@ New Features
norecursedirs = .hg data* # don't ever recurse in such dirs
addopts = -x --pyargs # add these command line options by default
see http://pytest.org/2.0.0/customize.html
see http://pytest.org/en/latest/customize.html
- improved standard unittest support. In general py.test should now
better be able to run custom unittest.TestCases like twisted trial

View File

@@ -57,7 +57,7 @@ Changes between 2.0.0 and 2.0.1
- refinements to "collecting" output on non-ttys
- refine internal plugin registration and --traceconfig output
- introduce a mechanism to prevent/unregister plugins from the
command line, see http://pytest.org/latest/plugins.html#cmdunregister
command line, see http://pytest.org/en/latest/plugins.html#cmdunregister
- activate resultlog plugin by default
- fix regression wrt yielded tests which due to the
collection-before-running semantics were not

View File

@@ -9,7 +9,7 @@ with these improvements:
- new @pytest.mark.parametrize decorator to run tests with different arguments
- new metafunc.parametrize() API for parametrizing arguments independently
- see examples at http://pytest.org/latest/example/parametrize.html
- see examples at http://pytest.org/en/latest/example/parametrize.html
- NOTE that parametrize() related APIs are still a bit experimental
and might change in future releases.
@@ -18,7 +18,7 @@ with these improvements:
- "-m markexpr" option for selecting tests according to their mark
- a new "markers" ini-variable for registering test markers for your project
- the new "--strict" bails out with an error if using unregistered markers.
- see examples at http://pytest.org/latest/example/markers.html
- see examples at http://pytest.org/en/latest/example/markers.html
* duration profiling: new "--duration=N" option showing the N slowest test
execution or setup/teardown calls. This is most useful if you want to
@@ -78,7 +78,7 @@ Changes between 2.1.3 and 2.2.0
or through plugin hooks. Also introduce a "--strict" option which
will treat unregistered markers as errors
allowing to avoid typos and maintain a well described set of markers
for your test suite. See examples at http://pytest.org/latest/mark.html
for your test suite. See examples at http://pytest.org/en/latest/mark.html
and its links.
- issue50: introduce "-m marker" option to select tests based on markers
(this is a stricter and more predictable version of "-k" in that "-m"

View File

@@ -13,12 +13,12 @@ re-useable fixture design.
For detailed info and tutorial-style examples, see:
http://pytest.org/latest/fixture.html
http://pytest.org/en/latest/fixture.html
Moreover, there is now support for using pytest fixtures/funcargs with
unittest-style suites, see here for examples:
http://pytest.org/latest/unittest.html
http://pytest.org/en/latest/unittest.html
Besides, more unittest-test suites are now expected to "simply work"
with pytest.
@@ -29,11 +29,11 @@ pytest-2.2.4.
If you are interested in the precise reasoning (including examples) of the
pytest-2.3 fixture evolution, please consult
http://pytest.org/latest/funcarg_compare.html
http://pytest.org/en/latest/funcarg_compare.html
For general info on installation and getting started:
http://pytest.org/latest/getting-started.html
http://pytest.org/en/latest/getting-started.html
Docs and PDF access as usual at:
@@ -94,7 +94,7 @@ Changes between 2.2.4 and 2.3.0
- pluginmanager.register(...) now raises ValueError if the
plugin has been already registered or the name is taken
- fix issue159: improve http://pytest.org/latest/faq.html
- fix issue159: improve http://pytest.org/en/latest/faq.html
especially with respect to the "magic" history, also mention
pytest-django, trial and unittest integration.

View File

@@ -16,7 +16,7 @@ comes with the following fixes and features:
- yielded test functions will now have autouse-fixtures active but
cannot accept fixtures as funcargs - it's anyway recommended to
rather use the post-2.0 parametrize features instead of yield, see:
http://pytest.org/latest/example/parametrize.html
http://pytest.org/en/latest/example/parametrize.html
- fix autouse-issue where autouse-fixtures would not be discovered
if defined in an a/conftest.py file and tests in a/tests/test_some.py
- fix issue226 - LIFO ordering for fixture teardowns

View File

@@ -7,7 +7,7 @@ from a few supposedly very minor incompatibilities. See below for
a full list of details. A few feature highlights:
- new yield-style fixtures `pytest.yield_fixture
<http://pytest.org/latest/yieldfixture.html>`_, allowing to use
<http://pytest.org/en/latest/yieldfixture.html>`_, allowing to use
existing with-style context managers in fixture functions.
- improved pdb support: ``import pdb ; pdb.set_trace()`` now works

View File

@@ -52,7 +52,7 @@ holger krekel
- add ability to set command line options by environment variable PYTEST_ADDOPTS.
- added documentation on the new pytest-dev teams on bitbucket and
github. See https://pytest.org/latest/contributing.html .
github. See https://pytest.org/en/latest/contributing.html .
Thanks to Anatoly for pushing and initial work on this.
- fix issue650: new option ``--docttest-ignore-import-errors`` which

View File

@@ -131,7 +131,7 @@ The py.test Development Team
with same name.
.. _`traceback style docs`: https://pytest.org/latest/usage.html#modifying-python-traceback-printing
.. _`traceback style docs`: https://pytest.org/en/latest/usage.html#modifying-python-traceback-printing
.. _#1422: https://github.com/pytest-dev/pytest/issues/1422
.. _#1379: https://github.com/pytest-dev/pytest/issues/1379

View File

@@ -0,0 +1,23 @@
pytest-5.1.3
=======================================
pytest 5.1.3 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:
* Anthony Sottile
* Bruno Oliveira
* Christian Neumüller
* Daniel Hahler
* Gene Wood
* Hugo
Happy testing,
The pytest Development Team

View File

@@ -0,0 +1,35 @@
pytest-5.2.0
=======================================
The pytest team is proud to announce the 5.2.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:
* Andrzej Klajnert
* Anthony Sottile
* Bruno Oliveira
* Daniel Hahler
* James Cooke
* Michael Goerz
* Ran Benita
* Tomáš Chvátal
Happy testing,
The Pytest Development Team

View File

@@ -0,0 +1,23 @@
pytest-5.2.1
=======================================
pytest 5.2.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:
* Anthony Sottile
* Bruno Oliveira
* Florian Bruhin
* Hynek Schlawack
* Kevin J. Foley
* tadashigaki
Happy testing,
The pytest Development Team

View File

@@ -0,0 +1,29 @@
pytest-5.2.2
=======================================
pytest 5.2.2 has just been released to PyPI.
This is a bug-fix release, being a drop-in replacement. To upgrade::
pip install --upgrade pytest
The full changelog is available at https://docs.pytest.org/en/latest/changelog.html.
Thanks to all who contributed to this release, among them:
* Albert Tugushev
* Andrzej Klajnert
* Anthony Sottile
* Bruno Oliveira
* Daniel Hahler
* Florian Bruhin
* Nattaphoom Chaipreecha
* Oliver Bestwalter
* Philipp Loose
* Ran Benita
* Victor Maryama
* Yoav Caspi
Happy testing,
The pytest Development Team

View File

@@ -0,0 +1,28 @@
pytest-5.2.3
=======================================
pytest 5.2.3 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:
* Anthony Sottile
* Brett Cannon
* Bruno Oliveira
* Daniel Hahler
* Daniil Galiev
* David Szotten
* Florian Bruhin
* Patrick Harmon
* Ran Benita
* Zac Hatfield-Dodds
* Zak Hassan
Happy testing,
The pytest Development Team

View File

@@ -0,0 +1,22 @@
pytest-5.2.4
=======================================
pytest 5.2.4 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:
* Anthony Sottile
* Bruno Oliveira
* Daniel Hahler
* Hugo
* Michael Shields
Happy testing,
The pytest Development Team

View File

@@ -0,0 +1,45 @@
pytest-5.3.0
=======================================
The pytest team is proud to announce the 5.3.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:
* AnjoMan
* Anthony Sottile
* Anton Lodder
* Bruno Oliveira
* Daniel Hahler
* Gregory Lee
* Josh Karpel
* JoshKarpel
* Joshua Storck
* Kale Kundert
* MarcoGorelli
* Michael Krebs
* NNRepos
* Ran Benita
* TH3CHARLie
* Tibor Arpas
* Zac Hatfield-Dodds
* 林玮
Happy testing,
The Pytest Development Team

View File

@@ -0,0 +1,26 @@
pytest-5.3.1
=======================================
pytest 5.3.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:
* Anthony Sottile
* Bruno Oliveira
* Daniel Hahler
* Felix Yan
* Florian Bruhin
* Mark Dickinson
* Nikolay Kondratyev
* Steffen Schroeder
* Zac Hatfield-Dodds
Happy testing,
The pytest Development Team

View File

@@ -279,7 +279,7 @@ the conftest file:
E vals: 1 != 2
test_foocompare.py:12: AssertionError
1 failed in 0.02s
1 failed in 0.12s
.. _assert-details:
.. _`assert introspection`:

View File

@@ -104,6 +104,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
Captured logs are available through the following properties/methods::
* caplog.messages -> list of format-interpolated log messages
* caplog.text -> string containing formatted log output
* caplog.records -> list of logging.LogRecord instances
* caplog.record_tuples -> list of (logger_name, level, message) tuples
@@ -160,7 +161,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
in python < 3.6 this is a pathlib2.Path
no tests ran in 0.00s
no tests ran in 0.12s
You can also interactively ask for help, e.g. by typing on the Python interactive prompt something like:

View File

@@ -75,7 +75,7 @@ If you run this for the first time you will see two failures:
E Failed: bad luck
test_50.py:7: Failed
2 failed, 48 passed in 0.07s
2 failed, 48 passed in 0.12s
If you then run it with ``--lf``:
@@ -230,7 +230,7 @@ If you run this command for the first time, you can see the print statement:
test_caching.py:20: AssertionError
-------------------------- Captured stdout setup ---------------------------
running expensive computation...
1 failed in 0.02s
1 failed in 0.12s
If you run it a second time, the value will be retrieved from
the cache and nothing will be printed:
@@ -249,7 +249,7 @@ the cache and nothing will be printed:
E assert 42 == 23
test_caching.py:20: AssertionError
1 failed in 0.02s
1 failed in 0.12s
See the :ref:`cache-api` for more details.
@@ -277,7 +277,60 @@ You can always peek at the content of the cache using the
'test_caching.py::test_function': True,
'test_foocompare.py::test_compare': True}
cache/nodeids contains:
['test_caching.py::test_function']
['test_assert1.py::test_function',
'test_assert2.py::test_set_comparison',
'test_foocompare.py::test_compare',
'test_50.py::test_num[0]',
'test_50.py::test_num[1]',
'test_50.py::test_num[2]',
'test_50.py::test_num[3]',
'test_50.py::test_num[4]',
'test_50.py::test_num[5]',
'test_50.py::test_num[6]',
'test_50.py::test_num[7]',
'test_50.py::test_num[8]',
'test_50.py::test_num[9]',
'test_50.py::test_num[10]',
'test_50.py::test_num[11]',
'test_50.py::test_num[12]',
'test_50.py::test_num[13]',
'test_50.py::test_num[14]',
'test_50.py::test_num[15]',
'test_50.py::test_num[16]',
'test_50.py::test_num[17]',
'test_50.py::test_num[18]',
'test_50.py::test_num[19]',
'test_50.py::test_num[20]',
'test_50.py::test_num[21]',
'test_50.py::test_num[22]',
'test_50.py::test_num[23]',
'test_50.py::test_num[24]',
'test_50.py::test_num[25]',
'test_50.py::test_num[26]',
'test_50.py::test_num[27]',
'test_50.py::test_num[28]',
'test_50.py::test_num[29]',
'test_50.py::test_num[30]',
'test_50.py::test_num[31]',
'test_50.py::test_num[32]',
'test_50.py::test_num[33]',
'test_50.py::test_num[34]',
'test_50.py::test_num[35]',
'test_50.py::test_num[36]',
'test_50.py::test_num[37]',
'test_50.py::test_num[38]',
'test_50.py::test_num[39]',
'test_50.py::test_num[40]',
'test_50.py::test_num[41]',
'test_50.py::test_num[42]',
'test_50.py::test_num[43]',
'test_50.py::test_num[44]',
'test_50.py::test_num[45]',
'test_50.py::test_num[46]',
'test_50.py::test_num[47]',
'test_50.py::test_num[48]',
'test_50.py::test_num[49]',
'test_caching.py::test_function']
cache/stepwise contains:
[]
example/value contains:

View File

@@ -92,7 +92,7 @@ exclude_patterns = [
# The reST default role (used for this markup: `text`) to use for all documents.
# default_role = None
default_role = "literal"
# If true, '()' will be appended to :func: etc. cross-reference text.
# add_function_parentheses = True
@@ -112,6 +112,19 @@ pygments_style = "sphinx"
# A list of ignored prefixes for module index sorting.
# modindex_common_prefix = []
# A list of regular expressions that match URIs that should not be checked when
# doing a linkcheck.
linkcheck_ignore = [
"https://github.com/numpy/numpy/blob/master/doc/release/1.16.0-notes.rst#new-deprecations",
"https://blogs.msdn.microsoft.com/bharry/2017/06/28/testing-in-a-cloud-delivery-cadence/",
"http://pythontesting.net/framework/pytest-introduction/",
r"https://github.com/pytest-dev/pytest/issues/\d+",
r"https://github.com/pytest-dev/pytest/pull/\d+",
]
# The number of worker threads to use when checking links (default=5).
linkcheck_workers = 5
# -- Options for HTML output ---------------------------------------------------

View File

@@ -38,19 +38,24 @@ Full pytest documentation
customize
example/index
bash-completion
faq
backwards-compatibility
deprecations
py27-py34-deprecation
historical-notes
license
contributing
development_guide
sponsor
tidelift
license
contact
historical-notes
talks
projects
faq
contact
sponsor
.. only:: html

View File

@@ -107,8 +107,8 @@ check for ini-files as follows:
# first look for pytest.ini files
path/pytest.ini
path/setup.cfg # must also contain [tool:pytest] section to match
path/tox.ini # must also contain [pytest] section to match
path/setup.cfg # must also contain [tool:pytest] section to match
pytest.ini
... # all the way down to the root
@@ -134,10 +134,13 @@ progress output, you can write it into a configuration file:
.. code-block:: ini
# content of pytest.ini or tox.ini
# setup.cfg files should use [tool:pytest] section instead
[pytest]
addopts = -ra -q
# content of setup.cfg
[tool:pytest]
addopts = -ra -q
Alternatively, you can set a ``PYTEST_ADDOPTS`` environment variable to add command
line options while the environment is in use:

View File

@@ -19,6 +19,27 @@ 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>`.
``junit_family`` default value change to "xunit2"
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 5.2
The default value of ``junit_family`` option will change to ``xunit2`` in pytest 6.0, given
that this is the version supported by default in modern tools that manipulate this type of file.
In order to smooth the transition, pytest will issue a warning in case the ``--junitxml`` option
is given in the command line but ``junit_family`` is not explicitly configured in ``pytest.ini``::
PytestDeprecationWarning: The 'junit_family' default value will change to 'xunit2' in pytest 6.0.
Add 'junit_family=legacy' to your pytest.ini file to silence this warning and make your suite compatible.
In order to silence this warning, users just need to configure the ``junit_family`` option explicitly:
.. code-block:: ini
[pytest]
junit_family=legacy
``funcargnames`` alias for ``fixturenames``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -40,15 +61,15 @@ Result log (``--result-log``)
.. deprecated:: 4.0
The ``--result-log`` option produces a stream of test reports which can be
analysed at runtime. It uses a custom format which requires users to implement their own
parser, but the team believes using a line-based format that can be parsed using standard
tools would provide a suitable and better alternative.
analysed at runtime, but it uses a custom format which requires users to implement their own
parser.
The current plan is to provide an alternative in the pytest 5.0 series and remove the ``--result-log``
option in pytest 6.0 after the new implementation proves satisfactory to all users and is deemed
stable.
The `pytest-reportlog <https://github.com/pytest-dev/pytest-reportlog>`__ plugin provides a ``--report-log`` option, a more standard and extensible alternative, producing
one JSON object per-line, and should cover the same use cases. Please try it out and provide feedback.
The actual alternative is still being discussed in issue `#4488 <https://github.com/pytest-dev/pytest/issues/4488>`__.
The plan is remove the ``--result-log`` option in pytest 6.0 if ``pytest-reportlog`` proves satisfactory
to all users and is deemed stable. The ``pytest-reportlog`` plugin might even be merged into the core
at some point, depending on the plans for the plugins and number of users using it.
Removed Features

View File

@@ -156,6 +156,8 @@ pytest also introduces new options:
a string! This means that it may not be appropriate to enable globally in
``doctest_optionflags`` in your configuration file.
.. versionadded:: 5.1
Continue on failure
-------------------

View File

@@ -1,7 +1,7 @@
import pytest
@pytest.fixture("session")
@pytest.fixture(scope="session")
def setup(request):
setup = CostlySetup()
yield setup

View File

@@ -499,7 +499,7 @@ The output is as follows:
$ pytest -q -s
Mark(name='my_marker', args=(<function hello_world at 0xdeadbeef>,), kwargs={})
.
1 passed in 0.01s
1 passed in 0.12s
We can see that the custom marker has its argument set extended with the function ``hello_world``. This is the key difference between creating a custom marker as a callable, which invokes ``__call__`` behind the scenes, and using ``with_args``.
@@ -551,7 +551,7 @@ Let's run this without capturing output and see what we get:
glob args=('class',) kwargs={'x': 2}
glob args=('module',) kwargs={'x': 1}
.
1 passed in 0.02s
1 passed in 0.12s
marking platform specific tests with pytest
--------------------------------------------------------------
@@ -622,7 +622,7 @@ then you will see two tests skipped and two executed tests as expected:
test_plat.py s.s. [100%]
========================= short test summary info ==========================
SKIPPED [2] $REGENDOC_TMPDIR/conftest.py:13: cannot run on platform linux
SKIPPED [2] $REGENDOC_TMPDIR/conftest.py:12: cannot run on platform linux
======================= 2 passed, 2 skipped in 0.12s =======================
Note that if you specify a platform via the marker-command line option like this:

View File

@@ -54,7 +54,7 @@ This means that we only run 2 tests if we do not pass ``--all``:
$ pytest -q test_compute.py
.. [100%]
2 passed in 0.01s
2 passed in 0.12s
We run only two computations, so we see two dots.
let's run the full monty:
@@ -73,7 +73,7 @@ let's run the full monty:
E assert 4 < 4
test_compute.py:4: AssertionError
1 failed, 4 passed in 0.02s
1 failed, 4 passed in 0.12s
As expected when running the full range of ``param1`` values
we'll get an error on the last one.
@@ -343,7 +343,7 @@ And then when we run the test:
E Failed: deliberately failing for demo purposes
test_backends.py:8: Failed
1 failed, 1 passed in 0.02s
1 failed, 1 passed in 0.12s
The first invocation with ``db == "DB1"`` passed while the second with ``db == "DB2"`` failed. Our ``db`` fixture function has instantiated each of the DB values during the setup phase while the ``pytest_generate_tests`` generated two according calls to the ``test_db_initialized`` during the collection phase.
@@ -454,7 +454,7 @@ argument sets to use for each test function. Let's run it:
E assert 1 == 2
test_parametrize.py:21: AssertionError
1 failed, 2 passed in 0.03s
1 failed, 2 passed in 0.12s
Indirect parametrization with multiple fixtures
--------------------------------------------------------------
@@ -475,11 +475,11 @@ Running it results in some skips if we don't have all the python interpreters in
.. code-block:: pytest
. $ pytest -rs -q multipython.py
ssssssssssss...ssssssssssss [100%]
ssssssssssssssssssssssss... [100%]
========================= short test summary info ==========================
SKIPPED [12] $REGENDOC_TMPDIR/CWD/multipython.py:30: 'python3.5' not found
SKIPPED [12] $REGENDOC_TMPDIR/CWD/multipython.py:30: 'python3.7' not found
3 passed, 24 skipped in 0.24s
SKIPPED [12] $REGENDOC_TMPDIR/CWD/multipython.py:29: 'python3.5' not found
SKIPPED [12] $REGENDOC_TMPDIR/CWD/multipython.py:29: 'python3.6' not found
3 passed, 24 skipped in 0.12s
Indirect parametrization of optional implementations/imports
--------------------------------------------------------------------
@@ -547,7 +547,7 @@ If you run this with reporting for skips enabled:
test_module.py .s [100%]
========================= short test summary info ==========================
SKIPPED [1] $REGENDOC_TMPDIR/conftest.py:13: could not import 'opt2': No module named 'opt2'
SKIPPED [1] $REGENDOC_TMPDIR/conftest.py:12: could not import 'opt2': No module named 'opt2'
======================= 1 passed, 1 skipped in 0.12s =======================
You'll see that we don't have an ``opt2`` module and thus the second test run
@@ -678,4 +678,4 @@ Or, if desired, you can ``pip install contextlib2`` and use:
.. code-block:: python
from contextlib2 import ExitStack as does_not_raise
from contextlib2 import nullcontext as does_not_raise

View File

@@ -436,7 +436,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
items = [1, 2, 3]
print("items is {!r}".format(items))
> a, b = items.pop()
E TypeError: 'int' object is not iterable
E TypeError: cannot unpack non-iterable int object
failure_demo.py:181: TypeError
--------------------------- Captured stdout call ---------------------------
@@ -516,7 +516,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
def test_z2_type_error(self):
items = 3
> a, b = items
E TypeError: 'int' object is not iterable
E TypeError: cannot unpack non-iterable int object
failure_demo.py:222: TypeError
______________________ TestMoreErrors.test_startswith ______________________

View File

@@ -65,7 +65,7 @@ Let's run this without supplying our new option:
test_sample.py:6: AssertionError
--------------------------- Captured stdout call ---------------------------
first
1 failed in 0.02s
1 failed in 0.12s
And now with supplying a command line option:
@@ -89,7 +89,7 @@ And now with supplying a command line option:
test_sample.py:6: AssertionError
--------------------------- Captured stdout call ---------------------------
second
1 failed in 0.02s
1 failed in 0.12s
You can see that the command line option arrived in our test. This
completes the basic pattern. However, one often rather wants to process
@@ -261,7 +261,7 @@ Let's run our little function:
E Failed: not configured: 42
test_checkconfig.py:11: Failed
1 failed in 0.02s
1 failed in 0.12s
If you only want to hide certain exceptions, you can set ``__tracebackhide__``
to a callable which gets the ``ExceptionInfo`` object. You can for example use
@@ -300,36 +300,33 @@ behave differently if called from a test. But if you
absolutely must find out if your application code is
running from a test you can do something like this:
.. code-block:: python
# content of your_module.py
_called_from_test = False
.. code-block:: python
# content of conftest.py
def pytest_configure(config):
import sys
your_module._called_from_test = True
sys._called_from_test = True
def pytest_unconfigure(config):
import sys
del sys._called_from_test
and then check for the ``sys._called_from_test`` flag:
and then check for the ``your_module._called_from_test`` flag:
.. code-block:: python
if hasattr(sys, "_called_from_test"):
if your_module._called_from_test:
# called from within a test run
...
else:
# called "normally"
...
accordingly in your application. It's also a good idea
to use your own application module rather than ``sys``
for handling flag.
accordingly in your application.
Adding info to test report header
--------------------------------------------------------------
@@ -445,8 +442,8 @@ Now we can profile which test functions execute the slowest:
========================= slowest 3 test durations =========================
0.30s call test_some_are_slow.py::test_funcslow2
0.20s call test_some_are_slow.py::test_funcslow1
0.10s call test_some_are_slow.py::test_funcfast
0.21s call test_some_are_slow.py::test_funcslow1
0.11s call test_some_are_slow.py::test_funcfast
============================ 3 passed in 0.12s =============================
incremental testing - test steps

View File

@@ -81,4 +81,4 @@ If you run this without output capturing:
.test other
.test_unit1 method called
.
4 passed in 0.01s
4 passed in 0.12s

View File

@@ -9,9 +9,9 @@ pytest fixtures: explicit, modular, scalable
.. _`xUnit`: http://en.wikipedia.org/wiki/XUnit
.. _`purpose of test fixtures`: http://en.wikipedia.org/wiki/Test_fixture#Software
.. _`Dependency injection`: http://en.wikipedia.org/wiki/Dependency_injection
.. _`xUnit`: https://en.wikipedia.org/wiki/XUnit
.. _`purpose of test fixtures`: https://en.wikipedia.org/wiki/Test_fixture#Software
.. _`Dependency injection`: https://en.wikipedia.org/wiki/Dependency_injection
The `purpose of test fixtures`_ is to provide a fixed baseline
upon which tests can reliably and repeatedly execute. pytest fixtures
@@ -301,6 +301,36 @@ are finalized when the last test of a *package* finishes.
Use this new feature sparingly and please make sure to report any issues you find.
.. _dynamic scope:
Dynamic scope
^^^^^^^^^^^^^
.. versionadded:: 5.2
In some cases, you might want to change the scope of the fixture without changing the code.
To do that, pass a callable to ``scope``. The callable must return a string with a valid scope
and will be executed only once - during the fixture definition. It will be called with two
keyword arguments - ``fixture_name`` as a string and ``config`` with a configuration object.
This can be especially useful when dealing with fixtures that need time for setup, like spawning
a docker container. You can use the command-line argument to control the scope of the spawned
containers for different environments. See the example below.
.. code-block:: python
def determine_scope(fixture_name, config):
if config.getoption("--keep-containers"):
return "session"
return "function"
@pytest.fixture(scope=determine_scope)
def docker_container():
yield spawn_container()
Order: Higher-scoped fixtures are instantiated first
----------------------------------------------------
@@ -361,7 +391,7 @@ Let's execute it:
$ pytest -s -q --tb=no
FFteardown smtp
2 failed in 0.79s
2 failed in 0.12s
We see that the ``smtp_connection`` instance is finalized after the two
tests finished execution. Note that if we decorated our fixture
@@ -515,7 +545,7 @@ again, nothing much has changed:
$ pytest -s -q --tb=no
FFfinalizing <smtplib.SMTP object at 0xdeadbeef> (smtp.gmail.com)
2 failed in 0.77s
2 failed in 0.12s
Let's quickly create another test module that actually sets the
server URL in its module namespace:
@@ -692,7 +722,7 @@ So let's just do another run:
test_module.py:13: AssertionError
------------------------- Captured stdout teardown -------------------------
finalizing <smtplib.SMTP object at 0xdeadbeef>
4 failed in 1.69s
4 failed in 0.12s
We see that our two test functions each ran twice, against the different
``smtp_connection`` instances. Note also, that with the ``mail.python.org``
@@ -1043,7 +1073,7 @@ to verify our fixture is activated and the tests pass:
$ pytest -q
.. [100%]
2 passed in 0.01s
2 passed in 0.12s
You can specify multiple fixtures like this:
@@ -1151,7 +1181,7 @@ If we run it, we get two passing tests:
$ pytest -q
.. [100%]
2 passed in 0.01s
2 passed in 0.12s
Here is how autouse fixtures work in other scopes:

View File

@@ -28,7 +28,7 @@ Install ``pytest``
.. code-block:: bash
$ pytest --version
This is pytest version 5.x.y, imported from $PYTHON_PREFIX/lib/python3.6/site-packages/pytest.py
This is pytest version 5.x.y, imported from $PYTHON_PREFIX/lib/python3.7/site-packages/pytest.py
.. _`simpletest`:
@@ -108,7 +108,7 @@ Execute the test function with “quiet” reporting mode:
$ pytest -q test_sysexit.py
. [100%]
1 passed in 0.01s
1 passed in 0.12s
Group multiple tests in a class
--------------------------------------------------------------
@@ -145,7 +145,7 @@ Once you develop multiple tests, you may want to group them into a class. pytest
E + where False = hasattr('hello', 'check')
test_class.py:8: AssertionError
1 failed, 1 passed in 0.02s
1 failed, 1 passed in 0.12s
The first test passed and the second failed. You can easily see the intermediate values in the assertion to help you understand the reason for the failure.
@@ -180,7 +180,7 @@ List the name ``tmpdir`` in the test function signature and ``pytest`` will look
test_tmpdir.py:3: AssertionError
--------------------------- Captured stdout call ---------------------------
PYTEST_TMPDIR/test_needsfiles0
1 failed in 0.02s
1 failed in 0.12s
More info on tmpdir handling is available at :ref:`Temporary directories and files <tmpdir handling>`.

View File

@@ -88,7 +88,7 @@ This has the following benefits:
.. note::
See :ref:`pythonpath` for more information about the difference between calling ``pytest`` and
See :ref:`pytest vs python -m pytest` for more information about the difference between calling ``pytest`` and
``python -m pytest``.
Note that using this scheme your test files must have **unique names**, because

View File

@@ -83,6 +83,39 @@ Changelog
Consult the :ref:`Changelog <changelog>` page for fixes and enhancements of each version.
Support pytest
--------------
`Open Collective`_ is an online funding platform for open and transparent communities.
It provide tools to raise money and share your finances in full transparency.
It is the platform of choice for individuals and companies that want to make one-time or
monthly donations directly to the project.
See more datails in the `pytest collective`_.
.. _Open Collective: https://opencollective.com
.. _pytest collective: https://opencollective.com/pytest
pytest for enterprise
---------------------
Available as part of the Tidelift Subscription.
The maintainers of pytest and thousands of other packages are working with Tidelift to deliver commercial support and
maintenance for the open source dependencies you use to build your applications.
Save time, reduce risk, and improve code health, while paying the maintainers of the exact dependencies you use.
`Learn more. <https://tidelift.com/subscription/pkg/pypi-pytest?utm_source=pypi-pytest&utm_medium=referral&utm_campaign=enterprise&utm_term=repo>`_
Security
^^^^^^^^
pytest has never been associated with a security vunerability, but in any case, to report a
security vulnerability please use the `Tidelift security contact <https://tidelift.com/security>`_.
Tidelift will coordinate the fix and disclosure.
License
-------

View File

@@ -161,7 +161,7 @@ the records for the ``setup`` and ``call`` stages during teardown like so:
yield window
for when in ("setup", "call"):
messages = [
x.message for x in caplog.get_records(when) if x.level == logging.WARNING
x.message for x in caplog.get_records(when) if x.levelno == logging.WARNING
]
if messages:
pytest.fail(

View File

@@ -205,7 +205,7 @@ If we now pass two stringinput values, our test will run twice:
$ pytest -q --stringinput="hello" --stringinput="world" test_strings.py
.. [100%]
2 passed in 0.01s
2 passed in 0.12s
Let's also run with a stringinput that will lead to a failing test:
@@ -225,7 +225,7 @@ Let's also run with a stringinput that will lead to a failing test:
E + where <built-in method isalpha of str object at 0xdeadbeef> = '!'.isalpha
test_strings.py:4: AssertionError
1 failed in 0.02s
1 failed in 0.12s
As expected our test function fails.
@@ -239,7 +239,7 @@ list:
s [100%]
========================= short test summary info ==========================
SKIPPED [1] test_strings.py: got empty parameter set ['stringinput'], function test_valid_string at $REGENDOC_TMPDIR/test_strings.py:2
1 skipped in 0.00s
1 skipped in 0.12s
Note that when calling ``metafunc.parametrize`` multiple times with different parameter sets, all parameter names across
those sets cannot be duplicated, otherwise an error will be raised.

View File

@@ -38,7 +38,8 @@ Here are some examples of projects using ``pytest`` (please send notes via :ref:
* `execnet <http://codespeak.net/execnet>`_ rapid multi-Python deployment
* `pylib <https://pylib.readthedocs.io/en/stable/>`_ cross-platform path, IO, dynamic code library
* `bbfreeze <https://pypi.org/project/bbfreeze/>`_ create standalone executables from Python scripts
* `pdb++ <http://bitbucket.org/antocuni/pdb>`_ a fancier version of PDB
* `pdb++ <https://github.com/pdbpp/pdbpp>`_ a fancier version of PDB
* `pudb <https://github.com/inducer/pudb>`_ full-screen console debugger for python
* `py-s3fuse <http://code.google.com/p/py-s3fuse/>`_ Amazon S3 FUSE based filesystem
* `waskr <http://code.google.com/p/waskr/>`_ WSGI Stats Middleware
* `guachi <http://code.google.com/p/guachi/>`_ global persistent configs for Python modules
@@ -72,7 +73,6 @@ Some organisations using pytest
* `Square Kilometre Array, Cape Town <http://ska.ac.za/>`_
* `Some Mozilla QA people <http://www.theautomatedtester.co.uk/blog/2011/pytest_and_xdist_plugin.html>`_ use pytest to distribute their Selenium tests
* `Tandberg <http://www.tandberg.com/>`_
* `Shootq <http://web.shootq.com/>`_
* `Stups department of Heinrich Heine University Duesseldorf <http://www.stups.uni-duesseldorf.de/projects.php>`_
* cellzome

View File

@@ -24,3 +24,8 @@ branch will continue to exist so the community itself can contribute patches. Th
be happy to accept those patches and make new ``4.6`` releases **until mid-2020**.
.. _`python_requires`: https://packaging.python.org/guides/distributing-packages-using-setuptools/#python-requires
Technical Aspects
-----------------
The technical aspects of the Python 2.7 and 3.4 support plan (such as when releases will occurr, how to backport fixes, etc) is described in issue `#5275 <https://github.com/pytest-dev/pytest/issues/5275>`__.

View File

@@ -72,6 +72,8 @@ imported in the global import namespace.
This is also discussed in details in :ref:`test discovery`.
.. _`pytest vs python -m pytest`:
Invoking ``pytest`` versus ``python -m pytest``
-----------------------------------------------

View File

@@ -59,7 +59,7 @@ pytest.raises
**Tutorial**: :ref:`assertraises`.
.. autofunction:: pytest.raises(expected_exception: Exception, [match], [message])
.. autofunction:: pytest.raises(expected_exception: Exception [, *, match])
:with: excinfo
pytest.deprecated_call
@@ -1003,7 +1003,7 @@ passed multiple times. The expected format is ``name=value``. For example::
[pytest]
addopts = --maxfail=2 -rf # exit after 2 failures, report fail info
issuing ``pytest test_hello.py`` actually means::
issuing ``pytest test_hello.py`` actually means:
.. code-block:: bash
@@ -1192,6 +1192,38 @@ passed multiple times. The expected format is ``name=value``. For example::
[pytest]
junit_suite_name = my_suite
.. confval:: log_auto_indent
Allow selective auto-indentation of multiline log messages.
Supports command line option ``--log-auto-indent [value]``
and config option ``log_auto_indent = [value]`` to set the
auto-indentation behavior for all logging.
``[value]`` can be:
* True or "On" - Dynamically auto-indent multiline log messages
* False or "Off" or 0 - Do not auto-indent multiline log messages (the default behavior)
* [positive integer] - auto-indent multiline log messages by [value] spaces
.. code-block:: ini
[pytest]
log_auto_indent = False
Supports passing kwarg ``extra={"auto_indent": [value]}`` to
calls to ``logging.log()`` to specify auto-indentation behavior for
a specific entry in the log. ``extra`` kwarg overrides the value specified
on the command line or in the config.
.. confval:: log_cli
Enable log display during test run (also known as :ref:`"live logging" <live_logs>`).
The default is ``False``.
.. code-block:: ini
[pytest]
log_cli = True
.. confval:: log_cli_date_format

View File

@@ -8,18 +8,6 @@ compensation when possible is welcome to justify time away from friends, family
Money is also used to fund local sprints, merchandising (stickers to distribute in conferences for example)
and every few years a large sprint involving all members.
If you or your company benefit from pytest and would like to contribute to the project financially,
we are members of two online donation platforms to better suit your needs.
Tidelift
--------
`Tidelift`_ aims to make Open Source sustainable by offering subscriptions to companies which rely
on Open Source packages. This subscription allows it to pay maintainers of those Open Source
packages to aid sustainability of the work.
You can help pytest and the ecosystem by obtaining a `Tidelift subscription`_.
OpenCollective
--------------
@@ -32,7 +20,6 @@ monthly donations directly to the project.
See more datails in the `pytest collective`_.
.. _Tidelift: https://tidelift.com
.. _Tidelift subscription: https://tidelift.com/subscription/pkg/pypi-pytest
.. _Open Collective: https://opencollective.com

View File

@@ -2,10 +2,6 @@
Talks and Tutorials
==========================
.. sidebar:: Next Open Trainings
- `3 day hands-on workshop covering pytest, tox and devpi: "Professional Testing with Python" <https://python-academy.com/courses/specialtopics/python_course_testing.html>`_ (English), October 21 - 23, 2019, Leipzig, Germany.
.. _`funcargs`: funcargs.html
Books
@@ -68,7 +64,7 @@ Talks and blog postings
- `pytest introduction from Brian Okken (January 2013)
<http://pythontesting.net/framework/pytest-introduction/>`_
- pycon australia 2012 pytest talk from Brianna Laugher (`video <http://www.youtube.com/watch?v=DTNejE9EraI>`_, `slides <http://www.slideshare.net/pfctdayelise/funcargs-other-fun-with-pytest>`_, `code <https://gist.github.com/3386951>`_)
- pycon australia 2012 pytest talk from Brianna Laugher (`video <http://www.youtube.com/watch?v=DTNejE9EraI>`_, `slides <https://www.slideshare.net/pfctdayelise/funcargs-other-fun-with-pytest>`_, `code <https://gist.github.com/3386951>`_)
- `pycon 2012 US talk video from Holger Krekel <http://www.youtube.com/watch?v=9LVqBQcFmyw>`_
- `monkey patching done right`_ (blog post, consult `monkeypatch plugin`_ for up-to-date API)

45
doc/en/tidelift.rst Normal file
View File

@@ -0,0 +1,45 @@
pytest for enterprise
=====================
`Tidelift`_ is working with the maintainers of pytest and thousands of other
open source projects to deliver commercial support and maintenance for the open source dependencies you use
to build your applications. Save time, reduce risk, and improve code health, while paying the maintainers of the
exact dependencies you use.
`Get more details <https://tidelift.com/subscription/pkg/pypi-pytest?utm_source=pypi-pytest&utm_medium=referral&utm_campaign=enterprise>`_
The Tidelift Subscription is a managed open source subscription for application dependencies covering millions of open source projects across JavaScript, Python, Java, PHP, Ruby, .NET, and more.
Your subscription includes:
* **Security updates**
- Tidelift's security response team coordinates patches for new breaking security vulnerabilities and alerts immediately through a private channel, so your software supply chain is always secure.
* **Licensing verification and indemnification**
- Tidelift verifies license information to enable easy policy enforcement and adds intellectual property indemnification to cover creators and users in case something goes wrong. You always have a 100% up-to-date bill of materials for your dependencies to share with your legal team, customers, or partners.
* **Maintenance and code improvement**
- Tidelift ensures the software you rely on keeps working as long as you need it to work. Your managed dependencies are actively maintained and we recruit additional maintainers where required.
* **Package selection and version guidance**
- Tidelift helps you choose the best open source packages from the start—and then guide you through updates to stay on the best releases as new issues arise.
* **Roadmap input**
- Take a seat at the table with the creators behind the software you use. Tidelift's participating maintainers earn more income as their software is used by more subscribers, so they're interested in knowing what you need.
* **Tooling and cloud integration**
- Tidelift works with GitHub, GitLab, BitBucket, and every cloud platform (and other deployment targets, too).
The end result? All of the capabilities you expect from commercial-grade software, for the full breadth of open
source you use. That means less time grappling with esoteric open source trivia, and more time building your own
applications—and your business.
`Request a demo <https://tidelift.com/subscription/request-a-demo?utm_source=pypi-pytest&utm_medium=referral&utm_campaign=enterprise>`_
.. _Tidelift: https://tidelift.com

View File

@@ -219,7 +219,7 @@ Running this test module ...:
$ pytest -q test_unittest_cleandir.py
. [100%]
1 passed in 0.01s
1 passed in 0.12s
... gives us one passed test because the ``initdir`` fixture function
was executed ahead of the ``test_method``.

View File

@@ -66,8 +66,8 @@ To stop the testing process after the first (N) failures:
.. code-block:: bash
pytest -x # stop after first failure
pytest --maxfail=2 # stop after two failures
pytest -x # stop after first failure
pytest --maxfail=2 # stop after two failures
.. _select-tests:
@@ -241,7 +241,7 @@ Example:
test_example.py:14: AssertionError
========================= short test summary info ==========================
SKIPPED [1] $REGENDOC_TMPDIR/test_example.py:23: skipping this test
SKIPPED [1] $REGENDOC_TMPDIR/test_example.py:22: skipping this test
XFAIL test_example.py::test_xfail
reason: xfailing this test
XPASS test_example.py::test_xpass always xfail
@@ -296,7 +296,7 @@ More than one character can be used, so for example to only see failed and skipp
test_example.py:14: AssertionError
========================= short test summary info ==========================
FAILED test_example.py::test_fail - assert 0
SKIPPED [1] $REGENDOC_TMPDIR/test_example.py:23: skipping this test
SKIPPED [1] $REGENDOC_TMPDIR/test_example.py:22: skipping this test
== 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12s ===
Using ``p`` lists the passing tests, whilst ``P`` adds an extra section "PASSES" with those tests that passed but had
@@ -679,12 +679,6 @@ Creating resultlog format files
----------------------------------------------------
This option is rarely used and is scheduled for removal in 5.0.
See `the deprecation docs <https://docs.pytest.org/en/latest/deprecations.html#result-log-result-log>`__
for more information.
To create plain-text machine-readable result files you can issue:
.. code-block:: bash
@@ -694,6 +688,16 @@ To create plain-text machine-readable result files you can issue:
and look at the content at the ``path`` location. Such files are used e.g.
by the `PyPy-test`_ web page to show test results over several revisions.
.. warning::
This option is rarely used and is scheduled for removal in pytest 6.0.
If you use this option, consider using the new `pytest-reportlog <https://github.com/pytest-dev/pytest-reportlog>`__ plugin instead.
See `the deprecation docs <https://docs.pytest.org/en/latest/deprecations.html#result-log-result-log>`__
for more information.
.. _`PyPy-test`: http://buildbot.pypy.org/summary
@@ -718,6 +722,11 @@ for example ``-x`` if you only want to send one particular failure.
Currently only pasting to the http://bpaste.net service is implemented.
.. versionchanged:: 5.2
If creating the URL fails for any reason, a warning is generated instead of failing the
entire test suite.
Early loading plugins
---------------------

View File

@@ -41,7 +41,7 @@ Running pytest now produces this output:
warnings.warn(UserWarning("api v1, should use functions from v2"))
-- Docs: https://docs.pytest.org/en/latest/warnings.html
====================== 1 passed, 1 warnings in 0.12s =======================
======================= 1 passed, 1 warning in 0.12s =======================
The ``-W`` flag can be passed to control which warnings will be displayed or even turn
them into errors:
@@ -64,7 +64,7 @@ them into errors:
E UserWarning: api v1, should use functions from v2
test_show_warnings.py:5: UserWarning
1 failed in 0.02s
1 failed in 0.12s
The same option can be set in the ``pytest.ini`` file using the ``filterwarnings`` ini option.
For example, the configuration below will ignore all user warnings, but will transform
@@ -407,7 +407,7 @@ defines an ``__init__`` constructor, as this prevents the class from being insta
class Test:
-- Docs: https://docs.pytest.org/en/latest/warnings.html
1 warnings in 0.00s
1 warning in 0.12s
These warnings might be filtered using the same builtin mechanisms used to filter other types of warnings.

View File

@@ -442,7 +442,7 @@ additionally it is possible to copy examples for an example folder before runnin
testdir.copy_example("test_example.py")
-- Docs: https://docs.pytest.org/en/latest/warnings.html
====================== 2 passed, 1 warnings in 0.12s =======================
======================= 2 passed, 1 warning in 0.12s =======================
For more information about the result object that ``runpytest()`` returns, and
the methods that it provides please check out the :py:class:`RunResult
@@ -677,6 +677,56 @@ Example:
print(config.hook)
.. _`addoptionhooks`:
Using hooks in pytest_addoption
-------------------------------
Occasionally, it is necessary to change the way in which command line options
are defined by one plugin based on hooks in another plugin. For example,
a plugin may expose a command line option for which another plugin needs
to define the default value. The pluginmanager can be used to install and
use hooks to accomplish this. The plugin would define and add the hooks
and use pytest_addoption as follows:
.. code-block:: python
# contents of hooks.py
# Use firstresult=True because we only want one plugin to define this
# default value
@hookspec(firstresult=True)
def pytest_config_file_default_value():
""" Return the default value for the config file command line option. """
# contents of myplugin.py
def pytest_addhooks(pluginmanager):
""" This example assumes the hooks are grouped in the 'hooks' module. """
from . import hook
pluginmanager.add_hookspecs(hook)
def pytest_addoption(parser, pluginmanager):
default_value = pluginmanager.hook.pytest_config_file_default_value()
parser.addoption(
"--config-file",
help="Config file to use, defaults to %(default)s",
default=default_value,
)
The conftest.py that is using myplugin would simply define the hook as follows:
.. code-block:: python
def pytest_config_file_default_value():
return "config.yaml"
Optionally using hooks from 3rd party plugins
---------------------------------------------

View File

@@ -79,12 +79,19 @@ def fix_formatting():
call(["pre-commit", "run", "--all-files"])
def check_links():
"""Runs sphinx-build to check links"""
print(f"{Fore.CYAN}[generate.check_links] {Fore.RESET}Checking links")
check_call(["tox", "-e", "docs-checklinks"])
def pre_release(version):
"""Generates new docs, release announcements and creates a local tag."""
announce(version)
regen()
changelog(version, write_out=True)
fix_formatting()
check_links()
msg = "Preparing release version {}".format(version)
check_call(["git", "commit", "-a", "-m", msg])

View File

@@ -13,4 +13,6 @@ fi
python -m coverage combine
python -m coverage xml
python -m coverage report -m
bash <(curl -s https://codecov.io/bash) -Z -X gcov -X coveragepy -X search -X xcode -X gcovout -X fix -f coverage.xml
# Set --connect-timeout to work around https://github.com/curl/curl/issues/4461
curl -S -L --connect-timeout 5 --retry 6 -s https://codecov.io/bash -o codecov-upload.sh
bash codecov-upload.sh -Z -X fix -f coverage.xml

View File

@@ -57,12 +57,13 @@ upload-dir = doc/en/build/html
[check-manifest]
ignore =
_pytest/_version.py
src/_pytest/_version.py
[devpi:upload]
formats = sdist.tgz,bdist_wheel
[mypy]
mypy_path = src
ignore_missing_imports = True
no_implicit_optional = True
strict_equality = True

View File

@@ -5,9 +5,9 @@ from setuptools import setup
INSTALL_REQUIRES = [
"py>=1.5.0",
"packaging",
"attrs>=17.4.0",
"attrs>=17.4.0", # should match oldattrs tox env.
"more-itertools>=4.0.0",
"atomicwrites>=1.0",
'atomicwrites>=1.0;sys_platform=="win32"',
'pathlib2>=2.2.0;python_version<"3.6"',
'colorama;sys_platform=="win32"',
"pluggy>=0.12,<1.0",

View File

@@ -4,11 +4,21 @@ import sys
import traceback
from inspect import CO_VARARGS
from inspect import CO_VARKEYWORDS
from io import StringIO
from traceback import format_exception_only
from types import CodeType
from types import FrameType
from types import TracebackType
from typing import Any
from typing import Callable
from typing import Dict
from typing import Generic
from typing import Iterable
from typing import List
from typing import Optional
from typing import Pattern
from typing import Sequence
from typing import Set
from typing import Tuple
from typing import TypeVar
from typing import Union
@@ -21,23 +31,29 @@ import py
import _pytest
from _pytest._io.saferepr import safeformat
from _pytest._io.saferepr import saferepr
from _pytest.compat import overload
if False: # TYPE_CHECKING
from typing import Type
from typing_extensions import Literal
from weakref import ReferenceType # noqa: F401
from _pytest._code import Source
_TracebackStyle = Literal["long", "short", "no", "native"]
class Code:
""" wrapper around Python code objects """
def __init__(self, rawcode):
def __init__(self, rawcode) -> None:
if not hasattr(rawcode, "co_filename"):
rawcode = getrawcode(rawcode)
try:
self.filename = rawcode.co_filename
self.firstlineno = rawcode.co_firstlineno - 1
self.name = rawcode.co_name
except AttributeError:
if not isinstance(rawcode, CodeType):
raise TypeError("not a code object: {!r}".format(rawcode))
self.filename = rawcode.co_filename
self.firstlineno = rawcode.co_firstlineno - 1
self.name = rawcode.co_name
self.raw = rawcode
def __eq__(self, other):
@@ -66,7 +82,7 @@ class Code:
return p
@property
def fullsource(self):
def fullsource(self) -> Optional["Source"]:
""" return a _pytest._code.Source object for the full source file of the code
"""
from _pytest._code import source
@@ -74,7 +90,7 @@ class Code:
full, _ = source.findsource(self.raw)
return full
def source(self):
def source(self) -> "Source":
""" return a _pytest._code.Source object for the code object's source only
"""
# return source only for that part of code
@@ -82,7 +98,7 @@ class Code:
return _pytest._code.Source(self.raw)
def getargs(self, var=False):
def getargs(self, var: bool = False) -> Tuple[str, ...]:
""" return a tuple with the argument names for the code object
if 'var' is set True also return the names of the variable and
@@ -101,7 +117,7 @@ class Frame:
"""Wrapper around a Python frame holding f_locals and f_globals
in which expressions can be evaluated."""
def __init__(self, frame):
def __init__(self, frame: FrameType) -> None:
self.lineno = frame.f_lineno - 1
self.f_globals = frame.f_globals
self.f_locals = frame.f_locals
@@ -109,7 +125,7 @@ class Frame:
self.code = Code(frame.f_code)
@property
def statement(self):
def statement(self) -> "Source":
""" statement this frame is at """
import _pytest._code
@@ -128,16 +144,16 @@ class Frame:
f_locals.update(vars)
return eval(code, self.f_globals, f_locals)
def exec_(self, code, **vars):
def exec_(self, code, **vars) -> None:
""" exec 'code' in the frame
'vars' are optiona; additional local variables
'vars' are optional; additional local variables
"""
f_locals = self.f_locals.copy()
f_locals.update(vars)
exec(code, self.f_globals, f_locals)
def repr(self, object):
def repr(self, object: object) -> str:
""" return a 'safe' (non-recursive, one-line) string repr for 'object'
"""
return saferepr(object)
@@ -145,7 +161,7 @@ class Frame:
def is_true(self, object):
return object
def getargs(self, var=False):
def getargs(self, var: bool = False):
""" return a list of tuples (name, value) for all arguments
if 'var' is set True also include the variable and keyword
@@ -163,35 +179,34 @@ class Frame:
class TracebackEntry:
""" a single entry in a traceback """
_repr_style = None
_repr_style = None # type: Optional[Literal["short", "long"]]
exprinfo = None
def __init__(self, rawentry, excinfo=None):
def __init__(self, rawentry: TracebackType, excinfo=None) -> None:
self._excinfo = excinfo
self._rawentry = rawentry
self.lineno = rawentry.tb_lineno - 1
def set_repr_style(self, mode):
def set_repr_style(self, mode: "Literal['short', 'long']") -> None:
assert mode in ("short", "long")
self._repr_style = mode
@property
def frame(self):
import _pytest._code
return _pytest._code.Frame(self._rawentry.tb_frame)
def frame(self) -> Frame:
return Frame(self._rawentry.tb_frame)
@property
def relline(self):
def relline(self) -> int:
return self.lineno - self.frame.code.firstlineno
def __repr__(self):
def __repr__(self) -> str:
return "<TracebackEntry %s:%d>" % (self.frame.code.path, self.lineno + 1)
@property
def statement(self):
def statement(self) -> "Source":
""" _pytest._code.Source object for the current statement """
source = self.frame.code.fullsource
assert source is not None
return source.getstatement(self.lineno)
@property
@@ -200,14 +215,14 @@ class TracebackEntry:
return self.frame.code.path
@property
def locals(self):
""" locals of underlaying frame """
def locals(self) -> Dict[str, Any]:
""" locals of underlying frame """
return self.frame.f_locals
def getfirstlinesource(self):
def getfirstlinesource(self) -> int:
return self.frame.code.firstlineno
def getsource(self, astcache=None):
def getsource(self, astcache=None) -> Optional["Source"]:
""" return failing source code. """
# we use the passed in astcache to not reparse asttrees
# within exception info printing
@@ -252,7 +267,7 @@ class TracebackEntry:
return tbh(None if self._excinfo is None else self._excinfo())
return tbh
def __str__(self):
def __str__(self) -> str:
try:
fn = str(self.path)
except py.error.Error:
@@ -267,36 +282,45 @@ class TracebackEntry:
return " File %r:%d in %s\n %s\n" % (fn, self.lineno + 1, name, line)
@property
def name(self):
""" co_name of underlaying code """
def name(self) -> str:
""" co_name of underlying code """
return self.frame.code.raw.co_name
class Traceback(list):
class Traceback(List[TracebackEntry]):
""" Traceback objects encapsulate and offer higher level
access to Traceback entries.
"""
Entry = TracebackEntry
def __init__(self, tb, excinfo=None):
def __init__(
self,
tb: Union[TracebackType, Iterable[TracebackEntry]],
excinfo: Optional["ReferenceType[ExceptionInfo]"] = None,
) -> None:
""" initialize from given python traceback object and ExceptionInfo """
self._excinfo = excinfo
if hasattr(tb, "tb_next"):
if isinstance(tb, TracebackType):
def f(cur):
while cur is not None:
yield self.Entry(cur, excinfo=excinfo)
cur = cur.tb_next
def f(cur: TracebackType) -> Iterable[TracebackEntry]:
cur_ = cur # type: Optional[TracebackType]
while cur_ is not None:
yield TracebackEntry(cur_, excinfo=excinfo)
cur_ = cur_.tb_next
list.__init__(self, f(tb))
super().__init__(f(tb))
else:
list.__init__(self, tb)
super().__init__(tb)
def cut(self, path=None, lineno=None, firstlineno=None, excludepath=None):
def cut(
self,
path=None,
lineno: Optional[int] = None,
firstlineno: Optional[int] = None,
excludepath=None,
) -> "Traceback":
""" return a Traceback instance wrapping part of this Traceback
by provding any combination of path, lineno and firstlineno, the
by providing any combination of path, lineno and firstlineno, the
first frame to start the to-be-returned traceback is determined
this allows cutting the first part of a Traceback instance e.g.
@@ -319,13 +343,25 @@ class Traceback(list):
return Traceback(x._rawentry, self._excinfo)
return self
def __getitem__(self, key):
val = super().__getitem__(key)
if isinstance(key, type(slice(0))):
val = self.__class__(val)
return val
@overload
def __getitem__(self, key: int) -> TracebackEntry:
raise NotImplementedError()
def filter(self, fn=lambda x: not x.ishidden()):
@overload # noqa: F811
def __getitem__(self, key: slice) -> "Traceback": # noqa: F811
raise NotImplementedError()
def __getitem__( # noqa: F811
self, key: Union[int, slice]
) -> Union[TracebackEntry, "Traceback"]:
if isinstance(key, slice):
return self.__class__(super().__getitem__(key))
else:
return super().__getitem__(key)
def filter(
self, fn: Callable[[TracebackEntry], bool] = lambda x: not x.ishidden()
) -> "Traceback":
""" return a Traceback instance with certain items removed
fn is a function that gets a single argument, a TracebackEntry
@@ -337,7 +373,7 @@ class Traceback(list):
"""
return Traceback(filter(fn, self), self._excinfo)
def getcrashentry(self):
def getcrashentry(self) -> TracebackEntry:
""" return last non-hidden traceback entry that lead
to the exception of a traceback.
"""
@@ -347,11 +383,11 @@ class Traceback(list):
return entry
return self[-1]
def recursionindex(self):
def recursionindex(self) -> Optional[int]:
""" return the index of the frame/TracebackEntry where recursion
originates if appropriate, None if no recursion occurred
"""
cache = {}
cache = {} # type: Dict[Tuple[Any, int, int], List[Dict[str, Any]]]
for i, entry in enumerate(self):
# id for the code.raw is needed to work around
# the strange metaprogramming in the decorator lib from pypi
@@ -443,7 +479,7 @@ class ExceptionInfo(Generic[_E]):
assert tup[1] is not None, "no current exception"
assert tup[2] is not None, "no current exception"
exc_info = (tup[0], tup[1], tup[2])
return cls.from_exc_info(exc_info)
return cls.from_exc_info(exc_info, exprinfo)
@classmethod
def for_later(cls) -> "ExceptionInfo[_E]":
@@ -502,7 +538,9 @@ class ExceptionInfo(Generic[_E]):
def __repr__(self) -> str:
if self._excinfo is None:
return "<ExceptionInfo for raises contextmanager>"
return "<ExceptionInfo %s tblen=%d>" % (self.typename, len(self.traceback))
return "<{} {} tblen={}>".format(
self.__class__.__name__, saferepr(self._excinfo[1]), len(self.traceback)
)
def exconly(self, tryshort: bool = False) -> str:
""" return the exception as a string
@@ -535,13 +573,13 @@ class ExceptionInfo(Generic[_E]):
def getrepr(
self,
showlocals: bool = False,
style: str = "long",
style: "_TracebackStyle" = "long",
abspath: bool = False,
tbfilter: bool = True,
funcargs: bool = False,
truncate_locals: bool = True,
chain: bool = True,
):
) -> Union["ReprExceptionInfo", "ExceptionChainRepr"]:
"""
Return str()able representation of this exception info.
@@ -613,16 +651,16 @@ class FormattedExcinfo:
flow_marker = ">"
fail_marker = "E"
showlocals = attr.ib(default=False)
style = attr.ib(default="long")
abspath = attr.ib(default=True)
tbfilter = attr.ib(default=True)
funcargs = attr.ib(default=False)
truncate_locals = attr.ib(default=True)
chain = attr.ib(default=True)
showlocals = attr.ib(type=bool, default=False)
style = attr.ib(type="_TracebackStyle", default="long")
abspath = attr.ib(type=bool, default=True)
tbfilter = attr.ib(type=bool, default=True)
funcargs = attr.ib(type=bool, default=False)
truncate_locals = attr.ib(type=bool, default=True)
chain = attr.ib(type=bool, default=True)
astcache = attr.ib(default=attr.Factory(dict), init=False, repr=False)
def _getindent(self, source):
def _getindent(self, source: "Source") -> int:
# figure out indent for given source
try:
s = str(source.getstatement(len(source) - 1))
@@ -637,20 +675,27 @@ class FormattedExcinfo:
return 0
return 4 + (len(s) - len(s.lstrip()))
def _getentrysource(self, entry):
def _getentrysource(self, entry: TracebackEntry) -> Optional["Source"]:
source = entry.getsource(self.astcache)
if source is not None:
source = source.deindent()
return source
def repr_args(self, entry):
def repr_args(self, entry: TracebackEntry) -> Optional["ReprFuncArgs"]:
if self.funcargs:
args = []
for argname, argvalue in entry.frame.getargs(var=True):
args.append((argname, saferepr(argvalue)))
return ReprFuncArgs(args)
return None
def get_source(self, source, line_index=-1, excinfo=None, short=False):
def get_source(
self,
source: "Source",
line_index: int = -1,
excinfo: Optional[ExceptionInfo] = None,
short: bool = False,
) -> List[str]:
""" return formatted and marked up source lines. """
import _pytest._code
@@ -674,19 +719,21 @@ class FormattedExcinfo:
lines.extend(self.get_exconly(excinfo, indent=indent, markall=True))
return lines
def get_exconly(self, excinfo, indent=4, markall=False):
def get_exconly(
self, excinfo: ExceptionInfo, indent: int = 4, markall: bool = False
) -> List[str]:
lines = []
indent = " " * indent
indentstr = " " * indent
# get the real exception information out
exlines = excinfo.exconly(tryshort=True).split("\n")
failindent = self.fail_marker + indent[1:]
failindent = self.fail_marker + indentstr[1:]
for line in exlines:
lines.append(failindent + line)
if not markall:
failindent = indent
failindent = indentstr
return lines
def repr_locals(self, locals):
def repr_locals(self, locals: Dict[str, object]) -> Optional["ReprLocals"]:
if self.showlocals:
lines = []
keys = [loc for loc in locals if loc[0] != "@"]
@@ -711,8 +758,11 @@ class FormattedExcinfo:
# # XXX
# pprint.pprint(value, stream=self.excinfowriter)
return ReprLocals(lines)
return None
def repr_traceback_entry(self, entry, excinfo=None):
def repr_traceback_entry(
self, entry: TracebackEntry, excinfo: Optional[ExceptionInfo] = None
) -> "ReprEntry":
import _pytest._code
source = self._getentrysource(entry)
@@ -722,10 +772,8 @@ class FormattedExcinfo:
else:
line_index = entry.lineno - entry.getfirstlinesource()
lines = []
style = entry._repr_style
if style is None:
style = self.style
lines = [] # type: List[str]
style = entry._repr_style if entry._repr_style is not None else self.style
if style in ("short", "long"):
short = style == "short"
reprargs = self.repr_args(entry) if not short else None
@@ -755,7 +803,7 @@ class FormattedExcinfo:
path = np
return path
def repr_traceback(self, excinfo):
def repr_traceback(self, excinfo: ExceptionInfo) -> "ReprTraceback":
traceback = excinfo.traceback
if self.tbfilter:
traceback = traceback.filter()
@@ -773,7 +821,9 @@ class FormattedExcinfo:
entries.append(reprentry)
return ReprTraceback(entries, extraline, style=self.style)
def _truncate_recursive_traceback(self, traceback):
def _truncate_recursive_traceback(
self, traceback: Traceback
) -> Tuple[Traceback, Optional[str]]:
"""
Truncate the given recursive traceback trying to find the starting point
of the recursion.
@@ -799,8 +849,10 @@ class FormattedExcinfo:
exc_msg=str(e),
max_frames=max_frames,
total=len(traceback),
)
traceback = traceback[:max_frames] + traceback[-max_frames:]
) # type: Optional[str]
# Type ignored because adding two instaces of a List subtype
# currently incorrectly has type List instead of the subtype.
traceback = traceback[:max_frames] + traceback[-max_frames:] # type: ignore
else:
if recursionindex is not None:
extraline = "!!! Recursion detected (same locals & position)"
@@ -810,17 +862,19 @@ class FormattedExcinfo:
return traceback, extraline
def repr_excinfo(self, excinfo):
repr_chain = []
def repr_excinfo(self, excinfo: ExceptionInfo) -> "ExceptionChainRepr":
repr_chain = (
[]
) # type: List[Tuple[ReprTraceback, Optional[ReprFileLocation], Optional[str]]]
e = excinfo.value
excinfo_ = excinfo # type: Optional[ExceptionInfo]
descr = None
seen = set()
seen = set() # type: Set[int]
while e is not None and id(e) not in seen:
seen.add(id(e))
if excinfo:
reprtraceback = self.repr_traceback(excinfo)
reprcrash = excinfo._getreprcrash()
if excinfo_:
reprtraceback = self.repr_traceback(excinfo_)
reprcrash = excinfo_._getreprcrash() # type: Optional[ReprFileLocation]
else:
# fallback to native repr if the exception doesn't have a traceback:
# ExceptionInfo objects require a full traceback to work
@@ -832,7 +886,7 @@ class FormattedExcinfo:
repr_chain += [(reprtraceback, reprcrash, descr)]
if e.__cause__ is not None and self.chain:
e = e.__cause__
excinfo = (
excinfo_ = (
ExceptionInfo((type(e), e, e.__traceback__))
if e.__traceback__
else None
@@ -842,7 +896,7 @@ class FormattedExcinfo:
e.__context__ is not None and not e.__suppress_context__ and self.chain
):
e = e.__context__
excinfo = (
excinfo_ = (
ExceptionInfo((type(e), e, e.__traceback__))
if e.__traceback__
else None
@@ -855,33 +909,41 @@ class FormattedExcinfo:
class TerminalRepr:
def __str__(self):
def __str__(self) -> str:
# FYI this is called from pytest-xdist's serialization of exception
# information.
io = py.io.TextIO()
io = StringIO()
tw = py.io.TerminalWriter(file=io)
self.toterminal(tw)
return io.getvalue().strip()
def __repr__(self):
def __repr__(self) -> str:
return "<{} instance at {:0x}>".format(self.__class__, id(self))
def toterminal(self, tw) -> None:
raise NotImplementedError()
class ExceptionRepr(TerminalRepr):
def __init__(self):
self.sections = []
def __init__(self) -> None:
self.sections = [] # type: List[Tuple[str, str, str]]
def addsection(self, name, content, sep="-"):
def addsection(self, name: str, content: str, sep: str = "-") -> None:
self.sections.append((name, content, sep))
def toterminal(self, tw):
def toterminal(self, tw) -> None:
for name, content, sep in self.sections:
tw.sep(sep, name)
tw.line(content)
class ExceptionChainRepr(ExceptionRepr):
def __init__(self, chain):
def __init__(
self,
chain: Sequence[
Tuple["ReprTraceback", Optional["ReprFileLocation"], Optional[str]]
],
) -> None:
super().__init__()
self.chain = chain
# reprcrash and reprtraceback of the outermost (the newest) exception
@@ -889,7 +951,7 @@ class ExceptionChainRepr(ExceptionRepr):
self.reprtraceback = chain[-1][0]
self.reprcrash = chain[-1][1]
def toterminal(self, tw):
def toterminal(self, tw) -> None:
for element in self.chain:
element[0].toterminal(tw)
if element[2] is not None:
@@ -899,12 +961,14 @@ class ExceptionChainRepr(ExceptionRepr):
class ReprExceptionInfo(ExceptionRepr):
def __init__(self, reprtraceback, reprcrash):
def __init__(
self, reprtraceback: "ReprTraceback", reprcrash: "ReprFileLocation"
) -> None:
super().__init__()
self.reprtraceback = reprtraceback
self.reprcrash = reprcrash
def toterminal(self, tw):
def toterminal(self, tw) -> None:
self.reprtraceback.toterminal(tw)
super().toterminal(tw)
@@ -912,12 +976,17 @@ class ReprExceptionInfo(ExceptionRepr):
class ReprTraceback(TerminalRepr):
entrysep = "_ "
def __init__(self, reprentries, extraline, style):
def __init__(
self,
reprentries: Sequence[Union["ReprEntry", "ReprEntryNative"]],
extraline: Optional[str],
style: "_TracebackStyle",
) -> None:
self.reprentries = reprentries
self.extraline = extraline
self.style = style
def toterminal(self, tw):
def toterminal(self, tw) -> None:
# the entries might have different styles
for i, entry in enumerate(self.reprentries):
if entry.style == "long":
@@ -937,32 +1006,40 @@ class ReprTraceback(TerminalRepr):
class ReprTracebackNative(ReprTraceback):
def __init__(self, tblines):
def __init__(self, tblines: Sequence[str]) -> None:
self.style = "native"
self.reprentries = [ReprEntryNative(tblines)]
self.extraline = None
class ReprEntryNative(TerminalRepr):
style = "native"
style = "native" # type: _TracebackStyle
def __init__(self, tblines):
def __init__(self, tblines: Sequence[str]) -> None:
self.lines = tblines
def toterminal(self, tw):
def toterminal(self, tw) -> None:
tw.write("".join(self.lines))
class ReprEntry(TerminalRepr):
def __init__(self, lines, reprfuncargs, reprlocals, filelocrepr, style):
def __init__(
self,
lines: Sequence[str],
reprfuncargs: Optional["ReprFuncArgs"],
reprlocals: Optional["ReprLocals"],
filelocrepr: Optional["ReprFileLocation"],
style: "_TracebackStyle",
) -> None:
self.lines = lines
self.reprfuncargs = reprfuncargs
self.reprlocals = reprlocals
self.reprfileloc = filelocrepr
self.style = style
def toterminal(self, tw):
def toterminal(self, tw) -> None:
if self.style == "short":
assert self.reprfileloc is not None
self.reprfileloc.toterminal(tw)
for line in self.lines:
red = line.startswith("E ")
@@ -981,21 +1058,21 @@ class ReprEntry(TerminalRepr):
tw.line("")
self.reprfileloc.toterminal(tw)
def __str__(self):
def __str__(self) -> str:
return "{}\n{}\n{}".format(
"\n".join(self.lines), self.reprlocals, self.reprfileloc
)
class ReprFileLocation(TerminalRepr):
def __init__(self, path, lineno, message):
def __init__(self, path, lineno: int, message: str) -> None:
self.path = str(path)
self.lineno = lineno
self.message = message
def toterminal(self, tw):
def toterminal(self, tw) -> None:
# filename and lineno output for each entry,
# using an output format that most editors unterstand
# using an output format that most editors understand
msg = self.message
i = msg.find("\n")
if i != -1:
@@ -1005,19 +1082,19 @@ class ReprFileLocation(TerminalRepr):
class ReprLocals(TerminalRepr):
def __init__(self, lines):
def __init__(self, lines: Sequence[str]) -> None:
self.lines = lines
def toterminal(self, tw):
def toterminal(self, tw) -> None:
for line in self.lines:
tw.line(line)
class ReprFuncArgs(TerminalRepr):
def __init__(self, args):
def __init__(self, args: Sequence[Tuple[str, object]]) -> None:
self.args = args
def toterminal(self, tw):
def toterminal(self, tw) -> None:
if self.args:
linesofar = ""
for name, value in self.args:
@@ -1036,13 +1113,11 @@ class ReprFuncArgs(TerminalRepr):
tw.line("")
def getrawcode(obj, trycall=True):
def getrawcode(obj, trycall: bool = True):
""" return code object for given function. """
try:
return obj.__code__
except AttributeError:
obj = getattr(obj, "im_func", obj)
obj = getattr(obj, "func_code", obj)
obj = getattr(obj, "f_code", obj)
obj = getattr(obj, "__code__", obj)
if trycall and not hasattr(obj, "co_firstlineno"):
@@ -1066,7 +1141,7 @@ _PYTEST_DIR = py.path.local(_pytest.__file__).dirpath()
_PY_DIR = py.path.local(py.__file__).dirpath()
def filter_traceback(entry):
def filter_traceback(entry: TracebackEntry) -> bool:
"""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.

View File

@@ -7,9 +7,18 @@ import tokenize
import warnings
from ast import PyCF_ONLY_AST as _AST_FLAG
from bisect import bisect_right
from types import FrameType
from typing import Iterator
from typing import List
from typing import Optional
from typing import Sequence
from typing import Tuple
from typing import Union
import py
from _pytest.compat import overload
class Source:
""" an immutable object holding a source code fragment,
@@ -18,12 +27,12 @@ class Source:
_compilecounter = 0
def __init__(self, *parts, **kwargs):
self.lines = lines = []
def __init__(self, *parts, **kwargs) -> None:
self.lines = lines = [] # type: List[str]
de = kwargs.get("deindent", True)
for part in parts:
if not part:
partlines = []
partlines = [] # type: List[str]
elif isinstance(part, Source):
partlines = part.lines
elif isinstance(part, (tuple, list)):
@@ -47,7 +56,15 @@ class Source:
# Ignore type because of https://github.com/python/mypy/issues/4266.
__hash__ = None # type: ignore
def __getitem__(self, key):
@overload
def __getitem__(self, key: int) -> str:
raise NotImplementedError()
@overload # noqa: F811
def __getitem__(self, key: slice) -> "Source": # noqa: F811
raise NotImplementedError()
def __getitem__(self, key: Union[int, slice]) -> Union[str, "Source"]: # noqa: F811
if isinstance(key, int):
return self.lines[key]
else:
@@ -57,10 +74,13 @@ class Source:
newsource.lines = self.lines[key.start : key.stop]
return newsource
def __len__(self):
def __iter__(self) -> Iterator[str]:
return iter(self.lines)
def __len__(self) -> int:
return len(self.lines)
def strip(self):
def strip(self) -> "Source":
""" return new source object with trailing
and leading blank lines removed.
"""
@@ -73,18 +93,20 @@ class Source:
source.lines[:] = self.lines[start:end]
return source
def putaround(self, before="", after="", indent=" " * 4):
def putaround(
self, before: str = "", after: str = "", indent: str = " " * 4
) -> "Source":
""" return a copy of the source object with
'before' and 'after' wrapped around it.
"""
before = Source(before)
after = Source(after)
beforesource = Source(before)
aftersource = Source(after)
newsource = Source()
lines = [(indent + line) for line in self.lines]
newsource.lines = before.lines + lines + after.lines
newsource.lines = beforesource.lines + lines + aftersource.lines
return newsource
def indent(self, indent=" " * 4):
def indent(self, indent: str = " " * 4) -> "Source":
""" return a copy of the source object with
all lines indented by the given indent-string.
"""
@@ -92,14 +114,14 @@ class Source:
newsource.lines = [(indent + line) for line in self.lines]
return newsource
def getstatement(self, lineno):
def getstatement(self, lineno: int) -> "Source":
""" return Source statement which contains the
given linenumber (counted from 0).
"""
start, end = self.getstatementrange(lineno)
return self[start:end]
def getstatementrange(self, lineno):
def getstatementrange(self, lineno: int):
""" return (start, end) tuple which spans the minimal
statement region which containing the given lineno.
"""
@@ -108,13 +130,13 @@ class Source:
ast, start, end = getstatementrange_ast(lineno, self)
return start, end
def deindent(self):
def deindent(self) -> "Source":
"""return a new source object deindented."""
newsource = Source()
newsource.lines[:] = deindent(self.lines)
return newsource
def isparseable(self, deindent=True):
def isparseable(self, deindent: bool = True) -> bool:
""" return True if source is parseable, heuristically
deindenting it by default.
"""
@@ -134,11 +156,16 @@ class Source:
else:
return True
def __str__(self):
def __str__(self) -> str:
return "\n".join(self.lines)
def compile(
self, filename=None, mode="exec", flag=0, dont_inherit=0, _genframe=None
self,
filename=None,
mode="exec",
flag: int = 0,
dont_inherit: int = 0,
_genframe: Optional[FrameType] = None,
):
""" return compiled code object. if filename is None
invent an artificial filename which displays
@@ -157,8 +184,7 @@ class Source:
source = "\n".join(self.lines) + "\n"
try:
co = compile(source, filename, mode, flag)
except SyntaxError:
ex = sys.exc_info()[1]
except SyntaxError as ex:
# re-represent syntax errors from parsing python strings
msglines = self.lines[: ex.lineno]
if ex.offset:
@@ -173,7 +199,8 @@ class Source:
if flag & _AST_FLAG:
return co
lines = [(x + "\n") for x in self.lines]
linecache.cache[filename] = (1, None, lines, filename)
# Type ignored because linecache.cache is private.
linecache.cache[filename] = (1, None, lines, filename) # type: ignore
return co
@@ -182,7 +209,7 @@ class Source:
#
def compile_(source, filename=None, mode="exec", flags=0, dont_inherit=0):
def compile_(source, filename=None, mode="exec", flags: int = 0, dont_inherit: int = 0):
""" compile the given source to a raw code object,
and maintain an internal cache which allows later
retrieval of the source code for the code object
@@ -232,7 +259,7 @@ def getfslineno(obj):
#
def findsource(obj):
def findsource(obj) -> Tuple[Optional[Source], int]:
try:
sourcelines, lineno = inspect.findsource(obj)
except Exception:
@@ -242,7 +269,7 @@ def findsource(obj):
return source, lineno
def getsource(obj, **kwargs):
def getsource(obj, **kwargs) -> Source:
from .code import getrawcode
obj = getrawcode(obj)
@@ -254,21 +281,21 @@ def getsource(obj, **kwargs):
return Source(strsrc, **kwargs)
def deindent(lines):
def deindent(lines: Sequence[str]) -> List[str]:
return textwrap.dedent("\n".join(lines)).splitlines()
def get_statement_startend2(lineno, node):
def get_statement_startend2(lineno: int, node: ast.AST) -> Tuple[int, Optional[int]]:
import ast
# flatten all statements and except handlers into one lineno-list
# AST's line numbers start indexing at 1
values = []
values = [] # type: List[int]
for x in ast.walk(node):
if isinstance(x, (ast.stmt, ast.ExceptHandler)):
values.append(x.lineno - 1)
for name in ("finalbody", "orelse"):
val = getattr(x, name, None)
val = getattr(x, name, None) # type: Optional[List[ast.stmt]]
if val:
# treat the finally/orelse part as its own statement
values.append(val[0].lineno - 1 - 1)
@@ -282,7 +309,12 @@ def get_statement_startend2(lineno, node):
return start, end
def getstatementrange_ast(lineno, source, assertion=False, astnode=None):
def getstatementrange_ast(
lineno: int,
source: Source,
assertion: bool = False,
astnode: Optional[ast.AST] = None,
) -> Tuple[ast.AST, int, int]:
if astnode is None:
content = str(source)
# See #4260:

View File

@@ -1,19 +1,30 @@
import pprint
import reprlib
from typing import Any
def _format_repr_exception(exc, obj):
exc_name = type(exc).__name__
def _try_repr_or_str(obj):
try:
exc_info = str(exc)
except Exception:
exc_info = "unknown"
return '<[{}("{}") raised in repr()] {} object at 0x{:x}>'.format(
exc_name, exc_info, obj.__class__.__name__, id(obj)
return repr(obj)
except (KeyboardInterrupt, SystemExit):
raise
except BaseException:
return '{}("{}")'.format(type(obj).__name__, obj)
def _format_repr_exception(exc: BaseException, obj: Any) -> str:
try:
exc_info = _try_repr_or_str(exc)
except (KeyboardInterrupt, SystemExit):
raise
except BaseException as exc:
exc_info = "unpresentable exception ({})".format(_try_repr_or_str(exc))
return "<[{} raised in repr()] {} object at 0x{:x}>".format(
exc_info, obj.__class__.__name__, id(obj)
)
def _ellipsize(s, maxsize):
def _ellipsize(s: str, maxsize: int) -> str:
if len(s) > maxsize:
i = max(0, (maxsize - 3) // 2)
j = max(0, maxsize - 3 - i)
@@ -26,27 +37,31 @@ class SafeRepr(reprlib.Repr):
and includes information on exceptions raised during the call.
"""
def __init__(self, maxsize):
def __init__(self, maxsize: int) -> None:
super().__init__()
self.maxstring = maxsize
self.maxsize = maxsize
def repr(self, x):
def repr(self, x: Any) -> str:
try:
s = super().repr(x)
except Exception as exc:
except (KeyboardInterrupt, SystemExit):
raise
except BaseException as exc:
s = _format_repr_exception(exc, x)
return _ellipsize(s, self.maxsize)
def repr_instance(self, x, level):
def repr_instance(self, x: Any, level: int) -> str:
try:
s = repr(x)
except Exception as exc:
except (KeyboardInterrupt, SystemExit):
raise
except BaseException as exc:
s = _format_repr_exception(exc, x)
return _ellipsize(s, self.maxsize)
def safeformat(obj):
def safeformat(obj: Any) -> str:
"""return a pretty printed string for the given object.
Failing __repr__ functions of user instances will be represented
with a short exception info.
@@ -57,7 +72,7 @@ def safeformat(obj):
return _format_repr_exception(exc, obj)
def saferepr(obj, maxsize=240):
def saferepr(obj: Any, maxsize: int = 240) -> str:
"""return a size-limited safe repr-string for the given object.
Failing __repr__ functions of user instances will be represented
with a short exception info and 'saferepr' generally takes

View File

@@ -2,6 +2,7 @@
support for presenting detailed information in failing assertions.
"""
import sys
from typing import Optional
from _pytest.assertion import rewrite
from _pytest.assertion import truncate
@@ -52,7 +53,9 @@ def register_assert_rewrite(*names):
importhook = hook
break
else:
importhook = DummyRewriteHook()
# TODO(typing): Add a protocol for mark_rewrite() and use it
# for importhook and for PytestPluginManager.rewrite_hook.
importhook = DummyRewriteHook() # type: ignore
importhook.mark_rewrite(*names)
@@ -69,7 +72,7 @@ class AssertionState:
def __init__(self, config, mode):
self.mode = mode
self.trace = config.trace.root.get("assertion")
self.hook = None
self.hook = None # type: Optional[rewrite.AssertionRewritingHook]
def install_importhook(config):
@@ -108,6 +111,7 @@ def pytest_runtest_setup(item):
"""
def callbinrepr(op, left, right):
# type: (str, object, object) -> Optional[str]
"""Call the pytest_assertrepr_compare hook and prepare the result
This uses the first result from the hook and then ensures the
@@ -133,12 +137,13 @@ def pytest_runtest_setup(item):
if item.config.getvalue("assertmode") == "rewrite":
res = res.replace("%", "%%")
return res
return None
util._reprcompare = callbinrepr
if item.ihook.pytest_assertion_pass.get_hookimpls():
def call_assertion_pass_hook(lineno, expl, orig):
def call_assertion_pass_hook(lineno, orig, expl):
item.ihook.pytest_assertion_pass(
item=item, lineno=lineno, orig=orig, expl=expl
)
@@ -158,5 +163,5 @@ def pytest_sessionfinish(session):
assertstate.hook.set_session(None)
# Expose this plugin's implementation for the pytest_assertrepr_compare hook
pytest_assertrepr_compare = util.assertrepr_compare
def pytest_assertrepr_compare(config, op, left, right):
return util.assertrepr_compare(config=config, op=op, left=left, right=right)

View File

@@ -2,6 +2,7 @@
import ast
import errno
import functools
import importlib.abc
import importlib.machinery
import importlib.util
import io
@@ -16,8 +17,7 @@ from typing import Dict
from typing import List
from typing import Optional
from typing import Set
import atomicwrites
from typing import Tuple
from _pytest._io.saferepr import saferepr
from _pytest._version import version
@@ -25,16 +25,18 @@ from _pytest.assertion import util
from _pytest.assertion.util import ( # noqa: F401
format_explanation as _format_explanation,
)
from _pytest.compat import fspath
from _pytest.pathlib import fnmatch_ex
from _pytest.pathlib import Path
from _pytest.pathlib import PurePath
# pytest caches rewritten pycs in __pycache__.
# pytest caches rewritten pycs in pycache dirs
PYTEST_TAG = "{}-pytest-{}".format(sys.implementation.cache_tag, version)
PYC_EXT = ".py" + (__debug__ and "c" or "o")
PYC_TAIL = "." + PYTEST_TAG + PYC_EXT
class AssertionRewritingHook:
class AssertionRewritingHook(importlib.abc.MetaPathFinder):
"""PEP302/PEP451 import hook which rewrites asserts."""
def __init__(self, config):
@@ -44,13 +46,13 @@ class AssertionRewritingHook:
except ValueError:
self.fnpats = ["test_*.py", "*_test.py"]
self.session = None
self._rewritten_names = set()
self._must_rewrite = set()
self._rewritten_names = set() # type: Set[str]
self._must_rewrite = set() # type: Set[str]
# flag to guard against trying to rewrite a pyc file while we are already writing another pyc file,
# which might result in infinite recursion (#3506)
self._writing_pyc = False
self._basenames_to_check_rewrite = {"conftest"}
self._marked_for_rewrite_cache = {}
self._marked_for_rewrite_cache = {} # type: Dict[str, bool]
self._session_paths_checked = False
def set_session(self, session):
@@ -76,7 +78,8 @@ class AssertionRewritingHook:
# there's nothing to rewrite there
# python3.5 - python3.6: `namespace`
# python3.7+: `None`
or spec.origin in {None, "namespace"}
or spec.origin == "namespace"
or spec.origin is None
# we can only rewrite source files
or not isinstance(spec.loader, importlib.machinery.SourceFileLoader)
# if the file doesn't exist, we can't rewrite it
@@ -100,7 +103,7 @@ class AssertionRewritingHook:
return None # default behaviour is fine
def exec_module(self, module):
fn = module.__spec__.origin
fn = Path(module.__spec__.origin)
state = self.config._assertstate
self._rewritten_names.add(module.__name__)
@@ -114,15 +117,15 @@ class AssertionRewritingHook:
# cached pyc is always a complete, valid pyc. Operations on it must be
# atomic. POSIX's atomic rename comes in handy.
write = not sys.dont_write_bytecode
cache_dir = os.path.join(os.path.dirname(fn), "__pycache__")
cache_dir = get_cache_dir(fn)
if write:
ok = try_mkdir(cache_dir)
ok = try_makedirs(cache_dir)
if not ok:
write = False
state.trace("read only directory: {}".format(os.path.dirname(fn)))
state.trace("read only directory: {}".format(cache_dir))
cache_name = os.path.basename(fn)[:-3] + PYC_TAIL
pyc = os.path.join(cache_dir, cache_name)
cache_name = fn.name[:-3] + PYC_TAIL
pyc = cache_dir / cache_name
# Notice that even if we're in a read-only directory, I'm going
# to check for a cached pyc. This may not be optimal...
co = _read_pyc(fn, pyc, state.trace)
@@ -136,7 +139,7 @@ class AssertionRewritingHook:
finally:
self._writing_pyc = False
else:
state.trace("found cached rewritten pyc for {!r}".format(fn))
state.trace("found cached rewritten pyc for {}".format(fn))
exec(co, module.__dict__)
def _early_rewrite_bailout(self, name, state):
@@ -199,7 +202,7 @@ class AssertionRewritingHook:
return self._is_marked_for_rewrite(name, state)
def _is_marked_for_rewrite(self, name, state):
def _is_marked_for_rewrite(self, name: str, state):
try:
return self._marked_for_rewrite_cache[name]
except KeyError:
@@ -214,7 +217,7 @@ class AssertionRewritingHook:
self._marked_for_rewrite_cache[name] = False
return False
def mark_rewrite(self, *names):
def mark_rewrite(self, *names: str) -> None:
"""Mark import names as needing to be rewritten.
The named module or package as well as any nested modules will
@@ -250,30 +253,64 @@ class AssertionRewritingHook:
return f.read()
def _write_pyc(state, co, source_stat, pyc):
def _write_pyc_fp(fp, source_stat, co):
# Technically, we don't have to have the same pyc format as
# (C)Python, since these "pycs" should never be seen by builtin
# import. However, there's little reason deviate.
try:
with atomicwrites.atomic_write(pyc, mode="wb", overwrite=True) as fp:
fp.write(importlib.util.MAGIC_NUMBER)
# as of now, bytecode header expects 32-bit numbers for size and mtime (#4903)
mtime = int(source_stat.st_mtime) & 0xFFFFFFFF
size = source_stat.st_size & 0xFFFFFFFF
# "<LL" stands for 2 unsigned longs, little-ending
fp.write(struct.pack("<LL", mtime, size))
fp.write(marshal.dumps(co))
except EnvironmentError as e:
state.trace("error writing pyc file at {}: errno={}".format(pyc, e.errno))
# we ignore any failure to write the cache file
# there are many reasons, permission-denied, __pycache__ being a
# file etc.
return False
return True
fp.write(importlib.util.MAGIC_NUMBER)
# as of now, bytecode header expects 32-bit numbers for size and mtime (#4903)
mtime = int(source_stat.st_mtime) & 0xFFFFFFFF
size = source_stat.st_size & 0xFFFFFFFF
# "<LL" stands for 2 unsigned longs, little-ending
fp.write(struct.pack("<LL", mtime, size))
fp.write(marshal.dumps(co))
if sys.platform == "win32":
from atomicwrites import atomic_write
def _write_pyc(state, co, source_stat, pyc):
try:
with atomic_write(fspath(pyc), mode="wb", overwrite=True) as fp:
_write_pyc_fp(fp, source_stat, co)
except EnvironmentError as e:
state.trace("error writing pyc file at {}: errno={}".format(pyc, e.errno))
# we ignore any failure to write the cache file
# there are many reasons, permission-denied, pycache dir being a
# file etc.
return False
return True
else:
def _write_pyc(state, co, source_stat, pyc):
proc_pyc = "{}.{}".format(pyc, os.getpid())
try:
fp = open(proc_pyc, "wb")
except EnvironmentError as e:
state.trace(
"error writing pyc file at {}: errno={}".format(proc_pyc, e.errno)
)
return False
try:
_write_pyc_fp(fp, source_stat, co)
os.rename(proc_pyc, fspath(pyc))
except BaseException as e:
state.trace("error writing pyc file at {}: errno={}".format(pyc, e.errno))
# we ignore any failure to write the cache file
# there are many reasons, permission-denied, pycache dir being a
# file etc.
return False
finally:
fp.close()
return True
def _rewrite_test(fn, config):
"""read and rewrite *fn* and return the code object."""
fn = fspath(fn)
stat = os.stat(fn)
with open(fn, "rb") as f:
source = f.read()
@@ -289,12 +326,12 @@ def _read_pyc(source, pyc, trace=lambda x: None):
Return rewritten code if successful or None if not.
"""
try:
fp = open(pyc, "rb")
fp = open(fspath(pyc), "rb")
except IOError:
return None
with fp:
try:
stat_result = os.stat(source)
stat_result = os.stat(fspath(source))
mtime = int(stat_result.st_mtime)
size = stat_result.st_size
data = fp.read(12)
@@ -381,6 +418,7 @@ def _format_boolop(explanations, is_or):
def _call_reprcompare(ops, results, expls, each_obj):
# type: (Tuple[str, ...], Tuple[bool, ...], Tuple[str, ...], Tuple[object, ...]) -> str
for i, res, expl in zip(range(len(ops)), results, expls):
try:
done = not res
@@ -396,11 +434,13 @@ def _call_reprcompare(ops, results, expls, each_obj):
def _call_assertion_pass(lineno, orig, expl):
# type: (int, str, str) -> None
if util._assertion_pass is not None:
util._assertion_pass(lineno=lineno, orig=orig, expl=expl)
util._assertion_pass(lineno, orig, expl)
def _check_if_assertion_pass_impl():
# type: () -> bool
"""Checks if any plugins implement the pytest_assertion_pass hook
in order not to generate explanation unecessarily (might be expensive)"""
return True if util._assertion_pass else False
@@ -574,7 +614,7 @@ class AssertionRewriter(ast.NodeVisitor):
def _assert_expr_to_lineno(self):
return _get_assertion_exprs(self.source)
def run(self, mod):
def run(self, mod: ast.Module) -> None:
"""Find all assert statements in *mod* and rewrite them."""
if not mod.body:
# Nothing to do.
@@ -616,12 +656,12 @@ class AssertionRewriter(ast.NodeVisitor):
]
mod.body[pos:pos] = imports
# Collect asserts.
nodes = [mod]
nodes = [mod] # type: List[ast.AST]
while nodes:
node = nodes.pop()
for name, field in ast.iter_fields(node):
if isinstance(field, list):
new = []
new = [] # type: List
for i, child in enumerate(field):
if isinstance(child, ast.Assert):
# Transform assert.
@@ -695,7 +735,7 @@ class AssertionRewriter(ast.NodeVisitor):
.explanation_param().
"""
self.explanation_specifiers = {}
self.explanation_specifiers = {} # type: Dict[str, ast.expr]
self.stack.append(self.explanation_specifiers)
def pop_format_context(self, expl_expr):
@@ -743,19 +783,19 @@ class AssertionRewriter(ast.NodeVisitor):
"assertion is always true, perhaps remove parentheses?"
),
category=None,
filename=self.module_path,
filename=fspath(self.module_path),
lineno=assert_.lineno,
)
self.statements = []
self.variables = []
self.statements = [] # type: List[ast.stmt]
self.variables = [] # type: List[str]
self.variable_counter = itertools.count()
if self.enable_assertion_pass_hook:
self.format_variables = []
self.format_variables = [] # type: List[str]
self.stack = []
self.expl_stmts = []
self.stack = [] # type: List[Dict[str, ast.expr]]
self.expl_stmts = [] # type: List[ast.stmt]
self.push_format_context()
# Rewrite assert into a bunch of statements.
top_condition, explanation = self.visit(assert_.test)
@@ -767,8 +807,9 @@ class AssertionRewriter(ast.NodeVisitor):
)
)
negation = ast.UnaryOp(ast.Not(), top_condition)
if self.enable_assertion_pass_hook: # Experimental pytest_assertion_pass hook
negation = ast.UnaryOp(ast.Not(), top_condition)
msg = self.pop_format_context(ast.Str(explanation))
# Failed
@@ -820,7 +861,6 @@ class AssertionRewriter(ast.NodeVisitor):
else: # Original assertion rewriting
# Create failure message.
body = self.expl_stmts
negation = ast.UnaryOp(ast.Not(), top_condition)
self.statements.append(ast.If(negation, body, []))
if assert_.msg:
assertmsg = self.helper("_format_assertmsg", assert_.msg)
@@ -866,7 +906,7 @@ warn_explicit(
lineno={lineno},
)
""".format(
filename=module_path, lineno=lineno
filename=fspath(module_path), lineno=lineno
)
).body
return ast.If(val_is_none, send_warning, [])
@@ -893,7 +933,7 @@ warn_explicit(
# Process each operand, short-circuiting if needed.
for i, v in enumerate(boolop.values):
if i:
fail_inner = []
fail_inner = [] # type: List[ast.stmt]
# cond is set in a prior loop iteration below
self.expl_stmts.append(ast.If(cond, fail_inner, [])) # noqa
self.expl_stmts = fail_inner
@@ -904,10 +944,10 @@ warn_explicit(
call = ast.Call(app, [expl_format], [])
self.expl_stmts.append(ast.Expr(call))
if i < levels:
cond = res
cond = res # type: ast.expr
if is_or:
cond = ast.UnaryOp(ast.Not(), cond)
inner = []
inner = [] # type: List[ast.stmt]
self.statements.append(ast.If(cond, inner, []))
self.statements = body = inner
self.statements = save
@@ -973,7 +1013,7 @@ warn_explicit(
expl = pat % (res_expl, res_expl, value_expl, attr.attr)
return res, expl
def visit_Compare(self, comp):
def visit_Compare(self, comp: ast.Compare):
self.push_format_context()
left_res, left_expl = self.visit(comp.left)
if isinstance(comp.left, (ast.Compare, ast.BoolOp)):
@@ -1006,24 +1046,21 @@ warn_explicit(
ast.Tuple(results, ast.Load()),
)
if len(comp.ops) > 1:
res = ast.BoolOp(ast.And(), load_names)
res = ast.BoolOp(ast.And(), load_names) # type: ast.expr
else:
res = load_names[0]
return res, self.explanation_param(self.pop_format_context(expl_call))
def try_mkdir(cache_dir):
"""Attempts to create the given directory, returns True if successful"""
def try_makedirs(cache_dir) -> bool:
"""Attempts to create the given directory and sub-directories exist, returns True if
successful or it already exists"""
try:
os.mkdir(cache_dir)
except FileExistsError:
# Either the __pycache__ directory already exists (the
# common case) or it's blocked by a non-dir node. In the
# latter case, we'll ignore it in _write_pyc.
return True
except (FileNotFoundError, NotADirectoryError):
# One of the path components was not a directory, likely
# because we're in a zip file.
os.makedirs(fspath(cache_dir), exist_ok=True)
except (FileNotFoundError, NotADirectoryError, FileExistsError):
# One of the path components was not a directory:
# - we're in a zip file
# - it is a file
return False
except PermissionError:
return False
@@ -1033,3 +1070,18 @@ def try_mkdir(cache_dir):
return False
raise
return True
def get_cache_dir(file_path: Path) -> Path:
"""Returns the cache directory to write .pyc files for the given .py file path"""
# Type ignored until added in next mypy release.
if sys.version_info >= (3, 8) and sys.pycache_prefix: # type: ignore
# given:
# prefix = '/tmp/pycs'
# path = '/home/user/proj/test_app.py'
# we want:
# '/tmp/pycs/home/user/proj'
return Path(sys.pycache_prefix) / Path(*file_path.parts[1:-1]) # type: ignore
else:
# classic pycache directory
return file_path.parent / "__pycache__"

View File

@@ -1,23 +1,55 @@
"""Utilities for assertion debugging"""
import collections.abc
import pprint
from collections.abc import Sequence
from typing import AbstractSet
from typing import Any
from typing import Callable
from typing import Iterable
from typing import List
from typing import Mapping
from typing import Optional
from typing import Sequence
from typing import Tuple
import _pytest._code
from _pytest import outcomes
from _pytest._io.saferepr import safeformat
from _pytest._io.saferepr import saferepr
from _pytest.compat import ATTRS_EQ_FIELD
# The _reprcompare attribute on the util module is used by the new assertion
# interpretation code and assertion rewriter to detect this plugin was
# loaded and in turn call the hooks defined here as part of the
# DebugInterpreter.
_reprcompare = None
_reprcompare = None # type: Optional[Callable[[str, object, object], Optional[str]]]
# Works similarly as _reprcompare attribute. Is populated with the hook call
# when pytest_runtest_setup is called.
_assertion_pass = None
_assertion_pass = None # type: Optional[Callable[[int, str, str], None]]
def format_explanation(explanation):
class AlwaysDispatchingPrettyPrinter(pprint.PrettyPrinter):
"""PrettyPrinter that always dispatches (regardless of width)."""
def _format(self, object, stream, indent, allowance, context, level):
p = self._dispatch.get(type(object).__repr__, None)
objid = id(object)
if objid in context or p is None:
return super()._format(object, stream, indent, allowance, context, level)
context[objid] = 1
p(self, object, stream, indent, allowance, context, level + 1)
del context[objid]
def _pformat_dispatch(object, indent=1, width=80, depth=None, *, compact=False):
return AlwaysDispatchingPrettyPrinter(
indent=1, width=80, depth=None, compact=False
).pformat(object)
def format_explanation(explanation: str) -> str:
"""This formats an explanation
Normally all embedded newlines are escaped, however there are
@@ -27,13 +59,12 @@ def format_explanation(explanation):
for when one explanation needs to span multiple lines, e.g. when
displaying diffs.
"""
explanation = explanation
lines = _split_explanation(explanation)
result = _format_lines(lines)
return "\n".join(result)
def _split_explanation(explanation):
def _split_explanation(explanation: str) -> List[str]:
"""Return a list of individual lines in the explanation
This will return a list of lines split on '\n{', '\n}' and '\n~'.
@@ -50,7 +81,7 @@ def _split_explanation(explanation):
return lines
def _format_lines(lines):
def _format_lines(lines: Sequence[str]) -> List[str]:
"""Format the individual lines
This will replace the '{', '}' and '~' characters of our mini
@@ -59,7 +90,7 @@ def _format_lines(lines):
Return a list of formatted lines.
"""
result = lines[:1]
result = list(lines[:1])
stack = [0]
stackcnt = [0]
for line in lines[1:]:
@@ -85,31 +116,31 @@ def _format_lines(lines):
return result
def issequence(x):
return isinstance(x, Sequence) and not isinstance(x, str)
def issequence(x: Any) -> bool:
return isinstance(x, collections.abc.Sequence) and not isinstance(x, str)
def istext(x):
def istext(x: Any) -> bool:
return isinstance(x, str)
def isdict(x):
def isdict(x: Any) -> bool:
return isinstance(x, dict)
def isset(x):
def isset(x: Any) -> bool:
return isinstance(x, (set, frozenset))
def isdatacls(obj):
def isdatacls(obj: Any) -> bool:
return getattr(obj, "__dataclass_fields__", None) is not None
def isattrs(obj):
def isattrs(obj: Any) -> bool:
return getattr(obj, "__attrs_attrs__", None) is not None
def isiterable(obj):
def isiterable(obj: Any) -> bool:
try:
iter(obj)
return not istext(obj)
@@ -117,15 +148,23 @@ def isiterable(obj):
return False
def assertrepr_compare(config, op, left, right):
def assertrepr_compare(config, op: str, left: Any, right: Any) -> Optional[List[str]]:
"""Return specialised explanations for some operators/operands"""
maxsize = (80 - 15 - len(op) - 2) // 2 # 15 chars indentation, 1 space around op
left_repr = saferepr(left, maxsize=maxsize)
right_repr = saferepr(right, maxsize=maxsize)
verbose = config.getoption("verbose")
if verbose > 1:
left_repr = safeformat(left)
right_repr = safeformat(right)
else:
# XXX: "15 chars indentation" is wrong
# ("E AssertionError: assert "); should use term width.
maxsize = (
80 - 15 - len(op) - 2
) // 2 # 15 chars indentation, 1 space around op
left_repr = saferepr(left, maxsize=maxsize)
right_repr = saferepr(right, maxsize=maxsize)
summary = "{} {} {}".format(left_repr, op, right_repr)
verbose = config.getoption("verbose")
explanation = None
try:
if op == "==":
@@ -167,33 +206,16 @@ def assertrepr_compare(config, op, left, right):
return [summary] + explanation
def _diff_text(left, right, verbose=0):
"""Return the explanation for the diff between text or bytes.
def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]:
"""Return the explanation for the diff between text.
Unless --verbose is used this will skip leading and trailing
characters which are identical to keep the diff minimal.
If the input are bytes they will be safely converted to text.
"""
from difflib import ndiff
explanation = []
explanation = [] # type: List[str]
def escape_for_readable_diff(binary_text):
"""
Ensures that the internal string is always valid unicode, converting any bytes safely to valid unicode.
This is done using repr() which then needs post-processing to fix the encompassing quotes and un-escape
newlines and carriage returns (#429).
"""
r = str(repr(binary_text)[1:-1])
r = r.replace(r"\n", "\n")
r = r.replace(r"\r", "\r")
return r
if isinstance(left, bytes):
left = escape_for_readable_diff(left)
if isinstance(right, bytes):
right = escape_for_readable_diff(right)
if verbose < 1:
i = 0 # just in case left or right has zero length
for i in range(min(len(left), len(right))):
@@ -230,19 +252,33 @@ def _diff_text(left, right, verbose=0):
return explanation
def _compare_eq_verbose(left, right):
def _compare_eq_verbose(left: Any, right: Any) -> List[str]:
keepends = True
left_lines = repr(left).splitlines(keepends)
right_lines = repr(right).splitlines(keepends)
explanation = []
explanation = [] # type: List[str]
explanation += ["-" + line for line in left_lines]
explanation += ["+" + line for line in right_lines]
return explanation
def _compare_eq_iterable(left, right, verbose=0):
def _surrounding_parens_on_own_lines(lines: List[str]) -> None:
"""Move opening/closing parenthesis/bracket to own lines."""
opening = lines[0][:1]
if opening in ["(", "[", "{"]:
lines[0] = " " + lines[0][1:]
lines[:] = [opening] + lines
closing = lines[-1][-1:]
if closing in [")", "]", "}"]:
lines[-1] = lines[-1][:-1] + ","
lines[:] = lines + [closing]
def _compare_eq_iterable(
left: Iterable[Any], right: Iterable[Any], verbose: int = 0
) -> List[str]:
if not verbose:
return ["Use -v to get the full diff"]
# dynamic import to speedup pytest
@@ -250,16 +286,30 @@ def _compare_eq_iterable(left, right, verbose=0):
left_formatting = pprint.pformat(left).splitlines()
right_formatting = pprint.pformat(right).splitlines()
# Re-format for different output lengths.
lines_left = len(left_formatting)
lines_right = len(right_formatting)
if lines_left != lines_right:
left_formatting = _pformat_dispatch(left).splitlines()
right_formatting = _pformat_dispatch(right).splitlines()
if lines_left > 1 or lines_right > 1:
_surrounding_parens_on_own_lines(left_formatting)
_surrounding_parens_on_own_lines(right_formatting)
explanation = ["Full diff:"]
explanation.extend(
line.strip() for line in difflib.ndiff(left_formatting, right_formatting)
line.rstrip() for line in difflib.ndiff(left_formatting, right_formatting)
)
return explanation
def _compare_eq_sequence(left, right, verbose=0):
def _compare_eq_sequence(
left: Sequence[Any], right: Sequence[Any], verbose: int = 0
) -> List[str]:
comparing_bytes = isinstance(left, bytes) and isinstance(right, bytes)
explanation = []
explanation = [] # type: List[str]
len_left = len(left)
len_right = len(right)
for i in range(min(len_left, len_right)):
@@ -311,7 +361,9 @@ def _compare_eq_sequence(left, right, verbose=0):
return explanation
def _compare_eq_set(left, right, verbose=0):
def _compare_eq_set(
left: AbstractSet[Any], right: AbstractSet[Any], verbose: int = 0
) -> List[str]:
explanation = []
diff_left = left - right
diff_right = right - left
@@ -326,8 +378,10 @@ def _compare_eq_set(left, right, verbose=0):
return explanation
def _compare_eq_dict(left, right, verbose=0):
explanation = []
def _compare_eq_dict(
left: Mapping[Any, Any], right: Mapping[Any, Any], verbose: int = 0
) -> List[str]:
explanation = [] # type: List[str]
set_left = set(left)
set_right = set(right)
common = set_left.intersection(set_right)
@@ -365,14 +419,21 @@ def _compare_eq_dict(left, right, verbose=0):
return explanation
def _compare_eq_cls(left, right, verbose, type_fns):
def _compare_eq_cls(
left: Any,
right: Any,
verbose: int,
type_fns: Tuple[Callable[[Any], bool], Callable[[Any], bool]],
) -> List[str]:
isdatacls, isattrs = type_fns
if isdatacls(left):
all_fields = left.__dataclass_fields__
fields_to_check = [field for field, info in all_fields.items() if info.compare]
elif isattrs(left):
all_fields = left.__attrs_attrs__
fields_to_check = [field.name for field in all_fields if field.cmp]
fields_to_check = [
field.name for field in all_fields if getattr(field, ATTRS_EQ_FIELD)
]
same = []
diff = []
@@ -397,7 +458,7 @@ def _compare_eq_cls(left, right, verbose, type_fns):
return explanation
def _notin_text(term, text, verbose=0):
def _notin_text(term: str, text: str, verbose: int = 0) -> List[str]:
index = text.find(term)
head = text[:index]
tail = text[index + len(term) :]

View File

@@ -7,6 +7,7 @@ ignores the external pytest-cache
import json
import os
from collections import OrderedDict
from typing import List
import attr
import py
@@ -15,6 +16,9 @@ import pytest
from .pathlib import Path
from .pathlib import resolve_from_str
from .pathlib import rm_rf
from _pytest import nodes
from _pytest.config import Config
from _pytest.main import Session
README_CONTENT = """\
# pytest cache directory #
@@ -121,13 +125,14 @@ class Cache:
return
if not cache_dir_exists_already:
self._ensure_supporting_files()
data = json.dumps(value, indent=2, sort_keys=True)
try:
f = path.open("w")
except (IOError, OSError):
self.warn("cache could not write path {path}", path=path)
else:
with f:
json.dump(value, f, indent=2, sort_keys=True)
f.write(data)
def _ensure_supporting_files(self):
"""Create supporting files in the cache dir that are not really part of the cache."""
@@ -135,7 +140,7 @@ class Cache:
readme_path.write_text(README_CONTENT)
gitignore_path = self._cachedir.joinpath(".gitignore")
msg = "# Created by pytest automatically.\n*"
msg = "# Created by pytest automatically.\n*\n"
gitignore_path.write_text(msg, encoding="UTF-8")
cachedir_tag_path = self._cachedir.joinpath("CACHEDIR.TAG")
@@ -263,10 +268,12 @@ class NFPlugin:
self.active = config.option.newfirst
self.cached_nodeids = config.cache.get("cache/nodeids", [])
def pytest_collection_modifyitems(self, session, config, items):
def pytest_collection_modifyitems(
self, session: Session, config: Config, items: List[nodes.Item]
) -> None:
new_items = OrderedDict() # type: OrderedDict[str, nodes.Item]
if self.active:
new_items = OrderedDict()
other_items = OrderedDict()
other_items = OrderedDict() # type: OrderedDict[str, nodes.Item]
for item in items:
if item.nodeid not in self.cached_nodeids:
new_items[item.nodeid] = item
@@ -276,7 +283,11 @@ class NFPlugin:
items[:] = self._get_increasing_order(
new_items.values()
) + self._get_increasing_order(other_items.values())
self.cached_nodeids = [x.nodeid for x in items if isinstance(x, pytest.Item)]
else:
for item in items:
if item.nodeid not in self.cached_nodeids:
new_items[item.nodeid] = item
self.cached_nodeids.extend(new_items)
def _get_increasing_order(self, items):
return sorted(items, key=lambda item: item.fspath.mtime(), reverse=True)

View File

@@ -12,6 +12,7 @@ from tempfile import TemporaryFile
import pytest
from _pytest.compat import CaptureIO
from _pytest.fixtures import FixtureRequest
patchsysdict = {0: "stdin", 1: "stdout", 2: "stderr"}
@@ -241,13 +242,12 @@ class CaptureManager:
capture_fixtures = {"capfd", "capfdbinary", "capsys", "capsysbinary"}
def _ensure_only_one_capture_fixture(request, name):
fixtures = set(request.fixturenames) & capture_fixtures - {name}
def _ensure_only_one_capture_fixture(request: FixtureRequest, name):
fixtures = sorted(set(request.fixturenames) & capture_fixtures - {name})
if fixtures:
fixtures = sorted(fixtures)
fixtures = fixtures[0] if len(fixtures) == 1 else fixtures
arg = fixtures[0] if len(fixtures) == 1 else fixtures
raise request.raiseerror(
"cannot use {} and {} at the same time".format(fixtures, name)
"cannot use {} and {} at the same time".format(arg, name)
)
@@ -693,17 +693,12 @@ class SysCaptureBinary(SysCapture):
class DontReadFromInput:
"""Temporary stub class. Ideally when stdin is accessed, the
capturing should be turned off, with possibly all data captured
so far sent to the screen. This should be configurable, though,
because in automated test runs it is better to crash than
hang indefinitely.
"""
encoding = None
def read(self, *args):
raise IOError("reading from stdin while output is captured")
raise IOError(
"pytest: reading from stdin while output is captured! Consider using `-s`."
)
readline = read
readlines = read
@@ -789,7 +784,11 @@ def _py36_windowsconsoleio_workaround(stream):
See https://github.com/pytest-dev/py/issues/103
"""
if not sys.platform.startswith("win32") or sys.version_info[:2] < (3, 6):
if (
not sys.platform.startswith("win32")
or sys.version_info[:2] < (3, 6)
or hasattr(sys, "pypy_version_info")
):
return
# bail out if ``stream`` doesn't seem like a proper ``io`` stream (#2666)

View File

@@ -4,12 +4,20 @@ python version compatibility code
import functools
import inspect
import io
import os
import re
import sys
from contextlib import contextmanager
from inspect import Parameter
from inspect import signature
from typing import Any
from typing import Callable
from typing import Generic
from typing import Optional
from typing import overload
from typing import Tuple
from typing import TypeVar
from typing import Union
import attr
import py
@@ -19,6 +27,13 @@ from _pytest._io.saferepr import saferepr
from _pytest.outcomes import fail
from _pytest.outcomes import TEST_OUTCOME
if False: # TYPE_CHECKING
from typing import Type # noqa: F401 (used in type string)
_T = TypeVar("_T")
_S = TypeVar("_S")
NOTSET = object()
@@ -28,12 +43,13 @@ MODULE_NOT_FOUND_ERROR = (
if sys.version_info >= (3, 8):
from importlib import metadata as importlib_metadata # noqa: F401
# Type ignored until next mypy release.
from importlib import metadata as importlib_metadata # type: ignore
else:
import importlib_metadata # noqa: F401
def _format_args(func):
def _format_args(func: Callable[..., Any]) -> str:
return str(signature(func))
@@ -41,12 +57,25 @@ def _format_args(func):
REGEX_TYPE = type(re.compile(""))
def is_generator(func):
if sys.version_info < (3, 6):
def fspath(p):
"""os.fspath replacement, useful to point out when we should replace it by the
real function once we drop py35.
"""
return str(p)
else:
fspath = os.fspath
def is_generator(func: object) -> bool:
genfunc = inspect.isgeneratorfunction(func)
return genfunc and not iscoroutinefunction(func)
def iscoroutinefunction(func):
def iscoroutinefunction(func: object) -> bool:
"""
Return True if func is a coroutine function (a function defined with async
def syntax, and doesn't contain yield), or a function decorated with
@@ -59,7 +88,7 @@ def iscoroutinefunction(func):
return inspect.iscoroutinefunction(func) or getattr(func, "_is_coroutine", False)
def getlocation(function, curdir=None):
def getlocation(function, curdir=None) -> str:
function = get_real_func(function)
fn = py.path.local(inspect.getfile(function))
lineno = function.__code__.co_firstlineno
@@ -68,7 +97,7 @@ def getlocation(function, curdir=None):
return "%s:%d" % (fn, lineno + 1)
def num_mock_patch_args(function):
def num_mock_patch_args(function) -> int:
""" return number of arguments used up by mock arguments (if any) """
patchings = getattr(function, "patchings", None)
if not patchings:
@@ -87,7 +116,13 @@ def num_mock_patch_args(function):
)
def getfuncargnames(function, *, name: str = "", is_method=False, cls=None):
def getfuncargnames(
function: Callable[..., Any],
*,
name: str = "",
is_method: bool = False,
cls: Optional[type] = None
) -> Tuple[str, ...]:
"""Returns the names of a function's mandatory arguments.
This should return the names of all function arguments that:
@@ -155,7 +190,7 @@ else:
from contextlib import nullcontext # noqa
def get_default_arg_names(function):
def get_default_arg_names(function: Callable[..., Any]) -> Tuple[str, ...]:
# Note: this code intentionally mirrors the code at the beginning of getfuncargnames,
# to get the arguments which were excluded from its result because they had default values
return tuple(
@@ -174,18 +209,18 @@ _non_printable_ascii_translate_table.update(
)
def _translate_non_printable(s):
def _translate_non_printable(s: str) -> str:
return s.translate(_non_printable_ascii_translate_table)
STRING_TYPES = bytes, str
def _bytes_to_ascii(val):
def _bytes_to_ascii(val: bytes) -> str:
return val.decode("ascii", "backslashreplace")
def ascii_escaped(val):
def ascii_escaped(val: Union[bytes, str]):
"""If val is pure ascii, returns it as a str(). Otherwise, escapes
bytes objects into a sequence of escaped bytes:
@@ -282,7 +317,7 @@ def getimfunc(func):
return func
def safe_getattr(object, name, default):
def safe_getattr(object: Any, name: str, default: Any) -> Any:
""" Like getattr but return default upon any Exception or any OutcomeException.
Attribute access can potentially fail for 'evil' Python objects.
@@ -296,7 +331,7 @@ def safe_getattr(object, name, default):
return default
def safe_isclass(obj):
def safe_isclass(obj: object) -> bool:
"""Ignore any exception via isinstance on Python 3."""
try:
return inspect.isclass(obj)
@@ -317,40 +352,65 @@ COLLECT_FAKEMODULE_ATTRIBUTES = (
)
def _setup_collect_fakemodule():
def _setup_collect_fakemodule() -> None:
from types import ModuleType
import pytest
pytest.collect = ModuleType("pytest.collect")
pytest.collect.__all__ = [] # used for setns
# Types ignored because the module is created dynamically.
pytest.collect = ModuleType("pytest.collect") # type: ignore
pytest.collect.__all__ = [] # type: ignore # used for setns
for attr_name in COLLECT_FAKEMODULE_ATTRIBUTES:
setattr(pytest.collect, attr_name, getattr(pytest, attr_name))
setattr(pytest.collect, attr_name, getattr(pytest, attr_name)) # type: ignore
class CaptureIO(io.TextIOWrapper):
def __init__(self):
def __init__(self) -> None:
super().__init__(io.BytesIO(), encoding="UTF-8", newline="", write_through=True)
def getvalue(self):
def getvalue(self) -> str:
assert isinstance(self.buffer, io.BytesIO)
return self.buffer.getvalue().decode("UTF-8")
class FuncargnamesCompatAttr:
""" helper class so that Metafunc, Function and FixtureRequest
don't need to each define the "funcargnames" compatibility attribute.
"""
@property
def funcargnames(self):
""" alias attribute for ``fixturenames`` for pre-2.3 compatibility"""
import warnings
from _pytest.deprecated import FUNCARGNAMES
warnings.warn(FUNCARGNAMES, stacklevel=2)
return self.fixturenames
if sys.version_info < (3, 5, 2): # pragma: no cover
def overload(f): # noqa: F811
return f
if getattr(attr, "__version_info__", ()) >= (19, 2):
ATTRS_EQ_FIELD = "eq"
else:
ATTRS_EQ_FIELD = "cmp"
if sys.version_info >= (3, 8):
# TODO: Remove type ignore on next mypy update.
# https://github.com/python/typeshed/commit/add0b5e930a1db16560fde45a3b710eefc625709
from functools import cached_property # type: ignore
else:
class cached_property(Generic[_S, _T]):
__slots__ = ("func", "__doc__")
def __init__(self, func: Callable[[_S], _T]) -> None:
self.func = func
self.__doc__ = func.__doc__
@overload
def __get__(
self, instance: None, owner: Optional["Type[_S]"] = ...
) -> "cached_property[_S, _T]":
raise NotImplementedError()
@overload # noqa: F811
def __get__( # noqa: F811
self, instance: _S, owner: Optional["Type[_S]"] = ...
) -> _T:
raise NotImplementedError()
def __get__(self, instance, owner=None): # noqa: F811
if instance is None:
return self
value = instance.__dict__[self.func.__name__] = self.func(instance)
return value

View File

@@ -8,7 +8,16 @@ import sys
import types
import warnings
from functools import lru_cache
from pathlib import Path
from types import TracebackType
from typing import Any
from typing import Callable
from typing import Dict
from typing import List
from typing import Optional
from typing import Sequence
from typing import Set
from typing import Tuple
from typing import Union
import attr
import py
@@ -30,9 +39,13 @@ from _pytest._code import filter_traceback
from _pytest.compat import importlib_metadata
from _pytest.outcomes import fail
from _pytest.outcomes import Skipped
from _pytest.pathlib import unique_path
from _pytest.pathlib import Path
from _pytest.warning_types import PytestConfigWarning
if False: # TYPE_CHECKING
from typing import Type
hookimpl = HookimplMarker("pytest")
hookspec = HookspecMarker("pytest")
@@ -41,10 +54,10 @@ class ConftestImportFailure(Exception):
def __init__(self, path, excinfo):
Exception.__init__(self, path, excinfo)
self.path = path
self.excinfo = excinfo
self.excinfo = excinfo # type: Tuple[Type[Exception], Exception, TracebackType]
def main(args=None, plugins=None):
def main(args=None, plugins=None) -> "Union[int, _pytest.main.ExitCode]":
""" return exit code, after performing an in-process test run.
:arg args: list of command line arguments.
@@ -72,10 +85,16 @@ def main(args=None, plugins=None):
formatted_tb = str(exc_repr)
for line in formatted_tb.splitlines():
tw.line(line.rstrip(), red=True)
return 4
return ExitCode.USAGE_ERROR
else:
try:
return config.hook.pytest_cmdline_main(config=config)
ret = config.hook.pytest_cmdline_main(
config=config
) # type: Union[ExitCode, int]
try:
return ExitCode(ret)
except ValueError:
return ret
finally:
config._ensure_unconfigure()
except UsageError as e:
@@ -112,13 +131,13 @@ def directory_arg(path, optname):
# Plugins that cannot be disabled via "-p no:X" currently.
essential_plugins = ( # fmt: off
essential_plugins = (
"mark",
"main",
"runner",
"fixtures",
"helpconfig", # Provides -p.
) # fmt: on
)
default_plugins = essential_plugins + (
"python",
@@ -157,7 +176,7 @@ def get_config(args=None, plugins=None):
config = Config(
pluginmanager,
invocation_params=Config.InvocationParams(
args=args, plugins=plugins, dir=Path().resolve()
args=args or (), plugins=plugins, dir=Path().resolve()
),
)
@@ -183,7 +202,6 @@ def get_plugin_manager():
def _prepareconfig(args=None, plugins=None):
warning = None
if args is None:
args = sys.argv[1:]
elif isinstance(args, py.path.local):
@@ -201,10 +219,6 @@ def _prepareconfig(args=None, plugins=None):
pluginmanager.consider_pluginarg(plugin)
else:
pluginmanager.register(plugin)
if warning:
from _pytest.warnings import _issue_warning_captured
_issue_warning_captured(warning, hook=config.hook, stacklevel=4)
return pluginmanager.hook.pytest_cmdline_parse(
pluginmanager=pluginmanager, args=args
)
@@ -238,14 +252,18 @@ class PytestPluginManager(PluginManager):
def __init__(self):
super().__init__("pytest")
self._conftest_plugins = set()
# The objects are module objects, only used generically.
self._conftest_plugins = set() # type: Set[object]
# state related to local conftest plugins
self._dirpath2confmods = {}
self._conftestpath2mod = {}
# Maps a py.path.local to a list of module objects.
self._dirpath2confmods = {} # type: Dict[Any, List[object]]
# Maps a py.path.local to a module object.
self._conftestpath2mod = {} # type: Dict[Any, object]
self._confcutdir = None
self._noconftest = False
self._duplicatepaths = set()
# Set of py.path.local's.
self._duplicatepaths = set() # type: Set[Any]
self.add_hookspecs(_pytest.hookspec)
self.register(self)
@@ -367,7 +385,7 @@ class PytestPluginManager(PluginManager):
"""
current = py.path.local()
self._confcutdir = (
unique_path(current.join(namespace.confcutdir, abs=True))
current.join(namespace.confcutdir, abs=True)
if namespace.confcutdir
else None
)
@@ -406,13 +424,11 @@ class PytestPluginManager(PluginManager):
else:
directory = path
directory = unique_path(directory)
# 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():
for parent in directory.realpath().parts():
if self._confcutdir and self._confcutdir.relto(parent):
continue
conftestpath = parent.join("conftest.py")
@@ -432,12 +448,14 @@ class PytestPluginManager(PluginManager):
raise KeyError(name)
def _importconftest(self, conftestpath):
# Use realpath to avoid loading the same conftest twice
# Use a resolved Path object as key to avoid loading the same conftest twice
# with build systems that create build directories containing
# symlinks to actual files.
conftestpath = unique_path(conftestpath)
# Using Path().resolve() is better than py.path.realpath because
# it resolves to the correct path/drive in case-insensitive file systems (#5792)
key = Path(str(conftestpath)).resolve()
try:
return self._conftestpath2mod[conftestpath]
return self._conftestpath2mod[key]
except KeyError:
pkgpath = conftestpath.pypkgpath()
if pkgpath is None:
@@ -454,7 +472,7 @@ class PytestPluginManager(PluginManager):
raise ConftestImportFailure(conftestpath, sys.exc_info())
self._conftest_plugins.add(mod)
self._conftestpath2mod[conftestpath] = mod
self._conftestpath2mod[key] = mod
dirpath = conftestpath.dirpath()
if dirpath in self._dirpath2confmods:
for path, mods in self._dirpath2confmods.items():
@@ -638,7 +656,7 @@ class Config:
Contains the following read-only attributes:
* ``args``: list of command-line arguments as passed to ``pytest.main()``.
* ``args``: tuple of command-line arguments as passed to ``pytest.main()``.
* ``plugins``: list of extra plugins, might be None.
* ``dir``: directory where ``pytest.main()`` was invoked from.
"""
@@ -647,17 +665,19 @@ class Config:
class InvocationParams:
"""Holds parameters passed during ``pytest.main()``
.. versionadded:: 5.1
.. note::
Currently the environment variable PYTEST_ADDOPTS is also handled by
pytest implicitly, not being part of the invocation.
Note that the environment variable ``PYTEST_ADDOPTS`` and the ``addopts``
ini option are handled by pytest, not being included in the ``args`` attribute.
Plugins accessing ``InvocationParams`` must be aware of that.
"""
args = attr.ib()
args = attr.ib(converter=tuple)
plugins = attr.ib()
dir = attr.ib()
dir = attr.ib(type=Path)
def __init__(self, pluginmanager, *, invocation_params=None):
from .argparsing import Parser, FILE_OR_DIR
@@ -678,13 +698,15 @@ class Config:
self.pluginmanager = pluginmanager
self.trace = self.pluginmanager.trace.root.get("config")
self.hook = self.pluginmanager.hook
self._inicache = {}
self._override_ini = ()
self._opt2dest = {}
self._cleanup = []
self._inicache = {} # type: Dict[str, Any]
self._override_ini = () # type: Sequence[str]
self._opt2dest = {} # type: Dict[str, str]
self._cleanup = [] # type: List[Callable[[], None]]
self.pluginmanager.register(self, "pytestconfig")
self._configured = False
self.hook.pytest_addoption.call_historic(kwargs=dict(parser=self._parser))
self.hook.pytest_addoption.call_historic(
kwargs=dict(parser=self._parser, pluginmanager=self.pluginmanager)
)
@property
def invocation_dir(self):
@@ -762,7 +784,7 @@ class Config:
@classmethod
def fromdictargs(cls, option_dict, args):
""" constructor useable for subprocesses. """
""" constructor usable for subprocesses. """
config = get_config(args)
config.option.__dict__.update(option_dict)
config.parse(args, addopts=False)
@@ -782,7 +804,7 @@ class Config:
def pytest_load_initial_conftests(self, early_config):
self.pluginmanager._set_initial_conftests(early_config.known_args_namespace)
def _initini(self, args):
def _initini(self, args) -> None:
ns, unknown_args = self._parser.parse_known_and_unknown_args(
args, namespace=copy.copy(self.option)
)
@@ -883,8 +905,7 @@ class Config:
self.hook.pytest_load_initial_conftests(
early_config=self, args=args, parser=self._parser
)
except ConftestImportFailure:
e = sys.exc_info()[1]
except ConftestImportFailure as e:
if ns.help or ns.version:
# we don't want to prevent --help/--version to work
# so just let is pass and print a warning at the end
@@ -921,7 +942,6 @@ class Config:
assert not hasattr(
self, "args"
), "can only parse cmdline args at most once per Config object"
assert self.invocation_params.args == args
self.hook.pytest_addhooks.call_historic(
kwargs=dict(pluginmanager=self.pluginmanager)
)
@@ -950,10 +970,10 @@ class Config:
assert isinstance(x, list)
x.append(line) # modifies the cached list inline
def getini(self, name):
def getini(self, name: str):
""" return configuration value from an :ref:`ini file <inifiles>`. If the
specified name hasn't been registered through a prior
:py:func:`parser.addini <_pytest.config.Parser.addini>`
:py:func:`parser.addini <_pytest.config.argparsing.Parser.addini>`
call (usually from a plugin), a ValueError is raised. """
try:
return self._inicache[name]
@@ -961,7 +981,7 @@ class Config:
self._inicache[name] = val = self._getini(name)
return val
def _getini(self, name):
def _getini(self, name: str) -> Any:
try:
description, type, default = self._parser._inidict[name]
except KeyError:
@@ -1006,7 +1026,7 @@ class Config:
values.append(relroot)
return values
def _get_override_ini_value(self, name):
def _get_override_ini_value(self, name: str) -> Optional[str]:
value = None
# override_ini is a list of "ini=value" options
# always use the last item if multiple values are set for same ini-name,
@@ -1021,7 +1041,7 @@ class Config:
value = user_ini_value
return value
def getoption(self, name, default=notset, skip=False):
def getoption(self, name: str, default=notset, skip: bool = False):
""" return command line option value.
:arg name: name of the option. You may also specify

View File

@@ -2,6 +2,11 @@ import argparse
import sys
import warnings
from gettext import gettext
from typing import Any
from typing import Dict
from typing import List
from typing import Optional
from typing import Tuple
import py
@@ -21,12 +26,12 @@ class Parser:
def __init__(self, usage=None, processopt=None):
self._anonymous = OptionGroup("custom options", parser=self)
self._groups = []
self._groups = [] # type: List[OptionGroup]
self._processopt = processopt
self._usage = usage
self._inidict = {}
self._ininames = []
self.extra_info = {}
self._inidict = {} # type: Dict[str, Tuple[str, Optional[str], Any]]
self._ininames = [] # type: List[str]
self.extra_info = {} # type: Dict[str, Any]
def processoption(self, option):
if self._processopt:
@@ -42,7 +47,7 @@ class Parser:
The returned group object has an ``addoption`` method with the same
signature as :py:func:`parser.addoption
<_pytest.config.Parser.addoption>` but will be shown in the
<_pytest.config.argparsing.Parser.addoption>` but will be shown in the
respective group in the output of ``pytest. --help``.
"""
for group in self._groups:
@@ -80,7 +85,7 @@ class Parser:
args = [str(x) if isinstance(x, py.path.local) else x for x in args]
return self.optparser.parse_args(args, namespace=namespace)
def _getparser(self):
def _getparser(self) -> "MyOptionParser":
from _pytest._argcomplete import filescompleter
optparser = MyOptionParser(self, self.extra_info, prog=self.prog)
@@ -94,7 +99,10 @@ class Parser:
a = option.attrs()
arggroup.add_argument(*n, **a)
# bash like autocompletion for dirs (appending '/')
optparser.add_argument(FILE_OR_DIR, nargs="*").completer = filescompleter
# Type ignored because typeshed doesn't know about argcomplete.
optparser.add_argument( # type: ignore
FILE_OR_DIR, nargs="*"
).completer = filescompleter
return optparser
def parse_setoption(self, args, option, namespace=None):
@@ -103,13 +111,15 @@ class Parser:
setattr(option, name, value)
return getattr(parsedoption, FILE_OR_DIR)
def parse_known_args(self, args, namespace=None):
def parse_known_args(self, args, namespace=None) -> argparse.Namespace:
"""parses and returns a namespace object with known arguments at this
point.
"""
return self.parse_known_and_unknown_args(args, namespace=namespace)[0]
def parse_known_and_unknown_args(self, args, namespace=None):
def parse_known_and_unknown_args(
self, args, namespace=None
) -> Tuple[argparse.Namespace, List[str]]:
"""parses and returns a namespace object with known arguments, and
the remaining arguments unknown at this point.
"""
@@ -163,8 +173,8 @@ class Argument:
def __init__(self, *names, **attrs):
"""store parms in private vars for use in add_argument"""
self._attrs = attrs
self._short_opts = []
self._long_opts = []
self._short_opts = [] # type: List[str]
self._long_opts = [] # type: List[str]
self.dest = attrs.get("dest")
if "%default" in (attrs.get("help") or ""):
warnings.warn(
@@ -268,8 +278,8 @@ class Argument:
)
self._long_opts.append(opt)
def __repr__(self):
args = []
def __repr__(self) -> str:
args = [] # type: List[str]
if self._short_opts:
args += ["_short_opts: " + repr(self._short_opts)]
if self._long_opts:
@@ -286,7 +296,7 @@ class OptionGroup:
def __init__(self, name, description="", parser=None):
self.name = name
self.description = description
self.options = []
self.options = [] # type: List[Argument]
self.parser = parser
def addoption(self, *optnames, **attrs):
@@ -385,7 +395,7 @@ class MyOptionParser(argparse.ArgumentParser):
options = ", ".join(option for _, option, _ in option_tuples)
self.error(msg % {"option": arg_string, "matches": options})
elif len(option_tuples) == 1:
option_tuple, = option_tuples
(option_tuple,) = option_tuples
return option_tuple
if self._negative_number_matcher.match(arg_string):
if not self._has_negative_number_optionals:
@@ -405,6 +415,12 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter):
- cache result on action object as this is called at least 2 times
"""
def __init__(self, *args, **kwargs):
"""Use more accurate terminal width via pylib."""
if "width" not in kwargs:
kwargs["width"] = py.io.get_terminal_width()
super().__init__(*args, **kwargs)
def _format_action_invocation(self, action):
orgstr = argparse.HelpFormatter._format_action_invocation(self, action)
if orgstr and orgstr[0] != "-": # only optional arguments
@@ -421,7 +437,7 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter):
option_map = getattr(action, "map_long_option", {})
if option_map is None:
option_map = {}
short_long = {}
short_long = {} # type: Dict[str, str]
for option in options:
if len(option) == 2 or option[2] == " ":
continue

View File

@@ -1,10 +1,15 @@
import os
from typing import List
from typing import Optional
import py
from .exceptions import UsageError
from _pytest.outcomes import fail
if False:
from . import Config # noqa: F401
def exists(path, ignore=EnvironmentError):
try:
@@ -102,7 +107,12 @@ def get_dirs_from_args(args):
CFG_PYTEST_SECTION = "[pytest] section in {filename} files is no longer supported, change to [tool:pytest] instead."
def determine_setup(inifile, args, rootdir_cmd_arg=None, config=None):
def determine_setup(
inifile: str,
args: List[str],
rootdir_cmd_arg: Optional[str] = None,
config: Optional["Config"] = None,
):
dirs = get_dirs_from_args(args)
if inifile:
iniconfig = py.iniconfig.IniConfig(inifile)

View File

@@ -1,8 +1,7 @@
""" interactive debugging with PDB, the Python Debugger. """
import argparse
import pdb
import functools
import sys
from doctest import UnexpectedException
from _pytest import outcomes
from _pytest.config import hookimpl
@@ -45,6 +44,8 @@ def pytest_addoption(parser):
def pytest_configure(config):
import pdb
if config.getvalue("trace"):
config.pluginmanager.register(PdbTrace(), "pdbtrace")
if config.getvalue("usepdb"):
@@ -87,6 +88,8 @@ class pytestPDB:
@classmethod
def _import_pdb_cls(cls, capman):
if not cls._config:
import pdb
# Happens when using pytest.set_trace outside of a test.
return pdb.Pdb
@@ -113,6 +116,8 @@ class pytestPDB:
"--pdbcls: could not import {!r}: {}".format(value, exc)
)
else:
import pdb
pdb_cls = pdb.Pdb
wrapped_cls = cls._get_pdb_wrapper_class(pdb_cls, capman)
@@ -274,13 +279,16 @@ class PdbTrace:
def _test_pytest_function(pyfuncitem):
_pdb = pytestPDB._init_pdb("runcall")
testfunction = pyfuncitem.obj
pyfuncitem.obj = _pdb.runcall
if "func" in pyfuncitem._fixtureinfo.argnames: # pragma: no branch
raise ValueError("--trace can't be used with a fixture named func!")
pyfuncitem.funcargs["func"] = testfunction
new_list = list(pyfuncitem._fixtureinfo.argnames)
new_list.append("func")
pyfuncitem._fixtureinfo.argnames = tuple(new_list)
# we can't just return `partial(pdb.runcall, testfunction)` because (on
# python < 3.7.4) runcall's first param is `func`, which means we'd get
# an exception if one of the kwargs to testfunction was called `func`
@functools.wraps(testfunction)
def wrapper(*args, **kwargs):
func = functools.partial(testfunction, *args, **kwargs)
_pdb.runcall(func)
pyfuncitem.obj = wrapper
def _enter_pdb(node, excinfo, rep):
@@ -313,6 +321,8 @@ def _enter_pdb(node, excinfo, rep):
def _postmortem_traceback(excinfo):
from doctest import UnexpectedException
if isinstance(excinfo.value, UnexpectedException):
# A doctest.UnexpectedException is not useful for post_mortem.
# Use the underlying exception instead:

View File

@@ -26,6 +26,16 @@ FUNCARGNAMES = PytestDeprecationWarning(
RESULT_LOG = PytestDeprecationWarning(
"--result-log is deprecated and scheduled for removal in pytest 6.0.\n"
"--result-log is deprecated, please try the new pytest-reportlog plugin.\n"
"See https://docs.pytest.org/en/latest/deprecations.html#result-log-result-log for more information."
)
FIXTURE_POSITIONAL_ARGUMENTS = PytestDeprecationWarning(
"Passing arguments to pytest.fixture() as positional arguments is deprecated - pass them "
"as a keyword argument instead."
)
JUNIT_XML_DEFAULT_FAMILY = PytestDeprecationWarning(
"The 'junit_family' default value will change to 'xunit2' in pytest 6.0.\n"
"Add 'junit_family=legacy' to your pytest.ini file to silence this warning and make your suite compatible."
)

View File

@@ -1,12 +1,20 @@
""" discover and run doctests in modules and test files."""
import bdb
import inspect
import platform
import sys
import traceback
import warnings
from contextlib import contextmanager
from typing import Dict
from typing import List
from typing import Optional
from typing import Sequence
from typing import Tuple
from typing import Union
import pytest
from _pytest import outcomes
from _pytest._code.code import ExceptionInfo
from _pytest._code.code import ReprFileLocation
from _pytest._code.code import TerminalRepr
@@ -16,6 +24,10 @@ from _pytest.outcomes import Skipped
from _pytest.python_api import approx
from _pytest.warning_types import PytestWarning
if False: # TYPE_CHECKING
import doctest
from typing import Type
DOCTEST_REPORT_CHOICE_NONE = "none"
DOCTEST_REPORT_CHOICE_CDIFF = "cdiff"
DOCTEST_REPORT_CHOICE_NDIFF = "ndiff"
@@ -32,6 +44,8 @@ DOCTEST_REPORT_CHOICES = (
# Lazy definition of runner class
RUNNER_CLASS = None
# Lazy definition of output checker class
CHECKER_CLASS = None # type: Optional[Type[doctest.OutputChecker]]
def pytest_addoption(parser):
@@ -84,6 +98,12 @@ def pytest_addoption(parser):
)
def pytest_unconfigure():
global RUNNER_CLASS
RUNNER_CLASS = None
def pytest_collect_file(path, parent):
config = parent.config
if path.ext == ".py":
@@ -111,11 +131,12 @@ def _is_doctest(config, path, parent):
class ReprFailDoctest(TerminalRepr):
def __init__(self, reprlocation_lines):
# List of (reprlocation, lines) tuples
def __init__(
self, reprlocation_lines: Sequence[Tuple[ReprFileLocation, Sequence[str]]]
):
self.reprlocation_lines = reprlocation_lines
def toterminal(self, tw):
def toterminal(self, tw) -> None:
for reprlocation, lines in self.reprlocation_lines:
for line in lines:
tw.line(line)
@@ -128,7 +149,7 @@ class MultipleDoctestFailures(Exception):
self.failures = failures
def _init_runner_class():
def _init_runner_class() -> "Type[doctest.DocTestRunner]":
import doctest
class PytestDoctestRunner(doctest.DebugRunner):
@@ -155,6 +176,8 @@ def _init_runner_class():
def report_unexpected_exception(self, out, test, example, exc_info):
if isinstance(exc_info[1], Skipped):
raise exc_info[1]
if isinstance(exc_info[1], bdb.BdbQuit):
outcomes.exit("Quitting debugger")
failure = doctest.UnexpectedException(test, example, exc_info)
if self.continue_on_failure:
out.append(failure)
@@ -164,12 +187,19 @@ def _init_runner_class():
return PytestDoctestRunner
def _get_runner(checker=None, verbose=None, optionflags=0, continue_on_failure=True):
def _get_runner(
checker: Optional["doctest.OutputChecker"] = None,
verbose: Optional[bool] = None,
optionflags: int = 0,
continue_on_failure: bool = True,
) -> "doctest.DocTestRunner":
# We need this in order to do a lazy import on doctest
global RUNNER_CLASS
if RUNNER_CLASS is None:
RUNNER_CLASS = _init_runner_class()
return RUNNER_CLASS(
# Type ignored because the continue_on_failure argument is only defined on
# PytestDoctestRunner, which is lazily defined so can't be used as a type.
return RUNNER_CLASS( # type: ignore
checker=checker,
verbose=verbose,
optionflags=optionflags,
@@ -198,7 +228,7 @@ class DoctestItem(pytest.Item):
def runtest(self):
_check_all_skipped(self.dtest)
self._disable_output_capturing_for_darwin()
failures = []
failures = [] # type: List[doctest.DocTestFailure]
self.runner.run(self.dtest, out=failures)
if failures:
raise MultipleDoctestFailures(failures)
@@ -219,7 +249,9 @@ class DoctestItem(pytest.Item):
def repr_failure(self, excinfo):
import doctest
failures = None
failures = (
None
) # type: Optional[List[Union[doctest.DocTestFailure, doctest.UnexpectedException]]]
if excinfo.errisinstance((doctest.DocTestFailure, doctest.UnexpectedException)):
failures = [excinfo.value]
elif excinfo.errisinstance(MultipleDoctestFailures):
@@ -242,8 +274,10 @@ class DoctestItem(pytest.Item):
self.config.getoption("doctestreport")
)
if lineno is not None:
assert failure.test.docstring is not None
lines = failure.test.docstring.splitlines(False)
# add line numbers to the left of the error message
assert test.lineno is not None
lines = [
"%03d %s" % (i + test.lineno + 1, x)
for (i, x) in enumerate(lines)
@@ -271,11 +305,11 @@ class DoctestItem(pytest.Item):
else:
return super().repr_failure(excinfo)
def reportinfo(self):
def reportinfo(self) -> Tuple[str, int, str]:
return self.fspath, self.dtest.lineno, "[doctest] %s" % self.name
def _get_flag_lookup():
def _get_flag_lookup() -> Dict[str, int]:
import doctest
return dict(
@@ -327,7 +361,7 @@ class DoctestTextfile(pytest.Module):
optionflags = get_optionflags(self)
runner = _get_runner(
verbose=0,
verbose=False,
optionflags=optionflags,
checker=_get_checker(),
continue_on_failure=_get_continue_on_failure(self.config),
@@ -401,12 +435,23 @@ class DoctestModule(pytest.Module):
https://bugs.python.org/issue25532
"""
def _find_lineno(self, obj, source_lines):
"""
Doctest code does not take into account `@property`, this is a hackish way to fix it.
https://bugs.python.org/issue17446
"""
if isinstance(obj, property):
obj = getattr(obj, "fget", obj)
return doctest.DocTestFinder._find_lineno(self, obj, source_lines)
def _find(self, tests, obj, name, module, source_lines, globs, seen):
if _is_mocked(obj):
return
with _patch_unwrap_mock_aware():
doctest.DocTestFinder._find(
# Type ignored because this is a private function.
doctest.DocTestFinder._find( # type: ignore
self, tests, obj, name, module, source_lines, globs, seen
)
@@ -424,7 +469,7 @@ class DoctestModule(pytest.Module):
finder = MockAwareDocTestFinder()
optionflags = get_optionflags(self)
runner = _get_runner(
verbose=0,
verbose=False,
optionflags=optionflags,
checker=_get_checker(),
continue_on_failure=_get_continue_on_failure(self.config),
@@ -453,24 +498,7 @@ def _setup_fixtures(doctest_item):
return fixture_request
def _get_checker():
"""
Returns a doctest.OutputChecker subclass that supports some
additional options:
* ALLOW_UNICODE and ALLOW_BYTES options to ignore u'' and b''
prefixes (respectively) in string literals. Useful when the same
doctest should run in Python 2 and Python 3.
* NUMBER to ignore floating-point differences smaller than the
precision of the literal number in the doctest.
An inner class is used to avoid importing "doctest" at the module
level.
"""
if hasattr(_get_checker, "LiteralsOutputChecker"):
return _get_checker.LiteralsOutputChecker()
def _init_checker_class() -> "Type[doctest.OutputChecker]":
import doctest
import re
@@ -560,11 +588,31 @@ def _get_checker():
offset += w.end() - w.start() - (g.end() - g.start())
return got
_get_checker.LiteralsOutputChecker = LiteralsOutputChecker
return _get_checker.LiteralsOutputChecker()
return LiteralsOutputChecker
def _get_allow_unicode_flag():
def _get_checker() -> "doctest.OutputChecker":
"""
Returns a doctest.OutputChecker subclass that supports some
additional options:
* ALLOW_UNICODE and ALLOW_BYTES options to ignore u'' and b''
prefixes (respectively) in string literals. Useful when the same
doctest should run in Python 2 and Python 3.
* NUMBER to ignore floating-point differences smaller than the
precision of the literal number in the doctest.
An inner class is used to avoid importing "doctest" at the module
level.
"""
global CHECKER_CLASS
if CHECKER_CLASS is None:
CHECKER_CLASS = _init_checker_class()
return CHECKER_CLASS()
def _get_allow_unicode_flag() -> int:
"""
Registers and returns the ALLOW_UNICODE flag.
"""
@@ -573,7 +621,7 @@ def _get_allow_unicode_flag():
return doctest.register_optionflag("ALLOW_UNICODE")
def _get_allow_bytes_flag():
def _get_allow_bytes_flag() -> int:
"""
Registers and returns the ALLOW_BYTES flag.
"""
@@ -582,7 +630,7 @@ def _get_allow_bytes_flag():
return doctest.register_optionflag("ALLOW_BYTES")
def _get_number_flag():
def _get_number_flag() -> int:
"""
Registers and returns the NUMBER flag.
"""
@@ -591,7 +639,7 @@ def _get_number_flag():
return doctest.register_optionflag("NUMBER")
def _get_report_choice(key):
def _get_report_choice(key: str) -> int:
"""
This function returns the actual `doctest` module flag value, we want to do it as late as possible to avoid
importing `doctest` and all its dependencies when parsing options, as it adds overhead and breaks tests.

View File

@@ -2,22 +2,22 @@ import functools
import inspect
import itertools
import sys
import warnings
from collections import defaultdict
from collections import deque
from collections import OrderedDict
from typing import Dict
from typing import List
from typing import Tuple
import attr
import py
import _pytest
from _pytest import nodes
from _pytest._code.code import FormattedExcinfo
from _pytest._code.code import TerminalRepr
from _pytest.compat import _format_args
from _pytest.compat import _PytestWrapper
from _pytest.compat import FuncargnamesCompatAttr
from _pytest.compat import get_real_func
from _pytest.compat import get_real_method
from _pytest.compat import getfslineno
@@ -27,12 +27,16 @@ from _pytest.compat import getlocation
from _pytest.compat import is_generator
from _pytest.compat import NOTSET
from _pytest.compat import safe_getattr
from _pytest.deprecated import FIXTURE_POSITIONAL_ARGUMENTS
from _pytest.deprecated import FUNCARGNAMES
from _pytest.outcomes import fail
from _pytest.outcomes import TEST_OUTCOME
if False: # TYPE_CHECKING
from typing import Type
from _pytest import nodes
@attr.s(frozen=True)
class PseudoFixtureDef:
@@ -58,7 +62,6 @@ def pytest_sessionstart(session):
scopename2class = {} # type: Dict[str, Type[nodes.Node]]
scope2props = dict(session=()) # type: Dict[str, Tuple[str, ...]]
scope2props["package"] = ("fspath",)
scope2props["module"] = ("fspath", "module")
@@ -333,7 +336,7 @@ class FuncFixtureInfo:
self.names_closure[:] = sorted(closure, key=self.names_closure.index)
class FixtureRequest(FuncargnamesCompatAttr):
class FixtureRequest:
""" A request for a fixture from a test or fixture function.
A request object gives access to the requesting test context
@@ -360,6 +363,12 @@ class FixtureRequest(FuncargnamesCompatAttr):
result.extend(set(self._fixture_defs).difference(result))
return result
@property
def funcargnames(self):
""" alias attribute for ``fixturenames`` for pre-2.3 compatibility"""
warnings.warn(FUNCARGNAMES, stacklevel=2)
return self.fixturenames
@property
def node(self):
""" underlying collection node (depends on current request scope)"""
@@ -688,8 +697,8 @@ class FixtureLookupError(LookupError):
self.fixturestack = request._get_fixturestack()
self.msg = msg
def formatrepr(self):
tblines = []
def formatrepr(self) -> "FixtureLookupErrorRepr":
tblines = [] # type: List[str]
addline = tblines.append
stack = [self.request._pyfuncitem.obj]
stack.extend(map(lambda x: x.func, self.fixturestack))
@@ -741,7 +750,7 @@ class FixtureLookupErrorRepr(TerminalRepr):
self.firstlineno = firstlineno
self.argname = argname
def toterminal(self, tw):
def toterminal(self, tw) -> None:
# tw.line("FixtureLookupError: %s" %(self.argname), red=True)
for tbline in self.tblines:
tw.line(tbline.rstrip())
@@ -792,6 +801,25 @@ def _teardown_yield_fixture(fixturefunc, it):
)
def _eval_scope_callable(scope_callable, fixture_name, config):
try:
result = scope_callable(fixture_name=fixture_name, config=config)
except Exception:
raise TypeError(
"Error evaluating {} while defining fixture '{}'.\n"
"Expected a function with the signature (*, fixture_name, config)".format(
scope_callable, fixture_name
)
)
if not isinstance(result, str):
fail(
"Expected {} to return a 'str' while defining fixture '{}', but it returned:\n"
"{!r}".format(scope_callable, fixture_name, result),
pytrace=False,
)
return result
class FixtureDef:
""" A container for a factory definition. """
@@ -811,6 +839,8 @@ class FixtureDef:
self.has_location = baseid is not None
self.func = func
self.argname = argname
if callable(scope):
scope = _eval_scope_callable(scope, argname, fixturemanager.config)
self.scope = scope
self.scopenum = scope2index(
scope or "function",
@@ -995,7 +1025,57 @@ class FixtureFunctionMarker:
return function
def fixture(scope="function", params=None, autouse=False, ids=None, name=None):
FIXTURE_ARGS_ORDER = ("scope", "params", "autouse", "ids", "name")
def _parse_fixture_args(callable_or_scope, *args, **kwargs):
arguments = {
"scope": "function",
"params": None,
"autouse": False,
"ids": None,
"name": None,
}
kwargs = {
key: value for key, value in kwargs.items() if arguments.get(key) != value
}
fixture_function = None
if isinstance(callable_or_scope, str):
args = list(args)
args.insert(0, callable_or_scope)
else:
fixture_function = callable_or_scope
positionals = set()
for positional, argument_name in zip(args, FIXTURE_ARGS_ORDER):
arguments[argument_name] = positional
positionals.add(argument_name)
duplicated_kwargs = {kwarg for kwarg in kwargs.keys() if kwarg in positionals}
if duplicated_kwargs:
raise TypeError(
"The fixture arguments are defined as positional and keyword: {}. "
"Use only keyword arguments.".format(", ".join(duplicated_kwargs))
)
if positionals:
warnings.warn(FIXTURE_POSITIONAL_ARGUMENTS, stacklevel=2)
arguments.update(kwargs)
return fixture_function, arguments
def fixture(
callable_or_scope=None,
*args,
scope="function",
params=None,
autouse=False,
ids=None,
name=None
):
"""Decorator to mark a fixture factory function.
This decorator can be used, with or without parameters, to define a
@@ -1016,9 +1096,13 @@ def fixture(scope="function", params=None, autouse=False, ids=None, name=None):
:arg scope: the scope for which this fixture is shared, one of
``"function"`` (default), ``"class"``, ``"module"``,
``"package"`` or ``"session"``.
``"package"`` or ``"session"`` (``"package"`` is considered **experimental**
at this time).
``"package"`` is considered **experimental** at this time.
This parameter may also be a callable which receives ``(fixture_name, config)``
as parameters, and must return a ``str`` with one of the values mentioned above.
See :ref:`dynamic scope` in the docs for more information.
:arg params: an optional list of parameters which will cause multiple
invocations of the fixture function and all of the tests
@@ -1041,21 +1125,56 @@ def fixture(scope="function", params=None, autouse=False, ids=None, name=None):
``fixture_<fixturename>`` and then use
``@pytest.fixture(name='<fixturename>')``.
"""
if callable(scope) and params is None and autouse is False:
# direct decoration
return FixtureFunctionMarker("function", params, autouse, name=name)(scope)
if params is not None and not isinstance(params, (list, tuple)):
if params is not None:
params = list(params)
fixture_function, arguments = _parse_fixture_args(
callable_or_scope,
*args,
scope=scope,
params=params,
autouse=autouse,
ids=ids,
name=name
)
scope = arguments.get("scope")
params = arguments.get("params")
autouse = arguments.get("autouse")
ids = arguments.get("ids")
name = arguments.get("name")
if fixture_function and params is None and autouse is False:
# direct decoration
return FixtureFunctionMarker(scope, params, autouse, name=name)(
fixture_function
)
return FixtureFunctionMarker(scope, params, autouse, ids=ids, name=name)
def yield_fixture(scope="function", params=None, autouse=False, ids=None, name=None):
def yield_fixture(
callable_or_scope=None,
*args,
scope="function",
params=None,
autouse=False,
ids=None,
name=None
):
""" (return a) decorator to mark a yield-fixture factory function.
.. deprecated:: 3.0
Use :py:func:`pytest.fixture` directly instead.
"""
return fixture(scope=scope, params=params, autouse=autouse, ids=ids, name=name)
return fixture(
callable_or_scope,
*args,
scope=scope,
params=params,
autouse=autouse,
ids=ids,
name=name
)
defaultfuncargprefixmarker = fixture()
@@ -1172,6 +1291,8 @@ class FixtureManager:
except AttributeError:
pass
else:
from _pytest import nodes
# construct the base nodeid which is later used to check
# what fixtures are visible for particular tests (as denoted
# by their test id)
@@ -1348,6 +1469,8 @@ class FixtureManager:
return tuple(self._matchfactories(fixturedefs, nodeid))
def _matchfactories(self, fixturedefs, nodeid):
from _pytest import nodes
for fixturedef in fixturedefs:
if nodes.ischildnode(fixturedef.baseid, nodeid):
yield fixturedef

View File

@@ -115,9 +115,10 @@ def pytest_cmdline_parse():
def showversion(config):
p = py.path.local(pytest.__file__)
sys.stderr.write(
"This is pytest version {}, imported from {}\n".format(pytest.__version__, p)
"This is pytest version {}, imported from {}\n".format(
pytest.__version__, pytest.__file__
)
)
plugininfo = getpluginversioninfo(config)
if plugininfo:

View File

@@ -35,7 +35,7 @@ def pytest_plugin_registered(plugin, manager):
@hookspec(historic=True)
def pytest_addoption(parser):
def pytest_addoption(parser, pluginmanager):
"""register argparse-style options and ini-style config values,
called once at the beginning of a test run.
@@ -45,10 +45,15 @@ def pytest_addoption(parser):
files situated at the tests root directory due to how pytest
:ref:`discovers plugins during startup <pluginorder>`.
:arg _pytest.config.Parser parser: To add command line options, call
:py:func:`parser.addoption(...) <_pytest.config.Parser.addoption>`.
:arg _pytest.config.argparsing.Parser parser: To add command line options, call
:py:func:`parser.addoption(...) <_pytest.config.argparsing.Parser.addoption>`.
To add ini-file values call :py:func:`parser.addini(...)
<_pytest.config.Parser.addini>`.
<_pytest.config.argparsing.Parser.addini>`.
:arg _pytest.config.PytestPluginManager pluginmanager: pytest plugin manager,
which can be used to install :py:func:`hookspec`'s or :py:func:`hookimpl`'s
and allow one plugin to call another plugin's hooks to change how
command line options are added.
Options can later be accessed through the
:py:class:`config <_pytest.config.Config>` object, respectively:
@@ -143,7 +148,7 @@ def pytest_load_initial_conftests(early_config, parser, args):
:param _pytest.config.Config early_config: pytest config object
:param list[str] args: list of arguments passed on the command line
:param _pytest.config.Parser parser: to add command line options
:param _pytest.config.argparsing.Parser parser: to add command line options
"""
@@ -381,16 +386,6 @@ def pytest_runtest_logreport(report):
@hookspec(firstresult=True)
def pytest_report_to_serializable(config, report):
"""
.. warning::
This hook is experimental and subject to change between pytest releases, even
bug fixes.
The intent is for this to be used by plugins maintained by the core-devs, such
as ``pytest-xdist``, ``pytest-subtests``, and as a replacement for the internal
'resultlog' plugin.
In the future it might become part of the public hook API.
Serializes the given report object into a data structure suitable for sending
over the wire, e.g. converted to JSON.
"""
@@ -399,16 +394,6 @@ def pytest_report_to_serializable(config, report):
@hookspec(firstresult=True)
def pytest_report_from_serializable(config, data):
"""
.. warning::
This hook is experimental and subject to change between pytest releases, even
bug fixes.
The intent is for this to be used by plugins maintained by the core-devs, such
as ``pytest-xdist``, ``pytest-subtests``, and as a replacement for the internal
'resultlog' plugin.
In the future it might become part of the public hook API.
Restores a report object previously serialized with pytest_report_to_serializable().
"""
@@ -488,6 +473,8 @@ def pytest_assertion_pass(item, lineno, orig, expl):
"""
**(Experimental)**
.. versionadded:: 5.0
Hook called whenever an assertion *passes*.
Use this hook to do some processing after a passing assertion.

View File

@@ -19,8 +19,10 @@ from datetime import datetime
import py
import pytest
from _pytest import deprecated
from _pytest import nodes
from _pytest.config import filename_arg
from _pytest.warnings import _issue_warning_captured
class Junit(py.xml.Namespace):
@@ -421,9 +423,7 @@ def pytest_addoption(parser):
default="total",
) # choices=['total', 'call'])
parser.addini(
"junit_family",
"Emit XML for schema: one of legacy|xunit1|xunit2",
default="xunit1",
"junit_family", "Emit XML for schema: one of legacy|xunit1|xunit2", default=None
)
@@ -431,13 +431,17 @@ def pytest_configure(config):
xmlpath = config.option.xmlpath
# prevent opening xmllog on slave nodes (xdist)
if xmlpath and not hasattr(config, "slaveinput"):
junit_family = config.getini("junit_family")
if not junit_family:
_issue_warning_captured(deprecated.JUNIT_XML_DEFAULT_FAMILY, config.hook, 2)
junit_family = "xunit1"
config._xml = LogXML(
xmlpath,
config.option.junitprefix,
config.getini("junit_suite_name"),
config.getini("junit_logging"),
config.getini("junit_duration_report"),
config.getini("junit_family"),
junit_family,
config.getini("junit_log_passing_tests"),
)
config.pluginmanager.register(config._xml)
@@ -513,7 +517,7 @@ class LogXML:
key = nodeid, slavenode
if key in self.node_reporters:
# TODO: breasks for --dist=each
# TODO: breaks for --dist=each
return self.node_reporters[key]
reporter = _NodeReporter(nodeid, self)

View File

@@ -2,11 +2,15 @@
import logging
import re
from contextlib import contextmanager
import py
from io import StringIO
from typing import AbstractSet
from typing import Dict
from typing import List
from typing import Mapping
import pytest
from _pytest.compat import nullcontext
from _pytest.config import _strtobool
from _pytest.config import create_terminal_writer
from _pytest.pathlib import Path
@@ -32,14 +36,15 @@ class ColoredLevelFormatter(logging.Formatter):
logging.INFO: {"green"},
logging.DEBUG: {"purple"},
logging.NOTSET: set(),
}
} # type: Mapping[int, AbstractSet[str]]
LEVELNAME_FMT_REGEX = re.compile(r"%\(levelname\)([+-.]?\d*s)")
def __init__(self, terminalwriter, *args, **kwargs):
def __init__(self, terminalwriter, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self._original_fmt = self._style._fmt
self._level_to_fmt_mapping = {}
self._level_to_fmt_mapping = {} # type: Dict[int, str]
assert self._fmt is not None
levelname_fmt_match = self.LEVELNAME_FMT_REGEX.search(self._fmt)
if not levelname_fmt_match:
return
@@ -72,24 +77,87 @@ class PercentStyleMultiline(logging.PercentStyle):
formats the message as if each line were logged separately.
"""
def __init__(self, fmt, auto_indent):
super().__init__(fmt)
self._auto_indent = self._get_auto_indent(auto_indent)
@staticmethod
def _update_message(record_dict, message):
tmp = record_dict.copy()
tmp["message"] = message
return tmp
@staticmethod
def _get_auto_indent(auto_indent_option) -> int:
"""Determines the current auto indentation setting
Specify auto indent behavior (on/off/fixed) by passing in
extra={"auto_indent": [value]} to the call to logging.log() or
using a --log-auto-indent [value] command line or the
log_auto_indent [value] config option.
Default behavior is auto-indent off.
Using the string "True" or "on" or the boolean True as the value
turns auto indent on, using the string "False" or "off" or the
boolean False or the int 0 turns it off, and specifying a
positive integer fixes the indentation position to the value
specified.
Any other values for the option are invalid, and will silently be
converted to the default.
:param any auto_indent_option: User specified option for indentation
from command line, config or extra kwarg. Accepts int, bool or str.
str option accepts the same range of values as boolean config options,
as well as positive integers represented in str form.
:returns: indentation value, which can be
-1 (automatically determine indentation) or
0 (auto-indent turned off) or
>0 (explicitly set indentation position).
"""
if type(auto_indent_option) is int:
return int(auto_indent_option)
elif type(auto_indent_option) is str:
try:
return int(auto_indent_option)
except ValueError:
pass
try:
if _strtobool(auto_indent_option):
return -1
except ValueError:
return 0
elif type(auto_indent_option) is bool:
if auto_indent_option:
return -1
return 0
def format(self, record):
if "\n" in record.message:
lines = record.message.splitlines()
formatted = self._fmt % self._update_message(record.__dict__, lines[0])
# TODO optimize this by introducing an option that tells the
# logging framework that the indentation doesn't
# change. This allows to compute the indentation only once.
indentation = _remove_ansi_escape_sequences(formatted).find(lines[0])
lines[0] = formatted
return ("\n" + " " * indentation).join(lines)
else:
return self._fmt % record.__dict__
if hasattr(record, "auto_indent"):
# passed in from the "extra={}" kwarg on the call to logging.log()
auto_indent = self._get_auto_indent(record.auto_indent)
else:
auto_indent = self._auto_indent
if auto_indent:
lines = record.message.splitlines()
formatted = self._fmt % self._update_message(record.__dict__, lines[0])
if auto_indent < 0:
indentation = _remove_ansi_escape_sequences(formatted).find(
lines[0]
)
else:
# optimizes logging by allowing a fixed indentation
indentation = auto_indent
lines[0] = formatted
return ("\n" + " " * indentation).join(lines)
return self._fmt % record.__dict__
def get_option_ini(config, *names):
@@ -183,6 +251,12 @@ def pytest_addoption(parser):
default=DEFAULT_LOG_DATE_FORMAT,
help="log date format as used by the logging module.",
)
add_option_ini(
"--log-auto-indent",
dest="log_auto_indent",
default=None,
help="Auto-indent multiline messages passed to the logging module. Accepts true|on, false|off or an integer.",
)
@contextmanager
@@ -216,31 +290,31 @@ def catching_logs(handler, formatter=None, level=None):
class LogCaptureHandler(logging.StreamHandler):
"""A logging handler that stores log records and the log text."""
def __init__(self):
def __init__(self) -> None:
"""Creates a new log handler."""
logging.StreamHandler.__init__(self, py.io.TextIO())
self.records = []
logging.StreamHandler.__init__(self, StringIO())
self.records = [] # type: List[logging.LogRecord]
def emit(self, record):
def emit(self, record: logging.LogRecord) -> None:
"""Keep the log records in a list in addition to the log text."""
self.records.append(record)
logging.StreamHandler.emit(self, record)
def reset(self):
def reset(self) -> None:
self.records = []
self.stream = py.io.TextIO()
self.stream = StringIO()
class LogCaptureFixture:
"""Provides access and control of log capturing."""
def __init__(self, item):
def __init__(self, item) -> None:
"""Creates a new funcarg."""
self._item = item
# dict of log name -> log level
self._initial_log_levels = {} # Dict[str, int]
self._initial_log_levels = {} # type: Dict[str, int]
def _finalize(self):
def _finalize(self) -> None:
"""Finalizes the fixture.
This restores the log levels changed by :meth:`set_level`.
@@ -356,6 +430,7 @@ def caplog(request):
Captured logs are available through the following properties/methods::
* caplog.messages -> list of format-interpolated log messages
* caplog.text -> string containing formatted log output
* caplog.records -> list of logging.LogRecord instances
* caplog.record_tuples -> list of (logger_name, level, message) tuples
@@ -413,6 +488,7 @@ class LoggingPlugin:
self.formatter = self._create_formatter(
get_option_ini(config, "log_format"),
get_option_ini(config, "log_date_format"),
get_option_ini(config, "log_auto_indent"),
)
self.log_level = get_actual_log_level(config, "log_level")
@@ -444,7 +520,7 @@ class LoggingPlugin:
if self._log_cli_enabled():
self._setup_cli_logging()
def _create_formatter(self, log_format, log_date_format):
def _create_formatter(self, log_format, log_date_format, auto_indent):
# color option doesn't exist if terminal plugin is disabled
color = getattr(self._config.option, "color", "no")
if color != "no" and ColoredLevelFormatter.LEVELNAME_FMT_REGEX.search(
@@ -452,11 +528,14 @@ class LoggingPlugin:
):
formatter = ColoredLevelFormatter(
create_terminal_writer(self._config), log_format, log_date_format
)
) # type: logging.Formatter
else:
formatter = logging.Formatter(log_format, log_date_format)
formatter._style = PercentStyleMultiline(formatter._style._fmt)
formatter._style = PercentStyleMultiline(
formatter._style._fmt, auto_indent=auto_indent
)
return formatter
def _setup_cli_logging(self):
@@ -473,6 +552,7 @@ class LoggingPlugin:
log_cli_formatter = self._create_formatter(
get_option_ini(config, "log_cli_format", "log_format"),
get_option_ini(config, "log_cli_date_format", "log_date_format"),
get_option_ini(config, "log_auto_indent"),
)
log_cli_level = get_actual_log_level(config, "log_cli_level", "log_level")

View File

@@ -5,6 +5,7 @@ import functools
import importlib
import os
import sys
from typing import Dict
import attr
import py
@@ -16,10 +17,13 @@ from _pytest.config import hookimpl
from _pytest.config import UsageError
from _pytest.outcomes import exit
from _pytest.runner import collect_one_node
from _pytest.runner import SetupState
class ExitCode(enum.IntEnum):
"""
.. versionadded:: 5.0
Encodes the valid exit codes by pytest.
Currently users and plugins may supply other exit codes as well.
@@ -105,6 +109,7 @@ def pytest_addoption(parser):
group.addoption(
"--collectonly",
"--collect-only",
"--co",
action="store_true",
help="only collect tests, don't execute them.",
),
@@ -207,11 +212,17 @@ def wrap_session(config, doit):
config.hook.pytest_keyboard_interrupt(excinfo=excinfo)
session.exitstatus = exitstatus
except: # noqa
excinfo = _pytest._code.ExceptionInfo.from_current()
config.notify_exception(excinfo, config.option)
session.exitstatus = ExitCode.INTERNAL_ERROR
if excinfo.errisinstance(SystemExit):
sys.stderr.write("mainloop: caught unexpected SystemExit!\n")
excinfo = _pytest._code.ExceptionInfo.from_current()
try:
config.notify_exception(excinfo, config.option)
except exit.Exception as exc:
if exc.returncode is not None:
session.exitstatus = exc.returncode
sys.stderr.write("{}: {}\n".format(type(exc).__name__, exc))
else:
if excinfo.errisinstance(SystemExit):
sys.stderr.write("mainloop: caught unexpected SystemExit!\n")
finally:
excinfo = None # Explicitly break reference cycle.
@@ -246,7 +257,10 @@ def pytest_collection(session):
def pytest_runtestloop(session):
if session.testsfailed and not session.config.option.continue_on_collection_errors:
raise session.Interrupted("%d errors during collection" % session.testsfailed)
raise session.Interrupted(
"%d error%s during collection"
% (session.testsfailed, "s" if session.testsfailed != 1 else "")
)
if session.config.option.collectonly:
return True
@@ -354,8 +368,8 @@ class Failed(Exception):
class _bestrelpath_cache(dict):
path = attr.ib()
def __missing__(self, path):
r = self.path.bestrelpath(path)
def __missing__(self, path: str) -> str:
r = self.path.bestrelpath(path) # type: str
self[path] = r
return r
@@ -363,6 +377,7 @@ class _bestrelpath_cache(dict):
class Session(nodes.FSCollector):
Interrupted = Interrupted
Failed = Failed
_setupstate = None # type: SetupState
def __init__(self, config):
nodes.FSCollector.__init__(
@@ -378,7 +393,9 @@ class Session(nodes.FSCollector):
self._initialpaths = frozenset()
# Keep track of any collected nodes in here, so we don't duplicate fixtures
self._node_cache = {}
self._bestrelpathcache = _bestrelpath_cache(config.rootdir)
self._bestrelpathcache = _bestrelpath_cache(
config.rootdir
) # type: Dict[str, str]
# Dirnames of pkgs with dunder-init files.
self._pkg_roots = {}
@@ -393,7 +410,7 @@ class Session(nodes.FSCollector):
self.testscollected,
)
def _node_location_to_relpath(self, node_path):
def _node_location_to_relpath(self, node_path: str) -> str:
# bestrelpath is a quite slow function
return self._bestrelpathcache[node_path]
@@ -427,7 +444,7 @@ class Session(nodes.FSCollector):
# one or more conftests are not in use at this fspath
proxy = FSHookProxy(fspath, pm, remove_mods)
else:
# all plugis are active for this fspath
# all plugins are active for this fspath
proxy = self.config.hook
return proxy
@@ -466,7 +483,6 @@ class Session(nodes.FSCollector):
for arg, exc in self._notfound:
line = "(no name {!r} in any of {!r})".format(arg, exc.args[0])
errors.append("not found: {}\n{}".format(arg, line))
# XXX: test this
raise UsageError(*errors)
if not genitems:
return rep.result
@@ -478,22 +494,22 @@ class Session(nodes.FSCollector):
def collect(self):
for initialpart in self._initialparts:
arg = "::".join(map(str, initialpart))
self.trace("processing argument", arg)
self.trace("processing argument", initialpart)
self.trace.root.indent += 1
try:
yield from self._collect(arg)
yield from self._collect(initialpart)
except NoMatch:
report_arg = "::".join(map(str, initialpart))
# we are inside a make_report hook so
# we cannot directly pass through the exception
self._notfound.append((arg, sys.exc_info()[1]))
self._notfound.append((report_arg, sys.exc_info()[1]))
self.trace.root.indent -= 1
def _collect(self, arg):
from _pytest.python import Package
names = self._parsearg(arg)
names = arg[:]
argpath = names.pop(0)
# Start with a Session root, and delve to argpath item (dir or file)

View File

@@ -8,6 +8,7 @@ from .structures import MARK_GEN
from .structures import MarkDecorator
from .structures import MarkGenerator
from .structures import ParameterSet
from _pytest.config import hookimpl
from _pytest.config import UsageError
__all__ = ["Mark", "MarkDecorator", "MarkGenerator", "get_empty_parameterset_mark"]
@@ -74,6 +75,7 @@ def pytest_addoption(parser):
parser.addini(EMPTY_PARAMETERSET_OPTION, "default marker for empty parametersets")
@hookimpl(tryfirst=True)
def pytest_cmdline_main(config):
import _pytest.config
@@ -91,10 +93,6 @@ def pytest_cmdline_main(config):
return 0
# Ignore type because of https://github.com/python/mypy/issues/2087.
pytest_cmdline_main.tryfirst = True # type: ignore
def deselect_by_keyword(items, config):
keywordexpr = config.option.keyword.lstrip()
if not keywordexpr:

View File

@@ -28,7 +28,7 @@ class MarkEvaluator:
self._mark_name = name
def __bool__(self):
# dont cache here to prevent staleness
# don't cache here to prevent staleness
return bool(self._get_marks())
__nonzero__ = __bool__
@@ -51,6 +51,8 @@ class MarkEvaluator:
except TEST_OUTCOME:
self.exc = sys.exc_info()
if isinstance(self.exc[1], SyntaxError):
# TODO: Investigate why SyntaxError.offset is Optional, and if it can be None here.
assert self.exc[1].offset is not None
msg = [" " * (self.exc[1].offset + 4) + "^"]
msg.append("SyntaxError: invalid syntax")
else:

View File

@@ -1,6 +1,6 @@
"""
this is a place where we put datastructures used by legacy apis
we hope ot remove
we hope to remove
"""
import keyword

View File

@@ -2,12 +2,12 @@ import inspect
import warnings
from collections import namedtuple
from collections.abc import MutableMapping
from operator import attrgetter
from typing import Set
import attr
from ..compat import ascii_escaped
from ..compat import ATTRS_EQ_FIELD
from ..compat import getfslineno
from ..compat import NOTSET
from _pytest.outcomes import fail
@@ -16,16 +16,6 @@ from _pytest.warning_types import PytestUnknownMarkWarning
EMPTY_PARAMETERSET_OPTION = "empty_parameter_set_mark"
def alias(name, warning=None):
getter = attrgetter(name)
def warned(self):
warnings.warn(warning, stacklevel=2)
return getter(self)
return property(getter if warning is None else warned, doc="alias for " + name)
def istestfunc(func):
return (
hasattr(func, "__call__")
@@ -204,17 +194,25 @@ class MarkDecorator:
mark = attr.ib(validator=attr.validators.instance_of(Mark))
name = alias("mark.name")
args = alias("mark.args")
kwargs = alias("mark.kwargs")
@property
def name(self):
"""alias for mark.name"""
return self.mark.name
@property
def args(self):
"""alias for mark.args"""
return self.mark.args
@property
def kwargs(self):
"""alias for mark.kwargs"""
return self.mark.kwargs
@property
def markname(self):
return self.name # for backward-compat (2.4.1 had this attr)
def __eq__(self, other):
return self.mark == other.mark if isinstance(other, MarkDecorator) else False
def __repr__(self):
return "<MarkDecorator {!r}>".format(self.mark)
@@ -292,7 +290,7 @@ class MarkGenerator:
_config = None
_markers = set() # type: Set[str]
def __getattr__(self, name):
def __getattr__(self, name: str) -> MarkDecorator:
if name[0] == "_":
raise AttributeError("Marker name must NOT start with underscore")
@@ -316,13 +314,18 @@ class MarkGenerator:
"{!r} not found in `markers` configuration option".format(name),
pytrace=False,
)
else:
warnings.warn(
"Unknown pytest.mark.%s - is this a typo? You can register "
"custom marks to avoid this warning - for details, see "
"https://docs.pytest.org/en/latest/mark.html" % name,
PytestUnknownMarkWarning,
)
# Raise a specific error for common misspellings of "parametrize".
if name in ["parameterize", "parametrise", "parameterise"]:
__tracebackhide__ = True
fail("Unknown '{}' mark, did you mean 'parametrize'?".format(name))
warnings.warn(
"Unknown pytest.mark.%s - is this a typo? You can register "
"custom marks to avoid this warning - for details, see "
"https://docs.pytest.org/en/latest/mark.html" % name,
PytestUnknownMarkWarning,
)
return MarkDecorator(Mark(name, (), {}))
@@ -367,7 +370,8 @@ class NodeKeywords(MutableMapping):
return "<NodeKeywords for node {}>".format(self.node)
@attr.s(cmp=False, hash=False)
# mypy cannot find this overload, remove when on attrs>=19.2
@attr.s(hash=False, **{ATTRS_EQ_FIELD: False}) # type: ignore
class NodeMarkers:
"""
internal structure for storing marks belonging to a node

View File

@@ -1,13 +1,34 @@
import os
import warnings
from functools import lru_cache
from typing import Any
from typing import Dict
from typing import List
from typing import Optional
from typing import Set
from typing import Tuple
from typing import Union
import py
import _pytest._code
from _pytest._code.code import ExceptionChainRepr
from _pytest._code.code import ExceptionInfo
from _pytest._code.code import ReprExceptionInfo
from _pytest.compat import cached_property
from _pytest.compat import getfslineno
from _pytest.config import Config
from _pytest.fixtures import FixtureDef
from _pytest.fixtures import FixtureLookupError
from _pytest.fixtures import FixtureLookupErrorRepr
from _pytest.mark.structures import Mark
from _pytest.mark.structures import MarkDecorator
from _pytest.mark.structures import NodeKeywords
from _pytest.outcomes import fail
from _pytest.outcomes import Failed
if False: # TYPE_CHECKING
# Imported here due to circular import.
from _pytest.main import Session # noqa: F401
SEP = "/"
@@ -57,8 +78,14 @@ class Node:
Collector subclasses have children, Items are terminal nodes."""
def __init__(
self, name, parent=None, config=None, session=None, fspath=None, nodeid=None
):
self,
name,
parent: Optional["Node"] = None,
config: Optional[Config] = None,
session: Optional["Session"] = None,
fspath: Optional[py.path.local] = None,
nodeid: Optional[str] = None,
) -> None:
#: a unique name within the scope of the parent node
self.name = name
@@ -66,10 +93,20 @@ class Node:
self.parent = parent
#: the pytest config object
self.config = config or parent.config
if config:
self.config = config
else:
if not parent:
raise TypeError("config or parent must be provided")
self.config = parent.config
#: the session this node is part of
self.session = session or parent.session
if session:
self.session = session
else:
if not parent:
raise TypeError("session or parent must be provided")
self.session = parent.session
#: filesystem path where this node was collected from (can be None)
self.fspath = fspath or getattr(parent, "fspath", None)
@@ -78,18 +115,20 @@ class Node:
self.keywords = NodeKeywords(self)
#: the marker objects belonging to this node
self.own_markers = []
self.own_markers = [] # type: List[Mark]
#: allow adding of extra keywords to use for matching
self.extra_keyword_matches = set()
self.extra_keyword_matches = set() # type: Set[str]
# used for storing artificial fixturedefs for direct parametrization
self._name2pseudofixturedef = {}
self._name2pseudofixturedef = {} # type: Dict[str, FixtureDef]
if nodeid is not None:
assert "::()" not in nodeid
self._nodeid = nodeid
else:
if not self.parent:
raise TypeError("nodeid or parent must be provided")
self._nodeid = self.parent.nodeid
if self.name != "()":
self._nodeid += "::" + self.name
@@ -153,14 +192,16 @@ class Node:
""" return list of all parent collectors up to self,
starting from root of collection tree. """
chain = []
item = self
item = self # type: Optional[Node]
while item is not None:
chain.append(item)
item = item.parent
chain.reverse()
return chain
def add_marker(self, marker, append=True):
def add_marker(
self, marker: Union[str, MarkDecorator], append: bool = True
) -> None:
"""dynamically add a marker object to the node.
:type marker: ``str`` or ``pytest.mark.*`` object
@@ -168,17 +209,19 @@ class Node:
``append=True`` whether to append the marker,
if ``False`` insert at position ``0``.
"""
from _pytest.mark import MarkDecorator, MARK_GEN
from _pytest.mark import MARK_GEN
if isinstance(marker, str):
marker = getattr(MARK_GEN, marker)
elif not isinstance(marker, MarkDecorator):
raise ValueError("is not a string or pytest.mark.* Marker")
self.keywords[marker.name] = marker
if append:
self.own_markers.append(marker.mark)
if isinstance(marker, MarkDecorator):
marker_ = marker
elif isinstance(marker, str):
marker_ = getattr(MARK_GEN, marker)
else:
self.own_markers.insert(0, marker.mark)
raise ValueError("is not a string or pytest.mark.* Marker")
self.keywords[marker_.name] = marker
if append:
self.own_markers.append(marker_.mark)
else:
self.own_markers.insert(0, marker_.mark)
def iter_markers(self, name=None):
"""
@@ -211,7 +254,7 @@ class Node:
def listextrakeywords(self):
""" Return a set of all extra keywords in self and any parents."""
extra_keywords = set()
extra_keywords = set() # type: Set[str]
for item in self.listchain():
extra_keywords.update(item.extra_keyword_matches)
return extra_keywords
@@ -230,7 +273,7 @@ class Node:
def getparent(self, cls):
""" get the next parent node (including ourself)
which is an instance of the given class"""
current = self
current = self # type: Optional[Node]
while current and not isinstance(current, cls):
current = current.parent
return current
@@ -238,14 +281,14 @@ class Node:
def _prunetraceback(self, excinfo):
pass
def _repr_failure_py(self, excinfo, style=None):
if excinfo.errisinstance(fail.Exception):
def _repr_failure_py(
self, excinfo: ExceptionInfo[Union[Failed, FixtureLookupError]], style=None
) -> Union[str, ReprExceptionInfo, ExceptionChainRepr, FixtureLookupErrorRepr]:
if isinstance(excinfo.value, Failed):
if not excinfo.value.pytrace:
return str(excinfo.value)
fm = self.session._fixturemanager
if excinfo.errisinstance(fm.FixtureLookupError):
if isinstance(excinfo.value, FixtureLookupError):
return excinfo.value.formatrepr()
tbfilter = True
if self.config.getoption("fulltrace", False):
style = "long"
else:
@@ -253,7 +296,6 @@ class Node:
self._prunetraceback(excinfo)
if len(excinfo.traceback) == 0:
excinfo.traceback = tb
tbfilter = False # prunetraceback already does it
if style == "auto":
style = "long"
# XXX should excinfo.getrepr record all data and toterminal() process it?
@@ -279,11 +321,13 @@ class Node:
abspath=abspath,
showlocals=self.config.getoption("showlocals", False),
style=style,
tbfilter=tbfilter,
tbfilter=False, # pruned already, or in --fulltrace mode.
truncate_locals=truncate_locals,
)
def repr_failure(self, excinfo, style=None):
def repr_failure(
self, excinfo, style=None
) -> Union[str, ReprExceptionInfo, ExceptionChainRepr, FixtureLookupErrorRepr]:
return self._repr_failure_py(excinfo, style)
@@ -349,8 +393,9 @@ def _check_initialpaths_for_relpath(session, fspath):
class FSCollector(Collector):
def __init__(self, fspath, parent=None, config=None, session=None, nodeid=None):
fspath = py.path.local(fspath) # xxx only for test_resultlog.py?
def __init__(
self, fspath: py.path.local, parent=None, config=None, session=None, nodeid=None
) -> None:
name = fspath.basename
if parent is not None:
rel = fspath.relto(parent.fspath)
@@ -385,13 +430,13 @@ class Item(Node):
def __init__(self, name, parent=None, config=None, session=None, nodeid=None):
super().__init__(name, parent, config, session, nodeid=nodeid)
self._report_sections = []
self._report_sections = [] # type: List[Tuple[str, str, str]]
#: user properties is a list of tuples (name, value) that holds user
#: defined properties for this test.
self.user_properties = []
self.user_properties = [] # type: List[Tuple[str, Any]]
def add_report_section(self, when, key, content):
def add_report_section(self, when: str, key: str, content: str) -> None:
"""
Adds a new report section, similar to what's done internally to add stdout and
stderr captured output::
@@ -410,16 +455,12 @@ class Item(Node):
if content:
self._report_sections.append((when, key, content))
def reportinfo(self):
def reportinfo(self) -> Tuple[str, Optional[int], str]:
return self.fspath, None, ""
@property
def location(self):
try:
return self._location
except AttributeError:
location = self.reportinfo()
fspath = self.session._node_location_to_relpath(location[0])
location = (fspath, location[1], str(location[2]))
self._location = location
return location
@cached_property
def location(self) -> Tuple[str, Optional[int], str]:
location = self.reportinfo()
fspath = self.session._node_location_to_relpath(location[0])
assert type(location[2]) is str
return (fspath, location[1], location[2])

View File

@@ -59,20 +59,25 @@ def create_new_paste(contents):
Creates a new paste using bpaste.net service.
:contents: paste contents as utf-8 encoded bytes
:returns: url to the pasted contents
:returns: url to the pasted contents or error message
"""
import re
from urllib.request import urlopen
from urllib.parse import urlencode
params = {"code": contents, "lexer": "python3", "expiry": "1week"}
params = {"code": contents, "lexer": "text", "expiry": "1week"}
url = "https://bpaste.net"
response = urlopen(url, data=urlencode(params).encode("ascii")).read()
m = re.search(r'href="/raw/(\w+)"', response.decode("utf-8"))
try:
response = (
urlopen(url, data=urlencode(params).encode("ascii")).read().decode("utf-8")
)
except OSError as exc_info: # urllib errors
return "bad response: %s" % exc_info
m = re.search(r'href="/raw/(\w+)"', response)
if m:
return "{}/show/{}".format(url, m.group(1))
else:
return "bad response: " + response.decode("utf-8")
return "bad response: invalid format ('" + response + "')"
def pytest_terminal_summary(terminalreporter):

View File

@@ -1,7 +1,6 @@
import atexit
import fnmatch
import itertools
import operator
import os
import shutil
import sys
@@ -11,9 +10,13 @@ from functools import partial
from os.path import expanduser
from os.path import expandvars
from os.path import isabs
from os.path import normcase
from os.path import sep
from posixpath import sep as posix_sep
from typing import Iterable
from typing import Iterator
from typing import Set
from typing import TypeVar
from typing import Union
from _pytest.warning_types import PytestWarning
@@ -27,10 +30,15 @@ __all__ = ["Path", "PurePath"]
LOCK_TIMEOUT = 60 * 60 * 3
get_lock_path = operator.methodcaller("joinpath", ".lock")
_AnyPurePath = TypeVar("_AnyPurePath", bound=PurePath)
def ensure_reset_dir(path):
def get_lock_path(path: _AnyPurePath) -> _AnyPurePath:
return path.joinpath(".lock")
def ensure_reset_dir(path: Path) -> None:
"""
ensures the given path is an empty directory
"""
@@ -39,26 +47,41 @@ def ensure_reset_dir(path):
path.mkdir()
def on_rm_rf_error(func, path: str, exc, *, start_path):
"""Handles known read-only errors during rmtree."""
excvalue = exc[1]
def on_rm_rf_error(func, path: str, exc, *, start_path: Path) -> bool:
"""Handles known read-only errors during rmtree.
The returned value is used only by our own tests.
"""
exctype, excvalue = exc[:2]
# another process removed the file in the middle of the "rm_rf" (xdist for example)
# more context: https://github.com/pytest-dev/pytest/issues/5974#issuecomment-543799018
if isinstance(excvalue, FileNotFoundError):
return False
if not isinstance(excvalue, PermissionError):
warnings.warn(
PytestWarning("(rm_rf) error removing {}: {}".format(path, excvalue))
PytestWarning(
"(rm_rf) error removing {}\n{}: {}".format(path, exctype, excvalue)
)
)
return
return False
if func not in (os.rmdir, os.remove, os.unlink):
warnings.warn(
PytestWarning("(rm_rf) error removing {}: {}".format(path, excvalue))
)
return
if func not in (os.open,):
warnings.warn(
PytestWarning(
"(rm_rf) unknown function {} when removing {}:\n{}: {}".format(
func, path, exctype, excvalue
)
)
)
return False
# Chmod + retry.
import stat
def chmod_rw(p: str):
def chmod_rw(p: str) -> None:
mode = os.stat(p).st_mode
os.chmod(p, mode | stat.S_IRUSR | stat.S_IWUSR)
@@ -74,9 +97,10 @@ def on_rm_rf_error(func, path: str, exc, *, start_path):
chmod_rw(str(path))
func(path)
return True
def rm_rf(path: Path):
def rm_rf(path: Path) -> None:
"""Remove the path contents recursively, even if some elements
are read-only.
"""
@@ -84,7 +108,7 @@ def rm_rf(path: Path):
shutil.rmtree(str(path), onerror=onerror)
def find_prefixed(root, prefix):
def find_prefixed(root: Path, prefix: str) -> Iterator[Path]:
"""finds all elements in root that begin with the prefix, case insensitive"""
l_prefix = prefix.lower()
for x in root.iterdir():
@@ -92,7 +116,7 @@ def find_prefixed(root, prefix):
yield x
def extract_suffixes(iter, prefix):
def extract_suffixes(iter: Iterable[PurePath], prefix: str) -> Iterator[str]:
"""
:param iter: iterator over path names
:param prefix: expected prefix of the path names
@@ -103,13 +127,13 @@ def extract_suffixes(iter, prefix):
yield p.name[p_len:]
def find_suffixes(root, prefix):
def find_suffixes(root: Path, prefix: str) -> Iterator[str]:
"""combines find_prefixes and extract_suffixes
"""
return extract_suffixes(find_prefixed(root, prefix), prefix)
def parse_num(maybe_num):
def parse_num(maybe_num) -> int:
"""parses number path suffixes, returns -1 on error"""
try:
return int(maybe_num)
@@ -117,7 +141,9 @@ def parse_num(maybe_num):
return -1
def _force_symlink(root, target, link_to):
def _force_symlink(
root: Path, target: Union[str, PurePath], link_to: Union[str, Path]
) -> None:
"""helper to create the current symlink
it's full of race conditions that are reasonably ok to ignore
@@ -137,7 +163,7 @@ def _force_symlink(root, target, link_to):
pass
def make_numbered_dir(root, prefix):
def make_numbered_dir(root: Path, prefix: str) -> Path:
"""create a directory with an increased number as suffix for the given prefix"""
for i in range(10):
# try up to 10 times to create the folder
@@ -158,7 +184,7 @@ def make_numbered_dir(root, prefix):
)
def create_cleanup_lock(p):
def create_cleanup_lock(p: Path) -> Path:
"""crates a lock to prevent premature folder cleanup"""
lock_path = get_lock_path(p)
try:
@@ -175,11 +201,11 @@ def create_cleanup_lock(p):
return lock_path
def register_cleanup_lock_removal(lock_path, register=atexit.register):
def register_cleanup_lock_removal(lock_path: 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):
def cleanup_on_exit(lock_path: Path = lock_path, original_pid: int = pid) -> None:
current_pid = os.getpid()
if current_pid != original_pid:
# fork
@@ -192,7 +218,7 @@ def register_cleanup_lock_removal(lock_path, register=atexit.register):
return register(cleanup_on_exit)
def maybe_delete_a_numbered_dir(path):
def maybe_delete_a_numbered_dir(path: Path) -> None:
"""removes a numbered directory if its lock can be obtained and it does not seem to be in use"""
lock_path = None
try:
@@ -218,7 +244,7 @@ def maybe_delete_a_numbered_dir(path):
pass
def ensure_deletable(path, consider_lock_dead_if_created_before):
def ensure_deletable(path: Path, consider_lock_dead_if_created_before: float) -> bool:
"""checks if a lock exists and breaks it if its considered dead"""
if path.is_symlink():
return False
@@ -237,13 +263,13 @@ def ensure_deletable(path, consider_lock_dead_if_created_before):
return False
def try_cleanup(path, consider_lock_dead_if_created_before):
def try_cleanup(path: Path, consider_lock_dead_if_created_before: float) -> None:
"""tries to cleanup a folder if we can ensure it's deletable"""
if ensure_deletable(path, consider_lock_dead_if_created_before):
maybe_delete_a_numbered_dir(path)
def cleanup_candidates(root, prefix, keep):
def cleanup_candidates(root: Path, prefix: str, keep: int) -> Iterator[Path]:
"""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
@@ -255,7 +281,9 @@ def cleanup_candidates(root, prefix, keep):
yield path
def cleanup_numbered_dir(root, prefix, keep, consider_lock_dead_if_created_before):
def cleanup_numbered_dir(
root: Path, prefix: str, keep: int, consider_lock_dead_if_created_before: float
) -> None:
"""cleanup for lock driven numbered directories"""
for path in cleanup_candidates(root, prefix, keep):
try_cleanup(path, consider_lock_dead_if_created_before)
@@ -263,7 +291,9 @@ def cleanup_numbered_dir(root, prefix, keep, consider_lock_dead_if_created_befor
try_cleanup(path, consider_lock_dead_if_created_before)
def make_numbered_dir_with_cleanup(root, prefix, keep, lock_timeout):
def make_numbered_dir_with_cleanup(
root: Path, prefix: str, keep: int, lock_timeout: float
) -> Path:
"""creates a numbered dir with a cleanup lock and removes old ones"""
e = None
for i in range(10):
@@ -297,7 +327,7 @@ def resolve_from_str(input, root):
return root.joinpath(input)
def fnmatch_ex(pattern, path):
def fnmatch_ex(pattern: str, path) -> bool:
"""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
@@ -332,15 +362,6 @@ def fnmatch_ex(pattern, path):
return fnmatch.fnmatch(name, pattern)
def parts(s):
def parts(s: str) -> Set[str]:
parts = s.split(sep)
return {sep.join(parts[: i + 1]) or sep for i in range(len(parts))}
def unique_path(path):
"""Returns a unique path in case-insensitive (but case-preserving) file
systems such as Windows.
This is needed only for ``py.path.local``; ``pathlib.Path`` handles this
natively with ``resolve()``."""
return type(path)(normcase(str(path.realpath())))

View File

@@ -1,4 +1,5 @@
"""(disabled by default) support for testing pytest and pytest plugins."""
import collections.abc
import gc
import importlib
import os
@@ -8,8 +9,16 @@ import subprocess
import sys
import time
import traceback
from collections.abc import Sequence
from fnmatch import fnmatch
from io import StringIO
from typing import Callable
from typing import Dict
from typing import Iterable
from typing import List
from typing import Optional
from typing import Sequence
from typing import Tuple
from typing import Union
from weakref import WeakKeyDictionary
import py
@@ -19,10 +28,16 @@ from _pytest._code import Source
from _pytest._io.saferepr import saferepr
from _pytest.capture import MultiCapture
from _pytest.capture import SysCapture
from _pytest.fixtures import FixtureRequest
from _pytest.main import ExitCode
from _pytest.main import Session
from _pytest.monkeypatch import MonkeyPatch
from _pytest.pathlib import Path
from _pytest.reports import TestReport
if False: # TYPE_CHECKING
from typing import Type
IGNORE_PAM = [ # filenames added when obtaining details about the current user
"/var/lib/sss/mc/passwd"
@@ -140,7 +155,7 @@ class LsofFdLeakChecker:
@pytest.fixture
def _pytest(request):
def _pytest(request: FixtureRequest) -> "PytestArg":
"""Return a helper which offers a gethookrecorder(hook) method which
returns a HookRecorder instance which helps to make assertions about called
hooks.
@@ -150,10 +165,10 @@ def _pytest(request):
class PytestArg:
def __init__(self, request):
def __init__(self, request: FixtureRequest) -> None:
self.request = request
def gethookrecorder(self, hook):
def gethookrecorder(self, hook) -> "HookRecorder":
hookrecorder = HookRecorder(hook._pm)
self.request.addfinalizer(hookrecorder.finish_recording)
return hookrecorder
@@ -174,6 +189,11 @@ class ParsedCall:
del d["_name"]
return "<ParsedCall {!r}(**{!r})>".format(self._name, d)
if False: # TYPE_CHECKING
# The class has undetermined attributes, this tells mypy about it.
def __getattr__(self, key):
raise NotImplementedError()
class HookRecorder:
"""Record all hooks called in a plugin manager.
@@ -183,27 +203,27 @@ class HookRecorder:
"""
def __init__(self, pluginmanager):
def __init__(self, pluginmanager) -> None:
self._pluginmanager = pluginmanager
self.calls = []
self.calls = [] # type: List[ParsedCall]
def before(hook_name, hook_impls, kwargs):
def before(hook_name: str, hook_impls, kwargs) -> None:
self.calls.append(ParsedCall(hook_name, kwargs))
def after(outcome, hook_name, hook_impls, kwargs):
def after(outcome, hook_name: str, hook_impls, kwargs) -> None:
pass
self._undo_wrapping = pluginmanager.add_hookcall_monitoring(before, after)
def finish_recording(self):
def finish_recording(self) -> None:
self._undo_wrapping()
def getcalls(self, names):
def getcalls(self, names: Union[str, Iterable[str]]) -> List[ParsedCall]:
if isinstance(names, str):
names = names.split()
return [call for call in self.calls if call._name in names]
def assert_contains(self, entries):
def assert_contains(self, entries) -> None:
__tracebackhide__ = True
i = 0
entries = list(entries)
@@ -224,7 +244,7 @@ class HookRecorder:
else:
pytest.fail("could not find {!r} check {!r}".format(name, check))
def popcall(self, name):
def popcall(self, name: str) -> ParsedCall:
__tracebackhide__ = True
for i, call in enumerate(self.calls):
if call._name == name:
@@ -234,20 +254,27 @@ class HookRecorder:
lines.extend([" %s" % x for x in self.calls])
pytest.fail("\n".join(lines))
def getcall(self, name):
def getcall(self, name: str) -> ParsedCall:
values = self.getcalls(name)
assert len(values) == 1, (name, values)
return values[0]
# functionality for test reports
def getreports(self, names="pytest_runtest_logreport pytest_collectreport"):
def getreports(
self,
names: Union[
str, Iterable[str]
] = "pytest_runtest_logreport pytest_collectreport",
) -> List[TestReport]:
return [x.report for x in self.getcalls(names)]
def matchreport(
self,
inamepart="",
names="pytest_runtest_logreport pytest_collectreport",
inamepart: str = "",
names: Union[
str, Iterable[str]
] = "pytest_runtest_logreport pytest_collectreport",
when=None,
):
"""return a testreport whose dotted import path matches"""
@@ -273,13 +300,20 @@ class HookRecorder:
)
return values[0]
def getfailures(self, names="pytest_runtest_logreport pytest_collectreport"):
def getfailures(
self,
names: Union[
str, Iterable[str]
] = "pytest_runtest_logreport pytest_collectreport",
) -> List[TestReport]:
return [rep for rep in self.getreports(names) if rep.failed]
def getfailedcollections(self):
def getfailedcollections(self) -> List[TestReport]:
return self.getfailures("pytest_collectreport")
def listoutcomes(self):
def listoutcomes(
self,
) -> Tuple[List[TestReport], List[TestReport], List[TestReport]]:
passed = []
skipped = []
failed = []
@@ -294,31 +328,38 @@ class HookRecorder:
failed.append(rep)
return passed, skipped, failed
def countoutcomes(self):
def countoutcomes(self) -> List[int]:
return [len(x) for x in self.listoutcomes()]
def assertoutcome(self, passed=0, skipped=0, failed=0):
realpassed, realskipped, realfailed = self.listoutcomes()
assert passed == len(realpassed)
assert skipped == len(realskipped)
assert failed == len(realfailed)
def assertoutcome(self, passed: int = 0, skipped: int = 0, failed: int = 0) -> None:
__tracebackhide__ = True
def clear(self):
outcomes = self.listoutcomes()
realpassed, realskipped, realfailed = outcomes
obtained = {
"passed": len(realpassed),
"skipped": len(realskipped),
"failed": len(realfailed),
}
expected = {"passed": passed, "skipped": skipped, "failed": failed}
assert obtained == expected, outcomes
def clear(self) -> None:
self.calls[:] = []
@pytest.fixture
def linecomp(request):
def linecomp(request: FixtureRequest) -> "LineComp":
return LineComp()
@pytest.fixture(name="LineMatcher")
def LineMatcher_fixture(request):
def LineMatcher_fixture(request: FixtureRequest) -> "Type[LineMatcher]":
return LineMatcher
@pytest.fixture
def testdir(request, tmpdir_factory):
def testdir(request: FixtureRequest, tmpdir_factory) -> "Testdir":
return Testdir(request, tmpdir_factory)
@@ -351,32 +392,40 @@ class RunResult:
Attributes:
:ret: the return value
:outlines: list of lines captured from stdout
:errlines: list of lines captures from stderr
:stdout: :py:class:`LineMatcher` of stdout, use ``stdout.str()`` to
:ivar ret: the return value
:ivar outlines: list of lines captured from stdout
:ivar errlines: list of lines captured from stderr
:ivar stdout: :py:class:`LineMatcher` of stdout, use ``stdout.str()`` to
reconstruct stdout or the commonly used ``stdout.fnmatch_lines()``
method
:stderr: :py:class:`LineMatcher` of stderr
:duration: duration in seconds
:ivar stderr: :py:class:`LineMatcher` of stderr
:ivar duration: duration in seconds
"""
def __init__(self, ret, outlines, errlines, duration):
self.ret = ret
def __init__(
self,
ret: Union[int, ExitCode],
outlines: Sequence[str],
errlines: Sequence[str],
duration: float,
) -> None:
try:
self.ret = pytest.ExitCode(ret) # type: Union[int, ExitCode]
except ValueError:
self.ret = ret
self.outlines = outlines
self.errlines = errlines
self.stdout = LineMatcher(outlines)
self.stderr = LineMatcher(errlines)
self.duration = duration
def __repr__(self):
def __repr__(self) -> str:
return (
"<RunResult ret=%r len(stdout.lines)=%d len(stderr.lines)=%d duration=%.2fs>"
"<RunResult ret=%s len(stdout.lines)=%d len(stderr.lines)=%d duration=%.2fs>"
% (self.ret, len(self.stdout.lines), len(self.stderr.lines), self.duration)
)
def parseoutcomes(self):
def parseoutcomes(self) -> Dict[str, int]:
"""Return a dictionary of outcomestring->num from parsing the terminal
output that the test process produced.
@@ -389,12 +438,19 @@ class RunResult:
raise ValueError("Pytest terminal summary report not found")
def assert_outcomes(
self, passed=0, skipped=0, failed=0, error=0, xpassed=0, xfailed=0
):
self,
passed: int = 0,
skipped: int = 0,
failed: int = 0,
error: int = 0,
xpassed: int = 0,
xfailed: int = 0,
) -> None:
"""Assert that the specified outcomes appear with the respective
numbers (0 means it didn't occur) in the text output from a test run.
"""
__tracebackhide__ = True
d = self.parseoutcomes()
obtained = {
"passed": d.get("passed", 0),
@@ -416,19 +472,19 @@ class RunResult:
class CwdSnapshot:
def __init__(self):
def __init__(self) -> None:
self.__saved = os.getcwd()
def restore(self):
def restore(self) -> None:
os.chdir(self.__saved)
class SysModulesSnapshot:
def __init__(self, preserve=None):
def __init__(self, preserve: Optional[Callable[[str], bool]] = None):
self.__preserve = preserve
self.__saved = dict(sys.modules)
def restore(self):
def restore(self) -> None:
if self.__preserve:
self.__saved.update(
(k, m) for k, m in sys.modules.items() if self.__preserve(k)
@@ -438,10 +494,10 @@ class SysModulesSnapshot:
class SysPathsSnapshot:
def __init__(self):
def __init__(self) -> None:
self.__saved = list(sys.path), list(sys.meta_path)
def restore(self):
def restore(self) -> None:
sys.path[:], sys.meta_path[:] = self.__saved
@@ -454,9 +510,9 @@ class Testdir:
Attributes:
:tmpdir: The :py:class:`py.path.local` instance of the temporary directory.
:ivar tmpdir: The :py:class:`py.path.local` instance of the temporary directory.
:plugins: A list of plugins to use with :py:meth:`parseconfig` and
:ivar plugins: A list of plugins to use with :py:meth:`parseconfig` and
:py:meth:`runpytest`. Initially this is an empty list but plugins can
be added to the list. The type of items to add to the list depends on
the method using them so refer to them for details.
@@ -480,11 +536,7 @@ class Testdir:
self._sys_modules_snapshot = self.__take_sys_modules_snapshot()
self.chdir()
self.request.addfinalizer(self.finalize)
method = self.request.config.getoption("--runpytest")
if method == "inprocess":
self._runpytest_method = self.runpytest_inprocess
elif method == "subprocess":
self._runpytest_method = self.runpytest_subprocess
self._method = self.request.config.getoption("--runpytest")
mp = self.monkeypatch = MonkeyPatch()
mp.setenv("PYTEST_DEBUG_TEMPROOT", str(self.test_tmproot))
@@ -832,7 +884,7 @@ class Testdir:
reprec = rec.pop()
else:
class reprec:
class reprec: # type: ignore
pass
reprec.ret = ret
@@ -848,7 +900,7 @@ class Testdir:
for finalizer in finalizers:
finalizer()
def runpytest_inprocess(self, *args, **kwargs):
def runpytest_inprocess(self, *args, **kwargs) -> RunResult:
"""Return result of running pytest in-process, providing a similar
interface to what self.runpytest() provides.
"""
@@ -863,15 +915,20 @@ class Testdir:
try:
reprec = self.inline_run(*args, **kwargs)
except SystemExit as e:
ret = e.args[0]
try:
ret = ExitCode(e.args[0])
except ValueError:
pass
class reprec:
ret = e.args[0]
class reprec: # type: ignore
ret = ret
except Exception:
traceback.print_exc()
class reprec:
ret = 3
class reprec: # type: ignore
ret = ExitCode(3)
finally:
out, err = capture.readouterr()
@@ -879,17 +936,23 @@ class Testdir:
sys.stdout.write(out)
sys.stderr.write(err)
res = RunResult(reprec.ret, out.split("\n"), err.split("\n"), time.time() - now)
res.reprec = reprec
res = RunResult(
reprec.ret, out.splitlines(), err.splitlines(), time.time() - now
)
res.reprec = reprec # type: ignore
return res
def runpytest(self, *args, **kwargs):
def runpytest(self, *args, **kwargs) -> RunResult:
"""Run pytest inline or in a subprocess, depending on the command line
option "--runpytest" and return a :py:class:`RunResult`.
"""
args = self._ensure_basetemp(args)
return self._runpytest_method(*args, **kwargs)
if self._method == "inprocess":
return self.runpytest_inprocess(*args, **kwargs)
elif self._method == "subprocess":
return self.runpytest_subprocess(*args, **kwargs)
raise RuntimeError("Unrecognized runpytest option: {}".format(self._method))
def _ensure_basetemp(self, args):
args = list(args)
@@ -928,11 +991,9 @@ class Testdir:
This returns a new :py:class:`_pytest.config.Config` instance like
:py:meth:`parseconfig`, but also calls the pytest_configure hook.
"""
config = self.parseconfig(*args)
config._do_configure()
self.request.addfinalizer(config._ensure_unconfigure)
return config
def getitem(self, source, funcname="test_func"):
@@ -1048,7 +1109,7 @@ class Testdir:
return popen
def run(self, *cmdargs, timeout=None, stdin=CLOSE_STDIN):
def run(self, *cmdargs, timeout=None, stdin=CLOSE_STDIN) -> RunResult:
"""Run a command with arguments.
Run a process using subprocess.Popen saving the stdout and stderr.
@@ -1066,9 +1127,9 @@ class Testdir:
"""
__tracebackhide__ = True
cmdargs = [
cmdargs = tuple(
str(arg) if isinstance(arg, py.path.local) else arg for arg in cmdargs
]
)
p1 = self.tmpdir.join("stdout")
p2 = self.tmpdir.join("stderr")
print("running:", *cmdargs)
@@ -1119,6 +1180,10 @@ class Testdir:
f2.close()
self._dump_lines(out, sys.stdout)
self._dump_lines(err, sys.stderr)
try:
ret = ExitCode(ret)
except ValueError:
pass
return RunResult(ret, out, err, time.time() - now)
def _dump_lines(self, lines, fp):
@@ -1131,7 +1196,7 @@ class Testdir:
def _getpytestargs(self):
return sys.executable, "-mpytest"
def runpython(self, script):
def runpython(self, script) -> RunResult:
"""Run a python script using sys.executable as interpreter.
Returns a :py:class:`RunResult`.
@@ -1143,7 +1208,7 @@ class Testdir:
"""Run python -c "command", return a :py:class:`RunResult`."""
return self.run(sys.executable, "-c", command)
def runpytest_subprocess(self, *args, timeout=None):
def runpytest_subprocess(self, *args, timeout=None) -> RunResult:
"""Run pytest as a subprocess with given arguments.
Any plugins added to the :py:attr:`plugins` list will be added using the
@@ -1192,8 +1257,6 @@ class Testdir:
pexpect = pytest.importorskip("pexpect", "3.0")
if hasattr(sys, "pypy_version_info") and "64" in platform.machine():
pytest.skip("pypy-64 bit not supported")
if sys.platform.startswith("freebsd"):
pytest.xfail("pexpect does not work reliably on freebsd")
if not hasattr(pexpect, "spawn"):
pytest.skip("pexpect.spawn not available")
logfile = self.tmpdir.join("spawn.out").open("wb")
@@ -1219,7 +1282,7 @@ def getdecoded(out):
class LineComp:
def __init__(self):
self.stringio = py.io.TextIO()
self.stringio = StringIO()
def assert_contains_lines(self, lines2):
"""Assert that lines2 are contained (linearly) in lines1.
@@ -1319,8 +1382,7 @@ class LineMatcher:
The argument is a list of lines which have to match and can use glob
wildcards. If they do not match a pytest.fail() is called. The
matches and non-matches are also printed on stdout.
matches and non-matches are also shown as part of the error message.
"""
__tracebackhide__ = True
self._match_lines(lines2, fnmatch, "fnmatch")
@@ -1331,8 +1393,7 @@ class LineMatcher:
The argument is a list of lines which have to match using ``re.match``.
If they do not match a pytest.fail() is called.
The matches and non-matches are also printed on stdout.
The matches and non-matches are also shown as part of the error message.
"""
__tracebackhide__ = True
self._match_lines(lines2, lambda name, pat: re.match(pat, name), "re.match")
@@ -1347,14 +1408,14 @@ class LineMatcher:
pattern
:param str match_nickname: the nickname for the match function that
will be logged to stdout when a match occurs
"""
assert isinstance(lines2, Sequence)
assert isinstance(lines2, collections.abc.Sequence)
lines2 = self._getlines(lines2)
lines1 = self.lines[:]
nextline = None
extralines = []
__tracebackhide__ = True
wnick = len(match_nickname) + 1
for line in lines2:
nomatchprinted = False
while lines1:
@@ -1364,14 +1425,63 @@ class LineMatcher:
break
elif match_func(nextline, line):
self._log("%s:" % match_nickname, repr(line))
self._log(" with:", repr(nextline))
self._log(
"{:>{width}}".format("with:", width=wnick), repr(nextline)
)
break
else:
if not nomatchprinted:
self._log("nomatch:", repr(line))
self._log(
"{:>{width}}".format("nomatch:", width=wnick), repr(line)
)
nomatchprinted = True
self._log(" and:", repr(nextline))
self._log("{:>{width}}".format("and:", width=wnick), repr(nextline))
extralines.append(nextline)
else:
self._log("remains unmatched: {!r}".format(line))
pytest.fail(self._log_text)
msg = "remains unmatched: {!r}".format(line)
self._log(msg)
self._fail(msg)
self._log_output = []
def no_fnmatch_line(self, pat):
"""Ensure captured lines do not match the given pattern, using ``fnmatch.fnmatch``.
:param str pat: the pattern to match lines.
"""
__tracebackhide__ = True
self._no_match_line(pat, fnmatch, "fnmatch")
def no_re_match_line(self, pat):
"""Ensure captured lines do not match the given pattern, using ``re.match``.
:param str pat: the regular expression to match lines.
"""
__tracebackhide__ = True
self._no_match_line(pat, lambda name, pat: re.match(pat, name), "re.match")
def _no_match_line(self, pat, match_func, match_nickname):
"""Ensure captured lines does not have a the given pattern, using ``fnmatch.fnmatch``
:param str pat: the pattern to match lines
"""
__tracebackhide__ = True
nomatch_printed = False
wnick = len(match_nickname) + 1
for line in self.lines:
if match_func(line, pat):
msg = "{}: {!r}".format(match_nickname, pat)
self._log(msg)
self._log("{:>{width}}".format("with:", width=wnick), repr(line))
self._fail(msg)
else:
if not nomatch_printed:
self._log("{:>{width}}".format("nomatch:", width=wnick), repr(pat))
nomatch_printed = True
self._log("{:>{width}}".format("and:", width=wnick), repr(line))
self._log_output = []
def _fail(self, msg):
__tracebackhide__ = True
log_text = self._log_text
self._log_output = []
pytest.fail(log_text)

View File

@@ -9,6 +9,8 @@ from collections import Counter
from collections.abc import Sequence
from functools import partial
from textwrap import dedent
from typing import List
from typing import Tuple
import py
@@ -30,6 +32,7 @@ from _pytest.compat import safe_getattr
from _pytest.compat import safe_isclass
from _pytest.compat import STRING_TYPES
from _pytest.config import hookimpl
from _pytest.deprecated import FUNCARGNAMES
from _pytest.main import FSHookProxy
from _pytest.mark import MARK_GEN
from _pytest.mark.structures import get_unpacked_marks
@@ -118,13 +121,6 @@ def pytest_cmdline_main(config):
def pytest_generate_tests(metafunc):
# those alternative spellings are common - raise a specific error to alert
# the user
alt_spellings = ["parameterize", "parametrise", "parameterise"]
for mark_name in alt_spellings:
if metafunc.definition.get_closest_marker(mark_name):
msg = "{0} has '{1}' mark, spelling should be 'parametrize'"
fail(msg.format(metafunc.function.__name__, mark_name), pytrace=False)
for marker in metafunc.definition.iter_markers(name="parametrize"):
metafunc.parametrize(*marker.args, **marker.kwargs)
@@ -210,8 +206,8 @@ def pytest_pycollect_makeitem(collector, name, obj):
# mock seems to store unbound methods (issue473), normalize it
obj = getattr(obj, "__func__", obj)
# We need to try and unwrap the function if it's a functools.partial
# or a funtools.wrapped.
# We musn't if it's been wrapped with mock.patch (python 2 only)
# or a functools.wrapped.
# We mustn't if it's been wrapped with mock.patch (python 2 only)
if not (inspect.isfunction(obj) or inspect.isfunction(get_real_func(obj))):
filename, lineno = getfslineno(obj)
warnings.warn_explicit(
@@ -235,10 +231,6 @@ def pytest_pycollect_makeitem(collector, name, obj):
outcome.force_result(res)
def pytest_make_parametrize_id(config, val, argname=None):
return None
class PyobjContext:
module = pyobj_property("Module")
cls = pyobj_property("Class")
@@ -285,10 +277,9 @@ class PyobjMixin(PyobjContext):
break
parts.append(name)
parts.reverse()
s = ".".join(parts)
return s.replace(".[", "[")
return ".".join(parts)
def reportinfo(self):
def reportinfo(self) -> Tuple[str, int, str]:
# XXX caching?
obj = self.obj
compat_co_firstlineno = getattr(obj, "compat_co_firstlineno", None)
@@ -595,7 +586,7 @@ class Package(Module):
# one or more conftests are not in use at this fspath
proxy = FSHookProxy(fspath, pm, remove_mods)
else:
# all plugis are active for this fspath
# all plugins are active for this fspath
proxy = self.config.hook
return proxy
@@ -881,7 +872,7 @@ class CallSpec2:
self.marks.extend(normalize_mark_list(marks))
class Metafunc(fixtures.FuncargnamesCompatAttr):
class Metafunc:
"""
Metafunc objects are passed to the :func:`pytest_generate_tests <_pytest.hookspec.pytest_generate_tests>` hook.
They help to inspect a test function and to generate tests according to
@@ -889,11 +880,14 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
test function is defined.
"""
def __init__(self, definition, fixtureinfo, config, cls=None, module=None):
assert (
isinstance(definition, FunctionDefinition)
or type(definition).__name__ == "DefinitionMock"
)
def __init__(
self,
definition: "FunctionDefinition",
fixtureinfo,
config,
cls=None,
module=None,
) -> None:
self.definition = definition
#: access to the :class:`_pytest.config.Config` object for the test session
@@ -911,10 +905,15 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
#: class object where the test function is defined in or ``None``.
self.cls = cls
self._calls = []
self._ids = set()
self._calls = [] # type: List[CallSpec2]
self._arg2fixturedefs = fixtureinfo.name2fixturedefs
@property
def funcargnames(self):
""" alias attribute for ``fixturenames`` for pre-2.3 compatibility"""
warnings.warn(FUNCARGNAMES, stacklevel=2)
return self.fixturenames
def parametrize(self, argnames, argvalues, indirect=False, ids=None, scope=None):
""" Add new invocations to the underlying test function using the list
of argvalues for the given argnames. Parametrization is performed
@@ -965,6 +964,12 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
)
del argvalues
if "request" in argnames:
fail(
"'request' is a reserved name and cannot be used in @pytest.mark.parametrize",
pytrace=False,
)
if scope is None:
scope = _find_parametrized_scope(argnames, self._arg2fixturedefs, indirect)
@@ -1161,7 +1166,8 @@ def _idval(val, argname, idx, idfn, item, config):
return ascii_escaped(val.pattern)
elif isinstance(val, enum.Enum):
return str(val)
elif (inspect.isclass(val) or inspect.isfunction(val)) and hasattr(val, "__name__"):
elif hasattr(val, "__name__") and isinstance(val.__name__, str):
# name of a class, function, module, etc.
return val.__name__
return str(argname) + str(idx)
@@ -1331,7 +1337,7 @@ def write_docstring(tw, doc, indent=" "):
tw.write(indent + line + "\n")
class Function(FunctionMixin, nodes.Item, fixtures.FuncargnamesCompatAttr):
class Function(FunctionMixin, nodes.Item):
""" a Function Item is responsible for setting up and executing a
Python test function.
"""
@@ -1418,6 +1424,12 @@ class Function(FunctionMixin, nodes.Item, fixtures.FuncargnamesCompatAttr):
"(compatonly) for code expecting pytest-2.2 style request objects"
return self
@property
def funcargnames(self):
""" alias attribute for ``fixturenames`` for pre-2.3 compatibility"""
warnings.warn(FUNCARGNAMES, stacklevel=2)
return self.fixturenames
def runtest(self):
""" execute the underlying test function. """
self.ihook.pytest_pyfunc_call(pyfuncitem=self)

View File

@@ -223,26 +223,24 @@ class ApproxScalar(ApproxBase):
def __repr__(self):
"""
Return a string communicating both the expected value and the tolerance
for the comparison being made, e.g. '1.0 +- 1e-6'. Use the unicode
plus/minus symbol if this is python3 (it's too hard to get right for
python2).
for the comparison being made, e.g. '1.0 ± 1e-6', '(3+4j) ± 5e-6 ∠ ±180°'.
"""
if isinstance(self.expected, complex):
return str(self.expected)
# Infinities aren't compared using tolerances, so don't show a
# tolerance.
if math.isinf(self.expected):
# tolerance. Need to call abs to handle complex numbers, e.g. (inf + 1j)
if math.isinf(abs(self.expected)):
return str(self.expected)
# If a sensible tolerance can't be calculated, self.tolerance will
# raise a ValueError. In this case, display '???'.
try:
vetted_tolerance = "{:.1e}".format(self.tolerance)
if isinstance(self.expected, complex) and not math.isinf(self.tolerance):
vetted_tolerance += " ∠ ±180°"
except ValueError:
vetted_tolerance = "???"
return "{} \u00b1 {}".format(self.expected, vetted_tolerance)
return "{} ± {}".format(self.expected, vetted_tolerance)
def __eq__(self, actual):
"""
@@ -554,7 +552,7 @@ def raises(
@overload # noqa: F811
def raises(
def raises( # noqa: F811
expected_exception: Union["Type[_E]", Tuple["Type[_E]", ...]],
func: Callable,
*args: Any,

View File

@@ -60,18 +60,18 @@ def warns(
*,
match: "Optional[Union[str, Pattern]]" = ...
) -> "WarningsChecker":
... # pragma: no cover
raise NotImplementedError()
@overload # noqa: F811
def warns(
def warns( # noqa: F811
expected_warning: Union["Type[Warning]", Tuple["Type[Warning]", ...]],
func: Callable,
*args: Any,
match: Optional[Union[str, "Pattern"]] = ...,
**kwargs: Any
) -> Union[Any]:
... # pragma: no cover
raise NotImplementedError()
def warns( # noqa: F811
@@ -187,7 +187,7 @@ class WarningsRecorder(warnings.catch_warnings):
exc_type: Optional["Type[BaseException]"],
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType],
) -> bool:
) -> None:
if not self._entered:
__tracebackhide__ = True
raise RuntimeError("Cannot exit %r without entering first" % self)
@@ -198,8 +198,6 @@ class WarningsRecorder(warnings.catch_warnings):
# manually here for this context manager to become reusable.
self._entered = False
return False
class WarningsChecker(WarningsRecorder):
def __init__(
@@ -232,7 +230,7 @@ class WarningsChecker(WarningsRecorder):
exc_type: Optional["Type[BaseException]"],
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType],
) -> bool:
) -> None:
super().__exit__(exc_type, exc_val, exc_tb)
__tracebackhide__ = True
@@ -263,4 +261,3 @@ class WarningsChecker(WarningsRecorder):
[each.message for each in self],
)
)
return False

View File

@@ -1,5 +1,9 @@
from io import StringIO
from pprint import pprint
from typing import List
from typing import Optional
from typing import Tuple
from typing import Union
import py
@@ -13,6 +17,7 @@ from _pytest._code.code import ReprFuncArgs
from _pytest._code.code import ReprLocals
from _pytest._code.code import ReprTraceback
from _pytest._code.code import TerminalRepr
from _pytest.nodes import Node
from _pytest.outcomes import skip
from _pytest.pathlib import Path
@@ -31,14 +36,17 @@ def getslaveinfoline(node):
class BaseReport:
when = None # type: Optional[str]
location = None
location = None # type: Optional[Tuple[str, Optional[int], str]]
longrepr = None
sections = [] # type: List[Tuple[str, str]]
nodeid = None # type: str
def __init__(self, **kw):
self.__dict__.update(kw)
def toterminal(self, out):
def toterminal(self, out) -> None:
if hasattr(self, "node"):
out.line(getslaveinfoline(self.node))
out.line(getslaveinfoline(self.node)) # type: ignore
longrepr = self.longrepr
if longrepr is None:
@@ -179,7 +187,7 @@ class BaseReport:
def _report_unserialization_failure(type_name, report_class, reportdict):
url = "https://github.com/pytest-dev/pytest/issues"
stream = py.io.TextIO()
stream = StringIO()
pprint("-" * 100, stream=stream)
pprint("INTERNALERROR: Unknown entry type returned: %s" % type_name, stream=stream)
pprint("report_name: %s" % report_class, stream=stream)
@@ -199,7 +207,7 @@ class TestReport(BaseReport):
def __init__(
self,
nodeid,
location,
location: Tuple[str, Optional[int], str],
keywords,
outcome,
longrepr,
@@ -208,14 +216,14 @@ class TestReport(BaseReport):
duration=0,
user_properties=None,
**extra
):
) -> None:
#: normalized collection node id
self.nodeid = nodeid
#: a (filesystempath, lineno, domaininfo) tuple indicating the
#: actual location of a test item - it might be different from the
#: collected one e.g. if a method is inherited from a different module.
self.location = location
self.location = location # type: Tuple[str, Optional[int], str]
#: a name -> value dictionary containing all keywords and
#: markers associated with a test invocation.
@@ -267,7 +275,8 @@ class TestReport(BaseReport):
if not isinstance(excinfo, ExceptionInfo):
outcome = "failed"
longrepr = excinfo
elif excinfo.errisinstance(skip.Exception):
# Type ignored -- see comment where skip.Exception is defined.
elif excinfo.errisinstance(skip.Exception): # type: ignore
outcome = "skipped"
r = excinfo._getreprcrash()
longrepr = (str(r.path), r.lineno, r.message)
@@ -297,7 +306,9 @@ class TestReport(BaseReport):
class CollectReport(BaseReport):
when = "collect"
def __init__(self, nodeid, outcome, longrepr, result, sections=(), **extra):
def __init__(
self, nodeid: str, outcome, longrepr, result: List[Node], sections=(), **extra
) -> None:
self.nodeid = nodeid
self.outcome = outcome
self.longrepr = longrepr
@@ -319,25 +330,25 @@ class CollectErrorRepr(TerminalRepr):
def __init__(self, msg):
self.longrepr = msg
def toterminal(self, out):
def toterminal(self, out) -> None:
out.line(self.longrepr, red=True)
def pytest_report_to_serializable(report):
if isinstance(report, (TestReport, CollectReport)):
data = report._to_json()
data["_report_type"] = report.__class__.__name__
data["$report_type"] = report.__class__.__name__
return data
def pytest_report_from_serializable(data):
if "_report_type" in data:
if data["_report_type"] == "TestReport":
if "$report_type" in data:
if data["$report_type"] == "TestReport":
return TestReport._from_json(data)
elif data["_report_type"] == "CollectReport":
elif data["$report_type"] == "CollectReport":
return CollectReport._from_json(data)
assert False, "Unknown report_type unserialize data: {}".format(
data["_report_type"]
data["$report_type"]
)
@@ -431,7 +442,7 @@ def _report_kwargs_from_json(reportdict):
reprlocals=reprlocals,
filelocrepr=reprfileloc,
style=data["style"],
)
) # type: Union[ReprEntry, ReprEntryNative]
elif entry_type == "ReprEntryNative":
reprentry = ReprEntryNative(data["lines"])
else:
@@ -469,7 +480,9 @@ def _report_kwargs_from_json(reportdict):
description,
)
)
exception_info = ExceptionChainRepr(chain)
exception_info = ExceptionChainRepr(
chain
) # type: Union[ExceptionChainRepr,ReprExceptionInfo]
else:
exception_info = ReprExceptionInfo(reprtraceback, reprcrash)

View File

@@ -3,6 +3,11 @@ import bdb
import os
import sys
from time import time
from typing import Callable
from typing import Dict
from typing import List
from typing import Optional
from typing import Tuple
import attr
@@ -10,10 +15,14 @@ from .reports import CollectErrorRepr
from .reports import CollectReport
from .reports import TestReport
from _pytest._code.code import ExceptionInfo
from _pytest.nodes import Node
from _pytest.outcomes import Exit
from _pytest.outcomes import Skipped
from _pytest.outcomes import TEST_OUTCOME
if False: # TYPE_CHECKING
from typing import Type
#
# pytest plugin hooks
@@ -99,8 +108,8 @@ def show_test_item(item):
tw = item.config.get_terminal_writer()
tw.line()
tw.write(" " * 8)
tw.write(item._nodeid)
used_fixtures = sorted(item._fixtureinfo.name2fixturedefs.keys())
tw.write(item.nodeid)
used_fixtures = sorted(getattr(item, "fixturenames", []))
if used_fixtures:
tw.write(" (fixtures used: {})".format(", ".join(used_fixtures)))
@@ -112,12 +121,18 @@ def pytest_runtest_setup(item):
def pytest_runtest_call(item):
_update_current_test_var(item, "call")
sys.last_type, sys.last_value, sys.last_traceback = (None, None, None)
try:
del sys.last_type
del sys.last_value
del sys.last_traceback
except AttributeError:
pass
try:
item.runtest()
except Exception:
# Store trace info to allow postmortem debugging
type, value, tb = sys.exc_info()
assert tb is not None
tb = tb.tb_next # Skip *this* frame
sys.last_type = type
sys.last_value = value
@@ -185,7 +200,7 @@ def check_interactive_exception(call, report):
def call_runtest_hook(item, when, **kwds):
hookname = "pytest_runtest_" + when
ihook = getattr(item.ihook, hookname)
reraise = (Exit,)
reraise = (Exit,) # type: Tuple[Type[BaseException], ...]
if not item.config.getoption("usepdb", False):
reraise += (KeyboardInterrupt,)
return CallInfo.from_call(
@@ -198,8 +213,7 @@ class CallInfo:
""" Result/Exception info a function invocation. """
_result = attr.ib()
# Optional[ExceptionInfo]
excinfo = attr.ib()
excinfo = attr.ib(type=Optional[ExceptionInfo])
start = attr.ib()
stop = attr.ib()
when = attr.ib()
@@ -211,7 +225,7 @@ class CallInfo:
return self._result
@classmethod
def from_call(cls, func, when, reraise=None):
def from_call(cls, func, when, reraise=None) -> "CallInfo":
#: context of invocation: one of "setup", "call",
#: "teardown", "memocollect"
start = time()
@@ -227,16 +241,9 @@ class CallInfo:
return cls(start=start, stop=stop, when=when, result=result, excinfo=excinfo)
def __repr__(self):
if self.excinfo is not None:
status = "exception"
value = self.excinfo.value
else:
# TODO: investigate unification
value = repr(self._result)
status = "result"
return "<CallInfo when={when!r} {status}: {value}>".format(
when=self.when, value=value, status=status
)
if self.excinfo is None:
return "<CallInfo when={!r} result: {!r}>".format(self.when, self._result)
return "<CallInfo when={!r} excinfo={!r}>".format(self.when, self.excinfo)
def pytest_runtest_makereport(item, call):
@@ -252,7 +259,8 @@ def pytest_make_collect_report(collector):
skip_exceptions = [Skipped]
unittest = sys.modules.get("unittest")
if unittest is not None:
skip_exceptions.append(unittest.SkipTest)
# Type ignored because unittest is loaded dynamically.
skip_exceptions.append(unittest.SkipTest) # type: ignore
if call.excinfo.errisinstance(tuple(skip_exceptions)):
outcome = "skipped"
r = collector._repr_failure_py(call.excinfo, "line").reprcrash
@@ -266,7 +274,7 @@ def pytest_make_collect_report(collector):
rep = CollectReport(
collector.nodeid, outcome, longrepr, getattr(call, "result", None)
)
rep.call = call # see collect_one_node
rep.call = call # type: ignore # see collect_one_node
return rep
@@ -274,8 +282,8 @@ class SetupState:
""" shared state for setting up/tearing down test items or collectors. """
def __init__(self):
self.stack = []
self._finalizers = {}
self.stack = [] # type: List[Node]
self._finalizers = {} # type: Dict[Node, List[Callable[[], None]]]
def addfinalizer(self, finalizer, colitem):
""" attach a finalizer to the given colitem. """
@@ -302,6 +310,7 @@ class SetupState:
exc = sys.exc_info()
if exc:
_, val, tb = exc
assert val is not None
raise val.with_traceback(tb)
def _teardown_with_finalization(self, colitem):
@@ -335,6 +344,7 @@ class SetupState:
exc = sys.exc_info()
if exc:
_, val, tb = exc
assert val is not None
raise val.with_traceback(tb)
def prepare(self, colitem):

View File

@@ -1,5 +1,3 @@
import sys
import pytest
@@ -22,8 +20,7 @@ def pytest_addoption(parser):
@pytest.hookimpl(hookwrapper=True)
def pytest_fixture_setup(fixturedef, request):
yield
config = request.config
if config.option.setupshow:
if request.config.option.setupshow:
if hasattr(request, "param"):
# Save the fixture parameter so ._show_fixture_action() can
# display it now and during the teardown (in .finish()).
@@ -51,7 +48,6 @@ def _show_fixture_action(fixturedef, msg):
capman = config.pluginmanager.getplugin("capturemanager")
if capman:
capman.suspend_global_capture()
out, err = capman.read_global_capture()
tw = config.get_terminal_writer()
tw.line()
@@ -74,8 +70,6 @@ def _show_fixture_action(fixturedef, msg):
if capman:
capman.resume_global_capture()
sys.stdout.write(out)
sys.stderr.write(err)
@pytest.hookimpl(tryfirst=True)

View File

@@ -16,7 +16,8 @@ def pytest_addoption(parser):
def pytest_fixture_setup(fixturedef, request):
# Will return a dummy fixture if the setuponly option is provided.
if request.config.option.setupplan:
fixturedef.cached_result = (None, None, None)
my_cache_key = fixturedef.cache_key(request)
fixturedef.cached_result = (None, my_cache_key, None)
return fixturedef.cached_result

View File

@@ -122,7 +122,7 @@ def pytest_runtest_makereport(item, call):
outcome = yield
rep = outcome.get_result()
evalxfail = getattr(item, "_evalxfail", None)
# unitttest special case, see setting of _unexpectedsuccess
# unittest special case, see setting of _unexpectedsuccess
if hasattr(item, "_unexpectedsuccess") and rep.when == "call":
if item._unexpectedsuccess:
@@ -132,7 +132,7 @@ def pytest_runtest_makereport(item, call):
rep.outcome = "failed"
elif item.config.option.runxfail:
pass # don't interefere
pass # don't interfere
elif call.excinfo and call.excinfo.errisinstance(xfail.Exception):
rep.wasxfail = "reason: " + call.excinfo.value.msg
rep.outcome = "skipped"
@@ -161,9 +161,9 @@ def pytest_runtest_makereport(item, call):
# skipped by mark.skipif; change the location of the failure
# to point to the item definition, otherwise it will display
# the location of where the skip exception was raised within pytest
filename, line, reason = rep.longrepr
_, _, reason = rep.longrepr
filename, line = item.location[:2]
rep.longrepr = filename, line, reason
rep.longrepr = filename, line + 1, reason
# called by terminalreporter progress reporting

Some files were not shown because too many files have changed in this diff Show More