Compare commits

...

372 Commits
4.2.1 ... 4.4.0

Author SHA1 Message Date
Daniel Hahler
4621638f07 Update CHANGELOG.rst
Co-Authored-By: nicoddemus <nicoddemus@gmail.com>
2019-03-29 20:29:40 -03:00
Bruno Oliveira
8881b201aa Preparing release version 4.4.0 2019-03-29 20:49:18 +00:00
Bruno Oliveira
278b289f37 Merge pull request #4968 from blueyed/pdb-do_debug-quit
pdb: do not raise outcomes.Exit with quit in debug
2019-03-29 16:22:02 -03:00
Daniel Hahler
e7ade066b6 Merge pull request #5011 from blueyed/merge-master-into-features
Merge master into features
2019-03-29 18:35:13 +01:00
Daniel Hahler
dee520e310 Merge pull request #5008 from blueyed/setup-cfg-tool-pytest
setup.cfg: use existing [tool:pytest] (ignoring [pytest])
2019-03-29 16:47:31 +01:00
Daniel Hahler
4e931b258d Merge master into features 2019-03-29 11:05:46 +01:00
Daniel Hahler
4011021823 pdb: do not raise outcomes.Exit with quit in debug 2019-03-29 11:02:34 +01:00
Daniel Hahler
bfda2a0050 setup.cfg: use existing [tool:pytest] (ignoring [pytest]) 2019-03-29 10:59:27 +01:00
Bruno Oliveira
2812c087ec Merge pull request #5010 from garytyler/docupdate
Update docs for 'pytest_cmdline_parse' hook to note availability limi…
2019-03-28 22:32:50 -03:00
Bruno Oliveira
6b5cddc48a Merge pull request #4951 from blueyed/fix-pdb-capfix
pdb: handle capturing with fixtures only
2019-03-28 20:34:28 -03:00
Gary Tyler
403f556928 Update docs for 'pytest_cmdline_parse' hook to note availability liminations 2019-03-28 19:25:55 -04:00
Bruno Oliveira
d8ef86aadf Merge pull request #4993 from blueyed/stepwise-report
stepwise: report status via pytest_report_collectionfinish
2019-03-28 20:24:18 -03:00
Bruno Oliveira
a9fe1e159a Merge pull request #4965 from nicoddemus/serialization-hooks
Serialization hooks
2019-03-28 20:22:19 -03:00
Bruno Oliveira
65c8e8a09e Rename hooks: to/from_serializable 2019-03-28 13:41:56 -03:00
Daniel Hahler
46d9243eb0 changelog 2019-03-28 11:56:53 +01:00
Daniel Hahler
63a01bdb33 Factor out pytestPDB._is_capturing 2019-03-28 11:49:01 +01:00
Daniel Hahler
d53209956b test_pdb_continue_with_recursive_debug: mock pdb.set_trace 2019-03-28 11:49:01 +01:00
Daniel Hahler
951213ee09 Use new suspend/resume in global_and_fixture_disabled 2019-03-28 11:49:01 +01:00
Daniel Hahler
ae067df941 add test_pdb_continue_with_recursive_debug 2019-03-28 11:49:01 +01:00
Daniel Hahler
40718efacc Fix/revisit do_continue with regard to conditions 2019-03-28 11:49:01 +01:00
Daniel Hahler
d406786a8d pdb: handle capturing with fixtures only 2019-03-28 11:49:01 +01:00
Daniel Hahler
0ac069da13 Merge pull request #5006 from blueyed/capture-clean
tests: ensure cleanup with configs via get_config()
2019-03-28 11:38:30 +01:00
Daniel Hahler
d17ea7a9c0 tests: ensure cleanup with configs via get_config()
Also done in test_pluginmanager, although no resource warnings are
there at least.

Fixes https://github.com/pytest-dev/pytest/issues/4355.
2019-03-28 00:14:13 +01:00
Daniel Hahler
c92021fc4f Merge pull request #5003 from blueyed/off
Fix off-by-one error with lineno in mark collection error
2019-03-28 00:09:53 +01:00
Daniel Hahler
50a5cebba8 Merge pull request #5002 from blueyed/report
skipping: factor out _get_pos, pass only config to _get_report_str
2019-03-27 23:07:11 +01:00
Bruno Oliveira
6c602c2282 Merge pull request #4995 from youknowone/disble_test_id_escaping
add ini option to disable string escape for parametrization
2019-03-27 17:34:35 -03:00
Daniel Hahler
76c70cbf4c Fix off-by-one error with lineno in mark collection error 2019-03-27 17:44:52 +01:00
Bruno Oliveira
3d9e68ecfd Update doc/en/parametrize.rst 2019-03-28 00:07:28 +09:00
Jeong YunWon
8b0b7156d9 Fix glitches of original patch of disable-test-id-escaping 2019-03-28 00:07:28 +09:00
ApaDoctor
cf6e2ceafd add ini option to disable string escape for parametrization 2019-03-28 00:07:28 +09:00
Bruno Oliveira
69a55d334a Merge pull request #5004 from blueyed/doc-pdb
doc: fix note about output capturing with pdb
2019-03-26 20:20:05 -03:00
Bruno Oliveira
241b7433cd Merge pull request #4978 from blueyed/exit-from-from_assertrepr_compare
Do not swallow outcomes.Exit in assertrepr_compare
2019-03-26 18:39:13 -03:00
Bruno Oliveira
057c97812b Merge pull request #4975 from blueyed/verbose-fixes
Fix usages of "verbose" option
2019-03-26 18:38:39 -03:00
Bruno Oliveira
02188e399d Merge pull request #4987 from blueyed/collect-tbstyle-repr_failure
CollectError.repr_failure: honor explicit tbstyle option
2019-03-26 18:37:49 -03:00
Bruno Oliveira
aae02863db Merge pull request #4999 from nicoddemus/cmdline_parse-early
Docs: modules implementing pytest_cmdline_parse can be early-loaded
2019-03-26 18:14:31 -03:00
Daniel Hahler
49f36bb028 Merge pull request #4988 from blueyed/logging-close
logging: close log_file_handler
2019-03-26 19:57:57 +01:00
Daniel Hahler
52730f6330 doc: fix note about output capturing with pdb
[skip travis]
2019-03-26 18:33:00 +01:00
Daniel Hahler
538efef1ba logging: close log_file_handler
While it should be closed in logging's shutdown [1], the following would
still issue a ResourceWarning:

```
import logging

log_file_handler = logging.FileHandler("temp.log", mode="w", encoding="UTF-8")

root_logger = logging.getLogger()
root_logger.addHandler(log_file_handler)
root_logger.removeHandler(log_file_handler)
root_logger.error("error")

del log_file_handler
```

It looks like the weakref might get lost for some reason.

See https://github.com/pytest-dev/pytest/pull/4981/commits/92ffe42b45 / #4981
for more information.

1: c1419578a1/Lib/logging/__init__.py (L2107-L2139)
2019-03-26 18:24:19 +01:00
Bruno Oliveira
9311d822c7 Fix assertion in pytest_report_unserialize 2019-03-26 12:47:31 -03:00
Daniel Hahler
351529cb50 skipping: factor out _get_pos, pass only config to _get_report_str 2019-03-26 16:29:16 +01:00
Daniel Hahler
94a2e3dddc stepwise: report status via pytest_report_collectionfinish 2019-03-26 13:20:33 +01:00
Bruno Oliveira
ee96214a8d Merge pull request #5000 from blueyed/merge-master-into-features
Merge master into features
2019-03-26 09:01:46 -03:00
Daniel Hahler
e1ae469504 Merge master into features 2019-03-26 10:23:21 +01:00
Daniel Hahler
0d00be4f4f Do not swallow outcomes.Exit in assertrepr_compare 2019-03-26 10:20:00 +01:00
Daniel Hahler
23146e7527 Fix usages of "verbose" option
With `-qq` `bool(config.getoption("verbose"))` is True; it needs to be
checked for `> 0`.
2019-03-26 10:11:25 +01:00
Daniel Hahler
b18df936ea changelog 2019-03-26 10:06:53 +01:00
Daniel Hahler
4148663706 Merge pull request #4979 from blueyed/minor
Minor: whitespace, typo, docs
2019-03-26 10:01:13 +01:00
Bruno Oliveira
3e1971eb16 Merge pull request #4994 from blueyed/test_as_errors-subprocess
test_as_errors: use subprocess with `-W`
2019-03-25 20:50:15 -03:00
Bruno Oliveira
bcdb86ee7e Merge pull request #4991 from blueyed/fix-tests
Fix pytest's own tests with `-W error::ResourceWarning`
2019-03-25 20:47:51 -03:00
Bruno Oliveira
2d77018d1b Improve coverage for _report_unserialization_failure 2019-03-25 20:16:59 -03:00
Bruno Oliveira
ceef0af1ae Improve coverage for to_json() with paths in reports 2019-03-25 20:16:59 -03:00
Bruno Oliveira
e4eec3416a Note that tests from xdist reference the correct xdist issues 2019-03-25 20:16:59 -03:00
Bruno Oliveira
645774295f Add CHANGELOG 2019-03-25 20:16:59 -03:00
Bruno Oliveira
f2e0c740d3 Code review suggestions 2019-03-25 20:16:59 -03:00
Bruno Oliveira
d856f4e51f Make sure TestReports are not collected as test classes 2019-03-25 20:16:59 -03:00
Bruno Oliveira
7b9a414524 Add pytest_report_serialize and pytest_report_unserialize hooks
These hooks will be used by pytest-xdist and pytest-subtests to
serialize and customize reports.
2019-03-25 20:16:59 -03:00
Bruno Oliveira
0c63f99016 Add experimental _to_json and _from_json to TestReport and CollectReport
This methods were moved from xdist (ca03269).

Our intention is to keep this code closer to the core, given that it
might break easily due to refactorings.

Having it in the core might also allow to improve the code by moving
some responsibility to the "code" objects (ReprEntry, etc) which
are often found in the reports.

Finally pytest-xdist and pytest-subtests can use those functions
instead of coding it themselves.
2019-03-25 20:16:59 -03:00
Daniel Hahler
3bc9cbea63 Merge pull request #4989 from blueyed/test_collect_capturing
test_collect_capturing: cover captured stderr
2019-03-25 23:41:53 +01:00
Daniel Hahler
6eff3069da Merge pull request #4851 from blueyed/addopts-vv
ci: PYTEST_ADDOPTS=-vv
2019-03-25 23:41:33 +01:00
Daniel Hahler
58a14b6b99 Merge pull request #4986 from blueyed/fnmatch_lines-list
tests: fnmatch_lines: use list
2019-03-25 23:31:04 +01:00
Daniel Hahler
b53bf44139 Merge pull request #4985 from blueyed/assert-from_current
ExceptionInfo.from_current: assert current exception
2019-03-25 23:28:58 +01:00
Daniel Hahler
d8758443bd Merge pull request #4983 from blueyed/coveragerc
.coveragerc: use "src" only from current dir
2019-03-25 23:28:23 +01:00
Daniel Hahler
51f64c2920 Merge pull request #4980 from blueyed/fixup_namespace_packages
monkeypatch.syspath_prepend: call fixup_namespace_packages
2019-03-25 23:10:00 +01:00
Bruno Oliveira
cea42ff9e4 Docs: modules implementing pytest_cmdline_parse can be early-loaded
Related to #4974
2019-03-25 18:43:06 -03:00
Daniel Hahler
2df9d05981 Merge pull request #4982 from blueyed/cover
Revisit coverage in some tests
2019-03-25 10:28:55 +01:00
Daniel Hahler
4142c41ffc Merge pull request #4990 from blueyed/session-repr
Session repr
2019-03-24 19:54:18 +01:00
Daniel Hahler
de44293d59 CollectError.repr_failure: honor explicit tbstyle option 2019-03-24 11:24:19 +01:00
Daniel Hahler
5efe6ab93c test_log_cli_auto_enable: get stdout once 2019-03-24 11:22:07 +01:00
Daniel Hahler
ce59f42ce1 revisit test_root_logger_affected 2019-03-24 11:21:13 +01:00
Daniel Hahler
7da7b9610c minor: whitespace 2019-03-24 11:20:24 +01:00
Daniel Hahler
d44e42ec15 doc: improve warning_record_to_str 2019-03-24 11:20:01 +01:00
Daniel Hahler
0ea1889265 test_as_errors: use subprocess with -W
Ref: https://github.com/pytest-dev/pytest/pull/4981
2019-03-24 11:17:55 +01:00
Daniel Hahler
6352cf2374 test_implicit_bad_repr1: harden/cleanup 2019-03-24 11:15:40 +01:00
Daniel Hahler
3127ec737b Fix pytest's own tests with -W error::ResourceWarning 2019-03-24 11:05:00 +01:00
Daniel Hahler
aa0b657e58 Add Session.__repr__ 2019-03-24 11:02:58 +01:00
Daniel Hahler
d0f3f26fff test_collect_capturing: cover captured stderr 2019-03-23 23:17:07 +01:00
Daniel Hahler
08f3b02dfc tests: fnmatch_lines: use list
For strings fnmatch_lines converts it into a Source objects, splitted on
newlines.  This is not necessary here, and it is more consistent to use
lists here in the first place.
2019-03-23 11:36:18 +01:00
Daniel Hahler
2d690b83bf ExceptionInfo.from_current: assert current exception 2019-03-23 00:29:36 +01:00
Daniel Hahler
0642da0145 .coveragerc: use "src" only from current dir
This avoids including generated test files with "src" in their path.
2019-03-22 17:48:14 +01:00
Daniel Hahler
afa985c135 Revisit coverage in some tests 2019-03-22 17:26:16 +01:00
Daniel Hahler
fd64fa1863 Revisit test_importplugin_error_message
Should be more helpful in case of errors than before:

    >       assert re.match(expected_message, str(excinfo.value))
    E       _pytest.warning_types.PytestWarning: asserting the value None, please use "assert is None"

    https://travis-ci.org/pytest-dev/pytest/jobs/509970576#L208
2019-03-22 17:02:26 +01:00
Daniel Hahler
56dc01ffe0 minor: revisit _possibly_invalidate_import_caches 2019-03-22 17:02:26 +01:00
Daniel Hahler
5df45f5b27 Use fixup_namespace_packages also with pytester.syspathinsert 2019-03-22 17:02:26 +01:00
Daniel Hahler
05d55b86df tests: minor sys.path cleanup 2019-03-22 16:20:55 +01:00
Daniel Hahler
475119988c monkeypatch.syspath_prepend: call fixup_namespace_packages
Without the patch the test fails as follows:

            # Prepending should call fixup_namespace_packages.
            monkeypatch.syspath_prepend("world")
    >       import ns_pkg.world
    E       ModuleNotFoundError: No module named 'ns_pkg.world'
2019-03-22 15:29:08 +01:00
Daniel Hahler
7a6bcc3639 Add reference to test_cmdline_python_namespace_package 2019-03-22 13:23:44 +01:00
Daniel Hahler
8e125c9759 doc/en/reference.rst: whitespace/alignment 2019-03-22 13:23:44 +01:00
Daniel Hahler
ade773390a minor: rename inner test 2019-03-22 13:23:44 +01:00
Daniel Hahler
5c26ba9cb1 minor: wrap_session: s/Spurious/unexpected/ 2019-03-22 13:23:44 +01:00
Daniel Hahler
5a544d4fac tox.ini: usedevelop implies skipsdist 2019-03-22 13:23:44 +01:00
Daniel Hahler
2e7d6a6202 Fix test_assertrewrite in verbose mode
Fixes https://github.com/pytest-dev/pytest/issues/4879.
2019-03-22 13:00:35 +01:00
Daniel Hahler
ea7357bc58 ci: PYTEST_ADDOPTS=-vv in general
This is useful when viewing logs, especially with hanging tests.

Uses non-verbose mode with a single job for full coverage.
2019-03-22 07:56:15 +01:00
Bruno Oliveira
b3319a6074 Merge pull request #4944 from henrykironde/k-EXPRESSION
Add example for k flag
2019-03-21 19:33:53 -03:00
Bruno Oliveira
c9628c52d6 Merge pull request #4971 from bskinn/patch-1
Fix pytestmark syntax in reference.rst
2019-03-21 18:59:25 -03:00
Brian Skinn
dcbdcc729b Fix pytestmark syntax in reference.rst
pytest 4.3.1 throws an error if `pytestmark` is set to a tuple of marks; it appears to insist on a list.



With `pytestmark = [pytest.mark.api, pytest.mark.good]`:

```
============================== test session starts ==============================
platform win32 -- Python 3.6.3, pytest-4.3.1, py-1.8.0, pluggy-0.9.0
rootdir: C:\Temp\git\sphobjinv, inifile: tox.ini
plugins: timeout-1.3.3
collected 48 items / 41 deselected / 7 selected

tests\test_api_good.py .......                                             [100%]

==================== 7 passed, 41 deselected in 0.15 seconds ====================
```


With `pytestmark = (pytest.mark.api, pytest.mark.good)`:
```
==================================== ERRORS =====================================
____________________ ERROR collecting tests/test_api_good.py ____________________
env\lib\site-packages\_pytest\runner.py:226: in from_call
    result = func()
env\lib\site-packages\_pytest\runner.py:289: in <lambda>
    call = CallInfo.from_call(lambda: list(collector.collect()), "collect")
env\lib\site-packages\_pytest\python.py:435: in collect
    self._inject_setup_module_fixture()
env\lib\site-packages\_pytest\python.py:447: in _inject_setup_module_fixture
    setup_module = _get_non_fixture_func(self.obj, "setUpModule")
env\lib\site-packages\_pytest\python.py:255: in obj
    self.own_markers.extend(get_unpacked_marks(self.obj))
env\lib\site-packages\_pytest\mark\structures.py:244: in get_unpacked_marks
    return normalize_mark_list(mark_list)
env\lib\site-packages\_pytest\mark\structures.py:259: in normalize_mark_list
    raise TypeError("got {!r} instead of Mark".format(mark))
E   TypeError: got (MarkDecorator(mark=Mark(name='api', args=(), kwargs={})), MarkDecorator(mark=Mark(name='good', args=(), kwargs={}))) instead of Mark
!!!!!!!!!!!!!!!!!!!! Interrupted: 1 errors during collection !!!!!!!!!!!!!!!!!!!!
==================== 19 deselected, 1 error in 0.27 seconds =====================
```
2019-03-21 15:14:28 -04:00
Daniel Hahler
15d608867d Merge pull request #4966 from blueyed/fix-preparse
config: fix consider_preparse with missing argument to -p
2019-03-21 19:12:57 +01:00
Daniel Hahler
ea2c6b8a88 config: fix consider_preparse with missing argument to -p
This is only required after/with 415899d4 - otherwise argparse ensures
there is an argument already.
2019-03-21 17:05:22 +01:00
Daniel Hahler
0e6cf0ff28 Merge pull request #4967 from blueyed/p-no-default
Fix some issues related to "-p no:X" with default_plugins
2019-03-21 17:02:34 +01:00
Daniel Hahler
553951c443 Fix some issues related to "-p no:X" with default_plugins 2019-03-21 17:01:50 +01:00
Bruno Oliveira
15ef168821 Merge pull request #4962 from blueyed/test_report_collect_after_half_a_second
tests: add test_report_collect_after_half_a_second
2019-03-20 19:35:08 -03:00
Daniel Hahler
cc6e5ec345 tests: add test_report_collect_after_half_a_second
This is meant for stable coverage with "collecting X item(s)".
2019-03-20 22:13:11 +01:00
Daniel Hahler
77643122a8 Merge pull request #4963 from blueyed/twisted
ci: rename "trial" tox factor to "twisted"
2019-03-20 21:58:48 +01:00
Daniel Hahler
832cef953b Merge pull request #4964 from blueyed/mkdir
Revisit mkdir/_ensure_supporting_files in cacheprovider
2019-03-20 21:58:24 +01:00
Daniel Hahler
bcdbb6b677 Revisit mkdir/_ensure_supporting_files in cacheprovider
- cacheprovider: move call to _ensure_supporting_files

  This makes it less likely to have a race here (which is not critical),
  but happened previously probably with xdist, causing flaky coverage with
  `if not readme_path.is_file():` etc checks in
  `_ensure_supporting_files`, which has been removed in the `features`
  branch already.
2019-03-20 19:00:11 +01:00
Daniel Hahler
543779fc43 tox: generic twisted factor 2019-03-20 18:41:48 +01:00
Daniel Hahler
2ade3d5c89 ci: rename "trial" tox factor to "twisted"
Ref: https://github.com/pytest-dev/pytest/pull/4848#issuecomment-467909204
2019-03-20 18:38:50 +01:00
Daniel Hahler
7939e5327c Merge pull request #4957 from blueyed/config-handle-pno-with-default-plugins
config: handle `-p no:plugin` with default plugins
2019-03-20 03:32:03 +01:00
Daniel Hahler
f7171034f9 terminal: remove unnecessary check in _get_progress_information_message
All calls to _get_progress_information_message are only done for
`_show_progress_info`, which is `False` with `capture=no`.
2019-03-20 03:04:41 +01:00
Daniel Hahler
c7c120fba6 terminal: handle "capture" option not being available
This is the case with `-p no:capture` now.
2019-03-20 03:01:26 +01:00
Daniel Hahler
415899d428 config: handle -p no:plugin with default plugins
`-p no:capture` should not load its fixtures in the first place.
2019-03-20 02:47:13 +01:00
Daniel Hahler
8dda5613ef Merge pull request #4956 from blueyed/home2
pytester: set HOME only with inline_run/popen
2019-03-20 02:29:29 +01:00
Daniel Hahler
714f2113bb Merge pull request #4929 from blueyed/fix-_factorytraceback-offset
Fix line offsets with `ScopeMismatch` errors
2019-03-20 01:30:58 +01:00
Daniel Hahler
a50b92ea67 pytester: set HOME only with inline_run/popen
Ref: https://github.com/pytest-dev/pytest/issues/4955
2019-03-20 01:15:51 +01:00
Daniel Hahler
da81c1e49a Merge pull request #4950 from blueyed/capture
Revisit capturing module: repr, doc fixes, minor
2019-03-20 01:12:22 +01:00
Bruno Oliveira
23ab43233e Merge pull request #4920 from nicoddemus/subtests-pytest-1367
Internal refactorings required for *external* pytest-subtests plugin
2019-03-19 18:57:11 -03:00
Bruno Oliveira
1a119a22d1 Internal refactorings in order to support the new pytest-subtests plugin
Related to #1367
2019-03-19 18:20:41 -03:00
Daniel Hahler
77c5191ad7 Merge pull request #4953 from blueyed/merge-master-into-features
Merge master into features
2019-03-19 04:13:31 +01:00
Daniel Hahler
7395501d1d Easier read with _colorama_workaround/_readline_workaround 2019-03-19 01:17:21 +01:00
Daniel Hahler
920bffbfbb Revisit _pytest.capture: repr, doc fixes, minor 2019-03-19 01:17:21 +01:00
Daniel Hahler
751c061d9a Merge master into features 2019-03-19 01:07:10 +01:00
Daniel Hahler
a624b84097 Merge pull request #4941 from blueyed/testdir-home
pytester: testdir: set $HOME to tmpdir
2019-03-18 23:02:15 +01:00
Daniel Hahler
c75dd10671 pytester: testdir: set $HOME to tmpdir
This avoids loading user configuration, which might interfere with test
results, e.g. a `~/.pdbrc.py` with pdb++.

Also sets USERPROFILE, which will be required with Python 3.8 [1].

1: https://bugs.python.org/issue36264
2019-03-18 20:55:39 +01:00
Bruno Oliveira
b696666f5a Merge pull request #4945 from blueyed/FDCapture-repr-None-targetfd_save
capture: fix FDCapture.__repr__ without targetfd_save
2019-03-18 13:09:30 -03:00
Bruno Oliveira
f4f6cb7532 Merge pull request #4946 from blueyed/fix-bench
Fix bench/bench.py without args
2019-03-18 13:04:12 -03:00
henrykironde
1e3d5a0412 Add example for k flag
Includes an example of multiple expressions
2019-03-17 22:33:16 -04:00
Daniel Hahler
98981276a0 capture: fix FDCapture.__repr__ without targetfd_save 2019-03-18 02:33:03 +01:00
Daniel Hahler
8c96b65082 Fix bench/bench.py without args
Fixes:

>   File "…/Vcs/pytest/src/_pytest/config/__init__.py", line 60, in main
>     config = _prepareconfig(args, plugins)
>   File "…/Vcs/pytest/src/_pytest/config/__init__.py", line 179, in _prepareconfig
>     raise TypeError(msg.format(args, type(args)))
> TypeError: `args` parameter expected to be a list or tuple of strings, got: 'empty.py' (type: <class 'str'>)
2019-03-18 02:28:41 +01:00
Bruno Oliveira
c926999cfb Merge pull request #4939 from smheidrich/mark_xfail_language
Minor grammar fixes in pytest.mark.xfail docs
2019-03-17 11:33:20 -03:00
smheidrich
519157cfcf Minor grammar fixes in pytest.mark.xfail docs 2019-03-17 10:14:40 +01:00
Bruno Oliveira
5d14362a75 Merge pull request #4936 from blueyed/use-blocked-plugin
Handle `-p plug` after `-p no:plug`
2019-03-16 12:21:33 -03:00
Daniel Hahler
15fe8c6e90 Handle -p plug after -p no:plug.
This can be used to override a blocked plugin (e.g. in "addopts") from the
command line etc.
2019-03-16 15:58:00 +01:00
Daniel Hahler
c1e01c2992 Merge pull request #4931 from blueyed/linematcher-list
pytester: LineMatcher: assert lines
2019-03-15 23:43:23 +01:00
Daniel Hahler
5e27ea5528 pytester: LineMatcher: assert Sequence when matching in order
This can be helpful when passing a set accidentally.
2019-03-15 23:07:08 +01:00
Bruno Oliveira
33d4c96aa2 Merge pull request #4830 from nicoddemus/warn-on-coroutine
Emit a warning when a coroutine test function is encountered
2019-03-15 09:51:34 -03:00
Bruno Oliveira
b3eb5d1eb7 Merge pull request #4932 from hroncok/sphinx2
Pin sphinx-removed-in to >= 0.2.0 to support Sphinx 2.0
2019-03-15 08:18:41 -03:00
Miro Hrončok
2af0a023c9 Pin sphinx-removed-in to >= 0.2.0 to support Sphinx 2.0
Fixes https://github.com/pytest-dev/pytest/issues/4912
2019-03-15 10:56:13 +01:00
Anthony Sottile
5f52d5ee17 Merge pull request #4927 from tkf/skip-doctest
Make pytest.skip work in doctest
2019-03-15 00:14:09 -07:00
Daniel Hahler
95701566f3 Update src/_pytest/outcomes.py
Co-Authored-By: tkf <takafumi.a@gmail.com>
2019-03-15 12:21:48 +09:00
Daniel Hahler
57be1d60dd Apply suggestions from code review
Co-Authored-By: tkf <takafumi.a@gmail.com>
2019-03-15 11:29:16 +09:00
Takafumi Arakaki
62f96eea6b Include documentation 2019-03-15 11:14:50 +09:00
Takafumi Arakaki
fa3cca51e1 Test pytest.skip in doctest 2019-03-15 11:06:57 +09:00
Daniel Hahler
d441fa66fe Fix line offsets with ScopeMismatch errors
Fixes https://github.com/pytest-dev/pytest/issues/4928.
2019-03-15 02:47:33 +01:00
Takafumi Arakaki
43aee15ba3 Make pytest.skip work in doctest 2019-03-15 10:20:46 +09:00
Bruno Oliveira
5c57d92978 Merge pull request #4926 from blueyed/merge-master-into-features
Merge master into features
2019-03-14 21:59:13 -03:00
Daniel Hahler
7afe17740f Merge master into features 2019-03-15 00:52:12 +01:00
Daniel Hahler
158432217c Merge pull request #4924 from blueyed/fix-pdbpp
Fix/harden some pdb tests
2019-03-15 00:51:12 +01:00
Daniel Hahler
437ff1c01a Merge pull request #4925 from blueyed/pm-super
pdb: post_mortem: use super()
2019-03-15 00:50:46 +01:00
Bruno Oliveira
40072b9511 Emit a warning when a async def function is not handled by a plugin
Fix #2224
2019-03-14 20:22:23 -03:00
Daniel Hahler
520af9d767 pdb: post_mortem: use super()
This is good practice in general, and I've seen it cause problems (MRO)
with pdb++.
2019-03-14 22:29:57 +01:00
Daniel Hahler
bdac9d3dd0 tests: improve test_pdb_interaction_doctest
- ignore pdbrc (might be done in general, but this was the only affected
  test)
- fail faster in case of unexpected failure
2019-03-14 19:16:34 +01:00
Daniel Hahler
37158f5303 tests: fix test_pdb_interaction_continue_recursive with pdbpp 2019-03-14 19:16:34 +01:00
Bruno Oliveira
612c3784e5 Merge pull request #4881 from blueyed/travis-cache
ci: Travis: disable cache by default, only for pre-commit
2019-03-14 12:39:47 -03:00
Daniel Hahler
951e07d71d Merge pull request #4919 from blueyed/opt
Optimize TracebackEntry.ishidden
2019-03-14 16:37:57 +01:00
Bruno Oliveira
36f774a8fb Merge pull request #4922 from hroncok/add_object_type
Remove deprecated Sphinx directive add_description_unit()
2019-03-14 12:31:34 -03:00
Bruno Oliveira
a2b921f890 Merge pull request #4921 from nicoddemus/mtime-test-for-4903
Add test for mtime issue in #4903
2019-03-14 10:56:08 -03:00
Bruno Oliveira
bd70f5c148 Add test for mtime issue in #4903 2019-03-14 10:16:08 -03:00
Miro Hrončok
134b957bf4 Remove deprecated Sphinx directive add_description_unit()
Partial solution for https://github.com/pytest-dev/pytest/issues/4912
2019-03-14 12:16:59 +01:00
Daniel Hahler
4d21dc4f2d Optimize TracebackEntry.ishidden
The expected behavior is that there is no "__tracebackhide__" attribute,
so use `getattr` instead of multiple try/except.
2019-03-14 01:02:46 +01:00
Bruno Oliveira
74416525d2 Merge pull request #4903 from bmwiedemann/y2038
Allow tests to pass after 2038
2019-03-13 20:07:25 -03:00
Bruno Oliveira
44cb51010c Improve CHANGELOG and code comment 2019-03-13 18:52:30 -03:00
Bruno Oliveira
90597226eb Merge pull request #4829 from nicoddemus/yield-tests-dead-code
Remove dead-code related to yield tests
2019-03-13 14:31:11 -03:00
Bruno Oliveira
7fb5ad82d9 Merge pull request #4916 from blueyed/pin-shinx
docs: pin Sphinx to <2.0
2019-03-12 22:55:24 -03:00
Daniel Hahler
f4bcb44025 docs: pin Sphinx to <2.0
Ref: https://github.com/pytest-dev/pytest/issues/4912
2019-03-13 02:36:15 +01:00
Bruno Oliveira
b7ae7a654b Remove callspec related block of code
It seems this is no longer required now that we don't support
yield tests anymore. The param attribute was added here:

91b6f2bda8/_pytest/python.py (L888-L891)
2019-03-12 20:10:59 -03:00
Bruno Oliveira
148e6a30c8 Improve coverage 2019-03-12 20:10:59 -03:00
Bruno Oliveira
47bd1688ed Remove dead-code related to yield tests
Just noticed some code that no longer is needed when we removed yield-tests
2019-03-12 20:10:59 -03:00
Daniel Hahler
6630d96253 Merge pull request #4914 from hroncok/issue4913
Fix pytest tests invocation with custom PYTHONPATH
2019-03-12 18:58:54 +01:00
Miro Hrončok
d32ab6029f Fix pytest tests invocation with custom PYTHONPATH
Fixes https://github.com/pytest-dev/pytest/issues/4913

Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
2019-03-12 17:31:42 +01:00
Bruno Oliveira
76c00d1c09 Merge pull request #4909 from nicoddemus/release-4.3.1
Prepare release 4.3.1
2019-03-12 13:23:32 -03:00
Bruno Oliveira
492cc4219c Prepare release 4.3.1 2019-03-11 12:59:54 -03:00
Bruno Oliveira
51bf7c3aef Merge pull request #4897 from altendky/4896-altendky-add_missing_hooks_to_docs
Add missing plugin hooks to docs
2019-03-11 12:08:56 -03:00
Bernhard M. Wiedemann
489c61a22d Allow tests to pass after 2038
without this change, the python-apache-libcloud tests failed
in the year 2039 with

     fp.write(struct.pack("<ll", mtime, size))
 E   error: 'l' format requires -2147483648 <= number <= 2147483647
2019-03-10 05:22:21 +01:00
Bruno Oliveira
0f3d630634 Merge pull request #4898 from shoyer/patch-1
Fix broken error message in pytester
2019-03-08 22:53:01 -03:00
Bruno Oliveira
a0f652c559 Rename 4898.trivial.rst to 4898.bugfix.rst 2019-03-08 22:41:57 -03:00
Bruno Oliveira
877b57ae9b Add CHANGELOG entry 2019-03-08 22:33:07 -03:00
Stephan Hoyer
dc7ae41f33 Fix broken error message in pytester 2019-03-08 09:22:00 -08:00
Kyle Altendorf
2d43f42769 Add missing plugin hooks to docs
pytest-dev/pytest#4896
2019-03-08 09:24:01 -05:00
Bruno Oliveira
03ef546706 Merge pull request #4893 from nicoddemus/simplify-obj-property
Simplify 'obj' property definition in PyobjMixin
2019-03-08 02:04:28 -03:00
Daniel Hahler
de5aa3847e Apply suggestions from code review
Co-Authored-By: nicoddemus <nicoddemus@gmail.com>
2019-03-07 12:53:47 -03:00
Bruno Oliveira
0f4905a259 Simplify 'obj' property definition in PyobjMixin
This uses modern property definition syntax, declaring both getter
and setter as obj() functions
2019-03-07 08:15:04 -03:00
Bruno Oliveira
936f725b81 Merge pull request #4890 from blueyed/dead
pytester: remove unused anypython fixture
2019-03-06 14:35:23 -03:00
Daniel Hahler
c86d2daf81 pytester: remove unused anypython fixture
This became unused after ab9f6a75 (in 2009).
2019-03-06 17:42:21 +01:00
Daniel Hahler
a70c1ca100 ci: Travis: disable cache by default, only for pre-commit
For pip the usual http caching should be good enough.
This keeps the cache for pre-commit with the linting env for now.

