probably the last major internal cleanup action: rename collection to

session which now is the root collection node.  This means that
session, collection and config objects have a more defined
relationship (previously there was no way to get from a collection
node or even from a runtest hook to the session object which
was strange).
This commit is contained in:
holger krekel 2010-11-07 10:19:58 +01:00
parent 722e20c7d6
commit 6461295ab4
29 changed files with 163 additions and 143 deletions

View File

@ -21,7 +21,7 @@ assertion fails you will see the value of ``x``::
$ py.test test_assert1.py $ py.test test_assert1.py
=========================== test session starts ============================ =========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev19 platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev22
test path 1: test_assert1.py test path 1: test_assert1.py
test_assert1.py F test_assert1.py F
@ -35,7 +35,7 @@ assertion fails you will see the value of ``x``::
E + where 3 = f() E + where 3 = f()
test_assert1.py:5: AssertionError test_assert1.py:5: AssertionError
========================= 1 failed in 0.02 seconds ========================= ========================= 1 failed in 0.05 seconds =========================
Reporting details about the failing assertion is achieved by re-evaluating Reporting details about the failing assertion is achieved by re-evaluating
the assert expression and recording intermediate values. the assert expression and recording intermediate values.
@ -101,7 +101,7 @@ if you run this module::
$ py.test test_assert2.py $ py.test test_assert2.py
=========================== test session starts ============================ =========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev19 platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev22
test path 1: test_assert2.py test path 1: test_assert2.py
test_assert2.py F test_assert2.py F

View File

@ -44,7 +44,7 @@ then you can just invoke ``py.test`` without command line options::
$ py.test $ py.test
=========================== test session starts ============================ =========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev19 platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev22
test path 1: /tmp/doc-exec-400 test path 1: /tmp/doc-exec-519
============================= in 0.00 seconds ============================= ============================= in 0.00 seconds =============================

View File

@ -27,10 +27,10 @@ Let's run our little function::
F F
================================= FAILURES ================================= ================================= FAILURES =================================
______________________________ test_something ______________________________ ______________________________ test_something ______________________________
def test_something(): def test_something():
> checkconfig(42) > checkconfig(42)
E Failed: not configured: 42 E Failed: not configured: 42
test_checkconfig.py:8: Failed test_checkconfig.py:8: Failed
1 failed in 0.02 seconds 1 failed in 0.02 seconds

View File

@ -36,12 +36,12 @@ and when running it will see a skipped "slow" test::
$ py.test test_module.py -rs # "-rs" means report on the little 's' $ py.test test_module.py -rs # "-rs" means report on the little 's'
=========================== test session starts ============================ =========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev19 platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev22
test path 1: test_module.py test path 1: test_module.py
test_module.py .s test_module.py .s
========================= short test summary info ========================== ========================= short test summary info ==========================
SKIP [1] /tmp/doc-exec-435/conftest.py:9: need --runslow option to run SKIP [1] /tmp/doc-exec-557/conftest.py:9: need --runslow option to run
=================== 1 passed, 1 skipped in 0.02 seconds ==================== =================== 1 passed, 1 skipped in 0.02 seconds ====================
@ -49,7 +49,7 @@ Or run it including the ``slow`` marked test::
$ py.test test_module.py --runslow $ py.test test_module.py --runslow
=========================== test session starts ============================ =========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev19 platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev22
test path 1: test_module.py test path 1: test_module.py
test_module.py .. test_module.py ..

View File

@ -49,7 +49,7 @@ You can now run the test::
$ py.test test_sample.py $ py.test test_sample.py
=========================== test session starts ============================ =========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev19 platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev22
test path 1: test_sample.py test path 1: test_sample.py
test_sample.py F test_sample.py F
@ -57,7 +57,7 @@ You can now run the test::
================================= FAILURES ================================= ================================= FAILURES =================================
_______________________________ test_answer ________________________________ _______________________________ test_answer ________________________________
mysetup = <conftest.MySetup instance at 0x1cf6b90> mysetup = <conftest.MySetup instance at 0x1ca5cf8>
def test_answer(mysetup): def test_answer(mysetup):
app = mysetup.myapp() app = mysetup.myapp()
@ -122,14 +122,14 @@ Running it yields::
$ py.test test_ssh.py -rs $ py.test test_ssh.py -rs
=========================== test session starts ============================ =========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev19 platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev22
test path 1: test_ssh.py test path 1: test_ssh.py
test_ssh.py s test_ssh.py s
========================= short test summary info ========================== ========================= short test summary info ==========================
SKIP [1] /tmp/doc-exec-438/conftest.py:22: specify ssh host with --ssh SKIP [1] /tmp/doc-exec-560/conftest.py:22: specify ssh host with --ssh
======================== 1 skipped in 0.02 seconds ========================= ======================== 1 skipped in 0.03 seconds =========================
If you specify a command line option like ``py.test --ssh=python.org`` the test will execute as expected. If you specify a command line option like ``py.test --ssh=python.org`` the test will execute as expected.

View File

