diff --git a/doc/customize.txt b/doc/customize.txt index e94efed57..a32bec338 100644 --- a/doc/customize.txt +++ b/doc/customize.txt @@ -15,38 +15,6 @@ You can see command line options by running:: This will display all available command line options in your specific environment. -.. _`project-specific test configuration`: -.. _`collect_ignore`: - -conftest.py: project specific hooks and configuration --------------------------------------------------------- - -A unique feature of py.test are its ``conftest.py`` files which -allow to: - -* `set option defaults`_ - -or set particular variables to influence the testing process: - -* ``pytest_plugins``: list of named plugins to load - -* ``collect_ignore``: list of paths to ignore during test collection, relative to the containing ``conftest.py`` file - -* ``rsyncdirs``: list of to-be-rsynced directories for distributed - testing, relative to the containing ``conftest.py`` file. - -You may put a conftest.py files in your project root directory or into -your package directory if you want to add project-specific test options. - -``py.test`` loads all ``conftest.py`` files upwards from the command -line file arguments. It usually looks up configuration values -right-to-left, i.e. the closer conftest files will be checked first. -This means you can have a ``conftest.py`` in your very home directory to -have some global configuration values. - -.. _`specify funcargs`: funcargs.html#application-setup-tutorial-example - -.. _`set option defaults`: setting persistent option defaults ------------------------------------ @@ -57,13 +25,13 @@ py.test will lookup option values in this order: * conftest.py files * environment variables -To find out about the particular switches and type:: +To get an overview on existing names and settings type:: py.test --help-config -This will print information about all options in your -environment, including your local plugins. - +This will print information about all available options +in your environment, including your local plugins and +command line options. .. _`function arguments`: funcargs.html .. _`extensions`: @@ -73,26 +41,51 @@ Plugin basics and project configuration .. _`local plugin`: -project specific "local" or named "global" plugins +py.test implements all aspects of its functionality by calling `well specified +hooks`_. Hook functions are discovered in :file:`conftest.py` files or in +`named plugins`_. :file:`conftest.py` files are useful for keeping test +extensions and customizations close to test code. + +local conftest.py plugins -------------------------------------------------------------- -py.test implements much of its functionality by calling `well specified -hooks`_. Python modules which contain such hook functions are called -plugins. Hook functions are discovered in ``conftest.py`` files or in -`named plugins`_. ``conftest.py`` files are sometimes called -"anonymous" or conftest plugins. They are useful for keeping test -extensions close to your application. Named plugins are normal python -modules or packages that can be distributed separately. Named plugins -need to follow a naming pattern; they have an all lowercase ``pytest_`` -prefixed name. While conftest plugins are discovered automatically, -named plugins must be explicitely specified. +local `conftest.py` plugins are usually automatically loaded and +registered but its contained hooks are only called when collecting or +running tests in files or directories next to or below the ``conftest.py`` +file. Assume the following layout and content of files:: + + a/conftest.py: + def pytest_runtest_setup(item): + print ("setting up", item) + + a/test_in_subdir.py: + def test_sub(): + pass + + test_flat.py: + def test_flat(): + pass + +Here is how you might run it:: + + py.test test_flat.py # will not show "setting up" + py.test a/test_sub.py # will show "setting up" + +``py.test`` loads all ``conftest.py`` files upwards from the command +line file arguments. It usually looks up configuration values or hooks +right-to-left, i.e. the closer conftest files are checked before +the further away ones. This means you can have a ``conftest.py`` +in your home directory to provide global configuration values. + +.. Note:: + if you have ``conftest.py`` files which do not reside in a + python package directory (one containing an ``__init__.py``) then + "import conftest" will be ambigous and should be avoided. **If you + want to import anything from a ``conftest.py`` file it is better + to put it inside a package to disambiguate. .. _`named plugins`: plugin/index.html -.. _`tool startup`: -.. _`loaded at tool startup`: -.. _`test tool starts up`: - Plugin discovery at tool startup -------------------------------------------- @@ -106,10 +99,10 @@ py.test loads plugin modules at tool startup in the following way: * by pre-scanning the command line for the ``-p name`` option and loading the specified plugin before actual command line parsing. -* by loading all `conftest.py plugin`_ files as inferred by the command line - invocation (test files and all of its parent directories). - Note that ``conftest.py`` files from sub directories are loaded - during test collection and not at tool startup. +* by loading all :file:`conftest.py` files as inferred by the command line + invocation (test files and all of its *parent* directories). + Note that ``conftest.py`` files from *sub* directories are by default + not loaded at tool startup. * by recursively loading all plugins specified by the ``pytest_plugins`` variable in a ``conftest.py`` file @@ -132,8 +125,8 @@ must be lowercase. Writing per-project plugins (conftest.py) ------------------------------------------------------ -The purpose of ``conftest.py`` files is to allow `project-specific -test configuration`_. They thus make for a good place to implement +The purpose of :file:`conftest.py` files is to allow project-specific +test customization. They thus make for a good place to implement project-specific test related features through hooks. For example you may set the `collect_ignore`_ variable depending on a command line option by defining the following hook in a ``conftest.py`` file:: @@ -147,6 +140,7 @@ by defining the following hook in a ``conftest.py`` file:: collect_ignore[:] = [] .. _`setuptools entry points`: +.. _registered: Writing setuptools-registered plugins ------------------------------------------------------ @@ -350,3 +344,30 @@ Reference of important objects involved in hooks .. autoclass:: pytest.plugin.runner.TestReport :members: + +conftest.py configuration files +================================================= + +conftest.py reference docs + +A unique feature of py.test are its ``conftest.py`` files which allow +project and directory specific customizations to testing. + +* `set option defaults`_ + +or set particular variables to influence the testing process: + +* ``pytest_plugins``: list of named plugins to load + +* ``collect_ignore``: list of paths to ignore during test collection, relative to the containing ``conftest.py`` file + +* ``rsyncdirs``: list of to-be-rsynced directories for distributed + testing, relative to the containing ``conftest.py`` file. + +You may put a conftest.py files in your project root directory or into +your package directory if you want to add project-specific test options. + + +.. _`specify funcargs`: funcargs.html#application-setup-tutorial-example + +.. _`set option defaults`: diff --git a/pytest/plugin/config.py b/pytest/plugin/config.py index d9f484b6a..89433a4d2 100644 --- a/pytest/plugin/config.py +++ b/pytest/plugin/config.py @@ -1,6 +1,6 @@ import py -import os +import sys, os from pytest._core import PluginManager def pytest_cmdline_parse(pluginmanager, args): @@ -194,14 +194,10 @@ class Conftest(object): try: return self._conftestpath2mod[conftestpath] except KeyError: - if not conftestpath.dirpath('__init__.py').check(file=1): - # HACK: we don't want any "globally" imported conftest.py, - # prone to conflicts and subtle problems - modname = str(conftestpath).replace('.', conftestpath.sep) - mod = conftestpath.pyimport(modname=modname) - else: - mod = conftestpath.pyimport() - self._conftestpath2mod[conftestpath] = mod + pkgpath = conftestpath.pypkgpath() + if pkgpath is None: + _ensure_removed_sysmodule(conftestpath.purebasename) + self._conftestpath2mod[conftestpath] = mod = conftestpath.pyimport() dirpath = conftestpath.dirpath() if dirpath in self._path2confmods: for path, mods in self._path2confmods.items(): @@ -216,6 +212,11 @@ class Conftest(object): self._onimport(mod) return mod +def _ensure_removed_sysmodule(modname): + try: + del sys.modules[modname] + except KeyError: + pass class CmdOptions(object): """ holds cmdline options as attributes.""" diff --git a/testing/test_conftest.py b/testing/test_conftest.py index 53f4dfd1f..2e4a3d3b4 100644 --- a/testing/test_conftest.py +++ b/testing/test_conftest.py @@ -106,6 +106,27 @@ def test_doubledash_not_considered(testdir): l = conftest.getconftestmodules(None) assert len(l) == 0 +def test_conftest_global_import(testdir): + testdir.makeconftest("x=3") + p = testdir.makepyfile(""" + import py + from pytest.plugin.config import Conftest + conf = Conftest() + mod = conf.importconftest(py.path.local("conftest.py")) + assert mod.x == 3 + import conftest + assert conftest is mod, (conftest, mod) + subconf = py.path.local().ensure("sub", "conftest.py") + subconf.write("y=4") + mod2 = conf.importconftest(subconf) + assert mod != mod2 + assert mod2.y == 4 + import conftest + assert conftest is mod2, (conftest, mod) + """) + res = testdir.runpython(p) + assert res.ret == 0 + def test_conftestcutdir(testdir): conf = testdir.makeconftest("") p = testdir.mkdir("x")