Compare commits

...

280 Commits
3.1.3 ... 3.2.2

Author SHA1 Message Date
Bruno Oliveira
c52f87ede3 Preparing release version 3.2.2 2017-09-06 21:37:57 +00:00
Bruno Oliveira
549f5c1a47 Merge pull request #2736 from xuanluong/issue-2604-documents-mark.with_args
[DOC] Add examples for mark.with_args
2017-09-06 16:33:01 -03:00
Xuan Luong
3f8ff7f090 [DOC] Add examples for mark.with_args 2017-09-06 14:26:28 -04:00
Ronny Pfannschmidt
ad36407747 Merge pull request #2700 from nicoddemus/staticmethods-fixtures
Allow tests declared as @staticmethod to use fixtures
2017-09-06 08:24:00 +02:00
Bruno Oliveira
1fc185b640 Add comment about possible future refactoring in the fixture mechanism 2017-09-05 19:28:39 -03:00
Ronny Pfannschmidt
181bd60bf9 Merge pull request #2742 from nicoddemus/resultlog-deprecation
Improve user guidance regarding ``--resultlog`` deprecation
2017-09-01 08:29:41 +02:00
Bruno Oliveira
3288c9a110 Improve user guidance regarding `--resultlog` deprecation
Fix #2739
2017-08-31 19:11:41 -03:00
Ronny Pfannschmidt
5e00549ecc Merge pull request #2735 from fgmacedo/fgm-fix-reprfuncargs-toterminal
2731.bug Fix ReprFuncArgs with mixed unicode and utf-8 args.
2017-08-31 09:36:56 +02:00
Ronny Pfannschmidt
b770a32dc8 Merge pull request #2707 from cybergrind/fix_baseexception
Catch BaseException in safe_getattr
2017-08-31 09:35:07 +02:00
Bruno Oliveira
f9157b1b6b Improve CHANGELOG entry to be more user-friendly 2017-08-30 21:21:44 -03:00
Bruno Oliveira
f4e811afc0 Merge remote-tracking branch 'upstream/master' into cybergrind/fix_baseexception 2017-08-30 21:11:22 -03:00
Fernando Macedo
59cdef92be fixes #2731 ReprFuncArgs with mixed unicode and utf-8 args 2017-08-30 16:06:12 -03:00
Bruno Oliveira
709b8b65a4 Merge pull request #2721 from josepht/patch-1
Fix typo in goodpractices.rst
2017-08-29 22:08:20 -03:00
Bruno Oliveira
0824076e11 Merge pull request #2710 from massich/raises_match_doc
[DOC] update raises documentation regarding regex match
2017-08-29 21:01:27 -03:00
Joe Talbott
67161ee9f8 Add changelog item for PR #2721. 2017-08-25 10:06:45 -04:00
Joe Talbott
1c891d7d97 Fix typo in goodpractices.rst 2017-08-25 10:01:24 -04:00
Kirill Pinchuk
12b1bff6c5 compat.safe_getattr now catches OutcomeExceptions too 2017-08-23 17:17:03 +03:00
Joan Massich
657976e98a update raises documentation regarding regex match 2017-08-22 12:12:48 +02:00
Bruno Oliveira
a993add783 Allow tests declared as @staticmethod to use fixtures
Fix #2699
2017-08-17 20:44:19 -03:00
Ronny Pfannschmidt
539523cfee Merge pull request #2697 from nicoddemus/match-kw-version
Update docs: ``match`` keyword was introduced in 3.1
2017-08-17 14:41:55 +02:00
Bruno Oliveira
f18780ed8a Update docs: `match` keyword was introduced in 3.1
Also update the text to recommend using the context-manager
over the callable/string forms.
2017-08-16 14:28:34 -03:00
Ronny Pfannschmidt
806d47b4d4 Merge pull request #2691 from anhiga/trivial_error
Fixed error in 'Good Practices' code snippet
2017-08-16 09:50:32 +02:00
Bruno Oliveira
bfc9f61482 Update the number of plugins in index.rst 2017-08-15 23:05:23 -03:00
Antonio Hidalgo
2a99d82c3b Fixed error in 'Good Practices' code snippet 2017-08-16 00:26:14 +02:00
Ronny Pfannschmidt
9b2753b302 Merge pull request #2687 from nicoddemus/use-py36-ci
Use 3.6 as preferred Python 3 interpreter for CI
2017-08-15 10:49:39 +02:00
Bruno Oliveira
e9bfccdf2d Merge pull request #2678 from jespino/fix/2676
Add default values documentation for python_files, python_classes and python_functions
2017-08-14 20:29:39 -03:00
Bruno Oliveira
7b5d26c1a8 Use py36 as preferred Python 3 interpreter for CI testing 2017-08-14 20:27:28 -03:00
Bruno Oliveira
362b1b3c4f Use tox release candidates in CI
Fix #2683
2017-08-14 19:19:20 -03:00
Ronny Pfannschmidt
5c0c1977e3 Merge pull request #2682 from pelme/getfuncargvalue-fix
Use the correct stacklevel for getfuncargvalue() deprecation warning.
2017-08-13 18:24:39 +02:00
Andreas Pelme
39331856ed Use the correct stacklevel for getfuncargvalue() deprecation warning.
Fixed #2681.
2017-08-13 14:59:33 +02:00
Jesús Espino
dc9154e8ff Add default values documentation for python_files, python_classes and python_functions 2017-08-12 12:29:13 +02:00
Bruno Oliveira
021fba4e84 Update number of plugins on README and poiint to plugincompat link 2017-08-10 21:15:22 -03:00
Bruno Oliveira
fd84c886ee Merge pull request #2671 from nicoddemus/release-3.2.1
Release 3.2.1
2017-08-09 17:15:21 -03:00
Bruno Oliveira
e6020781f6 Merge pull request #2653 from felipedau/slow-sharing-note
Add note on sharing the `slow` marker in the basic examples
2017-08-09 08:18:26 -03:00
Felipe Dau
acd3c4fbc4 Update changelog for #2653 2017-08-09 02:57:36 +00:00
Felipe Dau
c847b83d56 Use pytest_collection_modifyitems() in the run/skip option example 2017-08-09 02:51:07 +00:00
Bruno Oliveira
45d2962e97 Preparing release version 3.2.1 2017-08-08 21:11:11 +00:00
Bruno Oliveira
8b322afcdb Make generated doc in simple.rst more reliable
Sometimes `test_funcfast` would show up in the `setup` step in
the slowest test durations summary.
2017-08-08 18:04:21 -03:00
Ronny Pfannschmidt
523bfa6151 Merge pull request #2667 from nicoddemus/py36-windows-workaround-error
Fix windows console workaround error with non-standard io-streams
2017-08-08 07:10:49 +02:00
Bruno Oliveira
cc0f2473eb Fix windows console workaround error with non-standard io-streams
Fix #2666
2017-08-07 20:57:13 -03:00
Bruno Oliveira
76c55b31c6 Merge pull request #2630 from srinivasreddy/2591
Fixed#2591: Replaced os.sep with '/' as it behaves differently on linux and windows.
2017-08-06 11:19:10 -03:00
Srinivas Reddy Thatiparthy
a0101f024e remove os.sep as it behaves differently linux and windows.
* on linux it is '/'

* on windows it is '\'
2017-08-05 23:21:43 +05:30
Ronny Pfannschmidt
d5f4496bdf Merge pull request #2656 from nicoddemus/unittest-features
Document which pytest features work with `unittest`
2017-08-05 09:38:02 +02:00
Bruno Oliveira
37353a854e Implement suggestions by Raphael 2017-08-04 17:56:13 -03:00
Ronny Pfannschmidt
12e60956de Merge pull request #2655 from nicoddemus/terminal-collecting-glitch
Fix small terminal glitch when collecting a single test item
2017-08-04 14:10:45 +02:00
Bruno Oliveira
15cdf137d5 Document which pytest features work with unittest
Fix #2626
2017-08-04 07:44:04 -03:00
Bruno Oliveira
ad52f714a9 Fix small terminal glitch when collecting a single test item
Fix #2579
2017-08-03 20:57:46 -03:00
Florian Bruhin
8969bd43c9 Merge pull request #2646 from nicoddemus/issue-2644
Properly escape test names when setting PYTEST_CURRENT_TEST environment variable
2017-08-02 16:27:05 +02:00
Bruno Oliveira
7703dc921c Only skip null bytes before setting the environment variable
As discussed, node ids have already been "ascii" sanitized by the
parametrization process
2017-08-02 10:27:45 -03:00
Bruno Oliveira
1deac2e210 Properly escape test names when setting PYTEST_CURRENT_TEST environment variable
Fix #2644
2017-08-01 20:52:37 -03:00
Bruno Oliveira
02da156351 Merge pull request #2645 from alex/patch-1
Tiny rst syntax fix
2017-08-01 20:14:39 -03:00
Alex Gaynor
84061233ef Tiny rst syntax fix 2017-08-01 19:10:50 -04:00
Bruno Oliveira
0a15edd573 Merge branch 'release-3.2.0' 2017-08-01 18:58:43 -03:00
Bruno Oliveira
51ebad76f2 Fix merge instruction after a minor/major release 2017-08-01 18:24:17 -03:00
Bruno Oliveira
d2bca93109 Update grammar in changelog as requested 2017-08-01 18:01:22 -03:00
Bruno Oliveira
07dd1ca7b8 Preparing release version 3.2.0 2017-07-30 21:37:18 +00:00
Bruno Oliveira
f1467f8f03 Merge remote-tracking branch 'upstream/master' into features 2017-07-30 18:08:24 -03:00
Bruno Oliveira
763c580a2a Merge pull request #2576 from maiksensi/feat/raise-not-implemented-for-lt-gt-in-approx
#2003 Change behavior of `approx.py` to only support `__eq__` comparison
2017-07-30 17:48:33 -03:00
Bruno Oliveira
e1aed8cb17 Merge pull request #2490 from RonnyPfannschmidt/fix-580
Test Outcomes as BaseException - fix #580
2017-07-30 17:38:42 -03:00
Bruno Oliveira
713f7636e1 Merge pull request #2632 from jmoldow/pep_0415_suppress_exception_context
Support PEP-415's Exception.__suppress_context__
2017-07-30 17:22:40 -03:00
Bruno Oliveira
4cd8727379 Merge pull request #2617 from wence-/fix/nondeterministic-fixtures
Fix nondeterminism in fixture collection order
2017-07-30 17:17:40 -03:00
Bruno Oliveira
5e0e038fec Merge pull request #2625 from nicoddemus/historical-notes
Historical notes in the docs
2017-07-29 12:22:04 -03:00
Jordan Moldow
2e61f702c0 Support PEP-415's Exception.__suppress_context__
PEP-415 states that `exception.__context__` should be suppressed
in traceback outputs, if `exception.__suppress_context__` is
`True`.

Now if a ``raise exception from None`` is caught by pytest,
pytest will no longer chain the context in the test report.

The algorithm in `FormattedExcinfo` now better matches the one
in `traceback.TracebackException`.

`Exception.__suppress_context__` is available in all of the
versions of Python 3 that are supported by pytest.

Fixes #2631.
2017-07-29 02:39:17 -07:00
Bruno Oliveira
8c2319168a Rephrase the bit about unittest migration to pytest 2017-07-28 23:15:59 -03:00
Bruno Oliveira
768edde899 Merge pull request #2624 from nicoddemus/lf-report-after-collection
Fix --last-failed reported items in terminal
2017-07-28 14:48:23 -03:00
Ronny Pfannschmidt
be401bc2f8 fix linting issues 2017-07-28 18:27:59 +02:00
Ronny Pfannschmidt
06a49338b2 make Test Outcomes inherit from BaseException instead of exception
fixes #580
2017-07-28 15:28:51 +02:00
Bruno Oliveira
7a12acb6a1 Fix linting 2017-07-28 08:54:55 -03:00
Bruno Oliveira
5acb64be90 Add versionadded tag to pytest_report_collectionfinish hook 2017-07-28 08:54:55 -03:00
Bruno Oliveira
75e6f7717c Use new hook to report accurate tests skipped in --lf and --ff 2017-07-28 08:54:55 -03:00
Ronny Pfannschmidt
17121960b4 Merge pull request #2621 from nicoddemus/cumulative-cache
Make cache plugin cumulative
2017-07-28 13:46:37 +02:00
Bruno Oliveira
7082320f3f Apply modifications requested in review 2017-07-28 07:55:34 -03:00
Bruno Oliveira
6fe7069cbb Move historical notes to their own doc
Fix #2512
2017-07-27 20:47:12 -03:00
Bruno Oliveira
d46006f791 Fixes in "contact" doc 2017-07-27 19:28:55 -03:00
Bruno Oliveira
f770f16294 Replace deprecated "config.option.<name>" usages from docs 2017-07-27 19:18:44 -03:00
Bruno Oliveira
eb1bd3449e xfail and skipped tests are removed from the "last-failed" cache
This accommodates the case where a failing test is marked as
skipped/failed later
2017-07-27 18:43:04 -03:00
Bruno Oliveira
22212c4d61 Add xfail specific tests 2017-07-27 14:40:21 -03:00
Bruno Oliveira
62810f61b2 Make cache plugin always remember failed tests 2017-07-27 14:40:21 -03:00
Ronny Pfannschmidt
e97fd5ec55 Merge pull request #2623 from nicoddemus/post-collection-report-hook
Introduce new pytest_report_collectionfinish hook
2017-07-27 17:17:21 +02:00
Bruno Oliveira
17c544e793 Introduce new pytest_report_collectionfinish hook
Fix #2622
2017-07-27 10:44:29 -03:00
Ronny Pfannschmidt
ddf1751e6d Merge pull request #2613 from nicoddemus/features
Merge master into features
2017-07-27 07:59:49 +02:00
Bruno Oliveira
3d89905114 Merge remote-tracking branch 'upstream/master' into features 2017-07-26 19:01:28 -03:00
Bruno Oliveira
1a9bc141a5 Merge pull request #2620 from mihaic/multiple-issues-in-changelog
Show multiple issue links in CHANGELOG entries
2017-07-26 16:00:00 -03:00
Mihai Capotă
10ded399d8 Show multiple issue links in CHANGELOG entries
Restores the functionality removed in PR #2488.
2017-07-26 10:58:06 -07:00
Lawrence Mitchell
f047e078e2 Mention new (py26) ordereddict dependency in changelog and docs 2017-07-26 14:41:10 +01:00
Lawrence Mitchell
f8bd693f83 Add ordereddict to install_requires for py26 2017-07-26 14:41:10 +01:00
Lawrence Mitchell
a546a612bd Fix nondeterminism in fixture collection order
fixtures.reorder_items is non-deterministic because it reorders based
on iteration over an (unordered) set.  Change the code to use an
OrderedDict instead so that we get deterministic behaviour, fixes #920.
2017-07-26 14:41:10 +01:00
Bruno Oliveira
dd294aafb3 Merge pull request #2557 from blueyed/EncodedFile-name
capture: ensure name of EncodedFile being a string
2017-07-26 10:39:10 -03:00
Lawrence Mitchell
b39f957b88 Add test of issue #920 2017-07-26 14:38:38 +01:00
Bruno Oliveira
2c2cf81d0a Merge pull request #2580 from andras-tim/fix-runpytest-subprocess
Avoid interactive pdb when pytest tests itself - fix #2023
2017-07-26 10:37:48 -03:00
Bruno Oliveira
80f4699572 approx raises TypeError in Python 2 for comparison operators other than != and == 2017-07-25 20:07:10 -03:00
Maik Figura
57a232fc5a Remove out of scope change 2017-07-25 19:19:14 -03:00
Maik Figura
1851f36beb Add PR requirements changelog and authors 2017-07-25 19:19:14 -03:00
Maik Figura
f0936d42fb Fix linter errors 2017-07-25 19:17:58 -03:00
Maik Figura
d3ab1b9df4 Add user documentation
The new doc section explains why we raise a `NotImplementedError`.
2017-07-25 19:17:58 -03:00
Daniel Hahler
0603d1d500 capture: ensure name of EncodedFile being a string
Fixes https://github.com/pytest-dev/pytest/issues/2555.
2017-07-25 20:37:37 +02:00
Bruno Oliveira
79097e84e2 Merge pull request #2615 from blueyed/revisit_contributing.rst
Revisit CONTRIBUTING.rst
2017-07-25 13:40:45 -03:00
Bruno Oliveira
1a42e26586 Improve changelog wording to be more user-oriented 2017-07-25 13:37:06 -03:00
Bruno Oliveira
595ecd23fd Merge pull request #2548 from blueyed/skip-fix-lineno
Fix lineno offset in show_skipped
2017-07-25 13:35:14 -03:00
Daniel Hahler
949a1406f0 Revisit CONTRIBUTING.rst 2017-07-25 18:27:26 +02:00
Bruno Oliveira
71947cb4f0 Merge pull request #2546 from blueyed/better-skip-not-allowed-error
[RFC] Improve error message for CollectError with skip/skipif
2017-07-25 11:19:25 -03:00
Daniel Hahler
869eed9898 Fix lineno offset in show_skipped
The line number is 0-based here, so add 1.
2017-07-25 15:19:47 +02:00
Daniel Hahler
72531f30c0 Improve error message for CollectError with skip/skipif 2017-07-25 15:14:28 +02:00
Bruno Oliveira
73c6122f35 Merge remote-tracking branch 'upstream/master' into features 2017-07-24 21:12:51 -03:00
Bruno Oliveira
70d9f8638f Merge pull request #2610 from AgriConnect/doctest-lineno
Report lineno from doctest
2017-07-24 16:29:02 -03:00
Nguyễn Hồng Quân
d40d77432c Add test case for DoctestItem.reportinfo() 2017-07-24 23:07:45 +07:00
Bruno Oliveira
e44284c125 Merge pull request #2611 from segevfiner/patch-1
Early import colorama so that it get's the correct terminal
2017-07-24 12:37:22 -03:00
Nguyễn Hồng Quân
dea671f8ba Add changelog for #2610 2017-07-24 22:01:03 +07:00
Nguyễn Hồng Quân
cdaa720bc4 Merge remote-tracking branch 'upstream/master' into doctest-lineno 2017-07-24 21:49:35 +07:00
Segev Finer
d0ecfdf00f Delete trailing whitespace 2017-07-24 16:55:50 +03:00
Florian Bruhin
81ad185f0d Merge pull request #2595 from nicoddemus/docs-rootdir-pythonpath
Clarify PYTHONPATH changes and ``rootdir`` roles
2017-07-24 15:06:38 +02:00
Bruno Oliveira
d90bef44cc Update changelog file for #2510 2017-07-24 09:31:16 -03:00
Segev Finer
df12500661 Create 2611.bugfix 2017-07-24 15:28:20 +03:00
Segev Finer
43544a431c Early import colorama so that it get's the correct terminal 2017-07-24 15:17:39 +03:00
Bruno Oliveira
0aa2480e6a Fix travis build after change from "precise" to "trusty"
Travis recently has changed its dist from "precise" to "trusty", so
some Python versions are no longer installed by default
2017-07-24 07:44:04 -03:00
Florian Bruhin
6473a81b8b Merge pull request #2608 from nicoddemus/contributing-update
Update PR guide and add a "short" version
2017-07-24 12:14:56 +02:00
Nguyễn Hồng Quân
af2c153324 Report lineno from doctest
This is to fix pytest-sugar#122 issue.
2017-07-24 11:52:24 +07:00
Florian Bruhin
309152d9fd Merge pull request #2599 from nicoddemus/turn-warnings-into-errors
Turn warnings into errors WIP, waiting #2598
2017-07-23 11:27:14 +02:00
Bruno Oliveira
d5bb2004f9 Fix travis build after change from "precise" to "trusty"
Travis recently has changed its dist from "precise" to "trusty", so
some Python versions are no longer installed by default
2017-07-23 00:50:28 -03:00
Bruno Oliveira
bda07d8b27 Ignore socket warnings on windows for trial tests 2017-07-22 21:44:18 -03:00
Bruno Oliveira
0726d9a09f Turn warnings into errors in pytest's own test suite
Fix #2588
2017-07-22 21:44:18 -03:00
Bruno Oliveira
61219da0e2 Update PR guide and add a "short" version 2017-07-22 16:08:18 -03:00
Bruno Oliveira
1b732fe361 Merge pull request #2606 from kalekundert/simplify-numpy
Make approx more compatible with numpy
2017-07-22 14:21:08 -03:00
Bruno Oliveira
b35554ca2b Merge pull request #2607 from kalekundert/remove-code-dup
Remove stale copies of raises and approx
2017-07-22 14:20:53 -03:00
Kale Kundert
7e0553267d Remove unused import. 2017-07-22 09:19:13 -07:00
Kale Kundert
ebc7346be4 Raise TypeError for types that can't be compared to arrays. 2017-07-22 09:05:12 -07:00
Kale Kundert
a3b35e1c4b Remove raises and approx from python.py.
These two classes were recently moved to `python_api.py`, but it seems
that they found their way back into the original file somehow.  This
commit removes them again to avoid out-of-date code duplication.
2017-07-22 08:36:15 -07:00
Kale Kundert
4c45bc9971 Add the numpy tests back into tox.ini
I'm not sure why they were removed...
2017-07-22 08:24:45 -07:00
Kale Kundert
495f731760 Simplify how comparisons with numpy arrays work.
Previously I was subverting the natural order of operations by
subclassing from `ndarray`, but it turns out that you can tell just
numpy to call your operator instead of its by setting the
`__array_priority__` attribute on your class.  This is much simpler, and
it turns out the be a little more robust, too.
2017-07-22 07:52:03 -07:00
Andras Tim
50764d9ebb Avoid interactive pdb when pytest tests itself - fix #2023
The debugging.py calls post_mortem() on error and pdb will drops an
interactive debugger when the stdin is a readable fd.
2017-07-21 21:29:03 +02:00
Bruno Oliveira
6461dc9fc6 Merge pull request #2600 from RonnyPfannschmidt/mark_explicit_params
fix #2540, introduce mark.with_args
2017-07-21 10:42:25 -03:00
Florian Bruhin
1cf826624e Merge pull request #2602 from blueyed/doc-fix-filterwarnings
Fix help for filterwarnings ini option
2017-07-21 15:23:22 +02:00
Daniel Hahler
97e5a3c889 Fix help for filterwarnings ini option 2017-07-21 14:21:31 +02:00
Ronny Pfannschmidt
65b2de13a3 fix #2540, introduce mark.with_args 2017-07-21 13:37:09 +02:00
Bruno Oliveira
3d24485cae Clarify PYTHONPATH changes and `rootdir` roles
- Also minor adjustments in the docs (wording, formatting, links, etc).

Fix #2589
2017-07-21 07:28:11 -03:00
Ronny Pfannschmidt
ccc4b3a501 Merge pull request #2596 from nicoddemus/autopep-tox
Add "fix-lint" tox environment
2017-07-21 06:25:59 +02:00
Ronny Pfannschmidt
cbceef2008 Merge pull request #2598 from nicoddemus/filterwarnings-mark
Introduce filter-warnings mark
2017-07-21 06:23:18 +02:00
Bruno Oliveira
7341da1bc1 Introduce pytest.mark.filterwarnings 2017-07-20 22:31:49 -03:00
Bruno Oliveira
3c28a8ec1a Improve formatting/grammar of changelog entries 2017-07-20 22:12:15 -03:00
Bruno Oliveira
22f54784c2 Add "fix-lint" tox environment to fix linting errors 2017-07-20 22:12:15 -03:00
Bruno Oliveira
abb5d20841 Merge branch 'master' into features 2017-07-20 22:10:58 -03:00
Bruno Oliveira
da12c52347 Fix: do not load hypothesis during test_logging_initialized_in_test
A recent release seem to have added a "logging" import to the top-level,
which breaks test_logging_initialized_in_test
2017-07-20 21:43:24 -03:00
Floris Bruynooghe
9e3e58af60 Merge pull request #2594 from nicoddemus/fix-flake8-errors
Merge master into features and fix flake8 errors
2017-07-20 23:20:55 +02:00
Floris Bruynooghe
56e6b4b501 Merge pull request #2578 from Llandy3d/2375
Provides encoding attribute on CaptureIO
2017-07-20 23:14:14 +02:00
Bruno Oliveira
d44565f385 Merge remote-tracking branch 'upstream/master' into fix-flake8-errors 2017-07-19 17:57:30 -03:00
Bruno Oliveira
24da938321 Fix additional flake8 errors 2017-07-19 17:42:21 -03:00
Bruno Oliveira
26ee2355d9 Merge remote-tracking branch 'upstream/features' into fix-flake8-errors 2017-07-19 17:09:05 -03:00
Bruno Oliveira
c92760dca8 Merge branch 'fix-flake8-issues' 2017-07-19 17:03:36 -03:00
Bruno Oliveira
61d4345ea4 Merge pull request #2593 from hackebrot/extend-pytester-docs-for-testing-plugin-code
Extend pytester docs for testing plugin code
2017-07-19 16:40:00 -03:00
Raphael Pierzina
1ac02b8a3b Add plugin code 2017-07-19 20:15:27 +02:00
Raphael Pierzina
d06d97a7ac Remove unnecessary comma from docs 2017-07-19 19:42:33 +02:00
Raphael Pierzina
e73a2f7ad9 Add changelog entry changelog/971.doc 2017-07-19 19:38:21 +02:00
Raphael Pierzina
91b4b229aa Update documentation for testing plugin code 2017-07-19 19:38:21 +02:00
Raphael Pierzina
2840634c2c Fix typo and improve comment about cookiecutter-template 2017-07-19 19:38:21 +02:00
Florian Bruhin
eb79fa7825 Merge pull request #2590 from nicoddemus/current-test-var
Introduce new PYTEST_CURRENT_TEST environment variable
2017-07-19 15:56:32 +02:00
Bruno Oliveira
d7f182ac4f Remove SETUPTOOLS_SCM_PRETEND_VERSION during linting
It was needed because of check-manifest, but we no longer have a
MANIFEST file so it is not necessary
2017-07-19 10:02:13 -03:00
Bruno Oliveira
2d4f1f022e Introduce PYTEST_CURRENT_TEST environment variable
Fix #2583
2017-07-19 10:01:50 -03:00
Bruno Oliveira
2c03000b96 Merge pull request #2585 from RonnyPfannschmidt/fix-2573
remove the MARK_INFO_ATTRIBUTE warning until we can fix internal usage
2017-07-19 07:46:38 -03:00
Ronny Pfannschmidt
62556bada6 remove the MARK_INFO_ATTRIBUTE warning until we can fix internal usage
fixes #2573
2017-07-19 08:44:52 +02:00
Bruno Oliveira
637e566d05 Separate all options for running/selecting tests into sections 2017-07-18 22:50:32 -03:00
Bruno Oliveira
3a1c9c0e45 Clarify in the docs how PYTEST_ADDOPTS and addopts ini option work together 2017-07-18 15:37:01 -03:00
Ronny Pfannschmidt
0e559c978f Merge pull request #2587 from nicoddemus/remove-impl-file
Remove _pytest/impl file
2017-07-18 16:44:18 +02:00
Bruno Oliveira
bd96b0aabc Remove _pytest/impl file
The file apparently contains an early design document to what
has become @pytest.fixture and can be deleted
2017-07-18 09:47:06 -03:00
Bruno Oliveira
7b1870a94e Fix flake8 in features branch 2017-07-17 21:16:14 -03:00
Bruno Oliveira
4fd92ef9ba Merge branch 'fix-flake8-issues' into features 2017-07-17 21:05:39 -03:00
Bruno Oliveira
ac3f2207bb Merge pull request #2575 from MartinAltmayer/master
#2574 Options --fixtures and --fixtures-per-test keep indentation of docstrings
2017-07-17 18:06:56 -03:00
Andras Tim
b2a5ec3b94 updated meta 2017-07-17 02:16:51 +02:00
Andras Tim
b49e8baab3 Fixed E731 flake8 errors
do not assign a lambda expression, use a def
2017-07-17 01:44:23 +02:00
Andras Tim
15610289ac Fixed E712 flake8 errors
comparison to True should be ‘if cond is True:’ or ‘if cond:’
2017-07-17 01:44:23 +02:00
Andras Tim
5ae59279f4 Fixed E704 flake8 errors
multiple statements on one line (def)
2017-07-17 01:44:23 +02:00
Andras Tim
bf259d3c93 Fixed E702 flake8 errors
multiple statements on one line (semicolon)
2017-07-17 01:44:23 +02:00
Andras Tim
85141a419f Fixed E701 flake8 errors
multiple statements on one line (colon)
2017-07-17 01:44:23 +02:00
Andras Tim
7d2ceb7872 Fixed E501 flake8 errors
line too long (> 120 characters)
2017-07-17 01:44:23 +02:00
Andras Tim
b9e318866e Fixed E402 flake8 errors
module level import not at top of file
2017-07-17 01:44:23 +02:00
Andras Tim
45ac863069 Fixed E401 flake8 errors
multiple imports on one line
2017-07-17 01:44:23 +02:00
Andras Tim
7248b759e8 Fixed E303 flake8 errors
too many blank lines (3)
2017-07-17 01:44:23 +02:00
Andras Tim
b840622819 Fixed E302 flake8 errors
expected 2 blank lines, found 0
2017-07-17 01:44:23 +02:00
Andras Tim
17a21d540b Fixed E301 flake8 errors
expected 1 blank line, found 0
2017-07-17 01:44:23 +02:00
Andras Tim
9bad9b53d8 Fixed E293 flake8 errors 2017-07-17 01:44:23 +02:00
Andras Tim
4730c6d99d Fixed E272 flake8 errors
multiple spaces before keyword
2017-07-17 01:44:23 +02:00
Andras Tim
c9a081d1a3 Fixed E271 flake8 errors
multiple spaces after keyword
2017-07-17 01:44:22 +02:00
Andras Tim
195a816522 Fixed E265 flake8 errors
block comment should start with ‘# ‘
2017-07-17 01:44:22 +02:00
Andras Tim
eae8b41b07 Fixed E262 flake8 errors
inline comment should start with ‘# ‘
2017-07-17 01:44:22 +02:00
Andras Tim
8f3eb6dfc7 Fixed E261 flake8 errors
at least two spaces before inline comment
2017-07-17 01:44:22 +02:00
Andras Tim
b226454582 Fixed E251 flake8 errors
unexpected spaces around keyword / parameter equals
2017-07-17 01:44:22 +02:00
Andras Tim
4c24947785 Fixed E241 flake8 errors
multiple spaces after ‘,’
2017-07-17 01:44:22 +02:00
Andras Tim
617e510b6e Fixed E231 flake8 errors
missing whitespace after ‘,’, ‘;’, or ‘:’
2017-07-17 01:44:22 +02:00
Andras Tim
4b22f270a3 Fixed E226 flake8 errors
missing whitespace around arithmetic operator
2017-07-17 01:44:22 +02:00
Andras Tim
2e8caefcab Fixed E225 flake8 errors
missing whitespace around operator
2017-07-17 01:44:22 +02:00
Andras Tim
3fabc4d219 Fixed E222 flake8 errors
multiple spaces after operator
2017-07-17 01:44:22 +02:00
Andras Tim
f640e0cb04 Fixed E221 flake8 errors
multiple spaces before operator
2017-07-17 01:44:22 +02:00
Andras Tim
ebb6d0650b Fixed E203 flake8 errors
whitespace before ‘:’
2017-07-17 01:44:22 +02:00
Andras Tim
ba0a4d0b2e Fixed E202 flake8 errors
whitespace before ‘)’
2017-07-17 01:28:17 +02:00
Andras Tim
1ff54ba205 Fixed E201 flake8 errors
whitespace after ‘(‘
2017-07-17 01:28:17 +02:00
Andras Tim
df54bf0db5 Fixed E131 flake8 errors
continuation line unaligned for hanging indent
2017-07-17 01:28:16 +02:00
Andras Tim
1c935db571 Fixed E129 flake8 errors
visually indented line with same indent as next logical line
2017-07-17 01:28:16 +02:00
Andras Tim
cf97159009 Fixed E128 flake8 errors
continuation line under-indented for visual indent
2017-07-17 01:28:16 +02:00
Andras Tim
57438f3efe Fixed E127 flake8 errors
continuation line over-indented for visual indent
2017-07-17 01:28:16 +02:00
Andras Tim
e855a79dd4 Fixed E126 flake8 errors
continuation line over-indented for hanging indent
2017-07-17 01:28:16 +02:00
Andras Tim
92e2cd9c68 Fixed E125 flake8 errors
continuation line with same indent as next logical line
2017-07-17 01:28:16 +02:00
Andras Tim
051d76a63f Fixed E124 flake8 errors
closing bracket does not match visual indentation
2017-07-17 01:28:16 +02:00
Andras Tim
4b20b9d8d9 Fixed E123 flake8 errors
closing bracket does not match indentation of opening bracket’s line
2017-07-17 01:28:16 +02:00
Andras Tim
425665cf25 Fixed E122 flake8 errors
continuation line missing indentation or outdented
2017-07-17 01:28:16 +02:00
Andras Tim
0be97624b7 Fixed E121 flake8 errors
continuation line under-indented for hanging indent
2017-07-17 01:28:16 +02:00
Andras Tim
64a4b9058c Fixed E113 flake8 errors
unexpected indentation
2017-07-17 01:28:16 +02:00
Andras Tim
8de49e8742 Fixed E111 flake8 errors
indentation is not a multiple of four
2017-07-17 01:28:16 +02:00
Andras Tim
6146ac97d9 Fixed E101 flake8 errors
indentation contains mixed spaces and tabs
2017-07-17 01:28:16 +02:00
Andras Tim
6af2abdb53 Fixed flake8 warnings
W191 indentation contains tabs
W292 no newline at end of file
W293 blank line contains whitespace
W391 blank line at end of file
2017-07-17 01:28:16 +02:00
Andras Tim
796ffa5123 reformatted tox.ini 2017-07-17 01:27:37 +02:00
Llandy Riveron Del Risco
ba9a76fdb3 Provides encoding attribute on CaptureIO
Fix #2375
2017-07-16 14:29:00 +02:00
Martin Altmayer
cc39f41c53 Add myself to AUTHORS as required by the PR help text. 2017-07-15 13:54:18 +02:00
Martin Altmayer
2a979797ef Add a changelog entry. 2017-07-15 13:43:59 +02:00
Martin Altmayer
e5169a026a #2574: --fixtures, --fixtures-per-test keep indentation of docstring 2017-07-15 13:33:11 +02:00
Ronny Pfannschmidt
3578f4e405 Merge pull request #2571 from ahartoto/master
Ensure final collected line doesn't include artifacts of previous write
2017-07-13 22:37:33 +02:00
Alex Hartoto
97fdc9a7fe Ensure final collected line doesn't include artifacts
We sometimes would see the following line:
collected 1 item s

just because previous write to the terminal includes number of
characters greater than 'collected 1 item'.
2017-07-13 12:11:20 -07:00
Ronny Pfannschmidt
771cedd3da Merge pull request #2567 from nicoddemus/add-report-section-docs
Add docs for Item.add_report_section in the docs
2017-07-13 10:55:03 +02:00
Ronny Pfannschmidt
81cec9f5e3 Merge pull request #2563 from pv/yield-warn-spam
Make YIELD_TEST warning less spammy
2017-07-13 10:45:46 +02:00
Ronny Pfannschmidt
1485a3a902 Merge pull request #2566 from jmsdvl/iss2518
Detect and warn/ignore local python installations
2017-07-13 10:44:04 +02:00
Ronny Pfannschmidt
f16c3b9568 Merge pull request #2569 from nicoddemus/backwards-sidebar
Add a link to our backwards compatibility policy to our side-bar
2017-07-13 10:43:39 +02:00
Bruno Oliveira
e6b9a81ccf Add a link to our backwards compatibility policy to our side-bar
It is important enough that it should be easier to find
2017-07-12 16:22:16 -03:00
John Still
67fca04050 update docs and note; add virtualenv collection tests 2017-07-11 23:14:38 -05:00
Bruno Oliveira
73b07e1439 Add docs for Item.add_report_section in the docs
Fix #2381
2017-07-11 21:45:27 -03:00
John Still
b32cfc88da use presence of activate script rather than sys.prefix to determine if a dir is a virtualenv 2017-07-11 14:32:09 -05:00
John Still
676c4f970d trim trailing ws 2017-07-11 13:31:11 -05:00
John Still
c2d49e39a2 add news item 2017-07-11 13:01:56 -05:00
John Still
89c73582ca ignore the active python installation unless told otherwise 2017-07-11 11:52:16 -05:00
Bruno Oliveira
d9aaab7ab2 Merge remote-tracking branch 'upstream/master' into features 2017-07-10 17:51:59 -03:00
Bruno Oliveira
9e0b19cce2 Merge pull request #2561 from jmsdvl/iss2533
Rename _escape_strings to _ascii_escaped
2017-07-10 17:49:44 -03:00
Bruno Oliveira
a87f6f84cc Merge pull request #2559 from RockBomber/features
cache_dir ini option for setting cache directory
2017-07-10 17:49:05 -03:00
Pauli Virtanen
8a7d98fed9 Make YIELD_TEST warning less spammy
Emit it only once per each generator, rather than for each generated
function. Also add information about which test caused it to be emitted.
2017-07-10 21:41:09 +02:00
John Still
cdd788085d add news fragment to changelog folder 2017-07-10 12:37:12 -05:00
John Still
80595115b0 replace all _escape_strings to _ascii_escaped 2017-07-10 12:32:27 -05:00
V.Kuznetsov
bd52eebab4 changelog for ini option cache_dir 2017-07-07 13:20:39 +03:00
V.Kuznetsov
91418eda3b docs for ini option cache_dir 2017-07-07 13:08:12 +03:00
V.Kuznetsov
7a9fc69435 tests for ini option cache_dir 2017-07-07 13:07:33 +03:00
V.Kuznetsov
f471eef661 ini option cache_dir 2017-07-07 13:07:06 +03:00
Ronny Pfannschmidt
ef62b86335 Merge pull request #2492 from kalekundert/features
Add support for numpy arrays (and dicts) to approx.
2017-07-06 11:46:51 +02:00
Ronny Pfannschmidt
7cd03d7611 Merge pull request #2554 from nicoddemus/pytest-configure-order
Clarify pytest_configure hook call order
2017-07-05 14:22:55 +02:00
Bruno Oliveira
3667086acc Clarify pytest_configure hook call order
Fix #2539
2017-07-05 07:50:59 -03:00
Ronny Pfannschmidt
db24a3b0fb Merge pull request #2552 from nicoddemus/strict-help-message
Change --strict help message to clarify it deals with unregistered markers
2017-07-05 06:14:25 +02:00
Bruno Oliveira
221f42c5ce Change --strict help message to clarify it deals with unregistered markers
Fix #2444
2017-07-04 20:14:57 -03:00
Kale Kundert
7a1a439049 Use cls instead of ApproxNumpyBase.
Slightly more general, probably doesn't make a difference.
2017-07-04 09:20:52 -07:00
Bruno Oliveira
b62aef3372 Merge branch 'master' into features 2017-07-04 12:44:58 -03:00
Kale Kundert
c111e9dac3 Avoid making multiple ApproxNumpy types. 2017-07-03 22:45:24 -07:00
Kale Kundert
8524a57075 Add "approx" to all the repr-strings. 2017-07-03 22:44:37 -07:00
Ronny Pfannschmidt
0303d95a53 Merge pull request #2531 from waisbrot/staticmethods
Allow staticmethods to be detected as test functions
2017-06-29 16:18:55 +02:00
Nathaniel Waisbrot
9b9fede5be allow staticmethods to be detected as test functions
Allow a class method decorated `@staticmethod` to be collected as a test
function (if it meets the usual criteria).

This feature will not work in Python 2.6 -- static methods will still be
ignored there.
2017-06-29 07:44:36 -04:00
Ronny Pfannschmidt
9b51fc646c Merge pull request #2526 from nicoddemus/merge-master-into-features
Merge master into features
2017-06-24 11:25:35 +02:00
Ronny Pfannschmidt
6eeab45a8f Merge pull request #2525 from nicoddemus/deprecate-old-style
Add changelog entry explicitly deprecating old-style classes from pytest API
2017-06-24 11:24:53 +02:00
Bruno Oliveira
3de93657bd Merge remote-tracking branch 'upstream/master' into merge-master-into-features 2017-06-24 00:47:48 -03:00
Bruno Oliveira
1906f8c565 Merge pull request #2524 from RonnyPfannschmidt/fix-2427
introduce deprecation warnings for legacy parametersets, fixes #2427
2017-06-24 00:41:44 -03:00
Bruno Oliveira
655d44b413 Add changelog entry explicitly deprecating old-style classes from pytest API
Related to #2147
2017-06-24 00:37:40 -03:00
Ronny Pfannschmidt
0d0b01bded introduce deprecation warnings for legacy parametersets, fixes #2427 2017-06-23 22:06:49 +02:00
Bruno Oliveira
bab18e10eb Merge pull request #2517 from RonnyPfannschmidt/mark-expose-nontransfered
Mark expose nontransfered marks via pytestmark property
2017-06-23 10:15:39 -03:00
Ronny Pfannschmidt
8d5f2872d3 minor code style fix 2017-06-23 11:59:03 +02:00
Ronny Pfannschmidt
b0b6c355f7 fixup changelog, thanks Bruno 2017-06-23 11:09:16 +02:00
Ronny Pfannschmidt
23d016f114 address review comments
* enhance api for fetching marks off an object
* rename functions for storing marks
* enhance deprecation message for MarkInfo
2017-06-23 11:05:38 +02:00
Ronny Pfannschmidt
1d926011a4 add deprecation warnings for using markinfo attributes 2017-06-22 15:12:50 +02:00
Ronny Pfannschmidt
c791895c93 changelog addition 2017-06-22 10:50:46 +02:00
Ronny Pfannschmidt
19b12b22e7 store pristine marks on function.pytestmark
fixes #2516
2017-06-22 10:48:45 +02:00
Ronny Pfannschmidt
64ae6ae25d extract application of marks and legacy markinfos 2017-06-22 10:41:28 +02:00
Ronny Pfannschmidt
bdec2c8f9e move marker transfer to _pytest.mark 2017-06-22 08:45:10 +02:00
Kale Kundert
9597e674d9 Use sets to compare dictionary keys. 2017-06-16 08:25:13 -07:00
Kale Kundert
d6000e5ab1 Remove py36 from .travis.yml
I thought the file was just out of date, but adding py36 made Travis
complain "InterpreterNotFound: python3.6", so I guess it was correct as
it was.
2017-06-15 20:34:36 -07:00
Kale Kundert
4d02863b16 Remove a dict-comprehension.
Not compatible with python26.
2017-06-15 18:56:09 -07:00
Kale Kundert
5d2496862a Only test numpy with py27 and py35.
Travis was not successfully installing numpy with python<=2.6,
python<=3.3, or PyPy.  I decided that it didn't make sense to use numpy
for all the tests, so instead I made new testing environments
specifically for numpy.
2017-06-15 18:46:58 -07:00
Kale Kundert
50769557e8 Skip the numpy doctests.
They seem like more trouble that they're worth.
2017-06-15 14:53:27 -07:00
Kale Kundert
b41852c93b Use autofunction to document approx.
It used to be a class, but it's a function now.
2017-06-15 14:52:39 -07:00
Kale Kundert
8badb47db6 Implement suggestions from code review.
- Avoid importing numpy unless necessary.

- Mention numpy arrays and dictionaries in the docs.

- Add numpy to the list of tox dependencies.

- Don't unnecessarily copy arrays or allocate empty space for them.

- Use code from compat.py rather than writing py2/3 versions of things
  myself.

- Avoid reimplementing __repr__ for built-in types.

- Add an option to consider NaN == NaN, because sometimes people use NaN
  to mean "missing data".
2017-06-15 09:19:10 -07:00
Kale Kundert
89292f08dc Add a changelog entry. 2017-06-11 19:51:21 -07:00
Kale Kundert
8c22aee256 Resolve merge conflict due to approx being moved. 2017-06-11 19:46:59 -07:00
Kale Kundert
9f3122fec6 Add support for numpy arrays (and dicts) to approx.
This fixes #1994.  It turned out to require a lot of refactoring because
subclassing numpy.ndarray was necessary to coerce python into calling
the right `__eq__` operator.
2017-06-11 19:27:41 -07:00
Bruno Oliveira
9bd8907716 Merge pull request #2489 from RonnyPfannschmidt/move-python-api
Move python api code to own file
2017-06-11 11:25:35 -03:00
Ronny Pfannschmidt
f8b2277413 changelog fragment 2017-06-11 14:17:40 +02:00
Ronny Pfannschmidt
6be57a3711 move python api helpers out of the python module
this separates exposed normal api from collection elements
2017-06-11 12:27:16 +02:00
Ronny Pfannschmidt
36251e0db4 move approx to own file 2017-06-11 12:15:30 +02:00
Bruno Oliveira
467c526307 Merge remote-tracking branch 'upstream/master' into features 2017-06-09 18:33:46 -03:00
Bruno Oliveira
c67bf9d82a Merge remote-tracking branch 'upstream/master' into features 2017-06-06 20:45:23 -03:00
Bruno Oliveira
9adf513c4b Merge remote-tracking branch 'upstream/master' into features 2017-05-31 13:51:41 -03:00
Ronny Pfannschmidt
4e6e29dbee Merge pull request #2442 from nicoddemus/merge-master-into-features
Merge master into features
2017-05-29 17:55:08 +02:00
128 changed files with 4864 additions and 2591 deletions

View File

@@ -1,9 +1,10 @@
sudo: false
language: python
python:
- '3.5'
- '3.6'
# command to install dependencies
install: "pip install -U tox"
install:
- pip install --upgrade --pre tox
# # command to run tests
env:
matrix:
@@ -11,27 +12,33 @@ env:
- TOXENV=coveralls
# note: please use "tox --listenvs" to populate the build matrix below
- TOXENV=linting
- TOXENV=py26
- TOXENV=py27
- TOXENV=py33
- TOXENV=py34
- TOXENV=py35
- TOXENV=pypy
- TOXENV=py36
- TOXENV=py27-pexpect
- TOXENV=py27-xdist
- TOXENV=py27-trial
- TOXENV=py35-pexpect
- TOXENV=py35-xdist
- TOXENV=py35-trial
- TOXENV=py27-numpy
- TOXENV=py36-pexpect
- TOXENV=py36-xdist
- TOXENV=py36-trial
- TOXENV=py36-numpy
- TOXENV=py27-nobyte
- TOXENV=doctesting
- TOXENV=freeze
- TOXENV=docs
matrix:
include:
- env: TOXENV=py36
python: '3.6'
- env: TOXENV=py26
python: '2.6'
- env: TOXENV=py33
python: '3.3'
- env: TOXENV=pypy
python: 'pypy-5.4'
- env: TOXENV=py35
python: '3.5'
- env: TOXENV=py35-freeze
python: '3.5'
- env: TOXENV=py37
python: 'nightly'
allow_failures:

11
AUTHORS
View File

@@ -9,6 +9,7 @@ Ahn Ki-Wook
Alexander Johnson
Alexei Kozlenok
Anatoly Bubenkoff
Andras Tim
Andreas Zeidler
Andrzej Ostrowski
Andy Freeland
@@ -83,6 +84,7 @@ John Towler
Jon Sonesen
Jonas Obrist
Jordan Guymon
Jordan Moldow
Joshua Bronson
Jurko Gospodnetić
Justyna Janczyszyn
@@ -90,13 +92,16 @@ Kale Kundert
Katarzyna Jachim
Kevin Cox
Kodi B. Arfer
Lawrence Mitchell
Lee Kamentsky
Lev Maximov
Llandy Riveron Del Risco
Loic Esteve
Lukas Bednar
Luke Murphy
Maciek Fijalkowski
Maho
Maik Figura
Mandeep Bhutani
Manuel Krebber
Marc Schlaich
@@ -104,6 +109,7 @@ Marcin Bachry
Mark Abramowitz
Markus Unterwaditzer
Martijn Faassen
Martin Altmayer
Martin K. Scherer
Martin Prusse
Mathieu Clabaut
@@ -117,7 +123,9 @@ Michael Birtwell
Michael Droettboom
Michael Seifert
Michal Wajszczuk
Mihai Capotă
Mike Lundy
Nathaniel Waisbrot
Ned Batchelder
Neven Mundar
Nicolas Delaby
@@ -147,6 +155,7 @@ Samuele Pedroni
Segev Finer
Simon Gomizelj
Skylar Downes
Srinivas Reddy Thatiparthy
Stefan Farmbauer
Stefan Zimmermann
Stefano Taschini
@@ -164,4 +173,6 @@ Vidar T. Fauske
Vitaly Lashmanov
Vlad Dragos
Wouter van Ackooy
Xuan Luong
Xuecong Liao
Zoltán Máté

View File

@@ -8,6 +8,249 @@
.. towncrier release notes start
Pytest 3.2.2 (2017-09-06)
=========================
Bug Fixes
---------
- Calling the deprecated `request.getfuncargvalue()` now shows the source of
the call. (`#2681 <https://github.com/pytest-dev/pytest/issues/2681>`_)
- Allow tests declared as ``@staticmethod`` to use fixtures. (`#2699
<https://github.com/pytest-dev/pytest/issues/2699>`_)
- Fixed edge-case during collection: attributes which raised ``pytest.fail``
when accessed would abort the entire collection. (`#2707
<https://github.com/pytest-dev/pytest/issues/2707>`_)
- Fix ``ReprFuncArgs`` with mixed unicode and UTF-8 args. (`#2731
<https://github.com/pytest-dev/pytest/issues/2731>`_)
Improved Documentation
----------------------
- In examples on working with custom markers, add examples demonstrating the
usage of ``pytest.mark.MARKER_NAME.with_args`` in comparison with
``pytest.mark.MARKER_NAME.__call__`` (`#2604
<https://github.com/pytest-dev/pytest/issues/2604>`_)
- In one of the simple examples, use `pytest_collection_modifyitems()` to skip
tests based on a command-line option, allowing its sharing while preventing a
user error when acessing `pytest.config` before the argument parsing. (`#2653
<https://github.com/pytest-dev/pytest/issues/2653>`_)
Trivial/Internal Changes
------------------------
- Fixed minor error in 'Good Practices/Manual Integration' code snippet.
(`#2691 <https://github.com/pytest-dev/pytest/issues/2691>`_)
- Fixed typo in goodpractices.rst. (`#2721
<https://github.com/pytest-dev/pytest/issues/2721>`_)
- Improve user guidance regarding ``--resultlog`` deprecation. (`#2739
<https://github.com/pytest-dev/pytest/issues/2739>`_)
Pytest 3.2.1 (2017-08-08)
=========================
Bug Fixes
---------
- Fixed small terminal glitch when collecting a single test item. (`#2579
<https://github.com/pytest-dev/pytest/issues/2579>`_)
- Correctly consider ``/`` as the file separator to automatically mark plugin
files for rewrite on Windows. (`#2591 <https://github.com/pytest-
dev/pytest/issues/2591>`_)
- Properly escape test names when setting ``PYTEST_CURRENT_TEST`` environment
variable. (`#2644 <https://github.com/pytest-dev/pytest/issues/2644>`_)
- Fix error on Windows and Python 3.6+ when ``sys.stdout`` has been replaced
with a stream-like object which does not implement the full ``io`` module
buffer protocol. In particular this affects ``pytest-xdist`` users on the
aforementioned platform. (`#2666 <https://github.com/pytest-
dev/pytest/issues/2666>`_)
Improved Documentation
----------------------
- Explicitly document which pytest features work with ``unittest``. (`#2626
<https://github.com/pytest-dev/pytest/issues/2626>`_)
Pytest 3.2.0 (2017-07-30)
=========================
Deprecations and Removals
-------------------------
- ``pytest.approx`` no longer supports ``>``, ``>=``, ``<`` and ``<=``
operators to avoid surprising/inconsistent behavior. See `the docs
<https://docs.pytest.org/en/latest/builtin.html#pytest.approx>`_ for more
information. (`#2003 <https://github.com/pytest-dev/pytest/issues/2003>`_)
- All old-style specific behavior in current classes in the pytest's API is
considered deprecated at this point and will be removed in a future release.
This affects Python 2 users only and in rare situations. (`#2147
<https://github.com/pytest-dev/pytest/issues/2147>`_)
- A deprecation warning is now raised when using marks for parameters
in ``pytest.mark.parametrize``. Use ``pytest.param`` to apply marks to
parameters instead. (`#2427 <https://github.com/pytest-dev/pytest/issues/2427>`_)
Features
--------
- Add support for numpy arrays (and dicts) to approx. (`#1994
<https://github.com/pytest-dev/pytest/issues/1994>`_)
- Now test function objects have a ``pytestmark`` attribute containing a list
of marks applied directly to the test function, as opposed to marks inherited
from parent classes or modules. (`#2516 <https://github.com/pytest-
dev/pytest/issues/2516>`_)
- Collection ignores local virtualenvs by default; `--collect-in-virtualenv`
overrides this behavior. (`#2518 <https://github.com/pytest-
dev/pytest/issues/2518>`_)
- Allow class methods decorated as ``@staticmethod`` to be candidates for
collection as a test function. (Only for Python 2.7 and above. Python 2.6
will still ignore static methods.) (`#2528 <https://github.com/pytest-
dev/pytest/issues/2528>`_)
- Introduce ``mark.with_args`` in order to allow passing functions/classes as
sole argument to marks. (`#2540 <https://github.com/pytest-
dev/pytest/issues/2540>`_)
- New ``cache_dir`` ini option: sets the directory where the contents of the
cache plugin are stored. Directory may be relative or absolute path: if relative path, then
directory is created relative to ``rootdir``, otherwise it is used as is.
Additionally path may contain environment variables which are expanded during
runtime. (`#2543 <https://github.com/pytest-dev/pytest/issues/2543>`_)
- Introduce the ``PYTEST_CURRENT_TEST`` environment variable that is set with
the ``nodeid`` and stage (``setup``, ``call`` and ``teardown``) of the test
being currently executed. See the `documentation
<https://docs.pytest.org/en/latest/example/simple.html#pytest-current-test-
environment-variable>`_ for more info. (`#2583 <https://github.com/pytest-
dev/pytest/issues/2583>`_)
- Introduced ``@pytest.mark.filterwarnings`` mark which allows overwriting the
warnings filter on a per test, class or module level. See the `docs
<https://docs.pytest.org/en/latest/warnings.html#pytest-mark-
filterwarnings>`_ for more information. (`#2598 <https://github.com/pytest-
dev/pytest/issues/2598>`_)
- ``--last-failed`` now remembers forever when a test has failed and only
forgets it if it passes again. This makes it easy to fix a test suite by
selectively running files and fixing tests incrementally. (`#2621
<https://github.com/pytest-dev/pytest/issues/2621>`_)
- New ``pytest_report_collectionfinish`` hook which allows plugins to add
messages to the terminal reporting after collection has been finished
successfully. (`#2622 <https://github.com/pytest-dev/pytest/issues/2622>`_)
- Added support for `PEP-415's <https://www.python.org/dev/peps/pep-0415/>`_
``Exception.__suppress_context__``. Now if a ``raise exception from None`` is
caught by pytest, pytest will no longer chain the context in the test report.
The behavior now matches Python's traceback behavior. (`#2631
<https://github.com/pytest-dev/pytest/issues/2631>`_)
- Exceptions raised by ``pytest.fail``, ``pytest.skip`` and ``pytest.xfail``
now subclass BaseException, making them harder to be caught unintentionally
by normal code. (`#580 <https://github.com/pytest-dev/pytest/issues/580>`_)
Bug Fixes
---------
- Set ``stdin`` to a closed ``PIPE`` in ``pytester.py.Testdir.popen()`` for
avoid unwanted interactive ``pdb`` (`#2023 <https://github.com/pytest-
dev/pytest/issues/2023>`_)
- Add missing ``encoding`` attribute to ``sys.std*`` streams when using
``capsys`` capture mode. (`#2375 <https://github.com/pytest-
dev/pytest/issues/2375>`_)
- Fix terminal color changing to black on Windows if ``colorama`` is imported
in a ``conftest.py`` file. (`#2510 <https://github.com/pytest-
dev/pytest/issues/2510>`_)
- Fix line number when reporting summary of skipped tests. (`#2548
<https://github.com/pytest-dev/pytest/issues/2548>`_)
- capture: ensure that EncodedFile.name is a string. (`#2555
<https://github.com/pytest-dev/pytest/issues/2555>`_)
- The options ``--fixtures`` and ``--fixtures-per-test`` will now keep
indentation within docstrings. (`#2574 <https://github.com/pytest-
dev/pytest/issues/2574>`_)
- doctests line numbers are now reported correctly, fixing `pytest-sugar#122
<https://github.com/Frozenball/pytest-sugar/issues/122>`_. (`#2610
<https://github.com/pytest-dev/pytest/issues/2610>`_)
- Fix non-determinism in order of fixture collection. Adds new dependency
(ordereddict) for Python 2.6. (`#920 <https://github.com/pytest-
dev/pytest/issues/920>`_)
Improved Documentation
----------------------
- Clarify ``pytest_configure`` hook call order. (`#2539
<https://github.com/pytest-dev/pytest/issues/2539>`_)
- Extend documentation for testing plugin code with the ``pytester`` plugin.
(`#971 <https://github.com/pytest-dev/pytest/issues/971>`_)
Trivial/Internal Changes
------------------------
- Update help message for ``--strict`` to make it clear it only deals with
unregistered markers, not warnings. (`#2444 <https://github.com/pytest-
dev/pytest/issues/2444>`_)
- Internal code move: move code for pytest.approx/pytest.raises to own files in
order to cut down the size of python.py (`#2489 <https://github.com/pytest-
dev/pytest/issues/2489>`_)
- Renamed the utility function ``_pytest.compat._escape_strings`` to
``_ascii_escaped`` to better communicate the function's purpose. (`#2533
<https://github.com/pytest-dev/pytest/issues/2533>`_)
- Improve error message for CollectError with skip/skipif. (`#2546
<https://github.com/pytest-dev/pytest/issues/2546>`_)
- Emit warning about ``yield`` tests being deprecated only once per generator.
(`#2562 <https://github.com/pytest-dev/pytest/issues/2562>`_)
- Ensure final collected line doesn't include artifacts of previous write.
(`#2571 <https://github.com/pytest-dev/pytest/issues/2571>`_)
- Fixed all flake8 errors and warnings. (`#2581 <https://github.com/pytest-
dev/pytest/issues/2581>`_)
- Added ``fix-lint`` tox environment to run automatic pep8 fixes on the code.
(`#2582 <https://github.com/pytest-dev/pytest/issues/2582>`_)
- Turn warnings into errors in pytest's own test suite in order to catch
regressions due to deprecations more promptly. (`#2588
<https://github.com/pytest-dev/pytest/issues/2588>`_)
- Show multiple issue links in CHANGELOG entries. (`#2620
<https://github.com/pytest-dev/pytest/issues/2620>`_)
Pytest 3.1.3 (2017-07-03)
=========================

View File

@@ -34,13 +34,13 @@ If you are reporting a bug, please include:
* Your operating system name and version.
* Any details about your local setup that might be helpful in troubleshooting,
specifically Python interpreter version,
installed libraries and pytest version.
specifically the Python interpreter version, installed libraries, and pytest
version.
* Detailed steps to reproduce the bug.
If you can write a demonstration test that currently fails but should pass (xfail),
that is a very useful commit to make as well, even if you can't find how
to fix the bug yet.
If you can write a demonstration test that currently fails but should pass
(xfail), that is a very useful commit to make as well, even if you cannot
fix the bug itself.
.. _fixbugs:
@@ -158,19 +158,40 @@ As stated, the objective is to share maintenance and avoid "plugin-abandon".
.. _`pull requests`:
.. _pull-requests:
Preparing Pull Requests on GitHub
---------------------------------
Preparing Pull Requests
-----------------------
.. note::
What is a "pull request"? It informs project's core developers about the
changes you want to review and merge. Pull requests are stored on
`GitHub servers <https://github.com/pytest-dev/pytest/pulls>`_.
Once you send a pull request, we can discuss its potential modifications and
even add more commits to it later on.
Short version
~~~~~~~~~~~~~
There's an excellent tutorial on how Pull Requests work in the
`GitHub Help Center <https://help.github.com/articles/using-pull-requests/>`_,
but here is a simple overview:
#. Fork the repository;
#. Target ``master`` for bugfixes and doc changes;
#. Target ``features`` for new features or functionality changes.
#. Follow **PEP-8**. There's a ``tox`` command to help fixing it: ``tox -e fix-lint``.
#. Tests are run using ``tox``::
tox -e linting,py27,py36
The test environments above are usually enough to cover most cases locally.
#. Write a ``changelog`` entry: ``changelog/2574.bugfix``, use issue id number
and one of ``bugfix``, ``removal``, ``feature``, ``vendor``, ``doc`` or
``trivial`` for the issue type.
#. Add yourself to ``AUTHORS`` file if not there yet, in alphabetical order.
Long version
~~~~~~~~~~~~
What is a "pull request"? It informs the project's core developers about the
changes you want to review and merge. Pull requests are stored on
`GitHub servers <https://github.com/pytest-dev/pytest/pulls>`_.
Once you send a pull request, we can discuss its potential modifications and
even add more commits to it later on. There's an excellent tutorial on how Pull
Requests work in the
`GitHub Help Center <https://help.github.com/articles/using-pull-requests/>`_.
Here is a simple overview, with pytest-specific bits:
#. Fork the
`pytest GitHub repository <https://github.com/pytest-dev/pytest>`__. It's
@@ -214,12 +235,18 @@ but here is a simple overview:
This command will run tests via the "tox" tool against Python 2.7 and 3.6
and also perform "lint" coding-style checks.
#. You can now edit your local working copy.
#. You can now edit your local working copy. Please follow PEP-8.
You can now make the changes you want and run the tests again as necessary.
To run tests on Python 2.7 and pass options to pytest (e.g. enter pdb on
failure) to pytest you can do::
If you have too much linting errors, try running::
$ tox -e fix-lint
To fix pep8 related errors.
You can pass different options to ``tox``. For example, to run tests on Python 2.7 and pass options to pytest
(e.g. enter pdb on failure) to pytest you can do::
$ tox -e py27 -- --pdb
@@ -232,9 +259,11 @@ but here is a simple overview:
$ git commit -a -m "<commit message>"
$ git push -u
Make sure you add a message to ``CHANGELOG.rst`` and add yourself to
``AUTHORS``. If you are unsure about either of these steps, submit your
pull request and we'll help you fix it up.
#. Create a new changelog entry in ``changelog``. The file should be named ``<issueid>.<type>``,
where *issueid* is the number of the issue related to the change and *type* is one of
``bugfix``, ``removal``, ``feature``, ``vendor``, ``doc`` or ``trivial``.
#. Add yourself to ``AUTHORS`` file if not there yet, in alphabetical order.
#. Finally, submit a pull request through the GitHub website using this data::

View File

@@ -54,7 +54,7 @@ How to release pytest
where PYPI_NAME is the name of pypi.python.org as configured in your ``~/.pypirc``
file `for devpi <http://doc.devpi.net/latest/quickstart-releaseprocess.html?highlight=pypirc#devpi-push-releasing-to-an-external-index>`_.
#. After a minor/major release, merge ``features`` into ``master`` and push (or open a PR).
#. After a minor/major release, merge ``release-X.Y.Z`` into ``master`` and push (or open a PR).
.. _devpi-cloud-test: https://github.com/obestwalter/devpi-cloud-test
.. _AppVeyor: https://www.appveyor.com/

View File

@@ -78,7 +78,7 @@ Features
- Python2.6+, Python3.3+, PyPy-2.3, Jython-2.5 (untested);
- Rich plugin architecture, with over 150+ `external plugins <http://docs.pytest.org/en/latest/plugins.html#installing-external-plugins-searching>`_ and thriving community;
- Rich plugin architecture, with over 315+ `external plugins <http://plugincompat.herokuapp.com>`_ and thriving community;
Documentation

View File

@@ -62,14 +62,16 @@ import sys
import os
from glob import glob
class FastFilesCompleter:
'Fast file completer class'
def __init__(self, directories=True):
self.directories = directories
def __call__(self, prefix, **kwargs):
"""only called on non option completions"""
if os.path.sep in prefix[1:]: #
if os.path.sep in prefix[1:]:
prefix_dir = len(os.path.dirname(prefix) + os.path.sep)
else:
prefix_dir = 0
@@ -98,5 +100,6 @@ if os.environ.get('_ARGCOMPLETE'):
def try_argcomplete(parser):
argcomplete.autocomplete(parser)
else:
def try_argcomplete(parser): pass
def try_argcomplete(parser):
pass
filescompleter = None

View File

@@ -5,6 +5,7 @@
from __future__ import absolute_import, division, print_function
import types
def format_exception_only(etype, value):
"""Format the exception part of a traceback.
@@ -30,7 +31,7 @@ def format_exception_only(etype, value):
# would throw another exception and mask the original problem.
if (isinstance(etype, BaseException) or
isinstance(etype, types.InstanceType) or
etype is None or type(etype) is str):
etype is None or type(etype) is str):
return [_format_final_exc_line(etype, value)]
stype = etype.__name__
@@ -62,6 +63,7 @@ def format_exception_only(etype, value):
lines.append(_format_final_exc_line(stype, value))
return lines
def _format_final_exc_line(etype, value):
"""Return a list of a single line -- normal case for format_exception_only"""
valuestr = _some_str(value)
@@ -71,6 +73,7 @@ def _format_final_exc_line(etype, value):
line = "%s: %s\n" % (etype, valuestr)
return line
def _some_str(value):
try:
return unicode(value)

View File

@@ -18,6 +18,7 @@ else:
class Code(object):
""" wrapper around Python code objects """
def __init__(self, rawcode):
if not hasattr(rawcode, "co_filename"):
rawcode = getrawcode(rawcode)
@@ -26,7 +27,7 @@ class Code(object):
self.firstlineno = rawcode.co_firstlineno - 1
self.name = rawcode.co_name
except AttributeError:
raise TypeError("not a code object: %r" %(rawcode,))
raise TypeError("not a code object: %r" % (rawcode,))
self.raw = rawcode
def __eq__(self, other):
@@ -82,6 +83,7 @@ class Code(object):
argcount += raw.co_flags & CO_VARKEYWORDS
return raw.co_varnames[:argcount]
class Frame(object):
"""Wrapper around a Python frame holding f_locals and f_globals
in which expressions can be evaluated."""
@@ -119,7 +121,7 @@ class Frame(object):
"""
f_locals = self.f_locals.copy()
f_locals.update(vars)
py.builtin.exec_(code, self.f_globals, f_locals )
py.builtin.exec_(code, self.f_globals, f_locals)
def repr(self, object):
""" return a 'safe' (non-recursive, one-line) string repr for 'object'
@@ -143,6 +145,7 @@ class Frame(object):
pass # this can occur when using Psyco
return retval
class TracebackEntry(object):
""" a single entry in a traceback """
@@ -168,7 +171,7 @@ class TracebackEntry(object):
return self.lineno - self.frame.code.firstlineno
def __repr__(self):
return "<TracebackEntry %s:%d>" %(self.frame.code.path, self.lineno+1)
return "<TracebackEntry %s:%d>" % (self.frame.code.path, self.lineno + 1)
@property
def statement(self):
@@ -249,17 +252,19 @@ class TracebackEntry(object):
raise
except:
line = "???"
return " File %r:%d in %s\n %s\n" %(fn, self.lineno+1, name, line)
return " File %r:%d in %s\n %s\n" % (fn, self.lineno + 1, name, line)
def name(self):
return self.frame.code.raw.co_name
name = property(name, None, None, "co_name of underlaying code")
class Traceback(list):
""" Traceback objects encapsulate and offer higher level
access to Traceback entries.
"""
Entry = TracebackEntry
def __init__(self, tb, excinfo=None):
""" initialize from given python traceback object and ExceptionInfo """
self._excinfo = excinfo
@@ -289,7 +294,7 @@ class Traceback(list):
(excludepath is None or not hasattr(codepath, 'relto') or
not codepath.relto(excludepath)) and
(lineno is None or x.lineno == lineno) and
(firstlineno is None or x.frame.code.firstlineno == firstlineno)):
(firstlineno is None or x.frame.code.firstlineno == firstlineno)):
return Traceback(x._rawentry, self._excinfo)
return self
@@ -315,7 +320,7 @@ class Traceback(list):
""" return last non-hidden traceback entry that lead
to the exception of a traceback.
"""
for i in range(-1, -len(self)-1, -1):
for i in range(-1, -len(self) - 1, -1):
entry = self[i]
if not entry.ishidden():
return entry
@@ -330,17 +335,17 @@ class Traceback(list):
# id for the code.raw is needed to work around
# the strange metaprogramming in the decorator lib from pypi
# which generates code objects that have hash/value equality
#XXX needs a test
# XXX needs a test
key = entry.frame.code.path, id(entry.frame.code.raw), entry.lineno
#print "checking for recursion at", key
# print "checking for recursion at", key
l = cache.setdefault(key, [])
if l:
f = entry.frame
loc = f.f_locals
for otherloc in l:
if f.is_true(f.eval(co_equal,
__recursioncache_locals_1=loc,
__recursioncache_locals_2=otherloc)):
__recursioncache_locals_1=loc,
__recursioncache_locals_2=otherloc)):
return i
l.append(entry.frame.f_locals)
return None
@@ -349,6 +354,7 @@ class Traceback(list):
co_equal = compile('__recursioncache_locals_1 == __recursioncache_locals_2',
'?', 'eval')
class ExceptionInfo(object):
""" wraps sys.exc_info() objects and offers
help for navigating the traceback.
@@ -405,10 +411,10 @@ class ExceptionInfo(object):
exconly = self.exconly(tryshort=True)
entry = self.traceback.getcrashentry()
path, lineno = entry.frame.code.raw.co_filename, entry.lineno
return ReprFileLocation(path, lineno+1, exconly)
return ReprFileLocation(path, lineno + 1, exconly)
def getrepr(self, showlocals=False, style="long",
abspath=False, tbfilter=True, funcargs=False):
abspath=False, tbfilter=True, funcargs=False):
""" return str()able representation of this exception info.
showlocals: show locals per traceback entry
style: long|short|no|native traceback style
@@ -425,7 +431,7 @@ class ExceptionInfo(object):
)), self._getreprcrash())
fmt = FormattedExcinfo(showlocals=showlocals, style=style,
abspath=abspath, tbfilter=tbfilter, funcargs=funcargs)
abspath=abspath, tbfilter=tbfilter, funcargs=funcargs)
return fmt.repr_excinfo(self)
def __str__(self):
@@ -469,7 +475,7 @@ class FormattedExcinfo(object):
def _getindent(self, source):
# figure out indent for given source
try:
s = str(source.getstatement(len(source)-1))
s = str(source.getstatement(len(source) - 1))
except KeyboardInterrupt:
raise
except:
@@ -513,7 +519,7 @@ class FormattedExcinfo(object):
for line in source.lines[:line_index]:
lines.append(space_prefix + line)
lines.append(self.flow_marker + " " + source.lines[line_index])
for line in source.lines[line_index+1:]:
for line in source.lines[line_index + 1:]:
lines.append(space_prefix + line)
if excinfo is not None:
indent = 4 if short else self._getindent(source)
@@ -546,10 +552,10 @@ class FormattedExcinfo(object):
# _repr() function, which is only reprlib.Repr in
# disguise, so is very configurable.
str_repr = self._saferepr(value)
#if len(str_repr) < 70 or not isinstance(value,
# if len(str_repr) < 70 or not isinstance(value,
# (list, tuple, dict)):
lines.append("%-10s = %s" %(name, str_repr))
#else:
lines.append("%-10s = %s" % (name, str_repr))
# else:
# self._line("%-10s =\\" % (name,))
# # XXX
# py.std.pprint.pprint(value, stream=self.excinfowriter)
@@ -575,14 +581,14 @@ class FormattedExcinfo(object):
s = self.get_source(source, line_index, excinfo, short=short)
lines.extend(s)
if short:
message = "in %s" %(entry.name)
message = "in %s" % (entry.name)
else:
message = excinfo and excinfo.typename or ""
path = self._makepath(entry.path)
filelocrepr = ReprFileLocation(path, entry.lineno+1, message)
filelocrepr = ReprFileLocation(path, entry.lineno + 1, message)
localsrepr = None
if not short:
localsrepr = self.repr_locals(entry.locals)
localsrepr = self.repr_locals(entry.locals)
return ReprEntry(lines, reprargs, localsrepr, filelocrepr, style)
if excinfo:
lines.extend(self.get_exconly(excinfo, indent=4))
@@ -645,7 +651,7 @@ class FormattedExcinfo(object):
traceback = traceback[:recursionindex + 1]
else:
extraline = None
return traceback, extraline
def repr_excinfo(self, excinfo):
@@ -673,7 +679,7 @@ class FormattedExcinfo(object):
e = e.__cause__
excinfo = ExceptionInfo((type(e), e, e.__traceback__)) if e.__traceback__ else None
descr = 'The above exception was the direct cause of the following exception:'
elif e.__context__ is not None:
elif (e.__context__ is not None and not e.__suppress_context__):
e = e.__context__
excinfo = ExceptionInfo((type(e), e, e.__traceback__)) if e.__traceback__ else None
descr = 'During handling of the above exception, another exception occurred:'
@@ -699,7 +705,7 @@ class TerminalRepr(object):
return io.getvalue().strip()
def __repr__(self):
return "<%s instance at %0x>" %(self.__class__, id(self))
return "<%s instance at %0x>" % (self.__class__, id(self))
class ExceptionRepr(TerminalRepr):
@@ -743,6 +749,7 @@ class ReprExceptionInfo(ExceptionRepr):
self.reprtraceback.toterminal(tw)
super(ReprExceptionInfo, self).toterminal(tw)
class ReprTraceback(TerminalRepr):
entrysep = "_ "
@@ -758,7 +765,7 @@ class ReprTraceback(TerminalRepr):
tw.line("")
entry.toterminal(tw)
if i < len(self.reprentries) - 1:
next_entry = self.reprentries[i+1]
next_entry = self.reprentries[i + 1]
if entry.style == "long" or \
entry.style == "short" and next_entry.style == "long":
tw.sep(self.entrysep)
@@ -766,12 +773,14 @@ class ReprTraceback(TerminalRepr):
if self.extraline:
tw.line(self.extraline)
class ReprTracebackNative(ReprTraceback):
def __init__(self, tblines):
self.style = "native"
self.reprentries = [ReprEntryNative(tblines)]
self.extraline = None
class ReprEntryNative(TerminalRepr):
style = "native"
@@ -781,6 +790,7 @@ class ReprEntryNative(TerminalRepr):
def toterminal(self, tw):
tw.write("".join(self.lines))
class ReprEntry(TerminalRepr):
localssep = "_ "
@@ -797,7 +807,7 @@ class ReprEntry(TerminalRepr):
for line in self.lines:
red = line.startswith("E ")
tw.line(line, bold=True, red=red)
#tw.line("")
# tw.line("")
return
if self.reprfuncargs:
self.reprfuncargs.toterminal(tw)
@@ -805,7 +815,7 @@ class ReprEntry(TerminalRepr):
red = line.startswith("E ")
tw.line(line, bold=True, red=red)
if self.reprlocals:
#tw.sep(self.localssep, "Locals")
# tw.sep(self.localssep, "Locals")
tw.line("")
self.reprlocals.toterminal(tw)
if self.reprfileloc:
@@ -818,6 +828,7 @@ class ReprEntry(TerminalRepr):
self.reprlocals,
self.reprfileloc)
class ReprFileLocation(TerminalRepr):
def __init__(self, path, lineno, message):
self.path = str(path)
@@ -834,6 +845,7 @@ class ReprFileLocation(TerminalRepr):
tw.write(self.path, bold=True, red=True)
tw.line(":%s: %s" % (self.lineno, msg))
class ReprLocals(TerminalRepr):
def __init__(self, lines):
self.lines = lines
@@ -842,6 +854,7 @@ class ReprLocals(TerminalRepr):
for line in self.lines:
tw.line(line)
class ReprFuncArgs(TerminalRepr):
def __init__(self, args):
self.args = args
@@ -850,11 +863,11 @@ class ReprFuncArgs(TerminalRepr):
if self.args:
linesofar = ""
for name, value in self.args:
ns = "%s = %s" %(name, value)
ns = "%s = %s" % (safe_str(name), safe_str(value))
if len(ns) + len(linesofar) + 2 > tw.fullwidth:
if linesofar:
tw.line(linesofar)
linesofar = ns
linesofar = ns
else:
if linesofar:
linesofar += ", " + ns

View File

@@ -2,7 +2,8 @@ from __future__ import absolute_import, division, generators, print_function
from bisect import bisect_right
import sys
import inspect, tokenize
import inspect
import tokenize
import py
cpy_compile = compile
@@ -19,6 +20,7 @@ class Source(object):
possibly deindenting it.
"""
_compilecounter = 0
def __init__(self, *parts, **kwargs):
self.lines = lines = []
de = kwargs.get('deindent', True)
@@ -73,7 +75,7 @@ class Source(object):
start, end = 0, len(self)
while start < end and not self.lines[start].strip():
start += 1
while end > start and not self.lines[end-1].strip():
while end > start and not self.lines[end - 1].strip():
end -= 1
source = Source()
source.lines[:] = self.lines[start:end]
@@ -86,8 +88,8 @@ class Source(object):
before = Source(before)
after = Source(after)
newsource = Source()
lines = [ (indent + line) for line in self.lines]
newsource.lines = before.lines + lines + after.lines
lines = [(indent + line) for line in self.lines]
newsource.lines = before.lines + lines + after.lines
return newsource
def indent(self, indent=' ' * 4):
@@ -95,7 +97,7 @@ class Source(object):
all lines indented by the given indent-string.
"""
newsource = Source()
newsource.lines = [(indent+line) for line in self.lines]
newsource.lines = [(indent + line) for line in self.lines]
return newsource
def getstatement(self, lineno, assertion=False):
@@ -134,7 +136,8 @@ class Source(object):
try:
import parser
except ImportError:
syntax_checker = lambda x: compile(x, 'asd', 'exec')
def syntax_checker(x):
return compile(x, 'asd', 'exec')
else:
syntax_checker = parser.suite
@@ -143,8 +146,8 @@ class Source(object):
else:
source = str(self)
try:
#compile(source+'\n', "x", "exec")
syntax_checker(source+'\n')
# compile(source+'\n', "x", "exec")
syntax_checker(source + '\n')
except KeyboardInterrupt:
raise
except Exception:
@@ -164,8 +167,8 @@ class Source(object):
"""
if not filename or py.path.local(filename).check(file=0):
if _genframe is None:
_genframe = sys._getframe(1) # the caller
fn,lineno = _genframe.f_code.co_filename, _genframe.f_lineno
_genframe = sys._getframe(1) # the caller
fn, lineno = _genframe.f_code.co_filename, _genframe.f_lineno
base = "<%d-codegen " % self._compilecounter
self.__class__._compilecounter += 1
if not filename:
@@ -180,7 +183,7 @@ class Source(object):
# re-represent syntax errors from parsing python strings
msglines = self.lines[:ex.lineno]
if ex.offset:
msglines.append(" "*ex.offset + '^')
msglines.append(" " * ex.offset + '^')
msglines.append("(code was compiled probably from here: %s)" % filename)
newex = SyntaxError('\n'.join(msglines))
newex.offset = ex.offset
@@ -198,8 +201,8 @@ class Source(object):
# public API shortcut functions
#
def compile_(source, filename=None, mode='exec', flags=
generators.compiler_flag, dont_inherit=0):
def compile_(source, filename=None, mode='exec', flags=generators.compiler_flag, dont_inherit=0):
""" compile the given source to a raw code object,
and maintain an internal cache which allows later
retrieval of the source code for the code object
@@ -208,7 +211,7 @@ def compile_(source, filename=None, mode='exec', flags=
if _ast is not None and isinstance(source, _ast.AST):
# XXX should Source support having AST?
return cpy_compile(source, filename, mode, flags, dont_inherit)
_genframe = sys._getframe(1) # the caller
_genframe = sys._getframe(1) # the caller
s = Source(source)
co = s.compile(filename, mode, flags, _genframe=_genframe)
return co
@@ -245,6 +248,7 @@ def getfslineno(obj):
# helper functions
#
def findsource(obj):
try:
sourcelines, lineno = py.std.inspect.findsource(obj)
@@ -274,7 +278,7 @@ def deindent(lines, offset=None):
line = line.expandtabs()
s = line.lstrip()
if s:
offset = len(line)-len(s)
offset = len(line) - len(s)
break
else:
offset = 0
@@ -293,11 +297,11 @@ def deindent(lines, offset=None):
try:
for _, _, (sline, _), (eline, _), _ in tokenize.generate_tokens(lambda: next(it)):
if sline > len(lines):
break # End of input reached
break # End of input reached
if sline > len(newlines):
line = lines[sline - 1].expandtabs()
if line.lstrip() and line[:offset].isspace():
line = line[offset:] # Deindent
line = line[offset:] # Deindent
newlines.append(line)
for i in range(sline, eline):
@@ -337,7 +341,7 @@ def get_statement_startend2(lineno, node):
def getstatementrange_ast(lineno, source, assertion=False, astnode=None):
if astnode is None:
content = str(source)
if sys.version_info < (2,7):
if sys.version_info < (2, 7):
content += "\n"
try:
astnode = compile(content, "source", "exec", 1024) # 1024 for AST
@@ -393,7 +397,7 @@ def getstatementrange_old(lineno, source, assertion=False):
raise IndexError("likely a subclass")
if "assert" not in line and "raise" not in line:
continue
trylines = source.lines[start:lineno+1]
trylines = source.lines[start:lineno + 1]
# quick hack to prepare parsing an indented line with
# compile_command() (which errors on "return" outside defs)
trylines.insert(0, 'def xxx():')
@@ -405,10 +409,8 @@ def getstatementrange_old(lineno, source, assertion=False):
continue
# 2. find the end of the statement
for end in range(lineno+1, len(source)+1):
for end in range(lineno + 1, len(source) + 1):
trysource = source[start:end]
if trysource.isparseable():
return start, end
raise SyntaxError("no valid source range around line %d " % (lineno,))

View File

@@ -25,7 +25,6 @@ def pytest_addoption(parser):
expression information.""")
def register_assert_rewrite(*names):
"""Register one or more module names to be rewritten on import.

View File

@@ -36,10 +36,11 @@ PYC_TAIL = "." + PYTEST_TAG + PYC_EXT
REWRITE_NEWLINES = sys.version_info[:2] != (2, 7) and sys.version_info < (3, 2)
ASCII_IS_DEFAULT_ENCODING = sys.version_info[0] < 3
if sys.version_info >= (3,5):
if sys.version_info >= (3, 5):
ast_Call = ast.Call
else:
ast_Call = lambda a,b,c: ast.Call(a, b, c, None, None)
def ast_Call(a, b, c):
return ast.Call(a, b, c, None, None)
class AssertionRewritingHook(object):
@@ -215,8 +216,6 @@ class AssertionRewritingHook(object):
raise
return sys.modules[name]
def is_package(self, name):
try:
fd, fn, desc = imp.find_module(name)
@@ -261,7 +260,7 @@ def _write_pyc(state, co, source_stat, pyc):
fp = open(pyc, "wb")
except IOError:
err = sys.exc_info()[1].errno
state.trace("error writing pyc file at %s: errno=%s" %(pyc, err))
state.trace("error writing pyc file at %s: errno=%s" % (pyc, err))
# we ignore any failure to write the cache file
# there are many reasons, permission-denied, __pycache__ being a
# file etc.
@@ -283,6 +282,7 @@ N = "\n".encode("utf-8")
cookie_re = re.compile(r"^[ \t\f]*#.*coding[:=][ \t]*[-\w.]+")
BOM_UTF8 = '\xef\xbb\xbf'
def _rewrite_test(config, fn):
"""Try to read and rewrite *fn* and return the code object."""
state = config._assertstate
@@ -307,7 +307,7 @@ def _rewrite_test(config, fn):
end2 = source.find("\n", end1 + 1)
if (not source.startswith(BOM_UTF8) and
cookie_re.match(source[0:end1]) is None and
cookie_re.match(source[end1 + 1:end2]) is None):
cookie_re.match(source[end1 + 1:end2]) is None):
if hasattr(state, "_indecode"):
# encodings imported us again, so don't rewrite.
return None, None
@@ -340,6 +340,7 @@ def _rewrite_test(config, fn):
return None, None
return stat, co
def _make_rewritten_pyc(state, source_stat, pyc, co):
"""Try to dump rewritten code to *pyc*."""
if sys.platform.startswith("win"):
@@ -353,6 +354,7 @@ def _make_rewritten_pyc(state, source_stat, pyc, co):
if _write_pyc(state, co, source_stat, proc_pyc):
os.rename(proc_pyc, pyc)
def _read_pyc(source, pyc, trace=lambda x: None):
"""Possibly read a pytest pyc containing rewritten code.
@@ -410,7 +412,8 @@ def _saferepr(obj):
return repr.replace(t("\n"), t("\\n"))
from _pytest.assertion.util import format_explanation as _format_explanation # noqa
from _pytest.assertion.util import format_explanation as _format_explanation # noqa
def _format_assertmsg(obj):
"""Format the custom assertion message given.
@@ -439,9 +442,11 @@ def _format_assertmsg(obj):
s = s.replace(t("\\n"), t("\n~"))
return s
def _should_repr_global_name(obj):
return not hasattr(obj, "__name__") and not py.builtin.callable(obj)
def _format_boolop(explanations, is_or):
explanation = "(" + (is_or and " or " or " and ").join(explanations) + ")"
if py.builtin._istext(explanation):
@@ -450,6 +455,7 @@ def _format_boolop(explanations, is_or):
t = py.builtin.bytes
return explanation.replace(t('%'), t('%%'))
def _call_reprcompare(ops, results, expls, each_obj):
for i, res, expl in zip(range(len(ops)), results, expls):
try:
@@ -483,7 +489,7 @@ binop_map = {
ast.Mult: "*",
ast.Div: "/",
ast.FloorDiv: "//",
ast.Mod: "%%", # escaped for string formatting
ast.Mod: "%%", # escaped for string formatting
ast.Eq: "==",
ast.NotEq: "!=",
ast.Lt: "<",
@@ -723,7 +729,7 @@ class AssertionRewriter(ast.NodeVisitor):
if isinstance(assert_.test, ast.Tuple) and self.config is not None:
fslocation = (self.module_path, assert_.lineno)
self.config.warn('R1', 'assertion is always true, perhaps '
'remove parentheses?', fslocation=fslocation)
'remove parentheses?', fslocation=fslocation)
self.statements = []
self.variables = []
self.variable_counter = itertools.count()
@@ -787,7 +793,7 @@ class AssertionRewriter(ast.NodeVisitor):
if i:
fail_inner = []
# cond is set in a prior loop iteration below
self.on_failure.append(ast.If(cond, fail_inner, [])) # noqa
self.on_failure.append(ast.If(cond, fail_inner, [])) # noqa
self.on_failure = fail_inner
self.push_format_context()
res, expl = self.visit(v)
@@ -839,7 +845,7 @@ class AssertionRewriter(ast.NodeVisitor):
new_kwargs.append(ast.keyword(keyword.arg, res))
if keyword.arg:
arg_expls.append(keyword.arg + "=" + expl)
else: ## **args have `arg` keywords with an .arg of None
else: # **args have `arg` keywords with an .arg of None
arg_expls.append("**" + expl)
expl = "%s(%s)" % (func_expl, ', '.join(arg_expls))
@@ -893,7 +899,6 @@ class AssertionRewriter(ast.NodeVisitor):
else:
visit_Call = visit_Call_legacy
def visit_Attribute(self, attr):
if not isinstance(attr.ctx, ast.Load):
return self.generic_visit(attr)

View File

@@ -82,7 +82,7 @@ def _format_lines(lines):
stack.append(len(result))
stackcnt[-1] += 1
stackcnt.append(0)
result.append(u(' +') + u(' ')*(len(stack)-1) + s + line[1:])
result.append(u(' +') + u(' ') * (len(stack) - 1) + s + line[1:])
elif line.startswith('}'):
stack.pop()
stackcnt.pop()
@@ -91,7 +91,7 @@ def _format_lines(lines):
assert line[0] in ['~', '>']
stack[-1] += 1
indent = len(stack) if line.startswith('~') else len(stack) - 1
result.append(u(' ')*indent + line[1:])
result.append(u(' ') * indent + line[1:])
assert len(stack) == 1
return result
@@ -106,16 +106,22 @@ except NameError:
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
left_repr = py.io.saferepr(left, maxsize=int(width//2))
right_repr = py.io.saferepr(right, maxsize=width-len(left_repr))
left_repr = py.io.saferepr(left, maxsize=int(width // 2))
right_repr = py.io.saferepr(right, maxsize=width - len(left_repr))
summary = u('%s %s %s') % (ecu(left_repr), op, ecu(right_repr))
issequence = lambda x: (isinstance(x, (list, tuple, Sequence)) and
not isinstance(x, basestring))
istext = lambda x: isinstance(x, basestring)
isdict = lambda x: isinstance(x, dict)
isset = lambda x: isinstance(x, (set, frozenset))
def issequence(x):
return (isinstance(x, (list, tuple, 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 isiterable(obj):
try:
@@ -285,7 +291,7 @@ def _compare_eq_dict(left, right, verbose=False):
def _notin_text(term, text, verbose=False):
index = text.find(term)
head = text[:index]
tail = text[index+len(term):]
tail = text[index + len(term):]
correct_text = head + tail
diff = _diff_text(correct_text, text, verbose)
newdiff = [u('%s is contained here:') % py.io.saferepr(term, maxsize=42)]

View File

@@ -8,13 +8,14 @@ from __future__ import absolute_import, division, print_function
import py
import pytest
import json
import os
from os.path import sep as _sep, altsep as _altsep
class Cache(object):
def __init__(self, config):
self.config = config
self._cachedir = config.rootdir.join(".cache")
self._cachedir = Cache.cache_dir_from_config(config)
self.trace = config.trace.root.get("cache")
if config.getvalue("cacheclear"):
self.trace("clearing cachedir")
@@ -22,6 +23,16 @@ class Cache(object):
self._cachedir.remove()
self._cachedir.mkdir()
@staticmethod
def cache_dir_from_config(config):
cache_dir = config.getini("cache_dir")
cache_dir = os.path.expanduser(cache_dir)
cache_dir = os.path.expandvars(cache_dir)
if os.path.isabs(cache_dir):
return py.path.local(cache_dir)
else:
return config.rootdir.join(cache_dir)
def makedir(self, name):
""" return a directory path object with the given name. If the
directory does not yet exist, it will be created. You can use it
@@ -89,31 +100,31 @@ class Cache(object):
class LFPlugin:
""" Plugin which implements the --lf (run last-failing) option """
def __init__(self, config):
self.config = config
active_keys = 'lf', 'failedfirst'
self.active = any(config.getvalue(key) for key in active_keys)
if self.active:
self.lastfailed = config.cache.get("cache/lastfailed", {})
else:
self.lastfailed = {}
self.lastfailed = config.cache.get("cache/lastfailed", {})
self._previously_failed_count = None
def pytest_report_header(self):
def pytest_report_collectionfinish(self):
if self.active:
if not self.lastfailed:
if not self._previously_failed_count:
mode = "run all (no recorded failures)"
else:
mode = "rerun last %d failures%s" % (
len(self.lastfailed),
" first" if self.config.getvalue("failedfirst") else "")
noun = 'failure' if self._previously_failed_count == 1 else 'failures'
suffix = " first" if self.config.getvalue("failedfirst") else ""
mode = "rerun previous {count} {noun}{suffix}".format(
count=self._previously_failed_count, suffix=suffix, noun=noun
)
return "run-last-failure: %s" % mode
def pytest_runtest_logreport(self, report):
if report.failed and "xfail" not in report.keywords:
if (report.when == 'call' and report.passed) or report.skipped:
self.lastfailed.pop(report.nodeid, None)
elif report.failed:
self.lastfailed[report.nodeid] = True
elif not report.failed:
if report.when == "call":
self.lastfailed.pop(report.nodeid, None)
def pytest_collectreport(self, report):
passed = report.outcome in ('passed', 'skipped')
@@ -135,11 +146,12 @@ class LFPlugin:
previously_failed.append(item)
else:
previously_passed.append(item)
if not previously_failed and previously_passed:
self._previously_failed_count = len(previously_failed)
if not previously_failed:
# running a subset of all tests with recorded failures outside
# of the set of tests currently executing
pass
elif self.config.getvalue("lf"):
return
if self.config.getvalue("lf"):
items[:] = previously_failed
config.hook.pytest_deselected(items=previously_passed)
else:
@@ -149,8 +161,9 @@ class LFPlugin:
config = self.config
if config.getvalue("cacheshow") or hasattr(config, "slaveinput"):
return
prev_failed = config.cache.get("cache/lastfailed", None) is not None
if (session.testscollected and prev_failed) or self.lastfailed:
saved_lastfailed = config.cache.get("cache/lastfailed", {})
if saved_lastfailed != self.lastfailed:
config.cache.set("cache/lastfailed", self.lastfailed)
@@ -171,6 +184,9 @@ def pytest_addoption(parser):
group.addoption(
'--cache-clear', action='store_true', dest="cacheclear",
help="remove all cache contents at start of test run.")
parser.addini(
"cache_dir", default='.cache',
help="cache directory path.")
def pytest_cmdline_main(config):
@@ -179,7 +195,6 @@ def pytest_cmdline_main(config):
return wrap_session(config, cacheshow)
@pytest.hookimpl(tryfirst=True)
def pytest_configure(config):
config.cache = Cache(config)
@@ -224,7 +239,7 @@ def cacheshow(config, session):
val = config.cache.get(key, dummy)
if val is dummy:
tw.line("%s contains unreadable content, "
"will be ignored" % key)
"will be ignored" % key)
else:
tw.line("%s contains:" % key)
stream = py.io.TextIO()
@@ -236,7 +251,7 @@ def cacheshow(config, session):
if ddir.isdir() and ddir.listdir():
tw.sep("-", "cache directories")
for p in sorted(basedir.join("d").visit()):
#if p.check(dir=1):
# if p.check(dir=1):
# print("%s/" % p.relto(basedir))
if p.isfile():
key = p.relto(basedir)

View File

@@ -36,7 +36,8 @@ def pytest_addoption(parser):
def pytest_load_initial_conftests(early_config, parser, args):
ns = early_config.known_args_namespace
if ns.capture == "fd":
_py36_windowsconsoleio_workaround()
_py36_windowsconsoleio_workaround(sys.stdout)
_colorama_workaround()
_readline_workaround()
pluginmanager = early_config.pluginmanager
capman = CaptureManager(ns.capture)
@@ -134,7 +135,7 @@ class CaptureManager:
self.resumecapture()
self.activate_funcargs(item)
yield
#self.deactivate_funcargs() called from suspendcapture()
# self.deactivate_funcargs() called from suspendcapture()
self.suspendcapture_item(item, "call")
@pytest.hookimpl(hookwrapper=True)
@@ -171,6 +172,7 @@ def capsys(request):
request.node._capfuncarg = c = CaptureFixture(SysCapture, request)
return c
@pytest.fixture
def capfd(request):
"""Enable capturing of writes to file descriptors 1 and 2 and make
@@ -238,6 +240,7 @@ def safe_text_dupfile(f, mode, default_encoding="UTF8"):
class EncodedFile(object):
errors = "strict" # possibly needed by py3 code (issue555)
def __init__(self, buffer, encoding):
self.buffer = buffer
self.encoding = encoding
@@ -251,6 +254,11 @@ class EncodedFile(object):
data = ''.join(linelist)
self.write(data)
@property
def name(self):
"""Ensure that file.name is a string."""
return repr(self.buffer)
def __getattr__(self, name):
return getattr(object.__getattribute__(self, "buffer"), name)
@@ -318,9 +326,11 @@ class MultiCapture(object):
return (self.out.snap() if self.out is not None else "",
self.err.snap() if self.err is not None else "")
class NoCapture:
__init__ = start = done = suspend = resume = lambda *args: None
class FDCapture:
""" Capture IO to/from a given os-level filedescriptor. """
@@ -393,7 +403,7 @@ class FDCapture:
def writeorg(self, data):
""" write to original file descriptor. """
if py.builtin._istext(data):
data = data.encode("utf8") # XXX use encoding of original stream
data = data.encode("utf8") # XXX use encoding of original stream
os.write(self.targetfd_save, data)
@@ -463,12 +473,30 @@ class DontReadFromInput:
@property
def buffer(self):
if sys.version_info >= (3,0):
if sys.version_info >= (3, 0):
return self
else:
raise AttributeError('redirected stdin has no attribute buffer')
def _colorama_workaround():
"""
Ensure colorama is imported so that it attaches to the correct stdio
handles on Windows.
colorama uses the terminal on import time. So if something does the
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
def _readline_workaround():
"""
Ensure readline is imported so that it attaches to the correct stdio
@@ -496,7 +524,7 @@ def _readline_workaround():
pass
def _py36_windowsconsoleio_workaround():
def _py36_windowsconsoleio_workaround(stream):
"""
Python 3.6 implemented unicode console handling for Windows. This works
by reading/writing to the raw console handle using
@@ -513,13 +541,20 @@ def _py36_windowsconsoleio_workaround():
also means a different handle by replicating the logic in
"Py_lifecycle.c:initstdio/create_stdio".
:param stream: in practice ``sys.stdout`` or ``sys.stderr``, but given
here as parameter for unittesting purposes.
See https://github.com/pytest-dev/py/issues/103
"""
if not sys.platform.startswith('win32') or sys.version_info[:2] < (3, 6):
return
buffered = hasattr(sys.stdout.buffer, 'raw')
raw_stdout = sys.stdout.buffer.raw if buffered else sys.stdout.buffer
# bail out if ``stream`` doesn't seem like a proper ``io`` stream (#2666)
if not hasattr(stream, 'buffer'):
return
buffered = hasattr(stream.buffer, 'raw')
raw_stdout = stream.buffer.raw if buffered else stream.buffer
if not isinstance(raw_stdout, io._WindowsConsoleIO):
return

View File

@@ -10,8 +10,8 @@ import functools
import py
import _pytest
import _pytest
from _pytest.outcomes import TEST_OUTCOME
try:
@@ -59,7 +59,7 @@ def iscoroutinefunction(func):
which in turns also initializes the "logging" module as side-effect (see issue #8).
"""
return (getattr(func, '_is_coroutine', False) or
(hasattr(inspect, 'iscoroutinefunction') and inspect.iscoroutinefunction(func)))
(hasattr(inspect, 'iscoroutinefunction') and inspect.iscoroutinefunction(func)))
def getlocation(function, curdir):
@@ -68,7 +68,7 @@ def getlocation(function, curdir):
lineno = py.builtin._getcode(function).co_firstlineno
if fn.relto(curdir):
fn = fn.relto(curdir)
return "%s:%d" %(fn, lineno+1)
return "%s:%d" % (fn, lineno + 1)
def num_mock_patch_args(function):
@@ -79,13 +79,21 @@ def num_mock_patch_args(function):
mock = sys.modules.get("mock", sys.modules.get("unittest.mock", None))
if mock is not None:
return len([p for p in patchings
if not p.attribute_name and p.new is mock.DEFAULT])
if not p.attribute_name and p.new is mock.DEFAULT])
return len(patchings)
def getfuncargnames(function, startindex=None):
def getfuncargnames(function, startindex=None, cls=None):
"""
@RonnyPfannschmidt: This function should be refactored when we revisit fixtures. The
fixture mechanism should ask the node for the fixture names, and not try to obtain
directly from the function object well after collection has occurred.
"""
if startindex is None and cls is not None:
is_staticmethod = isinstance(cls.__dict__.get(function.__name__, None), staticmethod)
startindex = 0 if is_staticmethod else 1
# XXX merge with main.py's varnames
#assert not isclass(function)
# assert not isclass(function)
realfunction = function
while hasattr(realfunction, "__wrapped__"):
realfunction = realfunction.__wrapped__
@@ -111,8 +119,7 @@ def getfuncargnames(function, startindex=None):
return tuple(argnames[startindex:])
if sys.version_info[:2] == (2, 6):
if sys.version_info[:2] == (2, 6):
def isclass(object):
""" Return true if the object is a class. Overrides inspect.isclass for
python 2.6 because it will return True for objects which always return
@@ -125,10 +132,11 @@ if sys.version_info[:2] == (2, 6):
if _PY3:
import codecs
imap = map
izip = zip
STRING_TYPES = bytes, str
UNICODE_TYPES = str,
def _escape_strings(val):
def _ascii_escaped(val):
"""If val is pure ascii, returns it as a str(). Otherwise, escapes
bytes objects into a sequence of escaped bytes:
@@ -160,9 +168,9 @@ else:
STRING_TYPES = bytes, str, unicode
UNICODE_TYPES = unicode,
from itertools import imap # NOQA
from itertools import imap, izip # NOQA
def _escape_strings(val):
def _ascii_escaped(val):
"""In py2 bytes and str are the same type, so return if it's a bytes
object, return it unchanged if it is a full ascii string,
otherwise escape it into its binary form.
@@ -222,14 +230,16 @@ def getimfunc(func):
def safe_getattr(object, name, default):
""" Like getattr but return default upon any Exception.
""" Like getattr but return default upon any Exception or any OutcomeException.
Attribute access can potentially fail for 'evil' Python objects.
See issue #214.
It catches OutcomeException because of #2490 (issue #580), new outcomes are derived from BaseException
instead of Exception (for more details check #2707)
"""
try:
return getattr(object, name, default)
except Exception:
except TEST_OUTCOME:
return default
@@ -283,7 +293,15 @@ def _setup_collect_fakemodule():
if _PY2:
from py.io import TextIO as CaptureIO
# Without this the test_dupfile_on_textio will fail, otherwise CaptureIO could directly inherit from StringIO.
from py.io import TextIO
class CaptureIO(TextIO):
@property
def encoding(self):
return getattr(self, '_encoding', 'UTF-8')
else:
import io
@@ -297,6 +315,7 @@ else:
def getvalue(self):
return self.buffer.getvalue().decode('UTF-8')
class FuncargnamesCompatAttr(object):
""" helper class so that Metafunc, Function and FixtureRequest
don't need to each define the "funcargnames" compatibility attribute.

View File

@@ -60,9 +60,10 @@ def main(args=None, plugins=None):
config._ensure_unconfigure()
except UsageError as e:
for msg in e.args:
sys.stderr.write("ERROR: %s\n" %(msg,))
sys.stderr.write("ERROR: %s\n" % (msg,))
return 4
class cmdline: # compatibility namespace
main = staticmethod(main)
@@ -102,10 +103,10 @@ def directory_arg(path, optname):
_preinit = []
default_plugins = (
"mark main terminal runner python fixtures debugging unittest capture skipping "
"tmpdir monkeypatch recwarn pastebin helpconfig nose assertion "
"junitxml resultlog doctest cacheprovider freeze_support "
"setuponly setupplan warnings").split()
"mark main terminal runner python fixtures debugging unittest capture skipping "
"tmpdir monkeypatch recwarn pastebin helpconfig nose assertion "
"junitxml resultlog doctest cacheprovider freeze_support "
"setuponly setupplan warnings").split()
builtin_plugins = set(default_plugins)
@@ -116,6 +117,7 @@ def _preloadplugins():
assert not _preinit
_preinit.append(get_config())
def get_config():
if _preinit:
return _preinit.pop(0)
@@ -126,6 +128,7 @@ def get_config():
pluginmanager.import_plugin(spec)
return config
def get_plugin_manager():
"""
Obtain a new instance of the
@@ -137,6 +140,7 @@ def get_plugin_manager():
"""
return get_config().pluginmanager
def _prepareconfig(args=None, plugins=None):
warning = None
if args is None:
@@ -161,7 +165,7 @@ def _prepareconfig(args=None, plugins=None):
if warning:
config.warn('C1', warning)
return pluginmanager.hook.pytest_cmdline_parse(
pluginmanager=pluginmanager, args=args)
pluginmanager=pluginmanager, args=args)
except BaseException:
config._ensure_unconfigure()
raise
@@ -176,6 +180,7 @@ class PytestPluginManager(PluginManager):
``pytest_plugins`` global variables found in plugins being loaded;
* ``conftest.py`` loading during start-up;
"""
def __init__(self):
super(PytestPluginManager, self).__init__("pytest", implprefix="pytest_")
self._conftest_plugins = set()
@@ -206,7 +211,8 @@ class PytestPluginManager(PluginManager):
"""
.. deprecated:: 2.8
Use :py:meth:`pluggy.PluginManager.add_hookspecs <_pytest.vendored_packages.pluggy.PluginManager.add_hookspecs>` instead.
Use :py:meth:`pluggy.PluginManager.add_hookspecs <_pytest.vendored_packages.pluggy.PluginManager.add_hookspecs>`
instead.
"""
warning = dict(code="I2",
fslocation=_pytest._code.getfslineno(sys._getframe(1)),
@@ -235,7 +241,7 @@ class PytestPluginManager(PluginManager):
def parse_hookspec_opts(self, module_or_class, name):
opts = super(PytestPluginManager, self).parse_hookspec_opts(
module_or_class, name)
module_or_class, name)
if opts is None:
method = getattr(module_or_class, name)
if name.startswith("pytest_"):
@@ -258,7 +264,7 @@ class PytestPluginManager(PluginManager):
ret = super(PytestPluginManager, self).register(plugin, name)
if ret:
self.hook.pytest_plugin_registered.call_historic(
kwargs=dict(plugin=plugin, manager=self))
kwargs=dict(plugin=plugin, manager=self))
if isinstance(plugin, types.ModuleType):
self.consider_module(plugin)
@@ -276,11 +282,11 @@ class PytestPluginManager(PluginManager):
# XXX now that the pluginmanager exposes hookimpl(tryfirst...)
# we should remove tryfirst/trylast as markers
config.addinivalue_line("markers",
"tryfirst: mark a hook implementation function such that the "
"plugin machinery will try to call it first/as early as possible.")
"tryfirst: mark a hook implementation function such that the "
"plugin machinery will try to call it first/as early as possible.")
config.addinivalue_line("markers",
"trylast: mark a hook implementation function such that the "
"plugin machinery will try to call it last/as late as possible.")
"trylast: mark a hook implementation function such that the "
"plugin machinery will try to call it last/as late as possible.")
def _warn(self, message):
kwargs = message if isinstance(message, dict) else {
@@ -304,7 +310,7 @@ class PytestPluginManager(PluginManager):
"""
current = py.path.local()
self._confcutdir = current.join(namespace.confcutdir, abs=True) \
if namespace.confcutdir else None
if namespace.confcutdir else None
self._noconftest = namespace.noconftest
testpaths = namespace.file_or_dir
foundanchor = False
@@ -315,7 +321,7 @@ class PytestPluginManager(PluginManager):
if i != -1:
path = path[:i]
anchor = current.join(path, abs=1)
if exists(anchor): # we found some file object
if exists(anchor): # we found some file object
self._try_load_conftest(anchor)
foundanchor = True
if not foundanchor:
@@ -382,7 +388,7 @@ class PytestPluginManager(PluginManager):
if path and path.relto(dirpath) or path == dirpath:
assert mod not in mods
mods.append(mod)
self.trace("loaded conftestmodule %r" %(mod))
self.trace("loaded conftestmodule %r" % (mod))
self.consider_conftest(mod)
return mod
@@ -392,7 +398,7 @@ class PytestPluginManager(PluginManager):
#
def consider_preparse(self, args):
for opt1,opt2 in zip(args, args[1:]):
for opt1, opt2 in zip(args, args[1:]):
if opt1 == "-p":
self.consider_pluginarg(opt2)
@@ -446,7 +452,7 @@ class PytestPluginManager(PluginManager):
import pytest
if not hasattr(pytest, 'skip') or not isinstance(e, pytest.skip.Exception):
raise
self._warn("skipped plugin %r: %s" %((modname, e.msg)))
self._warn("skipped plugin %r: %s" % ((modname, e.msg)))
else:
mod = sys.modules[importspec]
self.register(mod, modname)
@@ -511,7 +517,7 @@ class Parser:
for i, grp in enumerate(self._groups):
if grp.name == after:
break
self._groups.insert(i+1, group)
self._groups.insert(i + 1, group)
return group
def addoption(self, *opts, **attrs):
@@ -549,7 +555,7 @@ class Parser:
a = option.attrs()
arggroup.add_argument(*n, **a)
# bash like autocompletion for dirs (appending '/')
optparser.add_argument(FILE_OR_DIR, nargs='*').completer=filescompleter
optparser.add_argument(FILE_OR_DIR, nargs='*').completer = filescompleter
return optparser
def parse_setoption(self, args, option, namespace=None):
@@ -693,7 +699,7 @@ class Argument:
if self._attrs.get('help'):
a = self._attrs['help']
a = a.replace('%default', '%(default)s')
#a = a.replace('%prog', '%(prog)s')
# a = a.replace('%prog', '%(prog)s')
self._attrs['help'] = a
return self._attrs
@@ -777,7 +783,7 @@ class MyOptionParser(argparse.ArgumentParser):
extra_info = {}
self._parser = parser
argparse.ArgumentParser.__init__(self, usage=parser._usage,
add_help=False, formatter_class=DropShorterLongHelpFormatter)
add_help=False, formatter_class=DropShorterLongHelpFormatter)
# extra_info is a dict of (param -> value) to display if there's
# an usage error to provide more contextual information to the user
self.extra_info = extra_info
@@ -805,9 +811,10 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter):
- shortcut if there are only two options and one of them is a short one
- cache result on action object as this is called at least 2 times
"""
def _format_action_invocation(self, action):
orgstr = argparse.HelpFormatter._format_action_invocation(self, action)
if orgstr and orgstr[0] != '-': # only optional arguments
if orgstr and orgstr[0] != '-': # only optional arguments
return orgstr
res = getattr(action, '_formatted_action_invocation', None)
if res:
@@ -818,7 +825,7 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter):
action._formatted_action_invocation = orgstr
return orgstr
return_list = []
option_map = getattr(action, 'map_long_option', {})
option_map = getattr(action, 'map_long_option', {})
if option_map is None:
option_map = {}
short_long = {}
@@ -836,7 +843,7 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter):
short_long[shortened] = xxoption
# now short_long has been filled out to the longest with dashes
# **and** we keep the right option ordering from add_argument
for option in options: #
for option in options:
if len(option) == 2 or option[2] == ' ':
return_list.append(option)
if option[2:] == short_long.get(option.replace('-', '')):
@@ -845,22 +852,26 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter):
return action._formatted_action_invocation
def _ensure_removed_sysmodule(modname):
try:
del sys.modules[modname]
except KeyError:
pass
class CmdOptions(object):
""" holds cmdline options as attributes."""
def __init__(self, values=()):
self.__dict__.update(values)
def __repr__(self):
return "<CmdOptions %r>" %(self.__dict__,)
return "<CmdOptions %r>" % (self.__dict__,)
def copy(self):
return CmdOptions(self.__dict__)
class Notset:
def __repr__(self):
return "<NOTSET>"
@@ -870,6 +881,18 @@ notset = Notset()
FILE_OR_DIR = 'file_or_dir'
def _iter_rewritable_modules(package_files):
for fn in package_files:
is_simple_module = '/' not in fn and fn.endswith('.py')
is_package = fn.count('/') == 1 and fn.endswith('__init__.py')
if is_simple_module:
module_name, _ = os.path.splitext(fn)
yield module_name
elif is_package:
package_name = os.path.dirname(fn)
yield package_name
class Config(object):
""" access to configuration values, pluginmanager and plugin hooks. """
@@ -940,14 +963,14 @@ class Config(object):
else:
style = "native"
excrepr = excinfo.getrepr(funcargs=True,
showlocals=getattr(option, 'showlocals', False),
style=style,
)
showlocals=getattr(option, 'showlocals', False),
style=style,
)
res = self.hook.pytest_internalerror(excrepr=excrepr,
excinfo=excinfo)
if not py.builtin.any(res):
for line in str(excrepr).split("\n"):
sys.stderr.write("INTERNALERROR> %s\n" %line)
sys.stderr.write("INTERNALERROR> %s\n" % line)
sys.stderr.flush()
def cwd_relative_nodeid(self, nodeid):
@@ -1030,15 +1053,8 @@ class Config(object):
for entry in entrypoint.dist._get_metadata(metadata)
)
for fn in package_files:
is_simple_module = os.sep not in fn and fn.endswith('.py')
is_package = fn.count(os.sep) == 1 and fn.endswith('__init__.py')
if is_simple_module:
module_name, ext = os.path.splitext(fn)
hook.mark_rewrite(module_name)
elif is_package:
package_name = os.path.dirname(fn)
hook.mark_rewrite(package_name)
for name in _iter_rewritable_modules(package_files):
hook.mark_rewrite(name)
def _warn_about_missing_assertion(self, mode):
try:
@@ -1068,13 +1084,12 @@ class Config(object):
self.pluginmanager.load_setuptools_entrypoints('pytest11')
self.pluginmanager.consider_env()
self.known_args_namespace = ns = self._parser.parse_known_args(args, namespace=self.option.copy())
confcutdir = self.known_args_namespace.confcutdir
if self.known_args_namespace.confcutdir is None and self.inifile:
confcutdir = py.path.local(self.inifile).dirname
self.known_args_namespace.confcutdir = confcutdir
try:
self.hook.pytest_load_initial_conftests(early_config=self,
args=args, parser=self._parser)
args=args, parser=self._parser)
except ConftestImportFailure:
e = sys.exc_info()[1]
if ns.help or ns.version:
@@ -1092,17 +1107,17 @@ class Config(object):
myver = pytest.__version__.split(".")
if myver < ver:
raise pytest.UsageError(
"%s:%d: requires pytest-%s, actual pytest-%s'" %(
self.inicfg.config.path, self.inicfg.lineof('minversion'),
minver, pytest.__version__))
"%s:%d: requires pytest-%s, actual pytest-%s'" % (
self.inicfg.config.path, self.inicfg.lineof('minversion'),
minver, pytest.__version__))
def parse(self, args, addopts=True):
# parse given cmdline arguments into this config object.
assert not hasattr(self, 'args'), (
"can only parse cmdline args at most once per Config object")
"can only parse cmdline args at most once per Config object")
self._origargs = args
self.hook.pytest_addhooks.call_historic(
kwargs=dict(pluginmanager=self.pluginmanager))
kwargs=dict(pluginmanager=self.pluginmanager))
self._preparse(args, addopts=addopts)
# XXX deprecated hook:
self.hook.pytest_cmdline_preparse(config=self, args=args)
@@ -1125,7 +1140,7 @@ class Config(object):
the first line in its value. """
x = self.getini(name)
assert isinstance(x, list)
x.append(line) # modifies the cached list inline
x.append(line) # modifies the cached list inline
def getini(self, name):
""" return configuration value from an :ref:`ini file <inifiles>`. If the
@@ -1142,7 +1157,7 @@ class Config(object):
try:
description, type, default = self._parser._inidict[name]
except KeyError:
raise ValueError("unknown configuration value: %r" %(name,))
raise ValueError("unknown configuration value: %r" % (name,))
value = self._get_override_ini_value(name)
if value is None:
try:
@@ -1219,7 +1234,7 @@ class Config(object):
return default
if skip:
import pytest
pytest.skip("no %r option found" %(name,))
pytest.skip("no %r option found" % (name,))
raise ValueError("no option named %r" % (name,))
def getvalue(self, name, path=None):
@@ -1230,12 +1245,14 @@ class Config(object):
""" (deprecated, use getoption(skip=True)) """
return self.getoption(name, skip=True)
def exists(path, ignore=EnvironmentError):
try:
return path.check()
except ignore:
return False
def getcfg(args, warnfunc=None):
"""
Search the list of arguments for a valid ini-file for pytest,
@@ -1339,7 +1356,7 @@ def determine_setup(inifile, args, warnfunc=None):
rootdir, inifile, inicfg = getcfg(dirs, warnfunc=warnfunc)
if rootdir is None:
rootdir = get_common_ancestor([py.path.local(), ancestor])
is_fs_root = os.path.splitdrive(str(rootdir))[1] == os.sep
is_fs_root = os.path.splitdrive(str(rootdir))[1] == '/'
if is_fs_root:
rootdir = ancestor
return rootdir, inifile, inicfg or {}
@@ -1361,7 +1378,7 @@ def setns(obj, dic):
else:
setattr(obj, name, value)
obj.__all__.append(name)
#if obj != pytest:
# if obj != pytest:
# pytest.__all__.append(name)
setattr(pytest, name, value)

View File

@@ -4,7 +4,6 @@ import pdb
import sys
def pytest_addoption(parser):
group = parser.getgroup("general")
group._addoption(
@@ -40,6 +39,7 @@ def pytest_configure(config):
pytestPDB._pdb_cls = pdb_cls
config._cleanup.append(fin)
class pytestPDB:
""" Pseudo PDB that defers to the real pdb. """
_pluginmanager = None

View File

@@ -7,8 +7,13 @@ be removed when the time comes.
"""
from __future__ import absolute_import, division, print_function
class RemovedInPytest4Warning(DeprecationWarning):
"""warning class for features removed in pytest 4.0"""
MAIN_STR_ARGS = 'passing a string to pytest.main() is deprecated, ' \
'pass a list of arguments instead.'
'pass a list of arguments instead.'
YIELD_TESTS = 'yield tests are deprecated, and scheduled to be removed in pytest 4.0'
@@ -21,4 +26,17 @@ SETUP_CFG_PYTEST = '[pytest] section in setup.cfg files is deprecated, use [tool
GETFUNCARGVALUE = "use of getfuncargvalue is deprecated, use getfixturevalue"
RESULT_LOG = '--result-log is deprecated and scheduled for removal in pytest 4.0'
RESULT_LOG = (
'--result-log is deprecated and scheduled for removal in pytest 4.0.\n'
'See https://docs.pytest.org/en/latest/usage.html#creating-resultlog-format-files for more information.'
)
MARK_INFO_ATTRIBUTE = RemovedInPytest4Warning(
"MarkInfo objects are deprecated as they contain the merged marks"
)
MARK_PARAMETERSET_UNPACKING = RemovedInPytest4Warning(
"Applying marks directly to parameters is deprecated,"
" please use pytest.param(..., marks=...) instead.\n"
"For more details, see: https://docs.pytest.org/en/latest/parametrize.html"
)

View File

@@ -22,28 +22,29 @@ DOCTEST_REPORT_CHOICES = (
DOCTEST_REPORT_CHOICE_ONLY_FIRST_FAILURE,
)
def pytest_addoption(parser):
parser.addini('doctest_optionflags', 'option flags for doctests',
type="args", default=["ELLIPSIS"])
type="args", default=["ELLIPSIS"])
parser.addini("doctest_encoding", 'encoding used for doctest files', default="utf-8")
group = parser.getgroup("collect")
group.addoption("--doctest-modules",
action="store_true", default=False,
help="run doctests in all .py modules",
dest="doctestmodules")
action="store_true", default=False,
help="run doctests in all .py modules",
dest="doctestmodules")
group.addoption("--doctest-report",
type=str.lower, default="udiff",
help="choose another output format for diffs on doctest failure",
choices=DOCTEST_REPORT_CHOICES,
dest="doctestreport")
type=str.lower, default="udiff",
help="choose another output format for diffs on doctest failure",
choices=DOCTEST_REPORT_CHOICES,
dest="doctestreport")
group.addoption("--doctest-glob",
action="append", default=[], metavar="pat",
help="doctests file matching pattern, default: test*.txt",
dest="doctestglob")
action="append", default=[], metavar="pat",
help="doctests file matching pattern, default: test*.txt",
dest="doctestglob")
group.addoption("--doctest-ignore-import-errors",
action="store_true", default=False,
help="ignore doctest ImportErrors",
dest="doctest_ignore_import_errors")
action="store_true", default=False,
help="ignore doctest ImportErrors",
dest="doctest_ignore_import_errors")
def pytest_collect_file(path, parent):
@@ -128,18 +129,18 @@ class DoctestItem(pytest.Item):
indent = '...'
if excinfo.errisinstance(doctest.DocTestFailure):
lines += checker.output_difference(example,
doctestfailure.got, report_choice).split("\n")
doctestfailure.got, report_choice).split("\n")
else:
inner_excinfo = ExceptionInfo(excinfo.value.exc_info)
lines += ["UNEXPECTED EXCEPTION: %s" %
repr(inner_excinfo.value)]
repr(inner_excinfo.value)]
lines += traceback.format_exception(*excinfo.value.exc_info)
return ReprFailDoctest(reprlocation, lines)
else:
return super(DoctestItem, self).repr_failure(excinfo)
def reportinfo(self):
return self.fspath, None, "[doctest] %s" % self.name
return self.fspath, self.dtest.lineno, "[doctest] %s" % self.name
def _get_flag_lookup():
@@ -163,6 +164,7 @@ def get_optionflags(parent):
flag_acc |= flag_lookup_table[flag]
return flag_acc
class DoctestTextfile(pytest.Module):
obj = None
@@ -332,7 +334,7 @@ def _fix_spoof_python2(runner, encoding):
should patch only doctests for text files because they don't have a way to declare their
encoding. Doctests in docstrings from Python modules don't have the same problem given that
Python already decoded the strings.
This fixes the problem related in issue #2434.
"""
from _pytest.compat import _PY2

View File

@@ -16,9 +16,15 @@ from _pytest.compat import (
getlocation, getfuncargnames,
safe_getattr,
)
from _pytest.runner import fail
from _pytest.outcomes import fail, TEST_OUTCOME
from _pytest.compat import FuncargnamesCompatAttr
if sys.version_info[:2] == (2, 6):
from ordereddict import OrderedDict
else:
from collections import OrderedDict
def pytest_sessionstart(session):
import _pytest.python
scopename2class.update({
@@ -38,6 +44,7 @@ scope2props["class"] = scope2props["module"] + ("cls",)
scope2props["instance"] = scope2props["class"] + ("instance", )
scope2props["function"] = scope2props["instance"] + ("function", "keywords")
def scopeproperty(name=None, doc=None):
def decoratescope(func):
scopename = name or func.__name__
@@ -69,7 +76,7 @@ def add_funcarg_pseudo_fixture_def(collector, metafunc, fixturemanager):
# XXX we can probably avoid this algorithm if we modify CallSpec2
# to directly care for creating the fixturedefs within its methods.
if not metafunc._calls[0].funcargs:
return # this function call does not have direct parametrization
return # this function call does not have direct parametrization
# collect funcargs of all callspecs into a list of values
arg2params = {}
arg2scope = {}
@@ -105,28 +112,26 @@ def add_funcarg_pseudo_fixture_def(collector, metafunc, fixturemanager):
if node and argname in node._name2pseudofixturedef:
arg2fixturedefs[argname] = [node._name2pseudofixturedef[argname]]
else:
fixturedef = FixtureDef(fixturemanager, '', argname,
get_direct_param_fixture_func,
arg2scope[argname],
valuelist, False, False)
fixturedef = FixtureDef(fixturemanager, '', argname,
get_direct_param_fixture_func,
arg2scope[argname],
valuelist, False, False)
arg2fixturedefs[argname] = [fixturedef]
if node is not None:
node._name2pseudofixturedef[argname] = fixturedef
def getfixturemarker(obj):
""" return fixturemarker or None if it doesn't exist or raised
exceptions."""
try:
return getattr(obj, "_pytestfixturefunction", None)
except Exception:
except TEST_OUTCOME:
# some objects raise errors like request (from flask import request)
# we don't expect them to be fixture functions
return None
def get_parametrized_fixture_keys(item, scopenum):
""" return list of keys for all parametrized arguments which match
the specified scope. """
@@ -136,10 +141,10 @@ def get_parametrized_fixture_keys(item, scopenum):
except AttributeError:
pass
else:
# cs.indictes.items() is random order of argnames but
# then again different functions (items) can change order of
# arguments so it doesn't matter much probably
for argname, param_index in cs.indices.items():
# cs.indices.items() is random order of argnames. Need to
# sort this so that different calls to
# get_parametrized_fixture_keys will be deterministic.
for argname, param_index in sorted(cs.indices.items()):
if cs._arg2scopenum[argname] != scopenum:
continue
if scopenum == 0: # session
@@ -161,20 +166,21 @@ def reorder_items(items):
for scopenum in range(0, scopenum_function):
argkeys_cache[scopenum] = d = {}
for item in items:
keys = set(get_parametrized_fixture_keys(item, scopenum))
keys = OrderedDict.fromkeys(get_parametrized_fixture_keys(item, scopenum))
if keys:
d[item] = keys
return reorder_items_atscope(items, set(), argkeys_cache, 0)
def reorder_items_atscope(items, ignore, argkeys_cache, scopenum):
if scopenum >= scopenum_function or len(items) < 3:
return items
items_done = []
while 1:
items_before, items_same, items_other, newignore = \
slice_items(items, ignore, argkeys_cache[scopenum])
slice_items(items, ignore, argkeys_cache[scopenum])
items_before = reorder_items_atscope(
items_before, ignore, argkeys_cache,scopenum+1)
items_before, ignore, argkeys_cache, scopenum + 1)
if items_same is None:
# nothing to reorder in this scope
assert items_other is None
@@ -195,9 +201,9 @@ def slice_items(items, ignore, scoped_argkeys_cache):
for i, item in enumerate(it):
argkeys = scoped_argkeys_cache.get(item)
if argkeys is not None:
argkeys = argkeys.difference(ignore)
if argkeys: # found a slicing key
slicing_argkey = argkeys.pop()
newargkeys = OrderedDict.fromkeys(k for k in argkeys if k not in ignore)
if newargkeys: # found a slicing key
slicing_argkey, _ = newargkeys.popitem()
items_before = items[:i]
items_same = [item]
items_other = []
@@ -205,7 +211,7 @@ def slice_items(items, ignore, scoped_argkeys_cache):
for item in it:
argkeys = scoped_argkeys_cache.get(item)
if argkeys and slicing_argkey in argkeys and \
slicing_argkey not in ignore:
slicing_argkey not in ignore:
items_same.append(item)
else:
items_other.append(item)
@@ -237,10 +243,10 @@ def fillfixtures(function):
request._fillfixtures()
def get_direct_param_fixture_func(request):
return request.param
class FuncFixtureInfo:
def __init__(self, argnames, names_closure, name2fixturedefs):
self.argnames = argnames
@@ -279,7 +285,6 @@ class FixtureRequest(FuncargnamesCompatAttr):
""" underlying collection node (depends on current request scope)"""
return self._getscopeitem(self.scope)
def _getnextfixturedef(self, argname):
fixturedefs = self._arg2fixturedefs.get(argname, None)
if fixturedefs is None:
@@ -301,7 +306,6 @@ class FixtureRequest(FuncargnamesCompatAttr):
""" the pytest config object associated with this request. """
return self._pyfuncitem.config
@scopeproperty()
def function(self):
""" test function object if the request has a per-function scope. """
@@ -397,7 +401,7 @@ class FixtureRequest(FuncargnamesCompatAttr):
:arg extrakey: added to internal caching key of (funcargname, scope).
"""
if not hasattr(self.config, '_setupcache'):
self.config._setupcache = {} # XXX weakref?
self.config._setupcache = {} # XXX weakref?
cachekey = (self.fixturename, self._getscopeitem(scope), extrakey)
cache = self.config._setupcache
try:
@@ -428,7 +432,8 @@ class FixtureRequest(FuncargnamesCompatAttr):
from _pytest import deprecated
warnings.warn(
deprecated.GETFUNCARGVALUE,
DeprecationWarning)
DeprecationWarning,
stacklevel=2)
return self.getfixturevalue(argname)
def _get_active_fixturedef(self, argname):
@@ -527,8 +532,8 @@ class FixtureRequest(FuncargnamesCompatAttr):
fail("ScopeMismatch: You tried to access the %r scoped "
"fixture %r with a %r scoped request object, "
"involved factories\n%s" % (
(requested_scope, argname, invoking_scope, "\n".join(lines))),
pytrace=False)
(requested_scope, argname, invoking_scope, "\n".join(lines))),
pytrace=False)
def _factorytraceback(self):
lines = []
@@ -553,12 +558,13 @@ class FixtureRequest(FuncargnamesCompatAttr):
return node
def __repr__(self):
return "<FixtureRequest for %r>" %(self.node)
return "<FixtureRequest for %r>" % (self.node)
class SubRequest(FixtureRequest):
""" a sub request for handling getting a fixture from a
test function/fixture. """
def __init__(self, request, scope, param, param_index, fixturedef):
self._parent_request = request
self.fixturename = fixturedef.argname
@@ -569,7 +575,7 @@ class SubRequest(FixtureRequest):
self._fixturedef = fixturedef
self.addfinalizer = fixturedef.addfinalizer
self._pyfuncitem = request._pyfuncitem
self._fixture_values = request._fixture_values
self._fixture_values = request._fixture_values
self._fixture_defs = request._fixture_defs
self._arg2fixturedefs = request._arg2fixturedefs
self._arg2index = request._arg2index
@@ -609,6 +615,7 @@ def scope2index(scope, descr, where=None):
class FixtureLookupError(LookupError):
""" could not return a requested Fixture (missing or invalid). """
def __init__(self, argname, request, msg=None):
self.argname = argname
self.request = request
@@ -631,9 +638,9 @@ class FixtureLookupError(LookupError):
lines, _ = inspect.getsourcelines(get_real_func(function))
except (IOError, IndexError, TypeError):
error_msg = "file %s, line %s: source code not available"
addline(error_msg % (fspath, lineno+1))
addline(error_msg % (fspath, lineno + 1))
else:
addline("file %s, line %s" % (fspath, lineno+1))
addline("file %s, line %s" % (fspath, lineno + 1))
for i, line in enumerate(lines):
line = line.rstrip()
addline(" " + line)
@@ -649,7 +656,7 @@ class FixtureLookupError(LookupError):
if faclist and name not in available:
available.append(name)
msg = "fixture %r not found" % (self.argname,)
msg += "\n available fixtures: %s" %(", ".join(sorted(available)),)
msg += "\n available fixtures: %s" % (", ".join(sorted(available)),)
msg += "\n use 'pytest --fixtures [testpath]' for help on them."
return FixtureLookupErrorRepr(fspath, lineno, tblines, msg, self.argname)
@@ -675,12 +682,12 @@ class FixtureLookupErrorRepr(TerminalRepr):
tw.line('{0} {1}'.format(FormattedExcinfo.flow_marker,
line.strip()), red=True)
tw.line()
tw.line("%s:%d" % (self.filename, self.firstlineno+1))
tw.line("%s:%d" % (self.filename, self.firstlineno + 1))
def fail_fixturefunc(fixturefunc, msg):
fs, lineno = getfslineno(fixturefunc)
location = "%s:%s" % (fs, lineno+1)
location = "%s:%s" % (fs, lineno + 1)
source = _pytest._code.Source(fixturefunc)
fail(msg + ":\n\n" + str(source.indent()) + "\n" + location,
pytrace=False)
@@ -699,7 +706,7 @@ def call_fixture_func(fixturefunc, request, kwargs):
pass
else:
fail_fixturefunc(fixturefunc,
"yield_fixture function has more than one 'yield'")
"yield_fixture function has more than one 'yield'")
request.addfinalizer(teardown)
else:
@@ -709,6 +716,7 @@ def call_fixture_func(fixturefunc, request, kwargs):
class FixtureDef:
""" A container for a factory definition. """
def __init__(self, fixturemanager, baseid, argname, func, scope, params,
unittest=False, ids=None):
self._fixturemanager = fixturemanager
@@ -783,6 +791,7 @@ class FixtureDef:
return ("<FixtureDef name=%r scope=%r baseid=%r >" %
(self.argname, self.scope, self.baseid))
def pytest_fixture_setup(fixturedef, request):
""" Execution of fixture setup. """
kwargs = {}
@@ -808,7 +817,7 @@ def pytest_fixture_setup(fixturedef, request):
my_cache_key = request.param_index
try:
result = call_fixture_func(fixturefunc, request, kwargs)
except Exception:
except TEST_OUTCOME:
fixturedef.cached_result = (None, my_cache_key, sys.exc_info())
raise
fixturedef.cached_result = (result, my_cache_key, None)
@@ -826,12 +835,11 @@ class FixtureFunctionMarker:
def __call__(self, function):
if isclass(function):
raise ValueError(
"class fixtures not supported (may be in the future)")
"class fixtures not supported (may be in the future)")
function._pytestfixturefunction = self
return function
def fixture(scope="function", params=None, autouse=False, ids=None, name=None):
""" (return a) decorator to mark a fixture factory function.
@@ -870,10 +878,10 @@ def fixture(scope="function", params=None, autouse=False, ids=None, name=None):
instead of ``return``. In this case, the code block after the ``yield`` statement is executed
as teardown code regardless of the test outcome. A fixture function must yield exactly once.
"""
if callable(scope) and params is None and autouse == False:
if callable(scope) and params is None and autouse is False:
# direct decoration
return FixtureFunctionMarker(
"function", params, autouse, name=name)(scope)
"function", params, autouse, name=name)(scope)
if params is not None and not isinstance(params, (list, tuple)):
params = list(params)
return FixtureFunctionMarker(scope, params, autouse, ids=ids, name=name)
@@ -888,7 +896,7 @@ def yield_fixture(scope="function", params=None, autouse=False, ids=None, name=N
if callable(scope) and params is None and not autouse:
# direct decoration
return FixtureFunctionMarker(
"function", params, autouse, ids=ids, name=name)(scope)
"function", params, autouse, ids=ids, name=name)(scope)
else:
return FixtureFunctionMarker(scope, params, autouse, ids=ids, name=name)
@@ -947,14 +955,9 @@ class FixtureManager:
self._nodeid_and_autousenames = [("", self.config.getini("usefixtures"))]
session.config.pluginmanager.register(self, "funcmanage")
def getfixtureinfo(self, node, func, cls, funcargs=True):
if funcargs and not hasattr(node, "nofuncargs"):
if cls is not None:
startindex = 1
else:
startindex = None
argnames = getfuncargnames(func, startindex)
argnames = getfuncargnames(func, cls=cls)
else:
argnames = ()
usefixtures = getattr(func, "usefixtures", None)
@@ -989,7 +992,7 @@ class FixtureManager:
if nodeid.startswith(baseid):
if baseid:
i = len(baseid)
nextchar = nodeid[i:i+1]
nextchar = nodeid[i:i + 1]
if nextchar and nextchar not in ":/":
continue
autousenames.extend(basenames)
@@ -1126,4 +1129,3 @@ class FixtureManager:
for fixturedef in fixturedefs:
if nodeid.startswith(fixturedef.baseid):
yield fixturedef

View File

@@ -5,7 +5,6 @@ pytest
from __future__ import absolute_import, division, print_function
def freeze_includes():
"""
Returns a list of module names used by py.test that should be

View File

@@ -4,7 +4,8 @@ from __future__ import absolute_import, division, print_function
import py
import pytest
from _pytest.config import PrintHelp
import os, sys
import os
import sys
from argparse import Action
@@ -41,20 +42,20 @@ class HelpAction(Action):
def pytest_addoption(parser):
group = parser.getgroup('debugconfig')
group.addoption('--version', action="store_true",
help="display pytest lib version and import information.")
help="display pytest lib version and import information.")
group._addoption("-h", "--help", action=HelpAction, dest="help",
help="show help message and configuration info")
group._addoption('-p', action="append", dest="plugins", default = [],
metavar="name",
help="early-load given plugin (multi-allowed). "
"To avoid loading of plugins, use the `no:` prefix, e.g. "
"`no:doctest`.")
help="show help message and configuration info")
group._addoption('-p', action="append", dest="plugins", default=[],
metavar="name",
help="early-load given plugin (multi-allowed). "
"To avoid loading of plugins, use the `no:` prefix, e.g. "
"`no:doctest`.")
group.addoption('--traceconfig', '--trace-config',
action="store_true", default=False,
help="trace considerations of conftest.py files."),
action="store_true", default=False,
help="trace considerations of conftest.py files."),
group.addoption('--debug',
action="store_true", dest="debug", default=False,
help="store internal tracing debug information in 'pytestdebug.log'.")
action="store_true", dest="debug", default=False,
help="store internal tracing debug information in 'pytestdebug.log'.")
group._addoption(
'-o', '--override-ini', nargs='*', dest="override_ini",
action="append",
@@ -69,10 +70,10 @@ def pytest_cmdline_parse():
path = os.path.abspath("pytestdebug.log")
debugfile = open(path, 'w')
debugfile.write("versions pytest-%s, py-%s, "
"python-%s\ncwd=%s\nargs=%s\n\n" %(
pytest.__version__, py.__version__,
".".join(map(str, sys.version_info)),
os.getcwd(), config._origargs))
"python-%s\ncwd=%s\nargs=%s\n\n" % (
pytest.__version__, py.__version__,
".".join(map(str, sys.version_info)),
os.getcwd(), config._origargs))
config.trace.root.setwriter(debugfile.write)
undo_tracing = config.pluginmanager.enable_tracing()
sys.stderr.write("writing pytestdebug information to %s\n" % path)
@@ -86,11 +87,12 @@ def pytest_cmdline_parse():
config.add_cleanup(unset_tracing)
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))
(pytest.__version__, p))
plugininfo = getpluginversioninfo(config)
if plugininfo:
for line in plugininfo:
@@ -102,6 +104,7 @@ def pytest_cmdline_main(config):
config._ensure_unconfigure()
return 0
def showhelp(config):
reporter = config.pluginmanager.get_plugin('terminalreporter')
tw = reporter._tw
@@ -117,7 +120,7 @@ def showhelp(config):
if type is None:
type = "string"
spec = "%s (%s)" % (name, type)
line = " %-24s %s" %(spec, help)
line = " %-24s %s" % (spec, help)
tw.line(line[:tw.fullwidth])
tw.line()
@@ -146,6 +149,7 @@ conftest_options = [
('pytest_plugins', 'list of plugin names to load'),
]
def getpluginversioninfo(config):
lines = []
plugininfo = config.pluginmanager.list_plugin_distinfo()
@@ -157,11 +161,12 @@ def getpluginversioninfo(config):
lines.append(" " + content)
return lines
def pytest_report_header(config):
lines = []
if config.option.debug or config.option.traceconfig:
lines.append("using: pytest-%s pylib-%s" %
(pytest.__version__,py.__version__))
(pytest.__version__, py.__version__))
verinfo = getpluginversioninfo(config)
if verinfo:
@@ -175,5 +180,5 @@ def pytest_report_header(config):
r = plugin.__file__
else:
r = repr(plugin)
lines.append(" %-20s: %s" %(name, r))
lines.append(" %-20s: %s" % (name, r))
return lines

View File

@@ -8,6 +8,7 @@ hookspec = HookspecMarker("pytest")
# Initialization hooks called for every plugin
# -------------------------------------------------------------------------
@hookspec(historic=True)
def pytest_addhooks(pluginmanager):
"""called at plugin registration time to allow adding new hooks via a call to
@@ -23,6 +24,7 @@ def pytest_namespace():
time.
"""
@hookspec(historic=True)
def pytest_plugin_registered(plugin, manager):
""" a new pytest plugin got registered. """
@@ -58,11 +60,20 @@ def pytest_addoption(parser):
via (deprecated) ``pytest.config``.
"""
@hookspec(historic=True)
def pytest_configure(config):
""" called after command line options have been parsed
and all plugins and initial conftest files been loaded.
This hook is called for every plugin.
"""
Allows plugins and conftest files to perform initial configuration.
This hook is called for every plugin and initial conftest file
after command line options have been parsed.
After that, the hook is called for other conftest files as they are
imported.
:arg config: pytest config object
:type config: _pytest.config.Config
"""
# -------------------------------------------------------------------------
@@ -71,15 +82,18 @@ def pytest_configure(config):
# discoverable conftest.py local plugins.
# -------------------------------------------------------------------------
@hookspec(firstresult=True)
def pytest_cmdline_parse(pluginmanager, args):
"""return initialized config object, parsing the specified args.
Stops at first non-None result, see :ref:`firstresult` """
def pytest_cmdline_preparse(config, args):
"""(deprecated) modify command line arguments before option parsing. """
@hookspec(firstresult=True)
def pytest_cmdline_main(config):
""" called for performing the main command line action. The default
@@ -87,6 +101,7 @@ def pytest_cmdline_main(config):
Stops at first non-None result, see :ref:`firstresult` """
def pytest_load_initial_conftests(early_config, parser, args):
""" implements the loading of initial conftest files ahead
of command line option parsing. """
@@ -102,13 +117,16 @@ def pytest_collection(session):
Stops at first non-None result, see :ref:`firstresult` """
def pytest_collection_modifyitems(session, config, items):
""" called after collection has been performed, may filter or re-order
the items in-place."""
def pytest_collection_finish(session):
""" called after collection has been performed and modified. """
@hookspec(firstresult=True)
def pytest_ignore_collect(path, config):
""" return True to prevent considering this path for collection.
@@ -118,29 +136,37 @@ def pytest_ignore_collect(path, config):
Stops at first non-None result, see :ref:`firstresult`
"""
@hookspec(firstresult=True)
def pytest_collect_directory(path, parent):
""" called before traversing a directory for collection files.
Stops at first non-None result, see :ref:`firstresult` """
def pytest_collect_file(path, parent):
""" return collection Node or None for the given path. Any new node
needs to have the specified ``parent`` as a parent."""
# logging hooks for collection
def pytest_collectstart(collector):
""" collector starts collecting. """
def pytest_itemcollected(item):
""" we just collected a test item. """
def pytest_collectreport(report):
""" collector finished collecting. """
def pytest_deselected(items):
""" called for test items deselected by keyword. """
@hookspec(firstresult=True)
def pytest_make_collect_report(collector):
""" perform ``collector.collect()`` and return a CollectReport.
@@ -151,6 +177,7 @@ def pytest_make_collect_report(collector):
# Python test function related hooks
# -------------------------------------------------------------------------
@hookspec(firstresult=True)
def pytest_pycollect_makemodule(path, parent):
""" return a Module collector or None for the given path.
@@ -160,21 +187,25 @@ def pytest_pycollect_makemodule(path, parent):
Stops at first non-None result, see :ref:`firstresult` """
@hookspec(firstresult=True)
def pytest_pycollect_makeitem(collector, name, obj):
""" return custom item/collector for a python object in a module, or None.
Stops at first non-None result, see :ref:`firstresult` """
@hookspec(firstresult=True)
def pytest_pyfunc_call(pyfuncitem):
""" call underlying test function.
Stops at first non-None result, see :ref:`firstresult` """
def pytest_generate_tests(metafunc):
""" generate (multiple) parametrized calls to a test function."""
@hookspec(firstresult=True)
def pytest_make_parametrize_id(config, val, argname):
"""Return a user-friendly string representation of the given ``val`` that will be used
@@ -187,6 +218,7 @@ def pytest_make_parametrize_id(config, val, argname):
# generic runtest related hooks
# -------------------------------------------------------------------------
@hookspec(firstresult=True)
def pytest_runtestloop(session):
""" called for performing the main runtest loop
@@ -194,9 +226,11 @@ def pytest_runtestloop(session):
Stops at first non-None result, see :ref:`firstresult` """
def pytest_itemstart(item, node):
""" (deprecated, use pytest_runtest_logstart). """
@hookspec(firstresult=True)
def pytest_runtest_protocol(item, nextitem):
""" implements the runtest_setup/call/teardown protocol for
@@ -214,15 +248,19 @@ def pytest_runtest_protocol(item, nextitem):
Stops at first non-None result, see :ref:`firstresult` """
def pytest_runtest_logstart(nodeid, location):
""" signal the start of running a single test item. """
def pytest_runtest_setup(item):
""" called before ``pytest_runtest_call(item)``. """
def pytest_runtest_call(item):
""" called to execute the test ``item``. """
def pytest_runtest_teardown(item, nextitem):
""" called after ``pytest_runtest_call``.
@@ -232,6 +270,7 @@ def pytest_runtest_teardown(item, nextitem):
so that nextitem only needs to call setup-functions.
"""
@hookspec(firstresult=True)
def pytest_runtest_makereport(item, call):
""" return a :py:class:`_pytest.runner.TestReport` object
@@ -240,6 +279,7 @@ def pytest_runtest_makereport(item, call):
Stops at first non-None result, see :ref:`firstresult` """
def pytest_runtest_logreport(report):
""" process a test setup/call/teardown report relating to
the respective phase of executing a test. """
@@ -248,12 +288,14 @@ def pytest_runtest_logreport(report):
# Fixture related hooks
# -------------------------------------------------------------------------
@hookspec(firstresult=True)
def pytest_fixture_setup(fixturedef, request):
""" performs fixture setup execution.
Stops at first non-None result, see :ref:`firstresult` """
def pytest_fixture_post_finalizer(fixturedef):
""" called after fixture teardown, but before the cache is cleared so
the fixture result cache ``fixturedef.cached_result`` can
@@ -263,12 +305,15 @@ def pytest_fixture_post_finalizer(fixturedef):
# test session related hooks
# -------------------------------------------------------------------------
def pytest_sessionstart(session):
""" before session.main() is called. """
def pytest_sessionfinish(session, exitstatus):
""" whole test run finishes. """
def pytest_unconfigure(config):
""" called before test process is exited. """
@@ -290,8 +335,12 @@ def pytest_assertrepr_compare(config, op, left, right):
# hooks for influencing reporting (invoked from _pytest_terminal)
# -------------------------------------------------------------------------
def pytest_report_header(config, startdir):
""" return a string to be displayed as header info for terminal reporting.
""" return a string or list of strings to be displayed as header info for terminal reporting.
:param config: the pytest config object.
:param startdir: py.path object with the starting dir
.. note::
@@ -300,12 +349,28 @@ def pytest_report_header(config, startdir):
:ref:`discovers plugins during startup <pluginorder>`.
"""
def pytest_report_collectionfinish(config, startdir, items):
"""
.. versionadded:: 3.2
return a string or list of strings to be displayed after collection has finished successfully.
This strings will be displayed after the standard "collected X items" message.
:param config: the pytest config object.
:param startdir: py.path object with the starting dir
:param items: list of pytest items that are going to be executed; this list should not be modified.
"""
@hookspec(firstresult=True)
def pytest_report_teststatus(report):
""" return result-category, shortletter and verbose word for reporting.
Stops at first non-None result, see :ref:`firstresult` """
def pytest_terminal_summary(terminalreporter, exitstatus):
""" add additional section in terminal summary reporting. """
@@ -320,6 +385,7 @@ def pytest_logwarning(message, code, nodeid, fslocation):
# doctest hooks
# -------------------------------------------------------------------------
@hookspec(firstresult=True)
def pytest_doctest_prepare_content(content):
""" return processed content for a given doctest
@@ -330,12 +396,15 @@ def pytest_doctest_prepare_content(content):
# error handling and internal debugging hooks
# -------------------------------------------------------------------------
def pytest_internalerror(excrepr, excinfo):
""" called for internal errors. """
def pytest_keyboard_interrupt(excinfo):
""" called for keyboard interrupt. """
def pytest_exception_interact(node, call, report):
"""called when an exception was raised which can potentially be
interactively handled.
@@ -344,6 +413,7 @@ def pytest_exception_interact(node, call, report):
that is not an internal exception like ``skip.Exception``.
"""
def pytest_enter_pdb(config):
""" called upon pdb.set_trace(), can be used by plugins to take special
action just before the python debugger enters in interactive mode.

View File

@@ -1,254 +0,0 @@
Sorting per-resource
-----------------------------
for any given set of items:
- collect items per session-scoped parametrized funcarg
- re-order until items no parametrizations are mixed
examples:
test()
test1(s1)
test1(s2)
test2()
test3(s1)
test3(s2)
gets sorted to:
test()
test2()
test1(s1)
test3(s1)
test1(s2)
test3(s2)
the new @setup functions
--------------------------------------
Consider a given @setup-marked function::
@pytest.mark.setup(maxscope=SCOPE)
def mysetup(request, arg1, arg2, ...)
...
request.addfinalizer(fin)
...
then FUNCARGSET denotes the set of (arg1, arg2, ...) funcargs and
all of its dependent funcargs. The mysetup function will execute
for any matching test item once per scope.
The scope is determined as the minimum scope of all scopes of the args
in FUNCARGSET and the given "maxscope".
If mysetup has been called and no finalizers have been called it is
called "active".
Furthermore the following rules apply:
- if an arg value in FUNCARGSET is about to be torn down, the
mysetup-registered finalizers will execute as well.
- There will never be two active mysetup invocations.
Example 1, session scope::
@pytest.mark.funcarg(scope="session", params=[1,2])
def db(request):
request.addfinalizer(db_finalize)
@pytest.mark.setup
def mysetup(request, db):
request.addfinalizer(mysetup_finalize)
...
And a given test module:
def test_something():
...
def test_otherthing():
pass
Here is what happens::
db(request) executes with request.param == 1
mysetup(request, db) executes
test_something() executes
test_otherthing() executes
mysetup_finalize() executes
db_finalize() executes
db(request) executes with request.param == 2
mysetup(request, db) executes
test_something() executes
test_otherthing() executes
mysetup_finalize() executes
db_finalize() executes
Example 2, session/function scope::
@pytest.mark.funcarg(scope="session", params=[1,2])
def db(request):
request.addfinalizer(db_finalize)
@pytest.mark.setup(scope="function")
def mysetup(request, db):
...
request.addfinalizer(mysetup_finalize)
...
And a given test module:
def test_something():
...
def test_otherthing():
pass
Here is what happens::
db(request) executes with request.param == 1
mysetup(request, db) executes
test_something() executes
mysetup_finalize() executes
mysetup(request, db) executes
test_otherthing() executes
mysetup_finalize() executes
db_finalize() executes
db(request) executes with request.param == 2
mysetup(request, db) executes
test_something() executes
mysetup_finalize() executes
mysetup(request, db) executes
test_otherthing() executes
mysetup_finalize() executes
db_finalize() executes
Example 3 - funcargs session-mix
----------------------------------------
Similar with funcargs, an example::
@pytest.mark.funcarg(scope="session", params=[1,2])
def db(request):
request.addfinalizer(db_finalize)
@pytest.mark.funcarg(scope="function")
def table(request, db):
...
request.addfinalizer(table_finalize)
...
And a given test module:
def test_something(table):
...
def test_otherthing(table):
pass
def test_thirdthing():
pass
Here is what happens::
db(request) executes with param == 1
table(request, db)
test_something(table)
table_finalize()
table(request, db)
test_otherthing(table)
table_finalize()
db_finalize
db(request) executes with param == 2
table(request, db)
test_something(table)
table_finalize()
table(request, db)
test_otherthing(table)
table_finalize()
db_finalize
test_thirdthing()
Data structures
--------------------
pytest internally maintains a dict of active funcargs with cache, param,
finalizer, (scopeitem?) information:
active_funcargs = dict()
if a parametrized "db" is activated:
active_funcargs["db"] = FuncargInfo(dbvalue, paramindex,
FuncargFinalize(...), scopeitem)
if a test is torn down and the next test requires a differently
parametrized "db":
for argname in item.callspec.params:
if argname in active_funcargs:
funcarginfo = active_funcargs[argname]
if funcarginfo.param != item.callspec.params[argname]:
funcarginfo.callfinalizer()
del node2funcarg[funcarginfo.scopeitem]
del active_funcargs[argname]
nodes_to_be_torn_down = ...
for node in nodes_to_be_torn_down:
if node in node2funcarg:
argname = node2funcarg[node]
active_funcargs[argname].callfinalizer()
del node2funcarg[node]
del active_funcargs[argname]
if a test is setup requiring a "db" funcarg:
if "db" in active_funcargs:
return active_funcargs["db"][0]
funcarginfo = setup_funcarg()
active_funcargs["db"] = funcarginfo
node2funcarg[funcarginfo.scopeitem] = "db"
Implementation plan for resources
------------------------------------------
1. Revert FuncargRequest to the old form, unmerge item/request
(done)
2. make funcarg factories be discovered at collection time
3. Introduce funcarg marker
4. Introduce funcarg scope parameter
5. Introduce funcarg parametrize parameter
6. make setup functions be discovered at collection time
7. (Introduce a pytest_fixture_protocol/setup_funcargs hook)
methods and data structures
--------------------------------
A FuncarcManager holds all information about funcarg definitions
including parametrization and scope definitions. It implements
a pytest_generate_tests hook which performs parametrization as appropriate.
as a simple example, let's consider a tree where a test function requires
a "abc" funcarg and its factory defines it as parametrized and scoped
for Modules. When collections hits the function item, it creates
the metafunc object, and calls funcargdb.pytest_generate_tests(metafunc)
which looks up available funcarg factories and their scope and parametrization.
This information is equivalent to what can be provided today directly
at the function site and it should thus be relatively straight forward
to implement the additional way of defining parametrization/scoping.
conftest loading:
each funcarg-factory will populate the session.funcargmanager
When a test item is collected, it grows a dictionary
(funcargname2factorycalllist). A factory lookup is performed
for each required funcarg. The resulting factory call is stored
with the item. If a function is parametrized multiple items are
created with respective factory calls. Else if a factory is parametrized
multiple items and calls to the factory function are created as well.
At setup time, an item populates a funcargs mapping, mapping names
to values. If a value is funcarg factories are queried for a given item
test functions and setup functions are put in a class
which looks up required funcarg factories.

View File

@@ -378,8 +378,8 @@ class LogXML(object):
close_report = next(
(rep for rep in self.open_reports
if (rep.nodeid == report.nodeid and
getattr(rep, "item_index", None) == report_ii and
getattr(rep, "worker_id", None) == report_wid
getattr(rep, "item_index", None) == report_ii and
getattr(rep, "worker_id", None) == report_wid
)
), None)
if close_report:
@@ -444,9 +444,9 @@ class LogXML(object):
"""
if self.global_properties:
return Junit.properties(
[
Junit.property(name=name, value=value)
for name, value in self.global_properties
]
[
Junit.property(name=name, value=value)
for name, value in self.global_properties
]
)
return ''

View File

@@ -14,7 +14,8 @@ except ImportError:
from UserDict import DictMixin as MappingMixin
from _pytest.config import directory_arg, UsageError, hookimpl
from _pytest.runner import collect_one_node, exit
from _pytest.runner import collect_one_node
from _pytest.outcomes import exit
tracebackcutdir = py.path.local(_pytest.__file__).dirpath()
@@ -29,53 +30,57 @@ EXIT_NOTESTSCOLLECTED = 5
def pytest_addoption(parser):
parser.addini("norecursedirs", "directory patterns to avoid for recursion",
type="args", default=['.*', 'build', 'dist', 'CVS', '_darcs', '{arch}', '*.egg', 'venv'])
parser.addini("testpaths", "directories to search for tests when no files or directories are given in the command line.",
type="args", default=[])
#parser.addini("dirpatterns",
type="args", default=['.*', 'build', 'dist', 'CVS', '_darcs', '{arch}', '*.egg', 'venv'])
parser.addini("testpaths", "directories to search for tests when no files or directories are given in the "
"command line.",
type="args", default=[])
# parser.addini("dirpatterns",
# "patterns specifying possible locations of test files",
# type="linelist", default=["**/test_*.txt",
# "**/test_*.py", "**/*_test.py"]
#)
# )
group = parser.getgroup("general", "running and selection options")
group._addoption('-x', '--exitfirst', action="store_const",
dest="maxfail", const=1,
help="exit instantly on first error or failed test."),
dest="maxfail", const=1,
help="exit instantly on first error or failed test."),
group._addoption('--maxfail', metavar="num",
action="store", type=int, dest="maxfail", default=0,
help="exit after first num failures or errors.")
action="store", type=int, dest="maxfail", default=0,
help="exit after first num failures or errors.")
group._addoption('--strict', action="store_true",
help="run pytest in strict mode, warnings become errors.")
help="marks not registered in configuration file raise errors.")
group._addoption("-c", metavar="file", type=str, dest="inifilename",
help="load configuration from `file` instead of trying to locate one of the implicit configuration files.")
help="load configuration from `file` instead of trying to locate one of the implicit "
"configuration files.")
group._addoption("--continue-on-collection-errors", action="store_true",
default=False, dest="continue_on_collection_errors",
help="Force test execution even if collection errors occur.")
default=False, dest="continue_on_collection_errors",
help="Force test execution even if collection errors occur.")
group = parser.getgroup("collect", "collection")
group.addoption('--collectonly', '--collect-only', action="store_true",
help="only collect tests, don't execute them."),
help="only collect tests, don't execute them."),
group.addoption('--pyargs', action="store_true",
help="try to interpret all arguments as python packages.")
help="try to interpret all arguments as python packages.")
group.addoption("--ignore", action="append", metavar="path",
help="ignore path during collection (multi-allowed).")
help="ignore path during collection (multi-allowed).")
# when changing this to --conf-cut-dir, config.py Conftest.setinitial
# needs upgrading as well
group.addoption('--confcutdir', dest="confcutdir", default=None,
metavar="dir", type=functools.partial(directory_arg, optname="--confcutdir"),
help="only load conftest.py's relative to specified dir.")
metavar="dir", type=functools.partial(directory_arg, optname="--confcutdir"),
help="only load conftest.py's relative to specified dir.")
group.addoption('--noconftest', action="store_true",
dest="noconftest", default=False,
help="Don't load any conftest.py files.")
dest="noconftest", default=False,
help="Don't load any conftest.py files.")
group.addoption('--keepduplicates', '--keep-duplicates', action="store_true",
dest="keepduplicates", default=False,
help="Keep duplicate tests.")
dest="keepduplicates", default=False,
help="Keep duplicate tests.")
group.addoption('--collect-in-virtualenv', action='store_true',
dest='collect_in_virtualenv', default=False,
help="Don't ignore tests in a local virtualenv directory")
group = parser.getgroup("debugconfig",
"test session debugging and configuration")
"test session debugging and configuration")
group.addoption('--basetemp', dest="basetemp", default=None, metavar="dir",
help="base temporary directory for this test run.")
help="base temporary directory for this test run.")
def pytest_namespace():
@@ -88,7 +93,7 @@ def pytest_namespace():
def pytest_configure(config):
__import__('pytest').config = config # compatibiltiy
__import__('pytest').config = config # compatibiltiy
def wrap_session(config, doit):
@@ -160,13 +165,24 @@ def pytest_runtestloop(session):
return True
for i, item in enumerate(session.items):
nextitem = session.items[i+1] if i+1 < len(session.items) else None
nextitem = session.items[i + 1] if i + 1 < len(session.items) else None
item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem)
if session.shouldstop:
raise session.Interrupted(session.shouldstop)
return True
def _in_venv(path):
"""Attempts to detect if ``path`` is the root of a Virtual Environment by
checking for the existence of the appropriate activate script"""
bindir = path.join('Scripts' if sys.platform.startswith('win') else 'bin')
if not bindir.exists():
return False
activates = ('activate', 'activate.csh', 'activate.fish',
'Activate', 'Activate.bat', 'Activate.ps1')
return any([fname.basename in activates for fname in bindir.listdir()])
def pytest_ignore_collect(path, config):
ignore_paths = config._getconftest_pathlist("collect_ignore", path=path.dirpath())
ignore_paths = ignore_paths or []
@@ -177,6 +193,10 @@ def pytest_ignore_collect(path, config):
if py.path.local(path) in ignore_paths:
return True
allow_in_venv = config.getoption("collect_in_virtualenv")
if _in_venv(path) and not allow_in_venv:
return True
# Skip duplicate paths.
keepduplicates = config.getoption("keepduplicates")
duplicate_paths = config.pluginmanager._duplicatepaths
@@ -200,6 +220,7 @@ class FSHookProxy:
self.__dict__[name] = x
return x
class _CompatProperty(object):
def __init__(self, name):
self.name = name
@@ -216,7 +237,6 @@ class _CompatProperty(object):
return getattr(__import__('pytest'), self.name)
class NodeKeywords(MappingMixin):
def __init__(self, node):
self.node = node
@@ -307,8 +327,8 @@ class Node(object):
return cls
def __repr__(self):
return "<%s %r>" %(self.__class__.__name__,
getattr(self, 'name', None))
return "<%s %r>" % (self.__class__.__name__,
getattr(self, 'name', None))
def warn(self, code, message):
""" generate a warning with the given code and message for this
@@ -429,7 +449,7 @@ class Node(object):
return excinfo.value.formatrepr()
tbfilter = True
if self.config.option.fulltrace:
style="long"
style = "long"
else:
tb = _pytest._code.Traceback([excinfo.traceback[-1]])
self._prunetraceback(excinfo)
@@ -457,6 +477,7 @@ class Node(object):
repr_failure = _repr_failure_py
class Collector(Node):
""" Collector instances create children through collect()
and thus iteratively build a tree.
@@ -486,9 +507,10 @@ class Collector(Node):
ntraceback = ntraceback.cut(excludepath=tracebackcutdir)
excinfo.traceback = ntraceback.filter()
class FSCollector(Collector):
def __init__(self, fspath, parent=None, config=None, session=None):
fspath = py.path.local(fspath) # xxx only for test_resultlog.py?
fspath = py.path.local(fspath) # xxx only for test_resultlog.py?
name = fspath.basename
if parent is not None:
rel = fspath.relto(parent.fspath)
@@ -504,9 +526,11 @@ class FSCollector(Collector):
relpath = relpath.replace(os.sep, "/")
return relpath
class File(FSCollector):
""" base class for collecting tests from a file. """
class Item(Node):
""" a basic test invocation item. Note that for a single function
there might be multiple test invocation items.
@@ -518,6 +542,21 @@ class Item(Node):
self._report_sections = []
def add_report_section(self, when, key, content):
"""
Adds a new report section, similar to what's done internally to add stdout and
stderr captured output::
item.add_report_section("call", "stdout", "report section contents")
:param str when:
One of the possible capture states, ``"setup"``, ``"call"``, ``"teardown"``.
:param str key:
Name of the section, can be customized at will. Pytest uses ``"stdout"`` and
``"stderr"`` internally.
:param str content:
The full contents as a string.
"""
if content:
self._report_sections.append((when, key, content))
@@ -541,12 +580,15 @@ class Item(Node):
self._location = location
return location
class NoMatch(Exception):
""" raised if matching cannot locate a matching names. """
class Interrupted(KeyboardInterrupt):
""" signals an interrupted test run. """
__module__ = 'builtins' # for py3
__module__ = 'builtins' # for py3
class Session(FSCollector):
Interrupted = Interrupted
@@ -603,7 +645,7 @@ class Session(FSCollector):
items = self._perform_collect(args, genitems)
self.config.pluginmanager.check_pending()
hook.pytest_collection_modifyitems(session=self,
config=self.config, items=items)
config=self.config, items=items)
finally:
hook.pytest_collection_finish(session=self)
self.testscollected = len(items)

View File

@@ -2,13 +2,21 @@
from __future__ import absolute_import, division, print_function
import inspect
import warnings
from collections import namedtuple
from operator import attrgetter
from .compat import imap
from .deprecated import MARK_PARAMETERSET_UNPACKING
def alias(name):
return property(attrgetter(name), doc='alias for ' + name)
def alias(name, warning=None):
getter = attrgetter(name)
def warned(self):
warnings.warn(warning, stacklevel=2)
return getter(self)
return property(getter if warning is None else warned, doc='alias for ' + name)
class ParameterSet(namedtuple('ParameterSet', 'values, marks, id')):
@@ -54,6 +62,9 @@ class ParameterSet(namedtuple('ParameterSet', 'values, marks, id')):
if legacy_force_tuple:
argval = argval,
if newmarks:
warnings.warn(MARK_PARAMETERSET_UNPACKING)
return cls(argval, marks=newmarks, id=None)
@property
@@ -155,6 +166,7 @@ def pytest_collection_modifyitems(items, config):
class MarkMapping:
"""Provides a local mapping for markers where item access
resolves to True if the marker is present. """
def __init__(self, keywords):
mymarks = set()
for key, value in keywords.items():
@@ -170,6 +182,7 @@ class KeywordMapping:
"""Provides a local mapping for keywords.
Given a list of names, map any substring of one of these names to True.
"""
def __init__(self, names):
self._names = names
@@ -243,7 +256,6 @@ class MarkGenerator:
on the ``test_function`` object. """
_config = None
def __getattr__(self, name):
if name[0] == "_":
raise AttributeError("Marker name must NOT start with underscore")
@@ -270,6 +282,7 @@ def istestfunc(func):
return hasattr(func, "__call__") and \
getattr(func, "__name__", "<lambda>") != "<lambda>"
class MarkDecorator:
""" A decorator for test functions and test classes. When applied
it will create :class:`MarkInfo` objects which may be
@@ -303,6 +316,7 @@ class MarkDecorator:
additional keyword or positional arguments.
"""
def __init__(self, mark):
assert isinstance(mark, Mark), repr(mark)
self.mark = mark
@@ -313,7 +327,7 @@ class MarkDecorator:
@property
def markname(self):
return self.name # for backward-compat (2.4.1 had this attr)
return self.name # for backward-compat (2.4.1 had this attr)
def __eq__(self, other):
return self.mark == other.mark
@@ -321,6 +335,17 @@ class MarkDecorator:
def __repr__(self):
return "<MarkDecorator %r>" % (self.mark,)
def with_args(self, *args, **kwargs):
""" return a MarkDecorator with extra arguments added
unlike call this can be used even if the sole argument is a callable/class
:return: MarkDecorator
"""
mark = Mark(self.name, args, kwargs)
return self.__class__(self.mark.combined_with(mark))
def __call__(self, *args, **kwargs):
""" if passed a single callable argument: decorate it with mark info.
otherwise add *args/**kwargs in-place to mark information. """
@@ -329,30 +354,49 @@ class MarkDecorator:
is_class = inspect.isclass(func)
if len(args) == 1 and (istestfunc(func) or is_class):
if is_class:
if hasattr(func, 'pytestmark'):
mark_list = func.pytestmark
if not isinstance(mark_list, list):
mark_list = [mark_list]
# always work on a copy to avoid updating pytestmark
# from a superclass by accident
mark_list = mark_list + [self]
func.pytestmark = mark_list
else:
func.pytestmark = [self]
store_mark(func, self.mark)
else:
holder = getattr(func, self.name, None)
if holder is None:
holder = MarkInfo(self.mark)
setattr(func, self.name, holder)
else:
holder.add_mark(self.mark)
store_legacy_markinfo(func, self.mark)
store_mark(func, self.mark)
return func
mark = Mark(self.name, args, kwargs)
return self.__class__(self.mark.combined_with(mark))
return self.with_args(*args, **kwargs)
def get_unpacked_marks(obj):
"""
obtain the unpacked marks that are stored on a object
"""
mark_list = getattr(obj, 'pytestmark', [])
if not isinstance(mark_list, list):
mark_list = [mark_list]
return [
getattr(mark, 'mark', mark) # unpack MarkDecorator
for mark in mark_list
]
def store_mark(obj, mark):
"""store a Mark on a object
this is used to implement the Mark declarations/decorators correctly
"""
assert isinstance(mark, Mark), mark
# always reassign name to avoid updating pytestmark
# in a referene that was only borrowed
obj.pytestmark = get_unpacked_marks(obj) + [mark]
def store_legacy_markinfo(func, mark):
"""create the legacy MarkInfo objects and put them onto the function
"""
if not isinstance(mark, Mark):
raise TypeError("got {mark!r} instead of a Mark".format(mark=mark))
holder = getattr(func, mark.name, None)
if holder is None:
holder = MarkInfo(mark)
setattr(func, mark.name, holder)
else:
holder.add_mark(mark)
class Mark(namedtuple('Mark', 'name, args, kwargs')):
@@ -366,6 +410,7 @@ class Mark(namedtuple('Mark', 'name, args, kwargs')):
class MarkInfo(object):
""" Marking object created by :class:`MarkDecorator` instances. """
def __init__(self, mark):
assert isinstance(mark, Mark), repr(mark)
self.combined = mark
@@ -389,3 +434,30 @@ class MarkInfo(object):
MARK_GEN = MarkGenerator()
def _marked(func, mark):
""" Returns True if :func: is already marked with :mark:, False otherwise.
This can happen if marker is applied to class and the test file is
invoked more than once.
"""
try:
func_mark = getattr(func, mark.name)
except AttributeError:
return False
return mark.args == func_mark.args and mark.kwargs == func_mark.kwargs
def transfer_markers(funcobj, cls, mod):
"""
this function transfers class level markers and module level markers
into function level markinfo objects
this is the main reason why marks are so broken
the resolution will involve phasing out function level MarkInfo objects
"""
for obj in (cls, mod):
for mark in get_unpacked_marks(obj):
if not _marked(funcobj, mark):
store_legacy_markinfo(funcobj, mark)

View File

@@ -71,9 +71,9 @@ def annotated_getattr(obj, name, ann):
obj = getattr(obj, name)
except AttributeError:
raise AttributeError(
'%r object at %s has no attribute %r' % (
type(obj).__name__, ann, name
)
'%r object at %s has no attribute %r' % (
type(obj).__name__, ann, name
)
)
return obj

View File

@@ -38,14 +38,15 @@ def pytest_runtest_setup(item):
if not call_optional(item.obj, 'setup'):
# call module level setup if there is no object level one
call_optional(item.parent.obj, 'setup')
#XXX this implies we only call teardown when setup worked
# XXX this implies we only call teardown when setup worked
item.session._setupstate.addfinalizer((lambda: teardown_nose(item)), item)
def teardown_nose(item):
if is_potential_nosetest(item):
if not call_optional(item.obj, 'teardown'):
call_optional(item.parent.obj, 'teardown')
#if hasattr(item.parent, '_nosegensetup'):
# if hasattr(item.parent, '_nosegensetup'):
# #call_optional(item._nosegensetup, 'teardown')
# del item.parent._nosegensetup

140
_pytest/outcomes.py Normal file
View File

@@ -0,0 +1,140 @@
"""
exception classes and constants handling test outcomes
as well as functions creating them
"""
from __future__ import absolute_import, division, print_function
import py
import sys
class OutcomeException(BaseException):
""" OutcomeException and its subclass instances indicate and
contain info about test and collection outcomes.
"""
def __init__(self, msg=None, pytrace=True):
BaseException.__init__(self, msg)
self.msg = msg
self.pytrace = pytrace
def __repr__(self):
if self.msg:
val = self.msg
if isinstance(val, bytes):
val = py._builtin._totext(val, errors='replace')
return val
return "<%s instance>" % (self.__class__.__name__,)
__str__ = __repr__
TEST_OUTCOME = (OutcomeException, Exception)
class Skipped(OutcomeException):
# XXX hackish: on 3k we fake to live in the builtins
# in order to have Skipped exception printing shorter/nicer
__module__ = 'builtins'
def __init__(self, msg=None, pytrace=True, allow_module_level=False):
OutcomeException.__init__(self, msg=msg, pytrace=pytrace)
self.allow_module_level = allow_module_level
class Failed(OutcomeException):
""" raised from an explicit call to pytest.fail() """
__module__ = 'builtins'
class Exit(KeyboardInterrupt):
""" raised for immediate program exits (no tracebacks/summaries)"""
def __init__(self, msg="unknown reason"):
self.msg = msg
KeyboardInterrupt.__init__(self, msg)
# exposed helper methods
def exit(msg):
""" exit testing process as if KeyboardInterrupt was triggered. """
__tracebackhide__ = True
raise Exit(msg)
exit.Exception = Exit
def skip(msg=""):
""" skip an executing test with the given message. Note: it's usually
better to use the pytest.mark.skipif marker to declare a test to be
skipped under certain conditions like mismatching platforms or
dependencies. See the pytest_skipping plugin for details.
"""
__tracebackhide__ = True
raise Skipped(msg=msg)
skip.Exception = Skipped
def fail(msg="", pytrace=True):
""" explicitly fail an currently-executing test with the given Message.
:arg pytrace: if false the msg represents the full failure information
and no python traceback will be reported.
"""
__tracebackhide__ = True
raise Failed(msg=msg, pytrace=pytrace)
fail.Exception = Failed
class XFailed(fail.Exception):
""" raised from an explicit call to pytest.xfail() """
def xfail(reason=""):
""" xfail an executing test or setup functions with the given reason."""
__tracebackhide__ = True
raise XFailed(reason)
xfail.Exception = XFailed
def importorskip(modname, minversion=None):
""" return imported module if it has at least "minversion" as its
__version__ attribute. If no minversion is specified the a skip
is only triggered if the module can not be imported.
"""
import warnings
__tracebackhide__ = True
compile(modname, '', 'eval') # to catch syntaxerrors
should_skip = False
with warnings.catch_warnings():
# make sure to ignore ImportWarnings that might happen because
# of existing directories with the same name we're trying to
# import but without a __init__.py file
warnings.simplefilter('ignore')
try:
__import__(modname)
except ImportError:
# Do not raise chained exception here(#1485)
should_skip = True
if should_skip:
raise Skipped("could not import %r" % (modname,), allow_module_level=True)
mod = sys.modules[modname]
if minversion is None:
return mod
verattr = getattr(mod, '__version__', None)
if minversion is not None:
try:
from pkg_resources import parse_version as pv
except ImportError:
raise Skipped("we have a required version for %r but can not import "
"pkg_resources to parse version strings." % (modname,),
allow_module_level=True)
if verattr is None or pv(verattr) < pv(minversion):
raise Skipped("module %r has __version__ %r, required is: %r" % (
modname, verattr, minversion), allow_module_level=True)
return mod

View File

@@ -9,9 +9,9 @@ import tempfile
def pytest_addoption(parser):
group = parser.getgroup("terminal reporting")
group._addoption('--pastebin', metavar="mode",
action='store', dest="pastebin", default=None,
choices=['failed', 'all'],
help="send failed|all info to bpaste.net pastebin service.")
action='store', dest="pastebin", default=None,
choices=['failed', 'all'],
help="send failed|all info to bpaste.net pastebin service.")
@pytest.hookimpl(trylast=True)
@@ -97,4 +97,4 @@ def pytest_terminal_summary(terminalreporter):
s = tw.stringio.getvalue()
assert len(s)
pastebinurl = create_new_paste(s)
tr.write_line("%s --> %s" %(msg, pastebinurl))
tr.write_line("%s --> %s" % (msg, pastebinurl))

View File

@@ -25,13 +25,13 @@ from _pytest.assertion.rewrite import AssertionRewritingHook
def pytest_addoption(parser):
# group = parser.getgroup("pytester", "pytester (self-tests) options")
parser.addoption('--lsof',
action="store_true", dest="lsof", default=False,
help=("run FD checks if lsof is available"))
action="store_true", dest="lsof", default=False,
help=("run FD checks if lsof is available"))
parser.addoption('--runpytest', default="inprocess", dest="runpytest",
choices=("inprocess", "subprocess", ),
help=("run pytest sub runs in tests using an 'inprocess' "
"or 'subprocess' (python -m main) method"))
choices=("inprocess", "subprocess", ),
help=("run pytest sub runs in tests using an 'inprocess' "
"or 'subprocess' (python -m main) method"))
def pytest_configure(config):
@@ -62,7 +62,7 @@ class LsofFdLeakChecker(object):
def _parse_lsof_output(self, out):
def isopen(line):
return line.startswith('f') and ("deleted" not in line and
'mem' not in line and "txt" not in line and 'cwd' not in line)
'mem' not in line and "txt" not in line and 'cwd' not in line)
open_files = []
@@ -122,6 +122,7 @@ winpymap = {
'python3.5': r'C:\Python35\python.exe',
}
def getexecutable(name, cache={}):
try:
return cache[name]
@@ -130,19 +131,20 @@ def getexecutable(name, cache={}):
if executable:
import subprocess
popen = subprocess.Popen([str(executable), "--version"],
universal_newlines=True, stderr=subprocess.PIPE)
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
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.6', 'python2.7', 'python3.3', "python3.4",
'pypy', 'pypy3'])
def anypython(request):
@@ -159,6 +161,8 @@ def anypython(request):
return executable
# used at least by pytest-xdist plugin
@pytest.fixture
def _pytest(request):
""" Return a helper which offers a gethookrecorder(hook)
@@ -167,6 +171,7 @@ def _pytest(request):
"""
return PytestArg(request)
class PytestArg:
def __init__(self, request):
self.request = request
@@ -190,7 +195,7 @@ class ParsedCall:
def __repr__(self):
d = self.__dict__.copy()
del d['_name']
return "<ParsedCall %r(**%r)>" %(self._name, d)
return "<ParsedCall %r(**%r)>" % (self._name, d)
class HookRecorder:
@@ -264,7 +269,7 @@ class HookRecorder:
return [x.report for x in self.getcalls(names)]
def matchreport(self, inamepart="",
names="pytest_runtest_logreport pytest_collectreport", when=None):
names="pytest_runtest_logreport pytest_collectreport", when=None):
""" return a testreport whose dotted import path matches """
l = []
for rep in self.getreports(names=names):
@@ -283,7 +288,7 @@ class HookRecorder:
"no test reports at all!" % (inamepart,))
if len(l) > 1:
raise ValueError(
"found 2 or more testreports matching %r: %s" %(inamepart, l))
"found 2 or more testreports matching %r: %s" % (inamepart, l))
return l[0]
def getfailures(self,
@@ -298,7 +303,7 @@ class HookRecorder:
skipped = []
failed = []
for rep in self.getreports(
"pytest_collectreport pytest_runtest_logreport"):
"pytest_collectreport pytest_runtest_logreport"):
if rep.passed:
if getattr(rep, "when", None) == "call":
passed.append(rep)
@@ -337,6 +342,8 @@ def testdir(request, tmpdir_factory):
rex_outcome = re.compile(r"(\d+) ([\w-]+)")
class RunResult:
"""The result of running a command.
@@ -352,6 +359,7 @@ class RunResult:
:duration: Duration in seconds.
"""
def __init__(self, ret, outlines, errlines, duration):
self.ret = ret
self.outlines = outlines
@@ -373,14 +381,17 @@ class RunResult:
return d
raise ValueError("Pytest terminal report not found")
def assert_outcomes(self, passed=0, skipped=0, failed=0):
def assert_outcomes(self, passed=0, skipped=0, failed=0, error=0):
""" assert that the specified outcomes appear with the respective
numbers (0 means it didn't occur) in the text output from a test run."""
d = self.parseoutcomes()
assert passed == d.get("passed", 0)
assert skipped == d.get("skipped", 0)
assert failed == d.get("failed", 0)
obtained = {
'passed': d.get('passed', 0),
'skipped': d.get('skipped', 0),
'failed': d.get('failed', 0),
'error': d.get('error', 0),
}
assert obtained == dict(passed=passed, skipped=skipped, failed=failed, error=error)
class Testdir:
@@ -406,7 +417,7 @@ class Testdir:
def __init__(self, request, tmpdir_factory):
self.request = request
self._mod_collections = WeakKeyDictionary()
self._mod_collections = WeakKeyDictionary()
# XXX remove duplication with tmpdir plugin
basetmp = tmpdir_factory.ensuretemp("testdir")
name = request.function.__name__
@@ -420,7 +431,7 @@ class Testdir:
self.plugins = []
self._savesyspath = (list(sys.path), list(sys.meta_path))
self._savemodulekeys = set(sys.modules)
self.chdir() # always chdir
self.chdir() # always chdir
self.request.addfinalizer(self.finalize)
method = self.request.config.getoption("--runpytest")
if method == "inprocess":
@@ -495,8 +506,8 @@ class Testdir:
source_unicode = "\n".join([my_totext(line) for line in source.lines])
source = py.builtin._totext(source_unicode)
content = source.strip().encode(encoding) # + "\n"
#content = content.rstrip() + "\n"
content = source.strip().encode(encoding) # + "\n"
# content = content.rstrip() + "\n"
p.write(content, "wb")
if ret is None:
ret = p
@@ -581,6 +592,7 @@ class Testdir:
return p
Session = Session
def getnode(self, config, arg):
"""Return the collection node of a file.
@@ -763,7 +775,7 @@ class Testdir:
res = RunResult(reprec.ret,
out.split("\n"), err.split("\n"),
time.time()-now)
time.time() - now)
res.reprec = reprec
return res
@@ -779,11 +791,11 @@ class Testdir:
args = [str(x) for x in args]
for x in args:
if str(x).startswith('--basetemp'):
#print ("basedtemp exists: %s" %(args,))
# print("basedtemp exists: %s" %(args,))
break
else:
args.append("--basetemp=%s" % self.tmpdir.dirpath('basetemp'))
#print ("added basetemp: %s" %(args,))
# print("added basetemp: %s" %(args,))
return args
def parseconfig(self, *args):
@@ -821,7 +833,7 @@ class Testdir:
self.request.addfinalizer(config._ensure_unconfigure)
return config
def getitem(self, source, funcname="test_func"):
def getitem(self, source, funcname="test_func"):
"""Return the test item for a test function.
This writes the source to a python file and runs pytest's
@@ -838,10 +850,10 @@ class Testdir:
for item in items:
if item.name == funcname:
return item
assert 0, "%r item not found in module:\n%s\nitems: %s" %(
assert 0, "%r item not found in module:\n%s\nitems: %s" % (
funcname, source, items)
def getitems(self, source):
def getitems(self, source):
"""Return all test items collected from the module.
This writes the source to a python file and runs pytest's
@@ -852,7 +864,7 @@ class Testdir:
modcol = self.getmodulecol(source)
return self.genitems([modcol])
def getmodulecol(self, source, configargs=(), withinit=False):
def getmodulecol(self, source, configargs=(), withinit=False):
"""Return the module collection node for ``source``.
This writes ``source`` to a file using :py:meth:`makepyfile`
@@ -871,7 +883,7 @@ class Testdir:
kw = {self.request.function.__name__: Source(source).strip()}
path = self.makepyfile(**kw)
if withinit:
self.makepyfile(__init__ = "#")
self.makepyfile(__init__="#")
self.config = config = self.parseconfigure(path, *configargs)
node = self.getnode(config, path)
@@ -908,8 +920,11 @@ class Testdir:
env['PYTHONPATH'] = os.pathsep.join(filter(None, [
str(os.getcwd()), env.get('PYTHONPATH', '')]))
kw['env'] = env
return subprocess.Popen(cmdargs,
stdout=stdout, stderr=stderr, **kw)
popen = subprocess.Popen(cmdargs, stdin=subprocess.PIPE, stdout=stdout, stderr=stderr, **kw)
popen.stdin.close()
return popen
def run(self, *cmdargs):
"""Run a command with arguments.
@@ -933,7 +948,7 @@ class Testdir:
try:
now = time.time()
popen = self.popen(cmdargs, stdout=f1, stderr=f2,
close_fds=(sys.platform != "win32"))
close_fds=(sys.platform != "win32"))
ret = popen.wait()
finally:
f1.close()
@@ -948,7 +963,7 @@ class Testdir:
f2.close()
self._dump_lines(out, sys.stdout)
self._dump_lines(err, sys.stderr)
return RunResult(ret, out, err, time.time()-now)
return RunResult(ret, out, err, time.time() - now)
def _dump_lines(self, lines, fp):
try:
@@ -960,7 +975,7 @@ class Testdir:
def _getpytestargs(self):
# we cannot use "(sys.executable,script)"
# because on windows the script is e.g. a pytest.exe
return (sys.executable, _pytest_fullpath,) # noqa
return (sys.executable, _pytest_fullpath,) # noqa
def runpython(self, script):
"""Run a python script using sys.executable as interpreter.
@@ -987,12 +1002,12 @@ class Testdir:
"""
p = py.path.local.make_numbered_dir(prefix="runpytest-",
keep=None, rootdir=self.tmpdir)
keep=None, rootdir=self.tmpdir)
args = ('--basetemp=%s' % p, ) + args
#for x in args:
# for x in args:
# if '--confcutdir' in str(x):
# break
#else:
# else:
# pass
# args = ('--confcutdir=.',) + args
plugins = [x for x in self.plugins if isinstance(x, str)]
@@ -1031,12 +1046,13 @@ class Testdir:
child.timeout = expect_timeout
return child
def getdecoded(out):
try:
return out.decode("utf-8")
except UnicodeDecodeError:
return "INTERNAL not-utf8-decodeable, truncated string:\n%s" % (
py.io.saferepr(out),)
try:
return out.decode("utf-8")
except UnicodeDecodeError:
return "INTERNAL not-utf8-decodeable, truncated string:\n%s" % (
py.io.saferepr(out),)
class LineComp:
@@ -1066,7 +1082,7 @@ class LineMatcher:
"""
def __init__(self, lines):
def __init__(self, lines):
self.lines = lines
self._log_output = []
@@ -1105,7 +1121,7 @@ class LineMatcher:
"""
for i, line in enumerate(self.lines):
if fnline == line or fnmatch(line, fnline):
return self.lines[i+1:]
return self.lines[i + 1:]
raise ValueError("line %r not found in output" % fnline)
def _log(self, *args):

View File

@@ -6,7 +6,7 @@ import inspect
import sys
import os
import collections
import math
from textwrap import dedent
from itertools import count
import py
@@ -18,12 +18,13 @@ import _pytest._pluggy as pluggy
from _pytest import fixtures
from _pytest import main
from _pytest.compat import (
isclass, isfunction, is_generator, _escape_strings,
isclass, isfunction, is_generator, _ascii_escaped,
REGEX_TYPE, STRING_TYPES, NoneType, NOTSET,
get_real_func, getfslineno, safe_getattr,
safe_str, getlocation, enum,
)
from _pytest.runner import fail
from _pytest.outcomes import fail
from _pytest.mark import transfer_markers
cutdir1 = py.path.local(pluggy.__file__.rstrip("oc"))
cutdir2 = py.path.local(_pytest.__file__).dirpath()
@@ -48,7 +49,6 @@ def filter_traceback(entry):
return p != cutdir1 and not p.relto(cutdir2) and not p.relto(cutdir3)
def pyobj_property(name):
def get(self):
node = self.getparent(getattr(__import__('pytest'), name))
@@ -62,8 +62,8 @@ def pyobj_property(name):
def pytest_addoption(parser):
group = parser.getgroup("general")
group.addoption('--fixtures', '--funcargs',
action="store_true", dest="showfixtures", default=False,
help="show available fixtures, sorted by plugin appearance")
action="store_true", dest="showfixtures", default=False,
help="show available fixtures, sorted by plugin appearance")
group.addoption(
'--fixtures-per-test',
action="store_true",
@@ -72,20 +72,20 @@ def pytest_addoption(parser):
help="show fixtures per test",
)
parser.addini("usefixtures", type="args", default=[],
help="list of default fixtures to be used with this project")
help="list of default fixtures to be used with this project")
parser.addini("python_files", type="args",
default=['test_*.py', '*_test.py'],
help="glob-style file patterns for Python test module discovery")
parser.addini("python_classes", type="args", default=["Test",],
help="prefixes or glob names for Python test class discovery")
parser.addini("python_functions", type="args", default=["test",],
help="prefixes or glob names for Python test function and "
"method discovery")
default=['test_*.py', '*_test.py'],
help="glob-style file patterns for Python test module discovery")
parser.addini("python_classes", type="args", default=["Test", ],
help="prefixes or glob names for Python test class discovery")
parser.addini("python_functions", type="args", default=["test", ],
help="prefixes or glob names for Python test function and "
"method discovery")
group.addoption("--import-mode", default="prepend",
choices=["prepend", "append"], dest="importmode",
help="prepend/append to sys.path when importing test modules, "
"default is to prepend.")
choices=["prepend", "append"], dest="importmode",
help="prepend/append to sys.path when importing test modules, "
"default is to prepend.")
def pytest_cmdline_main(config):
@@ -112,21 +112,22 @@ def pytest_generate_tests(metafunc):
for marker in markers:
metafunc.parametrize(*marker.args, **marker.kwargs)
def pytest_configure(config):
config.addinivalue_line("markers",
"parametrize(argnames, argvalues): call a test function multiple "
"times passing in different arguments in turn. argvalues generally "
"needs to be a list of values if argnames specifies only one name "
"or a list of tuples of values if argnames specifies multiple names. "
"Example: @parametrize('arg1', [1,2]) would lead to two calls of the "
"decorated test function, one with arg1=1 and another with arg1=2."
"see http://pytest.org/latest/parametrize.html for more info and "
"examples."
)
"parametrize(argnames, argvalues): call a test function multiple "
"times passing in different arguments in turn. argvalues generally "
"needs to be a list of values if argnames specifies only one name "
"or a list of tuples of values if argnames specifies multiple names. "
"Example: @parametrize('arg1', [1,2]) would lead to two calls of the "
"decorated test function, one with arg1=1 and another with arg1=2."
"see http://pytest.org/latest/parametrize.html for more info and "
"examples."
)
config.addinivalue_line("markers",
"usefixtures(fixturename1, fixturename2, ...): mark tests as needing "
"all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures "
)
"usefixtures(fixturename1, fixturename2, ...): mark tests as needing "
"all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures "
)
@hookimpl(trylast=True)
@@ -151,13 +152,15 @@ def pytest_collect_file(path, parent):
if path.fnmatch(pat):
break
else:
return
return
ihook = parent.session.gethookproxy(path)
return ihook.pytest_pycollect_makemodule(path=path, parent=parent)
def pytest_pycollect_makemodule(path, parent):
return Module(path, parent)
@hookimpl(hookwrapper=True)
def pytest_pycollect_makeitem(collector, name, obj):
outcome = yield
@@ -176,9 +179,8 @@ def pytest_pycollect_makeitem(collector, name, obj):
# or a funtools.wrapped.
# We musn't if it's been wrapped with mock.patch (python 2 only)
if not (isfunction(obj) or isfunction(get_real_func(obj))):
collector.warn(code="C2", message=
"cannot collect %r because it is not a function."
% name, )
collector.warn(code="C2", message="cannot collect %r because it is not a function."
% name, )
elif getattr(obj, "__test__", True):
if is_generator(obj):
res = Generator(name, parent=collector)
@@ -186,16 +188,17 @@ def pytest_pycollect_makeitem(collector, name, obj):
res = list(collector._genfunctions(name, obj))
outcome.force_result(res)
def pytest_make_parametrize_id(config, val, argname=None):
return None
class PyobjContext(object):
module = pyobj_property("Module")
cls = pyobj_property("Class")
instance = pyobj_property("Instance")
class PyobjMixin(PyobjContext):
def obj():
def fget(self):
@@ -253,6 +256,7 @@ class PyobjMixin(PyobjContext):
assert isinstance(lineno, int)
return fspath, lineno, modpath
class PyCollector(PyobjMixin, main.Collector):
def funcnamefilter(self, name):
@@ -271,10 +275,22 @@ class PyCollector(PyobjMixin, main.Collector):
return self._matches_prefix_or_glob_option('python_classes', name)
def istestfunction(self, obj, name):
return (
(self.funcnamefilter(name) or self.isnosetest(obj)) and
safe_getattr(obj, "__call__", False) and fixtures.getfixturemarker(obj) is None
)
if self.funcnamefilter(name) or self.isnosetest(obj):
if isinstance(obj, staticmethod):
# static methods need to be unwrapped
obj = safe_getattr(obj, '__func__', False)
if obj is False:
# Python 2.6 wraps in a different way that we won't try to handle
msg = "cannot collect static method %r because " \
"it is not a function (always the case in Python 2.6)"
self.warn(
code="C2", message=msg % name)
return False
return (
safe_getattr(obj, "__call__", False) and fixtures.getfixturemarker(obj) is None
)
else:
return False
def istestclass(self, obj, name):
return self.classnamefilter(name) or self.isnosetest(obj)
@@ -321,7 +337,7 @@ class PyCollector(PyobjMixin, main.Collector):
return l
def makeitem(self, name, obj):
#assert self.ihook.fspath == self.fspath, self
# assert self.ihook.fspath == self.fspath, self
return self.ihook.pytest_pycollect_makeitem(
collector=self, name=name, obj=obj)
@@ -357,40 +373,11 @@ class PyCollector(PyobjMixin, main.Collector):
yield Function(name=subname, parent=self,
callspec=callspec, callobj=funcobj,
fixtureinfo=fixtureinfo,
keywords={callspec.id:True},
keywords={callspec.id: True},
originalname=name,
)
def _marked(func, mark):
""" Returns True if :func: is already marked with :mark:, False otherwise.
This can happen if marker is applied to class and the test file is
invoked more than once.
"""
try:
func_mark = getattr(func, mark.name)
except AttributeError:
return False
return mark.args == func_mark.args and mark.kwargs == func_mark.kwargs
def transfer_markers(funcobj, cls, mod):
# XXX this should rather be code in the mark plugin or the mark
# plugin should merge with the python plugin.
for holder in (cls, mod):
try:
pytestmark = holder.pytestmark
except AttributeError:
continue
if isinstance(pytestmark, list):
for mark in pytestmark:
if not _marked(funcobj, mark):
mark(funcobj)
else:
if not _marked(funcobj, pytestmark):
pytestmark(funcobj)
class Module(main.File, PyCollector):
""" Collector for test classes and functions. """
@@ -419,7 +406,7 @@ class Module(main.File, PyCollector):
" %s\n"
"HINT: remove __pycache__ / .pyc files and/or use a "
"unique basename for your test file modules"
% e.args
% e.args
)
except ImportError:
from _pytest._code.code import ExceptionInfo
@@ -438,9 +425,10 @@ class Module(main.File, PyCollector):
if e.allow_module_level:
raise
raise self.CollectError(
"Using pytest.skip outside of a test is not allowed. If you are "
"trying to decorate a test function, use the @pytest.mark.skip "
"or @pytest.mark.skipif decorators instead."
"Using pytest.skip outside of a test is not allowed. "
"To decorate a test function, use the @pytest.mark.skip "
"or @pytest.mark.skipif decorators instead, and to skip a "
"module use `pytestmark = pytest.mark.{skip,skipif}."
)
self.config.pluginmanager.consider_module(mod)
return mod
@@ -491,12 +479,13 @@ def _get_xunit_func(obj, name):
class Class(PyCollector):
""" Collector for test methods. """
def collect(self):
if not safe_getattr(self.obj, "__test__", True):
return []
if hasinit(self.obj):
self.warn("C1", "cannot collect test class %r because it has a "
"__init__ constructor" % self.obj.__name__)
"__init__ constructor" % self.obj.__name__)
return []
elif hasnew(self.obj):
self.warn("C1", "cannot collect test class %r because it has a "
@@ -517,6 +506,7 @@ class Class(PyCollector):
fin_class = getattr(fin_class, '__func__', fin_class)
self.addfinalizer(lambda: fin_class(self.obj))
class Instance(PyCollector):
def _getobj(self):
return self.parent.obj()
@@ -529,6 +519,7 @@ class Instance(PyCollector):
self.obj = self._getobj()
return self.obj
class FunctionMixin(PyobjMixin):
""" mixin for the code common to Function and Generator.
"""
@@ -564,7 +555,7 @@ class FunctionMixin(PyobjMixin):
if ntraceback == traceback:
ntraceback = ntraceback.cut(path=path)
if ntraceback == traceback:
#ntraceback = ntraceback.cut(excludepath=cutdir2)
# ntraceback = ntraceback.cut(excludepath=cutdir2)
ntraceback = ntraceback.filter(filter_traceback)
if not ntraceback:
ntraceback = traceback
@@ -582,7 +573,7 @@ class FunctionMixin(PyobjMixin):
if not excinfo.value.pytrace:
return py._builtin._totext(excinfo.value)
return super(FunctionMixin, self)._repr_failure_py(excinfo,
style=style)
style=style)
def repr_failure(self, excinfo, outerr=None):
assert outerr is None, "XXX outerr usage is deprecated"
@@ -606,16 +597,16 @@ class Generator(FunctionMixin, PyCollector):
for i, x in enumerate(self.obj()):
name, call, args = self.getcallargs(x)
if not callable(call):
raise TypeError("%r yielded non callable test %r" %(self.obj, call,))
raise TypeError("%r yielded non callable test %r" % (self.obj, call,))
if name is None:
name = "[%d]" % i
else:
name = "['%s']" % name
if name in seen:
raise ValueError("%r generated tests with non-unique name %r" %(self, name))
raise ValueError("%r generated tests with non-unique name %r" % (self, name))
seen[name] = True
l.append(self.Function(name, self, args=args, callobj=call))
self.config.warn('C1', deprecated.YIELD_TESTS, fslocation=self.fspath)
self.warn('C1', deprecated.YIELD_TESTS)
return l
def getcallargs(self, obj):
@@ -671,7 +662,7 @@ class CallSpec2(object):
def _checkargnotcontained(self, arg):
if arg in self.params or arg in self.funcargs:
raise ValueError("duplicate %r" %(arg,))
raise ValueError("duplicate %r" % (arg,))
def getparam(self, name):
try:
@@ -687,7 +678,7 @@ class CallSpec2(object):
def setmulti(self, valtypes, argnames, valset, id, keywords, scopenum,
param_index):
for arg,val in zip(argnames, valset):
for arg, val in zip(argnames, valset):
self._checkargnotcontained(arg)
valtype_for_arg = valtypes[arg]
getattr(self, valtype_for_arg)[arg] = val
@@ -716,6 +707,7 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
test configuration or values specified in the class or module where a
test function is defined.
"""
def __init__(self, function, fixtureinfo, config, cls=None, module=None):
#: access to the :class:`_pytest.config.Config` object for the test session
self.config = config
@@ -737,7 +729,7 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
self._arg2fixturedefs = fixtureinfo.name2fixturedefs
def parametrize(self, argnames, argvalues, indirect=False, ids=None,
scope=None):
scope=None):
""" Add new invocations to the underlying test function using the list
of argvalues for the given argnames. Parametrization is performed
during the collection phase. If you need to setup expensive resources
@@ -792,7 +784,7 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
if not parameters:
fs, lineno = getfslineno(self.function)
reason = "got empty parameter set %r, function %s at %s:%d" % (
argnames, self.function.__name__, fs, lineno)
argnames, self.function.__name__, fs, lineno)
mark = MARK_GEN.skip(reason=reason)
parameters.append(ParameterSet(
values=(NOTSET,) * len(argnames),
@@ -813,7 +805,7 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
name = 'fixture' if indirect else 'argument'
raise ValueError(
"%r uses no %s %r" % (
self.function, name, arg))
self.function, name, arg))
if indirect is True:
valtypes = dict.fromkeys(argnames, "params")
@@ -904,7 +896,7 @@ def _find_parametrized_scope(argnames, arg2fixturedefs, indirect):
from _pytest.fixtures import scopes
indirect_as_list = isinstance(indirect, (list, tuple))
all_arguments_are_fixtures = indirect is True or \
indirect_as_list and len(indirect) == argnames
indirect_as_list and len(indirect) == argnames
if all_arguments_are_fixtures:
fixturedefs = arg2fixturedefs or {}
used_scopes = [fixturedef[0].scope for name, fixturedef in fixturedefs.items()]
@@ -929,7 +921,7 @@ def _idval(val, argname, idx, idfn, config=None):
msg += '\nUpdate your code as this will raise an error in pytest-4.0.'
warnings.warn(msg, DeprecationWarning)
if s:
return _escape_strings(s)
return _ascii_escaped(s)
if config:
hook_id = config.hook.pytest_make_parametrize_id(
@@ -938,16 +930,16 @@ def _idval(val, argname, idx, idfn, config=None):
return hook_id
if isinstance(val, STRING_TYPES):
return _escape_strings(val)
return _ascii_escaped(val)
elif isinstance(val, (float, int, bool, NoneType)):
return str(val)
elif isinstance(val, REGEX_TYPE):
return _escape_strings(val.pattern)
return _ascii_escaped(val.pattern)
elif enum is not None and isinstance(val, enum.Enum):
return str(val)
elif isclass(val) and hasattr(val, '__name__'):
return val.__name__
return str(argname)+str(idx)
return str(argname) + str(idx)
def _idvalset(idx, parameterset, argnames, idfn, ids, config=None):
@@ -958,7 +950,7 @@ def _idvalset(idx, parameterset, argnames, idfn, ids, config=None):
for val, argname in zip(parameterset.values, argnames)]
return "-".join(this_id)
else:
return _escape_strings(ids[idx])
return _ascii_escaped(ids[idx])
def idmaker(argnames, parametersets, idfn=None, ids=None, config=None):
@@ -1003,14 +995,12 @@ def _show_fixtures_per_test(config, session):
funcargspec = argname
tw.line(funcargspec, green=True)
INDENT = ' {0}'
fixture_doc = fixture_def.func.__doc__
if fixture_doc:
for line in fixture_doc.strip().split('\n'):
tw.line(INDENT.format(line.strip()))
write_docstring(tw, fixture_doc)
else:
tw.line(INDENT.format('no docstring available'), red=True)
tw.line(' no docstring available', red=True)
def write_item(item):
name2fixturedefs = item._fixtureinfo.name2fixturedefs
@@ -1072,458 +1062,46 @@ def _showfixtures_main(config, session):
if currentmodule != module:
if not module.startswith("_pytest."):
tw.line()
tw.sep("-", "fixtures defined from %s" %(module,))
tw.sep("-", "fixtures defined from %s" % (module,))
currentmodule = module
if verbose <= 0 and argname[0] == "_":
continue
if verbose > 0:
funcargspec = "%s -- %s" %(argname, bestrel,)
funcargspec = "%s -- %s" % (argname, bestrel,)
else:
funcargspec = argname
tw.line(funcargspec, green=True)
loc = getlocation(fixturedef.func, curdir)
doc = fixturedef.func.__doc__ or ""
if doc:
for line in doc.strip().split("\n"):
tw.line(" " + line.strip())
write_docstring(tw, doc)
else:
tw.line(" %s: no docstring available" %(loc,),
red=True)
tw.line(" %s: no docstring available" % (loc,),
red=True)
# builtin pytest.raises helper
def raises(expected_exception, *args, **kwargs):
"""
Assert that a code block/function call raises ``expected_exception``
and raise a failure exception otherwise.
This helper produces a ``ExceptionInfo()`` object (see below).
If using Python 2.5 or above, you may use this function as a
context manager::
>>> with raises(ZeroDivisionError):
... 1/0
.. versionchanged:: 2.10
In the context manager form you may use the keyword argument
``message`` to specify a custom failure message::
>>> with raises(ZeroDivisionError, message="Expecting ZeroDivisionError"):
... pass
Traceback (most recent call last):
...
Failed: Expecting ZeroDivisionError
.. note::
When using ``pytest.raises`` as a context manager, it's worthwhile to
note that normal context manager rules apply and that the exception
raised *must* be the final line in the scope of the context manager.
Lines of code after that, within the scope of the context manager will
not be executed. For example::
>>> value = 15
>>> with raises(ValueError) as exc_info:
... if value > 10:
... raise ValueError("value must be <= 10")
... assert exc_info.type == ValueError # this will not execute
Instead, the following approach must be taken (note the difference in
scope)::
>>> with raises(ValueError) as exc_info:
... if value > 10:
... raise ValueError("value must be <= 10")
...
>>> assert exc_info.type == ValueError
Or you can use the keyword argument ``match`` to assert that the
exception matches a text or regex::
>>> with raises(ValueError, match='must be 0 or None'):
... raise ValueError("value must be 0 or None")
>>> with raises(ValueError, match=r'must be \d+$'):
... raise ValueError("value must be 42")
Or you can specify a callable by passing a to-be-called lambda::
>>> raises(ZeroDivisionError, lambda: 1/0)
<ExceptionInfo ...>
or you can specify an arbitrary callable with arguments::
>>> def f(x): return 1/x
...
>>> raises(ZeroDivisionError, f, 0)
<ExceptionInfo ...>
>>> raises(ZeroDivisionError, f, x=0)
<ExceptionInfo ...>
A third possibility is to use a string to be executed::
>>> raises(ZeroDivisionError, "f(0)")
<ExceptionInfo ...>
.. autoclass:: _pytest._code.ExceptionInfo
:members:
.. note::
Similar to caught exception objects in Python, explicitly clearing
local references to returned ``ExceptionInfo`` objects can
help the Python interpreter speed up its garbage collection.
Clearing those references breaks a reference cycle
(``ExceptionInfo`` --> caught exception --> frame stack raising
the exception --> current frame stack --> local variables -->
``ExceptionInfo``) which makes Python keep all objects referenced
from that cycle (including all local variables in the current
frame) alive until the next cyclic garbage collection run. See the
official Python ``try`` statement documentation for more detailed
information.
"""
__tracebackhide__ = True
msg = ("exceptions must be old-style classes or"
" derived from BaseException, not %s")
if isinstance(expected_exception, tuple):
for exc in expected_exception:
if not isclass(exc):
raise TypeError(msg % type(exc))
elif not isclass(expected_exception):
raise TypeError(msg % type(expected_exception))
message = "DID NOT RAISE {0}".format(expected_exception)
match_expr = None
if not args:
if "message" in kwargs:
message = kwargs.pop("message")
if "match" in kwargs:
match_expr = kwargs.pop("match")
message += " matching '{0}'".format(match_expr)
return RaisesContext(expected_exception, message, match_expr)
elif isinstance(args[0], str):
code, = args
assert isinstance(code, str)
frame = sys._getframe(1)
loc = frame.f_locals.copy()
loc.update(kwargs)
#print "raises frame scope: %r" % frame.f_locals
try:
code = _pytest._code.Source(code).compile()
py.builtin.exec_(code, frame.f_globals, loc)
# XXX didn'T mean f_globals == f_locals something special?
# this is destroyed here ...
except expected_exception:
return _pytest._code.ExceptionInfo()
def write_docstring(tw, doc):
INDENT = " "
doc = doc.rstrip()
if "\n" in doc:
firstline, rest = doc.split("\n", 1)
else:
func = args[0]
try:
func(*args[1:], **kwargs)
except expected_exception:
return _pytest._code.ExceptionInfo()
fail(message)
firstline, rest = doc, ""
if firstline.strip():
tw.line(INDENT + firstline.strip())
raises.Exception = fail.Exception
if rest:
for line in dedent(rest).split("\n"):
tw.write(INDENT + line + "\n")
class RaisesContext(object):
def __init__(self, expected_exception, message, match_expr):
self.expected_exception = expected_exception
self.message = message
self.match_expr = match_expr
self.excinfo = None
def __enter__(self):
self.excinfo = object.__new__(_pytest._code.ExceptionInfo)
return self.excinfo
def __exit__(self, *tp):
__tracebackhide__ = True
if tp[0] is None:
fail(self.message)
if sys.version_info < (2, 7):
# py26: on __exit__() exc_value often does not contain the
# exception value.
# http://bugs.python.org/issue7853
if not isinstance(tp[1], BaseException):
exc_type, value, traceback = tp
tp = exc_type, exc_type(value), traceback
self.excinfo.__init__(tp)
suppress_exception = issubclass(self.excinfo.type, self.expected_exception)
if sys.version_info[0] == 2 and suppress_exception:
sys.exc_clear()
if self.match_expr:
self.excinfo.match(self.match_expr)
return suppress_exception
# builtin pytest.approx helper
class approx(object):
"""
Assert that two numbers (or two sets of numbers) are equal to each other
within some tolerance.
Due to the `intricacies of floating-point arithmetic`__, numbers that we
would intuitively expect to be equal are not always so::
>>> 0.1 + 0.2 == 0.3
False
__ https://docs.python.org/3/tutorial/floatingpoint.html
This problem is commonly encountered when writing tests, e.g. when making
sure that floating-point values are what you expect them to be. One way to
deal with this problem is to assert that two floating-point numbers are
equal to within some appropriate tolerance::
>>> abs((0.1 + 0.2) - 0.3) < 1e-6
True
However, comparisons like this are tedious to write and difficult to
understand. Furthermore, absolute comparisons like the one above are
usually discouraged because there's no tolerance that works well for all
situations. ``1e-6`` is good for numbers around ``1``, but too small for
very big numbers and too big for very small ones. It's better to express
the tolerance as a fraction of the expected value, but relative comparisons
like that are even more difficult to write correctly and concisely.
The ``approx`` class performs floating-point comparisons using a syntax
that's as intuitive as possible::
>>> from pytest import approx
>>> 0.1 + 0.2 == approx(0.3)
True
The same syntax also works on sequences of numbers::
>>> (0.1 + 0.2, 0.2 + 0.4) == approx((0.3, 0.6))
True
By default, ``approx`` considers numbers within a relative tolerance of
``1e-6`` (i.e. one part in a million) of its expected value to be equal.
This treatment would lead to surprising results if the expected value was
``0.0``, because nothing but ``0.0`` itself is relatively close to ``0.0``.
To handle this case less surprisingly, ``approx`` also considers numbers
within an absolute tolerance of ``1e-12`` of its expected value to be
equal. Infinite numbers are another special case. They are only
considered equal to themselves, regardless of the relative tolerance. Both
the relative and absolute tolerances can be changed by passing arguments to
the ``approx`` constructor::
>>> 1.0001 == approx(1)
False
>>> 1.0001 == approx(1, rel=1e-3)
True
>>> 1.0001 == approx(1, abs=1e-3)
True
If you specify ``abs`` but not ``rel``, the comparison will not consider
the relative tolerance at all. In other words, two numbers that are within
the default relative tolerance of ``1e-6`` will still be considered unequal
if they exceed the specified absolute tolerance. If you specify both
``abs`` and ``rel``, the numbers will be considered equal if either
tolerance is met::
>>> 1 + 1e-8 == approx(1)
True
>>> 1 + 1e-8 == approx(1, abs=1e-12)
False
>>> 1 + 1e-8 == approx(1, rel=1e-6, abs=1e-12)
True
If you're thinking about using ``approx``, then you might want to know how
it compares to other good ways of comparing floating-point numbers. All of
these algorithms are based on relative and absolute tolerances and should
agree for the most part, but they do have meaningful differences:
- ``math.isclose(a, b, rel_tol=1e-9, abs_tol=0.0)``: True if the relative
tolerance is met w.r.t. either ``a`` or ``b`` or if the absolute
tolerance is met. Because the relative tolerance is calculated w.r.t.
both ``a`` and ``b``, this test is symmetric (i.e. neither ``a`` nor
``b`` is a "reference value"). You have to specify an absolute tolerance
if you want to compare to ``0.0`` because there is no tolerance by
default. Only available in python>=3.5. `More information...`__
__ https://docs.python.org/3/library/math.html#math.isclose
- ``numpy.isclose(a, b, rtol=1e-5, atol=1e-8)``: True if the difference
between ``a`` and ``b`` is less that the sum of the relative tolerance
w.r.t. ``b`` and the absolute tolerance. Because the relative tolerance
is only calculated w.r.t. ``b``, this test is asymmetric and you can
think of ``b`` as the reference value. Support for comparing sequences
is provided by ``numpy.allclose``. `More information...`__
__ http://docs.scipy.org/doc/numpy-1.10.0/reference/generated/numpy.isclose.html
- ``unittest.TestCase.assertAlmostEqual(a, b)``: True if ``a`` and ``b``
are within an absolute tolerance of ``1e-7``. No relative tolerance is
considered and the absolute tolerance cannot be changed, so this function
is not appropriate for very large or very small numbers. Also, it's only
available in subclasses of ``unittest.TestCase`` and it's ugly because it
doesn't follow PEP8. `More information...`__
__ https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertAlmostEqual
- ``a == pytest.approx(b, rel=1e-6, abs=1e-12)``: True if the relative
tolerance is met w.r.t. ``b`` or if the absolute tolerance is met.
Because the relative tolerance is only calculated w.r.t. ``b``, this test
is asymmetric and you can think of ``b`` as the reference value. In the
special case that you explicitly specify an absolute tolerance but not a
relative tolerance, only the absolute tolerance is considered.
"""
def __init__(self, expected, rel=None, abs=None):
self.expected = expected
self.abs = abs
self.rel = rel
def __repr__(self):
return ', '.join(repr(x) for x in self.expected)
def __eq__(self, actual):
from collections import Iterable
if not isinstance(actual, Iterable):
actual = [actual]
if len(actual) != len(self.expected):
return False
return all(a == x for a, x in zip(actual, self.expected))
__hash__ = None
def __ne__(self, actual):
return not (actual == self)
@property
def expected(self):
# Regardless of whether the user-specified expected value is a number
# or a sequence of numbers, return a list of ApproxNotIterable objects
# that can be compared against.
from collections import Iterable
approx_non_iter = lambda x: ApproxNonIterable(x, self.rel, self.abs)
if isinstance(self._expected, Iterable):
return [approx_non_iter(x) for x in self._expected]
else:
return [approx_non_iter(self._expected)]
@expected.setter
def expected(self, expected):
self._expected = expected
class ApproxNonIterable(object):
"""
Perform approximate comparisons for single numbers only.
In other words, the ``expected`` attribute for objects of this class must
be some sort of number. This is in contrast to the ``approx`` class, where
the ``expected`` attribute can either be a number of a sequence of numbers.
This class is responsible for making comparisons, while ``approx`` is
responsible for abstracting the difference between numbers and sequences of
numbers. Although this class can stand on its own, it's only meant to be
used within ``approx``.
"""
def __init__(self, expected, rel=None, abs=None):
self.expected = expected
self.abs = abs
self.rel = rel
def __repr__(self):
if isinstance(self.expected, complex):
return str(self.expected)
# Infinities aren't compared using tolerances, so don't show a
# tolerance.
if math.isinf(self.expected):
return str(self.expected)
# If a sensible tolerance can't be calculated, self.tolerance will
# raise a ValueError. In this case, display '???'.
try:
vetted_tolerance = '{:.1e}'.format(self.tolerance)
except ValueError:
vetted_tolerance = '???'
if sys.version_info[0] == 2:
return '{0} +- {1}'.format(self.expected, vetted_tolerance)
else:
return u'{0} \u00b1 {1}'.format(self.expected, vetted_tolerance)
def __eq__(self, actual):
# Short-circuit exact equality.
if actual == self.expected:
return True
# Infinity shouldn't be approximately equal to anything but itself, but
# if there's a relative tolerance, it will be infinite and infinity
# will seem approximately equal to everything. The equal-to-itself
# case would have been short circuited above, so here we can just
# return false if the expected value is infinite. The abs() call is
# for compatibility with complex numbers.
if math.isinf(abs(self.expected)):
return False
# Return true if the two numbers are within the tolerance.
return abs(self.expected - actual) <= self.tolerance
__hash__ = None
def __ne__(self, actual):
return not (actual == self)
@property
def tolerance(self):
set_default = lambda x, default: x if x is not None else default
# Figure out what the absolute tolerance should be. ``self.abs`` is
# either None or a value specified by the user.
absolute_tolerance = set_default(self.abs, 1e-12)
if absolute_tolerance < 0:
raise ValueError("absolute tolerance can't be negative: {}".format(absolute_tolerance))
if math.isnan(absolute_tolerance):
raise ValueError("absolute tolerance can't be NaN.")
# If the user specified an absolute tolerance but not a relative one,
# just return the absolute tolerance.
if self.rel is None:
if self.abs is not None:
return absolute_tolerance
# Figure out what the relative tolerance should be. ``self.rel`` is
# either None or a value specified by the user. This is done after
# we've made sure the user didn't ask for an absolute tolerance only,
# because we don't want to raise errors about the relative tolerance if
# we aren't even going to use it.
relative_tolerance = set_default(self.rel, 1e-6) * abs(self.expected)
if relative_tolerance < 0:
raise ValueError("relative tolerance can't be negative: {}".format(absolute_tolerance))
if math.isnan(relative_tolerance):
raise ValueError("relative tolerance can't be NaN.")
# Return the larger of the relative and absolute tolerances.
return max(relative_tolerance, absolute_tolerance)
#
# the basic pytest Function item
#
class Function(FunctionMixin, main.Item, fixtures.FuncargnamesCompatAttr):
""" a Function Item is responsible for setting up and executing a
Python test function.
"""
_genid = None
def __init__(self, name, parent, args=None, config=None,
callspec=None, callobj=NOTSET, keywords=None, session=None,
fixtureinfo=None, originalname=None):
@@ -1575,7 +1153,7 @@ class Function(FunctionMixin, main.Item, fixtures.FuncargnamesCompatAttr):
def _getobj(self):
name = self.name
i = name.find("[") # parametrization
i = name.find("[") # parametrization
if i != -1:
name = name[:i]
return getattr(self.parent.obj, name)

625
_pytest/python_api.py Normal file
View File

@@ -0,0 +1,625 @@
import math
import sys
import py
from _pytest.compat import isclass, izip
from _pytest.outcomes import fail
import _pytest._code
def _cmp_raises_type_error(self, other):
"""__cmp__ implementation which raises TypeError. Used
by Approx base classes to implement only == and != and raise a
TypeError for other comparisons.
Needed in Python 2 only, Python 3 all it takes is not implementing the
other operators at all.
"""
__tracebackhide__ = True
raise TypeError('Comparison operators other than == and != not supported by approx objects')
# builtin pytest.approx helper
class ApproxBase(object):
"""
Provide shared utilities for making approximate comparisons between numbers
or sequences of numbers.
"""
def __init__(self, expected, rel=None, abs=None, nan_ok=False):
self.expected = expected
self.abs = abs
self.rel = rel
self.nan_ok = nan_ok
def __repr__(self):
raise NotImplementedError
def __eq__(self, actual):
return all(
a == self._approx_scalar(x)
for a, x in self._yield_comparisons(actual))
__hash__ = None
def __ne__(self, actual):
return not (actual == self)
if sys.version_info[0] == 2:
__cmp__ = _cmp_raises_type_error
def _approx_scalar(self, x):
return ApproxScalar(x, rel=self.rel, abs=self.abs, nan_ok=self.nan_ok)
def _yield_comparisons(self, actual):
"""
Yield all the pairs of numbers to be compared. This is used to
implement the `__eq__` method.
"""
raise NotImplementedError
class ApproxNumpy(ApproxBase):
"""
Perform approximate comparisons for numpy arrays.
"""
# Tell numpy to use our `__eq__` operator instead of its.
__array_priority__ = 100
def __repr__(self):
# It might be nice to rewrite this function to account for the
# shape of the array...
return "approx({0!r})".format(list(
self._approx_scalar(x) for x in self.expected))
if sys.version_info[0] == 2:
__cmp__ = _cmp_raises_type_error
def __eq__(self, actual):
import numpy as np
try:
actual = np.asarray(actual)
except:
raise TypeError("cannot compare '{0}' to numpy.ndarray".format(actual))
if actual.shape != self.expected.shape:
return False
return ApproxBase.__eq__(self, actual)
def _yield_comparisons(self, actual):
import numpy as np
# We can be sure that `actual` is a numpy array, because it's
# casted in `__eq__` before being passed to `ApproxBase.__eq__`,
# which is the only method that calls this one.
for i in np.ndindex(self.expected.shape):
yield actual[i], self.expected[i]
class ApproxMapping(ApproxBase):
"""
Perform approximate comparisons for mappings where the values are numbers
(the keys can be anything).
"""
def __repr__(self):
return "approx({0!r})".format(dict(
(k, self._approx_scalar(v))
for k, v in self.expected.items()))
def __eq__(self, actual):
if set(actual.keys()) != set(self.expected.keys()):
return False
return ApproxBase.__eq__(self, actual)
def _yield_comparisons(self, actual):
for k in self.expected.keys():
yield actual[k], self.expected[k]
class ApproxSequence(ApproxBase):
"""
Perform approximate comparisons for sequences of numbers.
"""
# Tell numpy to use our `__eq__` operator instead of its.
__array_priority__ = 100
def __repr__(self):
seq_type = type(self.expected)
if seq_type not in (tuple, list, set):
seq_type = list
return "approx({0!r})".format(seq_type(
self._approx_scalar(x) for x in self.expected))
def __eq__(self, actual):
if len(actual) != len(self.expected):
return False
return ApproxBase.__eq__(self, actual)
def _yield_comparisons(self, actual):
return izip(actual, self.expected)
class ApproxScalar(ApproxBase):
"""
Perform approximate comparisons for single numbers only.
"""
def __repr__(self):
"""
Return a string communicating both the expected value and the tolerance
for the comparison being made, e.g. '1.0 +- 1e-6'. Use the unicode
plus/minus symbol if this is python3 (it's too hard to get right for
python2).
"""
if isinstance(self.expected, complex):
return str(self.expected)
# Infinities aren't compared using tolerances, so don't show a
# tolerance.
if math.isinf(self.expected):
return str(self.expected)
# If a sensible tolerance can't be calculated, self.tolerance will
# raise a ValueError. In this case, display '???'.
try:
vetted_tolerance = '{:.1e}'.format(self.tolerance)
except ValueError:
vetted_tolerance = '???'
if sys.version_info[0] == 2:
return '{0} +- {1}'.format(self.expected, vetted_tolerance)
else:
return u'{0} \u00b1 {1}'.format(self.expected, vetted_tolerance)
def __eq__(self, actual):
"""
Return true if the given value is equal to the expected value within
the pre-specified tolerance.
"""
# Short-circuit exact equality.
if actual == self.expected:
return True
# Allow the user to control whether NaNs are considered equal to each
# other or not. The abs() calls are for compatibility with complex
# numbers.
if math.isnan(abs(self.expected)):
return self.nan_ok and math.isnan(abs(actual))
# Infinity shouldn't be approximately equal to anything but itself, but
# if there's a relative tolerance, it will be infinite and infinity
# will seem approximately equal to everything. The equal-to-itself
# case would have been short circuited above, so here we can just
# return false if the expected value is infinite. The abs() call is
# for compatibility with complex numbers.
if math.isinf(abs(self.expected)):
return False
# Return true if the two numbers are within the tolerance.
return abs(self.expected - actual) <= self.tolerance
__hash__ = None
@property
def tolerance(self):
"""
Return the tolerance for the comparison. This could be either an
absolute tolerance or a relative tolerance, depending on what the user
specified or which would be larger.
"""
def set_default(x, default): return x if x is not None else default
# Figure out what the absolute tolerance should be. ``self.abs`` is
# either None or a value specified by the user.
absolute_tolerance = set_default(self.abs, 1e-12)
if absolute_tolerance < 0:
raise ValueError("absolute tolerance can't be negative: {}".format(absolute_tolerance))
if math.isnan(absolute_tolerance):
raise ValueError("absolute tolerance can't be NaN.")
# If the user specified an absolute tolerance but not a relative one,
# just return the absolute tolerance.
if self.rel is None:
if self.abs is not None:
return absolute_tolerance
# Figure out what the relative tolerance should be. ``self.rel`` is
# either None or a value specified by the user. This is done after
# we've made sure the user didn't ask for an absolute tolerance only,
# because we don't want to raise errors about the relative tolerance if
# we aren't even going to use it.
relative_tolerance = set_default(self.rel, 1e-6) * abs(self.expected)
if relative_tolerance < 0:
raise ValueError("relative tolerance can't be negative: {}".format(absolute_tolerance))
if math.isnan(relative_tolerance):
raise ValueError("relative tolerance can't be NaN.")
# Return the larger of the relative and absolute tolerances.
return max(relative_tolerance, absolute_tolerance)
def approx(expected, rel=None, abs=None, nan_ok=False):
"""
Assert that two numbers (or two sets of numbers) are equal to each other
within some tolerance.
Due to the `intricacies of floating-point arithmetic`__, numbers that we
would intuitively expect to be equal are not always so::
>>> 0.1 + 0.2 == 0.3
False
__ https://docs.python.org/3/tutorial/floatingpoint.html
This problem is commonly encountered when writing tests, e.g. when making
sure that floating-point values are what you expect them to be. One way to
deal with this problem is to assert that two floating-point numbers are
equal to within some appropriate tolerance::
>>> abs((0.1 + 0.2) - 0.3) < 1e-6
True
However, comparisons like this are tedious to write and difficult to
understand. Furthermore, absolute comparisons like the one above are
usually discouraged because there's no tolerance that works well for all
situations. ``1e-6`` is good for numbers around ``1``, but too small for
very big numbers and too big for very small ones. It's better to express
the tolerance as a fraction of the expected value, but relative comparisons
like that are even more difficult to write correctly and concisely.
The ``approx`` class performs floating-point comparisons using a syntax
that's as intuitive as possible::
>>> from pytest import approx
>>> 0.1 + 0.2 == approx(0.3)
True
The same syntax also works for sequences of numbers::
>>> (0.1 + 0.2, 0.2 + 0.4) == approx((0.3, 0.6))
True
Dictionary *values*::
>>> {'a': 0.1 + 0.2, 'b': 0.2 + 0.4} == approx({'a': 0.3, 'b': 0.6})
True
And ``numpy`` arrays::
>>> import numpy as np # doctest: +SKIP
>>> np.array([0.1, 0.2]) + np.array([0.2, 0.4]) == approx(np.array([0.3, 0.6])) # doctest: +SKIP
True
By default, ``approx`` considers numbers within a relative tolerance of
``1e-6`` (i.e. one part in a million) of its expected value to be equal.
This treatment would lead to surprising results if the expected value was
``0.0``, because nothing but ``0.0`` itself is relatively close to ``0.0``.
To handle this case less surprisingly, ``approx`` also considers numbers
within an absolute tolerance of ``1e-12`` of its expected value to be
equal. Infinity and NaN are special cases. Infinity is only considered
equal to itself, regardless of the relative tolerance. NaN is not
considered equal to anything by default, but you can make it be equal to
itself by setting the ``nan_ok`` argument to True. (This is meant to
facilitate comparing arrays that use NaN to mean "no data".)
Both the relative and absolute tolerances can be changed by passing
arguments to the ``approx`` constructor::
>>> 1.0001 == approx(1)
False
>>> 1.0001 == approx(1, rel=1e-3)
True
>>> 1.0001 == approx(1, abs=1e-3)
True
If you specify ``abs`` but not ``rel``, the comparison will not consider
the relative tolerance at all. In other words, two numbers that are within
the default relative tolerance of ``1e-6`` will still be considered unequal
if they exceed the specified absolute tolerance. If you specify both
``abs`` and ``rel``, the numbers will be considered equal if either
tolerance is met::
>>> 1 + 1e-8 == approx(1)
True
>>> 1 + 1e-8 == approx(1, abs=1e-12)
False
>>> 1 + 1e-8 == approx(1, rel=1e-6, abs=1e-12)
True
If you're thinking about using ``approx``, then you might want to know how
it compares to other good ways of comparing floating-point numbers. All of
these algorithms are based on relative and absolute tolerances and should
agree for the most part, but they do have meaningful differences:
- ``math.isclose(a, b, rel_tol=1e-9, abs_tol=0.0)``: True if the relative
tolerance is met w.r.t. either ``a`` or ``b`` or if the absolute
tolerance is met. Because the relative tolerance is calculated w.r.t.
both ``a`` and ``b``, this test is symmetric (i.e. neither ``a`` nor
``b`` is a "reference value"). You have to specify an absolute tolerance
if you want to compare to ``0.0`` because there is no tolerance by
default. Only available in python>=3.5. `More information...`__
__ https://docs.python.org/3/library/math.html#math.isclose
- ``numpy.isclose(a, b, rtol=1e-5, atol=1e-8)``: True if the difference
between ``a`` and ``b`` is less that the sum of the relative tolerance
w.r.t. ``b`` and the absolute tolerance. Because the relative tolerance
is only calculated w.r.t. ``b``, this test is asymmetric and you can
think of ``b`` as the reference value. Support for comparing sequences
is provided by ``numpy.allclose``. `More information...`__
__ http://docs.scipy.org/doc/numpy-1.10.0/reference/generated/numpy.isclose.html
- ``unittest.TestCase.assertAlmostEqual(a, b)``: True if ``a`` and ``b``
are within an absolute tolerance of ``1e-7``. No relative tolerance is
considered and the absolute tolerance cannot be changed, so this function
is not appropriate for very large or very small numbers. Also, it's only
available in subclasses of ``unittest.TestCase`` and it's ugly because it
doesn't follow PEP8. `More information...`__
__ https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertAlmostEqual
- ``a == pytest.approx(b, rel=1e-6, abs=1e-12)``: True if the relative
tolerance is met w.r.t. ``b`` or if the absolute tolerance is met.
Because the relative tolerance is only calculated w.r.t. ``b``, this test
is asymmetric and you can think of ``b`` as the reference value. In the
special case that you explicitly specify an absolute tolerance but not a
relative tolerance, only the absolute tolerance is considered.
.. warning::
.. versionchanged:: 3.2
In order to avoid inconsistent behavior, ``TypeError`` is
raised for ``>``, ``>=``, ``<`` and ``<=`` comparisons.
The example below illustrates the problem::
assert approx(0.1) > 0.1 + 1e-10 # calls approx(0.1).__gt__(0.1 + 1e-10)
assert 0.1 + 1e-10 > approx(0.1) # calls approx(0.1).__lt__(0.1 + 1e-10)
In the second example one expects ``approx(0.1).__le__(0.1 + 1e-10)``
to be called. But instead, ``approx(0.1).__lt__(0.1 + 1e-10)`` is used to
comparison. This is because the call hierarchy of rich comparisons
follows a fixed behavior. `More information...`__
__ https://docs.python.org/3/reference/datamodel.html#object.__ge__
"""
from collections import Mapping, Sequence
from _pytest.compat import STRING_TYPES as String
# Delegate the comparison to a class that knows how to deal with the type
# of the expected value (e.g. int, float, list, dict, numpy.array, etc).
#
# This architecture is really driven by the need to support numpy arrays.
# The only way to override `==` for arrays without requiring that approx be
# the left operand is to inherit the approx object from `numpy.ndarray`.
# But that can't be a general solution, because it requires (1) numpy to be
# installed and (2) the expected value to be a numpy array. So the general
# solution is to delegate each type of expected value to a different class.
#
# This has the advantage that it made it easy to support mapping types
# (i.e. dict). The old code accepted mapping types, but would only compare
# their keys, which is probably not what most people would expect.
if _is_numpy_array(expected):
cls = ApproxNumpy
elif isinstance(expected, Mapping):
cls = ApproxMapping
elif isinstance(expected, Sequence) and not isinstance(expected, String):
cls = ApproxSequence
else:
cls = ApproxScalar
return cls(expected, rel, abs, nan_ok)
def _is_numpy_array(obj):
"""
Return true if the given object is a numpy array. Make a special effort to
avoid importing numpy unless it's really necessary.
"""
import inspect
for cls in inspect.getmro(type(obj)):
if cls.__module__ == 'numpy':
try:
import numpy as np
return isinstance(obj, np.ndarray)
except ImportError:
pass
return False
# builtin pytest.raises helper
def raises(expected_exception, *args, **kwargs):
"""
Assert that a code block/function call raises ``expected_exception``
and raise a failure exception otherwise.
This helper produces a ``ExceptionInfo()`` object (see below).
If using Python 2.5 or above, you may use this function as a
context manager::
>>> with raises(ZeroDivisionError):
... 1/0
.. versionchanged:: 2.10
In the context manager form you may use the keyword argument
``message`` to specify a custom failure message::
>>> with raises(ZeroDivisionError, message="Expecting ZeroDivisionError"):
... pass
Traceback (most recent call last):
...
Failed: Expecting ZeroDivisionError
.. note::
When using ``pytest.raises`` as a context manager, it's worthwhile to
note that normal context manager rules apply and that the exception
raised *must* be the final line in the scope of the context manager.
Lines of code after that, within the scope of the context manager will
not be executed. For example::
>>> value = 15
>>> with raises(ValueError) as exc_info:
... if value > 10:
... raise ValueError("value must be <= 10")
... assert exc_info.type == ValueError # this will not execute
Instead, the following approach must be taken (note the difference in
scope)::
>>> with raises(ValueError) as exc_info:
... if value > 10:
... raise ValueError("value must be <= 10")
...
>>> assert exc_info.type == ValueError
Since version ``3.1`` you can use the keyword argument ``match`` to assert that the
exception matches a text or regex::
>>> with raises(ValueError, match='must be 0 or None'):
... raise ValueError("value must be 0 or None")
>>> with raises(ValueError, match=r'must be \d+$'):
... raise ValueError("value must be 42")
**Legacy forms**
The forms below are fully supported but are discouraged for new code because the
context manager form is regarded as more readable and less error-prone.
It is possible to specify a callable by passing a to-be-called lambda::
>>> raises(ZeroDivisionError, lambda: 1/0)
<ExceptionInfo ...>
or you can specify an arbitrary callable with arguments::
>>> def f(x): return 1/x
...
>>> raises(ZeroDivisionError, f, 0)
<ExceptionInfo ...>
>>> raises(ZeroDivisionError, f, x=0)
<ExceptionInfo ...>
It is also possible to pass a string to be evaluated at runtime::
>>> raises(ZeroDivisionError, "f(0)")
<ExceptionInfo ...>
The string will be evaluated using the same ``locals()`` and ``globals()``
at the moment of the ``raises`` call.
.. autoclass:: _pytest._code.ExceptionInfo
:members:
.. note::
Similar to caught exception objects in Python, explicitly clearing
local references to returned ``ExceptionInfo`` objects can
help the Python interpreter speed up its garbage collection.
Clearing those references breaks a reference cycle
(``ExceptionInfo`` --> caught exception --> frame stack raising
the exception --> current frame stack --> local variables -->
``ExceptionInfo``) which makes Python keep all objects referenced
from that cycle (including all local variables in the current
frame) alive until the next cyclic garbage collection run. See the
official Python ``try`` statement documentation for more detailed
information.
"""
__tracebackhide__ = True
msg = ("exceptions must be old-style classes or"
" derived from BaseException, not %s")
if isinstance(expected_exception, tuple):
for exc in expected_exception:
if not isclass(exc):
raise TypeError(msg % type(exc))
elif not isclass(expected_exception):
raise TypeError(msg % type(expected_exception))
message = "DID NOT RAISE {0}".format(expected_exception)
match_expr = None
if not args:
if "message" in kwargs:
message = kwargs.pop("message")
if "match" in kwargs:
match_expr = kwargs.pop("match")
message += " matching '{0}'".format(match_expr)
return RaisesContext(expected_exception, message, match_expr)
elif isinstance(args[0], str):
code, = args
assert isinstance(code, str)
frame = sys._getframe(1)
loc = frame.f_locals.copy()
loc.update(kwargs)
# print "raises frame scope: %r" % frame.f_locals
try:
code = _pytest._code.Source(code).compile()
py.builtin.exec_(code, frame.f_globals, loc)
# XXX didn'T mean f_globals == f_locals something special?
# this is destroyed here ...
except expected_exception:
return _pytest._code.ExceptionInfo()
else:
func = args[0]
try:
func(*args[1:], **kwargs)
except expected_exception:
return _pytest._code.ExceptionInfo()
fail(message)
raises.Exception = fail.Exception
class RaisesContext(object):
def __init__(self, expected_exception, message, match_expr):
self.expected_exception = expected_exception
self.message = message
self.match_expr = match_expr
self.excinfo = None
def __enter__(self):
self.excinfo = object.__new__(_pytest._code.ExceptionInfo)
return self.excinfo
def __exit__(self, *tp):
__tracebackhide__ = True
if tp[0] is None:
fail(self.message)
if sys.version_info < (2, 7):
# py26: on __exit__() exc_value often does not contain the
# exception value.
# http://bugs.python.org/issue7853
if not isinstance(tp[1], BaseException):
exc_type, value, traceback = tp
tp = exc_type, exc_type(value), traceback
self.excinfo.__init__(tp)
suppress_exception = issubclass(self.excinfo.type, self.expected_exception)
if sys.version_info[0] == 2 and suppress_exception:
sys.exc_clear()
if self.match_expr:
self.excinfo.match(self.match_expr)
return suppress_exception

View File

@@ -7,7 +7,9 @@ import _pytest._code
import py
import sys
import warnings
from _pytest.fixtures import yield_fixture
from _pytest.outcomes import fail
@yield_fixture
@@ -197,8 +199,7 @@ class WarningsChecker(WarningsRecorder):
if not any(issubclass(r.category, self.expected_warning)
for r in self):
__tracebackhide__ = True
from _pytest.runner import fail
fail("DID NOT WARN. No warnings of type {0} was emitted. "
"The list of emitted warnings is: {1}.".format(
self.expected_warning,
[each.message for each in self]))
self.expected_warning,
[each.message for each in self]))

View File

@@ -6,11 +6,13 @@ from __future__ import absolute_import, division, print_function
import py
import os
def pytest_addoption(parser):
group = parser.getgroup("terminal reporting", "resultlog plugin options")
group.addoption('--resultlog', '--result-log', action="store",
metavar="path", default=None,
help="DEPRECATED path for machine-readable result log.")
metavar="path", default=None,
help="DEPRECATED path for machine-readable result log.")
def pytest_configure(config):
resultlog = config.option.resultlog
@@ -19,13 +21,14 @@ def pytest_configure(config):
dirname = os.path.dirname(os.path.abspath(resultlog))
if not os.path.isdir(dirname):
os.makedirs(dirname)
logfile = open(resultlog, 'w', 1) # line buffered
logfile = open(resultlog, 'w', 1) # line buffered
config._resultlog = ResultLog(config, logfile)
config.pluginmanager.register(config._resultlog)
from _pytest.deprecated import RESULT_LOG
config.warn('C1', RESULT_LOG)
def pytest_unconfigure(config):
resultlog = getattr(config, '_resultlog', None)
if resultlog:
@@ -33,6 +36,7 @@ def pytest_unconfigure(config):
del config._resultlog
config.pluginmanager.unregister(resultlog)
def generic_path(item):
chain = item.listchain()
gpath = [chain[0].name]
@@ -56,10 +60,11 @@ def generic_path(item):
fspath = newfspath
return ''.join(gpath)
class ResultLog(object):
def __init__(self, config, logfile):
self.config = config
self.logfile = logfile # preferably line buffered
self.logfile = logfile # preferably line buffered
def write_log_entry(self, testpath, lettercode, longrepr):
print("%s %s" % (lettercode, testpath), file=self.logfile)

View File

@@ -2,22 +2,25 @@
from __future__ import absolute_import, division, print_function
import bdb
import os
import sys
from time import time
import py
from _pytest.compat import _PY2
from _pytest._code.code import TerminalRepr, ExceptionInfo
from _pytest.outcomes import skip, Skipped, TEST_OUTCOME
#
# pytest plugin hooks
def pytest_addoption(parser):
group = parser.getgroup("terminal reporting", "reporting", after="general")
group.addoption('--durations',
action="store", type=int, default=None, metavar="N",
help="show N slowest setup/test durations (N=0 for all)."),
action="store", type=int, default=None, metavar="N",
help="show N slowest setup/test durations (N=0 for all)."),
def pytest_terminal_summary(terminalreporter):
durations = terminalreporter.config.option.durations
@@ -42,17 +45,22 @@ def pytest_terminal_summary(terminalreporter):
for rep in dlist:
nodeid = rep.nodeid.replace("::()::", "::")
tr.write_line("%02.2fs %-8s %s" %
(rep.duration, rep.when, nodeid))
(rep.duration, rep.when, nodeid))
def pytest_sessionstart(session):
session._setupstate = SetupState()
def pytest_sessionfinish(session):
session._setupstate.teardown_all()
class NodeInfo:
def __init__(self, location):
self.location = location
def pytest_runtest_protocol(item, nextitem):
item.ihook.pytest_runtest_logstart(
nodeid=item.nodeid, location=item.location,
@@ -60,6 +68,7 @@ def pytest_runtest_protocol(item, nextitem):
runtestprotocol(item, nextitem=nextitem)
return True
def runtestprotocol(item, log=True, nextitem=None):
hasrequest = hasattr(item, "_request")
if hasrequest and not item._request:
@@ -72,7 +81,7 @@ def runtestprotocol(item, log=True, nextitem=None):
if not item.config.option.setuponly:
reports.append(call_and_report(item, "call", log))
reports.append(call_and_report(item, "teardown", log,
nextitem=nextitem))
nextitem=nextitem))
# after all teardown hooks have been called
# want funcargs and request info to go away
if hasrequest:
@@ -80,6 +89,7 @@ def runtestprotocol(item, log=True, nextitem=None):
item.funcargs = None
return reports
def show_test_item(item):
"""Show test function, parameters and the fixtures of the test item."""
tw = item.config.get_terminal_writer()
@@ -90,10 +100,14 @@ def show_test_item(item):
if used_fixtures:
tw.write(' (fixtures used: {0})'.format(', '.join(used_fixtures)))
def pytest_runtest_setup(item):
_update_current_test_var(item, 'setup')
item.session._setupstate.prepare(item)
def pytest_runtest_call(item):
_update_current_test_var(item, 'call')
try:
item.runtest()
except Exception:
@@ -106,8 +120,29 @@ def pytest_runtest_call(item):
del tb # Get rid of it in this namespace
raise
def pytest_runtest_teardown(item, nextitem):
_update_current_test_var(item, 'teardown')
item.session._setupstate.teardown_exact(item, nextitem)
_update_current_test_var(item, None)
def _update_current_test_var(item, when):
"""
Update PYTEST_CURRENT_TEST to reflect the current item and stage.
If ``when`` is None, delete PYTEST_CURRENT_TEST from the environment.
"""
var_name = 'PYTEST_CURRENT_TEST'
if when:
value = '{0} ({1})'.format(item.nodeid, when)
if _PY2:
# python 2 doesn't like null bytes on environment variables (see #2644)
value = value.replace('\x00', '(null)')
os.environ[var_name] = value
else:
os.environ.pop(var_name)
def pytest_report_teststatus(report):
if report.when in ("setup", "teardown"):
@@ -133,21 +168,25 @@ def call_and_report(item, when, log=True, **kwds):
hook.pytest_exception_interact(node=item, call=call, report=report)
return report
def check_interactive_exception(call, report):
return call.excinfo and not (
hasattr(report, "wasxfail") or
call.excinfo.errisinstance(skip.Exception) or
call.excinfo.errisinstance(bdb.BdbQuit))
hasattr(report, "wasxfail") or
call.excinfo.errisinstance(skip.Exception) or
call.excinfo.errisinstance(bdb.BdbQuit))
def call_runtest_hook(item, when, **kwds):
hookname = "pytest_runtest_" + when
ihook = getattr(item.ihook, hookname)
return CallInfo(lambda: ihook(item=item, **kwds), when=when)
class CallInfo:
""" Result/Exception info a function invocation. """
#: None or ExceptionInfo object.
excinfo = None
def __init__(self, func, when):
#: context of invocation: one of "setup", "call",
#: "teardown", "memocollect"
@@ -169,6 +208,7 @@ class CallInfo:
status = "result: %r" % (self.result,)
return "<CallInfo when=%r %s>" % (self.when, status)
def getslaveinfoline(node):
try:
return node._slaveinfocache
@@ -179,6 +219,7 @@ def getslaveinfoline(node):
d['id'], d['sysplatform'], ver, d['executable'])
return s
class BaseReport(object):
def __init__(self, **kw):
@@ -243,10 +284,11 @@ class BaseReport(object):
def fspath(self):
return self.nodeid.split("::")[0]
def pytest_runtest_makereport(item, call):
when = call.when
duration = call.stop-call.start
keywords = dict([(x,1) for x in item.keywords])
duration = call.stop - call.start
keywords = dict([(x, 1) for x in item.keywords])
excinfo = call.excinfo
sections = []
if not call.excinfo:
@@ -264,19 +306,21 @@ def pytest_runtest_makereport(item, call):
outcome = "failed"
if call.when == "call":
longrepr = item.repr_failure(excinfo)
else: # exception in setup or teardown
else: # exception in setup or teardown
longrepr = item._repr_failure_py(excinfo,
style=item.config.option.tbstyle)
style=item.config.option.tbstyle)
for rwhen, key, content in item._report_sections:
sections.append(("Captured %s %s" %(key, rwhen), content))
sections.append(("Captured %s %s" % (key, rwhen), content))
return TestReport(item.nodeid, item.location,
keywords, outcome, longrepr, when,
sections, duration)
class TestReport(BaseReport):
""" Basic test report object (also used for setup and teardown calls if
they fail).
"""
def __init__(self, nodeid, location, keywords, outcome,
longrepr, when, sections=(), duration=0, **extra):
#: normalized collection node id
@@ -315,14 +359,17 @@ class TestReport(BaseReport):
return "<TestReport %r when=%r outcome=%r>" % (
self.nodeid, self.when, self.outcome)
class TeardownErrorReport(BaseReport):
outcome = "failed"
when = "teardown"
def __init__(self, longrepr, **extra):
self.longrepr = longrepr
self.sections = []
self.__dict__.update(extra)
def pytest_make_collect_report(collector):
call = CallInfo(
lambda: list(collector.collect()),
@@ -344,7 +391,7 @@ def pytest_make_collect_report(collector):
errorinfo = CollectErrorRepr(errorinfo)
longrepr = errorinfo
rep = CollectReport(collector.nodeid, outcome, longrepr,
getattr(call, 'result', None))
getattr(call, 'result', None))
rep.call = call # see collect_one_node
return rep
@@ -365,16 +412,20 @@ class CollectReport(BaseReport):
def __repr__(self):
return "<CollectReport %r lenresult=%s outcome=%r>" % (
self.nodeid, len(self.result), self.outcome)
self.nodeid, len(self.result), self.outcome)
class CollectErrorRepr(TerminalRepr):
def __init__(self, msg):
self.longrepr = msg
def toterminal(self, out):
out.line(self.longrepr, red=True)
class SetupState(object):
""" shared state for setting up/tearing down test items or collectors. """
def __init__(self):
self.stack = []
self._finalizers = {}
@@ -386,7 +437,7 @@ class SetupState(object):
"""
assert colitem and not isinstance(colitem, tuple)
assert py.builtin.callable(finalizer)
#assert colitem in self.stack # some unit tests don't setup stack :/
# assert colitem in self.stack # some unit tests don't setup stack :/
self._finalizers.setdefault(colitem, []).append(finalizer)
def _pop_and_teardown(self):
@@ -400,7 +451,7 @@ class SetupState(object):
fin = finalizers.pop()
try:
fin()
except Exception:
except TEST_OUTCOME:
# XXX Only first exception will be seen by user,
# ideally all should be reported.
if exc is None:
@@ -414,7 +465,7 @@ class SetupState(object):
colitem.teardown()
for colitem in self._finalizers:
assert colitem is None or colitem in self.stack \
or isinstance(colitem, tuple)
or isinstance(colitem, tuple)
def teardown_all(self):
while self.stack:
@@ -447,10 +498,11 @@ class SetupState(object):
self.stack.append(col)
try:
col.setup()
except Exception:
except TEST_OUTCOME:
col._prepare_exc = sys.exc_info()
raise
def collect_one_node(collector):
ihook = collector.ihook
ihook.pytest_collectstart(collector=collector)
@@ -459,122 +511,3 @@ def collect_one_node(collector):
if call and check_interactive_exception(call, rep):
ihook.pytest_exception_interact(node=collector, call=call, report=rep)
return rep
# =============================================================
# Test OutcomeExceptions and helpers for creating them.
class OutcomeException(Exception):
""" OutcomeException and its subclass instances indicate and
contain info about test and collection outcomes.
"""
def __init__(self, msg=None, pytrace=True):
Exception.__init__(self, msg)
self.msg = msg
self.pytrace = pytrace
def __repr__(self):
if self.msg:
val = self.msg
if isinstance(val, bytes):
val = py._builtin._totext(val, errors='replace')
return val
return "<%s instance>" %(self.__class__.__name__,)
__str__ = __repr__
class Skipped(OutcomeException):
# XXX hackish: on 3k we fake to live in the builtins
# in order to have Skipped exception printing shorter/nicer
__module__ = 'builtins'
def __init__(self, msg=None, pytrace=True, allow_module_level=False):
OutcomeException.__init__(self, msg=msg, pytrace=pytrace)
self.allow_module_level = allow_module_level
class Failed(OutcomeException):
""" raised from an explicit call to pytest.fail() """
__module__ = 'builtins'
class Exit(KeyboardInterrupt):
""" raised for immediate program exits (no tracebacks/summaries)"""
def __init__(self, msg="unknown reason"):
self.msg = msg
KeyboardInterrupt.__init__(self, msg)
# exposed helper methods
def exit(msg):
""" exit testing process as if KeyboardInterrupt was triggered. """
__tracebackhide__ = True
raise Exit(msg)
exit.Exception = Exit
def skip(msg=""):
""" skip an executing test with the given message. Note: it's usually
better to use the pytest.mark.skipif marker to declare a test to be
skipped under certain conditions like mismatching platforms or
dependencies. See the pytest_skipping plugin for details.
"""
__tracebackhide__ = True
raise Skipped(msg=msg)
skip.Exception = Skipped
def fail(msg="", pytrace=True):
""" explicitly fail an currently-executing test with the given Message.
:arg pytrace: if false the msg represents the full failure information
and no python traceback will be reported.
"""
__tracebackhide__ = True
raise Failed(msg=msg, pytrace=pytrace)
fail.Exception = Failed
def importorskip(modname, minversion=None):
""" return imported module if it has at least "minversion" as its
__version__ attribute. If no minversion is specified the a skip
is only triggered if the module can not be imported.
"""
import warnings
__tracebackhide__ = True
compile(modname, '', 'eval') # to catch syntaxerrors
should_skip = False
with warnings.catch_warnings():
# make sure to ignore ImportWarnings that might happen because
# of existing directories with the same name we're trying to
# import but without a __init__.py file
warnings.simplefilter('ignore')
try:
__import__(modname)
except ImportError:
# Do not raise chained exception here(#1485)
should_skip = True
if should_skip:
raise Skipped("could not import %r" %(modname,), allow_module_level=True)
mod = sys.modules[modname]
if minversion is None:
return mod
verattr = getattr(mod, '__version__', None)
if minversion is not None:
try:
from pkg_resources import parse_version as pv
except ImportError:
raise Skipped("we have a required version for %r but can not import "
"pkg_resources to parse version strings." % (modname,),
allow_module_level=True)
if verattr is None or pv(verattr) < pv(minversion):
raise Skipped("module %r has __version__ %r, required is: %r" %(
modname, verattr, minversion), allow_module_level=True)
return mod

View File

@@ -8,13 +8,14 @@ import traceback
import py
from _pytest.config import hookimpl
from _pytest.mark import MarkInfo, MarkDecorator
from _pytest.runner import fail, skip
from _pytest.outcomes import fail, skip, xfail, TEST_OUTCOME
def pytest_addoption(parser):
group = parser.getgroup("general")
group.addoption('--runxfail',
action="store_true", dest="runxfail", default=False,
help="run tests even if they are marked xfail")
action="store_true", dest="runxfail", default=False,
help="run tests even if they are marked xfail")
parser.addini("xfail_strict", "default for the strict parameter of xfail "
"markers when not given explicitly (default: "
@@ -33,43 +34,30 @@ def pytest_configure(config):
def nop(*args, **kwargs):
pass
nop.Exception = XFailed
nop.Exception = xfail.Exception
setattr(pytest, "xfail", nop)
config.addinivalue_line("markers",
"skip(reason=None): skip the given test function with an optional reason. "
"Example: skip(reason=\"no way of currently testing this\") skips the "
"test."
)
"skip(reason=None): skip the given test function with an optional reason. "
"Example: skip(reason=\"no way of currently testing this\") skips the "
"test."
)
config.addinivalue_line("markers",
"skipif(condition): skip the given test function if eval(condition) "
"results in a True value. Evaluation happens within the "
"module global context. Example: skipif('sys.platform == \"win32\"') "
"skips the test if we are on the win32 platform. see "
"http://pytest.org/latest/skipping.html"
)
"skipif(condition): skip the given test function if eval(condition) "
"results in a True value. Evaluation happens within the "
"module global context. Example: skipif('sys.platform == \"win32\"') "
"skips the test if we are on the win32 platform. see "
"http://pytest.org/latest/skipping.html"
)
config.addinivalue_line("markers",
"xfail(condition, reason=None, run=True, raises=None, strict=False): "
"mark the test function as an expected failure if eval(condition) "
"has a True value. Optionally specify a reason for better reporting "
"and run=False if you don't even want to execute the test function. "
"If only specific exception(s) are expected, you can list them in "
"raises, and if the test fails in other ways, it will be reported as "
"a true failure. See http://pytest.org/latest/skipping.html"
)
class XFailed(fail.Exception):
""" raised from an explicit call to pytest.xfail() """
def xfail(reason=""):
""" xfail an executing test or setup functions with the given reason."""
__tracebackhide__ = True
raise XFailed(reason)
xfail.Exception = XFailed
"xfail(condition, reason=None, run=True, raises=None, strict=False): "
"mark the test function as an expected failure if eval(condition) "
"has a True value. Optionally specify a reason for better reporting "
"and run=False if you don't even want to execute the test function. "
"If only specific exception(s) are expected, you can list them in "
"raises, and if the test fails in other ways, it will be reported as "
"a true failure. See http://pytest.org/latest/skipping.html"
)
class MarkEvaluator:
@@ -97,7 +85,7 @@ class MarkEvaluator:
def istrue(self):
try:
return self._istrue()
except Exception:
except TEST_OUTCOME:
self.exc = sys.exc_info()
if isinstance(self.exc[1], SyntaxError):
msg = [" " * (self.exc[1].offset + 4) + "^", ]
@@ -243,7 +231,7 @@ def pytest_runtest_makereport(item, call):
rep.wasxfail = "reason: " + call.excinfo.value.msg
rep.outcome = "skipped"
elif evalxfail and not rep.skipped and evalxfail.wasvalid() and \
evalxfail.istrue():
evalxfail.istrue():
if call.excinfo:
if evalxfail.invalidraise(call.excinfo.value):
rep.outcome = "failed"
@@ -269,6 +257,8 @@ def pytest_runtest_makereport(item, call):
rep.longrepr = filename, line, reason
# called by terminalreporter progress reporting
def pytest_report_teststatus(report):
if hasattr(report, "wasxfail"):
if report.skipped:
@@ -277,10 +267,12 @@ def pytest_report_teststatus(report):
return "xpassed", "X", ("XPASS", {'yellow': True})
# called by the terminalreporter instance/plugin
def pytest_terminal_summary(terminalreporter):
tr = terminalreporter
if not tr.reportchars:
#for name in "xfailed skipped failed xpassed":
# for name in "xfailed skipped failed xpassed":
# if not tr.stats.get(name, 0):
# tr.write_line("HINT: use '-r' option to see extra "
# "summary info about tests")
@@ -364,17 +356,17 @@ def show_skipped(terminalreporter, lines):
tr = terminalreporter
skipped = tr.stats.get('skipped', [])
if skipped:
#if not tr.hasopt('skipped'):
# if not tr.hasopt('skipped'):
# tr.write_line(
# "%d skipped tests, specify -rs for more info" %
# len(skipped))
# return
fskips = folded_skips(skipped)
if fskips:
#tr.write_sep("_", "skipped test summary")
# tr.write_sep("_", "skipped test summary")
for num, fspath, lineno, reason in fskips:
if reason.startswith("Skipped: "):
reason = reason[9:]
lines.append(
"SKIP [%d] %s:%d: %s" %
(num, fspath, lineno, reason))
(num, fspath, lineno + 1, reason))

View File

@@ -19,33 +19,34 @@ import _pytest._pluggy as pluggy
def pytest_addoption(parser):
group = parser.getgroup("terminal reporting", "reporting", after="general")
group._addoption('-v', '--verbose', action="count",
dest="verbose", default=0, help="increase verbosity."),
dest="verbose", default=0, help="increase verbosity."),
group._addoption('-q', '--quiet', action="count",
dest="quiet", default=0, help="decrease verbosity."),
dest="quiet", default=0, help="decrease verbosity."),
group._addoption('-r',
action="store", dest="reportchars", default='', metavar="chars",
help="show extra test summary info as specified by chars (f)ailed, "
"(E)error, (s)skipped, (x)failed, (X)passed, "
"(p)passed, (P)passed with output, (a)all except pP. "
"Warnings are displayed at all times except when "
"--disable-warnings is set")
action="store", dest="reportchars", default='', metavar="chars",
help="show extra test summary info as specified by chars (f)ailed, "
"(E)error, (s)skipped, (x)failed, (X)passed, "
"(p)passed, (P)passed with output, (a)all except pP. "
"Warnings are displayed at all times except when "
"--disable-warnings is set")
group._addoption('--disable-warnings', '--disable-pytest-warnings', default=False,
dest='disable_warnings', action='store_true',
help='disable warnings summary')
group._addoption('-l', '--showlocals',
action="store_true", dest="showlocals", default=False,
help="show locals in tracebacks (disabled by default).")
action="store_true", dest="showlocals", default=False,
help="show locals in tracebacks (disabled by default).")
group._addoption('--tb', metavar="style",
action="store", dest="tbstyle", default='auto',
choices=['auto', 'long', 'short', 'no', 'line', 'native'],
help="traceback print mode (auto/long/short/line/native/no).")
action="store", dest="tbstyle", default='auto',
choices=['auto', 'long', 'short', 'no', 'line', 'native'],
help="traceback print mode (auto/long/short/line/native/no).")
group._addoption('--fulltrace', '--full-trace',
action="store_true", default=False,
help="don't cut any tracebacks (default is to cut).")
action="store_true", default=False,
help="don't cut any tracebacks (default is to cut).")
group._addoption('--color', metavar="color",
action="store", dest="color", default='auto',
choices=['yes', 'no', 'auto'],
help="color terminal output (yes/no/auto).")
action="store", dest="color", default='auto',
choices=['yes', 'no', 'auto'],
help="color terminal output (yes/no/auto).")
def pytest_configure(config):
config.option.verbose -= config.option.quiet
@@ -57,6 +58,7 @@ def pytest_configure(config):
reporter.write_line("[traceconfig] " + msg)
config.trace.root.setprocessor("pytest:config", mywriter)
def getreportopt(config):
reportopts = ""
reportchars = config.option.reportchars
@@ -72,6 +74,7 @@ def getreportopt(config):
reportopts = 'fEsxXw'
return reportopts
def pytest_report_teststatus(report):
if report.passed:
letter = "."
@@ -88,6 +91,7 @@ class WarningReport:
"""
Simple structure to hold warnings information captured by ``pytest_logwarning``.
"""
def __init__(self, code, message, nodeid=None, fslocation=None):
"""
:param code: unused
@@ -176,8 +180,22 @@ class TerminalReporter:
self._tw.line(line, **markup)
def rewrite(self, line, **markup):
"""
Rewinds the terminal cursor to the beginning and writes the given line.
:kwarg erase: if True, will also add spaces until the full terminal width to ensure
previous lines are properly erased.
The rest of the keyword arguments are markup instructions.
"""
erase = markup.pop('erase', False)
if erase:
fill_count = self._tw.fullwidth - len(line)
fill = ' ' * fill_count
else:
fill = ''
line = str(line)
self._tw.write("\r" + line, **markup)
self._tw.write("\r" + line + fill, **markup)
def write_sep(self, sep, title=None, **markup):
self.ensure_newline()
@@ -240,15 +258,15 @@ class TerminalReporter:
word, markup = word
else:
if rep.passed:
markup = {'green':True}
markup = {'green': True}
elif rep.failed:
markup = {'red':True}
markup = {'red': True}
elif rep.skipped:
markup = {'yellow':True}
markup = {'yellow': True}
line = self._locationline(rep.nodeid, *rep.location)
if not hasattr(rep, 'node'):
self.write_ensure_prefix(line, word, **markup)
#self._tw.write(word, **markup)
# self._tw.write(word, **markup)
else:
self.ensure_newline()
if hasattr(rep, 'node'):
@@ -269,7 +287,7 @@ class TerminalReporter:
items = [x for x in report.result if isinstance(x, pytest.Item)]
self._numcollected += len(items)
if self.isatty:
#self.write_fspath_result(report.nodeid, 'E')
# self.write_fspath_result(report.nodeid, 'E')
self.report_collect()
def report_collect(self, final=False):
@@ -288,9 +306,9 @@ class TerminalReporter:
if skipped:
line += " / %d skipped" % skipped
if self.isatty:
self.rewrite(line, bold=True, erase=True)
if final:
line += " \n"
self.rewrite(line, bold=True)
self.write('\n')
else:
self.write_line(line)
@@ -316,6 +334,9 @@ class TerminalReporter:
self.write_line(msg)
lines = self.config.hook.pytest_report_header(
config=self.config, startdir=self.startdir)
self._write_report_lines_from_hooks(lines)
def _write_report_lines_from_hooks(self, lines):
lines.reverse()
for line in flatten(lines):
self.write_line(line)
@@ -342,10 +363,9 @@ class TerminalReporter:
rep.toterminal(self._tw)
return 1
return 0
if not self.showheader:
return
#for i, testarg in enumerate(self.config.args):
# self.write_line("test path %d: %s" %(i+1, testarg))
lines = self.config.hook.pytest_report_collectionfinish(
config=self.config, startdir=self.startdir, items=session.items)
self._write_report_lines_from_hooks(lines)
def _printcollecteditems(self, items):
# to print out items and their parent collectors
@@ -368,14 +388,14 @@ class TerminalReporter:
stack = []
indent = ""
for item in items:
needed_collectors = item.listchain()[1:] # strip root node
needed_collectors = item.listchain()[1:] # strip root node
while stack:
if stack == needed_collectors[:len(stack)]:
break
stack.pop()
for col in needed_collectors[len(stack):]:
stack.append(col)
#if col.name == "()":
# if col.name == "()":
# continue
indent = (len(stack) - 1) * " "
self._tw.line("%s%s" % (indent, col))
@@ -443,7 +463,7 @@ class TerminalReporter:
fspath, lineno, domain = rep.location
return domain
else:
return "test session" # XXX?
return "test session" # XXX?
def _getcrashline(self, rep):
try:
@@ -502,7 +522,6 @@ class TerminalReporter:
content = content[:-1]
self._tw.line(content)
def summary_failures(self):
if self.config.option.tbstyle != "no":
reports = self.getreports('failed')
@@ -564,6 +583,7 @@ class TerminalReporter:
self.write_sep("=", "%d tests deselected" % (
len(self.stats['deselected'])), bold=True)
def repr_pythonversion(v=None):
if v is None:
v = sys.version_info
@@ -572,6 +592,7 @@ def repr_pythonversion(v=None):
except (TypeError, ValueError):
return str(v)
def flatten(l):
for x in l:
if isinstance(x, (list, tuple)):
@@ -580,13 +601,14 @@ def flatten(l):
else:
yield x
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
if key: # setup/teardown reports have an empty key, ignore them
keys.append(key)
unknown_key_seen = True
parts = []

View File

@@ -25,7 +25,7 @@ class TempdirFactory:
provides an empty unique-per-test-invocation directory
and is guaranteed to be empty.
"""
#py.log._apiwarn(">1.1", "use tmpdir function argument")
# py.log._apiwarn(">1.1", "use tmpdir function argument")
return self.getbasetemp().ensure(string, dir=dir)
def mktemp(self, basename, numbered=True):
@@ -38,7 +38,7 @@ class TempdirFactory:
p = basetemp.mkdir(basename)
else:
p = py.path.local.make_numbered_dir(prefix=basename,
keep=0, rootdir=basetemp, lock_timeout=None)
keep=0, rootdir=basetemp, lock_timeout=None)
self.trace("mktemp", p)
return p

View File

@@ -7,9 +7,9 @@ import traceback
# for transferring markers
import _pytest._code
from _pytest.config import hookimpl
from _pytest.runner import fail, skip
from _pytest.outcomes import fail, skip, xfail
from _pytest.python import transfer_markers, Class, Module, Function
from _pytest.skipping import MarkEvaluator, xfail
from _pytest.skipping import MarkEvaluator
def pytest_pycollect_makeitem(collector, name, obj):
@@ -158,7 +158,7 @@ class TestCaseFunction(Function):
# analog to pythons Lib/unittest/case.py:run
testMethod = getattr(self._testcase, self._testcase._testMethodName)
if (getattr(self._testcase.__class__, "__unittest_skip__", False) or
getattr(testMethod, "__unittest_skip__", False)):
getattr(testMethod, "__unittest_skip__", False)):
# If the class or method was skipped.
skip_why = (getattr(self._testcase.__class__, '__unittest_skip_why__', '') or
getattr(testMethod, '__unittest_skip_why__', ''))
@@ -210,7 +210,7 @@ def pytest_runtest_protocol(item):
check_testcase_implements_trial_reporter()
def excstore(self, exc_value=None, exc_type=None, exc_tb=None,
captureVars=None):
captureVars=None):
if exc_value is None:
self._rawexcinfo = sys.exc_info()
else:
@@ -219,7 +219,7 @@ def pytest_runtest_protocol(item):
self._rawexcinfo = (exc_type, exc_value, exc_tb)
try:
Failure__init__(self, exc_value, exc_type, exc_tb,
captureVars=captureVars)
captureVars=captureVars)
except TypeError:
Failure__init__(self, exc_value, exc_type, exc_tb)

View File

@@ -39,8 +39,9 @@ def pytest_addoption(parser):
'-W', '--pythonwarnings', action='append',
help="set which warnings to report, see -W option of python itself.")
parser.addini("filterwarnings", type="linelist",
help="Each line specifies warning filter pattern which would be passed"
"to warnings.filterwarnings. Process after -W and --pythonwarnings.")
help="Each line specifies a pattern for "
"warnings.filterwarnings. "
"Processed after -W and --pythonwarnings.")
@contextmanager
@@ -59,6 +60,11 @@ def catch_warnings_for_item(item):
for arg in inifilters:
_setoption(warnings, arg)
mark = item.get_marker('filterwarnings')
if mark:
for arg in mark.args:
warnings._setoption(arg)
yield
for warning in log:
@@ -78,7 +84,7 @@ def catch_warnings_for_item(item):
if unicode_warning:
warnings.warn(
"Warning is using unicode non convertible to ascii, "
"converting to a safe representation:\n %s" % msg,
"converting to a safe representation:\n %s" % msg,
UnicodeWarning)

View File

@@ -20,12 +20,14 @@ environment:
- TOXENV: "py27-pexpect"
- TOXENV: "py27-xdist"
- TOXENV: "py27-trial"
- TOXENV: "py35-pexpect"
- TOXENV: "py35-xdist"
- TOXENV: "py35-trial"
- TOXENV: "py27-numpy"
- TOXENV: "py36-pexpect"
- TOXENV: "py36-xdist"
- TOXENV: "py36-trial"
- TOXENV: "py36-numpy"
- TOXENV: "py27-nobyte"
- TOXENV: "doctesting"
- TOXENV: "freeze"
- TOXENV: "py35-freeze"
- TOXENV: "docs"
install:
@@ -34,7 +36,7 @@ install:
- if "%TOXENV%" == "pypy" call scripts\install-pypy.bat
- C:\Python35\python -m pip install tox
- C:\Python36\python -m pip install --upgrade --pre tox
build: false # Not a C# project, build stuff at the test step instead.

View File

@@ -13,7 +13,8 @@
{% if definitions[category]['showcontent'] %}
{% for text, values in sections[section][category]|dictsort(by='value') %}
- {{ text }}{% if category != 'vendor' %} (`{{ values[0] }} <https://github.com/pytest-dev/pytest/issues/{{ values[0][1:] }}>`_){% endif %}
{% set issue_joiner = joiner(', ') %}
- {{ text }}{% if category != 'vendor' %} ({% for value in values|sort %}{{ issue_joiner() }}`{{ value }} <https://github.com/pytest-dev/pytest/issues/{{ value[1:] }}>`_{% endfor %}){% endif %}
{% endfor %}

View File

@@ -9,6 +9,7 @@
<li><a href="{{ pathto('contact') }}">Contact</a></li>
<li><a href="{{ pathto('talks') }}">Talks/Posts</a></li>
<li><a href="{{ pathto('changelog') }}">Changelog</a></li>
<li><a href="{{ pathto('backwards-compatibility') }}">Backwards Compatibility</a></li>
<li><a href="{{ pathto('license') }}">License</a></li>
</ul>

View File

@@ -6,6 +6,9 @@ Release announcements
:maxdepth: 2
release-3.2.2
release-3.2.1
release-3.2.0
release-3.1.3
release-3.1.2
release-3.1.1

View File

@@ -0,0 +1,48 @@
pytest-3.2.0
=======================================
The pytest team is proud to announce the 3.2.0 release!
pytest is a mature Python testing tool with more than a 1600 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:
http://doc.pytest.org/en/latest/changelog.html
For complete documentation, please visit:
http://docs.pytest.org
As usual, you can upgrade from pypi via:
pip install -U pytest
Thanks to all who contributed to this release, among them:
* Alex Hartoto
* Andras Tim
* Bruno Oliveira
* Daniel Hahler
* Florian Bruhin
* Floris Bruynooghe
* John Still
* Jordan Moldow
* Kale Kundert
* Lawrence Mitchell
* Llandy Riveron Del Risco
* Maik Figura
* Martin Altmayer
* Mihai Capotă
* Nathaniel Waisbrot
* Nguyễn Hồng Quân
* Pauli Virtanen
* Raphael Pierzina
* Ronny Pfannschmidt
* Segev Finer
* V.Kuznetsov
Happy testing,
The Pytest Development Team

View File

@@ -0,0 +1,22 @@
pytest-3.2.1
=======================================
pytest 3.2.1 has just been released to PyPI.
This is a bug-fix release, being a drop-in replacement. To upgrade::
pip install --upgrade pytest
The full changelog is available at http://doc.pytest.org/en/latest/changelog.html.
Thanks to all who contributed to this release, among them:
* Alex Gaynor
* Bruno Oliveira
* Florian Bruhin
* Ronny Pfannschmidt
* Srinivas Reddy Thatiparthy
Happy testing,
The pytest Development Team

View File

@@ -0,0 +1,28 @@
pytest-3.2.2
=======================================
pytest 3.2.2 has just been released to PyPI.
This is a bug-fix release, being a drop-in replacement. To upgrade::
pip install --upgrade pytest
The full changelog is available at http://doc.pytest.org/en/latest/changelog.html.
Thanks to all who contributed to this release, among them:
* Andreas Pelme
* Antonio Hidalgo
* Bruno Oliveira
* Felipe Dau
* Fernando Macedo
* Jesús Espino
* Joan Massich
* Joe Talbott
* Kirill Pinchuk
* Ronny Pfannschmidt
* Xuan Luong
Happy testing,
The pytest Development Team

View File

@@ -119,9 +119,9 @@ exceptions your own code is deliberately raising, whereas using
like documenting unfixed bugs (where the test describes what "should" happen)
or bugs in dependencies.
If you want to test that a regular expression matches on the string
representation of an exception (like the ``TestCase.assertRaisesRegexp`` method
from ``unittest``) you can use the ``ExceptionInfo.match`` method::
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
@@ -129,12 +129,11 @@ from ``unittest``) you can use the ``ExceptionInfo.match`` method::
raise ValueError("Exception 123 raised")
def test_match():
with pytest.raises(ValueError) as excinfo:
with pytest.raises(ValueError, match=r'.* 123 .*'):
myfunc()
excinfo.match(r'.* 123 .*')
The regexp parameter of the ``match`` method is matched with the ``re.search``
function. So in the above example ``excinfo.match('123')`` would have worked as
function. So in the above example ``match='123'`` would have worked as
well.

View File

@@ -38,7 +38,7 @@ Examples at :ref:`assertraises`.
Comparing floating point numbers
--------------------------------
.. autoclass:: approx
.. autofunction:: approx
Raising a specific test outcome
--------------------------------------
@@ -47,11 +47,11 @@ You can use the following functions in your test, fixture or setup
functions to force a certain test outcome. Note that most often
you can rather use declarative marks, see :ref:`skipping`.
.. autofunction:: _pytest.runner.fail
.. autofunction:: _pytest.runner.skip
.. autofunction:: _pytest.runner.importorskip
.. autofunction:: _pytest.skipping.xfail
.. autofunction:: _pytest.runner.exit
.. autofunction:: _pytest.outcomes.fail
.. autofunction:: _pytest.outcomes.skip
.. autofunction:: _pytest.outcomes.importorskip
.. autofunction:: _pytest.outcomes.xfail
.. autofunction:: _pytest.outcomes.exit
Fixtures and requests
-----------------------------------------------------
@@ -108,14 +108,14 @@ You can ask for available builtin or project-custom
The returned ``monkeypatch`` fixture provides these
helper methods to modify objects, dictionaries or os.environ::
monkeypatch.setattr(obj, name, value, raising=True)
monkeypatch.delattr(obj, name, raising=True)
monkeypatch.setitem(mapping, name, value)
monkeypatch.delitem(obj, name, raising=True)
monkeypatch.setenv(name, value, prepend=False)
monkeypatch.delenv(name, value, raising=True)
monkeypatch.syspath_prepend(path)
monkeypatch.chdir(path)
monkeypatch.setattr(obj, name, value, raising=True)
monkeypatch.delattr(obj, name, raising=True)
monkeypatch.setitem(mapping, name, value)
monkeypatch.delitem(obj, name, raising=True)
monkeypatch.setenv(name, value, prepend=False)
monkeypatch.delenv(name, value, raising=True)
monkeypatch.syspath_prepend(path)
monkeypatch.chdir(path)
All modifications will be undone after the requesting
test function or fixture has finished. The ``raising``

View File

@@ -1,16 +1,12 @@
.. _`cache_provider`:
.. _cache:
Cache: working with cross-testrun state
=======================================
.. versionadded:: 2.8
.. warning::
The functionality of this core plugin was previously distributed
as a third party plugin named ``pytest-cache``. The core plugin
is compatible regarding command line options and API usage except that you
can only store/receive data between test runs that is json-serializable.
Usage
---------
@@ -81,9 +77,9 @@ If you then run it with ``--lf``::
$ pytest --lf
======= test session starts ========
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
run-last-failure: rerun last 2 failures
rootdir: $REGENDOC_TMPDIR, inifile:
collected 50 items
run-last-failure: rerun previous 2 failures
test_50.py FF
@@ -123,9 +119,9 @@ of ``FF`` and dots)::
$ pytest --ff
======= test session starts ========
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
run-last-failure: rerun last 2 failures first
rootdir: $REGENDOC_TMPDIR, inifile:
collected 50 items
run-last-failure: rerun previous 2 failures first
test_50.py FF................................................

View File

@@ -19,9 +19,9 @@ Contact channels
- `pytest-commit at python.org (mailing list)`_: for commits and new issues
- :doc:`contribution guide <contributing>` for help on submitting pull
requests to bitbucket (including using git via gitifyhg).
requests to GitHub.
- #pylib on irc.freenode.net IRC channel for random questions.
- ``#pylib`` on irc.freenode.net IRC channel for random questions.
- private mail to Holger.Krekel at gmail com if you want to communicate sensitive issues
@@ -46,6 +46,5 @@ Contact channels
.. _`py-dev`:
.. _`development mailing list`:
.. _`pytest-dev at python.org (mailing list)`: http://mail.python.org/mailman/listinfo/pytest-dev
.. _`py-svn`:
.. _`pytest-commit at python.org (mailing list)`: http://mail.python.org/mailman/listinfo/pytest-commit

View File

@@ -31,12 +31,14 @@ Full pytest documentation
plugins
writing_plugins
example/index
goodpractices
pythonpath
customize
example/index
bash-completion
backwards-compatibility
historical-notes
license
contributing
talks

View File

@@ -1,5 +1,5 @@
Basic test configuration
===================================
Configuration
=============
Command line options and configuration file settings
-----------------------------------------------------------------
@@ -15,17 +15,31 @@ which were registered by installed plugins.
.. _rootdir:
.. _inifiles:
initialization: determining rootdir and inifile
Initialization: determining rootdir and inifile
-----------------------------------------------
.. versionadded:: 2.7
pytest determines a "rootdir" for each test run which depends on
pytest determines a ``rootdir`` for each test run which depends on
the command line arguments (specified test files, paths) and on
the existence of inifiles. The determined rootdir and ini-file are
printed as part of the pytest header. The rootdir is used for constructing
"nodeids" during collection and may also be used by plugins to store
project/testrun-specific information.
the existence of *ini-files*. The determined ``rootdir`` and *ini-file* are
printed as part of the pytest header during startup.
Here's a summary what ``pytest`` uses ``rootdir`` for:
* Construct *nodeids* during collection; each test is assigned
a unique *nodeid* which is rooted at the ``rootdir`` and takes in account full path,
class name, function name and parametrization (if any).
* Is used by plugins as a stable location to store project/test run specific information;
for example, the internal :ref:`cache <cache>` plugin creates a ``.cache`` subdirectory
in ``rootdir`` to store its cross-test run state.
Important to emphasize that ``rootdir`` is **NOT** used to modify ``sys.path``/``PYTHONPATH`` or
influence how modules are imported. See :ref:`pythonpath` for more details.
Finding the ``rootdir``
~~~~~~~~~~~~~~~~~~~~~~~
Here is the algorithm which finds the rootdir from ``args``:
@@ -112,15 +126,27 @@ progress output, you can write it into a configuration file:
# content of pytest.ini
# (or tox.ini or setup.cfg)
[pytest]
addopts = -rsxX -q
addopts = -ra -q
Alternatively, you can set a PYTEST_ADDOPTS environment variable to add command
Alternatively, you can set a ``PYTEST_ADDOPTS`` environment variable to add command
line options while the environment is in use::
export PYTEST_ADDOPTS="-rsxX -q"
export PYTEST_ADDOPTS="-v"
From now on, running ``pytest`` will add the specified options.
Here's how the command-line is built in the presence of ``addopts`` or the environment variable::
<pytest.ini:addopts> $PYTEST_ADDOTPS <extra command-line arguments>
So if the user executes in the command-line::
pytest -m slow
The actual command line executed is::
pytest -ra -q -v -m slow
Note that as usual for other command-line applications, in case of conflicting options the last one wins, so the example
above will show verbose output because ``-v`` overwrites ``-q``.
Builtin configuration file options
@@ -171,7 +197,16 @@ Builtin configuration file options
norecursedirs = .svn _build tmp*
This would tell ``pytest`` to not look into typical subversion or
sphinx-build directories or into any ``tmp`` prefixed directory.
sphinx-build directories or into any ``tmp`` prefixed directory.
Additionally, ``pytest`` will attempt to intelligently identify and ignore a
virtualenv by the presence of an activation script. Any directory deemed to
be the root of a virtual environment will not be considered during test
collection unless ``collectinvirtualenv`` is given. Note also that
``norecursedirs`` takes precedence over ``collectinvirtualenv``; e.g. if
you intend to run tests in a virtualenv with a base directory that matches
``'.*'`` you *must* override ``norecursedirs`` in addition to using the
``collectinvirtualenv`` flag.
.. confval:: testpaths
@@ -195,13 +230,16 @@ Builtin configuration file options
.. confval:: python_files
One or more Glob-style file patterns determining which python files
are considered as test modules.
are considered as test modules. By default, pytest will consider
any file matching with ``test_*.py`` and ``*_test.py`` globs as a test
module.
.. confval:: python_classes
One or more name prefixes or glob-style patterns determining which classes
are considered for test collection. Here is an example of how to collect
tests from classes that end in ``Suite``:
are considered for test collection. By default, pytest will consider any
class prefixed with ``Test`` as a test collection. Here is an example of how
to collect tests from classes that end in ``Suite``:
.. code-block:: ini
@@ -216,7 +254,8 @@ Builtin configuration file options
.. confval:: python_functions
One or more name prefixes or glob-patterns determining which test functions
and methods are considered tests. Here is an example of how
and methods are considered tests. By default, pytest will consider any
function prefixed with ``test`` as a test. Here is an example of how
to collect test functions and methods that end in ``_test``:
.. code-block:: ini
@@ -262,3 +301,14 @@ Builtin configuration file options
This tells pytest to ignore deprecation warnings and turn all other warnings
into errors. For more information please refer to :ref:`warnings`.
.. confval:: cache_dir
.. versionadded:: 3.2
Sets a directory where stores content of cache plugin. Default directory is
``.cache`` which is created in :ref:`rootdir <rootdir>`. Directory may be
relative or absolute path. If setting relative path, then directory is created
relative to :ref:`rootdir <rootdir>`. Additionally path may contain environment
variables, that will be expanded. For more information about cache plugin
please refer to :ref:`cache_provider`.

View File

@@ -17,7 +17,7 @@ example: specifying and selecting acceptance tests
class AcceptFixture(object):
def __init__(self, request):
if not request.config.option.acceptance:
if not request.config.getoption('acceptance'):
pytest.skip("specify -A to run acceptance tests")
self.tmpdir = request.config.mktemp(request.function.__name__, numbered=True)

View File

@@ -1,8 +1,8 @@
.. _examples:
Usages and Examples
===========================================
Examples and customization tricks
=================================
Here is a (growing) list of examples. :ref:`Contact <contact>` us if you
need more examples or have questions. Also take a look at the

View File

@@ -173,14 +173,18 @@ Or to select "http" and "quick" tests::
.. note::
If you are using expressions such as "X and Y" then both X and Y
need to be simple non-keyword names. For example, "pass" or "from"
will result in SyntaxErrors because "-k" evaluates the expression.
If you are using expressions such as ``"X and Y"`` then both ``X`` and ``Y``
need to be simple non-keyword names. For example, ``"pass"`` or ``"from"``
will result in SyntaxErrors because ``"-k"`` evaluates the expression using
Python's `eval`_ function.
However, if the "-k" argument is a simple string, no such restrictions
apply. Also "-k 'not STRING'" has no restrictions. You can also
specify numbers like "-k 1.3" to match tests which are parametrized
with the float "1.3".
.. _`eval`: https://docs.python.org/3.6/library/functions.html#eval
However, if the ``"-k"`` argument is a simple string, no such restrictions
apply. Also ``"-k 'not STRING'"`` has no restrictions. You can also
specify numbers like ``"-k 1.3"`` to match tests which are parametrized
with the float ``"1.3"``.
Registering markers
-------------------------------------
@@ -223,13 +227,12 @@ For an example on how to add and work with markers from a plugin, see
It is recommended to explicitly register markers so that:
* there is one place in your test suite defining your markers
* There is one place in your test suite defining your markers
* asking for existing markers via ``pytest --markers`` gives good output
* Asking for existing markers via ``pytest --markers`` gives good output
* typos in function markers are treated as an error if you use
the ``--strict`` option. Future versions of ``pytest`` are probably
going to start treating non-registered markers as errors at some point.
* Typos in function markers are treated as an error if you use
the ``--strict`` option.
.. _`scoped-marking`:
@@ -392,6 +395,49 @@ The ``--markers`` option always gives you a list of available markers::
@pytest.mark.trylast: mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible.
.. _`passing callables to custom markers`:
Passing a callable to custom markers
--------------------------------------------
.. regendoc:wipe
Below is the config file that will be used in the next examples::
# content of conftest.py
import sys
def pytest_runtest_setup(item):
marker = item.get_marker('my_marker')
if marker is not None:
for info in marker:
print('Marker info name={} args={} kwars={}'.format(info.name, info.args, info.kwargs))
sys.stdout.flush()
A custom marker can have its argument set, i.e. ``args`` and ``kwargs`` properties, defined by either invoking it as a callable or using ``pytest.mark.MARKER_NAME.with_args``. These two methods achieve the same effect most of the time.
However, if there is a callable as the single positional argument with no keyword arguments, using the ``pytest.mark.MARKER_NAME(c)`` will not pass ``c`` as a positional argument but decorate ``c`` with the custom marker (see :ref:`MarkDecorator <mark>`). Fortunately, ``pytest.mark.MARKER_NAME.with_args`` comes to the rescue::
# content of test_custom_marker.py
import pytest
def hello_world(*args, **kwargs):
return 'Hello World'
@pytest.mark.my_marker.with_args(hello_world)
def test_with_args():
pass
The output is as follows::
$ pytest -q -s
Marker info name=my_marker args=(<function hello_world at 0xdeadbeef>,) kwars={}
.
1 passed in 0.12 seconds
We can see that the custom marker has its argument set extended with the function ``hello_world``. This is the key different between creating a custom marker as a callable, which invokes ``__call__`` behind the scenes, and using ``with_args``.
Reading markers which were set from multiple places
----------------------------------------------------
@@ -491,7 +537,7 @@ then you will see two tests skipped and two executed tests as expected::
test_plat.py s.s.
======= short test summary info ========
SKIP [2] $REGENDOC_TMPDIR/conftest.py:12: cannot run on platform linux
SKIP [2] $REGENDOC_TMPDIR/conftest.py:13: cannot run on platform linux
======= 2 passed, 2 skipped in 0.12 seconds ========

View File

@@ -36,7 +36,7 @@ Now we add a test configuration like this::
def pytest_generate_tests(metafunc):
if 'param1' in metafunc.fixturenames:
if metafunc.config.option.all:
if metafunc.config.getoption('all'):
end = 5
else:
end = 2
@@ -413,7 +413,7 @@ Running it results in some skips if we don't have all the python interpreters in
. $ pytest -rs -q multipython.py
sssssssssssssss.........sss.........sss.........
======= short test summary info ========
SKIP [21] $REGENDOC_TMPDIR/CWD/multipython.py:23: 'python2.6' not found
SKIP [21] $REGENDOC_TMPDIR/CWD/multipython.py:24: 'python2.6' not found
27 passed, 21 skipped in 0.12 seconds
Indirect parametrization of optional implementations/imports
@@ -467,7 +467,7 @@ If you run this with reporting for skips enabled::
test_module.py .s
======= short test summary info ========
SKIP [1] $REGENDOC_TMPDIR/conftest.py:10: could not import 'opt2'
SKIP [1] $REGENDOC_TMPDIR/conftest.py:11: could not import 'opt2'
======= 1 passed, 1 skipped in 0.12 seconds ========

View File

@@ -358,7 +358,7 @@ get on the terminal - we are working on that)::
> int(s)
E ValueError: invalid literal for int() with base 10: 'qwe'
<0-codegen $PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/python.py:1219>:1: ValueError
<0-codegen $PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/python_api.py:579>:1: ValueError
_______ TestRaises.test_raises_doesnt ________
self = <failure_demo.TestRaises object at 0xdeadbeef>

View File

@@ -127,7 +127,7 @@ Control skipping of tests according to command line option
.. regendoc:wipe
Here is a ``conftest.py`` file adding a ``--runslow`` command
line option to control skipping of ``slow`` marked tests:
line option to control skipping of ``pytest.mark.slow`` marked tests:
.. code-block:: python
@@ -136,7 +136,16 @@ line option to control skipping of ``slow`` marked tests:
import pytest
def pytest_addoption(parser):
parser.addoption("--runslow", action="store_true",
help="run slow tests")
default=False, help="run slow tests")
def pytest_collection_modifyitems(config, items):
if config.getoption("--runslow"):
# --runslow given in cli: do not skip slow tests
return
skip_slow = pytest.mark.skip(reason="need --runslow option to run")
for item in items:
if "slow" in item.keywords:
item.add_marker(skip_slow)
We can now write a test module like this:
@@ -146,17 +155,11 @@ We can now write a test module like this:
import pytest
slow = pytest.mark.skipif(
not pytest.config.getoption("--runslow"),
reason="need --runslow option to run"
)
def test_func_fast():
pass
@slow
@pytest.mark.slow
def test_func_slow():
pass
@@ -170,7 +173,7 @@ and when running it will see a skipped "slow" test::
test_module.py .s
======= short test summary info ========
SKIP [1] test_module.py:13: need --runslow option to run
SKIP [1] test_module.py:8: need --runslow option to run
======= 1 passed, 1 skipped in 0.12 seconds ========
@@ -363,14 +366,14 @@ out which tests are the slowest. Let's make an artificial test suite:
import time
def test_funcfast():
pass
def test_funcslow1():
time.sleep(0.1)
def test_funcslow2():
def test_funcslow1():
time.sleep(0.2)
def test_funcslow2():
time.sleep(0.3)
Now we can profile which test functions execute the slowest::
$ pytest --durations=3
@@ -382,9 +385,9 @@ Now we can profile which test functions execute the slowest::
test_some_are_slow.py ...
======= slowest 3 test durations ========
0.20s call test_some_are_slow.py::test_funcslow2
0.10s call test_some_are_slow.py::test_funcslow1
0.00s setup test_some_are_slow.py::test_funcfast
0.30s call test_some_are_slow.py::test_funcslow2
0.20s call test_some_are_slow.py::test_funcslow1
0.10s call test_some_are_slow.py::test_funcfast
======= 3 passed in 0.12 seconds ========
incremental testing - test steps
@@ -761,6 +764,47 @@ and run it::
You'll see that the fixture finalizers could use the precise reporting
information.
``PYTEST_CURRENT_TEST`` environment variable
--------------------------------------------
.. versionadded:: 3.2
Sometimes a test session might get stuck and there might be no easy way to figure out
which test got stuck, for example if pytest was run in quiet mode (``-q``) or you don't have access to the console
output. This is particularly a problem if the problem helps only sporadically, the famous "flaky" kind of tests.
``pytest`` sets a ``PYTEST_CURRENT_TEST`` environment variable when running tests, which can be inspected
by process monitoring utilities or libraries like `psutil <https://pypi.python.org/pypi/psutil>`_ to discover which
test got stuck if necessary:
.. code-block:: python
import psutil
for pid in psutil.pids():
environ = psutil.Process(pid).environ()
if 'PYTEST_CURRENT_TEST' in environ:
print(f'pytest process {pid} running: {environ["PYTEST_CURRENT_TEST"]}')
During the test session pytest will set ``PYTEST_CURRENT_TEST`` to the current test
:ref:`nodeid <nodeids>` and the current stage, which can be ``setup``, ``call``
and ``teardown``.
For example, when running a single test function named ``test_foo`` from ``foo_module.py``,
``PYTEST_CURRENT_TEST`` will be set to:
#. ``foo_module.py::test_foo (setup)``
#. ``foo_module.py::test_foo (call)``
#. ``foo_module.py::test_foo (teardown)``
In that order.
.. note::
The contents of ``PYTEST_CURRENT_TEST`` is meant to be human readable and the actual format
can be changed between releases (even bug fixes) so it shouldn't be relied on for scripting
or automation.
Freezing pytest
---------------

View File

@@ -109,7 +109,7 @@ Note that if you misspell a function argument or want
to use one that isn't available, you'll see an error
with a list of available function arguments.
.. Note::
.. note::
You can always issue::
@@ -117,12 +117,6 @@ with a list of available function arguments.
to see available fixtures.
In versions prior to 2.3 there was no ``@pytest.fixture`` marker
and you had to use a magic ``pytest_funcarg__NAME`` prefix
for the fixture factory. This remains and will remain supported
but is not anymore advertised as the primary means of declaring fixture
functions.
Fixtures: a prime example of dependency injection
---------------------------------------------------
@@ -292,14 +286,6 @@ the ``with`` statement ends.
Note that if an exception happens during the *setup* code (before the ``yield`` keyword), the
*teardown* code (after the ``yield``) will not be called.
.. note::
Prior to version 2.10, in order to use a ``yield`` statement to execute teardown code one
had to mark a fixture using the ``yield_fixture`` marker. From 2.10 onward, normal
fixtures can use ``yield`` directly so the ``yield_fixture`` decorator is no longer needed
and considered deprecated.
An alternative option for executing *teardown* code is to
make use of the ``addfinalizer`` method of the `request-context`_ object to register
finalization functions.

View File

@@ -9,7 +9,8 @@ Installation and Getting Started
**dependencies**: `py <http://pypi.python.org/pypi/py>`_,
`colorama (Windows) <http://pypi.python.org/pypi/colorama>`_,
`argparse (py26) <http://pypi.python.org/pypi/argparse>`_.
`argparse (py26) <http://pypi.python.org/pypi/argparse>`_,
`ordereddict (py26) <http://pypi.python.org/pypi/ordereddict>`_.
**documentation as PDF**: `download latest <https://media.readthedocs.org/pdf/pytest/latest/pytest.pdf>`_

View File

@@ -122,7 +122,7 @@ want to distribute them along with your application::
test_view.py
...
In this scheme, it is easy to your run tests using the ``--pyargs`` option::
In this scheme, it is easy to run your tests using the ``--pyargs`` option::
pytest --pyargs mypkg
@@ -249,15 +249,6 @@ by putting them into a ``[tool:pytest]`` section:
python_files = testing/*/*.py
.. note::
Prior to 3.0, the supported section name was ``[pytest]``. Due to how
this may collide with some distutils commands, the recommended
section name for ``setup.cfg`` files is now ``[tool:pytest]``.
Note that for ``pytest.ini`` and ``tox.ini`` files the section
name is ``[pytest]``.
Manual Integration
^^^^^^^^^^^^^^^^^^
@@ -276,7 +267,7 @@ your own setuptools Test command for invoking pytest.
def initialize_options(self):
TestCommand.initialize_options(self)
self.pytest_args = []
self.pytest_args = ''
def run_tests(self):
import shlex

177
doc/en/historical-notes.rst Normal file
View File

@@ -0,0 +1,177 @@
Historical Notes
================
This page lists features or behavior from previous versions of pytest which have changed over the years. They are
kept here as a historical note so users looking at old code can find documentation related to them.
cache plugin integrated into the core
-------------------------------------
.. versionadded:: 2.8
The functionality of the :ref:`core cache <cache>` plugin was previously distributed
as a third party plugin named ``pytest-cache``. The core plugin
is compatible regarding command line options and API usage except that you
can only store/receive data between test runs that is json-serializable.
funcargs and ``pytest_funcarg__``
---------------------------------
.. versionchanged:: 2.3
In versions prior to 2.3 there was no ``@pytest.fixture`` marker
and you had to use a magic ``pytest_funcarg__NAME`` prefix
for the fixture factory. This remains and will remain supported
but is not anymore advertised as the primary means of declaring fixture
functions.
``@pytest.yield_fixture`` decorator
-----------------------------------
.. versionchanged:: 2.10
Prior to version 2.10, in order to use a ``yield`` statement to execute teardown code one
had to mark a fixture using the ``yield_fixture`` marker. From 2.10 onward, normal
fixtures can use ``yield`` directly so the ``yield_fixture`` decorator is no longer needed
and considered deprecated.
``[pytest]`` header in ``setup.cfg``
------------------------------------
.. versionchanged:: 3.0
Prior to 3.0, the supported section name was ``[pytest]``. Due to how
this may collide with some distutils commands, the recommended
section name for ``setup.cfg`` files is now ``[tool:pytest]``.
Note that for ``pytest.ini`` and ``tox.ini`` files the section
name is ``[pytest]``.
Applying marks to ``@pytest.mark.parametrize`` parameters
---------------------------------------------------------
.. versionchanged:: 3.1
Prior to version 3.1 the supported mechanism for marking values
used the syntax::
import pytest
@pytest.mark.parametrize("test_input,expected", [
("3+5", 8),
("2+4", 6),
pytest.mark.xfail(("6*9", 42),),
])
def test_eval(test_input, expected):
assert eval(test_input) == expected
This was an initial hack to support the feature but soon was demonstrated to be incomplete,
broken for passing functions or applying multiple marks with the same name but different parameters.
The old syntax is planned to be removed in pytest-4.0.
``@pytest.mark.parametrize`` argument names as a tuple
------------------------------------------------------
.. versionchanged:: 2.4
In versions prior to 2.4 one needed to specify the argument
names as a tuple. This remains valid but the simpler ``"name1,name2,..."``
comma-separated-string syntax is now advertised first because
it's easier to write and produces less line noise.
setup: is now an "autouse fixture"
----------------------------------
.. versionchanged:: 2.3
During development prior to the pytest-2.3 release the name
``pytest.setup`` was used but before the release it was renamed
and moved to become part of the general fixture mechanism,
namely :ref:`autouse fixtures`
.. _string conditions:
Conditions as strings instead of booleans
-----------------------------------------
.. versionchanged:: 2.4
Prior to pytest-2.4 the only way to specify skipif/xfail conditions was
to use strings::
import sys
@pytest.mark.skipif("sys.version_info >= (3,3)")
def test_function():
...
During test function setup the skipif condition is evaluated by calling
``eval('sys.version_info >= (3,0)', namespace)``. The namespace contains
all the module globals, and ``os`` and ``sys`` as a minimum.
Since pytest-2.4 :ref:`boolean conditions <condition booleans>` are considered preferable
because markers can then be freely imported between test modules.
With strings you need to import not only the marker but all variables
used by the marker, which violates encapsulation.
The reason for specifying the condition as a string was that ``pytest`` can
report a summary of skip conditions based purely on the condition string.
With conditions as booleans you are required to specify a ``reason`` string.
Note that string conditions will remain fully supported and you are free
to use them if you have no need for cross-importing markers.
The evaluation of a condition string in ``pytest.mark.skipif(conditionstring)``
or ``pytest.mark.xfail(conditionstring)`` takes place in a namespace
dictionary which is constructed as follows:
* the namespace is initialized by putting the ``sys`` and ``os`` modules
and the pytest ``config`` object into it.
* updated with the module globals of the test function for which the
expression is applied.
The pytest ``config`` object allows you to skip based on a test
configuration value which you might have added::
@pytest.mark.skipif("not config.getvalue('db')")
def test_function(...):
...
The equivalent with "boolean conditions" is::
@pytest.mark.skipif(not pytest.config.getvalue("db"),
reason="--db was not specified")
def test_function(...):
pass
.. note::
You cannot use ``pytest.config.getvalue()`` in code
imported before pytest's argument parsing takes place. For example,
``conftest.py`` files are imported before command line parsing and thus
``config.getvalue()`` will not execute correctly.
``pytest.set_trace()``
----------------------
.. versionchanged:: 2.4
Previous to version 2.4 to set a break point in code one needed to use ``pytest.set_trace()``::
import pytest
def test_function():
...
pytest.set_trace() # invoke PDB debugger and tracing
This is no longer needed and one can use the native ``import pdb;pdb.set_trace()`` call directly.
For more details see :ref:`breakpoints`.

View File

@@ -59,7 +59,7 @@ Features
- Python2.6+, Python3.3+, PyPy-2.3, Jython-2.5 (untested);
- Rich plugin architecture, with over 150+ :ref:`external plugins <extplugins>` and thriving community;
- Rich plugin architecture, with over 315+ `external plugins <http://plugincompat.herokuapp.com>`_ and thriving community;
Documentation

View File

@@ -10,6 +10,7 @@ By using the ``pytest.mark`` helper you can easily set
metadata on your test functions. There are
some builtin markers, for example:
* :ref:`skip <skip>` - always skip a test function
* :ref:`skipif <skipif>` - skip a test function if a certain condition is met
* :ref:`xfail <xfail>` - produce an "expected failure" outcome if a certain
condition is met

View File

@@ -26,7 +26,7 @@ Supported nose Idioms
* setup and teardown at module/class/method level
* SkipTest exceptions and markers
* setup/teardown decorators
* ``yield``-based tests and their setup
* ``yield``-based tests and their setup (considered deprecated as of pytest 3.0)
* ``__test__`` attribute on modules/classes/functions
* general usage of nose utilities

View File

@@ -99,26 +99,6 @@ for example with the builtin ``mark.xfail``::
def test_eval(test_input, expected):
assert eval(test_input) == expected
.. note::
prior to version 3.1 the supported mechanism for marking values
used the syntax::
import pytest
@pytest.mark.parametrize("test_input,expected", [
("3+5", 8),
("2+4", 6),
pytest.mark.xfail(("6*9", 42),),
])
def test_eval(test_input, expected):
assert eval(test_input) == expected
This was an initial hack to support the feature but soon was demonstrated to be incomplete,
broken for passing functions or applying multiple marks with the same name but different parameters.
The old syntax will be removed in pytest-4.0.
Let's run this::
$ pytest
@@ -143,15 +123,8 @@ To get all combinations of multiple parametrized arguments you can stack
def test_foo(x, y):
pass
This will run the test with the arguments set to x=0/y=2, x=0/y=3, x=1/y=2 and
x=1/y=3.
.. note::
In versions prior to 2.4 one needed to specify the argument
names as a tuple. This remains valid but the simpler ``"name1,name2,..."``
comma-separated-string syntax is now advertised first because
it's easier to write and produces less line noise.
This will run the test with the arguments set to ``x=0/y=2``, ``x=0/y=3``, ``x=1/y=2`` and
``x=1/y=3``.
.. _`pytest_generate_tests`:
@@ -187,7 +160,7 @@ command line option and the parametrization of our test function::
def pytest_generate_tests(metafunc):
if 'stringinput' in metafunc.fixturenames:
metafunc.parametrize("stringinput",
metafunc.config.option.stringinput)
metafunc.config.getoption('stringinput'))
If we now pass two stringinput values, our test will run twice::
@@ -222,7 +195,7 @@ list::
$ pytest -q -rs test_strings.py
s
======= short test summary info ========
SKIP [1] test_strings.py:1: got empty parameter set ['stringinput'], function test_valid_string at $REGENDOC_TMPDIR/test_strings.py:1
SKIP [1] test_strings.py:2: got empty parameter set ['stringinput'], function test_valid_string at $REGENDOC_TMPDIR/test_strings.py:1
1 skipped in 0.12 seconds
For further examples, you might want to look at :ref:`more

71
doc/en/pythonpath.rst Normal file
View File

@@ -0,0 +1,71 @@
.. _pythonpath:
pytest import mechanisms and ``sys.path``/``PYTHONPATH``
========================================================
Here's a list of scenarios where pytest may need to change ``sys.path`` in order
to import test modules or ``conftest.py`` files.
Test modules / ``conftest.py`` files inside packages
----------------------------------------------------
Consider this file and directory layout::
root/
|- foo/
|- __init__.py
|- conftest.py
|- bar/
|- __init__.py
|- tests/
|- __init__.py
|- test_foo.py
When executing::
pytest root/
pytest will find ``foo/bar/tests/test_foo.py`` and realize it is part of a package given that
there's an ``__init__.py`` file in the same folder. It will then search upwards until it can find the
last folder which still contains an ``__init__.py`` file in order to find the package *root* (in
this case ``foo/``). To load the module, it will insert ``root/`` to the front of
``sys.path`` (if not there already) in order to load
``test_foo.py`` as the *module* ``foo.bar.tests.test_foo``.
The same logic applies to the ``conftest.py`` file: it will be imported as ``foo.conftest`` module.
Preserving the full package name is important when tests live in a package to avoid problems
and allow test modules to have duplicated names. This is also discussed in details in
:ref:`test discovery`.
Standalone test modules / ``conftest.py`` files
-----------------------------------------------
Consider this file and directory layout::
root/
|- foo/
|- conftest.py
|- bar/
|- tests/
|- test_foo.py
When executing::
pytest root/
pytest will find ``foo/bar/tests/test_foo.py`` and realize it is NOT part of a package given that
there's no ``__init__.py`` file in the same folder. It will then add ``root/foo/bar/tests`` to
``sys.path`` in order to import ``test_foo.py`` as the *module* ``test_foo``. The same is done
with the ``conftest.py`` file by adding ``root/foo`` to ``sys.path`` to import it as ``conftest``.
For this reason this layout cannot have test modules with the same name, as they all will be
imported in the global import namespace.
This is also discussed in details in :ref:`test discovery`.

View File

@@ -1,10 +0,0 @@
setup: is now an "autouse fixture"
========================================================
During development prior to the pytest-2.3 release the name
``pytest.setup`` was used but before the release it was renamed
and moved to become part of the general fixture mechanism,
namely :ref:`autouse fixtures`

View File

@@ -27,6 +27,7 @@ corresponding to the "short" letters shown in the test progress::
(See :ref:`how to change command line options defaults`)
.. _skipif:
.. _skip:
.. _`condition booleans`:
Skipping test functions
@@ -347,64 +348,3 @@ test instances when using parametrize:
assert n + 1 == expected
.. _string conditions:
Conditions as strings instead of booleans
-----------------------------------------
Prior to pytest-2.4 the only way to specify skipif/xfail conditions was
to use strings::
import sys
@pytest.mark.skipif("sys.version_info >= (3,3)")
def test_function():
...
During test function setup the skipif condition is evaluated by calling
``eval('sys.version_info >= (3,0)', namespace)``. The namespace contains
all the module globals, and ``os`` and ``sys`` as a minimum.
Since pytest-2.4 `condition booleans`_ are considered preferable
because markers can then be freely imported between test modules.
With strings you need to import not only the marker but all variables
used by the marker, which violates encapsulation.
The reason for specifying the condition as a string was that ``pytest`` can
report a summary of skip conditions based purely on the condition string.
With conditions as booleans you are required to specify a ``reason`` string.
Note that string conditions will remain fully supported and you are free
to use them if you have no need for cross-importing markers.
The evaluation of a condition string in ``pytest.mark.skipif(conditionstring)``
or ``pytest.mark.xfail(conditionstring)`` takes place in a namespace
dictionary which is constructed as follows:
* the namespace is initialized by putting the ``sys`` and ``os`` modules
and the pytest ``config`` object into it.
* updated with the module globals of the test function for which the
expression is applied.
The pytest ``config`` object allows you to skip based on a test
configuration value which you might have added::
@pytest.mark.skipif("not config.getvalue('db')")
def test_function(...):
...
The equivalent with "boolean conditions" is::
@pytest.mark.skipif(not pytest.config.getvalue("db"),
reason="--db was not specified")
def test_function(...):
pass
.. note::
You cannot use ``pytest.config.getvalue()`` in code
imported before pytest's argument parsing takes place. For example,
``conftest.py`` files are imported before command line parsing and thus
``config.getvalue()`` will not execute correctly.

View File

@@ -2,58 +2,77 @@
.. _`unittest.TestCase`:
.. _`unittest`:
Support for unittest.TestCase / Integration of fixtures
=====================================================================
unittest.TestCase Support
=========================
.. _`unittest.py style`: http://docs.python.org/library/unittest.html
``pytest`` supports running Python ``unittest``-based tests out of the box.
It's meant for leveraging existing ``unittest``-based test suites
to use pytest as a test runner and also allow to incrementally adapt
the test suite to take full advantage of pytest's features.
``pytest`` has support for running Python `unittest.py style`_ tests.
It's meant for leveraging existing unittest-style projects
to use pytest features. Concretely, pytest will automatically
collect ``unittest.TestCase`` subclasses and their ``test`` methods in
test files. It will invoke typical setup/teardown methods and
generally try to make test suites written to run on unittest, to also
run using ``pytest``. We assume here that you are familiar with writing
``unittest.TestCase`` style tests and rather focus on
integration aspects.
To run an existing ``unittest``-style test suite using ``pytest``, type::
Note that this is meant as a provisional way of running your test code
until you fully convert to pytest-style tests. To fully take advantage of
:ref:`fixtures <fixture>`, :ref:`parametrization <parametrize>` and
:ref:`hooks <writing-plugins>` you should convert (tools like `unittest2pytest
<https://pypi.python.org/pypi/unittest2pytest/>`__ are helpful).
Also, not all 3rd party pluging are expected to work best with
``unittest.TestCase`` style tests.
pytest tests
Usage
-------------------------------------------------------------------
After :ref:`installation` type::
pytest will automatically collect ``unittest.TestCase`` subclasses and
their ``test`` methods in ``test_*.py`` or ``*_test.py`` files.
pytest
Almost all ``unittest`` features are supported:
and you should be able to run your unittest-style tests if they
are contained in ``test_*`` modules. If that works for you then
you can make use of most :ref:`pytest features <features>`, for example
``--pdb`` debugging in failures, using :ref:`plain assert-statements <assert>`,
:ref:`more informative tracebacks <tbreportdemo>`, stdout-capturing or
distributing tests to multiple CPUs via the ``-nNUM`` option if you
installed the ``pytest-xdist`` plugin. Please refer to
the general ``pytest`` documentation for many more examples.
* ``@unittest.skip`` style decorators;
* ``setUp/tearDown``;
* ``setUpClass/tearDownClass()``;
.. note::
.. _`load_tests protocol`: https://docs.python.org/3/library/unittest.html#load-tests-protocol
.. _`setUpModule/tearDownModule`: https://docs.python.org/3/library/unittest.html#setupmodule-and-teardownmodule
.. _`subtests`: https://docs.python.org/3/library/unittest.html#distinguishing-test-iterations-using-subtests
Running tests from ``unittest.TestCase`` subclasses with ``--pdb`` will
disable tearDown and cleanup methods for the case that an Exception
occurs. This allows proper post mortem debugging for all applications
which have significant logic in their tearDown machinery. However,
supporting this feature has the following side effect: If people
overwrite ``unittest.TestCase`` ``__call__`` or ``run``, they need to
to overwrite ``debug`` in the same way (this is also true for standard
unittest).
Up to this point pytest does not have support for the following features:
Mixing pytest fixtures into unittest.TestCase style tests
-----------------------------------------------------------
* `load_tests protocol`_;
* `setUpModule/tearDownModule`_;
* `subtests`_;
Benefits out of the box
-----------------------
By running your test suite with pytest you can make use of several features,
in most cases without having to modify existing code:
* Obtain :ref:`more informative tracebacks <tbreportdemo>`;
* :ref:`stdout and stderr <captures>` capturing;
* :ref:`Test selection options <select-tests>` using ``-k`` and ``-m`` flags;
* :ref:`maxfail`;
* :ref:`--pdb <pdb-option>` command-line option for debugging on test failures
(see :ref:`note <pdb-unittest-note>` below);
* Distribute tests to multiple CPUs using the `pytest-xdist <http://pypi.python.org/pypi/pytest-xdist>`_ plugin;
* Use :ref:`plain assert-statements <assert>` instead of ``self.assert*`` functions (`unittest2pytest
<https://pypi.python.org/pypi/unittest2pytest/>`__ is immensely helpful in this);
pytest features in ``unittest.TestCase`` subclasses
---------------------------------------------------
The following pytest features work in ``unittest.TestCase`` subclasses:
* :ref:`Marks <mark>`: :ref:`skip <skip>`, :ref:`skipif <skipif>`, :ref:`xfail <xfail>`;
* :ref:`Auto-use fixtures <mixing-fixtures>`;
The following pytest features **do not** work, and probably
never will due to different design philosophies:
* :ref:`Fixtures <fixture>` (except for ``autouse`` fixtures, see :ref:`below <mixing-fixtures>`);
* :ref:`Parametrization <parametrize>`;
* :ref:`Custom hooks <writing-plugins>`;
Third party plugins may or may not work well, depending on the plugin and the test suite.
.. _mixing-fixtures:
Mixing pytest fixtures into ``unittest.TestCase`` subclasses using marks
------------------------------------------------------------------------
Running your unittest with ``pytest`` allows you to use its
:ref:`fixture mechanism <fixture>` with ``unittest.TestCase`` style
@@ -143,8 +162,8 @@ share the same ``self.db`` instance which was our intention
when writing the class-scoped fixture function above.
autouse fixtures and accessing other fixtures
-------------------------------------------------------------------
Using autouse fixtures and accessing other fixtures
---------------------------------------------------
Although it's usually better to explicitly declare use of fixtures you need
for a given test, you may sometimes want to have fixtures that are
@@ -165,6 +184,7 @@ creation of a per-test temporary directory::
import unittest
class MyTest(unittest.TestCase):
@pytest.fixture(autouse=True)
def initdir(self, tmpdir):
tmpdir.chdir() # change to pytest-provided temporary directory
@@ -191,12 +211,25 @@ was executed ahead of the ``test_method``.
.. note::
While pytest supports receiving fixtures via :ref:`test function arguments <funcargs>` for non-unittest test methods, ``unittest.TestCase`` methods cannot directly receive fixture
function arguments as implementing that is likely to inflict
``unittest.TestCase`` methods cannot directly receive fixture
arguments as implementing that is likely to inflict
on the ability to run general unittest.TestCase test suites.
Maybe optional support would be possible, though. If unittest finally
grows a plugin system that should help as well. In the meanwhile, the
above ``usefixtures`` and ``autouse`` examples should help to mix in
pytest fixtures into unittest suites. And of course you can also start
to selectively leave away the ``unittest.TestCase`` subclassing, use
plain asserts and get the unlimited pytest feature set.
The above ``usefixtures`` and ``autouse`` examples should help to mix in
pytest fixtures into unittest suites.
You can also gradually move away from subclassing from ``unittest.TestCase`` to *plain asserts*
and then start to benefit from the full pytest feature set step by step.
.. _pdb-unittest-note:
.. note::
Running tests from ``unittest.TestCase`` subclasses with ``--pdb`` will
disable tearDown and cleanup methods for the case that an Exception
occurs. This allows proper post mortem debugging for all applications
which have significant logic in their tearDown machinery. However,
supporting this feature has the following side effect: If people
overwrite ``unittest.TestCase`` ``__call__`` or ``run``, they need to
to overwrite ``debug`` in the same way (this is also true for standard
unittest).

View File

@@ -17,7 +17,7 @@ You can invoke testing through the Python interpreter from the command line::
python -m pytest [...]
This is almost equivalent to invoking the command line script ``pytest [...]``
directly, except that python will also add the current directory to ``sys.path``.
directly, except that Python will also add the current directory to ``sys.path``.
Possible exit codes
--------------------------------------------------------------
@@ -41,6 +41,8 @@ Getting help on version, option names, environment variables
pytest -h | --help # show help on command line and config file options
.. _maxfail:
Stopping after the first (or N) failures
---------------------------------------------------
@@ -49,26 +51,69 @@ To stop the testing process after the first (N) failures::
pytest -x # stop after first failure
pytest --maxfail=2 # stop after two failures
.. _select-tests:
Specifying tests / selecting tests
---------------------------------------------------
Several test run options::
Pytest supports several ways to run and select tests from the command-line.
pytest test_mod.py # run tests in module
pytest somepath # run all tests below somepath
pytest -k stringexpr # only run tests with names that match the
# "string expression", e.g. "MyClass and not method"
# will select TestMyClass.test_something
# but not TestMyClass.test_method_simple
pytest test_mod.py::test_func # only run tests that match the "node ID",
# e.g. "test_mod.py::test_func" will select
# only test_func in test_mod.py
pytest test_mod.py::TestClass::test_method # run a single method in
# a single class
**Run tests in a module**
Import 'pkg' and use its filesystem location to find and run tests::
::
pytest --pyargs pkg # run all tests found below directory of pkg
pytest test_mod.py
**Run tests in a directory**
::
pytest testing/
**Run tests by keyword expressions**
::
pytest -k "MyClass and not method"
This will run tests which contain names that match the given *string expression*, which can
include Python operators that use filenames, class names and function names as variables.
The example above will run ``TestMyClass.test_something`` but not ``TestMyClass.test_method_simple``.
.. _nodeids:
**Run tests by node ids**
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::
pytest test_mod.py::test_func
Another example specifying a test method in the command line::
pytest test_mod.py::TestClass::test_method
**Run tests by marker expressions**
::
pytest -m slow
Will run all tests which are decorated with the ``@pytest.mark.slow`` decorator.
For more information see :ref:`marks <mark>`.
**Run tests from packages**
::
pytest --pyargs pkg.testing
This will import ``pkg.testing`` and use its filesystem location to find and run tests from.
Modifying Python traceback printing
----------------------------------------------
@@ -94,6 +139,9 @@ with Ctrl+C to find out where the tests are *hanging*. By default no output
will be shown (because KeyboardInterrupt is caught by pytest). By using this
option you make sure a trace is shown.
.. _pdb-option:
Dropping to PDB_ (Python Debugger) on failures
-----------------------------------------------
@@ -123,22 +171,15 @@ for example::
>>> sys.last_value
AssertionError('assert result == "ok"',)
Setting a breakpoint / aka ``set_trace()``
----------------------------------------------------
.. _breakpoints:
If you want to set a breakpoint and enter the ``pdb.set_trace()`` you
can use a helper::
Setting breakpoints
-------------------
import pytest
def test_function():
...
pytest.set_trace() # invoke PDB debugger and tracing
.. versionadded: 2.4.0
.. versionadded: 2.0.0
Prior to pytest version 2.0.0 you could only enter PDB_ tracing if you disabled
capturing on the command line via ``pytest -s``. In later versions, pytest
automatically disables its output capture when you enter PDB_ tracing:
To set a breakpoint in your code use the native Python ``import pdb;pdb.set_trace()`` call
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
@@ -148,12 +189,6 @@ automatically disables its output capture when you enter PDB_ tracing:
for test output occurring after you exit the interactive PDB_ tracing session
and continue with the regular test run.
.. versionadded: 2.4.0
Since pytest version 2.4.0 you can also use the native Python
``import pdb;pdb.set_trace()`` call to enter PDB_ tracing without having to use
the ``pytest.set_trace()`` wrapper or explicitly disable pytest's output
capturing via ``pytest -s``.
.. _durations:
@@ -276,6 +311,13 @@ Creating resultlog format files
This option is rarely used and is scheduled for removal in 4.0.
An alternative for users which still need similar functionality is to use the
`pytest-tap <https://pypi.python.org/pypi/pytest-tap>`_ plugin which provides
a stream of test data.
If you have any concerns, please don't hesitate to
`open an issue <https://github.com/pytest-dev/pytest/issues>`_.
To create plain-text machine-readable result files you can issue::
pytest --resultlog=path

View File

@@ -78,6 +78,40 @@ Both ``-W`` command-line option and ``filterwarnings`` ini option are based on P
`-W option`_ and `warnings.simplefilter`_, so please refer to those sections in the Python
documentation for other examples and advanced usage.
``@pytest.mark.filterwarnings``
-------------------------------
.. versionadded:: 3.2
You can use the ``@pytest.mark.filterwarnings`` to add warning filters to specific test items,
allowing you to have finer control of which warnings should be captured at test, class or
even module level:
.. code-block:: python
import warnings
def api_v1():
warnings.warn(UserWarning("api v1, should use functions from v2"))
return 1
@pytest.mark.filterwarnings('ignore:api v1')
def test_one():
assert api_v1() == 1
Filters applied using a mark take precedence over filters passed on the command line or configured
by the ``filterwarnings`` ini option.
You may apply a filter to all tests of a class by using the ``filterwarnings`` mark as a class
decorator or to all tests in a module by setting the ``pytestmark`` variable:
.. code-block:: python
# turns all warnings into errors for this module
pytestmark = @pytest.mark.filterwarnings('error')
.. note::
``DeprecationWarning`` and ``PendingDeprecationWarning`` are hidden by the standard library

View File

@@ -49,7 +49,7 @@ Plugin discovery order at tool startup
Note that pytest does not find ``conftest.py`` files in deeper nested
sub directories at tool startup. It is usually a good idea to keep
your conftest.py file in the top level test or project root directory.
your ``conftest.py`` file in the top level test or project root directory.
* by recursively loading all plugins specified by the
``pytest_plugins`` variable in ``conftest.py`` files
@@ -90,14 +90,16 @@ Here is how you might run it::
pytest test_flat.py # will not show "setting up"
pytest a/test_sub.py # will show "setting up"
.. Note::
.. note::
If you have ``conftest.py`` files which do not reside in a
python package directory (i.e. one containing an ``__init__.py``) then
"import conftest" can be ambiguous because there might be other
``conftest.py`` files as well on your PYTHONPATH or ``sys.path``.
``conftest.py`` files as well on your ``PYTHONPATH`` or ``sys.path``.
It is thus good practice for projects to either put ``conftest.py``
under a package scope or to never import anything from a
conftest.py file.
``conftest.py`` file.
See also: :ref:`pythonpath`.
Writing your own plugin
@@ -122,8 +124,8 @@ to extend and add functionality.
for authoring plugins.
The template provides an excellent starting point with a working plugin,
tests running with tox, comprehensive README and
entry-pointy already pre-configured.
tests running with tox, a comprehensive README file as well as a
pre-configured entry-point.
Also consider :ref:`contributing your plugin to pytest-dev<submitplugin>`
once it has some happy users other than yourself.
@@ -286,34 +288,101 @@ the ``--trace-config`` option.
Testing plugins
---------------
pytest comes with some facilities that you can enable for testing your
plugin. Given that you have an installed plugin you can enable the
:py:class:`testdir <_pytest.pytester.Testdir>` fixture via specifying a
command line option to include the pytester plugin (``-p pytester``) or
by putting ``pytest_plugins = "pytester"`` into your test or
``conftest.py`` file. You then will have a ``testdir`` fixture which you
can use like this::
pytest comes with a plugin named ``pytester`` that helps you write tests for
your plugin code. The plugin is disabled by default, so you will have to enable
it before you can use it.
# content of test_myplugin.py
You can do so by adding the following line to a ``conftest.py`` file in your
testing directory:
pytest_plugins = "pytester" # to get testdir fixture
.. code-block:: python
def test_myplugin(testdir):
# content of conftest.py
pytest_plugins = ["pytester"]
Alternatively you can invoke pytest with the ``-p pytester`` command line
option.
This will allow you to use the :py:class:`testdir <_pytest.pytester.Testdir>`
fixture for testing your plugin code.
Let's demonstrate what you can do with the plugin with an example. Imagine we
developed a plugin that provides a fixture ``hello`` which yields a function
and we can invoke this function with one optional parameter. It will return a
string value of ``Hello World!`` if we do not supply a value or ``Hello
{value}!`` if we do supply a string value.
.. code-block:: python
# -*- coding: utf-8 -*-
import pytest
def pytest_addoption(parser):
group = parser.getgroup('helloworld')
group.addoption(
'--name',
action='store',
dest='name',
default='World',
help='Default "name" for hello().'
)
@pytest.fixture
def hello(request):
name = request.config.getoption('name')
def _hello(name=None):
if not name:
name = request.config.getoption('name')
return "Hello {name}!".format(name=name)
return _hello
Now the ``testdir`` fixture provides a convenient API for creating temporary
``conftest.py`` files and test files. It also allows us to run the tests and
return a result object, with which we can assert the tests' outcomes.
.. code-block:: python
def test_hello(testdir):
"""Make sure that our plugin works."""
# create a temporary conftest.py file
testdir.makeconftest("""
import pytest
@pytest.fixture(params=[
"Brianna",
"Andreas",
"Floris",
])
def name(request):
return request.param
""")
# create a temporary pytest test file
testdir.makepyfile("""
def test_example():
pass
""")
result = testdir.runpytest("--verbose")
result.stdout.fnmatch_lines("""
test_example*
def test_hello_default(hello):
assert hello() == "Hello World!"
def test_hello_name(hello, name):
assert hello(name) == "Hello {0}!".format(name)
""")
Note that by default ``testdir.runpytest()`` will perform a pytest
in-process. You can pass the command line option ``--runpytest=subprocess``
to have it happen in a subprocess.
# run all tests with pytest
result = testdir.runpytest()
# check that all 4 tests passed
result.assert_outcomes(passed=4)
For more information about the result object that ``runpytest()`` returns, and
the methods that it provides please check out the :py:class:`RunResult
<_pytest.pytester.RunResult>` documentation.
Also see the :py:class:`RunResult <_pytest.pytester.RunResult>` for more
methods of the result object that you get from a call to ``runpytest``.
.. _`writinghooks`:
@@ -575,6 +644,7 @@ Session related reporting hooks:
.. autofunction:: pytest_collectreport
.. autofunction:: pytest_deselected
.. autofunction:: pytest_report_header
.. autofunction:: pytest_report_collectionfinish
.. autofunction:: pytest_report_teststatus
.. autofunction:: pytest_terminal_summary
.. autofunction:: pytest_fixture_setup

View File

@@ -16,16 +16,16 @@ from _pytest.freeze_support import freeze_includes
from _pytest import __version__
from _pytest.debugging import pytestPDB as __pytestPDB
from _pytest.recwarn import warns, deprecated_call
from _pytest.runner import fail, skip, importorskip, exit
from _pytest.outcomes import fail, skip, importorskip, exit, xfail
from _pytest.mark import MARK_GEN as mark, param
from _pytest.skipping import xfail
from _pytest.main import Item, Collector, File, Session
from _pytest.fixtures import fillfixtures as _fillfuncargs
from _pytest.python import (
raises, approx,
Module, Class, Instance, Function, Generator,
)
from _pytest.python_api import approx, raises
set_trace = __pytestPDB.set_trace
__all__ = [

View File

@@ -5,4 +5,4 @@ if "%TOXENV%" == "coveralls" (
exit /b 0
)
)
C:\Python35\python -m tox
C:\Python36\python -m tox

View File

@@ -46,11 +46,12 @@ def main():
install_requires = ['py>=1.4.33', 'setuptools'] # pluggy is vendored in _pytest.vendored_packages
extras_require = {}
if has_environment_marker_support():
extras_require[':python_version=="2.6"'] = ['argparse']
extras_require[':python_version=="2.6"'] = ['argparse', 'ordereddict']
extras_require[':sys_platform=="win32"'] = ['colorama']
else:
if sys.version_info < (2, 7):
install_requires.append('argparse')
install_requires.append('ordereddict')
if sys.platform == 'win32':
install_requires.append('colorama')

View File

@@ -74,14 +74,13 @@ class TestGeneralUsage(object):
print("---unconfigure")
""")
result = testdir.runpytest("-s", "asd")
assert result.ret == 4 # EXIT_USAGEERROR
assert result.ret == 4 # EXIT_USAGEERROR
result.stderr.fnmatch_lines(["ERROR: file not found*asd"])
result.stdout.fnmatch_lines([
"*---configure",
"*---unconfigure",
])
def test_config_preparse_plugin_option(self, testdir):
testdir.makepyfile(pytest_xyz="""
def pytest_addoption(parser):
@@ -119,7 +118,7 @@ class TestGeneralUsage(object):
testdir.makepyfile(import_fails="import does_not_work")
result = testdir.runpytest(p)
result.stdout.fnmatch_lines([
#XXX on jython this fails: "> import import_fails",
# XXX on jython this fails: "> import import_fails",
"ImportError while importing test module*",
"*No module named *does_not_work*",
])
@@ -131,7 +130,7 @@ class TestGeneralUsage(object):
result = testdir.runpytest(p1, p2)
assert result.ret
result.stderr.fnmatch_lines([
"*ERROR: not found:*%s" %(p2.basename,)
"*ERROR: not found:*%s" % (p2.basename,)
])
def test_issue486_better_reporting_on_conftest_load_failure(self, testdir):
@@ -147,7 +146,6 @@ class TestGeneralUsage(object):
*ERROR*could not load*conftest.py*
""")
def test_early_skip(self, testdir):
testdir.mkdir("xyz")
testdir.makeconftest("""
@@ -255,7 +253,7 @@ class TestGeneralUsage(object):
if path.basename.startswith("conftest"):
return MyCollector(path, parent)
""")
result = testdir.runpytest(c.basename+"::"+"xyz")
result = testdir.runpytest(c.basename + "::" + "xyz")
assert result.ret == 0
result.stdout.fnmatch_lines([
"*1 pass*",
@@ -310,7 +308,7 @@ class TestGeneralUsage(object):
x
""")
result = testdir.runpytest()
assert result.ret == 3 # internal error
assert result.ret == 3 # internal error
result.stderr.fnmatch_lines([
"INTERNAL*pytest_configure*",
"INTERNAL*x*",
@@ -402,7 +400,7 @@ class TestGeneralUsage(object):
monkeypatch.setitem(sys.modules, 'myplugin', mod)
assert pytest.main(args=[str(tmpdir)], plugins=['myplugin']) == 0
def test_parameterized_with_bytes_regex(self, testdir):
def test_parametrized_with_bytes_regex(self, testdir):
p = testdir.makepyfile("""
import re
import pytest
@@ -410,12 +408,25 @@ class TestGeneralUsage(object):
def test_stuff(r):
pass
"""
)
)
res = testdir.runpytest(p)
res.stdout.fnmatch_lines([
'*1 passed*'
])
def test_parametrized_with_null_bytes(self, testdir):
"""Test parametrization with values that contain null bytes and unicode characters (#2644)"""
p = testdir.makepyfile(u"""
# encoding: UTF-8
import pytest
@pytest.mark.parametrize("data", ["\\x00", u'ação'])
def test_foo(data):
assert data
""")
res = testdir.runpytest(p)
res.assert_outcomes(passed=2)
class TestInvocationVariants(object):
def test_earlyinit(self, testdir):
@@ -440,8 +451,8 @@ class TestInvocationVariants(object):
#collect
#cmdline
#Item
#assert collect.Item is Item
#assert collect.Collector is Collector
# assert collect.Item is Item
# assert collect.Collector is Collector
main
skip
xfail
@@ -676,7 +687,6 @@ class TestInvocationVariants(object):
import _pytest.config
assert type(_pytest.config.get_plugin_manager()) is _pytest.config.PytestPluginManager
def test_has_plugin(self, request):
"""Test hasplugin function of the plugin manager (#932)."""
assert request.config.pluginmanager.hasplugin('python')
@@ -719,12 +729,12 @@ class TestDurations(object):
result = testdir.runpytest("--durations=0")
assert result.ret == 0
for x in "123":
for y in 'call',: #'setup', 'call', 'teardown':
for y in 'call', : # 'setup', 'call', 'teardown':
for line in result.stdout.lines:
if ("test_%s" % x) in line and y in line:
break
else:
raise AssertionError("not found %s %s" % (x,y))
raise AssertionError("not found %s %s" % (x, y))
def test_with_deselected(self, testdir):
testdir.makepyfile(self.source)
@@ -764,6 +774,7 @@ class TestDurationWithFixture(object):
def test_2():
time.sleep(frag)
"""
def test_setup_function(self, testdir):
testdir.makepyfile(self.source)
result = testdir.runpytest("--durations=10")

View File

@@ -1,9 +1,11 @@
# coding: utf-8
from __future__ import absolute_import, division, print_function
import sys
import _pytest._code
import py
import pytest
from test_excinfo import TWMock
def test_ne():
@@ -12,6 +14,7 @@ def test_ne():
code2 = _pytest._code.Code(compile('foo = "baz"', '', 'exec'))
assert code2 != code1
def test_code_gives_back_name_for_not_existing_file():
name = 'abc-123'
co_code = compile("pass\n", name, 'exec')
@@ -20,6 +23,7 @@ def test_code_gives_back_name_for_not_existing_file():
assert str(code.path) == name
assert code.fullsource is None
def test_code_with_class():
class A(object):
pass
@@ -30,11 +34,13 @@ if True:
def x():
pass
def test_code_fullsource():
code = _pytest._code.Code(x)
full = code.fullsource
assert 'test_code_fullsource()' in str(full)
def test_code_source():
code = _pytest._code.Code(x)
src = code.source()
@@ -42,6 +48,7 @@ def test_code_source():
pass"""
assert str(src) == expected
def test_frame_getsourcelineno_myself():
def func():
return sys._getframe(0)
@@ -50,6 +57,7 @@ def test_frame_getsourcelineno_myself():
source, lineno = f.code.fullsource, f.lineno
assert source[lineno].startswith(" return sys._getframe(0)")
def test_getstatement_empty_fullsource():
def func():
return sys._getframe(0)
@@ -62,6 +70,7 @@ def test_getstatement_empty_fullsource():
finally:
f.code.__class__.fullsource = prop
def test_code_from_func():
co = _pytest._code.Code(test_frame_getsourcelineno_myself)
assert co.firstlineno
@@ -92,6 +101,7 @@ def test_unicode_handling_syntax_error():
if sys.version_info[0] < 3:
unicode(excinfo)
def test_code_getargs():
def f1(x):
pass
@@ -141,8 +151,10 @@ class TestExceptionInfo(object):
def test_bad_getsource(self):
try:
if False: pass
else: assert False
if False:
pass
else:
assert False
except AssertionError:
exci = _pytest._code.ExceptionInfo()
assert exci.getrepr()
@@ -152,11 +164,33 @@ class TestTracebackEntry(object):
def test_getsource(self):
try:
if False: pass
else: assert False
if False:
pass
else:
assert False
except AssertionError:
exci = _pytest._code.ExceptionInfo()
entry = exci.traceback[0]
source = entry.getsource()
assert len(source) == 4
assert 'else: assert False' in source[3]
assert len(source) == 6
assert 'assert False' in source[5]
class TestReprFuncArgs(object):
def test_not_raise_exception_with_mixed_encoding(self):
from _pytest._code.code import ReprFuncArgs
tw = TWMock()
args = [
('unicode_string', u"São Paulo"),
('utf8_string', 'S\xc3\xa3o Paulo'),
]
r = ReprFuncArgs(args)
r.toterminal(tw)
if sys.version_info[0] >= 3:
assert tw.lines[0] == 'unicode_string = São Paulo, utf8_string = São Paulo'
else:
assert tw.lines[0] == 'unicode_string = São Paulo, utf8_string = São Paulo'

View File

@@ -12,9 +12,6 @@ from _pytest._code.code import (
ReprExceptionInfo,
ExceptionChainRepr)
queue = py.builtin._tryimport('queue', 'Queue')
failsonjython = pytest.mark.xfail("sys.platform.startswith('java')")
from test_source import astonly
try:
@@ -24,23 +21,32 @@ except ImportError:
else:
invalidate_import_caches = getattr(importlib, "invalidate_caches", None)
import pytest
queue = py.builtin._tryimport('queue', 'Queue')
failsonjython = pytest.mark.xfail("sys.platform.startswith('java')")
pytest_version_info = tuple(map(int, pytest.__version__.split(".")[:3]))
class TWMock(object):
WRITE = object()
def __init__(self):
self.lines = []
self.is_writing = False
def sep(self, sep, line=None):
self.lines.append((sep, line))
def write(self, msg, **kw):
self.lines.append((TWMock.WRITE, msg))
def line(self, line, **kw):
self.lines.append(line)
def markup(self, text, **kw):
return text
def get_write_msg(self, idx):
flag, msg = self.lines[idx]
assert flag == TWMock.WRITE
@@ -48,6 +54,7 @@ class TWMock(object):
fullwidth = 80
def test_excinfo_simple():
try:
raise ValueError
@@ -55,6 +62,7 @@ def test_excinfo_simple():
info = _pytest._code.ExceptionInfo()
assert info.type == ValueError
def test_excinfo_getstatement():
def g():
raise ValueError
@@ -72,25 +80,32 @@ def test_excinfo_getstatement():
l = list(excinfo.traceback)
foundlinenumbers = [x.lineno for x in l]
assert foundlinenumbers == linenumbers
#for x in info:
# for x in info:
# print "%s:%d %s" %(x.path.relto(root), x.lineno, x.statement)
#xxx
# xxx
# testchain for getentries test below
def f():
#
raise ValueError
#
def g():
#
__tracebackhide__ = True
f()
#
def h():
#
g()
#
class TestTraceback_f_g_h(object):
def setup_method(self, method):
try:
@@ -101,8 +116,8 @@ class TestTraceback_f_g_h(object):
def test_traceback_entries(self):
tb = self.excinfo.traceback
entries = list(tb)
assert len(tb) == 4 # maybe fragile test
assert len(entries) == 4 # maybe fragile test
assert len(tb) == 4 # maybe fragile test
assert len(entries) == 4 # maybe fragile test
names = ['f', 'g', 'h']
for entry in entries:
try:
@@ -113,7 +128,7 @@ class TestTraceback_f_g_h(object):
def test_traceback_entry_getsource(self):
tb = self.excinfo.traceback
s = str(tb[-1].getsource() )
s = str(tb[-1].getsource())
assert s.startswith("def f():")
assert s.endswith("raise ValueError")
@@ -129,10 +144,10 @@ class TestTraceback_f_g_h(object):
xyz()
""")
try:
exec (source.compile())
exec(source.compile())
except NameError:
tb = _pytest._code.ExceptionInfo().traceback
print (tb[-1].getsource())
print(tb[-1].getsource())
s = str(tb[-1].getsource())
assert s.startswith("def xyz():\n try:")
assert s.strip().endswith("except somenoname:")
@@ -143,7 +158,7 @@ class TestTraceback_f_g_h(object):
traceback = self.excinfo.traceback
newtraceback = traceback.cut(path=path, firstlineno=firstlineno)
assert len(newtraceback) == 1
newtraceback = traceback.cut(path=path, lineno=firstlineno+2)
newtraceback = traceback.cut(path=path, lineno=firstlineno + 2)
assert len(newtraceback) == 1
def test_traceback_cut_excludepath(self, testdir):
@@ -210,7 +225,7 @@ class TestTraceback_f_g_h(object):
def f(n):
if n == 0:
raise RuntimeError("hello")
f(n-1)
f(n - 1)
excinfo = pytest.raises(RuntimeError, f, 100)
monkeypatch.delattr(excinfo.traceback.__class__, "recursionindex")
@@ -238,7 +253,7 @@ class TestTraceback_f_g_h(object):
assert recindex is None
def test_traceback_messy_recursion(self):
#XXX: simplified locally testable version
# XXX: simplified locally testable version
decorator = pytest.importorskip('decorator').decorator
def log(f, *k, **kw):
@@ -294,44 +309,50 @@ class TestTraceback_f_g_h(object):
assert entry.lineno == co.firstlineno + 2
assert entry.frame.code.name == 'g'
def test_excinfo_exconly():
excinfo = pytest.raises(ValueError, h)
assert excinfo.exconly().startswith('ValueError')
excinfo = pytest.raises(ValueError,
"raise ValueError('hello\\nworld')")
"raise ValueError('hello\\nworld')")
msg = excinfo.exconly(tryshort=True)
assert msg.startswith('ValueError')
assert msg.endswith("world")
def test_excinfo_repr():
excinfo = pytest.raises(ValueError, h)
s = repr(excinfo)
assert s == "<ExceptionInfo ValueError tblen=4>"
def test_excinfo_str():
excinfo = pytest.raises(ValueError, h)
s = str(excinfo)
assert s.startswith(__file__[:-9]) # pyc file and $py.class
assert s.startswith(__file__[:-9]) # pyc file and $py.class
assert s.endswith("ValueError")
assert len(s.split(":")) >= 3 # on windows it's 4
assert len(s.split(":")) >= 3 # on windows it's 4
def test_excinfo_errisinstance():
excinfo = pytest.raises(ValueError, h)
assert excinfo.errisinstance(ValueError)
def test_excinfo_no_sourcecode():
try:
exec ("raise ValueError()")
exec("raise ValueError()")
except ValueError:
excinfo = _pytest._code.ExceptionInfo()
s = str(excinfo.traceback[-1])
if py.std.sys.version_info < (2,5):
if py.std.sys.version_info < (2, 5):
assert s == " File '<string>':1 in ?\n ???\n"
else:
assert s == " File '<string>':1 in <module>\n ???\n"
def test_excinfo_no_python_sourcecode(tmpdir):
#XXX: simplified locally testable version
# XXX: simplified locally testable version
tmpdir.join('test.txt').write("{{ h()}}:")
jinja2 = pytest.importorskip('jinja2')
@@ -339,10 +360,10 @@ def test_excinfo_no_python_sourcecode(tmpdir):
env = jinja2.Environment(loader=loader)
template = env.get_template('test.txt')
excinfo = pytest.raises(ValueError,
template.render, h=h)
template.render, h=h)
for item in excinfo.traceback:
print(item) #XXX: for some reason jinja.Template.render is printed in full
item.source # shouldnt fail
print(item) # XXX: for some reason jinja.Template.render is printed in full
item.source # shouldnt fail
if item.path.basename == 'test.txt':
assert str(item.source) == '{{ h()}}:'
@@ -358,6 +379,7 @@ def test_entrysource_Queue_example():
s = str(source).strip()
assert s.startswith("def get")
def test_codepath_Queue_example():
try:
queue.Queue().get(timeout=0.001)
@@ -369,11 +391,13 @@ def test_codepath_Queue_example():
assert path.basename.lower() == "queue.py"
assert path.check()
def test_match_succeeds():
with pytest.raises(ZeroDivisionError) as excinfo:
0 // 0
excinfo.match(r'.*zero.*')
def test_match_raises_error(testdir):
testdir.makepyfile("""
import pytest
@@ -388,6 +412,7 @@ def test_match_raises_error(testdir):
"*AssertionError*Pattern*[123]*not found*",
])
class TestFormattedExcinfo(object):
@pytest.fixture
@@ -406,7 +431,7 @@ class TestFormattedExcinfo(object):
def excinfo_from_exec(self, source):
source = _pytest._code.Source(source).strip()
try:
exec (source.compile())
exec(source.compile())
except KeyboardInterrupt:
raise
except:
@@ -442,12 +467,11 @@ class TestFormattedExcinfo(object):
'E AssertionError'
]
def test_repr_source_not_existing(self):
pr = FormattedExcinfo()
co = compile("raise ValueError()", "", "exec")
try:
exec (co)
exec(co)
except ValueError:
excinfo = _pytest._code.ExceptionInfo()
repr = pr.repr_excinfo(excinfo)
@@ -462,7 +486,7 @@ a = 1
raise ValueError()
""", "", "exec")
try:
exec (co)
exec(co)
except ValueError:
excinfo = _pytest._code.ExceptionInfo()
repr = pr.repr_excinfo(excinfo)
@@ -492,7 +516,7 @@ raise ValueError()
class FakeTracebackEntry(_pytest._code.Traceback.Entry):
def __init__(self, tb, excinfo=None):
self.lineno = 5+3
self.lineno = 5 + 3
@property
def frame(self):
@@ -528,14 +552,12 @@ raise ValueError()
if py.std.sys.version_info[0] >= 3:
assert repr.chain[0][0].reprentries[0].lines[0] == "> ???"
fail = py.error.ENOENT # noqa
repr = pr.repr_excinfo(excinfo)
assert repr.reprtraceback.reprentries[0].lines[0] == "> ???"
if py.std.sys.version_info[0] >= 3:
assert repr.chain[0][0].reprentries[0].lines[0] == "> ???"
def test_repr_local(self):
p = FormattedExcinfo(showlocals=True)
loc = {'y': 5, 'z': 7, 'x': 3, '@x': 2, '__builtins__': {}}
@@ -575,19 +597,19 @@ raise ValueError()
loc = repr_entry.reprfileloc
assert loc.path == mod.__file__
assert loc.lineno == 3
#assert loc.message == "ValueError: hello"
# assert loc.message == "ValueError: hello"
def test_repr_tracebackentry_lines2(self, importasmod):
mod = importasmod("""
def func1(m, x, y, z):
raise ValueError("hello\\nworld")
""")
excinfo = pytest.raises(ValueError, mod.func1, "m"*90, 5, 13, "z"*120)
excinfo = pytest.raises(ValueError, mod.func1, "m" * 90, 5, 13, "z" * 120)
excinfo.traceback = excinfo.traceback.filter()
entry = excinfo.traceback[-1]
p = FormattedExcinfo(funcargs=True)
reprfuncargs = p.repr_args(entry)
assert reprfuncargs.args[0] == ('m', repr("m"*90))
assert reprfuncargs.args[0] == ('m', repr("m" * 90))
assert reprfuncargs.args[1] == ('x', '5')
assert reprfuncargs.args[2] == ('y', '13')
assert reprfuncargs.args[3] == ('z', repr("z" * 120))
@@ -934,10 +956,10 @@ raise ValueError()
@pytest.mark.parametrize('reproptions', [
{'style': style, 'showlocals': showlocals,
'funcargs': funcargs, 'tbfilter': tbfilter
} for style in ("long", "short", "no")
for showlocals in (True, False)
for tbfilter in (True, False)
for funcargs in (True, False)])
} for style in ("long", "short", "no")
for showlocals in (True, False)
for tbfilter in (True, False)
for funcargs in (True, False)])
def test_format_excinfo(self, importasmod, reproptions):
mod = importasmod("""
def g(x):
@@ -969,7 +991,8 @@ raise ValueError()
r = excinfo.getrepr(style="long")
tw = TWMock()
r.toterminal(tw)
for line in tw.lines: print (line)
for line in tw.lines:
print(line)
assert tw.lines[0] == ""
assert tw.lines[1] == " def f():"
assert tw.lines[2] == "> g()"
@@ -1016,19 +1039,20 @@ raise ValueError()
r = excinfo.getrepr(style="long")
tw = TWMock()
r.toterminal(tw)
for line in tw.lines: print (line)
assert tw.lines[0] == ""
assert tw.lines[1] == " def f():"
assert tw.lines[2] == " try:"
assert tw.lines[3] == "> g()"
assert tw.lines[4] == ""
for line in tw.lines:
print(line)
assert tw.lines[0] == ""
assert tw.lines[1] == " def f():"
assert tw.lines[2] == " try:"
assert tw.lines[3] == "> g()"
assert tw.lines[4] == ""
line = tw.get_write_msg(5)
assert line.endswith('mod.py')
assert tw.lines[6] == ':6: '
assert tw.lines[7] == ("_ ", None)
assert tw.lines[8] == ""
assert tw.lines[9] == " def g():"
assert tw.lines[10] == "> raise ValueError()"
assert tw.lines[6] == ':6: '
assert tw.lines[7] == ("_ ", None)
assert tw.lines[8] == ""
assert tw.lines[9] == " def g():"
assert tw.lines[10] == "> raise ValueError()"
assert tw.lines[11] == "E ValueError"
assert tw.lines[12] == ""
line = tw.get_write_msg(13)
@@ -1071,6 +1095,36 @@ raise ValueError()
assert line.endswith('mod.py')
assert tw.lines[47] == ":15: AttributeError"
@pytest.mark.skipif("sys.version_info[0] < 3")
def test_exc_repr_with_raise_from_none_chain_suppression(self, importasmod):
mod = importasmod("""
def f():
try:
g()
except Exception:
raise AttributeError() from None
def g():
raise ValueError()
""")
excinfo = pytest.raises(AttributeError, mod.f)
r = excinfo.getrepr(style="long")
tw = TWMock()
r.toterminal(tw)
for line in tw.lines:
print(line)
assert tw.lines[0] == ""
assert tw.lines[1] == " def f():"
assert tw.lines[2] == " try:"
assert tw.lines[3] == " g()"
assert tw.lines[4] == " except Exception:"
assert tw.lines[5] == "> raise AttributeError() from None"
assert tw.lines[6] == "E AttributeError"
assert tw.lines[7] == ""
line = tw.get_write_msg(8)
assert line.endswith('mod.py')
assert tw.lines[9] == ":6: AttributeError"
assert len(tw.lines) == 10
@pytest.mark.skipif("sys.version_info[0] < 3")
@pytest.mark.parametrize('reason, description', [
('cause', 'The above exception was the direct cause of the following exception:'),

View File

@@ -17,6 +17,7 @@ else:
failsonjython = pytest.mark.xfail("sys.platform.startswith('java')")
def test_source_str_function():
x = Source("3")
assert str(x) == "3"
@@ -34,6 +35,7 @@ def test_source_str_function():
""", rstrip=True)
assert str(x) == "\n3"
def test_unicode():
try:
unicode
@@ -45,10 +47,12 @@ def test_unicode():
val = eval(co)
assert isinstance(val, unicode)
def test_source_from_function():
source = _pytest._code.Source(test_source_str_function)
assert str(source).startswith('def test_source_str_function():')
def test_source_from_method():
class TestClass(object):
def test_method(self):
@@ -57,11 +61,13 @@ def test_source_from_method():
assert source.lines == ["def test_method(self):",
" pass"]
def test_source_from_lines():
lines = ["a \n", "b\n", "c"]
source = _pytest._code.Source(lines)
assert source.lines == ['a ', 'b', 'c']
def test_source_from_inner_function():
def f():
pass
@@ -70,6 +76,7 @@ def test_source_from_inner_function():
source = _pytest._code.Source(f)
assert str(source).startswith('def f():')
def test_source_putaround_simple():
source = Source("raise ValueError")
source = source.putaround(
@@ -78,7 +85,7 @@ def test_source_putaround_simple():
x = 42
else:
x = 23""")
assert str(source)=="""\
assert str(source) == """\
try:
raise ValueError
except ValueError:
@@ -86,6 +93,7 @@ except ValueError:
else:
x = 23"""
def test_source_putaround():
source = Source()
source = source.putaround("""
@@ -94,24 +102,28 @@ def test_source_putaround():
""")
assert str(source).strip() == "if 1:\n x=1"
def test_source_strips():
source = Source("")
assert source == Source()
assert str(source) == ''
assert source.strip() == source
def test_source_strip_multiline():
source = Source()
source.lines = ["", " hello", " "]
source2 = source.strip()
assert source2.lines == [" hello"]
def test_syntaxerror_rerepresentation():
ex = pytest.raises(SyntaxError, _pytest._code.compile, 'xyz xyz')
assert ex.value.lineno == 1
assert ex.value.offset in (4,7) # XXX pypy/jython versus cpython?
assert ex.value.offset in (4, 7) # XXX pypy/jython versus cpython?
assert ex.value.text.strip(), 'x x'
def test_isparseable():
assert Source("hello").isparseable()
assert Source("if 1:\n pass").isparseable()
@@ -120,6 +132,7 @@ def test_isparseable():
assert not Source(" \nif 1:\npass").isparseable()
assert not Source(chr(0)).isparseable()
class TestAccesses(object):
source = Source("""\
def f(x):
@@ -127,6 +140,7 @@ class TestAccesses(object):
def g(x):
pass
""")
def test_getrange(self):
x = self.source[0:2]
assert x.isparseable()
@@ -144,6 +158,7 @@ class TestAccesses(object):
l = [x for x in self.source]
assert len(l) == 4
class TestSourceParsingAndCompiling(object):
source = Source("""\
def f(x):
@@ -155,12 +170,12 @@ class TestSourceParsingAndCompiling(object):
def test_compile(self):
co = _pytest._code.compile("x=3")
d = {}
exec (co, d)
exec(co, d)
assert d['x'] == 3
def test_compile_and_getsource_simple(self):
co = _pytest._code.compile("x=3")
exec (co)
exec(co)
source = _pytest._code.Source(co)
assert str(source) == "x=3"
@@ -181,16 +196,16 @@ class TestSourceParsingAndCompiling(object):
assert 'ValueError' in source2
def test_getstatement(self):
#print str(self.source)
# print str(self.source)
ass = str(self.source[1:])
for i in range(1, 4):
#print "trying start in line %r" % self.source[i]
# print "trying start in line %r" % self.source[i]
s = self.source.getstatement(i)
#x = s.deindent()
assert str(s) == ass
def test_getstatementrange_triple_quoted(self):
#print str(self.source)
# print str(self.source)
source = Source("""hello('''
''')""")
s = source.getstatement(0)
@@ -211,12 +226,12 @@ class TestSourceParsingAndCompiling(object):
""")
assert len(source) == 7
# check all lineno's that could occur in a traceback
#assert source.getstatementrange(0) == (0, 7)
#assert source.getstatementrange(1) == (1, 5)
# assert source.getstatementrange(0) == (0, 7)
# assert source.getstatementrange(1) == (1, 5)
assert source.getstatementrange(2) == (2, 3)
assert source.getstatementrange(3) == (3, 4)
assert source.getstatementrange(4) == (4, 5)
#assert source.getstatementrange(5) == (0, 7)
# assert source.getstatementrange(5) == (0, 7)
assert source.getstatementrange(6) == (6, 7)
def test_getstatementrange_bug(self):
@@ -262,7 +277,7 @@ class TestSourceParsingAndCompiling(object):
def test_getstatementrange_out_of_bounds_py3(self):
source = Source("if xxx:\n from .collections import something")
r = source.getstatementrange(1)
assert r == (1,2)
assert r == (1, 2)
def test_getstatementrange_with_syntaxerror_issue7(self):
source = Source(":")
@@ -283,7 +298,7 @@ class TestSourceParsingAndCompiling(object):
excinfo = pytest.raises(AssertionError, "f(6)")
frame = excinfo.traceback[-1].frame
stmt = frame.code.fullsource.getstatement(frame.lineno)
#print "block", str(block)
# print "block", str(block)
assert str(stmt).strip().startswith('assert')
@pytest.mark.parametrize('name', ['', None, 'my'])
@@ -291,9 +306,9 @@ class TestSourceParsingAndCompiling(object):
def check(comp, name):
co = comp(self.source, name)
if not name:
expected = "codegen %s:%d>" %(mypath, mylineno+2+2)
expected = "codegen %s:%d>" % (mypath, mylineno + 2 + 2)
else:
expected = "codegen %r %s:%d>" % (name, mypath, mylineno+2+2)
expected = "codegen %r %s:%d>" % (name, mypath, mylineno + 2 + 2)
fn = co.co_filename
assert fn.endswith(expected)
@@ -307,6 +322,7 @@ class TestSourceParsingAndCompiling(object):
def test_offsetless_synerr(self):
pytest.raises(SyntaxError, _pytest._code.compile, "lambda a,a: 0", mode='eval')
def test_getstartingblock_singleline():
class A(object):
def __init__(self, *args):
@@ -318,19 +334,6 @@ def test_getstartingblock_singleline():
l = [i for i in x.source.lines if i.strip()]
assert len(l) == 1
def test_getstartingblock_multiline():
class A(object):
def __init__(self, *args):
frame = sys._getframe(1)
self.source = _pytest._code.Frame(frame).statement
x = A('x',
'y' \
,
'z')
l = [i for i in x.source.lines if i.strip()]
assert len(l) == 4
def test_getline_finally():
def c(): pass
@@ -345,6 +348,7 @@ def test_getline_finally():
source = excinfo.traceback[-1].statement
assert str(source).strip() == 'c(1)'
def test_getfuncsource_dynamic():
source = """
def f():
@@ -386,6 +390,7 @@ def test_deindent():
lines = deindent(source.splitlines())
assert lines == ['', 'def f():', ' def g():', ' pass', ' ']
@pytest.mark.xfail("sys.version_info[:3] < (2,7,0)")
def test_source_of_class_at_eof_without_newline(tmpdir):
# this test fails because the implicit inspect.getsource(A) below
@@ -400,10 +405,12 @@ def test_source_of_class_at_eof_without_newline(tmpdir):
s2 = _pytest._code.Source(tmpdir.join("a.py").pyimport().A)
assert str(source).strip() == str(s2).strip()
if True:
def x():
pass
def test_getsource_fallback():
from _pytest._code.source import getsource
expected = """def x():
@@ -411,6 +418,7 @@ def test_getsource_fallback():
src = getsource(x)
assert src == expected
def test_idem_compile_and_getsource():
from _pytest._code.source import getsource
expected = "def x(): pass"
@@ -418,12 +426,14 @@ def test_idem_compile_and_getsource():
src = getsource(co)
assert src == expected
def test_findsource_fallback():
from _pytest._code.source import findsource
src, lineno = findsource(x)
assert 'test_findsource_simple' in str(src)
assert src[lineno] == ' def x():'
def test_findsource():
from _pytest._code.source import findsource
co = _pytest._code.compile("""if 1:
@@ -450,7 +460,7 @@ def test_getfslineno():
fspath, lineno = getfslineno(f)
assert fspath.basename == "test_source.py"
assert lineno == _pytest._code.getrawcode(f).co_firstlineno - 1 # see findsource
assert lineno == _pytest._code.getrawcode(f).co_firstlineno - 1 # see findsource
class A(object):
pass
@@ -462,15 +472,18 @@ def test_getfslineno():
assert lineno == A_lineno
assert getfslineno(3) == ("", -1)
class B(object):
pass
B.__name__ = "B2"
assert getfslineno(B)[1] == -1
def test_code_of_object_instance_with_call():
class A(object):
pass
pytest.raises(TypeError, lambda: _pytest._code.Source(A()))
class WithCall(object):
def __call__(self):
pass
@@ -490,10 +503,12 @@ def getstatement(lineno, source):
ast, start, end = getstatementrange_ast(lineno, source)
return source[start:end]
def test_oneline():
source = getstatement(0, "raise ValueError")
assert str(source) == "raise ValueError"
def test_comment_and_no_newline_at_end():
from _pytest._code.source import getstatementrange_ast
source = Source(['def test_basic_complex():',
@@ -502,10 +517,12 @@ def test_comment_and_no_newline_at_end():
ast, start, end = getstatementrange_ast(1, source)
assert end == 2
def test_oneline_and_comment():
source = getstatement(0, "raise ValueError\n#hello")
assert str(source) == "raise ValueError"
@pytest.mark.xfail(hasattr(sys, "pypy_version_info"),
reason='does not work on pypy')
def test_comments():
@@ -521,29 +538,33 @@ def test_comments():
comment 4
"""
'''
for line in range(2,6):
for line in range(2, 6):
assert str(getstatement(line, source)) == ' x = 1'
for line in range(6,10):
for line in range(6, 10):
assert str(getstatement(line, source)) == ' assert False'
assert str(getstatement(10, source)) == '"""'
def test_comment_in_statement():
source = '''test(foo=1,
# comment 1
bar=2)
'''
for line in range(1,3):
for line in range(1, 3):
assert str(getstatement(line, source)) == \
'test(foo=1,\n # comment 1\n bar=2)'
'test(foo=1,\n # comment 1\n bar=2)'
def test_single_line_else():
source = getstatement(1, "if False: 2\nelse: 3")
assert str(source) == "else: 3"
def test_single_line_finally():
source = getstatement(1, "try: 1\nfinally: 3")
assert str(source) == "finally: 3"
def test_issue55():
source = ('def round_trip(dinp):\n assert 1 == dinp\n'
'def test_rt():\n round_trip("""\n""")\n')
@@ -560,6 +581,7 @@ x = 3
""")
assert str(source) == "raise ValueError(\n 23\n)"
class TestTry(object):
pytestmark = astonly
source = """\
@@ -587,6 +609,7 @@ else:
source = getstatement(5, self.source)
assert str(source) == " raise KeyError()"
class TestTryFinally(object):
source = """\
try:
@@ -604,7 +627,6 @@ finally:
assert str(source) == " raise IndexError(1)"
class TestIf(object):
pytestmark = astonly
source = """\
@@ -632,6 +654,7 @@ else:
source = getstatement(5, self.source)
assert str(source) == " y = 7"
def test_semicolon():
s = """\
hello ; pytest.skip()
@@ -639,6 +662,7 @@ hello ; pytest.skip()
source = getstatement(0, s)
assert str(source) == s.strip()
def test_def_online():
s = """\
def func(): raise ValueError(42)
@@ -649,6 +673,7 @@ def something():
source = getstatement(0, s)
assert str(source) == "def func(): raise ValueError(42)"
def XXX_test_expression_multiline():
source = """\
something

View File

@@ -0,0 +1,26 @@
# flake8: noqa
import sys
import _pytest._code
def test_getstartingblock_multiline():
"""
This test was originally found in test_source.py, but it depends on the weird
formatting of the ``x = A`` construct seen here and our autopep8 tool can only exclude entire
files (it does not support excluding lines/blocks using the traditional #noqa comment yet,
see hhatto/autopep8#307). It was considered better to just move this single test to its own
file and exclude it from autopep8 than try to complicate things.
"""
class A(object):
def __init__(self, *args):
frame = sys._getframe(1)
self.source = _pytest._code.Frame(frame).statement
x = A('x',
'y'
,
'z')
l = [i for i in x.source.lines if i.strip()]
assert len(l) == 4

View File

@@ -9,12 +9,16 @@ def test_yield_tests_deprecation(testdir):
def test_gen():
yield "m1", func1, 15, 3*5
yield "m2", func1, 42, 6*7
def test_gen2():
for k in range(10):
yield func1, 1, 1
""")
result = testdir.runpytest('-ra')
result.stdout.fnmatch_lines([
'*yield tests are deprecated, and scheduled to be removed in pytest 4.0*',
'*2 passed*',
])
assert result.stdout.str().count('yield tests are deprecated') == 2
def test_funcarg_prefix_deprecation(testdir):
@@ -74,4 +78,7 @@ def test_resultlog_is_deprecated(testdir):
pass
''')
result = testdir.runpytest('--result-log=%s' % testdir.tmpdir.join('result.log'))
result.stdout.fnmatch_lines(['*--result-log is deprecated and scheduled for removal in pytest 4.0*'])
result.stdout.fnmatch_lines([
'*--result-log is deprecated and scheduled for removal in pytest 4.0*',
'*See https://docs.pytest.org/*/usage.html#creating-resultlog-format-files for more information*',
])

View File

@@ -10,4 +10,3 @@ if __name__ == '__main__':
hidden.extend(['--hidden-import', x])
args = ['pyinstaller', '--noconfirm'] + hidden + ['runtests_script.py']
subprocess.check_call(' '.join(args), shell=True)

View File

@@ -6,4 +6,4 @@ py.test main().
if __name__ == '__main__':
import sys
import pytest
sys.exit(pytest.main())
sys.exit(pytest.main())

View File

@@ -2,5 +2,6 @@
def test_upper():
assert 'foo'.upper() == 'FOO'
def test_lower():
assert 'FOO'.lower() == 'foo'
assert 'FOO'.lower() == 'foo'

View File

@@ -9,4 +9,4 @@ if __name__ == '__main__':
executable = os.path.join(os.getcwd(), 'dist', 'runtests_script', 'runtests_script')
if sys.platform.startswith('win'):
executable += '.exe'
sys.exit(os.system('%s tests' % executable))
sys.exit(os.system('%s tests' % executable))

View File

@@ -1,4 +1,5 @@
# encoding: utf-8
import operator
import sys
import pytest
import doctest
@@ -29,12 +30,21 @@ class TestApprox(object):
if sys.version_info[:2] == (2, 6):
tol1, tol2, infr = '???', '???', '???'
assert repr(approx(1.0)) == '1.0 {pm} {tol1}'.format(pm=plus_minus, tol1=tol1)
assert repr(approx([1.0, 2.0])) == '1.0 {pm} {tol1}, 2.0 {pm} {tol2}'.format(pm=plus_minus, tol1=tol1, tol2=tol2)
assert repr(approx([1.0, 2.0])) == 'approx([1.0 {pm} {tol1}, 2.0 {pm} {tol2}])'.format(
pm=plus_minus, tol1=tol1, tol2=tol2)
assert repr(approx((1.0, 2.0))) == 'approx((1.0 {pm} {tol1}, 2.0 {pm} {tol2}))'.format(
pm=plus_minus, tol1=tol1, tol2=tol2)
assert repr(approx(inf)) == 'inf'
assert repr(approx(1.0, rel=nan)) == '1.0 {pm} ???'.format(pm=plus_minus)
assert repr(approx(1.0, rel=inf)) == '1.0 {pm} {infr}'.format(pm=plus_minus, infr=infr)
assert repr(approx(1.0j, rel=inf)) == '1j'
# Dictionaries aren't ordered, so we need to check both orders.
assert repr(approx({'a': 1.0, 'b': 2.0})) in (
"approx({{'a': 1.0 {pm} {tol1}, 'b': 2.0 {pm} {tol2}}})".format(pm=plus_minus, tol1=tol1, tol2=tol2),
"approx({{'b': 2.0 {pm} {tol2}, 'a': 1.0 {pm} {tol1}}})".format(pm=plus_minus, tol1=tol1, tol2=tol2),
)
def test_operator_overloading(self):
assert 1 == approx(1, rel=1e-6, abs=1e-12)
assert not (1 != approx(1, rel=1e-6, abs=1e-12))
@@ -43,30 +53,30 @@ class TestApprox(object):
def test_exactly_equal(self):
examples = [
(2.0, 2.0),
(0.1e200, 0.1e200),
(1.123e-300, 1.123e-300),
(12345, 12345.0),
(0.0, -0.0),
(345678, 345678),
(Decimal('1.0001'), Decimal('1.0001')),
(Fraction(1, 3), Fraction(-1, -3)),
(2.0, 2.0),
(0.1e200, 0.1e200),
(1.123e-300, 1.123e-300),
(12345, 12345.0),
(0.0, -0.0),
(345678, 345678),
(Decimal('1.0001'), Decimal('1.0001')),
(Fraction(1, 3), Fraction(-1, -3)),
]
for a, x in examples:
assert a == approx(x)
def test_opposite_sign(self):
examples = [
(eq, 1e-100, -1e-100),
(ne, 1e100, -1e100),
(eq, 1e-100, -1e-100),
(ne, 1e100, -1e100),
]
for op, a, x in examples:
assert op(a, approx(x))
def test_zero_tolerance(self):
within_1e10 = [
(1.1e-100, 1e-100),
(-1.1e-100, -1e-100),
(1.1e-100, 1e-100),
(-1.1e-100, -1e-100),
]
for a, x in within_1e10:
assert x == approx(x, rel=0.0, abs=0.0)
@@ -79,11 +89,11 @@ class TestApprox(object):
def test_negative_tolerance(self):
# Negative tolerances are not allowed.
illegal_kwargs = [
dict(rel=-1e100),
dict(abs=-1e100),
dict(rel=1e100, abs=-1e100),
dict(rel=-1e100, abs=1e100),
dict(rel=-1e100, abs=-1e100),
dict(rel=-1e100),
dict(abs=-1e100),
dict(rel=1e100, abs=-1e100),
dict(rel=-1e100, abs=1e100),
dict(rel=-1e100, abs=-1e100),
]
for kwargs in illegal_kwargs:
with pytest.raises(ValueError):
@@ -92,10 +102,10 @@ class TestApprox(object):
def test_inf_tolerance(self):
# Everything should be equal if the tolerance is infinite.
large_diffs = [
(1, 1000),
(1e-50, 1e50),
(-1.0, -1e300),
(0.0, 10),
(1, 1000),
(1e-50, 1e50),
(-1.0, -1e300),
(0.0, 10),
]
for a, x in large_diffs:
assert a != approx(x, rel=0.0, abs=0.0)
@@ -107,8 +117,8 @@ class TestApprox(object):
# If the relative tolerance is zero but the expected value is infinite,
# the actual tolerance is a NaN, which should be an error.
illegal_kwargs = [
dict(rel=inf, abs=0.0),
dict(rel=inf, abs=inf),
dict(rel=inf, abs=0.0),
dict(rel=inf, abs=inf),
]
for kwargs in illegal_kwargs:
with pytest.raises(ValueError):
@@ -116,9 +126,9 @@ class TestApprox(object):
def test_nan_tolerance(self):
illegal_kwargs = [
dict(rel=nan),
dict(abs=nan),
dict(rel=nan, abs=nan),
dict(rel=nan),
dict(abs=nan),
dict(rel=nan, abs=nan),
]
for kwargs in illegal_kwargs:
with pytest.raises(ValueError):
@@ -135,15 +145,15 @@ class TestApprox(object):
# None of the other tests (except the doctests) should be affected by
# the choice of defaults.
examples = [
# Relative tolerance used.
(eq, 1e100 + 1e94, 1e100),
(ne, 1e100 + 2e94, 1e100),
(eq, 1e0 + 1e-6, 1e0),
(ne, 1e0 + 2e-6, 1e0),
# Absolute tolerance used.
(eq, 1e-100, + 1e-106),
(eq, 1e-100, + 2e-106),
(eq, 1e-100, 0),
# Relative tolerance used.
(eq, 1e100 + 1e94, 1e100),
(ne, 1e100 + 2e94, 1e100),
(eq, 1e0 + 1e-6, 1e0),
(ne, 1e0 + 2e-6, 1e0),
# Absolute tolerance used.
(eq, 1e-100, + 1e-106),
(eq, 1e-100, + 2e-106),
(eq, 1e-100, 0),
]
for op, a, x in examples:
assert op(a, approx(x))
@@ -166,9 +176,9 @@ class TestApprox(object):
def test_relative_tolerance(self):
within_1e8_rel = [
(1e8 + 1e0, 1e8),
(1e0 + 1e-8, 1e0),
(1e-8 + 1e-16, 1e-8),
(1e8 + 1e0, 1e8),
(1e0 + 1e-8, 1e0),
(1e-8 + 1e-16, 1e-8),
]
for a, x in within_1e8_rel:
assert a == approx(x, rel=5e-8, abs=0.0)
@@ -176,9 +186,9 @@ class TestApprox(object):
def test_absolute_tolerance(self):
within_1e8_abs = [
(1e8 + 9e-9, 1e8),
(1e0 + 9e-9, 1e0),
(1e-8 + 9e-9, 1e-8),
(1e8 + 9e-9, 1e8),
(1e0 + 9e-9, 1e0),
(1e-8 + 9e-9, 1e-8),
]
for a, x in within_1e8_abs:
assert a == approx(x, rel=0, abs=5e-8)
@@ -186,106 +196,170 @@ class TestApprox(object):
def test_expecting_zero(self):
examples = [
(ne, 1e-6, 0.0),
(ne, -1e-6, 0.0),
(eq, 1e-12, 0.0),
(eq, -1e-12, 0.0),
(ne, 2e-12, 0.0),
(ne, -2e-12, 0.0),
(ne, inf, 0.0),
(ne, nan, 0.0),
]
(ne, 1e-6, 0.0),
(ne, -1e-6, 0.0),
(eq, 1e-12, 0.0),
(eq, -1e-12, 0.0),
(ne, 2e-12, 0.0),
(ne, -2e-12, 0.0),
(ne, inf, 0.0),
(ne, nan, 0.0),
]
for op, a, x in examples:
assert op(a, approx(x, rel=0.0, abs=1e-12))
assert op(a, approx(x, rel=1e-6, abs=1e-12))
def test_expecting_inf(self):
examples = [
(eq, inf, inf),
(eq, -inf, -inf),
(ne, inf, -inf),
(ne, 0.0, inf),
(ne, nan, inf),
(eq, inf, inf),
(eq, -inf, -inf),
(ne, inf, -inf),
(ne, 0.0, inf),
(ne, nan, inf),
]
for op, a, x in examples:
assert op(a, approx(x))
def test_expecting_nan(self):
examples = [
(nan, nan),
(-nan, -nan),
(nan, -nan),
(0.0, nan),
(inf, nan),
(eq, nan, nan),
(eq, -nan, -nan),
(eq, nan, -nan),
(ne, 0.0, nan),
(ne, inf, nan),
]
for a, x in examples:
# If there is a relative tolerance and the expected value is NaN,
# the actual tolerance is a NaN, which should be an error.
with pytest.raises(ValueError):
a != approx(x, rel=inf)
for op, a, x in examples:
# Nothing is equal to NaN by default.
assert a != approx(x)
# You can make comparisons against NaN by not specifying a relative
# tolerance, so only an absolute tolerance is calculated.
assert a != approx(x, abs=inf)
def test_expecting_sequence(self):
within_1e8 = [
(1e8 + 1e0, 1e8),
(1e0 + 1e-8, 1e0),
(1e-8 + 1e-16, 1e-8),
]
actual, expected = zip(*within_1e8)
assert actual == approx(expected, rel=5e-8, abs=0.0)
def test_expecting_sequence_wrong_len(self):
assert [1, 2] != approx([1])
assert [1, 2] != approx([1,2,3])
def test_complex(self):
within_1e6 = [
( 1.000001 + 1.0j, 1.0 + 1.0j),
(1.0 + 1.000001j, 1.0 + 1.0j),
(-1.000001 + 1.0j, -1.0 + 1.0j),
(1.0 - 1.000001j, 1.0 - 1.0j),
]
for a, x in within_1e6:
assert a == approx(x, rel=5e-6, abs=0)
assert a != approx(x, rel=5e-7, abs=0)
# If ``nan_ok=True``, then NaN is equal to NaN.
assert op(a, approx(x, nan_ok=True))
def test_int(self):
within_1e6 = [
(1000001, 1000000),
(-1000001, -1000000),
(1000001, 1000000),
(-1000001, -1000000),
]
for a, x in within_1e6:
assert a == approx(x, rel=5e-6, abs=0)
assert a != approx(x, rel=5e-7, abs=0)
assert approx(x, rel=5e-6, abs=0) == a
assert approx(x, rel=5e-7, abs=0) != a
def test_decimal(self):
within_1e6 = [
(Decimal('1.000001'), Decimal('1.0')),
(Decimal('-1.000001'), Decimal('-1.0')),
(Decimal('1.000001'), Decimal('1.0')),
(Decimal('-1.000001'), Decimal('-1.0')),
]
for a, x in within_1e6:
assert a == approx(x, rel=Decimal('5e-6'), abs=0)
assert a != approx(x, rel=Decimal('5e-7'), abs=0)
assert approx(x, rel=Decimal('5e-6'), abs=0) == a
assert approx(x, rel=Decimal('5e-7'), abs=0) != a
def test_fraction(self):
within_1e6 = [
(1 + Fraction(1, 1000000), Fraction(1)),
(-1 - Fraction(-1, 1000000), Fraction(-1)),
(1 + Fraction(1, 1000000), Fraction(1)),
(-1 - Fraction(-1, 1000000), Fraction(-1)),
]
for a, x in within_1e6:
assert a == approx(x, rel=5e-6, abs=0)
assert a != approx(x, rel=5e-7, abs=0)
assert approx(x, rel=5e-6, abs=0) == a
assert approx(x, rel=5e-7, abs=0) != a
def test_complex(self):
within_1e6 = [
(1.000001 + 1.0j, 1.0 + 1.0j),
(1.0 + 1.000001j, 1.0 + 1.0j),
(-1.000001 + 1.0j, -1.0 + 1.0j),
(1.0 - 1.000001j, 1.0 - 1.0j),
]
for a, x in within_1e6:
assert a == approx(x, rel=5e-6, abs=0)
assert a != approx(x, rel=5e-7, abs=0)
assert approx(x, rel=5e-6, abs=0) == a
assert approx(x, rel=5e-7, abs=0) != a
def test_list(self):
actual = [1 + 1e-7, 2 + 1e-8]
expected = [1, 2]
# Return false if any element is outside the tolerance.
assert actual == approx(expected, rel=5e-7, abs=0)
assert actual != approx(expected, rel=5e-8, abs=0)
assert approx(expected, rel=5e-7, abs=0) == actual
assert approx(expected, rel=5e-8, abs=0) != actual
def test_list_wrong_len(self):
assert [1, 2] != approx([1])
assert [1, 2] != approx([1, 2, 3])
def test_tuple(self):
actual = (1 + 1e-7, 2 + 1e-8)
expected = (1, 2)
# Return false if any element is outside the tolerance.
assert actual == approx(expected, rel=5e-7, abs=0)
assert actual != approx(expected, rel=5e-8, abs=0)
assert approx(expected, rel=5e-7, abs=0) == actual
assert approx(expected, rel=5e-8, abs=0) != actual
def test_tuple_wrong_len(self):
assert (1, 2) != approx((1,))
assert (1, 2) != approx((1, 2, 3))
def test_dict(self):
actual = {'a': 1 + 1e-7, 'b': 2 + 1e-8}
# Dictionaries became ordered in python3.6, so switch up the order here
# to make sure it doesn't matter.
expected = {'b': 2, 'a': 1}
# Return false if any element is outside the tolerance.
assert actual == approx(expected, rel=5e-7, abs=0)
assert actual != approx(expected, rel=5e-8, abs=0)
assert approx(expected, rel=5e-7, abs=0) == actual
assert approx(expected, rel=5e-8, abs=0) != actual
def test_dict_wrong_len(self):
assert {'a': 1, 'b': 2} != approx({'a': 1})
assert {'a': 1, 'b': 2} != approx({'a': 1, 'c': 2})
assert {'a': 1, 'b': 2} != approx({'a': 1, 'b': 2, 'c': 3})
def test_numpy_array(self):
np = pytest.importorskip('numpy')
actual = np.array([1 + 1e-7, 2 + 1e-8])
expected = np.array([1, 2])
# Return false if any element is outside the tolerance.
assert actual == approx(expected, rel=5e-7, abs=0)
assert actual != approx(expected, rel=5e-8, abs=0)
assert approx(expected, rel=5e-7, abs=0) == expected
assert approx(expected, rel=5e-8, abs=0) != actual
# Should be able to compare lists with numpy arrays.
assert list(actual) == approx(expected, rel=5e-7, abs=0)
assert list(actual) != approx(expected, rel=5e-8, abs=0)
assert actual == approx(list(expected), rel=5e-7, abs=0)
assert actual != approx(list(expected), rel=5e-8, abs=0)
def test_numpy_array_wrong_shape(self):
np = pytest.importorskip('numpy')
a12 = np.array([[1, 2]])
a21 = np.array([[1], [2]])
assert a12 != approx(a21)
assert a21 != approx(a12)
def test_doctests(self):
parser = doctest.DocTestParser()
test = parser.get_doctest(
approx.__doc__,
{'approx': approx},
approx.__name__,
None, None,
approx.__doc__,
{'approx': approx},
approx.__name__,
None, None,
)
runner = MyDocTestRunner()
runner.run(test)
@@ -310,3 +384,15 @@ class TestApprox(object):
'=* 1 failed in *=',
])
@pytest.mark.parametrize('op', [
pytest.param(operator.le, id='<='),
pytest.param(operator.lt, id='<'),
pytest.param(operator.ge, id='>='),
pytest.param(operator.gt, id='>'),
])
def test_comparison_operator_type_error(self, op):
"""
pytest.approx should raise TypeError for operators other than == and != (#2003).
"""
with pytest.raises(TypeError):
op(1, approx(1, rel=1e-6, abs=1e-12))

View File

@@ -12,6 +12,9 @@ from _pytest.main import (
)
ignore_parametrized_marks = pytest.mark.filterwarnings('ignore:Applying marks directly to parameters')
class TestModule(object):
def test_failing_import(self, testdir):
modcol = testdir.getmodulecol("import alksdjalskdjalkjals")
@@ -143,6 +146,36 @@ class TestClass(object):
"*collected 0*",
])
def test_static_method(self, testdir):
"""Support for collecting staticmethod tests (#2528, #2699)"""
testdir.getmodulecol("""
import pytest
class Test(object):
@staticmethod
def test_something():
pass
@pytest.fixture
def fix(self):
return 1
@staticmethod
def test_fix(fix):
assert fix == 1
""")
result = testdir.runpytest()
if sys.version_info < (2, 7):
# in 2.6, the code to handle static methods doesn't work
result.stdout.fnmatch_lines([
"*collected 0 items*",
"*cannot collect static method*",
])
else:
result.stdout.fnmatch_lines([
"*collected 2 items*",
"*2 passed in*",
])
def test_setup_teardown_class_as_classmethod(self, testdir):
testdir.makepyfile(test_mod1="""
class TestClassMethod(object):
@@ -419,10 +452,10 @@ class TestFunction(object):
pass
f1 = pytest.Function(name="name", parent=session, config=config,
args=(1,), callobj=func1)
args=(1,), callobj=func1)
assert f1 == f1
f2 = pytest.Function(name="name",config=config,
callobj=func2, parent=session)
f2 = pytest.Function(name="name", config=config,
callobj=func2, parent=session)
assert f1 != f2
def test_issue197_parametrize_emptyset(self, testdir):
@@ -476,7 +509,6 @@ class TestFunction(object):
rec = testdir.inline_run()
rec.assertoutcome(passed=2)
def test_parametrize_with_non_hashable_values_indirect(self, testdir):
"""Test parametrization with non-hashable values with indirect parametrization."""
testdir.makepyfile("""
@@ -504,7 +536,6 @@ class TestFunction(object):
rec = testdir.inline_run()
rec.assertoutcome(passed=2)
def test_parametrize_overrides_fixture(self, testdir):
"""Test parametrization when parameter overrides existing fixture with same name."""
testdir.makepyfile("""
@@ -532,7 +563,6 @@ class TestFunction(object):
rec = testdir.inline_run()
rec.assertoutcome(passed=3)
def test_parametrize_overrides_parametrized_fixture(self, testdir):
"""Test parametrization when parameter overrides existing parametrized fixture with same name."""
testdir.makepyfile("""
@@ -550,7 +580,8 @@ class TestFunction(object):
rec = testdir.inline_run()
rec.assertoutcome(passed=1)
def test_parametrize_with_mark(selfself, testdir):
@ignore_parametrized_marks
def test_parametrize_with_mark(self, testdir):
items = testdir.getitems("""
import pytest
@pytest.mark.foo
@@ -623,6 +654,7 @@ class TestFunction(object):
assert colitems[2].name == 'test2[a-c]'
assert colitems[3].name == 'test2[b-c]'
@ignore_parametrized_marks
def test_parametrize_skipif(self, testdir):
testdir.makepyfile("""
import pytest
@@ -636,6 +668,7 @@ class TestFunction(object):
result = testdir.runpytest()
result.stdout.fnmatch_lines('* 2 passed, 1 skipped in *')
@ignore_parametrized_marks
def test_parametrize_skip(self, testdir):
testdir.makepyfile("""
import pytest
@@ -649,6 +682,7 @@ class TestFunction(object):
result = testdir.runpytest()
result.stdout.fnmatch_lines('* 2 passed, 1 skipped in *')
@ignore_parametrized_marks
def test_parametrize_skipif_no_skip(self, testdir):
testdir.makepyfile("""
import pytest
@@ -662,6 +696,7 @@ class TestFunction(object):
result = testdir.runpytest()
result.stdout.fnmatch_lines('* 1 failed, 2 passed in *')
@ignore_parametrized_marks
def test_parametrize_xfail(self, testdir):
testdir.makepyfile("""
import pytest
@@ -675,6 +710,7 @@ class TestFunction(object):
result = testdir.runpytest()
result.stdout.fnmatch_lines('* 2 passed, 1 xfailed in *')
@ignore_parametrized_marks
def test_parametrize_passed(self, testdir):
testdir.makepyfile("""
import pytest
@@ -688,6 +724,7 @@ class TestFunction(object):
result = testdir.runpytest()
result.stdout.fnmatch_lines('* 2 passed, 1 xpassed in *')
@ignore_parametrized_marks
def test_parametrize_xfail_passed(self, testdir):
testdir.makepyfile("""
import pytest
@@ -733,11 +770,11 @@ class TestSorting(object):
assert not (fn1 == fn3)
assert fn1 != fn3
for fn in fn1,fn2,fn3:
for fn in fn1, fn2, fn3:
assert fn != 3
assert fn != modcol
assert fn != [1,2,3]
assert [1,2,3] != fn
assert fn != [1, 2, 3]
assert [1, 2, 3] != fn
assert modcol != fn
def test_allow_sane_sorting_for_decorators(self, testdir):
@@ -838,7 +875,7 @@ class TestConftestCustomization(object):
modcol = testdir.getmodulecol("def _hello(): pass")
l = []
monkeypatch.setattr(pytest.Module, 'makeitem',
lambda self, name, obj: l.append(name))
lambda self, name, obj: l.append(name))
l = modcol.collect()
assert '_hello' not in l
@@ -870,6 +907,7 @@ class TestConftestCustomization(object):
result = testdir.runpytest_subprocess()
result.stdout.fnmatch_lines('*1 passed*')
def test_setup_only_available_in_subdir(testdir):
sub1 = testdir.mkpydir("sub1")
sub2 = testdir.mkpydir("sub2")
@@ -896,6 +934,7 @@ def test_setup_only_available_in_subdir(testdir):
result = testdir.runpytest("-v", "-s")
result.assert_outcomes(passed=2)
def test_modulecol_roundtrip(testdir):
modcol = testdir.getmodulecol("pass", withinit=True)
trail = modcol.nodeid
@@ -923,13 +962,13 @@ class TestTracebackCutting(object):
out = result.stdout.str()
assert "xyz" in out
assert "conftest.py:5: ValueError" in out
numentries = out.count("_ _ _") # separator for traceback entries
numentries = out.count("_ _ _") # separator for traceback entries
assert numentries == 0
result = testdir.runpytest("--fulltrace", p)
out = result.stdout.str()
assert "conftest.py:5: ValueError" in out
numentries = out.count("_ _ _ _") # separator for traceback entries
numentries = out.count("_ _ _ _") # separator for traceback entries
assert numentries > 3
def test_traceback_error_during_import(self, testdir):
@@ -1180,6 +1219,7 @@ def test_collector_attributes(testdir):
"*1 passed*",
])
def test_customize_through_attributes(testdir):
testdir.makeconftest("""
import pytest
@@ -1349,7 +1389,6 @@ def test_skip_duplicates_by_default(testdir):
])
def test_keep_duplicates(testdir):
"""Test for issue https://github.com/pytest-dev/pytest/issues/1609 (#1609)

View File

@@ -7,27 +7,39 @@ from _pytest.pytester import get_public_names
from _pytest.fixtures import FixtureLookupError
from _pytest import fixtures
def test_getfuncargnames():
def f(): pass
def f():
pass
assert not fixtures.getfuncargnames(f)
def g(arg): pass
def g(arg):
pass
assert fixtures.getfuncargnames(g) == ('arg',)
def h(arg1, arg2="hello"): pass
def h(arg1, arg2="hello"):
pass
assert fixtures.getfuncargnames(h) == ('arg1',)
def h(arg1, arg2, arg3="hello"): pass
def h(arg1, arg2, arg3="hello"):
pass
assert fixtures.getfuncargnames(h) == ('arg1', 'arg2')
class A(object):
def f(self, arg1, arg2="hello"):
pass
@staticmethod
def static(arg1, arg2):
pass
assert fixtures.getfuncargnames(A().f) == ('arg1',)
if sys.version_info < (3,0):
if sys.version_info < (3, 0):
assert fixtures.getfuncargnames(A.f) == ('arg1',)
assert fixtures.getfuncargnames(A.static, cls=A) == ('arg1', 'arg2')
class TestFillFixtures(object):
def test_fillfuncargs_exposed(self):
# used by oejskit, kept for compatibility
@@ -44,7 +56,7 @@ class TestFillFixtures(object):
def test_func(some):
pass
""")
result = testdir.runpytest() # "--collect-only")
result = testdir.runpytest() # "--collect-only")
assert result.ret != 0
result.stdout.fnmatch_lines([
"*def test_func(some)*",
@@ -439,7 +451,6 @@ class TestFillFixtures(object):
])
assert "INTERNAL" not in result.stdout.str()
def test_fixture_excinfo_leak(self, testdir):
# on python2 sys.excinfo would leak into fixture executions
testdir.makepyfile("""
@@ -551,7 +562,8 @@ class TestRequestBasic(object):
else:
# see #1830 for a cleaner way to accomplish this
@contextlib.contextmanager
def expecting_no_warning(): yield
def expecting_no_warning():
yield
warning_expectation = expecting_no_warning()
@@ -639,7 +651,6 @@ class TestRequestBasic(object):
mod = reprec.getcalls("pytest_runtest_setup")[0].item.module
assert not mod.l
def test_request_addfinalizer_partial_setup_failure(self, testdir):
p = testdir.makepyfile("""
import pytest
@@ -655,7 +666,7 @@ class TestRequestBasic(object):
result = testdir.runpytest(p)
result.stdout.fnmatch_lines([
"*1 error*" # XXX the whole module collection fails
])
])
def test_request_subrequest_addfinalizer_exceptions(self, testdir):
"""
@@ -815,9 +826,10 @@ class TestRequestBasic(object):
reprec = testdir.inline_run()
reprec.assertoutcome(passed=2)
class TestRequestMarking(object):
def test_applymarker(self, testdir):
item1,item2 = testdir.getitems("""
item1, item2 = testdir.getitems("""
import pytest
@pytest.fixture
@@ -875,6 +887,7 @@ class TestRequestMarking(object):
reprec = testdir.inline_run()
reprec.assertoutcome(passed=2)
class TestRequestCachedSetup(object):
def test_request_cachedsetup_defaultmodule(self, testdir):
reprec = testdir.inline_runsource("""
@@ -1040,6 +1053,7 @@ class TestRequestCachedSetup(object):
"*ZeroDivisionError*",
])
class TestFixtureUsages(object):
def test_noargfixturedec(self, testdir):
testdir.makepyfile("""
@@ -1297,7 +1311,7 @@ class TestFixtureUsages(object):
reprec = testdir.inline_run("-v")
reprec.assertoutcome(passed=4)
l = reprec.getcalls("pytest_runtest_call")[0].item.module.l
assert l == [1,2, 10,20]
assert l == [1, 2, 10, 20]
class TestFixtureManagerParseFactories(object):
@@ -1598,8 +1612,6 @@ class TestAutouseManagement(object):
reprec = testdir.inline_run()
reprec.assertoutcome(passed=2)
def test_funcarg_and_setup(self, testdir):
testdir.makepyfile("""
import pytest
@@ -1706,7 +1718,7 @@ class TestAutouseManagement(object):
pass
""")
confcut = "--confcutdir={0}".format(testdir.tmpdir)
reprec = testdir.inline_run("-v","-s", confcut)
reprec = testdir.inline_run("-v", "-s", confcut)
reprec.assertoutcome(passed=8)
config = reprec.getcalls("pytest_unconfigure")[0].config
l = config.pluginmanager._getconftestmodules(p)[0].l
@@ -1776,8 +1788,8 @@ class TestAutouseManagement(object):
reprec.assertoutcome(passed=1)
@pytest.mark.issue226
@pytest.mark.parametrize("param1", ["", "params=[1]"], ids=["p00","p01"])
@pytest.mark.parametrize("param2", ["", "params=[1]"], ids=["p10","p11"])
@pytest.mark.parametrize("param1", ["", "params=[1]"], ids=["p00", "p01"])
@pytest.mark.parametrize("param2", ["", "params=[1]"], ids=["p10", "p11"])
def test_ordering_dependencies_torndown_first(self, testdir, param1, param2):
testdir.makepyfile("""
import pytest
@@ -2108,7 +2120,7 @@ class TestFixtureMarker(object):
reprec = testdir.inline_run("-v")
reprec.assertoutcome(passed=4)
l = reprec.getcalls("pytest_runtest_call")[0].item.module.l
assert l == [1,1,2,2]
assert l == [1, 1, 2, 2]
def test_module_parametrized_ordering(self, testdir):
testdir.makeconftest("""
@@ -2243,7 +2255,7 @@ class TestFixtureMarker(object):
'fin:mod1', 'create:mod2', 'test2', 'create:1', 'test3',
'fin:1', 'create:2', 'test3', 'fin:2', 'create:1',
'test4', 'fin:1', 'create:2', 'test4', 'fin:2',
'fin:mod2']
'fin:mod2']
import pprint
pprint.pprint(list(zip(l, expected)))
assert l == expected
@@ -2351,7 +2363,7 @@ class TestFixtureMarker(object):
""")
reprec = testdir.inline_run("-s")
l = reprec.getcalls("pytest_runtest_call")[0].item.module.l
assert l == [1,2]
assert l == [1, 2]
def test_parametrize_separated_lifecycle(self, testdir):
testdir.makepyfile("""
@@ -2373,7 +2385,7 @@ class TestFixtureMarker(object):
l = reprec.getcalls("pytest_runtest_call")[0].item.module.l
import pprint
pprint.pprint(l)
#assert len(l) == 6
# assert len(l) == 6
assert l[0] == l[1] == 1
assert l[2] == "fin1"
assert l[3] == l[4] == 2
@@ -2401,7 +2413,6 @@ class TestFixtureMarker(object):
reprec = testdir.inline_run("-v")
reprec.assertoutcome(passed=5)
@pytest.mark.issue246
@pytest.mark.parametrize("scope", ["session", "function", "module"])
def test_finalizer_order_on_parametrization(self, scope, testdir):
@@ -2542,9 +2553,42 @@ class TestFixtureMarker(object):
'*test_foo*alpha*',
'*test_foo*beta*'])
@pytest.mark.issue920
def test_deterministic_fixture_collection(self, testdir, monkeypatch):
testdir.makepyfile("""
import pytest
@pytest.fixture(scope="module",
params=["A",
"B",
"C"])
def A(request):
return request.param
@pytest.fixture(scope="module",
params=["DDDDDDDDD", "EEEEEEEEEEEE", "FFFFFFFFFFF", "banansda"])
def B(request, A):
return request.param
def test_foo(B):
# Something funky is going on here.
# Despite specified seeds, on what is collected,
# sometimes we get unexpected passes. hashing B seems
# to help?
assert hash(B) or True
""")
monkeypatch.setenv("PYTHONHASHSEED", "1")
out1 = testdir.runpytest_subprocess("-v")
monkeypatch.setenv("PYTHONHASHSEED", "2")
out2 = testdir.runpytest_subprocess("-v")
out1 = [line for line in out1.outlines if line.startswith("test_deterministic_fixture_collection.py::test_foo")]
out2 = [line for line in out2.outlines if line.startswith("test_deterministic_fixture_collection.py::test_foo")]
assert len(out1) == 12
assert out1 == out2
class TestRequestScopeAccess(object):
pytestmark = pytest.mark.parametrize(("scope", "ok", "error"),[
pytestmark = pytest.mark.parametrize(("scope", "ok", "error"), [
["session", "", "fspath class function module"],
["module", "module fspath", "cls function"],
["class", "module fspath cls", "function"],
@@ -2565,7 +2609,7 @@ class TestRequestScopeAccess(object):
assert request.config
def test_func():
pass
""" %(scope, ok.split(), error.split()))
""" % (scope, ok.split(), error.split()))
reprec = testdir.inline_run("-l")
reprec.assertoutcome(passed=1)
@@ -2583,10 +2627,11 @@ class TestRequestScopeAccess(object):
assert request.config
def test_func(arg):
pass
""" %(scope, ok.split(), error.split()))
""" % (scope, ok.split(), error.split()))
reprec = testdir.inline_run()
reprec.assertoutcome(passed=1)
class TestErrors(object):
def test_subfactory_missing_funcarg(self, testdir):
testdir.makepyfile("""
@@ -2632,8 +2677,6 @@ class TestErrors(object):
*3 pass*2 error*
""")
def test_setupfunc_missing_funcarg(self, testdir):
testdir.makepyfile("""
import pytest
@@ -2651,6 +2694,7 @@ class TestErrors(object):
"*1 error*",
])
class TestShowFixtures(object):
def test_funcarg_compat(self, testdir):
config = testdir.parseconfigure("--funcargs")
@@ -2659,18 +2703,16 @@ class TestShowFixtures(object):
def test_show_fixtures(self, testdir):
result = testdir.runpytest("--fixtures")
result.stdout.fnmatch_lines([
"*tmpdir*",
"*temporary directory*",
]
)
"*tmpdir*",
"*temporary directory*",
])
def test_show_fixtures_verbose(self, testdir):
result = testdir.runpytest("--fixtures", "-v")
result.stdout.fnmatch_lines([
"*tmpdir*--*tmpdir.py*",
"*temporary directory*",
]
)
"*tmpdir*--*tmpdir.py*",
"*temporary directory*",
])
def test_show_fixtures_testmodule(self, testdir):
p = testdir.makepyfile('''
@@ -2713,7 +2755,7 @@ class TestShowFixtures(object):
""")
def test_show_fixtures_trimmed_doc(self, testdir):
p = testdir.makepyfile('''
p = testdir.makepyfile(dedent('''
import pytest
@pytest.fixture
def arg1():
@@ -2729,9 +2771,9 @@ class TestShowFixtures(object):
line2
"""
''')
'''))
result = testdir.runpytest("--fixtures", p)
result.stdout.fnmatch_lines("""
result.stdout.fnmatch_lines(dedent("""
* fixtures defined from test_show_fixtures_trimmed_doc *
arg2
line1
@@ -2740,8 +2782,64 @@ class TestShowFixtures(object):
line1
line2
""")
"""))
def test_show_fixtures_indented_doc(self, testdir):
p = testdir.makepyfile(dedent('''
import pytest
@pytest.fixture
def fixture1():
"""
line1
indented line
"""
'''))
result = testdir.runpytest("--fixtures", p)
result.stdout.fnmatch_lines(dedent("""
* fixtures defined from test_show_fixtures_indented_doc *
fixture1
line1
indented line
"""))
def test_show_fixtures_indented_doc_first_line_unindented(self, testdir):
p = testdir.makepyfile(dedent('''
import pytest
@pytest.fixture
def fixture1():
"""line1
line2
indented line
"""
'''))
result = testdir.runpytest("--fixtures", p)
result.stdout.fnmatch_lines(dedent("""
* fixtures defined from test_show_fixtures_indented_doc_first_line_unindented *
fixture1
line1
line2
indented line
"""))
def test_show_fixtures_indented_in_class(self, testdir):
p = testdir.makepyfile(dedent('''
import pytest
class TestClass:
@pytest.fixture
def fixture1():
"""line1
line2
indented line
"""
'''))
result = testdir.runpytest("--fixtures", p)
result.stdout.fnmatch_lines(dedent("""
* fixtures defined from test_show_fixtures_indented_in_class *
fixture1
line1
line2
indented line
"""))
def test_show_fixtures_different_files(self, testdir):
"""
@@ -2921,6 +3019,7 @@ class TestContextManagerFixtureFuncs(object):
result = testdir.runpytest("-s")
result.stdout.fnmatch_lines("*mew*")
class TestParameterizedSubRequest(object):
def test_call_from_fixture(self, testdir):
testfile = testdir.makepyfile("""
@@ -3026,6 +3125,3 @@ class TestParameterizedSubRequest(object):
E*{1}:5
*1 failed*
""".format(fixfile.strpath, testfile.basename))

View File

@@ -4,7 +4,7 @@ from _pytest import runner
class TestOEJSKITSpecials(object):
def test_funcarg_non_pycollectobj(self, testdir): # rough jstests usage
def test_funcarg_non_pycollectobj(self, testdir): # rough jstests usage
testdir.makeconftest("""
import pytest
def pytest_pycollect_makeitem(collector, name, obj):
@@ -30,7 +30,7 @@ class TestOEJSKITSpecials(object):
pytest._fillfuncargs(clscol)
assert clscol.funcargs['arg1'] == 42
def test_autouse_fixture(self, testdir): # rough jstests usage
def test_autouse_fixture(self, testdir): # rough jstests usage
testdir.makeconftest("""
import pytest
def pytest_pycollect_makeitem(collector, name, obj):
@@ -76,6 +76,7 @@ def test_wrapped_getfslineno():
fs2, lineno2 = python.getfslineno(wrap)
assert lineno > lineno2, "getfslineno does not unwrap correctly"
class TestMockDecoration(object):
def test_wrapped_getfuncargnames(self):
from _pytest.compat import getfuncargnames
@@ -173,7 +174,7 @@ class TestMockDecoration(object):
reprec.assertoutcome(passed=2)
calls = reprec.getcalls("pytest_runtest_logreport")
funcnames = [call.report.location[2] for call in calls
if call.report.when == "call"]
if call.report.when == "call"]
assert funcnames == ["T.test_hello", "test_someting"]
def test_mock_sorting(self, testdir):
@@ -246,6 +247,7 @@ class TestReRunTests(object):
*2 passed*
""")
def test_pytestconfig_is_session_scoped():
from _pytest.fixtures import pytestconfig
assert pytestconfig._pytestfixturefunction.scope == "session"

View File

@@ -29,13 +29,15 @@ class TestMetafunc(object):
return python.Metafunc(func, fixtureinfo, None)
def test_no_funcargs(self, testdir):
def function(): pass
def function():
pass
metafunc = self.Metafunc(function)
assert not metafunc.fixturenames
repr(metafunc._calls)
def test_function_basic(self):
def func(arg1, arg2="qwe"): pass
def func(arg1, arg2="qwe"):
pass
metafunc = self.Metafunc(func)
assert len(metafunc.fixturenames) == 1
assert 'arg1' in metafunc.fixturenames
@@ -43,7 +45,8 @@ class TestMetafunc(object):
assert metafunc.cls is None
def test_addcall_no_args(self):
def func(arg1): pass
def func(arg1):
pass
metafunc = self.Metafunc(func)
metafunc.addcall()
assert len(metafunc._calls) == 1
@@ -52,7 +55,8 @@ class TestMetafunc(object):
assert not hasattr(call, 'param')
def test_addcall_id(self):
def func(arg1): pass
def func(arg1):
pass
metafunc = self.Metafunc(func)
pytest.raises(ValueError, "metafunc.addcall(id=None)")
@@ -65,10 +69,12 @@ class TestMetafunc(object):
assert metafunc._calls[1].id == "2"
def test_addcall_param(self):
def func(arg1): pass
def func(arg1):
pass
metafunc = self.Metafunc(func)
class obj(object): pass
class obj(object):
pass
metafunc.addcall(param=obj)
metafunc.addcall(param=obj)
@@ -79,11 +85,13 @@ class TestMetafunc(object):
assert metafunc._calls[2].getparam("arg1") == 1
def test_addcall_funcargs(self):
def func(x): pass
def func(x):
pass
metafunc = self.Metafunc(func)
class obj(object): pass
class obj(object):
pass
metafunc.addcall(funcargs={"x": 2})
metafunc.addcall(funcargs={"x": 3})
@@ -94,17 +102,19 @@ class TestMetafunc(object):
assert not hasattr(metafunc._calls[1], 'param')
def test_parametrize_error(self):
def func(x, y): pass
def func(x, y):
pass
metafunc = self.Metafunc(func)
metafunc.parametrize("x", [1,2])
pytest.raises(ValueError, lambda: metafunc.parametrize("x", [5,6]))
pytest.raises(ValueError, lambda: metafunc.parametrize("x", [5,6]))
metafunc.parametrize("y", [1,2])
pytest.raises(ValueError, lambda: metafunc.parametrize("y", [5,6]))
pytest.raises(ValueError, lambda: metafunc.parametrize("y", [5,6]))
metafunc.parametrize("x", [1, 2])
pytest.raises(ValueError, lambda: metafunc.parametrize("x", [5, 6]))
pytest.raises(ValueError, lambda: metafunc.parametrize("x", [5, 6]))
metafunc.parametrize("y", [1, 2])
pytest.raises(ValueError, lambda: metafunc.parametrize("y", [5, 6]))
pytest.raises(ValueError, lambda: metafunc.parametrize("y", [5, 6]))
def test_parametrize_bad_scope(self, testdir):
def func(x): pass
def func(x):
pass
metafunc = self.Metafunc(func)
try:
metafunc.parametrize("x", [1], scope='doggy')
@@ -112,42 +122,47 @@ class TestMetafunc(object):
assert "has an unsupported scope value 'doggy'" in str(ve)
def test_parametrize_and_id(self):
def func(x, y): pass
def func(x, y):
pass
metafunc = self.Metafunc(func)
metafunc.parametrize("x", [1,2], ids=['basic', 'advanced'])
metafunc.parametrize("x", [1, 2], ids=['basic', 'advanced'])
metafunc.parametrize("y", ["abc", "def"])
ids = [x.id for x in metafunc._calls]
assert ids == ["basic-abc", "basic-def", "advanced-abc", "advanced-def"]
def test_parametrize_and_id_unicode(self):
"""Allow unicode strings for "ids" parameter in Python 2 (##1905)"""
def func(x): pass
def func(x):
pass
metafunc = self.Metafunc(func)
metafunc.parametrize("x", [1, 2], ids=[u'basic', u'advanced'])
ids = [x.id for x in metafunc._calls]
assert ids == [u"basic", u"advanced"]
def test_parametrize_with_wrong_number_of_ids(self, testdir):
def func(x, y): pass
def func(x, y):
pass
metafunc = self.Metafunc(func)
pytest.raises(ValueError, lambda:
metafunc.parametrize("x", [1,2], ids=['basic']))
metafunc.parametrize("x", [1, 2], ids=['basic']))
pytest.raises(ValueError, lambda:
metafunc.parametrize(("x","y"), [("abc", "def"),
("ghi", "jkl")], ids=["one"]))
metafunc.parametrize(("x", "y"), [("abc", "def"),
("ghi", "jkl")], ids=["one"]))
@pytest.mark.issue510
def test_parametrize_empty_list(self):
def func( y): pass
def func(y):
pass
metafunc = self.Metafunc(func)
metafunc.parametrize("y", [])
assert 'skip' in metafunc._calls[0].keywords
def test_parametrize_with_userobjects(self):
def func(x, y): pass
def func(x, y):
pass
metafunc = self.Metafunc(func)
class A(object):
@@ -178,11 +193,27 @@ class TestMetafunc(object):
"""
from _pytest.python import _idval
values = [
(u'', ''),
(u'ascii', 'ascii'),
(u'ação', 'a\\xe7\\xe3o'),
(u'josé@blah.com', 'jos\\xe9@blah.com'),
(u'δοκ.ιμή@παράδειγμα.δοκιμή', '\\u03b4\\u03bf\\u03ba.\\u03b9\\u03bc\\u03ae@\\u03c0\\u03b1\\u03c1\\u03ac\\u03b4\\u03b5\\u03b9\\u03b3\\u03bc\\u03b1.\\u03b4\\u03bf\\u03ba\\u03b9\\u03bc\\u03ae'),
(
u'',
''
),
(
u'ascii',
'ascii'
),
(
u'ação',
'a\\xe7\\xe3o'
),
(
u'josé@blah.com',
'jos\\xe9@blah.com'
),
(
u'δοκ.ιμή@παράδειγμα.δοκιμή',
'\\u03b4\\u03bf\\u03ba.\\u03b9\\u03bc\\u03ae@\\u03c0\\u03b1\\u03c1\\u03ac\\u03b4\\u03b5\\u03b9\\u03b3'
'\\u03bc\\u03b1.\\u03b4\\u03bf\\u03ba\\u03b9\\u03bc\\u03ae'
),
]
for val, expected in values:
assert _idval(val, 'a', 6, None) == expected
@@ -279,7 +310,7 @@ class TestMetafunc(object):
assert result == ["10.0-IndexError()",
"20-KeyError()",
"three-b2",
]
]
@pytest.mark.issue351
def test_idmaker_idfn_unique_names(self):
@@ -291,11 +322,11 @@ class TestMetafunc(object):
result = idmaker(("a", "b"), [pytest.param(10.0, IndexError()),
pytest.param(20, KeyError()),
pytest.param("three", [1, 2, 3]),
], idfn=ids)
], idfn=ids)
assert result == ["a-a0",
"a-a1",
"a-a2",
]
]
@pytest.mark.issue351
def test_idmaker_idfn_exception(self):
@@ -331,7 +362,6 @@ class TestMetafunc(object):
"\nUpdate your code as this will raise an error in pytest-4.0.",
]
def test_parametrize_ids_exception(self, testdir):
"""
:param testdir: the instance of Testdir class, a temporary
@@ -371,15 +401,16 @@ class TestMetafunc(object):
def test_idmaker_with_ids_unique_names(self):
from _pytest.python import idmaker
result = idmaker(("a"), map(pytest.param, [1,2,3,4,5]),
result = idmaker(("a"), map(pytest.param, [1, 2, 3, 4, 5]),
ids=["a", "a", "b", "c", "b"])
assert result == ["a0", "a1", "b0", "c", "b1"]
def test_addcall_and_parametrize(self):
def func(x, y): pass
def func(x, y):
pass
metafunc = self.Metafunc(func)
metafunc.addcall({'x': 1})
metafunc.parametrize('y', [2,3])
metafunc.parametrize('y', [2, 3])
assert len(metafunc._calls) == 2
assert metafunc._calls[0].funcargs == {'x': 1, 'y': 2}
assert metafunc._calls[1].funcargs == {'x': 1, 'y': 3}
@@ -388,19 +419,21 @@ class TestMetafunc(object):
@pytest.mark.issue714
def test_parametrize_indirect(self):
def func(x, y): pass
def func(x, y):
pass
metafunc = self.Metafunc(func)
metafunc.parametrize('x', [1], indirect=True)
metafunc.parametrize('y', [2,3], indirect=True)
metafunc.parametrize('y', [2, 3], indirect=True)
assert len(metafunc._calls) == 2
assert metafunc._calls[0].funcargs == {}
assert metafunc._calls[1].funcargs == {}
assert metafunc._calls[0].params == dict(x=1,y=2)
assert metafunc._calls[1].params == dict(x=1,y=3)
assert metafunc._calls[0].params == dict(x=1, y=2)
assert metafunc._calls[1].params == dict(x=1, y=3)
@pytest.mark.issue714
def test_parametrize_indirect_list(self):
def func(x, y): pass
def func(x, y):
pass
metafunc = self.Metafunc(func)
metafunc.parametrize('x, y', [('a', 'b')], indirect=['x'])
assert metafunc._calls[0].funcargs == dict(y='b')
@@ -408,7 +441,8 @@ class TestMetafunc(object):
@pytest.mark.issue714
def test_parametrize_indirect_list_all(self):
def func(x, y): pass
def func(x, y):
pass
metafunc = self.Metafunc(func)
metafunc.parametrize('x, y', [('a', 'b')], indirect=['x', 'y'])
assert metafunc._calls[0].funcargs == {}
@@ -416,7 +450,8 @@ class TestMetafunc(object):
@pytest.mark.issue714
def test_parametrize_indirect_list_empty(self):
def func(x, y): pass
def func(x, y):
pass
metafunc = self.Metafunc(func)
metafunc.parametrize('x, y', [('a', 'b')], indirect=[])
assert metafunc._calls[0].funcargs == dict(x='a', y='b')
@@ -454,7 +489,8 @@ class TestMetafunc(object):
@pytest.mark.issue714
def test_parametrize_indirect_list_error(self, testdir):
def func(x, y): pass
def func(x, y):
pass
metafunc = self.Metafunc(func)
with pytest.raises(ValueError):
metafunc.parametrize('x, y', [('a', 'b')], indirect=['x', 'z'])
@@ -550,16 +586,17 @@ class TestMetafunc(object):
])
def test_addcalls_and_parametrize_indirect(self):
def func(x, y): pass
def func(x, y):
pass
metafunc = self.Metafunc(func)
metafunc.addcall(param="123")
metafunc.parametrize('x', [1], indirect=True)
metafunc.parametrize('y', [2,3], indirect=True)
metafunc.parametrize('y', [2, 3], indirect=True)
assert len(metafunc._calls) == 2
assert metafunc._calls[0].funcargs == {}
assert metafunc._calls[1].funcargs == {}
assert metafunc._calls[0].params == dict(x=1,y=2)
assert metafunc._calls[1].params == dict(x=1,y=3)
assert metafunc._calls[0].params == dict(x=1, y=2)
assert metafunc._calls[1].params == dict(x=1, y=3)
def test_parametrize_functional(self, testdir):
testdir.makepyfile("""
@@ -584,7 +621,7 @@ class TestMetafunc(object):
def test_parametrize_onearg(self):
metafunc = self.Metafunc(lambda x: None)
metafunc.parametrize("x", [1,2])
metafunc.parametrize("x", [1, 2])
assert len(metafunc._calls) == 2
assert metafunc._calls[0].funcargs == dict(x=1)
assert metafunc._calls[0].id == "1"
@@ -593,15 +630,15 @@ class TestMetafunc(object):
def test_parametrize_onearg_indirect(self):
metafunc = self.Metafunc(lambda x: None)
metafunc.parametrize("x", [1,2], indirect=True)
metafunc.parametrize("x", [1, 2], indirect=True)
assert metafunc._calls[0].params == dict(x=1)
assert metafunc._calls[0].id == "1"
assert metafunc._calls[1].params == dict(x=2)
assert metafunc._calls[1].id == "2"
def test_parametrize_twoargs(self):
metafunc = self.Metafunc(lambda x,y: None)
metafunc.parametrize(("x", "y"), [(1,2), (3,4)])
metafunc = self.Metafunc(lambda x, y: None)
metafunc.parametrize(("x", "y"), [(1, 2), (3, 4)])
assert len(metafunc._calls) == 2
assert metafunc._calls[0].funcargs == dict(x=1, y=2)
assert metafunc._calls[0].id == "1-2"
@@ -672,16 +709,20 @@ class TestMetafunc(object):
""")
def test_format_args(self):
def function1(): pass
def function1():
pass
assert fixtures._format_args(function1) == '()'
def function2(arg1): pass
def function2(arg1):
pass
assert fixtures._format_args(function2) == "(arg1)"
def function3(arg1, arg2="qwe"): pass
def function3(arg1, arg2="qwe"):
pass
assert fixtures._format_args(function3) == "(arg1, arg2='qwe')"
def function4(arg1, *args, **kwargs): pass
def function4(arg1, *args, **kwargs):
pass
assert fixtures._format_args(function4) == "(arg1, *args, **kwargs)"
@@ -776,7 +817,6 @@ class TestMetafuncFunctional(object):
result = testdir.runpytest(p)
result.assert_outcomes(passed=1)
def test_generate_plugin_and_module(self, testdir):
testdir.makeconftest("""
def pytest_generate_tests(metafunc):
@@ -1114,7 +1154,7 @@ class TestMetafuncFunctional(object):
@pytest.mark.issue463
@pytest.mark.parametrize('attr', ['parametrise', 'parameterize',
'parameterise'])
'parameterise'])
def test_parametrize_misspelling(self, testdir, attr):
testdir.makepyfile("""
import pytest
@@ -1249,8 +1289,10 @@ class TestMetafuncFunctionalAuto(object):
assert output.count('preparing foo-3') == 1
@pytest.mark.filterwarnings('ignore:Applying marks directly to parameters')
@pytest.mark.issue308
class TestMarkersWithParametrization(object):
pytestmark = pytest.mark.issue308
def test_simple_mark(self, testdir):
s = """
import pytest
@@ -1434,7 +1476,6 @@ class TestMarkersWithParametrization(object):
reprec = testdir.inline_run()
reprec.assertoutcome(passed=2, skipped=2)
@pytest.mark.issue290
def test_parametrize_ID_generation_string_int_works(self, testdir):
testdir.makepyfile("""
@@ -1451,7 +1492,6 @@ class TestMarkersWithParametrization(object):
reprec = testdir.inline_run()
reprec.assertoutcome(passed=2)
@pytest.mark.parametrize('strict', [True, False])
def test_parametrize_marked_value(self, testdir, strict):
s = """
@@ -1475,7 +1515,6 @@ class TestMarkersWithParametrization(object):
passed, failed = (0, 2) if strict else (2, 0)
reprec.assertoutcome(passed=passed, failed=failed)
def test_pytest_make_parametrize_id(self, testdir):
testdir.makeconftest("""
def pytest_make_parametrize_id(config, val):

View File

@@ -118,7 +118,6 @@ class TestRaises(object):
for o in gc.get_objects():
assert type(o) is not T
def test_raises_match(self):
msg = r"with base \d+"
with pytest.raises(ValueError, match=msg):

View File

@@ -187,7 +187,7 @@ def test_dynamic_fixture_request(testdir):
pass
@pytest.fixture()
def dependent_fixture(request):
request.getfuncargvalue('dynamically_requested_fixture')
request.getfixturevalue('dynamically_requested_fixture')
def test_dyn(dependent_fixture):
pass
''')

View File

@@ -1,8 +1,10 @@
from __future__ import absolute_import, division, print_function
import py, pytest
import py
import pytest
# test for _argcomplete but not specific for any application
def equal_with_bash(prefix, ffc, fc, out=None):
res = ffc(prefix)
res_bash = set(fc(prefix))
@@ -17,10 +19,12 @@ def equal_with_bash(prefix, ffc, fc, out=None):
# copied from argcomplete.completers as import from there
# also pulls in argcomplete.__init__ which opens filedescriptor 9
# this gives an IOError at the end of testrun
def _wrapcall(*args, **kargs):
try:
if py.std.sys.version_info > (2,7):
return py.std.subprocess.check_output(*args,**kargs).decode().splitlines()
if py.std.sys.version_info > (2, 7):
return py.std.subprocess.check_output(*args, **kargs).decode().splitlines()
if 'stdout' in kargs:
raise ValueError('stdout argument not allowed, it will be overridden.')
process = py.std.subprocess.Popen(
@@ -36,9 +40,11 @@ def _wrapcall(*args, **kargs):
except py.std.subprocess.CalledProcessError:
return []
class FilesCompleter(object):
'File completer class, optionally takes a list of allowed extensions'
def __init__(self,allowednames=(),directories=True):
def __init__(self, allowednames=(), directories=True):
# Fix if someone passes in a string instead of a list
if type(allowednames) is str:
allowednames = [allowednames]
@@ -50,25 +56,26 @@ class FilesCompleter(object):
completion = []
if self.allowednames:
if self.directories:
files = _wrapcall(['bash','-c',
"compgen -A directory -- '{p}'".format(p=prefix)])
completion += [ f + '/' for f in files]
files = _wrapcall(['bash', '-c',
"compgen -A directory -- '{p}'".format(p=prefix)])
completion += [f + '/' for f in files]
for x in self.allowednames:
completion += _wrapcall(['bash', '-c',
"compgen -A file -X '!*.{0}' -- '{p}'".format(x,p=prefix)])
"compgen -A file -X '!*.{0}' -- '{p}'".format(x, p=prefix)])
else:
completion += _wrapcall(['bash', '-c',
"compgen -A file -- '{p}'".format(p=prefix)])
"compgen -A file -- '{p}'".format(p=prefix)])
anticomp = _wrapcall(['bash', '-c',
"compgen -A directory -- '{p}'".format(p=prefix)])
"compgen -A directory -- '{p}'".format(p=prefix)])
completion = list( set(completion) - set(anticomp))
completion = list(set(completion) - set(anticomp))
if self.directories:
completion += [f + '/' for f in anticomp]
return completion
class TestArgComplete(object):
@pytest.mark.skipif("sys.platform in ('win32', 'darwin')")
def test_compare_with_compgen(self):

View File

@@ -283,6 +283,7 @@ class TestBinReprIntegration(object):
"*test_check*PASS*",
])
def callequal(left, right, verbose=False):
config = mock_config()
config.verbose = verbose
@@ -303,15 +304,15 @@ class TestAssert_reprcompare(object):
assert '+ eggs' in diff
def test_text_skipping(self):
lines = callequal('a'*50 + 'spam', 'a'*50 + 'eggs')
lines = callequal('a' * 50 + 'spam', 'a' * 50 + 'eggs')
assert 'Skipping' in lines[1]
for line in lines:
assert 'a'*50 not in line
assert 'a' * 50 not in line
def test_text_skipping_verbose(self):
lines = callequal('a'*50 + 'spam', 'a'*50 + 'eggs', verbose=True)
assert '- ' + 'a'*50 + 'spam' in lines
assert '+ ' + 'a'*50 + 'eggs' in lines
lines = callequal('a' * 50 + 'spam', 'a' * 50 + 'eggs', verbose=True)
assert '- ' + 'a' * 50 + 'spam' in lines
assert '+ ' + 'a' * 50 + 'eggs' in lines
def test_multiline_text_diff(self):
left = 'foo\nspam\nbar'
@@ -437,9 +438,9 @@ class TestAssert_reprcompare(object):
assert len(expl) > 1
def test_list_tuples(self):
expl = callequal([], [(1,2)])
expl = callequal([], [(1, 2)])
assert len(expl) > 1
expl = callequal([(1,2)], [])
expl = callequal([(1, 2)], [])
assert len(expl) > 1
def test_list_bad_repr(self):
@@ -609,7 +610,7 @@ class TestTruncateExplanation(object):
def test_doesnt_truncate_at_when_input_is_5_lines_and_LT_max_chars(self):
expl = ['a' * 100 for x in range(5)]
result = truncate._truncate_explanation(expl, max_lines=8, max_chars=8*80)
result = truncate._truncate_explanation(expl, max_lines=8, max_chars=8 * 80)
assert result == expl
def test_truncates_at_8_lines_when_given_list_of_empty_strings(self):
@@ -619,27 +620,27 @@ class TestTruncateExplanation(object):
assert len(result) == 8 + self.LINES_IN_TRUNCATION_MSG
assert "Full output truncated" in result[-1]
assert "43 lines hidden" in result[-1]
last_line_before_trunc_msg = result[- self.LINES_IN_TRUNCATION_MSG -1]
last_line_before_trunc_msg = result[- self.LINES_IN_TRUNCATION_MSG - 1]
assert last_line_before_trunc_msg.endswith("...")
def test_truncates_at_8_lines_when_first_8_lines_are_LT_max_chars(self):
expl = ['a' for x in range(100)]
result = truncate._truncate_explanation(expl, max_lines=8, max_chars=8*80)
result = truncate._truncate_explanation(expl, max_lines=8, max_chars=8 * 80)
assert result != expl
assert len(result) == 8 + self.LINES_IN_TRUNCATION_MSG
assert "Full output truncated" in result[-1]
assert "93 lines hidden" in result[-1]
last_line_before_trunc_msg = result[- self.LINES_IN_TRUNCATION_MSG -1]
last_line_before_trunc_msg = result[- self.LINES_IN_TRUNCATION_MSG - 1]
assert last_line_before_trunc_msg.endswith("...")
def test_truncates_at_8_lines_when_first_8_lines_are_EQ_max_chars(self):
expl = ['a' * 80 for x in range(16)]
result = truncate._truncate_explanation(expl, max_lines=8, max_chars=8*80)
result = truncate._truncate_explanation(expl, max_lines=8, max_chars=8 * 80)
assert result != expl
assert len(result) == 8 + self.LINES_IN_TRUNCATION_MSG
assert "Full output truncated" in result[-1]
assert "9 lines hidden" in result[-1]
last_line_before_trunc_msg = result[- self.LINES_IN_TRUNCATION_MSG -1]
last_line_before_trunc_msg = result[- self.LINES_IN_TRUNCATION_MSG - 1]
assert last_line_before_trunc_msg.endswith("...")
def test_truncates_at_4_lines_when_first_4_lines_are_GT_max_chars(self):
@@ -649,7 +650,7 @@ class TestTruncateExplanation(object):
assert len(result) == 4 + self.LINES_IN_TRUNCATION_MSG
assert "Full output truncated" in result[-1]
assert "7 lines hidden" in result[-1]
last_line_before_trunc_msg = result[- self.LINES_IN_TRUNCATION_MSG -1]
last_line_before_trunc_msg = result[- self.LINES_IN_TRUNCATION_MSG - 1]
assert last_line_before_trunc_msg.endswith("...")
def test_truncates_at_1_line_when_first_line_is_GT_max_chars(self):
@@ -659,7 +660,7 @@ class TestTruncateExplanation(object):
assert len(result) == 1 + self.LINES_IN_TRUNCATION_MSG
assert "Full output truncated" in result[-1]
assert "1000 lines hidden" in result[-1]
last_line_before_trunc_msg = result[- self.LINES_IN_TRUNCATION_MSG -1]
last_line_before_trunc_msg = result[- self.LINES_IN_TRUNCATION_MSG - 1]
assert last_line_before_trunc_msg.endswith("...")
def test_full_output_truncated(self, monkeypatch, testdir):
@@ -712,6 +713,7 @@ def test_python25_compile_issue257(testdir):
*1 failed*
""")
def test_rewritten(testdir):
testdir.makepyfile("""
def test_rewritten():
@@ -719,11 +721,13 @@ def test_rewritten(testdir):
""")
assert testdir.runpytest().ret == 0
def test_reprcompare_notin(mock_config):
detail = plugin.pytest_assertrepr_compare(
mock_config, 'not in', 'foo', 'aaafoobbb')[1:]
assert detail == ["'foo' is contained here:", ' aaafoobbb', '? +++']
def test_pytest_assertrepr_compare_integration(testdir):
testdir.makepyfile("""
def test_hello():
@@ -740,6 +744,7 @@ def test_pytest_assertrepr_compare_integration(testdir):
"*E*50*",
])
def test_sequence_comparison_uses_repr(testdir):
testdir.makepyfile("""
def test_hello():
@@ -772,12 +777,12 @@ def test_assertrepr_loaded_per_dir(testdir):
b_conftest.write('def pytest_assertrepr_compare(): return ["summary b"]')
result = testdir.runpytest()
result.stdout.fnmatch_lines([
'*def test_base():*',
'*E*assert 1 == 2*',
'*def test_a():*',
'*E*assert summary a*',
'*def test_b():*',
'*E*assert summary b*'])
'*def test_base():*',
'*E*assert 1 == 2*',
'*def test_a():*',
'*E*assert summary a*',
'*def test_b():*',
'*E*assert summary b*'])
def test_assertion_options(testdir):
@@ -791,6 +796,7 @@ def test_assertion_options(testdir):
result = testdir.runpytest_subprocess("--assert=plain")
assert "3 == 4" not in result.stdout.str()
def test_triple_quoted_string_issue113(testdir):
testdir.makepyfile("""
def test_hello():
@@ -802,6 +808,7 @@ def test_triple_quoted_string_issue113(testdir):
])
assert 'SyntaxError' not in result.stdout.str()
def test_traceback_failure(testdir):
p1 = testdir.makepyfile("""
def g():
@@ -822,7 +829,7 @@ def test_traceback_failure(testdir):
"",
"*test_*.py:6: ",
"_ _ _ *",
#"",
# "",
" def f(x):",
"> assert x == g()",
"E assert 3 == 2",
@@ -831,7 +838,7 @@ def test_traceback_failure(testdir):
"*test_traceback_failure.py:4: AssertionError"
])
result = testdir.runpytest(p1) # "auto"
result = testdir.runpytest(p1) # "auto"
result.stdout.fnmatch_lines([
"*test_traceback_failure.py F",
"====* FAILURES *====",
@@ -881,7 +888,7 @@ def test_exception_handling_no_traceback(testdir):
])
@pytest.mark.skipif("'__pypy__' in sys.builtin_module_names or sys.platform.startswith('java')" )
@pytest.mark.skipif("'__pypy__' in sys.builtin_module_names or sys.platform.startswith('java')")
def test_warn_missing(testdir):
testdir.makepyfile("")
result = testdir.run(sys.executable, "-OO", "-m", "pytest", "-h")
@@ -893,6 +900,7 @@ def test_warn_missing(testdir):
"*WARNING*assert statements are not executed*",
])
def test_recursion_source_decode(testdir):
testdir.makepyfile("""
def test_something():
@@ -907,6 +915,7 @@ def test_recursion_source_decode(testdir):
<Module*>
""")
def test_AssertionError_message(testdir):
testdir.makepyfile("""
def test_hello():
@@ -920,6 +929,7 @@ def test_AssertionError_message(testdir):
*AssertionError: (1, 2)*
""")
@pytest.mark.skipif(PY3, reason='This bug does not exist on PY3')
def test_set_with_unsortable_elements():
# issue #718
@@ -956,6 +966,7 @@ def test_set_with_unsortable_elements():
""").strip()
assert '\n'.join(expl) == dedent
def test_diff_newline_at_end(monkeypatch, testdir):
testdir.makepyfile(r"""
def test_diff():
@@ -970,6 +981,7 @@ def test_diff_newline_at_end(monkeypatch, testdir):
* ? +
""")
def test_assert_tuple_warning(testdir):
testdir.makepyfile("""
def test_tuple():
@@ -981,6 +993,7 @@ def test_assert_tuple_warning(testdir):
'*assertion is always true*',
])
def test_assert_indirect_tuple_no_warning(testdir):
testdir.makepyfile("""
def test_tuple():
@@ -991,6 +1004,7 @@ def test_assert_indirect_tuple_no_warning(testdir):
output = '\n'.join(result.stdout.lines)
assert 'WR1' not in output
def test_assert_with_unicode(monkeypatch, testdir):
testdir.makepyfile(u"""
# -*- coding: utf-8 -*-
@@ -1000,6 +1014,7 @@ def test_assert_with_unicode(monkeypatch, testdir):
result = testdir.runpytest()
result.stdout.fnmatch_lines(['*AssertionError*'])
def test_raise_unprintable_assertion_error(testdir):
testdir.makepyfile(r"""
def test_raise_assertion_error():
@@ -1008,6 +1023,7 @@ def test_raise_unprintable_assertion_error(testdir):
result = testdir.runpytest()
result.stdout.fnmatch_lines([r"> raise AssertionError('\xff')", 'E AssertionError: *'])
def test_raise_assertion_error_raisin_repr(testdir):
testdir.makepyfile(u"""
class RaisingRepr(object):
@@ -1019,6 +1035,7 @@ def test_raise_assertion_error_raisin_repr(testdir):
result = testdir.runpytest()
result.stdout.fnmatch_lines(['E AssertionError: <unprintable AssertionError object>'])
def test_issue_1944(testdir):
testdir.makepyfile("""
def f():

View File

@@ -1,29 +1,30 @@
from __future__ import absolute_import, division, print_function
import glob
import os
import py_compile
import stat
import sys
import zipfile
import py
import pytest
ast = pytest.importorskip("ast")
if sys.platform.startswith("java"):
# XXX should be xfail
pytest.skip("assert rewrite does currently not work on jython")
import _pytest._code
from _pytest.assertion import util
from _pytest.assertion.rewrite import rewrite_asserts, PYTEST_TAG, AssertionRewritingHook
from _pytest.main import EXIT_NOTESTSCOLLECTED
ast = pytest.importorskip("ast")
if sys.platform.startswith("java"):
# XXX should be xfail
pytest.skip("assert rewrite does currently not work on jython")
def setup_module(mod):
mod._old_reprcompare = util._reprcompare
_pytest._code._reprcompare = None
def teardown_module(mod):
util._reprcompare = mod._old_reprcompare
del mod._old_reprcompare
@@ -34,6 +35,7 @@ def rewrite(src):
rewrite_asserts(tree)
return tree
def getmsg(f, extra_ns=None, must_pass=False):
"""Rewrite the assertions in f, run it, and get the failure message."""
src = '\n'.join(_pytest._code.Code(f).source().lines)
@@ -118,12 +120,12 @@ class TestAssertionRewrite(object):
def f():
assert a_global # noqa
assert getmsg(f, {"a_global" : False}) == "assert False"
assert getmsg(f, {"a_global": False}) == "assert False"
def f():
assert sys == 42
assert getmsg(f, {"sys" : sys}) == "assert sys == 42"
assert getmsg(f, {"sys": sys}) == "assert sys == 42"
def f():
assert cls == 42 # noqa
@@ -131,7 +133,7 @@ class TestAssertionRewrite(object):
class X(object):
pass
assert getmsg(f, {"cls" : X}) == "assert cls == 42"
assert getmsg(f, {"cls": X}) == "assert cls == 42"
def test_assert_already_has_message(self):
def f():
@@ -238,13 +240,13 @@ class TestAssertionRewrite(object):
def f():
assert x() and x()
assert getmsg(f, {"x" : x}) == """assert (False)
assert getmsg(f, {"x": x}) == """assert (False)
+ where False = x()"""
def f():
assert False or x()
assert getmsg(f, {"x" : x}) == """assert (False or False)
assert getmsg(f, {"x": x}) == """assert (False or False)
+ where False = x()"""
def f():
@@ -255,7 +257,7 @@ class TestAssertionRewrite(object):
def f():
x = 1
y = 2
assert x in {1 : None} and y in {}
assert x in {1: None} and y in {}
assert getmsg(f) == "assert (1 in {1: None} and 2 in {})"
@@ -348,7 +350,7 @@ class TestAssertionRewrite(object):
def g(a=42, *args, **kwargs):
return False
ns = {"g" : g}
ns = {"g": g}
def f():
assert g()
@@ -389,7 +391,7 @@ class TestAssertionRewrite(object):
def f():
x = "a"
assert g(**{x : 2})
assert g(**{x: 2})
assert getmsg(f, ns) == """assert False
+ where False = g(**{'a': 2})"""
@@ -398,10 +400,10 @@ class TestAssertionRewrite(object):
class X(object):
g = 3
ns = {"x" : X}
ns = {"x": X}
def f():
assert not x.g # noqa
assert not x.g # noqa
assert getmsg(f, ns) == """assert not 3
+ where 3 = x.g"""
@@ -556,7 +558,7 @@ class TestRewriteOnImport(object):
def test_readonly(self, testdir):
sub = testdir.mkdir("testing")
sub.join("test_readonly.py").write(
py.builtin._totext("""
py.builtin._totext("""
def test_rewritten():
assert "@py_builtins" in globals()
""").encode("utf-8"), "wb")
@@ -609,7 +611,7 @@ def test_rewritten():
def test_optimized():
"hello"
assert test_optimized.__doc__ is None"""
)
)
p = py.path.local.make_numbered_dir(prefix="runpytest-", keep=None,
rootdir=testdir.tmpdir)
tmp = "--basetemp=%s" % p
@@ -638,8 +640,8 @@ def test_rewritten():
testdir.tmpdir.join("test_newlines.py").write(b, "wb")
assert testdir.runpytest().ret == 0
@pytest.mark.skipif(sys.version_info < (3,3),
reason='packages without __init__.py not supported on python 2')
@pytest.mark.skipif(sys.version_info < (3, 3),
reason='packages without __init__.py not supported on python 2')
def test_package_without__init__py(self, testdir):
pkg = testdir.mkdir('a_package_without_init_py')
pkg.join('module.py').ensure()

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