Compare commits

...

1448 Commits
2.6.3 ... 2.9.2

Author SHA1 Message Date
Ronny Pfannschmidt
978bb190a1 add release announcement 2016-05-31 19:06:29 +02:00
Ronny Pfannschmidt
a2a904466c alter changelog header 2016-05-31 19:05:36 +02:00
Ronny Pfannschmidt
77c28825df regendoc and comment out a python2.7 example as per #1573 2016-05-31 19:05:36 +02:00
Ronny Pfannschmidt
d3dcc2b8f1 bump regendoc python version to 3.5 2016-05-31 19:05:36 +02:00
Ronny Pfannschmidt
85541113eb update version 2016-05-31 19:05:36 +02:00
Ronny Pfannschmidt
7ef06822cb ignore hypothesis temp folder 2016-05-31 19:05:36 +02:00
Ronny Pfannschmidt
289e0091de Merge pull request #1577 from pytest-dev/docs-hookwrapper-experimental
Remove "experimental" status from hookwrapper
2016-05-31 17:56:08 +02:00
Bruno Oliveira
28efdebfcd Remove "experimental" status from hookwrapper 2016-05-31 12:17:55 -03:00
Ronny Pfannschmidt
fb2e7cc727 Merge pull request #1575 from hackebrot/fix-showfixtures-for-multiple-fixturedefs
Fix showfixtures for multiple fixturedefs
2016-05-31 13:53:13 +02:00
Raphael Pierzina
fb8ad714b1 Implement a test for showfixtures to show fixtures with same name 2016-05-31 11:47:16 +01:00
Raphael Pierzina
945072b89a Update changelog with --fixtures fix 2016-05-31 11:47:16 +01:00
Raphael Pierzina
0d80a9c729 Change _showfixtures_main to loop over fixturedefs 2016-05-31 11:47:16 +01:00
Ronny Pfannschmidt
357a7c79ef Merge branch 'tomviner-issue-1496-xfail-cond-kw'
fixes  #1496
2016-05-31 11:25:15 +02:00
Ronny Pfannschmidt
158f3cfaea merge master 2016-05-31 11:24:53 +02:00
Bruno Oliveira
82c74fe7e6 Merge pull request #1530 from RonnyPfannschmidt/fix-510
fix #510 by adding a describing skip marker on empty parameterize
2016-05-30 10:54:33 -03:00
Ronny Pfannschmidt
afc5c7e4f6 better message for empty argument skip
include the argument names to help determining the fixture/parametrization
2016-05-30 14:42:55 +02:00
Ronny Pfannschmidt
03eb9203fd remove the old empty argument triggers
unlike the marker based one its not composable
2016-05-30 14:41:00 +02:00
Ronny Pfannschmidt
ae4dff0e0a add missing link to changelog 2016-05-30 14:34:38 +02:00
Ronny Pfannschmidt
d217b52508 fix #510 by adding a describing skip marker on empty parameterize 2016-05-30 14:34:38 +02:00
Ronny Pfannschmidt
436e13ac25 Merge pull request #1566 from nicoddemus/fix-win32-path
Fix shell argument split in win32
2016-05-24 08:14:01 +02:00
Bruno Oliveira
9fb5ddf778 Fix shell argument split in win32
This fixes the bug inserted by accident in #1523
2016-05-23 20:41:47 -03:00
Ronny Pfannschmidt
6f98cd6faa Merge pull request #1560 from nicoddemus/fix-py26-tox-comment
Move comment in tox.ini due to recent bug in pip
2016-05-18 21:31:56 +02:00
Bruno Oliveira
561a5fb558 Move comment in tox.ini due to recent bug in pip
As discussed in #1554

* https://bitbucket.org/hpk42/tox/issues/332/
* https://github.com/pypa/pip/issues/3667
2016-05-18 16:15:29 -03:00
Bruno Oliveira
47739291cf Merge pull request #1557 from adamchainz/readthedocs.io
Convert readthedocs link for their .org -> .io migration for hosted projects
2016-05-18 13:24:03 -03:00
Adam Chainz
8a39869347 Convert readthedocs link for their .org -> .io migration for hosted projects
As per their email ‘Changes to project subdomains’:

> Starting today, Read the Docs will start hosting projects from subdomains on the domain readthedocs.io, instead of on readthedocs.org. This change addresses some security concerns around site cookies while hosting user generated data on the same domain as our dashboard.

Test Plan: Manually visited all the links I’ve modified. One was not modified - `http://media.readthedocs.org/epub/pytest/latest/pytest.epub` - since it doesn't work on `readthedocs.io`.
2016-05-18 17:12:39 +01:00
Ronny Pfannschmidt
8a94c66e68 Merge pull request #1551 from fushi/master
Fix #1549 - count skips in junit-xml
2016-05-05 22:03:30 +02:00
John Towler
0d07b64571 Fixes Issue 1549 2016-05-05 11:29:05 -07:00
Florian Bruhin
60212e8831 Merge pull request #1537 from ben4ever/patch-1
Fix typo in doc
2016-04-27 22:13:51 -07:00
Benjamin Dopplinger
75abfbe8d4 Fix typo in doc 2016-04-28 14:40:17 +10:00
Ronny Pfannschmidt
6d661ace0a Merge pull request #1523 from MengJueM/master 2016-04-20 16:55:31 +02:00
MengJueM
f51c34ef31 Add changelog 2016-04-20 22:32:35 +08:00
Ronny Pfannschmidt
a986b8f945 Merge pull request #1525 from The-Compiler/traceback-item
Fix TracebackItem documentation in pytest.code
2016-04-20 09:38:03 +02:00
Florian Bruhin
c24e8e01b4 Fix TracebackItem documentation in pytest.code
The TracebackItem class does not exist, but it seems the docstrings refer to
TracebackEntry.
2016-04-20 08:35:17 +02:00
MengJueM
1a37035d71 Add test test_absolute_win32_path 2016-04-20 01:27:37 +08:00
TomV
99c4b6fdc3 issue 1496 - xfail with condition keyword 2016-04-19 18:12:37 +01:00
Meng Jue
dd2425675b Fix a small issue about shlex.split not working well with win32 path 2016-04-19 13:08:08 +08:00
Bruno Oliveira
6a3c943ce2 Merge pull request #1514 from The-Compiler/docfix
doc: Use ascii chars for file tree
2016-04-13 19:13:53 -03:00
Florian Bruhin
98430a17f2 doc: Use ascii chars for file tree
LaTeX doesn't like those particular unicode chars, so let's avoid them so the
PDF builds easily.
2016-04-13 23:38:58 +02:00
Bruno Oliveira
fe6e1b2059 Merge pull request #1506 from prusse-martin/fix-repr-tb-with-unicode
Fix `repr_traceback` to work with unicode errors
2016-04-09 13:03:55 -03:00
Martin Prusse
7ce5873da2 Perform a "unicode aware" check for maximum recursion depth error
Avoid errors `UnicodeErrosr`s due non maximum recursion depth errors
when checking for those errors.
2016-04-08 23:32:18 -03:00
Ronny Pfannschmidt
0eb80bcb5a Merge pull request #1504 from guettli/patch-1
Hudson -> Jenkins
2016-04-05 14:57:03 +02:00
Thomas Güttler
fb45f82840 Hudson -> Jenkins 2016-04-05 14:08:30 +02:00
Bruno Oliveira
5af5ba11d3 Merge pull request #1500 from tgoodlet/lstrip-keywordexpr
Always lstrip() keyword expression
2016-04-01 17:48:58 -03:00
Tyler Goodlet
053c052190 Always lstrip() keyword expression 2016-04-01 13:10:05 -04:00
Bruno Oliveira
56156bb119 Merge pull request #1490 from omarkohl/get_issues
Adapt get_issues.py script for GitHub (instead of Bitbucket)
2016-03-28 20:15:29 -03:00
Omar Kohl
e048315d9b Adapt get_issues.py script for GitHub (instead of Bitbucket) 2016-03-28 20:00:57 +02:00
Florian Bruhin
3155d0ca9c Merge pull request #1475 from nicoddemus/classifier-docs
Mention Pytest::Framework PyPI classifier on docs
2016-03-23 06:12:34 +01:00
Bruno Oliveira
53d319144d Mention Pytest::Framework PyPI classifier on docs 2016-03-22 20:42:52 -03:00
Ronny Pfannschmidt
6ddfd60ce7 Merge pull request #1472 from pquentin/master
Fix pytest.mark.skip mark when used in strict mode
2016-03-22 13:49:57 +01:00
Quentin Pradet
653a53226a Add strict parameter to xfail marker doc 2016-03-22 16:01:51 +04:00
Quentin Pradet
1fbd19b8cb Fix pytest.mark.skip mark when used in strict mode 2016-03-22 15:40:34 +04:00
Ronny Pfannschmidt
890c2fa555 Merge pull request #1469 from omarkohl/doc-fixes
Doc fixes
2016-03-20 20:48:36 +01:00
Omar Kohl
725290a8ab Add 'Minor doc fixes' to CHANGELOG 2016-03-20 20:39:31 +01:00
Omar Kohl
98c707561c Document --full-trace option and KeyboardInterrupt
fix #513
2016-03-20 20:07:16 +01:00
Omar Kohl
a341dddc74 Replace --fulltrace with --full-trace in doc
Since 'py.test --help' shows --full-trace as an option (and not --fulltrace)
even though both forms are accepted I assume --full-trace is the preferred form
and should therefore be used in the documentation.
2016-03-20 19:51:23 +01:00
Omar Kohl
16e49d96d1 Replace --traceconfig with --trace-config in doc
Since 'py.test --help' shows --trace-config as an option (and not
--traceconfig) even though both forms are accepted I assume --trace-config is
the preferred form and should therefore be used in the documentation.
2016-03-20 17:12:50 +01:00
Omar Kohl
ec62a3c9e4 Fix minor typo in 'writing plugins' doc 2016-03-20 17:01:04 +01:00
Omar Kohl
f70ed83479 Fix 'test grouping by fixture instances' doc example
The fin() function was never added as a finalizer and did therefore not print
anything in the captured output.

In general improve the output by making it more verbose/explicit and extend the
final explanation.
2016-03-20 15:53:32 +01:00
Omar Kohl
dff914cadd Add short explanation and link to yield fixtures in fixture doc 2016-03-20 14:10:20 +01:00
Omar Kohl
3135463573 No longer refer to the 'yield fixture mechanism' as experimental (doc)
The feature has been there for a long time and in the 2.7.1 release notes it
says:

> fixed docs to remove the notion that yield-fixtures are experimental.

Therefore this one place was probably just missed.
2016-03-20 13:54:56 +01:00
Omar Kohl
266b53dfc2 Add Jenkins xUnit Plugin link to doc 2016-03-20 13:39:10 +01:00
Omar Kohl
bdb3581a52 Fix minor mistake in test discovery doc
The example output shown was for Python3 not Python2.

Add Python2 output and rephrase for clarity.
2016-03-20 13:24:43 +01:00
Omar Kohl
27b62740e3 Fix minor mistake in usage doc (pkg instead of pypkg) 2016-03-20 12:47:17 +01:00
Bruno Oliveira
a70e92777f Bump version to 2.9.2.dev1 2016-03-18 18:24:24 -03:00
Bruno Oliveira
3c011c05db Merge pull request #1460 from nicoddemus/release-2.9.1
Release 2.9.1
2016-03-18 18:22:21 -03:00
nicoddemus
168daaa71f Regendocs 2016-03-17 17:13:41 -04:00
nicoddemus
43fc1b47c0 Add announce doc 2016-03-17 17:10:02 -04:00
nicoddemus
699f094b0c Finalize CHANGELOG for 2.9.1 2016-03-17 17:04:26 -04:00
nicoddemus
9bdf51fcc5 Bump version to 2.9.1 2016-03-17 17:03:46 -04:00
Ronny Pfannschmidt
1d23999033 Merge pull request #1458 from The-Compiler/talks
Update talks page
2016-03-17 09:09:35 +01:00
Florian Bruhin
1d35a03812 Update talks page 2016-03-17 06:37:49 +01:00
Ronny Pfannschmidt
d70596da91 Merge pull request #1452 from palaviv/specify-paramterized-node-in-command-line
Specify paramterized node in command line - fixes #649
2016-03-16 11:16:54 +01:00
palaviv
a1277aaf0e added TODO comment to remove the parametrized workaround 2016-03-15 23:42:25 +02:00
Ronny Pfannschmidt
19cec79363 Merge pull request #1451 from blueyed/fix-doc-pytest_load_initial_conftests
docs: sort arguments for pytest_load_initial_conftests
the change is purely cosmetic, but its nice to have them ordered by name
2016-03-14 14:14:22 +01:00
palaviv
981fcb2798 added CHANGELOG and AUTHORS entry 2016-03-13 23:47:41 +02:00
palaviv
1ee3d40dbe allow parametrized nodes to be specified from command line 2016-03-13 23:37:21 +02:00
Daniel Hahler
bdddc9c38b doc: fix argument order for pytest_load_initial_conftests 2016-03-12 17:35:27 +01:00
Bruno Oliveira
b46c7dddaa Fix 'check' in markdown for github templates 2016-03-11 18:22:15 -03:00
Ronny Pfannschmidt
4636bf6160 Merge pull request #1391 from malinoff/issue1342
LastFailed now creates .cache only when needed. Fixes #1342
2016-03-11 11:18:50 +01:00
Dmitry Malinovsky
52a5acda92 Use testscollected to make xdist happy 2016-03-11 14:48:17 +06:00
Florian Bruhin
2b0ad4630d Merge pull request #1443 from nicoddemus/improve-import-error-msg
Improve error message when a plugin fails to import
2016-03-11 06:10:58 +01:00
Bruno Oliveira
3ea987ef9d Copy over name and path attributes to the re-raised exception 2016-03-10 18:13:59 -03:00
Bruno Oliveira
7a186df271 Improve error message when a plugin fails to import 2016-03-08 19:18:13 -03:00
Ronny Pfannschmidt
6d4b14d7ee Merge pull request #1438 from Bachmann1234/issue-1437
Make a good faith effort to display a bytestring when one is provided…
2016-03-06 06:48:32 +01:00
Ronny Pfannschmidt
fd0010e6e9 Merge pull request #1439 from pytest-dev/fix-1178
Support pytest.fail with non-ascii characters

Fixes #1178
2016-03-06 06:42:35 +01:00
Matt Bachmann
8ce32b0795 When a regex pattern contains bytes instead of a string use escape_encode to turn it into a string before further processing. Thanks @nicoddemus for the review and tips! 2016-03-05 21:04:34 -05:00
Bruno Oliveira
5d4703852e Add test when using native strings with non-ascii chars 2016-03-05 19:34:15 -03:00
Ronny Pfannschmidt
9b51536a18 Merge pull request #1440 from nicoddemus/issue-578
Fix decoding issue while formatting SyntaxErrors during collection
2016-03-05 21:47:44 +01:00
Bruno Oliveira
d8403d793f Fix decoding issue while formatting SyntaxErrors during collection
This happens only in Python 2, as in Python 3 we receive
the "badline" in the exception is already properly encoded

Fix #578
2016-03-05 16:58:50 -03:00
Bruno Oliveira
24d3e01548 pytest.fail with non-ascii characters raises an internal pytest error
Fix #1178
2016-03-05 16:09:01 -03:00
Ronny Pfannschmidt
3884398055 Merge pull request #1433 from nicoddemus/issue-1427
Mention that pytest_addoption may be used from conftest files

fixes #1427
2016-03-04 10:48:43 +01:00
Bruno Oliveira
819e0ead44 Mention that pytest_addoption may be used from conftest files
fix #1427
2016-03-03 18:26:01 -03:00
Bruno Oliveira
c8ca1d12d7 Merge pull request #1432 from tomviner/issue469-junit-double-colon
issue469: junit parsing nodeid, add method test
2016-03-03 08:03:27 -03:00
TomV
3d2b7aeea5 issue469: junit parsing nodeid, add method test 2016-03-03 09:12:56 +00:00
Ronny Pfannschmidt
7aa7c6bbfd Merge pull request #1431 from tomviner/issue469-junit-double-colon
fix #469 - correctly mangle junit test names when double colons are in parameter id's
2016-03-03 08:45:30 +01:00
TomV
28530836c9 issue469: add fix junit double colon split issue 2016-03-02 22:18:57 +00:00
Bruno Oliveira
055f1dc9ea Merge pull request #1430 from vovanbo/patch-1
Reformat versions and header levels in CHANGELOG file
2016-03-02 16:23:55 -03:00
Vladimir Bolshakov
0cbd58e16a Reformat versions and header levels in CHANGELOG file 2016-03-02 22:07:30 +03:00
Bruno Oliveira
b7b863b7bf Merge pull request #1429 from Zearin/patch-1
Minor README tweaks (markup, phrasing)
2016-03-02 15:37:12 -03:00
Zearin
4bcc06362d Minor README tweaks (markup, phrasing) 2016-03-02 11:59:04 -05:00
Bruno Oliveira
316e39872a Add 2.9.1.dev1 to CHANGELOG 2016-03-01 18:55:53 -03:00
Bruno Oliveira
81af1c024a Bump version to 2.9.1.dev1 2016-03-01 18:46:17 -03:00
Bruno Oliveira
e656dbb602 Merge branch 'release-290' 2016-03-01 18:45:33 -03:00
Floris Bruynooghe
63b69326b8 Add company name to flub in sprint attendees 2016-03-01 10:17:31 +00:00
nicoddemus
19d05814d2 Fix reference to _CallOutcome in docs 2016-02-29 17:09:31 -05:00
nicoddemus
7d2b65813e Fix typo in CHANGELOG 2016-02-29 16:48:28 -05:00
nicoddemus
f82c03833f Regendocs 2016-02-29 15:52:56 -05:00
nicoddemus
486421fca2 Release announcement for 2.9.0 2016-02-29 15:47:23 -05:00
nicoddemus
2d7cfcd686 Finalize CHANGELOG 2016-02-29 15:42:15 -05:00
nicoddemus
623e786524 Bump version to 2.9.0 2016-02-29 15:39:00 -05:00
Bruno Oliveira
5c09b33150 Merge pull request #1423 from lukas-bednar/junit_properties
junit: allow multiple properties with same name
2016-02-29 15:35:03 -03:00
Lukas Bednar
7e758a9dc6 junit: allow multiple properties with same name
It might happen that test can be affected by two or more bugs.
I need to be able to track them all.
2016-02-29 16:00:26 +01:00
Bruno Oliveira
3445bdaca2 Merge pull request #1418 from hackebrot/update-sprint-info
Add company name sprint doc
2016-02-27 19:48:36 -03:00
Raphael Pierzina
89151b8c63 Add company name to @hackebrot in sprint attendees 2016-02-27 22:15:07 +00:00
Bruno Oliveira
4194ddd5b4 Merge pull request #1417 from hackebrot/fix-docs-on-traceback-styles
Fix docs on traceback styles
2016-02-27 18:28:57 -03:00
Raphael Pierzina
ab90043adc Update changelog with traceback style docs fix 2016-02-27 21:07:13 +00:00
Raphael Pierzina
a95fe3693b Add auto to tb styles (new default since v2.6) 2016-02-27 20:54:44 +00:00
Bruno Oliveira
b64aaac7ec Merge pull request #1416 from blubber/fix-mark-documentation
Fix a typo in the docstring for mark.MarkGenerator
2016-02-27 11:58:43 -03:00
Tiemo Kieft
424b46de1b Fix a typo in the docstring for mark.MarkGenerator 2016-02-27 14:15:42 +01:00
Florian Bruhin
c9927bb66f Merge pull request #1414 from rygwdn/indexerror
catch IndexError exceptions when getting exception source location
2016-02-26 18:53:10 +01:00
Florian Bruhin
cbb5d48fdd Merge pull request #1413 from nicoddemus/skip-if-parametrize
Fix skip/xfail markers in parametrized arguments
2016-02-26 18:41:37 +01:00
Ryan Wooden
d98d655094 Simplify IndexError test. 2016-02-26 08:25:49 -04:00
Anatoly Bubenkov
cf9a09e988 catch IndexError exceptions when getting exception source location 2016-02-26 08:18:12 -04:00
Bruno Oliveira
d9ede1bac2 Add test for unconditional skip with reason 2016-02-25 20:02:34 -03:00
Bruno Oliveira
5f90907509 Fix skip/xfail markers in parametrized arguments
Fix #1412
2016-02-25 19:13:09 -03:00
Bruno Oliveira
310bada6f5 Merge pull request #1409 from nicoddemus/builtin-fixtures-docs
Add reference to some builtin fixtures in "contents"
2016-02-25 15:46:33 -03:00
Bruno Oliveira
08b40396c9 Merge pull request #1400 from nicoddemus/contributing-improvements
Contributions to CONTRIBUTING.rst
2016-02-25 15:46:04 -03:00
holger krekel
a50209b29e add some more people, and preliminary company affiliations 2016-02-25 06:50:19 +01:00
Bruno Oliveira
a7b907d325 Add Raphael Pierzina to AUTHORS 2016-02-23 20:29:08 -03:00
Bruno Oliveira
c78a8b28dc Add reference to some builtin fixtures in "contents"
Fix #1408
2016-02-23 17:26:24 -03:00
Bruno Oliveira
3a6a0f1220 Merge pull request #1410 from milliams/master
Correct JUnit test invocation example
2016-02-23 08:06:21 -03:00
Matt Williams
fc4e240596 Correct JUnit test invocation example 2016-02-23 10:05:51 +00:00
holger krekel
5507a4d239 Merge pull request #1405 from nicoddemus/collection-atty
Display collect progress only when in a terminal
2016-02-21 08:17:35 +01:00
Bruno Oliveira
01479189a5 Merge pull request #1406 from lwm/fix-typo
fix typo
2016-02-20 20:49:58 -02:00
Luke Murphy
ddb7060535 fix typo 2016-02-20 23:44:55 +01:00
Bruno Oliveira
96a331e32f Display collect progress only when in a terminal
Fix #1397
2016-02-20 14:38:30 -02:00
Bruno Oliveira
688622f5cf Code review suggestions 2016-02-19 18:27:33 -02:00
Bruno Oliveira
fa7b0086a2 Move sections around (from easier to hardest) and added howto for repo transfers
#1282
2016-02-18 19:17:06 -02:00
Ronny Pfannschmidt
3874d53ee1 Merge pull request #1399 from nicoddemus/master
Add issue and PR GitHub templates
2016-02-18 21:38:50 +01:00
Bruno Oliveira
b76de91474 Add issue and PR GitHub templates 2016-02-18 18:26:38 -02:00
Bruno Oliveira
6940f82235 Merge pull request #1389 from nicoddemus/fix-strict-xfail
Fix bug in strict xfail: test was not being actually called
2016-02-17 17:58:49 -02:00
Bruno Oliveira
f6a2f779ae Merge pull request #1393 from hackebrot/add-license-section-to-homepage
Add license section to homepage
2016-02-17 17:53:49 -02:00
Raphael Pierzina
19536c9f05 Add full MIT license text to the docs section 2016-02-17 00:54:54 +00:00
Raphael Pierzina
e4c1b9c1c4 Add link to license page to index.rst 2016-02-17 00:47:49 +00:00
Raphael Pierzina
25aed0dca8 Add a license section to the docs toc 2016-02-16 19:42:16 +00:00
Raphael Pierzina
b2f837acd8 Add a reference to LICENSE file to README 2016-02-16 19:40:06 +00:00
Raphael Pierzina
3ef003f0ff Wrap lines at 79 characters 2016-02-16 19:14:55 +00:00
Raphael Pierzina
baabde76ae Insert copyright holders and year as mentioned in README 2016-02-16 19:13:39 +00:00
Raphael Pierzina
c805412d47 Grab official MIT license text from opensource.org 2016-02-16 19:12:22 +00:00
Dmitry Malinovsky
e4d361b093 LastFailed now creates .cache only when needed. Fixes #1342 2016-02-16 11:42:04 +06:00
Bruno Oliveira
bed56e504a Merge pull request #1390 from tomviner/bugfix/docs-typo-plugins
Fix usage of backticks in ReStructured Text files
2016-02-15 22:14:05 -02:00
TomV
3dd50d039d Fix rst syntax 2016-02-15 23:07:31 +00:00
Bruno Oliveira
0eeb466f11 Fix strict xfail: it should behave exactly like xfail when a test fails 2016-02-15 19:18:48 -02:00
Bruno Oliveira
ee88679c54 Fix bug in strict xfail: test was not being actually called 2016-02-15 18:43:45 -02:00
Bruno Oliveira
9af1f63ab6 Small typo in CHANGELOG 2016-02-14 23:36:17 -02:00
Bruno Oliveira
fb7bbf816c Merge branch 'strict-xpass' 2016-02-14 23:35:03 -02:00
Bruno Oliveira
9aae4b782b Add CHANGELOG entry for #1355 2016-02-14 23:31:31 -02:00
Ronny Pfannschmidt
1d190dc618 Merge pull request #1386 from nicoddemus/strict-xpass
Add a "strict" parameter to xfail
2016-02-15 02:29:43 +01:00
Bruno Oliveira
7823838e69 Add strict option to xfail, making tests which XPASS to actually fail the suite
Fix #1355
2016-02-14 20:52:27 -02:00
Bruno Oliveira
a965386b9e Add bool type to addini 2016-02-14 20:52:27 -02:00
Bruno Oliveira
6f0d90cd5a Merge pull request #1385 from RonnyPfannschmidt/release-process-update
update release process to ensure correct usage of base branches and p…
2016-02-14 00:54:25 -02:00
Ronny Pfannschmidt
48424a6bf6 update release process to ensure correct usage of base branches and propper finalization 2016-02-13 20:30:58 +01:00
holger krekel
2a8d58814a Merge pull request #1384 from hpk42/master
trying to settle the new layout
2016-02-13 16:56:44 +01:00
holger krekel
fa601de5c4 update participants 2016-02-13 16:55:16 +01:00
holger krekel
8284d14ec4 re-introduce topics 2016-02-13 16:54:06 +01:00
holger krekel
238dcd8bae some layout refinements 2016-02-13 14:30:10 +01:00
holger krekel
b95ff7104c Merge branch 'master' of https://github.com/pytest-dev/pytest
removed doc/en/announce/sprint-funding-2016.txt
because /doc/en/announnce/sprint2016.txt is the one which
we published.
2016-02-13 14:18:05 +01:00
holger krekel
03f8b50c8a Merge pull request #1383 from RonnyPfannschmidt/founding-header
give the founding header contrast
2016-02-13 14:10:44 +01:00
Ronny Pfannschmidt
dc7f76c276 adapt the header text based on a discussion with bruno 2016-02-13 13:40:59 +01:00
Ronny Pfannschmidt
20bd56f4b2 give the header contrast 2016-02-13 13:24:28 +01:00
Bruno Oliveira
89c75b2c91 Fix tox doc task 2016-02-13 02:09:25 -02:00
Bruno Oliveira
341bc33ff3 Merge pull request #1380 from biern/fix-utf8-explanation
Fix formatting utf-8 error explanation
2016-02-12 19:14:50 -02:00
Bruno Oliveira
fe10057c15 Merge pull request #1382 from RonnyPfannschmidt/founding-header
add links for the funding campaign in the header and sidebar
2016-02-12 18:45:32 -02:00
Ronny Pfannschmidt
48b62e4d89 add links for the funding campaign in the header and sidebar 2016-02-12 21:14:06 +01:00
Marcin Biernat
1b431d6644 fix formatting utf-8 error explanation 2016-02-12 20:28:06 +01:00
holger krekel
b1955c7f84 Merge pull request #1374 from RonnyPfannschmidt/fix-1366
fix issue #1366 by showing a note on the --fulltrace option
2016-02-11 10:45:12 +01:00
Ronny Pfannschmidt
bfa2fadac1 fix issue #1366 by showing a note on the --fulltrace option 2016-02-10 20:27:50 +01:00
holger krekel
48109b0e60 remove superflous file 2016-02-09 15:45:51 +01:00
holger krekel
fdce2306a7 fix names once again 2016-02-09 15:44:32 +01:00
Florian Bruhin
f00577f7c4 Also fix Brianna's name. 2016-02-09 15:37:45 +01:00
Florian Bruhin
569dbeb087 Fix names properly. 2016-02-09 15:36:59 +01:00
Bruno Oliveira
c6938221ab Merge branch 'issue1290-at-operator' 2016-02-06 09:38:45 -02:00
Bruno Oliveira
2b764a0e73 Fix CHANGELOG entry to new format 2016-02-06 09:37:13 -02:00
Bruno Oliveira
51694b8295 Merge branch 'master' into issue1290-at-operator 2016-02-06 09:31:42 -02:00
Bruno Oliveira
2e04771893 Merge pull request #1358 from RonnyPfannschmidt/features
merge features into master to begin the 2.9 line
2016-02-06 00:26:30 -02:00
TomV
7d107018e8 Fix #1290: Py3.5's @ operator/assertion rewriting. 2016-02-05 23:09:57 +00:00
holger krekel
05aad5c381 fix brianna's name once again 2016-02-05 14:12:35 +01:00
holger krekel
1e0088a949 fix EUR->USD 2016-02-05 14:11:30 +01:00
holger krekel
ba3b29e831 add a link to lieve indiegogo campaign in the pytest sprint page 2016-02-05 14:04:32 +01:00
holger krekel
6218e20e88 - preliminary sprint page, not yet linked through the header
- also removed plugins_index call from tox.ini "doc" env because it's not there
2016-02-05 11:34:40 +01:00
Ronny Pfannschmidt
190a52badb correct merge misstake in appveyor.yml 2016-02-05 01:18:11 +01:00
Ronny Pfannschmidt
7b2956e10b merge latest master into features as well 2016-02-05 00:13:48 +01:00
Ronny Pfannschmidt
de1a9f574c merge from master 2016-02-05 00:10:28 +01:00
Ronny Pfannschmidt
05a26d995b Merge pull request #1354 from gdyuldin/no_raise_message
Add expected exceptions to pytest.raises fail message
2016-02-03 13:54:24 +01:00
Georgy Dyuldin
79722ae89b Add expected exceptions to 'DID NOT RAISE' msg 2016-02-03 14:12:41 +03:00
Bruno Oliveira
b5dc7d9be1 Merge pull request #1350 from pytest-dev/appveyor-pypy
Test with pypy and enable coveralls in AppVeyor
2016-02-01 19:31:48 -02:00
Bruno Oliveira
30e61f2777 Test with pypy and enable coveralls in AppVeyor
* Install pypy using chocolatey
* Enable coveralls test environment in AppVeyor
* Suggest maintaining build matrix in .travis.yml by using "tox --listenvs"

 Fix #1254
2016-02-01 17:40:30 -02:00
holger krekel
58af604f82 Merge pull request #1346 from hpk42/master
merge sprint funding draft -- please feel free to commit directly.
2016-01-30 19:13:17 +01:00
holger krekel
c0024a723d fixed name 2016-01-30 19:12:41 +01:00
holger krekel
545bf0d5a1 a few refinements 2016-01-29 15:43:15 +01:00
holger krekel
70b5d5aee9 sprint funding draft 2016-01-29 14:52:07 +01:00
Bruno Oliveira
fde44f4f30 Merge branch 'better_flag_names' of https://github.com/MichaelAquilina/pytest into features 2016-01-27 20:02:50 -02:00
Bruno Oliveira
fa4d832507 Merge branch 'better_flag_names' into features 2016-01-27 19:57:23 -02:00
Bruno Oliveira
74a68b5ec6 Add CHANGELOG and docs for #1345 2016-01-27 19:57:11 -02:00
Michael Aquilina
e35ce98f89 Add explicit flag names for failed first and last failed 2016-01-27 21:28:38 +00:00
Bruno Oliveira
dd0062c177 Dummy change to test readthedocs hook 2016-01-27 08:59:26 -02:00
Bruno Oliveira
6c37a51f95 Fix version at the top of the CHANGELOG 2016-01-27 08:28:18 -02:00
Ronny Pfannschmidt
52ac6cd7a9 Merge pull request #1199 from nicoddemus/move-pycode-to-pytest
Copy py.code code to py.test
2016-01-26 23:29:09 +01:00
Bruno Oliveira
4825678e1a Add CHANGELOG entry for py.code merge 2016-01-26 20:24:08 -02:00
Bruno Oliveira
e43eaffd93 Remove unused import 2016-01-25 23:30:53 -02:00
Bruno Oliveira
9f85d4c952 mark test_comments as xfail on pypy
while migrating this code it was noticed that this test was failing even
on the original py repository, so it was decided to xfail it and investigate
later
2016-01-25 23:18:04 -02:00
Bruno Oliveira
7a6f902f6f Drop assertionnew and assertionold from _pytest._code 2016-01-25 23:18:04 -02:00
Bruno Oliveira
a912d3745b Moved py.code code over to py.test
Fix #103
2016-01-25 23:18:04 -02:00
Ronny Pfannschmidt
f23307b06d Merge release branch into mater 2016-01-25 00:01:43 +01:00
Ronny Pfannschmidt
6c3e6401d4 begin 2.8.8 cycle 2016-01-24 23:56:56 +01:00
Ronny Pfannschmidt
3315b3a12f finalize changelog for 2.8.7 2016-01-24 23:23:15 +01:00
Ronny Pfannschmidt
64d7d00218 Prepare 2.8.7 release 2016-01-24 17:59:48 +01:00
Ronny Pfannschmidt
7c747c97ec Merge pull request #1339 from RonnyPfannschmidt/fix-1338
Fix 1338
2016-01-24 17:51:00 +01:00
Ronny Pfannschmidt
56c5db6e12 add requests dependency to tox.ini to ensure all monkeypatch tests run 2016-01-24 12:30:38 +01:00
Ronny Pfannschmidt
2d05f831fe monkeypatch: unnest handling code
this avoid python3 nested exceptions
2016-01-24 12:28:14 +01:00
Ronny Pfannschmidt
cb6181255e changelog 2016-01-24 00:45:59 +01:00
Ronny Pfannschmidt
cd9e30b221 work around python 2/3 difference by using str(exception) 2016-01-24 00:40:27 +01:00
Ronny Pfannschmidt
d028fe1e66 remove unused import 2016-01-23 20:29:54 +01:00
Ronny Pfannschmidt
b825af2e66 pass trough annotated exceptions 2016-01-23 19:31:17 +01:00
Ronny Pfannschmidt
60e9698530 fix issue 1338 2016-01-23 19:12:51 +01:00
Ronny Pfannschmidt
9e6bb74d71 reformat monkeypatch core plugin/its tests 2016-01-23 19:12:51 +01:00
Bruno Oliveira
ed3c96ee58 Merge pull request #1336 from nicoddemus/merge-master-in-features
Merge master in features
2016-01-22 19:05:34 -02:00
Bruno Oliveira
199fcf93d4 Merge branch 'master' into 'features' 2016-01-22 18:32:45 -02:00
Bruno Oliveira
c8caa87759 Merge branch 'release-2.8.6' 2016-01-22 18:00:01 -02:00
Bruno Oliveira
b7de0401b8 Bump version to 2.8.7.dev1 2016-01-22 17:59:25 -02:00
Bruno Oliveira
01793ed8bc Use a full url to pytest logo on README
So it appers correctly in PyPI
2016-01-22 17:46:28 -02:00
Bruno Oliveira
82d00efa8d 2.8.6 release: version, CHANGELOG
Remove note about expected failing envs in tox, as tox now supports
skipping certain environments based on the platform.
2016-01-21 19:17:53 -02:00
Bruno Oliveira
61c569f960 Merge branch 'junit_stdout_err_on_error' 2016-01-20 19:06:50 -02:00
Bruno Oliveira
dd56d7b7fc Add CHANGELOG and AUTHORS entry for #1334 2016-01-20 19:00:52 -02:00
Bruno Oliveira
4de3d595c9 Merge pull request #1333 from lesteve/fix-practise-typo
Fix practise -> practice typo in documentation
2016-01-20 18:03:15 -02:00
Georgy Dyuldin
b28b3cc271 Add captured stdout to jUnit report on setup error 2016-01-20 20:13:01 +03:00
Loïc Estève
99072ea8c9 Fix practise -> practice typo in documentation 2016-01-20 16:35:27 +01:00
Ronny Pfannschmidt
11a7bcaaa5 Merge pull request #1322 from nicoddemus/missing-hooks-docs
Add a few missing hooks to the docs
2016-01-19 08:12:58 +01:00
Bruno Oliveira
0caee1a673 Add a few missing hooks to the docs 2016-01-18 19:27:35 -02:00
Bruno Oliveira
4c87a6aa09 Merge pull request #1330 from nicoddemus/fix-flakes
Fix flakes
2016-01-18 15:36:20 -02:00
Bruno Oliveira
7b13c4bec0 Fix flakes 2016-01-14 21:01:57 -02:00
Bruno Oliveira
aa8c352c10 Merge pull request #1323 from jeffwidman/patch-1
Change `input` to `test_input` in docs for clarity
2016-01-12 23:28:52 -02:00
Jeff Widman
ee75ecbda0 Change input to test_input in docs for clarity
Using `input` is confusing because it's also the name of a Python built-in function. So we use `test_input` instead. Fix #1321
2016-01-12 17:01:34 -08:00
Ronny Pfannschmidt
bc32e45bb6 Merge pull request #1314 from The-Compiler/ci-verbose
Always show full comparison output if on CI.
2016-01-11 10:05:15 +01:00
Florian Bruhin
3e5c9038ec Always show full comparison output if on CI.
When you don't get enough information with a test running on a CI, it's quite
frustrating, for various reasons:

- It's more likely to be a flaky test, so you might not be able to reproduce
  the failure.
- Passing -vv is quite bothersome (creating a temporary commit and reverting
  it)

For those reasons, if something goes wrong on CI, it's good to have as much
information as possible.
2016-01-11 08:45:04 +01:00
Ronny Pfannschmidt
b2c0864fbf Merge pull request #1318 from nicoddemus/doctest-unicode-error
Fix decode error in Python 2.7 when docstrings contain a non-ascii character
2016-01-11 08:37:24 +01:00
Florian Bruhin
a80efb038a Merge pull request #1320 from bionikspoon/patch-1
update docs plugin.rst typo
2016-01-11 07:37:46 +01:00
Manu Phatak
5b29f579c5 update docs plugin.rst typo 2016-01-11 00:11:46 -06:00
Bruno Oliveira
808cb8e3ad Merge branch 'stdout-pdb-capture' 2016-01-09 11:46:13 -02:00
Bruno Oliveira
8727503dd4 Add CHANGELOG entry for #1223 2016-01-09 11:45:56 -02:00
foxx
f46de68804 Fixes bug with stdout/stderr capture on pdb 2016-01-09 12:04:26 +00:00
Bruno Oliveira
3c19cfcd9a Fix decode error in Python 2.7 when docstrings contain a non-ascii character
Fix #628
2016-01-08 23:10:02 -02:00
Bruno Oliveira
b8784c28c9 Merge branch 'master' into 'features' 2016-01-08 21:51:34 -02:00
Bruno Oliveira
29b05c8391 Merge pull request #1309 from k4rtik/patch-1
Correct platform name osx -> darwin
2016-01-05 23:29:33 -02:00
Kartik Singhal
26c835eea5 Correct platform name osx -> darwin 2016-01-05 18:43:03 -05:00
Florian Bruhin
eebf5c1d2c Merge pull request #1304 from nicoddemus/rst-changelog
Changelog now in rst format (2.9.0 and onward) and add rst-lint check
2016-01-05 14:05:53 -08:00
Bruno Oliveira
3daa0756eb Add CHANGELOG.rst to MANIFEST and small format fix 2016-01-05 20:01:41 -02:00
Bruno Oliveira
3e34db50fb Rename "flakes" testenv to "linting" as requested in review 2016-01-05 18:18:29 -02:00
Ronny Pfannschmidt
e2603d7050 Merge pull request #1306 from nicoddemus/summary-warnings
pytest warnings emitted during ``pytest_terminal_summary`` are now properly displayed.
2016-01-04 11:16:33 +01:00
Bruno Oliveira
369d9ecaa5 pytest warnings emitted during `pytest_terminal_summary` are now properly displayed.
Fix #1305
2016-01-04 00:07:45 -02:00
Bruno Oliveira
02dd6df6e6 Changelog now in rst format (2.9.0 and onward) and add rst-lint check
Fix #1274
2016-01-03 23:09:24 -02:00
Ronny Pfannschmidt
6c170201d6 Merge branch 'master' into features 2016-01-02 23:56:01 +01:00
Ronny Pfannschmidt
bf4de4bd68 Merge pull request #1294 from nicoddemus/doctest-bytes-literals
Doctest bytes literals
2016-01-01 18:18:08 +01:00
Bruno Oliveira
80d6d94635 Merge pull request #1298 from aktech/patch-1
Update copyright in README
2016-01-01 12:06:21 -02:00
AMiT Kumar
63cba1ed0d Update copyright in README 2016-01-01 18:52:56 +05:30
Ronny Pfannschmidt
71ab6b8b05 Merge pull request #1295 from nicoddemus/monkeypatch-perf
Make monkeypatch calls O(1)
2015-12-30 22:21:16 +01:00
Ronny Pfannschmidt
c367180ab2 Merge pull request #1297 from nicoddemus/multi-doctest-glob
Multiple --doctest-glob arguments
2015-12-30 22:18:46 +01:00
Bruno Oliveira
1bdf71730a Complement #1255 by adding tests and docs
Fix #1242
2015-12-30 18:24:59 -02:00
jab
0ea8dc0d40 make --doctest-glob multi-allowed 2015-12-30 17:32:14 -02:00
Bruno Oliveira
638b3f5e39 Make monkeypatch calls O(1)
Fix #1292
2015-12-29 22:04:35 -02:00
Bruno Oliveira
309ecf7ab3 Rename "BugFixes/Adjustments" to just "Bug Fixes" as commented elsewhere 2015-12-29 21:09:15 -02:00
Bruno Oliveira
719d63085d Add CHANGELOG entry for ALLOW_BYTES doctest option 2015-12-29 21:08:25 -02:00
Bruno Oliveira
5a5b732fe1 Add docs for ALLOW_BYTES doctest option
Fix #1287
2015-12-29 21:05:11 -02:00
Bruno Oliveira
a0edbb75a4 Implement ALLOW_BYTES doctest option
Fix #1287
2015-12-29 20:55:19 -02:00
Bruno Oliveira
0ef73ed3e0 Small typo on README 2015-12-28 18:56:14 -02:00
Bruno Oliveira
7cfb750d7f Small wording on README 2015-12-28 18:55:20 -02:00
Bruno Oliveira
70f72229c6 Merge pull request #1291 from ulope/master
Make monkeypatch differentiate ImportError sources
2015-12-28 13:18:25 -02:00
Ulrich Petri
2e02579437 Add CHANGELOG entry for #900 fix 2015-12-28 10:53:38 +01:00
Ulrich Petri
8d49abb0d1 Make monkeypatch differentiate ImportError sources
Previously `monkeypatch` assumed that any `ImportError` was caused by
a mistake in the specified import path. However this assumption is false 
in case the import target itself causes an `ImportError`.

Fixes: #900
2015-12-27 22:44:23 +01:00
Ronny Pfannschmidt
c5631b6567 Merge pull request #1284 from nicoddemus/pyversions-badge
Add badge of supported python versions to README
2015-12-27 16:05:43 +01:00
Ronny Pfannschmidt
522224ee7c Merge pull request #1285 from nicoddemus/readme-changes-logo
Add logo and some readme changes
2015-12-27 15:47:29 +01:00
Bruno Oliveira
015e8e574a Merge pull request #1289 from WoLpH/patch-1
Updated out of date link
2015-12-27 07:22:41 -02:00
Rick van Hattem
87ff7ee232 Updated out of date link 2015-12-27 02:35:02 +01:00
Bruno Oliveira
b5490b289d Add logo and update readme
* Show pytest logo
* Show assertion introspection in the example
* Use separate sections for change-log, docs, license, etc.
2015-12-26 12:47:22 -02:00
Ronny Pfannschmidt
46039f8687 Merge pull request #1286 from nicoddemus/changelog-sections
Separate 2.9.0 CHANGELOG into sections
2015-12-26 11:09:45 +01:00
Bruno Oliveira
6b25fb4d64 Separate 2.9.0 CHANGELOG into sections
Fix #1275
2015-12-25 20:44:03 -02:00
Bruno Oliveira
99a5067edb Merge branch 'pyreadline-workaround' 2015-12-25 18:03:25 -02:00
Bruno Oliveira
5afb61ad26 Fix trailing white-space 2015-12-25 17:51:55 -02:00
Bruno Oliveira
fbfab6778c Give proper thanks for the PR in the CHANGELOG 2015-12-25 17:48:03 -02:00
Bruno Oliveira
57bc14caa0 Apply readline workaround during initial conftest loading 2015-12-25 17:46:19 -02:00
Bruno Oliveira
df3f21afb6 Add badge of supported python versions to README 2015-12-25 16:28:50 -02:00
Ronny Pfannschmidt
1a87bb2416 Merge pull request #1280 from nicoddemus/del-index-from-overview
Remove "index" entry from overview.rst
2015-12-25 07:33:15 +01:00
Erik M. Bray
6e170a4a1c * Moved workaround to its own function, mostly for the sake of adding
a more descriptive docstring (the workaround itself is just to import
  readline earlier).  I removed the conditional on targetfd from the
  workaround since it doesn't really matter.

* Added # noqa marker.

* Added changelog entry, and self to authors.
2015-12-24 16:43:34 -05:00
Erik M. Bray
924a9667e1 Make sure readline has been imported before duping any stdio handles--otherwise pyreadline fails to connect to the correct handle for the console's stdout/in. 2015-12-24 14:56:57 -05:00
Bruno Oliveira
319f6310f8 Remove "index" entry from overview.rst
"index" is already displayed in the main docs, repeating it here breaks the reading flow
2015-12-24 16:08:43 -02:00
Bruno Oliveira
cd3a441304 Merge pull request #1277 from peterdemin/patch-1
doc typo
2015-12-24 15:57:01 -02:00
Peter Demin
8180165229 doc typo 2015-12-24 08:56:54 -05:00
Ronny Pfannschmidt
ec5a429c77 junitxml tests: extend with extra items 2015-12-17 22:30:27 +01:00
Ronny Pfannschmidt
713069ebd4 implement review comments for #1266 2015-12-17 22:27:01 +01:00
Ronny Pfannschmidt
f7af08d309 Merge pull request #1268 from nicoddemus/merge-newinterpret
Merge newinterpret.py into reinterpret.py
2015-12-17 07:10:47 +01:00
Bruno Oliveira
943099ddd1 Merge newinterpret into reinterpret.py 2015-12-16 18:31:43 -02:00
Bruno Oliveira
379562107e Merge pull request #1267 from nicoddemus/remove-pre-2.6
Remove pre-2.6 support code and docs (including obsolete oldinterpret module)
2015-12-16 18:30:53 -02:00
Bruno Oliveira
25c392196f Mention #1226 in the CHANGELOG 2015-12-16 16:48:49 -02:00
Bruno Oliveira
ec597e81a4 Remove references to python pre-2.6 from docs 2015-12-16 16:16:22 -02:00
Bruno Oliveira
81588d7f63 Remove obsolete "oldinterpret" module 2015-12-16 16:16:22 -02:00
Bruno Oliveira
af893aab26 Remove code related to support python <= 2.5
Fix #1226
2015-12-16 16:16:22 -02:00
Ronny Pfannschmidt
8bf7e7cc4b fixes #1259 - take finalized nodes out of the mapping
this allows double node id usage for file based items
2015-12-16 19:09:44 +01:00
Ronny Pfannschmidt
7b20288c2b Merge pull request #1264 from nicoddemus/exception-interact-deprecated
Remove experimental status from pytest_exception_interact
2015-12-16 18:32:58 +01:00
Bruno Oliveira
2b2bec6b97 Remove experimental status from pytest_exception_interact 2015-12-16 14:29:39 -02:00
Ronny Pfannschmidt
fcc20d4181 Merge pull request #1257 from nicoddemus/merge-master-into-features
Merge master into features after 2.8.5
2015-12-12 08:15:55 +01:00
Bruno Oliveira
6ac31088c5 Merge branch 'master' into merge-master-into-features 2015-12-11 23:13:50 -02:00
Bruno Oliveira
7eea6b3b02 Add 2.8.6.dev1 to CHANGELOG 2015-12-11 23:11:36 -02:00
Bruno Oliveira
e87facfb22 Bump master to 2.8.6.dev1 2015-12-11 23:07:30 -02:00
Bruno Oliveira
855b115dab Remove plugins_index generation from HOWTORELEASE 2015-12-11 22:35:44 -02:00
Bruno Oliveira
4263b8b407 Restore regen-doc result 2015-12-11 22:34:16 -02:00
Florian Bruhin
7d150c20cf Regenerate 2.8.5 docs. 2015-12-11 22:28:23 -02:00
Bruno Oliveira
a124163425 Prepare for 2.8.5: bump version, CHANGELOG, announce 2015-12-11 19:20:07 -02:00
Ronny Pfannschmidt
2382546112 Merge pull request #1252 from nicoddemus/optimize-app-veyor
Optimize appveyor build
2015-12-11 16:10:45 +01:00
Bruno Oliveira
946bb08da5 Tyding up the CHANGELOG 2015-12-11 00:36:26 -02:00
Bruno Oliveira
85d1f0404a Merge pull request #1243 from aselus-hub/master
Update python.py
2015-12-11 00:33:46 -02:00
Bruno Oliveira
1d60f61ba8 Optimize appveyor build
- AppVeyor does not run matrix builds in parallel, so creating a matrix with the intent to
  speed up the build will actually result in longer build times, as each matrix will execute
  in a brand new VM.
- tox does not detect 64 bit installations in AppVeyor, it always installs interpreters
  from C:\PythonX.Y, so there's no point to have 64bit builds
- No need for the auxiliary script "install.ps1" because all python versions we are interested
  in are already pre-installed in AppVeyor
2015-12-11 00:20:20 -02:00
aselus-hub
ad05cbe6da fixed meesage. 2015-12-10 15:39:31 -08:00
aselus-hub
1216a27b44 added docstrign to inection collection test. 2015-12-10 15:19:08 -08:00
aselus-hub
2b2240e904 added myself to authors, added changelog entry. 2015-12-10 15:15:09 -08:00
aselus-hub
74f7efd2a3 added line comparison that is pytest-sugar agnostic. 2015-12-10 15:10:55 -08:00
aselus-hub
34db8aed34 added verification that test actually passed. 2015-12-10 15:02:57 -08:00
Bruno Oliveira
926c6028bb Merge pull request #1250 from nicoddemus/merge-master-into-features
Merge master into features
2015-12-10 20:56:10 -02:00
aselus-hub
af54e09759 nit: fixed newline 2015-12-10 14:46:51 -08:00
aselus-hub
dfaeefd692 added test to verify injection. 2015-12-10 14:45:36 -08:00
Bruno Oliveira
86b6ce5042 Merge remote-tracking branch 'upstream/master' into merge-master-into-features 2015-12-10 19:41:14 -02:00
Bruno Oliveira
8f880e1625 Fix CHANGELOG merge 2015-12-10 19:40:45 -02:00
Bruno Oliveira
46c85bc352 Merge pull request #1249 from nicoddemus/remove-plugins-index
Remove plugins_index from the docs
2015-12-10 19:11:32 -02:00
Bruno Oliveira
8b61a332ba Merge remote-tracking branch 'bukzor/features-merge-master' into features 2015-12-10 19:03:55 -02:00
Bruno Oliveira
139c97930b Remove plugins_index from the docs
Fix #1229
2015-12-10 19:00:01 -02:00
Ronny Pfannschmidt
9cfee82f9b Merge pull request #1247 from alex/patch-1
Use https for images in readme
2015-12-10 00:02:47 +01:00
Alex Gaynor
c0a5f3df10 Use https for images in readme 2015-12-09 18:01:36 -05:00
Bruno Oliveira
8220c05b01 Merge pull request #1245 from RonnyPfannschmidt/howtorelease
add a reminder of the features branch merge to HOWTORELEASE.rst
2015-12-09 18:34:41 -02:00
Ronny Pfannschmidt
d0f5f6676b add a reminder of the features branch merge to HOWTORELEASE.rst 2015-12-09 21:20:54 +01:00
aselus-hub
ec02f694ef Update python.py
updated dictionary itteration to create a list for generation, so that tests can be added in the generator functions under python3. This works fine as-is in python2 because python 2 already creates a list, whereas python3 returns an itterator. Forcing a list format for the return fixes python3 to work the same way as python2
2015-12-09 11:32:19 -08:00
Ronny Pfannschmidt
6351e2846c Merge pull request #1232 from codewarrior0/report-passing-option
Add -rp and -rP options to report passing tests.
2015-12-09 06:57:42 +01:00
Ronny Pfannschmidt
1c70827f33 Merge pull request #1241 from nicoddemus/fix-deprecated-call-args
Fix deprecated_call regression introduced in 2.8.4
2015-12-09 06:48:43 +01:00
David Vierra
ccfd962170 Add "no -rP" case to test_pass_output_reporting 2015-12-08 17:33:03 -10:00
David Vierra
b417d7cb79 Add tests to test_terminal.py for -rp and -rP 2015-12-08 15:54:23 -10:00
Bruno Oliveira
1c46462991 Fix deprecated_call regression introduced in 2.8.4
Fix #1238
2015-12-08 22:40:05 -02:00
Buck Golemon
5ccb7b1ced update test_recwarn to new style 2015-12-08 11:08:33 -08:00
Buck Golemon
eabf2f9091 Merge branch 'master' into features
Conflicts:
	AUTHORS
	_pytest/__init__.py
	_pytest/hookspec.py
	_pytest/recwarn.py
	testing/test_recwarn.py
2015-12-07 14:28:59 -08:00
David Vierra
1db4cbcc9f Update AUTHORS and CHANGELOG 2015-12-07 12:17:30 -10:00
David Vierra
fbac936596 Add -rp and -rP options to report passing tests.
-rP is an alternative to `-s` for viewing the output of passing tests.
This causes the captured stdout/stderr of passing tests to be output in
the same way as that of failing tests.

-rp adds a simple one-line-per-test summary for passing tests.

Neither option is included by -ra.

Additional changes to `pytest_capturelog` and `pytest_catchlog` are
needed for this option to also output captured logs: They must be
changed to use `rep.sections.add` instead of `rep.longrepr.addsection`,
and to add these additional sections even if the test passes, since
passing tests don't seem to have a `longrepr` at report time.
2015-12-07 11:32:56 -10:00
Ronny Pfannschmidt
ffa572531a Merge pull request #1091 from RonnyPfannschmidt/fix-1074
fix issue 1074 and clean up junitxml
2015-12-07 22:13:16 +01:00
Ronny Pfannschmidt
fde2a6f5fd add changelog entry 2015-12-07 21:58:34 +01:00
Ronny Pfannschmidt
7b7737bf96 handle duplicate test ids via collection and xdist each reporting 2015-12-07 21:54:25 +01:00
Ronny Pfannschmidt
04e9ae75c8 add xfailing test for double test id failure 2015-12-07 21:54:25 +01:00
Ronny Pfannschmidt
9ea7826427 Junitxml: correct node reporter attribute names 2015-12-07 21:54:25 +01:00
Ronny Pfannschmidt
09cc45b0c5 junitxml: correct docstring of make_properties_node 2015-12-07 21:54:25 +01:00
Ronny Pfannschmidt
0aa54101c9 junitxml: follow Bruno's attribute/method naming hint from the review 2015-12-07 21:54:25 +01:00
Ronny Pfannschmidt
5eef6a2821 junitxml: fix python3 compat of the tests 2015-12-07 21:54:25 +01:00
Ronny Pfannschmidt
518c88f149 finalize nodereporters by throwing away the intermediate xml nodes, fixes issue #1074 2015-12-07 21:54:25 +01:00
Ronny Pfannschmidt
5f5a7995b9 reintroduce junitxml report order and debug cleanups 2015-12-07 21:54:24 +01:00
Ronny Pfannschmidt
0528e5b45f junitxml: intermediate, move testcase generation to NodeReporter 2015-12-07 21:54:24 +01:00
Ronny Pfannschmidt
9b04958303 junitxml: keep track of custom property insert order
+ review: should we allow the same key multile times
2015-12-07 21:54:24 +01:00
Ronny Pfannschmidt
faed54d6c7 junitxml: use node.warn to ensure fslocations 2015-12-07 21:54:24 +01:00
Ronny Pfannschmidt
1f609f96e6 junitxml: introduce nodereporter and track durations/properties there 2015-12-07 21:54:24 +01:00
Ronny Pfannschmidt
0664ae137c junitxml: remove debug print 2015-12-07 21:54:24 +01:00
Ronny Pfannschmidt
d0107c898e junitxml restrucutre stat generation - use node tags 2015-12-07 21:54:24 +01:00
Ronny Pfannschmidt
9128fec4c4 junitxml: simplify the api used for testing junitml 2015-12-07 21:54:24 +01:00
Ronny Pfannschmidt
80bcf8d624 junitxml: simplify tests by creating a api wrapper 2015-12-07 21:54:24 +01:00
Ronny Pfannschmidt
b8df5446c0 junitxml: yapf clean the tests 2015-12-07 21:54:24 +01:00
Ronny Pfannschmidt
2a31df072b junitxml: reverse the if/else logic for failure appending 2015-12-07 21:54:24 +01:00
Ronny Pfannschmidt
02f5defd89 yapf junitxml 2015-12-07 21:54:24 +01:00
Bruno Oliveira
efb5332023 Merge pull request #1186 from RonnyPfannschmidt/fix-manifest
fix #1185 - ensure MANIFEST.in exactly matches what should go to a sdist
2015-12-07 13:48:39 -02:00
Ronny Pfannschmidt
b9908cc036 Merge pull request #1228 from pytest-dev/pytest-2.8.4
finish the release process of pytest 2.8.4
2015-12-06 20:32:52 +01:00
Ronny Pfannschmidt
c727860241 bump to 2.8.5.dev 2015-12-06 20:31:14 +01:00
Ronny Pfannschmidt
8c17c7cd12 correct copy&paste error in the release announcement version number 2015-12-06 17:44:08 +01:00
Ronny Pfannschmidt
b7459b8a64 finish release announcement 2015-12-06 16:46:44 +01:00
Ronny Pfannschmidt
b920f09a95 doc regen for release 2.8.4 2015-12-06 16:14:23 +01:00
Ronny Pfannschmidt
a3353c49fd prepare release 2.8.4 - changelog updates + version bump 2015-12-06 16:13:55 +01:00
Ronny Pfannschmidt
a4a12b8356 Merge pull request #1227 from nicoddemus/warn-deprecated-call
Add a note about how DeprecationWarning and PendingDeprecationWarning are treated differently
2015-12-06 10:52:58 +01:00
Ronny Pfannschmidt
13ae2fe28b adopt review comment of #1186 2015-12-06 10:24:24 +01:00
Ronny Pfannschmidt
141a463fed fix #1185 - ensure MANIFEST.in exactly matches what should go to a sdist 2015-12-06 10:24:24 +01:00
Ronny Pfannschmidt
f508a52ca9 Merge pull request #1225 from pytest-dev/skipping-docs-inconsistency
Fix inconsistency in skipif example
2015-12-05 20:44:01 +01:00
Bruno Oliveira
b48a02fdb1 Add a note about how DeprecationWarning and PendingDeprecationWarning are treated differently
Fix #1026
2015-12-05 13:53:58 -02:00
Bruno Oliveira
382efc6363 Fix same inconsistency in next example 2015-12-05 00:43:06 -02:00
Bruno Oliveira
1bed514eb6 Fix inconsistency in skipif example
Fix #1224
2015-12-05 00:35:31 -02:00
Bruno Oliveira
41f19796e8 Merge pull request #1212 from nicoddemus/goodpractices
Goodpractises docs reorganization/review
2015-12-05 00:27:28 -02:00
Ronny Pfannschmidt
427e6c3b4d Merge pull request #1222 from nicoddemus/pastebin-unicode
Fix #1222 - pastebin when captured output contains non-ascii characters
2015-12-04 07:10:49 +01:00
Bruno Oliveira
14bc3c4009 Fix pastebin when captured output contains non-ascii characters
Fix #1219
2015-12-03 20:07:18 -02:00
Florian Bruhin
7e063eec08 Merge pull request #1221 from jeffwidman/patch-1
Fix typo: previosuly --> previously
2015-12-03 21:25:40 +01:00
Jeff Widman
61934ae82d Fix typo: previosuly --> previously 2015-12-03 11:56:18 -08:00
Bruno Oliveira
8c74bb0d25 Improve description on how pytest starts test collection in goodpractises 2015-12-03 01:01:34 -02:00
Bruno Oliveira
bb4771cedf Remove promise about documenting how to create a zipped pytest 2015-12-03 00:13:16 -02:00
Bruno Oliveira
9475cd3fb8 Replace "--assertmode=off" by "--assert=plain"
Fix #1214
2015-12-02 18:39:32 -02:00
Bruno Oliveira
464e16deca Removed incorrect note about genscript requiring wheels 2015-12-02 18:33:53 -02:00
Bruno Oliveira
d9b78f2a95 Remove reference to distutils 2015-12-02 18:30:12 -02:00
Ronny Pfannschmidt
7232b45f25 Merge pull request #1213 from nicoddemus/pastebin-py3
merge Pastebin py3 support

 also closes #1202 and fixes #1198
2015-12-02 09:08:50 +01:00
Bruno Oliveira
a54e4e64cd Merge remote-tracking branch 'upstream/master' into pastebin-py3 2015-12-01 23:51:14 -02:00
Bruno Oliveira
edfb567091 Add #1198 fix to CHANGELOG 2015-12-01 23:37:16 -02:00
Bruno Oliveira
6a2ebddc7c Decode urlopen response in pastebin
Fix #1198
2015-12-01 23:33:37 -02:00
Bruno Oliveira
5040dde0c5 Fix genscript deprecation version and document reasons for such 2015-12-01 23:09:15 -02:00
Bruno Oliveira
095abfd035 Fix formatting errors 2015-12-01 22:52:22 -02:00
Bruno Oliveira
69ef0ab189 Merged virtual env into the Tox section
Nowadays virtualenv use is widespread so we don't need to
devote a how-to section in pytest's docs
2015-12-01 22:47:36 -02:00
Bruno Oliveira
d851a8fd07 Fixed some formatting 2015-12-01 22:43:11 -02:00
Bruno Oliveira
0704fcacd7 Removed setuptools/genscript session 2015-12-01 22:32:07 -02:00
Bruno Oliveira
4f17d56ecb Move deprecated genscript method to the bottom of the document 2015-12-01 22:30:08 -02:00
Bruno Oliveira
b1f6dc23da Moved "conventions for Python test discovery to the top" 2015-12-01 22:28:20 -02:00
Bruno Oliveira
c6f90c25e3 Remove finalize_options override from goodpractices
This is not required in latest versions of `setuptools`, and
`self.test_args` is a read-only attribute in some of the
versions of the 18.X series.

Fix #1134
2015-12-01 22:20:40 -02:00
Florian Bruhin
f0e5cb362e Merge pull request #1211 from nchammas/patch-1
Update xdist newhooks.py link; BitBucket -> GitHub
2015-12-01 20:15:40 +01:00
Nicholas Chammas
c7cf4adfd0 Update xdist link; BitBucket -> GitHub
xdist is now hosted on GitHub.
2015-12-01 13:20:55 -05:00
Ronny Pfannschmidt
def543924b Merge pull request #1209 from jeffwidman/master
Fix spelling: explicitely --> explicitly
2015-12-01 10:51:26 +01:00
Jeff Widman
6be6798cdf Fix spelling: explicitely --> explicitly 2015-12-01 01:41:47 -08:00
Ronny Pfannschmidt
ce4eb51ee0 Merge pull request #1208 from The-Compiler/no-tests-run-spelling
Fix spelling mistake in #1207.
2015-11-30 17:40:26 +01:00
Florian Bruhin
0d2668017d Fix spelling mistake in #1207. 2015-11-30 17:33:34 +01:00
Ronny Pfannschmidt
e7e4860ded Merge pull request #1207 from The-Compiler/no-tests-run
Fix terminal output if no tests were run.
2015-11-30 17:26:47 +01:00
Florian Bruhin
aba55a0fb2 Fix terminal output if no tests were run.
Before:
====  in 0.00 seconds ====

After:
==== no tests run in 0.00 seconds ====
2015-11-30 17:24:40 +01:00
Ronny Pfannschmidt
b5d65e5139 Merge pull request #1206 from The-Compiler/collect-getattr
Don't collect classes with truthy __getattr__.
2015-11-30 17:23:47 +01:00
Ronny Pfannschmidt
3a3f0f5c56 Merge pull request #1205 from The-Compiler/reportinfo-getattr
Fix getting line number with nasty __getattr__. fixes #1204
2015-11-30 17:23:05 +01:00
Florian Bruhin
ba9146c131 Don't collect classes with truthy __getattr__.
When we have a metaclass which returns something truthy (like a method) in its
__getattr__, we collected the class because pytest thought its __test__
attribute was set to True.

We can work around this to some degree by assuming __test__ will always be set
to an explicit True if that's what the user has intended, and if it's something
other than that, this is probably a mistake.

Fixes #1204.
2015-11-30 16:41:13 +01:00
Florian Bruhin
c790f7475e Fix getting line number with nasty __getattr__.
When an object has a custom __getattr__ which always returns a non-int, we
tried to get compat_co_firstlineno from it and checked it was a integer, which
caused an exception if such a class is mistakenly collected.

If we still mistakenly collect such a class (which is likely to be something
other than a test), we now skip it with a warning (because it probably has an
__init__) instead of producing an error.

See #1204.
2015-11-30 16:13:15 +01:00
mehdy
f9b1e39b8a fix #1198 - decoding monkeypatched data to unicode 2015-11-29 19:42:50 +03:30
mehdy
81ad1689b9 fix #1198 - removed docoding the result 2015-11-29 19:20:37 +03:30
mehdy
44f60ba141 fixed #1198 issue by encoding the unicode parameters to bytes and decoding the
bytes response to unicode
2015-11-29 18:27:05 +03:30
Ronny Pfannschmidt
bced5a3f81 Merge pull request #1200 from macrotim/master
Merge #1200 - correct various documentation spelling misstakes
2015-11-28 08:00:24 +01:00
Tim Chan
a8d7e513f4 Fixed docs 2015-11-27 22:46:45 -08:00
Ronny Pfannschmidt
604a021a2a Merge pull request #1196 from nicoddemus/deprecated-call-1190
Make deprecated_call() use monkey-patching again , fixes #1190
2015-11-27 22:09:09 +01:00
Bruno Oliveira
603d81ef2f deprecated_call now uses monkey patching strategy to capture warnings
similar to what we had in 2.7, with a few enhancements

Fix #1190
2015-11-26 16:48:58 -02:00
Bruno Oliveira
6378cdf7a9 Restored 2.7 implentation of deprecated_call 2015-11-26 15:54:57 -02:00
Bruno Oliveira
84eacf3e3c Small changelog formatting fix 2015-11-26 14:37:55 -02:00
Bruno Oliveira
320c95ca43 Fix formatting in HOWTORELEASE.rst 2015-11-24 12:05:41 -02:00
Bruno Oliveira
b20803f0a6 Mention pytest_enter_pdb in the docs 2015-11-23 18:00:02 -02:00
Ronny Pfannschmidt
df767cca8f Merge pull request #1188 from nicoddemus/pytest_enter_pdb
Pass pytest config object to pytest_enter_pdb
2015-11-23 20:55:33 +01:00
Bruno Oliveira
b3166a538c Pass pytest's config object to pytest_enter_pdb 2015-11-23 14:42:21 -02:00
Bruno Oliveira
1f148a93ec Mention pytest_enter_pdb in the docs 2015-11-23 13:02:15 -02:00
Ronny Pfannschmidt
af46ffe021 bump to 2.8.4.dev 2015-11-19 22:01:34 +01:00
Ronny Pfannschmidt
e4a000bb00 refer more explicit to osx el cap in the changelogs 2015-11-18 17:57:41 +01:00
Ronny Pfannschmidt
d04505553e update authors list for 2.8.3 and add a log command to the howto 2015-11-18 17:55:18 +01:00
Ronny Pfannschmidt
2b5c46b2ab correct drop in version in announcement 2015-11-18 17:48:35 +01:00
Ronny Pfannschmidt
063f90f0d2 run regendoc 2015-11-18 17:47:17 +01:00
Ronny Pfannschmidt
6096cae3dd release announcements for 2.8.3 2015-11-18 17:38:31 +01:00
Ronny Pfannschmidt
3dc57d99f2 Changelog groming and release preps for 2.8.3 2015-11-18 17:34:30 +01:00
Florian Bruhin
3cc5a4ecb0 Merge pull request #1175 from gabehollombe/add-pyconsg2015-talk
Add 'Improve your testing with Pytest and Mock' video from PyCon SG 2015
2015-11-11 06:16:46 +01:00
Gabe Hollombe
ac0b7b9803 Add 'Improve your testing with Pytest and Mock' video from PyCon SG 2015 2015-11-11 11:28:59 +08:00
Bruno Oliveira
a1d226b751 Merge pull request #1168 from hackebrot/write-docs-on-ignore-cli-option
Write docs on ignore cli option
2015-11-07 12:38:21 -02:00
Raphael Pierzina
d667259e31 Change pytest ignore invocation as suggested by @nicoddemus 2015-11-04 23:45:29 +01:00
Bruno Oliveira
98668c943d Merge pull request #1171 from nicoddemus/fix-1169
Allow @unittest.skip decorators in Python 2.7
2015-11-04 20:27:00 -02:00
Bruno Oliveira
a7a470b56f Give credit where it is due 2015-11-04 19:55:05 -02:00
Bruno Oliveira
7d6edb9ca5 Fix unittest.skip decorator test and separate the fix into a different, self-doc function 2015-11-04 19:54:18 -02:00
Lee Kamentsky
313050b15b Suggested edits by Bruno.
Moved fix to TestCaseFunction.setup. Added myself to AUTHORS and added entry to CHANGELOG
2015-11-04 15:30:16 -05:00
Lee Kamentsky
1833547936 Added test for issue #1169
(I undid my fix, checked for failure, redid my fix and it passes)
2015-11-04 14:24:22 -05:00
LeeKamentsky
8de2c035e2 Fixes #1169
The unittest.skip decorator runs @functools.wraps on self._testcase. functools.wraps expects a "__name__" attribute and this patch adds one. It might not be the correct fix, but it works for me.
2015-11-04 13:52:40 -05:00
Raphael Pierzina
a2d07bfa93 Add pytest invocation along with result 2015-11-04 15:34:18 +01:00
Raphael Pierzina
586fdbcbbd Add example tests directory structure 2015-11-04 15:00:37 +01:00
Raphael Pierzina
04079f9ae5 Start doc section on `--ignore` in pytestcollection.rst 2015-11-04 14:49:02 +01:00
Bruno Oliveira
af2d391903 Merge pull request #1166 from Toilal/pytest-runner
Use pytest-runner for setuptools integration
2015-11-02 12:50:21 -02:00
Toilal
8abf85e96c Use pytest-runner for setuptools integration 2015-11-02 12:22:40 +01:00
Ronny Pfannschmidt
9013cb1b4c Merge pull request #1144 from The-Compiler/elcapitan-sip
Handle EPERM when writing rewritten bytecode.
2015-11-01 08:20:00 +01:00
Florian Bruhin
2eb345f9b7 Merge pull request #1159 from zoidbergwill/patch-1
Fix typo in parametrize.rst
2015-10-30 09:02:29 +01:00
William Martin Stewart
1376d75fbe Fix typo in parametrize.rst 2015-10-30 09:58:41 +02:00
Bruno Oliveira
4fe7cca44e Merge pull request #1157 from gabrielcnr/master
Color highlight on the name of the failed tests
2015-10-28 17:36:47 -02:00
gabriel.reis
a83d5c0f08 Updated entries on CHANGELOG and AUTHORS. Ref: #1157 2015-10-28 19:35:04 +00:00
Gabriel Reis
f3f61fb5d1 Color highlight on the name of the failed tests 2015-10-28 08:12:57 +00:00
Bruno Oliveira
8e54d07c40 Fix link in FAQ about parametrizing tests
Fix #1154
2015-10-26 22:46:35 -02:00
Florian Bruhin
bd36e00174 Merge pull request #1153 from b-jazz/patch-2
Update usage.rst
2015-10-24 15:41:24 +02:00
Ronny Pfannschmidt
da6b1557f0 Merge pull request #1152 from b-jazz/patch-1
Update CONTRIBUTING.rst
2015-10-24 12:00:33 +02:00
b-jazz
f174026f53 Update usage.rst
Remove double "the" in usage comments
2015-10-24 02:36:02 -07:00
b-jazz
390e1c93ed Update CONTRIBUTING.rst
Clean up the grammar of one of the lines.
2015-10-24 02:21:22 -07:00
Bruno Oliveira
8f31a1a64b Merge pull request #1147 from jayvdb/remove-extrainit
Remove commented out extrainit
2015-10-20 10:25:54 -02:00
John Vandenberg
9a04879129 Remove commented out extrainit
Unused since 2012, and the docstring syntax is incorrect with
four quotes.
2015-10-20 21:08:21 +11:00
Bruno Oliveira
7469b5591e Update plugins_index.rst 2015-10-17 13:37:38 -03:00
Bruno Oliveira
75932a92d0 Make pytest-version mandatory
Fix #1118
2015-10-17 13:37:01 -03:00
Bruno Oliveira
cf56f59f58 Merge pull request #1145 from htgoebel/patch-1
Add link to unittest2pytest
2015-10-17 13:16:55 -03:00
Hartmut Goebel
4fba20e544 Add link to unittest2pytest
Closes #991.
2015-10-17 18:14:52 +02:00
Florian Bruhin
2956627b8b Handle EPERM when writing rewritten bytecode.
Fixes #1143.
2015-10-17 11:55:55 +02:00
Ronny Pfannschmidt
3404d2a99b Merge pull request #1142 from nicoddemus/issue-1133-str-relto
Fix situation where a traceback entry "path" returns a str object
2015-10-17 08:58:48 +02:00
Bruno Oliveira
311b0a9683 Fix situation where a traceback entry "path" returns a str object
Fix #1133
2015-10-16 20:18:14 -03:00
Ronny Pfannschmidt
3b11995dbe Merge pull request #1053 from mbirtwell/issue_331_failure_for_test_with_no_name
Prevent non-function callables from being collected
2015-10-13 23:35:32 +02:00
Michael Birtwell
a3bda59a30 collection: Prevent non-function callables from being collected
Fixes issue 331
previously to this change the collection code would issue a warning for
when ever it encountered a variable that looked like a test but wasn't a
function saying that it wouldn't collect it because it wasn't a function.
This fixes the logic so that if that warning is issued it really isn't
collected.
However previously special cases existed to support tests that were
created using functools.wraps and functools.partial. So the condition for
issuing that warning has been updated to take that in to account

Also try the old way of detecting functions just for proper integration
with mock.path in python 2.7 the get_real_func returned the unbound method
2015-10-12 22:23:18 +01:00
Bruno Oliveira
0c21533cc5 Merge pull request #1130 from The-Compiler/pytest_doc_fix
Use a string for pytest_plugins in docs.
2015-10-12 10:58:39 -03:00
Florian Bruhin
dadf03baea Use a string for pytest_plugins in docs. 2015-10-12 06:57:43 +02:00
Ronny Pfannschmidt
1525cc78f6 Merge pull request #1127 from michael-k/indentation
[doc] Fixed indentation
2015-10-10 14:22:18 +02:00
Ronny Pfannschmidt
db077555f6 Merge pull request #1102 from nicoddemus/doctest-fixtures-fix
Fix autouse fixtures and doctest modules
2015-10-10 14:21:53 +02:00
Michael K
37e96c9335 [doc] Fixed indentation 2015-10-10 13:29:22 +02:00
Bruno Oliveira
b052becba9 Merge pull request #1121 from tomviner/issue1035-getattr
Issue1035 getattr
2015-10-09 16:45:52 -03:00
TomV
88c8dd96f9 issue1035 Override inspect.isclass for python 2.6 2015-10-09 19:44:47 +01:00
Bruno Oliveira
cee828130c Merge pull request #1124 from bukzor/unit-test-config-fromdictargs
tests of (and fixes for) Config.fromdictargs (2)
2015-10-09 15:10:40 -03:00
Buck Golemon
67236d6de3 strengthen the ini assertion 2015-10-09 09:58:12 -07:00
Buck Golemon
470e4f9e91 changelog entry 2015-10-09 09:58:12 -07:00
Buck Golemon
0e55a8793f all tests pass 2015-10-09 09:58:12 -07:00
Buck Golemon
49d46a0059 an ugly patch to fix all but the most important part =/ 2015-10-08 10:44:58 -07:00
Buck Golemon
616d8251f3 unit tests of Config.fromdictargs. currently failing 2015-10-08 10:44:58 -07:00
TomV
707226298a issue1035 add test for classes setting __getattr__ 2015-10-08 09:08:32 +01:00
Florian Bruhin
1f6988bdec Add empty 2.8.3.dev section to CHANGELOG. 2015-10-07 22:47:22 +02:00
Florian Bruhin
ec74a8deb8 Fix contents.rst.
This was broken in 787c866191.
2015-10-07 18:10:13 +02:00
Florian Bruhin
b5b53b6aec start 2.8.3 development 2015-10-07 17:56:05 +02:00
Florian Bruhin
7c529e0afe Merge branch '2.8.2-release' 2015-10-07 17:51:34 +02:00
Florian Bruhin
b955473533 Add 2.8.2 release annoucement. 2015-10-06 22:37:59 +02:00
Florian Bruhin
2026ce0ed2 Update plugin_index. 2015-10-06 22:35:16 +02:00
Florian Bruhin
42937d4bb6 Prepare 2.8.2 release. 2015-10-06 21:06:25 +02:00
Bruno Oliveira
cfd259ae6f Merge pull request #1113 from demianbrecht/typo_fix
typo fix, changed position of context manager notes
2015-10-05 17:21:59 -03:00
Demian Brecht
493530ec6d typo fix, changed position of context manager notes 2015-10-05 12:13:25 -07:00
Florian Bruhin
4dea0892cb Merge pull request #1108 from nicoddemus/plugins_index
plugins_index was still generating ".txt" files, so it was effectively being ignored
2015-10-05 09:37:31 -07:00
Bruno Oliveira
a14c77aeba Fix problems when mixing autouse fixtures and doctest modules
The main problem was that previously DoctestModule was setting
up its fixtures during collection, instead of letting
each DoctestItem make its own fixture setup

Fix #1100
Fix #1057
2015-10-03 16:07:14 -03:00
Bruno Oliveira
a24126effb Add credit for pytest.mark.skip to the CHANGELOG 2015-10-03 14:15:22 -03:00
Michael Aquilina
8984177448 TestXFail also shouldnt explicitly inherit from object 2015-10-03 17:12:44 +01:00
Michael Aquilina
750442909c Add unconditional skip entry to CHANGELOG 2015-10-03 17:04:06 +01:00
Michael Aquilina
df874db817 Update default reason to "unconditional skip" 2015-10-03 17:02:18 +01:00
Michael Aquilina
00d0c74657 Update reason in test to prevent confusing with test_no_reason 2015-10-03 17:01:21 +01:00
Michael Aquilina
122980ecad Add myself to AUTHORS 2015-10-03 17:01:11 +01:00
Michael Aquilina
fc0bd9412c Test that "unconditional skip" is the default reason if none given 2015-10-03 17:00:16 +01:00
Michael Aquilina
5ff9a0ff54 Remove redundant comments 2015-10-03 16:59:27 +01:00
Michael Aquilina
25d74a5919 Dont explicitly inherit from object 2015-10-03 16:55:04 +01:00
Michael Aquilina
213dbe7a5f newlines 2015-10-03 16:42:15 +01:00
Michael Aquilina
9e57954b03 First argument in pytest.mark.skip is a reason 2015-10-03 16:42:15 +01:00
Michael Aquilina
1b5aa2868d Check no reason displayed if none specified 2015-10-03 16:42:15 +01:00
Michael Aquilina
eee24138b0 Fix failing test 2015-10-03 16:42:15 +01:00
Michael Aquilina
04545f8a54 classes inherit from object 2015-10-03 16:42:15 +01:00
Michael Aquilina
d1628944a6 Update skippings tests for better coverage 2015-10-03 16:42:15 +01:00
Michael Aquilina
abc27f56fc Update skipping.rst with correct version marker 2015-10-03 16:42:15 +01:00
Michael Aquilina
771aef9ddb Add a test_skip_class test 2015-10-03 16:42:15 +01:00
Michael Aquilina
dc7153e33c Spelling and grammar fixes 2015-10-03 16:42:15 +01:00
Michael Aquilina
5ec08d3081 Delete trailing whitespace 2015-10-03 16:42:15 +01:00
Michael Aquilina
61b8443723 Update docs with new skip marker 2015-10-03 16:42:14 +01:00
Michael Aquilina
ad0b8e31b8 Fix case where skip is assigned to as an attribute directly 2015-10-03 16:42:14 +01:00
Michael Aquilina
f144666f8b Work towards test coverage of mark.skip 2015-10-03 16:42:14 +01:00
Michael Aquilina
4e94135d36 Remove incorrect use of pytest.mark.skip 2015-10-03 16:42:14 +01:00
Michael Aquilina
b71add27da Add MarkEvaluator for skip 2015-10-03 16:42:14 +01:00
Bruno Oliveira
9deab6c0fd Add a reminder on HOWTORELEASE to update plugins_index 2015-10-02 19:44:00 -03:00
Bruno Oliveira
f9e9413f52 plugins_index was still generating ".txt" files, so it was effectively being ignored
Fix #1105
2015-10-02 19:21:55 -03:00
Bruno Oliveira
5171d167ce Move "note" to the end of the main text
Fix #1097
2015-10-02 18:14:17 -03:00
Demian Brecht
2981bece55 pyflakes fix 2015-10-02 13:03:43 -07:00
Demian Brecht
fd211bf490 Add a note about usage of the context manager
Addesses #1097
2015-10-02 12:40:58 -07:00
Ronny Pfannschmidt
9263b3a051 Merge pull request #1096 from hackebrot/update-docs-skip-with-cli-option
Update docs skip with cli option
2015-10-01 18:31:55 +02:00
holger krekel
88209c6ac6 Merge pull request #1092 from nicoddemus/param-ids-fix
Param ids fix
2015-10-01 15:09:11 +02:00
Ronny Pfannschmidt
688f955f5e Merge pull request #1058 from nicoddemus/doc-how-disable-cache
Add docs on how to disable cache provider
2015-10-01 13:06:02 +02:00
Bruno Oliveira
f3cee8f0b5 Merge remote-tracking branch 'upstream/master' into param-ids-fix 2015-10-01 07:58:55 -03:00
Ronny Pfannschmidt
b7fd3f0031 Merge pull request #1093 from nicoddemus/relto-bug
Fix internal error when filtering tracebacks where one entry was generated by an exec() statement
Fixes #995
2015-10-01 06:00:40 +02:00
Bruno Oliveira
d1e00f6e19 Detect dynamic code explicitly in filter_traceback 2015-09-30 17:32:49 -03:00
Bruno Oliveira
c9480c5b8b Move imports outside _escape_bytes as suggested in review 2015-09-30 17:02:19 -03:00
Raphael Pierzina
4829eac1e1 Use a variable for the skipif marker as suggested by @nicoddemus 2015-09-30 21:53:34 +02:00
Raphael Pierzina
a10da0e540 Implement skipif marker for slow test based on pytest.config 2015-09-30 19:41:47 +02:00
Raphael Pierzina
7ac8a88a05 Remove pytest_runtest_setup from conftest.py 2015-09-30 19:25:57 +02:00
Bruno Oliveira
639ae0cfe0 Merge pull request #1095 from ionelmc/fix-hook-docs
Correct hook examples in docs.
2015-09-30 11:28:43 -03:00
Ionel Cristian Mărieș
c8f5a40fd9 Edit examples again to use hookimpl. 2015-09-30 16:58:12 +03:00
Ionel Cristian Mărieș
f4f23e8e09 Correct hook examples in docs. 2015-09-30 16:12:33 +03:00
Florian Bruhin
dc9ad12182 Merge pull request #1094 from nicoddemus/cache-fixture-docstring-fix
fix docstring for cache fixture regarding ``/`` on keys
2015-09-30 06:02:38 +02:00
Bruno Oliveira
a808e09204 fix docstring for cache fixture regarding `/` on keys 2015-09-30 00:27:52 -03:00
Bruno Oliveira
6ae16eba36 add entries for #1085 and #1087 to the CHANGELOG 2015-09-29 22:37:11 -03:00
Bruno Oliveira
11f100813e Fix internal error when filtering tracebacks where one entry was generated by an exec() statement
Fix #995
2015-09-29 22:29:43 -03:00
Bruno Oliveira
b64470443f Fix SystemError when using unicode_escape on Python 3
Fix #1087
2015-09-29 18:20:30 -03:00
Bruno Oliveira
8633c4cefd Fix encoding errors for parametrized tests with unicode parameters in py2
Fix #1085
2015-09-29 17:57:49 -03:00
Florian Bruhin
e9240f7eee Merge pull request #1090 from RonnyPfannschmidt/fix-flaky-getusertest
fix flaky get_user fallback tests
2015-09-29 21:35:45 +02:00
Ronny Pfannschmidt
04deea3c6d fix flaky get_user fallback tests 2015-09-29 21:00:12 +02:00
Bruno Oliveira
6ca8923ec7 Merge pull request #1088 from hpk42/docfix
remove unclear todo that irritates in the online docs
2015-09-29 13:26:54 -03:00
holger krekel
e3562be530 remove unclear todo that irritates in the online docs 2015-09-29 18:23:44 +02:00
holger krekel
cb58eaa611 Merge remote-tracking branch 'upstream/master' into features
Conflicts:
	_pytest/__init__.py
	testing/test_recwarn.py
2015-09-29 15:56:41 +02:00
holger krekel
c5fd42b699 start 2.8.2 development 2015-09-29 15:32:33 +02:00
holger krekel
afdbb6b17a Merge pull request #1084 from hpk42/branch-281
2.8.1 release
2015-09-29 15:19:07 +02:00
holger krekel
a0076460db avoid pullrequest picture and fix bugfix/feature target info.
I could not get the pullrequest picture to be included correctly
in both latex and html sphinx builders due to the "include" directive
from doc/en/contributing.rst
2015-09-29 13:48:38 +02:00
holger krekel
787c866191 hide some parts from generating latexpdf 2015-09-29 13:37:30 +02:00
holger krekel
95245b935c - fix a flaky test on py35-xdist by calling
importlib.invalidate_caches()

- bump version to 2.8.1

- regen docs

- amend changelog, authors
2015-09-29 13:10:59 +02:00
holger krekel
f61f39efdd Merge pull request #1052 from The-Compiler/no-std-prefix
Don't hardcode 'std' for captured stdout/stderr.
2015-09-29 09:19:40 +02:00
holger krekel
a73d517bee Merge pull request #1080 from vodik/master
fix #1034: Add missing nodeid on pytest_logwarning call in addhook.  Thanks Simon Gomizelj for the PR.
2015-09-29 09:07:43 +02:00
Simon Gomizelj
b3727438d6 Add missing nodeid on pytest_logwarning call in addhook.
Otherwise KeyError: 'nodeid' gets thrown, killing pytest. This may fix
issue 1034, but the details of it may be caused by something similar
somewhere else.
2015-09-28 20:23:54 -04:00
Bruno Oliveira
b184f391c6 Show known environment variables in py.test --help 2015-09-28 18:34:16 -03:00
Bruno Oliveira
5f860181b6 Remove print left by accident 2015-09-28 18:25:20 -03:00
Bruno Oliveira
6caa7083db Moved more detailed options on how to disable a plugin to plugins.rst 2015-09-28 18:23:08 -03:00
holger krekel
839909f3f6 Merge pull request #1081 from hunse/fix-pendingdep
`deprecated_call` detects pending warnings again
2015-09-28 19:05:44 +02:00
Eric Hunsberger
4194c9cce2 Check deprecated_call specific to deprecation
`deprecated_call` used to accept any warning. As of #897, it
is now specific to DeprecationWarnings, and another commit in
this PR extends this to PendingDeprecationWarnings. This commit
makes sure this stays the case.
2015-09-28 12:35:24 -04:00
Eric Hunsberger
e8261e0c77 deprecated_call detects pending warnings again
`deprecated_call` now looks for PendingDeprecationWarnings,
as it did previously but was broken by #897. Fixes #1037.

Also added a test so this does not happen again.
2015-09-28 12:11:52 -04:00
Ronny Pfannschmidt
22e1f4946e Merge pull request #1079 from hpk42/issue1073
fix issue #1073 -- shortcut plugin hook lookup if the attrname is not prefixed with pytest_.
2015-09-28 16:15:42 +02:00
holger krekel
971ebcbd77 simplify by removing the single-call "exclude_pytest_names" function 2015-09-28 15:43:55 +02:00
holger krekel
03aca9ea79 add changelog entry for fix #1073 2015-09-28 14:17:58 +02:00
holger krekel
1c0ffc5caf seems like pypy's callable builtin calls __getattr__ so we do the check
later.
2015-09-28 14:02:30 +02:00
holger krekel
4e3a807733 fix issue #1073 -- shortcut plugin hook lookup if the attrname is not
prefixed with pytest_.
2015-09-28 13:34:28 +02:00
holger krekel
d29084ec2c add changelog note for fix #704 2015-09-28 11:45:51 +02:00
holger krekel
c5faa00ace Merge pull request #1077 from nicoddemus/pluggy-0.3.1
Vendor pluggy-0.3.1
2015-09-28 11:44:07 +02:00
Bruno Oliveira
910b25d416 Vendor pluggy-0.3.1
Fix #704
2015-09-27 16:04:29 -03:00
holger krekel
0c05ca1fd5 Merge branch 'master' into features 2015-09-26 10:03:42 +02:00
Ronny Pfannschmidt
cb4b5bd684 Merge pull request #1072 from nicoddemus/use-more-py35-tox
Use py35 as preferred py3 interpreter for special test environments
2015-09-26 09:26:16 +02:00
Ronny Pfannschmidt
842aa5746f Merge pull request #1069 from nicoddemus/docs-contents-reorg
Reorganize "Contents" page
2015-09-26 09:18:42 +02:00
Ronny Pfannschmidt
c2c2451788 Merge pull request #1071 from nicoddemus/xml-xdist
Wrong xml report when used with pytest-xdist
2015-09-26 09:05:25 +02:00
Bruno Oliveira
cd7ca3de68 Test py27-xdist and py35 environments on AppVeyor 2015-09-26 03:46:03 -03:00
Bruno Oliveira
d52d0251b2 Test py35 on AppVeyor 2015-09-26 03:44:12 -03:00
Bruno Oliveira
cdead1a991 Update travis with new tox environments 2015-09-26 03:43:37 -03:00
Bruno Oliveira
031a20699c Use py35 as preferred py3 interpreter for special test environments 2015-09-26 03:37:21 -03:00
Bruno Oliveira
e4b18ea5c3 Update CHANGELOG 2015-09-26 03:28:30 -03:00
Bruno Oliveira
b72c3310bc Add note on docs about record_xml_property not working with xdist 2015-09-26 03:24:07 -03:00
Bruno Oliveira
748da0e5d7 Fix xml generation when used with pytest-xdist
pytest_runtest_logreport must be careful to not depend on setup/call/teardown
being called sequentially in that order, as xdist will call them as they are reported
from the slaves

Fix #1064
2015-09-26 03:21:24 -03:00
Bruno Oliveira
bc501a28af Merge pull request #1070 from oleg-alexandrov/master
Documentation clarification
2015-09-26 03:16:44 -03:00
Oleg Alexandrov
9d2d2d17af Clarify documentation
Fix doc markup
2015-09-25 21:46:06 -07:00
Bruno Oliveira
b403395cdb Reorganize "Contents" page
Change the organization in a more logical way to a newcomer
2015-09-26 01:09:50 -03:00
Bruno Oliveira
fbce3bb48f Small format fixes in HOWTORELEASE 2015-09-25 23:10:35 -03:00
Ronny Pfannschmidt
bf23a0f4b8 Merge pull request #1065 from The-Compiler/doc-fixes
Doc fixes
2015-09-25 19:18:09 +02:00
Florian Bruhin
448ec8b740 Mark sources correctly in parametrize.rst.
Sphinx didn't pick this up as monospace text.
2015-09-25 18:43:33 +02:00
Florian Bruhin
272f987b0c Fix test_time in parametrize doc.
The source was indented with a mixture of tabs and spaces, causing syntax
errors when executed.
2015-09-25 18:42:06 +02:00
Florian Bruhin
0ebac22bb2 Regenerate fixture list doc with cache docstring. 2015-09-25 18:41:07 +02:00
Bruno Oliveira
c55e42f856 Merge pull request #1063 from timstaley/docs-module-level-fixtures-pytestmark
Docs: Fixtures page - clarify usage of module-level pytestmark.
2015-09-25 12:42:35 -03:00
Tim Staley
eb2caa554c Docs: Fixtures page - clarify usage of module-level pytestmark.
It certainly wasn't clear to me that the variable assigned by
pytest.mark.usefixtures should also be called pytestmark,
so I was confused when::

  foo = pytest.mark.usefixtures('fixture_bar')

wasn't behaving as expected; correct usage is of course::

  pytestmark = pytest.mark.usefixtures('fixture_bar')

I assume this is by design, otherwise that's a separate issue, at
least current behaviour should be documented.
2015-09-25 14:57:08 +01:00
Florian Bruhin
7c5d2ea81d Merge pull request #1046 from RonnyPfannschmidt/docs
fix documentation regeneration, kill Makefile
2015-09-25 08:35:18 +02:00
Bruno Oliveira
b09d3724a0 Add docs on how to disable cache provider 2015-09-23 19:15:44 -03:00
holger krekel
4867554eec Merge branch 'master' into features 2015-09-23 16:42:42 +02:00
Ronny Pfannschmidt
acb6b186f2 Merge pull request #1056 from icemac/patch-1
Remove duplicate line.
2015-09-23 09:10:20 +02:00
Ronny Pfannschmidt
a3fdcd9b17 Merge pull request #1031 from pytest-dev/unmarshable-parametrize
Parametrized values containing non-ascii bytes break cache
2015-09-23 09:03:51 +02:00
Ronny Pfannschmidt
e5c76cb22d Merge pull request #1054 from nicoddemus/cache-fixture-docstring
Add docstring to cache fixture
2015-09-23 09:00:01 +02:00
Michael Howitz
730114d088 Remove duplicate line. 2015-09-23 08:53:13 +02:00
Florian Bruhin
c0c685a5de Merge pull request #1055 from nicoddemus/fix-help-pytest-warnings
(w)warnings -> (w)pytest-warnings in "-r chars" help
2015-09-23 06:53:46 +02:00
Bruno Oliveira
79ebca3f30 (w)warnings -> (w)pytest-warnings in "-r chars" help 2015-09-23 01:09:09 -03:00
Bruno Oliveira
25d2cc4604 Add docstring to cache fixture
Fixes #1049
2015-09-22 23:46:05 -03:00
Bruno Oliveira
716fa97fa1 Update CHANGELOG 2015-09-22 23:21:36 -03:00
Bruno Oliveira
e1063678f1 escape bytes when creating ids for parametrized values 2015-09-22 23:18:09 -03:00
Bruno Oliveira
661495e5c5 Write failing test for parametrized tests with unmarshable parameters
Related to #1030; committing directly to pytest repository to
get feedback from others on how to proceed.
2015-09-22 23:18:07 -03:00
Bruno Oliveira
9d1ae0a149 Merge pull request #1048 from RonnyPfannschmidt/pytest-1029
fixes #1029 by handling access errors for cache dirs as pytest warning
2015-09-22 19:04:04 -03:00
Ronny Pfannschmidt
7f776fe19a skip chmod using cache access warning tests on windows 2015-09-22 20:49:11 +02:00
Florian Bruhin
c10f483b9f Don't hardcode 'std' for captured stdout/stderr.
This will make Item.add_report_sect more usable for plugins.
See https://github.com/eisensheng/pytest-catchlog/pull/7
2015-09-22 20:29:16 +02:00
Ronny Pfannschmidt
ea9a491fb3 add an acceptance test for cache write errors 2015-09-22 20:24:37 +02:00
holger krekel
ca460e11e6 Merge pull request #1051 from Akasurde/issue_1027
Added minor documentation change in python.py
2015-09-22 17:09:25 +02:00
Ronny Pfannschmidt
c3588b545f generate docs on python3.4 2015-09-22 16:52:35 +02:00
Abhijeet Kasurde
dc1ce51ac2 Added minor documentation change
Signed-off-by: Abhijeet Kasurde <akasurde@redhat.com>
2015-09-22 20:15:49 +05:30
Ronny Pfannschmidt
6e51918353 update changelog 2015-09-22 16:33:27 +02:00
Bruno Oliveira
1d2afada83 Minor typo in CHANGELOG 2015-09-22 11:30:00 -03:00
Ronny Pfannschmidt
29f4da93d4 handle access errors when writing cache files silently as pytest warning, fixes #1039 2015-09-22 16:28:19 +02:00
Bruno Oliveira
5acbd7bc86 Fix git checkout command in CONTRIBUTING.rst 2015-09-22 10:54:37 -03:00
Ronny Pfannschmidt
36924b59bd Merge branch '653-deprecated-context-manager' of https://github.com/chiller/pytest into features 2015-09-22 15:14:06 +02:00
Ronny Pfannschmidt
5ff75a41ea fix erroring doc example 2015-09-22 15:05:56 +02:00
Ronny Pfannschmidt
89badfec0c update regendoc, substitutes PYTHON_PREFIX 2015-09-22 14:48:04 +02:00
Ronny Pfannschmidt
662d755974 fix documentation gegeneration, kill Makefile 2015-09-22 14:22:37 +02:00
holger krekel
7c088d1104 remove nonsense line 2015-09-22 11:23:24 +02:00
holger krekel
48df2f6842 Merge branch 'master' into features 2015-09-22 11:23:05 +02:00
holger krekel
bf9b94595c fix changelog 2015-09-22 11:18:19 +02:00
holger krekel
41d61ed221 Merge pull request #1023 from RonnyPfannschmidt/fix-877
Fix 877
2015-09-22 11:17:02 +02:00
holger krekel
79587d4b70 Merge branch 'master' into features
Conflicts:
	_pytest/__init__.py
2015-09-22 11:06:21 +02:00
holger krekel
8a4517fd17 re-add 2.8.x changelog so that MASTER can be merged into features wrt
to the changelog
2015-09-22 11:05:19 +02:00
holger krekel
97f7815feb also change pytest version to target 2.9.0 2015-09-22 11:01:33 +02:00
holger krekel
d8fbb0b8e3 start features branch 2015-09-22 11:00:55 +02:00
holger krekel
e2f72ffed8 start bugfix changelog on master 2015-09-22 10:59:23 +02:00
Florian Bruhin
79c2abf531 Merge pull request #1038 from hpk42/newbranching
adapt contributing to new versioning and to python3.5 as latest.
2015-09-22 10:55:30 +02:00
holger krekel
a5ff345f7b fix another occurence of "macro" to "major" 2015-09-22 10:45:55 +02:00
holger krekel
f19ba6c2b1 fix naming 2015-09-22 10:29:12 +02:00
holger krekel
745cd26850 Merge branch 'master' into newbranching 2015-09-22 10:22:05 +02:00
holger krekel
71c5883d52 update according to discussion on pytest-dev and in PR 2015-09-22 10:21:01 +02:00
Galaczi Endre
9f77a8507e removed mutation of global state, changed filter addition in WarningsRecorder 2015-09-21 19:01:23 +01:00
Galaczi Endre
beaa8e55bd Fixes #653 use deprecated_call as context_manager 2015-09-21 19:01:23 +01:00
Floris Bruynooghe
c30eafa254 Update changelog for merged PR. 2015-09-21 14:59:48 +01:00
Floris Bruynooghe
7c3be72ac7 Merge pull request #1042 from Bjwebb/issue411
Add __eq__ method to assertion comparison example
2015-09-21 14:57:59 +01:00
Ben Webb
622e64320a Add __eq__ method to assertion comparison example
This makes the example make more sense, because now Foo(1) == Foo(1) is
true. Fixes #411
2015-09-21 14:23:26 +01:00
holger krekel
0efcfeed17 adapt contributing to new versioning and to python3.5 as latest. 2015-09-21 14:04:30 +02:00
Floris Bruynooghe
65de43e67d Mention fix for issue 766 in changelog
Fixes issue #766.
2015-09-21 12:21:24 +01:00
Floris Bruynooghe
cd4557cce8 Merge pull request #1036 from russel/rlw_766
Remove references to distutils as per Issue 766.
2015-09-21 12:19:14 +01:00
Russel Winder
714a97e452 Remove references to distutils as per Issue 766. 2015-09-21 12:00:48 +01:00
Floris Bruynooghe
80897f62a6 Merge pull request #974 from nicoddemus/addoption-conftest
Add warning about implementing pytest_addoption in conftest files
2015-09-21 11:28:31 +01:00
Floris Bruynooghe
dfd83b59f8 Merge pull request #1033 from mbirtwell/changes_to_contributing_rst
contributing: Add instructions from PyCon
2015-09-21 11:13:14 +01:00
Michael Birtwell
bb42e43ee7 contributing: Add instructions from PyCon
At PyCon we realised that the virtualenv created in the make file wasn't
used in the subsequent steps in CONTRIBUTING.rst. This change removes the
reference the make and suggesting installing tox directly.
Also changed the explanation that follows to make more sense with this
change
2015-09-21 10:56:05 +01:00
Bruno Oliveira
8fe5c704e3 Merge pull request #1024 from rouge8/tmpdir-uid-not-found
Make tmpdir resiliant to user ids that do not exist
2015-09-18 22:53:17 -03:00
Andy Freeland
b1c9b8b415 Make tmpdir resiliant to user ids that do not exist
Previously, the tmpdir fixture would fail if the current process's user
id does not correspond to a valid user (e.g. running pytest in a Docker
container with 'docker run -u').
2015-09-18 21:26:19 -04:00
Ronny Pfannschmidt
7658f60146 #877 - reencoding causes typeerrors on python2 2015-09-19 00:30:01 +02:00
Ronny Pfannschmidt
49c99a41ea reencode non-ascii python2 assertion reprs, fixes #877
i decided against using a warning since the problem goes away with python3
the support code can be removed once we drop python2 in 10 years or so
2015-09-19 00:30:01 +02:00
Bruno Oliveira
f02d9425f9 Bump version to 2.8.0 2015-09-18 19:04:20 -03:00
Ronny Pfannschmidt
1aca998f3f Merge pull request #1012 from lukas-bednar/junitxml_plugin
junitxml: align custom properties with junit format
2015-09-18 16:01:22 +02:00
holger krekel
2ba0fe2876 Merge pull request #1022 from nicoddemus/cxfreeze-tmpdir
Use temporary directory for cx_freeze testing
2015-09-18 09:28:33 +02:00
Bruno Oliveira
546093175e White list rm for cxfreeze env 2015-09-17 23:34:09 -03:00
Bruno Oliveira
e4d5f88257 Fix install cx_freeze 2015-09-17 23:32:27 -03:00
Bruno Oliveira
3a8e375efe Fix import 2015-09-17 14:52:51 -03:00
Bruno Oliveira
f5f3a2a928 Fix flaky failure about unused shutil 2015-09-17 12:54:02 -03:00
Bruno Oliveira
6bca5a1c25 Use temporary directory for cx_freeze testing 2015-09-17 11:54:44 -03:00
Lukas Bednar
02a2272cfe junitxml: move custom properties to properties element
<testsuite>
  <testcase>
    <properties>
      <property name="ABC" value="XYZ" />
      <property name="DEF" value="ZYX" />
    </properties>
  </testcase>
</testsuite>
2015-09-17 15:56:41 +02:00
Florian Bruhin
fe8def98e4 Merge pull request #1020 from hpk42/pluggy031
re-vendor pluggy to 0.3.1 release and also fix the README
2015-09-17 14:41:32 +02:00
holger krekel
f4bfd571ee re-vendor pluggy to 0.3.1 release and also fix the README 2015-09-17 13:54:23 +02:00
holger krekel (rather uses bitbucket/hpk42)
f32f3f1980 Merge pull request #1019 from mdboom/python3.5-docs
Various updates to the docs to mention support for Python 3.5
2015-09-17 13:45:35 +02:00
holger krekel
baf5b5d005 update changelog/author info, bump version number 2015-09-17 13:43:39 +02:00
holger krekel (rather uses bitbucket/hpk42)
3bcda48ba4 Merge pull request #1009 from mdboom/deprecation-warnings
avoid deprecation warnings, introduce helpers.
2015-09-17 13:37:26 +02:00
Michael Droettboom
16f90ffc92 Various Python 3.5 documentation mentions 2015-09-17 07:32:52 -04:00
Ronny Pfannschmidt
6520cf00e9 Merge pull request #1018 from hpk42/no_scm
revert setuptools_scm addition
2015-09-17 13:25:48 +02:00
holger krekel
7eb1211192 revert setuptools_scm addition introduced with f22d14b105 2015-09-17 12:58:04 +02:00
Florian Bruhin
f978b545c5 Merge pull request #1016 from hpk42/importappend3
revert importing test module behaviour to prepend by default
2015-09-17 12:41:28 +02:00
holger krekel
6192e74f03 remove __init__.py as advised by ronny 2015-09-17 10:51:03 +02:00
Michael Droettboom
7f71ce0ab2 Reorganize tests 2015-09-16 16:52:37 -04:00
holger krekel
a62d12634c revert importing test module behaviour to prepend by default 2015-09-16 22:12:20 +02:00
holger krekel (rather uses bitbucket/hpk42)
886ac82c43 Merge pull request #1013 from nicoddemus/issue1010
Make tmpdir more resilient in case environment variables required by getpass are missing
2015-09-16 21:57:44 +02:00
Florian Bruhin
2575053697 Merge pull request #1015 from hpk42/cache-integration
refined pytest-cache integration
2015-09-16 21:46:46 +02:00
Bruno Oliveira
130e6cf8a2 Use temproot as a fallback if the current user couldn't be obtained 2015-09-16 16:42:07 -03:00
holger krekel
3841e99720 avoid oldstyle funcarg usage 2015-09-16 21:06:44 +02:00
holger krekel
79d22bf334 some more doc refinements 2015-09-16 20:44:41 +02:00
holger krekel
45065e4e2e refine command line option naming and docs 2015-09-16 20:41:22 +02:00
Bruno Oliveira
558e5406e8 test_get_user should execute on windows only 2015-09-16 13:06:39 -03:00
Bruno Oliveira
1150e87e31 Extract get_user logic into a separate function 2015-09-16 12:47:50 -03:00
Bruno Oliveira
6676aeda5a Make tmpdir more resilient in case environment variables required by getpass are missing
Fix #1010
2015-09-16 12:20:07 -03:00
holger krekel
9a90aaca96 improve and integrate docs 2015-09-16 17:15:31 +02:00
Michael Droettboom
bf74401fd3 Use __code__.co_argcount 2015-09-16 11:05:54 -04:00
Michael Droettboom
9a84c9edb6 Add tests for argument formatting 2015-09-16 11:02:48 -04:00
Michael Droettboom
08a572086b Fix lookup of special values 2015-09-16 11:02:36 -04:00
Bruno Oliveira
2093889ac2 Merge pull request #1011 from hpk42/master
merge latest pytest-2.7 changes
2015-09-16 11:22:05 -03:00
Michael Droettboom
9c19728d2b Fix checking for args 2015-09-16 06:45:51 -04:00
Michael Droettboom
859259ddae Simplify 2015-09-16 06:43:16 -04:00
Michael Droettboom
a0cefb3213 Use inspect.signature instead of inspect.getargspec 2015-09-16 06:33:53 -04:00
holger krekel
fdd23878ec Merge latest changes from branch 'pytest-2.7'
Conflicts:
	_pytest/__init__.py
	doc/en/fixture.rst
	setup.cfg
	testing/test_runner.py
2015-09-16 09:26:55 +02:00
Floris Bruynooghe
b4c47c0ac0 Merge pull request #1006 from hpk42/pytest-2.7
some fixes, streamlines to get 2.7.3 out the door
2015-09-15 11:37:38 +01:00
holger krekel
5af262daa3 fix flakes problem, configure devpi upload to always upload wheels and sdist 2015-09-15 12:28:09 +02:00
holger krekel
16720b96b4 fix a few issues with pytest-2.7 branch and bump version number
- importorskip: skip a test if we have a minversion but cannot parse
  version numbers due to pkg_resources not present
- make runner tests work with latest xdist
2015-09-15 12:17:52 +02:00
Bruno Oliveira
b2b003dcac Merge pull request #1001 from RonnyPfannschmidt/exitcode-refactor
restrucure pytest.main.wrap_session to allow for non-testrun wraps
2015-09-14 15:56:04 -03:00
Ronny Pfannschmidt
c5bf148ed7 Merge pull request #959 from nicoddemus/vendor-pluggy
Vendor pluggy
2015-09-14 18:37:52 +02:00
Ronny Pfannschmidt
81018f1996 restrucure pytest.main.wrap_session to allow for non-testrun wraps 2015-09-14 18:34:29 +02:00
Bruno Oliveira
a13f23d218 Merge branch 'master' into vendor-pluggy 2015-09-14 12:22:18 -03:00
Anatoly Bubenkov
6d5641afce Merge pull request #999 from nicoddemus/fix-appveyor
Use pip and tox from py27 in appveyor
2015-09-14 17:20:46 +02:00
Bruno Oliveira
9396489b4c Remove unused run_with_env script 2015-09-14 11:28:07 -03:00
Ronny Pfannschmidt
2eb3bdf132 Merge pull request #998 from nicoddemus/py35
Use python "3.5" on travis
2015-09-14 16:16:42 +02:00
Bruno Oliveira
a0dd478637 Use pip and tox from py27 in appveyor 2015-09-14 10:59:10 -03:00
Bruno Oliveira
1d1eac4c6b Use python "3.5" on travis 2015-09-14 10:36:20 -03:00
Bruno Oliveira
d6bb5cb6c1 Ignore vendored_packages folder for coverage purposes 2015-09-14 10:30:58 -03:00
Bruno Oliveira
d79c89beba Merge pull request #997 from RonnyPfannschmidt/monkeypatch-cleanup
monkeypatch: ensure the internal _savesyspath attribute always exists
2015-09-14 10:25:09 -03:00
Ronny Pfannschmidt
2cfc183029 monkeypatch: ensure the internal _savesyspath attribute always exists 2015-09-14 07:42:23 +02:00
Ronny Pfannschmidt
e0645564fe more pep8 fixes 2015-09-13 21:49:01 +02:00
Ronny Pfannschmidt
cd475c7b27 minor flake8 fixes 2015-09-13 19:55:44 +02:00
Ronny Pfannschmidt
c06ff2a992 update docs on the json usage of builtin cache 2015-09-13 19:55:44 +02:00
Ronny Pfannschmidt
1e107e6bd1 restrucure pytest.main.wrap_session to allow for non-testrun wraps 2015-09-13 19:55:44 +02:00
Ronny Pfannschmidt
3d843edc69 minor doc reference fixes 2015-09-13 19:55:44 +02:00
Ronny Pfannschmidt
2e87cf4a62 create the previously missing cache fixture
there was a disabled test
2015-09-13 19:55:44 +02:00
Ronny Pfannschmidt
1de38a25fc use flake8 in the flakes testenv
and extend the ignored errors list so pytest is clean
we def have to trim down that one
2015-09-13 19:55:44 +02:00
Ronny Pfannschmidt
e035f57535 s/--cache/--show-cache/ 2015-09-13 19:55:44 +02:00
Ronny Pfannschmidt
e20216a1a8 merge the pytest-cache plugin into core 2015-09-13 19:55:44 +02:00
Bruno Oliveira
c7888d1d97 Add note in changelog about issue 960 2015-09-12 11:17:50 -03:00
Bruno Oliveira
41d2faccea Merge branch 'issue_960' of https://github.com/Akasurde/pytest 2015-09-12 10:57:23 -03:00
Abhijeet Kasurde
dd69394598 Added testcase and help for report option
Fix added to show help of new reporting option 'a'.
Also, added testcase for checking reporting functionality
with option 'a'.

Signed-off-by: Abhijeet Kasurde <akasurde@redhat.com>
2015-09-11 12:24:45 +05:30
Abhijeet Kasurde
c40947e651 Added fix for specifying every option for reporting
The fix will add option 'a' to reporting which will ease task of
adding all options or reporting at once.

Signed-off-by: Abhijeet Kasurde <akasurde@redhat.com>
2015-09-10 15:43:43 +05:30
Bruno Oliveira
b6f876e721 Fix --target parameter for pip install 2015-09-07 14:13:41 -03:00
Bruno Oliveira
5a1c679f78 Keep *dist-info directory on vendored_packages 2015-09-07 14:09:59 -03:00
Bruno Oliveira
0b3f1b4a7c Create _pytest.vendored_packages and _pytest._pluggy
- Replace imports from "pluggy" to "_pytest._pluggy"
- Fallback to import "pluggy" from global namespace if
  not found in _pytest.vendored_packges
2015-09-07 13:01:54 -03:00
Bruno Oliveira
e622975ffd Merge pull request #987 from benauthor/master
Document single-method usage
2015-09-04 17:24:15 -03:00
Evan Bender
6ffb659282 Document single-method usage
It is possible to run a single test method in a class, but the syntax is
not glaringly obvious and documentation would help people out.
2015-09-04 15:11:57 -04:00
Bruno Oliveira
8625eb643e Merge pull request #986 from RonnyPfannschmidt/early-logging
replay initial warnings when terminalreporter is loaded
2015-09-02 19:10:53 -03:00
Ronny Pfannschmidt
76f0988551 turn the pytest_logwarning hook historic 2015-09-02 18:49:49 +02:00
Ronny Pfannschmidt
348e519437 Merge pull request #962 from nicoddemus/doctest-skip
Make doctest SKIP option register doctests as SKIPPED rather than PASSED
2015-09-01 12:31:35 +02:00
Ronny Pfannschmidt
7292212d5a Merge pull request #976 from nicoddemus/ren-warnings-on-terminal
Rename "warnings" to "pytest-warnings" in terminal output
2015-09-01 07:56:42 +02:00
Bruno Oliveira
4ad56e84a8 Make doctest SKIP option register doctests as SKIPPED rather than PASSED
Fix 957
2015-08-31 21:25:11 -03:00
Bruno Oliveira
e3734ef337 Add CHANGELOG entry about renaming "warnings" to "pytest-warnings" 2015-08-31 21:21:29 -03:00
Bruno Oliveira
06a70b6d6d Merge pull request #982 from codewarrior0/patch-1
Add notes to docstring of `monkeypatch.undo()`
2015-08-31 21:18:53 -03:00
Ronny Pfannschmidt
3fa261564b replay initial warnings when terminalreporter is loaded 2015-08-31 21:54:12 +02:00
Anatoly Bubenkov
18d175708c Merge pull request #983 from The-Compiler/issue-979
Fix overriding of fixtures with parametrization.
2015-08-31 15:21:45 +02:00
Florian Bruhin
19791546da Add docstring. 2015-08-31 15:11:57 +02:00
Florian Bruhin
885e461ae3 Fix overriding of fixtures with parametrization.
Fixes #979, see #926.
2015-08-31 14:38:39 +02:00
David Vierra
3a27cd87ce Adjust phrasing in monkeypatch.undo docstring
s/test invocation/test function invocation/
2015-08-29 19:00:00 -10:00
David Vierra
6380e19f07 Add notes to docstring of monkeypatch.undo()
Note that calling `undo()` is generally not needed, and describe the "gotcha" discovered in #981.
2015-08-29 16:53:57 -10:00
Bruno Oliveira
c0eec5d61c Merge pull request #975 from nicoddemus/fix-regression-parse-known-args
Fix regression caused by changing the signature for parse_known_args
2015-08-29 23:44:32 -03:00
Brianna Laugher
c493f263b6 Merge branch 'Elizaveta239-master' closes #908 2015-08-29 17:34:35 +10:00
Floris Bruynooghe
553aef57aa Merge pull request #978 from nicoddemus/string-diff
Show a few diff lines when truncating string diffs
2015-08-28 09:04:39 +01:00
Florian Bruhin
9f30ae9850 Merge pull request #977 from nicoddemus/marks-fixtures-note
Add note that using marks on fixtures is not supported
2015-08-28 06:16:49 +02:00
Bruno Oliveira
dd7112d5ea Show a few diff lines when truncating string diffs
Fix #934
2015-08-27 22:20:13 -03:00
Brianna Laugher
b49bedcf0c Merge branch 'master' of git://github.com/Elizaveta239/pytest into Elizaveta239-master 2015-08-28 11:10:22 +10:00
Bruno Oliveira
87cb5bc5b7 Add note that using marks on fixtures is not supported
Fix #964
2015-08-27 20:11:18 -03:00
Bruno Oliveira
e59471766a Rename "warnings" to "pytest-warnings" in terminal output
Fix #970
2015-08-27 19:59:52 -03:00
Bruno Oliveira
4533a50542 Fix regression caused by changing the signature for parse_known_args
Fix #973
2015-08-27 19:35:32 -03:00
Bruno Oliveira
ccf7584fac Add warning about implementing pytest_addoption from conftest files 2015-08-27 19:14:56 -03:00
Bruno Oliveira
06b1b69fb7 Merge pull request #963 from nicoddemus/cookiecutter-docs
Mention cookie-cutter in the docs and fix broken links to <extplugin>
2015-08-26 16:41:56 -03:00
Bruno Oliveira
333cb27272 Merge pull request #961 from nicoddemus/args-after-options
Paths after normal options are now properly used to discover rootdir and ini files
2015-08-26 12:50:19 -03:00
Bruno Oliveira
d988d2006f Mention cookie-cutter in the docs and fix broken links to <extplugin> 2015-08-25 23:59:18 -03:00
Bruno Oliveira
42b43a7d7b Paths after normal options are now properly used to discover rootdir and ini files
Fix #949
2015-08-25 22:08:05 -03:00
elizabeth
e67d66a5d4 Merge branch 'pytest-dev'
# Conflicts:
#	AUTHORS
2015-08-24 22:55:11 +03:00
Ronny Pfannschmidt
b25e41e348 Merge pull request #956 from nicoddemus/record-xml-property
add preliminary support for extended junit xml properties
2015-08-24 09:13:04 +02:00
Bruno Oliveira
70da93145d Improve docs and using warning system for record_xml_property fixture 2015-08-23 11:56:27 -03:00
Bruno Oliveira
a20c6d072d Fix getdoctarget to ignore comment lines 2015-08-23 11:20:34 -03:00
elizabeth
06585f5bdd Always report error about parametrize data that doesn't correspond to fixtures in test functions. 2015-08-23 13:42:40 +03:00
David Diaz
44d9365da0 Add warning of "preliminary feature" to record_xml_property 2015-08-21 15:21:12 -06:00
David Diaz
2ddbac1f98 Correcting implementation based on pull request feed back 2015-08-21 14:31:53 -06:00
Ronny Pfannschmidt
287df16c9c Merge pull request #817 from nicoddemus/non-collection-exit-error
return non-zero exit code if no tests are collected
2015-08-21 18:30:57 +02:00
Bruno Oliveira
5f0e92a432 Merge pull request #942 from RonnyPfannschmidt/adapt-print
Adapt plugin name printing
2015-08-21 12:02:58 -03:00
Bruno Oliveira
d7d418cd47 Fix forked_run_report in pytest.xdist 1.13 2015-08-20 21:17:27 -04:00
David Diaz
24212fd97f Add support to record custom properties on xml output 2015-08-20 17:55:38 -06:00
Bruno Oliveira
3e41c3cbb3 Using packages from pypi 2015-08-18 09:19:51 -03:00
Bruno Oliveira
359f248729 Force to use xdist >= 1.13 2015-08-18 07:35:03 -03:00
Bruno Oliveira
f730291904 Make testsfailed and testscollected public Session attributes
As suggested by @flub in review
2015-08-18 07:35:02 -03:00
Bruno Oliveira
2ffd37b816 return non-zero exit code if no tests are collected
Fix #812
Fix #500
2015-08-18 07:35:02 -03:00
Ronny Pfannschmidt
7758bcd141 terminalwriter: extract plugin printing logic and add positive unittests 2015-08-17 09:10:01 +02:00
Ronny Pfannschmidt
740a97a8cc terinalwriter: use dash between plugin name and version 2015-08-17 08:48:38 +02:00
Ronny Pfannschmidt
2c42f15e00 adapt plugin printing
* print each distribution only once(xdist now has 3 entrypoints)
* include the distribution version
2015-08-17 08:41:24 +02:00
Ronny Pfannschmidt
37ed391cc2 Merge pull request #938 from nicoddemus/doctest-unicode
New ALLOW_UNICODE doctest option
2015-08-16 11:35:10 +02:00
Floris Bruynooghe
f22d14b105 Merge pull request #879 from RonnyPfannschmidt/setuptools-scm
use setuptools_scm to determine the version
2015-08-15 00:56:16 +01:00
Bruno Oliveira
d749021a31 Fix coverage
Also make sure a test that doesn't set ALLOW_UNICODE fails
on Python 2 and passes Python 3.
2015-08-12 22:49:41 -03:00
Bruno Oliveira
93aee0f814 Add docs and CHANGELOG for ALLOW_UNICODE option 2015-08-12 22:17:15 -03:00
Bruno Oliveira
420823070b Add ALLOW_UNICODE doctest option
When enabled, the ``u`` prefix is stripped from unicode strings in
expected doctest output. This allows doctests which use unicode
to run in Python 2 and 3 unchanged.

Fix #710
2015-08-12 22:16:44 -03:00
Ronny Pfannschmidt
ddbdcab522 Merge pull request #937 from ctheune/patch-1
Fix accidental inversion in skip example.
2015-08-12 07:51:59 +02:00
Christian Theune
2174f3ce37 Fix accidental inversion in skip example. 2015-08-12 07:38:04 +02:00
Bruno Oliveira
73fdda0e45 Merge pull request #926 from untitaker/multifixture
Don't skip fixtures that are substrings of params
2015-08-10 21:12:51 -03:00
Markus Unterwaditzer
1d5215ab4f Add myself to authors 2015-08-11 00:47:18 +02:00
Markus Unterwaditzer
cafd71eb29 Update changelog for #926
Also fix some inconsistencies in the changelog on the way.
2015-08-11 00:47:18 +02:00
Markus Unterwaditzer
41cef6f5f2 Don't skip fixtures that are substrings of params
Bug introduced with
https://bitbucket.org/pytest-dev/pytest/pull-requests/257/allow-to-override-parametrized-fixtures

Fix #736
2015-08-11 00:41:21 +02:00
Bruno Oliveira
681e502c12 Use "union" merge strategy for CHANGELOG
Credits: @The-Compiler 😄
2015-08-10 19:27:22 -03:00
Ronny Pfannschmidt
bc509b42e8 Merge pull request #933 from nicoddemus/hasplugin
Reintroduce hasplugin to PytestPluginManager
2015-08-10 09:40:52 +02:00
Bruno Oliveira
e103932aad Reintroduce hasplugin to PytestPluginManager
Fix #932
2015-08-09 19:30:49 -03:00
Ronny Pfannschmidt
1c680210c2 Merge pull request #931 from nicoddemus/docs-hookwrapper
Change docs to use hookwraper instead of __multicall__
2015-08-09 09:22:31 +02:00
Ronny Pfannschmidt
eb989c8257 Merge pull request #921 from tomviner/issue562-noseistest
Issue #562 - Ensure @nose.tools.istest is respected
2015-08-09 07:54:19 +02:00
TomV
14625907ae update changelog 2015-08-09 00:11:16 +01:00
TomV
353360dbe5 use local isclass 2015-08-09 00:11:15 +01:00
TomV
3497aa0766 check nose.istest __test__ attr issue526 2015-08-09 00:11:15 +01:00
Bruno Oliveira
d49fb8a2d5 Change cx_freeze example to not use regendoc 2015-08-08 19:17:50 -03:00
Bruno Oliveira
e8583f01a0 Replaced __multicall__ examples in docs by hookwrapper
Fix #929
2015-08-08 19:07:27 -03:00
TomV
e130a0257d add test for @nose.tools.istest 2015-08-08 21:08:05 +01:00
Bruno Oliveira
729b5e9b2f Merge pull request #923 from The-Compiler/parametrize-idfunc
Generate parametrize IDs for enum/re/class objects.
2015-08-08 15:09:54 -03:00
Brianna Laugher
36b86af4b9 Added more test cases 2015-08-08 19:20:09 +02:00
Brianna Laugher
40fa7b25c5 Merge branch 'master' of git://github.com/Elizaveta239/pytest into Elizaveta239-master
Fixed merge conflict in CHANGELOG
2015-08-08 18:54:53 +02:00
Florian Bruhin
4e21d1d77b Move enum import and adjust comments. 2015-08-08 15:25:10 +02:00
Florian Bruhin
84fdba129a More style changes. 2015-08-08 12:57:54 +02:00
Bruno Oliveira
9ae40e393a Fix PR image link 2015-08-07 22:32:22 -03:00
Florian Bruhin
18125c7d1f Clean up type comparisons. 2015-08-07 23:10:22 +02:00
Florian Bruhin
03d8a6c05d Update CHANGELOG. 2015-08-07 23:08:22 +02:00
Brianna Laugher
fd4d35d9a2 Merge pull request #924 from The-Compiler/parametrise
Alert user about other parametrize spellings.
2015-08-07 13:32:04 +02:00
Florian Bruhin
91a29932a6 Add CHANGELOG entry. 2015-08-07 07:58:13 +02:00
Florian Bruhin
b59376bea4 Alert user about other parametrize spellings. 2015-08-07 07:51:59 +02:00
Florian Bruhin
13c5456868 Generate parametrize IDs for enum/re/class objects. 2015-08-07 07:37:59 +02:00
Bruno Oliveira
4f83586f55 Give credit where it is due in CHANGELOG 😄
Related to #890
2015-08-06 22:51:32 -03:00
Bruno Oliveira
77f54dbdae Merge pull request #915 from Akasurde/issue-890
Converted docs from txt to rst
2015-08-06 22:47:53 -03:00
Bruno Oliveira
2bbe709bce Use testdir fixture in test_double_test to ensure controlled environment for execution
Because the test relies that two subsequent tests try to use pytest.warns to capture
the exact same warning, it is better to use testdir to ensure test execution
occurs in the order we expect (which might be different with pytest-xdist or
pytest-random for example)
2015-08-06 22:30:01 -03:00
Eric Hunsberger
39f1471e93 Warnings always raised in WarningsRecorder
This ensures that if tests for warnings are run more than once,
the warning is still raised as expected.
2015-08-06 15:05:01 -04:00
Abhijeet Kasurde
ab9befb197 Updated CHANGELOG and AUTHORS 2015-08-05 15:21:37 +05:30
Abhijeet Kasurde
6d446e7167 Converted documentation from txt to rst 2015-08-05 10:24:14 +05:30
Bruno Oliveira
02e742b7a6 Move WarningsChecker import in test to a local import to workaround flakes issue
See #897 for discussion
2015-08-04 19:27:13 -03:00
Abhijeet Kasurde
185e730feb Added makefile changes for docs
Signed-off-by: Abhijeet Kasurde <akasurde@redhat.com>
2015-08-04 18:44:52 +05:30
Abhijeet Kasurde
e79413acc4 Updated documentation from txt to rst
Signed-off-by: Abhijeet Kasurde <akasurde@redhat.com>
2015-08-04 16:41:02 +05:30
Bruno Oliveira
d761bfffd7 Merge pull request #897 from hunse/warns
Warns
2015-08-03 22:48:20 -03:00
elizabeth
f7bacd169e Update docs 2015-08-04 00:48:41 +03:00
Bruno Oliveira
8fca4781f1 Merge pull request #910 from oleksmarkh/patch-1
fix docs folder name in HOWTORELEASE
2015-08-03 18:12:14 -03:00
elizabeth
657ca97dbd Some refactorings after code review 2015-08-04 00:02:03 +03:00
Oleksii Markhovskyi
3868b61443 fix docs folder name in HOWTORELEASE 2015-08-03 23:01:03 +02:00
Bruno Oliveira
c605cf92f9 Merge pull request #909 from flub/assertdoc
Update docs to represent reality
2015-08-02 18:53:47 -03:00
Floris Bruynooghe
b097d8c0a6 Update docs to represent reality
This was changed after EuroPython 2014, now both the message and the
introspection is presented.
2015-08-02 22:36:19 +01:00
elizabeth
db9809d6dc Update test for error in 'indirect' parameter 2015-08-02 23:53:44 +03:00
elizabeth
63bac67fb5 Add test for bad value in argument 'indirect' 2015-08-02 23:06:24 +03:00
elizabeth
54a3d6210b Update CHANGELOG and AUTHORS 2015-08-02 20:39:31 +03:00
elizabeth
15b865f502 Rewrite test using @pytest.fixture 2015-08-02 20:30:23 +03:00
elizabeth
9846953597 Support for python 2.6 2015-08-02 17:28:27 +03:00
elizabeth
2fc7aede0b Request #714: Apply indirect=True on particular argnames 2015-08-02 16:40:40 +03:00
Ronny Pfannschmidt
f6506fa6ca Merge pull request #907 from nicoddemus/regendocs-fixture
Use smtp.gmail.com server for examples and fixes examples for python 3
2015-07-31 06:55:08 +02:00
Eric Hunsberger
52b4eb6c46 Added warns to assert warnings are thrown
Works in a similar manner to `raises`, but for warnings instead
of exceptions. Also refactored `recwarn.py` so that all the
warning recording and checking use the same core code.
2015-07-30 23:28:32 -04:00
Eric Hunsberger
aac371cf07 Added myself to AUTHORS 2015-07-30 23:28:32 -04:00
Bruno Oliveira
4d8e3cbcb0 Use smtp.gmail.com server for examples and fixes examples for python 3
Examples in py3 where showing the wrong message:

    TypeError: Type str doesn't support the buffer API
2015-07-30 23:54:24 -03:00
Bruno Oliveira
7a918a1617 Fix regendoc section in fixture.txt
Noticed thanks to #903
2015-07-30 23:21:19 -03:00
Ronny Pfannschmidt
0b83b4076a Merge pull request #898 from nicoddemus/tmpdir-factory-new-name
Use pytest-of-$user as base directory for tmpdir_factory
2015-07-29 13:58:42 +02:00
Bruno Oliveira
4302972c23 Use pytest-of-$user as base directory for tmpdir_factory
Before tmpdir_factory, pytest used to create a link named "pytest-$user" to the current numbered directory. Use
 a different name so there's no conflict when running different pytest versions.

Fix #894
2015-07-28 20:57:48 -03:00
Ronny Pfannschmidt
235f9da432 special-case _pytest.__init__ in genscript to avoid a python3 bug 2015-07-27 11:33:27 +02:00
Bruno Oliveira
9cf9900721 Merge pull request #892 from The-Compiler/multi-parametrize
Document and test stacking of parametrize.
2015-07-26 21:00:29 -03:00
Bruno Oliveira
2035c5f8be Merge pull request #893 from pfctdayelise/dupereleaseguides
Delete duplicate release guide
2015-07-26 20:59:07 -03:00
Brianna Laugher
5ebad5c96d Delete release.txt from docs, merge extra comments to HOWTORELEASE.rst 2015-07-26 15:20:32 +02:00
Florian Bruhin
2ab4bf13ab Document and test stacking of parametrize.
Closes #815.
2015-07-26 14:39:13 +02:00
Ronny Pfannschmidt
5098e5fc47 fix version import 2015-07-26 14:04:52 +02:00
Brianna Laugher
017ceffb76 minor tweaks to CONTRIBUTING 2015-07-26 12:19:39 +02:00
Floris Bruynooghe
e69e785b23 Mention fix for issue #683 2015-07-26 12:15:29 +02:00
jakubo
e8f4819876 Test file run twice fails if it contains marked class #683 2015-07-26 12:15:29 +02:00
Brianna Laugher
985fd0cc2b Mention Contributors team by name in CONTRIBUTING 2015-07-26 12:07:50 +02:00
Floris Bruynooghe
97e5f7d7e9 Merge merge branch
I suspect I don't yet know how to use git...  Surely I don't need two
merges to merge a contribution.
2015-07-26 11:49:42 +02:00
Floris Bruynooghe
011565ca10 Add changelog entry for capture improvement 2015-07-26 11:42:33 +02:00
Floris Bruynooghe
e68fa641ff Merge reset-capgture-on-reeadouterr-errors
Merge branch 'reset-capture-on-readouterr-errors' of github.com:davidszotten/pytest into merge-capmerge
2015-07-26 11:39:03 +02:00
David Szotten
3e6bee2fc6 fail if snap is removed in the future 2015-07-26 01:28:00 +02:00
David Szotten
983d249680 reset capture even if readouterr throws
otherwise that exception (and all following output) end up in /dev/null
2015-07-26 01:15:51 +02:00
Floris Bruynooghe
8338c692a3 Merge pull request #887 from maartenq/753content_of
changed contents -> content but still #753 did not solved, might be i…
2015-07-25 19:11:53 +02:00
Floris Bruynooghe
f64d5f1209 Merge pull request #884 from cpommranz/deprecate_genscript
Deprecate and warn about --genscript, fixes #730
2015-07-25 19:09:52 +02:00
Floris Bruynooghe
f38c632635 Merge branch 'jb-fix-751' 2015-07-25 19:04:59 +02:00
Floris Bruynooghe
251fc68ef9 Merge fix for #751
Merge branch 'fix-751' of github.com:JanBednarik/pytest into jb-fix-751
2015-07-25 19:03:44 +02:00
Maarten
df8a83b2a1 changed contents -> content but still #753 did not solved, might be issues with regendoc 2015-07-25 17:48:12 +02:00
Jan Bednařík
0b92650494 Update changelog. 2015-07-25 16:25:29 +02:00
Jan Bednařík
9906a19e29 Issue #751 - fix. 2015-07-25 15:38:27 +02:00
Jan Bednařík
f78d87ee38 Issue #751 - test. 2015-07-25 15:38:11 +02:00
Christian Pommranz
00e1a2122a Deprecate and warn about --genscript, fixes #730 2015-07-25 15:10:22 +02:00
Floris Bruynooghe
80ee620459 Merge pull request #881 from maartenq/master
setuptools not present issue deprecating gentest
2015-07-25 15:03:50 +02:00
Floris Bruynooghe
023687d8d0 Merge pull request #872 from nicoddemus/confcutdir-inifile
Avoid detecting conftest files upwards from setup.cfg/pytest.ini/tox.ini by default
2015-07-25 14:03:24 +02:00
Maarten
e3d60024aa setuptools not present issue deprecating gentest 2015-07-25 13:50:40 +02:00
Ronny Pfannschmidt
a060b8ff73 use setuptools_scm to determine the version 2015-07-25 10:44:18 +02:00
Bruno Oliveira
ae28e4ba0f Merge remote-tracking branch 'upstream/pytest-2.7'
Conflicts:
	.travis.yml
2015-07-24 20:02:12 -03:00
Bruno Oliveira
92c61b0de3 Merge pull request #876 from takluyver/travis-reenable-35-pt27
Re-enable Python 3.5 testing on Travis
2015-07-24 19:59:43 -03:00
Thomas Kluyver
ed2c06c8cd Re-enable Python 3.5 testing on Travis
This is PR #875, but reissued against the pytest-2.7 branch.

According to the docs, it looks like this should work:
http://docs.travis-ci.com/user/languages/python/

I've explicitly specified beta 3, so it will need updating when future
betas come out. You could instead specify '3.5-dev', which would give
less need to update, but then it's less clear whether a change in status
is due to a change in pytest or in Python itself.
2015-07-24 15:50:45 -07:00
Bruno Oliveira
033def0a7a Workaround for cmdexec bug on Windows
This bug fails the entire pytest suite when executed with the
--lsof option in Python 2 on Windows.
2015-07-24 19:24:31 -03:00
Bruno Oliveira
9f94e443ff Merge remote-tracking branch 'upstream/pytest-2.7'
Conflicts:
	testing/conftest.py
2015-07-24 19:24:21 -03:00
Bruno Oliveira
91e8e59cea Merge pull request #860 from nicoddemus/warn-plugins-as-str-main
Make passing plugins as str objects a more obvious failure
2015-07-24 19:13:39 -03:00
Bruno Oliveira
4f1ae8c45e Workaround for cmdexec bug on Windows
This bug fails the entire pytest suite when executed with the
--lsof option in Python 2 on Windows.
2015-07-24 18:05:30 -03:00
Bruno Oliveira
b84fcbc85e Merge pull request #873 from hunse/fix-dev-versions
Allow non-integer version strings in `importorskip`
2015-07-24 16:08:15 -03:00
Eric Hunsberger
3cd19a7e45 Use monkeypatch for setting modules in tests
Instead of directly setting `sys.modules`. This ensures that they
get removed at the end of the test.
2015-07-24 12:34:06 -04:00
Eric Hunsberger
d104487282 importorskip: Allow non-integer version strings
Use `pkg_resources.parse_version` to parse version strings.
This can handle 'dev', 'rc', alpha and beta version strings,
among others.
2015-07-24 12:34:06 -04:00
Bruno Oliveira
35bbcc39a2 Interpret strings to "plugins" arg in pytest.main() as module names
See #855
2015-07-23 23:37:09 -03:00
Bruno Oliveira
ab9e246ab0 Avoid detecting conftest files upwards from setup.cfg/pytest.ini/tox.ini files by default
As discussed in #82
2015-07-23 23:21:07 -03:00
Bruno Oliveira
df29120abe Merge branch 'pytest-2.7'
Conflicts:
	_pytest/config.py
	_pytest/genscript.py
	_pytest/mark.py
	tox.ini
2015-07-23 22:35:51 -03:00
Bruno Oliveira
5a17e797c7 Fix basepython for tox envs "doctesting" and "coveralls"
Because we are currently using "nighly" python for travis, which
uses python 3.6 as the default interpreter. This breaks the environments
listed above because "inspect.getargspec" has been removed in py36.
2015-07-23 21:15:45 -03:00
Bruno Oliveira
ce96973ed5 Merge pull request #864 from pytest-dev/consider-color-yes-everywhere
Consider --color option in more places which deal with TerminalWriters
2015-07-23 08:03:52 -03:00
Bruno Oliveira
77844ec5f3 Fixed typo in CHANGELOG: "testdirs" -> "testpaths" 2015-07-22 12:14:49 -03:00
Floris Bruynooghe
0e26de2218 Merge pull request #867 from nicoddemus/autouse-yield-fixture-class
Fix autouse fixtures defined in a TestCase subclass
2015-07-22 01:25:21 +02:00
Bruno Oliveira
81f18f8a0f Fix flakes 2015-07-21 19:55:01 -03:00
Bruno Oliveira
0769bb4898 Make pastebin use _pytest.config.create_terminal_writer 2015-07-21 12:55:18 -03:00
Bruno Oliveira
31cfbac1f4 Fix autouse fixtures defined in a TestCase subclass 2015-07-20 19:28:54 -03:00
Anatoly Bubenkov
045274e647 remove merge artifact 2015-07-19 15:49:43 +02:00
Anatoly Bubenkov
6af7172204 Merge branch 'pytest-2.7' 2015-07-19 15:25:04 +02:00
Anatoly Bubenkov
412b43b216 Merge pull request #865 from nicoddemus/skipif-reason-multiple
Report correct reason when using multiple skip/xfail markers
2015-07-19 15:14:47 +02:00
Bruno Oliveira
de65737cb1 Fix flakes 2015-07-18 18:23:17 -03:00
Bruno Oliveira
953916df49 Report correct reason when using multiple skip/xfail markers 2015-07-18 18:18:45 -03:00
Bruno Oliveira
2f7d0f8bd9 Consider --color option in more places which deal with TerminalWriters 2015-07-18 16:39:55 -03:00
Bruno Oliveira
23aaa8a62c Allow py35 to fail on Travis until it is properly supported (fix2)
Conflicts:
	.travis.yml
2015-07-18 15:39:22 -03:00
Bruno Oliveira
604a39f5ef Allow py35 to fail on Travis until it is properly supported (fix2) 2015-07-18 15:30:16 -03:00
Bruno Oliveira
a5bc98136d Allow py35 to fail on Travis until it is properly supported (fix) 2015-07-18 15:18:57 -03:00
Bruno Oliveira
7dab2e1efe Allow py35 to fail on Travis until it is properly supported 2015-07-18 14:46:36 -03:00
Bruno Oliveira
da1d5712cf Fix broken links
Fix #857
2015-07-18 14:15:07 -03:00
Bruno Oliveira
30ff723d57 Merge pull request #859 from pytest-dev/py35fix-port
Py35fix port
2015-07-18 13:53:11 -03:00
Thomas Kluyver
e227950b06 Style fix 2015-07-18 13:18:38 -03:00
Thomas Kluyver
6719a818e7 Match .travis.yml env list to tox envs
Conflicts:
	.travis.yml
2015-07-18 13:18:36 -03:00
Thomas Kluyver
08432c3e97 No more failures 2015-07-18 13:17:29 -03:00
Thomas Kluyver
15497dcd77 OK, try running flakes with 2.7 2015-07-18 13:16:47 -03:00
Thomas Kluyver
8a0867c580 Try running flakes tests with Python 3.4 2015-07-18 13:16:47 -03:00
Thomas Kluyver
d4789f1ac4 Fix AST rewriting with starred expressions in function calls 2015-07-18 13:16:47 -03:00
Thomas Kluyver
26e7532756 Move Interrupted exception class out of Session 2015-07-18 13:16:47 -03:00
Thomas Kluyver
570c4cc55a No Starred node type on Python 2 2015-07-18 13:16:46 -03:00
Matthias Bussonnier
728d8fbdc5 generify 2015-07-18 13:16:26 -03:00
Matthias Bussonnier
fad569ae1b simplify + fix 2015-07-18 13:16:26 -03:00
Matthias Bussonnier
a4dbb27fab a test 2015-07-18 13:16:26 -03:00
Matthias Bussonnier
ec5286ea8c nigh 2015-07-18 13:16:08 -03:00
Matthias Bussonnier
195afa0733 try isntall 35 on tox 2015-07-18 13:16:08 -03:00
Matthias Bussonnier
8bde0c5957 allow faillure on 35 2015-07-18 13:16:08 -03:00
Matthias Bussonnier
b18e6439bd Ast Call signature changed on 3.5
fix issue 744 on bitbucket

port of merge request 296

https://bitbucket.org/pytest-dev/pytest/pull-request/296/astcall-signature-changed-on-35
https://bitbucket.org/pytest-dev/pytest/issue/744/

Conflicts:
	CHANGELOG
2015-07-18 13:15:46 -03:00
Floris Bruynooghe
5e0235946b Merge pull request #801 from Carreau/test-35
Support Python 3.5
2015-07-18 11:30:36 +01:00
Anatoly Bubenkov
360c09a1e7 appveyor scripts 2015-07-16 21:21:55 -03:00
Anatoly Bubenkov
85f7aa2f9b appveyor integration 2015-07-16 21:21:47 -03:00
Bruno Oliveira
a7b4ed89da Use functools.partial name explicitly and simplify the code a bit as asked in review 2015-07-16 20:37:04 -03:00
Bruno Oliveira
dcdc823dd2 Support for tests created with functools.partial
Fix #811
2015-07-16 20:37:03 -03:00
Bruno Oliveira
30e7104b05 Merge pull request #845 from nicoddemus/tmpdir-improvements
Introduce tmpdir_factory session fixture
2015-07-16 20:02:08 -03:00
Bruno Oliveira
330de0a93d Use a subdirectory in the TEMP directory to speed up tmpdir creation
Fix #105
2015-07-16 19:50:49 -03:00
Bruno Oliveira
aa25fb05a9 Make sure marks in subclasses don't change marks in superclasses
Fix #842
2015-07-16 19:29:16 -03:00
Thomas Kluyver
4462b83258 Style fix 2015-07-16 11:04:36 -07:00
Bruno Oliveira
94cdec2cfe Update sidebar links from Bitbucket to GitHub 2015-07-16 03:08:06 +00:00
Bruno Oliveira
ebf32ae8a9 Update CONTRIBUTING documentation with GitHub instructions 2015-07-16 03:07:16 +00:00
Bruno Oliveira
74bc50e97c Fix flakes 2015-07-15 20:45:35 -03:00
Bruno Oliveira
0f52856f99 Use a subdirectory in the TEMP directory to speed up tmpdir creation
Fix #105
2015-07-15 20:03:58 -03:00
Thomas Kluyver
077f0d3d66 Match .travis.yml env list to tox envs 2015-07-15 16:03:14 -07:00
Thomas Kluyver
37a09a6c30 No more failures 2015-07-15 15:58:42 -07:00
Thomas Kluyver
4e98d2b7f1 OK, try running flakes with 2.7 2015-07-15 15:52:28 -07:00
Thomas Kluyver
d73c8e6a5e Try running flakes tests with Python 3.4 2015-07-15 15:49:15 -07:00
Thomas Kluyver
195422f9c0 Fix AST rewriting with starred expressions in function calls 2015-07-15 15:31:35 -07:00
Thomas Kluyver
62ca4ae963 Move Interrupted exception class out of Session 2015-07-15 14:28:43 -07:00
Thomas Kluyver
35bea86c9f No Starred node type on Python 2 2015-07-15 14:11:21 -07:00
Matthias Bussonnier
d774f3ca86 generify 2015-07-15 13:49:40 -07:00
Matthias Bussonnier
167625d24d simplify + fix 2015-07-15 13:49:40 -07:00
Matthias Bussonnier
3bc6c0f936 a test 2015-07-15 13:49:40 -07:00
Matthias Bussonnier
3cf82c6594 nigh 2015-07-15 13:49:40 -07:00
Matthias Bussonnier
71a00c3223 try isntall 35 on tox 2015-07-15 13:49:40 -07:00
Matthias Bussonnier
e2e29284f0 allow faillure on 35 2015-07-15 13:49:40 -07:00
Matthias Bussonnier
26e64fc45c Ast Call signature changed on 3.5
fix issue 744 on bitbucket

port of merge request 296

https://bitbucket.org/pytest-dev/pytest/pull-request/296/astcall-signature-changed-on-35
https://bitbucket.org/pytest-dev/pytest/issue/744/
2015-07-15 13:49:40 -07:00
Bruno Oliveira
8f4f2c665d Mention pytest-runner in the docs
Fix #147
2015-07-13 21:59:37 -03:00
Bruno Oliveira
7a71b69a87 Pinning mock module to < 1.1 fo py26
It has been announced that mock>=1.1 will be
supported for python 2.7 only.

Conflicts:
	tox.ini
2015-07-13 13:43:03 -03:00
holger krekel (rather uses bitbucket/hpk42)
8910ec2870 Merge pull request #836 from nicoddemus/doctest-modules-fixtures
Consider session fixtures for doctest docstrings in modules
2015-07-13 18:02:51 +02:00
holger krekel (rather uses bitbucket/hpk42)
c5d26ae1bb Merge pull request #834 from nicoddemus/show-fixtures-bug
Fix: --fixtures only shows fixtures from first file
2015-07-13 17:59:42 +02:00
Bruno Oliveira
d6033037ac Consider session fixtures for doctest docstrings in modules
Fixes #768
2015-07-13 12:29:09 -03:00
Bruno Oliveira
5750ae784a Reword CHANGELOG a bit 2015-07-13 12:01:54 -03:00
holger krekel (rather uses bitbucket/hpk42)
194581ab5f Merge pull request #829 from nicoddemus/pep302-get-data-support
Add support for PEP302 get_data API
2015-07-13 16:55:16 +02:00
Bruno Oliveira
5ec2a17f08 --fixtures only shows fixtures from first file
Fix #833
2015-07-12 17:32:39 -03:00
Bruno Oliveira
7445c5bd70 Add support for PEP302 get_data API
Fix #808
2015-07-11 14:13:43 -03:00
Bruno Oliveira
1baa1a4d01 Merge pull request #822 from nicoddemus/extra-usage-info
Print inifile and rootdir when there's usage errors
2015-07-11 13:43:20 -03:00
Bruno Oliveira
40ec0ec97d Merge pull request #823 from nicoddemus/testdirs-option
Add new testdirs ini option
2015-07-11 13:42:54 -03:00
Bruno Oliveira
8a6aa5e17e Print inifile and rootdir when there's usage errors
Related to #821
2015-07-10 21:56:12 -03:00
Bruno Oliveira
854e603f84 Add new testdirs ini option
Fix #694
2015-07-10 21:52:47 -03:00
Bruno Oliveira
65c56d4c00 Pinning mock module to < 1.1 fo py26
It has been announced that mock>=1.1 will be
supported for python 2.7 only.
2015-07-10 19:57:48 -03:00
holger krekel (rather uses bitbucket/hpk42)
e7374c39ba Merge pull request #825 from nicoddemus/syntax-highlight-docs
Add syntax highlighting to missing snippets in the documentation and other fixes
2015-07-10 13:02:52 +02:00
Bruno Oliveira
0ee3ee7333 Add syntax highlighting to missing snippets in the documentation and other fixes
- Add explicit "code-block" for sessions without syntax highlight
- Moved Metafunc documentation to the class docstring and reused that in the
   docs;
2015-07-09 21:50:38 -03:00
Bruno Oliveira
0624a69964 Merge pull request #824 from hpk42/devpiupload
configure a plain "devpi upload" to create sdist and universal wheel
2015-07-09 12:08:52 -03:00
holger krekel
fe897c81be configure a plain "devpi upload" to create sdist and universal wheel 2015-07-09 14:56:56 +02:00
Bruno Oliveira
ae4c8b8635 Merge pull request #787 from pytest-dev/pluggy-bc-fix
Reintroduce get_plugin_manager() for backward-compatibility
2015-07-08 19:28:01 -03:00
Bruno Oliveira
d70a31168b Merge pull request #820 from jeffwidman/patch-1
Fix typo + grammar
2015-07-07 21:13:50 -03:00
Jeff Widman
4fa64a962e Fix typo + grammar 2015-07-07 16:35:21 -07:00
Bruno Oliveira
438ea86137 Fix flaky test_timing_function in Windows
This tests fails consistently on Windows due to small time
difference:

>       assert float(val) >= 0.03
E       assert 0.0299999713898 >= 0.03
E        +  where 0.0299999713898 = float('0.0299999713898')

Windows time.time() function is not as accurate as linux's,
so relaxed the test a bit.
2015-07-04 15:05:45 -03:00
Bruno Oliveira
d773c7498f Reword entry in the changelog to show case the new status bar colors better 2015-07-03 18:36:15 -03:00
Bruno Oliveira
4ef6cdfb68 Add small thanks note to Eric Siegerman 2015-07-03 18:31:15 -03:00
Bruno Oliveira
d10054a38d Merge branch 'esiegerman/summary_colors' of github.com:esiegerman/pytest 2015-07-03 18:29:17 -03:00
Bruno Oliveira
9293062221 Merge pull request #809 from kevincox/junitxml-file-attribute
Add `file` and `line` attributes to junit-xml output.
2015-07-03 08:24:36 -03:00
Kevin Cox
7fa27af408 Add file and line attributes to junit-xml output.
This adds the `file` and `line` attributes to the junit-xml output
which can be used by tooling to identify where tests come from. This can be
used for many things such as IDEs jumping to failures and test
runners evenly balancing tests among multiple executors.

Update test_junitxml.py

Foo.
2015-07-02 18:41:52 -04:00
Eric Siegerman
2c419c4790 Add CHANGELOG and AUTHORS entries 2015-07-02 15:47:03 -04:00
Eric Siegerman
afcad74be8 PEP8ify parametrized-test parameters 2015-07-02 13:39:05 -04:00
Eric Siegerman
6c395cb58c Default color is now yellow
Passing tests override that default, making the color green; but several other
"boring" statuses (xfailed, xpassed, deselected, skipped) have no effect.

Net effect: if only "boring" tests are seen, or no tests at all, the summary
bar is yellow.
2015-07-02 13:39:05 -04:00
Eric Siegerman
044d874c5b If there are warnings, make the status bar yellow
Also if we see any statuses the code doesn't know about.
2015-07-02 13:39:05 -04:00
Eric Siegerman
cb21d844d9 Add missing "error" status to the list 2015-07-02 13:39:05 -04:00
Eric Siegerman
0282da9ddf Refactor slightly
Check for the empty-key special case in the first loop,
not the second.
2015-07-02 13:39:05 -04:00
Eric Siegerman
e07144aeb4 Add tests for the empty-key special case 2015-07-02 13:39:05 -04:00
Eric Siegerman
7993afae46 test_summary_stats() now prints its parameter values
This makes it easier to identify failing tests.
2015-07-02 13:39:05 -04:00
Eric Siegerman
bfc3e48fd5 Factor out build_summary_stats_line(), and add tests
--HG--
branch : esiegerman/summary_colors
2015-07-02 13:39:04 -04:00
Bruno Oliveira
76497c2542 Fix plugins_index script and update plugins_index.txt 2015-06-30 19:01:28 -03:00
Bruno Oliveira
27a98788a8 start 2.7.3 CHANGELOG 2015-06-26 00:30:26 -03:00
Bruno Oliveira
9fb1637ce2 Test that deprecated_call keeps internal warnings structures intact 2015-06-26 00:26:59 -03:00
Pieter Mulder
75679f08c9 Update AUTHORS and CHANGELOG 2015-06-25 17:38:45 +02:00
Pieter Mulder
444cdfe6e3 Correct test_deprecated_call_preserves test.
Test if we preserve the correct functions.
2015-06-25 17:33:40 +02:00
Pieter Mulder
89b00d714f deprecated_call dit not revert functions back to original
Make sure that the warnings.warn and warnings.warn_explicit are reverted
to there original function after running the function in deprecated_call
2015-06-25 17:11:50 +02:00
Bruno Oliveira
3c2fd833ca Reintroduce get_plugin_manager() for backward-compatibility
PyCharm pytest runner depends on this function existing (see #787).

Added reference to get_plugin_manager() and PluginManager/PytestPluginManager to docs
2015-06-25 00:48:47 -03:00
holger krekel
01f5913826 try to fix coveralls env 2015-06-23 23:25:19 +02:00
holger krekel
0998170a01 check webhook again 2015-06-23 17:22:37 +02:00
holger krekel
28cf4c3226 small fix to test webhook 2015-06-23 17:14:08 +02:00
holger krekel (rather uses bitbucket/hpk42)
08613b621e Merge pull request #799 from pytest-dev/noconftest
Add a --noconftest option.
2015-06-23 16:53:22 +02:00
holger krekel
ee40ea5f6d Merge branch 'pytest-2.7'
Conflicts:
	AUTHORS
	_pytest/__init__.py
	setup.py
	testing/conftest.py
	tox.ini
2015-06-23 16:49:05 +02:00
holger krekel
e84c00efae finish pytest-2.7.2 release 2015-06-23 16:31:24 +02:00
Florian Bruhin
f78b6df8bc Return an empty list directly. 2015-06-23 14:05:44 +02:00
holger krekel (rather uses bitbucket/hpk42)
53021ea264 Merge pull request #800 from pytest-dev/confcutdir-test-fix
Check conftest.py is not loaded with --confcutdir.
2015-06-23 10:57:33 +02:00
Florian Bruhin
01d067ec2b Check conftest.py is not loaded with --confcutdir.
The test creates a conftest.py with "assert 0" which never should be loaded.
However, if it were loaded, the test would still pass as it never checks if it
was loaded or not.

See #799.
2015-06-23 10:12:30 +02:00
Florian Bruhin
8e0589af69 Add changelog entry. 2015-06-23 09:47:07 +02:00
Florian Bruhin
8664fc4102 Add a --noconftest option. 2015-06-23 09:38:54 +02:00
holger krekel (rather uses bitbucket/hpk42)
755d7b3787 Merge pull request #798 from radarhere/patch-1
Fixed typo
2015-06-23 09:00:42 +02:00
Andrew Murray
cba1ca6244 Fixed typo 2015-06-23 10:29:37 +10:00
holger krekel
cdd25c9512 bump version to 2.7, depend on py-1.4.29 2015-06-22 23:53:41 +02:00
holger krekel
898b63b665 change spelling, thanks Maciej Maciaszek 2015-06-22 11:34:00 +02:00
Anatoly Bubenkov
e947732bde Merge pull request #791 from pytest-dev/coverage-config
Improve coverage score by tweaking options
2015-06-21 00:54:25 +02:00
Bruno Oliveira
b0491e1a5e Merge pull request #792 from iKevinY/svg-badges
Use SVGs for all README badges
2015-06-20 16:10:55 -03:00
Kevin Yap
190326c186 Use SVGs for all README badges 2015-06-20 10:58:38 -07:00
Bruno Oliveira
0008bcb877 Add coveragerc file for coverage options 2015-06-20 00:58:41 -03:00
holger krekel (rather uses bitbucket/hpk42)
080dfb9841 Merge pull request #788 from pytest-dev/issue-767
Fix issue where pytest.raises() doesn't always return Exception instance in py26
2015-06-19 13:41:44 +02:00
Bruno Oliveira
02421790bf Merge pull request #786 from edisongustavo/pytest-2.7
Fix Python2 bug with PrettyPrinter raising with sets of unsortable elements
2015-06-19 08:39:15 -03:00
Edison Gustavo Muenz
48d91def7e Added workaround to still show the diff on containers with unsortable elements.
fix issue #718
2015-06-19 07:51:59 -03:00
Bruno Oliveira
eb73db56c7 Fix issue where pytest.raises() doesn't always return Exception instance in py26
Fixes #767
2015-06-19 07:46:47 -03:00
Bruno Oliveira
80b5ebc398 Merge pull request #790 from pytest-dev/irc-notifications
Adjust IRC notifications to use #pytest.
2015-06-19 07:31:50 -03:00
Florian Bruhin
94332ed1d8 Adjust IRC notifications to use #pytest.
It seems #pytest-dev is quite dead. Also I set it to only send notifications on
changes (i.e. success -> failure and vice-versa).
2015-06-19 11:37:10 +02:00
holger krekel
13c01193d6 Merge remote-tracking branch 'origin/pytest-2.7' 2015-06-19 10:03:37 +02:00
holger krekel (rather uses bitbucket/hpk42)
26f590babe Merge pull request #789 from pytest-dev/issue-114
Skip reports refer to pytest code instead of original test functions
2015-06-19 10:02:58 +02:00
Bruno Oliveira
f90b2f845c unittest.SkipTest now reports original function location
Fix #748
2015-06-18 22:27:43 -03:00
Bruno Oliveira
9d4e0365da Skipif marker report now refers to the skipped function
Fix #114
2015-06-18 21:59:44 -03:00
Anatoly Bubenkov
4d6fef36f4 Merge branch 'pytest-2.7' 2015-06-17 13:42:41 +02:00
Anatoly Bubenkov
0431b8bb74 Merge pull request #784 from pytest-dev/create-dirs-xml-log
Automatically create directories for junit-xml and resultlog
2015-06-17 13:28:07 +02:00
Bruno Oliveira
2653024409 Use os.path.isdir instead of os.path.exists
As suggested during review
2015-06-17 08:08:03 -03:00
Bruno Oliveira
923174718e Allow failure of py27-subprocess tox-env while on pytest-2.7
"inprocess by default" was introduced in 2.8, and the current travis
configuration was backported from master so this specific
tox-env will remain disabled while pytest-2.7 maintenance branch
lasts.
2015-06-17 00:53:38 -03:00
Anatoly Bubenkov
73f37d0989 split travis jobs
Conflicts:
	README.rst
2015-06-17 00:22:34 -03:00
Anatoly Bubenkov
0722b95e53 use travis containers 2015-06-17 00:21:32 -03:00
Bruno Oliveira
ae89436d97 Remove duplicated author from AUTHORS 2015-06-17 00:05:56 -03:00
Bruno Oliveira
af77a23501 Add docs for new tests 2015-06-17 00:04:25 -03:00
Bruno Oliveira
2a1424e563 Merge branch 'pytest-2.7' of github.com:curzona/pytest into pytest-2.7
Conflicts:
	AUTHORS
	CHANGELOG
2015-06-17 00:02:26 -03:00
curzona
1871d526ac Update CHANGELOG and AUTHORS 2015-06-16 19:33:26 -07:00
curzona
9346e18d8c Test creating directory for junit-xml and resultlog 2015-06-16 19:30:43 -07:00
curzona
1db5c95414 Automatically create directory for results 2015-06-16 19:30:19 -07:00
Punyashloka Biswal
0c05b906d4 backport fix for #713 2015-06-16 21:27:22 -03:00
Bruno Oliveira
f04e01f55f Sort AUTHORS names 2015-06-16 21:20:43 -03:00
Bruno Oliveira
a0da7b9774 Mention issue 713 in the test's docstring 2015-06-16 20:42:49 -03:00
Punyashloka Biswal
0b0406fa85 Handle reports that don't have a reprcrash
Closes #713 (which happens because ReprFailDoctest
doesn't have a reprcrash)
2015-06-16 18:53:20 -04:00
Anatoly Bubenkov
f1245b094f Merge pull request #779 from pytest-dev/coveralls-shields-badge
Use shields.io badge for coveralls
2015-06-16 04:09:49 +02:00
Bruno Oliveira
c840e5c06e Use shields.io badge for coveralls
All badges have a similar look
2015-06-15 23:08:17 -03:00
Anatoly Bubenkov
c7573a92b2 Merge pull request #778 from pytest-dev/pypi-badge
Use pypi version badge from shields.io
2015-06-16 04:05:30 +02:00
Bruno Oliveira
84a476af87 Use pypi version badge from shields.io
The old one from pypip.in seems to always be broken as of late
2015-06-15 23:03:53 -03:00
Anatoly Bubenkov
e890d60459 appveyor badge 2015-06-16 03:28:12 +02:00
Anatoly Bubenkov
5dbe565b6d Merge branch 'master' of github.com:pytest-dev/pytest 2015-06-16 03:25:39 +02:00
Anatoly Bubenkov
8f09904c06 appveyor scripts 2015-06-16 03:25:28 +02:00
Anatoly Bubenkov
c0db143d4a Merge pull request #777 from bubenkoff/appveyor-integration
appveyor integration
2015-06-16 03:05:14 +02:00
Anatoly Bubenkov
534dbacc80 appveyor integration 2015-06-16 02:58:07 +02:00
Anatoly Bubenkov
af439e7d74 Merge pull request #775 from bubenkoff/pass-coveralls-env
pass coveralls env
2015-06-16 02:39:17 +02:00
Anatoly Bubenkov
b38cadbecd pass coveralls env 2015-06-16 02:38:52 +02:00
Anatoly Bubenkov
edfadf3a7c pass coveralls env 2015-06-16 01:12:24 +02:00
Anatoly Bubenkov
ae66683998 Merge pull request #773 from pytest-dev/remove-hg-files
Remove hgignore and hgtags files
2015-06-16 01:06:47 +02:00
Bruno Oliveira
4a8a4cb21b Merge pull request #770 from bubenkoff/add-travis-badge
add travis badge
2015-06-15 20:04:32 -03:00
Anatoly Bubenkov
ccebf8e1d6 add travis badge 2015-06-16 01:03:38 +02:00
Bruno Oliveira
8663be34b4 Remove hgignore and hgtags files 2015-06-15 20:02:41 -03:00
Bruno Oliveira
502a372405 Merge pull request #771 from pytest-dev/use-travis-containers
use travis containers
2015-06-15 19:56:34 -03:00
Bruno Oliveira
088ead2477 Merge pull request #772 from bubenkoff/split-travis-jobs
split travis jobs
2015-06-15 19:56:20 -03:00
Anatoly Bubenkov
f2494ecb3d split travis jobs 2015-06-16 00:49:55 +02:00
Anatoly Bubenkov
e9e01f2c9c use travis containers 2015-06-16 00:22:16 +02:00
Bruno Oliveira
d4e77faea7 Update sidebar links from Bitbucket to GitHub 2015-06-15 19:18:03 -03:00
Bruno Oliveira
ff58ae66c0 Update CONTRIBUTING documentation with GitHub instructions 2015-06-15 18:54:44 -03:00
Bruno Oliveira
b40f760cc3 Change links from bitbucket to GitHub 2015-06-15 18:28:31 -03:00
Floris Bruynooghe
42e7456780 Merged in RonnyPfannschmidt/pytest/regendoc-upgrade (pull request #303)
use regendoc normalization and regenerate docs
2015-06-10 10:44:38 +01:00
Ronny Pfannschmidt
43d27ec7ed use regendoc normalization and regenerate docs
--HG--
branch : regendoc-upgrade
2015-06-06 23:30:49 +02:00
Ronny Pfannschmidt
645ddc917f make regendoc a direct requirement 2015-06-06 14:15:23 +02:00
Ronny Pfannschmidt
33a7f03ccc sort AUTHORS and add Janne 2015-06-06 13:38:25 +02:00
Ronny Pfannschmidt
8b53a72dde add Janne`s changes to CHANGELOG 2015-06-06 11:53:34 +02:00
Ronny Pfannschmidt
8ee73e028f Merged in jpvanhal/pytest/include-setup-teardown-duration-in-junitxml (pull request #287)
Include setup and teardown in junitxml test durations
2015-06-06 11:50:37 +02:00
holger krekel
ca44e88e54 backport fixed issue735
--HG--
branch : pytest-2.7
2015-06-04 07:52:25 +02:00
holger krekel
304fc4f222 Merged in issue735 (pull request #302)
use NameConstant node when it exists (fixes #735)
2015-06-04 07:48:53 +02:00
holger krekel
09edb813bc Close branch issue735
--HG--
branch : issue735
2015-06-04 07:48:53 +02:00
Benjamin Peterson
f3ad2e4ad2 use NameConstant node when it exists (fixes #735)
--HG--
branch : issue735
2015-06-03 18:07:10 -05:00
holger krekel
a2bb2ff164 port typo fix 2015-06-03 23:43:12 +02:00
holger krekel
b5fd3cfb84 fix typo
--HG--
branch : pytest-2.7
2015-06-03 23:42:38 +02:00
holger krekel
c037f52878 fix issue741: make running output from testdir.run copy/pasteable 2015-06-03 23:40:42 +02:00
holger krekel
584cccf7ec Merged in nicoddemus/pytest/issue-741-pytester-output (pull request #293)
Make "running" output from testdir.run copy/pastable
2015-06-03 23:40:08 +02:00
holger krekel
c92467df2f add changelog for latest merge 2015-06-03 23:39:00 +02:00
holger krekel
ad08bf79e7 Merged in The-Compiler/pytest (pull request #298)
Fix monkeypatch.setenv with string and raising=False.
2015-06-03 23:37:26 +02:00
holger krekel
dc727832a0 Close branch pytest-2.7
--HG--
branch : pytest-2.7
2015-06-03 23:34:38 +02:00
holger krekel
22c62dbd90 Merged in flub/pytest (pull request #299)
Ignore emacs' .dir-locals.el file
2015-06-03 23:34:13 +02:00
Floris Bruynooghe
d220be8468 Use platform.python_version() to show Python version number
This results in something like "3.5.0b2" for non-final releases
while still being "3.5.0" for final releases.
2015-05-31 20:31:31 +01:00
Floris Bruynooghe
b09be4e3ef Merged in flub/pytest (pull request #297)
Merge 2.7.1 release changes into default
2015-05-31 20:27:13 +01:00
Darren Yin
efce2ababa add in missing finalizer to doc example 2015-05-21 19:15:52 -07:00
Floris Bruynooghe
773b8b69dd Ignore emacs' .dir-locals.el file
This can be used to e.g. automatically set a specific syntax
checker or virtualenv to activate.
2015-05-20 10:33:13 +01:00
Florian Bruhin
ff8d787cd5 Fix monkeypatch.setenv with string and raising=False.
Fixes #746.
2015-05-19 22:59:49 +02:00
Floris Bruynooghe
d299f0d99f Merge changes from 2.7.1 release
This is mostly doc examples regeneration, other changes are:
- Add 2.7.1 tag to .hgtags
- Update changelog to use real versions
- Update HOWTORELEASE.rst
- Fix rst in doc/en/adopt.txt
- Fix CHANGELOG and release-2.7.0 rst formatting
2015-05-19 21:43:50 +01:00
Floris Bruynooghe
bf837164b4 Add how to upload a wheel and tag the release
--HG--
branch : pytest-2.7
2015-05-19 09:32:21 +01:00
Floris Bruynooghe
f6d589caa1 Added tag 2.7.1 for changeset edc1d080bab5
--HG--
branch : pytest-2.7
2015-05-19 09:26:59 +01:00
Floris Bruynooghe
738c8762df Regenerate docs using 2.7.1
--HG--
branch : pytest-2.7
2015-05-19 01:54:24 +01:00
Floris Bruynooghe
3d990a6237 Fix rest formatting and set final version number
--HG--
branch : pytest-2.7
2015-05-19 01:53:06 +01:00
Floris Bruynooghe
b024dbe794 Remove unused link
--HG--
branch : pytest-2.7
2015-05-19 01:52:33 +01:00
Floris Bruynooghe
83a93e5e9a More updates/fixes to release process
Some of this is temporary, but this is how I got things to work.

--HG--
branch : pytest-2.7
2015-05-19 01:52:06 +01:00
Floris Bruynooghe
504b6b72a9 Set 2.7.1 version number
--HG--
branch : pytest-2.7
2015-05-19 01:50:32 +01:00
holger krekel
3401049dea remove adopt pytest note 2015-05-12 13:36:43 +02:00
holger krekel
45851644c1 remove adopt message
--HG--
branch : pytest-2.7
2015-05-12 13:34:32 +02:00
Bruno Oliveira
a7f880fa1f Make "running" output from testdir.run copy/pastable
fix 741

--HG--
branch : issue-741-pytester-output
2015-05-09 10:24:33 -03:00
holger krekel
c4bd3c672b another try at ignoring cx_freeze during other tox envs 2015-05-07 12:19:35 +02:00
holger krekel
52354b9ab5 try to ignore cx_freeze from regular pytest runs and see if travis is happy 2015-05-07 12:02:14 +02:00
holger krekel
3c187c844e make default depend on just published py/pluggy releases 2015-05-07 11:50:08 +02:00
Ronny Pfannschmidt
45ed4c726e Merged in hpk42/pytest-patches/plug30 (pull request #291)
use new pluggy api (now at 0.3.0) for adding hookcall monitoring
2015-05-07 11:24:39 +02:00
holger krekel
d9a44098ce use new pluggy api (now at 0.3.0) for adding hookcall monitoring
and reraise real keyboard interrupts during inline pytest runs
to allow for better stopping of the pytest tests.

--HG--
branch : plug30
2015-05-07 11:02:55 +02:00
holger krekel
b93abfb3d1 Merged in hpk42/pytest-patches/pluggy1 (pull request #290)
integrate pluggy as external plugin manager
2015-05-06 15:02:05 +02:00
holger krekel
bb8141e27c - make sure sub pytest runs use the same basetemp
- depend on pluggy < 0.3

--HG--
branch : pluggy1
2015-05-06 14:58:46 +02:00
holger krekel
bddc88f09e adapt to pluggy naming, rename pytest.hookspec_opts to pytest.hookspec,s ame with hookimpl_opts
--HG--
branch : pluggy1
2015-05-06 10:08:08 +02:00
holger krekel
5ea7f0342b Merged default into pluggy1
--HG--
branch : pluggy1
2015-05-05 22:29:53 +02:00
holger krekel
13db045fc4 merge 2015-05-05 21:55:39 +02:00
holger krekel
23538bcd31 - some more adaptation to most recent pluggy API
- avoid using pluggin underscore api
- show pluggy version in header

--HG--
branch : pluggy1
2015-05-05 21:53:04 +02:00
holger krekel
a4f2236b36 merge default
--HG--
branch : pluggy1
2015-05-05 14:52:16 +02:00
holger krekel
88538f13ba adapt for current API changes of pluggy
--HG--
branch : pluggy1
2015-05-04 15:08:41 +02:00
holger krekel
920b5afe45 now that we are going to have wheels, py source code might be not be installed
and the resulting genscript is useless

--HG--
branch : pluggy1
2015-05-04 14:42:01 +02:00
holger krekel
e4b9603fa8 Merged in famousgarkin/pytest/famousgarkin/monkeypatchchdir-docstring-punctuation-1430453576841 (pull request #288)
monkeypatch.chdir docstring punctuation
2015-05-04 14:28:32 +02:00
holger krekel
ef7f627573 merging pytest-2.7 2015-05-04 14:27:09 +02:00
Eric Siegerman
5a06330f61 Doc typo
--HG--
branch : pytest-2.7
2015-05-01 11:18:17 -04:00
Janne Vanhala
93628fc0eb Include setup and teardown in junitxml test durations
--HG--
branch : include-setup-teardown-duration-in-junitxml
2015-05-01 14:55:52 +03:00
famousgarkin
47936643c9 monkeypatch.chdir docstring punctuation
--HG--
branch : famousgarkin/monkeypatchchdir-docstring-punctuation-1430453576841
2015-05-01 04:13:06 +00:00
Floris Bruynooghe
11ad61ba07 Use current regendocs location
--HG--
branch : pytest-2.7
2015-05-01 00:27:47 +01:00
Floris Bruynooghe
129549fd5b Some more tweaks of how to release
--HG--
branch : pytest-2.7
2015-05-01 00:27:04 +01:00
Floris Bruynooghe
652a353319 Some small corrections to release process
--HG--
branch : pytest-2.7
2015-04-30 22:55:01 +01:00
Floris Bruynooghe
d6d8d5b9d1 Prepare docs for bugfix release
--HG--
branch : pytest-2.7
2015-04-30 22:54:43 +01:00
Floris Bruynooghe
aa2ffb9805 Merge fix for issue 731 from pytest-2.7 2015-04-30 21:13:03 +01:00
Floris Bruynooghe
7f554f50e3 Fix collapse false to look at unescaped braces only
Sometimes the repr of an object can contain the "\n{" sequence which is
used as a formatting language, so they are escaped to "\\n{".  But the
collapse-false code needs to look for the real "\n{" token instead of
simply "{" as otherwise it may get unbalanced braces from the object's
repr (sometimes caused by the collapsing of long reprs by saferepr).

Fixes issue #731.

--HG--
branch : pytest-2.7
2015-04-30 02:31:12 +01:00
holger krekel
25963e0544 adapt pytest to pluggy's decoratorclass branch
--HG--
branch : pluggy1
2015-04-29 16:40:52 +02:00
holger krekel
5ee7ee0850 adapt pytest to using pluggy (current master)
--HG--
branch : pluggy1
2015-04-29 16:40:51 +02:00
holger krekel
d6670bd6a8 Merged in hpk42/pytest-patches/testrefactor (pull request #284)
majorly refactor pytester and speed/streamline  tests
2015-04-29 16:32:28 +02:00
holger krekel
9aec5cd52d use runpytest() instead of runpytest_inprocess if a test can run as subprocess as well
--HG--
branch : testrefactor
2015-04-28 12:05:08 +02:00
holger krekel
4d5161444c merge
--HG--
branch : testrefactor
2015-04-28 11:56:57 +02:00
holger krekel
db5649ec6a streamline pytester API majorly:
- integrate conftest into pytester plugin
- introduce runpytest() to either call runpytest_inline (default) or
  runpytest_subprocess (python -m pytest)
- move testdir.inline_runsource1 to pdb tests
- strike some unneccessary methods.
- a new section "writing plugins" and some better pytester docs

--HG--
branch : testrefactor
2015-04-28 11:54:53 +02:00
holger krekel
a8afba054a - refine lsof checking
- make runpytest() create an inline testing process instead of
  a subprocess one

--HG--
branch : testrefactor
2015-04-28 11:54:46 +02:00
holger krekel
d3e363b97a - make API between runpytest() and inline_run() more similar
- shift a number of tests to become inline_run() tests

--HG--
branch : testrefactor
2015-04-28 11:54:45 +02:00
holger krekel
2e3e1e9b7e Merged in hpk42/pytest-patches/plugtestfix (pull request #283)
make test suite more tolerable against xdist causing warnings itself
2015-04-28 11:52:53 +02:00
holger krekel
424e5d1394 make test suite more tolerable against xdist causing warnings itself (which it does
currently)

--HG--
branch : plugtestfix
2015-04-27 15:06:47 +02:00
Floris Bruynooghe
2d8f115d8c Merged in hpk42/pytest-patches/more_plugin (pull request #282)
another major pluginmanager refactor and docs
2015-04-27 13:17:40 +01:00
holger krekel
b2d66b9e7b simplify load_setuptools_entrypoints and refine comments/docstrings
--HG--
branch : more_plugin
2015-04-27 14:10:33 +02:00
holger krekel
c54afbe42e deprecate and warn about __multicall__ usage in hooks, refine docs about hook ordering,
make hookwrappers respect tryfirst/trylast

--HG--
branch : more_plugin
2015-04-27 12:50:34 +02:00
holger krekel
dea1c96031 actually revert back to using older simpler method for subset hook
calling.  It is slightly more inefficient but easier to implement and
read.

--HG--
branch : more_plugin
2015-04-26 17:17:59 +02:00
holger krekel
16c922bf53 merge contribution fix 2015-04-26 01:06:17 +02:00
holger krekel
e94f5727c4 Merged in contributing-fix (pull request #281)
Remove duplicated step in CONTRIBUTING

--HG--
branch : pytest-2.7
2015-04-26 01:05:50 +02:00
holger krekel
35da487d46 Close branch contributing-fix
--HG--
branch : contributing-fix
2015-04-26 01:05:50 +02:00
holger krekel
d2ea7387f2 re-add tryfirst/trylast marker documentation, mark it as to be removed
--HG--
branch : more_plugin
2015-04-26 00:47:24 +02:00
holger krekel
8e009ee31c move consider_setuptools_entrypoints to core pluginmanager
--HG--
branch : more_plugin
2015-04-26 00:41:29 +02:00
holger krekel
0c961deeaa fix some doc strings
--HG--
branch : more_plugin
2015-04-26 00:22:34 +02:00
holger krekel
32165d82b1 introduce a new subset_hook_caller instead of remove make_hook_caller
and adapat and refine conftest/global plugin management accordingly

--HG--
branch : more_plugin
2015-04-26 00:10:52 +02:00
holger krekel
d422247433 specialize make_hook_caller to work with a subset of the registered plugins.
--HG--
branch : more_plugin
2015-04-25 22:13:42 +02:00
holger krekel
a042c57227 ensure proper get_name references
--HG--
branch : more_plugin
2015-04-25 20:42:41 +02:00
holger krekel
3a1374e69c simplify plugins bookkeeping further, refine API
--HG--
branch : more_plugin
2015-04-25 20:17:32 +02:00
holger krekel
1c0582eaa7 simplify addition of method and scanning of plugins
--HG--
branch : more_plugin
2015-04-25 18:15:42 +02:00
holger krekel
1e883f5979 simplify tracing mechanics by simply going through an indirection
--HG--
branch : more_plugin
2015-04-25 18:15:39 +02:00
holger krekel
9c5495832c avoid direct circular reference between config and pluginmanager
--HG--
branch : more_plugin
2015-04-25 18:14:41 +02:00
holger krekel
7364647f2f fix issue732: make sure removed plugins remove all hook callers.
--HG--
branch : more_plugin
2015-04-25 18:14:39 +02:00
holger krekel
4e116ed503 make pytest_plugin_registered a historic hook
--HG--
branch : more_plugin
2015-04-25 13:38:30 +02:00
holger krekel
e7a2e53108 Streamline data structures
--HG--
branch : more_plugin
2015-04-25 13:38:29 +02:00
holger krekel
2f8a1aed6e properly perform hook calls with extra methods
--HG--
branch : more_plugin
2015-04-25 11:29:11 +02:00
holger krekel
a63585dcab introduce historic hook spec which will memorize calls to a hook
in order to call them on later registered plugins

--HG--
branch : more_plugin
2015-04-25 11:29:11 +02:00
holger krekel
ea50ef1588 split plugin documentation into "using" and "writing plugins",
referencing each other.  Also add tryfirst/trylast examples.

--HG--
branch : more_plugin
2015-04-25 11:29:11 +02:00
holger krekel
d2a5c7f99b add documented hookimpl_opts and hookspec_opts decorators
so that one doesn't have to use pytest.mark or function-attribute setting anymore

--HG--
branch : more_plugin
2015-04-25 11:29:11 +02:00
holger krekel
bbbb6dc2e3 remove _do_register indirection between PluginManager and PytestPluginManager
--HG--
branch : more_plugin
2015-04-25 11:29:11 +02:00
holger krekel
f41528433b also incrementally remove plugins from hook callers
--HG--
branch : more_plugin
2015-04-25 11:29:11 +02:00
holger krekel
02a4042dca incrementally update hook call lists instead of regenerating the whole
list on each registered plugin

--HG--
branch : more_plugin
2015-04-25 11:29:11 +02:00
holger krekel
b03c1342ac allow to register plugins with hooks that are only added later
--HG--
branch : more_plugin
2015-04-25 11:29:11 +02:00
holger krekel
d8e91d9fee Merged in hpk42/pytest-patches/plugin_no_pytest (pull request #278)
Refactor pluginmanagement
2015-04-25 09:08:21 +02:00
Bruno Oliveira
a4ddec1157 Remove duplicated step in CONTRIBUTING
--HG--
branch : contributing-fix
2015-04-25 00:48:11 -03:00
holger krekel
9020bf48b7 remove useless check
--HG--
branch : plugin_no_pytest
2015-04-24 14:09:57 +02:00
holger krekel
237ac8562f minimize HookCaller attributes: avoid passing in hookrelay to HookCallers
--HG--
branch : plugin_no_pytest
2015-04-24 13:02:49 +02:00
holger krekel
feb4b2249a remove some redundancy when parsing import spec
--HG--
branch : plugin_no_pytest
2015-04-23 13:15:34 +02:00
holger krekel
95dd2eb1da streamline and document handling of builtin module special casing.
--HG--
branch : plugin_no_pytest
2015-04-23 12:39:11 +02:00
holger krekel
8fde2f98ae merge default
--HG--
branch : plugin_no_pytest
2015-04-23 12:35:15 +02:00
holger krekel
2b80caf1af merge cxfreeze fix 2015-04-23 12:07:12 +02:00
holger krekel
b4a655af52 Merged in nicoddemus/pytest/cx_freeze_ubuntu (pull request #280)
Fix py27-cxfreeze tox environment

--HG--
branch : pytest-2.7
2015-04-23 12:03:14 +02:00
Floris Bruynooghe
e450218daa Merge _pytest fixture reintroduction from pytest-2.7 branch
This was accidentally removed while some plugins depend on it.
2015-04-23 00:55:58 +01:00
Floris Bruynooghe
0cc9d7e6b6 Merged in hpk42/pytest-patches/reintroduce_pytest_fixture (pull request #279)
reintroduced _pytest fixture of the pytester plugin

--HG--
branch : pytest-2.7
2015-04-23 00:52:13 +01:00
Bruno Oliveira
6fe5493c3c Fix py27-cxfreeze tox environment
Use a custom script to install a patched version of cx_freeze,
as required in Ubuntu 14.04 systems

--HG--
branch : cx_freeze_ubuntu
2015-04-22 19:46:06 -03:00
holger krekel
a3aebfaefe accomodate Floris' comments. (The reason was i just reinstanted the old code :)
--HG--
branch : reintroduce_pytest_fixture
2015-04-22 21:04:36 +02:00
holger krekel
9d5182eaad reintroduced _pytest fixture of the pytester plugin which is used
at least by pytest-xdist.

--HG--
branch : reintroduce_pytest_fixture
2015-04-22 17:06:00 +02:00
holger krekel
27589eb7e1 reshuffle pluginmanager methods and add some docstrings.
--HG--
branch : plugin_no_pytest
2015-04-22 16:42:41 +02:00
holger krekel
d5db9faba8 shuffle PluginManager method order to first have the public API
and then the internal.

--HG--
branch : plugin_no_pytest
2015-04-22 16:34:42 +02:00
holger krekel
715a235b45 remove shutdown logic from PluginManager and add a add_cleanup() API
for the already existing cleanup logic of the config object.
This simplifies lifecycle management as we don't keep two
layers of shutdown functions and also simplifies the pluginmanager
interface.

also add some docstrings.

--HG--
branch : plugin_no_pytest
2015-04-22 16:33:20 +02:00
holger krekel
f746c190ac slight cleanup of plugin register() functionality
--HG--
branch : plugin_no_pytest
2015-04-22 14:54:28 +02:00
holger krekel
d632a0d5c2 merge conftest management into PytestPluginManager
--HG--
branch : plugin_no_pytest
2015-04-22 14:15:42 +02:00
holger krekel
894d7dca22 avoid undocumented special casing of "pytest_" prefix
--HG--
branch : plugin_no_pytest
2015-04-22 13:44:37 +02:00
holger krekel
20d6c0b560 simplify exception capturing
--HG--
branch : plugin_no_pytest
2015-04-22 13:37:42 +02:00
holger krekel
c08dfdc330 move bookkeeping of conftest plugins in core pluginmanager to PytestPluginManager
--HG--
branch : plugin_no_pytest
2015-04-22 13:33:01 +02:00
holger krekel
7049ebe4e2 avoid prepend to register api as it's redundant wrt to hooks
--HG--
branch : plugin_no_pytest
2015-04-22 13:31:46 +02:00
holger krekel
1ef49ac5ab minimize HookRelay to become a pure container, refactor initialization and
tests of plugin management to be a bit better split between pytest
and pytest-independent bits

--HG--
branch : plugin_no_pytest
2015-04-22 13:31:46 +02:00
holger krekel
db650de372 remove redundant py check as our setup.py excludes py <=1.4 already
--HG--
branch : plugin_no_pytest
2015-04-22 10:04:13 +02:00
Floris Bruynooghe
d9885388d0 Merge pull request #277 from pytest-2.7 branch
This removes the drone.io badge from the README as it doesn't make
sense to have it on the cheeseshop status page.

Fixes issue #726.
2015-04-21 15:04:08 +01:00
Floris Bruynooghe
a6b5f583a7 Merged in hpk42/pytest-patches/nodrone (pull request #277)
strike drone badge as it doesn't make sense on PYPI (where the README is rendered)

--HG--
branch : pytest-2.7
2015-04-21 15:01:42 +01:00
holger krekel
cfe9c9c169 strike drone badge as it doesn't make sense on PYPI (where the README is rendered)
--HG--
branch : nodrone
2015-04-21 15:55:48 +02:00
Floris Bruynooghe
240cd1f28d Merge cleaning of sys.modules after pytester.inline_run()
Merged in schettino72/pytest/pytester-inline-run-clean-sys-modules
(pull request #278).
2015-04-21 11:00:32 +01:00
Floris Bruynooghe
31af381c56 Merged in hpk42/pytest-patches/prefer_installed (pull request #275)
change test module importing behaviour to append to sys.path
2015-04-21 10:47:33 +01:00
Eduardo Schettino
bc0ecd1d06 pytester: add method `TmpTestdir.delete_loaded_modules()`
, and call it from ``inline_run()`` to allow temporary modules to be reloaded.

--HG--
branch : pytester-inline-run-clean-sys-modules
2015-04-21 10:18:04 +08:00
Eduardo Schettino
c792ab8aed fix regendoc repository location on requirements-docs.txt.
--HG--
branch : pytester-inline-run-clean-sys-modules
2015-04-21 10:16:04 +08:00
holger krekel
bac1ccd5b3 merge fix of issue553 on pytest-2.7 2015-04-17 22:32:49 +02:00
holger krekel
0b361c62c8 Merged in getsourcelines-error-issue-553-pytest2.7 (pull request #273)
Handle inspect.getsourcelines failures in FixtureLookupError

--HG--
branch : pytest-2.7
2015-04-17 22:31:55 +02:00
holger krekel
282f4ce920 Close branch getsourcelines-error-issue-553-pytest2.7
--HG--
branch : getsourcelines-error-issue-553-pytest2.7
2015-04-17 22:31:55 +02:00
holger krekel
5c8e5acf9d change test module importing behaviour to append to sys.path
instead of prepending.  This better allows to run test modules
against installated versions of a package even if the package
under test has the same import root.  In this example::

   testing/__init__.py
   testing/test_pkg_under_test.py
   pkg_under_test/

the tests will preferrably run against the installed version
of pkg_under_test whereas before they would always pick
up the local version.

--HG--
branch : prefer_installed
2015-04-17 22:25:35 +02:00
Floris Bruynooghe
fb07a09964 Merge pull request #274 from pytest-2.7
fixed regression to 2.6.4 which surfaced e.g. in lost stdout capture printing
2015-04-17 11:10:47 +01:00
Floris Bruynooghe
6e2bc7712c Merged in hpk42/pytest-patches/systemexit (pull request #274)
fixed regression to 2.6.4 which surfaced e.g. in lost stdout capture printing

--HG--
branch : pytest-2.7
2015-04-17 11:07:24 +01:00
holger krekel
173bd13ece backport Y->y fix from floris
--HG--
branch : pytest-2.7
2015-04-17 11:57:09 +02:00
holger krekel
e04273df57 fixed regression to 2.6.4 which surfaced e.g. in lost stdout capture printing
when tests raised SystemExit.

--HG--
branch : systemexit
2015-04-17 11:47:29 +02:00
Bruno Oliveira
15328c04eb Handle inspect.getsourcelines failures in FixtureLookupError
Fixes #553

--HG--
branch : getsourcelines-error-issue-553-pytest2.7
2015-04-15 19:31:22 -03:00
Floris Bruynooghe
c7a60af666 Use capital Y as the tests look for that 2015-04-13 09:08:10 +01:00
Floris Bruynooghe
09dff73607 Merge pytest-2.7 for issue660 fix 2015-04-13 00:04:53 +01:00
Floris Bruynooghe
6591d7a209 Merged in issue660 (pull request #268)
fix issue660

--HG--
branch : pytest-2.7
2015-04-13 00:00:40 +01:00
Floris Bruynooghe
220d177e76 Close branch issue660
--HG--
branch : issue660
2015-04-13 00:00:40 +01:00
Floris Bruynooghe
ddffa9d1cc Merge wheel support from pytest-2.7 branch
Merged in pytest-2.7 (pull request #269).
2015-04-12 23:47:10 +01:00
Floris Bruynooghe
9cae9a2a97 Add wheel support in the changelog
--HG--
branch : pytest-2.7
2015-04-12 23:36:43 +01:00
Floris Bruynooghe
129bb5393b Do imports at the head of the file
--HG--
branch : pytest-2.7
2015-04-12 23:36:28 +01:00
Floris Bruynooghe
bf0b70aa66 Some docstrings for the pytester plugin
These aren't quite complete but are a jolly good start anyway.  It
seems better to commit this now then leave it lingering until it
gets lost.
2015-04-11 17:07:37 +01:00
Ionel Cristian Maries
db08dcab14 Rename function and add nice docstring.
--HG--
branch : pytest-2.7
2015-04-10 22:11:17 +03:00
Ionel Cristian Maries
edd6c0f9b3 Make argpase a dependency for python 3.0 and 3.1 too.
--HG--
branch : pytest-2.7
2015-04-10 21:59:47 +03:00
Ionel Cristian Maries
a58cb3b95c Test for setuptools 0.7.2, turns out there's no 0.7 release on pypi.
--HG--
branch : pytest-2.7
2015-04-10 21:58:59 +03:00
Ionel Cristian Maries
40a682476d Improve version test (use pkg_resources to compare versions). Also log failures to stderr.
--HG--
branch : pytest-2.7
2015-04-10 21:44:27 +03:00
Ionel Cristian Maries
b7d43c5a5d Add support for building proper wheels (universal and proper dependency evnironment markers for argparse/colorama if setuptools is new-ish).
--HG--
branch : pytest-2.7
2015-04-10 21:08:50 +03:00
holger krekel
236cac86b1 add me as author of PR
--HG--
branch : issue660
2015-04-04 16:36:32 +02:00
holger krekel
79c0515945 merge pytest-2.7 branch
--HG--
branch : issue660
2015-04-04 16:35:14 +02:00
holger krekel
74019025b9 shift default to 2.8.0.dev 2015-04-04 16:32:25 +02:00
holger krekel
0e916460d8 add changelog entries
--HG--
branch : pytest-2.7
2015-04-04 16:29:38 +02:00
holger krekel
1ab93a5f1d Merged in release-checklist (pull request #266)
add a release checklist
2015-04-04 16:27:07 +02:00
holger krekel
69a0eacb4f Close branch release-checklist
--HG--
branch : release-checklist
2015-04-04 16:27:07 +02:00
holger krekel
d9d932aef5 Merged in yield-experimental-docs (pull request #267)
Removed note about yield fixtures being experimental
2015-04-04 16:25:24 +02:00
holger krekel
421c43a873 Close branch yield-experimental-docs
--HG--
branch : yield-experimental-docs
2015-04-04 16:25:24 +02:00
Bruno Oliveira
6558c7245f Brought back discussion session
Reworded it a bit to bring it to par with the current status

--HG--
branch : yield-experimental-docs
2015-04-03 19:44:06 -03:00
Bruno Oliveira
b3d646455a Fixed straightforward spelling
--HG--
branch : yield-experimental-docs
2015-04-03 17:06:51 -03:00
Bruno Oliveira
161d9e5971 Removed "discussion" session
Kept a note about exceptions after yield not being reraised

--HG--
branch : yield-experimental-docs
2015-04-03 16:59:33 -03:00
Bruno Oliveira
30260361dd Reviewed wording about yield being a "new" feature
--HG--
branch : yield-experimental-docs
2015-04-03 16:55:10 -03:00
Bruno Oliveira
c1b935adbd Removed note about yield fixtures being experimental
--HG--
branch : yield-experimental-docs
2015-04-03 16:28:20 -03:00
holger krekel
7f05cd8bfc more streamlining of release checklist
--HG--
branch : release-checklist
2015-04-02 10:55:24 +02:00
holger krekel
b55d66d0cf - avoid setting of versions and targets in conf.py and Makefile
as discussed on pytest-dev

- "make help" now prints pytest specific information.

- add a "_getdoctarget.py" helper

- make ``setup.py`` read the version from ``_pytest/__init__.py``

--HG--
branch : release-checklist
2015-04-02 10:38:25 +02:00
holger krekel
3ed3e51997 fix issue660: properly report fixture scope mismatches independent
from fixture argument ordering.

--HG--
branch : issue660
2015-04-01 18:42:48 +02:00
holger krekel
81d7883884 refine release process according to Bruno's feedback
--HG--
branch : release-checklist
2015-03-27 09:27:31 +01:00
holger krekel
ad137d46a2 add a release checklist
--HG--
branch : release-checklist
2015-03-26 14:04:33 +01:00
holger krekel
e16983d265 bump versions to dev, including changing the doc-install target to "dev"
instead of "latest"
2015-03-26 13:48:59 +01:00
holger krekel
fd6ff9bc01 Added tag 2.7.0 for changeset 7ed701fa2fb5 2015-03-26 13:44:58 +01:00
holger krekel
d2a8866bce bump verrsion to python2.7, fix a too precise test for windows, regen docs 2015-03-26 09:34:10 +01:00
Anatoly Bubenkov
c69978fbb0 revert verbosity 2015-03-24 13:41:49 +01:00
Anatoly Bubenkov
c1eaa72883 correct config 2015-03-24 11:56:59 +01:00
Anatoly Bubenkov
55f3ffd2ba add verbosity to the tests 2015-03-24 10:51:45 +00:00
Anatoly Bubenkov
55eb82c434 fix pep257 2015-03-23 21:28:29 +01:00
Anatoly Bubenkov
d94a29e866 merge almarklein/default 2015-03-23 21:25:10 +01:00
Anatoly Bubenkov
a7d2a82caa merge pfctdayelise/issue463 2015-03-23 20:42:16 +01:00
Anatoly Bubenkov
ce95437dee merge with default
--HG--
branch : issue463
2015-03-23 20:41:27 +01:00
Brianna Laugher
3ba3112a85 update changelog
--HG--
branch : issue463
2015-03-23 20:28:04 +01:00
Brianna Laugher
c019e489d2 Change docstring style
--HG--
branch : issue463
2015-03-23 20:27:53 +01:00
Brianna Laugher
43e4fcf6dd Raise specific MarkerError rather than generic ValueError
--HG--
branch : issue463
2015-03-23 20:01:58 +01:00
tigeraniya
65ca554230 duplicate assignment 2015-03-23 20:47:34 +05:30
holger krekel
52a2999a91 merge 2015-03-23 10:10:26 +01:00
holger krekel
f3580bee2d fix issue435: make reload() work when assert rewriting is active.
Thanks Daniel Hahler.
2015-03-23 10:08:47 +01:00
Brianna Laugher
deb163d237 Change string format syntax from {} to {0} for py2.6
--HG--
branch : issue463
2015-03-21 23:57:06 +01:00
Brianna Laugher
6f81602ba2 Use hasattr instead of try/except
--HG--
branch : issue463
2015-03-21 23:30:13 +01:00
Brianna Laugher
ac17f20d98 #463
Raise a ValueError early if user misspells 'parametrize' as 'parameterize'.

--HG--
branch : issue463
2015-03-21 23:06:25 +01:00
Almar Klein
a9b7de8bf0 address reviewer comments 2015-03-21 17:26:23 +01:00
Almar Klein
0fc75c9622 Storing sys.last_traceback: test, docs and changelog 2015-03-21 17:06:24 +01:00
Almar Klein
9726fafa98 allow postmortem debugging on failed test 2015-03-21 09:26:35 +01:00
Floris Bruynooghe
cf7bb70809 Mention requirement for licensing information
Repositories should always have proper licensing information.
2015-03-19 11:53:32 +00:00
holger krekel
03c3930734 add a project someone reported a while ago 2015-03-17 13:21:44 +01:00
holger krekel
25a4d7d882 remove default-verbose running 2015-03-17 13:19:26 +01:00
Bruno Oliveira
eead0365b5 Merged in parametrized-fixture-override (pull request #257)
allow to override parametrized fixtures with non-parametrized ones and vice versa
2015-03-12 09:40:56 -03:00
Bruno Oliveira
eb3b989286 Close branch parametrized-fixture-override
--HG--
branch : parametrized-fixture-override
2015-03-12 09:40:56 -03:00
Anatoly Bubenkov
cdb46e9f16 Merged in blueyed/pytest/strip-docstrings-from-fixtures (pull request #260)
Strip docstrings in output with `--fixtures`
2015-03-12 08:12:43 +01:00
Anatoly Bubenkov
ead25634fe Merged in link-update (pull request #261)
update website sidebar links for repo move
2015-03-12 08:11:46 +01:00
Anatoly Bubenkov
3f3521c826 Close branch link-update
--HG--
branch : link-update
2015-03-12 08:11:46 +01:00
Benjamin Peterson
0c2dfb9683 update website sidebar links for repo move
--HG--
branch : link-update
2015-03-11 20:04:14 -05:00
Daniel Hahler
5d6b0a59c0 Strip docstrings in output with --fixtures
Fixes https://bitbucket.org/pytest-dev/pytest/issue/550.

--HG--
branch : strip-docstrings-from-fixtures
2015-03-04 17:00:24 +01:00
Daniel Hahler
c629f6b18b Fix reload() with modules handled via python_files
If a module exists in `sys.modules` already, `load_module` has to return it.

Fixes https://bitbucket.org/pytest-dev/pytest/issue/435

--HG--
branch : fix-reload
2015-03-04 16:21:27 +01:00
Anatoly Bubenkov
b48eda6d7d update regendocs
--HG--
branch : parametrized-fixture-override
2015-03-02 22:07:42 +01:00
Anatoly Bubenkov
81243657be add docs target
--HG--
branch : parametrized-fixture-override
2015-03-02 21:43:31 +01:00
Anatoly Bubenkov
24fe051803 use make develop in contribution guide
--HG--
branch : parametrized-fixture-override
2015-03-02 20:48:09 +01:00
Anatoly Bubenkov
16bcfa749e merge with upstream
--HG--
branch : parametrized-fixture-override
2015-03-02 20:18:31 +01:00
Anatoly Bubenkov
d083153548 fix typo
--HG--
branch : parametrized-fixture-override
2015-03-02 20:18:06 +01:00
Ronny Pfannschmidt
41e6b04f0b Merged in eks/pytest/issue616 (pull request #258)
Add comments
2015-03-02 14:38:10 +01:00
Anatoly Bubenkov
33c2a3a3e5 make loop more readable
--HG--
branch : parametrized-fixture-override
2015-03-02 08:55:57 +01:00
Eric Siegerman
97d5da5c99 Add comments
--HG--
branch : issue616
2015-03-01 17:32:00 -05:00
Anatoly Bubenkov
d1005ebb8f document fixture override techniques
--HG--
branch : parametrized-fixture-override
2015-03-01 18:54:24 +01:00
Anatoly Bubenkov
c4623939af support override of the parametrized fixture on the test level
--HG--
branch : parametrized-fixture-override
2015-03-01 15:15:37 +01:00
Anatoly Bubenkov
060609317a allow to override parametrized fixtures with non-parametrized ones and vice versa
--HG--
branch : parametrized-fixture-override
2015-03-01 13:54:43 +01:00
Anatoly Bubenkov
bae0f7f46a Merged in plugincompat-move (pull request #256)
moved pytest-plugs to plugincompat
2015-02-28 21:32:57 +01:00
Anatoly Bubenkov
09a50b6e6d Close branch plugincompat-move
--HG--
branch : plugincompat-move
2015-02-28 21:32:57 +01:00
Bruno Oliveira
200fe072f1 moved pytest-plugs to plugincompat
also plugins_index is ready for 2.7.0 release

--HG--
branch : plugincompat-move
2015-02-28 17:22:42 -03:00
holger krekel
ff9ec13ddb mention "pytestdotorg" twitter account 2015-02-28 20:02:31 +01:00
Ronny Pfannschmidt
9384ff38c9 Close branch issue616
--HG--
branch : issue616
2015-02-28 10:02:58 +01:00
Ronny Pfannschmidt
1e6e373913 Merged in issue616 (pull request #252)
fix issue616 - conftest visibility fixes.
2015-02-28 10:02:58 +01:00
holger krekel
e510eb0d45 a few more fixes 2015-02-27 16:42:03 +01:00
holger krekel
2e2d6a8bef make "Adopt pytest" note more prominent, fix link from plugins page. 2015-02-27 15:49:25 +01:00
Benjamin Peterson
44cd1c9862 merge dead branch 2015-02-27 09:34:33 -05:00
Benjamin Peterson
f6f084fa01 close branch
--HG--
branch : update-issues
2015-02-27 09:34:16 -05:00
holger krekel
b7d212c0c9 Merged in update-issues (pull request #255)
another update for get_issues.py to account for repository move
2015-02-27 15:32:13 +01:00
Benjamin Peterson
bda3e53df6 another update for get_issues.py to account for repository move
--HG--
branch : update-issues
2015-02-27 09:12:00 -05:00
Anatoly Bubenkov
9d4354c0a4 Merged in docs_community (pull request #254)
added documentationo on the new pytest-dev teams on bitbucket and
2015-02-27 14:58:27 +01:00
Anatoly Bubenkov
f99ddbfdf2 Close branch docs_community
--HG--
branch : docs_community
2015-02-27 14:58:27 +01:00
holger krekel
019dc14fc4 fix typos, clarify git/hg
--HG--
branch : docs_community
2015-02-27 13:38:45 +01:00
holger krekel
dbb58b39d9 switch to push to latest docs, because otherwise people get misled to old repos which don't exist anymore
--HG--
branch : docs_community
2015-02-27 12:53:14 +01:00
holger krekel
c3ca44b46f change links to go to the new pytest-dev bitbucket team's repo location
--HG--
branch : docs_community
2015-02-27 12:27:40 +01:00
holger krekel
f6df3b0b97 added documentationo on the new pytest-dev teams on bitbucket and
github.  See https://pytest.org/latest/contributing.html (tentative)
Thanks to Anatoly for pushing and initial work on this.

--HG--
branch : docs_community
2015-02-27 11:54:17 +01:00
holger krekel
e9d4853296 Merged contributing-community into default 2015-02-27 11:17:14 +01:00
holger krekel
28c2327f73 move conftest visibility tests and their setup into a class, accomodates @nicoddemus 's comment
--HG--
branch : issue616
2015-02-27 09:51:53 +01:00
holger krekel
8ed5b77aba fix issue650: introduce new --doctest-ignore-import-errors option courtesy
of Charles Cloud.
2015-02-26 22:02:49 +01:00
holger krekel
660b84a052 Merged in cpcloud/pytest/ignore-doctest-import-errors (pull request #243)
Add option to ignore import errors in doctests
2015-02-26 21:59:54 +01:00
holger krekel
d73e689991 fix issue616 - conftest visibility fixes. This is achieved by
refactoring how nodeid's are constructed.  They now are always
relative to the "common rootdir" of a test run which is determined by
finding a common ancestor of all testrun arguments.

--HG--
branch : issue616
2015-02-26 21:56:44 +01:00
Phillip Cloud
c773ea664b Add test for command line usage
--HG--
branch : ignore-doctest-import-errors
2015-02-26 12:39:36 -05:00
holger krekel
aa757f7715 change "install" targets to use pytest-dev user so that Brianna, Ronny, me
and others can push to pytest.org
2015-02-23 16:14:54 +01:00
Ronny Pfannschmidt
4222a806f1 Merged in LucasC/pytest (pull request #247)
Fixing description of --dist=load in the --help documentation for the xdist plugin
2015-02-22 20:04:05 +01:00
Ronny Pfannschmidt
415c76b255 Merged in tush/pytest/junit-verbose-failures (pull request #240)
Made failure message in junit xml report more informative
2015-02-22 19:13:07 +01:00
Ronny Pfannschmidt
f1c9554f42 merge 2015-02-22 12:29:26 +01:00
Ronny Pfannschmidt
3d0b756877 merge in the 2.6 maintenance branch 2015-02-22 11:45:07 +01:00
holger krekel
ea4e9fa4e2 Merged in msabramo/pytest/remove_pdbpp_xfail (pull request #250)
Remove pdbpp xfails; don't seem necessary anymore
2015-02-19 12:42:23 +01:00
Marc Abramowitz
777bde7c60 Remove xfail_if_pdbpp_installed from test_pdb.py
--HG--
branch : remove_pdbpp_xfail
2015-02-19 03:22:05 -08:00
Marc Abramowitz
e19f3c260f Remove pdbpp xfails; don't seem necessary anymore
It seems that they're no longer necessary as the tests pass now

    [marca@marca-mac2 pytest]$ pip freeze
    backports.inspect==0.0.2
    fancycompleter==0.4
    funcsigs==0.2
    ordereddict==1.1
    -e hg+ssh://hg@bitbucket.org/antocuni/pdb@4bda65ac8f8cc9b1850e0301669aac39200f2f9a#egg=pdbpp-fix_pytest_doctest_NoneType_object_has_no_attribute_lower
    py==1.4.26
    Pygments==1.6
    pyrepl==0.8.4
    -e hg+ssh://hg@bitbucket.org/hpk42/pytest@5fc366c50b56a94638c959be8456d2cb3c7e7c1c#egg=pytest-dev
    wmctrl==0.1
    [marca@marca-mac2 pytest]$ py.test testing/test_doctest.py --tb=short
    ============================================================================= test session starts ==============================================================================
    platform darwin -- Python 2.7.9 -- py-1.4.26 -- pytest-2.7.0.dev1
    collected 22 items

    testing/test_doctest.py ......................

    ========================================================================== 22 passed in 1.61 seconds ===========================================================================

See https://bitbucket.org/antocuni/pdb/issue/24/doctests-fail-when-pdbpp-is-installed

--HG--
branch : remove_pdbpp_xfail
2015-02-19 03:03:56 -08:00
Lucas Cimon
5025478ac2 Fixing description of --dist=load in the --help documentation for the xdist plugin 2015-02-14 00:07:19 +01:00
Floris Bruynooghe
5b73de92c2 Merged in jeffwidman/pytest/jeffwidman/fix-faq-grammar-and-spelling-1423641159173 (pull request #246)
Fix FAQ grammar and spelling
2015-02-12 09:32:32 +00:00
Jeff Widman
6a237bd49f Changed back to "parametrized" to match the API
--HG--
branch : jeffwidman/fix-faq-grammar-and-spelling-1423641159173
2015-02-11 19:27:51 +00:00
Jeff Widman
89710b4901 Fix: FAQ grammar and spelling
--HG--
branch : jeffwidman/fix-faq-grammar-and-spelling-1423641159173
2015-02-11 07:52:48 +00:00
Floris Bruynooghe
459d6e610c Merged in davehunt/pytest/env-addopts (pull request #241)
Support setting configuration using the PYTEST_ADDOPTS environment variable
2015-02-09 16:33:36 +00:00
Dave Hunt
d1adbf4a5c Added documentation for PYTEST_ADDOPTS environment variable, updated CHANGELOG and AUTHORS.
--HG--
branch : env-addopts
2015-02-09 14:11:54 +00:00
holger krekel
c11cc36997 Merged in pfctdayelise/pytest_new/adopt-pytest-docs (pull request #244)
#676 Add docs page describing 'adopt pytest month'
2015-02-09 12:40:41 +01:00
Brianna Laugher
2b7fae2368 #676
Add  docs page describing 'adopt pytest month'

--HG--
branch : adopt-pytest-docs
2015-02-09 11:59:15 +01:00
Phillip Cloud
f2ca0b8170 Add option to ignore import errors in doctests
--HG--
branch : ignore-doctest-import-errors
2015-02-08 01:25:23 -05:00
Dave Hunt
912c8f0540 Use monkeypatch to set the PYTEST_ADDOPTS environment variable in the test.
--HG--
branch : env-addopts
2015-01-29 10:52:01 +00:00
Dave Hunt
8f12269db7 Use shlex to split the arguments from PYTEST_ADDOPTS.
--HG--
branch : env-addopts
2015-01-26 10:39:21 +00:00
Dave Hunt
7325a5fe2e Support setting configuration using the PYTEST_ADDOPTS environment variable.
--HG--
branch : env-addopts
2015-01-23 20:09:42 +00:00
tush home
9f4d0be895 Failure message in junit xml report now are more informative
--HG--
branch : junit-verbose-failures
2015-01-20 01:45:26 +03:00
holger krekel
394367e1d2 Merged in nicoddemus/pytest/pastebin-xdist (pull request #239)
Using pytest-xdist and --paste=all results in error
2015-01-19 22:34:43 +01:00
Bruno Oliveira
7e15fb7f2d Attempting to patch terminal only if terminalreporter is available
This fixes the flag "--paste=all" when running tests with xdist, as slaves would
attempt to patch a non-existing terminal during pytest_configure. Only the master
node has a terminalreporter installed.

--HG--
branch : pastebin-xdist
2015-01-19 19:20:01 -02:00
holger krekel
8859936301 Merged in okin/pytest/okin/only-one-import-on-one-line-if-this-shou-1421239108222 (pull request #238)
Only one import on one line.
2015-01-15 21:22:49 +01:00
okin
d580f5bac8 Only one import on one line.
If this should show good practices, the code should not set a bad example ;)

--HG--
branch : okin/only-one-import-on-one-line-if-this-shou-1421239108222
2015-01-14 12:38:37 +00:00
Ronny Pfannschmidt
5941b2e071 fix issue 655: crude workarounds around python2/3 exception leaks 2015-01-09 19:55:49 +01:00
James Tatum
bca19a1156 Cleaning up the docstrings in monkeypatch.py 2015-01-08 17:15:22 -08:00
Floris Bruynooghe
3df5989326 Revert pypi badge again as the svg broke
This reverts the pypi badge back to the pypip.in provided one
because the shields.io one failed to render properly (and also
wasn't on https).
2014-11-24 08:48:09 +00:00
Floris Bruynooghe
98b9de4ff1 Merged in techtonik/pytest/techtonik/readmerst-fix-link-to-pypi-1416719991514 (pull request #234)
README.rst fix link to PyPI
2014-11-24 08:41:53 +00:00
anatoly techtonik
0e248c9aed README.rst fix link to PyPI
--HG--
branch : techtonik/readmerst-fix-link-to-pypi-1416719991514
2014-11-23 05:20:01 +00:00
holger krekel
883b3ca2c1 add a py.test placeholder setup that errors out when trying to get installed
(this is registered at pypi.python.org already)
2014-10-30 11:38:13 +01:00
Floris Bruynooghe
e4bb48995a Mention fix for issue 615 in changelog
Fixes issue 615.
2014-10-28 13:31:09 +00:00
Floris Bruynooghe
1d15bb2880 Merged in tomviner/pytest/format_boolop_percent6 (pull request #231)
fix for issue #615: _format_boolop must escape %
2014-10-28 13:26:21 +00:00
holger krekel
959395b796 fix py26 compatibility 2014-10-27 10:02:15 +01:00
TomV
f6caf230f8 fix for issue615: _format_boolop must escape %
fix test for issue615: expression must eval False

--HG--
branch : format_boolop_percent6
2014-10-27 08:57:58 +00:00
holger krekel
f2cdbe776e bump version to 2.7 on default 2014-10-26 10:52:04 +01:00
holger krekel
97c9c10f3c remove "universal wheel" again until the py26/py27 difference (argparse) does not play a role anymore. 2014-10-25 07:21:21 +02:00
holger krekel
f74930db79 Merged in alex_gaynor/pytest-1/alex_gaynor/mark-pytest-as-a-universal-wheel-1414169551518 (pull request #230)
Mark pytest as a universal wheel
2014-10-24 20:04:13 +02:00
Alex Gaynor
5a88a9b22a Mark pytest as a universal wheel
--HG--
branch : alex_gaynor/mark-pytest-as-a-universal-wheel-1414169551518
2014-10-24 16:52:43 +00:00
holger krekel
d0d4759c96 fix changelog -- we should have all changes in trunk that are in 2.6.4 2014-10-24 17:19:54 +02:00
holger krekel
749316623b Added tag 2.6.4 for changeset f03b6de8325f
--HG--
branch : pytest-2.6
2014-10-24 17:12:24 +02:00
holger krekel
60cdb875ed backport pastebin fix
--HG--
branch : pytest-2.6
2014-10-24 15:24:44 +02:00
holger krekel
e0251ecb41 regen docs
--HG--
branch : pytest-2.6
2014-10-24 15:08:43 +02:00
holger krekel
0834b63560 backport fixed issue620 (doc for genscript)
--HG--
branch : pytest-2.6
2014-10-24 13:53:39 +02:00
Dinu Gherman
0c8569dcb0 Added explanation for the binary blob in runtests.py 2014-10-24 13:04:20 +02:00
holger krekel
5f2444d2a2 Merged in nicoddemus/pytest/fix-pastebin (pull request #228)
Fix --pastebin option
2014-10-23 09:08:30 +02:00
Bruno Oliveira
537dca477b Fixing --pastebin option by using a POST request instead of a XMLRPC call
fixes #614

--HG--
branch : fix-pastebin
2014-10-22 21:52:40 -02:00
Floris Bruynooghe
8480111012 closing branch
--HG--
branch : python-classes-glob
2014-10-22 23:21:58 +01:00
Floris Bruynooghe
c63dc62294 Mention issue351/PR161 in changelog 2014-10-22 23:20:27 +01:00
Floris Bruynooghe
8d19ccb56f Merged in pfctdayelise/pytest/issue351 (pull request #161)
Fixes issue351: Add ability to specify parametrize ids as a
callable, to generate custom test ids. + tests, docs

Hg branch merge
2014-10-22 23:18:01 +01:00
holger krekel
eac4514227 Merged in nicoddemus/pytest/python-classes-glob (pull request #225)
added support for glob-style patterns to python_classes and python_functions config options
2014-10-22 07:14:10 +02:00
Bruno Oliveira
16f0d100cc added changelog entry about glob-patterns in python_classes and python_functions
- also fixed small typo

--HG--
branch : python-classes-glob
2014-10-21 19:22:53 -02:00
Bruno Oliveira
0b620c304b checking that option contains glob characters before calling fnmatch
requested during code review

--HG--
branch : python-classes-glob
2014-10-20 18:36:31 -02:00
David Röthlisberger
7371d436d2 Fix assertion.rewrite on read-only filesystem 2014-10-17 21:18:37 +01:00
Bruno Oliveira
b928928942 added support for glob-style patterns to python_classes and python_functions config options
fixes #600

--HG--
branch : python-classes-glob
2014-10-16 19:27:10 -03:00
Floris Bruynooghe
4337e9c4ba Merged in tomviner/pytest/assert_percent_test (pull request #223)
Test for issue615: compound assert with percent
2014-10-15 20:44:24 +01:00
holger krekel
8dfd6c17e3 add changelog note for tom's PR about parametrized markers. 2014-10-14 10:12:45 +02:00
holger krekel
de7c97aa1e Merged in tomviner/pytest (pull request #222)
fix issue552: Add a note to the docs about marking single callables
2014-10-14 10:02:21 +02:00
holger krekel
ace668ae8f Merged in tomviner/pytest/some_spelling_fixes (pull request #224)
A few spelling fixes
2014-10-14 09:59:49 +02:00
TomV
857db415bc fix some spelling mistakes
--HG--
branch : some_spelling_fixes
2014-10-13 23:16:26 +01:00
TomV
f8623a6668 test for issue615: compound assert with percent
--HG--
branch : assert_percent_test
2014-10-13 09:26:18 +01:00
TomV
4cadc600d5 fix issue552: note about marking single callables 2014-10-12 19:07:03 +01:00
Bruno Oliveira
d62ec2985d Adding "auto" to help for "--tb" option 2014-10-10 20:43:33 +00:00
Bruno Oliveira
b2341899c5 Adding "auto" to help for "--tb" option 2014-10-10 20:43:33 +00:00
holger krekel
2c84e9ddac remove unused import 2014-10-09 22:45:33 +02:00
holger krekel
b7dd8eac8e remove unused import 2014-10-09 22:45:33 +02:00
holger krekel
61d0209093 fix changelog 2014-10-09 17:06:39 +02:00
holger krekel
8b9bb5ff54 fix changelog 2014-10-09 17:06:39 +02:00
holger krekel
cb2c89a070 Merged in HolgerPeters/pytest (pull request #221)
Make doctest flags configurable
2014-10-09 17:05:48 +02:00
holger krekel
cea9367739 Merged in HolgerPeters/pytest (pull request #221)
Make doctest flags configurable
2014-10-09 17:05:48 +02:00
Holger Peters
e1a66d54b9 link fix: Use restructured text :doc: link instead of html link 2014-10-09 16:59:42 +02:00
Holger Peters
a37f9f19d4 link fix: Use restructured text :doc: link instead of html link 2014-10-09 16:59:42 +02:00
holger krekel
91aa6ab0df add changelog entry for new hookwrapper mechanism 2014-10-09 12:29:57 +02:00
holger krekel
c85c5d05c5 add changelog entry for new hookwrapper mechanism 2014-10-09 12:29:57 +02:00
holger krekel
34df678bc3 some docs and refined semantics for wrappers 2014-10-09 12:21:01 +02:00
holger krekel
8c91ffc701 some docs and refined semantics for wrappers 2014-10-09 12:21:01 +02:00
holger krekel
eda39f361d re-scan methods during plugin register and unregister and not
during hook calling anymore.  Simplify register/getplugin api of PluginManager
2014-10-09 10:47:32 +02:00
holger krekel
c58770bfef re-scan methods during plugin register and unregister and not
during hook calling anymore.  Simplify register/getplugin api of PluginManager
2014-10-09 10:47:32 +02:00
holger krekel
d9858844c3 improve docstring, remove unused custom Exception 2014-10-09 09:55:46 +02:00
holger krekel
04b8111f8f improve docstring, remove unused custom Exception 2014-10-09 09:55:46 +02:00
holger krekel
0253f7b8d5 remove all occurences of "__multicall__" on hook impls in pytest/*.
also simplify pytest_runtest_markereport hook in _pytest/skipping.py
while touching the code anyway.
2014-10-08 20:23:40 +02:00
holger krekel
5999368002 remove all occurences of "__multicall__" on hook impls in pytest/*.
also simplify pytest_runtest_markereport hook in _pytest/skipping.py
while touching the code anyway.
2014-10-08 20:23:40 +02:00
Holger Peters
bf2f2dc2a6 Add a doctest for module docstrings 2014-10-08 15:54:08 +02:00
Holger Peters
787b0212d1 Add a doctest for module docstrings 2014-10-08 15:54:08 +02:00
Holger Peters
1de5af66da Add documentation for doctest flags and remove dead code 2014-10-08 15:48:41 +02:00
Holger Peters
61caa4f776 Add documentation for doctest flags and remove dead code 2014-10-08 15:48:41 +02:00
Holger Peters
9ca7e46a0a Add configuration option for doctest flags 2014-10-08 14:31:17 +02:00
Holger Peters
f66e0825b2 Add configuration option for doctest flags 2014-10-08 14:31:17 +02:00
holger krekel
f5f924d293 - refactor wrapped call support to also accomodate
pytest.mark.hookwrapper
- introduce a CallOutcome class to hold the result/excinfo status of
  calling a function.
- rename add_method_controller to add_method_wrapper
2014-10-08 11:27:14 +02:00
holger krekel
c3d1986101 - refactor wrapped call support to also accomodate
pytest.mark.hookwrapper
- introduce a CallOutcome class to hold the result/excinfo status of
  calling a function.
- rename add_method_controller to add_method_wrapper
2014-10-08 11:27:14 +02:00
Floris Bruynooghe
89de87dce1 Document the ids keyword for fixture parametrisation
--HG--
branch : issue351
2014-10-08 00:43:27 +01:00
Floris Bruynooghe
ab005a4261 Functional tests for id function
--HG--
branch : issue351
2014-10-08 00:11:32 +01:00
holger krekel
3d6ad054c0 merge pytest default 2014-10-07 18:11:15 +02:00
holger krekel
b6e619413f merge pytest default 2014-10-07 18:11:15 +02:00
holger krekel
a43fb9cd93 fix add_method_controller to deal properly in the event of exceptions.
add a docstring as well.
2014-10-07 16:16:47 +02:00
holger krekel
68f3818562 fix add_method_controller to deal properly in the event of exceptions.
add a docstring as well.
2014-10-07 16:16:47 +02:00
holger krekel
1ed1ef3c71 open up pytest-2.6 branch in case we want to release a 2.6.4 before 2.7.0 happens
--HG--
branch : pytest-2.6
2014-10-07 11:14:57 +02:00
holger krekel
b6475b058f Merged in bubenkoff/pytest/test_for_issue_604 (pull request #220)
Escape % character in the assertion message
2014-10-07 09:06:47 +02:00
holger krekel
e39556ada0 Merged in bubenkoff/pytest/test_for_issue_604 (pull request #220)
Escape % character in the assertion message
2014-10-07 09:06:47 +02:00
Anatoly Bubenkov
966c63d477 merge with default
--HG--
branch : test_for_issue_604
2014-10-07 01:06:15 +02:00
Anatoly Bubenkov
a298077461 merge with default 2014-10-07 01:06:15 +02:00
Anatoly Bubenkov
e1aed27c15 Escape % character in the assertion message. closes #604
--HG--
branch : test_for_issue_604
2014-10-07 01:01:21 +02:00
Anatoly Bubenkov
a759da0208 Escape % character in the assertion message. closes #604 2014-10-07 01:01:21 +02:00
holger krekel
6ab36592ea docs for "pytest_addhooks" hook. Thanks Bruno Oliveira.
updated plugin index docs.  Thanks Bruno Oliveira.

fix issue557: with "-k" we only allow the old style "-" for negation
at the beginning of strings and even that is deprecated.  Use "not" instead.
This should allow to pick parametrized tests where "-" appeared in the parameter.
2014-10-06 14:26:03 +02:00
holger krekel
c45b7012f5 docs for "pytest_addhooks" hook. Thanks Bruno Oliveira.
updated plugin index docs.  Thanks Bruno Oliveira.

fix issue557: with "-k" we only allow the old style "-" for negation
at the beginning of strings and even that is deprecated.  Use "not" instead.
This should allow to pick parametrized tests where "-" appeared in the parameter.
2014-10-06 14:26:03 +02:00
holger krekel
1d10db4bab cleanup core collection of python methods and remove unncessary cache 2014-10-06 14:06:17 +02:00
holger krekel
767e44ef29 cleanup core collection of python methods and remove unncessary cache 2014-10-06 14:06:17 +02:00
Anatoly Bubenkov
4a11edfb68 Merged in issue557 (pull request #218)
fix issue557: with "-k" we only allow the old style "-" for negation
2014-10-06 13:42:53 +02:00
Anatoly Bubenkov
e53bb1af45 Merged in issue557 (pull request #218)
fix issue557: with "-k" we only allow the old style "-" for negation
2014-10-06 13:42:53 +02:00
Anatoly Bubenkov
e5d0862fa5 Close branch issue557
--HG--
branch : issue557
2014-10-06 13:42:53 +02:00
Anatoly Bubenkov
2ed4c2135a Close branch issue557 2014-10-06 13:42:53 +02:00
holger krekel
8cfec56a82 simplify internal pytester machinery 2014-10-06 13:37:57 +02:00
holger krekel
818a412d29 simplify internal pytester machinery 2014-10-06 13:37:57 +02:00
holger krekel
39158957f4 fix issue557: with "-k" we only allow the old style "-" for negation
at the beginning of strings and even that is deprecated.  Use "not" instead.
This should allow to pick parametrized tests where "-" appeared in the parameter.

--HG--
branch : issue557
2014-10-06 12:11:48 +02:00
holger krekel
2cb0145bce fix issue557: with "-k" we only allow the old style "-" for negation
at the beginning of strings and even that is deprecated.  Use "not" instead.
This should allow to pick parametrized tests where "-" appeared in the parameter.
2014-10-06 12:11:48 +02:00
holger krekel
dfda9acd6d add some changelog entries 2014-10-06 11:56:56 +02:00
holger krekel
c6951d5184 add some changelog entries 2014-10-06 11:56:56 +02:00
holger krekel
f85648bbf8 Merged in nicoddemus/pytest/defer-hook-example (pull request #216)
Documentation for new hooks and how to use them
2014-10-06 11:55:35 +02:00
holger krekel
dd1ee22293 Merged in nicoddemus/pytest/defer-hook-example (pull request #216)
Documentation for new hooks and how to use them
2014-10-06 11:55:35 +02:00
holger krekel
9def8522f3 Merged in nicoddemus/pytest (pull request #214)
added plugins_index page generation to tox
2014-10-06 11:23:41 +02:00
holger krekel
a999fcd36a Merged in nicoddemus/pytest (pull request #214)
added plugins_index page generation to tox
2014-10-06 11:23:41 +02:00
pytry
24468a6f34 Added test for ValueError in custom assert message with % sign (issue #604)
https://bitbucket.org/hpk42/pytest/issue/604/valueerror-unsupported-format-character-in

--HG--
branch : test_for_issue_604
2014-10-05 14:49:15 +02:00
pytry
ba8ae427e2 Added test for ValueError in custom assert message with % sign (issue #604)
https://bitbucket.org/hpk42/pytest/issue/604/valueerror-unsupported-format-character-in
2014-10-05 14:49:15 +02:00
Bruno Oliveira
d6d2e6c615 Adding docs on how to properly add new hooks and using them in 3rd party plugins
--HG--
branch : defer-hook-example
2014-10-04 14:48:19 -03:00
Bruno Oliveira
7f93063945 Adding docs on how to properly add new hooks and using them in 3rd party plugins 2014-10-04 14:48:19 -03:00
holger krekel
63f070317c simplify method to record calls 2014-10-04 15:49:31 +02:00
holger krekel
3d84f35850 simplify method to record calls 2014-10-04 15:49:31 +02:00
holger krekel
3d794b6b38 factor out a small "wrapping" helper 2014-10-04 15:49:31 +02:00
holger krekel
d8f4663f49 factor out a small "wrapping" helper 2014-10-04 15:49:31 +02:00
Dj Gilcrease
34b9f90f35 Merged in dgilcrease/change-defaults-from-a-tuple-to-a-list-1412285169250 (pull request #1)
change the defaults from a tuple to a list so I can use config.addinivalue_line("norecursedirs", "...") to append new options in my own plugins
2014-10-02 14:33:48 -07:00
Dj Gilcrease
5ec1dd2609 Merged in dgilcrease/change-defaults-from-a-tuple-to-a-list-1412285169250 (pull request #1)
change the defaults from a tuple to a list so I can use config.addinivalue_line("norecursedirs", "...") to append new options in my own plugins
2014-10-02 14:33:48 -07:00
Dj Gilcrease
b691cbd7e2 Close branch dgilcrease/change-defaults-from-a-tuple-to-a-list-1412285169250
--HG--
branch : dgilcrease/change-defaults-from-a-tuple-to-a-list-1412285169250
2014-10-02 14:33:48 -07:00
Dj Gilcrease
d7ada40130 Close branch dgilcrease/change-defaults-from-a-tuple-to-a-list-1412285169250 2014-10-02 14:33:48 -07:00
Dj Gilcrease
2f141bbc54 change the defaults from a tuple to a list so I can use config.addinivalue_line("python_files", "...") to append new options in my own plugins
--HG--
branch : dgilcrease/change-defaults-from-a-tuple-to-a-list-1412285169250
2014-10-02 21:32:35 +00:00
Dj Gilcrease
a098226ee4 change the defaults from a tuple to a list so I can use config.addinivalue_line("python_files", "...") to append new options in my own plugins 2014-10-02 21:32:35 +00:00
Dj Gilcrease
fd2572a39d change the defaults from a tuple to a list so I can use config.addinivalue_line("norecursedirs", "...") to append new options in my own plugins
--HG--
branch : dgilcrease/change-defaults-from-a-tuple-to-a-list-1412285169250
2014-10-02 21:27:19 +00:00
Dj Gilcrease
05d4a3f9eb change the defaults from a tuple to a list so I can use config.addinivalue_line("norecursedirs", "...") to append new options in my own plugins 2014-10-02 21:27:19 +00:00
holger krekel
2161b54555 remove overhead for tracing of hook calls and remove some old unused code 2014-10-02 15:25:42 +02:00
holger krekel
69ff29bf44 remove overhead for tracing of hook calls and remove some old unused code 2014-10-02 15:25:42 +02:00
holger krekel
de83d35994 optimize argument slicing when calling plugin hooks 2014-10-01 14:55:54 +02:00
holger krekel
c7c4f62f77 optimize argument slicing when calling plugin hooks 2014-10-01 14:55:54 +02:00
holger krekel
e635f9f9b2 simplify _scan_plugin implementation and store argnames on HookCaller 2014-10-01 13:57:35 +02:00
holger krekel
f250e912eb simplify _scan_plugin implementation and store argnames on HookCaller 2014-10-01 13:57:35 +02:00
holger krekel
351931d5ca call scanning of plugins directly, code is shifted from helpconfig.py to core.py 2014-10-01 12:20:11 +02:00
holger krekel
28c785a0d1 call scanning of plugins directly, code is shifted from helpconfig.py to core.py 2014-10-01 12:20:11 +02:00
holger krekel
3de715ec13 refine internal management of plugins and conftest files 2014-10-01 12:19:11 +02:00
holger krekel
ea5fb0c153 refine internal management of plugins and conftest files 2014-10-01 12:19:11 +02:00
holger krekel
9e549a1acf removed outdated japanese docs from source tree. 2014-09-29 12:31:15 +02:00
holger krekel
c7a45d6eaf removed outdated japanese docs from source tree. 2014-09-29 12:31:15 +02:00
Bruno Oliveira
37631dbfa0 added plugins_index page generation to tox
Also minor improvements in the page:
- Removed version from plugin name
- Using "home" instead of "repo", seems more appropriate
- Reduced default verbosity
2014-09-27 12:28:29 -03:00
Bruno Oliveira
e9f240a9a3 added plugins_index page generation to tox
Also minor improvements in the page:
- Removed version from plugin name
- Using "home" instead of "repo", seems more appropriate
- Reduced default verbosity
2014-09-27 12:28:29 -03:00
Bruno Oliveira
531be22a87 Fixed minor typo in plugins.txt 2014-09-27 11:59:59 -03:00
Bruno Oliveira
be93fdf11d Fixed minor typo in plugins.txt 2014-09-27 11:59:59 -03:00
Floris Bruynooghe
f05cb934a9 Merged in bubenkoff/pytest/better-diff-on-verbose-2 (pull request #213)
Improve assertion failure reporting on iterables, by using ndiff and pprint.
2014-09-27 09:09:16 +01:00
Floris Bruynooghe
82d806deb6 Merged in bubenkoff/pytest/better-diff-on-verbose-2 (pull request #213)
Improve assertion failure reporting on iterables, by using ndiff and pprint.
2014-09-27 09:09:16 +01:00
Anatoly Bubenkov
72e6f55b45 Improve assertion failure reporting on iterables, by using ndiff and pprint.
--HG--
branch : better-diff-on-verbose-2
2014-09-27 01:29:47 +00:00
Anatoly Bubenkov
9a0f2a9fb7 Improve assertion failure reporting on iterables, by using ndiff and pprint. 2014-09-27 01:29:47 +00:00
holger krekel
49b7237581 bump version to 2.6.4.dev 2014-09-24 16:27:34 +02:00
holger krekel
814d348e7d add release announce 2014-09-24 16:00:06 +02:00
holger krekel
88c14cad94 Added tag 2.6.3 for changeset 2967aa416a4f 2014-09-24 14:59:57 +02:00
Anatoly Bubenkov
d99243c1a7 rename orgname to pytest-dev
--HG--
branch : contributing-community
2014-09-24 10:53:08 +00:00
Anatoly Bubenkov
8844d9d04f detalize the plugin development proposal
--HG--
branch : contributing-community
2014-09-06 02:37:48 +02:00
Anatoly Bubenkov
54e00429e4 plugin organization proposal
--HG--
branch : contributing-community
2014-08-20 00:52:40 +02:00
Brianna Laugher
4e35c00ab0 issue351: Add ability to specify parametrize ids as a callable, to generate custom test ids. + tests, docs
--HG--
branch : issue351
2014-04-17 15:08:49 -04:00
340 changed files with 19348 additions and 18283 deletions

7
.coveragerc Normal file
View File

@@ -0,0 +1,7 @@
[run]
omit =
# standlonetemplate is read dynamically and tested by test_genscript
*standalonetemplate.py
# oldinterpret could be removed, as it is no longer used in py26+
*oldinterpret.py
vendored_packages

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
CHANGELOG merge=union

8
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,8 @@
Thanks for submitting an issue!
Here's a quick checklist in what to include:
- [ ] Include a detailed description of the bug or suggestion
- [ ] `pip list` of the virtual environment you are using
- [ ] py.test and operating system versions
- [ ] Minimal example if possible

8
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,8 @@
Thanks for submitting a PR, your contribution is really appreciated!
Here's a quick checklist that should be present in PRs:
- [ ] Target: for bug or doc fixes, target `master`; for new features, target `features`
- [ ] Make sure to include one or more tests for your change
- [ ] Add yourself to `AUTHORS`
- [ ] Add a new entry to the `CHANGELOG` (choose any open position to avoid merge conflicts with other PRs)

5
.gitignore vendored
View File

@@ -16,6 +16,9 @@ include/
*.class
*.orig
*~
.hypothesis/
.eggs/
doc/*/_build
build/
@@ -23,10 +26,10 @@ dist/
*.egg-info
issue/
env/
.env/
3rdparty/
.tox
.cache
.coverage
.ropeproject
.idea

View File

@@ -1,37 +0,0 @@
# Automatically generated by `hgimportsvn`
syntax:glob
.svn
.hgsvn
# Ignore local virtualenvs
syntax:glob
lib/
bin/
include/
.Python/
.env/
# These lines are suggested according to the svn:ignore property
# Feel free to enable them by uncommenting them
syntax:glob
*.pyc
*.pyo
*.swp
*.html
*.class
*.orig
*~
doc/*/_build
build/
dist/
*.egg-info
issue/
env/
env3/
3rdparty/
.tox
.cache
.coverage
.ropeproject
*.sublime-*

73
.hgtags
View File

@@ -1,73 +0,0 @@
52c6d9e78777a5a34e813123997dfc614a1a4767 1.0.0b3
1c7aaa8c61f3b0945921a9acc7beb184201aed4b 1.0.0b4
1c7aaa8c61f3b0945921a9acc7beb184201aed4b 1.0.0b4
0000000000000000000000000000000000000000 1.0.0b4
0000000000000000000000000000000000000000 1.0.0b4
8cd6eb91eba313b012d6e568f37d844dc0751f2e 1.0.0b4
8cd6eb91eba313b012d6e568f37d844dc0751f2e 1.0.0b4
0000000000000000000000000000000000000000 1.0.0b4
2cc0507f117ffe721dff7ee026648cfce00ec92f 1.0.0b6
86f1e1b6e49bf5882a809f11edd1dbb08162cdad 1.0.0b8
86f1e1b6e49bf5882a809f11edd1dbb08162cdad 1.0.0b8
c63f35c266cbb26dad6b87b5e115d65685adf448 1.0.0b8
c63f35c266cbb26dad6b87b5e115d65685adf448 1.0.0b8
0eaa0fdf2ba0163cf534dc2eff4ba2e5fc66c261 1.0.0b8
e2a60653cb490aeed81bbbd83c070b99401c211c 1.0.0b9
5ea0cdf7854c3d4278d36eda94a2b68483a0e211 1.0.0
5ea0cdf7854c3d4278d36eda94a2b68483a0e211 1.0.0
7acde360d94b6a2690ce3d03ff39301da84c0a2b 1.0.0
6bd221981ac99103002c1cb94fede400d23a96a1 1.0.1
4816e8b80602a3fd3a0a120333ad85fbe7d8bab4 1.0.2
60c44bdbf093285dc69d5462d4dbb4acad325ca6 1.1.0
319187fcda66714c5eb1353492babeec3d3c826f 1.1.1
4fc5212f7626a56b9eb6437b5c673f56dd7eb942 1.2.0
c143a8c8840a1c68570890c8ac6165bbf92fd3c6 1.2.1
eafd3c256e8732dfb0a4d49d051b5b4339858926 1.3.0
d5eacf390af74553227122b85e20345d47b2f9e6 1.3.1
d5eacf390af74553227122b85e20345d47b2f9e6 1.3.1
8b8e7c25a13cf863f01b2dd955978285ae9daf6a 1.3.1
3bff44b188a7ec1af328d977b9d39b6757bb38df 1.3.2
c59d3fa8681a5b5966b8375b16fccd64a3a8dbeb 1.3.3
79ef6377705184c55633d456832eea318fedcf61 1.3.4
79ef6377705184c55633d456832eea318fedcf61 1.3.4
90fffd35373e9f125af233f78b19416f0938d841 1.3.4
e9e127acd6f0497324ef7f40cfb997cad4c4cd17 2.0.0
e4497c2aed358c1988cf7be83ca9394c3c707fa2 2.0.1
84e5c54b72448194a0f6f815da7e048ac8019d50 2.0.2
2ef82d82daacb72733a3a532a95c5a37164e5819 2.0.3
2ef82d82daacb72733a3a532a95c5a37164e5819 2.0.3
c777dcad166548b7499564cb49ae5c8b4b07f935 2.0.3
c777dcad166548b7499564cb49ae5c8b4b07f935 2.0.3
49f11dbff725acdcc5fe3657cbcdf9ae04e25bbc 2.0.3
49f11dbff725acdcc5fe3657cbcdf9ae04e25bbc 2.0.3
363e5a5a59c803e6bc176a6f9cc4bf1a1ca2dab0 2.0.3
e5e1746a197f0398356a43fbe2eebac9690f795d 2.1.0
5864412c6f3c903384243bd315639d101d7ebc67 2.1.2
12a05d59249f80276e25fd8b96e8e545b1332b7a 2.1.3
1522710369337d96bf9568569d5f0ca9b38a74e0 2.2.0
3da8cec6c5326ed27c144c9b6d7a64a648370005 2.2.1
92b916483c1e65a80dc80e3f7816b39e84b36a4d 2.2.2
3c11c5c9776f3c678719161e96cc0a08169c1cb8 2.2.3
ad9fe504a371ad8eb613052d58f229aa66f53527 2.2.4
c27a60097767c16a54ae56d9669a77925b213b9b 2.3.0
acf0e1477fb19a1d35a4e40242b77fa6af32eb17 2.3.1
8738b828dec53937765db71951ef955cca4c51f6 2.3.2
7fe44182c434f8ac89149a3c340479872a5d5ccb 2.3.3
ef299e57f24218dbdd949498d7e660723636bcc3 2.3.4
fc3a793e87ec907000a47ea0d3a372a2fe218c0a 2.3.5
b93ac0cdae02effaa3c136a681cc45bba757fe46 1.4.14
b93ac0cdae02effaa3c136a681cc45bba757fe46 1.4.14
0000000000000000000000000000000000000000 1.4.14
0000000000000000000000000000000000000000 1.4.14
0000000000000000000000000000000000000000 1.4.14
af860de70cc3f157ac34ca1d4bf557a057bff775 2.4.0
8828c924acae0b4cad2e2cb92943d51da7cb744a 2.4.1
8d051f89184bfa3033f5e59819dff9f32a612941 2.4.2
a064ad64d167508a8e9e73766b1a4e6bd10c85db 2.5.0
039d543d1ca02a716c0b0de9a7131beb8021e8a2 2.5.1
421d3b4d150d901de24b1cbeb8955547b1420483 2.5.2
60725b17a9d1af4100abb8be3f9f4ddf6262bf34 2.6.0
60725b17a9d1af4100abb8be3f9f4ddf6262bf34 2.6.0
88af949b9611494e2c65d528f9e565b00fb7e8ca 2.6.0
a4f9639702baa3eb4f3b16e162f74f7b69f3f9e1 2.6.1
a4f25c5e649892b5cc746d21be971e4773478af9 2.6.2

View File

@@ -1,11 +1,40 @@
sudo: false
language: python
python:
- '3.5'
# command to install dependencies
install: "pip install -U detox"
install: "pip install -U tox"
# # command to run tests
script: detox --recreate -i ALL=https://devpi.net/hpk/dev/
env:
matrix:
# coveralls is not listed in tox's envlist, but should run in travis
- TESTENV=coveralls
# note: please use "tox --listenvs" to populate the build matrix below
- TESTENV=linting
- TESTENV=py26
- TESTENV=py27
- TESTENV=py33
- TESTENV=py34
- TESTENV=py35
- TESTENV=pypy
- TESTENV=py27-pexpect
- TESTENV=py27-xdist
- TESTENV=py27-trial
- TESTENV=py35-pexpect
- TESTENV=py35-xdist
- TESTENV=py35-trial
- TESTENV=py27-nobyte
- TESTENV=doctesting
- TESTENV=py27-cxfreeze
script: tox --recreate -e $TESTENV
notifications:
irc:
- "chat.freenode.net#pytest-dev"
channels:
- "chat.freenode.net#pytest"
on_success: change
on_failure: change
skip_join: true
email:
- pytest-commit@python.org

114
AUTHORS
View File

@@ -3,45 +3,89 @@ merlinux GmbH, Germany, office at merlinux eu
Contributors include::
Ronny Pfannschmidt
Benjamin Peterson
Floris Bruynooghe
Jason R. Coombs
Wouter van Ackooy
Samuele Pedroni
Abhijeet Kasurde
Anatoly Bubenkoff
Brianna Laugher
Carl Friedrich Bolz
Armin Rigo
Maho
Jaap Broekhuizen
Maciek Fijalkowski
Guido Wesdorp
Brian Dorsey
Ross Lawley
Ralf Schmitt
Chris Lamb
Harald Armin Massa
Martijn Faassen
Ian Bicking
Jan Balster
Grig Gheorghiu
Bob Ippolito
Christian Tismer
Daniel Nuri
Graham Horler
Andreas Zeidler
Brian Okken
Katarzyna Jachim
Christian Theunert
Andy Freeland
Anthon van der Neut
Mark Abramowitz
Piotr Banaszkiewicz
Jurko Gospodnetić
Marc Schlaich
Armin Rigo
Aron Curzon
Aviv Palivoda
Benjamin Peterson
Bob Ippolito
Brian Dorsey
Brian Okken
Brianna Laugher
Bruno Oliveira
Carl Friedrich Bolz
Charles Cloud
Chris Lamb
Christian Theunert
Christian Tismer
Christopher Gilling
Daniel Grana
Andy Freeland
Trevor Bekolay
Daniel Hahler
Daniel Nuri
Dave Hunt
David Mohr
David Vierra
Edison Gustavo Muenz
Eduardo Schettino
Endre Galaczi
Elizaveta Shashkova
Eric Hunsberger
Eric Siegerman
Erik M. Bray
Florian Bruhin
Floris Bruynooghe
Gabriel Reis
Georgy Dyuldin
Graham Horler
Grig Gheorghiu
Guido Wesdorp
Harald Armin Massa
Ian Bicking
Jaap Broekhuizen
Jan Balster
Janne Vanhala
Jason R. Coombs
John Towler
Joshua Bronson
Jurko Gospodnetić
Katarzyna Jachim
Kevin Cox
Lee Kamentsky
Lukas Bednar
Maciek Fijalkowski
Maho
Marc Schlaich
Mark Abramowitz
Markus Unterwaditzer
Martijn Faassen
Martin Prusse
Matt Bachmann
Michael Aquilina
Michael Birtwell
Michael Droettboom
Nicolas Delaby
Pieter Mulder
Piotr Banaszkiewicz
Punyashloka Biswal
Quentin Pradet
Ralf Schmitt
Raphael Pierzina
Ronny Pfannschmidt
Ross Lawley
Ryan Wooden
Samuele Pedroni
Tom Viner
Trevor Bekolay
Wouter van Ackooy
David Díaz-Barquero
Eric Hunsberger
Simon Gomizelj
Russel Winder
Ben Webb
Alexei Kozlenok
Cal Leeming
Feng Ma

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +1,34 @@
============
Contributing
============
============================
Contribution getting started
============================
Contributions are highly welcomed and appreciated. Every little help counts,
so do not hesitate!
.. contents:: Contribution links
:depth: 2
Types of contributions
======================
.. _submitfeedback:
Feature requests and feedback
-----------------------------
Do you like pytest? Share some love on Twitter or in your blog posts!
We'd also like to hear about your propositions and suggestions. Feel free to
`submit them as issues <https://github.com/pytest-dev/pytest/issues>`_ and:
* Explain in detail how they should work.
* Keep the scope as narrow as possible. This will make it easier to implement.
.. _reportbugs:
Report bugs
-----------
Report bugs at https://bitbucket.org/hpk42/pytest/issues.
Report bugs for pytest in the `issue tracker <https://github.com/pytest-dev/pytest/issues>`_.
If you are reporting a bug, please include:
@@ -22,36 +38,31 @@ If you are reporting a bug, please include:
installed libraries and pytest version.
* Detailed steps to reproduce the bug.
Submit feedback for developers
------------------------------
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.
Do you like pytest? Share some love on Twitter or in your blog posts!
We'd also like to hear about your propositions and suggestions. Feel free to
`submit them as issues <https://bitbucket.org/hpk42/pytest/issues>`__ and:
* Set the "kind" to "enhancement" or "proposal" so that we can quickly find
about them.
* Explain in detail how they should work.
* Keep the scope as narrow as possible. This will make it easier to implement.
* If you have required skills and/or knowledge, we are very happy for
:ref:`pull requests <pull-requests>`.
.. _fixbugs:
Fix bugs
--------
Look through the BitBucket issues for bugs. Here is sample filter you can use:
https://bitbucket.org/hpk42/pytest/issues?status=new&status=open&kind=bug
Look through the GitHub issues for bugs. Here is sample filter you can use:
https://github.com/pytest-dev/pytest/labels/bug
:ref:`Talk <contact>` to developers to find out how you can fix specific bugs.
Don't forget to check the issue trackers of your favourite plugins, too!
.. _writeplugins:
Implement features
------------------
Look through the BitBucket issues for enhancements. Here is sample filter you
Look through the GitHub issues for enhancements. Here is sample filter you
can use:
https://bitbucket.org/hpk42/pytest/issues?status=new&status=open&kind=enhancement
https://github.com/pytest-dev/pytest/labels/enhancement
:ref:`Talk <contact>` to developers to find out how you can implement specific
features.
@@ -62,110 +73,181 @@ Write documentation
pytest could always use more documentation. What exactly is needed?
* More complementary documentation. Have you perhaps found something unclear?
* Documentation translations. We currently have English and Japanese versions.
* Docstrings. There's never too much of them.
* Documentation translations. We currently have only English.
* Docstrings. There can never be too many of them.
* Blog posts, articles and such -- they're all very appreciated.
You can also edit documentation files directly in the Github web interface
without needing to make a fork and local copy. This can be convenient for
small fixes.
.. _submitplugin:
Submitting Plugins to pytest-dev
--------------------------------
Pytest development of the core, some plugins and support code happens
in repositories living under the ``pytest-dev`` organisations:
- `pytest-dev on GitHub <https://github.com/pytest-dev>`_
- `pytest-dev on Bitbucket <https://bitbucket.org/pytest-dev>`_
All pytest-dev Contributors team members have write access to all contained
repositories. pytest core and plugins are generally developed
using `pull requests`_ to respective repositories.
The objectives of the ``pytest-dev`` organisation are:
* Having a central location for popular pytest plugins
* Sharing some of the maintenance responsibility (in case a maintainer no longer whishes to maintain a plugin)
You can submit your plugin by subscribing to the `pytest-dev mail list
<https://mail.python.org/mailman/listinfo/pytest-dev>`_ and writing a
mail pointing to your existing pytest plugin repository which must have
the following:
- PyPI presence with a ``setup.py`` that contains a license, ``pytest-``
prefixed name, version number, authors, short and long description.
- a ``tox.ini`` for running tests using `tox <http://tox.testrun.org>`_.
- a ``README.txt`` describing how to use the plugin and on which
platforms it runs.
- a ``LICENSE.txt`` file or equivalent containing the licensing
information, with matching info in ``setup.py``.
- an issue tracker for bug reports and enhancement requests.
If no contributor strongly objects and two agree, the repository can then be
transferred to the ``pytest-dev`` organisation.
Here's a rundown of how a repository transfer usually proceeds
(using a repository named ``joedoe/pytest-xyz`` as example):
* One of the ``pytest-dev`` administrators creates:
- ``pytest-xyz-admin`` team, with full administration rights to
``pytest-dev/pytest-xyz``.
- ``pytest-xyz-developers`` team, with write access to
``pytest-dev/pytest-xyz``.
* ``joedoe`` is invited to the ``pytest-xyz-admin`` team;
* After accepting the invitation, ``joedoe`` transfers the repository from its
original location to ``pytest-dev/pytest-xyz`` (A nice feature is that GitHub handles URL redirection from
the old to the new location automatically).
* ``joedoe`` is free to add any other collaborators to the
``pytest-xyz-admin`` or ``pytest-xyz-developers`` team as desired.
The ``pytest-dev/Contributors`` team has write access to all projects, and
every project administrator is in it. We recommend that each plugin has at least three
people who have the right to release to PyPI.
Repository owners can be assured that no ``pytest-dev`` administrator will ever make
releases of your repository or take ownership in any way, except in rare cases
where someone becomes unresponsive after months of contact attempts.
As stated, the objective is to share maintenance and avoid "plugin-abandon".
.. _`pull requests`:
.. _pull-requests:
Preparing Pull Requests on Bitbucket
=====================================
Preparing Pull Requests on GitHub
---------------------------------
There's an excellent tutorial on how Pull Requests work in the
`GitHub Help Center <https://help.github.com/articles/using-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
`BitBucket servers <https://bitbucket.org/hpk42/pytest/pull-requests>`__.
`GitHub servers <https://github.com/pytest-dev/pytest/pulls>`_.
Once you send pull request, we can discuss it's potential modifications and
even add more commits to it later on.
The primary development platform for pytest is BitBucket. You can find all
the issues there and submit your pull requests.
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:
1. Fork the
`pytest BitBucket repository <https://bitbucket.org/hpk42/pytest>`__. It's
#. Fork the
`pytest GitHub repository <https://github.com/pytest-dev/pytest>`__. It's
fine to use ``pytest`` as your fork repository name because it will live
under your user.
.. _virtualenvactivate:
#. Clone your fork locally using `git <https://git-scm.com/>`_ and create a branch::
2. Create and activate a fork-specific virtualenv
(http://www.virtualenv.org/en/latest/)::
$ virtualenv pytest-venv
$ source pytest-venv/bin/activate
.. _checkout:
3. Clone your fork locally using `Mercurial <http://mercurial.selenic.com/>`_
(``hg``) and create a branch::
$ hg clone ssh://hg@bitbucket.org/YOUR_BITBUCKET_USERNAME/pytest
$ git clone git@github.com:YOUR_GITHUB_USERNAME/pytest.git
$ cd pytest
$ hg branch your-branch-name
# now, to fix a bug create your own branch off "master":
$ git checkout -b your-bugfix-branch-name master
If you need some help with Mercurial, follow this quick start
guide: http://mercurial.selenic.com/wiki/QuickStart
# or to instead add a feature create your own branch off "features":
$ git checkout -b your-feature-branch-name features
.. _testing-pytest:
Given we have "major.minor.micro" version numbers, bugfixes will usually
be released in micro releases whereas features will be released in
minor releases and incompatible changes in major releases.
4. You can now edit your local working copy. To test you need to
install the "tox" tool into your virtualenv::
If you need some help with Git, follow this quick start
guide: https://git.wiki.kernel.org/index.php/QuickStart
#. Install tox
Tox is used to run all the tests and will automatically setup virtualenvs
to run the tests in.
(will implicitly use http://www.virtualenv.org/en/latest/)::
$ pip install tox
You need to have Python 2.7 and 3.3 available in your system. Now
running tests is as simple as issuing this command::
#. Run all the tests
$ python runtox.py -e py27,py33,flakes
You need to have Python 2.7 and 3.5 available in your system. Now
running tests is as simple as issuing this command::
This command will run tests via the "tox" tool against Python 2.7 and 3.3
and also perform "flakes" coding-style checks. ``runtox.py`` is
a thin wrapper around ``tox`` which installs from a development package
index where newer (not yet released to pypi) versions of dependencies
(especially ``py``) might be present.
$ python runtox.py -e linting,py27,py35
To run tests on py27 and pass options (e.g. enter pdb on failure)
to pytest you can do::
This command will run tests via the "tox" tool against Python 2.7 and 3.5
and also perform "lint" coding-style checks. ``runtox.py`` is
a thin wrapper around ``tox`` which installs from a development package
index where newer (not yet released to pypi) versions of dependencies
(especially ``py``) might be present.
#. You can now edit your local working copy.
You can now make the changes you want and run the tests again as necessary.
To run tests on py27 and pass options to pytest (e.g. enter pdb on failure)
to pytest you can do::
$ python runtox.py -e py27 -- --pdb
or to only run tests in a particular test module on py33::
or to only run tests in a particular test module on py35::
$ python runtox.py -e py33 -- testing/test_config.py
$ python runtox.py -e py35 -- testing/test_config.py
5. Commit and push once your tests pass and you are happy with your change(s)::
#. Commit and push once your tests pass and you are happy with your change(s)::
$ hg commit -m"<commit message>"
$ hg push -b .
$ git commit -a -m "<commit message>"
$ git push -u
6. Finally, submit a pull request through the BitBucket website:
Make sure you add a CHANGELOG message, 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.
.. image:: img/pullrequest.png
:width: 700px
:align: center
#. Finally, submit a pull request through the GitHub website using this data::
::
head-fork: YOUR_GITHUB_USERNAME/pytest
compare: your-branch-name
source: YOUR_BITBUCKET_USERNAME/pytest
branch: your-branch-name
base-fork: pytest-dev/pytest
base: master # if it's a bugfix
base: feature # if it's a feature
target: hpk42/pytest
branch: default
.. _contribution-using-git:
What about git (and so GitHub)?
-------------------------------
There used to be the pytest GitHub mirror. It was removed in favor of the
Mercurial one, to remove confusion of people not knowing where it's better to
put their issues and pull requests. Also it wasn't easily possible to automate
the mirroring process.
However, it's still possible to use git to contribute to pytest using tools
like `gitifyhg <https://github.com/buchuki/gitifyhg>`_ which allows you to
clone and work with Mercurial repo still using git.
.. warning::
Remember that git is **not** a default version control system for pytest and
you need to be careful using it.

View File

@@ -1,47 +1,92 @@
How to release pytest (draft)
How to release pytest
--------------------------------------------
1. bump version numbers in setup.py and pytest/__init__.py
Note: this assumes you have already registered on pypi.
2. check and finalize CHANGELOG
0. create the branch release-VERSION
use features as base for minor/major releases
and master as base for bugfix releases
3. write doc/en/announce/pytest-VERSION.txt and include
it in doc/en/announce/index.txt
1. Bump version numbers in _pytest/__init__.py (setup.py reads it)
4. use devpi for uploading a release tarball to a staging area:
- ``devpi use https://devpi.net/USER/dev``
- ``devpi upload``
2. Check and finalize CHANGELOG
5. run from multiple machines:
- ``devpi use https://devpi.net/USER/dev``
- ``devpi test pytest-VERSION``
3. Write doc/en/announce/release-VERSION.txt and include
it in doc/en/announce/index.txt::
git log 2.8.2..HEAD --format='%aN' | sort -u # lists the names of authors involved
4. Use devpi for uploading a release tarball to a staging area::
devpi use https://devpi.net/USER/dev
devpi upload --formats sdist,bdist_wheel
5. Run from multiple machines::
devpi use https://devpi.net/USER/dev
devpi test pytest==VERSION
6. Check that tests pass for relevant combinations with::
devpi list pytest
6. check that tests pass for relevant combinations with
``devpi list pytest``
or look at failures with "devpi list -f pytest".
There will be some failed environments like e.g. the py33-trial
or py27-pexpect tox environments on Win32 platforms
which is ok (tox does not support skipping on
per-platform basis yet).
7. XXX "regen docs" (not easy to do currently as it requires
a development version of the regendoc tool from ronny)
7. Regenerate the docs examples using tox, and check for regressions::
8. go to "doc/en" and upload docs with "make install"
(the latter requires ssh-login permissions on pytest.org
because it uses rsync). Note that the "install" target of
doc/en/Makefile defines where the rsync goes to, typically
to the "latest" section of pytest.org.
tox -e regen
git diff
9. publish to pypi "devpi push pytest-2.6.2 pypi:NAME" where NAME
is the name of pypi.python.org as configured in your
~/.pypirc file -- it's the same you would use with
"setup.py upload -r NAME"
10. send release announcement to mailing lists:
8. Build the docs, you need a virtualenv with py and sphinx
installed::
pytest-dev
testing-in-python
python-announce-list@python.org
cd doc/en
make html
Commit any changes before tagging the release.
9. Tag the release::
git tag VERSION
git push
10. Upload the docs using doc/en/Makefile::
cd doc/en
make install # or "installall" if you have LaTeX installed for PDF
This requires ssh-login permission on pytest.org because it uses
rsync.
Note that the ``install`` target of ``doc/en/Makefile`` defines where the
rsync goes to, typically to the "latest" section of pytest.org.
If you are making a minor release (e.g. 5.4), you also need to manually
create a symlink for "latest"::
ssh pytest-dev@pytest.org
ln -s 5.4 latest
Browse to pytest.org to verify.
11. Publish to pypi::
devpi push pytest-VERSION pypi:NAME
where 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>`_.
12. Send release announcement to mailing lists:
- pytest-dev
- testing-in-python
- python-announce-list@python.org
13. **after the release** Bump the version number in ``_pytest/__init__.py``,
to the next Minor release version (i.e. if you released ``pytest-2.8.0``,
set it to ``pytest-2.9.0.dev1``).
14. merge the actual release into the master branch and do a pull request against it
15. merge from master to features

View File

@@ -18,6 +18,7 @@ Unlike mock, "args.path" acts on the parsed auto-spec'ed ``os.path.abspath``
so it's independent from if the client side called "os.path.abspath(path=...)"
or "os.path.abspath('positional')".
refine parametrize API
-------------------------------------------------------------
tags: critical feature
@@ -49,7 +50,7 @@ probably makes sense in order to keep the declarative nature. This mirrors
the marker-mechanism with respect to a test module but puts it to a directory
scale.
When doing larger scoped parametrization it probably becomes neccessary
When doing larger scoped parametrization it probably becomes necessary
to allow parametrization to be ignored if the according parameter is not
used (currently any parametrized argument that is not present in a function will cause a ValueError). Example:
@@ -77,7 +78,7 @@ this would run the test_hello() function three times with three
different values for self.db. This could also work with unittest/nose
style tests, i.e. it leverages existing test suites without needing
to rewrite them. Together with the previously mentioned setup_test()
maybe the setupfunc could be ommitted?
maybe the setupfunc could be omitted?
optimizations
---------------------------------------------------------------
@@ -115,7 +116,7 @@ tags: feature
- introduce pytest.mark.nocollect for not considering a function for
test collection at all. maybe also introduce a pytest.mark.test to
explicitely mark a function to become a tested one. Lookup JUnit ways
explicitly mark a function to become a tested one. Lookup JUnit ways
of tagging tests.
introduce pytest.mark.importorskip
@@ -229,7 +230,7 @@ tags: feature
pytest.ensuretemp and pytest.config are probably the last
objects containing global state. Often using them is not
neccessary. This is about trying to get rid of them, i.e.
necessary. This is about trying to get rid of them, i.e.
deprecating them and checking with PyPy's usages as well
as others.
@@ -298,7 +299,7 @@ tags: feature
The idea is that you can e.g. import modules in a test and afterwards
sys.modules, sys.meta_path etc would be reverted. It can go further
then just importing however, e.g. current working direcroty, file
then just importing however, e.g. current working directory, file
descriptors, ...
This would probably be done by marking::
@@ -357,7 +358,7 @@ a few use-cases come to mind:
id, call = prepare_check(check)
# bubble should only prevent exception propagation after a failure
# the whole test should still fail
# there might be need for a loer level api and taking custom markers into account
# there might be need for a lower level api and taking custom markers into account
with pytest.section(id, bubble=False):
call()

36
LICENSE
View File

@@ -1,19 +1,21 @@
The MIT License (MIT)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Copyright (c) 2004-2016 Holger Krekel and others
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,7 +1,34 @@
include CHANGELOG
include README.rst
include setup.py
include tox.ini
include CHANGELOG.rst
include LICENSE
graft doc
include AUTHORS
include README.rst
include CONTRIBUTING.rst
include tox.ini
include setup.py
include .coveragerc
include plugin-test.sh
include requirements-docs.txt
include runtox.py
recursive-include bench *.py
recursive-include extra *.py
graft testing
graft doc
exclude _pytest/impl
graft _pytest/vendored_packages
recursive-exclude * *.pyc *.pyo
exclude appveyor/install.ps1
exclude appveyor.yml
exclude appveyor
exclude ISSUES.txt
exclude HOWTORELEASE.rst

View File

@@ -1,55 +1,102 @@
.. image:: https://drone.io/bitbucket.org/hpk42/pytest/status.png
:target: https://drone.io/bitbucket.org/hpk42/pytest/latest
.. image:: https://pypip.in/v/pytest/badge.png
:target: https://crate.io/packages/pytest/
.. image:: http://pytest.org/latest/_static/pytest1.png
:target: http://pytest.org
:align: center
:alt: pytest
Documentation: http://pytest.org/latest/
------
Changelog: http://pytest.org/latest/changelog.html
.. image:: https://img.shields.io/pypi/v/pytest.svg
:target: https://pypi.python.org/pypi/pytest
.. image:: https://img.shields.io/pypi/pyversions/pytest.svg
:target: https://pypi.python.org/pypi/pytest
.. image:: https://img.shields.io/coveralls/pytest-dev/pytest/master.svg
:target: https://coveralls.io/r/pytest-dev/pytest
.. image:: https://travis-ci.org/pytest-dev/pytest.svg?branch=master
:target: https://travis-ci.org/pytest-dev/pytest
.. image:: https://ci.appveyor.com/api/projects/status/mrgbjaua7t33pg6b?svg=true
:target: https://ci.appveyor.com/project/pytestbot/pytest
Issues: https://bitbucket.org/hpk42/pytest/issues?status=open
The ``pytest`` framework makes it easy to write small tests, yet
scales to support complex functional testing for applications and libraries.
CI: https://drone.io/bitbucket.org/hpk42/pytest
An example of a simple test:
The ``pytest`` testing tool makes it easy to write small tests, yet
scales to support complex functional testing. It provides
.. code-block:: python
- `auto-discovery
<http://pytest.org/latest/goodpractises.html#python-test-discovery>`_
of test modules and functions,
- detailed info on failing `assert statements <http://pytest.org/latest/assert.html>`_ (no need to remember ``self.assert*`` names)
- `modular fixtures <http://pytest.org/latest/fixture.html>`_ for
managing small or parametrized long-lived test resources.
- multi-paradigm support: you can use ``pytest`` to run test suites based
on `unittest <http://pytest.org/latest/unittest.html>`_ (or trial),
`nose <http://pytest.org/latest/nose.html>`_
- single-source compatibility from Python2.6 all the way up to
Python3.4, PyPy-2.3, (jython-2.5 untested)
# content of test_sample.py
def func(x):
return x + 1
def test_answer():
assert func(3) == 5
- many `external plugins <http://pytest.org/latest/plugins.html#installing-external-plugins-searching>`_.
To execute it::
A simple example for a test::
$ py.test
======= test session starts ========
platform linux -- Python 3.4.3, pytest-2.8.5, py-1.4.31, pluggy-0.3.1
collected 1 items
# content of test_module.py
def test_function():
i = 4
assert i == 3
test_sample.py F
which can be run with ``py.test test_module.py``. See `getting-started <http://pytest.org/latest/getting-started.html#our-first-test-run>`_ for more examples.
======= FAILURES ========
_______ test_answer ________
For much more info, including PDF docs, see
def test_answer():
> assert func(3) == 5
E assert 4 == 5
E + where 4 = func(3)
http://pytest.org
test_sample.py:5: AssertionError
======= 1 failed in 0.12 seconds ========
and report bugs at:
Due to ``py.test``'s detailed assertion introspection, only plain ``assert`` statements are used. See `getting-started <http://pytest.org/latest/getting-started.html#our-first-test-run>`_ for more examples.
http://bitbucket.org/hpk42/pytest/issues/
Features
--------
and checkout or fork repo at:
- Detailed info on failing `assert statements <http://pytest.org/latest/assert.html>`_ (no need to remember ``self.assert*`` names);
http://bitbucket.org/hpk42/pytest/
- `Auto-discovery
<http://pytest.org/latest/goodpractices.html#python-test-discovery>`_
of test modules and functions;
- `Modular fixtures <http://pytest.org/latest/fixture.html>`_ for
managing small or parametrized long-lived test resources;
- Can run `unittest <http://pytest.org/latest/unittest.html>`_ (or trial),
`nose <http://pytest.org/latest/nose.html>`_ test suites out of the box;
- Python2.6+, Python3.2+, PyPy-2.3, Jython-2.5 (untested);
- Rich plugin architecture, with over 150+ `external plugins <http://pytest.org/latest/plugins.html#installing-external-plugins-searching>`_ and thriving community;
Copyright Holger Krekel and others, 2004-2014
Licensed under the MIT license.
Documentation
-------------
For full documentation, including installation, tutorials and PDF documents, please see http://pytest.org.
Bugs/Requests
-------------
Please use the `GitHub issue tracker <https://github.com/pytest-dev/pytest/issues>`_ to submit bugs or request features.
Changelog
---------
Consult the `Changelog <http://pytest.org/latest/changelog.html>`_ page for fixes and enhancements of each version.
License
-------
Copyright Holger Krekel and others, 2004-2016.
Distributed under the terms of the `MIT`_ license, pytest is free and open source software.
.. _`MIT`: https://github.com/pytest-dev/pytest/blob/master/LICENSE

View File

@@ -1,2 +1,2 @@
#
__version__ = '2.6.3'
__version__ = '2.9.2'

View File

@@ -88,9 +88,6 @@ class FastFilesCompleter:
return completion
if os.environ.get('_ARGCOMPLETE'):
# argcomplete 0.5.6 is not compatible with python 2.5.6: print/with/format
if sys.version_info[:2] < (2, 6):
sys.exit(1)
try:
import argcomplete.completers
except ImportError:

12
_pytest/_code/__init__.py Normal file
View File

@@ -0,0 +1,12 @@
""" python inspection/code generation API """
from .code import Code # noqa
from .code import ExceptionInfo # noqa
from .code import Frame # noqa
from .code import Traceback # noqa
from .code import getrawcode # noqa
from .code import patch_builtins # noqa
from .code import unpatch_builtins # noqa
from .source import Source # noqa
from .source import compile_ as compile # noqa
from .source import getfslineno # noqa

View File

@@ -0,0 +1,81 @@
# copied from python-2.7.3's traceback.py
# CHANGES:
# - some_str is replaced, trying to create unicode strings
#
import types
def format_exception_only(etype, value):
"""Format the exception part of a traceback.
The arguments are the exception type and value such as given by
sys.last_type and sys.last_value. The return value is a list of
strings, each ending in a newline.
Normally, the list contains a single string; however, for
SyntaxError exceptions, it contains several lines that (when
printed) display detailed information about where the syntax
error occurred.
The message indicating which exception occurred is always the last
string in the list.
"""
# An instance should not have a meaningful value parameter, but
# sometimes does, particularly for string exceptions, such as
# >>> raise string1, string2 # deprecated
#
# Clear these out first because issubtype(string1, SyntaxError)
# 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):
return [_format_final_exc_line(etype, value)]
stype = etype.__name__
if not issubclass(etype, SyntaxError):
return [_format_final_exc_line(stype, value)]
# It was a syntax error; show exactly where the problem was found.
lines = []
try:
msg, (filename, lineno, offset, badline) = value.args
except Exception:
pass
else:
filename = filename or "<string>"
lines.append(' File "%s", line %d\n' % (filename, lineno))
if badline is not None:
if isinstance(badline, bytes): # python 2 only
badline = badline.decode('utf-8', 'replace')
lines.append(u' %s\n' % badline.strip())
if offset is not None:
caretspace = badline.rstrip('\n')[:offset].lstrip()
# non-space whitespace (likes tabs) must be kept for alignment
caretspace = ((c.isspace() and c or ' ') for c in caretspace)
# only three spaces to account for offset1 == pos 0
lines.append(' %s^\n' % ''.join(caretspace))
value = msg
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)
if value is None or not valuestr:
line = "%s\n" % etype
else:
line = "%s: %s\n" % (etype, valuestr)
return line
def _some_str(value):
try:
return unicode(value)
except Exception:
try:
return str(value)
except Exception:
pass
return '<unprintable %s object>' % type(value).__name__

805
_pytest/_code/code.py Normal file
View File

@@ -0,0 +1,805 @@
import sys
from inspect import CO_VARARGS, CO_VARKEYWORDS
import py
builtin_repr = repr
reprlib = py.builtin._tryimport('repr', 'reprlib')
if sys.version_info[0] >= 3:
from traceback import format_exception_only
else:
from ._py2traceback import format_exception_only
class Code(object):
""" wrapper around Python code objects """
def __init__(self, rawcode):
if not hasattr(rawcode, "co_filename"):
rawcode = getrawcode(rawcode)
try:
self.filename = rawcode.co_filename
self.firstlineno = rawcode.co_firstlineno - 1
self.name = rawcode.co_name
except AttributeError:
raise TypeError("not a code object: %r" %(rawcode,))
self.raw = rawcode
def __eq__(self, other):
return self.raw == other.raw
def __ne__(self, other):
return not self == other
@property
def path(self):
""" return a path object pointing to source code (note that it
might not point to an actually existing file). """
p = py.path.local(self.raw.co_filename)
# maybe don't try this checking
if not p.check():
# XXX maybe try harder like the weird logic
# in the standard lib [linecache.updatecache] does?
p = self.raw.co_filename
return p
@property
def fullsource(self):
""" return a _pytest._code.Source object for the full source file of the code
"""
from _pytest._code import source
full, _ = source.findsource(self.raw)
return full
def source(self):
""" return a _pytest._code.Source object for the code object's source only
"""
# return source only for that part of code
import _pytest._code
return _pytest._code.Source(self.raw)
def getargs(self, var=False):
""" return a tuple with the argument names for the code object
if 'var' is set True also return the names of the variable and
keyword arguments when present
"""
# handfull shortcut for getting args
raw = self.raw
argcount = raw.co_argcount
if var:
argcount += raw.co_flags & CO_VARARGS
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."""
def __init__(self, frame):
self.lineno = frame.f_lineno - 1
self.f_globals = frame.f_globals
self.f_locals = frame.f_locals
self.raw = frame
self.code = Code(frame.f_code)
@property
def statement(self):
""" statement this frame is at """
import _pytest._code
if self.code.fullsource is None:
return _pytest._code.Source("")
return self.code.fullsource.getstatement(self.lineno)
def eval(self, code, **vars):
""" evaluate 'code' in the frame
'vars' are optional additional local variables
returns the result of the evaluation
"""
f_locals = self.f_locals.copy()
f_locals.update(vars)
return eval(code, self.f_globals, f_locals)
def exec_(self, code, **vars):
""" exec 'code' in the frame
'vars' are optiona; additional local variables
"""
f_locals = self.f_locals.copy()
f_locals.update(vars)
py.builtin.exec_(code, self.f_globals, f_locals )
def repr(self, object):
""" return a 'safe' (non-recursive, one-line) string repr for 'object'
"""
return py.io.saferepr(object)
def is_true(self, object):
return object
def getargs(self, var=False):
""" return a list of tuples (name, value) for all arguments
if 'var' is set True also include the variable and keyword
arguments when present
"""
retval = []
for arg in self.code.getargs(var):
try:
retval.append((arg, self.f_locals[arg]))
except KeyError:
pass # this can occur when using Psyco
return retval
class TracebackEntry(object):
""" a single entry in a traceback """
_repr_style = None
exprinfo = None
def __init__(self, rawentry):
self._rawentry = rawentry
self.lineno = rawentry.tb_lineno - 1
def set_repr_style(self, mode):
assert mode in ("short", "long")
self._repr_style = mode
@property
def frame(self):
import _pytest._code
return _pytest._code.Frame(self._rawentry.tb_frame)
@property
def relline(self):
return self.lineno - self.frame.code.firstlineno
def __repr__(self):
return "<TracebackEntry %s:%d>" %(self.frame.code.path, self.lineno+1)
@property
def statement(self):
""" _pytest._code.Source object for the current statement """
source = self.frame.code.fullsource
return source.getstatement(self.lineno)
@property
def path(self):
""" path to the source code """
return self.frame.code.path
def getlocals(self):
return self.frame.f_locals
locals = property(getlocals, None, None, "locals of underlaying frame")
def reinterpret(self):
"""Reinterpret the failing statement and returns a detailed information
about what operations are performed."""
from _pytest.assertion.reinterpret import reinterpret
if self.exprinfo is None:
source = py.builtin._totext(self.statement).strip()
x = reinterpret(source, self.frame, should_fail=True)
if not py.builtin._istext(x):
raise TypeError("interpret returned non-string %r" % (x,))
self.exprinfo = x
return self.exprinfo
def getfirstlinesource(self):
# on Jython this firstlineno can be -1 apparently
return max(self.frame.code.firstlineno, 0)
def getsource(self, astcache=None):
""" return failing source code. """
# we use the passed in astcache to not reparse asttrees
# within exception info printing
from _pytest._code.source import getstatementrange_ast
source = self.frame.code.fullsource
if source is None:
return None
key = astnode = None
if astcache is not None:
key = self.frame.code.path
if key is not None:
astnode = astcache.get(key, None)
start = self.getfirstlinesource()
try:
astnode, _, end = getstatementrange_ast(self.lineno, source,
astnode=astnode)
except SyntaxError:
end = self.lineno + 1
else:
if key is not None:
astcache[key] = astnode
return source[start:end]
source = property(getsource)
def ishidden(self):
""" return True if the current frame has a var __tracebackhide__
resolving to True
mostly for internal use
"""
try:
return self.frame.f_locals['__tracebackhide__']
except KeyError:
try:
return self.frame.f_globals['__tracebackhide__']
except KeyError:
return False
def __str__(self):
try:
fn = str(self.path)
except py.error.Error:
fn = '???'
name = self.frame.code.name
try:
line = str(self.statement).lstrip()
except KeyboardInterrupt:
raise
except:
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):
""" initialize from given python traceback object. """
if hasattr(tb, 'tb_next'):
def f(cur):
while cur is not None:
yield self.Entry(cur)
cur = cur.tb_next
list.__init__(self, f(tb))
else:
list.__init__(self, tb)
def cut(self, path=None, lineno=None, firstlineno=None, excludepath=None):
""" return a Traceback instance wrapping part of this Traceback
by provding any combination of path, lineno and firstlineno, the
first frame to start the to-be-returned traceback is determined
this allows cutting the first part of a Traceback instance e.g.
for formatting reasons (removing some uninteresting bits that deal
with handling of the exception/traceback)
"""
for x in self:
code = x.frame.code
codepath = code.path
if ((path is None or codepath == path) and
(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)):
return Traceback(x._rawentry)
return self
def __getitem__(self, key):
val = super(Traceback, self).__getitem__(key)
if isinstance(key, type(slice(0))):
val = self.__class__(val)
return val
def filter(self, fn=lambda x: not x.ishidden()):
""" return a Traceback instance with certain items removed
fn is a function that gets a single argument, a TracebackEntry
instance, and should return True when the item should be added
to the Traceback, False when not
by default this removes all the TracebackEntries which are hidden
(see ishidden() above)
"""
return Traceback(filter(fn, self))
def getcrashentry(self):
""" return last non-hidden traceback entry that lead
to the exception of a traceback.
"""
for i in range(-1, -len(self)-1, -1):
entry = self[i]
if not entry.ishidden():
return entry
return self[-1]
def recursionindex(self):
""" return the index of the frame/TracebackEntry where recursion
originates if appropriate, None if no recursion occurred
"""
cache = {}
for i, entry in enumerate(self):
# id for the code.raw is needed to work around
# the strange metaprogramming in the decorator lib from pypi
# which generates code objects that have hash/value equality
#XXX needs a test
key = entry.frame.code.path, id(entry.frame.code.raw), entry.lineno
#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)):
return i
l.append(entry.frame.f_locals)
return None
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.
"""
_striptext = ''
def __init__(self, tup=None, exprinfo=None):
import _pytest._code
if tup is None:
tup = sys.exc_info()
if exprinfo is None and isinstance(tup[1], AssertionError):
exprinfo = getattr(tup[1], 'msg', None)
if exprinfo is None:
exprinfo = str(tup[1])
if exprinfo and exprinfo.startswith('assert '):
self._striptext = 'AssertionError: '
self._excinfo = tup
#: the exception class
self.type = tup[0]
#: the exception instance
self.value = tup[1]
#: the exception raw traceback
self.tb = tup[2]
#: the exception type name
self.typename = self.type.__name__
#: the exception traceback (_pytest._code.Traceback instance)
self.traceback = _pytest._code.Traceback(self.tb)
def __repr__(self):
return "<ExceptionInfo %s tblen=%d>" % (self.typename, len(self.traceback))
def exconly(self, tryshort=False):
""" return the exception as a string
when 'tryshort' resolves to True, and the exception is a
_pytest._code._AssertionError, only the actual exception part of
the exception representation is returned (so 'AssertionError: ' is
removed from the beginning)
"""
lines = format_exception_only(self.type, self.value)
text = ''.join(lines)
text = text.rstrip()
if tryshort:
if text.startswith(self._striptext):
text = text[len(self._striptext):]
return text
def errisinstance(self, exc):
""" return True if the exception is an instance of exc """
return isinstance(self.value, exc)
def _getreprcrash(self):
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)
def getrepr(self, showlocals=False, style="long",
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
tbfilter: hide entries (where __tracebackhide__ is true)
in case of style==native, tbfilter and showlocals is ignored.
"""
if style == 'native':
return ReprExceptionInfo(ReprTracebackNative(
py.std.traceback.format_exception(
self.type,
self.value,
self.traceback[0]._rawentry,
)), self._getreprcrash())
fmt = FormattedExcinfo(showlocals=showlocals, style=style,
abspath=abspath, tbfilter=tbfilter, funcargs=funcargs)
return fmt.repr_excinfo(self)
def __str__(self):
entry = self.traceback[-1]
loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
return str(loc)
def __unicode__(self):
entry = self.traceback[-1]
loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
return unicode(loc)
class FormattedExcinfo(object):
""" presenting information about failing Functions and Generators. """
# for traceback entries
flow_marker = ">"
fail_marker = "E"
def __init__(self, showlocals=False, style="long", abspath=True, tbfilter=True, funcargs=False):
self.showlocals = showlocals
self.style = style
self.tbfilter = tbfilter
self.funcargs = funcargs
self.abspath = abspath
self.astcache = {}
def _getindent(self, source):
# figure out indent for given source
try:
s = str(source.getstatement(len(source)-1))
except KeyboardInterrupt:
raise
except:
try:
s = str(source[-1])
except KeyboardInterrupt:
raise
except:
return 0
return 4 + (len(s) - len(s.lstrip()))
def _getentrysource(self, entry):
source = entry.getsource(self.astcache)
if source is not None:
source = source.deindent()
return source
def _saferepr(self, obj):
return py.io.saferepr(obj)
def repr_args(self, entry):
if self.funcargs:
args = []
for argname, argvalue in entry.frame.getargs(var=True):
args.append((argname, self._saferepr(argvalue)))
return ReprFuncArgs(args)
def get_source(self, source, line_index=-1, excinfo=None, short=False):
""" return formatted and marked up source lines. """
import _pytest._code
lines = []
if source is None or line_index >= len(source.lines):
source = _pytest._code.Source("???")
line_index = 0
if line_index < 0:
line_index += len(source)
space_prefix = " "
if short:
lines.append(space_prefix + source.lines[line_index].strip())
else:
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:]:
lines.append(space_prefix + line)
if excinfo is not None:
indent = 4 if short else self._getindent(source)
lines.extend(self.get_exconly(excinfo, indent=indent, markall=True))
return lines
def get_exconly(self, excinfo, indent=4, markall=False):
lines = []
indent = " " * indent
# get the real exception information out
exlines = excinfo.exconly(tryshort=True).split('\n')
failindent = self.fail_marker + indent[1:]
for line in exlines:
lines.append(failindent + line)
if not markall:
failindent = indent
return lines
def repr_locals(self, locals):
if self.showlocals:
lines = []
keys = [loc for loc in locals if loc[0] != "@"]
keys.sort()
for name in keys:
value = locals[name]
if name == '__builtins__':
lines.append("__builtins__ = <builtins>")
else:
# This formatting could all be handled by the
# _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,
# (list, tuple, dict)):
lines.append("%-10s = %s" %(name, str_repr))
#else:
# self._line("%-10s =\\" % (name,))
# # XXX
# py.std.pprint.pprint(value, stream=self.excinfowriter)
return ReprLocals(lines)
def repr_traceback_entry(self, entry, excinfo=None):
import _pytest._code
source = self._getentrysource(entry)
if source is None:
source = _pytest._code.Source("???")
line_index = 0
else:
# entry.getfirstlinesource() can be -1, should be 0 on jython
line_index = entry.lineno - max(entry.getfirstlinesource(), 0)
lines = []
style = entry._repr_style
if style is None:
style = self.style
if style in ("short", "long"):
short = style == "short"
reprargs = self.repr_args(entry) if not short else None
s = self.get_source(source, line_index, excinfo, short=short)
lines.extend(s)
if short:
message = "in %s" %(entry.name)
else:
message = excinfo and excinfo.typename or ""
path = self._makepath(entry.path)
filelocrepr = ReprFileLocation(path, entry.lineno+1, message)
localsrepr = None
if not short:
localsrepr = self.repr_locals(entry.locals)
return ReprEntry(lines, reprargs, localsrepr, filelocrepr, style)
if excinfo:
lines.extend(self.get_exconly(excinfo, indent=4))
return ReprEntry(lines, None, None, None, style)
def _makepath(self, path):
if not self.abspath:
try:
np = py.path.local().bestrelpath(path)
except OSError:
return path
if len(np) < len(str(path)):
path = np
return path
def repr_traceback(self, excinfo):
traceback = excinfo.traceback
if self.tbfilter:
traceback = traceback.filter()
recursionindex = None
if is_recursion_error(excinfo):
recursionindex = traceback.recursionindex()
last = traceback[-1]
entries = []
extraline = None
for index, entry in enumerate(traceback):
einfo = (last == entry) and excinfo or None
reprentry = self.repr_traceback_entry(entry, einfo)
entries.append(reprentry)
if index == recursionindex:
extraline = "!!! Recursion detected (same locals & position)"
break
return ReprTraceback(entries, extraline, style=self.style)
def repr_excinfo(self, excinfo):
reprtraceback = self.repr_traceback(excinfo)
reprcrash = excinfo._getreprcrash()
return ReprExceptionInfo(reprtraceback, reprcrash)
class TerminalRepr:
def __str__(self):
s = self.__unicode__()
if sys.version_info[0] < 3:
s = s.encode('utf-8')
return s
def __unicode__(self):
# FYI this is called from pytest-xdist's serialization of exception
# information.
io = py.io.TextIO()
tw = py.io.TerminalWriter(file=io)
self.toterminal(tw)
return io.getvalue().strip()
def __repr__(self):
return "<%s instance at %0x>" %(self.__class__, id(self))
class ReprExceptionInfo(TerminalRepr):
def __init__(self, reprtraceback, reprcrash):
self.reprtraceback = reprtraceback
self.reprcrash = reprcrash
self.sections = []
def addsection(self, name, content, sep="-"):
self.sections.append((name, content, sep))
def toterminal(self, tw):
self.reprtraceback.toterminal(tw)
for name, content, sep in self.sections:
tw.sep(sep, name)
tw.line(content)
class ReprTraceback(TerminalRepr):
entrysep = "_ "
def __init__(self, reprentries, extraline, style):
self.reprentries = reprentries
self.extraline = extraline
self.style = style
def toterminal(self, tw):
# the entries might have different styles
for i, entry in enumerate(self.reprentries):
if entry.style == "long":
tw.line("")
entry.toterminal(tw)
if i < len(self.reprentries) - 1:
next_entry = self.reprentries[i+1]
if entry.style == "long" or \
entry.style == "short" and next_entry.style == "long":
tw.sep(self.entrysep)
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"
def __init__(self, tblines):
self.lines = tblines
def toterminal(self, tw):
tw.write("".join(self.lines))
class ReprEntry(TerminalRepr):
localssep = "_ "
def __init__(self, lines, reprfuncargs, reprlocals, filelocrepr, style):
self.lines = lines
self.reprfuncargs = reprfuncargs
self.reprlocals = reprlocals
self.reprfileloc = filelocrepr
self.style = style
def toterminal(self, tw):
if self.style == "short":
self.reprfileloc.toterminal(tw)
for line in self.lines:
red = line.startswith("E ")
tw.line(line, bold=True, red=red)
#tw.line("")
return
if self.reprfuncargs:
self.reprfuncargs.toterminal(tw)
for line in self.lines:
red = line.startswith("E ")
tw.line(line, bold=True, red=red)
if self.reprlocals:
#tw.sep(self.localssep, "Locals")
tw.line("")
self.reprlocals.toterminal(tw)
if self.reprfileloc:
if self.lines:
tw.line("")
self.reprfileloc.toterminal(tw)
def __str__(self):
return "%s\n%s\n%s" % ("\n".join(self.lines),
self.reprlocals,
self.reprfileloc)
class ReprFileLocation(TerminalRepr):
def __init__(self, path, lineno, message):
self.path = str(path)
self.lineno = lineno
self.message = message
def toterminal(self, tw):
# filename and lineno output for each entry,
# using an output format that most editors unterstand
msg = self.message
i = msg.find("\n")
if i != -1:
msg = msg[:i]
tw.line("%s:%s: %s" %(self.path, self.lineno, msg))
class ReprLocals(TerminalRepr):
def __init__(self, lines):
self.lines = lines
def toterminal(self, tw):
for line in self.lines:
tw.line(line)
class ReprFuncArgs(TerminalRepr):
def __init__(self, args):
self.args = args
def toterminal(self, tw):
if self.args:
linesofar = ""
for name, value in self.args:
ns = "%s = %s" %(name, value)
if len(ns) + len(linesofar) + 2 > tw.fullwidth:
if linesofar:
tw.line(linesofar)
linesofar = ns
else:
if linesofar:
linesofar += ", " + ns
else:
linesofar = ns
if linesofar:
tw.line(linesofar)
tw.line("")
oldbuiltins = {}
def patch_builtins(assertion=True, compile=True):
""" put compile and AssertionError builtins to Python's builtins. """
if assertion:
from _pytest.assertion import reinterpret
l = oldbuiltins.setdefault('AssertionError', [])
l.append(py.builtin.builtins.AssertionError)
py.builtin.builtins.AssertionError = reinterpret.AssertionError
if compile:
import _pytest._code
l = oldbuiltins.setdefault('compile', [])
l.append(py.builtin.builtins.compile)
py.builtin.builtins.compile = _pytest._code.compile
def unpatch_builtins(assertion=True, compile=True):
""" remove compile and AssertionError builtins from Python builtins. """
if assertion:
py.builtin.builtins.AssertionError = oldbuiltins['AssertionError'].pop()
if compile:
py.builtin.builtins.compile = oldbuiltins['compile'].pop()
def getrawcode(obj, trycall=True):
""" return code object for given function. """
try:
return obj.__code__
except AttributeError:
obj = getattr(obj, 'im_func', obj)
obj = getattr(obj, 'func_code', obj)
obj = getattr(obj, 'f_code', obj)
obj = getattr(obj, '__code__', obj)
if trycall and not hasattr(obj, 'co_firstlineno'):
if hasattr(obj, '__call__') and not py.std.inspect.isclass(obj):
x = getrawcode(obj.__call__, trycall=False)
if hasattr(x, 'co_firstlineno'):
return x
return obj
if sys.version_info[:2] >= (3, 5): # RecursionError introduced in 3.5
def is_recursion_error(excinfo):
return excinfo.errisinstance(RecursionError) # noqa
else:
def is_recursion_error(excinfo):
if not excinfo.errisinstance(RuntimeError):
return False
try:
return "maximum recursion depth exceeded" in str(excinfo.value)
except UnicodeError:
return False

421
_pytest/_code/source.py Normal file
View File

@@ -0,0 +1,421 @@
from __future__ import generators
from bisect import bisect_right
import sys
import inspect, tokenize
import py
from types import ModuleType
cpy_compile = compile
try:
import _ast
from _ast import PyCF_ONLY_AST as _AST_FLAG
except ImportError:
_AST_FLAG = 0
_ast = None
class Source(object):
""" a immutable object holding a source code fragment,
possibly deindenting it.
"""
_compilecounter = 0
def __init__(self, *parts, **kwargs):
self.lines = lines = []
de = kwargs.get('deindent', True)
rstrip = kwargs.get('rstrip', True)
for part in parts:
if not part:
partlines = []
if isinstance(part, Source):
partlines = part.lines
elif isinstance(part, (tuple, list)):
partlines = [x.rstrip("\n") for x in part]
elif isinstance(part, py.builtin._basestring):
partlines = part.split('\n')
if rstrip:
while partlines:
if partlines[-1].strip():
break
partlines.pop()
else:
partlines = getsource(part, deindent=de).lines
if de:
partlines = deindent(partlines)
lines.extend(partlines)
def __eq__(self, other):
try:
return self.lines == other.lines
except AttributeError:
if isinstance(other, str):
return str(self) == other
return False
def __getitem__(self, key):
if isinstance(key, int):
return self.lines[key]
else:
if key.step not in (None, 1):
raise IndexError("cannot slice a Source with a step")
return self.__getslice__(key.start, key.stop)
def __len__(self):
return len(self.lines)
def __getslice__(self, start, end):
newsource = Source()
newsource.lines = self.lines[start:end]
return newsource
def strip(self):
""" return new source object with trailing
and leading blank lines removed.
"""
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():
end -= 1
source = Source()
source.lines[:] = self.lines[start:end]
return source
def putaround(self, before='', after='', indent=' ' * 4):
""" return a copy of the source object with
'before' and 'after' wrapped around it.
"""
before = Source(before)
after = Source(after)
newsource = Source()
lines = [ (indent + line) for line in self.lines]
newsource.lines = before.lines + lines + after.lines
return newsource
def indent(self, indent=' ' * 4):
""" return a copy of the source object with
all lines indented by the given indent-string.
"""
newsource = Source()
newsource.lines = [(indent+line) for line in self.lines]
return newsource
def getstatement(self, lineno, assertion=False):
""" return Source statement which contains the
given linenumber (counted from 0).
"""
start, end = self.getstatementrange(lineno, assertion)
return self[start:end]
def getstatementrange(self, lineno, assertion=False):
""" return (start, end) tuple which spans the minimal
statement region which containing the given lineno.
"""
if not (0 <= lineno < len(self)):
raise IndexError("lineno out of range")
ast, start, end = getstatementrange_ast(lineno, self)
return start, end
def deindent(self, offset=None):
""" return a new source object deindented by offset.
If offset is None then guess an indentation offset from
the first non-blank line. Subsequent lines which have a
lower indentation offset will be copied verbatim as
they are assumed to be part of multilines.
"""
# XXX maybe use the tokenizer to properly handle multiline
# strings etc.pp?
newsource = Source()
newsource.lines[:] = deindent(self.lines, offset)
return newsource
def isparseable(self, deindent=True):
""" return True if source is parseable, heuristically
deindenting it by default.
"""
try:
import parser
except ImportError:
syntax_checker = lambda x: compile(x, 'asd', 'exec')
else:
syntax_checker = parser.suite
if deindent:
source = str(self.deindent())
else:
source = str(self)
try:
#compile(source+'\n', "x", "exec")
syntax_checker(source+'\n')
except KeyboardInterrupt:
raise
except Exception:
return False
else:
return True
def __str__(self):
return "\n".join(self.lines)
def compile(self, filename=None, mode='exec',
flag=generators.compiler_flag,
dont_inherit=0, _genframe=None):
""" return compiled code object. if filename is None
invent an artificial filename which displays
the source/line position of the caller frame.
"""
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
base = "<%d-codegen " % self._compilecounter
self.__class__._compilecounter += 1
if not filename:
filename = base + '%s:%d>' % (fn, lineno)
else:
filename = base + '%r %s:%d>' % (filename, fn, lineno)
source = "\n".join(self.lines) + '\n'
try:
co = cpy_compile(source, filename, mode, flag)
except SyntaxError:
ex = sys.exc_info()[1]
# re-represent syntax errors from parsing python strings
msglines = self.lines[:ex.lineno]
if 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
newex.lineno = ex.lineno
newex.text = ex.text
raise newex
else:
if flag & _AST_FLAG:
return co
lines = [(x + "\n") for x in self.lines]
if sys.version_info[0] >= 3:
# XXX py3's inspect.getsourcefile() checks for a module
# and a pep302 __loader__ ... we don't have a module
# at code compile-time so we need to fake it here
m = ModuleType("_pycodecompile_pseudo_module")
py.std.inspect.modulesbyfile[filename] = None
py.std.sys.modules[None] = m
m.__loader__ = 1
py.std.linecache.cache[filename] = (1, None, lines, filename)
return co
#
# public API shortcut functions
#
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
and any recursively created code objects.
"""
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
s = Source(source)
co = s.compile(filename, mode, flags, _genframe=_genframe)
return co
def getfslineno(obj):
""" Return source location (path, lineno) for the given object.
If the source cannot be determined return ("", -1)
"""
import _pytest._code
try:
code = _pytest._code.Code(obj)
except TypeError:
try:
fn = (py.std.inspect.getsourcefile(obj) or
py.std.inspect.getfile(obj))
except TypeError:
return "", -1
fspath = fn and py.path.local(fn) or None
lineno = -1
if fspath:
try:
_, lineno = findsource(obj)
except IOError:
pass
else:
fspath = code.path
lineno = code.firstlineno
assert isinstance(lineno, int)
return fspath, lineno
#
# helper functions
#
def findsource(obj):
try:
sourcelines, lineno = py.std.inspect.findsource(obj)
except py.builtin._sysex:
raise
except:
return None, -1
source = Source()
source.lines = [line.rstrip() for line in sourcelines]
return source, lineno
def getsource(obj, **kwargs):
import _pytest._code
obj = _pytest._code.getrawcode(obj)
try:
strsrc = inspect.getsource(obj)
except IndentationError:
strsrc = "\"Buggy python version consider upgrading, cannot get source\""
assert isinstance(strsrc, str)
return Source(strsrc, **kwargs)
def deindent(lines, offset=None):
if offset is None:
for line in lines:
line = line.expandtabs()
s = line.lstrip()
if s:
offset = len(line)-len(s)
break
else:
offset = 0
if offset == 0:
return list(lines)
newlines = []
def readline_generator(lines):
for line in lines:
yield line + '\n'
while True:
yield ''
it = readline_generator(lines)
try:
for _, _, (sline, _), (eline, _), _ in tokenize.generate_tokens(lambda: next(it)):
if sline > len(lines):
break # End of input reached
if sline > len(newlines):
line = lines[sline - 1].expandtabs()
if line.lstrip() and line[:offset].isspace():
line = line[offset:] # Deindent
newlines.append(line)
for i in range(sline, eline):
# Don't deindent continuing lines of
# multiline tokens (i.e. multiline strings)
newlines.append(lines[i])
except (IndentationError, tokenize.TokenError):
pass
# Add any lines we didn't see. E.g. if an exception was raised.
newlines.extend(lines[len(newlines):])
return newlines
def get_statement_startend2(lineno, node):
import ast
# flatten all statements and except handlers into one lineno-list
# AST's line numbers start indexing at 1
l = []
for x in ast.walk(node):
if isinstance(x, _ast.stmt) or isinstance(x, _ast.ExceptHandler):
l.append(x.lineno - 1)
for name in "finalbody", "orelse":
val = getattr(x, name, None)
if val:
# treat the finally/orelse part as its own statement
l.append(val[0].lineno - 1 - 1)
l.sort()
insert_index = bisect_right(l, lineno)
start = l[insert_index - 1]
if insert_index >= len(l):
end = None
else:
end = l[insert_index]
return start, end
def getstatementrange_ast(lineno, source, assertion=False, astnode=None):
if astnode is None:
content = str(source)
if sys.version_info < (2,7):
content += "\n"
try:
astnode = compile(content, "source", "exec", 1024) # 1024 for AST
except ValueError:
start, end = getstatementrange_old(lineno, source, assertion)
return None, start, end
start, end = get_statement_startend2(lineno, astnode)
# we need to correct the end:
# - ast-parsing strips comments
# - there might be empty lines
# - we might have lesser indented code blocks at the end
if end is None:
end = len(source.lines)
if end > start + 1:
# make sure we don't span differently indented code blocks
# by using the BlockFinder helper used which inspect.getsource() uses itself
block_finder = inspect.BlockFinder()
# if we start with an indented line, put blockfinder to "started" mode
block_finder.started = source.lines[start][0].isspace()
it = ((x + "\n") for x in source.lines[start:end])
try:
for tok in tokenize.generate_tokens(lambda: next(it)):
block_finder.tokeneater(*tok)
except (inspect.EndOfBlock, IndentationError):
end = block_finder.last + start
except Exception:
pass
# the end might still point to a comment or empty line, correct it
while end:
line = source.lines[end - 1].lstrip()
if line.startswith("#") or not line:
end -= 1
else:
break
return astnode, start, end
def getstatementrange_old(lineno, source, assertion=False):
""" return (start, end) tuple which spans the minimal
statement region which containing the given lineno.
raise an IndexError if no such statementrange can be found.
"""
# XXX this logic is only used on python2.4 and below
# 1. find the start of the statement
from codeop import compile_command
for start in range(lineno, -1, -1):
if assertion:
line = source.lines[start]
# the following lines are not fully tested, change with care
if 'super' in line and 'self' in line and '__init__' in line:
raise IndexError("likely a subclass")
if "assert" not in line and "raise" not in line:
continue
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():')
trysource = '\n '.join(trylines)
# ^ space here
try:
compile_command(trysource)
except (SyntaxError, OverflowError, ValueError):
continue
# 2. find the end of the statement
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,))

11
_pytest/_pluggy.py Normal file
View File

@@ -0,0 +1,11 @@
"""
imports symbols from vendored "pluggy" if available, otherwise
falls back to importing "pluggy" from the default namespace.
"""
try:
from _pytest.vendored_packages.pluggy import * # noqa
from _pytest.vendored_packages.pluggy import __version__ # noqa
except ImportError:
from pluggy import * # noqa
from pluggy import __version__ # noqa

View File

@@ -2,6 +2,7 @@
support for presenting detailed information in failing assertions.
"""
import py
import os
import sys
from _pytest.monkeypatch import monkeypatch
from _pytest.assertion import util
@@ -70,12 +71,11 @@ def pytest_configure(config):
config._assertstate = AssertionState(config, mode)
config._assertstate.hook = hook
config._assertstate.trace("configured with mode set to %r" % (mode,))
def pytest_unconfigure(config):
hook = config._assertstate.hook
if hook is not None and hook in sys.meta_path:
sys.meta_path.remove(hook)
def undo():
hook = config._assertstate.hook
if hook is not None and hook in sys.meta_path:
sys.meta_path.remove(hook)
config.add_cleanup(undo)
def pytest_collection(session):
@@ -87,6 +87,12 @@ def pytest_collection(session):
hook.set_session(session)
def _running_on_ci():
"""Check if we're currently running on a CI system."""
env_vars = ['CI', 'BUILD_NUMBER']
return any(var in os.environ for var in env_vars)
def pytest_runtest_setup(item):
"""Setup the pytest_assertrepr_compare hook
@@ -100,7 +106,8 @@ def pytest_runtest_setup(item):
This uses the first result from the hook and then ensures the
following:
* Overly verbose explanations are dropped unles -vv was used.
* Overly verbose explanations are dropped unless -vv was used or
running on a CI.
* Embedded newlines are escaped to help util.format_explanation()
later.
* If the rewrite mode is used embedded %-characters are replaced
@@ -113,10 +120,14 @@ def pytest_runtest_setup(item):
config=item.config, op=op, left=left, right=right)
for new_expl in hook_result:
if new_expl:
if (sum(len(p) for p in new_expl[1:]) > 80*8
and item.config.option.verbose < 2):
new_expl[1:] = [py.builtin._totext(
'Detailed information truncated, use "-vv" to show')]
if (sum(len(p) for p in new_expl[1:]) > 80*8 and
item.config.option.verbose < 2 and
not _running_on_ci()):
show_max = 10
truncated_lines = len(new_expl) - show_max
new_expl[show_max:] = [py.builtin._totext(
'Detailed information truncated (%d more lines)'
', use "-vv" to show' % truncated_lines)]
new_expl = [line.replace("\n", "\\n") for line in new_expl]
res = py.builtin._totext("\n~").join(new_expl)
if item.config.getvalue("assertmode") == "rewrite":

View File

@@ -1,345 +0,0 @@
"""
Find intermediate evalutation results in assert statements through builtin AST.
This should replace oldinterpret.py eventually.
"""
import sys
import ast
import py
from _pytest.assertion import util
from _pytest.assertion.reinterpret import BuiltinAssertionError
if sys.platform.startswith("java"):
# See http://bugs.jython.org/issue1497
_exprs = ("BoolOp", "BinOp", "UnaryOp", "Lambda", "IfExp", "Dict",
"ListComp", "GeneratorExp", "Yield", "Compare", "Call",
"Repr", "Num", "Str", "Attribute", "Subscript", "Name",
"List", "Tuple")
_stmts = ("FunctionDef", "ClassDef", "Return", "Delete", "Assign",
"AugAssign", "Print", "For", "While", "If", "With", "Raise",
"TryExcept", "TryFinally", "Assert", "Import", "ImportFrom",
"Exec", "Global", "Expr", "Pass", "Break", "Continue")
_expr_nodes = set(getattr(ast, name) for name in _exprs)
_stmt_nodes = set(getattr(ast, name) for name in _stmts)
def _is_ast_expr(node):
return node.__class__ in _expr_nodes
def _is_ast_stmt(node):
return node.__class__ in _stmt_nodes
else:
def _is_ast_expr(node):
return isinstance(node, ast.expr)
def _is_ast_stmt(node):
return isinstance(node, ast.stmt)
class Failure(Exception):
"""Error found while interpreting AST."""
def __init__(self, explanation=""):
self.cause = sys.exc_info()
self.explanation = explanation
def interpret(source, frame, should_fail=False):
mod = ast.parse(source)
visitor = DebugInterpreter(frame)
try:
visitor.visit(mod)
except Failure:
failure = sys.exc_info()[1]
return getfailure(failure)
if should_fail:
return ("(assertion failed, but when it was re-run for "
"printing intermediate values, it did not fail. Suggestions: "
"compute assert expression before the assert or use --assert=plain)")
def run(offending_line, frame=None):
if frame is None:
frame = py.code.Frame(sys._getframe(1))
return interpret(offending_line, frame)
def getfailure(e):
explanation = util.format_explanation(e.explanation)
value = e.cause[1]
if str(value):
lines = explanation.split('\n')
lines[0] += " << %s" % (value,)
explanation = '\n'.join(lines)
text = "%s: %s" % (e.cause[0].__name__, explanation)
if text.startswith('AssertionError: assert '):
text = text[16:]
return text
operator_map = {
ast.BitOr : "|",
ast.BitXor : "^",
ast.BitAnd : "&",
ast.LShift : "<<",
ast.RShift : ">>",
ast.Add : "+",
ast.Sub : "-",
ast.Mult : "*",
ast.Div : "/",
ast.FloorDiv : "//",
ast.Mod : "%",
ast.Eq : "==",
ast.NotEq : "!=",
ast.Lt : "<",
ast.LtE : "<=",
ast.Gt : ">",
ast.GtE : ">=",
ast.Pow : "**",
ast.Is : "is",
ast.IsNot : "is not",
ast.In : "in",
ast.NotIn : "not in"
}
unary_map = {
ast.Not : "not %s",
ast.Invert : "~%s",
ast.USub : "-%s",
ast.UAdd : "+%s"
}
class DebugInterpreter(ast.NodeVisitor):
"""Interpret AST nodes to gleam useful debugging information. """
def __init__(self, frame):
self.frame = frame
def generic_visit(self, node):
# Fallback when we don't have a special implementation.
if _is_ast_expr(node):
mod = ast.Expression(node)
co = self._compile(mod)
try:
result = self.frame.eval(co)
except Exception:
raise Failure()
explanation = self.frame.repr(result)
return explanation, result
elif _is_ast_stmt(node):
mod = ast.Module([node])
co = self._compile(mod, "exec")
try:
self.frame.exec_(co)
except Exception:
raise Failure()
return None, None
else:
raise AssertionError("can't handle %s" %(node,))
def _compile(self, source, mode="eval"):
return compile(source, "<assertion interpretation>", mode)
def visit_Expr(self, expr):
return self.visit(expr.value)
def visit_Module(self, mod):
for stmt in mod.body:
self.visit(stmt)
def visit_Name(self, name):
explanation, result = self.generic_visit(name)
# See if the name is local.
source = "%r in locals() is not globals()" % (name.id,)
co = self._compile(source)
try:
local = self.frame.eval(co)
except Exception:
# have to assume it isn't
local = None
if local is None or not self.frame.is_true(local):
return name.id, result
return explanation, result
def visit_Compare(self, comp):
left = comp.left
left_explanation, left_result = self.visit(left)
for op, next_op in zip(comp.ops, comp.comparators):
next_explanation, next_result = self.visit(next_op)
op_symbol = operator_map[op.__class__]
explanation = "%s %s %s" % (left_explanation, op_symbol,
next_explanation)
source = "__exprinfo_left %s __exprinfo_right" % (op_symbol,)
co = self._compile(source)
try:
result = self.frame.eval(co, __exprinfo_left=left_result,
__exprinfo_right=next_result)
except Exception:
raise Failure(explanation)
try:
if not self.frame.is_true(result):
break
except KeyboardInterrupt:
raise
except:
break
left_explanation, left_result = next_explanation, next_result
if util._reprcompare is not None:
res = util._reprcompare(op_symbol, left_result, next_result)
if res:
explanation = res
return explanation, result
def visit_BoolOp(self, boolop):
is_or = isinstance(boolop.op, ast.Or)
explanations = []
for operand in boolop.values:
explanation, result = self.visit(operand)
explanations.append(explanation)
if result == is_or:
break
name = is_or and " or " or " and "
explanation = "(" + name.join(explanations) + ")"
return explanation, result
def visit_UnaryOp(self, unary):
pattern = unary_map[unary.op.__class__]
operand_explanation, operand_result = self.visit(unary.operand)
explanation = pattern % (operand_explanation,)
co = self._compile(pattern % ("__exprinfo_expr",))
try:
result = self.frame.eval(co, __exprinfo_expr=operand_result)
except Exception:
raise Failure(explanation)
return explanation, result
def visit_BinOp(self, binop):
left_explanation, left_result = self.visit(binop.left)
right_explanation, right_result = self.visit(binop.right)
symbol = operator_map[binop.op.__class__]
explanation = "(%s %s %s)" % (left_explanation, symbol,
right_explanation)
source = "__exprinfo_left %s __exprinfo_right" % (symbol,)
co = self._compile(source)
try:
result = self.frame.eval(co, __exprinfo_left=left_result,
__exprinfo_right=right_result)
except Exception:
raise Failure(explanation)
return explanation, result
def visit_Call(self, call):
func_explanation, func = self.visit(call.func)
arg_explanations = []
ns = {"__exprinfo_func" : func}
arguments = []
for arg in call.args:
arg_explanation, arg_result = self.visit(arg)
arg_name = "__exprinfo_%s" % (len(ns),)
ns[arg_name] = arg_result
arguments.append(arg_name)
arg_explanations.append(arg_explanation)
for keyword in call.keywords:
arg_explanation, arg_result = self.visit(keyword.value)
arg_name = "__exprinfo_%s" % (len(ns),)
ns[arg_name] = arg_result
keyword_source = "%s=%%s" % (keyword.arg)
arguments.append(keyword_source % (arg_name,))
arg_explanations.append(keyword_source % (arg_explanation,))
if call.starargs:
arg_explanation, arg_result = self.visit(call.starargs)
arg_name = "__exprinfo_star"
ns[arg_name] = arg_result
arguments.append("*%s" % (arg_name,))
arg_explanations.append("*%s" % (arg_explanation,))
if call.kwargs:
arg_explanation, arg_result = self.visit(call.kwargs)
arg_name = "__exprinfo_kwds"
ns[arg_name] = arg_result
arguments.append("**%s" % (arg_name,))
arg_explanations.append("**%s" % (arg_explanation,))
args_explained = ", ".join(arg_explanations)
explanation = "%s(%s)" % (func_explanation, args_explained)
args = ", ".join(arguments)
source = "__exprinfo_func(%s)" % (args,)
co = self._compile(source)
try:
result = self.frame.eval(co, **ns)
except Exception:
raise Failure(explanation)
pattern = "%s\n{%s = %s\n}"
rep = self.frame.repr(result)
explanation = pattern % (rep, rep, explanation)
return explanation, result
def _is_builtin_name(self, name):
pattern = "%r not in globals() and %r not in locals()"
source = pattern % (name.id, name.id)
co = self._compile(source)
try:
return self.frame.eval(co)
except Exception:
return False
def visit_Attribute(self, attr):
if not isinstance(attr.ctx, ast.Load):
return self.generic_visit(attr)
source_explanation, source_result = self.visit(attr.value)
explanation = "%s.%s" % (source_explanation, attr.attr)
source = "__exprinfo_expr.%s" % (attr.attr,)
co = self._compile(source)
try:
try:
result = self.frame.eval(co, __exprinfo_expr=source_result)
except AttributeError:
# Maybe the attribute name needs to be mangled?
if not attr.attr.startswith("__") or attr.attr.endswith("__"):
raise
source = "getattr(__exprinfo_expr.__class__, '__name__', '')"
co = self._compile(source)
class_name = self.frame.eval(co, __exprinfo_expr=source_result)
mangled_attr = "_" + class_name + attr.attr
source = "__exprinfo_expr.%s" % (mangled_attr,)
co = self._compile(source)
result = self.frame.eval(co, __exprinfo_expr=source_result)
except Exception:
raise Failure(explanation)
explanation = "%s\n{%s = %s.%s\n}" % (self.frame.repr(result),
self.frame.repr(result),
source_explanation, attr.attr)
# Check if the attr is from an instance.
source = "%r in getattr(__exprinfo_expr, '__dict__', {})"
source = source % (attr.attr,)
co = self._compile(source)
try:
from_instance = self.frame.eval(co, __exprinfo_expr=source_result)
except Exception:
from_instance = None
if from_instance is None or self.frame.is_true(from_instance):
rep = self.frame.repr(result)
pattern = "%s\n{%s = %s\n}"
explanation = pattern % (rep, rep, explanation)
return explanation, result
def visit_Assert(self, assrt):
test_explanation, test_result = self.visit(assrt.test)
explanation = "assert %s" % (test_explanation,)
if not self.frame.is_true(test_result):
try:
raise BuiltinAssertionError
except Exception:
raise Failure(explanation)
return explanation, test_result
def visit_Assign(self, assign):
value_explanation, value_result = self.visit(assign.value)
explanation = "... = %s" % (value_explanation,)
name = ast.Name("__exprinfo_expr", ast.Load(),
lineno=assign.value.lineno,
col_offset=assign.value.col_offset)
new_assign = ast.Assign(assign.targets, name, lineno=assign.lineno,
col_offset=assign.col_offset)
mod = ast.Module([new_assign])
co = self._compile(mod, "exec")
try:
self.frame.exec_(co, __exprinfo_expr=value_result)
except Exception:
raise Failure(explanation)
return explanation, value_result

View File

@@ -1,566 +0,0 @@
import traceback
import types
import py
import sys, inspect
from compiler import parse, ast, pycodegen
from _pytest.assertion.util import format_explanation, BuiltinAssertionError
passthroughex = py.builtin._sysex
class Failure:
def __init__(self, node):
self.exc, self.value, self.tb = sys.exc_info()
self.node = node
class View(object):
"""View base class.
If C is a subclass of View, then C(x) creates a proxy object around
the object x. The actual class of the proxy is not C in general,
but a *subclass* of C determined by the rules below. To avoid confusion
we call view class the class of the proxy (a subclass of C, so of View)
and object class the class of x.
Attributes and methods not found in the proxy are automatically read on x.
Other operations like setting attributes are performed on the proxy, as
determined by its view class. The object x is available from the proxy
as its __obj__ attribute.
The view class selection is determined by the __view__ tuples and the
optional __viewkey__ method. By default, the selected view class is the
most specific subclass of C whose __view__ mentions the class of x.
If no such subclass is found, the search proceeds with the parent
object classes. For example, C(True) will first look for a subclass
of C with __view__ = (..., bool, ...) and only if it doesn't find any
look for one with __view__ = (..., int, ...), and then ..., object,...
If everything fails the class C itself is considered to be the default.
Alternatively, the view class selection can be driven by another aspect
of the object x, instead of the class of x, by overriding __viewkey__.
See last example at the end of this module.
"""
_viewcache = {}
__view__ = ()
def __new__(rootclass, obj, *args, **kwds):
self = object.__new__(rootclass)
self.__obj__ = obj
self.__rootclass__ = rootclass
key = self.__viewkey__()
try:
self.__class__ = self._viewcache[key]
except KeyError:
self.__class__ = self._selectsubclass(key)
return self
def __getattr__(self, attr):
# attributes not found in the normal hierarchy rooted on View
# are looked up in the object's real class
return getattr(object.__getattribute__(self, '__obj__'), attr)
def __viewkey__(self):
return self.__obj__.__class__
def __matchkey__(self, key, subclasses):
if inspect.isclass(key):
keys = inspect.getmro(key)
else:
keys = [key]
for key in keys:
result = [C for C in subclasses if key in C.__view__]
if result:
return result
return []
def _selectsubclass(self, key):
subclasses = list(enumsubclasses(self.__rootclass__))
for C in subclasses:
if not isinstance(C.__view__, tuple):
C.__view__ = (C.__view__,)
choices = self.__matchkey__(key, subclasses)
if not choices:
return self.__rootclass__
elif len(choices) == 1:
return choices[0]
else:
# combine the multiple choices
return type('?', tuple(choices), {})
def __repr__(self):
return '%s(%r)' % (self.__rootclass__.__name__, self.__obj__)
def enumsubclasses(cls):
for subcls in cls.__subclasses__():
for subsubclass in enumsubclasses(subcls):
yield subsubclass
yield cls
class Interpretable(View):
"""A parse tree node with a few extra methods."""
explanation = None
def is_builtin(self, frame):
return False
def eval(self, frame):
# fall-back for unknown expression nodes
try:
expr = ast.Expression(self.__obj__)
expr.filename = '<eval>'
self.__obj__.filename = '<eval>'
co = pycodegen.ExpressionCodeGenerator(expr).getCode()
result = frame.eval(co)
except passthroughex:
raise
except:
raise Failure(self)
self.result = result
self.explanation = self.explanation or frame.repr(self.result)
def run(self, frame):
# fall-back for unknown statement nodes
try:
expr = ast.Module(None, ast.Stmt([self.__obj__]))
expr.filename = '<run>'
co = pycodegen.ModuleCodeGenerator(expr).getCode()
frame.exec_(co)
except passthroughex:
raise
except:
raise Failure(self)
def nice_explanation(self):
return format_explanation(self.explanation)
class Name(Interpretable):
__view__ = ast.Name
def is_local(self, frame):
source = '%r in locals() is not globals()' % self.name
try:
return frame.is_true(frame.eval(source))
except passthroughex:
raise
except:
return False
def is_global(self, frame):
source = '%r in globals()' % self.name
try:
return frame.is_true(frame.eval(source))
except passthroughex:
raise
except:
return False
def is_builtin(self, frame):
source = '%r not in locals() and %r not in globals()' % (
self.name, self.name)
try:
return frame.is_true(frame.eval(source))
except passthroughex:
raise
except:
return False
def eval(self, frame):
super(Name, self).eval(frame)
if not self.is_local(frame):
self.explanation = self.name
class Compare(Interpretable):
__view__ = ast.Compare
def eval(self, frame):
expr = Interpretable(self.expr)
expr.eval(frame)
for operation, expr2 in self.ops:
if hasattr(self, 'result'):
# shortcutting in chained expressions
if not frame.is_true(self.result):
break
expr2 = Interpretable(expr2)
expr2.eval(frame)
self.explanation = "%s %s %s" % (
expr.explanation, operation, expr2.explanation)
source = "__exprinfo_left %s __exprinfo_right" % operation
try:
self.result = frame.eval(source,
__exprinfo_left=expr.result,
__exprinfo_right=expr2.result)
except passthroughex:
raise
except:
raise Failure(self)
expr = expr2
class And(Interpretable):
__view__ = ast.And
def eval(self, frame):
explanations = []
for expr in self.nodes:
expr = Interpretable(expr)
expr.eval(frame)
explanations.append(expr.explanation)
self.result = expr.result
if not frame.is_true(expr.result):
break
self.explanation = '(' + ' and '.join(explanations) + ')'
class Or(Interpretable):
__view__ = ast.Or
def eval(self, frame):
explanations = []
for expr in self.nodes:
expr = Interpretable(expr)
expr.eval(frame)
explanations.append(expr.explanation)
self.result = expr.result
if frame.is_true(expr.result):
break
self.explanation = '(' + ' or '.join(explanations) + ')'
# == Unary operations ==
keepalive = []
for astclass, astpattern in {
ast.Not : 'not __exprinfo_expr',
ast.Invert : '(~__exprinfo_expr)',
}.items():
class UnaryArith(Interpretable):
__view__ = astclass
def eval(self, frame, astpattern=astpattern):
expr = Interpretable(self.expr)
expr.eval(frame)
self.explanation = astpattern.replace('__exprinfo_expr',
expr.explanation)
try:
self.result = frame.eval(astpattern,
__exprinfo_expr=expr.result)
except passthroughex:
raise
except:
raise Failure(self)
keepalive.append(UnaryArith)
# == Binary operations ==
for astclass, astpattern in {
ast.Add : '(__exprinfo_left + __exprinfo_right)',
ast.Sub : '(__exprinfo_left - __exprinfo_right)',
ast.Mul : '(__exprinfo_left * __exprinfo_right)',
ast.Div : '(__exprinfo_left / __exprinfo_right)',
ast.Mod : '(__exprinfo_left % __exprinfo_right)',
ast.Power : '(__exprinfo_left ** __exprinfo_right)',
}.items():
class BinaryArith(Interpretable):
__view__ = astclass
def eval(self, frame, astpattern=astpattern):
left = Interpretable(self.left)
left.eval(frame)
right = Interpretable(self.right)
right.eval(frame)
self.explanation = (astpattern
.replace('__exprinfo_left', left .explanation)
.replace('__exprinfo_right', right.explanation))
try:
self.result = frame.eval(astpattern,
__exprinfo_left=left.result,
__exprinfo_right=right.result)
except passthroughex:
raise
except:
raise Failure(self)
keepalive.append(BinaryArith)
class CallFunc(Interpretable):
__view__ = ast.CallFunc
def is_bool(self, frame):
source = 'isinstance(__exprinfo_value, bool)'
try:
return frame.is_true(frame.eval(source,
__exprinfo_value=self.result))
except passthroughex:
raise
except:
return False
def eval(self, frame):
node = Interpretable(self.node)
node.eval(frame)
explanations = []
vars = {'__exprinfo_fn': node.result}
source = '__exprinfo_fn('
for a in self.args:
if isinstance(a, ast.Keyword):
keyword = a.name
a = a.expr
else:
keyword = None
a = Interpretable(a)
a.eval(frame)
argname = '__exprinfo_%d' % len(vars)
vars[argname] = a.result
if keyword is None:
source += argname + ','
explanations.append(a.explanation)
else:
source += '%s=%s,' % (keyword, argname)
explanations.append('%s=%s' % (keyword, a.explanation))
if self.star_args:
star_args = Interpretable(self.star_args)
star_args.eval(frame)
argname = '__exprinfo_star'
vars[argname] = star_args.result
source += '*' + argname + ','
explanations.append('*' + star_args.explanation)
if self.dstar_args:
dstar_args = Interpretable(self.dstar_args)
dstar_args.eval(frame)
argname = '__exprinfo_kwds'
vars[argname] = dstar_args.result
source += '**' + argname + ','
explanations.append('**' + dstar_args.explanation)
self.explanation = "%s(%s)" % (
node.explanation, ', '.join(explanations))
if source.endswith(','):
source = source[:-1]
source += ')'
try:
self.result = frame.eval(source, **vars)
except passthroughex:
raise
except:
raise Failure(self)
if not node.is_builtin(frame) or not self.is_bool(frame):
r = frame.repr(self.result)
self.explanation = '%s\n{%s = %s\n}' % (r, r, self.explanation)
class Getattr(Interpretable):
__view__ = ast.Getattr
def eval(self, frame):
expr = Interpretable(self.expr)
expr.eval(frame)
source = '__exprinfo_expr.%s' % self.attrname
try:
try:
self.result = frame.eval(source, __exprinfo_expr=expr.result)
except AttributeError:
# Maybe the attribute name needs to be mangled?
if (not self.attrname.startswith("__") or
self.attrname.endswith("__")):
raise
source = "getattr(__exprinfo_expr.__class__, '__name__', '')"
class_name = frame.eval(source, __exprinfo_expr=expr.result)
mangled_attr = "_" + class_name + self.attrname
source = "__exprinfo_expr.%s" % (mangled_attr,)
self.result = frame.eval(source, __exprinfo_expr=expr.result)
except passthroughex:
raise
except:
raise Failure(self)
self.explanation = '%s.%s' % (expr.explanation, self.attrname)
# if the attribute comes from the instance, its value is interesting
source = ('hasattr(__exprinfo_expr, "__dict__") and '
'%r in __exprinfo_expr.__dict__' % self.attrname)
try:
from_instance = frame.is_true(
frame.eval(source, __exprinfo_expr=expr.result))
except passthroughex:
raise
except:
from_instance = True
if from_instance:
r = frame.repr(self.result)
self.explanation = '%s\n{%s = %s\n}' % (r, r, self.explanation)
# == Re-interpretation of full statements ==
class Assert(Interpretable):
__view__ = ast.Assert
def run(self, frame):
test = Interpretable(self.test)
test.eval(frame)
# print the result as 'assert <explanation>'
self.result = test.result
self.explanation = 'assert ' + test.explanation
if not frame.is_true(test.result):
try:
raise BuiltinAssertionError
except passthroughex:
raise
except:
raise Failure(self)
class Assign(Interpretable):
__view__ = ast.Assign
def run(self, frame):
expr = Interpretable(self.expr)
expr.eval(frame)
self.result = expr.result
self.explanation = '... = ' + expr.explanation
# fall-back-run the rest of the assignment
ass = ast.Assign(self.nodes, ast.Name('__exprinfo_expr'))
mod = ast.Module(None, ast.Stmt([ass]))
mod.filename = '<run>'
co = pycodegen.ModuleCodeGenerator(mod).getCode()
try:
frame.exec_(co, __exprinfo_expr=expr.result)
except passthroughex:
raise
except:
raise Failure(self)
class Discard(Interpretable):
__view__ = ast.Discard
def run(self, frame):
expr = Interpretable(self.expr)
expr.eval(frame)
self.result = expr.result
self.explanation = expr.explanation
class Stmt(Interpretable):
__view__ = ast.Stmt
def run(self, frame):
for stmt in self.nodes:
stmt = Interpretable(stmt)
stmt.run(frame)
def report_failure(e):
explanation = e.node.nice_explanation()
if explanation:
explanation = ", in: " + explanation
else:
explanation = ""
sys.stdout.write("%s: %s%s\n" % (e.exc.__name__, e.value, explanation))
def check(s, frame=None):
if frame is None:
frame = sys._getframe(1)
frame = py.code.Frame(frame)
expr = parse(s, 'eval')
assert isinstance(expr, ast.Expression)
node = Interpretable(expr.node)
try:
node.eval(frame)
except passthroughex:
raise
except Failure:
e = sys.exc_info()[1]
report_failure(e)
else:
if not frame.is_true(node.result):
sys.stderr.write("assertion failed: %s\n" % node.nice_explanation())
###########################################################
# API / Entry points
# #########################################################
def interpret(source, frame, should_fail=False):
module = Interpretable(parse(source, 'exec').node)
#print "got module", module
if isinstance(frame, types.FrameType):
frame = py.code.Frame(frame)
try:
module.run(frame)
except Failure:
e = sys.exc_info()[1]
return getfailure(e)
except passthroughex:
raise
except:
traceback.print_exc()
if should_fail:
return ("(assertion failed, but when it was re-run for "
"printing intermediate values, it did not fail. Suggestions: "
"compute assert expression before the assert or use --assert=plain)")
else:
return None
def getmsg(excinfo):
if isinstance(excinfo, tuple):
excinfo = py.code.ExceptionInfo(excinfo)
#frame, line = gettbline(tb)
#frame = py.code.Frame(frame)
#return interpret(line, frame)
tb = excinfo.traceback[-1]
source = str(tb.statement).strip()
x = interpret(source, tb.frame, should_fail=True)
if not isinstance(x, str):
raise TypeError("interpret returned non-string %r" % (x,))
return x
def getfailure(e):
explanation = e.node.nice_explanation()
if str(e.value):
lines = explanation.split('\n')
lines[0] += " << %s" % (e.value,)
explanation = '\n'.join(lines)
text = "%s: %s" % (e.exc.__name__, explanation)
if text.startswith('AssertionError: assert '):
text = text[16:]
return text
def run(s, frame=None):
if frame is None:
frame = sys._getframe(1)
frame = py.code.Frame(frame)
module = Interpretable(parse(s, 'exec').node)
try:
module.run(frame)
except Failure:
e = sys.exc_info()[1]
report_failure(e)
if __name__ == '__main__':
# example:
def f():
return 5
def g():
return 3
def h(x):
return 'never'
check("f() * g() == 5")
check("not f()")
check("not (f() and g() or 0)")
check("f() == g()")
i = 4
check("i == f()")
check("len(f()) == 0")
check("isinstance(2+3+4, float)")
run("x = i")
check("x == 5")
run("assert not f(), 'oops'")
run("a, b, c = 1, 2")
run("a, b, c = f()")
check("max([f(),g()]) == 4")
check("'hello'[g()] == 'h'")
run("'guk%d' % h(f())")

View File

@@ -1,12 +1,18 @@
"""
Find intermediate evalutation results in assert statements through builtin AST.
"""
import ast
import sys
import _pytest._code
import py
from _pytest.assertion.util import BuiltinAssertionError
from _pytest.assertion import util
u = py.builtin._totext
class AssertionError(BuiltinAssertionError):
class AssertionError(util.BuiltinAssertionError):
def __init__(self, *args):
BuiltinAssertionError.__init__(self, *args)
util.BuiltinAssertionError.__init__(self, *args)
if args:
# on Python2.6 we get len(args)==2 for: assert 0, (x,y)
# on Python2.7 and above we always get len(args) == 1
@@ -22,7 +28,7 @@ class AssertionError(BuiltinAssertionError):
"<[broken __repr__] %s at %0xd>"
% (toprint.__class__, id(toprint)))
else:
f = py.code.Frame(sys._getframe(1))
f = _pytest._code.Frame(sys._getframe(1))
try:
source = f.code.fullsource
if source is not None:
@@ -46,7 +52,356 @@ class AssertionError(BuiltinAssertionError):
if sys.version_info > (3, 0):
AssertionError.__module__ = "builtins"
if sys.version_info >= (2, 6) or sys.platform.startswith("java"):
from _pytest.assertion.newinterpret import interpret as reinterpret
if sys.platform.startswith("java"):
# See http://bugs.jython.org/issue1497
_exprs = ("BoolOp", "BinOp", "UnaryOp", "Lambda", "IfExp", "Dict",
"ListComp", "GeneratorExp", "Yield", "Compare", "Call",
"Repr", "Num", "Str", "Attribute", "Subscript", "Name",
"List", "Tuple")
_stmts = ("FunctionDef", "ClassDef", "Return", "Delete", "Assign",
"AugAssign", "Print", "For", "While", "If", "With", "Raise",
"TryExcept", "TryFinally", "Assert", "Import", "ImportFrom",
"Exec", "Global", "Expr", "Pass", "Break", "Continue")
_expr_nodes = set(getattr(ast, name) for name in _exprs)
_stmt_nodes = set(getattr(ast, name) for name in _stmts)
def _is_ast_expr(node):
return node.__class__ in _expr_nodes
def _is_ast_stmt(node):
return node.__class__ in _stmt_nodes
else:
from _pytest.assertion.oldinterpret import interpret as reinterpret
def _is_ast_expr(node):
return isinstance(node, ast.expr)
def _is_ast_stmt(node):
return isinstance(node, ast.stmt)
try:
_Starred = ast.Starred
except AttributeError:
# Python 2. Define a dummy class so isinstance() will always be False.
class _Starred(object): pass
class Failure(Exception):
"""Error found while interpreting AST."""
def __init__(self, explanation=""):
self.cause = sys.exc_info()
self.explanation = explanation
def reinterpret(source, frame, should_fail=False):
mod = ast.parse(source)
visitor = DebugInterpreter(frame)
try:
visitor.visit(mod)
except Failure:
failure = sys.exc_info()[1]
return getfailure(failure)
if should_fail:
return ("(assertion failed, but when it was re-run for "
"printing intermediate values, it did not fail. Suggestions: "
"compute assert expression before the assert or use --assert=plain)")
def run(offending_line, frame=None):
if frame is None:
frame = _pytest._code.Frame(sys._getframe(1))
return reinterpret(offending_line, frame)
def getfailure(e):
explanation = util.format_explanation(e.explanation)
value = e.cause[1]
if str(value):
lines = explanation.split('\n')
lines[0] += " << %s" % (value,)
explanation = '\n'.join(lines)
text = "%s: %s" % (e.cause[0].__name__, explanation)
if text.startswith('AssertionError: assert '):
text = text[16:]
return text
operator_map = {
ast.BitOr : "|",
ast.BitXor : "^",
ast.BitAnd : "&",
ast.LShift : "<<",
ast.RShift : ">>",
ast.Add : "+",
ast.Sub : "-",
ast.Mult : "*",
ast.Div : "/",
ast.FloorDiv : "//",
ast.Mod : "%",
ast.Eq : "==",
ast.NotEq : "!=",
ast.Lt : "<",
ast.LtE : "<=",
ast.Gt : ">",
ast.GtE : ">=",
ast.Pow : "**",
ast.Is : "is",
ast.IsNot : "is not",
ast.In : "in",
ast.NotIn : "not in"
}
unary_map = {
ast.Not : "not %s",
ast.Invert : "~%s",
ast.USub : "-%s",
ast.UAdd : "+%s"
}
class DebugInterpreter(ast.NodeVisitor):
"""Interpret AST nodes to gleam useful debugging information. """
def __init__(self, frame):
self.frame = frame
def generic_visit(self, node):
# Fallback when we don't have a special implementation.
if _is_ast_expr(node):
mod = ast.Expression(node)
co = self._compile(mod)
try:
result = self.frame.eval(co)
except Exception:
raise Failure()
explanation = self.frame.repr(result)
return explanation, result
elif _is_ast_stmt(node):
mod = ast.Module([node])
co = self._compile(mod, "exec")
try:
self.frame.exec_(co)
except Exception:
raise Failure()
return None, None
else:
raise AssertionError("can't handle %s" %(node,))
def _compile(self, source, mode="eval"):
return compile(source, "<assertion interpretation>", mode)
def visit_Expr(self, expr):
return self.visit(expr.value)
def visit_Module(self, mod):
for stmt in mod.body:
self.visit(stmt)
def visit_Name(self, name):
explanation, result = self.generic_visit(name)
# See if the name is local.
source = "%r in locals() is not globals()" % (name.id,)
co = self._compile(source)
try:
local = self.frame.eval(co)
except Exception:
# have to assume it isn't
local = None
if local is None or not self.frame.is_true(local):
return name.id, result
return explanation, result
def visit_Compare(self, comp):
left = comp.left
left_explanation, left_result = self.visit(left)
for op, next_op in zip(comp.ops, comp.comparators):
next_explanation, next_result = self.visit(next_op)
op_symbol = operator_map[op.__class__]
explanation = "%s %s %s" % (left_explanation, op_symbol,
next_explanation)
source = "__exprinfo_left %s __exprinfo_right" % (op_symbol,)
co = self._compile(source)
try:
result = self.frame.eval(co, __exprinfo_left=left_result,
__exprinfo_right=next_result)
except Exception:
raise Failure(explanation)
try:
if not self.frame.is_true(result):
break
except KeyboardInterrupt:
raise
except:
break
left_explanation, left_result = next_explanation, next_result
if util._reprcompare is not None:
res = util._reprcompare(op_symbol, left_result, next_result)
if res:
explanation = res
return explanation, result
def visit_BoolOp(self, boolop):
is_or = isinstance(boolop.op, ast.Or)
explanations = []
for operand in boolop.values:
explanation, result = self.visit(operand)
explanations.append(explanation)
if result == is_or:
break
name = is_or and " or " or " and "
explanation = "(" + name.join(explanations) + ")"
return explanation, result
def visit_UnaryOp(self, unary):
pattern = unary_map[unary.op.__class__]
operand_explanation, operand_result = self.visit(unary.operand)
explanation = pattern % (operand_explanation,)
co = self._compile(pattern % ("__exprinfo_expr",))
try:
result = self.frame.eval(co, __exprinfo_expr=operand_result)
except Exception:
raise Failure(explanation)
return explanation, result
def visit_BinOp(self, binop):
left_explanation, left_result = self.visit(binop.left)
right_explanation, right_result = self.visit(binop.right)
symbol = operator_map[binop.op.__class__]
explanation = "(%s %s %s)" % (left_explanation, symbol,
right_explanation)
source = "__exprinfo_left %s __exprinfo_right" % (symbol,)
co = self._compile(source)
try:
result = self.frame.eval(co, __exprinfo_left=left_result,
__exprinfo_right=right_result)
except Exception:
raise Failure(explanation)
return explanation, result
def visit_Call(self, call):
func_explanation, func = self.visit(call.func)
arg_explanations = []
ns = {"__exprinfo_func" : func}
arguments = []
for arg in call.args:
arg_explanation, arg_result = self.visit(arg)
if isinstance(arg, _Starred):
arg_name = "__exprinfo_star"
ns[arg_name] = arg_result
arguments.append("*%s" % (arg_name,))
arg_explanations.append("*%s" % (arg_explanation,))
else:
arg_name = "__exprinfo_%s" % (len(ns),)
ns[arg_name] = arg_result
arguments.append(arg_name)
arg_explanations.append(arg_explanation)
for keyword in call.keywords:
arg_explanation, arg_result = self.visit(keyword.value)
if keyword.arg:
arg_name = "__exprinfo_%s" % (len(ns),)
keyword_source = "%s=%%s" % (keyword.arg)
arguments.append(keyword_source % (arg_name,))
arg_explanations.append(keyword_source % (arg_explanation,))
else:
arg_name = "__exprinfo_kwds"
arguments.append("**%s" % (arg_name,))
arg_explanations.append("**%s" % (arg_explanation,))
ns[arg_name] = arg_result
if getattr(call, 'starargs', None):
arg_explanation, arg_result = self.visit(call.starargs)
arg_name = "__exprinfo_star"
ns[arg_name] = arg_result
arguments.append("*%s" % (arg_name,))
arg_explanations.append("*%s" % (arg_explanation,))
if getattr(call, 'kwargs', None):
arg_explanation, arg_result = self.visit(call.kwargs)
arg_name = "__exprinfo_kwds"
ns[arg_name] = arg_result
arguments.append("**%s" % (arg_name,))
arg_explanations.append("**%s" % (arg_explanation,))
args_explained = ", ".join(arg_explanations)
explanation = "%s(%s)" % (func_explanation, args_explained)
args = ", ".join(arguments)
source = "__exprinfo_func(%s)" % (args,)
co = self._compile(source)
try:
result = self.frame.eval(co, **ns)
except Exception:
raise Failure(explanation)
pattern = "%s\n{%s = %s\n}"
rep = self.frame.repr(result)
explanation = pattern % (rep, rep, explanation)
return explanation, result
def _is_builtin_name(self, name):
pattern = "%r not in globals() and %r not in locals()"
source = pattern % (name.id, name.id)
co = self._compile(source)
try:
return self.frame.eval(co)
except Exception:
return False
def visit_Attribute(self, attr):
if not isinstance(attr.ctx, ast.Load):
return self.generic_visit(attr)
source_explanation, source_result = self.visit(attr.value)
explanation = "%s.%s" % (source_explanation, attr.attr)
source = "__exprinfo_expr.%s" % (attr.attr,)
co = self._compile(source)
try:
try:
result = self.frame.eval(co, __exprinfo_expr=source_result)
except AttributeError:
# Maybe the attribute name needs to be mangled?
if not attr.attr.startswith("__") or attr.attr.endswith("__"):
raise
source = "getattr(__exprinfo_expr.__class__, '__name__', '')"
co = self._compile(source)
class_name = self.frame.eval(co, __exprinfo_expr=source_result)
mangled_attr = "_" + class_name + attr.attr
source = "__exprinfo_expr.%s" % (mangled_attr,)
co = self._compile(source)
result = self.frame.eval(co, __exprinfo_expr=source_result)
except Exception:
raise Failure(explanation)
explanation = "%s\n{%s = %s.%s\n}" % (self.frame.repr(result),
self.frame.repr(result),
source_explanation, attr.attr)
# Check if the attr is from an instance.
source = "%r in getattr(__exprinfo_expr, '__dict__', {})"
source = source % (attr.attr,)
co = self._compile(source)
try:
from_instance = self.frame.eval(co, __exprinfo_expr=source_result)
except Exception:
from_instance = None
if from_instance is None or self.frame.is_true(from_instance):
rep = self.frame.repr(result)
pattern = "%s\n{%s = %s\n}"
explanation = pattern % (rep, rep, explanation)
return explanation, result
def visit_Assert(self, assrt):
test_explanation, test_result = self.visit(assrt.test)
explanation = "assert %s" % (test_explanation,)
if not self.frame.is_true(test_result):
try:
raise util.BuiltinAssertionError
except Exception:
raise Failure(explanation)
return explanation, test_result
def visit_Assign(self, assign):
value_explanation, value_result = self.visit(assign.value)
explanation = "... = %s" % (value_explanation,)
name = ast.Name("__exprinfo_expr", ast.Load(),
lineno=assign.value.lineno,
col_offset=assign.value.col_offset)
new_assign = ast.Assign(assign.targets, name, lineno=assign.lineno,
col_offset=assign.col_offset)
mod = ast.Module([new_assign])
co = self._compile(mod, "exec")
try:
self.frame.exec_(co, __exprinfo_expr=value_result)
except Exception:
raise Failure(explanation)
return explanation, value_result

View File

@@ -35,6 +35,12 @@ 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):
ast_Call = ast.Call
else:
ast_Call = lambda a,b,c: ast.Call(a, b, c, None, None)
class AssertionRewritingHook(object):
"""PEP302 Import hook which rewrites asserts."""
@@ -122,7 +128,7 @@ class AssertionRewritingHook(object):
# One of the path components was not a directory, likely
# because we're in a zip file.
write = False
elif e == errno.EACCES:
elif e in [errno.EACCES, errno.EROFS, errno.EPERM]:
state.trace("read only directory: %r" % fn_pypath.dirname)
write = False
else:
@@ -146,6 +152,12 @@ class AssertionRewritingHook(object):
return self
def load_module(self, name):
# If there is an existing module object named 'fullname' in
# sys.modules, the loader must use that existing module. (Otherwise,
# the reload() builtin will not work correctly.)
if name in sys.modules:
return sys.modules[name]
co, pyc = self.modules.pop(name)
# I wish I could just call imp.load_compiled here, but __file__ has to
# be set properly. In Python 3.2+, this all would be handled correctly
@@ -191,6 +203,12 @@ class AssertionRewritingHook(object):
# DefaultProvider is appropriate.
pkg_resources.register_loader_type(cls, pkg_resources.DefaultProvider)
def get_data(self, pathname):
"""Optional PEP302 get_data API.
"""
with open(pathname, 'rb') as f:
return f.read()
def _write_pyc(state, co, source_stat, pyc):
# Technically, we don't have to have the same pyc format as
@@ -373,7 +391,7 @@ def _format_assertmsg(obj):
t = py.builtin.text
else:
t = py.builtin.bytes
s = s.replace(t("\n"), t("\n~"))
s = s.replace(t("\n"), t("\n~")).replace(t("%"), t("%%"))
if is_repr:
s = s.replace(t("\\n"), t("\n~"))
return s
@@ -382,7 +400,12 @@ def _should_repr_global_name(obj):
return not hasattr(obj, "__name__") and not py.builtin.callable(obj)
def _format_boolop(explanations, is_or):
return "(" + (is_or and " or " or " and ").join(explanations) + ")"
explanation = "(" + (is_or and " or " or " and ").join(explanations) + ")"
if py.builtin._istext(explanation):
t = py.builtin.text
else:
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):
@@ -430,6 +453,18 @@ binop_map = {
ast.In: "in",
ast.NotIn: "not in"
}
# Python 3.5+ compatibility
try:
binop_map[ast.MatMult] = "@"
except AttributeError:
pass
# Python 3.4+ compatibility
if hasattr(ast, "NameConstant"):
_NameConstant = ast.NameConstant
else:
def _NameConstant(c):
return ast.Name(str(c), ast.Load())
def set_location(node, lineno, col_offset):
@@ -455,7 +490,7 @@ class AssertionRewriter(ast.NodeVisitor):
for an overview of how this works.
The entry point here is .run() which will iterate over all the
statenemts in an ast.Module and for each ast.Assert statement it
statements in an ast.Module and for each ast.Assert statement it
finds call .visit() with it. Then .visit_Assert() takes over and
is responsible for creating new ast statements to replace the
original assert statement: it re-writes the test of an assertion
@@ -569,7 +604,7 @@ class AssertionRewriter(ast.NodeVisitor):
"""Call a helper in this module."""
py_name = ast.Name("@pytest_ar", ast.Load())
attr = ast.Attribute(py_name, "_" + name, ast.Load())
return ast.Call(attr, list(args), [], None, None)
return ast_Call(attr, list(args), [])
def builtin(self, name):
"""Return the builtin called *name*."""
@@ -659,7 +694,7 @@ class AssertionRewriter(ast.NodeVisitor):
msg = self.pop_format_context(template)
fmt = self.helper("format_explanation", msg)
err_name = ast.Name("AssertionError", ast.Load())
exc = ast.Call(err_name, [fmt], [], None, None)
exc = ast_Call(err_name, [fmt], [])
if sys.version_info[0] >= 3:
raise_ = ast.Raise(exc, None)
else:
@@ -669,7 +704,7 @@ class AssertionRewriter(ast.NodeVisitor):
if self.variables:
variables = [ast.Name(name, ast.Store())
for name in self.variables]
clear = ast.Assign(variables, ast.Name("None", ast.Load()))
clear = ast.Assign(variables, _NameConstant(None))
self.statements.append(clear)
# Fix line numbers.
for stmt in self.statements:
@@ -679,7 +714,7 @@ class AssertionRewriter(ast.NodeVisitor):
def visit_Name(self, name):
# Display the repr of the name if it's a local variable or
# _should_repr_global_name() thinks it's acceptable.
locs = ast.Call(self.builtin("locals"), [], [], None, None)
locs = ast_Call(self.builtin("locals"), [], [])
inlocs = ast.Compare(ast.Str(name.id), [ast.In()], [locs])
dorepr = self.helper("should_repr_global_name", name)
test = ast.BoolOp(ast.Or(), [inlocs, dorepr])
@@ -706,7 +741,7 @@ class AssertionRewriter(ast.NodeVisitor):
res, expl = self.visit(v)
body.append(ast.Assign([ast.Name(res_var, ast.Store())], res))
expl_format = self.pop_format_context(ast.Str(expl))
call = ast.Call(app, [expl_format], [], None, None)
call = ast_Call(app, [expl_format], [])
self.on_failure.append(ast.Expr(call))
if i < levels:
cond = res
@@ -735,7 +770,42 @@ class AssertionRewriter(ast.NodeVisitor):
res = self.assign(ast.BinOp(left_expr, binop.op, right_expr))
return res, explanation
def visit_Call(self, call):
def visit_Call_35(self, call):
"""
visit `ast.Call` nodes on Python3.5 and after
"""
new_func, func_expl = self.visit(call.func)
arg_expls = []
new_args = []
new_kwargs = []
for arg in call.args:
res, expl = self.visit(arg)
arg_expls.append(expl)
new_args.append(res)
for keyword in call.keywords:
res, expl = self.visit(keyword.value)
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
arg_expls.append("**" + expl)
expl = "%s(%s)" % (func_expl, ', '.join(arg_expls))
new_call = ast.Call(new_func, new_args, new_kwargs)
res = self.assign(new_call)
res_expl = self.explanation_param(self.display(res))
outer_expl = "%s\n{%s = %s\n}" % (res_expl, res_expl, expl)
return res, outer_expl
def visit_Starred(self, starred):
# From Python 3.5, a Starred node can appear in a function call
res, expl = self.visit(starred.value)
return starred, '*' + expl
def visit_Call_legacy(self, call):
"""
visit `ast.Call nodes on 3.4 and below`
"""
new_func, func_expl = self.visit(call.func)
arg_expls = []
new_args = []
@@ -763,6 +833,15 @@ class AssertionRewriter(ast.NodeVisitor):
outer_expl = "%s\n{%s = %s\n}" % (res_expl, res_expl, expl)
return res, outer_expl
# ast.Call signature changed on 3.5,
# conditionally change which methods is named
# visit_Call depending on Python version
if sys.version_info >= (3, 5):
visit_Call = visit_Call_35
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

@@ -1,6 +1,7 @@
"""Utilities for assertion debugging"""
import pprint
import _pytest._code
import py
try:
from collections import Sequence
@@ -17,6 +18,15 @@ u = py.builtin._totext
_reprcompare = None
# the re-encoding is needed for python2 repr
# with non-ascii characters (see issue 877 and 1379)
def ecu(s):
try:
return u(s, 'utf-8', 'replace')
except TypeError:
return s
def format_explanation(explanation):
"""This formats an explanation
@@ -27,6 +37,7 @@ def format_explanation(explanation):
for when one explanation needs to span multiple lines, e.g. when
displaying diffs.
"""
explanation = ecu(explanation)
explanation = _collapse_false(explanation)
lines = _split_explanation(explanation)
result = _format_lines(lines)
@@ -45,13 +56,15 @@ def _collapse_false(explanation):
if where == -1:
break
level = 0
prev_c = explanation[start]
for i, c in enumerate(explanation[start:]):
if c == "{":
if prev_c + c == "\n{":
level += 1
elif c == "}":
elif prev_c + c == "\n}":
level -= 1
if not level:
break
prev_c = c
else:
raise AssertionError("unbalanced braces: %r" % (explanation,))
end = start + i
@@ -127,26 +140,41 @@ def assertrepr_compare(config, op, left, right):
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))
summary = u('%s %s %s') % (left_repr, op, right_repr)
issequence = lambda x: (isinstance(x, (list, tuple, Sequence))
and not isinstance(x, basestring))
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 isiterable(obj):
try:
iter(obj)
return not istext(obj)
except TypeError:
return False
verbose = config.getoption('verbose')
explanation = None
try:
if op == '==':
if istext(left) and istext(right):
explanation = _diff_text(left, right, verbose)
elif issequence(left) and issequence(right):
explanation = _compare_eq_sequence(left, right, verbose)
elif isset(left) and isset(right):
explanation = _compare_eq_set(left, right, verbose)
elif isdict(left) and isdict(right):
explanation = _compare_eq_dict(left, right, verbose)
else:
if issequence(left) and issequence(right):
explanation = _compare_eq_sequence(left, right, verbose)
elif isset(left) and isset(right):
explanation = _compare_eq_set(left, right, verbose)
elif isdict(left) and isdict(right):
explanation = _compare_eq_dict(left, right, verbose)
if isiterable(left) and isiterable(right):
expl = _compare_eq_iterable(left, right, verbose)
if explanation is not None:
explanation.extend(expl)
else:
explanation = expl
elif op == 'not in':
if istext(left) and istext(right):
explanation = _notin_text(left, right, verbose)
@@ -154,7 +182,7 @@ def assertrepr_compare(config, op, left, right):
explanation = [
u('(pytest_assertion plugin: representation of details failed. '
'Probably an object has a faulty __repr__.)'),
u(py.code.ExceptionInfo())]
u(_pytest._code.ExceptionInfo())]
if not explanation:
return None
@@ -203,6 +231,27 @@ def _diff_text(left, right, verbose=False):
return explanation
def _compare_eq_iterable(left, right, verbose=False):
if not verbose:
return [u('Use -v to get the full diff')]
# dynamic import to speedup pytest
import difflib
try:
left_formatting = pprint.pformat(left).splitlines()
right_formatting = pprint.pformat(right).splitlines()
explanation = [u('Full diff:')]
except Exception:
# hack: PrettyPrinter.pformat() in python 2 fails when formatting items that can't be sorted(), ie, calling
# sorted() on a list would raise. See issue #718.
# As a workaround, the full diff is generated by using the repr() string of each item of each container.
left_formatting = sorted(repr(x) for x in left)
right_formatting = sorted(repr(x) for x in right)
explanation = [u('Full diff (fallback to calling repr on each item):')]
explanation.extend(line.strip() for line in difflib.ndiff(left_formatting, right_formatting))
return explanation
def _compare_eq_sequence(left, right, verbose=False):
explanation = []
for i in range(min(len(left), len(right))):
@@ -217,8 +266,7 @@ def _compare_eq_sequence(left, right, verbose=False):
explanation += [
u('Right contains more items, first extra item: %s') %
py.io.saferepr(right[len(left)],)]
return explanation # + _diff_text(pprint.pformat(left),
# pprint.pformat(right))
return explanation
def _compare_eq_set(left, right, verbose=False):

245
_pytest/cacheprovider.py Executable file
View File

@@ -0,0 +1,245 @@
"""
merged implementation of the cache provider
the name cache was not choosen to ensure pluggy automatically
ignores the external pytest-cache
"""
import py
import pytest
import json
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.trace = config.trace.root.get("cache")
if config.getvalue("cacheclear"):
self.trace("clearing cachedir")
if self._cachedir.check():
self._cachedir.remove()
self._cachedir.mkdir()
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
to manage files likes e. g. store/retrieve database
dumps across test sessions.
:param name: must be a string not containing a ``/`` separator.
Make sure the name contains your plugin or application
identifiers to prevent clashes with other cache users.
"""
if _sep in name or _altsep is not None and _altsep in name:
raise ValueError("name is not allowed to contain path separators")
return self._cachedir.ensure_dir("d", name)
def _getvaluepath(self, key):
return self._cachedir.join('v', *key.split('/'))
def get(self, key, default):
""" return cached value for the given key. If no value
was yet cached or the value cannot be read, the specified
default is returned.
:param key: must be a ``/`` separated value. Usually the first
name is the name of your plugin or your application.
:param default: must be provided in case of a cache-miss or
invalid cache values.
"""
path = self._getvaluepath(key)
if path.check():
try:
with path.open("r") as f:
return json.load(f)
except ValueError:
self.trace("cache-invalid at %s" % (path,))
return default
def set(self, key, value):
""" save value for the given key.
:param key: must be a ``/`` separated value. Usually the first
name is the name of your plugin or your application.
:param value: must be of any combination of basic
python types, including nested types
like e. g. lists of dictionaries.
"""
path = self._getvaluepath(key)
try:
path.dirpath().ensure_dir()
except (py.error.EEXIST, py.error.EACCES):
self.config.warn(
code='I9', message='could not create cache path %s' % (path,)
)
return
try:
f = path.open('w')
except py.error.ENOTDIR:
self.config.warn(
code='I9', message='cache could not write path %s' % (path,))
else:
with f:
self.trace("cache-write %s: %r" % (key, value,))
json.dump(value, f, indent=2, sort_keys=True)
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 = {}
def pytest_report_header(self):
if self.active:
if not self.lastfailed:
mode = "run all (no recorded failures)"
else:
mode = "rerun last %d failures%s" % (
len(self.lastfailed),
" first" if self.config.getvalue("failedfirst") else "")
return "run-last-failure: %s" % mode
def pytest_runtest_logreport(self, report):
if report.failed and "xfail" not in report.keywords:
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')
if passed:
if report.nodeid in self.lastfailed:
self.lastfailed.pop(report.nodeid)
self.lastfailed.update(
(item.nodeid, True)
for item in report.result)
else:
self.lastfailed[report.nodeid] = True
def pytest_collection_modifyitems(self, session, config, items):
if self.active and self.lastfailed:
previously_failed = []
previously_passed = []
for item in items:
if item.nodeid in self.lastfailed:
previously_failed.append(item)
else:
previously_passed.append(item)
if not previously_failed and previously_passed:
# running a subset of all tests with recorded failures outside
# of the set of tests currently executing
pass
elif self.config.getvalue("failedfirst"):
items[:] = previously_failed + previously_passed
else:
items[:] = previously_failed
config.hook.pytest_deselected(items=previously_passed)
def pytest_sessionfinish(self, session):
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:
config.cache.set("cache/lastfailed", self.lastfailed)
def pytest_addoption(parser):
group = parser.getgroup("general")
group.addoption(
'--lf', '--last-failed', action='store_true', dest="lf",
help="rerun only the tests that failed "
"at the last run (or all if none failed)")
group.addoption(
'--ff', '--failed-first', action='store_true', dest="failedfirst",
help="run all tests but run the last failures first. "
"This may re-order tests and thus lead to "
"repeated fixture setup/teardown")
group.addoption(
'--cache-show', action='store_true', dest="cacheshow",
help="show cache contents, don't perform collection or tests")
group.addoption(
'--cache-clear', action='store_true', dest="cacheclear",
help="remove all cache contents at start of test run.")
def pytest_cmdline_main(config):
if config.option.cacheshow:
from _pytest.main import wrap_session
return wrap_session(config, cacheshow)
@pytest.hookimpl(tryfirst=True)
def pytest_configure(config):
config.cache = Cache(config)
config.pluginmanager.register(LFPlugin(config), "lfplugin")
@pytest.fixture
def cache(request):
"""
Return a cache object that can persist state between testing sessions.
cache.get(key, default)
cache.set(key, value)
Keys must be a ``/`` separated value, where the first part is usually the
name of your plugin or application to avoid clashes with other cache users.
Values can be any object handled by the json stdlib module.
"""
return request.config.cache
def pytest_report_header(config):
if config.option.verbose:
relpath = py.path.local().bestrelpath(config.cache._cachedir)
return "cachedir: %s" % relpath
def cacheshow(config, session):
from pprint import pprint
tw = py.io.TerminalWriter()
tw.line("cachedir: " + str(config.cache._cachedir))
if not config.cache._cachedir.check():
tw.line("cache is empty")
return 0
dummy = object()
basedir = config.cache._cachedir
vdir = basedir.join("v")
tw.sep("-", "cache values")
for valpath in vdir.visit(lambda x: x.isfile()):
key = valpath.relto(vdir).replace(valpath.sep, "/")
val = config.cache.get(key, dummy)
if val is dummy:
tw.line("%s contains unreadable content, "
"will be ignored" % key)
else:
tw.line("%s contains:" % key)
stream = py.io.TextIO()
pprint(val, stream=stream)
for line in stream.getvalue().splitlines():
tw.line(" " + line)
ddir = basedir.join("d")
if ddir.isdir() and ddir.listdir():
tw.sep("-", "cache directories")
for p in basedir.join("d").visit():
#if p.check(dir=1):
# print("%s/" % p.relto(basedir))
if p.isfile():
key = p.relto(basedir)
tw.line("%s is a file of length %d" % (
key, p.size()))
return 0

View File

@@ -29,33 +29,30 @@ def pytest_addoption(parser):
help="shortcut for --capture=no.")
@pytest.mark.tryfirst
def pytest_load_initial_conftests(early_config, parser, args, __multicall__):
@pytest.hookimpl(hookwrapper=True)
def pytest_load_initial_conftests(early_config, parser, args):
_readline_workaround()
ns = early_config.known_args_namespace
pluginmanager = early_config.pluginmanager
capman = CaptureManager(ns.capture)
pluginmanager.register(capman, "capturemanager")
# make sure that capturemanager is properly reset at final shutdown
pluginmanager.add_shutdown(capman.reset_capturings)
early_config.add_cleanup(capman.reset_capturings)
# make sure logging does not raise exceptions at the end
def silence_logging_at_shutdown():
if "logging" in sys.modules:
sys.modules["logging"].raiseExceptions = False
pluginmanager.add_shutdown(silence_logging_at_shutdown)
early_config.add_cleanup(silence_logging_at_shutdown)
# finally trigger conftest loading but while capturing (issue93)
capman.init_capturings()
try:
try:
return __multicall__.execute()
finally:
out, err = capman.suspendcapture()
except:
outcome = yield
out, err = capman.suspendcapture()
if outcome.excinfo is not None:
sys.stdout.write(out)
sys.stderr.write(err)
raise
class CaptureManager:
@@ -90,8 +87,10 @@ class CaptureManager:
self.deactivate_funcargs()
cap = getattr(self, "_capturing", None)
if cap is not None:
outerr = cap.readouterr()
cap.suspend_capturing(in_=in_)
try:
outerr = cap.readouterr()
finally:
cap.suspend_capturing(in_=in_)
return outerr
def activate_funcargs(self, pyfuncitem):
@@ -105,28 +104,27 @@ class CaptureManager:
if capfuncarg is not None:
capfuncarg.close()
@pytest.mark.tryfirst
def pytest_make_collect_report(self, __multicall__, collector):
if not isinstance(collector, pytest.File):
return
self.resumecapture()
try:
rep = __multicall__.execute()
finally:
@pytest.hookimpl(hookwrapper=True)
def pytest_make_collect_report(self, collector):
if isinstance(collector, pytest.File):
self.resumecapture()
outcome = yield
out, err = self.suspendcapture()
if out:
rep.sections.append(("Captured stdout", out))
if err:
rep.sections.append(("Captured stderr", err))
return rep
rep = outcome.get_result()
if out:
rep.sections.append(("Captured stdout", out))
if err:
rep.sections.append(("Captured stderr", err))
else:
yield
@pytest.mark.hookwrapper
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_setup(self, item):
self.resumecapture()
yield
self.suspendcapture_item(item, "setup")
@pytest.mark.hookwrapper
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_call(self, item):
self.resumecapture()
self.activate_funcargs(item)
@@ -134,24 +132,24 @@ class CaptureManager:
#self.deactivate_funcargs() called from suspendcapture()
self.suspendcapture_item(item, "call")
@pytest.mark.hookwrapper
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_teardown(self, item):
self.resumecapture()
yield
self.suspendcapture_item(item, "teardown")
@pytest.mark.tryfirst
@pytest.hookimpl(tryfirst=True)
def pytest_keyboard_interrupt(self, excinfo):
self.reset_capturings()
@pytest.mark.tryfirst
@pytest.hookimpl(tryfirst=True)
def pytest_internalerror(self, excinfo):
self.reset_capturings()
def suspendcapture_item(self, item, when):
out, err = self.suspendcapture()
item.add_report_section(when, "out", out)
item.add_report_section(when, "err", err)
item.add_report_section(when, "stdout", out)
item.add_report_section(when, "stderr", err)
error_capsysfderror = "cannot use capsys and capfd at the same time"
@@ -445,3 +443,30 @@ class DontReadFromInput:
def close(self):
pass
def _readline_workaround():
"""
Ensure readline is imported so that it attaches to the correct stdio
handles on Windows.
Pdb uses readline support where available--when not running from the Python
prompt, the readline module is not imported until running the pdb REPL. If
running py.test with the --pdb option this means the readline module is not
imported until after I/O capture has been started.
This is a problem for pyreadline, which is often used to implement readline
support on Windows, as it does not attach to the correct handles for stdout
and/or stdin if they have been redirected by the FDCapture mechanism. This
workaround ensures that readline is imported before I/O capture is setup so
that it can attach to the actual stdin/out for the console.
See https://github.com/pytest-dev/pytest/pull/1281
"""
if not sys.platform.startswith('win32'):
return
try:
import readline # noqa
except ImportError:
pass

View File

@@ -8,11 +8,17 @@ import warnings
import py
# DON't import pytest here because it causes import cycle troubles
import sys, os
from _pytest import hookspec # the extension point definitions
from _pytest.core import PluginManager
import _pytest._code
import _pytest.hookspec # the extension point definitions
from _pytest._pluggy import PluginManager, HookimplMarker, HookspecMarker
hookimpl = HookimplMarker("pytest")
hookspec = HookspecMarker("pytest")
# pytest startup
#
class ConftestImportFailure(Exception):
def __init__(self, path, excinfo):
Exception.__init__(self, path, excinfo)
@@ -29,16 +35,24 @@ def main(args=None, plugins=None):
initialization.
"""
try:
config = _prepareconfig(args, plugins)
except ConftestImportFailure:
e = sys.exc_info()[1]
tw = py.io.TerminalWriter(sys.stderr)
for line in traceback.format_exception(*e.excinfo):
tw.line(line.rstrip(), red=True)
tw.line("ERROR: could not load %s\n" % (e.path), red=True)
try:
config = _prepareconfig(args, plugins)
except ConftestImportFailure as e:
tw = py.io.TerminalWriter(sys.stderr)
for line in traceback.format_exception(*e.excinfo):
tw.line(line.rstrip(), red=True)
tw.line("ERROR: could not load %s\n" % (e.path), red=True)
return 4
else:
try:
config.pluginmanager.check_pending()
return config.hook.pytest_cmdline_main(config=config)
finally:
config._ensure_unconfigure()
except UsageError as e:
for msg in e.args:
sys.stderr.write("ERROR: %s\n" %(msg,))
return 4
else:
return config.hook.pytest_cmdline_main(config=config)
class cmdline: # compatibility namespace
main = staticmethod(main)
@@ -51,21 +65,36 @@ _preinit = []
default_plugins = (
"mark main terminal runner python pdb unittest capture skipping "
"tmpdir monkeypatch recwarn pastebin helpconfig nose assertion genscript "
"junitxml resultlog doctest").split()
"junitxml resultlog doctest cacheprovider").split()
builtin_plugins = set(default_plugins)
builtin_plugins.add("pytester")
def _preloadplugins():
assert not _preinit
_preinit.append(get_plugin_manager())
_preinit.append(get_config())
def get_plugin_manager():
def get_config():
if _preinit:
return _preinit.pop(0)
# subsequent calls to main will create a fresh instance
pluginmanager = PytestPluginManager()
pluginmanager.config = Config(pluginmanager) # XXX attr needed?
config = Config(pluginmanager)
for spec in default_plugins:
pluginmanager.import_plugin(spec)
return pluginmanager
return config
def get_plugin_manager():
"""
Obtain a new instance of the
:py:class:`_pytest.config.PytestPluginManager`, with default plugins
already loaded.
This function can be used by integration with other tools, like hooking
into pytest to run tests into an IDE.
"""
return get_config().pluginmanager
def _prepareconfig(args=None, plugins=None):
if args is None:
@@ -75,21 +104,43 @@ def _prepareconfig(args=None, plugins=None):
elif not isinstance(args, (tuple, list)):
if not isinstance(args, str):
raise ValueError("not a string or argument list: %r" % (args,))
args = shlex.split(args)
pluginmanager = get_plugin_manager()
args = shlex.split(args, posix=sys.platform != "win32")
config = get_config()
pluginmanager = config.pluginmanager
try:
if plugins:
for plugin in plugins:
pluginmanager.register(plugin)
if isinstance(plugin, py.builtin._basestring):
pluginmanager.consider_pluginarg(plugin)
else:
pluginmanager.register(plugin)
return pluginmanager.hook.pytest_cmdline_parse(
pluginmanager=pluginmanager, args=args)
except Exception:
pluginmanager.ensure_shutdown()
except BaseException:
config._ensure_unconfigure()
raise
class PytestPluginManager(PluginManager):
def __init__(self, hookspecs=[hookspec]):
super(PytestPluginManager, self).__init__(hookspecs=hookspecs)
"""
Overwrites :py:class:`pluggy.PluginManager` to add pytest-specific
functionality:
* loading plugins from the command line, ``PYTEST_PLUGIN`` env variable and
``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()
# state related to local conftest plugins
self._path2confmods = {}
self._conftestpath2mod = {}
self._confcutdir = None
self._noconftest = False
self.add_hookspecs(_pytest.hookspec)
self.register(self)
if os.environ.get('PYTEST_DEBUG'):
err = sys.stderr
@@ -99,20 +150,263 @@ class PytestPluginManager(PluginManager):
except Exception:
pass
self.trace.root.setwriter(err.write)
self.enable_tracing()
def addhooks(self, module_or_class):
"""
.. deprecated:: 2.8
Use :py:meth:`pluggy.PluginManager.add_hookspecs` instead.
"""
warning = dict(code="I2",
fslocation=_pytest._code.getfslineno(sys._getframe(1)),
nodeid=None,
message="use pluginmanager.add_hookspecs instead of "
"deprecated addhooks() method.")
self._warn(warning)
return self.add_hookspecs(module_or_class)
def parse_hookimpl_opts(self, plugin, name):
# pytest hooks are always prefixed with pytest_
# so we avoid accessing possibly non-readable attributes
# (see issue #1073)
if not name.startswith("pytest_"):
return
# ignore some historic special names which can not be hooks anyway
if name == "pytest_plugins" or name.startswith("pytest_funcarg__"):
return
method = getattr(plugin, name)
opts = super(PytestPluginManager, self).parse_hookimpl_opts(plugin, name)
if opts is not None:
for name in ("tryfirst", "trylast", "optionalhook", "hookwrapper"):
opts.setdefault(name, hasattr(method, name))
return opts
def parse_hookspec_opts(self, module_or_class, name):
opts = super(PytestPluginManager, self).parse_hookspec_opts(
module_or_class, name)
if opts is None:
method = getattr(module_or_class, name)
if name.startswith("pytest_"):
opts = {"firstresult": hasattr(method, "firstresult"),
"historic": hasattr(method, "historic")}
return opts
def _verify_hook(self, hook, hookmethod):
super(PytestPluginManager, self)._verify_hook(hook, hookmethod)
if "__multicall__" in hookmethod.argnames:
fslineno = _pytest._code.getfslineno(hookmethod.function)
warning = dict(code="I1",
fslocation=fslineno,
nodeid=None,
message="%r hook uses deprecated __multicall__ "
"argument" % (hook.name))
self._warn(warning)
def register(self, plugin, name=None):
ret = super(PytestPluginManager, self).register(plugin, name)
if ret:
self.hook.pytest_plugin_registered.call_historic(
kwargs=dict(plugin=plugin, manager=self))
return ret
def getplugin(self, name):
# support deprecated naming because plugins (xdist e.g.) use it
return self.get_plugin(name)
def hasplugin(self, name):
"""Return True if the plugin with the given name is registered."""
return bool(self.get_plugin(name))
def pytest_configure(self, config):
# 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.")
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.")
for warning in self._warnings:
config.warn(code="I1", message=warning)
def _warn(self, message):
kwargs = message if isinstance(message, dict) else {
'code': 'I1',
'message': message,
'fslocation': None,
'nodeid': None,
}
self.hook.pytest_logwarning.call_historic(kwargs=kwargs)
#
# internal API for local conftest plugin handling
#
def _set_initial_conftests(self, namespace):
""" load initial conftest files given a preparsed "namespace".
As conftest files may add their own command line options
which have arguments ('--my-opt somepath') we might get some
false positives. All builtin and 3rd party plugins will have
been loaded, however, so common options will not confuse our logic
here.
"""
current = py.path.local()
self._confcutdir = current.join(namespace.confcutdir, abs=True) \
if namespace.confcutdir else None
self._noconftest = namespace.noconftest
testpaths = namespace.file_or_dir
foundanchor = False
for path in testpaths:
path = str(path)
# remove node-id syntax
i = path.find("::")
if i != -1:
path = path[:i]
anchor = current.join(path, abs=1)
if exists(anchor): # we found some file object
self._try_load_conftest(anchor)
foundanchor = True
if not foundanchor:
self._try_load_conftest(current)
def _try_load_conftest(self, anchor):
self._getconftestmodules(anchor)
# let's also consider test* subdirs
if anchor.check(dir=1):
for x in anchor.listdir("test*"):
if x.check(dir=1):
self._getconftestmodules(x)
def _getconftestmodules(self, path):
if self._noconftest:
return []
try:
return self._path2confmods[path]
except KeyError:
if path.isfile():
clist = self._getconftestmodules(path.dirpath())
else:
# XXX these days we may rather want to use config.rootdir
# and allow users to opt into looking into the rootdir parent
# directories instead of requiring to specify confcutdir
clist = []
for parent in path.parts():
if self._confcutdir and self._confcutdir.relto(parent):
continue
conftestpath = parent.join("conftest.py")
if conftestpath.isfile():
mod = self._importconftest(conftestpath)
clist.append(mod)
self._path2confmods[path] = clist
return clist
def _rget_with_confmod(self, name, path):
modules = self._getconftestmodules(path)
for mod in reversed(modules):
try:
return mod, getattr(mod, name)
except AttributeError:
continue
raise KeyError(name)
def _importconftest(self, conftestpath):
try:
return self._conftestpath2mod[conftestpath]
except KeyError:
pkgpath = conftestpath.pypkgpath()
if pkgpath is None:
_ensure_removed_sysmodule(conftestpath.purebasename)
try:
mod = conftestpath.pyimport()
except Exception:
raise ConftestImportFailure(conftestpath, sys.exc_info())
self._conftest_plugins.add(mod)
self._conftestpath2mod[conftestpath] = mod
dirpath = conftestpath.dirpath()
if dirpath in self._path2confmods:
for path, mods in self._path2confmods.items():
if path and path.relto(dirpath) or path == dirpath:
assert mod not in mods
mods.append(mod)
self.trace("loaded conftestmodule %r" %(mod))
self.consider_conftest(mod)
return mod
#
# API for bootstrapping plugin loading
#
#
def consider_preparse(self, args):
for opt1,opt2 in zip(args, args[1:]):
if opt1 == "-p":
self.consider_pluginarg(opt2)
def consider_pluginarg(self, arg):
if arg.startswith("no:"):
name = arg[3:]
self.set_blocked(name)
if not name.startswith("pytest_"):
self.set_blocked("pytest_" + name)
else:
self.import_plugin(arg)
def consider_conftest(self, conftestmodule):
if self.register(conftestmodule, name=conftestmodule.__file__):
self.consider_module(conftestmodule)
def consider_env(self):
self._import_plugin_specs(os.environ.get("PYTEST_PLUGINS"))
def consider_module(self, mod):
self._import_plugin_specs(getattr(mod, "pytest_plugins", None))
def _import_plugin_specs(self, spec):
if spec:
if isinstance(spec, str):
spec = spec.split(",")
for import_spec in spec:
self.import_plugin(import_spec)
def import_plugin(self, modname):
# most often modname refers to builtin modules, e.g. "pytester",
# "terminal" or "capture". Those plugins are registered under their
# basename for historic purposes but must be imported with the
# _pytest prefix.
assert isinstance(modname, str)
if self.get_plugin(modname) is not None:
return
if modname in builtin_plugins:
importspec = "_pytest." + modname
else:
importspec = modname
try:
__import__(importspec)
except ImportError as e:
new_exc = ImportError('Error importing plugin "%s": %s' % (modname, e))
# copy over name and path attributes
for attr in ('name', 'path'):
if hasattr(e, attr):
setattr(new_exc, attr, getattr(e, attr))
raise new_exc
except Exception as e:
import pytest
if not hasattr(pytest, 'skip') or not isinstance(e, pytest.skip.Exception):
raise
self._warn("skipped plugin %r: %s" %((modname, e.msg)))
else:
mod = sys.modules[importspec]
self.register(mod, modname)
self.consider_module(mod)
class Parser:
""" Parser for command line arguments and ini-file values. """
""" Parser for command line arguments and ini-file values.
:ivar extra_info: dict of generic param -> value to display in case
there's an error processing the command line arguments.
"""
def __init__(self, usage=None, processopt=None):
self._anonymous = OptionGroup("custom options", parser=self)
@@ -121,6 +415,7 @@ class Parser:
self._usage = usage
self._inidict = {}
self._ininames = []
self.extra_info = {}
def processoption(self, option):
if self._processopt:
@@ -166,15 +461,15 @@ class Parser:
"""
self._anonymous.addoption(*opts, **attrs)
def parse(self, args):
def parse(self, args, namespace=None):
from _pytest._argcomplete import try_argcomplete
self.optparser = self._getparser()
try_argcomplete(self.optparser)
return self.optparser.parse_args([str(x) for x in args])
return self.optparser.parse_args([str(x) for x in args], namespace=namespace)
def _getparser(self):
from _pytest._argcomplete import filescompleter
optparser = MyOptionParser(self)
optparser = MyOptionParser(self, self.extra_info)
groups = self._groups + [self._anonymous]
for group in groups:
if group.options:
@@ -188,28 +483,38 @@ class Parser:
optparser.add_argument(FILE_OR_DIR, nargs='*').completer=filescompleter
return optparser
def parse_setoption(self, args, option):
parsedoption = self.parse(args)
def parse_setoption(self, args, option, namespace=None):
parsedoption = self.parse(args, namespace=namespace)
for name, value in parsedoption.__dict__.items():
setattr(option, name, value)
return getattr(parsedoption, FILE_OR_DIR)
def parse_known_args(self, args):
def parse_known_args(self, args, namespace=None):
"""parses and returns a namespace object with known arguments at this
point.
"""
return self.parse_known_and_unknown_args(args, namespace=namespace)[0]
def parse_known_and_unknown_args(self, args, namespace=None):
"""parses and returns a namespace object with known arguments, and
the remaining arguments unknown at this point.
"""
optparser = self._getparser()
args = [str(x) for x in args]
return optparser.parse_known_args(args)[0]
return optparser.parse_known_args(args, namespace=namespace)
def addini(self, name, help, type=None, default=None):
""" register an ini-file option.
:name: name of the ini-variable
:type: type of the variable, can be ``pathlist``, ``args`` or ``linelist``.
:type: type of the variable, can be ``pathlist``, ``args``, ``linelist``
or ``bool``.
:default: default value if no ini-file option exists but is queried.
The value of ini-variables can be retrieved via a call to
:py:func:`config.getini(name) <_pytest.config.Config.getini>`.
"""
assert type in (None, "pathlist", "args", "linelist")
assert type in (None, "pathlist", "args", "linelist", "bool")
self._inidict[name] = (help, type, default)
self._ininames.append(name)
@@ -399,10 +704,15 @@ class OptionGroup:
class MyOptionParser(argparse.ArgumentParser):
def __init__(self, parser):
def __init__(self, parser, extra_info=None):
if not extra_info:
extra_info = {}
self._parser = parser
argparse.ArgumentParser.__init__(self, usage=parser._usage,
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
def parse_args(self, args=None, namespace=None):
"""allow splitting of positional arguments"""
@@ -410,11 +720,14 @@ class MyOptionParser(argparse.ArgumentParser):
if argv:
for arg in argv:
if arg and arg[0] == '-':
msg = argparse._('unrecognized arguments: %s')
self.error(msg % ' '.join(argv))
lines = ['unrecognized arguments: %s' % (' '.join(argv))]
for k, v in sorted(self.extra_info.items()):
lines.append(' %s: %s' % (k, v))
self.error('\n'.join(lines))
getattr(args, FILE_OR_DIR).extend(argv)
return args
class DropShorterLongHelpFormatter(argparse.HelpFormatter):
"""shorten help for long options that differ only in extra hyphens
@@ -464,96 +777,6 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter):
return action._formatted_action_invocation
class Conftest(object):
""" the single place for accessing values and interacting
towards conftest modules from pytest objects.
"""
def __init__(self, onimport=None):
self._path2confmods = {}
self._onimport = onimport
self._conftestpath2mod = {}
self._confcutdir = None
def setinitial(self, namespace):
""" load initial conftest files given a preparsed "namespace".
As conftest files may add their own command line options
which have arguments ('--my-opt somepath') we might get some
false positives. All builtin and 3rd party plugins will have
been loaded, however, so common options will not confuse our logic
here.
"""
current = py.path.local()
self._confcutdir = current.join(namespace.confcutdir, abs=True) \
if namespace.confcutdir else None
testpaths = namespace.file_or_dir
foundanchor = False
for path in testpaths:
path = str(path)
# remove node-id syntax
i = path.find("::")
if i != -1:
path = path[:i]
anchor = current.join(path, abs=1)
if exists(anchor): # we found some file object
self._try_load_conftest(anchor)
foundanchor = True
if not foundanchor:
self._try_load_conftest(current)
def _try_load_conftest(self, anchor):
self.getconftestmodules(anchor)
# let's also consider test* subdirs
if anchor.check(dir=1):
for x in anchor.listdir("test*"):
if x.check(dir=1):
self.getconftestmodules(x)
def getconftestmodules(self, path):
try:
return self._path2confmods[path]
except KeyError:
clist = []
for parent in path.parts():
if self._confcutdir and self._confcutdir.relto(parent):
continue
conftestpath = parent.join("conftest.py")
if conftestpath.check(file=1):
mod = self.importconftest(conftestpath)
clist.append(mod)
self._path2confmods[path] = clist
return clist
def rget_with_confmod(self, name, path):
modules = self.getconftestmodules(path)
for mod in reversed(modules):
try:
return mod, getattr(mod, name)
except AttributeError:
continue
raise KeyError(name)
def importconftest(self, conftestpath):
try:
return self._conftestpath2mod[conftestpath]
except KeyError:
pkgpath = conftestpath.pypkgpath()
if pkgpath is None:
_ensure_removed_sysmodule(conftestpath.purebasename)
try:
mod = conftestpath.pyimport()
except Exception:
raise ConftestImportFailure(conftestpath, sys.exc_info())
self._conftestpath2mod[conftestpath] = mod
dirpath = conftestpath.dirpath()
if dirpath in self._path2confmods:
for path, mods in self._path2confmods.items():
if path and path.relto(dirpath) or path == dirpath:
assert mod not in mods
mods.append(mod)
if self._onimport:
self._onimport(mod)
return mod
def _ensure_removed_sysmodule(modname):
try:
@@ -563,10 +786,12 @@ def _ensure_removed_sysmodule(modname):
class CmdOptions(object):
""" holds cmdline options as attributes."""
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
def __init__(self, values=()):
self.__dict__.update(values)
def __repr__(self):
return "<CmdOptions %r>" %(self.__dict__,)
def copy(self):
return CmdOptions(self.__dict__)
class Notset:
def __repr__(self):
@@ -574,6 +799,7 @@ class Notset:
notset = Notset()
FILE_OR_DIR = 'file_or_dir'
class Config(object):
""" access to configuration values, pluginmanager and plugin hooks. """
@@ -589,58 +815,52 @@ class Config(object):
#: a pluginmanager instance
self.pluginmanager = pluginmanager
self.trace = self.pluginmanager.trace.root.get("config")
self._conftest = Conftest(onimport=self._onimportconftest)
self.hook = self.pluginmanager.hook
self._inicache = {}
self._opt2dest = {}
self._cleanup = []
self._warn = self.pluginmanager._warn
self.pluginmanager.register(self, "pytestconfig")
self.pluginmanager.set_register_callback(self._register_plugin)
self._configured = False
def _register_plugin(self, plugin, name):
call_plugin = self.pluginmanager.call_plugin
call_plugin(plugin, "pytest_addhooks",
{'pluginmanager': self.pluginmanager})
self.hook.pytest_plugin_registered(plugin=plugin,
manager=self.pluginmanager)
dic = call_plugin(plugin, "pytest_namespace", {}) or {}
if dic:
def do_setns(dic):
import pytest
setns(pytest, dic)
call_plugin(plugin, "pytest_addoption", {'parser': self._parser})
if self._configured:
call_plugin(plugin, "pytest_configure", {'config': self})
self.hook.pytest_namespace.call_historic(do_setns, {})
self.hook.pytest_addoption.call_historic(kwargs=dict(parser=self._parser))
def do_configure(self):
def add_cleanup(self, func):
""" Add a function to be called when the config object gets out of
use (usually coninciding with pytest_unconfigure)."""
self._cleanup.append(func)
def _do_configure(self):
assert not self._configured
self._configured = True
self.hook.pytest_configure(config=self)
self.hook.pytest_configure.call_historic(kwargs=dict(config=self))
def do_unconfigure(self):
assert self._configured
self._configured = False
self.hook.pytest_unconfigure(config=self)
self.pluginmanager.ensure_shutdown()
def _ensure_unconfigure(self):
if self._configured:
self._configured = False
self.hook.pytest_unconfigure(config=self)
self.hook.pytest_configure._call_history = []
while self._cleanup:
fin = self._cleanup.pop()
fin()
def warn(self, code, message):
def warn(self, code, message, fslocation=None):
""" generate a warning for this test session. """
self.hook.pytest_logwarning(code=code, message=message,
fslocation=None, nodeid=None)
self.hook.pytest_logwarning.call_historic(kwargs=dict(
code=code, message=message,
fslocation=fslocation, nodeid=None))
def get_terminal_writer(self):
return self.pluginmanager.getplugin("terminalreporter")._tw
return self.pluginmanager.get_plugin("terminalreporter")._tw
def pytest_cmdline_parse(self, pluginmanager, args):
assert self == pluginmanager.config, (self, pluginmanager.config)
# REF1 assert self == pluginmanager.config, (self, pluginmanager.config)
self.parse(args)
return self
def pytest_unconfigure(config):
while config._cleanup:
fin = config._cleanup.pop()
fin()
def notify_exception(self, excinfo, option=None):
if option and option.fulltrace:
style = "long"
@@ -657,22 +877,23 @@ class Config(object):
sys.stderr.write("INTERNALERROR> %s\n" %line)
sys.stderr.flush()
def cwd_relative_nodeid(self, nodeid):
# nodeid's are relative to the rootpath, compute relative to cwd
if self.invocation_dir != self.rootdir:
fullpath = self.rootdir.join(nodeid)
nodeid = self.invocation_dir.bestrelpath(fullpath)
return nodeid
@classmethod
def fromdictargs(cls, option_dict, args):
""" constructor useable for subprocesses. """
pluginmanager = get_plugin_manager()
config = pluginmanager.config
config._preparse(args, addopts=False)
config = get_config()
config.option.__dict__.update(option_dict)
config.parse(args, addopts=False)
for x in config.option.plugins:
config.pluginmanager.consider_pluginarg(x)
return config
def _onimportconftest(self, conftestmodule):
self.trace("loaded conftestmodule %r" %(conftestmodule,))
self.pluginmanager.consider_conftest(conftestmodule)
def _processopt(self, opt):
for name in opt._short_opts + opt._long_opts:
self._opt2dest[name] = opt.dest
@@ -681,39 +902,36 @@ class Config(object):
if not hasattr(self.option, opt.dest):
setattr(self.option, opt.dest, opt.default)
def _getmatchingplugins(self, fspath):
allconftests = self._conftest._conftestpath2mod.values()
plugins = [x for x in self.pluginmanager.getplugins()
if x not in allconftests]
plugins += self._conftest.getconftestmodules(fspath)
return plugins
@hookimpl(trylast=True)
def pytest_load_initial_conftests(self, early_config):
self._conftest.setinitial(early_config.known_args_namespace)
pytest_load_initial_conftests.trylast = True
self.pluginmanager._set_initial_conftests(early_config.known_args_namespace)
def _initini(self, args):
parsed_args = self._parser.parse_known_args(args)
if parsed_args.inifilename:
iniconfig = py.iniconfig.IniConfig(parsed_args.inifilename)
if 'pytest' in iniconfig.sections:
self.inicfg = iniconfig['pytest']
else:
self.inicfg = {}
else:
self.inicfg = getcfg(args, ["pytest.ini", "tox.ini", "setup.cfg"])
ns, unknown_args = self._parser.parse_known_and_unknown_args(args, namespace=self.option.copy())
r = determine_setup(ns.inifilename, ns.file_or_dir + unknown_args)
self.rootdir, self.inifile, self.inicfg = r
self._parser.extra_info['rootdir'] = self.rootdir
self._parser.extra_info['inifile'] = self.inifile
self.invocation_dir = py.path.local()
self._parser.addini('addopts', 'extra command line options', 'args')
self._parser.addini('minversion', 'minimally required pytest version')
def _preparse(self, args, addopts=True):
self._initini(args)
if addopts:
args[:] = shlex.split(os.environ.get('PYTEST_ADDOPTS', '')) + args
args[:] = self.getini("addopts") + args
self._checkversion()
self.pluginmanager.consider_preparse(args)
self.pluginmanager.consider_setuptools_entrypoints()
try:
self.pluginmanager.load_setuptools_entrypoints("pytest11")
except ImportError as e:
self.warn("I2", "could not load setuptools entry import: %s" % (e,))
self.pluginmanager.consider_env()
self.known_args_namespace = ns = self._parser.parse_known_args(args)
self.known_args_namespace = ns = self._parser.parse_known_args(args, namespace=self.option.copy())
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)
@@ -722,8 +940,7 @@ class Config(object):
if ns.help or ns.version:
# we don't want to prevent --help/--version to work
# so just let is pass and print a warning at the end
self.pluginmanager._warnings.append(
"could not load initial conftests (%s)\n" % e.path)
self._warn("could not load initial conftests (%s)\n" % e.path)
else:
raise
@@ -739,17 +956,23 @@ class Config(object):
self.inicfg.config.path, self.inicfg.lineof('minversion'),
minver, pytest.__version__))
def parse(self, args):
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")
self._origargs = args
self._preparse(args)
self.hook.pytest_addhooks.call_historic(
kwargs=dict(pluginmanager=self.pluginmanager))
self._preparse(args, addopts=addopts)
# XXX deprecated hook:
self.hook.pytest_cmdline_preparse(config=self, args=args)
args = self._parser.parse_setoption(args, self.option)
args = self._parser.parse_setoption(args, self.option, namespace=self.option)
if not args:
args.append(os.getcwd())
cwd = os.getcwd()
if cwd == self.rootdir:
args = self.getini('testpaths')
if not args:
args = [cwd]
self.args = args
def addinivalue_line(self, name, line):
@@ -794,13 +1017,15 @@ class Config(object):
return shlex.split(value)
elif type == "linelist":
return [t for t in map(lambda x: x.strip(), value.split("\n")) if t]
elif type == "bool":
return bool(_strtobool(value.strip()))
else:
assert type is None
return value
def _getconftest_pathlist(self, name, path):
try:
mod, relroots = self._conftest.rget_with_confmod(name, path)
mod, relroots = self.pluginmanager._rget_with_confmod(name, path)
except KeyError:
return None
modpath = py.path.local(mod.__file__).dirpath()
@@ -861,8 +1086,58 @@ def getcfg(args, inibasenames):
if exists(p):
iniconfig = py.iniconfig.IniConfig(p)
if 'pytest' in iniconfig.sections:
return iniconfig['pytest']
return {}
return base, p, iniconfig['pytest']
elif inibasename == "pytest.ini":
# allowed to be empty
return base, p, {}
return None, None, None
def get_common_ancestor(args):
# args are what we get after early command line parsing (usually
# strings, but can be py.path.local objects as well)
common_ancestor = None
for arg in args:
if str(arg)[0] == "-":
continue
p = py.path.local(arg)
if common_ancestor is None:
common_ancestor = p
else:
if p.relto(common_ancestor) or p == common_ancestor:
continue
elif common_ancestor.relto(p):
common_ancestor = p
else:
shared = p.common(common_ancestor)
if shared is not None:
common_ancestor = shared
if common_ancestor is None:
common_ancestor = py.path.local()
elif not common_ancestor.isdir():
common_ancestor = common_ancestor.dirpath()
return common_ancestor
def determine_setup(inifile, args):
if inifile:
iniconfig = py.iniconfig.IniConfig(inifile)
try:
inicfg = iniconfig["pytest"]
except KeyError:
inicfg = None
rootdir = get_common_ancestor(args)
else:
ancestor = get_common_ancestor(args)
rootdir, inifile, inicfg = getcfg(
[ancestor], ["pytest.ini", "tox.ini", "setup.cfg"])
if rootdir is None:
for rootdir in ancestor.parts(reverse=True):
if rootdir.join("setup.py").exists():
break
else:
rootdir = ancestor
return rootdir, inifile, inicfg or {}
def setns(obj, dic):
@@ -884,3 +1159,34 @@ def setns(obj, dic):
#if obj != pytest:
# pytest.__all__.append(name)
setattr(pytest, name, value)
def create_terminal_writer(config, *args, **kwargs):
"""Create a TerminalWriter instance configured according to the options
in the config object. Every code which requires a TerminalWriter object
and has access to a config object should use this function.
"""
tw = py.io.TerminalWriter(*args, **kwargs)
if config.option.color == 'yes':
tw.hasmarkup = True
if config.option.color == 'no':
tw.hasmarkup = False
return tw
def _strtobool(val):
"""Convert a string representation of truth to true (1) or false (0).
True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values
are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if
'val' is anything else.
.. note:: copied from distutils.util
"""
val = val.lower()
if val in ('y', 'yes', 't', 'true', 'on', '1'):
return 1
elif val in ('n', 'no', 'f', 'false', 'off', '0'):
return 0
else:
raise ValueError("invalid truth value %r" % (val,))

View File

@@ -1,430 +0,0 @@
"""
pytest PluginManager, basic initialization and tracing.
"""
import os
import sys
import inspect
import py
# don't import pytest to avoid circular imports
assert py.__version__.split(".")[:2] >= ['1', '4'], ("installation problem: "
"%s is too old, remove or upgrade 'py'" % (py.__version__))
class TagTracer:
def __init__(self):
self._tag2proc = {}
self.writer = None
self.indent = 0
def get(self, name):
return TagTracerSub(self, (name,))
def format_message(self, tags, args):
if isinstance(args[-1], dict):
extra = args[-1]
args = args[:-1]
else:
extra = {}
content = " ".join(map(str, args))
indent = " " * self.indent
lines = [
"%s%s [%s]\n" %(indent, content, ":".join(tags))
]
for name, value in extra.items():
lines.append("%s %s: %s\n" % (indent, name, value))
return lines
def processmessage(self, tags, args):
if self.writer is not None and args:
lines = self.format_message(tags, args)
self.writer(''.join(lines))
try:
self._tag2proc[tags](tags, args)
except KeyError:
pass
def setwriter(self, writer):
self.writer = writer
def setprocessor(self, tags, processor):
if isinstance(tags, str):
tags = tuple(tags.split(":"))
else:
assert isinstance(tags, tuple)
self._tag2proc[tags] = processor
class TagTracerSub:
def __init__(self, root, tags):
self.root = root
self.tags = tags
def __call__(self, *args):
self.root.processmessage(self.tags, args)
def setmyprocessor(self, processor):
self.root.setprocessor(self.tags, processor)
def get(self, name):
return self.__class__(self.root, self.tags + (name,))
class PluginManager(object):
def __init__(self, hookspecs=None):
self._name2plugin = {}
self._listattrcache = {}
self._plugins = []
self._warnings = []
self.trace = TagTracer().get("pluginmanage")
self._plugin_distinfo = []
self._shutdown = []
self.hook = HookRelay(hookspecs or [], pm=self)
def do_configure(self, config):
# backward compatibility
config.do_configure()
def set_register_callback(self, callback):
assert not hasattr(self, "_registercallback")
self._registercallback = callback
def register(self, plugin, name=None, prepend=False):
if self._name2plugin.get(name, None) == -1:
return
name = name or getattr(plugin, '__name__', str(id(plugin)))
if self.isregistered(plugin, name):
raise ValueError("Plugin already registered: %s=%s\n%s" %(
name, plugin, self._name2plugin))
#self.trace("registering", name, plugin)
self._name2plugin[name] = plugin
reg = getattr(self, "_registercallback", None)
if reg is not None:
reg(plugin, name)
if not prepend:
self._plugins.append(plugin)
else:
self._plugins.insert(0, plugin)
return True
def unregister(self, plugin=None, name=None):
if plugin is None:
plugin = self.getplugin(name=name)
self._plugins.remove(plugin)
for name, value in list(self._name2plugin.items()):
if value == plugin:
del self._name2plugin[name]
def add_shutdown(self, func):
self._shutdown.append(func)
def ensure_shutdown(self):
while self._shutdown:
func = self._shutdown.pop()
func()
self._plugins = []
self._name2plugin.clear()
self._listattrcache.clear()
def isregistered(self, plugin, name=None):
if self.getplugin(name) is not None:
return True
for val in self._name2plugin.values():
if plugin == val:
return True
def addhooks(self, spec, prefix="pytest_"):
self.hook._addhooks(spec, prefix=prefix)
def getplugins(self):
return list(self._plugins)
def skipifmissing(self, name):
if not self.hasplugin(name):
import pytest
pytest.skip("plugin %r is missing" % name)
def hasplugin(self, name):
return bool(self.getplugin(name))
def getplugin(self, name):
if name is None:
return None
try:
return self._name2plugin[name]
except KeyError:
return self._name2plugin.get("_pytest." + name, None)
# API for bootstrapping
#
def _envlist(self, varname):
val = os.environ.get(varname, None)
if val is not None:
return val.split(',')
return ()
def consider_env(self):
for spec in self._envlist("PYTEST_PLUGINS"):
self.import_plugin(spec)
def consider_setuptools_entrypoints(self):
try:
from pkg_resources import iter_entry_points, DistributionNotFound
except ImportError:
return # XXX issue a warning
for ep in iter_entry_points('pytest11'):
name = ep.name
if name.startswith("pytest_"):
name = name[7:]
if ep.name in self._name2plugin or name in self._name2plugin:
continue
try:
plugin = ep.load()
except DistributionNotFound:
continue
self._plugin_distinfo.append((ep.dist, plugin))
self.register(plugin, name=name)
def consider_preparse(self, args):
for opt1,opt2 in zip(args, args[1:]):
if opt1 == "-p":
self.consider_pluginarg(opt2)
def consider_pluginarg(self, arg):
if arg.startswith("no:"):
name = arg[3:]
if self.getplugin(name) is not None:
self.unregister(None, name=name)
self._name2plugin[name] = -1
else:
if self.getplugin(arg) is None:
self.import_plugin(arg)
def consider_conftest(self, conftestmodule):
if self.register(conftestmodule, name=conftestmodule.__file__):
self.consider_module(conftestmodule)
def consider_module(self, mod):
attr = getattr(mod, "pytest_plugins", ())
if attr:
if not isinstance(attr, (list, tuple)):
attr = (attr,)
for spec in attr:
self.import_plugin(spec)
def import_plugin(self, modname):
assert isinstance(modname, str)
if self.getplugin(modname) is not None:
return
try:
mod = importplugin(modname)
except KeyboardInterrupt:
raise
except ImportError:
if modname.startswith("pytest_"):
return self.import_plugin(modname[7:])
raise
except:
e = sys.exc_info()[1]
import pytest
if not hasattr(pytest, 'skip') or not isinstance(e, pytest.skip.Exception):
raise
self._warnings.append("skipped plugin %r: %s" %((modname, e.msg)))
else:
self.register(mod, modname)
self.consider_module(mod)
def listattr(self, attrname, plugins=None):
if plugins is None:
plugins = self._plugins
key = (attrname,) + tuple(plugins)
try:
return list(self._listattrcache[key])
except KeyError:
pass
l = []
last = []
wrappers = []
for plugin in plugins:
try:
meth = getattr(plugin, attrname)
except AttributeError:
continue
if hasattr(meth, 'hookwrapper'):
wrappers.append(meth)
elif hasattr(meth, 'tryfirst'):
last.append(meth)
elif hasattr(meth, 'trylast'):
l.insert(0, meth)
else:
l.append(meth)
l.extend(last)
l.extend(wrappers)
self._listattrcache[key] = list(l)
return l
def call_plugin(self, plugin, methname, kwargs):
return MultiCall(methods=self.listattr(methname, plugins=[plugin]),
kwargs=kwargs, firstresult=True).execute()
def importplugin(importspec):
name = importspec
try:
mod = "_pytest." + name
__import__(mod)
return sys.modules[mod]
except ImportError:
__import__(importspec)
return sys.modules[importspec]
class MultiCall:
""" execute a call into multiple python functions/methods. """
class WrongHookWrapper(Exception):
""" a hook wrapper does not behave correctly. """
def __init__(self, func, message):
Exception.__init__(self, func, message)
self.func = func
self.message = message
def __init__(self, methods, kwargs, firstresult=False):
self.methods = list(methods)
self.kwargs = kwargs
self.results = []
self.firstresult = firstresult
def __repr__(self):
status = "%d results, %d meths" % (len(self.results), len(self.methods))
return "<MultiCall %s, kwargs=%r>" %(status, self.kwargs)
def execute(self):
next_finalizers = []
try:
while self.methods:
method = self.methods.pop()
kwargs = self.getkwargs(method)
if hasattr(method, "hookwrapper"):
it = method(**kwargs)
next = getattr(it, "next", None)
if next is None:
next = getattr(it, "__next__", None)
if next is None:
raise self.WrongHookWrapper(method,
"wrapper does not contain a yield")
res = next()
next_finalizers.append((method, next))
else:
res = method(**kwargs)
if res is not None:
self.results.append(res)
if self.firstresult:
return res
if not self.firstresult:
return self.results
finally:
for method, fin in reversed(next_finalizers):
try:
fin()
except StopIteration:
pass
else:
raise self.WrongHookWrapper(method,
"wrapper contain more than one yield")
def getkwargs(self, method):
kwargs = {}
for argname in varnames(method):
try:
kwargs[argname] = self.kwargs[argname]
except KeyError:
if argname == "__multicall__":
kwargs[argname] = self
return kwargs
def varnames(func):
""" return argument name tuple for a function, method, class or callable.
In case of a class, its "__init__" method is considered.
For methods the "self" parameter is not included unless you are passing
an unbound method with Python3 (which has no supports for unbound methods)
"""
cache = getattr(func, "__dict__", {})
try:
return cache["_varnames"]
except KeyError:
pass
if inspect.isclass(func):
try:
func = func.__init__
except AttributeError:
return ()
ismethod = True
else:
if not inspect.isfunction(func) and not inspect.ismethod(func):
func = getattr(func, '__call__', func)
ismethod = inspect.ismethod(func)
rawcode = py.code.getrawcode(func)
try:
x = rawcode.co_varnames[ismethod:rawcode.co_argcount]
except AttributeError:
x = ()
try:
cache["_varnames"] = x
except TypeError:
pass
return x
class HookRelay:
def __init__(self, hookspecs, pm, prefix="pytest_"):
if not isinstance(hookspecs, list):
hookspecs = [hookspecs]
self._hookspecs = []
self._pm = pm
self.trace = pm.trace.root.get("hook")
for hookspec in hookspecs:
self._addhooks(hookspec, prefix)
def _addhooks(self, hookspecs, prefix):
self._hookspecs.append(hookspecs)
added = False
for name, method in vars(hookspecs).items():
if name.startswith(prefix):
firstresult = getattr(method, 'firstresult', False)
hc = HookCaller(self, name, firstresult=firstresult)
setattr(self, name, hc)
added = True
#print ("setting new hook", name)
if not added:
raise ValueError("did not find new %r hooks in %r" %(
prefix, hookspecs,))
class HookCaller:
def __init__(self, hookrelay, name, firstresult):
self.hookrelay = hookrelay
self.name = name
self.firstresult = firstresult
self.trace = self.hookrelay.trace
def __repr__(self):
return "<HookCaller %r>" %(self.name,)
def __call__(self, **kwargs):
methods = self.hookrelay._pm.listattr(self.name)
return self._docall(methods, kwargs)
def pcall(self, plugins, **kwargs):
methods = self.hookrelay._pm.listattr(self.name, plugins=plugins)
return self._docall(methods, kwargs)
def _docall(self, methods, kwargs):
self.trace(self.name, kwargs)
self.trace.root.indent += 1
mc = MultiCall(methods, kwargs, firstresult=self.firstresult)
try:
res = mc.execute()
if res:
self.trace("finish", self.name, "-->", res)
finally:
self.trace.root.indent -= 1
return res

View File

@@ -1,46 +1,80 @@
""" discover and run doctests in modules and test files."""
from __future__ import absolute_import
import traceback
import pytest, py
from _pytest.python import FixtureRequest, FuncFixtureInfo
from py._code.code import TerminalRepr, ReprFileLocation
import pytest
from _pytest._code.code import TerminalRepr, ReprFileLocation, ExceptionInfo
from _pytest.python import FixtureRequest
def pytest_addoption(parser):
parser.addini('doctest_optionflags', 'option flags for doctests',
type="args", default=["ELLIPSIS"])
group = parser.getgroup("collect")
group.addoption("--doctest-modules",
action="store_true", default=False,
help="run doctests in all .py modules",
dest="doctestmodules")
group.addoption("--doctest-glob",
action="store", default="test*.txt", metavar="pat",
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")
def pytest_collect_file(path, parent):
config = parent.config
if path.ext == ".py":
if config.option.doctestmodules:
return DoctestModule(path, parent)
elif (path.ext in ('.txt', '.rst') and parent.session.isinitpath(path)) or \
path.check(fnmatch=config.getvalue("doctestglob")):
elif _is_doctest(config, path, parent):
return DoctestTextfile(path, parent)
def _is_doctest(config, path, parent):
if path.ext in ('.txt', '.rst') and parent.session.isinitpath(path):
return True
globs = config.getoption("doctestglob") or ['test*.txt']
for glob in globs:
if path.check(fnmatch=glob):
return True
return False
class ReprFailDoctest(TerminalRepr):
def __init__(self, reprlocation, lines):
self.reprlocation = reprlocation
self.lines = lines
def toterminal(self, tw):
for line in self.lines:
tw.line(line)
self.reprlocation.toterminal(tw)
class DoctestItem(pytest.Item):
def __init__(self, name, parent, runner=None, dtest=None):
super(DoctestItem, self).__init__(name, parent)
self.runner = runner
self.dtest = dtest
self.obj = None
self.fixture_request = None
def setup(self):
if self.dtest is not None:
self.fixture_request = _setup_fixtures(self)
globs = dict(getfixture=self.fixture_request.getfuncargvalue)
self.dtest.globs.update(globs)
def runtest(self):
_check_all_skipped(self.dtest)
self.runner.run(self.dtest)
def repr_failure(self, excinfo):
@@ -57,17 +91,17 @@ class DoctestItem(pytest.Item):
lineno = test.lineno + example.lineno + 1
message = excinfo.type.__name__
reprlocation = ReprFileLocation(filename, lineno, message)
checker = doctest.OutputChecker()
checker = _get_checker()
REPORT_UDIFF = doctest.REPORT_UDIFF
filelines = py.path.local(filename).readlines(cr=0)
lines = []
if lineno is not None:
i = max(test.lineno, max(0, lineno - 10)) # XXX?
for line in filelines[i:lineno]:
lines.append("%03d %s" % (i+1, line))
i += 1
lines = doctestfailure.test.docstring.splitlines(False)
# add line numbers to the left of the error message
lines = ["%03d %s" % (i + test.lineno + 1, x)
for (i, x) in enumerate(lines)]
# trim docstring error lines to 10
lines = lines[example.lineno - 9:example.lineno + 1]
else:
lines.append('EXAMPLE LOCATION UNKNOWN, not showing all tests of that example')
lines = ['EXAMPLE LOCATION UNKNOWN, not showing all tests of that example']
indent = '>>>'
for line in example.source.splitlines():
lines.append('??? %s %s' % (indent, line))
@@ -76,7 +110,7 @@ class DoctestItem(pytest.Item):
lines += checker.output_difference(example,
doctestfailure.got, REPORT_UDIFF).split("\n")
else:
inner_excinfo = py.code.ExceptionInfo(excinfo.value.exc_info)
inner_excinfo = ExceptionInfo(excinfo.value.exc_info)
lines += ["UNEXPECTED EXCEPTION: %s" %
repr(inner_excinfo.value)]
lines += traceback.format_exception(*excinfo.value.exc_info)
@@ -87,40 +121,170 @@ class DoctestItem(pytest.Item):
def reportinfo(self):
return self.fspath, None, "[doctest] %s" % self.name
class DoctestTextfile(DoctestItem, pytest.File):
def _get_flag_lookup():
import doctest
return dict(DONT_ACCEPT_TRUE_FOR_1=doctest.DONT_ACCEPT_TRUE_FOR_1,
DONT_ACCEPT_BLANKLINE=doctest.DONT_ACCEPT_BLANKLINE,
NORMALIZE_WHITESPACE=doctest.NORMALIZE_WHITESPACE,
ELLIPSIS=doctest.ELLIPSIS,
IGNORE_EXCEPTION_DETAIL=doctest.IGNORE_EXCEPTION_DETAIL,
COMPARISON_FLAGS=doctest.COMPARISON_FLAGS,
ALLOW_UNICODE=_get_allow_unicode_flag(),
ALLOW_BYTES=_get_allow_bytes_flag(),
)
def get_optionflags(parent):
optionflags_str = parent.config.getini("doctest_optionflags")
flag_lookup_table = _get_flag_lookup()
flag_acc = 0
for flag in optionflags_str:
flag_acc |= flag_lookup_table[flag]
return flag_acc
class DoctestTextfile(DoctestItem, pytest.Module):
def runtest(self):
import doctest
# satisfy `FixtureRequest` constructor...
self.funcargs = {}
fm = self.session._fixturemanager
def func():
pass
self._fixtureinfo = fm.getfixtureinfo(node=self, func=func,
cls=None, funcargs=False)
fixture_request = FixtureRequest(self)
fixture_request._fillfixtures()
failed, tot = doctest.testfile(
str(self.fspath), module_relative=False,
optionflags=doctest.ELLIPSIS,
extraglobs=dict(getfixture=fixture_request.getfuncargvalue),
raise_on_error=True, verbose=0)
fixture_request = _setup_fixtures(self)
class DoctestModule(pytest.File):
# inspired by doctest.testfile; ideally we would use it directly,
# but it doesn't support passing a custom checker
text = self.fspath.read()
filename = str(self.fspath)
name = self.fspath.basename
globs = dict(getfixture=fixture_request.getfuncargvalue)
if '__name__' not in globs:
globs['__name__'] = '__main__'
optionflags = get_optionflags(self)
runner = doctest.DebugRunner(verbose=0, optionflags=optionflags,
checker=_get_checker())
parser = doctest.DocTestParser()
test = parser.get_doctest(text, globs, name, filename, 0)
_check_all_skipped(test)
runner.run(test)
def _check_all_skipped(test):
"""raises pytest.skip() if all examples in the given DocTest have the SKIP
option set.
"""
import doctest
all_skipped = all(x.options.get(doctest.SKIP, False) for x in test.examples)
if all_skipped:
pytest.skip('all tests skipped by +SKIP option')
class DoctestModule(pytest.Module):
def collect(self):
import doctest
if self.fspath.basename == "conftest.py":
module = self.config._conftest.importconftest(self.fspath)
module = self.config.pluginmanager._importconftest(self.fspath)
else:
module = self.fspath.pyimport()
# satisfy `FixtureRequest` constructor...
self.funcargs = {}
self._fixtureinfo = FuncFixtureInfo((), [], {})
fixture_request = FixtureRequest(self)
doctest_globals = dict(getfixture=fixture_request.getfuncargvalue)
try:
module = self.fspath.pyimport()
except ImportError:
if self.config.getvalue('doctest_ignore_import_errors'):
pytest.skip('unable to import module %r' % self.fspath)
else:
raise
# uses internal doctest module parsing mechanism
finder = doctest.DocTestFinder()
runner = doctest.DebugRunner(verbose=0, optionflags=doctest.ELLIPSIS)
for test in finder.find(module, module.__name__,
extraglobs=doctest_globals):
if test.examples: # skip empty doctests
optionflags = get_optionflags(self)
runner = doctest.DebugRunner(verbose=0, optionflags=optionflags,
checker=_get_checker())
for test in finder.find(module, module.__name__):
if test.examples: # skip empty doctests
yield DoctestItem(test.name, self, runner, test)
def _setup_fixtures(doctest_item):
"""
Used by DoctestTextfile and DoctestItem to setup fixture information.
"""
def func():
pass
doctest_item.funcargs = {}
fm = doctest_item.session._fixturemanager
doctest_item._fixtureinfo = fm.getfixtureinfo(node=doctest_item, func=func,
cls=None, funcargs=False)
fixture_request = FixtureRequest(doctest_item)
fixture_request._fillfixtures()
return fixture_request
def _get_checker():
"""
Returns a doctest.OutputChecker subclass that takes in account the
ALLOW_UNICODE option to ignore u'' prefixes in strings and ALLOW_BYTES
to strip b'' prefixes.
Useful when the same doctest should run in Python 2 and Python 3.
An inner class is used to avoid importing "doctest" at the module
level.
"""
if hasattr(_get_checker, 'LiteralsOutputChecker'):
return _get_checker.LiteralsOutputChecker()
import doctest
import re
class LiteralsOutputChecker(doctest.OutputChecker):
"""
Copied from doctest_nose_plugin.py from the nltk project:
https://github.com/nltk/nltk
Further extended to also support byte literals.
"""
_unicode_literal_re = re.compile(r"(\W|^)[uU]([rR]?[\'\"])", re.UNICODE)
_bytes_literal_re = re.compile(r"(\W|^)[bB]([rR]?[\'\"])", re.UNICODE)
def check_output(self, want, got, optionflags):
res = doctest.OutputChecker.check_output(self, want, got,
optionflags)
if res:
return True
allow_unicode = optionflags & _get_allow_unicode_flag()
allow_bytes = optionflags & _get_allow_bytes_flag()
if not allow_unicode and not allow_bytes:
return False
else: # pragma: no cover
def remove_prefixes(regex, txt):
return re.sub(regex, r'\1\2', txt)
if allow_unicode:
want = remove_prefixes(self._unicode_literal_re, want)
got = remove_prefixes(self._unicode_literal_re, got)
if allow_bytes:
want = remove_prefixes(self._bytes_literal_re, want)
got = remove_prefixes(self._bytes_literal_re, got)
res = doctest.OutputChecker.check_output(self, want, got,
optionflags)
return res
_get_checker.LiteralsOutputChecker = LiteralsOutputChecker
return _get_checker.LiteralsOutputChecker()
def _get_allow_unicode_flag():
"""
Registers and returns the ALLOW_UNICODE flag.
"""
import doctest
return doctest.register_optionflag('ALLOW_UNICODE')
def _get_allow_bytes_flag():
"""
Registers and returns the ALLOW_BYTES flag.
"""
import doctest
return doctest.register_optionflag('ALLOW_BYTES')

View File

@@ -1,10 +1,9 @@
""" generate a single-file self-contained version of pytest """
""" (deprecated) generate a single-file self-contained version of pytest """
import os
import sys
import pkgutil
import py
import _pytest
@@ -33,6 +32,9 @@ def pkg_to_mapping(name):
for pyfile in toplevel.visit('*.py'):
pkg = pkgname(name, toplevel, pyfile)
name2src[pkg] = pyfile.read()
# with wheels py source code might be not be installed
# and the resulting genscript is useless, just bail out.
assert name2src, "no source code found for %r at %r" %(name, toplevel)
return name2src
def compress_mapping(mapping):
@@ -66,10 +68,13 @@ def pytest_addoption(parser):
help="create standalone pytest script at given target path.")
def pytest_cmdline_main(config):
import _pytest.config
genscript = config.getvalue("genscript")
if genscript:
tw = py.io.TerminalWriter()
deps = ['py', '_pytest', 'pytest']
tw = _pytest.config.create_terminal_writer(config)
tw.line("WARNING: usage of genscript is deprecated.",
red=True)
deps = ['py', '_pytest', 'pytest'] # pluggy is vendored
if sys.version_info < (2,7):
deps.append("argparse")
tw.line("generated script will run on python2.6-python3.3++")

View File

@@ -1,8 +1,7 @@
""" version info, help messages, tracing configuration. """
import py
import pytest
import os, inspect, sys
from _pytest.core import varnames
import os, sys
def pytest_addoption(parser):
group = parser.getgroup('debugconfig')
@@ -23,27 +22,28 @@ def pytest_addoption(parser):
help="store internal tracing debug information in 'pytestdebug.log'.")
def pytest_cmdline_parse(__multicall__):
config = __multicall__.execute()
@pytest.hookimpl(hookwrapper=True)
def pytest_cmdline_parse():
outcome = yield
config = outcome.get_result()
if config.option.debug:
path = os.path.abspath("pytestdebug.log")
f = open(path, 'w')
config._debugfile = f
f.write("versions pytest-%s, py-%s, python-%s\ncwd=%s\nargs=%s\n\n" %(
pytest.__version__, py.__version__, ".".join(map(str, sys.version_info)),
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))
config.trace.root.setwriter(f.write)
config.trace.root.setwriter(debugfile.write)
undo_tracing = config.pluginmanager.enable_tracing()
sys.stderr.write("writing pytestdebug information to %s\n" % path)
return config
@pytest.mark.trylast
def pytest_unconfigure(config):
if hasattr(config, '_debugfile'):
config._debugfile.close()
sys.stderr.write("wrote pytestdebug information to %s\n" %
config._debugfile.name)
config.trace.root.setwriter(None)
def unset_tracing():
debugfile.close()
sys.stderr.write("wrote pytestdebug information to %s\n" %
debugfile.name)
config.trace.root.setwriter(None)
undo_tracing()
config.add_cleanup(unset_tracing)
def pytest_cmdline_main(config):
if config.option.version:
@@ -56,13 +56,14 @@ def pytest_cmdline_main(config):
sys.stderr.write(line + "\n")
return 0
elif config.option.help:
config.do_configure()
config._do_configure()
showhelp(config)
config.do_unconfigure()
config._ensure_unconfigure()
return 0
def showhelp(config):
tw = py.io.TerminalWriter()
reporter = config.pluginmanager.get_plugin('terminalreporter')
tw = reporter._tw
tw.write(config._parser.optparser.format_help())
tw.line()
tw.line()
@@ -79,14 +80,25 @@ def showhelp(config):
line = " %-24s %s" %(spec, help)
tw.line(line[:tw.fullwidth])
tw.line() ; tw.line()
#tw.sep("=")
tw.line()
tw.line("environment variables:")
vars = [
("PYTEST_ADDOPTS", "extra command line options"),
("PYTEST_PLUGINS", "comma-separated plugins to load during startup"),
("PYTEST_DEBUG", "set to enable debug tracing of pytest's internals")
]
for name, help in vars:
tw.line(" %-24s %s" % (name, help))
tw.line()
tw.line()
tw.line("to see available markers type: py.test --markers")
tw.line("to see available fixtures type: py.test --fixtures")
tw.line("(shown according to specified file_or_dir or current dir "
"if not specified)")
for warning in config.pluginmanager._warnings:
tw.line("warning: %s" % (warning,), red=True)
for warningreport in reporter.stats.get('warnings', []):
tw.line("warning : " + warningreport.message, red=True)
return
@@ -96,10 +108,10 @@ conftest_options = [
def getpluginversioninfo(config):
lines = []
plugininfo = config.pluginmanager._plugin_distinfo
plugininfo = config.pluginmanager.list_plugin_distinfo()
if plugininfo:
lines.append("setuptools registered plugins:")
for dist, plugin in plugininfo:
for plugin, dist in plugininfo:
loc = getattr(plugin, '__file__', repr(plugin))
content = "%s-%s at %s" % (dist.project_name, dist.version, loc)
lines.append(" " + content)
@@ -117,7 +129,7 @@ def pytest_report_header(config):
if config.option.traceconfig:
lines.append("active plugins:")
items = config.pluginmanager._name2plugin.items()
items = config.pluginmanager.list_name_plugin()
for name, plugin in items:
if hasattr(plugin, '__file__'):
r = plugin.__file__
@@ -125,72 +137,3 @@ def pytest_report_header(config):
r = repr(plugin)
lines.append(" %-20s: %s" %(name, r))
return lines
# =====================================================
# validate plugin syntax and hooks
# =====================================================
def pytest_plugin_registered(manager, plugin):
methods = collectattr(plugin)
hooks = {}
for hookspec in manager.hook._hookspecs:
hooks.update(collectattr(hookspec))
stringio = py.io.TextIO()
def Print(*args):
if args:
stringio.write(" ".join(map(str, args)))
stringio.write("\n")
fail = False
while methods:
name, method = methods.popitem()
#print "checking", name
if isgenerichook(name):
continue
if name not in hooks:
if not getattr(method, 'optionalhook', False):
Print("found unknown hook:", name)
fail = True
else:
#print "checking", method
method_args = list(varnames(method))
if '__multicall__' in method_args:
method_args.remove('__multicall__')
hook = hooks[name]
hookargs = varnames(hook)
for arg in method_args:
if arg not in hookargs:
Print("argument %r not available" %(arg, ))
Print("actual definition: %s" %(formatdef(method)))
Print("available hook arguments: %s" %
", ".join(hookargs))
fail = True
break
#if not fail:
# print "matching hook:", formatdef(method)
if fail:
name = getattr(plugin, '__name__', plugin)
raise PluginValidationError("%s:\n%s" % (name, stringio.getvalue()))
class PluginValidationError(Exception):
""" plugin failed validation. """
def isgenerichook(name):
return name == "pytest_plugins" or \
name.startswith("pytest_funcarg__")
def collectattr(obj):
methods = {}
for apiname in dir(obj):
if apiname.startswith("pytest_"):
methods[apiname] = getattr(obj, apiname)
return methods
def formatdef(func):
return "%s%s" % (
func.__name__,
inspect.formatargspec(*inspect.getargspec(func))
)

View File

@@ -1,33 +1,42 @@
""" hook specifications for pytest plugins, invoked from main.py and builtin plugins. """
from _pytest._pluggy import HookspecMarker
hookspec = HookspecMarker("pytest")
# -------------------------------------------------------------------------
# Initialization
# Initialization hooks called for every plugin
# -------------------------------------------------------------------------
@hookspec(historic=True)
def pytest_addhooks(pluginmanager):
"""called at plugin load time to allow adding new hooks via a call to
pluginmanager.registerhooks(module)."""
"""called at plugin registration time to allow adding new hooks via a call to
pluginmanager.add_hookspecs(module_or_class, prefix)."""
@hookspec(historic=True)
def pytest_namespace():
"""return dict of name->object to be made globally available in
the pytest namespace. This hook is called before command line options
are parsed.
the pytest namespace. This hook is called at plugin registration
time.
"""
def pytest_cmdline_parse(pluginmanager, args):
"""return initialized config object, parsing the specified args. """
pytest_cmdline_parse.firstresult = True
@hookspec(historic=True)
def pytest_plugin_registered(plugin, manager):
""" a new pytest plugin got registered. """
def pytest_cmdline_preparse(config, args):
"""(deprecated) modify command line arguments before option parsing. """
@hookspec(historic=True)
def pytest_addoption(parser):
"""register argparse-style options and ini-style config values.
This function must be implemented in a :ref:`plugin <pluginorder>` and is
"""register argparse-style options and ini-style config values,
called once at the beginning of a test run.
.. note::
This function should be implemented only in plugins or ``conftest.py``
files situated at the tests root directory due to how py.test
:ref:`discovers plugins during startup <pluginorder>`.
:arg parser: To add command line options, call
:py:func:`parser.addoption(...) <_pytest.config.Parser.addoption>`.
To add ini-file values call :py:func:`parser.addini(...)
@@ -47,35 +56,43 @@ 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.
"""
# -------------------------------------------------------------------------
# Bootstrapping hooks called for plugins registered early enough:
# internal and 3rd party plugins as well as directly
# discoverable conftest.py local plugins.
# -------------------------------------------------------------------------
@hookspec(firstresult=True)
def pytest_cmdline_parse(pluginmanager, args):
"""return initialized config object, parsing the specified args. """
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
implementation will invoke the configure hooks and runtest_mainloop. """
pytest_cmdline_main.firstresult = True
def pytest_load_initial_conftests(args, early_config, parser):
def pytest_load_initial_conftests(early_config, parser, args):
""" implements the loading of initial conftest files ahead
of command line option parsing. """
def pytest_configure(config):
""" called after command line options have been parsed
and all plugins and initial conftest files been loaded.
"""
def pytest_unconfigure(config):
""" called before test process is exited. """
def pytest_runtestloop(session):
""" called for performing the main runtest loop
(after collection finished). """
pytest_runtestloop.firstresult = True
# -------------------------------------------------------------------------
# collection hooks
# -------------------------------------------------------------------------
@hookspec(firstresult=True)
def pytest_collection(session):
""" perform the collection protocol for the given session. """
pytest_collection.firstresult = True
def pytest_collection_modifyitems(session, config, items):
""" called after collection has been performed, may filter or re-order
@@ -84,16 +101,16 @@ def pytest_collection_modifyitems(session, config, items):
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.
This hook is consulted for all files and directories prior to calling
more specific hooks.
"""
pytest_ignore_collect.firstresult = True
@hookspec(firstresult=True)
def pytest_collect_directory(path, parent):
""" called before traversing a directory for collection files. """
pytest_collect_directory.firstresult = True
def pytest_collect_file(path, parent):
""" return collection Node or None for the given path. Any new node
@@ -112,29 +129,29 @@ def pytest_collectreport(report):
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. """
pytest_make_collect_report.firstresult = True
# -------------------------------------------------------------------------
# Python test function related hooks
# -------------------------------------------------------------------------
@hookspec(firstresult=True)
def pytest_pycollect_makemodule(path, parent):
""" return a Module collector or None for the given path.
This hook will be called for each matching test module path.
The pytest_collect_file hook needs to be used if you want to
create test modules for files that do not match as a test module.
"""
pytest_pycollect_makemodule.firstresult = True
@hookspec(firstresult=True)
def pytest_pycollect_makeitem(collector, name, obj):
""" return custom item/collector for a python object in a module, or None. """
pytest_pycollect_makeitem.firstresult = True
@hookspec(firstresult=True)
def pytest_pyfunc_call(pyfuncitem):
""" call underlying test function. """
pytest_pyfunc_call.firstresult = True
def pytest_generate_tests(metafunc):
""" generate (multiple) parametrized calls to a test function."""
@@ -142,9 +159,16 @@ def pytest_generate_tests(metafunc):
# -------------------------------------------------------------------------
# generic runtest related hooks
# -------------------------------------------------------------------------
def pytest_itemstart(item, node=None):
@hookspec(firstresult=True)
def pytest_runtestloop(session):
""" called for performing the main runtest loop
(after collection finished). """
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
the given test item, including capturing exceptions and calling
@@ -158,7 +182,6 @@ def pytest_runtest_protocol(item, nextitem):
:return boolean: True if no further hook implementations should be invoked.
"""
pytest_runtest_protocol.firstresult = True
def pytest_runtest_logstart(nodeid, location):
""" signal the start of running a single test item. """
@@ -178,12 +201,12 @@ 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
for the given :py:class:`pytest.Item` and
:py:class:`_pytest.runner.CallInfo`.
"""
pytest_runtest_makereport.firstresult = True
def pytest_runtest_logreport(report):
""" process a test setup/call/teardown report relating to
@@ -199,6 +222,9 @@ def pytest_sessionstart(session):
def pytest_sessionfinish(session, exitstatus):
""" whole test run finishes. """
def pytest_unconfigure(config):
""" called before test process is exited. """
# -------------------------------------------------------------------------
# hooks for customising the assert methods
@@ -220,13 +246,15 @@ def pytest_assertrepr_compare(config, op, left, right):
def pytest_report_header(config, startdir):
""" return a string to be displayed as header info for terminal reporting."""
@hookspec(firstresult=True)
def pytest_report_teststatus(report):
""" return result-category, shortletter and verbose word for reporting."""
pytest_report_teststatus.firstresult = True
def pytest_terminal_summary(terminalreporter):
""" add additional section in terminal summary reporting. """
@hookspec(historic=True)
def pytest_logwarning(message, code, nodeid, fslocation):
""" process a warning specified by a message, a code string,
a nodeid and fslocation (both of which may be None
@@ -236,17 +264,14 @@ 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"""
pytest_doctest_prepare_content.firstresult = True
# -------------------------------------------------------------------------
# error handling and internal debugging hooks
# -------------------------------------------------------------------------
def pytest_plugin_registered(plugin, manager):
""" a new pytest plugin got registered. """
def pytest_internalerror(excrepr, excinfo):
""" called for internal errors. """
@@ -254,13 +279,17 @@ def pytest_keyboard_interrupt(excinfo):
""" called for keyboard interrupt. """
def pytest_exception_interact(node, call, report):
""" (experimental, new in 2.4) called when
an exception was raised which can potentially be
"""called when an exception was raised which can potentially be
interactively handled.
This hook is only called if an exception was raised
that is not an internal exception like "skip.Exception".
that is not an internal exception like ``skip.Exception``.
"""
def pytest_enter_pdb():
""" called upon pdb.set_trace()"""
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.
:arg config: pytest config object
:type config: _pytest.config.Config
"""

View File

@@ -1,12 +1,19 @@
""" report test results in JUnit-XML format, for use with Hudson and build integration servers.
"""
report test results in JUnit-XML format,
for use with Jenkins and build integration servers.
Based on initial code from Ross Lawley.
"""
# Output conforms to https://github.com/jenkinsci/xunit-plugin/blob/master/
# src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd
import py
import os
import re
import sys
import time
import pytest
# Python 2.X and 3.X compatibility
if sys.version_info[0] < 3:
@@ -16,10 +23,10 @@ else:
unicode = str
long = int
class Junit(py.xml.Namespace):
pass
# We need to get the subset of the invalid unicode ranges according to
# XML 1.0 which are valid in this python build. Hence we calculate
# this dynamically instead of hardcoding it. The spec range of valid
@@ -27,21 +34,21 @@ class Junit(py.xml.Namespace):
# | [#x10000-#x10FFFF]
_legal_chars = (0x09, 0x0A, 0x0d)
_legal_ranges = (
(0x20, 0x7E),
(0x80, 0xD7FF),
(0xE000, 0xFFFD),
(0x10000, 0x10FFFF),
(0x20, 0x7E), (0x80, 0xD7FF), (0xE000, 0xFFFD), (0x10000, 0x10FFFF),
)
_legal_xml_re = [unicode("%s-%s") % (unichr(low), unichr(high))
for (low, high) in _legal_ranges
if low < sys.maxunicode]
_legal_xml_re = [
unicode("%s-%s") % (unichr(low), unichr(high))
for (low, high) in _legal_ranges if low < sys.maxunicode
]
_legal_xml_re = [unichr(x) for x in _legal_chars] + _legal_xml_re
illegal_xml_re = re.compile(unicode('[^%s]') %
unicode('').join(_legal_xml_re))
illegal_xml_re = re.compile(unicode('[^%s]') % unicode('').join(_legal_xml_re))
del _legal_chars
del _legal_ranges
del _legal_xml_re
_py_ext_re = re.compile(r"\.py$")
def bin_xml_escape(arg):
def repl(matchobj):
i = ord(matchobj.group())
@@ -49,16 +56,171 @@ def bin_xml_escape(arg):
return unicode('#x%02X') % i
else:
return unicode('#x%04X') % i
return py.xml.raw(illegal_xml_re.sub(repl, py.xml.escape(arg)))
class _NodeReporter(object):
def __init__(self, nodeid, xml):
self.id = nodeid
self.xml = xml
self.add_stats = self.xml.add_stats
self.duration = 0
self.properties = []
self.nodes = []
self.testcase = None
self.attrs = {}
def append(self, node):
self.xml.add_stats(type(node).__name__)
self.nodes.append(node)
def add_property(self, name, value):
self.properties.append((str(name), bin_xml_escape(value)))
def make_properties_node(self):
"""Return a Junit node containing custom properties, if any.
"""
if self.properties:
return Junit.properties([
Junit.property(name=name, value=value)
for name, value in self.properties
])
return ''
def record_testreport(self, testreport):
assert not self.testcase
names = mangle_test_address(testreport.nodeid)
classnames = names[:-1]
if self.xml.prefix:
classnames.insert(0, self.xml.prefix)
attrs = {
"classname": ".".join(classnames),
"name": bin_xml_escape(names[-1]),
"file": testreport.location[0],
}
if testreport.location[1] is not None:
attrs["line"] = testreport.location[1]
self.attrs = attrs
def to_xml(self):
testcase = Junit.testcase(time=self.duration, **self.attrs)
testcase.append(self.make_properties_node())
for node in self.nodes:
testcase.append(node)
return testcase
def _add_simple(self, kind, message, data=None):
data = bin_xml_escape(data)
node = kind(data, message=message)
self.append(node)
def _write_captured_output(self, report):
for capname in ('out', 'err'):
allcontent = ""
for name, content in report.get_sections("Captured std%s" %
capname):
allcontent += content
if allcontent:
tag = getattr(Junit, 'system-' + capname)
self.append(tag(bin_xml_escape(allcontent)))
def append_pass(self, report):
self.add_stats('passed')
self._write_captured_output(report)
def append_failure(self, report):
# msg = str(report.longrepr.reprtraceback.extraline)
if hasattr(report, "wasxfail"):
self._add_simple(
Junit.skipped,
"xfail-marked test passes unexpectedly")
else:
if hasattr(report.longrepr, "reprcrash"):
message = report.longrepr.reprcrash.message
elif isinstance(report.longrepr, (unicode, str)):
message = report.longrepr
else:
message = str(report.longrepr)
message = bin_xml_escape(message)
fail = Junit.failure(message=message)
fail.append(bin_xml_escape(report.longrepr))
self.append(fail)
self._write_captured_output(report)
def append_collect_error(self, report):
# msg = str(report.longrepr.reprtraceback.extraline)
self.append(Junit.error(bin_xml_escape(report.longrepr),
message="collection failure"))
def append_collect_skipped(self, report):
self._add_simple(
Junit.skipped, "collection skipped", report.longrepr)
def append_error(self, report):
self._add_simple(
Junit.error, "test setup failure", report.longrepr)
self._write_captured_output(report)
def append_skipped(self, report):
if hasattr(report, "wasxfail"):
self._add_simple(
Junit.skipped, "expected test failure", report.wasxfail
)
else:
filename, lineno, skipreason = report.longrepr
if skipreason.startswith("Skipped: "):
skipreason = bin_xml_escape(skipreason[9:])
self.append(
Junit.skipped("%s:%s: %s" % (filename, lineno, skipreason),
type="pytest.skip",
message=skipreason))
self._write_captured_output(report)
def finalize(self):
data = self.to_xml().unicode(indent=0)
self.__dict__.clear()
self.to_xml = lambda: py.xml.raw(data)
@pytest.fixture
def record_xml_property(request):
"""Fixture that adds extra xml properties to the tag for the calling test.
The fixture is callable with (name, value), with value being automatically
xml-encoded.
"""
request.node.warn(
code='C3',
message='record_xml_property is an experimental feature',
)
xml = getattr(request.config, "_xml", None)
if xml is not None:
node_reporter = xml.node_reporter(request.node.nodeid)
return node_reporter.add_property
else:
def add_property_noop(name, value):
pass
return add_property_noop
def pytest_addoption(parser):
group = parser.getgroup("terminal reporting")
group.addoption('--junitxml', '--junit-xml', action="store",
dest="xmlpath", metavar="path", default=None,
help="create junit-xml style report file at given path.")
group.addoption('--junitprefix', '--junit-prefix', action="store",
metavar="str", default=None,
help="prepend prefix to classnames in junit-xml output")
group.addoption(
'--junitxml', '--junit-xml',
action="store",
dest="xmlpath",
metavar="path",
default=None,
help="create junit-xml style report file at given path.")
group.addoption(
'--junitprefix', '--junit-prefix',
action="store",
metavar="str",
default=None,
help="prepend prefix to classnames in junit-xml output")
def pytest_configure(config):
xmlpath = config.option.xmlpath
@@ -67,6 +229,7 @@ def pytest_configure(config):
config._xml = LogXML(xmlpath, config.option.junitprefix)
config.pluginmanager.register(config._xml)
def pytest_unconfigure(config):
xml = getattr(config, '_xml', None)
if xml:
@@ -74,146 +237,151 @@ def pytest_unconfigure(config):
config.pluginmanager.unregister(xml)
def mangle_testnames(names):
names = [x.replace(".py", "") for x in names if x != '()']
def mangle_test_address(address):
path, possible_open_bracket, params = address.partition('[')
names = path.split("::")
try:
names.remove('()')
except ValueError:
pass
# convert file path to dotted path
names[0] = names[0].replace("/", '.')
names[0] = _py_ext_re.sub("", names[0])
# put any params back
names[-1] += possible_open_bracket + params
return names
class LogXML(object):
def __init__(self, logfile, prefix):
logfile = os.path.expanduser(os.path.expandvars(logfile))
self.logfile = os.path.normpath(os.path.abspath(logfile))
self.prefix = prefix
self.tests = []
self.passed = self.skipped = 0
self.failed = self.errors = 0
self.stats = dict.fromkeys([
'error',
'passed',
'failure',
'skipped',
], 0)
self.node_reporters = {} # nodeid -> _NodeReporter
self.node_reporters_ordered = []
def finalize(self, report):
nodeid = getattr(report, 'nodeid', report)
# local hack to handle xdist report order
slavenode = getattr(report, 'node', None)
reporter = self.node_reporters.pop((nodeid, slavenode))
if reporter is not None:
reporter.finalize()
def node_reporter(self, report):
nodeid = getattr(report, 'nodeid', report)
# local hack to handle xdist report order
slavenode = getattr(report, 'node', None)
key = nodeid, slavenode
if key in self.node_reporters:
# TODO: breasks for --dist=each
return self.node_reporters[key]
reporter = _NodeReporter(nodeid, self)
self.node_reporters[key] = reporter
self.node_reporters_ordered.append(reporter)
return reporter
def add_stats(self, key):
if key in self.stats:
self.stats[key] += 1
def _opentestcase(self, report):
names = mangle_testnames(report.nodeid.split("::"))
classnames = names[:-1]
if self.prefix:
classnames.insert(0, self.prefix)
self.tests.append(Junit.testcase(
classname=".".join(classnames),
name=bin_xml_escape(names[-1]),
time=getattr(report, 'duration', 0)
))
def _write_captured_output(self, report):
for capname in ('out', 'err'):
allcontent = ""
for name, content in report.get_sections("Captured std%s" %
capname):
allcontent += content
if allcontent:
tag = getattr(Junit, 'system-'+capname)
self.append(tag(bin_xml_escape(allcontent)))
def append(self, obj):
self.tests[-1].append(obj)
def append_pass(self, report):
self.passed += 1
self._write_captured_output(report)
def append_failure(self, report):
#msg = str(report.longrepr.reprtraceback.extraline)
if hasattr(report, "wasxfail"):
self.append(
Junit.skipped(message="xfail-marked test passes unexpectedly"))
self.skipped += 1
else:
fail = Junit.failure(message="test failure")
fail.append(bin_xml_escape(report.longrepr))
self.append(fail)
self.failed += 1
self._write_captured_output(report)
def append_collect_error(self, report):
#msg = str(report.longrepr.reprtraceback.extraline)
self.append(Junit.error(bin_xml_escape(report.longrepr),
message="collection failure"))
self.errors += 1
def append_collect_skipped(self, report):
#msg = str(report.longrepr.reprtraceback.extraline)
self.append(Junit.skipped(bin_xml_escape(report.longrepr),
message="collection skipped"))
self.skipped += 1
def append_error(self, report):
self.append(Junit.error(bin_xml_escape(report.longrepr),
message="test setup failure"))
self.errors += 1
def append_skipped(self, report):
if hasattr(report, "wasxfail"):
self.append(Junit.skipped(bin_xml_escape(report.wasxfail),
message="expected test failure"))
else:
filename, lineno, skipreason = report.longrepr
if skipreason.startswith("Skipped: "):
skipreason = bin_xml_escape(skipreason[9:])
self.append(
Junit.skipped("%s:%s: %s" % (filename, lineno, skipreason),
type="pytest.skip",
message=skipreason
))
self.skipped += 1
self._write_captured_output(report)
reporter = self.node_reporter(report)
reporter.record_testreport(report)
return reporter
def pytest_runtest_logreport(self, report):
"""handle a setup/call/teardown report, generating the appropriate
xml tags as necessary.
note: due to plugins like xdist, this hook may be called in interlaced
order with reports from other nodes. for example:
usual call order:
-> setup node1
-> call node1
-> teardown node1
-> setup node2
-> call node2
-> teardown node2
possible call order in xdist:
-> setup node1
-> call node1
-> setup node2
-> call node2
-> teardown node2
-> teardown node1
"""
if report.passed:
if report.when == "call": # ignore setup/teardown
self._opentestcase(report)
self.append_pass(report)
if report.when == "call": # ignore setup/teardown
reporter = self._opentestcase(report)
reporter.append_pass(report)
elif report.failed:
self._opentestcase(report)
if report.when != "call":
self.append_error(report)
reporter = self._opentestcase(report)
if report.when == "call":
reporter.append_failure(report)
else:
self.append_failure(report)
reporter.append_error(report)
elif report.skipped:
self._opentestcase(report)
self.append_skipped(report)
reporter = self._opentestcase(report)
reporter.append_skipped(report)
self.update_testcase_duration(report)
if report.when == "teardown":
self.finalize(report)
def update_testcase_duration(self, report):
"""accumulates total duration for nodeid from given report and updates
the Junit.testcase with the new total if already created.
"""
reporter = self.node_reporter(report)
reporter.duration += getattr(report, 'duration', 0.0)
def pytest_collectreport(self, report):
if not report.passed:
self._opentestcase(report)
reporter = self._opentestcase(report)
if report.failed:
self.append_collect_error(report)
reporter.append_collect_error(report)
else:
self.append_collect_skipped(report)
reporter.append_collect_skipped(report)
def pytest_internalerror(self, excrepr):
self.errors += 1
data = bin_xml_escape(excrepr)
self.tests.append(
Junit.testcase(
Junit.error(data, message="internal error"),
classname="pytest",
name="internal"))
reporter = self.node_reporter('internal')
reporter.attrs.update(classname="pytest", name='internal')
reporter._add_simple(Junit.error, 'internal error', excrepr)
def pytest_sessionstart(self):
self.suite_start_time = time.time()
def pytest_sessionfinish(self):
dirname = os.path.dirname(os.path.abspath(self.logfile))
if not os.path.isdir(dirname):
os.makedirs(dirname)
logfile = open(self.logfile, 'w', encoding='utf-8')
suite_stop_time = time.time()
suite_time_delta = suite_stop_time - self.suite_start_time
numtests = self.passed + self.failed
numtests = self.stats['passed'] + self.stats['failure'] + self.stats['skipped']
logfile.write('<?xml version="1.0" encoding="utf-8"?>')
logfile.write(Junit.testsuite(
self.tests,
[x.to_xml() for x in self.node_reporters_ordered],
name="pytest",
errors=self.errors,
failures=self.failed,
skips=self.skipped,
errors=self.stats['error'],
failures=self.stats['failure'],
skips=self.stats['skipped'],
tests=numtests,
time="%.3f" % suite_time_delta,
).unicode(indent=0))
time="%.3f" % suite_time_delta, ).unicode(indent=0))
logfile.close()
def pytest_terminal_summary(self, terminalreporter):
terminalreporter.write_sep("-", "generated xml file: %s" % (self.logfile))
terminalreporter.write_sep("-",
"generated xml file: %s" % (self.logfile))

View File

@@ -1,9 +1,13 @@
""" core implementation of testing process: init, session, runtest loop. """
import imp
import os
import re
import sys
import _pytest
import _pytest._code
import py
import pytest, _pytest
import os, sys, imp
import pytest
try:
from collections import MutableMapping as MappingMixin
except ImportError:
@@ -19,12 +23,15 @@ EXIT_TESTSFAILED = 1
EXIT_INTERRUPTED = 2
EXIT_INTERNALERROR = 3
EXIT_USAGEERROR = 4
EXIT_NOTESTSCOLLECTED = 5
name_re = re.compile("^[a-zA-Z_]\w*$")
def pytest_addoption(parser):
parser.addini("norecursedirs", "directory patterns to avoid for recursion",
type="args", default=('.*', 'CVS', '_darcs', '{arch}', '*.egg'))
type="args", default=['.*', 'CVS', '_darcs', '{arch}', '*.egg'])
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",
@@ -54,6 +61,9 @@ def pytest_addoption(parser):
group.addoption('--confcutdir', dest="confcutdir", default=None,
metavar="dir",
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.")
group = parser.getgroup("debugconfig",
"test session debugging and configuration")
@@ -77,29 +87,24 @@ def wrap_session(config, doit):
initstate = 0
try:
try:
config.do_configure()
config._do_configure()
initstate = 1
config.hook.pytest_sessionstart(session=session)
initstate = 2
doit(config, session)
session.exitstatus = doit(config, session) or 0
except pytest.UsageError:
args = sys.exc_info()[1].args
for msg in args:
sys.stderr.write("ERROR: %s\n" %(msg,))
session.exitstatus = EXIT_USAGEERROR
raise
except KeyboardInterrupt:
excinfo = py.code.ExceptionInfo()
excinfo = _pytest._code.ExceptionInfo()
config.hook.pytest_keyboard_interrupt(excinfo=excinfo)
session.exitstatus = EXIT_INTERRUPTED
except:
excinfo = py.code.ExceptionInfo()
excinfo = _pytest._code.ExceptionInfo()
config.notify_exception(excinfo, config.option)
session.exitstatus = EXIT_INTERNALERROR
if excinfo.errisinstance(SystemExit):
sys.stderr.write("mainloop: caught Spurious SystemExit!\n")
else:
if session._testsfailed:
session.exitstatus = EXIT_TESTSFAILED
finally:
excinfo = None # Explicitly break reference cycle.
session.startdir.chdir()
@@ -107,9 +112,7 @@ def wrap_session(config, doit):
config.hook.pytest_sessionfinish(
session=session,
exitstatus=session.exitstatus)
if initstate >= 1:
config.do_unconfigure()
config.pluginmanager.ensure_shutdown()
config._ensure_unconfigure()
return session.exitstatus
def pytest_cmdline_main(config):
@@ -121,6 +124,11 @@ def _main(config, session):
config.hook.pytest_collection(session=session)
config.hook.pytest_runtestloop(session=session)
if session.testsfailed:
return EXIT_TESTSFAILED
elif session.testscollected == 0:
return EXIT_NOTESTSCOLLECTED
def pytest_collection(session):
return session.perform_collect()
@@ -153,19 +161,16 @@ def pytest_ignore_collect(path, config):
ignore_paths.extend([py.path.local(x) for x in excludeopt])
return path in ignore_paths
class HookProxy(object):
def __init__(self, fspath, config):
class FSHookProxy:
def __init__(self, fspath, pm, remove_mods):
self.fspath = fspath
self.config = config
self.pm = pm
self.remove_mods = remove_mods
def __getattr__(self, name):
config = object.__getattribute__(self, "config")
hookmethod = getattr(config.hook, name)
def call_matching_hooks(**kwargs):
plugins = self.config._getmatchingplugins(self.fspath)
return hookmethod.pcall(plugins, **kwargs)
return call_matching_hooks
x = self.pm.subset_hook_caller(name, remove_plugins=self.remove_mods)
self.__dict__[name] = x
return x
def compatproperty(name):
def fget(self):
@@ -239,17 +244,11 @@ class Node(object):
# used for storing artificial fixturedefs for direct parametrization
self._name2pseudofixturedef = {}
#self.extrainit()
@property
def ihook(self):
""" fspath sensitive hook proxy used to call pytest hooks"""
return self.session.gethookproxy(self.fspath)
#def extrainit(self):
# """"extra initialization after Node is initialized. Implemented
# by some subclasses. """
Module = compatproperty("Module")
Class = compatproperty("Class")
Instance = compatproperty("Instance")
@@ -279,9 +278,9 @@ class Node(object):
else:
fslocation = "%s:%s" % fslocation[:2]
self.ihook.pytest_logwarning(code=code, message=message,
nodeid=self.nodeid,
fslocation=fslocation)
self.ihook.pytest_logwarning.call_historic(kwargs=dict(
code=code, message=message,
nodeid=self.nodeid, fslocation=fslocation))
# methods for ordering nodes
@property
@@ -366,9 +365,6 @@ class Node(object):
def listnames(self):
return [x.name for x in self.listchain()]
def getplugins(self):
return self.config._getmatchingplugins(self.fspath)
def addfinalizer(self, fin):
""" register a function to be called when this node is finalized.
@@ -459,9 +455,7 @@ class FSCollector(Collector):
self.fspath = fspath
def _makeid(self):
if self == self.session:
return "."
relpath = self.session.fspath.bestrelpath(self.fspath)
relpath = self.fspath.relto(self.config.rootdir)
if os.sep != "/":
relpath = relpath.replace(os.sep, "/")
return relpath
@@ -506,39 +500,64 @@ class Item(Node):
class NoMatch(Exception):
""" raised if matching cannot locate a matching names. """
class Interrupted(KeyboardInterrupt):
""" signals an interrupted test run. """
__module__ = 'builtins' # for py3
class Session(FSCollector):
class Interrupted(KeyboardInterrupt):
""" signals an interrupted test run. """
__module__ = 'builtins' # for py3
Interrupted = Interrupted
def __init__(self, config):
FSCollector.__init__(self, py.path.local(), parent=None,
FSCollector.__init__(self, config.rootdir, parent=None,
config=config, session=self)
self.config.pluginmanager.register(self, name="session", prepend=True)
self._testsfailed = 0
self._fs2hookproxy = {}
self.testsfailed = 0
self.testscollected = 0
self.shouldstop = False
self.trace = config.trace.root.get("collection")
self._norecursepatterns = config.getini("norecursedirs")
self.startdir = py.path.local()
self.config.pluginmanager.register(self, name="session")
def _makeid(self):
return ""
@pytest.hookimpl(tryfirst=True)
def pytest_collectstart(self):
if self.shouldstop:
raise self.Interrupted(self.shouldstop)
@pytest.hookimpl(tryfirst=True)
def pytest_runtest_logreport(self, report):
if report.failed and not hasattr(report, 'wasxfail'):
self._testsfailed += 1
self.testsfailed += 1
maxfail = self.config.getvalue("maxfail")
if maxfail and self._testsfailed >= maxfail:
if maxfail and self.testsfailed >= maxfail:
self.shouldstop = "stopping after %d failures" % (
self._testsfailed)
self.testsfailed)
pytest_collectreport = pytest_runtest_logreport
def isinitpath(self, path):
return path in self._initialpaths
def gethookproxy(self, fspath):
return HookProxy(fspath, self.config)
try:
return self._fs2hookproxy[fspath]
except KeyError:
# check if we have the common case of running
# hooks with all conftest.py filesall conftest.py
pm = self.config.pluginmanager
my_conftestmodules = pm._getconftestmodules(fspath)
remove_mods = pm._conftest_plugins.difference(my_conftestmodules)
if remove_mods:
# one or more conftests are not in use at this fspath
proxy = FSHookProxy(fspath, pm, remove_mods)
else:
# all plugis are active for this fspath
proxy = self.config.hook
self._fs2hookproxy[fspath] = proxy
return proxy
def perform_collect(self, args=None, genitems=True):
hook = self.config.hook
@@ -548,6 +567,7 @@ class Session(FSCollector):
config=self.config, items=items)
finally:
hook.pytest_collection_finish(session=self)
self.testscollected = len(items)
return items
def _perform_collect(self, args, genitems):
@@ -660,7 +680,7 @@ class Session(FSCollector):
arg = self._tryconvertpyarg(arg)
parts = str(arg).split("::")
relpath = parts[0].replace("/", os.sep)
path = self.fspath.join(relpath, abs=True)
path = self.config.invocation_dir.join(relpath, abs=True)
if not path.check():
if self.config.option.pyargs:
msg = "file or package not found: "
@@ -698,7 +718,8 @@ class Session(FSCollector):
if rep.passed:
has_matched = False
for x in rep.result:
if x.name == name:
# TODO: remove parametrized workaround once collection structure contains parametrization
if x.name == name or x.name.split("[")[0] == name:
resultnodes.extend(self.matchnodes([x], nextnames))
has_matched = True
# XXX accept IDs that don't have "()" for class instances
@@ -721,5 +742,3 @@ class Session(FSCollector):
for x in self.genitems(subnode):
yield x
node.ihook.pytest_collectreport(report=rep)

View File

@@ -1,5 +1,10 @@
""" generic mechanism for marking and selecting python functions. """
import py
import inspect
class MarkerError(Exception):
"""Error in use of a pytest marker/attribute."""
def pytest_namespace():
@@ -38,24 +43,30 @@ def pytest_addoption(parser):
def pytest_cmdline_main(config):
import _pytest.config
if config.option.markers:
config.do_configure()
tw = py.io.TerminalWriter()
config._do_configure()
tw = _pytest.config.create_terminal_writer(config)
for line in config.getini("markers"):
name, rest = line.split(":", 1)
tw.write("@pytest.mark.%s:" % name, bold=True)
tw.line(rest)
tw.line()
config.do_unconfigure()
config._ensure_unconfigure()
return 0
pytest_cmdline_main.tryfirst = True
def pytest_collection_modifyitems(items, config):
keywordexpr = config.option.keyword
keywordexpr = config.option.keyword.lstrip()
matchexpr = config.option.markexpr
if not keywordexpr and not matchexpr:
return
# pytest used to allow "-" for negating
# but today we just allow "-" at the beginning, use "not" instead
# we probably remove "-" alltogether soon
if keywordexpr.startswith("-"):
keywordexpr = "not " + keywordexpr[1:]
selectuntil = False
if keywordexpr[-1:] == ":":
selectuntil = True
@@ -122,7 +133,6 @@ def matchkeyword(colitem, keywordexpr):
Additionally, matches on names in the 'extra_keyword_matches' set of
any item, as well as names directly assigned to test functions.
"""
keywordexpr = keywordexpr.replace("-", "not ")
mapped_names = set()
# Add the names of the current item and any parent items
@@ -159,7 +169,7 @@ class MarkGenerator:
""" Factory for :class:`MarkDecorator` objects - exposed as
a ``pytest.mark`` singleton instance. Example::
import py
import pytest
@pytest.mark.slowtest
def test_function():
pass
@@ -244,15 +254,17 @@ class MarkDecorator:
otherwise add *args/**kwargs in-place to mark information. """
if args and not kwargs:
func = args[0]
if len(args) == 1 and (istestfunc(func) or
hasattr(func, '__bases__')):
if hasattr(func, '__bases__'):
is_class = inspect.isclass(func)
if len(args) == 1 and (istestfunc(func) or is_class):
if is_class:
if hasattr(func, 'pytestmark'):
l = func.pytestmark
if not isinstance(l, list):
func.pytestmark = [l, self]
else:
l.append(self)
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]
else:
@@ -279,7 +291,7 @@ class MarkInfo:
#: positional argument list, empty if none specified
self.args = args
#: keyword argument dictionary, empty if nothing specified
self.kwargs = kwargs
self.kwargs = kwargs.copy()
self._arglist = [(args, kwargs.copy())]
def __repr__(self):

View File

@@ -1,8 +1,13 @@
""" monkeypatching and mocking functionality. """
import os, sys
import re
from py.builtin import _basestring
RE_IMPORT_ERROR_NAME = re.compile("^No module named (.*)$")
def pytest_funcarg__monkeypatch(request):
"""The returned ``monkeypatch`` funcarg provides these
helper methods to modify objects, dictionaries or os.environ::
@@ -26,54 +31,79 @@ def pytest_funcarg__monkeypatch(request):
return mpatch
def resolve(name):
# simplified from zope.dottedname
parts = name.split('.')
def derive_importpath(import_path):
import pytest
used = parts.pop(0)
found = __import__(used)
for part in parts:
used += '.' + part
try:
found = getattr(found, part)
except AttributeError:
pass
else:
continue
# we use explicit un-nesting of the handling block in order
# to avoid nested exceptions on python 3
try:
__import__(used)
except ImportError as ex:
# str is used for py2 vs py3
expected = str(ex).split()[-1]
if expected == used:
raise
else:
raise ImportError(
'import error in %s: %s' % (used, ex)
)
found = annotated_getattr(found, part, used)
return found
def annotated_getattr(obj, name, ann):
try:
obj = getattr(obj, name)
except AttributeError:
raise AttributeError(
'%r object at %s has no attribute %r' % (
type(obj).__name__, ann, name
)
)
return obj
def derive_importpath(import_path, raising):
if not isinstance(import_path, _basestring) or "." not in import_path:
raise TypeError("must be absolute import path string, not %r" %
(import_path,))
rest = []
target = import_path
while target:
try:
obj = __import__(target, None, None, "__doc__")
except ImportError:
if "." not in target:
__tracebackhide__ = True
pytest.fail("could not import any sub part: %s" %
import_path)
target, name = target.rsplit(".", 1)
rest.append(name)
else:
assert rest
try:
while len(rest) > 1:
attr = rest.pop()
obj = getattr(obj, attr)
attr = rest[0]
getattr(obj, attr)
except AttributeError:
__tracebackhide__ = True
pytest.fail("object %r has no attribute %r" % (obj, attr))
return attr, obj
module, attr = import_path.rsplit('.', 1)
target = resolve(module)
if raising:
annotated_getattr(target, attr, ann=module)
return attr, target
class Notset:
def __repr__(self):
return "<notset>"
notset = Notset()
class monkeypatch:
""" object keeping a record of setattr/item/env/syspath changes. """
""" Object keeping a record of setattr/item/env/syspath changes. """
def __init__(self):
self._setattr = []
self._setitem = []
self._cwd = None
self._savesyspath = None
def setattr(self, target, name, value=notset, raising=True):
""" set attribute value on target, memorizing the old value.
""" Set attribute value on target, memorizing the old value.
By default raise AttributeError if the attribute did not exist.
For convenience you can specify a string as ``target`` which
@@ -92,31 +122,31 @@ class monkeypatch:
if value is notset:
if not isinstance(target, _basestring):
raise TypeError("use setattr(target, name, value) or "
"setattr(target, value) with target being a dotted "
"import string")
"setattr(target, value) with target being a dotted "
"import string")
value = name
name, target = derive_importpath(target)
name, target = derive_importpath(target, raising)
oldval = getattr(target, name, notset)
if raising and oldval is notset:
raise AttributeError("%r has no attribute %r" %(target, name))
raise AttributeError("%r has no attribute %r" % (target, name))
# avoid class descriptors like staticmethod/classmethod
if inspect.isclass(target):
oldval = target.__dict__.get(name, notset)
self._setattr.insert(0, (target, name, oldval))
self._setattr.append((target, name, oldval))
setattr(target, name, value)
def delattr(self, target, name=notset, raising=True):
""" delete attribute ``name`` from ``target``, by default raise
""" Delete attribute ``name`` from ``target``, by default raise
AttributeError it the attribute did not previously exist.
If no ``name`` is specified and ``target`` is a string
it will be interpreted as a dotted import path with the
last part being the attribute name.
If raising is set to false, the attribute is allowed to not
pre-exist.
If ``raising`` is set to False, no exception will be raised if the
attribute is missing.
"""
__tracebackhide__ = True
if name is notset:
@@ -124,32 +154,35 @@ class monkeypatch:
raise TypeError("use delattr(target, name) or "
"delattr(target) with target being a dotted "
"import string")
name, target = derive_importpath(target)
name, target = derive_importpath(target, raising)
if not hasattr(target, name):
if raising:
raise AttributeError(name)
else:
self._setattr.insert(0, (target, name,
getattr(target, name, notset)))
self._setattr.append((target, name, getattr(target, name, notset)))
delattr(target, name)
def setitem(self, dic, name, value):
""" set dictionary entry ``name`` to value. """
self._setitem.insert(0, (dic, name, dic.get(name, notset)))
""" Set dictionary entry ``name`` to value. """
self._setitem.append((dic, name, dic.get(name, notset)))
dic[name] = value
def delitem(self, dic, name, raising=True):
""" delete ``name`` from dict, raise KeyError if it doesn't exist."""
""" Delete ``name`` from dict. Raise KeyError if it doesn't exist.
If ``raising`` is set to False, no exception will be raised if the
key is missing.
"""
if name not in dic:
if raising:
raise KeyError(name)
else:
self._setitem.insert(0, (dic, name, dic.get(name, notset)))
self._setitem.append((dic, name, dic.get(name, notset)))
del dic[name]
def setenv(self, name, value, prepend=None):
""" set environment variable ``name`` to ``value``. if ``prepend``
""" Set environment variable ``name`` to ``value``. If ``prepend``
is a character, read the current environment variable value
and prepend the ``value`` adjoined with the ``prepend`` character."""
value = str(value)
@@ -158,18 +191,23 @@ class monkeypatch:
self.setitem(os.environ, name, value)
def delenv(self, name, raising=True):
""" delete ``name`` from environment, raise KeyError it not exists."""
""" Delete ``name`` from the environment. Raise KeyError it does not
exist.
If ``raising`` is set to False, no exception will be raised if the
environment variable is missing.
"""
self.delitem(os.environ, name, raising=raising)
def syspath_prepend(self, path):
""" prepend ``path`` to ``sys.path`` list of import locations. """
if not hasattr(self, '_savesyspath'):
""" Prepend ``path`` to ``sys.path`` list of import locations. """
if self._savesyspath is None:
self._savesyspath = sys.path[:]
sys.path.insert(0, str(path))
def chdir(self, path):
""" change the current working directory to the specified path
path can be a string or a py.path.local object
""" Change the current working directory to the specified path.
Path can be a string or a py.path.local object.
"""
if self._cwd is None:
self._cwd = os.getcwd()
@@ -179,27 +217,37 @@ class monkeypatch:
os.chdir(path)
def undo(self):
""" undo previous changes. This call consumes the
undo stack. Calling it a second time has no effect unless
you do more monkeypatching after the undo call."""
for obj, name, value in self._setattr:
""" Undo previous changes. This call consumes the
undo stack. Calling it a second time has no effect unless
you do more monkeypatching after the undo call.
There is generally no need to call `undo()`, since it is
called automatically during tear-down.
Note that the same `monkeypatch` fixture is used across a
single test function invocation. If `monkeypatch` is used both by
the test function itself and one of the test fixtures,
calling `undo()` will undo all of the changes made in
both functions.
"""
for obj, name, value in reversed(self._setattr):
if value is not notset:
setattr(obj, name, value)
else:
delattr(obj, name)
self._setattr[:] = []
for dictionary, name, value in self._setitem:
for dictionary, name, value in reversed(self._setitem):
if value is notset:
try:
del dictionary[name]
except KeyError:
pass # was already deleted, so we have the desired state
pass # was already deleted, so we have the desired state
else:
dictionary[name] = value
self._setitem[:] = []
if hasattr(self, '_savesyspath'):
if self._savesyspath is not None:
sys.path[:] = self._savesyspath
del self._savesyspath
self._savesyspath = None
if self._cwd is not None:
os.chdir(self._cwd)

View File

@@ -16,7 +16,7 @@ def get_skip_exceptions():
return tuple(skip_classes)
def pytest_runtest_makereport(__multicall__, item, call):
def pytest_runtest_makereport(item, call):
if call.excinfo and call.excinfo.errisinstance(get_skip_exceptions()):
# let's substitute the excinfo with a pytest.skip one
call2 = call.__class__(lambda:
@@ -24,7 +24,7 @@ def pytest_runtest_makereport(__multicall__, item, call):
call.excinfo = call2.excinfo
@pytest.mark.trylast
@pytest.hookimpl(trylast=True)
def pytest_runtest_setup(item):
if is_potential_nosetest(item):
if isinstance(item.parent, pytest.Generator):

View File

@@ -1,10 +1,8 @@
""" submit failure or test session information to a pastebin service. """
import py, sys
import pytest
import sys
import tempfile
class url:
base = "http://bpaste.net"
xmlrpc = base + "/xmlrpc/"
show = base + "/show/"
def pytest_addoption(parser):
group = parser.getgroup("terminal reporting")
@@ -13,55 +11,82 @@ def pytest_addoption(parser):
choices=['failed', 'all'],
help="send failed|all info to bpaste.net pastebin service.")
def pytest_configure(__multicall__, config):
import tempfile
__multicall__.execute()
@pytest.hookimpl(trylast=True)
def pytest_configure(config):
import py
if config.option.pastebin == "all":
config._pastebinfile = tempfile.TemporaryFile('w+')
tr = config.pluginmanager.getplugin('terminalreporter')
oldwrite = tr._tw.write
def tee_write(s, **kwargs):
oldwrite(s, **kwargs)
config._pastebinfile.write(str(s))
tr._tw.write = tee_write
# if no terminal reporter plugin is present, nothing we can do here;
# this can happen when this function executes in a slave node
# when using pytest-xdist, for example
if tr is not None:
# pastebin file will be utf-8 encoded binary file
config._pastebinfile = tempfile.TemporaryFile('w+b')
oldwrite = tr._tw.write
def tee_write(s, **kwargs):
oldwrite(s, **kwargs)
if py.builtin._istext(s):
s = s.encode('utf-8')
config._pastebinfile.write(s)
tr._tw.write = tee_write
def pytest_unconfigure(config):
if hasattr(config, '_pastebinfile'):
# get terminal contents and delete file
config._pastebinfile.seek(0)
sessionlog = config._pastebinfile.read()
config._pastebinfile.close()
del config._pastebinfile
proxyid = getproxy().newPaste("python", sessionlog)
pastebinurl = "%s%s" % (url.show, proxyid)
sys.stderr.write("pastebin session-log: %s\n" % pastebinurl)
# undo our patching in the terminal reporter
tr = config.pluginmanager.getplugin('terminalreporter')
del tr._tw.__dict__['write']
# write summary
tr.write_sep("=", "Sending information to Paste Service")
pastebinurl = create_new_paste(sessionlog)
tr.write_line("pastebin session-log: %s\n" % pastebinurl)
def getproxy():
def create_new_paste(contents):
"""
Creates a new paste using bpaste.net service.
:contents: paste contents as utf-8 encoded bytes
:returns: url to the pasted contents
"""
import re
if sys.version_info < (3, 0):
from xmlrpclib import ServerProxy
from urllib import urlopen, urlencode
else:
from xmlrpc.client import ServerProxy
return ServerProxy(url.xmlrpc).pastes
from urllib.request import urlopen
from urllib.parse import urlencode
params = {
'code': contents,
'lexer': 'python3' if sys.version_info[0] == 3 else 'python',
'expiry': '1week',
}
url = 'https://bpaste.net'
response = urlopen(url, data=urlencode(params).encode('ascii')).read()
m = re.search(r'href="/raw/(\w+)"', response.decode('utf-8'))
if m:
return '%s/show/%s' % (url, m.group(1))
else:
return 'bad response: ' + response
def pytest_terminal_summary(terminalreporter):
import _pytest.config
if terminalreporter.config.option.pastebin != "failed":
return
tr = terminalreporter
if 'failed' in tr.stats:
terminalreporter.write_sep("=", "Sending information to Paste Service")
if tr.config.option.debug:
terminalreporter.write_line("xmlrpcurl: %s" %(url.xmlrpc,))
serverproxy = getproxy()
for rep in terminalreporter.stats.get('failed'):
try:
msg = rep.longrepr.reprtraceback.reprentries[-1].reprfileloc
except AttributeError:
msg = tr._getfailureheadline(rep)
tw = py.io.TerminalWriter(stringio=True)
tw = _pytest.config.create_terminal_writer(terminalreporter.config, stringio=True)
rep.toterminal(tw)
s = tw.stringio.getvalue()
assert len(s)
proxyid = serverproxy.newPaste("python", s)
pastebinurl = "%s%s" % (url.show, proxyid)
pastebinurl = create_new_paste(s)
tr.write_line("%s --> %s" %(msg, pastebinurl))

View File

@@ -4,7 +4,6 @@ import pdb
import sys
import pytest
import py
def pytest_addoption(parser):
@@ -23,26 +22,29 @@ def pytest_configure(config):
old = (pdb.set_trace, pytestPDB._pluginmanager)
def fin():
pdb.set_trace, pytestPDB._pluginmanager = old
pytestPDB._config = None
pdb.set_trace = pytest.set_trace
pytestPDB._pluginmanager = config.pluginmanager
pytestPDB._config = config
config._cleanup.append(fin)
class pytestPDB:
""" Pseudo PDB that defers to the real pdb. """
_pluginmanager = None
_config = None
def set_trace(self):
""" invoke PDB set_trace debugging, dropping any IO capturing. """
import _pytest.config
frame = sys._getframe().f_back
capman = None
if self._pluginmanager is not None:
capman = self._pluginmanager.getplugin("capturemanager")
if capman:
capman.suspendcapture(in_=True)
tw = py.io.TerminalWriter()
tw = _pytest.config.create_terminal_writer(self._config)
tw.line()
tw.sep(">", "PDB set_trace (IO-capturing turned off)")
self._pluginmanager.hook.pytest_enter_pdb()
self._pluginmanager.hook.pytest_enter_pdb(config=self._config)
pdb.Pdb().set_trace(frame)
@@ -50,7 +52,9 @@ class PdbInvoke:
def pytest_exception_interact(self, node, call, report):
capman = node.config.pluginmanager.getplugin("capturemanager")
if capman:
capman.suspendcapture(in_=True)
out, err = capman.suspendcapture(in_=True)
sys.stdout.write(out)
sys.stdout.write(err)
_enter_pdb(node, call.excinfo, report)
def pytest_internalerror(self, excrepr, excinfo):

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,16 @@
""" recording warnings during test function execution. """
import inspect
import _pytest._code
import py
import sys
import warnings
import pytest
def pytest_funcarg__recwarn(request):
@pytest.yield_fixture
def recwarn(request):
"""Return a WarningsRecorder instance that provides these methods:
* ``pop(category=None)``: return last warning matching the category.
@@ -13,83 +19,203 @@ def pytest_funcarg__recwarn(request):
See http://docs.python.org/library/warnings.html for information
on warning categories.
"""
if sys.version_info >= (2,7):
oldfilters = warnings.filters[:]
warnings.simplefilter('default')
def reset_filters():
warnings.filters[:] = oldfilters
request.addfinalizer(reset_filters)
wrec = WarningsRecorder()
request.addfinalizer(wrec.finalize)
return wrec
with wrec:
warnings.simplefilter('default')
yield wrec
def pytest_namespace():
return {'deprecated_call': deprecated_call}
return {'deprecated_call': deprecated_call,
'warns': warns}
def deprecated_call(func, *args, **kwargs):
""" assert that calling ``func(*args, **kwargs)``
triggers a DeprecationWarning.
def deprecated_call(func=None, *args, **kwargs):
""" assert that calling ``func(*args, **kwargs)`` triggers a
``DeprecationWarning`` or ``PendingDeprecationWarning``.
This function can be used as a context manager::
>>> with deprecated_call():
... myobject.deprecated_method()
Note: we cannot use WarningsRecorder here because it is still subject
to the mechanism that prevents warnings of the same type from being
triggered twice for the same module. See #1190.
"""
l = []
oldwarn_explicit = getattr(warnings, 'warn_explicit')
def warn_explicit(*args, **kwargs):
l.append(args)
oldwarn_explicit(*args, **kwargs)
oldwarn = getattr(warnings, 'warn')
def warn(*args, **kwargs):
l.append(args)
oldwarn(*args, **kwargs)
if not func:
return WarningsChecker(expected_warning=DeprecationWarning)
categories = []
def warn_explicit(message, category, *args, **kwargs):
categories.append(category)
old_warn_explicit(message, category, *args, **kwargs)
def warn(message, category=None, *args, **kwargs):
if isinstance(message, Warning):
categories.append(message.__class__)
else:
categories.append(category)
old_warn(message, category, *args, **kwargs)
old_warn = warnings.warn
old_warn_explicit = warnings.warn_explicit
warnings.warn_explicit = warn_explicit
warnings.warn = warn
try:
ret = func(*args, **kwargs)
finally:
warnings.warn_explicit = warn_explicit
warnings.warn = warn
if not l:
warnings.warn_explicit = old_warn_explicit
warnings.warn = old_warn
deprecation_categories = (DeprecationWarning, PendingDeprecationWarning)
if not any(issubclass(c, deprecation_categories) for c in categories):
__tracebackhide__ = True
raise AssertionError("%r did not produce DeprecationWarning" %(func,))
raise AssertionError("%r did not produce DeprecationWarning" % (func,))
return ret
class RecordedWarning:
def __init__(self, message, category, filename, lineno, line):
def warns(expected_warning, *args, **kwargs):
"""Assert that code raises a particular class of warning.
Specifically, the input @expected_warning can be a warning class or
tuple of warning classes, and the code must return that warning
(if a single class) or one of those warnings (if a tuple).
This helper produces a list of ``warnings.WarningMessage`` objects,
one for each warning raised.
This function can be used as a context manager, or any of the other ways
``pytest.raises`` can be used::
>>> with warns(RuntimeWarning):
... warnings.warn("my warning", RuntimeWarning)
"""
wcheck = WarningsChecker(expected_warning)
if not args:
return wcheck
elif isinstance(args[0], str):
code, = args
assert isinstance(code, str)
frame = sys._getframe(1)
loc = frame.f_locals.copy()
loc.update(kwargs)
with wcheck:
code = _pytest._code.Source(code).compile()
py.builtin.exec_(code, frame.f_globals, loc)
else:
func = args[0]
with wcheck:
return func(*args[1:], **kwargs)
class RecordedWarning(object):
def __init__(self, message, category, filename, lineno, file, line):
self.message = message
self.category = category
self.filename = filename
self.lineno = lineno
self.file = file
self.line = line
class WarningsRecorder:
def __init__(self):
self.list = []
def showwarning(message, category, filename, lineno, line=0):
self.list.append(RecordedWarning(
message, category, filename, lineno, line))
try:
self.old_showwarning(message, category,
filename, lineno, line=line)
except TypeError:
# < python2.6
self.old_showwarning(message, category, filename, lineno)
self.old_showwarning = warnings.showwarning
warnings.showwarning = showwarning
class WarningsRecorder(object):
"""A context manager to record raised warnings.
Adapted from `warnings.catch_warnings`.
"""
def __init__(self, module=None):
self._module = sys.modules['warnings'] if module is None else module
self._entered = False
self._list = []
@property
def list(self):
"""The list of recorded warnings."""
return self._list
def __getitem__(self, i):
"""Get a recorded warning by index."""
return self._list[i]
def __iter__(self):
"""Iterate through the recorded warnings."""
return iter(self._list)
def __len__(self):
"""The number of recorded warnings."""
return len(self._list)
def pop(self, cls=Warning):
""" pop the first recorded warning, raise exception if not exists."""
for i, w in enumerate(self.list):
"""Pop the first recorded warning, raise exception if not exists."""
for i, w in enumerate(self._list):
if issubclass(w.category, cls):
return self.list.pop(i)
return self._list.pop(i)
__tracebackhide__ = True
assert 0, "%r not found in %r" %(cls, self.list)
#def resetregistry(self):
# warnings.onceregistry.clear()
# warnings.__warningregistry__.clear()
raise AssertionError("%r not found in warning list" % cls)
def clear(self):
self.list[:] = []
"""Clear the list of recorded warnings."""
self._list[:] = []
def finalize(self):
warnings.showwarning = self.old_showwarning
def __enter__(self):
if self._entered:
__tracebackhide__ = True
raise RuntimeError("Cannot enter %r twice" % self)
self._entered = True
self._filters = self._module.filters
self._module.filters = self._filters[:]
self._showwarning = self._module.showwarning
def showwarning(message, category, filename, lineno,
file=None, line=None):
self._list.append(RecordedWarning(
message, category, filename, lineno, file, line))
# still perform old showwarning functionality
self._showwarning(
message, category, filename, lineno, file=file, line=line)
self._module.showwarning = showwarning
# allow the same warning to be raised more than once
self._module.simplefilter('always')
return self
def __exit__(self, *exc_info):
if not self._entered:
__tracebackhide__ = True
raise RuntimeError("Cannot exit %r without entering first" % self)
self._module.filters = self._filters
self._module.showwarning = self._showwarning
class WarningsChecker(WarningsRecorder):
def __init__(self, expected_warning=None, module=None):
super(WarningsChecker, self).__init__(module=module)
msg = ("exceptions must be old-style classes or "
"derived from Warning, not %s")
if isinstance(expected_warning, tuple):
for exc in expected_warning:
if not inspect.isclass(exc):
raise TypeError(msg % type(exc))
elif inspect.isclass(expected_warning):
expected_warning = (expected_warning,)
elif expected_warning is not None:
raise TypeError(msg % type(expected_warning))
self.expected_warning = expected_warning
def __exit__(self, *exc_info):
super(WarningsChecker, self).__exit__(*exc_info)
# only check if we're not currently handling an exception
if all(a is None for a in exc_info):
if self.expected_warning is not None:
if not any(r.category in self.expected_warning for r in self):
__tracebackhide__ = True
pytest.fail("DID NOT WARN")

View File

@@ -3,6 +3,7 @@ text file.
"""
import py
import os
def pytest_addoption(parser):
group = parser.getgroup("terminal reporting", "resultlog plugin options")
@@ -14,6 +15,9 @@ def pytest_configure(config):
resultlog = config.option.resultlog
# prevent opening resultlog on slave nodes (xdist)
if resultlog and not hasattr(config, 'slaveinput'):
dirname = os.path.dirname(os.path.abspath(resultlog))
if not os.path.isdir(dirname):
os.makedirs(dirname)
logfile = open(resultlog, 'w', 1) # line buffered
config._resultlog = ResultLog(config, logfile)
config.pluginmanager.register(config._resultlog)

View File

@@ -5,7 +5,8 @@ from time import time
import py
import pytest
from py._code.code import TerminalRepr
from _pytest._code.code import TerminalRepr, ExceptionInfo
def pytest_namespace():
return {
@@ -86,7 +87,17 @@ def pytest_runtest_setup(item):
item.session._setupstate.prepare(item)
def pytest_runtest_call(item):
item.runtest()
try:
item.runtest()
except Exception:
# Store trace info to allow postmortem debugging
type, value, tb = sys.exc_info()
tb = tb.tb_next # Skip *this* frame
sys.last_type = type
sys.last_value = value
sys.last_traceback = tb
del tb # Get rid of it in this namespace
raise
def pytest_runtest_teardown(item, nextitem):
item.session._setupstate.teardown_exact(item, nextitem)
@@ -141,7 +152,7 @@ class CallInfo:
self.stop = time()
raise
except:
self.excinfo = py.code.ExceptionInfo()
self.excinfo = ExceptionInfo()
self.stop = time()
def __repr__(self):
@@ -167,9 +178,13 @@ class BaseReport(object):
self.__dict__.update(kw)
def toterminal(self, out):
longrepr = self.longrepr
if hasattr(self, 'node'):
out.line(getslaveinfoline(self.node))
longrepr = self.longrepr
if longrepr is None:
return
if hasattr(longrepr, 'toterminal'):
longrepr.toterminal(out)
else:
@@ -201,7 +216,7 @@ def pytest_runtest_makereport(item, call):
outcome = "passed"
longrepr = None
else:
if not isinstance(excinfo, py.code.ExceptionInfo):
if not isinstance(excinfo, ExceptionInfo):
outcome = "failed"
longrepr = excinfo
elif excinfo.errisinstance(pytest.skip.Exception):
@@ -216,7 +231,7 @@ def pytest_runtest_makereport(item, call):
longrepr = item._repr_failure_py(excinfo,
style=item.config.option.tbstyle)
for rwhen, key, content in item._report_sections:
sections.append(("Captured std%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)
@@ -420,7 +435,10 @@ class OutcomeException(Exception):
def __repr__(self):
if self.msg:
return str(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__
@@ -459,7 +477,7 @@ def skip(msg=""):
skip.Exception = Skipped
def fail(msg="", pytrace=True):
""" explicitely fail an currently-executing test with the given Message.
""" 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.
@@ -473,8 +491,6 @@ 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.
Note that version comparison only works with simple version strings
like "1.2.3" but not "1.2.3.dev1" or others.
"""
__tracebackhide__ = True
compile(modname, '', 'eval') # to catch syntaxerrors
@@ -486,9 +502,14 @@ def importorskip(modname, minversion=None):
if minversion is None:
return mod
verattr = getattr(mod, '__version__', None)
def intver(verstring):
return [int(x) for x in verstring.split(".")]
if verattr is None or intver(verattr) < intver(minversion):
skip("module %r has __version__ %r, required is: %r" %(
modname, verattr, minversion))
if minversion is not None:
try:
from pkg_resources import parse_version as pv
except ImportError:
skip("we have a required version for %r but can not import "
"no pkg_resources to parse version strings." %(modname,))
if verattr is None or pv(verattr) < pv(minversion):
skip("module %r has __version__ %r, required is: %r" %(
modname, verattr, minversion))
return mod

View File

@@ -5,6 +5,8 @@ import traceback
import py
import pytest
from _pytest.mark import MarkInfo, MarkDecorator
def pytest_addoption(parser):
group = parser.getgroup("general")
@@ -12,6 +14,13 @@ def pytest_addoption(parser):
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: "
"False)",
default=False,
type="bool")
def pytest_configure(config):
if config.option.runxfail:
old = pytest.xfail
@@ -21,6 +30,11 @@ def pytest_configure(config):
nop.Exception = XFailed
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."
)
config.addinivalue_line("markers",
"skipif(condition): skip the given test function if eval(condition) "
"results in a True value. Evaluation happens within the "
@@ -29,27 +43,31 @@ def pytest_configure(config):
"http://pytest.org/latest/skipping.html"
)
config.addinivalue_line("markers",
"xfail(condition, reason=None, run=True, raises=None): mark the 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"
"xfail(condition, reason=None, run=True, raises=None, strict=False): "
"mark the 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"
)
def pytest_namespace():
return dict(xfail=xfail)
class XFailed(pytest.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
class MarkEvaluator:
def __init__(self, item, name):
self.item = item
@@ -57,7 +75,7 @@ class MarkEvaluator:
@property
def holder(self):
return self.item.keywords.get(self.name, None)
return self.item.keywords.get(self.name)
def __bool__(self):
return bool(self.holder)
@@ -75,9 +93,7 @@ class MarkEvaluator:
def istrue(self):
try:
return self._istrue()
except KeyboardInterrupt:
raise
except:
except Exception:
self.exc = sys.exc_info()
if isinstance(self.exc[1], SyntaxError):
msg = [" " * (self.exc[1].offset + 4) + "^",]
@@ -100,24 +116,38 @@ class MarkEvaluator:
return d
def _istrue(self):
if hasattr(self, 'result'):
return self.result
if self.holder:
d = self._getglobals()
if self.holder.args:
if self.holder.args or 'condition' in self.holder.kwargs:
self.result = False
for expr in self.holder.args:
self.expr = expr
if isinstance(expr, py.builtin._basestring):
result = cached_eval(self.item.config, expr, d)
else:
if self.get("reason") is None:
# XXX better be checked at collection time
pytest.fail("you need to specify reason=STRING "
"when using booleans as conditions.")
result = bool(expr)
if result:
self.result = True
# "holder" might be a MarkInfo or a MarkDecorator; only
# MarkInfo keeps track of all parameters it received in an
# _arglist attribute
if hasattr(self.holder, '_arglist'):
arglist = self.holder._arglist
else:
arglist = [(self.holder.args, self.holder.kwargs)]
for args, kwargs in arglist:
if 'condition' in kwargs:
args = (kwargs['condition'],)
for expr in args:
self.expr = expr
break
if isinstance(expr, py.builtin._basestring):
result = cached_eval(self.item.config, expr, d)
else:
if "reason" not in kwargs:
# XXX better be checked at collection time
msg = "you need to specify reason=STRING " \
"when using booleans as conditions."
pytest.fail(msg)
result = bool(expr)
if result:
self.result = True
self.reason = kwargs.get('reason', None)
self.expr = expr
return self.result
else:
self.result = True
return getattr(self, 'result', False)
@@ -126,7 +156,7 @@ class MarkEvaluator:
return self.holder.kwargs.get(attr, default)
def getexplanation(self):
expl = self.get('reason', None)
expl = getattr(self, 'reason', None) or self.get('reason', None)
if not expl:
if not hasattr(self, 'expr'):
return ""
@@ -135,62 +165,95 @@ class MarkEvaluator:
return expl
@pytest.mark.tryfirst
@pytest.hookimpl(tryfirst=True)
def pytest_runtest_setup(item):
evalskip = MarkEvaluator(item, 'skipif')
if evalskip.istrue():
pytest.skip(evalskip.getexplanation())
# Check if skip or skipif are specified as pytest marks
skipif_info = item.keywords.get('skipif')
if isinstance(skipif_info, (MarkInfo, MarkDecorator)):
eval_skipif = MarkEvaluator(item, 'skipif')
if eval_skipif.istrue():
item._evalskip = eval_skipif
pytest.skip(eval_skipif.getexplanation())
skip_info = item.keywords.get('skip')
if isinstance(skip_info, (MarkInfo, MarkDecorator)):
item._evalskip = True
if 'reason' in skip_info.kwargs:
pytest.skip(skip_info.kwargs['reason'])
elif skip_info.args:
pytest.skip(skip_info.args[0])
else:
pytest.skip("unconditional skip")
item._evalxfail = MarkEvaluator(item, 'xfail')
check_xfail_no_run(item)
@pytest.mark.hookwrapper
def pytest_pyfunc_call(pyfuncitem):
check_xfail_no_run(pyfuncitem)
outcome = yield
passed = outcome.excinfo is None
if passed:
check_strict_xfail(pyfuncitem)
def check_xfail_no_run(item):
"""check xfail(run=False)"""
if not item.config.option.runxfail:
evalxfail = item._evalxfail
if evalxfail.istrue():
if not evalxfail.get('run', True):
pytest.xfail("[NOTRUN] " + evalxfail.getexplanation())
def pytest_runtest_makereport(__multicall__, item, call):
def check_strict_xfail(pyfuncitem):
"""check xfail(strict=True) for the given PASSING test"""
evalxfail = pyfuncitem._evalxfail
if evalxfail.istrue():
strict_default = pyfuncitem.config.getini('xfail_strict')
is_strict_xfail = evalxfail.get('strict', strict_default)
if is_strict_xfail:
del pyfuncitem._evalxfail
explanation = evalxfail.getexplanation()
pytest.fail('[XPASS(strict)] ' + explanation, pytrace=False)
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call):
outcome = yield
rep = outcome.get_result()
evalxfail = getattr(item, '_evalxfail', None)
evalskip = getattr(item, '_evalskip', None)
# unitttest special case, see setting of _unexpectedsuccess
if hasattr(item, '_unexpectedsuccess'):
rep = __multicall__.execute()
if rep.when == "call":
# we need to translate into how pytest encodes xpass
rep.wasxfail = "reason: " + repr(item._unexpectedsuccess)
rep.outcome = "failed"
return rep
if not (call.excinfo and
call.excinfo.errisinstance(pytest.xfail.Exception)):
evalxfail = getattr(item, '_evalxfail', None)
if not evalxfail:
return
if call.excinfo and call.excinfo.errisinstance(pytest.xfail.Exception):
if not item.config.getvalue("runxfail"):
rep = __multicall__.execute()
rep.wasxfail = "reason: " + call.excinfo.value.msg
rep.outcome = "skipped"
return rep
rep = __multicall__.execute()
evalxfail = item._evalxfail
if not rep.skipped:
if not item.config.option.runxfail:
if evalxfail.wasvalid() and evalxfail.istrue():
if call.excinfo:
if evalxfail.invalidraise(call.excinfo.value):
rep.outcome = "failed"
return rep
else:
rep.outcome = "skipped"
elif call.when == "call":
rep.outcome = "failed"
else:
return rep
if hasattr(item, '_unexpectedsuccess') and rep.when == "call":
# we need to translate into how pytest encodes xpass
rep.wasxfail = "reason: " + repr(item._unexpectedsuccess)
rep.outcome = "failed"
elif item.config.option.runxfail:
pass # don't interefere
elif call.excinfo and call.excinfo.errisinstance(pytest.xfail.Exception):
rep.wasxfail = "reason: " + call.excinfo.value.msg
rep.outcome = "skipped"
elif evalxfail and not rep.skipped and evalxfail.wasvalid() and \
evalxfail.istrue():
if call.excinfo:
if evalxfail.invalidraise(call.excinfo.value):
rep.outcome = "failed"
else:
rep.outcome = "skipped"
rep.wasxfail = evalxfail.getexplanation()
return rep
return rep
elif call.when == "call":
rep.outcome = "failed" # xpass outcome
rep.wasxfail = evalxfail.getexplanation()
elif evalskip is not None and rep.skipped and type(rep.longrepr) is tuple:
# skipped by mark.skipif; change the location of the failure
# to point to the item definition, otherwise it will display
# the location of where the skip exception was raised within pytest
filename, line, reason = rep.longrepr
filename, line = item.location[:2]
rep.longrepr = filename, line, reason
# called by terminalreporter progress reporting
def pytest_report_teststatus(report):
@@ -223,6 +286,9 @@ def pytest_terminal_summary(terminalreporter):
show_skipped(terminalreporter, lines)
elif char == "E":
show_simple(terminalreporter, lines, 'error', "ERROR %s")
elif char == 'p':
show_simple(terminalreporter, lines, 'passed', "PASSED %s")
if lines:
tr._tw.sep("=", "short test summary info")
for line in lines:
@@ -232,14 +298,14 @@ def show_simple(terminalreporter, lines, stat, format):
failed = terminalreporter.stats.get(stat)
if failed:
for rep in failed:
pos = rep.nodeid
lines.append(format %(pos, ))
pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid)
lines.append(format %(pos,))
def show_xfailed(terminalreporter, lines):
xfailed = terminalreporter.stats.get("xfailed")
if xfailed:
for rep in xfailed:
pos = rep.nodeid
pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid)
reason = rep.wasxfail
lines.append("XFAIL %s" % (pos,))
if reason:
@@ -249,7 +315,7 @@ def show_xpassed(terminalreporter, lines):
xpassed = terminalreporter.stats.get("xpassed")
if xpassed:
for rep in xpassed:
pos = rep.nodeid
pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid)
reason = rep.wasxfail
lines.append("XPASS %s %s" %(pos, reason))
@@ -259,9 +325,8 @@ def cached_eval(config, expr, d):
try:
return config._evalcache[expr]
except KeyError:
#import sys
#print >>sys.stderr, ("cache-miss: %r" % expr)
exprcode = py.code.compile(expr, mode="eval")
import _pytest._code
exprcode = _pytest._code.compile(expr, mode="eval")
config._evalcache[expr] = x = eval(exprcode, d)
return x

View File

@@ -1,5 +1,24 @@
#! /usr/bin/env python
# Hi There!
# You may be wondering what this giant blob of binary data here is, you might
# even be worried that we're up to something nefarious (good for you for being
# paranoid!). This is a base64 encoding of a zip file, this zip file contains
# a fully functional basic pytest script.
#
# Pytest is a thing that tests packages, pytest itself is a package that some-
# one might want to install, especially if they're looking to run tests inside
# some package they want to install. Pytest has a lot of code to collect and
# execute tests, and other such sort of "tribal knowledge" that has been en-
# coded in its code base. Because of this we basically include a basic copy
# of pytest inside this blob. We do this because it let's you as a maintainer
# or application developer who wants people who don't deal with python much to
# easily run tests without installing the complete pytest package.
#
# If you're wondering how this is created: you can create it yourself if you
# have a complete pytest installation by using this command on the command-
# line: ``py.test --genscript=runtests.py``.
sources = """
@SOURCES@"""
@@ -49,6 +68,11 @@ class DictImporter(object):
return res
if __name__ == "__main__":
try:
import pkg_resources # noqa
except ImportError:
sys.stderr.write("ERROR: setuptools not installed\n")
sys.exit(2)
if sys.version_info >= (3, 0):
exec("def do_exec(co, loc): exec(co, loc)\n")
import pickle
@@ -61,6 +85,5 @@ if __name__ == "__main__":
importer = DictImporter(sources)
sys.meta_path.insert(0, importer)
entry = "@ENTRY@"
do_exec(entry, locals()) # noqa

View File

@@ -2,10 +2,15 @@
This is a good source for looking at the various reporting hooks.
"""
from _pytest.main import EXIT_OK, EXIT_TESTSFAILED, EXIT_INTERRUPTED, \
EXIT_USAGEERROR, EXIT_NOTESTSCOLLECTED
import pytest
import py
import sys
import time
import platform
import _pytest._pluggy as pluggy
def pytest_addoption(parser):
@@ -17,7 +22,8 @@ def pytest_addoption(parser):
group._addoption('-r',
action="store", dest="reportchars", default=None, metavar="chars",
help="show extra test summary info as specified by chars (f)ailed, "
"(E)error, (s)skipped, (x)failed, (X)passed (w)warnings.")
"(E)error, (s)skipped, (x)failed, (X)passed (w)pytest-warnings "
"(p)passed, (P)passed with output, (a)all except pP.")
group._addoption('-l', '--showlocals',
action="store_true", dest="showlocals", default=False,
help="show locals in tracebacks (disabled by default).")
@@ -27,7 +33,7 @@ def pytest_addoption(parser):
group._addoption('--tb', metavar="style",
action="store", dest="tbstyle", default='auto',
choices=['auto', 'long', 'short', 'no', 'line', 'native'],
help="traceback print mode (long/short/line/native/no).")
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).")
@@ -62,8 +68,10 @@ def getreportopt(config):
reportchars = config.option.reportchars
if reportchars:
for char in reportchars:
if char not in reportopts:
if char not in reportopts and char != 'a':
reportopts += char
elif char == 'a':
reportopts = 'fEsxXw'
return reportopts
def pytest_report_teststatus(report):
@@ -87,6 +95,7 @@ class WarningReport:
class TerminalReporter:
def __init__(self, config, file=None):
import _pytest.config
self.config = config
self.verbosity = self.config.option.verbose
self.showheader = self.verbosity >= 0
@@ -95,28 +104,26 @@ class TerminalReporter:
self._numcollected = 0
self.stats = {}
self.startdir = self.curdir = py.path.local()
self.startdir = py.path.local()
if file is None:
file = sys.stdout
self._tw = self.writer = py.io.TerminalWriter(file)
if self.config.option.color == 'yes':
self._tw.hasmarkup = True
if self.config.option.color == 'no':
self._tw.hasmarkup = False
self._tw = self.writer = _pytest.config.create_terminal_writer(config,
file)
self.currentfspath = None
self.reportchars = getreportopt(config)
self.hasmarkup = self._tw.hasmarkup
self.isatty = file.isatty()
def hasopt(self, char):
char = {'xfailed': 'x', 'skipped': 's'}.get(char, char)
return char in self.reportchars
def write_fspath_result(self, fspath, res):
def write_fspath_result(self, nodeid, res):
fspath = self.config.rootdir.join(nodeid.split("::")[0])
if fspath != self.currentfspath:
self.currentfspath = fspath
#fspath = self.startdir.bestrelpath(fspath)
fspath = self.startdir.bestrelpath(fspath)
self._tw.line()
#relpath = self.startdir.bestrelpath(fspath)
self._tw.write(fspath + " ")
self._tw.write(res)
@@ -164,6 +171,8 @@ class TerminalReporter:
def pytest_logwarning(self, code, fslocation, message, nodeid):
warnings = self.stats.setdefault("warnings", [])
if isinstance(fslocation, tuple):
fslocation = "%s:%d" % fslocation
warning = WarningReport(code=code, fslocation=fslocation,
message=message, nodeid=nodeid)
warnings.append(warning)
@@ -182,12 +191,12 @@ class TerminalReporter:
def pytest_runtest_logstart(self, nodeid, location):
# ensure that the path is printed before the
# 1st test of a module starts running
fspath = nodeid.split("::")[0]
if self.showlongtestinfo:
line = self._locationline(fspath, *location)
line = self._locationline(nodeid, *location)
self.write_ensure_prefix(line, "")
elif self.showfspath:
self.write_fspath_result(fspath, "")
fsid = nodeid.split("::")[0]
self.write_fspath_result(fsid, "")
def pytest_runtest_logreport(self, report):
rep = report
@@ -200,7 +209,7 @@ class TerminalReporter:
return
if self.verbosity <= 0:
if not hasattr(rep, 'node') and self.showfspath:
self.write_fspath_result(rep.fspath, letter)
self.write_fspath_result(rep.nodeid, letter)
else:
self._tw.write(letter)
else:
@@ -213,7 +222,7 @@ class TerminalReporter:
markup = {'red':True}
elif rep.skipped:
markup = {'yellow':True}
line = self._locationline(str(rep.fspath), *rep.location)
line = self._locationline(rep.nodeid, *rep.location)
if not hasattr(rep, 'node'):
self.write_ensure_prefix(line, word, **markup)
#self._tw.write(word, **markup)
@@ -226,7 +235,7 @@ class TerminalReporter:
self.currentfspath = -2
def pytest_collection(self):
if not self.hasmarkup and self.config.option.verbose >= 1:
if not self.isatty and self.config.option.verbose >= 1:
self.write("collecting ... ", bold=True)
def pytest_collectreport(self, report):
@@ -236,8 +245,8 @@ class TerminalReporter:
self.stats.setdefault("skipped", []).append(report)
items = [x for x in report.result if isinstance(x, pytest.Item)]
self._numcollected += len(items)
if self.hasmarkup:
#self.write_fspath_result(report.fspath, 'E')
if self.isatty:
#self.write_fspath_result(report.nodeid, 'E')
self.report_collect()
def report_collect(self, final=False):
@@ -255,7 +264,7 @@ class TerminalReporter:
line += " / %d errors" % errors
if skipped:
line += " / %d skipped" % skipped
if self.hasmarkup:
if self.isatty:
if final:
line += " \n"
self.rewrite(line, bold=True)
@@ -265,18 +274,19 @@ class TerminalReporter:
def pytest_collection_modifyitems(self):
self.report_collect(True)
@pytest.mark.trylast
@pytest.hookimpl(trylast=True)
def pytest_sessionstart(self, session):
self._sessionstarttime = time.time()
if not self.showheader:
return
self.write_sep("=", "test session starts", bold=True)
verinfo = ".".join(map(str, sys.version_info[:3]))
verinfo = platform.python_version()
msg = "platform %s -- Python %s" % (sys.platform, verinfo)
if hasattr(sys, 'pypy_version_info'):
verinfo = ".".join(map(str, sys.pypy_version_info[:3]))
msg += "[pypy-%s-%s]" % (verinfo, sys.pypy_version_info[3])
msg += " -- py-%s -- pytest-%s" % (py.__version__, pytest.__version__)
msg += ", pytest-%s, py-%s, pluggy-%s" % (
pytest.__version__, py.__version__, pluggy.__version__)
if self.verbosity > 0 or self.config.option.debug or \
getattr(self.config.option, 'pastebin', None):
msg += " -- " + str(sys.executable)
@@ -288,15 +298,17 @@ class TerminalReporter:
self.write_line(line)
def pytest_report_header(self, config):
plugininfo = config.pluginmanager._plugin_distinfo
inifile = ""
if config.inifile:
inifile = config.rootdir.bestrelpath(config.inifile)
lines = ["rootdir: %s, inifile: %s" %(config.rootdir, inifile)]
plugininfo = config.pluginmanager.list_plugin_distinfo()
if plugininfo:
l = []
for dist, plugin in plugininfo:
name = dist.project_name
if name.startswith("pytest-"):
name = name[7:]
l.append(name)
return "plugins: %s" % ", ".join(l)
lines.append(
"plugins: %s" % ", ".join(_plugin_nameversions(plugininfo)))
return lines
def pytest_collection_finish(self, session):
if self.config.option.collectonly:
@@ -345,15 +357,21 @@ class TerminalReporter:
indent = (len(stack) - 1) * " "
self._tw.line("%s%s" % (indent, col))
def pytest_sessionfinish(self, exitstatus, __multicall__):
__multicall__.execute()
@pytest.hookimpl(hookwrapper=True)
def pytest_sessionfinish(self, exitstatus):
outcome = yield
outcome.get_result()
self._tw.line("")
if exitstatus in (0, 1, 2, 4):
summary_exit_codes = (
EXIT_OK, EXIT_TESTSFAILED, EXIT_INTERRUPTED, EXIT_USAGEERROR,
EXIT_NOTESTSCOLLECTED)
if exitstatus in summary_exit_codes:
self.config.hook.pytest_terminal_summary(terminalreporter=self)
self.summary_errors()
self.summary_failures()
self.summary_warnings()
self.config.hook.pytest_terminal_summary(terminalreporter=self)
if exitstatus == 2:
self.summary_passes()
if exitstatus == EXIT_INTERRUPTED:
self._report_keyboardinterrupt()
del self._keyboardinterrupt_memo
self.summary_deselected()
@@ -374,21 +392,27 @@ class TerminalReporter:
if self.config.option.fulltrace:
excrepr.toterminal(self._tw)
else:
self._tw.line("to show a full traceback on KeyboardInterrupt use --fulltrace", yellow=True)
excrepr.reprcrash.toterminal(self._tw)
def _locationline(self, collect_fspath, fspath, lineno, domain):
def _locationline(self, nodeid, fspath, lineno, domain):
def mkrel(nodeid):
line = self.config.cwd_relative_nodeid(nodeid)
if domain and line.endswith(domain):
line = line[:-len(domain)]
l = domain.split("[")
l[0] = l[0].replace('.', '::') # don't replace '.' in params
line += "[".join(l)
return line
# collect_fspath comes from testid which has a "/"-normalized path
if fspath and fspath.replace("\\", "/") != collect_fspath:
fspath = "%s <- %s" % (collect_fspath, fspath)
if fspath:
line = str(fspath)
if domain:
split = str(domain).split('[')
split[0] = split[0].replace('.', '::') # don't replace '.' in params
line += "::" + '['.join(split)
res = mkrel(nodeid).replace("::()", "") # parens-normalization
if nodeid.split("::")[0] != fspath.replace("\\", "/"):
res += " <- " + self.startdir.bestrelpath(fspath)
else:
line = "[location]"
return line + " "
res = "[location]"
return res + " "
def _getfailureheadline(self, rep):
if hasattr(rep, 'location'):
@@ -421,11 +445,23 @@ class TerminalReporter:
warnings = self.stats.get("warnings")
if not warnings:
return
self.write_sep("=", "warning summary")
self.write_sep("=", "pytest-warning summary")
for w in warnings:
self._tw.line("W%s %s %s" % (w.code,
w.fslocation, w.message))
def summary_passes(self):
if self.config.option.tbstyle != "no":
if self.hasopt("P"):
reports = self.getreports('passed')
if not reports:
return
self.write_sep("=", "PASSES")
for rep in reports:
msg = self._getfailureheadline(rep)
self.write_sep("_", msg)
self._outrep_summary(rep)
def summary_failures(self):
if self.config.option.tbstyle != "no":
reports = self.getreports('failed')
@@ -438,7 +474,8 @@ class TerminalReporter:
self.write_line(line)
else:
msg = self._getfailureheadline(rep)
self.write_sep("_", msg)
markup = {'red': True, 'bold': True}
self.write_sep("_", msg, **markup)
self._outrep_summary(rep)
def summary_errors(self):
@@ -469,26 +506,9 @@ class TerminalReporter:
def summary_stats(self):
session_duration = time.time() - self._sessionstarttime
keys = ("failed passed skipped deselected "
"xfailed xpassed warnings").split()
for key in self.stats.keys():
if key not in keys:
keys.append(key)
parts = []
for key in keys:
if key: # setup/teardown reports have an empty key, ignore them
val = self.stats.get(key, None)
if val:
parts.append("%d %s" % (len(val), key))
line = ", ".join(parts)
(line, color) = build_summary_stats_line(self.stats)
msg = "%s in %.2f seconds" % (line, session_duration)
markup = {'bold': True}
if 'failed' in self.stats or 'error' in self.stats:
markup = {'red': True, 'bold': True}
else:
markup = {'green': True, 'bold': True}
markup = {color: True, 'bold': True}
if self.verbosity >= 0:
self.write_sep("=", msg, **markup)
@@ -524,3 +544,50 @@ def flatten(l):
else:
yield x
def build_summary_stats_line(stats):
keys = ("failed passed skipped deselected "
"xfailed xpassed warnings error").split()
key_translation = {'warnings': 'pytest-warnings'}
unknown_key_seen = False
for key in stats.keys():
if key not in keys:
if key: # setup/teardown reports have an empty key, ignore them
keys.append(key)
unknown_key_seen = True
parts = []
for key in keys:
val = stats.get(key, None)
if val:
key_name = key_translation.get(key, key)
parts.append("%d %s" % (len(val), key_name))
if parts:
line = ", ".join(parts)
else:
line = "no tests ran"
if 'failed' in stats or 'error' in stats:
color = 'red'
elif 'warnings' in stats or unknown_key_seen:
color = 'yellow'
elif 'passed' in stats:
color = 'green'
else:
color = 'yellow'
return (line, color)
def _plugin_nameversions(plugininfo):
l = []
for plugin, dist in plugininfo:
# gets us name and version!
name = '{dist.project_name}-{dist.version}'.format(dist=dist)
# questionable convenience, but it keeps things short
if name.startswith("pytest-"):
name = name[7:]
# we decided to print python package names
# they can have more than one plugin
if name not in l:
l.append(name)
return l

View File

@@ -6,7 +6,12 @@ import py
from _pytest.monkeypatch import monkeypatch
class TempdirHandler:
class TempdirFactory:
"""Factory for temporary directories under the common base temp directory.
The base directory can be configured using the ``--basetemp`` option.
"""
def __init__(self, config):
self.config = config
self.trace = config.trace.get("tmpdir")
@@ -22,6 +27,10 @@ class TempdirHandler:
return self.getbasetemp().ensure(string, dir=dir)
def mktemp(self, basename, numbered=True):
"""Create a subdirectory of the base temporary directory and return it.
If ``numbered``, ensure the directory is unique by adding a number
prefix greater than any existing one.
"""
basetemp = self.getbasetemp()
if not numbered:
p = basetemp.mkdir(basename)
@@ -43,7 +52,17 @@ class TempdirHandler:
basetemp.remove()
basetemp.mkdir()
else:
basetemp = py.path.local.make_numbered_dir(prefix='pytest-')
temproot = py.path.local.get_temproot()
user = get_user()
if user:
# use a sub-directory in the temproot to speed-up
# make_numbered_dir() call
rootdir = temproot.join('pytest-of-%s' % user)
else:
rootdir = temproot
rootdir.ensure(dir=1)
basetemp = py.path.local.make_numbered_dir(prefix='pytest-',
rootdir=rootdir)
self._basetemp = t = basetemp.realpath()
self.trace("new basetemp", t)
return t
@@ -51,15 +70,44 @@ class TempdirHandler:
def finish(self):
self.trace("finish")
def get_user():
"""Return the current user name, or None if getuser() does not work
in the current environment (see #1010).
"""
import getpass
try:
return getpass.getuser()
except (ImportError, KeyError):
return None
# backward compatibility
TempdirHandler = TempdirFactory
def pytest_configure(config):
"""Create a TempdirFactory and attach it to the config object.
This is to comply with existing plugins which expect the handler to be
available at pytest_configure time, but ideally should be moved entirely
to the tmpdir_factory session fixture.
"""
mp = monkeypatch()
t = TempdirHandler(config)
t = TempdirFactory(config)
config._cleanup.extend([mp.undo, t.finish])
mp.setattr(config, '_tmpdirhandler', t, raising=False)
mp.setattr(pytest, 'ensuretemp', t.ensuretemp, raising=False)
@pytest.fixture(scope='session')
def tmpdir_factory(request):
"""Return a TempdirFactory instance for the test session.
"""
return request.config._tmpdirhandler
@pytest.fixture
def tmpdir(request):
def tmpdir(request, tmpdir_factory):
"""return a temporary directory path object
which is unique to each test function invocation,
created as a sub directory of the base temporary
@@ -71,5 +119,5 @@ def tmpdir(request):
MAXVAL = 30
if len(name) > MAXVAL:
name = name[:MAXVAL]
x = request.config._tmpdirhandler.mktemp(name, numbered=True)
x = tmpdir_factory.mktemp(name, numbered=True)
return x

View File

@@ -1,14 +1,14 @@
""" discovery and running of std-library "unittest" style tests. """
from __future__ import absolute_import
import traceback
import sys
import traceback
import pytest
import py
# for transfering markers
import _pytest._code
from _pytest.python import transfer_markers
from _pytest.skipping import MarkEvaluator
def pytest_pycollect_makeitem(collector, name, obj):
@@ -23,9 +23,10 @@ def pytest_pycollect_makeitem(collector, name, obj):
class UnitTestCase(pytest.Class):
nofuncargs = True # marker for fixturemanger.getfixtureinfo()
# to declare that our children do not support funcargs
#
# marker for fixturemanger.getfixtureinfo()
# to declare that our children do not support funcargs
nofuncargs = True
def setup(self):
cls = self.obj
if getattr(cls, '__unittest_skip__', False):
@@ -68,12 +69,26 @@ class TestCaseFunction(pytest.Function):
def setup(self):
self._testcase = self.parent.obj(self.name)
self._fix_unittest_skip_decorator()
self._obj = getattr(self._testcase, self.name)
if hasattr(self._testcase, 'setup_method'):
self._testcase.setup_method(self._obj)
if hasattr(self, "_request"):
self._request._fillfixtures()
def _fix_unittest_skip_decorator(self):
"""
The @unittest.skip decorator calls functools.wraps(self._testcase)
The call to functools.wraps() fails unless self._testcase
has a __name__ attribute. This is usually automatically supplied
if the test is a function or method, but we need to add manually
here.
See issue #1169
"""
if sys.version_info[0] == 2:
setattr(self._testcase, "__name__", self.name)
def teardown(self):
if hasattr(self._testcase, 'teardown_method'):
self._testcase.teardown_method(self._obj)
@@ -85,7 +100,7 @@ class TestCaseFunction(pytest.Function):
# unwrap potential exception info (see twisted trial support below)
rawexcinfo = getattr(rawexcinfo, '_rawexcinfo', rawexcinfo)
try:
excinfo = py.code.ExceptionInfo(rawexcinfo)
excinfo = _pytest._code.ExceptionInfo(rawexcinfo)
except TypeError:
try:
try:
@@ -101,7 +116,7 @@ class TestCaseFunction(pytest.Function):
except KeyboardInterrupt:
raise
except pytest.fail.Exception:
excinfo = py.code.ExceptionInfo()
excinfo = _pytest._code.ExceptionInfo()
self.__dict__.setdefault('_excinfo', []).append(excinfo)
def addError(self, testcase, rawexcinfo):
@@ -113,6 +128,8 @@ class TestCaseFunction(pytest.Function):
try:
pytest.skip(reason)
except pytest.skip.Exception:
self._evalskip = MarkEvaluator(self, 'SkipTest')
self._evalskip.result = True
self._addexcinfo(sys.exc_info())
def addExpectedFailure(self, testcase, rawexcinfo, reason=""):
@@ -140,7 +157,7 @@ class TestCaseFunction(pytest.Function):
if traceback:
excinfo.traceback = traceback
@pytest.mark.tryfirst
@pytest.hookimpl(tryfirst=True)
def pytest_runtest_makereport(item, call):
if isinstance(item, TestCaseFunction):
if item._excinfo:
@@ -151,30 +168,33 @@ def pytest_runtest_makereport(item, call):
pass
# twisted trial support
def pytest_runtest_protocol(item, __multicall__):
if isinstance(item, TestCaseFunction):
if 'twisted.trial.unittest' in sys.modules:
ut = sys.modules['twisted.python.failure']
Failure__init__ = ut.Failure.__init__
check_testcase_implements_trial_reporter()
def excstore(self, exc_value=None, exc_type=None, exc_tb=None,
captureVars=None):
if exc_value is None:
self._rawexcinfo = sys.exc_info()
else:
if exc_type is None:
exc_type = type(exc_value)
self._rawexcinfo = (exc_type, exc_value, exc_tb)
try:
Failure__init__(self, exc_value, exc_type, exc_tb,
captureVars=captureVars)
except TypeError:
Failure__init__(self, exc_value, exc_type, exc_tb)
ut.Failure.__init__ = excstore
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_protocol(item):
if isinstance(item, TestCaseFunction) and \
'twisted.trial.unittest' in sys.modules:
ut = sys.modules['twisted.python.failure']
Failure__init__ = ut.Failure.__init__
check_testcase_implements_trial_reporter()
def excstore(self, exc_value=None, exc_type=None, exc_tb=None,
captureVars=None):
if exc_value is None:
self._rawexcinfo = sys.exc_info()
else:
if exc_type is None:
exc_type = type(exc_value)
self._rawexcinfo = (exc_type, exc_value, exc_tb)
try:
return __multicall__.execute()
finally:
ut.Failure.__init__ = Failure__init__
Failure__init__(self, exc_value, exc_type, exc_tb,
captureVars=captureVars)
except TypeError:
Failure__init__(self, exc_value, exc_type, exc_tb)
ut.Failure.__init__ = excstore
yield
ut.Failure.__init__ = Failure__init__
else:
yield
def check_testcase_implements_trial_reporter(done=[]):
if done:

View File

@@ -0,0 +1,13 @@
This directory vendors the `pluggy` module.
For a more detailed discussion for the reasons to vendoring this
package, please see [this issue](https://github.com/pytest-dev/pytest/issues/944).
To update the current version, execute:
```
$ pip install -U pluggy==<version> --no-compile --target=_pytest/vendored_packages
```
And commit the modified files. The `pluggy-<version>.dist-info` directory
created by `pip` should be ignored.

View File

@@ -0,0 +1,10 @@
Plugin registration and hook calling for Python
===============================================
This is the plugin manager as used by pytest but stripped
of pytest specific details.
During the 0.x series this plugin does not have much documentation
except extensive docstrings in the pluggy.py module.

View File

@@ -0,0 +1,39 @@
Metadata-Version: 2.0
Name: pluggy
Version: 0.3.1
Summary: plugin and hook calling mechanisms for python
Home-page: UNKNOWN
Author: Holger Krekel
Author-email: holger at merlinux.eu
License: MIT license
Platform: unix
Platform: linux
Platform: osx
Platform: win32
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: POSIX
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: MacOS :: MacOS X
Classifier: Topic :: Software Development :: Testing
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Utilities
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.6
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Plugin registration and hook calling for Python
===============================================
This is the plugin manager as used by pytest but stripped
of pytest specific details.
During the 0.x series this plugin does not have much documentation
except extensive docstrings in the pluggy.py module.

View File

@@ -0,0 +1,8 @@
pluggy.py,sha256=v_RfWzyW6DPU1cJu_EFoL_OHq3t13qloVdR6UaMCXQA,29862
pluggy-0.3.1.dist-info/top_level.txt,sha256=xKSCRhai-v9MckvMuWqNz16c1tbsmOggoMSwTgcpYHE,7
pluggy-0.3.1.dist-info/pbr.json,sha256=xX3s6__wOcAyF-AZJX1sdZyW6PUXT-FkfBlM69EEUCg,47
pluggy-0.3.1.dist-info/RECORD,,
pluggy-0.3.1.dist-info/metadata.json,sha256=nLKltOT78dMV-00uXD6Aeemp4xNsz2q59j6ORSDeLjw,1027
pluggy-0.3.1.dist-info/METADATA,sha256=1b85Ho2u4iK30M099k7axMzcDDhLcIMb-A82JUJZnSo,1334
pluggy-0.3.1.dist-info/WHEEL,sha256=AvR0WeTpDaxT645bl5FQxUK6NPsTls2ttpcGJg3j1Xg,110
pluggy-0.3.1.dist-info/DESCRIPTION.rst,sha256=P5Akh1EdIBR6CeqtV2P8ZwpGSpZiTKPw0NyS7jEiD-g,306

View File

@@ -0,0 +1,6 @@
Wheel-Version: 1.0
Generator: bdist_wheel (0.24.0)
Root-Is-Purelib: true
Tag: py2-none-any
Tag: py3-none-any

View File

@@ -0,0 +1 @@
{"license": "MIT license", "name": "pluggy", "metadata_version": "2.0", "generator": "bdist_wheel (0.24.0)", "summary": "plugin and hook calling mechanisms for python", "platform": "unix", "version": "0.3.1", "extensions": {"python.details": {"document_names": {"description": "DESCRIPTION.rst"}, "contacts": [{"role": "author", "email": "holger at merlinux.eu", "name": "Holger Krekel"}]}}, "classifiers": ["Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: POSIX", "Operating System :: Microsoft :: Windows", "Operating System :: MacOS :: MacOS X", "Topic :: Software Development :: Testing", "Topic :: Software Development :: Libraries", "Topic :: Utilities", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.6", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5"]}

View File

@@ -0,0 +1 @@
{"is_release": false, "git_version": "7d4c9cd"}

View File

@@ -0,0 +1 @@
pluggy

View File

@@ -0,0 +1,777 @@
"""
PluginManager, basic initialization and tracing.
pluggy is the cristallized core of plugin management as used
by some 150 plugins for pytest.
Pluggy uses semantic versioning. Breaking changes are only foreseen for
Major releases (incremented X in "X.Y.Z"). If you want to use pluggy in
your project you should thus use a dependency restriction like
"pluggy>=0.1.0,<1.0" to avoid surprises.
pluggy is concerned with hook specification, hook implementations and hook
calling. For any given hook specification a hook call invokes up to N implementations.
A hook implementation can influence its position and type of execution:
if attributed "tryfirst" or "trylast" it will be tried to execute
first or last. However, if attributed "hookwrapper" an implementation
can wrap all calls to non-hookwrapper implementations. A hookwrapper
can thus execute some code ahead and after the execution of other hooks.
Hook specification is done by way of a regular python function where
both the function name and the names of all its arguments are significant.
Each hook implementation function is verified against the original specification
function, including the names of all its arguments. To allow for hook specifications
to evolve over the livetime of a project, hook implementations can
accept less arguments. One can thus add new arguments and semantics to
a hook specification by adding another argument typically without breaking
existing hook implementations.
The chosen approach is meant to let a hook designer think carefuly about
which objects are needed by an extension writer. By contrast, subclass-based
extension mechanisms often expose a lot more state and behaviour than needed,
thus restricting future developments.
Pluggy currently consists of functionality for:
- a way to register new hook specifications. Without a hook
specification no hook calling can be performed.
- a registry of plugins which contain hook implementation functions. It
is possible to register plugins for which a hook specification is not yet
known and validate all hooks when the system is in a more referentially
consistent state. Setting an "optionalhook" attribution to a hook
implementation will avoid PluginValidationError's if a specification
is missing. This allows to have optional integration between plugins.
- a "hook" relay object from which you can launch 1:N calls to
registered hook implementation functions
- a mechanism for ordering hook implementation functions
- mechanisms for two different type of 1:N calls: "firstresult" for when
the call should stop when the first implementation returns a non-None result.
And the other (default) way of guaranteeing that all hook implementations
will be called and their non-None result collected.
- mechanisms for "historic" extension points such that all newly
registered functions will receive all hook calls that happened
before their registration.
- a mechanism for discovering plugin objects which are based on
setuptools based entry points.
- a simple tracing mechanism, including tracing of plugin calls and
their arguments.
"""
import sys
import inspect
__version__ = '0.3.1'
__all__ = ["PluginManager", "PluginValidationError",
"HookspecMarker", "HookimplMarker"]
_py3 = sys.version_info > (3, 0)
class HookspecMarker:
""" Decorator helper class for marking functions as hook specifications.
You can instantiate it with a project_name to get a decorator.
Calling PluginManager.add_hookspecs later will discover all marked functions
if the PluginManager uses the same project_name.
"""
def __init__(self, project_name):
self.project_name = project_name
def __call__(self, function=None, firstresult=False, historic=False):
""" if passed a function, directly sets attributes on the function
which will make it discoverable to add_hookspecs(). If passed no
function, returns a decorator which can be applied to a function
later using the attributes supplied.
If firstresult is True the 1:N hook call (N being the number of registered
hook implementation functions) will stop at I<=N when the I'th function
returns a non-None result.
If historic is True calls to a hook will be memorized and replayed
on later registered plugins.
"""
def setattr_hookspec_opts(func):
if historic and firstresult:
raise ValueError("cannot have a historic firstresult hook")
setattr(func, self.project_name + "_spec",
dict(firstresult=firstresult, historic=historic))
return func
if function is not None:
return setattr_hookspec_opts(function)
else:
return setattr_hookspec_opts
class HookimplMarker:
""" Decorator helper class for marking functions as hook implementations.
You can instantiate with a project_name to get a decorator.
Calling PluginManager.register later will discover all marked functions
if the PluginManager uses the same project_name.
"""
def __init__(self, project_name):
self.project_name = project_name
def __call__(self, function=None, hookwrapper=False, optionalhook=False,
tryfirst=False, trylast=False):
""" if passed a function, directly sets attributes on the function
which will make it discoverable to register(). If passed no function,
returns a decorator which can be applied to a function later using
the attributes supplied.
If optionalhook is True a missing matching hook specification will not result
in an error (by default it is an error if no matching spec is found).
If tryfirst is True this hook implementation will run as early as possible
in the chain of N hook implementations for a specfication.
If trylast is True this hook implementation will run as late as possible
in the chain of N hook implementations.
If hookwrapper is True the hook implementations needs to execute exactly
one "yield". The code before the yield is run early before any non-hookwrapper
function is run. The code after the yield is run after all non-hookwrapper
function have run. The yield receives an ``_CallOutcome`` object representing
the exception or result outcome of the inner calls (including other hookwrapper
calls).
"""
def setattr_hookimpl_opts(func):
setattr(func, self.project_name + "_impl",
dict(hookwrapper=hookwrapper, optionalhook=optionalhook,
tryfirst=tryfirst, trylast=trylast))
return func
if function is None:
return setattr_hookimpl_opts
else:
return setattr_hookimpl_opts(function)
def normalize_hookimpl_opts(opts):
opts.setdefault("tryfirst", False)
opts.setdefault("trylast", False)
opts.setdefault("hookwrapper", False)
opts.setdefault("optionalhook", False)
class _TagTracer:
def __init__(self):
self._tag2proc = {}
self.writer = None
self.indent = 0
def get(self, name):
return _TagTracerSub(self, (name,))
def format_message(self, tags, args):
if isinstance(args[-1], dict):
extra = args[-1]
args = args[:-1]
else:
extra = {}
content = " ".join(map(str, args))
indent = " " * self.indent
lines = [
"%s%s [%s]\n" % (indent, content, ":".join(tags))
]
for name, value in extra.items():
lines.append("%s %s: %s\n" % (indent, name, value))
return lines
def processmessage(self, tags, args):
if self.writer is not None and args:
lines = self.format_message(tags, args)
self.writer(''.join(lines))
try:
self._tag2proc[tags](tags, args)
except KeyError:
pass
def setwriter(self, writer):
self.writer = writer
def setprocessor(self, tags, processor):
if isinstance(tags, str):
tags = tuple(tags.split(":"))
else:
assert isinstance(tags, tuple)
self._tag2proc[tags] = processor
class _TagTracerSub:
def __init__(self, root, tags):
self.root = root
self.tags = tags
def __call__(self, *args):
self.root.processmessage(self.tags, args)
def setmyprocessor(self, processor):
self.root.setprocessor(self.tags, processor)
def get(self, name):
return self.__class__(self.root, self.tags + (name,))
def _raise_wrapfail(wrap_controller, msg):
co = wrap_controller.gi_code
raise RuntimeError("wrap_controller at %r %s:%d %s" %
(co.co_name, co.co_filename, co.co_firstlineno, msg))
def _wrapped_call(wrap_controller, func):
""" Wrap calling to a function with a generator which needs to yield
exactly once. The yield point will trigger calling the wrapped function
and return its _CallOutcome to the yield point. The generator then needs
to finish (raise StopIteration) in order for the wrapped call to complete.
"""
try:
next(wrap_controller) # first yield
except StopIteration:
_raise_wrapfail(wrap_controller, "did not yield")
call_outcome = _CallOutcome(func)
try:
wrap_controller.send(call_outcome)
_raise_wrapfail(wrap_controller, "has second yield")
except StopIteration:
pass
return call_outcome.get_result()
class _CallOutcome:
""" Outcome of a function call, either an exception or a proper result.
Calling the ``get_result`` method will return the result or reraise
the exception raised when the function was called. """
excinfo = None
def __init__(self, func):
try:
self.result = func()
except BaseException:
self.excinfo = sys.exc_info()
def force_result(self, result):
self.result = result
self.excinfo = None
def get_result(self):
if self.excinfo is None:
return self.result
else:
ex = self.excinfo
if _py3:
raise ex[1].with_traceback(ex[2])
_reraise(*ex) # noqa
if not _py3:
exec("""
def _reraise(cls, val, tb):
raise cls, val, tb
""")
class _TracedHookExecution:
def __init__(self, pluginmanager, before, after):
self.pluginmanager = pluginmanager
self.before = before
self.after = after
self.oldcall = pluginmanager._inner_hookexec
assert not isinstance(self.oldcall, _TracedHookExecution)
self.pluginmanager._inner_hookexec = self
def __call__(self, hook, hook_impls, kwargs):
self.before(hook.name, hook_impls, kwargs)
outcome = _CallOutcome(lambda: self.oldcall(hook, hook_impls, kwargs))
self.after(outcome, hook.name, hook_impls, kwargs)
return outcome.get_result()
def undo(self):
self.pluginmanager._inner_hookexec = self.oldcall
class PluginManager(object):
""" Core Pluginmanager class which manages registration
of plugin objects and 1:N hook calling.
You can register new hooks by calling ``addhooks(module_or_class)``.
You can register plugin objects (which contain hooks) by calling
``register(plugin)``. The Pluginmanager is initialized with a
prefix that is searched for in the names of the dict of registered
plugin objects. An optional excludefunc allows to blacklist names which
are not considered as hooks despite a matching prefix.
For debugging purposes you can call ``enable_tracing()``
which will subsequently send debug information to the trace helper.
"""
def __init__(self, project_name, implprefix=None):
""" if implprefix is given implementation functions
will be recognized if their name matches the implprefix. """
self.project_name = project_name
self._name2plugin = {}
self._plugin2hookcallers = {}
self._plugin_distinfo = []
self.trace = _TagTracer().get("pluginmanage")
self.hook = _HookRelay(self.trace.root.get("hook"))
self._implprefix = implprefix
self._inner_hookexec = lambda hook, methods, kwargs: \
_MultiCall(methods, kwargs, hook.spec_opts).execute()
def _hookexec(self, hook, methods, kwargs):
# called from all hookcaller instances.
# enable_tracing will set its own wrapping function at self._inner_hookexec
return self._inner_hookexec(hook, methods, kwargs)
def register(self, plugin, name=None):
""" Register a plugin and return its canonical name or None if the name
is blocked from registering. Raise a ValueError if the plugin is already
registered. """
plugin_name = name or self.get_canonical_name(plugin)
if plugin_name in self._name2plugin or plugin in self._plugin2hookcallers:
if self._name2plugin.get(plugin_name, -1) is None:
return # blocked plugin, return None to indicate no registration
raise ValueError("Plugin already registered: %s=%s\n%s" %
(plugin_name, plugin, self._name2plugin))
# XXX if an error happens we should make sure no state has been
# changed at point of return
self._name2plugin[plugin_name] = plugin
# register matching hook implementations of the plugin
self._plugin2hookcallers[plugin] = hookcallers = []
for name in dir(plugin):
hookimpl_opts = self.parse_hookimpl_opts(plugin, name)
if hookimpl_opts is not None:
normalize_hookimpl_opts(hookimpl_opts)
method = getattr(plugin, name)
hookimpl = HookImpl(plugin, plugin_name, method, hookimpl_opts)
hook = getattr(self.hook, name, None)
if hook is None:
hook = _HookCaller(name, self._hookexec)
setattr(self.hook, name, hook)
elif hook.has_spec():
self._verify_hook(hook, hookimpl)
hook._maybe_apply_history(hookimpl)
hook._add_hookimpl(hookimpl)
hookcallers.append(hook)
return plugin_name
def parse_hookimpl_opts(self, plugin, name):
method = getattr(plugin, name)
res = getattr(method, self.project_name + "_impl", None)
if res is not None and not isinstance(res, dict):
# false positive
res = None
elif res is None and self._implprefix and name.startswith(self._implprefix):
res = {}
return res
def unregister(self, plugin=None, name=None):
""" unregister a plugin object and all its contained hook implementations
from internal data structures. """
if name is None:
assert plugin is not None, "one of name or plugin needs to be specified"
name = self.get_name(plugin)
if plugin is None:
plugin = self.get_plugin(name)
# if self._name2plugin[name] == None registration was blocked: ignore
if self._name2plugin.get(name):
del self._name2plugin[name]
for hookcaller in self._plugin2hookcallers.pop(plugin, []):
hookcaller._remove_plugin(plugin)
return plugin
def set_blocked(self, name):
""" block registrations of the given name, unregister if already registered. """
self.unregister(name=name)
self._name2plugin[name] = None
def is_blocked(self, name):
""" return True if the name blogs registering plugins of that name. """
return name in self._name2plugin and self._name2plugin[name] is None
def add_hookspecs(self, module_or_class):
""" add new hook specifications defined in the given module_or_class.
Functions are recognized if they have been decorated accordingly. """
names = []
for name in dir(module_or_class):
spec_opts = self.parse_hookspec_opts(module_or_class, name)
if spec_opts is not None:
hc = getattr(self.hook, name, None)
if hc is None:
hc = _HookCaller(name, self._hookexec, module_or_class, spec_opts)
setattr(self.hook, name, hc)
else:
# plugins registered this hook without knowing the spec
hc.set_specification(module_or_class, spec_opts)
for hookfunction in (hc._wrappers + hc._nonwrappers):
self._verify_hook(hc, hookfunction)
names.append(name)
if not names:
raise ValueError("did not find any %r hooks in %r" %
(self.project_name, module_or_class))
def parse_hookspec_opts(self, module_or_class, name):
method = getattr(module_or_class, name)
return getattr(method, self.project_name + "_spec", None)
def get_plugins(self):
""" return the set of registered plugins. """
return set(self._plugin2hookcallers)
def is_registered(self, plugin):
""" Return True if the plugin is already registered. """
return plugin in self._plugin2hookcallers
def get_canonical_name(self, plugin):
""" Return canonical name for a plugin object. Note that a plugin
may be registered under a different name which was specified
by the caller of register(plugin, name). To obtain the name
of an registered plugin use ``get_name(plugin)`` instead."""
return getattr(plugin, "__name__", None) or str(id(plugin))
def get_plugin(self, name):
""" Return a plugin or None for the given name. """
return self._name2plugin.get(name)
def get_name(self, plugin):
""" Return name for registered plugin or None if not registered. """
for name, val in self._name2plugin.items():
if plugin == val:
return name
def _verify_hook(self, hook, hookimpl):
if hook.is_historic() and hookimpl.hookwrapper:
raise PluginValidationError(
"Plugin %r\nhook %r\nhistoric incompatible to hookwrapper" %
(hookimpl.plugin_name, hook.name))
for arg in hookimpl.argnames:
if arg not in hook.argnames:
raise PluginValidationError(
"Plugin %r\nhook %r\nargument %r not available\n"
"plugin definition: %s\n"
"available hookargs: %s" %
(hookimpl.plugin_name, hook.name, arg,
_formatdef(hookimpl.function), ", ".join(hook.argnames)))
def check_pending(self):
""" Verify that all hooks which have not been verified against
a hook specification are optional, otherwise raise PluginValidationError"""
for name in self.hook.__dict__:
if name[0] != "_":
hook = getattr(self.hook, name)
if not hook.has_spec():
for hookimpl in (hook._wrappers + hook._nonwrappers):
if not hookimpl.optionalhook:
raise PluginValidationError(
"unknown hook %r in plugin %r" %
(name, hookimpl.plugin))
def load_setuptools_entrypoints(self, entrypoint_name):
""" Load modules from querying the specified setuptools entrypoint name.
Return the number of loaded plugins. """
from pkg_resources import iter_entry_points, DistributionNotFound
for ep in iter_entry_points(entrypoint_name):
# is the plugin registered or blocked?
if self.get_plugin(ep.name) or self.is_blocked(ep.name):
continue
try:
plugin = ep.load()
except DistributionNotFound:
continue
self.register(plugin, name=ep.name)
self._plugin_distinfo.append((plugin, ep.dist))
return len(self._plugin_distinfo)
def list_plugin_distinfo(self):
""" return list of distinfo/plugin tuples for all setuptools registered
plugins. """
return list(self._plugin_distinfo)
def list_name_plugin(self):
""" return list of name/plugin pairs. """
return list(self._name2plugin.items())
def get_hookcallers(self, plugin):
""" get all hook callers for the specified plugin. """
return self._plugin2hookcallers.get(plugin)
def add_hookcall_monitoring(self, before, after):
""" add before/after tracing functions for all hooks
and return an undo function which, when called,
will remove the added tracers.
``before(hook_name, hook_impls, kwargs)`` will be called ahead
of all hook calls and receive a hookcaller instance, a list
of HookImpl instances and the keyword arguments for the hook call.
``after(outcome, hook_name, hook_impls, kwargs)`` receives the
same arguments as ``before`` but also a :py:class:`_CallOutcome`` object
which represents the result of the overall hook call.
"""
return _TracedHookExecution(self, before, after).undo
def enable_tracing(self):
""" enable tracing of hook calls and return an undo function. """
hooktrace = self.hook._trace
def before(hook_name, methods, kwargs):
hooktrace.root.indent += 1
hooktrace(hook_name, kwargs)
def after(outcome, hook_name, methods, kwargs):
if outcome.excinfo is None:
hooktrace("finish", hook_name, "-->", outcome.result)
hooktrace.root.indent -= 1
return self.add_hookcall_monitoring(before, after)
def subset_hook_caller(self, name, remove_plugins):
""" Return a new _HookCaller instance for the named method
which manages calls to all registered plugins except the
ones from remove_plugins. """
orig = getattr(self.hook, name)
plugins_to_remove = [plug for plug in remove_plugins if hasattr(plug, name)]
if plugins_to_remove:
hc = _HookCaller(orig.name, orig._hookexec, orig._specmodule_or_class,
orig.spec_opts)
for hookimpl in (orig._wrappers + orig._nonwrappers):
plugin = hookimpl.plugin
if plugin not in plugins_to_remove:
hc._add_hookimpl(hookimpl)
# we also keep track of this hook caller so it
# gets properly removed on plugin unregistration
self._plugin2hookcallers.setdefault(plugin, []).append(hc)
return hc
return orig
class _MultiCall:
""" execute a call into multiple python functions/methods. """
# XXX note that the __multicall__ argument is supported only
# for pytest compatibility reasons. It was never officially
# supported there and is explicitly deprecated since 2.8
# so we can remove it soon, allowing to avoid the below recursion
# in execute() and simplify/speed up the execute loop.
def __init__(self, hook_impls, kwargs, specopts={}):
self.hook_impls = hook_impls
self.kwargs = kwargs
self.kwargs["__multicall__"] = self
self.specopts = specopts
def execute(self):
all_kwargs = self.kwargs
self.results = results = []
firstresult = self.specopts.get("firstresult")
while self.hook_impls:
hook_impl = self.hook_impls.pop()
args = [all_kwargs[argname] for argname in hook_impl.argnames]
if hook_impl.hookwrapper:
return _wrapped_call(hook_impl.function(*args), self.execute)
res = hook_impl.function(*args)
if res is not None:
if firstresult:
return res
results.append(res)
if not firstresult:
return results
def __repr__(self):
status = "%d meths" % (len(self.hook_impls),)
if hasattr(self, "results"):
status = ("%d results, " % len(self.results)) + status
return "<_MultiCall %s, kwargs=%r>" % (status, self.kwargs)
def varnames(func, startindex=None):
""" return argument name tuple for a function, method, class or callable.
In case of a class, its "__init__" method is considered.
For methods the "self" parameter is not included unless you are passing
an unbound method with Python3 (which has no supports for unbound methods)
"""
cache = getattr(func, "__dict__", {})
try:
return cache["_varnames"]
except KeyError:
pass
if inspect.isclass(func):
try:
func = func.__init__
except AttributeError:
return ()
startindex = 1
else:
if not inspect.isfunction(func) and not inspect.ismethod(func):
func = getattr(func, '__call__', func)
if startindex is None:
startindex = int(inspect.ismethod(func))
try:
rawcode = func.__code__
except AttributeError:
return ()
try:
x = rawcode.co_varnames[startindex:rawcode.co_argcount]
except AttributeError:
x = ()
else:
defaults = func.__defaults__
if defaults:
x = x[:-len(defaults)]
try:
cache["_varnames"] = x
except TypeError:
pass
return x
class _HookRelay:
""" hook holder object for performing 1:N hook calls where N is the number
of registered plugins.
"""
def __init__(self, trace):
self._trace = trace
class _HookCaller(object):
def __init__(self, name, hook_execute, specmodule_or_class=None, spec_opts=None):
self.name = name
self._wrappers = []
self._nonwrappers = []
self._hookexec = hook_execute
if specmodule_or_class is not None:
assert spec_opts is not None
self.set_specification(specmodule_or_class, spec_opts)
def has_spec(self):
return hasattr(self, "_specmodule_or_class")
def set_specification(self, specmodule_or_class, spec_opts):
assert not self.has_spec()
self._specmodule_or_class = specmodule_or_class
specfunc = getattr(specmodule_or_class, self.name)
argnames = varnames(specfunc, startindex=inspect.isclass(specmodule_or_class))
assert "self" not in argnames # sanity check
self.argnames = ["__multicall__"] + list(argnames)
self.spec_opts = spec_opts
if spec_opts.get("historic"):
self._call_history = []
def is_historic(self):
return hasattr(self, "_call_history")
def _remove_plugin(self, plugin):
def remove(wrappers):
for i, method in enumerate(wrappers):
if method.plugin == plugin:
del wrappers[i]
return True
if remove(self._wrappers) is None:
if remove(self._nonwrappers) is None:
raise ValueError("plugin %r not found" % (plugin,))
def _add_hookimpl(self, hookimpl):
if hookimpl.hookwrapper:
methods = self._wrappers
else:
methods = self._nonwrappers
if hookimpl.trylast:
methods.insert(0, hookimpl)
elif hookimpl.tryfirst:
methods.append(hookimpl)
else:
# find last non-tryfirst method
i = len(methods) - 1
while i >= 0 and methods[i].tryfirst:
i -= 1
methods.insert(i + 1, hookimpl)
def __repr__(self):
return "<_HookCaller %r>" % (self.name,)
def __call__(self, **kwargs):
assert not self.is_historic()
return self._hookexec(self, self._nonwrappers + self._wrappers, kwargs)
def call_historic(self, proc=None, kwargs=None):
self._call_history.append((kwargs or {}, proc))
# historizing hooks don't return results
self._hookexec(self, self._nonwrappers + self._wrappers, kwargs)
def call_extra(self, methods, kwargs):
""" Call the hook with some additional temporarily participating
methods using the specified kwargs as call parameters. """
old = list(self._nonwrappers), list(self._wrappers)
for method in methods:
opts = dict(hookwrapper=False, trylast=False, tryfirst=False)
hookimpl = HookImpl(None, "<temp>", method, opts)
self._add_hookimpl(hookimpl)
try:
return self(**kwargs)
finally:
self._nonwrappers, self._wrappers = old
def _maybe_apply_history(self, method):
if self.is_historic():
for kwargs, proc in self._call_history:
res = self._hookexec(self, [method], kwargs)
if res and proc is not None:
proc(res[0])
class HookImpl:
def __init__(self, plugin, plugin_name, function, hook_impl_opts):
self.function = function
self.argnames = varnames(self.function)
self.plugin = plugin
self.opts = hook_impl_opts
self.plugin_name = plugin_name
self.__dict__.update(hook_impl_opts)
class PluginValidationError(Exception):
""" plugin failed validation. """
if hasattr(inspect, 'signature'):
def _formatdef(func):
return "%s%s" % (
func.__name__,
str(inspect.signature(func))
)
else:
def _formatdef(func):
return "%s%s" % (
func.__name__,
inspect.formatargspec(*inspect.getargspec(func))
)

28
appveyor.yml Normal file
View File

@@ -0,0 +1,28 @@
environment:
COVERALLS_REPO_TOKEN:
secure: 2NJ5Ct55cHJ9WEg3xbSqCuv0rdgzzb6pnzOIG5OkMbTndw3wOBrXntWFoQrXiMFi
# this is pytest's token in coveralls.io, encrypted
# using pytestbot account as detailed here:
# https://www.appveyor.com/docs/build-configuration#secure-variables
install:
- echo Installed Pythons
- dir c:\Python*
# install pypy using choco (redirect to a file and write to console in case
# choco install returns non-zero, because choco install python.pypy is too
# noisy)
- choco install python.pypy > pypy-inst.log 2>&1 || (type pypy-inst.log & exit /b 1)
- set PATH=C:\tools\pypy\pypy;%PATH% # so tox can find pypy
- echo PyPy installed
- pypy --version
- C:\Python35\python -m pip install tox
build: false # Not a C# project, build stuff at the test step instead.
test_script:
- C:\Python35\python -m tox
# coveralls is not in tox's envlist, plus for PRs the secure variable
# is not defined so we have to check for it
- if defined COVERALLS_REPO_TOKEN C:\Python35\python -m tox -e coveralls

View File

@@ -9,4 +9,4 @@ if __name__ == '__main__':
p = pstats.Stats("prof")
p.strip_dirs()
p.sort_stats('cumulative')
print(p.print_stats(250))
print(p.print_stats(500))

View File

@@ -12,46 +12,49 @@ PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
SITETARGET=latest
REGENDOC_ARGS := \
--normalize "/={8,} (.*) ={8,}/======= \1 ========/" \
--normalize "/_{8,} (.*) _{8,}/_______ \1 ________/" \
--normalize "/in \d+.\d+ seconds/in 0.12 seconds/" \
--normalize "@/tmp/pytest-of-.*/pytest-\d+@PYTEST_TMPDIR@" \
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
regen:
PYTHONDONTWRITEBYTECODE=1 COLUMNS=76 regendoc --update *.txt */*.txt
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " showtarget to show the pytest.org target directory"
@echo " install to install docs to pytest.org/SITETARGET"
@echo " install-ldf to install the doc pdf to pytest.org/SITETARGET"
@echo " regen to regenerate pytest examples using the installed pytest"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
-rm -rf $(BUILDDIR)/*
SITETARGET=$(shell ./_getdoctarget.py)
showtarget:
@echo $(SITETARGET)
install: html
rsync -avz _build/html/ pytest.org:/www/pytest.org/$(SITETARGET)
# for access talk to someone with login rights to
# pytest-dev@pytest.org to add your ssh key
rsync -avz _build/html/ pytest-dev@pytest.org:pytest.org/$(SITETARGET)
installpdf: latexpdf
@scp $(BUILDDIR)/latex/pytest.pdf pytest.org:/www/pytest.org/$(SITETARGET)
@scp $(BUILDDIR)/latex/pytest.pdf pytest-dev@pytest.org:pytest.org/$(SITETARGET)
installall: clean install installpdf
@echo "done"
regen:
PYTHONDONTWRITEBYTECODE=1 COLUMNS=76 regendoc --update *.rst */*.rst ${REGENDOC_ARGS}
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo

16
doc/en/_getdoctarget.py Executable file
View File

@@ -0,0 +1,16 @@
#!/usr/bin/env python
import py
def get_version_string():
fn = py.path.local(__file__).join("..", "..", "..",
"_pytest", "__init__.py")
for line in fn.readlines():
if "version" in line and not line.strip().startswith('#'):
return eval(line.split("=")[-1])
def get_minor_version_string():
return ".".join(get_version_string().split(".")[:2])
if __name__ == "__main__":
print (get_minor_version_string())

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('license') }}">License</a></li>
</ul>
{%- if display_toc %}

View File

@@ -1,5 +1,21 @@
{% extends "!layout.html" %}
{% block header %}
<div align="center" xmlns="http://www.w3.org/1999/html" style="background-color: lightgreen; padding: .5em">
<h4>
Want to help improve pytest? Please
<a href="https://www.indiegogo.com/projects/python-testing-sprint-mid-2016#/">
contribute to
</a>
or
<a href="announce/sprint2016.html">
join
</a>
our upcoming sprint in June 2016!
</h4>
</div>
{{super()}}
{% endblock %}
{% block footer %}
{{ super() }}
<script type="text/javascript">

View File

@@ -1,11 +1,16 @@
<h3>Useful Links</h3>
<ul>
<li>
<a href="https://www.indiegogo.com/projects/python-testing-sprint-mid-2016#/">
<b>Sprint funding campaign</b>
</a>
</li>
<li><a href="{{ pathto('index') }}">The pytest Website</a></li>
<li><a href="{{ pathto('contributing') }}">Contribution Guide</a></li>
<li><a href="https://pypi.python.org/pypi/pytest">pytest @ PyPI</a></li>
<li><a href="https://bitbucket.org/hpk42/pytest/">pytest @ Bitbucket</a></li>
<li><a href="http://pytest.org/latest/plugins_index/index.html">3rd party plugins</a></li>
<li><a href="https://bitbucket.org/hpk42/pytest/issues?status=new&status=open">Issue Tracker</a></li>
<li><a href="https://github.com/pytest-dev/pytest/">pytest @ GitHub</a></li>
<li><a href="http://plugincompat.herokuapp.com/">3rd party plugins</a></li>
<li><a href="https://github.com/pytest-dev/pytest/issues">Issue Tracker</a></li>
<li><a href="http://pytest.org/latest/pytest.pdf">PDF Documentation</a>
</ul>

View File

@@ -12,7 +12,7 @@
{% set link_color = '#000' %}
{% set link_hover_color = '#000' %}
{% set base_font = 'sans-serif' %}
{% set header_font = 'sans-serif' %}
{% set header_font = 'serif' %}
@import url("basic.css");
@@ -265,9 +265,10 @@ p.admonition-title:after {
content: ":";
}
pre, tt {
pre, tt, code {
font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
font-size: 0.9em;
background: #eee;
}
img.screenshot {

78
doc/en/adopt.rst Normal file
View File

@@ -0,0 +1,78 @@
April 2015 is "adopt pytest month"
=============================================
Are you an enthusiastic pytest user, the local testing guru in your workplace? Or are you considering using pytest for your open source project, but not sure how to get started? Then you may be interested in "adopt pytest month"!
We will pair experienced pytest users with open source projects, for a month's effort of getting new development teams started with pytest.
In 2015 we are trying this for the first time. In February and March 2015 we will gather volunteers on both sides, in April we will do the work, and in May we will evaluate how it went. This effort is being coordinated by Brianna Laugher. If you have any questions or comments, you can raise them on the `@pytestdotorg twitter account <https://twitter.com/pytestdotorg>`_ the `issue tracker`_ or the `pytest-dev mailing list`_.
.. _`issue tracker`: https://github.com/pytest-dev/pytest/issues/676
.. _`pytest-dev mailing list`: https://mail.python.org/mailman/listinfo/pytest-dev
The ideal pytest helper
-----------------------------------------
- will be able to commit 2-4 hours a week to working with their particular project (this might involve joining their mailing list, installing the software and exploring any existing tests, offering advice, writing some example tests)
- feels confident in using pytest (e.g. has explored command line options, knows how to write parametrized tests, has an idea about conftest contents)
- does not need to be an expert in every aspect!
`Pytest helpers, sign up here`_! (preferably in February, hard deadline 22 March)
.. _`Pytest helpers, sign up here`: http://goo.gl/forms/nxqAhqWt1P
The ideal partner project
-----------------------------------------
- is open source, and predominantly written in Python
- has an automated/documented install process for developers
- has more than one core developer
- has at least one official release (e.g. is available on pypi)
- has the support of the core development team, in trying out pytest adoption
- has no tests... or 100% test coverage... or somewhere in between!
`Partner projects, sign up here`_! (by 22 March)
.. _`Partner projects, sign up here`: http://goo.gl/forms/ZGyqlHiwk3
What does it mean to "adopt pytest"?
-----------------------------------------
There can be many different definitions of "success". Pytest can run many `nose and unittest`_ tests by default, so using pytest as your testrunner may be possible from day 1. Job done, right?
Progressive success might look like:
- tests can be run (by pytest) without errors (there may be failures)
- tests can be run (by pytest) without failures
- test runner is integrated into CI server
- existing tests are rewritten to take advantage of pytest features - this can happen in several iterations, for example:
- changing to native assert_ statements (pycmd_ has a script to help with that, ``pyconvert_unittest.py``)
- changing `setUp/tearDown methods`_ to fixtures_
- adding markers_
- other changes to reduce boilerplate
- assess needs for future tests to be written, e.g. new fixtures, distributed_ testing tweaks
"Success" should also include that the development team feels comfortable with their knowledge of how to use pytest. In fact this is probably more important than anything else. So spending a lot of time on communication, giving examples, etc will probably be important - both in running the tests, and in writing them.
It may be after the month is up, the partner project decides that pytest is not right for it. That's okay - hopefully the pytest team will also learn something about its weaknesses or deficiencies.
.. _`nose and unittest`: faq.html#how-does-pytest-relate-to-nose-and-unittest
.. _assert: asserts.html
.. _pycmd: https://bitbucket.org/hpk42/pycmd/overview
.. _`setUp/tearDown methods`: xunit_setup.html
.. _fixtures: fixture.html
.. _markers: markers.html
.. _distributed: xdist.html
Other ways to help
-----------------------------------------
Promote! Do your favourite open source Python projects use pytest? If not, why not tell them about this page?

View File

@@ -5,6 +5,21 @@ Release announcements
.. toctree::
:maxdepth: 2
sprint2016
release-2.9.1
release-2.9.1
release-2.9.0
release-2.8.7
release-2.8.6
release-2.8.5
release-2.8.4
release-2.8.3
release-2.8.2
release-2.7.2
release-2.7.1
release-2.7.0
release-2.6.3
release-2.6.2
release-2.6.1
release-2.6.0
@@ -32,4 +47,3 @@ Release announcements
release-2.0.2
release-2.0.1
release-2.0.0

View File

@@ -34,7 +34,7 @@ a full list of details. A few feature highlights:
influence the environment before conftest files import ``django``.
- reporting: color the last line red or green depending if
failures/errors occured or everything passed.
failures/errors occurred or everything passed.
The documentation has been updated to accomodate the changes,
see `http://pytest.org <http://pytest.org>`_
@@ -95,7 +95,7 @@ new features:
as strings will remain fully supported.
- reporting: color the last line red or green depending if
failures/errors occured or everything passed. thanks Christian
failures/errors occurred or everything passed. thanks Christian
Theunert.
- make "import pdb ; pdb.set_trace()" work natively wrt capturing (no

View File

@@ -21,7 +21,7 @@ pytest-2.4.2 is another bug-fixing release:
- introduce node.get_marker/node.add_marker API for plugins
like pytest-pep8 and pytest-flakes to avoid the messy
details of the node.keywords pseudo-dicts. Adapated
details of the node.keywords pseudo-dicts. Adapted
docs.
- remove attempt to "dup" stdout at startup as it's icky.

View File

@@ -21,7 +21,7 @@ fixes a number of bugs and brings some new features, mainly:
Note also that 2.6.0 departs with the "zero reported bugs" policy
because it has been too hard to keep up with it, unfortunately.
Instead we are for now rather bound to work on "upvoted" issues in
the https://bitbucket.org/hpk42/pytest/issues?status=new&status=open&sort=-votes
the https://bitbucket.org/pytest-dev/pytest/issues?status=new&status=open&sort=-votes
issue tracker.
See docs at:

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