Ref: https://github.com/pytest-dev/pytest/issues/3502
2019-03-05 23:12:11 +01:00
Daniel Hahler
4668ee03f6 Merge pull request #4887 from blueyed/merge-master-into-features
Merge master into features
2019-03-05 23:05:55 +01:00
Daniel Hahler
236bada755 Merge pull request #4768 from horta/avoid-pkg_resources
Avoid pkg_resources import at the top-level.
2019-03-05 23:02:02 +01:00
Daniel Hahler
76687030f0 Merge branch 'master' into merge-master-into-features
Conflicts:
	appveyor.yml
	setup.py
2019-03-05 19:07:36 +01:00
Daniel Hahler
2b3d69da2b Merge pull request #4865 from blueyed/revisit-cov-new
ci: revisit coverage reporting
2019-03-05 19:05:36 +01:00
Daniel Hahler
8481e438bd ci: revisit coverage reporting
This brings coverage back that got missing with 9dcd6f2.

Continuation of #4839 / #4846.
2019-03-05 13:34:46 +01:00
Daniel Hahler
bd2c9bedcf Merge pull request #4885 from blueyed/fix-master
Fix master: ci: Azure: remove pypy3
2019-03-04 19:32:38 +01:00
Daniel Hahler
2fe922608f Merge pull request #4883 from blueyed/fix-coverage-includes
.coveragerc: fix include for pypy
2019-03-04 19:31:14 +01:00
Daniel Hahler
07fa69335c ci: Azure: remove pypy3
It was enabled accidentally.
2019-03-04 18:16:20 +01:00
Daniel Hahler
ddb16a1ab1 Merge pull request #4874 from blueyed/pypy3-azure
ci: azure: fix coverage reporting with PyPy (not used currently)
2019-03-04 17:58:28 +01:00
Daniel Hahler
2e871f35f3 ci: Azure: clarify name for coverage job 2019-03-04 17:51:25 +01:00
Daniel Hahler
fa94e3c1b2 ci: Azure: fix coverage generation with PyPy
While this is not used currently, it is still good for having it covered
(it failed when running coverage with all jobs unconditionally).
2019-03-04 17:46:01 +01:00
Daniel Hahler
c6eb3413f3 .coveragerc: fix include for pypy
PyPy uses "site-packages" directly.
2019-03-04 17:40:05 +01:00
Daniel Hahler
54c70bc02c Merge pull request #4878 from blueyed/fix-test_crash_on_closing_tmpfile_py27
tests: make test_crash_on_closing_tmpfile_py27 more reliable
2019-03-04 16:22:14 +01:00
Daniel Hahler
83558a0ba3 tests: make test_crash_on_closing_tmpfile_py27 more reliable
It fails reliable for me now without the fix from 9517c3a2a.

Ref: #2370
2019-03-04 15:04:55 +01:00
Bruno Oliveira
23ea04f910 Merge pull request #4860 from nicoddemus/getfixturevalue-cleanup-1895
getfixturevalue does not correctly declare dependency with the calling fixture
2019-03-03 15:56:45 -03:00
Daniel Hahler
c334adc78f Apply suggestions from code review
Co-Authored-By: nicoddemus <nicoddemus@gmail.com>
2019-03-03 11:20:00 -03:00
Bruno Oliveira
f3f6cb2093 Merge pull request #4877 from blueyed/pluggymaster
tox: generic pluggymaster factor, remove env hack
2019-03-03 09:52:54 -03:00
Daniel Hahler
c4aa57bc4c tox/setup.py: remove _PYTEST_SETUP_SKIP_PLUGGY_DEP=1 hack 2019-03-03 13:15:13 +01:00
Daniel Hahler
2970c1df24 tox: generic pluggymaster factor 2019-03-03 13:12:44 +01:00
Bruno Oliveira
35c85f0db9 Merge pull request #4876 from nicoddemus/show-testpaths-in-header-4875
Show testpaths option in the header if it has been used for collection
2019-03-02 13:01:50 -03:00
Bruno Oliveira
0deb7b1696 Do not show "inifile:" string if there's no configuration file 2019-03-02 11:45:08 -03:00
Bruno Oliveira
53b8aa065c Show testpaths option in the header if it has been used for collection
Fix #4875
2019-03-02 11:35:32 -03:00
Bruno Oliveira
6a2d122a50 Remove code debugging leftovers 2019-03-02 09:56:15 -03:00
Bruno Oliveira
d97473e551 Add test and CHANGELOG for #1895 2019-03-02 09:39:30 -03:00
Bruno Oliveira
525639eaa0 Rename fixtures testing file to be consistent with the module name 2019-03-02 09:37:30 -03:00
Bruno Oliveira
7dceabfcb2 Ensure fixtures obtained with getfixturevalue() are finalized in the correct order
Fix #1895
2019-03-02 09:36:16 -03:00
Daniel Hahler
e1f97e41e3 Merge pull request #4872 from blueyed/_ensure_supporting_files
cacheprovider: _ensure_supporting_files: remove unused branches
2019-03-02 02:35:07 +01:00
Daniel Hahler
2d2f6cd4fd cacheprovider: _ensure_supporting_files: remove unused branches
It is only called with empty/new dirs since 0385c273.
2019-03-01 22:51:45 +01:00
Bruno Oliveira
44c940765b Merge pull request #4868 from blueyed/pytester-unset-PYTEST_ADDOPTS
pytester: unset PYTEST_ADDOPTS
2019-03-01 18:48:36 -03:00
Daniel Hahler
ed68fcf665 Merge pull request #4867 from blueyed/fix-cov
.coveragerc: use globs with includes
2019-03-01 21:35:20 +01:00
Bruno Oliveira
907e9495a2 Merge pull request #4869 from nicoddemus/tmppath-docs
Add missing ref docs to tmp_path and tmp_path_factory
2019-03-01 17:22:14 -03:00
Bruno Oliveira
dac164cc99 Add missing ref docs to tmp_path and tmp_path_factory 2019-03-01 17:09:07 -03:00
Daniel Hahler
4290cacb86 Merge pull request #4864 from blueyed/isort-cfg
Add config for isort
2019-03-01 18:44:56 +01:00
Daniel Hahler
db5cc35b44 pytester: unset PYTEST_ADDOPTS 2019-03-01 18:43:17 +01:00
Daniel Hahler
90031edde8 Merge pull request #4847 from blueyed/tox-posargs-lsof
tox: split default posargs, use --lsof with single job only
2019-03-01 18:41:29 +01:00
Daniel Hahler
a96907a9db .coveragerc: use globs with includes
Apparently this caused missing coverage with pdb/pexpect tests.
2019-03-01 18:26:30 +01:00
Daniel Hahler
f8160f7bc5 ci: Travis: run py37-lsof-numpy-xdist with coverage 2019-03-01 16:59:56 +01:00
Daniel Hahler
f0d7773ffa tox: split default posargs in multiple env vars
This is required for combining.

Use it for new lsof facor also.
2019-03-01 16:49:51 +01:00
Bruno Oliveira
84555c89de Merge pull request #4855 from blueyed/pdbcls-attr
--pdbcls: improve validation, and allow for "mod:attr.class"
2019-03-01 12:20:29 -03:00
Daniel Hahler
f7a3e001f7 pdb: allow for --pdbclass=mod:attr.class 2019-03-01 15:20:04 +01:00
Daniel Hahler
42561db1ae Merge pull request #4863 from blueyed/remove-import
Move import of  _format_explanation in _pytest.assertion.rewrite
2019-03-01 15:13:26 +01:00
Daniel Hahler
0d31e852b1 Run isort 2019-03-01 14:24:18 +01:00
Daniel Hahler
75e1fde668 tox.ini: add config for isort 2019-03-01 14:24:18 +01:00
Daniel Hahler
9cb71af9e5 _pytest.assertion.rewrite: move _format_explanation import 2019-03-01 14:13:28 +01:00
Bruno Oliveira
0dd4cb0f8f Merge pull request #4862 from blueyed/encodedfile-write-typerror
Validate type with writing to captured output like without
2019-03-01 10:12:58 -03:00
Bruno Oliveira
33db5e081d Tweak changelog 2019-03-01 10:09:29 -03:00
Daniel Hahler
a51dc0c7ce Validate type with writing to captured output like without
Fixes https://github.com/pytest-dev/pytest/issues/4861.
2019-03-01 14:03:51 +01:00
Bruno Oliveira
276ffa81f6 Merge pull request #4856 from blueyed/travis-no-cov-with-cron
ci: Travis: skip coverage with cron runs
2019-03-01 09:03:56 -03:00
Bruno Oliveira
50610311a7 Merge pull request #4859 from blueyed/tox-py27-nobyte-xdist
tox: add generic nobyte and numpy factors
2019-03-01 08:48:50 -03:00
Daniel Hahler
c30ab1014e tox: add generic nobyte and numpy factors
Remove `py27-nobyte` from tox.ini, which was using xdist already.
Therefore this also removes `py27-xdist` from Travis.

"nobyte" was added in 036557ac to test that test_assertrewrite.py works
with a global PYTHONDONTWRITEBYTECODE=1 setting.

"numpy" is only a special dependency, and can be run together with
nobyte/xdist.
2019-03-01 09:12:16 +01:00
Bruno Oliveira
df8869cf1a Merge pull request #4858 from blueyed/codecov-remove-flags
ci: codecov: remove flags completely for now
2019-02-28 20:11:09 -03:00
Daniel Hahler
8b447878dc ci: codecov: remove flags completely for now
This appears to be one of the reasons for timeouts on their backend.
2019-02-28 21:24:26 +01:00
Daniel Hahler
9c590fa474 ci: Travis: skip coverage with cron runs
The current commit on features has 50+ uploads already:
c7bbb2a788/build
2019-02-28 19:40:24 +01:00
Daniel Hahler
a868a9ac13 pdb: validate --pdbcls option 2019-02-28 18:11:58 +01:00
Bruno Oliveira
55b78ff780 Merge pull request #4848 from blueyed/fix-test_argcomplete
Fix test_argcomplete: use python -m pytest
2019-02-27 14:11:53 -03:00
Daniel Hahler
ccab469a0c Fix test_argcomplete: use python -m pytest
Previously it was not run with a) xdist ("-c"), and b) "python -m
pytest" ("…/pytest.py", not executable).
2019-02-27 16:52:46 +01:00
Daniel Hahler
e711a6c275 Merge pull request #4841 from blueyed/coverage-source
coverage: use run.include, remove --ignore-errors, send TOXENV as name to codecov
2019-02-27 13:16:31 +01:00
Daniel Hahler
c1e3128b3f Merge pull request #4844 from blueyed/tox-pexpect
tox: generic pexpect factor
2019-02-27 12:33:51 +01:00
Daniel Hahler
05bb5ffb65 Merge pull request #4842 from nicoddemus/disable-bytecode-writing
Document how to disable caching rewritten .pyc files to disk
2019-02-27 12:11:20 +01:00
Daniel Hahler
ee95d666f8 coverage: run.include, drop --ignore-errors, codecov name 2019-02-27 12:07:19 +01:00
Daniel Hahler
1e2810e07d tox: generic pexpect factor 2019-02-27 11:48:22 +01:00
Daniel Hahler
fec656b3b1 Apply suggestions from code review
Co-Authored-By: nicoddemus <nicoddemus@gmail.com>
2019-02-27 07:46:03 -03:00
Daniel Hahler
31174f3f83 .coveragerc: fix/tighten paths 2019-02-27 10:02:11 +01:00
Daniel Hahler
5a0f379289 ci: codecov: set name for uploads/builds 2019-02-27 10:02:11 +01:00
Daniel Hahler
0138e9cbb0 ci: coverage: remove --ignore-errors
This should not be necessary (anymore).
2019-02-27 09:57:28 +01:00
Daniel Hahler
b5cf61312b coverage: use source=. and report.include
This appears to improve performance - ~4s with `tox -e py37-coverage --
testing/test_collection.py`.
2019-02-27 09:57:28 +01:00
Bruno Oliveira
16cbb3196c Document how to disable caching rewritten .pyc files to disk
Also changed how the section is presented: instead of "Note" blocks, use proper
sections as those contain enough information to exist on their own.

Fix #1680
2019-02-26 20:42:59 -03:00
Bruno Oliveira
f1254c4461 Merge pull request #4840 from nicoddemus/drop-appveyor
Drop AppVeyor
2019-02-26 20:42:47 -03:00
Bruno Oliveira
cd9415baf2 Configure azure to enable coverage on PYTEST_COVERAGE=1, similar to Travis 2019-02-26 19:31:52 -03:00
Bruno Oliveira
6bd77c0abd Remove appveyor.yml and related scripts 2019-02-26 19:03:53 -03:00
Bruno Oliveira
fb7ee7f42c Add badge for azure pipelines 2019-02-26 19:01:59 -03:00
Bruno Oliveira
9dcd6f2a87 Merge pull request #4839 from blueyed/less-cov
Less coverage reporting
2019-02-26 18:57:30 -03:00
Daniel Hahler
e3eb26f91a ci: Travis: no coverage by default [skip appveyor] 2019-02-26 21:56:01 +01:00
Daniel Hahler
86070f0b7d ci: AppVeyor: no coverage reporting
Should be covered by Azure.
2019-02-26 21:16:04 +01:00
Daniel Hahler
3fbe100a02 ci: Travis: remove coverage from some jobs 2019-02-26 21:14:54 +01:00
Bruno Oliveira
ee62674322 Merge pull request #4831 from nicoddemus/codocov-azure
Upload code coverage from azure
2019-02-26 14:42:14 -03:00
Daniel Hahler
a4192160ce Merge pull request #4837 from blueyed/travis-install
ci: Travis: use single install section
2019-02-26 17:20:05 +01:00
Daniel Hahler
4d9296c71f ci: Travis: use single install section 2019-02-26 17:06:03 +01:00
Bruno Oliveira
d5d190335c Upload code coverage from azure 2019-02-26 16:56:25 +01:00
Bruno Oliveira
7428064f79 Merge pull request #4836 from blueyed/travis-pluggymaster-xdist
ci: xdist with pluggymaster
2019-02-26 12:52:59 -03:00
Bruno Oliveira
af706edd59 Merge pull request #4835 from blueyed/travis-macos
Travis: improve macos setup (py37)
2019-02-26 12:42:51 -03:00
Daniel Hahler
4eb40ef283 ci: azure: use xdist with pluggymaster 2019-02-26 14:03:15 +01:00
Daniel Hahler
f85f36ed03 AppVeyor: drop pluggymaster 2019-02-26 14:01:37 +01:00
Daniel Hahler
904f1ca1ce ci: Travis: macos: use existing py37
`brew update` and `brew upgrade python` is very slow.

