commit
63bb9efd29
3
.hgtags
3
.hgtags
|
@ -28,3 +28,6 @@ d5eacf390af74553227122b85e20345d47b2f9e6 1.3.1
|
|||
8b8e7c25a13cf863f01b2dd955978285ae9daf6a 1.3.1
|
||||
3bff44b188a7ec1af328d977b9d39b6757bb38df 1.3.2
|
||||
c59d3fa8681a5b5966b8375b16fccd64a3a8dbeb 1.3.3
|
||||
79ef6377705184c55633d456832eea318fedcf61 1.3.4
|
||||
79ef6377705184c55633d456832eea318fedcf61 1.3.4
|
||||
90fffd35373e9f125af233f78b19416f0938d841 1.3.4
|
||||
|
|
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
|
||||
==================================================
|
||||
|
||||
|
|
|
@ -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
|
||||
---------------------------------------------------------------
|
||||
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()
|
||||
|
||||
|
||||
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):
|
||||
return x+1
|
||||
|
|
|
@ -26,7 +26,47 @@ py.test/pylib installation info in a nutshell
|
|||
.. _`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
|
||||
|
@ -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
|
||||
reproducible and reliable test environment.
|
||||
|
||||
|
||||
.. _`virtualenv`: http://pypi.python.org/pypi/virtualenv
|
||||
.. _`buildout`: http://www.buildout.org/
|
||||
.. _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:
|
||||
|
||||
|
@ -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
|
||||
all core features and runs unchanged under Python2 and Python3 interpreters.
|
||||
|
||||
Troubleshooting / known issues
|
||||
===============================
|
||||
|
||||
.. _`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
|
||||
.. _`Python for Windows`: http://www.imladris.com/Scripts/PythonForWindows.html
|
||||
|
||||
.. _mercurial: http://mercurial.selenic.com/wiki/
|
||||
.. _`Distribute`:
|
||||
|
|
|
@ -6,27 +6,39 @@ produce code coverage reports using the 'coverage' package, including support fo
|
|||
.. contents::
|
||||
:local:
|
||||
|
||||
This plugin produces coverage reports using the coverage package. It
|
||||
supports centralised testing and distributed testing in both load and
|
||||
each modes.
|
||||
This plugin produces coverage reports. It supports centralised testing and distributed testing in
|
||||
both load and each modes. It also supports coverage of subprocesses.
|
||||
|
||||
All features offered by the coverage package should be available,
|
||||
either through this plugin or through coverage's own config file.
|
||||
All features offered by the coverage package should be available, either through pytest-cov or
|
||||
through coverage's config file.
|
||||
|
||||
|
||||
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 uninstall pytest-cov
|
||||
|
||||
Alternatively easy_install can be used::
|
||||
|
||||
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
|
||||
|
@ -35,6 +47,9 @@ Usage
|
|||
Centralised Testing
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Centralised testing will report on the combined coverage of the main process and all of it's
|
||||
subprocesses.
|
||||
|
||||
Running centralised testing::
|
||||
|
||||
py.test --cov myproj tests/
|
||||
|
@ -42,150 +57,149 @@ Running centralised testing::
|
|||
Shows a terminal report::
|
||||
|
||||
-------------------- coverage: platform linux2, python 2.6.4-final-0 ---------------------
|
||||
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%
|
||||
Name Stmts Miss Cover
|
||||
----------------------------------------
|
||||
myproj/__init__ 2 0 100%
|
||||
myproj/myproj 257 13 94%
|
||||
myproj/feature4286 94 7 92%
|
||||
----------------------------------------
|
||||
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/
|
||||
|
||||
The results from the slaves will be combined like so::
|
||||
Shows a terminal report::
|
||||
|
||||
-------------------- coverage: platform linux2, python 2.6.4-final-0 ---------------------
|
||||
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%
|
||||
Name Stmts Miss Cover
|
||||
----------------------------------------
|
||||
myproj/__init__ 2 0 100%
|
||||
myproj/myproj 257 13 94%
|
||||
myproj/feature4286 94 7 92%
|
||||
----------------------------------------
|
||||
TOTAL 353 20 94%
|
||||
|
||||
|
||||
Distributed testing in each mode::
|
||||
Again but spread over different hosts and different directories::
|
||||
|
||||
py.test --cov myproj --dist=each
|
||||
--tx=popen//python=/usr/local/python265/bin/python
|
||||
--tx=popen//python=/usr/local/python27b1/bin/python
|
||||
py.test --cov myproj --dist load
|
||||
--tx ssh=memedough@host1//chdir=testenv1
|
||||
--tx ssh=memedough@host2//chdir=/tmp/testenv2//python=/tmp/env1/bin/python
|
||||
--rsyncdir myproj --rsyncdir tests --rsync examples
|
||||
tests/
|
||||
|
||||
Will produce a report for each slave::
|
||||
Shows a terminal report::
|
||||
|
||||
-------------------- coverage: platform linux2, python 2.6.5-final-0 ---------------------
|
||||
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%
|
||||
--------------------- 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%
|
||||
-------------------- 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%
|
||||
|
||||
|
||||
Distributed testing in each mode can also produce a single combined
|
||||
report. This is useful to get coverage information spanning things
|
||||
such as all python versions::
|
||||
Distributed Testing: Each
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
py.test --cov myproj --cov-combine-each --dist=each
|
||||
--tx=popen//python=/usr/local/python265/bin/python
|
||||
--tx=popen//python=/usr/local/python27b1/bin/python
|
||||
Distributed testing with dist mode set to each will report on the combined coverage of all slaves.
|
||||
Since each slave is running all tests this allows generating a combined coverage report for multiple
|
||||
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/
|
||||
|
||||
Which looks like::
|
||||
Shows a terminal report::
|
||||
|
||||
---------------------------------------- coverage ----------------------------------------
|
||||
platform linux2, python 2.6.5-final-0
|
||||
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%
|
||||
platform linux2, python 2.7.0-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%
|
||||
|
||||
|
||||
Reporting
|
||||
---------
|
||||
|
||||
By default a terminal report is output. This report can be disabled
|
||||
if desired, such as when results are going to a continuous integration
|
||||
system and the terminal output won't be seen.
|
||||
It is possible to generate any combination of the reports for a single test run.
|
||||
|
||||
In addition and without rerunning tests it is possible to generate
|
||||
annotated source code, a html report and an xml report.
|
||||
The available reports are terminal (with or without missing line numbers shown), HTML, XML and
|
||||
annotated source code.
|
||||
|
||||
The directories for annotated source code and html reports can be
|
||||
specified as can the file name for the xml report.
|
||||
The terminal report without line numbers (default)::
|
||||
|
||||
Since testing often takes a non trivial amount of time at the end of
|
||||
testing any / all of the reports may be generated.
|
||||
py.test --cov-report term --cov myproj tests/
|
||||
|
||||
-------------------- 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
|
||||
------------------
|
||||
|
||||
During testing there may be many data files with coverage data. These
|
||||
will have unique suffixes and will be combined at the end of testing.
|
||||
The data file is erased at the beginning of testing to ensure clean data for each test run.
|
||||
|
||||
Upon completion, for --dist=load (and also for --dist=each when the
|
||||
--cov-combine-each option is used) there will only be one data file.
|
||||
|
||||
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
|
||||
The data file is left at the end of testing so that it is possible to use normal coverage tools to
|
||||
examine it.
|
||||
|
||||
|
||||
Limitations
|
||||
-----------
|
||||
|
||||
For distributed testing the slaves must have the pytest-cov package
|
||||
installed. This is needed since the plugin must be registered through
|
||||
setuptools / distribute for pytest to start the plugin on the slave.
|
||||
For distributed testing the slaves must have the pytest-cov package installed. This is needed since
|
||||
the plugin must be registered through setuptools / distribute for pytest to start the plugin on the
|
||||
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
|
||||
|
@ -193,14 +207,11 @@ Acknowledgements
|
|||
|
||||
Holger Krekel for pytest with its distributed testing support.
|
||||
|
||||
Ned Batchelder for coverage and its ability to combine the coverage
|
||||
results of parallel runs.
|
||||
Ned Batchelder for coverage and its ability to combine the coverage results of parallel runs.
|
||||
|
||||
Whilst this plugin has been built fresh from the ground up to support
|
||||
distributed testing it has been influenced by the work done on
|
||||
pytest-coverage (Ross Lawley, James Mills, Holger Krekel) and
|
||||
nose-cover (Jason Pellerin) which are other coverage plugins for
|
||||
pytest and nose respectively.
|
||||
Whilst this plugin has been built fresh from the ground up to support distributed testing it has
|
||||
been influenced by the work done on pytest-coverage (Ross Lawley, James Mills, Holger Krekel) and
|
||||
nose-cover (Jason Pellerin) which are other coverage plugins for pytest and nose respectively.
|
||||
|
||||
No doubt others have contributed to these tools as well.
|
||||
|
||||
|
@ -208,43 +219,11 @@ command line options
|
|||
--------------------
|
||||
|
||||
|
||||
``--cov-on``
|
||||
enable coverage, only needed if not specifying any --cov options
|
||||
``--cov=package``
|
||||
collect coverage for the specified package (multi-allowed)
|
||||
``--cov-no-terminal``
|
||||
disable printing a report on the terminal
|
||||
``--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
|
||||
``--cov=path``
|
||||
measure coverage for filesystem path (multi-allowed)
|
||||
``--cov-report=type``
|
||||
type of report to generate: term, term-missing, annotate, html, xml (multi-allowed)
|
||||
``--cov-config=path``
|
||||
config file for coverage, default: .coveragerc
|
||||
|
||||
.. include:: links.txt
|
||||
|
|
|
@ -6,10 +6,27 @@ Write and report coverage data with the 'coverage' package.
|
|||
.. contents::
|
||||
:local:
|
||||
|
||||
Original code by Ross Lawley.
|
||||
Note: Original code by Ross Lawley.
|
||||
|
||||
Requires Ned Batchelder's excellent coverage:
|
||||
http://nedbatchelder.com/code/coverage/
|
||||
Install
|
||||
--------------
|
||||
|
||||
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
|
||||
--------------------
|
||||
|
@ -21,8 +38,11 @@ command line options
|
|||
html: Directory for html output.
|
||||
report: Output a text report.
|
||||
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``
|
||||
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``
|
||||
Show missing files
|
||||
``--cover-ignore-errors=IGNORE_ERRORS``
|
||||
|
|
|
@ -6,16 +6,29 @@ report test coverage using the 'figleaf' package.
|
|||
.. contents::
|
||||
: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
|
||||
---------------
|
||||
|
||||
after pip or easy_install mediated installation of ``pytest-figleaf`` you can type::
|
||||
After installation you can simply type::
|
||||
|
||||
py.test --figleaf [...]
|
||||
|
||||
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 ``fig-html`` to modify the paths.
|
||||
and "html" directory will be created. You can use command line options
|
||||
to control where data and html files are created.
|
||||
|
||||
command line options
|
||||
--------------------
|
||||
|
|
|
@ -1,47 +1,47 @@
|
|||
.. _`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
|
||||
.. _`pytest_monkeypatch.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.2/py/_plugin/pytest_monkeypatch.py
|
||||
.. _`pytest_genscript.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.2/py/_plugin/pytest_genscript.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.4/py/_plugin/pytest_genscript.py
|
||||
.. _`pastebin`: pastebin.html
|
||||
.. _`skipping`: skipping.html
|
||||
.. _`genscript`: genscript.html
|
||||
.. _`plugins`: index.html
|
||||
.. _`mark`: mark.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
|
||||
.. _`pytest_nose.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.2/py/_plugin/pytest_nose.py
|
||||
.. _`pytest_restdoc.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.2/py/_plugin/pytest_restdoc.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.4/py/_plugin/pytest_restdoc.py
|
||||
.. _`restdoc`: restdoc.html
|
||||
.. _`xdist`: xdist.html
|
||||
.. _`pytest_pastebin.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.2/py/_plugin/pytest_pastebin.py
|
||||
.. _`pytest_tmpdir.py`: http://bitbucket.org/hpk42/py-trunk/raw/1.3.2/py/_plugin/pytest_tmpdir.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.4/py/_plugin/pytest_tmpdir.py
|
||||
.. _`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
|
||||
.. _`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
|
||||
.. _`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
|
||||
.. _`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
|
||||
.. _`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
|
||||
.. _`customize`: ../customize.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
|
||||
.. _`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
|
||||
.. _`coverage`: coverage.html
|
||||
.. _`resultlog`: resultlog.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
|
||||
.. _`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
|
||||
.. _`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
|
||||
|
|
|
@ -8,7 +8,7 @@ dictionary or an import path.
|
|||
|
||||
(c) Holger Krekel and others, 2004-2010
|
||||
"""
|
||||
__version__ = version = "1.3.4a1"
|
||||
__version__ = version = "1.4.0a1"
|
||||
|
||||
import py.apipkg
|
||||
|
||||
|
@ -45,15 +45,9 @@ py.apipkg.initpkg(__name__, dict(
|
|||
'Directory' : '._test.collect:Directory',
|
||||
'File' : '._test.collect:File',
|
||||
'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': {
|
||||
'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',
|
||||
'_reinterpret_old' : '._code.assertion:reinterpret_old',
|
||||
'_reinterpret' : '._code.assertion:reinterpret',
|
||||
'_reprcompare' : '._code.assertion:_reprcompare',
|
||||
},
|
||||
|
||||
# backports and additions of builtins
|
||||
|
@ -111,6 +106,7 @@ py.apipkg.initpkg(__name__, dict(
|
|||
'frozenset' : '._builtin:frozenset',
|
||||
'BaseException' : '._builtin:BaseException',
|
||||
'GeneratorExit' : '._builtin:GeneratorExit',
|
||||
'_sysex' : '._builtin:_sysex',
|
||||
'print_' : '._builtin:print_',
|
||||
'_reraise' : '._builtin:_reraise',
|
||||
'_tryimport' : '._builtin:_tryimport',
|
||||
|
|
|
@ -87,6 +87,8 @@ except NameError:
|
|||
pass
|
||||
GeneratorExit.__module__ = 'exceptions'
|
||||
|
||||
_sysex = (KeyboardInterrupt, SystemExit, MemoryError, GeneratorExit)
|
||||
|
||||
if sys.version_info >= (3, 0):
|
||||
exec ("print_ = print ; exec_=exec")
|
||||
import builtins
|
||||
|
|
|
@ -108,7 +108,7 @@ unary_map = {
|
|||
|
||||
|
||||
class DebugInterpreter(ast.NodeVisitor):
|
||||
"""Interpret AST nodes to gleam useful debugging information."""
|
||||
"""Interpret AST nodes to gleam useful debugging information. """
|
||||
|
||||
def __init__(self, frame):
|
||||
self.frame = frame
|
||||
|
@ -162,10 +162,7 @@ class DebugInterpreter(ast.NodeVisitor):
|
|||
def visit_Compare(self, comp):
|
||||
left = comp.left
|
||||
left_explanation, left_result = self.visit(left)
|
||||
got_result = False
|
||||
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)
|
||||
op_symbol = operator_map[op.__class__]
|
||||
explanation = "%s %s %s" % (left_explanation, op_symbol,
|
||||
|
@ -177,9 +174,15 @@ class DebugInterpreter(ast.NodeVisitor):
|
|||
__exprinfo_right=next_result)
|
||||
except Exception:
|
||||
raise Failure(explanation)
|
||||
else:
|
||||
got_result = True
|
||||
if not result:
|
||||
break
|
||||
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
|
||||
|
||||
def visit_BoolOp(self, boolop):
|
||||
|
|
|
@ -3,7 +3,7 @@ import sys, inspect
|
|||
from compiler import parse, ast, pycodegen
|
||||
from py._code.assertion import BuiltinAssertionError, _format_explanation
|
||||
|
||||
passthroughex = (KeyboardInterrupt, SystemExit, MemoryError)
|
||||
passthroughex = py.builtin._sysex
|
||||
|
||||
class Failure:
|
||||
def __init__(self, node):
|
||||
|
|
|
@ -3,14 +3,23 @@ import py
|
|||
|
||||
BuiltinAssertionError = py.builtin.builtins.AssertionError
|
||||
|
||||
_reprcompare = None # if set, will be called by assert reinterp for comparison ops
|
||||
|
||||
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')
|
||||
# escape newlines not followed by { and }
|
||||
# escape newlines not followed by {, } and ~
|
||||
lines = [raw_lines[0]]
|
||||
for l in raw_lines[1:]:
|
||||
if l.startswith('{') or l.startswith('}'):
|
||||
if l.startswith('{') or l.startswith('}') or l.startswith('~'):
|
||||
lines.append(l)
|
||||
else:
|
||||
lines[-1] += '\\n' + l
|
||||
|
@ -28,23 +37,25 @@ def _format_explanation(explanation):
|
|||
stackcnt[-1] += 1
|
||||
stackcnt.append(0)
|
||||
result.append(' +' + ' '*(len(stack)-1) + s + line[1:])
|
||||
else:
|
||||
elif line.startswith('}'):
|
||||
assert line.startswith('}')
|
||||
stack.pop()
|
||||
stackcnt.pop()
|
||||
result[stack[-1]] += line[1:]
|
||||
else:
|
||||
assert line.startswith('~')
|
||||
result.append(' '*len(stack) + line[1:])
|
||||
assert len(stack) == 1
|
||||
return '\n'.join(result)
|
||||
|
||||
|
||||
class AssertionError(BuiltinAssertionError):
|
||||
|
||||
def __init__(self, *args):
|
||||
BuiltinAssertionError.__init__(self, *args)
|
||||
if args:
|
||||
try:
|
||||
self.msg = str(args[0])
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
except py.builtin._sysex:
|
||||
raise
|
||||
except:
|
||||
self.msg = "<[broken __repr__] %s at %0xd>" %(
|
||||
|
|
|
@ -189,7 +189,7 @@ class TracebackEntry(object):
|
|||
"""
|
||||
try:
|
||||
return self.frame.eval("__tracebackhide__")
|
||||
except (SystemExit, KeyboardInterrupt):
|
||||
except py.builtin._sysex:
|
||||
raise
|
||||
except:
|
||||
return False
|
||||
|
@ -354,9 +354,17 @@ class ExceptionInfo(object):
|
|||
abspath=False, tbfilter=True, funcargs=False):
|
||||
""" return str()able representation of this exception info.
|
||||
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)
|
||||
"""
|
||||
if style == 'native':
|
||||
import traceback
|
||||
return ''.join(traceback.format_exception(
|
||||
self.type,
|
||||
self.value,
|
||||
self.traceback[0]._rawentry,
|
||||
))
|
||||
|
||||
fmt = FormattedExcinfo(showlocals=showlocals, style=style,
|
||||
abspath=abspath, tbfilter=tbfilter, funcargs=funcargs)
|
||||
return fmt.repr_excinfo(self)
|
||||
|
|
|
@ -276,7 +276,7 @@ def getfslineno(obj):
|
|||
def findsource(obj):
|
||||
try:
|
||||
sourcelines, lineno = py.std.inspect.findsource(obj)
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
except py.builtin._sysex:
|
||||
raise
|
||||
except:
|
||||
return None, None
|
||||
|
|
|
@ -5,8 +5,6 @@ builtin_repr = repr
|
|||
|
||||
reprlib = py.builtin._tryimport('repr', 'reprlib')
|
||||
|
||||
sysex = (KeyboardInterrupt, MemoryError, SystemExit)
|
||||
|
||||
class SafeRepr(reprlib.Repr):
|
||||
""" subclass of repr.Repr that limits the resulting size of repr()
|
||||
and includes information on exceptions raised during the call.
|
||||
|
@ -21,7 +19,7 @@ class SafeRepr(reprlib.Repr):
|
|||
try:
|
||||
# Try the vanilla repr and make sure that the result is a string
|
||||
s = call(x, *args)
|
||||
except sysex:
|
||||
except py.builtin._sysex:
|
||||
raise
|
||||
except:
|
||||
cls, e, tb = sys.exc_info()
|
||||
|
|
|
@ -26,7 +26,7 @@ def _getdimensions():
|
|||
def get_terminal_width():
|
||||
try:
|
||||
height, width = _getdimensions()
|
||||
except (SystemExit, KeyboardInterrupt):
|
||||
except py.builtin._sysex:
|
||||
raise
|
||||
except:
|
||||
# FALLBACK
|
||||
|
|
|
@ -519,6 +519,8 @@ class LocalPath(FSBase):
|
|||
pkg = __import__(pkgpath.basename, None, None, [])
|
||||
names = self.new(ext='').relto(pkgpath.dirpath())
|
||||
names = names.split(self.sep)
|
||||
if names and names[-1] == "__init__":
|
||||
names.pop()
|
||||
modname = ".".join(names)
|
||||
else:
|
||||
# no package scope, still make it possible
|
||||
|
@ -532,6 +534,7 @@ class LocalPath(FSBase):
|
|||
elif modfile.endswith('$py.class'):
|
||||
modfile = modfile[:-9] + '.py'
|
||||
if modfile.endswith("__init__.py"):
|
||||
if self.basename != "__init__.py":
|
||||
modfile = modfile[:-12]
|
||||
if not self.samefile(modfile):
|
||||
raise self.ImportMismatchError(modname, modfile, self)
|
||||
|
|
|
@ -20,6 +20,14 @@ def pytest_configure(config):
|
|||
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):
|
||||
""" called before test process is exited. """
|
||||
|
||||
|
@ -27,6 +35,16 @@ def pytest_unconfigure(config):
|
|||
# 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):
|
||||
""" return true value to prevent considering this path for collection.
|
||||
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):
|
||||
""" return Collection node or None for the given path. """
|
||||
|
||||
# logging hooks for collection
|
||||
def pytest_collectstart(collector):
|
||||
""" collector starts collecting. """
|
||||
|
||||
def pytest_log_itemcollect(item):
|
||||
""" we just collected a test item. """
|
||||
|
||||
def pytest_collectreport(report):
|
||||
""" collector finished collecting. """
|
||||
|
||||
|
@ -54,10 +76,6 @@ def pytest_make_collect_report(collector):
|
|||
""" perform a collection and return a collection. """
|
||||
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
|
||||
# -------------------------------------------------------------------------
|
||||
|
@ -84,11 +102,16 @@ def pytest_generate_tests(metafunc):
|
|||
# -------------------------------------------------------------------------
|
||||
# generic runtest related hooks
|
||||
# -------------------------------------------------------------------------
|
||||
def pytest_itemstart(item, node=None):
|
||||
""" (deprecated, use pytest_runtest_logstart). """
|
||||
|
||||
def pytest_runtest_protocol(item):
|
||||
""" implement fixture, run and report about the given test item. """
|
||||
pytest_runtest_protocol.firstresult = True
|
||||
|
||||
def pytest_runtest_logstart(nodeid, location, fspath):
|
||||
""" signal the start of a test run. """
|
||||
|
||||
def pytest_runtest_setup(item):
|
||||
""" called before pytest_runtest_call(). """
|
||||
|
||||
|
@ -110,7 +133,7 @@ def pytest__teardown_final(session):
|
|||
""" called before test session finishes. """
|
||||
pytest__teardown_final.firstresult = True
|
||||
|
||||
def pytest__teardown_final_logerror(report):
|
||||
def pytest__teardown_final_logerror(report, session):
|
||||
""" called if runtest_teardown_final failed. """
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
|
@ -123,6 +146,20 @@ def pytest_sessionstart(session):
|
|||
def pytest_sessionfinish(session, exitstatus):
|
||||
""" 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)
|
||||
# -------------------------------------------------------------------------
|
||||
|
@ -138,8 +175,10 @@ def pytest_terminal_summary(terminalreporter):
|
|||
""" add additional section in terminal summary reporting. """
|
||||
|
||||
def pytest_report_iteminfo(item):
|
||||
""" return (fspath, lineno, name) for the item.
|
||||
the information is used for result display and to sort tests
|
||||
""" return (fspath, lineno, domainpath) location info for the item.
|
||||
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
|
||||
|
||||
|
|
|
@ -87,6 +87,28 @@ class HookRecorder:
|
|||
l.append(call)
|
||||
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):
|
||||
for i, call in enumerate(self.calls):
|
||||
if call._name == name:
|
||||
|
|
|
@ -8,15 +8,29 @@ def pytest_addoption(parser):
|
|||
help="disable python assert expression reinterpretation."),
|
||||
|
||||
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"):
|
||||
warn_about_missing_assertion()
|
||||
config._oldassertion = py.builtin.builtins.AssertionError
|
||||
config._oldbinrepr = py.code._reprcompare
|
||||
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):
|
||||
if hasattr(config, '_oldassertion'):
|
||||
py.builtin.builtins.AssertionError = config._oldassertion
|
||||
py.code._reprcompare = config._oldbinrepr
|
||||
del config._oldassertion
|
||||
del config._oldbinrepr
|
||||
|
||||
def warn_about_missing_assertion():
|
||||
try:
|
||||
|
@ -26,3 +40,109 @@ def warn_about_missing_assertion():
|
|||
else:
|
||||
py.std.warnings.warn("Assertions are turned off!"
|
||||
" (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 py
|
||||
|
||||
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_cmdline_main(config):
|
||||
from py._test.session import Session
|
||||
return Session(config).main()
|
||||
|
||||
def pytest_collect_file(path, parent):
|
||||
ext = path.ext
|
||||
pb = path.purebasename
|
||||
if pb.startswith("test_") or pb.endswith("_test") or \
|
||||
path in parent.config._argfspaths:
|
||||
if ext == ".py":
|
||||
return parent.ihook.pytest_pycollect_makemodule(
|
||||
path=path, parent=parent)
|
||||
def pytest_perform_collection(session):
|
||||
collection = session.collection
|
||||
assert not hasattr(collection, 'items')
|
||||
hook = session.config.hook
|
||||
collection.items = items = collection.perform_collect()
|
||||
hook.pytest_collection_modifyitems(config=session.config, items=items)
|
||||
hook.pytest_log_finishcollection(collection=collection)
|
||||
return True
|
||||
|
||||
def pytest_pycollect_makemodule(path, parent):
|
||||
return parent.Module(path, parent)
|
||||
|
||||
def pytest_funcarg__pytestconfig(request):
|
||||
""" the pytest config object with access to command line opts."""
|
||||
return request.config
|
||||
def pytest_runtest_mainloop(session):
|
||||
if session.config.option.collectonly:
|
||||
return True
|
||||
for item in session.collection.items:
|
||||
item.config.hook.pytest_runtest_protocol(item=item)
|
||||
if session.shouldstop:
|
||||
raise session.Interrupted(session.shouldstop)
|
||||
return True
|
||||
|
||||
def pytest_ignore_collect(path, config):
|
||||
ignore_paths = config.getconftest_pathlist("collect_ignore", path=path)
|
||||
|
@ -35,12 +32,6 @@ def pytest_ignore_collect(path, config):
|
|||
if excludeopt:
|
||||
ignore_paths.extend([py.path.local(x) for x in excludeopt])
|
||||
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):
|
||||
# XXX reconsider the following comment
|
||||
|
@ -49,7 +40,7 @@ def pytest_collect_directory(path, parent):
|
|||
# define Directory(dir) already
|
||||
if not parent.recfilter(path): # by default special ".cvs", ...
|
||||
# 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):
|
||||
break
|
||||
else:
|
||||
|
@ -68,12 +59,6 @@ def pytest_addoption(parser):
|
|||
group._addoption('--maxfail', metavar="num",
|
||||
action="store", type="int", dest="maxfail", default=0,
|
||||
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.addoption('--collectonly',
|
||||
|
@ -91,41 +76,7 @@ def pytest_addoption(parser):
|
|||
help="base temporary directory for this test run.")
|
||||
|
||||
def pytest_configure(config):
|
||||
setsession(config)
|
||||
# compat
|
||||
if config.getvalue("exitfirst"):
|
||||
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:
|
||||
return super(DoctestItem, self).repr_failure(excinfo)
|
||||
|
||||
def reportinfo(self):
|
||||
return self.fspath, None, "[doctest]"
|
||||
|
||||
class DoctestTextfile(DoctestItem):
|
||||
def runtest(self):
|
||||
if not self._deprecated_testexecution():
|
||||
|
|
|
@ -11,7 +11,7 @@ def pytest_addoption(parser):
|
|||
dest="genscript", metavar="path",
|
||||
help="create standalone py.test script at given target path.")
|
||||
|
||||
def pytest_configure(config):
|
||||
def pytest_cmdline_main(config):
|
||||
genscript = config.getvalue("genscript")
|
||||
if genscript:
|
||||
import py
|
||||
|
@ -20,7 +20,7 @@ def pytest_configure(config):
|
|||
pybasedir = py.path.local(py.__file__).dirpath().dirpath()
|
||||
genscript = py.path.local(genscript)
|
||||
main(pybasedir, outfile=genscript, infile=infile)
|
||||
raise SystemExit(0)
|
||||
return 0
|
||||
|
||||
def main(pybasedir, outfile, infile):
|
||||
import base64
|
||||
|
|
|
@ -23,15 +23,18 @@ def pytest_addoption(parser):
|
|||
help="show available conftest.py and ENV-variable names.")
|
||||
|
||||
|
||||
def pytest_configure(__multicall__, config):
|
||||
def pytest_cmdline_main(config):
|
||||
if config.option.version:
|
||||
p = py.path.local(py.__file__).dirpath()
|
||||
sys.stderr.write("This is py.test version %s, imported from %s\n" %
|
||||
(py.__version__, p))
|
||||
sys.exit(0)
|
||||
if not config.option.helpconfig:
|
||||
return
|
||||
__multicall__.execute()
|
||||
return 0
|
||||
elif config.option.helpconfig:
|
||||
config.pluginmanager.do_configure(config)
|
||||
showpluginhelp(config)
|
||||
return 0
|
||||
|
||||
def showpluginhelp(config):
|
||||
options = []
|
||||
for group in config._parser._groups:
|
||||
options.extend(group.options)
|
||||
|
@ -65,9 +68,7 @@ def pytest_configure(__multicall__, config):
|
|||
help,
|
||||
)
|
||||
tw.line(line[:tw.fullwidth])
|
||||
|
||||
tw.sep("-")
|
||||
sys.exit(0)
|
||||
|
||||
conftest_options = (
|
||||
('pytest_plugins', 'list of plugin names to load'),
|
||||
|
|
|
@ -37,12 +37,9 @@ class LogXML(object):
|
|||
self._durations = {}
|
||||
|
||||
def _opentestcase(self, report):
|
||||
if hasattr(report, 'item'):
|
||||
node = report.item
|
||||
else:
|
||||
node = report.collector
|
||||
d = {'time': self._durations.pop(node, "0")}
|
||||
names = [x.replace(".py", "") for x in node.listnames() if x != "()"]
|
||||
names = report.nodenames
|
||||
d = {'time': self._durations.pop(names, "0")}
|
||||
names = [x.replace(".py", "") for x in names if x != "()"]
|
||||
classnames = names[:-1]
|
||||
if self.prefix:
|
||||
classnames.insert(0, self.prefix)
|
||||
|
@ -122,11 +119,12 @@ class LogXML(object):
|
|||
self.append_skipped(report)
|
||||
|
||||
def pytest_runtest_call(self, item, __multicall__):
|
||||
names = tuple(item.listnames())
|
||||
start = time.time()
|
||||
try:
|
||||
return __multicall__.execute()
|
||||
finally:
|
||||
self._durations[item] = time.time() - start
|
||||
self._durations[names] = time.time() - start
|
||||
|
||||
def pytest_collectreport(self, report):
|
||||
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/
|
||||
"""
|
||||
|
||||
import py, os, sys
|
||||
import os, sys
|
||||
|
||||
def pytest_funcarg__monkeypatch(request):
|
||||
"""The returned ``monkeypatch`` funcarg provides these
|
||||
|
|
|
@ -7,13 +7,14 @@ import sys, os
|
|||
import re
|
||||
import inspect
|
||||
import time
|
||||
from fnmatch import fnmatch
|
||||
from py._test.config import Config as pytestConfig
|
||||
from py.builtin import print_
|
||||
|
||||
def pytest_addoption(parser):
|
||||
group = parser.getgroup("pylib")
|
||||
group.addoption('--tools-on-path',
|
||||
action="store_true", dest="toolsonpath", default=False,
|
||||
group.addoption('--no-tools-on-path',
|
||||
action="store_true", dest="notoolsonpath", default=False,
|
||||
help=("discover tools on PATH instead of going through py.cmdline.")
|
||||
)
|
||||
|
||||
|
@ -74,10 +75,8 @@ class TmpTestdir:
|
|||
def __repr__(self):
|
||||
return "<TmpTestdir %r>" % (self.tmpdir,)
|
||||
|
||||
def Config(self, topdir=None):
|
||||
if topdir is None:
|
||||
topdir = self.tmpdir.dirpath()
|
||||
return pytestConfig(topdir=topdir)
|
||||
def Config(self):
|
||||
return pytestConfig()
|
||||
|
||||
def finalize(self):
|
||||
for p in self._syspathremove:
|
||||
|
@ -149,16 +148,23 @@ class TmpTestdir:
|
|||
p.ensure("__init__.py")
|
||||
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):
|
||||
return list(self.session.genitems(colitems))
|
||||
collection = colitems[0].collection
|
||||
result = []
|
||||
collection.genitems(colitems, (), result)
|
||||
return result
|
||||
|
||||
def inline_genitems(self, *args):
|
||||
#config = self.parseconfig(*args)
|
||||
config = self.parseconfig(*args)
|
||||
session = config.initsession()
|
||||
from py._test.session import Collection
|
||||
config = self.parseconfigure(*args)
|
||||
rec = self.getreportrecorder(config)
|
||||
colitems = [config.getnode(arg) for arg in config.args]
|
||||
items = list(session.genitems(colitems))
|
||||
items = Collection(config).perform_collect()
|
||||
return items, rec
|
||||
|
||||
def runitem(self, source):
|
||||
|
@ -187,12 +193,10 @@ class TmpTestdir:
|
|||
def inline_run(self, *args):
|
||||
args = ("-s", ) + args # otherwise FD leakage
|
||||
config = self.parseconfig(*args)
|
||||
config.pluginmanager.do_configure(config)
|
||||
session = config.initsession()
|
||||
reprec = self.getreportrecorder(config)
|
||||
colitems = config.getinitialnodes()
|
||||
session.main(colitems)
|
||||
config.pluginmanager.do_unconfigure(config)
|
||||
#config.pluginmanager.do_configure(config)
|
||||
config.hook.pytest_cmdline_main(config=config)
|
||||
#config.pluginmanager.do_unconfigure(config)
|
||||
return reprec
|
||||
|
||||
def config_preparse(self):
|
||||
|
@ -245,29 +249,17 @@ class TmpTestdir:
|
|||
|
||||
def getitems(self, source):
|
||||
modcol = self.getmodulecol(source)
|
||||
return list(modcol.config.initsession().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)
|
||||
return self.genitems([modcol])
|
||||
|
||||
def getmodulecol(self, source, configargs=(), withinit=False):
|
||||
kw = {self.request.function.__name__: py.code.Source(source).strip()}
|
||||
path = self.makepyfile(**kw)
|
||||
if withinit:
|
||||
self.makepyfile(__init__ = "#")
|
||||
self.config = self.parseconfig(path, *configargs)
|
||||
self.session = self.config.initsession()
|
||||
#self.config.pluginmanager.do_configure(config=self.config)
|
||||
# XXX
|
||||
self.config.pluginmanager.import_plugin("runner")
|
||||
plugin = self.config.pluginmanager.getplugin("runner")
|
||||
plugin.pytest_configure(config=self.config)
|
||||
|
||||
return self.config.getnode(path)
|
||||
self.config = config = self.parseconfigure(path, *configargs)
|
||||
node = self.getnode(config, path)
|
||||
#config.pluginmanager.do_unconfigure(config)
|
||||
return node
|
||||
|
||||
def popen(self, cmdargs, stdout, stderr, **kw):
|
||||
if not hasattr(py.std, 'subprocess'):
|
||||
|
@ -314,7 +306,7 @@ class TmpTestdir:
|
|||
return self.run(*fullargs)
|
||||
|
||||
def _getpybinargs(self, scriptname):
|
||||
if self.request.config.getvalue("toolsonpath"):
|
||||
if not self.request.config.getvalue("notoolsonpath"):
|
||||
script = py.path.local.sysfind(scriptname)
|
||||
assert script, "script %r not found" % scriptname
|
||||
return (script,)
|
||||
|
@ -334,7 +326,7 @@ class TmpTestdir:
|
|||
return self.run(sys.executable, script)
|
||||
|
||||
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())
|
||||
else:
|
||||
s = ""
|
||||
|
@ -360,8 +352,8 @@ class TmpTestdir:
|
|||
|
||||
def spawn_pytest(self, string, expect_timeout=10.0):
|
||||
pexpect = py.test.importorskip("pexpect", "2.4")
|
||||
if not self.request.config.getvalue("toolsonpath"):
|
||||
py.test.skip("need --tools-on-path to run py.test script")
|
||||
if self.request.config.getvalue("notoolsonpath"):
|
||||
py.test.skip("--no-tools-on-path prevents running pexpect-spawn tests")
|
||||
basetemp = self.tmpdir.mkdir("pexpect")
|
||||
invoke = self._getpybinargs("py.test")[0]
|
||||
cmd = "%s --basetemp=%s %s" % (invoke, basetemp, string)
|
||||
|
@ -405,8 +397,7 @@ class ReportRecorder(object):
|
|||
""" return a testreport whose dotted import path matches """
|
||||
l = []
|
||||
for rep in self.getreports(names=names):
|
||||
colitem = rep.getnode()
|
||||
if not inamepart or inamepart in colitem.listnames():
|
||||
if not inamepart or inamepart in rep.nodenames:
|
||||
l.append(rep)
|
||||
if not l:
|
||||
raise ValueError("could not find test report matching %r: no test reports at all!" %
|
||||
|
@ -474,13 +465,25 @@ class LineMatcher:
|
|||
def str(self):
|
||||
return "\n".join(self.lines)
|
||||
|
||||
def fnmatch_lines(self, lines2):
|
||||
def _getlines(self, lines2):
|
||||
if isinstance(lines2, str):
|
||||
lines2 = py.code.Source(lines2)
|
||||
if isinstance(lines2, py.code.Source):
|
||||
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[:]
|
||||
nextline = None
|
||||
extralines = []
|
||||
|
|
|
@ -5,9 +5,80 @@ import py
|
|||
import inspect
|
||||
import sys
|
||||
from py._test.collect import configproperty, warnoldcollect
|
||||
from py._test import funcargs
|
||||
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):
|
||||
def obj():
|
||||
def fget(self):
|
||||
|
@ -120,10 +191,10 @@ class PyCollectorMixin(PyobjMixin, py.test.collect.Collector):
|
|||
module = self.getparent(Module).obj
|
||||
clscol = self.getparent(Class)
|
||||
cls = clscol and clscol.obj or None
|
||||
metafunc = funcargs.Metafunc(funcobj, config=self.config,
|
||||
metafunc = Metafunc(funcobj, config=self.config,
|
||||
cls=cls, module=module)
|
||||
gentesthook = self.config.hook.pytest_generate_tests
|
||||
plugins = funcargs.getplugins(self, withpy=True)
|
||||
plugins = getplugins(self, withpy=True)
|
||||
gentesthook.pcall(plugins, metafunc=metafunc)
|
||||
if not metafunc._calls:
|
||||
return self.Function(name, parent=self)
|
||||
|
@ -212,16 +283,9 @@ class Class(PyCollectorMixin, py.test.collect.Collector):
|
|||
class Instance(PyCollectorMixin, py.test.collect.Collector):
|
||||
def _getobj(self):
|
||||
return self.parent.obj()
|
||||
def Function(self):
|
||||
return getattr(self.obj, 'Function',
|
||||
PyCollectorMixin.Function.__get__(self)) # XXX for python 2.2
|
||||
|
||||
def _keywords(self):
|
||||
return []
|
||||
Function = property(Function)
|
||||
|
||||
#def __repr__(self):
|
||||
# return "<%s of '%s'>" %(self.__class__.__name__,
|
||||
# self.parent.obj.__name__)
|
||||
|
||||
def newinstance(self):
|
||||
self.obj = self._getobj()
|
||||
|
@ -270,7 +334,7 @@ class FunctionMixin(PyobjMixin):
|
|||
return traceback
|
||||
|
||||
def _repr_failure_py(self, excinfo, style="long"):
|
||||
if excinfo.errisinstance(funcargs.FuncargRequest.LookupError):
|
||||
if excinfo.errisinstance(FuncargRequest.LookupError):
|
||||
fspath, lineno, msg = self.reportinfo()
|
||||
lines, _ = inspect.getsourcelines(self.obj)
|
||||
for i, line in enumerate(lines):
|
||||
|
@ -348,8 +412,9 @@ class Function(FunctionMixin, py.test.collect.Item):
|
|||
"""
|
||||
_genid = None
|
||||
def __init__(self, name, parent=None, args=None, config=None,
|
||||
callspec=None, callobj=_dummy):
|
||||
super(Function, self).__init__(name, parent, config=config)
|
||||
callspec=None, callobj=_dummy, collection=None):
|
||||
super(Function, self).__init__(name, parent,
|
||||
config=config, collection=collection)
|
||||
self._args = args
|
||||
if self._isyieldedfunction():
|
||||
assert not callspec, "yielded functions (deprecated) cannot have funcargs"
|
||||
|
@ -383,7 +448,7 @@ class Function(FunctionMixin, py.test.collect.Item):
|
|||
def setup(self):
|
||||
super(Function, self).setup()
|
||||
if hasattr(self, 'funcargs'):
|
||||
funcargs.fillfuncargs(self)
|
||||
fillfuncargs(self)
|
||||
|
||||
def __eq__(self, other):
|
||||
try:
|
||||
|
@ -409,3 +474,229 @@ def hasinit(obj):
|
|||
if init:
|
||||
if init != object.__init__:
|
||||
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.logfile = logfile # preferably line buffered
|
||||
|
||||
def write_log_entry(self, testpath, shortrepr, longrepr):
|
||||
print_("%s %s" % (shortrepr, testpath), file=self.logfile)
|
||||
def write_log_entry(self, testpath, lettercode, longrepr):
|
||||
print_("%s %s" % (lettercode, testpath), file=self.logfile)
|
||||
for line in longrepr.splitlines():
|
||||
print_(" %s" % line, file=self.logfile)
|
||||
|
||||
def log_outcome(self, node, shortrepr, longrepr):
|
||||
testpath = generic_path(node)
|
||||
self.write_log_entry(testpath, shortrepr, longrepr)
|
||||
def log_outcome(self, report, lettercode, longrepr):
|
||||
testpath = getattr(report, 'nodeid', None)
|
||||
if testpath is None:
|
||||
testpath = report.fspath
|
||||
self.write_log_entry(testpath, lettercode, longrepr)
|
||||
|
||||
def pytest_runtest_logreport(self, report):
|
||||
res = self.config.hook.pytest_report_teststatus(report=report)
|
||||
if res is not None:
|
||||
code = res[1]
|
||||
else:
|
||||
code = report.shortrepr
|
||||
if code == 'x':
|
||||
longrepr = str(report.longrepr)
|
||||
elif code == 'X':
|
||||
|
@ -82,7 +81,7 @@ class ResultLog(object):
|
|||
longrepr = str(report.longrepr)
|
||||
elif report.skipped:
|
||||
longrepr = str(report.longrepr.reprcrash.message)
|
||||
self.log_outcome(report.item, code, longrepr)
|
||||
self.log_outcome(report, code, longrepr)
|
||||
|
||||
def pytest_collectreport(self, report):
|
||||
if not report.passed:
|
||||
|
@ -92,7 +91,7 @@ class ResultLog(object):
|
|||
assert report.skipped
|
||||
code = "S"
|
||||
longrepr = str(report.longrepr.reprcrash)
|
||||
self.log_outcome(report.collector, code, longrepr)
|
||||
self.log_outcome(report, code, longrepr)
|
||||
|
||||
def pytest_internalerror(self, excrepr):
|
||||
path = excrepr.reprcrash.path
|
||||
|
|
|
@ -3,6 +3,7 @@ collect and run test items and create reports.
|
|||
"""
|
||||
|
||||
import py, sys
|
||||
from py._code.code import TerminalRepr
|
||||
|
||||
def pytest_namespace():
|
||||
return {
|
||||
|
@ -26,19 +27,35 @@ def pytest_sessionfinish(session, exitstatus):
|
|||
hook = session.config.hook
|
||||
rep = hook.pytest__teardown_final(session=session)
|
||||
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):
|
||||
result = excinfo = None
|
||||
class NodeInfo:
|
||||
def __init__(self, nodeid, nodenames, fspath, location):
|
||||
self.nodeid = nodeid
|
||||
self.nodenames = nodenames
|
||||
self.fspath = fspath
|
||||
self.location = location
|
||||
|
||||
def getitemnodeinfo(item):
|
||||
try:
|
||||
result = collector._memocollect()
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except:
|
||||
excinfo = py.code.ExceptionInfo()
|
||||
return CollectReport(collector, result, excinfo)
|
||||
return item._nodeinfo
|
||||
except AttributeError:
|
||||
location = item.ihook.pytest_report_iteminfo(item=item)
|
||||
location = (str(location[0]), location[1], str(location[2]))
|
||||
nodenames = tuple(item.listnames())
|
||||
nodeid = item.collection.getid(item)
|
||||
fspath = item.fspath
|
||||
item._nodeinfo = n = NodeInfo(nodeid, nodenames, fspath, location)
|
||||
return n
|
||||
|
||||
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)
|
||||
return True
|
||||
|
||||
|
@ -57,9 +74,6 @@ def pytest_runtest_call(item):
|
|||
if not item._deprecated_testexecution():
|
||||
item.runtest()
|
||||
|
||||
def pytest_runtest_makereport(item, call):
|
||||
return ItemTestReport(item, call.excinfo, call.when)
|
||||
|
||||
def pytest_runtest_teardown(item):
|
||||
item.config._setupstate.teardown_exact(item)
|
||||
|
||||
|
@ -68,8 +82,8 @@ def pytest__teardown_final(session):
|
|||
if call.excinfo:
|
||||
ntraceback = call.excinfo.traceback .cut(excludepath=py._pydir)
|
||||
call.excinfo.traceback = ntraceback.filter()
|
||||
rep = TeardownErrorReport(call.excinfo)
|
||||
return rep
|
||||
longrepr = call.excinfo.getrepr(funcargs=True)
|
||||
return TeardownErrorReport(longrepr)
|
||||
|
||||
def pytest_report_teststatus(report):
|
||||
if report.when in ("setup", "teardown"):
|
||||
|
@ -80,6 +94,8 @@ def pytest_report_teststatus(report):
|
|||
return "skipped", "s", "SKIPPED"
|
||||
else:
|
||||
return "", "", ""
|
||||
|
||||
|
||||
#
|
||||
# Implementation
|
||||
|
||||
|
@ -115,123 +131,117 @@ class CallInfo:
|
|||
return "<CallInfo when=%r %s>" % (self.when, status)
|
||||
|
||||
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):
|
||||
for line in self.headerlines:
|
||||
out.line(line)
|
||||
longrepr = self.longrepr
|
||||
if hasattr(longrepr, 'toterminal'):
|
||||
longrepr.toterminal(out)
|
||||
else:
|
||||
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):
|
||||
super(CollectErrorRepr, self).__init__()
|
||||
self.longrepr = msg
|
||||
def toterminal(self, out):
|
||||
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):
|
||||
""" shared state for setting up/tearing down test items or collectors. """
|
||||
def __init__(self):
|
||||
|
|
|
@ -231,19 +231,16 @@ def pytest_runtest_makereport(__multicall__, item, call):
|
|||
if not item.config.getvalue("runxfail"):
|
||||
rep = __multicall__.execute()
|
||||
rep.keywords['xfail'] = "reason: " + call.excinfo.value.msg
|
||||
rep.skipped = True
|
||||
rep.failed = False
|
||||
rep.outcome = "skipped"
|
||||
return rep
|
||||
if call.when == "call":
|
||||
rep = __multicall__.execute()
|
||||
evalxfail = getattr(item, '_evalxfail')
|
||||
if not item.config.getvalue("runxfail") and evalxfail.istrue():
|
||||
if call.excinfo:
|
||||
rep.skipped = True
|
||||
rep.failed = rep.passed = False
|
||||
rep.outcome = "skipped"
|
||||
else:
|
||||
rep.skipped = rep.passed = False
|
||||
rep.failed = True
|
||||
rep.outcome = "failed"
|
||||
rep.keywords['xfail'] = evalxfail.getexplanation()
|
||||
else:
|
||||
if 'xfail' in rep.keywords:
|
||||
|
@ -275,9 +272,9 @@ def pytest_terminal_summary(terminalreporter):
|
|||
show_xfailed(terminalreporter, lines)
|
||||
elif char == "X":
|
||||
show_xpassed(terminalreporter, lines)
|
||||
elif char == "f":
|
||||
elif char in "fF":
|
||||
show_failed(terminalreporter, lines)
|
||||
elif char == "s":
|
||||
elif char in "sS":
|
||||
show_skipped(terminalreporter, lines)
|
||||
if lines:
|
||||
tr._tw.sep("=", "short test summary info")
|
||||
|
@ -289,22 +286,24 @@ def show_failed(terminalreporter, lines):
|
|||
failed = terminalreporter.stats.get("failed")
|
||||
if failed:
|
||||
for rep in failed:
|
||||
pos = terminalreporter.gettestid(rep.item)
|
||||
pos = rep.nodeid
|
||||
lines.append("FAIL %s" %(pos, ))
|
||||
|
||||
def show_xfailed(terminalreporter, lines):
|
||||
xfailed = terminalreporter.stats.get("xfailed")
|
||||
if xfailed:
|
||||
for rep in xfailed:
|
||||
pos = terminalreporter.gettestid(rep.item)
|
||||
pos = rep.nodeid
|
||||
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):
|
||||
xpassed = terminalreporter.stats.get("xpassed")
|
||||
if xpassed:
|
||||
for rep in xpassed:
|
||||
pos = terminalreporter.gettestid(rep.item)
|
||||
pos = rep.nodeid
|
||||
reason = rep.keywords['xfail']
|
||||
lines.append("XPASS %s %s" %(pos, reason))
|
||||
|
||||
|
|
|
@ -22,31 +22,19 @@ def pytest_addoption(parser):
|
|||
help="(deprecated, use -r)")
|
||||
group._addoption('--tb', metavar="style",
|
||||
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).")
|
||||
group._addoption('--fulltrace',
|
||||
action="store_true", dest="fulltrace", default=False,
|
||||
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):
|
||||
if config.option.showfuncargs:
|
||||
return
|
||||
if config.option.collectonly:
|
||||
reporter = CollectonlyReporter(config)
|
||||
elif config.option.showfuncargs:
|
||||
config.setsessionclass(ShowFuncargSession)
|
||||
reporter = None
|
||||
else:
|
||||
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')
|
||||
|
||||
def getreportopt(config):
|
||||
|
@ -69,6 +57,17 @@ def getreportopt(config):
|
|||
reportopts += char
|
||||
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:
|
||||
def __init__(self, config, file=None):
|
||||
self.config = config
|
||||
|
@ -85,12 +84,12 @@ class TerminalReporter:
|
|||
return char in self.reportchars
|
||||
|
||||
def write_fspath_result(self, fspath, res):
|
||||
fspath = self.curdir.bestrelpath(fspath)
|
||||
if fspath != self.currentfspath:
|
||||
self.currentfspath = fspath
|
||||
fspath = self.curdir.bestrelpath(fspath)
|
||||
self._tw.line()
|
||||
relpath = self.curdir.bestrelpath(fspath)
|
||||
self._tw.write(relpath + " ")
|
||||
self.currentfspath = fspath
|
||||
self._tw.write(res)
|
||||
|
||||
def write_ensure_prefix(self, prefix, extra="", **kwargs):
|
||||
|
@ -116,42 +115,6 @@ class TerminalReporter:
|
|||
self.ensure_newline()
|
||||
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):
|
||||
for line in str(excrepr).split("\n"):
|
||||
self.write_line("INTERNALERROR> " + line)
|
||||
|
@ -170,37 +133,47 @@ class TerminalReporter:
|
|||
self.write_line("[%s] %s" %(category, msg))
|
||||
|
||||
def pytest_deselected(self, items):
|
||||
self.stats.setdefault('deselected', []).append(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), "")
|
||||
self.stats.setdefault('deselected', []).extend(items)
|
||||
|
||||
def pytest__teardown_final_logerror(self, 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):
|
||||
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:
|
||||
# probably passed setup/teardown
|
||||
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):
|
||||
word, markup = word
|
||||
else:
|
||||
markup = {}
|
||||
self.stats.setdefault(cat, []).append(rep)
|
||||
if not self.config.option.verbose:
|
||||
self.write_fspath_result(self._getfspath(rep.item), letter)
|
||||
else:
|
||||
line = self._reportinfoline(rep.item)
|
||||
if rep.passed:
|
||||
markup = {'green':True}
|
||||
elif rep.failed:
|
||||
markup = {'red':True}
|
||||
elif rep.skipped:
|
||||
markup = {'yellow':True}
|
||||
line = self._locationline(str(rep.fspath), *rep.location)
|
||||
if not hasattr(rep, 'node'):
|
||||
self.write_ensure_prefix(line, word, **markup)
|
||||
#self._tw.write(word, **markup)
|
||||
else:
|
||||
self.ensure_newline()
|
||||
if hasattr(rep, 'node'):
|
||||
|
@ -213,25 +186,27 @@ class TerminalReporter:
|
|||
if not report.passed:
|
||||
if report.failed:
|
||||
self.stats.setdefault("error", []).append(report)
|
||||
self.write_fspath_result(report.collector.fspath, "E")
|
||||
self.write_fspath_result(report.fspath, "E")
|
||||
elif report.skipped:
|
||||
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):
|
||||
self.write_sep("=", "test session starts", bold=True)
|
||||
self._sessionstarttime = py.std.time.time()
|
||||
|
||||
verinfo = ".".join(map(str, sys.version_info[:3]))
|
||||
msg = "platform %s -- Python %s" % (sys.platform, verinfo)
|
||||
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)
|
||||
self.write_line(msg)
|
||||
lines = self.config.hook.pytest_report_header(config=self.config)
|
||||
lines.reverse()
|
||||
for line in flatten(lines):
|
||||
self.write_line(line)
|
||||
|
||||
def pytest_log_finishcollection(self):
|
||||
for i, testarg in enumerate(self.config.args):
|
||||
self.write_line("test path %d: %s" %(i+1, testarg))
|
||||
|
||||
|
@ -260,52 +235,40 @@ class TerminalReporter:
|
|||
else:
|
||||
excrepr.reprcrash.toterminal(self._tw)
|
||||
|
||||
def _reportinfoline(self, item):
|
||||
collect_fspath = self._getfspath(item)
|
||||
fspath, lineno, msg = self._getreportinfo(item)
|
||||
def _locationline(self, collect_fspath, fspath, lineno, domain):
|
||||
if fspath and fspath != collect_fspath:
|
||||
fspath = "%s <- %s" % (
|
||||
self.curdir.bestrelpath(collect_fspath),
|
||||
self.curdir.bestrelpath(fspath))
|
||||
self.curdir.bestrelpath(py.path.local(collect_fspath)),
|
||||
self.curdir.bestrelpath(py.path.local(fspath)))
|
||||
elif fspath:
|
||||
fspath = self.curdir.bestrelpath(fspath)
|
||||
fspath = self.curdir.bestrelpath(py.path.local(fspath))
|
||||
if lineno is not None:
|
||||
lineno += 1
|
||||
if fspath and lineno and msg:
|
||||
line = "%(fspath)s:%(lineno)s: %(msg)s"
|
||||
elif fspath and msg:
|
||||
line = "%(fspath)s: %(msg)s"
|
||||
if fspath and lineno and domain:
|
||||
line = "%(fspath)s:%(lineno)s: %(domain)s"
|
||||
elif fspath and domain:
|
||||
line = "%(fspath)s: %(domain)s"
|
||||
elif fspath and lineno:
|
||||
line = "%(fspath)s:%(lineno)s %(extrapath)s"
|
||||
else:
|
||||
line = "[noreportinfo]"
|
||||
line = "[nolocation]"
|
||||
return line % locals() + " "
|
||||
|
||||
def _getfailureheadline(self, rep):
|
||||
if hasattr(rep, "collector"):
|
||||
return str(rep.collector.fspath)
|
||||
elif hasattr(rep, 'item'):
|
||||
fspath, lineno, msg = self._getreportinfo(rep.item)
|
||||
return msg
|
||||
if hasattr(rep, 'location'):
|
||||
fspath, lineno, domain = rep.location
|
||||
return domain
|
||||
else:
|
||||
return "test session"
|
||||
return "test session" # XXX?
|
||||
|
||||
def _getreportinfo(self, item):
|
||||
def _getcrashline(self, rep):
|
||||
try:
|
||||
return item.__reportinfo
|
||||
return str(rep.longrepr.reprcrash)
|
||||
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:
|
||||
return item.fspath
|
||||
return str(rep.longrepr)[:50]
|
||||
except AttributeError:
|
||||
fspath, lineno, msg = self._getreportinfo(item)
|
||||
return fspath
|
||||
return ""
|
||||
|
||||
#
|
||||
# summaries for sessionfinish
|
||||
|
@ -317,7 +280,7 @@ class TerminalReporter:
|
|||
self.write_sep("=", "FAILURES")
|
||||
for rep in self.stats['failed']:
|
||||
if tbstyle == "line":
|
||||
line = rep._getcrashline()
|
||||
line = self._getcrashline(rep)
|
||||
self.write_line(line)
|
||||
else:
|
||||
msg = self._getfailureheadline(rep)
|
||||
|
@ -383,21 +346,27 @@ class CollectonlyReporter:
|
|||
self.outindent(collector)
|
||||
self.indent += self.INDENT
|
||||
|
||||
def pytest_itemstart(self, item, node=None):
|
||||
def pytest_log_itemcollect(self, item):
|
||||
self.outindent(item)
|
||||
|
||||
def pytest_collectreport(self, report):
|
||||
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.indent = self.indent[:-len(self.INDENT)]
|
||||
|
||||
def pytest_sessionfinish(self, session, exitstatus):
|
||||
def pytest_log_finishcollection(self):
|
||||
if self._failed:
|
||||
self._tw.sep("!", "collection failures")
|
||||
for rep in self._failed:
|
||||
rep.toterminal(self._tw)
|
||||
|
||||
return self._failed and 1 or 0
|
||||
|
||||
def repr_pythonversion(v=None):
|
||||
if v is None:
|
||||
|
@ -415,50 +384,3 @@ def flatten(l):
|
|||
else:
|
||||
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.
|
||||
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.parent = parent
|
||||
self.config = config or parent.config
|
||||
self.collection = collection or getattr(parent, 'collection', None)
|
||||
self.fspath = getattr(parent, 'fspath', None)
|
||||
self.ihook = HookProxy(self)
|
||||
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):
|
||||
if getattr(self.config.option, 'debug', False):
|
||||
return "<%s %r %0x>" %(self.__class__.__name__,
|
||||
|
@ -79,7 +47,8 @@ class Node(object):
|
|||
def __eq__(self, other):
|
||||
if not isinstance(other, Node):
|
||||
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):
|
||||
return not self == other
|
||||
|
@ -102,7 +71,7 @@ class Node(object):
|
|||
return getattr(self, attrname)
|
||||
try:
|
||||
res = function()
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
except py.builtin._sysex:
|
||||
raise
|
||||
except:
|
||||
failure = py.std.sys.exc_info()
|
||||
|
@ -117,7 +86,7 @@ class Node(object):
|
|||
l = [self]
|
||||
while 1:
|
||||
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)
|
||||
else:
|
||||
return l
|
||||
|
@ -137,39 +106,6 @@ class Node(object):
|
|||
def _keywords(self):
|
||||
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):
|
||||
return traceback
|
||||
|
||||
|
@ -190,7 +126,6 @@ class Node(object):
|
|||
style=style)
|
||||
|
||||
repr_failure = _repr_failure_py
|
||||
shortfailurerepr = "F"
|
||||
|
||||
class Collector(Node):
|
||||
"""
|
||||
|
@ -270,19 +205,12 @@ class Collector(Node):
|
|||
return traceback
|
||||
|
||||
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)
|
||||
super(FSCollector, self).__init__(fspath.basename, parent, config=config)
|
||||
super(FSCollector, self).__init__(fspath.basename,
|
||||
parent, config, collection)
|
||||
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):
|
||||
""" base class for collecting tests from a file. """
|
||||
|
||||
|
@ -368,59 +296,3 @@ def warnoldtestrun(function=None):
|
|||
"item.run() and item.execute()",
|
||||
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.pluginmanager import PluginManager
|
||||
from py._test import parseopt
|
||||
from py._test.collect import RootCollector
|
||||
|
||||
def ensuretemp(string, dir=1):
|
||||
""" (deprecated) return temporary directory path with
|
||||
|
@ -31,9 +30,8 @@ class Config(object):
|
|||
basetemp = None
|
||||
_sessionclass = None
|
||||
|
||||
def __init__(self, topdir=None, option=None):
|
||||
self.option = option or CmdOptions()
|
||||
self.topdir = topdir
|
||||
def __init__(self):
|
||||
self.option = CmdOptions()
|
||||
self._parser = parseopt.Parser(
|
||||
usage="usage: %prog [options] [file_or_dir] [file_or_dir] [...]",
|
||||
processopt=self._processopt,
|
||||
|
@ -97,39 +95,7 @@ class Config(object):
|
|||
args = self._parser.parse_setoption(args, self.option)
|
||||
if not args:
|
||||
args.append(py.std.os.getcwd())
|
||||
self.topdir = gettopdir(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)
|
||||
self.args = args
|
||||
|
||||
def ensuretemp(self, string, dir=True):
|
||||
return self.getbasetemp().ensure(string, dir=dir)
|
||||
|
@ -154,27 +120,6 @@ class Config(object):
|
|||
return py.path.local.make_numbered_dir(prefix=basename,
|
||||
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):
|
||||
try:
|
||||
cls = self._conftest.rget(name, path)
|
||||
|
@ -239,48 +184,10 @@ class Config(object):
|
|||
except AttributeError:
|
||||
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
|
||||
#
|
||||
|
||||
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():
|
||||
# it's enough to have our containing module loaded as
|
||||
# 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
|
||||
|
||||
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 "
|
||||
"junitxml doctest").split()
|
||||
"junitxml doctest keyword").split()
|
||||
|
||||
def check_old_use(mod, modname):
|
||||
clsname = modname[len('pytest_'):].capitalize() + "Plugin"
|
||||
|
@ -32,7 +32,7 @@ class PluginManager(object):
|
|||
name = id(plugin)
|
||||
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.registry.isregistered(plugin), plugin
|
||||
name = self._getpluginname(plugin, name)
|
||||
|
@ -41,7 +41,7 @@ class PluginManager(object):
|
|||
self._name2plugin[name] = plugin
|
||||
self.call_plugin(plugin, "pytest_addhooks", {'pluginmanager': self})
|
||||
self.hook.pytest_plugin_registered(manager=self, plugin=plugin)
|
||||
self.registry.register(plugin)
|
||||
self.registry.register(plugin, prepend=prepend)
|
||||
return True
|
||||
|
||||
def unregister(self, plugin):
|
||||
|
@ -259,6 +259,8 @@ class MultiCall:
|
|||
return kwargs
|
||||
|
||||
def varnames(func):
|
||||
if not inspect.isfunction(func) and not inspect.ismethod(func):
|
||||
func = getattr(func, '__call__', func)
|
||||
ismethod = inspect.ismethod(func)
|
||||
rawcode = py.code.getrawcode(func)
|
||||
try:
|
||||
|
@ -275,10 +277,13 @@ class Registry:
|
|||
plugins = []
|
||||
self._plugins = plugins
|
||||
|
||||
def register(self, plugin):
|
||||
def register(self, plugin, prepend=False):
|
||||
assert not isinstance(plugin, str)
|
||||
assert not plugin in self._plugins
|
||||
if not prepend:
|
||||
self._plugins.append(plugin)
|
||||
else:
|
||||
self._plugins.insert(0, plugin)
|
||||
|
||||
def unregister(self, plugin):
|
||||
self._plugins.remove(plugin)
|
||||
|
|
|
@ -6,6 +6,25 @@
|
|||
"""
|
||||
|
||||
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
|
||||
EXIT_OK = 0
|
||||
|
@ -14,10 +33,6 @@ EXIT_INTERRUPTED = 2
|
|||
EXIT_INTERNALERROR = 3
|
||||
EXIT_NOHOSTS = 4
|
||||
|
||||
# imports used for genitems()
|
||||
Item = py.test.collect.Item
|
||||
Collector = py.test.collect.Collector
|
||||
|
||||
class Session(object):
|
||||
nodeid = ""
|
||||
class Interrupted(KeyboardInterrupt):
|
||||
|
@ -26,67 +41,10 @@ class Session(object):
|
|||
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
self.pluginmanager = config.pluginmanager # shortcut
|
||||
self.pluginmanager.register(self)
|
||||
self.config.pluginmanager.register(self, name="session", prepend=True)
|
||||
self._testsfailed = 0
|
||||
self._nomatch = False
|
||||
self.shouldstop = False
|
||||
|
||||
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)
|
||||
self.collection = Collection(config) # XXX move elswehre
|
||||
|
||||
def pytest_runtest_logreport(self, report):
|
||||
if report.failed:
|
||||
|
@ -95,41 +53,195 @@ class Session(object):
|
|||
if maxfail and self._testsfailed >= maxfail:
|
||||
self.shouldstop = "stopping after %d failures" % (
|
||||
self._testsfailed)
|
||||
self.collection.shouldstop = self.shouldstop
|
||||
pytest_collectreport = pytest_runtest_logreport
|
||||
|
||||
def sessionfinishes(self, exitstatus):
|
||||
""" teardown any resources after a test run. """
|
||||
self.config.hook.pytest_sessionfinish(
|
||||
session=self,
|
||||
exitstatus=exitstatus,
|
||||
)
|
||||
|
||||
def main(self, colitems):
|
||||
def main(self):
|
||||
""" main loop for running tests. """
|
||||
self.shouldstop = False
|
||||
self.sessionstarts()
|
||||
exitstatus = EXIT_OK
|
||||
self.exitstatus = EXIT_OK
|
||||
config = self.config
|
||||
try:
|
||||
self._mainloop(colitems)
|
||||
if self._testsfailed:
|
||||
exitstatus = EXIT_TESTSFAILED
|
||||
self.sessionfinishes(exitstatus=exitstatus)
|
||||
config.pluginmanager.do_configure(config)
|
||||
config.hook.pytest_sessionstart(session=self)
|
||||
config.hook.pytest_perform_collection(session=self)
|
||||
config.hook.pytest_runtest_mainloop(session=self)
|
||||
except self.config.Error:
|
||||
raise
|
||||
except KeyboardInterrupt:
|
||||
excinfo = py.code.ExceptionInfo()
|
||||
self.config.hook.pytest_keyboard_interrupt(excinfo=excinfo)
|
||||
exitstatus = EXIT_INTERRUPTED
|
||||
self.exitstatus = EXIT_INTERRUPTED
|
||||
except:
|
||||
excinfo = py.code.ExceptionInfo()
|
||||
self.config.pluginmanager.notify_exception(excinfo)
|
||||
exitstatus = EXIT_INTERNALERROR
|
||||
if exitstatus in (EXIT_INTERNALERROR, EXIT_INTERRUPTED):
|
||||
self.sessionfinishes(exitstatus=exitstatus)
|
||||
return exitstatus
|
||||
self.exitstatus = EXIT_INTERNALERROR
|
||||
if excinfo.errisinstance(SystemExit):
|
||||
sys.stderr.write("mainloop: caught Spurious SystemExit!\n")
|
||||
|
||||
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
|
||||
|
||||
Platforms: Linux, Win32, OSX
|
||||
|
||||
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.path`: http://pylib.org/path.html
|
||||
|
@ -26,14 +30,14 @@ def main():
|
|||
name='py',
|
||||
description='py.test and pylib: rapid testing and development utils.',
|
||||
long_description = long_description,
|
||||
version= '1.3.4a1',
|
||||
version= '1.4.0a1',
|
||||
url='http://pylib.org',
|
||||
license='MIT license',
|
||||
platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],
|
||||
author='holger krekel, Guido Wesdorp, Carl Friedrich Bolz, Armin Rigo, Maciej Fijalkowski & others',
|
||||
author_email='holger at merlinux.eu',
|
||||
entry_points= make_entry_points(),
|
||||
classifiers=['Development Status :: 5 - Production/Stable',
|
||||
classifiers=['Development Status :: 6 - Mature',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: MIT License',
|
||||
'Operating System :: POSIX',
|
||||
|
|
|
@ -72,7 +72,7 @@ class TestGeneralUsage:
|
|||
result = testdir.runpytest(p1, p2)
|
||||
assert result.ret != 0
|
||||
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):
|
||||
testdir.makeconftest("""
|
||||
import py
|
||||
|
@ -163,3 +162,52 @@ class TestGeneralUsage:
|
|||
"""))
|
||||
result = testdir.runpython(p, prepend=False)
|
||||
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)
|
||||
assert py.builtin.callable(main)
|
||||
assert name[:2] == "py"
|
||||
if pytestconfig.getvalue("toolsonpath"):
|
||||
if not pytestconfig.getvalue("notoolsonpath"):
|
||||
scriptname = "py." + name[2:]
|
||||
assert py.path.local.sysfind(scriptname), scriptname
|
||||
|
||||
|
|
|
@ -217,3 +217,13 @@ def test_underscore_api():
|
|||
py.code._AssertionError
|
||||
py.code._reinterpret_old # used by pypy
|
||||
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.toterminal(tw)
|
||||
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())
|
||||
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):
|
||||
# this test fails because the implicit inspect.getsource(A) below
|
||||
# does not return the "x = 1" last line.
|
||||
|
|
|
@ -306,9 +306,11 @@ class TestImport:
|
|||
|
||||
def test_pyimport_dir(self, tmpdir):
|
||||
p = tmpdir.join("hello_123")
|
||||
p.ensure("__init__.py")
|
||||
p_init = p.ensure("__init__.py")
|
||||
m = p.pyimport()
|
||||
assert m.__name__ == "hello_123"
|
||||
m = p_init.pyimport()
|
||||
assert m.__name__ == "hello_123"
|
||||
|
||||
def test_pyimport_execfile_different_name(self, path1):
|
||||
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):
|
||||
testdir.makepyfile("""
|
||||
def test_hello():
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
from py._plugin.pytest_doctest import DoctestModule, DoctestTextfile
|
||||
import py
|
||||
|
||||
pytest_plugins = ["pytest_doctest"]
|
||||
|
||||
|
@ -73,16 +74,16 @@ class TestDoctests:
|
|||
reprec = testdir.inline_run(p, "--doctest-modules")
|
||||
reprec.assertoutcome(failed=1)
|
||||
|
||||
def test_doctestmodule_external(self, testdir):
|
||||
p = testdir.makepyfile("""
|
||||
#
|
||||
def test_doctestmodule_external_and_issue116(self, testdir):
|
||||
p = testdir.mkpydir("hello")
|
||||
p.join("__init__.py").write(py.code.Source("""
|
||||
def somefunc():
|
||||
'''
|
||||
>>> i = 0
|
||||
>>> i + 1
|
||||
2
|
||||
'''
|
||||
""")
|
||||
"""))
|
||||
result = testdir.runpytest(p, "--doctest-modules")
|
||||
result.stdout.fnmatch_lines([
|
||||
'004 *>>> i = 0',
|
||||
|
|
|
@ -26,6 +26,7 @@ def test_gen(testdir, anypython, standalone):
|
|||
"*imported from*mypytest"
|
||||
])
|
||||
|
||||
@py.test.mark.xfail(reason="fix-dist", run=False)
|
||||
def test_rundist(testdir, pytestconfig, standalone):
|
||||
pytestconfig.pluginmanager.skipifmissing("xdist")
|
||||
testdir.makepyfile("""
|
||||
|
|
|
@ -8,5 +8,5 @@ def test_functional(testdir):
|
|||
testdir.runpytest("--hooklog=hook.log")
|
||||
s = testdir.tmpdir.join("hook.log").read()
|
||||
assert s.find("pytest_sessionstart") != -1
|
||||
assert s.find("ItemTestReport") != -1
|
||||
assert s.find("TestReport") != -1
|
||||
assert s.find("sessionfinish") != -1
|
||||
|
|
|
@ -64,7 +64,7 @@ class TestKeywordSelection:
|
|||
reprec = testdir.inline_run("-s", "-k", keyword, file_test)
|
||||
passed, skipped, failed = reprec.listoutcomes()
|
||||
assert len(failed) == 1
|
||||
assert failed[0].item.name == name
|
||||
assert failed[0].nodeid.split("::")[-1] == name
|
||||
assert len(reprec.getcalls('pytest_deselected')) == 1
|
||||
|
||||
for keyword in ['test_one', 'est_on']:
|
||||
|
@ -92,7 +92,7 @@ class TestKeywordSelection:
|
|||
py.builtin.print_("keyword", repr(keyword))
|
||||
passed, skipped, failed = reprec.listoutcomes()
|
||||
assert len(passed) == 1
|
||||
assert passed[0].item.name == "test_2"
|
||||
assert passed[0].nodeid.endswith("test_2")
|
||||
dlist = reprec.getcalls("pytest_deselected")
|
||||
assert len(dlist) == 1
|
||||
assert dlist[0].items[0].name == 'test_1'
|
|
@ -5,6 +5,8 @@ def test_reportrecorder(testdir):
|
|||
item = testdir.getitem("def test_func(): pass")
|
||||
recorder = testdir.getreportrecorder(item.config)
|
||||
assert not recorder.getfailures()
|
||||
|
||||
py.test.xfail("internal reportrecorder tests need refactoring")
|
||||
class rep:
|
||||
excinfo = None
|
||||
passed = False
|
||||
|
|
|
@ -1,5 +1,521 @@
|
|||
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 f(): pass
|
||||
|
@ -593,3 +1109,28 @@ def test_funcarg_lookup_error(testdir):
|
|||
"*1 error*",
|
||||
])
|
||||
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, \
|
||||
pytest_configure, pytest_unconfigure
|
||||
from py._test.collect import Node, Item, FSCollector
|
||||
from py._test.session import Collection
|
||||
|
||||
def test_generic_path(testdir):
|
||||
config = testdir.parseconfig()
|
||||
p1 = Node('a', parent=config._rootcol)
|
||||
collection = Collection(config)
|
||||
p1 = Node('a', config=config, collection=collection)
|
||||
#assert p1.fspath is None
|
||||
p2 = Node('B', parent=p1)
|
||||
p3 = Node('()', parent = p2)
|
||||
|
@ -15,7 +17,7 @@ def test_generic_path(testdir):
|
|||
res = generic_path(item)
|
||||
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)
|
||||
p2 = Node('B', parent=p1)
|
||||
p3 = Node('()', parent = p2)
|
||||
|
|
|
@ -53,8 +53,8 @@ class BaseFunctionalTests:
|
|||
rep = reports[1]
|
||||
assert rep.passed
|
||||
assert not rep.failed
|
||||
assert rep.shortrepr == "."
|
||||
assert not hasattr(rep, 'longrepr')
|
||||
assert rep.outcome == "passed"
|
||||
assert not rep.longrepr
|
||||
|
||||
def test_failfunction(self, testdir):
|
||||
reports = testdir.runitem("""
|
||||
|
@ -66,23 +66,8 @@ class BaseFunctionalTests:
|
|||
assert not rep.skipped
|
||||
assert rep.failed
|
||||
assert rep.when == "call"
|
||||
assert isinstance(rep.longrepr, ReprExceptionInfo)
|
||||
assert str(rep.shortrepr) == "F"
|
||||
|
||||
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():*"
|
||||
])
|
||||
assert rep.outcome == "failed"
|
||||
#assert isinstance(rep.longrepr, ReprExceptionInfo)
|
||||
|
||||
def test_skipfunction(self, testdir):
|
||||
reports = testdir.runitem("""
|
||||
|
@ -94,6 +79,7 @@ class BaseFunctionalTests:
|
|||
assert not rep.failed
|
||||
assert not rep.passed
|
||||
assert rep.skipped
|
||||
assert rep.outcome == "skipped"
|
||||
#assert rep.skipped.when == "call"
|
||||
#assert rep.skipped.when == "call"
|
||||
#assert rep.skipped == "%sreason == "hello"
|
||||
|
@ -150,8 +136,8 @@ class BaseFunctionalTests:
|
|||
assert not rep.passed
|
||||
assert rep.failed
|
||||
assert rep.when == "teardown"
|
||||
assert rep.longrepr.reprcrash.lineno == 3
|
||||
assert rep.longrepr.reprtraceback.reprentries
|
||||
#assert rep.longrepr.reprcrash.lineno == 3
|
||||
#assert rep.longrepr.reprtraceback.reprentries
|
||||
|
||||
def test_custom_failure_repr(self, testdir):
|
||||
testdir.makepyfile(conftest="""
|
||||
|
@ -270,6 +256,10 @@ class TestCollectionReports:
|
|||
assert not rep.failed
|
||||
assert not rep.skipped
|
||||
assert rep.passed
|
||||
locinfo = rep.location
|
||||
assert locinfo[0] == col.fspath
|
||||
assert not locinfo[1]
|
||||
assert locinfo[2] == col.fspath
|
||||
res = rep.result
|
||||
assert len(res) == 2
|
||||
assert res[0].name == "test_func1"
|
||||
|
@ -299,7 +289,7 @@ def test_callinfo():
|
|||
assert "exc" in repr(ci)
|
||||
|
||||
# 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
|
||||
def test_runtest_in_module_ordering(testdir):
|
||||
p1 = testdir.makepyfile("""
|
||||
|
|
|
@ -183,8 +183,10 @@ class TestXFail:
|
|||
""")
|
||||
result = testdir.runpytest(p, '--report=xfailed', )
|
||||
result.stdout.fnmatch_lines([
|
||||
"*test_one*test_this*NOTRUN*noway",
|
||||
"*test_one*test_this_true*NOTRUN*condition:*True*",
|
||||
"*test_one*test_this*",
|
||||
"*NOTRUN*noway",
|
||||
"*test_one*test_this_true*",
|
||||
"*NOTRUN*condition:*True*",
|
||||
"*1 passed*",
|
||||
])
|
||||
|
||||
|
@ -199,7 +201,8 @@ class TestXFail:
|
|||
""")
|
||||
result = testdir.runpytest(p, '--report=xfailed', )
|
||||
result.stdout.fnmatch_lines([
|
||||
"*test_one*test_this*NOTRUN*hello",
|
||||
"*test_one*test_this*",
|
||||
"*NOTRUN*hello",
|
||||
"*1 xfailed*",
|
||||
])
|
||||
|
||||
|
@ -229,7 +232,8 @@ class TestXFail:
|
|||
])
|
||||
result = testdir.runpytest(p, "-rx")
|
||||
result.stdout.fnmatch_lines([
|
||||
"*XFAIL*test_this*reason:*hello*",
|
||||
"*XFAIL*test_this*",
|
||||
"*reason:*hello*",
|
||||
])
|
||||
result = testdir.runpytest(p, "--runxfail")
|
||||
result.stdout.fnmatch_lines([
|
||||
|
@ -252,7 +256,8 @@ class TestXFail:
|
|||
])
|
||||
result = testdir.runpytest(p, "-rx")
|
||||
result.stdout.fnmatch_lines([
|
||||
"*XFAIL*test_this*reason:*hello*",
|
||||
"*XFAIL*test_this*",
|
||||
"*reason:*hello*",
|
||||
])
|
||||
result = testdir.runpytest(p, "--runxfail")
|
||||
result.stdout.fnmatch_lines([
|
||||
|
@ -286,7 +291,8 @@ class TestXFail:
|
|||
""")
|
||||
result = testdir.runpytest(p, '-rxX')
|
||||
result.stdout.fnmatch_lines([
|
||||
"*XFAIL*test_this*NOTRUN*",
|
||||
"*XFAIL*test_this*",
|
||||
"*NOTRUN*",
|
||||
])
|
||||
|
||||
def test_dynamic_xfail_set_during_funcarg_setup(self, testdir):
|
||||
|
@ -360,7 +366,6 @@ def test_skipif_class(testdir):
|
|||
|
||||
|
||||
def test_skip_reasons_folding():
|
||||
from py._plugin import pytest_runner as runner
|
||||
from py._plugin.pytest_skipping import folded_skips
|
||||
class longrepr:
|
||||
class reprcrash:
|
||||
|
@ -368,12 +373,15 @@ def test_skip_reasons_folding():
|
|||
lineno = 3
|
||||
message = "justso"
|
||||
|
||||
ev1 = runner.CollectReport(None, None)
|
||||
class X:
|
||||
pass
|
||||
ev1 = X()
|
||||
ev1.when = "execute"
|
||||
ev1.skipped = True
|
||||
ev1.longrepr = longrepr
|
||||
|
||||
ev2 = runner.ItemTestReport(None, excinfo=longrepr)
|
||||
ev2 = X()
|
||||
ev2.longrepr = longrepr
|
||||
ev2.skipped = True
|
||||
|
||||
l = folded_skips([ev1, ev2])
|
||||
|
@ -408,8 +416,8 @@ def test_skipped_reasons_functional(testdir):
|
|||
)
|
||||
result = testdir.runpytest('--report=skipped')
|
||||
result.stdout.fnmatch_lines([
|
||||
"*test_one.py ss",
|
||||
"*test_two.py S",
|
||||
"*test_one.py ss",
|
||||
"*SKIP*3*conftest.py:3: 'test'",
|
||||
])
|
||||
assert result.ret == 0
|
||||
|
|
|
@ -89,60 +89,28 @@ class TestTerminal:
|
|||
assert lines[1].endswith("xy.py .")
|
||||
assert lines[2] == "hello world"
|
||||
|
||||
def test_testid(self, testdir, linecomp):
|
||||
func,method = testdir.getitems("""
|
||||
def test_func():
|
||||
pass
|
||||
class TestClass:
|
||||
def test_method(self):
|
||||
pass
|
||||
def test_show_runtest_logstart(self, testdir, linecomp):
|
||||
item = testdir.getitem("def test_func(): pass")
|
||||
tr = TerminalReporter(item.config, file=linecomp.stringio)
|
||||
item.config.pluginmanager.register(tr)
|
||||
nodeid = item.collection.getid(item)
|
||||
location = item.ihook.pytest_report_iteminfo(item=item)
|
||||
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)
|
||||
id = tr.gettestid(func)
|
||||
assert id.endswith("test_testid.py::test_func")
|
||||
fspath = py.path.local(id.split("::")[0])
|
||||
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*"
|
||||
])
|
||||
child = testdir.spawn_pytest("")
|
||||
child.expect(".*test_runtest_location.*py")
|
||||
child.sendeof()
|
||||
child.kill(15)
|
||||
|
||||
def test_itemreport_subclasses_show_subclassed_file(self, testdir):
|
||||
p1 = testdir.makepyfile(test_p1="""
|
||||
|
@ -206,12 +174,12 @@ class TestCollectonly:
|
|||
"<Module 'test_collectonly_basic.py'>"
|
||||
])
|
||||
item = modcol.join("test_func")
|
||||
rep.config.hook.pytest_itemstart(item=item)
|
||||
rep.config.hook.pytest_log_itemcollect(item=item)
|
||||
linecomp.assert_contains_lines([
|
||||
" <Function 'test_func'>",
|
||||
])
|
||||
rep.config.hook.pytest_collectreport(
|
||||
report=runner.CollectReport(modcol, [], excinfo=None))
|
||||
report = rep.config.hook.pytest_make_collect_report(collector=modcol)
|
||||
rep.config.hook.pytest_collectreport(report=report)
|
||||
assert rep.indent == indent
|
||||
|
||||
def test_collectonly_skipped_module(self, testdir, linecomp):
|
||||
|
@ -264,13 +232,13 @@ class TestCollectonly:
|
|||
stderr = result.stderr.str().strip()
|
||||
#assert stderr.startswith("inserting into sys.path")
|
||||
assert result.ret == 0
|
||||
extra = result.stdout.fnmatch_lines(py.code.Source("""
|
||||
<Module '*.py'>
|
||||
<Function 'test_func1'*>
|
||||
<Class 'TestClass'>
|
||||
<Instance '()'>
|
||||
<Function 'test_method'*>
|
||||
""").strip())
|
||||
extra = result.stdout.fnmatch_lines([
|
||||
"*<Module '*.py'>",
|
||||
"* <Function 'test_func1'*>",
|
||||
"* <Class 'TestClass'>",
|
||||
"* <Instance '()'>",
|
||||
"* <Function 'test_method'*>",
|
||||
])
|
||||
|
||||
def test_collectonly_error(self, testdir):
|
||||
p = testdir.makepyfile("import Errlkjqweqwe")
|
||||
|
@ -278,9 +246,9 @@ class TestCollectonly:
|
|||
stderr = result.stderr.str().strip()
|
||||
assert result.ret == 1
|
||||
extra = result.stdout.fnmatch_lines(py.code.Source("""
|
||||
<Module '*.py'>
|
||||
*<Module '*.py'>
|
||||
*ImportError*
|
||||
!!!*failures*!!!
|
||||
*!!!*failures*!!!
|
||||
*test_collectonly_error.py:1*
|
||||
""").strip())
|
||||
|
||||
|
@ -454,6 +422,7 @@ class TestTerminalFunctional:
|
|||
"*test_verbose_reporting.py:10: test_gen*FAIL*",
|
||||
])
|
||||
assert result.ret == 1
|
||||
py.test.xfail("fix dist-testing")
|
||||
pytestconfig.pluginmanager.skipifmissing("xdist")
|
||||
result = testdir.runpytest(p1, '-v', '-n 1')
|
||||
result.stdout.fnmatch_lines([
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
from py._plugin.pytest_tmpdir import pytest_funcarg__tmpdir
|
||||
from py._plugin.pytest_python import FuncargRequest
|
||||
|
||||
def test_funcarg(testdir):
|
||||
from py._test.funcargs import FuncargRequest
|
||||
item = testdir.getitem("def test_func(tmpdir): pass")
|
||||
p = pytest_funcarg__tmpdir(FuncargRequest(item))
|
||||
assert p.check()
|
||||
|
|
|
@ -59,19 +59,18 @@ class TestCollector:
|
|||
import py
|
||||
class CustomFile(py.test.collect.File):
|
||||
pass
|
||||
class MyDirectory(py.test.collect.Directory):
|
||||
def collect(self):
|
||||
return [CustomFile(self.fspath.join("hello.xxx"), parent=self)]
|
||||
def pytest_collect_directory(path, parent):
|
||||
return MyDirectory(path, parent=parent)
|
||||
def pytest_collect_file(path, parent):
|
||||
if path.ext == ".xxx":
|
||||
return CustomFile(path, parent=parent)
|
||||
""")
|
||||
config = testdir.parseconfig(hello)
|
||||
node = config.getnode(hello)
|
||||
node = testdir.getnode(config, hello)
|
||||
assert isinstance(node, py.test.collect.File)
|
||||
assert node.name == "hello.xxx"
|
||||
names = config._rootcol.totrail(node)
|
||||
node = config._rootcol.getbynames(names)
|
||||
assert isinstance(node, py.test.collect.File)
|
||||
id = node.collection.getid(node)
|
||||
nodes = node.collection.getbyid(id)
|
||||
assert len(nodes) == 1
|
||||
assert isinstance(nodes[0], py.test.collect.File)
|
||||
|
||||
class TestCollectFS:
|
||||
def test_ignored_certain_directories(self, testdir):
|
||||
|
@ -84,7 +83,7 @@ class TestCollectFS:
|
|||
tmpdir.ensure("normal", 'test_found.py')
|
||||
tmpdir.ensure('test_found.py')
|
||||
|
||||
col = testdir.parseconfig(tmpdir).getnode(tmpdir)
|
||||
col = testdir.getnode(testdir.parseconfig(tmpdir), tmpdir)
|
||||
items = col.collect()
|
||||
names = [x.name for x in items]
|
||||
assert len(items) == 2
|
||||
|
@ -93,7 +92,7 @@ class TestCollectFS:
|
|||
|
||||
def test_found_certain_testfiles(self, testdir):
|
||||
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
|
||||
assert len(items) == 2
|
||||
assert items[1].name == 'test_found.py'
|
||||
|
@ -106,7 +105,7 @@ class TestCollectFS:
|
|||
testdir.makepyfile(test_two="hello")
|
||||
p1.dirpath().mkdir("dir2")
|
||||
config = testdir.parseconfig()
|
||||
col = config.getnode(p1.dirpath())
|
||||
col = testdir.getnode(config, p1.dirpath())
|
||||
names = [x.name for x in col.collect()]
|
||||
assert names == ["dir1", "dir2", "test_one.py", "test_two.py", "x"]
|
||||
|
||||
|
@ -120,7 +119,7 @@ class TestCollectPluginHookRelay:
|
|||
config = testdir.Config()
|
||||
config.pluginmanager.register(Plugin())
|
||||
config.parse([tmpdir])
|
||||
col = config.getnode(tmpdir)
|
||||
col = testdir.getnode(config, tmpdir)
|
||||
testdir.makefile(".abc", "xyz")
|
||||
res = col.collect()
|
||||
assert len(wascalled) == 1
|
||||
|
@ -141,7 +140,7 @@ class TestCollectPluginHookRelay:
|
|||
assert "world" in wascalled
|
||||
# make sure the directories do not get double-appended
|
||||
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
|
||||
|
||||
class TestPrunetraceback:
|
||||
|
@ -181,6 +180,7 @@ class TestPrunetraceback:
|
|||
"*hello world*",
|
||||
])
|
||||
|
||||
@py.test.mark.xfail(reason="other mechanism for adding to reporting needed")
|
||||
def test_collect_report_postprocessing(self, testdir):
|
||||
p = testdir.makepyfile("""
|
||||
import not_exists
|
||||
|
@ -227,16 +227,18 @@ class TestCustomConftests:
|
|||
testdir.mkdir("hello")
|
||||
testdir.makepyfile(test_world="#")
|
||||
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 'test_world.py' not in names
|
||||
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 'test_world.py' in names
|
||||
|
||||
def test_pytest_fs_collect_hooks_are_seen(self, testdir):
|
||||
testdir.makeconftest("""
|
||||
conf = testdir.makeconftest("""
|
||||
import py
|
||||
class MyDirectory(py.test.collect.Directory):
|
||||
pass
|
||||
|
@ -247,79 +249,11 @@ class TestCustomConftests:
|
|||
def pytest_collect_file(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.stdout.fnmatch_lines([
|
||||
"*MyDirectory*",
|
||||
"*MyModule*",
|
||||
"*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
|
||||
from py._test.collect import RootCollector
|
||||
|
||||
|
||||
class TestConfigCmdlineParsing:
|
||||
def test_parser_addoption_default_env(self, testdir, monkeypatch):
|
||||
|
@ -106,104 +104,6 @@ class TestConfigAPI:
|
|||
assert pl[0] == tmpdir
|
||||
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 runfiletest(opts):
|
||||
|
@ -247,133 +147,3 @@ def test_preparse_ordering(testdir, monkeypatch):
|
|||
config = testdir.parseconfig()
|
||||
plugin = config.pluginmanager.getplugin("mytestplugin")
|
||||
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
|
||||
|
||||
def test_value_access_with_confmod(self, basedir):
|
||||
topdir = basedir.join("adir", "b")
|
||||
topdir.ensure("xx", dir=True)
|
||||
conftest = ConftestWithSetinitial(topdir)
|
||||
mod, value = conftest.rget_with_confmod("a", topdir)
|
||||
startdir = basedir.join("adir", "b")
|
||||
startdir.ensure("xx", dir=True)
|
||||
conftest = ConftestWithSetinitial(startdir)
|
||||
mod, value = conftest.rget_with_confmod("a", startdir)
|
||||
assert value == 1.5
|
||||
path = py.path.local(mod.__file__)
|
||||
assert path.dirpath() == basedir.join("adir", "b")
|
||||
|
|
|
@ -49,7 +49,7 @@ class TestCollectDeprecated:
|
|||
def check2(self): pass
|
||||
"""))
|
||||
config = testdir.parseconfig(somefile)
|
||||
dirnode = config.getnode(somefile.dirpath())
|
||||
dirnode = testdir.getnode(config, somefile.dirpath())
|
||||
colitems = dirnode.collect()
|
||||
w = recwarn.pop(DeprecationWarning)
|
||||
assert w.filename.find("conftest.py") != -1
|
||||
|
@ -171,9 +171,12 @@ class TestCollectDeprecated:
|
|||
return Module(path, parent=self)
|
||||
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")
|
||||
config = testdir.parseconfig(testme)
|
||||
col = config.getnode(testme)
|
||||
col = testdir.getnode(config, testme)
|
||||
assert col.collect() == []
|
||||
|
||||
|
||||
|
@ -219,7 +222,7 @@ class TestDisabled:
|
|||
""")
|
||||
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):
|
||||
testdir.makeconftest("""
|
||||
import py
|
||||
|
@ -235,11 +238,11 @@ class TestDisabled:
|
|||
""")
|
||||
config = testdir.parseconfig()
|
||||
if name == "Directory":
|
||||
config.getnode(testdir.tmpdir)
|
||||
testdir.getnode(config, testdir.tmpdir)
|
||||
elif name in ("Module", "File"):
|
||||
config.getnode(p)
|
||||
testdir.getnode(config, p)
|
||||
else:
|
||||
fnode = config.getnode(p)
|
||||
fnode = testdir.getnode(config, p)
|
||||
recwarn.clear()
|
||||
fnode.collect()
|
||||
w = recwarn.pop(DeprecationWarning)
|
||||
|
@ -278,9 +281,10 @@ def test_conftest_non_python_items(recwarn, testdir):
|
|||
checkfile = testdir.makefile(ext="xxx", hello="world")
|
||||
testdir.makepyfile(x="")
|
||||
testdir.maketxtfile(x="")
|
||||
config = testdir.parseconfig()
|
||||
recwarn.clear()
|
||||
dircol = config.getnode(checkfile.dirpath())
|
||||
config = testdir.parseconfig()
|
||||
dircol = testdir.getnode(config, checkfile.dirpath())
|
||||
|
||||
w = recwarn.pop(DeprecationWarning)
|
||||
assert str(w.message).find("conftest.py") != -1
|
||||
colitems = dircol.collect()
|
||||
|
@ -288,7 +292,7 @@ def test_conftest_non_python_items(recwarn, testdir):
|
|||
assert colitems[0].name == "hello.xxx"
|
||||
assert colitems[0].__class__.__name__ == "CustomItem"
|
||||
|
||||
item = config.getnode(checkfile)
|
||||
item = testdir.getnode(config, checkfile)
|
||||
assert item.name == "hello.xxx"
|
||||
assert item.__class__.__name__ == "CustomItem"
|
||||
|
||||
|
@ -321,14 +325,14 @@ def test_extra_python_files_and_functions(testdir, recwarn):
|
|||
""")
|
||||
# check that directory collects "check_" files
|
||||
config = testdir.parseconfig()
|
||||
col = config.getnode(checkfile.dirpath())
|
||||
col = testdir.getnode(config, checkfile.dirpath())
|
||||
colitems = col.collect()
|
||||
assert len(colitems) == 1
|
||||
assert isinstance(colitems[0], py.test.collect.Module)
|
||||
|
||||
# check that module collects "check_" functions and methods
|
||||
config = testdir.parseconfig(checkfile)
|
||||
col = config.getnode(checkfile)
|
||||
col = testdir.getnode(config, checkfile)
|
||||
assert isinstance(col, py.test.collect.Module)
|
||||
colitems = col.collect()
|
||||
assert len(colitems) == 2
|
||||
|
|
|
@ -160,6 +160,19 @@ class TestBootstrapping:
|
|||
pp.unregister(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):
|
||||
pp = PluginManager()
|
||||
mod = py.std.types.ModuleType("x.y.pytest_hello")
|
||||
|
@ -340,8 +353,12 @@ def test_varnames():
|
|||
class A:
|
||||
def f(self, y):
|
||||
pass
|
||||
class B(object):
|
||||
def __call__(self, z):
|
||||
pass
|
||||
assert varnames(f) == ("x",)
|
||||
assert varnames(A().f) == ('y',)
|
||||
assert varnames(B()) == ('z',)
|
||||
|
||||
class TestMultiCall:
|
||||
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
|
||||
|
||||
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):
|
||||
tfile = testdir.makepyfile("""
|
||||
def test_one():
|
||||
|
@ -22,14 +17,14 @@ class SessionTests:
|
|||
assert len(skipped) == 0
|
||||
assert len(passed) == 1
|
||||
assert len(failed) == 3
|
||||
assert failed[0].item.name == "test_one_one"
|
||||
assert failed[1].item.name == "test_other"
|
||||
assert failed[2].item.name == "test_two"
|
||||
itemstarted = reprec.getcalls("pytest_itemstart")
|
||||
assert failed[0].nodenames[-1] == "test_one_one"
|
||||
assert failed[1].nodenames[-1] == "test_other"
|
||||
assert failed[2].nodenames[-1] == "test_two"
|
||||
itemstarted = reprec.getcalls("pytest_log_itemcollect")
|
||||
assert len(itemstarted) == 4
|
||||
colstarted = reprec.getcalls("pytest_collectstart")
|
||||
assert len(colstarted) == 1
|
||||
col = colstarted[0].collector
|
||||
assert len(colstarted) == 1 + 1 # XXX ExtraTopCollector
|
||||
col = colstarted[1].collector
|
||||
assert isinstance(col, py.test.collect.Module)
|
||||
|
||||
def test_nested_import_error(self, testdir):
|
||||
|
@ -183,13 +178,13 @@ class TestNewSession(SessionTests):
|
|||
)
|
||||
reprec = testdir.inline_run('--collectonly', p.dirpath())
|
||||
|
||||
itemstarted = reprec.getcalls("pytest_itemstart")
|
||||
itemstarted = reprec.getcalls("pytest_log_itemcollect")
|
||||
assert len(itemstarted) == 3
|
||||
assert not reprec.getreports("pytest_runtest_logreport")
|
||||
started = reprec.getcalls("pytest_collectstart")
|
||||
finished = reprec.getreports("pytest_collectreport")
|
||||
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]
|
||||
colskipped = [x for x in finished if x.skipped]
|
||||
assert len(colfail) == 1
|
||||
|
|
8
tox.ini
8
tox.ini
|
@ -9,7 +9,7 @@ sdistsrc={distshare}/py-*
|
|||
[testenv]
|
||||
changedir=testing
|
||||
commands=
|
||||
py.test -rfsxX --junitxml={envlogdir}/junit-{envname}.xml --tools-on-path []
|
||||
py.test -rfsxX --junitxml={envlogdir}/junit-{envname}.xml []
|
||||
deps=
|
||||
pexpect
|
||||
[testenv:py27]
|
||||
|
@ -21,7 +21,7 @@ deps=
|
|||
{distshare}/pytest-xdist-*
|
||||
commands=
|
||||
py.test -n3 -rfsxX \
|
||||
--junitxml={envlogdir}/junit-{envname}.xml --tools-on-path []
|
||||
--junitxml={envlogdir}/junit-{envname}.xml []
|
||||
|
||||
[testenv:py26]
|
||||
basepython=python2.6
|
||||
|
@ -47,6 +47,10 @@ basepython=python2.4
|
|||
[testenv:py31]
|
||||
basepython=python3.1
|
||||
deps=
|
||||
[testenv:py32]
|
||||
basepython=python3.2
|
||||
deps=
|
||||
#{distshare}/pytest-xdist-*
|
||||
#[testenv:pypy]
|
||||
#python=pypy-c
|
||||
[testenv:jython]
|
||||
|
|
Loading…
Reference in New Issue