From b5b8e5f0c2d462a95cd66c647b5eaaf08774547f Mon Sep 17 00:00:00 2001 From: holger krekel Date: Mon, 11 Oct 2010 12:54:28 +0200 Subject: [PATCH] advance customization docs, enhance docstrings, add more reference object docs. --HG-- branch : trunk --- doc/customize.txt | 198 ++++++++++++++-------------------------- doc/install.txt | 2 +- pytest/collect.py | 43 +++++---- pytest/hookspec.py | 29 ++++-- pytest/plugin/runner.py | 24 +++++ 5 files changed, 139 insertions(+), 157 deletions(-) diff --git a/doc/customize.txt b/doc/customize.txt index e04113f91..ee06e465b 100644 --- a/doc/customize.txt +++ b/doc/customize.txt @@ -256,158 +256,87 @@ initialisation, command line and configuration hooks generic "runtest" hooks ------------------------------ -Each test item is usually executed by calling the following three hooks: +Almost runtest related hooks receive a :py:class:`pytest.collect.Item` object. +.. autofunction:: pytest_runtest_protocol .. autofunction:: pytest_runtest_setup .. autofunction:: pytest_runtest_call .. autofunction:: pytest_runtest_teardown +.. autofunction:: pytest_runtest_makereport -For each of the three invocations a `call object`_ encapsulates -information about the outcome of the call and is subsequently used -to make a report object: +For deeper understanding you may look at the default implementation of +these hooks in :py:mod:`pytest.plugin.runner` and maybe also +in :py:mod:`pytest.plugin.pdb` which intercepts creation +of reports in order to drop to interactive debugging. -.. sourcecode:: python +The :py:mod:`pytest.plugin.terminal` reported specifically uses +the reporting hook to print information about a test run. - report = hook.pytest_runtest_makereport(item, call) - -For example, the `pytest_pdb plugin`_ uses this hook to activate -interactive debugging on failures when ``--pdb`` is specified on the -command line. - -Usually three reports will be generated for a single test item for each -of the three runtest hooks respectively. If ``pytest_runtest_setup`` -fails then ``pytest_runtest_teardown`` will be called but not -``pytest_runtest_call``. - -Each of the up to three reports is eventually fed to the logreport hook: - -.. sourcecode:: python - - pytest_runtest_logreport(report) - -A ``report`` object contains status and reporting information: - -.. sourcecode:: python - - report.longrepr = string/lines/object to print - report.when = "setup", "call" or "teardown" - report.shortrepr = letter for progress-report - report.passed = True or False - report.failed = True or False - report.skipped = True or False - -The `terminal plugin`_ uses this hook to print information -about a test run. - -The whole protocol described here is implemented via this hook: - -.. sourcecode:: python - - pytest_runtest_protocol(item) -> True - -.. _`call object`: - -The call object contains information about a performed call: - -.. sourcecode:: python - - call.excinfo = ExceptionInfo object or None - call.when = "setup", "call" or "teardown" - call.outerr = None or tuple of strings representing captured stdout/stderr - -.. _`pytest_pdb plugin`: plugin/pdb.html -.. _`terminal plugin`: plugin/terminal.html - - -generic collection hooks +collection hooks ------------------------------ -py.test calls the following two fundamental hooks for collecting files and directories: +py.test calls the following hooks for collecting files and directories: -.. sourcecode:: python - - def pytest_collect_directory(path, parent): - """ return Collection node or None for the given path. """ - - def pytest_collect_file(path, parent): - """ return Collection node or None for the given path. """ - -Both return a `collection node`_ for a given path. All returned -nodes from all hook implementations will participate in the -collection and running protocol. The ``parent`` object is -the parent node and may be used to access command line -options via the ``parent.config`` object. - - -Python test function and module hooks ----------------------------------------------------- +.. autofunction:: pytest_ignore_collect +.. autofunction:: pytest_collect_directory +.. autofunction:: pytest_collect_file For influencing the collection of objects in Python modules you can use the following hook: -.. sourcecode:: python - - def pytest_pycollect_makeitem(collector, name, obj): - """ return custom item/collector for a python object in a module, or None. """ - -This hook will be called for each Python object in a collected -Python module. The return value is a custom `collection node`_ or None. - -.. XXX or ``False`` if you want to indicate that the given item should not be collected. +.. autofunction:: pytest_pycollect_makeitem -Gateway initialization (distributed testing) ----------------------------------------------------- +reporting hooks +------------------------------ -(alpha) For distributed testing it can be useful to prepare the -remote environment. For this you can implement the newgateway hook: +Collection related reporting hooks: -.. sourcecode:: python +.. autofunction: pytest_collectstart +.. autofunction: pytest_log_itemcollect +.. autofunction: pytest_collectreport +.. autofunction: pytest_deselected - def pytest_gwmanage_newgateway(gateway, platinfo): - """ called after a gateway is instantiated. """ +And here is the central hook for reporting about +test execution: -The ``gateway`` object here has a ``spec`` attribute which is an ``execnet.XSpec`` -object, which has attributes that map key/values as specified from a ``--txspec`` -option. The platinfo object is a dictionary with information about the remote process: +.. autofunction: pytest_runtest_logreport -* ``version``: remote python's ``sys.version_info`` -* ``platform``: remote ``sys.platform`` -* ``cwd``: remote ``os.getcwd`` - - -.. _`collection process`: -.. _`collection node`: -.. _`test collection`: - - -Test Collection process +The test collection tree ====================================================== -the collection tree ---------------------------------- -The collecting process is iterative so that distribution -and execution of tests can start as soon as the first test -item is collected. Collection nodes with children are -called "Collectors" and terminal nodes are called "Items". -Here is an example of such a tree, generated with the -command ``py.test --collectonly py/xmlobj``:: +Default filesystem test discovery +----------------------------------------------- - - - - - - - - - - - - - - +Test collection starts from specified paths or from the current +directory. All tests are collected ahead of running the first test. +(This used to be different in earlier versions of ``py.test`` where +collection and running was interweaved which made test randomization +and distributed testing harder). + +Collection nodes which have children are called "Collectors" and otherwise +they are called "Items" or "test items". Here is an example of such a +tree:: + + testing $ py.test --collectonly test_parseonly.py + + + + + + + + + + + + + + + + + By default all directories not starting with a dot are traversed, looking for ``test_*.py`` and ``*_test.py`` files. Those Python @@ -441,3 +370,18 @@ name. Given a filesystem ``fspath`` it is constructed as follows: * perform ``sys.path.insert(0, basedir)``. * import the root package as ``root`` + +Complete reference of objects involved in hooks +=========================================================== + +.. autoclass:: pytest.plugin.runner.CallInfo + :members: + +.. autoclass:: pytest.plugin.runner.TestReport + :members: + +.. autoclass:: pytest.collect.Node + :members: + +.. autoclass:: pytest.collect.Item + :inherited-members: diff --git a/doc/install.txt b/doc/install.txt index 6b2031411..b522c90e1 100644 --- a/doc/install.txt +++ b/doc/install.txt @@ -82,7 +82,7 @@ all core features and runs unchanged under Python2 and Python3 interpreters. .. _`Python for Windows`: http://www.imladris.com/Scripts/PythonForWindows.html -.. _`Distribute`: .. _`Distribute for installation`: http://pypi.python.org/pypi/distribute#installation-instructions .. _`distribute installation`: http://pypi.python.org/pypi/distribute +.. include:: links.inc diff --git a/pytest/collect.py b/pytest/collect.py index 456958916..101da73eb 100644 --- a/pytest/collect.py +++ b/pytest/collect.py @@ -25,13 +25,20 @@ class HookProxy: class Node(object): """ 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): + #: a unique name with the scope of the parent self.name = name + + #: the parent collector node. self.parent = parent + self.config = config or parent.config + #: the collection this node is part of. self.collection = collection or getattr(parent, 'collection', None) + + #: the file where this item is contained/collected from. self.fspath = getattr(parent, 'fspath', None) self.ihook = HookProxy(self) self.keywords = self.readkeywords() @@ -130,13 +137,8 @@ class Node(object): repr_failure = _repr_failure_py class Collector(Node): - """ - Collector instances create children through collect() - and thus iteratively build a tree. attributes:: - - parent: attribute pointing to the parent collector - (or None if this is the root collector) - name: basename of this collector object + """ Collector instances create children through collect() + and thus iteratively build a tree. """ Directory = configproperty('Directory') Module = configproperty('Module') @@ -166,6 +168,15 @@ class Collector(Node): """ internal helper method to cache results of calling collect(). """ return self._memoizedcall('_collected', self.collect) + def _prunetraceback(self, traceback): + if hasattr(self, 'fspath'): + path = self.fspath + ntraceback = traceback.cut(path=self.fspath) + if ntraceback == traceback: + ntraceback = ntraceback.cut(excludepath=py._pydir) + traceback = ntraceback.filter() + return traceback + # ********************************************************************** # DEPRECATED METHODS # ********************************************************************** @@ -197,15 +208,6 @@ class Collector(Node): """ return self.collect_by_name(name) - def _prunetraceback(self, traceback): - if hasattr(self, 'fspath'): - path = self.fspath - ntraceback = traceback.cut(path=self.fspath) - if ntraceback == traceback: - ntraceback = ntraceback.cut(excludepath=py._pydir) - traceback = ntraceback.filter() - return traceback - class FSCollector(Collector): def __init__(self, fspath, parent=None, config=None, collection=None): fspath = py.path.local(fspath) @@ -264,7 +266,10 @@ class Directory(FSCollector): return self.ihook.pytest_collect_directory(path=path, parent=self) class Item(Node): - """ a basic test item. """ + """ a basic test invocation item. Note that for a single function + there might be multiple test invocation items. Attributes: + + """ def _deprecated_testexecution(self): if self.__class__.run != Item.run: warnoldtestrun(function=self.run) diff --git a/pytest/hookspec.py b/pytest/hookspec.py index 7b75f3aed..b61c0e9a9 100644 --- a/pytest/hookspec.py +++ b/pytest/hookspec.py @@ -53,18 +53,20 @@ 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 - collection hooks. + """ return True to prevent considering this path for collection. + This hook is consulted for all files and directories prior to calling + more specific hooks. """ pytest_ignore_collect.firstresult = True def pytest_collect_directory(path, parent): - """ return Collection node or None for the given path. """ + """ return collection Node or None for the given path. Any new node + needs to have the specified ``parent`` as a parent.""" pytest_collect_directory.firstresult = True def pytest_collect_file(path, parent): - """ return Collection node or None for the given path. """ + """ return collection Node or None for the given path. Any new node + needs to have the specified ``parent`` as a parent.""" # logging hooks for collection def pytest_collectstart(collector): @@ -113,23 +115,30 @@ 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. """ + """ implements the standard runtest_setup/call/teardown protocol including + capturing exceptions and calling reporting hooks on the results accordingly. + + :return boolean: True if no further hook implementations should be invoked. + """ 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(). """ + """ called before ``pytest_runtest_call(item)``. """ def pytest_runtest_call(item): - """ execute test item. """ + """ called to execute the test ``item``. """ def pytest_runtest_teardown(item): - """ called after pytest_runtest_call(). """ + """ called after ``pytest_runtest_call``. """ def pytest_runtest_makereport(item, call): - """ make a test report for the given item and call outcome. """ + """ return a :py:class:`pytest.plugin.runner.TestReport` object + for the given :py:class:`pytest.collect.Item` and + :py:class:`pytest.plugin.runner.CallInfo`. + """ pytest_runtest_makereport.firstresult = True def pytest_runtest_logreport(report): diff --git a/pytest/plugin/runner.py b/pytest/plugin/runner.py index 7f7672b49..f9721c60d 100644 --- a/pytest/plugin/runner.py +++ b/pytest/plugin/runner.py @@ -113,8 +113,11 @@ def call_runtest_hook(item, when): return CallInfo(lambda: ihook(item=item), when=when) class CallInfo: + """ Call Information about a hook call. """ + #: None or ExceptionInfo object. excinfo = None def __init__(self, func, when): + #: one of "setup", "call", "teardown" specifying the runtest phase. self.when = when try: self.result = func() @@ -169,15 +172,36 @@ def pytest_runtest_makereport(item, call): keywords, outcome, longrepr, when) class TestReport(BaseReport): + """ Basic test report object (also used for setup and teardown calls if + they fail). + """ def __init__(self, nodeid, nodenames, fspath, location, keywords, outcome, longrepr, when): + #: normalized collection node id self.nodeid = nodeid + + #: list of names indicating position in collection tree. self.nodenames = nodenames + + #: the collected path of the file containing the test. self.fspath = fspath # where the test was collected + + #: a (filesystempath, lineno, domaininfo) tuple indicating the + #: actual location of a test item - it might be different from the + #: collected one e.g. if a method is inherited from a different module. self.location = location + + #: a name -> value dictionary containing all keywords and + #: markers associated with a test invocation. self.keywords = keywords + + #: test outcome, always one of "passed", "failed", "skipped". self.outcome = outcome + + #: None or a failure representation. self.longrepr = longrepr + + #: one of 'setup', 'call', 'teardown' to indicate runtest phase. self.when = when def __repr__(self):