get option settings from ini-file. make getting configuration options from conftest.py only an internal feature.

--HG--
branch : trunk
This commit is contained in:
holger krekel 2010-10-31 17:41:58 +01:00
parent 1280041f0c
commit b6ec5a575d
12 changed files with 145 additions and 159 deletions

View File

@ -5,22 +5,39 @@ Customizing and Extending py.test
basic test configuration basic test configuration
=================================== ===================================
Command line options Command line options and configuration file settings
--------------------------------- -----------------------------------------------------------------
You can get help on options and configuration by running:: You can get help on options and configuration options by running::
py.test -h # or --help py.test -h # prints options _and_ config file settings
This will display command line options, ini-settings and conftest.py This will display command line and configuration file settings
settings in your specific environment. which were registered by installed plugins.
reading test configuration from ini-files how test configuration is read from setup/tox ini-files
-------------------------------------------------------- --------------------------------------------------------
py.test tries to find a configuration INI format file, trying py.test looks for the first ``[pytest]`` section in either the first ``setup.cfg`` or the first ``tox.ini`` file found upwards from the arguments. Example::
to find a section ``[pytest]`` in a ``tox.ini`` (or XXX ``pytest.ini`` file).
Possible entries in a ``[pytest]`` section are: py.test path/to/testdir
will look in the following dirs for a config file::
path/to/testdir/setup.cfg
path/to/setup.cfg
path/setup.cfg
setup.cfg
... # up until root of filesystem
path/to/testdir/tox.ini
path/to/tox.ini
path/tox.ini
... # up until root of filesystem
If no path was provided at all the current working directory is used for the lookup.
builtin configuration file options
----------------------------------------------
.. confval:: minversion = VERSTRING .. confval:: minversion = VERSTRING
@ -31,28 +48,14 @@ Possible entries in a ``[pytest]`` section are:
.. confval:: addargs = OPTS .. confval:: addargs = OPTS
add the specified ``OPTS`` to the set of command line arguments as if they add the specified ``OPTS`` to the set of command line arguments as if they
had been specified by the user. Example:: had been specified by the user. Example: if you have this ini file content::
addargs = --maxfail=2 -rf # exit after 2 failures, report fail info [pytest]
addargs = --maxfail=2 -rf # exit after 2 failures, report fail info
issuing ``py.test test_hello.py`` actually means::
setting persistent option defaults py.test --maxfail=2 -rf test_hello.py
------------------------------------
py.test will lookup option values in this order:
* command line
* ``[pytest]`` section in upwards ``setup.cfg`` or ``tox.ini`` files.
* conftest.py files
* environment variables
To get an overview on existing names and settings type::
py.test --help-config
This will print information about all available options
in your environment, including your local plugins and
command line options.
.. _`function arguments`: funcargs.html .. _`function arguments`: funcargs.html
.. _`extensions`: .. _`extensions`:
@ -70,10 +73,8 @@ extensions and customizations close to test code.
local conftest.py plugins local conftest.py plugins
-------------------------------------------------------------- --------------------------------------------------------------
local `conftest.py` plugins are usually automatically loaded and local ``conftest.py`` plugins contain directory-specific hook implemenations. Its contained runtest- and collection- related hooks are called when collecting or running tests in files or directories next to or below the ``conftest.py``
registered but its contained hooks are only called when collecting or file. Example: Assume the following layout and content of files::
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: a/conftest.py:
def pytest_runtest_setup(item): def pytest_runtest_setup(item):
@ -93,17 +94,18 @@ Here is how you might run it::
py.test a/test_sub.py # will show "setting up" py.test a/test_sub.py # will show "setting up"
``py.test`` loads all ``conftest.py`` files upwards from the command ``py.test`` loads all ``conftest.py`` files upwards from the command
line file arguments. It usually looks up configuration values or hooks line file arguments. It usually performs look up right-to-left, i.e.
right-to-left, i.e. the closer conftest files are checked before the hooks in "closer" conftest files will be called earlier than further
the further away ones. This means you can have a ``conftest.py`` away ones. This means you can even have a ``conftest.py`` file in your home
in your home directory to provide global configuration values. directory to customize test functionality globally for all of your projects.
.. Note:: .. Note::
if you have ``conftest.py`` files which do not reside in a If you have ``conftest.py`` files which do not reside in a
python package directory (i.e. one containing an ``__init__.py``) then python package directory (i.e. one containing an ``__init__.py``) then
"import conftest" will be ambigous and should be avoided. If you "import conftest" can be ambigous because there might be other
ever want to import anything from a ``conftest.py`` file ``conftest.py`` files as well on your PYTHONPATH or ``sys.path``.
put it inside a package. You avoid trouble this way. It is good practise for projects to put ``conftest.py`` within a package
scope or to never import anything from the conftest.py file.
.. _`named plugins`: plugin/index.html .. _`named plugins`: plugin/index.html
@ -115,9 +117,6 @@ py.test loads plugin modules at tool startup in the following way:
* by loading all plugins registered through `setuptools entry points`_. * by loading all plugins registered through `setuptools entry points`_.
* by reading the ``PYTEST_PLUGINS`` environment variable
and importing the comma-separated list of named plugins.
* by pre-scanning the command line for the ``-p name`` option * by pre-scanning the command line for the ``-p name`` option
and loading the specified plugin before actual command line parsing. and loading the specified plugin before actual command line parsing.
@ -127,39 +126,17 @@ py.test loads plugin modules at tool startup in the following way:
not loaded at tool startup. not loaded at tool startup.
* by recursively loading all plugins specified by the * by recursively loading all plugins specified by the
``pytest_plugins`` variable in a ``conftest.py`` file ``pytest_plugins`` variable in ``conftest.py`` files
Requiring/Loading plugins in a test module or plugin Requiring/Loading plugins in a test module or plugin
------------------------------------------------------------- -------------------------------------------------------------
You can specify plugins in a test module or a plugin like this:: You can require plugins in a test module or a plugin like this::
pytest_plugins = "name1", "name2", pytest_plugins = "name1", "name2",
When the test module or plugin is loaded the specified plugins When the test module or plugin is loaded the specified plugins
will be loaded. If you specify plugins without the ``pytest_`` will be loaded.
prefix it will be automatically added. All plugin names
must be lowercase.
.. _`conftest.py plugin`:
.. _`conftestplugin`:
Writing per-project plugins (conftest.py)
------------------------------------------------------
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::
# ./conftest.py in your root or package dir
collect_ignore = ['hello', 'test_world.py']
def pytest_addoption(parser):
parser.addoption("--runall", action="store_true", default=False)
def pytest_configure(config):
if config.getvalue("runall"):
collect_ignore[:] = []
.. _`setuptools entry points`: .. _`setuptools entry points`:
.. _registered: .. _registered:
@ -357,10 +334,13 @@ Reference of important objects involved in hooks
.. autoclass:: pytest.plugin.config.Parser .. autoclass:: pytest.plugin.config.Parser
:members: :members:
.. autoclass:: pytest.plugin.session.File
:inherited-members:
.. autoclass:: pytest.plugin.session.Item .. autoclass:: pytest.plugin.session.Item
:inherited-members: :inherited-members:
.. autoclass:: pytest.plugin.session.Node .. autoclass:: pytest.plugin.python.Function
:members: :members:
.. autoclass:: pytest.plugin.runner.CallInfo .. autoclass:: pytest.plugin.runner.CallInfo
@ -370,30 +350,3 @@ Reference of important objects involved in hooks
:members: :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`:

View File

@ -2,8 +2,6 @@
import py import py
failure_demo = py.path.local(__file__).dirpath('failure_demo.py') failure_demo = py.path.local(__file__).dirpath('failure_demo.py')
pytest_plugins = "pytest_pytester"
def test_failure_demo_fails_properly(testdir): def test_failure_demo_fails_properly(testdir):
target = testdir.tmpdir.join(failure_demo.basename) target = testdir.tmpdir.join(failure_demo.basename)
failure_demo.copy(target) failure_demo.copy(target)

View File

@ -11,7 +11,7 @@ def pytest_cmdline_parse(pluginmanager, args):
return config return config
def pytest_addoption(parser): def pytest_addoption(parser):
parser.addini('addargs', 'extra command line arguments') parser.addini('addargs', 'default command line arguments')
parser.addini('minversion', 'minimally required pytest version') parser.addini('minversion', 'minimally required pytest version')
class Parser: class Parser:
@ -72,14 +72,9 @@ class Parser:
setattr(option, name, value) setattr(option, name, value)
return args return args
def addini(self, name, description): def addini(self, name, description, type=None):
""" add an ini-file option with the given name and description. """ """ add an ini-file option with the given name and description. """
self._inidict[name] = description self._inidict[name] = (description, type)
def setfromini(self, inisection, option):
for name, value in inisection.items():
assert name in self._inidict
return setattr(option, name, value)
class OptionGroup: class OptionGroup:
def __init__(self, name, description="", parser=None): def __init__(self, name, description="", parser=None):
@ -330,7 +325,6 @@ class Config(object):
self._preparse(args) self._preparse(args)
self._parser.hints.extend(self.pluginmanager._hints) self._parser.hints.extend(self.pluginmanager._hints)
args = self._parser.parse_setoption(args, self.option) args = self._parser.parse_setoption(args, self.option)
self._parser.setfromini(self.inicfg, self.option)
if not args: if not args:
args.append(py.std.os.getcwd()) args.append(py.std.os.getcwd())
self.args = args self.args = args
@ -358,12 +352,26 @@ class Config(object):
return py.path.local.make_numbered_dir(prefix=basename, return py.path.local.make_numbered_dir(prefix=basename,
keep=0, rootdir=basetemp, lock_timeout=None) keep=0, rootdir=basetemp, lock_timeout=None)
def getconftest_pathlist(self, name, path=None): def getini(self, name):
""" return a matching value, which needs to be sequence """ return configuration value from an ini file. """
of filenames that will be returned as a list of Path try:
objects (they can be relative to the location description, type = self._parser._inidict[name]
where they were found). except KeyError:
""" raise ValueError("unknown configuration value: %r" %(name,))
try:
value = self.inicfg[name]
except KeyError:
return # None indicates nothing found
if type == "pathlist":
dp = py.path.local(self.inicfg.config.path).dirpath()
l = []
for relpath in py.std.shlex.split(value):
l.append(dp.join(relpath, abs=True))
return l
else:
return value
def _getconftest_pathlist(self, name, path=None):
try: try:
mod, relroots = self._conftest.rget_with_confmod(name, path) mod, relroots = self._conftest.rget_with_confmod(name, path)
except KeyError: except KeyError:
@ -377,6 +385,21 @@ class Config(object):
l.append(relroot) l.append(relroot)
return l return l
def _getconftest(self, name, path=None, check=False):
if check:
self._checkconftest(name)
return self._conftest.rget(name, path)
def getvalue(self, name, path=None):
""" return 'name' value looked up from command line 'options'.
(deprecated) if we can't find the option also lookup
the name in a matching conftest file.
"""
try:
return getattr(self.option, name)
except AttributeError:
return self._getconftest(name, path, check=False)
def getvalueorskip(self, name, path=None): def getvalueorskip(self, name, path=None):
""" return getvalue(name) or call py.test.skip if no value exists. """ """ return getvalue(name) or call py.test.skip if no value exists. """
try: try:
@ -387,17 +410,6 @@ class Config(object):
except KeyError: except KeyError:
py.test.skip("no %r value found" %(name,)) py.test.skip("no %r value found" %(name,))
def getvalue(self, name, path=None):
""" return 'name' value looked up from the 'options'
and then from the first conftest file found up
the path (including the path itself).
if path is None, lookup the value in the initial
conftest modules found during command line parsing.
"""
try:
return getattr(self.option, name)
except AttributeError:
return self._conftest.rget(name, path)
def getcfg(args, inibasenames): def getcfg(args, inibasenames):
if not args: if not args:

View File

@ -40,9 +40,8 @@ def showhelp(config):
tw.write(config._parser.optparser.format_help()) tw.write(config._parser.optparser.format_help())
tw.line() tw.line()
tw.line() tw.line()
tw.sep( "=", "config file settings") #tw.sep( "=", "config file settings")
tw.line("the following values can be defined in [pytest] sections of") tw.line("setup.cfg or tox.ini options to be put into [pytest] section:")
tw.line("setup.cfg or tox.ini files:")
tw.line() tw.line()
for name, help in sorted(config._parser._inidict.items()): for name, help in sorted(config._parser._inidict.items()):
@ -50,21 +49,21 @@ def showhelp(config):
tw.line(line[:tw.fullwidth]) tw.line(line[:tw.fullwidth])
tw.line() ; tw.line() tw.line() ; tw.line()
#tw.sep( "=", "conftest.py settings") #tw.sep("=")
tw.line("the following values can be defined in conftest.py files") return
tw.line("conftest.py options:")
tw.line() tw.line()
for name, help in conftest_options: conftestitems = sorted(config._parser._conftestdict.items())
for name, help in conftest_options + conftestitems:
line = " %-15s %s" %(name, help) line = " %-15s %s" %(name, help)
tw.line(line[:tw.fullwidth]) tw.line(line[:tw.fullwidth])
tw.line() tw.line()
tw.sep( "=") #tw.sep( "=")
conftest_options = [
conftest_options = (
('pytest_plugins', 'list of plugin names to load'), ('pytest_plugins', 'list of plugin names to load'),
('collect_ignore', '(relative) paths ignored during collection'), ]
('rsyncdirs', 'to-be-rsynced directories for dist-testing'),
)
def pytest_report_header(config): def pytest_report_header(config):
lines = [] lines = []

View File

@ -248,6 +248,13 @@ class TmpTestdir:
def makeconftest(self, source): def makeconftest(self, source):
return self.makepyfile(conftest=source) return self.makepyfile(conftest=source)
def makeini(self, source):
return self.makefile('.ini', tox=source)
def getinicfg(self, source):
p = self.makeini(source)
return py.iniconfig.IniConfig(p)['pytest']
def makepyfile(self, *args, **kwargs): def makepyfile(self, *args, **kwargs):
return self._makefile('.py', args, kwargs) return self._makefile('.py', args, kwargs)

View File

@ -9,6 +9,7 @@ import pytest
import os, sys import os, sys
def pytest_addoption(parser): def pytest_addoption(parser):
group = parser.getgroup("general", "running and selection options") group = parser.getgroup("general", "running and selection options")
group._addoption('-x', '--exitfirst', action="store_true", default=False, group._addoption('-x', '--exitfirst', action="store_true", default=False,
dest="exitfirst", dest="exitfirst",
@ -32,6 +33,7 @@ def pytest_addoption(parser):
group.addoption('--basetemp', dest="basetemp", default=None, metavar="dir", group.addoption('--basetemp', dest="basetemp", default=None, metavar="dir",
help="base temporary directory for this test run.") help="base temporary directory for this test run.")
def pytest_namespace(): def pytest_namespace():
return dict(collect=dict(Item=Item, Collector=Collector, return dict(collect=dict(Item=Item, Collector=Collector,
File=File, Directory=Directory)) File=File, Directory=Directory))
@ -64,7 +66,7 @@ def pytest_runtest_mainloop(session):
def pytest_ignore_collect(path, config): def pytest_ignore_collect(path, config):
p = path.dirpath() p = path.dirpath()
ignore_paths = config.getconftest_pathlist("collect_ignore", path=p) ignore_paths = config._getconftest_pathlist("collect_ignore", path=p)
ignore_paths = ignore_paths or [] ignore_paths = ignore_paths or []
excludeopt = config.getvalue("ignore") excludeopt = config.getvalue("ignore")
if excludeopt: if excludeopt:
@ -445,7 +447,6 @@ class Collector(Node):
raise NotImplementedError("abstract") raise NotImplementedError("abstract")
def collect_by_name(self, name): def collect_by_name(self, name):
""" return a child matching the given name, else None. """
for colitem in self._memocollect(): for colitem in self._memocollect():
if colitem.name == name: if colitem.name == name:
return colitem return colitem

View File

@ -2,9 +2,6 @@ import py
import sys import sys
pytest_plugins = "pytester", pytest_plugins = "pytester",
collect_ignore = ['../build', '../doc/_build']
rsyncdirs = ['conftest.py', '../pytest', '../doc', '.']
import os, py import os, py
pid = os.getpid() pid = os.getpid()

View File

@ -1,6 +1,5 @@
import py import py
pytest_plugins = "pytester"
import pytest.plugin import pytest.plugin
plugindir = py.path.local(pytest.plugin.__file__).dirpath() plugindir = py.path.local(pytest.plugin.__file__).dirpath()
from pytest._core import default_plugins from pytest._core import default_plugins

View File

@ -15,8 +15,8 @@ def test_help(testdir):
assert result.ret == 0 assert result.ret == 0
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines([
"*-v*verbose*", "*-v*verbose*",
"*settings*", "*setup.cfg*",
"*conftest.py*", "*minversion*",
]) ])
def test_collectattr(): def test_collectattr():

View File

@ -37,7 +37,6 @@ class TestParseIni:
]) ])
class TestConfigCmdlineParsing: class TestConfigCmdlineParsing:
def test_parsing_again_fails(self, testdir): def test_parsing_again_fails(self, testdir):
config = testdir.reparseconfig([testdir.tmpdir]) config = testdir.reparseconfig([testdir.tmpdir])
py.test.raises(AssertionError, "config.parse([])") py.test.raises(AssertionError, "config.parse([])")
@ -108,13 +107,43 @@ class TestConfigAPI:
p = tmpdir.join("conftest.py") p = tmpdir.join("conftest.py")
p.write("pathlist = ['.', %r]" % str(somepath)) p.write("pathlist = ['.', %r]" % str(somepath))
config = testdir.reparseconfig([p]) config = testdir.reparseconfig([p])
assert config.getconftest_pathlist('notexist') is None assert config._getconftest_pathlist('notexist') is None
pl = config.getconftest_pathlist('pathlist') pl = config._getconftest_pathlist('pathlist')
print(pl) print(pl)
assert len(pl) == 2 assert len(pl) == 2
assert pl[0] == tmpdir assert pl[0] == tmpdir
assert pl[1] == somepath assert pl[1] == somepath
def test_addini(self, testdir):
testdir.makeconftest("""
def pytest_addoption(parser):
parser.addini("myname", "my new ini value")
""")
testdir.makeini("""
[pytest]
myname=hello
""")
config = testdir.parseconfig()
val = config.getini("myname")
assert val == "hello"
py.test.raises(ValueError, config.getini, 'other')
def test_addini_pathlist(self, testdir):
testdir.makeconftest("""
def pytest_addoption(parser):
parser.addini("paths", "my new ini value", type="pathlist")
parser.addini("abc", "abc value")
""")
p = testdir.makeini("""
[pytest]
paths=hello world/sub.py
""")
config = testdir.parseconfig()
l = config.getini("paths")
assert len(l) == 2
assert l[0] == p.dirpath('hello')
assert l[1] == p.dirpath('world/sub.py')
py.test.raises(ValueError, config.getini, 'other')
def test_options_on_small_file_do_not_blow_up(testdir): def test_options_on_small_file_do_not_blow_up(testdir):
def runfiletest(opts): def runfiletest(opts):
@ -151,3 +180,4 @@ def test_preparse_ordering(testdir, monkeypatch):
config = testdir.parseconfig() config = testdir.parseconfig()
plugin = config.pluginmanager.getplugin("mytestplugin") plugin = config.pluginmanager.getplugin("mytestplugin")
assert plugin.x == 42 assert plugin.x == 42

View File

@ -101,17 +101,6 @@ class TestParser:
assert option.hello == "world" assert option.hello == "world"
assert option.this == 42 assert option.this == 42
def test_parser_addini(self, tmpdir):
parser = parseopt.Parser()
parser.addini("myname", "my new ini value")
cfg = py.iniconfig.IniConfig("tox.ini", dedent("""
[pytest]
myname=hello
"""))['pytest']
class option:
pass
parser.setfromini(cfg, option)
assert option.myname == "hello"
@py.test.mark.skipif("sys.version_info < (2,5)") @py.test.mark.skipif("sys.version_info < (2,5)")
def test_addoption_parser_epilog(testdir): def test_addoption_parser_epilog(testdir):

View File

@ -51,3 +51,4 @@ commands=
[pytest] [pytest]
minversion=2.0 minversion=2.0
plugins=pytester