@ -27,17 +27,19 @@ now execute the test specification::
nonpython $ py.test test_simple.yml nonpython $ py.test test_simple.yml
=========================== test session starts ============================ =========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev19 platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev22
test path 1: test_simple.yml test path 1: test_simple.yml
test_simple.yml .F test_simple.yml .F
================================= FAILURES ================================= ================================= FAILURES =================================
______________________________ usecase: hello ______________________________ ______________________________ usecase: hello ______________________________
usecase execution failed usecase execution failed
spec failed: 'some': 'other' spec failed: 'some': 'other'
no further details known at this point. no further details known at this point.
==================== 1 failed, 1 passed in 0.42 seconds ==================== ========================= short test summary info ==========================
FAIL test_simple.yml::hello
==================== 1 failed, 1 passed in 0.43 seconds ====================
You get one dot for the passing ``sub1: sub1`` check and one failure. You get one dot for the passing ``sub1: sub1`` check and one failure.
Obviously in the above ``conftest.py`` you'll want to implement a more Obviously in the above ``conftest.py`` you'll want to implement a more
@ -56,24 +58,25 @@ reporting in ``verbose`` mode::
nonpython $ py.test -v nonpython $ py.test -v
=========================== test session starts ============================ =========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev19 -- /home/hpk/venv/0/bin/python platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev22 -- /home/hpk/venv/0/bin/python
test path 1: /home/hpk/p/pytest/doc/example/nonpython test path 1: /home/hpk/p/pytest/doc/example/nonpython
test_simple.yml <- test_simple.yml:1: usecase: ok PASSED test_simple.yml <- test_simple.yml:1: usecase: ok PASSED
test_simple.yml <- test_simple.yml:1: usecase: hello FAILED test_simple.yml <- test_simple.yml:1: usecase: hello FAILED
================================= FAILURES ================================= ================================= FAILURES =================================
______________________________ usecase: hello ______________________________ ______________________________ usecase: hello ______________________________
usecase execution failed usecase execution failed
spec failed: 'some': 'other' spec failed: 'some': 'other'
no further details known at this point. no further details known at this point.
========================= short test summary info ==========================
FAIL test_simple.yml::hello
==================== 1 failed, 1 passed in 0.07 seconds ==================== ==================== 1 failed, 1 passed in 0.07 seconds ====================
While developing your custom test collection and execution it's also While developing your custom test collection and execution it's also
interesting to just look at the collection tree:: interesting to just look at the collection tree::
nonpython $ py.test --collectonly nonpython $ py.test --collectonly
<Collection 'nonpython'>
<YamlFile 'test_simple.yml'> <YamlFile 'test_simple.yml'>
<YamlItem 'ok'> <YamlItem 'ok'>
<YamlItem 'hello'> <YamlItem 'hello'>

View File

@ -31,7 +31,6 @@ finding out what is collected
You can always peek at the collection tree without running tests like this:: You can always peek at the collection tree without running tests like this::
. $ py.test --collectonly collectonly.py . $ py.test --collectonly collectonly.py
<Collection 'example'>
<Module 'collectonly.py'> <Module 'collectonly.py'>
<Function 'test_function'> <Function 'test_function'>
<Class 'TestClass'> <Class 'TestClass'>

View File

@ -130,7 +130,7 @@ let's run the full monty::
E assert 4 < 4 E assert 4 < 4
test_compute.py:3: AssertionError test_compute.py:3: AssertionError
1 failed, 4 passed in 0.02 seconds 1 failed, 4 passed in 0.03 seconds
As expected when running the full range of ``param1`` values As expected when running the full range of ``param1`` values
we'll get an error on the last one. we'll get an error on the last one.

View File

@ -12,11 +12,11 @@ On naming, nosetests, licensing and magic XXX
Why a ``py.test`` instead of a ``pytest`` command? Why a ``py.test`` instead of a ``pytest`` command?
++++++++++++++++++++++++++++++++++++++++++++++++++ ++++++++++++++++++++++++++++++++++++++++++++++++++
Some historic, some practical reasons: ``py.test`` used to be part of Some historic, some practical reasons: ``py.test`` used to be part of
the ``py`` package which provided several developer utitilities, the ``py`` package which provided several developer utitilities,
all starting with ``py.<TAB>``, providing nice TAB-completion. If all starting with ``py.<TAB>``, providing nice TAB-completion. If
you install ``pip install pycmd`` you get these tools from a separate you install ``pip install pycmd`` you get these tools from a separate
package. These days the command line tool could be ``pytest`` package. These days the command line tool could be ``pytest``
but then many people have gotten used to the old name and there but then many people have gotten used to the old name and there
also is another tool with this same which would lead to some clashes. also is another tool with this same which would lead to some clashes.
@ -37,11 +37,11 @@ What's this "magic" with py.test?
Around 2007 (version ``0.8``) some several people claimed that py.test Around 2007 (version ``0.8``) some several people claimed that py.test
was using too much "magic". It has been refactored a lot. It is today was using too much "magic". It has been refactored a lot. It is today
probably one of the smallest, most universally runnable and most probably one of the smallest, most universally runnable and most
customizable testing frameworks for Python. It remains true customizable testing frameworks for Python. It remains true
that ``py.test`` uses metaprogramming techniques, i.e. it views that ``py.test`` uses metaprogramming techniques, i.e. it views
test code similar to how compilers view programs, using a test code similar to how compilers view programs, using a
somewhat abstract internal model. somewhat abstract internal model.
It's also true that the no-boilerplate testing is implemented by making It's also true that the no-boilerplate testing is implemented by making
use of the Python assert statement through "re-interpretation": use of the Python assert statement through "re-interpretation":
@ -64,7 +64,7 @@ function arguments, parametrized tests and setup
Is using funcarg- versus xUnit setup a style question? Is using funcarg- versus xUnit setup a style question?
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
For simple applications and for people experienced with nose_ or For simple applications and for people experienced with nose_ or
unittest-style test setup using `xUnit style setup`_ unittest-style test setup using `xUnit style setup`_
feels natural. For larger test suites, parametrized testing feels natural. For larger test suites, parametrized testing
or setup of complex test resources using funcargs_ is recommended. or setup of complex test resources using funcargs_ is recommended.

View File

