introduce reading of setup.cfg / ini-style configuration files

rename internal config.Error to pytest.UsageError

--HG--
branch : trunk
This commit is contained in:
holger krekel 2010-10-27 19:35:27 +02:00
parent f7b4f70a16
commit b86b1628bb
11 changed files with 144 additions and 14 deletions

View File

@ -1,7 +1,9 @@
Changes between 1.3.4 and 2.0.0dev0
----------------------------------------------
- pytest-2.0 is now its own package and depends on pylib
- pytest-2.0 is now its own package and depends on pylib-2.0
- introduce a new way to set config options via ini-style files,
by default setup.cfg and tox.ini files are searched.
- fix issue126 - introduce py.test.set_trace() to trace execution via
PDB during the running of tests even if capturing is ongoing.
- fix issue123 - new "python -m py.test" invocation for py.test

View File

@ -258,3 +258,9 @@ epub_copyright = u'2010, holger krekel et aliter'
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {'http://docs.python.org/': None}
def setup(app):
#from sphinx.ext.autodoc import cut_lines
#app.connect('autodoc-process-docstring', cut_lines(4, what=['module']))
app.add_description_unit('confval', 'confval',
objname='configuration value',
indextemplate='pair: %s; configuration value')

View File

@ -15,6 +15,26 @@ You can see command line options by running::
This will display all available command line options
in your specific environment.
reading test configuration from ini-files
--------------------------------------------------------
py.test tries to find a configuration INI format file, trying
to find a section ``[pytest]`` in a ``tox.ini`` (or XXX ``pytest.ini`` file).
Possible entries in a ``[pytest]`` section are:
.. confval:: minversion = VERSTRING
specifies the minimal pytest version that is needed for this test suite.
minversion = 2.1 # will fail if we run with pytest-2.0
.. confval:: appendargs = OPTS
append the specified ``OPTS`` to the command line arguments as if they
had been specified by the user. Example::
appendargs = --maxfail=2 -rf # exit after 2 failures, report fail info
setting persistent option defaults
------------------------------------
@ -22,6 +42,7 @@ setting persistent option defaults
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
@ -86,6 +107,7 @@ in your home directory to provide global configuration values.
.. _`named plugins`: plugin/index.html
Plugin discovery at tool startup
--------------------------------------------

View File

@ -10,6 +10,7 @@ __version__ = '2.0.0.dev10'
__all__ = ['config', 'cmdline']
from pytest import _core as cmdline
UsageError = cmdline.UsageError
def __main__():
raise SystemExit(cmdline.main())
raise SystemExit(cmdline.main())

View File

@ -345,13 +345,16 @@ def main(args=None):
if args is None:
args = sys.argv[1:]
hook = pluginmanager.hook
config = hook.pytest_cmdline_parse(pluginmanager=pluginmanager, args=args)
try:
config = hook.pytest_cmdline_parse(
pluginmanager=pluginmanager, args=args)
exitstatus = hook.pytest_cmdline_main(config=config)
except config.Error:
except UsageError:
e = sys.exc_info()[1]
sys.stderr.write("ERROR: %s\n" %(e.args[0],))
exitstatus = 3
pluginmanager = PluginManager(load=True)
return exitstatus
class UsageError(Exception):
""" error in py.test usage or invocation"""

View File

@ -2,6 +2,7 @@
import py
import sys, os
from pytest._core import PluginManager
import pytest
def pytest_cmdline_parse(pluginmanager, args):
@ -226,13 +227,9 @@ class CmdOptions(object):
def __repr__(self):
return "<CmdOptions %r>" %(self.__dict__,)
class Error(Exception):
""" Test Configuration Error. """
class Config(object):
""" access to configuration values, pluginmanager and plugin hooks. """
Option = py.std.optparse.Option
Error = Error
basetemp = None
def __init__(self, pluginmanager=None):
@ -280,7 +277,10 @@ class Config(object):
try:
opt.default = self._conftest.rget(name)
except (ValueError, KeyError):
pass
try:
opt.default = self.inicfg[opt.dest]
except KeyError:
pass
if not hasattr(self.option, opt.dest):
setattr(self.option, opt.dest, opt.default)
@ -299,12 +299,25 @@ class Config(object):
raise
def _preparse(self, args):
self.inicfg = getcfg(args, ["setup.cfg", "tox.ini",])
self._checkversion()
self.pluginmanager.consider_setuptools_entrypoints()
self.pluginmanager.consider_env()
self.pluginmanager.consider_preparse(args)
self._setinitialconftest(args)
self.pluginmanager.do_addoption(self._parser)
def _checkversion(self):
minver = self.inicfg.get('minversion', None)
if minver:
ver = minver.split(".")
myver = pytest.__version__.split(".")
if myver < ver:
raise pytest.UsageError(
"%s:%d: requires pytest-%s, actual pytest-%s'" %(
self.inicfg.config.path, self.inicfg.lineof('minversion'),
minver, pytest.__version__))
def parse(self, args):
# cmdline arguments into this config object.
# Note that this can only be called once per testing process.
@ -312,6 +325,10 @@ class Config(object):
"can only parse cmdline args at most once per Config object")
self._preparse(args)
self._parser.hints.extend(self.pluginmanager._hints)
if self.inicfg:
newargs = self.inicfg.get("appendargs", None)
if newargs:
args += py.std.shlex.split(newargs)
args = self._parser.parse_setoption(args, self.option)
if not args:
args.append(py.std.os.getcwd())
@ -381,3 +398,26 @@ class Config(object):
except AttributeError:
return self._conftest.rget(name, path)
def getcfg(args, inibasenames):
if not args:
args = [py.path.local()]
for inibasename in inibasenames:
for p in args:
x = findupwards(p, inibasename)
if x is not None:
iniconfig = py.iniconfig.IniConfig(x)
if 'pytest' in iniconfig.sections:
return iniconfig['pytest']
return {}
def findupwards(current, basename):
current = py.path.local(current)
while 1:
p = current.join(basename)
if p.check():
return p
p = current.dirpath()
if p == current:
return
current = p

