===============
Writing Plugins
===============

nose2 supports plugins for test collection, selection, observation and
reporting -- among other things. There are two basic rules for plugins:

* Plugin classes must subclass :class:`nose2.events.Plugin`.

* Plugins may implement any of the methods described in the
  :doc:`hook_reference`.


Hello World
===========

Here's a basic plugin. It doesn't do anything besides log a message at
the start of a test run.

.. code-block:: python

    import logging
    import os

    from nose2.events import Plugin

    log = logging.getLogger('nose2.plugins.helloworld')

    class HelloWorld(Plugin):
        configSection = 'helloworld'
        commandLineSwitch = (None, 'hello-world', 'Say hello!')

        def startTestRun(self, event):
            log.info('Hello pluginized world!')

To see this plugin in action, save it into an importable module, then
add that module to the ``plugins`` key in the ``[unittest]`` section
of a config file loaded by nose2, such as ``unittest.cfg``. Then run
nose2::

  nose2 --log-level=INFO --hello-world

And you should see the log message before the first dot appears.


Loading plugins
===============

As mentioned above, for nose2 to find a plugin, it must be in an
importable module, and the module must be listed under the ``plugins``
key in the ``[unittest]`` section of a config file loaded by nose2:

.. code-block:: ini

   [unittest]
   plugins = mypackage.someplugin
             otherpackage.thatplugin
             thirdpackage.plugins.metoo

As you can see, plugin *modules* are listed, one per line. All plugin
classes in those modules will be loaded -- but not necessarily
active. Typically plugins do not activate themselves ("register")
without seeing a command-line flag, or ``always-on = True`` in their
config file section.


Command-line Options
====================

nose2 uses `argparse`_ for command-line argument parsing. Plugins may
enable command-line options that register them as active, or take
arguments or flags controlling their operation.

The most basic thing to do is to set the plugin's
``commandLineSwitch`` attribute, which will automatically add a
command-line flag that registers the plugin.

To add other flags or arguments, you can use the Plugin methods
:meth:`nose2.events.Plugin.addFlag`,
:meth:`nose2.events.Plugin.addArgument` or
:meth:`nose2.events.Plugin.addOption`. If those don't offer enough
flexibility, you can directly manipulate the argument parser by
accessing ``self.session.argparse`` or the plugin option group by
accessing ``self.session.pluginargs``.

Please note though that the *majority* of your plugin's configuration
should be done via config file options, not command line options.


Config File Options
===================

Plugins may specify a config file section that holds their
configuration by setting their ``configSection`` attribute. All
plugins, regardless of whether they specify a config section, have a
``config`` attribute that holds a :class:`nose2.config.Config`
instance. This will be empty of values if the plugin does not specify
a config section or if no loaded config file includes that section.

Plugins should extract the user's configuration selections from their
config attribute in their ``__init__`` methods. Plugins that want to
use nose2's `Sphinx`_ extension to automatically document themselves
**must** do so.

Config file options may be extracted as strings, ints, booleans or
lists.

You should provide reasonable defaults for all config options.

Guidelines
==========

Events
------

nose2's plugin API is based on the API in unittest2's
``plugins`` branch (under-development). Its differs from nose's
in one major area: what it passes to hooks. Where nose passes a
variety of arguments, nose2 *always passes an event*. The events are
listed in the :doc:`event_reference`.

Here's the key thing about that: *event attributes are
read-write*. Unless stated otherwise in the documentation for a hook,
you can set a new value for any event attribute, and *this will do
something*. Plugins and nose2 systems will see that new value and
either use it instead of what was originally set in the event
(example: the reporting stream or test executor), or use it to
supplement something they find elsewhere (example: extraTests on a
test loading event).

"Handling" events
~~~~~~~~~~~~~~~~~

Many hooks give plugins a chance to completely handle events,
bypassing other plugins and any core nose2 operations. To do this, a
plugin sets ``event.handled`` to True and, generally, returns an
appropriate value from the hook method. What is an appropriate value
varies by hook, and some hooks *can't* be handled in this way. But
even for hooks where handling the event doesn't stop all processing,
it *will* stop subsequently-loaded plugins from seeing the event.

Logging
-------

nose2 uses the logging classes from the standard library. To enable users
to view debug messages easily, plugins should use ``logging.getLogger()`` to
acquire a logger in the ``nose2.plugins`` namespace.

.. todo ::

   more guidelines

Recipes
=======

* Writing a plugin that monitors or controls test result output

  Implement any of the ``report*`` hook methods, especially if you
  want to output to the console. If outputting to file or other system,
  you might implement :func:`testOutcome` instead.

  Example: :class:`nose2.plugins.result.ResultReporter`

* Writing a plugin that handles exceptions

  If you just want to handle some exceptions as skips or failures
  instead of errors, see :class:`nose2.plugins.outcomes.Outcomes`,
  which offers a simple way to do that. Otherwise, implement
  :func:`setTestOutcome` to change test outcomes.

  Example: :class:`nose2.plugins.outcomes.Outcomes`

* Writing a plugin that adds detail to error reports

  Implement :func:`testOutcome` and put your extra information into
  ``event.metadata``, then implement :func:`outcomeDetail` to extract
  it and add it to the error report.

  Examples: :class:`nose2.plugins.buffer.OutputBufferPlugin`, :class:`nose2.plugins.logcapture.LogCapture`

* Writing a plugin that loads tests from files other than python modules

  Implement :func:`handleFile`.

  Example: :class:`nose2.plugins.doctests.DocTestLoader`

* Writing a plugin that loads tests from python modules

  Implement at least :func:`loadTestsFromModule`.

  .. _loading-from-module:

  .. warning ::

     One thing to beware of here is that if you return tests as
     dynamically-generated test cases, or instances of a testcase
     class that is defined *anywhere* but the module being loaded, you
     *must* use :func:`nose2.util.transplant_class` to make the test
     case class appear to have originated in that module. Otherwise,
     module-level fixtures will not work for that test, and may be
     ignored entirely for the module if there are no test cases that
     are or appear to be defined there.

* Writing a plugin that prints a report

  Implement :func:`beforeErrorList`, :func:`beforeSummaryReport` or
  :func:`afterSummaryReport`

  Example: :class:`nose2.plugins.prof.Profiler`

* Writing a plugin that selects or rejects tests

  Implement :class:`matchPath` or :class:`getTestCaseNames`.

  Example: :class:`nose2.plugins.loader.parameters.Parameters`

.. _argparse : http://pypi.python.org/pypi/argparse/1.2.1
.. _Sphinx : http://sphinx.pocoo.org/