This uses the existing `/usr/local/bin/python3` (3.7.0).
2019-02-26 13:55:52 +01:00
Daniel Hahler
32b85e4ccc ci: Travis: use xdist with pluggymaster jobs 2019-02-26 12:57:27 +01:00
Daniel Hahler
c7bbb2a788 Merge pull request #4833 from blueyed/merge-master
Merge master into features
2019-02-26 12:40:15 +01:00
Daniel Hahler
29112d7e0b Merge master into features 2019-02-26 11:48:10 +01:00
Bruno Oliveira
3fd2f43fb6 Merge pull request #4828 from Zac-HD/mark-docs
Clarify docs on --strict marks
2019-02-25 10:07:40 -03:00
Bruno Oliveira
2cf1de3f2d Merge pull request #4727 from nicoddemus/early-load-4718
Change -p so it is possible to early load setuptools plugins
2019-02-25 10:05:51 -03:00
Zac-HD
d9bdf5cfca Clarify docs on --strict marks 2019-02-25 22:30:49 +11:00
Anthony Sottile
f494eefcae Merge pull request #4823 from nicoddemus/unittest2env
Add unittest2 testing to trial environment
2019-02-24 10:33:26 -08:00
Anthony Sottile
c9e69438b1 Merge pull request #4822 from nicoddemus/funcsigs
Require funcsigs>=1.0 on Python 2.7
2019-02-24 09:14:09 -08:00
Bruno Oliveira
2e89812fad Add unittest2 testing to trial environment
Just noticed that `test_usefixtures_marker_on_unittest` is parametrized
for unittest2, but no environment ever installed that library.
2019-02-24 13:21:00 -03:00
Bruno Oliveira
a0207274f4 -p option now can be used to early-load plugins by entry-point name
Fixes #4718
2019-02-24 13:20:17 -03:00
Bruno Oliveira
759d7fde5d Merge pull request #4786 from blueyed/av
AppVeyor: use xdist with pypy, drop pluggymaster
2019-02-24 12:17:59 -03:00
Bruno Oliveira
a0f5c4c8f5 Merge pull request #4799 from blueyed/codecov
codecov: use only linux/windows/osx flags
2019-02-24 12:16:51 -03:00
Bruno Oliveira
2e210acd00 Merge pull request #4819 from Handsome2734/doc-fix
add notice to use [tool:pytest] in setup.cfg
2019-02-24 12:16:30 -03:00
Bruno Oliveira
ede6387caa Require funcsigs>=1.0 on Python 2.7
Fix #4815
2019-02-24 12:11:08 -03:00
Anthony Sottile
ff25b52110 Merge pull request #4820 from nicoddemus/fix-test-prefix
Fix docs about pytest conventions for test functions
2019-02-23 12:02:04 -08:00
Daniel Hahler
dc8c27037a AppVeyor: drop pluggymaster 2019-02-23 20:15:11 +01:00
Bruno Oliveira
3e11bd0d6e Update wording about [tool:pytest] in setup.cfg files 2019-02-23 15:32:42 -03:00
Bruno Oliveira
6a4c7063fd Fix docs about pytest conventions for test functions
Fix #4818
2019-02-23 15:22:27 -03:00
songbowen
15fe60aa25 add notice to use [tool:pytest] in setup.cfg 2019-02-24 00:24:56 +08:00
Daniel Hahler
1ec7f60484 codecov: use only linux/windows flags
Using many flags are a reason for timeouts on Codecov's backend.
2019-02-23 08:28:08 +01:00
Daniel Hahler
63e7f8e340 Merge pull request #4817 from nicoddemus/merge-master-into-features
Merge master into features (including fix from 4816)
2019-02-23 08:24:21 +01:00
Bruno Oliveira
1cf9c2e76f Merge pull request #4816 from nicoddemus/fix-new-pluggy
Fix test failures after pluggy 1.8 release
2019-02-22 22:42:40 -03:00
Bruno Oliveira
0ca1f6e0f4 Merge branch 'fix-new-pluggy' into merge-master-into-features 2019-02-22 18:59:51 -03:00
Bruno Oliveira
a68f4fd2b9 Fix test failures after pluggy 1.8 release
pluggy now calls iter_entry_points with different arguments, and tests
which mocked that call need to be updated accordingly.
2019-02-22 18:58:54 -03:00
Bruno Oliveira
5b35241470 Merge pull request #4812 from mitzkia/logging_from_runtest_logreport
Logging: Make pytest_runtest_logreport() hook available for logging
2019-02-22 18:47:06 -03:00
Andras Mitzki
b26b731498 Logging: Make pytest_runtest_logreport() available for logging
Signed-off-by: Andras Mitzki <andras.mitzki@balabit.com>
2019-02-22 05:14:44 +01:00
Anthony Sottile
da305966d2 Merge pull request #4793 from nicoddemus/azure-include-more-envs
Add same environments to Azure, except py37-freeze
2019-02-21 09:15:44 -08:00
Bruno Oliveira
4ee10d2266 Merge pull request #4813 from discdiver/patch-1
Doc update - recommend use venv instead of virtualenv
2019-02-21 14:06:36 -03:00
Bruno Oliveira
e1aeb6915e Fix linting 2019-02-21 13:27:12 -03:00
Bruno Oliveira
e75915bb73 Revert recommending virtualenv for Python 3.4
`venv` is already available in Python 3.4, my mistake
2019-02-21 13:03:19 -03:00
Bruno Oliveira
ba2a43266a Mention that virtualenv should be used for Python 3.4 as well 2019-02-21 12:52:09 -03:00
Jeff Hale
cfaa8bbee8 recommend venv or virtualenv, depending on python version
venv has been installed with Python since 3.3.  https://docs.python.org/3/library/venv.html
2019-02-21 10:50:49 -05:00
Jeff Hale
6b661795cf update links.inc to include both venv and virtualenv 2019-02-21 10:41:59 -05:00
Jeff Hale
fa65b71c98 Merge pull request #1 from discdiver/discdiver-patch-1
update links for virtualenv -> venv
2019-02-20 15:41:39 -05:00
Jeff Hale
da5dec83f6 update links for virtualenv -> venv 2019-02-20 15:36:34 -05:00
Jeff Hale
2ef3cb2510 Recommend use venv instead of virtualenv
From the Python docs:
"Changed in version 3.5: The use of venv is now recommended for creating virtual environments."
-https://docs.python.org/3/library/venv.html
2019-02-20 15:33:48 -05:00
Zac Hatfield-Dodds
c8a87e48ab Merge pull request #4783 from gyermolenko/fix_syntax_highlighting_for_two_rst_docs
Fix sphinx code-block types (syntax highlighting) in two docs
2019-02-20 06:31:10 +11:00
Bruno Oliveira
b9561e29ff Merge pull request #4808 from nicoddemus/merge-master-into-features
Merge master into features
2019-02-19 08:08:06 -03:00
Bruno Oliveira
bf6dcd64dc Merge pull request #4757 from blueyed/pypy
Travis: use pypy from Xenial, add pypy3
2019-02-18 19:55:43 -03:00
Daniel Hahler
214c331236 Travis: use pypy from Xenial, add pypy3 2019-02-18 22:32:31 +01:00
Bruno Oliveira
9cb504ca9a Add same environments to Azure as have in AppVeyor, except py37-freeze
py37-freeze will be tackled in https://github.com/pytest-dev/pytest/issues/4807
2019-02-18 17:23:39 -03:00
Bruno Oliveira
f0a9f9042f Merge pull request #4805 from nicoddemus/release-4.3.0
Release 4.3.0
2019-02-18 17:18:50 -03:00
Daniel Hahler
ff015f6308 Fix docs (tox -e regen, plus pre-commit) 2019-02-18 18:46:03 +01:00
Bruno Oliveira
eeac28f4ab Merge pull request #4804 from asottile/fix_py38
Fix python3.8 / pypy failures
2019-02-18 12:59:21 -03:00
Anthony Sottile
5505826db9 Fix python3.8 / pypy failures 2019-02-16 11:23:23 -08:00
Bruno Oliveira
31c869b4c4 Preparing release version 4.3.0 2019-02-16 14:11:58 +00:00
Bruno Oliveira
0395996756 Merge remote-tracking branch 'upstream/master' into release-4.3.0 2019-02-16 14:06:51 +00:00
Bruno Oliveira
986dd84375 LoggingPlugin: Support to customize log_file from hook (#4752)
LoggingPlugin: Support to customize log_file from hook
2019-02-16 12:01:21 -02:00
Bruno Oliveira
a36e986920 Merge pull request #4803 from blueyed/travis-cron
Travis: remove cron_only stage, use conditional job
2019-02-16 11:29:51 -02:00
Anthony Sottile
68dc433bf5 Merge pull request #4802 from gyermolenko/fix_code_block_in_Node_docstring
Fix code-block in Node docstring
2019-02-15 20:09:32 -05:00
Daniel Hahler
e59fc730f8 Merge pull request #4801 from gyermolenko/add_talk
Add good talk by Andrew Svetlov
2019-02-16 01:42:39 +01:00
Daniel Hahler
4f9e835472 Travis: remove cron_only stage, use conditional job
Ref: https://github.com/pytest-dev/pytest/pull/4789#issuecomment-464135675
2019-02-15 18:53:43 +01:00
Daniel Hahler
498b994eb4 Merge pull request #4787 from blueyed/travis-xdist
Travis: use xdist for py?? envs, keeping py27/py37
2019-02-15 18:35:21 +01:00
Daniel Hahler
40eef6da0c AppVeyor: use xdist for py?? envs, drop py27/py37 2019-02-15 17:55:01 +01:00
Daniel Hahler
71373b04b0 tox: add generic xdist factor
Cherry-picked from features.

Conflicts:
	tox.ini
2019-02-15 17:54:44 +01:00
Daniel Hahler
6fb7269979 terminal: write_fspath_result: work around py bug 2019-02-15 17:47:00 +01:00
Daniel Hahler
3460105def Travis: use xdist for py?? envs, keeping py27/py37 2019-02-15 17:46:58 +01:00
Andras Mitzki
e3824d23bc LoggingPlugin: Expose setting log_file_handler
- This patch allows to set log_file (path) from hook

Signed-off-by: Thomas Hisch
Signed-off-by: Andras Mitzki <andras.mitzki@balabit.com>
2019-02-15 16:05:10 +01:00
Grygorii Iermolenko
80ad448590 Fix code-block in Node docstring 2019-02-15 16:12:10 +02:00
Grygorii Iermolenko
59f69dd9e7 Add good talk by Andrew Svetlov 2019-02-15 15:37:18 +02:00
Grygorii Iermolenko
6e1ee0802f Fix sphinx code-block types for remaining rst docs 2019-02-15 15:10:37 +02:00
Grygorii Iermolenko
5cf58a9ae9 Revert '$' changes to not trigger regendoc 2019-02-15 14:09:37 +02:00
Daniel Hahler
2106515f6d Merge pull request #4796 from cclauss/patch-1
Travis CI: The 'sudo' tag is now deprecated
2019-02-14 18:06:40 +01:00
Anthony Sottile
66b4709830 Merge pull request #4798 from nicoddemus/remove-message-docs
Remove 'message' parameter docs from assert.rst
2019-02-14 09:37:22 -05:00
Daniel Hahler
0b1f813c38 Merge pull request #4784 from blueyed/fix-4782
collect: python: fix `AssertionError` with broken symlinks
2019-02-14 15:10:14 +01:00
Bruno Oliveira
0b6960446e Merge pull request #4795 from ramatevish/update-attr-kwarg
Update kwarg for attr.ib to use 'converter' as 'convert' is due to be deprecated.
2019-02-14 08:37:34 -02:00
Bruno Oliveira
4bc2f96c93 Remove 'message' parameter docs from assert.rst
As per:

	https://github.com/pytest-dev/pytest/issues/3974#issuecomment-463462732

Also made the 'match' parameter more prominent
2019-02-14 08:32:49 -02:00
cclauss
0e4750d837 Travis CI: The 'sudo' tag is now deprecated
[Travis are now recommending removing the __sudo__ tag](https://blog.travis-ci.com/2018-11-19-required-linux-infrastructure-migration).
2019-02-14 09:40:00 +01:00
R. Alex Matevish
40cec637d7 Update kwarg for attr.ib to use 'converter' as 'convert' is due to be deprecated 2019-02-14 00:03:41 -08:00
Daniel Hahler
407d4a0cf0 collect: python: fix AssertionError with broken symlinks
Fixes https://github.com/pytest-dev/pytest/issues/4782.
2019-02-14 00:18:14 +01:00
Daniel Hahler
c84ae0bb7a Merge pull request #4789 from blueyed/travis-pluggymaster-cron
Travis: add cron_only stage for py38-dev
2019-02-13 22:03:41 +01:00
Daniel Hahler
1dbf440194 Merge pull request #4785 from blueyed/merge-master-into-features
Merge master into features
2019-02-13 20:23:24 +01:00
Daniel Hahler
afaaa7e411 Travis: test py38-dev only with cron builds
The master and features branches are tested daily.
2019-02-13 19:02:02 +01:00
Daniel Hahler
7b91952645 Merge master into features
Conflicts:
	tox.ini
2019-02-13 17:58:16 +01:00
Grygorii Iermolenko
799bcccd1b Fix sphinx code-block types (syntax highlighting) in two docs 2019-02-13 17:08:49 +02:00
Bruno Oliveira
8726be27a6 Merge pull request #4779 from pytest-dev/azure-pipelines
Set up CI with Azure Pipelines [skip travis] [skip appveyor]
2019-02-13 08:39:32 -02:00
Bruno Oliveira
e26c5bda6e Merge pull request #4776 from nicoddemus/release-4.2.1
Preparing release version 4.2.1
2019-02-13 08:39:24 -02:00
Anthony Sottile
f672b7e39e Merge pull request #4773 from nicoddemus/remove-py27-py34-deprecation-warning
Remove py27 py34 deprecation warning
2019-02-12 21:42:07 -05:00
Anthony Sottile
f0e6bf7604 Merge pull request #4775 from asottile/stdlib
Replace flatten() with chain.from_iterable
2019-02-12 21:37:05 -05:00
Bruno Oliveira
f729d5d4ee Remove --color=yes from PYTEST_ADDOPTS [skip travis] [skip appveyor]
Does not work on Azure Pipelines at all unfortunately
2019-02-12 20:34:51 -02:00
Bruno Oliveira
04a941c818 Pass PYTEST_ADDOPTS to tox envs [skip travis] [skip appveyor] 2019-02-12 20:31:29 -02:00
Bruno Oliveira
215d537624 Set junitxml and colors using PYTEST_ADDOPTS [skip travis] [skip appveyor] 2019-02-12 20:24:32 -02:00
Bruno Oliveira
f63fbf8114 Merge pull request #4767 from blueyed/appveyor-xdist
AppVeyor: use xdist for py?? envs, drop py27/py37
2019-02-12 20:04:45 -02:00
Bruno Oliveira
b595587031 Set up CI with Azure Pipelines [skip travis] [skip appveyor]
Just a few environments for now to see how it will behave for a few days
2019-02-12 20:01:13 -02:00
Bruno Oliveira
d03444db4a Merge pull request #4651 from blueyed/help-with-argumenterror
Display --help/--version with ArgumentErrors
2019-02-12 16:01:49 -02:00
Anthony Sottile
f9c1329dab Replace flatten() with chain.from_iterable
flatten is an alias in more-itertools anyway
2019-02-12 06:30:00 -08:00
Daniel Hahler
747a8ae3a6 AppVeyor: use xdist for py?? envs, drop py27/py37 2019-02-12 14:26:57 +01:00
Bruno Oliveira
b759ebdb93 Add CHANGELOG entry for #4698 2019-02-12 10:39:58 -02:00
Bruno Oliveira
b41632e9a8 Revert "Show deprecation message when running under Python 2.7 and 3.4"
This reverts commit eb92e57509.
2019-02-12 10:39:25 -02:00
Danilo Horta
821b6ef2a6 Avoid pkg_resources import at the top-level. 2019-02-11 23:26:35 +00:00
Daniel Hahler
31c948184a Merge pull request #4753 from blueyed/tox-xdist
tox: add generic xdist factor
2019-02-11 23:36:59 +01:00
Daniel Hahler
f13935da53 Display --help/--version with ArgumentErrors 2019-02-11 15:49:48 +01:00
Daniel Hahler
7bee359459 tox: add generic xdist factor 2019-02-11 15:09:07 +01:00
Daniel Hahler
ed01dc6567 Merge pull request #4652 from blueyed/RunResult-repr
Add __repr__ for RunResult
2019-02-09 01:26:24 +01:00
Daniel Hahler
fc8800c71f Merge pull request #4722 from fetzerch/ignore_wildcards
Add ability to use globs when using --ignore
2019-02-09 00:11:04 +01:00
Daniel Hahler
9bcbf552d6 Add __repr__ for RunResult 2019-02-08 23:41:20 +01:00
Daniel Hahler
a131cd6c3b Merge pull request #4749 from blueyed/merge-master-into-features
Merge master into features
2019-02-08 22:05:43 +01:00
Daniel Hahler
9c03196e79 Merge master into features 2019-02-08 22:02:29 +01:00
Daniel Hahler
64e8185ff7 Merge master into features 2019-02-08 20:09:09 +01:00
Ronny Pfannschmidt
4cd268dc5d Merge pull request #4724 from nicoddemus/pytest-warns-kwargs
emit warning when pytest.warns receives unknown keyword arguments
2019-02-07 07:04:25 +01:00
Bruno Oliveira
e276bd3332 pytest.warns emits a warning on unknown keyword arguments 2019-02-06 19:52:13 -02:00
Christian Fetzer
1876a928d3 Document collect_ignore and collect_ignore_glob in reference 2019-02-06 17:15:30 +01:00
Christian Fetzer
2dc2a19db5 Add ability to exclude files matching glob patterns in conftest.py
This adds the `collect_ignore_glob` option for `conftest.py` to allow
Unix-style wildcards for excluding files.
2019-02-06 16:49:43 +01:00
Christian Fetzer
fc5d4654e5 Add ability to exclude files matching glob patterns with --ignore-glob
This adds the `--ignore-glob` option to allow Unix-style wildcards so
that `--ignore-glob=integration*` excludes all tests that reside in
files starting with `integration`.

Fixes: #3711
2019-02-06 11:29:30 +01:00
Bruno Oliveira
2461a43e00 Merge pull request #4697 from nicoddemus/merge-master-into-features
Merge master into features
2019-01-31 17:10:35 -02:00
122 changed files with 3602 additions and 1144 deletions

View File

@@ -1,9 +1,18 @@
[run]
source = pytest,_pytest,testing/
include =
src/*
testing/*
*/lib/python*/site-packages/_pytest/*
*/lib/python*/site-packages/pytest.py
*/pypy*/site-packages/_pytest/*
*/pypy*/site-packages/pytest.py
*\Lib\site-packages\_pytest\*
*\Lib\site-packages\pytest.py
parallel = 1
branch = 1
[paths]
source = src/
.tox/*/lib/python*/site-packages/
.tox\*\Lib\site-packages\
*/lib/python*/site-packages/
*/pypy*/site-packages/
*\Lib\site-packages\

View File

@@ -1,4 +1,3 @@
sudo: false
language: python
dist: xenial
stages:
@@ -7,67 +6,98 @@ stages:
if: repo = pytest-dev/pytest AND tag IS NOT present
- name: deploy
if: repo = pytest-dev/pytest AND tag IS present
python:
- '3.7'
install:
- pip install --upgrade --pre tox
env:
matrix:
- TOXENV=py27
# Specialized factors for py27.
- TOXENV=py27-nobyte
- TOXENV=py27-xdist
- TOXENV=py27-pluggymaster
# Specialized factors for py37.
- TOXENV=py37-pexpect,py37-trial,py37-numpy
- TOXENV=py37-pluggymaster
- TOXENV=py37-freeze PYTEST_NO_COVERAGE=1
python: '3.7'
cache: false
matrix:
allow_failures:
- python: '3.8-dev'
env: TOXENV=py38
env:
global:
- PYTEST_ADDOPTS=-vv
install:
- python -m pip install --upgrade --pre tox
jobs:
include:
# Coverage tracking is slow with pypy, skip it.
- env: TOXENV=pypy PYTEST_NO_COVERAGE=1
python: 'pypy-5.4'
dist: trusty
- env: TOXENV=py34
python: '3.4'
- env: TOXENV=py35
python: '3.5'
- env: TOXENV=py36
python: '3.6'
- env: TOXENV=py38
python: '3.8-dev'
- env: TOXENV=py37
# OSX tests - first (in test stage), since they are the slower ones.
- &test-macos
language: generic
# NOTE: (tests with) pexpect appear to be buggy on Travis,
# at least with coverage.
# Log: https://travis-ci.org/pytest-dev/pytest/jobs/500358864
os: osx
osx_image: xcode9.4
sudo: required
install:
- python -m pip install --pre tox
env: TOXENV=py27
- <<: *test-macos
env: TOXENV=py37
osx_image: xcode10.1
language: generic
# Coverage for:
# - py2 with symlink in test_cmdline_python_package_symlink.
env: TOXENV=py27-xdist PYTEST_COVERAGE=1
before_install:
- brew update
- brew upgrade python
- brew unlink python
- brew link python
- python -V
- test $(python -c 'import sys; print("%d%d" % sys.version_info[0:2])') = 27
- <<: *test-macos
env: TOXENV=py37-xdist
before_install:
- which python3
- python3 -V
- ln -sfn "$(which python3)" /usr/local/bin/python
- python -V
- test $(python -c 'import sys; print("%d%d" % sys.version_info[0:2])') = 37
# Full run of latest (major) supported versions, without xdist.
- env: TOXENV=py27
python: '2.7'
- env: TOXENV=py37
python: '3.7'
# Coverage tracking is slow with pypy, skip it.
- env: TOXENV=pypy-xdist
python: 'pypy2.7-6.0'
- env: TOXENV=pypy3-xdist
python: 'pypy3.5-6.0'
- env: TOXENV=py34-xdist
python: '3.4'
- env: TOXENV=py35-xdist
python: '3.5'
# Coverage for:
# - pytester's LsofFdLeakChecker
# - TestArgComplete (linux only)
# - numpy
# Empty PYTEST_ADDOPTS to run this non-verbose.
- env: TOXENV=py37-lsof-numpy-xdist PYTEST_COVERAGE=1 PYTEST_ADDOPTS=
# Specialized factors for py27.
- env: TOXENV=py27-nobyte-numpy-xdist
python: '2.7'
- env: TOXENV=py27-pluggymaster-xdist
python: '2.7'
# Specialized factors for py37.
# Coverage for:
# - test_sys_breakpoint_interception (via pexpect).
- env: TOXENV=py37-pexpect,py37-twisted PYTEST_COVERAGE=1
- env: TOXENV=py37-pluggymaster-xdist
- env: TOXENV=py37-freeze
# Jobs only run via Travis cron jobs (currently daily).
- env: TOXENV=py38-xdist
python: '3.8-dev'
if: type = cron
- stage: baseline
env: TOXENV=py27-pexpect,py27-trial,py27-numpy
- env: TOXENV=py37-xdist
- env: TOXENV=linting,docs,doctesting
python: '3.7'
# Coverage for:
# - _pytest.unittest._handle_skip (via pexpect).
env: TOXENV=py27-pexpect,py27-twisted PYTEST_COVERAGE=1
python: '2.7'
# Use py36 here for faster baseline.
- env: TOXENV=py36-xdist
python: '3.6'
- env: TOXENV=linting,docs,doctesting PYTEST_COVERAGE=1
cache:
directories:
- $HOME/.cache/pre-commit
- stage: deploy
python: '3.6'
env: PYTEST_NO_COVERAGE=1
install: pip install -U setuptools setuptools_scm
script: skip
deploy:
@@ -81,9 +111,19 @@ jobs:
tags: true
repo: pytest-dev/pytest
matrix:
allow_failures:
- python: '3.8-dev'
env: TOXENV=py38-xdist
before_script:
- |
if [[ "$PYTEST_NO_COVERAGE" != 1 ]]; then
# Do not (re-)upload coverage with cron runs.
if [[ "$TRAVIS_EVENT_TYPE" = cron ]]; then
PYTEST_COVERAGE=0
fi
- |
if [[ "$PYTEST_COVERAGE" = 1 ]]; then
export COVERAGE_FILE="$PWD/.coverage"
export COVERAGE_PROCESS_START="$PWD/.coveragerc"
export _PYTEST_TOX_COVERAGE_RUN="coverage run -m"
@@ -94,14 +134,14 @@ script: tox --recreate
after_success:
- |
if [[ "$PYTEST_NO_COVERAGE" != 1 ]]; then
if [[ "$PYTEST_COVERAGE" = 1 ]]; then
set -e
# Add last TOXENV to $PATH.
PATH="$PWD/.tox/${TOXENV##*,}/bin:$PATH"
coverage combine
coverage xml --ignore-errors
coverage report -m --ignore-errors
bash <(curl -s https://codecov.io/bash) -Z -X gcov -X coveragepy -X search -X xcode -X gcovout -X fix -f coverage.xml -F "${TOXENV//-/,},linux"
coverage xml
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 -n $TOXENV-$TRAVIS_OS_NAME
fi
notifications:
@@ -113,7 +153,3 @@ notifications:
skip_join: true
email:
- pytest-commit@python.org
cache:
directories:
- $HOME/.cache/pip
- $HOME/.cache/pre-commit

View File

@@ -16,6 +16,7 @@ Allan Feldman
Aly Sivji
Anatoly Bubenkoff
Anders Hovmöller
Andras Mitzki
Andras Tim
Andrea Cimatoribus
Andreas Zeidler
@@ -51,6 +52,7 @@ Charles Cloud
Charnjit SiNGH (CCSJ)
Chris Lamb
Christian Boelsen
Christian Fetzer
Christian Theunert
Christian Tismer
Christopher Gilling
@@ -220,6 +222,7 @@ Steffen Allner
Stephan Obermann
Sven-Hendrik Haase
Tadek Teleżyński
Takafumi Arakaki
Tarcisio Fischer
Tareq Alayan
Ted Xiao
@@ -239,6 +242,7 @@ Vidar T. Fauske
Virgil Dupras
Vitaly Lashmanov
Vlad Dragos
Volodymyr Piskun
Wil Cooley
William Lee
Wim Glenn

View File

@@ -18,6 +18,228 @@ with advance notice in the **Deprecations** section of releases.
.. towncrier release notes start
pytest 4.4.0 (2019-03-29)
=========================
Features
--------
- `#2224 <https://github.com/pytest-dev/pytest/issues/2224>`_: ``async`` test functions are skipped and a warning is emitted when a suitable
async plugin is not installed (such as ``pytest-asyncio`` or ``pytest-trio``).
Previously ``async`` functions would not execute at all but still be marked as "passed".
- `#2482 <https://github.com/pytest-dev/pytest/issues/2482>`_: Include new ``disable_test_id_escaping_and_forfeit_all_rights_to_community_support`` option to disable ascii-escaping in parametrized values. This may cause a series of problems and as the name makes clear, use at your own risk.
- `#4718 <https://github.com/pytest-dev/pytest/issues/4718>`_: The ``-p`` option can now be used to early-load plugins also by entry-point name, instead of just
by module name.
This makes it possible to early load external plugins like ``pytest-cov`` in the command-line::
pytest -p pytest_cov
- `#4855 <https://github.com/pytest-dev/pytest/issues/4855>`_: The ``--pdbcls`` option handles classes via module attributes now (e.g.
``pdb:pdb.Pdb`` with `pdb++`_), and its validation was improved.
.. _pdb++: https://pypi.org/project/pdbpp/
- `#4875 <https://github.com/pytest-dev/pytest/issues/4875>`_: The `testpaths <https://docs.pytest.org/en/latest/reference.html#confval-testpaths>`__ configuration option is now displayed next
to the ``rootdir`` and ``inifile`` lines in the pytest header if the option is in effect, i.e., directories or file names were
not explicitly passed in the command line.
Also, ``inifile`` is only displayed if there's a configuration file, instead of an empty ``inifile:`` string.
- `#4911 <https://github.com/pytest-dev/pytest/issues/4911>`_: Doctests can be skipped now dynamically using ``pytest.skip()``.
- `#4920 <https://github.com/pytest-dev/pytest/issues/4920>`_: Internal refactorings have been made in order to make the implementation of the
`pytest-subtests <https://github.com/pytest-dev/pytest-subtests>`__ plugin
possible, which adds unittest sub-test support and a new ``subtests`` fixture as discussed in
`#1367 <https://github.com/pytest-dev/pytest/issues/1367>`__.
For details on the internal refactorings, please see the details on the related PR.
- `#4931 <https://github.com/pytest-dev/pytest/issues/4931>`_: pytester's ``LineMatcher`` asserts that the passed lines are a sequence.
- `#4936 <https://github.com/pytest-dev/pytest/issues/4936>`_: Handle ``-p plug`` after ``-p no:plug``.
This can be used to override a blocked plugin (e.g. in "addopts") from the
command line etc.
- `#4951 <https://github.com/pytest-dev/pytest/issues/4951>`_: Output capturing is handled correctly when only capturing via fixtures (capsys, capfs) with ``pdb.set_trace()``.
- `#4956 <https://github.com/pytest-dev/pytest/issues/4956>`_: ``pytester`` sets ``$HOME`` and ``$USERPROFILE`` to the temporary directory during test runs.
This ensures to not load configuration files from the real user's home directory.
- `#4980 <https://github.com/pytest-dev/pytest/issues/4980>`_: Namespace packages are handled better with ``monkeypatch.syspath_prepend`` and ``testdir.syspathinsert`` (via ``pkg_resources.fixup_namespace_packages``).
- `#4993 <https://github.com/pytest-dev/pytest/issues/4993>`_: The stepwise plugin reports status information now.
- `#5008 <https://github.com/pytest-dev/pytest/issues/5008>`_: If a ``setup.cfg`` file contains ``[tool:pytest]`` and also the no longer supported ``[pytest]`` section, pytest will use ``[tool:pytest]`` ignoring ``[pytest]``. Previously it would unconditionally error out.
This makes it simpler for plugins to support old pytest versions.
Bug Fixes
---------
- `#1895 <https://github.com/pytest-dev/pytest/issues/1895>`_: Fix bug where fixtures requested dynamically via ``request.getfixturevalue()`` might be teardown
before the requesting fixture.
- `#4851 <https://github.com/pytest-dev/pytest/issues/4851>`_: pytester unsets ``PYTEST_ADDOPTS`` now to not use outer options with ``testdir.runpytest()``.
- `#4903 <https://github.com/pytest-dev/pytest/issues/4903>`_: Use the correct modified time for years after 2038 in rewritten ``.pyc`` files.
- `#4928 <https://github.com/pytest-dev/pytest/issues/4928>`_: Fix line offsets with ``ScopeMismatch`` errors.
- `#4957 <https://github.com/pytest-dev/pytest/issues/4957>`_: ``-p no:plugin`` is handled correctly for default (internal) plugins now, e.g. with ``-p no:capture``.
Previously they were loaded (imported) always, making e.g. the ``capfd`` fixture available.
- `#4968 <https://github.com/pytest-dev/pytest/issues/4968>`_: The pdb ``quit`` command is handled properly when used after the ``debug`` command with `pdb++`_.
.. _pdb++: https://pypi.org/project/pdbpp/
- `#4975 <https://github.com/pytest-dev/pytest/issues/4975>`_: Fix the interpretation of ``-qq`` option where it was being considered as ``-v`` instead.
- `#4978 <https://github.com/pytest-dev/pytest/issues/4978>`_: ``outcomes.Exit`` is not swallowed in ``assertrepr_compare`` anymore.
- `#4988 <https://github.com/pytest-dev/pytest/issues/4988>`_: Close logging's file handler explicitly when the session finishes.
- `#5003 <https://github.com/pytest-dev/pytest/issues/5003>`_: Fix line offset with mark collection error (off by one).
Improved Documentation
----------------------
- `#4974 <https://github.com/pytest-dev/pytest/issues/4974>`_: Update docs for ``pytest_cmdline_parse`` hook to note availability liminations
Trivial/Internal Changes
------------------------
- `#4718 <https://github.com/pytest-dev/pytest/issues/4718>`_: ``pluggy>=0.9`` is now required.
- `#4815 <https://github.com/pytest-dev/pytest/issues/4815>`_: ``funcsigs>=1.0`` is now required for Python 2.7.
- `#4829 <https://github.com/pytest-dev/pytest/issues/4829>`_: Some left-over internal code related to ``yield`` tests has been removed.
- `#4890 <https://github.com/pytest-dev/pytest/issues/4890>`_: Remove internally unused ``anypython`` fixture from the pytester plugin.
- `#4912 <https://github.com/pytest-dev/pytest/issues/4912>`_: Remove deprecated Sphinx directive, ``add_description_unit()``,
pin sphinx-removed-in to >= 0.2.0 to support Sphinx 2.0.
- `#4913 <https://github.com/pytest-dev/pytest/issues/4913>`_: Fix pytest tests invocation with custom ``PYTHONPATH``.
- `#4965 <https://github.com/pytest-dev/pytest/issues/4965>`_: New ``pytest_report_to_serializable`` and ``pytest_report_from_serializable`` **experimental** hooks.
These hooks will be used by ``pytest-xdist``, ``pytest-subtests``, and the replacement for
resultlog to serialize and customize reports.
They are experimental, meaning that their details might change or even be removed
completely in future patch releases without warning.
Feedback is welcome from plugin authors and users alike.
- `#4987 <https://github.com/pytest-dev/pytest/issues/4987>`_: ``Collector.repr_failure`` respects the ``--tb`` option, but only defaults to ``short`` now (with ``auto``).
pytest 4.3.1 (2019-03-11)
=========================
Bug Fixes
---------
- `#4810 <https://github.com/pytest-dev/pytest/issues/4810>`_: Logging messages inside ``pytest_runtest_logreport()`` are now properly captured and displayed.
- `#4861 <https://github.com/pytest-dev/pytest/issues/4861>`_: Improve validation of contents written to captured output so it behaves the same as when capture is disabled.
- `#4898 <https://github.com/pytest-dev/pytest/issues/4898>`_: Fix ``AttributeError: FixtureRequest has no 'confg' attribute`` bug in ``testdir.copy_example``.
Trivial/Internal Changes
------------------------
- `#4768 <https://github.com/pytest-dev/pytest/issues/4768>`_: Avoid pkg_resources import at the top-level.
pytest 4.3.0 (2019-02-16)
=========================
Deprecations
------------
- `#4724 <https://github.com/pytest-dev/pytest/issues/4724>`_: ``pytest.warns()`` now emits a warning when it receives unknown keyword arguments.
This will be changed into an error in the future.
Features
--------
- `#2753 <https://github.com/pytest-dev/pytest/issues/2753>`_: Usage errors from argparse are mapped to pytest's ``UsageError``.
- `#3711 <https://github.com/pytest-dev/pytest/issues/3711>`_: Add the ``--ignore-glob`` parameter to exclude test-modules with Unix shell-style wildcards.
Add the ``collect_ignore_glob`` for ``conftest.py`` to exclude test-modules with Unix shell-style wildcards.
- `#4698 <https://github.com/pytest-dev/pytest/issues/4698>`_: The warning about Python 2.7 and 3.4 not being supported in pytest 5.0 has been removed.
In the end it was considered to be more
of a nuisance than actual utility and users of those Python versions shouldn't have problems as ``pip`` will not
install pytest 5.0 on those interpreters.
- `#4707 <https://github.com/pytest-dev/pytest/issues/4707>`_: With the help of new ``set_log_path()`` method there is a way to set ``log_file`` paths from hooks.
Bug Fixes
---------
- `#4651 <https://github.com/pytest-dev/pytest/issues/4651>`_: ``--help`` and ``--version`` are handled with ``UsageError``.
- `#4782 <https://github.com/pytest-dev/pytest/issues/4782>`_: Fix ``AssertionError`` with collection of broken symlinks with packages.
pytest 4.2.1 (2019-02-12)
=========================

View File

@@ -22,8 +22,8 @@
.. image:: https://travis-ci.org/pytest-dev/pytest.svg?branch=master
:target: https://travis-ci.org/pytest-dev/pytest
.. image:: https://ci.appveyor.com/api/projects/status/mrgbjaua7t33pg6b?svg=true
:target: https://ci.appveyor.com/project/pytestbot/pytest
.. image:: https://dev.azure.com/pytest-dev/pytest/_apis/build/status/pytest-CI?branchName=master
:target: https://dev.azure.com/pytest-dev/pytest
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
:target: https://github.com/ambv/black

View File

@@ -1,51 +0,0 @@
environment:
matrix:
- TOXENV: "py37-xdist"
- TOXENV: "py27-xdist"
- TOXENV: "py27"
- TOXENV: "py37"
- TOXENV: "linting,docs,doctesting"
- TOXENV: "py36"
- TOXENV: "py35"
- TOXENV: "py34"
- TOXENV: "pypy"
PYTEST_NO_COVERAGE: "1"
# Specialized factors for py27.
- TOXENV: "py27-trial,py27-numpy,py27-nobyte"
- TOXENV: "py27-pluggymaster"
# Specialized factors for py37.
- TOXENV: "py37-trial,py37-numpy"
- TOXENV: "py37-pluggymaster"
- TOXENV: "py37-freeze"
PYTEST_NO_COVERAGE: "1"
matrix:
fast_finish: true
install:
- echo Installed Pythons
- dir c:\Python*
- if "%TOXENV%" == "pypy" call scripts\install-pypy.bat
- C:\Python36\python -m pip install --upgrade pip
- C:\Python36\python -m pip install --upgrade --pre tox
build: false # Not a C# project, build stuff at the test step instead.
before_test:
- call scripts\prepare-coverage.bat
test_script:
- C:\Python36\python -m tox
on_success:
- call scripts\upload-coverage.bat
cache:
- '%LOCALAPPDATA%\pip\cache'
- '%USERPROFILE%\.cache\pre-commit'
# We don't deploy anything on tags with AppVeyor, we use Travis instead, so we
# might as well save resources
skip_tags: true

135
azure-pipelines.yml Normal file
View File

@@ -0,0 +1,135 @@
trigger:
- master
- features
variables:
PYTEST_ADDOPTS: "--junitxml=build/test-results/$(tox.env).xml -vv"
python.needs_vc: False
python.exe: "python"
COVERAGE_FILE: "$(Build.Repository.LocalPath)/.coverage"
COVERAGE_PROCESS_START: "$(Build.Repository.LocalPath)/.coveragerc"
PYTEST_COVERAGE: '0'
jobs:
- job: 'Test'
pool:
vmImage: "vs2017-win2016"
strategy:
matrix:
py27:
python.version: '2.7'
tox.env: 'py27'
py27-nobyte-lsof-numpy:
python.version: '2.7'
tox.env: 'py27-lsof-nobyte-numpy'
# Coverage for:
# - test_supports_breakpoint_module_global
# - test_terminal_reporter_writer_attr (without xdist)
# - "if write" branch in _pytest.assertion.rewrite
# - numpy
# - pytester's LsofFdLeakChecker (being skipped)
PYTEST_COVERAGE: '1'
py27-twisted:
python.version: '2.7'
tox.env: 'py27-twisted'
python.needs_vc: True
py27-pluggymaster-xdist:
python.version: '2.7'
tox.env: 'py27-pluggymaster-xdist'
# Coverage for:
# - except-IOError in _attempt_to_close_capture_file for py2.
# Also seen with py27-nobyte (using xdist), and py27-xdist.
# But no exception with py27-pexpect,py27-twisted,py27-numpy.
PYTEST_COVERAGE: '1'
pypy:
python.version: 'pypy'
tox.env: 'pypy'
python.exe: 'pypy'
# NOTE: pypy3 fails to install pip currently due to an interal error.
# pypy3:
# python.version: 'pypy3'
# tox.env: 'pypy3'
# python.exe: 'pypy3'
py34-xdist:
python.version: '3.4'
tox.env: 'py34-xdist'
# Coverage for:
# - _pytest.compat._bytes_to_ascii
PYTEST_COVERAGE: '1'
py35-xdist:
python.version: '3.5'
tox.env: 'py35-xdist'
# Coverage for:
# - test_supports_breakpoint_module_global
PYTEST_COVERAGE: '1'
py36-xdist:
python.version: '3.6'
tox.env: 'py36-xdist'
py37:
python.version: '3.7'
tox.env: 'py37'
# Coverage for:
# - _py36_windowsconsoleio_workaround (with py36+)
# - test_request_garbage (no xdist)
PYTEST_COVERAGE: '1'
py37-linting/docs/doctesting:
python.version: '3.7'
tox.env: 'linting,docs,doctesting'
py37-twisted/numpy:
python.version: '3.7'
tox.env: 'py37-twisted,py37-numpy'
py37-pluggymaster-xdist:
python.version: '3.7'
tox.env: 'py37-pluggymaster-xdist'
maxParallel: 10
steps:
- task: UsePythonVersion@0
condition: not(startsWith(variables['python.exe'], 'pypy'))
inputs:
versionSpec: '$(python.version)'
architecture: 'x64'
- script: choco install vcpython27
condition: eq(variables['python.needs_vc'], True)
displayName: 'Install VC for py27'
- script: choco install python.pypy
condition: eq(variables['python.exe'], 'pypy')
displayName: 'Install pypy'
- script: choco install pypy3
condition: eq(variables['python.exe'], 'pypy3')
displayName: 'Install pypy3'
- task: PowerShell@2
inputs:
targetType: 'inline'
script: |
Invoke-WebRequest -Uri "https://bootstrap.pypa.io/get-pip.py" -OutFile "get-pip.py"
$(python.exe) get-pip.py
condition: startsWith(variables['python.exe'], 'pypy')
displayName: 'Install pip'
- script: $(python.exe) -m pip install --upgrade pip && $(python.exe) -m pip install tox
displayName: 'Install tox'
- script: |
call scripts/setup-coverage-vars.bat || goto :eof
$(python.exe) -m tox -e $(tox.env)
displayName: 'Run tests'
- task: PublishTestResults@2
inputs:
testResultsFiles: 'build/test-results/$(tox.env).xml'
testRunTitle: '$(tox.env)'
condition: succeededOrFailed()
- script: call scripts\upload-coverage.bat
displayName: 'Report and upload coverage'
condition: eq(variables['PYTEST_COVERAGE'], '1')
env:
PYTHON: $(python.exe)
CODECOV_TOKEN: $(CODECOV_TOKEN)
PYTEST_CODECOV_NAME: $(tox.env)

View File

@@ -5,7 +5,7 @@ if __name__ == "__main__":
import pytest # NOQA
import pstats
script = sys.argv[1:] if len(sys.argv) > 1 else "empty.py"
script = sys.argv[1:] if len(sys.argv) > 1 else ["empty.py"]
stats = cProfile.run("pytest.cmdline.main(%r)" % script, "prof")
p = pstats.Stats("prof")
p.strip_dirs()

View File

@@ -2,7 +2,6 @@ from six.moves import range
import pytest
SKIP = True

View File

@@ -6,6 +6,9 @@ Release announcements
:maxdepth: 2
release-4.4.0
release-4.3.1
release-4.3.0
release-4.2.1
release-4.2.0
release-4.1.1

View File

@@ -0,0 +1,36 @@
pytest-4.3.0
=======================================
The pytest team is proud to announce the 4.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:
* Andras Mitzki
* Anthony Sottile
* Bruno Oliveira
* Christian Fetzer
* Daniel Hahler
* Grygorii Iermolenko
* R. Alex Matevish
* Ronny Pfannschmidt
* cclauss
Happy testing,
The Pytest Development Team

View File

@@ -0,0 +1,29 @@
pytest-4.3.1
=======================================
pytest 4.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:
* Andras Mitzki
* Anthony Sottile
* Bruno Oliveira
* Daniel Hahler
* Danilo Horta
* Grygorii Iermolenko
* Jeff Hale
* Kyle Altendorf
* Stephan Hoyer
* Zac Hatfield-Dodds
* Zac-HD
* songbowen
Happy testing,
The pytest Development Team

View File

@@ -0,0 +1,39 @@
pytest-4.4.0
=======================================
The pytest team is proud to announce the 4.4.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:
* Anthony Sottile
* ApaDoctor
* Bernhard M. Wiedemann
* Brian Skinn
* Bruno Oliveira
* Daniel Hahler
* Gary Tyler
* Jeong YunWon
* Miro Hrončok
* Takafumi Arakaki
* henrykironde
* smheidrich
Happy testing,
The Pytest Development Team

View File

@@ -30,7 +30,7 @@ you will see the return value of the function call:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collected 1 item
test_assert1.py F [100%]
@@ -88,23 +88,30 @@ and if you need to have access to the actual exception info you may use::
the actual exception raised. The main attributes of interest are
``.type``, ``.value`` and ``.traceback``.
.. versionchanged:: 3.0
You can pass a ``match`` keyword parameter to the context-manager to test
that a regular expression matches on the string representation of an exception
(similar to the ``TestCase.assertRaisesRegexp`` method from ``unittest``)::
In the context manager form you may use the keyword argument
``message`` to specify a custom failure message::
import pytest
>>> with raises(ZeroDivisionError, message="Expecting ZeroDivisionError"):
... pass
... Failed: Expecting ZeroDivisionError
def myfunc():
raise ValueError("Exception 123 raised")
If you want to write test code that works on Python 2.4 as well,
you may also use two other ways to test for an expected exception::
def test_match():
with pytest.raises(ValueError, match=r'.* 123 .*'):
myfunc()
The regexp parameter of the ``match`` method is matched with the ``re.search``
function, so in the above example ``match='123'`` would have worked as
well.
There's an alternate form of the ``pytest.raises`` function where you pass
a function that will be executed with the given ``*args`` and ``**kwargs`` and
assert that the given exception is raised::
pytest.raises(ExpectedException, func, *args, **kwargs)
which will execute the specified function with args and kwargs and
assert that the given ``ExpectedException`` is raised. The reporter will
provide you with helpful output in case of failures such as *no
The reporter will provide you with helpful output in case of failures such as *no
exception* or *wrong exception*.
Note that it is also possible to specify a "raises" argument to
@@ -121,23 +128,6 @@ exceptions your own code is deliberately raising, whereas using
like documenting unfixed bugs (where the test describes what "should" happen)
or bugs in dependencies.
Also, the context manager form accepts a ``match`` keyword parameter to test
that a regular expression matches on the string representation of an exception
(like the ``TestCase.assertRaisesRegexp`` method from ``unittest``)::
import pytest
def myfunc():
raise ValueError("Exception 123 raised")
def test_match():
with pytest.raises(ValueError, match=r'.* 123 .*'):
myfunc()
The regexp parameter of the ``match`` method is matched with the ``re.search``
function. So in the above example ``match='123'`` would have worked as
well.
.. _`assertwarns`:
@@ -175,7 +165,7 @@ if you run this module:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collected 1 item
test_assert2.py F [100%]
@@ -262,8 +252,8 @@ the conftest file:
.. _assert-details:
.. _`assert introspection`:
Advanced assertion introspection
----------------------------------
Assertion introspection details
-------------------------------
.. versionadded:: 2.1
@@ -276,28 +266,46 @@ supporting modules which are not themselves test modules will not be rewritten**
You can manually enable assertion rewriting for an imported module by calling
`register_assert_rewrite <https://docs.pytest.org/en/latest/writing_plugins.html#assertion-rewriting>`_
before you import it (a good place to do that is in ``conftest.py``).
.. note::
``pytest`` rewrites test modules on import by using an import
hook to write new ``pyc`` files. Most of the time this works transparently.
However, if you are messing with import yourself, the import hook may
interfere.
If this is the case you have two options:
* Disable rewriting for a specific module by adding the string
``PYTEST_DONT_REWRITE`` to its docstring.
* Disable rewriting for all modules by using ``--assert=plain``.
Additionally, rewriting will fail silently if it cannot write new ``.pyc`` files,
i.e. in a read-only filesystem or a zipfile.
before you import it (a good place to do that is in your root ``conftest.py``).
For further information, Benjamin Peterson wrote up `Behind the scenes of pytest's new assertion rewriting <http://pybites.blogspot.com/2011/07/behind-scenes-of-pytests-new-assertion.html>`_.
Assertion rewriting caches files on disk
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``pytest`` will write back the rewritten modules to disk for caching. You can disable
this behavior (for example to avoid leaving stale ``.pyc`` files around in projects that
move files around a lot) by adding this to the top of your ``conftest.py`` file:
.. code-block:: python
import sys
sys.dont_write_bytecode = True
Note that you still get the benefits of assertion introspection, the only change is that
the ``.pyc`` files won't be cached on disk.
Additionally, rewriting will silently skip caching if it cannot write new ``.pyc`` files,
i.e. in a read-only filesystem or a zipfile.
Disabling assert rewriting
~~~~~~~~~~~~~~~~~~~~~~~~~~
``pytest`` rewrites test modules on import by using an import
hook to write new ``pyc`` files. Most of the time this works transparently.
However, if you are working with the import machinery yourself, the import hook may
interfere.
If this is the case you have two options:
* Disable rewriting for a specific module by adding the string
``PYTEST_DONT_REWRITE`` to its docstring.
* Disable rewriting for all modules by using ``--assert=plain``.
.. versionadded:: 2.1
Add assert rewriting as an alternate introspection technique.

View File

@@ -8,18 +8,26 @@ When using bash as your shell, ``pytest`` can use argcomplete
(https://argcomplete.readthedocs.io/) for auto-completion.
For this ``argcomplete`` needs to be installed **and** enabled.
Install argcomplete using::
Install argcomplete using:
sudo pip install 'argcomplete>=0.5.7'
.. code-block:: bash
For global activation of all argcomplete enabled python applications run::
sudo pip install 'argcomplete>=0.5.7'
For global activation of all argcomplete enabled python applications run:
.. code-block:: bash
sudo activate-global-python-argcomplete
For permanent (but not global) ``pytest`` activation, use::
For permanent (but not global) ``pytest`` activation, use:
register-python-argcomplete pytest >> ~/.bashrc
.. code-block:: bash
For one-time activation of argcomplete for ``pytest`` only, use::
register-python-argcomplete pytest >> ~/.bashrc
eval "$(register-python-argcomplete pytest)"
For one-time activation of argcomplete for ``pytest`` only, use:
.. code-block:: bash
eval "$(register-python-argcomplete pytest)"

View File

@@ -28,25 +28,29 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
Values can be any object handled by the json stdlib module.
capsys
Enable capturing of writes to ``sys.stdout`` and ``sys.stderr`` and make
captured output available via ``capsys.readouterr()`` method calls
which return a ``(out, err)`` namedtuple. ``out`` and ``err`` will be ``text``
objects.
Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``.
The captured output is made available via ``capsys.readouterr()`` method
calls, which return a ``(out, err)`` namedtuple.
``out`` and ``err`` will be ``text`` objects.
capsysbinary
Enable capturing of writes to ``sys.stdout`` and ``sys.stderr`` and make
captured output available via ``capsys.readouterr()`` method calls
which return a ``(out, err)`` tuple. ``out`` and ``err`` will be ``bytes``
objects.
Enable bytes capturing of writes to ``sys.stdout`` and ``sys.stderr``.
The captured output is made available via ``capsysbinary.readouterr()``
method calls, which return a ``(out, err)`` namedtuple.
``out`` and ``err`` will be ``bytes`` objects.
capfd
Enable capturing of writes to file descriptors ``1`` and ``2`` and make
captured output available via ``capfd.readouterr()`` method calls
which return a ``(out, err)`` tuple. ``out`` and ``err`` will be ``text``
objects.
Enable text capturing of writes to file descriptors ``1`` and ``2``.
The captured output is made available via ``capfd.readouterr()`` method
calls, which return a ``(out, err)`` namedtuple.
``out`` and ``err`` will be ``text`` objects.
capfdbinary
Enable capturing of write to file descriptors 1 and 2 and make
captured output available via ``capfdbinary.readouterr`` method calls
which return a ``(out, err)`` tuple. ``out`` and ``err`` will be
``bytes`` objects.
Enable bytes capturing of writes to file descriptors ``1`` and ``2``.
The captured output is made available via ``capfd.readouterr()`` method
calls, which return a ``(out, err)`` namedtuple.
``out`` and ``err`` will be ``byte`` objects.
doctest_namespace
Fixture that returns a :py:class:`dict` that will be injected into the namespace of doctests.
pytestconfig
@@ -55,7 +59,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
Example::
def test_foo(pytestconfig):
if pytestconfig.getoption("verbose"):
if pytestconfig.getoption("verbose") > 0:
...
record_property
Add an extra properties the calling test.

View File

@@ -82,7 +82,7 @@ If you then run it with ``--lf``:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collected 50 items / 48 deselected / 2 selected
run-last-failure: rerun previous 2 failures
@@ -126,7 +126,7 @@ of ``FF`` and dots):
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collected 50 items
run-last-failure: rerun previous 2 failures first
@@ -168,7 +168,9 @@ Behavior when no tests failed in the last run
When no tests failed in the last run, or when no cached ``lastfailed`` data was
found, ``pytest`` can be configured either to run all of the tests or no tests,
using the ``--last-failed-no-failures`` option, which takes one of the following values::
using the ``--last-failed-no-failures`` option, which takes one of the following values:
.. code-block:: bash
pytest --last-failed --last-failed-no-failures all # run all tests (default behavior)
pytest --last-failed --last-failed-no-failures none # run no tests and exit
@@ -216,12 +218,8 @@ If you run this command for the first time, you can see the print statement:
def test_function(mydata):
> assert mydata == 23
E assert 42 == 23
E -42
E +23
test_caching.py:17: AssertionError
-------------------------- Captured stdout setup ---------------------------
running expensive computation...
1 failed in 0.12 seconds
If you run it a second time the value will be retrieved from
@@ -239,8 +237,6 @@ the cache and nothing will be printed:
def test_function(mydata):
> assert mydata == 23
E assert 42 == 23
E -42
E +23
test_caching.py:17: AssertionError
1 failed in 0.12 seconds
@@ -260,16 +256,96 @@ You can always peek at the content of the cache using the
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
cachedir: $PYTHON_PREFIX/.pytest_cache
------------------------------- cache values -------------------------------
cache/lastfailed contains:
{'test_50.py::test_num[17]': True,
{'a/test_db.py::test_a1': True,
'a/test_db2.py::test_a2': True,
'b/test_error.py::test_root': True,
'failure_demo.py::TestCustomAssertMsg::test_custom_repr': True,
'failure_demo.py::TestCustomAssertMsg::test_multiline': True,
'failure_demo.py::TestCustomAssertMsg::test_single_line': True,
'failure_demo.py::TestFailing::test_not': True,
'failure_demo.py::TestFailing::test_simple': True,
'failure_demo.py::TestFailing::test_simple_multiline': True,
'failure_demo.py::TestMoreErrors::test_compare': True,
'failure_demo.py::TestMoreErrors::test_complex_error': True,
'failure_demo.py::TestMoreErrors::test_global_func': True,
'failure_demo.py::TestMoreErrors::test_instance': True,
'failure_demo.py::TestMoreErrors::test_startswith': True,
'failure_demo.py::TestMoreErrors::test_startswith_nested': True,
'failure_demo.py::TestMoreErrors::test_try_finally': True,
'failure_demo.py::TestMoreErrors::test_z1_unpack_error': True,
'failure_demo.py::TestMoreErrors::test_z2_type_error': True,
'failure_demo.py::TestRaises::test_raise': True,
'failure_demo.py::TestRaises::test_raises': True,
'failure_demo.py::TestRaises::test_raises_doesnt': True,
'failure_demo.py::TestRaises::test_reinterpret_fails_with_print_for_the_fun_of_it': True,
'failure_demo.py::TestRaises::test_some_error': True,
'failure_demo.py::TestRaises::test_tupleerror': True,
'failure_demo.py::TestSpecialisedExplanations::test_eq_attrs': True,
'failure_demo.py::TestSpecialisedExplanations::test_eq_dataclass': True,
'failure_demo.py::TestSpecialisedExplanations::test_eq_dict': True,
'failure_demo.py::TestSpecialisedExplanations::test_eq_list': True,
'failure_demo.py::TestSpecialisedExplanations::test_eq_list_long': True,
'failure_demo.py::TestSpecialisedExplanations::test_eq_long_text': True,
'failure_demo.py::TestSpecialisedExplanations::test_eq_long_text_multiline': True,
'failure_demo.py::TestSpecialisedExplanations::test_eq_longer_list': True,
'failure_demo.py::TestSpecialisedExplanations::test_eq_multiline_text': True,
'failure_demo.py::TestSpecialisedExplanations::test_eq_set': True,
'failure_demo.py::TestSpecialisedExplanations::test_eq_similar_text': True,
'failure_demo.py::TestSpecialisedExplanations::test_eq_text': True,
'failure_demo.py::TestSpecialisedExplanations::test_in_list': True,
'failure_demo.py::TestSpecialisedExplanations::test_not_in_text_multiline': True,
'failure_demo.py::TestSpecialisedExplanations::test_not_in_text_single': True,
'failure_demo.py::TestSpecialisedExplanations::test_not_in_text_single_long': True,
'failure_demo.py::TestSpecialisedExplanations::test_not_in_text_single_long_term': True,
'failure_demo.py::test_attribute': True,
'failure_demo.py::test_attribute_failure': True,
'failure_demo.py::test_attribute_instance': True,
'failure_demo.py::test_attribute_multiple': True,
'failure_demo.py::test_dynamic_compile_shows_nicely': True,
'failure_demo.py::test_generative[3-6]': True,
'test_50.py::test_num[17]': True,
'test_50.py::test_num[25]': True,
'test_anothersmtp.py::test_showhelo': True,
'test_assert1.py::test_function': True,
'test_assert2.py::test_set_comparison': True,
'test_backends.py::test_db_initialized[d2]': True,
'test_caching.py::test_function': True,
'test_foocompare.py::test_compare': True}
'test_checkconfig.py::test_something': True,
'test_class.py::TestClass::test_two': True,
'test_compute.py::test_compute[4]': True,
'test_example.py::test_error': True,
'test_example.py::test_fail': True,
'test_foocompare.py::test_compare': True,
'test_module.py::test_call_fails': True,
'test_module.py::test_ehlo': True,
'test_module.py::test_ehlo[mail.python.org]': True,
'test_module.py::test_ehlo[smtp.gmail.com]': True,
'test_module.py::test_event_simple': True,
'test_module.py::test_fail1': True,
'test_module.py::test_fail2': True,
'test_module.py::test_func2': True,
'test_module.py::test_interface_complex': True,
'test_module.py::test_interface_simple': True,
'test_module.py::test_noop': True,
'test_module.py::test_noop[mail.python.org]': True,
'test_module.py::test_noop[smtp.gmail.com]': True,
'test_module.py::test_setup_fails': True,
'test_parametrize.py::TestClass::test_equals[1-2]': True,
'test_sample.py::test_answer': True,
'test_show_warnings.py::test_one': True,
'test_simple.yml::hello': True,
'test_smtpsimple.py::test_ehlo': True,
'test_step.py::TestUserHandling::test_modification': True,
'test_strings.py::test_valid_string[!]': True,
'test_tmp_path.py::test_create_file': True,
'test_tmpdir.py::test_create_file': True,
'test_tmpdir.py::test_needsfiles': True,
'test_unittest_db.py::MyTest::test_method1': True,
'test_unittest_db.py::MyTest::test_method2': True}
cache/nodeids contains:
['test_caching.py::test_function']
cache/stepwise contains:
@@ -283,7 +359,9 @@ Clearing Cache content
-------------------------------
You can instruct pytest to clear all cache files and values
by adding the ``--cache-clear`` option like this::
by adding the ``--cache-clear`` option like this:
.. code-block:: bash
pytest --cache-clear

View File

@@ -35,7 +35,9 @@ There are two ways in which ``pytest`` can perform capturing:
.. _`disable capturing`:
You can influence output capturing mechanisms from the command line::
You can influence output capturing mechanisms from the command line:
.. code-block:: bash
pytest -s # disable all capturing
pytest --capture=sys # replace sys.stdout/stderr with in-mem files
@@ -69,7 +71,7 @@ of the failing function and hide the other one:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collected 2 items
test_module.py .F [100%]

View File

@@ -335,7 +335,7 @@ intersphinx_mapping = {"python": ("https://docs.python.org/3", None)}
def setup(app):
# from sphinx.ext.autodoc import cut_lines
# app.connect('autodoc-process-docstring', cut_lines(4, what=['module']))
app.add_description_unit(
app.add_object_type(
"confval",
"confval",
objname="configuration value",

View File

@@ -5,7 +5,9 @@ Command line options and configuration file settings
-----------------------------------------------------------------
You can get help on command line options and values in INI-style
configurations files by using the general help option::
configurations files by using the general help option:
.. code-block:: bash
pytest -h # prints options _and_ config file settings
@@ -92,12 +94,16 @@ The rootdir is used a reference directory for constructing test
addresses ("nodeids") and can be used also by plugins for storing
per-testrun information.
Example::
Example:
.. code-block:: bash
pytest path/to/testdir path/other/
will determine the common ancestor as ``path`` and then
check for ini-files as follows::
check for ini-files as follows:
.. code-block:: text
# first look for pytest.ini files
path/pytest.ini
@@ -127,25 +133,33 @@ progress output, you can write it into a configuration file:
.. code-block:: ini
# content of pytest.ini
# (or tox.ini or setup.cfg)
# content of pytest.ini or tox.ini
# setup.cfg files should use [tool:pytest] section instead
[pytest]
addopts = -ra -q
Alternatively, you can set a ``PYTEST_ADDOPTS`` environment variable to add command
line options while the environment is in use::
line options while the environment is in use:
.. code-block:: bash
export PYTEST_ADDOPTS="-v"
Here's how the command-line is built in the presence of ``addopts`` or the environment variable::
Here's how the command-line is built in the presence of ``addopts`` or the environment variable:
.. code-block:: text
<pytest.ini:addopts> $PYTEST_ADDOPTS <extra command-line arguments>
So if the user executes in the command-line::
So if the user executes in the command-line:
.. code-block:: bash
pytest -m slow
The actual command line executed is::
The actual command line executed is:
.. code-block:: bash
pytest -ra -q -v -m slow

View File

@@ -4,7 +4,9 @@ Doctest integration for modules and test files
By default all files matching the ``test*.txt`` pattern will
be run through the python standard ``doctest`` module. You
can change the pattern by issuing::
can change the pattern by issuing:
.. code-block:: bash
pytest --doctest-glob='*.rst'
@@ -26,7 +28,9 @@ can be given multiple times in the command-line.
You can also trigger running of doctests
from docstrings in all python modules (including regular
python test modules)::
python test modules):
.. code-block:: bash
pytest --doctest-modules
@@ -39,7 +43,9 @@ putting them into a pytest.ini file like this:
[pytest]
addopts = --doctest-modules
If you then have a text file like this::
If you then have a text file like this:
.. code-block:: text
# content of example.rst
@@ -73,7 +79,9 @@ then you can just invoke ``pytest`` without command line options:
========================= 1 passed in 0.12 seconds =========================
It is possible to use fixtures using the ``getfixture`` helper::
It is possible to use fixtures using the ``getfixture`` helper:
.. code-block:: text
# content of example.rst
>>> tmp = getfixture('tmpdir')
@@ -112,14 +120,18 @@ the ``doctest_optionflags`` ini option:
Alternatively, it can be enabled by an inline comment in the doc test
itself::
itself:
.. code-block:: rst
# content of example.rst
>>> get_unicode_greeting() # doctest: +ALLOW_UNICODE
'Hello'
By default, pytest would report only the first failure for a given doctest. If
you want to continue the test even when you have failures, do::
you want to continue the test even when you have failures, do:
.. code-block:: bash
pytest --doctest-modules --doctest-continue-on-failure
@@ -167,7 +179,9 @@ Output format
You can change the diff output format on failure for your doctests
by using one of standard doctest modules format in options
(see :data:`python:doctest.REPORT_UDIFF`, :data:`python:doctest.REPORT_CDIFF`,
:data:`python:doctest.REPORT_NDIFF`, :data:`python:doctest.REPORT_ONLY_FIRST_FAILURE`)::
:data:`python:doctest.REPORT_NDIFF`, :data:`python:doctest.REPORT_ONLY_FIRST_FAILURE`):
.. code-block:: bash
pytest --doctest-modules --doctest-report none
pytest --doctest-modules --doctest-report udiff

View File

@@ -35,7 +35,7 @@ You can then restrict a test run to only run tests marked with ``webtest``:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collecting ... collected 4 items / 3 deselected / 1 selected
test_server.py::test_send_http PASSED [100%]
@@ -50,7 +50,7 @@ Or the inverse, running all tests except the webtest ones:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collecting ... collected 4 items / 1 deselected / 3 selected
test_server.py::test_something_quick PASSED [ 33%]
@@ -72,7 +72,7 @@ tests based on their module, class, method, or function name:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collecting ... collected 1 item
test_server.py::TestClass::test_method PASSED [100%]
@@ -87,7 +87,7 @@ You can also select on the class:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collecting ... collected 1 item
test_server.py::TestClass::test_method PASSED [100%]
@@ -102,7 +102,7 @@ Or select multiple nodes:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collecting ... collected 2 items
test_server.py::TestClass::test_method PASSED [ 50%]
@@ -142,7 +142,7 @@ select tests based on their names:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collecting ... collected 4 items / 3 deselected / 1 selected
test_server.py::test_send_http PASSED [100%]
@@ -157,7 +157,7 @@ And you can also run all tests except the ones that match the keyword:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collecting ... collected 4 items / 1 deselected / 3 selected
test_server.py::test_something_quick PASSED [ 33%]
@@ -174,7 +174,7 @@ Or to select "http" and "quick" tests:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collecting ... collected 4 items / 2 deselected / 2 selected
test_server.py::test_send_http PASSED [ 50%]
@@ -204,14 +204,18 @@ Registering markers
.. ini-syntax for custom markers:
Registering markers for your test suite is simple::
Registering markers for your test suite is simple:
.. code-block:: ini
# content of pytest.ini
[pytest]
markers =
webtest: mark a test as a webtest.
You can ask which markers exist for your test suite - the list includes our just defined ``webtest`` markers::
You can ask which markers exist for your test suite - the list includes our just defined ``webtest`` markers:
.. code-block:: pytest
$ pytest --markers
@pytest.mark.webtest: mark a test as a webtest.
@@ -366,7 +370,7 @@ the test needs:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collected 1 item
test_someenv.py s [100%]
@@ -381,14 +385,16 @@ and here is one that specifies exactly the environment needed:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collected 1 item
test_someenv.py . [100%]
========================= 1 passed in 0.12 seconds =========================
The ``--markers`` option always gives you a list of available markers::
The ``--markers`` option always gives you a list of available markers:
.. code-block:: pytest
$ pytest --markers
@pytest.mark.env(name): mark test to run only on named environment
@@ -549,7 +555,7 @@ then you will see two tests skipped and two executed tests as expected:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collected 4 items
test_plat.py s.s. [100%]
@@ -566,7 +572,7 @@ Note that if you specify a platform via the marker-command line option like this
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collected 4 items / 3 deselected / 1 selected
test_plat.py . [100%]
@@ -620,7 +626,7 @@ We can now use the ``-m option`` to select one set:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collected 4 items / 2 deselected / 2 selected
test_module.py FF [100%]
@@ -644,7 +650,7 @@ or to select both "event" and "interface" tests:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collected 4 items / 1 deselected / 3 selected
test_module.py FFF [100%]

View File

@@ -31,7 +31,7 @@ now execute the test specification:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR/nonpython, inifile:
rootdir: $REGENDOC_TMPDIR/nonpython
collected 2 items
test_simple.yml F. [100%]
@@ -66,7 +66,7 @@ consulted when reporting in ``verbose`` mode:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR/nonpython, inifile:
rootdir: $REGENDOC_TMPDIR/nonpython
collecting ... collected 2 items
test_simple.yml::hello FAILED [ 50%]
@@ -90,7 +90,7 @@ interesting to just look at the collection tree:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR/nonpython, inifile:
rootdir: $REGENDOC_TMPDIR/nonpython
collected 2 items
<Package $REGENDOC_TMPDIR/nonpython>
<YamlFile test_simple.yml>

View File

@@ -146,7 +146,7 @@ objects, they are still using the default pytest representation:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collected 8 items
<Module test_time.py>
<Function test_timedistance_v0[a0-b0-expected0]>
@@ -205,7 +205,7 @@ this is a fully self-contained example which you can run with:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collected 4 items
test_scenarios.py .... [100%]
@@ -220,7 +220,7 @@ If you just collect tests you'll also nicely see 'advanced' and 'basic' as varia
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collected 4 items
<Module test_scenarios.py>
<Class TestSampleWithScenarios>
@@ -287,7 +287,7 @@ Let's first see how it looks like at collection time:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collected 2 items
<Module test_backends.py>
<Function test_db_initialized[d1]>
@@ -353,7 +353,7 @@ The result of this test will be successful:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collected 1 item
<Module test_indirect_list.py>
<Function test_indirect[a-b]>
@@ -411,8 +411,6 @@ argument sets to use for each test function. Let's run it:
def test_equals(self, a, b):
> assert a == b
E assert 1 == 2
E -1
E +2
test_parametrize.py:18: AssertionError
1 failed, 2 passed in 0.12 seconds
@@ -490,7 +488,7 @@ If you run this with reporting for skips enabled:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collected 2 items
test_module.py .s [100%]
@@ -548,7 +546,7 @@ Then run ``pytest`` with verbose mode and with only the ``basic`` marker:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collecting ... collected 17 items / 14 deselected / 3 selected
test_pytest_param_example.py::test_eval[1+7-8] PASSED [ 33%]

View File

@@ -6,7 +6,9 @@ Ignore paths during test collection
You can easily ignore certain test directories and modules during collection
by passing the ``--ignore=path`` option on the cli. ``pytest`` allows multiple
``--ignore`` options. Example::
``--ignore`` options. Example:
.. code-block:: text
tests/
|-- example
@@ -41,6 +43,9 @@ you will see that ``pytest`` only collects test-modules, which do not match the
========================= 5 passed in 0.02 seconds =========================
The ``--ignore-glob`` option allows to ignore test file paths based on Unix shell-style wildcards.
If you want to exclude test-modules that end with ``_01.py``, execute ``pytest`` with ``--ignore-glob='*_01.py'``.
Deselect tests during test collection
-------------------------------------
@@ -54,7 +59,9 @@ Keeping duplicate paths specified from command line
----------------------------------------------------
Default behavior of ``pytest`` is to ignore duplicate paths specified from the command line.
Example::
Example:
.. code-block:: pytest
pytest path_a path_a
@@ -65,7 +72,9 @@ Example::
Just collect tests once.
To collect duplicate tests, use the ``--keep-duplicates`` option on the cli.
Example::
Example:
.. code-block:: pytest
pytest --keep-duplicates path_a path_a
@@ -75,7 +84,9 @@ Example::
As the collector just works on directories, if you specify twice a single test file, ``pytest`` will
still collect it twice, no matter if the ``--keep-duplicates`` is not specified.
Example::
Example:
.. code-block:: pytest
pytest test_a.py test_a.py
@@ -87,7 +98,9 @@ Example::
Changing directory recursion
-----------------------------------------------------
You can set the :confval:`norecursedirs` option in an ini-file, for example your ``pytest.ini`` in the project root directory::
You can set the :confval:`norecursedirs` option in an ini-file, for example your ``pytest.ini`` in the project root directory:
.. code-block:: ini
# content of pytest.ini
[pytest]
@@ -103,7 +116,9 @@ Changing naming conventions
You can configure different naming conventions by setting
the :confval:`python_files`, :confval:`python_classes` and
:confval:`python_functions` configuration options.
Here is an example::
Here is an example:
.. code-block:: ini
# content of pytest.ini
# Example 1: have pytest look for "check" instead of "test"
@@ -142,7 +157,9 @@ The test collection would look like this:
======================= no tests ran in 0.12 seconds =======================
You can check for multiple glob patterns by adding a space between the patterns::
You can check for multiple glob patterns by adding a space between the patterns:
.. code-block:: ini
# Example 2: have pytest look for files with "test" and "example"
# content of pytest.ini, tox.ini, or setup.cfg file (replace "pytest"
@@ -162,13 +179,17 @@ Interpreting cmdline arguments as Python packages
You can use the ``--pyargs`` option to make ``pytest`` try
interpreting arguments as python package names, deriving
their file system path and then running the test. For
example if you have unittest2 installed you can type::
example if you have unittest2 installed you can type:
.. code-block:: bash
pytest --pyargs unittest2.test.test_skipping -q
which would run the respective test module. Like with
other options, through an ini-file and the :confval:`addopts` option you
can make this change more permanently::
can make this change more permanently:
.. code-block:: ini
# content of pytest.ini
[pytest]
@@ -206,7 +227,9 @@ Customizing test collection
.. regendoc:wipe
You can easily instruct ``pytest`` to discover tests from every Python file::
You can easily instruct ``pytest`` to discover tests from every Python file:
.. code-block:: ini
# content of pytest.ini
[pytest]
@@ -266,3 +289,17 @@ file will be left out:
collected 0 items
======================= no tests ran in 0.12 seconds =======================
It's also possible to ignore files based on Unix shell-style wildcards by adding
patterns to ``collect_ignore_glob``.
The following example ``conftest.py`` ignores the file ``setup.py`` and in
addition all files that end with ``*_py2.py`` when executed with a Python 3
interpreter::
# content of conftest.py
import sys
collect_ignore = ["setup.py"]
if sys.version_info[0] > 2:
collect_ignore_glob = ["*_py2.py"]

View File

@@ -15,7 +15,7 @@ get on the terminal - we are working on that):
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR/assertion, inifile:
rootdir: $REGENDOC_TMPDIR/assertion
collected 44 items
failure_demo.py FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF [100%]

View File

@@ -129,7 +129,7 @@ directory with the above conftest.py:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collected 0 items
======================= no tests ran in 0.12 seconds =======================
@@ -190,7 +190,7 @@ and when running it will see a skipped "slow" test:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collected 2 items
test_module.py .s [100%]
@@ -207,7 +207,7 @@ Or run it including the ``slow`` marked test:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collected 2 items
test_module.py .. [100%]
@@ -351,7 +351,7 @@ which will add the string to the test header accordingly:
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
project deps: mylib-1.1
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collected 0 items
======================= no tests ran in 0.12 seconds =======================
@@ -381,7 +381,7 @@ which will add info only when run with "--v":
cachedir: $PYTHON_PREFIX/.pytest_cache
info1: did you know that ...
did you?
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collecting ... collected 0 items
======================= no tests ran in 0.12 seconds =======================
@@ -394,7 +394,7 @@ and nothing when run plainly:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collected 0 items
======================= no tests ran in 0.12 seconds =======================
@@ -434,7 +434,7 @@ Now we can profile which test functions execute the slowest:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collected 3 items
test_some_are_slow.py ... [100%]
@@ -509,7 +509,7 @@ If we run this:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collected 4 items
test_step.py .Fx. [100%]
@@ -593,7 +593,7 @@ We can run this:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collected 7 items
test_step.py .Fx. [ 57%]
@@ -707,7 +707,7 @@ and run them:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collected 2 items
test_module.py FF [100%]
@@ -731,7 +731,9 @@ and run them:
test_module.py:6: AssertionError
========================= 2 failed in 0.12 seconds =========================
you will have a "failures" file which contains the failing test ids::
you will have a "failures" file which contains the failing test ids:
.. code-block:: bash
$ cat failures
test_module.py::test_fail1 (PYTEST_TMPDIR/test_fail10)
@@ -809,7 +811,7 @@ and run it:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collected 3 items
test_module.py Esetting up a test failed! test_module.py::test_setup_fails
@@ -935,6 +937,8 @@ like ``pytest-timeout`` they must be imported explicitly and passed on to pytest
This allows you to execute tests using the frozen
application with standard ``pytest`` command-line options::
application with standard ``pytest`` command-line options:
.. code-block:: bash
./app_main --pytest --verbose --tb=long --junitxml=results.xml test-suite/

View File

@@ -74,7 +74,7 @@ marked ``smtp_connection`` fixture function. Running the test looks like this:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collected 1 item
test_smtpsimple.py F [100%]
@@ -114,7 +114,9 @@ with a list of available function arguments.
.. note::
You can always issue ::
You can always issue:
.. code-block:: bash
pytest --fixtures test_simplefactory.py
@@ -215,7 +217,7 @@ inspect what is going on and can now run the tests:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collected 2 items
test_module.py FF [100%]
@@ -362,7 +364,9 @@ The ``print`` and ``smtp.close()`` statements will execute when the last test in
the module has finished execution, regardless of the exception status of the
tests.
Let's execute it::
Let's execute it:
.. code-block:: pytest
$ pytest -s -q --tb=no
FFteardown smtp
@@ -471,7 +475,9 @@ read an optional server URL from the test module which uses our fixture::
We use the ``request.module`` attribute to optionally obtain an
``smtpserver`` attribute from the test module. If we just execute
again, nothing much has changed::
again, nothing much has changed:
.. code-block:: pytest
$ pytest -s -q --tb=no
FFfinalizing <smtplib.SMTP object at 0xdeadbeef> (smtp.gmail.com)
@@ -704,7 +710,7 @@ Running the above tests results in the following test IDs being used:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collected 10 items
<Module test_anothersmtp.py>
<Function test_showhelo[smtp.gmail.com]>
@@ -749,7 +755,7 @@ Running this test will *skip* the invocation of ``data_set`` with value ``2``:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collecting ... collected 3 items
test_fixture_marks.py::test_data[0] PASSED [ 33%]
@@ -794,7 +800,7 @@ Here we declare an ``app`` fixture which receives the previously defined
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collecting ... collected 2 items
test_appsetup.py::test_smtp_connection_exists[smtp.gmail.com] PASSED [ 50%]
@@ -865,7 +871,7 @@ Let's run the tests in verbose mode and with looking at the print-output:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collecting ... collected 8 items
test_module.py::test_0[1] SETUP otherarg 1

View File

@@ -17,11 +17,15 @@ Installation and Getting Started
Install ``pytest``
----------------------------------------
1. Run the following command in your command line::
1. Run the following command in your command line:
.. code-block:: bash
pip install -U pytest
2. Check that you installed the correct version::
2. Check that you installed the correct version:
.. code-block:: bash
$ pytest --version
This is pytest version 4.x.y, imported from $PYTHON_PREFIX/lib/python3.6/site-packages/pytest.py
@@ -48,7 +52,7 @@ Thats it. You can now execute the test function:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collected 1 item
test_sample.py F [100%]
@@ -168,7 +172,9 @@ List the name ``tmpdir`` in the test function signature and ``pytest`` will look
More info on tmpdir handling is available at :ref:`Temporary directories and files <tmpdir handling>`.
Find out what kind of builtin :ref:`pytest fixtures <fixtures>` exist with the command::
Find out what kind of builtin :ref:`pytest fixtures <fixtures>` exist with the command:
.. code-block:: bash
pytest --fixtures # shows builtin and custom fixtures

View File

@@ -7,12 +7,13 @@ Good Integration Practices
Install package with pip
-------------------------------------------------
For development, we recommend to use virtualenv_ environments and pip_
for installing your application and any dependencies
as well as the ``pytest`` package itself. This ensures your code and
dependencies are isolated from the system Python installation.
For development, we recommend you use venv_ for virtual environments
(or virtualenv_ for Python 2.7) and
pip_ for installing your application and any dependencies,
as well as the ``pytest`` package itself.
This ensures your code and dependencies are isolated from your system Python installation.
First you need to place a ``setup.py`` file in the root of your package with the following minimum content::
Next, place a ``setup.py`` file in the root of your package with the following minimum content::
from setuptools import setup, find_packages
@@ -41,8 +42,8 @@ Conventions for Python test discovery
* In those directories, search for ``test_*.py`` or ``*_test.py`` files, imported by their `test package name`_.
* From those files, collect test items:
* ``test_`` prefixed test functions or methods outside of class
* ``test_`` prefixed test functions or methods inside ``Test`` prefixed test classes (without an ``__init__`` method)
* ``test`` prefixed test functions or methods outside of class
* ``test`` prefixed test functions or methods inside ``Test`` prefixed test classes (without an ``__init__`` method)
For examples of how to customize your test discovery :doc:`example/pythoncollection`.

View File

@@ -30,7 +30,7 @@ To execute it:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collected 1 item
test_sample.py F [100%]

View File

@@ -5,7 +5,7 @@ License
Distributed under the terms of the `MIT`_ license, pytest is free and open source software.
::
.. code-block:: text
The MIT License (MIT)

View File

@@ -14,6 +14,7 @@
.. _`distribute docs`:
.. _`distribute`: https://pypi.org/project/distribute/
.. _`pip`: https://pypi.org/project/pip/
.. _`venv`: https://docs.python.org/3/library/venv.html/
.. _`virtualenv`: https://pypi.org/project/virtualenv/
.. _hudson: http://hudson-ci.org/
.. _jenkins: http://jenkins-ci.org/

View File

@@ -9,11 +9,15 @@ Logging
pytest captures log messages of level ``WARNING`` or above automatically and displays them in their own section
for each failed test in the same manner as captured stdout and stderr.
Running without options::
Running without options:
.. code-block:: bash
pytest
Shows failed tests like so::
Shows failed tests like so:
.. code-block:: pytest
----------------------- Captured stdlog call ----------------------
test_reporting.py 26 WARNING text going to logger
@@ -27,12 +31,16 @@ By default each captured log message shows the module, line number, log level
and message.
If desired the log and date format can be specified to
anything that the logging module supports by passing specific formatting options::
anything that the logging module supports by passing specific formatting options:
.. code-block:: bash
pytest --log-format="%(asctime)s %(levelname)s %(message)s" \
--log-date-format="%Y-%m-%d %H:%M:%S"
Shows failed tests like so::
Shows failed tests like so:
.. code-block:: pytest
----------------------- Captured stdlog call ----------------------
2010-04-10 14:48:44 WARNING text going to logger
@@ -51,7 +59,9 @@ These options can also be customized through ``pytest.ini`` file:
log_date_format = %Y-%m-%d %H:%M:%S
Further it is possible to disable reporting of captured content (stdout,
stderr and logs) on failed tests completely with::
stderr and logs) on failed tests completely with:
.. code-block:: bash
pytest --show-capture=no
@@ -133,7 +143,6 @@ the records for the ``setup`` and ``call`` stages during teardown like so:
.. code-block:: python
@pytest.fixture
def window(caplog):
window = create_window()
@@ -198,6 +207,9 @@ option names are:
* ``log_file_format``
* ``log_file_date_format``
You can call ``set_log_path()`` to customize the log_file path dynamically. This functionality
is considered **experimental**.
.. _log_release_notes:
Release notes

View File

@@ -29,9 +29,11 @@ which also serve as documentation.
Raising errors on unknown marks: --strict
-----------------------------------------
When the ``--strict`` command-line flag is passed, any marks not registered in the ``pytest.ini`` file will trigger an error.
When the ``--strict`` command-line flag is passed, any unknown marks applied
with the ``@pytest.mark.name_of_the_mark`` decorator will trigger an error.
Marks defined or added by pytest or by a plugin will not trigger an error.
Marks can be registered like this:
Marks can be registered in ``pytest.ini`` like this:
.. code-block:: ini

View File

@@ -12,7 +12,9 @@ Running tests written for nose
Usage
-------------
After :ref:`installation` type::
After :ref:`installation` type:
.. code-block:: bash
python setup.py develop # make sure tests can import our package
pytest # instead of 'nosetests'

View File

@@ -58,7 +58,7 @@ them in turn:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collected 3 items
test_expectation.py ..F [100%]
@@ -81,6 +81,21 @@ them in turn:
test_expectation.py:8: AssertionError
==================== 1 failed, 2 passed in 0.12 seconds ====================
.. note::
pytest by default escapes any non-ascii characters used in unicode strings
for the parametrization because it has several downsides.
If however you would like to use unicode strings in parametrization and see them in the terminal as is (non-escaped), use this option in your ``pytest.ini``:
.. code-block:: ini
[pytest]
disable_test_id_escaping_and_forfeit_all_rights_to_community_support = True
Keep in mind however that this might cause unwanted side effects and
even bugs depending on the OS used and plugins currently installed, so use it at your own risk.
As designed in this example, only one pair of input/output values fails
the simple test function. And as usual with test function arguments,
you can see the ``input`` and ``output`` values in the traceback.
@@ -110,7 +125,7 @@ Let's run this:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collected 3 items
test_expectation.py ..x [100%]
@@ -172,7 +187,9 @@ command line option and the parametrization of our test function::
metafunc.parametrize("stringinput",
metafunc.config.getoption('stringinput'))
If we now pass two stringinput values, our test will run twice::
If we now pass two stringinput values, our test will run twice:
.. code-block:: pytest
$ pytest -q --stringinput="hello" --stringinput="world" test_strings.py
.. [100%]

View File

@@ -27,7 +27,7 @@ Here is a little annotated list for some popular plugins:
for `twisted <http://twistedmatrix.com>`_ apps, starting a reactor and
processing deferreds from test functions.
* `pytest-cov <https://pypi.org/project/pytest-cov/>`_:
* `pytest-cov <https://pypi.org/project/pytest-cov/>`__:
coverage reporting, compatible with distributed testing
* `pytest-xdist <https://pypi.org/project/pytest-xdist/>`_:

View File

@@ -1,4 +1,3 @@
Reference
=========
@@ -49,7 +48,7 @@ pytest.main
.. autofunction:: _pytest.config.main
pytest.param
~~~~~~~~~~~~~
~~~~~~~~~~~~
.. autofunction:: pytest.param(*values, [id], [marks])
@@ -199,16 +198,18 @@ Marks a test function as *expected to fail*.
.. py:function:: pytest.mark.xfail(condition=None, *, reason=None, raises=None, run=True, strict=False)
:type condition: bool or str
:param condition: ``True/False`` if the condition should be marked as xfail or a :ref:`condition string <string conditions>`.
:param condition:
Condition for marking the test function as xfail (``True/False`` or a
:ref:`condition string <string conditions>`).
:keyword str reason: Reason why the test function is marked as xfail.
:keyword Exception raises: Exception subclass expected to be raised by the test function; other exceptions will fail the test.
:keyword bool run:
If the test function should actually be executed. If ``False``, the function will always xfail and will
not be executed (useful a function is segfaulting).
not be executed (useful if a function is segfaulting).
:keyword bool strict:
* If ``False`` (the default) the function will be shown in the terminal output as ``xfailed`` if it fails
and as ``xpass`` if it passes. In both cases this will not cause the test suite to fail as a whole. This
is particularly useful to mark *flaky* tests (tests that random at fail) to be tackled later.
is particularly useful to mark *flaky* tests (tests that fail at random) to be tackled later.
* If ``True``, the function will be shown in the terminal output as ``xfailed`` if it fails, but if it
unexpectedly passes then it will **fail** the test suite. This is particularly useful to mark functions
that are always failing and there should be a clear indication if they unexpectedly start to pass (for example
@@ -499,6 +500,32 @@ Each recorded warning is an instance of :class:`warnings.WarningMessage`.
differently; see :ref:`ensuring_function_triggers`.
tmp_path
~~~~~~~~
**Tutorial**: :doc:`tmpdir`
.. currentmodule:: _pytest.tmpdir
.. autofunction:: tmp_path()
:no-auto-options:
tmp_path_factory
~~~~~~~~~~~~~~~~
**Tutorial**: :ref:`tmp_path_factory example`
.. _`tmp_path_factory factory api`:
``tmp_path_factory`` instances have the following methods:
.. currentmodule:: _pytest.tmpdir
.. automethod:: TempPathFactory.mktemp
.. automethod:: TempPathFactory.getbasetemp
tmpdir
~~~~~~
@@ -558,6 +585,8 @@ Initialization hooks called for plugins and ``conftest.py`` files.
.. autofunction:: pytest_sessionstart
.. autofunction:: pytest_sessionfinish
.. autofunction:: pytest_plugin_registered
Test running hooks
~~~~~~~~~~~~~~~~~~
@@ -581,6 +610,8 @@ into interactive debugging when a test failure occurs.
The :py:mod:`_pytest.terminal` reported specifically uses
the reporting hook to print information about a test run.
.. autofunction:: pytest_pyfunc_call
Collection hooks
~~~~~~~~~~~~~~~~
@@ -590,6 +621,7 @@ Collection hooks
.. autofunction:: pytest_ignore_collect
.. autofunction:: pytest_collect_directory
.. autofunction:: pytest_collect_file
.. autofunction:: pytest_pycollect_makemodule
For influencing the collection of objects in Python modules
you can use the following hook:
@@ -603,12 +635,15 @@ items, delete or otherwise amend the test items:
.. autofunction:: pytest_collection_modifyitems
.. autofunction:: pytest_collection_finish
Reporting hooks
~~~~~~~~~~~~~~~
Session related reporting hooks:
.. autofunction:: pytest_collectstart
.. autofunction:: pytest_make_collect_report
.. autofunction:: pytest_itemcollected
.. autofunction:: pytest_collectreport
.. autofunction:: pytest_deselected
@@ -797,6 +832,33 @@ Special Variables
pytest treats some global variables in a special manner when defined in a test module.
collect_ignore
~~~~~~~~~~~~~~
**Tutorial**: :ref:`customizing-test-collection`
Can be declared in *conftest.py files* to exclude test directories or modules.
Needs to be ``list[str]``.
.. code-block:: python
collect_ignore = ["setup.py"]
collect_ignore_glob
~~~~~~~~~~~~~~~~~~~
**Tutorial**: :ref:`customizing-test-collection`
Can be declared in *conftest.py files* to exclude test directories or modules
with Unix shell-style wildcards. Needs to be ``list[str]`` where ``str`` can
contain glob patterns.
.. code-block:: python
collect_ignore_glob = ["*_ignore.py"]
pytest_plugins
~~~~~~~~~~~~~~
@@ -820,7 +882,7 @@ pytest_mark
**Tutorial**: :ref:`scoped-marking`
Can be declared at the **global** level in *test modules* to apply one or more :ref:`marks <marks ref>` to all
test functions and methods. Can be either a single mark or a sequence of marks.
test functions and methods. Can be either a single mark or a list of marks.
.. code-block:: python
@@ -833,7 +895,7 @@ test functions and methods. Can be either a single mark or a sequence of marks.
import pytest
pytestmark = (pytest.mark.integration, pytest.mark.slow)
pytestmark = [pytest.mark.integration, pytest.mark.slow]
PYTEST_DONT_REWRITE (module docstring)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -1199,8 +1261,11 @@ passed multiple times. The expected format is ``name=value``. For example::
.. confval:: markers
List of markers that are allowed in test functions, enforced when ``--strict`` command-line argument is used.
You can use a marker name per line, indented from the option name.
When the ``--strict`` command-line argument is used, only known markers -
defined in code by core pytest or some plugin - are allowed.
You can list additional markers in this setting to add them to the whitelist.
You can list one marker name per line, indented from the option name.
.. code-block:: ini

View File

@@ -1,4 +1,4 @@
pygments-pytest>=1.1.0
sphinx>=1.8.2
sphinx>=1.8.2,<2.1
sphinxcontrib-trio
sphinx-removed-in>=0.1.3
sphinx-removed-in>=0.2.0

View File

@@ -22,7 +22,9 @@ it's an **xpass** and will be reported in the test summary.
``pytest`` counts and lists *skip* and *xfail* tests separately. Detailed
information about skipped/xfailed tests is not shown by default to avoid
cluttering the output. You can use the ``-r`` option to see details
corresponding to the "short" letters shown in the test progress::
corresponding to the "short" letters shown in the test progress:
.. code-block:: bash
pytest -rxXs # show extra info on xfailed, xpassed, and skipped tests
@@ -82,7 +84,7 @@ It is also possible to skip the whole module using
If you wish to skip something conditionally then you can use ``skipif`` instead.
Here is an example of marking a test function to be skipped
when run on an interpreter earlier than Python3.6 ::
when run on an interpreter earlier than Python3.6::
import sys
@pytest.mark.skipif(sys.version_info < (3,6),
@@ -309,7 +311,9 @@ investigated later.
Ignoring xfail
~~~~~~~~~~~~~~
By specifying on the commandline::
By specifying on the commandline:
.. code-block:: bash
pytest --runxfail
@@ -331,7 +335,7 @@ Running it with the report-on-xfail option gives this output:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR/example, inifile:
rootdir: $REGENDOC_TMPDIR/example
collected 7 items
xfail_demo.py xxxxxxx [100%]

View File

@@ -25,6 +25,9 @@ Talks and blog postings
- pytest: recommendations, basic packages for testing in Python and Django, Andreu Vallbona, PyconES 2017 (`slides in english <http://talks.apsl.io/testing-pycones-2017/>`_, `video in spanish <https://www.youtube.com/watch?v=K20GeR-lXDk>`_)
- `pytest advanced, Andrew Svetlov (Russian, PyCon Russia, 2016)
<https://www.youtube.com/watch?v=7KgihdKTWY4>`_.
- `Pythonic testing, Igor Starikov (Russian, PyNsk, November 2016)
<https://www.youtube.com/watch?v=_92nfdd5nK8>`_.

View File

@@ -43,7 +43,7 @@ Running this would result in a passed test except for the last
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collected 1 item
test_tmp_path.py F [100%]
@@ -66,6 +66,8 @@ Running this would result in a passed test except for the last
test_tmp_path.py:13: AssertionError
========================= 1 failed in 0.12 seconds =========================
.. _`tmp_path_factory example`:
The ``tmp_path_factory`` fixture
--------------------------------
@@ -77,6 +79,8 @@ to create arbitrary temporary directories from any other fixture or test.
It is intended to replace ``tmpdir_factory``, and returns :class:`pathlib.Path` instances.
See :ref:`tmp_path_factory API <tmp_path_factory factory api>` for details.
The 'tmpdir' fixture
--------------------
@@ -106,7 +110,7 @@ Running this would result in a passed test except for the last
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collected 1 item
test_tmpdir.py F [100%]
@@ -174,7 +178,9 @@ the system temporary directory. The base name will be ``pytest-NUM`` where
``NUM`` will be incremented with each test run. Moreover, entries older
than 3 temporary directories will be removed.
You can override the default temporary directory setting like this::
You can override the default temporary directory setting like this:
.. code-block:: bash
pytest --basetemp=mydir

View File

@@ -130,7 +130,7 @@ the ``self.db`` values in the traceback:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collected 2 items
test_unittest_db.py FF [100%]

View File

@@ -12,7 +12,9 @@ Calling pytest through ``python -m pytest``
.. versionadded:: 2.0
You can invoke testing through the Python interpreter from the command line::
You can invoke testing through the Python interpreter from the command line:
.. code-block:: text
python -m pytest [...]
@@ -34,7 +36,7 @@ Running ``pytest`` can result in six different exit codes:
Getting help on version, option names, environment variables
--------------------------------------------------------------
::
.. code-block:: bash
pytest --version # shows where pytest was imported from
pytest --fixtures # show available builtin function arguments
@@ -46,7 +48,9 @@ Getting help on version, option names, environment variables
Stopping after the first (or N) failures
---------------------------------------------------
To stop the testing process after the first (N) failures::
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
@@ -60,19 +64,19 @@ Pytest supports several ways to run and select tests from the command-line.
**Run tests in a module**
::
.. code-block:: bash
pytest test_mod.py
**Run tests in a directory**
::
.. code-block:: bash
pytest testing/
**Run tests by keyword expressions**
::
.. code-block:: bash
pytest -k "MyClass and not method"
@@ -87,18 +91,22 @@ The example above will run ``TestMyClass.test_something`` but not ``TestMyClass
Each collected test is assigned a unique ``nodeid`` which consist of the module filename followed
by specifiers like class names, function names and parameters from parametrization, separated by ``::`` characters.
To run a specific test within a module::
To run a specific test within a module:
.. code-block:: bash
pytest test_mod.py::test_func
Another example specifying a test method in the command line::
Another example specifying a test method in the command line:
.. code-block:: bash
pytest test_mod.py::TestClass::test_method
**Run tests by marker expressions**
::
.. code-block:: bash
pytest -m slow
@@ -108,7 +116,7 @@ For more information see :ref:`marks <mark>`.
**Run tests from packages**
::
.. code-block:: bash
pytest --pyargs pkg.testing
@@ -118,7 +126,9 @@ This will import ``pkg.testing`` and use its filesystem location to find and run
Modifying Python traceback printing
----------------------------------------------
Examples for modifying traceback printing::
Examples for modifying traceback printing:
.. code-block:: bash
pytest --showlocals # show local variables in tracebacks
pytest -l # show local variables (shortcut)
@@ -194,7 +204,7 @@ Example:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collected 6 items
test_example.py .FEsxX [100%]
@@ -246,7 +256,7 @@ More than one character can be used, so for example to only see failed and skipp
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collected 6 items
test_example.py .FEsxX [100%]
@@ -282,7 +292,7 @@ captured output:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collected 6 items
test_example.py .FEsxX [100%]
@@ -320,13 +330,17 @@ Dropping to PDB_ (Python Debugger) on failures
.. _PDB: http://docs.python.org/library/pdb.html
Python comes with a builtin Python debugger called PDB_. ``pytest``
allows one to drop into the PDB_ prompt via a command line option::
allows one to drop into the PDB_ prompt via a command line option:
.. code-block:: bash
pytest --pdb
This will invoke the Python debugger on every failure (or KeyboardInterrupt).
Often you might only want to do this for the first failing test to understand
a certain failure situation::
a certain failure situation:
.. code-block:: bash
pytest -x --pdb # drop to PDB on first failure, then end test session
pytest --pdb --maxfail=3 # drop to PDB for first three failures
@@ -349,7 +363,9 @@ Dropping to PDB_ (Python Debugger) at the start of a test
----------------------------------------------------------
``pytest`` allows one to drop into the PDB_ prompt immediately at the start of each test via a command line option::
``pytest`` allows one to drop into the PDB_ prompt immediately at the start of each test via a command line option:
.. code-block:: bash
pytest --trace
@@ -368,10 +384,8 @@ in your code and pytest automatically disables its output capture for that test:
* Output capture in other tests is not affected.
* Any prior test output that has already been captured and will be processed as
such.
* Any later output produced within the same test will not be captured and will
instead get sent directly to ``sys.stdout``. Note that this holds true even
for test output occurring after you exit the interactive PDB_ tracing session
and continue with the regular test run.
* Output capture gets resumed when ending the debugger session (via the
``continue`` command).
.. _`breakpoint-builtin`:
@@ -394,7 +408,9 @@ Profiling test execution duration
.. versionadded: 2.2
To get a list of the slowest 10 test durations::
To get a list of the slowest 10 test durations:
.. code-block:: bash
pytest --durations=10
@@ -404,7 +420,9 @@ Creating JUnitXML format files
----------------------------------------------------
To create result files which can be read by Jenkins_ or other Continuous
integration servers, use this invocation::
integration servers, use this invocation:
.. code-block:: bash
pytest --junitxml=path
@@ -627,7 +645,9 @@ Creating resultlog format files
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::
To create plain-text machine-readable result files you can issue:
.. code-block:: bash
pytest --resultlog=path
@@ -640,7 +660,9 @@ by the `PyPy-test`_ web page to show test results over several revisions.
Sending test report to online pastebin service
-----------------------------------------------------
**Creating a URL for each test failure**::
**Creating a URL for each test failure**:
.. code-block:: bash
pytest --pastebin=failed
@@ -648,12 +670,30 @@ This will submit test run information to a remote Paste service and
provide a URL for each failure. You may select tests as usual or add
for example ``-x`` if you only want to send one particular failure.
**Creating a URL for a whole test session log**::
**Creating a URL for a whole test session log**:
.. code-block:: bash
pytest --pastebin=all
Currently only pasting to the http://bpaste.net service is implemented.
Early loading plugins
---------------------
You can early-load plugins (internal and external) explicitly in the command-line with the ``-p`` option::
pytest -p mypluginmodule
The option receives a ``name`` parameter, which can be:
* A full module dotted name, for example ``myproject.plugins``. This dotted name must be importable.
* The entry-point name of a plugin. This is the name passed to ``setuptools`` when the plugin is
registered. For example to early-load the `pytest-cov <https://pypi.org/project/pytest-cov/>`__ plugin you can use::
pytest -p pytest_cov
Disabling plugins
-----------------
@@ -661,7 +701,9 @@ To disable loading specific plugins at invocation time, use the ``-p`` option
together with the prefix ``no:``.
Example: to disable loading the plugin ``doctest``, which is responsible for
executing doctest tests from text files, invoke pytest like this::
executing doctest tests from text files, invoke pytest like this:
.. code-block:: bash
pytest -p no:doctest
@@ -693,7 +735,9 @@ You can specify additional plugins to ``pytest.main``::
pytest.main(["-qq"], plugins=[MyPlugin()])
Running it will show that ``MyPlugin`` was added and its
hook was invoked::
hook was invoked:
.. code-block:: pytest
$ python myinvoke.py
.FEsxX. [100%]*** test run reporting finishing

View File

@@ -26,7 +26,7 @@ Running pytest now produces this output:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR, inifile:
rootdir: $REGENDOC_TMPDIR
collected 1 item
test_show_warnings.py . [100%]

View File

@@ -1,6 +0,0 @@
REM install pypy using choco
REM redirect to a file because choco install python.pypy is too noisy. If the command fails, write output to console
choco install python.pypy > pypy-inst.log 2>&1 || (type pypy-inst.log & exit /b 1)
set PATH=C:\tools\pypy\pypy;%PATH% # so tox can find pypy
echo PyPy installed
pypy --version

View File

@@ -1,10 +0,0 @@
REM scripts called by AppVeyor to setup the environment variables to enable coverage
if not defined PYTEST_NO_COVERAGE (
set "COVERAGE_FILE=%CD%\.coverage"
set "COVERAGE_PROCESS_START=%CD%\.coveragerc"
set "_PYTEST_TOX_COVERAGE_RUN=coverage run -m"
set "_PYTEST_TOX_EXTRA_DEP=coverage-enable-subprocess"
echo Coverage setup completed
) else (
echo Skipping coverage setup, PYTEST_NO_COVERAGE is set
)

View File

@@ -0,0 +1,7 @@
if "%PYTEST_COVERAGE%" == "1" (
set "_PYTEST_TOX_COVERAGE_RUN=coverage run -m"
set "_PYTEST_TOX_EXTRA_DEP=coverage-enable-subprocess"
echo Coverage vars configured, PYTEST_COVERAGE=%PYTEST_COVERAGE%
) else (
echo Skipping coverage vars setup, PYTEST_COVERAGE=%PYTEST_COVERAGE%
)

View File

@@ -1,11 +1,16 @@
REM script called by AppVeyor to combine and upload coverage information to codecov
if not defined PYTEST_NO_COVERAGE (
REM script called by Azure to combine and upload coverage information to codecov
if "%PYTEST_COVERAGE%" == "1" (
echo Prepare to upload coverage information
C:\Python36\Scripts\pip install codecov
C:\Python36\Scripts\coverage combine
C:\Python36\Scripts\coverage xml --ignore-errors
C:\Python36\Scripts\coverage report -m --ignore-errors
scripts\appveyor-retry C:\Python36\Scripts\codecov --required -X gcov pycov search -f coverage.xml --flags %TOXENV:-= % windows
if defined CODECOV_TOKEN (
echo CODECOV_TOKEN defined
) else (
echo CODECOV_TOKEN NOT defined
)
%PYTHON% -m pip install codecov
%PYTHON% -m coverage combine
%PYTHON% -m coverage xml
%PYTHON% -m coverage report -m
scripts\retry %PYTHON% -m codecov --required -X gcov pycov search -f coverage.xml --name %PYTEST_CODECOV_NAME%
) else (
echo Skipping coverage upload, PYTEST_NO_COVERAGE is set
echo Skipping coverage upload, PYTEST_COVERAGE=%PYTEST_COVERAGE%
)

View File

@@ -1,8 +1,5 @@
import os
from setuptools import setup
# TODO: if py gets upgrade to >=1.6,
# remove _width_of_current_line in terminal.py
INSTALL_REQUIRES = [
@@ -13,18 +10,13 @@ INSTALL_REQUIRES = [
'more-itertools>=4.0.0,<6.0.0;python_version<="2.7"',
'more-itertools>=4.0.0;python_version>"2.7"',
"atomicwrites>=1.0",
'funcsigs;python_version<"3.0"',
'funcsigs>=1.0;python_version<"3.0"',
'pathlib2>=2.2.0;python_version<"3.6"',
'colorama;sys_platform=="win32"',
"pluggy>=0.9",
]
# if _PYTEST_SETUP_SKIP_PLUGGY_DEP is set, skip installing pluggy;
# used by tox.ini to test with pluggy master
if "_PYTEST_SETUP_SKIP_PLUGGY_DEP" not in os.environ:
INSTALL_REQUIRES.append("pluggy>=0.7")
def main():
setup(
use_scm_version={"write_to": "src/_pytest/_version.py"},
@@ -33,6 +25,7 @@ def main():
# fmt: off
extras_require={
"testing": [
"argcomplete",
"hypothesis>=3.56",
"nose",
"requests",

View File

@@ -241,25 +241,20 @@ class TracebackEntry(object):
def ishidden(self):
""" return True if the current frame has a var __tracebackhide__
resolving to True
resolving to True.
If __tracebackhide__ is a callable, it gets called with the
ExceptionInfo instance and can decide whether to hide the traceback.
mostly for internal use
"""
try:
tbh = self.frame.f_locals["__tracebackhide__"]
except KeyError:
try:
tbh = self.frame.f_globals["__tracebackhide__"]
except KeyError:
return False
if callable(tbh):
f = self.frame
tbh = f.f_locals.get(
"__tracebackhide__", f.f_globals.get("__tracebackhide__", False)
)
if tbh and callable(tbh):
return tbh(None if self._excinfo is None else self._excinfo())
else:
return tbh
return tbh
def __str__(self):
try:
@@ -418,6 +413,7 @@ class ExceptionInfo(object):
to the exception message/``__str__()``
"""
tup = sys.exc_info()
assert tup[0] is not None, "no current exception"
_striptext = ""
if exprinfo is None and isinstance(tup[1], AssertionError):
exprinfo = getattr(tup[1], "msg", None)

View File

@@ -203,7 +203,9 @@ def compile_(source, filename=None, mode="exec", flags=0, dont_inherit=0):
def getfslineno(obj):
""" Return source location (path, lineno) for the given object.
If the source cannot be determined return ("", -1)
If the source cannot be determined return ("", -1).
The line number is 0-based.
"""
from .code import Code

View File

@@ -21,6 +21,9 @@ import six
from _pytest._io.saferepr import saferepr
from _pytest.assertion import util
from _pytest.assertion.util import ( # noqa: F401
format_explanation as _format_explanation,
)
from _pytest.compat import spec_from_file_location
from _pytest.pathlib import fnmatch_ex
from _pytest.pathlib import PurePath
@@ -344,9 +347,11 @@ def _write_pyc(state, co, source_stat, pyc):
try:
with atomicwrites.atomic_write(pyc, mode="wb", overwrite=True) as fp:
fp.write(imp.get_magic())
mtime = int(source_stat.mtime)
# as of now, bytecode header expects 32-bit numbers for size and mtime (#4903)
mtime = int(source_stat.mtime) & 0xFFFFFFFF
size = source_stat.size & 0xFFFFFFFF
fp.write(struct.pack("<ll", mtime, size))
# "<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 %s: errno=%s" % (pyc, e.errno))
@@ -441,7 +446,7 @@ def _read_pyc(source, pyc, trace=lambda x: None):
if (
len(data) != 12
or data[:4] != imp.get_magic()
or struct.unpack("<ll", data[4:]) != (mtime, size)
or struct.unpack("<LL", data[4:]) != (mtime & 0xFFFFFFFF, size & 0xFFFFFFFF)
):
trace("_read_pyc(%s): invalid or out of date pyc" % source)
return None
@@ -483,9 +488,6 @@ def _saferepr(obj):
return r.replace(u"\n", u"\\n")
from _pytest.assertion.util import format_explanation as _format_explanation # noqa
def _format_assertmsg(obj):
"""Format the custom assertion message given.

View File

@@ -12,7 +12,6 @@ import os
import six
DEFAULT_MAX_LINES = 8
DEFAULT_MAX_CHARS = 8 * 80
USAGE_MSG = "use '-vv' to show"

View File

@@ -9,6 +9,7 @@ import six
import _pytest._code
from ..compat import Sequence
from _pytest import outcomes
from _pytest._io.saferepr import saferepr
# The _reprcompare attribute on the util module is used by the new assertion
@@ -102,6 +103,38 @@ except NameError:
basestring = str
def issequence(x):
return isinstance(x, Sequence) and not isinstance(x, basestring)
def istext(x):
return isinstance(x, basestring)
def isdict(x):
return isinstance(x, dict)
def isset(x):
return isinstance(x, (set, frozenset))
def isdatacls(obj):
return getattr(obj, "__dataclass_fields__", None) is not None
def isattrs(obj):
return getattr(obj, "__attrs_attrs__", None) is not None
def isiterable(obj):
try:
iter(obj)
return not istext(obj)
except TypeError:
return False
def assertrepr_compare(config, op, left, right):
"""Return specialised explanations for some operators/operands"""
width = 80 - 15 - len(op) - 2 # 15 chars indentation, 1 space around op
@@ -110,31 +143,6 @@ def assertrepr_compare(config, op, left, right):
summary = u"%s %s %s" % (ecu(left_repr), op, ecu(right_repr))
def issequence(x):
return isinstance(x, Sequence) and not isinstance(x, basestring)
def istext(x):
return isinstance(x, basestring)
def isdict(x):
return isinstance(x, dict)
def isset(x):
return isinstance(x, (set, frozenset))
def isdatacls(obj):
return getattr(obj, "__dataclass_fields__", None) is not None
def isattrs(obj):
return getattr(obj, "__attrs_attrs__", None) is not None
def isiterable(obj):
try:
iter(obj)
return not istext(obj)
except TypeError:
return False
verbose = config.getoption("verbose")
explanation = None
try:
@@ -151,7 +159,7 @@ def assertrepr_compare(config, op, left, right):
elif type(left) == type(right) and (isdatacls(left) or isattrs(left)):
type_fn = (isdatacls, isattrs)
explanation = _compare_eq_cls(left, right, verbose, type_fn)
elif verbose:
elif verbose > 0:
explanation = _compare_eq_verbose(left, right)
if isiterable(left) and isiterable(right):
expl = _compare_eq_iterable(left, right, verbose)
@@ -162,6 +170,8 @@ def assertrepr_compare(config, op, left, right):
elif op == "not in":
if istext(left) and istext(right):
explanation = _notin_text(left, right, verbose)
except outcomes.Exit:
raise
except Exception:
explanation = [
u"(pytest_assertion plugin: representation of details failed. "
@@ -175,8 +185,8 @@ def assertrepr_compare(config, op, left, right):
return [summary] + explanation
def _diff_text(left, right, verbose=False):
"""Return the explanation for the diff between text or bytes
def _diff_text(left, right, verbose=0):
"""Return the explanation for the diff between text or bytes.
Unless --verbose is used this will skip leading and trailing
characters which are identical to keep the diff minimal.
@@ -202,7 +212,7 @@ def _diff_text(left, right, verbose=False):
left = escape_for_readable_diff(left)
if isinstance(right, bytes):
right = escape_for_readable_diff(right)
if not verbose:
if verbose < 1:
i = 0 # just in case left or right has zero length
for i in range(min(len(left), len(right))):
if left[i] != right[i]:
@@ -250,7 +260,7 @@ def _compare_eq_verbose(left, right):
return explanation
def _compare_eq_iterable(left, right, verbose=False):
def _compare_eq_iterable(left, right, verbose=0):
if not verbose:
return [u"Use -v to get the full diff"]
# dynamic import to speedup pytest
@@ -273,7 +283,7 @@ def _compare_eq_iterable(left, right, verbose=False):
return explanation
def _compare_eq_sequence(left, right, verbose=False):
def _compare_eq_sequence(left, right, verbose=0):
explanation = []
for i in range(min(len(left), len(right))):
if left[i] != right[i]:
@@ -292,7 +302,7 @@ def _compare_eq_sequence(left, right, verbose=False):
return explanation
def _compare_eq_set(left, right, verbose=False):
def _compare_eq_set(left, right, verbose=0):
explanation = []
diff_left = left - right
diff_right = right - left
@@ -307,7 +317,7 @@ def _compare_eq_set(left, right, verbose=False):
return explanation
def _compare_eq_dict(left, right, verbose=False):
def _compare_eq_dict(left, right, verbose=0):
explanation = []
common = set(left).intersection(set(right))
same = {k: left[k] for k in common if left[k] == right[k]}
@@ -368,7 +378,7 @@ def _compare_eq_cls(left, right, verbose, type_fns):
return explanation
def _notin_text(term, text, verbose=False):
def _notin_text(term, text, verbose=0):
index = text.find(term)
head = text[:index]
tail = text[index + len(term) :]

View File

@@ -121,10 +121,12 @@ class Cache(object):
cache_dir_exists_already = True
else:
cache_dir_exists_already = self._cachedir.exists()
path.parent.mkdir(exist_ok=True, parents=True)
path.parent.mkdir(exist_ok=True, parents=True)
except (IOError, OSError):
self.warn("could not create cache path {path}", path=path)
return
if not cache_dir_exists_already:
self._ensure_supporting_files()
try:
f = path.open("wb" if PY2 else "w")
except (IOError, OSError):
@@ -132,24 +134,18 @@ class Cache(object):
else:
with f:
json.dump(value, f, indent=2, sort_keys=True)
if not cache_dir_exists_already:
self._ensure_supporting_files()
def _ensure_supporting_files(self):
"""Create supporting files in the cache dir that are not really part of the cache."""
if self._cachedir.is_dir():
readme_path = self._cachedir / "README.md"
if not readme_path.is_file():
readme_path.write_text(README_CONTENT)
readme_path = self._cachedir / "README.md"
readme_path.write_text(README_CONTENT)
gitignore_path = self._cachedir.joinpath(".gitignore")
if not gitignore_path.is_file():
msg = u"# Created by pytest automatically.\n*"
gitignore_path.write_text(msg, encoding="UTF-8")
gitignore_path = self._cachedir.joinpath(".gitignore")
msg = u"# Created by pytest automatically.\n*"
gitignore_path.write_text(msg, encoding="UTF-8")
cachedir_tag_path = self._cachedir.joinpath("CACHEDIR.TAG")
if not cachedir_tag_path.is_file():
cachedir_tag_path.write_bytes(CACHEDIR_TAG_CONTENT)
cachedir_tag_path = self._cachedir.joinpath("CACHEDIR.TAG")
cachedir_tag_path.write_bytes(CACHEDIR_TAG_CONTENT)
class LFPlugin(object):
@@ -344,7 +340,7 @@ def cache(request):
def pytest_report_header(config):
"""Display cachedir with --cache-show and if non-default."""
if config.option.verbose or config.getini("cache_dir") != ".pytest_cache":
if config.option.verbose > 0 or config.getini("cache_dir") != ".pytest_cache":
cachedir = config.cache._cachedir
# TODO: evaluate generating upward relative paths
# starting with .., ../.. if sensible

View File

@@ -17,6 +17,7 @@ from tempfile import TemporaryFile
import six
import pytest
from _pytest.compat import _PY3
from _pytest.compat import CaptureIO
patchsysdict = {0: "stdin", 1: "stdout", 2: "stderr"}
@@ -90,6 +91,13 @@ class CaptureManager(object):
self._global_capturing = None
self._current_item = None
def __repr__(self):
return "<CaptureManager _method=%r _global_capturing=%r _current_item=%r>" % (
self._method,
self._global_capturing,
self._current_item,
)
def _getcapture(self, method):
if method == "fd":
return MultiCapture(out=True, err=True, Capture=FDCapture)
@@ -97,8 +105,17 @@ class CaptureManager(object):
return MultiCapture(out=True, err=True, Capture=SysCapture)
elif method == "no":
return MultiCapture(out=False, err=False, in_=False)
else:
raise ValueError("unknown capturing method: %r" % method)
raise ValueError("unknown capturing method: %r" % method) # pragma: no cover
def is_capturing(self):
if self.is_globally_capturing():
return "global"
capture_fixture = getattr(self._current_item, "_capture_fixture", None)
if capture_fixture is not None:
return (
"fixture %s" % self._current_item._capture_fixture.request.fixturename
)
return False
# Global capturing control
@@ -127,6 +144,15 @@ class CaptureManager(object):
if cap is not None:
cap.suspend_capturing(in_=in_)
def suspend(self, in_=False):
# Need to undo local capsys-et-al if it exists before disabling global capture.
self.suspend_fixture(self._current_item)
self.suspend_global_capture(in_)
def resume(self):
self.resume_global_capture()
self.resume_fixture(self._current_item)
def read_global_capture(self):
return self._global_capturing.readouterr()
@@ -160,15 +186,12 @@ class CaptureManager(object):
@contextlib.contextmanager
def global_and_fixture_disabled(self):
"""Context manager to temporarily disables global and current fixture capturing."""
# Need to undo local capsys-et-al if exists before disabling global capture
self.suspend_fixture(self._current_item)
self.suspend_global_capture(in_=False)
"""Context manager to temporarily disable global and current fixture capturing."""
self.suspend()
try:
yield
finally:
self.resume_global_capture()
self.resume_fixture(self._current_item)
self.resume()
@contextlib.contextmanager
def item_capture(self, when, item):
@@ -246,10 +269,11 @@ def _ensure_only_one_capture_fixture(request, name):
@pytest.fixture
def capsys(request):
"""Enable capturing of writes to ``sys.stdout`` and ``sys.stderr`` and make
captured output available via ``capsys.readouterr()`` method calls
which return a ``(out, err)`` namedtuple. ``out`` and ``err`` will be ``text``
objects.
"""Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``.
The captured output is made available via ``capsys.readouterr()`` method
calls, which return a ``(out, err)`` namedtuple.
``out`` and ``err`` will be ``text`` objects.
"""
_ensure_only_one_capture_fixture(request, "capsys")
with _install_capture_fixture_on_item(request, SysCapture) as fixture:
@@ -258,26 +282,28 @@ def capsys(request):
@pytest.fixture
def capsysbinary(request):
"""Enable capturing of writes to ``sys.stdout`` and ``sys.stderr`` and make
captured output available via ``capsys.readouterr()`` method calls
which return a ``(out, err)`` tuple. ``out`` and ``err`` will be ``bytes``
objects.
"""Enable bytes capturing of writes to ``sys.stdout`` and ``sys.stderr``.
The captured output is made available via ``capsysbinary.readouterr()``
method calls, which return a ``(out, err)`` namedtuple.
``out`` and ``err`` will be ``bytes`` objects.
"""
_ensure_only_one_capture_fixture(request, "capsysbinary")
# Currently, the implementation uses the python3 specific `.buffer`
# property of CaptureIO.
if sys.version_info < (3,):
raise request.raiseerror("capsysbinary is only supported on python 3")
raise request.raiseerror("capsysbinary is only supported on Python 3")
with _install_capture_fixture_on_item(request, SysCaptureBinary) as fixture:
yield fixture
@pytest.fixture
def capfd(request):
"""Enable capturing of writes to file descriptors ``1`` and ``2`` and make
captured output available via ``capfd.readouterr()`` method calls
which return a ``(out, err)`` tuple. ``out`` and ``err`` will be ``text``
objects.
"""Enable text capturing of writes to file descriptors ``1`` and ``2``.
The captured output is made available via ``capfd.readouterr()`` method
calls, which return a ``(out, err)`` namedtuple.
``out`` and ``err`` will be ``text`` objects.
"""
_ensure_only_one_capture_fixture(request, "capfd")
if not hasattr(os, "dup"):
@@ -290,10 +316,11 @@ def capfd(request):
@pytest.fixture
def capfdbinary(request):
"""Enable capturing of write to file descriptors 1 and 2 and make
captured output available via ``capfdbinary.readouterr`` method calls
which return a ``(out, err)`` tuple. ``out`` and ``err`` will be
``bytes`` objects.
"""Enable bytes capturing of writes to file descriptors ``1`` and ``2``.
The captured output is made available via ``capfd.readouterr()`` method
calls, which return a ``(out, err)`` namedtuple.
``out`` and ``err`` will be ``byte`` objects.
"""
_ensure_only_one_capture_fixture(request, "capfdbinary")
if not hasattr(os, "dup"):
@@ -315,9 +342,9 @@ def _install_capture_fixture_on_item(request, capture_class):
"""
request.node._capture_fixture = fixture = CaptureFixture(capture_class, request)
capmanager = request.config.pluginmanager.getplugin("capturemanager")
# need to active this fixture right away in case it is being used by another fixture (setup phase)
# if this fixture is being used only by a test function (call phase), then we wouldn't need this
# activation, but it doesn't hurt
# Need to active this fixture right away in case it is being used by another fixture (setup phase).
# If this fixture is being used only by a test function (call phase), then we wouldn't need this
# activation, but it doesn't hurt.
capmanager.activate_fixture(request.node)
yield fixture
fixture.close()
@@ -356,7 +383,7 @@ class CaptureFixture(object):
def readouterr(self):
"""Read and return the captured output so far, resetting the internal buffer.
:return: captured content as a namedtuple with ``out`` and ``err`` string attributes
:return: captured content as a namedtuple with ``out`` and ``err`` string attributes
"""
captured_out, captured_err = self._captured_out, self._captured_err
if self._capture is not None:
@@ -412,6 +439,10 @@ class EncodedFile(object):
def write(self, obj):
if isinstance(obj, six.text_type):
obj = obj.encode(self.encoding, "replace")
elif _PY3:
raise TypeError(
"write() argument must be str, not {}".format(type(obj).__name__)
)
self.buffer.write(obj)
def writelines(self, linelist):
@@ -441,6 +472,9 @@ class MultiCapture(object):
if err:
self.err = Capture(2)
def __repr__(self):
return "<MultiCapture out=%r err=%r in_=%r>" % (self.out, self.err, self.in_)
def start_capturing(self):
if self.in_:
self.in_.start()
@@ -534,7 +568,10 @@ class FDCaptureBinary(object):
self.tmpfile_fd = tmpfile.fileno()
def __repr__(self):
return "<FDCapture %s oldfd=%s>" % (self.targetfd, self.targetfd_save)
return "<FDCapture %s oldfd=%s>" % (
self.targetfd,
getattr(self, "targetfd_save", None),
)
def start(self):
""" Start capturing on targetfd using memorized tmpfile. """
@@ -585,7 +622,7 @@ class FDCapture(FDCaptureBinary):
EMPTY_BUFFER = str()
def snap(self):
res = FDCaptureBinary.snap(self)
res = super(FDCapture, self).snap()
enc = getattr(self.tmpfile, "encoding", None)
if enc and isinstance(res, bytes):
res = six.text_type(res, enc, "replace")
@@ -688,13 +725,11 @@ def _colorama_workaround():
first import of colorama while I/O capture is active, colorama will
fail in various ways.
"""
if not sys.platform.startswith("win32"):
return
try:
import colorama # noqa
except ImportError:
pass
if sys.platform.startswith("win32"):
try:
import colorama # noqa: F401
except ImportError:
pass
def _readline_workaround():
@@ -715,13 +750,11 @@ def _readline_workaround():
See https://github.com/pytest-dev/pytest/pull/1281
"""
if not sys.platform.startswith("win32"):
return
try:
import readline # noqa
except ImportError:
pass
if sys.platform.startswith("win32"):
try:
import readline # noqa: F401
except ImportError:
pass
def _py36_windowsconsoleio_workaround(stream):

View File

@@ -14,7 +14,6 @@ import warnings
import py
import six
from pkg_resources import parse_version
from pluggy import HookimplMarker
from pluggy import HookspecMarker
from pluggy import PluginManager
@@ -141,6 +140,7 @@ default_plugins = (
"stepwise",
"warnings",
"logging",
"reports",
)
@@ -148,10 +148,15 @@ builtin_plugins = set(default_plugins)
builtin_plugins.add("pytester")
def get_config():
def get_config(args=None):
# subsequent calls to main will create a fresh instance
pluginmanager = PytestPluginManager()
config = Config(pluginmanager)
if args is not None:
# Handle any "-p no:plugin" args.
pluginmanager.consider_preparse(args)
for spec in default_plugins:
pluginmanager.import_plugin(spec)
return config
@@ -179,7 +184,7 @@ def _prepareconfig(args=None, plugins=None):
msg = "`args` parameter expected to be a list or tuple of strings, got: {!r} (type: {})"
raise TypeError(msg.format(args, type(args)))
config = get_config()
config = get_config(args)
pluginmanager = config.pluginmanager
try:
if plugins:
@@ -477,7 +482,10 @@ class PytestPluginManager(PluginManager):
i += 1
if isinstance(opt, six.string_types):
if opt == "-p":
parg = args[i]
try:
parg = args[i]
except IndexError:
return
i += 1
elif opt.startswith("-p"):
parg = opt[2:]
@@ -497,7 +505,15 @@ class PytestPluginManager(PluginManager):
if not name.startswith("pytest_"):
self.set_blocked("pytest_" + name)
else:
self.import_plugin(arg)
name = arg
# Unblock the plugin. None indicates that it has been blocked.
# There is no interface with pluggy for this.
if self._name2plugin.get(name, -1) is None:
del self._name2plugin[name]
if not name.startswith("pytest_"):
if self._name2plugin.get("pytest_" + name, -1) is None:
del self._name2plugin["pytest_" + name]
self.import_plugin(arg, consider_entry_points=True)
def consider_conftest(self, conftestmodule):
self.register(conftestmodule, name=conftestmodule.__file__)
@@ -513,7 +529,11 @@ class PytestPluginManager(PluginManager):
for import_spec in plugins:
self.import_plugin(import_spec)
def import_plugin(self, modname):
def import_plugin(self, modname, consider_entry_points=False):
"""
Imports a plugin with ``modname``. If ``consider_entry_points`` is True, entry point
names are also considered to find a plugin.
"""
# most often modname refers to builtin modules, e.g. "pytester",
# "terminal" or "capture". Those plugins are registered under their
# basename for historic purposes but must be imported with the
@@ -524,22 +544,26 @@ class PytestPluginManager(PluginManager):
modname = str(modname)
if self.is_blocked(modname) or self.get_plugin(modname) is not None:
return
if modname in builtin_plugins:
importspec = "_pytest." + modname
else:
importspec = modname
importspec = "_pytest." + modname if modname in builtin_plugins else modname
self.rewrite_hook.mark_rewrite(importspec)
if consider_entry_points:
loaded = self.load_setuptools_entrypoints("pytest11", name=modname)
if loaded:
return
try:
__import__(importspec)
except ImportError as e:
new_exc_type = ImportError
new_exc_message = 'Error importing plugin "%s": %s' % (
modname,
safe_str(e.args[0]),
)
new_exc = new_exc_type(new_exc_message)
new_exc = ImportError(new_exc_message)
tb = sys.exc_info()[2]
six.reraise(new_exc_type, new_exc, sys.exc_info()[2])
six.reraise(ImportError, new_exc, tb)
except Skipped as e:
from _pytest.warnings import _issue_warning_captured
@@ -651,8 +675,27 @@ class Config(object):
return self.pluginmanager.get_plugin("terminalreporter")._tw
def pytest_cmdline_parse(self, pluginmanager, args):
# REF1 assert self == pluginmanager.config, (self, pluginmanager.config)
self.parse(args)
try:
self.parse(args)
except UsageError:
# Handle --version and --help here in a minimal fashion.
# This gets done via helpconfig normally, but its
# pytest_cmdline_main is not called in case of errors.
if getattr(self.option, "version", False) or "--version" in args:
from _pytest.helpconfig import showversion
showversion(self)
elif (
getattr(self.option, "help", False) or "--help" in args or "-h" in args
):
self._parser._getparser().print_help()
sys.stdout.write(
"\nNOTE: displaying only minimal help due to UsageError.\n\n"
)
raise
return self
def notify_exception(self, excinfo, option=None):
@@ -679,7 +722,7 @@ class Config(object):
@classmethod
def fromdictargs(cls, option_dict, args):
""" constructor useable for subprocesses. """
config = get_config()
config = get_config(args)
config.option.__dict__.update(option_dict)
config.parse(args, addopts=False)
for x in config.option.plugins:
@@ -723,7 +766,7 @@ class Config(object):
by the importhook.
"""
ns, unknown_args = self._parser.parse_known_and_unknown_args(args)
mode = ns.assertmode
mode = getattr(ns, "assertmode", "plain")
if mode == "rewrite":
try:
hook = _pytest.assertion.install_importhook(self)
@@ -763,21 +806,32 @@ class Config(object):
for name in _iter_rewritable_modules(package_files):
hook.mark_rewrite(name)
def _validate_args(self, args):
def _validate_args(self, args, via):
"""Validate known args."""
self._parser.parse_known_and_unknown_args(
args, namespace=copy.copy(self.option)
)
self._parser._config_source_hint = via
try:
self._parser.parse_known_and_unknown_args(
args, namespace=copy.copy(self.option)
)
finally:
del self._parser._config_source_hint
return args
def _preparse(self, args, addopts=True):
if addopts:
env_addopts = os.environ.get("PYTEST_ADDOPTS", "")
if len(env_addopts):
args[:] = self._validate_args(shlex.split(env_addopts)) + args
args[:] = (
self._validate_args(shlex.split(env_addopts), "via PYTEST_ADDOPTS")
+ args
)
self._initini(args)
if addopts:
args[:] = self._validate_args(self.getini("addopts")) + args
args[:] = (
self._validate_args(self.getini("addopts"), "via addopts config") + args
)
self._checkversion()
self._consider_importhook(args)
self.pluginmanager.consider_preparse(args)
@@ -815,6 +869,7 @@ class Config(object):
def _checkversion(self):
import pytest
from pkg_resources import parse_version
minver = self.inicfg.get("minversion", None)
if minver:

View File

@@ -1,12 +1,10 @@
import argparse
import sys as _sys
import warnings
from gettext import gettext as _
import py
import six
from ..main import EXIT_USAGEERROR
from _pytest.config.exceptions import UsageError
FILE_OR_DIR = "file_or_dir"
@@ -337,14 +335,13 @@ class MyOptionParser(argparse.ArgumentParser):
self.extra_info = extra_info
def error(self, message):
"""error(message: string)
"""Transform argparse error message into UsageError."""
msg = "%s: error: %s" % (self.prog, message)
Prints a usage message incorporating the message to stderr and
exits.
Overrides the method in parent class to change exit code"""
self.print_usage(_sys.stderr)
args = {"prog": self.prog, "message": message}
self.exit(EXIT_USAGEERROR, _("%(prog)s: error: %(message)s\n") % args)
if hasattr(self._parser, "_config_source_hint"):
msg = "%s (%s)" % (msg, self._parser._config_source_hint)
raise UsageError(self.format_usage() + msg)
def parse_args(self, args=None, namespace=None):
"""allow splitting of positional arguments"""

View File

@@ -33,7 +33,12 @@ def getcfg(args, config=None):
p = base.join(inibasename)
if exists(p):
iniconfig = py.iniconfig.IniConfig(p)
if "pytest" in iniconfig.sections:
if (
inibasename == "setup.cfg"
and "tool:pytest" in iniconfig.sections
):
return base, p, iniconfig["tool:pytest"]
elif "pytest" in iniconfig.sections:
if inibasename == "setup.cfg" and config is not None:
fail(
@@ -41,11 +46,6 @@ def getcfg(args, config=None):
pytrace=False,
)
return base, p, iniconfig["pytest"]
if (
inibasename == "setup.cfg"
and "tool:pytest" in iniconfig.sections
):
return base, p, iniconfig["tool:pytest"]
elif inibasename == "pytest.ini":
# allowed to be empty
return base, p, {}

View File

@@ -3,6 +3,7 @@ from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import argparse
import pdb
import sys
from doctest import UnexpectedException
@@ -11,6 +12,31 @@ from _pytest import outcomes
from _pytest.config import hookimpl
def _validate_usepdb_cls(value):
try:
modname, classname = value.split(":")
except ValueError:
raise argparse.ArgumentTypeError(
"{!r} is not in the format 'modname:classname'".format(value)
)
try:
__import__(modname)
mod = sys.modules[modname]
# Handle --pdbcls=pdb:pdb.Pdb (useful e.g. with pdbpp).
parts = classname.split(".")
pdb_cls = getattr(mod, parts[0])
for part in parts[1:]:
pdb_cls = getattr(pdb_cls, part)
return pdb_cls
except Exception as exc:
raise argparse.ArgumentTypeError(
"could not get pdb class for {!r}: {}".format(value, exc)
)
def pytest_addoption(parser):
group = parser.getgroup("general")
group._addoption(
@@ -23,6 +49,7 @@ def pytest_addoption(parser):
"--pdbcls",
dest="usepdb_cls",
metavar="modulename:classname",
type=_validate_usepdb_cls,
help="start a custom interactive Python debugger on errors. "
"For example: --pdbcls=IPython.terminal.debugger:TerminalPdb",
)
@@ -35,11 +62,8 @@ def pytest_addoption(parser):
def pytest_configure(config):
if config.getvalue("usepdb_cls"):
modname, classname = config.getvalue("usepdb_cls").split(":")
__import__(modname)
pdb_cls = getattr(sys.modules[modname], classname)
else:
pdb_cls = config.getvalue("usepdb_cls")
if not pdb_cls:
pdb_cls = pdb.Pdb
if config.getvalue("trace"):
@@ -77,6 +101,12 @@ class pytestPDB(object):
_saved = []
_recursive_debug = 0
@classmethod
def _is_capturing(cls, capman):
if capman:
return capman.is_capturing()
return False
@classmethod
def _init_pdb(cls, *args, **kwargs):
""" Initialize PDB debugging, dropping any IO capturing. """
@@ -85,7 +115,7 @@ class pytestPDB(object):
if cls._pluginmanager is not None:
capman = cls._pluginmanager.getplugin("capturemanager")
if capman:
capman.suspend_global_capture(in_=True)
capman.suspend(in_=True)
tw = _pytest.config.create_terminal_writer(cls._config)
tw.line()
if cls._recursive_debug == 0:
@@ -93,10 +123,19 @@ class pytestPDB(object):
header = kwargs.pop("header", None)
if header is not None:
tw.sep(">", header)
elif capman and capman.is_globally_capturing():
tw.sep(">", "PDB set_trace (IO-capturing turned off)")
else:
tw.sep(">", "PDB set_trace")
capturing = cls._is_capturing(capman)
if capturing:
if capturing == "global":
tw.sep(">", "PDB set_trace (IO-capturing turned off)")
else:
tw.sep(
">",
"PDB set_trace (IO-capturing turned off for %s)"
% capturing,
)
else:
tw.sep(">", "PDB set_trace")
class _PdbWrapper(cls._pdb_cls, object):
_pytest_capman = capman
@@ -110,15 +149,24 @@ class pytestPDB(object):
def do_continue(self, arg):
ret = super(_PdbWrapper, self).do_continue(arg)
if self._pytest_capman:
if cls._recursive_debug == 0:
tw = _pytest.config.create_terminal_writer(cls._config)
tw.line()
if cls._recursive_debug == 0:
if self._pytest_capman.is_globally_capturing():
capman = self._pytest_capman
capturing = pytestPDB._is_capturing(capman)
if capturing:
if capturing == "global":
tw.sep(">", "PDB continue (IO-capturing resumed)")
else:
tw.sep(">", "PDB continue")
self._pytest_capman.resume_global_capture()
tw.sep(
">",
"PDB continue (IO-capturing resumed for %s)"
% capturing,
)
capman.resume()
else:
tw.sep(">", "PDB continue")
cls._pluginmanager.hook.pytest_leave_pdb(
config=cls._config, pdb=self
)
@@ -128,8 +176,15 @@ class pytestPDB(object):
do_c = do_cont = do_continue
def set_quit(self):
"""Raise Exit outcome when quit command is used in pdb.
This is a bit of a hack - it would be better if BdbQuit
could be handled, but this would require to wrap the
whole pytest run, and adjust the report etc.
"""
super(_PdbWrapper, self).set_quit()
outcomes.exit("Quitting debugger")
if cls._recursive_debug == 0:
outcomes.exit("Quitting debugger")
def setup(self, f, tb):
"""Suspend on setup().
@@ -185,17 +240,12 @@ def _test_pytest_function(pyfuncitem):
_pdb = pytestPDB._init_pdb()
testfunction = pyfuncitem.obj
pyfuncitem.obj = _pdb.runcall
if pyfuncitem._isyieldedfunction():
arg_list = list(pyfuncitem._args)
arg_list.insert(0, testfunction)
pyfuncitem._args = tuple(arg_list)
else:
if "func" in pyfuncitem._fixtureinfo.argnames:
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)
if "func" in pyfuncitem._fixtureinfo.argnames: # noqa
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)
def _enter_pdb(node, excinfo, rep):
@@ -244,9 +294,9 @@ def _find_last_non_hidden_frame(stack):
def post_mortem(t):
class Pdb(pytestPDB._pdb_cls):
class Pdb(pytestPDB._pdb_cls, object):
def get_stack(self, f, t):
stack, i = pdb.Pdb.get_stack(self, f, t)
stack, i = super(Pdb, self).get_stack(f, t)
if f is None:
i = _find_last_non_hidden_frame(stack)
return stack, i

View File

@@ -14,7 +14,7 @@ from __future__ import print_function
from _pytest.warning_types import PytestDeprecationWarning
from _pytest.warning_types import RemovedInPytest4Warning
from _pytest.warning_types import UnformattedWarning
YIELD_TESTS = "yield tests were removed in pytest 4.0 - {name} will be ignored"
@@ -87,3 +87,9 @@ PYTEST_LOGWARNING = PytestDeprecationWarning(
"pytest_logwarning is deprecated, no longer being called, and will be removed soon\n"
"please use pytest_warning_captured instead"
)
PYTEST_WARNS_UNKNOWN_KWARGS = UnformattedWarning(
PytestDeprecationWarning,
"pytest.warns() got unexpected keyword arguments: {args!r}.\n"
"This will be an error in future versions.",
)

View File

@@ -15,6 +15,7 @@ from _pytest._code.code import ReprFileLocation
from _pytest._code.code import TerminalRepr
from _pytest.compat import safe_getattr
from _pytest.fixtures import FixtureRequest
from _pytest.outcomes import Skipped
DOCTEST_REPORT_CHOICE_NONE = "none"
DOCTEST_REPORT_CHOICE_CDIFF = "cdiff"
@@ -153,6 +154,8 @@ def _init_runner_class():
raise failure
def report_unexpected_exception(self, out, test, example, exc_info):
if isinstance(exc_info[1], Skipped):
raise exc_info[1]
failure = doctest.UnexpectedException(test, example, exc_info)
if self.continue_on_failure:
out.append(failure)

View File

@@ -4,6 +4,7 @@ from __future__ import print_function
import functools
import inspect
import itertools
import sys
import warnings
from collections import defaultdict
@@ -13,7 +14,6 @@ from collections import OrderedDict
import attr
import py
import six
from more_itertools import flatten
import _pytest
from _pytest import nodes
@@ -585,11 +585,13 @@ class FixtureRequest(FuncargnamesCompatAttr):
# call the fixture function
fixturedef.execute(request=subrequest)
finally:
# if fixture function failed it might have registered finalizers
self.session._setupstate.addfinalizer(
functools.partial(fixturedef.finish, request=subrequest),
subrequest.node,
)
self._schedule_finalizers(fixturedef, subrequest)
def _schedule_finalizers(self, fixturedef, subrequest):
# if fixture function failed it might have registered finalizers
self.session._setupstate.addfinalizer(
functools.partial(fixturedef.finish, request=subrequest), subrequest.node
)
def _check_scope(self, argname, invoking_scope, requested_scope):
if argname == "request":
@@ -612,7 +614,7 @@ class FixtureRequest(FuncargnamesCompatAttr):
fs, lineno = getfslineno(factory)
p = self._pyfuncitem.session.fspath.bestrelpath(fs)
args = _format_args(factory)
lines.append("%s:%d: def %s%s" % (p, lineno, factory.__name__, args))
lines.append("%s:%d: def %s%s" % (p, lineno + 1, factory.__name__, args))
return lines
def _getscopeitem(self, scope):
@@ -659,6 +661,16 @@ class SubRequest(FixtureRequest):
def addfinalizer(self, finalizer):
self._fixturedef.addfinalizer(finalizer)
def _schedule_finalizers(self, fixturedef, subrequest):
# if the executing fixturedef was not explicitly requested in the argument list (via
# getfixturevalue inside the fixture call) then ensure this fixture def will be finished
# first
if fixturedef.argname not in self.funcargnames:
fixturedef.addfinalizer(
functools.partial(self._fixturedef.finish, request=self)
)
super(SubRequest, self)._schedule_finalizers(fixturedef, subrequest)
scopes = "session package module class function".split()
scopenum_function = scopes.index("function")
@@ -1053,7 +1065,7 @@ def pytestconfig(request):
Example::
def test_foo(pytestconfig):
if pytestconfig.getoption("verbose"):
if pytestconfig.getoption("verbose") > 0:
...
"""
@@ -1109,7 +1121,7 @@ class FixtureManager(object):
argnames = getfuncargnames(func, cls=cls)
else:
argnames = ()
usefixtures = flatten(
usefixtures = itertools.chain.from_iterable(
mark.args for mark in node.iter_markers(name="usefixtures")
)
initialnames = tuple(usefixtures) + argnames

View File

@@ -60,7 +60,7 @@ def pytest_addoption(parser):
dest="plugins",
default=[],
metavar="name",
help="early-load given plugin (multi-allowed). "
help="early-load given plugin module name or entry point (multi-allowed). "
"To avoid loading of plugins, use the `no:` prefix, e.g. "
"`no:doctest`.",
)
@@ -118,16 +118,20 @@ def pytest_cmdline_parse():
config.add_cleanup(unset_tracing)
def showversion(config):
p = py.path.local(pytest.__file__)
sys.stderr.write(
"This is pytest version %s, imported from %s\n" % (pytest.__version__, p)
)
plugininfo = getpluginversioninfo(config)
if plugininfo:
for line in plugininfo:
sys.stderr.write(line + "\n")
def pytest_cmdline_main(config):
if config.option.version:
p = py.path.local(pytest.__file__)
sys.stderr.write(
"This is pytest version %s, imported from %s\n" % (pytest.__version__, p)
)
plugininfo = getpluginversioninfo(config)
if plugininfo:
for line in plugininfo:
sys.stderr.write(line + "\n")
showversion(config)
return 0
elif config.option.help:
config._do_configure()

View File

@@ -3,7 +3,6 @@ from pluggy import HookspecMarker
from _pytest.deprecated import PYTEST_LOGWARNING
hookspec = HookspecMarker("pytest")
# -------------------------------------------------------------------------
@@ -100,7 +99,8 @@ def pytest_cmdline_parse(pluginmanager, args):
Stops at first non-None result, see :ref:`firstresult`
.. note::
This hook will not be called for ``conftest.py`` files, only for setuptools plugins.
This hook will only be called for plugin classes passed to the ``plugins`` arg when using `pytest.main`_ to
perform an in-process test run.
:param _pytest.config.PytestPluginManager pluginmanager: pytest plugin manager
:param list[str] args: list of arguments passed on the command line
@@ -376,6 +376,41 @@ def pytest_runtest_logreport(report):
the respective phase of executing a test. """
@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.
"""
@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().
"""
# -------------------------------------------------------------------------
# Fixture related hooks
# -------------------------------------------------------------------------

View File

@@ -13,7 +13,7 @@ import six
import pytest
from _pytest.compat import dummy_context_manager
from _pytest.config import create_terminal_writer
from _pytest.pathlib import Path
DEFAULT_LOG_FORMAT = "%(filename)-25s %(lineno)4d %(levelname)-8s %(message)s"
DEFAULT_LOG_DATE_FORMAT = "%H:%M:%S"
@@ -389,7 +389,7 @@ class LoggingPlugin(object):
self._config = config
# enable verbose output automatically if live logging is enabled
if self._log_cli_enabled() and not config.getoption("verbose"):
if self._log_cli_enabled() and config.getoption("verbose") < 1:
config.option.verbose = 1
self.print_logs = get_option_ini(config, "log_print")
@@ -399,22 +399,21 @@ class LoggingPlugin(object):
)
self.log_level = get_actual_log_level(config, "log_level")
self.log_file_level = get_actual_log_level(config, "log_file_level")
self.log_file_format = get_option_ini(config, "log_file_format", "log_format")
self.log_file_date_format = get_option_ini(
config, "log_file_date_format", "log_date_format"
)
self.log_file_formatter = logging.Formatter(
self.log_file_format, datefmt=self.log_file_date_format
)
log_file = get_option_ini(config, "log_file")
if log_file:
self.log_file_level = get_actual_log_level(config, "log_file_level")
log_file_format = get_option_ini(config, "log_file_format", "log_format")
log_file_date_format = get_option_ini(
config, "log_file_date_format", "log_date_format"
)
# Each pytest runtests session will write to a clean logfile
self.log_file_handler = logging.FileHandler(
log_file, mode="w", encoding="UTF-8"
)
log_file_formatter = logging.Formatter(
log_file_format, datefmt=log_file_date_format
)
self.log_file_handler.setFormatter(log_file_formatter)
self.log_file_handler.setFormatter(self.log_file_formatter)
else:
self.log_file_handler = None
@@ -461,6 +460,27 @@ class LoggingPlugin(object):
log_cli_handler, formatter=log_cli_formatter, level=log_cli_level
)
def set_log_path(self, fname):
"""Public method, which can set filename parameter for
Logging.FileHandler(). Also creates parent directory if
it does not exist.
.. warning::
Please considered as an experimental API.
"""
fname = Path(fname)
if not fname.is_absolute():
fname = Path(self._config.rootdir, fname)
if not fname.parent.exists():
fname.parent.mkdir(exist_ok=True, parents=True)
self.log_file_handler = logging.FileHandler(
str(fname), mode="w", encoding="UTF-8"
)
self.log_file_handler.setFormatter(self.log_file_formatter)
def _log_cli_enabled(self):
"""Return True if log_cli should be considered enabled, either explicitly
or because --log-cli-level was given in the command-line.
@@ -483,6 +503,15 @@ class LoggingPlugin(object):
@contextmanager
def _runtest_for(self, item, when):
with self._runtest_for_main(item, when):
if self.log_file_handler is not None:
with catching_logs(self.log_file_handler, level=self.log_file_level):
yield
else:
yield
@contextmanager
def _runtest_for_main(self, item, when):
"""Implements the internals of pytest_runtest_xxx() hook."""
with catching_logs(
LogCaptureHandler(), formatter=self.formatter, level=self.log_level
@@ -537,14 +566,26 @@ class LoggingPlugin(object):
with self._runtest_for(None, "finish"):
yield
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_logreport(self):
with self._runtest_for(None, "logreport"):
yield
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
def pytest_sessionfinish(self):
with self.live_logs_context():
if self.log_cli_handler:
self.log_cli_handler.set_when("sessionfinish")
if self.log_file_handler is not None:
with catching_logs(self.log_file_handler, level=self.log_file_level):
yield
try:
with catching_logs(
self.log_file_handler, level=self.log_file_level
):
yield
finally:
# Close the FileHandler explicitly.
# (logging.shutdown might have lost the weakref?!)
self.log_file_handler.close()
else:
yield

View File

@@ -4,6 +4,7 @@ from __future__ import division
from __future__ import print_function
import contextlib
import fnmatch
import functools
import os
import pkgutil
@@ -23,7 +24,6 @@ from _pytest.deprecated import PYTEST_CONFIG_GLOBAL
from _pytest.outcomes import exit
from _pytest.runner import collect_one_node
# exitcodes for the command line
EXIT_OK = 0
EXIT_TESTSFAILED = 1
@@ -117,6 +117,12 @@ def pytest_addoption(parser):
metavar="path",
help="ignore path during collection (multi-allowed).",
)
group.addoption(
"--ignore-glob",
action="append",
metavar="path",
help="ignore path pattern during collection (multi-allowed).",
)
group.addoption(
"--deselect",
action="append",
@@ -219,7 +225,7 @@ def wrap_session(config, doit):
config.notify_exception(excinfo, config.option)
session.exitstatus = EXIT_INTERNALERROR
if excinfo.errisinstance(SystemExit):
sys.stderr.write("mainloop: caught Spurious SystemExit!\n")
sys.stderr.write("mainloop: caught unexpected SystemExit!\n")
finally:
excinfo = None # Explicitly break reference cycle.
@@ -296,6 +302,20 @@ def pytest_ignore_collect(path, config):
if py.path.local(path) in ignore_paths:
return True
ignore_globs = config._getconftest_pathlist(
"collect_ignore_glob", path=path.dirpath()
)
ignore_globs = ignore_globs or []
excludeglobopt = config.getoption("ignore_glob")
if excludeglobopt:
ignore_globs.extend([py.path.local(x) for x in excludeglobopt])
if any(
fnmatch.fnmatch(six.text_type(path), six.text_type(glob))
for glob in ignore_globs
):
return True
allow_in_venv = config.getoption("collect_in_virtualenv")
if not allow_in_venv and _in_venv(path):
return True
@@ -421,6 +441,15 @@ class Session(nodes.FSCollector):
self.config.pluginmanager.register(self, name="session")
def __repr__(self):
return "<%s %s exitstatus=%r testsfailed=%d testscollected=%d>" % (
self.__class__.__name__,
self.name,
getattr(self, "exitstatus", "<UNSET>"),
self.testsfailed,
self.testscollected,
)
def _node_location_to_relpath(self, node_path):
# bestrelpath is a quite slow function
return self._bestrelpathcache[node_path]
@@ -528,7 +557,7 @@ class Session(nodes.FSCollector):
# Start with a Session root, and delve to argpath item (dir or file)
# and stack all Packages found on the way.
# No point in finding packages when collecting doctests
if not self.config.option.doctestmodules:
if not self.config.getoption("doctestmodules", False):
pm = self.config.pluginmanager
for parent in reversed(argpath.parts()):
if pm._confcutdir and pm._confcutdir.relto(parent):
@@ -597,7 +626,12 @@ class Session(nodes.FSCollector):
yield y
def _collectfile(self, path, handle_dupes=True):
assert path.isfile()
assert path.isfile(), "%r is not a file (isdir=%r, exists=%r, islink=%r)" % (
path,
path.isdir(),
path.exists(),
path.islink(),
)
ihook = self.gethookproxy(path)
if not self.isinitpath(path):
if ihook.pytest_ignore_collect(path=path, config=self.config):

View File

@@ -52,6 +52,7 @@ def pytest_addoption(parser):
"other' matches all test functions and classes whose name "
"contains 'test_method' or 'test_other', while -k 'not test_method' "
"matches those that don't contain 'test_method' in their names. "
"-k 'not test_method and not test_other' will eliminate the matches. "
"Additionally keywords are matched to classes and functions "
"containing extra names in their 'extra_keyword_matches' set, "
"as well as functions which have names assigned directly to them.",

View File

@@ -12,7 +12,6 @@ from ..compat import MappingMixin
from ..compat import NOTSET
from _pytest.outcomes import fail
EMPTY_PARAMETERSET_OPTION = "empty_parameter_set_mark"
@@ -45,7 +44,7 @@ def get_empty_parameterset_mark(config, argnames, func):
f_name = func.__name__
_, lineno = getfslineno(func)
raise Collector.CollectError(
"Empty parameter set in '%s' at line %d" % (f_name, lineno)
"Empty parameter set in '%s' at line %d" % (f_name, lineno + 1)
)
else:
raise LookupError(requested_mark)

View File

@@ -262,10 +262,15 @@ class MonkeyPatch(object):
def syspath_prepend(self, path):
""" Prepend ``path`` to ``sys.path`` list of import locations. """
from pkg_resources import fixup_namespace_packages
if self._savesyspath is None:
self._savesyspath = sys.path[:]
sys.path.insert(0, str(path))
# https://github.com/pypa/setuptools/blob/d8b901bc/docs/pkg_resources.txt#L162-L171
fixup_namespace_packages(str(path))
def chdir(self, path):
""" Change the current working directory to the specified path.
Path can be a string or a py.path.local object.

View File

@@ -113,11 +113,12 @@ class Node(object):
:raise ValueError: if ``warning`` instance is not a subclass of PytestWarning.
Example usage::
Example usage:
.. code-block:: python
node.warn(PytestWarning("some message"))
"""
from _pytest.warning_types import PytestWarning
@@ -324,7 +325,14 @@ class Collector(Node):
if excinfo.errisinstance(self.CollectError):
exc = excinfo.value
return str(exc.args[0])
return self._repr_failure_py(excinfo, style="short")
# Respect explicit tbstyle option, but default to "short"
# (None._repr_failure_py defaults to "long" without "fulltrace" option).
tbstyle = self.config.getoption("tbstyle")
if tbstyle == "auto":
tbstyle = "short"
return self._repr_failure_py(excinfo, style=tbstyle)
def _prunetraceback(self, excinfo):
if hasattr(self, "fspath"):

View File

@@ -80,7 +80,8 @@ def skip(msg="", **kwargs):
Skip an executing test with the given message.
This function should be called only during testing (setup, call or teardown) or
during collection by using the ``allow_module_level`` flag.
during collection by using the ``allow_module_level`` flag. This function can
be called in doctests as well.
:kwarg bool allow_module_level: allows this function to be called at
module level, skipping the rest of the module. Default to False.
@@ -89,6 +90,9 @@ def skip(msg="", **kwargs):
It is better to use the :ref:`pytest.mark.skipif ref` marker when possible to declare a test to be
skipped under certain conditions like mismatching platforms or
dependencies.
Similarly, use the ``# doctest: +SKIP`` directive (see `doctest.SKIP
<https://docs.python.org/3/library/doctest.html#doctest.SKIP>`_)
to skip a doctest statically.
"""
__tracebackhide__ = True
allow_module_level = kwargs.pop("allow_module_level", False)

View File

@@ -19,7 +19,6 @@ from six.moves import map
from .compat import PY36
if PY36:
from pathlib import Path, PurePath
else:

View File

@@ -4,7 +4,6 @@ from __future__ import division
from __future__ import print_function
import codecs
import distutils.spawn
import gc
import os
import platform
@@ -26,9 +25,11 @@ from _pytest.assertion.rewrite import AssertionRewritingHook
from _pytest.capture import MultiCapture
from _pytest.capture import SysCapture
from _pytest.compat import safe_str
from _pytest.compat import Sequence
from _pytest.main import EXIT_INTERRUPTED
from _pytest.main import EXIT_OK
from _pytest.main import Session
from _pytest.monkeypatch import MonkeyPatch
from _pytest.pathlib import Path
IGNORE_PAM = [ # filenames added when obtaining details about the current user
@@ -151,47 +152,6 @@ winpymap = {
}
def getexecutable(name, cache={}):
try:
return cache[name]
except KeyError:
executable = distutils.spawn.find_executable(name)
if executable:
import subprocess
popen = subprocess.Popen(
[str(executable), "--version"],
universal_newlines=True,
stderr=subprocess.PIPE,
)
out, err = popen.communicate()
if name == "jython":
if not err or "2.5" not in err:
executable = None
if "2.5.2" in err:
executable = None # http://bugs.jython.org/issue1790
elif popen.returncode != 0:
# handle pyenv's 127
executable = None
cache[name] = executable
return executable
@pytest.fixture(params=["python2.7", "python3.4", "pypy", "pypy3"])
def anypython(request):
name = request.param
executable = getexecutable(name)
if executable is None:
if sys.platform == "win32":
executable = winpymap.get(name, None)
if executable:
executable = py.path.local(executable)
if executable.check():
return executable
pytest.skip("no suitable %s found" % (name,))
return executable
# used at least by pytest-xdist plugin
@@ -375,6 +335,15 @@ def testdir(request, tmpdir_factory):
return Testdir(request, tmpdir_factory)
@pytest.fixture
def _config_for_test():
from _pytest.config import get_config
config = get_config()
yield config
config._ensure_unconfigure() # cleanup, e.g. capman closing tmpfiles.
rex_outcome = re.compile(r"(\d+) ([\w-]+)")
@@ -402,6 +371,12 @@ class RunResult(object):
self.stderr = LineMatcher(errlines)
self.duration = duration
def __repr__(self):
return (
"<RunResult ret=%r 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):
"""Return a dictionary of outcomestring->num from parsing the terminal
output that the test process produced.
@@ -503,6 +478,7 @@ class Testdir(object):
self.test_tmproot = tmpdir_factory.mktemp("tmp-" + name, numbered=True)
os.environ["PYTEST_DEBUG_TEMPROOT"] = str(self.test_tmproot)
os.environ.pop("TOX_ENV_DIR", None) # Ensure that it is not used for caching.
os.environ.pop("PYTEST_ADDOPTS", None) # Do not use outer options.
self.plugins = []
self._cwd_snapshot = CwdSnapshot()
self._sys_path_snapshot = SysPathsSnapshot()
@@ -626,11 +602,16 @@ class Testdir(object):
This is undone automatically when this object dies at the end of each
test.
"""
from pkg_resources import fixup_namespace_packages
if path is None:
path = self.tmpdir
sys.path.insert(0, str(path))
dirname = str(path)
sys.path.insert(0, dirname)
fixup_namespace_packages(dirname)
# a call to syspathinsert() usually means that the caller wants to
# import some dynamically created files, thus with python3 we
# invalidate its import caches
@@ -639,12 +620,10 @@ class Testdir(object):
def _possibly_invalidate_import_caches(self):
# invalidate caches if we can (py33 and above)
try:
import importlib
from importlib import invalidate_caches
except ImportError:
pass
else:
if hasattr(importlib, "invalidate_caches"):
importlib.invalidate_caches()
return
invalidate_caches()
def mkdir(self, name):
"""Create a new (sub)directory."""
@@ -687,7 +666,7 @@ class Testdir(object):
else:
raise LookupError(
"{} cant be found as module or package in {}".format(
func_name, example_dir.bestrelpath(self.request.confg.rootdir)
func_name, example_dir.bestrelpath(self.request.config.rootdir)
)
)
else:
@@ -820,6 +799,12 @@ class Testdir(object):
"""
finalizers = []
try:
# Do not load user config.
monkeypatch = MonkeyPatch()
monkeypatch.setenv("HOME", str(self.tmpdir))
monkeypatch.setenv("USERPROFILE", str(self.tmpdir))
finalizers.append(monkeypatch.undo)
# When running pytest inline any plugins active in the main test
# process are already imported. So this disables the warning which
# will trigger to say they can no longer be rewritten, which is
@@ -1050,6 +1035,9 @@ class Testdir(object):
env["PYTHONPATH"] = os.pathsep.join(
filter(None, [os.getcwd(), env.get("PYTHONPATH", "")])
)
# Do not load user config.
env["HOME"] = str(self.tmpdir)
env["USERPROFILE"] = env["HOME"]
kw["env"] = env
popen = subprocess.Popen(
@@ -1360,6 +1348,7 @@ class LineMatcher(object):
will be logged to stdout when a match occurs
"""
assert isinstance(lines2, Sequence)
lines2 = self._getlines(lines2)
lines1 = self.lines[:]
nextline = None

View File

@@ -43,6 +43,7 @@ from _pytest.mark import MARK_GEN
from _pytest.mark.structures import get_unpacked_marks
from _pytest.mark.structures import normalize_mark_list
from _pytest.outcomes import fail
from _pytest.outcomes import skip
from _pytest.pathlib import parts
from _pytest.warning_types import PytestWarning
@@ -101,6 +102,13 @@ def pytest_addoption(parser):
default=["test"],
help="prefixes or glob names for Python test function and method discovery",
)
parser.addini(
"disable_test_id_escaping_and_forfeit_all_rights_to_community_support",
type="bool",
default=False,
help="disable string escape non-ascii characters, might cause unwanted "
"side effects(use at your own risk)",
)
group.addoption(
"--import-mode",
@@ -156,14 +164,18 @@ def pytest_configure(config):
@hookimpl(trylast=True)
def pytest_pyfunc_call(pyfuncitem):
testfunction = pyfuncitem.obj
if pyfuncitem._isyieldedfunction():
testfunction(*pyfuncitem._args)
else:
funcargs = pyfuncitem.funcargs
testargs = {}
for arg in pyfuncitem._fixtureinfo.argnames:
testargs[arg] = funcargs[arg]
testfunction(**testargs)
iscoroutinefunction = getattr(inspect, "iscoroutinefunction", None)
if iscoroutinefunction is not None and iscoroutinefunction(testfunction):
msg = "Coroutine functions are not natively supported and have been skipped.\n"
msg += "You need to install a suitable plugin for your async framework, for example:\n"
msg += " - pytest-asyncio\n"
msg += " - pytest-trio\n"
msg += " - pytest-tornasync"
warnings.warn(PytestWarning(msg.format(pyfuncitem.nodeid)))
skip(msg="coroutine function and no async plugin installed (see warnings)")
funcargs = pyfuncitem.funcargs
testargs = {arg: funcargs[arg] for arg in pyfuncitem._fixtureinfo.argnames}
testfunction(**testargs)
return True
@@ -243,25 +255,24 @@ class PyobjMixin(PyobjContext):
def __init__(self, *k, **kw):
super(PyobjMixin, self).__init__(*k, **kw)
def obj():
def fget(self):
obj = getattr(self, "_obj", None)
if obj is None:
self._obj = obj = self._getobj()
# XXX evil hack
# used to avoid Instance collector marker duplication
if self._ALLOW_MARKERS:
self.own_markers.extend(get_unpacked_marks(self.obj))
return obj
@property
def obj(self):
"""Underlying Python object."""
obj = getattr(self, "_obj", None)
if obj is None:
self._obj = obj = self._getobj()
# XXX evil hack
# used to avoid Instance collector marker duplication
if self._ALLOW_MARKERS:
self.own_markers.extend(get_unpacked_marks(self.obj))
return obj
def fset(self, value):
self._obj = value
return property(fget, fset, None, "underlying python object")
obj = obj()
@obj.setter
def obj(self, value):
self._obj = value
def _getobj(self):
"""Gets the underlying Python object. May be overwritten by subclasses."""
return getattr(self.parent.obj, self.name)
def getmodpath(self, stopatmodule=True, includemodule=False):
@@ -599,7 +610,12 @@ class Package(Module):
return proxy
def _collectfile(self, path, handle_dupes=True):
assert path.isfile()
assert path.isfile(), "%r is not a file (isdir=%r, exists=%r, islink=%r)" % (
path,
path.isdir(),
path.exists(),
path.islink(),
)
ihook = self.gethookproxy(path)
if not self.isinitpath(path):
if ihook.pytest_ignore_collect(path=path, config=self.config):
@@ -632,7 +648,8 @@ class Package(Module):
pkg_prefixes = set()
for path in this_path.visit(rec=self._recurse, bf=True, sort=True):
# We will visit our own __init__.py file, in which case we skip it.
if path.isfile():
is_file = path.isfile()
if is_file:
if path.basename == "__init__.py" and path.dirpath() == this_path:
continue
@@ -643,12 +660,14 @@ class Package(Module):
):
continue
if path.isdir():
if path.join("__init__.py").check(file=1):
pkg_prefixes.add(path)
else:
if is_file:
for x in self._collectfile(path):
yield x
elif not path.isdir():
# Broken symlink or invalid/missing file.
continue
elif path.join("__init__.py").check(file=1):
pkg_prefixes.add(path)
def _get_xunit_setup_teardown(holder, attr_name, param_obj=None):
@@ -1144,6 +1163,16 @@ def _find_parametrized_scope(argnames, arg2fixturedefs, indirect):
return "function"
def _ascii_escaped_by_config(val, config):
if config is None:
escape_option = False
else:
escape_option = config.getini(
"disable_test_id_escaping_and_forfeit_all_rights_to_community_support"
)
return val if escape_option else ascii_escaped(val)
def _idval(val, argname, idx, idfn, item, config):
if idfn:
try:
@@ -1165,7 +1194,7 @@ def _idval(val, argname, idx, idfn, item, config):
return hook_id
if isinstance(val, STRING_TYPES):
return ascii_escaped(val)
return _ascii_escaped_by_config(val, config)
elif isinstance(val, (float, int, bool, NoneType)):
return str(val)
elif isinstance(val, REGEX_TYPE):
@@ -1397,7 +1426,7 @@ class Function(FunctionMixin, nodes.Item, fixtures.FuncargnamesCompatAttr):
if fixtureinfo is None:
fixtureinfo = self.session._fixturemanager.getfixtureinfo(
self, self.obj, self.cls, funcargs=not self._isyieldedfunction()
self, self.obj, self.cls, funcargs=True
)
self._fixtureinfo = fixtureinfo
self.fixturenames = fixtureinfo.names_closure
@@ -1411,16 +1440,6 @@ class Function(FunctionMixin, nodes.Item, fixtures.FuncargnamesCompatAttr):
def _initrequest(self):
self.funcargs = {}
if self._isyieldedfunction():
assert not hasattr(
self, "callspec"
), "yielded functions (deprecated) cannot have funcargs"
else:
if hasattr(self, "callspec"):
callspec = self.callspec
assert not callspec.funcargs
if hasattr(callspec, "param"):
self.param = callspec.param
self._request = fixtures.FixtureRequest(self)
@property
@@ -1440,9 +1459,6 @@ class Function(FunctionMixin, nodes.Item, fixtures.FuncargnamesCompatAttr):
"(compatonly) for code expecting pytest-2.2 style request objects"
return self
def _isyieldedfunction(self):
return getattr(self, "_args", None) is not None
def runtest(self):
""" execute the underlying test function. """
self.ihook.pytest_pyfunc_call(pyfuncitem=self)

View File

@@ -11,6 +11,7 @@ import warnings
import six
import _pytest._code
from _pytest.deprecated import PYTEST_WARNS_UNKNOWN_KWARGS
from _pytest.deprecated import WARNS_EXEC
from _pytest.fixtures import yield_fixture
from _pytest.outcomes import fail
@@ -84,10 +85,12 @@ def warns(expected_warning, *args, **kwargs):
"""
__tracebackhide__ = True
match_expr = None
if not args:
if "match" in kwargs:
match_expr = kwargs.pop("match")
match_expr = kwargs.pop("match", None)
if kwargs:
warnings.warn(
PYTEST_WARNS_UNKNOWN_KWARGS.format(args=sorted(kwargs)), stacklevel=2
)
return WarningsChecker(expected_warning, match_expr=match_expr)
elif isinstance(args[0], str):
warnings.warn(WARNS_EXEC, stacklevel=2)
@@ -97,12 +100,12 @@ def warns(expected_warning, *args, **kwargs):
loc = frame.f_locals.copy()
loc.update(kwargs)
with WarningsChecker(expected_warning, match_expr=match_expr):
with WarningsChecker(expected_warning):
code = _pytest._code.Source(code).compile()
six.exec_(code, frame.f_globals, loc)
else:
func = args[0]
with WarningsChecker(expected_warning, match_expr=match_expr):
with WarningsChecker(expected_warning):
return func(*args[1:], **kwargs)

View File

@@ -1,6 +1,19 @@
import py
from pprint import pprint
import py
import six
from _pytest._code.code import ExceptionInfo
from _pytest._code.code import ReprEntry
from _pytest._code.code import ReprEntryNative
from _pytest._code.code import ReprExceptionInfo
from _pytest._code.code import ReprFileLocation
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.outcomes import skip
from _pytest.pathlib import Path
def getslaveinfoline(node):
@@ -20,6 +33,7 @@ def getslaveinfoline(node):
class BaseReport(object):
when = None
location = None
def __init__(self, **kw):
self.__dict__.update(kw)
@@ -97,12 +111,173 @@ class BaseReport(object):
def fspath(self):
return self.nodeid.split("::")[0]
@property
def count_towards_summary(self):
"""
**Experimental**
Returns True if this report should be counted towards the totals shown at the end of the
test session: "1 passed, 1 failure, etc".
.. note::
This function is considered **experimental**, so beware that it is subject to changes
even in patch releases.
"""
return True
@property
def head_line(self):
"""
**Experimental**
Returns the head line shown with longrepr output for this report, more commonly during
traceback representation during failures::
________ Test.foo ________
In the example above, the head_line is "Test.foo".
.. note::
This function is considered **experimental**, so beware that it is subject to changes
even in patch releases.
"""
if self.location is not None:
fspath, lineno, domain = self.location
return domain
def _to_json(self):
"""
This was originally the serialize_report() function from xdist (ca03269).
Returns the contents of this report as a dict of builtin entries, suitable for
serialization.
Experimental method.
"""
def disassembled_report(rep):
reprtraceback = rep.longrepr.reprtraceback.__dict__.copy()
reprcrash = rep.longrepr.reprcrash.__dict__.copy()
new_entries = []
for entry in reprtraceback["reprentries"]:
entry_data = {
"type": type(entry).__name__,
"data": entry.__dict__.copy(),
}
for key, value in entry_data["data"].items():
if hasattr(value, "__dict__"):
entry_data["data"][key] = value.__dict__.copy()
new_entries.append(entry_data)
reprtraceback["reprentries"] = new_entries
return {
"reprcrash": reprcrash,
"reprtraceback": reprtraceback,
"sections": rep.longrepr.sections,
}
d = self.__dict__.copy()
if hasattr(self.longrepr, "toterminal"):
if hasattr(self.longrepr, "reprtraceback") and hasattr(
self.longrepr, "reprcrash"
):
d["longrepr"] = disassembled_report(self)
else:
d["longrepr"] = six.text_type(self.longrepr)
else:
d["longrepr"] = self.longrepr
for name in d:
if isinstance(d[name], (py.path.local, Path)):
d[name] = str(d[name])
elif name == "result":
d[name] = None # for now
return d
@classmethod
def _from_json(cls, reportdict):
"""
This was originally the serialize_report() function from xdist (ca03269).
Factory method that returns either a TestReport or CollectReport, depending on the calling
class. It's the callers responsibility to know which class to pass here.
Experimental method.
"""
if reportdict["longrepr"]:
if (
"reprcrash" in reportdict["longrepr"]
and "reprtraceback" in reportdict["longrepr"]
):
reprtraceback = reportdict["longrepr"]["reprtraceback"]
reprcrash = reportdict["longrepr"]["reprcrash"]
unserialized_entries = []
reprentry = None
for entry_data in reprtraceback["reprentries"]:
data = entry_data["data"]
entry_type = entry_data["type"]
if entry_type == "ReprEntry":
reprfuncargs = None
reprfileloc = None
reprlocals = None
if data["reprfuncargs"]:
reprfuncargs = ReprFuncArgs(**data["reprfuncargs"])
if data["reprfileloc"]:
reprfileloc = ReprFileLocation(**data["reprfileloc"])
if data["reprlocals"]:
reprlocals = ReprLocals(data["reprlocals"]["lines"])
reprentry = ReprEntry(
lines=data["lines"],
reprfuncargs=reprfuncargs,
reprlocals=reprlocals,
filelocrepr=reprfileloc,
style=data["style"],
)
elif entry_type == "ReprEntryNative":
reprentry = ReprEntryNative(data["lines"])
else:
_report_unserialization_failure(entry_type, cls, reportdict)
unserialized_entries.append(reprentry)
reprtraceback["reprentries"] = unserialized_entries
exception_info = ReprExceptionInfo(
reprtraceback=ReprTraceback(**reprtraceback),
reprcrash=ReprFileLocation(**reprcrash),
)
for section in reportdict["longrepr"]["sections"]:
exception_info.addsection(*section)
reportdict["longrepr"] = exception_info
return cls(**reportdict)
def _report_unserialization_failure(type_name, report_class, reportdict):
url = "https://github.com/pytest-dev/pytest/issues"
stream = py.io.TextIO()
pprint("-" * 100, stream=stream)
pprint("INTERNALERROR: Unknown entry type returned: %s" % type_name, stream=stream)
pprint("report_name: %s" % report_class, stream=stream)
pprint(reportdict, stream=stream)
pprint("Please report this bug at %s" % url, stream=stream)
pprint("-" * 100, stream=stream)
raise RuntimeError(stream.getvalue())
class TestReport(BaseReport):
""" Basic test report object (also used for setup and teardown calls if
they fail).
"""
__test__ = False
def __init__(
self,
nodeid,
@@ -159,6 +334,49 @@ class TestReport(BaseReport):
self.outcome,
)
@classmethod
def from_item_and_call(cls, item, call):
"""
Factory method to create and fill a TestReport with standard item and call info.
"""
when = call.when
duration = call.stop - call.start
keywords = {x: 1 for x in item.keywords}
excinfo = call.excinfo
sections = []
if not call.excinfo:
outcome = "passed"
longrepr = None
else:
if not isinstance(excinfo, ExceptionInfo):
outcome = "failed"
longrepr = excinfo
elif excinfo.errisinstance(skip.Exception):
outcome = "skipped"
r = excinfo._getreprcrash()
longrepr = (str(r.path), r.lineno, r.message)
else:
outcome = "failed"
if call.when == "call":
longrepr = item.repr_failure(excinfo)
else: # exception in setup or teardown
longrepr = item._repr_failure_py(
excinfo, style=item.config.option.tbstyle
)
for rwhen, key, content in item._report_sections:
sections.append(("Captured %s %s" % (key, rwhen), content))
return cls(
item.nodeid,
item.location,
keywords,
outcome,
longrepr,
when,
sections,
duration,
user_properties=item.user_properties,
)
class CollectReport(BaseReport):
when = "collect"
@@ -189,3 +407,21 @@ class CollectErrorRepr(TerminalRepr):
def toterminal(self, out):
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__
return data
def pytest_report_from_serializable(data):
if "_report_type" in data:
if data["_report_type"] == "TestReport":
return TestReport._from_json(data)
elif data["_report_type"] == "CollectReport":
return CollectReport._from_json(data)
assert False, "Unknown report_type unserialize data: {}".format(
data["_report_type"]
)

View File

@@ -87,9 +87,9 @@ def runtestprotocol(item, log=True, nextitem=None):
rep = call_and_report(item, "setup", log)
reports = [rep]
if rep.passed:
if item.config.option.setupshow:
if item.config.getoption("setupshow", False):
show_test_item(item)
if not item.config.option.setuponly:
if not item.config.getoption("setuponly", False):
reports.append(call_and_report(item, "call", log))
reports.append(call_and_report(item, "teardown", log, nextitem=nextitem))
# after all teardown hooks have been called
@@ -192,7 +192,7 @@ def call_runtest_hook(item, when, **kwds):
hookname = "pytest_runtest_" + when
ihook = getattr(item.ihook, hookname)
reraise = (Exit,)
if not item.config.getvalue("usepdb"):
if not item.config.getoption("usepdb", False):
reraise += (KeyboardInterrupt,)
return CallInfo.from_call(
lambda: ihook(item=item, **kwds), when=when, reraise=reraise
@@ -246,43 +246,7 @@ class CallInfo(object):
def pytest_runtest_makereport(item, call):
when = call.when
duration = call.stop - call.start
keywords = {x: 1 for x in item.keywords}
excinfo = call.excinfo
sections = []
if not call.excinfo:
outcome = "passed"
longrepr = None
else:
if not isinstance(excinfo, ExceptionInfo):
outcome = "failed"
longrepr = excinfo
elif excinfo.errisinstance(skip.Exception):
outcome = "skipped"
r = excinfo._getreprcrash()
longrepr = (str(r.path), r.lineno, r.message)
else:
outcome = "failed"
if call.when == "call":
longrepr = item.repr_failure(excinfo)
else: # exception in setup or teardown
longrepr = item._repr_failure_py(
excinfo, style=item.config.option.tbstyle
)
for rwhen, key, content in item._report_sections:
sections.append(("Captured %s %s" % (key, rwhen), content))
return TestReport(
item.nodeid,
item.location,
keywords,
outcome,
longrepr,
when,
sections,
duration,
user_properties=item.user_properties,
)
return TestReport.from_item_and_call(item, call)
def pytest_make_collect_report(collector):

View File

@@ -207,20 +207,22 @@ def pytest_terminal_summary(terminalreporter):
def show_simple(terminalreporter, lines, stat):
failed = terminalreporter.stats.get(stat)
if failed:
config = terminalreporter.config
for rep in failed:
verbose_word = _get_report_str(terminalreporter, rep)
pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid)
verbose_word = _get_report_str(config, rep)
pos = _get_pos(config, rep)
lines.append("%s %s" % (verbose_word, pos))
def show_xfailed(terminalreporter, lines):
xfailed = terminalreporter.stats.get("xfailed")
if xfailed:
config = terminalreporter.config
for rep in xfailed:
verbose_word = _get_report_str(terminalreporter, rep)
pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid)
reason = rep.wasxfail
verbose_word = _get_report_str(config, rep)
pos = _get_pos(config, rep)
lines.append("%s %s" % (verbose_word, pos))
reason = rep.wasxfail
if reason:
lines.append(" " + str(reason))
@@ -228,9 +230,10 @@ def show_xfailed(terminalreporter, lines):
def show_xpassed(terminalreporter, lines):
xpassed = terminalreporter.stats.get("xpassed")
if xpassed:
config = terminalreporter.config
for rep in xpassed:
verbose_word = _get_report_str(terminalreporter, rep)
pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid)
verbose_word = _get_report_str(config, rep)
pos = _get_pos(config, rep)
reason = rep.wasxfail
lines.append("%s %s %s" % (verbose_word, pos, reason))
@@ -261,9 +264,9 @@ def show_skipped(terminalreporter, lines):
tr = terminalreporter
skipped = tr.stats.get("skipped", [])
if skipped:
verbose_word = _get_report_str(terminalreporter, report=skipped[0])
fskips = folded_skips(skipped)
if fskips:
verbose_word = _get_report_str(terminalreporter.config, report=skipped[0])
for num, fspath, lineno, reason in fskips:
if reason.startswith("Skipped: "):
reason = reason[9:]
@@ -283,13 +286,18 @@ def shower(stat):
return show_
def _get_report_str(terminalreporter, report):
_category, _short, verbose = terminalreporter.config.hook.pytest_report_teststatus(
report=report, config=terminalreporter.config
def _get_report_str(config, report):
_category, _short, verbose = config.hook.pytest_report_teststatus(
report=report, config=config
)
return verbose
def _get_pos(config, rep):
nodeid = config.cwd_relative_nodeid(rep.nodeid)
return nodeid
REPORTCHAR_ACTIONS = {
"x": show_xfailed,
"X": show_xpassed,

View File

@@ -8,7 +8,7 @@ def pytest_addoption(parser):
"--stepwise",
action="store_true",
dest="stepwise",
help="exit on test fail and continue from last failing test next time",
help="exit on test failure and continue from last failing test next time",
)
group.addoption(
"--stepwise-skip",
@@ -37,7 +37,10 @@ class StepwisePlugin:
self.session = session
def pytest_collection_modifyitems(self, session, config, items):
if not self.active or not self.lastfailed:
if not self.active:
return
if not self.lastfailed:
self.report_status = "no previously failed tests, not skipping."
return
already_passed = []
@@ -54,7 +57,12 @@ class StepwisePlugin:
# If the previously failed test was not found among the test items,
# do not skip any tests.
if not found:
self.report_status = "previously failed test not found, not skipping."
already_passed = []
else:
self.report_status = "skipping {} already passed items.".format(
len(already_passed)
)
for item in already_passed:
items.remove(item)
@@ -94,6 +102,10 @@ class StepwisePlugin:
if report.nodeid == self.lastfailed:
self.lastfailed = None
def pytest_report_collectionfinish(self):
if self.active and self.config.getoption("verbose") >= 0:
return "stepwise: %s" % self.report_status
def pytest_sessionfinish(self, session):
if self.active:
self.config.cache.set("cache/stepwise", self.lastfailed)

View File

@@ -26,6 +26,8 @@ from _pytest.main import EXIT_OK
from _pytest.main import EXIT_TESTSFAILED
from _pytest.main import EXIT_USAGEERROR
REPORT_COLLECTING_RESOLUTION = 0.5
class MoreQuietAction(argparse.Action):
"""
@@ -197,6 +199,7 @@ class WarningReport(object):
message = attr.ib()
nodeid = attr.ib(default=None)
fslocation = attr.ib(default=None)
count_towards_summary = True
def get_location(self, config):
"""
@@ -245,10 +248,10 @@ class TerminalReporter(object):
def _determine_show_progress_info(self):
"""Return True if we should display progress information based on the current config"""
# do not show progress if we are not capturing output (#3038)
if self.config.getoption("capture") == "no":
if self.config.getoption("capture", "no") == "no":
return False
# do not show progress if we are showing fixture setup/teardown
if self.config.getoption("setupshow"):
if self.config.getoption("setupshow", False):
return False
return self.config.getini("console_output_style") in ("progress", "count")
@@ -280,7 +283,9 @@ class TerminalReporter(object):
def write_fspath_result(self, nodeid, res, **markup):
fspath = self.config.rootdir.join(nodeid.split("::")[0])
if fspath != self.currentfspath:
# NOTE: explicitly check for None to work around py bug, and for less
# overhead in general (https://github.com/pytest-dev/py/pull/207).
if self.currentfspath is None or fspath != self.currentfspath:
if self.currentfspath is not None and self._show_progress_info:
self._write_progress_information_filling_space()
self.currentfspath = fspath
@@ -381,6 +386,7 @@ class TerminalReporter(object):
self.write_fspath_result(fsid, "")
def pytest_runtest_logreport(self, report):
self._tests_ran = True
rep = report
res = self.config.hook.pytest_report_teststatus(report=rep, config=self.config)
category, letter, word = res
@@ -389,7 +395,6 @@ class TerminalReporter(object):
else:
markup = None
self.stats.setdefault(category, []).append(rep)
self._tests_ran = True
if not letter and not word:
# probably passed setup/teardown
return
@@ -453,8 +458,6 @@ class TerminalReporter(object):
self._tw.write(msg + "\n", cyan=True)
def _get_progress_information_message(self):
if self.config.getoption("capture") == "no":
return ""
collected = self._session.testscollected
if self.config.getini("console_output_style") == "count":
if collected:
@@ -511,7 +514,7 @@ class TerminalReporter(object):
t = time.time()
if (
self._collect_report_last_write is not None
and self._collect_report_last_write > t - 0.5
and self._collect_report_last_write > t - REPORT_COLLECTING_RESOLUTION
):
return
self._collect_report_last_write = t
@@ -581,16 +584,21 @@ class TerminalReporter(object):
self.write_line(line)
def pytest_report_header(self, config):
inifile = ""
line = "rootdir: %s" % config.rootdir
if config.inifile:
inifile = " " + config.rootdir.bestrelpath(config.inifile)
lines = ["rootdir: %s, inifile:%s" % (config.rootdir, inifile)]
line += ", inifile: " + config.rootdir.bestrelpath(config.inifile)
testpaths = config.getini("testpaths")
if testpaths and config.args == testpaths:
rel_paths = [config.rootdir.bestrelpath(x) for x in testpaths]
line += ", testpaths: {}".format(", ".join(rel_paths))
result = [line]
plugininfo = config.pluginmanager.list_plugin_distinfo()
if plugininfo:
lines.append("plugins: %s" % ", ".join(_plugin_nameversions(plugininfo)))
return lines
result.append("plugins: %s" % ", ".join(_plugin_nameversions(plugininfo)))
return result
def pytest_collection_finish(self, session):
if self.config.getoption("collectonly"):
@@ -672,7 +680,6 @@ class TerminalReporter(object):
self.summary_passes()
# Display any extra warnings from teardown here (if any).
self.summary_warnings()
self.summary_deprecated_python()
def pytest_keyboard_interrupt(self, excinfo):
self._keyboardinterrupt_memo = excinfo.getrepr(funcargs=True)
@@ -718,9 +725,8 @@ class TerminalReporter(object):
return res + " "
def _getfailureheadline(self, rep):
if hasattr(rep, "location"):
fspath, lineno, domain = rep.location
return domain
if rep.head_line:
return rep.head_line
else:
return "test session" # XXX?
@@ -794,20 +800,6 @@ class TerminalReporter(object):
self.write_sep("_", msg)
self._outrep_summary(rep)
def summary_deprecated_python(self):
if sys.version_info[:2] <= (3, 4) and self.verbosity >= 0:
self.write_sep("=", "deprecated python version", yellow=True, bold=False)
using_version = ".".join(str(x) for x in sys.version_info[:3])
self.line(
"You are using Python {}, which will no longer be supported in pytest 5.0".format(
using_version
),
yellow=True,
bold=False,
)
self.line("For more information, please read:")
self.line(" https://docs.pytest.org/en/latest/py27-py34-deprecation.html")
def print_teardown_sections(self, rep):
showcapture = self.config.option.showcapture
if showcapture == "no":
@@ -882,18 +874,23 @@ class TerminalReporter(object):
def build_summary_stats_line(stats):
keys = ("failed passed skipped deselected xfailed xpassed warnings error").split()
unknown_key_seen = False
for key in stats.keys():
if key not in keys:
if key: # setup/teardown reports have an empty key, ignore them
keys.append(key)
unknown_key_seen = True
known_types = (
"failed passed skipped deselected xfailed xpassed warnings error".split()
)
unknown_type_seen = False
for found_type in stats:
if found_type not in known_types:
if found_type: # setup/teardown reports have an empty key, ignore them
known_types.append(found_type)
unknown_type_seen = True
parts = []
for key in keys:
val = stats.get(key, None)
if val:
parts.append("%d %s" % (len(val), key))
for key in known_types:
reports = stats.get(key, None)
if reports:
count = sum(
1 for rep in reports if getattr(rep, "count_towards_summary", True)
)
parts.append("%d %s" % (count, key))
if parts:
line = ", ".join(parts)
@@ -902,14 +899,14 @@ def build_summary_stats_line(stats):
if "failed" in stats or "error" in stats:
color = "red"
elif "warnings" in stats or unknown_key_seen:
elif "warnings" in stats or unknown_type_seen:
color = "yellow"
elif "passed" in stats:
color = "green"
else:
color = "yellow"
return (line, color)
return line, color
def _plugin_nameversions(plugininfo):

View File

@@ -31,7 +31,7 @@ class TempPathFactory(object):
# using os.path.abspath() to get absolute path instead of resolve() as it
# does not work the same in all platforms (see #4427)
# Path.absolute() exists, but it is not public (see https://bugs.python.org/issue25012)
convert=attr.converters.optional(
converter=attr.converters.optional(
lambda p: Path(os.path.abspath(six.text_type(p)))
)
)

View File

@@ -103,8 +103,9 @@ def catch_warnings_for_item(config, ihook, when, item):
def warning_record_to_str(warning_message):
"""Convert a warnings.WarningMessage to a string, taking in account a lot of unicode shenaningans in Python 2.
"""Convert a warnings.WarningMessage to a string.
This takes lot of unicode shenaningans into account for Python 2.
When Python 2 support is dropped this function can be greatly simplified.
"""
warn_msg = warning_message.message

View File

@@ -8,6 +8,7 @@ import sys
import textwrap
import types
import attr
import py
import six
@@ -108,6 +109,60 @@ class TestGeneralUsage(object):
assert result.ret == 0
result.stdout.fnmatch_lines(["*1 passed*"])
@pytest.mark.parametrize("load_cov_early", [True, False])
def test_early_load_setuptools_name(self, testdir, monkeypatch, load_cov_early):
pkg_resources = pytest.importorskip("pkg_resources")
testdir.makepyfile(mytestplugin1_module="")
testdir.makepyfile(mytestplugin2_module="")
testdir.makepyfile(mycov_module="")
testdir.syspathinsert()
loaded = []
@attr.s
class DummyEntryPoint(object):
name = attr.ib()
module = attr.ib()
version = "1.0"
@property
def project_name(self):
return self.name
def load(self):
__import__(self.module)
loaded.append(self.name)
return sys.modules[self.module]
@property
def dist(self):
return self
def _get_metadata(self, *args):
return []
entry_points = [
DummyEntryPoint("myplugin1", "mytestplugin1_module"),
DummyEntryPoint("myplugin2", "mytestplugin2_module"),
DummyEntryPoint("mycov", "mycov_module"),
]
def my_iter(group, name=None):
assert group == "pytest11"
for ep in entry_points:
if name is not None and ep.name != name:
continue
yield ep
monkeypatch.setattr(pkg_resources, "iter_entry_points", my_iter)
params = ("-p", "mycov") if load_cov_early else ()
testdir.runpytest_inprocess(*params)
if load_cov_early:
assert loaded == ["mycov", "myplugin1", "myplugin2"]
else:
assert loaded == ["myplugin1", "myplugin2", "mycov"]
def test_assertion_magic(self, testdir):
p = testdir.makepyfile(
"""
@@ -622,6 +677,8 @@ class TestInvocationVariants(object):
def test_cmdline_python_namespace_package(self, testdir, monkeypatch):
"""
test --pyargs option with namespace packages (#1567)
Ref: https://packaging.python.org/guides/packaging-namespace-packages/
"""
monkeypatch.delenv("PYTHONDONTWRITEBYTECODE", raising=False)
@@ -854,9 +911,7 @@ class TestDurations(object):
result = testdir.runpytest("--durations=2")
assert result.ret == 0
lines = result.stdout.get_lines_after("*slowest*durations*")
# account for the "deprecated python version" header
index = 2 if sys.version_info[:2] > (3, 4) else 6
assert "4 passed" in lines[index]
assert "4 passed" in lines[2]
def test_calls_showall(self, testdir):
testdir.makepyfile(self.source)
@@ -980,7 +1035,7 @@ def test_pytest_plugins_as_module(testdir):
}
)
result = testdir.runpytest()
result.stdout.fnmatch_lines("* 1 passed in *")
result.stdout.fnmatch_lines(["* 1 passed in *"])
def test_deferred_hook_checking(testdir):
@@ -1120,9 +1175,37 @@ def test_fixture_mock_integration(testdir):
"""Test that decorators applied to fixture are left working (#3774)"""
p = testdir.copy_example("acceptance/fixture_mock_integration.py")
result = testdir.runpytest(p)
result.stdout.fnmatch_lines("*1 passed*")
result.stdout.fnmatch_lines(["*1 passed*"])
def test_usage_error_code(testdir):
result = testdir.runpytest("-unknown-option-")
assert result.ret == EXIT_USAGEERROR
@pytest.mark.skipif(
sys.version_info[:2] < (3, 5), reason="async def syntax python 3.5+ only"
)
@pytest.mark.filterwarnings("default")
def test_warn_on_async_function(testdir):
testdir.makepyfile(
test_async="""
async def test_1():
pass
async def test_2():
pass
"""
)
result = testdir.runpytest()
result.stdout.fnmatch_lines(
[
"test_async.py::test_1",
"test_async.py::test_2",
"*Coroutine functions are not natively supported*",
"*2 skipped, 2 warnings in*",
]
)
# ensure our warning message appears only once
assert (
result.stdout.str().count("Coroutine functions are not natively supported") == 1
)

View File

@@ -172,6 +172,10 @@ class TestExceptionInfo(object):
exci = _pytest._code.ExceptionInfo.from_current()
assert exci.getrepr()
def test_from_current_with_missing(self):
with pytest.raises(AssertionError, match="no current exception"):
_pytest._code.ExceptionInfo.from_current()
class TestTracebackEntry(object):
def test_getsource(self):

View File

@@ -16,7 +16,6 @@ import _pytest._code
import pytest
from _pytest._code import Source
astonly = pytest.mark.nothing
failsonjython = pytest.mark.xfail("sys.platform.startswith('java')")
@@ -560,7 +559,6 @@ def test_oneline_and_comment():
assert str(source) == "raise ValueError"
@pytest.mark.xfail(hasattr(sys, "pypy_version_info"), reason="does not work on pypy")
def test_comments():
source = '''def test():
"comment 1"
@@ -576,9 +574,15 @@ comment 4
'''
for line in range(2, 6):
assert str(getstatement(line, source)) == " x = 1"
for line in range(6, 10):
if sys.version_info >= (3, 8) or hasattr(sys, "pypy_version_info"):
tqs_start = 8
else:
tqs_start = 10
assert str(getstatement(10, source)) == '"""'
for line in range(6, tqs_start):
assert str(getstatement(line, source)) == " assert False"
assert str(getstatement(10, source)) == '"""'
for line in range(tqs_start, 10):
assert str(getstatement(line, source)) == '"""\ncomment 4\n"""'
def test_comment_in_statement():

View File

@@ -3,9 +3,9 @@ from __future__ import division
from __future__ import print_function
import os
import sys
import pytest
from _pytest.warning_types import PytestDeprecationWarning
from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG
pytestmark = pytest.mark.pytester_example_path("deprecated")
@@ -147,7 +147,7 @@ def test_pytest_plugins_in_non_top_level_conftest_unsupported_pyargs(
if use_pyargs:
assert msg not in res.stdout.str()
else:
res.stdout.fnmatch_lines("*{msg}*".format(msg=msg))
res.stdout.fnmatch_lines(["*{msg}*".format(msg=msg)])
def test_pytest_plugins_in_non_top_level_conftest_unsupported_no_top_level_conftest(
@@ -222,19 +222,9 @@ def test_fixture_named_request(testdir):
)
def test_python_deprecation(testdir):
result = testdir.runpytest()
python_ver = ".".join(str(x) for x in sys.version_info[:3])
msg = "You are using Python {}, which will no longer be supported in pytest 5.0".format(
python_ver
)
if sys.version_info[:2] <= (3, 4):
result.stdout.fnmatch_lines(
[
msg,
"For more information, please read:",
" https://docs.pytest.org/en/latest/py27-py34-deprecation.html",
]
)
else:
assert msg not in result.stdout.str()
def test_pytest_warns_unknown_kwargs():
with pytest.warns(
PytestDeprecationWarning,
match=r"pytest.warns\(\) got unexpected keyword arguments: \['foo'\]",
):
pytest.warns(UserWarning, foo="hello")

View File

@@ -1,7 +1,6 @@
import argparse
import pathlib
HERE = pathlib.Path(__file__).parent
TEST_CONTENT = (HERE / "template_test.py").read_bytes()

View File

@@ -8,5 +8,6 @@ if __name__ == "__main__":
hidden = []
for x in pytest.freeze_includes():
hidden.extend(["--hidden-import", x])
hidden.extend(["--hidden-import", "distutils"])
args = ["pyinstaller", "--noconfirm"] + hidden + ["runtests_script.py"]
subprocess.check_call(" ".join(args), shell=True)

View File

@@ -54,6 +54,7 @@ def test_root_logger_affected(testdir):
"""
import logging
logger = logging.getLogger()
def test_foo():
logger.info('info text ' + 'going to logger')
logger.warning('warning text ' + 'going to logger')
@@ -66,15 +67,14 @@ def test_root_logger_affected(testdir):
result = testdir.runpytest("--log-level=ERROR", "--log-file=pytest.log")
assert result.ret == 1
# the capture log calls in the stdout section only contain the
# logger.error msg, because --log-level=ERROR
# The capture log calls in the stdout section only contain the
# logger.error msg, because of --log-level=ERROR.
result.stdout.fnmatch_lines(["*error text going to logger*"])
with pytest.raises(pytest.fail.Exception):
result.stdout.fnmatch_lines(["*warning text going to logger*"])
with pytest.raises(pytest.fail.Exception):
result.stdout.fnmatch_lines(["*info text going to logger*"])
stdout = result.stdout.str()
assert "warning text going to logger" not in stdout
assert "info text going to logger" not in stdout
# the log file should contain the warning and the error log messages and
# The log file should contain the warning and the error log messages and
# not the info one, because the default level of the root logger is
# WARNING.
assert os.path.isfile(log_file)
@@ -635,7 +635,6 @@ def test_log_cli_auto_enable(testdir, request, cli_args):
"""
testdir.makepyfile(
"""
import pytest
import logging
def test_log_1():
@@ -653,6 +652,7 @@ def test_log_cli_auto_enable(testdir, request, cli_args):
)
result = testdir.runpytest(cli_args)
stdout = result.stdout.str()
if cli_args == "--log-cli-level=WARNING":
result.stdout.fnmatch_lines(
[
@@ -663,13 +663,13 @@ def test_log_cli_auto_enable(testdir, request, cli_args):
"=* 1 passed in *=",
]
)
assert "INFO" not in result.stdout.str()
assert "INFO" not in stdout
else:
result.stdout.fnmatch_lines(
["*test_log_cli_auto_enable*100%*", "=* 1 passed in *="]
)
assert "INFO" not in result.stdout.str()
assert "WARNING" not in result.stdout.str()
assert "INFO" not in stdout
assert "WARNING" not in stdout
def test_log_file_cli(testdir):
@@ -747,7 +747,7 @@ def test_log_level_not_changed_by_default(testdir):
"""
)
result = testdir.runpytest("-s")
result.stdout.fnmatch_lines("* 1 passed in *")
result.stdout.fnmatch_lines(["* 1 passed in *"])
def test_log_file_ini(testdir):
@@ -1002,3 +1002,85 @@ def test_log_in_hooks(testdir):
assert "sessionstart" in contents
assert "runtestloop" in contents
assert "sessionfinish" in contents
def test_log_in_runtest_logreport(testdir):
log_file = testdir.tmpdir.join("pytest.log").strpath
testdir.makeini(
"""
[pytest]
log_file={}
log_file_level = INFO
log_cli=true
""".format(
log_file
)
)
testdir.makeconftest(
"""
import logging
logger = logging.getLogger(__name__)
def pytest_runtest_logreport(report):
logger.info("logreport")
"""
)
testdir.makepyfile(
"""
def test_first():
assert True
"""
)
testdir.runpytest()
with open(log_file) as rfh:
contents = rfh.read()
assert contents.count("logreport") == 3
def test_log_set_path(testdir):
report_dir_base = testdir.tmpdir.strpath
testdir.makeini(
"""
[pytest]
log_file_level = DEBUG
log_cli=true
"""
)
testdir.makeconftest(
"""
import os
import pytest
@pytest.hookimpl(hookwrapper=True, tryfirst=True)
def pytest_runtest_setup(item):
config = item.config
logging_plugin = config.pluginmanager.get_plugin("logging-plugin")
report_file = os.path.join({}, item._request.node.name)
logging_plugin.set_log_path(report_file)
yield
""".format(
repr(report_dir_base)
)
)
testdir.makepyfile(
"""
import logging
logger = logging.getLogger("testcase-logger")
def test_first():
logger.info("message from test 1")
assert True
def test_second():
logger.debug("message from test 2")
assert True
"""
)
testdir.runpytest()
with open(os.path.join(report_dir_base, "test_first"), "r") as rfh:
content = rfh.read()
assert "message from test 1" in content
with open(os.path.join(report_dir_base, "test_second"), "r") as rfh:
content = rfh.read()
assert "message from test 2" in content

View File

@@ -34,8 +34,6 @@ class TestModule(object):
)
def test_import_prepend_append(self, testdir, monkeypatch):
syspath = list(sys.path)
monkeypatch.setattr(sys, "path", syspath)
root1 = testdir.mkdir("root1")
root2 = testdir.mkdir("root2")
root1.ensure("x456.py")
@@ -560,7 +558,7 @@ class TestFunction(object):
"""
)
result = testdir.runpytest()
result.stdout.fnmatch_lines("* 2 passed, 1 skipped in *")
result.stdout.fnmatch_lines(["* 2 passed, 1 skipped in *"])
def test_parametrize_skip(self, testdir):
testdir.makepyfile(
@@ -575,7 +573,7 @@ class TestFunction(object):
"""
)
result = testdir.runpytest()
result.stdout.fnmatch_lines("* 2 passed, 1 skipped in *")
result.stdout.fnmatch_lines(["* 2 passed, 1 skipped in *"])
def test_parametrize_skipif_no_skip(self, testdir):
testdir.makepyfile(
@@ -590,7 +588,7 @@ class TestFunction(object):
"""
)
result = testdir.runpytest()
result.stdout.fnmatch_lines("* 1 failed, 2 passed in *")
result.stdout.fnmatch_lines(["* 1 failed, 2 passed in *"])
def test_parametrize_xfail(self, testdir):
testdir.makepyfile(
@@ -605,7 +603,7 @@ class TestFunction(object):
"""
)
result = testdir.runpytest()
result.stdout.fnmatch_lines("* 2 passed, 1 xfailed in *")
result.stdout.fnmatch_lines(["* 2 passed, 1 xfailed in *"])
def test_parametrize_passed(self, testdir):
testdir.makepyfile(
@@ -620,7 +618,7 @@ class TestFunction(object):
"""
)
result = testdir.runpytest()
result.stdout.fnmatch_lines("* 2 passed, 1 xpassed in *")
result.stdout.fnmatch_lines(["* 2 passed, 1 xpassed in *"])
def test_parametrize_xfail_passed(self, testdir):
testdir.makepyfile(
@@ -635,7 +633,7 @@ class TestFunction(object):
"""
)
result = testdir.runpytest()
result.stdout.fnmatch_lines("* 3 passed in *")
result.stdout.fnmatch_lines(["* 3 passed in *"])
def test_function_original_name(self, testdir):
items = testdir.getitems(
@@ -833,7 +831,7 @@ class TestConftestCustomization(object):
)
# Use runpytest_subprocess, since we're futzing with sys.meta_path.
result = testdir.runpytest_subprocess()
result.stdout.fnmatch_lines("*1 passed*")
result.stdout.fnmatch_lines(["*1 passed*"])
def test_setup_only_available_in_subdir(testdir):
@@ -1298,14 +1296,14 @@ def test_keep_duplicates(testdir):
def test_package_collection_infinite_recursion(testdir):
testdir.copy_example("collect/package_infinite_recursion")
result = testdir.runpytest()
result.stdout.fnmatch_lines("*1 passed*")
result.stdout.fnmatch_lines(["*1 passed*"])
def test_package_collection_init_given_as_argument(testdir):
"""Regression test for #3749"""
p = testdir.copy_example("collect/package_init_given_as_arg")
result = testdir.runpytest(p / "pkg" / "__init__.py")
result.stdout.fnmatch_lines("*1 passed*")
result.stdout.fnmatch_lines(["*1 passed*"])
def test_package_with_modules(testdir):

View File

@@ -536,7 +536,7 @@ class TestRequestBasic(object):
"""
)
result = testdir.runpytest_subprocess()
result.stdout.fnmatch_lines("* 1 passed in *")
result.stdout.fnmatch_lines(["* 1 passed in *"])
def test_getfixturevalue_recursive(self, testdir):
testdir.makeconftest(
@@ -562,6 +562,44 @@ class TestRequestBasic(object):
reprec = testdir.inline_run()
reprec.assertoutcome(passed=1)
def test_getfixturevalue_teardown(self, testdir):
"""
Issue #1895
`test_inner` requests `inner` fixture, which in turn requests `resource`
using `getfixturevalue`. `test_func` then requests `resource`.
`resource` is teardown before `inner` because the fixture mechanism won't consider
`inner` dependent on `resource` when it is used via `getfixturevalue`: `test_func`
will then cause the `resource`'s finalizer to be called first because of this.
"""
testdir.makepyfile(
"""
import pytest
@pytest.fixture(scope='session')
def resource():
r = ['value']
yield r
r.pop()
@pytest.fixture(scope='session')
def inner(request):
resource = request.getfixturevalue('resource')
assert resource == ['value']
yield
assert resource == ['value']
def test_inner(inner):
pass
def test_func(resource):
pass
"""
)
result = testdir.runpytest()
result.stdout.fnmatch_lines(["* 2 passed in *"])
@pytest.mark.parametrize("getfixmethod", ("getfixturevalue", "getfuncargvalue"))
def test_getfixturevalue(self, testdir, getfixmethod):
item = testdir.getitem(
@@ -749,7 +787,7 @@ class TestRequestBasic(object):
"""Regression test for #3057"""
testdir.copy_example("fixtures/test_getfixturevalue_dynamic.py")
result = testdir.runpytest()
result.stdout.fnmatch_lines("*1 passed*")
result.stdout.fnmatch_lines(["*1 passed*"])
def test_funcargnames_compatattr(self, testdir):
testdir.makepyfile(
@@ -992,8 +1030,8 @@ class TestFixtureUsages(object):
result.stdout.fnmatch_lines(
[
"*ScopeMismatch*involved factories*",
"* def arg2*",
"* def arg1*",
"test_receives_funcargs_scope_mismatch.py:6: def arg2(arg1)",
"test_receives_funcargs_scope_mismatch.py:2: def arg1()",
"*1 error*",
]
)
@@ -1103,6 +1141,7 @@ class TestFixtureUsages(object):
values = reprec.getfailedcollections()
assert len(values) == 1
@pytest.mark.filterwarnings("ignore::pytest.PytestDeprecationWarning")
def test_request_can_be_overridden(self, testdir):
testdir.makepyfile(
"""
@@ -1489,7 +1528,7 @@ class TestFixtureManagerParseFactories(object):
def test_collect_custom_items(self, testdir):
testdir.copy_example("fixtures/custom_item")
result = testdir.runpytest("foo")
result.stdout.fnmatch_lines("*passed*")
result.stdout.fnmatch_lines(["*passed*"])
class TestAutouseDiscovery(object):
@@ -2571,7 +2610,7 @@ class TestFixtureMarker(object):
)
reprec = testdir.runpytest("-s")
for test in ["test_browser"]:
reprec.stdout.fnmatch_lines("*Finalized*")
reprec.stdout.fnmatch_lines(["*Finalized*"])
def test_class_scope_with_normal_tests(self, testdir):
testpath = testdir.makepyfile(
@@ -3412,7 +3451,7 @@ class TestContextManagerFixtureFuncs(object):
"""
)
result = testdir.runpytest("-s")
result.stdout.fnmatch_lines("*mew*")
result.stdout.fnmatch_lines(["*mew*"])
class TestParameterizedSubRequest(object):

View File

@@ -11,6 +11,7 @@ import six
import _pytest.assertion as plugin
import pytest
from _pytest import outcomes
from _pytest.assertion import truncate
from _pytest.assertion import util
@@ -209,7 +210,7 @@ class TestImportHookInstallation(object):
import spamplugin
return spamplugin
def iter_entry_points(name):
def iter_entry_points(group, name=None):
yield DummyEntryPoint()
pkg_resources.iter_entry_points = iter_entry_points
@@ -1305,3 +1306,13 @@ def test_issue_1944(testdir):
"AttributeError: 'Module' object has no attribute '_obj'"
not in result.stdout.str()
)
def test_exit_from_assertrepr_compare(monkeypatch):
def raise_exit(obj):
outcomes.exit("Quitting debugger")
monkeypatch.setattr(util, "istext", raise_exit)
with pytest.raises(outcomes.Exit, match="Quitting debugger"):
callequal(1, 1)

View File

@@ -127,7 +127,7 @@ class TestAssertionRewrite(object):
result = testdir.runpytest_subprocess()
assert "warnings" not in "".join(result.outlines)
def test_name(self):
def test_name(self, request):
def f():
assert False
@@ -147,17 +147,41 @@ class TestAssertionRewrite(object):
def f():
assert sys == 42
assert getmsg(f, {"sys": sys}) == "assert sys == 42"
verbose = request.config.getoption("verbose")
msg = getmsg(f, {"sys": sys})
if verbose > 0:
assert msg == (
"assert <module 'sys' (built-in)> == 42\n"
" -<module 'sys' (built-in)>\n"
" +42"
)
else:
assert msg == "assert sys == 42"
def f():
assert cls == 42 # noqa
assert cls == 42 # noqa: F821
class X(object):
pass
assert getmsg(f, {"cls": X}) == "assert cls == 42"
msg = getmsg(f, {"cls": X}).splitlines()
if verbose > 0:
if six.PY2:
assert msg == [
"assert <class 'test_assertrewrite.X'> == 42",
" -<class 'test_assertrewrite.X'>",
" +42",
]
else:
assert msg == [
"assert <class 'test_...e.<locals>.X'> == 42",
" -<class 'test_assertrewrite.TestAssertionRewrite.test_name.<locals>.X'>",
" +42",
]
else:
assert msg == ["assert cls == 42"]
def test_dont_rewrite_if_hasattr_fails(self):
def test_dont_rewrite_if_hasattr_fails(self, request):
class Y(object):
""" A class whos getattr fails, but not with `AttributeError` """
@@ -173,10 +197,16 @@ class TestAssertionRewrite(object):
def f():
assert cls().foo == 2 # noqa
message = getmsg(f, {"cls": Y})
assert "assert 3 == 2" in message
assert "+ where 3 = Y.foo" in message
assert "+ where Y = cls()" in message
# XXX: looks like the "where" should also be there in verbose mode?!
message = getmsg(f, {"cls": Y}).splitlines()
if request.config.getoption("verbose") > 0:
assert message == ["assert 3 == 2", " -3", " +2"]
else:
assert message == [
"assert 3 == 2",
" + where 3 = Y.foo",
" + where Y = cls()",
]
def test_assert_already_has_message(self):
def f():
@@ -552,15 +582,16 @@ class TestAssertionRewrite(object):
getmsg(f, must_pass=True)
def test_len(self):
def test_len(self, request):
def f():
values = list(range(10))
assert len(values) == 11
assert getmsg(f).startswith(
"""assert 10 == 11
+ where 10 = len(["""
)
msg = getmsg(f)
if request.config.getoption("verbose") > 0:
assert msg == "assert 10 == 11\n -10\n +11"
else:
assert msg == "assert 10 == 11\n + where 10 = len([0, 1, 2, 3, 4, 5, ...])"
def test_custom_reprcompare(self, monkeypatch):
def my_reprcompare(op, left, right):
@@ -608,7 +639,7 @@ class TestAssertionRewrite(object):
assert getmsg(f).startswith("assert '%test' == 'test'")
def test_custom_repr(self):
def test_custom_repr(self, request):
def f():
class Foo(object):
a = 1
@@ -619,7 +650,11 @@ class TestAssertionRewrite(object):
f = Foo()
assert 0 == f.a
assert r"where 1 = \n{ \n~ \n}.a" in util._format_lines([getmsg(f)])[0]
lines = util._format_lines([getmsg(f)])
if request.config.getoption("verbose") > 0:
assert lines == ["assert 0 == 1\n -0\n +1"]
else:
assert lines == ["assert 0 == 1\n + where 1 = \\n{ \\n~ \\n}.a"]
def test_custom_repr_non_ascii(self):
def f():
@@ -796,7 +831,7 @@ def test_rewritten():
)
# needs to be a subprocess because pytester explicitly disables this warning
result = testdir.runpytest_subprocess()
result.stdout.fnmatch_lines("*Module already imported*: _pytest")
result.stdout.fnmatch_lines(["*Module already imported*: _pytest"])
def test_rewrite_module_imported_from_conftest(self, testdir):
testdir.makeconftest(
@@ -1123,7 +1158,7 @@ class TestAssertionRewriteHookDetails(object):
)
path.join("data.txt").write("Hey")
result = testdir.runpytest()
result.stdout.fnmatch_lines("*1 passed*")
result.stdout.fnmatch_lines(["*1 passed*"])
def test_issue731(testdir):
@@ -1154,7 +1189,7 @@ class TestIssue925(object):
"""
)
result = testdir.runpytest()
result.stdout.fnmatch_lines("*E*assert (False == False) == False")
result.stdout.fnmatch_lines(["*E*assert (False == False) == False"])
def test_long_case(self, testdir):
testdir.makepyfile(
@@ -1164,7 +1199,7 @@ class TestIssue925(object):
"""
)
result = testdir.runpytest()
result.stdout.fnmatch_lines("*E*assert (False == True) == True")
result.stdout.fnmatch_lines(["*E*assert (False == True) == True"])
def test_many_brackets(self, testdir):
testdir.makepyfile(
@@ -1174,7 +1209,7 @@ class TestIssue925(object):
"""
)
result = testdir.runpytest()
result.stdout.fnmatch_lines("*E*assert True == ((False == True) == True)")
result.stdout.fnmatch_lines(["*E*assert True == ((False == True) == True)"])
class TestIssue2121:
@@ -1194,7 +1229,30 @@ class TestIssue2121:
"""
)
result = testdir.runpytest()
result.stdout.fnmatch_lines("*E*assert (1 + 1) == 3")
result.stdout.fnmatch_lines(["*E*assert (1 + 1) == 3"])
@pytest.mark.parametrize("offset", [-1, +1])
def test_source_mtime_long_long(testdir, offset):
"""Support modification dates after 2038 in rewritten files (#4903).
pytest would crash with:
fp.write(struct.pack("<ll", mtime, size))
E struct.error: argument out of range
"""
p = testdir.makepyfile(
"""
def test(): pass
"""
)
# use unsigned long timestamp which overflows signed long,
# which was the cause of the bug
# +1 offset also tests masking of 0xFFFFFFFF
timestamp = 2 ** 32 + offset
os.utime(str(p), (timestamp, timestamp))
result = testdir.runpytest()
assert result.ret == 0
def test_rewrite_infinite_recursion(testdir, pytestconfig, monkeypatch):
@@ -1308,10 +1366,16 @@ class TestEarlyRewriteBailout(object):
@pytest.mark.skipif(
sys.platform.startswith("win32"), reason="cannot remove cwd on Windows"
)
def test_cwd_changed(self, testdir):
def test_cwd_changed(self, testdir, monkeypatch):
# Setup conditions for py's fspath trying to import pathlib on py34
# always (previously triggered via xdist only).
# Ref: https://github.com/pytest-dev/py/pull/207
monkeypatch.syspath_prepend("")
monkeypatch.delitem(sys.modules, "pathlib", raising=False)
testdir.makepyfile(
**{
"test_bar.py": """
"test_setup_nonexisting_cwd.py": """
import os
import shutil
import tempfile
@@ -1320,11 +1384,11 @@ class TestEarlyRewriteBailout(object):
os.chdir(d)
shutil.rmtree(d)
""",
"test_foo.py": """
"test_test.py": """
def test():
pass
""",
}
)
result = testdir.runpytest()
result.stdout.fnmatch_lines("* 1 passed in *")
result.stdout.fnmatch_lines(["* 1 passed in *"])

View File

@@ -393,7 +393,7 @@ class TestLastFailed(object):
"""
)
result = testdir.runpytest()
result.stdout.fnmatch_lines("*1 failed in*")
result.stdout.fnmatch_lines(["*1 failed in*"])
def test_terminal_report_lastfailed(self, testdir):
test_a = testdir.makepyfile(
@@ -574,7 +574,7 @@ class TestLastFailed(object):
"""
)
result = testdir.runpytest()
result.stdout.fnmatch_lines("*1 xfailed*")
result.stdout.fnmatch_lines(["*1 xfailed*"])
assert self.get_cached_last_failed(testdir) == []
def test_xfail_strict_considered_failure(self, testdir):
@@ -587,7 +587,7 @@ class TestLastFailed(object):
"""
)
result = testdir.runpytest()
result.stdout.fnmatch_lines("*1 failed*")
result.stdout.fnmatch_lines(["*1 failed*"])
assert self.get_cached_last_failed(testdir) == [
"test_xfail_strict_considered_failure.py::test"
]
@@ -680,12 +680,12 @@ class TestLastFailed(object):
"""
)
result = testdir.runpytest(test_bar)
result.stdout.fnmatch_lines("*2 passed*")
result.stdout.fnmatch_lines(["*2 passed*"])
# ensure cache does not forget that test_foo_4 failed once before
assert self.get_cached_last_failed(testdir) == ["test_foo.py::test_foo_4"]
result = testdir.runpytest("--last-failed")
result.stdout.fnmatch_lines("*1 failed, 3 deselected*")
result.stdout.fnmatch_lines(["*1 failed, 3 deselected*"])
assert self.get_cached_last_failed(testdir) == ["test_foo.py::test_foo_4"]
# 3. fix test_foo_4, run only test_foo.py
@@ -698,11 +698,11 @@ class TestLastFailed(object):
"""
)
result = testdir.runpytest(test_foo, "--last-failed")
result.stdout.fnmatch_lines("*1 passed, 1 deselected*")
result.stdout.fnmatch_lines(["*1 passed, 1 deselected*"])
assert self.get_cached_last_failed(testdir) == []
result = testdir.runpytest("--last-failed")
result.stdout.fnmatch_lines("*4 passed*")
result.stdout.fnmatch_lines(["*4 passed*"])
assert self.get_cached_last_failed(testdir) == []
def test_lastfailed_no_failures_behavior_all_passed(self, testdir):
@@ -884,7 +884,7 @@ class TestReadme(object):
def test_readme_failed(self, testdir):
testdir.makepyfile(
"""
def test_always_passes():
def test_always_fails():
assert 0
"""
)

View File

@@ -18,6 +18,7 @@ from six import text_type
import pytest
from _pytest import capture
from _pytest.capture import CaptureManager
from _pytest.compat import _PY3
from _pytest.main import EXIT_NOTESTSCOLLECTED
# note: py.io capture tests where copied from
@@ -133,12 +134,22 @@ def test_capturing_bytes_in_utf8_encoding(testdir, method):
def test_collect_capturing(testdir):
p = testdir.makepyfile(
"""
import sys
print("collect %s failure" % 13)
sys.stderr.write("collect %s_stderr failure" % 13)
import xyz42123
"""
)
result = testdir.runpytest(p)
result.stdout.fnmatch_lines(["*Captured stdout*", "*collect 13 failure*"])
result.stdout.fnmatch_lines(
[
"*Captured stdout*",
"collect 13 failure",
"*Captured stderr*",
"collect 13_stderr failure",
]
)
class TestPerTestCapturing(object):
@@ -565,7 +576,7 @@ class TestCaptureFixture(object):
result.stdout.fnmatch_lines(
[
"*test_hello*",
"*capsysbinary is only supported on python 3*",
"*capsysbinary is only supported on Python 3*",
"*1 error in*",
]
)
@@ -672,7 +683,7 @@ class TestCaptureFixture(object):
)
)
result = testdir.runpytest_subprocess()
result.stdout.fnmatch_lines("*1 passed*")
result.stdout.fnmatch_lines(["*1 passed*"])
assert "stdout contents begin" not in result.stdout.str()
assert "stderr contents begin" not in result.stdout.str()
@@ -1230,20 +1241,27 @@ class TestStdCaptureFDinvalidFD(object):
"""
import os
from _pytest import capture
def StdCaptureFD(out=True, err=True, in_=True):
return capture.MultiCapture(out, err, in_,
Capture=capture.FDCapture)
Capture=capture.FDCapture)
def test_stdout():
os.close(1)
cap = StdCaptureFD(out=True, err=False, in_=False)
assert repr(cap.out) == "<FDCapture 1 oldfd=None>"
cap.stop_capturing()
def test_stderr():
os.close(2)
cap = StdCaptureFD(out=False, err=True, in_=False)
assert repr(cap.err) == "<FDCapture 2 oldfd=None>"
cap.stop_capturing()
def test_stdin():
os.close(0)
cap = StdCaptureFD(out=False, err=False, in_=True)
assert repr(cap.in_) == "<FDCapture 0 oldfd=None>"
cap.stop_capturing()
"""
)
@@ -1402,28 +1420,36 @@ def test_dontreadfrominput_has_encoding(testdir):
def test_crash_on_closing_tmpfile_py27(testdir):
testdir.makepyfile(
p = testdir.makepyfile(
"""
from __future__ import print_function
import time
import threading
import sys
printing = threading.Event()
def spam():
f = sys.stderr
while True:
print('.', end='', file=f)
print('SPAMBEFORE', end='', file=f)
printing.set()
def test_silly():
while True:
try:
f.flush()
except (OSError, ValueError):
break
def test_spam_in_thread():
t = threading.Thread(target=spam)
t.daemon = True
t.start()
time.sleep(0.5)
printing.wait()
"""
)
result = testdir.runpytest_subprocess()
result = testdir.runpytest_subprocess(str(p))
assert result.ret == 0
assert result.stderr.str() == ""
assert "IOError" not in result.stdout.str()
@@ -1526,3 +1552,26 @@ def test_capture_with_live_logging(testdir, capture_fixture):
result = testdir.runpytest_subprocess("--log-cli-level=INFO")
assert result.ret == 0
def test_typeerror_encodedfile_write(testdir):
"""It should behave the same with and without output capturing (#4861)."""
p = testdir.makepyfile(
"""
def test_fails():
import sys
sys.stdout.write(b"foo")
"""
)
result_without_capture = testdir.runpytest("-s", str(p))
result_with_capture = testdir.runpytest(str(p))
assert result_with_capture.ret == result_without_capture.ret
if _PY3:
result_with_capture.stdout.fnmatch_lines(
["E TypeError: write() argument must be str, not bytes"]
)
else:
assert result_with_capture.ret == 0

View File

@@ -2,6 +2,7 @@ from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import os
import pprint
import sys
import textwrap
@@ -10,6 +11,7 @@ import py
import pytest
from _pytest.main import _in_venv
from _pytest.main import EXIT_INTERRUPTED
from _pytest.main import EXIT_NOTESTSCOLLECTED
from _pytest.main import Session
@@ -349,10 +351,10 @@ class TestCustomConftests(object):
p = testdir.makepyfile("def test_hello(): pass")
result = testdir.runpytest(p)
assert result.ret == 0
result.stdout.fnmatch_lines("*1 passed*")
result.stdout.fnmatch_lines(["*1 passed*"])
result = testdir.runpytest()
assert result.ret == EXIT_NOTESTSCOLLECTED
result.stdout.fnmatch_lines("*collected 0 items*")
result.stdout.fnmatch_lines(["*collected 0 items*"])
def test_collectignore_exclude_on_option(self, testdir):
testdir.makeconftest(
@@ -374,6 +376,26 @@ class TestCustomConftests(object):
assert result.ret == 0
assert "passed" in result.stdout.str()
def test_collectignoreglob_exclude_on_option(self, testdir):
testdir.makeconftest(
"""
collect_ignore_glob = ['*w*l[dt]*']
def pytest_addoption(parser):
parser.addoption("--XX", action="store_true", default=False)
def pytest_configure(config):
if config.getvalue("XX"):
collect_ignore_glob[:] = []
"""
)
testdir.makepyfile(test_world="def test_hello(): pass")
testdir.makepyfile(test_welt="def test_hallo(): pass")
result = testdir.runpytest()
assert result.ret == EXIT_NOTESTSCOLLECTED
result.stdout.fnmatch_lines(["*collected 0 items*"])
result = testdir.runpytest("--XX")
assert result.ret == 0
result.stdout.fnmatch_lines(["*2 passed*"])
def test_pytest_fs_collect_hooks_are_seen(self, testdir):
testdir.makeconftest(
"""
@@ -1088,7 +1110,7 @@ def test_collect_pyargs_with_testpaths(testdir, monkeypatch):
"""
)
)
monkeypatch.setenv("PYTHONPATH", str(testdir.tmpdir))
monkeypatch.setenv("PYTHONPATH", str(testdir.tmpdir), prepend=os.pathsep)
with root.as_cwd():
result = testdir.runpytest_subprocess()
result.stdout.fnmatch_lines(["*1 passed in*"])
@@ -1186,3 +1208,47 @@ def test_collect_pkg_init_and_file_in_args(testdir):
"*2 passed in*",
]
)
@pytest.mark.skipif(
not hasattr(py.path.local, "mksymlinkto"),
reason="symlink not available on this platform",
)
@pytest.mark.parametrize("use_pkg", (True, False))
def test_collect_sub_with_symlinks(use_pkg, testdir):
sub = testdir.mkdir("sub")
if use_pkg:
sub.ensure("__init__.py")
sub.ensure("test_file.py").write("def test_file(): pass")
# Create a broken symlink.
sub.join("test_broken.py").mksymlinkto("test_doesnotexist.py")
# Symlink that gets collected.
sub.join("test_symlink.py").mksymlinkto("test_file.py")
result = testdir.runpytest("-v", str(sub))
result.stdout.fnmatch_lines(
[
"sub/test_file.py::test_file PASSED*",
"sub/test_symlink.py::test_file PASSED*",
"*2 passed in*",
]
)
def test_collector_respects_tbstyle(testdir):
p1 = testdir.makepyfile("assert 0")
result = testdir.runpytest(p1, "--tb=native")
assert result.ret == EXIT_INTERRUPTED
result.stdout.fnmatch_lines(
[
"*_ ERROR collecting test_collector_respects_tbstyle.py _*",
"Traceback (most recent call last):",
' File "*/test_collector_respects_tbstyle.py", line 1, in <module>',
" assert 0",
"AssertionError: assert 0",
"*! Interrupted: 1 errors during collection !*",
"*= 1 error in *",
]
)

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