@ -34,7 +34,7 @@ Running the test looks like this::
$ py.test test_simplefactory.py $ py.test test_simplefactory.py
=========================== test session starts ============================ =========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev19 platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev22
test path 1: test_simplefactory.py test path 1: test_simplefactory.py
test_simplefactory.py F test_simplefactory.py F
@ -136,7 +136,7 @@ Running this::
$ py.test test_example.py $ py.test test_example.py
=========================== test session starts ============================ =========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev19 platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev22
test path 1: test_example.py test path 1: test_example.py
test_example.py .........F test_example.py .........F
@ -158,7 +158,6 @@ the test collection phase which is separate from the actual test running.
Let's just look at what is collected:: Let's just look at what is collected::
$ py.test --collectonly test_example.py $ py.test --collectonly test_example.py
<Collection 'doc-exec-403'>
<Module 'test_example.py'> <Module 'test_example.py'>
<Function 'test_func[0]'> <Function 'test_func[0]'>
<Function 'test_func[1]'> <Function 'test_func[1]'>
@ -175,7 +174,7 @@ If you want to select only the run with the value ``7`` you could do::
$ py.test -v -k 7 test_example.py # or -k test_func[7] $ py.test -v -k 7 test_example.py # or -k test_func[7]
=========================== test session starts ============================ =========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev19 -- /home/hpk/venv/0/bin/python platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev22 -- /home/hpk/venv/0/bin/python
test path 1: test_example.py test path 1: test_example.py
test_example.py <- test_example.py:6: test_func[7] PASSED test_example.py <- test_example.py:6: test_func[7] PASSED

View File

@ -14,7 +14,7 @@ Installation options::
To check your installation has installed the correct version:: To check your installation has installed the correct version::
$ py.test --version $ py.test --version
This is py.test version 2.0.0.dev19, imported from /home/hpk/p/pytest/pytest This is py.test version 2.0.0.dev22, imported from /home/hpk/p/pytest/pytest
If you get an error checkout :ref:`installation issues`. If you get an error checkout :ref:`installation issues`.
@ -34,8 +34,8 @@ That's it. You can execute the test function now::
$ py.test $ py.test
=========================== test session starts ============================ =========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev19 platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev22
test path 1: /tmp/doc-exec-404 test path 1: /tmp/doc-exec-523
test_sample.py F test_sample.py F
@ -121,7 +121,7 @@ run the module by passing its filename::
================================= FAILURES ================================= ================================= FAILURES =================================
____________________________ TestClass.test_two ____________________________ ____________________________ TestClass.test_two ____________________________
self = <test_class.TestClass instance at 0x2c2c560> self = <test_class.TestClass instance at 0x254f6c8>
def test_two(self): def test_two(self):
x = "hello" x = "hello"
@ -129,7 +129,7 @@ run the module by passing its filename::
E assert hasattr('hello', 'check') E assert hasattr('hello', 'check')
test_class.py:8: AssertionError test_class.py:8: AssertionError
1 failed, 1 passed in 0.02 seconds 1 failed, 1 passed in 0.03 seconds
The first test passed, the second failed. Again we can easily see The first test passed, the second failed. Again we can easily see
the intermediate values used in the assertion, helping us to the intermediate values used in the assertion, helping us to
@ -157,7 +157,7 @@ before performing the test function call. Let's just run it::
================================= FAILURES ================================= ================================= FAILURES =================================
_____________________________ test_needsfiles ______________________________ _____________________________ test_needsfiles ______________________________
tmpdir = local('/tmp/pytest-240/test_needsfiles0') tmpdir = local('/tmp/pytest-446/test_needsfiles0')
def test_needsfiles(tmpdir): def test_needsfiles(tmpdir):
print tmpdir print tmpdir
@ -166,8 +166,8 @@ before performing the test function call. Let's just run it::
test_tmpdir.py:3: AssertionError test_tmpdir.py:3: AssertionError
----------------------------- Captured stdout ------------------------------ ----------------------------- Captured stdout ------------------------------
/tmp/pytest-240/test_needsfiles0 /tmp/pytest-446/test_needsfiles0
1 failed in 0.04 seconds 1 failed in 0.07 seconds
Before the test runs, a unique-per-test-invocation temporary directory Before the test runs, a unique-per-test-invocation temporary directory
was created. More info at :ref:`tmpdir handling`. was created. More info at :ref:`tmpdir handling`.

View File

@ -88,20 +88,20 @@ You can use the ``-k`` command line option to select tests::
$ py.test -k webtest # running with the above defined examples yields $ py.test -k webtest # running with the above defined examples yields
=========================== test session starts ============================ =========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev19 platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev22
test path 1: /tmp/doc-exec-407 test path 1: /tmp/doc-exec-527
test_mark.py .. test_mark.py ..
test_mark_classlevel.py .. test_mark_classlevel.py ..
========================= 4 passed in 0.01 seconds ========================= ========================= 4 passed in 0.02 seconds =========================
And you can also run all tests except the ones that match the keyword:: And you can also run all tests except the ones that match the keyword::
$ py.test -k-webtest $ py.test -k-webtest
=========================== test session starts ============================ =========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev19 platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev22
test path 1: /tmp/doc-exec-407 test path 1: /tmp/doc-exec-527
===================== 4 tests deselected by '-webtest' ===================== ===================== 4 tests deselected by '-webtest' =====================
======================= 4 deselected in 0.01 seconds ======================= ======================= 4 deselected in 0.01 seconds =======================
@ -110,8 +110,8 @@ Or to only select the class::
$ py.test -kTestClass $ py.test -kTestClass
=========================== test session starts ============================ =========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev19 platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev22
test path 1: /tmp/doc-exec-407 test path 1: /tmp/doc-exec-527
test_mark_classlevel.py .. test_mark_classlevel.py ..

View File

@ -39,8 +39,8 @@ will be undone.
.. background check: .. background check:
$ py.test $ py.test
=========================== test session starts ============================ =========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev19 platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev22
test path 1: /tmp/doc-exec-408 test path 1: /tmp/doc-exec-528
============================= in 0.00 seconds ============================= ============================= in 0.00 seconds =============================

View File

@ -16,7 +16,7 @@ conftest.py: local per-directory plugins
-------------------------------------------------------------- --------------------------------------------------------------
local ``conftest.py`` plugins contain directory-specific hook local ``conftest.py`` plugins contain directory-specific hook
implementations. Collection and test running activities will implementations. Session and test running activities will
invoke all hooks defined in "higher up" ``conftest.py`` files. invoke all hooks defined in "higher up" ``conftest.py`` files.
Example: Assume the following layout and content of files:: Example: Assume the following layout and content of files::
@ -268,7 +268,7 @@ you can use the following hook:
reporting hooks reporting hooks
------------------------------ ------------------------------
Collection related reporting hooks: Session related reporting hooks:
.. autofunction: pytest_collectstart .. autofunction: pytest_collectstart
.. autofunction: pytest_itemcollected .. autofunction: pytest_itemcollected

View File

@ -28,7 +28,7 @@ Running this would result in a passed test except for the last
$ py.test test_tmpdir.py $ py.test test_tmpdir.py
=========================== test session starts ============================ =========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev19 platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev22
test path 1: test_tmpdir.py test path 1: test_tmpdir.py
test_tmpdir.py F test_tmpdir.py F
@ -36,7 +36,7 @@ Running this would result in a passed test except for the last
================================= FAILURES ================================= ================================= FAILURES =================================
_____________________________ test_create_file _____________________________ _____________________________ test_create_file _____________________________
tmpdir = local('/tmp/pytest-243/test_create_file0') tmpdir = local('/tmp/pytest-447/test_create_file0')
def test_create_file(tmpdir): def test_create_file(tmpdir):
p = tmpdir.mkdir("sub").join("hello.txt") p = tmpdir.mkdir("sub").join("hello.txt")
@ -47,7 +47,7 @@ Running this would result in a passed test except for the last
E assert 0 E assert 0
test_tmpdir.py:7: AssertionError test_tmpdir.py:7: AssertionError
========================= 1 failed in 0.04 seconds ========================= ========================= 1 failed in 0.15 seconds =========================
.. _`base temporary directory`: .. _`base temporary directory`:

View File

@ -24,7 +24,7 @@ Running it yields::
$ py.test test_unittest.py $ py.test test_unittest.py
=========================== test session starts ============================ =========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev19 platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev22
test path 1: test_unittest.py test path 1: test_unittest.py
test_unittest.py F test_unittest.py F
@ -56,7 +56,7 @@ Running it yields::
/usr/lib/python2.6/unittest.py:350: AssertionError /usr/lib/python2.6/unittest.py:350: AssertionError
----------------------------- Captured stdout ------------------------------ ----------------------------- Captured stdout ------------------------------
hello hello
========================= 1 failed in 0.02 seconds ========================= ========================= 1 failed in 0.12 seconds =========================
.. _`unittest.py style`: http://docs.python.org/library/unittest.html .. _`unittest.py style`: http://docs.python.org/library/unittest.html

View File

@ -175,5 +175,4 @@ Running it will exit quickly::
$ python myinvoke.py $ python myinvoke.py
ERROR: hi from our plugin ERROR: hi from our plugin
.. include:: links.inc .. include:: links.inc

View File

@ -5,7 +5,7 @@ see http://pytest.org for documentation and details
(c) Holger Krekel and others, 2004-2010 (c) Holger Krekel and others, 2004-2010
""" """
__version__ = '2.0.0.dev22' __version__ = '2.0.0.dev23'
__all__ = ['config', 'cmdline'] __all__ = ['config', 'cmdline']

View File

@ -38,7 +38,8 @@ def pytest_unconfigure(config):
""" called before test process is exited. """ """ called before test process is exited. """
def pytest_runtestloop(session): def pytest_runtestloop(session):
""" called for performing the main runtest loop (after collection. """ """ called for performing the main runtest loop
(after collection finished). """
pytest_runtestloop.firstresult = True pytest_runtestloop.firstresult = True
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
@ -49,11 +50,11 @@ def pytest_collection(session):
""" perform the collection protocol for the given session. """ """ perform the collection protocol for the given session. """
pytest_collection.firstresult = True pytest_collection.firstresult = True
def pytest_collection_modifyitems(config, items): def pytest_collection_modifyitems(session, config, items):
""" called after collection has been performed, may filter or re-order """ called after collection has been performed, may filter or re-order
the items in-place.""" the items in-place."""
def pytest_collection_finish(collection): def pytest_collection_finish(session):
""" called after collection has been performed and modified. """ """ called after collection has been performed and modified. """
def pytest_ignore_collect(path, config): def pytest_ignore_collect(path, config):
@ -64,8 +65,7 @@ def pytest_ignore_collect(path, config):
pytest_ignore_collect.firstresult = True pytest_ignore_collect.firstresult = True
def pytest_collect_directory(path, parent): def pytest_collect_directory(path, parent):
""" return collection Node or None for the given path. Any new node """ called before traversing a directory for collection files. """
needs to have the specified ``parent`` as a parent."""
pytest_collect_directory.firstresult = True pytest_collect_directory.firstresult = True
def pytest_collect_file(path, parent): def pytest_collect_file(path, parent):

View File

