commit
63bb9efd29
3
.hgtags
3
.hgtags
|
@ -28,3 +28,6 @@ d5eacf390af74553227122b85e20345d47b2f9e6 1.3.1
|
||||||
8b8e7c25a13cf863f01b2dd955978285ae9daf6a 1.3.1
|
8b8e7c25a13cf863f01b2dd955978285ae9daf6a 1.3.1
|
||||||
3bff44b188a7ec1af328d977b9d39b6757bb38df 1.3.2
|
3bff44b188a7ec1af328d977b9d39b6757bb38df 1.3.2
|
||||||
c59d3fa8681a5b5966b8375b16fccd64a3a8dbeb 1.3.3
|
c59d3fa8681a5b5966b8375b16fccd64a3a8dbeb 1.3.3
|
||||||
|
79ef6377705184c55633d456832eea318fedcf61 1.3.4
|
||||||
|
79ef6377705184c55633d456832eea318fedcf61 1.3.4
|
||||||
|
90fffd35373e9f125af233f78b19416f0938d841 1.3.4
|
||||||
|
|
18
CHANGELOG
18
CHANGELOG
|
@ -1,3 +1,21 @@
|
||||||
|
|
||||||
|
Changes between 1.3.4 and 1.4.0.dev0
|
||||||
|
==================================================
|
||||||
|
|
||||||
|
- introduce (customizable) assertion failure representations (Floris Bruynooghe)
|
||||||
|
- major refactoring of internal collection handling
|
||||||
|
- majorly reduce py.test core code, shift function/python testing to own plugin
|
||||||
|
- fix issue88 (finding custom test nodes from command line arg)
|
||||||
|
|
||||||
|
Changes between 1.3.3 and 1.3.4
|
||||||
|
==================================================
|
||||||
|
|
||||||
|
- fix issue111: improve install documentation for windows
|
||||||
|
- fix issue119: fix custom collectability of __init__.py as a module
|
||||||
|
- fix issue116: --doctestmodules work with __init__.py files as well
|
||||||
|
- fix issue115: unify internal exception passthrough/catching/GeneratorExit
|
||||||
|
- fix issue118: new --tb=native for presenting cpython-standard exceptions
|
||||||
|
|
||||||
Changes between 1.3.2 and 1.3.3
|
Changes between 1.3.2 and 1.3.3
|
||||||
==================================================
|
==================================================
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,12 @@
|
||||||
|
checks / deprecations for next release
|
||||||
|
---------------------------------------------------------------
|
||||||
|
tags: bug 1.4 core xdist
|
||||||
|
|
||||||
|
* reportinfo -> location in hooks and items
|
||||||
|
* check oejskit plugin compatibility
|
||||||
|
* terminal reporting - dot-printing
|
||||||
|
* some simple profiling
|
||||||
|
|
||||||
refine session initialization / fix custom collect crash
|
refine session initialization / fix custom collect crash
|
||||||
---------------------------------------------------------------
|
---------------------------------------------------------------
|
||||||
tags: bug 1.4 core xdist
|
tags: bug 1.4 core xdist
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
py.test/pylib 1.3.4: fixes and new native traceback option
|
||||||
|
===========================================================================
|
||||||
|
|
||||||
|
pylib/py.test 1.3.4 is a minor maintenance release mostly containing bug fixes
|
||||||
|
and a new "--tb=native" traceback option to show "normal" Python standard
|
||||||
|
tracebacks instead of the py.test enhanced tracebacks. See below for more
|
||||||
|
change info and http://pytest.org for more general information on features
|
||||||
|
and configuration of the testing tool.
|
||||||
|
|
||||||
|
Thanks to the issue reporters and generally to Ronny Pfannschmidt for help.
|
||||||
|
|
||||||
|
cheers,
|
||||||
|
holger krekel
|
||||||
|
|
||||||
|
Changes between 1.3.3 and 1.3.4
|
||||||
|
==================================================
|
||||||
|
|
||||||
|
- fix issue111: improve install documentation for windows
|
||||||
|
- fix issue119: fix custom collectability of __init__.py as a module
|
||||||
|
- fix issue116: --doctestmodules work with __init__.py files as well
|
||||||
|
- fix issue115: unify internal exception passthrough/catching/GeneratorExit
|
||||||
|
- fix issue118: new --tb=native for presenting cpython-standard exceptions
|
|
@ -118,5 +118,43 @@ def test_dynamic_compile_shows_nicely():
|
||||||
module.foo()
|
module.foo()
|
||||||
|
|
||||||
|
|
||||||
|
class TestSpecialisedExplanations(object):
|
||||||
|
def test_eq_text(self):
|
||||||
|
assert 'spam' == 'eggs'
|
||||||
|
|
||||||
|
def test_eq_similar_text(self):
|
||||||
|
assert 'foo 1 bar' == 'foo 2 bar'
|
||||||
|
|
||||||
|
def test_eq_multiline_text(self):
|
||||||
|
assert 'foo\nspam\nbar' == 'foo\neggs\nbar'
|
||||||
|
|
||||||
|
def test_eq_long_text(self):
|
||||||
|
a = '1'*100 + 'a' + '2'*100
|
||||||
|
b = '1'*100 + 'b' + '2'*100
|
||||||
|
assert a == b
|
||||||
|
|
||||||
|
def test_eq_long_text_multiline(self):
|
||||||
|
a = '1\n'*100 + 'a' + '2\n'*100
|
||||||
|
b = '1\n'*100 + 'b' + '2\n'*100
|
||||||
|
assert a == b
|
||||||
|
|
||||||
|
def test_eq_list(self):
|
||||||
|
assert [0, 1, 2] == [0, 1, 3]
|
||||||
|
|
||||||
|
def test_eq_list_long(self):
|
||||||
|
a = [0]*100 + [1] + [3]*100
|
||||||
|
b = [0]*100 + [2] + [3]*100
|
||||||
|
assert a == b
|
||||||
|
|
||||||
|
def test_eq_dict(self):
|
||||||
|
assert {'a': 0, 'b': 1} == {'a': 0, 'b': 2}
|
||||||
|
|
||||||
|
def test_eq_set(self):
|
||||||
|
assert set([0, 10, 11, 12]) == set([0, 20, 21])
|
||||||
|
|
||||||
|
def test_in_list(self):
|
||||||
|
assert 1 in [0, 2, 3, 4, 5]
|
||||||
|
|
||||||
|
|
||||||
def globf(x):
|
def globf(x):
|
||||||
return x+1
|
return x+1
|
||||||
|
|
|
@ -26,7 +26,47 @@ py.test/pylib installation info in a nutshell
|
||||||
.. _`bin`: bin.html
|
.. _`bin`: bin.html
|
||||||
|
|
||||||
|
|
||||||
Best practise: install tool and dependencies virtually
|
.. _`easy_install`:
|
||||||
|
|
||||||
|
Installation using easy_install
|
||||||
|
===================================================
|
||||||
|
|
||||||
|
Both `Distribute`_ and setuptools_ provide the ``easy_install``
|
||||||
|
installation tool with which you can type into a command line window::
|
||||||
|
|
||||||
|
easy_install -U py
|
||||||
|
|
||||||
|
to install the latest release of the py lib and py.test. The ``-U`` switch
|
||||||
|
will trigger an upgrade if you already have an older version installed.
|
||||||
|
Note that setuptools works ok with Python2 interpreters while `Distribute`_
|
||||||
|
additionally works with Python3 and also avoid some issues on Windows.
|
||||||
|
|
||||||
|
Known issues:
|
||||||
|
|
||||||
|
- **Windows**: If "easy_install" or "py.test" are not found
|
||||||
|
please see here for preparing your environment for running
|
||||||
|
command line tools: `Python for Windows`_. You may alternatively
|
||||||
|
use an `ActivePython install`_ which makes command line tools
|
||||||
|
automatically available under Windows.
|
||||||
|
|
||||||
|
.. _`ActivePython install`: http://www.activestate.com/activepython/downloads
|
||||||
|
|
||||||
|
.. _`Jython does not create command line launchers`: http://bugs.jython.org/issue1491
|
||||||
|
|
||||||
|
- **Jython2.5.1 on Windows XP**: `Jython does not create command line launchers`_
|
||||||
|
so ``py.test`` will not work correctly. You may install py.test on
|
||||||
|
CPython and type ``py.test --genscript=mytest`` and then use
|
||||||
|
``jython mytest`` to run py.test for your tests to run in Jython.
|
||||||
|
|
||||||
|
- **On Linux**: If ``easy_install`` fails because it needs to run
|
||||||
|
as the superuser you are trying to install things globally
|
||||||
|
and need to put ``sudo`` in front of the command.
|
||||||
|
|
||||||
|
|
||||||
|
.. _quickstart: test/quickstart.html
|
||||||
|
|
||||||
|
|
||||||
|
Recommendation: install tool and dependencies virtually
|
||||||
===========================================================
|
===========================================================
|
||||||
|
|
||||||
It is recommended to work with virtual environments
|
It is recommended to work with virtual environments
|
||||||
|
@ -36,34 +76,9 @@ you need to run your tests. Local virtual Python environments
|
||||||
(as opposed to system-wide "global" environments) make for a more
|
(as opposed to system-wide "global" environments) make for a more
|
||||||
reproducible and reliable test environment.
|
reproducible and reliable test environment.
|
||||||
|
|
||||||
|
|
||||||
.. _`virtualenv`: http://pypi.python.org/pypi/virtualenv
|
.. _`virtualenv`: http://pypi.python.org/pypi/virtualenv
|
||||||
.. _`buildout`: http://www.buildout.org/
|
.. _`buildout`: http://www.buildout.org/
|
||||||
.. _pip: http://pypi.python.org/pypi/pip
|
.. _pip: http://pypi.python.org/pypi/pip
|
||||||
.. _`easy_install`:
|
|
||||||
|
|
||||||
using easy_install (from setuptools or Distribute)
|
|
||||||
===================================================
|
|
||||||
|
|
||||||
Both `Distribute`_ and setuptools_ provide the ``easy_install``
|
|
||||||
installation tool. While setuptools should work ok with
|
|
||||||
Python2 interpreters, `Distribute`_ also works with Python3
|
|
||||||
and it avoids some issues on Windows. In both cases you
|
|
||||||
can open a command line window and then type::
|
|
||||||
|
|
||||||
easy_install -U py
|
|
||||||
|
|
||||||
to install the latest release of the py lib and py.test. The ``-U`` switch
|
|
||||||
will trigger an upgrade if you already have an older version installed.
|
|
||||||
|
|
||||||
If you now type::
|
|
||||||
|
|
||||||
py.test --version
|
|
||||||
|
|
||||||
you should see the version number and the import location of the tool.
|
|
||||||
Maybe you want to head on with the `quickstart`_ now?
|
|
||||||
|
|
||||||
.. _quickstart: test/quickstart.html
|
|
||||||
|
|
||||||
.. _standalone:
|
.. _standalone:
|
||||||
|
|
||||||
|
@ -84,24 +99,7 @@ disguise. You can tell people to download and then e.g. run it like this::
|
||||||
and ask them to send you the resulting URL. The resulting script has
|
and ask them to send you the resulting URL. The resulting script has
|
||||||
all core features and runs unchanged under Python2 and Python3 interpreters.
|
all core features and runs unchanged under Python2 and Python3 interpreters.
|
||||||
|
|
||||||
Troubleshooting / known issues
|
.. _`Python for Windows`: http://www.imladris.com/Scripts/PythonForWindows.html
|
||||||
===============================
|
|
||||||
|
|
||||||
.. _`Jython does not create command line launchers`: http://bugs.jython.org/issue1491
|
|
||||||
|
|
||||||
**Jython2.5.1 on XP**: `Jython does not create command line launchers`_
|
|
||||||
so ``py.test`` will not work correctly. You may install py.test on
|
|
||||||
CPython and type ``py.test --genscript=mytest`` and then use
|
|
||||||
``jython mytest`` to run py.test for your tests to run in Jython.
|
|
||||||
|
|
||||||
**On Linux**: If ``easy_install`` fails because it needs to run
|
|
||||||
as the superuser you are trying to install things globally
|
|
||||||
and need to put ``sudo`` in front of the command.
|
|
||||||
|
|
||||||
**On Windows**: If "easy_install" or "py.test" are not found
|
|
||||||
please see here: `How do i run a Python program under Windows?`_
|
|
||||||
|
|
||||||
.. _`How do i run a Python program under Windows?`: http://www.python.org/doc/faq/windows/#how-do-i-run-a-python-program-under-windows
|
|
||||||
|
|
||||||
.. _mercurial: http://mercurial.selenic.com/wiki/
|
.. _mercurial: http://mercurial.selenic.com/wiki/
|
||||||
.. _`Distribute`:
|
.. _`Distribute`:
|
||||||
|
|
|
@ -6,27 +6,39 @@ produce code coverage reports using the 'coverage' package, including support fo
|
||||||
.. contents::
|
.. contents::
|
||||||
:local:
|
:local:
|
||||||
|
|
||||||
This plugin produces coverage reports using the coverage package. It
|
This plugin produces coverage reports. It supports centralised testing and distributed testing in
|
||||||
supports centralised testing and distributed testing in both load and
|
both load and each modes. It also supports coverage of subprocesses.
|
||||||
each modes.
|
|
||||||
|
|
||||||
All features offered by the coverage package should be available,
|
All features offered by the coverage package should be available, either through pytest-cov or
|
||||||
either through this plugin or through coverage's own config file.
|
through coverage's config file.
|
||||||
|
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
------------
|
------------
|
||||||
|
|
||||||
The `pytest-cov pypi`_ package may be installed / uninstalled with pip::
|
The `pytest-cov`_ package may be installed with pip or easy_install::
|
||||||
|
|
||||||
pip install pytest-cov
|
pip install pytest-cov
|
||||||
pip uninstall pytest-cov
|
|
||||||
|
|
||||||
Alternatively easy_install can be used::
|
|
||||||
|
|
||||||
easy_install pytest-cov
|
easy_install pytest-cov
|
||||||
|
|
||||||
.. _`pytest-cov pypi`: http://pypi.python.org/pypi/pytest-cov/
|
.. _`pytest-cov`: http://pypi.python.org/pypi/pytest-cov/
|
||||||
|
|
||||||
|
|
||||||
|
Uninstallation
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Uninstalling packages is supported by pip::
|
||||||
|
|
||||||
|
pip uninstall pytest-cov
|
||||||
|
|
||||||
|
However easy_install does not provide an uninstall facility.
|
||||||
|
|
||||||
|
.. IMPORTANT::
|
||||||
|
|
||||||
|
Ensure that you manually delete the init_cov_core.pth file in your site-packages directory.
|
||||||
|
|
||||||
|
This file starts coverage collection of subprocesses if appropriate during site initialisation
|
||||||
|
at python startup.
|
||||||
|
|
||||||
|
|
||||||
Usage
|
Usage
|
||||||
|
@ -35,6 +47,9 @@ Usage
|
||||||
Centralised Testing
|
Centralised Testing
|
||||||
~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Centralised testing will report on the combined coverage of the main process and all of it's
|
||||||
|
subprocesses.
|
||||||
|
|
||||||
Running centralised testing::
|
Running centralised testing::
|
||||||
|
|
||||||
py.test --cov myproj tests/
|
py.test --cov myproj tests/
|
||||||
|
@ -42,150 +57,149 @@ Running centralised testing::
|
||||||
Shows a terminal report::
|
Shows a terminal report::
|
||||||
|
|
||||||
-------------------- coverage: platform linux2, python 2.6.4-final-0 ---------------------
|
-------------------- coverage: platform linux2, python 2.6.4-final-0 ---------------------
|
||||||
Name Stmts Exec Cover Missing
|
Name Stmts Miss Cover
|
||||||
--------------------------------------------------
|
----------------------------------------
|
||||||
myproj/__init__ 2 2 100%
|
myproj/__init__ 2 0 100%
|
||||||
myproj/myproj 257 244 94% 24-26, 99, 149, 233-236, 297-298, 369-370
|
myproj/myproj 257 13 94%
|
||||||
myproj/feature4286 94 87 92% 183-188, 197
|
myproj/feature4286 94 7 92%
|
||||||
--------------------------------------------------
|
----------------------------------------
|
||||||
TOTAL 353 333 94%
|
TOTAL 353 20 94%
|
||||||
|
|
||||||
|
|
||||||
Distributed Testing
|
Distributed Testing: Load
|
||||||
~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Distributed testing with dist mode set to load::
|
Distributed testing with dist mode set to load will report on the combined coverage of all slaves.
|
||||||
|
The slaves may be spread out over any number of hosts and each slave may be located anywhere on the
|
||||||
|
file system. Each slave will have it's subprocesses measured.
|
||||||
|
|
||||||
|
Running distributed testing with dist mode set to load::
|
||||||
|
|
||||||
py.test --cov myproj -n 2 tests/
|
py.test --cov myproj -n 2 tests/
|
||||||
|
|
||||||
The results from the slaves will be combined like so::
|
Shows a terminal report::
|
||||||
|
|
||||||
-------------------- coverage: platform linux2, python 2.6.4-final-0 ---------------------
|
-------------------- coverage: platform linux2, python 2.6.4-final-0 ---------------------
|
||||||
Name Stmts Exec Cover Missing
|
Name Stmts Miss Cover
|
||||||
--------------------------------------------------
|
----------------------------------------
|
||||||
myproj/__init__ 2 2 100%
|
myproj/__init__ 2 0 100%
|
||||||
myproj/myproj 257 244 94% 24-26, 99, 149, 233-236, 297-298, 369-370
|
myproj/myproj 257 13 94%
|
||||||
myproj/feature4286 94 87 92% 183-188, 197
|
myproj/feature4286 94 7 92%
|
||||||
--------------------------------------------------
|
----------------------------------------
|
||||||
TOTAL 353 333 94%
|
TOTAL 353 20 94%
|
||||||
|
|
||||||
|
|
||||||
Distributed testing in each mode::
|
Again but spread over different hosts and different directories::
|
||||||
|
|
||||||
py.test --cov myproj --dist=each
|
py.test --cov myproj --dist load
|
||||||
--tx=popen//python=/usr/local/python265/bin/python
|
--tx ssh=memedough@host1//chdir=testenv1
|
||||||
--tx=popen//python=/usr/local/python27b1/bin/python
|
--tx ssh=memedough@host2//chdir=/tmp/testenv2//python=/tmp/env1/bin/python
|
||||||
|
--rsyncdir myproj --rsyncdir tests --rsync examples
|
||||||
tests/
|
tests/
|
||||||
|
|
||||||
Will produce a report for each slave::
|
Shows a terminal report::
|
||||||
|
|
||||||
-------------------- coverage: platform linux2, python 2.6.5-final-0 ---------------------
|
-------------------- coverage: platform linux2, python 2.6.4-final-0 ---------------------
|
||||||
Name Stmts Exec Cover Missing
|
Name Stmts Miss Cover
|
||||||
--------------------------------------------------
|
----------------------------------------
|
||||||
myproj/__init__ 2 2 100%
|
myproj/__init__ 2 0 100%
|
||||||
myproj/myproj 257 244 94% 24-26, 99, 149, 233-236, 297-298, 369-370
|
myproj/myproj 257 13 94%
|
||||||
myproj/feature4286 94 87 92% 183-188, 197
|
myproj/feature4286 94 7 92%
|
||||||
--------------------------------------------------
|
----------------------------------------
|
||||||
TOTAL 353 333 94%
|
TOTAL 353 20 94%
|
||||||
--------------------- coverage: platform linux2, python 2.7.0-beta-1 ---------------------
|
|
||||||
Name Stmts Exec Cover Missing
|
|
||||||
--------------------------------------------------
|
|
||||||
myproj/__init__ 2 2 100%
|
|
||||||
myproj/myproj 257 244 94% 24-26, 99, 149, 233-236, 297-298, 369-370
|
|
||||||
myproj/feature4286 94 87 92% 183-188, 197
|
|
||||||
--------------------------------------------------
|
|
||||||
TOTAL 353 333 94%
|
|
||||||
|
|
||||||
|
|
||||||
Distributed testing in each mode can also produce a single combined
|
Distributed Testing: Each
|
||||||
report. This is useful to get coverage information spanning things
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
such as all python versions::
|
|
||||||
|
|
||||||
py.test --cov myproj --cov-combine-each --dist=each
|
Distributed testing with dist mode set to each will report on the combined coverage of all slaves.
|
||||||
--tx=popen//python=/usr/local/python265/bin/python
|
Since each slave is running all tests this allows generating a combined coverage report for multiple
|
||||||
--tx=popen//python=/usr/local/python27b1/bin/python
|
environments.
|
||||||
|
|
||||||
|
Running distributed testing with dist mode set to each::
|
||||||
|
|
||||||
|
py.test --cov myproj --dist each
|
||||||
|
--tx popen//chdir=/tmp/testenv3//python=/usr/local/python27/bin/python
|
||||||
|
--tx ssh=memedough@host2//chdir=/tmp/testenv4//python=/tmp/env2/bin/python
|
||||||
|
--rsyncdir myproj --rsyncdir tests --rsync examples
|
||||||
tests/
|
tests/
|
||||||
|
|
||||||
Which looks like::
|
Shows a terminal report::
|
||||||
|
|
||||||
---------------------------------------- coverage ----------------------------------------
|
---------------------------------------- coverage ----------------------------------------
|
||||||
platform linux2, python 2.6.5-final-0
|
platform linux2, python 2.6.5-final-0
|
||||||
platform linux2, python 2.7.0-beta-1
|
platform linux2, python 2.7.0-final-0
|
||||||
Name Stmts Exec Cover Missing
|
Name Stmts Miss Cover
|
||||||
--------------------------------------------------
|
----------------------------------------
|
||||||
myproj/__init__ 2 2 100%
|
myproj/__init__ 2 0 100%
|
||||||
myproj/myproj 257 244 94% 24-26, 99, 149, 233-236, 297-298, 369-370
|
myproj/myproj 257 13 94%
|
||||||
myproj/feature4286 94 87 92% 183-188, 197
|
myproj/feature4286 94 7 92%
|
||||||
--------------------------------------------------
|
----------------------------------------
|
||||||
TOTAL 353 333 94%
|
TOTAL 353 20 94%
|
||||||
|
|
||||||
|
|
||||||
Reporting
|
Reporting
|
||||||
---------
|
---------
|
||||||
|
|
||||||
By default a terminal report is output. This report can be disabled
|
It is possible to generate any combination of the reports for a single test run.
|
||||||
if desired, such as when results are going to a continuous integration
|
|
||||||
system and the terminal output won't be seen.
|
|
||||||
|
|
||||||
In addition and without rerunning tests it is possible to generate
|
The available reports are terminal (with or without missing line numbers shown), HTML, XML and
|
||||||
annotated source code, a html report and an xml report.
|
annotated source code.
|
||||||
|
|
||||||
The directories for annotated source code and html reports can be
|
The terminal report without line numbers (default)::
|
||||||
specified as can the file name for the xml report.
|
|
||||||
|
|
||||||
Since testing often takes a non trivial amount of time at the end of
|
py.test --cov-report term --cov myproj tests/
|
||||||
testing any / all of the reports may be generated.
|
|
||||||
|
-------------------- coverage: platform linux2, python 2.6.4-final-0 ---------------------
|
||||||
|
Name Stmts Miss Cover
|
||||||
|
----------------------------------------
|
||||||
|
myproj/__init__ 2 0 100%
|
||||||
|
myproj/myproj 257 13 94%
|
||||||
|
myproj/feature4286 94 7 92%
|
||||||
|
----------------------------------------
|
||||||
|
TOTAL 353 20 94%
|
||||||
|
|
||||||
|
|
||||||
|
The terminal report with line numbers::
|
||||||
|
|
||||||
|
py.test --cov-report term-missing --cov myproj tests/
|
||||||
|
|
||||||
|
-------------------- coverage: platform linux2, python 2.6.4-final-0 ---------------------
|
||||||
|
Name Stmts Miss Cover Missing
|
||||||
|
--------------------------------------------------
|
||||||
|
myproj/__init__ 2 0 100%
|
||||||
|
myproj/myproj 257 13 94% 24-26, 99, 149, 233-236, 297-298, 369-370
|
||||||
|
myproj/feature4286 94 7 92% 183-188, 197
|
||||||
|
--------------------------------------------------
|
||||||
|
TOTAL 353 20 94%
|
||||||
|
|
||||||
|
|
||||||
|
The remaining three reports output to files without showing anything on the terminal (useful for
|
||||||
|
when the output is going to a continuous integration server)::
|
||||||
|
|
||||||
|
py.test --cov-report html --cov-report xml --cov-report annotate --cov myproj tests/
|
||||||
|
|
||||||
|
|
||||||
Coverage Data File
|
Coverage Data File
|
||||||
------------------
|
------------------
|
||||||
|
|
||||||
During testing there may be many data files with coverage data. These
|
The data file is erased at the beginning of testing to ensure clean data for each test run.
|
||||||
will have unique suffixes and will be combined at the end of testing.
|
|
||||||
|
|
||||||
Upon completion, for --dist=load (and also for --dist=each when the
|
The data file is left at the end of testing so that it is possible to use normal coverage tools to
|
||||||
--cov-combine-each option is used) there will only be one data file.
|
examine it.
|
||||||
|
|
||||||
For --dist=each there may be many data files where each one will have
|
|
||||||
the platform / python version info appended to the name.
|
|
||||||
|
|
||||||
These data files are left at the end of testing so that it is possible
|
|
||||||
to use normal coverage tools to examine them.
|
|
||||||
|
|
||||||
At the beginning of testing any data files that are about to be used
|
|
||||||
will first be erased so ensure the data is clean for each test run.
|
|
||||||
|
|
||||||
It is possible to set the name of the data file. If needed the
|
|
||||||
platform / python version will be appended automatically to this name.
|
|
||||||
|
|
||||||
|
|
||||||
Coverage Config File
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
Coverage by default will read its own config file. An alternative
|
|
||||||
file name may be specified or reading config can be disabled entirely.
|
|
||||||
|
|
||||||
Care has been taken to ensure that the coverage env vars and config
|
|
||||||
file options work the same under this plugin as they do under coverage
|
|
||||||
itself.
|
|
||||||
|
|
||||||
Since options may be specified in different ways the order of
|
|
||||||
precedence between pytest-cov and coverage from highest to lowest is:
|
|
||||||
|
|
||||||
1. pytest command line
|
|
||||||
2. pytest env var
|
|
||||||
3. pytest conftest
|
|
||||||
4. coverage env var
|
|
||||||
5. coverage config file
|
|
||||||
6. coverage default
|
|
||||||
|
|
||||||
|
|
||||||
Limitations
|
Limitations
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
For distributed testing the slaves must have the pytest-cov package
|
For distributed testing the slaves must have the pytest-cov package installed. This is needed since
|
||||||
installed. This is needed since the plugin must be registered through
|
the plugin must be registered through setuptools / distribute for pytest to start the plugin on the
|
||||||
setuptools / distribute for pytest to start the plugin on the slave.
|
slave.
|
||||||
|
|
||||||
|
For subprocess measurement environment variables must make it from the main process to the
|
||||||
|
subprocess. The python used by the subprocess must have pytest-cov installed. The subprocess must
|
||||||
|
do normal site initialisation so that the environment variables can be detected and coverage
|
||||||
|
started.
|
||||||
|
|
||||||
|
|
||||||
Acknowledgements
|
Acknowledgements
|
||||||
|
@ -193,14 +207,11 @@ Acknowledgements
|
||||||
|
|
||||||
Holger Krekel for pytest with its distributed testing support.
|
Holger Krekel for pytest with its distributed testing support.
|
||||||
|
|
||||||
Ned Batchelder for coverage and its ability to combine the coverage
|
Ned Batchelder for coverage and its ability to combine the coverage results of parallel runs.
|
||||||
results of parallel runs.
|
|
||||||
|
|
||||||
Whilst this plugin has been built fresh from the ground up to support
|
Whilst this plugin has been built fresh from the ground up to support distributed testing it has
|
||||||
distributed testing it has been influenced by the work done on
|
been influenced by the work done on pytest-coverage (Ross Lawley, James Mills, Holger Krekel) and
|
||||||
pytest-coverage (Ross Lawley, James Mills, Holger Krekel) and
|
nose-cover (Jason Pellerin) which are other coverage plugins for pytest and nose respectively.
|
||||||
nose-cover (Jason Pellerin) which are other coverage plugins for
|
|
||||||
pytest and nose respectively.
|
|
||||||
|
|
||||||
No doubt others have contributed to these tools as well.
|
No doubt others have contributed to these tools as well.
|
||||||
|
|
||||||
|
@ -208,43 +219,11 @@ command line options
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
|
|
||||||
``--cov-on``
|
``--cov=path``
|
||||||
enable coverage, only needed if not specifying any --cov options
|
measure coverage for filesystem path (multi-allowed)
|
||||||
``--cov=package``
|
``--cov-report=type``
|
||||||
collect coverage for the specified package (multi-allowed)
|
type of report to generate: term, term-missing, annotate, html, xml (multi-allowed)
|
||||||
``--cov-no-terminal``
|
``--cov-config=path``
|
||||||
disable printing a report on the terminal
|
config file for coverage, default: .coveragerc
|
||||||
``--cov-annotate``
|
|
||||||
generate an annotated source code report
|
|
||||||
``--cov-html``
|
|
||||||
generate a html report
|
|
||||||
``--cov-xml``
|
|
||||||
generate an xml report
|
|
||||||
``--cov-annotate-dir=dir``
|
|
||||||
directory for the annotate report, default: %default
|
|
||||||
``--cov-html-dir=dir``
|
|
||||||
directory for the html report, default: coverage_html
|
|
||||||
``--cov-xml-file=path``
|
|
||||||
file for the xml report, default: coverage.xml
|
|
||||||
``--cov-data-file=path``
|
|
||||||
file containing coverage data, default: .coverage
|
|
||||||
``--cov-combine-each``
|
|
||||||
for dist=each mode produce a single combined report
|
|
||||||
``--cov-branch``
|
|
||||||
enable branch coverage
|
|
||||||
``--cov-pylib``
|
|
||||||
enable python library coverage
|
|
||||||
``--cov-timid``
|
|
||||||
enable slower and simpler tracing
|
|
||||||
``--cov-no-missing-lines``
|
|
||||||
disable showing missing lines, only relevant to the terminal report
|
|
||||||
``--cov-no-missing-files``
|
|
||||||
disable showing message about missing source files
|
|
||||||
``--cov-omit=prefix1,prefix2,...``
|
|
||||||
ignore files with these prefixes
|
|
||||||
``--cov-no-config``
|
|
||||||
disable coverage reading its config file
|
|
||||||
``--cov-config-file=path``
|
|
||||||
config file for coverage, default: %default
|
|
||||||
|
|
||||||
.. include:: links.txt
|
.. include:: links.txt
|
||||||
|
|
|
@ -6,10 +6,27 @@ Write and report coverage data with the 'coverage' package.
|
||||||
.. contents::
|
.. contents::
|
||||||
:local:
|
:local:
|
||||||
|
|
||||||
Original code by Ross Lawley.
|
Note: Original code by Ross Lawley.
|
||||||
|
|
||||||
Requires Ned Batchelder's excellent coverage:
|
Install
|
||||||
http://nedbatchelder.com/code/coverage/
|
--------------
|
||||||
|
|
||||||
|
Use pip to (un)install::
|
||||||
|
|
||||||
|
pip install pytest-coverage
|
||||||
|
pip uninstall pytest-coverage
|
||||||
|
|
||||||
|
or alternatively use easy_install to install::
|
||||||
|
|
||||||
|
easy_install pytest-coverage
|
||||||
|
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-------------
|
||||||
|
|
||||||
|
To get full test coverage reports for a particular package type::
|
||||||
|
|
||||||
|
py.test --cover-report=report
|
||||||
|
|
||||||
command line options
|
command line options
|
||||||
--------------------
|
--------------------
|
||||||
|
@ -21,8 +38,11 @@ command line options
|
||||||
html: Directory for html output.
|
html: Directory for html output.
|
||||||
report: Output a text report.
|
report: Output a text report.
|
||||||
annotate: Annotate your source code for which lines were executed and which were not.
|
annotate: Annotate your source code for which lines were executed and which were not.
|
||||||
|
xml: Output an xml report compatible with the cobertura plugin for hudson.
|
||||||
``--cover-directory=DIRECTORY``
|
``--cover-directory=DIRECTORY``
|
||||||
Directory for the reports (html / annotate results) defaults to ./coverage
|
Directory for the reports (html / annotate results) defaults to ./coverage
|
||||||
|
``--cover-xml-file=XML_FILE``
|
||||||
|
File for the xml report defaults to ./coverage.xml
|
||||||
``--cover-show-missing``
|
``--cover-show-missing``
|
||||||
Show missing files
|
Show missing files
|
||||||
``--cover-ignore-errors=IGNORE_ERRORS``
|
``--cover-ignore-errors=IGNORE_ERRORS``
|
||||||
|
|
|
@ -6,16 +6,29 @@ report test coverage using the 'figleaf' package.
|
||||||
.. contents::
|
.. contents::
|
||||||
:local:
|
:local:
|
||||||
|
|
||||||
|
Install
|
||||||
|
---------------
|
||||||
|
|
||||||
|
To install the plugin issue::
|
||||||
|
|
||||||
|
easy_install pytest-figleaf # or
|
||||||
|
pip install pytest-figleaf
|
||||||
|
|
||||||
|
and if you are using pip you can also uninstall::
|
||||||
|
|
||||||
|
pip uninstall pytest-figleaf
|
||||||
|
|
||||||
|
|
||||||
Usage
|
Usage
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
after pip or easy_install mediated installation of ``pytest-figleaf`` you can type::
|
After installation you can simply type::
|
||||||
|
|
||||||
py.test --figleaf [...]
|
py.test --figleaf [...]
|
||||||
|
|
||||||
to enable figleaf coverage in your test run. A default ".figleaf" data file
|
to enable figleaf coverage in your test run. A default ".figleaf" data file
|
||||||
and "html" directory will be created. You can use ``--fig-data``
|
and "html" directory will be created. You can use command line options
|
||||||
and ``fig-html`` to modify the paths.
|
to control where data and html files are created.
|
||||||
|
|
||||||
command line options
|
command line options
|
||||||
--------------------
|
--------------------
|
||||||
|
|
|
@ -1,47 +1,47 @@
|
||||||
.. _`helpconfig`: helpconfig.html
|
.. _`helpconfig`: helpconfig.html
|
||||||
.. _`pytest_recwarn.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.2/py/_plugin/pytest_recwarn.py
|
.. _`pytest_recwarn.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_recwarn.py
|
||||||
.. _`unittest`: unittest.html
|
.. _`unittest`: unittest.html
|
||||||
.. _`pytest_monkeypatch.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.2/py/_plugin/pytest_monkeypatch.py
|
.. _`pytest_monkeypatch.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_monkeypatch.py
|
||||||
.. _`pytest_genscript.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.2/py/_plugin/pytest_genscript.py
|
.. _`pytest_genscript.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_genscript.py
|
||||||
.. _`pastebin`: pastebin.html
|
.. _`pastebin`: pastebin.html
|
||||||
.. _`skipping`: skipping.html
|
.. _`skipping`: skipping.html
|
||||||
.. _`genscript`: genscript.html
|
.. _`genscript`: genscript.html
|
||||||
.. _`plugins`: index.html
|
.. _`plugins`: index.html
|
||||||
.. _`mark`: mark.html
|
.. _`mark`: mark.html
|
||||||
.. _`tmpdir`: tmpdir.html
|
.. _`tmpdir`: tmpdir.html
|
||||||
.. _`pytest_doctest.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.2/py/_plugin/pytest_doctest.py
|
.. _`pytest_doctest.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_doctest.py
|
||||||
.. _`capture`: capture.html
|
.. _`capture`: capture.html
|
||||||
.. _`pytest_nose.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.2/py/_plugin/pytest_nose.py
|
.. _`pytest_nose.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_nose.py
|
||||||
.. _`pytest_restdoc.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.2/py/_plugin/pytest_restdoc.py
|
.. _`pytest_restdoc.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_restdoc.py
|
||||||
.. _`restdoc`: restdoc.html
|
.. _`restdoc`: restdoc.html
|
||||||
.. _`xdist`: xdist.html
|
.. _`xdist`: xdist.html
|
||||||
.. _`pytest_pastebin.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.2/py/_plugin/pytest_pastebin.py
|
.. _`pytest_pastebin.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_pastebin.py
|
||||||
.. _`pytest_tmpdir.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.2/py/_plugin/pytest_tmpdir.py
|
.. _`pytest_tmpdir.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_tmpdir.py
|
||||||
.. _`terminal`: terminal.html
|
.. _`terminal`: terminal.html
|
||||||
.. _`pytest_hooklog.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.2/py/_plugin/pytest_hooklog.py
|
.. _`pytest_hooklog.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_hooklog.py
|
||||||
.. _`capturelog`: capturelog.html
|
.. _`capturelog`: capturelog.html
|
||||||
.. _`junitxml`: junitxml.html
|
.. _`junitxml`: junitxml.html
|
||||||
.. _`pytest_skipping.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.2/py/_plugin/pytest_skipping.py
|
.. _`pytest_skipping.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_skipping.py
|
||||||
.. _`checkout the py.test development version`: ../../install.html#checkout
|
.. _`checkout the py.test development version`: ../../install.html#checkout
|
||||||
.. _`pytest_helpconfig.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.2/py/_plugin/pytest_helpconfig.py
|
.. _`pytest_helpconfig.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_helpconfig.py
|
||||||
.. _`oejskit`: oejskit.html
|
.. _`oejskit`: oejskit.html
|
||||||
.. _`doctest`: doctest.html
|
.. _`doctest`: doctest.html
|
||||||
.. _`pytest_mark.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.2/py/_plugin/pytest_mark.py
|
.. _`pytest_mark.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_mark.py
|
||||||
.. _`get in contact`: ../../contact.html
|
.. _`get in contact`: ../../contact.html
|
||||||
.. _`pytest_capture.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.2/py/_plugin/pytest_capture.py
|
.. _`pytest_capture.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_capture.py
|
||||||
.. _`figleaf`: figleaf.html
|
.. _`figleaf`: figleaf.html
|
||||||
.. _`customize`: ../customize.html
|
.. _`customize`: ../customize.html
|
||||||
.. _`hooklog`: hooklog.html
|
.. _`hooklog`: hooklog.html
|
||||||
.. _`pytest_terminal.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.2/py/_plugin/pytest_terminal.py
|
.. _`pytest_terminal.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_terminal.py
|
||||||
.. _`recwarn`: recwarn.html
|
.. _`recwarn`: recwarn.html
|
||||||
.. _`pytest_pdb.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.2/py/_plugin/pytest_pdb.py
|
.. _`pytest_pdb.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_pdb.py
|
||||||
.. _`monkeypatch`: monkeypatch.html
|
.. _`monkeypatch`: monkeypatch.html
|
||||||
.. _`coverage`: coverage.html
|
.. _`coverage`: coverage.html
|
||||||
.. _`resultlog`: resultlog.html
|
.. _`resultlog`: resultlog.html
|
||||||
.. _`cov`: cov.html
|
.. _`cov`: cov.html
|
||||||
.. _`pytest_junitxml.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.2/py/_plugin/pytest_junitxml.py
|
.. _`pytest_junitxml.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_junitxml.py
|
||||||
.. _`django`: django.html
|
.. _`django`: django.html
|
||||||
.. _`pytest_unittest.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.2/py/_plugin/pytest_unittest.py
|
.. _`pytest_unittest.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_unittest.py
|
||||||
.. _`nose`: nose.html
|
.. _`nose`: nose.html
|
||||||
.. _`pytest_resultlog.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.2/py/_plugin/pytest_resultlog.py
|
.. _`pytest_resultlog.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.4/py/_plugin/pytest_resultlog.py
|
||||||
.. _`pdb`: pdb.html
|
.. _`pdb`: pdb.html
|
||||||
|
|
|
@ -8,7 +8,7 @@ dictionary or an import path.
|
||||||
|
|
||||||
(c) Holger Krekel and others, 2004-2010
|
(c) Holger Krekel and others, 2004-2010
|
||||||
"""
|
"""
|
||||||
__version__ = version = "1.3.4a1"
|
__version__ = version = "1.4.0a1"
|
||||||
|
|
||||||
import py.apipkg
|
import py.apipkg
|
||||||
|
|
||||||
|
@ -45,15 +45,9 @@ py.apipkg.initpkg(__name__, dict(
|
||||||
'Directory' : '._test.collect:Directory',
|
'Directory' : '._test.collect:Directory',
|
||||||
'File' : '._test.collect:File',
|
'File' : '._test.collect:File',
|
||||||
'Item' : '._test.collect:Item',
|
'Item' : '._test.collect:Item',
|
||||||
'Module' : '._test.pycollect:Module',
|
|
||||||
'Class' : '._test.pycollect:Class',
|
|
||||||
'Instance' : '._test.pycollect:Instance',
|
|
||||||
'Generator' : '._test.pycollect:Generator',
|
|
||||||
'Function' : '._test.pycollect:Function',
|
|
||||||
'_fillfuncargs' : '._test.funcargs:fillfuncargs',
|
|
||||||
},
|
},
|
||||||
'cmdline': {
|
'cmdline': {
|
||||||
'main' : '._test.cmdline:main', # backward compat
|
'main' : '._test.session:main', # backward compat
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -99,6 +93,7 @@ py.apipkg.initpkg(__name__, dict(
|
||||||
'_AssertionError' : '._code.assertion:AssertionError',
|
'_AssertionError' : '._code.assertion:AssertionError',
|
||||||
'_reinterpret_old' : '._code.assertion:reinterpret_old',
|
'_reinterpret_old' : '._code.assertion:reinterpret_old',
|
||||||
'_reinterpret' : '._code.assertion:reinterpret',
|
'_reinterpret' : '._code.assertion:reinterpret',
|
||||||
|
'_reprcompare' : '._code.assertion:_reprcompare',
|
||||||
},
|
},
|
||||||
|
|
||||||
# backports and additions of builtins
|
# backports and additions of builtins
|
||||||
|
@ -111,6 +106,7 @@ py.apipkg.initpkg(__name__, dict(
|
||||||
'frozenset' : '._builtin:frozenset',
|
'frozenset' : '._builtin:frozenset',
|
||||||
'BaseException' : '._builtin:BaseException',
|
'BaseException' : '._builtin:BaseException',
|
||||||
'GeneratorExit' : '._builtin:GeneratorExit',
|
'GeneratorExit' : '._builtin:GeneratorExit',
|
||||||
|
'_sysex' : '._builtin:_sysex',
|
||||||
'print_' : '._builtin:print_',
|
'print_' : '._builtin:print_',
|
||||||
'_reraise' : '._builtin:_reraise',
|
'_reraise' : '._builtin:_reraise',
|
||||||
'_tryimport' : '._builtin:_tryimport',
|
'_tryimport' : '._builtin:_tryimport',
|
||||||
|
|
|
@ -87,6 +87,8 @@ except NameError:
|
||||||
pass
|
pass
|
||||||
GeneratorExit.__module__ = 'exceptions'
|
GeneratorExit.__module__ = 'exceptions'
|
||||||
|
|
||||||
|
_sysex = (KeyboardInterrupt, SystemExit, MemoryError, GeneratorExit)
|
||||||
|
|
||||||
if sys.version_info >= (3, 0):
|
if sys.version_info >= (3, 0):
|
||||||
exec ("print_ = print ; exec_=exec")
|
exec ("print_ = print ; exec_=exec")
|
||||||
import builtins
|
import builtins
|
||||||
|
|
|
@ -162,10 +162,7 @@ class DebugInterpreter(ast.NodeVisitor):
|
||||||
def visit_Compare(self, comp):
|
def visit_Compare(self, comp):
|
||||||
left = comp.left
|
left = comp.left
|
||||||
left_explanation, left_result = self.visit(left)
|
left_explanation, left_result = self.visit(left)
|
||||||
got_result = False
|
|
||||||
for op, next_op in zip(comp.ops, comp.comparators):
|
for op, next_op in zip(comp.ops, comp.comparators):
|
||||||
if got_result and not result:
|
|
||||||
break
|
|
||||||
next_explanation, next_result = self.visit(next_op)
|
next_explanation, next_result = self.visit(next_op)
|
||||||
op_symbol = operator_map[op.__class__]
|
op_symbol = operator_map[op.__class__]
|
||||||
explanation = "%s %s %s" % (left_explanation, op_symbol,
|
explanation = "%s %s %s" % (left_explanation, op_symbol,
|
||||||
|
@ -177,9 +174,15 @@ class DebugInterpreter(ast.NodeVisitor):
|
||||||
__exprinfo_right=next_result)
|
__exprinfo_right=next_result)
|
||||||
except Exception:
|
except Exception:
|
||||||
raise Failure(explanation)
|
raise Failure(explanation)
|
||||||
else:
|
if not result:
|
||||||
got_result = True
|
break
|
||||||
left_explanation, left_result = next_explanation, next_result
|
left_explanation, left_result = next_explanation, next_result
|
||||||
|
|
||||||
|
rcomp = py.code._reprcompare
|
||||||
|
if rcomp:
|
||||||
|
res = rcomp(op_symbol, left_result, next_result)
|
||||||
|
if res:
|
||||||
|
explanation = res
|
||||||
return explanation, result
|
return explanation, result
|
||||||
|
|
||||||
def visit_BoolOp(self, boolop):
|
def visit_BoolOp(self, boolop):
|
||||||
|
|
|
@ -3,7 +3,7 @@ import sys, inspect
|
||||||
from compiler import parse, ast, pycodegen
|
from compiler import parse, ast, pycodegen
|
||||||
from py._code.assertion import BuiltinAssertionError, _format_explanation
|
from py._code.assertion import BuiltinAssertionError, _format_explanation
|
||||||
|
|
||||||
passthroughex = (KeyboardInterrupt, SystemExit, MemoryError)
|
passthroughex = py.builtin._sysex
|
||||||
|
|
||||||
class Failure:
|
class Failure:
|
||||||
def __init__(self, node):
|
def __init__(self, node):
|
||||||
|
|
|
@ -3,14 +3,23 @@ import py
|
||||||
|
|
||||||
BuiltinAssertionError = py.builtin.builtins.AssertionError
|
BuiltinAssertionError = py.builtin.builtins.AssertionError
|
||||||
|
|
||||||
|
_reprcompare = None # if set, will be called by assert reinterp for comparison ops
|
||||||
|
|
||||||
def _format_explanation(explanation):
|
def _format_explanation(explanation):
|
||||||
# uck! See CallFunc for where \n{ and \n} escape sequences are used
|
"""This formats an explanation
|
||||||
|
|
||||||
|
Normally all embedded newlines are escaped, however there are
|
||||||
|
three exceptions: \n{, \n} and \n~. The first two are intended
|
||||||
|
cover nested explanations, see function and attribute explanations
|
||||||
|
for examples (.visit_Call(), visit_Attribute()). The last one is
|
||||||
|
for when one explanation needs to span multiple lines, e.g. when
|
||||||
|
displaying diffs.
|
||||||
|
"""
|
||||||
raw_lines = (explanation or '').split('\n')
|
raw_lines = (explanation or '').split('\n')
|
||||||
# escape newlines not followed by { and }
|
# escape newlines not followed by {, } and ~
|
||||||
lines = [raw_lines[0]]
|
lines = [raw_lines[0]]
|
||||||
for l in raw_lines[1:]:
|
for l in raw_lines[1:]:
|
||||||
if l.startswith('{') or l.startswith('}'):
|
if l.startswith('{') or l.startswith('}') or l.startswith('~'):
|
||||||
lines.append(l)
|
lines.append(l)
|
||||||
else:
|
else:
|
||||||
lines[-1] += '\\n' + l
|
lines[-1] += '\\n' + l
|
||||||
|
@ -28,23 +37,25 @@ def _format_explanation(explanation):
|
||||||
stackcnt[-1] += 1
|
stackcnt[-1] += 1
|
||||||
stackcnt.append(0)
|
stackcnt.append(0)
|
||||||
result.append(' +' + ' '*(len(stack)-1) + s + line[1:])
|
result.append(' +' + ' '*(len(stack)-1) + s + line[1:])
|
||||||
else:
|
elif line.startswith('}'):
|
||||||
assert line.startswith('}')
|
assert line.startswith('}')
|
||||||
stack.pop()
|
stack.pop()
|
||||||
stackcnt.pop()
|
stackcnt.pop()
|
||||||
result[stack[-1]] += line[1:]
|
result[stack[-1]] += line[1:]
|
||||||
|
else:
|
||||||
|
assert line.startswith('~')
|
||||||
|
result.append(' '*len(stack) + line[1:])
|
||||||
assert len(stack) == 1
|
assert len(stack) == 1
|
||||||
return '\n'.join(result)
|
return '\n'.join(result)
|
||||||
|
|
||||||
|
|
||||||
class AssertionError(BuiltinAssertionError):
|
class AssertionError(BuiltinAssertionError):
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
BuiltinAssertionError.__init__(self, *args)
|
BuiltinAssertionError.__init__(self, *args)
|
||||||
if args:
|
if args:
|
||||||
try:
|
try:
|
||||||
self.msg = str(args[0])
|
self.msg = str(args[0])
|
||||||
except (KeyboardInterrupt, SystemExit):
|
except py.builtin._sysex:
|
||||||
raise
|
raise
|
||||||
except:
|
except:
|
||||||
self.msg = "<[broken __repr__] %s at %0xd>" %(
|
self.msg = "<[broken __repr__] %s at %0xd>" %(
|
||||||
|
|
|
@ -189,7 +189,7 @@ class TracebackEntry(object):
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return self.frame.eval("__tracebackhide__")
|
return self.frame.eval("__tracebackhide__")
|
||||||
except (SystemExit, KeyboardInterrupt):
|
except py.builtin._sysex:
|
||||||
raise
|
raise
|
||||||
except:
|
except:
|
||||||
return False
|
return False
|
||||||
|
@ -354,9 +354,17 @@ class ExceptionInfo(object):
|
||||||
abspath=False, tbfilter=True, funcargs=False):
|
abspath=False, tbfilter=True, funcargs=False):
|
||||||
""" return str()able representation of this exception info.
|
""" return str()able representation of this exception info.
|
||||||
showlocals: show locals per traceback entry
|
showlocals: show locals per traceback entry
|
||||||
style: long|short|no traceback style
|
style: long|short|no|native traceback style
|
||||||
tbfilter: hide entries (where __tracebackhide__ is true)
|
tbfilter: hide entries (where __tracebackhide__ is true)
|
||||||
"""
|
"""
|
||||||
|
if style == 'native':
|
||||||
|
import traceback
|
||||||
|
return ''.join(traceback.format_exception(
|
||||||
|
self.type,
|
||||||
|
self.value,
|
||||||
|
self.traceback[0]._rawentry,
|
||||||
|
))
|
||||||
|
|
||||||
fmt = FormattedExcinfo(showlocals=showlocals, style=style,
|
fmt = FormattedExcinfo(showlocals=showlocals, style=style,
|
||||||
abspath=abspath, tbfilter=tbfilter, funcargs=funcargs)
|
abspath=abspath, tbfilter=tbfilter, funcargs=funcargs)
|
||||||
return fmt.repr_excinfo(self)
|
return fmt.repr_excinfo(self)
|
||||||
|
|
|
@ -276,7 +276,7 @@ def getfslineno(obj):
|
||||||
def findsource(obj):
|
def findsource(obj):
|
||||||
try:
|
try:
|
||||||
sourcelines, lineno = py.std.inspect.findsource(obj)
|
sourcelines, lineno = py.std.inspect.findsource(obj)
|
||||||
except (KeyboardInterrupt, SystemExit):
|
except py.builtin._sysex:
|
||||||
raise
|
raise
|
||||||
except:
|
except:
|
||||||
return None, None
|
return None, None
|
||||||
|
|
|
@ -5,8 +5,6 @@ builtin_repr = repr
|
||||||
|
|
||||||
reprlib = py.builtin._tryimport('repr', 'reprlib')
|
reprlib = py.builtin._tryimport('repr', 'reprlib')
|
||||||
|
|
||||||
sysex = (KeyboardInterrupt, MemoryError, SystemExit)
|
|
||||||
|
|
||||||
class SafeRepr(reprlib.Repr):
|
class SafeRepr(reprlib.Repr):
|
||||||
""" subclass of repr.Repr that limits the resulting size of repr()
|
""" subclass of repr.Repr that limits the resulting size of repr()
|
||||||
and includes information on exceptions raised during the call.
|
and includes information on exceptions raised during the call.
|
||||||
|
@ -21,7 +19,7 @@ class SafeRepr(reprlib.Repr):
|
||||||
try:
|
try:
|
||||||
# Try the vanilla repr and make sure that the result is a string
|
# Try the vanilla repr and make sure that the result is a string
|
||||||
s = call(x, *args)
|
s = call(x, *args)
|
||||||
except sysex:
|
except py.builtin._sysex:
|
||||||
raise
|
raise
|
||||||
except:
|
except:
|
||||||
cls, e, tb = sys.exc_info()
|
cls, e, tb = sys.exc_info()
|
||||||
|
|
|
@ -26,7 +26,7 @@ def _getdimensions():
|
||||||
def get_terminal_width():
|
def get_terminal_width():
|
||||||
try:
|
try:
|
||||||
height, width = _getdimensions()
|
height, width = _getdimensions()
|
||||||
except (SystemExit, KeyboardInterrupt):
|
except py.builtin._sysex:
|
||||||
raise
|
raise
|
||||||
except:
|
except:
|
||||||
# FALLBACK
|
# FALLBACK
|
||||||
|
|
|
@ -519,6 +519,8 @@ class LocalPath(FSBase):
|
||||||
pkg = __import__(pkgpath.basename, None, None, [])
|
pkg = __import__(pkgpath.basename, None, None, [])
|
||||||
names = self.new(ext='').relto(pkgpath.dirpath())
|
names = self.new(ext='').relto(pkgpath.dirpath())
|
||||||
names = names.split(self.sep)
|
names = names.split(self.sep)
|
||||||
|
if names and names[-1] == "__init__":
|
||||||
|
names.pop()
|
||||||
modname = ".".join(names)
|
modname = ".".join(names)
|
||||||
else:
|
else:
|
||||||
# no package scope, still make it possible
|
# no package scope, still make it possible
|
||||||
|
@ -532,6 +534,7 @@ class LocalPath(FSBase):
|
||||||
elif modfile.endswith('$py.class'):
|
elif modfile.endswith('$py.class'):
|
||||||
modfile = modfile[:-9] + '.py'
|
modfile = modfile[:-9] + '.py'
|
||||||
if modfile.endswith("__init__.py"):
|
if modfile.endswith("__init__.py"):
|
||||||
|
if self.basename != "__init__.py":
|
||||||
modfile = modfile[:-12]
|
modfile = modfile[:-12]
|
||||||
if not self.samefile(modfile):
|
if not self.samefile(modfile):
|
||||||
raise self.ImportMismatchError(modname, modfile, self)
|
raise self.ImportMismatchError(modname, modfile, self)
|
||||||
|
|
|
@ -20,6 +20,14 @@ def pytest_configure(config):
|
||||||
and all plugins and initial conftest files been loaded.
|
and all plugins and initial conftest files been loaded.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def pytest_cmdline_main(config):
|
||||||
|
""" called for performing the main (cmdline) action. """
|
||||||
|
pytest_cmdline_main.firstresult = True
|
||||||
|
|
||||||
|
def pytest_runtest_mainloop(session):
|
||||||
|
""" called for performing the main runtest loop (after collection. """
|
||||||
|
pytest_runtest_mainloop.firstresult = True
|
||||||
|
|
||||||
def pytest_unconfigure(config):
|
def pytest_unconfigure(config):
|
||||||
""" called before test process is exited. """
|
""" called before test process is exited. """
|
||||||
|
|
||||||
|
@ -27,6 +35,16 @@ def pytest_unconfigure(config):
|
||||||
# collection hooks
|
# collection hooks
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def pytest_perform_collection(session):
|
||||||
|
""" perform the collection protocol for the given session. """
|
||||||
|
pytest_perform_collection.firstresult = True
|
||||||
|
|
||||||
|
def pytest_collection_modifyitems(config, items):
|
||||||
|
""" called to allow filtering and selecting of test items (inplace). """
|
||||||
|
|
||||||
|
def pytest_log_finishcollection(collection):
|
||||||
|
""" called after collection has finished. """
|
||||||
|
|
||||||
def pytest_ignore_collect(path, config):
|
def pytest_ignore_collect(path, config):
|
||||||
""" return true value to prevent considering this path for collection.
|
""" return true value to prevent considering this path for collection.
|
||||||
This hook is consulted for all files and directories prior to considering
|
This hook is consulted for all files and directories prior to considering
|
||||||
|
@ -41,9 +59,13 @@ pytest_collect_directory.firstresult = True
|
||||||
def pytest_collect_file(path, parent):
|
def pytest_collect_file(path, parent):
|
||||||
""" return Collection node or None for the given path. """
|
""" return Collection node or None for the given path. """
|
||||||
|
|
||||||
|
# logging hooks for collection
|
||||||
def pytest_collectstart(collector):
|
def pytest_collectstart(collector):
|
||||||
""" collector starts collecting. """
|
""" collector starts collecting. """
|
||||||
|
|
||||||
|
def pytest_log_itemcollect(item):
|
||||||
|
""" we just collected a test item. """
|
||||||
|
|
||||||
def pytest_collectreport(report):
|
def pytest_collectreport(report):
|
||||||
""" collector finished collecting. """
|
""" collector finished collecting. """
|
||||||
|
|
||||||
|
@ -54,10 +76,6 @@ def pytest_make_collect_report(collector):
|
||||||
""" perform a collection and return a collection. """
|
""" perform a collection and return a collection. """
|
||||||
pytest_make_collect_report.firstresult = True
|
pytest_make_collect_report.firstresult = True
|
||||||
|
|
||||||
# XXX rename to item_collected()? meaning in distribution context?
|
|
||||||
def pytest_itemstart(item, node=None):
|
|
||||||
""" test item gets collected. """
|
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# Python test function related hooks
|
# Python test function related hooks
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
|
@ -84,11 +102,16 @@ def pytest_generate_tests(metafunc):
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# generic runtest related hooks
|
# generic runtest related hooks
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
|
def pytest_itemstart(item, node=None):
|
||||||
|
""" (deprecated, use pytest_runtest_logstart). """
|
||||||
|
|
||||||
def pytest_runtest_protocol(item):
|
def pytest_runtest_protocol(item):
|
||||||
""" implement fixture, run and report about the given test item. """
|
""" implement fixture, run and report about the given test item. """
|
||||||
pytest_runtest_protocol.firstresult = True
|
pytest_runtest_protocol.firstresult = True
|
||||||
|
|
||||||
|
def pytest_runtest_logstart(nodeid, location, fspath):
|
||||||
|
""" signal the start of a test run. """
|
||||||
|
|
||||||
def pytest_runtest_setup(item):
|
def pytest_runtest_setup(item):
|
||||||
""" called before pytest_runtest_call(). """
|
""" called before pytest_runtest_call(). """
|
||||||
|
|
||||||
|
@ -110,7 +133,7 @@ def pytest__teardown_final(session):
|
||||||
""" called before test session finishes. """
|
""" called before test session finishes. """
|
||||||
pytest__teardown_final.firstresult = True
|
pytest__teardown_final.firstresult = True
|
||||||
|
|
||||||
def pytest__teardown_final_logerror(report):
|
def pytest__teardown_final_logerror(report, session):
|
||||||
""" called if runtest_teardown_final failed. """
|
""" called if runtest_teardown_final failed. """
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
|
@ -123,6 +146,20 @@ def pytest_sessionstart(session):
|
||||||
def pytest_sessionfinish(session, exitstatus):
|
def pytest_sessionfinish(session, exitstatus):
|
||||||
""" whole test run finishes. """
|
""" whole test run finishes. """
|
||||||
|
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
# hooks for customising the assert methods
|
||||||
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def pytest_assertrepr_compare(config, op, left, right):
|
||||||
|
"""return explanation for comparisons in failing assert expressions.
|
||||||
|
|
||||||
|
Return None for no custom explanation, otherwise return a list
|
||||||
|
of strings. The strings will be joined by newlines but any newlines
|
||||||
|
*in* a string will be escaped. Note that all but the first line will
|
||||||
|
be indented sligthly, the intention is for the first line to be a summary.
|
||||||
|
"""
|
||||||
|
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
# hooks for influencing reporting (invoked from pytest_terminal)
|
# hooks for influencing reporting (invoked from pytest_terminal)
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
|
@ -138,8 +175,10 @@ def pytest_terminal_summary(terminalreporter):
|
||||||
""" add additional section in terminal summary reporting. """
|
""" add additional section in terminal summary reporting. """
|
||||||
|
|
||||||
def pytest_report_iteminfo(item):
|
def pytest_report_iteminfo(item):
|
||||||
""" return (fspath, lineno, name) for the item.
|
""" return (fspath, lineno, domainpath) location info for the item.
|
||||||
the information is used for result display and to sort tests
|
the information is used for result display and to sort tests.
|
||||||
|
fspath,lineno: file and linenumber of source of item definition.
|
||||||
|
domainpath: custom id - e.g. for python: dotted import address
|
||||||
"""
|
"""
|
||||||
pytest_report_iteminfo.firstresult = True
|
pytest_report_iteminfo.firstresult = True
|
||||||
|
|
||||||
|
|
|
@ -87,6 +87,28 @@ class HookRecorder:
|
||||||
l.append(call)
|
l.append(call)
|
||||||
return l
|
return l
|
||||||
|
|
||||||
|
def contains(self, entries):
|
||||||
|
from py.builtin import print_
|
||||||
|
i = 0
|
||||||
|
entries = list(entries)
|
||||||
|
backlocals = py.std.sys._getframe(1).f_locals
|
||||||
|
while entries:
|
||||||
|
name, check = entries.pop(0)
|
||||||
|
for ind, call in enumerate(self.calls[i:]):
|
||||||
|
if call._name == name:
|
||||||
|
print_("NAMEMATCH", name, call)
|
||||||
|
if eval(check, backlocals, call.__dict__):
|
||||||
|
print_("CHECKERMATCH", repr(check), "->", call)
|
||||||
|
else:
|
||||||
|
print_("NOCHECKERMATCH", repr(check), "-", call)
|
||||||
|
continue
|
||||||
|
i += ind + 1
|
||||||
|
break
|
||||||
|
print_("NONAMEMATCH", name, "with", call)
|
||||||
|
else:
|
||||||
|
raise AssertionError("could not find %r in %r" %(
|
||||||
|
name, self.calls[i:]))
|
||||||
|
|
||||||
def popcall(self, name):
|
def popcall(self, name):
|
||||||
for i, call in enumerate(self.calls):
|
for i, call in enumerate(self.calls):
|
||||||
if call._name == name:
|
if call._name == name:
|
||||||
|
|
|
@ -8,15 +8,29 @@ def pytest_addoption(parser):
|
||||||
help="disable python assert expression reinterpretation."),
|
help="disable python assert expression reinterpretation."),
|
||||||
|
|
||||||
def pytest_configure(config):
|
def pytest_configure(config):
|
||||||
|
# The _pytesthook attribute on the AssertionError is used by
|
||||||
|
# py._code._assertionnew to detect this plugin was loaded and in
|
||||||
|
# turn call the hooks defined here as part of the
|
||||||
|
# DebugInterpreter.
|
||||||
if not config.getvalue("noassert") and not config.getvalue("nomagic"):
|
if not config.getvalue("noassert") and not config.getvalue("nomagic"):
|
||||||
warn_about_missing_assertion()
|
warn_about_missing_assertion()
|
||||||
config._oldassertion = py.builtin.builtins.AssertionError
|
config._oldassertion = py.builtin.builtins.AssertionError
|
||||||
|
config._oldbinrepr = py.code._reprcompare
|
||||||
py.builtin.builtins.AssertionError = py.code._AssertionError
|
py.builtin.builtins.AssertionError = py.code._AssertionError
|
||||||
|
def callbinrepr(op, left, right):
|
||||||
|
hook_result = config.hook.pytest_assertrepr_compare(
|
||||||
|
config=config, op=op, left=left, right=right)
|
||||||
|
for new_expl in hook_result:
|
||||||
|
if new_expl:
|
||||||
|
return '\n~'.join(new_expl)
|
||||||
|
py.code._reprcompare = callbinrepr
|
||||||
|
|
||||||
def pytest_unconfigure(config):
|
def pytest_unconfigure(config):
|
||||||
if hasattr(config, '_oldassertion'):
|
if hasattr(config, '_oldassertion'):
|
||||||
py.builtin.builtins.AssertionError = config._oldassertion
|
py.builtin.builtins.AssertionError = config._oldassertion
|
||||||
|
py.code._reprcompare = config._oldbinrepr
|
||||||
del config._oldassertion
|
del config._oldassertion
|
||||||
|
del config._oldbinrepr
|
||||||
|
|
||||||
def warn_about_missing_assertion():
|
def warn_about_missing_assertion():
|
||||||
try:
|
try:
|
||||||
|
@ -26,3 +40,109 @@ def warn_about_missing_assertion():
|
||||||
else:
|
else:
|
||||||
py.std.warnings.warn("Assertions are turned off!"
|
py.std.warnings.warn("Assertions are turned off!"
|
||||||
" (are you using python -O?)")
|
" (are you using python -O?)")
|
||||||
|
|
||||||
|
|
||||||
|
# Provide basestring in python3
|
||||||
|
try:
|
||||||
|
basestring = basestring
|
||||||
|
except NameError:
|
||||||
|
basestring = str
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_assertrepr_compare(op, left, right):
|
||||||
|
"""return specialised explanations for some operators/operands"""
|
||||||
|
left_repr = py.io.saferepr(left, maxsize=30)
|
||||||
|
right_repr = py.io.saferepr(right, maxsize=30)
|
||||||
|
summary = '%s %s %s' % (left_repr, op, right_repr)
|
||||||
|
|
||||||
|
issequence = lambda x: isinstance(x, (list, tuple))
|
||||||
|
istext = lambda x: isinstance(x, basestring)
|
||||||
|
isdict = lambda x: isinstance(x, dict)
|
||||||
|
isset = lambda x: isinstance(x, set)
|
||||||
|
|
||||||
|
explanation = None
|
||||||
|
if op == '==':
|
||||||
|
if istext(left) and istext(right):
|
||||||
|
explanation = _diff_text(left, right)
|
||||||
|
elif issequence(left) and issequence(right):
|
||||||
|
explanation = _compare_eq_sequence(left, right)
|
||||||
|
elif isset(left) and isset(right):
|
||||||
|
explanation = _compare_eq_set(left, right)
|
||||||
|
elif isdict(left) and isdict(right):
|
||||||
|
explanation = _diff_text(py.std.pprint.pformat(left),
|
||||||
|
py.std.pprint.pformat(right))
|
||||||
|
elif op == 'in':
|
||||||
|
pass # XXX
|
||||||
|
|
||||||
|
if not explanation:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Don't include pageloads of data, should be configurable
|
||||||
|
if len(''.join(explanation)) > 80*8:
|
||||||
|
explanation = ['Detailed information too verbose, truncated']
|
||||||
|
|
||||||
|
return [summary] + explanation
|
||||||
|
|
||||||
|
|
||||||
|
def _diff_text(left, right):
|
||||||
|
"""Return the explanation for the diff between text
|
||||||
|
|
||||||
|
This will skip leading and trailing characters which are
|
||||||
|
identical to keep the diff minimal.
|
||||||
|
"""
|
||||||
|
explanation = []
|
||||||
|
for i in range(min(len(left), len(right))):
|
||||||
|
if left[i] != right[i]:
|
||||||
|
break
|
||||||
|
if i > 42:
|
||||||
|
i -= 10 # Provide some context
|
||||||
|
explanation = ['Skipping %s identical '
|
||||||
|
'leading characters in diff' % i]
|
||||||
|
left = left[i:]
|
||||||
|
right = right[i:]
|
||||||
|
if len(left) == len(right):
|
||||||
|
for i in range(len(left)):
|
||||||
|
if left[-i] != right[-i]:
|
||||||
|
break
|
||||||
|
if i > 42:
|
||||||
|
i -= 10 # Provide some context
|
||||||
|
explanation += ['Skipping %s identical '
|
||||||
|
'trailing characters in diff' % i]
|
||||||
|
left = left[:-i]
|
||||||
|
right = right[:-i]
|
||||||
|
explanation += [line.strip('\n')
|
||||||
|
for line in py.std.difflib.ndiff(left.splitlines(),
|
||||||
|
right.splitlines())]
|
||||||
|
return explanation
|
||||||
|
|
||||||
|
|
||||||
|
def _compare_eq_sequence(left, right):
|
||||||
|
explanation = []
|
||||||
|
for i in range(min(len(left), len(right))):
|
||||||
|
if left[i] != right[i]:
|
||||||
|
explanation += ['First differing item %s: %s != %s' %
|
||||||
|
(i, left[i], right[i])]
|
||||||
|
break
|
||||||
|
if len(left) > len(right):
|
||||||
|
explanation += ['Left contains more items, '
|
||||||
|
'first extra item: %s' % left[len(right)]]
|
||||||
|
elif len(left) < len(right):
|
||||||
|
explanation += ['Right contains more items, '
|
||||||
|
'first extra item: %s' % right[len(left)]]
|
||||||
|
return explanation + _diff_text(py.std.pprint.pformat(left),
|
||||||
|
py.std.pprint.pformat(right))
|
||||||
|
|
||||||
|
|
||||||
|
def _compare_eq_set(left, right):
|
||||||
|
explanation = []
|
||||||
|
diff_left = left - right
|
||||||
|
diff_right = right - left
|
||||||
|
if diff_left:
|
||||||
|
explanation.append('Extra items in the left set:')
|
||||||
|
for item in diff_left:
|
||||||
|
explanation.append(py.io.saferepr(item))
|
||||||
|
if diff_right:
|
||||||
|
explanation.append('Extra items in the right set:')
|
||||||
|
for item in diff_right:
|
||||||
|
explanation.append(py.io.saferepr(item))
|
||||||
|
return explanation
|
||||||
|
|
|
@ -3,30 +3,27 @@
|
||||||
import sys
|
import sys
|
||||||
import py
|
import py
|
||||||
|
|
||||||
def pytest_pyfunc_call(__multicall__, pyfuncitem):
|
def pytest_cmdline_main(config):
|
||||||
if not __multicall__.execute():
|
from py._test.session import Session
|
||||||
testfunction = pyfuncitem.obj
|
return Session(config).main()
|
||||||
if pyfuncitem._isyieldedfunction():
|
|
||||||
testfunction(*pyfuncitem._args)
|
|
||||||
else:
|
|
||||||
funcargs = pyfuncitem.funcargs
|
|
||||||
testfunction(**funcargs)
|
|
||||||
|
|
||||||
def pytest_collect_file(path, parent):
|
def pytest_perform_collection(session):
|
||||||
ext = path.ext
|
collection = session.collection
|
||||||
pb = path.purebasename
|
assert not hasattr(collection, 'items')
|
||||||
if pb.startswith("test_") or pb.endswith("_test") or \
|
hook = session.config.hook
|
||||||
path in parent.config._argfspaths:
|
collection.items = items = collection.perform_collect()
|
||||||
if ext == ".py":
|
hook.pytest_collection_modifyitems(config=session.config, items=items)
|
||||||
return parent.ihook.pytest_pycollect_makemodule(
|
hook.pytest_log_finishcollection(collection=collection)
|
||||||
path=path, parent=parent)
|
return True
|
||||||
|
|
||||||
def pytest_pycollect_makemodule(path, parent):
|
def pytest_runtest_mainloop(session):
|
||||||
return parent.Module(path, parent)
|
if session.config.option.collectonly:
|
||||||
|
return True
|
||||||
def pytest_funcarg__pytestconfig(request):
|
for item in session.collection.items:
|
||||||
""" the pytest config object with access to command line opts."""
|
item.config.hook.pytest_runtest_protocol(item=item)
|
||||||
return request.config
|
if session.shouldstop:
|
||||||
|
raise session.Interrupted(session.shouldstop)
|
||||||
|
return True
|
||||||
|
|
||||||
def pytest_ignore_collect(path, config):
|
def pytest_ignore_collect(path, config):
|
||||||
ignore_paths = config.getconftest_pathlist("collect_ignore", path=path)
|
ignore_paths = config.getconftest_pathlist("collect_ignore", path=path)
|
||||||
|
@ -35,12 +32,6 @@ def pytest_ignore_collect(path, config):
|
||||||
if excludeopt:
|
if excludeopt:
|
||||||
ignore_paths.extend([py.path.local(x) for x in excludeopt])
|
ignore_paths.extend([py.path.local(x) for x in excludeopt])
|
||||||
return path in ignore_paths
|
return path in ignore_paths
|
||||||
# XXX more refined would be:
|
|
||||||
if ignore_paths:
|
|
||||||
for p in ignore_paths:
|
|
||||||
if path == p or path.relto(p):
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def pytest_collect_directory(path, parent):
|
def pytest_collect_directory(path, parent):
|
||||||
# XXX reconsider the following comment
|
# XXX reconsider the following comment
|
||||||
|
@ -49,7 +40,7 @@ def pytest_collect_directory(path, parent):
|
||||||
# define Directory(dir) already
|
# define Directory(dir) already
|
||||||
if not parent.recfilter(path): # by default special ".cvs", ...
|
if not parent.recfilter(path): # by default special ".cvs", ...
|
||||||
# check if cmdline specified this dir or a subdir directly
|
# check if cmdline specified this dir or a subdir directly
|
||||||
for arg in parent.config._argfspaths:
|
for arg in parent.collection._argfspaths:
|
||||||
if path == arg or arg.relto(path):
|
if path == arg or arg.relto(path):
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
|
@ -68,12 +59,6 @@ def pytest_addoption(parser):
|
||||||
group._addoption('--maxfail', metavar="num",
|
group._addoption('--maxfail', metavar="num",
|
||||||
action="store", type="int", dest="maxfail", default=0,
|
action="store", type="int", dest="maxfail", default=0,
|
||||||
help="exit after first num failures or errors.")
|
help="exit after first num failures or errors.")
|
||||||
group._addoption('-k',
|
|
||||||
action="store", dest="keyword", default='',
|
|
||||||
help="only run test items matching the given "
|
|
||||||
"space separated keywords. precede a keyword with '-' to negate. "
|
|
||||||
"Terminate the expression with ':' to treat a match as a signal "
|
|
||||||
"to run all subsequent tests. ")
|
|
||||||
|
|
||||||
group = parser.getgroup("collect", "collection")
|
group = parser.getgroup("collect", "collection")
|
||||||
group.addoption('--collectonly',
|
group.addoption('--collectonly',
|
||||||
|
@ -91,41 +76,7 @@ def pytest_addoption(parser):
|
||||||
help="base temporary directory for this test run.")
|
help="base temporary directory for this test run.")
|
||||||
|
|
||||||
def pytest_configure(config):
|
def pytest_configure(config):
|
||||||
setsession(config)
|
|
||||||
# compat
|
# compat
|
||||||
if config.getvalue("exitfirst"):
|
if config.getvalue("exitfirst"):
|
||||||
config.option.maxfail = 1
|
config.option.maxfail = 1
|
||||||
|
|
||||||
def setsession(config):
|
|
||||||
val = config.getvalue
|
|
||||||
if val("collectonly"):
|
|
||||||
from py._test.session import Session
|
|
||||||
config.setsessionclass(Session)
|
|
||||||
|
|
||||||
# pycollect related hooks and code, should move to pytest_pycollect.py
|
|
||||||
|
|
||||||
def pytest_pycollect_makeitem(__multicall__, collector, name, obj):
|
|
||||||
res = __multicall__.execute()
|
|
||||||
if res is not None:
|
|
||||||
return res
|
|
||||||
if collector._istestclasscandidate(name, obj):
|
|
||||||
res = collector._deprecated_join(name)
|
|
||||||
if res is not None:
|
|
||||||
return res
|
|
||||||
return collector.Class(name, parent=collector)
|
|
||||||
elif collector.funcnamefilter(name) and hasattr(obj, '__call__'):
|
|
||||||
res = collector._deprecated_join(name)
|
|
||||||
if res is not None:
|
|
||||||
return res
|
|
||||||
if is_generator(obj):
|
|
||||||
# XXX deprecation warning
|
|
||||||
return collector.Generator(name, parent=collector)
|
|
||||||
else:
|
|
||||||
return collector._genfunctions(name, obj)
|
|
||||||
|
|
||||||
def is_generator(func):
|
|
||||||
try:
|
|
||||||
return py.code.getrawcode(func).co_flags & 32 # generator function
|
|
||||||
except AttributeError: # builtin functions have no bytecode
|
|
||||||
# assume them to not be generators
|
|
||||||
return False
|
|
||||||
|
|
|
@ -86,6 +86,9 @@ class DoctestItem(py.test.collect.Item):
|
||||||
else:
|
else:
|
||||||
return super(DoctestItem, self).repr_failure(excinfo)
|
return super(DoctestItem, self).repr_failure(excinfo)
|
||||||
|
|
||||||
|
def reportinfo(self):
|
||||||
|
return self.fspath, None, "[doctest]"
|
||||||
|
|
||||||
class DoctestTextfile(DoctestItem):
|
class DoctestTextfile(DoctestItem):
|
||||||
def runtest(self):
|
def runtest(self):
|
||||||
if not self._deprecated_testexecution():
|
if not self._deprecated_testexecution():
|
||||||
|
|
|
@ -11,7 +11,7 @@ def pytest_addoption(parser):
|
||||||
dest="genscript", metavar="path",
|
dest="genscript", metavar="path",
|
||||||
help="create standalone py.test script at given target path.")
|
help="create standalone py.test script at given target path.")
|
||||||
|
|
||||||
def pytest_configure(config):
|
def pytest_cmdline_main(config):
|
||||||
genscript = config.getvalue("genscript")
|
genscript = config.getvalue("genscript")
|
||||||
if genscript:
|
if genscript:
|
||||||
import py
|
import py
|
||||||
|
@ -20,7 +20,7 @@ def pytest_configure(config):
|
||||||
pybasedir = py.path.local(py.__file__).dirpath().dirpath()
|
pybasedir = py.path.local(py.__file__).dirpath().dirpath()
|
||||||
genscript = py.path.local(genscript)
|
genscript = py.path.local(genscript)
|
||||||
main(pybasedir, outfile=genscript, infile=infile)
|
main(pybasedir, outfile=genscript, infile=infile)
|
||||||
raise SystemExit(0)
|
return 0
|
||||||
|
|
||||||
def main(pybasedir, outfile, infile):
|
def main(pybasedir, outfile, infile):
|
||||||
import base64
|
import base64
|
||||||
|
|
|
@ -23,15 +23,18 @@ def pytest_addoption(parser):
|
||||||
help="show available conftest.py and ENV-variable names.")
|
help="show available conftest.py and ENV-variable names.")
|
||||||
|
|
||||||
|
|
||||||
def pytest_configure(__multicall__, config):
|
def pytest_cmdline_main(config):
|
||||||
if config.option.version:
|
if config.option.version:
|
||||||
p = py.path.local(py.__file__).dirpath()
|
p = py.path.local(py.__file__).dirpath()
|
||||||
sys.stderr.write("This is py.test version %s, imported from %s\n" %
|
sys.stderr.write("This is py.test version %s, imported from %s\n" %
|
||||||
(py.__version__, p))
|
(py.__version__, p))
|
||||||
sys.exit(0)
|
return 0
|
||||||
if not config.option.helpconfig:
|
elif config.option.helpconfig:
|
||||||
return
|
config.pluginmanager.do_configure(config)
|
||||||
__multicall__.execute()
|
showpluginhelp(config)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def showpluginhelp(config):
|
||||||
options = []
|
options = []
|
||||||
for group in config._parser._groups:
|
for group in config._parser._groups:
|
||||||
options.extend(group.options)
|
options.extend(group.options)
|
||||||
|
@ -65,9 +68,7 @@ def pytest_configure(__multicall__, config):
|
||||||
help,
|
help,
|
||||||
)
|
)
|
||||||
tw.line(line[:tw.fullwidth])
|
tw.line(line[:tw.fullwidth])
|
||||||
|
|
||||||
tw.sep("-")
|
tw.sep("-")
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
conftest_options = (
|
conftest_options = (
|
||||||
('pytest_plugins', 'list of plugin names to load'),
|
('pytest_plugins', 'list of plugin names to load'),
|
||||||
|
|
|
@ -37,12 +37,9 @@ class LogXML(object):
|
||||||
self._durations = {}
|
self._durations = {}
|
||||||
|
|
||||||
def _opentestcase(self, report):
|
def _opentestcase(self, report):
|
||||||
if hasattr(report, 'item'):
|
names = report.nodenames
|
||||||
node = report.item
|
d = {'time': self._durations.pop(names, "0")}
|
||||||
else:
|
names = [x.replace(".py", "") for x in names if x != "()"]
|
||||||
node = report.collector
|
|
||||||
d = {'time': self._durations.pop(node, "0")}
|
|
||||||
names = [x.replace(".py", "") for x in node.listnames() if x != "()"]
|
|
||||||
classnames = names[:-1]
|
classnames = names[:-1]
|
||||||
if self.prefix:
|
if self.prefix:
|
||||||
classnames.insert(0, self.prefix)
|
classnames.insert(0, self.prefix)
|
||||||
|
@ -122,11 +119,12 @@ class LogXML(object):
|
||||||
self.append_skipped(report)
|
self.append_skipped(report)
|
||||||
|
|
||||||
def pytest_runtest_call(self, item, __multicall__):
|
def pytest_runtest_call(self, item, __multicall__):
|
||||||
|
names = tuple(item.listnames())
|
||||||
start = time.time()
|
start = time.time()
|
||||||
try:
|
try:
|
||||||
return __multicall__.execute()
|
return __multicall__.execute()
|
||||||
finally:
|
finally:
|
||||||
self._durations[item] = time.time() - start
|
self._durations[names] = time.time() - start
|
||||||
|
|
||||||
def pytest_collectreport(self, report):
|
def pytest_collectreport(self, report):
|
||||||
if not report.passed:
|
if not report.passed:
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
|
||||||
|
def pytest_addoption(parser):
|
||||||
|
group = parser.getgroup("general")
|
||||||
|
group._addoption('-k',
|
||||||
|
action="store", dest="keyword", default='',
|
||||||
|
help="only run test items matching the given "
|
||||||
|
"space separated keywords. precede a keyword with '-' to negate. "
|
||||||
|
"Terminate the expression with ':' to treat a match as a signal "
|
||||||
|
"to run all subsequent tests. ")
|
||||||
|
|
||||||
|
def pytest_collection_modifyitems(items, config):
|
||||||
|
keywordexpr = config.option.keyword
|
||||||
|
if not keywordexpr:
|
||||||
|
return
|
||||||
|
selectuntil = False
|
||||||
|
if keywordexpr[-1] == ":":
|
||||||
|
selectuntil = True
|
||||||
|
keywordexpr = keywordexpr[:-1]
|
||||||
|
|
||||||
|
remaining = []
|
||||||
|
deselected = []
|
||||||
|
for colitem in items:
|
||||||
|
if keywordexpr and skipbykeyword(colitem, keywordexpr):
|
||||||
|
deselected.append(colitem)
|
||||||
|
else:
|
||||||
|
remaining.append(colitem)
|
||||||
|
if selectuntil:
|
||||||
|
keywordexpr = None
|
||||||
|
|
||||||
|
if deselected:
|
||||||
|
config.hook.pytest_deselected(items=deselected)
|
||||||
|
items[:] = remaining
|
||||||
|
|
||||||
|
def skipbykeyword(colitem, keywordexpr):
|
||||||
|
""" return True if they given keyword expression means to
|
||||||
|
skip this collector/item.
|
||||||
|
"""
|
||||||
|
if not keywordexpr:
|
||||||
|
return
|
||||||
|
chain = colitem.listchain()
|
||||||
|
for key in filter(None, keywordexpr.split()):
|
||||||
|
eor = key[:1] == '-'
|
||||||
|
if eor:
|
||||||
|
key = key[1:]
|
||||||
|
if not (eor ^ matchonekeyword(key, chain)):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def matchonekeyword(key, chain):
|
||||||
|
elems = key.split(".")
|
||||||
|
# XXX O(n^2), anyone cares?
|
||||||
|
chain = [item.keywords for item in chain if item.keywords]
|
||||||
|
for start, _ in enumerate(chain):
|
||||||
|
if start + len(elems) > len(chain):
|
||||||
|
return False
|
||||||
|
for num, elem in enumerate(elems):
|
||||||
|
for keyword in chain[num + start]:
|
||||||
|
ok = False
|
||||||
|
if elem in keyword:
|
||||||
|
ok = True
|
||||||
|
break
|
||||||
|
if not ok:
|
||||||
|
break
|
||||||
|
if num == len(elems) - 1 and ok:
|
||||||
|
return True
|
||||||
|
return False
|
|
@ -53,7 +53,7 @@ you start monkeypatching after the undo call.
|
||||||
.. _`monkeypatch blog post`: http://tetamap.wordpress.com/2009/03/03/monkeypatching-in-unit-tests-done-right/
|
.. _`monkeypatch blog post`: http://tetamap.wordpress.com/2009/03/03/monkeypatching-in-unit-tests-done-right/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import py, os, sys
|
import os, sys
|
||||||
|
|
||||||
def pytest_funcarg__monkeypatch(request):
|
def pytest_funcarg__monkeypatch(request):
|
||||||
"""The returned ``monkeypatch`` funcarg provides these
|
"""The returned ``monkeypatch`` funcarg provides these
|
||||||
|
|
|
@ -7,13 +7,14 @@ import sys, os
|
||||||
import re
|
import re
|
||||||
import inspect
|
import inspect
|
||||||
import time
|
import time
|
||||||
|
from fnmatch import fnmatch
|
||||||
from py._test.config import Config as pytestConfig
|
from py._test.config import Config as pytestConfig
|
||||||
from py.builtin import print_
|
from py.builtin import print_
|
||||||
|
|
||||||
def pytest_addoption(parser):
|
def pytest_addoption(parser):
|
||||||
group = parser.getgroup("pylib")
|
group = parser.getgroup("pylib")
|
||||||
group.addoption('--tools-on-path',
|
group.addoption('--no-tools-on-path',
|
||||||
action="store_true", dest="toolsonpath", default=False,
|
action="store_true", dest="notoolsonpath", default=False,
|
||||||
help=("discover tools on PATH instead of going through py.cmdline.")
|
help=("discover tools on PATH instead of going through py.cmdline.")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -74,10 +75,8 @@ class TmpTestdir:
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<TmpTestdir %r>" % (self.tmpdir,)
|
return "<TmpTestdir %r>" % (self.tmpdir,)
|
||||||
|
|
||||||
def Config(self, topdir=None):
|
def Config(self):
|
||||||
if topdir is None:
|
return pytestConfig()
|
||||||
topdir = self.tmpdir.dirpath()
|
|
||||||
return pytestConfig(topdir=topdir)
|
|
||||||
|
|
||||||
def finalize(self):
|
def finalize(self):
|
||||||
for p in self._syspathremove:
|
for p in self._syspathremove:
|
||||||
|
@ -149,16 +148,23 @@ class TmpTestdir:
|
||||||
p.ensure("__init__.py")
|
p.ensure("__init__.py")
|
||||||
return p
|
return p
|
||||||
|
|
||||||
|
def getnode(self, config, arg):
|
||||||
|
from py._test.session import Collection
|
||||||
|
collection = Collection(config)
|
||||||
|
return collection.getbyid(collection._normalizearg(arg))[0]
|
||||||
|
|
||||||
def genitems(self, colitems):
|
def genitems(self, colitems):
|
||||||
return list(self.session.genitems(colitems))
|
collection = colitems[0].collection
|
||||||
|
result = []
|
||||||
|
collection.genitems(colitems, (), result)
|
||||||
|
return result
|
||||||
|
|
||||||
def inline_genitems(self, *args):
|
def inline_genitems(self, *args):
|
||||||
#config = self.parseconfig(*args)
|
#config = self.parseconfig(*args)
|
||||||
config = self.parseconfig(*args)
|
from py._test.session import Collection
|
||||||
session = config.initsession()
|
config = self.parseconfigure(*args)
|
||||||
rec = self.getreportrecorder(config)
|
rec = self.getreportrecorder(config)
|
||||||
colitems = [config.getnode(arg) for arg in config.args]
|
items = Collection(config).perform_collect()
|
||||||
items = list(session.genitems(colitems))
|
|
||||||
return items, rec
|
return items, rec
|
||||||
|
|
||||||
def runitem(self, source):
|
def runitem(self, source):
|
||||||
|
@ -187,12 +193,10 @@ class TmpTestdir:
|
||||||
def inline_run(self, *args):
|
def inline_run(self, *args):
|
||||||
args = ("-s", ) + args # otherwise FD leakage
|
args = ("-s", ) + args # otherwise FD leakage
|
||||||
config = self.parseconfig(*args)
|
config = self.parseconfig(*args)
|
||||||
config.pluginmanager.do_configure(config)
|
|
||||||
session = config.initsession()
|
|
||||||
reprec = self.getreportrecorder(config)
|
reprec = self.getreportrecorder(config)
|
||||||
colitems = config.getinitialnodes()
|
#config.pluginmanager.do_configure(config)
|
||||||
session.main(colitems)
|
config.hook.pytest_cmdline_main(config=config)
|
||||||
config.pluginmanager.do_unconfigure(config)
|
#config.pluginmanager.do_unconfigure(config)
|
||||||
return reprec
|
return reprec
|
||||||
|
|
||||||
def config_preparse(self):
|
def config_preparse(self):
|
||||||
|
@ -245,29 +249,17 @@ class TmpTestdir:
|
||||||
|
|
||||||
def getitems(self, source):
|
def getitems(self, source):
|
||||||
modcol = self.getmodulecol(source)
|
modcol = self.getmodulecol(source)
|
||||||
return list(modcol.config.initsession().genitems([modcol]))
|
return self.genitems([modcol])
|
||||||
#assert item is not None, "%r item not found in module:\n%s" %(funcname, source)
|
|
||||||
#return item
|
|
||||||
|
|
||||||
def getfscol(self, path, configargs=()):
|
|
||||||
self.config = self.parseconfig(path, *configargs)
|
|
||||||
self.session = self.config.initsession()
|
|
||||||
return self.config.getnode(path)
|
|
||||||
|
|
||||||
def getmodulecol(self, source, configargs=(), withinit=False):
|
def getmodulecol(self, source, configargs=(), withinit=False):
|
||||||
kw = {self.request.function.__name__: py.code.Source(source).strip()}
|
kw = {self.request.function.__name__: py.code.Source(source).strip()}
|
||||||
path = self.makepyfile(**kw)
|
path = self.makepyfile(**kw)
|
||||||
if withinit:
|
if withinit:
|
||||||
self.makepyfile(__init__ = "#")
|
self.makepyfile(__init__ = "#")
|
||||||
self.config = self.parseconfig(path, *configargs)
|
self.config = config = self.parseconfigure(path, *configargs)
|
||||||
self.session = self.config.initsession()
|
node = self.getnode(config, path)
|
||||||
#self.config.pluginmanager.do_configure(config=self.config)
|
#config.pluginmanager.do_unconfigure(config)
|
||||||
# XXX
|
return node
|
||||||
self.config.pluginmanager.import_plugin("runner")
|
|
||||||
plugin = self.config.pluginmanager.getplugin("runner")
|
|
||||||
plugin.pytest_configure(config=self.config)
|
|
||||||
|
|
||||||
return self.config.getnode(path)
|
|
||||||
|
|
||||||
def popen(self, cmdargs, stdout, stderr, **kw):
|
def popen(self, cmdargs, stdout, stderr, **kw):
|
||||||
if not hasattr(py.std, 'subprocess'):
|
if not hasattr(py.std, 'subprocess'):
|
||||||
|
@ -314,7 +306,7 @@ class TmpTestdir:
|
||||||
return self.run(*fullargs)
|
return self.run(*fullargs)
|
||||||
|
|
||||||
def _getpybinargs(self, scriptname):
|
def _getpybinargs(self, scriptname):
|
||||||
if self.request.config.getvalue("toolsonpath"):
|
if not self.request.config.getvalue("notoolsonpath"):
|
||||||
script = py.path.local.sysfind(scriptname)
|
script = py.path.local.sysfind(scriptname)
|
||||||
assert script, "script %r not found" % scriptname
|
assert script, "script %r not found" % scriptname
|
||||||
return (script,)
|
return (script,)
|
||||||
|
@ -334,7 +326,7 @@ class TmpTestdir:
|
||||||
return self.run(sys.executable, script)
|
return self.run(sys.executable, script)
|
||||||
|
|
||||||
def _getsysprepend(self):
|
def _getsysprepend(self):
|
||||||
if not self.request.config.getvalue("toolsonpath"):
|
if self.request.config.getvalue("notoolsonpath"):
|
||||||
s = "import sys;sys.path.insert(0,%r);" % str(py._pydir.dirpath())
|
s = "import sys;sys.path.insert(0,%r);" % str(py._pydir.dirpath())
|
||||||
else:
|
else:
|
||||||
s = ""
|
s = ""
|
||||||
|
@ -360,8 +352,8 @@ class TmpTestdir:
|
||||||
|
|
||||||
def spawn_pytest(self, string, expect_timeout=10.0):
|
def spawn_pytest(self, string, expect_timeout=10.0):
|
||||||
pexpect = py.test.importorskip("pexpect", "2.4")
|
pexpect = py.test.importorskip("pexpect", "2.4")
|
||||||
if not self.request.config.getvalue("toolsonpath"):
|
if self.request.config.getvalue("notoolsonpath"):
|
||||||
py.test.skip("need --tools-on-path to run py.test script")
|
py.test.skip("--no-tools-on-path prevents running pexpect-spawn tests")
|
||||||
basetemp = self.tmpdir.mkdir("pexpect")
|
basetemp = self.tmpdir.mkdir("pexpect")
|
||||||
invoke = self._getpybinargs("py.test")[0]
|
invoke = self._getpybinargs("py.test")[0]
|
||||||
cmd = "%s --basetemp=%s %s" % (invoke, basetemp, string)
|
cmd = "%s --basetemp=%s %s" % (invoke, basetemp, string)
|
||||||
|
@ -405,8 +397,7 @@ class ReportRecorder(object):
|
||||||
""" return a testreport whose dotted import path matches """
|
""" return a testreport whose dotted import path matches """
|
||||||
l = []
|
l = []
|
||||||
for rep in self.getreports(names=names):
|
for rep in self.getreports(names=names):
|
||||||
colitem = rep.getnode()
|
if not inamepart or inamepart in rep.nodenames:
|
||||||
if not inamepart or inamepart in colitem.listnames():
|
|
||||||
l.append(rep)
|
l.append(rep)
|
||||||
if not l:
|
if not l:
|
||||||
raise ValueError("could not find test report matching %r: no test reports at all!" %
|
raise ValueError("could not find test report matching %r: no test reports at all!" %
|
||||||
|
@ -474,13 +465,25 @@ class LineMatcher:
|
||||||
def str(self):
|
def str(self):
|
||||||
return "\n".join(self.lines)
|
return "\n".join(self.lines)
|
||||||
|
|
||||||
def fnmatch_lines(self, lines2):
|
def _getlines(self, lines2):
|
||||||
if isinstance(lines2, str):
|
if isinstance(lines2, str):
|
||||||
lines2 = py.code.Source(lines2)
|
lines2 = py.code.Source(lines2)
|
||||||
if isinstance(lines2, py.code.Source):
|
if isinstance(lines2, py.code.Source):
|
||||||
lines2 = lines2.strip().lines
|
lines2 = lines2.strip().lines
|
||||||
|
return lines2
|
||||||
|
|
||||||
from fnmatch import fnmatch
|
def fnmatch_lines_random(self, lines2):
|
||||||
|
lines2 = self._getlines(lines2)
|
||||||
|
for line in lines2:
|
||||||
|
for x in self.lines:
|
||||||
|
if line == x or fnmatch(x, line):
|
||||||
|
print_("matched: ", repr(line))
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
raise ValueError("line %r not found in output" % line)
|
||||||
|
|
||||||
|
def fnmatch_lines(self, lines2):
|
||||||
|
lines2 = self._getlines(lines2)
|
||||||
lines1 = self.lines[:]
|
lines1 = self.lines[:]
|
||||||
nextline = None
|
nextline = None
|
||||||
extralines = []
|
extralines = []
|
||||||
|
|
|
@ -5,9 +5,80 @@ import py
|
||||||
import inspect
|
import inspect
|
||||||
import sys
|
import sys
|
||||||
from py._test.collect import configproperty, warnoldcollect
|
from py._test.collect import configproperty, warnoldcollect
|
||||||
from py._test import funcargs
|
|
||||||
from py._code.code import TerminalRepr
|
from py._code.code import TerminalRepr
|
||||||
|
|
||||||
|
def pytest_addoption(parser):
|
||||||
|
group = parser.getgroup("terminal reporting")
|
||||||
|
group._addoption('--funcargs',
|
||||||
|
action="store_true", dest="showfuncargs", default=False,
|
||||||
|
help="show available function arguments, sorted by plugin")
|
||||||
|
|
||||||
|
def pytest_cmdline_main(config):
|
||||||
|
if config.option.showfuncargs:
|
||||||
|
showfuncargs(config)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def pytest_namespace():
|
||||||
|
# XXX rather return than set directly
|
||||||
|
py.test.collect.Module = Module
|
||||||
|
py.test.collect.Class = Class
|
||||||
|
py.test.collect.Instance = Instance
|
||||||
|
py.test.collect.Function = Function
|
||||||
|
py.test.collect.Generator = Generator
|
||||||
|
py.test.collect._fillfuncargs = fillfuncargs
|
||||||
|
|
||||||
|
def pytest_funcarg__pytestconfig(request):
|
||||||
|
""" the pytest config object with access to command line opts."""
|
||||||
|
return request.config
|
||||||
|
|
||||||
|
def pytest_pyfunc_call(__multicall__, pyfuncitem):
|
||||||
|
if not __multicall__.execute():
|
||||||
|
testfunction = pyfuncitem.obj
|
||||||
|
if pyfuncitem._isyieldedfunction():
|
||||||
|
testfunction(*pyfuncitem._args)
|
||||||
|
else:
|
||||||
|
funcargs = pyfuncitem.funcargs
|
||||||
|
testfunction(**funcargs)
|
||||||
|
|
||||||
|
def pytest_collect_file(path, parent):
|
||||||
|
ext = path.ext
|
||||||
|
pb = path.purebasename
|
||||||
|
if pb.startswith("test_") or pb.endswith("_test") or \
|
||||||
|
path in parent.collection._argfspaths:
|
||||||
|
if ext == ".py":
|
||||||
|
return parent.ihook.pytest_pycollect_makemodule(
|
||||||
|
path=path, parent=parent)
|
||||||
|
|
||||||
|
def pytest_pycollect_makemodule(path, parent):
|
||||||
|
return parent.Module(path, parent)
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_pycollect_makeitem(__multicall__, collector, name, obj):
|
||||||
|
res = __multicall__.execute()
|
||||||
|
if res is not None:
|
||||||
|
return res
|
||||||
|
if collector._istestclasscandidate(name, obj):
|
||||||
|
res = collector._deprecated_join(name)
|
||||||
|
if res is not None:
|
||||||
|
return res
|
||||||
|
return collector.Class(name, parent=collector)
|
||||||
|
elif collector.funcnamefilter(name) and hasattr(obj, '__call__'):
|
||||||
|
res = collector._deprecated_join(name)
|
||||||
|
if res is not None:
|
||||||
|
return res
|
||||||
|
if is_generator(obj):
|
||||||
|
# XXX deprecation warning
|
||||||
|
return collector.Generator(name, parent=collector)
|
||||||
|
else:
|
||||||
|
return collector._genfunctions(name, obj)
|
||||||
|
|
||||||
|
def is_generator(func):
|
||||||
|
try:
|
||||||
|
return py.code.getrawcode(func).co_flags & 32 # generator function
|
||||||
|
except AttributeError: # builtin functions have no bytecode
|
||||||
|
# assume them to not be generators
|
||||||
|
return False
|
||||||
|
|
||||||
class PyobjMixin(object):
|
class PyobjMixin(object):
|
||||||
def obj():
|
def obj():
|
||||||
def fget(self):
|
def fget(self):
|
||||||
|
@ -120,10 +191,10 @@ class PyCollectorMixin(PyobjMixin, py.test.collect.Collector):
|
||||||
module = self.getparent(Module).obj
|
module = self.getparent(Module).obj
|
||||||
clscol = self.getparent(Class)
|
clscol = self.getparent(Class)
|
||||||
cls = clscol and clscol.obj or None
|
cls = clscol and clscol.obj or None
|
||||||
metafunc = funcargs.Metafunc(funcobj, config=self.config,
|
metafunc = Metafunc(funcobj, config=self.config,
|
||||||
cls=cls, module=module)
|
cls=cls, module=module)
|
||||||
gentesthook = self.config.hook.pytest_generate_tests
|
gentesthook = self.config.hook.pytest_generate_tests
|
||||||
plugins = funcargs.getplugins(self, withpy=True)
|
plugins = getplugins(self, withpy=True)
|
||||||
gentesthook.pcall(plugins, metafunc=metafunc)
|
gentesthook.pcall(plugins, metafunc=metafunc)
|
||||||
if not metafunc._calls:
|
if not metafunc._calls:
|
||||||
return self.Function(name, parent=self)
|
return self.Function(name, parent=self)
|
||||||
|
@ -212,16 +283,9 @@ class Class(PyCollectorMixin, py.test.collect.Collector):
|
||||||
class Instance(PyCollectorMixin, py.test.collect.Collector):
|
class Instance(PyCollectorMixin, py.test.collect.Collector):
|
||||||
def _getobj(self):
|
def _getobj(self):
|
||||||
return self.parent.obj()
|
return self.parent.obj()
|
||||||
def Function(self):
|
|
||||||
return getattr(self.obj, 'Function',
|
|
||||||
PyCollectorMixin.Function.__get__(self)) # XXX for python 2.2
|
|
||||||
def _keywords(self):
|
def _keywords(self):
|
||||||
return []
|
return []
|
||||||
Function = property(Function)
|
|
||||||
|
|
||||||
#def __repr__(self):
|
|
||||||
# return "<%s of '%s'>" %(self.__class__.__name__,
|
|
||||||
# self.parent.obj.__name__)
|
|
||||||
|
|
||||||
def newinstance(self):
|
def newinstance(self):
|
||||||
self.obj = self._getobj()
|
self.obj = self._getobj()
|
||||||
|
@ -270,7 +334,7 @@ class FunctionMixin(PyobjMixin):
|
||||||
return traceback
|
return traceback
|
||||||
|
|
||||||
def _repr_failure_py(self, excinfo, style="long"):
|
def _repr_failure_py(self, excinfo, style="long"):
|
||||||
if excinfo.errisinstance(funcargs.FuncargRequest.LookupError):
|
if excinfo.errisinstance(FuncargRequest.LookupError):
|
||||||
fspath, lineno, msg = self.reportinfo()
|
fspath, lineno, msg = self.reportinfo()
|
||||||
lines, _ = inspect.getsourcelines(self.obj)
|
lines, _ = inspect.getsourcelines(self.obj)
|
||||||
for i, line in enumerate(lines):
|
for i, line in enumerate(lines):
|
||||||
|
@ -348,8 +412,9 @@ class Function(FunctionMixin, py.test.collect.Item):
|
||||||
"""
|
"""
|
||||||
_genid = None
|
_genid = None
|
||||||
def __init__(self, name, parent=None, args=None, config=None,
|
def __init__(self, name, parent=None, args=None, config=None,
|
||||||
callspec=None, callobj=_dummy):
|
callspec=None, callobj=_dummy, collection=None):
|
||||||
super(Function, self).__init__(name, parent, config=config)
|
super(Function, self).__init__(name, parent,
|
||||||
|
config=config, collection=collection)
|
||||||
self._args = args
|
self._args = args
|
||||||
if self._isyieldedfunction():
|
if self._isyieldedfunction():
|
||||||
assert not callspec, "yielded functions (deprecated) cannot have funcargs"
|
assert not callspec, "yielded functions (deprecated) cannot have funcargs"
|
||||||
|
@ -383,7 +448,7 @@ class Function(FunctionMixin, py.test.collect.Item):
|
||||||
def setup(self):
|
def setup(self):
|
||||||
super(Function, self).setup()
|
super(Function, self).setup()
|
||||||
if hasattr(self, 'funcargs'):
|
if hasattr(self, 'funcargs'):
|
||||||
funcargs.fillfuncargs(self)
|
fillfuncargs(self)
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
try:
|
try:
|
||||||
|
@ -409,3 +474,229 @@ def hasinit(obj):
|
||||||
if init:
|
if init:
|
||||||
if init != object.__init__:
|
if init != object.__init__:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def getfuncargnames(function):
|
||||||
|
argnames = py.std.inspect.getargs(py.code.getrawcode(function))[0]
|
||||||
|
startindex = py.std.inspect.ismethod(function) and 1 or 0
|
||||||
|
defaults = getattr(function, 'func_defaults',
|
||||||
|
getattr(function, '__defaults__', None)) or ()
|
||||||
|
numdefaults = len(defaults)
|
||||||
|
if numdefaults:
|
||||||
|
return argnames[startindex:-numdefaults]
|
||||||
|
return argnames[startindex:]
|
||||||
|
|
||||||
|
def fillfuncargs(function):
|
||||||
|
""" fill missing funcargs. """
|
||||||
|
request = FuncargRequest(pyfuncitem=function)
|
||||||
|
request._fillfuncargs()
|
||||||
|
|
||||||
|
def getplugins(node, withpy=False): # might by any node
|
||||||
|
plugins = node.config._getmatchingplugins(node.fspath)
|
||||||
|
if withpy:
|
||||||
|
mod = node.getparent(py.test.collect.Module)
|
||||||
|
if mod is not None:
|
||||||
|
plugins.append(mod.obj)
|
||||||
|
inst = node.getparent(py.test.collect.Instance)
|
||||||
|
if inst is not None:
|
||||||
|
plugins.append(inst.obj)
|
||||||
|
return plugins
|
||||||
|
|
||||||
|
_notexists = object()
|
||||||
|
class CallSpec:
|
||||||
|
def __init__(self, funcargs, id, param):
|
||||||
|
self.funcargs = funcargs
|
||||||
|
self.id = id
|
||||||
|
if param is not _notexists:
|
||||||
|
self.param = param
|
||||||
|
def __repr__(self):
|
||||||
|
return "<CallSpec id=%r param=%r funcargs=%r>" %(
|
||||||
|
self.id, getattr(self, 'param', '?'), self.funcargs)
|
||||||
|
|
||||||
|
class Metafunc:
|
||||||
|
def __init__(self, function, config=None, cls=None, module=None):
|
||||||
|
self.config = config
|
||||||
|
self.module = module
|
||||||
|
self.function = function
|
||||||
|
self.funcargnames = getfuncargnames(function)
|
||||||
|
self.cls = cls
|
||||||
|
self.module = module
|
||||||
|
self._calls = []
|
||||||
|
self._ids = py.builtin.set()
|
||||||
|
|
||||||
|
def addcall(self, funcargs=None, id=_notexists, param=_notexists):
|
||||||
|
assert funcargs is None or isinstance(funcargs, dict)
|
||||||
|
if id is None:
|
||||||
|
raise ValueError("id=None not allowed")
|
||||||
|
if id is _notexists:
|
||||||
|
id = len(self._calls)
|
||||||
|
id = str(id)
|
||||||
|
if id in self._ids:
|
||||||
|
raise ValueError("duplicate id %r" % id)
|
||||||
|
self._ids.add(id)
|
||||||
|
self._calls.append(CallSpec(funcargs, id, param))
|
||||||
|
|
||||||
|
class FuncargRequest:
|
||||||
|
_argprefix = "pytest_funcarg__"
|
||||||
|
_argname = None
|
||||||
|
|
||||||
|
class LookupError(LookupError):
|
||||||
|
""" error on performing funcarg request. """
|
||||||
|
|
||||||
|
def __init__(self, pyfuncitem):
|
||||||
|
self._pyfuncitem = pyfuncitem
|
||||||
|
self.function = pyfuncitem.obj
|
||||||
|
self.module = pyfuncitem.getparent(py.test.collect.Module).obj
|
||||||
|
clscol = pyfuncitem.getparent(py.test.collect.Class)
|
||||||
|
self.cls = clscol and clscol.obj or None
|
||||||
|
self.instance = py.builtin._getimself(self.function)
|
||||||
|
self.config = pyfuncitem.config
|
||||||
|
self.fspath = pyfuncitem.fspath
|
||||||
|
if hasattr(pyfuncitem, '_requestparam'):
|
||||||
|
self.param = pyfuncitem._requestparam
|
||||||
|
self._plugins = getplugins(pyfuncitem, withpy=True)
|
||||||
|
self._funcargs = self._pyfuncitem.funcargs.copy()
|
||||||
|
self._name2factory = {}
|
||||||
|
self._currentarg = None
|
||||||
|
|
||||||
|
def _fillfuncargs(self):
|
||||||
|
argnames = getfuncargnames(self.function)
|
||||||
|
if argnames:
|
||||||
|
assert not getattr(self._pyfuncitem, '_args', None), (
|
||||||
|
"yielded functions cannot have funcargs")
|
||||||
|
for argname in argnames:
|
||||||
|
if argname not in self._pyfuncitem.funcargs:
|
||||||
|
self._pyfuncitem.funcargs[argname] = self.getfuncargvalue(argname)
|
||||||
|
|
||||||
|
|
||||||
|
def applymarker(self, marker):
|
||||||
|
""" apply a marker to a test function invocation.
|
||||||
|
|
||||||
|
The 'marker' must be created with py.test.mark.* XYZ.
|
||||||
|
"""
|
||||||
|
if not isinstance(marker, py.test.mark.XYZ.__class__):
|
||||||
|
raise ValueError("%r is not a py.test.mark.* object")
|
||||||
|
self._pyfuncitem.keywords[marker.markname] = marker
|
||||||
|
|
||||||
|
def cached_setup(self, setup, teardown=None, scope="module", extrakey=None):
|
||||||
|
""" cache and return result of calling setup().
|
||||||
|
|
||||||
|
The requested argument name, the scope and the ``extrakey``
|
||||||
|
determine the cache key. The scope also determines when
|
||||||
|
teardown(result) will be called. valid scopes are:
|
||||||
|
scope == 'function': when the single test function run finishes.
|
||||||
|
scope == 'module': when tests in a different module are run
|
||||||
|
scope == 'session': when tests of the session have run.
|
||||||
|
"""
|
||||||
|
if not hasattr(self.config, '_setupcache'):
|
||||||
|
self.config._setupcache = {} # XXX weakref?
|
||||||
|
cachekey = (self._currentarg, self._getscopeitem(scope), extrakey)
|
||||||
|
cache = self.config._setupcache
|
||||||
|
try:
|
||||||
|
val = cache[cachekey]
|
||||||
|
except KeyError:
|
||||||
|
val = setup()
|
||||||
|
cache[cachekey] = val
|
||||||
|
if teardown is not None:
|
||||||
|
def finalizer():
|
||||||
|
del cache[cachekey]
|
||||||
|
teardown(val)
|
||||||
|
self._addfinalizer(finalizer, scope=scope)
|
||||||
|
return val
|
||||||
|
|
||||||
|
def getfuncargvalue(self, argname):
|
||||||
|
try:
|
||||||
|
return self._funcargs[argname]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
if argname not in self._name2factory:
|
||||||
|
self._name2factory[argname] = self.config.pluginmanager.listattr(
|
||||||
|
plugins=self._plugins,
|
||||||
|
attrname=self._argprefix + str(argname)
|
||||||
|
)
|
||||||
|
#else: we are called recursively
|
||||||
|
if not self._name2factory[argname]:
|
||||||
|
self._raiselookupfailed(argname)
|
||||||
|
funcargfactory = self._name2factory[argname].pop()
|
||||||
|
oldarg = self._currentarg
|
||||||
|
self._currentarg = argname
|
||||||
|
try:
|
||||||
|
self._funcargs[argname] = res = funcargfactory(request=self)
|
||||||
|
finally:
|
||||||
|
self._currentarg = oldarg
|
||||||
|
return res
|
||||||
|
|
||||||
|
def _getscopeitem(self, scope):
|
||||||
|
if scope == "function":
|
||||||
|
return self._pyfuncitem
|
||||||
|
elif scope == "module":
|
||||||
|
return self._pyfuncitem.getparent(py.test.collect.Module)
|
||||||
|
elif scope == "session":
|
||||||
|
return None
|
||||||
|
raise ValueError("unknown finalization scope %r" %(scope,))
|
||||||
|
|
||||||
|
def _addfinalizer(self, finalizer, scope):
|
||||||
|
colitem = self._getscopeitem(scope)
|
||||||
|
self.config._setupstate.addfinalizer(
|
||||||
|
finalizer=finalizer, colitem=colitem)
|
||||||
|
|
||||||
|
def addfinalizer(self, finalizer):
|
||||||
|
""" call the given finalizer after test function finished execution. """
|
||||||
|
self._addfinalizer(finalizer, scope="function")
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<FuncargRequest for %r>" %(self._pyfuncitem)
|
||||||
|
|
||||||
|
def _raiselookupfailed(self, argname):
|
||||||
|
available = []
|
||||||
|
for plugin in self._plugins:
|
||||||
|
for name in vars(plugin):
|
||||||
|
if name.startswith(self._argprefix):
|
||||||
|
name = name[len(self._argprefix):]
|
||||||
|
if name not in available:
|
||||||
|
available.append(name)
|
||||||
|
fspath, lineno, msg = self._pyfuncitem.reportinfo()
|
||||||
|
msg = "LookupError: no factory found for function argument %r" % (argname,)
|
||||||
|
msg += "\n available funcargs: %s" %(", ".join(available),)
|
||||||
|
msg += "\n use 'py.test --funcargs [testpath]' for help on them."
|
||||||
|
raise self.LookupError(msg)
|
||||||
|
|
||||||
|
def showfuncargs(config):
|
||||||
|
from py._test.session import Collection
|
||||||
|
collection = Collection(config)
|
||||||
|
colitem = collection.getinitialnodes()[0]
|
||||||
|
curdir = py.path.local()
|
||||||
|
tw = py.io.TerminalWriter()
|
||||||
|
plugins = getplugins(colitem, withpy=True)
|
||||||
|
verbose = config.getvalue("verbose")
|
||||||
|
for plugin in plugins:
|
||||||
|
available = []
|
||||||
|
for name, factory in vars(plugin).items():
|
||||||
|
if name.startswith(FuncargRequest._argprefix):
|
||||||
|
name = name[len(FuncargRequest._argprefix):]
|
||||||
|
if name not in available:
|
||||||
|
available.append([name, factory])
|
||||||
|
if available:
|
||||||
|
pluginname = plugin.__name__
|
||||||
|
for name, factory in available:
|
||||||
|
loc = getlocation(factory, curdir)
|
||||||
|
if verbose:
|
||||||
|
funcargspec = "%s -- %s" %(name, loc,)
|
||||||
|
else:
|
||||||
|
funcargspec = name
|
||||||
|
tw.line(funcargspec, green=True)
|
||||||
|
doc = factory.__doc__ or ""
|
||||||
|
if doc:
|
||||||
|
for line in doc.split("\n"):
|
||||||
|
tw.line(" " + line.strip())
|
||||||
|
else:
|
||||||
|
tw.line(" %s: no docstring available" %(loc,),
|
||||||
|
red=True)
|
||||||
|
|
||||||
|
def getlocation(function, curdir):
|
||||||
|
import inspect
|
||||||
|
fn = py.path.local(inspect.getfile(function))
|
||||||
|
lineno = py.builtin._getcode(function).co_firstlineno
|
||||||
|
if fn.relto(curdir):
|
||||||
|
fn = fn.relto(curdir)
|
||||||
|
return "%s:%d" %(fn, lineno+1)
|
|
@ -57,21 +57,20 @@ class ResultLog(object):
|
||||||
self.config = config
|
self.config = config
|
||||||
self.logfile = logfile # preferably line buffered
|
self.logfile = logfile # preferably line buffered
|
||||||
|
|
||||||
def write_log_entry(self, testpath, shortrepr, longrepr):
|
def write_log_entry(self, testpath, lettercode, longrepr):
|
||||||
print_("%s %s" % (shortrepr, testpath), file=self.logfile)
|
print_("%s %s" % (lettercode, testpath), file=self.logfile)
|
||||||
for line in longrepr.splitlines():
|
for line in longrepr.splitlines():
|
||||||
print_(" %s" % line, file=self.logfile)
|
print_(" %s" % line, file=self.logfile)
|
||||||
|
|
||||||
def log_outcome(self, node, shortrepr, longrepr):
|
def log_outcome(self, report, lettercode, longrepr):
|
||||||
testpath = generic_path(node)
|
testpath = getattr(report, 'nodeid', None)
|
||||||
self.write_log_entry(testpath, shortrepr, longrepr)
|
if testpath is None:
|
||||||
|
testpath = report.fspath
|
||||||
|
self.write_log_entry(testpath, lettercode, longrepr)
|
||||||
|
|
||||||
def pytest_runtest_logreport(self, report):
|
def pytest_runtest_logreport(self, report):
|
||||||
res = self.config.hook.pytest_report_teststatus(report=report)
|
res = self.config.hook.pytest_report_teststatus(report=report)
|
||||||
if res is not None:
|
|
||||||
code = res[1]
|
code = res[1]
|
||||||
else:
|
|
||||||
code = report.shortrepr
|
|
||||||
if code == 'x':
|
if code == 'x':
|
||||||
longrepr = str(report.longrepr)
|
longrepr = str(report.longrepr)
|
||||||
elif code == 'X':
|
elif code == 'X':
|
||||||
|
@ -82,7 +81,7 @@ class ResultLog(object):
|
||||||
longrepr = str(report.longrepr)
|
longrepr = str(report.longrepr)
|
||||||
elif report.skipped:
|
elif report.skipped:
|
||||||
longrepr = str(report.longrepr.reprcrash.message)
|
longrepr = str(report.longrepr.reprcrash.message)
|
||||||
self.log_outcome(report.item, code, longrepr)
|
self.log_outcome(report, code, longrepr)
|
||||||
|
|
||||||
def pytest_collectreport(self, report):
|
def pytest_collectreport(self, report):
|
||||||
if not report.passed:
|
if not report.passed:
|
||||||
|
@ -92,7 +91,7 @@ class ResultLog(object):
|
||||||
assert report.skipped
|
assert report.skipped
|
||||||
code = "S"
|
code = "S"
|
||||||
longrepr = str(report.longrepr.reprcrash)
|
longrepr = str(report.longrepr.reprcrash)
|
||||||
self.log_outcome(report.collector, code, longrepr)
|
self.log_outcome(report, code, longrepr)
|
||||||
|
|
||||||
def pytest_internalerror(self, excrepr):
|
def pytest_internalerror(self, excrepr):
|
||||||
path = excrepr.reprcrash.path
|
path = excrepr.reprcrash.path
|
||||||
|
|
|
@ -3,6 +3,7 @@ collect and run test items and create reports.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import py, sys
|
import py, sys
|
||||||
|
from py._code.code import TerminalRepr
|
||||||
|
|
||||||
def pytest_namespace():
|
def pytest_namespace():
|
||||||
return {
|
return {
|
||||||
|
@ -26,19 +27,35 @@ def pytest_sessionfinish(session, exitstatus):
|
||||||
hook = session.config.hook
|
hook = session.config.hook
|
||||||
rep = hook.pytest__teardown_final(session=session)
|
rep = hook.pytest__teardown_final(session=session)
|
||||||
if rep:
|
if rep:
|
||||||
hook.pytest__teardown_final_logerror(report=rep)
|
hook.pytest__teardown_final_logerror(session=session, report=rep)
|
||||||
|
session.exitstatus = 1
|
||||||
|
|
||||||
def pytest_make_collect_report(collector):
|
class NodeInfo:
|
||||||
result = excinfo = None
|
def __init__(self, nodeid, nodenames, fspath, location):
|
||||||
|
self.nodeid = nodeid
|
||||||
|
self.nodenames = nodenames
|
||||||
|
self.fspath = fspath
|
||||||
|
self.location = location
|
||||||
|
|
||||||
|
def getitemnodeinfo(item):
|
||||||
try:
|
try:
|
||||||
result = collector._memocollect()
|
return item._nodeinfo
|
||||||
except KeyboardInterrupt:
|
except AttributeError:
|
||||||
raise
|
location = item.ihook.pytest_report_iteminfo(item=item)
|
||||||
except:
|
location = (str(location[0]), location[1], str(location[2]))
|
||||||
excinfo = py.code.ExceptionInfo()
|
nodenames = tuple(item.listnames())
|
||||||
return CollectReport(collector, result, excinfo)
|
nodeid = item.collection.getid(item)
|
||||||
|
fspath = item.fspath
|
||||||
|
item._nodeinfo = n = NodeInfo(nodeid, nodenames, fspath, location)
|
||||||
|
return n
|
||||||
|
|
||||||
def pytest_runtest_protocol(item):
|
def pytest_runtest_protocol(item):
|
||||||
|
nodeinfo = getitemnodeinfo(item)
|
||||||
|
item.ihook.pytest_runtest_logstart(
|
||||||
|
nodeid=nodeinfo.nodeid,
|
||||||
|
location=nodeinfo.location,
|
||||||
|
fspath=str(item.fspath),
|
||||||
|
)
|
||||||
runtestprotocol(item)
|
runtestprotocol(item)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -57,9 +74,6 @@ def pytest_runtest_call(item):
|
||||||
if not item._deprecated_testexecution():
|
if not item._deprecated_testexecution():
|
||||||
item.runtest()
|
item.runtest()
|
||||||
|
|
||||||
def pytest_runtest_makereport(item, call):
|
|
||||||
return ItemTestReport(item, call.excinfo, call.when)
|
|
||||||
|
|
||||||
def pytest_runtest_teardown(item):
|
def pytest_runtest_teardown(item):
|
||||||
item.config._setupstate.teardown_exact(item)
|
item.config._setupstate.teardown_exact(item)
|
||||||
|
|
||||||
|
@ -68,8 +82,8 @@ def pytest__teardown_final(session):
|
||||||
if call.excinfo:
|
if call.excinfo:
|
||||||
ntraceback = call.excinfo.traceback .cut(excludepath=py._pydir)
|
ntraceback = call.excinfo.traceback .cut(excludepath=py._pydir)
|
||||||
call.excinfo.traceback = ntraceback.filter()
|
call.excinfo.traceback = ntraceback.filter()
|
||||||
rep = TeardownErrorReport(call.excinfo)
|
longrepr = call.excinfo.getrepr(funcargs=True)
|
||||||
return rep
|
return TeardownErrorReport(longrepr)
|
||||||
|
|
||||||
def pytest_report_teststatus(report):
|
def pytest_report_teststatus(report):
|
||||||
if report.when in ("setup", "teardown"):
|
if report.when in ("setup", "teardown"):
|
||||||
|
@ -80,6 +94,8 @@ def pytest_report_teststatus(report):
|
||||||
return "skipped", "s", "SKIPPED"
|
return "skipped", "s", "SKIPPED"
|
||||||
else:
|
else:
|
||||||
return "", "", ""
|
return "", "", ""
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Implementation
|
# Implementation
|
||||||
|
|
||||||
|
@ -115,123 +131,117 @@ class CallInfo:
|
||||||
return "<CallInfo when=%r %s>" % (self.when, status)
|
return "<CallInfo when=%r %s>" % (self.when, status)
|
||||||
|
|
||||||
class BaseReport(object):
|
class BaseReport(object):
|
||||||
def __init__(self):
|
|
||||||
self.headerlines = []
|
|
||||||
def __repr__(self):
|
|
||||||
l = ["%s=%s" %(key, value)
|
|
||||||
for key, value in self.__dict__.items()]
|
|
||||||
return "<%s %s>" %(self.__class__.__name__, " ".join(l),)
|
|
||||||
|
|
||||||
def _getcrashline(self):
|
|
||||||
try:
|
|
||||||
return str(self.longrepr.reprcrash)
|
|
||||||
except AttributeError:
|
|
||||||
try:
|
|
||||||
return str(self.longrepr)[:50]
|
|
||||||
except AttributeError:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def toterminal(self, out):
|
def toterminal(self, out):
|
||||||
for line in self.headerlines:
|
|
||||||
out.line(line)
|
|
||||||
longrepr = self.longrepr
|
longrepr = self.longrepr
|
||||||
if hasattr(longrepr, 'toterminal'):
|
if hasattr(longrepr, 'toterminal'):
|
||||||
longrepr.toterminal(out)
|
longrepr.toterminal(out)
|
||||||
else:
|
else:
|
||||||
out.line(str(longrepr))
|
out.line(str(longrepr))
|
||||||
|
|
||||||
class CollectErrorRepr(BaseReport):
|
passed = property(lambda x: x.outcome == "passed")
|
||||||
|
failed = property(lambda x: x.outcome == "failed")
|
||||||
|
skipped = property(lambda x: x.outcome == "skipped")
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_runtest_makereport(item, call):
|
||||||
|
nodeinfo = getitemnodeinfo(item)
|
||||||
|
when = call.when
|
||||||
|
keywords = dict([(x,1) for x in item.keywords])
|
||||||
|
excinfo = call.excinfo
|
||||||
|
if not call.excinfo:
|
||||||
|
outcome = "passed"
|
||||||
|
longrepr = None
|
||||||
|
else:
|
||||||
|
if not isinstance(excinfo, py.code.ExceptionInfo):
|
||||||
|
outcome = "failed"
|
||||||
|
longrepr = excinfo
|
||||||
|
elif excinfo.errisinstance(py.test.skip.Exception):
|
||||||
|
outcome = "skipped"
|
||||||
|
longrepr = item._repr_failure_py(excinfo)
|
||||||
|
else:
|
||||||
|
outcome = "failed"
|
||||||
|
if call.when == "call":
|
||||||
|
longrepr = item.repr_failure(excinfo)
|
||||||
|
else: # exception in setup or teardown
|
||||||
|
longrepr = item._repr_failure_py(excinfo)
|
||||||
|
return TestReport(nodeinfo.nodeid, nodeinfo.nodenames,
|
||||||
|
nodeinfo.fspath, nodeinfo.location,
|
||||||
|
keywords, outcome, longrepr, when)
|
||||||
|
|
||||||
|
class TestReport(BaseReport):
|
||||||
|
def __init__(self, nodeid, nodenames, fspath, location,
|
||||||
|
keywords, outcome, longrepr, when):
|
||||||
|
self.nodeid = nodeid
|
||||||
|
self.nodenames = nodenames
|
||||||
|
self.fspath = fspath # where the test was collected
|
||||||
|
self.location = location
|
||||||
|
self.keywords = keywords
|
||||||
|
self.outcome = outcome
|
||||||
|
self.longrepr = longrepr
|
||||||
|
self.when = when
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<TestReport %r when=%r outcome=%r>" % (
|
||||||
|
self.nodeid, self.when, self.outcome)
|
||||||
|
|
||||||
|
class TeardownErrorReport(BaseReport):
|
||||||
|
outcome = "failed"
|
||||||
|
when = "teardown"
|
||||||
|
def __init__(self, longrepr):
|
||||||
|
self.longrepr = longrepr
|
||||||
|
|
||||||
|
def pytest_make_collect_report(collector):
|
||||||
|
result = excinfo = None
|
||||||
|
try:
|
||||||
|
result = collector._memocollect()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
raise
|
||||||
|
except:
|
||||||
|
excinfo = py.code.ExceptionInfo()
|
||||||
|
nodenames = tuple(collector.listnames())
|
||||||
|
nodeid = collector.collection.getid(collector)
|
||||||
|
fspath = str(collector.fspath)
|
||||||
|
reason = longrepr = None
|
||||||
|
if not excinfo:
|
||||||
|
outcome = "passed"
|
||||||
|
else:
|
||||||
|
if excinfo.errisinstance(py.test.skip.Exception):
|
||||||
|
outcome = "skipped"
|
||||||
|
reason = str(excinfo.value)
|
||||||
|
longrepr = collector._repr_failure_py(excinfo, "line")
|
||||||
|
else:
|
||||||
|
outcome = "failed"
|
||||||
|
errorinfo = collector.repr_failure(excinfo)
|
||||||
|
if not hasattr(errorinfo, "toterminal"):
|
||||||
|
errorinfo = CollectErrorRepr(errorinfo)
|
||||||
|
longrepr = errorinfo
|
||||||
|
return CollectReport(nodenames, nodeid, fspath,
|
||||||
|
outcome, longrepr, result, reason)
|
||||||
|
|
||||||
|
class CollectReport(BaseReport):
|
||||||
|
def __init__(self, nodenames, nodeid, fspath, outcome,
|
||||||
|
longrepr, result, reason):
|
||||||
|
self.nodenames = nodenames
|
||||||
|
self.nodeid = nodeid
|
||||||
|
self.fspath = fspath
|
||||||
|
self.outcome = outcome
|
||||||
|
self.longrepr = longrepr
|
||||||
|
self.result = result
|
||||||
|
self.reason = reason
|
||||||
|
|
||||||
|
@property
|
||||||
|
def location(self):
|
||||||
|
return (self.fspath, None, self.fspath)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<CollectReport %r outcome=%r>" % (self.nodeid, self.outcome)
|
||||||
|
|
||||||
|
class CollectErrorRepr(TerminalRepr):
|
||||||
def __init__(self, msg):
|
def __init__(self, msg):
|
||||||
super(CollectErrorRepr, self).__init__()
|
|
||||||
self.longrepr = msg
|
self.longrepr = msg
|
||||||
def toterminal(self, out):
|
def toterminal(self, out):
|
||||||
out.line(str(self.longrepr), red=True)
|
out.line(str(self.longrepr), red=True)
|
||||||
|
|
||||||
class ItemTestReport(BaseReport):
|
|
||||||
failed = passed = skipped = False
|
|
||||||
|
|
||||||
def __init__(self, item, excinfo=None, when=None):
|
|
||||||
super(ItemTestReport, self).__init__()
|
|
||||||
self.item = item
|
|
||||||
self.when = when
|
|
||||||
if item and when != "setup":
|
|
||||||
self.keywords = item.keywords
|
|
||||||
else:
|
|
||||||
# if we fail during setup it might mean
|
|
||||||
# we are not able to access the underlying object
|
|
||||||
# this might e.g. happen if we are unpickled
|
|
||||||
# and our parent collector did not collect us
|
|
||||||
# (because it e.g. skipped for platform reasons)
|
|
||||||
self.keywords = {}
|
|
||||||
if not excinfo:
|
|
||||||
self.passed = True
|
|
||||||
self.shortrepr = "."
|
|
||||||
else:
|
|
||||||
if not isinstance(excinfo, py.code.ExceptionInfo):
|
|
||||||
self.failed = True
|
|
||||||
shortrepr = "?"
|
|
||||||
longrepr = excinfo
|
|
||||||
elif excinfo.errisinstance(py.test.skip.Exception):
|
|
||||||
self.skipped = True
|
|
||||||
shortrepr = "s"
|
|
||||||
longrepr = self.item._repr_failure_py(excinfo)
|
|
||||||
else:
|
|
||||||
self.failed = True
|
|
||||||
shortrepr = self.item.shortfailurerepr
|
|
||||||
if self.when == "call":
|
|
||||||
longrepr = self.item.repr_failure(excinfo)
|
|
||||||
else: # exception in setup or teardown
|
|
||||||
longrepr = self.item._repr_failure_py(excinfo)
|
|
||||||
shortrepr = shortrepr.lower()
|
|
||||||
self.shortrepr = shortrepr
|
|
||||||
self.longrepr = longrepr
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
status = (self.passed and "passed" or
|
|
||||||
self.skipped and "skipped" or
|
|
||||||
self.failed and "failed" or
|
|
||||||
"CORRUPT")
|
|
||||||
l = [repr(self.item.name), "when=%r" % self.when, "outcome %r" % status,]
|
|
||||||
if hasattr(self, 'node'):
|
|
||||||
l.append("txnode=%s" % self.node.gateway.id)
|
|
||||||
info = " " .join(map(str, l))
|
|
||||||
return "<ItemTestReport %s>" % info
|
|
||||||
|
|
||||||
def getnode(self):
|
|
||||||
return self.item
|
|
||||||
|
|
||||||
class CollectReport(BaseReport):
|
|
||||||
skipped = failed = passed = False
|
|
||||||
|
|
||||||
def __init__(self, collector, result, excinfo=None):
|
|
||||||
super(CollectReport, self).__init__()
|
|
||||||
self.collector = collector
|
|
||||||
if not excinfo:
|
|
||||||
self.passed = True
|
|
||||||
self.result = result
|
|
||||||
else:
|
|
||||||
if excinfo.errisinstance(py.test.skip.Exception):
|
|
||||||
self.skipped = True
|
|
||||||
self.reason = str(excinfo.value)
|
|
||||||
self.longrepr = self.collector._repr_failure_py(excinfo, "line")
|
|
||||||
else:
|
|
||||||
self.failed = True
|
|
||||||
errorinfo = self.collector.repr_failure(excinfo)
|
|
||||||
if not hasattr(errorinfo, "toterminal"):
|
|
||||||
errorinfo = CollectErrorRepr(errorinfo)
|
|
||||||
self.longrepr = errorinfo
|
|
||||||
|
|
||||||
def getnode(self):
|
|
||||||
return self.collector
|
|
||||||
|
|
||||||
class TeardownErrorReport(BaseReport):
|
|
||||||
skipped = passed = False
|
|
||||||
failed = True
|
|
||||||
when = "teardown"
|
|
||||||
def __init__(self, excinfo):
|
|
||||||
super(TeardownErrorReport, self).__init__()
|
|
||||||
self.longrepr = excinfo.getrepr(funcargs=True)
|
|
||||||
|
|
||||||
class SetupState(object):
|
class SetupState(object):
|
||||||
""" shared state for setting up/tearing down test items or collectors. """
|
""" shared state for setting up/tearing down test items or collectors. """
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
|
@ -231,19 +231,16 @@ def pytest_runtest_makereport(__multicall__, item, call):
|
||||||
if not item.config.getvalue("runxfail"):
|
if not item.config.getvalue("runxfail"):
|
||||||
rep = __multicall__.execute()
|
rep = __multicall__.execute()
|
||||||
rep.keywords['xfail'] = "reason: " + call.excinfo.value.msg
|
rep.keywords['xfail'] = "reason: " + call.excinfo.value.msg
|
||||||
rep.skipped = True
|
rep.outcome = "skipped"
|
||||||
rep.failed = False
|
|
||||||
return rep
|
return rep
|
||||||
if call.when == "call":
|
if call.when == "call":
|
||||||
rep = __multicall__.execute()
|
rep = __multicall__.execute()
|
||||||
evalxfail = getattr(item, '_evalxfail')
|
evalxfail = getattr(item, '_evalxfail')
|
||||||
if not item.config.getvalue("runxfail") and evalxfail.istrue():
|
if not item.config.getvalue("runxfail") and evalxfail.istrue():
|
||||||
if call.excinfo:
|
if call.excinfo:
|
||||||
rep.skipped = True
|
rep.outcome = "skipped"
|
||||||
rep.failed = rep.passed = False
|
|
||||||
else:
|
else:
|
||||||
rep.skipped = rep.passed = False
|
rep.outcome = "failed"
|
||||||
rep.failed = True
|
|
||||||
rep.keywords['xfail'] = evalxfail.getexplanation()
|
rep.keywords['xfail'] = evalxfail.getexplanation()
|
||||||
else:
|
else:
|
||||||
if 'xfail' in rep.keywords:
|
if 'xfail' in rep.keywords:
|
||||||
|
@ -275,9 +272,9 @@ def pytest_terminal_summary(terminalreporter):
|
||||||
show_xfailed(terminalreporter, lines)
|
show_xfailed(terminalreporter, lines)
|
||||||
elif char == "X":
|
elif char == "X":
|
||||||
show_xpassed(terminalreporter, lines)
|
show_xpassed(terminalreporter, lines)
|
||||||
elif char == "f":
|
elif char in "fF":
|
||||||
show_failed(terminalreporter, lines)
|
show_failed(terminalreporter, lines)
|
||||||
elif char == "s":
|
elif char in "sS":
|
||||||
show_skipped(terminalreporter, lines)
|
show_skipped(terminalreporter, lines)
|
||||||
if lines:
|
if lines:
|
||||||
tr._tw.sep("=", "short test summary info")
|
tr._tw.sep("=", "short test summary info")
|
||||||
|
@ -289,22 +286,24 @@ def show_failed(terminalreporter, lines):
|
||||||
failed = terminalreporter.stats.get("failed")
|
failed = terminalreporter.stats.get("failed")
|
||||||
if failed:
|
if failed:
|
||||||
for rep in failed:
|
for rep in failed:
|
||||||
pos = terminalreporter.gettestid(rep.item)
|
pos = rep.nodeid
|
||||||
lines.append("FAIL %s" %(pos, ))
|
lines.append("FAIL %s" %(pos, ))
|
||||||
|
|
||||||
def show_xfailed(terminalreporter, lines):
|
def show_xfailed(terminalreporter, lines):
|
||||||
xfailed = terminalreporter.stats.get("xfailed")
|
xfailed = terminalreporter.stats.get("xfailed")
|
||||||
if xfailed:
|
if xfailed:
|
||||||
for rep in xfailed:
|
for rep in xfailed:
|
||||||
pos = terminalreporter.gettestid(rep.item)
|
pos = rep.nodeid
|
||||||
reason = rep.keywords['xfail']
|
reason = rep.keywords['xfail']
|
||||||
lines.append("XFAIL %s %s" %(pos, reason))
|
lines.append("XFAIL %s" % (pos,))
|
||||||
|
if reason:
|
||||||
|
lines.append(" " + str(reason))
|
||||||
|
|
||||||
def show_xpassed(terminalreporter, lines):
|
def show_xpassed(terminalreporter, lines):
|
||||||
xpassed = terminalreporter.stats.get("xpassed")
|
xpassed = terminalreporter.stats.get("xpassed")
|
||||||
if xpassed:
|
if xpassed:
|
||||||
for rep in xpassed:
|
for rep in xpassed:
|
||||||
pos = terminalreporter.gettestid(rep.item)
|
pos = rep.nodeid
|
||||||
reason = rep.keywords['xfail']
|
reason = rep.keywords['xfail']
|
||||||
lines.append("XPASS %s %s" %(pos, reason))
|
lines.append("XPASS %s %s" %(pos, reason))
|
||||||
|
|
||||||
|
|
|
@ -22,31 +22,19 @@ def pytest_addoption(parser):
|
||||||
help="(deprecated, use -r)")
|
help="(deprecated, use -r)")
|
||||||
group._addoption('--tb', metavar="style",
|
group._addoption('--tb', metavar="style",
|
||||||
action="store", dest="tbstyle", default='long',
|
action="store", dest="tbstyle", default='long',
|
||||||
type="choice", choices=['long', 'short', 'no', 'line'],
|
type="choice", choices=['long', 'short', 'no', 'line', 'native'],
|
||||||
help="traceback print mode (long/short/line/no).")
|
help="traceback print mode (long/short/line/no).")
|
||||||
group._addoption('--fulltrace',
|
group._addoption('--fulltrace',
|
||||||
action="store_true", dest="fulltrace", default=False,
|
action="store_true", dest="fulltrace", default=False,
|
||||||
help="don't cut any tracebacks (default is to cut).")
|
help="don't cut any tracebacks (default is to cut).")
|
||||||
group._addoption('--funcargs',
|
|
||||||
action="store_true", dest="showfuncargs", default=False,
|
|
||||||
help="show available function arguments, sorted by plugin")
|
|
||||||
|
|
||||||
def pytest_configure(config):
|
def pytest_configure(config):
|
||||||
|
if config.option.showfuncargs:
|
||||||
|
return
|
||||||
if config.option.collectonly:
|
if config.option.collectonly:
|
||||||
reporter = CollectonlyReporter(config)
|
reporter = CollectonlyReporter(config)
|
||||||
elif config.option.showfuncargs:
|
|
||||||
config.setsessionclass(ShowFuncargSession)
|
|
||||||
reporter = None
|
|
||||||
else:
|
else:
|
||||||
reporter = TerminalReporter(config)
|
reporter = TerminalReporter(config)
|
||||||
if reporter:
|
|
||||||
# XXX see remote.py's XXX
|
|
||||||
for attr in 'pytest_terminal_hasmarkup', 'pytest_terminal_fullwidth':
|
|
||||||
if hasattr(config, attr):
|
|
||||||
#print "SETTING TERMINAL OPTIONS", attr, getattr(config, attr)
|
|
||||||
name = attr.split("_")[-1]
|
|
||||||
assert hasattr(self.reporter._tw, name), name
|
|
||||||
setattr(reporter._tw, name, getattr(config, attr))
|
|
||||||
config.pluginmanager.register(reporter, 'terminalreporter')
|
config.pluginmanager.register(reporter, 'terminalreporter')
|
||||||
|
|
||||||
def getreportopt(config):
|
def getreportopt(config):
|
||||||
|
@ -69,6 +57,17 @@ def getreportopt(config):
|
||||||
reportopts += char
|
reportopts += char
|
||||||
return reportopts
|
return reportopts
|
||||||
|
|
||||||
|
def pytest_report_teststatus(report):
|
||||||
|
if report.passed:
|
||||||
|
letter = "."
|
||||||
|
elif report.skipped:
|
||||||
|
letter = "s"
|
||||||
|
elif report.failed:
|
||||||
|
letter = "F"
|
||||||
|
if report.when != "call":
|
||||||
|
letter = "f"
|
||||||
|
return report.outcome, letter, report.outcome.upper()
|
||||||
|
|
||||||
class TerminalReporter:
|
class TerminalReporter:
|
||||||
def __init__(self, config, file=None):
|
def __init__(self, config, file=None):
|
||||||
self.config = config
|
self.config = config
|
||||||
|
@ -85,12 +84,12 @@ class TerminalReporter:
|
||||||
return char in self.reportchars
|
return char in self.reportchars
|
||||||
|
|
||||||
def write_fspath_result(self, fspath, res):
|
def write_fspath_result(self, fspath, res):
|
||||||
fspath = self.curdir.bestrelpath(fspath)
|
|
||||||
if fspath != self.currentfspath:
|
if fspath != self.currentfspath:
|
||||||
|
self.currentfspath = fspath
|
||||||
|
fspath = self.curdir.bestrelpath(fspath)
|
||||||
self._tw.line()
|
self._tw.line()
|
||||||
relpath = self.curdir.bestrelpath(fspath)
|
relpath = self.curdir.bestrelpath(fspath)
|
||||||
self._tw.write(relpath + " ")
|
self._tw.write(relpath + " ")
|
||||||
self.currentfspath = fspath
|
|
||||||
self._tw.write(res)
|
self._tw.write(res)
|
||||||
|
|
||||||
def write_ensure_prefix(self, prefix, extra="", **kwargs):
|
def write_ensure_prefix(self, prefix, extra="", **kwargs):
|
||||||
|
@ -116,42 +115,6 @@ class TerminalReporter:
|
||||||
self.ensure_newline()
|
self.ensure_newline()
|
||||||
self._tw.sep(sep, title, **markup)
|
self._tw.sep(sep, title, **markup)
|
||||||
|
|
||||||
def getcategoryletterword(self, rep):
|
|
||||||
res = self.config.hook.pytest_report_teststatus(report=rep)
|
|
||||||
if res:
|
|
||||||
return res
|
|
||||||
for cat in 'skipped failed passed ???'.split():
|
|
||||||
if getattr(rep, cat, None):
|
|
||||||
break
|
|
||||||
return cat, self.getoutcomeletter(rep), self.getoutcomeword(rep)
|
|
||||||
|
|
||||||
def getoutcomeletter(self, rep):
|
|
||||||
return rep.shortrepr
|
|
||||||
|
|
||||||
def getoutcomeword(self, rep):
|
|
||||||
if rep.passed:
|
|
||||||
return "PASS", dict(green=True)
|
|
||||||
elif rep.failed:
|
|
||||||
return "FAIL", dict(red=True)
|
|
||||||
elif rep.skipped:
|
|
||||||
return "SKIP"
|
|
||||||
else:
|
|
||||||
return "???", dict(red=True)
|
|
||||||
|
|
||||||
def gettestid(self, item, relative=True):
|
|
||||||
fspath = item.fspath
|
|
||||||
chain = [x for x in item.listchain() if x.fspath == fspath]
|
|
||||||
chain = chain[1:]
|
|
||||||
names = [x.name for x in chain if x.name != "()"]
|
|
||||||
path = item.fspath
|
|
||||||
if relative:
|
|
||||||
relpath = path.relto(self.curdir)
|
|
||||||
if relpath:
|
|
||||||
path = relpath
|
|
||||||
names.insert(0, str(path))
|
|
||||||
return "::".join(names)
|
|
||||||
|
|
||||||
|
|
||||||
def pytest_internalerror(self, excrepr):
|
def pytest_internalerror(self, excrepr):
|
||||||
for line in str(excrepr).split("\n"):
|
for line in str(excrepr).split("\n"):
|
||||||
self.write_line("INTERNALERROR> " + line)
|
self.write_line("INTERNALERROR> " + line)
|
||||||
|
@ -170,37 +133,47 @@ class TerminalReporter:
|
||||||
self.write_line("[%s] %s" %(category, msg))
|
self.write_line("[%s] %s" %(category, msg))
|
||||||
|
|
||||||
def pytest_deselected(self, items):
|
def pytest_deselected(self, items):
|
||||||
self.stats.setdefault('deselected', []).append(items)
|
self.stats.setdefault('deselected', []).extend(items)
|
||||||
|
|
||||||
def pytest_itemstart(self, item, node=None):
|
|
||||||
if self.config.option.verbose:
|
|
||||||
line = self._reportinfoline(item)
|
|
||||||
self.write_ensure_prefix(line, "")
|
|
||||||
else:
|
|
||||||
# ensure that the path is printed before the
|
|
||||||
# 1st test of a module starts running
|
|
||||||
self.write_fspath_result(self._getfspath(item), "")
|
|
||||||
|
|
||||||
def pytest__teardown_final_logerror(self, report):
|
def pytest__teardown_final_logerror(self, report):
|
||||||
self.stats.setdefault("error", []).append(report)
|
self.stats.setdefault("error", []).append(report)
|
||||||
|
|
||||||
|
def pytest_runtest_logstart(self, nodeid, location, fspath):
|
||||||
|
# ensure that the path is printed before the
|
||||||
|
# 1st test of a module starts running
|
||||||
|
if self.config.option.verbose:
|
||||||
|
line = self._locationline(fspath, *location)
|
||||||
|
self.write_ensure_prefix(line, "")
|
||||||
|
else:
|
||||||
|
self.write_fspath_result(py.path.local(fspath), "")
|
||||||
|
|
||||||
def pytest_runtest_logreport(self, report):
|
def pytest_runtest_logreport(self, report):
|
||||||
rep = report
|
rep = report
|
||||||
cat, letter, word = self.getcategoryletterword(rep)
|
res = self.config.hook.pytest_report_teststatus(report=rep)
|
||||||
|
cat, letter, word = res
|
||||||
|
self.stats.setdefault(cat, []).append(rep)
|
||||||
if not letter and not word:
|
if not letter and not word:
|
||||||
# probably passed setup/teardown
|
# probably passed setup/teardown
|
||||||
return
|
return
|
||||||
|
if not self.config.option.verbose:
|
||||||
|
if not hasattr(rep, 'node'):
|
||||||
|
self.write_fspath_result(rep.fspath, letter)
|
||||||
|
else:
|
||||||
|
self._tw.write(letter)
|
||||||
|
else:
|
||||||
if isinstance(word, tuple):
|
if isinstance(word, tuple):
|
||||||
word, markup = word
|
word, markup = word
|
||||||
else:
|
else:
|
||||||
markup = {}
|
if rep.passed:
|
||||||
self.stats.setdefault(cat, []).append(rep)
|
markup = {'green':True}
|
||||||
if not self.config.option.verbose:
|
elif rep.failed:
|
||||||
self.write_fspath_result(self._getfspath(rep.item), letter)
|
markup = {'red':True}
|
||||||
else:
|
elif rep.skipped:
|
||||||
line = self._reportinfoline(rep.item)
|
markup = {'yellow':True}
|
||||||
|
line = self._locationline(str(rep.fspath), *rep.location)
|
||||||
if not hasattr(rep, 'node'):
|
if not hasattr(rep, 'node'):
|
||||||
self.write_ensure_prefix(line, word, **markup)
|
self.write_ensure_prefix(line, word, **markup)
|
||||||
|
#self._tw.write(word, **markup)
|
||||||
else:
|
else:
|
||||||
self.ensure_newline()
|
self.ensure_newline()
|
||||||
if hasattr(rep, 'node'):
|
if hasattr(rep, 'node'):
|
||||||
|
@ -213,25 +186,27 @@ class TerminalReporter:
|
||||||
if not report.passed:
|
if not report.passed:
|
||||||
if report.failed:
|
if report.failed:
|
||||||
self.stats.setdefault("error", []).append(report)
|
self.stats.setdefault("error", []).append(report)
|
||||||
self.write_fspath_result(report.collector.fspath, "E")
|
self.write_fspath_result(report.fspath, "E")
|
||||||
elif report.skipped:
|
elif report.skipped:
|
||||||
self.stats.setdefault("skipped", []).append(report)
|
self.stats.setdefault("skipped", []).append(report)
|
||||||
self.write_fspath_result(report.collector.fspath, "S")
|
self.write_fspath_result(report.fspath, "S")
|
||||||
|
|
||||||
def pytest_sessionstart(self, session):
|
def pytest_sessionstart(self, session):
|
||||||
self.write_sep("=", "test session starts", bold=True)
|
self.write_sep("=", "test session starts", bold=True)
|
||||||
self._sessionstarttime = py.std.time.time()
|
self._sessionstarttime = py.std.time.time()
|
||||||
|
|
||||||
verinfo = ".".join(map(str, sys.version_info[:3]))
|
verinfo = ".".join(map(str, sys.version_info[:3]))
|
||||||
msg = "platform %s -- Python %s" % (sys.platform, verinfo)
|
msg = "platform %s -- Python %s" % (sys.platform, verinfo)
|
||||||
msg += " -- pytest-%s" % (py.__version__)
|
msg += " -- pytest-%s" % (py.__version__)
|
||||||
if self.config.option.verbose or self.config.option.debug or getattr(self.config.option, 'pastebin', None):
|
if self.config.option.verbose or self.config.option.debug or \
|
||||||
|
getattr(self.config.option, 'pastebin', None):
|
||||||
msg += " -- " + str(sys.executable)
|
msg += " -- " + str(sys.executable)
|
||||||
self.write_line(msg)
|
self.write_line(msg)
|
||||||
lines = self.config.hook.pytest_report_header(config=self.config)
|
lines = self.config.hook.pytest_report_header(config=self.config)
|
||||||
lines.reverse()
|
lines.reverse()
|
||||||
for line in flatten(lines):
|
for line in flatten(lines):
|
||||||
self.write_line(line)
|
self.write_line(line)
|
||||||
|
|
||||||
|
def pytest_log_finishcollection(self):
|
||||||
for i, testarg in enumerate(self.config.args):
|
for i, testarg in enumerate(self.config.args):
|
||||||
self.write_line("test path %d: %s" %(i+1, testarg))
|
self.write_line("test path %d: %s" %(i+1, testarg))
|
||||||
|
|
||||||
|
@ -260,52 +235,40 @@ class TerminalReporter:
|
||||||
else:
|
else:
|
||||||
excrepr.reprcrash.toterminal(self._tw)
|
excrepr.reprcrash.toterminal(self._tw)
|
||||||
|
|
||||||
def _reportinfoline(self, item):
|
def _locationline(self, collect_fspath, fspath, lineno, domain):
|
||||||
collect_fspath = self._getfspath(item)
|
|
||||||
fspath, lineno, msg = self._getreportinfo(item)
|
|
||||||
if fspath and fspath != collect_fspath:
|
if fspath and fspath != collect_fspath:
|
||||||
fspath = "%s <- %s" % (
|
fspath = "%s <- %s" % (
|
||||||
self.curdir.bestrelpath(collect_fspath),
|
self.curdir.bestrelpath(py.path.local(collect_fspath)),
|
||||||
self.curdir.bestrelpath(fspath))
|
self.curdir.bestrelpath(py.path.local(fspath)))
|
||||||
elif fspath:
|
elif fspath:
|
||||||
fspath = self.curdir.bestrelpath(fspath)
|
fspath = self.curdir.bestrelpath(py.path.local(fspath))
|
||||||
if lineno is not None:
|
if lineno is not None:
|
||||||
lineno += 1
|
lineno += 1
|
||||||
if fspath and lineno and msg:
|
if fspath and lineno and domain:
|
||||||
line = "%(fspath)s:%(lineno)s: %(msg)s"
|
line = "%(fspath)s:%(lineno)s: %(domain)s"
|
||||||
elif fspath and msg:
|
elif fspath and domain:
|
||||||
line = "%(fspath)s: %(msg)s"
|
line = "%(fspath)s: %(domain)s"
|
||||||
elif fspath and lineno:
|
elif fspath and lineno:
|
||||||
line = "%(fspath)s:%(lineno)s %(extrapath)s"
|
line = "%(fspath)s:%(lineno)s %(extrapath)s"
|
||||||
else:
|
else:
|
||||||
line = "[noreportinfo]"
|
line = "[nolocation]"
|
||||||
return line % locals() + " "
|
return line % locals() + " "
|
||||||
|
|
||||||
def _getfailureheadline(self, rep):
|
def _getfailureheadline(self, rep):
|
||||||
if hasattr(rep, "collector"):
|
if hasattr(rep, 'location'):
|
||||||
return str(rep.collector.fspath)
|
fspath, lineno, domain = rep.location
|
||||||
elif hasattr(rep, 'item'):
|
return domain
|
||||||
fspath, lineno, msg = self._getreportinfo(rep.item)
|
|
||||||
return msg
|
|
||||||
else:
|
else:
|
||||||
return "test session"
|
return "test session" # XXX?
|
||||||
|
|
||||||
def _getreportinfo(self, item):
|
def _getcrashline(self, rep):
|
||||||
try:
|
try:
|
||||||
return item.__reportinfo
|
return str(rep.longrepr.reprcrash)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
|
||||||
reportinfo = item.config.hook.pytest_report_iteminfo(item=item)
|
|
||||||
# cache on item
|
|
||||||
item.__reportinfo = reportinfo
|
|
||||||
return reportinfo
|
|
||||||
|
|
||||||
def _getfspath(self, item):
|
|
||||||
try:
|
try:
|
||||||
return item.fspath
|
return str(rep.longrepr)[:50]
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
fspath, lineno, msg = self._getreportinfo(item)
|
return ""
|
||||||
return fspath
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# summaries for sessionfinish
|
# summaries for sessionfinish
|
||||||
|
@ -317,7 +280,7 @@ class TerminalReporter:
|
||||||
self.write_sep("=", "FAILURES")
|
self.write_sep("=", "FAILURES")
|
||||||
for rep in self.stats['failed']:
|
for rep in self.stats['failed']:
|
||||||
if tbstyle == "line":
|
if tbstyle == "line":
|
||||||
line = rep._getcrashline()
|
line = self._getcrashline(rep)
|
||||||
self.write_line(line)
|
self.write_line(line)
|
||||||
else:
|
else:
|
||||||
msg = self._getfailureheadline(rep)
|
msg = self._getfailureheadline(rep)
|
||||||
|
@ -383,21 +346,27 @@ class CollectonlyReporter:
|
||||||
self.outindent(collector)
|
self.outindent(collector)
|
||||||
self.indent += self.INDENT
|
self.indent += self.INDENT
|
||||||
|
|
||||||
def pytest_itemstart(self, item, node=None):
|
def pytest_log_itemcollect(self, item):
|
||||||
self.outindent(item)
|
self.outindent(item)
|
||||||
|
|
||||||
def pytest_collectreport(self, report):
|
def pytest_collectreport(self, report):
|
||||||
if not report.passed:
|
if not report.passed:
|
||||||
self.outindent("!!! %s !!!" % report.longrepr.reprcrash.message)
|
if hasattr(report.longrepr, 'reprcrash'):
|
||||||
|
msg = report.longrepr.reprcrash.message
|
||||||
|
else:
|
||||||
|
# XXX unify (we have CollectErrorRepr here)
|
||||||
|
msg = str(report.longrepr.longrepr)
|
||||||
|
self.outindent("!!! %s !!!" % msg)
|
||||||
|
#self.outindent("!!! error !!!")
|
||||||
self._failed.append(report)
|
self._failed.append(report)
|
||||||
self.indent = self.indent[:-len(self.INDENT)]
|
self.indent = self.indent[:-len(self.INDENT)]
|
||||||
|
|
||||||
def pytest_sessionfinish(self, session, exitstatus):
|
def pytest_log_finishcollection(self):
|
||||||
if self._failed:
|
if self._failed:
|
||||||
self._tw.sep("!", "collection failures")
|
self._tw.sep("!", "collection failures")
|
||||||
for rep in self._failed:
|
for rep in self._failed:
|
||||||
rep.toterminal(self._tw)
|
rep.toterminal(self._tw)
|
||||||
|
return self._failed and 1 or 0
|
||||||
|
|
||||||
def repr_pythonversion(v=None):
|
def repr_pythonversion(v=None):
|
||||||
if v is None:
|
if v is None:
|
||||||
|
@ -415,50 +384,3 @@ def flatten(l):
|
||||||
else:
|
else:
|
||||||
yield x
|
yield x
|
||||||
|
|
||||||
from py._test.session import Session
|
|
||||||
class ShowFuncargSession(Session):
|
|
||||||
def main(self, colitems):
|
|
||||||
self.fspath = py.path.local()
|
|
||||||
self.sessionstarts()
|
|
||||||
try:
|
|
||||||
self.showargs(colitems[0])
|
|
||||||
finally:
|
|
||||||
self.sessionfinishes(exitstatus=1)
|
|
||||||
|
|
||||||
def showargs(self, colitem):
|
|
||||||
tw = py.io.TerminalWriter()
|
|
||||||
from py._test.funcargs import getplugins
|
|
||||||
from py._test.funcargs import FuncargRequest
|
|
||||||
plugins = getplugins(colitem, withpy=True)
|
|
||||||
verbose = self.config.getvalue("verbose")
|
|
||||||
for plugin in plugins:
|
|
||||||
available = []
|
|
||||||
for name, factory in vars(plugin).items():
|
|
||||||
if name.startswith(FuncargRequest._argprefix):
|
|
||||||
name = name[len(FuncargRequest._argprefix):]
|
|
||||||
if name not in available:
|
|
||||||
available.append([name, factory])
|
|
||||||
if available:
|
|
||||||
pluginname = plugin.__name__
|
|
||||||
for name, factory in available:
|
|
||||||
loc = self.getlocation(factory)
|
|
||||||
if verbose:
|
|
||||||
funcargspec = "%s -- %s" %(name, loc,)
|
|
||||||
else:
|
|
||||||
funcargspec = name
|
|
||||||
tw.line(funcargspec, green=True)
|
|
||||||
doc = factory.__doc__ or ""
|
|
||||||
if doc:
|
|
||||||
for line in doc.split("\n"):
|
|
||||||
tw.line(" " + line.strip())
|
|
||||||
else:
|
|
||||||
tw.line(" %s: no docstring available" %(loc,),
|
|
||||||
red=True)
|
|
||||||
|
|
||||||
def getlocation(self, function):
|
|
||||||
import inspect
|
|
||||||
fn = py.path.local(inspect.getfile(function))
|
|
||||||
lineno = py.builtin._getcode(function).co_firstlineno
|
|
||||||
if fn.relto(self.fspath):
|
|
||||||
fn = fn.relto(self.fspath)
|
|
||||||
return "%s:%d" %(fn, lineno+1)
|
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
import py
|
|
||||||
import sys
|
|
||||||
|
|
||||||
#
|
|
||||||
# main entry point
|
|
||||||
#
|
|
||||||
|
|
||||||
def main(args=None):
|
|
||||||
if args is None:
|
|
||||||
args = sys.argv[1:]
|
|
||||||
config = py.test.config
|
|
||||||
try:
|
|
||||||
config.parse(args)
|
|
||||||
config.pluginmanager.do_configure(config)
|
|
||||||
session = config.initsession()
|
|
||||||
colitems = config.getinitialnodes()
|
|
||||||
exitstatus = session.main(colitems)
|
|
||||||
config.pluginmanager.do_unconfigure(config)
|
|
||||||
except config.Error:
|
|
||||||
e = sys.exc_info()[1]
|
|
||||||
sys.stderr.write("ERROR: %s\n" %(e.args[0],))
|
|
||||||
exitstatus = 3
|
|
||||||
py.test.config = py.test.config.__class__()
|
|
||||||
return exitstatus
|
|
|
@ -25,47 +25,15 @@ class Node(object):
|
||||||
""" base class for all Nodes in the collection tree.
|
""" base class for all Nodes in the collection tree.
|
||||||
Collector subclasses have children, Items are terminal nodes.
|
Collector subclasses have children, Items are terminal nodes.
|
||||||
"""
|
"""
|
||||||
def __init__(self, name, parent=None, config=None):
|
def __init__(self, name, parent=None, config=None, collection=None):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self.config = config or parent.config
|
self.config = config or parent.config
|
||||||
|
self.collection = collection or getattr(parent, 'collection', None)
|
||||||
self.fspath = getattr(parent, 'fspath', None)
|
self.fspath = getattr(parent, 'fspath', None)
|
||||||
self.ihook = HookProxy(self)
|
self.ihook = HookProxy(self)
|
||||||
self.keywords = self.readkeywords()
|
self.keywords = self.readkeywords()
|
||||||
|
|
||||||
def _reraiseunpicklingproblem(self):
|
|
||||||
if hasattr(self, '_unpickle_exc'):
|
|
||||||
py.builtin._reraise(*self._unpickle_exc)
|
|
||||||
|
|
||||||
#
|
|
||||||
# note to myself: Pickling is uh.
|
|
||||||
#
|
|
||||||
def __getstate__(self):
|
|
||||||
return (self.name, self.parent)
|
|
||||||
def __setstate__(self, nameparent):
|
|
||||||
name, parent = nameparent
|
|
||||||
try:
|
|
||||||
colitems = parent._memocollect()
|
|
||||||
for colitem in colitems:
|
|
||||||
if colitem.name == name:
|
|
||||||
# we are a copy that will not be returned
|
|
||||||
# by our parent
|
|
||||||
self.__dict__ = colitem.__dict__
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
raise ValueError("item %r not found in parent collection %r" %(
|
|
||||||
name, [x.name for x in colitems]))
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
raise
|
|
||||||
except Exception:
|
|
||||||
# our parent can't collect us but we want unpickling to
|
|
||||||
# otherwise continue - self._reraiseunpicklingproblem() will
|
|
||||||
# reraise the problem
|
|
||||||
self._unpickle_exc = py.std.sys.exc_info()
|
|
||||||
self.name = name
|
|
||||||
self.parent = parent
|
|
||||||
self.config = parent.config
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
if getattr(self.config.option, 'debug', False):
|
if getattr(self.config.option, 'debug', False):
|
||||||
return "<%s %r %0x>" %(self.__class__.__name__,
|
return "<%s %r %0x>" %(self.__class__.__name__,
|
||||||
|
@ -79,7 +47,8 @@ class Node(object):
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
if not isinstance(other, Node):
|
if not isinstance(other, Node):
|
||||||
return False
|
return False
|
||||||
return self.name == other.name and self.parent == other.parent
|
return self.__class__ == other.__class__ and \
|
||||||
|
self.name == other.name and self.parent == other.parent
|
||||||
|
|
||||||
def __ne__(self, other):
|
def __ne__(self, other):
|
||||||
return not self == other
|
return not self == other
|
||||||
|
@ -102,7 +71,7 @@ class Node(object):
|
||||||
return getattr(self, attrname)
|
return getattr(self, attrname)
|
||||||
try:
|
try:
|
||||||
res = function()
|
res = function()
|
||||||
except (KeyboardInterrupt, SystemExit):
|
except py.builtin._sysex:
|
||||||
raise
|
raise
|
||||||
except:
|
except:
|
||||||
failure = py.std.sys.exc_info()
|
failure = py.std.sys.exc_info()
|
||||||
|
@ -117,7 +86,7 @@ class Node(object):
|
||||||
l = [self]
|
l = [self]
|
||||||
while 1:
|
while 1:
|
||||||
x = l[0]
|
x = l[0]
|
||||||
if x.parent is not None and x.parent.parent is not None:
|
if x.parent is not None: # and x.parent.parent is not None:
|
||||||
l.insert(0, x.parent)
|
l.insert(0, x.parent)
|
||||||
else:
|
else:
|
||||||
return l
|
return l
|
||||||
|
@ -137,39 +106,6 @@ class Node(object):
|
||||||
def _keywords(self):
|
def _keywords(self):
|
||||||
return [self.name]
|
return [self.name]
|
||||||
|
|
||||||
def _skipbykeyword(self, keywordexpr):
|
|
||||||
""" return True if they given keyword expression means to
|
|
||||||
skip this collector/item.
|
|
||||||
"""
|
|
||||||
if not keywordexpr:
|
|
||||||
return
|
|
||||||
chain = self.listchain()
|
|
||||||
for key in filter(None, keywordexpr.split()):
|
|
||||||
eor = key[:1] == '-'
|
|
||||||
if eor:
|
|
||||||
key = key[1:]
|
|
||||||
if not (eor ^ self._matchonekeyword(key, chain)):
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _matchonekeyword(self, key, chain):
|
|
||||||
elems = key.split(".")
|
|
||||||
# XXX O(n^2), anyone cares?
|
|
||||||
chain = [item.keywords for item in chain if item.keywords]
|
|
||||||
for start, _ in enumerate(chain):
|
|
||||||
if start + len(elems) > len(chain):
|
|
||||||
return False
|
|
||||||
for num, elem in enumerate(elems):
|
|
||||||
for keyword in chain[num + start]:
|
|
||||||
ok = False
|
|
||||||
if elem in keyword:
|
|
||||||
ok = True
|
|
||||||
break
|
|
||||||
if not ok:
|
|
||||||
break
|
|
||||||
if num == len(elems) - 1 and ok:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _prunetraceback(self, traceback):
|
def _prunetraceback(self, traceback):
|
||||||
return traceback
|
return traceback
|
||||||
|
|
||||||
|
@ -190,7 +126,6 @@ class Node(object):
|
||||||
style=style)
|
style=style)
|
||||||
|
|
||||||
repr_failure = _repr_failure_py
|
repr_failure = _repr_failure_py
|
||||||
shortfailurerepr = "F"
|
|
||||||
|
|
||||||
class Collector(Node):
|
class Collector(Node):
|
||||||
"""
|
"""
|
||||||
|
@ -270,19 +205,12 @@ class Collector(Node):
|
||||||
return traceback
|
return traceback
|
||||||
|
|
||||||
class FSCollector(Collector):
|
class FSCollector(Collector):
|
||||||
def __init__(self, fspath, parent=None, config=None):
|
def __init__(self, fspath, parent=None, config=None, collection=None):
|
||||||
fspath = py.path.local(fspath)
|
fspath = py.path.local(fspath)
|
||||||
super(FSCollector, self).__init__(fspath.basename, parent, config=config)
|
super(FSCollector, self).__init__(fspath.basename,
|
||||||
|
parent, config, collection)
|
||||||
self.fspath = fspath
|
self.fspath = fspath
|
||||||
|
|
||||||
def __getstate__(self):
|
|
||||||
# RootCollector.getbynames() inserts a directory which we need
|
|
||||||
# to throw out here for proper re-instantiation
|
|
||||||
if isinstance(self.parent.parent, RootCollector):
|
|
||||||
assert self.parent.fspath == self.parent.parent.fspath, self.parent
|
|
||||||
return (self.name, self.parent.parent) # shortcut
|
|
||||||
return super(Collector, self).__getstate__()
|
|
||||||
|
|
||||||
class File(FSCollector):
|
class File(FSCollector):
|
||||||
""" base class for collecting tests from a file. """
|
""" base class for collecting tests from a file. """
|
||||||
|
|
||||||
|
@ -368,59 +296,3 @@ def warnoldtestrun(function=None):
|
||||||
"item.run() and item.execute()",
|
"item.run() and item.execute()",
|
||||||
stacklevel=2, function=function)
|
stacklevel=2, function=function)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class RootCollector(Directory):
|
|
||||||
def __init__(self, config):
|
|
||||||
Directory.__init__(self, config.topdir, parent=None, config=config)
|
|
||||||
self.name = None
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "<RootCollector fspath=%r>" %(self.fspath,)
|
|
||||||
|
|
||||||
def getbynames(self, names):
|
|
||||||
current = self.consider(self.config.topdir)
|
|
||||||
while names:
|
|
||||||
name = names.pop(0)
|
|
||||||
if name == ".": # special "identity" name
|
|
||||||
continue
|
|
||||||
l = []
|
|
||||||
for x in current._memocollect():
|
|
||||||
if x.name == name:
|
|
||||||
l.append(x)
|
|
||||||
elif x.fspath == current.fspath.join(name):
|
|
||||||
l.append(x)
|
|
||||||
elif x.name == "()":
|
|
||||||
names.insert(0, name)
|
|
||||||
l.append(x)
|
|
||||||
break
|
|
||||||
if not l:
|
|
||||||
raise ValueError("no node named %r below %r" %(name, current))
|
|
||||||
current = l[0]
|
|
||||||
return current
|
|
||||||
|
|
||||||
def totrail(self, node):
|
|
||||||
chain = node.listchain()
|
|
||||||
names = [self._getrelpath(chain[0].fspath)]
|
|
||||||
names += [x.name for x in chain[1:]]
|
|
||||||
return names
|
|
||||||
|
|
||||||
def fromtrail(self, trail):
|
|
||||||
return self.config._rootcol.getbynames(trail)
|
|
||||||
|
|
||||||
def _getrelpath(self, fspath):
|
|
||||||
topdir = self.config.topdir
|
|
||||||
relpath = fspath.relto(topdir)
|
|
||||||
if not relpath:
|
|
||||||
if fspath == topdir:
|
|
||||||
relpath = "."
|
|
||||||
else:
|
|
||||||
raise ValueError("%r not relative to topdir %s"
|
|
||||||
%(self.fspath, topdir))
|
|
||||||
return relpath
|
|
||||||
|
|
||||||
def __getstate__(self):
|
|
||||||
return self.config
|
|
||||||
|
|
||||||
def __setstate__(self, config):
|
|
||||||
self.__init__(config)
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ import py, os
|
||||||
from py._test.conftesthandle import Conftest
|
from py._test.conftesthandle import Conftest
|
||||||
from py._test.pluginmanager import PluginManager
|
from py._test.pluginmanager import PluginManager
|
||||||
from py._test import parseopt
|
from py._test import parseopt
|
||||||
from py._test.collect import RootCollector
|
|
||||||
|
|
||||||
def ensuretemp(string, dir=1):
|
def ensuretemp(string, dir=1):
|
||||||
""" (deprecated) return temporary directory path with
|
""" (deprecated) return temporary directory path with
|
||||||
|
@ -31,9 +30,8 @@ class Config(object):
|
||||||
basetemp = None
|
basetemp = None
|
||||||
_sessionclass = None
|
_sessionclass = None
|
||||||
|
|
||||||
def __init__(self, topdir=None, option=None):
|
def __init__(self):
|
||||||
self.option = option or CmdOptions()
|
self.option = CmdOptions()
|
||||||
self.topdir = topdir
|
|
||||||
self._parser = parseopt.Parser(
|
self._parser = parseopt.Parser(
|
||||||
usage="usage: %prog [options] [file_or_dir] [file_or_dir] [...]",
|
usage="usage: %prog [options] [file_or_dir] [file_or_dir] [...]",
|
||||||
processopt=self._processopt,
|
processopt=self._processopt,
|
||||||
|
@ -97,39 +95,7 @@ class Config(object):
|
||||||
args = self._parser.parse_setoption(args, self.option)
|
args = self._parser.parse_setoption(args, self.option)
|
||||||
if not args:
|
if not args:
|
||||||
args.append(py.std.os.getcwd())
|
args.append(py.std.os.getcwd())
|
||||||
self.topdir = gettopdir(args)
|
self.args = args
|
||||||
self._rootcol = RootCollector(config=self)
|
|
||||||
self._setargs(args)
|
|
||||||
|
|
||||||
def _setargs(self, args):
|
|
||||||
self.args = list(args)
|
|
||||||
self._argfspaths = [py.path.local(decodearg(x)[0]) for x in args]
|
|
||||||
|
|
||||||
# config objects are usually pickled across system
|
|
||||||
# barriers but they contain filesystem paths.
|
|
||||||
# upon getstate/setstate we take care to do everything
|
|
||||||
# relative to "topdir".
|
|
||||||
def __getstate__(self):
|
|
||||||
l = []
|
|
||||||
for path in self.args:
|
|
||||||
path = py.path.local(path)
|
|
||||||
l.append(path.relto(self.topdir))
|
|
||||||
return l, self.option.__dict__
|
|
||||||
|
|
||||||
def __setstate__(self, repr):
|
|
||||||
# we have to set py.test.config because loading
|
|
||||||
# of conftest files may use it (deprecated)
|
|
||||||
# mainly by py.test.config.addoptions()
|
|
||||||
global config_per_process
|
|
||||||
py.test.config = config_per_process = self
|
|
||||||
args, cmdlineopts = repr
|
|
||||||
cmdlineopts = CmdOptions(**cmdlineopts)
|
|
||||||
# next line will registers default plugins
|
|
||||||
self.__init__(topdir=py.path.local(), option=cmdlineopts)
|
|
||||||
self._rootcol = RootCollector(config=self)
|
|
||||||
args = [str(self.topdir.join(x)) for x in args]
|
|
||||||
self._preparse(args)
|
|
||||||
self._setargs(args)
|
|
||||||
|
|
||||||
def ensuretemp(self, string, dir=True):
|
def ensuretemp(self, string, dir=True):
|
||||||
return self.getbasetemp().ensure(string, dir=dir)
|
return self.getbasetemp().ensure(string, dir=dir)
|
||||||
|
@ -154,27 +120,6 @@ class Config(object):
|
||||||
return py.path.local.make_numbered_dir(prefix=basename,
|
return py.path.local.make_numbered_dir(prefix=basename,
|
||||||
keep=0, rootdir=basetemp, lock_timeout=None)
|
keep=0, rootdir=basetemp, lock_timeout=None)
|
||||||
|
|
||||||
def getinitialnodes(self):
|
|
||||||
return [self.getnode(arg) for arg in self.args]
|
|
||||||
|
|
||||||
def getnode(self, arg):
|
|
||||||
parts = decodearg(arg)
|
|
||||||
path = py.path.local(parts.pop(0))
|
|
||||||
if not path.check():
|
|
||||||
raise self.Error("file not found: %s" %(path,))
|
|
||||||
topdir = self.topdir
|
|
||||||
if path != topdir and not path.relto(topdir):
|
|
||||||
raise self.Error("path %r is not relative to %r" %
|
|
||||||
(str(path), str(topdir)))
|
|
||||||
# assumtion: pytest's fs-collector tree follows the filesystem tree
|
|
||||||
names = list(filter(None, path.relto(topdir).split(path.sep)))
|
|
||||||
names += parts
|
|
||||||
try:
|
|
||||||
return self._rootcol.getbynames(names)
|
|
||||||
except ValueError:
|
|
||||||
e = py.std.sys.exc_info()[1]
|
|
||||||
raise self.Error("can't collect: %s\n%s" % (arg, e.args[0]))
|
|
||||||
|
|
||||||
def _getcollectclass(self, name, path):
|
def _getcollectclass(self, name, path):
|
||||||
try:
|
try:
|
||||||
cls = self._conftest.rget(name, path)
|
cls = self._conftest.rget(name, path)
|
||||||
|
@ -239,48 +184,10 @@ class Config(object):
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return self._conftest.rget(name, path)
|
return self._conftest.rget(name, path)
|
||||||
|
|
||||||
def setsessionclass(self, cls):
|
|
||||||
if self._sessionclass is not None:
|
|
||||||
raise ValueError("sessionclass already set to: %r" %(
|
|
||||||
self._sessionclass))
|
|
||||||
self._sessionclass = cls
|
|
||||||
|
|
||||||
def initsession(self):
|
|
||||||
""" return an initialized session object. """
|
|
||||||
cls = self._sessionclass
|
|
||||||
if cls is None:
|
|
||||||
from py._test.session import Session
|
|
||||||
cls = Session
|
|
||||||
session = cls(self)
|
|
||||||
self.trace("instantiated session %r" % session)
|
|
||||||
return session
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# helpers
|
# helpers
|
||||||
#
|
#
|
||||||
|
|
||||||
def gettopdir(args):
|
|
||||||
""" return the top directory for the given paths.
|
|
||||||
if the common base dir resides in a python package
|
|
||||||
parent directory of the root package is returned.
|
|
||||||
"""
|
|
||||||
fsargs = [py.path.local(decodearg(arg)[0]) for arg in args]
|
|
||||||
p = fsargs and fsargs[0] or None
|
|
||||||
for x in fsargs[1:]:
|
|
||||||
p = p.common(x)
|
|
||||||
assert p, "cannot determine common basedir of %s" %(fsargs,)
|
|
||||||
pkgdir = p.pypkgpath()
|
|
||||||
if pkgdir is None:
|
|
||||||
if p.check(file=1):
|
|
||||||
p = p.dirpath()
|
|
||||||
return p
|
|
||||||
else:
|
|
||||||
return pkgdir.dirpath()
|
|
||||||
|
|
||||||
def decodearg(arg):
|
|
||||||
arg = str(arg)
|
|
||||||
return arg.split("::")
|
|
||||||
|
|
||||||
def onpytestaccess():
|
def onpytestaccess():
|
||||||
# it's enough to have our containing module loaded as
|
# it's enough to have our containing module loaded as
|
||||||
# it initializes a per-process config instance
|
# it initializes a per-process config instance
|
||||||
|
|
|
@ -1,186 +0,0 @@
|
||||||
import py
|
|
||||||
|
|
||||||
def getfuncargnames(function):
|
|
||||||
argnames = py.std.inspect.getargs(py.code.getrawcode(function))[0]
|
|
||||||
startindex = py.std.inspect.ismethod(function) and 1 or 0
|
|
||||||
defaults = getattr(function, 'func_defaults',
|
|
||||||
getattr(function, '__defaults__', None)) or ()
|
|
||||||
numdefaults = len(defaults)
|
|
||||||
if numdefaults:
|
|
||||||
return argnames[startindex:-numdefaults]
|
|
||||||
return argnames[startindex:]
|
|
||||||
|
|
||||||
def fillfuncargs(function):
|
|
||||||
""" fill missing funcargs. """
|
|
||||||
request = FuncargRequest(pyfuncitem=function)
|
|
||||||
request._fillfuncargs()
|
|
||||||
|
|
||||||
def getplugins(node, withpy=False): # might by any node
|
|
||||||
plugins = node.config._getmatchingplugins(node.fspath)
|
|
||||||
if withpy:
|
|
||||||
mod = node.getparent(py.test.collect.Module)
|
|
||||||
if mod is not None:
|
|
||||||
plugins.append(mod.obj)
|
|
||||||
inst = node.getparent(py.test.collect.Instance)
|
|
||||||
if inst is not None:
|
|
||||||
plugins.append(inst.obj)
|
|
||||||
return plugins
|
|
||||||
|
|
||||||
_notexists = object()
|
|
||||||
class CallSpec:
|
|
||||||
def __init__(self, funcargs, id, param):
|
|
||||||
self.funcargs = funcargs
|
|
||||||
self.id = id
|
|
||||||
if param is not _notexists:
|
|
||||||
self.param = param
|
|
||||||
def __repr__(self):
|
|
||||||
return "<CallSpec id=%r param=%r funcargs=%r>" %(
|
|
||||||
self.id, getattr(self, 'param', '?'), self.funcargs)
|
|
||||||
|
|
||||||
class Metafunc:
|
|
||||||
def __init__(self, function, config=None, cls=None, module=None):
|
|
||||||
self.config = config
|
|
||||||
self.module = module
|
|
||||||
self.function = function
|
|
||||||
self.funcargnames = getfuncargnames(function)
|
|
||||||
self.cls = cls
|
|
||||||
self.module = module
|
|
||||||
self._calls = []
|
|
||||||
self._ids = py.builtin.set()
|
|
||||||
|
|
||||||
def addcall(self, funcargs=None, id=_notexists, param=_notexists):
|
|
||||||
assert funcargs is None or isinstance(funcargs, dict)
|
|
||||||
if id is None:
|
|
||||||
raise ValueError("id=None not allowed")
|
|
||||||
if id is _notexists:
|
|
||||||
id = len(self._calls)
|
|
||||||
id = str(id)
|
|
||||||
if id in self._ids:
|
|
||||||
raise ValueError("duplicate id %r" % id)
|
|
||||||
self._ids.add(id)
|
|
||||||
self._calls.append(CallSpec(funcargs, id, param))
|
|
||||||
|
|
||||||
class FuncargRequest:
|
|
||||||
_argprefix = "pytest_funcarg__"
|
|
||||||
_argname = None
|
|
||||||
|
|
||||||
class LookupError(LookupError):
|
|
||||||
""" error on performing funcarg request. """
|
|
||||||
|
|
||||||
def __init__(self, pyfuncitem):
|
|
||||||
self._pyfuncitem = pyfuncitem
|
|
||||||
self.function = pyfuncitem.obj
|
|
||||||
self.module = pyfuncitem.getparent(py.test.collect.Module).obj
|
|
||||||
clscol = pyfuncitem.getparent(py.test.collect.Class)
|
|
||||||
self.cls = clscol and clscol.obj or None
|
|
||||||
self.instance = py.builtin._getimself(self.function)
|
|
||||||
self.config = pyfuncitem.config
|
|
||||||
self.fspath = pyfuncitem.fspath
|
|
||||||
if hasattr(pyfuncitem, '_requestparam'):
|
|
||||||
self.param = pyfuncitem._requestparam
|
|
||||||
self._plugins = getplugins(pyfuncitem, withpy=True)
|
|
||||||
self._funcargs = self._pyfuncitem.funcargs.copy()
|
|
||||||
self._name2factory = {}
|
|
||||||
self._currentarg = None
|
|
||||||
|
|
||||||
def _fillfuncargs(self):
|
|
||||||
argnames = getfuncargnames(self.function)
|
|
||||||
if argnames:
|
|
||||||
assert not getattr(self._pyfuncitem, '_args', None), (
|
|
||||||
"yielded functions cannot have funcargs")
|
|
||||||
for argname in argnames:
|
|
||||||
if argname not in self._pyfuncitem.funcargs:
|
|
||||||
self._pyfuncitem.funcargs[argname] = self.getfuncargvalue(argname)
|
|
||||||
|
|
||||||
|
|
||||||
def applymarker(self, marker):
|
|
||||||
""" apply a marker to a test function invocation.
|
|
||||||
|
|
||||||
The 'marker' must be created with py.test.mark.* XYZ.
|
|
||||||
"""
|
|
||||||
if not isinstance(marker, py.test.mark.XYZ.__class__):
|
|
||||||
raise ValueError("%r is not a py.test.mark.* object")
|
|
||||||
self._pyfuncitem.keywords[marker.markname] = marker
|
|
||||||
|
|
||||||
def cached_setup(self, setup, teardown=None, scope="module", extrakey=None):
|
|
||||||
""" cache and return result of calling setup().
|
|
||||||
|
|
||||||
The requested argument name, the scope and the ``extrakey``
|
|
||||||
determine the cache key. The scope also determines when
|
|
||||||
teardown(result) will be called. valid scopes are:
|
|
||||||
scope == 'function': when the single test function run finishes.
|
|
||||||
scope == 'module': when tests in a different module are run
|
|
||||||
scope == 'session': when tests of the session have run.
|
|
||||||
"""
|
|
||||||
if not hasattr(self.config, '_setupcache'):
|
|
||||||
self.config._setupcache = {} # XXX weakref?
|
|
||||||
cachekey = (self._currentarg, self._getscopeitem(scope), extrakey)
|
|
||||||
cache = self.config._setupcache
|
|
||||||
try:
|
|
||||||
val = cache[cachekey]
|
|
||||||
except KeyError:
|
|
||||||
val = setup()
|
|
||||||
cache[cachekey] = val
|
|
||||||
if teardown is not None:
|
|
||||||
def finalizer():
|
|
||||||
del cache[cachekey]
|
|
||||||
teardown(val)
|
|
||||||
self._addfinalizer(finalizer, scope=scope)
|
|
||||||
return val
|
|
||||||
|
|
||||||
def getfuncargvalue(self, argname):
|
|
||||||
try:
|
|
||||||
return self._funcargs[argname]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
if argname not in self._name2factory:
|
|
||||||
self._name2factory[argname] = self.config.pluginmanager.listattr(
|
|
||||||
plugins=self._plugins,
|
|
||||||
attrname=self._argprefix + str(argname)
|
|
||||||
)
|
|
||||||
#else: we are called recursively
|
|
||||||
if not self._name2factory[argname]:
|
|
||||||
self._raiselookupfailed(argname)
|
|
||||||
funcargfactory = self._name2factory[argname].pop()
|
|
||||||
oldarg = self._currentarg
|
|
||||||
self._currentarg = argname
|
|
||||||
try:
|
|
||||||
self._funcargs[argname] = res = funcargfactory(request=self)
|
|
||||||
finally:
|
|
||||||
self._currentarg = oldarg
|
|
||||||
return res
|
|
||||||
|
|
||||||
def _getscopeitem(self, scope):
|
|
||||||
if scope == "function":
|
|
||||||
return self._pyfuncitem
|
|
||||||
elif scope == "module":
|
|
||||||
return self._pyfuncitem.getparent(py.test.collect.Module)
|
|
||||||
elif scope == "session":
|
|
||||||
return None
|
|
||||||
raise ValueError("unknown finalization scope %r" %(scope,))
|
|
||||||
|
|
||||||
def _addfinalizer(self, finalizer, scope):
|
|
||||||
colitem = self._getscopeitem(scope)
|
|
||||||
self.config._setupstate.addfinalizer(
|
|
||||||
finalizer=finalizer, colitem=colitem)
|
|
||||||
|
|
||||||
def addfinalizer(self, finalizer):
|
|
||||||
""" call the given finalizer after test function finished execution. """
|
|
||||||
self._addfinalizer(finalizer, scope="function")
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "<FuncargRequest for %r>" %(self._pyfuncitem)
|
|
||||||
|
|
||||||
def _raiselookupfailed(self, argname):
|
|
||||||
available = []
|
|
||||||
for plugin in self._plugins:
|
|
||||||
for name in vars(plugin):
|
|
||||||
if name.startswith(self._argprefix):
|
|
||||||
name = name[len(self._argprefix):]
|
|
||||||
if name not in available:
|
|
||||||
available.append(name)
|
|
||||||
fspath, lineno, msg = self._pyfuncitem.reportinfo()
|
|
||||||
msg = "LookupError: no factory found for function argument %r" % (argname,)
|
|
||||||
msg += "\n available funcargs: %s" %(", ".join(available),)
|
|
||||||
msg += "\n use 'py.test --funcargs [testpath]' for help on them."
|
|
||||||
raise self.LookupError(msg)
|
|
|
@ -6,9 +6,9 @@ import inspect
|
||||||
from py._plugin import hookspec
|
from py._plugin import hookspec
|
||||||
|
|
||||||
default_plugins = (
|
default_plugins = (
|
||||||
"default runner pdb capture mark terminal skipping tmpdir monkeypatch "
|
"default terminal python runner pdb capture mark skipping tmpdir monkeypatch "
|
||||||
"recwarn pastebin unittest helpconfig nose assertion genscript "
|
"recwarn pastebin unittest helpconfig nose assertion genscript "
|
||||||
"junitxml doctest").split()
|
"junitxml doctest keyword").split()
|
||||||
|
|
||||||
def check_old_use(mod, modname):
|
def check_old_use(mod, modname):
|
||||||
clsname = modname[len('pytest_'):].capitalize() + "Plugin"
|
clsname = modname[len('pytest_'):].capitalize() + "Plugin"
|
||||||
|
@ -32,7 +32,7 @@ class PluginManager(object):
|
||||||
name = id(plugin)
|
name = id(plugin)
|
||||||
return name
|
return name
|
||||||
|
|
||||||
def register(self, plugin, name=None):
|
def register(self, plugin, name=None, prepend=False):
|
||||||
assert not self.isregistered(plugin), plugin
|
assert not self.isregistered(plugin), plugin
|
||||||
assert not self.registry.isregistered(plugin), plugin
|
assert not self.registry.isregistered(plugin), plugin
|
||||||
name = self._getpluginname(plugin, name)
|
name = self._getpluginname(plugin, name)
|
||||||
|
@ -41,7 +41,7 @@ class PluginManager(object):
|
||||||
self._name2plugin[name] = plugin
|
self._name2plugin[name] = plugin
|
||||||
self.call_plugin(plugin, "pytest_addhooks", {'pluginmanager': self})
|
self.call_plugin(plugin, "pytest_addhooks", {'pluginmanager': self})
|
||||||
self.hook.pytest_plugin_registered(manager=self, plugin=plugin)
|
self.hook.pytest_plugin_registered(manager=self, plugin=plugin)
|
||||||
self.registry.register(plugin)
|
self.registry.register(plugin, prepend=prepend)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def unregister(self, plugin):
|
def unregister(self, plugin):
|
||||||
|
@ -259,6 +259,8 @@ class MultiCall:
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
def varnames(func):
|
def varnames(func):
|
||||||
|
if not inspect.isfunction(func) and not inspect.ismethod(func):
|
||||||
|
func = getattr(func, '__call__', func)
|
||||||
ismethod = inspect.ismethod(func)
|
ismethod = inspect.ismethod(func)
|
||||||
rawcode = py.code.getrawcode(func)
|
rawcode = py.code.getrawcode(func)
|
||||||
try:
|
try:
|
||||||
|
@ -275,10 +277,13 @@ class Registry:
|
||||||
plugins = []
|
plugins = []
|
||||||
self._plugins = plugins
|
self._plugins = plugins
|
||||||
|
|
||||||
def register(self, plugin):
|
def register(self, plugin, prepend=False):
|
||||||
assert not isinstance(plugin, str)
|
assert not isinstance(plugin, str)
|
||||||
assert not plugin in self._plugins
|
assert not plugin in self._plugins
|
||||||
|
if not prepend:
|
||||||
self._plugins.append(plugin)
|
self._plugins.append(plugin)
|
||||||
|
else:
|
||||||
|
self._plugins.insert(0, plugin)
|
||||||
|
|
||||||
def unregister(self, plugin):
|
def unregister(self, plugin):
|
||||||
self._plugins.remove(plugin)
|
self._plugins.remove(plugin)
|
||||||
|
|
|
@ -6,6 +6,25 @@
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import py
|
import py
|
||||||
|
import os, sys
|
||||||
|
|
||||||
|
#
|
||||||
|
# main entry point
|
||||||
|
#
|
||||||
|
|
||||||
|
def main(args=None):
|
||||||
|
if args is None:
|
||||||
|
args = sys.argv[1:]
|
||||||
|
config = py.test.config
|
||||||
|
config.parse(args)
|
||||||
|
try:
|
||||||
|
exitstatus = config.hook.pytest_cmdline_main(config=config)
|
||||||
|
except config.Error:
|
||||||
|
e = sys.exc_info()[1]
|
||||||
|
sys.stderr.write("ERROR: %s\n" %(e.args[0],))
|
||||||
|
exitstatus = EXIT_INTERNALERROR
|
||||||
|
py.test.config = py.test.config.__class__()
|
||||||
|
return exitstatus
|
||||||
|
|
||||||
# exitcodes for the command line
|
# exitcodes for the command line
|
||||||
EXIT_OK = 0
|
EXIT_OK = 0
|
||||||
|
@ -14,10 +33,6 @@ EXIT_INTERRUPTED = 2
|
||||||
EXIT_INTERNALERROR = 3
|
EXIT_INTERNALERROR = 3
|
||||||
EXIT_NOHOSTS = 4
|
EXIT_NOHOSTS = 4
|
||||||
|
|
||||||
# imports used for genitems()
|
|
||||||
Item = py.test.collect.Item
|
|
||||||
Collector = py.test.collect.Collector
|
|
||||||
|
|
||||||
class Session(object):
|
class Session(object):
|
||||||
nodeid = ""
|
nodeid = ""
|
||||||
class Interrupted(KeyboardInterrupt):
|
class Interrupted(KeyboardInterrupt):
|
||||||
|
@ -26,67 +41,10 @@ class Session(object):
|
||||||
|
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
self.config = config
|
self.config = config
|
||||||
self.pluginmanager = config.pluginmanager # shortcut
|
self.config.pluginmanager.register(self, name="session", prepend=True)
|
||||||
self.pluginmanager.register(self)
|
|
||||||
self._testsfailed = 0
|
self._testsfailed = 0
|
||||||
self._nomatch = False
|
|
||||||
self.shouldstop = False
|
self.shouldstop = False
|
||||||
|
self.collection = Collection(config) # XXX move elswehre
|
||||||
def genitems(self, colitems, keywordexpr=None):
|
|
||||||
""" yield Items from iterating over the given colitems. """
|
|
||||||
if colitems:
|
|
||||||
colitems = list(colitems)
|
|
||||||
while colitems:
|
|
||||||
next = colitems.pop(0)
|
|
||||||
if isinstance(next, (tuple, list)):
|
|
||||||
colitems[:] = list(next) + colitems
|
|
||||||
continue
|
|
||||||
assert self.pluginmanager is next.config.pluginmanager
|
|
||||||
if isinstance(next, Item):
|
|
||||||
remaining = self.filteritems([next])
|
|
||||||
if remaining:
|
|
||||||
self.config.hook.pytest_itemstart(item=next)
|
|
||||||
yield next
|
|
||||||
else:
|
|
||||||
assert isinstance(next, Collector)
|
|
||||||
self.config.hook.pytest_collectstart(collector=next)
|
|
||||||
rep = self.config.hook.pytest_make_collect_report(collector=next)
|
|
||||||
if rep.passed:
|
|
||||||
for x in self.genitems(rep.result, keywordexpr):
|
|
||||||
yield x
|
|
||||||
self.config.hook.pytest_collectreport(report=rep)
|
|
||||||
if self.shouldstop:
|
|
||||||
raise self.Interrupted(self.shouldstop)
|
|
||||||
|
|
||||||
def filteritems(self, colitems):
|
|
||||||
""" return items to process (some may be deselected)"""
|
|
||||||
keywordexpr = self.config.option.keyword
|
|
||||||
if not keywordexpr or self._nomatch:
|
|
||||||
return colitems
|
|
||||||
if keywordexpr[-1] == ":":
|
|
||||||
keywordexpr = keywordexpr[:-1]
|
|
||||||
remaining = []
|
|
||||||
deselected = []
|
|
||||||
for colitem in colitems:
|
|
||||||
if isinstance(colitem, Item):
|
|
||||||
if colitem._skipbykeyword(keywordexpr):
|
|
||||||
deselected.append(colitem)
|
|
||||||
continue
|
|
||||||
remaining.append(colitem)
|
|
||||||
if deselected:
|
|
||||||
self.config.hook.pytest_deselected(items=deselected)
|
|
||||||
if self.config.option.keyword.endswith(":"):
|
|
||||||
self._nomatch = True
|
|
||||||
return remaining
|
|
||||||
|
|
||||||
def collect(self, colitems):
|
|
||||||
keyword = self.config.option.keyword
|
|
||||||
for x in self.genitems(colitems, keyword):
|
|
||||||
yield x
|
|
||||||
|
|
||||||
def sessionstarts(self):
|
|
||||||
""" setup any neccessary resources ahead of the test run. """
|
|
||||||
self.config.hook.pytest_sessionstart(session=self)
|
|
||||||
|
|
||||||
def pytest_runtest_logreport(self, report):
|
def pytest_runtest_logreport(self, report):
|
||||||
if report.failed:
|
if report.failed:
|
||||||
|
@ -95,41 +53,195 @@ class Session(object):
|
||||||
if maxfail and self._testsfailed >= maxfail:
|
if maxfail and self._testsfailed >= maxfail:
|
||||||
self.shouldstop = "stopping after %d failures" % (
|
self.shouldstop = "stopping after %d failures" % (
|
||||||
self._testsfailed)
|
self._testsfailed)
|
||||||
|
self.collection.shouldstop = self.shouldstop
|
||||||
pytest_collectreport = pytest_runtest_logreport
|
pytest_collectreport = pytest_runtest_logreport
|
||||||
|
|
||||||
def sessionfinishes(self, exitstatus):
|
def main(self):
|
||||||
""" teardown any resources after a test run. """
|
|
||||||
self.config.hook.pytest_sessionfinish(
|
|
||||||
session=self,
|
|
||||||
exitstatus=exitstatus,
|
|
||||||
)
|
|
||||||
|
|
||||||
def main(self, colitems):
|
|
||||||
""" main loop for running tests. """
|
""" main loop for running tests. """
|
||||||
self.shouldstop = False
|
self.shouldstop = False
|
||||||
self.sessionstarts()
|
self.exitstatus = EXIT_OK
|
||||||
exitstatus = EXIT_OK
|
config = self.config
|
||||||
try:
|
try:
|
||||||
self._mainloop(colitems)
|
config.pluginmanager.do_configure(config)
|
||||||
if self._testsfailed:
|
config.hook.pytest_sessionstart(session=self)
|
||||||
exitstatus = EXIT_TESTSFAILED
|
config.hook.pytest_perform_collection(session=self)
|
||||||
self.sessionfinishes(exitstatus=exitstatus)
|
config.hook.pytest_runtest_mainloop(session=self)
|
||||||
|
except self.config.Error:
|
||||||
|
raise
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
excinfo = py.code.ExceptionInfo()
|
excinfo = py.code.ExceptionInfo()
|
||||||
self.config.hook.pytest_keyboard_interrupt(excinfo=excinfo)
|
self.config.hook.pytest_keyboard_interrupt(excinfo=excinfo)
|
||||||
exitstatus = EXIT_INTERRUPTED
|
self.exitstatus = EXIT_INTERRUPTED
|
||||||
except:
|
except:
|
||||||
excinfo = py.code.ExceptionInfo()
|
excinfo = py.code.ExceptionInfo()
|
||||||
self.config.pluginmanager.notify_exception(excinfo)
|
self.config.pluginmanager.notify_exception(excinfo)
|
||||||
exitstatus = EXIT_INTERNALERROR
|
self.exitstatus = EXIT_INTERNALERROR
|
||||||
if exitstatus in (EXIT_INTERNALERROR, EXIT_INTERRUPTED):
|
if excinfo.errisinstance(SystemExit):
|
||||||
self.sessionfinishes(exitstatus=exitstatus)
|
sys.stderr.write("mainloop: caught Spurious SystemExit!\n")
|
||||||
return exitstatus
|
|
||||||
|
if not self.exitstatus and self._testsfailed:
|
||||||
|
self.exitstatus = EXIT_TESTSFAILED
|
||||||
|
self.config.hook.pytest_sessionfinish(
|
||||||
|
session=self, exitstatus=self.exitstatus,
|
||||||
|
)
|
||||||
|
config.pluginmanager.do_unconfigure(config)
|
||||||
|
return self.exitstatus
|
||||||
|
|
||||||
|
class Collection:
|
||||||
|
def __init__(self, config):
|
||||||
|
self.config = config
|
||||||
|
self.topdir = gettopdir(self.config.args)
|
||||||
|
self._argfspaths = [py.path.local(decodearg(x)[0])
|
||||||
|
for x in self.config.args]
|
||||||
|
x = py.test.collect.Directory(fspath=self.topdir,
|
||||||
|
config=config, collection=self)
|
||||||
|
self._topcollector = x.consider_dir(self.topdir)
|
||||||
|
self._topcollector.parent = None
|
||||||
|
|
||||||
|
def _normalizearg(self, arg):
|
||||||
|
return "::".join(self._parsearg(arg))
|
||||||
|
|
||||||
|
def _parsearg(self, arg, base=None):
|
||||||
|
""" return normalized name list for a command line specified id
|
||||||
|
which might be of the form x/y/z::name1::name2
|
||||||
|
and should result into the form x::y::z::name1::name2
|
||||||
|
"""
|
||||||
|
if base is None:
|
||||||
|
base = py.path.local()
|
||||||
|
parts = str(arg).split("::")
|
||||||
|
path = base.join(parts[0], abs=True)
|
||||||
|
if not path.check():
|
||||||
|
raise self.config.Error("file not found: %s" %(path,))
|
||||||
|
topdir = self.topdir
|
||||||
|
if path != topdir and not path.relto(topdir):
|
||||||
|
raise self.config.Error("path %r is not relative to %r" %
|
||||||
|
(str(path), str(topdir)))
|
||||||
|
topparts = path.relto(topdir).split(path.sep)
|
||||||
|
return topparts + parts[1:]
|
||||||
|
|
||||||
|
def getid(self, node):
|
||||||
|
""" return id for node, relative to topdir. """
|
||||||
|
path = node.fspath
|
||||||
|
chain = [x for x in node.listchain() if x.fspath == path]
|
||||||
|
chain = chain[1:]
|
||||||
|
names = [x.name for x in chain if x.name != "()"]
|
||||||
|
relpath = path.relto(self.topdir)
|
||||||
|
if not relpath:
|
||||||
|
assert path == self.topdir
|
||||||
|
path = ''
|
||||||
|
else:
|
||||||
|
path = relpath
|
||||||
|
if os.sep != "/":
|
||||||
|
path = str(path).replace(os.sep, "/")
|
||||||
|
names.insert(0, path)
|
||||||
|
return "::".join(names)
|
||||||
|
|
||||||
|
def getbyid(self, id):
|
||||||
|
""" return one or more nodes matching the id. """
|
||||||
|
names = [x for x in id.split("::") if x]
|
||||||
|
if names and '/' in names[0]:
|
||||||
|
names[:1] = names[0].split("/")
|
||||||
|
return self._match([self._topcollector], names)
|
||||||
|
|
||||||
|
def _match(self, matching, names):
|
||||||
|
while names:
|
||||||
|
name = names.pop(0)
|
||||||
|
l = []
|
||||||
|
for current in matching:
|
||||||
|
for x in current._memocollect():
|
||||||
|
if x.name == name:
|
||||||
|
l.append(x)
|
||||||
|
elif x.name == "()":
|
||||||
|
names.insert(0, name)
|
||||||
|
l.append(x)
|
||||||
|
break
|
||||||
|
if not l:
|
||||||
|
raise ValueError("no node named %r below %r" %(name, current))
|
||||||
|
matching = l
|
||||||
|
return matching
|
||||||
|
|
||||||
|
def getinitialnodes(self):
|
||||||
|
idlist = [self._normalizearg(arg) for arg in self.config.args]
|
||||||
|
nodes = []
|
||||||
|
for id in idlist:
|
||||||
|
nodes.extend(self.getbyid(id))
|
||||||
|
return nodes
|
||||||
|
|
||||||
|
def perform_collect(self):
|
||||||
|
nodes = []
|
||||||
|
for arg in self.config.args:
|
||||||
|
names = self._parsearg(arg)
|
||||||
|
try:
|
||||||
|
self.genitems([self._topcollector], names, nodes)
|
||||||
|
except NoMatch:
|
||||||
|
raise self.config.Error("can't collect: %s" % (arg,))
|
||||||
|
return nodes
|
||||||
|
|
||||||
|
def genitems(self, matching, names, result):
|
||||||
|
if not matching:
|
||||||
|
assert not names
|
||||||
|
return
|
||||||
|
if names:
|
||||||
|
name = names[0]
|
||||||
|
names = names[1:]
|
||||||
|
else:
|
||||||
|
name = None
|
||||||
|
for node in matching:
|
||||||
|
if isinstance(node, py.test.collect.Item):
|
||||||
|
if name is None:
|
||||||
|
node.ihook.pytest_log_itemcollect(item=node)
|
||||||
|
result.append(node)
|
||||||
|
continue
|
||||||
|
assert isinstance(node, py.test.collect.Collector)
|
||||||
|
node.ihook.pytest_collectstart(collector=node)
|
||||||
|
rep = node.ihook.pytest_make_collect_report(collector=node)
|
||||||
|
#print "matching", rep.result, "against name", name
|
||||||
|
if rep.passed:
|
||||||
|
if not name:
|
||||||
|
self.genitems(rep.result, [], result)
|
||||||
|
else:
|
||||||
|
matched = False
|
||||||
|
for x in rep.result:
|
||||||
|
try:
|
||||||
|
if x.name == name or x.fspath.basename == name:
|
||||||
|
self.genitems([x], names, result)
|
||||||
|
matched = True
|
||||||
|
elif x.name == "()": # XXX special Instance() case
|
||||||
|
self.genitems([x], [name] + names, result)
|
||||||
|
matched = True
|
||||||
|
except NoMatch:
|
||||||
|
pass
|
||||||
|
if not matched:
|
||||||
|
node.ihook.pytest_collectreport(report=rep)
|
||||||
|
raise NoMatch(name)
|
||||||
|
node.ihook.pytest_collectreport(report=rep)
|
||||||
|
x = getattr(self, 'shouldstop', None)
|
||||||
|
if x:
|
||||||
|
raise Session.Interrupted(x)
|
||||||
|
|
||||||
|
class NoMatch(Exception):
|
||||||
|
""" raised if genitems cannot locate a matching names. """
|
||||||
|
|
||||||
|
def gettopdir(args):
|
||||||
|
""" return the top directory for the given paths.
|
||||||
|
if the common base dir resides in a python package
|
||||||
|
parent directory of the root package is returned.
|
||||||
|
"""
|
||||||
|
fsargs = [py.path.local(decodearg(arg)[0]) for arg in args]
|
||||||
|
p = fsargs and fsargs[0] or None
|
||||||
|
for x in fsargs[1:]:
|
||||||
|
p = p.common(x)
|
||||||
|
assert p, "cannot determine common basedir of %s" %(fsargs,)
|
||||||
|
pkgdir = p.pypkgpath()
|
||||||
|
if pkgdir is None:
|
||||||
|
if p.check(file=1):
|
||||||
|
p = p.dirpath()
|
||||||
|
return p
|
||||||
|
else:
|
||||||
|
return pkgdir.dirpath()
|
||||||
|
|
||||||
|
def decodearg(arg):
|
||||||
|
arg = str(arg)
|
||||||
|
return arg.split("::")
|
||||||
|
|
||||||
def _mainloop(self, colitems):
|
|
||||||
for item in self.collect(colitems):
|
|
||||||
if not self.config.option.collectonly:
|
|
||||||
item.config.hook.pytest_runtest_protocol(item=item)
|
|
||||||
if self.shouldstop:
|
|
||||||
raise self.Interrupted(self.shouldstop)
|
|
||||||
|
|
||||||
|
|
10
setup.py
10
setup.py
|
@ -12,8 +12,12 @@ py.test and pylib: rapid testing and development utils
|
||||||
- `py.code`_: dynamic code compile and traceback printing support
|
- `py.code`_: dynamic code compile and traceback printing support
|
||||||
|
|
||||||
Platforms: Linux, Win32, OSX
|
Platforms: Linux, Win32, OSX
|
||||||
|
|
||||||
Interpreters: Python versions 2.4 through to 3.2, Jython 2.5.1 and PyPy
|
Interpreters: Python versions 2.4 through to 3.2, Jython 2.5.1 and PyPy
|
||||||
For questions please check out http://pylib.org/contact.html
|
|
||||||
|
Bugs and issues: http://bitbucket.org/hpk42/py-trunk/issues/
|
||||||
|
|
||||||
|
Mailing lists and more contact points: http://pylib.org/contact.html
|
||||||
|
|
||||||
.. _`py.test`: http://pytest.org
|
.. _`py.test`: http://pytest.org
|
||||||
.. _`py.path`: http://pylib.org/path.html
|
.. _`py.path`: http://pylib.org/path.html
|
||||||
|
@ -26,14 +30,14 @@ def main():
|
||||||
name='py',
|
name='py',
|
||||||
description='py.test and pylib: rapid testing and development utils.',
|
description='py.test and pylib: rapid testing and development utils.',
|
||||||
long_description = long_description,
|
long_description = long_description,
|
||||||
version= '1.3.4a1',
|
version= '1.4.0a1',
|
||||||
url='http://pylib.org',
|
url='http://pylib.org',
|
||||||
license='MIT license',
|
license='MIT license',
|
||||||
platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],
|
platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],
|
||||||
author='holger krekel, Guido Wesdorp, Carl Friedrich Bolz, Armin Rigo, Maciej Fijalkowski & others',
|
author='holger krekel, Guido Wesdorp, Carl Friedrich Bolz, Armin Rigo, Maciej Fijalkowski & others',
|
||||||
author_email='holger at merlinux.eu',
|
author_email='holger at merlinux.eu',
|
||||||
entry_points= make_entry_points(),
|
entry_points= make_entry_points(),
|
||||||
classifiers=['Development Status :: 5 - Production/Stable',
|
classifiers=['Development Status :: 6 - Mature',
|
||||||
'Intended Audience :: Developers',
|
'Intended Audience :: Developers',
|
||||||
'License :: OSI Approved :: MIT License',
|
'License :: OSI Approved :: MIT License',
|
||||||
'Operating System :: POSIX',
|
'Operating System :: POSIX',
|
||||||
|
|
|
@ -72,7 +72,7 @@ class TestGeneralUsage:
|
||||||
result = testdir.runpytest(p1, p2)
|
result = testdir.runpytest(p1, p2)
|
||||||
assert result.ret != 0
|
assert result.ret != 0
|
||||||
result.stderr.fnmatch_lines([
|
result.stderr.fnmatch_lines([
|
||||||
"*ERROR: can't collect: %s" %(p2,)
|
"*ERROR: can't collect:*%s" %(p2.basename,)
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
|
@ -122,7 +122,6 @@ class TestGeneralUsage:
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
@py.test.mark.xfail
|
|
||||||
def test_issue88_initial_file_multinodes(self, testdir):
|
def test_issue88_initial_file_multinodes(self, testdir):
|
||||||
testdir.makeconftest("""
|
testdir.makeconftest("""
|
||||||
import py
|
import py
|
||||||
|
@ -163,3 +162,52 @@ class TestGeneralUsage:
|
||||||
"""))
|
"""))
|
||||||
result = testdir.runpython(p, prepend=False)
|
result = testdir.runpython(p, prepend=False)
|
||||||
assert not result.ret
|
assert not result.ret
|
||||||
|
|
||||||
|
@py.test.mark.xfail(reason="http://bitbucket.org/hpk42/py-trunk/issue/109")
|
||||||
|
def test_sibling_conftest_issue109(self, testdir):
|
||||||
|
"""
|
||||||
|
This test is to make sure that the conftest.py of sibling directories is not loaded
|
||||||
|
if py.test is run for/in one of the siblings directory and those sibling directories
|
||||||
|
are not packaged together with an __init__.py. See bitbucket issue #109.
|
||||||
|
"""
|
||||||
|
for dirname in ['a', 'b']:
|
||||||
|
testdir.tmpdir.ensure(dirname, dir=True)
|
||||||
|
testdir.tmpdir.ensure(dirname, '__init__.py')
|
||||||
|
|
||||||
|
# To create the conftest.py I would like to use testdir.make*-methods
|
||||||
|
# but as far as I have seen they can only create files in testdir.tempdir
|
||||||
|
# Maybe there is a way to explicitly specifiy the directory on which those
|
||||||
|
# methods work or a completely better way to do that?
|
||||||
|
backupTmpDir = testdir.tmpdir
|
||||||
|
testdir.tmpdir = testdir.tmpdir.join(dirname)
|
||||||
|
testdir.makeconftest("""
|
||||||
|
_DIR_NAME = '%s'
|
||||||
|
def pytest_configure(config):
|
||||||
|
if config.args and config.args[0] != _DIR_NAME:
|
||||||
|
raise Exception("py.test run for '" + config.args[0] + "', but '" + _DIR_NAME + "/conftest.py' loaded.")
|
||||||
|
""" % dirname)
|
||||||
|
testdir.tmpdir = backupTmpDir
|
||||||
|
|
||||||
|
for dirname, other_dirname in [('a', 'b'), ('b', 'a')]:
|
||||||
|
result = testdir.runpytest(dirname)
|
||||||
|
assert result.ret == 0, "test_sibling_conftest: py.test run for '%s', but '%s/conftest.py' loaded." % (dirname, other_dirname)
|
||||||
|
|
||||||
|
def test_multiple_items_per_collector_byid(self, testdir):
|
||||||
|
c = testdir.makeconftest("""
|
||||||
|
import py
|
||||||
|
class MyItem(py.test.collect.Item):
|
||||||
|
def runtest(self):
|
||||||
|
pass
|
||||||
|
class MyCollector(py.test.collect.File):
|
||||||
|
def collect(self):
|
||||||
|
return [MyItem(name="xyz", parent=self)]
|
||||||
|
def pytest_collect_file(path, parent):
|
||||||
|
if path.basename.startswith("conftest"):
|
||||||
|
return MyCollector(path, parent)
|
||||||
|
""")
|
||||||
|
result = testdir.runpytest(c.basename+"::"+"xyz")
|
||||||
|
assert result.ret == 0
|
||||||
|
result.stdout.fnmatch_lines([
|
||||||
|
"*1 pass*",
|
||||||
|
])
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ def test_cmdmain(name, pytestconfig):
|
||||||
main = getattr(py.cmdline, name)
|
main = getattr(py.cmdline, name)
|
||||||
assert py.builtin.callable(main)
|
assert py.builtin.callable(main)
|
||||||
assert name[:2] == "py"
|
assert name[:2] == "py"
|
||||||
if pytestconfig.getvalue("toolsonpath"):
|
if not pytestconfig.getvalue("notoolsonpath"):
|
||||||
scriptname = "py." + name[2:]
|
scriptname = "py." + name[2:]
|
||||||
assert py.path.local.sysfind(scriptname), scriptname
|
assert py.path.local.sysfind(scriptname), scriptname
|
||||||
|
|
||||||
|
|
|
@ -217,3 +217,13 @@ def test_underscore_api():
|
||||||
py.code._AssertionError
|
py.code._AssertionError
|
||||||
py.code._reinterpret_old # used by pypy
|
py.code._reinterpret_old # used by pypy
|
||||||
py.code._reinterpret
|
py.code._reinterpret
|
||||||
|
|
||||||
|
@py.test.mark.skipif("sys.version_info < (2,6)")
|
||||||
|
def test_assert_customizable_reprcompare(monkeypatch):
|
||||||
|
monkeypatch.setattr(py.code, '_reprcompare', lambda *args: 'hello')
|
||||||
|
try:
|
||||||
|
assert 3 == 4
|
||||||
|
except AssertionError:
|
||||||
|
e = exvalue()
|
||||||
|
s = str(e)
|
||||||
|
assert "hello" in s
|
||||||
|
|
|
@ -700,3 +700,16 @@ raise ValueError()
|
||||||
repr = excinfo.getrepr(**reproptions)
|
repr = excinfo.getrepr(**reproptions)
|
||||||
repr.toterminal(tw)
|
repr.toterminal(tw)
|
||||||
assert tw.stringio.getvalue()
|
assert tw.stringio.getvalue()
|
||||||
|
|
||||||
|
|
||||||
|
def test_native_style(self):
|
||||||
|
excinfo = self.excinfo_from_exec("""
|
||||||
|
assert 0
|
||||||
|
""")
|
||||||
|
repr = excinfo.getrepr(style='native')
|
||||||
|
assert repr.startswith('Traceback (most recent call last):\n File')
|
||||||
|
assert repr.endswith('\nAssertionError: assert 0\n')
|
||||||
|
assert 'exec (source.compile())' in repr
|
||||||
|
# python 2.4 fails to get the source line for the assert
|
||||||
|
if py.std.sys.version_info >= (2, 5):
|
||||||
|
assert repr.count('assert 0') == 2
|
||||||
|
|
|
@ -348,7 +348,7 @@ def test_deindent():
|
||||||
lines = deindent(source.splitlines())
|
lines = deindent(source.splitlines())
|
||||||
assert lines == ['', 'def f():', ' def g():', ' pass', ' ']
|
assert lines == ['', 'def f():', ' def g():', ' pass', ' ']
|
||||||
|
|
||||||
@py.test.mark.xfail("sys.version_info[:2] != (2,7)")
|
@py.test.mark.xfail("sys.version_info[:2] != (2,7) and sys.version_info[:2]<(3,2)")
|
||||||
def test_source_of_class_at_eof_without_newline(tmpdir):
|
def test_source_of_class_at_eof_without_newline(tmpdir):
|
||||||
# this test fails because the implicit inspect.getsource(A) below
|
# this test fails because the implicit inspect.getsource(A) below
|
||||||
# does not return the "x = 1" last line.
|
# does not return the "x = 1" last line.
|
||||||
|
|
|
@ -306,9 +306,11 @@ class TestImport:
|
||||||
|
|
||||||
def test_pyimport_dir(self, tmpdir):
|
def test_pyimport_dir(self, tmpdir):
|
||||||
p = tmpdir.join("hello_123")
|
p = tmpdir.join("hello_123")
|
||||||
p.ensure("__init__.py")
|
p_init = p.ensure("__init__.py")
|
||||||
m = p.pyimport()
|
m = p.pyimport()
|
||||||
assert m.__name__ == "hello_123"
|
assert m.__name__ == "hello_123"
|
||||||
|
m = p_init.pyimport()
|
||||||
|
assert m.__name__ == "hello_123"
|
||||||
|
|
||||||
def test_pyimport_execfile_different_name(self, path1):
|
def test_pyimport_execfile_different_name(self, path1):
|
||||||
obj = path1.join('execfile.py').pyimport(modname="0x.y.z")
|
obj = path1.join('execfile.py').pyimport(modname="0x.y.z")
|
||||||
|
|
|
@ -1,3 +1,107 @@
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import py
|
||||||
|
import py._plugin.pytest_assertion as plugin
|
||||||
|
|
||||||
|
needsnewassert = py.test.mark.skipif("sys.version_info < (2,6)")
|
||||||
|
|
||||||
|
def interpret(expr):
|
||||||
|
return py.code._reinterpret(expr, py.code.Frame(sys._getframe(1)))
|
||||||
|
|
||||||
|
class TestBinReprIntegration:
|
||||||
|
pytestmark = needsnewassert
|
||||||
|
|
||||||
|
def pytest_funcarg__hook(self, request):
|
||||||
|
class MockHook(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.called = False
|
||||||
|
self.args = tuple()
|
||||||
|
self.kwargs = dict()
|
||||||
|
|
||||||
|
def __call__(self, op, left, right):
|
||||||
|
self.called = True
|
||||||
|
self.op = op
|
||||||
|
self.left = left
|
||||||
|
self.right = right
|
||||||
|
mockhook = MockHook()
|
||||||
|
monkeypatch = request.getfuncargvalue("monkeypatch")
|
||||||
|
monkeypatch.setattr(py.code, '_reprcompare', mockhook)
|
||||||
|
return mockhook
|
||||||
|
|
||||||
|
def test_pytest_assertrepr_compare_called(self, hook):
|
||||||
|
interpret('assert 0 == 1')
|
||||||
|
assert hook.called
|
||||||
|
|
||||||
|
|
||||||
|
def test_pytest_assertrepr_compare_args(self, hook):
|
||||||
|
interpret('assert [0, 1] == [0, 2]')
|
||||||
|
assert hook.op == '=='
|
||||||
|
assert hook.left == [0, 1]
|
||||||
|
assert hook.right == [0, 2]
|
||||||
|
|
||||||
|
def test_configure_unconfigure(self, testdir, hook):
|
||||||
|
assert hook == py.code._reprcompare
|
||||||
|
config = testdir.parseconfig()
|
||||||
|
plugin.pytest_configure(config)
|
||||||
|
assert hook != py.code._reprcompare
|
||||||
|
plugin.pytest_unconfigure(config)
|
||||||
|
assert hook == py.code._reprcompare
|
||||||
|
|
||||||
|
class TestAssert_reprcompare:
|
||||||
|
def test_different_types(self):
|
||||||
|
assert plugin.pytest_assertrepr_compare('==', [0, 1], 'foo') is None
|
||||||
|
|
||||||
|
def test_summary(self):
|
||||||
|
summary = plugin.pytest_assertrepr_compare('==', [0, 1], [0, 2])[0]
|
||||||
|
assert len(summary) < 65
|
||||||
|
|
||||||
|
def test_text_diff(self):
|
||||||
|
diff = plugin.pytest_assertrepr_compare('==', 'spam', 'eggs')[1:]
|
||||||
|
assert '- spam' in diff
|
||||||
|
assert '+ eggs' in diff
|
||||||
|
|
||||||
|
def test_multiline_text_diff(self):
|
||||||
|
left = 'foo\nspam\nbar'
|
||||||
|
right = 'foo\neggs\nbar'
|
||||||
|
diff = plugin.pytest_assertrepr_compare('==', left, right)
|
||||||
|
assert '- spam' in diff
|
||||||
|
assert '+ eggs' in diff
|
||||||
|
|
||||||
|
def test_list(self):
|
||||||
|
expl = plugin.pytest_assertrepr_compare('==', [0, 1], [0, 2])
|
||||||
|
assert len(expl) > 1
|
||||||
|
|
||||||
|
def test_list_different_lenghts(self):
|
||||||
|
expl = plugin.pytest_assertrepr_compare('==', [0, 1], [0, 1, 2])
|
||||||
|
assert len(expl) > 1
|
||||||
|
expl = plugin.pytest_assertrepr_compare('==', [0, 1, 2], [0, 1])
|
||||||
|
assert len(expl) > 1
|
||||||
|
|
||||||
|
def test_dict(self):
|
||||||
|
expl = plugin.pytest_assertrepr_compare('==', {'a': 0}, {'a': 1})
|
||||||
|
assert len(expl) > 1
|
||||||
|
|
||||||
|
def test_set(self):
|
||||||
|
expl = plugin.pytest_assertrepr_compare('==', set([0, 1]), set([0, 2]))
|
||||||
|
assert len(expl) > 1
|
||||||
|
|
||||||
|
@needsnewassert
|
||||||
|
def test_pytest_assertrepr_compare_integration(testdir):
|
||||||
|
testdir.makepyfile("""
|
||||||
|
def test_hello():
|
||||||
|
x = set(range(100))
|
||||||
|
y = x.copy()
|
||||||
|
y.remove(50)
|
||||||
|
assert x == y
|
||||||
|
""")
|
||||||
|
result = testdir.runpytest()
|
||||||
|
result.stdout.fnmatch_lines([
|
||||||
|
"*def test_hello():*",
|
||||||
|
"*assert x == y*",
|
||||||
|
"*E*Extra items*left*",
|
||||||
|
"*E*50*",
|
||||||
|
])
|
||||||
|
|
||||||
def test_functional(testdir):
|
def test_functional(testdir):
|
||||||
testdir.makepyfile("""
|
testdir.makepyfile("""
|
||||||
def test_hello():
|
def test_hello():
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from py._plugin.pytest_doctest import DoctestModule, DoctestTextfile
|
from py._plugin.pytest_doctest import DoctestModule, DoctestTextfile
|
||||||
|
import py
|
||||||
|
|
||||||
pytest_plugins = ["pytest_doctest"]
|
pytest_plugins = ["pytest_doctest"]
|
||||||
|
|
||||||
|
@ -73,16 +74,16 @@ class TestDoctests:
|
||||||
reprec = testdir.inline_run(p, "--doctest-modules")
|
reprec = testdir.inline_run(p, "--doctest-modules")
|
||||||
reprec.assertoutcome(failed=1)
|
reprec.assertoutcome(failed=1)
|
||||||
|
|
||||||
def test_doctestmodule_external(self, testdir):
|
def test_doctestmodule_external_and_issue116(self, testdir):
|
||||||
p = testdir.makepyfile("""
|
p = testdir.mkpydir("hello")
|
||||||
#
|
p.join("__init__.py").write(py.code.Source("""
|
||||||
def somefunc():
|
def somefunc():
|
||||||
'''
|
'''
|
||||||
>>> i = 0
|
>>> i = 0
|
||||||
>>> i + 1
|
>>> i + 1
|
||||||
2
|
2
|
||||||
'''
|
'''
|
||||||
""")
|
"""))
|
||||||
result = testdir.runpytest(p, "--doctest-modules")
|
result = testdir.runpytest(p, "--doctest-modules")
|
||||||
result.stdout.fnmatch_lines([
|
result.stdout.fnmatch_lines([
|
||||||
'004 *>>> i = 0',
|
'004 *>>> i = 0',
|
||||||
|
|
|
@ -26,6 +26,7 @@ def test_gen(testdir, anypython, standalone):
|
||||||
"*imported from*mypytest"
|
"*imported from*mypytest"
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@py.test.mark.xfail(reason="fix-dist", run=False)
|
||||||
def test_rundist(testdir, pytestconfig, standalone):
|
def test_rundist(testdir, pytestconfig, standalone):
|
||||||
pytestconfig.pluginmanager.skipifmissing("xdist")
|
pytestconfig.pluginmanager.skipifmissing("xdist")
|
||||||
testdir.makepyfile("""
|
testdir.makepyfile("""
|
||||||
|
|
|
@ -8,5 +8,5 @@ def test_functional(testdir):
|
||||||
testdir.runpytest("--hooklog=hook.log")
|
testdir.runpytest("--hooklog=hook.log")
|
||||||
s = testdir.tmpdir.join("hook.log").read()
|
s = testdir.tmpdir.join("hook.log").read()
|
||||||
assert s.find("pytest_sessionstart") != -1
|
assert s.find("pytest_sessionstart") != -1
|
||||||
assert s.find("ItemTestReport") != -1
|
assert s.find("TestReport") != -1
|
||||||
assert s.find("sessionfinish") != -1
|
assert s.find("sessionfinish") != -1
|
||||||
|
|
|
@ -64,7 +64,7 @@ class TestKeywordSelection:
|
||||||
reprec = testdir.inline_run("-s", "-k", keyword, file_test)
|
reprec = testdir.inline_run("-s", "-k", keyword, file_test)
|
||||||
passed, skipped, failed = reprec.listoutcomes()
|
passed, skipped, failed = reprec.listoutcomes()
|
||||||
assert len(failed) == 1
|
assert len(failed) == 1
|
||||||
assert failed[0].item.name == name
|
assert failed[0].nodeid.split("::")[-1] == name
|
||||||
assert len(reprec.getcalls('pytest_deselected')) == 1
|
assert len(reprec.getcalls('pytest_deselected')) == 1
|
||||||
|
|
||||||
for keyword in ['test_one', 'est_on']:
|
for keyword in ['test_one', 'est_on']:
|
||||||
|
@ -92,7 +92,7 @@ class TestKeywordSelection:
|
||||||
py.builtin.print_("keyword", repr(keyword))
|
py.builtin.print_("keyword", repr(keyword))
|
||||||
passed, skipped, failed = reprec.listoutcomes()
|
passed, skipped, failed = reprec.listoutcomes()
|
||||||
assert len(passed) == 1
|
assert len(passed) == 1
|
||||||
assert passed[0].item.name == "test_2"
|
assert passed[0].nodeid.endswith("test_2")
|
||||||
dlist = reprec.getcalls("pytest_deselected")
|
dlist = reprec.getcalls("pytest_deselected")
|
||||||
assert len(dlist) == 1
|
assert len(dlist) == 1
|
||||||
assert dlist[0].items[0].name == 'test_1'
|
assert dlist[0].items[0].name == 'test_1'
|
|
@ -5,6 +5,8 @@ def test_reportrecorder(testdir):
|
||||||
item = testdir.getitem("def test_func(): pass")
|
item = testdir.getitem("def test_func(): pass")
|
||||||
recorder = testdir.getreportrecorder(item.config)
|
recorder = testdir.getreportrecorder(item.config)
|
||||||
assert not recorder.getfailures()
|
assert not recorder.getfailures()
|
||||||
|
|
||||||
|
py.test.xfail("internal reportrecorder tests need refactoring")
|
||||||
class rep:
|
class rep:
|
||||||
excinfo = None
|
excinfo = None
|
||||||
passed = False
|
passed = False
|
||||||
|
|
|
@ -1,5 +1,521 @@
|
||||||
import py, sys
|
import py, sys
|
||||||
from py._test import funcargs
|
from py._plugin import pytest_python as funcargs
|
||||||
|
|
||||||
|
class TestModule:
|
||||||
|
def test_module_file_not_found(self, testdir):
|
||||||
|
tmpdir = testdir.tmpdir
|
||||||
|
fn = tmpdir.join('nada','no')
|
||||||
|
col = py.test.collect.Module(fn, config=testdir.Config())
|
||||||
|
col.config = testdir.parseconfig(tmpdir)
|
||||||
|
py.test.raises(py.error.ENOENT, col.collect)
|
||||||
|
|
||||||
|
def test_failing_import(self, testdir):
|
||||||
|
modcol = testdir.getmodulecol("import alksdjalskdjalkjals")
|
||||||
|
py.test.raises(ImportError, modcol.collect)
|
||||||
|
py.test.raises(ImportError, modcol.collect)
|
||||||
|
py.test.raises(ImportError, modcol.run)
|
||||||
|
|
||||||
|
def test_import_duplicate(self, testdir):
|
||||||
|
a = testdir.mkdir("a")
|
||||||
|
b = testdir.mkdir("b")
|
||||||
|
p = a.ensure("test_whatever.py")
|
||||||
|
p.pyimport()
|
||||||
|
del py.std.sys.modules['test_whatever']
|
||||||
|
b.ensure("test_whatever.py")
|
||||||
|
result = testdir.runpytest()
|
||||||
|
result.stdout.fnmatch_lines([
|
||||||
|
"*import*mismatch*",
|
||||||
|
"*imported*test_whatever*",
|
||||||
|
"*%s*" % a.join("test_whatever.py"),
|
||||||
|
"*not the same*",
|
||||||
|
"*%s*" % b.join("test_whatever.py"),
|
||||||
|
"*HINT*",
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_syntax_error_in_module(self, testdir):
|
||||||
|
modcol = testdir.getmodulecol("this is a syntax error")
|
||||||
|
py.test.raises(modcol.CollectError, modcol.collect)
|
||||||
|
py.test.raises(modcol.CollectError, modcol.collect)
|
||||||
|
py.test.raises(modcol.CollectError, modcol.run)
|
||||||
|
|
||||||
|
def test_module_considers_pluginmanager_at_import(self, testdir):
|
||||||
|
modcol = testdir.getmodulecol("pytest_plugins='xasdlkj',")
|
||||||
|
py.test.raises(ImportError, "modcol.obj")
|
||||||
|
|
||||||
|
class TestClass:
|
||||||
|
def test_class_with_init_not_collected(self, testdir):
|
||||||
|
modcol = testdir.getmodulecol("""
|
||||||
|
class TestClass1:
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
class TestClass2(object):
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
""")
|
||||||
|
l = modcol.collect()
|
||||||
|
assert len(l) == 0
|
||||||
|
|
||||||
|
if py.std.sys.version_info > (3, 0):
|
||||||
|
_func_name_attr = "__name__"
|
||||||
|
else:
|
||||||
|
_func_name_attr = "func_name"
|
||||||
|
|
||||||
|
class TestGenerator:
|
||||||
|
def test_generative_functions(self, testdir):
|
||||||
|
modcol = testdir.getmodulecol("""
|
||||||
|
def func1(arg, arg2):
|
||||||
|
assert arg == arg2
|
||||||
|
|
||||||
|
def test_gen():
|
||||||
|
yield func1, 17, 3*5
|
||||||
|
yield func1, 42, 6*7
|
||||||
|
""")
|
||||||
|
colitems = modcol.collect()
|
||||||
|
assert len(colitems) == 1
|
||||||
|
gencol = colitems[0]
|
||||||
|
assert isinstance(gencol, py.test.collect.Generator)
|
||||||
|
gencolitems = gencol.collect()
|
||||||
|
assert len(gencolitems) == 2
|
||||||
|
assert isinstance(gencolitems[0], py.test.collect.Function)
|
||||||
|
assert isinstance(gencolitems[1], py.test.collect.Function)
|
||||||
|
assert gencolitems[0].name == '[0]'
|
||||||
|
assert getattr(gencolitems[0].obj, _func_name_attr) == 'func1'
|
||||||
|
|
||||||
|
def test_generative_methods(self, testdir):
|
||||||
|
modcol = testdir.getmodulecol("""
|
||||||
|
def func1(arg, arg2):
|
||||||
|
assert arg == arg2
|
||||||
|
class TestGenMethods:
|
||||||
|
def test_gen(self):
|
||||||
|
yield func1, 17, 3*5
|
||||||
|
yield func1, 42, 6*7
|
||||||
|
""")
|
||||||
|
gencol = modcol.collect()[0].collect()[0].collect()[0]
|
||||||
|
assert isinstance(gencol, py.test.collect.Generator)
|
||||||
|
gencolitems = gencol.collect()
|
||||||
|
assert len(gencolitems) == 2
|
||||||
|
assert isinstance(gencolitems[0], py.test.collect.Function)
|
||||||
|
assert isinstance(gencolitems[1], py.test.collect.Function)
|
||||||
|
assert gencolitems[0].name == '[0]'
|
||||||
|
assert getattr(gencolitems[0].obj, _func_name_attr) == 'func1'
|
||||||
|
|
||||||
|
def test_generative_functions_with_explicit_names(self, testdir):
|
||||||
|
modcol = testdir.getmodulecol("""
|
||||||
|
def func1(arg, arg2):
|
||||||
|
assert arg == arg2
|
||||||
|
|
||||||
|
def test_gen():
|
||||||
|
yield "seventeen", func1, 17, 3*5
|
||||||
|
yield "fortytwo", func1, 42, 6*7
|
||||||
|
""")
|
||||||
|
colitems = modcol.collect()
|
||||||
|
assert len(colitems) == 1
|
||||||
|
gencol = colitems[0]
|
||||||
|
assert isinstance(gencol, py.test.collect.Generator)
|
||||||
|
gencolitems = gencol.collect()
|
||||||
|
assert len(gencolitems) == 2
|
||||||
|
assert isinstance(gencolitems[0], py.test.collect.Function)
|
||||||
|
assert isinstance(gencolitems[1], py.test.collect.Function)
|
||||||
|
assert gencolitems[0].name == "['seventeen']"
|
||||||
|
assert getattr(gencolitems[0].obj, _func_name_attr) == 'func1'
|
||||||
|
assert gencolitems[1].name == "['fortytwo']"
|
||||||
|
assert getattr(gencolitems[1].obj, _func_name_attr) == 'func1'
|
||||||
|
|
||||||
|
def test_generative_functions_unique_explicit_names(self, testdir):
|
||||||
|
# generative
|
||||||
|
modcol = testdir.getmodulecol("""
|
||||||
|
def func(): pass
|
||||||
|
def test_gen():
|
||||||
|
yield "name", func
|
||||||
|
yield "name", func
|
||||||
|
""")
|
||||||
|
colitems = modcol.collect()
|
||||||
|
assert len(colitems) == 1
|
||||||
|
gencol = colitems[0]
|
||||||
|
assert isinstance(gencol, py.test.collect.Generator)
|
||||||
|
py.test.raises(ValueError, "gencol.collect()")
|
||||||
|
|
||||||
|
def test_generative_methods_with_explicit_names(self, testdir):
|
||||||
|
modcol = testdir.getmodulecol("""
|
||||||
|
def func1(arg, arg2):
|
||||||
|
assert arg == arg2
|
||||||
|
class TestGenMethods:
|
||||||
|
def test_gen(self):
|
||||||
|
yield "m1", func1, 17, 3*5
|
||||||
|
yield "m2", func1, 42, 6*7
|
||||||
|
""")
|
||||||
|
gencol = modcol.collect()[0].collect()[0].collect()[0]
|
||||||
|
assert isinstance(gencol, py.test.collect.Generator)
|
||||||
|
gencolitems = gencol.collect()
|
||||||
|
assert len(gencolitems) == 2
|
||||||
|
assert isinstance(gencolitems[0], py.test.collect.Function)
|
||||||
|
assert isinstance(gencolitems[1], py.test.collect.Function)
|
||||||
|
assert gencolitems[0].name == "['m1']"
|
||||||
|
assert getattr(gencolitems[0].obj, _func_name_attr) == 'func1'
|
||||||
|
assert gencolitems[1].name == "['m2']"
|
||||||
|
assert getattr(gencolitems[1].obj, _func_name_attr) == 'func1'
|
||||||
|
|
||||||
|
def test_order_of_execution_generator_same_codeline(self, testdir, tmpdir):
|
||||||
|
o = testdir.makepyfile("""
|
||||||
|
def test_generative_order_of_execution():
|
||||||
|
import py
|
||||||
|
test_list = []
|
||||||
|
expected_list = list(range(6))
|
||||||
|
|
||||||
|
def list_append(item):
|
||||||
|
test_list.append(item)
|
||||||
|
|
||||||
|
def assert_order_of_execution():
|
||||||
|
py.builtin.print_('expected order', expected_list)
|
||||||
|
py.builtin.print_('but got ', test_list)
|
||||||
|
assert test_list == expected_list
|
||||||
|
|
||||||
|
for i in expected_list:
|
||||||
|
yield list_append, i
|
||||||
|
yield assert_order_of_execution
|
||||||
|
""")
|
||||||
|
reprec = testdir.inline_run(o)
|
||||||
|
passed, skipped, failed = reprec.countoutcomes()
|
||||||
|
assert passed == 7
|
||||||
|
assert not skipped and not failed
|
||||||
|
|
||||||
|
def test_order_of_execution_generator_different_codeline(self, testdir):
|
||||||
|
o = testdir.makepyfile("""
|
||||||
|
def test_generative_tests_different_codeline():
|
||||||
|
import py
|
||||||
|
test_list = []
|
||||||
|
expected_list = list(range(3))
|
||||||
|
|
||||||
|
def list_append_2():
|
||||||
|
test_list.append(2)
|
||||||
|
|
||||||
|
def list_append_1():
|
||||||
|
test_list.append(1)
|
||||||
|
|
||||||
|
def list_append_0():
|
||||||
|
test_list.append(0)
|
||||||
|
|
||||||
|
def assert_order_of_execution():
|
||||||
|
py.builtin.print_('expected order', expected_list)
|
||||||
|
py.builtin.print_('but got ', test_list)
|
||||||
|
assert test_list == expected_list
|
||||||
|
|
||||||
|
yield list_append_0
|
||||||
|
yield list_append_1
|
||||||
|
yield list_append_2
|
||||||
|
yield assert_order_of_execution
|
||||||
|
""")
|
||||||
|
reprec = testdir.inline_run(o)
|
||||||
|
passed, skipped, failed = reprec.countoutcomes()
|
||||||
|
assert passed == 4
|
||||||
|
assert not skipped and not failed
|
||||||
|
|
||||||
|
class TestFunction:
|
||||||
|
def test_getmodulecollector(self, testdir):
|
||||||
|
item = testdir.getitem("def test_func(): pass")
|
||||||
|
modcol = item.getparent(py.test.collect.Module)
|
||||||
|
assert isinstance(modcol, py.test.collect.Module)
|
||||||
|
assert hasattr(modcol.obj, 'test_func')
|
||||||
|
|
||||||
|
def test_function_equality(self, testdir, tmpdir):
|
||||||
|
config = testdir.reparseconfig()
|
||||||
|
f1 = py.test.collect.Function(name="name", config=config,
|
||||||
|
args=(1,), callobj=isinstance)
|
||||||
|
f2 = py.test.collect.Function(name="name",config=config,
|
||||||
|
args=(1,), callobj=py.builtin.callable)
|
||||||
|
assert not f1 == f2
|
||||||
|
assert f1 != f2
|
||||||
|
f3 = py.test.collect.Function(name="name", config=config,
|
||||||
|
args=(1,2), callobj=py.builtin.callable)
|
||||||
|
assert not f3 == f2
|
||||||
|
assert f3 != f2
|
||||||
|
|
||||||
|
assert not f3 == f1
|
||||||
|
assert f3 != f1
|
||||||
|
|
||||||
|
f1_b = py.test.collect.Function(name="name", config=config,
|
||||||
|
args=(1,), callobj=isinstance)
|
||||||
|
assert f1 == f1_b
|
||||||
|
assert not f1 != f1_b
|
||||||
|
|
||||||
|
def test_function_equality_with_callspec(self, testdir, tmpdir):
|
||||||
|
config = testdir.reparseconfig()
|
||||||
|
class callspec1:
|
||||||
|
param = 1
|
||||||
|
funcargs = {}
|
||||||
|
id = "hello"
|
||||||
|
class callspec2:
|
||||||
|
param = 1
|
||||||
|
funcargs = {}
|
||||||
|
id = "world"
|
||||||
|
collection = object()
|
||||||
|
f5 = py.test.collect.Function(name="name", config=config,
|
||||||
|
callspec=callspec1, callobj=isinstance, collection=collection)
|
||||||
|
f5b = py.test.collect.Function(name="name", config=config,
|
||||||
|
callspec=callspec2, callobj=isinstance, collection=collection)
|
||||||
|
assert f5 != f5b
|
||||||
|
assert not (f5 == f5b)
|
||||||
|
|
||||||
|
def test_pyfunc_call(self, testdir):
|
||||||
|
item = testdir.getitem("def test_func(): raise ValueError")
|
||||||
|
config = item.config
|
||||||
|
class MyPlugin1:
|
||||||
|
def pytest_pyfunc_call(self, pyfuncitem):
|
||||||
|
raise ValueError
|
||||||
|
class MyPlugin2:
|
||||||
|
def pytest_pyfunc_call(self, pyfuncitem):
|
||||||
|
return True
|
||||||
|
config.pluginmanager.register(MyPlugin1())
|
||||||
|
config.pluginmanager.register(MyPlugin2())
|
||||||
|
config.hook.pytest_pyfunc_call(pyfuncitem=item)
|
||||||
|
|
||||||
|
class TestSorting:
|
||||||
|
def test_check_equality(self, testdir):
|
||||||
|
modcol = testdir.getmodulecol("""
|
||||||
|
def test_pass(): pass
|
||||||
|
def test_fail(): assert 0
|
||||||
|
""")
|
||||||
|
fn1 = modcol.collect_by_name("test_pass")
|
||||||
|
assert isinstance(fn1, py.test.collect.Function)
|
||||||
|
fn2 = modcol.collect_by_name("test_pass")
|
||||||
|
assert isinstance(fn2, py.test.collect.Function)
|
||||||
|
|
||||||
|
assert fn1 == fn2
|
||||||
|
assert fn1 != modcol
|
||||||
|
if py.std.sys.version_info < (3, 0):
|
||||||
|
assert cmp(fn1, fn2) == 0
|
||||||
|
assert hash(fn1) == hash(fn2)
|
||||||
|
|
||||||
|
fn3 = modcol.collect_by_name("test_fail")
|
||||||
|
assert isinstance(fn3, py.test.collect.Function)
|
||||||
|
assert not (fn1 == fn3)
|
||||||
|
assert fn1 != fn3
|
||||||
|
|
||||||
|
for fn in fn1,fn2,fn3:
|
||||||
|
assert fn != 3
|
||||||
|
assert fn != modcol
|
||||||
|
assert fn != [1,2,3]
|
||||||
|
assert [1,2,3] != fn
|
||||||
|
assert modcol != fn
|
||||||
|
|
||||||
|
def test_allow_sane_sorting_for_decorators(self, testdir):
|
||||||
|
modcol = testdir.getmodulecol("""
|
||||||
|
def dec(f):
|
||||||
|
g = lambda: f(2)
|
||||||
|
g.place_as = f
|
||||||
|
return g
|
||||||
|
|
||||||
|
|
||||||
|
def test_b(y):
|
||||||
|
pass
|
||||||
|
test_b = dec(test_b)
|
||||||
|
|
||||||
|
def test_a(y):
|
||||||
|
pass
|
||||||
|
test_a = dec(test_a)
|
||||||
|
""")
|
||||||
|
colitems = modcol.collect()
|
||||||
|
assert len(colitems) == 2
|
||||||
|
assert [item.name for item in colitems] == ['test_b', 'test_a']
|
||||||
|
|
||||||
|
|
||||||
|
class TestConftestCustomization:
|
||||||
|
def test_pytest_pycollect_module(self, testdir):
|
||||||
|
testdir.makeconftest("""
|
||||||
|
import py
|
||||||
|
class MyModule(py.test.collect.Module):
|
||||||
|
pass
|
||||||
|
def pytest_pycollect_makemodule(path, parent):
|
||||||
|
if path.basename == "test_xyz.py":
|
||||||
|
return MyModule(path, parent)
|
||||||
|
""")
|
||||||
|
testdir.makepyfile("def some(): pass")
|
||||||
|
testdir.makepyfile(test_xyz="")
|
||||||
|
result = testdir.runpytest("--collectonly")
|
||||||
|
result.stdout.fnmatch_lines([
|
||||||
|
"*<Module*test_pytest*",
|
||||||
|
"*<MyModule*xyz*",
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_pytest_pycollect_makeitem(self, testdir):
|
||||||
|
testdir.makeconftest("""
|
||||||
|
import py
|
||||||
|
class MyFunction(py.test.collect.Function):
|
||||||
|
pass
|
||||||
|
def pytest_pycollect_makeitem(collector, name, obj):
|
||||||
|
if name == "some":
|
||||||
|
return MyFunction(name, collector)
|
||||||
|
""")
|
||||||
|
testdir.makepyfile("def some(): pass")
|
||||||
|
result = testdir.runpytest("--collectonly")
|
||||||
|
result.stdout.fnmatch_lines([
|
||||||
|
"*MyFunction*some*",
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_makeitem_non_underscore(self, testdir, monkeypatch):
|
||||||
|
modcol = testdir.getmodulecol("def _hello(): pass")
|
||||||
|
l = []
|
||||||
|
monkeypatch.setattr(py.test.collect.Module, 'makeitem',
|
||||||
|
lambda self, name, obj: l.append(name))
|
||||||
|
l = modcol.collect()
|
||||||
|
assert '_hello' not in l
|
||||||
|
|
||||||
|
|
||||||
|
class TestReportinfo:
|
||||||
|
|
||||||
|
def test_func_reportinfo(self, testdir):
|
||||||
|
item = testdir.getitem("def test_func(): pass")
|
||||||
|
fspath, lineno, modpath = item.reportinfo()
|
||||||
|
assert fspath == item.fspath
|
||||||
|
assert lineno == 0
|
||||||
|
assert modpath == "test_func"
|
||||||
|
|
||||||
|
def test_class_reportinfo(self, testdir):
|
||||||
|
modcol = testdir.getmodulecol("""
|
||||||
|
# lineno 0
|
||||||
|
class TestClass:
|
||||||
|
def test_hello(self): pass
|
||||||
|
""")
|
||||||
|
classcol = modcol.collect_by_name("TestClass")
|
||||||
|
fspath, lineno, msg = classcol.reportinfo()
|
||||||
|
assert fspath == modcol.fspath
|
||||||
|
assert lineno == 1
|
||||||
|
assert msg == "TestClass"
|
||||||
|
|
||||||
|
def test_generator_reportinfo(self, testdir):
|
||||||
|
modcol = testdir.getmodulecol("""
|
||||||
|
# lineno 0
|
||||||
|
def test_gen():
|
||||||
|
def check(x):
|
||||||
|
assert x
|
||||||
|
yield check, 3
|
||||||
|
""")
|
||||||
|
gencol = modcol.collect_by_name("test_gen")
|
||||||
|
fspath, lineno, modpath = gencol.reportinfo()
|
||||||
|
assert fspath == modcol.fspath
|
||||||
|
assert lineno == 1
|
||||||
|
assert modpath == "test_gen"
|
||||||
|
|
||||||
|
genitem = gencol.collect()[0]
|
||||||
|
fspath, lineno, modpath = genitem.reportinfo()
|
||||||
|
assert fspath == modcol.fspath
|
||||||
|
assert lineno == 2
|
||||||
|
assert modpath == "test_gen[0]"
|
||||||
|
"""
|
||||||
|
def test_func():
|
||||||
|
pass
|
||||||
|
def test_genfunc():
|
||||||
|
def check(x):
|
||||||
|
pass
|
||||||
|
yield check, 3
|
||||||
|
class TestClass:
|
||||||
|
def test_method(self):
|
||||||
|
pass
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_setup_only_available_in_subdir(testdir):
|
||||||
|
sub1 = testdir.mkpydir("sub1")
|
||||||
|
sub2 = testdir.mkpydir("sub2")
|
||||||
|
sub1.join("conftest.py").write(py.code.Source("""
|
||||||
|
import py
|
||||||
|
def pytest_runtest_setup(item):
|
||||||
|
assert item.fspath.purebasename == "test_in_sub1"
|
||||||
|
def pytest_runtest_call(item):
|
||||||
|
assert item.fspath.purebasename == "test_in_sub1"
|
||||||
|
def pytest_runtest_teardown(item):
|
||||||
|
assert item.fspath.purebasename == "test_in_sub1"
|
||||||
|
"""))
|
||||||
|
sub2.join("conftest.py").write(py.code.Source("""
|
||||||
|
import py
|
||||||
|
def pytest_runtest_setup(item):
|
||||||
|
assert item.fspath.purebasename == "test_in_sub2"
|
||||||
|
def pytest_runtest_call(item):
|
||||||
|
assert item.fspath.purebasename == "test_in_sub2"
|
||||||
|
def pytest_runtest_teardown(item):
|
||||||
|
assert item.fspath.purebasename == "test_in_sub2"
|
||||||
|
"""))
|
||||||
|
sub1.join("test_in_sub1.py").write("def test_1(): pass")
|
||||||
|
sub2.join("test_in_sub2.py").write("def test_2(): pass")
|
||||||
|
result = testdir.runpytest("-v", "-s")
|
||||||
|
result.stdout.fnmatch_lines([
|
||||||
|
"*2 passed*"
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_generate_tests_only_done_in_subdir(testdir):
|
||||||
|
sub1 = testdir.mkpydir("sub1")
|
||||||
|
sub2 = testdir.mkpydir("sub2")
|
||||||
|
sub1.join("conftest.py").write(py.code.Source("""
|
||||||
|
def pytest_generate_tests(metafunc):
|
||||||
|
assert metafunc.function.__name__ == "test_1"
|
||||||
|
"""))
|
||||||
|
sub2.join("conftest.py").write(py.code.Source("""
|
||||||
|
def pytest_generate_tests(metafunc):
|
||||||
|
assert metafunc.function.__name__ == "test_2"
|
||||||
|
"""))
|
||||||
|
sub1.join("test_in_sub1.py").write("def test_1(): pass")
|
||||||
|
sub2.join("test_in_sub2.py").write("def test_2(): pass")
|
||||||
|
result = testdir.runpytest("-v", "-s", sub1, sub2, sub1)
|
||||||
|
result.stdout.fnmatch_lines([
|
||||||
|
"*3 passed*"
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_modulecol_roundtrip(testdir):
|
||||||
|
modcol = testdir.getmodulecol("pass", withinit=True)
|
||||||
|
trail = modcol.collection.getid(modcol)
|
||||||
|
newcol = modcol.collection.getbyid(trail)[0]
|
||||||
|
assert modcol.name == newcol.name
|
||||||
|
|
||||||
|
|
||||||
|
class TestTracebackCutting:
|
||||||
|
def test_skip_simple(self):
|
||||||
|
excinfo = py.test.raises(py.test.skip.Exception, 'py.test.skip("xxx")')
|
||||||
|
assert excinfo.traceback[-1].frame.code.name == "skip"
|
||||||
|
assert excinfo.traceback[-1].ishidden()
|
||||||
|
|
||||||
|
def test_traceback_argsetup(self, testdir):
|
||||||
|
testdir.makeconftest("""
|
||||||
|
def pytest_funcarg__hello(request):
|
||||||
|
raise ValueError("xyz")
|
||||||
|
""")
|
||||||
|
p = testdir.makepyfile("def test(hello): pass")
|
||||||
|
result = testdir.runpytest(p)
|
||||||
|
assert result.ret != 0
|
||||||
|
out = result.stdout.str()
|
||||||
|
assert out.find("xyz") != -1
|
||||||
|
assert out.find("conftest.py:2: ValueError") != -1
|
||||||
|
numentries = out.count("_ _ _") # separator for traceback entries
|
||||||
|
assert numentries == 0
|
||||||
|
|
||||||
|
result = testdir.runpytest("--fulltrace", p)
|
||||||
|
out = result.stdout.str()
|
||||||
|
assert out.find("conftest.py:2: ValueError") != -1
|
||||||
|
numentries = out.count("_ _ _ _") # separator for traceback entries
|
||||||
|
assert numentries >3
|
||||||
|
|
||||||
|
def test_traceback_error_during_import(self, testdir):
|
||||||
|
testdir.makepyfile("""
|
||||||
|
x = 1
|
||||||
|
x = 2
|
||||||
|
x = 17
|
||||||
|
asd
|
||||||
|
""")
|
||||||
|
result = testdir.runpytest()
|
||||||
|
assert result.ret != 0
|
||||||
|
out = result.stdout.str()
|
||||||
|
assert "x = 1" not in out
|
||||||
|
assert "x = 2" not in out
|
||||||
|
result.stdout.fnmatch_lines([
|
||||||
|
">*asd*",
|
||||||
|
"E*NameError*",
|
||||||
|
])
|
||||||
|
result = testdir.runpytest("--fulltrace")
|
||||||
|
out = result.stdout.str()
|
||||||
|
assert "x = 1" in out
|
||||||
|
assert "x = 2" in out
|
||||||
|
result.stdout.fnmatch_lines([
|
||||||
|
">*asd*",
|
||||||
|
"E*NameError*",
|
||||||
|
])
|
||||||
|
|
||||||
def test_getfuncargnames():
|
def test_getfuncargnames():
|
||||||
def f(): pass
|
def f(): pass
|
||||||
|
@ -593,3 +1109,28 @@ def test_funcarg_lookup_error(testdir):
|
||||||
"*1 error*",
|
"*1 error*",
|
||||||
])
|
])
|
||||||
assert "INTERNAL" not in result.stdout.str()
|
assert "INTERNAL" not in result.stdout.str()
|
||||||
|
|
||||||
|
class TestReportInfo:
|
||||||
|
def test_itemreport_reportinfo(self, testdir, linecomp):
|
||||||
|
testdir.makeconftest("""
|
||||||
|
import py
|
||||||
|
class Function(py.test.collect.Function):
|
||||||
|
def reportinfo(self):
|
||||||
|
return "ABCDE", 42, "custom"
|
||||||
|
""")
|
||||||
|
item = testdir.getitem("def test_func(): pass")
|
||||||
|
runner = item.config.pluginmanager.getplugin("runner")
|
||||||
|
nodeinfo = runner.getitemnodeinfo(item)
|
||||||
|
assert nodeinfo.location == ("ABCDE", 42, "custom")
|
||||||
|
|
||||||
|
def test_itemreport_pytest_report_iteminfo(self, testdir, linecomp):
|
||||||
|
item = testdir.getitem("def test_func(): pass")
|
||||||
|
tup = "FGHJ", 42, "custom"
|
||||||
|
class Plugin:
|
||||||
|
def pytest_report_iteminfo(self, item):
|
||||||
|
return tup
|
||||||
|
item.config.pluginmanager.register(Plugin())
|
||||||
|
runner = runner = item.config.pluginmanager.getplugin("runner")
|
||||||
|
nodeinfo = runner.getitemnodeinfo(item)
|
||||||
|
location = nodeinfo.location
|
||||||
|
assert location == tup
|
|
@ -3,10 +3,12 @@ import os
|
||||||
from py._plugin.pytest_resultlog import generic_path, ResultLog, \
|
from py._plugin.pytest_resultlog import generic_path, ResultLog, \
|
||||||
pytest_configure, pytest_unconfigure
|
pytest_configure, pytest_unconfigure
|
||||||
from py._test.collect import Node, Item, FSCollector
|
from py._test.collect import Node, Item, FSCollector
|
||||||
|
from py._test.session import Collection
|
||||||
|
|
||||||
def test_generic_path(testdir):
|
def test_generic_path(testdir):
|
||||||
config = testdir.parseconfig()
|
config = testdir.parseconfig()
|
||||||
p1 = Node('a', parent=config._rootcol)
|
collection = Collection(config)
|
||||||
|
p1 = Node('a', config=config, collection=collection)
|
||||||
#assert p1.fspath is None
|
#assert p1.fspath is None
|
||||||
p2 = Node('B', parent=p1)
|
p2 = Node('B', parent=p1)
|
||||||
p3 = Node('()', parent = p2)
|
p3 = Node('()', parent = p2)
|
||||||
|
@ -15,7 +17,7 @@ def test_generic_path(testdir):
|
||||||
res = generic_path(item)
|
res = generic_path(item)
|
||||||
assert res == 'a.B().c'
|
assert res == 'a.B().c'
|
||||||
|
|
||||||
p0 = FSCollector('proj/test', parent=config._rootcol)
|
p0 = FSCollector('proj/test', config=config, collection=collection)
|
||||||
p1 = FSCollector('proj/test/a', parent=p0)
|
p1 = FSCollector('proj/test/a', parent=p0)
|
||||||
p2 = Node('B', parent=p1)
|
p2 = Node('B', parent=p1)
|
||||||
p3 = Node('()', parent = p2)
|
p3 = Node('()', parent = p2)
|
||||||
|
|
|
@ -53,8 +53,8 @@ class BaseFunctionalTests:
|
||||||
rep = reports[1]
|
rep = reports[1]
|
||||||
assert rep.passed
|
assert rep.passed
|
||||||
assert not rep.failed
|
assert not rep.failed
|
||||||
assert rep.shortrepr == "."
|
assert rep.outcome == "passed"
|
||||||
assert not hasattr(rep, 'longrepr')
|
assert not rep.longrepr
|
||||||
|
|
||||||
def test_failfunction(self, testdir):
|
def test_failfunction(self, testdir):
|
||||||
reports = testdir.runitem("""
|
reports = testdir.runitem("""
|
||||||
|
@ -66,23 +66,8 @@ class BaseFunctionalTests:
|
||||||
assert not rep.skipped
|
assert not rep.skipped
|
||||||
assert rep.failed
|
assert rep.failed
|
||||||
assert rep.when == "call"
|
assert rep.when == "call"
|
||||||
assert isinstance(rep.longrepr, ReprExceptionInfo)
|
assert rep.outcome == "failed"
|
||||||
assert str(rep.shortrepr) == "F"
|
#assert isinstance(rep.longrepr, ReprExceptionInfo)
|
||||||
|
|
||||||
def test_failfunction_customized_report(self, testdir, LineMatcher):
|
|
||||||
reports = testdir.runitem("""
|
|
||||||
def test_func():
|
|
||||||
assert 0
|
|
||||||
""")
|
|
||||||
rep = reports[1]
|
|
||||||
rep.headerlines += ["hello world"]
|
|
||||||
tr = py.io.TerminalWriter(stringio=True)
|
|
||||||
rep.toterminal(tr)
|
|
||||||
val = tr.stringio.getvalue()
|
|
||||||
LineMatcher(val.split("\n")).fnmatch_lines([
|
|
||||||
"*hello world",
|
|
||||||
"*def test_func():*"
|
|
||||||
])
|
|
||||||
|
|
||||||
def test_skipfunction(self, testdir):
|
def test_skipfunction(self, testdir):
|
||||||
reports = testdir.runitem("""
|
reports = testdir.runitem("""
|
||||||
|
@ -94,6 +79,7 @@ class BaseFunctionalTests:
|
||||||
assert not rep.failed
|
assert not rep.failed
|
||||||
assert not rep.passed
|
assert not rep.passed
|
||||||
assert rep.skipped
|
assert rep.skipped
|
||||||
|
assert rep.outcome == "skipped"
|
||||||
#assert rep.skipped.when == "call"
|
#assert rep.skipped.when == "call"
|
||||||
#assert rep.skipped.when == "call"
|
#assert rep.skipped.when == "call"
|
||||||
#assert rep.skipped == "%sreason == "hello"
|
#assert rep.skipped == "%sreason == "hello"
|
||||||
|
@ -150,8 +136,8 @@ class BaseFunctionalTests:
|
||||||
assert not rep.passed
|
assert not rep.passed
|
||||||
assert rep.failed
|
assert rep.failed
|
||||||
assert rep.when == "teardown"
|
assert rep.when == "teardown"
|
||||||
assert rep.longrepr.reprcrash.lineno == 3
|
#assert rep.longrepr.reprcrash.lineno == 3
|
||||||
assert rep.longrepr.reprtraceback.reprentries
|
#assert rep.longrepr.reprtraceback.reprentries
|
||||||
|
|
||||||
def test_custom_failure_repr(self, testdir):
|
def test_custom_failure_repr(self, testdir):
|
||||||
testdir.makepyfile(conftest="""
|
testdir.makepyfile(conftest="""
|
||||||
|
@ -270,6 +256,10 @@ class TestCollectionReports:
|
||||||
assert not rep.failed
|
assert not rep.failed
|
||||||
assert not rep.skipped
|
assert not rep.skipped
|
||||||
assert rep.passed
|
assert rep.passed
|
||||||
|
locinfo = rep.location
|
||||||
|
assert locinfo[0] == col.fspath
|
||||||
|
assert not locinfo[1]
|
||||||
|
assert locinfo[2] == col.fspath
|
||||||
res = rep.result
|
res = rep.result
|
||||||
assert len(res) == 2
|
assert len(res) == 2
|
||||||
assert res[0].name == "test_func1"
|
assert res[0].name == "test_func1"
|
||||||
|
@ -299,7 +289,7 @@ def test_callinfo():
|
||||||
assert "exc" in repr(ci)
|
assert "exc" in repr(ci)
|
||||||
|
|
||||||
# design question: do we want general hooks in python files?
|
# design question: do we want general hooks in python files?
|
||||||
# following passes if withpy defaults to True in pycoll.PyObjMix._getplugins()
|
# then something like the following functional tests makes sense
|
||||||
@py.test.mark.xfail
|
@py.test.mark.xfail
|
||||||
def test_runtest_in_module_ordering(testdir):
|
def test_runtest_in_module_ordering(testdir):
|
||||||
p1 = testdir.makepyfile("""
|
p1 = testdir.makepyfile("""
|
||||||
|
|
|
@ -183,8 +183,10 @@ class TestXFail:
|
||||||
""")
|
""")
|
||||||
result = testdir.runpytest(p, '--report=xfailed', )
|
result = testdir.runpytest(p, '--report=xfailed', )
|
||||||
result.stdout.fnmatch_lines([
|
result.stdout.fnmatch_lines([
|
||||||
"*test_one*test_this*NOTRUN*noway",
|
"*test_one*test_this*",
|
||||||
"*test_one*test_this_true*NOTRUN*condition:*True*",
|
"*NOTRUN*noway",
|
||||||
|
"*test_one*test_this_true*",
|
||||||
|
"*NOTRUN*condition:*True*",
|
||||||
"*1 passed*",
|
"*1 passed*",
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@ -199,7 +201,8 @@ class TestXFail:
|
||||||
""")
|
""")
|
||||||
result = testdir.runpytest(p, '--report=xfailed', )
|
result = testdir.runpytest(p, '--report=xfailed', )
|
||||||
result.stdout.fnmatch_lines([
|
result.stdout.fnmatch_lines([
|
||||||
"*test_one*test_this*NOTRUN*hello",
|
"*test_one*test_this*",
|
||||||
|
"*NOTRUN*hello",
|
||||||
"*1 xfailed*",
|
"*1 xfailed*",
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@ -229,7 +232,8 @@ class TestXFail:
|
||||||
])
|
])
|
||||||
result = testdir.runpytest(p, "-rx")
|
result = testdir.runpytest(p, "-rx")
|
||||||
result.stdout.fnmatch_lines([
|
result.stdout.fnmatch_lines([
|
||||||
"*XFAIL*test_this*reason:*hello*",
|
"*XFAIL*test_this*",
|
||||||
|
"*reason:*hello*",
|
||||||
])
|
])
|
||||||
result = testdir.runpytest(p, "--runxfail")
|
result = testdir.runpytest(p, "--runxfail")
|
||||||
result.stdout.fnmatch_lines([
|
result.stdout.fnmatch_lines([
|
||||||
|
@ -252,7 +256,8 @@ class TestXFail:
|
||||||
])
|
])
|
||||||
result = testdir.runpytest(p, "-rx")
|
result = testdir.runpytest(p, "-rx")
|
||||||
result.stdout.fnmatch_lines([
|
result.stdout.fnmatch_lines([
|
||||||
"*XFAIL*test_this*reason:*hello*",
|
"*XFAIL*test_this*",
|
||||||
|
"*reason:*hello*",
|
||||||
])
|
])
|
||||||
result = testdir.runpytest(p, "--runxfail")
|
result = testdir.runpytest(p, "--runxfail")
|
||||||
result.stdout.fnmatch_lines([
|
result.stdout.fnmatch_lines([
|
||||||
|
@ -286,7 +291,8 @@ class TestXFail:
|
||||||
""")
|
""")
|
||||||
result = testdir.runpytest(p, '-rxX')
|
result = testdir.runpytest(p, '-rxX')
|
||||||
result.stdout.fnmatch_lines([
|
result.stdout.fnmatch_lines([
|
||||||
"*XFAIL*test_this*NOTRUN*",
|
"*XFAIL*test_this*",
|
||||||
|
"*NOTRUN*",
|
||||||
])
|
])
|
||||||
|
|
||||||
def test_dynamic_xfail_set_during_funcarg_setup(self, testdir):
|
def test_dynamic_xfail_set_during_funcarg_setup(self, testdir):
|
||||||
|
@ -360,7 +366,6 @@ def test_skipif_class(testdir):
|
||||||
|
|
||||||
|
|
||||||
def test_skip_reasons_folding():
|
def test_skip_reasons_folding():
|
||||||
from py._plugin import pytest_runner as runner
|
|
||||||
from py._plugin.pytest_skipping import folded_skips
|
from py._plugin.pytest_skipping import folded_skips
|
||||||
class longrepr:
|
class longrepr:
|
||||||
class reprcrash:
|
class reprcrash:
|
||||||
|
@ -368,12 +373,15 @@ def test_skip_reasons_folding():
|
||||||
lineno = 3
|
lineno = 3
|
||||||
message = "justso"
|
message = "justso"
|
||||||
|
|
||||||
ev1 = runner.CollectReport(None, None)
|
class X:
|
||||||
|
pass
|
||||||
|
ev1 = X()
|
||||||
ev1.when = "execute"
|
ev1.when = "execute"
|
||||||
ev1.skipped = True
|
ev1.skipped = True
|
||||||
ev1.longrepr = longrepr
|
ev1.longrepr = longrepr
|
||||||
|
|
||||||
ev2 = runner.ItemTestReport(None, excinfo=longrepr)
|
ev2 = X()
|
||||||
|
ev2.longrepr = longrepr
|
||||||
ev2.skipped = True
|
ev2.skipped = True
|
||||||
|
|
||||||
l = folded_skips([ev1, ev2])
|
l = folded_skips([ev1, ev2])
|
||||||
|
@ -408,8 +416,8 @@ def test_skipped_reasons_functional(testdir):
|
||||||
)
|
)
|
||||||
result = testdir.runpytest('--report=skipped')
|
result = testdir.runpytest('--report=skipped')
|
||||||
result.stdout.fnmatch_lines([
|
result.stdout.fnmatch_lines([
|
||||||
"*test_one.py ss",
|
|
||||||
"*test_two.py S",
|
"*test_two.py S",
|
||||||
|
"*test_one.py ss",
|
||||||
"*SKIP*3*conftest.py:3: 'test'",
|
"*SKIP*3*conftest.py:3: 'test'",
|
||||||
])
|
])
|
||||||
assert result.ret == 0
|
assert result.ret == 0
|
||||||
|
|
|
@ -89,60 +89,28 @@ class TestTerminal:
|
||||||
assert lines[1].endswith("xy.py .")
|
assert lines[1].endswith("xy.py .")
|
||||||
assert lines[2] == "hello world"
|
assert lines[2] == "hello world"
|
||||||
|
|
||||||
def test_testid(self, testdir, linecomp):
|
def test_show_runtest_logstart(self, testdir, linecomp):
|
||||||
func,method = testdir.getitems("""
|
item = testdir.getitem("def test_func(): pass")
|
||||||
def test_func():
|
tr = TerminalReporter(item.config, file=linecomp.stringio)
|
||||||
pass
|
item.config.pluginmanager.register(tr)
|
||||||
class TestClass:
|
nodeid = item.collection.getid(item)
|
||||||
def test_method(self):
|
location = item.ihook.pytest_report_iteminfo(item=item)
|
||||||
pass
|
tr.config.hook.pytest_runtest_logstart(nodeid=nodeid,
|
||||||
|
location=location, fspath=str(item.fspath))
|
||||||
|
linecomp.assert_contains_lines([
|
||||||
|
"*test_show_runtest_logstart.py*"
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_runtest_location_shown_before_test_starts(self, testdir):
|
||||||
|
p1 = testdir.makepyfile("""
|
||||||
|
def test_1():
|
||||||
|
import time
|
||||||
|
time.sleep(20)
|
||||||
""")
|
""")
|
||||||
tr = TerminalReporter(func.config, file=linecomp.stringio)
|
child = testdir.spawn_pytest("")
|
||||||
id = tr.gettestid(func)
|
child.expect(".*test_runtest_location.*py")
|
||||||
assert id.endswith("test_testid.py::test_func")
|
child.sendeof()
|
||||||
fspath = py.path.local(id.split("::")[0])
|
child.kill(15)
|
||||||
assert fspath.check()
|
|
||||||
id = tr.gettestid(method)
|
|
||||||
assert id.endswith("test_testid.py::TestClass::test_method")
|
|
||||||
|
|
||||||
def test_show_path_before_running_test(self, testdir, linecomp):
|
|
||||||
item = testdir.getitem("def test_func(): pass")
|
|
||||||
tr = TerminalReporter(item.config, file=linecomp.stringio)
|
|
||||||
item.config.pluginmanager.register(tr)
|
|
||||||
tr.config.hook.pytest_itemstart(item=item)
|
|
||||||
linecomp.assert_contains_lines([
|
|
||||||
"*test_show_path_before_running_test.py*"
|
|
||||||
])
|
|
||||||
|
|
||||||
def test_itemreport_reportinfo(self, testdir, linecomp):
|
|
||||||
testdir.makeconftest("""
|
|
||||||
import py
|
|
||||||
class Function(py.test.collect.Function):
|
|
||||||
def reportinfo(self):
|
|
||||||
return "ABCDE", 42, "custom"
|
|
||||||
""")
|
|
||||||
item = testdir.getitem("def test_func(): pass")
|
|
||||||
tr = TerminalReporter(item.config, file=linecomp.stringio)
|
|
||||||
item.config.pluginmanager.register(tr)
|
|
||||||
tr.config.option.verbose = True
|
|
||||||
tr.config.hook.pytest_itemstart(item=item)
|
|
||||||
linecomp.assert_contains_lines([
|
|
||||||
"*ABCDE:43: custom*"
|
|
||||||
])
|
|
||||||
|
|
||||||
def test_itemreport_pytest_report_iteminfo(self, testdir, linecomp):
|
|
||||||
item = testdir.getitem("def test_func(): pass")
|
|
||||||
class Plugin:
|
|
||||||
def pytest_report_iteminfo(self, item):
|
|
||||||
return "FGHJ", 42, "custom"
|
|
||||||
item.config.pluginmanager.register(Plugin())
|
|
||||||
tr = TerminalReporter(item.config, file=linecomp.stringio)
|
|
||||||
item.config.pluginmanager.register(tr)
|
|
||||||
tr.config.option.verbose = True
|
|
||||||
tr.config.hook.pytest_itemstart(item=item)
|
|
||||||
linecomp.assert_contains_lines([
|
|
||||||
"*FGHJ:43: custom*"
|
|
||||||
])
|
|
||||||
|
|
||||||
def test_itemreport_subclasses_show_subclassed_file(self, testdir):
|
def test_itemreport_subclasses_show_subclassed_file(self, testdir):
|
||||||
p1 = testdir.makepyfile(test_p1="""
|
p1 = testdir.makepyfile(test_p1="""
|
||||||
|
@ -206,12 +174,12 @@ class TestCollectonly:
|
||||||
"<Module 'test_collectonly_basic.py'>"
|
"<Module 'test_collectonly_basic.py'>"
|
||||||
])
|
])
|
||||||
item = modcol.join("test_func")
|
item = modcol.join("test_func")
|
||||||
rep.config.hook.pytest_itemstart(item=item)
|
rep.config.hook.pytest_log_itemcollect(item=item)
|
||||||
linecomp.assert_contains_lines([
|
linecomp.assert_contains_lines([
|
||||||
" <Function 'test_func'>",
|
" <Function 'test_func'>",
|
||||||
])
|
])
|
||||||
rep.config.hook.pytest_collectreport(
|
report = rep.config.hook.pytest_make_collect_report(collector=modcol)
|
||||||
report=runner.CollectReport(modcol, [], excinfo=None))
|
rep.config.hook.pytest_collectreport(report=report)
|
||||||
assert rep.indent == indent
|
assert rep.indent == indent
|
||||||
|
|
||||||
def test_collectonly_skipped_module(self, testdir, linecomp):
|
def test_collectonly_skipped_module(self, testdir, linecomp):
|
||||||
|
@ -264,13 +232,13 @@ class TestCollectonly:
|
||||||
stderr = result.stderr.str().strip()
|
stderr = result.stderr.str().strip()
|
||||||
#assert stderr.startswith("inserting into sys.path")
|
#assert stderr.startswith("inserting into sys.path")
|
||||||
assert result.ret == 0
|
assert result.ret == 0
|
||||||
extra = result.stdout.fnmatch_lines(py.code.Source("""
|
extra = result.stdout.fnmatch_lines([
|
||||||
<Module '*.py'>
|
"*<Module '*.py'>",
|
||||||
<Function 'test_func1'*>
|
"* <Function 'test_func1'*>",
|
||||||
<Class 'TestClass'>
|
"* <Class 'TestClass'>",
|
||||||
<Instance '()'>
|
"* <Instance '()'>",
|
||||||
<Function 'test_method'*>
|
"* <Function 'test_method'*>",
|
||||||
""").strip())
|
])
|
||||||
|
|
||||||
def test_collectonly_error(self, testdir):
|
def test_collectonly_error(self, testdir):
|
||||||
p = testdir.makepyfile("import Errlkjqweqwe")
|
p = testdir.makepyfile("import Errlkjqweqwe")
|
||||||
|
@ -278,9 +246,9 @@ class TestCollectonly:
|
||||||
stderr = result.stderr.str().strip()
|
stderr = result.stderr.str().strip()
|
||||||
assert result.ret == 1
|
assert result.ret == 1
|
||||||
extra = result.stdout.fnmatch_lines(py.code.Source("""
|
extra = result.stdout.fnmatch_lines(py.code.Source("""
|
||||||
<Module '*.py'>
|
*<Module '*.py'>
|
||||||
*ImportError*
|
*ImportError*
|
||||||
!!!*failures*!!!
|
*!!!*failures*!!!
|
||||||
*test_collectonly_error.py:1*
|
*test_collectonly_error.py:1*
|
||||||
""").strip())
|
""").strip())
|
||||||
|
|
||||||
|
@ -454,6 +422,7 @@ class TestTerminalFunctional:
|
||||||
"*test_verbose_reporting.py:10: test_gen*FAIL*",
|
"*test_verbose_reporting.py:10: test_gen*FAIL*",
|
||||||
])
|
])
|
||||||
assert result.ret == 1
|
assert result.ret == 1
|
||||||
|
py.test.xfail("fix dist-testing")
|
||||||
pytestconfig.pluginmanager.skipifmissing("xdist")
|
pytestconfig.pluginmanager.skipifmissing("xdist")
|
||||||
result = testdir.runpytest(p1, '-v', '-n 1')
|
result = testdir.runpytest(p1, '-v', '-n 1')
|
||||||
result.stdout.fnmatch_lines([
|
result.stdout.fnmatch_lines([
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from py._plugin.pytest_tmpdir import pytest_funcarg__tmpdir
|
from py._plugin.pytest_tmpdir import pytest_funcarg__tmpdir
|
||||||
|
from py._plugin.pytest_python import FuncargRequest
|
||||||
|
|
||||||
def test_funcarg(testdir):
|
def test_funcarg(testdir):
|
||||||
from py._test.funcargs import FuncargRequest
|
|
||||||
item = testdir.getitem("def test_func(tmpdir): pass")
|
item = testdir.getitem("def test_func(tmpdir): pass")
|
||||||
p = pytest_funcarg__tmpdir(FuncargRequest(item))
|
p = pytest_funcarg__tmpdir(FuncargRequest(item))
|
||||||
assert p.check()
|
assert p.check()
|
||||||
|
|
|
@ -59,19 +59,18 @@ class TestCollector:
|
||||||
import py
|
import py
|
||||||
class CustomFile(py.test.collect.File):
|
class CustomFile(py.test.collect.File):
|
||||||
pass
|
pass
|
||||||
class MyDirectory(py.test.collect.Directory):
|
def pytest_collect_file(path, parent):
|
||||||
def collect(self):
|
if path.ext == ".xxx":
|
||||||
return [CustomFile(self.fspath.join("hello.xxx"), parent=self)]
|
return CustomFile(path, parent=parent)
|
||||||
def pytest_collect_directory(path, parent):
|
|
||||||
return MyDirectory(path, parent=parent)
|
|
||||||
""")
|
""")
|
||||||
config = testdir.parseconfig(hello)
|
config = testdir.parseconfig(hello)
|
||||||
node = config.getnode(hello)
|
node = testdir.getnode(config, hello)
|
||||||
assert isinstance(node, py.test.collect.File)
|
assert isinstance(node, py.test.collect.File)
|
||||||
assert node.name == "hello.xxx"
|
assert node.name == "hello.xxx"
|
||||||
names = config._rootcol.totrail(node)
|
id = node.collection.getid(node)
|
||||||
node = config._rootcol.getbynames(names)
|
nodes = node.collection.getbyid(id)
|
||||||
assert isinstance(node, py.test.collect.File)
|
assert len(nodes) == 1
|
||||||
|
assert isinstance(nodes[0], py.test.collect.File)
|
||||||
|
|
||||||
class TestCollectFS:
|
class TestCollectFS:
|
||||||
def test_ignored_certain_directories(self, testdir):
|
def test_ignored_certain_directories(self, testdir):
|
||||||
|
@ -84,7 +83,7 @@ class TestCollectFS:
|
||||||
tmpdir.ensure("normal", 'test_found.py')
|
tmpdir.ensure("normal", 'test_found.py')
|
||||||
tmpdir.ensure('test_found.py')
|
tmpdir.ensure('test_found.py')
|
||||||
|
|
||||||
col = testdir.parseconfig(tmpdir).getnode(tmpdir)
|
col = testdir.getnode(testdir.parseconfig(tmpdir), tmpdir)
|
||||||
items = col.collect()
|
items = col.collect()
|
||||||
names = [x.name for x in items]
|
names = [x.name for x in items]
|
||||||
assert len(items) == 2
|
assert len(items) == 2
|
||||||
|
@ -93,7 +92,7 @@ class TestCollectFS:
|
||||||
|
|
||||||
def test_found_certain_testfiles(self, testdir):
|
def test_found_certain_testfiles(self, testdir):
|
||||||
p1 = testdir.makepyfile(test_found = "pass", found_test="pass")
|
p1 = testdir.makepyfile(test_found = "pass", found_test="pass")
|
||||||
col = testdir.parseconfig(p1).getnode(p1.dirpath())
|
col = testdir.getnode(testdir.parseconfig(p1), p1.dirpath())
|
||||||
items = col.collect() # Directory collect returns files sorted by name
|
items = col.collect() # Directory collect returns files sorted by name
|
||||||
assert len(items) == 2
|
assert len(items) == 2
|
||||||
assert items[1].name == 'test_found.py'
|
assert items[1].name == 'test_found.py'
|
||||||
|
@ -106,7 +105,7 @@ class TestCollectFS:
|
||||||
testdir.makepyfile(test_two="hello")
|
testdir.makepyfile(test_two="hello")
|
||||||
p1.dirpath().mkdir("dir2")
|
p1.dirpath().mkdir("dir2")
|
||||||
config = testdir.parseconfig()
|
config = testdir.parseconfig()
|
||||||
col = config.getnode(p1.dirpath())
|
col = testdir.getnode(config, p1.dirpath())
|
||||||
names = [x.name for x in col.collect()]
|
names = [x.name for x in col.collect()]
|
||||||
assert names == ["dir1", "dir2", "test_one.py", "test_two.py", "x"]
|
assert names == ["dir1", "dir2", "test_one.py", "test_two.py", "x"]
|
||||||
|
|
||||||
|
@ -120,7 +119,7 @@ class TestCollectPluginHookRelay:
|
||||||
config = testdir.Config()
|
config = testdir.Config()
|
||||||
config.pluginmanager.register(Plugin())
|
config.pluginmanager.register(Plugin())
|
||||||
config.parse([tmpdir])
|
config.parse([tmpdir])
|
||||||
col = config.getnode(tmpdir)
|
col = testdir.getnode(config, tmpdir)
|
||||||
testdir.makefile(".abc", "xyz")
|
testdir.makefile(".abc", "xyz")
|
||||||
res = col.collect()
|
res = col.collect()
|
||||||
assert len(wascalled) == 1
|
assert len(wascalled) == 1
|
||||||
|
@ -141,7 +140,7 @@ class TestCollectPluginHookRelay:
|
||||||
assert "world" in wascalled
|
assert "world" in wascalled
|
||||||
# make sure the directories do not get double-appended
|
# make sure the directories do not get double-appended
|
||||||
colreports = reprec.getreports("pytest_collectreport")
|
colreports = reprec.getreports("pytest_collectreport")
|
||||||
names = [rep.collector.name for rep in colreports]
|
names = [rep.nodenames[-1] for rep in colreports]
|
||||||
assert names.count("hello") == 1
|
assert names.count("hello") == 1
|
||||||
|
|
||||||
class TestPrunetraceback:
|
class TestPrunetraceback:
|
||||||
|
@ -181,6 +180,7 @@ class TestPrunetraceback:
|
||||||
"*hello world*",
|
"*hello world*",
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@py.test.mark.xfail(reason="other mechanism for adding to reporting needed")
|
||||||
def test_collect_report_postprocessing(self, testdir):
|
def test_collect_report_postprocessing(self, testdir):
|
||||||
p = testdir.makepyfile("""
|
p = testdir.makepyfile("""
|
||||||
import not_exists
|
import not_exists
|
||||||
|
@ -227,16 +227,18 @@ class TestCustomConftests:
|
||||||
testdir.mkdir("hello")
|
testdir.mkdir("hello")
|
||||||
testdir.makepyfile(test_world="#")
|
testdir.makepyfile(test_world="#")
|
||||||
reprec = testdir.inline_run(testdir.tmpdir)
|
reprec = testdir.inline_run(testdir.tmpdir)
|
||||||
names = [rep.collector.name for rep in reprec.getreports("pytest_collectreport")]
|
names = [rep.nodenames[-1]
|
||||||
|
for rep in reprec.getreports("pytest_collectreport")]
|
||||||
assert 'hello' not in names
|
assert 'hello' not in names
|
||||||
assert 'test_world.py' not in names
|
assert 'test_world.py' not in names
|
||||||
reprec = testdir.inline_run(testdir.tmpdir, "--XX")
|
reprec = testdir.inline_run(testdir.tmpdir, "--XX")
|
||||||
names = [rep.collector.name for rep in reprec.getreports("pytest_collectreport")]
|
names = [rep.nodenames[-1]
|
||||||
|
for rep in reprec.getreports("pytest_collectreport")]
|
||||||
assert 'hello' in names
|
assert 'hello' in names
|
||||||
assert 'test_world.py' in names
|
assert 'test_world.py' in names
|
||||||
|
|
||||||
def test_pytest_fs_collect_hooks_are_seen(self, testdir):
|
def test_pytest_fs_collect_hooks_are_seen(self, testdir):
|
||||||
testdir.makeconftest("""
|
conf = testdir.makeconftest("""
|
||||||
import py
|
import py
|
||||||
class MyDirectory(py.test.collect.Directory):
|
class MyDirectory(py.test.collect.Directory):
|
||||||
pass
|
pass
|
||||||
|
@ -247,79 +249,11 @@ class TestCustomConftests:
|
||||||
def pytest_collect_file(path, parent):
|
def pytest_collect_file(path, parent):
|
||||||
return MyModule(path, parent)
|
return MyModule(path, parent)
|
||||||
""")
|
""")
|
||||||
testdir.makepyfile("def test_x(): pass")
|
sub = testdir.mkdir("sub")
|
||||||
|
p = testdir.makepyfile("def test_x(): pass")
|
||||||
result = testdir.runpytest("--collectonly")
|
result = testdir.runpytest("--collectonly")
|
||||||
result.stdout.fnmatch_lines([
|
result.stdout.fnmatch_lines([
|
||||||
"*MyDirectory*",
|
"*MyDirectory*",
|
||||||
"*MyModule*",
|
"*MyModule*",
|
||||||
"*test_x*"
|
"*test_x*"
|
||||||
])
|
])
|
||||||
|
|
||||||
class TestRootCol:
|
|
||||||
def test_totrail_and_back(self, testdir, tmpdir):
|
|
||||||
a = tmpdir.ensure("a", dir=1)
|
|
||||||
tmpdir.ensure("a", "__init__.py")
|
|
||||||
x = tmpdir.ensure("a", "trail.py")
|
|
||||||
config = testdir.reparseconfig([x])
|
|
||||||
col = config.getnode(x)
|
|
||||||
trail = config._rootcol.totrail(col)
|
|
||||||
col2 = config._rootcol.fromtrail(trail)
|
|
||||||
assert col2 == col
|
|
||||||
|
|
||||||
@py.test.mark.xfail(reason="http://bitbucket.org/hpk42/py-trunk/issue/109")
|
|
||||||
def test_sibling_conftest_issue109(self, testdir):
|
|
||||||
"""
|
|
||||||
This test is to make sure that the conftest.py of sibling directories is not loaded
|
|
||||||
if py.test is run for/in one of the siblings directory and those sibling directories
|
|
||||||
are not packaged together with an __init__.py. See bitbucket issue #109.
|
|
||||||
"""
|
|
||||||
for dirname in ['a', 'b']:
|
|
||||||
testdir.tmpdir.ensure(dirname, dir=True)
|
|
||||||
testdir.tmpdir.ensure(dirname, '__init__.py')
|
|
||||||
|
|
||||||
# To create the conftest.py I would like to use testdir.make*-methods
|
|
||||||
# but as far as I have seen they can only create files in testdir.tempdir
|
|
||||||
# Maybe there is a way to explicitly specifiy the directory on which those
|
|
||||||
# methods work or a completely better way to do that?
|
|
||||||
backupTmpDir = testdir.tmpdir
|
|
||||||
testdir.tmpdir = testdir.tmpdir.join(dirname)
|
|
||||||
testdir.makeconftest("""
|
|
||||||
_DIR_NAME = '%s'
|
|
||||||
def pytest_configure(config):
|
|
||||||
if config.args and config.args[0] != _DIR_NAME:
|
|
||||||
raise Exception("py.test run for '" + config.args[0] + "', but '" + _DIR_NAME + "/conftest.py' loaded.")
|
|
||||||
""" % dirname)
|
|
||||||
testdir.tmpdir = backupTmpDir
|
|
||||||
|
|
||||||
for dirname, other_dirname in [('a', 'b'), ('b', 'a')]:
|
|
||||||
result = testdir.runpytest(dirname)
|
|
||||||
assert result.ret == 0, "test_sibling_conftest: py.test run for '%s', but '%s/conftest.py' loaded." % (dirname, other_dirname)
|
|
||||||
|
|
||||||
def test_totrail_topdir_and_beyond(self, testdir, tmpdir):
|
|
||||||
config = testdir.reparseconfig()
|
|
||||||
col = config.getnode(config.topdir)
|
|
||||||
trail = config._rootcol.totrail(col)
|
|
||||||
col2 = config._rootcol.fromtrail(trail)
|
|
||||||
assert col2.fspath == config.topdir
|
|
||||||
assert len(col2.listchain()) == 1
|
|
||||||
py.test.raises(config.Error, "config.getnode(config.topdir.dirpath())")
|
|
||||||
#col3 = config.getnode(config.topdir.dirpath())
|
|
||||||
#py.test.raises(ValueError,
|
|
||||||
# "col3._totrail()")
|
|
||||||
|
|
||||||
def test_argid(self, testdir, tmpdir):
|
|
||||||
cfg = testdir.parseconfig()
|
|
||||||
p = testdir.makepyfile("def test_func(): pass")
|
|
||||||
item = cfg.getnode("%s::test_func" % p)
|
|
||||||
assert item.name == "test_func"
|
|
||||||
|
|
||||||
def test_argid_with_method(self, testdir, tmpdir):
|
|
||||||
cfg = testdir.parseconfig()
|
|
||||||
p = testdir.makepyfile("""
|
|
||||||
class TestClass:
|
|
||||||
def test_method(self): pass
|
|
||||||
""")
|
|
||||||
item = cfg.getnode("%s::TestClass::()::test_method" % p)
|
|
||||||
assert item.name == "test_method"
|
|
||||||
item = cfg.getnode("%s::TestClass::test_method" % p)
|
|
||||||
assert item.name == "test_method"
|
|
||||||
|
|
|
@ -0,0 +1,314 @@
|
||||||
|
import py
|
||||||
|
|
||||||
|
from py._test.session import Collection, gettopdir
|
||||||
|
|
||||||
|
class TestCollection:
|
||||||
|
def test_parsearg(self, testdir):
|
||||||
|
p = testdir.makepyfile("def test_func(): pass")
|
||||||
|
subdir = testdir.mkdir("sub")
|
||||||
|
subdir.ensure("__init__.py")
|
||||||
|
target = subdir.join(p.basename)
|
||||||
|
p.move(target)
|
||||||
|
testdir.chdir()
|
||||||
|
subdir.chdir()
|
||||||
|
config = testdir.parseconfig(p.basename)
|
||||||
|
rcol = Collection(config=config)
|
||||||
|
assert rcol.topdir == testdir.tmpdir
|
||||||
|
parts = rcol._parsearg(p.basename)
|
||||||
|
assert parts[0] == "sub"
|
||||||
|
assert parts[1] == p.basename
|
||||||
|
assert len(parts) == 2
|
||||||
|
parts = rcol._parsearg(p.basename + "::test_func")
|
||||||
|
assert parts[0] == "sub"
|
||||||
|
assert parts[1] == p.basename
|
||||||
|
assert parts[2] == "test_func"
|
||||||
|
assert len(parts) == 3
|
||||||
|
|
||||||
|
def test_collect_topdir(self, testdir):
|
||||||
|
p = testdir.makepyfile("def test_func(): pass")
|
||||||
|
id = "::".join([p.basename, "test_func"])
|
||||||
|
config = testdir.parseconfig(id)
|
||||||
|
topdir = testdir.tmpdir
|
||||||
|
rcol = Collection(config)
|
||||||
|
assert topdir == rcol.topdir
|
||||||
|
hookrec = testdir.getreportrecorder(config)
|
||||||
|
items = rcol.perform_collect()
|
||||||
|
assert len(items) == 1
|
||||||
|
root = items[0].listchain()[0]
|
||||||
|
root_id = rcol.getid(root)
|
||||||
|
root2 = rcol.getbyid(root_id)[0]
|
||||||
|
assert root2.fspath == root.fspath
|
||||||
|
|
||||||
|
def test_collect_protocol_single_function(self, testdir):
|
||||||
|
p = testdir.makepyfile("def test_func(): pass")
|
||||||
|
id = "::".join([p.basename, "test_func"])
|
||||||
|
config = testdir.parseconfig(id)
|
||||||
|
topdir = testdir.tmpdir
|
||||||
|
rcol = Collection(config)
|
||||||
|
assert topdir == rcol.topdir
|
||||||
|
hookrec = testdir.getreportrecorder(config)
|
||||||
|
items = rcol.perform_collect()
|
||||||
|
assert len(items) == 1
|
||||||
|
item = items[0]
|
||||||
|
assert item.name == "test_func"
|
||||||
|
newid = rcol.getid(item)
|
||||||
|
assert newid == id
|
||||||
|
py.std.pprint.pprint(hookrec.hookrecorder.calls)
|
||||||
|
hookrec.hookrecorder.contains([
|
||||||
|
("pytest_collectstart", "collector.fspath == topdir"),
|
||||||
|
("pytest_make_collect_report", "collector.fspath == topdir"),
|
||||||
|
("pytest_collectstart", "collector.fspath == p"),
|
||||||
|
("pytest_make_collect_report", "collector.fspath == p"),
|
||||||
|
("pytest_pycollect_makeitem", "name == 'test_func'"),
|
||||||
|
("pytest_collectreport", "report.fspath == p"),
|
||||||
|
("pytest_collectreport", "report.fspath == topdir")
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_collect_protocol_method(self, testdir):
|
||||||
|
p = testdir.makepyfile("""
|
||||||
|
class TestClass:
|
||||||
|
def test_method(self):
|
||||||
|
pass
|
||||||
|
""")
|
||||||
|
normid = p.basename + "::TestClass::test_method"
|
||||||
|
for id in [p.basename,
|
||||||
|
p.basename + "::TestClass",
|
||||||
|
p.basename + "::TestClass::()",
|
||||||
|
p.basename + "::TestClass::()::test_method",
|
||||||
|
normid,
|
||||||
|
]:
|
||||||
|
config = testdir.parseconfig(id)
|
||||||
|
rcol = Collection(config=config)
|
||||||
|
nodes = rcol.perform_collect()
|
||||||
|
assert len(nodes) == 1
|
||||||
|
assert nodes[0].name == "test_method"
|
||||||
|
newid = rcol.getid(nodes[0])
|
||||||
|
assert newid == normid
|
||||||
|
|
||||||
|
def test_collect_custom_nodes_multi_id(self, testdir):
|
||||||
|
p = testdir.makepyfile("def test_func(): pass")
|
||||||
|
testdir.makeconftest("""
|
||||||
|
import py
|
||||||
|
class SpecialItem(py.test.collect.Item):
|
||||||
|
def runtest(self):
|
||||||
|
return # ok
|
||||||
|
class SpecialFile(py.test.collect.File):
|
||||||
|
def collect(self):
|
||||||
|
return [SpecialItem(name="check", parent=self)]
|
||||||
|
def pytest_collect_file(path, parent):
|
||||||
|
if path.basename == %r:
|
||||||
|
return SpecialFile(fspath=path, parent=parent)
|
||||||
|
""" % p.basename)
|
||||||
|
id = p.basename
|
||||||
|
|
||||||
|
config = testdir.parseconfig(id)
|
||||||
|
rcol = Collection(config)
|
||||||
|
hookrec = testdir.getreportrecorder(config)
|
||||||
|
items = rcol.perform_collect()
|
||||||
|
py.std.pprint.pprint(hookrec.hookrecorder.calls)
|
||||||
|
assert len(items) == 2
|
||||||
|
hookrec.hookrecorder.contains([
|
||||||
|
("pytest_collectstart",
|
||||||
|
"collector.fspath == collector.collection.topdir"),
|
||||||
|
("pytest_collectstart",
|
||||||
|
"collector.__class__.__name__ == 'SpecialFile'"),
|
||||||
|
("pytest_collectstart",
|
||||||
|
"collector.__class__.__name__ == 'Module'"),
|
||||||
|
("pytest_pycollect_makeitem", "name == 'test_func'"),
|
||||||
|
("pytest_collectreport", "report.fspath == p"),
|
||||||
|
("pytest_collectreport",
|
||||||
|
"report.fspath == %r" % str(rcol.topdir)),
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_collect_subdir_event_ordering(self, testdir):
|
||||||
|
p = testdir.makepyfile("def test_func(): pass")
|
||||||
|
aaa = testdir.mkpydir("aaa")
|
||||||
|
test_aaa = aaa.join("test_aaa.py")
|
||||||
|
p.move(test_aaa)
|
||||||
|
config = testdir.parseconfig()
|
||||||
|
rcol = Collection(config)
|
||||||
|
hookrec = testdir.getreportrecorder(config)
|
||||||
|
items = rcol.perform_collect()
|
||||||
|
assert len(items) == 1
|
||||||
|
py.std.pprint.pprint(hookrec.hookrecorder.calls)
|
||||||
|
hookrec.hookrecorder.contains([
|
||||||
|
("pytest_collectstart", "collector.fspath == aaa"),
|
||||||
|
("pytest_collectstart", "collector.fspath == test_aaa"),
|
||||||
|
("pytest_pycollect_makeitem", "name == 'test_func'"),
|
||||||
|
("pytest_collectreport", "report.fspath == test_aaa"),
|
||||||
|
("pytest_collectreport", "report.fspath == aaa"),
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_collect_two_commandline_args(self, testdir):
|
||||||
|
p = testdir.makepyfile("def test_func(): pass")
|
||||||
|
aaa = testdir.mkpydir("aaa")
|
||||||
|
bbb = testdir.mkpydir("bbb")
|
||||||
|
p.copy(aaa.join("test_aaa.py"))
|
||||||
|
p.move(bbb.join("test_bbb.py"))
|
||||||
|
|
||||||
|
id = "."
|
||||||
|
config = testdir.parseconfig(id)
|
||||||
|
rcol = Collection(config)
|
||||||
|
hookrec = testdir.getreportrecorder(config)
|
||||||
|
items = rcol.perform_collect()
|
||||||
|
assert len(items) == 2
|
||||||
|
py.std.pprint.pprint(hookrec.hookrecorder.calls)
|
||||||
|
hookrec.hookrecorder.contains([
|
||||||
|
("pytest_collectstart", "collector.fspath == aaa"),
|
||||||
|
("pytest_pycollect_makeitem", "name == 'test_func'"),
|
||||||
|
("pytest_collectreport", "report.fspath == aaa"),
|
||||||
|
("pytest_collectstart", "collector.fspath == bbb"),
|
||||||
|
("pytest_pycollect_makeitem", "name == 'test_func'"),
|
||||||
|
("pytest_collectreport", "report.fspath == bbb"),
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_serialization_byid(self, testdir):
|
||||||
|
p = testdir.makepyfile("def test_func(): pass")
|
||||||
|
config = testdir.parseconfig()
|
||||||
|
rcol = Collection(config)
|
||||||
|
items = rcol.perform_collect()
|
||||||
|
assert len(items) == 1
|
||||||
|
item, = items
|
||||||
|
id = rcol.getid(item)
|
||||||
|
newcol = Collection(config)
|
||||||
|
item2, = newcol.getbyid(id)
|
||||||
|
assert item2.name == item.name
|
||||||
|
assert item2.fspath == item.fspath
|
||||||
|
item2b, = newcol.getbyid(id)
|
||||||
|
assert item2b is item2
|
||||||
|
|
||||||
|
class Test_gettopdir:
|
||||||
|
def test_gettopdir(self, testdir):
|
||||||
|
tmp = testdir.tmpdir
|
||||||
|
assert gettopdir([tmp]) == tmp
|
||||||
|
topdir = gettopdir([tmp.join("hello"), tmp.join("world")])
|
||||||
|
assert topdir == tmp
|
||||||
|
somefile = tmp.ensure("somefile.py")
|
||||||
|
assert gettopdir([somefile]) == tmp
|
||||||
|
|
||||||
|
def test_gettopdir_pypkg(self, testdir):
|
||||||
|
tmp = testdir.tmpdir
|
||||||
|
a = tmp.ensure('a', dir=1)
|
||||||
|
b = tmp.ensure('a', 'b', '__init__.py')
|
||||||
|
c = tmp.ensure('a', 'b', 'c.py')
|
||||||
|
Z = tmp.ensure('Z', dir=1)
|
||||||
|
assert gettopdir([c]) == a
|
||||||
|
assert gettopdir([c, Z]) == tmp
|
||||||
|
assert gettopdir(["%s::xyc" % c]) == a
|
||||||
|
assert gettopdir(["%s::xyc::abc" % c]) == a
|
||||||
|
assert gettopdir(["%s::xyc" % c, "%s::abc" % Z]) == tmp
|
||||||
|
|
||||||
|
class Test_getinitialnodes:
|
||||||
|
def test_onedir(self, testdir):
|
||||||
|
config = testdir.reparseconfig([testdir.tmpdir])
|
||||||
|
colitems = Collection(config).getinitialnodes()
|
||||||
|
assert len(colitems) == 1
|
||||||
|
col = colitems[0]
|
||||||
|
assert isinstance(col, py.test.collect.Directory)
|
||||||
|
for col in col.listchain():
|
||||||
|
assert col.config is config
|
||||||
|
|
||||||
|
def test_twodirs(self, testdir, tmpdir):
|
||||||
|
config = testdir.reparseconfig([tmpdir, tmpdir])
|
||||||
|
colitems = Collection(config).getinitialnodes()
|
||||||
|
assert len(colitems) == 2
|
||||||
|
col1, col2 = colitems
|
||||||
|
assert col1.name == col2.name
|
||||||
|
assert col1.parent == col2.parent
|
||||||
|
|
||||||
|
def test_curdir_and_subdir(self, testdir, tmpdir):
|
||||||
|
a = tmpdir.ensure("a", dir=1)
|
||||||
|
config = testdir.reparseconfig([tmpdir, a])
|
||||||
|
colitems = Collection(config).getinitialnodes()
|
||||||
|
assert len(colitems) == 2
|
||||||
|
col1, col2 = colitems
|
||||||
|
assert col1.name == tmpdir.basename
|
||||||
|
assert col2.name == 'a'
|
||||||
|
for col in colitems:
|
||||||
|
for subcol in col.listchain():
|
||||||
|
assert col.config is config
|
||||||
|
|
||||||
|
def test_global_file(self, testdir, tmpdir):
|
||||||
|
x = tmpdir.ensure("x.py")
|
||||||
|
config = testdir.reparseconfig([x])
|
||||||
|
col, = Collection(config).getinitialnodes()
|
||||||
|
assert isinstance(col, py.test.collect.Module)
|
||||||
|
assert col.name == 'x.py'
|
||||||
|
assert col.parent.name == tmpdir.basename
|
||||||
|
assert col.parent.parent is None
|
||||||
|
for col in col.listchain():
|
||||||
|
assert col.config is config
|
||||||
|
|
||||||
|
def test_global_dir(self, testdir, tmpdir):
|
||||||
|
x = tmpdir.ensure("a", dir=1)
|
||||||
|
config = testdir.reparseconfig([x])
|
||||||
|
col, = Collection(config).getinitialnodes()
|
||||||
|
assert isinstance(col, py.test.collect.Directory)
|
||||||
|
print(col.listchain())
|
||||||
|
assert col.name == 'a'
|
||||||
|
assert col.parent is None
|
||||||
|
assert col.config is config
|
||||||
|
|
||||||
|
def test_pkgfile(self, testdir, tmpdir):
|
||||||
|
tmpdir = tmpdir.join("subdir")
|
||||||
|
x = tmpdir.ensure("x.py")
|
||||||
|
tmpdir.ensure("__init__.py")
|
||||||
|
config = testdir.reparseconfig([x])
|
||||||
|
col, = Collection(config).getinitialnodes()
|
||||||
|
assert isinstance(col, py.test.collect.Module)
|
||||||
|
assert col.name == 'x.py'
|
||||||
|
assert col.parent.name == x.dirpath().basename
|
||||||
|
assert col.parent.parent.parent is None
|
||||||
|
for col in col.listchain():
|
||||||
|
assert col.config is config
|
||||||
|
|
||||||
|
class Test_genitems:
|
||||||
|
def test_check_collect_hashes(self, testdir):
|
||||||
|
p = testdir.makepyfile("""
|
||||||
|
def test_1():
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_2():
|
||||||
|
pass
|
||||||
|
""")
|
||||||
|
p.copy(p.dirpath(p.purebasename + "2" + ".py"))
|
||||||
|
items, reprec = testdir.inline_genitems(p.dirpath())
|
||||||
|
assert len(items) == 4
|
||||||
|
for numi, i in enumerate(items):
|
||||||
|
for numj, j in enumerate(items):
|
||||||
|
if numj != numi:
|
||||||
|
assert hash(i) != hash(j)
|
||||||
|
assert i != j
|
||||||
|
|
||||||
|
def test_root_conftest_syntax_error(self, testdir):
|
||||||
|
# do we want to unify behaviour with
|
||||||
|
# test_subdir_conftest_error?
|
||||||
|
p = testdir.makepyfile(conftest="raise SyntaxError\n")
|
||||||
|
py.test.raises(SyntaxError, testdir.inline_genitems, p.dirpath())
|
||||||
|
|
||||||
|
def test_example_items1(self, testdir):
|
||||||
|
p = testdir.makepyfile('''
|
||||||
|
def testone():
|
||||||
|
pass
|
||||||
|
|
||||||
|
class TestX:
|
||||||
|
def testmethod_one(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class TestY(TestX):
|
||||||
|
pass
|
||||||
|
''')
|
||||||
|
items, reprec = testdir.inline_genitems(p)
|
||||||
|
assert len(items) == 3
|
||||||
|
assert items[0].name == 'testone'
|
||||||
|
assert items[1].name == 'testmethod_one'
|
||||||
|
assert items[2].name == 'testmethod_one'
|
||||||
|
|
||||||
|
# let's also test getmodpath here
|
||||||
|
assert items[0].getmodpath() == "testone"
|
||||||
|
assert items[1].getmodpath() == "TestX.testmethod_one"
|
||||||
|
assert items[2].getmodpath() == "TestY.testmethod_one"
|
||||||
|
|
||||||
|
s = items[0].getmodpath(stopatmodule=False)
|
||||||
|
assert s.endswith("test_example_items1.testone")
|
||||||
|
print(s)
|
|
@ -1,6 +1,4 @@
|
||||||
import py
|
import py
|
||||||
from py._test.collect import RootCollector
|
|
||||||
|
|
||||||
|
|
||||||
class TestConfigCmdlineParsing:
|
class TestConfigCmdlineParsing:
|
||||||
def test_parser_addoption_default_env(self, testdir, monkeypatch):
|
def test_parser_addoption_default_env(self, testdir, monkeypatch):
|
||||||
|
@ -106,104 +104,6 @@ class TestConfigAPI:
|
||||||
assert pl[0] == tmpdir
|
assert pl[0] == tmpdir
|
||||||
assert pl[1] == somepath
|
assert pl[1] == somepath
|
||||||
|
|
||||||
def test_setsessionclass_and_initsession(self, testdir):
|
|
||||||
config = testdir.Config()
|
|
||||||
class Session1:
|
|
||||||
def __init__(self, config):
|
|
||||||
self.config = config
|
|
||||||
config.setsessionclass(Session1)
|
|
||||||
session = config.initsession()
|
|
||||||
assert isinstance(session, Session1)
|
|
||||||
assert session.config is config
|
|
||||||
py.test.raises(ValueError, "config.setsessionclass(Session1)")
|
|
||||||
|
|
||||||
|
|
||||||
class TestConfigApi_getinitialnodes:
|
|
||||||
def test_onedir(self, testdir):
|
|
||||||
config = testdir.reparseconfig([testdir.tmpdir])
|
|
||||||
colitems = config.getinitialnodes()
|
|
||||||
assert len(colitems) == 1
|
|
||||||
col = colitems[0]
|
|
||||||
assert isinstance(col, py.test.collect.Directory)
|
|
||||||
for col in col.listchain():
|
|
||||||
assert col.config is config
|
|
||||||
|
|
||||||
def test_twodirs(self, testdir, tmpdir):
|
|
||||||
config = testdir.reparseconfig([tmpdir, tmpdir])
|
|
||||||
colitems = config.getinitialnodes()
|
|
||||||
assert len(colitems) == 2
|
|
||||||
col1, col2 = colitems
|
|
||||||
assert col1.name == col2.name
|
|
||||||
assert col1.parent == col2.parent
|
|
||||||
|
|
||||||
def test_curdir_and_subdir(self, testdir, tmpdir):
|
|
||||||
a = tmpdir.ensure("a", dir=1)
|
|
||||||
config = testdir.reparseconfig([tmpdir, a])
|
|
||||||
colitems = config.getinitialnodes()
|
|
||||||
assert len(colitems) == 2
|
|
||||||
col1, col2 = colitems
|
|
||||||
assert col1.name == tmpdir.basename
|
|
||||||
assert col2.name == 'a'
|
|
||||||
for col in colitems:
|
|
||||||
for subcol in col.listchain():
|
|
||||||
assert col.config is config
|
|
||||||
|
|
||||||
def test_global_file(self, testdir, tmpdir):
|
|
||||||
x = tmpdir.ensure("x.py")
|
|
||||||
config = testdir.reparseconfig([x])
|
|
||||||
col, = config.getinitialnodes()
|
|
||||||
assert isinstance(col, py.test.collect.Module)
|
|
||||||
assert col.name == 'x.py'
|
|
||||||
assert col.parent.name == tmpdir.basename
|
|
||||||
assert isinstance(col.parent.parent, RootCollector)
|
|
||||||
for col in col.listchain():
|
|
||||||
assert col.config is config
|
|
||||||
|
|
||||||
def test_global_dir(self, testdir, tmpdir):
|
|
||||||
x = tmpdir.ensure("a", dir=1)
|
|
||||||
config = testdir.reparseconfig([x])
|
|
||||||
col, = config.getinitialnodes()
|
|
||||||
assert isinstance(col, py.test.collect.Directory)
|
|
||||||
print(col.listchain())
|
|
||||||
assert col.name == 'a'
|
|
||||||
assert isinstance(col.parent, RootCollector)
|
|
||||||
assert col.config is config
|
|
||||||
|
|
||||||
def test_pkgfile(self, testdir, tmpdir):
|
|
||||||
tmpdir = tmpdir.join("subdir")
|
|
||||||
x = tmpdir.ensure("x.py")
|
|
||||||
tmpdir.ensure("__init__.py")
|
|
||||||
config = testdir.reparseconfig([x])
|
|
||||||
col, = config.getinitialnodes()
|
|
||||||
assert isinstance(col, py.test.collect.Module)
|
|
||||||
assert col.name == 'x.py'
|
|
||||||
assert col.parent.name == x.dirpath().basename
|
|
||||||
assert isinstance(col.parent.parent.parent, RootCollector)
|
|
||||||
for col in col.listchain():
|
|
||||||
assert col.config is config
|
|
||||||
|
|
||||||
class TestConfig_gettopdir:
|
|
||||||
def test_gettopdir(self, testdir):
|
|
||||||
from py._test.config import gettopdir
|
|
||||||
tmp = testdir.tmpdir
|
|
||||||
assert gettopdir([tmp]) == tmp
|
|
||||||
topdir = gettopdir([tmp.join("hello"), tmp.join("world")])
|
|
||||||
assert topdir == tmp
|
|
||||||
somefile = tmp.ensure("somefile.py")
|
|
||||||
assert gettopdir([somefile]) == tmp
|
|
||||||
|
|
||||||
def test_gettopdir_pypkg(self, testdir):
|
|
||||||
from py._test.config import gettopdir
|
|
||||||
tmp = testdir.tmpdir
|
|
||||||
a = tmp.ensure('a', dir=1)
|
|
||||||
b = tmp.ensure('a', 'b', '__init__.py')
|
|
||||||
c = tmp.ensure('a', 'b', 'c.py')
|
|
||||||
Z = tmp.ensure('Z', dir=1)
|
|
||||||
assert gettopdir([c]) == a
|
|
||||||
assert gettopdir([c, Z]) == tmp
|
|
||||||
assert gettopdir(["%s::xyc" % c]) == a
|
|
||||||
assert gettopdir(["%s::xyc::abc" % c]) == a
|
|
||||||
assert gettopdir(["%s::xyc" % c, "%s::abc" % Z]) == tmp
|
|
||||||
|
|
||||||
def test_options_on_small_file_do_not_blow_up(testdir):
|
def test_options_on_small_file_do_not_blow_up(testdir):
|
||||||
def runfiletest(opts):
|
def runfiletest(opts):
|
||||||
|
@ -247,133 +147,3 @@ def test_preparse_ordering(testdir, monkeypatch):
|
||||||
config = testdir.parseconfig()
|
config = testdir.parseconfig()
|
||||||
plugin = config.pluginmanager.getplugin("mytestplugin")
|
plugin = config.pluginmanager.getplugin("mytestplugin")
|
||||||
assert plugin.x == 42
|
assert plugin.x == 42
|
||||||
|
|
||||||
|
|
||||||
import pickle
|
|
||||||
class TestConfigPickling:
|
|
||||||
def pytest_funcarg__testdir(self, request):
|
|
||||||
oldconfig = py.test.config
|
|
||||||
print("setting py.test.config to None")
|
|
||||||
py.test.config = None
|
|
||||||
def resetglobals():
|
|
||||||
py.builtin.print_("setting py.test.config to", oldconfig)
|
|
||||||
py.test.config = oldconfig
|
|
||||||
request.addfinalizer(resetglobals)
|
|
||||||
return request.getfuncargvalue("testdir")
|
|
||||||
|
|
||||||
def test_config_getstate_setstate(self, testdir):
|
|
||||||
from py._test.config import Config
|
|
||||||
testdir.makepyfile(__init__="", conftest="x=1; y=2")
|
|
||||||
hello = testdir.makepyfile(hello="")
|
|
||||||
tmp = testdir.tmpdir
|
|
||||||
testdir.chdir()
|
|
||||||
config1 = testdir.parseconfig(hello)
|
|
||||||
config2 = Config()
|
|
||||||
config2.__setstate__(config1.__getstate__())
|
|
||||||
assert config2.topdir == py.path.local()
|
|
||||||
config2_relpaths = [py.path.local(x).relto(config2.topdir)
|
|
||||||
for x in config2.args]
|
|
||||||
config1_relpaths = [py.path.local(x).relto(config1.topdir)
|
|
||||||
for x in config1.args]
|
|
||||||
|
|
||||||
assert config2_relpaths == config1_relpaths
|
|
||||||
for name, value in config1.option.__dict__.items():
|
|
||||||
assert getattr(config2.option, name) == value
|
|
||||||
assert config2.getvalue("x") == 1
|
|
||||||
|
|
||||||
def test_config_pickling_customoption(self, testdir):
|
|
||||||
testdir.makeconftest("""
|
|
||||||
def pytest_addoption(parser):
|
|
||||||
group = parser.getgroup("testing group")
|
|
||||||
group.addoption('-G', '--glong', action="store", default=42,
|
|
||||||
type="int", dest="gdest", help="g value.")
|
|
||||||
""")
|
|
||||||
config = testdir.parseconfig("-G", "11")
|
|
||||||
assert config.option.gdest == 11
|
|
||||||
repr = config.__getstate__()
|
|
||||||
|
|
||||||
config = testdir.Config()
|
|
||||||
py.test.raises(AttributeError, "config.option.gdest")
|
|
||||||
|
|
||||||
config2 = testdir.Config()
|
|
||||||
config2.__setstate__(repr)
|
|
||||||
assert config2.option.gdest == 11
|
|
||||||
|
|
||||||
def test_config_pickling_and_conftest_deprecated(self, testdir):
|
|
||||||
tmp = testdir.tmpdir.ensure("w1", "w2", dir=1)
|
|
||||||
tmp.ensure("__init__.py")
|
|
||||||
tmp.join("conftest.py").write(py.code.Source("""
|
|
||||||
def pytest_addoption(parser):
|
|
||||||
group = parser.getgroup("testing group")
|
|
||||||
group.addoption('-G', '--glong', action="store", default=42,
|
|
||||||
type="int", dest="gdest", help="g value.")
|
|
||||||
"""))
|
|
||||||
config = testdir.parseconfig(tmp, "-G", "11")
|
|
||||||
assert config.option.gdest == 11
|
|
||||||
repr = config.__getstate__()
|
|
||||||
|
|
||||||
config = testdir.Config()
|
|
||||||
py.test.raises(AttributeError, "config.option.gdest")
|
|
||||||
|
|
||||||
config2 = testdir.Config()
|
|
||||||
config2.__setstate__(repr)
|
|
||||||
assert config2.option.gdest == 11
|
|
||||||
|
|
||||||
option = config2.addoptions("testing group",
|
|
||||||
config2.Option('-G', '--glong', action="store", default=42,
|
|
||||||
type="int", dest="gdest", help="g value."))
|
|
||||||
assert option.gdest == 11
|
|
||||||
|
|
||||||
def test_config_picklability(self, testdir):
|
|
||||||
config = testdir.parseconfig()
|
|
||||||
s = pickle.dumps(config)
|
|
||||||
newconfig = pickle.loads(s)
|
|
||||||
assert hasattr(newconfig, "topdir")
|
|
||||||
assert newconfig.topdir == py.path.local()
|
|
||||||
|
|
||||||
def test_collector_implicit_config_pickling(self, testdir):
|
|
||||||
tmpdir = testdir.tmpdir
|
|
||||||
testdir.chdir()
|
|
||||||
testdir.makepyfile(hello="def test_x(): pass")
|
|
||||||
config = testdir.parseconfig(tmpdir)
|
|
||||||
col = config.getnode(config.topdir)
|
|
||||||
io = py.io.BytesIO()
|
|
||||||
pickler = pickle.Pickler(io)
|
|
||||||
pickler.dump(col)
|
|
||||||
io.seek(0)
|
|
||||||
unpickler = pickle.Unpickler(io)
|
|
||||||
col2 = unpickler.load()
|
|
||||||
assert col2.name == col.name
|
|
||||||
assert col2.listnames() == col.listnames()
|
|
||||||
|
|
||||||
def test_config_and_collector_pickling(self, testdir):
|
|
||||||
tmpdir = testdir.tmpdir
|
|
||||||
dir1 = tmpdir.ensure("sourcedir", "somedir", dir=1)
|
|
||||||
config = testdir.parseconfig()
|
|
||||||
assert config.topdir == tmpdir
|
|
||||||
col = config.getnode(dir1.dirpath())
|
|
||||||
col1 = config.getnode(dir1)
|
|
||||||
assert col1.parent == col
|
|
||||||
io = py.io.BytesIO()
|
|
||||||
pickler = pickle.Pickler(io)
|
|
||||||
pickler.dump(col)
|
|
||||||
pickler.dump(col1)
|
|
||||||
pickler.dump(col)
|
|
||||||
io.seek(0)
|
|
||||||
unpickler = pickle.Unpickler(io)
|
|
||||||
newtopdir = tmpdir.ensure("newtopdir", dir=1)
|
|
||||||
newtopdir.mkdir("sourcedir").mkdir("somedir")
|
|
||||||
old = newtopdir.chdir()
|
|
||||||
try:
|
|
||||||
newcol = unpickler.load()
|
|
||||||
newcol2 = unpickler.load()
|
|
||||||
newcol3 = unpickler.load()
|
|
||||||
assert newcol2.config is newcol.config
|
|
||||||
assert newcol2.parent == newcol
|
|
||||||
assert newcol2.config.topdir.realpath() == newtopdir.realpath()
|
|
||||||
newsourcedir = newtopdir.join("sourcedir")
|
|
||||||
assert newcol.fspath.realpath() == newsourcedir.realpath()
|
|
||||||
assert newcol2.fspath.basename == dir1.basename
|
|
||||||
assert newcol2.fspath.relto(newcol2.config.topdir)
|
|
||||||
finally:
|
|
||||||
old.chdir()
|
|
||||||
|
|
|
@ -82,10 +82,10 @@ class TestConftestValueAccessGlobal:
|
||||||
#conftest.lget("b") == 1
|
#conftest.lget("b") == 1
|
||||||
|
|
||||||
def test_value_access_with_confmod(self, basedir):
|
def test_value_access_with_confmod(self, basedir):
|
||||||
topdir = basedir.join("adir", "b")
|
startdir = basedir.join("adir", "b")
|
||||||
topdir.ensure("xx", dir=True)
|
startdir.ensure("xx", dir=True)
|
||||||
conftest = ConftestWithSetinitial(topdir)
|
conftest = ConftestWithSetinitial(startdir)
|
||||||
mod, value = conftest.rget_with_confmod("a", topdir)
|
mod, value = conftest.rget_with_confmod("a", startdir)
|
||||||
assert value == 1.5
|
assert value == 1.5
|
||||||
path = py.path.local(mod.__file__)
|
path = py.path.local(mod.__file__)
|
||||||
assert path.dirpath() == basedir.join("adir", "b")
|
assert path.dirpath() == basedir.join("adir", "b")
|
||||||
|
|
|
@ -49,7 +49,7 @@ class TestCollectDeprecated:
|
||||||
def check2(self): pass
|
def check2(self): pass
|
||||||
"""))
|
"""))
|
||||||
config = testdir.parseconfig(somefile)
|
config = testdir.parseconfig(somefile)
|
||||||
dirnode = config.getnode(somefile.dirpath())
|
dirnode = testdir.getnode(config, somefile.dirpath())
|
||||||
colitems = dirnode.collect()
|
colitems = dirnode.collect()
|
||||||
w = recwarn.pop(DeprecationWarning)
|
w = recwarn.pop(DeprecationWarning)
|
||||||
assert w.filename.find("conftest.py") != -1
|
assert w.filename.find("conftest.py") != -1
|
||||||
|
@ -171,9 +171,12 @@ class TestCollectDeprecated:
|
||||||
return Module(path, parent=self)
|
return Module(path, parent=self)
|
||||||
return super(Directory, self).consider_file(path)
|
return super(Directory, self).consider_file(path)
|
||||||
""")
|
""")
|
||||||
|
#def pytest_collect_file(path, parent):
|
||||||
|
# if path.basename == "testme.xxx":
|
||||||
|
# return Module(path, parent=parent)
|
||||||
testme = testdir.makefile('xxx', testme="hello")
|
testme = testdir.makefile('xxx', testme="hello")
|
||||||
config = testdir.parseconfig(testme)
|
config = testdir.parseconfig(testme)
|
||||||
col = config.getnode(testme)
|
col = testdir.getnode(config, testme)
|
||||||
assert col.collect() == []
|
assert col.collect() == []
|
||||||
|
|
||||||
|
|
||||||
|
@ -219,7 +222,7 @@ class TestDisabled:
|
||||||
""")
|
""")
|
||||||
reprec.assertoutcome(skipped=2)
|
reprec.assertoutcome(skipped=2)
|
||||||
|
|
||||||
@py.test.mark.multi(name="Directory Module Class Function".split())
|
@py.test.mark.multi(name="Module Class Function".split())
|
||||||
def test_function_deprecated_run_execute(self, name, testdir, recwarn):
|
def test_function_deprecated_run_execute(self, name, testdir, recwarn):
|
||||||
testdir.makeconftest("""
|
testdir.makeconftest("""
|
||||||
import py
|
import py
|
||||||
|
@ -235,11 +238,11 @@ class TestDisabled:
|
||||||
""")
|
""")
|
||||||
config = testdir.parseconfig()
|
config = testdir.parseconfig()
|
||||||
if name == "Directory":
|
if name == "Directory":
|
||||||
config.getnode(testdir.tmpdir)
|
testdir.getnode(config, testdir.tmpdir)
|
||||||
elif name in ("Module", "File"):
|
elif name in ("Module", "File"):
|
||||||
config.getnode(p)
|
testdir.getnode(config, p)
|
||||||
else:
|
else:
|
||||||
fnode = config.getnode(p)
|
fnode = testdir.getnode(config, p)
|
||||||
recwarn.clear()
|
recwarn.clear()
|
||||||
fnode.collect()
|
fnode.collect()
|
||||||
w = recwarn.pop(DeprecationWarning)
|
w = recwarn.pop(DeprecationWarning)
|
||||||
|
@ -278,9 +281,10 @@ def test_conftest_non_python_items(recwarn, testdir):
|
||||||
checkfile = testdir.makefile(ext="xxx", hello="world")
|
checkfile = testdir.makefile(ext="xxx", hello="world")
|
||||||
testdir.makepyfile(x="")
|
testdir.makepyfile(x="")
|
||||||
testdir.maketxtfile(x="")
|
testdir.maketxtfile(x="")
|
||||||
config = testdir.parseconfig()
|
|
||||||
recwarn.clear()
|
recwarn.clear()
|
||||||
dircol = config.getnode(checkfile.dirpath())
|
config = testdir.parseconfig()
|
||||||
|
dircol = testdir.getnode(config, checkfile.dirpath())
|
||||||
|
|
||||||
w = recwarn.pop(DeprecationWarning)
|
w = recwarn.pop(DeprecationWarning)
|
||||||
assert str(w.message).find("conftest.py") != -1
|
assert str(w.message).find("conftest.py") != -1
|
||||||
colitems = dircol.collect()
|
colitems = dircol.collect()
|
||||||
|
@ -288,7 +292,7 @@ def test_conftest_non_python_items(recwarn, testdir):
|
||||||
assert colitems[0].name == "hello.xxx"
|
assert colitems[0].name == "hello.xxx"
|
||||||
assert colitems[0].__class__.__name__ == "CustomItem"
|
assert colitems[0].__class__.__name__ == "CustomItem"
|
||||||
|
|
||||||
item = config.getnode(checkfile)
|
item = testdir.getnode(config, checkfile)
|
||||||
assert item.name == "hello.xxx"
|
assert item.name == "hello.xxx"
|
||||||
assert item.__class__.__name__ == "CustomItem"
|
assert item.__class__.__name__ == "CustomItem"
|
||||||
|
|
||||||
|
@ -321,14 +325,14 @@ def test_extra_python_files_and_functions(testdir, recwarn):
|
||||||
""")
|
""")
|
||||||
# check that directory collects "check_" files
|
# check that directory collects "check_" files
|
||||||
config = testdir.parseconfig()
|
config = testdir.parseconfig()
|
||||||
col = config.getnode(checkfile.dirpath())
|
col = testdir.getnode(config, checkfile.dirpath())
|
||||||
colitems = col.collect()
|
colitems = col.collect()
|
||||||
assert len(colitems) == 1
|
assert len(colitems) == 1
|
||||||
assert isinstance(colitems[0], py.test.collect.Module)
|
assert isinstance(colitems[0], py.test.collect.Module)
|
||||||
|
|
||||||
# check that module collects "check_" functions and methods
|
# check that module collects "check_" functions and methods
|
||||||
config = testdir.parseconfig(checkfile)
|
config = testdir.parseconfig(checkfile)
|
||||||
col = config.getnode(checkfile)
|
col = testdir.getnode(config, checkfile)
|
||||||
assert isinstance(col, py.test.collect.Module)
|
assert isinstance(col, py.test.collect.Module)
|
||||||
colitems = col.collect()
|
colitems = col.collect()
|
||||||
assert len(colitems) == 2
|
assert len(colitems) == 2
|
||||||
|
|
|
@ -160,6 +160,19 @@ class TestBootstrapping:
|
||||||
pp.unregister(a2)
|
pp.unregister(a2)
|
||||||
assert not pp.isregistered(a2)
|
assert not pp.isregistered(a2)
|
||||||
|
|
||||||
|
def test_registry_ordering(self):
|
||||||
|
pp = PluginManager()
|
||||||
|
class A: pass
|
||||||
|
a1, a2 = A(), A()
|
||||||
|
pp.register(a1)
|
||||||
|
pp.register(a2, "hello")
|
||||||
|
l = pp.getplugins()
|
||||||
|
assert l.index(a1) < l.index(a2)
|
||||||
|
a3 = A()
|
||||||
|
pp.register(a3, prepend=True)
|
||||||
|
l = pp.getplugins()
|
||||||
|
assert l.index(a3) == 0
|
||||||
|
|
||||||
def test_register_imported_modules(self):
|
def test_register_imported_modules(self):
|
||||||
pp = PluginManager()
|
pp = PluginManager()
|
||||||
mod = py.std.types.ModuleType("x.y.pytest_hello")
|
mod = py.std.types.ModuleType("x.y.pytest_hello")
|
||||||
|
@ -340,8 +353,12 @@ def test_varnames():
|
||||||
class A:
|
class A:
|
||||||
def f(self, y):
|
def f(self, y):
|
||||||
pass
|
pass
|
||||||
|
class B(object):
|
||||||
|
def __call__(self, z):
|
||||||
|
pass
|
||||||
assert varnames(f) == ("x",)
|
assert varnames(f) == ("x",)
|
||||||
assert varnames(A().f) == ('y',)
|
assert varnames(A().f) == ('y',)
|
||||||
|
assert varnames(B()) == ('z',)
|
||||||
|
|
||||||
class TestMultiCall:
|
class TestMultiCall:
|
||||||
def test_uses_copy_of_methods(self):
|
def test_uses_copy_of_methods(self):
|
||||||
|
|
|
@ -1,516 +0,0 @@
|
||||||
import py
|
|
||||||
|
|
||||||
class TestModule:
|
|
||||||
def test_module_file_not_found(self, testdir):
|
|
||||||
tmpdir = testdir.tmpdir
|
|
||||||
fn = tmpdir.join('nada','no')
|
|
||||||
col = py.test.collect.Module(fn, config=testdir.Config())
|
|
||||||
col.config = testdir.parseconfig(tmpdir)
|
|
||||||
py.test.raises(py.error.ENOENT, col.collect)
|
|
||||||
|
|
||||||
def test_failing_import(self, testdir):
|
|
||||||
modcol = testdir.getmodulecol("import alksdjalskdjalkjals")
|
|
||||||
py.test.raises(ImportError, modcol.collect)
|
|
||||||
py.test.raises(ImportError, modcol.collect)
|
|
||||||
py.test.raises(ImportError, modcol.run)
|
|
||||||
|
|
||||||
def test_import_duplicate(self, testdir):
|
|
||||||
a = testdir.mkdir("a")
|
|
||||||
b = testdir.mkdir("b")
|
|
||||||
p = a.ensure("test_whatever.py")
|
|
||||||
p.pyimport()
|
|
||||||
del py.std.sys.modules['test_whatever']
|
|
||||||
b.ensure("test_whatever.py")
|
|
||||||
result = testdir.runpytest()
|
|
||||||
result.stdout.fnmatch_lines([
|
|
||||||
"*import*mismatch*",
|
|
||||||
"*imported*test_whatever*",
|
|
||||||
"*%s*" % a.join("test_whatever.py"),
|
|
||||||
"*not the same*",
|
|
||||||
"*%s*" % b.join("test_whatever.py"),
|
|
||||||
"*HINT*",
|
|
||||||
])
|
|
||||||
|
|
||||||
def test_syntax_error_in_module(self, testdir):
|
|
||||||
modcol = testdir.getmodulecol("this is a syntax error")
|
|
||||||
py.test.raises(modcol.CollectError, modcol.collect)
|
|
||||||
py.test.raises(modcol.CollectError, modcol.collect)
|
|
||||||
py.test.raises(modcol.CollectError, modcol.run)
|
|
||||||
|
|
||||||
def test_module_considers_pluginmanager_at_import(self, testdir):
|
|
||||||
modcol = testdir.getmodulecol("pytest_plugins='xasdlkj',")
|
|
||||||
py.test.raises(ImportError, "modcol.obj")
|
|
||||||
|
|
||||||
class TestClass:
|
|
||||||
def test_class_with_init_not_collected(self, testdir):
|
|
||||||
modcol = testdir.getmodulecol("""
|
|
||||||
class TestClass1:
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
class TestClass2(object):
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
""")
|
|
||||||
l = modcol.collect()
|
|
||||||
assert len(l) == 0
|
|
||||||
|
|
||||||
if py.std.sys.version_info > (3, 0):
|
|
||||||
_func_name_attr = "__name__"
|
|
||||||
else:
|
|
||||||
_func_name_attr = "func_name"
|
|
||||||
|
|
||||||
class TestGenerator:
|
|
||||||
def test_generative_functions(self, testdir):
|
|
||||||
modcol = testdir.getmodulecol("""
|
|
||||||
def func1(arg, arg2):
|
|
||||||
assert arg == arg2
|
|
||||||
|
|
||||||
def test_gen():
|
|
||||||
yield func1, 17, 3*5
|
|
||||||
yield func1, 42, 6*7
|
|
||||||
""")
|
|
||||||
colitems = modcol.collect()
|
|
||||||
assert len(colitems) == 1
|
|
||||||
gencol = colitems[0]
|
|
||||||
assert isinstance(gencol, py.test.collect.Generator)
|
|
||||||
gencolitems = gencol.collect()
|
|
||||||
assert len(gencolitems) == 2
|
|
||||||
assert isinstance(gencolitems[0], py.test.collect.Function)
|
|
||||||
assert isinstance(gencolitems[1], py.test.collect.Function)
|
|
||||||
assert gencolitems[0].name == '[0]'
|
|
||||||
assert getattr(gencolitems[0].obj, _func_name_attr) == 'func1'
|
|
||||||
|
|
||||||
def test_generative_methods(self, testdir):
|
|
||||||
modcol = testdir.getmodulecol("""
|
|
||||||
def func1(arg, arg2):
|
|
||||||
assert arg == arg2
|
|
||||||
class TestGenMethods:
|
|
||||||
def test_gen(self):
|
|
||||||
yield func1, 17, 3*5
|
|
||||||
yield func1, 42, 6*7
|
|
||||||
""")
|
|
||||||
gencol = modcol.collect()[0].collect()[0].collect()[0]
|
|
||||||
assert isinstance(gencol, py.test.collect.Generator)
|
|
||||||
gencolitems = gencol.collect()
|
|
||||||
assert len(gencolitems) == 2
|
|
||||||
assert isinstance(gencolitems[0], py.test.collect.Function)
|
|
||||||
assert isinstance(gencolitems[1], py.test.collect.Function)
|
|
||||||
assert gencolitems[0].name == '[0]'
|
|
||||||
assert getattr(gencolitems[0].obj, _func_name_attr) == 'func1'
|
|
||||||
|
|
||||||
def test_generative_functions_with_explicit_names(self, testdir):
|
|
||||||
modcol = testdir.getmodulecol("""
|
|
||||||
def func1(arg, arg2):
|
|
||||||
assert arg == arg2
|
|
||||||
|
|
||||||
def test_gen():
|
|
||||||
yield "seventeen", func1, 17, 3*5
|
|
||||||
yield "fortytwo", func1, 42, 6*7
|
|
||||||
""")
|
|
||||||
colitems = modcol.collect()
|
|
||||||
assert len(colitems) == 1
|
|
||||||
gencol = colitems[0]
|
|
||||||
assert isinstance(gencol, py.test.collect.Generator)
|
|
||||||
gencolitems = gencol.collect()
|
|
||||||
assert len(gencolitems) == 2
|
|
||||||
assert isinstance(gencolitems[0], py.test.collect.Function)
|
|
||||||
assert isinstance(gencolitems[1], py.test.collect.Function)
|
|
||||||
assert gencolitems[0].name == "['seventeen']"
|
|
||||||
assert getattr(gencolitems[0].obj, _func_name_attr) == 'func1'
|
|
||||||
assert gencolitems[1].name == "['fortytwo']"
|
|
||||||
assert getattr(gencolitems[1].obj, _func_name_attr) == 'func1'
|
|
||||||
|
|
||||||
def test_generative_functions_unique_explicit_names(self, testdir):
|
|
||||||
# generative
|
|
||||||
modcol = testdir.getmodulecol("""
|
|
||||||
def func(): pass
|
|
||||||
def test_gen():
|
|
||||||
yield "name", func
|
|
||||||
yield "name", func
|
|
||||||
""")
|
|
||||||
colitems = modcol.collect()
|
|
||||||
assert len(colitems) == 1
|
|
||||||
gencol = colitems[0]
|
|
||||||
assert isinstance(gencol, py.test.collect.Generator)
|
|
||||||
py.test.raises(ValueError, "gencol.collect()")
|
|
||||||
|
|
||||||
def test_generative_methods_with_explicit_names(self, testdir):
|
|
||||||
modcol = testdir.getmodulecol("""
|
|
||||||
def func1(arg, arg2):
|
|
||||||
assert arg == arg2
|
|
||||||
class TestGenMethods:
|
|
||||||
def test_gen(self):
|
|
||||||
yield "m1", func1, 17, 3*5
|
|
||||||
yield "m2", func1, 42, 6*7
|
|
||||||
""")
|
|
||||||
gencol = modcol.collect()[0].collect()[0].collect()[0]
|
|
||||||
assert isinstance(gencol, py.test.collect.Generator)
|
|
||||||
gencolitems = gencol.collect()
|
|
||||||
assert len(gencolitems) == 2
|
|
||||||
assert isinstance(gencolitems[0], py.test.collect.Function)
|
|
||||||
assert isinstance(gencolitems[1], py.test.collect.Function)
|
|
||||||
assert gencolitems[0].name == "['m1']"
|
|
||||||
assert getattr(gencolitems[0].obj, _func_name_attr) == 'func1'
|
|
||||||
assert gencolitems[1].name == "['m2']"
|
|
||||||
assert getattr(gencolitems[1].obj, _func_name_attr) == 'func1'
|
|
||||||
|
|
||||||
def test_order_of_execution_generator_same_codeline(self, testdir, tmpdir):
|
|
||||||
o = testdir.makepyfile("""
|
|
||||||
def test_generative_order_of_execution():
|
|
||||||
import py
|
|
||||||
test_list = []
|
|
||||||
expected_list = list(range(6))
|
|
||||||
|
|
||||||
def list_append(item):
|
|
||||||
test_list.append(item)
|
|
||||||
|
|
||||||
def assert_order_of_execution():
|
|
||||||
py.builtin.print_('expected order', expected_list)
|
|
||||||
py.builtin.print_('but got ', test_list)
|
|
||||||
assert test_list == expected_list
|
|
||||||
|
|
||||||
for i in expected_list:
|
|
||||||
yield list_append, i
|
|
||||||
yield assert_order_of_execution
|
|
||||||
""")
|
|
||||||
reprec = testdir.inline_run(o)
|
|
||||||
passed, skipped, failed = reprec.countoutcomes()
|
|
||||||
assert passed == 7
|
|
||||||
assert not skipped and not failed
|
|
||||||
|
|
||||||
def test_order_of_execution_generator_different_codeline(self, testdir):
|
|
||||||
o = testdir.makepyfile("""
|
|
||||||
def test_generative_tests_different_codeline():
|
|
||||||
import py
|
|
||||||
test_list = []
|
|
||||||
expected_list = list(range(3))
|
|
||||||
|
|
||||||
def list_append_2():
|
|
||||||
test_list.append(2)
|
|
||||||
|
|
||||||
def list_append_1():
|
|
||||||
test_list.append(1)
|
|
||||||
|
|
||||||
def list_append_0():
|
|
||||||
test_list.append(0)
|
|
||||||
|
|
||||||
def assert_order_of_execution():
|
|
||||||
py.builtin.print_('expected order', expected_list)
|
|
||||||
py.builtin.print_('but got ', test_list)
|
|
||||||
assert test_list == expected_list
|
|
||||||
|
|
||||||
yield list_append_0
|
|
||||||
yield list_append_1
|
|
||||||
yield list_append_2
|
|
||||||
yield assert_order_of_execution
|
|
||||||
""")
|
|
||||||
reprec = testdir.inline_run(o)
|
|
||||||
passed, skipped, failed = reprec.countoutcomes()
|
|
||||||
assert passed == 4
|
|
||||||
assert not skipped and not failed
|
|
||||||
|
|
||||||
class TestFunction:
|
|
||||||
def test_getmodulecollector(self, testdir):
|
|
||||||
item = testdir.getitem("def test_func(): pass")
|
|
||||||
modcol = item.getparent(py.test.collect.Module)
|
|
||||||
assert isinstance(modcol, py.test.collect.Module)
|
|
||||||
assert hasattr(modcol.obj, 'test_func')
|
|
||||||
|
|
||||||
def test_function_equality(self, testdir, tmpdir):
|
|
||||||
config = testdir.reparseconfig()
|
|
||||||
f1 = py.test.collect.Function(name="name", config=config,
|
|
||||||
args=(1,), callobj=isinstance)
|
|
||||||
f2 = py.test.collect.Function(name="name",config=config,
|
|
||||||
args=(1,), callobj=py.builtin.callable)
|
|
||||||
assert not f1 == f2
|
|
||||||
assert f1 != f2
|
|
||||||
f3 = py.test.collect.Function(name="name", config=config,
|
|
||||||
args=(1,2), callobj=py.builtin.callable)
|
|
||||||
assert not f3 == f2
|
|
||||||
assert f3 != f2
|
|
||||||
|
|
||||||
assert not f3 == f1
|
|
||||||
assert f3 != f1
|
|
||||||
|
|
||||||
f1_b = py.test.collect.Function(name="name", config=config,
|
|
||||||
args=(1,), callobj=isinstance)
|
|
||||||
assert f1 == f1_b
|
|
||||||
assert not f1 != f1_b
|
|
||||||
|
|
||||||
def test_function_equality_with_callspec(self, testdir, tmpdir):
|
|
||||||
config = testdir.reparseconfig()
|
|
||||||
class callspec1:
|
|
||||||
param = 1
|
|
||||||
funcargs = {}
|
|
||||||
id = "hello"
|
|
||||||
class callspec2:
|
|
||||||
param = 1
|
|
||||||
funcargs = {}
|
|
||||||
id = "world"
|
|
||||||
f5 = py.test.collect.Function(name="name", config=config,
|
|
||||||
callspec=callspec1, callobj=isinstance)
|
|
||||||
f5b = py.test.collect.Function(name="name", config=config,
|
|
||||||
callspec=callspec2, callobj=isinstance)
|
|
||||||
assert f5 != f5b
|
|
||||||
assert not (f5 == f5b)
|
|
||||||
|
|
||||||
def test_pyfunc_call(self, testdir):
|
|
||||||
item = testdir.getitem("def test_func(): raise ValueError")
|
|
||||||
config = item.config
|
|
||||||
class MyPlugin1:
|
|
||||||
def pytest_pyfunc_call(self, pyfuncitem):
|
|
||||||
raise ValueError
|
|
||||||
class MyPlugin2:
|
|
||||||
def pytest_pyfunc_call(self, pyfuncitem):
|
|
||||||
return True
|
|
||||||
config.pluginmanager.register(MyPlugin1())
|
|
||||||
config.pluginmanager.register(MyPlugin2())
|
|
||||||
config.hook.pytest_pyfunc_call(pyfuncitem=item)
|
|
||||||
|
|
||||||
class TestSorting:
|
|
||||||
def test_check_equality(self, testdir):
|
|
||||||
modcol = testdir.getmodulecol("""
|
|
||||||
def test_pass(): pass
|
|
||||||
def test_fail(): assert 0
|
|
||||||
""")
|
|
||||||
fn1 = modcol.collect_by_name("test_pass")
|
|
||||||
assert isinstance(fn1, py.test.collect.Function)
|
|
||||||
fn2 = modcol.collect_by_name("test_pass")
|
|
||||||
assert isinstance(fn2, py.test.collect.Function)
|
|
||||||
|
|
||||||
assert fn1 == fn2
|
|
||||||
assert fn1 != modcol
|
|
||||||
if py.std.sys.version_info < (3, 0):
|
|
||||||
assert cmp(fn1, fn2) == 0
|
|
||||||
assert hash(fn1) == hash(fn2)
|
|
||||||
|
|
||||||
fn3 = modcol.collect_by_name("test_fail")
|
|
||||||
assert isinstance(fn3, py.test.collect.Function)
|
|
||||||
assert not (fn1 == fn3)
|
|
||||||
assert fn1 != fn3
|
|
||||||
|
|
||||||
for fn in fn1,fn2,fn3:
|
|
||||||
assert fn != 3
|
|
||||||
assert fn != modcol
|
|
||||||
assert fn != [1,2,3]
|
|
||||||
assert [1,2,3] != fn
|
|
||||||
assert modcol != fn
|
|
||||||
|
|
||||||
def test_allow_sane_sorting_for_decorators(self, testdir):
|
|
||||||
modcol = testdir.getmodulecol("""
|
|
||||||
def dec(f):
|
|
||||||
g = lambda: f(2)
|
|
||||||
g.place_as = f
|
|
||||||
return g
|
|
||||||
|
|
||||||
|
|
||||||
def test_b(y):
|
|
||||||
pass
|
|
||||||
test_b = dec(test_b)
|
|
||||||
|
|
||||||
def test_a(y):
|
|
||||||
pass
|
|
||||||
test_a = dec(test_a)
|
|
||||||
""")
|
|
||||||
colitems = modcol.collect()
|
|
||||||
assert len(colitems) == 2
|
|
||||||
assert [item.name for item in colitems] == ['test_b', 'test_a']
|
|
||||||
|
|
||||||
|
|
||||||
class TestConftestCustomization:
|
|
||||||
def test_pytest_pycollect_module(self, testdir):
|
|
||||||
testdir.makeconftest("""
|
|
||||||
import py
|
|
||||||
class MyModule(py.test.collect.Module):
|
|
||||||
pass
|
|
||||||
def pytest_pycollect_makemodule(path, parent):
|
|
||||||
if path.basename == "test_xyz.py":
|
|
||||||
return MyModule(path, parent)
|
|
||||||
""")
|
|
||||||
testdir.makepyfile("def some(): pass")
|
|
||||||
testdir.makepyfile(test_xyz="")
|
|
||||||
result = testdir.runpytest("--collectonly")
|
|
||||||
result.stdout.fnmatch_lines([
|
|
||||||
"*<Module*test_pytest*",
|
|
||||||
"*<MyModule*xyz*",
|
|
||||||
])
|
|
||||||
|
|
||||||
def test_pytest_pycollect_makeitem(self, testdir):
|
|
||||||
testdir.makeconftest("""
|
|
||||||
import py
|
|
||||||
class MyFunction(py.test.collect.Function):
|
|
||||||
pass
|
|
||||||
def pytest_pycollect_makeitem(collector, name, obj):
|
|
||||||
if name == "some":
|
|
||||||
return MyFunction(name, collector)
|
|
||||||
""")
|
|
||||||
testdir.makepyfile("def some(): pass")
|
|
||||||
result = testdir.runpytest("--collectonly")
|
|
||||||
result.stdout.fnmatch_lines([
|
|
||||||
"*MyFunction*some*",
|
|
||||||
])
|
|
||||||
|
|
||||||
def test_makeitem_non_underscore(self, testdir, monkeypatch):
|
|
||||||
modcol = testdir.getmodulecol("def _hello(): pass")
|
|
||||||
l = []
|
|
||||||
monkeypatch.setattr(py.test.collect.Module, 'makeitem',
|
|
||||||
lambda self, name, obj: l.append(name))
|
|
||||||
l = modcol.collect()
|
|
||||||
assert '_hello' not in l
|
|
||||||
|
|
||||||
|
|
||||||
class TestReportinfo:
|
|
||||||
|
|
||||||
def test_func_reportinfo(self, testdir):
|
|
||||||
item = testdir.getitem("def test_func(): pass")
|
|
||||||
fspath, lineno, modpath = item.reportinfo()
|
|
||||||
assert fspath == item.fspath
|
|
||||||
assert lineno == 0
|
|
||||||
assert modpath == "test_func"
|
|
||||||
|
|
||||||
def test_class_reportinfo(self, testdir):
|
|
||||||
modcol = testdir.getmodulecol("""
|
|
||||||
# lineno 0
|
|
||||||
class TestClass:
|
|
||||||
def test_hello(self): pass
|
|
||||||
""")
|
|
||||||
classcol = modcol.collect_by_name("TestClass")
|
|
||||||
fspath, lineno, msg = classcol.reportinfo()
|
|
||||||
assert fspath == modcol.fspath
|
|
||||||
assert lineno == 1
|
|
||||||
assert msg == "TestClass"
|
|
||||||
|
|
||||||
def test_generator_reportinfo(self, testdir):
|
|
||||||
modcol = testdir.getmodulecol("""
|
|
||||||
# lineno 0
|
|
||||||
def test_gen():
|
|
||||||
def check(x):
|
|
||||||
assert x
|
|
||||||
yield check, 3
|
|
||||||
""")
|
|
||||||
gencol = modcol.collect_by_name("test_gen")
|
|
||||||
fspath, lineno, modpath = gencol.reportinfo()
|
|
||||||
assert fspath == modcol.fspath
|
|
||||||
assert lineno == 1
|
|
||||||
assert modpath == "test_gen"
|
|
||||||
|
|
||||||
genitem = gencol.collect()[0]
|
|
||||||
fspath, lineno, modpath = genitem.reportinfo()
|
|
||||||
assert fspath == modcol.fspath
|
|
||||||
assert lineno == 2
|
|
||||||
assert modpath == "test_gen[0]"
|
|
||||||
"""
|
|
||||||
def test_func():
|
|
||||||
pass
|
|
||||||
def test_genfunc():
|
|
||||||
def check(x):
|
|
||||||
pass
|
|
||||||
yield check, 3
|
|
||||||
class TestClass:
|
|
||||||
def test_method(self):
|
|
||||||
pass
|
|
||||||
"""
|
|
||||||
|
|
||||||
def test_setup_only_available_in_subdir(testdir):
|
|
||||||
sub1 = testdir.mkpydir("sub1")
|
|
||||||
sub2 = testdir.mkpydir("sub2")
|
|
||||||
sub1.join("conftest.py").write(py.code.Source("""
|
|
||||||
import py
|
|
||||||
def pytest_runtest_setup(item):
|
|
||||||
assert item.fspath.purebasename == "test_in_sub1"
|
|
||||||
def pytest_runtest_call(item):
|
|
||||||
assert item.fspath.purebasename == "test_in_sub1"
|
|
||||||
def pytest_runtest_teardown(item):
|
|
||||||
assert item.fspath.purebasename == "test_in_sub1"
|
|
||||||
"""))
|
|
||||||
sub2.join("conftest.py").write(py.code.Source("""
|
|
||||||
import py
|
|
||||||
def pytest_runtest_setup(item):
|
|
||||||
assert item.fspath.purebasename == "test_in_sub2"
|
|
||||||
def pytest_runtest_call(item):
|
|
||||||
assert item.fspath.purebasename == "test_in_sub2"
|
|
||||||
def pytest_runtest_teardown(item):
|
|
||||||
assert item.fspath.purebasename == "test_in_sub2"
|
|
||||||
"""))
|
|
||||||
sub1.join("test_in_sub1.py").write("def test_1(): pass")
|
|
||||||
sub2.join("test_in_sub2.py").write("def test_2(): pass")
|
|
||||||
result = testdir.runpytest("-v", "-s")
|
|
||||||
result.stdout.fnmatch_lines([
|
|
||||||
"*2 passed*"
|
|
||||||
])
|
|
||||||
|
|
||||||
def test_generate_tests_only_done_in_subdir(testdir):
|
|
||||||
sub1 = testdir.mkpydir("sub1")
|
|
||||||
sub2 = testdir.mkpydir("sub2")
|
|
||||||
sub1.join("conftest.py").write(py.code.Source("""
|
|
||||||
def pytest_generate_tests(metafunc):
|
|
||||||
assert metafunc.function.__name__ == "test_1"
|
|
||||||
"""))
|
|
||||||
sub2.join("conftest.py").write(py.code.Source("""
|
|
||||||
def pytest_generate_tests(metafunc):
|
|
||||||
assert metafunc.function.__name__ == "test_2"
|
|
||||||
"""))
|
|
||||||
sub1.join("test_in_sub1.py").write("def test_1(): pass")
|
|
||||||
sub2.join("test_in_sub2.py").write("def test_2(): pass")
|
|
||||||
result = testdir.runpytest("-v", "-s", sub1, sub2, sub1)
|
|
||||||
result.stdout.fnmatch_lines([
|
|
||||||
"*3 passed*"
|
|
||||||
])
|
|
||||||
|
|
||||||
def test_modulecol_roundtrip(testdir):
|
|
||||||
modcol = testdir.getmodulecol("pass", withinit=True)
|
|
||||||
trail = modcol.config._rootcol.totrail(modcol)
|
|
||||||
newcol = modcol.config._rootcol.fromtrail(trail)
|
|
||||||
assert modcol.name == newcol.name
|
|
||||||
|
|
||||||
|
|
||||||
class TestTracebackCutting:
|
|
||||||
def test_skip_simple(self):
|
|
||||||
excinfo = py.test.raises(py.test.skip.Exception, 'py.test.skip("xxx")')
|
|
||||||
assert excinfo.traceback[-1].frame.code.name == "skip"
|
|
||||||
assert excinfo.traceback[-1].ishidden()
|
|
||||||
|
|
||||||
def test_traceback_argsetup(self, testdir):
|
|
||||||
testdir.makeconftest("""
|
|
||||||
def pytest_funcarg__hello(request):
|
|
||||||
raise ValueError("xyz")
|
|
||||||
""")
|
|
||||||
p = testdir.makepyfile("def test(hello): pass")
|
|
||||||
result = testdir.runpytest(p)
|
|
||||||
assert result.ret != 0
|
|
||||||
out = result.stdout.str()
|
|
||||||
assert out.find("xyz") != -1
|
|
||||||
assert out.find("conftest.py:2: ValueError") != -1
|
|
||||||
numentries = out.count("_ _ _") # separator for traceback entries
|
|
||||||
assert numentries == 0
|
|
||||||
|
|
||||||
result = testdir.runpytest("--fulltrace", p)
|
|
||||||
out = result.stdout.str()
|
|
||||||
assert out.find("conftest.py:2: ValueError") != -1
|
|
||||||
numentries = out.count("_ _ _ _") # separator for traceback entries
|
|
||||||
assert numentries >3
|
|
||||||
|
|
||||||
def test_traceback_error_during_import(self, testdir):
|
|
||||||
testdir.makepyfile("""
|
|
||||||
x = 1
|
|
||||||
x = 2
|
|
||||||
x = 17
|
|
||||||
asd
|
|
||||||
""")
|
|
||||||
result = testdir.runpytest()
|
|
||||||
assert result.ret != 0
|
|
||||||
out = result.stdout.str()
|
|
||||||
assert "x = 1" not in out
|
|
||||||
assert "x = 2" not in out
|
|
||||||
result.stdout.fnmatch_lines([
|
|
||||||
">*asd*",
|
|
||||||
"E*NameError*",
|
|
||||||
])
|
|
||||||
result = testdir.runpytest("--fulltrace")
|
|
||||||
out = result.stdout.str()
|
|
||||||
assert "x = 1" in out
|
|
||||||
assert "x = 2" in out
|
|
||||||
result.stdout.fnmatch_lines([
|
|
||||||
">*asd*",
|
|
||||||
"E*NameError*",
|
|
||||||
])
|
|
|
@ -1,11 +1,6 @@
|
||||||
import py
|
import py
|
||||||
|
|
||||||
class SessionTests:
|
class SessionTests:
|
||||||
def test_initsession(self, testdir, tmpdir):
|
|
||||||
config = testdir.reparseconfig()
|
|
||||||
session = config.initsession()
|
|
||||||
assert session.config is config
|
|
||||||
|
|
||||||
def test_basic_testitem_events(self, testdir):
|
def test_basic_testitem_events(self, testdir):
|
||||||
tfile = testdir.makepyfile("""
|
tfile = testdir.makepyfile("""
|
||||||
def test_one():
|
def test_one():
|
||||||
|
@ -22,14 +17,14 @@ class SessionTests:
|
||||||
assert len(skipped) == 0
|
assert len(skipped) == 0
|
||||||
assert len(passed) == 1
|
assert len(passed) == 1
|
||||||
assert len(failed) == 3
|
assert len(failed) == 3
|
||||||
assert failed[0].item.name == "test_one_one"
|
assert failed[0].nodenames[-1] == "test_one_one"
|
||||||
assert failed[1].item.name == "test_other"
|
assert failed[1].nodenames[-1] == "test_other"
|
||||||
assert failed[2].item.name == "test_two"
|
assert failed[2].nodenames[-1] == "test_two"
|
||||||
itemstarted = reprec.getcalls("pytest_itemstart")
|
itemstarted = reprec.getcalls("pytest_log_itemcollect")
|
||||||
assert len(itemstarted) == 4
|
assert len(itemstarted) == 4
|
||||||
colstarted = reprec.getcalls("pytest_collectstart")
|
colstarted = reprec.getcalls("pytest_collectstart")
|
||||||
assert len(colstarted) == 1
|
assert len(colstarted) == 1 + 1 # XXX ExtraTopCollector
|
||||||
col = colstarted[0].collector
|
col = colstarted[1].collector
|
||||||
assert isinstance(col, py.test.collect.Module)
|
assert isinstance(col, py.test.collect.Module)
|
||||||
|
|
||||||
def test_nested_import_error(self, testdir):
|
def test_nested_import_error(self, testdir):
|
||||||
|
@ -183,13 +178,13 @@ class TestNewSession(SessionTests):
|
||||||
)
|
)
|
||||||
reprec = testdir.inline_run('--collectonly', p.dirpath())
|
reprec = testdir.inline_run('--collectonly', p.dirpath())
|
||||||
|
|
||||||
itemstarted = reprec.getcalls("pytest_itemstart")
|
itemstarted = reprec.getcalls("pytest_log_itemcollect")
|
||||||
assert len(itemstarted) == 3
|
assert len(itemstarted) == 3
|
||||||
assert not reprec.getreports("pytest_runtest_logreport")
|
assert not reprec.getreports("pytest_runtest_logreport")
|
||||||
started = reprec.getcalls("pytest_collectstart")
|
started = reprec.getcalls("pytest_collectstart")
|
||||||
finished = reprec.getreports("pytest_collectreport")
|
finished = reprec.getreports("pytest_collectreport")
|
||||||
assert len(started) == len(finished)
|
assert len(started) == len(finished)
|
||||||
assert len(started) == 8
|
assert len(started) == 8 + 1 # XXX extra TopCollector
|
||||||
colfail = [x for x in finished if x.failed]
|
colfail = [x for x in finished if x.failed]
|
||||||
colskipped = [x for x in finished if x.skipped]
|
colskipped = [x for x in finished if x.skipped]
|
||||||
assert len(colfail) == 1
|
assert len(colfail) == 1
|
||||||
|
|
8
tox.ini
8
tox.ini
|
@ -9,7 +9,7 @@ sdistsrc={distshare}/py-*
|
||||||
[testenv]
|
[testenv]
|
||||||
changedir=testing
|
changedir=testing
|
||||||
commands=
|
commands=
|
||||||
py.test -rfsxX --junitxml={envlogdir}/junit-{envname}.xml --tools-on-path []
|
py.test -rfsxX --junitxml={envlogdir}/junit-{envname}.xml []
|
||||||
deps=
|
deps=
|
||||||
pexpect
|
pexpect
|
||||||
[testenv:py27]
|
[testenv:py27]
|
||||||
|
@ -21,7 +21,7 @@ deps=
|
||||||
{distshare}/pytest-xdist-*
|
{distshare}/pytest-xdist-*
|
||||||
commands=
|
commands=
|
||||||
py.test -n3 -rfsxX \
|
py.test -n3 -rfsxX \
|
||||||
--junitxml={envlogdir}/junit-{envname}.xml --tools-on-path []
|
--junitxml={envlogdir}/junit-{envname}.xml []
|
||||||
|
|
||||||
[testenv:py26]
|
[testenv:py26]
|
||||||
basepython=python2.6
|
basepython=python2.6
|
||||||
|
@ -47,6 +47,10 @@ basepython=python2.4
|
||||||
[testenv:py31]
|
[testenv:py31]
|
||||||
basepython=python3.1
|
basepython=python3.1
|
||||||
deps=
|
deps=
|
||||||
|
[testenv:py32]
|
||||||
|
basepython=python3.2
|
||||||
|
deps=
|
||||||
|
#{distshare}/pytest-xdist-*
|
||||||
#[testenv:pypy]
|
#[testenv:pypy]
|
||||||
#python=pypy-c
|
#python=pypy-c
|
||||||
[testenv:jython]
|
[testenv:jython]
|
||||||
|
|
Loading…
Reference in New Issue