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