@ -6,7 +6,7 @@ import re
import inspect import inspect
import time import time
from fnmatch import fnmatch from fnmatch import fnmatch
from pytest.plugin.session import Collection from pytest.plugin.session import Session
from py.builtin import print_ from py.builtin import print_
from pytest.main import HookRelay from pytest.main import HookRelay
@ -273,34 +273,34 @@ class TmpTestdir:
p.ensure("__init__.py") p.ensure("__init__.py")
return p return p
Collection = Collection Session = Session
def getnode(self, config, arg): def getnode(self, config, arg):
collection = Collection(config) session = Session(config)
assert '::' not in str(arg) assert '::' not in str(arg)
p = py.path.local(arg) p = py.path.local(arg)
x = collection.fspath.bestrelpath(p) x = session.fspath.bestrelpath(p)
return collection.perform_collect([x], genitems=False)[0] return session.perform_collect([x], genitems=False)[0]
def getpathnode(self, path): def getpathnode(self, path):
config = self.parseconfig(path) config = self.parseconfig(path)
collection = Collection(config) session = Session(config)
x = collection.fspath.bestrelpath(path) x = session.fspath.bestrelpath(path)
return collection.perform_collect([x], genitems=False)[0] return session.perform_collect([x], genitems=False)[0]
def genitems(self, colitems): def genitems(self, colitems):
collection = colitems[0].collection session = colitems[0].session
result = [] result = []
for colitem in colitems: for colitem in colitems:
result.extend(collection.genitems(colitem)) result.extend(session.genitems(colitem))
return result return result
def inline_genitems(self, *args): def inline_genitems(self, *args):
#config = self.parseconfig(*args) #config = self.parseconfig(*args)
config = self.parseconfigure(*args) config = self.parseconfigure(*args)
rec = self.getreportrecorder(config) rec = self.getreportrecorder(config)
collection = Collection(config) session = Session(config)
collection.perform_collect() session.perform_collect()
return collection.items, rec return session.items, rec
def runitem(self, source): def runitem(self, source):
# used from runner functional tests # used from runner functional tests

View File

