merge heads

--HG--
branch : trunk
This commit is contained in:
holger krekel 2010-10-02 19:36:15 +02:00
commit 63bb9efd29
74 changed files with 2730 additions and 2283 deletions

View File

@ -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

View File

@ -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
==================================================

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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`:

View File

@ -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

View File

@ -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``

View File

@ -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
--------------------

View File

@ -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

View File

@ -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',

View File

@ -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

View File

@ -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):

View File

@ -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):

View File

@ -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>" %(

View File

@ -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)

View File

@ -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

View File

@ -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()

View File

@ -26,7 +26,7 @@ def _getdimensions():
def get_terminal_width():
try:
height, width = _getdimensions()
except (SystemExit, KeyboardInterrupt):
except py.builtin._sysex:
raise
except:
# FALLBACK

View File

@ -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)

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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():

View File

@ -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

View File

@ -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'),

View File

@ -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:

View File

@ -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

View File

@ -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

View File

@ -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 = []

View File

@ -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)

View File

@ -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

View File

@ -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):

View File

@ -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))

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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',

View File

@ -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*",
])

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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")

View File

@ -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():

View File

@ -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',

View File

@ -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("""

View File

@ -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

View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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("""

View File

@ -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

View File

@ -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([

View File

@ -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()

View File

@ -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"

314
testing/test_collection.py Normal file
View File

@ -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)

View File

@ -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()

View File

@ -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")

View File

@ -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

View File

@ -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):

View File

@ -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*",
])

View File

@ -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

View File

@ -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]