introduce reading of setup.cfg / ini-style configuration files
rename internal config.Error to pytest.UsageError --HG-- branch : trunk
This commit is contained in:
parent
f7b4f70a16
commit
b86b1628bb
|
@ -1,7 +1,9 @@
|
||||||
Changes between 1.3.4 and 2.0.0dev0
|
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
|
- fix issue126 - introduce py.test.set_trace() to trace execution via
|
||||||
PDB during the running of tests even if capturing is ongoing.
|
PDB during the running of tests even if capturing is ongoing.
|
||||||
- fix issue123 - new "python -m py.test" invocation for py.test
|
- fix issue123 - new "python -m py.test" invocation for py.test
|
||||||
|
|
|
@ -258,3 +258,9 @@ epub_copyright = u'2010, holger krekel et aliter'
|
||||||
|
|
||||||
# Example configuration for intersphinx: refer to the Python standard library.
|
# Example configuration for intersphinx: refer to the Python standard library.
|
||||||
intersphinx_mapping = {'http://docs.python.org/': None}
|
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')
|
||||||
|
|
|
@ -15,6 +15,26 @@ You can see command line options by running::
|
||||||
This will display all available command line options
|
This will display all available command line options
|
||||||
in your specific environment.
|
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
|
setting persistent option defaults
|
||||||
------------------------------------
|
------------------------------------
|
||||||
|
@ -22,6 +42,7 @@ setting persistent option defaults
|
||||||
py.test will lookup option values in this order:
|
py.test will lookup option values in this order:
|
||||||
|
|
||||||
* command line
|
* command line
|
||||||
|
* ``[pytest]`` section in upwards ``setup.cfg`` or ``tox.ini`` files.
|
||||||
* conftest.py files
|
* conftest.py files
|
||||||
* environment variables
|
* environment variables
|
||||||
|
|
||||||
|
@ -86,6 +107,7 @@ in your home directory to provide global configuration values.
|
||||||
|
|
||||||
.. _`named plugins`: plugin/index.html
|
.. _`named plugins`: plugin/index.html
|
||||||
|
|
||||||
|
|
||||||
Plugin discovery at tool startup
|
Plugin discovery at tool startup
|
||||||
--------------------------------------------
|
--------------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ __version__ = '2.0.0.dev10'
|
||||||
__all__ = ['config', 'cmdline']
|
__all__ = ['config', 'cmdline']
|
||||||
|
|
||||||
from pytest import _core as cmdline
|
from pytest import _core as cmdline
|
||||||
|
UsageError = cmdline.UsageError
|
||||||
|
|
||||||
def __main__():
|
def __main__():
|
||||||
raise SystemExit(cmdline.main())
|
raise SystemExit(cmdline.main())
|
||||||
|
|
|
@ -345,13 +345,16 @@ def main(args=None):
|
||||||
if args is None:
|
if args is None:
|
||||||
args = sys.argv[1:]
|
args = sys.argv[1:]
|
||||||
hook = pluginmanager.hook
|
hook = pluginmanager.hook
|
||||||
config = hook.pytest_cmdline_parse(pluginmanager=pluginmanager, args=args)
|
|
||||||
try:
|
try:
|
||||||
|
config = hook.pytest_cmdline_parse(
|
||||||
|
pluginmanager=pluginmanager, args=args)
|
||||||
exitstatus = hook.pytest_cmdline_main(config=config)
|
exitstatus = hook.pytest_cmdline_main(config=config)
|
||||||
except config.Error:
|
except UsageError:
|
||||||
e = sys.exc_info()[1]
|
e = sys.exc_info()[1]
|
||||||
sys.stderr.write("ERROR: %s\n" %(e.args[0],))
|
sys.stderr.write("ERROR: %s\n" %(e.args[0],))
|
||||||
exitstatus = 3
|
exitstatus = 3
|
||||||
pluginmanager = PluginManager(load=True)
|
pluginmanager = PluginManager(load=True)
|
||||||
return exitstatus
|
return exitstatus
|
||||||
|
|
||||||
|
class UsageError(Exception):
|
||||||
|
""" error in py.test usage or invocation"""
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import py
|
import py
|
||||||
import sys, os
|
import sys, os
|
||||||
from pytest._core import PluginManager
|
from pytest._core import PluginManager
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
def pytest_cmdline_parse(pluginmanager, args):
|
def pytest_cmdline_parse(pluginmanager, args):
|
||||||
|
@ -226,13 +227,9 @@ class CmdOptions(object):
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<CmdOptions %r>" %(self.__dict__,)
|
return "<CmdOptions %r>" %(self.__dict__,)
|
||||||
|
|
||||||
class Error(Exception):
|
|
||||||
""" Test Configuration Error. """
|
|
||||||
|
|
||||||
class Config(object):
|
class Config(object):
|
||||||
""" access to configuration values, pluginmanager and plugin hooks. """
|
""" access to configuration values, pluginmanager and plugin hooks. """
|
||||||
Option = py.std.optparse.Option
|
Option = py.std.optparse.Option
|
||||||
Error = Error
|
|
||||||
basetemp = None
|
basetemp = None
|
||||||
|
|
||||||
def __init__(self, pluginmanager=None):
|
def __init__(self, pluginmanager=None):
|
||||||
|
@ -280,7 +277,10 @@ class Config(object):
|
||||||
try:
|
try:
|
||||||
opt.default = self._conftest.rget(name)
|
opt.default = self._conftest.rget(name)
|
||||||
except (ValueError, KeyError):
|
except (ValueError, KeyError):
|
||||||
pass
|
try:
|
||||||
|
opt.default = self.inicfg[opt.dest]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
if not hasattr(self.option, opt.dest):
|
if not hasattr(self.option, opt.dest):
|
||||||
setattr(self.option, opt.dest, opt.default)
|
setattr(self.option, opt.dest, opt.default)
|
||||||
|
|
||||||
|
@ -299,12 +299,25 @@ class Config(object):
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def _preparse(self, args):
|
def _preparse(self, args):
|
||||||
|
self.inicfg = getcfg(args, ["setup.cfg", "tox.ini",])
|
||||||
|
self._checkversion()
|
||||||
self.pluginmanager.consider_setuptools_entrypoints()
|
self.pluginmanager.consider_setuptools_entrypoints()
|
||||||
self.pluginmanager.consider_env()
|
self.pluginmanager.consider_env()
|
||||||
self.pluginmanager.consider_preparse(args)
|
self.pluginmanager.consider_preparse(args)
|
||||||
self._setinitialconftest(args)
|
self._setinitialconftest(args)
|
||||||
self.pluginmanager.do_addoption(self._parser)
|
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):
|
def parse(self, args):
|
||||||
# cmdline arguments into this config object.
|
# cmdline arguments into this config object.
|
||||||
# Note that this can only be called once per testing process.
|
# 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")
|
"can only parse cmdline args at most once per Config object")
|
||||||
self._preparse(args)
|
self._preparse(args)
|
||||||
self._parser.hints.extend(self.pluginmanager._hints)
|
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)
|
args = self._parser.parse_setoption(args, self.option)
|
||||||
if not args:
|
if not args:
|
||||||
args.append(py.std.os.getcwd())
|
args.append(py.std.os.getcwd())
|
||||||
|
@ -381,3 +398,26 @@ class Config(object):
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return self._conftest.rget(name, path)
|
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
|
||||||
|
|
||||||
|
|
|
@ -128,7 +128,7 @@ class Session(object):
|
||||||
config.hook.pytest_sessionstart(session=self)
|
config.hook.pytest_sessionstart(session=self)
|
||||||
config.hook.pytest_perform_collection(session=self)
|
config.hook.pytest_perform_collection(session=self)
|
||||||
config.hook.pytest_runtest_mainloop(session=self)
|
config.hook.pytest_runtest_mainloop(session=self)
|
||||||
except self.config.Error:
|
except pytest.UsageError:
|
||||||
raise
|
raise
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
excinfo = py.code.ExceptionInfo()
|
excinfo = py.code.ExceptionInfo()
|
||||||
|
@ -173,10 +173,10 @@ class Collection:
|
||||||
parts = str(arg).split("::")
|
parts = str(arg).split("::")
|
||||||
path = base.join(parts[0], abs=True)
|
path = base.join(parts[0], abs=True)
|
||||||
if not path.check():
|
if not path.check():
|
||||||
raise self.config.Error("file not found: %s" %(path,))
|
raise pytest.UsageError("file not found: %s" %(path,))
|
||||||
topdir = self.topdir
|
topdir = self.topdir
|
||||||
if path != topdir and not path.relto(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)))
|
(str(path), str(topdir)))
|
||||||
topparts = path.relto(topdir).split(path.sep)
|
topparts = path.relto(topdir).split(path.sep)
|
||||||
return topparts + parts[1:]
|
return topparts + parts[1:]
|
||||||
|
@ -213,7 +213,7 @@ class Collection:
|
||||||
for node in self.matchnodes([self._topcollector], names):
|
for node in self.matchnodes([self._topcollector], names):
|
||||||
items.extend(self.genitems(node))
|
items.extend(self.genitems(node))
|
||||||
except NoMatch:
|
except NoMatch:
|
||||||
raise self.config.Error("can't collect: %s" % (arg,))
|
raise pytest.UsageError("can't collect: %s" % (arg,))
|
||||||
return items
|
return items
|
||||||
|
|
||||||
def matchnodes(self, matching, names):
|
def matchnodes(self, matching, names):
|
||||||
|
|
|
@ -4,7 +4,8 @@ class TestGeneralUsage:
|
||||||
def test_config_error(self, testdir):
|
def test_config_error(self, testdir):
|
||||||
testdir.makeconftest("""
|
testdir.makeconftest("""
|
||||||
def pytest_configure(config):
|
def pytest_configure(config):
|
||||||
raise config.Error("hello")
|
import pytest
|
||||||
|
raise pytest.UsageError("hello")
|
||||||
""")
|
""")
|
||||||
result = testdir.runpytest(testdir.tmpdir)
|
result = testdir.runpytest(testdir.tmpdir)
|
||||||
assert result.ret != 0
|
assert result.ret != 0
|
||||||
|
|
|
@ -41,6 +41,12 @@ def pytest_unconfigure(config, __multicall__):
|
||||||
assert len2 < config._numfiles + 7, out2
|
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):
|
def pytest_generate_tests(metafunc):
|
||||||
multi = getattr(metafunc.function, 'multi', None)
|
multi = getattr(metafunc.function, 'multi', None)
|
||||||
if multi is not None:
|
if multi is not None:
|
||||||
|
|
|
@ -1,9 +1,55 @@
|
||||||
import py
|
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:
|
class TestConfigCmdlineParsing:
|
||||||
def test_parser_addoption_default_env(self, testdir, monkeypatch):
|
def test_parser_addoption_default_env(self, testdir, monkeypatch):
|
||||||
import os
|
import os
|
||||||
config = testdir.Config()
|
config = testdir.Config()
|
||||||
|
config._preparse([testdir.tmpdir])
|
||||||
group = config._parser.getgroup("hello")
|
group = config._parser.getgroup("hello")
|
||||||
|
|
||||||
monkeypatch.setitem(os.environ, 'PYTEST_OPTION_OPTION1', 'True')
|
monkeypatch.setitem(os.environ, 'PYTEST_OPTION_OPTION1', 'True')
|
||||||
|
|
3
tox.ini
3
tox.ini
|
@ -48,3 +48,6 @@ changedir=testing
|
||||||
commands=
|
commands=
|
||||||
{envpython} {envbindir}/py.test-jython --no-tools-on-path \
|
{envpython} {envbindir}/py.test-jython --no-tools-on-path \
|
||||||
-rfsxX --junitxml={envlogdir}/junit-{envname}2.xml [acceptance_test.py plugin]
|
-rfsxX --junitxml={envlogdir}/junit-{envname}2.xml [acceptance_test.py plugin]
|
||||||
|
|
||||||
|
[pytest]
|
||||||
|
minversion=2.0
|
||||||
|
|
Loading…
Reference in New Issue