View File

@ -128,7 +128,7 @@ class Session(object):
config.hook.pytest_sessionstart(session=self)
config.hook.pytest_perform_collection(session=self)
config.hook.pytest_runtest_mainloop(session=self)
except self.config.Error:
except pytest.UsageError:
raise
except KeyboardInterrupt:
excinfo = py.code.ExceptionInfo()
@ -173,10 +173,10 @@ class Collection:
parts = str(arg).split("::")
path = base.join(parts[0], abs=True)
if not path.check():
raise self.config.Error("file not found: %s" %(path,))
raise pytest.UsageError("file not found: %s" %(path,))
topdir = self.topdir
if path != topdir and not path.relto(topdir):
raise self.config.Error("path %r is not relative to %r" %
raise pytest.UsageError("path %r is not relative to %r" %
(str(path), str(topdir)))
topparts = path.relto(topdir).split(path.sep)
return topparts + parts[1:]
@ -213,7 +213,7 @@ class Collection:
for node in self.matchnodes([self._topcollector], names):
items.extend(self.genitems(node))
except NoMatch:
raise self.config.Error("can't collect: %s" % (arg,))
raise pytest.UsageError("can't collect: %s" % (arg,))
return items
def matchnodes(self, matching, names):

View File

@ -4,7 +4,8 @@ class TestGeneralUsage:
def test_config_error(self, testdir):
testdir.makeconftest("""
def pytest_configure(config):
raise config.Error("hello")
import pytest
raise pytest.UsageError("hello")
""")
result = testdir.runpytest(testdir.tmpdir)
assert result.ret != 0

View File

@ -41,6 +41,12 @@ def pytest_unconfigure(config, __multicall__):
assert len2 < config._numfiles + 7, out2
def pytest_runtest_setup(item):
item._oldir = py.path.local()
def pytest_runtest_teardown(item):
item._oldir.chdir()
def pytest_generate_tests(metafunc):
multi = getattr(metafunc.function, 'multi', None)
if multi is not None:

View File

@ -1,9 +1,55 @@
import py
from pytest.plugin.config import getcfg, Config
class TestParseIni:
def test_getcfg_and_config(self, tmpdir):
sub = tmpdir.mkdir("sub")
sub.chdir()
tmpdir.join("setup.cfg").write(py.code.Source("""
[pytest]
name = value
"""))
cfg = getcfg([sub], ["setup.cfg"])
assert cfg['name'] == "value"
config = Config()
config._preparse([sub])
assert config.inicfg['name'] == 'value'
def test_getvalue(self, tmpdir):
tmpdir.join("setup.cfg").write(py.code.Source("""
[pytest]
verbose = True
"""))
config = Config()
config._preparse([tmpdir])
assert config.option.verbose
def test_append_parse_args(self, tmpdir):
tmpdir.join("setup.cfg").write(py.code.Source("""
[pytest]
appendargs = --verbose
"""))
config = Config()
config.parse([tmpdir])
assert config.option.verbose
def test_tox_ini_wrong_version(self, testdir):
p = testdir.makefile('.ini', tox="""
[pytest]
minversion=9.0
""")
result = testdir.runpytest()
assert result.ret != 0
result.stderr.fnmatch_lines([
"*tox.ini:2*requires*9.0*actual*"
])
class TestConfigCmdlineParsing:
def test_parser_addoption_default_env(self, testdir, monkeypatch):
import os
config = testdir.Config()
config._preparse([testdir.tmpdir])
group = config._parser.getgroup("hello")
monkeypatch.setitem(os.environ, 'PYTEST_OPTION_OPTION1', 'True')

View File

@ -48,3 +48,6 @@ changedir=testing
commands=
{envpython} {envbindir}/py.test-jython --no-tools-on-path \
-rfsxX --junitxml={envlogdir}/junit-{envname}2.xml [acceptance_test.py plugin]
[pytest]
minversion=2.0