@ -47,7 +47,7 @@ def pytest_collect_file(path, parent):
ext = path.ext ext = path.ext
pb = path.purebasename pb = path.purebasename
if ext == ".py" and (pb.startswith("test_") or pb.endswith("_test") or if ext == ".py" and (pb.startswith("test_") or pb.endswith("_test") or
parent.collection.isinitpath(path)): parent.session.isinitpath(path)):
return parent.ihook.pytest_pycollect_makemodule( return parent.ihook.pytest_pycollect_makemodule(
path=path, parent=parent) path=path, parent=parent)
@ -393,9 +393,9 @@ class Function(FunctionMixin, pytest.collect.Item):
""" """
_genid = None _genid = None
def __init__(self, name, parent=None, args=None, config=None, def __init__(self, name, parent=None, args=None, config=None,
callspec=None, callobj=_dummy, keywords=None, collection=None): callspec=None, callobj=_dummy, keywords=None, session=None):
super(Function, self).__init__(name, parent, super(Function, self).__init__(name, parent,
config=config, collection=collection) config=config, session=session)
self._args = args self._args = args
if self._isyieldedfunction(): if self._isyieldedfunction():
assert not callspec, ( assert not callspec, (
@ -711,13 +711,13 @@ class FuncargRequest:
raise self.LookupError(msg) raise self.LookupError(msg)
def showfuncargs(config): def showfuncargs(config):
from pytest.plugin.session import Collection from pytest.plugin.session import Session
collection = Collection(config) session = Session(config)
collection.perform_collect() session.perform_collect()
if collection.items: if session.items:
plugins = getplugins(collection.items[0]) plugins = getplugins(session.items[0])
else: else:
plugins = getplugins(collection) plugins = getplugins(session)
curdir = py.path.local() curdir = py.path.local()
tw = py.io.TerminalWriter() tw = py.io.TerminalWriter()
verbose = config.getvalue("verbose") verbose = config.getvalue("verbose")

View File

@ -1,4 +1,4 @@
""" core implementation of testing process: init, collection, runtest loop. """ """ core implementation of testing process: init, session, runtest loop. """
import py import py
import pytest import pytest
@ -54,7 +54,7 @@ def pytest_configure(config):
config.option.maxfail = 1 config.option.maxfail = 1
def pytest_cmdline_main(config): def pytest_cmdline_main(config):
""" default command line protocol for initialization, collection, """ default command line protocol for initialization, session,
running tests and reporting. """ running tests and reporting. """
session = Session(config) session = Session(config)
session.exitstatus = EXIT_OK session.exitstatus = EXIT_OK
@ -83,20 +83,17 @@ def pytest_cmdline_main(config):
return session.exitstatus return session.exitstatus
def pytest_collection(session): def pytest_collection(session):
collection = session.collection session.perform_collect()
assert not hasattr(collection, 'items')
collection.perform_collect()
hook = session.config.hook hook = session.config.hook
items = collection.items hook.pytest_collection_modifyitems(session=session,
hook.pytest_collection_modifyitems(config=session.config, items=items) config=session.config, items=session.items)
hook.pytest_collection_finish(collection=collection) hook.pytest_collection_finish(session=session)
return True return True
def pytest_runtestloop(session): def pytest_runtestloop(session):
if session.config.option.collectonly: if session.config.option.collectonly:
return True return True
for item in session.collection.items: for item in session.session.items:
item.config.hook.pytest_runtest_protocol(item=item) item.config.hook.pytest_runtest_protocol(item=item)
if session.shouldstop: if session.shouldstop:
raise session.Interrupted(session.shouldstop) raise session.Interrupted(session.shouldstop)
@ -121,7 +118,7 @@ class Session(object):
self.config.pluginmanager.register(self, name="session", prepend=True) self.config.pluginmanager.register(self, name="session", prepend=True)
self._testsfailed = 0 self._testsfailed = 0
self.shouldstop = False self.shouldstop = False
self.collection = Collection(config) # XXX move elswehre self.session = Session(config) # XXX move elswehre
def pytest_collectstart(self): def pytest_collectstart(self):
if self.shouldstop: if self.shouldstop:
@ -154,7 +151,7 @@ def compatproperty(name):
def fget(self): def fget(self):
#print "retrieving %r property from %s" %(name, self.fspath) #print "retrieving %r property from %s" %(name, self.fspath)
py.log._apiwarn("2.0", "use py.test.collect.%s for " py.log._apiwarn("2.0", "use py.test.collect.%s for "
"Collection classes" % name) "Session classes" % name)
return getattr(pytest.collect, name) return getattr(pytest.collect, name)
return property(fget) return property(fget)
@ -162,7 +159,7 @@ class Node(object):
""" base class for all Nodes in the collection tree. """ base class for all Nodes in the collection tree.
Collector subclasses have children, Items are terminal nodes.""" Collector subclasses have children, Items are terminal nodes."""
def __init__(self, name, parent=None, config=None, collection=None): def __init__(self, name, parent=None, config=None, session=None):
#: a unique name with the scope of the parent #: a unique name with the scope of the parent
self.name = name self.name = name
@ -173,11 +170,11 @@ class Node(object):
self.config = config or parent.config self.config = config or parent.config
#: the collection this node is part of #: the collection this node is part of
self.collection = collection or parent.collection self.session = session or parent.session
#: filesystem path where this node was collected from #: filesystem path where this node was collected from
self.fspath = getattr(parent, 'fspath', None) self.fspath = getattr(parent, 'fspath', None)
self.ihook = self.collection.gethookproxy(self.fspath) self.ihook = self.session.gethookproxy(self.fspath)
self.keywords = {self.name: True} self.keywords = {self.name: True}
Module = compatproperty("Module") Module = compatproperty("Module")
@ -312,16 +309,16 @@ class Collector(Node):
excinfo.traceback = ntraceback.filter() excinfo.traceback = ntraceback.filter()
class FSCollector(Collector): class FSCollector(Collector):
def __init__(self, fspath, parent=None, config=None, collection=None): def __init__(self, fspath, parent=None, config=None, session=None):
fspath = py.path.local(fspath) # xxx only for test_resultlog.py? fspath = py.path.local(fspath) # xxx only for test_resultlog.py?
name = parent and fspath.relto(parent.fspath) or fspath.basename name = parent and fspath.relto(parent.fspath) or fspath.basename
super(FSCollector, self).__init__(name, parent, config, collection) super(FSCollector, self).__init__(name, parent, config, session)
self.fspath = fspath self.fspath = fspath
def _makeid(self): def _makeid(self):
if self == self.collection: if self == self.session:
return "." return "."
relpath = self.collection.fspath.bestrelpath(self.fspath) relpath = self.session.fspath.bestrelpath(self.fspath)
if os.sep != "/": if os.sep != "/":
relpath = relpath.replace(os.sep, "/") relpath = relpath.replace(os.sep, "/")
return relpath return relpath
@ -346,13 +343,33 @@ class Item(Node):
self._location = location self._location = location
return location return location
class Collection(FSCollector): class Session(FSCollector):
class Interrupted(KeyboardInterrupt):
""" signals an interrupted test run. """
__module__ = 'builtins' # for py3
def __init__(self, config): def __init__(self, config):
super(Collection, self).__init__(py.path.local(), parent=None, super(Session, self).__init__(py.path.local(), parent=None,
config=config, collection=self) config=config, session=self)
self.config.pluginmanager.register(self, name="session", prepend=True)
self._testsfailed = 0
self.shouldstop = False
self.trace = config.trace.root.get("collection") self.trace = config.trace.root.get("collection")
self._norecursepatterns = config.getini("norecursedirs") self._norecursepatterns = config.getini("norecursedirs")
def pytest_collectstart(self):
if self.shouldstop:
raise self.Interrupted(self.shouldstop)
def pytest_runtest_logreport(self, report):
if report.failed and 'xfail' not in getattr(report, 'keywords', []):
self._testsfailed += 1
maxfail = self.config.getvalue("maxfail")
if maxfail and self._testsfailed >= maxfail:
self.shouldstop = "stopping after %d failures" % (
self._testsfailed)
pytest_collectreport = pytest_runtest_logreport
def isinitpath(self, path): def isinitpath(self, path):
return path in self._initialpaths return path in self._initialpaths
@ -509,3 +526,4 @@ class Collection(FSCollector):
yield x yield x
node.ihook.pytest_collectreport(report=rep) node.ihook.pytest_collectreport(report=rep)
Session = Session

View File

@ -376,8 +376,9 @@ class CollectonlyReporter:
self._tw.line("INTERNALERROR> " + line) self._tw.line("INTERNALERROR> " + line)
def pytest_collectstart(self, collector): def pytest_collectstart(self, collector):
self.outindent(collector) if collector.session != collector:
self.indent += self.INDENT self.outindent(collector)
self.indent += self.INDENT
def pytest_itemcollected(self, item): def pytest_itemcollected(self, item):
self.outindent(item) self.outindent(item)

View File

@ -22,7 +22,7 @@ def main():
name='pytest', name='pytest',
description='py.test: simple powerful testing with Python', description='py.test: simple powerful testing with Python',
long_description = long_description, long_description = long_description,
version='2.0.0.dev22', version='2.0.0.dev23',
url='http://pytest.org', url='http://pytest.org',
license='MIT license', license='MIT license',
platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],

View File

@ -205,15 +205,15 @@ class TestFunction:
def test_function_equality(self, testdir, tmpdir): def test_function_equality(self, testdir, tmpdir):
config = testdir.reparseconfig() config = testdir.reparseconfig()
collection = testdir.Collection(config) session = testdir.Session(config)
f1 = py.test.collect.Function(name="name", config=config, f1 = py.test.collect.Function(name="name", config=config,
args=(1,), callobj=isinstance, collection=collection) args=(1,), callobj=isinstance, session=session)
f2 = py.test.collect.Function(name="name",config=config, f2 = py.test.collect.Function(name="name",config=config,
args=(1,), callobj=py.builtin.callable, collection=collection) args=(1,), callobj=py.builtin.callable, session=session)
assert not f1 == f2 assert not f1 == f2
assert f1 != f2 assert f1 != f2
f3 = py.test.collect.Function(name="name", config=config, f3 = py.test.collect.Function(name="name", config=config,
args=(1,2), callobj=py.builtin.callable, collection=collection) args=(1,2), callobj=py.builtin.callable, session=session)
assert not f3 == f2 assert not f3 == f2
assert f3 != f2 assert f3 != f2
@ -221,7 +221,7 @@ class TestFunction:
assert f3 != f1 assert f3 != f1
f1_b = py.test.collect.Function(name="name", config=config, f1_b = py.test.collect.Function(name="name", config=config,
args=(1,), callobj=isinstance, collection=collection) args=(1,), callobj=isinstance, session=session)
assert f1 == f1_b assert f1 == f1_b
assert not f1 != f1_b assert not f1 != f1_b
@ -235,11 +235,11 @@ class TestFunction:
param = 1 param = 1
funcargs = {} funcargs = {}
id = "world" id = "world"
collection = testdir.Collection(config) session = testdir.Session(config)
f5 = py.test.collect.Function(name="name", config=config, f5 = py.test.collect.Function(name="name", config=config,
callspec=callspec1, callobj=isinstance, collection=collection) callspec=callspec1, callobj=isinstance, session=session)
f5b = py.test.collect.Function(name="name", config=config, f5b = py.test.collect.Function(name="name", config=config,
callspec=callspec2, callobj=isinstance, collection=collection) callspec=callspec2, callobj=isinstance, session=session)
assert f5 != f5b assert f5 != f5b
assert not (f5 == f5b) assert not (f5 == f5b)
@ -396,7 +396,7 @@ def test_generate_tests_only_done_in_subdir(testdir):
def test_modulecol_roundtrip(testdir): def test_modulecol_roundtrip(testdir):
modcol = testdir.getmodulecol("pass", withinit=True) modcol = testdir.getmodulecol("pass", withinit=True)
trail = modcol.nodeid trail = modcol.nodeid
newcol = modcol.collection.perform_collect([trail], genitems=0)[0] newcol = modcol.session.perform_collect([trail], genitems=0)[0]
assert modcol.name == newcol.name assert modcol.name == newcol.name

View File

@ -5,10 +5,10 @@ from pytest.plugin.resultlog import generic_path, ResultLog, \
from pytest.plugin.session import Node, Item, FSCollector from pytest.plugin.session import Node, Item, FSCollector
def test_generic_path(testdir): def test_generic_path(testdir):
from pytest.plugin.session import Collection from pytest.plugin.session import Session
config = testdir.parseconfig() config = testdir.parseconfig()
collection = Collection(config) session = Session(config)
p1 = Node('a', config=config, collection=collection) p1 = Node('a', config=config, session=session)
#assert p1.fspath is None #assert p1.fspath is None
p2 = Node('B', parent=p1) p2 = Node('B', parent=p1)
p3 = Node('()', parent = p2) p3 = Node('()', parent = p2)
@ -17,7 +17,7 @@ def test_generic_path(testdir):
res = generic_path(item) res = generic_path(item)
assert res == 'a.B().c' assert res == 'a.B().c'
p0 = FSCollector('proj/test', config=config, collection=collection) p0 = FSCollector('proj/test', config=config, session=session)
p1 = FSCollector('proj/test/a', parent=p0) p1 = FSCollector('proj/test/a', parent=p0)
p2 = Node('B', parent=p1) p2 = Node('B', parent=p1)
p3 = Node('()', parent = p2) p3 = Node('()', parent = p2)

View File

@ -244,7 +244,7 @@ class TestExecutionForked(BaseFunctionalTests):
assert rep.failed assert rep.failed
assert rep.when == "???" assert rep.when == "???"
class TestCollectionReports: class TestSessionReports:
def test_collect_result(self, testdir): def test_collect_result(self, testdir):
col = testdir.getmodulecol(""" col = testdir.getmodulecol("""
def test_func1(): def test_func1():

View File

@ -516,7 +516,7 @@ def test_traceconfig(testdir, monkeypatch):
def test_debug(testdir, monkeypatch): def test_debug(testdir, monkeypatch):
result = testdir.runpytest("--debug") result = testdir.runpytest("--debug")
result.stderr.fnmatch_lines([ result.stderr.fnmatch_lines([
"*registered*session*", "*pytest_sessionstart*session*",
]) ])
assert result.ret == 0 assert result.ret == 0

View File

@ -1,6 +1,6 @@
import py import py
from pytest.plugin.session import Collection from pytest.plugin.session import Session
class TestCollector: class TestCollector:
def test_collect_versus_item(self): def test_collect_versus_item(self):
@ -86,7 +86,7 @@ class TestCollector:
node = testdir.getpathnode(hello) node = testdir.getpathnode(hello)
assert isinstance(node, py.test.collect.File) assert isinstance(node, py.test.collect.File)
assert node.name == "hello.xxx" assert node.name == "hello.xxx"
nodes = node.collection.perform_collect([node.nodeid], genitems=False) nodes = node.session.perform_collect([node.nodeid], genitems=False)
assert len(nodes) == 1 assert len(nodes) == 1
assert isinstance(nodes[0], py.test.collect.File) assert isinstance(nodes[0], py.test.collect.File)
@ -292,7 +292,7 @@ class TestCustomConftests:
"*test_x*" "*test_x*"
]) ])
class TestCollection: class TestSession:
def test_parsearg(self, testdir): def test_parsearg(self, testdir):
p = testdir.makepyfile("def test_func(): pass") p = testdir.makepyfile("def test_func(): pass")
subdir = testdir.mkdir("sub") subdir = testdir.mkdir("sub")
@ -302,7 +302,7 @@ class TestCollection:
testdir.chdir() testdir.chdir()
subdir.chdir() subdir.chdir()
config = testdir.parseconfig(p.basename) config = testdir.parseconfig(p.basename)
rcol = Collection(config=config) rcol = Session(config=config)
assert rcol.fspath == subdir assert rcol.fspath == subdir
parts = rcol._parsearg(p.basename) parts = rcol._parsearg(p.basename)
@ -318,7 +318,7 @@ class TestCollection:
id = "::".join([p.basename, "test_func"]) id = "::".join([p.basename, "test_func"])
config = testdir.parseconfig(id) config = testdir.parseconfig(id)
topdir = testdir.tmpdir topdir = testdir.tmpdir
rcol = Collection(config) rcol = Session(config)
assert topdir == rcol.fspath assert topdir == rcol.fspath
rootid = rcol.nodeid rootid = rcol.nodeid
#root2 = rcol.perform_collect([rcol.nodeid], genitems=False)[0] #root2 = rcol.perform_collect([rcol.nodeid], genitems=False)[0]
@ -333,7 +333,7 @@ class TestCollection:
id = "::".join([p.basename, "test_func"]) id = "::".join([p.basename, "test_func"])
config = testdir.parseconfig(id) config = testdir.parseconfig(id)
topdir = testdir.tmpdir topdir = testdir.tmpdir
rcol = Collection(config) rcol = Session(config)
assert topdir == rcol.fspath assert topdir == rcol.fspath
hookrec = testdir.getreportrecorder(config) hookrec = testdir.getreportrecorder(config)
rcol.perform_collect() rcol.perform_collect()
@ -367,7 +367,7 @@ class TestCollection:
normid, normid,
]: ]:
config = testdir.parseconfig(id) config = testdir.parseconfig(id)
rcol = Collection(config=config) rcol = Session(config=config)
rcol.perform_collect() rcol.perform_collect()
items = rcol.items items = rcol.items
assert len(items) == 1 assert len(items) == 1
@ -392,7 +392,7 @@ class TestCollection:
id = p.basename id = p.basename
config = testdir.parseconfig(id) config = testdir.parseconfig(id)
rcol = Collection(config) rcol = Session(config)
hookrec = testdir.getreportrecorder(config) hookrec = testdir.getreportrecorder(config)
rcol.perform_collect() rcol.perform_collect()
items = rcol.items items = rcol.items
@ -400,7 +400,7 @@ class TestCollection:
assert len(items) == 2 assert len(items) == 2
hookrec.hookrecorder.contains([ hookrec.hookrecorder.contains([
("pytest_collectstart", ("pytest_collectstart",
"collector.fspath == collector.collection.fspath"), "collector.fspath == collector.session.fspath"),
("pytest_collectstart", ("pytest_collectstart",
"collector.__class__.__name__ == 'SpecialFile'"), "collector.__class__.__name__ == 'SpecialFile'"),
("pytest_collectstart", ("pytest_collectstart",
@ -417,7 +417,7 @@ class TestCollection:
test_aaa = aaa.join("test_aaa.py") test_aaa = aaa.join("test_aaa.py")
p.move(test_aaa) p.move(test_aaa)
config = testdir.parseconfig() config = testdir.parseconfig()
rcol = Collection(config) rcol = Session(config)
hookrec = testdir.getreportrecorder(config) hookrec = testdir.getreportrecorder(config)
rcol.perform_collect() rcol.perform_collect()
items = rcol.items items = rcol.items
@ -441,7 +441,7 @@ class TestCollection:
id = "." id = "."
config = testdir.parseconfig(id) config = testdir.parseconfig(id)
rcol = Collection(config) rcol = Session(config)
hookrec = testdir.getreportrecorder(config) hookrec = testdir.getreportrecorder(config)
rcol.perform_collect() rcol.perform_collect()
items = rcol.items items = rcol.items
@ -459,12 +459,13 @@ class TestCollection:
def test_serialization_byid(self, testdir): def test_serialization_byid(self, testdir):
p = testdir.makepyfile("def test_func(): pass") p = testdir.makepyfile("def test_func(): pass")
config = testdir.parseconfig() config = testdir.parseconfig()
rcol = Collection(config) rcol = Session(config)
rcol.perform_collect() rcol.perform_collect()
items = rcol.items items = rcol.items
assert len(items) == 1 assert len(items) == 1
item, = items item, = items
newcol = Collection(config) rcol.config.pluginmanager.unregister(name="session")
newcol = Session(config)
item2, = newcol.perform_collect([item.nodeid], genitems=False) item2, = newcol.perform_collect([item.nodeid], genitems=False)
assert item2.name == item.name assert item2.name == item.name
assert item2.fspath == item.fspath assert item2.fspath == item.fspath