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 | ||||
| ---------------------------------------------- | ||||
| 
 | ||||
| - 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 | ||||
|  |  | |||
|  | @ -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') | ||||
|  |  | |||
|  | @ -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 | ||||
| -------------------------------------------- | ||||
| 
 | ||||
|  |  | |||
|  | @ -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()) | ||||
|  |  | |||
|  | @ -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""" | ||||
|  |  | |||
|  | @ -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 | ||||
| 
 | ||||
|  |  | |||
|  | @ -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): | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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: | ||||
|  |  | |||
|  | @ -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') | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue