diff --git a/CHANGELOG b/CHANGELOG index 76121a175..fc94aa771 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -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 diff --git a/doc/conf.py b/doc/conf.py index f1bc56f46..a6c82a057 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -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') diff --git a/doc/customize.txt b/doc/customize.txt index 91404806f..d4eb82bc7 100644 --- a/doc/customize.txt +++ b/doc/customize.txt @@ -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 -------------------------------------------- diff --git a/pytest/__init__.py b/pytest/__init__.py index afc40b3f1..98e67dd94 100644 --- a/pytest/__init__.py +++ b/pytest/__init__.py @@ -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()) \ No newline at end of file + raise SystemExit(cmdline.main()) diff --git a/pytest/_core.py b/pytest/_core.py index d28ab2643..4022af993 100644 --- a/pytest/_core.py +++ b/pytest/_core.py @@ -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""" diff --git a/pytest/plugin/config.py b/pytest/plugin/config.py index 619e84983..502ed7bdd 100644 --- a/pytest/plugin/config.py +++ b/pytest/plugin/config.py @@ -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 "" %(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 + diff --git a/pytest/plugin/session.py b/pytest/plugin/session.py index c4699f788..95c24ef98 100644 --- a/pytest/plugin/session.py +++ b/pytest/plugin/session.py @@ -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): diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index ca5d3d991..45d959fb2 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -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 diff --git a/testing/conftest.py b/testing/conftest.py index 45c0d6eb2..638c37fcc 100644 --- a/testing/conftest.py +++ b/testing/conftest.py @@ -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: diff --git a/testing/test_config.py b/testing/test_config.py index 23d347dfa..1edd8ad0e 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -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') diff --git a/tox.ini b/tox.ini index 982d61158..62b4b4905 100644 --- a/tox.ini +++ b/tox.ini @@ -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